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