vagrant-proxmox 0.0.3 → 0.0.5
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.
- checksums.yaml +4 -4
- data/lib/sanity_checks.rb +1 -1
- data/lib/vagrant-proxmox.rb +1 -2
- data/lib/vagrant-proxmox/action.rb +133 -86
- data/lib/vagrant-proxmox/action/connect_proxmox.rb +10 -10
- data/lib/vagrant-proxmox/action/create_vm.rb +33 -28
- data/lib/vagrant-proxmox/action/destroy_vm.rb +10 -6
- data/lib/vagrant-proxmox/action/get_node_list.rb +8 -6
- data/lib/vagrant-proxmox/action/is_created.rb +1 -0
- data/lib/vagrant-proxmox/action/is_stopped.rb +1 -0
- data/lib/vagrant-proxmox/action/message_file_not_found.rb +21 -0
- data/lib/vagrant-proxmox/action/message_not_running.rb +20 -0
- data/lib/vagrant-proxmox/action/message_upload_server_error.rb +20 -0
- data/lib/vagrant-proxmox/action/proxmox_action.rb +6 -22
- data/lib/vagrant-proxmox/action/read_ssh_info.rb +1 -0
- data/lib/vagrant-proxmox/action/read_state.rb +11 -14
- data/lib/vagrant-proxmox/action/select_node.rb +23 -0
- data/lib/vagrant-proxmox/action/shutdown_vm.rb +8 -8
- data/lib/vagrant-proxmox/action/start_vm.rb +20 -12
- data/lib/vagrant-proxmox/action/stop_vm.rb +9 -8
- data/lib/vagrant-proxmox/action/sync_folders.rb +1 -1
- data/lib/vagrant-proxmox/action/upload_iso_file.rb +38 -0
- data/lib/vagrant-proxmox/action/upload_template_file.rb +38 -0
- data/lib/vagrant-proxmox/config.rb +85 -5
- data/lib/vagrant-proxmox/errors.rb +22 -2
- data/lib/vagrant-proxmox/plugin.rb +0 -14
- data/lib/vagrant-proxmox/proxmox/connection.rb +217 -0
- data/lib/vagrant-proxmox/proxmox/errors.rb +23 -0
- data/lib/vagrant-proxmox/version.rb +1 -1
- data/locales/en.yml +29 -2
- data/spec/actions/cleanup_after_destroy_action_spec.rb +2 -2
- data/spec/actions/connect_proxmox_action_spec.rb +32 -15
- data/spec/actions/create_vm_action_spec.rb +155 -130
- data/spec/actions/destroy_vm_action_spec.rb +50 -23
- data/spec/actions/get_node_list_action_spec.rb +25 -12
- data/spec/actions/is_created_action_spec.rb +8 -8
- data/spec/actions/is_stopped_action_spec.rb +8 -8
- data/spec/actions/message_already_running_action_spec.rb +2 -2
- data/spec/actions/message_already_stopped_action_spec.rb +2 -2
- data/spec/actions/message_file_not_found_spec.rb +23 -0
- data/spec/actions/message_not_created_action_spec.rb +2 -2
- data/spec/actions/message_not_running_action_spec.rb +23 -0
- data/spec/actions/message_upload_server_error_spec.rb +23 -0
- data/spec/actions/proxmox_action_shared.rb +0 -64
- data/spec/actions/proxmox_action_spec.rb +57 -0
- data/spec/actions/read_ssh_info_action_spec.rb +6 -6
- data/spec/actions/read_state_action_spec.rb +36 -34
- data/spec/actions/select_node_spec.rb +33 -0
- data/spec/actions/shutdown_vm_action_spec.rb +50 -22
- data/spec/actions/start_vm_action_spec.rb +87 -33
- data/spec/actions/stop_vm_action_spec.rb +50 -23
- data/spec/actions/sync_folders_action_spec.rb +9 -9
- data/spec/actions/upload_iso_file_action_spec.rb +70 -0
- data/spec/actions/upload_template_file_action_spec.rb +70 -0
- data/spec/commands/destroy_command_spec.rb +25 -25
- data/spec/commands/halt_command_spec.rb +17 -17
- data/spec/commands/provision_command_spec.rb +22 -9
- data/spec/commands/ssh_command_spec.rb +18 -5
- data/spec/commands/ssh_run_command_spec.rb +19 -6
- data/spec/commands/status_command_spec.rb +2 -2
- data/spec/commands/up_command_spec.rb +174 -34
- data/spec/config_spec.rb +325 -46
- data/spec/plugin_spec.rb +1 -8
- data/spec/provider_spec.rb +4 -4
- data/spec/proxmox/connection_spec.rb +662 -0
- data/spec/proxmox/rest_call_shared.rb +41 -0
- data/spec/sanity_checks_spec.rb +1 -1
- data/spec/spec_helper.rb +15 -97
- data/spec/spec_helpers/common_helpers.rb +111 -0
- data/spec/spec_helpers/time_helpers.rb +90 -0
- metadata +161 -45
data/spec/plugin_spec.rb
CHANGED
@@ -2,14 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe 'Vagrant Proxmox plugin' do
|
4
4
|
|
5
|
-
specify { Vagrant.
|
6
|
-
|
7
|
-
describe 'contains a fix for RestClient header unescaping bug' do
|
8
|
-
let(:request) { RestClient::Request.new method: :get, url: 'url', cookies: { key: '+%20%21' }}
|
9
|
-
it 'should not unescape the cookies' do
|
10
|
-
request.make_headers({})['Cookie'].should == 'key=+%20%21'
|
11
|
-
end
|
12
|
-
end
|
5
|
+
specify { expect(Vagrant).to have_plugin 'Proxmox' }
|
13
6
|
|
14
7
|
describe 'when vagrant log level is set in ENV[VAGRANT_LOG]' do
|
15
8
|
before { ENV['VAGRANT_LOG'] = 'DEBUG' }
|
data/spec/provider_spec.rb
CHANGED
@@ -13,10 +13,10 @@ module VagrantPlugins::Proxmox
|
|
13
13
|
|
14
14
|
describe '#ssh_info', :need_box do
|
15
15
|
it 'should call the appropriate actions and return the ssh info' do
|
16
|
-
Action::ConfigValidate.
|
17
|
-
Action::ConnectProxmox.
|
18
|
-
Action::ReadSSHInfo.
|
19
|
-
subject.ssh_info.
|
16
|
+
expect(Action::ConfigValidate).to be_called
|
17
|
+
expect(Action::ConnectProxmox).to be_called
|
18
|
+
expect(Action::ReadSSHInfo).to be_called { |env| env[:machine_ssh_info] = 'ssh_info'}
|
19
|
+
expect(subject.ssh_info).to eq('ssh_info')
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -0,0 +1,662 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'proxmox/rest_call_shared'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module VagrantPlugins::Proxmox
|
6
|
+
|
7
|
+
describe Connection do
|
8
|
+
|
9
|
+
let(:api_url) { 'https://proxmox.example.com/api' }
|
10
|
+
let(:username) { 'user' }
|
11
|
+
let(:password) { 'password' }
|
12
|
+
let(:ticket) { 'valid ticket' }
|
13
|
+
let(:csrf_token) { 'csrf prevention token' }
|
14
|
+
let(:task_upid) { 'UPID:localhost:0000F6ED:00F8E25F:5268CD3B:task_type:100:vagrant@pve:' }
|
15
|
+
let(:task_response) { {data: task_upid} }
|
16
|
+
let(:params) { {} }
|
17
|
+
let(:connection_opts) { {} }
|
18
|
+
let(:task_timeout) { 40 }
|
19
|
+
let(:task_status_check_interval) { 5 }
|
20
|
+
let(:imgcopy_timeout) { 85 }
|
21
|
+
let(:vm_type) { '' }
|
22
|
+
let(:node) { 'localhost' }
|
23
|
+
let(:machine_id) { '100' }
|
24
|
+
let(:vm_info) { {type: vm_type, node: node, id: machine_id} }
|
25
|
+
|
26
|
+
subject(:connection) { Connection.new api_url, connection_opts }
|
27
|
+
|
28
|
+
describe 'contains a fix for RestClient header unescaping bug' do
|
29
|
+
let(:request) { RestClient::Request.new method: :get, url: 'url', cookies: {key: '+%20%21'} }
|
30
|
+
it 'should not unescape the cookies' do
|
31
|
+
expect(request.make_headers({})['Cookie']).to eq('key=+%20%21')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#initialize' do
|
36
|
+
|
37
|
+
describe '#api_url' do
|
38
|
+
subject { super().api_url }
|
39
|
+
it { is_expected.to eq(api_url) }
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with default values' do
|
43
|
+
describe '#vm_id_range' do
|
44
|
+
subject { super().vm_id_range }
|
45
|
+
it { is_expected.to eq(900..999) }
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#task_timeout' do
|
49
|
+
subject { super().task_timeout }
|
50
|
+
it { is_expected.to eq(60) }
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#task_status_check_interval' do
|
54
|
+
subject { super().task_status_check_interval }
|
55
|
+
it { is_expected.to eq(2) }
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#imgcopy_timeout' do
|
59
|
+
subject { super().imgcopy_timeout }
|
60
|
+
it { is_expected.to eq(120) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'with custom values' do
|
65
|
+
let(:connection_opts) { {vm_id_range: (500..599), task_timeout: 90, task_status_check_interval: 3, imgcopy_timeout: 83} }
|
66
|
+
|
67
|
+
describe '#vm_id_range' do
|
68
|
+
subject { super().vm_id_range }
|
69
|
+
it { is_expected.to eq(500..599) }
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#task_timeout' do
|
73
|
+
subject { super().task_timeout }
|
74
|
+
it { is_expected.to eq(90) }
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#task_status_check_interval' do
|
78
|
+
subject { super().task_status_check_interval }
|
79
|
+
it { is_expected.to eq(3) }
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#imgcopy_timeout' do
|
83
|
+
subject { super().imgcopy_timeout }
|
84
|
+
it { is_expected.to eq(83) }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#login' do
|
90
|
+
|
91
|
+
before do
|
92
|
+
allow(RestClient).to receive_messages :post => {data: {ticket: ticket, CSRFPreventionToken: csrf_token}}.to_json
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should call the REST API access/ticket' do
|
96
|
+
expect(RestClient).to receive(:post).with("#{api_url}/access/ticket", {username: username, password: password}, anything)
|
97
|
+
connection.login username: username, password: password
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'with valid credentials' do
|
101
|
+
it 'should store the access ticket token' do
|
102
|
+
connection.login username: username, password: password
|
103
|
+
expect(connection.ticket).to eq(ticket)
|
104
|
+
end
|
105
|
+
it 'should store the CSRF prevention token' do
|
106
|
+
connection.login username: username, password: password
|
107
|
+
expect(connection.csrf_token).to eq(csrf_token)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'with invalid credentials' do
|
112
|
+
it 'should raise an invalid credentials error' do
|
113
|
+
allow(RestClient).to receive(:post).and_raise RestClient::InternalServerError
|
114
|
+
expect do
|
115
|
+
connection.login username: username, password: password
|
116
|
+
end.to raise_error ApiError::InvalidCredentials
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'when a network error occurs' do
|
121
|
+
it 'should raise a connection error' do
|
122
|
+
allow(RestClient).to receive(:post).and_raise SocketError
|
123
|
+
expect do
|
124
|
+
connection.login username: username, password: password
|
125
|
+
end.to raise_error ApiError::ConnectionError
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#get_node_list' do
|
131
|
+
|
132
|
+
before { allow(RestClient).to receive_messages :get => {data: [{node: 'node1'}, {node: 'node2'}]}.to_json }
|
133
|
+
|
134
|
+
it 'should request the node list' do
|
135
|
+
expect(RestClient).to receive(:get).with("#{api_url}/nodes", anything)
|
136
|
+
connection.get_node_list
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should return an array of nodes' do
|
140
|
+
expect(connection.get_node_list).to eq(['node1', 'node2'])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#get_vm_state' do
|
145
|
+
|
146
|
+
let(:status) { '' }
|
147
|
+
|
148
|
+
before do
|
149
|
+
allow(connection).to receive_messages :get_vm_info => vm_info
|
150
|
+
allow(RestClient).to receive(:get).with("#{api_url}/nodes/localhost/#{vm_type}/#{machine_id}/status/current", anything).
|
151
|
+
and_return({data: {status: status}}.to_json)
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'when the machine is an openvz container' do
|
155
|
+
|
156
|
+
let(:vm_type) { 'openvz' }
|
157
|
+
|
158
|
+
it 'should request a machine state' do
|
159
|
+
expect(RestClient).to receive(:get).with("#{api_url}/nodes/localhost/openvz/100/status/current", anything)
|
160
|
+
connection.get_vm_state(100)
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'when the machine is stopped' do
|
164
|
+
let(:status) { :stopped }
|
165
|
+
it 'should return :stopped' do
|
166
|
+
expect(connection.get_vm_state(100)).to eq(:stopped)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'when the machine is running' do
|
171
|
+
let(:status) { :running }
|
172
|
+
it 'should return :running' do
|
173
|
+
expect(connection.get_vm_state(100)).to eq(:running)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'when the machine is a qemu emulation' do
|
179
|
+
|
180
|
+
let(:vm_type) { 'qemu' }
|
181
|
+
|
182
|
+
it 'should request a machine state' do
|
183
|
+
expect(RestClient).to receive(:get).with("#{api_url}/nodes/localhost/qemu/100/status/current", anything)
|
184
|
+
connection.get_vm_state(100)
|
185
|
+
end
|
186
|
+
|
187
|
+
context 'when the machine is stopped' do
|
188
|
+
let(:status) { :stopped }
|
189
|
+
it 'should return :stopped' do
|
190
|
+
expect(connection.get_vm_state(100)).to eq(:stopped)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context 'when the machine is running' do
|
195
|
+
let(:status) { :running }
|
196
|
+
it 'should return :running' do
|
197
|
+
expect(connection.get_vm_state(100)).to eq(:running)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context 'when the machine is not created' do
|
203
|
+
|
204
|
+
before { allow(RestClient).to receive(:get).and_raise RestClient::InternalServerError }
|
205
|
+
|
206
|
+
it 'should return :not_created' do
|
207
|
+
expect(connection.get_vm_state(100)).to eq(:not_created)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe '#get' do
|
213
|
+
|
214
|
+
it_should_behave_like 'a rest api call', :get
|
215
|
+
|
216
|
+
before { allow(RestClient).to receive_messages get: {data: {}}.to_json }
|
217
|
+
|
218
|
+
context 'with valid parameters' do
|
219
|
+
it 'should call the REST API with the correct parameters' do
|
220
|
+
expect(RestClient).to receive(:get).with("#{api_url}/resource", anything)
|
221
|
+
connection.send :get, '/resource'
|
222
|
+
end
|
223
|
+
it 'should send the authentication authorization ticket as a cookie' do
|
224
|
+
allow_any_instance_of(Connection).to receive_messages ticket: ticket
|
225
|
+
expect(RestClient).to receive(:get).with(anything, hash_including(cookies: {PVEAuthCookie: ticket}))
|
226
|
+
connection.send :get, '/resource'
|
227
|
+
end
|
228
|
+
it 'should return the JSON parsed response data' do
|
229
|
+
expect(RestClient).to receive_messages :get => ({data: 'some_response'}.to_json)
|
230
|
+
response = connection.send :get, '/resource'
|
231
|
+
expect(response).to eq({data: 'some_response'})
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
describe '#wait_for_completion' do
|
238
|
+
|
239
|
+
it 'should get the task exit status' do
|
240
|
+
expect(connection).to receive(:get_task_exitstatus).with(task_upid).and_return('OK')
|
241
|
+
connection.send(:wait_for_completion, task_response: task_response, timeout_message: '')
|
242
|
+
end
|
243
|
+
|
244
|
+
context 'when the task is completed' do
|
245
|
+
before { allow(connection).to receive_messages get_task_exitstatus: 'OK' }
|
246
|
+
it 'should return the task exit status' do
|
247
|
+
expect(connection.send(:wait_for_completion, task_response: task_response, timeout_message: '')).to eq('OK')
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'when the task times out' do
|
252
|
+
|
253
|
+
before do
|
254
|
+
Timecop.freeze
|
255
|
+
allow(connection).to receive_messages :get_task_exitstatus => nil
|
256
|
+
allow(connection).to receive_messages :task_timeout => task_timeout
|
257
|
+
allow(connection).to receive_messages :imgcopy_timeout => imgcopy_timeout
|
258
|
+
allow(connection).to receive_messages :task_status_check_interval => task_status_check_interval
|
259
|
+
allow(connection).to receive(:sleep) { |duration| Timecop.travel(Time.now + duration) }
|
260
|
+
end
|
261
|
+
|
262
|
+
after do
|
263
|
+
Timecop.return
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'should raise an timeout error' do
|
267
|
+
expect { connection.send(:wait_for_completion, task_response: task_response, timeout_message: '') }.to raise_error VagrantPlugins::Proxmox::Errors::Timeout
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'should check the task status a given number of times' do
|
271
|
+
task_iterations = task_timeout / task_status_check_interval + 1
|
272
|
+
expect(connection).to receive(:get_task_exitstatus).exactly(task_iterations).times
|
273
|
+
connection.send(:wait_for_completion, task_response: task_response, timeout_message: '') rescue nil
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'should check the task status a given number of times' do
|
277
|
+
task_iterations = task_timeout / task_status_check_interval + 1
|
278
|
+
expect(connection).to receive(:get_task_exitstatus).exactly(task_iterations).times
|
279
|
+
connection.send(:wait_for_completion, task_response: task_response, timeout_message: '') rescue nil
|
280
|
+
end
|
281
|
+
|
282
|
+
context 'when it is a regular task' do
|
283
|
+
|
284
|
+
it 'should wait out the task_timeout' do
|
285
|
+
connection.send(:wait_for_completion, task_response: task_response, timeout_message: '') rescue nil
|
286
|
+
expect(Time).to have_elapsed task_timeout.seconds
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
context 'when it is an upload task' do
|
291
|
+
|
292
|
+
let(:task_upid) { 'UPID:localhost:0000F6EF:00F8E35F:E268CD3B:imgcopy:100:vagrant@pve:' }
|
293
|
+
|
294
|
+
it 'should wait out the imgcopy_timeout' do
|
295
|
+
connection.send(:wait_for_completion, task_response: task_response, timeout_message: '') rescue nil
|
296
|
+
expect(Time).to have_elapsed imgcopy_timeout.seconds
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe '#get_task_exitstatus' do
|
303
|
+
|
304
|
+
it 'should request the task state from the proxmox server given in the task UPID' do
|
305
|
+
expect(RestClient).to receive(:get).with("#{api_url}/nodes/localhost/tasks/#{task_upid}/status", anything).
|
306
|
+
and_return({data: {}}.to_json)
|
307
|
+
connection.send(:get_task_exitstatus, task_upid)
|
308
|
+
end
|
309
|
+
|
310
|
+
context 'the task has exited' do
|
311
|
+
it 'should return the exit status' do
|
312
|
+
allow(RestClient).to receive_messages get: {data: {upid: task_response, status: 'stopped', exitstatus: 'OK'}}.to_json
|
313
|
+
expect(connection.send(:get_task_exitstatus, task_upid)).to eq('OK')
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
context 'the task is still running' do
|
318
|
+
it 'should return nil' do
|
319
|
+
allow(RestClient).to receive_messages get: {data: {upid: task_response, status: 'running'}}.to_json
|
320
|
+
expect(connection.send(:get_task_exitstatus, task_upid)).to eq(nil)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
describe '#delete_vm' do
|
326
|
+
|
327
|
+
before do
|
328
|
+
allow(connection).to receive_messages :delete => {data: 'task_id'}.to_json
|
329
|
+
allow(connection).to receive_messages :wait_for_completion => 'OK'
|
330
|
+
allow(connection).to receive_messages :get_vm_info => vm_info
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'when the machine is an openvz container' do
|
334
|
+
|
335
|
+
let(:vm_type) { 'openvz' }
|
336
|
+
|
337
|
+
it 'should call delete with the node and vm as parameter' do
|
338
|
+
expect(connection).to receive(:delete).with("/nodes/localhost/openvz/100")
|
339
|
+
connection.delete_vm 100
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'waits for completion of the server task' do
|
343
|
+
expect(connection).to receive(:wait_for_completion)
|
344
|
+
connection.delete_vm 100
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'should return the task exit status' do
|
348
|
+
expect(connection.delete_vm(100)).to eq('OK')
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
context 'when the machine is a qemu emulation' do
|
353
|
+
|
354
|
+
let(:vm_type) { 'qemu' }
|
355
|
+
|
356
|
+
it 'should call delete with the node and vm as parameter' do
|
357
|
+
expect(connection).to receive(:delete).with("/nodes/localhost/qemu/100")
|
358
|
+
connection.delete_vm 100
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'waits for completion of the server task' do
|
362
|
+
expect(connection).to receive(:wait_for_completion)
|
363
|
+
connection.delete_vm 100
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'should return the task exit status' do
|
367
|
+
expect(connection.delete_vm(100)).to eq('OK')
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
describe '#get_vm_type' do
|
373
|
+
|
374
|
+
before do
|
375
|
+
allow(RestClient).to receive(:get).with("#{api_url}/cluster/resources?type=vm", anything).
|
376
|
+
and_return({data: vm_list}.to_json)
|
377
|
+
end
|
378
|
+
let(:vm_list) { [{node: 'node', id: 'openvz/100'}, {node: 'anothernode', id: 'qemu/101'}] }
|
379
|
+
|
380
|
+
it 'should return the correct vm_information' do
|
381
|
+
expect(connection.send(:get_vm_info, 101)).to eq({id: 101, node: 'anothernode', type: 'qemu'})
|
382
|
+
end
|
383
|
+
|
384
|
+
context 'when the vm is not found' do
|
385
|
+
it 'should return nil' do
|
386
|
+
expect(connection.send(:get_vm_info, 105)).to be_nil
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
describe '#delete' do
|
392
|
+
|
393
|
+
it_should_behave_like 'a rest api call', :delete
|
394
|
+
|
395
|
+
before { allow(RestClient).to receive_messages delete: {data: {}}.to_json }
|
396
|
+
|
397
|
+
it 'should send a post request that deletes the openvz container' do
|
398
|
+
expect(RestClient).to receive(:delete).with("#{api_url}/nodes/localhost/openvz/100", anything)
|
399
|
+
connection.send :delete, "/nodes/localhost/openvz/100"
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
describe '#get_free_vm_id' do
|
404
|
+
|
405
|
+
it 'should query the proxmox server for all qemu and openvz machines' do
|
406
|
+
expect(RestClient).to receive(:get).with("#{api_url}/cluster/resources?type=vm", anything).and_return({data: []}.to_json)
|
407
|
+
connection.get_free_vm_id
|
408
|
+
end
|
409
|
+
|
410
|
+
describe 'find the smallest unused vm_id in the configured range' do
|
411
|
+
|
412
|
+
before { connection.vm_id_range = 3..4 }
|
413
|
+
|
414
|
+
context 'when free vm_ids are available' do
|
415
|
+
[
|
416
|
+
{used_vm_ids: {data: []}, smallest_free_in_range: 3},
|
417
|
+
{used_vm_ids: {data: [{vmid: 3}]}, smallest_free_in_range: 4},
|
418
|
+
{used_vm_ids: {data: [{vmid: 1}]}, smallest_free_in_range: 3},
|
419
|
+
{used_vm_ids: {data: [{vmid: 1}, {vmid: 3}]}, smallest_free_in_range: 4},
|
420
|
+
].each do |example|
|
421
|
+
it 'should return the smallest unused vm_id in the configured vm_id_range' do
|
422
|
+
allow(RestClient).to receive_messages :get => example[:used_vm_ids].to_json
|
423
|
+
expect(subject.send(:get_free_vm_id)).to eq(example[:smallest_free_in_range])
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
context 'when no vm_ids are available' do
|
429
|
+
it 'should throw a no_vm_id_available error' do
|
430
|
+
allow(RestClient).to receive_messages :get => {data: [{vmid: 1}, {vmid: 2}, {vmid: 3}, {vmid: 4}]}.to_json
|
431
|
+
expect { subject.send :get_free_vm_id }.to raise_error Errors::NoVmIdAvailable
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
describe '#create_vm' do
|
438
|
+
|
439
|
+
before do
|
440
|
+
allow(connection).to receive_messages :post => {data: 'task_id'}.to_json
|
441
|
+
allow(connection).to receive_messages :wait_for_completion => 'OK'
|
442
|
+
end
|
443
|
+
|
444
|
+
it 'should call post with the correct parameters' do
|
445
|
+
expect(connection).to receive(:post).with('/nodes/localhost/openvz', 'params')
|
446
|
+
connection.create_vm node: 'localhost', vm_type: 'openvz', params: 'params'
|
447
|
+
end
|
448
|
+
|
449
|
+
it 'waits for completion of the server task' do
|
450
|
+
expect(connection).to receive(:wait_for_completion)
|
451
|
+
connection.create_vm node: 'localhost', vm_type: 'openvz', params: params
|
452
|
+
end
|
453
|
+
|
454
|
+
it 'should return the task exit status' do
|
455
|
+
expect(connection.create_vm(node: 'localhost', vm_type: 'openvz', params: 'params')).to eq('OK')
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
describe '#post' do
|
460
|
+
|
461
|
+
it_should_behave_like 'a rest api call', :post
|
462
|
+
|
463
|
+
before { allow(RestClient).to receive_messages post: {data: {}}.to_json }
|
464
|
+
|
465
|
+
it 'should call the REST API with the correct parameters' do
|
466
|
+
expect(RestClient).to receive(:post).with("#{api_url}/resource", params, anything)
|
467
|
+
connection.send :post, '/resource', params
|
468
|
+
end
|
469
|
+
|
470
|
+
it 'should return the JSON parsed response data' do
|
471
|
+
expect(RestClient).to receive(:post).and_return({data: 'some_response'}.to_json)
|
472
|
+
response = connection.send :post, '/resource', params
|
473
|
+
expect(response).to eq({data: 'some_response'})
|
474
|
+
end
|
475
|
+
|
476
|
+
describe 'sending ticket and token as part of the post request' do
|
477
|
+
|
478
|
+
context 'when authorized' do
|
479
|
+
it 'should send the authentication authorization ticket as a cookie and the csrf token' do
|
480
|
+
allow_any_instance_of(Connection).to receive_messages ticket: ticket
|
481
|
+
allow_any_instance_of(Connection).to receive_messages csrf_token: csrf_token
|
482
|
+
expect(RestClient).to receive(:post).with(anything, anything, hash_including({CSRFPreventionToken: csrf_token, cookies: {PVEAuthCookie: ticket}}))
|
483
|
+
connection.send :post, '/resource', params
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
context 'when not authorized' do
|
488
|
+
it 'it should send the request without ticket and token' do
|
489
|
+
allow_any_instance_of(Connection).to receive_messages ticket: nil
|
490
|
+
allow_any_instance_of(Connection).to receive_messages csrf_token: nil
|
491
|
+
expect(RestClient).to receive(:post).with anything, anything, hash_not_including(:CSRFPreventionToken, :cookies)
|
492
|
+
connection.send :post, '/resource', params
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
describe '#start_vm' do
|
499
|
+
|
500
|
+
before do
|
501
|
+
allow(connection).to receive_messages :post => {data: 'task_id'}.to_json
|
502
|
+
allow(connection).to receive_messages :wait_for_completion => 'OK'
|
503
|
+
allow(connection).to receive_messages :get_vm_info => vm_info
|
504
|
+
end
|
505
|
+
|
506
|
+
it 'waits for completion of the server task' do
|
507
|
+
expect(connection).to receive(:wait_for_completion)
|
508
|
+
connection.start_vm '100'
|
509
|
+
end
|
510
|
+
|
511
|
+
it 'should return the task exit status' do
|
512
|
+
expect(connection.start_vm('100')).to eq('OK')
|
513
|
+
end
|
514
|
+
|
515
|
+
context 'when the machine is an openvz container' do
|
516
|
+
|
517
|
+
let(:vm_type) { 'openvz' }
|
518
|
+
|
519
|
+
it 'should call post with the correct parameters' do
|
520
|
+
expect(connection).to receive(:post).with("/nodes/localhost/openvz/100/status/start", nil)
|
521
|
+
connection.start_vm '100'
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
context 'when the machine is a qemu emulation' do
|
526
|
+
|
527
|
+
let(:vm_type) { 'qemu' }
|
528
|
+
|
529
|
+
it 'should call post with the correct parameters' do
|
530
|
+
expect(connection).to receive(:post).with("/nodes/localhost/qemu/100/status/start", nil)
|
531
|
+
connection.start_vm '100'
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
describe '#stop_vm' do
|
537
|
+
|
538
|
+
before do
|
539
|
+
allow(connection).to receive_messages :post => {data: 'task_id'}.to_json
|
540
|
+
allow(connection).to receive_messages :wait_for_completion => 'OK'
|
541
|
+
allow(connection).to receive_messages :get_vm_info => vm_info
|
542
|
+
end
|
543
|
+
|
544
|
+
it 'waits for completion of the server task' do
|
545
|
+
expect(connection).to receive(:wait_for_completion)
|
546
|
+
connection.stop_vm '100'
|
547
|
+
end
|
548
|
+
|
549
|
+
it 'should return the task exit status' do
|
550
|
+
expect(connection.stop_vm('100')).to eq('OK')
|
551
|
+
end
|
552
|
+
|
553
|
+
context 'when the machine is an openvz container' do
|
554
|
+
|
555
|
+
let(:vm_type) { 'openvz' }
|
556
|
+
|
557
|
+
it 'should call post with the correct parameters' do
|
558
|
+
expect(connection).to receive(:post).with("/nodes/localhost/openvz/100/status/stop", nil)
|
559
|
+
connection.stop_vm '100'
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
context 'when the machine is a qemu emulation' do
|
564
|
+
|
565
|
+
let(:vm_type) { 'qemu' }
|
566
|
+
|
567
|
+
it 'should call post with the correct parameters' do
|
568
|
+
expect(connection).to receive(:post).with("/nodes/localhost/qemu/100/status/stop", nil)
|
569
|
+
connection.stop_vm '100'
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
describe '#shutdown_vm' do
|
575
|
+
|
576
|
+
before do
|
577
|
+
allow(connection).to receive_messages :post => {data: 'task_id'}.to_json
|
578
|
+
allow(connection).to receive_messages :wait_for_completion => 'OK'
|
579
|
+
allow(connection).to receive_messages :get_vm_info => vm_info
|
580
|
+
end
|
581
|
+
|
582
|
+
it 'waits for completion of the server task' do
|
583
|
+
expect(connection).to receive(:wait_for_completion)
|
584
|
+
connection.shutdown_vm '100'
|
585
|
+
end
|
586
|
+
|
587
|
+
it 'should return the task exit status' do
|
588
|
+
expect(connection.shutdown_vm('100')).to eq('OK')
|
589
|
+
end
|
590
|
+
|
591
|
+
context 'when the machine is an openvz container' do
|
592
|
+
|
593
|
+
let(:vm_type) { 'openvz' }
|
594
|
+
|
595
|
+
it 'should call post with the correct parameters' do
|
596
|
+
expect(connection).to receive(:post).with("/nodes/localhost/openvz/100/status/shutdown", nil)
|
597
|
+
connection.shutdown_vm '100'
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
context 'when the machine is a qemu emulation' do
|
602
|
+
|
603
|
+
let(:vm_type) { 'qemu' }
|
604
|
+
it 'should call post with the correct parameters' do
|
605
|
+
expect(connection).to receive(:post).with("/nodes/localhost/qemu/100/status/shutdown", nil)
|
606
|
+
connection.shutdown_vm '100'
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
describe '#upload_file' do
|
612
|
+
|
613
|
+
let (:file) { '/my/dir/template.tar.gz' }
|
614
|
+
let (:storage_file_list) { [] }
|
615
|
+
|
616
|
+
before do
|
617
|
+
allow(connection).to receive(:post)
|
618
|
+
allow(File).to receive(:new).with(file, anything).and_return file
|
619
|
+
allow(RestClient).to receive(:get).with("#{api_url}/nodes/localhost/storage/local/content", anything()).
|
620
|
+
and_return(({data: storage_file_list}).to_json)
|
621
|
+
allow(connection).to receive_messages :wait_for_completion => 'OK'
|
622
|
+
end
|
623
|
+
|
624
|
+
it 'should call post with the correct parameters' do
|
625
|
+
expect(connection).to receive(:post).with('/nodes/localhost/storage/local/upload',
|
626
|
+
{:content => 'vztmpl', :filename => file, :node => 'localhost', :storage => 'local'})
|
627
|
+
connection.upload_file file, content_type: 'vztmpl', node: 'localhost', storage: 'local'
|
628
|
+
end
|
629
|
+
|
630
|
+
it 'waits for completion of the server task' do
|
631
|
+
expect(connection).to receive(:wait_for_completion)
|
632
|
+
connection.upload_file file, content_type: 'vztmpl', node: 'localhost', storage: 'local'
|
633
|
+
end
|
634
|
+
|
635
|
+
it 'should return the task exit status' do
|
636
|
+
expect(connection.upload_file file, content_type: 'vztmpl', node: 'localhost', storage: 'local').to eq('OK')
|
637
|
+
end
|
638
|
+
|
639
|
+
context 'when the file already exists in storage of the proxmox node' do
|
640
|
+
|
641
|
+
let (:storage_file_list) { [{volid: 'local:vztmpl/template.tar.gz'}] }
|
642
|
+
|
643
|
+
it 'should not upload the file' do
|
644
|
+
expect(connection).not_to receive(:post).with('/nodes/localhost/storage/local/upload', anything())
|
645
|
+
connection.upload_file file, content_type: 'vztmpl', node: 'localhost', storage: 'local'
|
646
|
+
end
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
describe '#list_storage_files' do
|
651
|
+
before do
|
652
|
+
expect(RestClient).to receive(:get).with("#{api_url}/nodes/node1/storage/local/content", anything()).
|
653
|
+
and_return(({data: [{volid: 'local:vztmpl/mytemplate.tar.gz'}]}).to_json)
|
654
|
+
end
|
655
|
+
|
656
|
+
it 'should return a list of the content of a storage' do
|
657
|
+
res = connection.list_storage_files node: 'node1', storage: 'local'
|
658
|
+
expect(res).to eq(['local:vztmpl/mytemplate.tar.gz'])
|
659
|
+
end
|
660
|
+
end
|
661
|
+
end
|
662
|
+
end
|