omf_common 6.0.2.pre.2 → 6.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/bin/file_broadcaster.rb +5 -0
  2. data/bin/file_receiver.rb +5 -0
  3. data/bin/omf_keygen +3 -3
  4. data/bin/omf_send_configure +114 -0
  5. data/bin/omf_send_request +19 -4
  6. data/example/engine_alt.rb +13 -7
  7. data/example/viz/garage_monitor.rb +69 -0
  8. data/example/viz/garage_viz.rb +52 -0
  9. data/example/viz/htdocs/image/garage.png +0 -0
  10. data/example/viz/htdocs/template/garage_banner.html +2 -0
  11. data/example/viz/layout.yaml +44 -0
  12. data/example/vm_alt.rb +5 -0
  13. data/lib/omf_common.rb +17 -8
  14. data/lib/omf_common/auth.rb +5 -0
  15. data/lib/omf_common/auth/certificate.rb +21 -2
  16. data/lib/omf_common/auth/certificate_store.rb +50 -20
  17. data/lib/omf_common/auth/ssh_pub_key_convert.rb +7 -0
  18. data/lib/omf_common/comm.rb +6 -1
  19. data/lib/omf_common/comm/amqp/amqp_communicator.rb +88 -12
  20. data/lib/omf_common/comm/amqp/amqp_file_transfer.rb +5 -0
  21. data/lib/omf_common/comm/amqp/amqp_topic.rb +37 -18
  22. data/lib/omf_common/comm/local/local_communicator.rb +5 -0
  23. data/lib/omf_common/comm/local/local_topic.rb +5 -0
  24. data/lib/omf_common/comm/topic.rb +32 -13
  25. data/lib/omf_common/comm/xmpp/communicator.rb +11 -1
  26. data/lib/omf_common/comm/xmpp/topic.rb +5 -0
  27. data/lib/omf_common/comm/xmpp/xmpp_mp.rb +5 -0
  28. data/lib/omf_common/command.rb +5 -0
  29. data/lib/omf_common/core_ext/string.rb +5 -0
  30. data/lib/omf_common/default_logging.rb +23 -5
  31. data/lib/omf_common/eventloop.rb +40 -23
  32. data/lib/omf_common/eventloop/em.rb +18 -5
  33. data/lib/omf_common/eventloop/local_evl.rb +18 -15
  34. data/lib/omf_common/exec_app.rb +44 -24
  35. data/lib/omf_common/key.rb +5 -0
  36. data/lib/omf_common/measure.rb +5 -0
  37. data/lib/omf_common/message.rb +5 -0
  38. data/lib/omf_common/message/json/json_message.rb +13 -5
  39. data/lib/omf_common/message/xml/message.rb +19 -4
  40. data/lib/omf_common/message/xml/relaxng_schema.rb +5 -0
  41. data/lib/omf_common/message/xml/topic_message.rb +5 -0
  42. data/lib/omf_common/version.rb +6 -1
  43. data/omf_common.gemspec +3 -2
  44. data/test/fixture/1st_level.pem +20 -0
  45. data/test/fixture/2nd_level.pem +19 -0
  46. data/test/fixture/3rd_level.pem +19 -0
  47. data/test/fixture/pubsub.rb +5 -0
  48. data/test/fixture/rc.pem +18 -0
  49. data/test/fixture/root.pem +17 -0
  50. data/test/omf_common/auth/certificate_spec.rb +27 -0
  51. data/test/omf_common/auth/certificate_store_spec.rb +58 -0
  52. data/test/omf_common/auth/ssh_pub_key_convert_spec.rb +5 -0
  53. data/test/omf_common/comm/topic_spec.rb +7 -1
  54. data/test/omf_common/comm/xmpp/communicator_spec.rb +5 -0
  55. data/test/omf_common/comm/xmpp/topic_spec.rb +5 -0
  56. data/test/omf_common/comm_spec.rb +5 -0
  57. data/test/omf_common/command_spec.rb +5 -0
  58. data/test/omf_common/core_ext/string_spec.rb +5 -0
  59. data/test/omf_common/message/xml/message_spec.rb +5 -0
  60. data/test/omf_common/message_spec.rb +8 -3
  61. data/test/test_helper.rb +5 -0
  62. metadata +48 -11
@@ -1,3 +1,8 @@
1
+ # Copyright (c) 2012 National ICT Australia Limited (NICTA).
2
+ # This software may be used and distributed solely under the terms of the MIT license (License).
3
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
+
1
6
 
2
7
 
3
8
  module OmfCommon
@@ -1,3 +1,8 @@
1
+ # Copyright (c) 2012 National ICT Australia Limited (NICTA).
2
+ # This software may be used and distributed solely under the terms of the MIT license (License).
3
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
+
1
6
  require 'openssl'
2
7
  require 'omf_common/auth'
3
8
  require 'omf_common/auth/ssh_pub_key_convert'
@@ -32,9 +37,11 @@ module OmfCommon::Auth
32
37
  # @param [String] pem is the content of existing x509 cert
33
38
  # @param [OpenSSL::PKey::RSA|String] key is the private key which can be attached to the instance for signing.
34
39
  def self.create_from_x509(pem, key = nil)
35
- unless pem.start_with? BEGIN_CERT
40
+ # Some command list generated cert can use \r\n as newline char
41
+ unless pem =~ /^-----BEGIN CERTIFICATE-----/
36
42
  pem = "#{BEGIN_CERT}#{pem}#{END_CERT}"
37
43
  end
44
+
38
45
  cert = OpenSSL::X509::Certificate.new(pem)
39
46
 
40
47
  key = OpenSSL::PKey::RSA.new(key) if key && key.is_a?(String)
@@ -64,7 +71,9 @@ module OmfCommon::Auth
64
71
 
65
72
  # Create a X509 certificate
66
73
  #
67
- # @param [] address
74
+ # @param [String] address
75
+ # @param [String] subject
76
+ # @param [OpenSSL::PKey::RSA] key
68
77
  # @return {cert, key}
69
78
  #
70
79
  def self._create_x509_cert(address, subject, key, digest = nil,
@@ -127,8 +136,18 @@ module OmfCommon::Auth
127
136
  @cert ||= _create_x509_cert(@address, @subject, @key, @digest)[:cert]
128
137
  end
129
138
 
139
+ # @param [OpenSSL::PKey::RSA|String] key is most likely the public key of the resource.
140
+ #
130
141
  def create_for(address, name, type, domain = DEF_DOMAIN_NAME, duration = 3600, key = nil)
131
142
  raise ArgumentError, "Address required" unless address
143
+
144
+ begin
145
+ key = OpenSSL::PKey::RSA.new(key) if key && key.is_a?(String)
146
+ rescue OpenSSL::PKey::RSAError
147
+ # It might be a SSH pub key, try that
148
+ key = OmfCommon::Auth::SSHPubKeyConvert.convert(key)
149
+ end
150
+
132
151
  cert = self.class.create(address, name, type, domain, self, Time.now, duration, key)
133
152
  CertificateStore.instance.register(cert, address)
134
153
  cert
@@ -1,27 +1,19 @@
1
+ # Copyright (c) 2012 National ICT Australia Limited (NICTA).
2
+ # This software may be used and distributed solely under the terms of the MIT license (License).
3
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
+
1
6
  require 'openssl'
7
+ require 'monitor'
2
8
 
3
9
  require 'omf_common/auth'
4
10
 
5
- #require 'singleton'
6
-
7
- # module OmfCommon
8
- # class Key
9
- # include Singleton
10
- #
11
- # attr_accessor :private_key
12
- #
13
- # def import(filename)
14
- # self.private_key = OpenSSL::PKey.read(File.read(filename))
15
- # end
16
- # end
17
- # end
18
-
19
11
  module OmfCommon::Auth
20
12
 
21
13
  class MissingPrivateKeyException < AuthException; end
22
14
 
23
15
  class CertificateStore
24
-
16
+ include MonitorMixin
25
17
 
26
18
  @@instance = nil
27
19
 
@@ -38,12 +30,27 @@ module OmfCommon::Auth
38
30
  end
39
31
 
40
32
  def register(certificate, address = nil)
41
- if address ||= certificate.address
42
- @certs[address] = certificate if address
43
- else
44
- warn "Register certificate without address - #{certificate}"
33
+ @@instance.synchronize do
34
+ begin
35
+ @x509_store.add_cert(certificate.to_x509) if @certs[address].nil? && @certs[certificate.subject].nil?
36
+ rescue OpenSSL::X509::StoreError => e
37
+ if e.message == "cert already in hash table"
38
+ raise "X509 cert '#{address}' already registered in X509 store"
39
+ else
40
+ raise e
41
+ end
42
+ end
43
+
44
+ address ||= certificate.address
45
+
46
+ if address
47
+ @certs[address] ||= certificate
48
+ else
49
+ debug "Register certificate without address - #{certificate}, is it a CA cert?"
50
+ end
51
+
52
+ @certs[certificate.subject] ||= certificate
45
53
  end
46
- @certs[certificate.subject] = certificate
47
54
  end
48
55
 
49
56
  def register_x509(cert_pem, address = nil)
@@ -57,15 +64,38 @@ module OmfCommon::Auth
57
64
  @certs[url]
58
65
  end
59
66
 
67
+ # @param [OpenSSL::X509::Certificate] cert
68
+ #
69
+ def verify(cert)
70
+ cert = cert.to_x509 if cert.kind_of? OmfCommon::Auth::Certificate
71
+ v_result = @x509_store.verify(cert)
72
+ warn "Cert verification failed: '#{@x509_store.error_string}'" unless v_result
73
+ v_result
74
+ end
75
+
76
+ # Load a set of CA certs into cert store from a given location
77
+ #
78
+ # @param [String] folder contains all the CA certs
79
+ #
80
+ def register_default_certs(folder)
81
+ Dir["#{folder}/*"].each do |cert|
82
+ register_x509(File.read(cert))
83
+ end
84
+ end
60
85
 
61
86
  private
87
+
62
88
  def initialize(opts)
89
+ @x509_store = OpenSSL::X509::Store.new
90
+
63
91
  @certs = {}
64
92
  if store = opts[:store]
65
93
  else
66
94
  @store = {private: {}, public: {}}
67
95
  end
68
96
  @serial = 0
97
+
98
+ super()
69
99
  end
70
100
  end # class
71
101
 
@@ -1,3 +1,8 @@
1
+ # Copyright (c) 2012 National ICT Australia Limited (NICTA).
2
+ # This software may be used and distributed solely under the terms of the MIT license (License).
3
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
+
1
6
  require 'base64'
2
7
  require 'openssl'
3
8
  require 'omf_common/auth'
@@ -42,6 +47,8 @@ module OmfCommon::Auth
42
47
  #
43
48
  def self.convert(keystring)
44
49
  (type, b64, id) = keystring.split(' ')
50
+ raise ArgumentError, "Invalid SSH public key '#{keystring}'" if b64.nil?
51
+
45
52
  decoded_key = Base64.decode64(b64)
46
53
  (n, bytes) = unpack_u32(decoded_key)
47
54
  (keytype, bytes) = unpack_string(bytes, n)
@@ -1,3 +1,8 @@
1
+ # Copyright (c) 2012 National ICT Australia Limited (NICTA).
2
+ # This software may be used and distributed solely under the terms of the MIT license (License).
3
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
+
1
6
  require 'omf_common/comm/topic'
2
7
 
3
8
  module OmfCommon
@@ -122,7 +127,7 @@ module OmfCommon
122
127
 
123
128
  # Returning connection information
124
129
  #
125
- # @retun [Hash] connection information hash, with type, user and domain.
130
+ # @return [Hash] connection information hash, with type, user and domain.
126
131
  def conn_info
127
132
  { proto: nil, user: nil, domain: nil }
128
133
  end
@@ -1,3 +1,8 @@
1
+ # Copyright (c) 2012 National ICT Australia Limited (NICTA).
2
+ # This software may be used and distributed solely under the terms of the MIT license (License).
3
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
+
1
6
  require 'amqp'
2
7
  require 'omf_common/comm/amqp/amqp_topic'
3
8
  #require 'omf_common/comm/monkey_patches'
@@ -11,23 +16,25 @@ module OmfCommon
11
16
  # # ignore arguments
12
17
  # end
13
18
 
19
+ attr_reader :channel
20
+
14
21
  # Initialize comms layer
15
22
  #
16
23
  def init(opts = {})
17
- unless (@url = opts[:url])
24
+ @opts = {
25
+ #:ssl (Hash) � TLS (SSL) parameters to use.
26
+ heartbeat: 20, # (Fixnum) � default: 0 � Connection heartbeat, in seconds. 0 means no heartbeat. Can also be configured server-side starting with RabbitMQ 3.0.
27
+ #:on_tcp_connection_failure (#call) � A callable object that will be run if connection to server fails
28
+ #:on_possible_authentication_failure (#call) � A callable object that will be run if authentication fails (see Authentication failure section)
29
+ reconnect_delay: 20 # (Fixnum) Delay in seconds before attempting reconnect on detected failure
30
+ }.merge(opts)
31
+
32
+ unless (@url = @opts.delete(:url))
18
33
  raise "Missing 'url' option for AQMP layer"
19
34
  end
20
35
  @address_prefix = @url + '/'
21
- ::AMQP.connect(@url) do |connection|
22
- @channel = ::AMQP::Channel.new(connection)
23
- @on_connected_procs.each do |proc|
24
- proc.arity == 1 ? proc.call(self) : proc.call
25
- end
26
-
27
- OmfCommon.eventloop.on_stop do
28
- connection.close
29
- end
30
- end
36
+ _connect()
37
+ #AMQP::Session#on_skipped_heartbeats callback that can be used to handle skipped heartbeats
31
38
  super
32
39
  end
33
40
 
@@ -44,13 +51,25 @@ module OmfCommon
44
51
  @on_connected_procs << block
45
52
  end
46
53
 
54
+ # register callbacks to be called when the underlying AMQP layer
55
+ # needs to reconnect to the AMQP server. This may require some additional
56
+ # repairs. If 'block' is nil, the callback is removed
57
+ #
58
+ def on_reconnect(key, &block)
59
+ if block.nil?
60
+ @on_reconnect.delete(key)
61
+ else
62
+ @on_reconnect[key] = block
63
+ end
64
+ end
65
+
47
66
  # Create a new pubsub topic with additional configuration
48
67
  #
49
68
  # @param [String] topic Pubsub topic name
50
69
  def create_topic(topic, opts = {})
51
70
  raise "Topic can't be nil or empty" if topic.nil? || topic.empty?
52
71
  opts = opts.dup
53
- opts[:channel] = @channel
72
+ opts[:communicator] = self
54
73
  topic = topic.to_s
55
74
  if topic.start_with? 'amqp:'
56
75
  # absolute address
@@ -95,8 +114,65 @@ module OmfCommon
95
114
  private
96
115
  def initialize(opts = {})
97
116
  @on_connected_procs = []
117
+ @on_reconnect = {}
98
118
  super
99
119
  end
120
+
121
+ def _connect()
122
+ last_reported_timestamp = nil
123
+ @session = ::AMQP.connect(@url, @opts) do |connection|
124
+ connection.on_tcp_connection_loss do |conn, settings|
125
+ now = Time.now
126
+ if last_reported_timestamp == nil || (now - last_reported_timestamp) > 60
127
+ warn "Lost connectivity. Trying to reconnect..."
128
+ last_reported_timestamp = now
129
+ end
130
+ conn.reconnect(false, 2)
131
+ end
132
+ @channel = ::AMQP::Channel.new(connection)
133
+ @channel.auto_recovery = true
134
+
135
+
136
+ @on_connected_procs.each do |proc|
137
+ proc.arity == 1 ? proc.call(self) : proc.call
138
+ end
139
+
140
+ OmfCommon.eventloop.on_stop do
141
+ connection.close
142
+ end
143
+ end
144
+
145
+ rec_delay = @opts[:reconnect_delay]
146
+ @session.on_tcp_connection_failure do
147
+ warn "Cannot connect to AMQP server '#{@url}'. Attempt to retry in #{rec_delay} sec"
148
+ @session = nil
149
+ OmfCommon.eventloop.after(rec_delay) do
150
+ info 'Retrying'
151
+ _connect
152
+ end
153
+ end
154
+ # @session.on_tcp_connection_loss do
155
+ # _reconnect "Appear to have lost tcp connection. Attempt to reconnect in #{rec_delay} sec"
156
+ # end
157
+ # @session.on_skipped_heartbeats do
158
+ # _reconnect "Appear to have lost heartbeat. Attempt to reconnect in #{rec_delay} sec"
159
+ # end
160
+ @session.on_recovery do
161
+ info 'Recovered!'
162
+ last_reported_timestamp = nil
163
+ @on_reconnect.values.each do |block|
164
+ block.call()
165
+ end
166
+ end
167
+ end
168
+
169
+ # def _reconnect(warn_message = nil)
170
+ # warn(warn_message) if warn_message
171
+ # OmfCommon.eventloop.after(@opts[:reconnect_delay]) do
172
+ # info 'Reconnecting'
173
+ # @session.reconnect
174
+ # end
175
+ # end
100
176
  end
101
177
  end
102
178
  end
@@ -1,3 +1,8 @@
1
+ # Copyright (c) 2012 National ICT Australia Limited (NICTA).
2
+ # This software may be used and distributed solely under the terms of the MIT license (License).
3
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
+
1
6
  require 'set'
2
7
  require 'monitor'
3
8
 
@@ -1,3 +1,8 @@
1
+ # Copyright (c) 2012 National ICT Australia Limited (NICTA).
2
+ # This software may be used and distributed solely under the terms of the MIT license (License).
3
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
+
1
6
 
2
7
 
3
8
  module OmfCommon
@@ -8,17 +13,17 @@ module OmfCommon
8
13
  def to_s
9
14
  "AMQP::Topic<#{id}>"
10
15
  end
11
-
16
+
12
17
  def address
13
18
  @address
14
19
  end
15
-
20
+
16
21
  # Call 'block' when topic is subscribed to underlying messaging
17
- # infrastructure.
22
+ # infrastructure.
18
23
  #
19
24
  def on_subscribed(&block)
20
25
  return unless block
21
-
26
+
22
27
  call_now = false
23
28
  @lock.synchronize do
24
29
  if @subscribed
@@ -30,24 +35,35 @@ module OmfCommon
30
35
  if call_now
31
36
  after(0, &block)
32
37
  end
33
- end
34
-
35
-
38
+ end
39
+
40
+
36
41
  private
37
-
42
+
38
43
  def initialize(id, opts = {})
39
- unless channel = opts.delete(:channel)
40
- raise "Missing :channel option"
44
+ unless @communicator = opts.delete(:communicator)
45
+ raise "Missing :communicator option"
41
46
  end
42
47
  super
43
48
  @address = opts[:address]
44
- @exchange = channel.topic(id, :auto_delete => true)
45
49
  @lock = Monitor.new
46
50
  @subscribed = false
47
51
  @on_subscribed_handlers = []
48
-
49
- # Subscribe as well
50
- #puts "QQ0(#{id})"
52
+
53
+ # @communicator.on_reconnect(self) do
54
+ # info "Resubscribe '#{self}'"
55
+ # _init_amqp
56
+ # end
57
+ _init_amqp
58
+ end
59
+
60
+ def _init_amqp()
61
+ channel = @communicator.channel
62
+ @exchange = channel.topic(id, :auto_delete => true)
63
+ # @exchange.on_connection_interruption do |ex|
64
+ # warn "Exchange #{ex.name} detected connection interruption"
65
+ # @exchange = nil
66
+ # end
51
67
  channel.queue("", :exclusive => true) do |queue|
52
68
  #puts "QQ1(#{id}): #{queue}"
53
69
  queue.bind(@exchange)
@@ -70,15 +86,18 @@ module OmfCommon
70
86
  end
71
87
  end
72
88
  end
73
-
74
-
89
+
75
90
  def _send_message(msg, block = nil)
76
91
  super
77
92
  content_type, content = msg.marshall(self)
78
93
  debug "(#{id}) Send message (#{content_type}) #{msg.inspect}"
79
- @exchange.publish(content, content_type: content_type, message_id: msg.mid)
94
+ if @exchange
95
+ @exchange.publish(content, content_type: content_type, message_id: msg.mid)
96
+ else
97
+ warn "Unavailable AMQP channel. Dropping message '#{msg}'"
98
+ end
80
99
  end
81
100
  end # class
82
- end # module
101
+ end # module
83
102
  end # module
84
103
  end # module