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/devserver_unittest.py b/devserver_unittest.py
index 4605e34..d730316 100755
--- a/devserver_unittest.py
+++ b/devserver_unittest.py
@@ -6,26 +6,22 @@
 
 """Regression tests for devserver."""
 
-from xml.dom import minidom
 import json
+from xml.dom import minidom
 import os
 import shutil
 import signal
 import subprocess
-import sys
+import tempfile
 import time
 import unittest
 import urllib2
 
 
 # Paths are relative to this script's base directory.
-STATIC_DIR = 'static'
 TEST_IMAGE_PATH = 'testdata/devserver'
-TEST_IMAGE_NAME = 'developer-test.gz'
+TEST_IMAGE_NAME = 'update.gz'
 TEST_IMAGE = TEST_IMAGE_PATH + '/' + TEST_IMAGE_NAME
-TEST_FACTORY_CONFIG = 'testdata/devserver/miniomaha-test.conf'
-TEST_DATA_PATH = '/tmp/devserver-test'
-TEST_CLIENT_PREFIX = 'ChromeOSUpdateEngine'
 EXPECTED_HASH = 'kGcOinJ0vA8vdYX53FN0F5BdwfY='
 
 # Update request based on Omaha v2 protocol format.
@@ -50,9 +46,10 @@
     </app>
 </request>
 """
+
 # TODO(girts): use a random available port.
 UPDATE_URL = 'http://127.0.0.1:8080/update'
-STATIC_URL = 'http://127.0.0.1:8080/static/'
+STATIC_URL = 'http://127.0.0.1:8080/static/archive/'
 
 API_HOST_INFO_BAD_URL = 'http://127.0.0.1:8080/api/hostinfo/'
 API_HOST_INFO_URL = API_HOST_INFO_BAD_URL + '127.0.0.1'
@@ -61,10 +58,8 @@
 API_SET_UPDATE_URL = API_SET_UPDATE_BAD_URL + '127.0.0.1'
 
 API_SET_UPDATE_REQUEST = 'new_update-test/the-new-update'
+DEVSERVER_STARTUP_DELAY = 1
 
-# Run all tests while being in /
-base_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
-os.chdir("/")
 
 class DevserverTest(unittest.TestCase):
   """Regressions tests for devserver."""
@@ -75,42 +70,37 @@
     # Copy in developer-test.gz, as "static/" directory is hardcoded, and it
     # would be very hard to change it (static file serving is handled deep
     # inside webpy).
-    self.image_src = os.path.join(base_dir, TEST_IMAGE)
-    self.image = os.path.join(base_dir, STATIC_DIR, TEST_IMAGE_NAME)
-    if os.path.exists(self.image):
-      os.unlink(self.image)
+    self.test_data_path = tempfile.mkdtemp()
+    self.src_dir = os.path.dirname(__file__)
+    self.image_src = os.path.join(self.src_dir, TEST_IMAGE)
+    self.image = os.path.join(self.test_data_path, TEST_IMAGE_NAME)
     shutil.copy(self.image_src, self.image)
 
-    self.factory_config = os.path.join(base_dir, TEST_FACTORY_CONFIG)
-
   def tearDown(self):
     """Removes testing files."""
-    if os.path.exists(self.image):
-      os.unlink(self.image)
+    shutil.rmtree(self.test_data_path)
 
   # Helper methods begin here.
 
-  def _StartServer(self, data_dir=''):
+  def _StartServer(self):
     """Starts devserver, returns process."""
     cmd = [
         'python',
-        os.path.join(base_dir, 'devserver.py'),
+        os.path.join(self.src_dir, 'devserver.py'),
         'devserver.py',
-        '--factory_config', self.factory_config,
-    ]
-    if data_dir:
-      cmd.append('--data_dir')
-      cmd.append(data_dir)
+        '--archive_dir',
+        self.test_data_path,
+        ]
+
     process = subprocess.Popen(cmd)
+    # Wait for the server to start up.
+    time.sleep(DEVSERVER_STARTUP_DELAY)
     return process.pid
 
-  def VerifyHandleUpdate(self, protocol, data_dir):
-    """Tests running the server and getting an update for the given protocol.
-       Takes an optional data_dir to pass to the devserver. """
-    pid = self._StartServer(data_dir)
+  def VerifyHandleUpdate(self, protocol):
+    """Tests running the server and getting an update for the given protocol."""
+    pid = self._StartServer()
     try:
-      # Wait for the server to start up.
-      time.sleep(1)
       request = urllib2.Request(UPDATE_URL, UPDATE_REQUEST[protocol])
       connection = urllib2.urlopen(request)
       response = connection.read()
@@ -136,12 +126,10 @@
   def VerifyV2Response(self, update):
     """Verifies the update DOM from a v2 response and returns the url."""
     codebase = update.getAttribute('codebase')
-    self.assertEqual(STATIC_URL + TEST_IMAGE_NAME,
-                     codebase)
+    self.assertEqual(STATIC_URL + TEST_IMAGE_NAME, codebase)
 
     hash_value = update.getAttribute('hash')
     self.assertEqual(EXPECTED_HASH, hash_value)
-
     return codebase
 
   def VerifyV3Response(self, update):
@@ -165,55 +153,17 @@
     url = os.path.join(codebase, filename)
     return url
 
-  def VerifyHandleDatadirUpdate(self, protocol):
-    """Tests getting an update from a specified datadir"""
-    # Push the image to the expected path where devserver picks it up.
-    image_path = os.path.join(TEST_DATA_PATH, STATIC_DIR)
-    if not os.path.exists(image_path):
-      os.makedirs(image_path)
-
-    foreign_image = os.path.join(image_path, TEST_IMAGE_NAME)
-    if os.path.exists(foreign_image):
-      os.unlink(foreign_image)
-    shutil.copy(self.image_src, foreign_image)
-
-    self.VerifyHandleUpdate(protocol, TEST_DATA_PATH)
-    os.unlink(foreign_image)
-
   # Tests begin here.
-
-  def testValidateFactoryConfig(self):
-    """Tests --validate_factory_config."""
-    cmd = [
-        'python',
-        os.path.join(base_dir, 'devserver.py'),
-        '--validate_factory_config',
-        '--factory_config', self.factory_config,
-    ]
-    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
-    stdout, _ = process.communicate()
-    self.assertEqual(0, process.returncode)
-    self.assertTrue('Config file looks good.' in stdout)
-
   def testHandleUpdateV2(self):
-    self.VerifyHandleUpdate('2.0', '')
+    self.VerifyHandleUpdate('2.0')
 
   def testHandleUpdateV3(self):
-    self.VerifyHandleUpdate('3.0', '')
-
-  def testHandleDatadirUpdateV2(self):
-    self.VerifyHandleDatadirUpdate('2.0')
-
-  def testHandleDatadirUpdateV3(self):
-    self.VerifyHandleDatadirUpdate('3.0')
+    self.VerifyHandleUpdate('3.0')
 
   def testApiBadSetNextUpdateRequest(self):
     """Tests sending a bad setnextupdate request."""
     pid = self._StartServer()
     try:
-      # Wait for the server to start up.
-      time.sleep(1)
-
       # Send bad request and ensure it fails...
       try:
         request = urllib2.Request(API_SET_UPDATE_URL, '')
@@ -230,9 +180,6 @@
     """Tests contacting a bad setnextupdate url."""
     pid = self._StartServer()
     try:
-      # Wait for the server to start up.
-      time.sleep(1)
-
       # Send bad request and ensure it fails...
       try:
         connection = urllib2.urlopen(API_SET_UPDATE_BAD_URL)
@@ -248,9 +195,6 @@
     """Tests contacting a bad hostinfo url."""
     pid = self._StartServer()
     try:
-      # Wait for the server to start up.
-      time.sleep(1)
-
       # Send bad request and ensure it fails...
       try:
         connection = urllib2.urlopen(API_HOST_INFO_BAD_URL)
@@ -266,9 +210,6 @@
     """Tests using the setnextupdate and hostinfo api commands."""
     pid = self._StartServer()
     try:
-      # Wait for the server to start up.
-      time.sleep(1)
-
       # Send setnextupdate command.
       request = urllib2.Request(API_SET_UPDATE_URL, API_SET_UPDATE_REQUEST)
       connection = urllib2.urlopen(request)
@@ -285,5 +226,6 @@
     finally:
       os.kill(pid, signal.SIGKILL)
 
+
 if __name__ == '__main__':
   unittest.main()