machine_manager.py revision 0d39863d8578fb1c6e7077b2c7cc5ca79edcf4dd
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 hashlib 9import image_chromeos 10import file_lock_machine 11import math 12import os.path 13import re 14import sys 15import threading 16import time 17 18import test_flag 19from cros_utils import command_executer 20from cros_utils import logger 21 22CHECKSUM_FILE = '/usr/local/osimage_checksum_file' 23 24 25class BadChecksum(Exception): 26 """Raised if all machines for a label don't have the same checksum.""" 27 pass 28 29 30class BadChecksumString(Exception): 31 """Raised if all machines for a label don't have the same checksum string.""" 32 pass 33 34 35class MissingLocksDirectory(Exception): 36 """Raised when cannot find/access the machine locks directory.""" 37 38 39class CrosCommandError(Exception): 40 """Raised when an error occurs running command on DUT.""" 41 42 43class CrosMachine(object): 44 """The machine class.""" 45 46 def __init__(self, name, chromeos_root, log_level, cmd_exec=None): 47 self.name = name 48 self.image = None 49 # We relate a dut with a label if we reimage the dut using label or we 50 # detect at the very beginning that the dut is running this label. 51 self.label = None 52 self.checksum = None 53 self.locked = False 54 self.released_time = time.time() 55 self.test_run = None 56 self.chromeos_root = chromeos_root 57 self.log_level = log_level 58 self.cpuinfo = None 59 self.machine_id = None 60 self.checksum_string = None 61 self.meminfo = None 62 self.phys_kbytes = None 63 self.ce = cmd_exec or command_executer.GetCommandExecuter( 64 log_level=self.log_level) 65 self.SetUpChecksumInfo() 66 67 def SetUpChecksumInfo(self): 68 if not self.IsReachable(): 69 self.machine_checksum = None 70 return 71 self._GetMemoryInfo() 72 self._GetCPUInfo() 73 self._ComputeMachineChecksumString() 74 self._GetMachineID() 75 self.machine_checksum = self._GetMD5Checksum(self.checksum_string) 76 self.machine_id_checksum = self._GetMD5Checksum(self.machine_id) 77 78 def IsReachable(self): 79 command = 'ls' 80 ret = self.ce.CrosRunCommand(command, 81 machine=self.name, 82 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, 124 machine=self.name, 125 chromeos_root=self.chromeos_root) 126 assert ret == 0, 'Could not get meminfo from machine: %s' % self.name 127 if ret == 0: 128 self._ParseMemoryInfo() 129 130 def _GetCPUInfo(self): 131 command = 'cat /proc/cpuinfo' 132 ret, self.cpuinfo, _ = self.ce.CrosRunCommandWOutput( 133 command, 134 machine=self.name, 135 chromeos_root=self.chromeos_root) 136 assert ret == 0, 'Could not get cpuinfo from machine: %s' % self.name 137 138 def _ComputeMachineChecksumString(self): 139 self.checksum_string = '' 140 exclude_lines_list = ['MHz', 'BogoMIPS', 'bogomips'] 141 for line in self.cpuinfo.splitlines(): 142 if not any([e in line for e in exclude_lines_list]): 143 self.checksum_string += line 144 self.checksum_string += ' ' + str(self.phys_kbytes) 145 146 def _GetMD5Checksum(self, ss): 147 if ss: 148 return hashlib.md5(ss).hexdigest() 149 else: 150 return '' 151 152 def _GetMachineID(self): 153 command = 'dump_vpd_log --full --stdout' 154 _, if_out, _ = self.ce.CrosRunCommandWOutput( 155 command, 156 machine=self.name, 157 chromeos_root=self.chromeos_root) 158 b = if_out.splitlines() 159 a = [l for l in b if 'Product' in l] 160 if len(a): 161 self.machine_id = a[0] 162 return 163 command = 'ifconfig' 164 _, if_out, _ = self.ce.CrosRunCommandWOutput( 165 command, 166 machine=self.name, 167 chromeos_root=self.chromeos_root) 168 b = if_out.splitlines() 169 a = [l for l in b if 'HWaddr' in l] 170 if len(a): 171 self.machine_id = '_'.join(a) 172 return 173 a = [l for l in b if 'ether' in l] 174 if len(a): 175 self.machine_id = '_'.join(a) 176 return 177 assert 0, 'Could not get machine_id from machine: %s' % self.name 178 179 def __str__(self): 180 l = [] 181 l.append(self.name) 182 l.append(str(self.image)) 183 l.append(str(self.checksum)) 184 l.append(str(self.locked)) 185 l.append(str(self.released_time)) 186 return ', '.join(l) 187 188 189class MachineManager(object): 190 """Lock, image and unlock machines locally for benchmark runs. 191 192 This class contains methods and calls to lock, unlock and image 193 machines and distribute machines to each benchmark run. The assumption is 194 that all of the machines for the experiment have been globally locked 195 (using an AFE server) in the ExperimentRunner, but the machines still need 196 to be locally locked/unlocked (allocated to benchmark runs) to prevent 197 multiple benchmark runs within the same experiment from trying to use the 198 same machine at the same time. 199 """ 200 201 def __init__(self, 202 chromeos_root, 203 acquire_timeout, 204 log_level, 205 locks_dir, 206 cmd_exec=None, 207 lgr=None): 208 self._lock = threading.RLock() 209 self._all_machines = [] 210 self._machines = [] 211 self.image_lock = threading.Lock() 212 self.num_reimages = 0 213 self.chromeos_root = None 214 self.machine_checksum = {} 215 self.machine_checksum_string = {} 216 self.acquire_timeout = acquire_timeout 217 self.log_level = log_level 218 self.locks_dir = locks_dir 219 self.ce = cmd_exec or command_executer.GetCommandExecuter( 220 log_level=self.log_level) 221 self.logger = lgr or logger.GetLogger() 222 223 if self.locks_dir and not os.path.isdir(self.locks_dir): 224 raise MissingLocksDirectory('Cannot access locks directory: %s' % 225 self.locks_dir) 226 227 self._initialized_machines = [] 228 self.chromeos_root = chromeos_root 229 230 def RemoveNonLockedMachines(self, locked_machines): 231 for m in self._all_machines: 232 if m.name not in locked_machines: 233 self._all_machines.remove(m) 234 235 for m in self._machines: 236 if m.name not in locked_machines: 237 self._machines.remove(m) 238 239 def GetChromeVersion(self, machine): 240 """Get the version of Chrome running on the DUT.""" 241 242 cmd = '/opt/google/chrome/chrome --version' 243 ret, version, _ = self.ce.CrosRunCommandWOutput( 244 cmd, 245 machine=machine.name, 246 chromeos_root=self.chromeos_root) 247 if ret != 0: 248 raise CrosCommandError("Couldn't get Chrome version from %s." % 249 machine.name) 250 251 if ret != 0: 252 version = '' 253 return version.rstrip() 254 255 def ImageMachine(self, machine, label): 256 checksum = label.checksum 257 258 if checksum and (machine.checksum == checksum): 259 return 260 chromeos_root = label.chromeos_root 261 if not chromeos_root: 262 chromeos_root = self.chromeos_root 263 image_chromeos_args = [image_chromeos.__file__, '--no_lock', 264 '--chromeos_root=%s' % chromeos_root, 265 '--image=%s' % label.chromeos_image, 266 '--image_args=%s' % label.image_args, '--remote=%s' % 267 machine.name, '--logging_level=%s' % self.log_level] 268 if label.board: 269 image_chromeos_args.append('--board=%s' % label.board) 270 271 # Currently can't image two machines at once. 272 # So have to serialized on this lock. 273 save_ce_log_level = self.ce.log_level 274 if self.log_level != 'verbose': 275 self.ce.log_level = 'average' 276 277 with self.image_lock: 278 if self.log_level != 'verbose': 279 self.logger.LogOutput('Pushing image onto machine.') 280 self.logger.LogOutput('Running image_chromeos.DoImage with %s' % 281 ' '.join(image_chromeos_args)) 282 retval = 0 283 if not test_flag.GetTestMode(): 284 retval = image_chromeos.DoImage(image_chromeos_args) 285 if retval: 286 cmd = 'reboot && exit' 287 if self.log_level != 'verbose': 288 self.logger.LogOutput('reboot & exit.') 289 self.ce.CrosRunCommand(cmd, 290 machine=machine.name, 291 chromeos_root=self.chromeos_root) 292 time.sleep(60) 293 if self.log_level != 'verbose': 294 self.logger.LogOutput('Pushing image onto machine.') 295 self.logger.LogOutput('Running image_chromeos.DoImage with %s' % 296 ' '.join(image_chromeos_args)) 297 retval = image_chromeos.DoImage(image_chromeos_args) 298 if retval: 299 raise Exception("Could not image machine: '%s'." % machine.name) 300 else: 301 self.num_reimages += 1 302 machine.checksum = checksum 303 machine.image = label.chromeos_image 304 machine.label = label 305 306 if not label.chrome_version: 307 label.chrome_version = self.GetChromeVersion(machine) 308 309 self.ce.log_level = save_ce_log_level 310 return retval 311 312 def ComputeCommonCheckSum(self, label): 313 # Since this is used for cache lookups before the machines have been 314 # compared/verified, check here to make sure they all have the same 315 # checksum (otherwise the cache lookup may not be valid). 316 common_checksum = None 317 for machine in self.GetMachines(label): 318 # Make sure the machine's checksums are calculated. 319 if not machine.machine_checksum: 320 machine.SetUpChecksumInfo() 321 cs = machine.machine_checksum 322 # If this is the first machine we've examined, initialize 323 # common_checksum. 324 if not common_checksum: 325 common_checksum = cs 326 # Make sure this machine's checksum matches our 'common' checksum. 327 if cs != common_checksum: 328 raise BadChecksum('Machine checksums do not match!') 329 self.machine_checksum[label.name] = common_checksum 330 331 def ComputeCommonCheckSumString(self, label): 332 # The assumption is that this function is only called AFTER 333 # ComputeCommonCheckSum, so there is no need to verify the machines 334 # are the same here. If this is ever changed, this function should be 335 # modified to verify that all the machines for a given label are the 336 # same. 337 for machine in self.GetMachines(label): 338 if machine.checksum_string: 339 self.machine_checksum_string[label.name] = machine.checksum_string 340 break 341 342 def _TryToLockMachine(self, cros_machine): 343 with self._lock: 344 assert cros_machine, "Machine can't be None" 345 for m in self._machines: 346 if m.name == cros_machine.name: 347 return 348 locked = True 349 if self.locks_dir: 350 locked = file_lock_machine.Machine(cros_machine.name, 351 self.locks_dir).Lock(True, 352 sys.argv[0]) 353 if locked: 354 self._machines.append(cros_machine) 355 command = 'cat %s' % CHECKSUM_FILE 356 ret, out, _ = self.ce.CrosRunCommandWOutput( 357 command, 358 chromeos_root=self.chromeos_root, 359 machine=cros_machine.name) 360 if ret == 0: 361 cros_machine.checksum = out.strip() 362 elif self.locks_dir: 363 self.logger.LogOutput("Couldn't lock: %s" % cros_machine.name) 364 365 # This is called from single threaded mode. 366 def AddMachine(self, machine_name): 367 with self._lock: 368 for m in self._all_machines: 369 assert m.name != machine_name, 'Tried to double-add %s' % machine_name 370 371 if self.log_level != 'verbose': 372 self.logger.LogOutput('Setting up remote access to %s' % machine_name) 373 self.logger.LogOutput('Checking machine characteristics for %s' % 374 machine_name) 375 cm = CrosMachine(machine_name, self.chromeos_root, self.log_level) 376 if cm.machine_checksum: 377 self._all_machines.append(cm) 378 379 def RemoveMachine(self, machine_name): 380 with self._lock: 381 self._machines = [m for m in self._machines if m.name != machine_name] 382 if self.locks_dir: 383 res = file_lock_machine.Machine(machine_name, 384 self.locks_dir).Unlock(True) 385 if not res: 386 self.logger.LogError("Could not unlock machine: '%s'." % machine_name) 387 388 def ForceSameImageToAllMachines(self, label): 389 machines = self.GetMachines(label) 390 for m in machines: 391 self.ImageMachine(m, label) 392 m.SetUpChecksumInfo() 393 394 def AcquireMachine(self, label): 395 image_checksum = label.checksum 396 machines = self.GetMachines(label) 397 check_interval_time = 120 398 with self._lock: 399 # Lazily external lock machines 400 while self.acquire_timeout >= 0: 401 for m in machines: 402 new_machine = m not in self._all_machines 403 self._TryToLockMachine(m) 404 if new_machine: 405 m.released_time = time.time() 406 if self.GetAvailableMachines(label): 407 break 408 else: 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 machine_names = [] 415 for machine in machines: 416 machine_names.append(machine.name) 417 self.logger.LogFatal('Could not acquire any of the ' 418 "following machines: '%s'" % 419 ', '.join(machine_names)) 420 421### for m in self._machines: 422### if (m.locked and time.time() - m.released_time < 10 and 423### m.checksum == image_checksum): 424### return None 425 for m in [machine 426 for machine in self.GetAvailableMachines(label) 427 if not machine.locked]: 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 [machine 433 for machine in self.GetAvailableMachines(label) 434 if not machine.locked]: 435 if not m.checksum: 436 m.locked = True 437 m.test_run = threading.current_thread() 438 return m 439 # This logic ensures that threads waiting on a machine will get a machine 440 # with a checksum equal to their image over other threads. This saves time 441 # when crosperf initially assigns the machines to threads by minimizing 442 # the number of re-images. 443 # TODO(asharif): If we centralize the thread-scheduler, we wont need this 444 # code and can implement minimal reimaging code more cleanly. 445 for m in [machine 446 for machine in self.GetAvailableMachines(label) 447 if not machine.locked]: 448 if time.time() - m.released_time > 15: 449 # The release time gap is too large, so it is probably in the start 450 # stage, we need to reset the released_time. 451 m.released_time = time.time() 452 elif time.time() - m.released_time > 8: 453 m.locked = True 454 m.test_run = threading.current_thread() 455 return m 456 return None 457 458 def GetAvailableMachines(self, label=None): 459 if not label: 460 return self._machines 461 return [m for m in self._machines if m.name in label.remote] 462 463 def GetMachines(self, label=None): 464 if not label: 465 return self._all_machines 466 return [m for m in self._all_machines if m.name in label.remote] 467 468 def ReleaseMachine(self, machine): 469 with self._lock: 470 for m in self._machines: 471 if machine.name == m.name: 472 assert m.locked == True, 'Tried to double-release %s' % m.name 473 m.released_time = time.time() 474 m.locked = False 475 m.status = 'Available' 476 break 477 478 def Cleanup(self): 479 with self._lock: 480 # Unlock all machines (via file lock) 481 for m in self._machines: 482 res = file_lock_machine.Machine(m.name, self.locks_dir).Unlock(True) 483 484 if not res: 485 self.logger.LogError("Could not unlock machine: '%s'." % m.name) 486 487 def __str__(self): 488 with self._lock: 489 l = ['MachineManager Status:'] 490 for m in self._machines: 491 l.append(str(m)) 492 return '\n'.join(l) 493 494 def AsString(self): 495 with self._lock: 496 stringify_fmt = '%-30s %-10s %-4s %-25s %-32s' 497 header = stringify_fmt % ('Machine', 'Thread', 'Lock', 'Status', 498 'Checksum') 499 table = [header] 500 for m in self._machines: 501 if m.test_run: 502 test_name = m.test_run.name 503 test_status = m.test_run.timeline.GetLastEvent() 504 else: 505 test_name = '' 506 test_status = '' 507 508 try: 509 machine_string = stringify_fmt % (m.name, test_name, m.locked, 510 test_status, m.checksum) 511 except ValueError: 512 machine_string = '' 513 table.append(machine_string) 514 return 'Machine Status:\n%s' % '\n'.join(table) 515 516 def GetAllCPUInfo(self, labels): 517 """Get cpuinfo for labels, merge them if their cpuinfo are the same.""" 518 dic = {} 519 for label in labels: 520 for machine in self._all_machines: 521 if machine.name in label.remote: 522 if machine.cpuinfo not in dic: 523 dic[machine.cpuinfo] = [label.name] 524 else: 525 dic[machine.cpuinfo].append(label.name) 526 break 527 output = '' 528 for key, v in dic.items(): 529 output += ' '.join(v) 530 output += '\n-------------------\n' 531 output += key 532 output += '\n\n\n' 533 return output 534 535 def GetAllMachines(self): 536 return self._all_machines 537 538 539class MockCrosMachine(CrosMachine): 540 """Mock cros machine class.""" 541 # pylint: disable=super-init-not-called 542 543 MEMINFO_STRING = """MemTotal: 3990332 kB 544MemFree: 2608396 kB 545Buffers: 147168 kB 546Cached: 811560 kB 547SwapCached: 0 kB 548Active: 503480 kB 549Inactive: 628572 kB 550Active(anon): 174532 kB 551Inactive(anon): 88576 kB 552Active(file): 328948 kB 553Inactive(file): 539996 kB 554Unevictable: 0 kB 555Mlocked: 0 kB 556SwapTotal: 5845212 kB 557SwapFree: 5845212 kB 558Dirty: 9384 kB 559Writeback: 0 kB 560AnonPages: 173408 kB 561Mapped: 146268 kB 562Shmem: 89676 kB 563Slab: 188260 kB 564SReclaimable: 169208 kB 565SUnreclaim: 19052 kB 566KernelStack: 2032 kB 567PageTables: 7120 kB 568NFS_Unstable: 0 kB 569Bounce: 0 kB 570WritebackTmp: 0 kB 571CommitLimit: 7840376 kB 572Committed_AS: 1082032 kB 573VmallocTotal: 34359738367 kB 574VmallocUsed: 364980 kB 575VmallocChunk: 34359369407 kB 576DirectMap4k: 45824 kB 577DirectMap2M: 4096000 kB 578""" 579 580 CPUINFO_STRING = """processor: 0 581vendor_id: GenuineIntel 582cpu family: 6 583model: 42 584model name: Intel(R) Celeron(R) CPU 867 @ 1.30GHz 585stepping: 7 586microcode: 0x25 587cpu MHz: 1300.000 588cache size: 2048 KB 589physical id: 0 590siblings: 2 591core id: 0 592cpu cores: 2 593apicid: 0 594initial apicid: 0 595fpu: yes 596fpu_exception: yes 597cpuid level: 13 598wp: yes 599flags: 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 600bogomips: 2594.17 601clflush size: 64 602cache_alignment: 64 603address sizes: 36 bits physical, 48 bits virtual 604power management: 605 606processor: 1 607vendor_id: GenuineIntel 608cpu family: 6 609model: 42 610model name: Intel(R) Celeron(R) CPU 867 @ 1.30GHz 611stepping: 7 612microcode: 0x25 613cpu MHz: 1300.000 614cache size: 2048 KB 615physical id: 0 616siblings: 2 617core id: 1 618cpu cores: 2 619apicid: 2 620initial apicid: 2 621fpu: yes 622fpu_exception: yes 623cpuid level: 13 624wp: yes 625flags: 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 626bogomips: 2594.17 627clflush size: 64 628cache_alignment: 64 629address sizes: 36 bits physical, 48 bits virtual 630power management: 631""" 632 633 def __init__(self, name, chromeos_root, log_level): 634 self.name = name 635 self.image = None 636 self.checksum = None 637 self.locked = False 638 self.released_time = time.time() 639 self.test_run = None 640 self.chromeos_root = chromeos_root 641 self.checksum_string = re.sub(r'\d', '', name) 642 #In test, we assume "lumpy1", "lumpy2" are the same machine. 643 self.machine_checksum = self._GetMD5Checksum(self.checksum_string) 644 self.log_level = log_level 645 self.label = None 646 self.ce = command_executer.GetCommandExecuter(log_level=self.log_level) 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