| # 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 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' |
| REGISTRATION_CODES_CSV = 'registration_codes.csv' |
| |
| |
| 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.""" |
| self._registration_code_log = open( |
| os.path.join(self.data_dir, REGISTRATION_CODES_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 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() |