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