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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/foreman_fog_proxmox/controller/parameters/compute_resource.rb +2 -1
  3. data/app/controllers/foreman_fog_proxmox/compute_resources_controller.rb +2 -1
  4. data/app/helpers/proxmox_vm_attrs_helper.rb +1 -1
  5. data/app/helpers/proxmox_vm_cdrom_helper.rb +9 -2
  6. data/app/helpers/proxmox_vm_cloudinit_helper.rb +8 -2
  7. data/app/helpers/proxmox_vm_volumes_helper.rb +14 -0
  8. data/app/models/concerns/orchestration/proxmox/compute.rb +8 -1
  9. data/app/models/foreman_fog_proxmox/proxmox.rb +10 -0
  10. data/app/models/foreman_fog_proxmox/proxmox_compute_attributes.rb +8 -1
  11. data/app/models/foreman_fog_proxmox/proxmox_images.rb +33 -20
  12. data/app/models/foreman_fog_proxmox/proxmox_pools.rb +19 -2
  13. data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +4 -1
  14. data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +1 -1
  15. data/app/models/foreman_fog_proxmox/proxmox_vm_queries.rb +36 -13
  16. data/app/models/foreman_fog_proxmox/proxmox_volumes.rb +16 -7
  17. data/app/views/api/v2/compute_resources/proxmox.json.rabl +1 -1
  18. data/app/views/compute_resources/form/_proxmox.html.erb +2 -0
  19. data/app/views/compute_resources/show/_proxmox.html.erb +4 -0
  20. data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +1 -0
  21. data/lib/foreman_fog_proxmox/engine.rb +2 -0
  22. data/lib/foreman_fog_proxmox/version.rb +1 -1
  23. data/test/functional/compute_resources_controller_test.rb +12 -0
  24. data/test/unit/foreman_fog_proxmox/helpers/proxmox_server_helper_test.rb +10 -0
  25. data/test/unit/foreman_fog_proxmox/proxmox_compute_attributes_test.rb +7 -0
  26. data/test/unit/foreman_fog_proxmox/proxmox_orchestration_compute_test.rb +34 -0
  27. data/test/unit/foreman_fog_proxmox/proxmox_test.rb +27 -0
  28. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_create_test.rb +31 -0
  29. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_update_cdrom_test.rb +51 -1
  30. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_update_test.rb +4 -4
  31. data/test/unit/foreman_fog_proxmox/proxmox_vm_queries_test.rb +61 -2
  32. data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +32 -11
  33. data/webpack/components/ProxmoxServer/components/CDRom.js +49 -9
  34. data/webpack/components/ProxmoxVmType.js +4 -0
  35. metadata +3 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f08732412c42423f30a2f448a7bc48c6249bbf0a62050b28e43a56f4a774caac
4
- data.tar.gz: ffecc9919a3f2c64f200b272a7e33065f43584f6734be25c74f794585beed665
3
+ metadata.gz: 494d9e13c53b6e6e1a4821d8c046d145060bac1058f9285763661bd935e5a4ef
4
+ data.tar.gz: ba0639aa5659316381788213a3d6049ae98a8238a1612dd1dc8db7eed583d11f
5
5
  SHA512:
6
- metadata.gz: d54a0db2ef661feeffb82d8002f7466bd1a8eb1d3b0b3d11215928b07600fbe591d9ca5cffeb36ff2d293e82523509406b949c8d81a8c2ab80ebdcf83bf95295
7
- data.tar.gz: '0740284790c3189d29502bae8359fbf57faace0e1d27bc0e625b37ff17d0d93b31dfbd213f803e5ff2afcacb3726630cfb7176aa2d8c753ac0d31f7f0836393a'
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
- vols = cr.storages(node_id).find { |s| s.storage == storage }&.volumes || []
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
- cdrom_image = args['volid'] if args.key?('volid')
30
- volid = cdrom_image.empty? ? cdrom_media : cdrom_image
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
- storage = storages(node, 'iso')[0]
115
- volume = storage.volumes.detect { |v| v.volid.include? File.basename(iso) }
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
- value += compute_resource.id.to_s + '_' if foreman_attr == :uuid
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
- node = client.nodes.get node_id
28
- node ||= 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
- storage.volumes.list_by_content_type(type).sort_by(&:volid) if storage
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
- volumes = []
49
- nodes.each do |node|
50
- storages(node.node).each do |storage|
51
- # Skip disabled storages (enabled == 0 or nil)
52
- unless storage.enabled.to_i == 1
53
- logger.warn("Skipping disabled storage #{storage.identity} on #{node.node}")
54
- next
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
- # Fetch QEMU and LXC template images
58
- volumes += storage.volumes.list_by_content_type('images')
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
- # for creating image, only list volumes which are templated
66
- volumes.select(&:template?)
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
- pools = identity_client.pools.all
24
- pools.sort_by(&:poolid)
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 { |volume_attributes| save_volume(vm, volume_attributes) }
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?
@@ -97,7 +97,7 @@ module ForemanFogProxmox
97
97
  end
98
98
 
99
99
  def default_node
100
- nodes.first
100
+ fog_nodes.first
101
101
  end
102
102
 
103
103
  def default_node_id
@@ -23,29 +23,47 @@ module ForemanFogProxmox
23
23
  include ProxmoxVMUuidHelper
24
24
 
25
25
  def nodes
26
- nodes = client.nodes.all if client
27
- nodes&.sort_by(&:node)
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
- node = client.nodes.get node_id
32
- node ||= default_node
33
- storages = node.storages.list_by_content_type type
34
- logger.debug("storages(): node_id #{node_id} type #{type}")
35
- storages.reject { |s| s.enabled.to_i.zero? || s.active.to_i.zero? }.sort_by(&:storage)
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
- node = network_client.nodes.get node_id
40
- node ||= network_client.nodes.first
41
- bridges = node.networks.all(type: 'any_bridge')
42
- bridges.sort_by(&:iface)
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
- nodes.each { |node| vms += node.servers.all + node.containers.all }
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
- nodes.each do |node|
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, ['cdrom', 'cloudinit', 'storage_type'])
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
- id = ''
133
+ return 'ide2' if volume_type?(volume_attributes, 'cdrom')
134
+
132
135
  if volume_exists?(vm, volume_attributes)
133
- id = volume_attributes['id']
136
+ volume_attributes['id']
134
137
  else
135
138
  device = vm.container? ? 'mp' : volume_attributes['controller']
136
- id = volume_type?(volume_attributes, 'cdrom') ? 'ide2' : device + volume_attributes['device']
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
- disk_attributes[:volid] = volume_attributes[:iso]
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
- update_volume(vm, disk, normalized_volume_attributes) if update_volume_required?(disk.attributes, normalized_volume_attributes)
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)
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- attributes :url, :user, :ssl_verify_peer, :ssl_certs, :renew
3
+ attributes :url, :user, :ssl_verify_peer, :ssl_certs, :renew, :caching_enabled
@@ -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,
@@ -54,6 +54,8 @@ module ForemanFogProxmox
54
54
  :bridges_by_id_and_node,
55
55
  :volumes_by_node_and_storage,
56
56
  :metadata] }
57
+ permission :attach_cdrom_image, {},
58
+ :resource_type => 'ComputeResource'
57
59
  end
58
60
  end
59
61
  end
@@ -18,5 +18,5 @@
18
18
  # along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
19
19
 
20
20
  module ForemanFogProxmox
21
- VERSION = '0.23.1'
21
+ VERSION = '0.24.0'
22
22
  end
@@ -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(:has_server?).with('100').returns(true)
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(:has_server?).with('100').returns(false)
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(:has_server?).with('100').returns(true)
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(:has_server?).with('100').returns(false)
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
- assert_equal [active_storage], @cr.storages('proxmox')
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 [valid], @cr.storages('proxmox')
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}][storageType]`,
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 = () => setCdRom(false);
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
- <CDRom
436
- onRemove={removeCDRom}
437
- data={cdRomData}
438
- storages={storages}
439
- nodeId={nodeId}
440
- computeResourceId={computeResourceId}
441
- isTabActive={isTabActive}
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 = ({ onRemove, data, storages, nodeId, computeResourceId }) => {
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.map(v => ({ value: v.volid, label: v.volid })),
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={cdrom?.cdrom?.value === 'none'}
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="physical"
142
- isChecked={cdrom?.cdrom?.value === 'physical'}
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={cdrom?.cdrom?.value === 'image'}
169
+ isChecked={mediaValue === 'image'}
152
170
  onChange={(e, _) => handleMediaChange(_, e)}
171
+ isDisabled={!canAttachCdromImage}
153
172
  />
154
173
  </div>
155
174
 
156
- {cdrom?.cdrom?.value === 'image' && (
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.23.1
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