def test_simple_add(self): # Add one slot together with a timespan allocation = Allocation(raster=15, resource=uuid()) allocation.start = datetime(2011, 1, 1, 15) allocation.end = datetime(2011, 1, 1, 15, 59) allocation.group = uuid() reservation = uuid() slot = ReservedSlot(resource=allocation.resource) slot.start = allocation.start slot.end = allocation.end slot.allocation = allocation slot.reservation = reservation allocation.reserved_slots.append(slot) # Ensure that the same slot cannot be doubly used anotherslot = ReservedSlot(resource=allocation.resource) anotherslot.start = allocation.start anotherslot.end = allocation.end anotherslot.allocation = allocation anotherslot.reservation = reservation Session.add(anotherslot) self.assertRaises(IntegrityError, Session.flush)
def add_something(resource=None): resource = resource or uuid() allocation = Allocation(raster=15, resource=resource, mirror_of=resource) allocation.start = datetime(2011, 1, 1, 15) allocation.end = datetime(2011, 1, 1, 15, 59) allocation.group = uuid() Session.add(allocation)
def _create_allocation(self): allocation = Allocation(raster=15, resource=uuid()) allocation.start = datetime(2011, 1, 1, 15, 00) allocation.end = datetime(2011, 1, 1, 16, 00) allocation.group = str(uuid()) allocation.mirror_of = allocation.resource Session.add(allocation) return allocation
def test_simple_add(self): # Test a simple add allocation = Allocation(raster=15, resource=uuid()) allocation.start = datetime(2011, 1, 1, 15) allocation.end = datetime(2011, 1, 1, 15, 59) allocation.group = str(uuid()) allocation.mirror_of = allocation.resource Session.add(allocation) self.assertEqual(Session.query(Allocation).count(), 1) # Test failing add allocation = Allocation(raster=15) Session.add(allocation) self.assertRaises(IntegrityError, Session.flush)
def test_date_functions(self): allocation = Allocation(raster=60, resource=uuid()) allocation.start = datetime(2011, 1, 1, 12, 30) allocation.end = datetime(2011, 1, 1, 14, 00) self.assertEqual(allocation.start.hour, 12) self.assertEqual(allocation.start.minute, 0) self.assertEqual(allocation.end.hour, 13) self.assertEqual(allocation.end.minute, 59) start = datetime(2011, 1, 1, 11, 00) end = datetime(2011, 1, 1, 12, 05) self.assertTrue(allocation.overlaps(start, end)) self.assertFalse(allocation.contains(start, end)) start = datetime(2011, 1, 1, 13, 00) end = datetime(2011, 1, 1, 15, 00) self.assertTrue(allocation.overlaps(start, end)) self.assertFalse(allocation.contains(start, end))
def test_whole_day(self): allocation = Allocation(raster=15, resource=uuid()) allocation.start = datetime(2013, 1, 1, 0, 0) allocation.end = datetime(2013, 1, 2, 0, 0) self.assertTrue(allocation.whole_day) allocation.start = datetime(2013, 1, 1, 0, 0) allocation.end = datetime(2013, 1, 1, 23, 59, 59, 999999) self.assertTrue(allocation.whole_day) allocation.start = datetime(2013, 1, 1, 0, 0) allocation.end = datetime(2013, 1, 2, 23, 59, 59, 999999) self.assertTrue(allocation.whole_day) allocation.start = datetime(2013, 1, 1, 0, 0) allocation.end = datetime(2013, 1, 2, 0, 0) self.assertTrue(allocation.whole_day) allocation.start = datetime(2013, 1, 1, 15, 0) allocation.end = datetime(2013, 1, 1, 0, 0) self.assertRaises(AssertionError, lambda: allocation.whole_day)
def move_allocation( self, master_id, new_start=None, new_end=None, group=None, new_quota=None, approve_manually=None, reservation_quota_limit=0, whole_day=None): assert master_id assert any([new_start and new_end, group, new_quota]) # Find allocation master = self.allocation_by_id(master_id) mirrors = self.allocation_mirrors_by_master(master) changing = [master] + mirrors ids = [c.id for c in changing] assert(group or master.group) # Simulate the new allocation new_start = new_start or master.start new_end = new_end or master.end if whole_day: new_start, new_end = utils.align_range_to_day(new_start, new_end) new = Allocation(start=new_start, end=new_end, raster=master.raster) # Ensure that the new span does not overlap an existing one existing_allocations = self.allocations_in_range(new.start, new.end) for existing in existing_allocations: if existing.id not in ids: raise OverlappingAllocationError(new.start, new.end, existing) for change in changing: if change.partly_available: # confirmed reservations for reservation in change.reserved_slots: if not new.contains(reservation.start, reservation.end): raise AffectedReservationError(reservation) # pending reservations if change.is_master: # (mirrors return the same values) for pending in change.pending_reservations.with_entities( Reservation.start, Reservation.end): if not new.contains(*pending): raise AffectedPendingReservationError(pending) else: # confirmed reservations if change.start != new.start or change.end != new.end: if len(change.reserved_slots): raise AffectedReservationError( change.reserved_slots[0] ) if change.is_master and \ change.pending_reservations.count(): raise AffectedPendingReservationError( change.pending_reservations[0] ) # the following attributes must be equal over all group members # (this still allows to use move_allocation to remove an allocation # from an existing group by specifiying the new group) for allocation in self.allocations_by_group(group or master.group): if approve_manually is not None: allocation.approve_manually = approve_manually if reservation_quota_limit is not None: allocation.reservation_quota_limit = reservation_quota_limit if new_quota is not None and allocation.is_master: self.change_quota(allocation, new_quota) for change in changing: change.start = new.start change.end = new.end change.group = group or master.group
def allocate(self, dates, raster=15, quota=None, partly_available=False, grouped=False, approve_manually=True, reservation_quota_limit=0, whole_day=False ): """Allocates a spot in the calendar. An allocation defines a timerange which can be reserved. No reservations can exist outside of existing allocations. In fact any reserved slot will link to an allocation. An allocation may be available as a whole (to reserve all or nothing). It may also be partly available which means reservations can be made for parts of the allocation. If an allocation is partly available a raster defines the granularity with which a reservation can be made (e.g. a raster of 15min will ensure that reservations are at least 15 minutes long and start either at :00, :15, :30 or :45) The reason for the raster is mainly to ensure that different reservations trying to reserve overlapping times need the same keys in the reserved_slots table, ensuring integrity at the database level. Allocations may have a quota, which determines how many times an allocation may be reserved. Quotas are enabled using a master-mirrors relationship. The master is the first allocation to be created. The mirrors copies of that allocation. See Scheduler.__doc__ """ dates = utils.pairs(dates) group = new_uuid() quota = quota or 1 # if the allocation is not partly available the raster is set to lowest # possible raster value raster = partly_available and raster or MIN_RASTER_VALUE # the whole day option results in the dates being aligned to # the beginning of the day / end of it -> not timezone aware! if whole_day: for ix, (start, end) in enumerate(dates): dates[ix] = utils.align_range_to_day(start, end) # Ensure that the list of dates contains no overlaps inside for start, end in dates: if utils.count_overlaps(dates, start, end) > 1: raise InvalidAllocationError # Make sure that this span does not overlap another master for start, end in dates: start, end = rasterize_span(start, end, raster) existing = self.allocations_in_range(start, end).first() if existing: raise OverlappingAllocationError(start, end, existing) # Write the master allocations allocations = [] for start, end in dates: allocation = Allocation(raster=raster) allocation.start = start allocation.end = end allocation.resource = self.uuid allocation.quota = quota allocation.mirror_of = self.uuid allocation.partly_available = partly_available allocation.approve_manually = approve_manually allocation.reservation_quota_limit = reservation_quota_limit if grouped: allocation.group = group else: allocation.group = new_uuid() allocations.append(allocation) Session.add_all(allocations) return allocations