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