Reland "Store metadata about updates in a file alongside with payload."

Relands CL: I3695b0903514eb6840e88810b8546fdca690819e

This original CL had an issue where we only generated metadata in the root
folder. This means updating twice using image_to_live with different images
would break updates.

I've fixed this in the second patch. Please compare P1 vs P2 as P1 is just
the re-landed version of I3695b0903514eb6840e88810b8546fdca690819e. I've
also fixed a bunch of pylint warnings that are now required per upload.

BUG=chromium-os:36990
TEST=Unittests on all changed code, pylint on all changed files, ran update
on x86-generic image using both the serve_only and generate latest workflows.
Ran autoupdate_EndToEndTest

Change-Id: I6bb65b23a34f071e388a4e522fb0fb42eae8781b
Reviewed-on: https://gerrit.chromium.org/gerrit/42271
Tested-by: Chris Sosa <sosa@chromium.org>
Reviewed-by: Gilad Arnold <garnold@chromium.org>
Commit-Queue: Chris Sosa <sosa@chromium.org>
diff --git a/autoupdate_unittest.py b/autoupdate_unittest.py
index 02c6670..453e0c9 100755
--- a/autoupdate_unittest.py
+++ b/autoupdate_unittest.py
@@ -8,6 +8,7 @@
 
 import json
 import os
+import shutil
 import socket
 import unittest
 
@@ -26,7 +27,7 @@
   <event eventresult="%(event_result)d" eventtype="%(event_type)d" />
 </client_test>"""
 
-
+#pylint: disable=W0212
 class AutoupdateTest(mox.MoxTestBase):
   def setUp(self):
     mox.MoxTestBase.setUp(self)
@@ -59,6 +60,10 @@
     self.payload = 'My payload'
     self.sha256 = 'SHA LA LA'
     cherrypy.request.base = 'http://%s' % self.hostname
+    os.makedirs(self.static_image_dir)
+
+  def tearDown(self):
+    shutil.rmtree(self.static_image_dir)
 
   def _DummyAutoupdateConstructor(self, **kwargs):
     """Creates a dummy autoupdater.  Used to avoid using constructor."""
@@ -93,15 +98,15 @@
   def testGenerateLatestUpdateImageWithForced(self):
     self.mox.StubOutWithMock(autoupdate.Autoupdate,
                              'GenerateUpdateImageWithCache')
-    autoupdate.Autoupdate._GetLatestImageDir(self.test_board).AndReturn(
-        '%s/%s/%s' % (self.build_root, self.test_board, self.latest_dir))
-    autoupdate.Autoupdate.GenerateUpdateImageWithCache(
-        '%s/%s/%s/chromiumos_image.bin' % (self.build_root, self.test_board,
-                                           self.latest_dir),
+    au_mock = self._DummyAutoupdateConstructor()
+    au_mock._GetLatestImageDir(self.test_board).AndReturn(
+        os.path.join(self.build_root, self.test_board, self.latest_dir))
+    au_mock.GenerateUpdateImageWithCache(
+        os.path.join(self.build_root, self.test_board, self.latest_dir,
+                     'chromiumos_image.bin'),
         static_image_dir=self.static_image_dir).AndReturn('update.gz')
 
     self.mox.ReplayAll()
-    au_mock = self._DummyAutoupdateConstructor()
     self.assertTrue(au_mock.GenerateLatestUpdateImage(self.test_board,
                                                       'ForcedUpdate',
                                                       self.static_image_dir))
@@ -110,50 +115,64 @@
   def testHandleUpdatePingForForcedImage(self):
     self.mox.StubOutWithMock(autoupdate.Autoupdate,
                              'GenerateUpdateImageWithCache')
-
+    self.mox.StubOutWithMock(autoupdate.Autoupdate, '_StoreMetadataToFile')
+    au_mock = self._DummyAutoupdateConstructor()
     test_data = _TEST_REQUEST % self.test_dict
 
-    autoupdate.Autoupdate.GenerateUpdateImageWithCache(
+    # Generate a fake payload.
+    update_gz = os.path.join(self.static_image_dir, autoupdate.UPDATE_FILE)
+    with open(update_gz, 'w') as fh:
+      fh.write('')
+
+    au_mock.GenerateUpdateImageWithCache(
         self.forced_image_path,
-        static_image_dir=self.static_image_dir).AndReturn('update.gz')
+        static_image_dir=self.static_image_dir).AndReturn(None)
     common_util.GetFileSha1(os.path.join(
         self.static_image_dir, 'update.gz')).AndReturn(self.sha1)
     common_util.GetFileSha256(os.path.join(
         self.static_image_dir, 'update.gz')).AndReturn(self.sha256)
     common_util.GetFileSize(os.path.join(
         self.static_image_dir, 'update.gz')).AndReturn(self.size)
+    au_mock._StoreMetadataToFile(self.static_image_dir,
+                                 mox.IsA(autoupdate.UpdateMetadata))
     autoupdate_lib.GetUpdateResponse(
         self.sha1, self.sha256, self.size, self.url, False, '3.0',
         False).AndReturn(self.payload)
 
     self.mox.ReplayAll()
-    au_mock = self._DummyAutoupdateConstructor()
     au_mock.forced_image = self.forced_image_path
     self.assertEqual(au_mock.HandleUpdatePing(test_data), self.payload)
     self.mox.VerifyAll()
 
   def testHandleUpdatePingForLatestImage(self):
     self.mox.StubOutWithMock(autoupdate.Autoupdate, 'GenerateLatestUpdateImage')
+    self.mox.StubOutWithMock(autoupdate.Autoupdate, '_StoreMetadataToFile')
+    au_mock = self._DummyAutoupdateConstructor()
 
     test_data = _TEST_REQUEST % self.test_dict
 
-    autoupdate.Autoupdate.GenerateLatestUpdateImage(
-        self.test_board, 'ForcedUpdate', self.static_image_dir).AndReturn(
-            'update.gz')
+    # Generate a fake payload.
+    update_gz = os.path.join(self.static_image_dir, autoupdate.UPDATE_FILE)
+    with open(update_gz, 'w') as fh:
+      fh.write('')
+
+    au_mock.GenerateLatestUpdateImage(
+        self.test_board, 'ForcedUpdate', self.static_image_dir).AndReturn(None)
     common_util.GetFileSha1(os.path.join(
         self.static_image_dir, 'update.gz')).AndReturn(self.sha1)
     common_util.GetFileSha256(os.path.join(
         self.static_image_dir, 'update.gz')).AndReturn(self.sha256)
     common_util.GetFileSize(os.path.join(
         self.static_image_dir, 'update.gz')).AndReturn(self.size)
+    au_mock._StoreMetadataToFile(self.static_image_dir,
+                                 mox.IsA(autoupdate.UpdateMetadata))
     autoupdate_lib.GetUpdateResponse(
-        self.sha1, self.sha256, self.size, self.url, False, '3.0', False).AndReturn(
-            self.payload)
+        self.sha1, self.sha256, self.size, self.url, False, '3.0',
+        False).AndReturn(self.payload)
 
     self.mox.ReplayAll()
-    au_mock = self._DummyAutoupdateConstructor()
     self.assertEqual(au_mock.HandleUpdatePing(test_data), self.payload)
-    curr_host_info = au_mock.host_infos.GetHostInfo('127.0.0.1');
+    curr_host_info = au_mock.host_infos.GetHostInfo('127.0.0.1')
     self.assertEqual(curr_host_info.attrs['last_known_version'],
                      'ForcedUpdate')
     self.assertEqual(curr_host_info.attrs['last_event_type'],
@@ -203,27 +222,36 @@
 
   def testHandleUpdatePingWithSetUpdate(self):
     self.mox.StubOutWithMock(autoupdate.Autoupdate, 'GenerateLatestUpdateImage')
+    self.mox.StubOutWithMock(autoupdate.Autoupdate, '_StoreMetadataToFile')
+    au_mock = self._DummyAutoupdateConstructor()
 
     test_data = _TEST_REQUEST % self.test_dict
     test_label = 'new_update-test/the-new-update'
     new_image_dir = os.path.join(self.static_image_dir, test_label)
     new_url = self.url.replace('update.gz', test_label + '/update.gz')
 
-    autoupdate.Autoupdate.GenerateLatestUpdateImage(
-        self.test_board, 'ForcedUpdate', new_image_dir).AndReturn(
-            'update.gz')
+    au_mock.GenerateLatestUpdateImage(
+        self.test_board, 'ForcedUpdate', new_image_dir).AndReturn(None)
+
+    # Generate a fake payload.
+    os.makedirs(new_image_dir)
+    update_gz = os.path.join(new_image_dir, autoupdate.UPDATE_FILE)
+    with open(update_gz, 'w') as fh:
+      fh.write('')
+
     common_util.GetFileSha1(os.path.join(
         new_image_dir, 'update.gz')).AndReturn(self.sha1)
     common_util.GetFileSha256(os.path.join(
         new_image_dir, 'update.gz')).AndReturn(self.sha256)
     common_util.GetFileSize(os.path.join(
         new_image_dir, 'update.gz')).AndReturn(self.size)
+    au_mock._StoreMetadataToFile(new_image_dir,
+                                 mox.IsA(autoupdate.UpdateMetadata))
     autoupdate_lib.GetUpdateResponse(
         self.sha1, self.sha256, self.size, new_url, False, '3.0',
         False).AndReturn(self.payload)
 
     self.mox.ReplayAll()
-    au_mock = self._DummyAutoupdateConstructor()
     au_mock.HandleSetUpdatePing('127.0.0.1', test_label)
     self.assertEqual(
         au_mock.host_infos.GetHostInfo('127.0.0.1').
@@ -273,22 +301,24 @@
     self.assertFalse(au._CanUpdate('0.16.892.0', '0.16.892.0'))
 
   def testHandleUpdatePingRemotePayload(self):
+    self.mox.StubOutWithMock(autoupdate.Autoupdate, '_GetRemotePayloadAttrs')
+
     remote_urlbase = 'http://remotehost:6666'
     remote_payload_path = 'static/path/to/update.gz'
     remote_url = '/'.join([remote_urlbase, remote_payload_path, 'update.gz'])
+    au_mock = self._DummyAutoupdateConstructor(urlbase=remote_urlbase,
+                                               payload_path=remote_payload_path,
+                                               remote_payload=True)
 
     test_data = _TEST_REQUEST % self.test_dict
 
-    autoupdate.Autoupdate._GetRemotePayloadAttrs(remote_url).AndReturn(
-            (self.sha1, self.sha256, self.size, False))
+    au_mock._GetRemotePayloadAttrs(remote_url).AndReturn(
+        autoupdate.UpdateMetadata(self.sha1, self.sha256, self.size, False))
     autoupdate_lib.GetUpdateResponse(
         self.sha1, self.sha256, self.size, remote_url, False,
         '3.0', False).AndReturn(self.payload)
 
     self.mox.ReplayAll()
-    au_mock = self._DummyAutoupdateConstructor(urlbase=remote_urlbase,
-                                               payload_path=remote_payload_path,
-                                               remote_payload=True)
     self.assertEqual(au_mock.HandleUpdatePing(test_data), self.payload)
     self.mox.VerifyAll()