[autotest] Eureka host.

Creates a eureka host capable of verifying, cleanup, reboot
and machine_install. Also implements the following chagnes:
1. Pipes a base command through is_up, since different ssh
   hosts are bound to have different things pre-installed.
2. Changes some timeouts to integers to avoid asserts.
3. Make host factory a little more generic, and check
   for chromeos host first.
4. Adds a method to remote wget to server/site_utils.

TEST=Tried verify, cleanup, reoboot and machine_install.
BUG=chromium:292629, chromium:273843

Change-Id: I62a700614838569c6d77184b3c3339e914f4b456
Reviewed-on: https://chromium-review.googlesource.com/176303
Tested-by: Prashanth B <[email protected]>
Reviewed-by: Scott Zawalski <[email protected]>
Reviewed-by: Simran Basi <[email protected]>
Commit-Queue: Prashanth B <[email protected]>
diff --git a/server/cros/eureka_client.py b/server/cros/eureka_client.py
new file mode 100644
index 0000000..1a7f1ba
--- /dev/null
+++ b/server/cros/eureka_client.py
@@ -0,0 +1,135 @@
+# Copyright (c) 2013 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 httplib
+import json
+import logging
+import socket
+import time
+import urllib2
+
+import common
+
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib.cros import retry
+
+# Give all our rpcs about six seconds of retry time. If a longer timeout
+# is desired one should retry from the caller, this timeout is only meant
+# to avoid uncontrolled circumstances like network flake, not, say, retry
+# right across a reboot.
+BASE_REQUEST_TIMEOUT = 0.1
+JSON_HEADERS = {'Content-Type': 'application/json'}
+RPC_EXCEPTIONS = (httplib.BadStatusLine, socket.error, urllib2.HTTPError)
+
+
[email protected](RPC_EXCEPTIONS, timeout_min=BASE_REQUEST_TIMEOUT)
+def _get(url):
+    """Get request to the give url.
+
+    @raises: Any of the retry exceptions, if we hit the timeout.
+    @raises: error.TimeoutException, if the call itself times out.
+        eg: a hanging urlopen will get killed with a TimeoutException while
+        multiple retries that hit different Http errors will raise the last
+        HttpError instead of the TimeoutException.
+    """
+    return urllib2.urlopen(url).read()
+
+
[email protected](RPC_EXCEPTIONS, timeout_min=BASE_REQUEST_TIMEOUT)
+def _post(url, data):
+    """Post data to the given url.
+
+    @param data: Json data to post.
+
+    @raises: Any of the retry exceptions, if we hit the timeout.
+    @raises: error.TimeoutException, if the call itself times out.
+        For examples see docstring for _get method.
+    """
+    request = urllib2.Request(url, json.dumps(data),
+                              headers=JSON_HEADERS)
+    urllib2.urlopen(request)
+
+
+class EurekaProxyException(Exception):
+    """Generic exception raised when a eureka rpc fails."""
+    pass
+
+
+class EurekaProxy(object):
+    """Client capable of making calls to the eureka device server."""
+    POLLING_INTERVAL = 5
+    SETUP_SERVER_PORT = '8008'
+    EUREKA_SETUP_SERVER = 'http://%s:%s/setup'
+
+    def __init__(self, hostname):
+        """
+        @param host: The host object representing the Eureka device.
+        """
+        self._eureka_setup_server = (self.EUREKA_SETUP_SERVER %
+                                     (hostname, self.SETUP_SERVER_PORT))
+
+
+    def get_info(self):
+        """Returns information about the eureka device.
+
+        @return: A dictionary containing information about the eureka device.
+        """
+        eureka_info_url = '%s/%s' % (self._eureka_setup_server, 'eureka_info')
+        try:
+            return json.loads(_get(eureka_info_url))
+        except (RPC_EXCEPTIONS, error.TimeoutException) as e:
+            raise EurekaProxyException('Could not retrieve information about '
+                                       'eureka device: %s' % e)
+
+
+    def get_build_number(self, timeout_mins=0.1):
+        """
+        Returns the build number of the build on the device.
+
+        @param timeout_mins: Timeout in minutes. By default this should
+            return almost immediately and hence has a timeout of 6 seconds.
+            If we're rebooting, and would like the boot id of the build after
+            the reboot is complete this timeout should be in O(minutes).
+
+        @raises EurekaProxyException: If unable to get build number within the
+            timeout specified.
+        """
+        current_time = int(time.time())
+        end_time = current_time + timeout_mins*60
+
+        while end_time > current_time:
+            try:
+                eureka_info = self.get_info()
+            except EurekaProxyException:
+                pass
+            else:
+                return eureka_info.get('build_version', None)
+            time.sleep(self.POLLING_INTERVAL)
+            current_time = int(time.time())
+
+        raise EurekaProxyException('Timed out trying to get build number.')
+
+
+    def reboot(self, when="now"):
+        """
+        Post to the server asking for a reboot.
+
+        @param when: The time till reboot. Can be any of:
+            now: immediately
+            fdr: set factory data reset flag and reboot now
+            ota: set recovery flag and reboot now
+            ota fdr: set both recovery and fdr flags, and reboot now
+            ota foreground: reboot and start force update page
+            idle: reboot only when idle screen usage > 10 mins
+
+        @raises EurekaProxyException: if we're unable to post a reboot request.
+        """
+        reboot_url = '%s/%s' % (self._eureka_setup_server, 'reboot')
+        reboot_params = {"params": when}
+        logging.info('Rebooting device through %s.', reboot_url)
+        try:
+            _post(reboot_url, reboot_params)
+        except (RPC_EXCEPTIONS, error.TimeoutException) as e:
+            raise EurekaProxyException('Could not reboot eureka device through '
+                                       '%s: %s' % (self.SETUP_SERVER_PORT, e))
diff --git a/server/hosts/abstract_ssh.py b/server/hosts/abstract_ssh.py
index 4120334..7af5a2e 100644
--- a/server/hosts/abstract_ssh.py
+++ b/server/hosts/abstract_ssh.py
@@ -382,19 +382,21 @@
                     raise error.AutoservRunError(e.args[0], e.args[1])
 
 
-    def ssh_ping(self, timeout=60):
+    def ssh_ping(self, timeout=60, base_cmd='true'):
         """
         Pings remote host via ssh.
 
         @param timeout: Time in seconds before giving up.
                         Defaults to 60 seconds.
+        @param base_cmd: The base command to run with the ssh ping.
+                         Defaults to true.
         @raise AutoservSSHTimeout: If the ssh ping times out.
         @raise AutoservSshPermissionDeniedError: If ssh ping fails due to
                                                  permissions.
         @raise AutoservSshPingHostError: For other AutoservRunErrors.
         """
         try:
-            self.run("true", timeout=timeout, connect_timeout=timeout)
+            self.run(base_cmd, timeout=timeout, connect_timeout=timeout)
         except error.AutoservSSHTimeout:
             msg = "Host (ssh) verify timed out (timeout = %d)" % timeout
             raise error.AutoservSSHTimeout(msg)
@@ -408,16 +410,17 @@
                                                  repr(e.result_obj))
 
 
-    def is_up(self, timeout=60):
+    def is_up(self, timeout=60, base_cmd='true'):
         """
-        Check if the remote host is up.
+        Check if the remote host is up by ssh-ing and running a base command.
 
         @param timeout: timeout in seconds.
+        @param base_cmd: a base command to run with ssh. The default is 'true'.
         @returns True if the remote host is up before the timeout expires,
                  False otherwise.
         """
         try:
-            self.ssh_ping(timeout=timeout)
+            self.ssh_ping(timeout=timeout, base_cmd=base_cmd)
         except error.AutoservError:
             return False
         else:
@@ -438,8 +441,8 @@
                  False otherwise
         """
         if timeout:
-            end_time = time.time() + timeout
-            current_time = time.time()
+            current_time = int(time.time())
+            end_time = current_time + timeout
 
         while not timeout or current_time < end_time:
             if self.is_up(timeout=end_time - current_time):
@@ -450,7 +453,7 @@
                 except error.AutoservError:
                     pass
             time.sleep(1)
-            current_time = time.time()
+            current_time = int(time.time())
 
         logging.debug('Host %s is still down after waiting %d seconds',
                       self.hostname, int(timeout + time.time() - end_time))
@@ -498,7 +501,7 @@
         """
         #TODO: there is currently no way to distinguish between knowing
         #TODO: boot_id was unsupported and not knowing the boot_id.
-        current_time = time.time()
+        current_time = int(time.time())
         if timeout:
             end_time = current_time + timeout
 
@@ -525,7 +528,7 @@
         # the same time that allowed us into that iteration of the loop.
         while not timeout or current_time < end_time:
             try:
-                new_boot_id = self.get_boot_id(timeout=end_time - current_time)
+                new_boot_id = self.get_boot_id(timeout=end_time-current_time)
             except error.AutoservError:
                 logging.debug('Host %s is now unreachable over ssh, is down',
                               self.hostname)
@@ -549,7 +552,7 @@
                 self.run('kill -HUP 1', ignore_status=True)
 
             time.sleep(1)
-            current_time = time.time()
+            current_time = int(time.time())
 
         return False
 
diff --git a/server/hosts/adb_host.py b/server/hosts/adb_host.py
index 8ab56da..cc8b99a 100644
--- a/server/hosts/adb_host.py
+++ b/server/hosts/adb_host.py
@@ -29,6 +29,27 @@
     """This class represents a host running an ADB server."""
 
 
+    @staticmethod
+    def check_host(host, timeout=10):
+        """
+        Check if the given host is an adb host.
+
+        @param host: An ssh host representing a device.
+        @param timeout: The timeout for the run command.
+
+
+        @return: True if the host device has adb.
+
+        @raises AutoservRunError: If the command failed.
+        @raises AutoservSSHTimeout: Ssh connection has timed out.
+        """
+        try:
+            result = host.run('which adb 2> /dev/null', timeout=timeout)
+        except (error.AutoservRunError, error.AutoservSSHTimeout):
+            return False
+        return result.exit_status == 0
+
+
     def _initialize(self, hostname='localhost', serial=None,
                     device_hostname=None, *args, **dargs):
         """Initialize an ADB Host.
diff --git a/server/hosts/cros_host.py b/server/hosts/cros_host.py
index d71ebe0..d55ff71 100644
--- a/server/hosts/cros_host.py
+++ b/server/hosts/cros_host.py
@@ -157,6 +157,26 @@
 
 
     @staticmethod
+    def check_host(host, timeout=10):
+        """
+        Check if the given host is a chrome-os host.
+
+        @param host: An ssh host representing a device.
+        @param timeout: The timeout for the run command.
+
+        @return: True if the host device is chromeos.
+
+        @raises AutoservRunError: If the command failed.
+        @raises AutoservSSHTimeout: Ssh connection has timed out.
+        """
+        try:
+            result = host.run('cat /etc/lsb-release > /dev/null', timeout=timeout)
+        except (error.AutoservRunError, error.AutoservSSHTimeout):
+            return False
+        return result.exit_status == 0
+
+
+    @staticmethod
     def get_servo_arguments(args_dict):
         """Extract servo options from `args_dict` and return the result.
 
diff --git a/server/hosts/eureka_host.py b/server/hosts/eureka_host.py
new file mode 100644
index 0000000..edd86b9
--- /dev/null
+++ b/server/hosts/eureka_host.py
@@ -0,0 +1,218 @@
+# Copyright (c) 2013 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.
+
+"""
+Eureka host.
+
+This host can perform actions either over ssh or by submitting requests to
+an http server running on the client. Though the server provides flexibility
+and allows us to test things at a modular level, there are times we must
+resort to ssh (eg: to reboot into recovery). The server exposes the same stack
+that the chromecast extension needs to communicate with the eureka device, so
+any test involving an eureka host will fail if it cannot submit posts/gets
+to the server. In cases where we can achieve the same action over ssh or
+the rpc server, we choose the rpc server by default, because several existing
+eureka tests do the same.
+"""
+
+import logging
+import os
+
+import common
+
+from autotest_lib.client.common_lib import error
+from autotest_lib.server import site_utils
+from autotest_lib.server.cros import eureka_client
+from autotest_lib.server.hosts import abstract_ssh
+
+
+class EurekaHost(abstract_ssh.AbstractSSHHost):
+    """This class represents a eureka host."""
+
+    # Maximum time to wait for the client server to start.
+    SERVER_START_TIME = 180
+
+    # Maximum time a reboot can take.
+    REBOOT_TIME = 360
+
+    COREDUMP_DIR = '/data/coredump'
+    OTA_LOCATION = '/cache/ota.zip'
+    RECOVERY_DIR = '/cache/recovery'
+    COMMAND_FILE = os.path.join(RECOVERY_DIR, 'command')
+
+
+    @staticmethod
+    def check_host(host, timeout=10):
+        """
+        Check if the given host is a eureka host.
+
+        @param host: An ssh host representing a device.
+        @param timeout: The timeout for the run command.
+
+        @return: True if the host device is eureka.
+
+        @raises AutoservRunError: If the command failed.
+        @raises AutoservSSHTimeout: Ssh connection has timed out.
+        """
+        try:
+            result = host.run('getprop ro.hardware', timeout=timeout)
+        except (error.AutoservRunError, error.AutoservSSHTimeout):
+            return False
+        return 'eureka' in result.stdout
+
+
+    def _initialize(self, hostname, *args, **dargs):
+        super(EurekaHost, self)._initialize(hostname=hostname, *args, **dargs)
+
+        # Eureka devices expose a server that can respond to json over http.
+        self.client = eureka_client.EurekaProxy(hostname)
+
+
+    def get_boot_id(self, timeout=60):
+        """Get a unique ID associated with the current boot.
+
+        @param timeout The number of seconds to wait before timing out, as
+            taken by base_utils.run.
+
+        @return A string unique to this boot or None if not available.
+        """
+        BOOT_ID_FILE = '/proc/sys/kernel/random/boot_id'
+        cmd = 'cat %r' % (BOOT_ID_FILE)
+        return self.run(cmd, timeout=timeout).stdout.strip()
+
+
+    def ssh_ping(self, timeout=60, base_cmd=''):
+        """Checks if we can ssh into the host and run getprop.
+
+        Ssh ping is vital for connectivity checks and waiting on a reboot.
+        A simple true check, or something like if [ 0 ], is not guaranteed
+        to always exit with a successful return value.
+
+        @param timeout: timeout in seconds to wait on the ssh_ping.
+        @param base_cmd: The base command to use to confirm that a round
+            trip ssh works.
+        """
+        super(EurekaHost, self).ssh_ping(timeout=timeout,
+                                         base_cmd="getprop>/dev/null")
+
+
+    def verify_software(self):
+        """Verified that the server on the client device is responding to gets.
+
+        The server on the client device is crucial for the eureka device to
+        communicate with the chromecast extension. Device verify on the whole
+        consists of verify_(hardware, connectivity and software), ssh
+        connectivity is verified in the base class' verify_connectivity.
+
+        @raises: EurekaProxyException if the server doesn't respond.
+        """
+        self.client.get_info()
+
+
+    def get_kernel_ver(self):
+        """Returns the build number of the build on the device."""
+        return self.client.get_build_number()
+
+
+    def reboot(self, timeout=5):
+        """Reboot the eureka device by submitting a post to the server."""
+
+        # TODO(beeps): crbug.com/318306
+        current_boot_id = self.get_boot_id()
+        try:
+            self.client.reboot()
+        except eureka_client.EurekaProxyException as e:
+            logging.error('Unable to reboot through the eureka proxy: %s', e)
+            return False
+
+        self.wait_for_restart(timeout=timeout, old_boot_id=current_boot_id)
+        return True
+
+
+    def cleanup(self):
+        """Cleanup state.
+
+        If removing state information fails, do a hard reboot. This will hit
+        our reboot method through the ssh host's cleanup.
+        """
+        try:
+            self.run('rm -r /data/*')
+            self.run('rm -f /cache/*')
+        except (error.AutotestRunError, error.AutoservRunError) as e:
+            logging.warn('Unable to remove /data and /cache %s', e)
+            super(EurekaHost, self).cleanup()
+
+
+    def _remount_root(self, permissions):
+        """Remount root partition.
+
+        @param permissions: Permissions to use for the remount, eg: ro, rw.
+
+        @raises error.AutoservRunError: If something goes wrong in executing
+            the remount command.
+        """
+        self.run('mount -o %s,remount /' % permissions)
+
+
+    def _setup_coredump_dirs(self):
+        """Sets up the /data/coredump directory on the client.
+
+        The device will write a memory dump to this directory on crash,
+        if it exists. No crashdump will get written if it doesn't.
+        """
+        try:
+            self.run('mkdir -p %s' % self.COREDUMP_DIR)
+            self.run('chmod 4777 %s' % self.COREDUMP_DIR)
+        except (error.AutotestRunError, error.AutoservRunError) as e:
+            error.AutoservRunError('Unable to create coredump directories with '
+                                   'the appropriate permissions: %s' % e)
+
+
+    def _setup_for_recovery(self, update_url):
+        """Sets up the /cache/recovery directory on the client.
+
+        Copies over the OTA zipfile from the update_url to /cache, then
+        sets up the recovery directory. Normal installs are achieved
+        by rebooting into recovery mode.
+
+        @param update_url: A url pointing to a staged ota zip file.
+
+        @raises error.AutoservRunError: If something goes wrong while
+            executing a command.
+        """
+        ssh_cmd = '%s %s' % (self.make_ssh_command(), self.hostname)
+        site_utils.remote_wget(update_url, self.OTA_LOCATION, ssh_cmd)
+        self.run('ls %s' % self.OTA_LOCATION)
+
+        self.run('mkdir -p %s' % self.RECOVERY_DIR)
+
+        # These 2 commands will always return a non-zero exit status
+        # even if they complete successfully. This is a confirmed
+        # non-issue, since the install will actually complete. If one
+        # of the commands fails we can only detect it as a failure
+        # to install the specified build.
+        self.run('echo --update_package>%s' % self.COMMAND_FILE,
+                 ignore_status=True)
+        self.run('echo %s>>%s' % (self.OTA_LOCATION, self.COMMAND_FILE),
+                 ignore_status=True)
+
+
+    def machine_install(self, update_url):
+        """Installs a build on the Eureka device."""
+        old_build_number = self.client.get_build_number()
+        self._remount_root(permissions='rw')
+        self._setup_coredump_dirs()
+        self._setup_for_recovery(update_url)
+
+        current_boot_id = self.get_boot_id()
+        self.run('reboot recovery &')
+        self.wait_for_restart(timeout=self.REBOOT_TIME,
+                              old_boot_id=current_boot_id)
+        new_build_number = self.client.get_build_number(self.SERVER_START_TIME)
+
+        # TODO(beeps): crbug.com/318278
+        if new_build_number ==  old_build_number:
+            raise error.AutoservRunError('Build number did not change on: '
+                                         '%s after update with %s' %
+                                         (self.hostname, update_url()))
diff --git a/server/hosts/factory.py b/server/hosts/factory.py
index 67f78ed..51bbecb 100644
--- a/server/hosts/factory.py
+++ b/server/hosts/factory.py
@@ -1,9 +1,12 @@
 """Provides a factory method to create a host object."""
 
+import logging
 from contextlib import closing
+
 from autotest_lib.client.common_lib import error, global_config
 from autotest_lib.server import autotest, utils as server_utils
 from autotest_lib.server.hosts import site_factory, cros_host, ssh_host, serial
+from autotest_lib.server.hosts import eureka_host
 from autotest_lib.server.hosts import adb_host, logfile_monitor
 
 
@@ -24,6 +27,11 @@
 # for tracking which hostnames have already had job_start called
 _started_hostnames = set()
 
+# A list of all the possible host types, ordered according to frequency of
+# host types in the lab, so the more common hosts don't incur a repeated ssh
+# overhead in checking for less common host types.
+host_types = [cros_host.CrosHost, eureka_host.EurekaHost, adb_host.ADBHost,]
+
 
 def _get_host_arguments():
     """Returns parameters needed to ssh into a host.
@@ -49,8 +57,10 @@
 def _detect_host(connectivity_class, hostname, **args):
     """Detect host type.
 
-    Currently checks if adb is on the host and if so returns ADBHost if not or
-    if the check fails, it will return CrosHost.
+    Goes through all the possible host classes, calling check_host with a
+    basic host object. Currently this is an ssh host, but theoretically it
+    can be any host object that the check_host method of appropriate host
+    type knows to use.
 
     @param connectivity_class: connectivity class to use to talk to the host
                                (ParamikoHost or SSHHost)
@@ -58,24 +68,19 @@
     @param args: Args that will be passed to the constructor of
                  the host class.
 
-    @returns Class type to use for this host.
+    @returns: Class type of the first host class that returns True to the
+              check_host method.
     """
-    # Detect if adb is on the host. If so we are using an ADBHost. If not use,
-    # CrosHost.
-    try:
-        # Attempt to find adb on the system. If that succeeds use ADBHost.
-        with closing(connectivity_class(hostname, **args)) as host:
-            # Send stderr to /dev/null which cleans up log spam about not
-            # finding adb installed.
-            result = host.run('which adb 2> /dev/null', timeout=10)
-            return adb_host.ADBHost
-    except (error.AutoservRunError, error.AutoservSSHTimeout):
-        # If any errors occur use CrosHost.
-        # TODO(fdeng): this method should should dynamically discover
-        # and allocate host types, crbug.com/273843
-        # TODO crbug.com/302026 (sbasi) - adjust this pathway for ADBHost in
-        # the future should a host require verify/repair.
-        return cros_host.CrosHost
+    # TODO crbug.com/302026 (sbasi) - adjust this pathway for ADBHost in
+    # the future should a host require verify/repair.
+    with closing(connectivity_class(hostname, **args)) as host:
+        for host_module in host_types:
+            if host_module.check_host(host, timeout=10):
+                return host_module
+
+    logging.warning('Unable to apply conventional host detection methods, '
+                    'defaulting to chromeos host.')
+    return cros_host.CrosHost
 
 
 def create_host(
diff --git a/server/site_tests/adb_Reboot/control b/server/site_tests/adb_Reboot/control
deleted file mode 100644
index f520b2e..0000000
--- a/server/site_tests/adb_Reboot/control
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (c) 2013 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.
-
-from autotest_lib.client.common_lib import utils
-
-AUTHOR = "sbasi, [email protected]"
-NAME = "adb_Reboot"
-TIME = "SHORT"
-TEST_CATEGORY = "Functional"
-TEST_CLASS = "adb"
-TEST_TYPE = "server"
-
-DOC = """
-This server side test suite tests the ADB Reboot functionality.
-"""
-
-args_dict = utils.args_to_dict(args)
-
-def run_benchmark(machine):
-    device_hostname = args_dict.get('device_hostname')
-    host = hosts.create_host(machine,
-                             device_hostname=device_hostname)
-    job.run_test("adb_Reboot", host=host)
-
-
-parallel_simple(run_benchmark, machines)
diff --git a/server/site_tests/generic_RebootTest/control b/server/site_tests/generic_RebootTest/control
new file mode 100644
index 0000000..95134fd
--- /dev/null
+++ b/server/site_tests/generic_RebootTest/control
@@ -0,0 +1,51 @@
+# Copyright (c) 2013 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 logging
+
+from autotest_lib.client.common_lib import error, utils
+
+
+AUTHOR = "sbasi, [email protected]"
+NAME = "generic_RebootTest"
+TIME = "SHORT"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "Generic"
+TEST_TYPE = "server"
+
+DOC = """
+This server side test checks if a host, specified through the command line,
+can successfully reboot. It automatically detects the type of the host,
+creates the appropriate host object, and calls reboot on the host object.
+
+Example usage:
+test_that generic_RebootTest <eureka/cros/beaglobonedevice ip> --board=<board>
+A note about --board: <unless you're emerging eureka sources you
+can just use a chromeos board here, as all we need is test_that
+from the sysroot>.
+
+Typically, for the case of an adb host, we can send adb commands to the
+android device either through usb or tcp. If no device_hostname is specified
+the adb commands are sent to the android device over usb, via the beaglebone,
+whereas if the ip of the android device is specified through device_hostname
+we'll send the adb commands over tcp.
+"""
+
+args_dict = utils.args_to_dict(args)
+
+def run_reboot_test(machine):
+    device_hostname = args_dict.get('device_hostname', None)
+    try:
+        host = hosts.create_host(machine,
+                                 device_hostname=device_hostname)
+    except error.AutoservError as e:
+        raise error.AutoservError(
+            'Failed to create host for %s: %s. If this is an android host that '
+            'requires a beaglebone jump host, you need to specify the device '
+            'hostname through test_that --args="device_hostname=<android ip>".'
+            % (machine, e))
+
+    job.run_test("generic_RebootTest", host=host, disable_sysinfo=True)
+
+
+parallel_simple(run_reboot_test, machines)
diff --git a/server/site_tests/adb_Reboot/adb_Reboot.py b/server/site_tests/generic_RebootTest/generic_RebootTest.py
similarity index 79%
rename from server/site_tests/adb_Reboot/adb_Reboot.py
rename to server/site_tests/generic_RebootTest/generic_RebootTest.py
index 58b8b5d..8ba3e27 100644
--- a/server/site_tests/adb_Reboot/adb_Reboot.py
+++ b/server/site_tests/generic_RebootTest/generic_RebootTest.py
@@ -6,11 +6,10 @@
 from autotest_lib.server import test
 
 
-class adb_Reboot(test.test):
+class generic_RebootTest(test.test):
     """Reboot a device. Should be ran on ADBHosts only."""
     version = 1
 
-
     def run_once(self, host):
         if not host.reboot():
-            raise error.TestFail('ADB failed to reboot as expected.')
+            raise error.TestFail('Host failed to reboot.')
diff --git a/server/site_utils.py b/server/site_utils.py
index ab8f496..c1390bf 100644
--- a/server/site_utils.py
+++ b/server/site_utils.py
@@ -103,3 +103,17 @@
             sheriff_ids += ['%[email protected]' % alias.replace(' ', '')
                             for alias in ldaps.group(1).split(',')]
     return sheriff_ids
+
+
+def remote_wget(source_url, dest_path, ssh_cmd):
+    """wget source_url from localhost to dest_path on remote host using ssh.
+
+    @param source_url: The complete url of the source of the package to send.
+    @param dest_path: The path on the remote host's file system where we would
+        like to store the package.
+    @param ssh_cmd: The ssh command to use in performing the remote wget.
+    """
+    wget_cmd = ("wget -O - %s | %s 'cat >%s'" %
+                (source_url, ssh_cmd, dest_path))
+    base_utils.run(wget_cmd)
+