gooftool: add 'verify_cros_config'

This command will:
  * Check if RLZ code is nonempty and not 'ZZCR'
  * Compare the content of /usr/share/chromeos-config/yaml/config.yaml,
    check if current model has the same set of identity and brand-code
    defined.

BUG=b:147011183
TEST=make test

Change-Id: I3e7cf00eda0837e2c1fecfb913642a6ba7601219
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/factory/+/1986595
Reviewed-by: Yong Hong <yhong@chromium.org>
Reviewed-by: Philip Chen <philipchen@chromium.org>
Commit-Queue: Wei-Han Chen <stimim@chromium.org>
Tested-by: Wei-Han Chen <stimim@chromium.org>
diff --git a/py/gooftool/commands.py b/py/gooftool/commands.py
index 657561b..fa59fc0 100755
--- a/py/gooftool/commands.py
+++ b/py/gooftool/commands.py
@@ -357,6 +357,12 @@
       options.enforced_release_channels)
 
 
+@Command('verify_cros_config')
+def VerifyCrosConfig(options):
+  """Verify entries in cros config make sense."""
+  return GetGooftool(options).VerifyCrosConfig()
+
+
 @Command('write_protect')
 def EnableFwWp(options):
   """Enable then verify firmware write protection."""
@@ -545,6 +551,7 @@
   VerifyTPM(options)
   VerifyVPD(options)
   VerifyReleaseChannel(options)
+  VerifyCrosConfig(options)
 
 
 @Command('untar_stateful_files')
diff --git a/py/gooftool/core.py b/py/gooftool/core.py
index 1711ed7..d8657ed 100644
--- a/py/gooftool/core.py
+++ b/py/gooftool/core.py
@@ -18,6 +18,7 @@
 
 from six import iteritems
 from six import reraise as raise_
+import yaml
 
 import factory_common  # pylint: disable=unused-import
 from cros.factory.gooftool.bmpblk import unpack_bmpblock
@@ -36,6 +37,8 @@
 from cros.factory.test.rules.registration_codes import RegistrationCode
 from cros.factory.utils import config_utils
 from cros.factory.utils import file_utils
+from cros.factory.utils import json_utils
+from cros.factory.utils import sys_utils
 from cros.factory.utils.type_utils import Error
 
 # The mismatch result tuple.
@@ -448,6 +451,54 @@
                   'Enforced channels are %s.' % (
                       release_channel, enforced_channels))
 
+  def VerifyCrosConfig(self):
+    """Verify that entries in cros config make sense."""
+    if phase.GetPhase() >= phase.PVT_DOGFOOD:
+      # The value actually comes from "cros_config / brand-code", however,
+      # most scripts are still using "mosys platform brand" to get the value,
+      # so we also check the value by mosys command.
+      rlz = self._util.shell(['mosys', 'platform', 'brand']).stdout.strip()
+      if not rlz or rlz == 'ZZCR':
+        # this is incorrect...
+        raise Error('RLZ code "%s" is not allowed in PVT' % rlz)
+
+    model = self._util.shell(['mosys', 'platform', 'model']).stdout.strip()
+    if not model:
+      raise Error('Model name is empty')
+
+    def _ParseCrosConfig(config_path):
+      with open(config_path) as f:
+        obj = yaml.load(f)
+      fields = ['name', 'identity', 'brand-code']
+      configs = [
+          {
+              field: config[field] for field in fields
+          }
+          for config in obj['chromeos']['configs']
+          if config['name'] == model
+      ]
+      configs = {
+          # set sort_keys=True to make the result stable.
+          json_utils.DumpStr(config, sort_keys=True) for config in configs
+      }
+      return configs
+
+    # Load config.yaml from release image (FSI) and test image, and compare the
+    # fields we cared about.
+    config_path = 'usr/share/chromeos-config/yaml/config.yaml'
+    test_configs = _ParseCrosConfig(os.path.join('/', config_path))
+    with sys_utils.Mountpartition(
+        self._util.GetReleaseRootPartitionPath()) as root:
+      release_configs = _ParseCrosConfig(os.path.join(root, config_path))
+
+    if test_configs != release_configs:
+      error = ['Detect different chromeos-config between test image and FSI.']
+      error += ['Configs in test image:']
+      error += ['\t' + config for config in test_configs]
+      error += ['Configs in FSI:']
+      error += ['\t' + config for config in release_configs]
+      raise Error('\n'.join(error))
+
   def ClearGBBFlags(self):
     """Zero out the GBB flags, in preparation for transition to release state.