vcap_common 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/json_message.rb +139 -0
- data/lib/json_schema.rb +84 -0
- data/lib/services/api/async_requests.rb +43 -0
- data/lib/services/api/clients/service_gateway_client.rb +107 -0
- data/lib/services/api/const.rb +9 -0
- data/lib/services/api/messages.rb +153 -0
- data/lib/services/api/util.rb +17 -0
- data/lib/services/api.rb +6 -0
- data/lib/vcap/common.rb +236 -0
- data/lib/vcap/component.rb +172 -0
- data/lib/vcap/config.rb +32 -0
- data/lib/vcap/fiber_tracing.rb +45 -0
- data/lib/vcap/json_schema.rb +202 -0
- data/lib/vcap/priority_queue.rb +164 -0
- data/lib/vcap/process_utils.rb +43 -0
- data/lib/vcap/quota.rb +152 -0
- data/lib/vcap/rolling_metric.rb +74 -0
- data/lib/vcap/spec/em.rb +32 -0
- data/lib/vcap/spec/forked_component/base.rb +87 -0
- data/lib/vcap/spec/forked_component/nats_server.rb +28 -0
- data/lib/vcap/spec/forked_component.rb +2 -0
- data/lib/vcap/subprocess.rb +211 -0
- data/lib/vcap/user_pools/user_ops.rb +47 -0
- data/lib/vcap/user_pools/user_pool.rb +45 -0
- data/lib/vcap/user_pools/user_pool_util.rb +107 -0
- metadata +166 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
#a priority queue with the added twist of FIFO behavior for elements with equal priorities
|
2
|
+
#implementation using binary max-heap on top of a ruby array.
|
3
|
+
#the FIFO behavior is implemented by storing a FIFO bucket of same-priority values
|
4
|
+
|
5
|
+
#The implementation is not meant to be high-performance, just decent, with two goals:
|
6
|
+
#1. clean interface
|
7
|
+
#2. proper time/space complexity of a binary heap
|
8
|
+
#3. no silly memory leaks (Ah, three weapons of the Spanish Inquisition)
|
9
|
+
|
10
|
+
#additionally, we implement a PrioritySet, that is a PriorityQueue
|
11
|
+
#into which an element can only be inserted once. This PrioritySet
|
12
|
+
#allows specifying identity for the object with a separate object.
|
13
|
+
#The identity is for determining whether an object being inserted is a
|
14
|
+
#duplicate, e.g.
|
15
|
+
|
16
|
+
# q.insert("boo", 1, "key")
|
17
|
+
# q.insert("zah", 1, "key")
|
18
|
+
|
19
|
+
#will result in just one object in the queue, "boo"
|
20
|
+
#
|
21
|
+
#See spec/unit/priority_queue_set for
|
22
|
+
#other examples
|
23
|
+
|
24
|
+
require 'set'
|
25
|
+
require 'pp'
|
26
|
+
|
27
|
+
module VCAP
|
28
|
+
class PriorityQueueFIFO
|
29
|
+
|
30
|
+
attr_reader :size
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@heap_arr = []
|
34
|
+
@p2b = {} #hash mapping priorities to buckets
|
35
|
+
@size = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def empty?
|
39
|
+
size == 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def insert(item, priority = 0)
|
43
|
+
raise ArgumentError, "priority can not be negative: #{priority}" if priority < 0
|
44
|
+
|
45
|
+
unless append_to_existing_priority_bucket(item, priority)
|
46
|
+
add_bucket_at_the_end_and_shift_up(item, priority)
|
47
|
+
end
|
48
|
+
@size += 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def remove
|
52
|
+
return nil if empty?
|
53
|
+
bucket = top_bucket
|
54
|
+
priority = top_priority
|
55
|
+
elem = bucket.shift
|
56
|
+
@size -= 1
|
57
|
+
if empty?
|
58
|
+
@heap_arr.clear
|
59
|
+
@p2b.clear
|
60
|
+
elsif bucket.empty?
|
61
|
+
@heap_arr[0] = @heap_arr.pop
|
62
|
+
@p2b.delete(priority)
|
63
|
+
shift_down
|
64
|
+
else
|
65
|
+
#do nothing, we just shifted a value from a bucket and it still isn't empty, so no rearrangement is needed
|
66
|
+
end
|
67
|
+
elem
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def add_bucket_at_the_end_and_shift_up(item, priority)
|
73
|
+
bucket = [item]
|
74
|
+
@p2b[priority] = bucket
|
75
|
+
|
76
|
+
#normal binary heap operation
|
77
|
+
@heap_arr.push priority
|
78
|
+
shift_up
|
79
|
+
end
|
80
|
+
|
81
|
+
def append_to_existing_priority_bucket(item, priority)
|
82
|
+
return false unless @p2b[priority]
|
83
|
+
@p2b[priority] << item
|
84
|
+
return true
|
85
|
+
end
|
86
|
+
|
87
|
+
def top_bucket
|
88
|
+
@p2b[top_priority]
|
89
|
+
end
|
90
|
+
|
91
|
+
def top_priority
|
92
|
+
priority_at(0)
|
93
|
+
end
|
94
|
+
|
95
|
+
def priority_at(index)
|
96
|
+
return -1 if index >= @heap_arr.size
|
97
|
+
@heap_arr[index]
|
98
|
+
end
|
99
|
+
|
100
|
+
def parent_index(index)
|
101
|
+
(index+1) / 2 - 1
|
102
|
+
end
|
103
|
+
|
104
|
+
def left_child_index(index)
|
105
|
+
(index+1) * 2 - 1
|
106
|
+
end
|
107
|
+
|
108
|
+
def right_child_index(index)
|
109
|
+
(index+1) * 2
|
110
|
+
end
|
111
|
+
|
112
|
+
def any_children_at?(index)
|
113
|
+
left_child_index(index) < @heap_arr.length
|
114
|
+
end
|
115
|
+
|
116
|
+
def shift_up
|
117
|
+
cur_index = @heap_arr.length - 1
|
118
|
+
while cur_index > 0 && priority_at(cur_index) > priority_at(parent_index(cur_index)) do
|
119
|
+
next_cur_index = parent_index cur_index
|
120
|
+
swap_at(cur_index, next_cur_index)
|
121
|
+
cur_index = next_cur_index
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def index_of_max_priority_child_at(index)
|
126
|
+
#raise(ArgumentError, "no children at #{index}") unless any_children_at?(index)
|
127
|
+
l = left_child_index(index)
|
128
|
+
r = right_child_index(index)
|
129
|
+
return r if priority_at(r) > priority_at(l) #this is safe since priority will return -1 for non-existent right child
|
130
|
+
return l
|
131
|
+
end
|
132
|
+
|
133
|
+
def shift_down
|
134
|
+
cur_index = 0
|
135
|
+
while any_children_at?(cur_index) && priority_at(cur_index) < priority_at(index_of_max_priority_child_at(cur_index)) do
|
136
|
+
next_cur_index = index_of_max_priority_child_at cur_index
|
137
|
+
swap_at(cur_index, next_cur_index)
|
138
|
+
cur_index = next_cur_index
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def swap_at(i,j)
|
143
|
+
@heap_arr[i], @heap_arr[j] = @heap_arr[j], @heap_arr[i]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
class PrioritySet < PriorityQueueFIFO
|
149
|
+
def initialize
|
150
|
+
super
|
151
|
+
@set = Set.new #the set is used to check for duplicates
|
152
|
+
end
|
153
|
+
|
154
|
+
def insert(elem, priority = 0, key = nil)
|
155
|
+
super([elem,key], priority) if @set.add?(key || elem)
|
156
|
+
end
|
157
|
+
|
158
|
+
def remove
|
159
|
+
elem, key = super
|
160
|
+
@set.delete(key || elem)
|
161
|
+
elem
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'vcap/subprocess'
|
4
|
+
|
5
|
+
module VCAP
|
6
|
+
module ProcessUtils
|
7
|
+
STAT_FIELDS = [
|
8
|
+
{:name => :rss, :parse_method => :to_i},
|
9
|
+
{:name => :vsize, :parse_method => :to_i},
|
10
|
+
{:name => :pcpu, :parse_method => :to_f},
|
11
|
+
]
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def get_stats(pid=nil)
|
16
|
+
pid ||= Process.pid
|
17
|
+
|
18
|
+
flags = STAT_FIELDS.map {|f| "-o #{f[:name]}=" }.join(' ')
|
19
|
+
begin
|
20
|
+
stdout, stderr, status = VCAP::Subprocess.run("ps #{flags} -p #{pid}")
|
21
|
+
rescue VCAP::SubprocessStatusError => se
|
22
|
+
# Process not running
|
23
|
+
if se.status.exitstatus == 1
|
24
|
+
return nil
|
25
|
+
else
|
26
|
+
raise se
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
ret = {}
|
31
|
+
stdout.split.each_with_index do |val, ii|
|
32
|
+
field = STAT_FIELDS[ii]
|
33
|
+
ret[field[:name]] = val.send(field[:parse_method])
|
34
|
+
end
|
35
|
+
|
36
|
+
ret
|
37
|
+
end
|
38
|
+
|
39
|
+
end # class << self
|
40
|
+
end # ProcessUtils
|
41
|
+
end # VCAP
|
42
|
+
|
43
|
+
|
data/lib/vcap/quota.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
module VCAP
|
2
|
+
module Quota
|
3
|
+
|
4
|
+
class Command
|
5
|
+
class ValidationError < StandardError; end
|
6
|
+
|
7
|
+
def run
|
8
|
+
validate
|
9
|
+
command = build_command
|
10
|
+
result = execute(command)
|
11
|
+
parse_result(result)
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_command
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute(command)
|
23
|
+
stdout = `#{command}`
|
24
|
+
[$?, stdout]
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_result(result)
|
28
|
+
result
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def assert_at_least_one_of(*fields)
|
34
|
+
for field in fields
|
35
|
+
has_value = send(field.to_sym)
|
36
|
+
return if has_value
|
37
|
+
end
|
38
|
+
raise ValidationError, "At least one of {#{fields.join(', ')}} must be set"
|
39
|
+
end
|
40
|
+
|
41
|
+
def assert_at_most_one_of(*fields)
|
42
|
+
existing_fields = fields.inject([]) do |accum, field|
|
43
|
+
accum << field unless send(field.to_sym)
|
44
|
+
accum
|
45
|
+
end
|
46
|
+
unless existing_fields.length == 1
|
47
|
+
raise ValidationError, "At most one of #{fields.join(', ')} must be set"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class SetQuota < Command
|
53
|
+
attr_accessor :user
|
54
|
+
attr_accessor :group
|
55
|
+
attr_accessor :filesystem
|
56
|
+
attr_accessor :quotas
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
@quotas = {
|
60
|
+
:block => {
|
61
|
+
:soft => 0,
|
62
|
+
:hard => 0,
|
63
|
+
},
|
64
|
+
:inode => {
|
65
|
+
:soft => 0,
|
66
|
+
:hard => 0,
|
67
|
+
},
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate
|
72
|
+
assert_at_least_one_of(:user)
|
73
|
+
assert_at_least_one_of(:filesystem)
|
74
|
+
assert_at_least_one_of(:quotas)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def build_command
|
80
|
+
cmd = ['setquota']
|
81
|
+
cmd << ['-u', self.user] if self.user
|
82
|
+
cmd << ['-g', self.group] if self.group
|
83
|
+
cmd << [self.quotas[:block][:soft], self.quotas[:block][:hard],
|
84
|
+
self.quotas[:inode][:soft], self.quotas[:inode][:hard]]
|
85
|
+
cmd << self.filesystem
|
86
|
+
cmd.flatten.join(' ')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class RepQuota < Command
|
91
|
+
attr_accessor :report_groups
|
92
|
+
attr_accessor :report_users
|
93
|
+
attr_accessor :ids_only
|
94
|
+
attr_accessor :filesystem
|
95
|
+
|
96
|
+
def initialize
|
97
|
+
@report_users = true
|
98
|
+
end
|
99
|
+
|
100
|
+
def validate
|
101
|
+
assert_at_least_one_of(:report_groups, :report_users)
|
102
|
+
assert_at_least_one_of(:filesystem)
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_command
|
106
|
+
cmd = ['repquota', '-p'] # -p reports grace as 0 when unset
|
107
|
+
cmd << '-u' if self.report_users
|
108
|
+
cmd << '-g' if self.report_groups
|
109
|
+
cmd << '-n' if self.ids_only
|
110
|
+
cmd << self.filesystem
|
111
|
+
cmd = cmd.flatten.join(' ')
|
112
|
+
cmd
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_result(result)
|
116
|
+
if result[0] == 0
|
117
|
+
quota_info = {}
|
118
|
+
result[1].lines.each do |line|
|
119
|
+
next unless line.match(/[^\s]+\s+[+-]+\s+\d+/)
|
120
|
+
fields = line.split(/\s+/)
|
121
|
+
if self.ids_only
|
122
|
+
match = fields[0].match(/^#(\d+)$/)
|
123
|
+
uid = match[1].to_i
|
124
|
+
else
|
125
|
+
uid = fields[0]
|
126
|
+
end
|
127
|
+
quota_info[uid] = {
|
128
|
+
:usage => {
|
129
|
+
:block => fields[2].to_i,
|
130
|
+
:inode => fields[6].to_i
|
131
|
+
},
|
132
|
+
:quotas => {
|
133
|
+
:block => {
|
134
|
+
:soft => fields[3].to_i,
|
135
|
+
:hard => fields[4].to_i,
|
136
|
+
},
|
137
|
+
:inode => {
|
138
|
+
:soft => fields[7].to_i,
|
139
|
+
:hard => fields[8].to_i,
|
140
|
+
},
|
141
|
+
}
|
142
|
+
}
|
143
|
+
end
|
144
|
+
[true, quota_info]
|
145
|
+
else
|
146
|
+
[false, result]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end # VCAP::Quota
|
152
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "monitor"
|
2
|
+
|
3
|
+
module VCAP
|
4
|
+
|
5
|
+
class RollingMetric
|
6
|
+
|
7
|
+
def initialize(duration, num_buckets = 60)
|
8
|
+
@duration = duration
|
9
|
+
num_buckets = [@duration, num_buckets].min
|
10
|
+
@bucket_duration = (@duration / num_buckets).to_i
|
11
|
+
@eviction_duration = @bucket_duration * 2
|
12
|
+
@buckets = []
|
13
|
+
num_buckets.times do
|
14
|
+
@buckets << {:timestamp => 0, :value => 0, :samples => 0}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def <<(value)
|
19
|
+
timestamp = Time.now.to_i
|
20
|
+
bucket = @buckets[(timestamp / @bucket_duration) % @buckets.length]
|
21
|
+
if timestamp - bucket[:timestamp] > @eviction_duration
|
22
|
+
bucket[:timestamp] = timestamp
|
23
|
+
bucket[:value] = value
|
24
|
+
bucket[:samples] = 1
|
25
|
+
else
|
26
|
+
bucket[:value] += value
|
27
|
+
bucket[:samples] += 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def value
|
32
|
+
timestamp = Time.now.to_i
|
33
|
+
min_timestamp = timestamp - @duration
|
34
|
+
|
35
|
+
value = 0
|
36
|
+
samples = 0
|
37
|
+
|
38
|
+
@buckets.each do |bucket|
|
39
|
+
if bucket[:timestamp] > min_timestamp
|
40
|
+
value += bucket[:value]
|
41
|
+
samples += bucket[:samples]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
{
|
46
|
+
:value => value,
|
47
|
+
:samples => samples
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_json
|
52
|
+
Yajl::Encoder.encode(value)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
class ThreadSafeRollingMetric < RollingMetric
|
58
|
+
|
59
|
+
def initialize(*args)
|
60
|
+
super(*args)
|
61
|
+
@lock = Monitor.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def <<(*args)
|
65
|
+
@lock.synchronize { super(*args) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def value(*args)
|
69
|
+
@lock.synchronize { super(*args) }
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
data/lib/vcap/spec/em.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
module VCAP
|
3
|
+
|
4
|
+
module Spec
|
5
|
+
|
6
|
+
module EM
|
7
|
+
|
8
|
+
def em(options = {})
|
9
|
+
raise "no block given" unless block_given?
|
10
|
+
timeout = options[:timeout] ||= 1.0
|
11
|
+
|
12
|
+
::EM.run {
|
13
|
+
quantum = 0.005
|
14
|
+
::EM.set_quantum(quantum * 1000) # Lowest possible timer resolution
|
15
|
+
::EM.set_heartbeat_interval(quantum) # Timeout connections asap
|
16
|
+
::EM.add_timer(timeout) { raise "timeout" }
|
17
|
+
yield
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def done
|
22
|
+
raise "reactor not running" if !::EM.reactor_running?
|
23
|
+
|
24
|
+
::EM.next_tick {
|
25
|
+
# Assert something to show a spec-pass
|
26
|
+
:done.should == :done
|
27
|
+
::EM.stop_event_loop
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'vcap/common'
|
2
|
+
|
3
|
+
|
4
|
+
module VCAP
|
5
|
+
module Spec
|
6
|
+
module ForkedComponent
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class VCAP::Spec::ForkedComponent::Base
|
12
|
+
attr_reader :pid, :pid_filename, :output_basedir, :name, :cmd
|
13
|
+
|
14
|
+
attr_accessor :reopen_stdio
|
15
|
+
|
16
|
+
# @param cmd String Command to run
|
17
|
+
# @param name String Short name for this component (e.g. 'redis')
|
18
|
+
# @param output_basedir String Stderr/stdout will be placed under this directory
|
19
|
+
# @param pid_filename String If not nil, we ready the pid from this file instead
|
20
|
+
# of using the pid returned from fork
|
21
|
+
def initialize(cmd, name, output_basedir='/tmp', pid_filename=nil)
|
22
|
+
@cmd = cmd
|
23
|
+
@name = name
|
24
|
+
@output_basedir = output_basedir
|
25
|
+
@pid_filename = pid_filename
|
26
|
+
|
27
|
+
@reopen_stdio = true
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def start
|
32
|
+
pid = fork do
|
33
|
+
|
34
|
+
if @reopen_stdio
|
35
|
+
fn = File.join(@output_basedir, "#{@name}.#{Process.pid}.out")
|
36
|
+
outfile = File.new(fn, 'w+')
|
37
|
+
$stderr.reopen(outfile)
|
38
|
+
$stdout.reopen(outfile)
|
39
|
+
end
|
40
|
+
|
41
|
+
exec(@cmd)
|
42
|
+
end
|
43
|
+
|
44
|
+
if @pid_filename
|
45
|
+
wait_for(5) { File.exists?(@pid_filename) }
|
46
|
+
@pid = File.read(@pid_filename).chomp.to_i
|
47
|
+
else
|
48
|
+
@pid = pid
|
49
|
+
end
|
50
|
+
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def stop
|
55
|
+
return unless @pid && VCAP.process_running?(@pid)
|
56
|
+
Process.kill('TERM', @pid)
|
57
|
+
Process.waitpid(@pid, 0)
|
58
|
+
FileUtils.rm_f(@pid_filename) if @pid_filename
|
59
|
+
@pid = nil
|
60
|
+
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def running?
|
65
|
+
VCAP.process_running?(@pid)
|
66
|
+
end
|
67
|
+
|
68
|
+
def ready?
|
69
|
+
raise NotImplementedError
|
70
|
+
end
|
71
|
+
|
72
|
+
def wait_ready(timeout=1)
|
73
|
+
wait_for { ready? }
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def wait_for(timeout=1, &predicate)
|
79
|
+
start = Time.now()
|
80
|
+
cond_met = predicate.call()
|
81
|
+
while !cond_met && ((Time.new() - start) < timeout)
|
82
|
+
cond_met = predicate.call()
|
83
|
+
sleep(0.2)
|
84
|
+
end
|
85
|
+
cond_met
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'nats/client'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
require 'vcap/spec/forked_component/base'
|
5
|
+
|
6
|
+
module VCAP
|
7
|
+
module Spec
|
8
|
+
module ForkedComponent
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class VCAP::Spec::ForkedComponent::NatsServer < VCAP::Spec::ForkedComponent::Base
|
14
|
+
|
15
|
+
attr_reader :uri, :port, :parsed_uri
|
16
|
+
|
17
|
+
def initialize(pid_filename, port, output_basedir='tmp')
|
18
|
+
cmd = "ruby -S bundle exec nats-server -p #{port} -P #{pid_filename} -V -D"
|
19
|
+
super(cmd, 'nats', output_basedir, pid_filename)
|
20
|
+
@port = port
|
21
|
+
@uri = "nats://127.0.0.1:#{@port}"
|
22
|
+
@parsed_uri = URI.parse(@uri)
|
23
|
+
end
|
24
|
+
|
25
|
+
def ready?
|
26
|
+
running? && NATS.server_running?(@parsed_uri)
|
27
|
+
end
|
28
|
+
end
|