Have the scheduler check for and sometimes cleanup various DB inconsistencies.
* in periodic cleanup, check for relationships to invalidated objects, and remove them (and send notification email)
* in 24hr cleanup, check for hosts with != 1 platform, and send notification email
Also changed AFE models to have labels remove associations with tests (as dependencies) when deleted (invalidated).
Signed-off-by: Steve Howard <showard@google.com>
git-svn-id: http://test.kernel.org/svn/autotest/trunk@3185 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/frontend/afe/models.py b/frontend/afe/models.py
index 559b3a7..27ed7bf 100644
--- a/frontend/afe/models.py
+++ b/frontend/afe/models.py
@@ -106,6 +106,7 @@
def clean_object(self):
self.host_set.clear()
+ self.test_set.clear()
def enqueue_job(self, job, atomic_group=None, is_template=False):
diff --git a/scheduler/monitor_db_cleanup.py b/scheduler/monitor_db_cleanup.py
index 59313c0..1ed71a1 100644
--- a/scheduler/monitor_db_cleanup.py
+++ b/scheduler/monitor_db_cleanup.py
@@ -108,17 +108,57 @@
def _check_for_db_inconsistencies(self):
- logging.info('Checking for db inconsistencies')
- query = models.HostQueueEntry.objects.filter(active=True, complete=True)
- if query.count() != 0:
- subject = ('%d queue entries found with active=complete=1'
- % query.count())
- message = '\n'.join(str(entry.get_object_dict())
- for entry in query[:50])
- if len(query) > 50:
- message += '\n(truncated)\n'
+ logging.info('Cleaning db inconsistencies')
+ self._check_all_invalid_related_objects()
- logging.error(subject)
+
+ def _check_invalid_related_objects_one_way(self, first_model,
+ relation_field, second_model):
+ if 'invalid' not in first_model.get_field_dict():
+ return []
+ invalid_objects = list(first_model.objects.filter(invalid=True))
+ first_model.objects.populate_relationships(invalid_objects,
+ second_model,
+ 'related_objects')
+ error_lines = []
+ for invalid_object in invalid_objects:
+ if invalid_object.related_objects:
+ related_list = ', '.join(str(related_object) for related_object
+ in invalid_object.related_objects)
+ error_lines.append('Invalid %s %s is related to %ss: %s'
+ % (first_model.__name__, invalid_object,
+ second_model.__name__, related_list))
+ related_manager = getattr(invalid_object, relation_field)
+ related_manager.clear()
+ return error_lines
+
+
+ def _check_invalid_related_objects(self, first_model, first_field,
+ second_model, second_field):
+ errors = self._check_invalid_related_objects_one_way(
+ first_model, first_field, second_model)
+ errors.extend(self._check_invalid_related_objects_one_way(
+ second_model, second_field, first_model))
+ return errors
+
+
+ def _check_all_invalid_related_objects(self):
+ model_pairs = ((models.Host, 'labels', models.Label, 'host_set'),
+ (models.AclGroup, 'hosts', models.Host, 'aclgroup_set'),
+ (models.AclGroup, 'users', models.User, 'aclgroup_set'),
+ (models.Test, 'dependency_labels', models.Label,
+ 'test_set'))
+ errors = []
+ for first_model, first_field, second_model, second_field in model_pairs:
+ errors.extend(self._check_invalid_related_objects(
+ first_model, first_field, second_model, second_field))
+
+ if errors:
+ subject = ('%s relationships to invalid models, cleaned all' %
+ len(errors))
+ message = '\n'.join(errors)
+ logging.warning(subject)
+ logging.warning(message)
email_manager.manager.enqueue_notify_email(subject, message)
@@ -150,6 +190,7 @@
def _cleanup(self):
logging.info('Running 24 hour clean up')
self._django_session_cleanup()
+ self._check_for_uncleanable_db_inconsistencies()
def _django_session_cleanup(self):
@@ -159,3 +200,60 @@
logging.info('Deleting old sessions from django_session')
sql = 'DELETE FROM django_session WHERE expire_date < NOW()'
self._db.execute(sql)
+
+
+ def _check_for_uncleanable_db_inconsistencies(self):
+ logging.info('Checking for uncleanable DB inconsistencies')
+ self._check_for_active_and_complete_queue_entries()
+ self._check_for_multiple_platform_hosts()
+ self._check_for_no_platform_hosts()
+
+
+ def _check_for_active_and_complete_queue_entries(self):
+ query = models.HostQueueEntry.objects.filter(active=True, complete=True)
+ if query.count() != 0:
+ subject = ('%d queue entries found with active=complete=1'
+ % query.count())
+ lines = [str(entry.get_object_dict()) for entry in query]
+ self._send_inconsistency_message(subject, lines)
+
+
+ def _check_for_multiple_platform_hosts(self):
+ rows = self._db.execute("""
+ SELECT hosts.id, hostname, COUNT(1) AS platform_count,
+ GROUP_CONCAT(labels.name)
+ FROM hosts
+ INNER JOIN hosts_labels ON hosts.id = hosts_labels.host_id
+ INNER JOIN labels ON hosts_labels.label_id = labels.id
+ WHERE labels.platform
+ GROUP BY hosts.id
+ HAVING platform_count > 1
+ ORDER BY hostname""")
+ if rows:
+ subject = '%s hosts with multiple platforms' % self._db.rowcount
+ lines = [' '.join(str(item) for item in row)
+ for row in rows]
+ self._send_inconsistency_message(subject, lines)
+
+
+ def _check_for_no_platform_hosts(self):
+ rows = self._db.execute("""
+ SELECT hostname
+ FROM hosts
+ LEFT JOIN hosts_labels
+ ON hosts.id = hosts_labels.host_id
+ AND hosts_labels.label_id IN (SELECT id FROM labels
+ WHERE platform)
+ WHERE NOT hosts.invalid AND hosts_labels.host_id IS NULL""")
+ if rows:
+ subject = '%s hosts with no platform' % self._db.rowcount
+ self._send_inconsistency_message(
+ subject, [', '.join(row[0] for row in rows)])
+
+
+ def _send_inconsistency_message(self, subject, lines):
+ logging.error(subject)
+ message = '\n'.join(lines)
+ if len(message) > 5000:
+ message = message[:5000] + '\n(truncated)\n'
+ email_manager.manager.enqueue_notify_email(subject, message)