foreman_fog_proxmox 0.23.1 → 0.25.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 (27) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/proxmox_vm_attrs_helper.rb +1 -1
  3. data/app/helpers/proxmox_vm_cdrom_helper.rb +9 -2
  4. data/app/helpers/proxmox_vm_cloudinit_helper.rb +8 -2
  5. data/app/helpers/proxmox_vm_volumes_helper.rb +14 -0
  6. data/app/models/concerns/orchestration/proxmox/compute.rb +8 -1
  7. data/app/models/foreman_fog_proxmox/proxmox_compute_attributes.rb +8 -1
  8. data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +13 -5
  9. data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +11 -0
  10. data/app/models/foreman_fog_proxmox/proxmox_volumes.rb +16 -7
  11. data/app/views/compute_resources/show/_proxmox.html.erb +4 -0
  12. data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +1 -0
  13. data/lib/foreman_fog_proxmox/engine.rb +15 -10
  14. data/lib/foreman_fog_proxmox/version.rb +1 -1
  15. data/test/unit/foreman_fog_proxmox/helpers/proxmox_server_helper_test.rb +10 -0
  16. data/test/unit/foreman_fog_proxmox/proxmox_compute_attributes_test.rb +7 -0
  17. data/test/unit/foreman_fog_proxmox/proxmox_orchestration_compute_test.rb +34 -0
  18. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_create_test.rb +41 -7
  19. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_update_cdrom_test.rb +51 -1
  20. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_test.rb +25 -0
  21. data/test/unit/foreman_fog_proxmox/proxmox_vm_new_test.rb +46 -0
  22. data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +32 -11
  23. data/webpack/components/ProxmoxServer/components/CDRom.js +49 -9
  24. data/webpack/components/ProxmoxStoragesUtils.js +6 -2
  25. data/webpack/components/ProxmoxVmType.js +4 -0
  26. data/webpack/global_test_setup.js +2 -1
  27. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f08732412c42423f30a2f448a7bc48c6249bbf0a62050b28e43a56f4a774caac
4
- data.tar.gz: ffecc9919a3f2c64f200b272a7e33065f43584f6734be25c74f794585beed665
3
+ metadata.gz: 329a340fdfd2b5e117a05a227477e29108df61d303a5ce5a5a2dc148ae41ec7c
4
+ data.tar.gz: '08b9aa3dca59fb2b7a80c2f1e6b0b59c9931f0d974e8d9849a720d6d37a819be'
5
5
  SHA512:
6
- metadata.gz: d54a0db2ef661feeffb82d8002f7466bd1a8eb1d3b0b3d11215928b07600fbe591d9ca5cffeb36ff2d293e82523509406b949c8d81a8c2ab80ebdcf83bf95295
7
- data.tar.gz: '0740284790c3189d29502bae8359fbf57faace0e1d27bc0e625b37ff17d0d93b31dfbd213f803e5ff2afcacb3726630cfb7176aa2d8c753ac0d31f7f0836393a'
6
+ metadata.gz: '094a19a8592b2ed3ad7a88c637c7859f3bbc631a4f08f2fb781ec21a8b9620789fb701dbab917973ee122fb2ead5a44d6af12a28fe6dd2d99dad2267c8d051a7'
7
+ data.tar.gz: ce956eaac9a5faf92f2decd8721910dd72fd9a0e7f0ccfbeab40462c1cfc7892897235e7d2fef925405abf993b85f902edac0f4afcb1ec685649d27df0e61168
@@ -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
@@ -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)
@@ -36,8 +36,6 @@ module ForemanFogProxmox
36
36
  type = args[:type]
37
37
  node = client.nodes.get(args[:node_id])
38
38
  vmid = args[:vmid] = assign_vmid(args[:vmid].to_i, node)
39
- raise ::Foreman::Exception, format(N_('invalid vmid=%<vmid>s'), vmid: vmid) unless node.servers.id_valid?(vmid)
40
-
41
39
  image_id = args[:image_id]
42
40
  remove_volume_keys(args)
43
41
  if image_id
@@ -55,8 +53,15 @@ module ForemanFogProxmox
55
53
  raise e
56
54
  end
57
55
 
58
- def assign_vmid(vmid, node)
59
- (vmid < 1) ? node.servers.next_id : vmid
56
+ def assign_vmid(vmid, node, log: true)
57
+ vmid = node.servers.next_id if vmid < 1
58
+
59
+ unless node.servers.id_valid?(vmid)
60
+ logger.warn("Requested VMID #{vmid} is occupied or out of range, requesting a new VMID") if log
61
+ vmid = node.servers.next_id
62
+ end
63
+
64
+ vmid
60
65
  end
61
66
 
62
67
  def compute_clone_attributes(args, container, type)
@@ -113,7 +118,10 @@ module ForemanFogProxmox
113
118
 
114
119
  volumes_attributes = new_attributes['volumes_attributes']
115
120
  logger.debug("save_vm(#{uuid}) volumes_attributes=#{volumes_attributes}")
116
- volumes_attributes&.each_value { |volume_attributes| save_volume(vm, volume_attributes) }
121
+ volumes_attributes&.each_value do |volume_attributes|
122
+ validate_cdrom_image_permission!(volume_attributes)
123
+ save_volume(vm, volume_attributes)
124
+ end
117
125
 
118
126
  efidisk_attributes = new_attributes['efidisk_attributes']
119
127
  if vm.config.efidisk.present? && efidisk_attributes.empty?
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/ModuleLength
4
+
3
5
  # Copyright 2019 Tristan Robert
4
6
 
5
7
  # This file is part of ForemanFogProxmox.
@@ -182,10 +184,17 @@ module ForemanFogProxmox
182
184
  config_attributes.delete_if { |key, _value| ['disks', 'interfaces'].include?(key) }
183
185
  end
184
186
 
187
+ def assign_available_vmid(new_attr, node)
188
+ return if new_attr['vmid'].blank? || new_attr['node_id'].blank?
189
+
190
+ new_attr['vmid'] = assign_vmid(new_attr['vmid'].to_i, node, log: false)
191
+ end
192
+
185
193
  def new_typed_vm(new_attr, type)
186
194
  convert_config_attributes(new_attr) if new_attr.key?(:config_attributes)
187
195
  node_id = new_attr['node_id']
188
196
  node = node_id ? client.nodes.get(node_id) : default_node
197
+ assign_available_vmid(new_attr, node)
189
198
  new_attr_type = new_attr['type']
190
199
  new_attr_type ||= new_attr['config_attributes']['type'] if new_attr.key?('config_attributes')
191
200
  new_attr_type ||= type
@@ -201,3 +210,5 @@ module ForemanFogProxmox
201
210
  end
202
211
  end
203
212
  end
213
+
214
+ # rubocop:enable Metrics/ModuleLength
@@ -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)
@@ -19,6 +19,10 @@ along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>. %>
19
19
  <td><%= _("URL") %></td>
20
20
  <td><%= @compute_resource.url %></td>
21
21
  </tr>
22
+ <tr>
23
+ <td><%= _("Username") %></td>
24
+ <td><%= @compute_resource.user %></td>
25
+ </tr>
22
26
  <tr>
23
27
  <td><%= _("Version") %></td>
24
28
  <td><%= @compute_resource.version %></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,
@@ -43,17 +43,22 @@ module ForemanFogProxmox
43
43
  parameter_filter(ComputeResource, :uuid)
44
44
  # add dashboard widget
45
45
  widget 'foreman_fog_proxmox_widget', name: N_('Foreman Fog Proxmox widget'), sizex: 8, sizey: 1
46
+
47
+ p = Foreman::AccessControl.permission(:view_compute_resources)
48
+ p.actions.concat(%w[foreman_fog_proxmox/compute_resources/ostemplates_by_id_and_node_and_storage
49
+ foreman_fog_proxmox/compute_resources/isos_by_id_and_node_and_storage
50
+ foreman_fog_proxmox/compute_resources/ostemplates_by_id_and_node
51
+ foreman_fog_proxmox/compute_resources/isos_by_id_and_node
52
+ foreman_fog_proxmox/compute_resources/storages_by_id_and_node
53
+ foreman_fog_proxmox/compute_resources/iso_storages_by_id_and_node
54
+ foreman_fog_proxmox/compute_resources/bridges_by_id_and_node
55
+ foreman_fog_proxmox/compute_resources/volumes_by_node_and_storage
56
+ foreman_fog_proxmox/compute_resources/metadata])
57
+ p.actions.uniq!
58
+
46
59
  security_block :foreman_fog_proxmox do
47
- permission :view_compute_resources, { :'foreman_fog_proxmox/compute_resources' =>
48
- [:ostemplates_by_id_and_node_and_storage,
49
- :isos_by_id_and_node_and_storage,
50
- :ostemplates_by_id_and_node,
51
- :isos_by_id_and_node,
52
- :storages_by_id_and_node,
53
- :iso_storages_by_id_and_node,
54
- :bridges_by_id_and_node,
55
- :volumes_by_node_and_storage,
56
- :metadata] }
60
+ permission :attach_cdrom_image, {},
61
+ :resource_type => 'ComputeResource'
57
62
  end
58
63
  end
59
64
  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.25.0'
22
22
  end
@@ -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
@@ -31,15 +31,18 @@ module ForemanFogProxmox
31
31
  include ProxmoxVMHelper
32
32
 
33
33
  describe 'create_vm' do
34
- it 'raises Foreman::Exception when vmid <= 100 and vmid > 0' do
35
- args = { vmid: '100' }
34
+ it 'uses next vmid when requested vmid is occupied or out of range' do
35
+ args = { vmid: '100', type: 'qemu', node_id: 'proxmox', start_after_create: '0' }
36
36
  servers = mock('servers')
37
- servers.stubs(:id_valid?).returns(false)
37
+ servers.expects(:id_valid?).with(100).returns(false)
38
+ servers.expects(:next_id).returns('101')
38
39
  cr = mock_node_servers(ForemanFogProxmox::Proxmox.new, servers)
39
- err = assert_raises Foreman::Exception do
40
- cr.create_vm(args)
41
- end
42
- assert err.message.end_with?('invalid vmid=100')
40
+ cr.stubs(:parse_typed_vm).with(args, 'qemu').returns(args)
41
+ vm = mock('vm')
42
+ servers.expects(:create).with(args).returns(vm)
43
+ cr.create_vm(args)
44
+
45
+ assert_equal '101', args[:vmid]
43
46
  end
44
47
 
45
48
  it 'computes next vmid when vmid == 0 and creates server' do
@@ -107,6 +110,37 @@ module ForemanFogProxmox
107
110
  vm.expects(:update).with(expected_args)
108
111
  cr.create_vm(args)
109
112
  end
113
+
114
+ it 'attaches generated cloud-init ISO from a later ISO storage' do
115
+ cr = ForemanFogProxmox::Proxmox.new
116
+ first_storage = mock('first_storage')
117
+ second_storage = mock('second_storage')
118
+ volume = mock('volume')
119
+
120
+ first_storage.stubs(:volumes).returns([])
121
+ volume.stubs(:volid).returns('local:iso/name_cloudinit.iso')
122
+ second_storage.stubs(:volumes).returns([volume])
123
+ cr.stubs(:storages).with('proxmox', 'iso').returns([first_storage, second_storage])
124
+
125
+ assert_equal({ ide2: 'local:iso/name_cloudinit.iso,media=cdrom' },
126
+ cr.attach_cloudinit_iso('proxmox', '/var/lib/vz/template/iso/name_cloudinit.iso'))
127
+ end
128
+
129
+ it 'raises Foreman::Exception when generated cloud-init ISO is not on any ISO storage' do
130
+ cr = ForemanFogProxmox::Proxmox.new
131
+ storage = mock('storage')
132
+ other_volume = mock('other_volume')
133
+
134
+ other_volume.stubs(:volid).returns('local:iso/other.iso')
135
+ storage.stubs(:volumes).returns([other_volume])
136
+ cr.stubs(:storages).with('proxmox', 'iso').returns([storage])
137
+
138
+ err = assert_raises Foreman::Exception do
139
+ cr.attach_cloudinit_iso('proxmox', '/var/lib/vz/template/iso/name_cloudinit.iso')
140
+ end
141
+
142
+ assert err.message.end_with?('Could not find generated cloud-init ISO name_cloudinit.iso on any ISO storage for node proxmox')
143
+ end
110
144
  end
111
145
  end
112
146
  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')
@@ -39,5 +39,30 @@ module ForemanFogProxmox
39
39
  assert cr.destroy_vm('abc')
40
40
  end
41
41
  end
42
+
43
+ describe 'assign_vmid' do
44
+ it 'uses next vmid when requested vmid is already occupied' do
45
+ cr = ForemanFogProxmox::Proxmox.new
46
+ servers = mock('servers')
47
+ node = mock('node')
48
+ node.stubs(:servers).returns(servers)
49
+ servers.expects(:id_valid?).with(100).returns(false)
50
+ servers.expects(:next_id).returns('101')
51
+
52
+ assert_equal '101', cr.assign_vmid(100, node)
53
+ end
54
+
55
+ it 'does not log when logging is disabled' do
56
+ cr = ForemanFogProxmox::Proxmox.new
57
+ servers = mock('servers')
58
+ node = mock('node')
59
+ node.stubs(:servers).returns(servers)
60
+ servers.expects(:id_valid?).with(100).returns(false)
61
+ servers.expects(:next_id).returns('101')
62
+ cr.logger.expects(:warn).never
63
+
64
+ assert_equal '101', cr.assign_vmid(100, node, log: false)
65
+ end
66
+ end
42
67
  end
43
68
  end
@@ -111,5 +111,51 @@ module ForemanFogProxmox
111
111
  assert_equal vm, @cr.new_vm(attr)
112
112
  end
113
113
  end
114
+
115
+ describe 'assign_available_vmid' do
116
+ before do
117
+ @cr = FactoryBot.build_stubbed(:proxmox_cr)
118
+ end
119
+
120
+ it 'uses next vmid when requested vmid is already occupied' do
121
+ attr = { 'vmid' => '100', 'node_id' => 'proxmox' }.with_indifferent_access
122
+ servers = mock('servers')
123
+ node = mock('node')
124
+ node.stubs(:servers).returns(servers)
125
+ servers.expects(:id_valid?).with(100).returns(false)
126
+ servers.expects(:next_id).returns('101')
127
+ @cr.logger.expects(:warn).never
128
+
129
+ @cr.assign_available_vmid(attr, node)
130
+
131
+ assert_equal '101', attr['vmid']
132
+ end
133
+
134
+ it 'keeps requested vmid when it is available' do
135
+ attr = { 'vmid' => '100', 'node_id' => 'proxmox' }.with_indifferent_access
136
+ servers = mock('servers')
137
+ node = mock('node')
138
+ node.stubs(:servers).returns(servers)
139
+ servers.expects(:id_valid?).with(100).returns(true)
140
+ servers.expects(:next_id).never
141
+
142
+ @cr.assign_available_vmid(attr, node)
143
+
144
+ assert_equal 100, attr['vmid']
145
+ end
146
+
147
+ it 'does not assign vmid when requested vmid is blank' do
148
+ attr = { 'vmid' => '', 'node_id' => 'proxmox' }.with_indifferent_access
149
+ servers = mock('servers')
150
+ node = mock('node')
151
+ node.stubs(:servers).returns(servers)
152
+ servers.expects(:id_valid?).never
153
+ servers.expects(:next_id).never
154
+
155
+ @cr.assign_available_vmid(attr, node)
156
+
157
+ assert_equal '', attr['vmid']
158
+ end
159
+ end
114
160
  end
115
161
  end
@@ -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;
@@ -1,8 +1,12 @@
1
1
  import { sprintf, translate as __ } from 'foremanReact/common/I18n';
2
2
 
3
+ // Unit selection uses a 1024 step (binary); the value is scaled by 1000 (decimal).
4
+ const BYTE_UNIT_STEP = 1024;
5
+ const BYTE_UNIT_DIVISOR = 1000;
6
+
3
7
  const humanSize = size => {
4
- const i = Math.floor(Math.log(size) / Math.log(1024));
5
- return `${(size / 1000 ** i).toFixed(2) * 1} ${
8
+ const i = Math.floor(Math.log(size) / Math.log(BYTE_UNIT_STEP));
9
+ return `${(size / BYTE_UNIT_DIVISOR ** i).toFixed(2) * 1} ${
6
10
  ['B', 'kB', 'MB', 'GB', 'TB'][i]
7
11
  }`;
8
12
  };
@@ -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
 
@@ -8,4 +8,5 @@ global.console.error = (error, stack) => {
8
8
  };
9
9
 
10
10
  // Increase jest timeout as some tests using multiple http mocks can time out on CI systems.
11
- jest.setTimeout(10000);
11
+ const JEST_TIMEOUT_MS = 10000;
12
+ jest.setTimeout(JEST_TIMEOUT_MS);
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.25.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
@@ -324,7 +325,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
324
325
  - !ruby/object:Gem::Version
325
326
  version: '0'
326
327
  requirements: []
327
- rubygems_version: 4.0.10
328
+ rubygems_version: 4.0.16
328
329
  specification_version: 4
329
330
  summary: Foreman plugin that adds Proxmox VE compute resource using fog-proxmox
330
331
  test_files:
@@ -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