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