1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """This module provides the lease data structures:
20
21 * Lease: Represents a lease
22 * LeaseStateMachine: A state machine to keep track of a lease's state
23 * Capacity: Used to represent a quantity of resources
24 * Timestamp: An exact moment in time
25 * Duration: A duration
26 * SoftwareEnvironment, UnmanagedSoftwareEnvironment, DiskImageSoftwareEnvironment:
27 Used to represent a lease's required software environment.
28 * LeaseWorkload: Represents a collection of lease requests submitted
29 in a specific order.
30 * Site: Represents the site with leasable resources.
31 * Nodes: Represents a collection of machines ("nodes"). This is used
32 both when specifying a site and when specifying the machines
33 needed by a leases.
34 """
35
36 from haizea.common.constants import LOGLEVEL_VDEBUG
37 from haizea.common.utils import StateMachine, round_datetime_delta, get_lease_id
38 from haizea.core.scheduler.slottable import ResourceReservation
39
40 from mx.DateTime import DateTime, TimeDelta, Parser
41
42 import logging
43
44 try:
45 import xml.etree.ElementTree as ET
46 except ImportError:
47
48 import elementtree.ElementTree as ET
49
50
51
52
53 -class Lease(object):
54 """A resource lease
55
56 This is one of the main data structures used in Haizea. A lease
57 is "a negotiated and renegotiable agreement between a resource
58 provider and a resource consumer, where the former agrees to make
59 a set of resources available to the latter, based on a set of
60 lease terms presented by the resource consumer". All the gory
61 details on what this means can be found on the Haizea website
62 and on the Haizea publications.
63
64 See the __init__ method for a description of the information that
65 is contained in a lease.
66
67 """
68
69
70 STATE_NEW = 0
71 STATE_PENDING = 1
72 STATE_REJECTED = 2
73 STATE_SCHEDULED = 3
74 STATE_QUEUED = 4
75 STATE_CANCELLED = 5
76 STATE_PREPARING = 6
77 STATE_READY = 7
78 STATE_ACTIVE = 8
79 STATE_SUSPENDING = 9
80 STATE_SUSPENDED_PENDING = 10
81 STATE_SUSPENDED_QUEUED = 11
82 STATE_SUSPENDED_SCHEDULED = 12
83 STATE_MIGRATING = 13
84 STATE_RESUMING = 14
85 STATE_RESUMED_READY = 15
86 STATE_DONE = 16
87 STATE_FAIL = 17
88
89
90 state_str = {STATE_NEW : "New",
91 STATE_PENDING : "Pending",
92 STATE_REJECTED : "Rejected",
93 STATE_SCHEDULED : "Scheduled",
94 STATE_QUEUED : "Queued",
95 STATE_CANCELLED : "Cancelled",
96 STATE_PREPARING : "Preparing",
97 STATE_READY : "Ready",
98 STATE_ACTIVE : "Active",
99 STATE_SUSPENDING : "Suspending",
100 STATE_SUSPENDED_PENDING : "Suspended-Pending",
101 STATE_SUSPENDED_QUEUED : "Suspended-Queued",
102 STATE_SUSPENDED_SCHEDULED : "Suspended-Scheduled",
103 STATE_MIGRATING : "Migrating",
104 STATE_RESUMING : "Resuming",
105 STATE_RESUMED_READY: "Resumed-Ready",
106 STATE_DONE : "Done",
107 STATE_FAIL : "Fail"}
108
109
110 BEST_EFFORT = 1
111 ADVANCE_RESERVATION = 2
112 IMMEDIATE = 3
113 UNKNOWN = -1
114
115
116 type_str = {BEST_EFFORT: "Best-effort",
117 ADVANCE_RESERVATION: "AR",
118 IMMEDIATE: "Immediate",
119 UNKNOWN: "Unknown"}
120
121 - def __init__(self, lease_id, submit_time, requested_resources, start, duration,
122 deadline, preemptible, software, state):
123 """Constructs a lease.
124
125 The arguments are the fundamental attributes of a lease.
126 The attributes that are not specified by the arguments are
127 the lease ID (which is an autoincremented integer), the
128 lease state (a lease always starts out in state "NEW").
129 A lease also has several bookkeeping attributes that are
130 only meant to be consumed by other Haizea objects.
131
132 Arguments:
133 id -- Unique identifier for the lease. If None, one
134 will be provided.
135 submit_time -- The time at which the lease was submitted
136 requested_resources -- A dictionary (int -> Capacity) mapping
137 each requested node to a capacity (i.e., the amount of
138 resources requested for that node)
139 start -- A Timestamp object containing the requested time.
140 duration -- A Duration object containing the requested duration.
141 deadline -- A Timestamp object containing the deadline by which
142 this lease must be completed.
143 preemptible -- A boolean indicating whether this lease can be
144 preempted or not.
145 software -- A SoftwareEnvironment object specifying the
146 software environment required by the lease.
147 """
148
149 self.id = lease_id
150
151
152 self.submit_time = submit_time
153 self.requested_resources = requested_resources
154 self.start = start
155 self.duration = duration
156 self.deadline = deadline
157 self.preemptible = preemptible
158 self.software = software
159
160
161
162
163 if state == None:
164 state = Lease.STATE_NEW
165 self.state = LeaseStateMachine(initial_state = state)
166
167
168 self.end = None
169
170
171 self.numnodes = len(requested_resources)
172
173
174
175
176
177
178
179
180 self.preparation_rrs = []
181
182
183 self.vm_rrs = []
184
185
186 self.enactment_info = None
187 self.vnode_enactment_info = dict([(n, None) for n in self.requested_resources.keys()])
188
189
190 @classmethod
191 - def create_new(cls, submit_time, requested_resources, start, duration,
192 deadline, preemptible, software):
193 lease_id = get_lease_id()
194 state = Lease.STATE_NEW
195 return cls(lease_id, submit_time, requested_resources, start, duration,
196 deadline, preemptible, software, state)
197
198 @classmethod
204
205 @classmethod
207 """Constructs a lease from an XML file.
208
209 See the Haizea documentation for details on the
210 lease XML format.
211
212 Argument:
213 xml_file -- XML file containing the lease in XML format.
214 """
215 return cls.from_xml_element(ET.parse(xml_file).getroot())
216
217 @classmethod
219 """Constructs a lease from an XML string.
220
221 See the Haizea documentation for details on the
222 lease XML format.
223
224 Argument:
225 xml_str -- String containing the lease in XML format.
226 """
227 return cls.from_xml_element(ET.fromstring(xml_str))
228
229 @classmethod
231 """Constructs a lease from an ElementTree element.
232
233 See the Haizea documentation for details on the
234 lease XML format.
235
236 Argument:
237 element -- Element object containing a "<lease>" element.
238 """
239
240 lease_id = element.get("id")
241
242 if lease_id == None:
243 lease_id = None
244 else:
245 lease_id = int(lease_id)
246
247 state = element.get("state")
248 if state == None:
249 state = None
250 else:
251 state = int(state)
252
253
254 submit_time = element.get("submit-time")
255 if submit_time == None:
256 submit_time = None
257 else:
258 submit_time = Parser.DateTimeFromString(submit_time)
259
260 nodes = Nodes.from_xml_element(element.find("nodes"))
261
262 requested_resources = nodes.get_all_nodes()
263
264 start = element.find("start")
265 if len(start.getchildren()) == 0:
266 start = Timestamp(Timestamp.UNSPECIFIED)
267 else:
268 child = start[0]
269 if child.tag == "now":
270 start = Timestamp(Timestamp.NOW)
271 elif child.tag == "exact":
272 start = Timestamp(Parser.DateTimeFromString(child.get("time")))
273
274 duration = Duration(Parser.DateTimeDeltaFromString(element.find("duration").get("time")))
275
276 deadline = None
277
278 preemptible = element.get("preemptible").capitalize()
279 if preemptible == "True":
280 preemptible = True
281 elif preemptible == "False":
282 preemptible = False
283
284 software = element.find("software")
285
286 if software.find("none") != None:
287 software = UnmanagedSoftwareEnvironment()
288 elif software.find("disk-image") != None:
289 disk_image = software.find("disk-image")
290 image_id = disk_image.get("id")
291 image_size = int(disk_image.get("size"))
292 software = DiskImageSoftwareEnvironment(image_id, image_size)
293
294 return Lease(lease_id, submit_time, requested_resources, start, duration,
295 deadline, preemptible, software, state)
296
297
299 """Returns an ElementTree XML representation of the lease
300
301 See the Haizea documentation for details on the
302 lease XML format.
303
304 """
305 lease = ET.Element("lease")
306 if self.id != None:
307 lease.set("id", str(self.id))
308 lease.set("state", str(self.get_state()))
309 lease.set("preemptible", str(self.preemptible))
310 if self.submit_time != None:
311 lease.set("submit-time", str(self.submit_time))
312
313 capacities = {}
314 for capacity in self.requested_resources.values():
315 key = capacity
316 for c in capacities:
317 if capacity == c:
318 key = c
319 break
320 numnodes = capacities.setdefault(key, 0)
321 capacities[key] += 1
322
323 nodes = Nodes([(numnodes,c) for c,numnodes in capacities.items()])
324 lease.append(nodes.to_xml())
325
326 start = ET.SubElement(lease, "start")
327 if self.start.requested == Timestamp.UNSPECIFIED:
328 pass
329 elif self.start.requested == Timestamp.NOW:
330 ET.SubElement(start, "now")
331 else:
332 exact = ET.SubElement(start, "exact")
333 exact.set("time", str(self.start.requested))
334
335 duration = ET.SubElement(lease, "duration")
336 duration.set("time", str(self.duration.requested))
337
338 software = ET.SubElement(lease, "software")
339 if isinstance(self.software, UnmanagedSoftwareEnvironment):
340 ET.SubElement(software, "none")
341 elif isinstance(self.software, DiskImageSoftwareEnvironment):
342 imagetransfer = ET.SubElement(software, "disk-image")
343 imagetransfer.set("id", self.software.image_id)
344 imagetransfer.set("size", str(self.software.image_size))
345
346 return lease
347
349 """Returns a string XML representation of the lease
350
351 See the Haizea documentation for details on the
352 lease XML format.
353
354 """
355 return ET.tostring(self.to_xml())
356
371
373 """Returns the lease's state.
374
375 """
376 return self.state.get_state()
377
379 """Changes the lease's state.
380
381 The state machine will throw an exception if the
382 requested transition is illegal.
383
384 Argument:
385 state -- The new state
386 """
387 self.state.change_state(state)
388
390 """Prints the lease's attributes to the log.
391
392 Argument:
393 loglevel -- The loglevel at which to print the information
394 """
395 logger = logging.getLogger("LEASES")
396 logger.log(loglevel, "__________________________________________________")
397 logger.log(loglevel, "Lease ID : %i" % self.id)
398 logger.log(loglevel, "Type : %s" % Lease.type_str[self.get_type()])
399 logger.log(loglevel, "Submission time: %s" % self.submit_time)
400 logger.log(loglevel, "Start : %s" % self.start)
401 logger.log(loglevel, "Duration : %s" % self.duration)
402 logger.log(loglevel, "State : %s" % Lease.state_str[self.get_state()])
403 logger.log(loglevel, "Resource req : %s" % self.requested_resources)
404 logger.log(loglevel, "Software : %s" % self.software)
405 self.print_rrs(loglevel)
406 logger.log(loglevel, "--------------------------------------------------")
407
409 """Prints the lease's resource reservations to the log.
410
411 Argument:
412 loglevel -- The loglevel at which to print the information
413 """
414 logger = logging.getLogger("LEASES")
415 if len(self.preparation_rrs) > 0:
416 logger.log(loglevel, "DEPLOYMENT RESOURCE RESERVATIONS")
417 logger.log(loglevel, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
418 for r in self.preparation_rrs:
419 r.print_contents(loglevel)
420 logger.log(loglevel, "##")
421 logger.log(loglevel, "VM RESOURCE RESERVATIONS")
422 logger.log(loglevel, "~~~~~~~~~~~~~~~~~~~~~~~~")
423 for r in self.vm_rrs:
424 r.print_contents(loglevel)
425 logger.log(loglevel, "##")
426
428 """Returns the active VM resource reservations at a given time
429
430 Argument:
431 time -- Time to look for active reservations
432 """
433 return [r for r in self.vm_rrs if r.start <= time and time <= r.end and r.state == ResourceReservation.STATE_ACTIVE]
434
440
442 """Returns the last VM reservation for this lease.
443
444 """
445 return self.vm_rrs[-1]
446
448 """Returns the time at which the last VM reservation
449 for this lease ends.
450
451 Note that this is not necessarily the time at which the lease
452 will end, just the time at which the last currently scheduled
453 VM will end.
454
455 """
456 vmrr = self.get_last_vmrr()
457 return vmrr.end
458
460 """Adds a VM resource reservation to the lease.
461
462 Argument:
463 vmrr -- The VM RR to add.
464 """
465 self.vm_rrs.append(vmrr)
466
468 """Removes a VM resource reservation from the lease.
469
470 Argument:
471 vmrr -- The VM RR to remove.
472 """
473 if not vmrr in self.vm_rrs:
474 raise Exception, "Tried to remove an VM RR not contained in this lease"
475 else:
476 self.vm_rrs.remove(vmrr)
477
479 """Adds a preparation resource reservation to the lease.
480
481 Argument:
482 preparation_rr -- The preparation RR to add.
483 """
484 self.preparation_rrs.append(preparation_rr)
485
487 """Removes a preparation resource reservation from the lease.
488
489 Argument:
490 preparation_rr -- The preparation RR to remove.
491 """
492 if not preparation_rr in self.preparation_rrs:
493 raise Exception, "Tried to remove a preparation RR not contained in this lease"
494 else:
495 self.preparation_rrs.remove(preparation_rr)
496
498 """Removes all resource reservations for this lease
499 (both preparation and VM)
500
501 """
502 self.preparation_rrs = []
503 self.vm_rrs = []
504
506 """Gets the waiting time for this lease.
507
508 The waiting time is the difference between the submission
509 time and the time at which the lease start. This method
510 mostly makes sense for best-effort leases, where the
511 starting time is determined by Haizea.
512
513 """
514 return self.start.actual - self.submit_time
515
517 """Determines the bounded slowdown for this lease.
518
519 Slowdown is a normalized measure of how much time a
520 request takes to make it through a queue (thus, like
521 get_waiting_time, the slowdown makes sense mostly for
522 best-effort leases). Slowdown is equal to the time the
523 lease took to run on a loaded system (i.e., a system where
524 it had to compete with other leases for resources)
525 divided by the time it would take if it just had the
526 system all to itself (i.e., starts running immediately
527 without having to wait in a queue and without the
528 possibility of being preempted).
529
530 "Bounded" slowdown is one where leases with very short
531 durations are rounded up to a bound, to prevent the
532 metric to be affected by reasonable but disproportionate
533 waiting times (e.g., a 5-second lease with a 15 second
534 waiting time -an arguably reasonable waiting time- has a
535 slowdown of 4, the same as 10 hour lease having to wait
536 30 hours for resources).
537
538 Argument:
539 bound -- The bound, specified in seconds.
540 All leases with a duration less than this
541 parameter are rounded up to the bound.
542 """
543 time_on_dedicated = self.duration.original
544 time_on_loaded = self.end - self.submit_time
545 bound = TimeDelta(seconds=bound)
546 if time_on_dedicated < bound:
547 time_on_dedicated = bound
548 return time_on_loaded / time_on_dedicated
549
551 """Adds a boot overhead to the lease.
552
553 Increments the requested duration to account for the fact
554 that some time will be spent booting up the resources.
555
556 Argument:
557 t -- Time to add
558 """
559 self.duration.incr(t)
560
562 """Adds a runtime overhead to the lease.
563
564 This method is mostly meant for simulations. Since VMs
565 run slower than physical hardware, this increments the
566 duration of a lease by a percent to observe the effect
567 of having all the leases run slower on account of
568 running on a VM.
569
570 Note: the whole "runtime overhead" problem is becoming
571 increasingly moot as people have lost their aversion to
572 VMs thanks to the cloud computing craze. Anecdotal evidence
573 suggests that most people don't care that VMs will run
574 X % slower (compared to a physical machine) because they
575 know full well that what they're getting is a virtual
576 machine (the same way a user of an HPC system would know
577 that he/she's getting processors with speed X as opposed to
578 those on some other site, with speed X*0.10)
579
580 Argument:
581 percent -- Runtime overhead (in percent of requested
582 duration) to add to the lease.
583 """
584 self.duration.incr_by_percent(percent)
585
588 """A lease state machine
589
590 A child of StateMachine, this class simply specifies the valid
591 states and transitions for a lease (the actual state machine code
592 is in StateMachine).
593
594 See the Haizea documentation for a description of states and
595 valid transitions.
596
597 """
598 transitions = {Lease.STATE_NEW: [(Lease.STATE_PENDING, "")],
599
600 Lease.STATE_PENDING: [(Lease.STATE_SCHEDULED, ""),
601 (Lease.STATE_QUEUED, ""),
602 (Lease.STATE_CANCELLED, ""),
603 (Lease.STATE_REJECTED, "")],
604
605 Lease.STATE_SCHEDULED: [(Lease.STATE_PREPARING, ""),
606 (Lease.STATE_QUEUED, ""),
607 (Lease.STATE_PENDING, ""),
608 (Lease.STATE_READY, ""),
609 (Lease.STATE_CANCELLED, ""),
610 (Lease.STATE_FAIL, "")],
611
612 Lease.STATE_QUEUED: [(Lease.STATE_SCHEDULED, ""),
613 (Lease.STATE_CANCELLED, "")],
614
615 Lease.STATE_PREPARING: [(Lease.STATE_READY, ""),
616 (Lease.STATE_PENDING, ""),
617 (Lease.STATE_CANCELLED, ""),
618 (Lease.STATE_FAIL, "")],
619
620 Lease.STATE_READY: [(Lease.STATE_ACTIVE, ""),
621 (Lease.STATE_QUEUED, ""),
622 (Lease.STATE_PENDING, ""),
623 (Lease.STATE_CANCELLED, ""),
624 (Lease.STATE_FAIL, "")],
625
626 Lease.STATE_ACTIVE: [(Lease.STATE_SUSPENDING, ""),
627 (Lease.STATE_QUEUED, ""),
628 (Lease.STATE_DONE, ""),
629 (Lease.STATE_CANCELLED, ""),
630 (Lease.STATE_FAIL, "")],
631
632 Lease.STATE_SUSPENDING: [(Lease.STATE_SUSPENDED_PENDING, ""),
633 (Lease.STATE_CANCELLED, ""),
634 (Lease.STATE_FAIL, "")],
635
636 Lease.STATE_SUSPENDED_PENDING: [(Lease.STATE_SUSPENDED_QUEUED, ""),
637 (Lease.STATE_SUSPENDED_SCHEDULED, ""),
638 (Lease.STATE_CANCELLED, ""),
639 (Lease.STATE_FAIL, "")],
640
641 Lease.STATE_SUSPENDED_QUEUED: [(Lease.STATE_SUSPENDED_SCHEDULED, ""),
642 (Lease.STATE_CANCELLED, ""),
643 (Lease.STATE_FAIL, "")],
644
645 Lease.STATE_SUSPENDED_SCHEDULED: [(Lease.STATE_SUSPENDED_QUEUED, ""),
646 (Lease.STATE_SUSPENDED_PENDING, ""),
647 (Lease.STATE_MIGRATING, ""),
648 (Lease.STATE_RESUMING, ""),
649 (Lease.STATE_CANCELLED, ""),
650 (Lease.STATE_FAIL, "")],
651
652 Lease.STATE_MIGRATING: [(Lease.STATE_SUSPENDED_SCHEDULED, ""),
653 (Lease.STATE_CANCELLED, ""),
654 (Lease.STATE_FAIL, "")],
655
656 Lease.STATE_RESUMING: [(Lease.STATE_RESUMED_READY, ""),
657 (Lease.STATE_CANCELLED, ""),
658 (Lease.STATE_FAIL, "")],
659
660 Lease.STATE_RESUMED_READY: [(Lease.STATE_ACTIVE, ""),
661 (Lease.STATE_CANCELLED, ""),
662 (Lease.STATE_FAIL, "")],
663
664
665 Lease.STATE_DONE: [],
666 Lease.STATE_CANCELLED: [],
667 Lease.STATE_FAIL: [],
668 Lease.STATE_REJECTED: [],
669 }
670
673
676 """A quantity of resources
677
678 This class is used to represent a quantity of resources, such
679 as those required by a lease. For example, if a lease needs a
680 single node with 1 CPU and 1024 MB of memory, a single Capacity
681 object would be used containing that information.
682
683 Resources in a Capacity object can be multi-instance, meaning
684 that several instances of the same type of resources can be
685 specified. For example, if a node requires 2 CPUs, then this is
686 represented as two instances of the same type of resource. Most
687 resources, however, will be "single instance" (e.g., a physical
688 node only has "one" memory).
689
690 Note: This class is similar, but distinct from, the ResourceTuple
691 class in the slottable module. The ResourceTuple class can contain
692 the same information, but uses a different internal representation
693 (which is optimized for long-running simulations) and is tightly
694 coupled to the SlotTable class. The Capacity and ResourceTuple
695 classes are kept separate so that the slottable module remains
696 independent from the rest of Haizea (in case we want to switch
697 to a different slottable implementation in the future).
698
699 """
701 """Constructs an empty Capacity object.
702
703 All resource types are initially set to be single-instance,
704 with a quantity of 0 for each resource.
705
706 Argument:
707 types -- List of resource types. e.g., ["CPU", "Memory"]
708 """
709 self.ninstances = dict([(res_type, 1) for res_type in types])
710 self.quantity = dict([(res_type, [0]) for res_type in types])
711
713 """Gets the number of instances for a resource type
714
715 Argument:
716 type -- The type of resource (using the same name passed
717 when constructing the Capacity object)
718 """
719 return self.ninstances[res_type]
720
722 """Gets the quantity of a single-instance resource
723
724 Argument:
725 type -- The type of resource (using the same name passed
726 when constructing the Capacity object)
727 """
728 return self.get_quantity_instance(res_type, 1)
729
731 """Gets the quantity of a specific instance of a
732 multi-instance resource.
733
734 Argument:
735 type -- The type of resource (using the same name passed
736 when constructing the Capacity object)
737 instance -- The instance. Note that instances are numbered
738 from 1.
739 """
740 return self.quantity[res_type][instance-1]
741
743 """Sets the quantity of a single-instance resource
744
745 Argument:
746 type -- The type of resource (using the same name passed
747 when constructing the Capacity object)
748 amount -- The amount to set the resource to.
749 """
750 self.set_quantity_instance(res_type, 1, amount)
751
753 """Sets the quantity of a specific instance of a
754 multi-instance resource.
755
756 Argument:
757 type -- The type of resource (using the same name passed
758 when constructing the Capacity object)
759 instance -- The instance. Note that instances are numbered
760 from 1.
761 amount -- The amount to set the instance of the resource to.
762 """
763 self.quantity[res_type][instance-1] = amount
764
766 """Changes the number of instances of a resource type.
767
768 Note that changing the number of instances will initialize
769 all the instances' amounts to zero. This method should
770 only be called right after constructing a Capacity object.
771
772 Argument:
773 type -- The type of resource (using the same name passed
774 when constructing the Capacity object)
775 ninstance -- The number of instances
776 """
777 self.ninstances[res_type] = ninstances
778 self.quantity[res_type] = [0] * ninstances
779
781 """Returns the types of resources in this capacity.
782
783 """
784 return self.quantity.keys()
785
787 """Tests if two capacities are the same
788
789 """
790 for res_type in self.quantity:
791 if not other.quantity.has_key(res_type):
792 return False
793 if self.ninstances[res_type] != other.ninstances[res_type]:
794 return False
795 if self.quantity[res_type] != other.quantity[res_type]:
796 return False
797 return True
798
800 """Tests if two capacities are not the same
801
802 """
803 return not self == other
804
806 """Returns a string representation of the Capacity"""
807 return " | ".join("%s: %i" % (res_type,q[0]) for res_type, q in self.quantity.items())
808
811 """An exact point in time.
812
813 This class is just a wrapper around three DateTimes. When
814 dealing with timestamps in Haizea (such as the requested
815 starting time for a lease), we want to keep track not just
816 of the requested timestamp, but also the scheduled timestamp
817 (which could differ from the requested one) and the
818 actual timestamp (which could differ from the scheduled one).
819 """
820
821 UNSPECIFIED = "Unspecified"
822 NOW = "Now"
823
825 """Constructor
826
827 Argument:
828 requested -- The requested timestamp
829 """
830 self.requested = requested
831 self.scheduled = None
832 self.actual = None
833
835 """Returns a string representation of the Duration"""
836 return "REQ: %s | SCH: %s | ACT: %s" % (self.requested, self.scheduled, self.actual)
837
839 """A duration
840
841 This class is just a wrapper around five DateTimes. When
842 dealing with durations in Haizea (such as the requested
843 duration for a lease), we want to keep track of the following:
844
845 - The requested duration
846 - The accumulated duration (when the entire duration of
847 the lease can't be scheduled without interrumption, this
848 keeps track of how much duration has been fulfilled so far)
849 - The actual duration (which might not be the same as the
850 requested duration)
851
852 For the purposes of simulation, we also want to keep track
853 of the "original" duration (since the requested duration
854 can be modified to simulate certain overheads) and the
855 "known" duration (when simulating lease workloads, this is
856 the actual duration of the lease, which is known a posteriori).
857 """
858
859 - def __init__(self, requested, known=None):
860 """Constructor
861
862 Argument:
863 requested -- The requested duration
864 known -- The known duration (ONLY in simulation)
865 """
866 self.original = requested
867 self.requested = requested
868 self.accumulated = TimeDelta()
869 self.actual = None
870
871 self.known = known
872
874 """Increments the requested duration by an amount.
875
876 Argument:
877 t -- The time to add to the requested duration.
878 """
879 self.requested += t
880 if self.known != None:
881 self.known += t
882
884 """Increments the requested duration by a percentage.
885
886 Argument:
887 pct -- The percentage of the requested duration to add.
888 """
889 factor = 1 + float(pct)/100
890 self.requested = round_datetime_delta(self.requested * factor)
891 if self.known != None:
892 self.requested = round_datetime_delta(self.known * factor)
893
895 """Increments the accumulated duration by an amount.
896
897 Argument:
898 t -- The time to add to the accumulated duration.
899 """
900 self.accumulated += t
901
903 """Returns the amount of time required to fulfil the entire
904 requested duration of the lease.
905
906 """
907 return self.requested - self.accumulated
908
910 """Returns the amount of time required to fulfil the entire
911 known duration of the lease.
912
913 ONLY for simulations.
914 """
915 return self.known - self.accumulated
916
918 """Returns a string representation of the Duration"""
919 return "REQ: %s | ACC: %s | ACT: %s | KNW: %s" % (self.requested, self.accumulated, self.actual, self.known)
920
922 """The base class for a lease's software environment"""
923
925 """Constructor.
926
927 Does nothing."""
928 pass
929
931 """Represents an "unmanaged" software environment.
932
933 When a lease has an unmanaged software environment,
934 Haizea does not need to perform any actions to prepare
935 a lease's software environment (it assumes that this
936 task is carried out by an external entity, and that
937 software environments can be assumed to be ready
938 when a lease has to start; e.g., if VM disk images are
939 predeployed on all physical nodes)."""
940
946
948 """Reprents a software environment encapsulated in a disk image.
949
950 When a lease's software environment is contained in a disk image,
951 this disk image must be deployed to the physical nodes the lease
952 is mapped to before the lease can start. This means that the
953 preparation for this lease must be handled by a preparation
954 scheduler (see documentation in lease_scheduler) capable of
955 handling a DiskImageSoftwareEnvironment.
956 """
957 - def __init__(self, image_id, image_size):
958 """Constructor.
959
960 Arguments:
961 image_id -- A unique identifier for the disk image required
962 by the lease.
963 image_size -- The size, in MB, of the disk image. """
964 self.image_id = image_id
965 self.image_size = image_size
966 SoftwareEnvironment.__init__(self)
967
971 """Reprents a sequence of lease requests.
972
973 A lease workload is a sequence of lease requests with a specific
974 arrival time for each lease. This class is currently only used
975 to load LWF (Lease Workload File) files. See the Haizea documentation
976 for details on the LWF format.
977 """
979 """Constructor.
980
981 Arguments:
982 leases -- An ordered list (by arrival time) of leases in the workload
983 """
984 self.leases = leases
985
986
988 """Returns the leases in the workload.
989
990 """
991 return self.leases
992
993 @classmethod
995 """Constructs a lease workload from an XML file.
996
997 See the Haizea documentation for details on the
998 lease workload XML format.
999
1000 Argument:
1001 xml_file -- XML file containing the lease in XML format.
1002 inittime -- The starting time of the lease workload. All relative
1003 times in the XML file will be converted to absolute times by
1004 adding them to inittime. If inittime is not specified, it will
1005 arbitrarily be 0000/01/01 00:00:00.
1006 """
1007 return cls.__from_xml_element(ET.parse(xml_file).getroot(), inittime)
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050 @classmethod
1052 """Constructs a lease from an ElementTree element.
1053
1054 See the Haizea documentation for details on the
1055 lease XML format.
1056
1057 Argument:
1058 element -- Element object containing a "<lease-workload>" element.
1059 inittime -- The starting time of the lease workload. All relative
1060 times in the XML file will be converted to absolute times by
1061 adding them to inittime.
1062 """
1063 reqs = element.findall("lease-requests/lease-request")
1064 leases = []
1065 for r in reqs:
1066 lease = r.find("lease")
1067
1068 submittime = inittime + Parser.DateTimeDeltaFromString(r.get("arrival"))
1069 lease.set("submit-time", str(submittime))
1070
1071
1072 exact = lease.find("start/exact")
1073 if exact != None:
1074 start = inittime + Parser.DateTimeDeltaFromString(exact.get("time"))
1075 exact.set("time", str(start))
1076
1077 lease = Lease.create_new_from_xml_element(lease)
1078
1079 realduration = r.find("realduration")
1080 if realduration != None:
1081 lease.duration.known = Parser.DateTimeDeltaFromString(realduration.get("time"))
1082
1083 leases.append(lease)
1084
1085 return cls(leases)
1086
1087 -class Site(object):
1088 """Represents a site containing machines ("nodes").
1089
1090 This class is used to load site descriptions in XML format or
1091 using a "resources string". Site descriptions can appear in two places:
1092 in a LWF file (where the site required for the lease workload is
1093 embedded in the LWF file) or in the Haizea configuration file. In both
1094 cases, the site description is only used in simulation (in OpenNebula mode,
1095 the available nodes and resources are obtained by querying OpenNebula).
1096
1097 Note that this class is distinct from the ResourcePool class, even though
1098 both are used to represent "collections of nodes". The Site class is used
1099 purely as a convenient way to load site information from an XML file
1100 and to manipulate that information elsewhere in Haizea, while the
1101 ResourcePool class is responsible for sending enactment commands
1102 to nodes, monitoring nodes, etc.
1103 """
1104 - def __init__(self, nodes, resource_types, attr_types):
1105 """Constructor.
1106
1107 Arguments:
1108 nodes -- A Nodes object
1109 resource_types -- A list of valid resource types in this site.
1110 attr_types -- A list of valid attribute types in this site
1111 """
1112 self.nodes = nodes
1113 self.resource_types = resource_types
1114 self.attr_types = attr_types
1115
1116 @classmethod
1118 """Constructs a site from an XML file.
1119
1120 See the Haizea documentation for details on the
1121 site XML format.
1122
1123 Argument:
1124 xml_file -- XML file containing the site in XML format.
1125 """
1126 return cls.__from_xml_element(ET.parse(xml_file).getroot())
1127
1128 @classmethod
1130 """Constructs a site from an LWF file.
1131
1132 LWF files can have site information embedded in them. This method
1133 loads this site information from an LWF file. See the Haizea
1134 documentation for details on the LWF format.
1135
1136 Argument:
1137 lwf_file -- LWF file.
1138 """
1139 return cls.__from_xml_element(ET.parse(lwf_file).getroot().find("site"))
1140
1141 @classmethod
1143 """Constructs a site from an ElementTree element.
1144
1145 See the Haizea documentation for details on the
1146 site XML format.
1147
1148 Argument:
1149 element -- Element object containing a "<site>" element.
1150 """
1151 resource_types = element.find("resource-types")
1152 resource_types = resource_types.get("names").split()
1153
1154
1155 attrs = []
1156
1157 nodes = Nodes.from_xml_element(element.find("nodes"))
1158
1159
1160 for node_set in nodes.node_sets:
1161 capacity = node_set[1]
1162 for resource_type in capacity.get_resource_types():
1163 if resource_type not in resource_types:
1164
1165 raise Exception
1166
1167 return cls(nodes, resource_types, attrs)
1168
1169 @classmethod
1171 """Constructs a site from a "resources string"
1172
1173 A "resources string" is a shorthand way of specifying a site
1174 with homogeneous resources and no attributes. The format is:
1175
1176 <numnodes> <resource_type>:<resource_quantity>[,<resource_type>:<resource_quantity>]*
1177
1178 For example: 4 CPU:100,Memory:1024
1179
1180 Argument:
1181 resource_str -- resources string
1182 """
1183
1184 resource_str = resource_str.split()
1185 numnodes = int(resource_str[0])
1186 resources = resource_str[1:]
1187 res = {}
1188
1189 for r in resources:
1190 res_type, amount = r.split(":")
1191 res[res_type] = int(amount)
1192
1193 capacity = Capacity(res.keys())
1194 for (res_type, amount) in res.items():
1195 capacity.set_quantity(res_type, amount)
1196
1197 nodes = Nodes([(numnodes,capacity)])
1198
1199 return cls(nodes, res.keys(), [])
1200
1202 """Adds a new resource to all nodes in the site.
1203
1204 Argument:
1205 name -- Name of the resource type
1206 amounts -- A list with the amounts of the resource to add to each
1207 node. If the resource is single-instance, then this will just
1208 be a list with a single element. If multi-instance, each element
1209 of the list represent the amount of an instance of the resource.
1210 """
1211 self.resource_types.append(name)
1212 self.nodes.add_resource(name, amounts)
1213
1215 """Returns the resource types in this site.
1216
1217 This method returns a list, each item being a pair with
1218 1. the name of the resource type and 2. the maximum number of
1219 instances for that resource type across all nodes.
1220
1221 """
1222 max_ninstances = dict((rt, 1) for rt in self.resource_types)
1223 for node_set in self.nodes.node_sets:
1224 capacity = node_set[1]
1225 for resource_type in capacity.get_resource_types():
1226 if capacity.ninstances[resource_type] > max_ninstances[resource_type]:
1227 max_ninstances[resource_type] = capacity.ninstances[resource_type]
1228
1229 max_ninstances = [(rt,max_ninstances[rt]) for rt in self.resource_types]
1230
1231 return max_ninstances
1232
1233
1234
1235 -class Nodes(object):
1236 """Represents a collection of machines ("nodes")
1237
1238 This class is used to load descriptions of nodes from an XML
1239 file. These nodes can appear in two places: in a site description
1240 (which, in turn, is loaded by the Site class) or in a lease's
1241 resource requirements (describing what nodes, with what resources,
1242 are required by the lease).
1243
1244 Nodes are stored as one or more "node sets". Each node set has nodes
1245 with the exact same resources. So, for example, a lease requiring 100
1246 nodes (all identical, except 50 have 1024MB of memory and the other 50
1247 have 512MB of memory) doesn't need to enumerate all 100 nodes. Instead,
1248 it just has to describe the two "node sets" (indicating that there are
1249 50 nodes of one type and 50 of the other). See the Haizea documentation
1250 for more details on the XML format.
1251
1252 Like the Site class, this class is distinct from the ResourcePool class, even
1253 though they both represent a "collection of nodes". See the
1254 Site class documentation for more details.
1255 """
1257 """Constructor.
1258
1259 Arguments:
1260 node_sets -- A list of (n,c) pairs (where n is the number of nodes
1261 in the set and c is a Capacity object; all nodes in the set have
1262 capacity c).
1263 """
1264 self.node_sets = node_sets
1265
1266 @classmethod
1268 """Constructs a node collection from an ElementTree element.
1269
1270 See the Haizea documentation for details on the
1271 <nodes> XML format.
1272
1273 Argument:
1274 element -- Element object containing a "<nodes>" element.
1275 """
1276 nodesets = []
1277 nodesets_elems = nodes_element.findall("node-set")
1278 for nodeset_elem in nodesets_elems:
1279 r = Capacity([])
1280 resources = nodeset_elem.findall("res")
1281 for i, res in enumerate(resources):
1282 res_type = res.get("type")
1283 if len(res.getchildren()) == 0:
1284 amount = int(res.get("amount"))
1285 r.set_ninstances(res_type, 1)
1286 r.set_quantity(res_type, amount)
1287 else:
1288 instances = res.findall("instance")
1289 r.set_ninstances(type, len(instances))
1290 for i, instance in enumerate(instances):
1291 amount = int(instance.get("amount"))
1292 r.set_quantity_instance(type, i+1, amount)
1293
1294 numnodes = int(nodeset_elem.get("numnodes"))
1295
1296 nodesets.append((numnodes,r))
1297
1298 return cls(nodesets)
1299
1301 """Returns an ElementTree XML representation of the nodes
1302
1303 See the Haizea documentation for details on the
1304 lease XML format.
1305
1306 """
1307 nodes = ET.Element("nodes")
1308 for (numnodes, capacity) in self.node_sets:
1309 nodeset = ET.SubElement(nodes, "node-set")
1310 nodeset.set("numnodes", str(numnodes))
1311 for res_type in capacity.get_resource_types():
1312 res = ET.SubElement(nodeset, "res")
1313 res.set("type", res_type)
1314 ninstances = capacity.get_ninstances(res_type)
1315 if ninstances == 1:
1316 res.set("amount", str(capacity.get_quantity(res_type)))
1317
1318 return nodes
1319
1321 """Returns a dictionary mapping individual nodes to capacities
1322
1323 """
1324 nodes = {}
1325 nodenum = 1
1326 for node_set in self.node_sets:
1327 numnodes = node_set[0]
1328 r = node_set[1]
1329 for i in range(numnodes):
1330 nodes[nodenum] = r
1331 nodenum += 1
1332 return nodes
1333
1335 """Adds a new resource to all the nodes
1336
1337 Argument:
1338 name -- Name of the resource type
1339 amounts -- A list with the amounts of the resource to add to each
1340 node. If the resource is single-instance, then this will just
1341 be a list with a single element. If multi-instance, each element
1342 of the list represent the amount of an instance of the resource.
1343 """
1344 for node_set in self.node_sets:
1345 r = node_set[1]
1346 r.set_ninstances(name, len(amounts))
1347 for ninstance, amount in enumerate(amounts):
1348 r.set_quantity_instance(name, ninstance+1, amount)
1349