blob: eca4bd5793f2d032efa9254e07ab1c425ca2ae23 [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
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070025class LockException(Exception):
cmticee5bc63b2015-05-27 16:59:37 -070026 """Base class for exceptions in this module."""
27
28
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070029class MachineNotPingable(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070030 """Raised when machine does not respond to ping."""
31
32
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070033class LockingError(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070034 """Raised when server fails to lock/unlock machine as requested."""
35
36
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070037class DontOwnLock(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070038 """Raised when user attmepts to unlock machine locked by someone else."""
39 # This should not be raised if the user specified '--force'
40
41
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070042class NoAFEServer(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070043 """Raised when cannot find/access the autotest server."""
44
45
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070046class AFEAccessError(LockException):
cmticee5bc63b2015-05-27 16:59:37 -070047 """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
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070057class LockManager(object):
58 """Class for locking/unlocking machines vie three different modes.
cmticee5bc63b2015-05-27 16:59:37 -070059
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'
Zhizhou Yang13446a72019-10-29 16:50:14 -070072
73 # TODO(zhizhouy): lease time may needs to be dynamically adjusted. For now we
74 # set it long enough to cover the period to finish nightly rotation tests.
75 LEASE_MINS = 1440
76
Zhizhou Yangf7234132019-10-03 14:09:22 -070077 SKYLAB_CREDENTIAL = '/usr/local/google/home/mobiletc-prebuild' \
78 '/sheriff_utils/skylab_credential' \
79 '/chromeos-swarming-credential.json'
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070080 SWARMING = 'chromite/third_party/swarming.client/swarming.py'
81 SUCCESS = 0
cmticee5bc63b2015-05-27 16:59:37 -070082
Luis Lozanof2a3ef42015-12-15 13:49:30 -080083 def __init__(self,
84 remotes,
85 force_option,
86 chromeos_root,
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070087 locks_dir='',
Luis Lozanof2a3ef42015-12-15 13:49:30 -080088 log=None):
Zhizhou Yangbf7ee872019-10-14 17:36:09 -070089 """Initializes an LockManager object.
cmticee5bc63b2015-05-27 16:59:37 -070090
91 Args:
92 remotes: A list of machine names or ip addresses to be managed. Names
Caroline Ticea4486452015-12-08 13:43:23 -080093 and ip addresses should be represented as strings. If the list is
94 empty, the lock manager will get all known machines.
95 force_option: A Boolean indicating whether or not to force an unlock of
cmticee5bc63b2015-05-27 16:59:37 -070096 a machine that was locked by someone else.
97 chromeos_root: The ChromeOS chroot to use for the autotest scripts.
Zhizhou Yangcdd9e342019-09-19 20:56:32 -070098 locks_dir: A directory used for file locking local devices.
cmticee5bc63b2015-05-27 16:59:37 -070099 log: If not None, this is the logger object to be used for writing out
100 informational output messages. It is expected to be an instance of
Caroline Ticea8af9a72016-07-20 12:52:59 -0700101 Logger class from cros_utils/logger.py.
cmticee5bc63b2015-05-27 16:59:37 -0700102 """
103 self.chromeos_root = chromeos_root
104 self.user = getpass.getuser()
105 self.logger = log or logger.GetLogger()
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700106 self.ce = command_executer.GetCommandExecuter(self.logger)
cmticee5bc63b2015-05-27 16:59:37 -0700107 autotest_path = os.path.join(chromeos_root,
108 'src/third_party/autotest/files')
109
cmticed1172b42015-06-12 15:14:09 -0700110 sys.path.append(chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700111 sys.path.append(autotest_path)
112 sys.path.append(os.path.join(autotest_path, 'server', 'cros'))
113
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700114 self.locks_dir = locks_dir
115
cmticee5bc63b2015-05-27 16:59:37 -0700116 # We have to wait to do these imports until the paths above have
117 # been fixed.
Yunlian Jiangd97422a2015-12-16 11:06:13 -0800118 # pylint: disable=import-error
cmticee5bc63b2015-05-27 16:59:37 -0700119 from client import setup_modules
Caroline Tice6b161382016-09-15 15:03:46 -0700120 setup_modules.setup(
121 base_path=autotest_path, root_module_name='autotest_lib')
cmticee5bc63b2015-05-27 16:59:37 -0700122
123 from dynamic_suite import frontend_wrappers
124
Caroline Tice6b161382016-09-15 15:03:46 -0700125 self.afe = frontend_wrappers.RetryingAFE(
126 timeout_min=30, delay_sec=10, debug=False, server='cautotest')
127
Caroline Tice6b161382016-09-15 15:03:46 -0700128 self.machines = list(set(remotes)) or []
129 self.toolchain_lab_machines = self.GetAllToolchainLabMachines()
Caroline Tice6b161382016-09-15 15:03:46 -0700130
cmticee5bc63b2015-05-27 16:59:37 -0700131 if not self.machines:
Zhizhou Yang4713fd12019-09-24 10:32:00 -0700132 self.machines = self.toolchain_lab_machines
Caroline Tice6b161382016-09-15 15:03:46 -0700133 self.force = force_option
134
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700135 self.local_machines = []
136 self.skylab_machines = []
137
cmticee5bc63b2015-05-27 16:59:37 -0700138 def CheckMachine(self, machine, error_msg):
139 """Verifies that machine is responding to ping.
140
141 Args:
142 machine: String containing the name or ip address of machine to check.
143 error_msg: Message to print if ping fails.
144
145 Raises:
146 MachineNotPingable: If machine is not responding to 'ping'
147 """
148 if not machines.MachineIsPingable(machine, logging_level='none'):
Caroline Ticea4486452015-12-08 13:43:23 -0800149 cros_machine = machine + '.cros'
150 if not machines.MachineIsPingable(cros_machine, logging_level='none'):
151 raise MachineNotPingable(error_msg)
cmticee5bc63b2015-05-27 16:59:37 -0700152
cmticee5bc63b2015-05-27 16:59:37 -0700153 def GetAllToolchainLabMachines(self):
154 """Gets a list of all the toolchain machines in the ChromeOS HW lab.
155
156 Returns:
157 A list of names of the toolchain machines in the ChromeOS HW lab.
158 """
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800159 machines_file = os.path.join(
160 os.path.dirname(__file__), 'crosperf', 'default_remotes')
cmticee5bc63b2015-05-27 16:59:37 -0700161 machine_list = []
162 with open(machines_file, 'r') as input_file:
163 lines = input_file.readlines()
164 for line in lines:
Caroline Ticea4486452015-12-08 13:43:23 -0800165 _, remotes = line.split(':')
cmticee5bc63b2015-05-27 16:59:37 -0700166 remotes = remotes.strip()
167 for r in remotes.split():
168 machine_list.append(r.strip())
169 return machine_list
170
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700171 def GetMachineType(self, m):
172 """Get where the machine is located.
cmticee5bc63b2015-05-27 16:59:37 -0700173
Caroline Ticea4486452015-12-08 13:43:23 -0800174 Args:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700175 m: String containing the name or ip address of machine.
176
177 Returns:
178 Value of the type in MachineType Enum.
cmticee5bc63b2015-05-27 16:59:37 -0700179 """
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700180 if m in self.local_machines:
181 return MachineType.LOCAL
182 if m in self.skylab_machines:
183 return MachineType.SKYLAB
184 return MachineType.AFE
185
186 def PrintStatusHeader(self):
187 """Prints the status header lines for machines."""
188 print('\nMachine (Board)\t\t\t\t\tStatus')
189 print('---------------\t\t\t\t\t------')
190
191 def PrintStatus(self, m, state, machine_type):
192 """Prints status for a single machine.
193
194 Args:
195 m: String containing the name or ip address of machine.
196 state: A dictionary of the current state of the machine.
197 machine_type: MachineType to determine where the machine is located.
198 """
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700199 if machine_type == MachineType.AFE and not m.endswith('.cros'):
200 m += '.cros'
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700201 if state['locked']:
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700202 print('%s (%s)\t\t%slocked by %s since %s' %
203 (m, state['board'], '\t\t' if machine_type == MachineType.LOCAL else
204 '', state['locked_by'], state['lock_time']))
cmticee5bc63b2015-05-27 16:59:37 -0700205 else:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700206 print(
207 '%s (%s)\t\t%sunlocked' % (m, state['board'], '\t\t' if
208 machine_type == MachineType.LOCAL else ''))
cmticee5bc63b2015-05-27 16:59:37 -0700209
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700210 def AddMachineToLocal(self, machine):
211 """Adds a machine to local machine list.
212
213 Args:
214 machine: The machine to be added.
215 """
216 if machine not in self.local_machines:
217 self.local_machines.append(machine)
218
219 def AddMachineToSkylab(self, machine):
220 """Adds a machine to skylab machine list.
221
222 Args:
223 machine: The machine to be added.
224 """
225 if machine not in self.skylab_machines:
226 self.skylab_machines.append(machine)
227
cmticee5bc63b2015-05-27 16:59:37 -0700228 def ListMachineStates(self, machine_states):
229 """Gets and prints the current status for a list of machines.
230
231 Prints out the current status for all of the machines in the current
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700232 LockManager's list of machines (set when the object is initialized).
cmticee5bc63b2015-05-27 16:59:37 -0700233
234 Args:
235 machine_states: A dictionary of the current state of every machine in
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700236 the current LockManager's list of machines. Normally obtained by
237 calling LockManager::GetMachineStates.
cmticee5bc63b2015-05-27 16:59:37 -0700238 """
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700239 self.PrintStatusHeader()
cmticee5bc63b2015-05-27 16:59:37 -0700240 for m in machine_states:
Zhizhou Yang5322d4a2019-09-30 13:10:29 -0700241 machine_type = self.GetMachineType(m)
242 state = machine_states[m]
243 self.PrintStatus(m, state, machine_type)
cmticee5bc63b2015-05-27 16:59:37 -0700244
cmticee5bc63b2015-05-27 16:59:37 -0700245 def UpdateLockInAFE(self, should_lock_machine, machine):
246 """Calls an AFE server to lock/unlock a machine.
247
248 Args:
249 should_lock_machine: Boolean indicating whether to lock the machine (True)
250 or unlock the machine (False).
251 machine: The machine to update.
252
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700253 Returns:
254 True if requested action succeeded, else False.
cmticee5bc63b2015-05-27 16:59:37 -0700255 """
cmticee5bc63b2015-05-27 16:59:37 -0700256 kwargs = {'locked': should_lock_machine}
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700257 if should_lock_machine:
258 kwargs['lock_reason'] = 'toolchain user request (%s)' % self.user
cmticee5bc63b2015-05-27 16:59:37 -0700259
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700260 m = machine.split('.')[0]
261 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
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700375 the current LockManager's list of machines. Normally obtained by
376 calling LockManager::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:
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700413 A dictionary of machine states for all the machines in the LockManager
cmticee5bc63b2015-05-27 16:59:37 -0700414 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
Zhizhou Yang5a53a332019-10-07 13:27:37 -0700458 self.ListMachineStates(machine_list)
459
Caroline Ticea4486452015-12-08 13:43:23 -0800460 return machine_list
cmticee5bc63b2015-05-27 16:59:37 -0700461
Zhizhou Yangcdd9e342019-09-19 20:56:32 -0700462 def CheckMachineInSkylab(self, machine):
463 """Run command to check if machine is in Skylab or not.
464
465 Returns:
466 True if machine in skylab, else False
467 """
468 credential = ''
469 if os.path.exists(self.SKYLAB_CREDENTIAL):
470 credential = '--auth-service-account-json %s' % self.SKYLAB_CREDENTIAL
471 swarming = os.path.join(self.chromeos_root, self.SWARMING)
472 cmd = (('%s query --swarming https://chromeos-swarming.appspot.com ' \
473 "%s 'bots/list?is_dead=FALSE&dimensions=dut_name:%s'") % \
474 (swarming,
475 credential,
476 machine.rstrip('.cros')))
477 ret_tup = self.ce.RunCommandWOutput(cmd)
478 # The command will return a json output as stdout. If machine not in skylab
479 # stdout will look like this:
480 # {
481 # "death_timeout": "600",
482 # "now": "TIMESTAMP"
483 # }
484 # Otherwise there will be a tuple starting with 'items', we simply detect
485 # this keyword for result.
486 if 'items' not in ret_tup[1]:
487 return False
488 else:
489 return True
490
491 def LeaseSkylabMachine(self, machine):
492 """Run command to lease dut from skylab.
493
494 Returns:
495 True if succeeded, False if failed.
496 """
497 credential = ''
498 if os.path.exists(self.SKYLAB_CREDENTIAL):
499 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
500 cmd = (('%s lease-dut -minutes %s %s %s') % \
501 (self.SKYLAB_PATH,
502 self.LEASE_MINS,
503 credential,
504 machine.rstrip('.cros')))
505 # Wait 120 seconds for server to start the lease task, if not started,
506 # we will treat it as unavailable.
507 check_interval_time = 120
508 retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time)
509 return retval == self.SUCCESS
510
511 def ReleaseSkylabMachine(self, machine):
512 """Run command to release dut from skylab.
513
514 Returns:
515 True if succeeded, False if failed.
516 """
517 credential = ''
518 if os.path.exists(self.SKYLAB_CREDENTIAL):
519 credential = '-service-account-json %s' % self.SKYLAB_CREDENTIAL
520 cmd = (('%s release-dut %s %s') % \
521 (self.SKYLAB_PATH,
522 credential,
523 machine.rstrip('.cros')))
524 retval = self.ce.RunCommand(cmd)
525 return retval == self.SUCCESS
526
cmticee5bc63b2015-05-27 16:59:37 -0700527
528def Main(argv):
Caroline Ticea4486452015-12-08 13:43:23 -0800529 """Parse the options, initialize lock manager and dispatch proper method.
cmticee5bc63b2015-05-27 16:59:37 -0700530
Caroline Ticea4486452015-12-08 13:43:23 -0800531 Args:
532 argv: The options with which this script was invoked.
cmticee5bc63b2015-05-27 16:59:37 -0700533
Caroline Ticea4486452015-12-08 13:43:23 -0800534 Returns:
535 0 unless an exception is raised.
536 """
537 parser = argparse.ArgumentParser()
cmticee5bc63b2015-05-27 16:59:37 -0700538
Caroline Tice6b161382016-09-15 15:03:46 -0700539 parser.add_argument(
540 '--list',
541 dest='cmd',
542 action='store_const',
543 const='status',
544 help='List current status of all known machines.')
545 parser.add_argument(
546 '--lock',
547 dest='cmd',
548 action='store_const',
549 const='lock',
550 help='Lock given machine(s).')
551 parser.add_argument(
552 '--unlock',
553 dest='cmd',
554 action='store_const',
555 const='unlock',
556 help='Unlock given machine(s).')
557 parser.add_argument(
558 '--status',
559 dest='cmd',
560 action='store_const',
561 const='status',
562 help='List current status of given machine(s).')
563 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700564 '--remote', dest='remote', help='machines on which to operate')
565 parser.add_argument(
566 '--chromeos_root',
567 dest='chromeos_root',
568 required=True,
569 help='ChromeOS root to use for autotest scripts.')
570 parser.add_argument(
Caroline Tice6b161382016-09-15 15:03:46 -0700571 '--force',
572 dest='force',
573 action='store_true',
574 default=False,
575 help='Force lock/unlock of machines, even if not'
576 ' current lock owner.')
cmticee5bc63b2015-05-27 16:59:37 -0700577
Caroline Ticea4486452015-12-08 13:43:23 -0800578 options = parser.parse_args(argv)
cmticee5bc63b2015-05-27 16:59:37 -0700579
Caroline Ticea4486452015-12-08 13:43:23 -0800580 if not options.remote and options.cmd != 'status':
581 parser.error('No machines specified for operation.')
cmticee5bc63b2015-05-27 16:59:37 -0700582
Caroline Ticea4486452015-12-08 13:43:23 -0800583 if not os.path.isdir(options.chromeos_root):
584 parser.error('Cannot find chromeos_root: %s.' % options.chromeos_root)
cmticee5bc63b2015-05-27 16:59:37 -0700585
Caroline Ticea4486452015-12-08 13:43:23 -0800586 if not options.cmd:
587 parser.error('No operation selected (--list, --status, --lock, --unlock,'
588 ' --add_machine, --remove_machine).')
cmticee5bc63b2015-05-27 16:59:37 -0700589
Caroline Ticea4486452015-12-08 13:43:23 -0800590 machine_list = []
591 if options.remote:
592 machine_list = options.remote.split()
cmticee5bc63b2015-05-27 16:59:37 -0700593
Zhizhou Yangbf7ee872019-10-14 17:36:09 -0700594 lock_manager = LockManager(machine_list, options.force, 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:]))