Add a locking mechanism to yaml_git storage
This stops multiple processes from updating the locale checkout of
a remote git repo simultaneously
diff --git a/reclass/storage/yaml_git/__init__.py b/reclass/storage/yaml_git/__init__.py
index 45cb6c0..a28079b 100644
--- a/reclass/storage/yaml_git/__init__.py
+++ b/reclass/storage/yaml_git/__init__.py
@@ -9,8 +9,11 @@
import collections
import distutils.version
+import errno
+import fcntl
import fnmatch
import os
+import time
# Squelch warning on centos7 due to upgrading cffi
# see https://github.com/saltstack/salt/pull/39871
@@ -50,6 +53,7 @@
self.branch = None
self.root = None
self.cache_dir = None
+ self.lock_dir = None
self.pubkey = None
self.privkey = None
self.password = None
@@ -59,6 +63,7 @@
if 'repo' in dictionary: self.repo = dictionary['repo']
if 'branch' in dictionary: self.branch = dictionary['branch']
if 'cache_dir' in dictionary: self.cache_dir = dictionary['cache_dir']
+ if 'lock_dir' in dictionary: self.lock_dir = dictionary['lock_dir']
if 'pubkey' in dictionary: self.pubkey = dictionary['pubkey']
if 'privkey' in dictionary: self.privkey = dictionary['privkey']
if 'password' in dictionary: self.password = dictionary['password']
@@ -72,8 +77,31 @@
return '<{0}: {1} {2} {3}>'.format(self.__class__.__name__, self.repo, self.branch, self.root)
-class GitRepo(object):
+class LockFile():
+ def __init__(self, file):
+ self._file = file
+ def __enter__(self):
+ self._fd = open(self._file, 'w+')
+ start = time.time()
+ while True:
+ if (time.time() - start) > 120:
+ raise IOError('Timeout waiting to lock file: {0}'.format(self._file))
+ try:
+ fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ break
+ except IOError as e:
+ # raise on unrelated IOErrors
+ if e.errno != errno.EAGAIN:
+ raise
+ else:
+ time.sleep(0.1)
+
+ def __exit__(self, type, value, traceback):
+ self._fd.close()
+
+
+class GitRepo(object):
def __init__(self, uri, node_name_mangler, class_name_mangler):
if pygit2 is None:
raise errors.MissingModuleError('pygit2')
@@ -85,11 +113,18 @@
self.cache_dir = '{0}/{1}/{2}'.format(os.path.expanduser("~"), '.reclass/cache/git', self.name)
else:
self.cache_dir = '{0}/{1}'.format(uri.cache_dir, self.name)
-
+ if uri.lock_dir is None:
+ self.lock_file = '{0}/{1}/{2}'.format(os.path.expanduser("~"), '.reclass/cache/lock', self.name)
+ else:
+ self.lock_file = '{0}/{1}'.format(uri.lock_dir, self.name)
+ lock_dir = os.path.dirname(self.lock_file)
+ if not os.path.exists(lock_dir):
+ os.makedirs(lock_dir)
self._node_name_mangler = node_name_mangler
self._class_name_mangler = class_name_mangler
- self._init_repo(uri)
- self._fetch()
+ with LockFile(self.lock_file):
+ self._init_repo(uri)
+ self._fetch()
self.branches = self.repo.listall_branches()
self.files = self.files_in_repo()
@@ -99,10 +134,7 @@
else:
os.makedirs(self.cache_dir)
self.repo = pygit2.init_repository(self.cache_dir, bare=True)
-
- if not self.repo.remotes:
self.repo.create_remote('origin', self.url)
-
if 'ssh' in self.transport:
if '@' in self.url:
user, _, _ = self.url.partition('@')
@@ -130,7 +162,6 @@
if self.credentials is not None:
origin.credentials = self.credentials
fetch_results = origin.fetch(**fetch_kwargs)
-
remote_branches = self.repo.listall_branches(pygit2.GIT_BRANCH_REMOTE)
local_branches = self.repo.listall_branches()
for remote_branch_name in remote_branches:
@@ -208,8 +239,8 @@
ret[node_name] = file
return ret
-class ExternalNodeStorage(ExternalNodeStorageBase):
+class ExternalNodeStorage(ExternalNodeStorageBase):
def __init__(self, nodes_uri, classes_uri, compose_node_name):
super(ExternalNodeStorage, self).__init__(STORAGE_NAME, compose_node_name)
self._repos = dict()