-
Notifications
You must be signed in to change notification settings - Fork 61
Conversation
644abf9
to
69c9791
Compare
Codecov Report
@@ Coverage Diff @@
## master #1258 +/- ##
=========================================
Coverage ? 79.38%
=========================================
Files ? 171
Lines ? 10211
Branches ? 0
=========================================
Hits ? 8106
Misses ? 2105
Partials ? 0
Continue to review full report at Codecov.
|
69c9791
to
5a1b54f
Compare
src/libaktualizr/uptane/tuf.cc
Outdated
|
||
for (auto it = t.ecus_.begin(); it != t.ecus_.end(); ++it) { | ||
os << it->first; | ||
for (auto it = t.ecus_.cbegin(); it != t.ecus_.cend(); ++it) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All these loops could use simpler for range loops:
for (const auto &ecu : t.ecus) {
...
}
src/libaktualizr/uptane/tuf.h
Outdated
if (filename_ != t2.filename_) { | ||
return false; | ||
} | ||
if (length_ != t2.length_) { | ||
return false; | ||
} | ||
|
||
// If the HWID vector and ECU->HWID map match, we're good. Otherwise, assume |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's maybe a naive question, but maybe it's the time where it's too much to have all this logic in an overloaded comparison operator and instead have a dedicated function? The potential for misuse and confusion seems big.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you prefer to have operator==
call that function, or to reserve operator==
only for exact matches and use the new match function for Uptane purposes? I'm hoping the former, because otherwise I'd worry about accidentally doing the wrong thing (using equality instead of the matching function).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagined that ==
would not call that but then you're probably right. In this case it would be better to delete the comparison operator and only keep the method.
But I'm ready to debate that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deleting the comparison operator is an interesting idea, but at that point, aren't we back to just having the same thing, just named differently? What's the benefit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a matter of style it can be surprising to have this complicated business logic inside this operator with a benign name. And the objects are not really "equal", they "match" in some application sense, so I think it's stretching the sense a bit. Also, it makes it hard to grep for usage of this comparison.
It's why I would prefer this to have a scary name but maybe it's not really feasible or we can argue that we contain the usage effectively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your points. I think if we delete the == operator then it's probably a reasonable way to go.
src/libaktualizr/uptane/tuf_test.cc
Outdated
ecu_map.insert({Uptane::EcuSerial("serial"), hwid}); | ||
Uptane::Target target1("abc", generateDirectorTarget("hash_good", 739, ecu_map)); | ||
Uptane::Target target2("abc", generateImagesTarget("hash_good", 739, hardwareIds)); | ||
EXPECT_TRUE(target1 == target2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also test target2 == target1
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not a bad idea. They should be identical, but it would help prevent potential mistakes to future edits to the Target class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I would be more comfortable if all the tests checked the reflexivity.
@@ -216,7 +216,8 @@ void INvStorage::FSSToSQLS(FSStorageRead& fs_storage, SQLStorage& sql_storage) { | |||
} | |||
|
|||
bool INvStorage::fsReadInstalledVersions(const boost::filesystem::path& filename, | |||
std::vector<Uptane::Target>* installed_versions, size_t* current_version) { | |||
std::vector<Uptane::Target>* installed_versions, size_t* current_version, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it intentional that importInstalledVersions
was not changed to adopt these changes? It's a code part that should get more hits than FSStorageRead::loadInstalledVersions
which is only used for legacy migrations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorta. This change was a bit of a hack to get tests to work. importInstalledVersions
should only run on a primary, and there is already logic to assume it's for the primary when reading the installation versions in the function that converts FS to SQL storage. However, the tests had to do some fakery to test that effectively, so I had to force the serial/hwid. Would you prefer that importInstalledVersions
also used it? In that case, I probably wouldn't need the parameters to be optional; I think I could make them a requirement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a bit annoying to have this almost dead code to make the tests work indeed. Then it should maybe explained somewhere with a good comment (maybe I've missed it?), as I found it a bit confusing in this state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no comment at present. The other option is to do some more hackery in the test itself, which is maybe safer, but makes the test slightly less meaningful. It's kind of ugly no matter how you slice it, but I'm open to advice.
boost::filesystem::path repo_dir(path_ / "repo/image"); | ||
|
||
boost::filesystem::path targets_path = | ||
delegation ? ((repo_dir / "delegations") / delegation.name).string() + ".json" : repo_dir / "targets.json"; | ||
Json::Value targets = Utils::parseJSONFile(targets_path)["signed"]; | ||
// TODO: support multiple hardware IDs. | ||
target["custom"]["hardwareIds"][0] = hardware_id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am just wondering why it's in "custom" section which sounds like a bucket for optional params while hardware-ID looks like a mandatory param.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't really tell you, but that's how Uptane specifies it. See https://uptane.github.io/uptane-standard/uptane-standard.html#custom-metadata-about-images. I think the point is that it is not a hard requirement, but rather a SHOULD. If we were to want to change it, first we'd have to convince the backend team, but I think it's fine as is, even though it is admittedly confusing.
std::string hwid = vm["hwid"].as<std::string>(); | ||
std::string serial = vm["serial"].as<std::string>(); | ||
const std::string targetname = vm["targetname"].as<std::string>(); | ||
const std::string hwid = vm["hwid"].as<std::string>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. However, they are two different subcommands of aktualizr-repo, and other subcommands don't require it, so the redundancy is probably the cleanest solution.
@@ -26,7 +26,8 @@ TEST(PackageManagerFake, FinalizeAfterReboot) { | |||
|
|||
PackageManagerFake fakepm(config.pacman, storage, bootloader, nullptr); | |||
|
|||
Uptane::Target target("pkg", {Uptane::Hash(Uptane::Hash::Type::kSha256, "hash")}, 1, ""); | |||
std::map<Uptane::EcuSerial, Uptane::HardwareIdentifier> primary_ecu; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not exactly related to this specific PR rather to the code base in general - this map can be found in many places of the code and effectively represents an ID of ECU, just thinking if it makes sense to define corresponding types, e.g. class ECU { class/using/typedef ID .....
so it can be referenced as Uptane::ECU::ID ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a bad thought. I'll look into it.
src/libaktualizr/uptane/tuf.cc
Outdated
@@ -209,6 +215,61 @@ bool Target::IsOstree() const { | |||
} | |||
} | |||
|
|||
bool Target::operator==(const Target &t2) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At first glance, it's confusing that a single class represents two types of Targets that require different context/class members, as well as their comparison.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is an interesting point. @lbonn what would you think of having two children classes, one for DirectorTarget and one for ImagesTarget? Almost all of the logic would still be in the parent class, but the ecu map and hwid vectors would be in the respective children, and the match function would probably have to be a static function outside of the classes. I think that would help make things much, much more explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, this is a pretty huge change and will require changing the API. I'm deep enough in this task already that I think I'd rather wait on that work and come back to it another time.
} else { | ||
return false; | ||
} | ||
for (auto map_it = ecu_map->cbegin(); map_it != ecu_map->cend(); ++map_it) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am rather trying to understand a design & control flow here than comment the code. What's the point to specify ECU serial at all if the solution will update all ECUs that have HW-ID specified in the target metadata. At least it's how it works for secondaries, I ask to update just one ECU but the solution updates all ECUs of the given type/HW-ID.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That logic (of selecting all secondaries with the same HWID) is on the server-side. I'd prefer to not rely on that in libaktualizr. Besides, we still have to distinguish the primary. It's also a safety check: we need to confirm that the ECU has the HWID we expect it to (which is checked elsewhere).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, policies are defined at the backend and can be even account/user specific, a a client/aktualizr does exactly what Director says without applying any policies.
src/libaktualizr/uptane/tuf.h
Outdated
Json::Value toDebugJson() const; | ||
friend std::ostream &operator<<(std::ostream &os, const Target &t); | ||
|
||
private: | ||
bool valid{true}; | ||
std::string filename_; | ||
std::string type_; | ||
std::map<EcuSerial, HardwareIdentifier> ecus_; | ||
std::map<EcuSerial, HardwareIdentifier> ecus_; // Director only |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I meant in one of the earlier comment, a field is used just for a specific type of Target instances.
907e488
to
3c3c64c
Compare
Signed-off-by: Patrick Vacek <[email protected]>
Signed-off-by: Patrick Vacek <[email protected]>
Added a check for HW IDs. The Director sends a map of serials to HWIDs, but the Image repo sends us a vector of HWIDs. If the map and vector match, the comparison succeeds. Otherwise, we expect that every HWID in the map can be found in the vector. Added tests for all of these cases. Also added explanations for the rest of the fields we use for why we don't check them. Signed-off-by: Patrick Vacek <[email protected]>
Mostly just better error handling and messaging for Target mismatches. Signed-off-by: Patrick Vacek <[email protected]>
3c3c64c
to
4620480
Compare
This required adding functionality to aktualizr-repo to add HWIDs to the image repo. Signed-off-by: Patrick Vacek <[email protected]>
Signed-off-by: Patrick Vacek <[email protected]>
Signed-off-by: Patrick Vacek <[email protected]>
Signed-off-by: Patrick Vacek <[email protected]>
Fixes OTA-2706 (Remove tests/test_data/fake_root). Also fixes the last broken test due to the Target matching changes. Signed-off-by: Patrick Vacek <[email protected]>
4620480
to
38537a4
Compare
This should make Target matching more explicit (and easier to grep for). The == operator is not usually meaningful or desirable for Target objects. Signed-off-by: Patrick Vacek <[email protected]>
Signed-off-by: Patrick Vacek <[email protected]>
I believe I've finally addressed all review comments except for making Director/Image-specific children of the Target class. I looked into it and it is way too much work to do as part of this PR. It's not a bad idea, but that's for some other time. |
Looks like a lot of changes, but almost all of it is just adapting existing tests to handle one big new change: hardware IDs must now match between Director and Image repo Target objects. This can be seen in tuf.cc.
Details: The Director sends a map of serials to HWIDs, but the Image repo sends us a vector of HWIDs. If the map and vector match, the comparison succeeds. Otherwise, we expect that every HWID in the map can be found in the vector. Added tests for all of these cases. Added explanations for the rest of the fields we use for why we don't check them. Added a requirement to aktualizr-repo that the "image" command must have a hardware ID so that we can properly populate the metadata.
This PR depends on advancedtelematic/tuf-test-vectors#56.