machine_manager.py revision a93683485612c985c661eb4ebbb02a7ae1c4a623
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
536class MockCrosMachine(CrosMachine):
537  """Mock cros machine class."""
538  # pylint: disable=super-init-not-called
539
540  MEMINFO_STRING = """MemTotal:        3990332 kB
541MemFree:         2608396 kB
542Buffers:          147168 kB
543Cached:           811560 kB
544SwapCached:            0 kB
545Active:           503480 kB
546Inactive:         628572 kB
547Active(anon):     174532 kB
548Inactive(anon):    88576 kB
549Active(file):     328948 kB
550Inactive(file):   539996 kB
551Unevictable:           0 kB
552Mlocked:               0 kB
553SwapTotal:       5845212 kB
554SwapFree:        5845212 kB
555Dirty:              9384 kB
556Writeback:             0 kB
557AnonPages:        173408 kB
558Mapped:           146268 kB
559Shmem:             89676 kB
560Slab:             188260 kB
561SReclaimable:     169208 kB
562SUnreclaim:        19052 kB
563KernelStack:        2032 kB
564PageTables:         7120 kB
565NFS_Unstable:          0 kB
566Bounce:                0 kB
567WritebackTmp:          0 kB
568CommitLimit:     7840376 kB
569Committed_AS:    1082032 kB
570VmallocTotal:   34359738367 kB
571VmallocUsed:      364980 kB
572VmallocChunk:   34359369407 kB
573DirectMap4k:       45824 kB
574DirectMap2M:     4096000 kB
575"""
576
577  CPUINFO_STRING = """processor: 0
578vendor_id: GenuineIntel
579cpu family: 6
580model: 42
581model name: Intel(R) Celeron(R) CPU 867 @ 1.30GHz
582stepping: 7
583microcode: 0x25
584cpu MHz: 1300.000
585cache size: 2048 KB
586physical id: 0
587siblings: 2
588core id: 0
589cpu cores: 2
590apicid: 0
591initial apicid: 0
592fpu: yes
593fpu_exception: yes
594cpuid level: 13
595wp: yes
596flags: 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
597bogomips: 2594.17
598clflush size: 64
599cache_alignment: 64
600address sizes: 36 bits physical, 48 bits virtual
601power management:
602
603processor: 1
604vendor_id: GenuineIntel
605cpu family: 6
606model: 42
607model name: Intel(R) Celeron(R) CPU 867 @ 1.30GHz
608stepping: 7
609microcode: 0x25
610cpu MHz: 1300.000
611cache size: 2048 KB
612physical id: 0
613siblings: 2
614core id: 1
615cpu cores: 2
616apicid: 2
617initial apicid: 2
618fpu: yes
619fpu_exception: yes
620cpuid level: 13
621wp: yes
622flags: 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
623bogomips: 2594.17
624clflush size: 64
625cache_alignment: 64
626address sizes: 36 bits physical, 48 bits virtual
627power management:
628"""
629
630  def __init__(self, name, chromeos_root, log_level):
631    self.name = name
632    self.image = None
633    self.checksum = None
634    self.locked = False
635    self.released_time = time.time()
636    self.test_run = None
637    self.chromeos_root = chromeos_root
638    self.checksum_string = re.sub(r'\d', '', name)
639    #In test, we assume "lumpy1", "lumpy2" are the same machine.
640    self.machine_checksum = self._GetMD5Checksum(self.checksum_string)
641    self.log_level = log_level
642    self.label = None
643    self.ce = command_executer.GetCommandExecuter(log_level=self.log_level)
644
645  def IsReachable(self):
646    return True
647
648  def _GetMemoryInfo(self):
649    self.meminfo = self.MEMINFO_STRING
650    self._ParseMemoryInfo()
651
652  def _GetCPUInfo(self):
653    self.cpuinfo = self.CPUINFO_STRING
654
655
656class MockMachineManager(MachineManager):
657  """Mock machine manager class."""
658
659  def __init__(self, chromeos_root, acquire_timeout, log_level, locks_dir):
660    super(MockMachineManager, self).__init__(
661        chromeos_root, acquire_timeout, log_level, locks_dir)
662
663  def _TryToLockMachine(self, cros_machine):
664    self._machines.append(cros_machine)
665    cros_machine.checksum = ''
666
667  def AddMachine(self, machine_name):
668    with self._lock:
669      for m in self._all_machines:
670        assert m.name != machine_name, 'Tried to double-add %s' % machine_name
671      cm = MockCrosMachine(machine_name, self.chromeos_root, self.log_level)
672      assert cm.machine_checksum, ('Could not find checksum for machine %s' %
673                                   machine_name)
674      # In Original MachineManager, the test is 'if cm.machine_checksum:' - if a
675      # machine is unreachable, then its machine_checksum is None. Here we
676      # cannot do this, because machine_checksum is always faked, so we directly
677      # test cm.IsReachable, which is properly mocked.
678      if cm.IsReachable():
679        self._all_machines.append(cm)
680
681  def GetChromeVersion(self, machine):
682    return 'Mock Chrome Version R50'
683
684  def AcquireMachine(self, label):
685    for machine in self._all_machines:
686      if not machine.locked:
687        machine.locked = True
688        return machine
689    return None
690
691  def ImageMachine(self, machine_name, label):
692    if machine_name or label:
693      return 0
694    return 1
695
696  def ReleaseMachine(self, machine):
697    machine.locked = False
698
699  def GetMachines(self, label=None):
700    return self._all_machines
701
702  def GetAvailableMachines(self, label=None):
703    return self._all_machines
704
705  def ForceSameImageToAllMachines(self, label=None):
706    return 0
707
708  def ComputeCommonCheckSum(self, label=None):
709    common_checksum = 12345
710    for machine in self.GetMachines(label):
711      machine.machine_checksum = common_checksum
712    self.machine_checksum[label.name] = common_checksum
713