foreman_ovirt 2.0.2 → 2.0.4

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -17
  3. data/app/assets/javascripts/foreman_ovirt/ovirt.js +37 -8
  4. data/app/controllers/concerns/foreman_ovirt/api_compute_resources_controller_extension.rb +42 -0
  5. data/app/controllers/concerns/foreman_ovirt/compute_resources_vms_controller.rb +3 -1
  6. data/app/controllers/concerns/foreman_ovirt/parameters_extension.rb +5 -5
  7. data/app/controllers/foreman_ovirt/concerns/compute_resources_controller_extensions.rb +12 -3
  8. data/app/helpers/ovirt_compute_resource_helper.rb +18 -4
  9. data/app/models/concerns/fog_extensions/ovirt/server.rb +13 -4
  10. data/app/models/concerns/fog_extensions/ovirt/template.rb +3 -1
  11. data/app/models/concerns/fog_extensions/ovirt/volume.rb +3 -1
  12. data/app/models/foreman_ovirt/ovirt.rb +167 -142
  13. data/app/views/api/v2/compute_resources/ovirt.json.rabl +2 -0
  14. data/config/routes.rb +8 -5
  15. data/db/migrate/20250810212811_update_legacy_ovirt_compute_resource_type.rb +5 -3
  16. data/lib/foreman_ovirt/engine.rb +8 -5
  17. data/lib/foreman_ovirt/version.rb +3 -1
  18. data/lib/foreman_ovirt.rb +2 -0
  19. data/lib/tasks/foreman_ovirt_tasks.rake +37 -22
  20. data/locale/gemspec.rb +3 -1
  21. data/package.json +1 -0
  22. data/test/controllers/api/v2/compute_resources_controller_test.rb +186 -0
  23. data/test/controllers/hosts_controller_test.rb +46 -0
  24. data/test/factories/foreman_ovirt_factories.rb +27 -2
  25. data/test/fixtures/ovirt_operating_systems.xml +243 -0
  26. data/test/graphql/types/provider_enum_test.rb +12 -0
  27. data/test/integration/compute_profile_js_test.rb +49 -0
  28. data/test/models/compute_resources/compute_resource_test_helpers.rb +143 -0
  29. data/test/models/compute_resources/ovirt_test.rb +687 -0
  30. data/test/models/ovirt_volume_test.rb +104 -0
  31. data/test/test_plugin_helper.rb +2 -0
  32. data/test/unit/compute_resource_host_importer_test.rb +48 -0
  33. data/test/unit/ovirt_connection_test.rb +123 -0
  34. data/webpack/components/__tests__/ovirt.test.js +17 -0
  35. data/webpack/components/extensions/HostDetails/DetailsTabCards/OvirtCard.js +40 -38
  36. data/webpack/global_index.js +0 -2
  37. data/webpack/global_test_setup.js +3 -3
  38. data/webpack/index.js +0 -1
  39. metadata +26 -8
  40. data/test/unit/foreman_ovirt_test.rb +0 -11
data/config/routes.rb CHANGED
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Rails.application.routes.draw do
2
- namespace :api, :defaults => { :format => 'json' } do
3
- scope "(:apiv)", :module => :v2, :defaults => { :apiv => 'v2' }, :apiv => /v1|v2/, :constraints => ApiConstraints.new(:version => 2, :default => true) do
4
- constraints(:id => /[^\/]+/) do
5
- resources :compute_resources, :except => [:new, :edit] do
6
- get :available_vnic_profiles, :on => :member
4
+ namespace :api, defaults: { format: 'json' } do
5
+ scope '(:apiv)', module: :v2, defaults: { apiv: 'v2' }, apiv: /v1|v2/,
6
+ constraints: ApiConstraints.new(version: 2, default: true) do
7
+ constraints(id: %r{[^/]+}) do
8
+ resources :compute_resources, except: %i[new edit] do
9
+ get :available_vnic_profiles, on: :member
7
10
  end
8
11
  end
9
12
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class UpdateLegacyOvirtComputeResourceType < ActiveRecord::Migration[7.0]
2
4
  # Temporary model definition to ensure the migration is self-contained
3
5
  # and independent of the application's model, which may change over time.
4
- class ComputeResource < ActiveRecord::Base
6
+ class ComputeResource < ApplicationRecord
5
7
  self.table_name = :compute_resources
6
8
  # Disable STI to allow direct manipulation of the 'type' column.
7
9
  self.inheritance_column = :_type_disabled
@@ -9,13 +11,13 @@ class UpdateLegacyOvirtComputeResourceType < ActiveRecord::Migration[7.0]
9
11
 
10
12
  # Migrate the legacy class name to the new plugin class name.
11
13
  def up
12
- say "Updating legacy oVirt compute resource types to ForemanOvirt::Ovirt"
14
+ say 'Updating legacy oVirt compute resource types to ForemanOvirt::Ovirt'
13
15
  ComputeResource.where(type: 'Foreman::Model::Ovirt').update_all(type: 'ForemanOvirt::Ovirt')
14
16
  end
15
17
 
16
18
  # Revert the class name back to the legacy value.
17
19
  def down
18
- say "Reverting oVirt compute resource types back to legacy Foreman::Model::Ovirt"
20
+ say 'Reverting oVirt compute resource types back to legacy Foreman::Model::Ovirt'
19
21
  ComputeResource.where(type: 'ForemanOvirt::Ovirt').update_all(type: 'Foreman::Model::Ovirt')
20
22
  end
21
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ForemanOvirt
2
4
  class Engine < ::Rails::Engine
3
5
  engine_name 'foreman_ovirt'
@@ -8,7 +10,7 @@ module ForemanOvirt
8
10
  end
9
11
  end
10
12
 
11
- initializer 'foreman_ovirt.register_plugin', :before => :finisher_hook do |app|
13
+ initializer 'foreman_ovirt.register_plugin', before: :finisher_hook do |app|
12
14
  app.reloader.to_prepare do
13
15
  Foreman::Plugin.register :foreman_ovirt do
14
16
  requires_foreman '>= 3.16'
@@ -17,10 +19,10 @@ module ForemanOvirt
17
19
 
18
20
  register_global_js_file 'global'
19
21
 
20
- security_block :foreman_ovirt do
21
- permission :view_compute_resources, { :'foreman_ovirt/compute_resources' =>
22
- [:available_vnic_profiles] }
23
- end
22
+ p = Foreman::AccessControl.permission(:view_compute_resources)
23
+ p.actions.concat(%w[foreman_ovirt/compute_resources/available_vnic_profiles
24
+ api/v2/compute_resources/available_vnic_profiles])
25
+ p.actions.uniq!
24
26
  end
25
27
  end
26
28
  end
@@ -57,6 +59,7 @@ module ForemanOvirt
57
59
 
58
60
  ::ComputeResourcesVmsController.include ForemanOvirt::ComputeResourcesVmsController
59
61
  ::ComputeResourcesController.include ForemanOvirt::ParametersExtension
62
+ ::Api::V2::ComputeResourcesController.include ForemanOvirt::ApiComputeResourcesControllerExtension
60
63
  rescue StandardError => e
61
64
  Rails.logger.warn "ForemanOvirt: skipping engine hook (#{e})"
62
65
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ForemanOvirt
2
- VERSION = '2.0.2'.freeze
4
+ VERSION = '2.0.4'
3
5
  end
data/lib/foreman_ovirt.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'foreman_ovirt/engine'
2
4
 
3
5
  module ForemanOvirt
@@ -1,30 +1,45 @@
1
- # Ensure the task is only defined when the main app is loaded.
2
- if defined?(Rails) && Rails.application
3
- namespace :foreman_ovirt do
4
- namespace :db do
5
- desc "Prevents the destructive core oVirt data migration from being run by marking it as complete."
6
- task :prevent_core_migration => :environment do
7
- # The version of the Foreman core migration to skip.
8
- version_to_skip = 20250414121956
9
- migration_context = ActiveRecord::Base.connection.migration_context
1
+ # frozen_string_literal: true
10
2
 
11
- all_migrations = migration_context.migrations.map(&:version)
12
- run_migrations = migration_context.get_all_versions
3
+ namespace :foreman_ovirt do
4
+ namespace :db do
5
+ desc 'Prevents destructive core oVirt data migration by marking it as complete'
6
+ task prevent_core_migration: :environment do
7
+ # The version of the Foreman core migration to skip.
8
+ version_to_skip = 20250414121956 # rubocop:disable Style/NumericLiterals
9
+ migration_context = ActiveRecord::Base.connection.migration_context
13
10
 
14
- # A migration needs to be run if it exists on disk but is not yet marked as run in the database.
15
- if all_migrations.include?(version_to_skip) && !run_migrations.include?(version_to_skip)
16
- Rails.logger.info "[foreman_ovirt] Marking core migration #{version_to_skip} (MigrateOvirtResources) as complete to prevent data loss."
17
- ActiveRecord::SchemaMigration.create!(version: version_to_skip.to_s)
18
- Rails.logger.info "[foreman_ovirt] Core migration successfully skipped."
19
- else
20
- Rails.logger.debug "[foreman_ovirt] Core migration #{version_to_skip} does not need to be skipped (already migrated or not found)."
21
- end
11
+ all_migrations = migration_context.migrations.map(&:version)
12
+ run_migrations = migration_context.get_all_versions
13
+
14
+ # A migration needs to be run if:
15
+ # - it's not a fresh (empty) database
16
+ # - the migration exists on disk but is not yet marked as run in the database
17
+ if migration_context.current_version != 0 && all_migrations.include?(version_to_skip) &&
18
+ !run_migrations.include?(version_to_skip)
19
+ Rails.logger.info "[foreman_ovirt] Marking core migration #{version_to_skip} as complete to prevent data loss."
20
+ ActiveRecord::SchemaMigration.create!(version: version_to_skip.to_s)
21
+ Rails.logger.info '[foreman_ovirt] Core migration successfully skipped.'
22
+ else
23
+ Rails.logger.debug "[foreman_ovirt] Core migration #{version_to_skip} already migrated or not found."
22
24
  end
23
25
  end
24
26
  end
27
+ end
25
28
 
26
- # Enhance the core db:migrate task to run our prevention task first.
27
- if Rake::Task.task_defined?('db:migrate')
28
- Rake::Task['db:migrate'].enhance(['foreman_ovirt:db:prevent_core_migration'])
29
+ # Enhance the core db:migrate task to run our prevention task first.
30
+ Rake::Task['db:migrate'].enhance(['foreman_ovirt:db:prevent_core_migration']) if Rake::Task.task_defined?('db:migrate')
31
+
32
+ # Tests
33
+ namespace :test do
34
+ desc 'Test Foreman Ovirt'
35
+ Rake::TestTask.new(:foreman_ovirt) do |t|
36
+ test_dir = File.expand_path('../../test', __dir__)
37
+ t.libs << 'test'
38
+ t.libs << test_dir
39
+ t.pattern = "#{test_dir}/**/*_test.rb"
40
+ t.verbose = true
41
+ t.warning = false
29
42
  end
30
43
  end
44
+
45
+ Rake::Task[:test].enhance ['test:foreman_ovirt'] if Rake::Task.task_defined?(:test)
data/locale/gemspec.rb CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Matches foreman_ovirt.gemspec
2
- _('ForemanOvirt plugin adds oVirt compute resource to Foreman using fog-ovirt.')
4
+ _('The Foreman oVirt plugin adds oVirt compute resource to Foreman using fog-ovirt.')
data/package.json CHANGED
@@ -26,6 +26,7 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@babel/core": "^7.7.0",
29
+ "@sheerun/mutationobserver-shim": "^0.3.3",
29
30
  "@theforeman/builder": ">= 15.0.1",
30
31
  "@theforeman/eslint-plugin-foreman": ">= 15.0.1",
31
32
  "@theforeman/find-foreman": ">= 15.0.1",
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+ require 'fog/ovirt/models/compute/quota'
5
+
6
+ module Api
7
+ module V2
8
+ # rubocop:disable Metrics/ClassLength
9
+ class OvirtComputeResourcesControllerTest < ActionController::TestCase
10
+ # rubocop:enable Metrics/ClassLength
11
+ tests Api::V2::ComputeResourcesController
12
+
13
+ def setup
14
+ Fog.mock!
15
+ end
16
+
17
+ def teardown
18
+ Fog.unmock!
19
+ end
20
+
21
+ context 'ovirt available resources' do
22
+ setup do
23
+ @ovirt_object = Object.new
24
+ @ovirt_object.stubs(:name).returns('test_ovirt_object')
25
+ @ovirt_object.stubs(:id).returns('my11-test35-uuid99')
26
+
27
+ quota = Fog::Ovirt::Compute::Quota.new(id: '1', name: 'Default')
28
+ client_mock = mock.tap { |m| m.stubs(datacenters: [], quotas: [quota], servers: []) }
29
+ ForemanOvirt::Ovirt.any_instance.stubs(:client).returns(client_mock)
30
+
31
+ @ovirt_cr = FactoryBot.create(:ovirt_cr)
32
+ end
33
+
34
+ teardown do
35
+ if @response.present? && @response.code.to_i.between?(200, 299)
36
+ available_objects = ActiveSupport::JSON.decode(@response.body)
37
+ assert_not_empty available_objects
38
+ end
39
+ end
40
+
41
+ test 'should get available virtual machines' do
42
+ ForemanOvirt::Ovirt.any_instance.stubs(:available_virtual_machines).returns([@ovirt_object])
43
+ get :available_virtual_machines, params: { id: @ovirt_cr.to_param }
44
+ end
45
+
46
+ test 'should get available networks' do
47
+ ForemanOvirt::Ovirt.any_instance.stubs(:available_networks).returns([@ovirt_object])
48
+ get :available_networks, params: { id: @ovirt_cr.to_param, cluster_id: '123-456-789' }
49
+ end
50
+
51
+ test 'should get available clusters' do
52
+ ForemanOvirt::Ovirt.any_instance.stubs(:available_clusters).returns([@ovirt_object])
53
+ get :available_clusters, params: { id: @ovirt_cr.to_param }
54
+ end
55
+
56
+ test 'should get available storage domains' do
57
+ ForemanOvirt::Ovirt.any_instance.stubs(:available_storage_domains).returns([@ovirt_object])
58
+ get :available_storage_domains, params: { id: @ovirt_cr.to_param }
59
+ end
60
+ end
61
+
62
+ context 'ovirt datacenters' do
63
+ setup do
64
+ quota = Fog::Ovirt::Compute::Quota.new(id: '1', name: 'Default')
65
+ client_mock = mock.tap { |m| m.stubs(datacenters: [], quotas: [quota], servers: []) }
66
+ ForemanOvirt::Ovirt.any_instance.stubs(:client).returns(client_mock)
67
+ end
68
+
69
+ test 'should create with datacenter name' do
70
+ datacenter_uuid = Foreman.uuid
71
+ ForemanOvirt::Ovirt.any_instance.stubs(:get_datacenter_uuid).returns(datacenter_uuid)
72
+ ForemanOvirt::Ovirt.any_instance.stubs(:test_connection).returns(true)
73
+
74
+ attrs = { name: 'Ovirt-create-test', url: 'https://myovirt/api', provider: 'ovirt',
75
+ datacenter: 'test', user: 'user@example.com', password: 'secret' }
76
+ post :create, params: { compute_resource: attrs }
77
+
78
+ assert_response :created
79
+ assert_equal datacenter_uuid, ActiveSupport::JSON.decode(@response.body)['datacenter']
80
+ end
81
+
82
+ test 'should create with datacenter uuid' do
83
+ datacenter_uuid = Foreman.uuid
84
+ # Implicitly tests that conversion is skipped
85
+ ForemanOvirt::Ovirt.any_instance.expects(:get_datacenter_uuid).never
86
+
87
+ attrs = { name: 'Ovirt-create-test', url: 'https://myovirt/api', provider: 'ovirt',
88
+ datacenter: datacenter_uuid, user: 'user@example.com', password: 'secret' }
89
+ post :create, params: { compute_resource: attrs }
90
+
91
+ assert_response :created
92
+ assert_equal datacenter_uuid, ActiveSupport::JSON.decode(@response.body)['datacenter']
93
+ end
94
+
95
+ test 'should update with datacenter name' do
96
+ datacenter_uuid = Foreman.uuid
97
+ compute_resource = FactoryBot.create(:ovirt_cr)
98
+
99
+ ForemanOvirt::Ovirt.any_instance.stubs(:get_datacenter_uuid).returns(datacenter_uuid)
100
+ ForemanOvirt::Ovirt.any_instance.stubs(:test_connection).returns(true)
101
+
102
+ attrs = { datacenter: 'test' }
103
+ put :update, params: { id: compute_resource.id, compute_resource: attrs }
104
+
105
+ assert_response :ok
106
+ assert_equal datacenter_uuid, ActiveSupport::JSON.decode(@response.body)['datacenter']
107
+ end
108
+
109
+ test 'should update with datacenter uuid' do
110
+ datacenter_uuid = Foreman.uuid
111
+ compute_resource = FactoryBot.create(:ovirt_cr)
112
+
113
+ # Implicitly tests that conversion is skipped
114
+ ForemanOvirt::Ovirt.any_instance.expects(:get_datacenter_uuid).never
115
+
116
+ attrs = { datacenter: datacenter_uuid }
117
+ put :update, params: { id: compute_resource.id, compute_resource: attrs }
118
+
119
+ assert_response :ok
120
+ assert_equal datacenter_uuid, ActiveSupport::JSON.decode(@response.body)['datacenter']
121
+ end
122
+
123
+ test 'should handle datacenter conversion failure' do
124
+ exception_msg = 'Datacenter not found or Auth failed'
125
+ ForemanOvirt::Ovirt.any_instance.stubs(:test_connection).raises(Foreman::Exception.new(exception_msg))
126
+
127
+ attrs = { name: 'Ovirt-rescue-test', url: 'https://myovirt/api', provider: 'ovirt',
128
+ datacenter: 'Failing-DC', user: 'user@example.com', password: 'secret' }
129
+ post :create, params: { compute_resource: attrs }
130
+
131
+ assert_response :unprocessable_entity
132
+ end
133
+
134
+ test 'should skip conversion safely if datacenter is completely omitted or blank' do
135
+ ForemanOvirt::Ovirt.any_instance.expects(:get_datacenter_uuid).never
136
+ attrs = { name: 'Ovirt-omitted-test', url: 'https://myovirt/api', provider: 'ovirt',
137
+ user: 'user@example.com', password: 'secret' }
138
+ post :create, params: { compute_resource: attrs }
139
+
140
+ assert_response :created
141
+ assert_nil ActiveSupport::JSON.decode(@response.body)['datacenter']
142
+ end
143
+
144
+ test 'should skip datacenter conversion if provider is not ovirt' do
145
+ ForemanOvirt::Ovirt.any_instance.expects(:get_datacenter_uuid).never
146
+
147
+ attrs = { name: 'Non-Ovirt-test', url: 'qemu:///system', provider: 'Libvirt', datacenter: 'leave-me-alone' }
148
+ post :create, params: { compute_resource: attrs }
149
+
150
+ assert_equal 'leave-me-alone', @controller.params[:compute_resource][:datacenter]
151
+ end
152
+
153
+ test 'should handle provider name with different casing' do
154
+ datacenter_uuid = Foreman.uuid
155
+ ForemanOvirt::Ovirt.any_instance.stubs(:get_datacenter_uuid).returns(datacenter_uuid)
156
+ ForemanOvirt::Ovirt.any_instance.stubs(:test_connection).returns(true)
157
+
158
+ attrs = { name: 'Ovirt-uppercase-test', url: 'https://myovirt/api', provider: 'OVIRT',
159
+ datacenter: 'test', user: 'user@example.com', password: 'secret' }
160
+ post :create, params: { compute_resource: attrs }
161
+
162
+ assert_response :created
163
+ assert_equal datacenter_uuid, ActiveSupport::JSON.decode(@response.body)['datacenter']
164
+ end
165
+ end
166
+
167
+ test 'should skip datacenter conversion on update if provider is not ovirt' do
168
+ compute_resource = FactoryBot.create(:vmware_cr)
169
+ ForemanOvirt::Ovirt.any_instance.expects(:get_datacenter_uuid).never
170
+
171
+ attrs = { datacenter: 'leave-me-alone' }
172
+ put :update, params: { id: compute_resource.id, compute_resource: attrs }
173
+
174
+ assert_equal 'leave-me-alone', @controller.params[:compute_resource][:datacenter]
175
+ end
176
+
177
+ context 'ovirt cache refreshing' do
178
+ test 'should fail if unsupported' do
179
+ ovirt_cr = FactoryBot.create(:ovirt_cr)
180
+ put :refresh_cache, params: { id: ovirt_cr.to_param }
181
+ assert_response :error
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+ require 'fog/ovirt/models/compute/quota'
5
+
6
+ module ForemanOvirt
7
+ class HostsControllerTest < ActionController::TestCase
8
+ tests ::HostsController
9
+
10
+ setup do
11
+ User.current = users(:admin)
12
+ end
13
+
14
+ test '#host update preserves association despite quota validation' do
15
+ hostgroup = FactoryBot.create(:hostgroup, :with_subnet, :with_domain, :with_os)
16
+
17
+ compute_resource = FactoryBot.build(:ovirt_cr)
18
+ compute_resource.stubs(:update_public_key)
19
+
20
+ # Setup quota mock to test that quota validation doesn't interfere with host update
21
+ quota = Fog::Ovirt::Compute::Quota.new(id: '1', name: 'Default')
22
+ client_mock = mock.tap { |m| m.stubs(datacenters: [], quotas: [quota]) }
23
+ compute_resource.stubs(:client).returns(client_mock)
24
+ compute_resource.save!
25
+
26
+ compute_resource.update(locations: hostgroup.locations, organizations: hostgroup.organizations)
27
+ host = FactoryBot.create(:host, hostgroup: hostgroup, compute_resource: compute_resource)
28
+
29
+ # Simulate form submission without compute_resource_id to test preservation
30
+ host_attributes = host.attributes.except('id', 'created_at', 'updated_at', 'compute_resource_id')
31
+
32
+ put :update, params: { commit: 'Update', id: host.id, host: host_attributes }, session: set_session_user
33
+
34
+ assert_response :redirect
35
+ host.reload
36
+ assert_equal compute_resource.id, host.compute_resource_id,
37
+ 'Host should remain associated with compute resource after update'
38
+ end
39
+
40
+ private
41
+
42
+ def set_session_user
43
+ { user: User.current.id }
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  FactoryBot.define do
2
- factory :host do
3
- name 'foreman_ovirt'
4
+ # This file defines factories for the ForemanOvirt plugin.
5
+ # The factory for the oVirt compute resource.
6
+ factory :ovirt_cr,
7
+ class: 'ForemanOvirt::Ovirt', parent: :compute_resource do
8
+ name { 'oVirt' }
9
+ url do
10
+ 'https://ovirt.example.com/ovirt-engine/api'
11
+ end
12
+ user do
13
+ 'admin@internal'
14
+ end
15
+ password do
16
+ 'secret'
17
+ end
18
+ uuid do
19
+ '56396f90-84c4-11e1-88f5-1231381c4021'
20
+ end
21
+ provider do
22
+ 'Ovirt'
23
+ end
24
+
25
+ # This ensures the test doesn't actually try to talk to a real server
26
+ after(:build) do |cr|
27
+ cr.stubs(:update_public_key)
28
+ end
4
29
  end
5
30
  end