vagrant-proxmox 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sanity_checks.rb +1 -1
  3. data/lib/vagrant-proxmox.rb +1 -2
  4. data/lib/vagrant-proxmox/action.rb +133 -86
  5. data/lib/vagrant-proxmox/action/connect_proxmox.rb +10 -10
  6. data/lib/vagrant-proxmox/action/create_vm.rb +33 -28
  7. data/lib/vagrant-proxmox/action/destroy_vm.rb +10 -6
  8. data/lib/vagrant-proxmox/action/get_node_list.rb +8 -6
  9. data/lib/vagrant-proxmox/action/is_created.rb +1 -0
  10. data/lib/vagrant-proxmox/action/is_stopped.rb +1 -0
  11. data/lib/vagrant-proxmox/action/message_file_not_found.rb +21 -0
  12. data/lib/vagrant-proxmox/action/message_not_running.rb +20 -0
  13. data/lib/vagrant-proxmox/action/message_upload_server_error.rb +20 -0
  14. data/lib/vagrant-proxmox/action/proxmox_action.rb +6 -22
  15. data/lib/vagrant-proxmox/action/read_ssh_info.rb +1 -0
  16. data/lib/vagrant-proxmox/action/read_state.rb +11 -14
  17. data/lib/vagrant-proxmox/action/select_node.rb +23 -0
  18. data/lib/vagrant-proxmox/action/shutdown_vm.rb +8 -8
  19. data/lib/vagrant-proxmox/action/start_vm.rb +20 -12
  20. data/lib/vagrant-proxmox/action/stop_vm.rb +9 -8
  21. data/lib/vagrant-proxmox/action/sync_folders.rb +1 -1
  22. data/lib/vagrant-proxmox/action/upload_iso_file.rb +38 -0
  23. data/lib/vagrant-proxmox/action/upload_template_file.rb +38 -0
  24. data/lib/vagrant-proxmox/config.rb +85 -5
  25. data/lib/vagrant-proxmox/errors.rb +22 -2
  26. data/lib/vagrant-proxmox/plugin.rb +0 -14
  27. data/lib/vagrant-proxmox/proxmox/connection.rb +217 -0
  28. data/lib/vagrant-proxmox/proxmox/errors.rb +23 -0
  29. data/lib/vagrant-proxmox/version.rb +1 -1
  30. data/locales/en.yml +29 -2
  31. data/spec/actions/cleanup_after_destroy_action_spec.rb +2 -2
  32. data/spec/actions/connect_proxmox_action_spec.rb +32 -15
  33. data/spec/actions/create_vm_action_spec.rb +155 -130
  34. data/spec/actions/destroy_vm_action_spec.rb +50 -23
  35. data/spec/actions/get_node_list_action_spec.rb +25 -12
  36. data/spec/actions/is_created_action_spec.rb +8 -8
  37. data/spec/actions/is_stopped_action_spec.rb +8 -8
  38. data/spec/actions/message_already_running_action_spec.rb +2 -2
  39. data/spec/actions/message_already_stopped_action_spec.rb +2 -2
  40. data/spec/actions/message_file_not_found_spec.rb +23 -0
  41. data/spec/actions/message_not_created_action_spec.rb +2 -2
  42. data/spec/actions/message_not_running_action_spec.rb +23 -0
  43. data/spec/actions/message_upload_server_error_spec.rb +23 -0
  44. data/spec/actions/proxmox_action_shared.rb +0 -64
  45. data/spec/actions/proxmox_action_spec.rb +57 -0
  46. data/spec/actions/read_ssh_info_action_spec.rb +6 -6
  47. data/spec/actions/read_state_action_spec.rb +36 -34
  48. data/spec/actions/select_node_spec.rb +33 -0
  49. data/spec/actions/shutdown_vm_action_spec.rb +50 -22
  50. data/spec/actions/start_vm_action_spec.rb +87 -33
  51. data/spec/actions/stop_vm_action_spec.rb +50 -23
  52. data/spec/actions/sync_folders_action_spec.rb +9 -9
  53. data/spec/actions/upload_iso_file_action_spec.rb +70 -0
  54. data/spec/actions/upload_template_file_action_spec.rb +70 -0
  55. data/spec/commands/destroy_command_spec.rb +25 -25
  56. data/spec/commands/halt_command_spec.rb +17 -17
  57. data/spec/commands/provision_command_spec.rb +22 -9
  58. data/spec/commands/ssh_command_spec.rb +18 -5
  59. data/spec/commands/ssh_run_command_spec.rb +19 -6
  60. data/spec/commands/status_command_spec.rb +2 -2
  61. data/spec/commands/up_command_spec.rb +174 -34
  62. data/spec/config_spec.rb +325 -46
  63. data/spec/plugin_spec.rb +1 -8
  64. data/spec/provider_spec.rb +4 -4
  65. data/spec/proxmox/connection_spec.rb +662 -0
  66. data/spec/proxmox/rest_call_shared.rb +41 -0
  67. data/spec/sanity_checks_spec.rb +1 -1
  68. data/spec/spec_helper.rb +15 -97
  69. data/spec/spec_helpers/common_helpers.rb +111 -0
  70. data/spec/spec_helpers/time_helpers.rb +90 -0
  71. metadata +161 -45
@@ -26,14 +26,34 @@ module VagrantPlugins
26
26
  error_key :no_vm_id_available
27
27
  end
28
28
 
29
- class VMCreationError < VagrantProxmoxError
30
- error_key :vm_creation_error
29
+ class VMCreateError < VagrantProxmoxError
30
+ error_key :vm_create_error
31
+ end
32
+
33
+ class VMDestroyError < VagrantProxmoxError
34
+ error_key :vm_destroy_error
35
+ end
36
+
37
+ class VMStartError < VagrantProxmoxError
38
+ error_key :vm_start_error
39
+ end
40
+
41
+ class VMStopError < VagrantProxmoxError
42
+ error_key :vm_stop_error
43
+ end
44
+
45
+ class VMShutdownError < VagrantProxmoxError
46
+ error_key :vm_shutdown_error
31
47
  end
32
48
 
33
49
  class RsyncError < VagrantProxmoxError
34
50
  error_key :rsync_error
35
51
  end
36
52
 
53
+ class SSHError < VagrantProxmoxError
54
+ error_key :ssh_error
55
+ end
56
+
37
57
  end
38
58
  end
39
59
  end
@@ -1,17 +1,3 @@
1
- # Fix wrong header unescaping in RestClient library.
2
- module RestClient
3
- class Request
4
- def make_headers user_headers
5
- unless @cookies.empty?
6
- user_headers[:cookie] = @cookies.map { |(key, val)| "#{key.to_s}=#{val}" }.sort.join('; ')
7
- end
8
- headers = stringify_headers(default_headers).merge(stringify_headers(user_headers))
9
- headers.merge!(@payload.headers) if @payload
10
- headers
11
- end
12
- end
13
- end
14
-
15
1
  module VagrantPlugins
16
2
  module Proxmox
17
3
  class Plugin < Vagrant.plugin ('2')
@@ -0,0 +1,217 @@
1
+ require 'vagrant-proxmox/proxmox/errors'
2
+ require 'rest-client'
3
+ require 'retryable'
4
+
5
+ # Fix wrong header unescaping in RestClient library.
6
+ module RestClient
7
+ class Request
8
+ def make_headers user_headers
9
+ unless @cookies.empty?
10
+ user_headers[:cookie] = @cookies.map { |(key, val)| "#{key.to_s}=#{val}" }.sort.join('; ')
11
+ end
12
+ headers = stringify_headers(default_headers).merge(stringify_headers(user_headers))
13
+ headers.merge!(@payload.headers) if @payload
14
+ headers
15
+ end
16
+ end
17
+ end
18
+
19
+ module VagrantPlugins
20
+ module Proxmox
21
+ class Connection
22
+
23
+ attr_reader :api_url
24
+ attr_reader :ticket
25
+ attr_reader :csrf_token
26
+ attr_accessor :vm_id_range
27
+ attr_accessor :task_timeout
28
+ attr_accessor :task_status_check_interval
29
+ attr_accessor :imgcopy_timeout
30
+
31
+ def initialize api_url, opts = {}
32
+ @api_url = api_url
33
+ @vm_id_range = opts[:vm_id_range] || (900..999)
34
+ @task_timeout = opts[:task_timeout] || 60
35
+ @task_status_check_interval = opts[:task_status_check_interval] || 2
36
+ @imgcopy_timeout = opts[:imgcopy_timeout] || 120
37
+ end
38
+
39
+ def login(username:, password:)
40
+ begin
41
+ response = post "/access/ticket", username: username, password: password
42
+ @ticket = response[:data][:ticket]
43
+ @csrf_token = response[:data][:CSRFPreventionToken]
44
+ rescue ApiError::ServerError
45
+ raise ApiError::InvalidCredentials
46
+ rescue => x
47
+ raise ApiError::ConnectionError, x.message
48
+ end
49
+ end
50
+
51
+ def get_node_list
52
+ nodelist = get '/nodes'
53
+ nodelist[:data].map { |n| n[:node] }
54
+ end
55
+
56
+ def get_vm_state vm_id
57
+ vm_info = get_vm_info vm_id
58
+ if vm_info
59
+ begin
60
+ response = get "/nodes/#{vm_info[:node]}/#{vm_info[:type]}/#{vm_id}/status/current"
61
+ states = {'running' => :running,
62
+ 'stopped' => :stopped}
63
+ states[response[:data][:status]]
64
+ rescue ApiError::ServerError
65
+ :not_created
66
+ end
67
+ else
68
+ :not_created
69
+ end
70
+ end
71
+
72
+ def wait_for_completion task_response: task_response, timeout_message: timeout_message
73
+ task_upid = task_response[:data]
74
+ timeout = task_timeout
75
+ task_type = /UPID:.*?:.*?:.*?:.*?:(.*)?:.*?:.*?:/.match(task_upid)[1]
76
+ timeout = imgcopy_timeout if task_type == 'imgcopy'
77
+ begin
78
+ retryable(on: VagrantPlugins::Proxmox::ProxmoxTaskNotFinished,
79
+ tries: timeout / task_status_check_interval + 1,
80
+ sleep: task_status_check_interval) do
81
+ exit_status = get_task_exitstatus task_upid
82
+ exit_status.nil? ? raise(VagrantPlugins::Proxmox::ProxmoxTaskNotFinished) : exit_status
83
+ end
84
+ rescue VagrantPlugins::Proxmox::ProxmoxTaskNotFinished
85
+ raise VagrantPlugins::Proxmox::Errors::Timeout.new timeout_message
86
+ end
87
+ end
88
+
89
+ def delete_vm vm_id
90
+ vm_info = get_vm_info vm_id
91
+ response = delete "/nodes/#{vm_info[:node]}/#{vm_info[:type]}/#{vm_id}"
92
+ wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.destroy_vm_timeout'
93
+ end
94
+
95
+ def create_vm(node:, vm_type:, params:)
96
+ response = post "/nodes/#{node}/#{vm_type}", params
97
+ wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.create_vm_timeout'
98
+ end
99
+
100
+ def start_vm vm_id
101
+ vm_info = get_vm_info vm_id
102
+ response = post "/nodes/#{vm_info[:node]}/#{vm_info[:type]}/#{vm_id}/status/start", nil
103
+ wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.start_vm_timeout'
104
+ end
105
+
106
+ def stop_vm vm_id
107
+ vm_info = get_vm_info vm_id
108
+ response = post "/nodes/#{vm_info[:node]}/#{vm_info[:type]}/#{vm_id}/status/stop", nil
109
+ wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.stop_vm_timeout'
110
+ end
111
+
112
+ def shutdown_vm vm_id
113
+ vm_info = get_vm_info vm_id
114
+ response = post "/nodes/#{vm_info[:node]}/#{vm_info[:type]}/#{vm_id}/status/shutdown", nil
115
+ wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.shutdown_vm_timeout'
116
+ end
117
+
118
+ def get_free_vm_id
119
+ response = get "/cluster/resources?type=vm"
120
+ allowed_vm_ids = vm_id_range.to_set
121
+ used_vm_ids = response[:data].map { |vm| vm[:vmid] }
122
+ free_vm_ids = (allowed_vm_ids - used_vm_ids).sort
123
+ free_vm_ids.empty? ? raise(VagrantPlugins::Proxmox::Errors::NoVmIdAvailable) : free_vm_ids.first
124
+ end
125
+
126
+ def upload_file(file, content_type:, node:, storage:)
127
+ unless is_file_in_storage? filename: file, node: node, storage: storage
128
+ res = post "/nodes/#{node}/storage/#{storage}/upload", content: content_type,
129
+ filename: File.new(file, 'rb'), node: node, storage: storage
130
+ wait_for_completion task_response: res, timeout_message: 'vagrant_proxmox.errors.upload_timeout'
131
+ end
132
+ end
133
+
134
+ def list_storage_files(node:, storage:)
135
+ res = get "/nodes/#{node}/storage/#{storage}/content"
136
+ res[:data].map { |e| e[:volid] }
137
+ end
138
+
139
+ # This is called every time to retrieve the node and vm_type, hence on large
140
+ # installations this could be a huge amount of data. Probably an optimization
141
+ # with a buffer for the machine info could be considered.
142
+ private
143
+ def get_vm_info vm_id
144
+ response = get '/cluster/resources?type=vm'
145
+ response[:data]
146
+ .select { |m| m[:id] =~ /^[a-z]*\/#{vm_id}$/ }
147
+ .map {|m| {id: vm_id, type: /^(.*)\/(.*)$/.match(m[:id])[1], node: m[:node]}}
148
+ .first
149
+ end
150
+
151
+ private
152
+ def get_task_exitstatus task_upid
153
+ node = /UPID:(.*?):/.match(task_upid)[1]
154
+ response = get "/nodes/#{node}/tasks/#{task_upid}/status"
155
+ response[:data][:exitstatus]
156
+ end
157
+
158
+ private
159
+ def get path
160
+ begin
161
+ response = RestClient.get "#{api_url}#{path}", {cookies: {PVEAuthCookie: ticket}}
162
+ JSON.parse response.to_s, symbolize_names: true
163
+ rescue RestClient::NotImplemented
164
+ raise ApiError::NotImplemented
165
+ rescue RestClient::InternalServerError
166
+ raise ApiError::ServerError
167
+ rescue RestClient::Unauthorized
168
+ raise ApiError::UnauthorizedError
169
+ rescue => x
170
+ raise ApiError::ConnectionError, x.message
171
+ end
172
+ end
173
+
174
+ private
175
+ def delete path
176
+ begin
177
+ response = RestClient.delete "#{api_url}#{path}", headers
178
+ JSON.parse response.to_s, symbolize_names: true
179
+ rescue RestClient::Unauthorized
180
+ raise ApiError::UnauthorizedError
181
+ rescue RestClient::NotImplemented
182
+ raise ApiError::NotImplemented
183
+ rescue RestClient::InternalServerError
184
+ raise ApiError::ServerError
185
+ rescue => x
186
+ raise ApiError::ConnectionError, x.message
187
+ end
188
+ end
189
+
190
+ private
191
+ def post path, params = {}
192
+ begin
193
+ response = RestClient.post "#{api_url}#{path}", params, headers
194
+ JSON.parse response.to_s, symbolize_names: true
195
+ rescue RestClient::Unauthorized
196
+ raise ApiError::UnauthorizedError
197
+ rescue RestClient::NotImplemented
198
+ raise ApiError::NotImplemented
199
+ rescue RestClient::InternalServerError
200
+ raise ApiError::ServerError
201
+ rescue => x
202
+ raise ApiError::ConnectionError, x.message
203
+ end
204
+ end
205
+
206
+ private
207
+ def headers
208
+ ticket.nil? ? {} : {CSRFPreventionToken: csrf_token, cookies: {PVEAuthCookie: ticket}}
209
+ end
210
+
211
+ private
212
+ def is_file_in_storage?(filename:, node:, storage:)
213
+ (list_storage_files node: node, storage: storage).find { |f| f =~ /#{File.basename filename}/ }
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,23 @@
1
+ module VagrantPlugins
2
+ module Proxmox
3
+
4
+ module ApiError
5
+
6
+ class InvalidCredentials < StandardError
7
+ end
8
+
9
+ class ConnectionError < StandardError
10
+ end
11
+
12
+ class NotImplemented < StandardError
13
+ end
14
+
15
+ class ServerError < StandardError
16
+ end
17
+
18
+ class UnauthorizedError < StandardError
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -1,7 +1,7 @@
1
1
  module VagrantPlugins
2
2
  module Proxmox
3
3
 
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.5'
5
5
 
6
6
  end
7
7
  end
data/locales/en.yml CHANGED
@@ -15,7 +15,7 @@ en:
15
15
  destroy_vm_timeout: |-
16
16
  Destroying the vm on the proxmox server timed out.
17
17
  no_endpoint_specified: 'No endpoint specified.'
18
- no_os_template_specified: 'No os_template specified.'
18
+ no_openvz_os_template_or_openvz_template_file_specified_for_type_openvz: 'No openvz_os_template or openvz_template_file specified for vm_type=:openvz'
19
19
  no_password_specified: 'No password specified.'
20
20
  no_user_name_specified: 'No user_name specified.'
21
21
  no_vm_id_available: |-
@@ -40,10 +40,37 @@ en:
40
40
  stop_vm_timeout: |-
41
41
  Stopping the vm on the proxmox server timed out.
42
42
  timeout: 'Timeout error'
43
- vm_creation_error: |-
43
+ vm_create_error: |-
44
44
  Unable to create the virtual machine!
45
45
 
46
46
  Cause: %{proxmox_exit_status}
47
+ vm_destroy_error: |-
48
+ Unable to destroy the virtual machine!
49
+
50
+ Cause: %{proxmox_exit_status}
51
+ vm_start_error: |-
52
+ Unable to start the virtual machine!
53
+
54
+ Cause: %{proxmox_exit_status}
55
+ vm_stop_error: |-
56
+ Unable to stop the virtual machine!
57
+
58
+ Cause: %{proxmox_exit_status}
59
+ vm_shutdown_error: |-
60
+ Unable to shutdown the virtual machine!
61
+
62
+ Cause: %{proxmox_exit_status}
63
+ vm_not_running: |-
64
+ VM must be running to execute this command. Run `vagrant up`
65
+ to start the virtual machine.
66
+ file_not_found: "File for upload not found"
67
+ upload_server_error: "Error during upload"
68
+ ssh_error: Unable to establish an ssh connection to the virtual machine...
69
+ no_vm_type_specified: "No vm_type specified"
70
+ no_qemu_iso_or_qemu_iso_file_specified_for_vm_type_openvz: "No qemu_iso or qemu_iso_file specified for vm_type=:qemu"
71
+ no_qemu_iso_or_qemu_iso_file_specified_for_vm_type_qemu: "No qemu_iso or qemu_iso_file specified for vm_type=:qemu"
72
+ no_qemu_os_specified_for_vm_type_qemu: "No qemu_os specified for vm_type=:qemu"
73
+ no_qemu_disk_size_specified_for_vm_type_qemu: "No qemu_disk_size specified for vm_type=:qemu"
47
74
  not_created: 'The virtual machine is not created on the server!'
48
75
  rsync_folder: |-
49
76
  Rsyncing folder: %{hostpath} => %{guestpath}
@@ -7,14 +7,14 @@ describe VagrantPlugins::Proxmox::Action::CleanupAfterDestroy do
7
7
  let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox)} }
8
8
  let(:ui) { double('ui').as_null_object }
9
9
 
10
- subject { described_class.new(-> (_) {}, environment) }
10
+ subject(:action) { described_class.new(-> (_) {}, environment) }
11
11
 
12
12
  it_behaves_like 'a proxmox action call'
13
13
 
14
14
  describe '#call', :need_box do
15
15
  it 'should delete the directory `.vagrant/[:machine].name`' do
16
16
  expect do
17
- subject.call env
17
+ action.call env
18
18
  end.to change{File.exists?(".vagrant/machines/#{env[:machine].name}/proxmox")}.to false
19
19
  end
20
20
  end
@@ -7,39 +7,56 @@ module VagrantPlugins::Proxmox
7
7
 
8
8
  let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' }
9
9
  let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox)} }
10
+ let(:api_url) { 'http://your.proxmox.machine/api' }
11
+ let(:username) { 'user' }
12
+ let(:password) { 'password' }
13
+ let(:connection) { env[:proxmox_connection] }
10
14
 
11
- subject { described_class.new(-> (_) {}, environment) }
15
+ subject(:action) { described_class.new(-> (_) {}, environment) }
12
16
 
13
17
  before { VagrantPlugins::Proxmox::Plugin.setup_i18n }
14
18
 
15
19
  describe '#call' do
16
20
 
17
21
  before do
18
- allow(RestClient).to receive(:post).and_return({data: {ticket: 'valid_ticket', CSRFPreventionToken: 'valid_token'}}.to_json)
22
+ env[:machine].provider_config.endpoint = api_url
23
+ env[:machine].provider_config.user_name = username
24
+ env[:machine].provider_config.password = password
25
+ env[:machine].provider_config.vm_id_range = 500..555
26
+ env[:machine].provider_config.task_timeout = 123
27
+ env[:machine].provider_config.task_status_check_interval = 5
28
+ env[:machine].provider_config.imgcopy_timeout = 99
29
+ allow_any_instance_of(Connection).to receive :login
19
30
  end
20
31
 
21
32
  it_behaves_like 'a proxmox action call'
22
33
 
23
- it 'should call the REST API access/ticket' do
24
- RestClient.should_receive(:post).with('https://your.proxmox.server/api2/json/access/ticket', {username: 'vagrant', password: 'password'})
25
- subject.call env
34
+ it 'should store a connection object in env[:proxmox_connection]' do
35
+ action.call env
36
+ expect(connection.api_url).to eq(api_url)
26
37
  end
27
38
 
28
- it 'should store the access ticket in env[:proxmox_ticket]' do
29
- subject.call env
30
- env[:proxmox_ticket].should == 'valid_ticket'
39
+ describe 'sets the connection configuration parameters' do
40
+ before { action.call env }
41
+ specify { expect(connection.vm_id_range).to eq(500..555) }
42
+ specify { expect(connection.task_timeout).to eq(123) }
43
+ specify { expect(connection.task_status_check_interval).to eq(5) }
44
+ specify { expect(connection.imgcopy_timeout).to eq(99) }
31
45
  end
32
46
 
33
- it 'should store the access ticket in env[:proxmox_csrf_prevention_token]' do
34
- subject.call env
35
- env[:proxmox_csrf_prevention_token].should == 'valid_token'
47
+ it 'should call the login function with credentials from configuration' do
48
+ expect_any_instance_of(Connection).to receive(:login).with username: username, password: password
49
+ action.call env
36
50
  end
37
51
 
38
- describe 'when the server communication fails' do
39
- before { RestClient.stub(:post).and_return nil }
40
- specify do
41
- expect { subject.call env }.to raise_error Errors::CommunicationError
52
+ context 'when the server communication fails' do
53
+
54
+ before { allow_any_instance_of(Connection).to receive(:login).and_raise ApiError::InvalidCredentials }
55
+
56
+ it 'should raise an error' do
57
+ expect { action.call env }.to raise_error Errors::CommunicationError
42
58
  end
59
+
43
60
  end
44
61
 
45
62
  end
@@ -1,167 +1,192 @@
1
1
  require 'spec_helper'
2
2
  require 'actions/proxmox_action_shared'
3
3
 
4
- describe VagrantPlugins::Proxmox::Action::CreateVm do
4
+ module VagrantPlugins::Proxmox
5
5
 
6
- it_behaves_like VagrantPlugins::Proxmox::Action::ProxmoxAction
6
+ describe Action::CreateVm do
7
7
 
8
- let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' }
9
- let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox),
10
- proxmox_nodes: [{node: 'localhost'}],
11
- ui: double('ui').as_null_object} }
12
- let(:app) { double('app').as_null_object }
13
- let(:task_upid) { 'UPID:localhost:0000F6ED:00F8E25F:5268CD3B:vzcreate:100:vagrant@pve:' }
8
+ let(:vagrantfile) { 'dummy_box/Vagrantfile' }
9
+ let(:environment) { Vagrant::Environment.new vagrantfile_name: vagrantfile }
10
+ let(:connection) { Connection.new 'https://your.proxmox.server/api2/json' }
11
+ let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox),
12
+ proxmox_selected_node: 'localhost',
13
+ ui: double('ui').as_null_object,
14
+ proxmox_connection: connection} }
15
+ let(:app) { double('app').as_null_object }
16
+ let(:task_upid) { 'UPID:localhost:0000F6ED:00F8E25F:5268CD3B:vzcreate:100:vagrant@pve:' }
14
17
 
15
- subject { described_class.new(app, environment) }
16
-
17
- describe '#call' do
18
+ subject(:action) { described_class.new(-> (_) {}, environment) }
18
19
 
19
20
  before do
20
- allow(RestClient).to receive(:post).and_return({data: task_upid}.to_json)
21
- allow(subject).to receive(:get_free_vm_id).with(env).and_return(100)
22
- allow(subject).to receive(:wait_for_completion).and_return('OK')
21
+ allow(connection).to receive_messages :get_free_vm_id => 100
22
+ allow(connection).to receive_messages :create_vm => 'OK'
23
23
  end
24
24
 
25
- it_behaves_like 'a proxmox action call'
26
- it_behaves_like 'a blocking proxmox action'
27
-
28
- describe 'the post request send to create a new virtual machine' do
29
-
30
- context 'with default config' do
31
- specify do
32
- RestClient.should_receive(:post).
33
- with('https://your.proxmox.server/api2/json/nodes/localhost/openvz',
34
- {vmid: 100,
35
- hostname: 'box',
36
- ostemplate: 'local:vztmpl/template.tgz',
37
- password: 'vagrant',
38
- memory: 256,
39
- description: 'vagrant_test_box'},
40
- anything).
41
- and_return({data: task_upid}.to_json)
42
- subject.call env
43
- end
44
- end
25
+ describe '#call' do
45
26
 
46
- context 'with a specified hostname' do
47
- before { env[:machine].config.vm.hostname = 'hostname' }
48
- specify do
49
- RestClient.should_receive(:post).
50
- with('https://your.proxmox.server/api2/json/nodes/localhost/openvz',
51
- {vmid: 100,
52
- hostname: 'hostname',
53
- ostemplate: 'local:vztmpl/template.tgz',
54
- password: 'vagrant',
55
- memory: 256,
56
- description: 'vagrant_test_box'},
57
- anything).
58
- and_return({data: task_upid}.to_json)
59
- subject.call env
60
- end
61
- end
27
+ it_behaves_like 'a proxmox action call'
62
28
 
63
- context 'with a specified ip address' do
64
- before { env[:machine].config.vm.stub(:networks) { [[:public_network, {ip: '127.0.0.1'}]] } }
65
- specify do
66
- RestClient.should_receive(:post).
67
- with('https://your.proxmox.server/api2/json/nodes/localhost/openvz',
68
- {vmid: 100,
69
- hostname: 'box',
70
- ip_address: '127.0.0.1',
71
- ostemplate: 'local:vztmpl/template.tgz',
72
- password: 'vagrant',
73
- memory: 256,
74
- description: 'vagrant_test_box'},
75
- anything).
76
- and_return({data: task_upid}.to_json)
77
- subject.call env
78
- end
79
- end
29
+ describe 'the call to create a new virtual machine' do
80
30
 
81
- end
31
+ context 'when the vm_type is :openvz' do
82
32
 
83
- it 'should print a message to the user interface' do
84
- env[:ui].should_receive(:info).with 'Creating the virtual machine...'
85
- env[:ui].should_receive(:info).with 'Done!'
86
- subject.call env
87
- end
33
+ let(:vagrantfile) { 'dummy_box/Vagrantfile' }
34
+ before { allow(env[:machine].provider_config).to receive(:vm_type) { :openvz } }
88
35
 
89
- it 'should store the node and vmid in env[:machine].id' do
90
- subject.call env
91
- env[:machine].id.should == 'localhost/100'
92
- end
36
+ context 'with default config' do
37
+ specify do
38
+ expect(connection).to receive(:create_vm).
39
+ with(node: 'localhost',
40
+ vm_type: :openvz,
41
+ params: {vmid: 100,
42
+ hostname: 'machine',
43
+ ostemplate: 'local:vztmpl/template.tar.gz',
44
+ password: 'vagrant',
45
+ memory: 256,
46
+ description: 'vagrant_test_machine'})
47
+ action.call env
48
+ end
49
+ end
93
50
 
94
- context 'when the proxmox server responds with an error to the create request' do
51
+ context 'with a specified hostname' do
52
+ before { env[:machine].config.vm.hostname = 'hostname' }
53
+ specify do
54
+ expect(connection).to receive(:create_vm).
55
+ with(node: 'localhost',
56
+ vm_type: :openvz,
57
+ params: {vmid: 100,
58
+ hostname: 'hostname',
59
+ ostemplate: 'local:vztmpl/template.tar.gz',
60
+ password: 'vagrant',
61
+ memory: 256,
62
+ description: 'vagrant_test_machine'})
63
+ action.call env
64
+ end
65
+ end
95
66
 
96
- let(:error_exit_status) { ["can't lock container 100", "close (rename) atomic file '/etc/pve/nodes/localhost/openvz/100.conf' failed: File exists"] }
67
+ context 'with a specified ip address' do
68
+ before { allow(env[:machine].config.vm).to receive(:networks) { [[:public_network, {ip: '127.0.0.1'}]] } }
69
+ specify do
70
+ expect(connection).to receive(:create_vm).
71
+ with(node: 'localhost',
72
+ vm_type: :openvz,
73
+ params: {vmid: 100,
74
+ hostname: 'machine',
75
+ ip_address: '127.0.0.1',
76
+ ostemplate: 'local:vztmpl/template.tar.gz',
77
+ password: 'vagrant',
78
+ memory: 256,
79
+ description: 'vagrant_test_machine'})
80
+ action.call env
81
+ end
82
+ end
83
+ end
97
84
 
98
- before { subject.stub :sleep }
85
+ context 'when the vm_type is :qemu' do
86
+
87
+ let(:vagrantfile) { 'dummy_box/Vagrantfile_qemu' }
88
+ before { allow(env[:machine].provider_config).to receive(:vm_type) { :qemu } }
89
+
90
+ context 'with default config' do
91
+ specify do
92
+ expect(connection).to receive(:create_vm).
93
+ with(node: 'localhost',
94
+ vm_type: :qemu,
95
+ params: {vmid: 100,
96
+ name: 'machine',
97
+ ostype: :l26,
98
+ ide2: 'local:iso/isofile.iso,media=cdrom',
99
+ sata0: 'raid:30,format=qcow2',
100
+ sockets: 1,
101
+ cores: 1,
102
+ net0: 'e1000,bridge=vmbr0',
103
+ memory: 256,
104
+ description: 'vagrant_test_machine'})
105
+ action.call env
106
+ end
107
+ end
99
108
 
100
- context 'when the proxmox server replies with an exit status describing the error' do
101
- it 'should create a virtual machine with another vmid' do
102
- expect(subject).to receive(:get_free_vm_id).with(env).twice.and_return(100, 101)
103
- expect(subject).to receive(:wait_for_completion).twice.and_return(error_exit_status.sample, 'OK')
104
- subject.call env
105
- env[:machine].id.should == 'localhost/101'
106
- end
107
- end
109
+ context 'with a specified hostname' do
110
+ before { env[:machine].config.vm.hostname = 'hostname' }
111
+ specify do
112
+ expect(connection).to receive(:create_vm).
113
+ with(node: 'localhost',
114
+ vm_type: :qemu,
115
+ params: {vmid: 100,
116
+ name: 'hostname',
117
+ ostype: :l26,
118
+ ide2: 'local:iso/isofile.iso,media=cdrom',
119
+ sata0: 'raid:30,format=qcow2',
120
+ sockets: 1,
121
+ cores: 1,
122
+ net0: 'e1000,bridge=vmbr0',
123
+ memory: 256,
124
+ description: 'vagrant_test_machine'})
125
+ action.call env
126
+ end
127
+ end
108
128
 
109
- context 'when the proxmox server replies with a "500 Internal Server Error"' do
110
- it 'should create a virtual machine with another vmid' do
111
- expect(subject).to receive(:get_free_vm_id).with(env).twice.and_return(100, 101)
112
- expect(subject).to receive(:wait_for_completion).and_raise(RestClient::InternalServerError).once
113
- expect(subject).to receive(:wait_for_completion).once.and_return('OK')
114
- subject.call env
115
- env[:machine].id.should == 'localhost/101'
129
+ context 'with predefined network settings' do
130
+ before { allow(env[:machine].config.vm).to receive(:networks) { [[:public_network, {ip: '127.0.0.1', macaddress: 'aa:bb:cc:dd:ee:ff'}]] } }
131
+ specify do
132
+ expect(connection).to receive(:create_vm).
133
+ with(node: 'localhost',
134
+ vm_type: :qemu,
135
+ params: {vmid: 100,
136
+ name: 'machine',
137
+ ostype: :l26,
138
+ ide2: 'local:iso/isofile.iso,media=cdrom',
139
+ sata0: 'raid:30,format=qcow2',
140
+ sockets: 1,
141
+ cores: 1,
142
+ net0: 'e1000=aa:bb:cc:dd:ee:ff,bridge=vmbr0',
143
+ memory: 256,
144
+ description: 'vagrant_test_machine'})
145
+ action.call env
146
+ end
147
+ end
116
148
  end
117
149
  end
118
150
 
119
- context 'when the create requests fail continuously before the timeout deadline is reached' do
120
- it 'should raise a VMCreationError' do
121
- expect(subject).to receive(:get_free_vm_id).with(env).exactly(30).times.and_return(100)
122
- expect(subject).to receive(:wait_for_completion).exactly(30).times.and_return(error_exit_status.sample)
123
- expect { subject.send :call, env }.to raise_error VagrantPlugins::Proxmox::Errors::VMCreationError
124
- end
151
+ it 'should print a message to the user interface' do
152
+ expect(env[:ui]).to receive(:info).with 'Creating the virtual machine...'
153
+ expect(env[:ui]).to receive(:info).with 'Done!'
154
+ action.call env
125
155
  end
126
156
 
127
- end
128
-
129
- end
130
-
131
- describe '#get_free_vm_id' do
157
+ it 'should store the node and vmid in env[:machine].id' do
158
+ action.call env
159
+ expect(env[:machine].id).to eq('localhost/100')
160
+ end
132
161
 
133
- it 'should query the proxmox server for all qemu and openvz machines' do
134
- RestClient.should_receive(:get).with('https://your.proxmox.server/api2/json/cluster/resources?type=vm', anything).and_return({data: []}.to_json)
135
- subject.send(:get_free_vm_id, env)
136
- end
162
+ it 'should get a free vm id from connection' do
163
+ expect(connection).to receive(:get_free_vm_id)
164
+ action.send :call, env
165
+ end
137
166
 
138
- describe 'find the smallest unused vm_id in the configured range' do
167
+ context 'when the proxmox server responds with an error to the create request' do
139
168
 
140
- before { env[:machine].provider_config.vm_id_range = 3..4 }
169
+ context 'when the proxmox server replies with an internal server error to the delete_vm call' do
170
+ it 'should raise a VMCreateError' do
171
+ allow(connection).to receive(:create_vm).and_raise ApiError::ServerError
172
+ expect { action.send :call, env }.to raise_error Errors::VMCreateError
173
+ end
174
+ end
141
175
 
142
- context 'when free vm_ids are available' do
143
- [
144
- {used_vm_ids: {data: []}, smallest_free_in_range: 3},
145
- {used_vm_ids: {data: [{vmid: 3}]}, smallest_free_in_range: 4},
146
- {used_vm_ids: {data: [{vmid: 1}]}, smallest_free_in_range: 3},
147
- {used_vm_ids: {data: [{vmid: 1}, {vmid: 3}]}, smallest_free_in_range: 4},
148
- ].each do |example|
149
- it 'should return the smallest unused vm_id in the configured vm_id_range' do
150
- allow(RestClient).to receive(:get).and_return(example[:used_vm_ids].to_json)
151
- subject.send(:get_free_vm_id, env).should == example[:smallest_free_in_range]
176
+ context 'when the proxmox server replies with an internal server error to the task status request' do
177
+ it 'should raise a VMCreateError' do
178
+ allow(connection).to receive(:create_vm).and_raise ApiError::ServerError
179
+ expect { action.send :call, env }.to raise_error Errors::VMCreateError
152
180
  end
153
181
  end
154
- end
155
182
 
156
- context 'when no vm_ids are available' do
157
- it 'should throw a no_vm_id_available error' do
158
- allow(RestClient).to receive(:get).and_return({data: [{vmid: 1}, {vmid: 2}, {vmid: 3}, {vmid: 4}]}.to_json)
159
- expect { subject.send :get_free_vm_id, env }.to raise_error VagrantPlugins::Proxmox::Errors::NoVmIdAvailable
183
+ context 'when the proxmox server does not reply the task status request with OK' do
184
+ it 'should raise a VMCreateError' do
185
+ allow(connection).to receive_messages :create_vm => 'create vm error'
186
+ expect { action.send :call, env }.to raise_error Errors::VMCreateError, /create vm error/
187
+ end
160
188
  end
161
189
  end
162
-
163
190
  end
164
-
165
191
  end
166
-
167
192
  end