cc1  v2.1
CC1 source code docs
 All Classes Namespaces Files Functions Variables Pages
user.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.user
22 #
23 # @author Maciej Nabożny <di.dijo@gmail.com>
24 # @author Tomek Sośnicki <tom.sosnicki@gmail.com>
25 #
26 from django.db import models
27 from django.db.models import Sum
28 
29 from django.conf import settings
30 from cm.utils.exception import CMException
31 from cm.utils import log
32 from common.states import vm_states, image_states
33 import datetime
34 import calendar
35 
36 
37 ##
38 #
39 # @model{USER} User's class for keeping quota.
40 #
41 # To have the ability to create and manage VMs and to have access to other
42 # CC1 features one needs to have an User account. User's general data is
43 # stored and proceeded on CLM and that's where User authorization and
44 # authentication is performed. However, User's quota differs within CM's and
45 # not every User has granted access to each Cluster. Thats why User class
46 # exists also on CM and User's quota is stored here.
47 #
48 # User quota is defined in "settings.py" file. It covers:
49 # - simultanous storage space usage,
50 # - simultanous CPU usage,
51 # - simultanous RAM memory usage,
52 # - IP requests count,
53 # - monthly points consumption.
54 #
55 # Points a month are calculated based on overall monthly storage, CPU, and
56 # RAM usage.
57 #
58 # By default each user gains quota speficied
59 # by CM settings. It may be further adjusted by admin via admin interface.
60 #
61 class User(models.Model):
62  memory = models.IntegerField()
63  cpu = models.IntegerField()
64  storage = models.IntegerField()
65  public_ip = models.IntegerField()
66  points = models.IntegerField()
67 
68  class Meta:
69  app_label = 'cm'
70 
71  # method for printing object instance
72  def __unicode__(self):
73  return str(self.id)
74 
75  def get_quota(self, fields=None, used=True, total=True):
76  quota = {}
77  fields = fields or ["memory", "cpu", "storage", "public_ip", "points"]
78  for field in fields:
79  if used and field != "public_ip":
80  quota["%s_used" % field] = getattr(self, "used_%s" % field)
81  if total:
82  quota[field] = getattr(self, field)
83  quota['user_id'] = self.id
84  return quota
85 
86  @property
87  ##
88  #
89  # @returns{dict} user's data
90  # \n fields:
91  # @dictkey{id,int}
92  # @dictkey{cpu,int} CPU total granted by User quota
93  # @dictkey{memory,int} memory total [MB] granted by User quota
94  # @dictkey{storage,int} storage total [MB] granted by User quota
95  # @dictkey{public_ip,int} public IPs count total granted by User quota
96  # @dictkey{used_points,int} points used within current month by User
97  #
98  def dict(self):
99  d = {}
100  d['user_id'] = self.id
101  d['cpu'] = self.cpu
102  d['memory'] = self.memory
103  d['storage'] = self.storage
104  d['public_ip'] = self.public_ip
105  d['used_points'] = self.used_points
106  # not points, only used points?
107  return d
108 
109  @property
110  ##
111  #
112  # @returns{dict} user's extended data in dictionary format.
113  # \n fields:
114  # @dictkey{id,int}
115  # @dictkey{cpu,int} CPU total granted by User quota
116  # @dictkey{memory,int} memory total [MB] granted by User quota
117  # @dictkey{storage,int} storage total [MB] granted by User quota
118  # @dictkey{public_ip,int} public IPs count total granted by User quota
119  # @dictkey{points,int} points total granted by User quota
120  # @dictkey{used_cpu,int} cpu currently used by User
121  # @dictkey{used_memory,int} memory currently used by User
122  # @dictkey{used_storage,int} storage currently used by User
123  # @dictkey{used_public_ip,int} public IPs count currently used by User
124  # @dictkey{used_points,int} points used within current month by User
125  #
126  def long_dict(self):
127  d = {}
128  d['user_id'] = self.id
129  d['cpu'] = self.cpu
130  d['memory'] = self.memory
131  d['storage'] = self.storage
132  d['public_ip'] = self.public_ip
133  d['points'] = self.points
134  d['used_cpu'] = self.used_cpu
135  d['used_memory'] = self.used_memory
136  d['used_storage'] = self.used_storage
137  d['used_public_ip'] = self.public_ips.count()
138  d['used_points'] = self.used_points
139  return d
140 
141  @staticmethod
142  ##
143  #
144  # Method creates and returns CM User's data. Quota is set to default
145  # defined in "settings.py" file.
146  #
147  # @parameter{user_id,int} id of the CLM User to be stored on particular CM
148  #
149  # @returns{User} new instance of CM User referencing to CLM user with
150  # given id
151  #
152  def create(user_id):
153 
154  u = User()
155  u.id = user_id # note: id is set explicitly
156  u.memory = settings.USER_QUOTA['memory']
157  u.cpu = settings.USER_QUOTA['cpu']
158  u.storage = settings.USER_QUOTA['storage']
159  u.public_ip = settings.USER_QUOTA['public_ip']
160  u.points = settings.USER_QUOTA['points']
161  u.save()
162 
163  return u
164 
165  @property
166  ##
167  #
168  # @returns{int} CPU being used by User the moment of call
169  #
170  def used_cpu(self):
171  # It sums all the cpu fields of the templates associated with the vms used by user
172  c = self.vm_set.filter(state__in=[vm_states['running'], vm_states['running ctx'], vm_states['init']]).aggregate(cpu_sum=Sum('template__cpu'))
173 
174  return c['cpu_sum'] or 0
175 
176  @property
177  ##
178  #
179  # @returns{int} memory being used by User [MB] the moment of call
180  #
181  def used_memory(self):
182  # Returns memory used by user. It sums all the memory fields of the templates associated
183  # with the vms used by user
184  m = self.vm_set.filter(state__in=[vm_states['running'], vm_states['running ctx'], vm_states['init']]).aggregate(memory_sum=Sum('template__memory'))
185 
186  return m['memory_sum'] or 0
187 
188  @property
189  ##
190  #
191  # @returns{int} storage being used by User [MB] the moment of call
192  #
193  def used_storage(self):
194 
195  # retrieve for all the images objects (sys, disk and cd) related to user (exluding images locked) the sum of their size
196  # if there are no images of a type related to user, 0 is put for the sum
197 
198  return self.image_set.exclude(state__exact=image_states['locked']).aggregate(Sum('size'))['size__sum'] or 0
199 
200  @property
201  ##
202  #
203  # @returns{int} points consumed by VMs of this User's that have been
204  # working within current calendar month. Those might either have been
205  # started the previous month and be still running during months break,
206  # or be just started in current month. Those may be still running or
207  # already closed VMs.
208  #
209  def used_points(self):
210 
211 
212  # Returns total points used by user. It sums all the points fields of the templates associated with the vms
213  # used by user
214 
215  p = 0
216  dt_now = datetime.datetime.now()
217  start_month = datetime.datetime(dt_now.year, dt_now.month, 1)
218 
219  # for making it timezone aware if USE_TZ is True
220  # start=timezone.make_aware(start,timezone.get_default_timezone())
221  # start=timezone.now(). #for comparing with datetime django field
222 
223  # next query should return all vms objects related to user (exluding vms failed, saving failed, erased)
224  # with stop time>start or stop_time=none (so excluding stop_time<=start should work)
225  # TODO:TEST
226  vms = self.vm_set.exclude(state__in=[vm_states['failed'], vm_states['saving failed'], vm_states['erased']]).exclude(stop_time__lte=start_month)
227  # or maybe, instead of exclude..., this: .filter(Q(stop_time__isnull=True) | Q(stop_time__gt=start) )
228 
229  for vm in vms:
230  start = start_month
231  if vm.start_time > start:
232  start = vm.start_time
233  t = (vm.stop_time or dt_now) - start
234  if t.total_seconds() < 0:
235  t = datetime.timedelta(0)
236  p += vm.template.points * (t.days * 24 + t.seconds / 3600.0)
237  return int(p + 0.5)
238 
239  # TODO: it works but what it does? and what it returns?
240  # @property
241  ##
242  #
243  # Finds all User's VM's that have been working within current callendar
244  # month, which didn't fail at any stage of existence. Counts points for
245  # those VMs consumed within current month. Failed VMs don't count.
246  #
247  # @returns{dict} \n fields:
248  # @dictkey{points,list(list)} infos about points used in specified
249  # moments of time
250  # @dictkey{limit,int} User's quota for points per month
251  #
252  def points_history(self):
253 
254  p = 0
255  pq = []
256  pts = {}
257  pt = []
258  vmn = 0 # vm number
259  dt_now = datetime.datetime.now()
260  start_month = datetime.datetime(dt_now.year, dt_now.month, 1)
261  start_time = calendar.timegm(start_month.timetuple())
262  now = calendar.timegm(dt_now.timetuple())
263 
264  # next query should return all vms objects related to user (exluding vms failed, saving failed, erased)
265  # with stop time>start TEST or stop_time=none
266  vms = self.vm_set.exclude(state__in=[vm_states['failed'],
267  vm_states['saving failed'],
268  vm_states['erased']]).exclude(stop_time__lte=start_month)
269 
270  for vm in vms:
271  start = start_month
272  if vm.start_time > start:
273  start = vm.start_time
274  stop = vm.stop_time or dt_now
275 
276  pq.append([vmn, calendar.timegm(start.timetuple()), vm.template.points, "%s started" % (vm.name)])
277  pq.append([vmn, calendar.timegm(stop.timetuple()), vm.template.points, "%s stopped" % (vm.name)])
278  vmn += 1
279 
280  pq = sorted(pq, key=lambda d: d[1])
281 
282  pt.append([start_time, p, "beginning of the month"])
283 
284  for w in pq:
285  for v in pts:
286  p += (w[1] - pts[v][0]) / 3600.0 * pts[v][1]
287  pts[v] = [w[1], pts[v][1]]
288  if(w[0] in pts):
289  pts.pop(w[0])
290  else:
291  pts.update({w[0]: [w[1], w[2]]})
292  if not (w[1] in [ts[0] for ts in pt] or w[1] == now):
293  pt.append([w[1], "%.4f" % p, "%s [%.0f]" % (w[3], p)])
294 
295  pt.append([now, "%.4f" % p, "now [%.0f]" % p])
296  pt = sorted(pt, key=lambda d: d[0])
297  return {'points': pt,
298  'limit': self.points}
299 
300  ##
301  #
302  # @todo Test
303  # template_count is a list of template objects?
304  #
305  # Method checks this User's quota for ability to run VMs based on
306  # given Template and it raises CMException, if it's exceeded:
307  #
308  # @parameter{template_count}
309  #
310  # @raises{user_cpu_limit,CMException}
311  # @raises{user_memory_limit,CMException}
312  # @raises{user_storage_limit,CMException}
313  # @raises{user_points_limit,CMException}
314  #
315  def check_quota(self, template_count):
316  cpu_sum = 0
317  mem_sum = 0
318  for template, count in template_count:
319  cpu_sum += template.cpu * count
320  mem_sum += template.memory * count
321  if self.used_cpu + cpu_sum > self.cpu:
322  raise CMException('user_cpu_limit')
323  if self.used_memory + mem_sum > self.memory:
324  raise CMException('user_memory_limit')
325  if self.used_storage > self.storage:
326  raise CMException('user_storage_limit')
327 
328  ##
329  #
330  # Checks if User's storage quota is sufficient for image with given size.
331  #
332  # @parameter{size,int} size to fit within User's storage quota
333  #
334  def check_storage(self, size):
335  # Add the passed size to the actual used storage to check if the sum is over the limit.
336  # It raises exception in that case.
337 
338  if self.used_storage + int(size) > self.storage:
339  raise CMException('user_storage_limit')
340 
341  ##
342  #
343  # Check if used points is over the limit
344  # It raises exception in that case.
345  #
346  def check_points(self):
347  if self.used_points >= self.points:
348  raise CMException('user_points_limit')
349 
350  @staticmethod
351  ##
352  #
353  # Returns the User instance by passed id.
354  #
355  # @parameter{user_id,int} id of the requested User
356  #
357  # @returns{User} instance of requested User
358  #
359  # @raises{user_get,CMException} cannot get user
360  #
361  def get(user_id):
362  try:
363  user = User.objects.get(pk=user_id)
364  except User.DoesNotExist:
365  log.exception(user_id, 'Cannot get user')
366  raise CMException('user_get')
367  return user
368 
369 
370  # Note: superuser method moved to Admin model
371