right_agent 2.0.7-x86-mingw32
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.
- data/LICENSE +20 -0
- data/README.rdoc +82 -0
- data/Rakefile +113 -0
- data/lib/right_agent.rb +59 -0
- data/lib/right_agent/actor.rb +182 -0
- data/lib/right_agent/actor_registry.rb +76 -0
- data/lib/right_agent/actors/agent_manager.rb +232 -0
- data/lib/right_agent/agent.rb +1149 -0
- data/lib/right_agent/agent_config.rb +480 -0
- data/lib/right_agent/agent_identity.rb +210 -0
- data/lib/right_agent/agent_tag_manager.rb +237 -0
- data/lib/right_agent/audit_formatter.rb +107 -0
- data/lib/right_agent/clients.rb +31 -0
- data/lib/right_agent/clients/api_client.rb +383 -0
- data/lib/right_agent/clients/auth_client.rb +247 -0
- data/lib/right_agent/clients/balanced_http_client.rb +369 -0
- data/lib/right_agent/clients/base_retry_client.rb +495 -0
- data/lib/right_agent/clients/right_http_client.rb +279 -0
- data/lib/right_agent/clients/router_client.rb +493 -0
- data/lib/right_agent/command.rb +30 -0
- data/lib/right_agent/command/agent_manager_commands.rb +150 -0
- data/lib/right_agent/command/command_client.rb +136 -0
- data/lib/right_agent/command/command_constants.rb +33 -0
- data/lib/right_agent/command/command_io.rb +126 -0
- data/lib/right_agent/command/command_parser.rb +87 -0
- data/lib/right_agent/command/command_runner.rb +118 -0
- data/lib/right_agent/command/command_serializer.rb +63 -0
- data/lib/right_agent/connectivity_checker.rb +179 -0
- data/lib/right_agent/console.rb +65 -0
- data/lib/right_agent/core_payload_types.rb +44 -0
- data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
- data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
- data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
- data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
- data/lib/right_agent/core_payload_types/dev_repositories.rb +100 -0
- data/lib/right_agent/core_payload_types/dev_repository.rb +76 -0
- data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
- data/lib/right_agent/core_payload_types/executable_bundle.rb +130 -0
- data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
- data/lib/right_agent/core_payload_types/login_user.rb +79 -0
- data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
- data/lib/right_agent/core_payload_types/recipe_instantiation.rb +73 -0
- data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
- data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
- data/lib/right_agent/core_payload_types/right_script_instantiation.rb +94 -0
- data/lib/right_agent/core_payload_types/runlist_policy.rb +44 -0
- data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
- data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
- data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
- data/lib/right_agent/daemonize.rb +35 -0
- data/lib/right_agent/dispatched_cache.rb +109 -0
- data/lib/right_agent/dispatcher.rb +272 -0
- data/lib/right_agent/enrollment_result.rb +221 -0
- data/lib/right_agent/exceptions.rb +87 -0
- data/lib/right_agent/history.rb +145 -0
- data/lib/right_agent/log.rb +460 -0
- data/lib/right_agent/minimal.rb +46 -0
- data/lib/right_agent/monkey_patches.rb +30 -0
- data/lib/right_agent/monkey_patches/ruby_patch.rb +55 -0
- data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
- data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
- data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
- data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
- data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +60 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
- data/lib/right_agent/multiplexer.rb +102 -0
- data/lib/right_agent/offline_handler.rb +270 -0
- data/lib/right_agent/operation_result.rb +300 -0
- data/lib/right_agent/packets.rb +673 -0
- data/lib/right_agent/payload_formatter.rb +104 -0
- data/lib/right_agent/pending_requests.rb +128 -0
- data/lib/right_agent/pid_file.rb +159 -0
- data/lib/right_agent/platform.rb +770 -0
- data/lib/right_agent/platform/unix/darwin/platform.rb +102 -0
- data/lib/right_agent/platform/unix/linux/platform.rb +305 -0
- data/lib/right_agent/platform/unix/platform.rb +226 -0
- data/lib/right_agent/platform/windows/mingw/platform.rb +447 -0
- data/lib/right_agent/platform/windows/mswin/platform.rb +236 -0
- data/lib/right_agent/platform/windows/platform.rb +1808 -0
- data/lib/right_agent/protocol_version_mixin.rb +69 -0
- data/lib/right_agent/retryable_request.rb +195 -0
- data/lib/right_agent/scripts/agent_controller.rb +543 -0
- data/lib/right_agent/scripts/agent_deployer.rb +400 -0
- data/lib/right_agent/scripts/common_parser.rb +160 -0
- data/lib/right_agent/scripts/log_level_manager.rb +192 -0
- data/lib/right_agent/scripts/stats_manager.rb +268 -0
- data/lib/right_agent/scripts/usage.rb +58 -0
- data/lib/right_agent/secure_identity.rb +92 -0
- data/lib/right_agent/security.rb +32 -0
- data/lib/right_agent/security/cached_certificate_store_proxy.rb +77 -0
- data/lib/right_agent/security/certificate.rb +102 -0
- data/lib/right_agent/security/certificate_cache.rb +89 -0
- data/lib/right_agent/security/distinguished_name.rb +56 -0
- data/lib/right_agent/security/encrypted_document.rb +83 -0
- data/lib/right_agent/security/rsa_key_pair.rb +76 -0
- data/lib/right_agent/security/signature.rb +86 -0
- data/lib/right_agent/security/static_certificate_store.rb +85 -0
- data/lib/right_agent/sender.rb +792 -0
- data/lib/right_agent/serialize.rb +29 -0
- data/lib/right_agent/serialize/message_pack.rb +107 -0
- data/lib/right_agent/serialize/secure_serializer.rb +151 -0
- data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
- data/lib/right_agent/serialize/serializable.rb +151 -0
- data/lib/right_agent/serialize/serializer.rb +159 -0
- data/lib/right_agent/subprocess.rb +38 -0
- data/lib/right_agent/tracer.rb +124 -0
- data/right_agent.gemspec +101 -0
- data/spec/actor_registry_spec.rb +80 -0
- data/spec/actor_spec.rb +162 -0
- data/spec/agent_config_spec.rb +235 -0
- data/spec/agent_identity_spec.rb +78 -0
- data/spec/agent_spec.rb +734 -0
- data/spec/agent_tag_manager_spec.rb +319 -0
- data/spec/clients/api_client_spec.rb +423 -0
- data/spec/clients/auth_client_spec.rb +272 -0
- data/spec/clients/balanced_http_client_spec.rb +576 -0
- data/spec/clients/base_retry_client_spec.rb +635 -0
- data/spec/clients/router_client_spec.rb +594 -0
- data/spec/clients/spec_helper.rb +111 -0
- data/spec/command/agent_manager_commands_spec.rb +51 -0
- data/spec/command/command_io_spec.rb +93 -0
- data/spec/command/command_parser_spec.rb +79 -0
- data/spec/command/command_runner_spec.rb +107 -0
- data/spec/command/command_serializer_spec.rb +51 -0
- data/spec/connectivity_checker_spec.rb +83 -0
- data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
- data/spec/core_payload_types/dev_repository_spec.rb +33 -0
- data/spec/core_payload_types/executable_bundle_spec.rb +67 -0
- data/spec/core_payload_types/login_user_spec.rb +102 -0
- data/spec/core_payload_types/recipe_instantiation_spec.rb +81 -0
- data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
- data/spec/core_payload_types/right_script_instantiation_spec.rb +79 -0
- data/spec/core_payload_types/spec_helper.rb +23 -0
- data/spec/dispatched_cache_spec.rb +136 -0
- data/spec/dispatcher_spec.rb +324 -0
- data/spec/enrollment_result_spec.rb +53 -0
- data/spec/history_spec.rb +246 -0
- data/spec/log_spec.rb +192 -0
- data/spec/monkey_patches/eventmachine_spec.rb +62 -0
- data/spec/multiplexer_spec.rb +48 -0
- data/spec/offline_handler_spec.rb +340 -0
- data/spec/operation_result_spec.rb +208 -0
- data/spec/packets_spec.rb +461 -0
- data/spec/pending_requests_spec.rb +136 -0
- data/spec/platform/spec_helper.rb +216 -0
- data/spec/platform/unix/darwin/platform_spec.rb +181 -0
- data/spec/platform/unix/linux/platform_spec.rb +540 -0
- data/spec/platform/unix/spec_helper.rb +149 -0
- data/spec/platform/windows/mingw/platform_spec.rb +222 -0
- data/spec/platform/windows/mswin/platform_spec.rb +259 -0
- data/spec/platform/windows/spec_helper.rb +720 -0
- data/spec/retryable_request_spec.rb +306 -0
- data/spec/secure_identity_spec.rb +50 -0
- data/spec/security/cached_certificate_store_proxy_spec.rb +62 -0
- data/spec/security/certificate_cache_spec.rb +71 -0
- data/spec/security/certificate_spec.rb +49 -0
- data/spec/security/distinguished_name_spec.rb +46 -0
- data/spec/security/encrypted_document_spec.rb +55 -0
- data/spec/security/rsa_key_pair_spec.rb +55 -0
- data/spec/security/signature_spec.rb +66 -0
- data/spec/security/static_certificate_store_spec.rb +58 -0
- data/spec/sender_spec.rb +1045 -0
- data/spec/serialize/message_pack_spec.rb +131 -0
- data/spec/serialize/secure_serializer_spec.rb +132 -0
- data/spec/serialize/serializable_spec.rb +90 -0
- data/spec/serialize/serializer_spec.rb +197 -0
- data/spec/spec.opts +2 -0
- data/spec/spec.win32.opts +1 -0
- data/spec/spec_helper.rb +130 -0
- data/spec/tracer_spec.rb +114 -0
- metadata +447 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2011 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightScale
|
24
|
+
|
25
|
+
# Build X.509 compliant distinguished names
|
26
|
+
# Distinguished names are used to describe both a certificate issuer and subject
|
27
|
+
class DistinguishedName
|
28
|
+
|
29
|
+
# Initialize distinguished name from hash
|
30
|
+
# e.g.:
|
31
|
+
# { 'C' => 'US',
|
32
|
+
# 'ST' => 'California',
|
33
|
+
# 'L' => 'Santa Barbara',
|
34
|
+
# 'O' => 'RightScale',
|
35
|
+
# 'OU' => 'Certification Services',
|
36
|
+
# 'CN' => 'rightscale.com/emailAddress=cert@rightscale.com' }
|
37
|
+
#
|
38
|
+
def initialize(hash)
|
39
|
+
@value = hash
|
40
|
+
end
|
41
|
+
|
42
|
+
# Conversion to OpenSSL X509 DN
|
43
|
+
def to_x509
|
44
|
+
if @value
|
45
|
+
OpenSSL::X509::Name.new(@value.to_a, OpenSSL::X509::Name::OBJECT_TYPE_TEMPLATE)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Human readable form
|
50
|
+
def to_s
|
51
|
+
'/' + @value.to_a.collect { |p| p.join('=') }.join('/') if @value
|
52
|
+
end
|
53
|
+
|
54
|
+
end # DistinguishedName
|
55
|
+
|
56
|
+
end # RightScale
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2011 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightScale
|
24
|
+
|
25
|
+
# Represents a signed an encrypted document that can be later decrypted using
|
26
|
+
# the right private key and whose signature can be verified using the right
|
27
|
+
# cert.
|
28
|
+
# This class can be used both to encrypt and sign data and to then check the
|
29
|
+
# signature and decrypt an encrypted document.
|
30
|
+
class EncryptedDocument
|
31
|
+
|
32
|
+
# Encrypt and sign data using certificate and key pair
|
33
|
+
#
|
34
|
+
# === Parameters
|
35
|
+
# data(String):: Data to be encrypted
|
36
|
+
# certs(Array|Certificate):: Target recipient certificates used to encrypt data
|
37
|
+
# cipher(Cipher):: Cipher used for encryption, AES 256 CBC by default
|
38
|
+
def initialize(data, certs, cipher = 'AES-256-CBC')
|
39
|
+
cipher = OpenSSL::Cipher::Cipher.new(cipher)
|
40
|
+
certs = [ certs ] unless certs.respond_to?(:collect)
|
41
|
+
raw_certs = certs.collect { |c| c.raw_cert }
|
42
|
+
@pkcs7 = OpenSSL::PKCS7.encrypt(raw_certs, data, cipher, OpenSSL::PKCS7::BINARY)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Initialize from encrypted data
|
46
|
+
#
|
47
|
+
# === Parameters
|
48
|
+
# encrypted_data(String):: Encrypted data
|
49
|
+
#
|
50
|
+
# === Return
|
51
|
+
# doc(EncryptedDocument):: Encrypted document
|
52
|
+
def self.from_data(encrypted_data)
|
53
|
+
doc = EncryptedDocument.allocate
|
54
|
+
doc.instance_variable_set(:@pkcs7, RightScale::PKCS7.new(encrypted_data))
|
55
|
+
doc
|
56
|
+
end
|
57
|
+
|
58
|
+
# Encrypted data in PEM (base64) or DER (binary) format
|
59
|
+
#
|
60
|
+
# === Parameters
|
61
|
+
# format(Symbol):: Encode format: :pem or :der, defaults to :pem
|
62
|
+
#
|
63
|
+
# === Return
|
64
|
+
# (String):: Encrypted data
|
65
|
+
def encrypted_data(format = :pem)
|
66
|
+
format == :pem ? @pkcs7.to_pem : @pkcs7.to_der
|
67
|
+
end
|
68
|
+
|
69
|
+
# Decrypted data
|
70
|
+
#
|
71
|
+
# === Parameters
|
72
|
+
# key(RsaKeyPair):: Key pair used for decryption
|
73
|
+
# cert(Certificate):: Certificate to use for decryption
|
74
|
+
#
|
75
|
+
# === Return
|
76
|
+
# (String):: Decrypted data
|
77
|
+
def decrypted_data(key, cert)
|
78
|
+
@pkcs7.decrypt(key.raw_key, cert.raw_cert)
|
79
|
+
end
|
80
|
+
|
81
|
+
end # EncryptedDocument
|
82
|
+
|
83
|
+
end # RightScale
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2011 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightScale
|
24
|
+
|
25
|
+
# Allows generating RSA key pairs and extracting public key component
|
26
|
+
# Note: Creating a RSA key pair can take a fair amount of time (seconds)
|
27
|
+
class RsaKeyPair
|
28
|
+
|
29
|
+
DEFAULT_LENGTH = 2048
|
30
|
+
|
31
|
+
# Underlying OpenSSL keys
|
32
|
+
attr_reader :raw_key
|
33
|
+
|
34
|
+
# Create new RSA key pair using 'length' bits
|
35
|
+
def initialize(length = DEFAULT_LENGTH)
|
36
|
+
@raw_key = OpenSSL::PKey::RSA.generate(length)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Does key pair include private key?
|
40
|
+
def has_private?
|
41
|
+
raw_key.private?
|
42
|
+
end
|
43
|
+
|
44
|
+
# New RsaKeyPair instance with identical public key but no private key
|
45
|
+
def to_public
|
46
|
+
RsaKeyPair.from_data(raw_key.public_key.to_pem)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Key material in PEM format
|
50
|
+
def data
|
51
|
+
raw_key.to_pem
|
52
|
+
end
|
53
|
+
alias :to_s :data
|
54
|
+
|
55
|
+
# Load key pair previously serialized via 'data'
|
56
|
+
def self.from_data(data)
|
57
|
+
res = RsaKeyPair.allocate
|
58
|
+
res.instance_variable_set(:@raw_key, OpenSSL::PKey::RSA.new(data))
|
59
|
+
res
|
60
|
+
end
|
61
|
+
|
62
|
+
# Load key from file
|
63
|
+
def self.load(file)
|
64
|
+
from_data(File.read(file))
|
65
|
+
end
|
66
|
+
|
67
|
+
# Save key to file in PEM format
|
68
|
+
def save(file)
|
69
|
+
File.open(file, "w") do |f|
|
70
|
+
f.write(@raw_key.to_pem)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end # RsaKeyPair
|
75
|
+
|
76
|
+
end # RightScale
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2011 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
if RUBY_VERSION < '1.8.7'
|
24
|
+
RightScale::PKCS7 = OpenSSL::PKCS7::PKCS7
|
25
|
+
else
|
26
|
+
RightScale::PKCS7 = OpenSSL::PKCS7
|
27
|
+
end
|
28
|
+
|
29
|
+
module RightScale
|
30
|
+
|
31
|
+
# Signature that can be validated against certificates
|
32
|
+
class Signature
|
33
|
+
|
34
|
+
FLAGS = OpenSSL::PKCS7::NOCERTS || OpenSSL::PKCS7::BINARY || OpenSSL::PKCS7::NOATTR || OpenSSL::PKCS7::NOSMIMECAP || OpenSSL::PKCS7::DETACH
|
35
|
+
|
36
|
+
# Create signature using certificate and key pair.
|
37
|
+
#
|
38
|
+
# === Parameters
|
39
|
+
# data(String):: Data to be signed
|
40
|
+
# cert(Certificate):: Certificate used for signature
|
41
|
+
# key(RsaKeyPair):: Key pair used for signature
|
42
|
+
def initialize(data, cert, key)
|
43
|
+
@p7 = OpenSSL::PKCS7.sign(cert.raw_cert, key.raw_key, data, [], FLAGS)
|
44
|
+
@store = OpenSSL::X509::Store.new
|
45
|
+
end
|
46
|
+
|
47
|
+
# Load signature from previously serialized data
|
48
|
+
#
|
49
|
+
# === Parameters
|
50
|
+
# data(String):: Serialized data
|
51
|
+
#
|
52
|
+
# === Return
|
53
|
+
# sig(Signature):: Signature for data
|
54
|
+
def self.from_data(data)
|
55
|
+
sig = Signature.allocate
|
56
|
+
sig.instance_variable_set(:@p7, RightScale::PKCS7.new(data))
|
57
|
+
sig.instance_variable_set(:@store, OpenSSL::X509::Store.new)
|
58
|
+
sig
|
59
|
+
end
|
60
|
+
|
61
|
+
# Check whether signature was created using cert
|
62
|
+
#
|
63
|
+
# === Parameters
|
64
|
+
# cert(Certificate):: Certificate
|
65
|
+
#
|
66
|
+
# === Return
|
67
|
+
# (Boolean):: true if created using given cert, otherwise false
|
68
|
+
def match?(cert)
|
69
|
+
@p7.verify([cert.raw_cert], @store, nil, OpenSSL::PKCS7::NOVERIFY)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Signature data in PEM or DER format
|
73
|
+
#
|
74
|
+
# === Parameters
|
75
|
+
# format(Symbol):: Encode format: :pem or :der, defaults to :pem
|
76
|
+
#
|
77
|
+
# === Return
|
78
|
+
# (String):: Signature
|
79
|
+
def data(format = :pem)
|
80
|
+
format == :pem ? @p7.to_pem : @p7.to_der
|
81
|
+
end
|
82
|
+
alias :to_s :data
|
83
|
+
|
84
|
+
end # Signature
|
85
|
+
|
86
|
+
end # RightScale
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2013 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightScale
|
24
|
+
|
25
|
+
# Simple certificate store that serves a static set of certificates and one key
|
26
|
+
class StaticCertificateStore
|
27
|
+
|
28
|
+
# Initialize store
|
29
|
+
#
|
30
|
+
# === Parameters
|
31
|
+
# receiver_cert(Certificate):: Certificate for decrypting serialized data being received
|
32
|
+
# receiver_key(RsaKeyPair):: Key corresponding to specified cert
|
33
|
+
# signer_certs(Array|Certificate):: Signer certificate(s) used when loading data to
|
34
|
+
# check the digital signature. The signature associated with the serialized data
|
35
|
+
# needs to match with one of the signer certificates for loading to succeed.
|
36
|
+
# target_certs(Array|Certificate):: Target certificate(s) used when serializing
|
37
|
+
# data for encryption. Loading the data can only be done through serializers that
|
38
|
+
# have been initialized with a certificate that's in the target certificates
|
39
|
+
# if encryption is enabled.
|
40
|
+
def initialize(receiver_cert, receiver_key, signer_certs, target_certs)
|
41
|
+
@receiver_cert = receiver_cert
|
42
|
+
@receiver_key = receiver_key
|
43
|
+
signer_certs = [ signer_certs ] unless signer_certs.respond_to?(:each)
|
44
|
+
@signer_certs = signer_certs
|
45
|
+
target_certs = [ target_certs ] unless target_certs.respond_to?(:each)
|
46
|
+
@target_certs = target_certs
|
47
|
+
end
|
48
|
+
|
49
|
+
# Retrieve signer certificates for use in verifying a signature
|
50
|
+
#
|
51
|
+
# === Parameters
|
52
|
+
# id(String):: Serialized identity of signer, ignored
|
53
|
+
#
|
54
|
+
# === Return
|
55
|
+
# (Array|Certificate):: Signer certificates
|
56
|
+
def get_signer(id)
|
57
|
+
@signer_certs
|
58
|
+
end
|
59
|
+
|
60
|
+
# Retrieve certificates of target for encryption
|
61
|
+
#
|
62
|
+
# === Parameters
|
63
|
+
# packet(RightScale::Packet):: Packet containing target identity, ignored
|
64
|
+
#
|
65
|
+
# === Return
|
66
|
+
# (Array|Certificate):: Target certificates
|
67
|
+
def get_target(packet)
|
68
|
+
@target_certs
|
69
|
+
end
|
70
|
+
|
71
|
+
# Retrieve receiver's certificate and key for decryption
|
72
|
+
#
|
73
|
+
# === Parameters
|
74
|
+
# id(String|nil):: Optional identifier of source of data for use
|
75
|
+
# in determining who is the receiver, ignored
|
76
|
+
#
|
77
|
+
# === Return
|
78
|
+
# (Array):: Certificate and key
|
79
|
+
def get_receiver(id)
|
80
|
+
[@receiver_cert, @receiver_key]
|
81
|
+
end
|
82
|
+
|
83
|
+
end # StaticCertificateStore
|
84
|
+
|
85
|
+
end # RightScale
|
@@ -0,0 +1,792 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2013 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightScale
|
24
|
+
|
25
|
+
# TODO require target to be hash or nil only once cleanup string values in RightLink usage
|
26
|
+
# TODO require payload to be hash or nil only once cleanup RightApi and RightLink usage
|
27
|
+
|
28
|
+
# This class allows sending requests to agents via RightNet
|
29
|
+
# It is used by Actor.request which is used by actors that need to send requests to remote agents
|
30
|
+
# If requested, it will queue requests when there are is no RightNet connection
|
31
|
+
class Sender
|
32
|
+
|
33
|
+
include OperationResultHelper
|
34
|
+
|
35
|
+
class SendFailure < RuntimeError; end
|
36
|
+
class TemporarilyOffline < RuntimeError; end
|
37
|
+
|
38
|
+
# Factor used on each retry iteration to achieve exponential backoff
|
39
|
+
RETRY_BACKOFF_FACTOR = 3
|
40
|
+
|
41
|
+
# (PendingRequests) Requests waiting for a response
|
42
|
+
attr_reader :pending_requests
|
43
|
+
|
44
|
+
# (OfflineHandler) Handler for requests when client disconnected
|
45
|
+
attr_reader :offline_handler
|
46
|
+
|
47
|
+
# (ConnectivityChecker) AMQP broker connection checker
|
48
|
+
attr_reader :connectivity_checker
|
49
|
+
|
50
|
+
# (String) Identity of the associated agent
|
51
|
+
attr_reader :identity
|
52
|
+
|
53
|
+
# (Agent) Associated agent
|
54
|
+
attr_reader :agent
|
55
|
+
|
56
|
+
# (Symbol) RightNet communication mode: :http or :amqp
|
57
|
+
attr_reader :mode
|
58
|
+
|
59
|
+
# For direct access to current sender
|
60
|
+
#
|
61
|
+
# === Return
|
62
|
+
# (Sender):: This sender instance if defined, otherwise nil
|
63
|
+
def self.instance
|
64
|
+
@@instance if defined?(@@instance)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Initialize sender
|
68
|
+
#
|
69
|
+
# === Parameters
|
70
|
+
# agent(Agent):: Agent using this sender; uses its identity, client, and following options:
|
71
|
+
# :exception_callback(Proc):: Callback with following parameters that is activated on exception events:
|
72
|
+
# exception(Exception):: Exception
|
73
|
+
# message(Packet):: Message being processed
|
74
|
+
# agent(Agent):: Reference to agent
|
75
|
+
# :offline_queueing(Boolean):: Whether to queue request if client currently disconnected,
|
76
|
+
# also requires agent invocation of initialize_offline_queue and start_offline_queue methods below,
|
77
|
+
# as well as enable_offline_mode and disable_offline_mode as client connection status changes
|
78
|
+
# :ping_interval(Integer):: Minimum number of seconds since last message receipt to ping RightNet
|
79
|
+
# to check connectivity, defaults to 0 meaning do not ping
|
80
|
+
# :restart_callback(Proc):: Callback that is activated on each restart vote with votes being initiated
|
81
|
+
# by offline queue exceeding MAX_QUEUED_REQUESTS or by repeated failures to access RightNet when online
|
82
|
+
# :retry_timeout(Numeric):: Maximum number of seconds to retry request before give up
|
83
|
+
# :retry_interval(Numeric):: Number of seconds before initial request retry, increases exponentially
|
84
|
+
# :time_to_live(Integer):: Number of seconds before a request expires and is to be ignored
|
85
|
+
# by the receiver, 0 means never expire
|
86
|
+
# :async_response(Boolean):: Whether to handle responses asynchronously or to handle them immediately
|
87
|
+
# upon arrival (for use by applications that were written expecting asynchronous AMQP responses)
|
88
|
+
# :secure(Boolean):: true indicates to use Security features of rabbitmq to restrict agents to themselves
|
89
|
+
def initialize(agent)
|
90
|
+
@agent = agent
|
91
|
+
@identity = @agent.identity
|
92
|
+
@options = @agent.options || {}
|
93
|
+
@mode = @agent.mode
|
94
|
+
@request_queue = @agent.request_queue
|
95
|
+
@secure = @options[:secure]
|
96
|
+
@retry_timeout = RightSupport::Stats.nil_if_zero(@options[:retry_timeout])
|
97
|
+
@retry_interval = RightSupport::Stats.nil_if_zero(@options[:retry_interval])
|
98
|
+
@pending_requests = PendingRequests.new
|
99
|
+
@terminating = nil
|
100
|
+
reset_stats
|
101
|
+
@offline_handler = OfflineHandler.new(@options[:restart_callback], @offline_stats)
|
102
|
+
@connectivity_checker = if @mode == :amqp
|
103
|
+
# Only need connectivity checker for AMQP broker since RightHttpClient does its own checking
|
104
|
+
# via periodic session renewal
|
105
|
+
ConnectivityChecker.new(self, @options[:ping_interval] || 0, @ping_stats, @exception_stats)
|
106
|
+
end
|
107
|
+
@@instance = self
|
108
|
+
end
|
109
|
+
|
110
|
+
# Initialize the offline queue
|
111
|
+
# All requests sent prior to running this initialization are queued if offline
|
112
|
+
# queueing is enabled and then are sent once this initialization has run
|
113
|
+
# All requests following this call and prior to calling start_offline_queue
|
114
|
+
# are prepended to the request queue
|
115
|
+
#
|
116
|
+
# === Return
|
117
|
+
# true:: Always return true
|
118
|
+
def initialize_offline_queue
|
119
|
+
@offline_handler.init if @options[:offline_queueing]
|
120
|
+
end
|
121
|
+
|
122
|
+
# Switch offline queueing to online mode and flush all buffered messages
|
123
|
+
#
|
124
|
+
# === Return
|
125
|
+
# true:: Always return true
|
126
|
+
def start_offline_queue
|
127
|
+
@offline_handler.start if @options[:offline_queueing]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Switch to offline mode
|
131
|
+
# In this mode requests are queued in memory rather than sent
|
132
|
+
# Idempotent
|
133
|
+
#
|
134
|
+
# === Return
|
135
|
+
# true:: Always return true
|
136
|
+
def enable_offline_mode
|
137
|
+
@offline_handler.enable if @options[:offline_queueing]
|
138
|
+
end
|
139
|
+
|
140
|
+
# Switch back to sending requests after in memory queue gets flushed
|
141
|
+
# Idempotent
|
142
|
+
#
|
143
|
+
# === Return
|
144
|
+
# true:: Always return true
|
145
|
+
def disable_offline_mode
|
146
|
+
@offline_handler.disable if @options[:offline_queueing]
|
147
|
+
end
|
148
|
+
|
149
|
+
# Determine whether currently connected to RightNet via client
|
150
|
+
#
|
151
|
+
# === Return
|
152
|
+
# (Boolean):: true if offline or if client disconnected, otherwise false
|
153
|
+
def connected?
|
154
|
+
@mode == :http ? @agent.client.connected? : @agent.client.connected.size == 0
|
155
|
+
end
|
156
|
+
|
157
|
+
# Update the time this agent last received a request or response message
|
158
|
+
# Also forward this message receipt notification to any callbacks that have registered
|
159
|
+
#
|
160
|
+
# === Block
|
161
|
+
# Optional block without parameters that is activated when a message is received
|
162
|
+
#
|
163
|
+
# === Return
|
164
|
+
# true:: Always return true
|
165
|
+
def message_received(&callback)
|
166
|
+
@connectivity_checker.message_received(&callback) if @connectivity_checker
|
167
|
+
end
|
168
|
+
|
169
|
+
# Send a request to a single target or multiple targets with no response expected other
|
170
|
+
# than routing failures
|
171
|
+
# Persist the request en route to reduce the chance of it being lost at the expense of some
|
172
|
+
# additional network overhead
|
173
|
+
# Enqueue the request if the target is not currently available
|
174
|
+
# Never automatically retry the request if there is the possibility of it being duplicated
|
175
|
+
# Set time-to-live to be forever
|
176
|
+
#
|
177
|
+
# === Parameters
|
178
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
179
|
+
# payload(Object):: Data to be sent with marshalling en route
|
180
|
+
# target(Hash|NilClass) Target for request, which may be a specific agent (using :agent_id),
|
181
|
+
# potentially multiple targets (using :tags, :scope, :selector), or nil to route solely
|
182
|
+
# using type:
|
183
|
+
# :agent_id(String):: serialized identity of specific target
|
184
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
185
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
186
|
+
# :account(Integer):: Restrict to agents with this account id
|
187
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
188
|
+
# ones with no shard id
|
189
|
+
# :selector(Symbol):: Which of the matched targets to be selected, either :any or :all,
|
190
|
+
# defaults to :any
|
191
|
+
# token(String|NilClass):: Token uniquely identifying request; defaults to random generated
|
192
|
+
#
|
193
|
+
# === Block
|
194
|
+
# Optional block used to process routing responses asynchronously with the following parameter:
|
195
|
+
# result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR,
|
196
|
+
# with an initial SUCCESS response containing the targets to which the request was sent
|
197
|
+
# and any additional responses indicating any failures to actually route the request
|
198
|
+
# to those targets, use RightScale::OperationResult.from_results to decode
|
199
|
+
#
|
200
|
+
# === Return
|
201
|
+
# true:: Always return true
|
202
|
+
#
|
203
|
+
# === Raise
|
204
|
+
# ArgumentError:: If target invalid
|
205
|
+
# SendFailure:: If sending of request failed unexpectedly
|
206
|
+
# TemporarilyOffline:: If cannot send request because RightNet client currently disconnected
|
207
|
+
# and offline queueing is disabled
|
208
|
+
def send_push(type, payload = nil, target = nil, token = nil, &callback)
|
209
|
+
build_and_send_packet(:send_push, type, payload, target, token, callback)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Send a request to a single target with a response expected
|
213
|
+
# Automatically retry the request if a response is not received in a reasonable amount of time
|
214
|
+
# or if there is a non-delivery response indicating the target is not currently available
|
215
|
+
# Timeout the request if a response is not received in time, typically configured to 2 minutes
|
216
|
+
# Because of retries there is the possibility of duplicated requests, and these are detected and
|
217
|
+
# discarded automatically for non-idempotent actions
|
218
|
+
# Allow the request to expire per the agent's configured time-to-live, typically 1 minute
|
219
|
+
# Note that receiving a response does not guarantee that the request activity has actually
|
220
|
+
# completed since the request processing may involve other asynchronous requests
|
221
|
+
#
|
222
|
+
# === Parameters
|
223
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
224
|
+
# payload(Object):: Data to be sent with marshalling en route
|
225
|
+
# target(Hash|NilClass) Target for request, which may be a specific agent (using :agent_id),
|
226
|
+
# one chosen randomly from potentially multiple targets (using :tags, :scope), or nil to
|
227
|
+
# route solely using type:
|
228
|
+
# :agent_id(String):: serialized identity of specific target
|
229
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
230
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
231
|
+
# :account(Integer):: Restrict to agents with this account id
|
232
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
233
|
+
# ones with no shard id
|
234
|
+
# token(String|NilClass):: Token uniquely identifying request; defaults to random generated
|
235
|
+
#
|
236
|
+
# === Block
|
237
|
+
# Required block used to process response asynchronously with the following parameter:
|
238
|
+
# result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR,
|
239
|
+
# use RightScale::OperationResult.from_results to decode
|
240
|
+
#
|
241
|
+
# === Return
|
242
|
+
# true:: Always return true
|
243
|
+
#
|
244
|
+
# === Raise
|
245
|
+
# ArgumentError:: If target invalid or block missing
|
246
|
+
def send_request(type, payload = nil, target = nil, token = nil, &callback)
|
247
|
+
raise ArgumentError, "Missing block for response callback" unless callback
|
248
|
+
build_and_send_packet(:send_request, type, payload, target, token, callback)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Build and send packet
|
252
|
+
#
|
253
|
+
# === Parameters
|
254
|
+
# kind(Symbol):: Kind of request: :send_push or :send_request
|
255
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
256
|
+
# payload(Object):: Data to be sent with marshalling en route
|
257
|
+
# target(Hash|NilClass):: Identity of specific target as string, or hash for selecting targets
|
258
|
+
# :agent_id(String):: Identity of specific target
|
259
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
260
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
261
|
+
# :account(Integer):: Restrict to agents with this account id
|
262
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
263
|
+
# ones with no shard id
|
264
|
+
# :selector(Symbol):: Which of the matched targets to be selected: :any or :all
|
265
|
+
# token(String|NilClass):: Token uniquely identifying request; defaults to random generated
|
266
|
+
# callback(Proc|nil):: Block used to process routing response
|
267
|
+
#
|
268
|
+
# === Return
|
269
|
+
# true:: Always return true
|
270
|
+
#
|
271
|
+
# === Raise
|
272
|
+
# ArgumentError:: If target invalid
|
273
|
+
def build_and_send_packet(kind, type, payload, target, token, callback)
|
274
|
+
if (packet = build_packet(kind, type, payload, target, token, callback))
|
275
|
+
action = type.split('/').last
|
276
|
+
received_at = @request_stats.update(action, packet.token)
|
277
|
+
@request_kind_stats.update((packet.selector == :all ? "fanout" : kind.to_s)[5..-1])
|
278
|
+
send("#{@mode}_send", kind, target, packet, received_at, callback)
|
279
|
+
end
|
280
|
+
true
|
281
|
+
end
|
282
|
+
|
283
|
+
# Build packet or queue it if offline
|
284
|
+
#
|
285
|
+
# === Parameters
|
286
|
+
# kind(Symbol):: Kind of request: :send_push or :send_request
|
287
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
288
|
+
# payload(Object):: Data to be sent with marshalling en route
|
289
|
+
# target(Hash|NilClass):: Identity of specific target as string, or hash for selecting targets
|
290
|
+
# :agent_id(String):: Identity of specific target
|
291
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
292
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
293
|
+
# :account(Integer):: Restrict to agents with this account id
|
294
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
295
|
+
# ones with no shard id
|
296
|
+
# :selector(Symbol):: Which of the matched targets to be selected: :any or :all
|
297
|
+
# token(String|NilClass):: Token uniquely identifying request; defaults to random generated
|
298
|
+
# callback(Boolean):: Whether this request has an associated response callback
|
299
|
+
#
|
300
|
+
# === Return
|
301
|
+
# (Push|Request|NilClass):: Packet created, or nil if queued instead
|
302
|
+
#
|
303
|
+
# === Raise
|
304
|
+
# ArgumentError:: If target is invalid
|
305
|
+
def build_packet(kind, type, payload, target, token, callback = false)
|
306
|
+
validate_target(target, kind == :send_push)
|
307
|
+
if queueing?
|
308
|
+
@offline_handler.queue_request(kind, type, payload, target, callback)
|
309
|
+
nil
|
310
|
+
else
|
311
|
+
if kind == :send_push
|
312
|
+
packet = Push.new(type, payload)
|
313
|
+
packet.selector = target[:selector] || :any if target.is_a?(Hash)
|
314
|
+
packet.persistent = true
|
315
|
+
packet.confirm = true if callback
|
316
|
+
else
|
317
|
+
packet = Request.new(type, payload)
|
318
|
+
ttl = @options[:time_to_live]
|
319
|
+
packet.expires_at = Time.now.to_i + ttl if ttl && ttl != 0
|
320
|
+
packet.selector = :any
|
321
|
+
end
|
322
|
+
packet.from = @identity
|
323
|
+
packet.token = token || RightSupport::Data::UUID.generate
|
324
|
+
if target.is_a?(Hash)
|
325
|
+
if (agent_id = target[:agent_id])
|
326
|
+
packet.target = agent_id
|
327
|
+
else
|
328
|
+
packet.tags = target[:tags] || []
|
329
|
+
packet.scope = target[:scope]
|
330
|
+
end
|
331
|
+
else
|
332
|
+
packet.target = target
|
333
|
+
end
|
334
|
+
packet
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
# Handle response to a request
|
339
|
+
#
|
340
|
+
# === Parameters
|
341
|
+
# response(Result):: Packet received as result of request
|
342
|
+
#
|
343
|
+
# === Return
|
344
|
+
# true:: Always return true
|
345
|
+
def handle_response(response)
|
346
|
+
if response.is_a?(Result)
|
347
|
+
token = response.token
|
348
|
+
if (result = OperationResult.from_results(response))
|
349
|
+
if result.non_delivery?
|
350
|
+
@non_delivery_stats.update(result.content.nil? ? "nil" : result.content.inspect)
|
351
|
+
elsif result.error?
|
352
|
+
@result_error_stats.update(result.content.nil? ? "nil" : result.content.inspect)
|
353
|
+
end
|
354
|
+
@result_stats.update(result.status)
|
355
|
+
else
|
356
|
+
@result_stats.update(response.results.nil? ? "nil" : response.results)
|
357
|
+
end
|
358
|
+
|
359
|
+
if (pending_request = @pending_requests[token])
|
360
|
+
if result && result.non_delivery? && pending_request.kind == :send_request
|
361
|
+
if result.content == OperationResult::TARGET_NOT_CONNECTED
|
362
|
+
# Log and temporarily ignore so that timeout retry mechanism continues, but save reason for use below if timeout
|
363
|
+
# Leave purging of associated request until final response, i.e., success response or retry timeout
|
364
|
+
if (parent_token = pending_request.retry_parent_token)
|
365
|
+
@pending_requests[parent_token].non_delivery = result.content
|
366
|
+
else
|
367
|
+
pending_request.non_delivery = result.content
|
368
|
+
end
|
369
|
+
Log.info("Non-delivery of <#{token}> because #{result.content}")
|
370
|
+
elsif result.content == OperationResult::RETRY_TIMEOUT && pending_request.non_delivery
|
371
|
+
# Request timed out but due to another non-delivery reason, so use that reason since more germane
|
372
|
+
response.results = OperationResult.non_delivery(pending_request.non_delivery)
|
373
|
+
deliver_response(response, pending_request)
|
374
|
+
else
|
375
|
+
deliver_response(response, pending_request)
|
376
|
+
end
|
377
|
+
else
|
378
|
+
deliver_response(response, pending_request)
|
379
|
+
end
|
380
|
+
elsif result && result.non_delivery?
|
381
|
+
Log.info("Non-delivery of <#{token}> because #{result.content}")
|
382
|
+
else
|
383
|
+
Log.debug("No pending request for response #{response.to_s([])}")
|
384
|
+
end
|
385
|
+
end
|
386
|
+
true
|
387
|
+
end
|
388
|
+
|
389
|
+
# Take any actions necessary to quiesce client interaction in preparation
|
390
|
+
# for agent termination but allow message receipt to continue
|
391
|
+
#
|
392
|
+
# === Return
|
393
|
+
# (Array):: Number of pending non-push requests and age of youngest request
|
394
|
+
def terminate
|
395
|
+
if @offline_handler
|
396
|
+
@offline_handler.terminate
|
397
|
+
@connectivity_checker.terminate if @connectivity_checker
|
398
|
+
pending = @pending_requests.kind(:send_request)
|
399
|
+
[pending.size, pending.youngest_age]
|
400
|
+
else
|
401
|
+
[0, nil]
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# Create displayable dump of unfinished non-push request information
|
406
|
+
# Truncate list if there are more than 50 requests
|
407
|
+
#
|
408
|
+
# === Return
|
409
|
+
# info(Array(String)):: Receive time and token for each request in descending time order
|
410
|
+
def dump_requests
|
411
|
+
info = []
|
412
|
+
if @pending_requests
|
413
|
+
@pending_requests.kind(:send_request).each do |token, request|
|
414
|
+
info << "#{request.receive_time.localtime} <#{token}>"
|
415
|
+
end
|
416
|
+
info.sort!.reverse!
|
417
|
+
info = info[0..49] + ["..."] if info.size > 50
|
418
|
+
end
|
419
|
+
info
|
420
|
+
end
|
421
|
+
|
422
|
+
# Get sender statistics
|
423
|
+
#
|
424
|
+
# === Parameters
|
425
|
+
# reset(Boolean):: Whether to reset the statistics after getting the current ones
|
426
|
+
#
|
427
|
+
# === Return
|
428
|
+
# stats(Hash):: Current statistics:
|
429
|
+
# "exceptions"(Hash|nil):: Exceptions raised per category, or nil if none
|
430
|
+
# "total"(Integer):: Total exceptions for this category
|
431
|
+
# "recent"(Array):: Most recent as a hash of "count", "type", "message", "when", and "where"
|
432
|
+
# "non-deliveries"(Hash|nil):: Non-delivery activity stats with keys "total", "percent", "last",
|
433
|
+
# and 'rate' with percentage breakdown per reason, or nil if none
|
434
|
+
# "offlines"(Hash|nil):: Offline activity stats with keys "total", "last", and "duration",
|
435
|
+
# or nil if none
|
436
|
+
# "pings"(Hash|nil):: Request activity stats with keys "total", "percent", "last", and "rate"
|
437
|
+
# with percentage breakdown for "success" vs. "timeout", or nil if none
|
438
|
+
# "request kinds"(Hash|nil):: Request kind activity stats with keys "total", "percent", and "last"
|
439
|
+
# with percentage breakdown per kind, or nil if none
|
440
|
+
# "requests"(Hash|nil):: Request activity stats with keys "total", "percent", "last", and "rate"
|
441
|
+
# with percentage breakdown per request type, or nil if none
|
442
|
+
# "requests pending"(Hash|nil):: Number of requests waiting for response and age of oldest,
|
443
|
+
# or nil if none
|
444
|
+
# "response time"(Float):: Average number of seconds to respond to a request recently
|
445
|
+
# "result errors"(Hash|nil):: Error result activity stats with keys "total", "percent", "last",
|
446
|
+
# and 'rate' with percentage breakdown per error, or nil if none
|
447
|
+
# "results"(Hash|nil):: Results activity stats with keys "total", "percent", "last", and "rate"
|
448
|
+
# with percentage breakdown per operation result type, or nil if none
|
449
|
+
# "retries"(Hash|nil):: Retry activity stats with keys "total", "percent", "last", and "rate"
|
450
|
+
# with percentage breakdown per request type, or nil if none
|
451
|
+
# "send failure"(Hash|nil):: Send failure activity stats with keys "total", "percent", "last", and "rate"
|
452
|
+
# with percentage breakdown per failure type, or nil if none
|
453
|
+
def stats(reset = false)
|
454
|
+
stats = {}
|
455
|
+
if @agent
|
456
|
+
offlines = @offline_stats.all
|
457
|
+
offlines.merge!("duration" => @offline_stats.avg_duration) if offlines
|
458
|
+
if @pending_requests.size > 0
|
459
|
+
pending = {}
|
460
|
+
pending["pushes"] = @pending_requests.kind(:send_push).size
|
461
|
+
requests = @pending_requests.kind(:send_request)
|
462
|
+
if (pending["requests"] = requests.size) > 0
|
463
|
+
pending["oldest age"] = requests.oldest_age
|
464
|
+
end
|
465
|
+
end
|
466
|
+
stats = {
|
467
|
+
"exceptions" => @exception_stats.stats,
|
468
|
+
"non-deliveries" => @non_delivery_stats.all,
|
469
|
+
"offlines" => offlines,
|
470
|
+
"pings" => @ping_stats.all,
|
471
|
+
"request kinds" => @request_kind_stats.all,
|
472
|
+
"requests" => @request_stats.all,
|
473
|
+
"requests pending" => pending,
|
474
|
+
"response time" => @request_stats.avg_duration,
|
475
|
+
"result errors" => @result_error_stats.all,
|
476
|
+
"results" => @result_stats.all,
|
477
|
+
"retries" => @retry_stats.all,
|
478
|
+
"send failures" => @send_failure_stats.all
|
479
|
+
}
|
480
|
+
reset_stats if reset
|
481
|
+
end
|
482
|
+
stats
|
483
|
+
end
|
484
|
+
|
485
|
+
protected
|
486
|
+
|
487
|
+
# Reset dispatch statistics
|
488
|
+
#
|
489
|
+
# === Return
|
490
|
+
# true:: Always return true
|
491
|
+
def reset_stats
|
492
|
+
@ping_stats = RightSupport::Stats::Activity.new
|
493
|
+
@retry_stats = RightSupport::Stats::Activity.new
|
494
|
+
@request_stats = RightSupport::Stats::Activity.new
|
495
|
+
@result_stats = RightSupport::Stats::Activity.new
|
496
|
+
@result_error_stats = RightSupport::Stats::Activity.new
|
497
|
+
@non_delivery_stats = RightSupport::Stats::Activity.new
|
498
|
+
@offline_stats = RightSupport::Stats::Activity.new(measure_rate = false)
|
499
|
+
@request_kind_stats = RightSupport::Stats::Activity.new(measure_rate = false)
|
500
|
+
@send_failure_stats = RightSupport::Stats::Activity.new
|
501
|
+
@exception_stats = RightSupport::Stats::Exceptions.new(@agent, @options[:exception_callback])
|
502
|
+
true
|
503
|
+
end
|
504
|
+
|
505
|
+
# Validate target argument of send per the semantics of each kind of send:
|
506
|
+
# - The target is either a specific target name, a non-empty hash, or nil
|
507
|
+
# - A specific target name must be a string (or use :agent_id key)
|
508
|
+
# - A non-empty hash target
|
509
|
+
# - may have keys in symbol or string format
|
510
|
+
# - may contain an :agent_id to select a specific target, but then cannot
|
511
|
+
# have any other keys
|
512
|
+
# - may be allowed to contain a :selector key with value :any or :all,
|
513
|
+
# depending on the kind of send
|
514
|
+
# - may contain a :scope key with a hash value with keys :account and/or :shard
|
515
|
+
# - may contain a :tags key with an array value
|
516
|
+
#
|
517
|
+
# === Parameters
|
518
|
+
# target(String|Hash):: Identity of specific target, or hash for selecting targets;
|
519
|
+
# returned with all hash keys converted to symbols
|
520
|
+
# allow_selector(Boolean):: Whether to allow :selector
|
521
|
+
#
|
522
|
+
# === Return
|
523
|
+
# true:: Always return true
|
524
|
+
#
|
525
|
+
# === Raise
|
526
|
+
# ArgumentError:: If target is invalid
|
527
|
+
def validate_target(target, allow_selector)
|
528
|
+
choices = ":agent_id OR " + (allow_selector ? ":selector, " : "") + ":tags and/or :scope"
|
529
|
+
if target.is_a?(Hash)
|
530
|
+
t = SerializationHelper.symbolize_keys(target)
|
531
|
+
|
532
|
+
if (agent_id = t[:agent_id])
|
533
|
+
raise ArgumentError, "Invalid target: #{target.inspect}" if t.size > 1
|
534
|
+
end
|
535
|
+
|
536
|
+
if (selector = t[:selector])
|
537
|
+
if allow_selector
|
538
|
+
selector = selector.to_sym
|
539
|
+
unless [:any, :all].include?(selector)
|
540
|
+
raise ArgumentError, "Invalid target selector (#{t[:selector].inspect}), choices are :any and :all"
|
541
|
+
end
|
542
|
+
t[:selector] = selector
|
543
|
+
else
|
544
|
+
raise ArgumentError, "Invalid target hash (#{target.inspect}), choices are #{choices}"
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
if (scope = t[:scope])
|
549
|
+
if scope.is_a?(Hash)
|
550
|
+
scope = SerializationHelper.symbolize_keys(scope)
|
551
|
+
unless (scope[:account] || scope[:shard]) && (scope.keys - [:account, :shard]).empty?
|
552
|
+
raise ArgumentError, "Invalid target scope (#{t[:scope].inspect}), choices are :account and :shard"
|
553
|
+
end
|
554
|
+
t[:scope] = scope
|
555
|
+
else
|
556
|
+
raise ArgumentError, "Invalid target scope (#{t[:scope].inspect}), must be a hash of :account and/or :shard"
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
if (tags = t[:tags]) && !tags.is_a?(Array)
|
561
|
+
raise ArgumentError, "Invalid target tags (#{t[:tags].inspect}), must be an array"
|
562
|
+
end
|
563
|
+
|
564
|
+
unless (agent_id || selector || scope || tags) && (t.keys - [:agent_id, :selector, :scope, :tags]).empty?
|
565
|
+
raise ArgumentError, "Invalid target hash (#{target.inspect}), choices are #{choices}"
|
566
|
+
end
|
567
|
+
target = t
|
568
|
+
elsif !target.nil? && !target.is_a?(String)
|
569
|
+
raise ArgumentError, "Invalid target (#{target.inspect}), choices are specific target name or a hash of #{choices}"
|
570
|
+
end
|
571
|
+
true
|
572
|
+
end
|
573
|
+
|
574
|
+
# Send request via HTTP and then immediately handle response
|
575
|
+
#
|
576
|
+
# === Parameters
|
577
|
+
# kind(Symbol):: Kind of request: :send_push or :send_request
|
578
|
+
# target(Hash|String|nil):: Target for request
|
579
|
+
# packet(Push|Request):: Request packet to send
|
580
|
+
# received_at(Time):: Time when request received
|
581
|
+
# callback(Proc|nil):: Block used to process response
|
582
|
+
#
|
583
|
+
# === Return
|
584
|
+
# true:: Always return true
|
585
|
+
def http_send(kind, target, packet, received_at, callback)
|
586
|
+
begin
|
587
|
+
method = packet.class.name.split("::").last.downcase
|
588
|
+
result = success_result(@agent.client.send(method, packet.type, packet.payload, target, packet.token))
|
589
|
+
rescue Exceptions::Unauthorized => e
|
590
|
+
result = error_result(e.message)
|
591
|
+
rescue Exceptions::ConnectivityFailure => e
|
592
|
+
if queueing?
|
593
|
+
@offline_handler.queue_request(kind, packet.type, packet.payload, target, callback)
|
594
|
+
result = nil
|
595
|
+
else
|
596
|
+
result = retry_result(e.message)
|
597
|
+
end
|
598
|
+
rescue Exceptions::RetryableError => e
|
599
|
+
result = retry_result(e.message)
|
600
|
+
rescue Exceptions::InternalServerError => e
|
601
|
+
result = error_result("#{e.server} internal error")
|
602
|
+
rescue Exceptions::Terminating => e
|
603
|
+
result = nil
|
604
|
+
rescue StandardError => e
|
605
|
+
# These errors are either unexpected errors or RestClient errors with an http_body
|
606
|
+
# giving details about the error that are conveyed in the error_result
|
607
|
+
if e.respond_to?(:http_body)
|
608
|
+
# No need to log here since any HTTP request errors have already been logged
|
609
|
+
result = error_result(e.inspect)
|
610
|
+
else
|
611
|
+
agent_type = AgentIdentity.parse(@identity).agent_type
|
612
|
+
Log.error("Failed to send #{packet.trace} #{packet.type}", e, :trace)
|
613
|
+
@exception_stats.track("request", e)
|
614
|
+
result = error_result("#{agent_type.capitalize} agent internal error")
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
if result && packet.is_a?(Request)
|
619
|
+
result = Result.new(packet.token, @identity, result, from = packet.target)
|
620
|
+
result.received_at = received_at.to_f
|
621
|
+
@pending_requests[packet.token] = PendingRequest.new(kind, received_at, callback) if callback
|
622
|
+
if @options[:async_response]
|
623
|
+
EM.next_tick { handle_response(result) }
|
624
|
+
else
|
625
|
+
handle_response(result)
|
626
|
+
end
|
627
|
+
end
|
628
|
+
true
|
629
|
+
end
|
630
|
+
|
631
|
+
# Send request via AMQP
|
632
|
+
# If lack connectivity and queueing enabled, queue request
|
633
|
+
#
|
634
|
+
# === Parameters
|
635
|
+
# kind(Symbol):: Kind of request: :send_push or :send_request
|
636
|
+
# target(Hash|String|nil):: Target for request
|
637
|
+
# received_at(Time):: Time when request received
|
638
|
+
# packet(Push|Request):: Request packet to send
|
639
|
+
# callback(Proc|nil):: Block used to process response
|
640
|
+
#
|
641
|
+
# === Return
|
642
|
+
# true:: Always return true
|
643
|
+
def amqp_send(kind, target, packet, received_at, callback)
|
644
|
+
begin
|
645
|
+
@pending_requests[packet.token] = PendingRequest.new(kind, received_at, callback) if callback
|
646
|
+
if packet.class == Request
|
647
|
+
amqp_send_retry(packet, packet.token)
|
648
|
+
else
|
649
|
+
amqp_send_once(packet)
|
650
|
+
end
|
651
|
+
rescue TemporarilyOffline => e
|
652
|
+
if queueing?
|
653
|
+
# Queue request until come back online
|
654
|
+
@offline_handler.queue_request(kind, packet.type, packet.payload, target, callback)
|
655
|
+
@pending_requests.delete(packet.token) if callback
|
656
|
+
else
|
657
|
+
# Send retry response so that requester, e.g., RetryableRequest, can retry
|
658
|
+
result = OperationResult.retry("lost RightNet connectivity")
|
659
|
+
handle_response(Result.new(packet.token, @identity, result, @identity))
|
660
|
+
end
|
661
|
+
rescue SendFailure => e
|
662
|
+
# Send non-delivery response so that requester, e.g., RetryableRequest, can retry
|
663
|
+
result = OperationResult.non_delivery("send failed unexpectedly")
|
664
|
+
handle_response(Result.new(packet.token, @identity, result, @identity))
|
665
|
+
end
|
666
|
+
true
|
667
|
+
end
|
668
|
+
|
669
|
+
# Send request via AMQP without retrying
|
670
|
+
# Use mandatory flag to request return of message if it cannot be delivered
|
671
|
+
#
|
672
|
+
# === Parameters
|
673
|
+
# packet(Push|Request):: Request packet to send
|
674
|
+
# ids(Array|nil):: Identity of specific brokers to choose from, or nil if any okay
|
675
|
+
#
|
676
|
+
# === Return
|
677
|
+
# (Array):: Identity of brokers to which request was published
|
678
|
+
#
|
679
|
+
# === Raise
|
680
|
+
# SendFailure:: If sending of request failed unexpectedly
|
681
|
+
# TemporarilyOffline:: If cannot send request because RightNet client currently disconnected
|
682
|
+
# and offline queueing is disabled
|
683
|
+
def amqp_send_once(packet, ids = nil)
|
684
|
+
name =
|
685
|
+
exchange = {:type => :fanout, :name => @request_queue, :options => {:durable => true, :no_declare => @secure}}
|
686
|
+
@agent.client.publish(exchange, packet, :persistent => packet.persistent, :mandatory => true,
|
687
|
+
:log_filter => [:tags, :target, :tries, :persistent], :brokers => ids)
|
688
|
+
rescue RightAMQP::HABrokerClient::NoConnectedBrokers => e
|
689
|
+
msg = "Failed to publish request #{packet.trace} #{packet.type}"
|
690
|
+
Log.error(msg, e)
|
691
|
+
@send_failure_stats.update("NoConnectedBrokers")
|
692
|
+
raise TemporarilyOffline.new(msg + " (#{e.class}: #{e.message})")
|
693
|
+
rescue Exception => e
|
694
|
+
msg = "Failed to publish request #{packet.trace} #{packet.type}"
|
695
|
+
Log.error(msg, e, :trace)
|
696
|
+
@send_failure_stats.update(e.class.name)
|
697
|
+
@exception_stats.track("publish", e, packet)
|
698
|
+
raise SendFailure.new(msg + " (#{e.class}: #{e.message})")
|
699
|
+
end
|
700
|
+
|
701
|
+
# Send request via AMQP with one or more retries if do not receive a response in time
|
702
|
+
# Send timeout result if reach configured retry timeout limit
|
703
|
+
# Use exponential backoff with RETRY_BACKOFF_FACTOR for retry spacing
|
704
|
+
# Adjust retry interval by average response time to avoid adding to system load
|
705
|
+
# when system gets slow
|
706
|
+
# Rotate through brokers on retries
|
707
|
+
# Check connectivity after first retry timeout
|
708
|
+
#
|
709
|
+
# === Parameters
|
710
|
+
# packet(Request):: Request packet to send
|
711
|
+
# parent_token(String):: Token for original request
|
712
|
+
# count(Integer):: Number of retries so far
|
713
|
+
# multiplier(Integer):: Multiplier for retry interval for exponential backoff
|
714
|
+
# elapsed(Integer):: Elapsed time in seconds since this request was first attempted
|
715
|
+
# broker_ids(Array):: Identity of brokers to be used in priority order
|
716
|
+
#
|
717
|
+
# === Return
|
718
|
+
# true:: Always return true
|
719
|
+
def amqp_send_retry(packet, parent_token, count = 0, multiplier = 1, elapsed = 0, broker_ids = nil)
|
720
|
+
check_broker_ids = amqp_send_once(packet, broker_ids)
|
721
|
+
|
722
|
+
if @retry_interval && @retry_timeout && parent_token
|
723
|
+
interval = [(@retry_interval * multiplier) + (@request_stats.avg_duration || 0), @retry_timeout - elapsed].min
|
724
|
+
EM.add_timer(interval) do
|
725
|
+
begin
|
726
|
+
if @pending_requests[parent_token]
|
727
|
+
count += 1
|
728
|
+
elapsed += interval
|
729
|
+
if elapsed < @retry_timeout
|
730
|
+
packet.tries << packet.token
|
731
|
+
packet.token = RightSupport::Data::UUID.generate
|
732
|
+
@pending_requests[parent_token].retry_parent_token = parent_token if count == 1
|
733
|
+
@pending_requests[packet.token] = @pending_requests[parent_token]
|
734
|
+
broker_ids ||= @agent.client.all
|
735
|
+
amqp_send_retry(packet, parent_token, count, multiplier * RETRY_BACKOFF_FACTOR, elapsed,
|
736
|
+
broker_ids.push(broker_ids.shift))
|
737
|
+
@retry_stats.update(packet.type.split('/').last)
|
738
|
+
else
|
739
|
+
Log.warning("RE-SEND TIMEOUT after #{elapsed.to_i} seconds for #{packet.trace} #{packet.type}")
|
740
|
+
result = OperationResult.non_delivery(OperationResult::RETRY_TIMEOUT)
|
741
|
+
@non_delivery_stats.update(result.content)
|
742
|
+
handle_response(Result.new(packet.token, @identity, result, @identity))
|
743
|
+
end
|
744
|
+
@connectivity_checker.check(check_broker_ids.first) if check_broker_ids.any? && count == 1
|
745
|
+
end
|
746
|
+
rescue TemporarilyOffline => e
|
747
|
+
Log.error("Failed retry for #{packet.trace} #{packet.type} because temporarily offline")
|
748
|
+
rescue SendFailure => e
|
749
|
+
Log.error("Failed retry for #{packet.trace} #{packet.type} because of send failure")
|
750
|
+
rescue Exception => e
|
751
|
+
# Not sending a response here because something more basic is broken in the retry
|
752
|
+
# mechanism and don't want an error response to preempt a delayed actual response
|
753
|
+
Log.error("Failed retry for #{packet.trace} #{packet.type} without responding", e, :trace)
|
754
|
+
@exception_stats.track("retry", e, packet)
|
755
|
+
end
|
756
|
+
end
|
757
|
+
end
|
758
|
+
true
|
759
|
+
end
|
760
|
+
|
761
|
+
# Deliver the response and remove associated non-push requests from pending
|
762
|
+
# including all associated retry requests
|
763
|
+
#
|
764
|
+
# === Parameters
|
765
|
+
# response(Result):: Packet received as result of request
|
766
|
+
# pending_request(Hash):: Associated pending request
|
767
|
+
#
|
768
|
+
# === Return
|
769
|
+
# true:: Always return true
|
770
|
+
def deliver_response(response, pending_request)
|
771
|
+
@request_stats.finish(pending_request.receive_time, response.token)
|
772
|
+
|
773
|
+
@pending_requests.delete(response.token) if pending_request.kind == :send_request
|
774
|
+
if (parent_token = pending_request.retry_parent_token)
|
775
|
+
@pending_requests.reject! { |k, v| k == parent_token || v.retry_parent_token == parent_token }
|
776
|
+
end
|
777
|
+
|
778
|
+
pending_request.response_handler.call(response) if pending_request.response_handler
|
779
|
+
true
|
780
|
+
end
|
781
|
+
|
782
|
+
# Determine whether currently queueing requests because offline
|
783
|
+
#
|
784
|
+
# === Return
|
785
|
+
# (Boolean):: true if queueing, otherwise false
|
786
|
+
def queueing?
|
787
|
+
@options[:offline_queueing] && @offline_handler.queueing?
|
788
|
+
end
|
789
|
+
|
790
|
+
end # Sender
|
791
|
+
|
792
|
+
end # RightScale
|