p2p_manager_unittest.cc revision aea4c1cea20dda7ae7e85fc8924a2d784f70d806
1// 2// Copyright (C) 2012 The Android Open Source Project 3// 4// Licensed under the Apache License, Version 2.0 (the "License"); 5// you may not use this file except in compliance with the License. 6// You may obtain a copy of the License at 7// 8// http://www.apache.org/licenses/LICENSE-2.0 9// 10// Unless required by applicable law or agreed to in writing, software 11// distributed under the License is distributed on an "AS IS" BASIS, 12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13// See the License for the specific language governing permissions and 14// limitations under the License. 15// 16 17#include "update_engine/p2p_manager.h" 18 19#include <dirent.h> 20#include <fcntl.h> 21#include <sys/stat.h> 22#include <unistd.h> 23#include <attr/xattr.h> // NOLINT - requires typed defined in unistd.h 24 25#include <memory> 26#include <string> 27#include <vector> 28 29#include <base/bind.h> 30#include <base/callback.h> 31#include <base/files/file_util.h> 32#include <base/message_loop/message_loop.h> 33#include <base/strings/stringprintf.h> 34#include <chromeos/asynchronous_signal_handler.h> 35#include <chromeos/message_loops/base_message_loop.h> 36#include <chromeos/message_loops/message_loop.h> 37#include <chromeos/message_loops/message_loop_utils.h> 38#include <gmock/gmock.h> 39#include <gtest/gtest.h> 40#include <policy/libpolicy.h> 41#include <policy/mock_device_policy.h> 42 43#include "update_engine/fake_clock.h" 44#include "update_engine/fake_p2p_manager_configuration.h" 45#include "update_engine/prefs.h" 46#include "update_engine/test_utils.h" 47#include "update_engine/update_manager/fake_update_manager.h" 48#include "update_engine/update_manager/mock_policy.h" 49#include "update_engine/utils.h" 50 51using base::TimeDelta; 52using chromeos::MessageLoop; 53using std::string; 54using std::unique_ptr; 55using std::vector; 56using testing::DoAll; 57using testing::Return; 58using testing::SetArgPointee; 59using testing::_; 60 61namespace chromeos_update_engine { 62 63// Test fixture that sets up a testing configuration (with e.g. a 64// temporary p2p dir) for P2PManager and cleans up when the test is 65// done. 66class P2PManagerTest : public testing::Test { 67 protected: 68 P2PManagerTest() : fake_um_(&fake_clock_) {} 69 ~P2PManagerTest() override {} 70 71 // Derived from testing::Test. 72 void SetUp() override { 73 loop_.SetAsCurrent(); 74 async_signal_handler_.Init(); 75 subprocess_.Init(&async_signal_handler_); 76 test_conf_ = new FakeP2PManagerConfiguration(); 77 78 // Allocate and install a mock policy implementation in the fake Update 79 // Manager. Note that the FakeUpdateManager takes ownership of the policy 80 // object. 81 mock_policy_ = new chromeos_update_manager::MockPolicy(&fake_clock_); 82 fake_um_.set_policy(mock_policy_); 83 84 // Construct the P2P manager under test. 85 manager_.reset(P2PManager::Construct(test_conf_, &fake_clock_, &fake_um_, 86 "cros_au", 3, 87 TimeDelta::FromDays(5))); 88 } 89 90 base::MessageLoopForIO base_loop_; 91 chromeos::BaseMessageLoop loop_{&base_loop_}; 92 chromeos::AsynchronousSignalHandler async_signal_handler_; 93 Subprocess subprocess_; 94 95 // The P2PManager::Configuration instance used for testing. 96 FakeP2PManagerConfiguration *test_conf_; 97 98 FakeClock fake_clock_; 99 chromeos_update_manager::MockPolicy *mock_policy_ = nullptr; 100 chromeos_update_manager::FakeUpdateManager fake_um_; 101 102 unique_ptr<P2PManager> manager_; 103}; 104 105 106// Check that IsP2PEnabled() polls the policy correctly, with the value not 107// changing between calls. 108TEST_F(P2PManagerTest, P2PEnabledInitAndNotChanged) { 109 EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _)); 110 EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false)); 111 112 EXPECT_FALSE(manager_->IsP2PEnabled()); 113 chromeos::MessageLoopRunMaxIterations(MessageLoop::current(), 100); 114 EXPECT_FALSE(manager_->IsP2PEnabled()); 115} 116 117// Check that IsP2PEnabled() polls the policy correctly, with the value changing 118// between calls. 119TEST_F(P2PManagerTest, P2PEnabledInitAndChanged) { 120 EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _)) 121 .WillOnce(DoAll( 122 SetArgPointee<3>(true), 123 Return(chromeos_update_manager::EvalStatus::kSucceeded))); 124 EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, true)); 125 EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false)); 126 127 EXPECT_TRUE(manager_->IsP2PEnabled()); 128 chromeos::MessageLoopRunMaxIterations(MessageLoop::current(), 100); 129 EXPECT_FALSE(manager_->IsP2PEnabled()); 130} 131 132// Check that we keep the $N newest files with the .$EXT.p2p extension. 133TEST_F(P2PManagerTest, HousekeepingCountLimit) { 134 // Specifically pass 0 for |max_file_age| to allow files of any age. Note that 135 // we need to reallocate the test_conf_ member, whose currently aliased object 136 // will be freed. 137 test_conf_ = new FakeP2PManagerConfiguration(); 138 manager_.reset(P2PManager::Construct( 139 test_conf_, &fake_clock_, &fake_um_, "cros_au", 3, 140 TimeDelta() /* max_file_age */)); 141 EXPECT_EQ(manager_->CountSharedFiles(), 0); 142 143 base::Time start_time = base::Time::FromDoubleT(1246996800.); 144 // Generate files with different timestamps matching our pattern and generate 145 // other files not matching the pattern. 146 for (int n = 0; n < 5; n++) { 147 base::FilePath path = test_conf_->GetP2PDir().Append(base::StringPrintf( 148 "file_%d.cros_au.p2p", n)); 149 base::Time file_time = start_time + TimeDelta::FromMinutes(n); 150 EXPECT_EQ(0, base::WriteFile(path, nullptr, 0)); 151 EXPECT_TRUE(base::TouchFile(path, file_time, file_time)); 152 153 path = test_conf_->GetP2PDir().Append(base::StringPrintf( 154 "file_%d.OTHER.p2p", n)); 155 EXPECT_EQ(0, base::WriteFile(path, nullptr, 0)); 156 EXPECT_TRUE(base::TouchFile(path, file_time, file_time)); 157 } 158 // CountSharedFiles() only counts 'cros_au' files. 159 EXPECT_EQ(manager_->CountSharedFiles(), 5); 160 161 EXPECT_TRUE(manager_->PerformHousekeeping()); 162 163 // At this point - after HouseKeeping - we should only have 164 // eight files left. 165 for (int n = 0; n < 5; n++) { 166 string file_name; 167 bool expect; 168 169 expect = (n >= 2); 170 file_name = base::StringPrintf( 171 "%s/file_%d.cros_au.p2p", 172 test_conf_->GetP2PDir().value().c_str(), n); 173 EXPECT_EQ(expect, utils::FileExists(file_name.c_str())); 174 175 file_name = base::StringPrintf( 176 "%s/file_%d.OTHER.p2p", 177 test_conf_->GetP2PDir().value().c_str(), n); 178 EXPECT_TRUE(utils::FileExists(file_name.c_str())); 179 } 180 // CountSharedFiles() only counts 'cros_au' files. 181 EXPECT_EQ(manager_->CountSharedFiles(), 3); 182} 183 184// Check that we keep files with the .$EXT.p2p extension not older 185// than some specificed age (5 days, in this test). 186TEST_F(P2PManagerTest, HousekeepingAgeLimit) { 187 // We set the cutoff time to be 1 billion seconds (01:46:40 UTC on 9 188 // September 2001 - arbitrary number, but constant to avoid test 189 // flakiness) since the epoch and then we put two files before that 190 // date and three files after. 191 base::Time cutoff_time = base::Time::FromTimeT(1000000000); 192 TimeDelta age_limit = TimeDelta::FromDays(5); 193 194 // Set the clock just so files with a timestamp before |cutoff_time| 195 // will be deleted at housekeeping. 196 fake_clock_.SetWallclockTime(cutoff_time + age_limit); 197 198 // Specifically pass 0 for |num_files_to_keep| to allow any number of files. 199 // Note that we need to reallocate the test_conf_ member, whose currently 200 // aliased object will be freed. 201 test_conf_ = new FakeP2PManagerConfiguration(); 202 manager_.reset(P2PManager::Construct( 203 test_conf_, &fake_clock_, &fake_um_, "cros_au", 204 0 /* num_files_to_keep */, age_limit)); 205 EXPECT_EQ(manager_->CountSharedFiles(), 0); 206 207 // Generate files with different timestamps matching our pattern and generate 208 // other files not matching the pattern. 209 for (int n = 0; n < 5; n++) { 210 base::FilePath path = test_conf_->GetP2PDir().Append(base::StringPrintf( 211 "file_%d.cros_au.p2p", n)); 212 213 // With five files and aiming for two of them to be before 214 // |cutoff_time|, we distribute it like this: 215 // 216 // -------- 0 -------- 1 -------- 2 -------- 3 -------- 4 -------- 217 // | 218 // cutoff_time 219 // 220 base::Time file_date = cutoff_time + (n - 2) * TimeDelta::FromDays(1) 221 + TimeDelta::FromHours(12); 222 223 EXPECT_EQ(0, base::WriteFile(path, nullptr, 0)); 224 EXPECT_TRUE(base::TouchFile(path, file_date, file_date)); 225 226 path = test_conf_->GetP2PDir().Append(base::StringPrintf( 227 "file_%d.OTHER.p2p", n)); 228 EXPECT_EQ(0, base::WriteFile(path, nullptr, 0)); 229 EXPECT_TRUE(base::TouchFile(path, file_date, file_date)); 230 } 231 // CountSharedFiles() only counts 'cros_au' files. 232 EXPECT_EQ(manager_->CountSharedFiles(), 5); 233 234 EXPECT_TRUE(manager_->PerformHousekeeping()); 235 236 // At this point - after HouseKeeping - we should only have 237 // eight files left. 238 for (int n = 0; n < 5; n++) { 239 string file_name; 240 bool expect; 241 242 expect = (n >= 2); 243 file_name = base::StringPrintf( 244 "%s/file_%d.cros_au.p2p", 245 test_conf_->GetP2PDir().value().c_str(), n); 246 EXPECT_EQ(expect, utils::FileExists(file_name.c_str())); 247 248 file_name = base::StringPrintf( 249 "%s/file_%d.OTHER.p2p", 250 test_conf_->GetP2PDir().value().c_str(), n); 251 EXPECT_TRUE(utils::FileExists(file_name.c_str())); 252 } 253 // CountSharedFiles() only counts 'cros_au' files. 254 EXPECT_EQ(manager_->CountSharedFiles(), 3); 255} 256 257static bool CheckP2PFile(const string& p2p_dir, const string& file_name, 258 ssize_t expected_size, ssize_t expected_size_xattr) { 259 string path = p2p_dir + "/" + file_name; 260 char ea_value[64] = { 0 }; 261 ssize_t ea_size; 262 263 off_t p2p_size = utils::FileSize(path); 264 if (p2p_size < 0) { 265 LOG(ERROR) << "File " << path << " does not exist"; 266 return false; 267 } 268 269 if (expected_size != 0) { 270 if (p2p_size != expected_size) { 271 LOG(ERROR) << "Expected size " << expected_size 272 << " but size was " << p2p_size; 273 return false; 274 } 275 } 276 277 if (expected_size_xattr == 0) { 278 ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize", 279 &ea_value, sizeof ea_value - 1); 280 if (ea_size == -1 && errno == ENOATTR) { 281 // This is valid behavior as we support files without the xattr set. 282 } else { 283 PLOG(ERROR) << "getxattr() didn't fail with ENOATTR as expected, " 284 << "ea_size=" << ea_size << ", errno=" << errno; 285 return false; 286 } 287 } else { 288 ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize", 289 &ea_value, sizeof ea_value - 1); 290 if (ea_size < 0) { 291 LOG(ERROR) << "Error getting xattr attribute"; 292 return false; 293 } 294 char* endp = nullptr; 295 long long int val = strtoll(ea_value, &endp, 0); // NOLINT(runtime/int) 296 if (endp == nullptr || *endp != '\0') { 297 LOG(ERROR) << "Error parsing xattr '" << ea_value 298 << "' as an integer"; 299 return false; 300 } 301 if (val != expected_size_xattr) { 302 LOG(ERROR) << "Expected xattr size " << expected_size_xattr 303 << " but size was " << val; 304 return false; 305 } 306 } 307 308 return true; 309} 310 311static bool CreateP2PFile(string p2p_dir, string file_name, 312 size_t size, size_t size_xattr) { 313 string path = p2p_dir + "/" + file_name; 314 315 int fd = open(path.c_str(), O_CREAT|O_RDWR, 0644); 316 if (fd == -1) { 317 PLOG(ERROR) << "Error creating file with path " << path; 318 return false; 319 } 320 if (ftruncate(fd, size) != 0) { 321 PLOG(ERROR) << "Error truncating " << path << " to size " << size; 322 close(fd); 323 return false; 324 } 325 326 if (size_xattr != 0) { 327 string decimal_size = std::to_string(size_xattr); 328 if (fsetxattr(fd, "user.cros-p2p-filesize", 329 decimal_size.c_str(), decimal_size.size(), 0) != 0) { 330 PLOG(ERROR) << "Error setting xattr on " << path; 331 close(fd); 332 return false; 333 } 334 } 335 336 close(fd); 337 return true; 338} 339 340// Check that sharing a *new* file works. 341TEST_F(P2PManagerTest, ShareFile) { 342 if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) { 343 LOG(WARNING) << "Skipping test because /tmp does not support xattr. " 344 << "Please update your system to support this feature."; 345 return; 346 } 347 const int kP2PTestFileSize = 1000 * 1000; // 1 MB 348 349 EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize)); 350 EXPECT_EQ(manager_->FileGetPath("foo"), 351 test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp")); 352 EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(), 353 "foo.cros_au.p2p.tmp", 0, kP2PTestFileSize)); 354 355 // Sharing it again - with the same expected size - should return true 356 EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize)); 357 358 // ... but if we use the wrong size, it should fail 359 EXPECT_FALSE(manager_->FileShare("foo", kP2PTestFileSize + 1)); 360} 361 362// Check that making a shared file visible, does what is expected. 363TEST_F(P2PManagerTest, MakeFileVisible) { 364 if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) { 365 LOG(WARNING) << "Skipping test because /tmp does not support xattr. " 366 << "Please update your system to support this feature."; 367 return; 368 } 369 const int kP2PTestFileSize = 1000 * 1000; // 1 MB 370 371 // First, check that it's not visible. 372 manager_->FileShare("foo", kP2PTestFileSize); 373 EXPECT_EQ(manager_->FileGetPath("foo"), 374 test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp")); 375 EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(), 376 "foo.cros_au.p2p.tmp", 0, kP2PTestFileSize)); 377 // Make the file visible and check that it changed its name. Do it 378 // twice to check that FileMakeVisible() is idempotent. 379 for (int n = 0; n < 2; n++) { 380 manager_->FileMakeVisible("foo"); 381 EXPECT_EQ(manager_->FileGetPath("foo"), 382 test_conf_->GetP2PDir().Append("foo.cros_au.p2p")); 383 EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(), 384 "foo.cros_au.p2p", 0, kP2PTestFileSize)); 385 } 386} 387 388// Check that we return the right values for existing files in P2P_DIR. 389TEST_F(P2PManagerTest, ExistingFiles) { 390 if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) { 391 LOG(WARNING) << "Skipping test because /tmp does not support xattr. " 392 << "Please update your system to support this feature."; 393 return; 394 } 395 396 bool visible; 397 398 // Check that errors are returned if the file does not exist 399 EXPECT_EQ(manager_->FileGetPath("foo"), base::FilePath()); 400 EXPECT_EQ(manager_->FileGetSize("foo"), -1); 401 EXPECT_EQ(manager_->FileGetExpectedSize("foo"), -1); 402 EXPECT_FALSE(manager_->FileGetVisible("foo", nullptr)); 403 // ... then create the file ... 404 EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(), 405 "foo.cros_au.p2p", 42, 43)); 406 // ... and then check that the expected values are returned 407 EXPECT_EQ(manager_->FileGetPath("foo"), 408 test_conf_->GetP2PDir().Append("foo.cros_au.p2p")); 409 EXPECT_EQ(manager_->FileGetSize("foo"), 42); 410 EXPECT_EQ(manager_->FileGetExpectedSize("foo"), 43); 411 EXPECT_TRUE(manager_->FileGetVisible("foo", &visible)); 412 EXPECT_TRUE(visible); 413 414 // One more time, this time with a .tmp variant. First ensure it errors out.. 415 EXPECT_EQ(manager_->FileGetPath("bar"), base::FilePath()); 416 EXPECT_EQ(manager_->FileGetSize("bar"), -1); 417 EXPECT_EQ(manager_->FileGetExpectedSize("bar"), -1); 418 EXPECT_FALSE(manager_->FileGetVisible("bar", nullptr)); 419 // ... then create the file ... 420 EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(), 421 "bar.cros_au.p2p.tmp", 44, 45)); 422 // ... and then check that the expected values are returned 423 EXPECT_EQ(manager_->FileGetPath("bar"), 424 test_conf_->GetP2PDir().Append("bar.cros_au.p2p.tmp")); 425 EXPECT_EQ(manager_->FileGetSize("bar"), 44); 426 EXPECT_EQ(manager_->FileGetExpectedSize("bar"), 45); 427 EXPECT_TRUE(manager_->FileGetVisible("bar", &visible)); 428 EXPECT_FALSE(visible); 429} 430 431// This is a little bit ugly but short of mocking a 'p2p' service this 432// will have to do. E.g. we essentially simulate the various 433// behaviours of initctl(8) that we rely on. 434TEST_F(P2PManagerTest, StartP2P) { 435 // Check that we can start the service 436 test_conf_->SetInitctlStartCommand({"true"}); 437 EXPECT_TRUE(manager_->EnsureP2PRunning()); 438 test_conf_->SetInitctlStartCommand({"false"}); 439 EXPECT_FALSE(manager_->EnsureP2PRunning()); 440 test_conf_->SetInitctlStartCommand({ 441 "sh", "-c", "echo \"initctl: Job is already running: p2p\" >&2; false"}); 442 EXPECT_TRUE(manager_->EnsureP2PRunning()); 443 test_conf_->SetInitctlStartCommand({ 444 "sh", "-c", "echo something else >&2; false"}); 445 EXPECT_FALSE(manager_->EnsureP2PRunning()); 446} 447 448// Same comment as for StartP2P 449TEST_F(P2PManagerTest, StopP2P) { 450 // Check that we can start the service 451 test_conf_->SetInitctlStopCommand({"true"}); 452 EXPECT_TRUE(manager_->EnsureP2PNotRunning()); 453 test_conf_->SetInitctlStopCommand({"false"}); 454 EXPECT_FALSE(manager_->EnsureP2PNotRunning()); 455 test_conf_->SetInitctlStopCommand({ 456 "sh", "-c", "echo \"initctl: Unknown instance \" >&2; false"}); 457 EXPECT_TRUE(manager_->EnsureP2PNotRunning()); 458 test_conf_->SetInitctlStopCommand({ 459 "sh", "-c", "echo something else >&2; false"}); 460 EXPECT_FALSE(manager_->EnsureP2PNotRunning()); 461} 462 463static void ExpectUrl(const string& expected_url, 464 const string& url) { 465 EXPECT_EQ(url, expected_url); 466 MessageLoop::current()->BreakLoop(); 467} 468 469// Like StartP2P, we're mocking the different results that p2p-client 470// can return. It's not pretty but it works. 471TEST_F(P2PManagerTest, LookupURL) { 472 // Emulate p2p-client returning valid URL with "fooX", 42 and "cros_au" 473 // being propagated in the right places. 474 test_conf_->SetP2PClientCommand({ 475 "echo", "http://1.2.3.4/{file_id}_{minsize}"}); 476 manager_->LookupUrlForFile("fooX", 42, TimeDelta(), 477 base::Bind(ExpectUrl, 478 "http://1.2.3.4/fooX.cros_au_42")); 479 loop_.Run(); 480 481 // Emulate p2p-client returning invalid URL. 482 test_conf_->SetP2PClientCommand({"echo", "not_a_valid_url"}); 483 manager_->LookupUrlForFile("foobar", 42, TimeDelta(), 484 base::Bind(ExpectUrl, "")); 485 loop_.Run(); 486 487 // Emulate p2p-client conveying failure. 488 test_conf_->SetP2PClientCommand({"false"}); 489 manager_->LookupUrlForFile("foobar", 42, TimeDelta(), 490 base::Bind(ExpectUrl, "")); 491 loop_.Run(); 492 493 // Emulate p2p-client not existing. 494 test_conf_->SetP2PClientCommand({"/path/to/non/existent/helper/program"}); 495 manager_->LookupUrlForFile("foobar", 42, 496 TimeDelta(), 497 base::Bind(ExpectUrl, "")); 498 loop_.Run(); 499 500 // Emulate p2p-client crashing. 501 test_conf_->SetP2PClientCommand({"sh", "-c", "kill -SEGV $$"}); 502 manager_->LookupUrlForFile("foobar", 42, TimeDelta(), 503 base::Bind(ExpectUrl, "")); 504 loop_.Run(); 505 506 // Emulate p2p-client exceeding its timeout. 507 test_conf_->SetP2PClientCommand({ 508 "sh", "-c", "echo http://1.2.3.4/; sleep 2"}); 509 manager_->LookupUrlForFile("foobar", 42, TimeDelta::FromMilliseconds(500), 510 base::Bind(ExpectUrl, "")); 511 loop_.Run(); 512} 513 514} // namespace chromeos_update_engine 515