1*800a58d9SAndroid Build Coastguard Worker#!/usr/bin/env python 2*800a58d9SAndroid Build Coastguard Worker# 3*800a58d9SAndroid Build Coastguard Worker# Copyright 2020 - The Android Open Source Project 4*800a58d9SAndroid Build Coastguard Worker# 5*800a58d9SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*800a58d9SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*800a58d9SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*800a58d9SAndroid Build Coastguard Worker# 9*800a58d9SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*800a58d9SAndroid Build Coastguard Worker# 11*800a58d9SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*800a58d9SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*800a58d9SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*800a58d9SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*800a58d9SAndroid Build Coastguard Worker# limitations under the License. 16*800a58d9SAndroid Build Coastguard Worker"""LocalInstanceLock class.""" 17*800a58d9SAndroid Build Coastguard Worker 18*800a58d9SAndroid Build Coastguard Workerimport errno 19*800a58d9SAndroid Build Coastguard Workerimport fcntl 20*800a58d9SAndroid Build Coastguard Workerimport logging 21*800a58d9SAndroid Build Coastguard Workerimport os 22*800a58d9SAndroid Build Coastguard Worker 23*800a58d9SAndroid Build Coastguard Workerfrom acloud import errors 24*800a58d9SAndroid Build Coastguard Workerfrom acloud.internal.lib import utils 25*800a58d9SAndroid Build Coastguard Worker 26*800a58d9SAndroid Build Coastguard Worker 27*800a58d9SAndroid Build Coastguard Workerlogger = logging.getLogger(__name__) 28*800a58d9SAndroid Build Coastguard Worker 29*800a58d9SAndroid Build Coastguard Worker_LOCK_FILE_SIZE = 1 30*800a58d9SAndroid Build Coastguard Worker# An empty file is equivalent to NOT_IN_USE. 31*800a58d9SAndroid Build Coastguard Worker_IN_USE_STATE = b"I" 32*800a58d9SAndroid Build Coastguard Worker_NOT_IN_USE_STATE = b"N" 33*800a58d9SAndroid Build Coastguard Worker 34*800a58d9SAndroid Build Coastguard Worker_DEFAULT_TIMEOUT_SECS = 5 35*800a58d9SAndroid Build Coastguard Worker 36*800a58d9SAndroid Build Coastguard Worker 37*800a58d9SAndroid Build Coastguard Workerclass LocalInstanceLock: 38*800a58d9SAndroid Build Coastguard Worker """The class that controls a lock file for a local instance. 39*800a58d9SAndroid Build Coastguard Worker 40*800a58d9SAndroid Build Coastguard Worker Acloud acquires the lock file of a local instance before it creates, 41*800a58d9SAndroid Build Coastguard Worker deletes, or queries it. The lock prevents multiple acloud processes from 42*800a58d9SAndroid Build Coastguard Worker accessing an instance simultaneously. 43*800a58d9SAndroid Build Coastguard Worker 44*800a58d9SAndroid Build Coastguard Worker The lock file records whether the instance is in use. Acloud checks the 45*800a58d9SAndroid Build Coastguard Worker state when it needs an unused id to create a new instance. 46*800a58d9SAndroid Build Coastguard Worker 47*800a58d9SAndroid Build Coastguard Worker Attributes: 48*800a58d9SAndroid Build Coastguard Worker _file_path: The path to the lock file. 49*800a58d9SAndroid Build Coastguard Worker _file_desc: The file descriptor of the file. It is set to None when 50*800a58d9SAndroid Build Coastguard Worker this object does not hold the lock. 51*800a58d9SAndroid Build Coastguard Worker """ 52*800a58d9SAndroid Build Coastguard Worker 53*800a58d9SAndroid Build Coastguard Worker def __init__(self, file_path): 54*800a58d9SAndroid Build Coastguard Worker self._file_path = file_path 55*800a58d9SAndroid Build Coastguard Worker self._file_desc = None 56*800a58d9SAndroid Build Coastguard Worker 57*800a58d9SAndroid Build Coastguard Worker def _Flock(self, timeout_secs): 58*800a58d9SAndroid Build Coastguard Worker """Call fcntl.flock with timeout. 59*800a58d9SAndroid Build Coastguard Worker 60*800a58d9SAndroid Build Coastguard Worker Args: 61*800a58d9SAndroid Build Coastguard Worker timeout_secs: An integer or a float, the timeout for acquiring the 62*800a58d9SAndroid Build Coastguard Worker lock file. 0 indicates non-block. 63*800a58d9SAndroid Build Coastguard Worker 64*800a58d9SAndroid Build Coastguard Worker Returns: 65*800a58d9SAndroid Build Coastguard Worker True if the file is locked successfully. False if timeout. 66*800a58d9SAndroid Build Coastguard Worker 67*800a58d9SAndroid Build Coastguard Worker Raises: 68*800a58d9SAndroid Build Coastguard Worker OSError: if any file operation fails. 69*800a58d9SAndroid Build Coastguard Worker """ 70*800a58d9SAndroid Build Coastguard Worker try: 71*800a58d9SAndroid Build Coastguard Worker if timeout_secs > 0: 72*800a58d9SAndroid Build Coastguard Worker wrapper = utils.TimeoutException(timeout_secs) 73*800a58d9SAndroid Build Coastguard Worker wrapper(fcntl.flock)(self._file_desc, fcntl.LOCK_EX) 74*800a58d9SAndroid Build Coastguard Worker else: 75*800a58d9SAndroid Build Coastguard Worker fcntl.flock(self._file_desc, fcntl.LOCK_EX | fcntl.LOCK_NB) 76*800a58d9SAndroid Build Coastguard Worker except errors.FunctionTimeoutError as e: 77*800a58d9SAndroid Build Coastguard Worker logger.debug("Cannot lock %s within %s seconds", 78*800a58d9SAndroid Build Coastguard Worker self._file_path, timeout_secs) 79*800a58d9SAndroid Build Coastguard Worker return False 80*800a58d9SAndroid Build Coastguard Worker except (OSError, IOError) as e: 81*800a58d9SAndroid Build Coastguard Worker # flock raises IOError in python2; OSError in python3. 82*800a58d9SAndroid Build Coastguard Worker if e.errno in (errno.EACCES, errno.EAGAIN): 83*800a58d9SAndroid Build Coastguard Worker logger.debug("Cannot lock %s", self._file_path) 84*800a58d9SAndroid Build Coastguard Worker return False 85*800a58d9SAndroid Build Coastguard Worker raise 86*800a58d9SAndroid Build Coastguard Worker return True 87*800a58d9SAndroid Build Coastguard Worker 88*800a58d9SAndroid Build Coastguard Worker def Lock(self, timeout_secs=_DEFAULT_TIMEOUT_SECS): 89*800a58d9SAndroid Build Coastguard Worker """Acquire the lock file. 90*800a58d9SAndroid Build Coastguard Worker 91*800a58d9SAndroid Build Coastguard Worker Args: 92*800a58d9SAndroid Build Coastguard Worker timeout_secs: An integer or a float, the timeout for acquiring the 93*800a58d9SAndroid Build Coastguard Worker lock file. 0 indicates non-block. 94*800a58d9SAndroid Build Coastguard Worker 95*800a58d9SAndroid Build Coastguard Worker Returns: 96*800a58d9SAndroid Build Coastguard Worker True if the file is locked successfully. False if timeout. 97*800a58d9SAndroid Build Coastguard Worker 98*800a58d9SAndroid Build Coastguard Worker Raises: 99*800a58d9SAndroid Build Coastguard Worker OSError: if any file operation fails. 100*800a58d9SAndroid Build Coastguard Worker """ 101*800a58d9SAndroid Build Coastguard Worker if self._file_desc is not None: 102*800a58d9SAndroid Build Coastguard Worker raise OSError("%s has been locked." % self._file_path) 103*800a58d9SAndroid Build Coastguard Worker parent_dir = os.path.dirname(self._file_path) 104*800a58d9SAndroid Build Coastguard Worker if not os.path.exists(parent_dir): 105*800a58d9SAndroid Build Coastguard Worker os.makedirs(parent_dir) 106*800a58d9SAndroid Build Coastguard Worker successful = False 107*800a58d9SAndroid Build Coastguard Worker self._file_desc = os.open(self._file_path, os.O_CREAT | os.O_RDWR, 108*800a58d9SAndroid Build Coastguard Worker 0o666) 109*800a58d9SAndroid Build Coastguard Worker os.chmod(self._file_path, 0o666) 110*800a58d9SAndroid Build Coastguard Worker os.chmod(parent_dir, 0o755) 111*800a58d9SAndroid Build Coastguard Worker try: 112*800a58d9SAndroid Build Coastguard Worker successful = self._Flock(timeout_secs) 113*800a58d9SAndroid Build Coastguard Worker finally: 114*800a58d9SAndroid Build Coastguard Worker if not successful: 115*800a58d9SAndroid Build Coastguard Worker os.close(self._file_desc) 116*800a58d9SAndroid Build Coastguard Worker self._file_desc = None 117*800a58d9SAndroid Build Coastguard Worker return successful 118*800a58d9SAndroid Build Coastguard Worker 119*800a58d9SAndroid Build Coastguard Worker def _CheckFileDescriptor(self): 120*800a58d9SAndroid Build Coastguard Worker """Raise an error if the file is not opened or locked.""" 121*800a58d9SAndroid Build Coastguard Worker if self._file_desc is None: 122*800a58d9SAndroid Build Coastguard Worker raise RuntimeError("%s has not been locked." % self._file_path) 123*800a58d9SAndroid Build Coastguard Worker 124*800a58d9SAndroid Build Coastguard Worker def SetInUse(self, in_use): 125*800a58d9SAndroid Build Coastguard Worker """Write the instance state to the file. 126*800a58d9SAndroid Build Coastguard Worker 127*800a58d9SAndroid Build Coastguard Worker Args: 128*800a58d9SAndroid Build Coastguard Worker in_use: A boolean, whether to set the instance to be in use. 129*800a58d9SAndroid Build Coastguard Worker 130*800a58d9SAndroid Build Coastguard Worker Raises: 131*800a58d9SAndroid Build Coastguard Worker OSError: if any file operation fails. 132*800a58d9SAndroid Build Coastguard Worker """ 133*800a58d9SAndroid Build Coastguard Worker self._CheckFileDescriptor() 134*800a58d9SAndroid Build Coastguard Worker os.lseek(self._file_desc, 0, os.SEEK_SET) 135*800a58d9SAndroid Build Coastguard Worker state = _IN_USE_STATE if in_use else _NOT_IN_USE_STATE 136*800a58d9SAndroid Build Coastguard Worker if os.write(self._file_desc, state) != _LOCK_FILE_SIZE: 137*800a58d9SAndroid Build Coastguard Worker raise OSError("Cannot write " + self._file_path) 138*800a58d9SAndroid Build Coastguard Worker 139*800a58d9SAndroid Build Coastguard Worker def Unlock(self): 140*800a58d9SAndroid Build Coastguard Worker """Unlock the file. 141*800a58d9SAndroid Build Coastguard Worker 142*800a58d9SAndroid Build Coastguard Worker Raises: 143*800a58d9SAndroid Build Coastguard Worker OSError: if any file operation fails. 144*800a58d9SAndroid Build Coastguard Worker """ 145*800a58d9SAndroid Build Coastguard Worker self._CheckFileDescriptor() 146*800a58d9SAndroid Build Coastguard Worker fcntl.flock(self._file_desc, fcntl.LOCK_UN) 147*800a58d9SAndroid Build Coastguard Worker os.close(self._file_desc) 148*800a58d9SAndroid Build Coastguard Worker self._file_desc = None 149*800a58d9SAndroid Build Coastguard Worker 150*800a58d9SAndroid Build Coastguard Worker def LockIfNotInUse(self, timeout_secs=_DEFAULT_TIMEOUT_SECS): 151*800a58d9SAndroid Build Coastguard Worker """Lock the file if the instance is not in use. 152*800a58d9SAndroid Build Coastguard Worker 153*800a58d9SAndroid Build Coastguard Worker Returns: 154*800a58d9SAndroid Build Coastguard Worker True if the file is locked successfully. 155*800a58d9SAndroid Build Coastguard Worker False if timeout or the instance is in use. 156*800a58d9SAndroid Build Coastguard Worker 157*800a58d9SAndroid Build Coastguard Worker Raises: 158*800a58d9SAndroid Build Coastguard Worker OSError: if any file operation fails. 159*800a58d9SAndroid Build Coastguard Worker """ 160*800a58d9SAndroid Build Coastguard Worker if not self.Lock(timeout_secs): 161*800a58d9SAndroid Build Coastguard Worker return False 162*800a58d9SAndroid Build Coastguard Worker in_use = True 163*800a58d9SAndroid Build Coastguard Worker try: 164*800a58d9SAndroid Build Coastguard Worker in_use = os.read(self._file_desc, _LOCK_FILE_SIZE) == _IN_USE_STATE 165*800a58d9SAndroid Build Coastguard Worker finally: 166*800a58d9SAndroid Build Coastguard Worker if in_use: 167*800a58d9SAndroid Build Coastguard Worker self.Unlock() 168*800a58d9SAndroid Build Coastguard Worker return not in_use 169