blob: b95678e8319212e9acf3d37449da2245f8b91e03 [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#
George Burgess IV2124be52022-04-21 10:27:37 -07004# Copyright 2019 The ChromiumOS Authors. All rights reserved.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -07005# 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 -070018from cros_utils import command_executer
Caroline Ticea8af9a72016-07-20 12:52:59 -070019from cros_utils import logger
20from cros_utils import machines
Denis Nikitin444382a2022-05-16 12:30:45 -070021import file_lock_machine
cmticee5bc63b2015-05-27 16:59:37 -070022
Luis Lozanof2a3ef42015-12-15 13:49:30 -080023
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070024class LockException(Exception):
cmticee5bc63b2015-05-27 16:59:37 -070025 """Base class for exceptions in this module."""
26
27
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070028class MachineNotPingable(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070029 """Raised when machine does not respond to ping."""
30
31
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070032class LockingError(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070033 """Raised when server fails to lock/unlock machine as requested."""
34
35
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070036class DontOwnLock(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070037 """Raised when user attmepts to unlock machine locked by someone else."""
38 # This should not be raised if the user specified '--force'
39
40
Zhizhou Yang5322d4a2019-09-30 13:10:29 -070041class MachineType(enum.Enum):
42 """Enum class to hold machine type."""
Zhizhou Yang5322d4a2019-09-30 13:10:29 -070043 LOCAL = 'local'
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +000044 CROSFLEET = 'crosfleet'
Zhizhou Yang5322d4a2019-09-30 13:10:29 -070045
46
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070047class LockManager(object):
48 """Class for locking/unlocking machines vie three different modes.
cmticee5bc63b2015-05-27 16:59:37 -070049
Zhizhou Yang4713fd12019-09-24 10:32:00 -070050 This class contains methods for checking the locked status of machines,
Zhizhou Yang0ee6a572020-01-22 15:55:19 -080051 and for changing the locked status. It handles HW lab machines and local
52 machines, using appropriate locking mechanisms for each.
cmticee5bc63b2015-05-27 16:59:37 -070053 """
54
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +000055 CROSFLEET_PATH = 'crosfleet'
Zhizhou Yang13446a72019-10-29 16:50:14 -070056
57 # TODO(zhizhouy): lease time may needs to be dynamically adjusted. For now we
58 # set it long enough to cover the period to finish nightly rotation tests.
George Burgess IVb35bdfe2020-05-02 10:52:07 -070059 LEASE_MINS = 1439
Zhizhou Yang13446a72019-10-29 16:50:14 -070060
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +000061 CROSFLEET_CREDENTIAL = ('/usr/local/google/home/mobiletc-prebuild'
62 '/sheriff_utils/credentials/skylab'
63 '/chromeos-swarming-credential.json')
Ryan Beltran20d36f92022-03-26 00:13:03 +000064 SWARMING = '~/cipd_binaries/swarming'
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070065 SUCCESS = 0
cmticee5bc63b2015-05-27 16:59:37 -070066
Luis Lozanof2a3ef42015-12-15 13:49:30 -080067 def __init__(self,
68 remotes,
69 force_option,
70 chromeos_root,
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070071 locks_dir='',
Luis Lozanof2a3ef42015-12-15 13:49:30 -080072 log=None):
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070073 """Initializes an LockManager object.
cmticee5bc63b2015-05-27 16:59:37 -070074
75 Args:
76 remotes: A list of machine names or ip addresses to be managed. Names
Caroline Ticea4486452015-12-08 13:43:23 -080077 and ip addresses should be represented as strings. If the list is
78 empty, the lock manager will get all known machines.
79 force_option: A Boolean indicating whether or not to force an unlock of
cmticee5bc63b2015-05-27 16:59:37 -070080 a machine that was locked by someone else.
81 chromeos_root: The ChromeOS chroot to use for the autotest scripts.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070082 locks_dir: A directory used for file locking local devices.
cmticee5bc63b2015-05-27 16:59:37 -070083 log: If not None, this is the logger object to be used for writing out
84 informational output messages. It is expected to be an instance of
Caroline Ticea8af9a72016-07-20 12:52:59 -070085 Logger class from cros_utils/logger.py.
cmticee5bc63b2015-05-27 16:59:37 -070086 """
87 self.chromeos_root = chromeos_root
88 self.user = getpass.getuser()
89 self.logger = log or logger.GetLogger()
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070090 self.ce = command_executer.GetCommandExecuter(self.logger)
cmticee5bc63b2015-05-27 16:59:37 -070091
cmticed1172b42015-06-12 15:14:09 -070092 sys.path.append(chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -070093
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070094 self.locks_dir = locks_dir
95
Caroline Tice6b161382016-09-15 15:03:46 -070096 self.machines = list(set(remotes)) or []
97 self.toolchain_lab_machines = self.GetAllToolchainLabMachines()
Caroline Tice6b161382016-09-15 15:03:46 -070098
cmticee5bc63b2015-05-27 16:59:37 -070099 if not self.machines:
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700100 self.machines = self.toolchain_lab_machines
Caroline Tice6b161382016-09-15 15:03:46 -0700101 self.force = force_option
102
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700103 self.local_machines = []
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000104 self.crosfleet_machines = []
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700105
cmticee5bc63b2015-05-27 16:59:37 -0700106 def CheckMachine(self, machine, error_msg):
107 """Verifies that machine is responding to ping.
108
109 Args:
110 machine: String containing the name or ip address of machine to check.
111 error_msg: Message to print if ping fails.
112
113 Raises:
114 MachineNotPingable: If machine is not responding to 'ping'
115 """
116 if not machines.MachineIsPingable(machine, logging_level='none'):
Caroline Ticea4486452015-12-08 13:43:23 -0800117 cros_machine = machine + '.cros'
118 if not machines.MachineIsPingable(cros_machine, logging_level='none'):
119 raise MachineNotPingable(error_msg)
cmticee5bc63b2015-05-27 16:59:37 -0700120
cmticee5bc63b2015-05-27 16:59:37 -0700121 def GetAllToolchainLabMachines(self):
122 """Gets a list of all the toolchain machines in the ChromeOS HW lab.
123
124 Returns:
125 A list of names of the toolchain machines in the ChromeOS HW lab.
126 """
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700127 machines_file = os.path.join(os.path.dirname(__file__), 'crosperf',
128 'default_remotes')
cmticee5bc63b2015-05-27 16:59:37 -0700129 machine_list = []
130 with open(machines_file, 'r') as input_file:
131 lines = input_file.readlines()
132 for line in lines:
Caroline Ticea4486452015-12-08 13:43:23 -0800133 _, remotes = line.split(':')
cmticee5bc63b2015-05-27 16:59:37 -0700134 remotes = remotes.strip()
135 for r in remotes.split():
136 machine_list.append(r.strip())
137 return machine_list
138
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700139 def GetMachineType(self, m):
140 """Get where the machine is located.
cmticee5bc63b2015-05-27 16:59:37 -0700141
Caroline Ticea4486452015-12-08 13:43:23 -0800142 Args:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700143 m: String containing the name or ip address of machine.
144
145 Returns:
146 Value of the type in MachineType Enum.
cmticee5bc63b2015-05-27 16:59:37 -0700147 """
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700148 if m in self.local_machines:
149 return MachineType.LOCAL
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000150 if m in self.crosfleet_machines:
151 return MachineType.CROSFLEET
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700152
153 def PrintStatusHeader(self):
154 """Prints the status header lines for machines."""
155 print('\nMachine (Board)\t\t\t\t\tStatus')
156 print('---------------\t\t\t\t\t------')
157
158 def PrintStatus(self, m, state, machine_type):
159 """Prints status for a single machine.
160
161 Args:
162 m: String containing the name or ip address of machine.
163 state: A dictionary of the current state of the machine.
164 machine_type: MachineType to determine where the machine is located.
165 """
166 if state['locked']:
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700167 print('%s (%s)\t\t%slocked by %s since %s' %
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700168 (m, state['board'], '\t\t' if machine_type == MachineType.LOCAL
169 else '', state['locked_by'], state['lock_time']))
cmticee5bc63b2015-05-27 16:59:37 -0700170 else:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000171 print('%s (%s)\t\t%sunlocked' %
172 (m, state['board'],
173 '\t\t' if machine_type == MachineType.LOCAL else ''))
cmticee5bc63b2015-05-27 16:59:37 -0700174
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700175 def AddMachineToLocal(self, machine):
176 """Adds a machine to local machine list.
177
178 Args:
179 machine: The machine to be added.
180 """
181 if machine not in self.local_machines:
182 self.local_machines.append(machine)
183
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000184 def AddMachineToCrosfleet(self, machine):
185 """Adds a machine to crosfleet machine list.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700186
187 Args:
188 machine: The machine to be added.
189 """
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000190 if machine not in self.crosfleet_machines:
191 self.crosfleet_machines.append(machine)
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700192
cmticee5bc63b2015-05-27 16:59:37 -0700193 def ListMachineStates(self, machine_states):
194 """Gets and prints the current status for a list of machines.
195
196 Prints out the current status for all of the machines in the current
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700197 LockManager's list of machines (set when the object is initialized).
cmticee5bc63b2015-05-27 16:59:37 -0700198
199 Args:
200 machine_states: A dictionary of the current state of every machine in
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700201 the current LockManager's list of machines. Normally obtained by
202 calling LockManager::GetMachineStates.
cmticee5bc63b2015-05-27 16:59:37 -0700203 """
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700204 self.PrintStatusHeader()
cmticee5bc63b2015-05-27 16:59:37 -0700205 for m in machine_states:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700206 machine_type = self.GetMachineType(m)
207 state = machine_states[m]
208 self.PrintStatus(m, state, machine_type)
cmticee5bc63b2015-05-27 16:59:37 -0700209
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000210 def UpdateLockInCrosfleet(self, should_lock_machine, machine):
211 """Ask crosfleet to lease/release a machine.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700212
213 Args:
214 should_lock_machine: Boolean indicating whether to lock the machine (True)
215 or unlock the machine (False).
216 machine: The machine to update.
217
218 Returns:
219 True if requested action succeeded, else False.
220 """
221 try:
222 if should_lock_machine:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000223 ret = self.LeaseCrosfleetMachine(machine)
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700224 else:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000225 ret = self.ReleaseCrosfleetMachine(machine)
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700226 except Exception:
227 return False
228 return ret
229
230 def UpdateFileLock(self, should_lock_machine, machine):
231 """Use file lock for local machines,
232
233 Args:
234 should_lock_machine: Boolean indicating whether to lock the machine (True)
235 or unlock the machine (False).
236 machine: The machine to update.
237
238 Returns:
239 True if requested action succeeded, else False.
240 """
241 try:
242 if should_lock_machine:
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700243 ret = file_lock_machine.Machine(machine, self.locks_dir).Lock(
244 True, sys.argv[0])
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700245 else:
246 ret = file_lock_machine.Machine(machine, self.locks_dir).Unlock(True)
247 except Exception:
248 return False
249 return ret
cmticee5bc63b2015-05-27 16:59:37 -0700250
251 def UpdateMachines(self, lock_machines):
252 """Sets the locked state of the machines to the requested value.
253
254 The machines updated are the ones in self.machines (specified when the
255 class object was intialized).
256
257 Args:
Caroline Ticea4486452015-12-08 13:43:23 -0800258 lock_machines: Boolean indicating whether to lock the machines (True) or
cmticee5bc63b2015-05-27 16:59:37 -0700259 unlock the machines (False).
cmticef3eb8032015-07-27 13:55:52 -0700260
261 Returns:
262 A list of the machines whose state was successfully updated.
cmticee5bc63b2015-05-27 16:59:37 -0700263 """
cmticef3eb8032015-07-27 13:55:52 -0700264 updated_machines = []
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700265 action = 'Locking' if lock_machines else 'Unlocking'
cmticee5bc63b2015-05-27 16:59:37 -0700266 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700267 # TODO(zhizhouy): Handling exceptions with more details when locking
268 # doesn't succeed.
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700269 machine_type = self.GetMachineType(m)
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000270 if machine_type == MachineType.CROSFLEET:
271 ret = self.UpdateLockInCrosfleet(lock_machines, m)
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700272 elif machine_type == MachineType.LOCAL:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700273 ret = self.UpdateFileLock(lock_machines, m)
cmticef3eb8032015-07-27 13:55:52 -0700274
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700275 if ret:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000276 self.logger.LogOutput('%s %s machine succeeded: %s.' %
277 (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700278 updated_machines.append(m)
279 else:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000280 self.logger.LogOutput('%s %s machine failed: %s.' %
281 (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700282
283 self.machines = updated_machines
cmticef3eb8032015-07-27 13:55:52 -0700284 return updated_machines
285
286 def _InternalRemoveMachine(self, machine):
287 """Remove machine from internal list of machines.
288
289 Args:
290 machine: Name of machine to be removed from internal list.
291 """
292 # Check to see if machine is lab machine and if so, make sure it has
293 # ".cros" on the end.
294 cros_machine = machine
295 if machine.find('rack') > 0 and machine.find('row') > 0:
296 if machine.find('.cros') == -1:
297 cros_machine = cros_machine + '.cros'
298
Caroline Ticef6ef4392017-04-06 17:16:05 -0700299 self.machines = [
Zhizhou Yang5534af82020-01-15 16:25:04 -0800300 m for m in self.machines if m not in (cros_machine, machine)
Caroline Ticef6ef4392017-04-06 17:16:05 -0700301 ]
cmticee5bc63b2015-05-27 16:59:37 -0700302
303 def CheckMachineLocks(self, machine_states, cmd):
304 """Check that every machine in requested list is in the proper state.
305
306 If the cmd is 'unlock' verify that every machine is locked by requestor.
307 If the cmd is 'lock' verify that every machine is currently unlocked.
308
309 Args:
310 machine_states: A dictionary of the current state of every machine in
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700311 the current LockManager's list of machines. Normally obtained by
312 calling LockManager::GetMachineStates.
Caroline Ticea4486452015-12-08 13:43:23 -0800313 cmd: The user-requested action for the machines: 'lock' or 'unlock'.
cmticee5bc63b2015-05-27 16:59:37 -0700314
315 Raises:
cmticee5bc63b2015-05-27 16:59:37 -0700316 DontOwnLock: The lock on a requested machine is owned by someone else.
317 """
Zhizhou Yang5534af82020-01-15 16:25:04 -0800318 for k, state in machine_states.items():
cmticee5bc63b2015-05-27 16:59:37 -0700319 if cmd == 'unlock':
320 if not state['locked']:
cmticef3eb8032015-07-27 13:55:52 -0700321 self.logger.LogWarning('Attempt to unlock already unlocked machine '
322 '(%s).' % k)
323 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700324
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000325 # TODO(zhizhouy): Crosfleet doesn't support host info such as locked_by.
326 # Need to update this when crosfleet supports it.
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700327 if (state['locked'] and state['locked_by']
328 and state['locked_by'] != self.user):
cmticee5bc63b2015-05-27 16:59:37 -0700329 raise DontOwnLock('Attempt to unlock machine (%s) locked by someone '
330 'else (%s).' % (k, state['locked_by']))
331 elif cmd == 'lock':
332 if state['locked']:
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700333 self.logger.LogWarning(
334 'Attempt to lock already locked machine (%s)' % k)
cmticef3eb8032015-07-27 13:55:52 -0700335 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700336
cmticee5bc63b2015-05-27 16:59:37 -0700337 def GetMachineStates(self, cmd=''):
338 """Gets the current state of all the requested machines.
339
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700340 Gets the current state of all the requested machines. Stores the data in a
341 dictionary keyed by machine name.
cmticee5bc63b2015-05-27 16:59:37 -0700342
343 Args:
344 cmd: The command for which we are getting the machine states. This is
345 important because if one of the requested machines is missing we raise
346 an exception, unless the requested command is 'add'.
347
348 Returns:
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700349 A dictionary of machine states for all the machines in the LockManager
cmticee5bc63b2015-05-27 16:59:37 -0700350 object.
cmticee5bc63b2015-05-27 16:59:37 -0700351 """
Caroline Ticea4486452015-12-08 13:43:23 -0800352 machine_list = {}
cmticee5bc63b2015-05-27 16:59:37 -0700353 for m in self.machines:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000354 # For local or crosfleet machines, we simply set {'locked': status} for
355 # them
356 # TODO(zhizhouy): This is a quick fix since crosfleet cannot return host
357 # info as afe does. We need to get more info such as locked_by when
358 # crosfleet 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
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000371 def CheckMachineInCrosfleet(self, machine):
372 """Run command to check if machine is in Crosfleet or not.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700373
374 Returns:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000375 True if machine in crosfleet, else False
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700376 """
377 credential = ''
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000378 if os.path.exists(self.CROSFLEET_CREDENTIAL):
Ryan Beltran20d36f92022-03-26 00:13:03 +0000379 credential = '--service-account-json %s' % self.CROSFLEET_CREDENTIAL
380 server = '--server https://chromeos-swarming.appspot.com'
381 dimensions = '--dimension dut_name=%s' % machine.rstrip('.cros')
382
383 cmd = f'{self.SWARMING} bots {server} {credential} {dimensions}'
George Burgess IV900d6e72020-05-02 10:28:55 -0700384 exit_code, stdout, stderr = self.ce.RunCommandWOutput(cmd)
385 if exit_code:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000386 raise ValueError('Querying bots failed (2); stdout: %r; stderr: %r' %
387 (stdout, stderr))
George Burgess IV900d6e72020-05-02 10:28:55 -0700388
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000389 # The command will return a json output as stdout. If machine not in
390 # crosfleet, stdout will look like this:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700391 # {
392 # "death_timeout": "600",
393 # "now": "TIMESTAMP"
394 # }
395 # Otherwise there will be a tuple starting with 'items', we simply detect
396 # this keyword for result.
Ryan Beltran20d36f92022-03-26 00:13:03 +0000397 return stdout != '[]'
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700398
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000399 def LeaseCrosfleetMachine(self, machine):
400 """Run command to lease dut from crosfleet.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700401
402 Returns:
403 True if succeeded, False if failed.
404 """
405 credential = ''
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000406 if os.path.exists(self.CROSFLEET_CREDENTIAL):
407 credential = '-service-account-json %s' % self.CROSFLEET_CREDENTIAL
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700408 cmd = (('%s dut lease -minutes %s %s %s %s') %
Denis Nikitin444382a2022-05-16 12:30:45 -0700409 (self.CROSFLEET_PATH, self.LEASE_MINS, credential, '-host',
410 machine.rstrip('.cros')))
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700411 # Wait 8 minutes for server to start the lease task, if not started,
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700412 # we will treat it as unavailable.
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700413 check_interval_time = 480
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700414 retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time)
415 return retval == self.SUCCESS
416
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000417 def ReleaseCrosfleetMachine(self, machine):
418 """Run command to release dut from crosfleet.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700419
420 Returns:
421 True if succeeded, False if failed.
422 """
423 credential = ''
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000424 if os.path.exists(self.CROSFLEET_CREDENTIAL):
425 credential = '-service-account-json %s' % self.CROSFLEET_CREDENTIAL
Ryan Beltran20d36f92022-03-26 00:13:03 +0000426
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700427 cmd = (('%s dut abandon %s %s') %
428 (self.CROSFLEET_PATH, credential, machine.rstrip('.cros')))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700429 retval = self.ce.RunCommand(cmd)
430 return retval == self.SUCCESS
431
cmticee5bc63b2015-05-27 16:59:37 -0700432
433def Main(argv):
Caroline Ticea4486452015-12-08 13:43:23 -0800434 """Parse the options, initialize lock manager and dispatch proper method.
cmticee5bc63b2015-05-27 16:59:37 -0700435
Caroline Ticea4486452015-12-08 13:43:23 -0800436 Args:
437 argv: The options with which this script was invoked.
cmticee5bc63b2015-05-27 16:59:37 -0700438
Caroline Ticea4486452015-12-08 13:43:23 -0800439 Returns:
440 0 unless an exception is raised.
441 """
442 parser = argparse.ArgumentParser()
cmticee5bc63b2015-05-27 16:59:37 -0700443
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700444 parser.add_argument('--list',
445 dest='cmd',
446 action='store_const',
447 const='status',
448 help='List current status of all known machines.')
449 parser.add_argument('--lock',
450 dest='cmd',
451 action='store_const',
452 const='lock',
453 help='Lock given machine(s).')
454 parser.add_argument('--unlock',
455 dest='cmd',
456 action='store_const',
457 const='unlock',
458 help='Unlock given machine(s).')
459 parser.add_argument('--status',
460 dest='cmd',
461 action='store_const',
462 const='status',
463 help='List current status of given machine(s).')
464 parser.add_argument('--remote',
465 dest='remote',
466 help='machines on which to operate')
467 parser.add_argument('--chromeos_root',
468 dest='chromeos_root',
469 required=True,
470 help='ChromeOS root to use for autotest scripts.')
471 parser.add_argument('--force',
472 dest='force',
473 action='store_true',
474 default=False,
475 help='Force lock/unlock of machines, even if not'
476 ' current lock owner.')
cmticee5bc63b2015-05-27 16:59:37 -0700477
Caroline Ticea4486452015-12-08 13:43:23 -0800478 options = parser.parse_args(argv)
cmticee5bc63b2015-05-27 16:59:37 -0700479
Caroline Ticea4486452015-12-08 13:43:23 -0800480 if not options.remote and options.cmd != 'status':
481 parser.error('No machines specified for operation.')
cmticee5bc63b2015-05-27 16:59:37 -0700482
Caroline Ticea4486452015-12-08 13:43:23 -0800483 if not os.path.isdir(options.chromeos_root):
484 parser.error('Cannot find chromeos_root: %s.' % options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700485
Caroline Ticea4486452015-12-08 13:43:23 -0800486 if not options.cmd:
487 parser.error('No operation selected (--list, --status, --lock, --unlock,'
488 ' --add_machine, --remove_machine).')
cmticee5bc63b2015-05-27 16:59:37 -0700489
Caroline Ticea4486452015-12-08 13:43:23 -0800490 machine_list = []
491 if options.remote:
492 machine_list = options.remote.split()
cmticee5bc63b2015-05-27 16:59:37 -0700493
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700494 lock_manager = LockManager(machine_list, options.force,
495 options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700496
Caroline Ticea4486452015-12-08 13:43:23 -0800497 machine_states = lock_manager.GetMachineStates(cmd=options.cmd)
498 cmd = options.cmd
cmticee5bc63b2015-05-27 16:59:37 -0700499
Caroline Ticea4486452015-12-08 13:43:23 -0800500 if cmd == 'status':
501 lock_manager.ListMachineStates(machine_states)
cmticee5bc63b2015-05-27 16:59:37 -0700502
Caroline Ticea4486452015-12-08 13:43:23 -0800503 elif cmd == 'lock':
504 if not lock_manager.force:
505 lock_manager.CheckMachineLocks(machine_states, cmd)
506 lock_manager.UpdateMachines(True)
cmticee5bc63b2015-05-27 16:59:37 -0700507
Caroline Ticea4486452015-12-08 13:43:23 -0800508 elif cmd == 'unlock':
509 if not lock_manager.force:
510 lock_manager.CheckMachineLocks(machine_states, cmd)
511 lock_manager.UpdateMachines(False)
cmticee5bc63b2015-05-27 16:59:37 -0700512
Caroline Ticea4486452015-12-08 13:43:23 -0800513 return 0
cmticee5bc63b2015-05-27 16:59:37 -0700514
515
516if __name__ == '__main__':
Caroline Ticea4486452015-12-08 13:43:23 -0800517 sys.exit(Main(sys.argv[1:]))