Package expedient :: Package clearinghouse :: Package slice :: Module models
[hide private]
[frames] | no frames]

Source Code for Module expedient.clearinghouse.slice.models

  1  ''' 
  2  @author: jnaous 
  3  ''' 
  4  from datetime import datetime, timedelta 
  5  from django.db import models 
  6  from expedient.clearinghouse.project.models import Project 
  7  from expedient.clearinghouse.aggregate.models import Aggregate 
  8  from django.contrib.auth.models import User 
  9  from expedient.common.permissions.models import ObjectPermission, Permittee 
 10  from expedient.clearinghouse.aggregate.utils import get_aggregate_classes 
 11  import logging 
 12  from django.db.models import signals 
 13  from expedient.common.messaging.models import DatedMessage 
 14  import traceback 
 15  from django.core.mail import send_mail 
 16  from django.conf import settings 
 17  from expedient.common.timer.models import Job 
 18  from expedient.common.timer.exceptions import JobAlreadyScheduled 
 19  from expedient.common.utils.modelfields import LimitedDateTimeField 
 20  from expedient.common.middleware import threadlocals 
 21   
 22  logger = logging.getLogger("slice.models") 
23 24 -def _get_slice_max_date():
25 return datetime.now() + timedelta(days=settings.MAX_SLICE_LIFE)
26
27 -class Slice(models.Model):
28 ''' 29 Holds information about reservations across aggregates 30 @ivar name: The name of the Slice 31 @type name: L{str} 32 @ivar description: Short description of the slice 33 @type description: L{str} 34 @ivar project: Project in which this slice belongs 35 @type project: L{models.ForeignKey} to L{Project} 36 @ivar owner: Original creator of the slice 37 @type owner: C{User} 38 @ivar started: Has this slice been reserved with the aggregates yet? 39 @type started: C{bool} 40 @ivar modified: Has this slice been modified since it was last reserved? 41 @type modified: C{bool} 42 @ivar expiration_date: Date and time of when the slice is going to 43 expire in local time. 44 @type expiration_date: L{datetime.datetime} instance 45 @ivar aggregates: Read-only property returning all aggregates that can 46 be used by the project (i.e. for which the project has the 47 "can_use_aggregate" permission). 48 @type aggregates: C{QuerySet} of L{Aggregate}s 49 ''' 50 51 name = models.CharField(max_length=200, unique=True) 52 description = models.TextField() 53 project = models.ForeignKey(Project) 54 owner = models.ForeignKey(User, related_name="owned_slices") 55 started = models.BooleanField(default=False, editable=False) 56 modified = models.BooleanField(default=False, editable=False) 57 expiration_date = LimitedDateTimeField( 58 default=datetime.now, 59 help_text="Enter a date and time. The date should be in the" 60 " following format: 'YYYY-MM-DD'. And for the time: 'HH:MM:SS'." 61 " The expiration date cannot be later than %s days from" 62 " now." % settings.MAX_SLICE_LIFE, 63 max_date=_get_slice_max_date, 64 ) 65
66 - def __unicode__(self):
67 return u"Slice '%s' in project '%s'" % (self.name, self.project.name)
68
69 - def start(self, user):
70 """ 71 Should be an idempotent operation on the aggregates. 72 """ 73 # check the expiration date 74 if self.expiration_date <= datetime.now(): 75 raise Exception("Slice expired. Update slice expiration time.") 76 logger.debug("Called start_slice on %s: %s" % (self, self.name)) 77 aggs = enumerate(self.aggregates.all()) 78 for i, agg in aggs: 79 logger.debug("starting slice on agg %s" % agg.name) 80 try: 81 agg.as_leaf_class().start_slice(self) 82 except Exception, e: 83 logger.error("Error starting slice on agg %s" % agg.name) 84 # try to stop slice on all previously started aggregates 85 for j in xrange(i): 86 try: 87 aggs[j][1].as_leaf_class().stop_slice(self) 88 except Exception, e2: 89 # error stopping slice 90 logger.error(traceback.format_exc()) 91 DatedMessage.objects.post_message_to_user( 92 msg_text="Error stopping slice %s on " 93 "aggregate %s" % (self, aggs[j][1].name), 94 user=user, msg_type=DatedMessage.TYPE_ERROR) 95 # raise the original exception raised starting the slice. 96 raise e 97 98 # all is well 99 self.started = True 100 self.modified = False 101 self.save()
102
103 - def stop(self, user):
104 """ 105 Should be an idempotent operation on the aggregates. 106 """ 107 for agg in self.aggregates.all(): 108 agg.as_leaf_class().stop_slice(self) 109 self.started = False 110 self.save()
111
112 - def _get_aggregates(self):
113 """Get all aggregates that can be used by the slice 114 (i.e. for which the slice has the "can_use_aggregate" permission). 115 """ 116 agg_ids = [] 117 agg_classes = get_aggregate_classes() 118 permittee = Permittee.objects.get_as_permittee(self) 119 for agg_class in agg_classes: 120 agg_ids.extend( 121 ObjectPermission.objects.filter_for_class( 122 agg_class, 123 permission__name="can_use_aggregate", 124 permittees=permittee, 125 ).values_list("object_id", flat=True) 126 ) 127 return Aggregate.objects.filter(pk__in=agg_ids)
128 aggregates=property(_get_aggregates) 129 130 @classmethod 131 @models.permalink
132 - def get_create_url(cls, proj_id):
133 "Returns the URL to create slices" 134 return ("slice_create", (), {"proj_id": proj_id})
135 136 @models.permalink
137 - def get_update_url(self):
138 "Returns the URL to update slice info" 139 return ("slice_update", (), {"slice_id": self.id})
140 141 @models.permalink
142 - def get_detail_url(self):
143 "Returns the URL for the slice detail page" 144 return ("slice_detail", (), {"slice_id": self.id})
145 146 @models.permalink
147 - def get_delete_url(self):
148 "Returns the URL to delete a slice" 149 return ("slice_delete", (), {"slice_id": self.id})
150 151 @models.permalink
152 - def get_start_url(self):
153 "Returns the URL to start the slice" 154 return ("slice_start", (), {"slice_id": self.id})
155 156 @models.permalink
157 - def get_stop_url(self):
158 "Returns the URL to stop the slice" 159 return ("slice_stop", (), {"slice_id": self.id})
160 161 @models.permalink
162 - def get_agg_add_url(self):
163 "Returns the URL to add an aggregate to a slice" 164 return ("slice_add_agg", (), {"slice_id": self.id})
165 166 @models.permalink
167 - def get_agg_update_url(self, aggregate):
168 "Returns URL to update an aggregate's info related to the slice" 169 return ("slice_update_agg", (), { 170 "slice_id": self.id, 171 "agg_id": aggregate.id})
172 173 @models.permalink
174 - def get_agg_remove_url(self, aggregate):
175 "Returns URL to remove aggregate from slice" 176 return ("slice_remove_agg", (), { 177 "slice_id": self.id, 178 "agg_id": aggregate.id})
179 180 @models.permalink
181 - def get_rsc_management_url(self):
182 "Returns the URL at which to select a UI plugin." 183 return ("slice_manage_resources", (), {"slice_id": self.id})
184
185 186 -def stop_slice_before_delete(sender, **kwargs):
187 """Before deleting a slice, make sure it is stopped""" 188 try: 189 kwargs["instance"].stop() 190 except: 191 pass
192 signals.pre_delete.connect(stop_slice_before_delete, Slice)
193 194 # Deal with expired slices ################################################## 195 196 -def stop_expired_slices():
197 """Find expired slices and stop them, sending an email to the owner.""" 198 199 expired_slices = Slice.objects.filter( 200 expiration_date__lte=datetime.now(), started=True) 201 202 for slice in expired_slices: 203 threadlocals.push_frame(user=slice.owner) 204 try: 205 slice.stop(slice.owner) 206 except: 207 logger.error( 208 "Error stopping expired slice" 209 " %s: %s" % (slice, traceback.format_exc())) 210 threadlocals.pop_frame() 211 try: 212 send_mail( 213 "Your slice %s expired." % slice, 214 "Your slice %s has been stopped because it expired on %s." 215 "Before you restart your slice, you will need to update the " 216 "slice's expiration date." % (slice, slice.expiration_date), 217 from_email=settings.DEFAULT_FROM_EMAIL, 218 recipient_list=[slice.owner.email], 219 ) 220 except: 221 logger.error( 222 "Error sending expired slice " 223 "email to user: %s" % traceback.format_exc())
224
225 -def notify_slice_expirations():
226 """Notify owners that their slices will expire soon.""" 227 228 expiration_time = datetime.now() + timedelta( 229 seconds=settings.SLICE_EXPIRATION_NOTIFICATION_TIME) 230 231 almost_expired_slices = Slice.objects.filter( 232 expiration_date__lte=expiration_time, started=True) 233 234 for slice in almost_expired_slices: 235 try: 236 send_mail( 237 "Your slice %s is almost expired." % slice, 238 "Your slice %s is almost expired. If you don't do anything, " 239 "it will expired on %s. " 240 "To renew your slice, you will need to update the " 241 "slice's expiration date." % (slice, slice.expiration_date), 242 from_email=settings.DEFAULT_FROM_EMAIL, 243 recipient_list=[slice.owner.email], 244 ) 245 except: 246 logger.error( 247 "Error sending almost expired slice " 248 "email to user: %s" % traceback.format_exc())
249 250 # schedule jobs 251 try: 252 Job.objects.schedule_post_syncdb(settings.SLICE_EXPIRATION_CHECK_INTERVAL, stop_expired_slices) 253 except JobAlreadyScheduled: 254 pass 255 256 try: 257 Job.objects.schedule_post_syncdb(settings.SLICE_EXPIRATION_NOTIFICATION_TIME, notify_slice_expirations) 258 except JobAlreadyScheduled: 259 pass 260