1# -*- coding: utf-8 -*- 2# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Machine Manager module.""" 7 8from __future__ import division 9from __future__ import print_function 10 11import collections 12import hashlib 13import math 14import os.path 15import re 16import sys 17import threading 18import time 19 20import file_lock_machine 21import image_chromeos 22import test_flag 23from cros_utils import command_executer 24from cros_utils import logger 25 26CHECKSUM_FILE = '/usr/local/osimage_checksum_file' 27 28 29class BadChecksum(Exception): 30 """Raised if all machines for a label don't have the same checksum.""" 31 pass 32 33 34class BadChecksumString(Exception): 35 """Raised if all machines for a label don't have the same checksum string.""" 36 pass 37 38 39class MissingLocksDirectory(Exception): 40 """Raised when cannot find/access the machine locks directory.""" 41 42 43class CrosCommandError(Exception): 44 """Raised when an error occurs running command on DUT.""" 45 46 47class CrosMachine(object): 48 """The machine class.""" 49 50 def __init__(self, name, chromeos_root, log_level, cmd_exec=None): 51 self.name = name 52 self.image = None 53 # We relate a dut with a label if we reimage the dut using label or we 54 # detect at the very beginning that the dut is running this label. 55 self.label = None 56 self.checksum = None 57 self.locked = False 58 self.released_time = time.time() 59 self.test_run = None 60 self.chromeos_root = chromeos_root 61 self.log_level = log_level 62 self.cpuinfo = None 63 self.machine_id = None 64 self.checksum_string = None 65 self.meminfo = None 66 self.phys_kbytes = None 67 self.cooldown_wait_time = 0 68 self.ce = cmd_exec or command_executer.GetCommandExecuter( 69 log_level=self.log_level) 70 self.SetUpChecksumInfo() 71 72 def SetUpChecksumInfo(self): 73 if not self.IsReachable(): 74 self.machine_checksum = None 75 return 76 self._GetMemoryInfo() 77 self._GetCPUInfo() 78 self._ComputeMachineChecksumString() 79 self._GetMachineID() 80 self.machine_checksum = self._GetMD5Checksum(self.checksum_string) 81 self.machine_id_checksum = self._GetMD5Checksum(self.machine_id) 82 83 def IsReachable(self): 84 command = 'ls' 85 ret = self.ce.CrosRunCommand( 86 command, machine=self.name, chromeos_root=self.chromeos_root) 87 if ret: 88 return False 89 return True 90 91 def AddCooldownWaitTime(self, wait_time): 92 self.cooldown_wait_time += wait_time 93 94 def GetCooldownWaitTime(self): 95 return self.cooldown_wait_time 96 97 def _ParseMemoryInfo(self): 98 line = self.meminfo.splitlines()[0] 99 usable_kbytes = int(line.split()[1]) 100 # This code is from src/third_party/test/files/client/bin/base_utils.py 101 # usable_kbytes is system's usable DRAM in kbytes, 102 # as reported by memtotal() from device /proc/meminfo memtotal 103 # after Linux deducts 1.5% to 9.5% for system table overhead 104 # Undo the unknown actual deduction by rounding up 105 # to next small multiple of a big power-of-two 106 # eg 12GB - 5.1% gets rounded back up to 12GB 107 mindeduct = 0.005 # 0.5 percent 108 maxdeduct = 0.095 # 9.5 percent 109 # deduction range 1.5% .. 9.5% supports physical mem sizes 110 # 6GB .. 12GB in steps of .5GB 111 # 12GB .. 24GB in steps of 1 GB 112 # 24GB .. 48GB in steps of 2 GB ... 113 # Finer granularity in physical mem sizes would require 114 # tighter spread between min and max possible deductions 115 116 # increase mem size by at least min deduction, without rounding 117 min_kbytes = int(usable_kbytes / (1.0 - mindeduct)) 118 # increase mem size further by 2**n rounding, by 0..roundKb or more 119 round_kbytes = int(usable_kbytes / (1.0 - maxdeduct)) - min_kbytes 120 # find least binary roundup 2**n that covers worst-cast roundKb 121 mod2n = 1 << int(math.ceil(math.log(round_kbytes, 2))) 122 # have round_kbytes <= mod2n < round_kbytes*2 123 # round min_kbytes up to next multiple of mod2n 124 phys_kbytes = min_kbytes + mod2n - 1 125 phys_kbytes -= phys_kbytes % mod2n # clear low bits 126 self.phys_kbytes = phys_kbytes 127 128 def _GetMemoryInfo(self): 129 # TODO yunlian: when the machine in rebooting, it will not return 130 # meminfo, the assert does not catch it either 131 command = 'cat /proc/meminfo' 132 ret, self.meminfo, _ = self.ce.CrosRunCommandWOutput( 133 command, machine=self.name, chromeos_root=self.chromeos_root) 134 assert ret == 0, 'Could not get meminfo from machine: %s' % self.name 135 if ret == 0: 136 self._ParseMemoryInfo() 137 138 def _GetCPUInfo(self): 139 command = 'cat /proc/cpuinfo' 140 ret, self.cpuinfo, _ = self.ce.CrosRunCommandWOutput( 141 command, machine=self.name, chromeos_root=self.chromeos_root) 142 assert ret == 0, 'Could not get cpuinfo from machine: %s' % self.name 143 144 def _ComputeMachineChecksumString(self): 145 self.checksum_string = '' 146 exclude_lines_list = ['MHz', 'BogoMIPS', 'bogomips'] 147 for line in self.cpuinfo.splitlines(): 148 if not any(e in line for e in exclude_lines_list): 149 self.checksum_string += line 150 self.checksum_string += ' ' + str(self.phys_kbytes) 151 152 def _GetMD5Checksum(self, ss): 153 if ss: 154 return hashlib.md5(ss.encode('utf-8')).hexdigest() 155 return '' 156 157 def _GetMachineID(self): 158 command = 'dump_vpd_log --full --stdout' 159 _, if_out, _ = self.ce.CrosRunCommandWOutput( 160 command, machine=self.name, chromeos_root=self.chromeos_root) 161 b = if_out.splitlines() 162 a = [l for l in b if 'Product' in l] 163 if a: 164 self.machine_id = a[0] 165 return 166 command = 'ifconfig' 167 _, if_out, _ = self.ce.CrosRunCommandWOutput( 168 command, machine=self.name, chromeos_root=self.chromeos_root) 169 b = if_out.splitlines() 170 a = [l for l in b if 'HWaddr' in l] 171 if a: 172 self.machine_id = '_'.join(a) 173 return 174 a = [l for l in b if 'ether' in l] 175 if 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 in the ExperimentRunner, but the machines still need to be locally 197 locked/unlocked (allocated to benchmark runs) to prevent multiple benchmark 198 runs within the same experiment from trying to use the same machine at the 199 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( 226 'Cannot access locks directory: %s' % 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, machine=machine.name, chromeos_root=self.chromeos_root) 246 if ret != 0: 247 raise CrosCommandError( 248 "Couldn't get Chrome version from %s." % machine.name) 249 250 if ret != 0: 251 version = '' 252 return version.rstrip() 253 254 def ImageMachine(self, machine, label): 255 checksum = label.checksum 256 257 if checksum and (machine.checksum == checksum): 258 return 259 chromeos_root = label.chromeos_root 260 if not chromeos_root: 261 chromeos_root = self.chromeos_root 262 image_chromeos_args = [ 263 image_chromeos.__file__, '--no_lock', 264 '--chromeos_root=%s' % chromeos_root, 265 '--image=%s' % label.chromeos_image, 266 '--image_args=%s' % label.image_args, 267 '--remote=%s' % machine.name, 268 '--logging_level=%s' % self.log_level 269 ] 270 if label.board: 271 image_chromeos_args.append('--board=%s' % label.board) 272 273 # Currently can't image two machines at once. 274 # So have to serialized on this lock. 275 save_ce_log_level = self.ce.log_level 276 if self.log_level != 'verbose': 277 self.ce.log_level = 'average' 278 279 with self.image_lock: 280 if self.log_level != 'verbose': 281 self.logger.LogOutput('Pushing image onto machine.') 282 self.logger.LogOutput('Running image_chromeos.DoImage with %s' % 283 ' '.join(image_chromeos_args)) 284 retval = 0 285 if not test_flag.GetTestMode(): 286 retval = image_chromeos.DoImage(image_chromeos_args) 287 if retval: 288 cmd = 'reboot && exit' 289 if self.log_level != 'verbose': 290 self.logger.LogOutput('reboot & exit.') 291 self.ce.CrosRunCommand( 292 cmd, machine=machine.name, 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( 353 True, 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( 375 'Checking machine characteristics for %s' % 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'" % ', '.join( 416 machine.name for machine in machines)) 417 418 419### for m in self._machines: 420### if (m.locked and time.time() - m.released_time < 10 and 421### m.checksum == image_checksum): 422### return None 423 unlocked_machines = [ 424 machine for machine in self.GetAvailableMachines(label) 425 if not machine.locked 426 ] 427 for m in unlocked_machines: 428 if image_checksum and m.checksum == image_checksum: 429 m.locked = True 430 m.test_run = threading.current_thread() 431 return m 432 for m in unlocked_machines: 433 if not m.checksum: 434 m.locked = True 435 m.test_run = threading.current_thread() 436 return m 437 # This logic ensures that threads waiting on a machine will get a machine 438 # with a checksum equal to their image over other threads. This saves time 439 # when crosperf initially assigns the machines to threads by minimizing 440 # the number of re-images. 441 # TODO(asharif): If we centralize the thread-scheduler, we wont need this 442 # code and can implement minimal reimaging code more cleanly. 443 for m in unlocked_machines: 444 if time.time() - m.released_time > 15: 445 # The release time gap is too large, so it is probably in the start 446 # stage, we need to reset the released_time. 447 m.released_time = time.time() 448 elif time.time() - m.released_time > 8: 449 m.locked = True 450 m.test_run = threading.current_thread() 451 return m 452 return None 453 454 def GetAvailableMachines(self, label=None): 455 if not label: 456 return self._machines 457 return [m for m in self._machines if m.name in label.remote] 458 459 def GetMachines(self, label=None): 460 if not label: 461 return self._all_machines 462 return [m for m in self._all_machines if m.name in label.remote] 463 464 def ReleaseMachine(self, machine): 465 with self._lock: 466 for m in self._machines: 467 if machine.name == m.name: 468 assert m.locked, 'Tried to double-release %s' % m.name 469 m.released_time = time.time() 470 m.locked = False 471 m.status = 'Available' 472 break 473 474 def Cleanup(self): 475 with self._lock: 476 # Unlock all machines (via file lock) 477 for m in self._machines: 478 res = file_lock_machine.Machine(m.name, self.locks_dir).Unlock(True) 479 480 if not res: 481 self.logger.LogError("Could not unlock machine: '%s'." % m.name) 482 483 def __str__(self): 484 with self._lock: 485 l = ['MachineManager Status:'] + [str(m) for m in self._machines] 486 return '\n'.join(l) 487 488 def AsString(self): 489 with self._lock: 490 stringify_fmt = '%-30s %-10s %-4s %-25s %-32s' 491 header = stringify_fmt % ('Machine', 'Thread', 'Lock', 'Status', 492 'Checksum') 493 table = [header] 494 for m in self._machines: 495 if m.test_run: 496 test_name = m.test_run.name 497 test_status = m.test_run.timeline.GetLastEvent() 498 else: 499 test_name = '' 500 test_status = '' 501 502 try: 503 machine_string = stringify_fmt % (m.name, test_name, m.locked, 504 test_status, m.checksum) 505 except ValueError: 506 machine_string = '' 507 table.append(machine_string) 508 return 'Machine Status:\n%s' % '\n'.join(table) 509 510 def GetAllCPUInfo(self, labels): 511 """Get cpuinfo for labels, merge them if their cpuinfo are the same.""" 512 dic = collections.defaultdict(list) 513 for label in labels: 514 for machine in self._all_machines: 515 if machine.name in label.remote: 516 dic[machine.cpuinfo].append(label.name) 517 break 518 output_segs = [] 519 for key, v in dic.items(): 520 output = ' '.join(v) 521 output += '\n-------------------\n' 522 output += key 523 output += '\n\n\n' 524 output_segs.append(output) 525 return ''.join(output_segs) 526 527 def GetAllMachines(self): 528 return self._all_machines 529 530 531class MockCrosMachine(CrosMachine): 532 """Mock cros machine class.""" 533 # pylint: disable=super-init-not-called 534 535 MEMINFO_STRING = """MemTotal: 3990332 kB 536MemFree: 2608396 kB 537Buffers: 147168 kB 538Cached: 811560 kB 539SwapCached: 0 kB 540Active: 503480 kB 541Inactive: 628572 kB 542Active(anon): 174532 kB 543Inactive(anon): 88576 kB 544Active(file): 328948 kB 545Inactive(file): 539996 kB 546Unevictable: 0 kB 547Mlocked: 0 kB 548SwapTotal: 5845212 kB 549SwapFree: 5845212 kB 550Dirty: 9384 kB 551Writeback: 0 kB 552AnonPages: 173408 kB 553Mapped: 146268 kB 554Shmem: 89676 kB 555Slab: 188260 kB 556SReclaimable: 169208 kB 557SUnreclaim: 19052 kB 558KernelStack: 2032 kB 559PageTables: 7120 kB 560NFS_Unstable: 0 kB 561Bounce: 0 kB 562WritebackTmp: 0 kB 563CommitLimit: 7840376 kB 564Committed_AS: 1082032 kB 565VmallocTotal: 34359738367 kB 566VmallocUsed: 364980 kB 567VmallocChunk: 34359369407 kB 568DirectMap4k: 45824 kB 569DirectMap2M: 4096000 kB 570""" 571 572 CPUINFO_STRING = """processor: 0 573vendor_id: GenuineIntel 574cpu family: 6 575model: 42 576model name: Intel(R) Celeron(R) CPU 867 @ 1.30GHz 577stepping: 7 578microcode: 0x25 579cpu MHz: 1300.000 580cache size: 2048 KB 581physical id: 0 582siblings: 2 583core id: 0 584cpu cores: 2 585apicid: 0 586initial apicid: 0 587fpu: yes 588fpu_exception: yes 589cpuid level: 13 590wp: yes 591flags: 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 592bogomips: 2594.17 593clflush size: 64 594cache_alignment: 64 595address sizes: 36 bits physical, 48 bits virtual 596power management: 597 598processor: 1 599vendor_id: GenuineIntel 600cpu family: 6 601model: 42 602model name: Intel(R) Celeron(R) CPU 867 @ 1.30GHz 603stepping: 7 604microcode: 0x25 605cpu MHz: 1300.000 606cache size: 2048 KB 607physical id: 0 608siblings: 2 609core id: 1 610cpu cores: 2 611apicid: 2 612initial apicid: 2 613fpu: yes 614fpu_exception: yes 615cpuid level: 13 616wp: yes 617flags: 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 618bogomips: 2594.17 619clflush size: 64 620cache_alignment: 64 621address sizes: 36 bits physical, 48 bits virtual 622power management: 623""" 624 625 def __init__(self, name, chromeos_root, log_level): 626 self.name = name 627 self.image = None 628 self.checksum = None 629 self.locked = False 630 self.released_time = time.time() 631 self.test_run = None 632 self.chromeos_root = chromeos_root 633 self.checksum_string = re.sub(r'\d', '', name) 634 # In test, we assume "lumpy1", "lumpy2" are the same machine. 635 self.machine_checksum = self._GetMD5Checksum(self.checksum_string) 636 self.log_level = log_level 637 self.label = None 638 self.cooldown_wait_time = 0 639 self.ce = command_executer.GetCommandExecuter(log_level=self.log_level) 640 self._GetCPUInfo() 641 642 def IsReachable(self): 643 return True 644 645 def _GetMemoryInfo(self): 646 self.meminfo = self.MEMINFO_STRING 647 self._ParseMemoryInfo() 648 649 def _GetCPUInfo(self): 650 self.cpuinfo = self.CPUINFO_STRING 651 652 653class MockMachineManager(MachineManager): 654 """Mock machine manager class.""" 655 656 def __init__(self, chromeos_root, acquire_timeout, log_level, locks_dir): 657 super(MockMachineManager, self).__init__(chromeos_root, acquire_timeout, 658 log_level, locks_dir) 659 660 def _TryToLockMachine(self, cros_machine): 661 self._machines.append(cros_machine) 662 cros_machine.checksum = '' 663 664 def AddMachine(self, machine_name): 665 with self._lock: 666 for m in self._all_machines: 667 assert m.name != machine_name, 'Tried to double-add %s' % machine_name 668 cm = MockCrosMachine(machine_name, self.chromeos_root, self.log_level) 669 assert cm.machine_checksum, ( 670 'Could not find checksum for machine %s' % machine_name) 671 # In Original MachineManager, the test is 'if cm.machine_checksum:' - if a 672 # machine is unreachable, then its machine_checksum is None. Here we 673 # cannot do this, because machine_checksum is always faked, so we directly 674 # test cm.IsReachable, which is properly mocked. 675 if cm.IsReachable(): 676 self._all_machines.append(cm) 677 678 def GetChromeVersion(self, machine): 679 return 'Mock Chrome Version R50' 680 681 def AcquireMachine(self, label): 682 for machine in self._all_machines: 683 if not machine.locked: 684 machine.locked = True 685 return machine 686 return None 687 688 def ImageMachine(self, machine, label): 689 if machine or label: 690 return 0 691 return 1 692 693 def ReleaseMachine(self, machine): 694 machine.locked = False 695 696 def GetMachines(self, label=None): 697 return self._all_machines 698 699 def GetAvailableMachines(self, label=None): 700 return self._all_machines 701 702 def ForceSameImageToAllMachines(self, label=None): 703 return 0 704 705 def ComputeCommonCheckSum(self, label=None): 706 common_checksum = 12345 707 for machine in self.GetMachines(label): 708 machine.machine_checksum = common_checksum 709 self.machine_checksum[label.name] = common_checksum 710 711 def GetAllMachines(self): 712 return self._all_machines 713