foreman_fog_proxmox 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_fog_proxmox/proxmox_compute_resource.js +11 -1
  3. data/app/controllers/concerns/foreman_fog_proxmox/controller/parameters/compute_resource.rb +2 -1
  4. data/app/controllers/foreman_fog_proxmox/compute_resources_controller.rb +2 -1
  5. data/app/helpers/proxmox_form_helper.rb +5 -2
  6. data/app/helpers/proxmox_vm_attrs_helper.rb +1 -1
  7. data/app/helpers/proxmox_vm_cdrom_helper.rb +9 -2
  8. data/app/helpers/proxmox_vm_cloudinit_helper.rb +8 -2
  9. data/app/helpers/proxmox_vm_interfaces_helper.rb +11 -0
  10. data/app/helpers/proxmox_vm_volumes_helper.rb +14 -0
  11. data/app/models/concerns/orchestration/proxmox/compute.rb +8 -1
  12. data/app/models/foreman_fog_proxmox/compute_attributes_update_detector.rb +41 -0
  13. data/app/models/foreman_fog_proxmox/proxmox.rb +11 -0
  14. data/app/models/foreman_fog_proxmox/proxmox_compute_attributes.rb +8 -1
  15. data/app/models/foreman_fog_proxmox/proxmox_images.rb +33 -20
  16. data/app/models/foreman_fog_proxmox/proxmox_pools.rb +19 -2
  17. data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +4 -1
  18. data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +1 -1
  19. data/app/models/foreman_fog_proxmox/proxmox_vm_queries.rb +36 -13
  20. data/app/models/foreman_fog_proxmox/proxmox_volumes.rb +16 -7
  21. data/app/views/api/v2/compute_resources/proxmox.json.rabl +1 -1
  22. data/app/views/compute_resources/form/_proxmox.html.erb +6 -5
  23. data/app/views/compute_resources/show/_proxmox.html.erb +4 -0
  24. data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +1 -0
  25. data/lib/foreman_fog_proxmox/engine.rb +2 -0
  26. data/lib/foreman_fog_proxmox/version.rb +1 -1
  27. data/test/functional/compute_resources_controller_test.rb +12 -0
  28. data/test/unit/foreman_fog_proxmox/helpers/proxmox_form_helper_test.rb +51 -0
  29. data/test/unit/foreman_fog_proxmox/helpers/proxmox_server_helper_test.rb +10 -0
  30. data/test/unit/foreman_fog_proxmox/proxmox_compute_attributes_test.rb +7 -0
  31. data/test/unit/foreman_fog_proxmox/proxmox_orchestration_compute_test.rb +34 -0
  32. data/test/unit/foreman_fog_proxmox/proxmox_test.rb +102 -0
  33. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_create_test.rb +31 -0
  34. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_update_cdrom_test.rb +51 -1
  35. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_update_test.rb +4 -4
  36. data/test/unit/foreman_fog_proxmox/proxmox_vm_queries_test.rb +61 -2
  37. data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +32 -11
  38. data/webpack/components/ProxmoxServer/components/CDRom.js +49 -9
  39. data/webpack/components/ProxmoxVmType.js +4 -0
  40. data/webpack/global_index.js +2 -0
  41. metadata +6 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a437699869ae38137e94761791e2a01d741e9dd36f924c286bfaa9afc139f9e7
4
- data.tar.gz: 5698645c6f2a3a3278959989e5214357263f3ba7c0091830d1f41205033582ea
3
+ metadata.gz: 494d9e13c53b6e6e1a4821d8c046d145060bac1058f9285763661bd935e5a4ef
4
+ data.tar.gz: ba0639aa5659316381788213a3d6049ae98a8238a1612dd1dc8db7eed583d11f
5
5
  SHA512:
6
- metadata.gz: 1d29712272ab785a33e4bdc70f345b0da96ff22d020820bbefc27c14302c24c1207d198d6ec742562a4d30a84083b1e081b43bb6a855c432e5a67ce60ca8a5b9
7
- data.tar.gz: 9f3c39bbeca9d7364f4ee92cef02cc6fc7c398692cbc12f03a7c581324a9c21ca65ef1af33fcc892b1da02e3378f3f7c214fad2fb79bded7cfb028d55f5e5827
6
+ metadata.gz: 4d7e5fc3d3bff0e58256217b5f69910dc03d0f08662dcc2ef1af35e242e5115f4e8023c565afa99cd83821dea73018d66b235354106766a4dbed351266c165ce
7
+ data.tar.gz: 6ec6c8942c3e58cc7b3a05be5f2c0131bc47a0fb3cb9a4948d46cea8b51610ee33b7b6f0cbd8a08eac66818e7e83a2f0f51e3dca2ce8d0a9c051092d84c21c81
@@ -23,11 +23,14 @@ $(document).ready(function () {
23
23
  function sslVerifyPeerSelected() {
24
24
  var selected = $("#compute_resource_ssl_verify_peer").is(':checked');
25
25
  var ssl_certs_block = $('#compute_resource_ssl_certs').parents('.clearfix');
26
+ var ssl_certs_group = $('#compute_resource_ssl_certs').parents('.form-group');
26
27
  var ssl_certs_textarea = $('#compute_resource_ssl_certs');
27
28
  if (selected) {
29
+ ssl_certs_group.removeClass('hide');
28
30
  ssl_certs_block.show();
29
31
  ssl_certs_textarea.show();
30
32
  } else {
33
+ ssl_certs_group.addClass('hide');
31
34
  ssl_certs_block.hide();
32
35
  ssl_certs_textarea.text('');
33
36
  ssl_certs_textarea.hide();
@@ -62,4 +65,11 @@ function authMethodSelected() {
62
65
  authMethods().forEach(function(method){
63
66
  toggleFieldset(method, selected);
64
67
  });
65
- }
68
+ }
69
+
70
+ window.tfm = window.tfm || {};
71
+ window.tfm.computeResource = window.tfm.computeResource || {};
72
+ window.tfm.computeResource.proxmox = {
73
+ authMethodSelected: authMethodSelected,
74
+ sslVerifyPeerSelected: sslVerifyPeerSelected,
75
+ };
@@ -27,7 +27,8 @@ module ForemanFogProxmox
27
27
  def compute_resource_params_filter
28
28
  super.tap do |filter|
29
29
  filter.permit :ssl_verify_peer,
30
- :ssl_certs, :disable_proxy, :auth_method, :token_id, :token
30
+ :ssl_certs, :disable_proxy, :auth_method, :token_id, :token,
31
+ :caching_enabled
31
32
  end
32
33
  end
33
34
 
@@ -84,7 +84,8 @@ module ForemanFogProxmox
84
84
  node_id = params[:node_id]
85
85
  storage = params[:storage]
86
86
 
87
- 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
@@ -130,8 +130,11 @@ module ProxmoxFormHelper
130
130
  def proxmox_valid_interface_compute_attributes?(compute_attributes, vm_type)
131
131
  return false unless compute_attributes.respond_to?(:keys)
132
132
 
133
- valid_keys = interface_compute_attributes_typed_keys(vm_type)
134
- compute_attributes.keys.map(&:to_s).all? { |key| valid_keys.include?(key) }
133
+ compute_attribute_keys = compute_attributes.keys.map(&:to_s)
134
+ required_keys = interface_compute_attributes_required_typed_keys(vm_type)
135
+ missing_keys = required_keys - compute_attribute_keys
136
+
137
+ required_keys.any? && missing_keys.empty?
135
138
  end
136
139
 
137
140
  def extract_attr(attrs, key)
@@ -76,7 +76,7 @@ module ProxmoxVMAttrsHelper
76
76
  keys = ['id', 'volid', 'storage_type', 'storage', 'controller', 'device', 'cache', 'size', '_delete', 'backup']
77
77
  type = 'hard_disk'
78
78
  elsif vol.cdrom?
79
- keys = ['id', 'storage_type', 'cdrom', 'storage', 'volid']
79
+ keys = ['id', 'storage_type', 'cdrom', 'storage', 'volid', '_delete']
80
80
  type = 'cdrom'
81
81
  elsif vol.cloud_init?
82
82
  keys = ['id', 'volid', 'storage_type', 'storage', 'controller', 'device']
@@ -26,8 +26,15 @@ require 'foreman_fog_proxmox/hash_collection'
26
26
  module ProxmoxVMCdromHelper
27
27
  def parse_server_cdrom(args)
28
28
  cdrom_media = args['cdrom'] if args.key?('cdrom')
29
- 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
 
@@ -55,6 +55,17 @@ module ProxmoxVMInterfacesHelper
55
55
  keys
56
56
  end
57
57
 
58
+ def interface_compute_attributes_required_typed_keys(type)
59
+ case type
60
+ when 'qemu'
61
+ ['model']
62
+ when 'lxc'
63
+ ['name']
64
+ else
65
+ []
66
+ end
67
+ end
68
+
58
69
  def interface_common_typed_keys(type)
59
70
  ['id', (type == 'qemu') ? 'macaddr' : 'hwaddr']
60
71
  end
@@ -86,15 +86,29 @@ module ProxmoxVMVolumesHelper
86
86
  if args.key?('storage_type')
87
87
  args['storage_type'] == type
88
88
  else
89
+ return true if type == 'cdrom' && args.key?('cdrom')
90
+
89
91
  Fog::Proxmox::DiskHelper.cloud_init?(args['volid']) if type == 'cloud_init'
90
92
  Fog::Proxmox::DiskHelper.cdrom?(args['volid']) if type == 'cdrom'
91
93
  Fog::Proxmox::DiskHelper.disk?(args['id']) if ['hard_disk', 'rootfs', 'mp'].include?(type)
92
94
  end
93
95
  end
94
96
 
97
+ def validate_cdrom_image_permission!(volume_attributes)
98
+ volume_attributes = volume_attributes.with_indifferent_access
99
+
100
+ return unless volume_type?(volume_attributes, 'cdrom')
101
+ return unless volume_attributes[:cdrom].to_s == 'image'
102
+ return if User.current&.can?(:attach_cdrom_image, self)
103
+
104
+ raise ::Foreman::Exception,
105
+ _('You are not authorized to attach or change CD-ROM ISO images.')
106
+ end
107
+
95
108
  def parse_typed_volume(args, type)
96
109
  return if Foreman::Cast.to_bool(args['_delete'])
97
110
  logger.debug("parse_typed_volume(#{type}): args=#{args}")
111
+ validate_cdrom_image_permission!(args)
98
112
  disk = parse_hard_disk_volume(args) if volume_type?(args,
99
113
  'hard_disk') || volume_type?(args, 'mp') || volume_type?(args, 'rootfs')
100
114
  disk = parse_server_cloudinit(args) if volume_type?(args, 'cloud_init')
@@ -63,7 +63,14 @@ module Orchestration
63
63
 
64
64
  def computeValue(foreman_attr, fog_attr)
65
65
  value = ''
66
- 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
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2026
4
+
5
+ # This file is part of ForemanFogProxmox.
6
+
7
+ # ForemanFogProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # ForemanFogProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module ForemanFogProxmox
21
+ module ComputeAttributesUpdateDetector
22
+ def update_required?(old_attrs, new_attrs)
23
+ old_attrs = old_attrs.deep_symbolize_keys
24
+ new_attrs = new_attrs.deep_symbolize_keys
25
+ new_attrs.each do |key, new_v|
26
+ old_v = old_attrs[key]
27
+ if new_v.is_a?(Hash)
28
+ unless old_v.is_a?(Hash)
29
+ logger.debug "Scheduling compute instance update because #{key} changed it's value from '#{old_v}' (#{old_v.class}) to '#{new_v}' (#{new_v.class})"
30
+ return true
31
+ end
32
+ return true if update_required?(old_v, new_v)
33
+ elsif old_v.to_s != new_v.to_s
34
+ logger.debug "Scheduling compute instance update because #{key} changed it's value from '#{old_v}' (#{old_v.class}) to '#{new_v}' (#{new_v.class})"
35
+ return true
36
+ end
37
+ end
38
+ false
39
+ end
40
+ end
41
+ end
@@ -35,6 +35,9 @@ module ForemanFogProxmox
35
35
  include ProxmoxOperatingSystems
36
36
  include ProxmoxVersion
37
37
  include ProxmoxConsole
38
+ include ComputeAttributesUpdateDetector
39
+ include ComputeResourceCaching
40
+
38
41
  validates :url, :format => { :with => URI::DEFAULT_PARSER.make_regexp }, :presence => true
39
42
  validates :auth_method, :presence => true, :inclusion => { in: ['access_ticket', 'user_token'],
40
43
  message: ->(value) do format('%<value>s is not a valid authentication method', { value: value }) end }
@@ -128,6 +131,14 @@ module ForemanFogProxmox
128
131
 
129
132
  private
130
133
 
134
+ def structs_from_cache(items)
135
+ Array(items).map { |item| OpenStruct.new(item) }
136
+ end
137
+
138
+ def extract_attributes(resource, fields)
139
+ slice_vm_attributes(fields.index_with { |field| resource.try(field) }, fields)
140
+ end
141
+
131
142
  def fog_credentials
132
143
  hash = {
133
144
  proxmox_url: url,
@@ -64,8 +64,15 @@ module ForemanFogProxmox
64
64
  vm_attrs
65
65
  end
66
66
 
67
+ def cdrom_compute_attributes(attrs)
68
+ return unless attrs[:storage_type].to_s == 'cdrom' || attrs[:media].to_s == 'cdrom'
69
+
70
+ { cdrom: attrs[:volid].to_s }
71
+ end
72
+
67
73
  def volume_compute_attributes(volume_attributes)
68
- volume_attributes.merge(_delete: '0')
74
+ attrs = volume_attributes.merge(_delete: '0')
75
+ cdrom_compute_attributes(attrs) || attrs
69
76
  end
70
77
 
71
78
  def vm_compute_attributes(vm)
@@ -24,11 +24,18 @@ module ForemanFogProxmox
24
24
  end
25
25
 
26
26
  def images_by_storage(node_id, storage_id, type = 'iso')
27
- 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
@@ -15,12 +15,10 @@ GNU General Public License for more details.
15
15
  You should have received a copy of the GNU General Public License
16
16
  along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>. %>
17
17
 
18
- <%= javascript_include_tag 'foreman_fog_proxmox/proxmox_compute_resource', "data-turbolinks-track" => true %>
19
-
20
18
  <% user_token = f.object.auth_method == 'user_token' %>
21
19
  <% access_ticket = f.object.auth_method == 'access_ticket' %>
22
- <% ssl_verify_peer = f.object.ssl_verify_peer == '1' %>
23
- <%= select_f f, :auth_method, proxmox_auth_methods_map, :id, :name, { }, :label_help => _("Click Test connection button before changing it"), :label => _('Authentication method'), :label_size => "col-md-2", :required => true, :onchange => 'authMethodSelected();' %>
20
+ <% ssl_verify_peer = f.object.ssl_verify_peer %>
21
+ <%= select_f f, :auth_method, proxmox_auth_methods_map, :id, :name, { }, :label_help => _("Click Test connection button before changing it"), :label => _('Authentication method'), :label_size => "col-md-2", :required => true, :onchange => 'tfm.computeResource.proxmox.authMethodSelected();' %>
24
22
  <%= field_set_tag _("Common fields"), :id => "compute_ressource_common_field_set" do %>
25
23
  <%= text_f f, :url, :help_block => _("e.g. https://127.0.0.1:8006/api2/json") %>
26
24
  <%= text_f f, :user , :help_block => _("e.g. root@pam") %>
@@ -33,10 +31,13 @@ along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>. %>
33
31
  <%= password_f f, :password, :keep_value => true, :unset => unset_password?, :required => access_ticket %>
34
32
  <% end %>
35
33
  <%= field_set_tag _("SSL fields"), :id => "compute_ressource_ssl_field_set" do %>
36
- <%= checkbox_f f, :ssl_verify_peer, :label => _("SSL verify peer"), :label_help => _("Click Test connection button before changing it"), :checked_value => '1', :onchange => 'sslVerifyPeerSelected();' %>
34
+ <%= checkbox_f f, :ssl_verify_peer, :label => _("SSL verify peer"), :label_help => _("Click Test connection button before changing it"), :checked_value => '1', :onchange => 'tfm.computeResource.proxmox.sslVerifyPeerSelected();' %>
37
35
  <%= textarea_f f, :ssl_certs, :label => _("X509 Certification Authorities"), :size => "col-md-4",
36
+ :wrapper_class => "form-group #{'hide' unless ssl_verify_peer}",
38
37
  :placeholder => _("Optionally provide a CA, or a correctly ordered CA chain. If left blank, disable ssl_verify_peer.") %>
39
38
  <% end %>
39
+ <%= checkbox_f f, :caching_enabled, :label => _("Enable Caching"),
40
+ :help_inline => _("Cache Proxmox resources.") %>
40
41
  <div class="col-md-offset-2">
41
42
  <%= test_connection_button_f(f, (f.object.nodes rescue true)) %>
42
43
  </div>
@@ -27,6 +27,10 @@ along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>. %>
27
27
  <td><%= _("Authentication method") %></td>
28
28
  <td><%= @compute_resource.auth_method %></td>
29
29
  </tr>
30
+ <tr>
31
+ <td><%= _("Caching") %></td>
32
+ <td><%= @compute_resource.caching_enabled ? _("Enabled") : _("Disabled") %></td>
33
+ </tr>
30
34
  <tr>
31
35
  <td><%= _("User token expires") %></td>
32
36
  <td><%= user_token_expiration_date(@compute_resource) %></td>
@@ -44,6 +44,7 @@ along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>. %>
44
44
  props = {
45
45
  vmAttrs: vm_attrs,
46
46
  computeResourceId: compute_resource.id,
47
+ canAttachCdromImage: User.current.can?(:attach_cdrom_image, compute_resource),
47
48
  propsLoaded: false,
48
49
  fromProfile: from_profile,
49
50
  newVm: new_vm,
@@ -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.0'
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