Revision 26515bc1

/dev/null
1
# Copyright 2012, 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or without
4
# modification, are permitted provided that the following conditions
5
# are met:
6
#
7
#   1. Redistributions of source code must retain the above copyright
8
#      notice, this list of conditions and the following disclaimer.
9
#
10
#  2. Redistributions in binary form must reproduce the above copyright
11
#     notice, this list of conditions and the following disclaimer in the
12
#     documentation and/or other materials provided with the distribution.
13
#
14
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
# SUCH DAMAGE.
25
#
26
# The views and conclusions contained in the software and documentation are
27
# those of the authors and should not be interpreted as representing official
28
# policies, either expressed or implied, of GRNET S.A.
29

  
30
from django.db.models import Manager
31
from django.db.models.query import QuerySet
32

  
33

  
34
class ProtectedDeleteManager(Manager):
35
    """Manager for protecting Backend deletion.
36

  
37
    Call Backend delete() method in order to prevent deletion
38
    of Backends that host non-deleted VirtualMachines.
39

  
40
    """
41

  
42
    def get_query_set(self):
43
        return BackendQuerySet(self.model, using=self._db)
44

  
45

  
46
class BackendQuerySet(QuerySet):
47
    def delete(self):
48
        for backend in self._clone():
49
            backend.delete()
b/snf-cyclades-app/synnefo/db/models.py
32 32
from copy import deepcopy
33 33
from django.conf import settings
34 34
from django.db import models
35
from django.db import IntegrityError
36 35

  
37 36
import utils
38 37
from contextlib import contextmanager
......
41 40
from django.conf import settings as snf_settings
42 41
from aes_encrypt import encrypt_db_charfield, decrypt_db_charfield
43 42

  
44
from synnefo.db.managers import ProtectedDeleteManager
45 43
from synnefo.db import pools, fields
46 44

  
47 45
from synnefo.logic.rapi_pool import (get_rapi_client,
......
102 100
                                            null=False)
103 101
    ctotal = models.PositiveIntegerField('Total number of logical processors',
104 102
                                         default=0, null=False)
105
    # Custom object manager to protect from cascade delete
106
    objects = ProtectedDeleteManager()
107 103

  
108 104
    HYPERVISORS = (
109 105
        ("kvm", "Linux KVM hypervisor"),
......
160 156
            self.virtual_machines.filter(deleted=False)\
161 157
                                 .update(backend_hash=self.hash)
162 158

  
163
    def delete(self, *args, **kwargs):
164
        # Integrity Error if non-deleted VMs are associated with Backend
165
        if self.virtual_machines.filter(deleted=False).count():
166
            raise IntegrityError("Non-deleted virtual machines are associated "
167
                                 "with backend: %s" % self)
168
        else:
169
            # ON_DELETE = SET NULL
170
            for vm in self.virtual_machines.all():
171
                vm.backend = None
172
                vm.save()
173
            self.virtual_machines.all().backend = None
174
            # Remove BackendNetworks of this Backend.
175
            # Do not use networks.all().delete(), since delete() method of
176
            # BackendNetwork will not be called!
177
            for net in self.networks.all():
178
                net.delete()
179
            super(Backend, self).delete(*args, **kwargs)
180

  
181 159
    def __init__(self, *args, **kwargs):
182 160
        super(Backend, self).__init__(*args, **kwargs)
183 161
        if not self.pk:
......
320 298
    userid = models.CharField('User ID of the owner', max_length=100,
321 299
                              db_index=True, null=False)
322 300
    backend = models.ForeignKey(Backend, null=True,
323
                                related_name="virtual_machines",)
301
                                related_name="virtual_machines",
302
                                on_delete=models.PROTECT)
324 303
    backend_hash = models.CharField(max_length=128, null=True, editable=False)
325 304
    created = models.DateTimeField(auto_now_add=True)
326 305
    updated = models.DateTimeField(auto_now=True)
......
639 618
    }
640 619

  
641 620
    network = models.ForeignKey(Network, related_name='backend_networks')
642
    backend = models.ForeignKey(Backend, related_name='networks')
621
    backend = models.ForeignKey(Backend, related_name='networks',
622
                                on_delete=models.PROTECT)
643 623
    created = models.DateTimeField(auto_now_add=True)
644 624
    updated = models.DateTimeField(auto_now=True)
645 625
    deleted = models.BooleanField('Deleted', default=False)
b/snf-cyclades-app/synnefo/db/tests.py
96 96
            mfact.BackendFactory()
97 97
        self.assertRaises(Exception, mfact.BackendFactory, ())
98 98

  
99
    def test_delete_backend(self):
100
        vm = mfact.VirtualMachineFactory(backend=self.backend, deleted=True)
101
        bnet = mfact.BackendNetworkFactory(backend=self.backend)
102
        self.backend.delete()
103
        self.assertRaises(Backend.DoesNotExist, Backend.objects.get,
104
                          id=self.backend.id)
105
        # Test that VM is not deleted
106
        vm2 = VirtualMachine.objects.get(id=vm.id)
107
        self.assertEqual(vm2.backend, None)
108
        # Test tha backend networks are deleted, but not the network
109
        self.assertRaises(BackendNetwork.DoesNotExist,
110
                          BackendNetwork.objects.get, id=bnet.id)
111
        Network.objects.get(id=bnet.network.id)
112

  
113 99
    def test_delete_active_backend(self):
114 100
        """Test that a backend with non-deleted VMS is not deleted"""
115
        mfact.VirtualMachineFactory(backend=self.backend)
116
        self.assertRaises(IntegrityError, self.backend.delete, ())
101
        backend = mfact.BackendFactory()
102
        vm = mfact.VirtualMachineFactory(backend=backend)
103
        self.assertRaises(IntegrityError, backend.delete, ())
104
        vm.backend = None
105
        vm.save()
106
        backend.delete()
117 107

  
118 108
    def test_password_encryption(self):
119 109
        password_hash = self.backend.password
b/snf-cyclades-app/synnefo/logic/management/commands/backend-remove.py
30 30

  
31 31
from django.core.management.base import BaseCommand, CommandError
32 32
from synnefo.management.common import get_backend
33
from synnefo.db.models import VirtualMachine, BackendNetwork
33
from synnefo.logic import backend as backend_mod
34
from synnefo.db.models import Backend
35
from django.db import transaction, models
34 36

  
35 37

  
36
class Command(BaseCommand):
37
    can_import_settings = True
38
HELP_MSG = """\
39
Remove a backend from the Database. Backend should be set to drained before
40
trying to remove it, in order to avoid the allocation of a new instances in
41
this Backend.  Removal of a backend will fail if the backend hosts any
42
non-deleted instances."""
38 43

  
39
    help = "Remove a backend from the Database. Backend should be set\n" \
40
           "to drained before trying to remove it, in order to avoid the\n" \
41
           "allocation of a new instances in this Backend.\n\n" \
42
           "Removal of a backend will fail if the backend hosts any\n" \
43
           "non-deleted instances."
44 44

  
45
    output_transaction = True  # The management command runs inside
46
                               # an SQL transaction
45
class Command(BaseCommand):
46
    help = HELP_MSG
47 47

  
48 48
    def handle(self, *args, **options):
49 49
        write = self.stdout.write
......
52 52

  
53 53
        backend = get_backend(args[0])
54 54

  
55
        write('Trying to remove backend: %s\n' % backend.clustername)
56

  
57
        vms_in_backend = VirtualMachine.objects.filter(backend=backend,
58
                                                       deleted=False)
55
        write("Trying to remove backend: %s\n" % backend.clustername)
59 56

  
60
        if vms_in_backend:
57
        if backend.virtual_machines.filter(deleted=False).exists():
61 58
            raise CommandError('Backend hosts non-deleted vms. Can not delete')
62 59

  
63
        networks = BackendNetwork.objects.filter(backend=backend,
64
                                                 deleted=False)
65
        networks = [net.network.backend_id for net in networks]
60
        # Get networks before deleting backend, because after deleting the
61
        # backend, all BackendNetwork objects are deleted!
62
        networks = [bn.network for bn in backend.networks.all()]
66 63

  
67
        backend.delete()
64
        try:
65
            delete_backend(backend)
66
        except models.ProtectedError as e:
67
            msg = ("Can not delete backend because it contains"
68
                   "non-deleted VMs:\n%s" % e)
69
            raise CommandError(msg)
68 70

  
69
        write('Successfully removed backend.\n')
71
        write('Successfully removed backend from DB.\n')
70 72

  
71 73
        if networks:
72
            write('Left the following orphans networks in Ganeti:\n')
73
            write('  ' + '\n  * '.join(networks) + '\n')
74
            write('Manually remove them.\n')
74
            write("Clearing networks from %s..\n" % backend.clustername)
75
            for network in networks:
76
                backend_mod.delete_network(network=network, backend=backend)
77
            write("Successfully issued jobs to remove all networks.\n")
78

  
79

  
80
@transaction.commit_on_success
81
def delete_backend(backend):
82
    # Get X-Lock
83
    backend = Backend.objects.select_for_update().get(id=backend.id)
84
    # Clear 'backend' field of 'deleted' VirtualMachines
85
    backend.virtual_machines.filter(deleted=True).update(backend=None)
86
    # Delete all BackendNetwork objects of this backend
87
    backend.networks.all().delete()
88
    backend.delete()

Also available in: Unified diff