Remove shopfloor server, which is now in factory package.

CQ-DEPEND=CL:32038
BUG=chrome-os-partner:8812
TEST=cbuildbot --remote x86-alex-release

Change-Id: I3bfa105f4d3d4da2bde53e27c932f7f7cfccbce0
Reviewed-on: https://gerrit.chromium.org/gerrit/31978
Reviewed-by: Jon Salz <jsalz@chromium.org>
Tested-by: Jon Salz <jsalz@chromium.org>
Commit-Ready: Jon Salz <jsalz@chromium.org>
diff --git a/factory_setup/README.txt b/factory_setup/README.txt
index fcbabef..5d51fb1 100644
--- a/factory_setup/README.txt
+++ b/factory_setup/README.txt
@@ -10,3 +10,5 @@
 So all scripts must use only the libraries in same folder and not relying on any
 files in cros source tree (except chromeos-common.sh).
 
+Shopfloor scripts have been moved to ../shopfloor in the factory bundle;
+source code is located in the platform/factory repository.
diff --git a/factory_setup/factory_update_server.py b/factory_setup/factory_update_server.py
deleted file mode 100755
index 8874279..0000000
--- a/factory_setup/factory_update_server.py
+++ /dev/null
@@ -1,244 +0,0 @@
-# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-'''Factory Update Server.
-
-The factory update server is implemented as a thread to be started by
-shop floor server.  It monitors the given state_dir and detects
-factory.tar.bz2 file changes and then sets up the new update files
-into factory_dir (under state_dir).  It also starts an rsync server
-to serve factory_dir for clients to fetch update files.
-'''
-
-import errno
-import logging
-import os
-import shutil
-import subprocess
-import threading
-import time
-
-
-FACTORY_DIR = 'factory'
-AUTOTEST_DIR = 'autotest'
-TARBALL_NAME = 'factory.tar.bz2'
-LATEST_SYMLINK = 'latest'
-LATEST_MD5SUM = 'latest.md5sum'
-MD5SUM = 'MD5SUM'
-DEFAULT_RSYNCD_PORT = 8083
-RSYNCD_CONFIG_TEMPLATE = '''port = %(port)d
-pid file = %(pidfile)s
-log file = %(logfile)s
-use chroot = no
-[factory]
-  path = %(factory_dir)s
-  read only = yes
-'''
-
-
-def StartRsyncServer(port, state_dir, factory_dir):
-  configfile = os.path.join(state_dir, 'rsyncd.conf')
-  pidfile = os.path.join(state_dir, 'rsyncd.pid')
-  if os.path.exists(pidfile):
-    # Since rsyncd will not overwrite it if it already exists
-    os.unlink(pidfile)
-  logfile = os.path.join(state_dir, 'rsyncd.log')
-  data = RSYNCD_CONFIG_TEMPLATE % dict(port=port,
-                                       pidfile=pidfile,
-                                       logfile=logfile,
-                                       factory_dir=factory_dir)
-  with open(configfile, 'w') as f:
-    f.write(data)
-
-  p = subprocess.Popen(('rsync', '--daemon', '--no-detach',
-                        '--config=%s' % configfile))
-  logging.info('Rsync server (pid %d) started on port %d', p.pid, port)
-  return p
-
-
-def StopRsyncServer(rsyncd_process):
-  logging.info('Stopping rsync server (pid %d)', rsyncd_process.pid)
-  rsyncd_process.terminate()
-
-  # If not terminated in a second, send a kill -9.
-  def WaitAndKill():
-    time.sleep(1)
-    try:
-      rsyncd_process.kill()
-    except:
-      pass
-  thread = threading.Thread(target=WaitAndKill)
-  thread.daemon = True
-  thread.start()
-
-  rsyncd_process.wait()
-  logging.debug('Rsync server stopped')
-
-
-def CalculateMd5sum(filename):
-  p = subprocess.Popen(('md5sum', filename), stdout=subprocess.PIPE)
-  output, _ = p.communicate()
-  return output.split()[0]
-
-
-class FactoryUpdateServer():
-
-  def __init__(self, state_dir, rsyncd_port=DEFAULT_RSYNCD_PORT,
-               poll_interval_sec=1):
-    self.state_dir = state_dir
-    self.factory_dir = os.path.join(state_dir, FACTORY_DIR)
-    self.rsyncd_port = rsyncd_port
-    if not os.path.exists(self.factory_dir):
-      os.mkdir(self.factory_dir)
-    self.poll_interval_sec = poll_interval_sec
-    self._stop_event = threading.Event()
-    self._rsyncd = StartRsyncServer(rsyncd_port, state_dir, self.factory_dir)
-    self._tarball_path = os.path.join(self.state_dir, TARBALL_NAME)
-
-    self._thread = None
-    self._last_stat = None
-    self._run_count = 0
-    self._update_count = 0
-    self._errors = 0
-
-  def Start(self):
-    assert not self._thread
-    self._thread = threading.Thread(target=self.Run)
-    self._thread.start()
-
-  def Stop(self):
-    if self._rsyncd:
-      StopRsyncServer(self._rsyncd)
-      self._rsyncd = None
-
-    self._stop_event.set()
-
-    if self._thread:
-      self._thread.join()
-      self._thread = None
-
-  def _HandleTarball(self):
-    new_tarball_path = self._tarball_path + '.new'
-
-    # Copy the tarball to avoid possible race condition.
-    shutil.copyfile(self._tarball_path, new_tarball_path)
-
-    # Calculate MD5.
-    md5sum = CalculateMd5sum(new_tarball_path)
-    logging.info('Processing tarball ' + self._tarball_path + ' (md5sum=%s)',
-                 md5sum)
-
-    # Move to a file containing the MD5.
-    final_tarball_path = self._tarball_path + '.' + md5sum
-    os.rename(new_tarball_path, final_tarball_path)
-
-    # Create subfolder to hold tarball contents.
-    final_subfolder = os.path.join(self.factory_dir, md5sum)
-    final_md5sum = os.path.join(final_subfolder, FACTORY_DIR, MD5SUM)
-    if os.path.exists(final_subfolder):
-      if not (os.path.exists(final_md5sum) and
-              open(final_md5sum).read().strip() == md5sum):
-        logging.warn('Update directory %s appears not to be set up properly '
-                     '(missing or bad MD5SUM); delete it and restart update '
-                     'server?', final_subfolder)
-        return
-      logging.info('Update is already deployed into %s', final_subfolder)
-    else:
-      new_subfolder = final_subfolder + '.new'
-      if os.path.exists(new_subfolder):
-        shutil.rmtree(new_subfolder)
-      os.mkdir(new_subfolder)
-
-      # Extract tarball.
-      success = False
-      try:
-        try:
-          logging.info('Staging into %s', new_subfolder)
-          subprocess.check_call(('tar', '-xjf', final_tarball_path,
-                                 '-C', new_subfolder))
-        except subprocess.CalledProcessError:
-          logging.error('Failed to extract update files to subfolder %s',
-                        new_subfolder)
-          return
-
-        missing_dirs = [
-          d for d in (FACTORY_DIR, AUTOTEST_DIR)
-          if not os.path.exists(os.path.join(new_subfolder, d))]
-        if missing_dirs:
-          logging.error('Tarball is missing directories: %r', missing_dirs)
-          return
-
-        factory_dir = os.path.join(new_subfolder, FACTORY_DIR)
-        with open(os.path.join(factory_dir, MD5SUM), 'w') as f:
-          f.write(md5sum)
-
-        # Extracted and verified.  Move it in place.
-        os.rename(new_subfolder, final_subfolder)
-        logging.info('Moved to final directory %s', final_subfolder)
-
-        success = True
-        self._update_count += 1
-      finally:
-        if os.path.exists(new_subfolder):
-          shutil.rmtree(new_subfolder, ignore_errors=True)
-        if (not success) and os.path.exists(final_subfolder):
-          shutil.rmtree(final_subfolder, ignore_errors=True)
-
-    # Update symlink and latest.md5sum.
-    linkname = os.path.join(self.factory_dir, LATEST_SYMLINK)
-    if os.path.islink(linkname):
-      os.remove(linkname)
-    os.symlink(md5sum, linkname)
-    with open(os.path.join(self.factory_dir, LATEST_MD5SUM), 'w') as f:
-      f.write(md5sum)
-    logging.info('Update files (%s) setup complete', md5sum)
-
-  def Run(self):
-    while True:
-      try:
-        self.RunOnce()
-      except:
-        logging.exception('Error in event loop')
-
-      self._stop_event.wait(self.poll_interval_sec)
-      if self._stop_event.is_set():
-        break
-
-  def RunOnce(self):
-    try:
-      self._run_count += 1
-
-      try:
-        stat = os.stat(self._tarball_path)
-      except OSError as e:
-        if e.errno == errno.ENOENT:
-          # File doesn't exist
-          return
-        raise
-
-      if (self._last_stat and
-          ((stat.st_mtime, stat.st_size) ==
-           (self._last_stat.st_mtime, self._last_stat.st_size))):
-        # No change
-        return
-
-      self._last_stat = stat
-      try:
-        with open(os.devnull, "w") as devnull:
-          logging.info('Verifying integrity of tarball %s', self._tarball_path)
-          subprocess.check_call(['tar', '-tjf', self._tarball_path],
-                                stdout=devnull, stderr=devnull)
-      except subprocess.CalledProcessError:
-        # E.g., still copying
-        logging.warn('Tarball %s (%d bytes) is corrupt or incomplete',
-                     self._tarball_path, stat.st_size)
-        return
-
-      # Re-stat in case it finished being written while we were
-      # verifying it.
-      self._last_stat = os.stat(self._tarball_path)
-      self._HandleTarball()
-    except:
-      self._errors += 1
-      raise
diff --git a/factory_setup/factory_update_server_unittest.py b/factory_setup/factory_update_server_unittest.py
deleted file mode 100755
index dcb1cae..0000000
--- a/factory_setup/factory_update_server_unittest.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-'''Tests for Factory Update Server.'''
-
-import factory_update_server
-import os
-import shutil
-import sys
-import tempfile
-import time
-import unittest
-
-
-BASE_DIR = os.path.dirname(os.path.realpath(__file__))
-
-
-class BasicTests(unittest.TestCase):
-  def testMd5sumCalculation(self):
-    md5sum = factory_update_server.CalculateMd5sum(
-        os.path.join(BASE_DIR, 'testdata/shopfloor/factory.tar.bz2'))
-    self.assertEqual(md5sum, '18cac06201e65e060f757193c153cacb')
-
-
-class FactoryUpdateServerTest(unittest.TestCase):
-  def setUp(self):
-    self.work_dir = tempfile.mkdtemp(prefix='dts')
-    self._CreateUpdateServer()
-
-  def _CreateUpdateServer(self):
-    self.update_server = factory_update_server.FactoryUpdateServer(
-        self.work_dir, poll_interval_sec=0.1)
-
-  def tearDown(self):
-    self.update_server.Stop()
-    self.assertEqual(0, self.update_server._errors)
-    shutil.rmtree(self.work_dir)
-
-  def testThread(self):
-    # Start the thread (make sure it starts/stops properly).
-    self.update_server.Start()
-    self.update_server.Stop()
-    self.assertTrue(self.update_server._run_count)
-
-  def testLogic(self):
-    self.update_server.RunOnce()
-
-    self.assertTrue(os.path.isdir(os.path.join(self.work_dir, 'factory')))
-    self.assertTrue(self.update_server._rsyncd.poll() is None)
-
-    # No latest.md5sum file at the beginning.
-    md5file = os.path.join(self.work_dir, 'factory/latest.md5sum')
-    self.assertFalse(os.path.exists(md5file))
-    self.assertEqual(0, self.update_server._update_count)
-
-    tarball_src = os.path.join(BASE_DIR, 'testdata/shopfloor/factory.tar.bz2')
-    tarball_dest = os.path.join(self.work_dir, 'factory.tar.bz2')
-
-    # Put partially-written factory.tar.bz2 into the working folder.
-    with open(tarball_dest, "w") as f:
-      print >>f, "Not really a bzip2"
-    self.update_server.RunOnce()
-
-    # Put factory.tar.bz2 into the working folder.
-    shutil.copy(tarball_src, tarball_dest)
-    # Kick the update server
-    self.update_server.RunOnce()
-
-    # Check that latest.md5sum is created with correct value and update files
-    # extracted.
-    self.assertTrue(os.path.isfile(md5file), md5file)
-    with open(md5file, 'r') as f:
-      self.assertEqual('18cac06201e65e060f757193c153cacb', f.read().strip())
-    self.assertTrue(os.path.isdir(os.path.join(
-        self.work_dir, 'factory/18cac06201e65e060f757193c153cacb')))
-    self.assertEqual(1, self.update_server._update_count)
-
-    # Kick the update server again.  Nothing should happen.
-    self.update_server.RunOnce()
-    self.assertEqual(1, self.update_server._update_count)
-
-    # Stop the update server and set up a new one.  The md5sum file
-    # should be recreated.
-    self.update_server.Stop()
-    del self.update_server
-    os.unlink(md5file)
-    self._CreateUpdateServer()
-    self.update_server.RunOnce()
-    with open(md5file, 'r') as f:
-      self.assertEqual('18cac06201e65e060f757193c153cacb', f.read().strip())
-
-if __name__ == '__main__':
-  unittest.main()
diff --git a/factory_setup/shopfloor/__init__.py b/factory_setup/shopfloor/__init__.py
deleted file mode 100644
index aa4a767..0000000
--- a/factory_setup/shopfloor/__init__.py
+++ /dev/null
@@ -1,276 +0,0 @@
-# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-"""The package initialization and abstract base class for shop floor systems.
-
-Every implementations should inherit ShopFloorBase and override the member
-functions to interact with their real shop floor system.
-"""
-
-import csv
-import glob
-import logging
-import os
-import time
-import xmlrpclib
-
-# In current implementation, we use xmlrpclib.Binary to prepare blobs.
-from xmlrpclib import Binary
-
-import factory_update_server
-
-
-EVENTS_DIR = 'events'
-REPORTS_DIR = 'reports'
-UPDATE_DIR = 'update'
-HWID_UPDATER_PATTERN = 'hwid_*'
-REGISTRATION_CODE_LOG_CSV = 'registration_code_log.csv'
-
-
-class ShopFloorException(Exception):
-  pass
-
-
-class ShopFloorBase(object):
-  """Base class for shopfloor servers.
-
-  Properties:
-    config: The configuration data provided by the '-c' argument to
-      shopfloor_server.
-    data_dir: The top-level directory for shopfloor data.
-  """
-
-  NAME = 'ShopFloorBase'
-  VERSION = 4
-
-  def _InitBase(self):
-    """Initializes the base class."""
-    if not os.path.exists(self.data_dir):
-      logging.warn('Data directory %s does not exist; creating it',
-                   self.data_dir)
-      os.makedirs(self.data_dir)
-
-    self._registration_code_log = open(
-        os.path.join(self.data_dir, REGISTRATION_CODE_LOG_CSV), "ab", 0)
-    class Dialect(csv.excel):
-      lineterminator = '\n'
-    self._registration_code_writer = csv.writer(self._registration_code_log,
-                                                dialect=Dialect)
-
-    # Put events uploaded from DUT in the "events" directory in data_dir.
-    self._events_dir = os.path.join(self.data_dir, EVENTS_DIR)
-    if not os.path.isdir(self._events_dir):
-      os.mkdir(self._events_dir)
-
-    # Dynamic test directory for holding updates is called "update" in data_dir.
-    update_dir = os.path.join(self.data_dir, UPDATE_DIR)
-    if os.path.exists(update_dir):
-      self.update_dir = os.path.realpath(update_dir)
-      self.update_server = factory_update_server.FactoryUpdateServer(
-          self.update_dir)
-    else:
-      logging.warn('Update directory %s does not exist; '
-                   'disabling update server.', update_dir)
-      self.update_dir = None
-      self.update_server = None
-
-  def _StartBase(self):
-    """Starts the base class."""
-    if self.update_server:
-      logging.debug('Starting factory update server...')
-      self.update_server.Start()
-
-  def _StopBase(self):
-    """Stops the base class."""
-    if self.update_server:
-      self.update_server.Stop()
-
-  def Init(self):
-    """Initializes the shop floor system.
-
-    Subclasses should implement this rather than __init__.
-    """
-    pass
-
-  def Ping(self):
-    """Always returns true (for client to check if server is working)."""
-    return True
-
-  def GetHWID(self, serial):
-    """Returns appropriate HWID according to given serial number.
-
-    Args:
-      serial: A string of device serial number.
-
-    Returns:
-      The associated HWID string.
-
-    Raises:
-      ValueError if serial is invalid, or other exceptions defined by individual
-      modules. Note this will be converted to xmlrpclib.Fault when being used as
-      a XML-RPC server module.
-    """
-    raise NotImplementedError('GetHWID')
-
-  def _GetHWIDUpdaterPath(self):
-    """Returns the path to HWID updater bundle, if available.
-
-    Returns:
-      The path to the file (or None).
-
-    Raises:
-      ShopFloorException if there are >1 HWID bundles available.
-    """
-    bundles = glob.glob(os.path.join(self.data_dir, HWID_UPDATER_PATTERN))
-    if not bundles:
-      return None
-
-    if len(bundles) > 1:
-      raise ShopFloorException('Multiple HWID bundles available: %s (please '
-                               'delete all but one)' % bundles)
-
-    return bundles[0]
-
-  def GetHWIDUpdater(self):
-    """Returns a HWID updater bundle, if available.
-
-    Returns:
-      The binary-encoded contents of a file named 'hwid_*' in the data
-      directory.  If there are no such files, returns None.
-
-    Raises:
-      ShopFloorException if there are >1 HWID bundles available.
-    """
-    path = self._GetHWIDUpdaterPath()
-    return open(path).read() if path else None
-
-  def GetVPD(self, serial):
-    """Returns VPD data to set (in dictionary format).
-
-    Args:
-      serial: A string of device serial number.
-
-    Returns:
-      VPD data in dict {'ro': dict(), 'rw': dict()}
-
-    Raises:
-      ValueError if serial is invalid, or other exceptions defined by individual
-      modules. Note this will be converted to xmlrpclib.Fault when being used as
-      a XML-RPC server module.
-    """
-    raise NotImplementedError('GetVPD')
-
-  def UploadReport(self, serial, report_blob, report_name=None):
-    """Uploads a report file.
-
-    Args:
-      serial: A string of device serial number.
-      report_blob: Blob of compressed report to be stored (must be prepared by
-          shopfloor.Binary)
-      report_name: (Optional) Suggested report file name. This is uslally
-          assigned by factory test client programs (ex, gooftool); however
-          server implementations still may use other names to store the report.
-
-    Returns:
-      True on success.
-
-    Raises:
-      ValueError if serial is invalid, or other exceptions defined by individual
-      modules. Note this will be converted to xmlrpclib.Fault when being used as
-      a XML-RPC server module.
-    """
-    raise NotImplementedError('UploadReport')
-
-  def Finalize(self, serial):
-    """Marks target device (by serial) to be ready for shipment.
-
-    Args:
-      serial: A string of device serial number.
-
-    Returns:
-      True on success.
-
-    Raises:
-      ValueError if serial is invalid, or other exceptions defined by individual
-      modules. Note this will be converted to xmlrpclib.Fault when being used as
-      a XML-RPC server module.
-    """
-    raise NotImplementedError('Finalize')
-
-  def GetRegistrationCodeMap(self, serial):
-    """Returns the registration code map for the given serial number.
-
-    Returns:
-      {'user': registration_code, 'group': group_code}
-
-    Raises:
-      ValueError if serial is invalid, or other exceptions defined by individual
-      modules. Note this will be converted to xmlrpclib.Fault when being used as
-      a XML-RPC server module.
-    """
-    raise NotImplementedError('GetRegistrationCode')
-
-  def LogRegistrationCodeMap(self, hwid, registration_code_map):
-    """Logs that a particular registration code has been used."""
-    self._registration_code_writer.writerow(
-        [hwid, registration_code_map['user'], registration_code_map['group'],
-         time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())])
-    os.fdatasync(self._registration_code_log.fileno())
-
-  def GetTestMd5sum(self):
-    """Gets the latest md5sum of dynamic test tarball.
-
-    Returns:
-      A string of md5sum.  None if no dynamic test tarball is installed.
-    """
-    if not self.update_dir:
-      return None
-
-    md5file = os.path.join(self.update_dir,
-                           factory_update_server.FACTORY_DIR,
-                           factory_update_server.LATEST_MD5SUM)
-    if not os.path.isfile(md5file):
-      return None
-    with open(md5file, 'r') as f:
-      return f.readline().strip()
-
-  def GetUpdatePort(self):
-    """Returns the port to use for rsync updates.
-
-    Returns:
-      The port, or None if there is no update server available.
-    """
-    return self.update_server.rsyncd_port if self.update_server else None
-
-  def UploadEvent(self, log_name, chunk):
-    """Uploads a chunk of events.
-
-    Args:
-      log_name: A string of the event log filename. Event logging module creates
-          event files with an unique identifier (uuid) as part of the filename.
-      chunk: A string containing one or more events. Events are in YAML format
-          and separated by a "---" as specified by YAML. A chunk contains one or
-          more events with separator.
-
-    Returns:
-      True on success.
-
-    Raises:
-      IOError if unable to save the chunk of events.
-    """
-    if not os.path.exists(self._events_dir):
-      os.makedirs(self._events_dir)
-
-    if isinstance(chunk, Binary):
-      chunk = chunk.data
-
-    log_file = os.path.join(self._events_dir, log_name)
-    with open(log_file, 'a') as f:
-      f.write(chunk)
-    return True
-
-  def GetTime(self):
-    """Returns the current time in seconds since the epoch."""
-    return time.time()
diff --git a/factory_setup/shopfloor/simple.py b/factory_setup/shopfloor/simple.py
deleted file mode 100644
index e36114b..0000000
--- a/factory_setup/shopfloor/simple.py
+++ /dev/null
@@ -1,185 +0,0 @@
-# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""ChromeOS factory shop floor system implementation, using CSV input.
-
-This module provides an easy way to setup and use shop floor system. Use Google
-Docs or Excel to create a spreadsheet and export as CSV (comma separated
-values), with following fields:
-
-  serial_number: The serial number of each device.
-  hwid: The HWID string assigned for each serial number.
-  ro_vpd_*: Read-only VPD values. Example: ro_vpd_test_data will be converted to
-            "test_data" in RO_VPD section.
-  rw_vpd_*: Read-writeable VPD values, using same syntax described in ro_vpd_*.
-
-To use this module, run following command in factory_setup folder:
-  shopfloor_server.py -m shopfloor.simple.ShopFloor -c PATH_TO_CSV_FILE.csv
-
-You can find a sample CSV file in in:
-  factory_setup/test_data/shopfloor/simple.csv
-"""
-
-import csv
-import logging
-import os
-import re
-import time
-
-from xmlrpclib import Binary
-
-import shopfloor
-
-
-class ShopFloor(shopfloor.ShopFloorBase):
-  """Sample shop floor system, using CSV file as input.
-
-  Device data is read from a 'devices.csv' file in the data directory.
-  """
-  NAME = "CSV-file based shop floor system"
-  VERSION = 4
-
-  def Init(self):
-    devices_csv = os.path.join(self.data_dir, 'devices.csv')
-    logging.info("Parsing %s...", devices_csv)
-    self.data_store = LoadCsvData(devices_csv)
-    logging.info("Loaded %d entries from %s.",
-                 len(self.data_store), devices_csv)
-
-    # Put uploaded reports in a "reports" folder inside data_dir.
-    self.reports_dir = os.path.join(self.data_dir, 'reports')
-    if not os.path.isdir(self.reports_dir):
-      os.mkdir(self.reports_dir)
-
-    # Try to touch some files inside directory, to make sure the directory is
-    # writable, and everything I/O system is working fine.
-    stamp_file = os.path.join(self.reports_dir, ".touch")
-    with open(stamp_file, "w") as stamp_handle:
-      stamp_handle.write("%s - VERSION %s" % (self.NAME, self.VERSION))
-    os.remove(stamp_file)
-
-  def _CheckSerialNumber(self, serial):
-    """Checks if serial number is valid, otherwise raise ValueError."""
-    if serial in self.data_store:
-      return True
-    message = "Unknown serial number: %s" % serial
-    logging.error(message)
-    raise ValueError(message)
-
-  def GetHWID(self, serial):
-    self._CheckSerialNumber(serial)
-    return self.data_store[serial]['hwid']
-
-  def GetVPD(self, serial):
-    self._CheckSerialNumber(serial)
-    return self.data_store[serial]['vpd']
-
-  def GetRegistrationCodeMap(self, serial):
-    self._CheckSerialNumber(serial)
-    registration_code_map = self.data_store[serial]['registration_code_map']
-    self.LogRegistrationCodeMap(self.data_store[serial]['hwid'],
-                                registration_code_map)
-    return registration_code_map
-
-  def UploadReport(self, serial, report_blob, report_name=None):
-    def is_gzip_blob(blob):
-      """Check (not 100% accurate) if input blob is gzipped."""
-      GZIP_MAGIC = '\x1f\x8b'
-      return blob[:len(GZIP_MAGIC)] == GZIP_MAGIC
-
-    self._CheckSerialNumber(serial)
-    if isinstance(report_blob, shopfloor.Binary):
-      report_blob = report_blob.data
-    if not report_name:
-      report_name = ('%s-%s.rpt' % (re.sub('[^a-zA-Z0-9]', '', serial),
-                                    time.strftime("%Y%m%d-%H%M%S%z")))
-      if is_gzip_blob(report_blob):
-        report_name += ".gz"
-    report_path = os.path.join(self.reports_dir, report_name)
-    with open(report_path, "wb") as report_obj:
-      report_obj.write(report_blob)
-
-  def Finalize(self, serial):
-    # Finalize is currently not implemented.
-    self._CheckSerialNumber(serial)
-    logging.info("Finalized: %s", serial)
-
-
-def LoadCsvData(filename):
-  """Loads a CSV file and returns structured shop floor system data."""
-  # Required fields.
-  KEY_SERIAL_NUMBER = 'serial_number'
-  KEY_HWID = 'hwid'
-  KEY_REGISTRATION_CODE_USER = 'registration_code_user'
-  KEY_REGISTRATION_CODE_GROUP = 'registration_code_group'
-
-  # Optional fields.
-  PREFIX_RO_VPD = 'ro_vpd_'
-  PREFIX_RW_VPD = 'rw_vpd_'
-  VPD_PREFIXES = (PREFIX_RO_VPD, PREFIX_RW_VPD)
-
-  REQUIRED_KEYS = (KEY_SERIAL_NUMBER, KEY_HWID)
-  OPTIONAL_KEYS = (KEY_REGISTRATION_CODE_USER, KEY_REGISTRATION_CODE_GROUP)
-  OPTIONAL_PREFIXES = VPD_PREFIXES
-
-  def check_field_name(name):
-    """Checks if argument is an valid input name."""
-    if name in REQUIRED_KEYS or name in OPTIONAL_KEYS:
-      return True
-    for prefix in OPTIONAL_PREFIXES:
-      if name.startswith(prefix):
-        return True
-    return False
-
-  def build_vpd(source):
-    """Builds VPD structure by input source."""
-    vpd = {'ro': {}, 'rw': {}}
-    for key, value in source.items():
-      for prefix in VPD_PREFIXES:
-        if not key.startswith(prefix):
-          continue
-        # Key format: $type_vpd_$name (ex, ro_vpd_serial_number)
-        (key_type, _, key_name) = key.split('_', 2)
-        if value is None:
-          continue
-        vpd[key_type][key_name.strip()] = value.strip()
-    return vpd
-
-  def build_registration_code_map(source):
-    """Builds registration_code_map structure.
-
-    Returns:
-      A dict containing 'user' and 'group' keys.
-    """
-    return {'user': source.get(KEY_REGISTRATION_CODE_USER),
-            'group': source.get(KEY_REGISTRATION_CODE_GROUP)}
-
-  data = {}
-  with open(filename, 'rb') as source:
-    reader = csv.DictReader(source)
-    row_number = 0
-    for row in reader:
-      row_number += 1
-      if KEY_SERIAL_NUMBER not in row:
-        raise ValueError("Missing %s in row %d" % (KEY_SERIAL_NUMBER,
-                                                   row_number))
-      serial_number = row[KEY_SERIAL_NUMBER].strip()
-      hwid = row[KEY_HWID].strip()
-
-      # Checks data validity.
-      if serial_number in data:
-        raise ValueError("Duplicated %s in row %d: %s" %
-                         (KEY_SERIAL_NUMBER, row_number, serial_number))
-      if None in row:
-        raise ValueError("Extra fields in row %d: %s" %
-                         (row_number, ','.join(row[None])))
-      for field in row:
-        if not check_field_name(field):
-          raise ValueError("Invalid field: %s" % field)
-
-      entry = {'hwid': hwid,
-               'vpd': build_vpd(row),
-               'registration_code_map': build_registration_code_map(row)}
-      data[serial_number] = entry
-  return data
diff --git a/factory_setup/shopfloor/template.py b/factory_setup/shopfloor/template.py
deleted file mode 100644
index 11c57cf..0000000
--- a/factory_setup/shopfloor/template.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-"""
-(CHANGE THIS) This is a template for creating new implementation of factory shop
-floor system module.
-"""
-
-
-# Add required python modules here.
-import logging
-
-# Always include 'shopfloor' for the abstract base class.
-import shopfloor
-
-
-class ShopFloor(shopfloor.ShopFloorBase):
-  """(CHANGE THIS) Implementation for factory shop floor system."""
-  NAME = '(CHANGE THIS) Shopfloor system template'
-  VERSION = 1
-
-  def __init__(self, config=None):
-    """See help(ShopFloorBase.__init__)"""
-    logging.info('Shop floor system started.')
-
-  def GetHWID(self, serial):
-    """See help(ShopFloorBase.GetHWID)"""
-    raise NotImplementedError('GetHWID')
-
-  def GetVPD(self, serial):
-    """See help(ShopFloorBase.GetVPD)"""
-    raise NotImplementedError('GetVPD')
-
-  def UploadReport(self, serial, report_blob, report_name=None):
-    """See help(ShopFloorBase.UploadReport)"""
-    raise NotImplementedError('UploadReport')
-
-  def Finalize(self, serial):
-    """See help(ShopFloorBase.Finalize)"""
-    raise NotImplementedError('Finalize')
-
-  def GetTestMd5sum(self):
-    """See help(ShopFloorBase.GetTestMd5sum)"""
-    raise NotImplementedError('GetTestMd5sum')
-
-  def UploadEvent(self, log_name, chunk):
-    """See help(ShopFloorBase.UploadEvent)"""
-    raise NotImplementedError('UploadEvent')
diff --git a/factory_setup/shopfloor_server b/factory_setup/shopfloor_server
deleted file mode 120000
index 6cc4b35..0000000
--- a/factory_setup/shopfloor_server
+++ /dev/null
@@ -1 +0,0 @@
-shopfloor_server.py
\ No newline at end of file
diff --git a/factory_setup/shopfloor_server.py b/factory_setup/shopfloor_server.py
deleted file mode 100755
index 091ef78..0000000
--- a/factory_setup/shopfloor_server.py
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-'''
-This file starts a server for factory shop floor system.
-
-To use it, invoke as a standalone program and assign the shop floor system
-module you want to use (modules are located in "shopfloor" subdirectory).
-
-Example:
-  ./shopfloor_server -m shopfloor.simple.ShopFloor
-'''
-
-
-import hashlib
-import imp
-import logging
-import optparse
-import os
-import shopfloor
-import socket
-import SimpleXMLRPCServer
-from subprocess import Popen, PIPE
-
-
-_DEFAULT_SERVER_PORT = 8082
-# By default, this server is supposed to serve on same host running omaha
-# server, accepting connections from client devices; so the address to bind is
-# "all interfaces (0.0.0.0)". For partners running server on clients, they may
-# want to change address to "localhost".
-_DEFAULT_SERVER_ADDRESS = '0.0.0.0'
-
-
-def _LoadShopFloorModule(name):
-  '''Loads a specified python module.
-
-  Args:
-    name: Name of target module, in PACKAGE.MODULE.CLASS format.
-
-  Returns:
-    Module reference.
-  '''
-  (module_path, _, class_name) = name.rpartition('.')
-  logging.debug('_LoadShopFloorModule: trying %s.%s', module_path, class_name)
-  return getattr(__import__(module_path, fromlist=[class_name]), class_name)
-
-
-def _RunAsServer(address, port, instance):
-  '''Starts a XML-RPC server in given address and port.
-
-  Args:
-    address: Address to bind server.
-    port: Port for server to listen.
-    instance: Server instance for incoming XML RPC requests.
-
-  Returns:
-    Never returns if the server is started successfully, otherwise some
-    exception will be raised.
-  '''
-  server = SimpleXMLRPCServer.SimpleXMLRPCServer((address, port),
-                                                 allow_none=True,
-                                                 logRequests=False)
-  server.register_introspection_functions()
-  server.register_instance(instance)
-  logging.info('Server started: http://%s:%s "%s" version %s',
-               address, port, instance.NAME, instance.VERSION)
-  server.serve_forever()
-
-
-def main():
-  '''Main entry when being invoked by command line.'''
-  parser = optparse.OptionParser()
-  parser.add_option('-a', '--address', dest='address', metavar='ADDR',
-                    default=_DEFAULT_SERVER_ADDRESS,
-                    help='address to bind (default: %default)')
-  parser.add_option('-p', '--port', dest='port', metavar='PORT', type='int',
-                    default=_DEFAULT_SERVER_PORT,
-                    help='port to bind (default: %default)')
-  parser.add_option('-m', '--module', dest='module', metavar='MODULE',
-                    default='shopfloor.ShopFloorBase',
-                    help=('shop floor system module to load, in '
-                          'PACKAGE.MODULE.CLASS format. E.g.: '
-                          'shopfloor.simple.ShopFloor (default: %default)'))
-  parser.add_option('-c', '--config', dest='config', metavar='CONFIG',
-                    help='configuration data for shop floor system')
-  parser.add_option('-d', '--data-dir', dest='data_dir', metavar='DIR',
-                    default=os.path.join(
-                        os.path.dirname(os.path.realpath(__file__)),
-                        'shopfloor_data'),
-                    help=('data directory for shop floor system '
-                          '(default: %default)'))
-  parser.add_option('-v', '--verbose', action='count', dest='verbose',
-                    help='increase message verbosity')
-  parser.add_option('-q', '--quiet', action='store_true', dest='quiet',
-                    help='turn off verbose messages')
-  (options, args) = parser.parse_args()
-  if args:
-    parser.error('Invalid args: %s' % ' '.join(args))
-
-  if not options.module:
-    parser.error('You need to assign the module to be loaded (-m).')
-
-  verbosity_map = {0: logging.INFO,
-                   1: logging.DEBUG}
-  verbosity = verbosity_map.get(options.verbose or 0, logging.NOTSET)
-  log_format = '%(asctime)s %(levelname)s '
-  if options.verbose > 0:
-    log_format += '(%(filename)s:%(lineno)d) '
-  log_format += '%(message)s'
-  logging.basicConfig(level=verbosity, format=log_format)
-  if options.quiet:
-    logging.disable(logging.INFO)
-
-  # Disable all DNS lookups, since otherwise the logging code may try to
-  # resolve IP addresses, which may delay request handling.
-  def FakeGetFQDN(name=''):
-    return name or 'localhost'
-  socket.getfqdn = FakeGetFQDN
-
-  try:
-    logging.debug('Loading shop floor system module: %s', options.module)
-    instance = _LoadShopFloorModule(options.module)()
-
-    if not isinstance(instance, shopfloor.ShopFloorBase):
-      logging.critical('Module does not inherit ShopFloorBase: %s',
-                       options.module)
-      exit(1)
-
-    instance.data_dir = options.data_dir
-    instance.config = options.config
-    instance._InitBase()
-    instance.Init()
-  except:
-    logging.exception('Failed loading module: %s', options.module)
-    exit(1)
-
-  # Find the HWID updater (if any).  Throw an exception if there are >1.
-  hwid_updater_path = instance._GetHWIDUpdaterPath()
-  if hwid_updater_path:
-    logging.info('Using HWID updater %s (md5sum %s)' % (
-        hwid_updater_path,
-        hashlib.md5(open(hwid_updater_path).read()).hexdigest()))
-  else:
-    logging.warn('No HWID updater id currently available; add a single '
-                 'file named %s to enable dynamic updating of HWIDs.' %
-                 os.path.join(options.data_dir, shopfloor.HWID_UPDATER_PATTERN))
-
-  try:
-    instance._StartBase()
-    logging.debug('Starting RPC server...')
-    _RunAsServer(address=options.address, port=options.port, instance=instance)
-  finally:
-    instance._StopBase()
-
-
-if __name__ == '__main__':
-  main()
diff --git a/factory_setup/shopfloor_unittest.py b/factory_setup/shopfloor_unittest.py
deleted file mode 100755
index 839df08..0000000
--- a/factory_setup/shopfloor_unittest.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Unit tests for shop floor server."""
-
-import os
-import re
-import shutil
-import subprocess
-import sys
-import tempfile
-import time
-import unittest
-import xmlrpclib
-
-import shopfloor
-import shopfloor_server
-
-
-class ShopFloorServerTest(unittest.TestCase):
-
-  def setUp(self):
-    '''Starts shop floor server and creates client proxy.'''
-    self.server_port = shopfloor_server._DEFAULT_SERVER_PORT
-    self.base_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
-    self.data_dir = tempfile.mkdtemp(prefix='shopfloor_data.')
-    self.registration_code_log = (
-        os.path.join(self.data_dir, shopfloor.REGISTRATION_CODE_LOG_CSV))
-    csv_source = os.path.join(self.base_dir, 'testdata', 'shopfloor',
-                              'devices.csv')
-    csv_work = os.path.join(self.data_dir, 'devices.csv')
-    shutil.copyfile(csv_source, csv_work)
-    os.mkdir(os.path.join(self.data_dir, shopfloor.UPDATE_DIR))
-    os.mkdir(os.path.join(self.data_dir, shopfloor.UPDATE_DIR, 'factory'))
-
-    cmd = ['python', os.path.join(self.base_dir, 'shopfloor_server.py'),
-           '-q', '-a', 'localhost', '-p', str(self.server_port),
-           '-m', 'shopfloor.simple.ShopFloor',
-           '-d', self.data_dir]
-    self.process = subprocess.Popen(cmd)
-    self.proxy = xmlrpclib.ServerProxy('http://localhost:%s' % self.server_port,
-                                       allow_none=True)
-    # Waits the server to be ready, up to 1 second.
-    for i in xrange(10):
-      try:
-        self.proxy.Ping()
-        break
-      except:
-        time.sleep(0.1)
-        continue
-    else:
-      self.fail('Server never came up')
-
-  def tearDown(self):
-    '''Terminates shop floor server'''
-    self.process.terminate()
-    self.process.wait()
-    shutil.rmtree(self.data_dir)
-
-  def testGetHWID(self):
-    # Valid HWIDs range from CR001001 to CR001025
-    for i in range(25):
-      serial = 'CR0010%02d' % (i + 1)
-      result = self.proxy.GetHWID(serial)
-      self.assertTrue(result.startswith('MAGICA '))
-      self.assertEqual(len(result.split(' ')), 4)
-
-    # Test invalid serial numbers
-    self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, '0000')
-    self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, 'garbage')
-    self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, '')
-    self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, None)
-    self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, 'CR001000')
-    self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, 'CR001026')
-
-  def testGetHWIDUpdater_None(self):
-    self.assertEquals(None, self.proxy.GetHWIDUpdater())
-
-  def testGetHWIDUpdater_One(self):
-    with open(os.path.join(self.data_dir, 'hwid_updater.sh'), 'w') as f:
-      f.write('foobar')
-    self.assertEquals('foobar', self.proxy.GetHWIDUpdater().data)
-
-  def testGetHWIDUpdater_Two(self):
-    for i in (1, 2):
-      open(os.path.join(self.data_dir, 'hwid_updater_%d.sh' % i), 'w').close()
-    self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWIDUpdater)
-
-  def testGetVPD(self):
-    # VPD fields defined in simple.csv
-    RO_FIELDS = ('keyboard_layout', 'initial_locale', 'initial_timezone')
-    RW_FIELDS_SET1 = ('wifi_mac', 'cellular_mac')
-    RW_FIELDS_SET2 = ('wifi_mac', )
-
-    vpd = self.proxy.GetVPD('CR001005')
-    for field in RO_FIELDS:
-      self.assertTrue(field in vpd['ro'] and vpd['ro'][field])
-    for field in RW_FIELDS_SET1:
-      self.assertTrue(field in vpd['rw'] and vpd['rw'][field])
-    self.assertEqual(vpd['ro']['keyboard_layout'], 'xkb:us::eng')
-    self.assertEqual(vpd['ro']['initial_locale'], 'en-US')
-    self.assertEqual(vpd['ro']['initial_timezone'], 'America/Los_Angeles')
-    self.assertEqual(vpd['rw']['wifi_mac'], '0b:ad:f0:0d:15:05')
-    self.assertEqual(vpd['rw']['cellular_mac'], '70:75:65:6c:6c:65')
-
-    vpd = self.proxy.GetVPD('CR001016')
-    for field in RO_FIELDS:
-      self.assertTrue(field in vpd['ro'] and vpd['ro'][field])
-    for field in RW_FIELDS_SET2:
-      self.assertTrue(field in vpd['rw'] and vpd['rw'][field])
-    self.assertEqual(vpd['ro']['keyboard_layout'], 'xkb:us:intl:eng')
-    self.assertEqual(vpd['ro']['initial_locale'], 'nl')
-    self.assertEqual(vpd['ro']['initial_timezone'], 'Europe/Amsterdam')
-    self.assertEqual(vpd['rw']['wifi_mac'], '0b:ad:f0:0d:15:10')
-    self.assertEqual(vpd['rw']['cellular_mac'], '')
-
-    # Checks MAC addresses
-    for i in range(25):
-      serial = 'CR0010%02d' % (i + 1)
-      vpd = self.proxy.GetVPD(serial)
-      wifi_mac = vpd['rw']['wifi_mac']
-      self.assertEqual(wifi_mac, "0b:ad:f0:0d:15:%02x" % (i + 1))
-      if i < 5:
-        cellular_mac = vpd['rw']['cellular_mac']
-        self.assertEqual(cellular_mac, "70:75:65:6c:6c:%02x" % (i + 0x61))
-
-    # Checks invalid serial numbers
-    self.assertRaises(xmlrpclib.Fault, self.proxy.GetVPD, 'MAGICA')
-    return True
-
-  def testUploadReport(self):
-    # Upload simple blob
-    blob = 'Simple Blob'
-    report_name = 'simple_blob.rpt'
-    report_path = os.path.join(self.data_dir, shopfloor.REPORTS_DIR,
-                               report_name)
-    self.proxy.UploadReport('CR001020', shopfloor.Binary('Simple Blob'),
-                            report_name)
-    self.assertTrue(os.path.exists(report_path))
-    self.assertTrue(open(report_path).read(), blob)
-
-    # Try to upload to invalid serial number
-    self.assertRaises(xmlrpclib.Fault, self.proxy.UploadReport, 'CR00200', blob)
-
-  def testFinalize(self):
-    self.proxy.Finalize('CR001024')
-    self.assertRaises(xmlrpclib.Fault, self.proxy.Finalize, '0999')
-
-  def testGetTestMd5sum(self):
-    md5_work = os.path.join(self.data_dir, shopfloor.UPDATE_DIR,
-                            'factory', 'latest.md5sum')
-    with open(md5_work, "w") as f:
-      f.write('0891a16c456fcc322b656d5f91fbf060')
-    self.assertEqual(self.proxy.GetTestMd5sum(),
-                     '0891a16c456fcc322b656d5f91fbf060')
-    os.remove(md5_work)
-
-  def testGetTestMd5sumWithoutMd5sumFile(self):
-    self.assertTrue(self.proxy.GetTestMd5sum() is None)
-
-  def testGetRegistrationCodeMap(self):
-    self.assertEquals(
-        {'user': ('000000000000000000000000000000000000'
-                  '0000000000000000000000000000190a55ad'),
-         'group': ('010101010101010101010101010101010101'
-                   '010101010101010101010101010162319fcc')},
-        self.proxy.GetRegistrationCodeMap('CR001001'))
-
-    # Make sure it was logged.
-    log = open(self.registration_code_log).read()
-    self.assertTrue(re.match(
-        '^MAGICA MADOKA A-A 1214,'
-        '000000000000000000000000000000000000'
-        '0000000000000000000000000000190a55ad,'
-        '010101010101010101010101010101010101'
-        '010101010101010101010101010162319fcc,'
-        '\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\n', log), repr(log))
-
-  def testUploadEvent(self):
-    # Check if events dir is created.
-    events_dir = os.path.join(self.data_dir, shopfloor.EVENTS_DIR)
-    self.assertTrue(os.path.isdir(events_dir))
-
-    # A new event file should be created.
-    self.assertTrue(self.proxy.UploadEvent('LOG_C835C718',
-                                           'PREAMBLE\n---\nEVENT_1\n'))
-    event_file = os.path.join(events_dir, 'LOG_C835C718')
-    self.assertTrue(os.path.isfile(event_file))
-
-    # Additional events should be appended to existing event files.
-    self.assertTrue(self.proxy.UploadEvent('LOG_C835C718',
-                                           '---\nEVENT_2\n'))
-    with open(event_file, 'r') as f:
-      events = [event.strip() for event in f.read().split('---')]
-      self.assertEqual(events[0], 'PREAMBLE')
-      self.assertEqual(events[1], 'EVENT_1')
-      self.assertEqual(events[2], 'EVENT_2')
-
-
-if __name__ == '__main__':
-  unittest.main()
diff --git a/factory_setup/testdata/shopfloor/devices.csv b/factory_setup/testdata/shopfloor/devices.csv
deleted file mode 100644
index 11b1083..0000000
--- a/factory_setup/testdata/shopfloor/devices.csv
+++ /dev/null
@@ -1,27 +0,0 @@
-serial_number,hwid,ro_vpd_serial_number,ro_vpd_keyboard_layout,ro_vpd_initial_locale,ro_vpd_initial_timezone,rw_vpd_wifi_mac,rw_vpd_cellular_mac,registration_code_user,registration_code_group
-CR001001,MAGICA MADOKA A-A 1214,CR001001,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:01,70:75:65:6c:6c:61,0000000000000000000000000000000000000000000000000000000000000000190a55ad,010101010101010101010101010101010101010101010101010101010101010162319fcc
-CR001002,MAGICA MADOKA A-A 1214,CR001002,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:02,70:75:65:6c:6c:62,0202020202020202020202020202020202020202020202020202020202020202ef7dc16f,030303030303030303030303030303030303030303030303030303030303030394460b0e
-CR001003,MAGICA MADOKA A-A 1214,CR001003,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:03,70:75:65:6c:6c:63,04040404040404040404040404040404040404040404040404040404040404042e947a68,050505050505050505050505050505050505050505050505050505050505050555afb009
-CR001004,MAGICA MADOKA A-A 1214,CR001004,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:04,70:75:65:6c:6c:64,0606060606060606060606060606060606060606060606060606060606060606d8e3eeaa,0707070707070707070707070707070707070707070707070707070707070707a3d824cb
-CR001005,MAGICA MADOKA A-A 1214,CR001005,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:05,70:75:65:6c:6c:65,080808080808080808080808080808080808080808080808080808080808080876360a27,09090909090909090909090909090909090909090909090909090909090909090d0dc046
-CR001006,MAGICA HOMURA A-A 7056,CR001006,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:06,0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a80419ee5,0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0bfb7a5484
-CR001007,MAGICA HOMURA A-A 7056,CR001007,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:07,,0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c41a825e2,0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d3a93ef83
-CR001008,MAGICA HOMURA B-A 8265,CR001008,xkb:de::ger,de-DE,Europe/Amsterdam,0b:ad:f0:0d:15:08,,0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0eb7dfb120,0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0fcce47b41
-CR001009,MAGICA HOMURA B-A 8265,CR001009,xkb:de::ger,de-DE,Europe/Amsterdam,0b:ad:f0:0d:15:09,,1010101010101010101010101010101010101010101010101010101010101010c772eab9,1111111111111111111111111111111111111111111111111111111111111111bc4920d8
-CR001010,MAGICA HOMURA C-A 2974,CR001010,xkb:fr::fra,fr-FR,Europe/Amsterdam,0b:ad:f0:0d:15:0a,,121212121212121212121212121212121212121212121212121212121212121231057e7b,13131313131313131313131313131313131313131313131313131313131313134a3eb41a
-CR001011,MAGICA SAYAKA A-A 2258,CR001011,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:0b,,1414141414141414141414141414141414141414141414141414141414141414f0ecc57c,15151515151515151515151515151515151515151515151515151515151515158bd70f1d
-CR001012,MAGICA SAYAKA A-B 3304,CR001012,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:0c,,1616161616161616161616161616161616161616161616161616161616161616069b51be,17171717171717171717171717171717171717171717171717171717171717177da09bdf
-CR001013,MAGICA SAYAKA A-C 2142,CR001013,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:0d,,1818181818181818181818181818181818181818181818181818181818181818a84eb533,1919191919191919191919191919191919191919191919191919191919191919d3757f52
-CR001014,MAGICA SAYAKA A-D 3565,CR001014,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:0e,,1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a5e3921f1,1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b2502eb90
-CR001015,MAGICA SAYAKA A-E 1787,CR001015,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:0f,,1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c9fd09af6,1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1de4eb5097
-CR001016,MAGICA MAMI A-A 2443,CR001016,xkb:us:intl:eng,nl,Europe/Amsterdam,0b:ad:f0:0d:15:10,,1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e69a70e34,1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f129cc455
-CR001017,MAGICA MAMI A-A 2443,CR001017,xkb:us:intl:eng,nl,Europe/Amsterdam,0b:ad:f0:0d:15:11,,20202020202020202020202020202020202020202020202020202020202020207e8a2dc4,212121212121212121212121212121212121212121212121212121212121212105b1e7a5
-CR001018,MAGICA MAMI A-A 2443,CR001018,xkb:us:intl:eng,zh-TW,Asia/Taipei,0b:ad:f0:0d:15:12,,222222222222222222222222222222222222222222222222222222222222222288fdb906,2323232323232323232323232323232323232323232323232323232323232323f3c67367
-CR001019,MAGICA MAMI A-A 2443,CR001019,xkb:us:intl:eng,zh-TW,Asia/Taipei,0b:ad:f0:0d:15:13,,242424242424242424242424242424242424242424242424242424242424242449140201,2525252525252525252525252525252525252525252525252525252525252525322fc860
-CR001020,MAGICA MAMI A-A 2443,CR001020,xkb:us:intl:eng,zh-TW,Asia/Taipei,0b:ad:f0:0d:15:14,,2626262626262626262626262626262626262626262626262626262626262626bf6396c3,2727272727272727272727272727272727272727272727272727272727272727c4585ca2
-CR001021,MAGICA KYOKO AA-A 0136,CR001021,xkb:jp::jpn,ja,Asia/Tokyo,0b:ad:f0:0d:15:15,,282828282828282828282828282828282828282828282828282828282828282811b6724e,29292929292929292929292929292929292929292929292929292929292929296a8db82f
-CR001022,MAGICA KYOKO AA-A 0136,CR001022,xkb:jp::jpn,ja,Asia/Tokyo,0b:ad:f0:0d:15:16,,2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2ae7c1e68c,2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b9cfa2ced
-CR001023,MAGICA KYOKO AB-A 9345,CR001023,xkb:jp::jpn,ja,Asia/Tokyo,0b:ad:f0:0d:15:17,,2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c26285d8b,2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d5d1397ea
-CR001024,MAGICA KYOKO AC-A 3910,CR001024,xkb:jp::jpn,ja,Asia/Tokyo,0b:ad:f0:0d:15:18,,2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2ed05fc949,2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2fab640328
-CR001025,MAGICA KYOKO AD-A 3779,CR001025,xkb:jp::jpn,ja,Asia/Tokyo,0b:ad:f0:0d:15:19,,3030303030303030303030303030303030303030303030303030303030303030a0f292d0,3131313131313131313131313131313131313131313131313131313131313131dbc958b1
-link,LINK PROTO A-A 0569,CR000102,xkb:us::eng,en-US,America/Los_Angeles,01:23:45:67:89:ab,12:34:56:78:9a:bc,323232323232323232323232323232323232323232323232323232323232323256850612,33333333333333333333333333333333333333333333333333333333333333332dbecc73
diff --git a/factory_setup/testdata/shopfloor/factory.tar.bz2 b/factory_setup/testdata/shopfloor/factory.tar.bz2
deleted file mode 100644
index fc1303f..0000000
--- a/factory_setup/testdata/shopfloor/factory.tar.bz2
+++ /dev/null
Binary files differ
diff --git a/factory_setup/testdata/shopfloor/latest.md5sum b/factory_setup/testdata/shopfloor/latest.md5sum
deleted file mode 100644
index 65fd6ce..0000000
--- a/factory_setup/testdata/shopfloor/latest.md5sum
+++ /dev/null
@@ -1 +0,0 @@
-0891a16c456fcc322b656d5f91fbf060