1*760c253cSXin Li#!/usr/bin/env python3 2*760c253cSXin Li# -*- coding: utf-8 -*- 3*760c253cSXin Li# 4*760c253cSXin Li# Copyright 2019 The ChromiumOS Authors 5*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be 6*760c253cSXin Li# found in the LICENSE file. 7*760c253cSXin Li 8*760c253cSXin Li"""This module controls locking and unlocking of test machines.""" 9*760c253cSXin Li 10*760c253cSXin Li 11*760c253cSXin Liimport argparse 12*760c253cSXin Liimport enum 13*760c253cSXin Liimport getpass 14*760c253cSXin Liimport os 15*760c253cSXin Liimport sys 16*760c253cSXin Li 17*760c253cSXin Lifrom cros_utils import command_executer 18*760c253cSXin Lifrom cros_utils import logger 19*760c253cSXin Lifrom cros_utils import machines 20*760c253cSXin Liimport file_lock_machine 21*760c253cSXin Li 22*760c253cSXin Li 23*760c253cSXin Liclass LockException(Exception): 24*760c253cSXin Li """Base class for exceptions in this module.""" 25*760c253cSXin Li 26*760c253cSXin Li 27*760c253cSXin Liclass MachineNotPingable(LockException): 28*760c253cSXin Li """Raised when machine does not respond to ping.""" 29*760c253cSXin Li 30*760c253cSXin Li 31*760c253cSXin Liclass LockingError(LockException): 32*760c253cSXin Li """Raised when server fails to lock/unlock machine as requested.""" 33*760c253cSXin Li 34*760c253cSXin Li 35*760c253cSXin Liclass DontOwnLock(LockException): 36*760c253cSXin Li """Raised when user attmepts to unlock machine locked by someone else.""" 37*760c253cSXin Li 38*760c253cSXin Li # This should not be raised if the user specified '--force' 39*760c253cSXin Li 40*760c253cSXin Li 41*760c253cSXin Liclass MachineType(enum.Enum): 42*760c253cSXin Li """Enum class to hold machine type.""" 43*760c253cSXin Li 44*760c253cSXin Li LOCAL = "local" 45*760c253cSXin Li CROSFLEET = "crosfleet" 46*760c253cSXin Li 47*760c253cSXin Li 48*760c253cSXin Liclass LockManager(object): 49*760c253cSXin Li """Class for locking/unlocking machines vie three different modes. 50*760c253cSXin Li 51*760c253cSXin Li This class contains methods for checking the locked status of machines, 52*760c253cSXin Li and for changing the locked status. It handles HW lab machines and local 53*760c253cSXin Li machines, using appropriate locking mechanisms for each. 54*760c253cSXin Li """ 55*760c253cSXin Li 56*760c253cSXin Li CROSFLEET_PATH = "crosfleet" 57*760c253cSXin Li 58*760c253cSXin Li # TODO(zhizhouy): lease time may needs to be dynamically adjusted. For now we 59*760c253cSXin Li # set it long enough to cover the period to finish nightly rotation tests. 60*760c253cSXin Li LEASE_MINS = 1439 61*760c253cSXin Li 62*760c253cSXin Li CROSFLEET_CREDENTIAL = ( 63*760c253cSXin Li "/usr/local/google/home/mobiletc-prebuild" 64*760c253cSXin Li "/sheriff_utils/credentials/skylab" 65*760c253cSXin Li "/chromeos-swarming-credential.json" 66*760c253cSXin Li ) 67*760c253cSXin Li SWARMING = "~/cipd_binaries/swarming" 68*760c253cSXin Li SUCCESS = 0 69*760c253cSXin Li 70*760c253cSXin Li def __init__( 71*760c253cSXin Li self, remotes, force_option, chromeos_root, locks_dir="", log=None 72*760c253cSXin Li ): 73*760c253cSXin Li """Initializes an LockManager object. 74*760c253cSXin Li 75*760c253cSXin Li Args: 76*760c253cSXin Li remotes: A list of machine names or ip addresses to be managed. Names 77*760c253cSXin Li and ip addresses should be represented as strings. If the list is 78*760c253cSXin Li empty, the lock manager will get all known machines. 79*760c253cSXin Li force_option: A Boolean indicating whether or not to force an unlock of 80*760c253cSXin Li a machine that was locked by someone else. 81*760c253cSXin Li chromeos_root: The ChromeOS chroot to use for the autotest scripts. 82*760c253cSXin Li locks_dir: A directory used for file locking local devices. 83*760c253cSXin Li log: If not None, this is the logger object to be used for writing out 84*760c253cSXin Li informational output messages. It is expected to be an instance of 85*760c253cSXin Li Logger class from cros_utils/logger.py. 86*760c253cSXin Li """ 87*760c253cSXin Li self.chromeos_root = chromeos_root 88*760c253cSXin Li self.user = getpass.getuser() 89*760c253cSXin Li self.logger = log or logger.GetLogger() 90*760c253cSXin Li self.ce = command_executer.GetCommandExecuter(self.logger) 91*760c253cSXin Li 92*760c253cSXin Li sys.path.append(chromeos_root) 93*760c253cSXin Li 94*760c253cSXin Li self.locks_dir = locks_dir 95*760c253cSXin Li 96*760c253cSXin Li self.machines = list(set(remotes)) or [] 97*760c253cSXin Li self.toolchain_lab_machines = self.GetAllToolchainLabMachines() 98*760c253cSXin Li 99*760c253cSXin Li if not self.machines: 100*760c253cSXin Li self.machines = self.toolchain_lab_machines 101*760c253cSXin Li self.force = force_option 102*760c253cSXin Li 103*760c253cSXin Li self.local_machines = [] 104*760c253cSXin Li self.crosfleet_machines = [] 105*760c253cSXin Li 106*760c253cSXin Li def CheckMachine(self, machine, error_msg): 107*760c253cSXin Li """Verifies that machine is responding to ping. 108*760c253cSXin Li 109*760c253cSXin Li Args: 110*760c253cSXin Li machine: String containing the name or ip address of machine to check. 111*760c253cSXin Li error_msg: Message to print if ping fails. 112*760c253cSXin Li 113*760c253cSXin Li Raises: 114*760c253cSXin Li MachineNotPingable: If machine is not responding to 'ping' 115*760c253cSXin Li """ 116*760c253cSXin Li if not machines.MachineIsPingable(machine, logging_level="none"): 117*760c253cSXin Li cros_machine = machine + ".cros" 118*760c253cSXin Li if not machines.MachineIsPingable( 119*760c253cSXin Li cros_machine, logging_level="none" 120*760c253cSXin Li ): 121*760c253cSXin Li raise MachineNotPingable(error_msg) 122*760c253cSXin Li 123*760c253cSXin Li def GetAllToolchainLabMachines(self): 124*760c253cSXin Li """Gets a list of all the toolchain machines in the ChromeOS HW lab. 125*760c253cSXin Li 126*760c253cSXin Li Returns: 127*760c253cSXin Li A list of names of the toolchain machines in the ChromeOS HW lab. 128*760c253cSXin Li """ 129*760c253cSXin Li machines_file = os.path.join( 130*760c253cSXin Li os.path.dirname(__file__), "crosperf", "default_remotes" 131*760c253cSXin Li ) 132*760c253cSXin Li machine_list = [] 133*760c253cSXin Li with open(machines_file, "r") as input_file: 134*760c253cSXin Li lines = input_file.readlines() 135*760c253cSXin Li for line in lines: 136*760c253cSXin Li _, remotes = line.split(":") 137*760c253cSXin Li remotes = remotes.strip() 138*760c253cSXin Li for r in remotes.split(): 139*760c253cSXin Li machine_list.append(r.strip()) 140*760c253cSXin Li return machine_list 141*760c253cSXin Li 142*760c253cSXin Li def GetMachineType(self, m): 143*760c253cSXin Li """Get where the machine is located. 144*760c253cSXin Li 145*760c253cSXin Li Args: 146*760c253cSXin Li m: String containing the name or ip address of machine. 147*760c253cSXin Li 148*760c253cSXin Li Returns: 149*760c253cSXin Li Value of the type in MachineType Enum. 150*760c253cSXin Li """ 151*760c253cSXin Li if m in self.local_machines: 152*760c253cSXin Li return MachineType.LOCAL 153*760c253cSXin Li if m in self.crosfleet_machines: 154*760c253cSXin Li return MachineType.CROSFLEET 155*760c253cSXin Li 156*760c253cSXin Li def PrintStatusHeader(self): 157*760c253cSXin Li """Prints the status header lines for machines.""" 158*760c253cSXin Li print("\nMachine (Board)\t\t\t\t\tStatus") 159*760c253cSXin Li print("---------------\t\t\t\t\t------") 160*760c253cSXin Li 161*760c253cSXin Li def PrintStatus(self, m, state, machine_type): 162*760c253cSXin Li """Prints status for a single machine. 163*760c253cSXin Li 164*760c253cSXin Li Args: 165*760c253cSXin Li m: String containing the name or ip address of machine. 166*760c253cSXin Li state: A dictionary of the current state of the machine. 167*760c253cSXin Li machine_type: MachineType to determine where the machine is located. 168*760c253cSXin Li """ 169*760c253cSXin Li if state["locked"]: 170*760c253cSXin Li print( 171*760c253cSXin Li "%s (%s)\t\t%slocked by %s since %s" 172*760c253cSXin Li % ( 173*760c253cSXin Li m, 174*760c253cSXin Li state["board"], 175*760c253cSXin Li "\t\t" if machine_type == MachineType.LOCAL else "", 176*760c253cSXin Li state["locked_by"], 177*760c253cSXin Li state["lock_time"], 178*760c253cSXin Li ) 179*760c253cSXin Li ) 180*760c253cSXin Li else: 181*760c253cSXin Li print( 182*760c253cSXin Li "%s (%s)\t\t%sunlocked" 183*760c253cSXin Li % ( 184*760c253cSXin Li m, 185*760c253cSXin Li state["board"], 186*760c253cSXin Li "\t\t" if machine_type == MachineType.LOCAL else "", 187*760c253cSXin Li ) 188*760c253cSXin Li ) 189*760c253cSXin Li 190*760c253cSXin Li def AddMachineToLocal(self, machine): 191*760c253cSXin Li """Adds a machine to local machine list. 192*760c253cSXin Li 193*760c253cSXin Li Args: 194*760c253cSXin Li machine: The machine to be added. 195*760c253cSXin Li """ 196*760c253cSXin Li if machine not in self.local_machines: 197*760c253cSXin Li self.local_machines.append(machine) 198*760c253cSXin Li 199*760c253cSXin Li def AddMachineToCrosfleet(self, machine): 200*760c253cSXin Li """Adds a machine to crosfleet machine list. 201*760c253cSXin Li 202*760c253cSXin Li Args: 203*760c253cSXin Li machine: The machine to be added. 204*760c253cSXin Li """ 205*760c253cSXin Li if machine not in self.crosfleet_machines: 206*760c253cSXin Li self.crosfleet_machines.append(machine) 207*760c253cSXin Li 208*760c253cSXin Li def ListMachineStates(self, machine_states): 209*760c253cSXin Li """Gets and prints the current status for a list of machines. 210*760c253cSXin Li 211*760c253cSXin Li Prints out the current status for all of the machines in the current 212*760c253cSXin Li LockManager's list of machines (set when the object is initialized). 213*760c253cSXin Li 214*760c253cSXin Li Args: 215*760c253cSXin Li machine_states: A dictionary of the current state of every machine in 216*760c253cSXin Li the current LockManager's list of machines. Normally obtained by 217*760c253cSXin Li calling LockManager::GetMachineStates. 218*760c253cSXin Li """ 219*760c253cSXin Li self.PrintStatusHeader() 220*760c253cSXin Li for m in machine_states: 221*760c253cSXin Li machine_type = self.GetMachineType(m) 222*760c253cSXin Li state = machine_states[m] 223*760c253cSXin Li self.PrintStatus(m, state, machine_type) 224*760c253cSXin Li 225*760c253cSXin Li def UpdateLockInCrosfleet(self, should_lock_machine, machine): 226*760c253cSXin Li """Ask crosfleet to lease/release a machine. 227*760c253cSXin Li 228*760c253cSXin Li Args: 229*760c253cSXin Li should_lock_machine: Boolean indicating whether to lock the machine (True) 230*760c253cSXin Li or unlock the machine (False). 231*760c253cSXin Li machine: The machine to update. 232*760c253cSXin Li 233*760c253cSXin Li Returns: 234*760c253cSXin Li True if requested action succeeded, else False. 235*760c253cSXin Li """ 236*760c253cSXin Li try: 237*760c253cSXin Li if should_lock_machine: 238*760c253cSXin Li ret = self.LeaseCrosfleetMachine(machine) 239*760c253cSXin Li else: 240*760c253cSXin Li ret = self.ReleaseCrosfleetMachine(machine) 241*760c253cSXin Li except Exception: 242*760c253cSXin Li return False 243*760c253cSXin Li return ret 244*760c253cSXin Li 245*760c253cSXin Li def UpdateFileLock(self, should_lock_machine, machine): 246*760c253cSXin Li """Use file lock for local machines, 247*760c253cSXin Li 248*760c253cSXin Li Args: 249*760c253cSXin Li should_lock_machine: Boolean indicating whether to lock the machine (True) 250*760c253cSXin Li or unlock the machine (False). 251*760c253cSXin Li machine: The machine to update. 252*760c253cSXin Li 253*760c253cSXin Li Returns: 254*760c253cSXin Li True if requested action succeeded, else False. 255*760c253cSXin Li """ 256*760c253cSXin Li try: 257*760c253cSXin Li if should_lock_machine: 258*760c253cSXin Li ret = file_lock_machine.Machine(machine, self.locks_dir).Lock( 259*760c253cSXin Li True, sys.argv[0] 260*760c253cSXin Li ) 261*760c253cSXin Li else: 262*760c253cSXin Li ret = file_lock_machine.Machine(machine, self.locks_dir).Unlock( 263*760c253cSXin Li True 264*760c253cSXin Li ) 265*760c253cSXin Li except Exception: 266*760c253cSXin Li return False 267*760c253cSXin Li return ret 268*760c253cSXin Li 269*760c253cSXin Li def UpdateMachines(self, lock_machines): 270*760c253cSXin Li """Sets the locked state of the machines to the requested value. 271*760c253cSXin Li 272*760c253cSXin Li The machines updated are the ones in self.machines (specified when the 273*760c253cSXin Li class object was intialized). 274*760c253cSXin Li 275*760c253cSXin Li Args: 276*760c253cSXin Li lock_machines: Boolean indicating whether to lock the machines (True) or 277*760c253cSXin Li unlock the machines (False). 278*760c253cSXin Li 279*760c253cSXin Li Returns: 280*760c253cSXin Li A list of the machines whose state was successfully updated. 281*760c253cSXin Li """ 282*760c253cSXin Li updated_machines = [] 283*760c253cSXin Li action = "Locking" if lock_machines else "Unlocking" 284*760c253cSXin Li for m in self.machines: 285*760c253cSXin Li # TODO(zhizhouy): Handling exceptions with more details when locking 286*760c253cSXin Li # doesn't succeed. 287*760c253cSXin Li machine_type = self.GetMachineType(m) 288*760c253cSXin Li if machine_type == MachineType.CROSFLEET: 289*760c253cSXin Li ret = self.UpdateLockInCrosfleet(lock_machines, m) 290*760c253cSXin Li elif machine_type == MachineType.LOCAL: 291*760c253cSXin Li ret = self.UpdateFileLock(lock_machines, m) 292*760c253cSXin Li 293*760c253cSXin Li if ret: 294*760c253cSXin Li self.logger.LogOutput( 295*760c253cSXin Li "%s %s machine succeeded: %s." 296*760c253cSXin Li % (action, machine_type.value, m) 297*760c253cSXin Li ) 298*760c253cSXin Li updated_machines.append(m) 299*760c253cSXin Li else: 300*760c253cSXin Li self.logger.LogOutput( 301*760c253cSXin Li "%s %s machine failed: %s." 302*760c253cSXin Li % (action, machine_type.value, m) 303*760c253cSXin Li ) 304*760c253cSXin Li 305*760c253cSXin Li self.machines = updated_machines 306*760c253cSXin Li return updated_machines 307*760c253cSXin Li 308*760c253cSXin Li def _InternalRemoveMachine(self, machine): 309*760c253cSXin Li """Remove machine from internal list of machines. 310*760c253cSXin Li 311*760c253cSXin Li Args: 312*760c253cSXin Li machine: Name of machine to be removed from internal list. 313*760c253cSXin Li """ 314*760c253cSXin Li # Check to see if machine is lab machine and if so, make sure it has 315*760c253cSXin Li # ".cros" on the end. 316*760c253cSXin Li cros_machine = machine 317*760c253cSXin Li if machine.find("rack") > 0 and machine.find("row") > 0: 318*760c253cSXin Li if machine.find(".cros") == -1: 319*760c253cSXin Li cros_machine = cros_machine + ".cros" 320*760c253cSXin Li 321*760c253cSXin Li self.machines = [ 322*760c253cSXin Li m for m in self.machines if m not in (cros_machine, machine) 323*760c253cSXin Li ] 324*760c253cSXin Li 325*760c253cSXin Li def CheckMachineLocks(self, machine_states, cmd): 326*760c253cSXin Li """Check that every machine in requested list is in the proper state. 327*760c253cSXin Li 328*760c253cSXin Li If the cmd is 'unlock' verify that every machine is locked by requestor. 329*760c253cSXin Li If the cmd is 'lock' verify that every machine is currently unlocked. 330*760c253cSXin Li 331*760c253cSXin Li Args: 332*760c253cSXin Li machine_states: A dictionary of the current state of every machine in 333*760c253cSXin Li the current LockManager's list of machines. Normally obtained by 334*760c253cSXin Li calling LockManager::GetMachineStates. 335*760c253cSXin Li cmd: The user-requested action for the machines: 'lock' or 'unlock'. 336*760c253cSXin Li 337*760c253cSXin Li Raises: 338*760c253cSXin Li DontOwnLock: The lock on a requested machine is owned by someone else. 339*760c253cSXin Li """ 340*760c253cSXin Li for k, state in machine_states.items(): 341*760c253cSXin Li if cmd == "unlock": 342*760c253cSXin Li if not state["locked"]: 343*760c253cSXin Li self.logger.LogWarning( 344*760c253cSXin Li "Attempt to unlock already unlocked machine " 345*760c253cSXin Li "(%s)." % k 346*760c253cSXin Li ) 347*760c253cSXin Li self._InternalRemoveMachine(k) 348*760c253cSXin Li 349*760c253cSXin Li # TODO(zhizhouy): Crosfleet doesn't support host info such as locked_by. 350*760c253cSXin Li # Need to update this when crosfleet supports it. 351*760c253cSXin Li if ( 352*760c253cSXin Li state["locked"] 353*760c253cSXin Li and state["locked_by"] 354*760c253cSXin Li and state["locked_by"] != self.user 355*760c253cSXin Li ): 356*760c253cSXin Li raise DontOwnLock( 357*760c253cSXin Li "Attempt to unlock machine (%s) locked by someone " 358*760c253cSXin Li "else (%s)." % (k, state["locked_by"]) 359*760c253cSXin Li ) 360*760c253cSXin Li elif cmd == "lock": 361*760c253cSXin Li if state["locked"]: 362*760c253cSXin Li self.logger.LogWarning( 363*760c253cSXin Li "Attempt to lock already locked machine (%s)" % k 364*760c253cSXin Li ) 365*760c253cSXin Li self._InternalRemoveMachine(k) 366*760c253cSXin Li 367*760c253cSXin Li def GetMachineStates(self, cmd=""): 368*760c253cSXin Li """Gets the current state of all the requested machines. 369*760c253cSXin Li 370*760c253cSXin Li Gets the current state of all the requested machines. Stores the data in a 371*760c253cSXin Li dictionary keyed by machine name. 372*760c253cSXin Li 373*760c253cSXin Li Args: 374*760c253cSXin Li cmd: The command for which we are getting the machine states. This is 375*760c253cSXin Li important because if one of the requested machines is missing we raise 376*760c253cSXin Li an exception, unless the requested command is 'add'. 377*760c253cSXin Li 378*760c253cSXin Li Returns: 379*760c253cSXin Li A dictionary of machine states for all the machines in the LockManager 380*760c253cSXin Li object. 381*760c253cSXin Li """ 382*760c253cSXin Li machine_list = {} 383*760c253cSXin Li for m in self.machines: 384*760c253cSXin Li # For local or crosfleet machines, we simply set {'locked': status} for 385*760c253cSXin Li # them 386*760c253cSXin Li # TODO(zhizhouy): This is a quick fix since crosfleet cannot return host 387*760c253cSXin Li # info as afe does. We need to get more info such as locked_by when 388*760c253cSXin Li # crosfleet supports that. 389*760c253cSXin Li values = { 390*760c253cSXin Li "locked": 0 if cmd == "lock" else 1, 391*760c253cSXin Li "board": "??", 392*760c253cSXin Li "locked_by": "", 393*760c253cSXin Li "lock_time": "", 394*760c253cSXin Li } 395*760c253cSXin Li machine_list[m] = values 396*760c253cSXin Li 397*760c253cSXin Li self.ListMachineStates(machine_list) 398*760c253cSXin Li 399*760c253cSXin Li return machine_list 400*760c253cSXin Li 401*760c253cSXin Li def CheckMachineInCrosfleet(self, machine): 402*760c253cSXin Li """Run command to check if machine is in Crosfleet or not. 403*760c253cSXin Li 404*760c253cSXin Li Returns: 405*760c253cSXin Li True if machine in crosfleet, else False 406*760c253cSXin Li """ 407*760c253cSXin Li credential = "" 408*760c253cSXin Li if os.path.exists(self.CROSFLEET_CREDENTIAL): 409*760c253cSXin Li credential = "--service-account-json %s" % self.CROSFLEET_CREDENTIAL 410*760c253cSXin Li server = "--server https://chromeos-swarming.appspot.com" 411*760c253cSXin Li dimensions = "--dimension dut_name=%s" % machine.rstrip(".cros") 412*760c253cSXin Li 413*760c253cSXin Li cmd = f"{self.SWARMING} bots {server} {credential} {dimensions}" 414*760c253cSXin Li exit_code, stdout, stderr = self.ce.RunCommandWOutput(cmd) 415*760c253cSXin Li if exit_code: 416*760c253cSXin Li raise ValueError( 417*760c253cSXin Li "Querying bots failed (2); stdout: %r; stderr: %r" 418*760c253cSXin Li % (stdout, stderr) 419*760c253cSXin Li ) 420*760c253cSXin Li 421*760c253cSXin Li # The command will return a json output as stdout. If machine not in 422*760c253cSXin Li # crosfleet, stdout will look like this: 423*760c253cSXin Li # { 424*760c253cSXin Li # "death_timeout": "600", 425*760c253cSXin Li # "now": "TIMESTAMP" 426*760c253cSXin Li # } 427*760c253cSXin Li # Otherwise there will be a tuple starting with 'items', we simply detect 428*760c253cSXin Li # this keyword for result. 429*760c253cSXin Li return stdout != "[]" 430*760c253cSXin Li 431*760c253cSXin Li def LeaseCrosfleetMachine(self, machine): 432*760c253cSXin Li """Run command to lease dut from crosfleet. 433*760c253cSXin Li 434*760c253cSXin Li Returns: 435*760c253cSXin Li True if succeeded, False if failed. 436*760c253cSXin Li """ 437*760c253cSXin Li credential = "" 438*760c253cSXin Li if os.path.exists(self.CROSFLEET_CREDENTIAL): 439*760c253cSXin Li credential = "-service-account-json %s" % self.CROSFLEET_CREDENTIAL 440*760c253cSXin Li cmd = ("%s dut lease -minutes %s %s %s %s") % ( 441*760c253cSXin Li self.CROSFLEET_PATH, 442*760c253cSXin Li self.LEASE_MINS, 443*760c253cSXin Li credential, 444*760c253cSXin Li "-host", 445*760c253cSXin Li machine.rstrip(".cros"), 446*760c253cSXin Li ) 447*760c253cSXin Li # Wait 8 minutes for server to start the lease task, if not started, 448*760c253cSXin Li # we will treat it as unavailable. 449*760c253cSXin Li check_interval_time = 480 450*760c253cSXin Li retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time) 451*760c253cSXin Li return retval == self.SUCCESS 452*760c253cSXin Li 453*760c253cSXin Li def ReleaseCrosfleetMachine(self, machine): 454*760c253cSXin Li """Run command to release dut from crosfleet. 455*760c253cSXin Li 456*760c253cSXin Li Returns: 457*760c253cSXin Li True if succeeded, False if failed. 458*760c253cSXin Li """ 459*760c253cSXin Li credential = "" 460*760c253cSXin Li if os.path.exists(self.CROSFLEET_CREDENTIAL): 461*760c253cSXin Li credential = "-service-account-json %s" % self.CROSFLEET_CREDENTIAL 462*760c253cSXin Li 463*760c253cSXin Li cmd = ("%s dut abandon %s %s") % ( 464*760c253cSXin Li self.CROSFLEET_PATH, 465*760c253cSXin Li credential, 466*760c253cSXin Li machine.rstrip(".cros"), 467*760c253cSXin Li ) 468*760c253cSXin Li retval = self.ce.RunCommand(cmd) 469*760c253cSXin Li return retval == self.SUCCESS 470*760c253cSXin Li 471*760c253cSXin Li 472*760c253cSXin Lidef Main(argv): 473*760c253cSXin Li """Parse the options, initialize lock manager and dispatch proper method. 474*760c253cSXin Li 475*760c253cSXin Li Args: 476*760c253cSXin Li argv: The options with which this script was invoked. 477*760c253cSXin Li 478*760c253cSXin Li Returns: 479*760c253cSXin Li 0 unless an exception is raised. 480*760c253cSXin Li """ 481*760c253cSXin Li parser = argparse.ArgumentParser() 482*760c253cSXin Li 483*760c253cSXin Li parser.add_argument( 484*760c253cSXin Li "--list", 485*760c253cSXin Li dest="cmd", 486*760c253cSXin Li action="store_const", 487*760c253cSXin Li const="status", 488*760c253cSXin Li help="List current status of all known machines.", 489*760c253cSXin Li ) 490*760c253cSXin Li parser.add_argument( 491*760c253cSXin Li "--lock", 492*760c253cSXin Li dest="cmd", 493*760c253cSXin Li action="store_const", 494*760c253cSXin Li const="lock", 495*760c253cSXin Li help="Lock given machine(s).", 496*760c253cSXin Li ) 497*760c253cSXin Li parser.add_argument( 498*760c253cSXin Li "--unlock", 499*760c253cSXin Li dest="cmd", 500*760c253cSXin Li action="store_const", 501*760c253cSXin Li const="unlock", 502*760c253cSXin Li help="Unlock given machine(s).", 503*760c253cSXin Li ) 504*760c253cSXin Li parser.add_argument( 505*760c253cSXin Li "--status", 506*760c253cSXin Li dest="cmd", 507*760c253cSXin Li action="store_const", 508*760c253cSXin Li const="status", 509*760c253cSXin Li help="List current status of given machine(s).", 510*760c253cSXin Li ) 511*760c253cSXin Li parser.add_argument( 512*760c253cSXin Li "--remote", dest="remote", help="machines on which to operate" 513*760c253cSXin Li ) 514*760c253cSXin Li parser.add_argument( 515*760c253cSXin Li "--chromeos_root", 516*760c253cSXin Li dest="chromeos_root", 517*760c253cSXin Li required=True, 518*760c253cSXin Li help="ChromeOS root to use for autotest scripts.", 519*760c253cSXin Li ) 520*760c253cSXin Li parser.add_argument( 521*760c253cSXin Li "--force", 522*760c253cSXin Li dest="force", 523*760c253cSXin Li action="store_true", 524*760c253cSXin Li default=False, 525*760c253cSXin Li help="Force lock/unlock of machines, even if not" 526*760c253cSXin Li " current lock owner.", 527*760c253cSXin Li ) 528*760c253cSXin Li 529*760c253cSXin Li options = parser.parse_args(argv) 530*760c253cSXin Li 531*760c253cSXin Li if not options.remote and options.cmd != "status": 532*760c253cSXin Li parser.error("No machines specified for operation.") 533*760c253cSXin Li 534*760c253cSXin Li if not os.path.isdir(options.chromeos_root): 535*760c253cSXin Li parser.error("Cannot find chromeos_root: %s." % options.chromeos_root) 536*760c253cSXin Li 537*760c253cSXin Li if not options.cmd: 538*760c253cSXin Li parser.error( 539*760c253cSXin Li "No operation selected (--list, --status, --lock, --unlock," 540*760c253cSXin Li " --add_machine, --remove_machine)." 541*760c253cSXin Li ) 542*760c253cSXin Li 543*760c253cSXin Li machine_list = [] 544*760c253cSXin Li if options.remote: 545*760c253cSXin Li machine_list = options.remote.split() 546*760c253cSXin Li 547*760c253cSXin Li lock_manager = LockManager( 548*760c253cSXin Li machine_list, options.force, options.chromeos_root 549*760c253cSXin Li ) 550*760c253cSXin Li 551*760c253cSXin Li machine_states = lock_manager.GetMachineStates(cmd=options.cmd) 552*760c253cSXin Li cmd = options.cmd 553*760c253cSXin Li 554*760c253cSXin Li if cmd == "status": 555*760c253cSXin Li lock_manager.ListMachineStates(machine_states) 556*760c253cSXin Li 557*760c253cSXin Li elif cmd == "lock": 558*760c253cSXin Li if not lock_manager.force: 559*760c253cSXin Li lock_manager.CheckMachineLocks(machine_states, cmd) 560*760c253cSXin Li lock_manager.UpdateMachines(True) 561*760c253cSXin Li 562*760c253cSXin Li elif cmd == "unlock": 563*760c253cSXin Li if not lock_manager.force: 564*760c253cSXin Li lock_manager.CheckMachineLocks(machine_states, cmd) 565*760c253cSXin Li lock_manager.UpdateMachines(False) 566*760c253cSXin Li 567*760c253cSXin Li return 0 568*760c253cSXin Li 569*760c253cSXin Li 570*760c253cSXin Liif __name__ == "__main__": 571*760c253cSXin Li sys.exit(Main(sys.argv[1:])) 572