Repository: fedora-infra/bodhi

Issue:
internal server error when querying some comments
I'm getting this HTTP 500 Internal Server Error when querying comments on bodhi.fedoraproject.org:

> 500 Internal Server Error
>
>Traceback (most recent call last): File "/usr/lib/python3.8/site-packages/pyramid_mako/__init__.py", line 148, in __call__ result = template.render_unicode(**system) File "/usr/lib/python3.8/site-packages/mako/template.py", line 481, in render_unicode return runtime._render( File "/usr/lib/python3.8/site-packages/mako/runtime.py", line 878, in _render _render_context( File "/usr/lib/python3.8/site-packages/mako/runtime.py", line 920, in _render_context _exec_template(inherit, lclcontext, args=args, kwargs=kwargs) File "/usr/lib/python3.8/site-packages/mako/runtime.py", line 947, in _exec_template callable_(context, *args, **kwargs) File "master_html", line 132, in render_body File "/usr/local/lib/python3.8/site-packages/bodhi/server/templates/comments.html", line 44, in render_body ${fragments.comment(comment)} File "fragments_html", line 174, in render_comment AttributeError: 'NoneType' object has no attribute 'get_url'

Looks like there are some invalid Comment objects in the bodhi database, where a field that should be NOT NULL is NULL.

For example: https://bodhi.fedoraproject.org/comments/?rows_per_page=5&page=1136

As far as I can tell, the page numbers (when querying with `rows_per_page=5`) that trigger this issue are 1136, 1138, 1141, 1142, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1183, 1184, 1188, and 1190. That does not pinpoint the problematic comments exactly, but should get you close to what's wrong.


Discussion:
It looks like we have a bunch of comments that are not assigned to their update object:
```
>>> com = m.Comment.query.filter_by(update=None).all()
>>> len(com)
54
```
(that's from my vagrant Bodhi, the latest database dump seems to be 3 weeks old)

This is something that must not happen. Maybe we should drop the buggy comments and set the update_id column in the comments table to be not nullable, since it already has `index=True` set.
Yeah, I thought that something like this might be the cause. Setting update_id to `NOT NULL` and removing the broken entries seems to be a good idea :+1:

Resolving patch:
diff --git a/bodhi/server/migrations/versions/559acf7e2c16_make_comments_update_not_nullable.py b/bodhi/server/migrations/versions/559acf7e2c16_make_comments_update_not_nullable.py
new file mode 100644
--- /dev/null
+++ b/bodhi/server/migrations/versions/559acf7e2c16_make_comments_update_not_nullable.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2020 Mattia Verga
+#
+# This file is part of Bodhi.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+"""
+Make comments.update not nullable.
+
+Revision ID: 559acf7e2c16
+Revises: 1c97477e38ee
+Create Date: 2020-11-17 16:25:38.464821
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '559acf7e2c16'
+down_revision = '1c97477e38ee'
+
+
+def upgrade():
+    """Delete orphan objects and set comments.update_id not nullable."""
+    # We must drop all comments not referenced to any update
+    op.execute('DELETE FROM comments WHERE update_id IS NULL')
+    # Then we remove TestCaseKarma and BugKarma orphaned of their comment
+    op.execute('DELETE FROM comment_bug_assoc WHERE comment_id IS NULL')
+    op.execute('DELETE FROM comment_testcase_assoc WHERE comment_id IS NULL')
+
+    op.alter_column('comments', 'update_id',
+                    existing_type=sa.INTEGER(),
+                    nullable=False)
+
+
+def downgrade():
+    """Restore comments.update_id to be nullable."""
+    op.alter_column('comments', 'update_id',
+                    existing_type=sa.INTEGER(),
+                    nullable=True)
diff --git a/bodhi/server/models.py b/bodhi/server/models.py
--- a/bodhi/server/models.py
+++ b/bodhi/server/models.py
@@ -1893,7 +1893,8 @@ class Update(Base):
         backref=backref('updates', passive_deletes=True, order_by='Update.date_submitted'))
 
     # One-to-many relationships
-    comments = relationship('Comment', backref='update', order_by='Comment.timestamp')
+    comments = relationship('Comment', backref='update', cascade="all,delete,delete-orphan",
+                            order_by='Comment.timestamp')
     builds = relationship('Build', backref='update', order_by='Build.nvr')
 
     # Many-to-many relationships
@@ -3340,12 +3341,10 @@ def comment(self, session, text, karma=0, author=None, karma_critpath=0,
             user = User(name=author)
             session.add(user)
 
-        comment = Comment(text=text, karma=karma, karma_critpath=karma_critpath, user=user)
+        comment = Comment(text=text, karma=karma, karma_critpath=karma_critpath,
+                          update=self, user=user)
         session.add(comment)
 
-        self.comments.append(comment)
-        session.flush()
-
         if karma != 0:
             # Determine whether this user has already left karma, and if so what the most recent
             # karma value they left was. We should examine all but the most recent comment, since
@@ -4311,7 +4310,8 @@ class BugKarma(Base):
 
     # Many-to-one relationships
     comment_id = Column(Integer, ForeignKey('comments.id'))
-    comment = relationship("Comment", backref='bug_feedback')
+    comment = relationship("Comment", backref=backref('bug_feedback',
+                                                      cascade="all,delete,delete-orphan"))
 
     bug_id = Column(Integer, ForeignKey('bugs.bug_id'))
     bug = relationship("Bug", backref='feedback')
@@ -4334,7 +4334,8 @@ class TestCaseKarma(Base):
 
     # Many-to-one relationships
     comment_id = Column(Integer, ForeignKey('comments.id'))
-    comment = relationship("Comment", backref='testcase_feedback')
+    comment = relationship("Comment", backref=backref('testcase_feedback',
+                                                      cascade="all,delete,delete-orphan"))
 
     testcase_id = Column(Integer, ForeignKey('testcases.id'))
     testcase = relationship("TestCase", backref='feedback')
@@ -4369,7 +4370,7 @@ class Comment(Base):
     # testcase_feedback backref from TestCaseKarma
 
     # Many-to-one relationships
-    update_id = Column(Integer, ForeignKey('updates.id'), index=True)
+    update_id = Column(Integer, ForeignKey('updates.id'), nullable=False, index=True)
     # update backref from Update
     user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
     # user backref from User


Test patch: