[autotest] Add in hwid library for label generation.
This library is the base part of a Cros Label mechanism that will use
hwid to generate new labels. The key file will be stored in a private
location in another cl.
BUG=chromium:571087
TEST=unittests
Change-Id: I10e40a8c2f4134c04e84c3b26d53bb70092c08b4
Reviewed-on: https://chromium-review.googlesource.com/321924
Commit-Ready: Kevin Cheng <[email protected]>
Tested-by: Kevin Cheng <[email protected]>
Reviewed-by: Kevin Cheng <[email protected]>
diff --git a/site_utils/hwid_lib.py b/site_utils/hwid_lib.py
new file mode 100644
index 0000000..f408b5f
--- /dev/null
+++ b/site_utils/hwid_lib.py
@@ -0,0 +1,69 @@
+# Copyright 2016 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.
+
+import json
+import urllib2
+
+
+# HWID info types to request.
+HWID_INFO_LABEL = 'dutlabel'
+HWID_INFO_BOM = 'bom'
+HWID_INFO_SKU = 'sku'
+HWID_INFO_TYPES = [HWID_INFO_BOM, HWID_INFO_SKU, HWID_INFO_LABEL]
+
+# HWID url vars.
+HWID_VERSION = 'v1'
+HWID_BASE_URL = 'https://www.googleapis.com/chromeoshwid'
+URL_FORMAT_STRING='%(base_url)s/%(version)s/%(info_type)s/%(hwid)s/?key=%(key)s'
+
+
+class HwIdException(Exception):
+ """Raised whenever anything fails in the hwid info request."""
+
+
+def get_hwid_info(hwid, info_type, key_file):
+ """Given a hwid and info type, return a dict of the requested info.
+
+ @param hwid: hwid to use for the query.
+ @param info_type: String of info type requested.
+ @param key_file: Filename that holds the key for authentication.
+
+ @return: A dict of the info.
+
+ @raises HwIdException: If hwid/info_type/key_file is invalid or there's an
+ error anywhere related to getting the raw hwid info
+ or decoding it.
+ """
+ if not isinstance(hwid, str):
+ raise ValueError('hwid is not a string.')
+
+ if info_type not in HWID_INFO_TYPES:
+ raise ValueError('invalid info type: "%s".' % info_type)
+
+ key = None
+ with open(key_file) as f:
+ key = f.read().strip()
+
+ url_format_dict = {'base_url': HWID_BASE_URL,
+ 'version': HWID_VERSION,
+ 'info_type': info_type,
+ 'hwid': hwid,
+ 'key': key}
+
+ url_request = URL_FORMAT_STRING % url_format_dict
+ try:
+ page_contents = urllib2.urlopen(url_request)
+ except (urllib2.URLError, urllib2.HTTPError) as e:
+ # TODO(kevcheng): Might need to scrub out key from exception message.
+ raise HwIdException('error retrieving raw hwid info: %s' % e)
+
+ try:
+ hwid_info_dict = json.load(page_contents)
+ except ValueError as e:
+ raise HwIdException('error decoding hwid info: %s - "%s"' %
+ (e, page_contents.getvalue()))
+ finally:
+ page_contents.close()
+
+ return hwid_info_dict
diff --git a/site_utils/hwid_lib_unittest.py b/site_utils/hwid_lib_unittest.py
new file mode 100644
index 0000000..71164fd
--- /dev/null
+++ b/site_utils/hwid_lib_unittest.py
@@ -0,0 +1,135 @@
+# Copyright 2016 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.
+
+import cStringIO
+import json
+import mock
+import os
+import shutil
+import tempfile
+import unittest
+import urllib2
+
+import common
+
+from autotest_lib.site_utils import hwid_lib
+
+
+class HwIdUnittests(unittest.TestCase):
+ """Unittest for testing get_hwid_info."""
+
+ def setUp(self):
+ # Create tmp dir and dummy key files.
+ self.tmp_dir = tempfile.mkdtemp(prefix='hwid_test')
+ self.dummy_key = 'dummy_key'
+ self.dummy_key_file = os.path.join(self.tmp_dir, 'dummy_key')
+ with open(self.dummy_key_file, 'w') as f:
+ f.write(self.dummy_key)
+ self.dummy_key_file_spaces = os.path.join(self.tmp_dir,
+ 'dummy_key_spaces')
+ with open(self.dummy_key_file_spaces, 'w') as f:
+ f.write(' %s ' % self.dummy_key)
+ self.dummy_key_file_newline = os.path.join(self.tmp_dir,
+ 'dummy_key_newline')
+ with open(self.dummy_key_file_newline, 'w') as f:
+ f.write('%s\n' % self.dummy_key)
+ self.invalid_dummy_key_file = os.path.join(self.tmp_dir,
+ 'invalid_dummy_key_file')
+
+
+ def tearDown(self):
+ mock.patch.stopall()
+ if os.path.exists(self.tmp_dir):
+ shutil.rmtree(self.tmp_dir)
+
+
+ def validate_exception(self, exception, *args):
+ """Helper method to validate proper exception is raised.
+
+ @param exception: The exception class to check against.
+ @param args: The unamed args to pass to func.
+ """
+ with self.assertRaises(exception):
+ hwid_lib.get_hwid_info(*args)
+
+
+ def test_none_hwid(self):
+ """Test that an empty hwid raises a ValueError."""
+ self.validate_exception(ValueError, None, None, None)
+
+
+ def test_invalid_info_type(self):
+ """Test that an invalid info type raises a ValueError."""
+ self.validate_exception(ValueError, 'hwid', 'invalid_info_type', None)
+
+
+ def test_fail_open_with_nonexistent_file(self):
+ """Test that trying to open non-existent file will raise an IOError."""
+ self.validate_exception(IOError, 'hwid', hwid_lib.HWID_INFO_BOM,
+ self.invalid_dummy_key_file)
+
+
+ @mock.patch('urllib2.urlopen', side_effect=urllib2.URLError('url error'))
+ def test_fail_to_open_url_urlerror(self, *args, **dargs):
+ """Test that failing to open a url will raise a HwIdException."""
+ self.validate_exception(hwid_lib.HwIdException, 'hwid',
+ hwid_lib.HWID_INFO_BOM, self.dummy_key_file)
+
+
+ # pylint: disable=missing-docstring
+ @mock.patch('urllib2.urlopen')
+ def test_fail_decode_hwid(self, mock_urlopen, *args, **dargs):
+ """Test that supplying bad json raises a HwIdException."""
+ mock_page_contents = mock.Mock(wraps=cStringIO.StringIO('bad json'))
+ mock_urlopen.return_value = mock_page_contents
+ self.validate_exception(hwid_lib.HwIdException, 'hwid',
+ hwid_lib.HWID_INFO_BOM, self.dummy_key_file)
+ mock_page_contents.close.assert_called_once_with()
+
+
+ # pylint: disable=missing-docstring
+ @mock.patch('urllib2.urlopen')
+ def test_success(self, mock_urlopen, *args, **dargs):
+ """Test that get_hwid_info successfully returns a hwid dict.
+
+ We want to check that it works on all valid info types.
+ """
+ returned_json = '{"key1": "data1"}'
+ expected_dict = json.loads(returned_json)
+ for valid_info_type in hwid_lib.HWID_INFO_TYPES:
+ mock_page_contents = mock.Mock(
+ wraps=cStringIO.StringIO(returned_json))
+ mock_urlopen.return_value = mock_page_contents
+ self.assertEqual(hwid_lib.get_hwid_info('hwid', valid_info_type,
+ self.dummy_key_file),
+ expected_dict)
+ mock_page_contents.close.assert_called_once_with()
+
+
+ # pylint: disable=missing-docstring
+ @mock.patch('urllib2.urlopen')
+ def test_url_properly_constructed(self, mock_urlopen, *args, **dargs):
+ """Test that the url is properly constructed.
+
+ Let's make sure that the key is properly cleaned before getting
+ inserted into the url by trying all the different dummy_key_files.
+ """
+ info_type = hwid_lib.HWID_INFO_BOM
+ hwid = 'mock_hwid'
+ expected_url = ('%s/%s/%s/%s/?key=%s' % (hwid_lib.HWID_BASE_URL,
+ hwid_lib.HWID_VERSION,
+ info_type, hwid,
+ self.dummy_key))
+
+ for dummy_key_file in [self.dummy_key_file,
+ self.dummy_key_file_spaces,
+ self.dummy_key_file_newline]:
+ mock_page_contents = mock.Mock(wraps=cStringIO.StringIO('{}'))
+ mock_urlopen.return_value = mock_page_contents
+ hwid_lib.get_hwid_info(hwid, info_type, dummy_key_file)
+ mock_urlopen.assert_called_with(expected_url)
+
+
+if __name__ == '__main__':
+ unittest.main()