right_agent 2.0.7-x86-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +82 -0
  3. data/Rakefile +113 -0
  4. data/lib/right_agent.rb +59 -0
  5. data/lib/right_agent/actor.rb +182 -0
  6. data/lib/right_agent/actor_registry.rb +76 -0
  7. data/lib/right_agent/actors/agent_manager.rb +232 -0
  8. data/lib/right_agent/agent.rb +1149 -0
  9. data/lib/right_agent/agent_config.rb +480 -0
  10. data/lib/right_agent/agent_identity.rb +210 -0
  11. data/lib/right_agent/agent_tag_manager.rb +237 -0
  12. data/lib/right_agent/audit_formatter.rb +107 -0
  13. data/lib/right_agent/clients.rb +31 -0
  14. data/lib/right_agent/clients/api_client.rb +383 -0
  15. data/lib/right_agent/clients/auth_client.rb +247 -0
  16. data/lib/right_agent/clients/balanced_http_client.rb +369 -0
  17. data/lib/right_agent/clients/base_retry_client.rb +495 -0
  18. data/lib/right_agent/clients/right_http_client.rb +279 -0
  19. data/lib/right_agent/clients/router_client.rb +493 -0
  20. data/lib/right_agent/command.rb +30 -0
  21. data/lib/right_agent/command/agent_manager_commands.rb +150 -0
  22. data/lib/right_agent/command/command_client.rb +136 -0
  23. data/lib/right_agent/command/command_constants.rb +33 -0
  24. data/lib/right_agent/command/command_io.rb +126 -0
  25. data/lib/right_agent/command/command_parser.rb +87 -0
  26. data/lib/right_agent/command/command_runner.rb +118 -0
  27. data/lib/right_agent/command/command_serializer.rb +63 -0
  28. data/lib/right_agent/connectivity_checker.rb +179 -0
  29. data/lib/right_agent/console.rb +65 -0
  30. data/lib/right_agent/core_payload_types.rb +44 -0
  31. data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
  32. data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
  33. data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
  34. data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
  35. data/lib/right_agent/core_payload_types/dev_repositories.rb +100 -0
  36. data/lib/right_agent/core_payload_types/dev_repository.rb +76 -0
  37. data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
  38. data/lib/right_agent/core_payload_types/executable_bundle.rb +130 -0
  39. data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
  40. data/lib/right_agent/core_payload_types/login_user.rb +79 -0
  41. data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
  42. data/lib/right_agent/core_payload_types/recipe_instantiation.rb +73 -0
  43. data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
  44. data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
  45. data/lib/right_agent/core_payload_types/right_script_instantiation.rb +94 -0
  46. data/lib/right_agent/core_payload_types/runlist_policy.rb +44 -0
  47. data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
  48. data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
  49. data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
  50. data/lib/right_agent/daemonize.rb +35 -0
  51. data/lib/right_agent/dispatched_cache.rb +109 -0
  52. data/lib/right_agent/dispatcher.rb +272 -0
  53. data/lib/right_agent/enrollment_result.rb +221 -0
  54. data/lib/right_agent/exceptions.rb +87 -0
  55. data/lib/right_agent/history.rb +145 -0
  56. data/lib/right_agent/log.rb +460 -0
  57. data/lib/right_agent/minimal.rb +46 -0
  58. data/lib/right_agent/monkey_patches.rb +30 -0
  59. data/lib/right_agent/monkey_patches/ruby_patch.rb +55 -0
  60. data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
  61. data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
  62. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
  63. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
  64. data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
  65. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
  66. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +60 -0
  67. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
  68. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
  69. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
  70. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
  71. data/lib/right_agent/multiplexer.rb +102 -0
  72. data/lib/right_agent/offline_handler.rb +270 -0
  73. data/lib/right_agent/operation_result.rb +300 -0
  74. data/lib/right_agent/packets.rb +673 -0
  75. data/lib/right_agent/payload_formatter.rb +104 -0
  76. data/lib/right_agent/pending_requests.rb +128 -0
  77. data/lib/right_agent/pid_file.rb +159 -0
  78. data/lib/right_agent/platform.rb +770 -0
  79. data/lib/right_agent/platform/unix/darwin/platform.rb +102 -0
  80. data/lib/right_agent/platform/unix/linux/platform.rb +305 -0
  81. data/lib/right_agent/platform/unix/platform.rb +226 -0
  82. data/lib/right_agent/platform/windows/mingw/platform.rb +447 -0
  83. data/lib/right_agent/platform/windows/mswin/platform.rb +236 -0
  84. data/lib/right_agent/platform/windows/platform.rb +1808 -0
  85. data/lib/right_agent/protocol_version_mixin.rb +69 -0
  86. data/lib/right_agent/retryable_request.rb +195 -0
  87. data/lib/right_agent/scripts/agent_controller.rb +543 -0
  88. data/lib/right_agent/scripts/agent_deployer.rb +400 -0
  89. data/lib/right_agent/scripts/common_parser.rb +160 -0
  90. data/lib/right_agent/scripts/log_level_manager.rb +192 -0
  91. data/lib/right_agent/scripts/stats_manager.rb +268 -0
  92. data/lib/right_agent/scripts/usage.rb +58 -0
  93. data/lib/right_agent/secure_identity.rb +92 -0
  94. data/lib/right_agent/security.rb +32 -0
  95. data/lib/right_agent/security/cached_certificate_store_proxy.rb +77 -0
  96. data/lib/right_agent/security/certificate.rb +102 -0
  97. data/lib/right_agent/security/certificate_cache.rb +89 -0
  98. data/lib/right_agent/security/distinguished_name.rb +56 -0
  99. data/lib/right_agent/security/encrypted_document.rb +83 -0
  100. data/lib/right_agent/security/rsa_key_pair.rb +76 -0
  101. data/lib/right_agent/security/signature.rb +86 -0
  102. data/lib/right_agent/security/static_certificate_store.rb +85 -0
  103. data/lib/right_agent/sender.rb +792 -0
  104. data/lib/right_agent/serialize.rb +29 -0
  105. data/lib/right_agent/serialize/message_pack.rb +107 -0
  106. data/lib/right_agent/serialize/secure_serializer.rb +151 -0
  107. data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
  108. data/lib/right_agent/serialize/serializable.rb +151 -0
  109. data/lib/right_agent/serialize/serializer.rb +159 -0
  110. data/lib/right_agent/subprocess.rb +38 -0
  111. data/lib/right_agent/tracer.rb +124 -0
  112. data/right_agent.gemspec +101 -0
  113. data/spec/actor_registry_spec.rb +80 -0
  114. data/spec/actor_spec.rb +162 -0
  115. data/spec/agent_config_spec.rb +235 -0
  116. data/spec/agent_identity_spec.rb +78 -0
  117. data/spec/agent_spec.rb +734 -0
  118. data/spec/agent_tag_manager_spec.rb +319 -0
  119. data/spec/clients/api_client_spec.rb +423 -0
  120. data/spec/clients/auth_client_spec.rb +272 -0
  121. data/spec/clients/balanced_http_client_spec.rb +576 -0
  122. data/spec/clients/base_retry_client_spec.rb +635 -0
  123. data/spec/clients/router_client_spec.rb +594 -0
  124. data/spec/clients/spec_helper.rb +111 -0
  125. data/spec/command/agent_manager_commands_spec.rb +51 -0
  126. data/spec/command/command_io_spec.rb +93 -0
  127. data/spec/command/command_parser_spec.rb +79 -0
  128. data/spec/command/command_runner_spec.rb +107 -0
  129. data/spec/command/command_serializer_spec.rb +51 -0
  130. data/spec/connectivity_checker_spec.rb +83 -0
  131. data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
  132. data/spec/core_payload_types/dev_repository_spec.rb +33 -0
  133. data/spec/core_payload_types/executable_bundle_spec.rb +67 -0
  134. data/spec/core_payload_types/login_user_spec.rb +102 -0
  135. data/spec/core_payload_types/recipe_instantiation_spec.rb +81 -0
  136. data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
  137. data/spec/core_payload_types/right_script_instantiation_spec.rb +79 -0
  138. data/spec/core_payload_types/spec_helper.rb +23 -0
  139. data/spec/dispatched_cache_spec.rb +136 -0
  140. data/spec/dispatcher_spec.rb +324 -0
  141. data/spec/enrollment_result_spec.rb +53 -0
  142. data/spec/history_spec.rb +246 -0
  143. data/spec/log_spec.rb +192 -0
  144. data/spec/monkey_patches/eventmachine_spec.rb +62 -0
  145. data/spec/multiplexer_spec.rb +48 -0
  146. data/spec/offline_handler_spec.rb +340 -0
  147. data/spec/operation_result_spec.rb +208 -0
  148. data/spec/packets_spec.rb +461 -0
  149. data/spec/pending_requests_spec.rb +136 -0
  150. data/spec/platform/spec_helper.rb +216 -0
  151. data/spec/platform/unix/darwin/platform_spec.rb +181 -0
  152. data/spec/platform/unix/linux/platform_spec.rb +540 -0
  153. data/spec/platform/unix/spec_helper.rb +149 -0
  154. data/spec/platform/windows/mingw/platform_spec.rb +222 -0
  155. data/spec/platform/windows/mswin/platform_spec.rb +259 -0
  156. data/spec/platform/windows/spec_helper.rb +720 -0
  157. data/spec/retryable_request_spec.rb +306 -0
  158. data/spec/secure_identity_spec.rb +50 -0
  159. data/spec/security/cached_certificate_store_proxy_spec.rb +62 -0
  160. data/spec/security/certificate_cache_spec.rb +71 -0
  161. data/spec/security/certificate_spec.rb +49 -0
  162. data/spec/security/distinguished_name_spec.rb +46 -0
  163. data/spec/security/encrypted_document_spec.rb +55 -0
  164. data/spec/security/rsa_key_pair_spec.rb +55 -0
  165. data/spec/security/signature_spec.rb +66 -0
  166. data/spec/security/static_certificate_store_spec.rb +58 -0
  167. data/spec/sender_spec.rb +1045 -0
  168. data/spec/serialize/message_pack_spec.rb +131 -0
  169. data/spec/serialize/secure_serializer_spec.rb +132 -0
  170. data/spec/serialize/serializable_spec.rb +90 -0
  171. data/spec/serialize/serializer_spec.rb +197 -0
  172. data/spec/spec.opts +2 -0
  173. data/spec/spec.win32.opts +1 -0
  174. data/spec/spec_helper.rb +130 -0
  175. data/spec/tracer_spec.rb +114 -0
  176. metadata +447 -0
@@ -0,0 +1,31 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ CLIENTS_BASE_DIR = File.join(File.dirname(__FILE__), 'clients')
25
+
26
+ require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'balanced_http_client'))
27
+ require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'base_retry_client'))
28
+ require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'auth_client'))
29
+ require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'api_client'))
30
+ require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'router_client'))
31
+ require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'right_http_client'))
@@ -0,0 +1,383 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module RightScale
25
+
26
+ # HTTP interface to RightApi for use when mapping actor-based requests to API requests
27
+ class ApiClient < BaseRetryClient
28
+
29
+ # RightApi API version for use in X-API-Version header
30
+ API_VERSION = "1.5"
31
+
32
+ # Maximum length of an audit summary as enforced by RightApi
33
+ MAX_AUDIT_SUMMARY_LENGTH = 255
34
+
35
+ # Default time to wait for HTTP connection to open
36
+ DEFAULT_OPEN_TIMEOUT = 2
37
+
38
+ # Default time to wait for response from request, which is chosen to be 5 seconds greater
39
+ # than the response timeout inside the RightNet router
40
+ DEFAULT_REQUEST_TIMEOUT = 35
41
+
42
+ # Map from actor-based request paths to RightApi HTTP verb and path; only requests whose type
43
+ # matches an entry in this hash will be routed to the RightApi; all others will be routed to RightNet
44
+ API_MAP = {
45
+ "/auditor/create_entry" => [:post, "/audit_entries"],
46
+ "/auditor/update_entry" => [:post, "/audit_entries/:id/append"],
47
+ "/booter/declare" => [:post, "/right_net/booter/declare"],
48
+ "/booter/get_repositories" => [:get, "/right_net/booter/get_repositories"],
49
+ "/booter/get_boot_bundle" => [:get, "/right_net/booter/get_boot_bundle"],
50
+ "/booter/get_decommission_bundle" => [:get, "/right_net/booter/get_decommission_bundle"],
51
+ "/booter/get_missing_attributes" => [:get, "/right_net/booter/get_missing_attributes"],
52
+ "/booter/get_login_policy" => [:get, "/right_net/booter/get_login_policy"],
53
+ "/forwarder/schedule_right_script" => [:post, "/right_net/scheduler/bundle_right_script"],
54
+ "/forwarder/schedule_recipe" => [:post, "/right_net/scheduler/bundle_recipe"],
55
+ "/forwarder/shutdown" => [:post, "/right_net/scheduler/shutdown"],
56
+ "/key_server/retrieve_public_keys" => [:get, "/right_net/key_server/retrieve_public_keys"],
57
+ "/router/ping" => [:get, "/health-check"],
58
+ "/router/query_tags" => [:post, "/tags/by_tag"],
59
+ "/router/add_tags" => [:post, "/tags/multi_add"],
60
+ "/router/delete_tags" => [:post, "/tags/multi_delete"],
61
+ "/state_recorder/record" => [:put, "/right_net/state_recorder/record"],
62
+ "/storage_valet/get_planned_volumes" => [:get, "/right_net/storage_valet/get_planned_volumes"],
63
+ "/storage_valet/attach_volume" => [:post, "/right_net/storage_valet/attach_volume"],
64
+ "/storage_valet/detach_volume" => [:post, "/right_net/storage_valet/detach_volume"],
65
+ "/updater/update_inputs" => [:post, "/right_net/scheduler/update_inputs"],
66
+ "/vault/read_documents" => [:get, "/right_net/vault/read_documents"] }
67
+
68
+ # Symbols for audit request parameters whose values are to be hidden when logging
69
+ AUDIT_FILTER_PARAMS = ["detail", "text"]
70
+
71
+ # Resource href for this agent
72
+ attr_reader :self_href
73
+
74
+ # Create RightApi client of specified type
75
+ #
76
+ # @param [AuthClient] auth_client providing authorization session for HTTP requests
77
+ #
78
+ # @option options [Numeric] :open_timeout maximum wait for connection; defaults to DEFAULT_OPEN_TIMEOUT
79
+ # @option options [Numeric] :request_timeout maximum wait for response; defaults to DEFAULT_REQUEST_TIMEOUT
80
+ # @option options [Numeric] :retry_timeout maximum before stop retrying; defaults to DEFAULT_RETRY_TIMEOUT
81
+ # @option options [Array] :retry_intervals between successive retries; defaults to DEFAULT_RETRY_INTERVALS
82
+ # @option options [Boolean] :retry_enabled for requests that fail to connect or that return a retry result
83
+ # @option options [Numeric] :reconnect_interval for reconnect attempts after lose connectivity
84
+ # @option options [Proc] :exception_callback for unexpected exceptions
85
+ #
86
+ # @raise [ArgumentError] auth client does not support this client type
87
+ def initialize(auth_client, options)
88
+ init(:api, auth_client, options.merge(:server_name => "RightApi", :api_version => API_VERSION))
89
+ end
90
+
91
+ # Route a request to a single target or multiple targets with no response expected
92
+ # Persist the request en route to reduce the chance of it being lost at the expense of some
93
+ # additional network overhead
94
+ # Enqueue the request if the target is not currently available
95
+ # Never automatically retry the request if there is the possibility of it being duplicated
96
+ # Set time-to-live to be forever
97
+ #
98
+ # @param [String] type of request as path specifying actor and action
99
+ # @param [Hash, NilClass] payload for request
100
+ # @param [String, Hash, NilClass] target for request, which may be identity of specific
101
+ # target, hash for selecting potentially multiple targets, or nil if routing solely
102
+ # using type; hash may contain:
103
+ # [Array] :tags that must all be associated with a target for it to be selected
104
+ # [Hash] :scope for restricting routing which may contain:
105
+ # [Integer] :account id that agents must be associated with to be included
106
+ # [Integer] :shard id that agents must be in to be included, or if value is
107
+ # Packet::GLOBAL, ones with no shard id
108
+ # [Symbol] :selector for picking from qualified targets: :any or :all;
109
+ # defaults to :any
110
+ # @param [String, NilClass] token uniquely identifying this request;
111
+ # defaults to randomly generated ID
112
+ #
113
+ # @return [NilClass] always nil since there is no expected response to the request
114
+ #
115
+ # @raise [Exceptions::Unauthorized] authorization failed
116
+ # @raise [Exceptions::ConnectivityFailure] cannot connect to server, lost connection
117
+ # to it, or it is out of service or too busy to respond
118
+ # @raise [Exceptions::RetryableError] request failed but if retried may succeed
119
+ # @raise [Exceptions::Terminating] closing client and terminating service
120
+ # @raise [Exceptions::InternalServerError] internal error in server being accessed
121
+ def push(type, payload, target, token = nil)
122
+ map_request(type, payload, token)
123
+ end
124
+
125
+ # Route a request to a single target with a response expected
126
+ # Automatically retry the request if a response is not received in a reasonable amount of time
127
+ # or if there is a non-delivery response indicating the target is not currently available
128
+ # Timeout the request if a response is not received in time, typically configured to 30 sec
129
+ # Because of retries there is the possibility of duplicated requests, and these are detected and
130
+ # discarded automatically for non-idempotent actions
131
+ # Allow the request to expire per the agent's configured time-to-live, typically 1 minute
132
+ #
133
+ # @param [String] type of request as path specifying actor and action
134
+ # @param [Hash, NilClass] payload for request
135
+ # @param [String, Hash, NilClass] target for request, which may be identity of specific
136
+ # target, hash for selecting targets of which one is picked randomly, or nil if routing solely
137
+ # using type; hash may contain:
138
+ # [Array] :tags that must all be associated with a target for it to be selected
139
+ # [Hash] :scope for restricting routing which may contain:
140
+ # [Integer] :account id that agents must be associated with to be included
141
+ # @param [String, NilClass] token uniquely identifying this request;
142
+ # defaults to randomly generated ID
143
+ #
144
+ # @return [Result, NilClass] response from request
145
+ #
146
+ # @raise [Exceptions::Unauthorized] authorization failed
147
+ # @raise [Exceptions::ConnectivityFailure] cannot connect to server, lost connection
148
+ # to it, or it is out of service or too busy to respond
149
+ # @raise [Exceptions::RetryableError] request failed but if retried may succeed
150
+ # @raise [Exceptions::Terminating] closing client and terminating service
151
+ # @raise [Exceptions::InternalServerError] internal error in server being accessed
152
+ def request(type, payload, target, token = nil)
153
+ map_request(type, payload, token)
154
+ end
155
+
156
+ # Determine whether request supported by this client
157
+ #
158
+ # @param [String] type of request as path specifying actor and action
159
+ #
160
+ # @return [Array] HTTP verb and path
161
+ def support?(type)
162
+ API_MAP.has_key?(type)
163
+ end
164
+
165
+ protected
166
+
167
+ # Convert request to RightApi form and then make request via HTTP
168
+ #
169
+ # @param [String] type of request as path specifying actor and action
170
+ # @param [Hash, NilClass] payload for request
171
+ # @param [String, NilClass] token uniquely identifying this request;
172
+ # defaults to randomly generated ID
173
+ #
174
+ # @return [Object, NilClass] response from request
175
+ #
176
+ # @raise [Exceptions::Unauthorized] authorization failed
177
+ # @raise [Exceptions::ConnectivityFailure] cannot connect to server, lost connection
178
+ # to it, or it is too busy to respond
179
+ # @raise [Exceptions::RetryableError] request failed but if retried may succeed
180
+ # @raise [Exceptions::Terminating] closing client and terminating service
181
+ # @raise [Exceptions::InternalServerError] internal error in server being accessed
182
+ def map_request(type, payload, token)
183
+ verb, path = API_MAP[type]
184
+ raise ArgumentError, "Unsupported request type: #{type}" if path.nil?
185
+ actor, action = type.split("/")[1..-1]
186
+ path, params, options = parameterize(actor, action, payload, path)
187
+ if action == "query_tags"
188
+ map_query_tags(verb, params, action, token, options)
189
+ else
190
+ map_response(make_request(verb, path, params, action, token, options), path)
191
+ end
192
+ end
193
+
194
+ # Convert response from request into required form where necessary
195
+ #
196
+ # @param [Object] response received
197
+ # @param [String] path in URI for desired resource
198
+ #
199
+ # @return [Object] converted response
200
+ def map_response(response, path)
201
+ case path
202
+ when "/audit_entries"
203
+ # Convert returned audit entry href to audit ID
204
+ response.sub!(/^.*\/api\/audit_entries\//, "") if response.is_a?(String)
205
+ when "/tags/by_resource", "/tags/by_tag"
206
+ # Extract tags for each instance resource from response array with members of form
207
+ # {"actions" => [], "links" => [{"rel" => "resource", "href" => <href>}, ...]}, "tags" => [{"name" => <tag>}, ...]
208
+ tags = {}
209
+ if response
210
+ response.each do |hash|
211
+ r = {}
212
+ hash["links"].each { |l| r[l["href"]] = {"tags" => []} if l["href"] =~ /instances/ }
213
+ hash["tags"].each { |t| r.each_key { |k| r[k]["tags"] << t["name"] } } if r.any?
214
+ tags.merge!(r)
215
+ end
216
+ end
217
+ response = tags
218
+ end
219
+ response
220
+ end
221
+
222
+ # Convert tag query request into one or more API requests and then convert responses
223
+ # Currently only retrieving "instances" resources
224
+ #
225
+ # @param [Symbol] verb for HTTP REST request
226
+ # @param [Hash] params for HTTP request
227
+ # @param [String] action from request type
228
+ # @param [String, NilClass] token uniquely identifying this request;
229
+ # defaults to randomly generated ID
230
+ # @param [Hash] options augmenting or overriding default options for HTTP request
231
+ #
232
+ # @return [Hash] tags retrieved with resource href as key and tags array as value
233
+ def map_query_tags(verb, params, action, token, options)
234
+ response = {}
235
+ hrefs = params[:resource_hrefs] || []
236
+ hrefs.concat(query_by_tag(verb, params[:tags], action, token, options)) if params[:tags]
237
+ response = query_by_resource(verb, hrefs, action, token, options) if hrefs.any?
238
+ response
239
+ end
240
+
241
+ # Query API for resources with specified tags
242
+ #
243
+ # @param [Symbol] verb for HTTP REST request
244
+ # @param [Array] tags that all resources retrieved must have
245
+ # @param [String] action from request type
246
+ # @param [String, NilClass] token uniquely identifying this request;
247
+ # defaults to randomly generated ID
248
+ # @param [Hash] options augmenting or overriding default options for HTTP request
249
+ #
250
+ # @return [Array] resource hrefs
251
+ def query_by_tag(verb, tags, action, token, options)
252
+ path = "/tags/by_tag"
253
+ params = {:tags => tags, :match_all => false, :resource_type => "instances"}
254
+ map_response(make_request(verb, path, params, action, token, options), path).keys
255
+ end
256
+
257
+ # Query API for tags associated with a set of resources
258
+ #
259
+ # @param [Symbol] verb for HTTP REST request
260
+ # @param [Array] hrefs for resources whose tags are to be retrieved
261
+ # @param [String] action from request type
262
+ # @param [String, NilClass] token uniquely identifying this request;
263
+ # defaults to randomly generated ID
264
+ # @param [Hash] options augmenting or overriding default options for HTTP request
265
+ #
266
+ # @return [Hash] tags retrieved with resource href as key and tags array as value
267
+ def query_by_resource(verb, hrefs, action, token, options)
268
+ path = "/tags/by_resource"
269
+ params = {:resource_hrefs => hrefs}
270
+ map_response(make_request(verb, path, params, action, token, options), path)
271
+ end
272
+
273
+ # Convert payload to HTTP parameters
274
+ #
275
+ # @param [String] actor from request type
276
+ # @param [String] action from request type
277
+ # @param [Hash, NilClass] payload for request
278
+ # @param [String] path in URI for desired resource
279
+ #
280
+ # @return [Array] path string and parameters and options hashes
281
+ def parameterize(actor, action, payload, path)
282
+ options = {}
283
+ params = {}
284
+ if actor == "auditor"
285
+ path = path.sub(/:id/, payload[:audit_id].to_s || "")
286
+ params = parameterize_audit(action, payload)
287
+ options = {:filter_params => AUDIT_FILTER_PARAMS}
288
+ elsif actor == "router" && action =~ /_tags/
289
+ if action != "query_tags"
290
+ params[:resource_hrefs] = [@self_href]
291
+ else
292
+ params[:resource_hrefs] = Array(payload[:hrefs]).flatten.compact if payload[:hrefs]
293
+ end
294
+ params[:tags] = Array(payload[:tags]).flatten.compact if payload[:tags]
295
+ else
296
+ # Can remove :agent_identity here since now carried in the authorization as the :agent
297
+ payload.each { |k, v| params[k.to_sym] = v if k.to_sym != :agent_identity } if payload.is_a?(Hash)
298
+ end
299
+ [path, params, options]
300
+ end
301
+
302
+ # Translate audit request payload to HTTP parameters
303
+ # Truncate audit summary to MAX_AUDIT_SUMMARY_LENGTH, the limit imposed by RightApi
304
+ #
305
+ # @param [String] action requested: create_entry or update_entry
306
+ # @param [Hash] payload from submitted request
307
+ #
308
+ # @return [Hash] HTTP request parameters
309
+ #
310
+ # @raise [ArgumentError] unknown request action
311
+ def parameterize_audit(action, payload)
312
+ params = {}
313
+ summary = non_blank(payload[:summary])
314
+ detail = non_blank(payload[:detail])
315
+ case action
316
+ when "create_entry"
317
+ params[:audit_entry] = {:auditee_href => @self_href}
318
+ params[:audit_entry][:summary] = truncate(summary, MAX_AUDIT_SUMMARY_LENGTH) if summary
319
+ params[:audit_entry][:detail] = detail if detail
320
+ if (user_email = non_blank(payload[:user_email]))
321
+ params[:user_email] = user_email
322
+ end
323
+ params[:notify] = payload[:category] if payload[:category]
324
+ when "update_entry"
325
+ params[:offset] = payload[:offset] if payload[:offset]
326
+ if summary
327
+ params[:summary] = truncate(summary, MAX_AUDIT_SUMMARY_LENGTH)
328
+ params[:notify] = payload[:category] if payload[:category]
329
+ end
330
+ params[:detail] = detail if detail
331
+ else
332
+ raise ArgumentError, "Unknown audit request action: #{action}"
333
+ end
334
+ params
335
+ end
336
+
337
+ # Truncate string if it exceeds maximum length
338
+ # Do length check with bytesize rather than size since this code
339
+ # is running with ruby 1.9.2 while the API uses 1.8.7, otherwise
340
+ # multi-byte characters could cause this code to be too lenient
341
+ #
342
+ # @param [String, NilClass] value to be truncated
343
+ # @param [Integer] max_length allowed; must be greater than 3
344
+ #
345
+ # @return [String, NilClass] truncated string or original value if it is not a string
346
+ #
347
+ # @raise [ArgumentError] max_length too small
348
+ def truncate(value, max_length)
349
+ raise ArgumentError, "max_length must be greater than 3" if max_length <= 3
350
+ if value.is_a?(String) && value.bytesize > max_length
351
+ max_truncated = max_length - 3
352
+ truncated = value[0, max_truncated]
353
+ while truncated.bytesize > max_truncated do
354
+ truncated.chop!
355
+ end
356
+ truncated + "..."
357
+ else
358
+ value
359
+ end
360
+ end
361
+
362
+ # Determine whether value is non-blank
363
+ #
364
+ # @param [String, NilClass] value to be tested
365
+ #
366
+ # @return [String, NilClass] value if non-blank, otherwise nil
367
+ def non_blank(value)
368
+ value && !value.empty? ? value : nil
369
+ end
370
+
371
+ # Perform any other steps needed to make this client fully usable
372
+ # once HTTP client has been created and server known to be accessible
373
+ #
374
+ # @return [TrueClass] always true
375
+ def enable_use
376
+ result = make_request(:get, "/sessions/instance", {}, "instance")
377
+ @self_href = result["links"].select { |link| link["rel"] == "self" }.first["href"]
378
+ true
379
+ end
380
+
381
+ end # ApiClient
382
+
383
+ end # RightScale
@@ -0,0 +1,247 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module RightScale
25
+
26
+ # Abstract base class for authorization client
27
+ class AuthClient
28
+
29
+ # State of authorization: :pending, :authorized, :unauthorized, :expired, :failed, :closed
30
+ attr_reader :state
31
+
32
+ PERMITTED_STATE_TRANSITIONS = {
33
+ :pending => [:pending, :authorized, :unauthorized, :failed, :closed],
34
+ :authorized => [:authorized, :unauthorized, :expired, :failed, :closed],
35
+ :unauthorized => [:authorized, :unauthorized, :failed, :closed],
36
+ :expired => [:authorized, :unauthorized, :expired, :failed, :closed],
37
+ :failed => [:failed, :closed],
38
+ :closed => [:closed] }
39
+
40
+ # Initialize client
41
+ # Derived classes need to call reset_stats
42
+ def initialize(options = {})
43
+ raise NotImplementedError, "#{self.class.name} is an abstract class"
44
+ end
45
+
46
+ # Identity of agent using this client
47
+ #
48
+ # @return [String] identity
49
+ def identity
50
+ @identity
51
+ end
52
+
53
+ # Headers to be added to HTTP request
54
+ # Include authorization header by default
55
+ #
56
+ # @return [Hash] headers to be added to request header
57
+ #
58
+ # @raise [Exceptions::Unauthorized] not authorized
59
+ # @raise [Exceptions::RetryableError] authorization expired, but retry may succeed
60
+ def headers
61
+ check_authorized
62
+ auth_header
63
+ end
64
+
65
+ # Authorization header to be added to HTTP request
66
+ #
67
+ # @return [Hash] authorization header
68
+ #
69
+ # @raise [Exceptions::Unauthorized] not authorized
70
+ # @raise [Exceptions::RetryableError] authorization expired, but retry may succeed
71
+ def auth_header
72
+ check_authorized
73
+ {"Authorization" => "Bearer #{@access_token}"}
74
+ end
75
+
76
+ # Account if any to which agent using this client belongs
77
+ #
78
+ # @return [Integer] account ID
79
+ #
80
+ # @raise [Exceptions::Unauthorized] not authorized
81
+ def account_id
82
+ check_authorized
83
+ @account_id
84
+ end
85
+
86
+ # URL for accessing RightApi including base path
87
+ #
88
+ # @return [String] URL including base path
89
+ #
90
+ # @raise [Exceptions::Unauthorized] not authorized
91
+ # @raise [Exceptions::RetryableError] authorization expired, but retry may succeed
92
+ def api_url
93
+ check_authorized
94
+ @api_url
95
+ end
96
+
97
+ # URL for accessing RightNet router including base path
98
+ #
99
+ # @return [String] URL including base path
100
+ #
101
+ # @raise [Exceptions::Unauthorized] not authorized
102
+ # @raise [Exceptions::RetryableError] authorization expired, but retry may succeed
103
+ def router_url
104
+ check_authorized
105
+ @router_url
106
+ end
107
+
108
+ # RightNet communication mode
109
+ #
110
+ # @return [Symbol] :http or :amqp
111
+ def mode
112
+ @mode
113
+ end
114
+
115
+ # An HTTP request had an authorization expiration error
116
+ # Renew authorization
117
+ #
118
+ # @return [TrueClass] always true
119
+ def expired
120
+ Log.info("Renewing authorization for #{identity} because request failed due to expiration")
121
+ self.state = :expired
122
+ renew_authorization
123
+ true
124
+ end
125
+
126
+ # An HTTP request received a redirect response
127
+ #
128
+ # @param [String] location to which response indicated to redirect
129
+ #
130
+ # @return [TrueClass] always true
131
+ def redirect(location)
132
+ true
133
+ end
134
+
135
+ # Take any actions necessary to quiesce client interaction in preparation
136
+ # for agent termination but allow any active requests to complete
137
+ #
138
+ # @return [TrueClass] always true
139
+ def close
140
+ self.state = :closed
141
+ true
142
+ end
143
+
144
+ # Record callback to be notified of authorization status changes
145
+ # Multiple callbacks are supported
146
+ #
147
+ # @yield [type, status] called when status changes (optional)
148
+ # @yieldparam [Symbol] type of client reporting status change: :auth
149
+ # @yieldparam [Symbol] state of authorization
150
+ #
151
+ # @return [Symbol] current state
152
+ def status(&callback)
153
+ @status_callbacks = (@status_callbacks || []) << callback if callback
154
+ state
155
+ end
156
+
157
+ # Current statistics for this client
158
+ #
159
+ # @param [Boolean] reset the statistics after getting the current ones
160
+ #
161
+ # @return [Hash] current statistics
162
+ # [Hash, NilClass] "state" Activity stats or nil if none
163
+ # [Hash, NilClass] "exceptions" Exceptions stats or nil if none
164
+ def stats(reset = false)
165
+ stats = {}
166
+ @stats.each { |k, v| stats[k] = v.all }
167
+ reset_stats if reset
168
+ stats
169
+ end
170
+
171
+ protected
172
+
173
+ # Reset statistics for this client
174
+ #
175
+ # @return [TrueClass] always true
176
+ def reset_stats
177
+ @stats = {
178
+ "state" => RightSupport::Stats::Activity.new,
179
+ "exceptions" => RightSupport::Stats::Exceptions.new(agent = nil, @exception_callback)}
180
+ true
181
+ end
182
+
183
+ # Check whether authorized
184
+ #
185
+ # @return [TrueClass] always true if don't raise exception
186
+ #
187
+ # @raise [Exceptions::Unauthorized] not authorized
188
+ # @raise [Exceptions::RetryableError] authorization expired, but retry may succeed
189
+ def check_authorized
190
+ if state == :expired
191
+ raise Exceptions::RetryableError, "Authorization expired"
192
+ elsif state != :authorized
193
+ raise Exceptions::Unauthorized, "Not authorized with RightScale" if state != :authorized
194
+ end
195
+ true
196
+ end
197
+
198
+ # Renew authorization
199
+ #
200
+ # @param [Integer] wait time before attempt to renew
201
+ #
202
+ # @return [TrueClass] always true
203
+ def renew_authorization(wait = 0)
204
+ true
205
+ end
206
+
207
+ # Update authorization state
208
+ # If state has changed, make external callbacks to notify of change
209
+ # Do not update state once set to :closed
210
+ #
211
+ # @param [Hash] value for new state
212
+ #
213
+ # @return [Symbol] updated state
214
+ #
215
+ # @raise [ArgumentError] invalid state transition
216
+ def state=(value)
217
+ return if @state == :closed
218
+ unless PERMITTED_STATE_TRANSITIONS[@state].include?(value)
219
+ raise ArgumentError, "Invalid state transition: #{@state.inspect} -> #{value.inspect}"
220
+ end
221
+
222
+ case value
223
+ when :pending, :closed
224
+ @stats["state"].update(value.to_s)
225
+ @state = value
226
+ when :authorized, :unauthorized, :expired, :failed
227
+ if value != @state
228
+ @stats["state"].update(value.to_s)
229
+ @state = value
230
+ (@status_callbacks || []).each do |callback|
231
+ begin
232
+ callback.call(:auth, @state)
233
+ rescue StandardError => e
234
+ Log.error("Failed status callback", e)
235
+ @stats["exceptions"].track("status", e)
236
+ end
237
+ end
238
+ end
239
+ else
240
+ raise ArgumentError, "Unknown state: #{value.inspect}"
241
+ end
242
+ @state
243
+ end
244
+
245
+ end # AuthClient
246
+
247
+ end # RightScale