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