right_agent 0.17.2 → 1.0.1

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 (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/right_agent.gemspec CHANGED
@@ -24,9 +24,9 @@ require 'rubygems'
24
24
 
25
25
  Gem::Specification.new do |spec|
26
26
  spec.name = 'right_agent'
27
- spec.version = '0.17.2'
28
- spec.date = '2013-10-28'
29
- spec.authors = ['Lee Kirchhoff', 'Raphael Simon', 'Tony Spataro']
27
+ spec.version = '1.0.1'
28
+ spec.date = '2013-10-29'
29
+ spec.authors = ['Lee Kirchhoff', 'Raphael Simon', 'Tony Spataro', 'Scott Messier']
30
30
  spec.email = 'lee@rightscale.com'
31
31
  spec.homepage = 'https://github.com/rightscale/right_agent'
32
32
  spec.platform = Gem::Platform::RUBY
@@ -39,15 +39,20 @@ Gem::Specification.new do |spec|
39
39
 
40
40
  spec.add_dependency('right_support', ['>= 2.4.1', '< 3.0'])
41
41
  spec.add_dependency('right_amqp', '~> 0.7')
42
- spec.add_dependency('json', ['>= 1.4', '<= 1.7.6']) # json_create behavior change in 1.7.7
43
42
  spec.add_dependency('eventmachine', ['>= 0.12.10', '< 2.0'])
44
- spec.add_dependency('msgpack', ['>= 0.4.4', '< 0.6'])
45
43
  spec.add_dependency('net-ssh', '~> 2.0')
46
44
 
47
- if spec.platform.to_s =~ /mswin|mingw/
48
- spec.add_dependency('win32-dir', '~> 0.3.5')
49
- spec.add_dependency('win32-process', '~> 0.6.1')
45
+ # not currently needed by Linux but it does no harm to have it.
46
+ spec.add_dependency('ffi')
47
+ case RUBY_PLATFORM
48
+ when /mswin|mingw/i
49
+ spec.add_dependency('win32-dir', '~> 0.4.6')
50
+ spec.add_dependency('win32-process', '~> 0.7.3')
51
+ when /win32|dos|cygwin/i
52
+ raise ::NotImplementedError, 'Unsupported Ruby-on-Windows variant'
50
53
  end
54
+ spec.add_dependency('msgpack', ['>= 0.4.4', '< 0.6'])
55
+ spec.add_dependency('json', '~> 1.4')
51
56
 
52
57
  spec.description = <<-EOF
53
58
  RightAgent provides a foundation for running an agent on a server to interface
@@ -0,0 +1,216 @@
1
+ #
2
+ # Copyright (c) 2009-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
+ # reloads the Platform overrides for the current platform under test and then
26
+ # restores the default platform to avoid breaking subsequent spec tests. works
27
+ # because each implementation is expected to fully override the methods in the
28
+ # base Platform class and so support multiple redefinition.
29
+ #
30
+ # requires the following to be defined by spec in a platform-specific manner:
31
+ # instrument_booted_at
32
+ module PlatformSpecHelper
33
+
34
+ # references mocked Platform class and instance to simplify singleton testing.
35
+ attr_reader :file_class, :platform_class, :platform_instance
36
+
37
+ # resets the Platform singleton.
38
+ def reset_platform_singleton
39
+ # release any existing singleton to force full reload from source.
40
+ ::RightScale::Platform.instance_variable_set(:@singleton__instance__, nil)
41
+ true
42
+ end
43
+
44
+ # Invoke within a before(:all) in described class.
45
+ def before_all_platform_tests
46
+ return true if RUBY_VERSION =~ /^1\.8/ # see below
47
+
48
+ reset_platform_singleton
49
+
50
+ # redefine Platform#initialize_platform_specific to defeat load-upon-
51
+ # initialize behavior of singleton and give us a chance to set genus/species
52
+ # explicitly before any loading occurs.
53
+ ::RightScale::Platform.class_eval('def initialize_platform_specific; end')
54
+
55
+ instance = ::RightScale::Platform.instance
56
+ instance.instance_variable_set(:@genus, expected_genus)
57
+ instance.instance_variable_set(:@species, expected_species)
58
+
59
+ # invoke require logic for load-once behavior.
60
+ unless instance.send(:load_platform_specific)
61
+ # forcibly reload platform under test when require returns false.
62
+ load instance.send(:platform_base_path) + '.rb'
63
+ load instance.send(:platform_genus_path) + '.rb'
64
+ load instance.send(:platform_species_path) + '.rb'
65
+ end
66
+ ::RightScale::Platform.genus.should == expected_genus
67
+ ::RightScale::Platform.species.should == expected_species
68
+ true
69
+ end
70
+
71
+ # Invoke within a after(:all) in described class.
72
+ def after_all_platform_tests
73
+ return true if RUBY_VERSION =~ /^1\.8/ # see below
74
+
75
+ # 'restore' original singleton. this does not undefine any additional nested
76
+ # classes that are platform-specific (for other than the current platform)
77
+ # but they will not be invoked and so are harmless.
78
+ reset_platform_singleton
79
+ instance = ::RightScale::Platform.instance
80
+ load instance.send(:platform_base_path) + '.rb'
81
+ load instance.send(:platform_genus_path) + '.rb'
82
+ load instance.send(:platform_species_path) + '.rb'
83
+ instance.send(:initialize_genus)
84
+ instance.send(:initialize_species)
85
+ true
86
+ end
87
+
88
+ # Invoke within a before(:each) in described class.
89
+ def before_each_platform_test
90
+ # TEAL FIX might be that flexmock version is too new for ruby 1.8 to work,
91
+ # but not really important going forward.
92
+ if RUBY_VERSION =~ /^1\.8/
93
+ pending 'before(:all) mocks do not work in ruby 1.8'
94
+ end
95
+
96
+ # reset singleton again but no need to reload platform code at this point.
97
+ # the issue is that flexmock needs a fresh instance to mock out each time or
98
+ # else the mock teardown goes haywire and spews meaningless stack-traces to
99
+ # the console. the problem seems to be exacerbated by these tests redefining
100
+ # methods of Platform (out of necessity).
101
+ reset_platform_singleton
102
+ platform = ::RightScale::Platform
103
+ instance = platform.instance
104
+ instance.instance_variable_set(:@genus, expected_genus)
105
+ instance.instance_variable_set(:@species, expected_species)
106
+
107
+ # create mocks.
108
+ @file_class = flexmock(::File)
109
+ @platform_class = flexmock(platform)
110
+ @platform_instance = flexmock(instance)
111
+
112
+ # require normalize_path to be mocked during spec runs as it actually
113
+ # invokes APIs on Windows.
114
+ file_class.should_receive(:normalize_path).and_return do |*args|
115
+ raise ::NotImplementedError, "Must mock all calls to File.normalize_path: #{args.inspect}"
116
+ end.by_default
117
+
118
+ # defeat any shell execution by default (i.e. until properly mocked).
119
+ platform_class.should_receive(:execute).and_return do |*args|
120
+ raise ::NotImplementedError, "Must mock all calls to Platform.execute for class: #{args.inspect}"
121
+ end.by_default
122
+ platform_instance.should_receive(:execute).and_return do |*args|
123
+ raise ::NotImplementedError, "Must instrument all calls to Platform#execute for instance: #{args.inspect}"
124
+ end.by_default
125
+ ::RightScale::Platform.genus.should == expected_genus
126
+ ::RightScale::Platform.species.should == expected_species
127
+ expect { ::RightScale::Platform.execute(nil) }.to raise_error(::NotImplementedError)
128
+ expect { ::RightScale::Platform.instance.execute(nil) }.to raise_error(::NotImplementedError)
129
+ true
130
+ end
131
+
132
+ # Invoke within a after(:each) in described class.
133
+ def after_each_platform_test
134
+ @file_class = nil
135
+ @platform_class = nil
136
+ @platform_instance = nil
137
+ true
138
+ end
139
+
140
+ end # PlatformSpecHelper
141
+
142
+ shared_examples_for 'supports any platform shell' do
143
+ let(:minimum_uptime) { 10 * 60 }
144
+ let(:expected_booted_at) { ::Time.now.to_i - minimum_uptime }
145
+
146
+ # Sleeps until timer ticks over. Note that sleep(1) does not guarantee
147
+ # a tick-over and it may take sleep(< 1) to observe the tick-over.
148
+ def wait_for_tick_change
149
+ start_tick = ::Time.now.to_i
150
+ while ::Time.now.to_i == start_tick
151
+ sleep 0.1
152
+ end
153
+ true
154
+ end
155
+
156
+ context '#format_redirect_stdout' do
157
+ it 'should format redirect of stdout' do
158
+ subject.format_redirect_stdout('foo bar').
159
+ should == "foo bar 1>#{subject.null_output_name}"
160
+ subject.format_redirect_stdout('bar foo', '/tmp/foo').
161
+ should == 'bar foo 1>/tmp/foo'
162
+ end
163
+ end # format_redirect_stdout
164
+
165
+ context '#format_redirect_stderr' do
166
+ it 'should format redirect of stderr' do
167
+ subject.format_redirect_stderr('foo bar').
168
+ should == "foo bar 2>#{subject.null_output_name}"
169
+ subject.format_redirect_stderr('bar foo', '/tmp/foo').
170
+ should == 'bar foo 2>/tmp/foo'
171
+ end
172
+ end # format_redirect_stderr
173
+
174
+ context '#format_redirect_both' do
175
+ it 'should format redirect of stderr' do
176
+ subject.format_redirect_both('foo bar').
177
+ should == "foo bar 1>#{subject.null_output_name} 2>&1"
178
+ subject.format_redirect_both('bar foo', '/tmp/foo').
179
+ should == 'bar foo 1>/tmp/foo 2>&1'
180
+ end
181
+ end # format_redirect_both
182
+
183
+ context '#uptime' do
184
+ it 'should be positive' do
185
+ instrument_booted_at(expected_booted_at) { minimum_uptime }
186
+ subject.uptime.should >= minimum_uptime
187
+ end
188
+
189
+ it 'should be strictly increasing' do
190
+ uptime = minimum_uptime
191
+ instrument_booted_at(expected_booted_at) { uptime }
192
+ u0 = subject.uptime
193
+ wait_for_tick_change # uptime may use either system data or a relative time
194
+ uptime += 1
195
+ u1 = subject.uptime
196
+ (u1 - u0).should > 0.0
197
+ end
198
+ end # uptime
199
+
200
+ context '#booted_at' do
201
+ it 'should be some time in the past' do
202
+ instrument_booted_at(expected_booted_at) { minimum_uptime }
203
+ subject.booted_at.should == expected_booted_at
204
+ end
205
+
206
+ it 'should be constant' do
207
+ uptime = minimum_uptime
208
+ instrument_booted_at(expected_booted_at) { uptime }
209
+ b0 = subject.booted_at
210
+ wait_for_tick_change # uptime may use either system data or a relative time
211
+ uptime += 1
212
+ b1 = subject.booted_at
213
+ b0.should == b1
214
+ end
215
+ end # booted_at
216
+ end # supports any platform shell
@@ -0,0 +1,181 @@
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
+ describe RightScale::Platform do
26
+ include PlatformSpecHelper
27
+
28
+ context 'unix/darwin' do
29
+
30
+ # required by before_all_platform_tests
31
+ # called by before(:all) so not defined using let().
32
+ def expected_genus; :unix; end
33
+ def expected_species; :darwin; end
34
+
35
+ before(:all) do
36
+ before_all_platform_tests
37
+ end
38
+
39
+ after(:all) do
40
+ after_all_platform_tests
41
+ end
42
+
43
+ before(:each) do
44
+ before_each_platform_test
45
+ end
46
+
47
+ after(:each) do
48
+ after_each_platform_test
49
+ end
50
+
51
+ context 'statics' do
52
+ subject { platform_class }
53
+
54
+ it 'should be linux' do
55
+ subject.unix?.should be_true
56
+ subject.windows?.should be_false
57
+ subject.linux?.should be_false
58
+ subject.darwin?.should be_true
59
+ end
60
+ end
61
+
62
+ context 'initialization' do
63
+
64
+ let(:sw_vers_cmd) { 'sw_vers -productVersion 2>&1' }
65
+
66
+ context 'when sw_vers is available' do
67
+ it 'should initialize from sw_vers data' do
68
+ platform_instance.
69
+ should_receive(:execute).
70
+ with(sw_vers_cmd).
71
+ and_return("10.8.5\n").
72
+ once
73
+ platform_instance.send(:initialize_species)
74
+ platform_instance.flavor.should == 'mac_os_x'
75
+ platform_instance.release.should == '10.8.5'
76
+ platform_instance.codename.should == ''
77
+ end
78
+ end # when lsb_release is available
79
+
80
+ context 'when sw_vers is not available' do
81
+ it 'should be unknown release' do
82
+ platform_instance.
83
+ should_receive(:execute).
84
+ with(sw_vers_cmd).
85
+ and_raise(described_class::CommandError).
86
+ once
87
+ platform_instance.send(:initialize_species)
88
+ platform_instance.flavor.should == 'mac_os_x'
89
+ platform_instance.release.should == 'unknown'
90
+ platform_instance.codename.should == 'unknown'
91
+ end
92
+ end # when lsb_release is not available
93
+ end # initialization
94
+
95
+ context :controller do
96
+ subject { described_class.controller }
97
+
98
+ context '#reboot' do
99
+ it 'should call shutdown -r now under darwin' do
100
+ platform_class.should_receive(:execute).with('shutdown -r now 2>&1', {}).and_return('')
101
+ subject.reboot.should be_true
102
+ end
103
+ end # reboot
104
+
105
+ context '#shutdown' do
106
+ it 'should call shutdown -h now under darwin' do
107
+ platform_class.should_receive(:execute).with('shutdown -h now 2>&1', {}).and_return('')
108
+ subject.shutdown.should be_true
109
+ end
110
+ end # shutdown
111
+ end # controller
112
+
113
+ context :filesystem do
114
+ subject { described_class.filesystem }
115
+
116
+ it_should_behave_like 'supports unix platform filesystem'
117
+ end
118
+
119
+ context :installer do
120
+ subject { described_class.installer }
121
+
122
+ context :install do
123
+ it 'should raise not implemented' do
124
+ expect { subject.install(%w[foo bar]) }.
125
+ to raise_error(
126
+ ::NotImplementedError, 'Not yet supporting Mac OS package install')
127
+ end
128
+ end # install
129
+ end # installer
130
+
131
+ context :shell do
132
+ subject { described_class.shell }
133
+
134
+ # required by 'supports any platform shell'
135
+ def instrument_booted_at(booted_at)
136
+ time = ::Time.at(booted_at.to_i)
137
+ platform_class.
138
+ should_receive(:execute).
139
+ with('sysctl kern.boottime', {}).
140
+ and_return("kern.boottime: { sec = #{time.to_i}, usec = 0 } #{time.to_s}")
141
+
142
+ # note that uptime is computed relative to boottime.
143
+ true
144
+ end
145
+
146
+ it_should_behave_like 'supports unix platform shell'
147
+ end
148
+
149
+ context :rng do
150
+ subject { described_class.rng }
151
+
152
+ it_should_behave_like 'supports unix platform rng'
153
+ end
154
+
155
+ context :process do
156
+ subject { described_class.process }
157
+
158
+ it_should_behave_like 'supports unix platform process'
159
+ end
160
+
161
+ context :volume_manager do
162
+ subject { platform_class.volume_manager }
163
+
164
+ context :volumes do
165
+ it 'should raise not implemented' do
166
+ expect { subject.volumes }.
167
+ to raise_error(
168
+ ::NotImplementedError, 'Not yet supporting Mac OS volume query')
169
+ end
170
+ end # volumes
171
+
172
+ context :mount_volume do
173
+ it 'should raise not implemented' do
174
+ expect { subject.volumes }.
175
+ to raise_error(
176
+ ::NotImplementedError, 'Not yet supporting Mac OS volume query')
177
+ end
178
+ end # mount_volume
179
+ end # volume_manager
180
+ end # under unix/darwin
181
+ end # RightScale::Platform
@@ -0,0 +1,540 @@
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
+ describe RightScale::Platform do
26
+
27
+ include PlatformSpecHelper
28
+
29
+ context 'unix/linux' do
30
+
31
+ # required by before_all_platform_tests
32
+ # called by before(:all) so not defined using let().
33
+ def expected_genus; :unix; end
34
+ def expected_species; :linux; end
35
+
36
+ before(:all) do
37
+ before_all_platform_tests
38
+ end
39
+
40
+ after(:all) do
41
+ after_all_platform_tests
42
+ end
43
+
44
+ before(:each) do
45
+ before_each_platform_test
46
+ end
47
+
48
+ after(:each) do
49
+ after_each_platform_test
50
+ end
51
+
52
+ context 'statics' do
53
+ subject { platform_class }
54
+
55
+ it 'should be linux' do
56
+ subject.unix?.should be_true
57
+ subject.windows?.should be_false
58
+ subject.linux?.should be_true
59
+ subject.darwin?.should be_false
60
+ end
61
+ end
62
+
63
+ context 'initialization' do
64
+ let(:lsb_help_cmd) { 'lsb_release --help >/dev/null 2>&1' }
65
+
66
+ context 'when lsb_release is available' do
67
+ it 'should initialize from lsb_release data' do
68
+ platform_instance.
69
+ should_receive(:execute).
70
+ with(lsb_help_cmd).
71
+ and_return("lsb_release\nhelp\ndump\n").
72
+ once
73
+ platform_instance.
74
+ should_receive(:execute).
75
+ with('lsb_release -is', { :raise_on_failure => false }).
76
+ and_return("Ubuntu\n").
77
+ once
78
+ platform_instance.
79
+ should_receive(:execute).
80
+ with('lsb_release -rs', { :raise_on_failure => false }).
81
+ and_return("11.04\n").
82
+ once
83
+ platform_instance.
84
+ should_receive(:execute).
85
+ with('lsb_release -cs', { :raise_on_failure => false }).
86
+ and_return("natty\n").
87
+ once
88
+ platform_instance.send(:initialize_species)
89
+ platform_instance.flavor.should == 'ubuntu'
90
+ platform_instance.release.should == '11.04'
91
+ platform_instance.codename.should == 'natty'
92
+ platform_instance.ubuntu?.should be_true
93
+ platform_instance.centos?.should be_false
94
+ platform_instance.suse?.should be_false
95
+ platform_instance.rhel?.should be_false
96
+ end
97
+ end # when lsb_release is available
98
+
99
+ context 'when lsb_release is not available' do
100
+ before(:each) do
101
+ platform_instance.
102
+ should_receive(:execute).
103
+ with(lsb_help_cmd).
104
+ and_raise(described_class::CommandError).
105
+ once
106
+ end
107
+
108
+ context 'when fedora release is available' do
109
+ it 'should be fedora' do
110
+ file_mock = flexmock(::File)
111
+ file_mock.
112
+ should_receive(:exist?).
113
+ with(described_class::FEDORA_REL).
114
+ and_return(true).
115
+ once
116
+ file_mock.
117
+ should_receive(:read).
118
+ with(described_class::FEDORA_REL).
119
+ and_return("Fedora release 7 (Moonshine)\n").
120
+ once
121
+ platform_instance.send(:initialize_species)
122
+ platform_instance.flavor.should == 'fedora'
123
+ platform_instance.release.should == '7'
124
+ platform_instance.codename.should == 'Moonshine'
125
+ platform_instance.ubuntu?.should be_false
126
+ platform_instance.centos?.should be_false
127
+ platform_instance.suse?.should be_false
128
+ platform_instance.rhel?.should be_false
129
+ end
130
+ end # when fedora release is available
131
+
132
+ context 'when fedora release is not available' do
133
+ it 'should be unknown' do
134
+ file_mock = flexmock(::File)
135
+ file_mock.
136
+ should_receive(:exist?).
137
+ with(described_class::FEDORA_REL).
138
+ and_return(false).
139
+ once
140
+ platform_instance.send(:initialize_species)
141
+ platform_instance.flavor.should == 'unknown'
142
+ platform_instance.release.should == 'unknown'
143
+ platform_instance.codename.should == 'unknown'
144
+ platform_instance.ubuntu?.should be_false
145
+ platform_instance.centos?.should be_false
146
+ platform_instance.suse?.should be_false
147
+ platform_instance.rhel?.should be_false
148
+ end
149
+ end # when fedora release is not available
150
+ end # when lsb_release is not available
151
+ end # initialization
152
+
153
+ context :controller do
154
+ subject { described_class.controller }
155
+
156
+ context '#reboot' do
157
+ it 'should call init 6 under unix' do
158
+ platform_class.should_receive(:execute).with('init 6 2>&1', {}).and_return('')
159
+ subject.reboot.should be_true
160
+ end
161
+ end # reboot
162
+
163
+ context '#shutdown' do
164
+ it 'should call init 0 under unix' do
165
+ platform_class.should_receive(:execute).with('init 0 2>&1', {}).and_return('')
166
+ subject.shutdown.should be_true
167
+ end
168
+ end # shutdown
169
+ end # controller
170
+
171
+ context :filesystem do
172
+ subject { described_class.filesystem }
173
+
174
+ it_should_behave_like 'supports unix platform filesystem'
175
+ end
176
+
177
+ context :installer do
178
+ subject { installer = described_class.installer; flexmock(installer) }
179
+
180
+ context :install do
181
+ let(:packages) { %w[foo bar] }
182
+
183
+ let(:installer_data) do
184
+ {
185
+ :aptitude => {
186
+ :command => "apt-get install -y #{packages.join(' ')} 2>&1",
187
+ :failure => "E: Couldn't find package #{packages.last}"
188
+ },
189
+ :yum => {
190
+ :command => "yum install -y #{packages.join(' ')} 2>&1",
191
+ :failure => "No package #{packages.last} available."
192
+ },
193
+ :zypper => {
194
+ :command => "zypper --no-gpg-checks -n #{packages.join(' ')} 2>&1",
195
+ :failure => "Package '#{packages.last}' not found."
196
+ }
197
+ }
198
+ end
199
+
200
+ before(:each) do
201
+ installer_data.keys.each do |installer_kind|
202
+ quiz = (installer_kind.to_s + '?').to_sym
203
+ subject.should_receive(quiz).and_return do
204
+ installer_kind == expected_installer_kind
205
+ end
206
+ end
207
+ end
208
+
209
+ shared_examples_for 'supports linux platform installer install' do
210
+ it 'should succeed if no packages are specified' do
211
+ subject.install([]).should == true
212
+ end
213
+
214
+ it 'should succeed if all packages install successfully' do
215
+ platform_class.
216
+ should_receive(:execute).
217
+ with(installer_data[expected_installer_kind][:command], {}).
218
+ and_return('ok')
219
+ subject.install(packages).should == true
220
+ end
221
+
222
+ it 'should fail if one more packages are not found' do
223
+ platform_class.
224
+ should_receive(:execute).
225
+ with(installer_data[expected_installer_kind][:command], {}).
226
+ and_return(installer_data[expected_installer_kind][:failure])
227
+ expect { subject.install(packages) }.
228
+ to raise_error(
229
+ described_class::Installer::PackageNotFound,
230
+ 'The following packages were not available: bar')
231
+ end
232
+ end
233
+
234
+ [:aptitude, :yum, :zypper].each do |eik|
235
+ context eik do
236
+ let(:expected_installer_kind) { eik }
237
+
238
+ it_should_behave_like 'supports linux platform installer install'
239
+ end
240
+ end
241
+
242
+ context 'given no installers' do
243
+ let(:expected_installer_kind) { nil }
244
+
245
+ it 'should fail if no installers are found' do
246
+ expect { subject.install(packages) }.
247
+ to raise_error(
248
+ described_class::Installer::PackageManagerNotFound,
249
+ 'No package manager binary (apt, yum, zypper) found in /usr/bin')
250
+ end
251
+ end
252
+ end # install
253
+ end # installer
254
+
255
+ context :shell do
256
+ subject { described_class.shell }
257
+
258
+ # required by 'supports any platform shell'
259
+ def instrument_booted_at(booted_at)
260
+ mock_file = flexmock(::File)
261
+ mock_file.should_receive(:read).with('/proc/stat').and_return(
262
+ <<EOF
263
+ ...
264
+ ctxt 234567
265
+ btime #{booted_at.to_i}
266
+ processes 2345
267
+ ...
268
+ EOF
269
+ )
270
+ mock_file.should_receive(:read).with('/proc/uptime').and_return do
271
+ uptime = yield
272
+ "#{uptime.to_f} #{0.99 * uptime.to_f}"
273
+ end
274
+ true
275
+ end
276
+
277
+ it_should_behave_like 'supports unix platform shell'
278
+ end
279
+
280
+ context :rng do
281
+ subject { described_class.rng }
282
+
283
+ it_should_behave_like 'supports unix platform rng'
284
+ end
285
+
286
+ context :process do
287
+ subject { described_class.process }
288
+
289
+ it_should_behave_like 'supports unix platform process'
290
+ end
291
+
292
+ context :volume_manager do
293
+ let(:blkid_cmd) { 'blkid 2>&1' }
294
+
295
+ subject { platform_class.volume_manager }
296
+
297
+ context :volumes do
298
+ it 'can parse volumes from blkid output' do
299
+ expected_volumes = [
300
+ {
301
+ :device => '/dev/xvdh1',
302
+ :sec_type => 'msdos',
303
+ :label => 'METADATA',
304
+ :uuid => '681B-8C5D',
305
+ :type => 'vfat',
306
+ :filesystem => 'vfat'
307
+ },
308
+ {
309
+ :device => '/dev/xvdb1',
310
+ :label => 'SWAP-xvdb1',
311
+ :uuid => 'd51fcca0-6b10-4934-a572-f3898dfd8840',
312
+ :type => 'swap',
313
+ :filesystem => 'swap'
314
+ },
315
+ {
316
+ :device => '/dev/xvda1',
317
+ :uuid => 'f4746f9c-0557-4406-9267-5e918e87ca2e',
318
+ :type => 'ext3',
319
+ :filesystem => 'ext3'
320
+ },
321
+ {
322
+ :device => '/dev/xvda2',
323
+ :uuid => '14d88b9e-9fe6-4974-a8d6-180acdae4016',
324
+ :type => 'ext3',
325
+ :filesystem => 'ext3'
326
+ }
327
+ ]
328
+ platform_class.should_receive(:execute).with(blkid_cmd, {}).and_return(
329
+ <<EOF
330
+ /dev/xvdh1: SEC_TYPE="msdos" LABEL="METADATA" UUID="681B-8C5D" TYPE="vfat"
331
+ /dev/xvdb1: LABEL="SWAP-xvdb1" UUID="d51fcca0-6b10-4934-a572-f3898dfd8840" TYPE="swap"
332
+ /dev/xvda1: UUID="f4746f9c-0557-4406-9267-5e918e87ca2e" TYPE="ext3"
333
+ /dev/xvda2: UUID="14d88b9e-9fe6-4974-a8d6-180acdae4016" TYPE="ext3"
334
+ EOF
335
+ ).once
336
+ subject.volumes.should == expected_volumes
337
+ end
338
+
339
+ it 'can parse volumes with hyphens or underscores (lvm use case)' do
340
+ expected_volumes = [
341
+ {
342
+ :device => '/dev/vg-rightscale-data_storage1/lvol0',
343
+ :uuid => 'ee34706d-866f-476e-9da4-6a18745456a4',
344
+ :type => 'xfs',
345
+ :filesystem => 'xfs'
346
+ }
347
+ ]
348
+ platform_class.should_receive(:execute).with(blkid_cmd, {}).and_return(
349
+ <<EOF
350
+ /dev/vg-rightscale-data_storage1/lvol0: UUID="ee34706d-866f-476e-9da4-6a18745456a4" TYPE="xfs"
351
+ EOF
352
+ ).once
353
+ subject.volumes.should == expected_volumes
354
+ end
355
+
356
+ it 'can parse volumes with periods' do
357
+ expected_volumes = [
358
+ {
359
+ :device => '/dev/please.dont.do.this',
360
+ :uuid => 'ee34706d-866f-476e-9da4-6a18745456a4',
361
+ :type => 'xfs',
362
+ :filesystem => 'xfs'
363
+ }
364
+ ]
365
+ platform_class.should_receive(:execute).with(blkid_cmd, {}).and_return(
366
+ <<EOF
367
+ /dev/please.dont.do.this: UUID="ee34706d-866f-476e-9da4-6a18745456a4" TYPE="xfs"
368
+ EOF
369
+ ).once
370
+ subject.volumes.should == expected_volumes
371
+ end
372
+
373
+ it 'raises a parser error when blkid output is malformed' do
374
+ platform_class.should_receive(:execute).with(blkid_cmd, {}).and_return('gibberish').once
375
+ expect { subject.volumes }.
376
+ to raise_error(described_class::VolumeManager::ParserError)
377
+ end
378
+
379
+ it 'returns an empty list of volumes when blkid output is empty' do
380
+ platform_class.should_receive(:execute).with(blkid_cmd, {}).and_return('').once
381
+ subject.volumes.should == []
382
+ end
383
+
384
+ it 'can filter results with single condition and single match' do
385
+ expected_volumes = [
386
+ {
387
+ :device => '/dev/xvdh1',
388
+ :sec_type => 'msdos',
389
+ :label => 'METADATA',
390
+ :uuid => '681B-8C5D',
391
+ :type => 'vfat',
392
+ :filesystem => 'vfat'
393
+ }
394
+ ]
395
+ platform_class.should_receive(:execute).with(blkid_cmd, {}).and_return(
396
+ <<EOF
397
+ /dev/xvdh1: SEC_TYPE="msdos" LABEL="METADATA" UUID="681B-8C5D" TYPE="vfat"
398
+ /dev/xvdb1: LABEL="SWAP-xvdb1" UUID="d51fcca0-6b10-4934-a572-f3898dfd8840" TYPE="swap"
399
+ /dev/xvda1: UUID="f4746f9c-0557-4406-9267-5e918e87ca2e" TYPE="ext3"
400
+ /dev/xvda2: UUID="14d88b9e-9fe6-4974-a8d6-180acdae4016" TYPE="ext3"
401
+ EOF
402
+ ).once
403
+ subject.volumes(:uuid => expected_volumes[0][:uuid]).should == expected_volumes
404
+ end
405
+
406
+ it 'can filter results with multiple matches' do
407
+ expected_volumes = [
408
+ {
409
+ :device => '/dev/xvda1',
410
+ :uuid => 'f4746f9c-0557-4406-9267-5e918e87ca2e',
411
+ :type => 'ext3',
412
+ :filesystem => 'ext3'
413
+ },
414
+ {
415
+ :device => '/dev/xvda2',
416
+ :uuid => '14d88b9e-9fe6-4974-a8d6-180acdae4016',
417
+ :type => 'ext3',
418
+ :filesystem => 'ext3'
419
+ }
420
+ ]
421
+ platform_class.should_receive(:execute).with(blkid_cmd, {}).and_return(
422
+ <<EOF
423
+ /dev/xvdh1: SEC_TYPE="msdos" LABEL="METADATA" UUID="681B-8C5D" TYPE="vfat"
424
+ /dev/xvdb1: LABEL="SWAP-xvdb1" UUID="d51fcca0-6b10-4934-a572-f3898dfd8840" TYPE="swap"
425
+ /dev/xvda1: UUID="f4746f9c-0557-4406-9267-5e918e87ca2e" TYPE="ext3"
426
+ /dev/xvda2: UUID="14d88b9e-9fe6-4974-a8d6-180acdae4016" TYPE="ext3"
427
+ EOF
428
+ ).once
429
+ subject.volumes(:filesystem => 'ext3', :type => 'ext3').should == expected_volumes
430
+ end
431
+ end
432
+
433
+ context :mount_volume do
434
+ it 'mounts the specified volume if it is not already mounted' do
435
+ platform_class.
436
+ should_receive(:execute).
437
+ with('mount 2>&1', {}).
438
+ and_return(
439
+ <<EOF
440
+ /dev/xvda2 on / type ext3 (rw,noatime,errors=remount-ro)
441
+ proc on /proc type proc (rw,noexec,nosuid,nodev)
442
+ EOF
443
+ )
444
+ platform_class.
445
+ should_receive(:execute).
446
+ with('mount -t vfat /dev/xvdh1 /var/spool/softlayer 2>&1', {}).
447
+ and_return('')
448
+ subject.mount_volume(
449
+ { :device => '/dev/xvdh1', :filesystem => 'vfat' },
450
+ '/var/spool/softlayer')
451
+ end
452
+
453
+ it 'does not attempt to re-mount the volume' do
454
+ platform_class.
455
+ should_receive(:execute).
456
+ with('mount 2>&1', {}).
457
+ and_return(
458
+ <<EOF
459
+ /dev/xvda2 on / type ext3 (rw,noatime,errors=remount-ro)
460
+ proc on /proc type proc (rw,noexec,nosuid,nodev)
461
+ /dev/xvdh1 on /var/spool/softlayer type vfat (rw) [METADATA]
462
+ EOF
463
+ )
464
+ platform_class.
465
+ should_receive(:execute).
466
+ with('mount -t vfat /dev/xvdh1 /var/spool/softlayer 2>&1', {}).
467
+ and_return('')
468
+ subject.mount_volume(
469
+ {:device => '/dev/xvdh1', :filesystem => 'vfat'},
470
+ '/var/spool/softlayer')
471
+ end
472
+
473
+ it 'raises argument error when the volume parameter is not a hash' do
474
+ expect { subject.mount_volume('', '') }.
475
+ to raise_error(ArgumentError)
476
+ end
477
+
478
+ it 'raises argument error when the volume parameter is a hash but does not contain :device' do
479
+ expect { subject.mount_volume({}, '') }.
480
+ to raise_error(ArgumentError)
481
+ end
482
+
483
+ it 'raises volume error when the device is already mounted to a different mountpoint' do
484
+ platform_class.
485
+ should_receive(:execute).
486
+ with('mount 2>&1', {}).
487
+ and_return(
488
+ <<EOF
489
+ /dev/xvda2 on / type ext3 (rw,noatime,errors=remount-ro)
490
+ proc on /proc type proc (rw,noexec,nosuid,nodev)
491
+ none on /sys type sysfs (rw,noexec,nosuid,nodev)
492
+ none on /sys/kernel/debug type debugfs (rw)
493
+ none on /sys/kernel/security type securityfs (rw)
494
+ none on /dev type devtmpfs (rw,mode=0755)
495
+ none on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
496
+ none on /dev/shm type tmpfs (rw,nosuid,nodev)
497
+ none on /var/run type tmpfs (rw,nosuid,mode=0755)
498
+ none on /var/lock type tmpfs (rw,noexec,nosuid,nodev)
499
+ none on /lib/init/rw type tmpfs (rw,nosuid,mode=0755)
500
+ /dev/xvda1 on /boot type ext3 (rw,noatime)
501
+ /dev/xvdh1 on /mnt type vfat (rw) [METADATA]
502
+ EOF
503
+ )
504
+ expect do
505
+ subject.mount_volume(
506
+ { :device => '/dev/xvdh1' }, '/var/spool/softlayer')
507
+ end.to raise_error(described_class::VolumeManager::VolumeError)
508
+ end
509
+
510
+ it 'raises volume error when a different device is already mounted to the specified mountpoint' do
511
+ platform_class.
512
+ should_receive(:execute).
513
+ with('mount 2>&1', {}).
514
+ and_return(
515
+ <<EOF
516
+ /dev/xvda2 on / type ext3 (rw,noatime,errors=remount-ro)
517
+ proc on /proc type proc (rw,noexec,nosuid,nodev)
518
+ none on /sys type sysfs (rw,noexec,nosuid,nodev)
519
+ none on /sys/kernel/debug type debugfs (rw)
520
+ none on /sys/kernel/security type securityfs (rw)
521
+ none on /dev type devtmpfs (rw,mode=0755)
522
+ none on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
523
+ none on /dev/shm type tmpfs (rw,nosuid,nodev)
524
+ none on /var/run type tmpfs (rw,nosuid,mode=0755)
525
+ none on /var/lock type tmpfs (rw,noexec,nosuid,nodev)
526
+ none on /lib/init/rw type tmpfs (rw,nosuid,mode=0755)
527
+ /dev/xvda1 on /boot type ext3 (rw,noatime)
528
+ /dev/xvdh2 on /var/spool/softlayer type vfat (rw) [METADATA]
529
+ EOF
530
+ )
531
+ expect do
532
+ subject.mount_volume(
533
+ { :device => '/dev/xvdh1' }, '/var/spool/softlayer')
534
+ end.to raise_error(described_class::VolumeManager::VolumeError)
535
+ end
536
+
537
+ end # mount_volume
538
+ end # volume_manager
539
+ end # under unix/linux
540
+ end # RightScale::Platform