[autotest] Expand DevServer class to support control-file related calls
Add the ability to call the control_files/ endpoint on the dev server.
BUG=chromium-os:24985
TEST=dev_server_unittest.py
Change-Id: If20e7c6d1f13813cd10511c34d64e9ffddfb37f4
Reviewed-on: https://gerrit.chromium.org/gerrit/14302
Commit-Ready: Chris Masone <cmasone@chromium.org>
Reviewed-by: Chris Masone <cmasone@chromium.org>
Tested-by: Chris Masone <cmasone@chromium.org>
diff --git a/client/common_lib/cros/dev_server.py b/client/common_lib/cros/dev_server.py
index a57194e..8cd977e 100644
--- a/client/common_lib/cros/dev_server.py
+++ b/client/common_lib/cros/dev_server.py
@@ -22,15 +22,32 @@
class DevServer(object):
"""Helper class for interacting with the Dev Server via http."""
- def __init__(self, dev_host):
+ def __init__(self, dev_host=None):
"""Constructor.
Args:
- @param host: Address of the Dev Server.
+ @param dev_host: Address of the Dev Server.
+ Defaults to None. If not set, CROS.dev_server is used.
"""
self._dev_server = dev_host if dev_host else _get_dev_server()
+ def _build_call(self, method, **kwargs):
+ """Build a URL that calls |method|, passing |kwargs|.
+
+ Build a URL that calls |method| on the dev server, passing a set
+ of key/value pairs built from the dict |kwargs|.
+
+ @param method: the dev server method to call.
+ @param kwargs: a dict mapping arg names to arg values
+ @return the URL string
+ """
+ argstr = '&'.join(map(lambda x: "%s=%s" % x, kwargs.iteritems()))
+ return "%(host)s/%(method)s?%(args)s" % {'host': self._dev_server,
+ 'method': method,
+ 'args': argstr}
+
+
def trigger_download(self, image):
"""Tell the dev server to download and stage |image|.
@@ -40,12 +57,12 @@
@param image: the image to fetch and stage.
@return True if the remote call returns HTTP OK, False if it returns
an internal server error.
- @throws urllib2.HTTPError upon any return code that's not 200 or 500
+ @throws urllib2.HTTPError upon any return code that's not 200 or 500.
"""
try:
call = self._build_call(
- method='download',
- named_args={'archive_url': _get_image_storage_server() + image})
+ 'download',
+ archive_url=_get_image_storage_server() + image)
response = urllib2.urlopen(call)
return response.read() == 'Success'
except urllib2.HTTPError as e:
@@ -56,17 +73,50 @@
raise
- def _build_call(self, method, named_args):
- """Build a URL that calls |method|, passing |named_args|.
+ def list_control_files(self, build):
+ """Ask the dev server to list all control files for |build|.
- Build a URL that calls |method| on the dev server, passing a set
- of key/value pairs built from the dict |named_args|.
+ Ask the dev server at |self._dev_server| to list all control files
+ for |build|.
- @param method: the dev server method to call.
- @param named_args: a dict mapping arg names to arg values
- @return the URL string
+ @param build: The build (e.g. x86-mario-release/R18-1586.0.0-a1-b1514)
+ whose control files the caller wants listed.
+ @return None on failure, or a list of control file paths
+ (e.g. server/site_tests/autoupdate/control)
+ @throws urllib2.HTTPError upon any return code that's not 200 or 500.
"""
- argstr = '&'.join(map(lambda x: "%s=%s" % x, named_args.iteritems()))
- return "%(host)s/%(method)s?%(args)s" % { 'host': self._dev_server,
- 'method': method,
- 'args': argstr }
+ try:
+ call = self._build_call('controlfiles', build=build)
+ response = urllib2.urlopen(call)
+ return [line.rstrip() for line in response]
+ except urllib2.HTTPError as e:
+ if e.code == httplib.INTERNAL_SERVER_ERROR:
+ return None
+ else:
+ logging.debug(e)
+ raise
+
+
+ def get_control_file(self, build, control_path):
+ """Ask the dev server for the contents of a control file.
+
+ Ask the dev server at |self._dev_server|for the contents of the
+ control file at |control_path| for |build|.
+
+ @param build: The build (e.g. x86-mario-release/R18-1586.0.0-a1-b1514)
+ whose control files the caller wants listed.
+ @param control_path: The file to list
+ (e.g. server/site_tests/autoupdate/control)
+ @return The contents of the desired file, or None
+ @throws urllib2.HTTPError upon any return code that's not 200 or 500.
+ """
+ try:
+ call = self._build_call('controlfiles',
+ build=build, control_path=control_path)
+ return urllib2.urlopen(call).read()
+ except urllib2.HTTPError as e:
+ if e.code == httplib.INTERNAL_SERVER_ERROR:
+ return None
+ else:
+ logging.debug(e)
+ raise
diff --git a/client/common_lib/cros/dev_server_unittest.py b/client/common_lib/cros/dev_server_unittest.py
index 22ef818..e5b593e 100644
--- a/client/common_lib/cros/dev_server_unittest.py
+++ b/client/common_lib/cros/dev_server_unittest.py
@@ -23,12 +23,32 @@
"""
_HOST = 'http://nothing'
+ _500 = urllib2.HTTPError(url='',
+ code=httplib.INTERNAL_SERVER_ERROR,
+ msg='',
+ hdrs=None,
+ fp=None)
+ _403 = urllib2.HTTPError(url='',
+ code=httplib.FORBIDDEN,
+ msg='',
+ hdrs=None,
+ fp=None)
def setUp(self):
super(DevServerTest, self).setUp()
self.dev_server = dev_server.DevServer(self._HOST)
+ def _returnHttpServerError(self):
+ self.mox.StubOutWithMock(urllib2, 'urlopen')
+ urllib2.urlopen(mox.IgnoreArg()).AndRaise(self._500)
+
+
+ def _returnHttpForbidden(self):
+ self.mox.StubOutWithMock(urllib2, 'urlopen')
+ urllib2.urlopen(mox.IgnoreArg()).AndRaise(self._403)
+
+
def testSuccessfulTriggerDownload(self):
"""Should successfully call the dev server's download method."""
name = 'fake/image'
@@ -42,27 +62,77 @@
def testFailedTriggerDownload(self):
"""Should call the dev server's download method, fail gracefully."""
- self.mox.StubOutWithMock(urllib2, 'urlopen')
- to_raise = urllib2.HTTPError(url='',
- code=httplib.INTERNAL_SERVER_ERROR,
- msg='',
- hdrs=None,
- fp=None)
- urllib2.urlopen(mox.IgnoreArg()).AndRaise(to_raise)
+ self._returnHttpServerError()
self.mox.ReplayAll()
self.assertFalse(self.dev_server.trigger_download(''))
def testExplodingTriggerDownload(self):
"""Should call the dev server's download method, get exception."""
- self.mox.StubOutWithMock(urllib2, 'urlopen')
- to_raise = urllib2.HTTPError(url='',
- code=httplib.FORBIDDEN,
- msg='',
- hdrs=None,
- fp=None)
- urllib2.urlopen(mox.IgnoreArg()).AndRaise(to_raise)
+ self._returnHttpForbidden()
self.mox.ReplayAll()
self.assertRaises(urllib2.HTTPError,
self.dev_server.trigger_download,
'')
+
+
+ def testListControlFiles(self):
+ """Should successfully list control files from the dev server."""
+ name = 'fake/build'
+ control_files = ['file/one', 'file/two']
+ self.mox.StubOutWithMock(urllib2, 'urlopen')
+ to_return = StringIO.StringIO('\n'.join(control_files))
+ urllib2.urlopen(mox.And(mox.StrContains(self._HOST),
+ mox.StrContains(name))).AndReturn(to_return)
+ self.mox.ReplayAll()
+ paths = self.dev_server.list_control_files(name)
+ self.assertEquals(len(paths), 2)
+ for f in control_files:
+ self.assertTrue(f in paths)
+
+
+ def testFailedListControlFiles(self):
+ """Should call the dev server's list-files method, fail gracefully."""
+ self._returnHttpServerError()
+ self.mox.ReplayAll()
+ self.assertEquals(self.dev_server.list_control_files(''), None)
+
+
+ def testExplodingListControlFiles(self):
+ """Should call the dev server's list-files method, get exception."""
+ self._returnHttpForbidden()
+ self.mox.ReplayAll()
+ self.assertRaises(urllib2.HTTPError,
+ self.dev_server.list_control_files,
+ '')
+
+
+ def testGetControlFile(self):
+ """Should successfully list control files from the dev server."""
+ name = 'fake/build'
+ file = 'file/one'
+ contents = 'Multi-line\nControl File Contents\n'
+ self.mox.StubOutWithMock(urllib2, 'urlopen')
+ to_return = StringIO.StringIO(contents)
+ urllib2.urlopen(mox.And(mox.StrContains(self._HOST),
+ mox.StrContains(name),
+ mox.StrContains(file))).AndReturn(to_return)
+ self.mox.ReplayAll()
+ self.assertEquals(self.dev_server.get_control_file(name, file),
+ contents)
+
+
+ def testFailedGetControlFile(self):
+ """Should try to get the contents of a control file, fail gracefully."""
+ self._returnHttpServerError()
+ self.mox.ReplayAll()
+ self.assertEquals(self.dev_server.get_control_file('', ''), None)
+
+
+ def testExplodingGetControlFile(self):
+ """Should try to get the contents of a control file, get exception."""
+ self._returnHttpForbidden()
+ self.mox.ReplayAll()
+ self.assertRaises(urllib2.HTTPError,
+ self.dev_server.get_control_file,
+ '', '')
diff --git a/server/cros/dynamic_suite.py b/server/cros/dynamic_suite.py
index 0104416..3cb0abe 100644
--- a/server/cros/dynamic_suite.py
+++ b/server/cros/dynamic_suite.py
@@ -14,6 +14,20 @@
CONFIG = global_config.global_config
+def inject_vars(vars, control_file_in):
+ """
+ Inject the contents of |vars| into |control_file_in|
+
+ @param vars: a dict to shoehorn into the provided control file string.
+ @param control_file_in: the contents of a control file to munge.
+ @return the modified control file string.
+ """
+ control_file = ''
+ for key, value in vars.iteritems():
+ control_file += "%s='%s'\n" % (key, value)
+ return control_file + control_file_in
+
+
def _image_url_pattern():
return CONFIG.get_config_value('CROS', 'image_url_pattern', type=str)
@@ -103,20 +117,6 @@
self._afe.create_label(name=name)
- def _inject_vars(self, vars, control_file_in):
- """
- Inject the contents of |vars| into |control_file_in|
-
- @param vars: a dict to shoehorn into the provided control file string.
- @param control_file_in: the contents of a control file to munge.
- @return the modified control file string.
- """
- control_file = ''
- for key, value in vars.iteritems():
- control_file += "%s='%s'\n" % (key, value)
- return control_file + control_file_in
-
-
def _schedule_reimage_job(self, name, num_machines, board):
"""
Schedules the reimaging of |num_machines| |board| devices with |image|.
@@ -129,7 +129,7 @@
@param board: which kind of devices to reimage.
@return a frontend.Job object for the reimaging job we scheduled.
"""
- control_file = self._inject_vars(
+ control_file = inject_vars(
{ 'image_url': _image_url_pattern() % name,
'image_name': name },
self._cf_getter.get_control_file_contents_by_name('autoupdate'))
diff --git a/server/cros/dynamic_suite_unittest.py b/server/cros/dynamic_suite_unittest.py
index 696ce31..b96ab92 100755
--- a/server/cros/dynamic_suite_unittest.py
+++ b/server/cros/dynamic_suite_unittest.py
@@ -71,8 +71,8 @@
return reduce(lambda b,i: "%s='%s'\n" % i in s, d.iteritems(), True)
v = {'v1': 'one', 'v2': 'two'}
- self.assertTrue(find_all_in(v, self.reimager._inject_vars(v, '')))
- self.assertTrue(find_all_in(v, self.reimager._inject_vars(v, 'ctrl')))
+ self.assertTrue(find_all_in(v, dynamic_suite.inject_vars(v, '')))
+ self.assertTrue(find_all_in(v, dynamic_suite.inject_vars(v, 'ctrl')))
def testReportResultsGood(self):