right_agent 0.17.2 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/lib/right_agent.rb +0 -1
  2. data/lib/right_agent/agent_config.rb +1 -1
  3. data/lib/right_agent/minimal.rb +8 -7
  4. data/lib/right_agent/monkey_patches.rb +4 -2
  5. data/lib/right_agent/monkey_patches/ruby_patch.rb +9 -9
  6. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +2 -2
  7. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +21 -51
  8. data/lib/right_agent/packets.rb +5 -1
  9. data/lib/right_agent/platform.rb +727 -299
  10. data/lib/right_agent/platform/unix/darwin/platform.rb +102 -0
  11. data/lib/right_agent/platform/unix/linux/platform.rb +305 -0
  12. data/lib/right_agent/platform/unix/platform.rb +226 -0
  13. data/lib/right_agent/platform/windows/mingw/platform.rb +447 -0
  14. data/lib/right_agent/platform/windows/mswin/platform.rb +236 -0
  15. data/lib/right_agent/platform/windows/platform.rb +1808 -0
  16. data/right_agent.gemspec +13 -8
  17. data/spec/platform/spec_helper.rb +216 -0
  18. data/spec/platform/unix/darwin/platform_spec.rb +181 -0
  19. data/spec/platform/unix/linux/platform_spec.rb +540 -0
  20. data/spec/platform/unix/spec_helper.rb +149 -0
  21. data/spec/platform/windows/mingw/platform_spec.rb +222 -0
  22. data/spec/platform/windows/mswin/platform_spec.rb +259 -0
  23. data/spec/platform/windows/spec_helper.rb +720 -0
  24. metadata +45 -30
  25. data/lib/right_agent/platform/darwin.rb +0 -285
  26. data/lib/right_agent/platform/linux.rb +0 -537
  27. data/lib/right_agent/platform/windows.rb +0 -1384
  28. data/spec/platform/darwin_spec.rb +0 -13
  29. data/spec/platform/linux_spec.rb +0 -38
  30. data/spec/platform/linux_volume_manager_spec.rb +0 -201
  31. data/spec/platform/platform_spec.rb +0 -80
  32. data/spec/platform/windows_spec.rb +0 -13
  33. data/spec/platform/windows_volume_manager_spec.rb +0 -318
data/lib/right_agent.rb CHANGED
@@ -26,7 +26,6 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'right_agent', 'minim
26
26
  require 'json'
27
27
  require 'openssl'
28
28
  require 'right_amqp'
29
- require 'right_support'
30
29
 
31
30
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'monkey_patches'))
32
31
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'payload_formatter'))
@@ -63,7 +63,7 @@ module RightScale
63
63
  module AgentConfig
64
64
 
65
65
  # Current agent protocol version
66
- PROTOCOL_VERSION = 21
66
+ PROTOCOL_VERSION = 22
67
67
 
68
68
  # Current agent protocol version
69
69
  #
@@ -23,13 +23,14 @@
23
23
  require 'rubygems'
24
24
  require 'eventmachine'
25
25
  require 'fileutils'
26
+ require 'right_support'
26
27
  require 'yaml'
27
28
 
28
29
  # load definition for File.normalize_path, etc.
29
- require File.expand_path(File.join(File.dirname(__FILE__), 'platform'))
30
+ require ::File.expand_path('../monkey_patches', __FILE__)
30
31
 
31
32
  unless defined?(RIGHT_AGENT_BASE_DIR)
32
- RIGHT_AGENT_BASE_DIR = File.normalize_path(File.dirname(__FILE__))
33
+ RIGHT_AGENT_BASE_DIR = ::File.normalize_path('..', __FILE__)
33
34
  end
34
35
 
35
36
  # require minimal gems needed to create a CommandClient and send a command.
@@ -37,8 +38,8 @@ end
37
38
  # FIX: agent_controller is currently the only minimal-load use case so these
38
39
  # requires are oriented toward that. any additional use cases may require a
39
40
  # rethink of minimal loading.
40
- require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'agent_config'))
41
- require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'command'))
42
- require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'log'))
43
- require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'pid_file'))
44
- require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'serialize', 'serializable'))
41
+ require ::File.normalize_path('agent_config', RIGHT_AGENT_BASE_DIR)
42
+ require ::File.normalize_path('command', RIGHT_AGENT_BASE_DIR)
43
+ require ::File.normalize_path('log', RIGHT_AGENT_BASE_DIR)
44
+ require ::File.normalize_path('pid_file', RIGHT_AGENT_BASE_DIR)
45
+ require ::File.normalize_path('serialize/serializable', RIGHT_AGENT_BASE_DIR)
@@ -23,6 +23,8 @@
23
23
  require 'rubygems'
24
24
  require 'rbconfig'
25
25
 
26
- MONKEY_PATCHES_BASE_DIR = File.join(File.dirname(__FILE__), 'monkey_patches')
26
+ unless defined?(RIGHT_AGENT_MONKEY_PATCHES_BASE_DIR)
27
+ require ::File.expand_path('../monkey_patches/ruby_patch', __FILE__)
27
28
 
28
- require File.normalize_path(File.join(MONKEY_PATCHES_BASE_DIR, 'ruby_patch'))
29
+ RIGHT_AGENT_MONKEY_PATCHES_BASE_DIR = ::File.normalize_path('../monkey_patches', __FILE__)
30
+ end
@@ -32,24 +32,24 @@ require 'socket'
32
32
  # using short path. Since this is where we define the File.normalize_path
33
33
  # method to alleviate this issue, we have a chicken & egg problem. So detect if
34
34
  # we already required this file and skip the rest if that was the case.
35
- unless defined?(RUBY_PATCH_BASE_DIR)
35
+ unless defined?(RIGHT_AGENT_RUBY_PATCH_BASE_DIR)
36
36
 
37
37
  # Load platform-specific patches before any other patches (in order to define
38
38
  # File.normalize_path, etc.)
39
- case (family = RbConfig::CONFIG['host_os'])
39
+ case ::RbConfig::CONFIG['host_os']
40
40
  when /mswin|win32|dos|mingw|cygwin/i
41
- require File.expand_path(File.join(File.dirname(__FILE__), 'ruby_patch', 'windows_patch'))
41
+ require ::File.expand_path('../ruby_patch/windows_patch', __FILE__)
42
42
  when /linux/i
43
- require File.expand_path(File.join(File.dirname(__FILE__), 'ruby_patch', 'linux_patch'))
43
+ require ::File.expand_path('../ruby_patch/linux_patch', __FILE__)
44
44
  when /darwin/i
45
- require File.expand_path(File.join(File.dirname(__FILE__), 'ruby_patch', 'darwin_patch'))
45
+ require ::File.expand_path('../ruby_patch/darwin_patch', __FILE__)
46
46
  else
47
- raise LoadError, "Unsupported platform: #{family}"
47
+ raise LoadError, "Unsupported platform: #{::RbConfig::CONFIG['host_os']}"
48
48
  end
49
49
 
50
- RUBY_PATCH_BASE_DIR = File.join(File.dirname(__FILE__), 'ruby_patch')
50
+ RIGHT_AGENT_RUBY_PATCH_BASE_DIR = ::File.normalize_path('../ruby_patch', __FILE__)
51
51
 
52
- require File.normalize_path(File.join(RUBY_PATCH_BASE_DIR, 'array_patch'))
53
- require File.normalize_path(File.join(RUBY_PATCH_BASE_DIR, 'object_patch'))
52
+ require File.join(RIGHT_AGENT_RUBY_PATCH_BASE_DIR, 'array_patch')
53
+ require File.join(RIGHT_AGENT_RUBY_PATCH_BASE_DIR, 'object_patch')
54
54
 
55
55
  end # Unless already defined
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2011 RightScale Inc
2
+ # Copyright (c) 2011-2013 RightScale Inc
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -24,7 +24,7 @@ class File
24
24
 
25
25
  # On *nix systems, resolves to File.expand_path
26
26
  def self.normalize_path(file_name, *dir_string)
27
- File.expand_path(file_name, *dir_string)
27
+ self.expand_path(file_name, *dir_string)
28
28
  end
29
29
 
30
30
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2011 RightScale Inc
2
+ # Copyright (c) 2011-2013 RightScale Inc
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -23,68 +23,38 @@
23
23
  require 'rubygems'
24
24
 
25
25
  begin
26
- require 'windows/file'
26
+
27
+ # attempt to early-load basic Windows API gems so that we can rescue and
28
+ # switch to using the simple definition of File.normalize_path when these gems
29
+ # are unavailable.
30
+ require ::File.expand_path('../../../../platform', __FILE__)
27
31
 
28
32
  class File
29
33
 
30
- # converts a long path to a short path. in windows terms, this means
31
- # taking any file/folder name over 8 characters in length and truncating
32
- # it to 6 characters with ~1..~n appended depending on how many similar
33
- # names exist in the same directory. file extensions are simply chopped
34
- # at three letters. the short name is equivalent for all API calls to
35
- # the long path but requires no special quoting, etc. the path must
36
- # exist at least partially for the API call to succeed.
34
+ # Expand the path then shorten the directory, if possible.
35
+ # Only shortens the parent directory and not the leaf (file or directory)
36
+ # name because 'gem' wants the file name to be long for require.
37
37
  #
38
- # === Parameters
39
- # long_path(String):: fully or partially existing long path to be
40
- # converted to its short path equivalent.
38
+ # @param [String] file_name to normalize
39
+ # @param [String] dir_string as base directory path for relative file_name
40
+ # or ignored when file_name is absolute (Default = working directory).
41
41
  #
42
- # === Return
43
- # short_path(String):: short path equivalent or same path if non-existent
44
- def self.long_path_to_short_path(long_path)
45
- if File.exists?(long_path)
46
- length = 260
47
- while true
48
- buffer = 0.chr * length
49
- length = ::Windows::File::GetShortPathName.call(long_path, buffer, buffer.length)
50
- if length < buffer.length
51
- break
52
- end
53
- end
54
- return buffer.unpack('A*').first.gsub("\\", "/")
55
- else
56
- # must get short path for any existing ancestor since child doesn't
57
- # (currently) exist.
58
- child_name = File.basename(long_path)
59
- long_parent_path = File.dirname(long_path)
60
-
61
- # note that root dirname is root itself (at least in windows)
62
- return long_path if long_path == long_parent_path
63
-
64
- # recursion
65
- short_parent_path = long_path_to_short_path(File.dirname(long_path))
66
- return File.join(short_parent_path, child_name)
67
- end
68
- end
69
-
70
- # First expand the path then shorten the directory.
71
- # Only shorten the directory and not the file name because 'gem' wants
72
- # long file names
42
+ # @return [String] normalized path
73
43
  def self.normalize_path(file_name, *dir_string)
74
- path = File.expand_path(file_name, *dir_string)
75
- dir = self.long_path_to_short_path(File.dirname(path))
76
- File.join(dir, File.basename(path))
44
+ path = self.expand_path(file_name, *dir_string)
45
+ dir = ::RightScale::Platform.filesystem.long_path_to_short_path(self.dirname(path))
46
+ self.join(dir, self.basename(path))
77
47
  end
78
-
79
48
  end
80
49
 
81
50
  rescue LoadError
82
- # use the simple definition of normalize_path to avoid breaking code which
83
- # depends on having this definition.
51
+
52
+ # use the simple definition of normalize_path on load error. the purpose of
53
+ # normalize_path is to disambiguate load paths but it is possible to continue
54
+ # with ambiguity in most cases and any other Win32 API calls will fail.
84
55
  class File
85
56
  def self.normalize_path(file_name, *dir_string)
86
- File.expand_path(file_name, *dir_string)
57
+ self.expand_path(file_name, *dir_string)
87
58
  end
88
59
  end
89
-
90
60
  end
@@ -24,6 +24,10 @@
24
24
  module JSON
25
25
  class << self
26
26
  def parse(source, opts = {})
27
+ # In gem version this options is set to true by default
28
+ # but in ruby native json library is set to false by default
29
+ # Explicitly set it to true so both json libraries act the same
30
+ opts[:create_additions] = true
27
31
  if source =~ /(.*)json_class":"Nanite::(.*)/
28
32
  JSON.parser.new( Regexp.last_match(1) + 'json_class":"RightScale::' + Regexp.last_match(2), opts).parse
29
33
  else
@@ -248,7 +252,7 @@ module RightScale
248
252
  def trace
249
253
  audit_id = self.respond_to?(:payload) && payload.is_a?(Hash) && (payload['audit_id'] || payload[:audit_id])
250
254
  tok = self.respond_to?(:token) && token
251
- tr = "<#{audit_id || nil}> <#{tok}>"
255
+ tr = "<#{audit_id || nil}> <#{tok}>"
252
256
  end
253
257
 
254
258
  # Retrieve protocol version of original creator of packet
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2009-2011 RightScale Inc
2
+ # Copyright (c) 2009-2013 RightScale Inc
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -26,317 +26,745 @@
26
26
  # we already required this file and skip the rest if that was the case.
27
27
  unless defined?(RightScale::Platform)
28
28
 
29
- # Note that the platform-specific submodules will be loaded on demand to resolve
30
- # some install-time gem dependency issues.
31
-
32
- require 'rbconfig'
33
- require 'right_support'
34
-
35
-
36
- # Load ruby interpreter monkey-patches first (to ensure File.normalize_path is defined, etc.).
37
- require File.expand_path(File.join(File.dirname(__FILE__), 'monkey_patches', 'ruby_patch'))
38
-
39
- module RightScale
40
- # Throw when the current platform is not supported for some reason
41
- class PlatformNotSupported < Exception; end
42
-
43
- # A utility class that provides information about the platform on which the RightAgent is running
44
- # Available information includes:
45
- # - which flavor cloud (EC2, Rackspace, Eucalyptus, ..)
46
- # - which flavor operating system (Linux, Windows or Mac)
47
- # - which OS release (a numeric value that is specific to the OS)
48
- # - directories in which various bits of RightScale state may be found
49
- # - platform-specific information such as Linux flavor or release
50
- #
51
- # For platform information only used in specific contexts, the dispatch method may be used
52
- #
53
- # This is a summary of the information you can query by calling Platform's instance methods:
54
- # - .flavor
55
- # - .release
56
- # - .linux?
57
- # - .darwin?
58
- # - .windows?
59
- # - .ec2?
60
- # - .rackspace?
61
- # - .eucalyptus?
62
- # - .filesystem
63
- # - right_scale_state_dir
64
- # - spool_dir
65
- # - cache_dir
66
- # - .linux (only available under Linux)
67
- # - ubuntu?
68
- # - centos?
69
- # - suse?
70
- class Platform
71
-
72
- include RightSupport::Ruby::EasySingleton
73
-
74
- # Generic platform family
75
- #
76
- # === Return
77
- # family(Symbol):: One of :linux, :windows or :darwin
78
- def family
79
- @family ||= case RbConfig::CONFIG['host_os']
80
- when /mswin|win32|dos|mingw|cygwin/i then :windows
81
- when /darwin/i then :darwin
82
- when /linux/i then :linux
83
- end
84
- end
85
-
86
- # Is current platform linux?
87
- #
88
- # === Return
89
- # true:: If current platform is linux
90
- # false:: Otherwise
91
- def linux?
92
- return family == :linux
93
- end
94
-
95
- # Is current platform darwin?
96
- #
97
- # === Return
98
- # true:: If current platform is darwin
99
- # false:: Otherwise
100
- def darwin?
101
- return family == :darwin
102
- end
103
- # Is current platform windows?
104
- #
105
- # === Return
106
- # true:: If current platform is Windows
107
- # false:: Otherwise
108
- def windows?
109
- return family == :windows
110
- end
111
-
112
- # Call platform specific implementation of method whose symbol is returned
113
- # by the passed in block. Arguments are passed through.
114
- # e.g.
115
- #
116
- # Platform.dispatch(2) { :echo }
117
- #
118
- # will result in 'echo_linux(2)' being executed in self if running on linux,
119
- # 'echo_windows(2)' if running on Windows and 'echo_darwin(2)' if on Mac OS X.
120
- # Note that the method is run in the instance of the caller.
121
- #
122
- # === Parameters
123
- # args:: Pass-through arguments
124
- #
125
- # === Block
126
- # Given block should not take any argument and return a symbol for the
127
- # method that should be called
128
- #
129
- # === Return
130
- # res(Object):: Result returned by platform specific implementation
131
- def dispatch(*args, &blk)
132
- raise "Platform.dispatch requires a block" unless blk
133
- binding = blk.binding.eval('self')
134
- meth = blk.call
135
- target = dispatch_candidates(meth).detect do |candidate|
136
- binding.respond_to?(candidate)
137
- end
138
- raise "No platform dispatch target found in #{binding.class} for " +
139
- "'#{meth.inspect}', tried " + dispatch_candidates(meth).join(', ') unless target
140
- binding.__send__(target, *args)
141
- end
142
-
143
- # Load platform specific implementation
144
- #
145
- # === Return
146
- # true:: Always return true
147
- def load_platform_specific
148
- if linux?
149
- require_linux
150
- elsif darwin?
151
- require_darwin
152
- elsif windows?
153
- require_windows
154
- else
155
- raise PlatformError.new('Unknown platform')
156
- end
157
- end
158
-
159
- # Are we in an EC2 cloud?
160
- #
161
- # === Return
162
- # true:: If machine is located in an EC2 cloud
163
- # false:: Otherwise
164
- def ec2?
165
- resolve_cloud_type if @ec2.nil?
166
- @ec2
167
- end
168
-
169
- # Are we in a Rackspace cloud?
170
- #
171
- # === Return
172
- # true:: If machine is located in an Rackspace cloud
173
- # false:: Otherwise
174
- def rackspace?
175
- resolve_cloud_type if @rackspace.nil?
176
- @rackspace
177
- end
178
-
179
- # Are we in a Eucalyptus cloud?
29
+ # Note that the platform-specific submodules will be loaded on demand to resolve
30
+ # some install-time gem dependency issues.
31
+ require 'rubygems'
32
+ require 'rbconfig'
33
+ require 'right_support'
34
+
35
+ require ::File.expand_path('../exceptions', __FILE__)
36
+
37
+ module RightScale
38
+
39
+ # A utility class that provides information about the platform on which the
40
+ # RightAgent is running.
180
41
  #
181
- # === Return
182
- # true:: If machine is located in an Eucalyptus cloud
183
- # false:: Otherwise
184
- def eucalyptus?
185
- resolve_cloud_type if @eucalyptus.nil?
186
- @eucalyptus
187
- end
188
-
189
- # Controller object
42
+ # Available information includes:
43
+ # - which flavor cloud (EC2, Rackspace, Eucalyptus, ..)
44
+ # - which flavor operating system (Linux, Windows or Mac)
45
+ # - which OS release (a numeric value that is specific to the OS)
46
+ # - directories in which various bits of RightScale state may be found
47
+ # - platform-specific information such as Linux flavor or release
190
48
  #
191
- # === Return
192
- # (Controller):: Platform-specific controller object
193
- def controller
194
- platform_service(:controller)
195
- end
49
+ # For platform information only used in specific contexts, the dispatch
50
+ # method may be used.
51
+ class Platform
196
52
 
197
- # Filesystem config object
198
- #
199
- # === Return
200
- # (Filesystem):: Platform-specific filesystem config object
201
- def filesystem
202
- platform_service(:filesystem)
203
- end
53
+ include RightSupport::Ruby::EasySingleton
204
54
 
205
- # VolumeManager config object
206
- #
207
- # === Return
208
- # (VolumeManager):: Platform-specific volume manager config object
209
- def volume_manager
210
- platform_service(:volume_manager)
211
- end
55
+ # exceptions
56
+ class CommandError < RightScale::Exceptions::PlatformError
57
+ attr_accessor :command
58
+ attr_accessor :status
59
+ attr_accessor :output_text
60
+ end
212
61
 
213
- # Shell information object
214
- #
215
- # === Return
216
- # (Object):: Platform-specific shell information object
217
- def shell
218
- platform_service(:shell)
219
- end
62
+ attr_reader :flavor, :release, :codename
63
+
64
+ # Generic platform family
65
+ #
66
+ # @deprecated family is a legacy definition, see genus/species for a more
67
+ # granular definition.
68
+ #
69
+ # @return [Symbol] result as one of :linux, :windows or :darwin
70
+ def family
71
+ warn "#{self.class.name}#family is deprecated, use genus or species instead.\n#{caller[0,2].join("\n")}"
72
+ (genus == :unix) ? species : genus
73
+ end
220
74
 
221
- # SSH information object
222
- #
223
- # === Return
224
- # (Object):: Platform-specific ssh object
225
- def ssh
226
- platform_service(:ssh)
227
- end
75
+ # @return [Symbol] platform genus
76
+ def genus
77
+ resolve_taxonomy unless @genus
78
+ @genus
79
+ end
228
80
 
229
- # Platform random number generator (RNG) facilities.
230
- #
231
- # === Return
232
- # (Object):: Platform-specific RNG object
233
- def rng
234
- platform_service(:rng)
235
- end
81
+ # @return [Symbol] platform species
82
+ def species
83
+ resolve_taxonomy unless @species
84
+ @species
85
+ end
236
86
 
237
- # Platform process facilities.
238
- #
239
- # === Return
240
- # (Object):: Platform-specific process facilities object
241
- def process
242
- platform_service(:process)
243
- end
244
-
245
- # Installer information object
246
- #
247
- # === Return
248
- # (Object):: Platform-specific installer information object
249
- def installer
250
- platform_service(:installer)
251
- end
252
-
253
- # Determines which cloud we're on by the cheap but simple expedient of
254
- # reading the RightScale cloud file
255
- def resolve_cloud_type
256
- cloud_type = read_cloud_file
257
- @ec2 = false
258
- @rackspace = false
259
- @eucalyptus = false
260
- case cloud_type
261
- when 'ec2' then @ec2 = true
262
- when 'rackspace' then @rackspace = true
263
- when 'eucalyptus' then @eucalyptus = true
264
- end
265
- end
266
-
267
- private
268
-
269
- # Load platform specific implementation
270
- def initialize
271
- require File.expand_path(File.join(File.dirname(__FILE__), 'platform', family.to_s))
272
- @filesystem = nil
273
- @shell = nil
274
- @ssh = nil
275
- @controller = nil
276
- @installer = nil
277
-
278
- @ec2 = nil
279
- @rackspace = nil
280
- @eucalyptus = nil
281
-
282
- init
283
-
284
- # Note that we must defer any use of filesystem until requested because
285
- # Windows setup scripts attempt to use Platform before installing some
286
- # of the required gems. Don't attempt to call code that requires gems in
287
- # initialize().
288
- end
289
-
290
- def require_linux
291
- require File.expand_path(File.join(File.dirname(__FILE__), 'platform', 'linux'))
292
- end
293
-
294
- def require_darwin
295
- require File.expand_path(File.join(File.dirname(__FILE__), 'platform', 'darwin'))
296
- end
297
-
298
- def require_windows
299
- require File.expand_path(File.join(File.dirname(__FILE__), 'platform', 'windows'))
300
- end
301
-
302
- # Reads the RightScale cloud file and returns its contents
303
- def read_cloud_file
304
- File.read(File.join(self.filesystem.right_scale_state_dir, 'cloud')) rescue nil
305
- end
306
-
307
- # Retrieve platform specific service implementation
308
- #
309
- # === Parameters
310
- # name(Symbol):: Service name, one of :filesystem, :shell, :ssh, :controller, :installer
311
- #
312
- # === Return
313
- # res(Object):: Service instance
314
- #
315
- # === Raise
316
- # RightScale::Exceptions::PlatformError:: If the service is not known
317
- def platform_service(name)
318
- instance_var = "@#{name.to_s}".to_sym
319
- const_name = name.to_s.camelize
87
+ # Is current platform in the Unix genus?
88
+ #
89
+ # @return [TrueClass|FalseClass] true if Unix
90
+ def unix?
91
+ genus == :unix
92
+ end
93
+
94
+ # Is current platform Linux?
95
+ #
96
+ # @return [TrueClass|FalseClass] true if Linux
97
+ def linux?
98
+ species == :linux
99
+ end
100
+
101
+ # Is current platform Darwin?
102
+ #
103
+ # @return [TrueClass|FalseClass] true if Darwin
104
+ def darwin?
105
+ species == :darwin
106
+ end
107
+
108
+ # Is current platform Windows?
109
+ #
110
+ # @return [TrueClass|FalseClass] true if Windows
111
+ def windows?
112
+ genus == :windows
113
+ end
114
+
115
+ # Are we on an EC2 cloud instance?
116
+ #
117
+ # @deprecated use right_link cloud libraries instead.
118
+ #
119
+ # @return [TrueClass|FalseClass] true if EC2
120
+ def ec2?
121
+ warn "#{self.class.name}#ec2? is deprecated, use right_link cloud libraries instead.\n#{caller[0,2].join("\n")}"
122
+ resolve_cloud_type unless @cloud_type
123
+ @cloud_type == 'ec2'
124
+ end
125
+
126
+ # Are we on an Rackspace cloud instance?
127
+ #
128
+ # @deprecated use right_link cloud libraries instead.
129
+ #
130
+ # @return [TrueClass|FalseClass] true if Rackspace
131
+ def rackspace?
132
+ warn "#{self.class.name}#rackspace? is deprecated, use right_link cloud libraries instead.\n#{caller[0,2].join("\n")}"
133
+ resolve_cloud_type unless @cloud_type
134
+ @cloud_type == 'rackspace'
135
+ end
136
+
137
+ # Are we on an Eucalyptus cloud instance?
138
+ #
139
+ # @deprecated use right_link cloud libraries instead.
140
+ #
141
+ # @return [TrueClass|FalseClass] true if Eucalyptus
142
+ def eucalyptus?
143
+ warn "#{self.class.name}#eucalyptus? is deprecated, use right_link cloud libraries instead.\n#{caller[0,2].join("\n")}"
144
+ resolve_cloud_type unless @cloud_type
145
+ @cloud_type == 'eucalyptus'
146
+ end
147
+
148
+ # Call platform specific implementation of method whose symbol is returned
149
+ # by the passed in block. Arguments are passed through.
150
+ # e.g.
151
+ #
152
+ # Platform.dispatch(2) { :echo }
153
+ #
154
+ # will result in 'echo_linux(2)' being executed in self if running on
155
+ # linux, 'echo_windows(2)' if running on Windows and 'echo_darwin(2)' if
156
+ # on Mac OS X. Note that the method is run in the instance of the caller.
157
+ #
158
+ # @param [Array] args as pass-through arguments
159
+ #
160
+ # @yield [] given block should not take any argument and return a symbol
161
+ # for the method that should be called.
162
+ #
163
+ # @return [Object] result of Platform-specific implementation
164
+ def dispatch(*args, &blk)
165
+ raise 'Platform.dispatch requires a block' unless blk
166
+ binding = blk.binding.eval('self')
167
+ meth = blk.call
168
+ target = dispatch_candidates(meth).detect do |candidate|
169
+ binding.respond_to?(candidate)
170
+ end
171
+ raise "No platform dispatch target found in #{binding.class} for " +
172
+ "'#{meth.inspect}', tried " + dispatch_candidates(meth).join(', ') unless target
173
+ binding.__send__(target, *args)
174
+ end
175
+
176
+ # @return [Controller] Platform-specific controller object
177
+ def controller
178
+ platform_service(:controller)
179
+ end
180
+
181
+ # @return [Filesystem] Platform-specific filesystem config object
182
+ def filesystem
183
+ platform_service(:filesystem)
184
+ end
185
+
186
+ # @return [VolumeManager] Platform-specific volume manager config object
187
+ def volume_manager
188
+ platform_service(:volume_manager)
189
+ end
190
+
191
+ # @return [Shell] Platform-specific shell information object
192
+ def shell
193
+ platform_service(:shell)
194
+ end
195
+
196
+ # @return [Rng] Platform-specific RNG object
197
+ def rng
198
+ platform_service(:rng)
199
+ end
200
+
201
+ # @return [Process] Platform-specific process facilities object
202
+ def process
203
+ platform_service(:process)
204
+ end
205
+
206
+ # @return [Installer] Platform-specific installer information object
207
+ def installer
208
+ platform_service(:installer)
209
+ end
210
+
211
+ # Blocking call to invoke a command line tool used to perform platform-
212
+ # specific tasks.
213
+ #
214
+ # Also provides a consistent interface for mocking command output during
215
+ # spec testing. Implementations should use this method instead of
216
+ # embedding popen/backtick calls to assist with testing.
217
+ #
218
+ # @param [String] command to run
219
+ # @param [Hash] options for execution
220
+ # @option options [TrueClass|FalseClass] :raise_on_failure true to raise on command failure (Default)
221
+ #
222
+ # @return [String] output from command or empty
223
+ #
224
+ # @raise [CommandError] on failure (by default)
225
+ def execute(command, options = {})
226
+ options = { :raise_on_failure => true }.merge(options)
227
+ raise_on_failure = options[:raise_on_failure]
228
+ output_text = ''
229
+ begin
230
+ output_text = `#{command}`
231
+ if !$?.success? && options[:raise_on_failure]
232
+ message = []
233
+ message << "Command failed with exit code = #{$?.exitstatus}:"
234
+ message << "> #{command}"
235
+ error_output_text = output_text.strip
236
+ message << error_output_text unless error_output_text.empty?
237
+ e = CommandError.new(message.join("\n"))
238
+ e.command = command
239
+ e.status = $?
240
+ e.output_text = output_text
241
+ raise e
242
+ end
243
+ rescue Errno::ENOENT => e
244
+ if raise_on_failure
245
+ raise CommandError, "Command failed: #{e.message}"
246
+ end
247
+ end
248
+ output_text
249
+ end
250
+
251
+ # Base class for platform helpers.
252
+ class PlatformHelperBase
253
+
254
+ private
255
+
256
+ # Convenience method for declaring must-be-overridden interfaces.
257
+ #
258
+ # @raise [NotImplementedError] always unless overridden
259
+ def must_be_overridden
260
+ raise ::NotImplementedError, 'Must be overridden'
261
+ end
262
+
263
+ # Convenience method for executing a command via platform.
264
+ #
265
+ # See Platform#execute
266
+ def execute(cmd, options = {})
267
+ ::RightScale::Platform.execute(cmd, options)
268
+ end
269
+ end
270
+
271
+ # Declares various file system APIs.
272
+ class Filesystem < PlatformHelperBase
273
+
274
+ # Is given command available in the PATH?
275
+ #
276
+ # @param [String] command_name to be tested
277
+ #
278
+ # @return [TrueClass|FalseClass] true if command is in path
279
+ def has_executable_in_path(command_name)
280
+ return !!find_executable_in_path(command_name)
281
+ end
282
+
283
+ # Finds the given command name in the PATH. this emulates the 'which'
284
+ # command from linux (without the terminating newline).
285
+ #
286
+ # @param [String] command_name to be tested
287
+ #
288
+ # @return [String] path to first matching executable file in PATH or nil
289
+ def find_executable_in_path(command_name)
290
+ must_be_overridden
291
+ end
292
+
293
+ # @return [String] directory containing generated agent configuration files
294
+ def right_agent_cfg_dir
295
+ must_be_overridden
296
+ end
297
+
298
+ # @return [String] static (time-invariant) state that is common to all RightScale apps/agents
299
+ def right_scale_static_state_dir
300
+ must_be_overridden
301
+ end
302
+
303
+ # @return [String] static (time-invariant) state that is specific to RightLink
304
+ def right_link_static_state_dir
305
+ must_be_overridden
306
+ end
307
+
308
+ # @return [String] dynamic, persistent runtime state that is specific to RightLink
309
+ def right_link_dynamic_state_dir
310
+ must_be_overridden
311
+ end
312
+
313
+ # @return [String] data which is awaiting some kind of later processing
314
+ def spool_dir
315
+ must_be_overridden
316
+ end
317
+
318
+ # TEAL TODO description
319
+ def ssh_cfg_dir
320
+ must_be_overridden
321
+ end
322
+
323
+ # Cached data from applications. Such data is locally generated as a
324
+ # result of time-consuming I/O or calculation. The application must
325
+ # be able to regenerate or restore the data.
326
+ #
327
+ # @return [String] cache directory
328
+ def cache_dir
329
+ must_be_overridden
330
+ end
331
+
332
+ # @return [String] system logs
333
+ def log_dir
334
+ must_be_overridden
335
+ end
320
336
 
321
- unless res = self.instance_variable_get(instance_var)
337
+ # For Unix compatibility; has no significance in Windows
338
+ #
339
+ # @return [String] source code directory, for reference purposes and for development
340
+ def source_code_dir
341
+ must_be_overridden
342
+ end
343
+
344
+ # @return [String] temporary files.
345
+ def temp_dir
346
+ must_be_overridden
347
+ end
348
+
349
+ # @return [String] path to place pid files
350
+ def pid_dir
351
+ must_be_overridden
352
+ end
353
+
354
+ # @return [String] installed home (parent of) right_link directory path
355
+ def right_link_home_dir
356
+ must_be_overridden
357
+ end
358
+
359
+ # @return [String] path to right link configuration and internal usage scripts
360
+ def private_bin_dir
361
+ must_be_overridden
362
+ end
363
+
364
+ # TEAL TODO description
365
+ def sandbox_dir
366
+ must_be_overridden
367
+ end
368
+
369
+ # Converts a long path (e.g. "C:/Program Files") to a short path
370
+ # (e.g. "C:/PROGRA~1") if necessary. See implementation for notes.
371
+ #
372
+ # For Windows compatibility; has no significance in Linux
373
+ #
374
+ # @param [String] long_path to convert
375
+ #
376
+ # @return [String] short path
377
+ def long_path_to_short_path(long_path)
378
+ must_be_overridden
379
+ end
380
+
381
+ # Converts slashes in a path to a consistent style.
382
+ #
383
+ # For Windows compatibility; has no significance in Linux
384
+ #
385
+ # @param [String] path to make pretty
386
+ # @param [String] native_fs_flag as true if path is pretty for native
387
+ # file system (i.e. file system calls more likely to succeed), false
388
+ # if pretty for Ruby interpreter (default).
389
+ #
390
+ # @return [String] pretty path
391
+ def pretty_path(path, native_fs_flag = false)
392
+ must_be_overridden
393
+ end
394
+
395
+ # Ensures a local drive location for the file or folder given by path
396
+ # by copying to a local temp directory given by name only if the item
397
+ # does not appear on the home drive. This method is useful because
398
+ # secure applications refuse to run scripts from network locations, etc.
399
+ # Replaces any similar files in given temp dir to ensure latest updates.
400
+ #
401
+ # For Windows compatibility; has no significance in Linux
402
+ #
403
+ # @param [String] path to file or directory to be placed locally
404
+ # @param [String] temp_dir_name relative to user temp_dir to use only if the file or folder is not on a local drive
405
+ #
406
+ # @return [String] local drive path
407
+ def ensure_local_drive_path(path, temp_dir_name)
408
+ must_be_overridden
409
+ end
410
+
411
+ # Creates a symlink (if supported by platform).
412
+ #
413
+ # @param [String] from_path the path to the real file/directory
414
+ # @param [String] to_path the path to the symlink to be created
415
+ #
416
+ # @return [Fixnum] always 0 as does File.symlink under Linux
417
+ #
418
+ # @raise [RightScale::Exceptions::PlatformError] on failure
419
+ def create_symlink(from_path, to_path)
420
+ must_be_overridden
421
+ end
422
+ end
423
+
424
+ # Provides utilities for managing volumes (disks).
425
+ class VolumeManager < PlatformHelperBase
426
+
427
+ # exceptions
428
+ class ParserError < ::RightScale::Exceptions::PlatformError; end
429
+ class VolumeError < ::RightScale::Exceptions::PlatformError; end
430
+
431
+ # Gets a list of currently visible volumes in the form:
432
+ # [{:device, :label, :uuid, :type, :filesystem}]
433
+ #
434
+ # @param [Hash] conditions to match, if any (Default = no conditions)
435
+ #
436
+ # @return [Array] volume info as an array of hashes or empty
437
+ #
438
+ # @raise [ParserError] on failure to parse volume list
439
+ # @raise [VolumeError] on failure to execute `blkid` to obtain raw output
440
+ def volumes(conditions = nil)
441
+ must_be_overridden
442
+ end
443
+ end # VolumeManager
444
+
445
+ # Declares various command shell APIs.
446
+ class Shell < PlatformHelperBase
447
+
448
+ # @return [String] name or path of file reserved for null output redirection
449
+ def null_output_name
450
+ must_be_overridden
451
+ end
452
+
453
+ # Fully qualifies a partial script file path to ensure it is executable on
454
+ # the current platform.
455
+ #
456
+ # For Windows compatibility; has no significance in Linux
457
+ #
458
+ # @param [String] partial_script_file_path to format
459
+ # @param [String] default_extension to use if no extension (Default = platform specific)
460
+ #
461
+ # @return [String] full script path
462
+ def format_script_file_name(partial_script_file_path, default_extension = nil)
463
+ must_be_overridden
464
+ end
465
+
466
+ # Formats an executable command by quoting any of the arguments as needed
467
+ # and building an executable command string.
468
+ #
469
+ # @param [String] executable_file_path full or partial
470
+ # @param [Array] arguments for executable, if any
471
+ #
472
+ # @return [String] executable command string
473
+ def format_executable_command(executable_file_path, *arguments)
474
+ must_be_overridden
475
+ end
476
+
477
+ # Formats a shell command using the given script path and arguments.
478
+ # Provides the path to the executable for the script as needed for the
479
+ # current platform.
480
+ #
481
+ # @param [String] shell_script_file_path shell script file path
482
+ # @param [Array] arguments for executable, if any
483
+ #
484
+ # @return [String] executable command string
485
+ def format_shell_command(shell_script_file_path, *arguments)
486
+ must_be_overridden
487
+ end
488
+
489
+ # Formats a ruby command using the given script path and arguments and
490
+ # the sandbox ruby path.
491
+ #
492
+ # @param [String] shell_script_file_path for formatting
493
+ # @param [Array] arguments for command or empty
494
+ #
495
+ # @return [String] executable command string
496
+ def format_ruby_command(shell_script_file_path, *arguments)
497
+ return format_executable_command(sandbox_ruby, [shell_script_file_path, arguments])
498
+ end
499
+
500
+ # Appends STDOUT redirection to the given shell command.
501
+ #
502
+ # @param [String] cmd to format
503
+ # @param [String] redirection target (Default = null output)
504
+ #
505
+ # @return [String] formatted for redirection
506
+ def format_redirect_stdout(cmd, target = nil)
507
+ target ||= null_output_name
508
+ return cmd + " 1>#{target}"
509
+ end
510
+
511
+ # Appends STDERR redirection to the given shell command.
512
+ #
513
+ # @param [String] cmd to format
514
+ # @param [String] redirection target (Default = null output)
515
+ #
516
+ # @return [String] formatted for redirection
517
+ def format_redirect_stderr(cmd, target = nil)
518
+ target ||= null_output_name
519
+ return cmd + " 2>#{target}"
520
+ end
521
+
522
+ # Appends STDERR redirection to the given shell command.
523
+ #
524
+ # @param [String] cmd to format
525
+ # @param [String] redirection target (Default = null output)
526
+ #
527
+ # @return [String] formatted for redirection
528
+ def format_redirect_both(cmd, target = nil)
529
+ target ||= null_output_name
530
+ return cmd + " 1>#{target} 2>&1"
531
+ end
532
+
533
+ # @return [String] full path to the RightScale sandboxed ruby executable
534
+ def sandbox_ruby
535
+ must_be_overridden
536
+ end
537
+
538
+ # Gets the current system uptime.
539
+ #
540
+ # @return [Float] time the machine has been up, in seconds, or 0.0
541
+ def uptime
542
+ must_be_overridden
543
+ end
544
+
545
+ # Gets the time at which the system was booted.
546
+ #
547
+ # @return [Integer] the UTC timestamp at which the system was booted or nil on failure
548
+ def booted_at
549
+ must_be_overridden
550
+ end
551
+ end
552
+
553
+ # System controller APIs.
554
+ class Controller < PlatformHelperBase
555
+
556
+ # Reboot machine now.
557
+ #
558
+ # @return [TrueClass] always true
559
+ def reboot
560
+ must_be_overridden
561
+ end
562
+
563
+ # Shutdown machine now.
564
+ #
565
+ # @return [TrueClass] always true
566
+ def shutdown
567
+ must_be_overridden
568
+ end
569
+ end
570
+
571
+ # Randomizer APIs.
572
+ class Rng < PlatformHelperBase
573
+
574
+ # Generates a pseudo-random byte string.
575
+ #
576
+ # @param [Fixnum] count of bytes
577
+ #
578
+ # @return [String] bytes
579
+ def pseudorandom_bytes(count)
580
+ must_be_overridden
581
+ end
582
+ end
583
+
584
+ # Process APIs.
585
+ class Process < PlatformHelperBase
586
+
587
+ # Queries resident/working set size (total memory used by process) for
588
+ # the process given by identifier (PID).
589
+ #
590
+ # @param [Fixnum] pid for query (Default = current process)
591
+ #
592
+ # @return [Integer] current set size in KB
593
+ def resident_set_size(pid = nil)
594
+ must_be_overridden
595
+ end
596
+ end
597
+
598
+ # Package installation APIs.
599
+ class Installer < PlatformHelperBase
600
+
601
+ # exceptions
602
+ class PackageNotFound < ::RightScale::Exceptions::PlatformError; end
603
+ class PackageManagerNotFound < ::RightScale::Exceptions::PlatformError; end
604
+
605
+ # @return [String] installer output or nil
606
+ attr_accessor :output
607
+
608
+ def initialize
609
+ @output = nil
610
+ end
611
+
612
+ # Install packages based on installed package manager.
613
+ #
614
+ # For Unix compatibility; has no significance in Windows
615
+ #
616
+ # @param [Array] packages to be installed
617
+ #
618
+ # @return [TrueClass] always true
619
+ #
620
+ # @raise [RightScale::Exceptions::PlatformError] if not supported by platform
621
+ # @raise [PackageNotFound] if package is not found
622
+ # @raise [PackageManagerNotFound] if package manager is not available
623
+ # @raise [CommandError] on any other command failure
624
+ def install(packages)
625
+ must_be_overridden
626
+ end
627
+ end
628
+
629
+ private
630
+
631
+ # Load platform specific implementation
632
+ def initialize
633
+ @genus = nil
634
+ @species = nil
635
+ @filesystem = nil
636
+ @shell = nil
637
+ @ssh = nil
638
+ @controller = nil
639
+ @installer = nil
640
+ @flavor = nil
641
+ @release = nil
642
+ @codename = nil
643
+
644
+ initialize_platform_specific
645
+ end
646
+
647
+ # First-initialization tasks. Also convenient for overriding during
648
+ # testing on platforms that differ from current platform.
649
+ def initialize_platform_specific
322
650
  load_platform_specific
323
- if linux?
324
- res = Platform.const_get(const_name).new
325
- elsif darwin?
326
- res = Platform.const_get(const_name).new
327
- elsif windows?
328
- res = Platform.const_get(const_name).new
651
+ initialize_genus
652
+ initialize_species
653
+ end
654
+
655
+ # Load platform specific implementation
656
+ #
657
+ # @return [TrueClass|FalseClass] true if loaded first time, false if already loaded
658
+ def load_platform_specific
659
+ # TEAL NOTE the unusal thing about this singleton is that it is
660
+ # redefined incrementally by first loading the base then the genus then
661
+ # the species, all of which define parts of the whole.
662
+ result = require platform_genus_path
663
+ result = (require platform_species_path) && result
664
+ result
665
+ end
666
+
667
+ # Performs any platform genus-specific initialization. This method is
668
+ # invoked only after the current platform's specific implementation has
669
+ # been loaded.
670
+ #
671
+ # @return [TrueClass] always true
672
+ def initialize_genus
673
+ raise ::NotImplementedError, 'Must be overridden'
674
+ end
675
+
676
+ # Performs any platform species-specific initialization. This method is
677
+ # invoked only after the current platform's specific implementation has
678
+ # been loaded.
679
+ #
680
+ # @return [TrueClass] always true
681
+ def initialize_species
682
+ raise ::NotImplementedError, 'Must be overridden'
683
+ end
684
+
685
+ # Determines genus/species for current platform.
686
+ def resolve_taxonomy
687
+ case ::RbConfig::CONFIG['host_os']
688
+ when /darwin/i
689
+ @genus = :unix
690
+ @species = :darwin
691
+ when /linux/i
692
+ @genus = :unix
693
+ @species = :linux
694
+ when /mingw/i
695
+ @genus = :windows
696
+ @species = :mingw
697
+ when /mswin/i
698
+ @genus = :windows
699
+ @species = :mswin
700
+ when /windows|win32|dos|cygwin/i
701
+ raise ::RightScale::Exceptions::PlatformError,
702
+ 'Unsupported Ruby-on-Windows variant'
703
+ else
704
+ raise ::RightScale::Exceptions::PlatformError, 'Unknown platform'
329
705
  end
330
- self.instance_variable_set(instance_var, res)
706
+ true
707
+ end
708
+
709
+ # @return [String] path to platform-independent implementation.
710
+ def platform_base_path
711
+ ::File.expand_path('../platform', __FILE__)
712
+ end
713
+
714
+ # @return [String] path to platform-specific genus implementation.
715
+ def platform_genus_path
716
+ ::File.expand_path("#{genus}/platform", platform_base_path)
717
+ end
718
+
719
+ # @return [String] path to platform-specific species implementation.
720
+ def platform_species_path
721
+ ::File.expand_path("#{genus}/#{species}/platform", platform_base_path)
722
+ end
723
+
724
+ # Retrieve platform specific service implementation
725
+ #
726
+ # @param [Symbol] name of platform service
727
+ #
728
+ # @return [PlatformHelperBase] service instance
729
+ #
730
+ # @raise [RightScale::Exceptions::PlatformError] on unknown service
731
+ def platform_service(name)
732
+ instance_var = "@#{name.to_s}".to_sym
733
+ const_name = name.to_s.camelize
734
+
735
+ unless res = self.instance_variable_get(instance_var)
736
+ load_platform_specific
737
+ if clazz = Platform.const_get(const_name)
738
+ res = clazz.new
739
+ self.instance_variable_set(instance_var, res)
740
+ else
741
+ raise ::RightScale::Exceptions::PlatformError,
742
+ "Unknown platform service: #{name}"
743
+ end
744
+ end
745
+ return res
746
+ end
747
+
748
+ # Determines which cloud we're on by the cheap but simple expedient of
749
+ # reading the RightScale cloud file.
750
+ #
751
+ # @deprecated leverage the right_link cloud libraries for any cloud-
752
+ # specific behavior because the behavior of all possible clouds is
753
+ # beyond the scope of hard-coded case statements.
754
+ #
755
+ # @return [String] cloud type or nil
756
+ def resolve_cloud_type
757
+ cloud_file_path = ::File.join(self.filesystem.right_scale_static_state_dir, 'cloud')
758
+ @cloud_type = ::File.read(cloud_file_path) rescue nil
759
+ @cloud_type
331
760
  end
332
- return res
333
- end
334
761
 
335
- end # Platform
762
+ end # Platform
336
763
 
337
- end # RightScale
764
+ end # RightScale
338
765
 
339
- # Initialize for current platform
340
- RightScale::Platform.load_platform_specific
766
+ # Initialize for current platform and/or force singleton creation on current
767
+ # thread to avoid any weird threaded initialization issues.
768
+ ::RightScale::Platform.instance
341
769
 
342
- end # Unless already defined
770
+ end # unless already defined