Revision aed9b901 snf-cyclades-app/synnefo/db/models.py

b/snf-cyclades-app/synnefo/db/models.py
31 31

  
32 32
from django.conf import settings
33 33
from django.db import models
34
from django.db import IntegrityError
35

  
36
from hashlib import sha1
37
from synnefo.api.faults import ServiceUnavailable
38
from synnefo.util.rapi import GanetiRapiClient
39

  
40

  
41
BACKEND_CLIENTS = {}    #{hash:Backend client}
42
BACKEND_HASHES = {}     #{Backend.id:hash}
43

  
44
def get_client(hash, backend):
45
    """Get a cached backend client or create a new one.
46

  
47
    @param hash: The hash of the backend
48
    @param backend: Either a backend object or backend ID
49
    """
50

  
51
    if backend is None:
52
        raise Exception("Backend is None. Cannot create a client.")
53

  
54
    if hash in BACKEND_CLIENTS:
55
        # Return cached client
56
        return BACKEND_CLIENTS[hash]
57

  
58
    # Always get a new instance to ensure latest credentials
59
    if isinstance(backend, Backend):
60
        backend = backend.id
61
    (credentials,) = Backend.objects.filter(id=backend).values_list('hash',
62
                                'clustername', 'port', 'username', 'password')
63

  
64
    hash, clustername, port, user, password = credentials
65

  
66
    # Check client for updated hash
67
    if hash in BACKEND_CLIENTS:
68
        return BACKEND_CLIENTS[hash]
69

  
70
    # Delete old version of the client
71
    if backend in BACKEND_HASHES:
72
        del BACKEND_CLIENTS[BACKEND_HASHES[backend]]
73

  
74
    # Create the new client
75
    client = GanetiRapiClient(clustername, port, user, password)
76

  
77
    # Store the client and the hash
78
    BACKEND_CLIENTS[hash] = client
79
    BACKEND_HASHES[backend] = hash
80

  
81
    return client
82

  
83

  
84
def clear_client_cache():
85
    BACKEND_CLIENTS.clear()
86
    BACKEND_HASHES.clear()
34 87

  
35 88

  
36 89
class Flavor(models.Model):
......
54 107
        return self.name
55 108

  
56 109

  
110
class BackendQuerySet(models.query.QuerySet):
111
    def delete(self):
112
        for backend in self._clone():
113
            backend.delete()
114

  
115
class ProtectDeleteManager(models.Manager):
116
    def get_query_set(self):
117
        return BackendQuerySet(self.model, using=self._db)
118

  
119

  
120
class Backend(models.Model):
121
    clustername = models.CharField('Cluster Name', max_length=128, unique=True)
122
    port = models.PositiveIntegerField('Port', default=5080)
123
    username = models.CharField('Username', max_length=64, blank=True,
124
                                null=True)
125
    password = models.CharField('Password', max_length=64, blank=True,
126
                                null=True)
127
    # Sha1 is up to 40 characters long
128
    hash = models.CharField('Hash', max_length=40, editable=False, null=False)
129
    drained = models.BooleanField('Drained', default=False, null=False)
130
    offline = models.BooleanField('Offline', default=False, null=False)
131
    # Last refresh of backend resources
132
    updated = models.DateTimeField(auto_now_add=True)
133
    # Backend resources
134
    mfree = models.PositiveIntegerField('Free Memory', default=0, null=False)
135
    mtotal = models.PositiveIntegerField('Total Memory', default=0, null=False)
136
    dfree = models.PositiveIntegerField('Free Disk', default=0, null=False)
137
    dtotal = models.PositiveIntegerField('Total Disk', default=0, null=False)
138
    pinst_cnt = models.PositiveIntegerField('Primary Instances', default=0,
139
                                            null=False)
140
    ctotal = models.PositiveIntegerField('Total number of logical processors',
141
                                         default=0, null=False)
142
    # Custom object manager to protect from cascade delete
143
    objects = ProtectDeleteManager()
144

  
145
    class Meta:
146
        verbose_name = u'Backend'
147
        ordering = ["clustername"]
148

  
149
    def __unicode__(self):
150
        return self.clustername
151

  
152
    @property
153
    def backend_id(self):
154
        return self.id
155

  
156
    @property
157
    def client(self):
158
        """Get or create a client. """
159
        if not self.offline:
160
            return get_client(self.hash, self)
161
        else:
162
            raise ServiceUnavailable
163

  
164
    def create_hash(self):
165
        """Create a hash for this backend. """
166
        return sha1('%s%s%s%s' % \
167
                (self.clustername, self.port, self.username, self.password)) \
168
                .hexdigest()
169

  
170
    def save(self, *args, **kwargs):
171
        # Create a new hash each time a Backend is saved
172
        old_hash = self.hash
173
        self.hash = self.create_hash()
174
        super(Backend, self).save(*args, **kwargs)
175
        if self.hash != old_hash:
176
            # Populate the new hash to the new instances
177
            self.virtual_machines.filter(deleted=False).update(backend_hash=self.hash)
178

  
179
    def delete(self, *args, **kwargs):
180
        # Integrity Error if non-deleted VMs are associated with Backend
181
        if self.virtual_machines.filter(deleted=False).count():
182
            raise IntegrityError("Non-deleted virtual machines are associated "
183
                                 "with backend: %s" % self)
184
        else:
185
            # ON_DELETE = SET NULL
186
            self.virtual_machines.all().backend=None
187
            super(Backend, self).delete(*args, **kwargs)
188

  
189

  
57 190
class VirtualMachine(models.Model):
58 191
    # The list of possible actions for a VM
59 192
    ACTIONS = (
......
142 275

  
143 276
    name = models.CharField('Virtual Machine Name', max_length=255)
144 277
    userid = models.CharField('User ID of the owner', max_length=100)
278
    backend = models.ForeignKey(Backend, null=True,
279
                                related_name="virtual_machines",)
280
    backend_hash = models.CharField(max_length=128, null=True, editable=False)
145 281
    created = models.DateTimeField(auto_now_add=True)
146 282
    updated = models.DateTimeField(auto_now=True)
147 283
    imageid = models.CharField(max_length=100, null=False)
......
164 300
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
165 301
    backendjobid = models.PositiveIntegerField(null=True)
166 302
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
167
            null=True)
303
                                     null=True)
168 304
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES,
169
            max_length=30, null=True)
305
                                        max_length=30, null=True)
170 306
    backendlogmsg = models.TextField(null=True)
171 307
    buildpercentage = models.IntegerField(default=0)
172 308
    backendtime = models.DateTimeField(default=datetime.datetime.min)
173 309

  
310
    @property
311
    def client(self):
312
        if not self.backend.offline:
313
            return get_client(self.backend_hash, self.backend_id)
314
        else:
315
            raise ServiceUnavailable
316

  
174 317
    # Error classes
175 318
    class InvalidBackendIdError(Exception):
176 319
        def __init__(self, value):
......
214 357
            self.backendlogmsg = None
215 358
            self.operstate = 'BUILD'
216 359

  
360
    def save(self, *args, **kwargs):
361
        # Store hash for first time saved vm
362
        if (self.id is None or self.backend_hash == '') and self.backend:
363
            self.backend_hash = self.backend.hash
364
        super(VirtualMachine, self).save(*args, **kwargs)
365

  
217 366
    @property
218 367
    def backend_vm_id(self):
219 368
        """Returns the backend id for this VM by prepending backend-prefix."""

Also available in: Unified diff