blob: 2f35e9bf08f911872017260520c54d6d586755c3 [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 """
195 if state['locked']:
196 if (machine_type == MachineType.AFE and
197 m not in self.toolchain_lab_machines):
198 m += '.cros'
199 print('%s (%s)\t%slocked by %s since %s' %
200 (m, state['board'], '\t\t\t' if machine_type == MachineType.LOCAL
201 else '', state['locked_by'], state['lock_time']))
cmticee5bc63b2015-05-27 16:59:37 -0700202 else:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700203 print(
204 '%s (%s)\t\t%sunlocked' % (m, state['board'], '\t\t' if
205 machine_type == MachineType.LOCAL else ''))
cmticee5bc63b2015-05-27 16:59:37 -0700206
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700207 def AddMachineToLocal(self, machine):
208 """Adds a machine to local machine list.
209
210 Args:
211 machine: The machine to be added.
212 """
213 if machine not in self.local_machines:
214 self.local_machines.append(machine)
215
216 def AddMachineToSkylab(self, machine):
217 """Adds a machine to skylab machine list.
218
219 Args:
220 machine: The machine to be added.
221 """
222 if machine not in self.skylab_machines:
223 self.skylab_machines.append(machine)
224
cmticee5bc63b2015-05-27 16:59:37 -0700225 def ListMachineStates(self, machine_states):
226 """Gets and prints the current status for a list of machines.
227
228 Prints out the current status for all of the machines in the current
229 AFELockManager's list of machines (set when the object is initialized).
230
231 Args:
232 machine_states: A dictionary of the current state of every machine in
233 the current AFELockManager's list of machines. Normally obtained by
234 calling AFELockManager::GetMachineStates.
235 """
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700236 self.PrintStatusHeader()
cmticee5bc63b2015-05-27 16:59:37 -0700237 for m in machine_states:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700238 machine_type = self.GetMachineType(m)
239 state = machine_states[m]
240 self.PrintStatus(m, state, machine_type)
cmticee5bc63b2015-05-27 16:59:37 -0700241
cmticee5bc63b2015-05-27 16:59:37 -0700242 def UpdateLockInAFE(self, should_lock_machine, machine):
243 """Calls an AFE server to lock/unlock a machine.
244
245 Args:
246 should_lock_machine: Boolean indicating whether to lock the machine (True)
247 or unlock the machine (False).
248 machine: The machine to update.
249
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700250 Returns:
251 True if requested action succeeded, else False.
cmticee5bc63b2015-05-27 16:59:37 -0700252 """
cmticee5bc63b2015-05-27 16:59:37 -0700253 kwargs = {'locked': should_lock_machine}
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700254 if should_lock_machine:
255 kwargs['lock_reason'] = 'toolchain user request (%s)' % self.user
cmticee5bc63b2015-05-27 16:59:37 -0700256
Caroline Tice3f432712015-12-07 14:51:53 -0800257 cros_name = machine + '.cros'
258 if cros_name in self.toolchain_lab_machines:
Caroline Ticea4486452015-12-08 13:43:23 -0800259 machine = cros_name
cmticee5bc63b2015-05-27 16:59:37 -0700260 if machine in self.toolchain_lab_machines:
261 m = machine.split('.')[0]
cmticee5bc63b2015-05-27 16:59:37 -0700262 afe_server = self.afe
cmticee5bc63b2015-05-27 16:59:37 -0700263
264 try:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700265 afe_server.run(
266 'modify_hosts',
267 host_filter_data={'hostname__in': [m]},
268 update_data=kwargs)
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700269 except Exception:
270 return False
271 return True
272
273 def UpdateLockInSkylab(self, should_lock_machine, machine):
274 """Ask skylab to lease/release a machine.
275
276 Args:
277 should_lock_machine: Boolean indicating whether to lock the machine (True)
278 or unlock the machine (False).
279 machine: The machine to update.
280
281 Returns:
282 True if requested action succeeded, else False.
283 """
284 try:
285 if should_lock_machine:
286 ret = self.LeaseSkylabMachine(machine)
287 else:
288 ret = self.ReleaseSkylabMachine(machine)
289 except Exception:
290 return False
291 return ret
292
293 def UpdateFileLock(self, should_lock_machine, machine):
294 """Use file lock for local machines,
295
296 Args:
297 should_lock_machine: Boolean indicating whether to lock the machine (True)
298 or unlock the machine (False).
299 machine: The machine to update.
300
301 Returns:
302 True if requested action succeeded, else False.
303 """
304 try:
305 if should_lock_machine:
306 ret = file_lock_machine.Machine(machine, self.locks_dir).Lock(
307 True, sys.argv[0])
308 else:
309 ret = file_lock_machine.Machine(machine, self.locks_dir).Unlock(True)
310 except Exception:
311 return False
312 return ret
cmticee5bc63b2015-05-27 16:59:37 -0700313
314 def UpdateMachines(self, lock_machines):
315 """Sets the locked state of the machines to the requested value.
316
317 The machines updated are the ones in self.machines (specified when the
318 class object was intialized).
319
320 Args:
Caroline Ticea4486452015-12-08 13:43:23 -0800321 lock_machines: Boolean indicating whether to lock the machines (True) or
cmticee5bc63b2015-05-27 16:59:37 -0700322 unlock the machines (False).
cmticef3eb8032015-07-27 13:55:52 -0700323
324 Returns:
325 A list of the machines whose state was successfully updated.
cmticee5bc63b2015-05-27 16:59:37 -0700326 """
cmticef3eb8032015-07-27 13:55:52 -0700327 updated_machines = []
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700328 action = 'Locking' if lock_machines else 'Unlocking'
cmticee5bc63b2015-05-27 16:59:37 -0700329 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700330 # TODO(zhizhouy): Handling exceptions with more details when locking
331 # doesn't succeed.
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700332 machine_type = self.GetMachineType(m)
333 if machine_type == MachineType.SKYLAB:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700334 ret = self.UpdateLockInSkylab(lock_machines, m)
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700335 elif machine_type == MachineType.LOCAL:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700336 ret = self.UpdateFileLock(lock_machines, m)
cmticee5bc63b2015-05-27 16:59:37 -0700337 else:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700338 ret = self.UpdateLockInAFE(lock_machines, m)
cmticef3eb8032015-07-27 13:55:52 -0700339
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700340 if ret:
341 self.logger.LogOutput(
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700342 '%s %s machine succeeded: %s.' % (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700343 updated_machines.append(m)
344 else:
345 self.logger.LogOutput(
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700346 '%s %s machine failed: %s.' % (action, machine_type.value, m))
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700347
348 self.machines = updated_machines
cmticef3eb8032015-07-27 13:55:52 -0700349 return updated_machines
350
351 def _InternalRemoveMachine(self, machine):
352 """Remove machine from internal list of machines.
353
354 Args:
355 machine: Name of machine to be removed from internal list.
356 """
357 # Check to see if machine is lab machine and if so, make sure it has
358 # ".cros" on the end.
359 cros_machine = machine
360 if machine.find('rack') > 0 and machine.find('row') > 0:
361 if machine.find('.cros') == -1:
362 cros_machine = cros_machine + '.cros'
363
Caroline Ticef6ef4392017-04-06 17:16:05 -0700364 self.machines = [
365 m for m in self.machines if m != cros_machine and m != machine
366 ]
cmticee5bc63b2015-05-27 16:59:37 -0700367
368 def CheckMachineLocks(self, machine_states, cmd):
369 """Check that every machine in requested list is in the proper state.
370
371 If the cmd is 'unlock' verify that every machine is locked by requestor.
372 If the cmd is 'lock' verify that every machine is currently unlocked.
373
374 Args:
375 machine_states: A dictionary of the current state of every machine in
376 the current AFELockManager's list of machines. Normally obtained by
377 calling AFELockManager::GetMachineStates.
Caroline Ticea4486452015-12-08 13:43:23 -0800378 cmd: The user-requested action for the machines: 'lock' or 'unlock'.
cmticee5bc63b2015-05-27 16:59:37 -0700379
380 Raises:
cmticee5bc63b2015-05-27 16:59:37 -0700381 DontOwnLock: The lock on a requested machine is owned by someone else.
382 """
383 for k, state in machine_states.iteritems():
384 if cmd == 'unlock':
385 if not state['locked']:
cmticef3eb8032015-07-27 13:55:52 -0700386 self.logger.LogWarning('Attempt to unlock already unlocked machine '
387 '(%s).' % k)
388 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700389
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700390 # TODO(zhizhouy): Skylab doesn't support host info such as locked_by.
391 # Need to update this when skylab supports it.
392 if (state['locked'] and state['locked_by'] and
393 state['locked_by'] != self.user):
cmticee5bc63b2015-05-27 16:59:37 -0700394 raise DontOwnLock('Attempt to unlock machine (%s) locked by someone '
395 'else (%s).' % (k, state['locked_by']))
396 elif cmd == 'lock':
397 if state['locked']:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700398 self.logger.LogWarning(
399 'Attempt to lock already locked machine (%s)' % k)
cmticef3eb8032015-07-27 13:55:52 -0700400 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700401
cmticee5bc63b2015-05-27 16:59:37 -0700402 def GetMachineStates(self, cmd=''):
403 """Gets the current state of all the requested machines.
404
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700405 Gets the current state of all the requested machines. Stores the data in a
406 dictionary keyed by machine name.
cmticee5bc63b2015-05-27 16:59:37 -0700407
408 Args:
409 cmd: The command for which we are getting the machine states. This is
410 important because if one of the requested machines is missing we raise
411 an exception, unless the requested command is 'add'.
412
413 Returns:
414 A dictionary of machine states for all the machines in the AFELockManager
415 object.
416
417 Raises:
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700418 NoAFEServer: Cannot find the HW Lab AFE server.
cmticee5bc63b2015-05-27 16:59:37 -0700419 AFEAccessError: An error occurred when querying the server about a
420 machine.
421 """
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700422 if not self.afe:
cmticee5bc63b2015-05-27 16:59:37 -0700423 raise NoAFEServer('Error: Cannot connect to main AFE server.')
424
Caroline Ticea4486452015-12-08 13:43:23 -0800425 machine_list = {}
cmticee5bc63b2015-05-27 16:59:37 -0700426 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700427 # For local or skylab machines, we simply set {'locked': status} for them
428 # TODO(zhizhouy): This is a quick fix since skylab cannot return host info
429 # as afe does. We need to get more info such as locked_by when skylab
430 # supports that.
431 if m in self.local_machines or m in self.skylab_machines:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700432 values = {
433 'locked': 0 if cmd == 'lock' else 1,
434 'board': '??',
435 'locked_by': '',
436 'lock_time': ''
437 }
438 machine_list[m] = values
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700439 else:
440 # For autotest machines, we use afe APIs to get locking info.
cmticee5bc63b2015-05-27 16:59:37 -0700441 mod_host = m.split('.')[0]
442 host_info = self.afe.get_hosts(hostname=mod_host)
443 if not host_info:
444 raise AFEAccessError('Unable to get information about %s from main'
445 ' autotest server.' % m)
cmticee5bc63b2015-05-27 16:59:37 -0700446 host_info = host_info[0]
447 name = host_info.hostname
448 values = {}
449 values['board'] = host_info.platform if host_info.platform else '??'
450 values['locked'] = host_info.locked
451 if host_info.locked:
Caroline Ticea4486452015-12-08 13:43:23 -0800452 values['locked_by'] = host_info.locked_by
453 values['lock_time'] = host_info.lock_time
cmticee5bc63b2015-05-27 16:59:37 -0700454 else:
Caroline Ticea4486452015-12-08 13:43:23 -0800455 values['locked_by'] = ''
456 values['lock_time'] = ''
457 machine_list[name] = values
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700458
Caroline Ticea4486452015-12-08 13:43:23 -0800459 return machine_list
cmticee5bc63b2015-05-27 16:59:37 -0700460
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700461 def CheckMachineInSkylab(self, machine):
462 """Run command to check if machine is in Skylab or not.
463
464 Returns:
465 True if machine in skylab, else False
466 """
467 credential = ''
468 if os.path.exists(self.SKYLAB_CREDENTIAL):
469 credential = '--auth-service-account-json %s' % self.SKYLAB_CREDENTIAL
470 swarming = os.path.join(self.chromeos_root, self.SWARMING)
471 cmd = (('%s query --swarming https://chromeos-swarming.appspot.com ' \
472 "%s 'bots/list?is_dead=FALSE&dimensions=dut_name:%s'") % \
473 (swarming,
474 credential,
475 machine.rstrip('.cros')))
476 ret_tup = self.ce.RunCommandWOutput(cmd)
477 # The command will return a json output as stdout. If machine not in skylab
478 # stdout will look like this:
479 # {
480 # "death_timeout": "600",
481 # "now": "TIMESTAMP"
482 # }
483 # Otherwise there will be a tuple starting with 'items', we simply detect
484 # this keyword for result.
485 if 'items' not in ret_tup[1]:
486 return False
487 else:
488 return True
489
490 def LeaseSkylabMachine(self, machine):
491 """Run command to lease dut from skylab.
492
493 Returns:
494 True if succeeded, False if failed.
495 """
496 credential = ''
497 if os.path.exists(self.SKYLAB_CREDENTIAL):
498 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
499 cmd = (('%s lease-dut -minutes %s %s %s') % \
500 (self.SKYLAB_PATH,
501 self.LEASE_MINS,
502 credential,
503 machine.rstrip('.cros')))
504 # Wait 120 seconds for server to start the lease task, if not started,
505 # we will treat it as unavailable.
506 check_interval_time = 120
507 retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time)
508 return retval == self.SUCCESS
509
510 def ReleaseSkylabMachine(self, machine):
511 """Run command to release dut from skylab.
512
513 Returns:
514 True if succeeded, False if failed.
515 """
516 credential = ''
517 if os.path.exists(self.SKYLAB_CREDENTIAL):
518 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
519 cmd = (('%s release-dut %s %s') % \
520 (self.SKYLAB_PATH,
521 credential,
522 machine.rstrip('.cros')))
523 retval = self.ce.RunCommand(cmd)
524 return retval == self.SUCCESS
525
cmticee5bc63b2015-05-27 16:59:37 -0700526
527def Main(argv):
Caroline Ticea4486452015-12-08 13:43:23 -0800528 """Parse the options, initialize lock manager and dispatch proper method.
cmticee5bc63b2015-05-27 16:59:37 -0700529
Caroline Ticea4486452015-12-08 13:43:23 -0800530 Args:
531 argv: The options with which this script was invoked.
cmticee5bc63b2015-05-27 16:59:37 -0700532
Caroline Ticea4486452015-12-08 13:43:23 -0800533 Returns:
534 0 unless an exception is raised.
535 """
536 parser = argparse.ArgumentParser()
cmticee5bc63b2015-05-27 16:59:37 -0700537
Caroline Tice6b161382016-09-15 15:03:46 -0700538 parser.add_argument(
539 '--list',
540 dest='cmd',
541 action='store_const',
542 const='status',
543 help='List current status of all known machines.')
544 parser.add_argument(
545 '--lock',
546 dest='cmd',
547 action='store_const',
548 const='lock',
549 help='Lock given machine(s).')
550 parser.add_argument(
551 '--unlock',
552 dest='cmd',
553 action='store_const',
554 const='unlock',
555 help='Unlock given machine(s).')
556 parser.add_argument(
557 '--status',
558 dest='cmd',
559 action='store_const',
560 const='status',
561 help='List current status of given machine(s).')
562 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700563 '--remote', dest='remote', help='machines on which to operate')
564 parser.add_argument(
565 '--chromeos_root',
566 dest='chromeos_root',
567 required=True,
568 help='ChromeOS root to use for autotest scripts.')
569 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700570 '--force',
571 dest='force',
572 action='store_true',
573 default=False,
574 help='Force lock/unlock of machines, even if not'
575 ' current lock owner.')
cmticee5bc63b2015-05-27 16:59:37 -0700576
Caroline Ticea4486452015-12-08 13:43:23 -0800577 options = parser.parse_args(argv)
cmticee5bc63b2015-05-27 16:59:37 -0700578
Caroline Ticea4486452015-12-08 13:43:23 -0800579 if not options.remote and options.cmd != 'status':
580 parser.error('No machines specified for operation.')
cmticee5bc63b2015-05-27 16:59:37 -0700581
Caroline Ticea4486452015-12-08 13:43:23 -0800582 if not os.path.isdir(options.chromeos_root):
583 parser.error('Cannot find chromeos_root: %s.' % options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700584
Caroline Ticea4486452015-12-08 13:43:23 -0800585 if not options.cmd:
586 parser.error('No operation selected (--list, --status, --lock, --unlock,'
587 ' --add_machine, --remove_machine).')
cmticee5bc63b2015-05-27 16:59:37 -0700588
Caroline Ticea4486452015-12-08 13:43:23 -0800589 machine_list = []
590 if options.remote:
591 machine_list = options.remote.split()
cmticee5bc63b2015-05-27 16:59:37 -0700592
Caroline Ticea4486452015-12-08 13:43:23 -0800593 lock_manager = AFELockManager(machine_list, options.force,
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700594 options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700595
Caroline Ticea4486452015-12-08 13:43:23 -0800596 machine_states = lock_manager.GetMachineStates(cmd=options.cmd)
597 cmd = options.cmd
cmticee5bc63b2015-05-27 16:59:37 -0700598
Caroline Ticea4486452015-12-08 13:43:23 -0800599 if cmd == 'status':
600 lock_manager.ListMachineStates(machine_states)
cmticee5bc63b2015-05-27 16:59:37 -0700601
Caroline Ticea4486452015-12-08 13:43:23 -0800602 elif cmd == 'lock':
603 if not lock_manager.force:
604 lock_manager.CheckMachineLocks(machine_states, cmd)
605 lock_manager.UpdateMachines(True)
cmticee5bc63b2015-05-27 16:59:37 -0700606
Caroline Ticea4486452015-12-08 13:43:23 -0800607 elif cmd == 'unlock':
608 if not lock_manager.force:
609 lock_manager.CheckMachineLocks(machine_states, cmd)
610 lock_manager.UpdateMachines(False)
cmticee5bc63b2015-05-27 16:59:37 -0700611
Caroline Ticea4486452015-12-08 13:43:23 -0800612 elif cmd == 'add':
613 lock_manager.AddMachinesToLocalServer()
cmticee5bc63b2015-05-27 16:59:37 -0700614
Caroline Ticea4486452015-12-08 13:43:23 -0800615 elif cmd == 'remove':
616 lock_manager.RemoveMachinesFromLocalServer()
cmticee5bc63b2015-05-27 16:59:37 -0700617
Caroline Ticea4486452015-12-08 13:43:23 -0800618 return 0
cmticee5bc63b2015-05-27 16:59:37 -0700619
620
621if __name__ == '__main__':
Caroline Ticea4486452015-12-08 13:43:23 -0800622 sys.exit(Main(sys.argv[1:]))