foreman_fog_proxmox 0.23.1 → 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/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_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_volumes_helper.rb +14 -0
- data/app/models/concerns/orchestration/proxmox/compute.rb +8 -1
- data/app/models/foreman_fog_proxmox/proxmox.rb +10 -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 +2 -0
- 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_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 +27 -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
- metadata +3 -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
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -36,6 +36,8 @@ module ForemanFogProxmox
|
|
|
36
36
|
include ProxmoxVersion
|
|
37
37
|
include ProxmoxConsole
|
|
38
38
|
include ComputeAttributesUpdateDetector
|
|
39
|
+
include ComputeResourceCaching
|
|
40
|
+
|
|
39
41
|
validates :url, :format => { :with => URI::DEFAULT_PARSER.make_regexp }, :presence => true
|
|
40
42
|
validates :auth_method, :presence => true, :inclusion => { in: ['access_ticket', 'user_token'],
|
|
41
43
|
message: ->(value) do format('%<value>s is not a valid authentication method', { value: value }) end }
|
|
@@ -129,6 +131,14 @@ module ForemanFogProxmox
|
|
|
129
131
|
|
|
130
132
|
private
|
|
131
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
|
+
|
|
132
142
|
def fog_credentials
|
|
133
143
|
hash = {
|
|
134
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)
|
|
@@ -36,6 +36,8 @@ along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>. %>
|
|
|
36
36
|
:wrapper_class => "form-group #{'hide' unless ssl_verify_peer}",
|
|
37
37
|
:placeholder => _("Optionally provide a CA, or a correctly ordered CA chain. If left blank, disable ssl_verify_peer.") %>
|
|
38
38
|
<% end %>
|
|
39
|
+
<%= checkbox_f f, :caching_enabled, :label => _("Enable Caching"),
|
|
40
|
+
:help_inline => _("Cache Proxmox resources.") %>
|
|
39
41
|
<div class="col-md-offset-2">
|
|
40
42
|
<%= test_connection_button_f(f, (f.object.nodes rescue true)) %>
|
|
41
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
|
|
@@ -107,6 +107,16 @@ module ForemanFogProxmox
|
|
|
107
107
|
assert_equal 'cdrom', cdrom[:media]
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
+
test '#cdrom physical' do
|
|
111
|
+
cdrom = parse_server_cdrom('cdrom' => 'physical')
|
|
112
|
+
assert cdrom.key?(:id)
|
|
113
|
+
assert_equal 'ide2', cdrom[:id]
|
|
114
|
+
assert cdrom.key?(:volid)
|
|
115
|
+
assert_equal 'cdrom', cdrom[:volid]
|
|
116
|
+
assert cdrom.key?(:media)
|
|
117
|
+
assert_equal 'cdrom', cdrom[:media]
|
|
118
|
+
end
|
|
119
|
+
|
|
110
120
|
test '#vm' do
|
|
111
121
|
expected_vm = ActiveSupport::HashWithIndifferentAccess.new(
|
|
112
122
|
{
|
|
@@ -150,6 +150,13 @@ module ForemanFogProxmox
|
|
|
150
150
|
assert_not vm_attrs[:compute_attributes].key?(:macaddr)
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
+
it 'normalizes cdrom volume attributes to form shape' do
|
|
154
|
+
assert_equal({ cdrom: 'none' }, @cr.volume_compute_attributes({ storage_type: 'cdrom', volid: 'none', media: 'cdrom' }))
|
|
155
|
+
assert_equal({ cdrom: 'cdrom' }, @cr.volume_compute_attributes({ storage_type: 'cdrom', volid: 'cdrom', media: 'cdrom' }))
|
|
156
|
+
assert_equal({ cdrom: 'local-lvm:iso/debian-netinst.iso' },
|
|
157
|
+
@cr.volume_compute_attributes({ storage_type: 'cdrom', volid: 'local-lvm:iso/debian-netinst.iso', media: 'cdrom' }))
|
|
158
|
+
end
|
|
159
|
+
|
|
153
160
|
it 'converts a server to hash' do
|
|
154
161
|
vm, config_attributes, volume_attributes, interface_attributes = mock_server_vm
|
|
155
162
|
vm_attrs = @cr.vm_compute_attributes(vm)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_plugin_helper'
|
|
4
|
+
|
|
5
|
+
module ForemanFogProxmox
|
|
6
|
+
class ProxmoxOrchestrationComputeTest < ActiveSupport::TestCase
|
|
7
|
+
class ComputeValueHost
|
|
8
|
+
include Orchestration::Proxmox::Compute
|
|
9
|
+
|
|
10
|
+
attr_accessor :compute_resource, :vm
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
test '#computeValue prefixes raw vmid for uuid' do
|
|
14
|
+
host = host_with_vm_value('113')
|
|
15
|
+
|
|
16
|
+
assert_equal '4_113', host.computeValue(:uuid, :foreman_uuid)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test '#computeValue does not prefix existing proxmox uuid' do
|
|
20
|
+
host = host_with_vm_value('4_113')
|
|
21
|
+
|
|
22
|
+
assert_equal '4_113', host.computeValue(:uuid, :foreman_uuid)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def host_with_vm_value(value)
|
|
28
|
+
host = ComputeValueHost.new
|
|
29
|
+
host.compute_resource = OpenStruct.new(id: 4)
|
|
30
|
+
host.vm = OpenStruct.new(foreman_uuid: value)
|
|
31
|
+
host
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -108,6 +108,12 @@ module ForemanFogProxmox
|
|
|
108
108
|
assert cr.update_required?(old_attrs, new_attrs)
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
+
test 'supports compute resource cache refreshing' do
|
|
112
|
+
cr = FactoryBot.build_stubbed(:proxmox_cr)
|
|
113
|
+
|
|
114
|
+
assert_respond_to cr, :refresh_cache
|
|
115
|
+
end
|
|
116
|
+
|
|
111
117
|
test '#node' do
|
|
112
118
|
node = mock('node')
|
|
113
119
|
cr = FactoryBot.build_stubbed(:proxmox_cr)
|
|
@@ -115,6 +121,27 @@ module ForemanFogProxmox
|
|
|
115
121
|
assert_equal node, (as_admin { cr.node })
|
|
116
122
|
end
|
|
117
123
|
|
|
124
|
+
test '#extract_attributes returns selected resource attributes' do
|
|
125
|
+
cr = FactoryBot.build_stubbed(:proxmox_cr)
|
|
126
|
+
|
|
127
|
+
resource = OpenStruct.new(
|
|
128
|
+
vmid: 101,
|
|
129
|
+
name: 'template-101',
|
|
130
|
+
ignored: 'ignored'
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
attributes = cr.send(:extract_attributes, resource, [:vmid, :name, :missing])
|
|
134
|
+
|
|
135
|
+
assert_equal(
|
|
136
|
+
{
|
|
137
|
+
vmid: 101,
|
|
138
|
+
name: 'template-101',
|
|
139
|
+
missing: nil,
|
|
140
|
+
},
|
|
141
|
+
attributes
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
|
|
118
145
|
private
|
|
119
146
|
|
|
120
147
|
def hdd_compute_attrs(volumes_attrs)
|
|
@@ -107,6 +107,37 @@ module ForemanFogProxmox
|
|
|
107
107
|
vm.expects(:update).with(expected_args)
|
|
108
108
|
cr.create_vm(args)
|
|
109
109
|
end
|
|
110
|
+
|
|
111
|
+
it 'attaches generated cloud-init ISO from a later ISO storage' do
|
|
112
|
+
cr = ForemanFogProxmox::Proxmox.new
|
|
113
|
+
first_storage = mock('first_storage')
|
|
114
|
+
second_storage = mock('second_storage')
|
|
115
|
+
volume = mock('volume')
|
|
116
|
+
|
|
117
|
+
first_storage.stubs(:volumes).returns([])
|
|
118
|
+
volume.stubs(:volid).returns('local:iso/name_cloudinit.iso')
|
|
119
|
+
second_storage.stubs(:volumes).returns([volume])
|
|
120
|
+
cr.stubs(:storages).with('proxmox', 'iso').returns([first_storage, second_storage])
|
|
121
|
+
|
|
122
|
+
assert_equal({ ide2: 'local:iso/name_cloudinit.iso,media=cdrom' },
|
|
123
|
+
cr.attach_cloudinit_iso('proxmox', '/var/lib/vz/template/iso/name_cloudinit.iso'))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'raises Foreman::Exception when generated cloud-init ISO is not on any ISO storage' do
|
|
127
|
+
cr = ForemanFogProxmox::Proxmox.new
|
|
128
|
+
storage = mock('storage')
|
|
129
|
+
other_volume = mock('other_volume')
|
|
130
|
+
|
|
131
|
+
other_volume.stubs(:volid).returns('local:iso/other.iso')
|
|
132
|
+
storage.stubs(:volumes).returns([other_volume])
|
|
133
|
+
cr.stubs(:storages).with('proxmox', 'iso').returns([storage])
|
|
134
|
+
|
|
135
|
+
err = assert_raises Foreman::Exception do
|
|
136
|
+
cr.attach_cloudinit_iso('proxmox', '/var/lib/vz/template/iso/name_cloudinit.iso')
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
assert err.message.end_with?('Could not find generated cloud-init ISO name_cloudinit.iso on any ISO storage for node proxmox')
|
|
140
|
+
end
|
|
110
141
|
end
|
|
111
142
|
end
|
|
112
143
|
end
|
|
@@ -35,7 +35,8 @@ module ForemanFogProxmox
|
|
|
35
35
|
@cr = FactoryBot.build_stubbed(:proxmox_cr)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
it 'saves modified server config with added cdrom' do
|
|
38
|
+
it 'saves modified server config with added cdrom image when user has permission' do
|
|
39
|
+
User.current = users(:admin)
|
|
39
40
|
uuid = '100'
|
|
40
41
|
config = mock('config')
|
|
41
42
|
disks = mock('disks')
|
|
@@ -58,6 +59,7 @@ module ForemanFogProxmox
|
|
|
58
59
|
vm.stubs(:type).returns('qemu')
|
|
59
60
|
vm.stubs(:node_id).returns('proxmox')
|
|
60
61
|
@cr.stubs(:find_vm_by_uuid).returns(vm)
|
|
62
|
+
User.current.stubs(:can?).with(:attach_cdrom_image, @cr).returns(true)
|
|
61
63
|
new_attributes = {
|
|
62
64
|
'templated' => '0',
|
|
63
65
|
'node_id' => 'proxmox',
|
|
@@ -88,6 +90,54 @@ module ForemanFogProxmox
|
|
|
88
90
|
@cr.save_vm(uuid, new_attributes)
|
|
89
91
|
end
|
|
90
92
|
|
|
93
|
+
it 'rejects added cdrom image when user has no permission' do
|
|
94
|
+
User.current = users(:admin)
|
|
95
|
+
uuid = '100'
|
|
96
|
+
config = mock('config')
|
|
97
|
+
disks = mock('disks')
|
|
98
|
+
disks.stubs(:get).returns
|
|
99
|
+
config.stubs(:disks).returns(disks)
|
|
100
|
+
config.stubs(:efidisk).returns(nil)
|
|
101
|
+
config.stubs(:attributes).returns(:cores => '')
|
|
102
|
+
vm = mock('vm')
|
|
103
|
+
vm.stubs(:identity).returns(uuid)
|
|
104
|
+
vm.stubs(:attributes).returns('' => '')
|
|
105
|
+
vm.stubs(:config).returns(config)
|
|
106
|
+
vm.stubs(:container?).returns(false)
|
|
107
|
+
vm.stubs(:type).returns('qemu')
|
|
108
|
+
vm.stubs(:node_id).returns('proxmox')
|
|
109
|
+
@cr.stubs(:find_vm_by_uuid).returns(vm)
|
|
110
|
+
User.current.stubs(:can?).with(:attach_cdrom_image, @cr).returns(false)
|
|
111
|
+
new_attributes = {
|
|
112
|
+
'templated' => '0',
|
|
113
|
+
'node_id' => 'proxmox',
|
|
114
|
+
'config_attributes' => {
|
|
115
|
+
'cores' => '1',
|
|
116
|
+
'cpulimit' => '1',
|
|
117
|
+
},
|
|
118
|
+
'volumes_attributes' => {
|
|
119
|
+
'0' => {
|
|
120
|
+
'id' => 'ide2',
|
|
121
|
+
'_delete' => '',
|
|
122
|
+
'device' => '2',
|
|
123
|
+
'controller' => 'ide',
|
|
124
|
+
'storage_type' => 'cdrom',
|
|
125
|
+
'storage' => 'local-lvm',
|
|
126
|
+
'cdrom' => 'image',
|
|
127
|
+
'volid' => 'local-lvm:iso/ubuntu-20_4.iso',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
}.with_indifferent_access
|
|
131
|
+
@cr.stubs(:parse_server_vm).returns('vmid' => '100', 'node_id' => 'proxmox', 'type' => 'qemu', 'cores' => '1',
|
|
132
|
+
'cpulimit' => '1', 'onboot' => '0')
|
|
133
|
+
vm.expects(:attach).never
|
|
134
|
+
vm.expects(:update).never
|
|
135
|
+
|
|
136
|
+
assert_raises(::Foreman::Exception) do
|
|
137
|
+
@cr.save_vm(uuid, new_attributes)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
91
141
|
it 'saves modified server config with removed cdrom' do
|
|
92
142
|
uuid = '100'
|
|
93
143
|
config = mock('config')
|
|
@@ -149,11 +149,11 @@ module ForemanFogProxmox
|
|
|
149
149
|
pools = mock('pools')
|
|
150
150
|
pool1 = mock('pool1')
|
|
151
151
|
pool1.stubs(:poolid).returns('pool1')
|
|
152
|
-
pool1.stubs(:
|
|
152
|
+
pool1.stubs(:members).returns([{ 'vmid' => '100' }])
|
|
153
153
|
pool1.expects(:remove_server).with(uuid)
|
|
154
154
|
pool2 = mock('pool2')
|
|
155
155
|
pool2.stubs(:poolid).returns('pool2')
|
|
156
|
-
pool2.stubs(:
|
|
156
|
+
pool2.stubs(:members).returns([])
|
|
157
157
|
pool2.expects(:add_server).with(uuid)
|
|
158
158
|
pools.stubs(:all).returns([pool1, pool2])
|
|
159
159
|
pools.expects(:get).with('pool1').returns(pool1)
|
|
@@ -185,7 +185,7 @@ module ForemanFogProxmox
|
|
|
185
185
|
identity_client = mock('identity_client')
|
|
186
186
|
pools = mock('pools')
|
|
187
187
|
pool1 = mock('pool1')
|
|
188
|
-
pool1.stubs(:
|
|
188
|
+
pool1.stubs(:members).returns([{ 'vmid' => '100' }])
|
|
189
189
|
pool1.stubs(:poolid).returns('pool1')
|
|
190
190
|
pool1.expects(:remove_server).with(uuid)
|
|
191
191
|
pools.stubs(:all).returns([pool1])
|
|
@@ -218,7 +218,7 @@ module ForemanFogProxmox
|
|
|
218
218
|
identity_client = mock('identity_client')
|
|
219
219
|
pools = mock('pools')
|
|
220
220
|
pool2 = mock('pool2')
|
|
221
|
-
pool2.stubs(:
|
|
221
|
+
pool2.stubs(:members).returns([])
|
|
222
222
|
pool2.stubs(:poolid).returns('pool2')
|
|
223
223
|
pool2.expects(:add_server).with(uuid)
|
|
224
224
|
pools.stubs(:all).returns([pool2])
|
|
@@ -33,6 +33,22 @@ module ForemanFogProxmox
|
|
|
33
33
|
include ProxmoxContainerMockFactory
|
|
34
34
|
include ProxmoxVMHelper
|
|
35
35
|
|
|
36
|
+
describe 'nodes' do
|
|
37
|
+
it 'caches nodes for persisted compute resources' do
|
|
38
|
+
cr = FactoryBot.build_stubbed(:proxmox_cr, :caching_enabled => true)
|
|
39
|
+
node_z = OpenStruct.new(node: 'z-proxmox')
|
|
40
|
+
node_a = OpenStruct.new(node: 'a-proxmox')
|
|
41
|
+
nodes = mock('nodes')
|
|
42
|
+
nodes.expects(:all).once.returns([node_z, node_a])
|
|
43
|
+
client = mock('client')
|
|
44
|
+
client.stubs(:nodes).returns(nodes)
|
|
45
|
+
cr.stubs(:client).returns(client)
|
|
46
|
+
|
|
47
|
+
assert_equal %w[a-proxmox z-proxmox], cr.nodes.map(&:node)
|
|
48
|
+
assert_equal %w[a-proxmox z-proxmox], cr.nodes.map(&:node)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
36
52
|
describe 'storages' do
|
|
37
53
|
before do
|
|
38
54
|
@cr = ForemanFogProxmox::Proxmox.new
|
|
@@ -50,7 +66,11 @@ module ForemanFogProxmox
|
|
|
50
66
|
active_storage = OpenStruct.new(storage: 'local', enabled: 1, active: 1)
|
|
51
67
|
@storages_collection.stubs(:list_by_content_type).with('images').returns([active_storage])
|
|
52
68
|
|
|
53
|
-
|
|
69
|
+
storage = @cr.storages('proxmox').first
|
|
70
|
+
assert_equal 'local', storage.storage
|
|
71
|
+
assert_equal 1, storage.enabled
|
|
72
|
+
assert_equal 1, storage.active
|
|
73
|
+
assert_equal 'local', storage.identity
|
|
54
74
|
end
|
|
55
75
|
|
|
56
76
|
it 'returns active storages sorted by storage name' do
|
|
@@ -67,7 +87,7 @@ module ForemanFogProxmox
|
|
|
67
87
|
|
|
68
88
|
@storages_collection.stubs(:list_by_content_type).with('images').returns([valid, invalid])
|
|
69
89
|
|
|
70
|
-
assert_equal [
|
|
90
|
+
assert_equal ['good'], @cr.storages('proxmox').map(&:storage)
|
|
71
91
|
end
|
|
72
92
|
|
|
73
93
|
it 'excludes disabled storages (enabled=0)' do
|
|
@@ -97,6 +117,45 @@ module ForemanFogProxmox
|
|
|
97
117
|
|
|
98
118
|
assert_empty @cr.storages('proxmox')
|
|
99
119
|
end
|
|
120
|
+
|
|
121
|
+
it 'caches storages by node and content type for persisted compute resources' do
|
|
122
|
+
cr = FactoryBot.build_stubbed(:proxmox_cr, :caching_enabled => true)
|
|
123
|
+
active_storage = OpenStruct.new(storage: 'local', enabled: 1, active: 1)
|
|
124
|
+
storages_collection = mock('storages_collection')
|
|
125
|
+
storages_collection.expects(:list_by_content_type).with('images').once.returns([active_storage])
|
|
126
|
+
node = mock('node')
|
|
127
|
+
node.stubs(:storages).returns(storages_collection)
|
|
128
|
+
nodes = mock('nodes')
|
|
129
|
+
nodes.stubs(:get).with('proxmox').returns(node)
|
|
130
|
+
client = mock('client')
|
|
131
|
+
client.stubs(:nodes).returns(nodes)
|
|
132
|
+
cr.stubs(:client).returns(client)
|
|
133
|
+
|
|
134
|
+
assert_equal [active_storage.storage], cr.storages('proxmox').map(&:storage)
|
|
135
|
+
assert_equal [active_storage.storage], cr.storages('proxmox').map(&:storage)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'restores cached storages with normalized attributes' do
|
|
139
|
+
cr = FactoryBot.build_stubbed(:proxmox_cr, :caching_enabled => true)
|
|
140
|
+
active_storage = OpenStruct.new(storage: 'local', enabled: 1, active: 1)
|
|
141
|
+
storages_collection = mock('storages_collection')
|
|
142
|
+
storages_collection.expects(:list_by_content_type).with('images').once.returns([active_storage])
|
|
143
|
+
node = mock('node')
|
|
144
|
+
node.stubs(:storages).returns(storages_collection)
|
|
145
|
+
nodes = mock('nodes')
|
|
146
|
+
nodes.stubs(:get).with('proxmox').returns(node)
|
|
147
|
+
client = mock('client')
|
|
148
|
+
client.stubs(:nodes).returns(nodes)
|
|
149
|
+
cr.stubs(:client).returns(client)
|
|
150
|
+
|
|
151
|
+
cr.storages('proxmox')
|
|
152
|
+
cached_storage = cr.storages('proxmox').first
|
|
153
|
+
|
|
154
|
+
assert_equal 'local', cached_storage.storage
|
|
155
|
+
assert_equal 'local', cached_storage.identity
|
|
156
|
+
assert_equal [:active, :avail, :content, :enabled, :identity, :node_id, :storage, :total, :used],
|
|
157
|
+
cached_storage.to_h.keys.sort
|
|
158
|
+
end
|
|
100
159
|
end
|
|
101
160
|
|
|
102
161
|
describe 'find_vm_by_uuid' do
|
|
@@ -33,6 +33,7 @@ const ProxmoxServerStorage = ({
|
|
|
33
33
|
isTabActive,
|
|
34
34
|
selectedImage,
|
|
35
35
|
provisionMethodState,
|
|
36
|
+
canAttachCdromImage,
|
|
36
37
|
}) => {
|
|
37
38
|
const bootDiskId = React.useMemo(() => {
|
|
38
39
|
if (!bootOrder) return null;
|
|
@@ -70,6 +71,8 @@ const ProxmoxServerStorage = ({
|
|
|
70
71
|
const [nextId, setNextId] = useState(0);
|
|
71
72
|
const [cdRom, setCdRom] = useState(false);
|
|
72
73
|
const [cdRomData, setCdRomData] = useState(null);
|
|
74
|
+
const [cdRomHidden, setCdRomHidden] = useState(false);
|
|
75
|
+
const [cdRomIsNew, setCdRomIsNew] = useState(false);
|
|
73
76
|
const [efiDisk, setEfiDisk] = useState(false);
|
|
74
77
|
const [efiDiskData, setEfiDiskData] = useState(null);
|
|
75
78
|
const [nextDeviceNumbers, setNextDeviceNumbers] = useState({
|
|
@@ -309,7 +312,7 @@ const ProxmoxServerStorage = ({
|
|
|
309
312
|
value: '',
|
|
310
313
|
},
|
|
311
314
|
storageType: {
|
|
312
|
-
name: `${paramScope}[volumes_attributes][${nextId}][
|
|
315
|
+
name: `${paramScope}[volumes_attributes][${nextId}][storage_type]`,
|
|
313
316
|
value: 'cdrom',
|
|
314
317
|
},
|
|
315
318
|
storage: {
|
|
@@ -318,17 +321,29 @@ const ProxmoxServerStorage = ({
|
|
|
318
321
|
},
|
|
319
322
|
cdrom: {
|
|
320
323
|
name: `${paramScope}[volumes_attributes][${nextId}][cdrom]`,
|
|
321
|
-
value: '',
|
|
324
|
+
value: 'none',
|
|
325
|
+
},
|
|
326
|
+
_delete: {
|
|
327
|
+
name: `${paramScope}[volumes_attributes][${nextId}][_delete]`,
|
|
322
328
|
},
|
|
323
329
|
};
|
|
324
330
|
|
|
325
331
|
setCdRom(true);
|
|
326
332
|
setCdRomData(initCDRom);
|
|
333
|
+
setCdRomHidden(false);
|
|
334
|
+
setCdRomIsNew(!isPreExisting);
|
|
327
335
|
},
|
|
328
336
|
[cdRom, nextId, paramScope, createUniqueDevice]
|
|
329
337
|
);
|
|
330
338
|
|
|
331
|
-
const removeCDRom = () =>
|
|
339
|
+
const removeCDRom = () => {
|
|
340
|
+
if (cdRomIsNew) {
|
|
341
|
+
setCdRom(false);
|
|
342
|
+
setCdRomData(null);
|
|
343
|
+
} else {
|
|
344
|
+
setCdRomHidden(true);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
332
347
|
|
|
333
348
|
const addEfiDisk = useCallback(
|
|
334
349
|
(event, initialData = null, isPreExisting = false) => {
|
|
@@ -432,14 +447,18 @@ const ProxmoxServerStorage = ({
|
|
|
432
447
|
</FormHelperText>
|
|
433
448
|
)}
|
|
434
449
|
{cdRom && cdRomData && (
|
|
435
|
-
<
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
450
|
+
<div style={{ display: cdRomHidden ? 'none' : 'block' }}>
|
|
451
|
+
<CDRom
|
|
452
|
+
onRemove={removeCDRom}
|
|
453
|
+
data={cdRomData}
|
|
454
|
+
storages={storages}
|
|
455
|
+
nodeId={nodeId}
|
|
456
|
+
computeResourceId={computeResourceId}
|
|
457
|
+
isTabActive={isTabActive}
|
|
458
|
+
canAttachCdromImage={canAttachCdromImage}
|
|
459
|
+
hidden={cdRomHidden}
|
|
460
|
+
/>
|
|
461
|
+
</div>
|
|
443
462
|
)}
|
|
444
463
|
{efiDisk && efiDiskData && (
|
|
445
464
|
<EFIDisk
|
|
@@ -537,6 +556,7 @@ ProxmoxServerStorage.propTypes = {
|
|
|
537
556
|
isTabActive: PropTypes.bool,
|
|
538
557
|
selectedImage: PropTypes.object,
|
|
539
558
|
provisionMethodState: PropTypes.string,
|
|
559
|
+
canAttachCdromImage: PropTypes.bool,
|
|
540
560
|
};
|
|
541
561
|
|
|
542
562
|
ProxmoxServerStorage.defaultProps = {
|
|
@@ -553,6 +573,7 @@ ProxmoxServerStorage.defaultProps = {
|
|
|
553
573
|
isTabActive: false,
|
|
554
574
|
selectedImage: null,
|
|
555
575
|
provisionMethodState: '',
|
|
576
|
+
canAttachCdromImage: false,
|
|
556
577
|
};
|
|
557
578
|
|
|
558
579
|
export default ProxmoxServerStorage;
|
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
PageSection,
|
|
7
7
|
Radio,
|
|
8
8
|
Spinner,
|
|
9
|
+
FormHelperText,
|
|
10
|
+
HelperText,
|
|
11
|
+
HelperTextItem,
|
|
9
12
|
} from '@patternfly/react-core';
|
|
10
13
|
import { TimesIcon } from '@patternfly/react-icons';
|
|
11
14
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
@@ -13,7 +16,15 @@ import { createStoragesMap } from '../../ProxmoxStoragesUtils';
|
|
|
13
16
|
import InputField from '../../common/FormInputs';
|
|
14
17
|
import useVolumes from '../../hooks/useVolumes';
|
|
15
18
|
|
|
16
|
-
const CDRom = ({
|
|
19
|
+
const CDRom = ({
|
|
20
|
+
onRemove,
|
|
21
|
+
data,
|
|
22
|
+
storages,
|
|
23
|
+
nodeId,
|
|
24
|
+
computeResourceId,
|
|
25
|
+
canAttachCdromImage,
|
|
26
|
+
hidden,
|
|
27
|
+
}) => {
|
|
17
28
|
const [cdrom, setCdrom] = useState(data);
|
|
18
29
|
|
|
19
30
|
const storagesMap = useMemo(
|
|
@@ -85,13 +96,20 @@ const CDRom = ({ onRemove, data, storages, nodeId, computeResourceId }) => {
|
|
|
85
96
|
const imagesMap = useMemo(
|
|
86
97
|
() => [
|
|
87
98
|
{ value: '', label: '' },
|
|
88
|
-
...volumes
|
|
99
|
+
...volumes
|
|
100
|
+
.filter(v => v.content === 'iso')
|
|
101
|
+
.map(v => ({ value: v.volid, label: v.volid })),
|
|
89
102
|
],
|
|
90
103
|
[volumes]
|
|
91
104
|
);
|
|
92
105
|
|
|
93
106
|
return (
|
|
94
107
|
<div style={{ position: 'relative' }}>
|
|
108
|
+
<input
|
|
109
|
+
name={cdrom?._delete?.name}
|
|
110
|
+
type="hidden"
|
|
111
|
+
value={hidden ? '1' : '0'}
|
|
112
|
+
/>
|
|
95
113
|
<div
|
|
96
114
|
style={{
|
|
97
115
|
display: 'flex',
|
|
@@ -130,7 +148,7 @@ const CDRom = ({ onRemove, data, storages, nodeId, computeResourceId }) => {
|
|
|
130
148
|
name={cdrom?.cdrom?.name}
|
|
131
149
|
label={__('None')}
|
|
132
150
|
value="none"
|
|
133
|
-
isChecked={
|
|
151
|
+
isChecked={mediaValue === 'none'}
|
|
134
152
|
onChange={(e, _) => handleMediaChange(_, e)}
|
|
135
153
|
/>
|
|
136
154
|
<Radio
|
|
@@ -138,8 +156,8 @@ const CDRom = ({ onRemove, data, storages, nodeId, computeResourceId }) => {
|
|
|
138
156
|
id="radio-physical"
|
|
139
157
|
name={cdrom?.cdrom?.name}
|
|
140
158
|
label={__('Physical')}
|
|
141
|
-
value="
|
|
142
|
-
isChecked={
|
|
159
|
+
value="cdrom"
|
|
160
|
+
isChecked={mediaValue === 'cdrom'}
|
|
143
161
|
onChange={(e, _) => handleMediaChange(_, e)}
|
|
144
162
|
/>
|
|
145
163
|
<Radio
|
|
@@ -148,12 +166,25 @@ const CDRom = ({ onRemove, data, storages, nodeId, computeResourceId }) => {
|
|
|
148
166
|
name={cdrom?.cdrom?.name}
|
|
149
167
|
label={__('Image')}
|
|
150
168
|
value="image"
|
|
151
|
-
isChecked={
|
|
169
|
+
isChecked={mediaValue === 'image'}
|
|
152
170
|
onChange={(e, _) => handleMediaChange(_, e)}
|
|
171
|
+
isDisabled={!canAttachCdromImage}
|
|
153
172
|
/>
|
|
154
173
|
</div>
|
|
155
174
|
|
|
156
|
-
{
|
|
175
|
+
{!canAttachCdromImage && (
|
|
176
|
+
<FormHelperText>
|
|
177
|
+
<HelperText id="helper-cdrom-image-permission">
|
|
178
|
+
<HelperTextItem variant="warning">
|
|
179
|
+
{__(
|
|
180
|
+
'You are not authorized to attach or change CD-ROM ISO images'
|
|
181
|
+
)}
|
|
182
|
+
</HelperTextItem>
|
|
183
|
+
</HelperText>
|
|
184
|
+
</FormHelperText>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
{mediaValue === 'image' && (
|
|
157
188
|
<PageSection padding={{ default: 'noPadding' }}>
|
|
158
189
|
<Title ouiaId="proxmox-server-cdrom-image-title" headingLevel="h5">
|
|
159
190
|
{__('Image')}
|
|
@@ -170,6 +201,7 @@ const CDRom = ({ onRemove, data, storages, nodeId, computeResourceId }) => {
|
|
|
170
201
|
}
|
|
171
202
|
options={storagesMap}
|
|
172
203
|
onChange={handleChange}
|
|
204
|
+
disabled={!canAttachCdromImage}
|
|
173
205
|
/>
|
|
174
206
|
|
|
175
207
|
{loadingVolumes ? (
|
|
@@ -191,6 +223,7 @@ const CDRom = ({ onRemove, data, storages, nodeId, computeResourceId }) => {
|
|
|
191
223
|
value={cdrom?.volid?.value}
|
|
192
224
|
options={imagesMap}
|
|
193
225
|
onChange={handleChange}
|
|
226
|
+
disabled={!canAttachCdromImage}
|
|
194
227
|
error={
|
|
195
228
|
volumeError
|
|
196
229
|
? __('Failed fetching images ISO please try again.')
|
|
@@ -198,8 +231,6 @@ const CDRom = ({ onRemove, data, storages, nodeId, computeResourceId }) => {
|
|
|
198
231
|
}
|
|
199
232
|
/>
|
|
200
233
|
)}
|
|
201
|
-
|
|
202
|
-
<input name={cdrom?.storageType?.name} type="hidden" value="cdrom" />
|
|
203
234
|
</PageSection>
|
|
204
235
|
)}
|
|
205
236
|
</div>
|
|
@@ -224,10 +255,19 @@ CDRom.propTypes = {
|
|
|
224
255
|
storageType: PropTypes.shape({
|
|
225
256
|
name: PropTypes.string.isRequired,
|
|
226
257
|
}).isRequired,
|
|
258
|
+
_delete: PropTypes.shape({
|
|
259
|
+
name: PropTypes.string.isRequired,
|
|
260
|
+
}),
|
|
227
261
|
}).isRequired,
|
|
228
262
|
storages: PropTypes.array.isRequired,
|
|
229
263
|
nodeId: PropTypes.string.isRequired,
|
|
230
264
|
computeResourceId: PropTypes.number.isRequired,
|
|
265
|
+
canAttachCdromImage: PropTypes.bool.isRequired,
|
|
266
|
+
hidden: PropTypes.bool,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
CDRom.defaultProps = {
|
|
270
|
+
hidden: false,
|
|
231
271
|
};
|
|
232
272
|
|
|
233
273
|
export default CDRom;
|
|
@@ -35,6 +35,7 @@ const ProxmoxVmType = ({
|
|
|
35
35
|
registerComp,
|
|
36
36
|
untemplatable,
|
|
37
37
|
computeResourceId,
|
|
38
|
+
canAttachCdromImage,
|
|
38
39
|
propsLoaded,
|
|
39
40
|
}) => {
|
|
40
41
|
const [activeTabKey, setActiveTabKey] = useState(0);
|
|
@@ -233,6 +234,7 @@ const ProxmoxVmType = ({
|
|
|
233
234
|
isLoading={!metaLoaded}
|
|
234
235
|
isTabActive={activeTabKey === 4}
|
|
235
236
|
computeResourceId={computeResourceId}
|
|
237
|
+
canAttachCdromImage={canAttachCdromImage}
|
|
236
238
|
selectedImage={selectedImage}
|
|
237
239
|
provisionMethodState={provisionMethodState}
|
|
238
240
|
/>
|
|
@@ -415,6 +417,7 @@ ProxmoxVmType.propTypes = {
|
|
|
415
417
|
registerComp: PropTypes.bool,
|
|
416
418
|
untemplatable: PropTypes.bool,
|
|
417
419
|
computeResourceId: PropTypes.number,
|
|
420
|
+
canAttachCdromImage: PropTypes.bool,
|
|
418
421
|
propsLoaded: PropTypes.bool,
|
|
419
422
|
};
|
|
420
423
|
|
|
@@ -426,6 +429,7 @@ ProxmoxVmType.defaultProps = {
|
|
|
426
429
|
registerComp: false,
|
|
427
430
|
untemplatable: false,
|
|
428
431
|
computeResourceId: null,
|
|
432
|
+
canAttachCdromImage: false,
|
|
429
433
|
propsLoaded: false,
|
|
430
434
|
};
|
|
431
435
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman_fog_proxmox
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tristan Robert
|
|
@@ -264,6 +264,7 @@ files:
|
|
|
264
264
|
- test/unit/foreman_fog_proxmox/proxmox_images_test.rb
|
|
265
265
|
- test/unit/foreman_fog_proxmox/proxmox_interfaces_test.rb
|
|
266
266
|
- test/unit/foreman_fog_proxmox/proxmox_operating_systems_test.rb
|
|
267
|
+
- test/unit/foreman_fog_proxmox/proxmox_orchestration_compute_test.rb
|
|
267
268
|
- test/unit/foreman_fog_proxmox/proxmox_test.rb
|
|
268
269
|
- test/unit/foreman_fog_proxmox/proxmox_version_test.rb
|
|
269
270
|
- test/unit/foreman_fog_proxmox/proxmox_vm_commands_container_test.rb
|
|
@@ -344,6 +345,7 @@ test_files:
|
|
|
344
345
|
- test/unit/foreman_fog_proxmox/proxmox_images_test.rb
|
|
345
346
|
- test/unit/foreman_fog_proxmox/proxmox_interfaces_test.rb
|
|
346
347
|
- test/unit/foreman_fog_proxmox/proxmox_operating_systems_test.rb
|
|
348
|
+
- test/unit/foreman_fog_proxmox/proxmox_orchestration_compute_test.rb
|
|
347
349
|
- test/unit/foreman_fog_proxmox/proxmox_test.rb
|
|
348
350
|
- test/unit/foreman_fog_proxmox/proxmox_version_test.rb
|
|
349
351
|
- test/unit/foreman_fog_proxmox/proxmox_vm_commands_container_test.rb
|