Add scheduler daemon to do scheduled job

In some network environment, networking may not get IP at the first
attempt. Install a scheduler so it can check network status
periodically.

BUG=chromium:624660
TEST=Unplugs ethernet, stop networking, and plugs ethernet back.
     Observer /var/log/scheduler_init at the same time and see it
     restarts networking to bring back network.

Change-Id: I331af39346ef867190157c02631fd22fef2b4447
Reviewed-on: https://chromium-review.googlesource.com/391909
Commit-Ready: Cheng-Yi Chiang <cychiang@chromium.org>
Tested-by: Cheng-Yi Chiang <cychiang@chromium.org>
Reviewed-by: Shyh-In Hwang <josephsih@chromium.org>
diff --git a/MANIFEST.in b/MANIFEST.in
index d16146a..25af1d7 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -5,6 +5,7 @@
 include deploy/deploy_pip
 include deploy/init.d/chameleond
 include deploy/init.d/displayd
+include deploy/init.d/scheduler
 include deploy/init.d/stream_server
 include deploy/init.d/chameleon-updater
 include updatable/python2.7/*.py
diff --git a/chameleond/utils/network_utils.py b/chameleond/utils/network_utils.py
new file mode 100644
index 0000000..5f65ac3
--- /dev/null
+++ b/chameleond/utils/network_utils.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+# 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.
+
+"""Network utilities."""
+
+import logging
+import re
+import subprocess
+
+
+def HasIp():
+  """Checks if system has IP on eth0.
+
+  Returns:
+    True if system has IP, false otherwise.
+  """
+  ip_output = subprocess.check_output(['ip', 'addr', 'show', 'dev', 'eth0'])
+
+  # Pattern is like " inet 100.102.7.163/25 scope global eth0"
+  match = re.search(r'^\s+inet ([.0-9]+)/[0-9]+', ip_output, re.MULTILINE)
+  if match:
+    ip_address = match.group(1)
+    logging.debug('Get IP %s', ip_address)
+    return True
+  else:
+    logging.warning('Can not get IP. Should restart networking.')
+    return False
+
+
+def RestartNetwork():
+  """Restarts networking daemon."""
+  logging.warning('Restart networking.')
+  try:
+    subprocess.check_output(['/etc/init.d/networking', 'restart'])
+    if HasIp():
+      logging.info('Network is back')
+  except subprocess.CalledProcessError as e:
+    # This is expected in some network environment.
+    logging.warning(e.output)
+    if 'No lease, failing' in e.output:
+      logging.warning('Can not get network, maybe try again later.')
+    else:
+      raise
+
+
+def PossiblyRestartNetwork():
+  """Checks network status and possibly restarts networking daemon."""
+  if not HasIp():
+    RestartNetwork()
diff --git a/deploy/deploy b/deploy/deploy
index 4060d57..7098590 100755
--- a/deploy/deploy
+++ b/deploy/deploy
@@ -11,6 +11,7 @@
 CHAMELEOND_NAME='chameleond'
 DISPLAYD_NAME='displayd'
 STREAM_SERVER_NAME='stream_server'
+SCHEDULER_NAME='scheduler'
 UPDATER_NAME='chameleon-updater'
 
 update-rc.d -f ${UPDATER_NAME} remove
@@ -39,6 +40,7 @@
 install_daemon_and_config "${CHAMELEOND_NAME}"
 install_daemon_and_config "${DISPLAYD_NAME}"
 install_daemon_and_config "${STREAM_SERVER_NAME}"
+install_daemon_and_config "${SCHEDULER_NAME}"
 
 cp -f "${SCRIPT_DIR}/${CHAMELEOND_NAME}" "${INITD_DIR}"
 update-rc.d "${CHAMELEOND_NAME}" defaults 92 8
@@ -49,6 +51,9 @@
 cp -f "${SCRIPT_DIR}/${STREAM_SERVER_NAME}" "${INITD_DIR}"
 update-rc.d "${STREAM_SERVER_NAME}" defaults 96 4
 
+cp -f "${SCRIPT_DIR}/${SCHEDULER_NAME}" "${INITD_DIR}"
+update-rc.d "${SCHEDULER_NAME}" defaults 98 4
+
 cp -f "${SCRIPT_DIR}/${UPDATER_NAME}" "${INITD_DIR}"
 update-rc.d "${UPDATER_NAME}" defaults 90 10
 
@@ -101,4 +106,5 @@
     "${INITD_DIR}/${CHAMELEOND_NAME}" restart
     "${INITD_DIR}/${DISPLAYD_NAME}" restart
     "${INITD_DIR}/${STREAM_SERVER_NAME}" restart
+    "${INITD_DIR}/${SCHEDULER_NAME}" restart
 fi
diff --git a/deploy/init.d/scheduler b/deploy/init.d/scheduler
new file mode 100644
index 0000000..02dbdd9
--- /dev/null
+++ b/deploy/init.d/scheduler
@@ -0,0 +1,69 @@
+#!/bin/sh
+# 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.
+
+### BEGIN INIT INFO
+# Provides:          stream_server
+# Required-Start:    $network $remote_fs
+# Required-Stop:     $network $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Start scheduler at boot time
+# Description:       Enable service provided by scheduler.
+### END INIT INFO
+
+DAEMON_NAME="scheduler"
+DAEMON="run_scheduler"
+DAEMON_ARGS=""
+DAEMON_USER='root'
+PIDFILE="/var/run/${DAEMON_NAME}.pid"
+LOGFILE="/var/log/${DAEMON_NAME}_init"
+
+is_already_running () {
+    start-stop-daemon --stop --test --quiet --pidfile "${PIDFILE}"
+}
+
+do_start () {
+    if ! is_already_running; then
+        start-stop-daemon --start --background --pidfile "${PIDFILE}" \
+            --make-pidfile --user "${DAEMON_USER}" --chuid "${DAEMON_USER}" \
+            --startas /bin/bash -- \
+            -c "exec ${DAEMON} ${DAEMON_ARGS} >> ${LOGFILE} 2>&1"
+        do_status
+    else
+        echo "${DAEMON_NAME} is already running."
+    fi
+}
+
+do_stop () {
+    if is_already_running; then
+        start-stop-daemon --stop --pidfile "${PIDFILE}" --retry 10
+        rm -f "${PIDFILE}"
+    else
+        echo "${DAEMON_NAME} is already stopped"
+    fi
+}
+
+do_status () {
+    is_already_running &&
+        echo "${DAEMON_NAME} is running." ||
+        echo "${DAEMON_NAME} is not running"
+}
+
+case "$1" in
+    start|stop|status)
+        do_${1}
+        ;;
+
+    restart)
+        do_stop
+        do_start
+        ;;
+
+    *)
+        echo "Usage: /etc/init.d/${DAEMON_NAME} {start|stop|restart|status}"
+        exit 1
+        ;;
+esac
+exit 0
diff --git a/setup.py b/setup.py
index 9f75312..6422306 100644
--- a/setup.py
+++ b/setup.py
@@ -20,7 +20,8 @@
     long_description='Server to communicate and control Chameleon board.',
     # Uses pyserial version 2.7. The newer 3.x version is not compatible
     # with chameleond/utils/serial_utils.py
-    install_requires=['pyserial==2.7'],
+    install_requires=['pyserial==2.7', 'schedule'],
     scripts=['utils/run_chameleond', 'utils/run_displayd',
-             'utils/run_stream_server', 'chameleond/utils/server_time']
+             'utils/run_stream_server', 'chameleond/utils/server_time',
+             'utils/run_scheduler']
 )
diff --git a/utils/run_scheduler b/utils/run_scheduler
new file mode 100644
index 0000000..5b63e92
--- /dev/null
+++ b/utils/run_scheduler
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# 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.
+
+"""Run scheduler daemon."""
+
+import argparse
+import logging
+import schedule
+import time
+
+from chameleond.utils import network_utils
+
+
+def Main():
+  """The main program, to run scheduler daemon."""
+  parser = argparse.ArgumentParser(
+      description='Launch a scheduler daemon.',
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+  parser.add_argument('--network_period_secs', type=float, default=10,
+                      help='Period in seconds to check network.')
+
+  args = parser.parse_args()
+
+  SetNetworkSchedule(args.network_period_secs)
+
+  Run()
+
+
+def Run():
+  """Runs scheduler."""
+  while True:
+    schedule.run_pending()
+    time.sleep(1)
+
+
+def SetNetworkSchedule(period_secs):
+  """Sets the networking schedule.
+
+  Args:
+    period_secs: Period in seconds to check network status.
+  """
+  schedule.every(period_secs).seconds.do(network_utils.PossiblyRestartNetwork)
+
+
+if __name__ == '__main__':
+  # schedule module uses logging.info when it does a scheduled work so it is
+  # to chatty.
+  logging.basicConfig(
+      level=logging.WARNING,
+      format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+  Main()