blob: 4eae6b069520aa946749df8596e9f210c5601014 [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
13import getpass
14import os
15import sys
cmticee5bc63b2015-05-27 16:59:37 -070016
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070017import file_lock_machine
18
19from cros_utils import command_executer
Caroline Ticea8af9a72016-07-20 12:52:59 -070020from cros_utils import logger
21from cros_utils import machines
cmticee5bc63b2015-05-27 16:59:37 -070022
Luis Lozanof2a3ef42015-12-15 13:49:30 -080023
cmticee5bc63b2015-05-27 16:59:37 -070024class AFELockException(Exception):
25 """Base class for exceptions in this module."""
26
27
28class MachineNotPingable(AFELockException):
29 """Raised when machine does not respond to ping."""
30
31
cmticee5bc63b2015-05-27 16:59:37 -070032class LockingError(AFELockException):
33 """Raised when server fails to lock/unlock machine as requested."""
34
35
cmticee5bc63b2015-05-27 16:59:37 -070036class DontOwnLock(AFELockException):
37 """Raised when user attmepts to unlock machine locked by someone else."""
38 # This should not be raised if the user specified '--force'
39
40
41class NoAFEServer(AFELockException):
42 """Raised when cannot find/access the autotest server."""
43
44
45class AFEAccessError(AFELockException):
46 """Raised when cannot get information about lab machine from lab server."""
47
48
49class AFELockManager(object):
50 """Class for locking/unlocking machines vie Autotest Front End servers.
51
Zhizhou Yang4713fd12019-09-24 10:32:00 -070052 This class contains methods for checking the locked status of machines,
53 and for changing the locked status. It handles HW lab machines (both AFE
54 and Skylab), and local machines, using appropriate locking mechanisms for
55 each.
cmticee5bc63b2015-05-27 16:59:37 -070056
57 !!!IMPORTANT NOTE!!! The AFE server can only be called from the main
58 thread/process of a program. If you launch threads and try to call it
59 from a thread, you will get an error. This has to do with restrictions
60 in the Python virtual machine (and signal handling) and cannot be changed.
61 """
62
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070063 SKYLAB_PATH = '/usr/local/bin/skylab'
64 LEASE_MINS = 600
65 SKYLAB_CREDENTIAL = '/usr/local/google/home/mobiletc-prebuild/' \
66 'chromeos-swarming-1adbe355c97c.json'
67 SWARMING = 'chromite/third_party/swarming.client/swarming.py'
68 SUCCESS = 0
cmticee5bc63b2015-05-27 16:59:37 -070069
Luis Lozanof2a3ef42015-12-15 13:49:30 -080070 def __init__(self,
71 remotes,
72 force_option,
73 chromeos_root,
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070074 locks_dir='',
Luis Lozanof2a3ef42015-12-15 13:49:30 -080075 log=None):
cmticee5bc63b2015-05-27 16:59:37 -070076 """Initializes an AFELockManager object.
77
78 Args:
79 remotes: A list of machine names or ip addresses to be managed. Names
Caroline Ticea4486452015-12-08 13:43:23 -080080 and ip addresses should be represented as strings. If the list is
81 empty, the lock manager will get all known machines.
82 force_option: A Boolean indicating whether or not to force an unlock of
cmticee5bc63b2015-05-27 16:59:37 -070083 a machine that was locked by someone else.
84 chromeos_root: The ChromeOS chroot to use for the autotest scripts.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070085 locks_dir: A directory used for file locking local devices.
cmticee5bc63b2015-05-27 16:59:37 -070086 log: If not None, this is the logger object to be used for writing out
87 informational output messages. It is expected to be an instance of
Caroline Ticea8af9a72016-07-20 12:52:59 -070088 Logger class from cros_utils/logger.py.
cmticee5bc63b2015-05-27 16:59:37 -070089 """
90 self.chromeos_root = chromeos_root
91 self.user = getpass.getuser()
92 self.logger = log or logger.GetLogger()
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070093 self.ce = command_executer.GetCommandExecuter(self.logger)
cmticee5bc63b2015-05-27 16:59:37 -070094 autotest_path = os.path.join(chromeos_root,
95 'src/third_party/autotest/files')
96
cmticed1172b42015-06-12 15:14:09 -070097 sys.path.append(chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -070098 sys.path.append(autotest_path)
99 sys.path.append(os.path.join(autotest_path, 'server', 'cros'))
100
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700101 self.locks_dir = locks_dir
102
cmticee5bc63b2015-05-27 16:59:37 -0700103 # We have to wait to do these imports until the paths above have
104 # been fixed.
Yunlian Jiangd97422a2015-12-16 11:06:13 -0800105 # pylint: disable=import-error
cmticee5bc63b2015-05-27 16:59:37 -0700106 from client import setup_modules
Caroline Tice6b161382016-09-15 15:03:46 -0700107 setup_modules.setup(
108 base_path=autotest_path, root_module_name='autotest_lib')
cmticee5bc63b2015-05-27 16:59:37 -0700109
110 from dynamic_suite import frontend_wrappers
111
Caroline Tice6b161382016-09-15 15:03:46 -0700112 self.afe = frontend_wrappers.RetryingAFE(
113 timeout_min=30, delay_sec=10, debug=False, server='cautotest')
114
Caroline Tice6b161382016-09-15 15:03:46 -0700115 self.machines = list(set(remotes)) or []
116 self.toolchain_lab_machines = self.GetAllToolchainLabMachines()
Caroline Tice6b161382016-09-15 15:03:46 -0700117
cmticee5bc63b2015-05-27 16:59:37 -0700118 if not self.machines:
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700119 self.machines = self.toolchain_lab_machines
Caroline Tice6b161382016-09-15 15:03:46 -0700120 self.force = force_option
121
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700122 self.local_machines = []
123 self.skylab_machines = []
124
cmticee5bc63b2015-05-27 16:59:37 -0700125 def CheckMachine(self, machine, error_msg):
126 """Verifies that machine is responding to ping.
127
128 Args:
129 machine: String containing the name or ip address of machine to check.
130 error_msg: Message to print if ping fails.
131
132 Raises:
133 MachineNotPingable: If machine is not responding to 'ping'
134 """
135 if not machines.MachineIsPingable(machine, logging_level='none'):
Caroline Ticea4486452015-12-08 13:43:23 -0800136 cros_machine = machine + '.cros'
137 if not machines.MachineIsPingable(cros_machine, logging_level='none'):
138 raise MachineNotPingable(error_msg)
cmticee5bc63b2015-05-27 16:59:37 -0700139
cmticee5bc63b2015-05-27 16:59:37 -0700140 def GetAllToolchainLabMachines(self):
141 """Gets a list of all the toolchain machines in the ChromeOS HW lab.
142
143 Returns:
144 A list of names of the toolchain machines in the ChromeOS HW lab.
145 """
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800146 machines_file = os.path.join(
147 os.path.dirname(__file__), 'crosperf', 'default_remotes')
cmticee5bc63b2015-05-27 16:59:37 -0700148 machine_list = []
149 with open(machines_file, 'r') as input_file:
150 lines = input_file.readlines()
151 for line in lines:
Caroline Ticea4486452015-12-08 13:43:23 -0800152 _, remotes = line.split(':')
cmticee5bc63b2015-05-27 16:59:37 -0700153 remotes = remotes.strip()
154 for r in remotes.split():
155 machine_list.append(r.strip())
156 return machine_list
157
cmticee5bc63b2015-05-27 16:59:37 -0700158 def PrintStatusHeader(self, is_lab_machine):
159 """Prints the status header lines for machines.
160
Caroline Ticea4486452015-12-08 13:43:23 -0800161 Args:
162 is_lab_machine: Boolean indicating whether to print HW Lab header or
163 local machine header (different spacing).
cmticee5bc63b2015-05-27 16:59:37 -0700164 """
165 if is_lab_machine:
Caroline Ticea4486452015-12-08 13:43:23 -0800166 print('\nMachine (Board)\t\t\t\t\tStatus')
167 print('---------------\t\t\t\t\t------\n')
cmticee5bc63b2015-05-27 16:59:37 -0700168 else:
Caroline Ticea4486452015-12-08 13:43:23 -0800169 print('\nMachine (Board)\t\tStatus')
170 print('---------------\t\t------\n')
cmticee5bc63b2015-05-27 16:59:37 -0700171
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700172 def AddMachineToLocal(self, machine):
173 """Adds a machine to local machine list.
174
175 Args:
176 machine: The machine to be added.
177 """
178 if machine not in self.local_machines:
179 self.local_machines.append(machine)
180
181 def AddMachineToSkylab(self, machine):
182 """Adds a machine to skylab machine list.
183
184 Args:
185 machine: The machine to be added.
186 """
187 if machine not in self.skylab_machines:
188 self.skylab_machines.append(machine)
189
cmticee5bc63b2015-05-27 16:59:37 -0700190 def ListMachineStates(self, machine_states):
191 """Gets and prints the current status for a list of machines.
192
193 Prints out the current status for all of the machines in the current
194 AFELockManager's list of machines (set when the object is initialized).
195
196 Args:
197 machine_states: A dictionary of the current state of every machine in
198 the current AFELockManager's list of machines. Normally obtained by
199 calling AFELockManager::GetMachineStates.
200 """
201 local_machines = []
202 printed_hdr = False
203 for m in machine_states:
204 cros_name = m + '.cros'
205 if (m in self.toolchain_lab_machines or
206 cros_name in self.toolchain_lab_machines):
Caroline Tice3f432712015-12-07 14:51:53 -0800207 name = m if m in self.toolchain_lab_machines else cros_name
cmticee5bc63b2015-05-27 16:59:37 -0700208 if not printed_hdr:
209 self.PrintStatusHeader(True)
210 printed_hdr = True
211 state = machine_states[m]
212 if state['locked']:
Caroline Ticea4486452015-12-08 13:43:23 -0800213 print('%s (%s)\tlocked by %s since %s' %
214 (name, state['board'], state['locked_by'], state['lock_time']))
cmticee5bc63b2015-05-27 16:59:37 -0700215 else:
Caroline Ticea4486452015-12-08 13:43:23 -0800216 print('%s (%s)\tunlocked' % (name, state['board']))
cmticee5bc63b2015-05-27 16:59:37 -0700217 else:
218 local_machines.append(m)
219
220 if local_machines:
221 self.PrintStatusHeader(False)
222 for m in local_machines:
223 state = machine_states[m]
224 if state['locked']:
Caroline Ticea4486452015-12-08 13:43:23 -0800225 print('%s (%s)\tlocked by %s since %s' %
226 (m, state['board'], state['locked_by'], state['lock_time']))
cmticee5bc63b2015-05-27 16:59:37 -0700227 else:
Caroline Ticea4486452015-12-08 13:43:23 -0800228 print('%s (%s)\tunlocked' % (m, state['board']))
cmticee5bc63b2015-05-27 16:59:37 -0700229
cmticee5bc63b2015-05-27 16:59:37 -0700230 def UpdateLockInAFE(self, should_lock_machine, machine):
231 """Calls an AFE server to lock/unlock a machine.
232
233 Args:
234 should_lock_machine: Boolean indicating whether to lock the machine (True)
235 or unlock the machine (False).
236 machine: The machine to update.
237
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700238 Returns:
239 True if requested action succeeded, else False.
cmticee5bc63b2015-05-27 16:59:37 -0700240 """
cmticee5bc63b2015-05-27 16:59:37 -0700241 kwargs = {'locked': should_lock_machine}
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700242 if should_lock_machine:
243 kwargs['lock_reason'] = 'toolchain user request (%s)' % self.user
cmticee5bc63b2015-05-27 16:59:37 -0700244
Caroline Tice3f432712015-12-07 14:51:53 -0800245 cros_name = machine + '.cros'
246 if cros_name in self.toolchain_lab_machines:
Caroline Ticea4486452015-12-08 13:43:23 -0800247 machine = cros_name
cmticee5bc63b2015-05-27 16:59:37 -0700248 if machine in self.toolchain_lab_machines:
249 m = machine.split('.')[0]
cmticee5bc63b2015-05-27 16:59:37 -0700250 afe_server = self.afe
cmticee5bc63b2015-05-27 16:59:37 -0700251
252 try:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700253 afe_server.run(
254 'modify_hosts',
255 host_filter_data={'hostname__in': [m]},
256 update_data=kwargs)
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700257 except Exception:
258 return False
259 return True
260
261 def UpdateLockInSkylab(self, should_lock_machine, machine):
262 """Ask skylab to lease/release a machine.
263
264 Args:
265 should_lock_machine: Boolean indicating whether to lock the machine (True)
266 or unlock the machine (False).
267 machine: The machine to update.
268
269 Returns:
270 True if requested action succeeded, else False.
271 """
272 try:
273 if should_lock_machine:
274 ret = self.LeaseSkylabMachine(machine)
275 else:
276 ret = self.ReleaseSkylabMachine(machine)
277 except Exception:
278 return False
279 return ret
280
281 def UpdateFileLock(self, should_lock_machine, machine):
282 """Use file lock for local machines,
283
284 Args:
285 should_lock_machine: Boolean indicating whether to lock the machine (True)
286 or unlock the machine (False).
287 machine: The machine to update.
288
289 Returns:
290 True if requested action succeeded, else False.
291 """
292 try:
293 if should_lock_machine:
294 ret = file_lock_machine.Machine(machine, self.locks_dir).Lock(
295 True, sys.argv[0])
296 else:
297 ret = file_lock_machine.Machine(machine, self.locks_dir).Unlock(True)
298 except Exception:
299 return False
300 return ret
cmticee5bc63b2015-05-27 16:59:37 -0700301
302 def UpdateMachines(self, lock_machines):
303 """Sets the locked state of the machines to the requested value.
304
305 The machines updated are the ones in self.machines (specified when the
306 class object was intialized).
307
308 Args:
Caroline Ticea4486452015-12-08 13:43:23 -0800309 lock_machines: Boolean indicating whether to lock the machines (True) or
cmticee5bc63b2015-05-27 16:59:37 -0700310 unlock the machines (False).
cmticef3eb8032015-07-27 13:55:52 -0700311
312 Returns:
313 A list of the machines whose state was successfully updated.
cmticee5bc63b2015-05-27 16:59:37 -0700314 """
cmticef3eb8032015-07-27 13:55:52 -0700315 updated_machines = []
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700316 action = 'Locking' if lock_machines else 'Unlocking'
cmticee5bc63b2015-05-27 16:59:37 -0700317 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700318 # TODO(zhizhouy): Handling exceptions with more details when locking
319 # doesn't succeed.
320 machine_type = 'afe'
321 if m in self.skylab_machines:
322 ret = self.UpdateLockInSkylab(lock_machines, m)
323 machine_type = 'skylab'
324 elif m in self.local_machines:
325 ret = self.UpdateFileLock(lock_machines, m)
326 machine_type = 'local'
cmticee5bc63b2015-05-27 16:59:37 -0700327 else:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700328 ret = self.UpdateLockInAFE(lock_machines, m)
cmticef3eb8032015-07-27 13:55:52 -0700329
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700330 if ret:
331 self.logger.LogOutput(
332 '%s %s machine succeeded: %s.' % (action, machine_type, m))
333 updated_machines.append(m)
334 else:
335 self.logger.LogOutput(
336 '%s %s machine failed: %s.' % (action, machine_type, m))
337
338 self.machines = updated_machines
cmticef3eb8032015-07-27 13:55:52 -0700339 return updated_machines
340
341 def _InternalRemoveMachine(self, machine):
342 """Remove machine from internal list of machines.
343
344 Args:
345 machine: Name of machine to be removed from internal list.
346 """
347 # Check to see if machine is lab machine and if so, make sure it has
348 # ".cros" on the end.
349 cros_machine = machine
350 if machine.find('rack') > 0 and machine.find('row') > 0:
351 if machine.find('.cros') == -1:
352 cros_machine = cros_machine + '.cros'
353
Caroline Ticef6ef4392017-04-06 17:16:05 -0700354 self.machines = [
355 m for m in self.machines if m != cros_machine and m != machine
356 ]
cmticee5bc63b2015-05-27 16:59:37 -0700357
358 def CheckMachineLocks(self, machine_states, cmd):
359 """Check that every machine in requested list is in the proper state.
360
361 If the cmd is 'unlock' verify that every machine is locked by requestor.
362 If the cmd is 'lock' verify that every machine is currently unlocked.
363
364 Args:
365 machine_states: A dictionary of the current state of every machine in
366 the current AFELockManager's list of machines. Normally obtained by
367 calling AFELockManager::GetMachineStates.
Caroline Ticea4486452015-12-08 13:43:23 -0800368 cmd: The user-requested action for the machines: 'lock' or 'unlock'.
cmticee5bc63b2015-05-27 16:59:37 -0700369
370 Raises:
cmticee5bc63b2015-05-27 16:59:37 -0700371 DontOwnLock: The lock on a requested machine is owned by someone else.
372 """
373 for k, state in machine_states.iteritems():
374 if cmd == 'unlock':
375 if not state['locked']:
cmticef3eb8032015-07-27 13:55:52 -0700376 self.logger.LogWarning('Attempt to unlock already unlocked machine '
377 '(%s).' % k)
378 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700379
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700380 if state['locked'] and 'locked_by' in state and \
381 state['locked_by'] != self.user:
cmticee5bc63b2015-05-27 16:59:37 -0700382 raise DontOwnLock('Attempt to unlock machine (%s) locked by someone '
383 'else (%s).' % (k, state['locked_by']))
384 elif cmd == 'lock':
385 if state['locked']:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700386 self.logger.LogWarning(
387 'Attempt to lock already locked machine (%s)' % k)
cmticef3eb8032015-07-27 13:55:52 -0700388 self._InternalRemoveMachine(k)
cmticee5bc63b2015-05-27 16:59:37 -0700389
cmticee5bc63b2015-05-27 16:59:37 -0700390 def GetMachineStates(self, cmd=''):
391 """Gets the current state of all the requested machines.
392
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700393 Gets the current state of all the requested machines. Stores the data in a
394 dictionary keyed by machine name.
cmticee5bc63b2015-05-27 16:59:37 -0700395
396 Args:
397 cmd: The command for which we are getting the machine states. This is
398 important because if one of the requested machines is missing we raise
399 an exception, unless the requested command is 'add'.
400
401 Returns:
402 A dictionary of machine states for all the machines in the AFELockManager
403 object.
404
405 Raises:
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700406 NoAFEServer: Cannot find the HW Lab AFE server.
cmticee5bc63b2015-05-27 16:59:37 -0700407 AFEAccessError: An error occurred when querying the server about a
408 machine.
409 """
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700410 if not self.afe:
cmticee5bc63b2015-05-27 16:59:37 -0700411 raise NoAFEServer('Error: Cannot connect to main AFE server.')
412
Caroline Ticea4486452015-12-08 13:43:23 -0800413 machine_list = {}
cmticee5bc63b2015-05-27 16:59:37 -0700414 for m in self.machines:
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700415 # For local or skylab machines, we simply set {'locked': status} for them
416 # TODO(zhizhouy): This is a quick fix since skylab cannot return host info
417 # as afe does. We need to get more info such as locked_by when skylab
418 # supports that.
419 if m in self.local_machines or m in self.skylab_machines:
420 machine_list[m] = {'locked': 0 if cmd == 'lock' else 1}
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700421 else:
422 # For autotest machines, we use afe APIs to get locking info.
cmticee5bc63b2015-05-27 16:59:37 -0700423 mod_host = m.split('.')[0]
424 host_info = self.afe.get_hosts(hostname=mod_host)
425 if not host_info:
426 raise AFEAccessError('Unable to get information about %s from main'
427 ' autotest server.' % m)
cmticee5bc63b2015-05-27 16:59:37 -0700428 host_info = host_info[0]
429 name = host_info.hostname
430 values = {}
431 values['board'] = host_info.platform if host_info.platform else '??'
432 values['locked'] = host_info.locked
433 if host_info.locked:
Caroline Ticea4486452015-12-08 13:43:23 -0800434 values['locked_by'] = host_info.locked_by
435 values['lock_time'] = host_info.lock_time
cmticee5bc63b2015-05-27 16:59:37 -0700436 else:
Caroline Ticea4486452015-12-08 13:43:23 -0800437 values['locked_by'] = ''
438 values['lock_time'] = ''
439 machine_list[name] = values
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700440
Caroline Ticea4486452015-12-08 13:43:23 -0800441 return machine_list
cmticee5bc63b2015-05-27 16:59:37 -0700442
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700443 def CheckMachineInSkylab(self, machine):
444 """Run command to check if machine is in Skylab or not.
445
446 Returns:
447 True if machine in skylab, else False
448 """
449 credential = ''
450 if os.path.exists(self.SKYLAB_CREDENTIAL):
451 credential = '--auth-service-account-json %s' % self.SKYLAB_CREDENTIAL
452 swarming = os.path.join(self.chromeos_root, self.SWARMING)
453 cmd = (('%s query --swarming https://chromeos-swarming.appspot.com ' \
454 "%s 'bots/list?is_dead=FALSE&dimensions=dut_name:%s'") % \
455 (swarming,
456 credential,
457 machine.rstrip('.cros')))
458 ret_tup = self.ce.RunCommandWOutput(cmd)
459 # The command will return a json output as stdout. If machine not in skylab
460 # stdout will look like this:
461 # {
462 # "death_timeout": "600",
463 # "now": "TIMESTAMP"
464 # }
465 # Otherwise there will be a tuple starting with 'items', we simply detect
466 # this keyword for result.
467 if 'items' not in ret_tup[1]:
468 return False
469 else:
470 return True
471
472 def LeaseSkylabMachine(self, machine):
473 """Run command to lease dut from skylab.
474
475 Returns:
476 True if succeeded, False if failed.
477 """
478 credential = ''
479 if os.path.exists(self.SKYLAB_CREDENTIAL):
480 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
481 cmd = (('%s lease-dut -minutes %s %s %s') % \
482 (self.SKYLAB_PATH,
483 self.LEASE_MINS,
484 credential,
485 machine.rstrip('.cros')))
486 # Wait 120 seconds for server to start the lease task, if not started,
487 # we will treat it as unavailable.
488 check_interval_time = 120
489 retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time)
490 return retval == self.SUCCESS
491
492 def ReleaseSkylabMachine(self, machine):
493 """Run command to release dut from skylab.
494
495 Returns:
496 True if succeeded, False if failed.
497 """
498 credential = ''
499 if os.path.exists(self.SKYLAB_CREDENTIAL):
500 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
501 cmd = (('%s release-dut %s %s') % \
502 (self.SKYLAB_PATH,
503 credential,
504 machine.rstrip('.cros')))
505 retval = self.ce.RunCommand(cmd)
506 return retval == self.SUCCESS
507
cmticee5bc63b2015-05-27 16:59:37 -0700508
509def Main(argv):
Caroline Ticea4486452015-12-08 13:43:23 -0800510 """Parse the options, initialize lock manager and dispatch proper method.
cmticee5bc63b2015-05-27 16:59:37 -0700511
Caroline Ticea4486452015-12-08 13:43:23 -0800512 Args:
513 argv: The options with which this script was invoked.
cmticee5bc63b2015-05-27 16:59:37 -0700514
Caroline Ticea4486452015-12-08 13:43:23 -0800515 Returns:
516 0 unless an exception is raised.
517 """
518 parser = argparse.ArgumentParser()
cmticee5bc63b2015-05-27 16:59:37 -0700519
Caroline Tice6b161382016-09-15 15:03:46 -0700520 parser.add_argument(
521 '--list',
522 dest='cmd',
523 action='store_const',
524 const='status',
525 help='List current status of all known machines.')
526 parser.add_argument(
527 '--lock',
528 dest='cmd',
529 action='store_const',
530 const='lock',
531 help='Lock given machine(s).')
532 parser.add_argument(
533 '--unlock',
534 dest='cmd',
535 action='store_const',
536 const='unlock',
537 help='Unlock given machine(s).')
538 parser.add_argument(
539 '--status',
540 dest='cmd',
541 action='store_const',
542 const='status',
543 help='List current status of given machine(s).')
544 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700545 '--remote', dest='remote', help='machines on which to operate')
546 parser.add_argument(
547 '--chromeos_root',
548 dest='chromeos_root',
549 required=True,
550 help='ChromeOS root to use for autotest scripts.')
551 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700552 '--force',
553 dest='force',
554 action='store_true',
555 default=False,
556 help='Force lock/unlock of machines, even if not'
557 ' current lock owner.')
cmticee5bc63b2015-05-27 16:59:37 -0700558
Caroline Ticea4486452015-12-08 13:43:23 -0800559 options = parser.parse_args(argv)
cmticee5bc63b2015-05-27 16:59:37 -0700560
Caroline Ticea4486452015-12-08 13:43:23 -0800561 if not options.remote and options.cmd != 'status':
562 parser.error('No machines specified for operation.')
cmticee5bc63b2015-05-27 16:59:37 -0700563
Caroline Ticea4486452015-12-08 13:43:23 -0800564 if not os.path.isdir(options.chromeos_root):
565 parser.error('Cannot find chromeos_root: %s.' % options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700566
Caroline Ticea4486452015-12-08 13:43:23 -0800567 if not options.cmd:
568 parser.error('No operation selected (--list, --status, --lock, --unlock,'
569 ' --add_machine, --remove_machine).')
cmticee5bc63b2015-05-27 16:59:37 -0700570
Caroline Ticea4486452015-12-08 13:43:23 -0800571 machine_list = []
572 if options.remote:
573 machine_list = options.remote.split()
cmticee5bc63b2015-05-27 16:59:37 -0700574
Caroline Ticea4486452015-12-08 13:43:23 -0800575 lock_manager = AFELockManager(machine_list, options.force,
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700576 options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700577
Caroline Ticea4486452015-12-08 13:43:23 -0800578 machine_states = lock_manager.GetMachineStates(cmd=options.cmd)
579 cmd = options.cmd
cmticee5bc63b2015-05-27 16:59:37 -0700580
Caroline Ticea4486452015-12-08 13:43:23 -0800581 if cmd == 'status':
582 lock_manager.ListMachineStates(machine_states)
cmticee5bc63b2015-05-27 16:59:37 -0700583
Caroline Ticea4486452015-12-08 13:43:23 -0800584 elif cmd == 'lock':
585 if not lock_manager.force:
586 lock_manager.CheckMachineLocks(machine_states, cmd)
587 lock_manager.UpdateMachines(True)
cmticee5bc63b2015-05-27 16:59:37 -0700588
Caroline Ticea4486452015-12-08 13:43:23 -0800589 elif cmd == 'unlock':
590 if not lock_manager.force:
591 lock_manager.CheckMachineLocks(machine_states, cmd)
592 lock_manager.UpdateMachines(False)
cmticee5bc63b2015-05-27 16:59:37 -0700593
Caroline Ticea4486452015-12-08 13:43:23 -0800594 elif cmd == 'add':
595 lock_manager.AddMachinesToLocalServer()
cmticee5bc63b2015-05-27 16:59:37 -0700596
Caroline Ticea4486452015-12-08 13:43:23 -0800597 elif cmd == 'remove':
598 lock_manager.RemoveMachinesFromLocalServer()
cmticee5bc63b2015-05-27 16:59:37 -0700599
Caroline Ticea4486452015-12-08 13:43:23 -0800600 return 0
cmticee5bc63b2015-05-27 16:59:37 -0700601
602
603if __name__ == '__main__':
Caroline Ticea4486452015-12-08 13:43:23 -0800604 sys.exit(Main(sys.argv[1:]))