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