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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 404c05c867f92b146e2f9d046fe2d8741bf38402
4
- data.tar.gz: 6dcb82793a413080b7230d34228d6aab9ae70845
2
+ SHA256:
3
+ metadata.gz: 39843870440d8a0b5c982dd74c45e26964e20b8ed36f67da279237bf5cf65888
4
+ data.tar.gz: 8cedec2b6ea1a00825e85506508d5bdfcf9a2587fa9b8ce183fda54bd328eee7
5
5
  SHA512:
6
- metadata.gz: d06217562d449a04f11c904d2cb0179a6bdbabe1458a48bc494f20fc38e7f29246bcfdb4d2173b99f1a1592d4a586d16b50cd638db7334523060f405110e646d
7
- data.tar.gz: 7b4d892e27a3271e312b326a660d64a406127963b1f52356158ff17ca0f3e821f17dd22ef74351f4576354c86c8b059745b5150419d450c7b1175da004edd2b9
6
+ metadata.gz: 2b3848de010761d878558d11a0ff055fe986c603d42cf757062dca1c2bdc9fbe573098d424d15cd5087216944bf097d196aba3fa51eafed180df35a63f53e335
7
+ data.tar.gz: d24367a53c159cc98ea731a735a78bd1c380679888963c0a6b65ef8106bf5d012823c5ee106ae53889cce5a7b092f227bc2e9f8bc0144b0d637c77d5e4c8a827
@@ -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
@@ -0,0 +1,4 @@
1
+ class PCPClient
2
+ VERSION = '0.5.2'.freeze
3
+ end
4
+
@@ -1,7 +1,54 @@
1
- require 'eventmachine-le'
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, @server] }
65
- @connection = Faye::WebSocket::Client.new(@server, nil, {:tls => {:private_key_file => @ssl_key,
66
- :cert_chain_file => @ssl_cert,
67
- :ssl_version => :TLSv1}})
68
-
69
- @connection.on :open do |event|
70
- begin
71
- @logger.info { [:open] }
72
- send(associate_request)
73
- rescue Exception => e
74
- @logger.error { [:open_exception, e] }
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
- @connection.on :message do |event|
79
- begin
80
- message = ::PCP::Message.new(event.data)
81
- @logger.debug { [:message, :decoded, message] }
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
- if message[:message_type] == 'http://puppetlabs.com/associate_response'
150
+ @connection.on :close do |event|
151
+ begin
152
+ @logger.info { [:close, event.code, event.reason] }
84
153
  mutex.synchronize do
85
- @associated = JSON.load(message.data)["success"]
154
+ @associated = false
86
155
  associated_cv.signal
87
156
  end
88
- elsif @on_message
89
- @on_message.call(message)
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
- @connection.on :close do |event|
97
- begin
98
- @logger.info { [:close, event.code, event.reason] }
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
- @connection.on :error do |event|
109
- @logger.error { [:error, event] }
110
- @associated = false
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
- @logger.debug { [:send, message] }
140
- message[:sender] = identity
141
- @connection.send(message.encode)
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
@@ -111,11 +111,19 @@ module PCP
111
111
  chunks << frame_chunk(i + 2, @chunks[i])
112
112
  end
113
113
 
114
- RSchema.validate!(PCP::Protocol::Envelope, envelope)
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
- RSchema.validate!(PCP::Protocol::Envelope, @envelope)
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.0
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: 2016-02-18 00:00:00.000000000 Z
11
+ date: 2020-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: eventmachine-le
14
+ name: eventmachine
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.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.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: '0.10'
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: '0.10'
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
- rubyforge_project:
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: