nebraska: Add flag to ignore appid.

Add a flag to ignore the request's appid. We need this for
autoupdate_StatefulCompatibility, which updates between <board> and
<board>-kernelnext which have mismatching app IDs.

BUG=b:168074829
TEST=./nebraska_unittests.py
TEST=autoupdate_StatefulCompatibility with the changes in place to pass
the new flag through.

Change-Id: I227dc8933d99a8bdef8534f92d8341fc9eff618a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/2541673
Tested-by: Kyle Shimabukuro <kyleshima@chromium.org>
Reviewed-by: Jae Hoon Kim <kimjae@chromium.org>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
Commit-Queue: Kyle Shimabukuro <kyleshima@chromium.org>
diff --git a/nebraska/nebraska.py b/nebraska/nebraska.py
index 67a0ee3..1a752b2 100755
--- a/nebraska/nebraska.py
+++ b/nebraska/nebraska.py
@@ -307,7 +307,7 @@
         raise InvalidRequestError('Invalid app request.')
 
     def MatchAppData(self, app_data, partial_match_appid=False,
-                     check_against_canary=False):
+                     check_against_canary=False, ignore_appid=False):
       """Returns true iff the app matches a given client request.
 
       An app matches a request if the appid matches the requested appid.
@@ -325,11 +325,12 @@
             properties file. But the good news is that there is only one canary
             App ID for all devices. Turning this flag on, checks the incoming
             request against the presumed canary App ID.
+        ignore_appid: If true, don't check the App ID and assume a match.
 
       Returns:
         True if the request matches the given app, False otherwise.
       """
-      if self.appid != app_data.appid:
+      if not ignore_appid and self.appid != app_data.appid:
         if partial_match_appid:
           if app_data.appid not in self.appid:
             return False
@@ -456,7 +457,8 @@
 
       elif self._app_request.request_type == Request.RequestType.UPDATE:
         self._app_data = nebraska_props.update_app_index.Find(
-            self._app_request, matched_apps, self._response_props.full_payload)
+            self._app_request, matched_apps, self._response_props.full_payload,
+            nebraska_props.ignore_appid)
         self._payloads_address = nebraska_props.update_payloads_address
 
       if self._app_data:
@@ -600,7 +602,7 @@
           raise
         logging.debug('Found app data: %s', str(app))
 
-  def Find(self, request, matched_apps, full_payload):
+  def Find(self, request, matched_apps, full_payload, ignore_appid=False):
     """Search the index for a given appid.
 
     Searches the index for the payloads matching a client request. Matching is
@@ -612,6 +614,8 @@
       matched_apps: The set of app data that have been matched already.
       full_payload: True if we want full payload, False if delta payload, None
         if we don't care.
+      ignore_appid: True to ignore the request's App ID and use the first
+        available app.
 
     Returns:
       An AppData object describing an available payload matching the client
@@ -640,6 +644,10 @@
       matches = [app_data for app_data in self._index if
                  request.MatchAppData(app_data, partial_match_appid=True)]
 
+    if not matches and ignore_appid:
+      matches = [app_data for app_data in self._index if
+                 request.MatchAppData(app_data, ignore_appid=True)]
+
     # Now remove App ID matches that have already been matched by other
     # requests.
     matches = [app_data for app_data in matches
@@ -754,7 +762,8 @@
                update_payloads_address=None,
                install_payloads_address=None,
                update_metadata_dir=None,
-               install_metadata_dir=None):
+               install_metadata_dir=None,
+               ignore_appid=False):
     """Initializes the NebraskaProperties instance.
 
     Args:
@@ -763,6 +772,8 @@
            is passed it will default to update_payloads_address.
       update_metadata_dir: Update payloads metadata directory.
       install_metadata_dir: Install payloads metadata directory.
+      ignore_appid: True to ignore the request's App ID and use the first
+        available app.
     """
     # Attach '/' at the end of the addresses if they don't have any. The update
     # engine just concatenates the base address with the payload file name and
@@ -774,6 +785,7 @@
         self.update_payloads_address)
     self.update_app_index = AppIndex(update_metadata_dir)
     self.install_app_index = AppIndex(install_metadata_dir)
+    self.ignore_appid = ignore_appid
 
 
 class ResponseProperties(object):
@@ -1100,6 +1112,9 @@
   parser.add_argument('--install-payloads-address', metavar='URL',
                       help='Base payload URI for install payloads. If not '
                       'passed it will default to --update-payloads-address')
+  parser.add_argument('--ignore-appid', action='store_true',
+                      help='Ignore the App ID field of incoming requests and '
+                      'use whichever app is available.')
 
   parser.add_argument('--port', metavar='PORT', type=int, default=0,
                       help='Port to run the server on.')
@@ -1134,7 +1149,8 @@
       update_payloads_address=opts.update_payloads_address,
       install_payloads_address=opts.install_payloads_address,
       update_metadata_dir=opts.update_metadata,
-      install_metadata_dir=opts.install_metadata)
+      install_metadata_dir=opts.install_metadata,
+      ignore_appid=opts.ignore_appid)
   nebraska = Nebraska(nebraska_props)
   nebraska_server = NebraskaServer(nebraska, runtime_root=opts.runtime_root,
                                    port=opts.port)
diff --git a/nebraska/nebraska_unittest.py b/nebraska/nebraska_unittest.py
index 5e3e5bb..21a5d1c 100755
--- a/nebraska/nebraska_unittest.py
+++ b/nebraska/nebraska_unittest.py
@@ -651,6 +651,19 @@
     update_check = ElementTree.fromstring(response).find('app/updatecheck')
     self.assertEqual(update_check.attrib['status'], 'noupdate')
 
+  def testIgnoreAppDataAppID(self):
+    """Tests ignoring appid."""
+    self.GenerateAppData(appid='bar')
+    neb_props = nebraska.NebraskaProperties(
+        update_metadata_dir=self.tempdir,
+        update_payloads_address=_PAYLOADS_ADDRESS, ignore_appid=True)
+    neb = nebraska.Nebraska(nebraska_props=neb_props)
+    request = GenerateXMLRequest([GenerateXMLAppRequest(appid='foo')])
+    response = neb.GetResponseToRequest(nebraska.Request(request))
+
+    update_check = ElementTree.fromstring(response).find('app/updatecheck')
+    self.assertEqual(update_check.attrib['status'], 'ok')
+
   def testMatchCanaryAppId(self):
     """Tests matching update request with canary appid."""
     self.GenerateAppData(appid='1' * len(nebraska._CANARY_APP_ID) + 'foo')