nebraska: Add a conf file

With this new .conf file (that will be put in /etc/init), we can bring
up nebraska.py with upstart only on test images before update_engine
comes up. This allows us to run some of the AU tests (like interrupt
tests) that currently we can't run (because they need the nebraska be up
right after system startup) and we can't access to device fast enough to
bring up nebraska.py.

Also added a --poll flag to nebraska.py so it can be called by this flag
to wait for a previous run of nebraska.py generates its pid/port files.

BUG=b:184980277
TEST=created a simple config.json file and did:
$ start nebraska
Then
$ cat /tmp/nebraska.log
INFO:root:Starting nebraska ...
DEBUG:root:Config updated to:
{'critical_update': False,
 'disable_payload_backoff': False,
 'eol_date': None,
 'failures_per_url': None,
 'full_payload': None,
 'ignore_appid': False,
 'install_app_index': <__main__.AppIndex object at 0x7cc01c1cd6d0>,
 'install_payloads_address': 'http://127.0.0.1:8080/',
 'is_rollback': False,
 'no_update': False,
 'num_urls': 1,
 'return_noupdate_starting': 0,
 'update_app_index': <__main__.AppIndex object at 0x7cc01c1cd650>,
 'update_payloads_address': 'http://127.0.0.1:8080/'}
INFO:root:Started nebraska on port 37691 and pid 27649.
DEBUG:root:Config updated to:
{'critical_update': True,
 'disable_payload_backoff': False,
 'eol_date': None,
 'failures_per_url': None,
 'full_payload': None,
 'ignore_appid': False,
 'install_app_index': <__main__.AppIndex object at 0x7cc01c1cd6d0>,
 'install_payloads_address': 'http://127.0.0.1:8080/',
 'is_rollback': False,
 'no_update': False,
 'num_urls': 1,
 'return_noupdate_starting': 0,
 'update_app_index': <__main__.AppIndex object at 0x7cc01c1cd650>,
 'update_payloads_address': 'http://127.0.0.1:8080/'}

which means the update config was successfully applied.

Change-Id: Iaa5ab473916ac5050a5b1f0919a8d4160559fab5
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/2819280
Tested-by: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Jae Hoon Kim <kimjae@chromium.org>
Commit-Queue: Amin Hassani <ahassani@chromium.org>
diff --git a/nebraska/nebraska.conf b/nebraska/nebraska.conf
new file mode 100644
index 0000000..65a7e0a
--- /dev/null
+++ b/nebraska/nebraska.conf
@@ -0,0 +1,53 @@
+# Copyright 2021 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.
+
+description     "Job to start nebraska.py"
+author          "chromeos-core-services@google.com"
+
+start on starting update-engine
+stop on stopping update-engine
+
+# We only run this on test images, but its a good idea to set this so we can
+# change the default.
+oom score -100
+
+env NEBRASKA_CONFIG_FILE=/usr/local/nebraska/config.json
+
+pre-start script
+  # We only want to run nebraska.py in test images. We don't include this
+  # upstart config file in base image anyway, but this is just another
+  # protection later.
+  if crossystem "devsw_boot?0"; then
+    logger -t "${UPSTART_JOB}" "Not running nebraska.py in non-dev mode."
+    stop
+    exit 0
+  fi
+
+  # If no JSON file for updating nebraska's config was provided, don't run
+  # nebraska. We only want to run nebraska when we intend to do so.
+  if [ ! -f "${NEBRASKA_CONFIG_FILE}" ]; then
+    logger -t "${UPSTART_JOB}" "Can't run nebraska.py without its config file."
+    stop
+    exit 0
+  fi
+
+  logger -t "${UPSTART_JOB}" "Running nebraska.py."
+end script
+
+exec nebraska.py
+
+post-start script
+  # Wait for the port file to appear for no more than 3 seconds.
+  nebraska.py --poll
+  if [ "$?" -ne 0 ]; then
+    logger -t "${UPSTART_JOB}" "Failed to find pid/port files of nebraska.py."
+    stop
+    exit 0
+  fi
+
+  PORT="$(cat /run/nebraska/port)"
+  curl -X POST -d @"${NEBRASKA_CONFIG_FILE}" \
+      "http://localhost:${PORT}/update_config"
+
+end script
diff --git a/nebraska/nebraska.py b/nebraska/nebraska.py
index 8d2a61b..bed9904 100755
--- a/nebraska/nebraska.py
+++ b/nebraska/nebraska.py
@@ -23,6 +23,7 @@
 import signal
 import sys
 import threading
+import time
 import traceback
 import urllib
 
@@ -1107,14 +1108,45 @@
   parser.add_argument('--log-file', metavar='FILE', default='/tmp/nebraska.log',
                       help='The file to write the logs.'
                       ' pass "stdout" to write to standard output.')
+  parser.add_argument('--poll', action='store_true',
+                      help='Wait for a previous run of nebraska.py to write '
+                      'its port and pid files and exit immediately once found. '
+                      'Otherwise exist with non-zero value after 3 seconds.')
 
   return parser.parse_args(argv[1:])
 
 
+def WaitForRuntimeFiles(runtime_root, seconds=3):
+  """Wait for pid and port file of a previous run of nebraska.py.
+
+  And exist with non-zero code after certain time.
+
+  Args:
+    runtime_root: The runtime root path of previous nebraska.py.
+    seconds: The number of seconds to wait before giving up.
+
+  Returns:
+    os.EX_OK for success and raises Error for failure.
+  """
+  runtime_files = [os.path.join(runtime_root, x) for x in ('port', 'pid')]
+  check_interval = 0.05
+  count = int(seconds // check_interval)
+  while count:
+    if all(os.path.exists(x) for x in runtime_files):
+      return os.EX_OK
+    time.sleep(0.05)
+    count -= 1
+
+  raise Error('Were not able to locate files: %s' % runtime_files)
+
+
 def main(argv):
   """Main function."""
   opts = ParseArguments(argv)
 
+  if opts.poll:
+    return WaitForRuntimeFiles(opts.runtime_root)
+
   # Reset the log file.
   if opts.log_file != 'stdout':
     with open(opts.log_file, 'w') as _: