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
@@ -0,0 +1,720 @@
1
+ #
2
+ # Copyright (c) 2013 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 ::File.expand_path('../../spec_helper', __FILE__)
24
+
25
+ shared_examples_for 'supports windows platform statics' do
26
+ it 'should be Windows' do
27
+ subject.unix?.should be_false
28
+ subject.windows?.should be_true
29
+ subject.linux?.should be_false
30
+ subject.darwin?.should be_false
31
+ end
32
+ end # supports windows platform statics
33
+
34
+ shared_examples_for 'supports windows platform initialization' do
35
+ it 'should query os info during initialize' do
36
+ instrument_os_info(6, 1, 7601)
37
+ platform_instance.send(:initialize_genus)
38
+ platform_instance.release.should == '6.1.7601'
39
+ platform_instance.codename.should == ''
40
+ end
41
+ end # supports windows platform initialization
42
+
43
+ shared_examples_for 'supports windows platform filesystem' do
44
+ context '#find_executable_in_path' do
45
+ let(:pathexts) { %w[.EXE .BAT] }
46
+ let(:paths) { %w[C:\Windows\system32 C:\Windows C:\Windows\System32\Wbem] }
47
+ let(:pathext_env) { pathexts.join(';') }
48
+ let(:path_env) { paths.join(';') }
49
+ let(:expected_path) { 'C:/Windows/foo.EXE' }
50
+
51
+ it 'should find executable' do
52
+ mock_env = flexmock(::ENV)
53
+ mock_env.should_receive(:[]).with('PATHEXT').and_return(pathext_env)
54
+ mock_env.should_receive(:[]).with('PATH').and_return(path_env)
55
+
56
+ all_paths = [::Dir.getwd.gsub("\\", '/')] + paths
57
+ foo_found = false
58
+ all_paths.each do |path|
59
+ pathexts.each do |pathext|
60
+ unless foo_found
61
+ foo_path = ::File.join(path.gsub("\\", '/'), 'foo' + pathext)
62
+ foo_matched = foo_path == expected_path
63
+ file_class.should_receive(:executable?).
64
+ with(foo_path).
65
+ and_return(foo_matched).
66
+ twice
67
+ foo_found = foo_matched
68
+ end
69
+ bar_path = ::File.join(path.gsub("\\", '/'), 'bar' + pathext)
70
+ file_class.should_receive(:executable?).with(bar_path).and_return(false).twice
71
+ end
72
+ end
73
+
74
+ subject.find_executable_in_path('foo').should == expected_path
75
+ subject.has_executable_in_path('foo').should be_true
76
+ subject.find_executable_in_path('bar').should be_nil
77
+ subject.has_executable_in_path('bar').should be_false
78
+ end
79
+ end # find_executable_in_path
80
+
81
+ context 'paths' do
82
+ context 'constants' do
83
+ before(:each) do
84
+ # win32/dir-based constants.
85
+ subject.should_receive(:common_app_data_dir).and_return('C:/ProgramData')
86
+ subject.should_receive(:program_files_dir).and_return('C:/Program Files (x86)')
87
+
88
+ # ENV-based constants.
89
+ @mock_env = flexmock(::ENV)
90
+ @mock_env.should_receive(:[]).with('SystemRoot').and_return("C:\\Windows")
91
+ @mock_env.should_receive(:[]).with('USERPROFILE').and_return("C:\\Users\\foo")
92
+ end
93
+
94
+ it 'should return windows path constants' do
95
+ # windows-specific constants.
96
+ subject.company_app_data_dir.should == 'C:/ProgramData/RightScale'
97
+ subject.company_program_files_dir.should == 'C:/Program Files (x86)/RightScale'
98
+ subject.system_root.should == 'C:/Windows'
99
+ subject.user_home_dir.should == 'C:/Users/foo'
100
+
101
+ # constants available to all platforms.
102
+ subject.right_agent_cfg_dir.should == 'C:/ProgramData/RightScale/right_agent'
103
+ subject.right_scale_static_state_dir.should == 'C:/ProgramData/RightScale/rightscale.d'
104
+ subject.right_link_static_state_dir.should == 'C:/ProgramData/RightScale/rightscale.d/right_link'
105
+ subject.right_link_dynamic_state_dir.should == 'C:/ProgramData/RightScale/right_link'
106
+ subject.spool_dir.should == 'C:/ProgramData/RightScale/spool'
107
+ subject.ssh_cfg_dir.should == 'C:/Users/foo/.ssh'
108
+ subject.cache_dir.should == 'C:/ProgramData/RightScale/cache'
109
+ subject.log_dir.should == 'C:/ProgramData/RightScale/log'
110
+ subject.source_code_dir.should == 'C:/ProgramData/RightScale/src'
111
+ subject.pid_dir.should == 'C:/ProgramData/RightScale/run'
112
+
113
+ @mock_env.
114
+ should_receive(:[]).
115
+ with('RS_RIGHT_LINK_HOME').
116
+ and_return("C:\\PROGRA~2\\RIGHTS~1\\RIGHTL~1")
117
+ file_class.
118
+ should_receive(:normalize_path).
119
+ with('C:/PROGRA~2/RIGHTS~1/RIGHTL~1').
120
+ and_return('C:/PROGRA~2/RIGHTS~1/RIGHTL~1').
121
+ once
122
+ subject.right_link_home_dir.should == 'C:/PROGRA~2/RIGHTS~1/RIGHTL~1'
123
+ subject.private_bin_dir.should == 'C:/PROGRA~2/RIGHTS~1/RIGHTL~1/bin'
124
+ subject.sandbox_dir.should == 'C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox'
125
+ end
126
+
127
+ it 'should generate right_link_home_dir when not set in ENV' do
128
+ @mock_env.should_receive(:[]).with('RS_RIGHT_LINK_HOME').and_return(nil)
129
+ file_class.
130
+ should_receive(:normalize_path).
131
+ with('C:/Program Files (x86)/RightScale/RightLink').
132
+ and_return('C:/PROGRA~2/RIGHTS~1/RIGHTL~1').
133
+ once
134
+ subject.right_link_home_dir.should == 'C:/PROGRA~2/RIGHTS~1/RIGHTL~1'
135
+ end
136
+ end
137
+
138
+ context '#temp_dir' do
139
+ it 'should query temp_dir by calling GetTempPath API' do
140
+ temp_path_from_api = "C:\\Users\\foo\\AppData\\Local\\Temp\\"
141
+ instrument_get_temp_path_api(temp_path_from_api) { temp_path_from_api.length }
142
+ subject.temp_dir.should == 'C:/Users/foo/AppData/Local/Temp'
143
+ end
144
+ end
145
+
146
+ context '#long_path_to_short_path' do
147
+ it 'should convert existing long path to short path' do
148
+ long_path = 'C:\Program Files (x86)\RightScale\RightLink'
149
+ short_path = 'C:/PROGRA~2/RIGHTS~1/RIGHTL~1'
150
+ instrument_get_short_path_name_api(long_path, short_path) { short_path.length }
151
+ file_class.should_receive(:exists?).with(long_path).and_return(true).once
152
+ subject.long_path_to_short_path(long_path).should == short_path
153
+ end
154
+
155
+ it 'should convert existing parent of missing long path to short path' do
156
+ long_path_parent = 'C:/Program Files (x86)/RightScale'
157
+ long_path = long_path_parent + '/RightLink'
158
+ short_path_parent = 'C:/PROGRA~2/RIGHTS~1'
159
+ short_path = short_path_parent + '/RightLink'
160
+ instrument_get_short_path_name_api(long_path_parent, short_path_parent) { short_path_parent.length }
161
+ file_class.should_receive(:exists?).with(long_path).and_return(false).once
162
+ file_class.should_receive(:exists?).with(long_path_parent).and_return(true).once
163
+ subject.long_path_to_short_path(long_path).should == short_path
164
+ end
165
+ end
166
+
167
+ context '#pretty_path' do
168
+ it 'should pretty path' do
169
+ ugly = 'c:\/make//\/me\pretty'
170
+ pretty_for_ruby = 'c:/make/me/pretty'
171
+ pretty_for_cmd_exe = 'c:\make\me\pretty'
172
+ subject.pretty_path(ugly).should == pretty_for_ruby
173
+ subject.pretty_path(ugly, true).should == pretty_for_cmd_exe
174
+ subject.pretty_path(pretty_for_ruby) == pretty_for_ruby
175
+ subject.pretty_path(pretty_for_cmd_exe) == pretty_for_ruby
176
+ subject.pretty_path(pretty_for_ruby, true) == pretty_for_cmd_exe
177
+ subject.pretty_path(pretty_for_cmd_exe, true) == pretty_for_cmd_exe
178
+ end
179
+ end
180
+
181
+ context '#ensure_local_drive_path' do
182
+ before(:each) do
183
+ flexmock(::ENV).should_receive(:[]).with('HOMEDRIVE').and_return('C:')
184
+ end
185
+
186
+ it 'should do nothing if path is already on home drive' do
187
+ path = 'c:\already\local.txt'
188
+ subject.ensure_local_drive_path(path, 'foo').should == path
189
+ end
190
+
191
+ it 'should copy file to temp directory if not local' do
192
+ network_path = 'z:/from/network/drive.txt'
193
+ local_path = 'c:/temp/foo/drive.txt'
194
+ subject.should_receive(:temp_dir).and_return('c:/temp')
195
+ mock_file_utils = flexmock(::FileUtils)
196
+ mock_file_utils.should_receive(:mkdir_p).with('c:/temp/foo').and_return(true).once
197
+ file_class.should_receive(:directory?).with(network_path).and_return(false).once
198
+ mock_file_utils.should_receive(:cp).with(network_path, local_path).and_return(true).once
199
+ subject.ensure_local_drive_path(network_path, 'foo').should == local_path
200
+ end
201
+ end
202
+ end # paths
203
+
204
+ context '#create_symlink' do
205
+ it 'should create directory symlink' do
206
+ from_path = 'c:\foo\bar'
207
+ to_path = 'c:\foo\baz'
208
+ file_class.should_receive(:directory?).with(from_path).and_return(true)
209
+ instrument_create_symbolic_link_api(from_path, to_path, 1) { 1 }
210
+ subject.create_symlink(from_path, to_path).should == 0
211
+ end
212
+
213
+ it 'should create file symlink' do
214
+ from_path = 'c:\foo\bar.txt'
215
+ to_path = 'c:\baz\bar.txt'
216
+ file_class.should_receive(:directory?).with(from_path).and_return(false)
217
+ instrument_create_symbolic_link_api(from_path, to_path, 0) { 1 }
218
+ subject.create_symlink(from_path, to_path).should == 0
219
+ end
220
+
221
+ it 'should raise Win32Error on failure' do
222
+ from_path = 'c:\some\file.txt'
223
+ to_path = 'c:\no_access\hmm.txt'
224
+ file_class.should_receive(:directory?).with(from_path).and_return(false)
225
+ instrument_create_symbolic_link_api(from_path, to_path, 0) { 0 }
226
+ instrument_win32_error(5, 'Access is denied.')
227
+ expect { subject.create_symlink(from_path, to_path) }.
228
+ to raise_error(
229
+ described_class::Win32Error,
230
+ "Failed to create link from #{from_path.inspect} to #{to_path.inspect}\nWin32 error code = 5\nAccess is denied.")
231
+ end
232
+ end # create_symlink
233
+ end # supports windows platform filesystem
234
+
235
+ shared_examples_for 'supports windows platform shell' do
236
+ # required by 'supports any platform shell'
237
+ def instrument_booted_at(booted_at)
238
+ # example:
239
+ # >echo | wmic OS Get LastBootUpTime 2>&1
240
+ # LastBootUpTime
241
+ # 20131025093127.375199-420
242
+ bat = ::Time.at(booted_at.to_i) # same bat time ...
243
+ platform_class.
244
+ should_receive(:execute).
245
+ with('echo | wmic OS Get LastBootUpTime 2>&1', {}).
246
+ and_return(
247
+ '%04d%02d%02d%02d%02d%02d.%06d%03d' %
248
+ [bat.year, bat.month, bat.day,
249
+ bat.hour, bat.min, bat.sec,
250
+ bat.usec, bat.utc_offset / 60]
251
+ )
252
+ end
253
+
254
+ it_should_behave_like 'supports any platform shell'
255
+
256
+ context 'paths' do
257
+ before(:each) do
258
+ # win32/dir-based constants.
259
+ flexmock(platform_class.filesystem).
260
+ should_receive(:sandbox_dir).
261
+ and_return('C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox')
262
+
263
+ # ENV-based constants.
264
+ @mock_env = flexmock(::ENV)
265
+ @mock_env.should_receive(:[]).
266
+ with('RS_RUBY_EXE').
267
+ and_return('C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox/ruby/bin/ruby.exe').
268
+ by_default
269
+ end
270
+
271
+ it 'should return path constants for windows' do
272
+ subject.null_output_name.should == 'NUL'
273
+ file_class.
274
+ should_receive(:normalize_path).
275
+ with('C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox/ruby/bin/ruby.exe').
276
+ and_return('C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox/ruby/bin/ruby.exe').
277
+ once
278
+ subject.sandbox_ruby.should == 'C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox/ruby/bin/ruby.exe'
279
+ end
280
+
281
+ it 'should generate right_link_home_dir when not set in ENV' do
282
+ @mock_env.should_receive(:[]).with('RS_RUBY_EXE').and_return(nil)
283
+ file_class.
284
+ should_receive(:normalize_path).
285
+ with('C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox/ruby/bin/ruby.exe').
286
+ and_return('C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox/ruby/bin/ruby.exe').
287
+ once
288
+ subject.sandbox_ruby.should == 'C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox/ruby/bin/ruby.exe'
289
+ end
290
+ end # paths
291
+
292
+ context 'commands' do
293
+ context '#format_script_file_name' do
294
+ let(:pathexts) { %w[.EXE .BAT] }
295
+ let(:pathext_env) { pathexts.join(';') }
296
+
297
+ before(:each) do
298
+ flexmock(::ENV).should_receive(:[]).with('PATHEXT').and_return(pathext_env)
299
+ end
300
+
301
+ it 'should format for powershell by default' do
302
+ subject.format_script_file_name('foo').should == 'foo.ps1'
303
+ end
304
+
305
+ it 'should keep extension when executable' do
306
+ subject.format_script_file_name('foo.bat').should == 'foo.bat'
307
+ end
308
+
309
+ it 'should append default extension when not executable' do
310
+ subject.format_script_file_name('foo.bar').should == 'foo.bar.ps1'
311
+ end
312
+ end
313
+
314
+ context '#format_executable_command' do
315
+ it 'should format using given script path' do
316
+ subject.format_executable_command('foo.bat', 'a/b', 'c d').should == 'foo.bat a/b "c d"'
317
+ end
318
+
319
+ it 'should insert cmd.exe if no extension was given' do
320
+ subject.format_executable_command('foo', 'bar', '1 2').should == 'cmd.exe /C "foo bar "1 2""'
321
+ end
322
+ end
323
+
324
+ context '#format_shell_command' do
325
+ it 'should insert powershell.exe when extension is .ps1' do
326
+ # RightRun runs child processes as 64-bit instead of 32-bit as would be
327
+ # the default behavior when creating a child process from ruby.exe
328
+ rrp = "C:\\PROGRA~1\\RIGHTS~1\\Shared\\RightRun.exe"
329
+ script_path = 'c:\temp\foo.ps1'
330
+ subject.should_receive(:right_run_path).and_return(rrp)
331
+
332
+ # we wrap powershell script execution with additional checks for
333
+ # $LastExitCode and $Error to ensure user script doesn't fail
334
+ # mysteriously without any kind of error reporting (i.e. it saves us
335
+ # some support calls).
336
+ expected_cmd =
337
+ "#{rrp} powershell.exe -command \"&{set-executionpolicy -executionPolicy RemoteSigned -Scope Process;" +
338
+ " &#{script_path} bar; if ($NULL -eq $LastExitCode) { $LastExitCode = 0 };" +
339
+ ' if ((0 -eq $LastExitCode) -and ($Error.Count -gt 0))' +
340
+ " { $RS_message = 'Script exited successfully but $Error contained '+($Error.Count)+' error(s):';" +
341
+ " write-output $RS_message; write-output $Error; $LastExitCode = 1 }; exit $LastExitCode}\""
342
+
343
+ subject.format_shell_command(script_path, 'bar').should == expected_cmd
344
+ end
345
+
346
+ it 'should insert ruby.exe when extension is .rb' do
347
+ ruby_exe_path = 'C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox/ruby/bin/ruby.exe'
348
+ subject.should_receive(:sandbox_ruby).and_return(ruby_exe_path)
349
+ subject.format_shell_command('foo.rb', 'bar').should == "#{ruby_exe_path} foo.rb bar"
350
+ end
351
+
352
+ it 'should format using given script path for other extensions' do
353
+ subject.format_shell_command('foo.bat', 'a/b', 'c d').should == 'foo.bat a/b "c d"'
354
+ end
355
+
356
+ it 'should insert cmd.exe if no extension was given' do
357
+ subject.format_shell_command('foo', 'bar', '1 2').should == 'cmd.exe /C "foo bar "1 2""'
358
+ end
359
+ end
360
+
361
+ context '#format_ruby_command' do
362
+ it 'should format for ruby' do
363
+ ruby_exe_path = 'C:/PROGRA~2/RIGHTS~1/RIGHTL~1/sandbox/ruby/bin/ruby.exe'
364
+ subject.should_receive(:sandbox_ruby).and_return(ruby_exe_path)
365
+ subject.format_ruby_command('foo.rb', 'bar').should == "#{ruby_exe_path} foo.rb bar"
366
+ end
367
+ end
368
+ end # commands
369
+ end # supports windows platform shell
370
+
371
+ shared_examples_for 'supports windows platform controller' do
372
+ context '#reboot' do
373
+ it 'should call InitiateSystemShutdown API' do
374
+ instrument_initiate_system_shutdown_api(true)
375
+ subject.reboot.should be_true
376
+ end
377
+ end # reboot
378
+
379
+ context '#shutdown' do
380
+ it 'should call InitiateSystemShutdown API' do
381
+ instrument_initiate_system_shutdown_api(false)
382
+ subject.shutdown.should be_true
383
+ end
384
+ end # shutdown
385
+ end # controller
386
+
387
+ shared_examples_for 'supports windows platform installer' do
388
+ context :install do
389
+ it 'should raise not implemented' do
390
+ expect { subject.install(%w[foo bar]) }.
391
+ to raise_error(
392
+ ::RightScale::Exceptions::PlatformError,
393
+ 'No package installers supported on Windows')
394
+ end
395
+ end # install
396
+ end # supports windows platform installer
397
+
398
+ shared_examples_for 'supports windows platform rng' do
399
+ context '#pseudorandom_bytes' do
400
+ it 'should generate random bytes using ruby under Windows' do
401
+ a = subject.pseudorandom_bytes(16)
402
+ b = subject.pseudorandom_bytes(16)
403
+ a.bytesize.should == 16
404
+ b.bytesize.should == 16
405
+ a.should_not == b # equality is statistically impossible
406
+ subject.pseudorandom_bytes(256).bytesize.should == 256
407
+ end
408
+ end # pseudorandom_bytes
409
+ end # supports windows platform rng
410
+
411
+ shared_examples_for 'supports windows platform process' do
412
+ context '#resident_set_size' do
413
+ it 'should return resident set size for current process' do
414
+ expected_size = 12345
415
+ instrument_get_process_memory_info_api(expected_size)
416
+ subject.resident_set_size.should == expected_size
417
+ end
418
+ end # pseudorandom_bytes
419
+ end # supports windows platform rng
420
+
421
+ shared_examples_for 'supports windows platform volume manager' do
422
+ let(:win2003_version) do
423
+ ::RightScale::Platform::WindowsSystemInformation::Version.new(5, 2, 3790)
424
+ end
425
+ let(:win2008r2_version) do
426
+ ::RightScale::Platform::WindowsSystemInformation::Version.new(6, 1, 7601)
427
+ end
428
+
429
+ before(:each) do
430
+ flexmock(platform_class.windows_system_information).
431
+ should_receive(:version).
432
+ and_return(win2008r2_version)
433
+ end
434
+
435
+ context :is_attachable_volume_path? do
436
+ it 'allows paths with hyphens and underscores' do
437
+ subject.is_attachable_volume_path?('C:\\Some_crazy-path').should == true
438
+ end
439
+
440
+ it 'allows paths with periods' do
441
+ subject.is_attachable_volume_path?('C:\\Some.crazy.path').should == true
442
+ end
443
+
444
+ it 'allows paths with tildes' do
445
+ subject.is_attachable_volume_path?('C:\\~Some~crazy~path').should == true
446
+ end
447
+ end
448
+
449
+ context :volumes do
450
+ let(:volumes_script) do
451
+ <<EOF
452
+ rescan
453
+ list volume
454
+ EOF
455
+ end
456
+
457
+ it 'can parse volumes from diskpart output' do
458
+ subject.
459
+ should_receive(:run_diskpart_script).
460
+ with(volumes_script, String).
461
+ and_return(
462
+ <<EOF
463
+ Volume ### Ltr Label Fs Type Size Status Info
464
+ ---------- --- ----------- ----- ---------- ------- --------- --------
465
+ Volume 0 C 2008Boot NTFS Partition 80 GB Healthy System
466
+ * Volume 1 D NTFS Partition 4094 MB Healthy
467
+ Volume 2 NTFS Partition 4094 MB Healthy
468
+ EOF
469
+ ).once
470
+ expected_volumes = [
471
+ { :index => 0, :device => 'C:', :label => '2008Boot', :filesystem => 'NTFS', :type => 'Partition', :total_size => 85899345920, :status => 'Healthy', :info => 'System' },
472
+ { :index => 1, :device => 'D:', :label => nil, :filesystem => 'NTFS', :type => 'Partition', :total_size => 4292870144, :status => 'Healthy', :info => nil },
473
+ { :index => 2, :device => nil, :label => nil, :filesystem => 'NTFS', :type => 'Partition', :total_size => 4292870144, :status => 'Healthy', :info => nil }
474
+ ]
475
+ subject.volumes.should == expected_volumes
476
+ end
477
+
478
+ it 'can parse volumes from diskpart output with mounted paths' do
479
+ subject.
480
+ should_receive(:run_diskpart_script).
481
+ with(volumes_script, String).
482
+ and_return(
483
+ <<EOF
484
+ Volume ### Ltr Label Fs Type Size Status Info
485
+ ---------- --- ----------- ----- ---------- ------- --------- --------
486
+ Volume 0 C NTFS Partition 80 GB Healthy System
487
+ * Volume 1 FAT32 Partition 1023 MB Healthy
488
+ C:\\Program Files\\RightScale\\Mount\\Softlayer\\
489
+ EOF
490
+ ).once
491
+ expected_volumes = [
492
+ { :index => 0, :device => 'C:', :label => nil, :filesystem => 'NTFS', :type => 'Partition', :total_size => 85899345920, :status => 'Healthy', :info => 'System' },
493
+ { :index => 1, :device => 'C:\\Program Files\\RightScale\\Mount\\Softlayer\\', :label => nil, :filesystem => 'FAT32', :type => 'Partition', :total_size => 1072693248, :status => 'Healthy', :info => nil }
494
+ ]
495
+ subject.volumes.should == expected_volumes
496
+ end
497
+
498
+ it 'raises a parser error when diskpart output is malformed' do
499
+ subject.
500
+ should_receive(:run_diskpart_script).
501
+ with(volumes_script, String).
502
+ and_return('gibberish').
503
+ once
504
+ expect { subject.volumes }.
505
+ to raise_error(RightScale::Platform::VolumeManager::ParserError)
506
+ end
507
+
508
+ it 'can filter results with only one condition' do
509
+ subject.
510
+ should_receive(:run_diskpart_script).
511
+ with(volumes_script, String).
512
+ and_return(
513
+ <<EOF
514
+ Volume ### Ltr Label Fs Type Size Status Info
515
+ ---------- --- ----------- ----- ---------- ------- --------- --------
516
+ Volume 0 C 2008Boot NTFS Partition 80 GB Healthy System
517
+ * Volume 1 D NTFS Partition 4094 MB Healthy
518
+ Volume 2 NTFS Partition 4094 MB Healthy
519
+ EOF
520
+ ).once
521
+ expected_volumes = [
522
+ { :index => 1, :device => 'D:', :label => nil, :filesystem => 'NTFS', :type => 'Partition', :total_size => 4292870144, :status => 'Healthy', :info => nil }
523
+ ]
524
+ subject.volumes(:device => 'D:').should == expected_volumes
525
+ end
526
+
527
+ it 'can filter results with many conditions' do
528
+ subject.
529
+ should_receive(:run_diskpart_script).
530
+ with(volumes_script, String).
531
+ and_return(
532
+ <<EOF
533
+ Volume ### Ltr Label Fs Type Size Status Info
534
+ ---------- --- ----------- ----- ---------- ------- --------- --------
535
+ Volume 0 C 2008Boot NTFS Partition 80 GB Healthy System
536
+ * Volume 1 D NTFS Partition 4094 MB Healthy
537
+ Volume 2 NTFS Partition 4094 MB Healthy
538
+ EOF
539
+ ).once
540
+ expected_volumes = [
541
+ {:index => 1, :device => 'D:', :label => nil, :filesystem => 'NTFS', :type => 'Partition', :total_size => 4292870144, :status => 'Healthy', :info => nil}
542
+ ]
543
+ conditions = { :device => 'D:', :filesystem => 'NTFS', :type => 'Partition' }
544
+ subject.volumes(conditions).should == expected_volumes
545
+ end
546
+ end
547
+
548
+ context :assign_device do
549
+ it 'assigns a device to a drive letter when a drive letter is specified' do
550
+ assign_device_script = <<EOF
551
+ rescan
552
+ list volume
553
+ select volume "0"
554
+ attribute volume clear readonly noerr
555
+
556
+ assign letter=S
557
+ EOF
558
+
559
+ subject.
560
+ should_receive(:run_diskpart_script).
561
+ with(assign_device_script, String).
562
+ and_return('').
563
+ once
564
+ subject.assign_device(0, 'S:')
565
+ end
566
+
567
+ it 'assigns a device to a path when a mount point is specified' do
568
+ assign_device_script = <<EOF
569
+ rescan
570
+ list volume
571
+ select volume "2"
572
+ attribute volume clear readonly noerr
573
+
574
+ assign mount="C:\\Program Files\\RightScale\\Mount\\Softlayer"
575
+ EOF
576
+ subject.
577
+ should_receive(:run_diskpart_script).
578
+ with(assign_device_script, String).
579
+ and_return('').
580
+ once
581
+ subject.assign_device(2, "C:\\Program Files\\RightScale\\Mount\\Softlayer")
582
+ end
583
+
584
+ it 'raises an exception when an invalid drive letter is specified' do
585
+ expect { subject.assign_device(0, 'C:') }.
586
+ to raise_error(RightScale::Platform::VolumeManager::ArgumentError)
587
+ end
588
+
589
+ it 'raises an exception when an invalid path is specified' do
590
+ expect { subject.assign_device(0, 'This is not a path') }.
591
+ to raise_error(RightScale::Platform::VolumeManager::ArgumentError)
592
+ end
593
+
594
+ it 'raises an exception when a mount path is specified and OS is pre 2008' do
595
+ subject.should_receive(:os_version).and_return(win2003_version)
596
+ expect { subject.assign_device(0, "C:\\Somepath") }.
597
+ to raise_error(RightScale::Platform::VolumeManager::ArgumentError)
598
+ end
599
+
600
+ it 'does not assign when already assigned by index' do
601
+ subject.should_receive(:run_diskpart_script).never
602
+ subject.
603
+ should_receive(:volumes).
604
+ and_return([{:index => '0', :device => "C:\\Program Files\\RightScale\\Mount\\Softlayer"}]).
605
+ once
606
+ subject.assign_device(0, "C:\\Program Files\\RightScale\\Mount\\Softlayer", :idempotent => true)
607
+ end
608
+
609
+ it 'does not assign when already assigned by device' do
610
+ subject.should_receive(:run_diskpart_script).never
611
+ subject.
612
+ should_receive(:volumes).
613
+ and_return([{:index => '0', :device => 'Z:'}]).
614
+ once
615
+ subject.assign_device('Z:', 'Z:', :idempotent => true)
616
+ end
617
+
618
+ it 'does not clear readonly flag if :clear_readonly option is set to false' do
619
+ assign_device_script = <<EOF
620
+ rescan
621
+ list volume
622
+ select volume "0"
623
+
624
+
625
+ assign mount="C:\\Program Files\\RightScale\\Mount\\Softlayer"
626
+ EOF
627
+ subject.
628
+ should_receive(:run_diskpart_script).
629
+ with(assign_device_script, String).
630
+ and_return('').
631
+ once
632
+ subject.assign_device(0, "C:\\Program Files\\RightScale\\Mount\\Softlayer", :clear_readonly => false)
633
+ end
634
+
635
+ it 'removes all previous assignments if :remove_all option is set to true' do
636
+ assign_device_script = <<EOF
637
+ rescan
638
+ list volume
639
+ select volume "0"
640
+ attribute volume clear readonly noerr
641
+ remove all noerr
642
+ assign mount="C:\\Program Files\\RightScale\\Mount\\Softlayer"
643
+ EOF
644
+ subject.
645
+ should_receive(:run_diskpart_script).
646
+ with(assign_device_script, String).
647
+ and_return('').
648
+ once
649
+ subject.assign_device(0, "C:\\Program Files\\RightScale\\Mount\\Softlayer", :remove_all => true)
650
+ end
651
+ end
652
+
653
+ context :format_disk do
654
+ it 'formats and assigns a drive to a drive letter when a drive letter is specified' do
655
+ assign_device_script = <<EOF
656
+ rescan
657
+ list disk
658
+ select disk 0
659
+ attribute disk clear readonly noerr
660
+ online disk noerr
661
+ clean
662
+ create partition primary
663
+ assign letter=S
664
+ format FS=NTFS quick
665
+ EOF
666
+ subject.
667
+ should_receive(:run_diskpart_script).
668
+ with(assign_device_script, String).
669
+ and_return('').
670
+ once
671
+ subject.format_disk(0, 'S:')
672
+ end
673
+
674
+ it 'formats and assigns a drive to a path when a mount point is specified' do
675
+ assign_device_script = <<EOF
676
+ rescan
677
+ list disk
678
+ select disk 0
679
+ attribute disk clear readonly noerr
680
+ online disk noerr
681
+ clean
682
+ create partition primary
683
+ assign mount="C:\\Somepath"
684
+ format FS=NTFS quick
685
+ EOF
686
+ subject.
687
+ should_receive(:run_diskpart_script).
688
+ with(assign_device_script, String).
689
+ and_return('').
690
+ once
691
+ subject.format_disk(0, 'C:\\Somepath')
692
+ end
693
+
694
+ it 'raises an exception when a mount path is specified and OS is pre 2008' do
695
+ subject.should_receive(:os_version).and_return(win2003_version)
696
+ expect { subject.format_disk(0, "C:\\Somepath") }.
697
+ to raise_error(RightScale::Platform::VolumeManager::ArgumentError)
698
+ end
699
+ end # format_disk
700
+
701
+ context :online_disk do
702
+ it 'does not online the disk if the disk is already online' do
703
+ online_disk_script = <<EOF
704
+ rescan
705
+ list disk
706
+ select disk 0
707
+ attribute disk clear readonly noerr
708
+ online disk noerr
709
+ EOF
710
+ subject.
711
+ should_receive(:run_diskpart_script).
712
+ with(online_disk_script, String).
713
+ and_return('').
714
+ once
715
+ subject.online_disk(0)
716
+ subject.should_receive(:disks).and_return([{:index => 0, :status => 'Online'}]).once
717
+ subject.online_disk(0, :idempotent => true)
718
+ end
719
+ end
720
+ end # supports windows platform volume manager