[moblab] Add update button

BUG=chromium:613747
TEST=Tested features on a local moblab

Change-Id: Id512a96e57c5449ace561b8a551b2570ab637468
Reviewed-on: https://chromium-review.googlesource.com/887794
Commit-Ready: Matt Mallett <[email protected]>
Tested-by: Matt Mallett <[email protected]>
Reviewed-by: Keith Haddow <[email protected]>
diff --git a/frontend/afe/moblab_rpc_interface.py b/frontend/afe/moblab_rpc_interface.py
index e368f4d..76fc227 100644
--- a/frontend/afe/moblab_rpc_interface.py
+++ b/frontend/afe/moblab_rpc_interface.py
@@ -53,6 +53,9 @@
 # File where information about the current device is stored.
 _ETC_LSB_RELEASE = '/etc/lsb-release'
 
+# ChromeOS update engine client binary location
+_UPDATE_ENGINE_CLIENT = '/usr/bin/update_engine_client'
+
 # Full path to the correct gsutil command to run.
 class GsUtil:
     """Helper class to find correct gsutil command."""
@@ -463,10 +466,73 @@
     version_response['MOBLAB_ID'] = utils.get_moblab_id();
     version_response['MOBLAB_MAC_ADDRESS'] = (
         utils.get_default_interface_mac_address())
+    _check_for_system_update()
+    update_status = _get_system_update_status()
+    version_response['MOBLAB_UPDATE_VERSION'] = update_status['NEW_VERSION']
+    version_response['MOBLAB_UPDATE_STATUS'] = update_status['CURRENT_OP']
+    version_response['MOBLAB_UPDATE_PROGRESS'] = update_status['PROGRESS']
     return rpc_utils.prepare_for_serialization(version_response)
 
 
 @rpc_utils.moblab_only
+def update_moblab():
+    """ RPC call to update and reboot moblab """
+    _install_system_update()
+
+
+def _check_for_system_update():
+    """ Run the ChromeOS update client to check update server for an
+    update. If an update exists, the update client begins downloading it
+    in the background
+    """
+    # sudo is required to run the update client
+    subprocess.call(['sudo', _UPDATE_ENGINE_CLIENT, '--check_for_update'])
+
+
+def _get_system_update_status():
+    """ Run the ChromeOS update client to check status on a
+    pending/downloading update
+
+    @return: A dictionary containing {
+        PROGRESS: str containing percent progress of an update download
+        CURRENT_OP: str current status of the update engine,
+            ex UPDATE_STATUS_UPDATED_NEED_REBOOT
+        NEW_SIZE: str size of the update
+        NEW_VERSION: str version number for the update
+        LAST_CHECKED_TIME: str unix time stamp of the last update check
+    }
+    """
+    # sudo is required to run the update client
+    cmd_out = subprocess.check_output(
+        ['sudo' ,_UPDATE_ENGINE_CLIENT, '--status'])
+    split_lines = [x.split('=') for x in cmd_out.strip().split('\n')]
+    status = dict((key, val) for [key, val] in split_lines)
+    return status
+
+
+def _install_system_update():
+    """ Installs a ChromeOS update, will cause the system to reboot
+    """
+    # sudo is required to run the update client
+    # first run a blocking command to check, fetch, prepare an update
+    # then check if a reboot is needed
+    try:
+        subprocess.check_call(['sudo', _UPDATE_ENGINE_CLIENT, '--update'])
+        try:
+            # --is_reboot_needed returns 2 if a reboot is required, which
+            # technically is an error
+            subprocess.check_call(
+                ['sudo', _UPDATE_ENGINE_CLIENT, '--is_reboot_needed'])
+        except subprocess.CalledProcessError as e:
+            if e.returncode == 2:
+                subprocess.call(['sudo', _UPDATE_ENGINE_CLIENT, '--reboot'])
+
+    except subprocess.CalledProcessError as e:
+        pass
+        #TODO(crbug/806311) surface error to UI
+
+
+@rpc_utils.moblab_only
 def get_connected_dut_info():
     """ RPC handler to get informaiton about the DUTs connected to the moblab.