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.
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