right_link 5.9.0

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 (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