vcap_common 1.0.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|