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.
Files changed (147) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +78 -0
  3. data/Rakefile +86 -0
  4. data/lib/right_agent.rb +66 -0
  5. data/lib/right_agent/actor.rb +163 -0
  6. data/lib/right_agent/actor_registry.rb +76 -0
  7. data/lib/right_agent/actors/agent_manager.rb +189 -0
  8. data/lib/right_agent/agent.rb +735 -0
  9. data/lib/right_agent/agent_config.rb +403 -0
  10. data/lib/right_agent/agent_identity.rb +209 -0
  11. data/lib/right_agent/agent_tags_manager.rb +213 -0
  12. data/lib/right_agent/audit_formatter.rb +107 -0
  13. data/lib/right_agent/broker_client.rb +683 -0
  14. data/lib/right_agent/command.rb +30 -0
  15. data/lib/right_agent/command/agent_manager_commands.rb +134 -0
  16. data/lib/right_agent/command/command_client.rb +136 -0
  17. data/lib/right_agent/command/command_constants.rb +42 -0
  18. data/lib/right_agent/command/command_io.rb +128 -0
  19. data/lib/right_agent/command/command_parser.rb +87 -0
  20. data/lib/right_agent/command/command_runner.rb +105 -0
  21. data/lib/right_agent/command/command_serializer.rb +63 -0
  22. data/lib/right_agent/console.rb +65 -0
  23. data/lib/right_agent/core_payload_types.rb +42 -0
  24. data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
  25. data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
  26. data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
  27. data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
  28. data/lib/right_agent/core_payload_types/dev_repositories.rb +90 -0
  29. data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
  30. data/lib/right_agent/core_payload_types/executable_bundle.rb +138 -0
  31. data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
  32. data/lib/right_agent/core_payload_types/login_user.rb +62 -0
  33. data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
  34. data/lib/right_agent/core_payload_types/recipe_instantiation.rb +60 -0
  35. data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
  36. data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
  37. data/lib/right_agent/core_payload_types/right_script_instantiation.rb +73 -0
  38. data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
  39. data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
  40. data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
  41. data/lib/right_agent/daemonize.rb +35 -0
  42. data/lib/right_agent/dispatcher.rb +348 -0
  43. data/lib/right_agent/enrollment_result.rb +217 -0
  44. data/lib/right_agent/exceptions.rb +30 -0
  45. data/lib/right_agent/ha_broker_client.rb +1278 -0
  46. data/lib/right_agent/idempotent_request.rb +140 -0
  47. data/lib/right_agent/log.rb +418 -0
  48. data/lib/right_agent/monkey_patches.rb +29 -0
  49. data/lib/right_agent/monkey_patches/amqp_patch.rb +274 -0
  50. data/lib/right_agent/monkey_patches/ruby_patch.rb +49 -0
  51. data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
  52. data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
  53. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
  54. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
  55. data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
  56. data/lib/right_agent/monkey_patches/ruby_patch/singleton_patch.rb +46 -0
  57. data/lib/right_agent/monkey_patches/ruby_patch/string_patch.rb +107 -0
  58. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
  59. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +90 -0
  60. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
  61. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
  62. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
  63. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
  64. data/lib/right_agent/multiplexer.rb +91 -0
  65. data/lib/right_agent/operation_result.rb +270 -0
  66. data/lib/right_agent/packets.rb +637 -0
  67. data/lib/right_agent/payload_formatter.rb +104 -0
  68. data/lib/right_agent/pid_file.rb +159 -0
  69. data/lib/right_agent/platform.rb +319 -0
  70. data/lib/right_agent/platform/darwin.rb +227 -0
  71. data/lib/right_agent/platform/linux.rb +268 -0
  72. data/lib/right_agent/platform/windows.rb +1204 -0
  73. data/lib/right_agent/scripts/agent_controller.rb +522 -0
  74. data/lib/right_agent/scripts/agent_deployer.rb +379 -0
  75. data/lib/right_agent/scripts/common_parser.rb +153 -0
  76. data/lib/right_agent/scripts/log_level_manager.rb +193 -0
  77. data/lib/right_agent/scripts/stats_manager.rb +256 -0
  78. data/lib/right_agent/scripts/usage.rb +58 -0
  79. data/lib/right_agent/secure_identity.rb +92 -0
  80. data/lib/right_agent/security.rb +32 -0
  81. data/lib/right_agent/security/cached_certificate_store_proxy.rb +63 -0
  82. data/lib/right_agent/security/certificate.rb +102 -0
  83. data/lib/right_agent/security/certificate_cache.rb +89 -0
  84. data/lib/right_agent/security/distinguished_name.rb +56 -0
  85. data/lib/right_agent/security/encrypted_document.rb +84 -0
  86. data/lib/right_agent/security/rsa_key_pair.rb +76 -0
  87. data/lib/right_agent/security/signature.rb +86 -0
  88. data/lib/right_agent/security/static_certificate_store.rb +69 -0
  89. data/lib/right_agent/sender.rb +937 -0
  90. data/lib/right_agent/serialize.rb +29 -0
  91. data/lib/right_agent/serialize/message_pack.rb +102 -0
  92. data/lib/right_agent/serialize/secure_serializer.rb +131 -0
  93. data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
  94. data/lib/right_agent/serialize/serializable.rb +135 -0
  95. data/lib/right_agent/serialize/serializer.rb +149 -0
  96. data/lib/right_agent/stats_helper.rb +731 -0
  97. data/lib/right_agent/subprocess.rb +38 -0
  98. data/lib/right_agent/tracer.rb +124 -0
  99. data/right_agent.gemspec +60 -0
  100. data/spec/actor_registry_spec.rb +81 -0
  101. data/spec/actor_spec.rb +99 -0
  102. data/spec/agent_config_spec.rb +226 -0
  103. data/spec/agent_identity_spec.rb +75 -0
  104. data/spec/agent_spec.rb +571 -0
  105. data/spec/broker_client_spec.rb +961 -0
  106. data/spec/command/agent_manager_commands_spec.rb +51 -0
  107. data/spec/command/command_io_spec.rb +93 -0
  108. data/spec/command/command_parser_spec.rb +79 -0
  109. data/spec/command/command_runner_spec.rb +72 -0
  110. data/spec/command/command_serializer_spec.rb +51 -0
  111. data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
  112. data/spec/core_payload_types/executable_bundle_spec.rb +59 -0
  113. data/spec/core_payload_types/login_user_spec.rb +98 -0
  114. data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
  115. data/spec/core_payload_types/spec_helper.rb +23 -0
  116. data/spec/dispatcher_spec.rb +372 -0
  117. data/spec/enrollment_result_spec.rb +53 -0
  118. data/spec/ha_broker_client_spec.rb +1673 -0
  119. data/spec/idempotent_request_spec.rb +136 -0
  120. data/spec/log_spec.rb +177 -0
  121. data/spec/monkey_patches/amqp_patch_spec.rb +100 -0
  122. data/spec/monkey_patches/eventmachine_spec.rb +62 -0
  123. data/spec/monkey_patches/string_patch_spec.rb +99 -0
  124. data/spec/multiplexer_spec.rb +48 -0
  125. data/spec/operation_result_spec.rb +171 -0
  126. data/spec/packets_spec.rb +418 -0
  127. data/spec/platform/platform_spec.rb +60 -0
  128. data/spec/results_mock.rb +45 -0
  129. data/spec/secure_identity_spec.rb +50 -0
  130. data/spec/security/cached_certificate_store_proxy_spec.rb +56 -0
  131. data/spec/security/certificate_cache_spec.rb +71 -0
  132. data/spec/security/certificate_spec.rb +49 -0
  133. data/spec/security/distinguished_name_spec.rb +46 -0
  134. data/spec/security/encrypted_document_spec.rb +55 -0
  135. data/spec/security/rsa_key_pair_spec.rb +55 -0
  136. data/spec/security/signature_spec.rb +66 -0
  137. data/spec/security/static_certificate_store_spec.rb +52 -0
  138. data/spec/sender_spec.rb +887 -0
  139. data/spec/serialize/message_pack_spec.rb +131 -0
  140. data/spec/serialize/secure_serializer_spec.rb +102 -0
  141. data/spec/serialize/serializable_spec.rb +90 -0
  142. data/spec/serialize/serializer_spec.rb +174 -0
  143. data/spec/spec.opts +2 -0
  144. data/spec/spec_helper.rb +77 -0
  145. data/spec/stats_helper_spec.rb +681 -0
  146. data/spec/tracer_spec.rb +114 -0
  147. 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