pcp-client 0.2.0 → 0.5.2

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