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,450 @@
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
+ class VolumeManagement
26
+ # Delay enough time for attach/detach state in core to refresh
27
+ VOLUME_RETRY_SECONDS = 15 # minimum retry time as recommended in docs when volumes are changing by automation
28
+
29
+ # Max retries for attaching/detaching a given volume.
30
+ MAX_VOLUME_ATTEMPTS = 8 # 8 * 15 seconds = 2 minutes
31
+
32
+ class InvalidResponse < Exception; end
33
+ class UnexpectedState < Exception; end
34
+ class UnsupportedMountPoint < Exception; end
35
+ end
36
+
37
+ # Provides helper methods for platforms which must manage volume attachments on the
38
+ # instance agent side
39
+ module VolumeManagementHelper
40
+
41
+ protected
42
+
43
+ # Manages planned volumes by caching planned volume state and then ensuring
44
+ # volumes have been reattached in a predictable order for proper assignment
45
+ # of local drives.
46
+ #
47
+ # === Parameters
48
+ # block(Proc):: continuation callback for when volume management is complete.
49
+ #
50
+ # === Return
51
+ # result(Boolean):: true if successful
52
+ def manage_planned_volumes(&block)
53
+ # state may have changed since timer calling this method was added, so
54
+ # ensure we are still booting (and not stranded).
55
+ return if InstanceState.value == 'stranded'
56
+
57
+ # query for planned volume mappings belonging to instance.
58
+ last_mappings = InstanceState.planned_volume_state.mappings || []
59
+ payload = {:agent_identity => @agent_identity}
60
+ req = IdempotentRequest.new("/storage_valet/get_planned_volumes", payload, :retry_delay => VolumeManagement::VOLUME_RETRY_SECONDS)
61
+ req.callback do |res|
62
+ begin
63
+ mappings = merge_planned_volume_mappings(last_mappings, res)
64
+ InstanceState.planned_volume_state.mappings = mappings
65
+ if mappings.empty?
66
+ # no volumes requiring management.
67
+ @audit.append_info("This instance has no planned volumes.")
68
+ block.call if block
69
+ elsif (detachable_volume_count = mappings.count { |mapping| is_unmanaged_attached_volume?(mapping) }) >= 1
70
+ # must detach all 'attached' volumes if any are attached (or
71
+ # attaching) but not yet managed on the instance side. this is the
72
+ # only way to ensure they receive the correct device names.
73
+ mappings.each do |mapping|
74
+ if is_unmanaged_attached_volume?(mapping)
75
+ detach_planned_volume(mapping) do
76
+ detachable_volume_count -= 1
77
+ if 0 == detachable_volume_count
78
+ # add a timer to resume volume management later and pass the
79
+ # block for continuation afterward (unless detachment stranded).
80
+ Log.info("Waiting for volumes to detach for management purposes. "\
81
+ "Retrying in #{VolumeManagement::VOLUME_RETRY_SECONDS} seconds...")
82
+ EM.add_timer(VolumeManagement::VOLUME_RETRY_SECONDS) { manage_planned_volumes(&block) }
83
+ end
84
+ end
85
+ end
86
+ end
87
+ elsif mapping = mappings.find { |mapping| is_detaching_volume?(mapping) }
88
+ # we successfully requested detachment but status has not
89
+ # changed to reflect this yet.
90
+ Log.info("Waiting for volume #{mapping[:volume_id]} to fully detach. "\
91
+ "Retrying in #{VolumeManagement::VOLUME_RETRY_SECONDS} seconds...")
92
+ EM.add_timer(VolumeManagement::VOLUME_RETRY_SECONDS) { manage_planned_volumes(&block) }
93
+ elsif mapping = mappings.find { |mapping| is_managed_attaching_volume?(mapping) }
94
+ Log.info("Waiting for volume #{mapping[:volume_id]} to fully attach. Retrying in #{VolumeManagement::VOLUME_RETRY_SECONDS} seconds...")
95
+ EM.add_timer(VolumeManagement::VOLUME_RETRY_SECONDS) { manage_planned_volumes(&block) }
96
+ elsif mapping = mappings.find { |mapping| is_managed_attached_unassigned_volume?(mapping) }
97
+ manage_volume_device_assignment(mapping) do
98
+ unless InstanceState.value == 'stranded'
99
+ # we can move on to next volume 'immediately' if volume was
100
+ # successfully assigned its device name.
101
+ if mapping[:management_status] == 'assigned'
102
+ EM.next_tick { manage_planned_volumes(&block) }
103
+ else
104
+ Log.info("Waiting for volume #{mapping[:volume_id]} to initialize using \"#{mapping[:mount_points].first}\". "\
105
+ "Retrying in #{VolumeManagement::VOLUME_RETRY_SECONDS} seconds...")
106
+ EM.add_timer(VolumeManagement::VOLUME_RETRY_SECONDS) { manage_planned_volumes(&block) }
107
+ end
108
+ end
109
+ end
110
+ elsif mapping = mappings.find { |mapping| is_detached_volume?(mapping) }
111
+ attach_planned_volume(mapping) do
112
+ unless InstanceState.value == 'stranded'
113
+ unless mapping[:attempts]
114
+ @audit.append_info("Attached volume #{mapping[:volume_id]} using \"#{mapping[:mount_points].first}\".")
115
+ Log.info("Waiting for volume #{mapping[:volume_id]} to appear using \"#{mapping[:mount_points].first}\". "\
116
+ "Retrying in #{VolumeManagement::VOLUME_RETRY_SECONDS} seconds...")
117
+ end
118
+ EM.add_timer(VolumeManagement::VOLUME_RETRY_SECONDS) { manage_planned_volumes(&block) }
119
+ end
120
+ end
121
+ elsif mapping = mappings.find { |mapping| is_unmanageable_volume?(mapping) }
122
+ strand("State of volume #{mapping[:volume_id]} was unmanageable: #{mapping[:volume_status]}")
123
+ else
124
+ # all volumes are managed and have been assigned and so we can proceed.
125
+ block.call if block
126
+ end
127
+ rescue Exception => e
128
+ strand(e)
129
+ end
130
+ end
131
+
132
+ req.errback do |res|
133
+ strand("Failed to retrieve planned volume mappings", res)
134
+ end
135
+
136
+ req.run
137
+ end
138
+
139
+ # Detaches the planned volume given by its mapping.
140
+ #
141
+ # === Parameters
142
+ # mapping(Hash):: details of planned volume
143
+ def detach_planned_volume(mapping)
144
+ payload = {:agent_identity => @agent_identity, :device_name => mapping[:device_name]}
145
+ Log.info("Detaching volume #{mapping[:volume_id]} for management purposes.")
146
+ req = IdempotentRequest.new("/storage_valet/detach_volume", payload, :retry_delay => VolumeManagement::VOLUME_RETRY_SECONDS)
147
+
148
+ req.callback do |res|
149
+ # don't set :volume_status here as that should only be queried
150
+ mapping[:management_status] = 'detached'
151
+ mapping[:attempts] = nil
152
+ yield if block_given?
153
+ end
154
+
155
+ req.errback do |res|
156
+ unless InstanceState.value == 'stranded'
157
+ # volume could already be detaching or have been deleted
158
+ # which we can't see because of latency; go around again
159
+ # and check state of volume later.
160
+ Log.error("Failed to detach volume #{mapping[:volume_id]}: #{res}")
161
+ mapping[:attempts] ||= 0
162
+ mapping[:attempts] += 1
163
+ # retry indefinitely so long as core api instructs us to retry or else fail after max attempts.
164
+ if mapping[:attempts] >= VolumeManagement::MAX_VOLUME_ATTEMPTS
165
+ strand("Exceeded maximum of #{VolumeManagement::MAX_VOLUME_ATTEMPTS} attempts detaching volume #{mapping[:volume_id]} with error: #{res}")
166
+ else
167
+ yield if block_given?
168
+ end
169
+ end
170
+ end
171
+
172
+ req.run
173
+ end
174
+
175
+ # Attaches the planned volume given by its mapping.
176
+ #
177
+ # === Parameters
178
+ # mapping(Hash):: details of planned volume
179
+ def attach_planned_volume(mapping)
180
+ # preserve the initial list of disks/volumes before attachment for comparison later.
181
+ vm = RightScale::Platform.volume_manager
182
+ InstanceState.planned_volume_state.disks ||= vm.disks
183
+ InstanceState.planned_volume_state.volumes ||= vm.volumes
184
+
185
+ # attach.
186
+ payload = {:agent_identity => @agent_identity, :volume_id => mapping[:volume_id], :device_name => mapping[:device_name]}
187
+ Log.info("Attaching volume #{mapping[:volume_id]}.")
188
+ req = IdempotentRequest.new("/storage_valet/attach_volume", payload, :retry_delay => VolumeManagement::VOLUME_RETRY_SECONDS)
189
+
190
+ req.callback do |res|
191
+ # don't set :volume_status here as that should only be queried
192
+ mapping[:management_status] = 'attached'
193
+ mapping[:attempts] = nil
194
+ yield if block_given?
195
+ end
196
+
197
+ req.errback do |res|
198
+ # volume could already be attaching or have been deleted
199
+ # which we can't see because of latency; go around again
200
+ # and check state of volume later.
201
+ Log.error("Failed to attach volume #{mapping[:volume_id]}: #{res}")
202
+ mapping[:attempts] ||= 0
203
+ mapping[:attempts] += 1
204
+ # retry indefinitely so long as core api instructs us to retry or else fail after max attempts.
205
+ if mapping[:attempts] >= VolumeManagement::MAX_VOLUME_ATTEMPTS
206
+ strand("Exceeded maximum of #{VolumeManagement::MAX_VOLUME_ATTEMPTS} attempts attaching volume #{mapping[:volume_id]} with error: #{res}")
207
+ else
208
+ yield if block_given?
209
+ end
210
+ end
211
+
212
+ req.run
213
+ end
214
+
215
+ # Manages device assignment for volumes with considerations for formatting
216
+ # blank attached volumes.
217
+ #
218
+ # === Parameters
219
+ # mapping(Hash):: details of planned volume
220
+ def manage_volume_device_assignment(mapping)
221
+
222
+ # only managed volumes should be in an attached state ready for assignment.
223
+ unless 'attached' == mapping[:management_status]
224
+ raise VolumeManagement::UnexpectedState.new("The volume #{mapping[:volume_id]} was in an unexpected managed state: #{mapping.inspect}")
225
+ end
226
+
227
+ # check for changes in disks.
228
+ last_disks = InstanceState.planned_volume_state.disks
229
+ last_volumes = InstanceState.planned_volume_state.volumes
230
+ vm = RightScale::Platform.volume_manager
231
+ current_disks = vm.disks
232
+ current_volumes = vm.volumes
233
+
234
+ # correctly managing device assignment requires expecting precise changes
235
+ # to disks and volumes. any deviation from this requires a retry.
236
+ succeeded = false
237
+ if new_disk = find_distinct_item(current_disks, last_disks, :index)
238
+ # if the new disk as no partitions, then we will format and assign device.
239
+ if vm.partitions(new_disk[:index]).empty?
240
+ # FIX: ignore multiple mount points for simplicity and only only create
241
+ # a single primary partition for the first mount point.
242
+ # if we had the UI for it, then the user would probably specify
243
+ # partition sizes as a percentage of disk size and associate those with
244
+ # mount points formatted optionally specifying file system, label, etc.
245
+ @audit.append_info("Creating primary partition and formatting \"#{mapping[:mount_points].first}\".")
246
+ vm.format_disk(new_disk[:index], mapping[:mount_points].first)
247
+ succeeded = true
248
+ else
249
+ # FIX: ignoring multiple existing partitiions on a disk (which should
250
+ # result in multiple new volumes appearing when the disk comes online)
251
+ # for simplicity until we have a UI supporting multiple mount points.
252
+ @audit.append_info("Preparing \"#{mapping[:mount_points].first}\" for use.")
253
+ new_volume = find_distinct_item(current_volumes, last_volumes, :device)
254
+ unless new_volume
255
+ vm.online_disk(new_disk[:index])
256
+ current_volumes = vm.volumes
257
+ new_volume = find_distinct_item(current_volumes, last_volumes, :device)
258
+ end
259
+ if new_volume
260
+ # prefer selection by existing device because it is more reliable in Windows 2003 case.
261
+ unless new_volume[:device] && (0 == new_volume[:device].casecmp(mapping[:mount_points].first))
262
+ device_or_index_to_select = new_volume[:device] || new_volume[:index]
263
+ vm.assign_device(device_or_index_to_select, mapping[:mount_points].first)
264
+ end
265
+ succeeded = true
266
+ end
267
+ end
268
+ end
269
+
270
+ # retry only if still not assigned.
271
+ if succeeded
272
+ # volume is (finally!) assigned to correct device name.
273
+ mapping[:management_status] = 'assigned'
274
+ mapping[:attempts] = nil
275
+
276
+ # reset cached volumes/disks for next attempt (to attach), if any.
277
+ InstanceState.planned_volume_state.disks = nil
278
+ InstanceState.planned_volume_state.volumes = nil
279
+
280
+ # continue.
281
+ yield if block_given?
282
+ else
283
+ mapping[:attempts] ||= 0
284
+ mapping[:attempts] += 1
285
+ if mapping[:attempts] >= VolumeManagement::MAX_VOLUME_ATTEMPTS
286
+ strand("Exceeded maximum of #{VolumeManagement::MAX_VOLUME_ATTEMPTS} attempts waiting for volume #{mapping[:volume_id]} to be in a managable state.")
287
+ else
288
+ yield if block_given?
289
+ end
290
+ end
291
+ rescue Exception => e
292
+ strand(e)
293
+ end
294
+
295
+ # Determines a single, unique item (hash) by given key in the current
296
+ # list which does not appear in the last list, if any. finds nothing if
297
+ # multiple new items appear. This is useful for inspecting lists of
298
+ # disks/volumes to determine when a new item appears.
299
+ #
300
+ # === Parameter
301
+ # current_list(Array):: current list
302
+ # last_list(Array):: last list
303
+ # key(Symbol):: key used to uniquely identify items
304
+ #
305
+ # === Return
306
+ # result(Hash):: item in current list which does not appear in last list or nil
307
+ def find_distinct_item(current_list, last_list, key)
308
+ if current_list.size == last_list.size + 1
309
+ unique_values = current_list.map { |item| item[key] } - last_list.map { |item| item[key] }
310
+ if unique_values.size == 1
311
+ unique_value = unique_values[0]
312
+ return current_list.find { |item| item[key] == unique_value }
313
+ end
314
+ end
315
+ return nil
316
+ end
317
+
318
+ # Determines if the given volume is in an attached (or attaching) state and
319
+ # not yet managed (in a managed state). Note that we can assign drive letters
320
+ # to 'attaching' volumes because they may appear locally before the repository
321
+ # can update their status to 'attached'.
322
+ #
323
+ # === Return
324
+ # result(Boolean):: true if volume is attached and unassigned
325
+ def is_unmanaged_attached_volume?(mapping)
326
+ case mapping[:volume_status]
327
+ when 'attached', 'attaching'
328
+ mapping[:management_status].nil?
329
+ else
330
+ false
331
+ end
332
+ end
333
+
334
+ # Determines if the given volume is detaching regardless of whether detachment
335
+ # was managed (will adopt any detatching volumes).
336
+ #
337
+ # === Return
338
+ # result(Boolean):: true if volume is detaching
339
+ def is_detaching_volume?(mapping)
340
+ case mapping[:volume_status]
341
+ when 'detaching'
342
+ true
343
+ when 'attached', 'attaching'
344
+ # also detaching if we have successfully requested detachment but volume
345
+ # status does not yet reflect this change.
346
+ 'detached' == mapping[:management_status]
347
+ else
348
+ false
349
+ end
350
+ end
351
+
352
+ # Determines if the given volume is detached regardless of whether detachment
353
+ # was managed (will adopt any detatched volumes).
354
+ #
355
+ # === Return
356
+ # result(Boolean):: true if volume is detaching
357
+ def is_detached_volume?(mapping)
358
+ # detached by volume status unless we have successfully requested attachment
359
+ # and volume status does not yet reflect this change. an unmanaged volume
360
+ # can also be detached.
361
+ return 'detached' == mapping[:volume_status] && 'attached' != mapping[:management_status]
362
+ end
363
+
364
+ # Determines if the given volume is attaching and managed (indicating that we
365
+ # requested attachment).
366
+ #
367
+ # === Return
368
+ # result(Boolean):: true if volume is managed and attaching
369
+ def is_managed_attaching_volume?(mapping)
370
+ return 'attached' == mapping[:management_status] && 'attached' != mapping[:volume_status]
371
+ end
372
+
373
+ # Determines if the given volume is attached and managed (indicating that we
374
+ # requested attachment) but not yet assigned its device name.
375
+ #
376
+ # === Return
377
+ # result(Boolean):: true if volume is managed, attached and unassigned
378
+ def is_managed_attached_unassigned_volume?(mapping)
379
+ return 'attached' == mapping[:volume_status] && 'attached' == mapping[:management_status]
380
+ end
381
+
382
+ # Determines if the given volume is in an unmanageable state (such as
383
+ # 'deleted').
384
+ #
385
+ # === Return
386
+ # result(Boolean):: true if volume is managed but unrecoverable
387
+ def is_unmanageable_volume?(mapping)
388
+ case mapping[:volume_status]
389
+ when 'attached', 'attaching', 'detached', 'detaching'
390
+ false
391
+ else
392
+ true
393
+ end
394
+ end
395
+
396
+ # Merges mappings from query with any last known mappings which may have a
397
+ # locally persisted state which needs to be evaluated.
398
+ #
399
+ # === Parameters
400
+ # last_mappings(Array):: previously merged mappings or empty
401
+ # current_mappings(Array):: current unmerged mappings or empty
402
+ #
403
+ # === Returns
404
+ # results(Array):: array of hashes representing merged mappings
405
+ def merge_planned_volume_mappings(last_mappings, current_planned_volumes)
406
+ results = []
407
+ vm = RightScale::Platform.volume_manager
408
+
409
+ # merge latest mappings with last mappings, if any.
410
+ current_planned_volumes.each do |planned_volume|
411
+ raise VolumeManagement::InvalidResponse.new("Reponse for volume mapping was invalid: #{mapping.inspect}") unless planned_volume.is_valid?
412
+ if mount_point = planned_volume.mount_points.find { |mount_point| false == vm.is_attachable_volume_path?(mount_point) }
413
+ raise VolumeManagement::UnsupportedMountPoint.new("Cannot mount a volume using \"#{mount_point}\".")
414
+ end
415
+
416
+ mapping = {:volume_id => planned_volume.volume_id,
417
+ :device_name => planned_volume.device_name,
418
+ :volume_status => planned_volume.volume_status,
419
+ :mount_points => planned_volume.mount_points.dup}
420
+ if last_mapping = last_mappings.find { |last_mapping| last_mapping[:volume_id] == mapping[:volume_id] }
421
+ # if device name or mount point(s) have changed then we must start
422
+ # over (we can't prevent the user from doing this).
423
+ if last_mapping[:device_name] != mapping[:device_name] || last_mapping[:mount_points] != mapping[:mount_points]
424
+ last_mapping[:device_name] = mapping[:device_name]
425
+ last_mapping[:mount_points] = mapping[:mount_points].dup
426
+ last_mapping[:management_status] = nil
427
+ end
428
+ last_mapping[:volume_status] = mapping[:volume_status]
429
+ mapping = last_mapping
430
+ end
431
+ results << mapping
432
+ end
433
+
434
+ # preserve any last mappings which do not appear in current mappings by
435
+ # assuming that they are 'detached' to support a limitation of the initial
436
+ # query implementation.
437
+ last_mappings.each do |last_mapping|
438
+ mapping = results.find { |mapping| mapping[:volume_id] == last_mapping[:volume_id] }
439
+ unless mapping
440
+ last_mapping[:volume_status] = 'detached'
441
+ results << last_mapping
442
+ end
443
+ end
444
+
445
+ return results
446
+ end
447
+
448
+ end
449
+
450
+ end
data/lib/instance.rb ADDED
@@ -0,0 +1,50 @@
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
+ INSTANCE_BASE_DIR = File.join(File.dirname(__FILE__), 'instance')
24
+
25
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'agent_config'))
26
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'audit_cook_stub'))
27
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'audit_proxy'))
28
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'bundle_queue'))
29
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'single_thread_bundle_queue'))
30
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'multi_thread_bundle_queue'))
31
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'cook', 'cook_state'))
32
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'downloader'))
33
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'duplicable'))
34
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'exceptions'))
35
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'executable_sequence_proxy'))
36
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'instance_commands'))
37
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'instance_state'))
38
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'login_user_manager'))
39
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'login_manager'))
40
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'message_encoder'))
41
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'operation_context'))
42
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'options_bag'))
43
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'payload_formatter'))
44
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'policy'))
45
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'policy_audit'))
46
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'policy_manager'))
47
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'reenroll_manager'))
48
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'right_scripts_cookbook'))
49
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'shutdown_request'))
50
+ require File.normalize_path(File.join(INSTANCE_BASE_DIR, 'volume_management'))
@@ -0,0 +1,106 @@
1
+ require 'right_agent/exceptions'
2
+
3
+ #
4
+ # Copyright (c) 2009-2011 RightScale Inc
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ module Apt
26
+
27
+ module Ubuntu
28
+ SUPPORTED_REPOS = ['hardy', 'intrepid', 'jaunty', 'karmic', 'lucid', 'maverick', 'precise', 'quantal']
29
+
30
+ # The different generate classes will always generate an exception ("string") if there's anything that went wrong. If no exception, things went well.
31
+ SUPPORTED_REPOS.each do |c|
32
+ module_eval <<-EOS
33
+ class #{c.capitalize}
34
+ def self.generate(description, base_urls, frozen_date="latest")
35
+ opts = { :repo_filename => "rightscale",
36
+ :repo_name => "default",
37
+ :description => description,
38
+ :codename => '#{c}',
39
+ :base_urls => base_urls,
40
+ :frozen_date => frozen_date,
41
+ :enabled => true }
42
+ opts[:frozen_date] = frozen_date || "latest" # Optional frozen date
43
+ Apt::Ubuntu::abstract_generate(opts)
44
+ end
45
+ end
46
+ EOS
47
+ end
48
+
49
+ def self.path_to_sources_list
50
+ "/etc/apt/sources.list.d"
51
+ end
52
+
53
+ ############## INTERNAL FUNCTIONS #######################################################
54
+ def self.abstract_generate(params)
55
+ return unless ::RightScale::Platform.linux? && ::RightScale::Platform.ubuntu?
56
+
57
+ opts = { :enabled => true, :frozen_date => "latest"}
58
+ opts.merge!(params)
59
+ raise ArgumentError.new("missing parameters to generate file!") unless opts[:repo_filename] &&
60
+ opts[:repo_name] &&
61
+ opts[:base_urls] &&
62
+ opts[:frozen_date] &&
63
+ opts[:enabled]
64
+
65
+ return unless opts[:enabled]
66
+
67
+ target = opts[:codename].downcase
68
+ codename = ::RightScale::Platform.codename.downcase
69
+
70
+ raise ::RightScale::Exceptions::PlatformError, "Unsupported Ubuntu release #{codename}" unless SUPPORTED_REPOS.include?(codename)
71
+ raise ::RightScale::Exceptions::PlatformError, "Wrong release; repo is for #{target}, we are #{codename}" unless target == codename
72
+
73
+ FileUtils.mkdir_p(Apt::Ubuntu::path_to_sources_list)
74
+
75
+ if opts[:frozen_date] != 'latest'
76
+ x = Date.parse(opts[:frozen_date]).to_s
77
+ x.gsub!(/-/,"/")
78
+ opts[:frozen_date] = x
79
+ end
80
+
81
+ mirror_list = opts[:base_urls].map do |bu|
82
+ bu +='/' unless bu[-1..-1] == '/' # ensure the base url is terminated with a '/'
83
+ bu + opts[:frozen_date]
84
+ end
85
+ config_body = ""
86
+ mirror_list.each do |mirror_url|
87
+ config_body += <<END
88
+ deb #{mirror_url} #{codename} main restricted multiverse universe
89
+ deb #{mirror_url} #{codename}-updates main restricted multiverse universe
90
+ deb #{mirror_url} #{codename}-security main restricted multiverse universe
91
+
92
+ END
93
+ end
94
+
95
+ target_filename = "#{Apt::Ubuntu::path_to_sources_list}/#{opts[:repo_filename]}.sources.list"
96
+ FileUtils.rm_f(target_filename) if File.exists?(target_filename)
97
+ File.open(target_filename,'w') { |f| f.write(config_body) }
98
+ FileUtils.mv("/etc/apt/sources.list", "/etc/apt/sources.list.ORIG") if File.exists?("/etc/apt/sources.list")
99
+
100
+ mirror_list
101
+ end
102
+ end
103
+ end
104
+
105
+ # Examples of usage...
106
+ #Apt::Ubuntu::Hardy.generate("Hardy", ["http://a.com/ubuntu_daily"], "20081010")