pcp-client 0.2.0 → 0.5.2
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.
- checksums.yaml +5 -5
- data/bin/pcp-ping +112 -0
- data/lib/pcp-client/version.rb +4 -0
- data/lib/pcp/client.rb +136 -50
- data/lib/pcp/message.rb +10 -2
- metadata +14 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 39843870440d8a0b5c982dd74c45e26964e20b8ed36f67da279237bf5cf65888
|
4
|
+
data.tar.gz: 8cedec2b6ea1a00825e85506508d5bdfcf9a2587fa9b8ce183fda54bd328eee7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b3848de010761d878558d11a0ff055fe986c603d42cf757062dca1c2bdc9fbe573098d424d15cd5087216944bf097d196aba3fa51eafed180df35a63f53e335
|
7
|
+
data.tar.gz: d24367a53c159cc98ea731a735a78bd1c380679888963c0a6b65ef8106bf5d012823c5ee106ae53889cce5a7b092f227bc2e9f8bc0144b0d637c77d5e4c8a827
|
data/bin/pcp-ping
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'pcp/client'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
ssl_key = '../pcp-broker/test-resources/ssl/private_keys/client01.example.com.pem'
|
6
|
+
ssl_cert = '../pcp-broker/test-resources/ssl/certs/client01.example.com.pem'
|
7
|
+
ssl_ca_cert = '../pcp-broker/test-resources/ssl/ca/ca_crt.pem'
|
8
|
+
debug = false
|
9
|
+
server = 'wss://localhost:8142/pcp'
|
10
|
+
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.on("-d", "--debug", "Run noisily") { |v| debug = v }
|
13
|
+
opts.on("--ssl_key FILE", "Use this ssl key") { |v| ssl_key = v }
|
14
|
+
opts.on("--ssl_cert FILE", "Use this ssl cert") { |v| ssl_cert = v }
|
15
|
+
opts.on("--ssl_ca_cert FILE", "Use this ssl ca cert") { |v| ssl_ca_cert = v }
|
16
|
+
opts.on("--server URL", "Server to connect to") { |v| server = v }
|
17
|
+
end.parse!
|
18
|
+
|
19
|
+
start_time = Time.now.to_f
|
20
|
+
|
21
|
+
Thread.new { EM.run }
|
22
|
+
Thread.pass until EM.reactor_running?
|
23
|
+
|
24
|
+
client = PCP::Client.new({:ssl_key => ssl_key,
|
25
|
+
:ssl_cert => ssl_cert,
|
26
|
+
:ssl_ca_cert => ssl_ca_cert,
|
27
|
+
:loglevel => debug ? Logger::DEBUG : Logger::WARN,
|
28
|
+
:server => server,
|
29
|
+
})
|
30
|
+
|
31
|
+
# Record the expected set of destinations from the destination_report,
|
32
|
+
# then all the responses from the rpc_blocking_response
|
33
|
+
mutex = Mutex.new
|
34
|
+
have_responses = ConditionVariable.new
|
35
|
+
responses = {}
|
36
|
+
expected = nil
|
37
|
+
ping_time = nil
|
38
|
+
|
39
|
+
client.on_message = proc do |message|
|
40
|
+
mutex.synchronize do
|
41
|
+
case message[:message_type]
|
42
|
+
when 'http://puppetlabs.com/destination_report'
|
43
|
+
expected = JSON.load(message.data)['targets']
|
44
|
+
when 'http://puppetlabs.com/rpc_blocking_response'
|
45
|
+
sender = message[:sender]
|
46
|
+
time = Time.now.to_f - ping_time
|
47
|
+
responses[sender] = time
|
48
|
+
puts '%-55s %0.2f seconds' % [sender, time]
|
49
|
+
else
|
50
|
+
p [:unexpected_message, message]
|
51
|
+
end
|
52
|
+
|
53
|
+
have_responses.signal
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
client.connect
|
58
|
+
|
59
|
+
if !client.associated?
|
60
|
+
puts "Didn't connect to broker."
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
|
64
|
+
connect_time = Time.now.to_f - start_time
|
65
|
+
puts "Connected to broker in %0.2f seconds." % [connect_time]
|
66
|
+
puts
|
67
|
+
|
68
|
+
ping = ::PCP::Message.new(:message_type => 'http://puppetlabs.com/rpc_blocking_request',
|
69
|
+
:targets => ['pcp://*/agent'],
|
70
|
+
:destination_report => true)
|
71
|
+
|
72
|
+
ping.data = {:transaction_id => ping[:id],
|
73
|
+
:module => 'echo',
|
74
|
+
:action => 'echo',
|
75
|
+
:params => {:argument => 'echo'}}.to_json
|
76
|
+
|
77
|
+
ping.expires(3)
|
78
|
+
|
79
|
+
ping_time = Time.now.to_f
|
80
|
+
client.send(ping)
|
81
|
+
|
82
|
+
begin
|
83
|
+
Timeout::timeout(3) do
|
84
|
+
done = false
|
85
|
+
loop do
|
86
|
+
mutex.synchronize do
|
87
|
+
have_responses.wait(mutex)
|
88
|
+
if expected && expected.all? { |from| responses.include?(from) }
|
89
|
+
# Have all responses we expect, bail
|
90
|
+
run_time = Time.now.to_f - start_time
|
91
|
+
puts
|
92
|
+
puts "Received all responses in %0.2f seconds." % [run_time]
|
93
|
+
done = true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
break if done
|
97
|
+
end
|
98
|
+
end
|
99
|
+
rescue Timeout::Error
|
100
|
+
puts
|
101
|
+
if expected
|
102
|
+
puts "Received %d/%d responses. Following nodes were missing" % [responses.keys.size, expected.size]
|
103
|
+
puts
|
104
|
+
expected.select { |from| !responses.include?(from) }.each do |missing|
|
105
|
+
puts " #{missing}"
|
106
|
+
end
|
107
|
+
else
|
108
|
+
puts "Didn't get destination report, not sure how many responses to expect. We got %d." % [responses.keys.size]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
EM.stop
|
data/lib/pcp/client.rb
CHANGED
@@ -1,7 +1,54 @@
|
|
1
|
-
require 'eventmachine
|
1
|
+
require 'eventmachine'
|
2
2
|
require 'faye/websocket'
|
3
3
|
require 'pcp/message'
|
4
4
|
require 'logger'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
# So EventMachine when you specify :verify_peer => true in the TLS
|
8
|
+
# options decides what that means is it should just fire off a
|
9
|
+
# #ssl_verify_peer(cert) on the Connection object; which is expected
|
10
|
+
# to be user-supplied. In this case the user is
|
11
|
+
# Faye::Websocket::Client::Connection, so we monkey-patch it to have a
|
12
|
+
# #ssl_verify_peer method.
|
13
|
+
|
14
|
+
module Faye
|
15
|
+
class WebSocket
|
16
|
+
class Client
|
17
|
+
module Connection
|
18
|
+
def ssl_verify_peer(cert)
|
19
|
+
# The :@socket_tls instance variable of
|
20
|
+
# Faye::Websocket::Client is passed to tls_start, so we can
|
21
|
+
# get parameters from there.
|
22
|
+
start_tls_options = parent.instance_variable_get(:@socket_tls)
|
23
|
+
logger = start_tls_options[:xxx_logger]
|
24
|
+
logger.debug { [:ssl_verify_peer] }
|
25
|
+
|
26
|
+
peer_cert = OpenSSL::X509::Certificate.new cert
|
27
|
+
|
28
|
+
hostname = start_tls_options[:xxx_hostname]
|
29
|
+
if !OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname)
|
30
|
+
logger.error { [:ssl_verify_peer, :fail,
|
31
|
+
"Certificate presented does not match '#{hostname}'"] }
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
|
35
|
+
ssl_ca_cert = start_tls_options[:xxx_ssl_ca_cert]
|
36
|
+
cert_store = OpenSSL::X509::Store.new
|
37
|
+
cert_store.add_file ssl_ca_cert
|
38
|
+
|
39
|
+
if !cert_store.verify(peer_cert)
|
40
|
+
logger.error { [:ssl_verify_peer, :ca_verify_failed,
|
41
|
+
"Peer certificate not verified by ca"] }
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
|
45
|
+
logger.debug { [:ssl_verify_peer, :success] }
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
5
52
|
|
6
53
|
module PCP
|
7
54
|
# Manages a client connection to a pcp broker
|
@@ -27,12 +74,14 @@ module PCP
|
|
27
74
|
@server = params[:server] || 'wss://localhost:8142/pcp'
|
28
75
|
@ssl_key = params[:ssl_key]
|
29
76
|
@ssl_cert = params[:ssl_cert]
|
77
|
+
@ssl_ca_cert = params[:ssl_ca_cert]
|
30
78
|
@logger = params[:logger] || Logger.new(STDOUT)
|
31
79
|
@logger.level = params[:loglevel] || Logger::WARN
|
32
80
|
@connection = nil
|
33
81
|
type = params[:type] || "ruby-pcp-client-#{$$}"
|
34
82
|
@identity = make_identity(@ssl_cert, type)
|
35
83
|
@on_message = params[:on_message]
|
84
|
+
@max_message_size = params[:max_message_size] || 64*1024*1024
|
36
85
|
@associated = false
|
37
86
|
end
|
38
87
|
|
@@ -42,17 +91,6 @@ module PCP
|
|
42
91
|
# @param seconds [Numeric]
|
43
92
|
# @return [true,false,nil]
|
44
93
|
def connect(seconds = 0)
|
45
|
-
unless EM.reactor_running?
|
46
|
-
raise "An Eventmachine reactor needs to be running"
|
47
|
-
end
|
48
|
-
|
49
|
-
if EM.reactor_thread?
|
50
|
-
# Because we use a condition variable to signal this thread
|
51
|
-
# from the reactor thread to provide an imperative interface,
|
52
|
-
# they cannot be the same thread
|
53
|
-
raise "Cannot be run on the same thread as the reactor"
|
54
|
-
end
|
55
|
-
|
56
94
|
if @connection
|
57
95
|
# We close over so much, we really just need to say no for now
|
58
96
|
raise "Can only connect once per client"
|
@@ -61,53 +99,86 @@ module PCP
|
|
61
99
|
mutex = Mutex.new
|
62
100
|
associated_cv = ConditionVariable.new
|
63
101
|
|
64
|
-
@logger.debug { [:connect,
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
102
|
+
@logger.debug { [:connect, :scheduling] }
|
103
|
+
EM.next_tick do
|
104
|
+
@logger.debug { [:connect, @server] }
|
105
|
+
|
106
|
+
start_tls_options = {
|
107
|
+
:ssl_version => ["TLSv1", "TLSv1_1", "TLSv1_2"],
|
108
|
+
:private_key_file => @ssl_key,
|
109
|
+
:cert_chain_file => @ssl_cert,
|
110
|
+
:verify_peer => true,
|
111
|
+
:fail_if_no_peer_cert => true,
|
112
|
+
# side-channeled properties we want around during ssl
|
113
|
+
# verification are prefixed with xxx_.
|
114
|
+
:xxx_logger => @logger,
|
115
|
+
:xxx_ssl_ca_cert => @ssl_ca_cert,
|
116
|
+
:xxx_hostname => URI.parse(@server).host,
|
117
|
+
}
|
118
|
+
|
119
|
+
@connection = Faye::WebSocket::Client.new(@server, nil, {:tls => start_tls_options,
|
120
|
+
:ping => 30,
|
121
|
+
:max_length => @max_message_size})
|
122
|
+
|
123
|
+
@connection.on :open do |event|
|
124
|
+
begin
|
125
|
+
@logger.info { [:open] }
|
126
|
+
send(associate_request)
|
127
|
+
rescue Exception => e
|
128
|
+
@logger.error { [:open_exception, e] }
|
129
|
+
end
|
75
130
|
end
|
76
|
-
end
|
77
131
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
132
|
+
@connection.on :message do |event|
|
133
|
+
begin
|
134
|
+
message = ::PCP::Message.new(event.data)
|
135
|
+
@logger.debug { [:message, :decoded, message] }
|
136
|
+
|
137
|
+
if message[:message_type] == 'http://puppetlabs.com/associate_response'
|
138
|
+
mutex.synchronize do
|
139
|
+
@associated = JSON.load(message.data)["success"]
|
140
|
+
associated_cv.signal
|
141
|
+
end
|
142
|
+
elsif @on_message
|
143
|
+
@on_message.call(message)
|
144
|
+
end
|
145
|
+
rescue Exception => e
|
146
|
+
@logger.error { [:message_exception, e] }
|
147
|
+
end
|
148
|
+
end
|
82
149
|
|
83
|
-
|
150
|
+
@connection.on :close do |event|
|
151
|
+
begin
|
152
|
+
@logger.info { [:close, event.code, event.reason] }
|
84
153
|
mutex.synchronize do
|
85
|
-
@associated =
|
154
|
+
@associated = false
|
86
155
|
associated_cv.signal
|
87
156
|
end
|
88
|
-
|
89
|
-
@
|
157
|
+
rescue Exception => e
|
158
|
+
@logger.error { [:close_exception, e] }
|
90
159
|
end
|
91
|
-
rescue Exception => e
|
92
|
-
@logger.error { [:message_exception, e] }
|
93
160
|
end
|
94
|
-
end
|
95
161
|
|
96
|
-
|
97
|
-
|
98
|
-
@
|
99
|
-
mutex.synchronize do
|
100
|
-
@associated = false
|
101
|
-
associated_cv.signal
|
102
|
-
end
|
103
|
-
rescue Exception => e
|
104
|
-
@logger.error { [:close_exception, e] }
|
162
|
+
@connection.on :error do |event|
|
163
|
+
@logger.error { [:error, event] }
|
164
|
+
@associated = false
|
105
165
|
end
|
106
166
|
end
|
107
167
|
|
108
|
-
|
109
|
-
@logger.
|
110
|
-
|
168
|
+
if !EM.reactor_running?
|
169
|
+
@logger.debug { [:no_eventmachine_reactor,
|
170
|
+
"Eventmachine reactor is not running" ] }
|
171
|
+
return nil
|
172
|
+
end
|
173
|
+
|
174
|
+
if EM.reactor_thread?
|
175
|
+
# Because we use a condition variable to signal this thread
|
176
|
+
# from the reactor thread to provide an imperative interface,
|
177
|
+
# they cannot be the same thread. We might associate later,
|
178
|
+
# we just can't wait on ourselves from here.
|
179
|
+
@logger.debug { [:connection_cannot_wait,
|
180
|
+
"Cannot wait on a connection if we are in the same thread as the reactor" ] }
|
181
|
+
return nil
|
111
182
|
end
|
112
183
|
|
113
184
|
begin
|
@@ -136,9 +207,24 @@ module PCP
|
|
136
207
|
# @param message [PCP::Message]
|
137
208
|
# @return unused
|
138
209
|
def send(message)
|
139
|
-
|
140
|
-
|
141
|
-
|
210
|
+
EM.next_tick do
|
211
|
+
@logger.debug { [:send, message] }
|
212
|
+
if message[:expires].nil?
|
213
|
+
message.expires(3)
|
214
|
+
end
|
215
|
+
message[:sender] = identity
|
216
|
+
@connection.send(message.encode)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Disconnect the client
|
221
|
+
# @api public
|
222
|
+
# @return unused
|
223
|
+
def close
|
224
|
+
EM.next_tick do
|
225
|
+
@logger.debug { [:close] }
|
226
|
+
@connection.close
|
227
|
+
end
|
142
228
|
end
|
143
229
|
|
144
230
|
private
|
data/lib/pcp/message.rb
CHANGED
@@ -111,11 +111,19 @@ module PCP
|
|
111
111
|
chunks << frame_chunk(i + 2, @chunks[i])
|
112
112
|
end
|
113
113
|
|
114
|
-
|
114
|
+
validate
|
115
115
|
|
116
116
|
[1, frame_chunk(1, envelope.to_json), chunks].flatten
|
117
117
|
end
|
118
118
|
|
119
|
+
# Validate the data in message against schema
|
120
|
+
#
|
121
|
+
# @api public
|
122
|
+
# @return ignore
|
123
|
+
def validate
|
124
|
+
RSchema.validate!(PCP::Protocol::Envelope, envelope)
|
125
|
+
end
|
126
|
+
|
119
127
|
private
|
120
128
|
|
121
129
|
# Decodes an array of bytes into the message
|
@@ -144,7 +152,7 @@ module PCP
|
|
144
152
|
parsed.each do |k,v|
|
145
153
|
@envelope[k.to_sym] = v
|
146
154
|
end
|
147
|
-
|
155
|
+
validate
|
148
156
|
else
|
149
157
|
@chunks[type - 2] = body
|
150
158
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pcp-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet Labs
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: eventmachine
|
14
|
+
name: eventmachine
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: faye-websocket
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - '='
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 0.10.9
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - '='
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 0.10.9
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rschema
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -54,10 +54,13 @@ dependencies:
|
|
54
54
|
version: '1.3'
|
55
55
|
description: See https://github.com/puppetlabs/pcp-specifications
|
56
56
|
email: puppet@puppetlabs.com
|
57
|
-
executables:
|
57
|
+
executables:
|
58
|
+
- pcp-ping
|
58
59
|
extensions: []
|
59
60
|
extra_rdoc_files: []
|
60
61
|
files:
|
62
|
+
- bin/pcp-ping
|
63
|
+
- lib/pcp-client/version.rb
|
61
64
|
- lib/pcp.rb
|
62
65
|
- lib/pcp/client.rb
|
63
66
|
- lib/pcp/message.rb
|
@@ -82,10 +85,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
85
|
- !ruby/object:Gem::Version
|
83
86
|
version: '0'
|
84
87
|
requirements: []
|
85
|
-
|
86
|
-
rubygems_version: 2.2.5
|
88
|
+
rubygems_version: 3.0.8
|
87
89
|
signing_key:
|
88
90
|
specification_version: 4
|
89
91
|
summary: Client library for PCP
|
90
92
|
test_files: []
|
91
|
-
has_rdoc:
|