p2p_manager_unittest.cc revision 88b591f24cb3f94f982d7024c2e8ed25c2cc26a2
1// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <glib.h> 6 7#include <dirent.h> 8#include <fcntl.h> 9#include <sys/stat.h> 10#include <unistd.h> 11#include <attr/xattr.h> // NOLINT - requires typed defined in unistd.h 12 13#include "gmock/gmock.h" 14#include "gtest/gtest.h" 15 16#include "base/bind.h" 17#include "base/callback.h" 18#include <base/strings/stringprintf.h> 19#include <policy/libpolicy.h> 20#include <policy/mock_device_policy.h> 21 22#include "update_engine/fake_p2p_manager_configuration.h" 23#include "update_engine/p2p_manager.h" 24#include "update_engine/prefs.h" 25#include "update_engine/test_utils.h" 26#include "update_engine/utils.h" 27 28using std::string; 29using std::vector; 30using base::TimeDelta; 31 32namespace chromeos_update_engine { 33 34// Test fixture that sets up a testing configuration (with e.g. a 35// temporary p2p dir) for P2PManager and cleans up when the test is 36// done. 37class P2PManagerTest : public testing::Test { 38 protected: 39 P2PManagerTest() {} 40 virtual ~P2PManagerTest() {} 41 42 // Derived from testing::Test. 43 virtual void SetUp() { 44 test_conf_ = new FakeP2PManagerConfiguration(); 45 } 46 virtual void TearDown() {} 47 48 // The P2PManager::Configuration instance used for testing. 49 FakeP2PManagerConfiguration *test_conf_; 50}; 51 52 53// Check that IsP2PEnabled() returns false if neither the crosh flag 54// nor the Enterprise Policy indicates p2p is to be used. 55TEST_F(P2PManagerTest, P2PEnabledNeitherCroshFlagNotEnterpriseSetting) { 56 string temp_dir; 57 Prefs prefs; 58 EXPECT_TRUE(utils::MakeTempDirectory("PayloadStateP2PTests.XXXXXX", 59 &temp_dir)); 60 prefs.Init(base::FilePath(temp_dir)); 61 62 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 63 &prefs, "cros_au", 3)); 64 EXPECT_FALSE(manager->IsP2PEnabled()); 65 66 EXPECT_TRUE(utils::RecursiveUnlinkDir(temp_dir)); 67} 68 69// Check that IsP2PEnabled() corresponds to value of the crosh flag 70// when Enterprise Policy is not set. 71TEST_F(P2PManagerTest, P2PEnabledCroshFlag) { 72 string temp_dir; 73 Prefs prefs; 74 EXPECT_TRUE(utils::MakeTempDirectory("PayloadStateP2PTests.XXXXXX", 75 &temp_dir)); 76 prefs.Init(base::FilePath(temp_dir)); 77 78 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 79 &prefs, "cros_au", 3)); 80 EXPECT_FALSE(manager->IsP2PEnabled()); 81 prefs.SetBoolean(kPrefsP2PEnabled, true); 82 EXPECT_TRUE(manager->IsP2PEnabled()); 83 prefs.SetBoolean(kPrefsP2PEnabled, false); 84 EXPECT_FALSE(manager->IsP2PEnabled()); 85 86 EXPECT_TRUE(utils::RecursiveUnlinkDir(temp_dir)); 87} 88 89// Check that IsP2PEnabled() always returns true if Enterprise Policy 90// indicates that p2p is to be used. 91TEST_F(P2PManagerTest, P2PEnabledEnterpriseSettingTrue) { 92 string temp_dir; 93 Prefs prefs; 94 EXPECT_TRUE(utils::MakeTempDirectory("PayloadStateP2PTests.XXXXXX", 95 &temp_dir)); 96 prefs.Init(base::FilePath(temp_dir)); 97 98 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 99 &prefs, "cros_au", 3)); 100 scoped_ptr<policy::MockDevicePolicy> device_policy( 101 new policy::MockDevicePolicy()); 102 EXPECT_CALL(*device_policy, GetAuP2PEnabled(testing::_)).WillRepeatedly( 103 DoAll(testing::SetArgumentPointee<0>(true), 104 testing::Return(true))); 105 manager->SetDevicePolicy(device_policy.get()); 106 EXPECT_TRUE(manager->IsP2PEnabled()); 107 108 // Should still return true even if crosh flag says otherwise. 109 prefs.SetBoolean(kPrefsP2PEnabled, false); 110 EXPECT_TRUE(manager->IsP2PEnabled()); 111 112 EXPECT_TRUE(utils::RecursiveUnlinkDir(temp_dir)); 113} 114 115// Check that IsP2PEnabled() corresponds to the value of the crosh 116// flag if Enterprise Policy indicates that p2p is not to be used. 117TEST_F(P2PManagerTest, P2PEnabledEnterpriseSettingFalse) { 118 string temp_dir; 119 Prefs prefs; 120 EXPECT_TRUE(utils::MakeTempDirectory("PayloadStateP2PTests.XXXXXX", 121 &temp_dir)); 122 prefs.Init(base::FilePath(temp_dir)); 123 124 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 125 &prefs, "cros_au", 3)); 126 scoped_ptr<policy::MockDevicePolicy> device_policy( 127 new policy::MockDevicePolicy()); 128 EXPECT_CALL(*device_policy, GetAuP2PEnabled(testing::_)).WillRepeatedly( 129 DoAll(testing::SetArgumentPointee<0>(false), 130 testing::Return(true))); 131 manager->SetDevicePolicy(device_policy.get()); 132 EXPECT_FALSE(manager->IsP2PEnabled()); 133 134 prefs.SetBoolean(kPrefsP2PEnabled, true); 135 EXPECT_TRUE(manager->IsP2PEnabled()); 136 prefs.SetBoolean(kPrefsP2PEnabled, false); 137 EXPECT_FALSE(manager->IsP2PEnabled()); 138 139 EXPECT_TRUE(utils::RecursiveUnlinkDir(temp_dir)); 140} 141 142// Check that we keep the $N newest files with the .$EXT.p2p extension. 143TEST_F(P2PManagerTest, Housekeeping) { 144 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 145 nullptr, "cros_au", 3)); 146 EXPECT_EQ(manager->CountSharedFiles(), 0); 147 148 // Generate files with different timestamps matching our pattern and generate 149 // other files not matching the pattern. 150 double last_timestamp = -1; 151 for (int n = 0; n < 5; n++) { 152 double current_timestamp; 153 do { 154 base::FilePath file = test_conf_->GetP2PDir().Append(base::StringPrintf( 155 "file_%d.cros_au.p2p", n)); 156 EXPECT_EQ(0, System(base::StringPrintf("touch %s", 157 file.value().c_str()))); 158 159 // Check that the current timestamp on the file is different from the 160 // previous generated file. This timestamp depends on the file system 161 // time resolution, for example, ext2/ext3 have a time resolution of one 162 // second while ext4 has a resolution of one nanosecond. If the assigned 163 // timestamp is the same, we introduce a bigger sleep and call touch 164 // again. 165 struct stat statbuf; 166 EXPECT_EQ(stat(file.value().c_str(), &statbuf), 0); 167 current_timestamp = utils::TimeFromStructTimespec(&statbuf.st_ctim) 168 .ToDoubleT(); 169 if (current_timestamp == last_timestamp) 170 sleep(1); 171 } while (current_timestamp == last_timestamp); 172 last_timestamp = current_timestamp; 173 174 EXPECT_EQ(0, System(base::StringPrintf( 175 "touch %s/file_%d.OTHER.p2p", 176 test_conf_->GetP2PDir().value().c_str(), n))); 177 178 // A sleep of one micro-second is enough to have a different "Change" time 179 // on the file on newer file systems. 180 g_usleep(1); 181 } 182 // CountSharedFiles() only counts 'cros_au' files. 183 EXPECT_EQ(manager->CountSharedFiles(), 5); 184 185 EXPECT_TRUE(manager->PerformHousekeeping()); 186 187 // At this point - after HouseKeeping - we should only have 188 // eight files left. 189 for (int n = 0; n < 5; n++) { 190 string file_name; 191 bool expect; 192 193 expect = (n >= 2); 194 file_name = base::StringPrintf( 195 "%s/file_%d.cros_au.p2p", 196 test_conf_->GetP2PDir().value().c_str(), n); 197 EXPECT_EQ(!!g_file_test(file_name.c_str(), G_FILE_TEST_EXISTS), expect); 198 199 file_name = base::StringPrintf( 200 "%s/file_%d.OTHER.p2p", 201 test_conf_->GetP2PDir().value().c_str(), n); 202 EXPECT_TRUE(g_file_test(file_name.c_str(), G_FILE_TEST_EXISTS)); 203 } 204 // CountSharedFiles() only counts 'cros_au' files. 205 EXPECT_EQ(manager->CountSharedFiles(), 3); 206} 207 208static bool CheckP2PFile(const string& p2p_dir, const string& file_name, 209 ssize_t expected_size, ssize_t expected_size_xattr) { 210 string path = p2p_dir + "/" + file_name; 211 struct stat statbuf; 212 char ea_value[64] = { 0 }; 213 ssize_t ea_size; 214 215 if (stat(path.c_str(), &statbuf) != 0) { 216 LOG(ERROR) << "File " << path << " does not exist"; 217 return false; 218 } 219 220 if (expected_size != 0) { 221 if (statbuf.st_size != expected_size) { 222 LOG(ERROR) << "Expected size " << expected_size 223 << " but size was " << statbuf.st_size; 224 return false; 225 } 226 } 227 228 if (expected_size_xattr == 0) { 229 ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize", 230 &ea_value, sizeof ea_value - 1); 231 if (ea_size == -1 && errno == ENOATTR) { 232 // This is valid behavior as we support files without the xattr set. 233 } else { 234 PLOG(ERROR) << "getxattr() didn't fail with ENOATTR as expected, " 235 << "ea_size=" << ea_size << ", errno=" << errno; 236 return false; 237 } 238 } else { 239 ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize", 240 &ea_value, sizeof ea_value - 1); 241 if (ea_size < 0) { 242 LOG(ERROR) << "Error getting xattr attribute"; 243 return false; 244 } 245 char* endp = nullptr; 246 long long int val = strtoll(ea_value, &endp, 0); // NOLINT(runtime/int) 247 if (endp == nullptr || *endp != '\0') { 248 LOG(ERROR) << "Error parsing xattr '" << ea_value 249 << "' as an integer"; 250 return false; 251 } 252 if (val != expected_size_xattr) { 253 LOG(ERROR) << "Expected xattr size " << expected_size_xattr 254 << " but size was " << val; 255 return false; 256 } 257 } 258 259 return true; 260} 261 262static bool CreateP2PFile(string p2p_dir, string file_name, 263 size_t size, size_t size_xattr) { 264 string path = p2p_dir + "/" + file_name; 265 266 int fd = open(path.c_str(), O_CREAT|O_RDWR, 0644); 267 if (fd == -1) { 268 PLOG(ERROR) << "Error creating file with path " << path; 269 return false; 270 } 271 if (ftruncate(fd, size) != 0) { 272 PLOG(ERROR) << "Error truncating " << path << " to size " << size; 273 close(fd); 274 return false; 275 } 276 277 if (size_xattr != 0) { 278 string decimal_size = base::StringPrintf("%zu", size_xattr); 279 if (fsetxattr(fd, "user.cros-p2p-filesize", 280 decimal_size.c_str(), decimal_size.size(), 0) != 0) { 281 PLOG(ERROR) << "Error setting xattr on " << path; 282 close(fd); 283 return false; 284 } 285 } 286 287 close(fd); 288 return true; 289} 290 291// Check that sharing a *new* file works. 292TEST_F(P2PManagerTest, ShareFile) { 293 if (!utils::IsXAttrSupported(base::FilePath("/tmp"))) { 294 LOG(WARNING) << "Skipping test because /tmp does not support xattr. " 295 << "Please update your system to support this feature."; 296 return; 297 } 298 299 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 300 nullptr, "cros_au", 3)); 301 EXPECT_TRUE(manager->FileShare("foo", 10 * 1000 * 1000)); 302 EXPECT_EQ(manager->FileGetPath("foo"), 303 test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp")); 304 EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(), 305 "foo.cros_au.p2p.tmp", 0, 10 * 1000 * 1000)); 306 307 // Sharing it again - with the same expected size - should return true 308 EXPECT_TRUE(manager->FileShare("foo", 10 * 1000 * 1000)); 309 310 // ... but if we use the wrong size, it should fail 311 EXPECT_FALSE(manager->FileShare("foo", 10 * 1000 * 1000 + 1)); 312} 313 314// Check that making a shared file visible, does what is expected. 315TEST_F(P2PManagerTest, MakeFileVisible) { 316 if (!utils::IsXAttrSupported(base::FilePath("/tmp"))) { 317 LOG(WARNING) << "Skipping test because /tmp does not support xattr. " 318 << "Please update your system to support this feature."; 319 return; 320 } 321 322 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 323 nullptr, "cros_au", 3)); 324 // First, check that it's not visible. 325 manager->FileShare("foo", 10*1000*1000); 326 EXPECT_EQ(manager->FileGetPath("foo"), 327 test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp")); 328 EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(), 329 "foo.cros_au.p2p.tmp", 0, 10 * 1000 * 1000)); 330 // Make the file visible and check that it changed its name. Do it 331 // twice to check that FileMakeVisible() is idempotent. 332 for (int n = 0; n < 2; n++) { 333 manager->FileMakeVisible("foo"); 334 EXPECT_EQ(manager->FileGetPath("foo"), 335 test_conf_->GetP2PDir().Append("foo.cros_au.p2p")); 336 EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(), 337 "foo.cros_au.p2p", 0, 10 * 1000 * 1000)); 338 } 339} 340 341// Check that we return the right values for existing files in P2P_DIR. 342TEST_F(P2PManagerTest, ExistingFiles) { 343 if (!utils::IsXAttrSupported(base::FilePath("/tmp"))) { 344 LOG(WARNING) << "Skipping test because /tmp does not support xattr. " 345 << "Please update your system to support this feature."; 346 return; 347 } 348 349 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 350 nullptr, "cros_au", 3)); 351 bool visible; 352 353 // Check that errors are returned if the file does not exist 354 EXPECT_EQ(manager->FileGetPath("foo"), base::FilePath()); 355 EXPECT_EQ(manager->FileGetSize("foo"), -1); 356 EXPECT_EQ(manager->FileGetExpectedSize("foo"), -1); 357 EXPECT_FALSE(manager->FileGetVisible("foo", nullptr)); 358 // ... then create the file ... 359 EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(), 360 "foo.cros_au.p2p", 42, 43)); 361 // ... and then check that the expected values are returned 362 EXPECT_EQ(manager->FileGetPath("foo"), 363 test_conf_->GetP2PDir().Append("foo.cros_au.p2p")); 364 EXPECT_EQ(manager->FileGetSize("foo"), 42); 365 EXPECT_EQ(manager->FileGetExpectedSize("foo"), 43); 366 EXPECT_TRUE(manager->FileGetVisible("foo", &visible)); 367 EXPECT_TRUE(visible); 368 369 // One more time, this time with a .tmp variant. First ensure it errors out.. 370 EXPECT_EQ(manager->FileGetPath("bar"), base::FilePath()); 371 EXPECT_EQ(manager->FileGetSize("bar"), -1); 372 EXPECT_EQ(manager->FileGetExpectedSize("bar"), -1); 373 EXPECT_FALSE(manager->FileGetVisible("bar", nullptr)); 374 // ... then create the file ... 375 EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(), 376 "bar.cros_au.p2p.tmp", 44, 45)); 377 // ... and then check that the expected values are returned 378 EXPECT_EQ(manager->FileGetPath("bar"), 379 test_conf_->GetP2PDir().Append("bar.cros_au.p2p.tmp")); 380 EXPECT_EQ(manager->FileGetSize("bar"), 44); 381 EXPECT_EQ(manager->FileGetExpectedSize("bar"), 45); 382 EXPECT_TRUE(manager->FileGetVisible("bar", &visible)); 383 EXPECT_FALSE(visible); 384} 385 386// This is a little bit ugly but short of mocking a 'p2p' service this 387// will have to do. E.g. we essentially simulate the various 388// behaviours of initctl(8) that we rely on. 389TEST_F(P2PManagerTest, StartP2P) { 390 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 391 nullptr, "cros_au", 3)); 392 393 // Check that we can start the service 394 test_conf_->SetInitctlStartCommandLine("true"); 395 EXPECT_TRUE(manager->EnsureP2PRunning()); 396 test_conf_->SetInitctlStartCommandLine("false"); 397 EXPECT_FALSE(manager->EnsureP2PRunning()); 398 test_conf_->SetInitctlStartCommandLine( 399 "sh -c 'echo \"initctl: Job is already running: p2p\" >&2; false'"); 400 EXPECT_TRUE(manager->EnsureP2PRunning()); 401 test_conf_->SetInitctlStartCommandLine( 402 "sh -c 'echo something else >&2; false'"); 403 EXPECT_FALSE(manager->EnsureP2PRunning()); 404} 405 406// Same comment as for StartP2P 407TEST_F(P2PManagerTest, StopP2P) { 408 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 409 nullptr, "cros_au", 3)); 410 411 // Check that we can start the service 412 test_conf_->SetInitctlStopCommandLine("true"); 413 EXPECT_TRUE(manager->EnsureP2PNotRunning()); 414 test_conf_->SetInitctlStopCommandLine("false"); 415 EXPECT_FALSE(manager->EnsureP2PNotRunning()); 416 test_conf_->SetInitctlStopCommandLine( 417 "sh -c 'echo \"initctl: Unknown instance \" >&2; false'"); 418 EXPECT_TRUE(manager->EnsureP2PNotRunning()); 419 test_conf_->SetInitctlStopCommandLine( 420 "sh -c 'echo something else >&2; false'"); 421 EXPECT_FALSE(manager->EnsureP2PNotRunning()); 422} 423 424static void ExpectUrl(const string& expected_url, 425 GMainLoop* loop, 426 const string& url) { 427 EXPECT_EQ(url, expected_url); 428 g_main_loop_quit(loop); 429} 430 431// Like StartP2P, we're mocking the different results that p2p-client 432// can return. It's not pretty but it works. 433TEST_F(P2PManagerTest, LookupURL) { 434 scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_, 435 nullptr, "cros_au", 3)); 436 GMainLoop *loop = g_main_loop_new(nullptr, FALSE); 437 438 // Emulate p2p-client returning valid URL with "fooX", 42 and "cros_au" 439 // being propagated in the right places. 440 test_conf_->SetP2PClientCommandLine( 441 "echo 'http://1.2.3.4/{file_id}_{minsize}'"); 442 manager->LookupUrlForFile("fooX", 42, TimeDelta(), 443 base::Bind(ExpectUrl, 444 "http://1.2.3.4/fooX.cros_au_42", loop)); 445 g_main_loop_run(loop); 446 447 // Emulate p2p-client returning invalid URL. 448 test_conf_->SetP2PClientCommandLine("echo 'not_a_valid_url'"); 449 manager->LookupUrlForFile("foobar", 42, TimeDelta(), 450 base::Bind(ExpectUrl, "", loop)); 451 g_main_loop_run(loop); 452 453 // Emulate p2p-client conveying failure. 454 test_conf_->SetP2PClientCommandLine("false"); 455 manager->LookupUrlForFile("foobar", 42, TimeDelta(), 456 base::Bind(ExpectUrl, "", loop)); 457 g_main_loop_run(loop); 458 459 // Emulate p2p-client not existing. 460 test_conf_->SetP2PClientCommandLine("/path/to/non/existent/helper/program"); 461 manager->LookupUrlForFile("foobar", 42, 462 TimeDelta(), 463 base::Bind(ExpectUrl, "", loop)); 464 g_main_loop_run(loop); 465 466 // Emulate p2p-client crashing. 467 test_conf_->SetP2PClientCommandLine("sh -c 'kill -SEGV $$'"); 468 manager->LookupUrlForFile("foobar", 42, TimeDelta(), 469 base::Bind(ExpectUrl, "", loop)); 470 g_main_loop_run(loop); 471 472 // Emulate p2p-client exceeding its timeout. 473 test_conf_->SetP2PClientCommandLine("sh -c 'echo http://1.2.3.4/; sleep 2'"); 474 manager->LookupUrlForFile("foobar", 42, TimeDelta::FromMilliseconds(500), 475 base::Bind(ExpectUrl, "", loop)); 476 g_main_loop_run(loop); 477 478 g_main_loop_unref(loop); 479} 480 481} // namespace chromeos_update_engine 482