Repository: readthedocs/readthedocs.org

Issue:
Domain deletion task is failing
This method,

https://github.com/rtfd/readthedocs.org/blob/df85fefc5f59c7a00bcc1ffe23965efe75e8c895/readthedocs/projects/models.py#L1030-L1034

deletes the Domain from the database and trigger a task called: `symlink_domain`. This task tries to get the Domain object using the `pk` from the database and it fails because the object was already removed.

I do see two different approaches here:

1. do not trigger this task and rely on the task to remove orphan symlinks implemented in https://github.com/rtfd/readthedocs.org/pull/3543
1. refactor the task and the helper methods (`Symlink.symlink_cnames` and `Symlink.remove_symlink_cname`) to depend only in the `Domain.domain` string instead of the whole `Domain` object

Currently, we are depending on 1) but many errors are being generated in our sentry when the Domain is deleted: https://sentry.io/read-the-docs/readthedocs-org/issues/235601094/

> I saw this code before, but I didn't noticed that the Domain object was deleted and then tried to get it from the DB inside the task: https://github.com/rtfd/readthedocs.org/issues/3493#issuecomment-359579064


Discussion:
@humitos 
I would like to work on this issue.
From my understanding, 
`Domain.domain` should be passed instead of `Domain.pk` in the `delete` method like this
```python3
def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
        from readthedocs.projects import tasks
        broadcast(type='app', task=tasks.symlink_domain,
                  args=[self.project.pk, self.domain, True],)
        super(Domain, self).delete(*args, **kwargs)
```
and then correcting the task and other helper methods.

Resolving patch:
diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py
--- a/readthedocs/core/symlink.py
+++ b/readthedocs/core/symlink.py
@@ -152,10 +152,10 @@ def symlink_cnames(self, domain=None):
         if domain:
             domains = [domain]
         else:
-            domains = Domain.objects.filter(project=self.project)
+            domains = Domain.objects.filter(project=self.project).values_list('domain', flat=True)
         for dom in domains:
             log_msg = 'Symlinking CNAME: {} -> {}'.format(
-                dom.domain,
+                dom,
                 self.project.slug,
             )
             log.info(
@@ -167,13 +167,13 @@ def symlink_cnames(self, domain=None):
             )
 
             # CNAME to doc root
-            symlink = os.path.join(self.CNAME_ROOT, dom.domain)
+            symlink = os.path.join(self.CNAME_ROOT, dom)
             self.environment.run('ln', '-nsf', self.project_root, symlink)
 
             # Project symlink
             project_cname_symlink = os.path.join(
                 self.PROJECT_CNAME_ROOT,
-                dom.domain,
+                dom,
             )
             self.environment.run(
                 'ln',
@@ -183,16 +183,21 @@ def symlink_cnames(self, domain=None):
             )
 
     def remove_symlink_cname(self, domain):
-        """Remove CNAME symlink."""
-        log_msg = 'Removing symlink for CNAME {}'.format(domain.domain)
+        """
+        Remove CNAME symlink.
+
+        :param domain: domain for which symlink is to be removed
+        :type domain: str
+        """
+        log_msg = 'Removing symlink for CNAME {}'.format(domain)
         log.info(
             constants.LOG_TEMPLATE.format(
                 project=self.project.slug,
                 version='',
-                msg=log_msg,
+                msg=log_msg
             ),
         )
-        symlink = os.path.join(self.CNAME_ROOT, domain.domain)
+        symlink = os.path.join(self.CNAME_ROOT, domain)
         safe_unlink(symlink)
 
     def symlink_subprojects(self):
diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py
--- a/readthedocs/projects/models.py
+++ b/readthedocs/projects/models.py
@@ -1199,7 +1199,7 @@ def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
         broadcast(
             type='app',
             task=tasks.symlink_domain,
-            args=[self.project.pk, self.pk],
+            args=[self.project.pk, self.domain],
         )
 
     def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
@@ -1207,7 +1207,7 @@ def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
         broadcast(
             type='app',
             task=tasks.symlink_domain,
-            args=[self.project.pk, self.pk, True],
+            args=[self.project.pk, self.domain, True],
         )
         super().delete(*args, **kwargs)
 
diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py
--- a/readthedocs/projects/tasks.py
+++ b/readthedocs/projects/tasks.py
@@ -1074,9 +1074,16 @@ def symlink_project(project_pk):
 
 
 @app.task(queue='web', throws=(BuildEnvironmentWarning,))
-def symlink_domain(project_pk, domain_pk, delete=False):
+def symlink_domain(project_pk, domain, delete=False):
+    """
+    Symlink domain.
+
+    :param project_pk: project's pk
+    :type project_pk: int
+    :param domain: domain for the symlink
+    :type domain: str
+    """
     project = Project.objects.get(pk=project_pk)
-    domain = Domain.objects.get(pk=domain_pk)
     for symlink in [PublicSymlink, PrivateSymlink]:
         sym = symlink(project=project)
         if delete:


Test patch: