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.
- data/actors/agent_manager.rb +88 -0
- data/actors/instance_scheduler.rb +321 -0
- data/actors/instance_services.rb +64 -0
- data/actors/instance_setup.rb +567 -0
- data/bin/cloud +25 -0
- data/bin/cook_runner +44 -0
- data/bin/deploy +120 -0
- data/bin/enroll +385 -0
- data/bin/rad +32 -0
- data/bin/rchk +29 -0
- data/bin/rnac +39 -0
- data/bin/rs_connect +33 -0
- data/bin/rs_log_level +31 -0
- data/bin/rs_ohai +28 -0
- data/bin/rs_reenroll +31 -0
- data/bin/rs_run_recipe +34 -0
- data/bin/rs_run_right_script +34 -0
- data/bin/rs_shutdown +33 -0
- data/bin/rs_tag +33 -0
- data/bin/rs_thunk +33 -0
- data/bin/rstat +31 -0
- data/bin/system +16 -0
- data/ext/Rakefile +18 -0
- data/init/config.yml +5 -0
- data/init/init.rb +79 -0
- data/lib/chef/ohai_setup.rb +51 -0
- data/lib/chef/plugins/cloud.rb +91 -0
- data/lib/chef/plugins/cloudstack.rb +23 -0
- data/lib/chef/plugins/ec2.rb +23 -0
- data/lib/chef/plugins/linux/block_device2.rb +24 -0
- data/lib/chef/plugins/rackspace.rb +23 -0
- data/lib/chef/plugins/rightscale.rb +125 -0
- data/lib/chef/plugins/windows/network.rb +114 -0
- data/lib/chef/plugins.rb +74 -0
- data/lib/chef/providers/dns_dnsmadeeasy_provider.rb +81 -0
- data/lib/chef/providers/dns_resource.rb +100 -0
- data/lib/chef/providers/executable_schedule_provider.rb +70 -0
- data/lib/chef/providers/executable_schedule_resource.rb +144 -0
- data/lib/chef/providers/remote_recipe_provider.rb +86 -0
- data/lib/chef/providers/remote_recipe_resource.rb +101 -0
- data/lib/chef/providers/right_link_tag_provider.rb +73 -0
- data/lib/chef/providers/right_link_tag_resource.rb +59 -0
- data/lib/chef/providers/right_script_provider.rb +190 -0
- data/lib/chef/providers/right_script_resource.rb +113 -0
- data/lib/chef/providers/rs_shutdown_provider.rb +75 -0
- data/lib/chef/providers/rs_shutdown_resource.rb +55 -0
- data/lib/chef/providers/server_collection_provider.rb +66 -0
- data/lib/chef/providers/server_collection_resource.rb +93 -0
- data/lib/chef/providers/windows/powershell_provider.rb +151 -0
- data/lib/chef/providers/windows/powershell_resource.rb +111 -0
- data/lib/chef/providers/windows/unsupported_provider.rb +51 -0
- data/lib/chef/right_providers.rb +55 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.csproj +104 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.dll-Help.xml +141 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Exceptions.cs +182 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionCommand.cs +178 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionRequest.cs +67 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionResponse.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueCommandBase.cs +142 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueRequestBase.cs +64 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueResponseBase.cs +69 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/JsonTransport.cs +110 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeClient.cs +158 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeServer.cs +142 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolConstants.cs +55 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolUtilities.cs +77 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ReadMe.txt +53 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeResponse.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceResponse.cs +40 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceResponse.cs +40 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueCommandBase.cs +293 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueRequestBase.cs +75 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueResponseBase.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Transport.cs +91 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet.sln +35 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Program.cs +374 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/TestChefNodeCmdlet.csproj +65 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Program.cs +136 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Properties/AssemblyInfo.cs +36 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/ReadMe.txt +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/TestNextActionCmdlet.csproj +68 -0
- data/lib/chef/windows/bin/Newtonsoft.Json.dll +0 -0
- data/lib/chef/windows/chef_node_server.rb +463 -0
- data/lib/chef/windows/dynamic_powershell_provider.rb +296 -0
- data/lib/chef/windows/pipe_server.rb +283 -0
- data/lib/chef/windows/powershell_host.rb +285 -0
- data/lib/chef/windows/powershell_pipe_server.rb +136 -0
- data/lib/chef/windows/powershell_provider_base.rb +92 -0
- data/lib/chef/windows/scripts/run_loop.ps1 +105 -0
- data/lib/clouds/cloud.rb +557 -0
- data/lib/clouds/cloud_factory.rb +250 -0
- data/lib/clouds/cloud_utilities.rb +244 -0
- data/lib/clouds/clouds/azure.rb +106 -0
- data/lib/clouds/clouds/cloudstack.rb +114 -0
- data/lib/clouds/clouds/ec2.rb +113 -0
- data/lib/clouds/clouds/eucalyptus.rb +46 -0
- data/lib/clouds/clouds/google.rb +102 -0
- data/lib/clouds/clouds/none.rb +76 -0
- data/lib/clouds/clouds/openstack.rb +30 -0
- data/lib/clouds/clouds/rackspace-ng.rb +54 -0
- data/lib/clouds/clouds/rackspace.rb +78 -0
- data/lib/clouds/clouds/softlayer.rb +91 -0
- data/lib/clouds/metadata_formatter.rb +108 -0
- data/lib/clouds/metadata_provider.rb +128 -0
- data/lib/clouds/metadata_source.rb +87 -0
- data/lib/clouds/metadata_sources/certificate_metadata_source.rb +207 -0
- data/lib/clouds/metadata_sources/config_drive_metadata_source.rb +129 -0
- data/lib/clouds/metadata_sources/file_metadata_source.rb +74 -0
- data/lib/clouds/metadata_sources/http_metadata_source.rb +277 -0
- data/lib/clouds/metadata_sources/selective_metadata_source.rb +122 -0
- data/lib/clouds/metadata_tree_climber.rb +144 -0
- data/lib/clouds/metadata_writer.rb +155 -0
- data/lib/clouds/metadata_writers/dictionary_metadata_writer.rb +72 -0
- data/lib/clouds/metadata_writers/ruby_metadata_writer.rb +76 -0
- data/lib/clouds/metadata_writers/shell_metadata_writer.rb +121 -0
- data/lib/clouds/register_clouds.rb +34 -0
- data/lib/clouds.rb +32 -0
- data/lib/gem_dependencies.rb +83 -0
- data/lib/git_hooks/commit-msg.rb +7 -0
- data/lib/instance/agent_config.rb +168 -0
- data/lib/instance/agent_watcher.rb +233 -0
- data/lib/instance/audit_cook_stub.rb +104 -0
- data/lib/instance/audit_proxy.rb +247 -0
- data/lib/instance/bundle_queue.rb +104 -0
- data/lib/instance/cook/agent_connection.rb +109 -0
- data/lib/instance/cook/audit_logger.rb +165 -0
- data/lib/instance/cook/audit_stub.rb +142 -0
- data/lib/instance/cook/ca-bundle.crt +2794 -0
- data/lib/instance/cook/chef_state.rb +211 -0
- data/lib/instance/cook/cook.rb +306 -0
- data/lib/instance/cook/cook_state.rb +298 -0
- data/lib/instance/cook/cookbook_path_mapping.rb +66 -0
- data/lib/instance/cook/cookbook_repo_retriever.rb +190 -0
- data/lib/instance/cook/executable_sequence.rb +765 -0
- data/lib/instance/cook/external_parameter_gatherer.rb +190 -0
- data/lib/instance/cook/repose_downloader.rb +349 -0
- data/lib/instance/cook/shutdown_request_proxy.rb +121 -0
- data/lib/instance/cook.rb +41 -0
- data/lib/instance/downloader.rb +208 -0
- data/lib/instance/duplicable.rb +67 -0
- data/lib/instance/exceptions.rb +49 -0
- data/lib/instance/executable_sequence_proxy.rb +278 -0
- data/lib/instance/instance_commands.rb +577 -0
- data/lib/instance/instance_state.rb +633 -0
- data/lib/instance/json_utilities.rb +102 -0
- data/lib/instance/login_manager.rb +533 -0
- data/lib/instance/login_user_manager.rb +522 -0
- data/lib/instance/message_encoder.rb +118 -0
- data/lib/instance/multi_thread_bundle_queue.rb +232 -0
- data/lib/instance/operation_context.rb +60 -0
- data/lib/instance/options_bag.rb +65 -0
- data/lib/instance/payload_formatter.rb +46 -0
- data/lib/instance/policy.rb +53 -0
- data/lib/instance/policy_audit.rb +100 -0
- data/lib/instance/policy_manager.rb +146 -0
- data/lib/instance/reenroll_manager.rb +104 -0
- data/lib/instance/right_scripts_cookbook.rb +181 -0
- data/lib/instance/shutdown_request.rb +221 -0
- data/lib/instance/single_thread_bundle_queue.rb +189 -0
- data/lib/instance/volume_management.rb +450 -0
- data/lib/instance.rb +50 -0
- data/lib/repo_conf_generators/apt_conf_generators.rb +106 -0
- data/lib/repo_conf_generators/gem_conf_generators.rb +80 -0
- data/lib/repo_conf_generators/rightscale_conf_generators.rb +254 -0
- data/lib/repo_conf_generators/rightscale_key.pub +17 -0
- data/lib/repo_conf_generators/yum_conf_generators.rb +225 -0
- data/lib/repo_conf_generators.rb +30 -0
- data/lib/run_shell.rb +28 -0
- data/scripts/agent_checker.rb +571 -0
- data/scripts/agent_controller.rb +247 -0
- data/scripts/agent_deployer.rb +148 -0
- data/scripts/bundle_runner.rb +336 -0
- data/scripts/cloud_controller.rb +176 -0
- data/scripts/log_level_manager.rb +142 -0
- data/scripts/ohai_runner.rb +33 -0
- data/scripts/reenroller.rb +193 -0
- data/scripts/server_importer.rb +293 -0
- data/scripts/shutdown_client.rb +183 -0
- data/scripts/system_configurator.rb +367 -0
- data/scripts/tagger.rb +381 -0
- data/scripts/thunker.rb +356 -0
- 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
|