commit 26515bc162dfce9ced4fcaadbf6653fa8ce7e4f8
Author: Christos Stavrakakis <cstavr@grnet.gr>
Date:   Wed Oct 2 15:53:12 2013 +0300

    cyclades: Remove custom ProtectedDeletedManager
    
    Remove custom ProtectedDeletedManager. Instead use the
    ForeignKey.on_delete attribute that has been introduced in Django 1.4.

diff --git a/snf-cyclades-app/synnefo/db/managers.py b/snf-cyclades-app/synnefo/db/managers.py
deleted file mode 100644
index 0d2b9c2..0000000
--- a/snf-cyclades-app/synnefo/db/managers.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2012, 2013 GRNET S.A. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-#   1. Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#
-#  2. Redistributions in binary form must reproduce the above copyright
-#     notice, this list of conditions and the following disclaimer in the
-#     documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of GRNET S.A.
-
-from django.db.models import Manager
-from django.db.models.query import QuerySet
-
-
-class ProtectedDeleteManager(Manager):
-    """Manager for protecting Backend deletion.
-
-    Call Backend delete() method in order to prevent deletion
-    of Backends that host non-deleted VirtualMachines.
-
-    """
-
-    def get_query_set(self):
-        return BackendQuerySet(self.model, using=self._db)
-
-
-class BackendQuerySet(QuerySet):
-    def delete(self):
-        for backend in self._clone():
-            backend.delete()
diff --git a/snf-cyclades-app/synnefo/db/models.py b/snf-cyclades-app/synnefo/db/models.py
index cefb943..b505fb3 100644
--- a/snf-cyclades-app/synnefo/db/models.py
+++ b/snf-cyclades-app/synnefo/db/models.py
@@ -32,7 +32,6 @@ import datetime
 from copy import deepcopy
 from django.conf import settings
 from django.db import models
-from django.db import IntegrityError
 
 import utils
 from contextlib import contextmanager
@@ -41,7 +40,6 @@ from snf_django.lib.api import faults
 from django.conf import settings as snf_settings
 from aes_encrypt import encrypt_db_charfield, decrypt_db_charfield
 
-from synnefo.db.managers import ProtectedDeleteManager
 from synnefo.db import pools, fields
 
 from synnefo.logic.rapi_pool import (get_rapi_client,
@@ -102,8 +100,6 @@ class Backend(models.Model):
                                             null=False)
     ctotal = models.PositiveIntegerField('Total number of logical processors',
                                          default=0, null=False)
-    # Custom object manager to protect from cascade delete
-    objects = ProtectedDeleteManager()
 
     HYPERVISORS = (
         ("kvm", "Linux KVM hypervisor"),
@@ -160,24 +156,6 @@ class Backend(models.Model):
             self.virtual_machines.filter(deleted=False)\
                                  .update(backend_hash=self.hash)
 
-    def delete(self, *args, **kwargs):
-        # Integrity Error if non-deleted VMs are associated with Backend
-        if self.virtual_machines.filter(deleted=False).count():
-            raise IntegrityError("Non-deleted virtual machines are associated "
-                                 "with backend: %s" % self)
-        else:
-            # ON_DELETE = SET NULL
-            for vm in self.virtual_machines.all():
-                vm.backend = None
-                vm.save()
-            self.virtual_machines.all().backend = None
-            # Remove BackendNetworks of this Backend.
-            # Do not use networks.all().delete(), since delete() method of
-            # BackendNetwork will not be called!
-            for net in self.networks.all():
-                net.delete()
-            super(Backend, self).delete(*args, **kwargs)
-
     def __init__(self, *args, **kwargs):
         super(Backend, self).__init__(*args, **kwargs)
         if not self.pk:
@@ -320,7 +298,8 @@ class VirtualMachine(models.Model):
     userid = models.CharField('User ID of the owner', max_length=100,
                               db_index=True, null=False)
     backend = models.ForeignKey(Backend, null=True,
-                                related_name="virtual_machines",)
+                                related_name="virtual_machines",
+                                on_delete=models.PROTECT)
     backend_hash = models.CharField(max_length=128, null=True, editable=False)
     created = models.DateTimeField(auto_now_add=True)
     updated = models.DateTimeField(auto_now=True)
@@ -639,7 +618,8 @@ class BackendNetwork(models.Model):
     }
 
     network = models.ForeignKey(Network, related_name='backend_networks')
-    backend = models.ForeignKey(Backend, related_name='networks')
+    backend = models.ForeignKey(Backend, related_name='networks',
+                                on_delete=models.PROTECT)
     created = models.DateTimeField(auto_now_add=True)
     updated = models.DateTimeField(auto_now=True)
     deleted = models.BooleanField('Deleted', default=False)
diff --git a/snf-cyclades-app/synnefo/db/tests.py b/snf-cyclades-app/synnefo/db/tests.py
index 0830439..92d8d9c 100644
--- a/snf-cyclades-app/synnefo/db/tests.py
+++ b/snf-cyclades-app/synnefo/db/tests.py
@@ -96,24 +96,14 @@ class BackendTest(TestCase):
             mfact.BackendFactory()
         self.assertRaises(Exception, mfact.BackendFactory, ())
 
-    def test_delete_backend(self):
-        vm = mfact.VirtualMachineFactory(backend=self.backend, deleted=True)
-        bnet = mfact.BackendNetworkFactory(backend=self.backend)
-        self.backend.delete()
-        self.assertRaises(Backend.DoesNotExist, Backend.objects.get,
-                          id=self.backend.id)
-        # Test that VM is not deleted
-        vm2 = VirtualMachine.objects.get(id=vm.id)
-        self.assertEqual(vm2.backend, None)
-        # Test tha backend networks are deleted, but not the network
-        self.assertRaises(BackendNetwork.DoesNotExist,
-                          BackendNetwork.objects.get, id=bnet.id)
-        Network.objects.get(id=bnet.network.id)
-
     def test_delete_active_backend(self):
         """Test that a backend with non-deleted VMS is not deleted"""
-        mfact.VirtualMachineFactory(backend=self.backend)
-        self.assertRaises(IntegrityError, self.backend.delete, ())
+        backend = mfact.BackendFactory()
+        vm = mfact.VirtualMachineFactory(backend=backend)
+        self.assertRaises(IntegrityError, backend.delete, ())
+        vm.backend = None
+        vm.save()
+        backend.delete()
 
     def test_password_encryption(self):
         password_hash = self.backend.password
diff --git a/snf-cyclades-app/synnefo/logic/management/commands/backend-remove.py b/snf-cyclades-app/synnefo/logic/management/commands/backend-remove.py
index 2f53df8..1255ced 100644
--- a/snf-cyclades-app/synnefo/logic/management/commands/backend-remove.py
+++ b/snf-cyclades-app/synnefo/logic/management/commands/backend-remove.py
@@ -30,20 +30,20 @@
 
 from django.core.management.base import BaseCommand, CommandError
 from synnefo.management.common import get_backend
-from synnefo.db.models import VirtualMachine, BackendNetwork
+from synnefo.logic import backend as backend_mod
+from synnefo.db.models import Backend
+from django.db import transaction, models
 
 
-class Command(BaseCommand):
-    can_import_settings = True
+HELP_MSG = """\
+Remove a backend from the Database. Backend should be set to drained before
+trying to remove it, in order to avoid the allocation of a new instances in
+this Backend.  Removal of a backend will fail if the backend hosts any
+non-deleted instances."""
 
-    help = "Remove a backend from the Database. Backend should be set\n" \
-           "to drained before trying to remove it, in order to avoid the\n" \
-           "allocation of a new instances in this Backend.\n\n" \
-           "Removal of a backend will fail if the backend hosts any\n" \
-           "non-deleted instances."
 
-    output_transaction = True  # The management command runs inside
-                               # an SQL transaction
+class Command(BaseCommand):
+    help = HELP_MSG
 
     def handle(self, *args, **options):
         write = self.stdout.write
@@ -52,23 +52,37 @@ class Command(BaseCommand):
 
         backend = get_backend(args[0])
 
-        write('Trying to remove backend: %s\n' % backend.clustername)
-
-        vms_in_backend = VirtualMachine.objects.filter(backend=backend,
-                                                       deleted=False)
+        write("Trying to remove backend: %s\n" % backend.clustername)
 
-        if vms_in_backend:
+        if backend.virtual_machines.filter(deleted=False).exists():
             raise CommandError('Backend hosts non-deleted vms. Can not delete')
 
-        networks = BackendNetwork.objects.filter(backend=backend,
-                                                 deleted=False)
-        networks = [net.network.backend_id for net in networks]
+        # Get networks before deleting backend, because after deleting the
+        # backend, all BackendNetwork objects are deleted!
+        networks = [bn.network for bn in backend.networks.all()]
 
-        backend.delete()
+        try:
+            delete_backend(backend)
+        except models.ProtectedError as e:
+            msg = ("Can not delete backend because it contains"
+                   "non-deleted VMs:\n%s" % e)
+            raise CommandError(msg)
 
-        write('Successfully removed backend.\n')
+        write('Successfully removed backend from DB.\n')
 
         if networks:
-            write('Left the following orphans networks in Ganeti:\n')
-            write('  ' + '\n  * '.join(networks) + '\n')
-            write('Manually remove them.\n')
+            write("Clearing networks from %s..\n" % backend.clustername)
+            for network in networks:
+                backend_mod.delete_network(network=network, backend=backend)
+            write("Successfully issued jobs to remove all networks.\n")
+
+
+@transaction.commit_on_success
+def delete_backend(backend):
+    # Get X-Lock
+    backend = Backend.objects.select_for_update().get(id=backend.id)
+    # Clear 'backend' field of 'deleted' VirtualMachines
+    backend.virtual_machines.filter(deleted=True).update(backend=None)
+    # Delete all BackendNetwork objects of this backend
+    backend.networks.all().delete()
+    backend.delete()
