blob: 5f3c7fc61bbac5ff18c68b055e8c7fe86742db03 [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
Zhizhou Yangf7234132019-10-03 14:09:22 -070073 SKYLAB_CREDENTIAL = '/usr/local/google/home/mobiletc-prebuild' \
74 '/sheriff_utils/skylab_credential' \
75 '/chromeos-swarming-credential.json'
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070076 SWARMING = 'chromite/third_party/swarming.client/swarming.py'
77 SUCCESS = 0
cmticee5bc63b2015-05-27 16:59:37 -070078
Luis Lozanof2a3ef42015-12-15 13:49:30 -080079 def __init__(self,
80 remotes,
81 force_option,
82 chromeos_root,
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070083 locks_dir='',
Luis Lozanof2a3ef42015-12-15 13:49:30 -080084 log=None):
cmticee5bc63b2015-05-27 16:59:37 -070085 """Initializes an AFELockManager object.
86
87 Args:
88 remotes: A list of machine names or ip addresses to be managed. Names
Caroline Ticea4486452015-12-08 13:43:23 -080089 and ip addresses should be represented as strings. If the list is
90 empty, the lock manager will get all known machines.
91 force_option: A Boolean indicating whether or not to force an unlock of
cmticee5bc63b2015-05-27 16:59:37 -070092 a machine that was locked by someone else.
93 chromeos_root: The ChromeOS chroot to use for the autotest scripts.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070094 locks_dir: A directory used for file locking local devices.
cmticee5bc63b2015-05-27 16:59:37 -070095 log: If not None, this is the logger object to be used for writing out
96 informational output messages. It is expected to be an instance of
Caroline Ticea8af9a72016-07-20 12:52:59 -070097 Logger class from cros_utils/logger.py.
cmticee5bc63b2015-05-27 16:59:37 -070098 """
99 self.chromeos_root = chromeos_root
100 self.user = getpass.getuser()
101 self.logger = log or logger.GetLogger()
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700102 self.ce = command_executer.GetCommandExecuter(self.logger)
cmticee5bc63b2015-05-27 16:59:37 -0700103 autotest_path = os.path.join(chromeos_root,
104 'src/third_party/autotest/files')
105
cmticed1172b42015-06-12 15:14:09 -0700106 sys.path.append(chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700107 sys.path.append(autotest_path)
108 sys.path.append(os.path.join(autotest_path, 'server', 'cros'))
109
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700110 self.locks_dir = locks_dir
111
cmticee5bc63b2015-05-27 16:59:37 -0700112 # We have to wait to do these imports until the paths above have
113 # been fixed.
Yunlian Jiangd97422a2015-12-16 11:06:13 -0800114 # pylint: disable=import-error
cmticee5bc63b2015-05-27 16:59:37 -0700115 from client import setup_modules
Caroline Tice6b161382016-09-15 15:03:46 -0700116 setup_modules.setup(
117 base_path=autotest_path, root_module_name='autotest_lib')
cmticee5bc63b2015-05-27 16:59:37 -0700118
119 from dynamic_suite import frontend_wrappers
120
Caroline Tice6b161382016-09-15 15:03:46 -0700121 self.afe = frontend_wrappers.RetryingAFE(
122 timeout_min=30, delay_sec=10, debug=False, server='cautotest')
123
Caroline Tice6b161382016-09-15 15:03:46 -0700124 self.machines = list(set(remotes)) or []
125 self.toolchain_lab_machines = self.GetAllToolchainLabMachines()
Caroline Tice6b161382016-09-15 15:03:46 -0700126
cmticee5bc63b2015-05-27 16:59:37 -0700127 if not self.machines:
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700128 self.machines = self.toolchain_lab_machines
Caroline Tice6b161382016-09-15 15:03:46 -0700129 self.force = force_option
130
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700131 self.local_machines = []
132 self.skylab_machines = []
133
cmticee5bc63b2015-05-27 16:59:37 -0700134 def CheckMachine(self, machine, error_msg):
135 """Verifies that machine is responding to ping.
136
137 Args:
138 machine: String containing the name or ip address of machine to check.
139 error_msg: Message to print if ping fails.
140
141 Raises:
142 MachineNotPingable: If machine is not responding to 'ping'
143 """
144 if not machines.MachineIsPingable(machine, logging_level='none'):
Caroline Ticea4486452015-12-08 13:43:23 -0800145 cros_machine = machine + '.cros'
146 if not machines.MachineIsPingable(cros_machine, logging_level='none'):
147 raise MachineNotPingable(error_msg)
cmticee5bc63b2015-05-27 16:59:37 -0700148
cmticee5bc63b2015-05-27 16:59:37 -0700149 def GetAllToolchainLabMachines(self):
150 """Gets a list of all the toolchain machines in the ChromeOS HW lab.
151
152 Returns:
153 A list of names of the toolchain machines in the ChromeOS HW lab.
154 """
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800155 machines_file = os.path.join(
156 os.path.dirname(__file__), 'crosperf', 'default_remotes')
cmticee5bc63b2015-05-27 16:59:37 -0700157 machine_list = []
158 with open(machines_file, 'r') as input_file:
159 lines = input_file.readlines()
160 for line in lines:
Caroline Ticea4486452015-12-08 13:43:23 -0800161 _, remotes = line.split(':')
cmticee5bc63b2015-05-27 16:59:37 -0700162 remotes = remotes.strip()
163 for r in remotes.split():
164 machine_list.append(r.strip())
165 return machine_list
166
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700167 def GetMachineType(self, m):
168 """Get where the machine is located.
cmticee5bc63b2015-05-27 16:59:37 -0700169
Caroline Ticea4486452015-12-08 13:43:23 -0800170 Args:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700171 m: String containing the name or ip address of machine.
172
173 Returns:
174 Value of the type in MachineType Enum.
cmticee5bc63b2015-05-27 16:59:37 -0700175 """
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700176 if m in self.local_machines:
177 return MachineType.LOCAL
178 if m in self.skylab_machines:
179 return MachineType.SKYLAB
180 return MachineType.AFE
181
182 def PrintStatusHeader(self):
183 """Prints the status header lines for machines."""
184 print('\nMachine (Board)\t\t\t\t\tStatus')
185 print('---------------\t\t\t\t\t------')
186
187 def PrintStatus(self, m, state, machine_type):
188 """Prints status for a single machine.
189
190 Args:
191 m: String containing the name or ip address of machine.
192 state: A dictionary of the current state of the machine.
193 machine_type: MachineType to determine where the machine is located.
194 """
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700195 if machine_type == MachineType.AFE and not m.endswith('.cros'):
196 m += '.cros'
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700197 if state['locked']:
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700198 print('%s (%s)\t\t%slocked by %s since %s' %
199 (m, state['board'], '\t\t' if machine_type == MachineType.LOCAL else
200 '', 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
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700256 m = machine.split('.')[0]
257 afe_server = self.afe
cmticee5bc63b2015-05-27 16:59:37 -0700258
259 try:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700260 afe_server.run(
261 'modify_hosts',
262 host_filter_data={'hostname__in': [m]},
263 update_data=kwargs)
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700264 except Exception:
265 return False
266 return True
267
268 def UpdateLockInSkylab(self, should_lock_machine, machine):
269 """Ask skylab to lease/release a machine.
270
271 Args:
272 should_lock_machine: Boolean indicating whether to lock the machine (True)
273 or unlock the machine (False).
274 machine: The machine to update.
275
276 Returns:
277 True if requested action succeeded, else False.
278 """
279 try:
280 if should_lock_machine:
281 ret = self.LeaseSkylabMachine(machine)
282 else:
283 ret = self.ReleaseSkylabMachine(machine)
284 except Exception:
285 return False
286 return ret
287
288 def UpdateFileLock(self, should_lock_machine, machine):
289 """Use file lock for local machines,
290
291 Args:
292 should_lock_machine: Boolean indicating whether to lock the machine (True)
293 or unlock the machine (False).
294 machine: The machine to update.
295
296 Returns:
297 True if requested action succeeded, else False.
298 """
299 try:
300 if should_lock_machine:
301 ret = file_lock_machine.Machine(machine, self.locks_dir).Lock(
302 True, sys.argv[0])
303 else:
304 ret = file_lock_machine.Machine(machine, self.locks_dir).Unlock(True)
305 except Exception:
306 return False
307 return ret
cmticee5bc63b2015-05-27 16:59:37 -0700308
309 def UpdateMachines(self, lock_machines):
310 """Sets the locked state of the machines to the requested value.
311
312 The machines updated are the ones in self.machines (specified when the
313 class object was intialized).
314
315 Args:
Caroline Ticea4486452015-12-08 13:43:23 -0800316 lock_machines: Boolean indicating whether to lock the machines (True) or
cmticee5bc63b2015-05-27 16:59:37 -0700317 unlock the machines (False).
cmticef3eb8032015-07-27 13:55:52 -0700318
319 Returns:
320 A list of the machines whose state was successfully updated.
cmticee5bc63b2015-05-27 16:59:37 -0700321 """
cmticef3eb8032015-07-27 13:55:52 -0700322 updated_machines = []
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700323 action = 'Locking' if lock_machines else 'Unlocking'
cmticee5bc63b2015-05-27 16:59:37 -0700324 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700325 # TODO(zhizhouy): Handling exceptions with more details when locking
326 # doesn't succeed.
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700327 machine_type = self.GetMachineType(m)
328 if machine_type == MachineType.SKYLAB:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700329 ret = self.UpdateLockInSkylab(lock_machines, m)
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700330 elif machine_type == MachineType.LOCAL:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700331 ret = self.UpdateFileLock(lock_machines, m)
cmticee5bc63b2015-05-27 16:59:37 -0700332 else:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700333 ret = self.UpdateLockInAFE(lock_machines, m)
cmticef3eb8032015-07-27 13:55:52 -0700334
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700335 if ret:
336 self.logger.LogOutput(
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700337 '%s %s machine succeeded: %s.' % (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700338 updated_machines.append(m)
339 else:
340 self.logger.LogOutput(
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700341 '%s %s machine failed: %s.' % (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700342
343 self.machines = updated_machines
cmticef3eb8032015-07-27 13:55:52 -0700344 return updated_machines
345
346 def _InternalRemoveMachine(self, machine):
347 """Remove machine from internal list of machines.
348
349 Args:
350 machine: Name of machine to be removed from internal list.
351 """
352 # Check to see if machine is lab machine and if so, make sure it has
353 # ".cros" on the end.
354 cros_machine = machine
355 if machine.find('rack') > 0 and machine.find('row') > 0:
356 if machine.find('.cros') == -1:
357 cros_machine = cros_machine + '.cros'
358
Caroline Ticef6ef4392017-04-06 17:16:05 -0700359 self.machines = [
360 m for m in self.machines if m != cros_machine and m != machine
361 ]
cmticee5bc63b2015-05-27 16:59:37 -0700362
363 def CheckMachineLocks(self, machine_states, cmd):
364 """Check that every machine in requested list is in the proper state.
365
366 If the cmd is 'unlock' verify that every machine is locked by requestor.
367 If the cmd is 'lock' verify that every machine is currently unlocked.
368
369 Args:
370 machine_states: A dictionary of the current state of every machine in
371 the current AFELockManager's list of machines. Normally obtained by
372 calling AFELockManager::GetMachineStates.
Caroline Ticea4486452015-12-08 13:43:23 -0800373 cmd: The user-requested action for the machines: 'lock' or 'unlock'.
cmticee5bc63b2015-05-27 16:59:37 -0700374
375 Raises:
cmticee5bc63b2015-05-27 16:59:37 -0700376 DontOwnLock: The lock on a requested machine is owned by someone else.
377 """
378 for k, state in machine_states.iteritems():
379 if cmd == 'unlock':
380 if not state['locked']:
cmticef3eb8032015-07-27 13:55:52 -0700381 self.logger.LogWarning('Attempt to unlock already unlocked machine '
382 '(%s).' % k)
383 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700384
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700385 # TODO(zhizhouy): Skylab doesn't support host info such as locked_by.
386 # Need to update this when skylab supports it.
387 if (state['locked'] and state['locked_by'] and
388 state['locked_by'] != self.user):
cmticee5bc63b2015-05-27 16:59:37 -0700389 raise DontOwnLock('Attempt to unlock machine (%s) locked by someone '
390 'else (%s).' % (k, state['locked_by']))
391 elif cmd == 'lock':
392 if state['locked']:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700393 self.logger.LogWarning(
394 'Attempt to lock already locked machine (%s)' % k)
cmticef3eb8032015-07-27 13:55:52 -0700395 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700396
cmticee5bc63b2015-05-27 16:59:37 -0700397 def GetMachineStates(self, cmd=''):
398 """Gets the current state of all the requested machines.
399
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700400 Gets the current state of all the requested machines. Stores the data in a
401 dictionary keyed by machine name.
cmticee5bc63b2015-05-27 16:59:37 -0700402
403 Args:
404 cmd: The command for which we are getting the machine states. This is
405 important because if one of the requested machines is missing we raise
406 an exception, unless the requested command is 'add'.
407
408 Returns:
409 A dictionary of machine states for all the machines in the AFELockManager
410 object.
411
412 Raises:
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700413 NoAFEServer: Cannot find the HW Lab AFE server.
cmticee5bc63b2015-05-27 16:59:37 -0700414 AFEAccessError: An error occurred when querying the server about a
415 machine.
416 """
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700417 if not self.afe:
cmticee5bc63b2015-05-27 16:59:37 -0700418 raise NoAFEServer('Error: Cannot connect to main AFE server.')
419
Caroline Ticea4486452015-12-08 13:43:23 -0800420 machine_list = {}
cmticee5bc63b2015-05-27 16:59:37 -0700421 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700422 # For local or skylab machines, we simply set {'locked': status} for them
423 # TODO(zhizhouy): This is a quick fix since skylab cannot return host info
424 # as afe does. We need to get more info such as locked_by when skylab
425 # supports that.
426 if m in self.local_machines or m in self.skylab_machines:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700427 values = {
428 'locked': 0 if cmd == 'lock' else 1,
429 'board': '??',
430 'locked_by': '',
431 'lock_time': ''
432 }
433 machine_list[m] = values
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700434 else:
435 # For autotest machines, we use afe APIs to get locking info.
cmticee5bc63b2015-05-27 16:59:37 -0700436 mod_host = m.split('.')[0]
437 host_info = self.afe.get_hosts(hostname=mod_host)
438 if not host_info:
439 raise AFEAccessError('Unable to get information about %s from main'
440 ' autotest server.' % m)
cmticee5bc63b2015-05-27 16:59:37 -0700441 host_info = host_info[0]
442 name = host_info.hostname
443 values = {}
444 values['board'] = host_info.platform if host_info.platform else '??'
445 values['locked'] = host_info.locked
446 if host_info.locked:
Caroline Ticea4486452015-12-08 13:43:23 -0800447 values['locked_by'] = host_info.locked_by
448 values['lock_time'] = host_info.lock_time
cmticee5bc63b2015-05-27 16:59:37 -0700449 else:
Caroline Ticea4486452015-12-08 13:43:23 -0800450 values['locked_by'] = ''
451 values['lock_time'] = ''
452 machine_list[name] = values
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700453
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700454 self.ListMachineStates(machine_list)
455
Caroline Ticea4486452015-12-08 13:43:23 -0800456 return machine_list
cmticee5bc63b2015-05-27 16:59:37 -0700457
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700458 def CheckMachineInSkylab(self, machine):
459 """Run command to check if machine is in Skylab or not.
460
461 Returns:
462 True if machine in skylab, else False
463 """
464 credential = ''
465 if os.path.exists(self.SKYLAB_CREDENTIAL):
466 credential = '--auth-service-account-json %s' % self.SKYLAB_CREDENTIAL
467 swarming = os.path.join(self.chromeos_root, self.SWARMING)
468 cmd = (('%s query --swarming https://chromeos-swarming.appspot.com ' \
469 "%s 'bots/list?is_dead=FALSE&dimensions=dut_name:%s'") % \
470 (swarming,
471 credential,
472 machine.rstrip('.cros')))
473 ret_tup = self.ce.RunCommandWOutput(cmd)
474 # The command will return a json output as stdout. If machine not in skylab
475 # stdout will look like this:
476 # {
477 # "death_timeout": "600",
478 # "now": "TIMESTAMP"
479 # }
480 # Otherwise there will be a tuple starting with 'items', we simply detect
481 # this keyword for result.
482 if 'items' not in ret_tup[1]:
483 return False
484 else:
485 return True
486
487 def LeaseSkylabMachine(self, machine):
488 """Run command to lease dut from skylab.
489
490 Returns:
491 True if succeeded, False if failed.
492 """
493 credential = ''
494 if os.path.exists(self.SKYLAB_CREDENTIAL):
495 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
496 cmd = (('%s lease-dut -minutes %s %s %s') % \
497 (self.SKYLAB_PATH,
498 self.LEASE_MINS,
499 credential,
500 machine.rstrip('.cros')))
501 # Wait 120 seconds for server to start the lease task, if not started,
502 # we will treat it as unavailable.
503 check_interval_time = 120
504 retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time)
505 return retval == self.SUCCESS
506
507 def ReleaseSkylabMachine(self, machine):
508 """Run command to release dut from skylab.
509
510 Returns:
511 True if succeeded, False if failed.
512 """
513 credential = ''
514 if os.path.exists(self.SKYLAB_CREDENTIAL):
515 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
516 cmd = (('%s release-dut %s %s') % \
517 (self.SKYLAB_PATH,
518 credential,
519 machine.rstrip('.cros')))
520 retval = self.ce.RunCommand(cmd)
521 return retval == self.SUCCESS
522
cmticee5bc63b2015-05-27 16:59:37 -0700523
524def Main(argv):
Caroline Ticea4486452015-12-08 13:43:23 -0800525 """Parse the options, initialize lock manager and dispatch proper method.
cmticee5bc63b2015-05-27 16:59:37 -0700526
Caroline Ticea4486452015-12-08 13:43:23 -0800527 Args:
528 argv: The options with which this script was invoked.
cmticee5bc63b2015-05-27 16:59:37 -0700529
Caroline Ticea4486452015-12-08 13:43:23 -0800530 Returns:
531 0 unless an exception is raised.
532 """
533 parser = argparse.ArgumentParser()
cmticee5bc63b2015-05-27 16:59:37 -0700534
Caroline Tice6b161382016-09-15 15:03:46 -0700535 parser.add_argument(
536 '--list',
537 dest='cmd',
538 action='store_const',
539 const='status',
540 help='List current status of all known machines.')
541 parser.add_argument(
542 '--lock',
543 dest='cmd',
544 action='store_const',
545 const='lock',
546 help='Lock given machine(s).')
547 parser.add_argument(
548 '--unlock',
549 dest='cmd',
550 action='store_const',
551 const='unlock',
552 help='Unlock given machine(s).')
553 parser.add_argument(
554 '--status',
555 dest='cmd',
556 action='store_const',
557 const='status',
558 help='List current status of given machine(s).')
559 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700560 '--remote', dest='remote', help='machines on which to operate')
561 parser.add_argument(
562 '--chromeos_root',
563 dest='chromeos_root',
564 required=True,
565 help='ChromeOS root to use for autotest scripts.')
566 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700567 '--force',
568 dest='force',
569 action='store_true',
570 default=False,
571 help='Force lock/unlock of machines, even if not'
572 ' current lock owner.')
cmticee5bc63b2015-05-27 16:59:37 -0700573
Caroline Ticea4486452015-12-08 13:43:23 -0800574 options = parser.parse_args(argv)
cmticee5bc63b2015-05-27 16:59:37 -0700575
Caroline Ticea4486452015-12-08 13:43:23 -0800576 if not options.remote and options.cmd != 'status':
577 parser.error('No machines specified for operation.')
cmticee5bc63b2015-05-27 16:59:37 -0700578
Caroline Ticea4486452015-12-08 13:43:23 -0800579 if not os.path.isdir(options.chromeos_root):
580 parser.error('Cannot find chromeos_root: %s.' % options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700581
Caroline Ticea4486452015-12-08 13:43:23 -0800582 if not options.cmd:
583 parser.error('No operation selected (--list, --status, --lock, --unlock,'
584 ' --add_machine, --remove_machine).')
cmticee5bc63b2015-05-27 16:59:37 -0700585
Caroline Ticea4486452015-12-08 13:43:23 -0800586 machine_list = []
587 if options.remote:
588 machine_list = options.remote.split()
cmticee5bc63b2015-05-27 16:59:37 -0700589
Caroline Ticea4486452015-12-08 13:43:23 -0800590 lock_manager = AFELockManager(machine_list, options.force,
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700591 options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700592
Caroline Ticea4486452015-12-08 13:43:23 -0800593 machine_states = lock_manager.GetMachineStates(cmd=options.cmd)
594 cmd = options.cmd
cmticee5bc63b2015-05-27 16:59:37 -0700595
Caroline Ticea4486452015-12-08 13:43:23 -0800596 if cmd == 'status':
597 lock_manager.ListMachineStates(machine_states)
cmticee5bc63b2015-05-27 16:59:37 -0700598
Caroline Ticea4486452015-12-08 13:43:23 -0800599 elif cmd == 'lock':
600 if not lock_manager.force:
601 lock_manager.CheckMachineLocks(machine_states, cmd)
602 lock_manager.UpdateMachines(True)
cmticee5bc63b2015-05-27 16:59:37 -0700603
Caroline Ticea4486452015-12-08 13:43:23 -0800604 elif cmd == 'unlock':
605 if not lock_manager.force:
606 lock_manager.CheckMachineLocks(machine_states, cmd)
607 lock_manager.UpdateMachines(False)
cmticee5bc63b2015-05-27 16:59:37 -0700608
Caroline Ticea4486452015-12-08 13:43:23 -0800609 elif cmd == 'add':
610 lock_manager.AddMachinesToLocalServer()
cmticee5bc63b2015-05-27 16:59:37 -0700611
Caroline Ticea4486452015-12-08 13:43:23 -0800612 elif cmd == 'remove':
613 lock_manager.RemoveMachinesFromLocalServer()
cmticee5bc63b2015-05-27 16:59:37 -0700614
Caroline Ticea4486452015-12-08 13:43:23 -0800615 return 0
cmticee5bc63b2015-05-27 16:59:37 -0700616
617
618if __name__ == '__main__':
Caroline Ticea4486452015-12-08 13:43:23 -0800619 sys.exit(Main(sys.argv[1:]))