blob: 510d55f23faf8fc1133365c2284150c282b6d1a4 [file] [log] [blame]
Caroline Ticef6ef4392017-04-06 17:16:05 -07001#!/usr/bin/env python2
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
cmticee5bc63b2015-05-27 16:59:37 -070025class AFELockException(Exception):
26 """Base class for exceptions in this module."""
27
28
29class MachineNotPingable(AFELockException):
30 """Raised when machine does not respond to ping."""
31
32
cmticee5bc63b2015-05-27 16:59:37 -070033class LockingError(AFELockException):
34 """Raised when server fails to lock/unlock machine as requested."""
35
36
cmticee5bc63b2015-05-27 16:59:37 -070037class DontOwnLock(AFELockException):
38 """Raised when user attmepts to unlock machine locked by someone else."""
39 # This should not be raised if the user specified '--force'
40
41
42class NoAFEServer(AFELockException):
43 """Raised when cannot find/access the autotest server."""
44
45
46class AFEAccessError(AFELockException):
47 """Raised when cannot get information about lab machine from lab server."""
48
49
Zhizhou Yang5322d4a2019-09-30 13:10:29 -070050class MachineType(enum.Enum):
51 """Enum class to hold machine type."""
52 AFE = 'afe'
53 LOCAL = 'local'
54 SKYLAB = 'skylab'
55
56
cmticee5bc63b2015-05-27 16:59:37 -070057class AFELockManager(object):
58 """Class for locking/unlocking machines vie Autotest Front End servers.
59
Zhizhou Yang4713fd12019-09-24 10:32:00 -070060 This class contains methods for checking the locked status of machines,
61 and for changing the locked status. It handles HW lab machines (both AFE
62 and Skylab), and local machines, using appropriate locking mechanisms for
63 each.
cmticee5bc63b2015-05-27 16:59:37 -070064
65 !!!IMPORTANT NOTE!!! The AFE server can only be called from the main
66 thread/process of a program. If you launch threads and try to call it
67 from a thread, you will get an error. This has to do with restrictions
68 in the Python virtual machine (and signal handling) and cannot be changed.
69 """
70
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070071 SKYLAB_PATH = '/usr/local/bin/skylab'
72 LEASE_MINS = 600
73 SKYLAB_CREDENTIAL = '/usr/local/google/home/mobiletc-prebuild/' \
74 'chromeos-swarming-1adbe355c97c.json'
75 SWARMING = 'chromite/third_party/swarming.client/swarming.py'
76 SUCCESS = 0
cmticee5bc63b2015-05-27 16:59:37 -070077
Luis Lozanof2a3ef42015-12-15 13:49:30 -080078 def __init__(self,
79 remotes,
80 force_option,
81 chromeos_root,
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070082 locks_dir='',
Luis Lozanof2a3ef42015-12-15 13:49:30 -080083 log=None):
cmticee5bc63b2015-05-27 16:59:37 -070084 """Initializes an AFELockManager object.
85
86 Args:
87 remotes: A list of machine names or ip addresses to be managed. Names
Caroline Ticea4486452015-12-08 13:43:23 -080088 and ip addresses should be represented as strings. If the list is
89 empty, the lock manager will get all known machines.
90 force_option: A Boolean indicating whether or not to force an unlock of
cmticee5bc63b2015-05-27 16:59:37 -070091 a machine that was locked by someone else.
92 chromeos_root: The ChromeOS chroot to use for the autotest scripts.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070093 locks_dir: A directory used for file locking local devices.
cmticee5bc63b2015-05-27 16:59:37 -070094 log: If not None, this is the logger object to be used for writing out
95 informational output messages. It is expected to be an instance of
Caroline Ticea8af9a72016-07-20 12:52:59 -070096 Logger class from cros_utils/logger.py.
cmticee5bc63b2015-05-27 16:59:37 -070097 """
98 self.chromeos_root = chromeos_root
99 self.user = getpass.getuser()
100 self.logger = log or logger.GetLogger()
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700101 self.ce = command_executer.GetCommandExecuter(self.logger)
cmticee5bc63b2015-05-27 16:59:37 -0700102 autotest_path = os.path.join(chromeos_root,
103 'src/third_party/autotest/files')
104
cmticed1172b42015-06-12 15:14:09 -0700105 sys.path.append(chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700106 sys.path.append(autotest_path)
107 sys.path.append(os.path.join(autotest_path, 'server', 'cros'))
108
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700109 self.locks_dir = locks_dir
110
cmticee5bc63b2015-05-27 16:59:37 -0700111 # We have to wait to do these imports until the paths above have
112 # been fixed.
Yunlian Jiangd97422a2015-12-16 11:06:13 -0800113 # pylint: disable=import-error
cmticee5bc63b2015-05-27 16:59:37 -0700114 from client import setup_modules
Caroline Tice6b161382016-09-15 15:03:46 -0700115 setup_modules.setup(
116 base_path=autotest_path, root_module_name='autotest_lib')
cmticee5bc63b2015-05-27 16:59:37 -0700117
118 from dynamic_suite import frontend_wrappers
119
Caroline Tice6b161382016-09-15 15:03:46 -0700120 self.afe = frontend_wrappers.RetryingAFE(
121 timeout_min=30, delay_sec=10, debug=False, server='cautotest')
122
Caroline Tice6b161382016-09-15 15:03:46 -0700123 self.machines = list(set(remotes)) or []
124 self.toolchain_lab_machines = self.GetAllToolchainLabMachines()
Caroline Tice6b161382016-09-15 15:03:46 -0700125
cmticee5bc63b2015-05-27 16:59:37 -0700126 if not self.machines:
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700127 self.machines = self.toolchain_lab_machines
Caroline Tice6b161382016-09-15 15:03:46 -0700128 self.force = force_option
129
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700130 self.local_machines = []
131 self.skylab_machines = []
132
cmticee5bc63b2015-05-27 16:59:37 -0700133 def CheckMachine(self, machine, error_msg):
134 """Verifies that machine is responding to ping.
135
136 Args:
137 machine: String containing the name or ip address of machine to check.
138 error_msg: Message to print if ping fails.
139
140 Raises:
141 MachineNotPingable: If machine is not responding to 'ping'
142 """
143 if not machines.MachineIsPingable(machine, logging_level='none'):
Caroline Ticea4486452015-12-08 13:43:23 -0800144 cros_machine = machine + '.cros'
145 if not machines.MachineIsPingable(cros_machine, logging_level='none'):
146 raise MachineNotPingable(error_msg)
cmticee5bc63b2015-05-27 16:59:37 -0700147
cmticee5bc63b2015-05-27 16:59:37 -0700148 def GetAllToolchainLabMachines(self):
149 """Gets a list of all the toolchain machines in the ChromeOS HW lab.
150
151 Returns:
152 A list of names of the toolchain machines in the ChromeOS HW lab.
153 """
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800154 machines_file = os.path.join(
155 os.path.dirname(__file__), 'crosperf', 'default_remotes')
cmticee5bc63b2015-05-27 16:59:37 -0700156 machine_list = []
157 with open(machines_file, 'r') as input_file:
158 lines = input_file.readlines()
159 for line in lines:
Caroline Ticea4486452015-12-08 13:43:23 -0800160 _, remotes = line.split(':')
cmticee5bc63b2015-05-27 16:59:37 -0700161 remotes = remotes.strip()
162 for r in remotes.split():
163 machine_list.append(r.strip())
164 return machine_list
165
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700166 def GetMachineType(self, m):
167 """Get where the machine is located.
cmticee5bc63b2015-05-27 16:59:37 -0700168
Caroline Ticea4486452015-12-08 13:43:23 -0800169 Args:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700170 m: String containing the name or ip address of machine.
171
172 Returns:
173 Value of the type in MachineType Enum.
cmticee5bc63b2015-05-27 16:59:37 -0700174 """
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700175 if m in self.local_machines:
176 return MachineType.LOCAL
177 if m in self.skylab_machines:
178 return MachineType.SKYLAB
179 return MachineType.AFE
180
181 def PrintStatusHeader(self):
182 """Prints the status header lines for machines."""
183 print('\nMachine (Board)\t\t\t\t\tStatus')
184 print('---------------\t\t\t\t\t------')
185
186 def PrintStatus(self, m, state, machine_type):
187 """Prints status for a single machine.
188
189 Args:
190 m: String containing the name or ip address of machine.
191 state: A dictionary of the current state of the machine.
192 machine_type: MachineType to determine where the machine is located.
193 """
194 if state['locked']:
195 if (machine_type == MachineType.AFE and
196 m not in self.toolchain_lab_machines):
197 m += '.cros'
198 print('%s (%s)\t%slocked by %s since %s' %
199 (m, state['board'], '\t\t\t' if machine_type == MachineType.LOCAL
200 else '', state['locked_by'], state['lock_time']))
cmticee5bc63b2015-05-27 16:59:37 -0700201 else:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700202 print(
203 '%s (%s)\t\t%sunlocked' % (m, state['board'], '\t\t' if
204 machine_type == MachineType.LOCAL else ''))
cmticee5bc63b2015-05-27 16:59:37 -0700205
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700206 def AddMachineToLocal(self, machine):
207 """Adds a machine to local machine list.
208
209 Args:
210 machine: The machine to be added.
211 """
212 if machine not in self.local_machines:
213 self.local_machines.append(machine)
214
215 def AddMachineToSkylab(self, machine):
216 """Adds a machine to skylab machine list.
217
218 Args:
219 machine: The machine to be added.
220 """
221 if machine not in self.skylab_machines:
222 self.skylab_machines.append(machine)
223
cmticee5bc63b2015-05-27 16:59:37 -0700224 def ListMachineStates(self, machine_states):
225 """Gets and prints the current status for a list of machines.
226
227 Prints out the current status for all of the machines in the current
228 AFELockManager's list of machines (set when the object is initialized).
229
230 Args:
231 machine_states: A dictionary of the current state of every machine in
232 the current AFELockManager's list of machines. Normally obtained by
233 calling AFELockManager::GetMachineStates.
234 """
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700235 self.PrintStatusHeader()
cmticee5bc63b2015-05-27 16:59:37 -0700236 for m in machine_states:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700237 machine_type = self.GetMachineType(m)
238 state = machine_states[m]
239 self.PrintStatus(m, state, machine_type)
cmticee5bc63b2015-05-27 16:59:37 -0700240
cmticee5bc63b2015-05-27 16:59:37 -0700241 def UpdateLockInAFE(self, should_lock_machine, machine):
242 """Calls an AFE server to lock/unlock a machine.
243
244 Args:
245 should_lock_machine: Boolean indicating whether to lock the machine (True)
246 or unlock the machine (False).
247 machine: The machine to update.
248
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700249 Returns:
250 True if requested action succeeded, else False.
cmticee5bc63b2015-05-27 16:59:37 -0700251 """
cmticee5bc63b2015-05-27 16:59:37 -0700252 kwargs = {'locked': should_lock_machine}
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700253 if should_lock_machine:
254 kwargs['lock_reason'] = 'toolchain user request (%s)' % self.user
cmticee5bc63b2015-05-27 16:59:37 -0700255
Caroline Tice3f432712015-12-07 14:51:53 -0800256 cros_name = machine + '.cros'
257 if cros_name in self.toolchain_lab_machines:
Caroline Ticea4486452015-12-08 13:43:23 -0800258 machine = cros_name
cmticee5bc63b2015-05-27 16:59:37 -0700259 if machine in self.toolchain_lab_machines:
260 m = machine.split('.')[0]
cmticee5bc63b2015-05-27 16:59:37 -0700261 afe_server = self.afe
cmticee5bc63b2015-05-27 16:59:37 -0700262
263 try:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700264 afe_server.run(
265 'modify_hosts',
266 host_filter_data={'hostname__in': [m]},
267 update_data=kwargs)
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700268 except Exception:
269 return False
270 return True
271
272 def UpdateLockInSkylab(self, should_lock_machine, machine):
273 """Ask skylab to lease/release a machine.
274
275 Args:
276 should_lock_machine: Boolean indicating whether to lock the machine (True)
277 or unlock the machine (False).
278 machine: The machine to update.
279
280 Returns:
281 True if requested action succeeded, else False.
282 """
283 try:
284 if should_lock_machine:
285 ret = self.LeaseSkylabMachine(machine)
286 else:
287 ret = self.ReleaseSkylabMachine(machine)
288 except Exception:
289 return False
290 return ret
291
292 def UpdateFileLock(self, should_lock_machine, machine):
293 """Use file lock for local machines,
294
295 Args:
296 should_lock_machine: Boolean indicating whether to lock the machine (True)
297 or unlock the machine (False).
298 machine: The machine to update.
299
300 Returns:
301 True if requested action succeeded, else False.
302 """
303 try:
304 if should_lock_machine:
305 ret = file_lock_machine.Machine(machine, self.locks_dir).Lock(
306 True, sys.argv[0])
307 else:
308 ret = file_lock_machine.Machine(machine, self.locks_dir).Unlock(True)
309 except Exception:
310 return False
311 return ret
cmticee5bc63b2015-05-27 16:59:37 -0700312
313 def UpdateMachines(self, lock_machines):
314 """Sets the locked state of the machines to the requested value.
315
316 The machines updated are the ones in self.machines (specified when the
317 class object was intialized).
318
319 Args:
Caroline Ticea4486452015-12-08 13:43:23 -0800320 lock_machines: Boolean indicating whether to lock the machines (True) or
cmticee5bc63b2015-05-27 16:59:37 -0700321 unlock the machines (False).
cmticef3eb8032015-07-27 13:55:52 -0700322
323 Returns:
324 A list of the machines whose state was successfully updated.
cmticee5bc63b2015-05-27 16:59:37 -0700325 """
cmticef3eb8032015-07-27 13:55:52 -0700326 updated_machines = []
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700327 action = 'Locking' if lock_machines else 'Unlocking'
cmticee5bc63b2015-05-27 16:59:37 -0700328 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700329 # TODO(zhizhouy): Handling exceptions with more details when locking
330 # doesn't succeed.
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700331 machine_type = self.GetMachineType(m)
332 if machine_type == MachineType.SKYLAB:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700333 ret = self.UpdateLockInSkylab(lock_machines, m)
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700334 elif machine_type == MachineType.LOCAL:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700335 ret = self.UpdateFileLock(lock_machines, m)
cmticee5bc63b2015-05-27 16:59:37 -0700336 else:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700337 ret = self.UpdateLockInAFE(lock_machines, m)
cmticef3eb8032015-07-27 13:55:52 -0700338
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700339 if ret:
340 self.logger.LogOutput(
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700341 '%s %s machine succeeded: %s.' % (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700342 updated_machines.append(m)
343 else:
344 self.logger.LogOutput(
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700345 '%s %s machine failed: %s.' % (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700346
347 self.machines = updated_machines
cmticef3eb8032015-07-27 13:55:52 -0700348 return updated_machines
349
350 def _InternalRemoveMachine(self, machine):
351 """Remove machine from internal list of machines.
352
353 Args:
354 machine: Name of machine to be removed from internal list.
355 """
356 # Check to see if machine is lab machine and if so, make sure it has
357 # ".cros" on the end.
358 cros_machine = machine
359 if machine.find('rack') > 0 and machine.find('row') > 0:
360 if machine.find('.cros') == -1:
361 cros_machine = cros_machine + '.cros'
362
Caroline Ticef6ef4392017-04-06 17:16:05 -0700363 self.machines = [
364 m for m in self.machines if m != cros_machine and m != machine
365 ]
cmticee5bc63b2015-05-27 16:59:37 -0700366
367 def CheckMachineLocks(self, machine_states, cmd):
368 """Check that every machine in requested list is in the proper state.
369
370 If the cmd is 'unlock' verify that every machine is locked by requestor.
371 If the cmd is 'lock' verify that every machine is currently unlocked.
372
373 Args:
374 machine_states: A dictionary of the current state of every machine in
375 the current AFELockManager's list of machines. Normally obtained by
376 calling AFELockManager::GetMachineStates.
Caroline Ticea4486452015-12-08 13:43:23 -0800377 cmd: The user-requested action for the machines: 'lock' or 'unlock'.
cmticee5bc63b2015-05-27 16:59:37 -0700378
379 Raises:
cmticee5bc63b2015-05-27 16:59:37 -0700380 DontOwnLock: The lock on a requested machine is owned by someone else.
381 """
382 for k, state in machine_states.iteritems():
383 if cmd == 'unlock':
384 if not state['locked']:
cmticef3eb8032015-07-27 13:55:52 -0700385 self.logger.LogWarning('Attempt to unlock already unlocked machine '
386 '(%s).' % k)
387 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700388
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700389 # TODO(zhizhouy): Skylab doesn't support host info such as locked_by.
390 # Need to update this when skylab supports it.
391 if (state['locked'] and state['locked_by'] and
392 state['locked_by'] != self.user):
cmticee5bc63b2015-05-27 16:59:37 -0700393 raise DontOwnLock('Attempt to unlock machine (%s) locked by someone '
394 'else (%s).' % (k, state['locked_by']))
395 elif cmd == 'lock':
396 if state['locked']:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700397 self.logger.LogWarning(
398 'Attempt to lock already locked machine (%s)' % k)
cmticef3eb8032015-07-27 13:55:52 -0700399 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700400
cmticee5bc63b2015-05-27 16:59:37 -0700401 def GetMachineStates(self, cmd=''):
402 """Gets the current state of all the requested machines.
403
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700404 Gets the current state of all the requested machines. Stores the data in a
405 dictionary keyed by machine name.
cmticee5bc63b2015-05-27 16:59:37 -0700406
407 Args:
408 cmd: The command for which we are getting the machine states. This is
409 important because if one of the requested machines is missing we raise
410 an exception, unless the requested command is 'add'.
411
412 Returns:
413 A dictionary of machine states for all the machines in the AFELockManager
414 object.
415
416 Raises:
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700417 NoAFEServer: Cannot find the HW Lab AFE server.
cmticee5bc63b2015-05-27 16:59:37 -0700418 AFEAccessError: An error occurred when querying the server about a
419 machine.
420 """
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700421 if not self.afe:
cmticee5bc63b2015-05-27 16:59:37 -0700422 raise NoAFEServer('Error: Cannot connect to main AFE server.')
423
Caroline Ticea4486452015-12-08 13:43:23 -0800424 machine_list = {}
cmticee5bc63b2015-05-27 16:59:37 -0700425 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700426 # For local or skylab machines, we simply set {'locked': status} for them
427 # TODO(zhizhouy): This is a quick fix since skylab cannot return host info
428 # as afe does. We need to get more info such as locked_by when skylab
429 # supports that.
430 if m in self.local_machines or m in self.skylab_machines:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700431 values = {
432 'locked': 0 if cmd == 'lock' else 1,
433 'board': '??',
434 'locked_by': '',
435 'lock_time': ''
436 }
437 machine_list[m] = values
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700438 else:
439 # For autotest machines, we use afe APIs to get locking info.
cmticee5bc63b2015-05-27 16:59:37 -0700440 mod_host = m.split('.')[0]
441 host_info = self.afe.get_hosts(hostname=mod_host)
442 if not host_info:
443 raise AFEAccessError('Unable to get information about %s from main'
444 ' autotest server.' % m)
cmticee5bc63b2015-05-27 16:59:37 -0700445 host_info = host_info[0]
446 name = host_info.hostname
447 values = {}
448 values['board'] = host_info.platform if host_info.platform else '??'
449 values['locked'] = host_info.locked
450 if host_info.locked:
Caroline Ticea4486452015-12-08 13:43:23 -0800451 values['locked_by'] = host_info.locked_by
452 values['lock_time'] = host_info.lock_time
cmticee5bc63b2015-05-27 16:59:37 -0700453 else:
Caroline Ticea4486452015-12-08 13:43:23 -0800454 values['locked_by'] = ''
455 values['lock_time'] = ''
456 machine_list[name] = values
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700457
Caroline Ticea4486452015-12-08 13:43:23 -0800458 return machine_list
cmticee5bc63b2015-05-27 16:59:37 -0700459
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700460 def CheckMachineInSkylab(self, machine):
461 """Run command to check if machine is in Skylab or not.
462
463 Returns:
464 True if machine in skylab, else False
465 """
466 credential = ''
467 if os.path.exists(self.SKYLAB_CREDENTIAL):
468 credential = '--auth-service-account-json %s' % self.SKYLAB_CREDENTIAL
469 swarming = os.path.join(self.chromeos_root, self.SWARMING)
470 cmd = (('%s query --swarming https://chromeos-swarming.appspot.com ' \
471 "%s 'bots/list?is_dead=FALSE&dimensions=dut_name:%s'") % \
472 (swarming,
473 credential,
474 machine.rstrip('.cros')))
475 ret_tup = self.ce.RunCommandWOutput(cmd)
476 # The command will return a json output as stdout. If machine not in skylab
477 # stdout will look like this:
478 # {
479 # "death_timeout": "600",
480 # "now": "TIMESTAMP"
481 # }
482 # Otherwise there will be a tuple starting with 'items', we simply detect
483 # this keyword for result.
484 if 'items' not in ret_tup[1]:
485 return False
486 else:
487 return True
488
489 def LeaseSkylabMachine(self, machine):
490 """Run command to lease dut from skylab.
491
492 Returns:
493 True if succeeded, False if failed.
494 """
495 credential = ''
496 if os.path.exists(self.SKYLAB_CREDENTIAL):
497 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
498 cmd = (('%s lease-dut -minutes %s %s %s') % \
499 (self.SKYLAB_PATH,
500 self.LEASE_MINS,
501 credential,
502 machine.rstrip('.cros')))
503 # Wait 120 seconds for server to start the lease task, if not started,
504 # we will treat it as unavailable.
505 check_interval_time = 120
506 retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time)
507 return retval == self.SUCCESS
508
509 def ReleaseSkylabMachine(self, machine):
510 """Run command to release dut from skylab.
511
512 Returns:
513 True if succeeded, False if failed.
514 """
515 credential = ''
516 if os.path.exists(self.SKYLAB_CREDENTIAL):
517 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
518 cmd = (('%s release-dut %s %s') % \
519 (self.SKYLAB_PATH,
520 credential,
521 machine.rstrip('.cros')))
522 retval = self.ce.RunCommand(cmd)
523 return retval == self.SUCCESS
524
cmticee5bc63b2015-05-27 16:59:37 -0700525
526def Main(argv):
Caroline Ticea4486452015-12-08 13:43:23 -0800527 """Parse the options, initialize lock manager and dispatch proper method.
cmticee5bc63b2015-05-27 16:59:37 -0700528
Caroline Ticea4486452015-12-08 13:43:23 -0800529 Args:
530 argv: The options with which this script was invoked.
cmticee5bc63b2015-05-27 16:59:37 -0700531
Caroline Ticea4486452015-12-08 13:43:23 -0800532 Returns:
533 0 unless an exception is raised.
534 """
535 parser = argparse.ArgumentParser()
cmticee5bc63b2015-05-27 16:59:37 -0700536
Caroline Tice6b161382016-09-15 15:03:46 -0700537 parser.add_argument(
538 '--list',
539 dest='cmd',
540 action='store_const',
541 const='status',
542 help='List current status of all known machines.')
543 parser.add_argument(
544 '--lock',
545 dest='cmd',
546 action='store_const',
547 const='lock',
548 help='Lock given machine(s).')
549 parser.add_argument(
550 '--unlock',
551 dest='cmd',
552 action='store_const',
553 const='unlock',
554 help='Unlock given machine(s).')
555 parser.add_argument(
556 '--status',
557 dest='cmd',
558 action='store_const',
559 const='status',
560 help='List current status of given machine(s).')
561 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700562 '--remote', dest='remote', help='machines on which to operate')
563 parser.add_argument(
564 '--chromeos_root',
565 dest='chromeos_root',
566 required=True,
567 help='ChromeOS root to use for autotest scripts.')
568 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700569 '--force',
570 dest='force',
571 action='store_true',
572 default=False,
573 help='Force lock/unlock of machines, even if not'
574 ' current lock owner.')
cmticee5bc63b2015-05-27 16:59:37 -0700575
Caroline Ticea4486452015-12-08 13:43:23 -0800576 options = parser.parse_args(argv)
cmticee5bc63b2015-05-27 16:59:37 -0700577
Caroline Ticea4486452015-12-08 13:43:23 -0800578 if not options.remote and options.cmd != 'status':
579 parser.error('No machines specified for operation.')
cmticee5bc63b2015-05-27 16:59:37 -0700580
Caroline Ticea4486452015-12-08 13:43:23 -0800581 if not os.path.isdir(options.chromeos_root):
582 parser.error('Cannot find chromeos_root: %s.' % options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700583
Caroline Ticea4486452015-12-08 13:43:23 -0800584 if not options.cmd:
585 parser.error('No operation selected (--list, --status, --lock, --unlock,'
586 ' --add_machine, --remove_machine).')
cmticee5bc63b2015-05-27 16:59:37 -0700587
Caroline Ticea4486452015-12-08 13:43:23 -0800588 machine_list = []
589 if options.remote:
590 machine_list = options.remote.split()
cmticee5bc63b2015-05-27 16:59:37 -0700591
Caroline Ticea4486452015-12-08 13:43:23 -0800592 lock_manager = AFELockManager(machine_list, options.force,
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700593 options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700594
Caroline Ticea4486452015-12-08 13:43:23 -0800595 machine_states = lock_manager.GetMachineStates(cmd=options.cmd)
596 cmd = options.cmd
cmticee5bc63b2015-05-27 16:59:37 -0700597
Caroline Ticea4486452015-12-08 13:43:23 -0800598 if cmd == 'status':
599 lock_manager.ListMachineStates(machine_states)
cmticee5bc63b2015-05-27 16:59:37 -0700600
Caroline Ticea4486452015-12-08 13:43:23 -0800601 elif cmd == 'lock':
602 if not lock_manager.force:
603 lock_manager.CheckMachineLocks(machine_states, cmd)
604 lock_manager.UpdateMachines(True)
cmticee5bc63b2015-05-27 16:59:37 -0700605
Caroline Ticea4486452015-12-08 13:43:23 -0800606 elif cmd == 'unlock':
607 if not lock_manager.force:
608 lock_manager.CheckMachineLocks(machine_states, cmd)
609 lock_manager.UpdateMachines(False)
cmticee5bc63b2015-05-27 16:59:37 -0700610
Caroline Ticea4486452015-12-08 13:43:23 -0800611 elif cmd == 'add':
612 lock_manager.AddMachinesToLocalServer()
cmticee5bc63b2015-05-27 16:59:37 -0700613
Caroline Ticea4486452015-12-08 13:43:23 -0800614 elif cmd == 'remove':
615 lock_manager.RemoveMachinesFromLocalServer()
cmticee5bc63b2015-05-27 16:59:37 -0700616
Caroline Ticea4486452015-12-08 13:43:23 -0800617 return 0
cmticee5bc63b2015-05-27 16:59:37 -0700618
619
620if __name__ == '__main__':
Caroline Ticea4486452015-12-08 13:43:23 -0800621 sys.exit(Main(sys.argv[1:]))