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