[autotest] Sync based on key instead of id for attribute-like models

HostAttribute and JobKeyval are updated on both shard and
master. Both table should be synced based on "key" instead
auto-incremental id.

BUG=chromium:453122
DEPLOY=shard_cient, apache
TEST=python
>>import common
>>from autotest_lib.frontend import setup_django_environment
>>from autotest_lib.frontend.afe import models
>>m = models.Host.objects.get(id=20)
>>s = m.serialize()
>>s['hostattribute_set'] = A LIST OF NEW ATTRIBUTE RECORDS
>>m = models.Host.deserialize(s)
>>m.hostattribute_set.all()[0].__dict__

>>j = models.Job.objects.get(id=35)
>>s = j.serialize()
>>s['jobkeyval_set'] = A LIST OF NEW JOBKEYVAL RECORDS
>>m = models.Job.deserialize(s)
>>m.jobkeyval_set.all()[0].__dict__

TEST=Run a shard, schedule a dummy suite, ensure everything still
works.

Change-Id: I9c6450f93f06510477312bd02b119fd96b70857c
Reviewed-on: https://chromium-review.googlesource.com/245560
Reviewed-by: Fang Deng <fdeng@chromium.org>
Tested-by: Fang Deng <fdeng@chromium.org>
Commit-Queue: Fang Deng <fdeng@chromium.org>
diff --git a/frontend/afe/models_test.py b/frontend/afe/models_test.py
index 29a155d..993f145 100755
--- a/frontend/afe/models_test.py
+++ b/frontend/afe/models_test.py
@@ -481,12 +481,15 @@
                                                   'status': 'Queued'}],
                           'id': 5,
                           'jobkeyval_set': [{'id': 10,
+                                             'job_id': 5,
                                              'key': 'suite',
                                              'value': 'dummy'},
                                             {'id': 11,
+                                             'job_id': 5,
                                              'key': 'build',
                                              'value': 'daisy-release'},
                                             {'id': 12,
+                                             'job_id': 5,
                                              'key': 'experimental',
                                              'value': 'False'}],
                           'max_runtime_hrs': 72,
@@ -545,12 +548,15 @@
                                                   'status': 'Queued'}],
                           'id': 7,
                           'jobkeyval_set': [{'id': 16,
+                                             'job_id': 7,
                                              'key': 'suite',
                                              'value': 'dummy'},
                                             {'id': 17,
+                                             'job_id': 7,
                                              'key': 'build',
                                              'value': 'daisy-release'},
                                             {'id': 18,
+                                             'job_id': 7,
                                              'key': 'experimental',
                                              'value': 'False'}],
                           'max_runtime_hrs': 72,
@@ -583,9 +589,16 @@
             'hosts': [host.serialize() for host in hosts],
             'jobs': [job.serialize() for job in jobs]
         }
-
-        self.assertEqual(generated_heartbeat_response,
-                         self._get_example_response())
+        example_response = self._get_example_response()
+        # For attribute-like objects, we don't care about its id.
+        for r in [generated_heartbeat_response, example_response]:
+            for job in r['jobs']:
+                for keyval in job['jobkeyval_set']:
+                    keyval.pop('id')
+            for host in r['hosts']:
+                for attribute in host['hostattribute_set']:
+                    keyval.pop('id')
+        self.assertEqual(generated_heartbeat_response, example_response)
 
 
     def test_update(self):