machine_manager.py revision 63f13489b1a02bbbc75b46fec6ae7a817442df94
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 Exception("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 else: 410 sleep_time = max(1, min(self.acquire_timeout, check_interval_time)) 411 time.sleep(sleep_time) 412 self.acquire_timeout -= sleep_time 413 414 if self.acquire_timeout < 0: 415 machine_names = [] 416 for machine in machines: 417 machine_names.append(machine.name) 418 self.logger.LogFatal('Could not acquire any of the ' 419 "following machines: '%s'" % 420 ', '.join(machine_names)) 421 422### for m in self._machines: 423### if (m.locked and time.time() - m.released_time < 10 and 424### m.checksum == image_checksum): 425### return None 426 for m in [machine 427 for machine in self.GetAvailableMachines(label) 428 if not machine.locked]: 429 if image_checksum and (m.checksum == image_checksum): 430 m.locked = True 431 m.test_run = threading.current_thread() 432 return m 433 for m in [machine 434 for machine in self.GetAvailableMachines(label) 435 if not machine.locked]: 436 if not m.checksum: 437 m.locked = True 438 m.test_run = threading.current_thread() 439 return m 440 # This logic ensures that threads waiting on a machine will get a machine 441 # with a checksum equal to their image over other threads. This saves time 442 # when crosperf initially assigns the machines to threads by minimizing 443 # the number of re-images. 444 # TODO(asharif): If we centralize the thread-scheduler, we wont need this 445 # code and can implement minimal reimaging code more cleanly. 446 for m in [machine 447 for machine in self.GetAvailableMachines(label) 448 if not machine.locked]: 449 if time.time() - m.released_time > 15: 450 # The release time gap is too large, so it is probably in the start 451 # stage, we need to reset the released_time. 452 m.released_time = time.time() 453 elif time.time() - m.released_time > 8: 454 m.locked = True 455 m.test_run = threading.current_thread() 456 return m 457 return None 458 459 def GetAvailableMachines(self, label=None): 460 if not label: 461 return self._machines 462 return [m for m in self._machines if m.name in label.remote] 463 464 def GetMachines(self, label=None): 465 if not label: 466 return self._all_machines 467 return [m for m in self._all_machines if m.name in label.remote] 468 469 def ReleaseMachine(self, machine): 470 with self._lock: 471 for m in self._machines: 472 if machine.name == m.name: 473 assert m.locked == True, 'Tried to double-release %s' % m.name 474 m.released_time = time.time() 475 m.locked = False 476 m.status = 'Available' 477 break 478 479 def Cleanup(self): 480 with self._lock: 481 # Unlock all machines (via file lock) 482 for m in self._machines: 483 res = file_lock_machine.Machine(m.name, self.locks_dir).Unlock(True) 484 485 if not res: 486 self.logger.LogError("Could not unlock machine: '%s'." % m.name) 487 488 def __str__(self): 489 with self._lock: 490 l = ['MachineManager Status:'] 491 for m in self._machines: 492 l.append(str(m)) 493 return '\n'.join(l) 494 495 def AsString(self): 496 with self._lock: 497 stringify_fmt = '%-30s %-10s %-4s %-25s %-32s' 498 header = stringify_fmt % ('Machine', 'Thread', 'Lock', 'Status', 499 'Checksum') 500 table = [header] 501 for m in self._machines: 502 if m.test_run: 503 test_name = m.test_run.name 504 test_status = m.test_run.timeline.GetLastEvent() 505 else: 506 test_name = '' 507 test_status = '' 508 509 try: 510 machine_string = stringify_fmt % (m.name, test_name, m.locked, 511 test_status, m.checksum) 512 except ValueError: 513 machine_string = '' 514 table.append(machine_string) 515 return 'Machine Status:\n%s' % '\n'.join(table) 516 517 def GetAllCPUInfo(self, labels): 518 """Get cpuinfo for labels, merge them if their cpuinfo are the same.""" 519 dic = collections.defaultdict(list) 520 for label in labels: 521 for machine in self._all_machines: 522 if machine.name in label.remote: 523 dic[machine.cpuinfo].append(label.name) 524 break 525 output_segs = [] 526 for key, v in dic.iteritems(): 527 output = ' '.join(v) 528 output += '\n-------------------\n' 529 output += key 530 output += '\n\n\n' 531 output_segs.append(output) 532 return ''.join(output_segs) 533 534 def GetAllMachines(self): 535 return self._all_machines 536 537 538class MockCrosMachine(CrosMachine): 539 """Mock cros machine class.""" 540 # pylint: disable=super-init-not-called 541 542 MEMINFO_STRING = """MemTotal: 3990332 kB 543MemFree: 2608396 kB 544Buffers: 147168 kB 545Cached: 811560 kB 546SwapCached: 0 kB 547Active: 503480 kB 548Inactive: 628572 kB 549Active(anon): 174532 kB 550Inactive(anon): 88576 kB 551Active(file): 328948 kB 552Inactive(file): 539996 kB 553Unevictable: 0 kB 554Mlocked: 0 kB 555SwapTotal: 5845212 kB 556SwapFree: 5845212 kB 557Dirty: 9384 kB 558Writeback: 0 kB 559AnonPages: 173408 kB 560Mapped: 146268 kB 561Shmem: 89676 kB 562Slab: 188260 kB 563SReclaimable: 169208 kB 564SUnreclaim: 19052 kB 565KernelStack: 2032 kB 566PageTables: 7120 kB 567NFS_Unstable: 0 kB 568Bounce: 0 kB 569WritebackTmp: 0 kB 570CommitLimit: 7840376 kB 571Committed_AS: 1082032 kB 572VmallocTotal: 34359738367 kB 573VmallocUsed: 364980 kB 574VmallocChunk: 34359369407 kB 575DirectMap4k: 45824 kB 576DirectMap2M: 4096000 kB 577""" 578 579 CPUINFO_STRING = """processor: 0 580vendor_id: GenuineIntel 581cpu family: 6 582model: 42 583model name: Intel(R) Celeron(R) CPU 867 @ 1.30GHz 584stepping: 7 585microcode: 0x25 586cpu MHz: 1300.000 587cache size: 2048 KB 588physical id: 0 589siblings: 2 590core id: 0 591cpu cores: 2 592apicid: 0 593initial apicid: 0 594fpu: yes 595fpu_exception: yes 596cpuid level: 13 597wp: yes 598flags: 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 599bogomips: 2594.17 600clflush size: 64 601cache_alignment: 64 602address sizes: 36 bits physical, 48 bits virtual 603power management: 604 605processor: 1 606vendor_id: GenuineIntel 607cpu family: 6 608model: 42 609model name: Intel(R) Celeron(R) CPU 867 @ 1.30GHz 610stepping: 7 611microcode: 0x25 612cpu MHz: 1300.000 613cache size: 2048 KB 614physical id: 0 615siblings: 2 616core id: 1 617cpu cores: 2 618apicid: 2 619initial apicid: 2 620fpu: yes 621fpu_exception: yes 622cpuid level: 13 623wp: yes 624flags: 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 625bogomips: 2594.17 626clflush size: 64 627cache_alignment: 64 628address sizes: 36 bits physical, 48 bits virtual 629power management: 630""" 631 632 def __init__(self, name, chromeos_root, log_level): 633 self.name = name 634 self.image = None 635 self.checksum = None 636 self.locked = False 637 self.released_time = time.time() 638 self.test_run = None 639 self.chromeos_root = chromeos_root 640 self.checksum_string = re.sub(r'\d', '', name) 641 #In test, we assume "lumpy1", "lumpy2" are the same machine. 642 self.machine_checksum = self._GetMD5Checksum(self.checksum_string) 643 self.log_level = log_level 644 self.label = None 645 self.ce = command_executer.GetCommandExecuter(log_level=self.log_level) 646 self._GetCPUInfo() 647 648 def IsReachable(self): 649 return True 650 651 def _GetMemoryInfo(self): 652 self.meminfo = self.MEMINFO_STRING 653 self._ParseMemoryInfo() 654 655 def _GetCPUInfo(self): 656 self.cpuinfo = self.CPUINFO_STRING 657 658 659class MockMachineManager(MachineManager): 660 """Mock machine manager class.""" 661 662 def __init__(self, chromeos_root, acquire_timeout, log_level, locks_dir): 663 super(MockMachineManager, self).__init__( 664 chromeos_root, acquire_timeout, log_level, locks_dir) 665 666 def _TryToLockMachine(self, cros_machine): 667 self._machines.append(cros_machine) 668 cros_machine.checksum = '' 669 670 def AddMachine(self, machine_name): 671 with self._lock: 672 for m in self._all_machines: 673 assert m.name != machine_name, 'Tried to double-add %s' % machine_name 674 cm = MockCrosMachine(machine_name, self.chromeos_root, self.log_level) 675 assert cm.machine_checksum, ('Could not find checksum for machine %s' % 676 machine_name) 677 # In Original MachineManager, the test is 'if cm.machine_checksum:' - if a 678 # machine is unreachable, then its machine_checksum is None. Here we 679 # cannot do this, because machine_checksum is always faked, so we directly 680 # test cm.IsReachable, which is properly mocked. 681 if cm.IsReachable(): 682 self._all_machines.append(cm) 683 684 def GetChromeVersion(self, machine): 685 return 'Mock Chrome Version R50' 686 687 def AcquireMachine(self, label): 688 for machine in self._all_machines: 689 if not machine.locked: 690 machine.locked = True 691 return machine 692 return None 693 694 def ImageMachine(self, machine_name, label): 695 if machine_name or label: 696 return 0 697 return 1 698 699 def ReleaseMachine(self, machine): 700 machine.locked = False 701 702 def GetMachines(self, label=None): 703 return self._all_machines 704 705 def GetAvailableMachines(self, label=None): 706 return self._all_machines 707 708 def ForceSameImageToAllMachines(self, label=None): 709 return 0 710 711 def ComputeCommonCheckSum(self, label=None): 712 common_checksum = 12345 713 for machine in self.GetMachines(label): 714 machine.machine_checksum = common_checksum 715 self.machine_checksum[label.name] = common_checksum 716 717 def GetAllMachines(self): 718 return self._all_machines 719