Package haizea :: Package core :: Module leases
[hide private]
[frames] | no frames]

Source Code for Module haizea.core.leases

   1  # -------------------------------------------------------------------------- # 
   2  # Copyright 2006-2009, University of Chicago                                 # 
   3  # Copyright 2008-2009, Distributed Systems Architecture Group, Universidad   # 
   4  # Complutense de Madrid (dsa-research.org)                                   # 
   5  #                                                                            # 
   6  # Licensed under the Apache License, Version 2.0 (the "License"); you may    # 
   7  # not use this file except in compliance with the License. You may obtain    # 
   8  # 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   
  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      # Compatibility with Python <=2.4 
  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 # Lease states 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 # String representation of lease states 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 # Lease types 110 BEST_EFFORT = 1 111 ADVANCE_RESERVATION = 2 112 IMMEDIATE = 3 113 UNKNOWN = -1 114 115 # String representation of lease types 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 # Lease ID (read only) 149 self.id = lease_id 150 151 # Lease attributes 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 # Bookkeeping attributes: 161 162 # Lease state 163 if state == None: 164 state = Lease.STATE_NEW 165 self.state = LeaseStateMachine(initial_state = state) 166 167 # End of lease (recorded when the lease ends) 168 self.end = None 169 170 # Number of nodes requested in the lease 171 self.numnodes = len(requested_resources) 172 173 # The following two lists contain all the resource reservations 174 # (or RRs) associated to this lease. These two lists are 175 # basically the link between the lease and Haizea's slot table. 176 177 # The preparation RRs are reservations that have to be 178 # completed before a lease can first transition into a 179 # READY state (e.g., image transfers) 180 self.preparation_rrs = [] 181 # The VM RRs are reservations for the VMs that implement 182 # the lease. 183 self.vm_rrs = [] 184 185 # Enactment information. Should only be manipulated by enactment module 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
199 - def create_new_from_xml_element(cls, element):
200 lease = cls.from_xml_element(element) 201 lease.id = get_lease_id() 202 lease.state = LeaseStateMachine(initial_state = Lease.STATE_NEW) 203 return lease
204 205 @classmethod
206 - def from_xml_file(cls, xml_file):
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
218 - def from_xml_string(cls, xml_str):
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
230 - def from_xml_element(cls, element):
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
298 - def to_xml(self):
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 # empty start element 329 elif self.start.requested == Timestamp.NOW: 330 ET.SubElement(start, "now") #empty now element 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
348 - def to_xml_string(self):
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
357 - def get_type(self):
358 """Determines the type of lease 359 360 Based on the lease's attributes, determines the lease's type. 361 Can return Lease.BEST_EFFORT, Lease.ADVANCE_RESERVATION, or 362 Lease.IMMEDIATE 363 364 """ 365 if self.start.requested == Timestamp.UNSPECIFIED: 366 return Lease.BEST_EFFORT 367 elif self.start.requested == Timestamp.NOW: 368 return Lease.IMMEDIATE 369 else: 370 return Lease.ADVANCE_RESERVATION
371
372 - def get_state(self):
373 """Returns the lease's state. 374 375 """ 376 return self.state.get_state()
377
378 - def set_state(self, state):
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
389 - def print_contents(self, loglevel=LOGLEVEL_VDEBUG):
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
408 - def print_rrs(self, loglevel=LOGLEVEL_VDEBUG):
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
427 - def get_active_vmrrs(self, time):
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
435 - def get_scheduled_reservations(self):
436 """Returns all scheduled reservations 437 438 """ 439 return [r for r in self.preparation_rrs + self.vm_rrs if r.state == ResourceReservation.STATE_SCHEDULED]
440
441 - def get_last_vmrr(self):
442 """Returns the last VM reservation for this lease. 443 444 """ 445 return self.vm_rrs[-1]
446
447 - def get_endtime(self):
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
459 - def append_vmrr(self, vmrr):
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
467 - def remove_vmrr(self, vmrr):
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
478 - def append_preparationrr(self, preparation_rr):
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
486 - def remove_preparationrr(self, preparation_rr):
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
497 - def clear_rrs(self):
498 """Removes all resource reservations for this lease 499 (both preparation and VM) 500 501 """ 502 self.preparation_rrs = [] 503 self.vm_rrs = []
504
505 - def get_waiting_time(self):
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
516 - def get_slowdown(self, bound=10):
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
550 - def add_boot_overhead(self, t):
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
561 - def add_runtime_overhead(self, percent):
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
586 587 -class LeaseStateMachine(StateMachine):
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 # Final states 665 Lease.STATE_DONE: [], 666 Lease.STATE_CANCELLED: [], 667 Lease.STATE_FAIL: [], 668 Lease.STATE_REJECTED: [], 669 } 670
671 - def __init__(self, initial_state):
673
674 675 -class Capacity(object):
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 """
700 - def __init__(self, types):
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
712 - def get_ninstances(self, res_type):
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
721 - def get_quantity(self, res_type):
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
730 - def get_quantity_instance(self, res_type, instance):
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
742 - def set_quantity(self, res_type, amount):
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
752 - def set_quantity_instance(self, res_type, instance, amount):
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
765 - def set_ninstances(self, res_type, ninstances):
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
780 - def get_resource_types(self):
781 """Returns the types of resources in this capacity. 782 783 """ 784 return self.quantity.keys()
785
786 - def __eq__(self, other):
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
799 - def __ne__(self, other):
800 """Tests if two capacities are not the same 801 802 """ 803 return not self == other
804
805 - def __repr__(self):
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
809 810 -class Timestamp(object):
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
824 - def __init__(self, requested):
825 """Constructor 826 827 Argument: 828 requested -- The requested timestamp 829 """ 830 self.requested = requested 831 self.scheduled = None 832 self.actual = None
833
834 - def __repr__(self):
835 """Returns a string representation of the Duration""" 836 return "REQ: %s | SCH: %s | ACT: %s" % (self.requested, self.scheduled, self.actual)
837
838 -class Duration(object):
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 # The following is ONLY used in simulation 871 self.known = known
872
873 - def incr(self, t):
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
883 - def incr_by_percent(self, pct):
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
894 - def accumulate_duration(self, t):
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
902 - def get_remaining_duration(self):
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
917 - def __repr__(self):
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
921 -class SoftwareEnvironment(object):
922 """The base class for a lease's software environment""" 923
924 - def __init__(self):
925 """Constructor. 926 927 Does nothing.""" 928 pass
929
930 -class UnmanagedSoftwareEnvironment(SoftwareEnvironment):
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
941 - def __init__(self):
942 """Constructor. 943 944 Does nothing.""" 945 SoftwareEnvironment.__init__(self)
946
947 -class DiskImageSoftwareEnvironment(SoftwareEnvironment):
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
968 969 970 -class LeaseWorkload(object):
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 """
978 - def __init__(self, leases):
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
987 - def get_leases(self):
988 """Returns the leases in the workload. 989 990 """ 991 return self.leases
992 993 @classmethod
994 - def from_xml_file(cls, xml_file, inittime = DateTime(0)):
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 # TODO: need to adapt the old SWF trace reading code to new Lease 1010 # data structures 1011 # @classmethod 1012 # def from_swf_file(cls, swf_file, inittime = DateTime(0)): 1013 # file = open (tracefile, "r") 1014 # requests = [] 1015 # inittime = config.get("starttime") 1016 # for line in file: 1017 # if line[0]!=';': 1018 # req = None 1019 # fields = line.split() 1020 # reqtime = float(fields[8]) 1021 # runtime = int(fields[3]) # 3: RunTime 1022 # waittime = int(fields[2]) 1023 # status = int(fields[10]) 1024 # 1025 # if reqtime > 0: 1026 # tSubmit = int(fields[1]) # 1: Submission time 1027 # tSubmit = inittime + TimeDelta(seconds=tSubmit) 1028 # vmimage = "NOIMAGE" 1029 # vmimagesize = 600 # Arbitrary 1030 # numnodes = int(fields[7]) # 7: reqNProcs 1031 # resreq = ResourceTuple.create_empty() 1032 # resreq.set_by_type(constants.RES_CPU, 1) # One CPU per VM, should be configurable 1033 # resreq.set_by_type(constants.RES_MEM, 1024) # Should be configurable 1034 # resreq.set_by_type(constants.RES_DISK, vmimagesize + 0) # Should be configurable 1035 # maxdur = TimeDelta(seconds=reqtime) 1036 # if runtime < 0 and status==5: 1037 # # This is a job that got cancelled while waiting in the queue 1038 # continue 1039 # else: 1040 # if runtime == 0: 1041 # runtime = 1 # Runtime of 0 is <0.5 rounded down. 1042 # realdur = TimeDelta(seconds=runtime) # 3: RunTime 1043 # if realdur > maxdur: 1044 # realdur = maxdur 1045 # preemptible = True 1046 # req = BestEffortLease(tSubmit, maxdur, vmimage, vmimagesize, numnodes, resreq, preemptible, realdur) 1047 # requests.append(req) 1048 # return requests 1049 1050 @classmethod
1051 - def __from_xml_element(cls, element, inittime):
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 # Add time lease is submitted 1068 submittime = inittime + Parser.DateTimeDeltaFromString(r.get("arrival")) 1069 lease.set("submit-time", str(submittime)) 1070 1071 # If an exact starting time is specified, add the init time 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
1117 - def from_xml_file(cls, xml_file):
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
1129 - def from_lwf_file(cls, lwf_file):
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
1142 - def __from_xml_element(cls, element):
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 # TODO: Attributes 1155 attrs = [] 1156 1157 nodes = Nodes.from_xml_element(element.find("nodes")) 1158 1159 # Validate nodes 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 # TODO: Raise something more meaningful 1165 raise Exception 1166 1167 return cls(nodes, resource_types, attrs)
1168 1169 @classmethod
1170 - def from_resources_string(cls, resource_str):
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
1201 - def add_resource(self, name, amounts):
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
1214 - def get_resource_types(self):
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 """
1256 - def __init__(self, node_sets):
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
1267 - def from_xml_element(cls, nodes_element):
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
1300 - def to_xml(self):
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
1320 - def get_all_nodes(self):
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
1334 - def add_resource(self, name, amounts):
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