blob: 8588ebfd555b824792da2598eaf9095752518ba6 [file] [log] [blame]
Zhizhou Yang5534af82020-01-15 16:25:04 -08001#!/usr/bin/env python3
Zhizhou Yangcdd9e342019-09-19 20:56:32 -07002# -*- coding: utf-8 -*-
Zhizhou Yang4713fd12019-09-24 10:32:00 -07003#
Zhizhou Yangcdd9e342019-09-19 20:56:32 -07004# Copyright 2019 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
Caroline Ticea4486452015-12-08 13:43:23 -08008"""This module controls locking and unlocking of test machines."""
9
10from __future__ import print_function
11
cmticee5bc63b2015-05-27 16:59:37 -070012import argparse
Zhizhou Yang5322d4a2019-09-30 13:10:29 -070013import enum
cmticee5bc63b2015-05-27 16:59:37 -070014import getpass
15import os
16import sys
cmticee5bc63b2015-05-27 16:59:37 -070017
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070018import file_lock_machine
19
20from cros_utils import command_executer
Caroline Ticea8af9a72016-07-20 12:52:59 -070021from cros_utils import logger
22from cros_utils import machines
cmticee5bc63b2015-05-27 16:59:37 -070023
Luis Lozanof2a3ef42015-12-15 13:49:30 -080024
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070025class LockException(Exception):
cmticee5bc63b2015-05-27 16:59:37 -070026 """Base class for exceptions in this module."""
27
28
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070029class MachineNotPingable(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070030 """Raised when machine does not respond to ping."""
31
32
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070033class LockingError(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070034 """Raised when server fails to lock/unlock machine as requested."""
35
36
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070037class DontOwnLock(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070038 """Raised when user attmepts to unlock machine locked by someone else."""
39 # This should not be raised if the user specified '--force'
40
41
Zhizhou Yang5322d4a2019-09-30 13:10:29 -070042class MachineType(enum.Enum):
43 """Enum class to hold machine type."""
Zhizhou Yang5322d4a2019-09-30 13:10:29 -070044 LOCAL = 'local'
45 SKYLAB = 'skylab'
46
47
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070048class LockManager(object):
49 """Class for locking/unlocking machines vie three different modes.
cmticee5bc63b2015-05-27 16:59:37 -070050
Zhizhou Yang4713fd12019-09-24 10:32:00 -070051 This class contains methods for checking the locked status of machines,
Zhizhou Yang0ee6a572020-01-22 15:55:19 -080052 and for changing the locked status. It handles HW lab machines and local
53 machines, using appropriate locking mechanisms for each.
cmticee5bc63b2015-05-27 16:59:37 -070054 """
55
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070056 SKYLAB_PATH = '/usr/local/bin/skylab'
Zhizhou Yang13446a72019-10-29 16:50:14 -070057
58 # TODO(zhizhouy): lease time may needs to be dynamically adjusted. For now we
59 # set it long enough to cover the period to finish nightly rotation tests.
60 LEASE_MINS = 1440
61
Zhizhou Yang8477fef2020-02-12 16:21:36 -080062 SKYLAB_CREDENTIAL = ('/usr/local/google/home/mobiletc-prebuild'
63 '/sheriff_utils/credentials/skylab'
64 '/chromeos-swarming-credential.json')
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070065 SWARMING = 'chromite/third_party/swarming.client/swarming.py'
66 SUCCESS = 0
cmticee5bc63b2015-05-27 16:59:37 -070067
Luis Lozanof2a3ef42015-12-15 13:49:30 -080068 def __init__(self,
69 remotes,
70 force_option,
71 chromeos_root,
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070072 locks_dir='',
Luis Lozanof2a3ef42015-12-15 13:49:30 -080073 log=None):
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070074 """Initializes an LockManager object.
cmticee5bc63b2015-05-27 16:59:37 -070075
76 Args:
77 remotes: A list of machine names or ip addresses to be managed. Names
Caroline Ticea4486452015-12-08 13:43:23 -080078 and ip addresses should be represented as strings. If the list is
79 empty, the lock manager will get all known machines.
80 force_option: A Boolean indicating whether or not to force an unlock of
cmticee5bc63b2015-05-27 16:59:37 -070081 a machine that was locked by someone else.
82 chromeos_root: The ChromeOS chroot to use for the autotest scripts.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070083 locks_dir: A directory used for file locking local devices.
cmticee5bc63b2015-05-27 16:59:37 -070084 log: If not None, this is the logger object to be used for writing out
85 informational output messages. It is expected to be an instance of
Caroline Ticea8af9a72016-07-20 12:52:59 -070086 Logger class from cros_utils/logger.py.
cmticee5bc63b2015-05-27 16:59:37 -070087 """
88 self.chromeos_root = chromeos_root
89 self.user = getpass.getuser()
90 self.logger = log or logger.GetLogger()
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070091 self.ce = command_executer.GetCommandExecuter(self.logger)
cmticee5bc63b2015-05-27 16:59:37 -070092
cmticed1172b42015-06-12 15:14:09 -070093 sys.path.append(chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -070094
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070095 self.locks_dir = locks_dir
96
Caroline Tice6b161382016-09-15 15:03:46 -070097 self.machines = list(set(remotes)) or []
98 self.toolchain_lab_machines = self.GetAllToolchainLabMachines()
Caroline Tice6b161382016-09-15 15:03:46 -070099
cmticee5bc63b2015-05-27 16:59:37 -0700100 if not self.machines:
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700101 self.machines = self.toolchain_lab_machines
Caroline Tice6b161382016-09-15 15:03:46 -0700102 self.force = force_option
103
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700104 self.local_machines = []
105 self.skylab_machines = []
106
cmticee5bc63b2015-05-27 16:59:37 -0700107 def CheckMachine(self, machine, error_msg):
108 """Verifies that machine is responding to ping.
109
110 Args:
111 machine: String containing the name or ip address of machine to check.
112 error_msg: Message to print if ping fails.
113
114 Raises:
115 MachineNotPingable: If machine is not responding to 'ping'
116 """
117 if not machines.MachineIsPingable(machine, logging_level='none'):
Caroline Ticea4486452015-12-08 13:43:23 -0800118 cros_machine = machine + '.cros'
119 if not machines.MachineIsPingable(cros_machine, logging_level='none'):
120 raise MachineNotPingable(error_msg)
cmticee5bc63b2015-05-27 16:59:37 -0700121
cmticee5bc63b2015-05-27 16:59:37 -0700122 def GetAllToolchainLabMachines(self):
123 """Gets a list of all the toolchain machines in the ChromeOS HW lab.
124
125 Returns:
126 A list of names of the toolchain machines in the ChromeOS HW lab.
127 """
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800128 machines_file = os.path.join(
129 os.path.dirname(__file__), 'crosperf', 'default_remotes')
cmticee5bc63b2015-05-27 16:59:37 -0700130 machine_list = []
131 with open(machines_file, 'r') as input_file:
132 lines = input_file.readlines()
133 for line in lines:
Caroline Ticea4486452015-12-08 13:43:23 -0800134 _, remotes = line.split(':')
cmticee5bc63b2015-05-27 16:59:37 -0700135 remotes = remotes.strip()
136 for r in remotes.split():
137 machine_list.append(r.strip())
138 return machine_list
139
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700140 def GetMachineType(self, m):
141 """Get where the machine is located.
cmticee5bc63b2015-05-27 16:59:37 -0700142
Caroline Ticea4486452015-12-08 13:43:23 -0800143 Args:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700144 m: String containing the name or ip address of machine.
145
146 Returns:
147 Value of the type in MachineType Enum.
cmticee5bc63b2015-05-27 16:59:37 -0700148 """
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700149 if m in self.local_machines:
150 return MachineType.LOCAL
151 if m in self.skylab_machines:
152 return MachineType.SKYLAB
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700153
154 def PrintStatusHeader(self):
155 """Prints the status header lines for machines."""
156 print('\nMachine (Board)\t\t\t\t\tStatus')
157 print('---------------\t\t\t\t\t------')
158
159 def PrintStatus(self, m, state, machine_type):
160 """Prints status for a single machine.
161
162 Args:
163 m: String containing the name or ip address of machine.
164 state: A dictionary of the current state of the machine.
165 machine_type: MachineType to determine where the machine is located.
166 """
167 if state['locked']:
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700168 print('%s (%s)\t\t%slocked by %s since %s' %
169 (m, state['board'], '\t\t' if machine_type == MachineType.LOCAL else
170 '', state['locked_by'], state['lock_time']))
cmticee5bc63b2015-05-27 16:59:37 -0700171 else:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700172 print(
173 '%s (%s)\t\t%sunlocked' % (m, state['board'], '\t\t' if
174 machine_type == MachineType.LOCAL else ''))
cmticee5bc63b2015-05-27 16:59:37 -0700175
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700176 def AddMachineToLocal(self, machine):
177 """Adds a machine to local machine list.
178
179 Args:
180 machine: The machine to be added.
181 """
182 if machine not in self.local_machines:
183 self.local_machines.append(machine)
184
185 def AddMachineToSkylab(self, machine):
186 """Adds a machine to skylab machine list.
187
188 Args:
189 machine: The machine to be added.
190 """
191 if machine not in self.skylab_machines:
192 self.skylab_machines.append(machine)
193
cmticee5bc63b2015-05-27 16:59:37 -0700194 def ListMachineStates(self, machine_states):
195 """Gets and prints the current status for a list of machines.
196
197 Prints out the current status for all of the machines in the current
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700198 LockManager's list of machines (set when the object is initialized).
cmticee5bc63b2015-05-27 16:59:37 -0700199
200 Args:
201 machine_states: A dictionary of the current state of every machine in
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700202 the current LockManager's list of machines. Normally obtained by
203 calling LockManager::GetMachineStates.
cmticee5bc63b2015-05-27 16:59:37 -0700204 """
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700205 self.PrintStatusHeader()
cmticee5bc63b2015-05-27 16:59:37 -0700206 for m in machine_states:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700207 machine_type = self.GetMachineType(m)
208 state = machine_states[m]
209 self.PrintStatus(m, state, machine_type)
cmticee5bc63b2015-05-27 16:59:37 -0700210
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700211 def UpdateLockInSkylab(self, should_lock_machine, machine):
212 """Ask skylab to lease/release a machine.
213
214 Args:
215 should_lock_machine: Boolean indicating whether to lock the machine (True)
216 or unlock the machine (False).
217 machine: The machine to update.
218
219 Returns:
220 True if requested action succeeded, else False.
221 """
222 try:
223 if should_lock_machine:
224 ret = self.LeaseSkylabMachine(machine)
225 else:
226 ret = self.ReleaseSkylabMachine(machine)
227 except Exception:
228 return False
229 return ret
230
231 def UpdateFileLock(self, should_lock_machine, machine):
232 """Use file lock for local machines,
233
234 Args:
235 should_lock_machine: Boolean indicating whether to lock the machine (True)
236 or unlock the machine (False).
237 machine: The machine to update.
238
239 Returns:
240 True if requested action succeeded, else False.
241 """
242 try:
243 if should_lock_machine:
244 ret = file_lock_machine.Machine(machine, self.locks_dir).Lock(
245 True, sys.argv[0])
246 else:
247 ret = file_lock_machine.Machine(machine, self.locks_dir).Unlock(True)
248 except Exception:
249 return False
250 return ret
cmticee5bc63b2015-05-27 16:59:37 -0700251
252 def UpdateMachines(self, lock_machines):
253 """Sets the locked state of the machines to the requested value.
254
255 The machines updated are the ones in self.machines (specified when the
256 class object was intialized).
257
258 Args:
Caroline Ticea4486452015-12-08 13:43:23 -0800259 lock_machines: Boolean indicating whether to lock the machines (True) or
cmticee5bc63b2015-05-27 16:59:37 -0700260 unlock the machines (False).
cmticef3eb8032015-07-27 13:55:52 -0700261
262 Returns:
263 A list of the machines whose state was successfully updated.
cmticee5bc63b2015-05-27 16:59:37 -0700264 """
cmticef3eb8032015-07-27 13:55:52 -0700265 updated_machines = []
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700266 action = 'Locking' if lock_machines else 'Unlocking'
cmticee5bc63b2015-05-27 16:59:37 -0700267 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700268 # TODO(zhizhouy): Handling exceptions with more details when locking
269 # doesn't succeed.
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700270 machine_type = self.GetMachineType(m)
271 if machine_type == MachineType.SKYLAB:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700272 ret = self.UpdateLockInSkylab(lock_machines, m)
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700273 elif machine_type == MachineType.LOCAL:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700274 ret = self.UpdateFileLock(lock_machines, m)
cmticef3eb8032015-07-27 13:55:52 -0700275
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700276 if ret:
277 self.logger.LogOutput(
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700278 '%s %s machine succeeded: %s.' % (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700279 updated_machines.append(m)
280 else:
281 self.logger.LogOutput(
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700282 '%s %s machine failed: %s.' % (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700283
284 self.machines = updated_machines
cmticef3eb8032015-07-27 13:55:52 -0700285 return updated_machines
286
287 def _InternalRemoveMachine(self, machine):
288 """Remove machine from internal list of machines.
289
290 Args:
291 machine: Name of machine to be removed from internal list.
292 """
293 # Check to see if machine is lab machine and if so, make sure it has
294 # ".cros" on the end.
295 cros_machine = machine
296 if machine.find('rack') > 0 and machine.find('row') > 0:
297 if machine.find('.cros') == -1:
298 cros_machine = cros_machine + '.cros'
299
Caroline Ticef6ef4392017-04-06 17:16:05 -0700300 self.machines = [
Zhizhou Yang5534af82020-01-15 16:25:04 -0800301 m for m in self.machines if m not in (cros_machine, machine)
Caroline Ticef6ef4392017-04-06 17:16:05 -0700302 ]
cmticee5bc63b2015-05-27 16:59:37 -0700303
304 def CheckMachineLocks(self, machine_states, cmd):
305 """Check that every machine in requested list is in the proper state.
306
307 If the cmd is 'unlock' verify that every machine is locked by requestor.
308 If the cmd is 'lock' verify that every machine is currently unlocked.
309
310 Args:
311 machine_states: A dictionary of the current state of every machine in
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700312 the current LockManager's list of machines. Normally obtained by
313 calling LockManager::GetMachineStates.
Caroline Ticea4486452015-12-08 13:43:23 -0800314 cmd: The user-requested action for the machines: 'lock' or 'unlock'.
cmticee5bc63b2015-05-27 16:59:37 -0700315
316 Raises:
cmticee5bc63b2015-05-27 16:59:37 -0700317 DontOwnLock: The lock on a requested machine is owned by someone else.
318 """
Zhizhou Yang5534af82020-01-15 16:25:04 -0800319 for k, state in machine_states.items():
cmticee5bc63b2015-05-27 16:59:37 -0700320 if cmd == 'unlock':
321 if not state['locked']:
cmticef3eb8032015-07-27 13:55:52 -0700322 self.logger.LogWarning('Attempt to unlock already unlocked machine '
323 '(%s).' % k)
324 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700325
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700326 # TODO(zhizhouy): Skylab doesn't support host info such as locked_by.
327 # Need to update this when skylab supports it.
328 if (state['locked'] and state['locked_by'] and
329 state['locked_by'] != self.user):
cmticee5bc63b2015-05-27 16:59:37 -0700330 raise DontOwnLock('Attempt to unlock machine (%s) locked by someone '
331 'else (%s).' % (k, state['locked_by']))
332 elif cmd == 'lock':
333 if state['locked']:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700334 self.logger.LogWarning(
335 'Attempt to lock already locked machine (%s)' % k)
cmticef3eb8032015-07-27 13:55:52 -0700336 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700337
cmticee5bc63b2015-05-27 16:59:37 -0700338 def GetMachineStates(self, cmd=''):
339 """Gets the current state of all the requested machines.
340
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700341 Gets the current state of all the requested machines. Stores the data in a
342 dictionary keyed by machine name.
cmticee5bc63b2015-05-27 16:59:37 -0700343
344 Args:
345 cmd: The command for which we are getting the machine states. This is
346 important because if one of the requested machines is missing we raise
347 an exception, unless the requested command is 'add'.
348
349 Returns:
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700350 A dictionary of machine states for all the machines in the LockManager
cmticee5bc63b2015-05-27 16:59:37 -0700351 object.
cmticee5bc63b2015-05-27 16:59:37 -0700352 """
Caroline Ticea4486452015-12-08 13:43:23 -0800353 machine_list = {}
cmticee5bc63b2015-05-27 16:59:37 -0700354 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700355 # For local or skylab machines, we simply set {'locked': status} for them
356 # TODO(zhizhouy): This is a quick fix since skylab cannot return host info
357 # as afe does. We need to get more info such as locked_by when skylab
358 # supports that.
Zhizhou Yang0ee6a572020-01-22 15:55:19 -0800359 values = {
360 'locked': 0 if cmd == 'lock' else 1,
361 'board': '??',
362 'locked_by': '',
363 'lock_time': ''
364 }
365 machine_list[m] = values
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700366
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700367 self.ListMachineStates(machine_list)
368
Caroline Ticea4486452015-12-08 13:43:23 -0800369 return machine_list
cmticee5bc63b2015-05-27 16:59:37 -0700370
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700371 def CheckMachineInSkylab(self, machine):
372 """Run command to check if machine is in Skylab or not.
373
374 Returns:
375 True if machine in skylab, else False
376 """
377 credential = ''
378 if os.path.exists(self.SKYLAB_CREDENTIAL):
379 credential = '--auth-service-account-json %s' % self.SKYLAB_CREDENTIAL
380 swarming = os.path.join(self.chromeos_root, self.SWARMING)
zhizhouy9258b052020-04-15 17:33:46 -0700381 # TODO(zhizhouy): Swarming script doesn't support python3 so explicitly
382 # launch it with python2 until migrated.
383 cmd = (('python2 %s ' \
384 'query --swarming https://chromeos-swarming.appspot.com ' \
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700385 "%s 'bots/list?is_dead=FALSE&dimensions=dut_name:%s'") % \
386 (swarming,
387 credential,
388 machine.rstrip('.cros')))
389 ret_tup = self.ce.RunCommandWOutput(cmd)
390 # The command will return a json output as stdout. If machine not in skylab
391 # stdout will look like this:
392 # {
393 # "death_timeout": "600",
394 # "now": "TIMESTAMP"
395 # }
396 # Otherwise there will be a tuple starting with 'items', we simply detect
397 # this keyword for result.
398 if 'items' not in ret_tup[1]:
399 return False
400 else:
401 return True
402
403 def LeaseSkylabMachine(self, machine):
404 """Run command to lease dut from skylab.
405
406 Returns:
407 True if succeeded, False if failed.
408 """
409 credential = ''
410 if os.path.exists(self.SKYLAB_CREDENTIAL):
411 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
412 cmd = (('%s lease-dut -minutes %s %s %s') % \
413 (self.SKYLAB_PATH,
414 self.LEASE_MINS,
415 credential,
416 machine.rstrip('.cros')))
417 # Wait 120 seconds for server to start the lease task, if not started,
418 # we will treat it as unavailable.
419 check_interval_time = 120
420 retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time)
421 return retval == self.SUCCESS
422
423 def ReleaseSkylabMachine(self, machine):
424 """Run command to release dut from skylab.
425
426 Returns:
427 True if succeeded, False if failed.
428 """
429 credential = ''
430 if os.path.exists(self.SKYLAB_CREDENTIAL):
431 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
432 cmd = (('%s release-dut %s %s') % \
433 (self.SKYLAB_PATH,
434 credential,
435 machine.rstrip('.cros')))
436 retval = self.ce.RunCommand(cmd)
437 return retval == self.SUCCESS
438
cmticee5bc63b2015-05-27 16:59:37 -0700439
440def Main(argv):
Caroline Ticea4486452015-12-08 13:43:23 -0800441 """Parse the options, initialize lock manager and dispatch proper method.
cmticee5bc63b2015-05-27 16:59:37 -0700442
Caroline Ticea4486452015-12-08 13:43:23 -0800443 Args:
444 argv: The options with which this script was invoked.
cmticee5bc63b2015-05-27 16:59:37 -0700445
Caroline Ticea4486452015-12-08 13:43:23 -0800446 Returns:
447 0 unless an exception is raised.
448 """
449 parser = argparse.ArgumentParser()
cmticee5bc63b2015-05-27 16:59:37 -0700450
Caroline Tice6b161382016-09-15 15:03:46 -0700451 parser.add_argument(
452 '--list',
453 dest='cmd',
454 action='store_const',
455 const='status',
456 help='List current status of all known machines.')
457 parser.add_argument(
458 '--lock',
459 dest='cmd',
460 action='store_const',
461 const='lock',
462 help='Lock given machine(s).')
463 parser.add_argument(
464 '--unlock',
465 dest='cmd',
466 action='store_const',
467 const='unlock',
468 help='Unlock given machine(s).')
469 parser.add_argument(
470 '--status',
471 dest='cmd',
472 action='store_const',
473 const='status',
474 help='List current status of given machine(s).')
475 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700476 '--remote', dest='remote', help='machines on which to operate')
477 parser.add_argument(
478 '--chromeos_root',
479 dest='chromeos_root',
480 required=True,
481 help='ChromeOS root to use for autotest scripts.')
482 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700483 '--force',
484 dest='force',
485 action='store_true',
486 default=False,
487 help='Force lock/unlock of machines, even if not'
488 ' current lock owner.')
cmticee5bc63b2015-05-27 16:59:37 -0700489
Caroline Ticea4486452015-12-08 13:43:23 -0800490 options = parser.parse_args(argv)
cmticee5bc63b2015-05-27 16:59:37 -0700491
Caroline Ticea4486452015-12-08 13:43:23 -0800492 if not options.remote and options.cmd != 'status':
493 parser.error('No machines specified for operation.')
cmticee5bc63b2015-05-27 16:59:37 -0700494
Caroline Ticea4486452015-12-08 13:43:23 -0800495 if not os.path.isdir(options.chromeos_root):
496 parser.error('Cannot find chromeos_root: %s.' % options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700497
Caroline Ticea4486452015-12-08 13:43:23 -0800498 if not options.cmd:
499 parser.error('No operation selected (--list, --status, --lock, --unlock,'
500 ' --add_machine, --remove_machine).')
cmticee5bc63b2015-05-27 16:59:37 -0700501
Caroline Ticea4486452015-12-08 13:43:23 -0800502 machine_list = []
503 if options.remote:
504 machine_list = options.remote.split()
cmticee5bc63b2015-05-27 16:59:37 -0700505
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700506 lock_manager = LockManager(machine_list, options.force, options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700507
Caroline Ticea4486452015-12-08 13:43:23 -0800508 machine_states = lock_manager.GetMachineStates(cmd=options.cmd)
509 cmd = options.cmd
cmticee5bc63b2015-05-27 16:59:37 -0700510
Caroline Ticea4486452015-12-08 13:43:23 -0800511 if cmd == 'status':
512 lock_manager.ListMachineStates(machine_states)
cmticee5bc63b2015-05-27 16:59:37 -0700513
Caroline Ticea4486452015-12-08 13:43:23 -0800514 elif cmd == 'lock':
515 if not lock_manager.force:
516 lock_manager.CheckMachineLocks(machine_states, cmd)
517 lock_manager.UpdateMachines(True)
cmticee5bc63b2015-05-27 16:59:37 -0700518
Caroline Ticea4486452015-12-08 13:43:23 -0800519 elif cmd == 'unlock':
520 if not lock_manager.force:
521 lock_manager.CheckMachineLocks(machine_states, cmd)
522 lock_manager.UpdateMachines(False)
cmticee5bc63b2015-05-27 16:59:37 -0700523
Caroline Ticea4486452015-12-08 13:43:23 -0800524 return 0
cmticee5bc63b2015-05-27 16:59:37 -0700525
526
527if __name__ == '__main__':
Caroline Ticea4486452015-12-08 13:43:23 -0800528 sys.exit(Main(sys.argv[1:]))