Initial locking support

Support locking between vagrant processes to prevent teardown of a
network which a machine is in the process of being configured to use.
This commit is contained in:
Darragh Bailey 2022-08-26 22:49:00 +01:00
parent df1dd25e1e
commit aba32f2c53
4 changed files with 176 additions and 0 deletions

View File

@ -154,6 +154,10 @@ module VagrantPlugins
end
# Other exceptions
class AlreadyLockedError < VagrantLibvirtError
error_key(:already_locked_error)
end
class UpdateServerError < VagrantLibvirtError
error_key(:create_server_error)
end

View File

@ -0,0 +1,90 @@
# frozen_string_literal: true
module VagrantPlugins
module ProviderLibvirt
class LockManager
def initialize(lockfactory=LocalLockFactory.new)
@logger = Log4r::Logger.new('vagrant_libvirt::locks')
@lockfactory = lockfactory
end
# Following the pattern in vagrant/lib/environment for creating
# locks, allowing for them to be created remotely or locally.
def lock(name="global", **opts)
flock = nil
return if !block_given?
@logger.debug("Attempting to acquire lock: #{name}")
flock = @lockfactory.create(name)
while flock.acquire() === false
@logger.warn("lock in use: #{name}")
if !opts[:retry]
raise VagrantPlugins::ProviderLibvirt::Errors::AlreadyLockedError, name: name
end
sleep 0.2
end
@logger.info("Acquired process lock: #{name}")
yield
ensure
begin
flock.release() if flock
rescue IOError
end
end
end
LOCK_DIR = "/var/tmp/vagrant-libvirt"
class LocalLockFactory
class LocalLock
def initialize(dir, name)
@f = File.open(File.join(dir, name), "w+", 0775)
end
def acquire
@f.flock(File::LOCK_EX | File::LOCK_NB)
end
def release
@f.close()
end
end
def initialize
FileUtils.mkdir_p(LOCK_DIR, :mode => 0777)
end
def create(name)
LocalLock.new(LOCK_DIR, name)
end
end
# not ready for usage yet, but defined to ensure API
class RemoteLockFactory
class RemoteLock
def initialize(conn, dir, name)
end
def acquire
end
def release
end
end
def initialize(conn)
@conn = conn
end
def create(name)
RemoteLock.new(@conn, LOCK_DIR, name)
end
end
end
end

View File

@ -142,6 +142,8 @@ en:
Error while creating a storage pool volume: %{error_message}
fog_create_domain_volume_error: |-
Error while creating volume for domain: %{error_message}
already_locked_error: |-
Lock %{name} already held
create_server_error: |-
Error while creating domain: %{error_message}
domain_name_exists: |-

80
spec/unit/locks_spec.rb Normal file
View File

@ -0,0 +1,80 @@
# frozen_string_literal: true
require 'support/sharedcontext'
require 'vagrant-libvirt/errors'
require 'vagrant-libvirt/locks'
describe VagrantPlugins::ProviderLibvirt::LockManager do
include_context "unit"
include_context "temporary_dir"
before do
stub_const "VagrantPlugins::ProviderLibvirt::LOCK_DIR", temp_dir
end
describe "#lock" do
it "does nothing if no block is given" do
subject.lock
end
it "raises exception if locked twice" do
another = described_class.new
result = false
subject.lock do
begin
# ensure following has a block otherwise it will skip attempting to lock the file
another.lock {}
rescue VagrantPlugins::ProviderLibvirt::Errors::AlreadyLockedError
result = true
end
end
expect(result).to be_truthy
end
context "with local lock" do
it "should handle multiple threads/processes with retry" do
t1wait = true
t1locked = false
# grab the lock and wait
t1 = Thread.new do
locker = described_class.new
locker.lock("common") do
t1locked = true
while t1wait === true
sleep 0.1
end
end
end
# wait to ensure first process has the lock
t2 = Thread.new do
locker = described_class.new
while t1locked === false
sleep 0.1
end
locker.lock("common", :retry => true) do
sleep 0.1
end
end
# let first thread complete
t1wait = false
# wait for threads to complete.
expect {
Timeout::timeout(2) do
puts sleep 0.1
expect(t1.value).to eq(nil) # result from loop
expect(t2.value).to eq(0) # result from sleep
end
}.to_not raise_error
end
end
end
end