p2p: Use p2p for updates

This is the main patch for enabling use of p2p for consuming and/or
sharing updates via p2p. Refer to the ddoc and other documentation for
how this works.

BUG=chromium:260426,chromium:273110
TEST=New unit tests + unit tests pass + manual testing
Change-Id: I6bc3bddae1e041ccc176969a651396e8e89cb3f0
Reviewed-on: https://chromium-review.googlesource.com/64829
Reviewed-by: David Zeuthen <zeuthen@chromium.org>
Commit-Queue: David Zeuthen <zeuthen@chromium.org>
Tested-by: David Zeuthen <zeuthen@chromium.org>
diff --git a/download_action_unittest.cc b/download_action_unittest.cc
index e937d71..c53b36c 100644
--- a/download_action_unittest.cc
+++ b/download_action_unittest.cc
@@ -2,17 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <string>
-#include <vector>
-
 #include <glib.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/file_path.h>
+#include <base/file_util.h>
+#include <base/stringprintf.h>
+
 #include "update_engine/action_pipe.h"
 #include "update_engine/download_action.h"
 #include "update_engine/mock_http_fetcher.h"
+#include "update_engine/mock_system_state.h"
 #include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/fake_p2p_manager_configuration.h"
 #include "update_engine/prefs_mock.h"
 #include "update_engine/test_utils.h"
 #include "update_engine/utils.h"
@@ -24,6 +31,10 @@
 using testing::_;
 using testing::AtLeast;
 using testing::InSequence;
+using base::FilePath;
+using base::StringPrintf;
+using file_util::WriteFile;
+using file_util::ReadFileToString;
 
 class DownloadActionTest : public ::testing::Test { };
 
@@ -417,4 +428,201 @@
   g_main_loop_unref(loop);
 }
 
+gboolean StartProcessorInRunLoopForP2P(gpointer user_data) {
+  ActionProcessor* processor = reinterpret_cast<ActionProcessor*>(user_data);
+  processor->StartProcessing();
+  return FALSE;
+}
+
+// Test fixture for P2P tests.
+class P2PDownloadActionTest : public testing::Test {
+protected:
+  P2PDownloadActionTest()
+    : loop_(NULL),
+      start_at_offset_(0) {}
+
+  virtual ~P2PDownloadActionTest() {}
+
+  // Derived from testing::Test.
+  virtual void SetUp() {
+    loop_ = g_main_loop_new(g_main_context_default(), FALSE);
+  }
+
+  // Derived from testing::Test.
+  virtual void TearDown() {
+    if (loop_ != NULL)
+      g_main_loop_unref(loop_);
+  }
+
+  // To be called by tests to setup the download. The
+  // |starting_offset| parameter is for where to resume.
+  void SetupDownload(off_t starting_offset) {
+    start_at_offset_ = starting_offset;
+    // Prepare data 10 kB of data.
+    data_.clear();
+    for (unsigned int i = 0; i < 10 * 1000; i++)
+      data_ += 'a' + (i % 25);
+
+    // Setup p2p.
+    FakeP2PManagerConfiguration *test_conf = new FakeP2PManagerConfiguration();
+    p2p_manager_.reset(P2PManager::Construct(test_conf, NULL, "cros_au", 3));
+    mock_system_state_.set_p2p_manager(p2p_manager_.get());
+  }
+
+  // To be called by tests to perform the download. The
+  // |use_p2p_to_share| parameter is used to indicate whether the
+  // payload should be shared via p2p.
+  void StartDownload(bool use_p2p_to_share) {
+    mock_system_state_.request_params()->set_use_p2p_for_sharing(
+        use_p2p_to_share);
+
+    ScopedTempFile output_temp_file;
+    TestDirectFileWriter writer;
+    InstallPlan install_plan(false,
+                             false,
+                             "",
+                             data_.length(),
+                             "1234hash",
+                             0,
+                             "",
+                             output_temp_file.GetPath(),
+                             "");
+    ObjectFeederAction<InstallPlan> feeder_action;
+    feeder_action.set_obj(install_plan);
+    PrefsMock prefs;
+    http_fetcher_ = new MockHttpFetcher(data_.c_str(),
+                                        data_.length(),
+                                        NULL);
+    // Note that DownloadAction takes ownership of the passed in HttpFetcher.
+    download_action_.reset(new DownloadAction(&prefs, &mock_system_state_,
+                                              http_fetcher_));
+    download_action_->SetTestFileWriter(&writer);
+    BondActions(&feeder_action, download_action_.get());
+    DownloadActionTestProcessorDelegate delegate(kErrorCodeSuccess);
+    delegate.loop_ = loop_;
+    delegate.expected_data_ = vector<char>(data_.begin() + start_at_offset_,
+                                           data_.end());
+    delegate.path_ = output_temp_file.GetPath();
+    processor_.set_delegate(&delegate);
+    processor_.EnqueueAction(&feeder_action);
+    processor_.EnqueueAction(download_action_.get());
+
+    g_timeout_add(0, &StartProcessorInRunLoopForP2P, this);
+    g_main_loop_run(loop_);
+  }
+
+  // The DownloadAction instance under test.
+  scoped_ptr<DownloadAction> download_action_;
+
+  // The HttpFetcher used in the test.
+  MockHttpFetcher* http_fetcher_;
+
+  // The P2PManager used in the test.
+  scoped_ptr<P2PManager> p2p_manager_;
+
+  // The ActionProcessor used for running the actions.
+  ActionProcessor processor_;
+
+  // A fake system state.
+  MockSystemState mock_system_state_;
+
+  // The data being downloaded.
+  string data_;
+
+private:
+  // Callback used in StartDownload() method.
+  static gboolean StartProcessorInRunLoopForP2P(gpointer user_data) {
+    class P2PDownloadActionTest *test =
+        reinterpret_cast<P2PDownloadActionTest*>(user_data);
+    test->processor_.StartProcessing();
+    test->http_fetcher_->SetOffset(test->start_at_offset_);
+    return FALSE;
+  }
+
+  // Mainloop used to make StartDownload() synchronous.
+  GMainLoop *loop_;
+
+  // The requested starting offset passed to SetupDownload().
+  off_t start_at_offset_;
+};
+
+TEST_F(P2PDownloadActionTest, IsWrittenTo) {
+  SetupDownload(0);    // starting_offset
+  StartDownload(true); // use_p2p_to_share
+
+  // Check the p2p file and its content matches what was sent.
+  string file_id = download_action_->p2p_file_id();
+  EXPECT_NE(file_id, "");
+  EXPECT_EQ(p2p_manager_->FileGetSize(file_id), data_.length());
+  EXPECT_EQ(p2p_manager_->FileGetExpectedSize(file_id), data_.length());
+  string p2p_file_contents;
+  EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+                               &p2p_file_contents));
+  EXPECT_EQ(data_, p2p_file_contents);
+}
+
+TEST_F(P2PDownloadActionTest, DeleteIfHoleExists) {
+  SetupDownload(1000); // starting_offset
+  StartDownload(true); // use_p2p_to_share
+
+  // DownloadAction should convey that the file is not being shared.
+  // and that we don't have any p2p files.
+  EXPECT_EQ(download_action_->p2p_file_id(), "");
+  EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
+TEST_F(P2PDownloadActionTest, CanAppend) {
+  SetupDownload(1000); // starting_offset
+
+  // Prepare the file with existing data before starting to write to
+  // it via DownloadAction.
+  string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+  ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+  string existing_data;
+  for (unsigned int i = 0; i < 1000; i++)
+    existing_data += '0' + (i % 10);
+  ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+                      1000), 1000);
+
+  StartDownload(true); // use_p2p_to_share
+
+  // DownloadAction should convey the same file_id and the file should
+  // have the expected size.
+  EXPECT_EQ(download_action_->p2p_file_id(), file_id);
+  EXPECT_EQ(p2p_manager_->FileGetSize(file_id), data_.length());
+  EXPECT_EQ(p2p_manager_->FileGetExpectedSize(file_id), data_.length());
+  string p2p_file_contents;
+  // Check that the first 1000 bytes wasn't touched and that we
+  // appended the remaining as appropriate.
+  EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+                               &p2p_file_contents));
+  EXPECT_EQ(existing_data, p2p_file_contents.substr(0, 1000));
+  EXPECT_EQ(data_.substr(1000), p2p_file_contents.substr(1000));
+}
+
+
+TEST_F(P2PDownloadActionTest, DeletePartialP2PFileIfResumingWithoutP2P) {
+  SetupDownload(1000); // starting_offset
+
+  // Prepare the file with all existing data before starting to write
+  // to it via DownloadAction.
+  string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+  ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+  string existing_data;
+  for (unsigned int i = 0; i < 1000; i++)
+    existing_data += '0' + (i % 10);
+  ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+                      1000), 1000);
+
+  // Check that the file is there.
+  EXPECT_EQ(p2p_manager_->FileGetSize(file_id), 1000);
+  EXPECT_EQ(p2p_manager_->CountSharedFiles(), 1);
+
+  StartDownload(false); // use_p2p_to_share
+
+  // DownloadAction should have deleted the p2p file. Check that it's gone.
+  EXPECT_EQ(p2p_manager_->FileGetSize(file_id), -1);
+  EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
 }  // namespace chromeos_update_engine