aboutsummaryrefslogtreecommitdiff
path: root/lock_machine.py
diff options
context:
space:
mode:
Diffstat (limited to 'lock_machine.py')
-rwxr-xr-xlock_machine.py981
1 files changed, 516 insertions, 465 deletions
diff --git a/lock_machine.py b/lock_machine.py
index 03c8c991..5c2bedb3 100755
--- a/lock_machine.py
+++ b/lock_machine.py
@@ -1,13 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
-# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Copyright 2019 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This module controls locking and unlocking of test machines."""
-from __future__ import print_function
import argparse
import enum
@@ -15,506 +14,558 @@ import getpass
import os
import sys
-import file_lock_machine
-
from cros_utils import command_executer
from cros_utils import logger
from cros_utils import machines
+import file_lock_machine
class LockException(Exception):
- """Base class for exceptions in this module."""
+ """Base class for exceptions in this module."""
class MachineNotPingable(LockException):
- """Raised when machine does not respond to ping."""
+ """Raised when machine does not respond to ping."""
class LockingError(LockException):
- """Raised when server fails to lock/unlock machine as requested."""
+ """Raised when server fails to lock/unlock machine as requested."""
class DontOwnLock(LockException):
- """Raised when user attmepts to unlock machine locked by someone else."""
- # This should not be raised if the user specified '--force'
-
-
-class MachineType(enum.Enum):
- """Enum class to hold machine type."""
- LOCAL = 'local'
- CROSFLEET = 'crosfleet'
-
-
-class LockManager(object):
- """Class for locking/unlocking machines vie three different modes.
-
- This class contains methods for checking the locked status of machines,
- and for changing the locked status. It handles HW lab machines and local
- machines, using appropriate locking mechanisms for each.
- """
-
- CROSFLEET_PATH = 'crosfleet'
-
- # TODO(zhizhouy): lease time may needs to be dynamically adjusted. For now we
- # set it long enough to cover the period to finish nightly rotation tests.
- LEASE_MINS = 1439
-
- CROSFLEET_CREDENTIAL = ('/usr/local/google/home/mobiletc-prebuild'
- '/sheriff_utils/credentials/skylab'
- '/chromeos-swarming-credential.json')
- SWARMING = 'chromite/third_party/swarming.client/swarming.py'
- SUCCESS = 0
-
- def __init__(self,
- remotes,
- force_option,
- chromeos_root,
- locks_dir='',
- log=None):
- """Initializes an LockManager object.
-
- Args:
- remotes: A list of machine names or ip addresses to be managed. Names
- and ip addresses should be represented as strings. If the list is
- empty, the lock manager will get all known machines.
- force_option: A Boolean indicating whether or not to force an unlock of
- a machine that was locked by someone else.
- chromeos_root: The ChromeOS chroot to use for the autotest scripts.
- locks_dir: A directory used for file locking local devices.
- log: If not None, this is the logger object to be used for writing out
- informational output messages. It is expected to be an instance of
- Logger class from cros_utils/logger.py.
- """
- self.chromeos_root = chromeos_root
- self.user = getpass.getuser()
- self.logger = log or logger.GetLogger()
- self.ce = command_executer.GetCommandExecuter(self.logger)
-
- sys.path.append(chromeos_root)
-
- self.locks_dir = locks_dir
-
- self.machines = list(set(remotes)) or []
- self.toolchain_lab_machines = self.GetAllToolchainLabMachines()
-
- if not self.machines:
- self.machines = self.toolchain_lab_machines
- self.force = force_option
-
- self.local_machines = []
- self.crosfleet_machines = []
-
- def CheckMachine(self, machine, error_msg):
- """Verifies that machine is responding to ping.
-
- Args:
- machine: String containing the name or ip address of machine to check.
- error_msg: Message to print if ping fails.
-
- Raises:
- MachineNotPingable: If machine is not responding to 'ping'
- """
- if not machines.MachineIsPingable(machine, logging_level='none'):
- cros_machine = machine + '.cros'
- if not machines.MachineIsPingable(cros_machine, logging_level='none'):
- raise MachineNotPingable(error_msg)
-
- def GetAllToolchainLabMachines(self):
- """Gets a list of all the toolchain machines in the ChromeOS HW lab.
-
- Returns:
- A list of names of the toolchain machines in the ChromeOS HW lab.
- """
- machines_file = os.path.join(os.path.dirname(__file__), 'crosperf',
- 'default_remotes')
- machine_list = []
- with open(machines_file, 'r') as input_file:
- lines = input_file.readlines()
- for line in lines:
- _, remotes = line.split(':')
- remotes = remotes.strip()
- for r in remotes.split():
- machine_list.append(r.strip())
- return machine_list
-
- def GetMachineType(self, m):
- """Get where the machine is located.
-
- Args:
- m: String containing the name or ip address of machine.
-
- Returns:
- Value of the type in MachineType Enum.
- """
- if m in self.local_machines:
- return MachineType.LOCAL
- if m in self.crosfleet_machines:
- return MachineType.CROSFLEET
-
- def PrintStatusHeader(self):
- """Prints the status header lines for machines."""
- print('\nMachine (Board)\t\t\t\t\tStatus')
- print('---------------\t\t\t\t\t------')
+ """Raised when user attmepts to unlock machine locked by someone else."""
- def PrintStatus(self, m, state, machine_type):
- """Prints status for a single machine.
+ # This should not be raised if the user specified '--force'
- Args:
- m: String containing the name or ip address of machine.
- state: A dictionary of the current state of the machine.
- machine_type: MachineType to determine where the machine is located.
- """
- if state['locked']:
- print('%s (%s)\t\t%slocked by %s since %s' %
- (m, state['board'], '\t\t' if machine_type == MachineType.LOCAL
- else '', state['locked_by'], state['lock_time']))
- else:
- print('%s (%s)\t\t%sunlocked' %
- (m, state['board'],
- '\t\t' if machine_type == MachineType.LOCAL else ''))
-
- def AddMachineToLocal(self, machine):
- """Adds a machine to local machine list.
-
- Args:
- machine: The machine to be added.
- """
- if machine not in self.local_machines:
- self.local_machines.append(machine)
- def AddMachineToCrosfleet(self, machine):
- """Adds a machine to crosfleet machine list.
+class MachineType(enum.Enum):
+ """Enum class to hold machine type."""
- Args:
- machine: The machine to be added.
- """
- if machine not in self.crosfleet_machines:
- self.crosfleet_machines.append(machine)
+ LOCAL = "local"
+ CROSFLEET = "crosfleet"
- def ListMachineStates(self, machine_states):
- """Gets and prints the current status for a list of machines.
- Prints out the current status for all of the machines in the current
- LockManager's list of machines (set when the object is initialized).
+class LockManager(object):
+ """Class for locking/unlocking machines vie three different modes.
- Args:
- machine_states: A dictionary of the current state of every machine in
- the current LockManager's list of machines. Normally obtained by
- calling LockManager::GetMachineStates.
+ This class contains methods for checking the locked status of machines,
+ and for changing the locked status. It handles HW lab machines and local
+ machines, using appropriate locking mechanisms for each.
"""
- self.PrintStatusHeader()
- for m in machine_states:
- machine_type = self.GetMachineType(m)
- state = machine_states[m]
- self.PrintStatus(m, state, machine_type)
- def UpdateLockInCrosfleet(self, should_lock_machine, machine):
- """Ask crosfleet to lease/release a machine.
+ CROSFLEET_PATH = "crosfleet"
+
+ # TODO(zhizhouy): lease time may needs to be dynamically adjusted. For now we
+ # set it long enough to cover the period to finish nightly rotation tests.
+ LEASE_MINS = 1439
+
+ CROSFLEET_CREDENTIAL = (
+ "/usr/local/google/home/mobiletc-prebuild"
+ "/sheriff_utils/credentials/skylab"
+ "/chromeos-swarming-credential.json"
+ )
+ SWARMING = "~/cipd_binaries/swarming"
+ SUCCESS = 0
+
+ def __init__(
+ self, remotes, force_option, chromeos_root, locks_dir="", log=None
+ ):
+ """Initializes an LockManager object.
+
+ Args:
+ remotes: A list of machine names or ip addresses to be managed. Names
+ and ip addresses should be represented as strings. If the list is
+ empty, the lock manager will get all known machines.
+ force_option: A Boolean indicating whether or not to force an unlock of
+ a machine that was locked by someone else.
+ chromeos_root: The ChromeOS chroot to use for the autotest scripts.
+ locks_dir: A directory used for file locking local devices.
+ log: If not None, this is the logger object to be used for writing out
+ informational output messages. It is expected to be an instance of
+ Logger class from cros_utils/logger.py.
+ """
+ self.chromeos_root = chromeos_root
+ self.user = getpass.getuser()
+ self.logger = log or logger.GetLogger()
+ self.ce = command_executer.GetCommandExecuter(self.logger)
+
+ sys.path.append(chromeos_root)
+
+ self.locks_dir = locks_dir
+
+ self.machines = list(set(remotes)) or []
+ self.toolchain_lab_machines = self.GetAllToolchainLabMachines()
+
+ if not self.machines:
+ self.machines = self.toolchain_lab_machines
+ self.force = force_option
+
+ self.local_machines = []
+ self.crosfleet_machines = []
+
+ def CheckMachine(self, machine, error_msg):
+ """Verifies that machine is responding to ping.
+
+ Args:
+ machine: String containing the name or ip address of machine to check.
+ error_msg: Message to print if ping fails.
+
+ Raises:
+ MachineNotPingable: If machine is not responding to 'ping'
+ """
+ if not machines.MachineIsPingable(machine, logging_level="none"):
+ cros_machine = machine + ".cros"
+ if not machines.MachineIsPingable(
+ cros_machine, logging_level="none"
+ ):
+ raise MachineNotPingable(error_msg)
+
+ def GetAllToolchainLabMachines(self):
+ """Gets a list of all the toolchain machines in the ChromeOS HW lab.
+
+ Returns:
+ A list of names of the toolchain machines in the ChromeOS HW lab.
+ """
+ machines_file = os.path.join(
+ os.path.dirname(__file__), "crosperf", "default_remotes"
+ )
+ machine_list = []
+ with open(machines_file, "r") as input_file:
+ lines = input_file.readlines()
+ for line in lines:
+ _, remotes = line.split(":")
+ remotes = remotes.strip()
+ for r in remotes.split():
+ machine_list.append(r.strip())
+ return machine_list
+
+ def GetMachineType(self, m):
+ """Get where the machine is located.
+
+ Args:
+ m: String containing the name or ip address of machine.
+
+ Returns:
+ Value of the type in MachineType Enum.
+ """
+ if m in self.local_machines:
+ return MachineType.LOCAL
+ if m in self.crosfleet_machines:
+ return MachineType.CROSFLEET
+
+ def PrintStatusHeader(self):
+ """Prints the status header lines for machines."""
+ print("\nMachine (Board)\t\t\t\t\tStatus")
+ print("---------------\t\t\t\t\t------")
+
+ def PrintStatus(self, m, state, machine_type):
+ """Prints status for a single machine.
+
+ Args:
+ m: String containing the name or ip address of machine.
+ state: A dictionary of the current state of the machine.
+ machine_type: MachineType to determine where the machine is located.
+ """
+ if state["locked"]:
+ print(
+ "%s (%s)\t\t%slocked by %s since %s"
+ % (
+ m,
+ state["board"],
+ "\t\t" if machine_type == MachineType.LOCAL else "",
+ state["locked_by"],
+ state["lock_time"],
+ )
+ )
+ else:
+ print(
+ "%s (%s)\t\t%sunlocked"
+ % (
+ m,
+ state["board"],
+ "\t\t" if machine_type == MachineType.LOCAL else "",
+ )
+ )
+
+ def AddMachineToLocal(self, machine):
+ """Adds a machine to local machine list.
+
+ Args:
+ machine: The machine to be added.
+ """
+ if machine not in self.local_machines:
+ self.local_machines.append(machine)
+
+ def AddMachineToCrosfleet(self, machine):
+ """Adds a machine to crosfleet machine list.
+
+ Args:
+ machine: The machine to be added.
+ """
+ if machine not in self.crosfleet_machines:
+ self.crosfleet_machines.append(machine)
+
+ def ListMachineStates(self, machine_states):
+ """Gets and prints the current status for a list of machines.
+
+ Prints out the current status for all of the machines in the current
+ LockManager's list of machines (set when the object is initialized).
+
+ Args:
+ machine_states: A dictionary of the current state of every machine in
+ the current LockManager's list of machines. Normally obtained by
+ calling LockManager::GetMachineStates.
+ """
+ self.PrintStatusHeader()
+ for m in machine_states:
+ machine_type = self.GetMachineType(m)
+ state = machine_states[m]
+ self.PrintStatus(m, state, machine_type)
+
+ def UpdateLockInCrosfleet(self, should_lock_machine, machine):
+ """Ask crosfleet to lease/release a machine.
+
+ Args:
+ should_lock_machine: Boolean indicating whether to lock the machine (True)
+ or unlock the machine (False).
+ machine: The machine to update.
+
+ Returns:
+ True if requested action succeeded, else False.
+ """
+ try:
+ if should_lock_machine:
+ ret = self.LeaseCrosfleetMachine(machine)
+ else:
+ ret = self.ReleaseCrosfleetMachine(machine)
+ except Exception:
+ return False
+ return ret
+
+ def UpdateFileLock(self, should_lock_machine, machine):
+ """Use file lock for local machines,
+
+ Args:
+ should_lock_machine: Boolean indicating whether to lock the machine (True)
+ or unlock the machine (False).
+ machine: The machine to update.
+
+ Returns:
+ True if requested action succeeded, else False.
+ """
+ try:
+ if should_lock_machine:
+ ret = file_lock_machine.Machine(machine, self.locks_dir).Lock(
+ True, sys.argv[0]
+ )
+ else:
+ ret = file_lock_machine.Machine(machine, self.locks_dir).Unlock(
+ True
+ )
+ except Exception:
+ return False
+ return ret
+
+ def UpdateMachines(self, lock_machines):
+ """Sets the locked state of the machines to the requested value.
+
+ The machines updated are the ones in self.machines (specified when the
+ class object was intialized).
+
+ Args:
+ lock_machines: Boolean indicating whether to lock the machines (True) or
+ unlock the machines (False).
+
+ Returns:
+ A list of the machines whose state was successfully updated.
+ """
+ updated_machines = []
+ action = "Locking" if lock_machines else "Unlocking"
+ for m in self.machines:
+ # TODO(zhizhouy): Handling exceptions with more details when locking
+ # doesn't succeed.
+ machine_type = self.GetMachineType(m)
+ if machine_type == MachineType.CROSFLEET:
+ ret = self.UpdateLockInCrosfleet(lock_machines, m)
+ elif machine_type == MachineType.LOCAL:
+ ret = self.UpdateFileLock(lock_machines, m)
+
+ if ret:
+ self.logger.LogOutput(
+ "%s %s machine succeeded: %s."
+ % (action, machine_type.value, m)
+ )
+ updated_machines.append(m)
+ else:
+ self.logger.LogOutput(
+ "%s %s machine failed: %s."
+ % (action, machine_type.value, m)
+ )
+
+ self.machines = updated_machines
+ return updated_machines
+
+ def _InternalRemoveMachine(self, machine):
+ """Remove machine from internal list of machines.
+
+ Args:
+ machine: Name of machine to be removed from internal list.
+ """
+ # Check to see if machine is lab machine and if so, make sure it has
+ # ".cros" on the end.
+ cros_machine = machine
+ if machine.find("rack") > 0 and machine.find("row") > 0:
+ if machine.find(".cros") == -1:
+ cros_machine = cros_machine + ".cros"
+
+ self.machines = [
+ m for m in self.machines if m not in (cros_machine, machine)
+ ]
+
+ def CheckMachineLocks(self, machine_states, cmd):
+ """Check that every machine in requested list is in the proper state.
+
+ If the cmd is 'unlock' verify that every machine is locked by requestor.
+ If the cmd is 'lock' verify that every machine is currently unlocked.
+
+ Args:
+ machine_states: A dictionary of the current state of every machine in
+ the current LockManager's list of machines. Normally obtained by
+ calling LockManager::GetMachineStates.
+ cmd: The user-requested action for the machines: 'lock' or 'unlock'.
+
+ Raises:
+ DontOwnLock: The lock on a requested machine is owned by someone else.
+ """
+ for k, state in machine_states.items():
+ if cmd == "unlock":
+ if not state["locked"]:
+ self.logger.LogWarning(
+ "Attempt to unlock already unlocked machine "
+ "(%s)." % k
+ )
+ self._InternalRemoveMachine(k)
+
+ # TODO(zhizhouy): Crosfleet doesn't support host info such as locked_by.
+ # Need to update this when crosfleet supports it.
+ if (
+ state["locked"]
+ and state["locked_by"]
+ and state["locked_by"] != self.user
+ ):
+ raise DontOwnLock(
+ "Attempt to unlock machine (%s) locked by someone "
+ "else (%s)." % (k, state["locked_by"])
+ )
+ elif cmd == "lock":
+ if state["locked"]:
+ self.logger.LogWarning(
+ "Attempt to lock already locked machine (%s)" % k
+ )
+ self._InternalRemoveMachine(k)
+
+ def GetMachineStates(self, cmd=""):
+ """Gets the current state of all the requested machines.
+
+ Gets the current state of all the requested machines. Stores the data in a
+ dictionary keyed by machine name.
+
+ Args:
+ cmd: The command for which we are getting the machine states. This is
+ important because if one of the requested machines is missing we raise
+ an exception, unless the requested command is 'add'.
+
+ Returns:
+ A dictionary of machine states for all the machines in the LockManager
+ object.
+ """
+ machine_list = {}
+ for m in self.machines:
+ # For local or crosfleet machines, we simply set {'locked': status} for
+ # them
+ # TODO(zhizhouy): This is a quick fix since crosfleet cannot return host
+ # info as afe does. We need to get more info such as locked_by when
+ # crosfleet supports that.
+ values = {
+ "locked": 0 if cmd == "lock" else 1,
+ "board": "??",
+ "locked_by": "",
+ "lock_time": "",
+ }
+ machine_list[m] = values
+
+ self.ListMachineStates(machine_list)
+
+ return machine_list
+
+ def CheckMachineInCrosfleet(self, machine):
+ """Run command to check if machine is in Crosfleet or not.
+
+ Returns:
+ True if machine in crosfleet, else False
+ """
+ credential = ""
+ if os.path.exists(self.CROSFLEET_CREDENTIAL):
+ credential = "--service-account-json %s" % self.CROSFLEET_CREDENTIAL
+ server = "--server https://chromeos-swarming.appspot.com"
+ dimensions = "--dimension dut_name=%s" % machine.rstrip(".cros")
+
+ cmd = f"{self.SWARMING} bots {server} {credential} {dimensions}"
+ exit_code, stdout, stderr = self.ce.RunCommandWOutput(cmd)
+ if exit_code:
+ raise ValueError(
+ "Querying bots failed (2); stdout: %r; stderr: %r"
+ % (stdout, stderr)
+ )
+
+ # The command will return a json output as stdout. If machine not in
+ # crosfleet, stdout will look like this:
+ # {
+ # "death_timeout": "600",
+ # "now": "TIMESTAMP"
+ # }
+ # Otherwise there will be a tuple starting with 'items', we simply detect
+ # this keyword for result.
+ return stdout != "[]"
+
+ def LeaseCrosfleetMachine(self, machine):
+ """Run command to lease dut from crosfleet.
+
+ Returns:
+ True if succeeded, False if failed.
+ """
+ credential = ""
+ if os.path.exists(self.CROSFLEET_CREDENTIAL):
+ credential = "-service-account-json %s" % self.CROSFLEET_CREDENTIAL
+ cmd = ("%s dut lease -minutes %s %s %s %s") % (
+ self.CROSFLEET_PATH,
+ self.LEASE_MINS,
+ credential,
+ "-host",
+ machine.rstrip(".cros"),
+ )
+ # Wait 8 minutes for server to start the lease task, if not started,
+ # we will treat it as unavailable.
+ check_interval_time = 480
+ retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time)
+ return retval == self.SUCCESS
+
+ def ReleaseCrosfleetMachine(self, machine):
+ """Run command to release dut from crosfleet.
+
+ Returns:
+ True if succeeded, False if failed.
+ """
+ credential = ""
+ if os.path.exists(self.CROSFLEET_CREDENTIAL):
+ credential = "-service-account-json %s" % self.CROSFLEET_CREDENTIAL
+
+ cmd = ("%s dut abandon %s %s") % (
+ self.CROSFLEET_PATH,
+ credential,
+ machine.rstrip(".cros"),
+ )
+ retval = self.ce.RunCommand(cmd)
+ return retval == self.SUCCESS
- Args:
- should_lock_machine: Boolean indicating whether to lock the machine (True)
- or unlock the machine (False).
- machine: The machine to update.
- Returns:
- True if requested action succeeded, else False.
- """
- try:
- if should_lock_machine:
- ret = self.LeaseCrosfleetMachine(machine)
- else:
- ret = self.ReleaseCrosfleetMachine(machine)
- except Exception:
- return False
- return ret
-
- def UpdateFileLock(self, should_lock_machine, machine):
- """Use file lock for local machines,
-
- Args:
- should_lock_machine: Boolean indicating whether to lock the machine (True)
- or unlock the machine (False).
- machine: The machine to update.
-
- Returns:
- True if requested action succeeded, else False.
- """
- try:
- if should_lock_machine:
- ret = file_lock_machine.Machine(machine, self.locks_dir).Lock(
- True, sys.argv[0])
- else:
- ret = file_lock_machine.Machine(machine, self.locks_dir).Unlock(True)
- except Exception:
- return False
- return ret
-
- def UpdateMachines(self, lock_machines):
- """Sets the locked state of the machines to the requested value.
-
- The machines updated are the ones in self.machines (specified when the
- class object was intialized).
+def Main(argv):
+ """Parse the options, initialize lock manager and dispatch proper method.
Args:
- lock_machines: Boolean indicating whether to lock the machines (True) or
- unlock the machines (False).
+ argv: The options with which this script was invoked.
Returns:
- A list of the machines whose state was successfully updated.
+ 0 unless an exception is raised.
"""
- updated_machines = []
- action = 'Locking' if lock_machines else 'Unlocking'
- for m in self.machines:
- # TODO(zhizhouy): Handling exceptions with more details when locking
- # doesn't succeed.
- machine_type = self.GetMachineType(m)
- if machine_type == MachineType.CROSFLEET:
- ret = self.UpdateLockInCrosfleet(lock_machines, m)
- elif machine_type == MachineType.LOCAL:
- ret = self.UpdateFileLock(lock_machines, m)
-
- if ret:
- self.logger.LogOutput('%s %s machine succeeded: %s.' %
- (action, machine_type.value, m))
- updated_machines.append(m)
- else:
- self.logger.LogOutput('%s %s machine failed: %s.' %
- (action, machine_type.value, m))
-
- self.machines = updated_machines
- return updated_machines
-
- def _InternalRemoveMachine(self, machine):
- """Remove machine from internal list of machines.
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument(
+ "--list",
+ dest="cmd",
+ action="store_const",
+ const="status",
+ help="List current status of all known machines.",
+ )
+ parser.add_argument(
+ "--lock",
+ dest="cmd",
+ action="store_const",
+ const="lock",
+ help="Lock given machine(s).",
+ )
+ parser.add_argument(
+ "--unlock",
+ dest="cmd",
+ action="store_const",
+ const="unlock",
+ help="Unlock given machine(s).",
+ )
+ parser.add_argument(
+ "--status",
+ dest="cmd",
+ action="store_const",
+ const="status",
+ help="List current status of given machine(s).",
+ )
+ parser.add_argument(
+ "--remote", dest="remote", help="machines on which to operate"
+ )
+ parser.add_argument(
+ "--chromeos_root",
+ dest="chromeos_root",
+ required=True,
+ help="ChromeOS root to use for autotest scripts.",
+ )
+ parser.add_argument(
+ "--force",
+ dest="force",
+ action="store_true",
+ default=False,
+ help="Force lock/unlock of machines, even if not"
+ " current lock owner.",
+ )
+
+ options = parser.parse_args(argv)
+
+ if not options.remote and options.cmd != "status":
+ parser.error("No machines specified for operation.")
+
+ if not os.path.isdir(options.chromeos_root):
+ parser.error("Cannot find chromeos_root: %s." % options.chromeos_root)
+
+ if not options.cmd:
+ parser.error(
+ "No operation selected (--list, --status, --lock, --unlock,"
+ " --add_machine, --remove_machine)."
+ )
- Args:
- machine: Name of machine to be removed from internal list.
- """
- # Check to see if machine is lab machine and if so, make sure it has
- # ".cros" on the end.
- cros_machine = machine
- if machine.find('rack') > 0 and machine.find('row') > 0:
- if machine.find('.cros') == -1:
- cros_machine = cros_machine + '.cros'
+ machine_list = []
+ if options.remote:
+ machine_list = options.remote.split()
- self.machines = [
- m for m in self.machines if m not in (cros_machine, machine)
- ]
+ lock_manager = LockManager(
+ machine_list, options.force, options.chromeos_root
+ )
- def CheckMachineLocks(self, machine_states, cmd):
- """Check that every machine in requested list is in the proper state.
+ machine_states = lock_manager.GetMachineStates(cmd=options.cmd)
+ cmd = options.cmd
- If the cmd is 'unlock' verify that every machine is locked by requestor.
- If the cmd is 'lock' verify that every machine is currently unlocked.
+ if cmd == "status":
+ lock_manager.ListMachineStates(machine_states)
- Args:
- machine_states: A dictionary of the current state of every machine in
- the current LockManager's list of machines. Normally obtained by
- calling LockManager::GetMachineStates.
- cmd: The user-requested action for the machines: 'lock' or 'unlock'.
+ elif cmd == "lock":
+ if not lock_manager.force:
+ lock_manager.CheckMachineLocks(machine_states, cmd)
+ lock_manager.UpdateMachines(True)
- Raises:
- DontOwnLock: The lock on a requested machine is owned by someone else.
- """
- for k, state in machine_states.items():
- if cmd == 'unlock':
- if not state['locked']:
- self.logger.LogWarning('Attempt to unlock already unlocked machine '
- '(%s).' % k)
- self._InternalRemoveMachine(k)
-
- # TODO(zhizhouy): Crosfleet doesn't support host info such as locked_by.
- # Need to update this when crosfleet supports it.
- if (state['locked'] and state['locked_by']
- and state['locked_by'] != self.user):
- raise DontOwnLock('Attempt to unlock machine (%s) locked by someone '
- 'else (%s).' % (k, state['locked_by']))
- elif cmd == 'lock':
- if state['locked']:
- self.logger.LogWarning(
- 'Attempt to lock already locked machine (%s)' % k)
- self._InternalRemoveMachine(k)
-
- def GetMachineStates(self, cmd=''):
- """Gets the current state of all the requested machines.
-
- Gets the current state of all the requested machines. Stores the data in a
- dictionary keyed by machine name.
+ elif cmd == "unlock":
+ if not lock_manager.force:
+ lock_manager.CheckMachineLocks(machine_states, cmd)
+ lock_manager.UpdateMachines(False)
- Args:
- cmd: The command for which we are getting the machine states. This is
- important because if one of the requested machines is missing we raise
- an exception, unless the requested command is 'add'.
+ return 0
- Returns:
- A dictionary of machine states for all the machines in the LockManager
- object.
- """
- machine_list = {}
- for m in self.machines:
- # For local or crosfleet machines, we simply set {'locked': status} for
- # them
- # TODO(zhizhouy): This is a quick fix since crosfleet cannot return host
- # info as afe does. We need to get more info such as locked_by when
- # crosfleet supports that.
- values = {
- 'locked': 0 if cmd == 'lock' else 1,
- 'board': '??',
- 'locked_by': '',
- 'lock_time': ''
- }
- machine_list[m] = values
-
- self.ListMachineStates(machine_list)
-
- return machine_list
-
- def CheckMachineInCrosfleet(self, machine):
- """Run command to check if machine is in Crosfleet or not.
- Returns:
- True if machine in crosfleet, else False
- """
- credential = ''
- if os.path.exists(self.CROSFLEET_CREDENTIAL):
- credential = '--auth-service-account-json %s' % self.CROSFLEET_CREDENTIAL
- swarming = os.path.join(self.chromeos_root, self.SWARMING)
- # TODO(zhizhouy): Swarming script doesn't support python3 so explicitly
- # launch it with python2 until migrated.
- cmd = (('python2 %s '
- 'query --swarming https://chromeos-swarming.appspot.com '
- "%s 'bots/list?is_dead=FALSE&dimensions=dut_name:%s'") %
- (swarming, credential, machine.rstrip('.cros')))
- exit_code, stdout, stderr = self.ce.RunCommandWOutput(cmd)
- if exit_code:
- raise ValueError('Querying bots failed (2); stdout: %r; stderr: %r' %
- (stdout, stderr))
-
- # The command will return a json output as stdout. If machine not in
- # crosfleet, stdout will look like this:
- # {
- # "death_timeout": "600",
- # "now": "TIMESTAMP"
- # }
- # Otherwise there will be a tuple starting with 'items', we simply detect
- # this keyword for result.
- return 'items' in stdout
-
- def LeaseCrosfleetMachine(self, machine):
- """Run command to lease dut from crosfleet.
-
- Returns:
- True if succeeded, False if failed.
- """
- credential = ''
- if os.path.exists(self.CROSFLEET_CREDENTIAL):
- credential = '-service-account-json %s' % self.CROSFLEET_CREDENTIAL
- cmd = (('%s dut lease -minutes %s %s %s %s') %
- (self.CROSFLEET_PATH, self.LEASE_MINS, credential, '-host'
- if '.cros' in machine else '-board', machine.rstrip('.cros')))
- # Wait 8 minutes for server to start the lease task, if not started,
- # we will treat it as unavailable.
- check_interval_time = 480
- retval = self.ce.RunCommand(cmd, command_timeout=check_interval_time)
- return retval == self.SUCCESS
-
- def ReleaseCrosfleetMachine(self, machine):
- """Run command to release dut from crosfleet.
-
- Returns:
- True if succeeded, False if failed.
- """
- credential = ''
- if os.path.exists(self.CROSFLEET_CREDENTIAL):
- credential = '-service-account-json %s' % self.CROSFLEET_CREDENTIAL
- cmd = (('%s dut abandon %s %s') %
- (self.CROSFLEET_PATH, credential, machine.rstrip('.cros')))
- retval = self.ce.RunCommand(cmd)
- return retval == self.SUCCESS
-
-
-def Main(argv):
- """Parse the options, initialize lock manager and dispatch proper method.
-
- Args:
- argv: The options with which this script was invoked.
-
- Returns:
- 0 unless an exception is raised.
- """
- parser = argparse.ArgumentParser()
-
- parser.add_argument('--list',
- dest='cmd',
- action='store_const',
- const='status',
- help='List current status of all known machines.')
- parser.add_argument('--lock',
- dest='cmd',
- action='store_const',
- const='lock',
- help='Lock given machine(s).')
- parser.add_argument('--unlock',
- dest='cmd',
- action='store_const',
- const='unlock',
- help='Unlock given machine(s).')
- parser.add_argument('--status',
- dest='cmd',
- action='store_const',
- const='status',
- help='List current status of given machine(s).')
- parser.add_argument('--remote',
- dest='remote',
- help='machines on which to operate')
- parser.add_argument('--chromeos_root',
- dest='chromeos_root',
- required=True,
- help='ChromeOS root to use for autotest scripts.')
- parser.add_argument('--force',
- dest='force',
- action='store_true',
- default=False,
- help='Force lock/unlock of machines, even if not'
- ' current lock owner.')
-
- options = parser.parse_args(argv)
-
- if not options.remote and options.cmd != 'status':
- parser.error('No machines specified for operation.')
-
- if not os.path.isdir(options.chromeos_root):
- parser.error('Cannot find chromeos_root: %s.' % options.chromeos_root)
-
- if not options.cmd:
- parser.error('No operation selected (--list, --status, --lock, --unlock,'
- ' --add_machine, --remove_machine).')
-
- machine_list = []
- if options.remote:
- machine_list = options.remote.split()
-
- lock_manager = LockManager(machine_list, options.force,
- options.chromeos_root)
-
- machine_states = lock_manager.GetMachineStates(cmd=options.cmd)
- cmd = options.cmd
-
- if cmd == 'status':
- lock_manager.ListMachineStates(machine_states)
-
- elif cmd == 'lock':
- if not lock_manager.force:
- lock_manager.CheckMachineLocks(machine_states, cmd)
- lock_manager.UpdateMachines(True)
-
- elif cmd == 'unlock':
- if not lock_manager.force:
- lock_manager.CheckMachineLocks(machine_states, cmd)
- lock_manager.UpdateMachines(False)
-
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(Main(sys.argv[1:]))
+if __name__ == "__main__":
+ sys.exit(Main(sys.argv[1:]))