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
@@ -26,14 +26,34 @@ module VagrantPlugins
|
|
26
26
|
error_key :no_vm_id_available
|
27
27
|
end
|
28
28
|
|
29
|
-
class
|
30
|
-
error_key :
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
24
|
-
|
25
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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
|
34
|
-
|
35
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
4
|
+
module VagrantPlugins::Proxmox
|
5
5
|
|
6
|
-
|
6
|
+
describe Action::CreateVm do
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
-
|
17
|
-
describe '#call' do
|
18
|
+
subject(:action) { described_class.new(-> (_) {}, environment) }
|
18
19
|
|
19
20
|
before do
|
20
|
-
allow(
|
21
|
-
allow(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
31
|
+
context 'when the vm_type is :openvz' do
|
82
32
|
|
83
|
-
|
84
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
167
|
+
context 'when the proxmox server responds with an error to the create request' do
|
139
168
|
|
140
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|