right_agent 0.5.1
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 +78 -0
- data/Rakefile +86 -0
- data/lib/right_agent.rb +66 -0
- data/lib/right_agent/actor.rb +163 -0
- data/lib/right_agent/actor_registry.rb +76 -0
- data/lib/right_agent/actors/agent_manager.rb +189 -0
- data/lib/right_agent/agent.rb +735 -0
- data/lib/right_agent/agent_config.rb +403 -0
- data/lib/right_agent/agent_identity.rb +209 -0
- data/lib/right_agent/agent_tags_manager.rb +213 -0
- data/lib/right_agent/audit_formatter.rb +107 -0
- data/lib/right_agent/broker_client.rb +683 -0
- data/lib/right_agent/command.rb +30 -0
- data/lib/right_agent/command/agent_manager_commands.rb +134 -0
- data/lib/right_agent/command/command_client.rb +136 -0
- data/lib/right_agent/command/command_constants.rb +42 -0
- data/lib/right_agent/command/command_io.rb +128 -0
- data/lib/right_agent/command/command_parser.rb +87 -0
- data/lib/right_agent/command/command_runner.rb +105 -0
- data/lib/right_agent/command/command_serializer.rb +63 -0
- data/lib/right_agent/console.rb +65 -0
- data/lib/right_agent/core_payload_types.rb +42 -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 +90 -0
- data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
- data/lib/right_agent/core_payload_types/executable_bundle.rb +138 -0
- data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
- data/lib/right_agent/core_payload_types/login_user.rb +62 -0
- data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
- data/lib/right_agent/core_payload_types/recipe_instantiation.rb +60 -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 +73 -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/dispatcher.rb +348 -0
- data/lib/right_agent/enrollment_result.rb +217 -0
- data/lib/right_agent/exceptions.rb +30 -0
- data/lib/right_agent/ha_broker_client.rb +1278 -0
- data/lib/right_agent/idempotent_request.rb +140 -0
- data/lib/right_agent/log.rb +418 -0
- data/lib/right_agent/monkey_patches.rb +29 -0
- data/lib/right_agent/monkey_patches/amqp_patch.rb +274 -0
- data/lib/right_agent/monkey_patches/ruby_patch.rb +49 -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/singleton_patch.rb +46 -0
- data/lib/right_agent/monkey_patches/ruby_patch/string_patch.rb +107 -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 +90 -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 +91 -0
- data/lib/right_agent/operation_result.rb +270 -0
- data/lib/right_agent/packets.rb +637 -0
- data/lib/right_agent/payload_formatter.rb +104 -0
- data/lib/right_agent/pid_file.rb +159 -0
- data/lib/right_agent/platform.rb +319 -0
- data/lib/right_agent/platform/darwin.rb +227 -0
- data/lib/right_agent/platform/linux.rb +268 -0
- data/lib/right_agent/platform/windows.rb +1204 -0
- data/lib/right_agent/scripts/agent_controller.rb +522 -0
- data/lib/right_agent/scripts/agent_deployer.rb +379 -0
- data/lib/right_agent/scripts/common_parser.rb +153 -0
- data/lib/right_agent/scripts/log_level_manager.rb +193 -0
- data/lib/right_agent/scripts/stats_manager.rb +256 -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 +63 -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 +84 -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 +69 -0
- data/lib/right_agent/sender.rb +937 -0
- data/lib/right_agent/serialize.rb +29 -0
- data/lib/right_agent/serialize/message_pack.rb +102 -0
- data/lib/right_agent/serialize/secure_serializer.rb +131 -0
- data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
- data/lib/right_agent/serialize/serializable.rb +135 -0
- data/lib/right_agent/serialize/serializer.rb +149 -0
- data/lib/right_agent/stats_helper.rb +731 -0
- data/lib/right_agent/subprocess.rb +38 -0
- data/lib/right_agent/tracer.rb +124 -0
- data/right_agent.gemspec +60 -0
- data/spec/actor_registry_spec.rb +81 -0
- data/spec/actor_spec.rb +99 -0
- data/spec/agent_config_spec.rb +226 -0
- data/spec/agent_identity_spec.rb +75 -0
- data/spec/agent_spec.rb +571 -0
- data/spec/broker_client_spec.rb +961 -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 +72 -0
- data/spec/command/command_serializer_spec.rb +51 -0
- data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
- data/spec/core_payload_types/executable_bundle_spec.rb +59 -0
- data/spec/core_payload_types/login_user_spec.rb +98 -0
- data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
- data/spec/core_payload_types/spec_helper.rb +23 -0
- data/spec/dispatcher_spec.rb +372 -0
- data/spec/enrollment_result_spec.rb +53 -0
- data/spec/ha_broker_client_spec.rb +1673 -0
- data/spec/idempotent_request_spec.rb +136 -0
- data/spec/log_spec.rb +177 -0
- data/spec/monkey_patches/amqp_patch_spec.rb +100 -0
- data/spec/monkey_patches/eventmachine_spec.rb +62 -0
- data/spec/monkey_patches/string_patch_spec.rb +99 -0
- data/spec/multiplexer_spec.rb +48 -0
- data/spec/operation_result_spec.rb +171 -0
- data/spec/packets_spec.rb +418 -0
- data/spec/platform/platform_spec.rb +60 -0
- data/spec/results_mock.rb +45 -0
- data/spec/secure_identity_spec.rb +50 -0
- data/spec/security/cached_certificate_store_proxy_spec.rb +56 -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 +52 -0
- data/spec/sender_spec.rb +887 -0
- data/spec/serialize/message_pack_spec.rb +131 -0
- data/spec/serialize/secure_serializer_spec.rb +102 -0
- data/spec/serialize/serializable_spec.rb +90 -0
- data/spec/serialize/serializer_spec.rb +174 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/stats_helper_spec.rb +681 -0
- data/spec/tracer_spec.rb +114 -0
- metadata +320 -0
|
@@ -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,69 @@
|
|
|
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
|
+
# Simple certificate store, serves a static set of certificates
|
|
26
|
+
class StaticCertificateStore
|
|
27
|
+
|
|
28
|
+
# Initialize store
|
|
29
|
+
#
|
|
30
|
+
# === Parameters
|
|
31
|
+
# signer_certs(Array|Certificate):: Signer certificate(s) used when loading data to
|
|
32
|
+
# check the digital signature. The signature associated with the serialized data
|
|
33
|
+
# needs to match with one of the signer certificates for loading to succeed.
|
|
34
|
+
# recipients_certs(Array|Certificate):: Recipient certificate(s) used when serializing
|
|
35
|
+
# data for encryption. Loading the data can only be done through serializers that
|
|
36
|
+
# have been initialized with a certificate that's in the recipient certificates
|
|
37
|
+
# if encryption is enabled.
|
|
38
|
+
def initialize(signer_certs, recipients_certs)
|
|
39
|
+
signer_certs = [ signer_certs ] unless signer_certs.respond_to?(:each)
|
|
40
|
+
@signer_certs = signer_certs
|
|
41
|
+
recipients_certs = [ recipients_certs ] unless recipients_certs.respond_to?(:each)
|
|
42
|
+
@recipients_certs = recipients_certs
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Retrieve signer certificates
|
|
46
|
+
#
|
|
47
|
+
# === Parameters
|
|
48
|
+
# id(String):: Serialized identity of signer, ignored
|
|
49
|
+
#
|
|
50
|
+
# === Return
|
|
51
|
+
# (Array):: Signer certificates
|
|
52
|
+
def get_signer(id)
|
|
53
|
+
@signer_certs
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Retrieve recipient certificates that will be able to decrypt the serialized data
|
|
57
|
+
#
|
|
58
|
+
# === Parameters
|
|
59
|
+
# packet(RightScale::Packet):: Packet containing recipient identity, ignored
|
|
60
|
+
#
|
|
61
|
+
# === Return
|
|
62
|
+
# (Array):: Recipient certificates
|
|
63
|
+
def get_recipients(packet)
|
|
64
|
+
@recipients_certs
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end # StaticCertificateStore
|
|
68
|
+
|
|
69
|
+
end # RightScale
|
|
@@ -0,0 +1,937 @@
|
|
|
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
|
+
# This class allows sending requests to agents without having to run a local mapper
|
|
26
|
+
# It is used by Actor.request which is used by actors that need to send requests to remote agents
|
|
27
|
+
# If requested, it will queue requests when there are no broker connections
|
|
28
|
+
# All requests go through the mapper for security purposes
|
|
29
|
+
class Sender
|
|
30
|
+
|
|
31
|
+
include StatsHelper
|
|
32
|
+
|
|
33
|
+
# Minimum number of seconds between restarts of the inactivity timer
|
|
34
|
+
MIN_RESTART_INACTIVITY_TIMER_INTERVAL = 60
|
|
35
|
+
|
|
36
|
+
# Number of seconds to wait for ping response from a mapper when checking connectivity
|
|
37
|
+
PING_TIMEOUT = 30
|
|
38
|
+
|
|
39
|
+
# Factor used on each retry iteration to achieve exponential backoff
|
|
40
|
+
RETRY_BACKOFF_FACTOR = 4
|
|
41
|
+
|
|
42
|
+
# Maximum seconds to wait before starting flushing offline queue when disabling offline mode
|
|
43
|
+
MAX_QUEUE_FLUSH_DELAY = 120 # 2 minutes
|
|
44
|
+
|
|
45
|
+
# Maximum number of offline queued requests before triggering restart vote
|
|
46
|
+
MAX_QUEUED_REQUESTS = 1000
|
|
47
|
+
|
|
48
|
+
# Number of seconds that should be spent in offline mode before triggering a restart vote
|
|
49
|
+
RESTART_VOTE_DELAY = 900 # 15 minutes
|
|
50
|
+
|
|
51
|
+
# (EM::Timer) Timer while waiting for mapper ping response
|
|
52
|
+
attr_accessor :pending_ping
|
|
53
|
+
|
|
54
|
+
# (Hash) Pending requests; key is request token and value is a hash
|
|
55
|
+
# :response_handler(Proc):: Block to be activated when response is received
|
|
56
|
+
# :receive_time(Time):: Time when message was received
|
|
57
|
+
# :request_kind(String):: Kind of Sender request, optional
|
|
58
|
+
# :retry_parent(String):: Token for parent request in a retry situation, optional
|
|
59
|
+
attr_accessor :pending_requests
|
|
60
|
+
|
|
61
|
+
# (HABrokerClient) High availability AMQP broker client
|
|
62
|
+
attr_accessor :broker
|
|
63
|
+
|
|
64
|
+
# (String) Identity of the associated agent
|
|
65
|
+
attr_reader :identity
|
|
66
|
+
|
|
67
|
+
# Accessor for use by actor
|
|
68
|
+
#
|
|
69
|
+
# === Return
|
|
70
|
+
# (Sender):: This sender instance if defined, otherwise nil
|
|
71
|
+
def self.instance
|
|
72
|
+
@@instance if defined?(@@instance)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Initialize sender
|
|
76
|
+
#
|
|
77
|
+
# === Parameters
|
|
78
|
+
# agent(Agent):: Agent using this sender; uses its identity, broker, and following options:
|
|
79
|
+
# :exception_callback(Proc):: Callback with following parameters that is activated on exception events:
|
|
80
|
+
# exception(Exception):: Exception
|
|
81
|
+
# message(Packet):: Message being processed
|
|
82
|
+
# agent(Agent):: Reference to agent
|
|
83
|
+
# :offline_queueing(Boolean):: Whether to queue request if currently not connected to any brokers,
|
|
84
|
+
# also requires agent invocation of initialize_offline_queue and start_offline_queue methods below
|
|
85
|
+
# :restart_callback(Proc):: Callback that is activated on each restart vote with votes being initiated
|
|
86
|
+
# by offline queue exceeding MAX_QUEUED_REQUESTS or by repeated failures to access mapper when online
|
|
87
|
+
# :retry_timeout(Numeric):: Maximum number of seconds to retry request before give up
|
|
88
|
+
# :retry_interval(Numeric):: Number of seconds before initial request retry, increases exponentially
|
|
89
|
+
# :time_to_live(Integer):: Number of seconds before a request expires and is to be ignored
|
|
90
|
+
# by the receiver, 0 means never expire
|
|
91
|
+
# :secure(Boolean):: true indicates to use Security features of rabbitmq to restrict agents to themselves
|
|
92
|
+
# :single_threaded(Boolean):: true indicates to run all operations in one thread; false indicates
|
|
93
|
+
# to do requested work on EM defer thread and all else, such as pings on main thread
|
|
94
|
+
def initialize(agent)
|
|
95
|
+
@agent = agent
|
|
96
|
+
@identity = @agent.identity
|
|
97
|
+
@options = @agent.options || {}
|
|
98
|
+
@broker = @agent.broker
|
|
99
|
+
@secure = @options[:secure]
|
|
100
|
+
@single_threaded = @options[:single_threaded]
|
|
101
|
+
@queueing_mode = :initializing
|
|
102
|
+
@queue_running = false
|
|
103
|
+
@queue_initializing = false
|
|
104
|
+
@queue = []
|
|
105
|
+
@restart_vote_count = 0
|
|
106
|
+
@retry_timeout = nil_if_zero(@options[:retry_timeout])
|
|
107
|
+
@retry_interval = nil_if_zero(@options[:retry_interval])
|
|
108
|
+
@ping_interval = @options[:ping_interval] || 0
|
|
109
|
+
|
|
110
|
+
# Only to be accessed from primary thread
|
|
111
|
+
@pending_requests = {}
|
|
112
|
+
@pending_ping = nil
|
|
113
|
+
|
|
114
|
+
reset_stats
|
|
115
|
+
@last_received = 0
|
|
116
|
+
@message_received_callbacks = []
|
|
117
|
+
restart_inactivity_timer if @ping_interval > 0
|
|
118
|
+
@@instance = self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Update the time this agent last received a request or response message
|
|
122
|
+
# and restart the inactivity timer thus deferring the next connectivity check
|
|
123
|
+
# Also forward this message receipt notification to any callbacks that have registered
|
|
124
|
+
#
|
|
125
|
+
# === Block
|
|
126
|
+
# Optional block without parameters that is activated when a message is received
|
|
127
|
+
#
|
|
128
|
+
# === Return
|
|
129
|
+
# true:: Always return true
|
|
130
|
+
def message_received(&callback)
|
|
131
|
+
if block_given?
|
|
132
|
+
@message_received_callbacks << callback
|
|
133
|
+
else
|
|
134
|
+
@message_received_callbacks.each { |c| c.call }
|
|
135
|
+
if @ping_interval > 0
|
|
136
|
+
now = Time.now.to_i
|
|
137
|
+
if (now - @last_received) > MIN_RESTART_INACTIVITY_TIMER_INTERVAL
|
|
138
|
+
@last_received = now
|
|
139
|
+
restart_inactivity_timer
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
true
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Initialize the offline queue (should be called once)
|
|
147
|
+
# All requests sent prior to running this initialization are queued if offline
|
|
148
|
+
# queueing is enabled and then are sent once this initialization has run
|
|
149
|
+
# All requests following this call and prior to calling start_offline_queue
|
|
150
|
+
# are prepended to the request queue
|
|
151
|
+
#
|
|
152
|
+
# === Return
|
|
153
|
+
# true:: Always return true
|
|
154
|
+
def initialize_offline_queue
|
|
155
|
+
unless @queue_running || !@options[:offline_queueing]
|
|
156
|
+
@queue_running = true
|
|
157
|
+
@queue_initializing = true
|
|
158
|
+
end
|
|
159
|
+
true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Switch offline queueing to online mode and flush all buffered messages
|
|
163
|
+
#
|
|
164
|
+
# === Return
|
|
165
|
+
# true:: Always return true
|
|
166
|
+
def start_offline_queue
|
|
167
|
+
if @queue_initializing
|
|
168
|
+
@queue_initializing = false
|
|
169
|
+
flush_queue unless @queueing_mode == :offline
|
|
170
|
+
@queueing_mode = :online if @queueing_mode == :initializing
|
|
171
|
+
end
|
|
172
|
+
true
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Send a request to a single target or multiple targets with no response expected other
|
|
176
|
+
# than routing failures
|
|
177
|
+
# Do not persist the request en route
|
|
178
|
+
# Enqueue the request if the target is not currently available
|
|
179
|
+
# Never automatically retry the request
|
|
180
|
+
# Set time-to-live to be forever
|
|
181
|
+
#
|
|
182
|
+
# === Parameters
|
|
183
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
|
184
|
+
# payload(Object):: Data to be sent with marshalling en route
|
|
185
|
+
# target(String|Hash):: Identity of specific target, hash for selecting potentially multiple
|
|
186
|
+
# targets, or nil if routing solely using type
|
|
187
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
|
188
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
|
189
|
+
# :account(Integer):: Restrict to agents with this account id
|
|
190
|
+
# :deployment(Integer):: Restrict to agents with this deployment id
|
|
191
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
|
192
|
+
# ones with no shard id
|
|
193
|
+
# :selector(Symbol):: Which of the matched targets to be selected, either :any or :all,
|
|
194
|
+
# defaults to :any
|
|
195
|
+
#
|
|
196
|
+
# === Block
|
|
197
|
+
# Optional block used to process routing response failures asynchronously with the following parameter:
|
|
198
|
+
# result(Result):: Response with an OperationResult of RETRY, NON_DELIVERY, or ERROR,
|
|
199
|
+
# use RightScale::OperationResult.from_results to decode
|
|
200
|
+
#
|
|
201
|
+
# === Return
|
|
202
|
+
# true:: Always return true
|
|
203
|
+
def send_push(type, payload = nil, target = nil, &callback)
|
|
204
|
+
build_push(:send_push, type, payload, target, &callback)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Send a request to a single target or multiple targets with no response expected other
|
|
208
|
+
# than routing failures
|
|
209
|
+
# Persist the request en route to reduce the chance of it being lost at the expense of some
|
|
210
|
+
# additional network overhead
|
|
211
|
+
# Enqueue the request if the target is not currently available
|
|
212
|
+
# Never automatically retry the request
|
|
213
|
+
# Set time-to-live to be forever
|
|
214
|
+
#
|
|
215
|
+
# === Parameters
|
|
216
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
|
217
|
+
# payload(Object):: Data to be sent with marshalling en route
|
|
218
|
+
# target(String|Hash):: Identity of specific target, hash for selecting potentially multiple
|
|
219
|
+
# targets, or nil if routing solely using type
|
|
220
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
|
221
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
|
222
|
+
# :account(Integer):: Restrict to agents with this account id
|
|
223
|
+
# :deployment(Integer):: Restrict to agents with this deployment id
|
|
224
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
|
225
|
+
# ones with no shard id
|
|
226
|
+
# :selector(Symbol):: Which of the matched targets to be selected, either :any or :all,
|
|
227
|
+
# defaults to :any
|
|
228
|
+
#
|
|
229
|
+
# === Block
|
|
230
|
+
# Optional block used to process routing response failures asynchronously with the following parameter:
|
|
231
|
+
# result(Result):: Response with an OperationResult of RETRY, NON_DELIVERY, or ERROR,
|
|
232
|
+
# use RightScale::OperationResult.from_results to decode
|
|
233
|
+
#
|
|
234
|
+
# === Return
|
|
235
|
+
# true:: Always return true
|
|
236
|
+
def send_persistent_push(type, payload = nil, target = nil, &callback)
|
|
237
|
+
build_push(:send_persistent_push, type, payload, target, &callback)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Send a request to a single target with a response expected
|
|
241
|
+
# Automatically retry the request if a response is not received in a reasonable amount of time
|
|
242
|
+
# or if there is a non-delivery response indicating the target is not currently available
|
|
243
|
+
# Timeout the request if a response is not received in time, typically configured to 2 minutes
|
|
244
|
+
# Because of retries there is the possibility of duplicated requests, and these are detected and
|
|
245
|
+
# discarded automatically unless the receiving agent is using a shared queue, in which case this
|
|
246
|
+
# method should not be used for actions that are non-idempotent
|
|
247
|
+
# Allow the request to expire per the agent's configured time-to-live, typically 1 minute
|
|
248
|
+
# Note that receiving a response does not guarantee that the request activity has actually
|
|
249
|
+
# completed since the request processing may involve other asynchronous requests
|
|
250
|
+
#
|
|
251
|
+
# === Parameters
|
|
252
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
|
253
|
+
# payload(Object):: Data to be sent with marshalling en route
|
|
254
|
+
# target(String|Hash):: Identity of specific target, hash for selecting targets of which one is picked
|
|
255
|
+
# randomly, or nil if routing solely using type
|
|
256
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
|
257
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
|
258
|
+
# :account(Integer):: Restrict to agents with this account id
|
|
259
|
+
# :deployment(Integer):: Restrict to agents with this deployment id
|
|
260
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
|
261
|
+
# ones with no shard id
|
|
262
|
+
#
|
|
263
|
+
# === Block
|
|
264
|
+
# Required block used to process response asynchronously with the following parameter:
|
|
265
|
+
# result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR,
|
|
266
|
+
# use RightScale::OperationResult.from_results to decode
|
|
267
|
+
#
|
|
268
|
+
# === Return
|
|
269
|
+
# true:: Always return true
|
|
270
|
+
def send_retryable_request(type, payload = nil, target = nil, &callback)
|
|
271
|
+
build_request(:send_retryable_request, type, payload, target, &callback)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Send a request to a single target with a response expected
|
|
275
|
+
# Persist the request en route to reduce the chance of it being lost at the expense of some
|
|
276
|
+
# additional network overhead
|
|
277
|
+
# Enqueue the request if the target is not currently available
|
|
278
|
+
# Never automatically retry the request if there is the possibility of the request being duplicated
|
|
279
|
+
# Set time-to-live to be forever
|
|
280
|
+
# Note that receiving a response does not guarantee that the request activity has actually
|
|
281
|
+
# completed since the request processing may involve other asynchronous requests
|
|
282
|
+
#
|
|
283
|
+
# === Parameters
|
|
284
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
|
285
|
+
# payload(Object):: Data to be sent with marshalling en route
|
|
286
|
+
# target(String|Hash):: Identity of specific target, hash for selecting targets of which one is picked
|
|
287
|
+
# randomly, or nil if routing solely using type
|
|
288
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
|
289
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
|
290
|
+
# :account(Integer):: Restrict to agents with this account id
|
|
291
|
+
# :deployment(Integer):: Restrict to agents with this deployment id
|
|
292
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
|
293
|
+
# ones with no shard id
|
|
294
|
+
#
|
|
295
|
+
# === Block
|
|
296
|
+
# Required block used to process response asynchronously with the following parameter:
|
|
297
|
+
# result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR,
|
|
298
|
+
# use RightScale::OperationResult.from_results to decode
|
|
299
|
+
#
|
|
300
|
+
# === Return
|
|
301
|
+
# true:: Always return true
|
|
302
|
+
def send_persistent_request(type, payload = nil, target = nil, &callback)
|
|
303
|
+
build_request(:send_persistent_request, type, payload, target, &callback)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Handle response to a request
|
|
307
|
+
# Only to be called from primary thread
|
|
308
|
+
#
|
|
309
|
+
# === Parameters
|
|
310
|
+
# response(Result):: Packet received as result of request
|
|
311
|
+
#
|
|
312
|
+
# === Return
|
|
313
|
+
# true:: Always return true
|
|
314
|
+
def handle_response(response)
|
|
315
|
+
token = response.token
|
|
316
|
+
if response.is_a?(Result)
|
|
317
|
+
if result = OperationResult.from_results(response)
|
|
318
|
+
if result.non_delivery?
|
|
319
|
+
@non_deliveries.update(result.content.nil? ? "nil" : result.content.inspect)
|
|
320
|
+
elsif result.error?
|
|
321
|
+
@result_errors.update(result.content.nil? ? "nil" : result.content.inspect)
|
|
322
|
+
end
|
|
323
|
+
@results.update(result.status)
|
|
324
|
+
else
|
|
325
|
+
@results.update(response.results.nil? ? "nil" : response.results)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
if handler = @pending_requests[token]
|
|
329
|
+
if result && result.non_delivery? && handler[:request_kind] == :send_retryable_request &&
|
|
330
|
+
[OperationResult::TARGET_NOT_CONNECTED, OperationResult::TTL_EXPIRATION].include?(result.content)
|
|
331
|
+
# Log and ignore so that timeout retry mechanism continues
|
|
332
|
+
# Leave purging of associated request until final response, i.e., success response or retry timeout
|
|
333
|
+
Log.info("Non-delivery of <#{token}> because #{result.content}")
|
|
334
|
+
else
|
|
335
|
+
deliver(response, handler)
|
|
336
|
+
end
|
|
337
|
+
elsif result && result.non_delivery?
|
|
338
|
+
Log.info("Non-delivery of <#{token}> because #{result.content}")
|
|
339
|
+
else
|
|
340
|
+
Log.debug("No pending request for response #{response.to_s([])}")
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
true
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Switch to offline mode, in this mode requests are queued in memory
|
|
347
|
+
# rather than sent to the mapper
|
|
348
|
+
# Idempotent
|
|
349
|
+
#
|
|
350
|
+
# === Return
|
|
351
|
+
# true:: Always return true
|
|
352
|
+
def enable_offline_mode
|
|
353
|
+
if offline?
|
|
354
|
+
if @flushing_queue
|
|
355
|
+
# If we were in offline mode then switched back to online but are still in the
|
|
356
|
+
# process of flushing the in memory queue and are now switching to offline mode
|
|
357
|
+
# again then stop the flushing
|
|
358
|
+
@stop_flushing_queue = true
|
|
359
|
+
end
|
|
360
|
+
else
|
|
361
|
+
Log.info("[offline] Disconnect from broker detected, entering offline mode")
|
|
362
|
+
Log.info("[offline] Messages will be queued in memory until connection to broker is re-established")
|
|
363
|
+
@offlines.update
|
|
364
|
+
@queue ||= [] # ensure queue is valid without losing any messages when going offline
|
|
365
|
+
@queueing_mode = :offline
|
|
366
|
+
@restart_vote_timer ||= EM::Timer.new(RESTART_VOTE_DELAY) { vote_to_restart(timer_trigger=true) }
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Switch back to sending requests to mapper after in memory queue gets flushed
|
|
371
|
+
# Idempotent
|
|
372
|
+
#
|
|
373
|
+
# === Return
|
|
374
|
+
# true:: Always return true
|
|
375
|
+
def disable_offline_mode
|
|
376
|
+
if offline? && @queue_running
|
|
377
|
+
Log.info("[offline] Connection to broker re-established")
|
|
378
|
+
@offlines.finish
|
|
379
|
+
@restart_vote_timer.cancel if @restart_vote_timer
|
|
380
|
+
@restart_vote_timer = nil
|
|
381
|
+
@stop_flushing_queue = false
|
|
382
|
+
@flushing_queue = true
|
|
383
|
+
# Let's wait a bit not to flood the mapper
|
|
384
|
+
EM.add_timer(rand(MAX_QUEUE_FLUSH_DELAY)) { flush_queue } if @queue_running
|
|
385
|
+
end
|
|
386
|
+
true
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Get age of youngest pending request
|
|
390
|
+
#
|
|
391
|
+
# === Return
|
|
392
|
+
# age(Integer|nil):: Age in seconds of youngest request, or nil if no pending requests
|
|
393
|
+
def request_age
|
|
394
|
+
time = Time.now
|
|
395
|
+
age = nil
|
|
396
|
+
@pending_requests.each_value do |request|
|
|
397
|
+
seconds = time - request[:receive_time]
|
|
398
|
+
age = seconds.to_i if age.nil? || seconds < age
|
|
399
|
+
end
|
|
400
|
+
age
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Take any actions necessary to quiesce mapper interaction in preparation
|
|
404
|
+
# for agent termination but allow message receipt to continue
|
|
405
|
+
#
|
|
406
|
+
# === Return
|
|
407
|
+
# (Array):: Number of pending requests and age of youngest request
|
|
408
|
+
def terminate
|
|
409
|
+
@terminating = true
|
|
410
|
+
@ping_interval = 0
|
|
411
|
+
if @pending_ping
|
|
412
|
+
@pending_ping.cancel
|
|
413
|
+
@pending_ping = nil
|
|
414
|
+
end
|
|
415
|
+
if @timer
|
|
416
|
+
@timer.cancel
|
|
417
|
+
@timer = nil
|
|
418
|
+
end
|
|
419
|
+
if @restart_vote_timer
|
|
420
|
+
@restart_vote_timer.cancel
|
|
421
|
+
@restart_vote_timer = nil
|
|
422
|
+
end
|
|
423
|
+
[@pending_requests.size, request_age]
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Create displayable dump of unfinished request information
|
|
427
|
+
# Truncate list if there are more than 50 requests
|
|
428
|
+
#
|
|
429
|
+
# === Return
|
|
430
|
+
# info(Array(String)):: Receive time and token for each request in descending time order
|
|
431
|
+
def dump_requests
|
|
432
|
+
info = []
|
|
433
|
+
@pending_requests.each do |token, request|
|
|
434
|
+
info << "#{request[:receive_time].localtime} <#{token}>"
|
|
435
|
+
end
|
|
436
|
+
info.sort.reverse
|
|
437
|
+
info = info[0..49] + ["..."] if info.size > 50
|
|
438
|
+
info
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Get sender statistics
|
|
442
|
+
#
|
|
443
|
+
# === Parameters
|
|
444
|
+
# reset(Boolean):: Whether to reset the statistics after getting the current ones
|
|
445
|
+
#
|
|
446
|
+
# === Return
|
|
447
|
+
# stats(Hash):: Current statistics:
|
|
448
|
+
# "exceptions"(Hash|nil):: Exceptions raised per category, or nil if none
|
|
449
|
+
# "total"(Integer):: Total exceptions for this category
|
|
450
|
+
# "recent"(Array):: Most recent as a hash of "count", "type", "message", "when", and "where"
|
|
451
|
+
# "non-deliveries"(Hash|nil):: Non-delivery activity stats with keys "total", "percent", "last",
|
|
452
|
+
# and 'rate' with percentage breakdown per reason, or nil if none
|
|
453
|
+
# "offlines"(Hash|nil):: Offline activity stats with keys "total", "last", and "duration",
|
|
454
|
+
# or nil if none
|
|
455
|
+
# "pings"(Hash|nil):: Request activity stats with keys "total", "percent", "last", and "rate"
|
|
456
|
+
# with percentage breakdown for "success" vs. "timeout", or nil if none
|
|
457
|
+
# "request kinds"(Hash|nil):: Request kind activity stats with keys "total", "percent", and "last"
|
|
458
|
+
# with percentage breakdown per kind, or nil if none
|
|
459
|
+
# "requests"(Hash|nil):: Request activity stats with keys "total", "percent", "last", and "rate"
|
|
460
|
+
# with percentage breakdown per request type, or nil if none
|
|
461
|
+
# "requests pending"(Hash|nil):: Number of requests waiting for response and age of oldest, or nil if none
|
|
462
|
+
# "response time"(Float):: Average number of seconds to respond to a request recently
|
|
463
|
+
# "result errors"(Hash|nil):: Error result activity stats with keys "total", "percent", "last",
|
|
464
|
+
# and 'rate' with percentage breakdown per error, or nil if none
|
|
465
|
+
# "results"(Hash|nil):: Results activity stats with keys "total", "percent", "last", and "rate"
|
|
466
|
+
# with percentage breakdown per operation result type, or nil if none
|
|
467
|
+
# "retries"(Hash|nil):: Retry activity stats with keys "total", "percent", "last", and "rate"
|
|
468
|
+
# with percentage breakdown per request type, or nil if none
|
|
469
|
+
def stats(reset = false)
|
|
470
|
+
offlines = @offlines.all
|
|
471
|
+
offlines.merge!("duration" => @offlines.avg_duration) if offlines
|
|
472
|
+
requests_pending = if @pending_requests.size > 0
|
|
473
|
+
now = Time.now.to_i
|
|
474
|
+
oldest = @pending_requests.values.inject(0) { |m, r| [m, now - r[:receive_time].to_i].max }
|
|
475
|
+
{"total" => @pending_requests.size, "oldest age" => oldest}
|
|
476
|
+
end
|
|
477
|
+
stats = {
|
|
478
|
+
"exceptions" => @exceptions.stats,
|
|
479
|
+
"non-deliveries" => @non_deliveries.all,
|
|
480
|
+
"offlines" => offlines,
|
|
481
|
+
"pings" => @pings.all,
|
|
482
|
+
"request kinds" => @request_kinds.all,
|
|
483
|
+
"requests" => @requests.all,
|
|
484
|
+
"requests pending" => requests_pending,
|
|
485
|
+
"response time" => @requests.avg_duration,
|
|
486
|
+
"result errors" => @result_errors.all,
|
|
487
|
+
"results" => @results.all,
|
|
488
|
+
"retries" => @retries.all
|
|
489
|
+
}
|
|
490
|
+
reset_stats if reset
|
|
491
|
+
stats
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
protected
|
|
495
|
+
|
|
496
|
+
# Reset dispatch statistics
|
|
497
|
+
#
|
|
498
|
+
# === Return
|
|
499
|
+
# true:: Always return true
|
|
500
|
+
def reset_stats
|
|
501
|
+
@pings = ActivityStats.new
|
|
502
|
+
@retries = ActivityStats.new
|
|
503
|
+
@requests = ActivityStats.new
|
|
504
|
+
@results = ActivityStats.new
|
|
505
|
+
@result_errors = ActivityStats.new
|
|
506
|
+
@non_deliveries = ActivityStats.new
|
|
507
|
+
@offlines = ActivityStats.new(measure_rate = false)
|
|
508
|
+
@request_kinds = ActivityStats.new(measure_rate = false)
|
|
509
|
+
@exceptions = ExceptionStats.new(@agent, @options[:exception_callback])
|
|
510
|
+
true
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# Build and send Push packet
|
|
514
|
+
#
|
|
515
|
+
# === Parameters
|
|
516
|
+
# kind(Symbol):: Kind of push: :send_push or :send_persistent_push
|
|
517
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
|
518
|
+
# payload(Object):: Data to be sent with marshalling en route
|
|
519
|
+
# target(String|Hash):: Identity of specific target, or hash for selecting potentially multiple
|
|
520
|
+
# targets, or nil if routing solely using type
|
|
521
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
|
522
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
|
523
|
+
# :account(Integer):: Restrict to agents with this account id
|
|
524
|
+
# :deployment(Integer):: Restrict to agents with this deployment id
|
|
525
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
|
526
|
+
# ones with no shard id
|
|
527
|
+
# :selector(Symbol):: Which of the matched targets to be selected, either :any or :all,
|
|
528
|
+
# defaults to :any
|
|
529
|
+
#
|
|
530
|
+
# === Block
|
|
531
|
+
# Optional block used to process routing response failures asynchronously with the following parameter:
|
|
532
|
+
# result(Result):: Response with an OperationResult of RETRY, NON_DELIVERY, or ERROR,
|
|
533
|
+
# use RightScale::OperationResult.from_results to decode
|
|
534
|
+
#
|
|
535
|
+
# === Return
|
|
536
|
+
# true:: Always return true
|
|
537
|
+
#
|
|
538
|
+
# === Raise
|
|
539
|
+
# ArgumentError:: If target is invalid
|
|
540
|
+
def build_push(kind, type, payload = nil, target = nil, &callback)
|
|
541
|
+
validate_target(target, allow_selector = true)
|
|
542
|
+
if should_queue?
|
|
543
|
+
queue_request(:kind => kind, :type => type, :payload => payload, :target => target, :callback => callback)
|
|
544
|
+
else
|
|
545
|
+
method = type.split('/').last
|
|
546
|
+
received_at = @requests.update(method)
|
|
547
|
+
push = Push.new(type, payload)
|
|
548
|
+
push.from = @identity
|
|
549
|
+
push.token = AgentIdentity.generate
|
|
550
|
+
if target.is_a?(Hash)
|
|
551
|
+
push.tags = target[:tags] || []
|
|
552
|
+
push.scope = target[:scope]
|
|
553
|
+
push.selector = target[:selector] || :any
|
|
554
|
+
else
|
|
555
|
+
push.target = target
|
|
556
|
+
end
|
|
557
|
+
push.persistent = kind == :send_persistent_push
|
|
558
|
+
@request_kinds.update((push.selector == :all ? kind.to_s.sub(/push/, "fanout") : kind.to_s)[5..-1])
|
|
559
|
+
@pending_requests[push.token] = {
|
|
560
|
+
:response_handler => callback,
|
|
561
|
+
:receive_time => received_at,
|
|
562
|
+
:request_kind => kind
|
|
563
|
+
} if callback
|
|
564
|
+
publish(push)
|
|
565
|
+
end
|
|
566
|
+
true
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Build and send Request packet
|
|
570
|
+
#
|
|
571
|
+
# === Parameters
|
|
572
|
+
# kind(Symbol):: Kind of request: :send_retryable_request or :send_persistent_request
|
|
573
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
|
574
|
+
# payload(Object):: Data to be sent with marshalling en route
|
|
575
|
+
# target(String|Hash):: Identity of specific target, or hash for selecting targets of which one is picked
|
|
576
|
+
# randomly, or nil if routing solely using type
|
|
577
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
|
578
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
|
579
|
+
# :account(Integer):: Restrict to agents with this account id
|
|
580
|
+
# :deployment(Integer):: Restrict to agents with this deployment id
|
|
581
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
|
582
|
+
# ones with no shard id
|
|
583
|
+
#
|
|
584
|
+
# === Block
|
|
585
|
+
# Required block used to process response asynchronously with the following parameter:
|
|
586
|
+
# result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR,
|
|
587
|
+
# use RightScale::OperationResult.from_results to decode
|
|
588
|
+
#
|
|
589
|
+
# === Return
|
|
590
|
+
# true:: Always return true
|
|
591
|
+
#
|
|
592
|
+
# === Raise
|
|
593
|
+
# ArgumentError:: If target is invalid
|
|
594
|
+
def build_request(kind, type, payload, target, &callback)
|
|
595
|
+
validate_target(target, allow_selector = false)
|
|
596
|
+
if should_queue?
|
|
597
|
+
queue_request(:kind => kind, :type => type, :payload => payload, :target => target, :callback => callback)
|
|
598
|
+
else
|
|
599
|
+
method = type.split('/').last
|
|
600
|
+
token = AgentIdentity.generate
|
|
601
|
+
non_duplicate = kind == :send_persistent_request
|
|
602
|
+
received_at = @requests.update(method, token)
|
|
603
|
+
@request_kinds.update(kind.to_s[5..-1])
|
|
604
|
+
|
|
605
|
+
# Using next_tick to ensure on primary thread since using @pending_requests
|
|
606
|
+
EM.next_tick do
|
|
607
|
+
begin
|
|
608
|
+
request = Request.new(type, payload)
|
|
609
|
+
request.from = @identity
|
|
610
|
+
request.token = token
|
|
611
|
+
if target.is_a?(Hash)
|
|
612
|
+
request.tags = target[:tags] || []
|
|
613
|
+
request.scope = target[:scope]
|
|
614
|
+
request.selector = :any
|
|
615
|
+
else
|
|
616
|
+
request.target = target
|
|
617
|
+
end
|
|
618
|
+
request.expires_at = Time.now.to_i + @options[:time_to_live] if !non_duplicate && @options[:time_to_live] && @options[:time_to_live] != 0
|
|
619
|
+
request.persistent = non_duplicate
|
|
620
|
+
@pending_requests[token] = {
|
|
621
|
+
:response_handler => callback,
|
|
622
|
+
:receive_time => received_at,
|
|
623
|
+
:request_kind => kind}
|
|
624
|
+
if non_duplicate
|
|
625
|
+
publish(request)
|
|
626
|
+
else
|
|
627
|
+
publish_with_timeout_retry(request, token)
|
|
628
|
+
end
|
|
629
|
+
rescue Exception => e
|
|
630
|
+
Log.error("Failed to send #{type} #{kind.to_s}", e, :trace)
|
|
631
|
+
@exceptions.track(kind.to_s, e, request)
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
true
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
# Validate target argument of send
|
|
639
|
+
#
|
|
640
|
+
# === Parameters
|
|
641
|
+
# target(String|Hash):: Identity of specific target, or hash for selecting targets of which one is picked
|
|
642
|
+
# allow_selector(Boolean):: Whether to allow :selector
|
|
643
|
+
#
|
|
644
|
+
# === Return
|
|
645
|
+
# true:: Always return true
|
|
646
|
+
#
|
|
647
|
+
# === Raise
|
|
648
|
+
# ArgumentError:: If target is invalid
|
|
649
|
+
def validate_target(target, allow_selector)
|
|
650
|
+
if target.is_a?(Hash)
|
|
651
|
+
selector = allow_selector ? ":selector, " : ""
|
|
652
|
+
t = SerializationHelper.symbolize_keys(target)
|
|
653
|
+
if s = target[:scope]
|
|
654
|
+
if s.is_a?(Hash)
|
|
655
|
+
s = SerializationHelper.symbolize_keys(s)
|
|
656
|
+
if ([:account, :deployment, :shard] & s.keys).empty? && !s.empty?
|
|
657
|
+
raise ArgumentError, "Invalid target scope (#{t[:scope].inspect}), choices are :account, :deployment, and :shard allowed"
|
|
658
|
+
end
|
|
659
|
+
t[:scope] = s
|
|
660
|
+
else
|
|
661
|
+
raise ArgumentError, "Invalid target scope (#{t[:scope].inspect}), must be a hash of :account, :deployment, and/or :shard"
|
|
662
|
+
end
|
|
663
|
+
elsif (s = t[:selector]) && allow_selector
|
|
664
|
+
s = s.to_sym
|
|
665
|
+
unless [:any, :all].include?(s)
|
|
666
|
+
raise ArgumentError, "Invalid target selector (#{t[:selector].inspect}), choices are :any and :all"
|
|
667
|
+
end
|
|
668
|
+
t[:selector] = s
|
|
669
|
+
elsif !t.has_key?(:tags) && !t.empty?
|
|
670
|
+
raise ArgumentError, "Invalid target hash (#{target.inspect}), choices are #{selector}:tags and/or :scope"
|
|
671
|
+
end
|
|
672
|
+
target = t
|
|
673
|
+
elsif !target.nil? && !target.is_a?(String)
|
|
674
|
+
raise ArgumentError, "Invalid target (#{target.inspect}), choices are specific target name or a hash of #{selector}:tags and/or :scope"
|
|
675
|
+
end
|
|
676
|
+
true
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
# Publish request with one or more retries if do not receive a response in time
|
|
680
|
+
# Send timeout result if reach configured retry timeout limit
|
|
681
|
+
# Use exponential backoff with RETRY_BACKOFF_FACTOR for retry spacing
|
|
682
|
+
# Adjust retry interval by average response time to avoid adding to system load
|
|
683
|
+
# when system gets slow
|
|
684
|
+
#
|
|
685
|
+
# === Parameters
|
|
686
|
+
# request(Request):: Request to be sent
|
|
687
|
+
# parent(String):: Token for original request
|
|
688
|
+
# count(Integer):: Number of retries so far
|
|
689
|
+
# multiplier(Integer):: Multiplier for retry interval for exponential backoff
|
|
690
|
+
# elapsed(Integer):: Elapsed time in seconds since this request was first attempted
|
|
691
|
+
#
|
|
692
|
+
# === Return
|
|
693
|
+
# true:: Always return true
|
|
694
|
+
def publish_with_timeout_retry(request, parent, count = 0, multiplier = 1, elapsed = 0)
|
|
695
|
+
ids = publish(request)
|
|
696
|
+
|
|
697
|
+
if @retry_interval && @retry_timeout && parent && !ids.empty?
|
|
698
|
+
interval = [(@retry_interval * multiplier) + (@requests.avg_duration || 0), @retry_timeout - elapsed].min
|
|
699
|
+
EM.add_timer(interval) do
|
|
700
|
+
begin
|
|
701
|
+
if handler = @pending_requests[parent]
|
|
702
|
+
count += 1
|
|
703
|
+
elapsed += interval
|
|
704
|
+
if elapsed < @retry_timeout
|
|
705
|
+
request.tries << request.token
|
|
706
|
+
request.token = AgentIdentity.generate
|
|
707
|
+
@pending_requests[parent][:retry_parent] = parent if count == 1
|
|
708
|
+
@pending_requests[request.token] = @pending_requests[parent]
|
|
709
|
+
publish_with_timeout_retry(request, parent, count, multiplier * RETRY_BACKOFF_FACTOR, elapsed)
|
|
710
|
+
@retries.update(request.type.split('/').last)
|
|
711
|
+
else
|
|
712
|
+
Log.warning("RE-SEND TIMEOUT after #{elapsed.to_i} seconds for #{request.to_s([:tags, :target, :tries])}")
|
|
713
|
+
result = OperationResult.non_delivery(OperationResult::RETRY_TIMEOUT)
|
|
714
|
+
@non_deliveries.update(result.content)
|
|
715
|
+
handle_response(Result.new(request.token, request.reply_to, result, @identity))
|
|
716
|
+
end
|
|
717
|
+
check_connection(ids.first) if count == 1
|
|
718
|
+
end
|
|
719
|
+
rescue Exception => e
|
|
720
|
+
Log.error("Failed retry for #{request.token}", e, :trace)
|
|
721
|
+
@exceptions.track("retry", e, request)
|
|
722
|
+
end
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
true
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
# Publish request
|
|
729
|
+
# Use mandatory flag to request return of message if it cannot be delivered
|
|
730
|
+
#
|
|
731
|
+
# === Parameters
|
|
732
|
+
# request(Push|Request):: Packet to be sent
|
|
733
|
+
# ids(Array|nil):: Identity of specific brokers to choose from, or nil if any okay
|
|
734
|
+
#
|
|
735
|
+
# === Return
|
|
736
|
+
# ids(Array):: Identity of brokers published to
|
|
737
|
+
def publish(request, ids = nil)
|
|
738
|
+
begin
|
|
739
|
+
exchange = {:type => :fanout, :name => "request", :options => {:durable => true, :no_declare => @secure}}
|
|
740
|
+
ids = @broker.publish(exchange, request, :persistent => request.persistent, :mandatory => true,
|
|
741
|
+
:log_filter => [:tags, :target, :tries, :persistent], :brokers => ids)
|
|
742
|
+
rescue HABrokerClient::NoConnectedBrokers => e
|
|
743
|
+
Log.error("Failed to publish request #{request.to_s([:tags, :target, :tries])}", e)
|
|
744
|
+
ids = []
|
|
745
|
+
rescue Exception => e
|
|
746
|
+
Log.error("Failed to publish request #{request.to_s([:tags, :target, :tries])}", e, :trace)
|
|
747
|
+
@exceptions.track("publish", e, request)
|
|
748
|
+
ids = []
|
|
749
|
+
end
|
|
750
|
+
ids
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
# Deliver the response and remove associated request(s) from pending
|
|
754
|
+
# Use defer thread instead of primary if not single threaded, consistent with dispatcher,
|
|
755
|
+
# so that all shared data is accessed from the same thread
|
|
756
|
+
# Do callback if there is an exception, consistent with agent identity queue handling
|
|
757
|
+
# Only to be called from primary thread
|
|
758
|
+
#
|
|
759
|
+
# === Parameters
|
|
760
|
+
# response(Result):: Packet received as result of request
|
|
761
|
+
# handler(Hash):: Associated request handler
|
|
762
|
+
#
|
|
763
|
+
# === Return
|
|
764
|
+
# true:: Always return true
|
|
765
|
+
def deliver(response, handler)
|
|
766
|
+
@requests.finish(handler[:receive_time], response.token)
|
|
767
|
+
|
|
768
|
+
@pending_requests.delete(response.token)
|
|
769
|
+
if parent = handler[:retry_parent]
|
|
770
|
+
@pending_requests.reject! { |k, v| k == parent || v[:retry_parent] == parent }
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
if handler[:response_handler]
|
|
774
|
+
EM.__send__(@single_threaded ? :next_tick : :defer) do
|
|
775
|
+
begin
|
|
776
|
+
handler[:response_handler].call(response)
|
|
777
|
+
rescue Exception => e
|
|
778
|
+
Log.error("Failed processing response {response.to_s([])}", e, :trace)
|
|
779
|
+
@exceptions.track("response", e, response)
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
end
|
|
783
|
+
true
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
# Check whether broker connection is usable by pinging a mapper via that broker
|
|
787
|
+
# Attempt to reconnect if ping does not respond in PING_TIMEOUT seconds
|
|
788
|
+
# Ignore request if already checking a connection
|
|
789
|
+
# Only to be called from primary thread
|
|
790
|
+
#
|
|
791
|
+
# === Parameters
|
|
792
|
+
# id(String):: Identity of specific broker to use to send ping, defaults to any
|
|
793
|
+
# currently connected broker
|
|
794
|
+
#
|
|
795
|
+
# === Return
|
|
796
|
+
# true:: Always return true
|
|
797
|
+
def check_connection(id = nil)
|
|
798
|
+
unless @terminating || @pending_ping || (id && !@broker.connected?(id))
|
|
799
|
+
@pending_ping = EM::Timer.new(PING_TIMEOUT) do
|
|
800
|
+
begin
|
|
801
|
+
@pings.update("timeout")
|
|
802
|
+
@pending_ping = nil
|
|
803
|
+
Log.warning("Mapper ping via broker #{id} timed out after #{PING_TIMEOUT} seconds, attempting to reconnect")
|
|
804
|
+
host, port, index, priority, _ = @broker.identity_parts(id)
|
|
805
|
+
@agent.connect(host, port, index, priority, force = true)
|
|
806
|
+
rescue Exception => e
|
|
807
|
+
Log.error("Failed to reconnect to broker #{id}", e, :trace)
|
|
808
|
+
@exceptions.track("ping timeout", e)
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
handler = lambda do |_|
|
|
813
|
+
begin
|
|
814
|
+
if @pending_ping
|
|
815
|
+
@pings.update("success")
|
|
816
|
+
@pending_ping.cancel
|
|
817
|
+
@pending_ping = nil
|
|
818
|
+
end
|
|
819
|
+
rescue Exception => e
|
|
820
|
+
Log.error("Failed to cancel mapper ping", e, :trace)
|
|
821
|
+
@exceptions.track("cancel ping", e)
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
request = Request.new("/mapper/ping", nil, {:from => @identity, :token => AgentIdentity.generate})
|
|
826
|
+
@pending_requests[request.token] = {:response_handler => handler, :receive_time => Time.now}
|
|
827
|
+
ids = [id] if id
|
|
828
|
+
id = publish(request, ids).first
|
|
829
|
+
end
|
|
830
|
+
true
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
# Vote for restart and reset trigger
|
|
834
|
+
#
|
|
835
|
+
# === Parameters
|
|
836
|
+
# timer_trigger(Boolean):: true if vote was triggered by timer, false if it
|
|
837
|
+
# was triggered by number of messages in in-memory queue
|
|
838
|
+
#
|
|
839
|
+
# === Return
|
|
840
|
+
# true:: Always return true
|
|
841
|
+
def vote_to_restart(timer_trigger)
|
|
842
|
+
if restart_vote = @options[:restart_callback]
|
|
843
|
+
restart_vote.call
|
|
844
|
+
if timer_trigger
|
|
845
|
+
@restart_vote_timer = EM::Timer.new(RESTART_VOTE_DELAY) { vote_to_restart(timer_trigger = true) }
|
|
846
|
+
else
|
|
847
|
+
@restart_vote_count = 0
|
|
848
|
+
end
|
|
849
|
+
end
|
|
850
|
+
true
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
# Is agent currently offline?
|
|
854
|
+
#
|
|
855
|
+
# === Return
|
|
856
|
+
# offline(Boolean):: true if agent is disconnected or not initialized
|
|
857
|
+
def offline?
|
|
858
|
+
offline = @queueing_mode == :offline || !@queue_running
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
# Start timer that waits for inactive messaging period to end before checking connectivity
|
|
862
|
+
#
|
|
863
|
+
# === Return
|
|
864
|
+
# true:: Always return true
|
|
865
|
+
def restart_inactivity_timer
|
|
866
|
+
@timer.cancel if @timer
|
|
867
|
+
@timer = EM::Timer.new(@ping_interval) do
|
|
868
|
+
begin
|
|
869
|
+
check_connection
|
|
870
|
+
rescue Exception => e
|
|
871
|
+
Log.error("Failed connectivity check", e, :trace)
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
true
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
# Should agent be queueing current request?
|
|
878
|
+
#
|
|
879
|
+
# === Return
|
|
880
|
+
# (Boolean):: true if should queue request, otherwise false
|
|
881
|
+
def should_queue?
|
|
882
|
+
@options[:offline_queueing] && offline? && !@flushing_queue
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
# Queue given request in memory
|
|
886
|
+
#
|
|
887
|
+
# === Parameters
|
|
888
|
+
# request(Hash):: Request to be stored
|
|
889
|
+
#
|
|
890
|
+
# === Return
|
|
891
|
+
# true:: Always return true
|
|
892
|
+
def queue_request(request)
|
|
893
|
+
Log.info("[offline] Queuing request: #{request.inspect}")
|
|
894
|
+
@restart_vote_count += 1 if @queue_running
|
|
895
|
+
vote_to_restart(timer_trigger = false) if @restart_vote_count >= MAX_QUEUED_REQUESTS
|
|
896
|
+
if @queue_initializing
|
|
897
|
+
# We are in the initialization callback, requests should be put at the head of the queue
|
|
898
|
+
@queue.unshift(request)
|
|
899
|
+
else
|
|
900
|
+
@queue << request
|
|
901
|
+
end
|
|
902
|
+
true
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
# Flush in memory queue of requests that were stored while in offline mode
|
|
906
|
+
# Do this asynchronously to allow for agents to respond to requests
|
|
907
|
+
# Once all in-memory requests have been flushed, switch off offline mode
|
|
908
|
+
#
|
|
909
|
+
# === Return
|
|
910
|
+
# true:: Always return true
|
|
911
|
+
def flush_queue
|
|
912
|
+
if @stop_flushing_queue
|
|
913
|
+
@stop_flushing_queue = false
|
|
914
|
+
@flushing_queue = false
|
|
915
|
+
else
|
|
916
|
+
Log.info("[offline] Starting to flush request queue of size #{@queue.size}") unless @queueing_mode == :initializing
|
|
917
|
+
unless @queue.empty?
|
|
918
|
+
r = @queue.shift
|
|
919
|
+
if r[:callback]
|
|
920
|
+
Sender.instance.__send__(r[:kind], r[:type], r[:payload], r[:target]) { |res| r[:callback].call(res) }
|
|
921
|
+
else
|
|
922
|
+
Sender.instance.__send__(r[:kind], r[:type], r[:payload], r[:target])
|
|
923
|
+
end
|
|
924
|
+
end
|
|
925
|
+
if @queue.empty?
|
|
926
|
+
Log.info("[offline] Request queue flushed, resuming normal operations") unless @queueing_mode == :initializing
|
|
927
|
+
@queueing_mode = :online
|
|
928
|
+
@flushing_queue = false
|
|
929
|
+
else
|
|
930
|
+
EM.next_tick { flush_queue }
|
|
931
|
+
end
|
|
932
|
+
end
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
end # Sender
|
|
936
|
+
|
|
937
|
+
end # RightScale
|