mirror of
https://github.com/vagrant-libvirt/vagrant-libvirt.git
synced 2025-02-25 18:55:27 -06:00
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:
parent
df1dd25e1e
commit
aba32f2c53
@ -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
|
||||
|
90
lib/vagrant-libvirt/locks.rb
Normal file
90
lib/vagrant-libvirt/locks.rb
Normal 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
|
@ -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
80
spec/unit/locks_spec.rb
Normal 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
|
Loading…
Reference in New Issue
Block a user