Skip to content

Commit e3d9d33

Browse files
selzoctcdowney
authored andcommitted
Add LockRunner class for acquiring a lock from the Locket service.
- Generated GRPC service for Locket, based on https://github.com/cloudfoundry/locket/blob/master/models/locket.proto - Mimic Locket's own "LockRunner" default behavior [#158367668] Signed-off-by: Tim Downey <[email protected]>
1 parent ede6831 commit e3d9d33

File tree

4 files changed

+261
-0
lines changed

4 files changed

+261
-0
lines changed

lib/locket/lock_runner.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
require 'locket/locket_services_pb'
2+
3+
module Locket
4+
class LockRunner
5+
class Error < StandardError
6+
end
7+
8+
def initialize(host:, port:, client_ca_path:, client_cert_path:, client_key_path:)
9+
client_ca = File.open(client_ca_path).read
10+
client_key = File.open(client_key_path).read
11+
client_cert = File.open(client_cert_path).read
12+
13+
@service = Models::Locket::Stub.new(
14+
"#{host}:#{port}",
15+
GRPC::Core::ChannelCredentials.new(client_ca, client_key, client_cert)
16+
)
17+
@lock_acquired = false
18+
end
19+
20+
def start(key, owner)
21+
raise Error.new('Cannot start more than once') if @thread
22+
23+
@thread = Thread.new do
24+
loop do
25+
begin
26+
service.lock(build_lock_request(key, owner))
27+
logger.debug("Acquired lock '#{key}' for owner '#{owner}'")
28+
@lock_acquired = true
29+
rescue GRPC::BadStatus => e
30+
logger.debug("Failed to acquire lock '#{key}' for owner '#{owner}': #{e.message}")
31+
@lock_acquired = false
32+
end
33+
34+
sleep 1
35+
end
36+
end
37+
end
38+
39+
def stop
40+
@thread.kill if @thread
41+
end
42+
43+
def lock_acquired?
44+
lock_acquired
45+
end
46+
47+
private
48+
49+
attr_reader :service, :lock_acquired
50+
51+
def build_lock_request(key, owner)
52+
Models::LockRequest.new(
53+
{
54+
resource: {
55+
key: key,
56+
owner: owner,
57+
type_code: Models::TypeCode::LOCK,
58+
},
59+
ttl_in_seconds: 15,
60+
}
61+
)
62+
end
63+
64+
def logger
65+
@logger ||= Steno.logger('cc.locket-client')
66+
end
67+
end
68+
end

lib/locket/locket_pb.rb

Lines changed: 56 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/locket/locket_services_pb.rb

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
require 'spec_helper'
2+
require 'locket/lock_runner'
3+
4+
RSpec.describe Locket::LockRunner do
5+
let(:locket_service) { instance_double(Models::Locket::Stub) }
6+
let(:key) { 'lock-key' }
7+
let(:owner) { 'lock-owner' }
8+
let(:host) { 'locket.capi.land' }
9+
let(:port) { '1234' }
10+
let(:client_ca_path) { File.join(Paths::FIXTURES, 'certs/bbs_ca.crt') }
11+
let(:client_cert_path) { File.join(Paths::FIXTURES, 'certs/bbs_client.crt') }
12+
let(:client_key_path) { File.join(Paths::FIXTURES, 'certs/bbs_client.key') }
13+
let(:credentials) { instance_double(GRPC::Core::ChannelCredentials) }
14+
let(:lock_request) do
15+
Models::LockRequest.new(
16+
{
17+
resource: { key: key, owner: owner, type_code: Models::TypeCode::LOCK },
18+
ttl_in_seconds: 15
19+
}
20+
)
21+
end
22+
23+
let(:client) do
24+
Locket::LockRunner.new(
25+
host: host,
26+
port: port,
27+
client_ca_path: client_ca_path,
28+
client_key_path: client_key_path,
29+
client_cert_path: client_cert_path,
30+
)
31+
end
32+
33+
before do
34+
client_ca = File.open(client_ca_path).read
35+
client_key = File.open(client_key_path).read
36+
client_cert = File.open(client_cert_path).read
37+
38+
allow(GRPC::Core::ChannelCredentials).to receive(:new).
39+
with(client_ca, client_key, client_cert).
40+
and_return(credentials)
41+
42+
allow(Models::Locket::Stub).to receive(:new).
43+
with("#{host}:#{port}", credentials).
44+
and_return(locket_service)
45+
end
46+
47+
after do
48+
client.stop
49+
end
50+
51+
describe '#start' do
52+
it 'continuously attempts to re-acquire the lock' do
53+
allow(locket_service).to receive(:lock)
54+
allow(client).to receive(:sleep)
55+
56+
client.start(key, owner)
57+
sleep 0.1
58+
59+
expect(locket_service).to have_received(:lock).with(lock_request).at_least(3).times
60+
end
61+
62+
it 'raises an error when restarted after it has already been started' do
63+
client.start(key, owner)
64+
65+
expect { client.start(key, owner) }.to raise_error(Locket::LockRunner::Error, 'Cannot start more than once')
66+
end
67+
end
68+
69+
describe '#lock_acquired?' do
70+
context 'initialization' do
71+
it 'does not report that it has a lock before start is called' do
72+
expect(client.lock_acquired?).to be(false)
73+
end
74+
end
75+
76+
context 'when attempting to acquire a lock' do
77+
let(:fake_logger) { instance_double(Steno::Logger, debug: nil) }
78+
79+
before do
80+
allow(Steno).to receive(:logger).and_return(fake_logger)
81+
allow(client).to receive(:sleep)
82+
end
83+
84+
context 'when it does not acquire a lock' do
85+
it 'does not report that it has a lock' do
86+
error = GRPC::BadStatus.new(GRPC::AlreadyExists)
87+
client.instance_variable_set(:@lock_acquired, true)
88+
allow(locket_service).to receive(:lock).
89+
and_raise(error)
90+
91+
client.start(key, owner)
92+
sleep 0.1
93+
94+
expect(client.lock_acquired?).to be(false)
95+
expect(fake_logger).to have_received(:debug).with("Failed to acquire lock '#{key}' for owner '#{owner}': #{error.message}").at_least(:once)
96+
end
97+
end
98+
99+
context 'when it does acquire a lock' do
100+
it 'reports that it has a lock' do
101+
allow(locket_service).to receive(:lock).
102+
and_return(Models::LockResponse)
103+
104+
client.start(key, owner)
105+
sleep 0.1
106+
107+
expect(client.lock_acquired?).to be(true)
108+
expect(fake_logger).to have_received(:debug).with("Acquired lock '#{key}' for owner '#{owner}'").at_least(:once)
109+
end
110+
end
111+
end
112+
end
113+
end

0 commit comments

Comments
 (0)