cc1  v2.1
CC1 source code docs
 All Classes Namespaces Files Functions Variables Pages
node.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 # @COPYRIGHT_begin
3 #
4 # Copyright [2010-2014] Institute of Nuclear Physics PAN, Krakow, Poland
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18 # @COPYRIGHT_end
19 
20 ##
21 # @package src.cm.models.node
22 #
23 
24 
25 import socket
26 
27 from django.db import models
28 from django.db.models import Sum
29 
30 from cm.utils.exception import CMException
31 from common.states import node_states, vm_states # , vnc_states,lease_states
32 import libvirt
33 
34 
35 ##
36 #
37 # @model{NODE} Class for Nodes in the cluster
38 #
39 # Node is physical machine providing its CPU for virtual machines ran
40 # within cluster. It hosts VMs with help of underlying Libvirt software.
41 # CM automatically selects Node fitting best for newly created VM.
42 # User doesn't know which Node it is exactly. He doesn't even need to be
43 # aware of nodes existence.
44 #
45 # VMs may start only on node with 'ok' state. CM automatically disables
46 # starting VMs on failed nodes.
47 #
48 class Node(models.Model):
49  username = models.CharField(max_length=30)
50  address = models.CharField(max_length=45)
51  transport = models.CharField(max_length=45)
52  driver = models.CharField(max_length=45)
53  suffix = models.CharField(max_length=20)
54  cpu_total = models.IntegerField()
55  memory_total = models.IntegerField()
56  hdd_total = models.IntegerField()
57  state = models.IntegerField()
58  comment = models.TextField(null=True, blank=True)
59  errors = models.TextField(null=True, blank=True)
60 
61  class Meta:
62  app_label = 'cm'
63 
64  # method for printing object instance
65  def __unicode__(self):
66  return str(self.id)
67 
68  @property
69  ##
70  #
71  # @returns{dict} node's data
72  # \n fields:
73  # @dictkey{id}
74  # @dictkey{address}
75  # @dictkey{cpu_total}
76  # @dictkey{cpu_free}
77  # @dictkey{memory_total}
78  # @dictkey{memory_free}
79  # @dictkey{hdd_total}
80  # @dictkey{hdd_free}
81  # @dictkey{state}
82  #
83  def dict(self):
84  d = {}
85  d['node_id'] = self.id
86  d['address'] = self.address or ''
87  d['cpu_total'] = self.cpu_total
88  d['cpu_free'] = self.cpu_free
89  d['memory_total'] = self.memory_total
90  d['memory_free'] = self.memory_free
91  d['hdd_total'] = self.hdd_total
92  d['hdd_free'] = self.hdd_free
93  d['state'] = self.state or ''
94  d['comment'] = self.comment or ''
95  d['errors'] = self.errors or ''
96 
97  return d
98 
99  @property
100  ##
101  #
102  # @returns{dict} node's extended data
103  # \n fields:
104  # @dictkey{id}
105  # @dictkey{username}
106  # @dictkey{address}
107  # @dictkey{transport}
108  # @dictkey{driver}
109  # @dictkey{cpu_total}
110  # @dictkey{cpu_free}
111  # @dictkey{memory_total}
112  # @dictkey{memory_free}
113  # @dictkey{hdd_total}
114  # @dictkey{hdd_free}
115  # @dictkey{state}
116  # @dictkey{suffix}
117  #
118  def long_dict(self):
119  d = {}
120  d['node_id'] = self.id
121  d['username'] = self.username or ''
122  d['address'] = self.address or ''
123  d['transport'] = self.transport or ''
124  d['driver'] = self.driver or ''
125  d['cpu_total'] = self.cpu_total
126  d['cpu_free'] = self.cpu_free
127  d['memory_total'] = self.memory_total
128  d['memory_free'] = self.memory_free
129  d['hdd_total'] = self.hdd_total
130  d['hdd_free'] = self.hdd_free
131  d['state'] = self.state or ''
132  d['suffix'] = self.suffix or ''
133  d['comment'] = self.comment or ''
134  d['errors'] = self.errors or ''
135 
136  return d
137 
138  @property
139  ##
140  #
141  # @returns{dict} node's further extended data
142  # \n fields:
143  # @dictkey{id}
144  # @dictkey{username}
145  # @dictkey{address}
146  # @dictkey{transport}
147  # @dictkey{driver}
148  # @dictkey{cpu_total}
149  # @dictkey{cpu_free}
150  # @dictkey{memory_total}
151  # @dictkey{memory_free}
152  # @dictkey{hdd_total}
153  # @dictkey{hdd_free}
154  # @dictkey{state}
155  # @dictkey{suffix}
156  # @dictkey{real_memory_total}
157  # @dictkey{real_memory_free}
158  # @dictkey{lv_memory_total}
159  # @dictkey{lv_memory_free}
160  # @dictkey{lv_cpu_total}
161  # @dictkey{lv_cpu_free}
162  #
163  def long_long_dict(self):
164  d = {}
165  d['node_id'] = self.id
166  d['username'] = self.username or ''
167  d['address'] = self.address or ''
168  d['transport'] = self.transport or ''
169  d['driver'] = self.driver or ''
170  d['cpu_total'] = self.cpu_total
171  d['cpu_free'] = self.cpu_free
172  d['memory_total'] = self.memory_total
173  d['memory_free'] = self.memory_free
174  d['hdd_total'] = self.hdd_total
175  d['hdd_free'] = self.hdd_free
176  d['state'] = self.state or ''
177  d['suffix'] = self.suffix or ''
178  d['real_memory_total'] = self.real_memory_total
179  d['real_memory_free'] = self.real_memory_free
180  d['real_hdd_total'] = self.real_hdd_total
181  d['real_hdd_free'] = self.real_hdd_free
182  d['lv_memory_total'] = self.lv_memory_total
183  d['lv_memory_free'] = self.lv_memory_free
184  d['lv_cpu_total'] = self.lv_cpu_total
185  d['lv_cpu_free'] = self.lv_cpu_free
186  d['comment'] = self.comment or ''
187  d['errors'] = self.errors or ''
188 
189  return d
190 
191  @property
192  ##
193  #
194  # @returns{string} this Node's connection string
195  #
196  def conn_string(self):
197  return '%s+%s://%s@%s%s' % (self.driver, self.transport, self.username, self.address, self.suffix)
198 
199  @property
200  ##
201  #
202  # @returns{string} SSH address of this Node ('user\@address')
203  #
204  def ssh_string(self):
205  return '%s@%s' % (self.username, self.address)
206 
207  @property
208  ##
209  #
210  # @returns{int} this Node's <b>free CPU</b> amount
211  #
212  def cpu_free(self):
213 
214  c = self.vm_set.exclude(state__in=[vm_states['closed'], vm_states['erased']]).aggregate(cpu_sum=Sum('template__cpu'))
215  csum = c['cpu_sum'] or 0 # 0 if no result exists in query
216 
217  c_free = self.cpu_total - csum
218 
219  return c_free
220 
221  @property
222  ##
223  #
224  # @returns{int} this Node's <b>free memory</b> amount
225  #
226  def memory_free(self):
227 
228  m = self.vm_set.exclude(state__in=[vm_states['closed'], vm_states['erased']]).aggregate(memory_sum=Sum('template__memory'))
229  msum = m['memory_sum'] or 0 # 0 if no results exists in query
230 
231  m_free = self.memory_total - msum
232 
233  return m_free
234 
235  @property
236  ##
237  #
238  # @returns{int} this Node's <b>free hdd</b> amount
239  #
240  def hdd_free(self):
241 
242  s = self.vm_set.exclude(state__in=[vm_states['closed'], vm_states['erased']]).aggregate(size_sum=Sum('system_image__size'))
243  ssum = s['size_sum'] or 0 # 0 if no results exists in query
244  h_free = self.hdd_total - ssum
245 
246  return h_free
247 
248  # it connects through libvirt and read information for the node and put them in lv_data node attribute
249  @property
250  ##
251  #
252  # @returns{array} resources data of this Node provided by Libvirt running on it:
253  # 0. used_cpu,
254  # 1. used_memory [KBytes],
255  # 2. total_cpu,
256  # 3. total_memory [MBytes],
257  # 4. free_memory [Bytes],
258  # 5. pool_total_space [Bytes],
259  # 6. pool_free_space [Bytes]
260  #
261  def read_lv_data(self):
262  if hasattr(self, 'lv_data'):
263  return self.lv_data
264 
265  conn = libvirt.open(self.conn_string)
266  used_cpu = 0
267  used_memory = 0
268  total_cpu = conn.getInfo()[2]
269  total_memory = conn.getInfo()[1] # MBytes
270  free_memory = conn.getFreeMemory() # Bytes
271 
272  pool = conn.storagePoolLookupByName('images')
273  pool.refresh(0)
274  pool_total_space = pool.info()[1] # Bytes
275  pool_free_space = pool.info()[3] # Bytes
276  for id_dom in conn.listDomainsID():
277  dom = conn.lookupByID(id_dom)
278  info = dom.info() # struct virDomainInfo
279  used_cpu += info[3]
280  used_memory += info[1] # KBytes
281  self.lv_data = [used_cpu, used_memory, total_cpu, total_memory, free_memory, pool_total_space, pool_free_space]
282  conn.close()
283  return self.lv_data
284 
285  @property
286  ##
287  #
288  # @returns{int} this Node's <b>total memory</b> amount [MB] (according to Libvirt)
289  #
290  def lv_memory_total(self):
291  return self.read_lv_data[3]
292 
293  @property
294  ##
295  #
296  # @returns{int} this Node's <b>free memory</b> amount [MB] (according to libvirt)
297  #
298  def lv_memory_free(self):
299  r = self.read_lv_data[3] - self.read_lv_data[1] / 1024
300  return r
301 
302  @property
303  ##
304  #
305  # @returns{int} this Node's <b>total CPU</b> amount (according to libvirt)
306  #
307  def lv_cpu_total(self):
308  return self.read_lv_data[2]
309 
310  @property
311  ##
312  #
313  # @returns{int} this Node's <b>free CPU</b> amount (according to libvirt)
314  #
315  def lv_cpu_free(self):
316  r = self.read_lv_data[2] - self.read_lv_data[0]
317  return r
318 
319  @property
320  ##
321  #
322  # @returns{int} this Node's <b>total memory</b> amount [MB] (real)
323  #
324  def real_memory_total(self):
325  return self.read_lv_data[3]
326 
327  @property
328  ##
329  #
330  # @returns{int} this Node's <b>free memory</b> amount [MB] (real)
331  #
332  def real_memory_free(self):
333  r = self.read_lv_data[4] / 1024 / 1024
334  return r
335 
336  @property
337  ##
338  #
339  # @returns{int} this Node's <b>total HDD</b> storage amount [MB] (real)
340  #
341  def real_hdd_total(self):
342  r = self.read_lv_data[5] / 1024 / 1024
343  return r
344 
345  @property
346  ##
347  #
348  # @returns{int} this Node's <b>free HDD</b> amount [MB] (real)
349  #
350  def real_hdd_free(self):
351  r = self.read_lv_data[6] / 1024 / 1024
352  return r
353 
354  @property
355  ##
356  #
357  # Method
358  #
359  def get_cm_ip(self):
360  try:
361  conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
362  conn.connect((self.address, 22))
363  except:
364  raise CMException('node_connect')
365  ip = conn.getsockname()[0]
366  conn.close()
367 
368  return ip
369 
370  @staticmethod
371  ##
372  #
373  # @parameter{user_id,int} id of the declared Node's owner
374  # @parameter{node_id,int} requested Node's id
375  #
376  # @returns{Node} instance of the requested Node
377  #
378  # @raises{node_get,CMException} no such Node
379  #
380  def get(user_id, node_id):
381 
382  try:
383  n = Node.objects.get(pk=node_id)
384  except:
385  raise CMException('node_get')
386 
387  return n
388 
389  # sets node's stat to locked
390  ##
391  #
392  # Method sets node's stat to locked.
393  #
394  def lock(self):
395  self.state = node_states['locked']
396  self.save()
397  # TODO: send email
398 
399  # the funcion is called by vm utils create
400  # finds first node (or with node_id, if given) that is sufficient enough for image and template and returns it.
401  @staticmethod
402  ##
403  #
404  # Method picks either requestd or, if none, first available Node that is
405  # sufficient enough for specified \c Image and \c Template and returns
406  # that Node.
407  #
408  # @parameter{template,Template} instance of the VM's Template to run on
409  # searched Node
410  # @parameter{image,Image} instance of the Image to run on searched Node
411  # @parameter{node_id,int} @optional{first suitable}
412  #
413  # @returns{Node} sufficient instance of the Node
414  #
415  # @raises{node_get,CMException} cannot get sufficient Node
416  #
417  def get_free_node(template, image, node_id=None):
418  if node_id:
419  node = Node.objects.get(pk=node_id)
420  if node.cpu_free >= template.cpu and node.memory_free >= template.memory and node.hdd_free >= image.size:
421  return node
422  else:
423  raise CMException('node_get')
424  else:
425  available_nodes = []
426 
427  # Get all nodes, which fit this VM
428  for node in Node.objects.filter(state__exact=node_states['ok']).order_by('id'):
429  if node.cpu_free >= template.cpu and node.memory_free >= template.memory and node.hdd_free >= image.size:
430  available_nodes.append(node)
431 
432  if not available_nodes:
433  raise CMException('node_get')
434 
435  # Get best matching (most filled) node
436  available_nodes.sort(key=lambda node: node.cpu_free)
437  return available_nodes[0]
438 
439  # TODO:
440  #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
441  # define: get_lease(), get_vnc()
442  #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
443