1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Machine Manager module.""" 5 6from __future__ import print_function 7 8import collections 9import file_lock_machine 10import hashlib 11import image_chromeos 12import math 13import os.path 14import re 15import sys 16import threading 17import time 18 19import test_flag 20from cros_utils import command_executer 21from cros_utils import logger 22 23CHECKSUM_FILE = '/usr/local/osimage_checksum_file' 24 25 26class BadChecksum(Exception): 27 """Raised if all machines for a label don't have the same checksum.""" 28 pass 29 30 31class BadChecksumString(Exception): 32 """Raised if all machines for a label don't have the same checksum string.""" 33 pass 34 35 36class MissingLocksDirectory(Exception): 37 """Raised when cannot find/access the machine locks directory.""" 38 39 40class CrosCommandError(Exception): 41 """Raised when an error occurs running command on DUT.""" 42 43 44class CrosMachine(object): 45 """The machine class.""" 46 47 def __init__(self, name, chromeos_root, log_level, cmd_exec=None): 48 self.name = name 49 self.image = None 50 # We relate a dut with a label if we reimage the dut using label or we 51 # detect at the very beginning that the dut is running this label. 52 self.label = None 53 self.checksum = None 54 self.locked = False 55 self.released_time = time.time() 56 self.test_run = None 57 self.chromeos_root = chromeos_root 58 self.log_level = log_level 59 self.cpuinfo = None 60 self.machine_id = None 61 self.checksum_string = None 62 self.meminfo = None 63 self.phys_kbytes = None 64 self.ce = cmd_exec or command_executer.GetCommandExecuter( 65 log_level=self.log_level) 66 self.SetUpChecksumInfo() 67 68 def SetUpChecksumInfo(self): 69 if not self.IsReachable(): 70 self.machine_checksum = None 71 return 72 self._GetMemoryInfo() 73 self._GetCPUInfo() 74 self._ComputeMachineChecksumString() 75 self._GetMachineID() 76 self.machine_checksum = self._GetMD5Checksum(self.checksum_string) 77 self.machine_id_checksum = self._GetMD5Checksum(self.machine_id) 78 79 def IsReachable(self): 80 command = 'ls' 81 ret = self.ce.CrosRunCommand( 82 command, machine=self.name, chromeos_root=self.chromeos_root) 83 if ret: 84 return False 85 return True 86 87 def _ParseMemoryInfo(self): 88 line = self.meminfo.splitlines()[0] 89 usable_kbytes = int(line.split()[1]) 90 # This code is from src/third_party/test/files/client/bin/base_utils.py 91 # usable_kbytes is system's usable DRAM in kbytes, 92 # as reported by memtotal() from device /proc/meminfo memtotal 93 # after Linux deducts 1.5% to 9.5% for system table overhead 94 # Undo the unknown actual deduction by rounding up 95 # to next small multiple of a big power-of-two 96 # eg 12GB - 5.1% gets rounded back up to 12GB 97 mindeduct = 0.005 # 0.5 percent 98 maxdeduct = 0.095 # 9.5 percent 99 # deduction range 1.5% .. 9.5% supports physical mem sizes 100 # 6GB .. 12GB in steps of .5GB 101 # 12GB .. 24GB in steps of 1 GB 102 # 24GB .. 48GB in steps of 2 GB ... 103 # Finer granularity in physical mem sizes would require 104 # tighter spread between min and max possible deductions 105 106 # increase mem size by at least min deduction, without rounding 107 min_kbytes = int(usable_kbytes / (1.0 - mindeduct)) 108 # increase mem size further by 2**n rounding, by 0..roundKb or more 109 round_kbytes = int(usable_kbytes / (1.0 - maxdeduct)) - min_kbytes 110 # find least binary roundup 2**n that covers worst-cast roundKb 111 mod2n = 1 << int(math.ceil(math.log(round_kbytes, 2))) 112 # have round_kbytes <= mod2n < round_kbytes*2 113 # round min_kbytes up to next multiple of mod2n 114 phys_kbytes = min_kbytes + mod2n - 1 115 phys_kbytes -= phys_kbytes % mod2n # clear low bits 116 self.phys_kbytes = phys_kbytes 117 118 def _GetMemoryInfo(self): 119 #TODO yunlian: when the machine in rebooting, it will not return 120 #meminfo, the assert does not catch it either 121 command = 'cat /proc/meminfo' 122 ret, self.meminfo, _ = self.ce.CrosRunCommandWOutput( 123 command, machine=self.name, chromeos_root=self.chromeos_root) 124 assert ret == 0, 'Could not get meminfo from machine: %s' % self.name 125 if ret == 0: 126 self._ParseMemoryInfo() 127 128 def _GetCPUInfo(self): 129 command = 'cat /proc/cpuinfo' 130 ret, self.cpuinfo, _ = self.ce.CrosRunCommandWOutput( 131 command, machine=self.name, chromeos_root=self.chromeos_root) 132 assert ret == 0, 'Could not get cpuinfo from machine: %s' % self.name 133 134 def _ComputeMachineChecksumString(self): 135 self.checksum_string = '' 136 exclude_lines_list = ['MHz', 'BogoMIPS', 'bogomips'] 137 for line in self.cpuinfo.splitlines(): 138 if not any(e in line for e in exclude_lines_list): 139 self.checksum_string += line 140 self.checksum_string += ' ' + str(self.phys_kbytes) 141 142 def _GetMD5Checksum(self, ss): 143 if ss: 144 return hashlib.md5(ss).hexdigest() 145 else: 146 return '' 147 148 def _GetMachineID(self): 149 command = 'dump_vpd_log --full --stdout' 150 _, if_out, _ = self.ce.CrosRunCommandWOutput( 151 command, machine=self.name, chromeos_root=self.chromeos_root) 152 b = if_out.splitlines() 153 a = [l for l in b if 'Product' in l] 154 if len(a): 155 self.machine_id = a[0] 156 return 157 command = 'ifconfig' 158 _, if_out, _ = self.ce.CrosRunCommandWOutput( 159 command, machine=self.name, chromeos_root=self.chromeos_root) 160 b = if_out.splitlines() 161 a = [l for l in b if 'HWaddr' in l] 162 if len(a): 163 self.machine_id = '_'.join(a) 164 return 165 a = [l for l in b if 'ether' in l] 166 if len(a): 167 self.machine_id = '_'.join(a) 168 return 169 assert 0, 'Could not get machine_id from machine: %s' % self.name 170 171 def __str__(self): 172 l = [] 173 l.append(self.name) 174 l.append(str(self.image)) 175 l.append(str(self.checksum)) 176 l.append(str(self.locked)) 177 l.append(str(self.released_time)) 178 return ', '.join(l) 179 180 181class MachineManager(object): 182 """Lock, image and unlock machines locally for benchmark runs. 183 184 This class contains methods and calls to lock, unlock and image 185 machines and distribute machines to each benchmark run. The assumption is 186 that all of the machines for the experiment have been globally locked 187 (using an AFE server) in the ExperimentRunner, but the machines still need 188 to be locally locked/unlocked (allocated to benchmark runs) to prevent 189 multiple benchmark runs within the same experiment from trying to use the 190 same machine at the same time. 191 """ 192 193 def __init__(self, 194 chromeos_root, 195 acquire_timeout, 196 log_level, 197 locks_dir, 198 cmd_exec=None, 199 lgr=None): 200 self._lock = threading.RLock() 201 self._all_machines = [] 202 self._machines = [] 203 self.image_lock = threading.Lock() 204 self.num_reimages = 0 205 self.chromeos_root = None 206 self.machine_checksum = {} 207 self.machine_checksum_string = {} 208 self.acquire_timeout = acquire_timeout 209 self.log_level = log_level 210 self.locks_dir = locks_dir 211 self.ce = cmd_exec or command_executer.GetCommandExecuter( 212 log_level=self.log_level) 213 self.logger = lgr or logger.GetLogger() 214 215 if self.locks_dir and not os.path.isdir(self.locks_dir): 216 raise MissingLocksDirectory( 217 'Cannot access locks directory: %s' % self.locks_dir) 218 219 self._initialized_machines = [] 220 self.chromeos_root = chromeos_root 221 222 def RemoveNonLockedMachines(self, locked_machines): 223 for m in self._all_machines: 224 if m.name not in locked_machines: 225 self._all_machines.remove(m) 226 227 for m in self._machines: 228 if m.name not in locked_machines: 229 self._machines.remove(m) 230 231 def GetChromeVersion(self, machine): 232 """Get the version of Chrome running on the DUT.""" 233 234 cmd = '/opt/google/chrome/chrome --version' 235 ret, version, _ = self.ce.CrosRunCommandWOutput( 236 cmd, machine=machine.name, chromeos_root=self.chromeos_root) 237 if ret != 0: 238 raise CrosCommandError( 239 "Couldn't get Chrome version from %s." % machine.name) 240 241 if ret != 0: 242 version = '' 243 return version.rstrip() 244 245 def ImageMachine(self, machine, label): 246 checksum = label.checksum 247 248 if checksum and (machine.checksum == checksum): 249 return 250 chromeos_root = label.chromeos_root 251 if not chromeos_root: 252 chromeos_root = self.chromeos_root 253 image_chromeos_args = [ 254 image_chromeos.__file__, '--no_lock', 255 '--chromeos_root=%s' % chromeos_root, 256 '--image=%s' % label.chromeos_image, 257 '--image_args=%s' % label.image_args, '--remote=%s' % machine.name, 258 '--logging_level=%s' % self.log_level 259 ] 260 if label.board: 261 image_chromeos_args.append('--board=%s' % label.board) 262 263 # Currently can't image two machines at once. 264 # So have to serialized on this lock. 265 save_ce_log_level = self.ce.log_level 266 if self.log_level != 'verbose': 267 self.ce.log_level = 'average' 268 269 with self.image_lock: 270 if self.log_level != 'verbose': 271 self.logger.LogOutput('Pushing image onto machine.') 272 self.logger.LogOutput('Running image_chromeos.DoImage with %s' % 273 ' '.join(image_chromeos_args)) 274 retval = 0 275 if not test_flag.GetTestMode(): 276 retval = image_chromeos.DoImage(image_chromeos_args) 277 if retval: 278 cmd = 'reboot && exit' 279 if self.log_level != 'verbose': 280 self.logger.LogOutput('reboot & exit.') 281 self.ce.CrosRunCommand( 282 cmd, machine=machine.name, chromeos_root=self.chromeos_root) 283 time.sleep(60) 284 if self.log_level != 'verbose': 285 self.logger.LogOutput('Pushing image onto machine.') 286 self.logger.LogOutput('Running image_chromeos.DoImage with %s' % 287 ' '.join(image_chromeos_args)) 288 retval = image_chromeos.DoImage(image_chromeos_args) 289 if retval: 290 raise RuntimeError("Could not image machine: '%s'." % machine.name) 291 else: 292 self.num_reimages += 1 293 machine.checksum = checksum 294 machine.image = label.chromeos_image 295 machine.label = label 296 297 if not label.chrome_version: 298 label.chrome_version = self.GetChromeVersion(machine) 299 300 self.ce.log_level = save_ce_log_level 301 return retval 302 303 def ComputeCommonCheckSum(self, label): 304 # Since this is used for cache lookups before the machines have been 305 # compared/verified, check here to make sure they all have the same 306 # checksum (otherwise the cache lookup may not be valid). 307 common_checksum = None 308 for machine in self.GetMachines(label): 309 # Make sure the machine's checksums are calculated. 310 if not machine.machine_checksum: 311 machine.SetUpChecksumInfo() 312 cs = machine.machine_checksum 313 # If this is the first machine we've examined, initialize 314 # common_checksum. 315 if not common_checksum: 316 common_checksum = cs 317 # Make sure this machine's checksum matches our 'common' checksum. 318 if cs != common_checksum: 319 raise BadChecksum('Machine checksums do not match!') 320 self.machine_checksum[label.name] = common_checksum 321 322 def ComputeCommonCheckSumString(self, label): 323 # The assumption is that this function is only called AFTER 324 # ComputeCommonCheckSum, so there is no need to verify the machines 325 # are the same here. If this is ever changed, this function should be 326 # modified to verify that all the machines for a given label are the 327 # same. 328 for machine in self.GetMachines(label): 329 if machine.checksum_string: 330 self.machine_checksum_string[label.name] = machine.checksum_string 331 break 332 333 def _TryToLockMachine(self, cros_machine): 334 with self._lock: 335 assert cros_machine, "Machine can't be None" 336 for m in self._machines: 337 if m.name == cros_machine.name: 338 return 339 locked = True 340 if self.locks_dir: 341 locked = file_lock_machine.Machine(cros_machine.name, 342 self.locks_dir).Lock( 343 True, sys.argv[0]) 344 if locked: 345 self._machines.append(cros_machine) 346 command = 'cat %s' % CHECKSUM_FILE 347 ret, out, _ = self.ce.CrosRunCommandWOutput( 348 command, 349 chromeos_root=self.chromeos_root, 350 machine=cros_machine.name) 351 if ret == 0: 352 cros_machine.checksum = out.strip() 353 elif self.locks_dir: 354 self.logger.LogOutput("Couldn't lock: %s" % cros_machine.name) 355 356 # This is called from single threaded mode. 357 def AddMachine(self, machine_name): 358 with self._lock: 359 for m in self._all_machines: 360 assert m.name != machine_name, 'Tried to double-add %s' % machine_name 361 362 if self.log_level != 'verbose': 363 self.logger.LogOutput('Setting up remote access to %s' % machine_name) 364 self.logger.LogOutput( 365 'Checking machine characteristics for %s' % machine_name) 366 cm = CrosMachine(machine_name, self.chromeos_root, self.log_level) 367 if cm.machine_checksum: 368 self._all_machines.append(cm) 369 370 def RemoveMachine(self, machine_name): 371 with self._lock: 372 self._machines = [m for m in self._machines if m.name != machine_name] 373 if self.locks_dir: 374 res = file_lock_machine.Machine(machine_name, 375 self.locks_dir).Unlock(True) 376 if not res: 377 self.logger.LogError("Could not unlock machine: '%s'." % machine_name) 378 379 def ForceSameImageToAllMachines(self, label): 380 machines = self.GetMachines(label) 381 for m in machines: 382 self.ImageMachine(m, label) 383 m.SetUpChecksumInfo() 384 385 def AcquireMachine(self, label): 386 image_checksum = label.checksum 387 machines = self.GetMachines(label) 388 check_interval_time = 120 389 with self._lock: 390 # Lazily external lock machines 391 while self.acquire_timeout >= 0: 392 for m in machines: 393 new_machine = m not in self._all_machines 394 self._TryToLockMachine(m) 395 if new_machine: 396 m.released_time = time.time() 397 if self.GetAvailableMachines(label): 398 break 399 sleep_time = max(1, min(self.acquire_timeout, check_interval_time)) 400 time.sleep(sleep_time) 401 self.acquire_timeout -= sleep_time 402 403 if self.acquire_timeout < 0: 404 self.logger.LogFatal( 405 'Could not acquire any of the ' 406 "following machines: '%s'" % ', '.join(machine.name 407 for machine in machines)) 408 409### for m in self._machines: 410### if (m.locked and time.time() - m.released_time < 10 and 411### m.checksum == image_checksum): 412### return None 413 unlocked_machines = [ 414 machine for machine in self.GetAvailableMachines(label) 415 if not machine.locked 416 ] 417 for m in unlocked_machines: 418 if image_checksum and m.checksum == image_checksum: 419 m.locked = True 420 m.test_run = threading.current_thread() 421 return m 422 for m in unlocked_machines: 423 if not m.checksum: 424 m.locked = True 425 m.test_run = threading.current_thread() 426 return m 427 # This logic ensures that threads waiting on a machine will get a machine 428 # with a checksum equal to their image over other threads. This saves time 429 # when crosperf initially assigns the machines to threads by minimizing 430 # the number of re-images. 431 # TODO(asharif): If we centralize the thread-scheduler, we wont need this 432 # code and can implement minimal reimaging code more cleanly. 433 for m in unlocked_machines: 434 if time.time() - m.released_time > 15: 435 # The release time gap is too large, so it is probably in the start 436 # stage, we need to reset the released_time. 437 m.released_time = time.time() 438 elif time.time() - m.released_time > 8: 439 m.locked = True 440 m.test_run = threading.current_thread() 441 return m 442 return None 443 444 def GetAvailableMachines(self, label=None): 445 if not label: 446 return self._machines 447 return [m for m in self._machines if m.name in label.remote] 448 449 def GetMachines(self, label=None): 450 if not label: 451 return self._all_machines 452 return [m for m in self._all_machines if m.name in label.remote] 453 454 def ReleaseMachine(self, machine): 455 with self._lock: 456 for m in self._machines: 457 if machine.name == m.name: 458 assert m.locked, 'Tried to double-release %s' % m.name 459 m.released_time = time.time() 460 m.locked = False 461 m.status = 'Available' 462 break 463 464 def Cleanup(self): 465 with self._lock: 466 # Unlock all machines (via file lock) 467 for m in self._machines: 468 res = file_lock_machine.Machine(m.name, self.locks_dir).Unlock(True) 469 470 if not res: 471 self.logger.LogError("Could not unlock machine: '%s'." % m.name) 472 473 def __str__(self): 474 with self._lock: 475 l = ['MachineManager Status:'] + [str(m) for m in self._machines] 476 return '\n'.join(l) 477 478 def AsString(self): 479 with self._lock: 480 stringify_fmt = '%-30s %-10s %-4s %-25s %-32s' 481 header = stringify_fmt % ('Machine', 'Thread', 'Lock', 'Status', 482 'Checksum') 483 table = [header] 484 for m in self._machines: 485 if m.test_run: 486 test_name = m.test_run.name 487 test_status = m.test_run.timeline.GetLastEvent() 488 else: 489 test_name = '' 490 test_status = '' 491 492 try: 493 machine_string = stringify_fmt % (m.name, test_name, m.locked, 494 test_status, m.checksum) 495 except ValueError: 496 machine_string = '' 497 table.append(machine_string) 498 return 'Machine Status:\n%s' % '\n'.join(table) 499 500 def GetAllCPUInfo(self, labels): 501 """Get cpuinfo for labels, merge them if their cpuinfo are the same.""" 502 dic = collections.defaultdict(list) 503 for label in labels: 504 for machine in self._all_machines: 505 if machine.name in label.remote: 506 dic[machine.cpuinfo].append(label.name) 507 break 508 output_segs = [] 509 for key, v in dic.iteritems(): 510 output = ' '.join(v) 511 output += '\n-------------------\n' 512 output += key 513 output += '\n\n\n' 514 output_segs.append(output) 515 return ''.join(output_segs) 516 517 def GetAllMachines(self): 518 return self._all_machines 519 520 521class MockCrosMachine(CrosMachine): 522 """Mock cros machine class.""" 523 # pylint: disable=super-init-not-called 524 525 MEMINFO_STRING = """MemTotal: 3990332 kB 526MemFree: 2608396 kB 527Buffers: 147168 kB 528Cached: 811560 kB 529SwapCached: 0 kB 530Active: 503480 kB 531Inactive: 628572 kB 532Active(anon): 174532 kB 533Inactive(anon): 88576 kB 534Active(file): 328948 kB 535Inactive(file): 539996 kB 536Unevictable: 0 kB 537Mlocked: 0 kB 538SwapTotal: 5845212 kB 539SwapFree: 5845212 kB 540Dirty: 9384 kB 541Writeback: 0 kB 542AnonPages: 173408 kB 543Mapped: 146268 kB 544Shmem: 89676 kB 545Slab: 188260 kB 546SReclaimable: 169208 kB 547SUnreclaim: 19052 kB 548KernelStack: 2032 kB 549PageTables: 7120 kB 550NFS_Unstable: 0 kB 551Bounce: 0 kB 552WritebackTmp: 0 kB 553CommitLimit: 7840376 kB 554Committed_AS: 1082032 kB 555VmallocTotal: 34359738367 kB 556VmallocUsed: 364980 kB 557VmallocChunk: 34359369407 kB 558DirectMap4k: 45824 kB 559DirectMap2M: 4096000 kB 560""" 561 562 CPUINFO_STRING = """processor: 0 563vendor_id: GenuineIntel 564cpu family: 6 565model: 42 566model name: Intel(R) Celeron(R) CPU 867 @ 1.30GHz 567stepping: 7 568microcode: 0x25 569cpu MHz: 1300.000 570cache size: 2048 KB 571physical id: 0 572siblings: 2 573core id: 0 574cpu cores: 2 575apicid: 0 576initial apicid: 0 577fpu: yes 578fpu_exception: yes 579cpuid level: 13 580wp: yes 581flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer xsave lahf_lm arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid 582bogomips: 2594.17 583clflush size: 64 584cache_alignment: 64 585address sizes: 36 bits physical, 48 bits virtual 586power management: 587 588processor: 1 589vendor_id: GenuineIntel 590cpu family: 6 591model: 42 592model name: Intel(R) Celeron(R) CPU 867 @ 1.30GHz 593stepping: 7 594microcode: 0x25 595cpu MHz: 1300.000 596cache size: 2048 KB 597physical id: 0 598siblings: 2 599core id: 1 600cpu cores: 2 601apicid: 2 602initial apicid: 2 603fpu: yes 604fpu_exception: yes 605cpuid level: 13 606wp: yes 607flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer xsave lahf_lm arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid 608bogomips: 2594.17 609clflush size: 64 610cache_alignment: 64 611address sizes: 36 bits physical, 48 bits virtual 612power management: 613""" 614 615 def __init__(self, name, chromeos_root, log_level): 616 self.name = name 617 self.image = None 618 self.checksum = None 619 self.locked = False 620 self.released_time = time.time() 621 self.test_run = None 622 self.chromeos_root = chromeos_root 623 self.checksum_string = re.sub(r'\d', '', name) 624 #In test, we assume "lumpy1", "lumpy2" are the same machine. 625 self.machine_checksum = self._GetMD5Checksum(self.checksum_string) 626 self.log_level = log_level 627 self.label = None 628 self.ce = command_executer.GetCommandExecuter(log_level=self.log_level) 629 self._GetCPUInfo() 630 631 def IsReachable(self): 632 return True 633 634 def _GetMemoryInfo(self): 635 self.meminfo = self.MEMINFO_STRING 636 self._ParseMemoryInfo() 637 638 def _GetCPUInfo(self): 639 self.cpuinfo = self.CPUINFO_STRING 640 641 642class MockMachineManager(MachineManager): 643 """Mock machine manager class.""" 644 645 def __init__(self, chromeos_root, acquire_timeout, log_level, locks_dir): 646 super(MockMachineManager, self).__init__(chromeos_root, acquire_timeout, 647 log_level, locks_dir) 648 649 def _TryToLockMachine(self, cros_machine): 650 self._machines.append(cros_machine) 651 cros_machine.checksum = '' 652 653 def AddMachine(self, machine_name): 654 with self._lock: 655 for m in self._all_machines: 656 assert m.name != machine_name, 'Tried to double-add %s' % machine_name 657 cm = MockCrosMachine(machine_name, self.chromeos_root, self.log_level) 658 assert cm.machine_checksum, ( 659 'Could not find checksum for machine %s' % machine_name) 660 # In Original MachineManager, the test is 'if cm.machine_checksum:' - if a 661 # machine is unreachable, then its machine_checksum is None. Here we 662 # cannot do this, because machine_checksum is always faked, so we directly 663 # test cm.IsReachable, which is properly mocked. 664 if cm.IsReachable(): 665 self._all_machines.append(cm) 666 667 def GetChromeVersion(self, machine): 668 return 'Mock Chrome Version R50' 669 670 def AcquireMachine(self, label): 671 for machine in self._all_machines: 672 if not machine.locked: 673 machine.locked = True 674 return machine 675 return None 676 677 def ImageMachine(self, machine_name, label): 678 if machine_name or label: 679 return 0 680 return 1 681 682 def ReleaseMachine(self, machine): 683 machine.locked = False 684 685 def GetMachines(self, label=None): 686 return self._all_machines 687 688 def GetAvailableMachines(self, label=None): 689 return self._all_machines 690 691 def ForceSameImageToAllMachines(self, label=None): 692 return 0 693 694 def ComputeCommonCheckSum(self, label=None): 695 common_checksum = 12345 696 for machine in self.GetMachines(label): 697 machine.machine_checksum = common_checksum 698 self.machine_checksum[label.name] = common_checksum 699 700 def GetAllMachines(self): 701 return self._all_machines 702