right_link 5.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (199) hide show
  1. data/actors/agent_manager.rb +88 -0
  2. data/actors/instance_scheduler.rb +321 -0
  3. data/actors/instance_services.rb +64 -0
  4. data/actors/instance_setup.rb +567 -0
  5. data/bin/cloud +25 -0
  6. data/bin/cook_runner +44 -0
  7. data/bin/deploy +120 -0
  8. data/bin/enroll +385 -0
  9. data/bin/rad +32 -0
  10. data/bin/rchk +29 -0
  11. data/bin/rnac +39 -0
  12. data/bin/rs_connect +33 -0
  13. data/bin/rs_log_level +31 -0
  14. data/bin/rs_ohai +28 -0
  15. data/bin/rs_reenroll +31 -0
  16. data/bin/rs_run_recipe +34 -0
  17. data/bin/rs_run_right_script +34 -0
  18. data/bin/rs_shutdown +33 -0
  19. data/bin/rs_tag +33 -0
  20. data/bin/rs_thunk +33 -0
  21. data/bin/rstat +31 -0
  22. data/bin/system +16 -0
  23. data/ext/Rakefile +18 -0
  24. data/init/config.yml +5 -0
  25. data/init/init.rb +79 -0
  26. data/lib/chef/ohai_setup.rb +51 -0
  27. data/lib/chef/plugins/cloud.rb +91 -0
  28. data/lib/chef/plugins/cloudstack.rb +23 -0
  29. data/lib/chef/plugins/ec2.rb +23 -0
  30. data/lib/chef/plugins/linux/block_device2.rb +24 -0
  31. data/lib/chef/plugins/rackspace.rb +23 -0
  32. data/lib/chef/plugins/rightscale.rb +125 -0
  33. data/lib/chef/plugins/windows/network.rb +114 -0
  34. data/lib/chef/plugins.rb +74 -0
  35. data/lib/chef/providers/dns_dnsmadeeasy_provider.rb +81 -0
  36. data/lib/chef/providers/dns_resource.rb +100 -0
  37. data/lib/chef/providers/executable_schedule_provider.rb +70 -0
  38. data/lib/chef/providers/executable_schedule_resource.rb +144 -0
  39. data/lib/chef/providers/remote_recipe_provider.rb +86 -0
  40. data/lib/chef/providers/remote_recipe_resource.rb +101 -0
  41. data/lib/chef/providers/right_link_tag_provider.rb +73 -0
  42. data/lib/chef/providers/right_link_tag_resource.rb +59 -0
  43. data/lib/chef/providers/right_script_provider.rb +190 -0
  44. data/lib/chef/providers/right_script_resource.rb +113 -0
  45. data/lib/chef/providers/rs_shutdown_provider.rb +75 -0
  46. data/lib/chef/providers/rs_shutdown_resource.rb +55 -0
  47. data/lib/chef/providers/server_collection_provider.rb +66 -0
  48. data/lib/chef/providers/server_collection_resource.rb +93 -0
  49. data/lib/chef/providers/windows/powershell_provider.rb +151 -0
  50. data/lib/chef/providers/windows/powershell_resource.rb +111 -0
  51. data/lib/chef/providers/windows/unsupported_provider.rb +51 -0
  52. data/lib/chef/right_providers.rb +55 -0
  53. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.csproj +104 -0
  54. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.dll-Help.xml +141 -0
  55. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Exceptions.cs +182 -0
  56. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeCommand.cs +58 -0
  57. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeRequest.cs +46 -0
  58. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeResponse.cs +45 -0
  59. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceCommand.cs +58 -0
  60. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceRequest.cs +46 -0
  61. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceResponse.cs +45 -0
  62. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceCommand.cs +58 -0
  63. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceRequest.cs +46 -0
  64. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceResponse.cs +45 -0
  65. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionCommand.cs +178 -0
  66. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionRequest.cs +67 -0
  67. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionResponse.cs +58 -0
  68. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueCommandBase.cs +142 -0
  69. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueRequestBase.cs +64 -0
  70. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueResponseBase.cs +69 -0
  71. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/JsonTransport.cs +110 -0
  72. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeClient.cs +158 -0
  73. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeServer.cs +142 -0
  74. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
  75. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolConstants.cs +55 -0
  76. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolUtilities.cs +77 -0
  77. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ReadMe.txt +53 -0
  78. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeCommand.cs +59 -0
  79. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeRequest.cs +46 -0
  80. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeResponse.cs +58 -0
  81. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceCommand.cs +59 -0
  82. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceRequest.cs +46 -0
  83. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceResponse.cs +40 -0
  84. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceCommand.cs +59 -0
  85. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceRequest.cs +46 -0
  86. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceResponse.cs +40 -0
  87. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueCommandBase.cs +293 -0
  88. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueRequestBase.cs +75 -0
  89. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueResponseBase.cs +45 -0
  90. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Transport.cs +91 -0
  91. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet.sln +35 -0
  92. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Program.cs +374 -0
  93. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
  94. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/TestChefNodeCmdlet.csproj +65 -0
  95. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Program.cs +136 -0
  96. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Properties/AssemblyInfo.cs +36 -0
  97. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/ReadMe.txt +46 -0
  98. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/TestNextActionCmdlet.csproj +68 -0
  99. data/lib/chef/windows/bin/Newtonsoft.Json.dll +0 -0
  100. data/lib/chef/windows/chef_node_server.rb +463 -0
  101. data/lib/chef/windows/dynamic_powershell_provider.rb +296 -0
  102. data/lib/chef/windows/pipe_server.rb +283 -0
  103. data/lib/chef/windows/powershell_host.rb +285 -0
  104. data/lib/chef/windows/powershell_pipe_server.rb +136 -0
  105. data/lib/chef/windows/powershell_provider_base.rb +92 -0
  106. data/lib/chef/windows/scripts/run_loop.ps1 +105 -0
  107. data/lib/clouds/cloud.rb +557 -0
  108. data/lib/clouds/cloud_factory.rb +250 -0
  109. data/lib/clouds/cloud_utilities.rb +244 -0
  110. data/lib/clouds/clouds/azure.rb +106 -0
  111. data/lib/clouds/clouds/cloudstack.rb +114 -0
  112. data/lib/clouds/clouds/ec2.rb +113 -0
  113. data/lib/clouds/clouds/eucalyptus.rb +46 -0
  114. data/lib/clouds/clouds/google.rb +102 -0
  115. data/lib/clouds/clouds/none.rb +76 -0
  116. data/lib/clouds/clouds/openstack.rb +30 -0
  117. data/lib/clouds/clouds/rackspace-ng.rb +54 -0
  118. data/lib/clouds/clouds/rackspace.rb +78 -0
  119. data/lib/clouds/clouds/softlayer.rb +91 -0
  120. data/lib/clouds/metadata_formatter.rb +108 -0
  121. data/lib/clouds/metadata_provider.rb +128 -0
  122. data/lib/clouds/metadata_source.rb +87 -0
  123. data/lib/clouds/metadata_sources/certificate_metadata_source.rb +207 -0
  124. data/lib/clouds/metadata_sources/config_drive_metadata_source.rb +129 -0
  125. data/lib/clouds/metadata_sources/file_metadata_source.rb +74 -0
  126. data/lib/clouds/metadata_sources/http_metadata_source.rb +277 -0
  127. data/lib/clouds/metadata_sources/selective_metadata_source.rb +122 -0
  128. data/lib/clouds/metadata_tree_climber.rb +144 -0
  129. data/lib/clouds/metadata_writer.rb +155 -0
  130. data/lib/clouds/metadata_writers/dictionary_metadata_writer.rb +72 -0
  131. data/lib/clouds/metadata_writers/ruby_metadata_writer.rb +76 -0
  132. data/lib/clouds/metadata_writers/shell_metadata_writer.rb +121 -0
  133. data/lib/clouds/register_clouds.rb +34 -0
  134. data/lib/clouds.rb +32 -0
  135. data/lib/gem_dependencies.rb +83 -0
  136. data/lib/git_hooks/commit-msg.rb +7 -0
  137. data/lib/instance/agent_config.rb +168 -0
  138. data/lib/instance/agent_watcher.rb +233 -0
  139. data/lib/instance/audit_cook_stub.rb +104 -0
  140. data/lib/instance/audit_proxy.rb +247 -0
  141. data/lib/instance/bundle_queue.rb +104 -0
  142. data/lib/instance/cook/agent_connection.rb +109 -0
  143. data/lib/instance/cook/audit_logger.rb +165 -0
  144. data/lib/instance/cook/audit_stub.rb +142 -0
  145. data/lib/instance/cook/ca-bundle.crt +2794 -0
  146. data/lib/instance/cook/chef_state.rb +211 -0
  147. data/lib/instance/cook/cook.rb +306 -0
  148. data/lib/instance/cook/cook_state.rb +298 -0
  149. data/lib/instance/cook/cookbook_path_mapping.rb +66 -0
  150. data/lib/instance/cook/cookbook_repo_retriever.rb +190 -0
  151. data/lib/instance/cook/executable_sequence.rb +765 -0
  152. data/lib/instance/cook/external_parameter_gatherer.rb +190 -0
  153. data/lib/instance/cook/repose_downloader.rb +349 -0
  154. data/lib/instance/cook/shutdown_request_proxy.rb +121 -0
  155. data/lib/instance/cook.rb +41 -0
  156. data/lib/instance/downloader.rb +208 -0
  157. data/lib/instance/duplicable.rb +67 -0
  158. data/lib/instance/exceptions.rb +49 -0
  159. data/lib/instance/executable_sequence_proxy.rb +278 -0
  160. data/lib/instance/instance_commands.rb +577 -0
  161. data/lib/instance/instance_state.rb +633 -0
  162. data/lib/instance/json_utilities.rb +102 -0
  163. data/lib/instance/login_manager.rb +533 -0
  164. data/lib/instance/login_user_manager.rb +522 -0
  165. data/lib/instance/message_encoder.rb +118 -0
  166. data/lib/instance/multi_thread_bundle_queue.rb +232 -0
  167. data/lib/instance/operation_context.rb +60 -0
  168. data/lib/instance/options_bag.rb +65 -0
  169. data/lib/instance/payload_formatter.rb +46 -0
  170. data/lib/instance/policy.rb +53 -0
  171. data/lib/instance/policy_audit.rb +100 -0
  172. data/lib/instance/policy_manager.rb +146 -0
  173. data/lib/instance/reenroll_manager.rb +104 -0
  174. data/lib/instance/right_scripts_cookbook.rb +181 -0
  175. data/lib/instance/shutdown_request.rb +221 -0
  176. data/lib/instance/single_thread_bundle_queue.rb +189 -0
  177. data/lib/instance/volume_management.rb +450 -0
  178. data/lib/instance.rb +50 -0
  179. data/lib/repo_conf_generators/apt_conf_generators.rb +106 -0
  180. data/lib/repo_conf_generators/gem_conf_generators.rb +80 -0
  181. data/lib/repo_conf_generators/rightscale_conf_generators.rb +254 -0
  182. data/lib/repo_conf_generators/rightscale_key.pub +17 -0
  183. data/lib/repo_conf_generators/yum_conf_generators.rb +225 -0
  184. data/lib/repo_conf_generators.rb +30 -0
  185. data/lib/run_shell.rb +28 -0
  186. data/scripts/agent_checker.rb +571 -0
  187. data/scripts/agent_controller.rb +247 -0
  188. data/scripts/agent_deployer.rb +148 -0
  189. data/scripts/bundle_runner.rb +336 -0
  190. data/scripts/cloud_controller.rb +176 -0
  191. data/scripts/log_level_manager.rb +142 -0
  192. data/scripts/ohai_runner.rb +33 -0
  193. data/scripts/reenroller.rb +193 -0
  194. data/scripts/server_importer.rb +293 -0
  195. data/scripts/shutdown_client.rb +183 -0
  196. data/scripts/system_configurator.rb +367 -0
  197. data/scripts/tagger.rb +381 -0
  198. data/scripts/thunker.rb +356 -0
  199. metadata +418 -0
@@ -0,0 +1,633 @@
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
+ require 'fileutils'
24
+ require File.normalize_path(File.join(File.dirname(__FILE__), 'json_utilities'))
25
+
26
+ module RightScale
27
+
28
+ # Manages instance state
29
+ class InstanceState
30
+
31
+ CONFIG_YAML_FILE = File.normalize_path(File.join(RightScale::Platform.filesystem.right_link_static_state_dir, 'features.yml'))
32
+
33
+ CONFIG=\
34
+ if File.exists?(CONFIG_YAML_FILE)
35
+ RightSupport::Config.features(CONFIG_YAML_FILE)
36
+ else
37
+ RightSupport::Config.features({})
38
+ end
39
+
40
+ # States that are recorded in a standard fashion and audited when transitioned to
41
+ RECORDED_STATES = %w{ booting operational stranded decommissioning }
42
+
43
+ # States that cause the system MOTD/banner to indicate that everything is OK
44
+ SUCCESSFUL_STATES = %w{ operational }
45
+
46
+ # States that cause the system MOTD/banner to indicate that something is wrong
47
+ FAILED_STATES = %w{ stranded }
48
+
49
+ # Initial state prior to booting
50
+ INITIAL_STATE = 'pending'
51
+
52
+ # Final state when shutting down that is recorded in a non-standard fashion
53
+ FINAL_STATE = 'decommissioned'
54
+
55
+ # Valid internal states
56
+ STATES = RECORDED_STATES + [FINAL_STATE]
57
+
58
+ # Path to JSON file where current instance state is serialized
59
+ STATE_DIR = AgentConfig.agent_state_dir
60
+ STATE_FILE = File.join(STATE_DIR, 'state.js')
61
+
62
+ # Path to JSON file where authorized login users are defined
63
+ LOGIN_POLICY_FILE = File.join(STATE_DIR, 'login_policy.js')
64
+
65
+ # Path to boot log
66
+ BOOT_LOG_FILE = File.join(RightScale::Platform.filesystem.log_dir, 'install')
67
+
68
+ # Path to decommission log
69
+ DECOMMISSION_LOG_FILE = File.join(RightScale::Platform.filesystem.log_dir, 'decommission')
70
+
71
+ # Number of seconds to wait for cloud to shutdown instance
72
+ FORCE_SHUTDOWN_DELAY = 180
73
+
74
+ # Maximum number of retries to record state with RightNet
75
+ MAX_RECORD_STATE_RETRIES = 5
76
+
77
+ # Number of seconds between attempts to record state
78
+ RETRY_RECORD_STATE_DELAY = 5
79
+
80
+ # Minimum interval in seconds for persistent storage of last communication
81
+ LAST_COMMUNICATION_STORAGE_INTERVAL = 2
82
+
83
+ # State for recording progress of planned volume management.
84
+ class PlannedVolumeState
85
+ attr_accessor :disks, :mappings, :volumes
86
+ end
87
+
88
+ # (String) One of STATES
89
+ def self.value
90
+ @value
91
+ end
92
+
93
+ # (String) One of STATES
94
+ def self.last_recorded_value
95
+ @last_recorded_value
96
+ end
97
+
98
+ # (IdempotentRequest) Current record state request
99
+ def self.record_request
100
+ @record_request
101
+ end
102
+
103
+ # (String) Instance agent identity
104
+ def self.identity
105
+ @identity
106
+ end
107
+
108
+ # (LoginPolicy) The most recently enacted login policy
109
+ def self.login_policy
110
+ @login_policy
111
+ end
112
+
113
+ # Queries most recent state of planned volume mappings.
114
+ #
115
+ # === Return
116
+ # result(Array):: persisted mappings or empty
117
+ def self.planned_volume_state
118
+ @planned_volume_state ||= PlannedVolumeState.new
119
+ end
120
+
121
+ # (String) Type of decommission currently in progress or nil
122
+ def self.decommission_type
123
+ if @value == 'decommissioning'
124
+ @decommission_type
125
+ else
126
+ raise RightScale::Exceptions::WrongState.new("Unexpected call to InstanceState.decommission_type for current state #{@value.inspect}")
127
+ end
128
+ end
129
+
130
+ # Set instance id with given id
131
+ # Load persisted state if any, compare instance ids and force boot if instance ID
132
+ # is different or if reboot flagged
133
+ # For reboot detection relying on rightboot script in linux and shutdown notification in windows
134
+ # to update the reboot flag in the state file
135
+ #
136
+ # === Parameters
137
+ # identity(String):: Instance identity
138
+ # read_only(Boolean):: Whether only allowed to read the instance state, defaults to false
139
+ #
140
+ # === Return
141
+ # true:: Always return true
142
+ def self.init(identity, read_only = false)
143
+ @identity = identity
144
+ @read_only = read_only
145
+ @startup_tags = []
146
+ @log_level = Logger::INFO
147
+ @initial_boot = false
148
+ @reboot = false
149
+ @resource_uid = nil
150
+ @last_recorded_value = nil
151
+ @record_retries = 0
152
+ @record_request = nil
153
+ @record_timer = nil
154
+ @last_communication = 0
155
+ @planned_volume_state = nil
156
+ @decommission_type = nil
157
+
158
+ Log.notify(lambda { |l| @log_level = l }) unless @read_only
159
+
160
+ Sender.instance.message_received { message_received } unless @read_only
161
+
162
+ # need to grab the current resource uid whether there is a state file or not.
163
+ @resource_uid = current_resource_uid
164
+
165
+ dir = File.dirname(STATE_FILE)
166
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
167
+ if File.file?(STATE_FILE)
168
+ state = RightScale::JsonUtilities::read_json(STATE_FILE)
169
+ Log.debug("Initializing instance #{identity} with #{state.inspect}")
170
+
171
+ # Initial state reconciliation: use recorded state and boot timestamp to determine how we last stopped.
172
+ # There are four basic scenarios to worry about:
173
+ # 1) first run -- Agent is starting up for the first time after a fresh install
174
+ # 2) reboot/restart -- Agent already ran; agent ID not changed; reboot detected: transition back to booting
175
+ # 3) bundled boot -- Agent already ran; agent ID changed: transition back to booting
176
+ # 4) decommission/crash -- Agent exited anyway; ID not changed; no reboot; keep old state entirely
177
+ # 5) ec2 restart -- Agent already ran; agent ID changed; instance ID is the same; transition back to booting
178
+ if state['identity'] && state['identity'] != identity
179
+ @last_recorded_value = state['last_recorded_value']
180
+ self.value = 'booting'
181
+ # if the current resource_uid is the same as the last
182
+ # observed resource_uid, then this is a restart,
183
+ # otherwise this is a bundle
184
+ old_resource_uid = state["last_observed_resource_uid"]
185
+ if @resource_uid && @resource_uid == old_resource_uid
186
+ # CASE 5 -- identity has changed; ec2 restart
187
+ Log.debug("Restart detected; transitioning state to booting")
188
+ @reboot = true
189
+ else
190
+ # CASE 3 -- identity has changed; bundled boot
191
+ Log.debug("Bundle detected; transitioning state to booting")
192
+ end
193
+ elsif state['reboot']
194
+ # CASE 2 -- rebooting flagged by rightboot script in linux or by shutdown notification in windows
195
+ Log.debug("Reboot detected; transitioning state to booting")
196
+ @last_recorded_value = state['last_recorded_value']
197
+ self.value = 'booting'
198
+ @reboot = true
199
+ else
200
+ # CASE 4 -- restart without reboot; continue with retries if recorded state does not match
201
+ @value = state['value']
202
+ @startup_tags = state['startup_tags']
203
+ @log_level = state['log_level']
204
+ @last_recorded_value = state['last_recorded_value']
205
+ @record_retries = state['record_retries']
206
+ @decommission_type = state['decommission_type'] if @value == 'decommissioning'
207
+ if @value != @last_recorded_value && RECORDED_STATES.include?(@value) &&
208
+ @record_retries < MAX_RECORD_STATE_RETRIES && !@read_only
209
+ record_state
210
+ else
211
+ @record_retries = 0
212
+ end
213
+ update_logger
214
+ end
215
+ else
216
+ # CASE 1 -- state file does not exist; initial boot, create state file
217
+ Log.debug("Initializing instance #{identity} with booting")
218
+ @last_recorded_value = INITIAL_STATE
219
+ self.value = 'booting'
220
+ @initial_boot = true
221
+ end
222
+
223
+ if File.file?(LOGIN_POLICY_FILE)
224
+ @login_policy = RightScale::JsonUtilities::read_json(LOGIN_POLICY_FILE) rescue nil #corrupt file here is not important enough to fail
225
+ else
226
+ @login_policy = nil
227
+ end
228
+ Log.debug("Existing login users: #{@login_policy.users.length} recorded") if @login_policy
229
+
230
+ #Ensure MOTD is up to date
231
+ update_motd
232
+
233
+ true
234
+ end
235
+
236
+ # Set instance state
237
+ #
238
+ # === Parameters
239
+ # val(String) One of STATES
240
+ #
241
+ # === Return
242
+ # val(String) new state
243
+ #
244
+ # === Raise
245
+ # RightScale::Exceptions::Application:: Cannot update in read-only mode
246
+ # RightScale::Exceptions::Argument:: Invalid new value
247
+ def self.value=(val)
248
+ previous_val = @value || INITIAL_STATE
249
+ raise RightScale::Exceptions::Application, "Not allowed to modify instance state in read-only mode" if @read_only
250
+ raise RightScale::Exceptions::Argument, "Invalid instance state #{val.inspect}" unless STATES.include?(val)
251
+ Log.info("Transitioning state from #{previous_val} to #{val}")
252
+ @reboot = false if val != :booting
253
+ @value = val
254
+ @decommission_type = nil unless @value == 'decommissioning'
255
+
256
+ update_logger
257
+ update_motd
258
+ broadcast_wall unless (previous_val == val)
259
+ record_state if RECORDED_STATES.include?(val)
260
+ store_state
261
+ @observers.each { |o| o.call(val) } if @observers
262
+
263
+ val
264
+ end
265
+
266
+ # Set decommission type and set state to 'decommissioning'
267
+ #
268
+ # === Parameters
269
+ # decommission_type(String):: One of RightScale::ShutdownRequest::LEVELS or nil
270
+ #
271
+ # === Return
272
+ # result(String):: new decommission type
273
+ #
274
+ # === Raise
275
+ # RightScale::Exceptions::Application:: Cannot update in read-only mod
276
+ def self.decommission_type=(decommission_type)
277
+ unless RightScale::ShutdownRequest::LEVELS.include?(decommission_type)
278
+ raise RightScale::ShutdownRequest::InvalidLevel.new("Unexpected decommission_type: #{decommission_type}")
279
+ end
280
+ @decommission_type = decommission_type
281
+ self.value = 'decommissioning'
282
+ @decommission_type
283
+ end
284
+
285
+ # Instance AWS id for EC2 instances
286
+ #
287
+ # === Return
288
+ # resource_uid(String):: Instance AWS ID on EC2, equivalent on other cloud when available
289
+ def self.resource_uid
290
+ resource_uid = @resource_uid
291
+ end
292
+
293
+ # Is this the initial boot?
294
+ #
295
+ # === Return
296
+ # res(Boolean):: Whether this is the instance first boot
297
+ def self.initial_boot?
298
+ res = @initial_boot
299
+ end
300
+
301
+ # Are we rebooting? (needed for RightScripts)
302
+ #
303
+ # === Return
304
+ # res(Boolean):: Whether this instance was rebooted
305
+ def self.reboot?
306
+ res = @reboot
307
+ end
308
+
309
+ # Update the time this instance last received a message
310
+ # thus demonstrating that it is still connected
311
+ #
312
+ # === Return
313
+ # true:: Always return true
314
+ def self.message_received
315
+ now = Time.now.to_i
316
+ if (now - @last_communication) > LAST_COMMUNICATION_STORAGE_INTERVAL
317
+ @last_communication = now
318
+ store_state
319
+ end
320
+ end
321
+
322
+ # Ask core agent to shut ourselves down for soft termination
323
+ # Do not specify the last recorded state since does not matter at this point
324
+ # and no need to risk request failure
325
+ # Add a timer to force shutdown if do not hear back from the cloud or the request hangs
326
+ #
327
+ # === Parameters
328
+ # user_id(Integer):: ID of user that triggered soft-termination
329
+ # skip_db_update(Boolean):: Whether to re-query instance state after call to Ec2 to terminate was made
330
+ # kind(String):: 'terminate', 'stop' or 'reboot'
331
+ #
332
+ # === Return
333
+ # true:: Always return true
334
+ def self.shutdown(user_id, skip_db_update, kind)
335
+ payload = {:agent_identity => @identity, :state => FINAL_STATE, :user_id => user_id, :skip_db_update => skip_db_update, :kind => kind}
336
+ Sender.instance.send_retryable_request("/state_recorder/record", payload) do |r|
337
+ res = OperationResult.from_results(r)
338
+ case kind
339
+ when 'reboot'
340
+ RightScale::Platform.controller.reboot unless res.success?
341
+ when 'terminate', 'stop'
342
+ Sender.instance.send_push("/registrar/remove", {:agent_identity => @identity, :created_at => Time.now.to_i})
343
+ RightScale::Platform.controller.shutdown unless res.success?
344
+ else
345
+ Log.error("InstanceState.shutdown() kind was unexpected: #{kind}")
346
+ end
347
+ end
348
+ case kind
349
+ when 'reboot'
350
+ EM.add_timer(FORCE_SHUTDOWN_DELAY) { RightScale::Platform.controller.reboot }
351
+ when 'terminate', 'stop'
352
+ EM.add_timer(FORCE_SHUTDOWN_DELAY) { RightScale::Platform.controller.shutdown }
353
+ else
354
+ Log.error("InstanceState.shutdown() kind was unexpected: #{kind}")
355
+ end
356
+ end
357
+
358
+ # Set startup tags
359
+ #
360
+ # === Parameters
361
+ # val(Array):: List of tags
362
+ #
363
+ # === Return
364
+ # val(Array):: List of tags
365
+ #
366
+ # === Raise
367
+ # RightScale::Exceptions::Application:: Cannot update in read-only mode
368
+ def self.startup_tags=(val)
369
+ raise RightScale::Exceptions::Application, "Not allowed to modify instance state in read-only mode" if @read_only
370
+ if @startup_tags.nil? || @startup_tags != val
371
+ @startup_tags = val
372
+ # FIX: storing state on change to ensure the most current set of tags is available to
373
+ # cook (or other processes that load instance state) when it is launched. Would
374
+ # be better to communicate state via other means.
375
+ store_state
376
+ end
377
+ val
378
+ end
379
+
380
+ # Tags retrieved on startup
381
+ #
382
+ # === Return
383
+ # tags(Array):: List of tags retrieved on startup
384
+ def self.startup_tags
385
+ @startup_tags
386
+ end
387
+
388
+ # Log level
389
+ #
390
+ # === Return
391
+ # log_level(Const):: One of Logger::DEBUG...Logger::FATAL
392
+ def self.log_level
393
+ @log_level
394
+ end
395
+
396
+ # Callback given observer on all state transitions
397
+ #
398
+ # === Block
399
+ # Given block should take one argument which will be the transitioned to state
400
+ #
401
+ # === Return
402
+ # true:: Always return true
403
+ def self.observe(&observer)
404
+ @observers ||= []
405
+ @observers << observer
406
+ true
407
+ end
408
+
409
+ # Point logger to log file corresponding to current instance state
410
+ #
411
+ # === Return
412
+ # true:: Always return true
413
+ def self.update_logger
414
+ previous_level = nil
415
+ if @current_logger
416
+ previous_level = @current_logger.level
417
+ Log.remove_logger(@current_logger)
418
+ @current_logger = nil
419
+ end
420
+ if file = log_file(@value)
421
+ dir = File.dirname(file)
422
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
423
+ @current_logger = ::Logger.new(file)
424
+ @current_logger.level = previous_level if previous_level
425
+ Log.add_logger(@current_logger)
426
+ end
427
+ true
428
+ end
429
+
430
+ # Record set of authorized login users
431
+ #
432
+ # === Parameters
433
+ # login_users(Array[(LoginUser)]) set of authorized login users
434
+ #
435
+ # === Return
436
+ # login_users(Array[(LoginUser)]) authorized login users
437
+ #
438
+ def self.login_policy=(login_policy)
439
+ @login_policy = login_policy.dup
440
+ File.open(LOGIN_POLICY_FILE, 'w') do |f|
441
+ f.write(@login_policy.to_json)
442
+ end
443
+ login_policy
444
+ end
445
+
446
+ # Log file to be used for given instance state
447
+ #
448
+ # === Parameters
449
+ # state(String):: Instance state, one of STATES
450
+ #
451
+ # === Return
452
+ # log(String):: Log file path
453
+ # nil:: Log file should not be changed
454
+ def self.log_file(state)
455
+ log_file = case state
456
+ when 'booting' then BOOT_LOG_FILE
457
+ when 'decommissioning' then DECOMMISSION_LOG_FILE
458
+ end
459
+ end
460
+
461
+ protected
462
+
463
+ # Determine uptime of this system.
464
+ #
465
+ # === Return
466
+ # uptime(Float):: Uptime of this system in seconds, or 0.0 if undetermined
467
+ def self.uptime()
468
+ return RightScale::Platform.shell.uptime
469
+ end
470
+
471
+ # Purely for informational purposes, attempt to update the Unix MOTD file
472
+ # with a pretty banner indicating success or failure. This operation is
473
+ # not critical and does not influence the functionality of the instance,
474
+ # so this method fails silently.
475
+ #
476
+ # === Return
477
+ # nil:: always return nil
478
+ def self.update_motd()
479
+ return unless CONFIG['motd']['update'] || RightScale::Platform.linux?
480
+
481
+ if File.directory?('/etc/update-motd.d')
482
+ #Ubuntu 10.04 and above use a dynamic MOTD update system. In this case we assume
483
+ #by convention that motd.tail will be appended to the dynamically-generated
484
+ #MOTD.
485
+ motd = '/etc/motd.tail'
486
+ else
487
+ motd = '/etc/motd'
488
+ end
489
+
490
+ FileUtils.rm(motd) rescue nil
491
+
492
+ etc = File.join(AgentConfig.parent_dir, 'etc')
493
+ if SUCCESSFUL_STATES.include?(@value)
494
+ FileUtils.cp(File.join(etc, 'motd-complete'), motd) rescue nil
495
+ elsif FAILED_STATES.include?(@value)
496
+ FileUtils.cp(File.join(etc, 'motd-failed'), motd) rescue nil
497
+ else
498
+ FileUtils.cp(File.join(etc, 'motd'), motd) rescue nil
499
+ end
500
+
501
+ return nil
502
+ end
503
+
504
+ # Purely for informational purposes, attempt to wall-broadcast a RightLink
505
+ # state transition. This should get the attention of anyone who's logged in.
506
+ #
507
+ # === Return
508
+ # nil:: always return nil
509
+ def self.broadcast_wall
510
+ return unless RightScale::Platform.linux?
511
+
512
+ if SUCCESSFUL_STATES.include?(@value)
513
+ system('echo "RightScale installation complete. Details can be found in /var/log/messages" | wall') rescue nil
514
+ elsif FAILED_STATES.include?(@value)
515
+ system('echo "RightScale installation failed. Please review /var/log/messages" | wall') rescue nil
516
+ end
517
+
518
+ return nil
519
+ end
520
+
521
+ private
522
+
523
+ # Persist state to local disk storage
524
+ #
525
+ # === Return
526
+ # true:: Always return true
527
+ def self.store_state
528
+ state_to_store = {'value' => @value,
529
+ 'identity' => @identity,
530
+ 'uptime' => uptime,
531
+ 'reboot' => @reboot,
532
+ 'startup_tags' => @startup_tags,
533
+ 'log_level' => @log_level,
534
+ 'record_retries' => @record_retries,
535
+ 'last_recorded_value' => @last_recorded_value,
536
+ 'last_communication' => @last_communication,
537
+ 'last_observed_resource_uid' => @resource_uid}
538
+
539
+ # Only include deommission_type when decommissioning
540
+ state_to_store['decommission_type'] = @decommission_type if @value == 'decommissioning'
541
+
542
+ RightScale::JsonUtilities::write_json(STATE_FILE, state_to_store)
543
+ true
544
+ end
545
+
546
+ # Record state transition
547
+ # Cancel any active attempts to record state before doing this one
548
+ # Retry up to MAX_RECORD_STATE_RETRIES times if an error is returned
549
+ # If state has changed during a failed attempt, reset retry counter
550
+ #
551
+ # === Return
552
+ # true:: Always return true
553
+ def self.record_state
554
+ # Cancel any running request
555
+ if @record_request
556
+ @record_request.cancel("re-request")
557
+ if @record_timer
558
+ @record_timer.cancel
559
+ @record_timer = nil
560
+ end
561
+ end
562
+
563
+ # Create new request
564
+ new_value = @value
565
+ payload = {:agent_identity => @identity, :state => new_value, :from_state => @last_recorded_value}
566
+ @record_request = RightScale::IdempotentRequest.new("/state_recorder/record", payload)
567
+
568
+ # Handle success result
569
+ @record_request.callback do
570
+ @record_retries = 0
571
+ @record_request = nil
572
+ @last_recorded_value = new_value
573
+
574
+ # Store any change in local state, recorded state, or retry count
575
+ store_state
576
+ end
577
+
578
+ # Handle request failure
579
+ @record_request.errback do |error|
580
+ if error.is_a?(Hash) && error['recorded_state']
581
+ # State transitioning from does not match recorded state, so update last recorded value
582
+ @last_recorded_value = error['recorded_state']
583
+ error = error['message']
584
+ end
585
+ if error != "re-request" && @value != @last_recorded_value
586
+ attempts = " after #{@record_retries + 1} attempts" if @record_retries >= MAX_RECORD_STATE_RETRIES
587
+ Log.error("Failed to record state '#{new_value}'#{attempts}: #{error}") unless @value == FINAL_STATE
588
+ @record_retries = 0 if @value != new_value
589
+ if RECORDED_STATES.include?(@value) && @record_retries < MAX_RECORD_STATE_RETRIES
590
+ Log.info("Will retry recording state in #{RETRY_RECORD_STATE_DELAY} seconds")
591
+ @record_timer = EM::Timer.new(RETRY_RECORD_STATE_DELAY) do
592
+ if @value != @last_recorded_value
593
+ @record_retries += 1
594
+ @record_request = nil
595
+ @record_timer = nil
596
+ record_state
597
+ end
598
+ end
599
+ else
600
+ # Give up since out of retries or state has changed to a non-recorded value
601
+ @record_retries = 0
602
+ @record_request = nil
603
+ end
604
+
605
+ # Store any change in local state, recorded state, or retry count
606
+ store_state
607
+ end
608
+ end
609
+
610
+ # Run request to record state with retry until succeeds or fails with error result
611
+ @record_request.run
612
+ end
613
+
614
+ #
615
+ # retrieve the resource uid from the metadata
616
+ #
617
+ # === Return
618
+ # resource_uid(String|nil):: the resource uid or nil
619
+ def self.current_resource_uid
620
+ resource_uid = nil
621
+ begin
622
+ meta_data_file = ::File.join(AgentConfig.cloud_state_dir, 'meta-data-cache.rb')
623
+ # metadata does not exist on all clouds, hence the conditional
624
+ load(meta_data_file) if File.file?(meta_data_file)
625
+ resource_uid = ENV['EC2_INSTANCE_ID']
626
+ rescue Exception => e
627
+ Log.warning("Failed to load metadata", e)
628
+ end
629
+ resource_uid
630
+ end
631
+ end
632
+
633
+ end