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,104 @@
1
+ #
2
+ # Copyright (c) 2009-2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightScale
24
+
25
+ # Tracks reenroll votes and trigger reenroll as necessary
26
+ class ReenrollManager
27
+
28
+ # Number of votes required to trigger re-enroll
29
+ REENROLL_THRESHOLD = 3
30
+
31
+ # Additional number of seconds that should be spent in offline mode before
32
+ # triggering a re-enroll after vote threshold has been reached
33
+ MAXIMUM_REENROLL_DELAY = 900 # 15 minutes
34
+
35
+ # Delay in seconds until votes count is reset if no more votes occur
36
+ # This value should be more than two hours as this is the period at which
37
+ # votes will get generated in offline mode
38
+ RESET_DELAY = 7200 # 2 hours
39
+
40
+ # Are we currently reenrolling?
41
+ #
42
+ # === Return
43
+ # true:: If we are currently re-enrolling (note: process is going away)
44
+ # false:: Otherwise
45
+ def self.reenrolling?
46
+ @reenrolling
47
+ end
48
+
49
+ # Set reenrolling flag
50
+ #
51
+ # === Return
52
+ # true:: Always return true
53
+ def self.set_reenrolling
54
+ @reenrolling = true
55
+ end
56
+
57
+ # Vote for re-enrolling, if threshold is reached re-enroll
58
+ # If no vote occurs in the next two hours, then reset counter
59
+ #
60
+ # === Return
61
+ # true:: Always return true
62
+ def self.vote
63
+ @total_votes ||= 0
64
+ @reenrolling ||= false
65
+ @total_votes += 1
66
+ @reset_timer.cancel if @reset_timer
67
+ @reset_timer = EM::Timer.new(RESET_DELAY) { reset_votes }
68
+ if @total_votes >= REENROLL_THRESHOLD && !@reenrolling
69
+ delay = rand(MAXIMUM_REENROLL_DELAY)
70
+ Log.info("[re-enroll] Re-enroll threshold reached, shutting down and re-enrolling in #{delay} seconds")
71
+ set_reenrolling
72
+ EM::Timer.new(delay) { reenroll! }
73
+ end
74
+ true
75
+ end
76
+
77
+ # Reset votes count
78
+ #
79
+ # === Return
80
+ # true:: Always return true
81
+ def self.reset_votes
82
+ @total_votes = 0
83
+ @reset_timer = nil
84
+ true
85
+ end
86
+
87
+ # Trigger reenroll
88
+ #
89
+ # === Parameters
90
+ # args(String):: Arguments to be passed through to rs_reenroll
91
+ #
92
+ # === Return
93
+ # true:: If command ran successfully
94
+ # false:: Otherwise
95
+ def self.reenroll!(args=nil)
96
+ cmd = "rs_reenroll #{args}"
97
+ cmd += '&' unless RightScale::Platform.windows?
98
+ AMQP.stop { EM.stop } rescue nil
99
+ system(cmd)
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,181 @@
1
+ #
2
+ # Copyright (c) 2009-2012 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
+
25
+ module RightScale
26
+
27
+ # Generate recipes dynamically for RightScripts
28
+ # Usage is:
29
+ # 1. Call 'recipe_from_right_script' for each RightScript that should be converted to a recipe
30
+ # 2. Call 'save' before running Chef, 'recipe_from_right_script' cannot be called after 'save'
31
+ # 3. Use 'repo_dir' to retrieve the Chef cookbook repo path (can be called at any time)
32
+ class RightScriptsCookbook
33
+
34
+ # Name of cookbook containing RightScript recipes
35
+ COOKBOOK_NAME = 'right_scripts_cookbook'
36
+
37
+ # Path to generated cookbook repo
38
+ attr_reader :repo_dir
39
+
40
+ # Wheter 'save' has been called
41
+ attr_reader :saved
42
+
43
+ # Setup temporary directory for cookbook repo containing
44
+ # recipes generated from RightScripts
45
+ def initialize(thread_name)
46
+ @saved = false
47
+ @recipes = {}
48
+ @repo_dir = File.join(AgentConfig.right_scripts_repo_dir, thread_name)
49
+ @cookbook_dir = File.join(@repo_dir, COOKBOOK_NAME)
50
+ @recipes_dir = File.join(@cookbook_dir, 'recipes')
51
+ FileUtils.rm_rf(@cookbook_dir)
52
+ FileUtils.mkdir_p(@recipes_dir)
53
+ end
54
+
55
+ # Add RightScript instantiation to cookbook
56
+ #
57
+ # === Parameters
58
+ # script(RightScale::RightScriptInstantiation):: RightScript to be added
59
+ #
60
+ # === Return
61
+ # recipe(RightScale::RecipeInstantiation):: Recipe that wraps RightScript
62
+ #
63
+ # === Raise
64
+ # (RightScale::Exceptions::Application):: If 'save' has been called
65
+ def recipe_from_right_script(script)
66
+ raise RightScale::Exceptions::Application, 'cannot create recipe after cookbook repo has been saved' if @saved
67
+ path = script_path(script.nickname)
68
+ recipe_name = File.basename(path)
69
+ @recipes[recipe_name] = script.nickname
70
+ recipe_content = <<-EOS
71
+ right_script '#{script.nickname.gsub("'", "\\\\'")}' do
72
+ parameters(node["#{script.nickname}"]["parameters"])
73
+ cache_dir '#{cache_dir(script)}'
74
+ source_file '#{path}'
75
+ display_version '#{script.display_version.to_s.strip.gsub("'", "\\\\'")}'
76
+ end
77
+ EOS
78
+ File.open(path, 'w') { |f| f.puts script.source }
79
+ File.chmod(0744, path)
80
+ recipe_path = "#{path}.rb"
81
+ File.open(recipe_path, 'w') { |f| f.puts recipe_content }
82
+
83
+ recipe = RecipeInstantiation.new("#{COOKBOOK_NAME}::#{recipe_name}",
84
+ { script.nickname => { "parameters" => script.parameters } },
85
+ script.id, script.ready)
86
+
87
+ end
88
+
89
+ # Produce file name for given script nickname
90
+ #
91
+ # === Parameters
92
+ # nickname(String):: Script nick name
93
+ #
94
+ # === Return
95
+ # path(String):: Path to corresponding recipe
96
+ def script_path(nickname)
97
+ base_path = nickname.gsub(/[^0-9a-zA-Z_]/,'_')
98
+ base_path = File.join(@recipes_dir, base_path)
99
+ candidate_path = RightScale::Platform.shell.format_script_file_name(base_path)
100
+ i = 1
101
+ path = candidate_path
102
+ path = candidate_path + (i += 1).to_s while File.exists?(path)
103
+ path
104
+ end
105
+
106
+ # Save cookbook repo
107
+ #
108
+ # === Return
109
+ # true:: Always return true
110
+ def save
111
+ unless empty?
112
+ recipes = @recipes.keys.sort
113
+ metadata_content = <<-EOS
114
+ description "Automatically generated repo, do not modify"
115
+ #{recipes.map { |r| "recipe \"#{COOKBOOK_NAME}::#{r}\", \"RightScript < #{@recipes[r]} >\"" }.join("\n")}
116
+ EOS
117
+ metadata_path = File.join(@cookbook_dir, 'metadata.rb')
118
+ File.open(metadata_path, 'w') { |f| f.puts metadata_content }
119
+ end
120
+ @saved = true
121
+ end
122
+
123
+ # Whether given recipe name corresponds to a converted RightScript
124
+ #
125
+ # === Parameters
126
+ # nickname(String):: Recipe nickname
127
+ #
128
+ # === Return
129
+ # true:: If recipe was created from converting a RightScript
130
+ # false:: Otherwise
131
+ def self.right_script?(nickname)
132
+ # the RightScripts deserialized from core appear to not have the
133
+ # RightScript cookbook in their nickname. actual Chef recipes will have
134
+ # their cookbook name, so assume missing cookbook name means RightScript.
135
+ regex = /^(.*)::/
136
+ match = regex.match(nickname)
137
+ return match.nil? || COOKBOOK_NAME == match[1]
138
+ end
139
+
140
+ # Human friendly title for given recipe instantiation
141
+ #
142
+ # === Parameters
143
+ # recipe(String):: Recipe nickname
144
+ #
145
+ # === Return
146
+ # title(String):: Recipe title to be used in audits
147
+ def self.recipe_title(recipe)
148
+ title = right_script?(recipe) ? 'RightScript' : 'recipe'
149
+ title = "#{title} < #{recipe} >"
150
+ end
151
+
152
+ # Path to cache directory for given script
153
+ #
154
+ # === Parameters
155
+ # script(Object):: script object of some kind (e.g. RightScale::RightScriptInstantiation)
156
+ #
157
+ # === Return
158
+ # path(String):: Path to directory used for attachments and source
159
+ def cache_dir(script)
160
+ # prefix object ID with a text constant to make a legal directory name
161
+ # in case object id is negative (Ubuntu, etc.). this method will be called
162
+ # more than once and must return the same directory each time for a given
163
+ # script instantiation.
164
+ path = File.normalize_path(File.join(AgentConfig.cache_dir, 'right_scripts_content', "rs_attach" + script.object_id.to_s))
165
+
166
+ # convert to native format for ease of scripting in Windows, etc. the
167
+ # normalized path is normal for Ruby but not necessarily for native FS.
168
+ return RightScale::Platform.filesystem.pretty_path(path, true)
169
+ end
170
+
171
+ # Is there no RightScript recipe in repo?
172
+ #
173
+ # === Return
174
+ # true:: If +recipe_from_right_script+ was never called
175
+ # false:: Otherwise
176
+ def empty?
177
+ @recipes.empty?
178
+ end
179
+
180
+ end
181
+ end
@@ -0,0 +1,221 @@
1
+ #
2
+ # Copyright (c) 2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightScale
24
+
25
+ # Mixin for defining a common interface for a shutdown request class and its
26
+ # proxy class.
27
+ module ShutdownRequestMixin
28
+
29
+ # initial kind; no shutdown requested.
30
+ CONTINUE = 'continue'
31
+
32
+ # requested a reboot.
33
+ REBOOT = 'reboot'
34
+
35
+ # requested a stop (keep boot volume after shutdown).
36
+ STOP = 'stop'
37
+
38
+ # requested a terminate (discard boot volume after shutdown).
39
+ TERMINATE = 'terminate'
40
+
41
+ # levels.
42
+ LEVELS = [CONTINUE, REBOOT, STOP, TERMINATE]
43
+
44
+ # exceptions.
45
+ class NotInitialized < Exception; end
46
+ class InvalidLevel < Exception; end
47
+
48
+
49
+ # true if no shutdown was requested, false if shutdown was requested.
50
+ def continue?; CONTINUE == @level; end
51
+
52
+ # true if any requested shutdown will interrupt sequence of running scripts
53
+ # immediately (current script is allowed to complete).
54
+ # false to defer shutdown until all outstanding scripts have run.
55
+ def immediately?; @immediately; end
56
+ def immediately!
57
+ raise InvalidLevel.new("Immediately is unexpected for current shutdown state") if continue?
58
+ @immediately = true
59
+ end
60
+
61
+ # Shutdown request level.
62
+ def level; @level; end
63
+ def level=(value)
64
+ value = value.to_s
65
+ raise InvalidLevel.new("Invalid shutdown level: #{value.inspect}") unless LEVELS.include?(value)
66
+
67
+ # strictly escalate to higher level and ignore lower level requests.
68
+ @level = value if LEVELS.index(value) > LEVELS.index(@level)
69
+ end
70
+
71
+ # Stringizer.
72
+ def to_s
73
+ # note that printing 'deferred' would seem strange at the time when the
74
+ # deferred shutdown is actually being processed, so only say immediately.
75
+ immediacy = if @immediately; ' immediately'; else; ''; end
76
+ return "#{@level}#{immediacy}"
77
+ end
78
+
79
+ protected
80
+
81
+ def initialize
82
+ @level = CONTINUE
83
+ @immediately = false
84
+ end
85
+
86
+ end
87
+
88
+ # Represents outstanding request(s) for reboot, stop or terminate instance.
89
+ # Requests are cumulative and implicitly non-decreasing in level (e.g. reboot
90
+ # never superceeds terminate).
91
+ class ShutdownRequest
92
+
93
+ include ShutdownRequestMixin
94
+
95
+ # Class initializer.
96
+ #
97
+ # === Parameters
98
+ # scheduler(InstanceScheduler):: scheduler for shutdown requests
99
+ #
100
+ # === Return
101
+ # always true
102
+ def self.init(scheduler)
103
+ @@instance = ShutdownRequest.new
104
+ @@scheduler = scheduler
105
+ true
106
+ end
107
+
108
+ # Factory method
109
+ #
110
+ # === Return
111
+ # (ShutdownRequest):: the singleton for this class
112
+ def self.instance
113
+ raise NotInitialized.new("ShutdownRequest.init has not been called") unless defined?(@@instance)
114
+ return @@instance
115
+ end
116
+
117
+ # Submits a new shutdown request state which may be superceded by a
118
+ # previous, higher-priority shutdown level.
119
+ #
120
+ # === Parameters
121
+ # request[:level](String):: shutdown level
122
+ # request[:immediately](Boolean):: shutdown immediacy or nil
123
+ #
124
+ # === Returns
125
+ # result(ShutdownRequest):: the updated instance
126
+ def self.submit(request)
127
+ # RightNet protocols use kind instead of level, so be a little flexible.
128
+ result = instance
129
+ result.level = request[:kind] || request[:level]
130
+ result.immediately! if request[:immediately]
131
+ @@scheduler.schedule_shutdown unless result.continue?
132
+ return result
133
+ end
134
+
135
+ # Processes shutdown requests by communicating the need to shutdown an
136
+ # instance with the core agent, if necessary.
137
+ #
138
+ # === Parameters
139
+ # errback(Proc):: error handler or nil
140
+ # audit(Audit):: audit for shutdown action, if needed, or nil.
141
+ #
142
+ # === Block
143
+ # block(Proc):: continuation block for successful handling of shutdown or nil
144
+ #
145
+ # === Return
146
+ # always true
147
+ def process(errback = nil, audit = nil, &block)
148
+ # yield if not shutting down (continuing) or if already requested shutdown.
149
+ if continue? || @shutdown_scheduled
150
+ block.call if block
151
+ return true
152
+ end
153
+
154
+ # ensure we have an audit, creating a temporary audit if necessary.
155
+ sender = Sender.instance
156
+ agent_identity = sender.identity
157
+ if audit
158
+ case @level
159
+ when REBOOT, STOP, TERMINATE
160
+ operation = "/forwarder/shutdown"
161
+ payload = {:agent_identity => agent_identity, :kind => @level}
162
+ else
163
+ raise InvalidLevel.new("Unexpected shutdown level: #{@level.inspect}")
164
+ end
165
+
166
+ # request shutdown (kind indicated by operation and/or payload).
167
+ audit.append_info("Shutdown requested: #{self}")
168
+ sender.send_persistent_request(operation, payload) do |r|
169
+ res = OperationResult.from_results(r)
170
+ if res.success?
171
+ @shutdown_scheduled = true
172
+ block.call if block
173
+ else
174
+ fail(errback, audit, "Failed to shutdown instance", res)
175
+ end
176
+ end
177
+ else
178
+ AuditProxy.create(agent_identity, "Shutdown requested: #{self}") do |new_audit|
179
+ process(errback, new_audit, &block)
180
+ end
181
+ end
182
+ true
183
+ rescue Exception => e
184
+ fail(errback, audit, e)
185
+ end
186
+
187
+ protected
188
+
189
+ def initialize
190
+ super
191
+ @shutdown_scheduled = false
192
+ end
193
+
194
+ # Handles any shutdown failure.
195
+ #
196
+ # === Parameters
197
+ # audit(Audit):: Audit or nil
198
+ # errback(Proc):: error handler or nil
199
+ # msg(String):: Error message that will be audited and logged
200
+ # res(RightScale::OperationResult):: Operation result with additional information
201
+ #
202
+ # === Return
203
+ # always true
204
+ def fail(errback, audit, msg, res = nil)
205
+ if msg.kind_of?(Exception)
206
+ e = msg
207
+ detailed = Log.format("Could not process shutdown state #{self}", e, :trace)
208
+ msg = e.message
209
+ else
210
+ detailed = nil
211
+ end
212
+ msg += ": #{res.content}" if res && res.content
213
+ audit.append_error(msg, :category => RightScale::EventCategories::CATEGORY_ERROR) if audit
214
+ Log.error(detailed) if detailed
215
+ errback.call if errback
216
+ true
217
+ end
218
+
219
+ end # ShutdownRequest
220
+
221
+ end # RightScale
@@ -0,0 +1,189 @@
1
+ #
2
+ # Copyright (c) 2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightScale
24
+
25
+ class SingleThreadBundleQueue < BundleQueue
26
+
27
+ attr_reader :thread_name
28
+
29
+ # Set continuation block to be called after 'close' is called
30
+ #
31
+ # === Block
32
+ # continuation block
33
+ def initialize(thread_name = ::RightScale::AgentConfig.default_thread_name, &continuation)
34
+ super(&continuation)
35
+ @active = false
36
+ @thread = nil
37
+ @thread_name = thread_name
38
+ @pid = nil
39
+ @mutex = Mutex.new
40
+ @queue = Queue.new
41
+ @sequence_finished = ConditionVariable.new
42
+ end
43
+
44
+ # Determines if queue is active
45
+ #
46
+ # === Return
47
+ # active(Boolean):: true if queue is active
48
+ def active?
49
+ active = false
50
+ @mutex.synchronize { active = @active }
51
+ return active
52
+ end
53
+
54
+ # Activate queue for execution, idempotent
55
+ # Any pending bundle will be run sequentially in order
56
+ #
57
+ # === Return
58
+ # true:: Always return true
59
+ def activate
60
+ @mutex.synchronize do
61
+ unless @active
62
+ @thread = Thread.new { run }
63
+ @active = true
64
+ end
65
+ end
66
+ true
67
+ end
68
+
69
+ # Determines if queue is busy
70
+ #
71
+ # === Return
72
+ # active(Boolean):: true if queue is busy
73
+ def busy?
74
+ busy = false
75
+ @mutex.synchronize { busy = !!@busy }
76
+ busy
77
+ end
78
+
79
+ # Push new context to bundle queue and run next bundle
80
+ #
81
+ # === Return
82
+ # true:: Always return true
83
+ def push(context)
84
+ @queue << context
85
+ true
86
+ end
87
+
88
+ # Clear queue content
89
+ #
90
+ # === Return
91
+ # true:: Always return true
92
+ def clear
93
+ @queue.clear
94
+ true
95
+ end
96
+
97
+ # Close queue so that further call to 'push' will be ignored
98
+ #
99
+ # === Return
100
+ # true:: Always return true
101
+ def close
102
+ push(FINAL_BUNDLE)
103
+ end
104
+
105
+ protected
106
+
107
+ # Run next bundle in the queue if active
108
+ # If bundle is FINAL_BUNDLE then call continuation block and deactivate
109
+ #
110
+ # === Return
111
+ # true:: Always return true
112
+ def run
113
+ loop do
114
+ context = @queue.shift
115
+ if context == FINAL_BUNDLE
116
+ break
117
+ elsif context == SHUTDOWN_BUNDLE
118
+ # process shutdown request.
119
+ ShutdownRequest.instance.process
120
+ # continue in queue in the expectation that the decommission bundle will
121
+ # shutdown the instance and its agent normally.
122
+ elsif !context.decommission? && ShutdownRequest.instance.immediately?
123
+ # immediate shutdown pre-empts any futher attempts to run operational
124
+ # scripts but still allows the decommission bundle to run.
125
+ context.audit.update_status("Skipped bundle due to immediate shutdown of #{@thread_name} thread: #{context.payload}")
126
+ # proceed ignoring bundles until final or shutdown are encountered.
127
+ else
128
+ sequence = create_sequence(context)
129
+ sequence.callback { audit_status(sequence) }
130
+ sequence.errback { audit_status(sequence) }
131
+
132
+ # wait until sequence is finished using a ruby mutex conditional.
133
+ # need to synchronize before run to ensure we are waiting before any
134
+ # immediate signalling occurs (under test conditions, etc.).
135
+ @mutex.synchronize do
136
+ @busy = true
137
+ sequence.run
138
+ @sequence_finished.wait(@mutex)
139
+ @pid = nil
140
+ @busy = false
141
+ end
142
+ end
143
+ end
144
+ true
145
+ rescue Exception => e
146
+ Log.error(Log.format("SingleThreadBundleQueue.run failed for #{@thread_name} thread", e, :trace))
147
+ ensure
148
+ # invoke continuation (off of this thread which is going away).
149
+ @mutex.synchronize { @active = false }
150
+ run_continuation
151
+ @thread = nil
152
+ end
153
+
154
+ # Factory method for a new sequence.
155
+ #
156
+ # context(RightScale::OperationContext)
157
+ def create_sequence(context)
158
+ pid_callback = lambda do |sequence|
159
+ # TODO preserve cook PIDs per thread in InstanceState and recover
160
+ # orphaned cook in case of agent crash.
161
+ @mutex.synchronize { @pid = sequence.pid }
162
+ end
163
+ return RightScale::ExecutableSequenceProxy.new(context, :pid_callback => pid_callback )
164
+ end
165
+
166
+ # Audit executable sequence status after it ran
167
+ #
168
+ # === Parameters
169
+ # sequence(RightScale::ExecutableSequence):: finished sequence being audited
170
+ #
171
+ # === Return
172
+ # true:: Always return true
173
+ def audit_status(sequence)
174
+ context = sequence.context
175
+ title = context.decommission? ? 'decommission ' : ''
176
+ title += context.succeeded ? 'completed' : 'failed'
177
+ context.audit.update_status("#{title}: #{context.payload}")
178
+ true
179
+ rescue Exception => e
180
+ Log.error(Log.format("SingleThreadBundleQueue.audit_status failed for #{@thread_name} thread", e, :trace))
181
+ ensure
182
+ # release queue thread to wait on next bundle in queue. we must ensure
183
+ # that we are not currently on the queue thread so next-tick the signal.
184
+ EM.next_tick { @mutex.synchronize { @sequence_finished.signal } }
185
+ end
186
+
187
+ end
188
+
189
+ end