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,66 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 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
|
+
|
|
24
|
+
module RightScale
|
|
25
|
+
|
|
26
|
+
# Class encapsulating the actual value of a credential.
|
|
27
|
+
class SecureDocument
|
|
28
|
+
|
|
29
|
+
include Serializable
|
|
30
|
+
|
|
31
|
+
# (String) Namespace-unique identifier for this credential value
|
|
32
|
+
attr_accessor :name
|
|
33
|
+
|
|
34
|
+
# (Integer) Monotonic version number of this document
|
|
35
|
+
attr_accessor :version
|
|
36
|
+
|
|
37
|
+
# The content of this document; if envelope_mime_type is not nil, then this value
|
|
38
|
+
# is actually an envelope that contains the document value
|
|
39
|
+
attr_accessor :content
|
|
40
|
+
|
|
41
|
+
# (String) Name of the storage policy used to encrypt this document (if any).
|
|
42
|
+
attr_accessor :storage_policy_name
|
|
43
|
+
|
|
44
|
+
# (String) MIME type of the document itself e.g. "text/plain"
|
|
45
|
+
attr_accessor :mime_type
|
|
46
|
+
|
|
47
|
+
# (String) MIME type of the value's envelope, e.g. an encoding or encryption format,
|
|
48
|
+
# or nil if there is no envelope MIME type.
|
|
49
|
+
attr_accessor :envelope_mime_type
|
|
50
|
+
|
|
51
|
+
# Initialize fields from given arguments
|
|
52
|
+
def initialize(*args)
|
|
53
|
+
@name = args[0] if args.size > 0
|
|
54
|
+
@version = args[1] if args.size > 1
|
|
55
|
+
@content = args[2] if args.size > 2
|
|
56
|
+
@storage_policy_name = args[3] if args.size > 3
|
|
57
|
+
@mime_type = args[4] if args.size > 4
|
|
58
|
+
@envelope_mime_type = args[5] if args.size > 5
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Array of serialized fields given to constructor
|
|
62
|
+
def serialized_members
|
|
63
|
+
[ @name, @version, @content, @storage_policy_name, @mime_type, @envelope_mime_type ]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 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
|
+
|
|
24
|
+
module RightScale
|
|
25
|
+
|
|
26
|
+
# Everything necessary to retrieve a SecureDocument from a RightNet agent.
|
|
27
|
+
class SecureDocumentLocation
|
|
28
|
+
|
|
29
|
+
include Serializable
|
|
30
|
+
|
|
31
|
+
# (String):: Namespace within which the document resides
|
|
32
|
+
attr_accessor :namespace
|
|
33
|
+
|
|
34
|
+
# (String):: Namespace-unique identifier of the document that should be retrieved.
|
|
35
|
+
attr_accessor :name
|
|
36
|
+
|
|
37
|
+
# (Integer):: Monotonic version of the document to be requested
|
|
38
|
+
attr_accessor :version
|
|
39
|
+
|
|
40
|
+
# (String):: Access token that should be used to fetch the document
|
|
41
|
+
attr_accessor :ticket
|
|
42
|
+
|
|
43
|
+
# Array(String):: names of RightNet agents capable of providing the document.
|
|
44
|
+
# If nil, then no target should be specified when sending RightNet requests;
|
|
45
|
+
# this is used to route requests to "trusted infrastructure" nodes at the
|
|
46
|
+
# discretion of the mapper.
|
|
47
|
+
attr_accessor :targets
|
|
48
|
+
|
|
49
|
+
# Initialize fields from given arguments
|
|
50
|
+
def initialize(*args)
|
|
51
|
+
@namespace = args[0] if args.size > 0
|
|
52
|
+
@name = args[1] if args.size > 1
|
|
53
|
+
@version = args[2] if args.size > 2
|
|
54
|
+
@ticket = args[3] if args.size > 3
|
|
55
|
+
@targets = args[4] if args.size > 4
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Array of serialized fields given to constructor
|
|
59
|
+
def serialized_members
|
|
60
|
+
[ @namespace, @name, @version, @ticket, @targets ]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
|
|
24
|
+
module RightScale
|
|
25
|
+
|
|
26
|
+
# Software repository
|
|
27
|
+
# May or may not be frozen depending on whether frozen_date is set
|
|
28
|
+
class SoftwareRepositoryInstantiation
|
|
29
|
+
|
|
30
|
+
include Serializable
|
|
31
|
+
|
|
32
|
+
# (String) Software repository name
|
|
33
|
+
attr_accessor :name
|
|
34
|
+
|
|
35
|
+
# (Array) Software repository base URL
|
|
36
|
+
attr_accessor :base_urls
|
|
37
|
+
|
|
38
|
+
# (Date) Frozen date if any
|
|
39
|
+
attr_accessor :frozen_date
|
|
40
|
+
|
|
41
|
+
def initialize(*args)
|
|
42
|
+
@name = args[0] if args.size > 0
|
|
43
|
+
@base_urls = args[1] if args.size > 1
|
|
44
|
+
@frozen_date = args[2] if args.size > 2
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Human readable representation
|
|
48
|
+
#
|
|
49
|
+
# === Return
|
|
50
|
+
# Text representing repository instantiation that can be audited
|
|
51
|
+
def to_s
|
|
52
|
+
res = "#{name} #{base_urls.inspect}"
|
|
53
|
+
frozen_date ? res + " @ #{frozen_date.to_s}" : res
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Array of serialized fields given to constructor
|
|
57
|
+
def serialized_members
|
|
58
|
+
[ @name, @base_urls, @frozen_date ]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
module DaemonizeHelper
|
|
25
|
+
def daemonize(identity, options = {})
|
|
26
|
+
exit if fork
|
|
27
|
+
Process.setsid
|
|
28
|
+
File.umask 0022
|
|
29
|
+
exit if fork
|
|
30
|
+
STDIN.reopen "/dev/null"
|
|
31
|
+
STDOUT.reopen "#{options[:log_path]}/#{identity}.out", "a"
|
|
32
|
+
STDERR.reopen "#{options[:log_path]}/#{identity}.err", "a"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,348 @@
|
|
|
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
|
+
# Dispatching of payload to specified actor
|
|
26
|
+
class Dispatcher
|
|
27
|
+
|
|
28
|
+
include StatsHelper
|
|
29
|
+
|
|
30
|
+
# Cache for requests that have been dispatched recently
|
|
31
|
+
# This cache is intended for use in checking for duplicate requests
|
|
32
|
+
class Dispatched
|
|
33
|
+
|
|
34
|
+
# Maximum number of seconds to retain a dispatched request in cache
|
|
35
|
+
# This must be greater than the maximum possible retry timeout to avoid
|
|
36
|
+
# duplicate execution of a request
|
|
37
|
+
MAX_AGE = 12 * 60 * 60
|
|
38
|
+
|
|
39
|
+
# Initialize cache
|
|
40
|
+
def initialize
|
|
41
|
+
@cache = {}
|
|
42
|
+
@lru = []
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Store dispatched request token in cache
|
|
46
|
+
#
|
|
47
|
+
# === Parameters
|
|
48
|
+
# token(String):: Generated message identifier
|
|
49
|
+
#
|
|
50
|
+
# === Return
|
|
51
|
+
# true:: Always return true
|
|
52
|
+
def store(token)
|
|
53
|
+
now ||= Time.now.to_i
|
|
54
|
+
if @cache.has_key?(token)
|
|
55
|
+
@cache[token] = now
|
|
56
|
+
@lru.push(@lru.delete(token))
|
|
57
|
+
else
|
|
58
|
+
@cache[token] = now
|
|
59
|
+
@lru.push(token)
|
|
60
|
+
@cache.delete(@lru.shift) while (now - @cache[@lru.first]) > MAX_AGE
|
|
61
|
+
end
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Fetch request
|
|
66
|
+
#
|
|
67
|
+
# === Parameters
|
|
68
|
+
# token(String):: Generated message identifier
|
|
69
|
+
#
|
|
70
|
+
# === Return
|
|
71
|
+
# (Boolean):: true if request has been dispatched, otherwise false
|
|
72
|
+
def fetch(token)
|
|
73
|
+
if @cache[token]
|
|
74
|
+
@cache[token] = Time.now.to_i
|
|
75
|
+
@lru.push(@lru.delete(token))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get cache size
|
|
80
|
+
#
|
|
81
|
+
# === Return
|
|
82
|
+
# (Integer):: Number of cache entries
|
|
83
|
+
def size
|
|
84
|
+
@cache.size
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Get cache statistics
|
|
88
|
+
#
|
|
89
|
+
# === Return
|
|
90
|
+
# stats(Hash|nil):: Current statistics, or nil if cache empty
|
|
91
|
+
# "total"(Integer):: Total number in cache, or nil if none
|
|
92
|
+
# "oldest"(Integer):: Number of seconds since oldest cache entry created or updated
|
|
93
|
+
# "youngest"(Integer):: Number of seconds since youngest cache entry created or updated
|
|
94
|
+
def stats
|
|
95
|
+
if size > 0
|
|
96
|
+
{
|
|
97
|
+
"total" => size,
|
|
98
|
+
"oldest age" => size > 0 ? Time.now.to_i - @cache[@lru.first] : 0,
|
|
99
|
+
"youngest age" => size > 0 ? Time.now.to_i - @cache[@lru.last] : 0
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end # Dispatched
|
|
105
|
+
|
|
106
|
+
# (ActorRegistry) Registry for actors
|
|
107
|
+
attr_reader :registry
|
|
108
|
+
|
|
109
|
+
# (String) Identity of associated agent
|
|
110
|
+
attr_reader :identity
|
|
111
|
+
|
|
112
|
+
# (HABrokerClient) High availability AMQP broker client
|
|
113
|
+
attr_reader :broker
|
|
114
|
+
|
|
115
|
+
# (EM) Event machine class (exposed for unit tests)
|
|
116
|
+
attr_accessor :em
|
|
117
|
+
|
|
118
|
+
# Initialize dispatcher
|
|
119
|
+
#
|
|
120
|
+
# === Parameters
|
|
121
|
+
# agent(Agent):: Agent using this dispatcher; uses its identity, broker, registry, and following options:
|
|
122
|
+
# :dup_check(Boolean):: Whether to check for and reject duplicate requests, e.g., due to retries,
|
|
123
|
+
# but only for requests that are dispatched from non-shared queues
|
|
124
|
+
# :secure(Boolean):: true indicates to use Security features of RabbitMQ to restrict agents to themselves
|
|
125
|
+
# :single_threaded(Boolean):: true indicates to run all operations in one thread; false indicates
|
|
126
|
+
# to do requested work on event machine defer thread and all else, such as pings on main thread
|
|
127
|
+
# :threadpool_size(Integer):: Number of threads in event machine thread pool
|
|
128
|
+
def initialize(agent)
|
|
129
|
+
@agent = agent
|
|
130
|
+
@broker = @agent.broker
|
|
131
|
+
@registry = @agent.registry
|
|
132
|
+
@identity = @agent.identity
|
|
133
|
+
options = @agent.options
|
|
134
|
+
@secure = options[:secure]
|
|
135
|
+
@single_threaded = options[:single_threaded]
|
|
136
|
+
@dup_check = options[:dup_check]
|
|
137
|
+
@pending_dispatches = 0
|
|
138
|
+
@em = EM
|
|
139
|
+
@em.threadpool_size = (options[:threadpool_size] || 20).to_i
|
|
140
|
+
reset_stats
|
|
141
|
+
|
|
142
|
+
# Only access following from primary thread
|
|
143
|
+
@dispatched = Dispatched.new if @dup_check
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Dispatch request to appropriate actor for servicing
|
|
147
|
+
# Handle returning of result to requester including logging any exceptions
|
|
148
|
+
# Reject requests whose TTL has expired or that are duplicates of work already dispatched
|
|
149
|
+
# but do not do duplicate checking if being dispatched from a shared queue
|
|
150
|
+
# Work is done in background defer thread if single threaded option is false
|
|
151
|
+
#
|
|
152
|
+
# === Parameters
|
|
153
|
+
# request(Request|Push):: Packet containing request
|
|
154
|
+
# shared(Boolean):: Whether being dispatched from a shared queue
|
|
155
|
+
#
|
|
156
|
+
# === Return
|
|
157
|
+
# r(Result):: Result from dispatched request, nil if not dispatched because dup or stale
|
|
158
|
+
def dispatch(request, shared = false)
|
|
159
|
+
|
|
160
|
+
# Determine which actor this request is for
|
|
161
|
+
prefix, method = request.type.split('/')[1..-1]
|
|
162
|
+
method ||= :index
|
|
163
|
+
actor = @registry.actor_for(prefix)
|
|
164
|
+
token = request.token
|
|
165
|
+
received_at = @requests.update(method, (token if request.kind_of?(Request)))
|
|
166
|
+
if actor.nil?
|
|
167
|
+
Log.error("No actor for dispatching request <#{request.token}> of type #{request.type}")
|
|
168
|
+
return nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Reject this request if its TTL has expired
|
|
172
|
+
if (expires_at = request.expires_at) && expires_at > 0 && received_at.to_i >= expires_at
|
|
173
|
+
@rejects.update("expired (#{method})")
|
|
174
|
+
Log.info("REJECT EXPIRED <#{token}> from #{request.from} TTL #{elapsed(received_at.to_i - expires_at)} ago")
|
|
175
|
+
if request.is_a?(Request)
|
|
176
|
+
# For agents that do not know about non-delivery, use error result
|
|
177
|
+
non_delivery = if request.recv_version < 13
|
|
178
|
+
OperationResult.error("Could not deliver request (#{OperationResult::TTL_EXPIRATION})")
|
|
179
|
+
else
|
|
180
|
+
OperationResult.non_delivery(OperationResult::TTL_EXPIRATION)
|
|
181
|
+
end
|
|
182
|
+
result = Result.new(token, request.reply_to, non_delivery, @identity, request.from, request.tries, request.persistent)
|
|
183
|
+
exchange = {:type => :queue, :name => request.reply_to, :options => {:durable => true, :no_declare => @secure}}
|
|
184
|
+
@broker.publish(exchange, result, :persistent => true, :mandatory => true)
|
|
185
|
+
end
|
|
186
|
+
return nil
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Reject this request if it is a duplicate
|
|
190
|
+
if @dup_check && !shared && request.kind_of?(Request)
|
|
191
|
+
if @dispatched.fetch(token)
|
|
192
|
+
@rejects.update("duplicate (#{method})")
|
|
193
|
+
Log.info("REJECT DUP <#{token}> of self")
|
|
194
|
+
return nil
|
|
195
|
+
end
|
|
196
|
+
request.tries.each do |t|
|
|
197
|
+
if @dispatched.fetch(t)
|
|
198
|
+
@rejects.update("retry duplicate (#{method})")
|
|
199
|
+
Log.info("REJECT RETRY DUP <#{token}> of <#{t}>")
|
|
200
|
+
return nil
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Proc for performing request in actor
|
|
206
|
+
operation = lambda do
|
|
207
|
+
begin
|
|
208
|
+
@pending_dispatches += 1
|
|
209
|
+
@last_request_dispatch_time = received_at.to_i
|
|
210
|
+
@dispatched.store(token) if @dup_check && !shared && request.kind_of?(Request) && token
|
|
211
|
+
actor.__send__(method, request.payload)
|
|
212
|
+
rescue Exception => e
|
|
213
|
+
@pending_dispatches = [@pending_dispatches - 1, 0].max
|
|
214
|
+
handle_exception(actor, method, request, e)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Proc for sending response
|
|
219
|
+
callback = lambda do |r|
|
|
220
|
+
begin
|
|
221
|
+
@pending_dispatches = [@pending_dispatches - 1, 0].max
|
|
222
|
+
if request.kind_of?(Request)
|
|
223
|
+
duration = @requests.finish(received_at, token)
|
|
224
|
+
r = Result.new(token, request.reply_to, r, @identity, request.from, request.tries, request.persistent, duration)
|
|
225
|
+
exchange = {:type => :queue, :name => request.reply_to, :options => {:durable => true, :no_declare => @secure}}
|
|
226
|
+
@broker.publish(exchange, r, :persistent => true, :mandatory => true, :log_filter => [:tries, :persistent, :duration])
|
|
227
|
+
end
|
|
228
|
+
rescue HABrokerClient::NoConnectedBrokers => e
|
|
229
|
+
Log.error("Failed to publish result of dispatched request #{request.trace}", e)
|
|
230
|
+
rescue Exception => e
|
|
231
|
+
Log.error("Failed to publish result of dispatched request #{request.trace}", e, :trace)
|
|
232
|
+
@exceptions.track("publish response", e)
|
|
233
|
+
end
|
|
234
|
+
r # For unit tests
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Process request and send response, if any
|
|
238
|
+
if @single_threaded
|
|
239
|
+
@em.next_tick { callback.call(operation.call) }
|
|
240
|
+
else
|
|
241
|
+
@em.defer(operation, callback)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Determine age of youngest request dispatch
|
|
246
|
+
#
|
|
247
|
+
# === Return
|
|
248
|
+
# (Integer|nil):: Age in seconds of youngest dispatch, or nil if none
|
|
249
|
+
def dispatch_age
|
|
250
|
+
age = Time.now.to_i - @last_request_dispatch_time if @last_request_dispatch_time && @pending_dispatches > 0
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Get dispatcher statistics
|
|
254
|
+
#
|
|
255
|
+
# === Parameters
|
|
256
|
+
# reset(Boolean):: Whether to reset the statistics after getting the current ones
|
|
257
|
+
#
|
|
258
|
+
# === Return
|
|
259
|
+
# stats(Hash):: Current statistics:
|
|
260
|
+
# "cached"(Hash|nil):: Number of dispatched requests cached and age of youngest and oldest,
|
|
261
|
+
# or nil if empty
|
|
262
|
+
# "exceptions"(Hash|nil):: Exceptions raised per category, or nil if none
|
|
263
|
+
# "total"(Integer):: Total for category
|
|
264
|
+
# "recent"(Array):: Most recent as a hash of "count", "type", "message", "when", and "where"
|
|
265
|
+
# "rejects"(Hash|nil):: Request reject activity stats with keys "total", "percent", "last", and "rate"
|
|
266
|
+
# "pending"(Hash|nil):: Pending request "total" and "youngest age", or nil if none
|
|
267
|
+
# with percentage breakdown per reason ("duplicate (<method>)", "retry duplicate (<method>)", or
|
|
268
|
+
# "stale (<method>)"), or nil if none
|
|
269
|
+
# "requests"(Hash|nil):: Request activity stats with keys "total", "percent", "last", and "rate"
|
|
270
|
+
# with percentage breakdown per request type, or nil if none
|
|
271
|
+
# "response time"(Float):: Average number of seconds to respond to a request recently
|
|
272
|
+
def stats(reset = false)
|
|
273
|
+
pending = if @pending_dispatches > 0
|
|
274
|
+
{
|
|
275
|
+
"total" => @pending_dispatches,
|
|
276
|
+
"youngest age" => dispatch_age
|
|
277
|
+
}
|
|
278
|
+
end
|
|
279
|
+
stats = {
|
|
280
|
+
"cached" => (@dispatched.stats if @dup_check),
|
|
281
|
+
"exceptions" => @exceptions.stats,
|
|
282
|
+
"pending" => pending,
|
|
283
|
+
"rejects" => @rejects.all,
|
|
284
|
+
"requests" => @requests.all,
|
|
285
|
+
"response time" => @requests.avg_duration
|
|
286
|
+
}
|
|
287
|
+
reset_stats if reset
|
|
288
|
+
stats
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
private
|
|
292
|
+
|
|
293
|
+
# Reset dispatch statistics
|
|
294
|
+
#
|
|
295
|
+
# === Return
|
|
296
|
+
# true:: Always return true
|
|
297
|
+
def reset_stats
|
|
298
|
+
@rejects = ActivityStats.new
|
|
299
|
+
@requests = ActivityStats.new
|
|
300
|
+
@exceptions = ExceptionStats.new(@agent)
|
|
301
|
+
true
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Handle exception by logging it, calling the actors exception callback method,
|
|
305
|
+
# and gathering exception statistics
|
|
306
|
+
#
|
|
307
|
+
# === Parameters
|
|
308
|
+
# actor(Actor):: Actor that failed to process request
|
|
309
|
+
# method(String):: Name of actor method being dispatched to
|
|
310
|
+
# request(Packet):: Packet that dispatcher is acting upon
|
|
311
|
+
# e(Exception):: Exception that was raised
|
|
312
|
+
#
|
|
313
|
+
# === Return
|
|
314
|
+
# error(String):: Error description for this exception
|
|
315
|
+
def handle_exception(actor, method, request, e)
|
|
316
|
+
error = Log.format("Failed processing #{request.type}", e, :trace)
|
|
317
|
+
Log.error(error)
|
|
318
|
+
begin
|
|
319
|
+
if actor && actor.class.exception_callback
|
|
320
|
+
case actor.class.exception_callback
|
|
321
|
+
when Symbol, String
|
|
322
|
+
actor.send(actor.class.exception_callback, method.to_sym, request, e)
|
|
323
|
+
when Proc
|
|
324
|
+
actor.instance_exec(method.to_sym, request, e, &actor.class.exception_callback)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
@exceptions.track(request.type, e)
|
|
328
|
+
rescue Exception => e2
|
|
329
|
+
Log.error("Failed handling error for #{request.type}", e2, :trace)
|
|
330
|
+
@exceptions.track(request.type, e2) rescue nil
|
|
331
|
+
end
|
|
332
|
+
error
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Convert value to nil if equals 0
|
|
336
|
+
#
|
|
337
|
+
# === Parameters
|
|
338
|
+
# value(Integer|nil):: Value to be converted
|
|
339
|
+
#
|
|
340
|
+
# === Return
|
|
341
|
+
# (Integer|nil):: Converted value
|
|
342
|
+
def nil_if_zero(value)
|
|
343
|
+
if !value || value == 0 then nil else value end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
end # Dispatcher
|
|
347
|
+
|
|
348
|
+
end # RightScale
|