-
Notifications
You must be signed in to change notification settings - Fork 726
Add check for available disk space on launch #1854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
714133e
to
d460bd3
Compare
d460bd3
to
88618b0
Compare
Hi @katie-knister, unfortunately to be able to support older systems (like earlier macOS versions), we can't be on the edge of C++, where For our cross-platform needs, we decided on Qt to abstract the underlying system where possible, and |
@katie-knister I also had a look at your code and saw that if the user did not pass a disk size, the default would be used, and that wouldn't be checked for available space. A better place to put this check would be here: multipass/src/daemon/daemon.cpp Lines 609 to 632 in 88618b0
|
…efault disk sizes as well as user-defined disk sizes. Make check use <QStorageInfo> instead of <filesystem> for increased compatability.
bdafe35
to
dec8403
Compare
dec8403
to
c4406b0
Compare
Hi @Saviq, thanks for the suggestions! I have updated the PR to change where the check is done and use QStorageInfo instead. The build now succeeds and passes 1238/1239 tests in BuildAndTest (Coverage). It fails DNSMasqServer.dnsmasq_fails_and_throws, where a runtime_error is expected but nothing is actually thrown. This is weird because the test seems unrelated to what I changed. Since my change is pretty straightforward and doesn’t involve suppressing any exceptions, I’m unsure what could have caused this, and am wondering if you have any ideas of how to resolve/debug this. |
Hi @katie-knister , yeah this test keeps coming back to bite us, I don't think it's related to your changes. The test is dependent on a timeout that is apparently still not enough... Thanks for your pull request! |
bors try |
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.
Hi @katie-knister, thanks again for the PR 🙂
I think your approach is good overall. Using MemorySize
for comparison and throwing runtime_error
makes sense to me, clear error message too. Unfortunately, locations and snap confinement make it a bit trickier, so I don't think this will work just yet. Details inline.
src/daemon/daemon.cpp
Outdated
disk_space = *command_line_value; | ||
} | ||
|
||
auto available_bytes = QStorageInfo::root().bytesAvailable(); |
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 two issues with this:
- we may be storing images elsewhere, either because multipass is installed in a different filesystem or because
MULTIPASS_STORAGE
was used - this does not currently work under snap confinement; you can try it yourself by using the PR channel which is built in CI:
multipass install/refresh channel=edge/pr1854 multipass
; but we're looking for a way to overcome this and will write back when we find 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.
You also need to cater for QStorageInfo::bytesAvailable
returning -1
in case of error.
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.
@ricab @Saviq Thank you for reviewing this! I changed from checking the root's available space to using DaemonConfig::data_directory
as @Saviq suggested. I also added a check for if QStorageInfo::bytesAvailable
returns -1
. The "BuildAndTest" checks don't seem to be running (nothing has updated after over an hour), so I thought I should just go ahead and let you know about these updates to see what you think.
@ricab Thank you for your review! It sounds like this is blocked for now (if that is not the case, please let me know). I plan to get started on a different “good first issue” in the meantime. Please let me know if this issue becomes unblocked or if you have any recommendations for any other “good first issues” that could be particularly helpful to finish now. |
Hi @ricab @katie-knister, I just tried this with I think in reality, the problem is what @ricab highlighted - you need to check the space where Multipass will actually be writing the image, not on the root filesystem, which, in case of running as a snap, is a read-only mount of the snap run --shell multipass -c "df /"
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/loop15 56704 56704 0 100% / So indeed when you check for the root's available space, it's going to be 0. As a pointer, you likely want to use the multipass/src/daemon/daemon_config.h Line 59 in bc0d9fd
|
…dd error check in case call to bytesAvailable fails
48105b6
to
d6140f9
Compare
bors try |
tryMerge conflict. |
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.
Hi @katie-knister, there seems to be a merge conflict now, so we don't get the snap and I couldn't test yet, but the code looks good to me.
One further thing I'd like to request is for tests to the new functionality you're adding (we try to add only tested code as much as possible). Tests for this should probably go around here.
You can't easily mock Qt stuff, but you wouldn't have to: you could check the available space in the test itself, then request just a bit more than that and confirm that the daemon throws the new runtime_error
. This may be useful for that. One other test would be the -1 case (you'd have to overwrite the data_directory
that is set here with something bogus).
5add6ca
to
3719323
Compare
3719323
to
23f5425
Compare
Codecov Report
@@ Coverage Diff @@
## master #1854 +/- ##
==========================================
+ Coverage 77.70% 77.83% +0.12%
==========================================
Files 234 234
Lines 8743 8766 +23
==========================================
+ Hits 6794 6823 +29
+ Misses 1949 1943 -6
Continue to review full report at Codecov.
|
Hi @ricab, thank you for the information on testing! I resolved the merge conflict and added tests. They are all passing. I added another check to the |
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.
Hi @katie-knister, thanks for the tests. I'm proposing a few simplifications inline.
You did well to separate the -1
check. Actually, I think the other one should be in a separate test too. The idea is that each test should check one property of the intended behavior.
Let me know if you need any help with it.
tests/test_daemon.cpp
Outdated
else if (other_command_line_parameters.size() > 0 && | ||
mp::MemorySize(other_command_line_parameters[1]) > mp::MemorySize(available_bytes_str + "B")) | ||
{ | ||
std::stringstream stream; | ||
EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)).Times(0); | ||
send_command(all_parameters, trash_stream, stream); | ||
EXPECT_THAT(stream.str(), AllOf(HasSubstr("Available disk"), HasSubstr("below requested/default size"))); | ||
} |
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.
Hmm, I think this should be in its own test, one where you made sure that the inequality in the if
holds (and so didn't need to check 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.
Makes sense, I was wondering about that. I switched it to its own test and removed the parts that I added to this test.
tests/test_daemon.cpp
Outdated
TEST_P(InvalidDataDirectorySuite, launch_fails_with_invalid_data_directory) | ||
{ | ||
auto mock_factory = use_a_mock_vm_factory(); | ||
const auto param = GetParam(); | ||
const auto& first_command_line_parameter = std::get<0>(param); | ||
const auto& other_command_line_parameters = std::get<1>(param); | ||
const auto& img_size_str = std::get<2>(param); | ||
|
||
auto mock_image_vault = std::make_unique<NiceMock<mpt::MockVMImageVault>>(); | ||
ON_CALL(*mock_image_vault.get(), minimum_image_size_for(_)).WillByDefault([&img_size_str](auto...) { | ||
return mp::MemorySize{img_size_str}; | ||
}); | ||
|
||
config_builder.vault = std::move(mock_image_vault); | ||
config_builder.data_directory = QString("invalid_data_directory"); | ||
mp::Daemon daemon{config_builder.build()}; | ||
|
||
std::vector<std::string> all_parameters{first_command_line_parameter}; | ||
for (const auto& p : other_command_line_parameters) | ||
all_parameters.push_back(p); | ||
|
||
std::stringstream stream; | ||
EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)).Times(0); | ||
send_command(all_parameters, trash_stream, stream); | ||
EXPECT_THAT(stream.str(), HasSubstr("Failed to determine information about the volume containing")); | ||
} |
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.
This test can be a lot simpler than the one you used as inspiration. For instance, you don't need all the parameters, since you use only one value; then you don't need to construct a complex command either; and neither do you need all the mocking. Consider this:
TEST_P(InvalidDataDirectorySuite, launch_fails_with_invalid_data_directory)
{
auto mock_factory = use_a_mock_vm_factory();
config_builder.data_directory = QString("invalid_data_directory");
mp::Daemon daemon{config_builder.build()};
std::stringstream stream;
EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)).Times(0);
send_command({GetParam()}, trash_stream, stream);
EXPECT_THAT(stream.str(), HasSubstr("Failed to determine information about the volume containing"));
}
You'd need to change the param interface to receive only one string. You could then do something very similar for the other test, adding just --disk ...
to the command.
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.
Thank you for this information! I was wondering about simplifying this test, but I didn't want to accidentally remove parts that were actually necessary -- so I appreciate you taking the time to discuss how to simplify it. Now I've made the changes you suggested.
tests/test_daemon.cpp
Outdated
Combine(Values("test_create", "launch"), | ||
Values(std::vector<std::string>{}, std::vector<std::string>{"--disk", "4G"}), | ||
Values(std::vector<std::string>{}, std::vector<std::string>{"--disk", "4G"}, | ||
std::vector<std::string>{"--disk", "9999G"}), |
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.
Good point, you can request a very large disk size and assume it's not acceptable. That will allow you to drop the QStorageInfo stuff.
However, 10 terabytes may not be so uncommon in a few years. MemorySize currently uses a long long
, which is guaranteed to be at least 64 bits. It's currently signed (something we could change), so we need something very large but below 2^63
. Perhaps "999999999G"
. When 1 exabyte is not enough we need to change the underlying representation anyway 😉
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.
Oh, interesting! Thank you, I've made this change.
d24aa06
to
ac042d9
Compare
…nch_fails_with_invalid_data_directory test
ac042d9
to
ded06f7
Compare
Hi @ricab, thank you for your feedback on the tests. I just made the changes you suggested. |
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.
Hi @katie-knister, I think there is some confusion regarding the parameterized tests. The principle of your tests is correct, but some parts come from other tests and are not really applicable. I created a draft PR with what I mean because it is simpler and more precise than words.
tests/test_daemon.cpp
Outdated
Values("1G", mp::default_disk_size, "10G"))); | ||
INSTANTIATE_TEST_SUITE_P(Daemon, DiskSpaceRequestedExceedsAvailableSuite, | ||
Combine(Values("test_create", "launch"), | ||
Values(std::vector<std::string>{"--disk", "999999999G"}))); |
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.
This is the only value ever used by the test (the vector with the 2 args), so there is no point having it as a parameter.
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.
OK, this should be ready to merge.
bors merge
1854: Add check for available disk space on launch r=ricab a=katie-knister To address #938 I used the std::filesystem library to check for available space in the user’s directory. Co-authored-by: Katie Knister <[email protected]> Co-authored-by: katie-knister <[email protected]> Co-authored-by: Ricardo Abreu <[email protected]>
This PR was included in a batch that successfully built, but then failed to merge into master (it was a non-fast-forward update). It will be automatically retried. |
To address #938 I used the std::filesystem library to check for available space in the user’s directory.