foreman_fog_proxmox 0.23.0 → 0.24.0
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/app/assets/javascripts/foreman_fog_proxmox/proxmox_compute_resource.js +11 -1
- data/app/controllers/concerns/foreman_fog_proxmox/controller/parameters/compute_resource.rb +2 -1
- data/app/controllers/foreman_fog_proxmox/compute_resources_controller.rb +2 -1
- data/app/helpers/proxmox_form_helper.rb +5 -2
- data/app/helpers/proxmox_vm_attrs_helper.rb +1 -1
- data/app/helpers/proxmox_vm_cdrom_helper.rb +9 -2
- data/app/helpers/proxmox_vm_cloudinit_helper.rb +8 -2
- data/app/helpers/proxmox_vm_interfaces_helper.rb +11 -0
- data/app/helpers/proxmox_vm_volumes_helper.rb +14 -0
- data/app/models/concerns/orchestration/proxmox/compute.rb +8 -1
- data/app/models/foreman_fog_proxmox/compute_attributes_update_detector.rb +41 -0
- data/app/models/foreman_fog_proxmox/proxmox.rb +11 -0
- data/app/models/foreman_fog_proxmox/proxmox_compute_attributes.rb +8 -1
- data/app/models/foreman_fog_proxmox/proxmox_images.rb +33 -20
- data/app/models/foreman_fog_proxmox/proxmox_pools.rb +19 -2
- data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +4 -1
- data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +1 -1
- data/app/models/foreman_fog_proxmox/proxmox_vm_queries.rb +36 -13
- data/app/models/foreman_fog_proxmox/proxmox_volumes.rb +16 -7
- data/app/views/api/v2/compute_resources/proxmox.json.rabl +1 -1
- data/app/views/compute_resources/form/_proxmox.html.erb +6 -5
- data/app/views/compute_resources/show/_proxmox.html.erb +4 -0
- data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +1 -0
- data/lib/foreman_fog_proxmox/engine.rb +2 -0
- data/lib/foreman_fog_proxmox/version.rb +1 -1
- data/test/functional/compute_resources_controller_test.rb +12 -0
- data/test/unit/foreman_fog_proxmox/helpers/proxmox_form_helper_test.rb +51 -0
- data/test/unit/foreman_fog_proxmox/helpers/proxmox_server_helper_test.rb +10 -0
- data/test/unit/foreman_fog_proxmox/proxmox_compute_attributes_test.rb +7 -0
- data/test/unit/foreman_fog_proxmox/proxmox_orchestration_compute_test.rb +34 -0
- data/test/unit/foreman_fog_proxmox/proxmox_test.rb +102 -0
- data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_create_test.rb +31 -0
- data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_update_cdrom_test.rb +51 -1
- data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_update_test.rb +4 -4
- data/test/unit/foreman_fog_proxmox/proxmox_vm_queries_test.rb +61 -2
- data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +32 -11
- data/webpack/components/ProxmoxServer/components/CDRom.js +49 -9
- data/webpack/components/ProxmoxVmType.js +4 -0
- data/webpack/global_index.js +2 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 494d9e13c53b6e6e1a4821d8c046d145060bac1058f9285763661bd935e5a4ef
|
|
4
|
+
data.tar.gz: ba0639aa5659316381788213a3d6049ae98a8238a1612dd1dc8db7eed583d11f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d7e5fc3d3bff0e58256217b5f69910dc03d0f08662dcc2ef1af35e242e5115f4e8023c565afa99cd83821dea73018d66b235354106766a4dbed351266c165ce
|
|
7
|
+
data.tar.gz: 6ec6c8942c3e58cc7b3a05be5f2c0131bc47a0fb3cb9a4948d46cea8b51610ee33b7b6f0cbd8a08eac66818e7e83a2f0f51e3dca2ce8d0a9c051092d84c21c81
|
|
@@ -23,11 +23,14 @@ $(document).ready(function () {
|
|
|
23
23
|
function sslVerifyPeerSelected() {
|
|
24
24
|
var selected = $("#compute_resource_ssl_verify_peer").is(':checked');
|
|
25
25
|
var ssl_certs_block = $('#compute_resource_ssl_certs').parents('.clearfix');
|
|
26
|
+
var ssl_certs_group = $('#compute_resource_ssl_certs').parents('.form-group');
|
|
26
27
|
var ssl_certs_textarea = $('#compute_resource_ssl_certs');
|
|
27
28
|
if (selected) {
|
|
29
|
+
ssl_certs_group.removeClass('hide');
|
|
28
30
|
ssl_certs_block.show();
|
|
29
31
|
ssl_certs_textarea.show();
|
|
30
32
|
} else {
|
|
33
|
+
ssl_certs_group.addClass('hide');
|
|
31
34
|
ssl_certs_block.hide();
|
|
32
35
|
ssl_certs_textarea.text('');
|
|
33
36
|
ssl_certs_textarea.hide();
|
|
@@ -62,4 +65,11 @@ function authMethodSelected() {
|
|
|
62
65
|
authMethods().forEach(function(method){
|
|
63
66
|
toggleFieldset(method, selected);
|
|
64
67
|
});
|
|
65
|
-
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
window.tfm = window.tfm || {};
|
|
71
|
+
window.tfm.computeResource = window.tfm.computeResource || {};
|
|
72
|
+
window.tfm.computeResource.proxmox = {
|
|
73
|
+
authMethodSelected: authMethodSelected,
|
|
74
|
+
sslVerifyPeerSelected: sslVerifyPeerSelected,
|
|
75
|
+
};
|
|
@@ -27,7 +27,8 @@ module ForemanFogProxmox
|
|
|
27
27
|
def compute_resource_params_filter
|
|
28
28
|
super.tap do |filter|
|
|
29
29
|
filter.permit :ssl_verify_peer,
|
|
30
|
-
:ssl_certs, :disable_proxy, :auth_method, :token_id, :token
|
|
30
|
+
:ssl_certs, :disable_proxy, :auth_method, :token_id, :token,
|
|
31
|
+
:caching_enabled
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
|
|
@@ -84,7 +84,8 @@ module ForemanFogProxmox
|
|
|
84
84
|
node_id = params[:node_id]
|
|
85
85
|
storage = params[:storage]
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
node = cr.send(:client).nodes.get(node_id)
|
|
88
|
+
vols = node&.storages&.get(storage)&.volumes || []
|
|
88
89
|
|
|
89
90
|
render json: Array(vols).map { |v|
|
|
90
91
|
h = v.respond_to?(:as_json) ? v.as_json : v
|
|
@@ -130,8 +130,11 @@ module ProxmoxFormHelper
|
|
|
130
130
|
def proxmox_valid_interface_compute_attributes?(compute_attributes, vm_type)
|
|
131
131
|
return false unless compute_attributes.respond_to?(:keys)
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
compute_attribute_keys = compute_attributes.keys.map(&:to_s)
|
|
134
|
+
required_keys = interface_compute_attributes_required_typed_keys(vm_type)
|
|
135
|
+
missing_keys = required_keys - compute_attribute_keys
|
|
136
|
+
|
|
137
|
+
required_keys.any? && missing_keys.empty?
|
|
135
138
|
end
|
|
136
139
|
|
|
137
140
|
def extract_attr(attrs, key)
|
|
@@ -76,7 +76,7 @@ module ProxmoxVMAttrsHelper
|
|
|
76
76
|
keys = ['id', 'volid', 'storage_type', 'storage', 'controller', 'device', 'cache', 'size', '_delete', 'backup']
|
|
77
77
|
type = 'hard_disk'
|
|
78
78
|
elsif vol.cdrom?
|
|
79
|
-
keys = ['id', 'storage_type', 'cdrom', 'storage', 'volid']
|
|
79
|
+
keys = ['id', 'storage_type', 'cdrom', 'storage', 'volid', '_delete']
|
|
80
80
|
type = 'cdrom'
|
|
81
81
|
elsif vol.cloud_init?
|
|
82
82
|
keys = ['id', 'volid', 'storage_type', 'storage', 'controller', 'device']
|
|
@@ -26,8 +26,15 @@ require 'foreman_fog_proxmox/hash_collection'
|
|
|
26
26
|
module ProxmoxVMCdromHelper
|
|
27
27
|
def parse_server_cdrom(args)
|
|
28
28
|
cdrom_media = args['cdrom'] if args.key?('cdrom')
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
return {} unless cdrom_media
|
|
30
|
+
cdrom_image = args['volid'] if args.key?('volid') && cdrom_media == 'image'
|
|
31
|
+
volid = if cdrom_image.present?
|
|
32
|
+
cdrom_image
|
|
33
|
+
elsif cdrom_media == 'none'
|
|
34
|
+
'none'
|
|
35
|
+
elsif ['physical', 'cdrom'].include?(cdrom_media)
|
|
36
|
+
'cdrom'
|
|
37
|
+
end
|
|
31
38
|
return {} unless volid
|
|
32
39
|
|
|
33
40
|
{ id: 'ide2', volid: volid, media: 'cdrom' }
|
|
@@ -111,8 +111,14 @@ module ProxmoxVMCloudinitHelper
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
def attach_cloudinit_iso(node, iso)
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
volume = nil
|
|
115
|
+
storages(node, 'iso').each do |storage|
|
|
116
|
+
volume = storage.volumes.detect { |v| v.volid.include? File.basename(iso) }
|
|
117
|
+
break if volume
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
raise ::Foreman::Exception, "Could not find generated cloud-init ISO #{File.basename(iso)} on any ISO storage for node #{node}" unless volume
|
|
121
|
+
|
|
116
122
|
{ ide2: "#{volume.volid},media=cdrom" }
|
|
117
123
|
end
|
|
118
124
|
|
|
@@ -55,6 +55,17 @@ module ProxmoxVMInterfacesHelper
|
|
|
55
55
|
keys
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
def interface_compute_attributes_required_typed_keys(type)
|
|
59
|
+
case type
|
|
60
|
+
when 'qemu'
|
|
61
|
+
['model']
|
|
62
|
+
when 'lxc'
|
|
63
|
+
['name']
|
|
64
|
+
else
|
|
65
|
+
[]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
58
69
|
def interface_common_typed_keys(type)
|
|
59
70
|
['id', (type == 'qemu') ? 'macaddr' : 'hwaddr']
|
|
60
71
|
end
|
|
@@ -86,15 +86,29 @@ module ProxmoxVMVolumesHelper
|
|
|
86
86
|
if args.key?('storage_type')
|
|
87
87
|
args['storage_type'] == type
|
|
88
88
|
else
|
|
89
|
+
return true if type == 'cdrom' && args.key?('cdrom')
|
|
90
|
+
|
|
89
91
|
Fog::Proxmox::DiskHelper.cloud_init?(args['volid']) if type == 'cloud_init'
|
|
90
92
|
Fog::Proxmox::DiskHelper.cdrom?(args['volid']) if type == 'cdrom'
|
|
91
93
|
Fog::Proxmox::DiskHelper.disk?(args['id']) if ['hard_disk', 'rootfs', 'mp'].include?(type)
|
|
92
94
|
end
|
|
93
95
|
end
|
|
94
96
|
|
|
97
|
+
def validate_cdrom_image_permission!(volume_attributes)
|
|
98
|
+
volume_attributes = volume_attributes.with_indifferent_access
|
|
99
|
+
|
|
100
|
+
return unless volume_type?(volume_attributes, 'cdrom')
|
|
101
|
+
return unless volume_attributes[:cdrom].to_s == 'image'
|
|
102
|
+
return if User.current&.can?(:attach_cdrom_image, self)
|
|
103
|
+
|
|
104
|
+
raise ::Foreman::Exception,
|
|
105
|
+
_('You are not authorized to attach or change CD-ROM ISO images.')
|
|
106
|
+
end
|
|
107
|
+
|
|
95
108
|
def parse_typed_volume(args, type)
|
|
96
109
|
return if Foreman::Cast.to_bool(args['_delete'])
|
|
97
110
|
logger.debug("parse_typed_volume(#{type}): args=#{args}")
|
|
111
|
+
validate_cdrom_image_permission!(args)
|
|
98
112
|
disk = parse_hard_disk_volume(args) if volume_type?(args,
|
|
99
113
|
'hard_disk') || volume_type?(args, 'mp') || volume_type?(args, 'rootfs')
|
|
100
114
|
disk = parse_server_cloudinit(args) if volume_type?(args, 'cloud_init')
|
|
@@ -63,7 +63,14 @@ module Orchestration
|
|
|
63
63
|
|
|
64
64
|
def computeValue(foreman_attr, fog_attr)
|
|
65
65
|
value = ''
|
|
66
|
-
|
|
66
|
+
|
|
67
|
+
if foreman_attr == :uuid
|
|
68
|
+
vm_value = vm.send(fog_attr).to_s
|
|
69
|
+
return vm_value if vm_value.match?(/\A\d+_\d+\z/)
|
|
70
|
+
|
|
71
|
+
value += compute_resource.id.to_s + '_'
|
|
72
|
+
end
|
|
73
|
+
|
|
67
74
|
value += vm.send(fog_attr).to_s
|
|
68
75
|
value
|
|
69
76
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright 2026
|
|
4
|
+
|
|
5
|
+
# This file is part of ForemanFogProxmox.
|
|
6
|
+
|
|
7
|
+
# ForemanFogProxmox is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
|
|
12
|
+
# ForemanFogProxmox is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
|
|
20
|
+
module ForemanFogProxmox
|
|
21
|
+
module ComputeAttributesUpdateDetector
|
|
22
|
+
def update_required?(old_attrs, new_attrs)
|
|
23
|
+
old_attrs = old_attrs.deep_symbolize_keys
|
|
24
|
+
new_attrs = new_attrs.deep_symbolize_keys
|
|
25
|
+
new_attrs.each do |key, new_v|
|
|
26
|
+
old_v = old_attrs[key]
|
|
27
|
+
if new_v.is_a?(Hash)
|
|
28
|
+
unless old_v.is_a?(Hash)
|
|
29
|
+
logger.debug "Scheduling compute instance update because #{key} changed it's value from '#{old_v}' (#{old_v.class}) to '#{new_v}' (#{new_v.class})"
|
|
30
|
+
return true
|
|
31
|
+
end
|
|
32
|
+
return true if update_required?(old_v, new_v)
|
|
33
|
+
elsif old_v.to_s != new_v.to_s
|
|
34
|
+
logger.debug "Scheduling compute instance update because #{key} changed it's value from '#{old_v}' (#{old_v.class}) to '#{new_v}' (#{new_v.class})"
|
|
35
|
+
return true
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
false
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -35,6 +35,9 @@ module ForemanFogProxmox
|
|
|
35
35
|
include ProxmoxOperatingSystems
|
|
36
36
|
include ProxmoxVersion
|
|
37
37
|
include ProxmoxConsole
|
|
38
|
+
include ComputeAttributesUpdateDetector
|
|
39
|
+
include ComputeResourceCaching
|
|
40
|
+
|
|
38
41
|
validates :url, :format => { :with => URI::DEFAULT_PARSER.make_regexp }, :presence => true
|
|
39
42
|
validates :auth_method, :presence => true, :inclusion => { in: ['access_ticket', 'user_token'],
|
|
40
43
|
message: ->(value) do format('%<value>s is not a valid authentication method', { value: value }) end }
|
|
@@ -128,6 +131,14 @@ module ForemanFogProxmox
|
|
|
128
131
|
|
|
129
132
|
private
|
|
130
133
|
|
|
134
|
+
def structs_from_cache(items)
|
|
135
|
+
Array(items).map { |item| OpenStruct.new(item) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def extract_attributes(resource, fields)
|
|
139
|
+
slice_vm_attributes(fields.index_with { |field| resource.try(field) }, fields)
|
|
140
|
+
end
|
|
141
|
+
|
|
131
142
|
def fog_credentials
|
|
132
143
|
hash = {
|
|
133
144
|
proxmox_url: url,
|
|
@@ -64,8 +64,15 @@ module ForemanFogProxmox
|
|
|
64
64
|
vm_attrs
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
def cdrom_compute_attributes(attrs)
|
|
68
|
+
return unless attrs[:storage_type].to_s == 'cdrom' || attrs[:media].to_s == 'cdrom'
|
|
69
|
+
|
|
70
|
+
{ cdrom: attrs[:volid].to_s }
|
|
71
|
+
end
|
|
72
|
+
|
|
67
73
|
def volume_compute_attributes(volume_attributes)
|
|
68
|
-
volume_attributes.merge(_delete: '0')
|
|
74
|
+
attrs = volume_attributes.merge(_delete: '0')
|
|
75
|
+
cdrom_compute_attributes(attrs) || attrs
|
|
69
76
|
end
|
|
70
77
|
|
|
71
78
|
def vm_compute_attributes(vm)
|
|
@@ -24,11 +24,18 @@ module ForemanFogProxmox
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def images_by_storage(node_id, storage_id, type = 'iso')
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
cached_images = cache.cache(:"images_by_storage-#{node_id}-#{storage_id}-#{type}") do
|
|
28
|
+
node = client.nodes.get(node_id) || default_node
|
|
29
|
+
storage = node.storages.get storage_id if storage_id
|
|
30
|
+
logger.debug("images_by_storage(): node_id #{node_id} storage_id #{storage_id} type #{type}")
|
|
31
|
+
next [] unless storage
|
|
32
|
+
|
|
33
|
+
storage.volumes.list_by_content_type(type).sort_by(&:volid).map do |volume|
|
|
34
|
+
extract_attributes(volume, [:volid, :content, :format, :size, :used])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
structs_from_cache(cached_images)
|
|
32
39
|
end
|
|
33
40
|
|
|
34
41
|
def template_name(template)
|
|
@@ -45,25 +52,31 @@ module ForemanFogProxmox
|
|
|
45
52
|
end
|
|
46
53
|
|
|
47
54
|
def templates
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
cached_templates = cache.cache(:templates) do
|
|
56
|
+
volumes = fog_nodes.flat_map do |node|
|
|
57
|
+
storages = node.storages.list_by_content_type 'images'
|
|
58
|
+
logger.debug("storages(): node_id #{node.node} type images")
|
|
59
|
+
storages.reject { |storage| storage.active.to_i.zero? }.sort_by(&:storage).flat_map do |storage|
|
|
60
|
+
# Skip disabled storages (enabled == 0 or nil)
|
|
61
|
+
unless storage.enabled.to_i == 1
|
|
62
|
+
logger.warn("Skipping disabled storage #{storage.identity} on #{node.node}")
|
|
63
|
+
next []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Fetch QEMU and LXC template images
|
|
67
|
+
storage.volumes.list_by_content_type('images') + storage.volumes.list_by_content_type('rootdir')
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
logger.error("Failed to fetch volumes for storage #{storage.identity} on #{node.node}: #{e.message}")
|
|
70
|
+
[]
|
|
55
71
|
end
|
|
72
|
+
end
|
|
56
73
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
volumes += storage.volumes.list_by_content_type('rootdir')
|
|
60
|
-
rescue StandardError => e
|
|
61
|
-
logger.error("Failed to fetch volumes for storage #{storage.identity} on #{node.node}: #{e.message}")
|
|
62
|
-
next
|
|
74
|
+
volumes.select(&:template?).map do |volume|
|
|
75
|
+
extract_attributes(volume, [:vmid, :name, :volid, :node_id]).merge(template: true)
|
|
63
76
|
end
|
|
64
77
|
end
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
|
|
79
|
+
structs_from_cache(cached_templates)
|
|
67
80
|
end
|
|
68
81
|
|
|
69
82
|
def template(uuid)
|
|
@@ -20,8 +20,25 @@
|
|
|
20
20
|
module ForemanFogProxmox
|
|
21
21
|
module ProxmoxPools
|
|
22
22
|
def pools
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
cached_pools = cache.cache(:pools) do
|
|
24
|
+
pools = identity_client.pools.all
|
|
25
|
+
pools.sort_by(&:poolid).map do |pool|
|
|
26
|
+
extract_attributes(pool, [:poolid, :comment]).merge(
|
|
27
|
+
members: Array(pool.try(:members)).map do |member|
|
|
28
|
+
{ vmid: member['vmid'] }
|
|
29
|
+
end
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Store only serializable hashes in the cache. After retrieval we recreate the pool objects and reattach the `has_server?` helper method.
|
|
35
|
+
Array(cached_pools).map do |pool|
|
|
36
|
+
OpenStruct.new(pool).tap do |pool_object|
|
|
37
|
+
pool_object.define_singleton_method(:has_server?) do |vmid|
|
|
38
|
+
Array(members).any? { |member| member[:vmid].to_s == vmid.to_s || member['vmid'].to_s == vmid.to_s }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
25
42
|
end
|
|
26
43
|
|
|
27
44
|
def pool_owner(vm)
|
|
@@ -113,7 +113,10 @@ module ForemanFogProxmox
|
|
|
113
113
|
|
|
114
114
|
volumes_attributes = new_attributes['volumes_attributes']
|
|
115
115
|
logger.debug("save_vm(#{uuid}) volumes_attributes=#{volumes_attributes}")
|
|
116
|
-
volumes_attributes&.each_value
|
|
116
|
+
volumes_attributes&.each_value do |volume_attributes|
|
|
117
|
+
validate_cdrom_image_permission!(volume_attributes)
|
|
118
|
+
save_volume(vm, volume_attributes)
|
|
119
|
+
end
|
|
117
120
|
|
|
118
121
|
efidisk_attributes = new_attributes['efidisk_attributes']
|
|
119
122
|
if vm.config.efidisk.present? && efidisk_attributes.empty?
|
|
@@ -23,29 +23,47 @@ module ForemanFogProxmox
|
|
|
23
23
|
include ProxmoxVMUuidHelper
|
|
24
24
|
|
|
25
25
|
def nodes
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
cached_nodes = cache.cache(:nodes) do
|
|
27
|
+
nodes = client.nodes.all if client
|
|
28
|
+
nodes&.sort_by(&:node)&.map do |node|
|
|
29
|
+
{ node: node.node }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
structs_from_cache(cached_nodes)
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
def storages(node_id = default_node_id, type = 'images')
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
cached_storages = cache.cache(:"storages-#{node_id}-#{type}") do
|
|
38
|
+
node = client.nodes.get(node_id) || default_node
|
|
39
|
+
storages = node.storages.list_by_content_type type
|
|
40
|
+
logger.debug("storages(): node_id #{node_id} type #{type}")
|
|
41
|
+
storages.reject { |s| s.enabled.to_i.zero? || s.active.to_i.zero? }.sort_by(&:storage).map do |storage|
|
|
42
|
+
fields = [:storage, :enabled, :active, :content, :node_id, :avail, :used, :total]
|
|
43
|
+
extract_attributes(storage, fields).merge(identity: storage.storage)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
structs_from_cache(cached_storages)
|
|
36
48
|
end
|
|
37
49
|
|
|
38
50
|
def bridges(node_id = default_node_id)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
cached_bridges = cache.cache(:"bridges-#{node_id}") do
|
|
52
|
+
node = network_client.nodes.get node_id
|
|
53
|
+
node ||= network_client.nodes.first
|
|
54
|
+
bridges = node.networks.all(type: 'any_bridge')
|
|
55
|
+
bridges.sort_by(&:iface).map do |bridge|
|
|
56
|
+
extract_attributes(bridge, [:iface, :node_id]).merge(identity: bridge.iface)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
structs_from_cache(cached_bridges)
|
|
43
61
|
end
|
|
44
62
|
|
|
45
63
|
# TODO: Pagination with filters
|
|
46
64
|
def vms(opts = {})
|
|
47
65
|
vms = []
|
|
48
|
-
|
|
66
|
+
fog_nodes.each { |node| vms += node.servers.all + node.containers.all }
|
|
49
67
|
vms.each { |vm| attach_compute_resource_id(vm) }
|
|
50
68
|
if opts.key?(:eager_loading) && opts[:eager_loading]
|
|
51
69
|
vms_eager = []
|
|
@@ -59,7 +77,7 @@ module ForemanFogProxmox
|
|
|
59
77
|
# look for the uuid on all known nodes
|
|
60
78
|
vm = nil
|
|
61
79
|
vmid = extract_vmid(uuid)
|
|
62
|
-
|
|
80
|
+
fog_nodes.each do |node|
|
|
63
81
|
vm = find_vm_in_servers_by_vmid(node.servers, vmid)
|
|
64
82
|
vm ||= find_vm_in_servers_by_vmid(node.containers, vmid)
|
|
65
83
|
next if vm.nil?
|
|
@@ -82,6 +100,11 @@ module ForemanFogProxmox
|
|
|
82
100
|
|
|
83
101
|
private
|
|
84
102
|
|
|
103
|
+
def fog_nodes
|
|
104
|
+
nodes = client.nodes.all if client
|
|
105
|
+
nodes&.sort_by(&:node) || []
|
|
106
|
+
end
|
|
107
|
+
|
|
85
108
|
def attach_compute_resource_id(virtual_machine)
|
|
86
109
|
return virtual_machine if virtual_machine.nil?
|
|
87
110
|
|
|
@@ -47,7 +47,7 @@ module ForemanFogProxmox
|
|
|
47
47
|
def update_volume_required?(old_volume_attributes, new_volume_attributes)
|
|
48
48
|
old_h = ForemanFogProxmox::HashCollection.new_hash_reject_empty_values(old_volume_attributes)
|
|
49
49
|
new_h = ForemanFogProxmox::HashCollection.new_hash_reject_empty_values(new_volume_attributes)
|
|
50
|
-
new_h = ForemanFogProxmox::HashCollection.new_hash_reject_keys(new_h, ['
|
|
50
|
+
new_h = ForemanFogProxmox::HashCollection.new_hash_reject_keys(new_h, ['cloudinit', 'storage_type'])
|
|
51
51
|
!ForemanFogProxmox::HashCollection.equals?(old_h.with_indifferent_access, new_h.with_indifferent_access)
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -112,6 +112,8 @@ module ForemanFogProxmox
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def volume_exists?(vm, volume_attributes)
|
|
115
|
+
return vm.config.disks.get('ide2').present? if volume_type?(volume_attributes, 'cdrom')
|
|
116
|
+
|
|
115
117
|
disk = vm.config.disks.get(volume_attributes['id'])
|
|
116
118
|
return false unless disk
|
|
117
119
|
|
|
@@ -128,14 +130,14 @@ module ForemanFogProxmox
|
|
|
128
130
|
end
|
|
129
131
|
|
|
130
132
|
def extract_id(vm, volume_attributes)
|
|
131
|
-
|
|
133
|
+
return 'ide2' if volume_type?(volume_attributes, 'cdrom')
|
|
134
|
+
|
|
132
135
|
if volume_exists?(vm, volume_attributes)
|
|
133
|
-
|
|
136
|
+
volume_attributes['id']
|
|
134
137
|
else
|
|
135
138
|
device = vm.container? ? 'mp' : volume_attributes['controller']
|
|
136
|
-
|
|
139
|
+
device + volume_attributes['device']
|
|
137
140
|
end
|
|
138
|
-
id
|
|
139
141
|
end
|
|
140
142
|
|
|
141
143
|
def add_volume(vm, id, volume_attributes)
|
|
@@ -145,7 +147,10 @@ module ForemanFogProxmox
|
|
|
145
147
|
disk_attributes[:storage] = volume_attributes['storage']
|
|
146
148
|
disk_attributes[:size] = volume_attributes['size']
|
|
147
149
|
elsif volume_type?(volume_attributes, 'cdrom')
|
|
148
|
-
|
|
150
|
+
cdrom_attributes = parse_server_cdrom(volume_attributes)
|
|
151
|
+
return if cdrom_attributes.empty?
|
|
152
|
+
|
|
153
|
+
disk_attributes.merge!(cdrom_attributes)
|
|
149
154
|
elsif volume_type?(volume_attributes, 'cloud_init')
|
|
150
155
|
disk_attributes[:storage] = volume_attributes['storage']
|
|
151
156
|
disk_attributes[:volid] = "#{volume_attributes['storage']}:cloudinit"
|
|
@@ -164,7 +169,11 @@ module ForemanFogProxmox
|
|
|
164
169
|
else
|
|
165
170
|
disk = vm.config.disks.get(id)
|
|
166
171
|
normalized_volume_attributes = normalize_existing_volume_attributes(disk, volume_attributes)
|
|
167
|
-
|
|
172
|
+
if volume_type?(volume_attributes, 'cdrom')
|
|
173
|
+
update_volume(vm, disk, normalized_volume_attributes)
|
|
174
|
+
elsif update_volume_required?(disk.attributes, normalized_volume_attributes)
|
|
175
|
+
update_volume(vm, disk, normalized_volume_attributes)
|
|
176
|
+
end
|
|
168
177
|
end
|
|
169
178
|
else
|
|
170
179
|
add_volume(vm, id, volume_attributes)
|
|
@@ -15,12 +15,10 @@ GNU General Public License for more details.
|
|
|
15
15
|
You should have received a copy of the GNU General Public License
|
|
16
16
|
along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>. %>
|
|
17
17
|
|
|
18
|
-
<%= javascript_include_tag 'foreman_fog_proxmox/proxmox_compute_resource', "data-turbolinks-track" => true %>
|
|
19
|
-
|
|
20
18
|
<% user_token = f.object.auth_method == 'user_token' %>
|
|
21
19
|
<% access_ticket = f.object.auth_method == 'access_ticket' %>
|
|
22
|
-
<% ssl_verify_peer = f.object.ssl_verify_peer
|
|
23
|
-
<%= select_f f, :auth_method, proxmox_auth_methods_map, :id, :name, { }, :label_help => _("Click Test connection button before changing it"), :label => _('Authentication method'), :label_size => "col-md-2", :required => true, :onchange => 'authMethodSelected();' %>
|
|
20
|
+
<% ssl_verify_peer = f.object.ssl_verify_peer %>
|
|
21
|
+
<%= select_f f, :auth_method, proxmox_auth_methods_map, :id, :name, { }, :label_help => _("Click Test connection button before changing it"), :label => _('Authentication method'), :label_size => "col-md-2", :required => true, :onchange => 'tfm.computeResource.proxmox.authMethodSelected();' %>
|
|
24
22
|
<%= field_set_tag _("Common fields"), :id => "compute_ressource_common_field_set" do %>
|
|
25
23
|
<%= text_f f, :url, :help_block => _("e.g. https://127.0.0.1:8006/api2/json") %>
|
|
26
24
|
<%= text_f f, :user , :help_block => _("e.g. root@pam") %>
|
|
@@ -33,10 +31,13 @@ along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>. %>
|
|
|
33
31
|
<%= password_f f, :password, :keep_value => true, :unset => unset_password?, :required => access_ticket %>
|
|
34
32
|
<% end %>
|
|
35
33
|
<%= field_set_tag _("SSL fields"), :id => "compute_ressource_ssl_field_set" do %>
|
|
36
|
-
<%= checkbox_f f, :ssl_verify_peer, :label => _("SSL verify peer"), :label_help => _("Click Test connection button before changing it"), :checked_value => '1', :onchange => 'sslVerifyPeerSelected();' %>
|
|
34
|
+
<%= checkbox_f f, :ssl_verify_peer, :label => _("SSL verify peer"), :label_help => _("Click Test connection button before changing it"), :checked_value => '1', :onchange => 'tfm.computeResource.proxmox.sslVerifyPeerSelected();' %>
|
|
37
35
|
<%= textarea_f f, :ssl_certs, :label => _("X509 Certification Authorities"), :size => "col-md-4",
|
|
36
|
+
:wrapper_class => "form-group #{'hide' unless ssl_verify_peer}",
|
|
38
37
|
:placeholder => _("Optionally provide a CA, or a correctly ordered CA chain. If left blank, disable ssl_verify_peer.") %>
|
|
39
38
|
<% end %>
|
|
39
|
+
<%= checkbox_f f, :caching_enabled, :label => _("Enable Caching"),
|
|
40
|
+
:help_inline => _("Cache Proxmox resources.") %>
|
|
40
41
|
<div class="col-md-offset-2">
|
|
41
42
|
<%= test_connection_button_f(f, (f.object.nodes rescue true)) %>
|
|
42
43
|
</div>
|
|
@@ -27,6 +27,10 @@ along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>. %>
|
|
|
27
27
|
<td><%= _("Authentication method") %></td>
|
|
28
28
|
<td><%= @compute_resource.auth_method %></td>
|
|
29
29
|
</tr>
|
|
30
|
+
<tr>
|
|
31
|
+
<td><%= _("Caching") %></td>
|
|
32
|
+
<td><%= @compute_resource.caching_enabled ? _("Enabled") : _("Disabled") %></td>
|
|
33
|
+
</tr>
|
|
30
34
|
<tr>
|
|
31
35
|
<td><%= _("User token expires") %></td>
|
|
32
36
|
<td><%= user_token_expiration_date(@compute_resource) %></td>
|
|
@@ -44,6 +44,7 @@ along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>. %>
|
|
|
44
44
|
props = {
|
|
45
45
|
vmAttrs: vm_attrs,
|
|
46
46
|
computeResourceId: compute_resource.id,
|
|
47
|
+
canAttachCdromImage: User.current.can?(:attach_cdrom_image, compute_resource),
|
|
47
48
|
propsLoaded: false,
|
|
48
49
|
fromProfile: from_profile,
|
|
49
50
|
newVm: new_vm,
|
|
@@ -69,6 +69,18 @@ module ForemanFogProxmox
|
|
|
69
69
|
assert_not show_response.empty?
|
|
70
70
|
end
|
|
71
71
|
test 'should get volumes by node and storage' do
|
|
72
|
+
mock_storage = mock('storage')
|
|
73
|
+
mock_storage.stubs(:volumes).returns([])
|
|
74
|
+
mock_storages = mock('storages')
|
|
75
|
+
mock_storages.stubs(:get).with('local').returns(mock_storage)
|
|
76
|
+
mock_node = mock('node')
|
|
77
|
+
mock_node.stubs(:storages).returns(mock_storages)
|
|
78
|
+
mock_nodes = mock('nodes')
|
|
79
|
+
mock_nodes.stubs(:get).with('proxmox').returns(mock_node)
|
|
80
|
+
mock_client = mock('client')
|
|
81
|
+
mock_client.stubs(:nodes).returns(mock_nodes)
|
|
82
|
+
@compute_resource.stubs(:client).returns(mock_client)
|
|
83
|
+
|
|
72
84
|
get :volumes_by_node_and_storage,
|
|
73
85
|
params: { :compute_resource_id => @compute_resource.id, :node_id => 'proxmox', :storage => 'local' },
|
|
74
86
|
session: set_session_user
|