blob: 03c8c99104b26a4975d3f76b0dc0def883be6b35 [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'
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +000045 CROSFLEET = 'crosfleet'
Zhizhou Yang5322d4a2019-09-30 13:10:29 -070046
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
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +000056 CROSFLEET_PATH = 'crosfleet'
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.
George Burgess IVb35bdfe2020-05-02 10:52:07 -070060 LEASE_MINS = 1439
Zhizhou Yang13446a72019-10-29 16:50:14 -070061
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +000062 CROSFLEET_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 = []
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000105 self.crosfleet_machines = []
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700106
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 """
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700128 machines_file = os.path.join(os.path.dirname(__file__), 'crosperf',
129 '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
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000151 if m in self.crosfleet_machines:
152 return MachineType.CROSFLEET
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' %
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700169 (m, state['board'], '\t\t' if machine_type == MachineType.LOCAL
170 else '', state['locked_by'], state['lock_time']))
cmticee5bc63b2015-05-27 16:59:37 -0700171 else:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000172 print('%s (%s)\t\t%sunlocked' %
173 (m, state['board'],
174 '\t\t' if 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
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000185 def AddMachineToCrosfleet(self, machine):
186 """Adds a machine to crosfleet machine list.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700187
188 Args:
189 machine: The machine to be added.
190 """
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000191 if machine not in self.crosfleet_machines:
192 self.crosfleet_machines.append(machine)
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700193
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
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000211 def UpdateLockInCrosfleet(self, should_lock_machine, machine):
212 """Ask crosfleet to lease/release a machine.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700213
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:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000224 ret = self.LeaseCrosfleetMachine(machine)
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700225 else:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000226 ret = self.ReleaseCrosfleetMachine(machine)
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700227 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:
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700244 ret = file_lock_machine.Machine(machine, self.locks_dir).Lock(
245 True, sys.argv[0])
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700246 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)
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000271 if machine_type == MachineType.CROSFLEET:
272 ret = self.UpdateLockInCrosfleet(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:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000277 self.logger.LogOutput('%s %s machine succeeded: %s.' %
278 (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700279 updated_machines.append(m)
280 else:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000281 self.logger.LogOutput('%s %s machine failed: %s.' %
282 (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
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000326 # TODO(zhizhouy): Crosfleet doesn't support host info such as locked_by.
327 # Need to update this when crosfleet supports it.
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700328 if (state['locked'] and state['locked_by']
329 and 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']:
Denis Nikitin9de6ecb2021-11-05 13:31:19 -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:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000355 # For local or crosfleet machines, we simply set {'locked': status} for
356 # them
357 # TODO(zhizhouy): This is a quick fix since crosfleet cannot return host
358 # info as afe does. We need to get more info such as locked_by when
359 # crosfleet supports that.
Zhizhou Yang0ee6a572020-01-22 15:55:19 -0800360 values = {
361 'locked': 0 if cmd == 'lock' else 1,
362 'board': '??',
363 'locked_by': '',
364 'lock_time': ''
365 }
366 machine_list[m] = values
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700367
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700368 self.ListMachineStates(machine_list)
369
Caroline Ticea4486452015-12-08 13:43:23 -0800370 return machine_list
cmticee5bc63b2015-05-27 16:59:37 -0700371
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000372 def CheckMachineInCrosfleet(self, machine):
373 """Run command to check if machine is in Crosfleet or not.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700374
375 Returns:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000376 True if machine in crosfleet, else False
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700377 """
378 credential = ''
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000379 if os.path.exists(self.CROSFLEET_CREDENTIAL):
380 credential = '--auth-service-account-json %s' % self.CROSFLEET_CREDENTIAL
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700381 swarming = os.path.join(self.chromeos_root, self.SWARMING)
zhizhouy9258b052020-04-15 17:33:46 -0700382 # TODO(zhizhouy): Swarming script doesn't support python3 so explicitly
383 # launch it with python2 until migrated.
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700384 cmd = (('python2 %s '
385 'query --swarming https://chromeos-swarming.appspot.com '
386 "%s 'bots/list?is_dead=FALSE&dimensions=dut_name:%s'") %
387 (swarming, credential, machine.rstrip('.cros')))
George Burgess IV900d6e72020-05-02 10:28:55 -0700388 exit_code, stdout, stderr = self.ce.RunCommandWOutput(cmd)
389 if exit_code:
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000390 raise ValueError('Querying bots failed (2); stdout: %r; stderr: %r' %
391 (stdout, stderr))
George Burgess IV900d6e72020-05-02 10:28:55 -0700392
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000393 # The command will return a json output as stdout. If machine not in
394 # crosfleet, stdout will look like this:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700395 # {
396 # "death_timeout": "600",
397 # "now": "TIMESTAMP"
398 # }
399 # Otherwise there will be a tuple starting with 'items', we simply detect
400 # this keyword for result.
George Burgess IV900d6e72020-05-02 10:28:55 -0700401 return 'items' in stdout
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700402
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000403 def LeaseCrosfleetMachine(self, machine):
404 """Run command to lease dut from crosfleet.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700405
406 Returns:
407 True if succeeded, False if failed.
408 """
409 credential = ''
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000410 if os.path.exists(self.CROSFLEET_CREDENTIAL):
411 credential = '-service-account-json %s' % self.CROSFLEET_CREDENTIAL
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700412 cmd = (('%s dut lease -minutes %s %s %s %s') %
413 (self.CROSFLEET_PATH, self.LEASE_MINS, credential, '-host'
414 if '.cros' in machine else '-board', machine.rstrip('.cros')))
415 # Wait 8 minutes for server to start the lease task, if not started,
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700416 # we will treat it as unavailable.
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700417 check_interval_time = 480
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700418 retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time)
419 return retval == self.SUCCESS
420
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000421 def ReleaseCrosfleetMachine(self, machine):
422 """Run command to release dut from crosfleet.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700423
424 Returns:
425 True if succeeded, False if failed.
426 """
427 credential = ''
Christopher Di Bella53e9fbe2021-03-18 20:31:06 +0000428 if os.path.exists(self.CROSFLEET_CREDENTIAL):
429 credential = '-service-account-json %s' % self.CROSFLEET_CREDENTIAL
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700430 cmd = (('%s dut abandon %s %s') %
431 (self.CROSFLEET_PATH, credential, machine.rstrip('.cros')))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700432 retval = self.ce.RunCommand(cmd)
433 return retval == self.SUCCESS
434
cmticee5bc63b2015-05-27 16:59:37 -0700435
436def Main(argv):
Caroline Ticea4486452015-12-08 13:43:23 -0800437 """Parse the options, initialize lock manager and dispatch proper method.
cmticee5bc63b2015-05-27 16:59:37 -0700438
Caroline Ticea4486452015-12-08 13:43:23 -0800439 Args:
440 argv: The options with which this script was invoked.
cmticee5bc63b2015-05-27 16:59:37 -0700441
Caroline Ticea4486452015-12-08 13:43:23 -0800442 Returns:
443 0 unless an exception is raised.
444 """
445 parser = argparse.ArgumentParser()
cmticee5bc63b2015-05-27 16:59:37 -0700446
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700447 parser.add_argument('--list',
448 dest='cmd',
449 action='store_const',
450 const='status',
451 help='List current status of all known machines.')
452 parser.add_argument('--lock',
453 dest='cmd',
454 action='store_const',
455 const='lock',
456 help='Lock given machine(s).')
457 parser.add_argument('--unlock',
458 dest='cmd',
459 action='store_const',
460 const='unlock',
461 help='Unlock given machine(s).')
462 parser.add_argument('--status',
463 dest='cmd',
464 action='store_const',
465 const='status',
466 help='List current status of given machine(s).')
467 parser.add_argument('--remote',
468 dest='remote',
469 help='machines on which to operate')
470 parser.add_argument('--chromeos_root',
471 dest='chromeos_root',
472 required=True,
473 help='ChromeOS root to use for autotest scripts.')
474 parser.add_argument('--force',
475 dest='force',
476 action='store_true',
477 default=False,
478 help='Force lock/unlock of machines, even if not'
479 ' current lock owner.')
cmticee5bc63b2015-05-27 16:59:37 -0700480
Caroline Ticea4486452015-12-08 13:43:23 -0800481 options = parser.parse_args(argv)
cmticee5bc63b2015-05-27 16:59:37 -0700482
Caroline Ticea4486452015-12-08 13:43:23 -0800483 if not options.remote and options.cmd != 'status':
484 parser.error('No machines specified for operation.')
cmticee5bc63b2015-05-27 16:59:37 -0700485
Caroline Ticea4486452015-12-08 13:43:23 -0800486 if not os.path.isdir(options.chromeos_root):
487 parser.error('Cannot find chromeos_root: %s.' % options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700488
Caroline Ticea4486452015-12-08 13:43:23 -0800489 if not options.cmd:
490 parser.error('No operation selected (--list, --status, --lock, --unlock,'
491 ' --add_machine, --remove_machine).')
cmticee5bc63b2015-05-27 16:59:37 -0700492
Caroline Ticea4486452015-12-08 13:43:23 -0800493 machine_list = []
494 if options.remote:
495 machine_list = options.remote.split()
cmticee5bc63b2015-05-27 16:59:37 -0700496
Denis Nikitin9de6ecb2021-11-05 13:31:19 -0700497 lock_manager = LockManager(machine_list, options.force,
498 options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700499
Caroline Ticea4486452015-12-08 13:43:23 -0800500 machine_states = lock_manager.GetMachineStates(cmd=options.cmd)
501 cmd = options.cmd
cmticee5bc63b2015-05-27 16:59:37 -0700502
Caroline Ticea4486452015-12-08 13:43:23 -0800503 if cmd == 'status':
504 lock_manager.ListMachineStates(machine_states)
cmticee5bc63b2015-05-27 16:59:37 -0700505
Caroline Ticea4486452015-12-08 13:43:23 -0800506 elif cmd == 'lock':
507 if not lock_manager.force:
508 lock_manager.CheckMachineLocks(machine_states, cmd)
509 lock_manager.UpdateMachines(True)
cmticee5bc63b2015-05-27 16:59:37 -0700510
Caroline Ticea4486452015-12-08 13:43:23 -0800511 elif cmd == 'unlock':
512 if not lock_manager.force:
513 lock_manager.CheckMachineLocks(machine_states, cmd)
514 lock_manager.UpdateMachines(False)
cmticee5bc63b2015-05-27 16:59:37 -0700515
Caroline Ticea4486452015-12-08 13:43:23 -0800516 return 0
cmticee5bc63b2015-05-27 16:59:37 -0700517
518
519if __name__ == '__main__':
Caroline Ticea4486452015-12-08 13:43:23 -0800520 sys.exit(Main(sys.argv[1:]))