Skip to content

Commit 7f88d40

Browse files
EnigmaXVnadjaheitmann
authored andcommitted
Align snapshot bulk actions with Foreman bulk actions
1 parent a17df3a commit 7f88d40

File tree

11 files changed

+368
-305
lines changed

11 files changed

+368
-305
lines changed

app/controllers/api/v2/bulk_snapshots_controller.rb

Lines changed: 28 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,27 @@
33
module Api
44
module V2
55
class BulkSnapshotsController < V2::BaseController
6-
SNAPSHOT_MODES = %w[full include_ram quiesce].freeze
6+
include ::Api::V2::BulkHostsExtension
77

8-
before_action :validate_hosts, :validate_snapshot, :validate_mode, only: :create
8+
SNAPSHOT_MODES = ForemanSnapshotManagement::Extensions::BulkHostsManager::SNAPSHOT_MODES
9+
10+
before_action :validate_snapshot, :validate_mode, :find_snapshotable_hosts, only: :create
911

1012
api :POST, '/snapshots/bulk/create_snapshot', N_('Create snapshots for multiple hosts')
11-
param :host_ids, Array, of: Integer, desc: N_('Array of host IDs to create snapshots for'), required: true
13+
14+
param :organization_id, :number, required: true, desc: N_('ID of the organization')
15+
16+
param :included, Hash, required: true, action_aware: true do
17+
param :search, String, required: false,
18+
desc: N_('Search string for hosts to perform an action on')
19+
param :ids, Array, required: false,
20+
desc: N_('List of host ids to perform an action on')
21+
end
22+
23+
param :excluded, Hash, required: true, action_aware: true do
24+
param :ids, Array, required: false,
25+
desc: N_('List of host ids to exclude and not run an action on')
26+
end
1227

1328
param :snapshot, Hash, desc: N_('Snapshot parameters'), required: true do
1429
param :name, String, desc: N_('Name of the snapshot'), required: true
@@ -21,17 +36,21 @@ class BulkSnapshotsController < V2::BaseController
2136
required: false
2237

2338
def create
24-
include_ram, quiesce = resolve_mode(params[:mode])
25-
26-
results = @hosts.map do |host|
27-
process_host(host, @snapshot_name, @snapshot_description, include_ram, quiesce)
28-
end
39+
results = ::BulkHostsManager.new(hosts: @hosts).create_snapshots(
40+
name: @snapshot_name,
41+
description: @snapshot_description,
42+
mode: params[:mode]
43+
)
2944

3045
render_results(results)
3146
end
3247

3348
private
3449

50+
def find_snapshotable_hosts
51+
find_bulk_hosts(:create_snapshots, params)
52+
end
53+
3554
def validate_mode
3655
mode = params[:mode]
3756
return true if mode.blank? || SNAPSHOT_MODES.include?(mode)
@@ -46,44 +65,6 @@ def validate_mode
4665
false
4766
end
4867

49-
def validate_hosts
50-
host_ids = params[:host_ids]
51-
52-
if host_ids.blank?
53-
render(
54-
json: { error: _('host_ids parameter is required and cannot be empty') },
55-
status: :unprocessable_entity
56-
)
57-
return false
58-
end
59-
60-
unless host_ids.is_a?(Array)
61-
render(
62-
json: { error: _('host_ids must be an array') },
63-
status: :unprocessable_entity
64-
)
65-
return false
66-
end
67-
68-
@requested_host_ids = host_ids.map(&:to_i)
69-
resource_base = Host.authorized("#{action_permission}_snapshots".to_sym, Host).friendly
70-
@hosts = resource_base.where(id: @requested_host_ids)
71-
72-
found_ids = @hosts.pluck(:id)
73-
missing_ids = @requested_host_ids - found_ids
74-
75-
return true if missing_ids.empty?
76-
77-
render(
78-
json: {
79-
error: _('Some host_ids are invalid'),
80-
invalid_ids: missing_ids,
81-
},
82-
status: :unprocessable_entity
83-
)
84-
false
85-
end
86-
8768
def validate_snapshot
8869
snap = params[:snapshot] || {}
8970
@snapshot_name = snap[:name].to_s.strip
@@ -101,13 +82,7 @@ def validate_snapshot
10182
def render_results(results)
10283
failed_hosts = results.select { |r| r[:status] == 'failed' }
10384
failed_count = failed_hosts.length
104-
105-
status =
106-
if failed_count.zero?
107-
:ok
108-
else
109-
:unprocessable_entity
110-
end
85+
status = failed_count.zero? ? :ok : :unprocessable_entity
11186

11287
render(
11388
json: {
@@ -121,66 +96,6 @@ def render_results(results)
12196
)
12297
end
12398

124-
def resolve_mode(mode)
125-
case mode
126-
when 'include_ram'
127-
[true, false]
128-
when 'quiesce'
129-
[false, true]
130-
else
131-
[false, false]
132-
end
133-
end
134-
135-
def process_host(host, name, description, include_ram, quiesce)
136-
snapshot = ForemanSnapshotManagement::Snapshot.new(
137-
name: name,
138-
description: description,
139-
include_ram: include_ram,
140-
quiesce: quiesce,
141-
host: host
142-
)
143-
144-
if snapshot.create
145-
{
146-
host_id: host.id,
147-
host_name: host.name,
148-
status: 'success',
149-
}
150-
else
151-
errors = snapshot.errors.full_messages
152-
errors << quiesce_error_message if quiesce
153-
154-
{
155-
host_id: host.id,
156-
host_name: host.name,
157-
status: 'failed',
158-
errors: errors,
159-
}
160-
end
161-
rescue StandardError => e
162-
Foreman::Logging.exception(
163-
"Failed to create snapshot for host #{host.name} (ID: #{host.id})",
164-
e
165-
)
166-
{
167-
host_id: host.id,
168-
host_name: host.name,
169-
status: 'failed',
170-
errors: [_('Snapshot creation failed: %s') % e.message],
171-
}
172-
end
173-
174-
def quiesce_error_message
175-
_(
176-
'Unable to create VMWare Snapshot with Quiesce. Check Power and VMWare Tools status.'
177-
)
178-
end
179-
180-
def action_permission
181-
:create
182-
end
183-
18499
def resource_class
185100
::ForemanSnapshotManagement::Snapshot
186101
end
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# frozen_string_literal: true
2+
3+
module ForemanSnapshotManagement
4+
module Extensions
5+
module BulkHostsManager
6+
extend ActiveSupport::Concern
7+
8+
SNAPSHOT_MODES = %w[full include_ram quiesce].freeze
9+
QUIESCE_ERROR_MESSAGE =
10+
_('Unable to create VMWare Snapshot with Quiesce. Check Power and VMWare Tools status.').freeze
11+
12+
def create_snapshots(name:, description:, mode:)
13+
include_ram, quiesce = resolve_mode(mode)
14+
15+
@hosts.map do |host|
16+
process_snapshot_for_host(
17+
host: host,
18+
name: name,
19+
description: description,
20+
include_ram: include_ram,
21+
quiesce: quiesce
22+
)
23+
end
24+
end
25+
26+
private
27+
28+
def resolve_mode(mode)
29+
case mode
30+
when 'include_ram'
31+
[true, false]
32+
when 'quiesce'
33+
[false, true]
34+
else
35+
[false, false]
36+
end
37+
end
38+
39+
def process_snapshot_for_host(host:, name:, description:, include_ram:, quiesce:)
40+
validation_error = validate_host(host)
41+
return validation_error if validation_error
42+
43+
create_snapshot_for_host(host, name, description, include_ram, quiesce)
44+
rescue ActiveRecord::RecordNotFound => e
45+
handle_record_not_found(host, e)
46+
rescue StandardError => e
47+
handle_snapshot_error(host, e)
48+
end
49+
50+
def validate_host(host)
51+
if host.compute_resource.nil?
52+
return {
53+
host_id: host.id,
54+
host_name: host.name,
55+
status: 'failed',
56+
errors: [_('Host has no compute resource, cannot create snapshot.')],
57+
}
58+
end
59+
60+
return if host.uuid.present?
61+
62+
{
63+
host_id: host.id,
64+
host_name: host.name,
65+
status: 'failed',
66+
errors: [_('Host has no UUID, cannot create snapshot.')],
67+
}
68+
end
69+
70+
def create_snapshot_for_host(host, name, description, include_ram, quiesce)
71+
snapshot = ::ForemanSnapshotManagement::Snapshot.new(
72+
name: name,
73+
description: description,
74+
include_ram: include_ram,
75+
quiesce: quiesce,
76+
host: host
77+
)
78+
79+
if snapshot.create
80+
{ host_id: host.id, host_name: host.name, status: 'success' }
81+
else
82+
errors = snapshot.errors.full_messages
83+
errors << QUIESCE_ERROR_MESSAGE if quiesce
84+
{ host_id: host.id, host_name: host.name, status: 'failed', errors: errors }
85+
end
86+
end
87+
88+
def handle_record_not_found(host, exception)
89+
::Foreman::Logging.exception(
90+
"Failed to create snapshot for host #{host.name} (ID: #{host.id})",
91+
exception
92+
)
93+
94+
cr_name = host.compute_resource&.name
95+
cr_name = "#{cr_name} " if cr_name.present?
96+
97+
msg =
98+
_('VM details could not be retrieved from compute resource %s. The VM may be missing/deleted or inaccessible.') % cr_name
99+
100+
{
101+
host_id: host.id,
102+
host_name: host.name,
103+
status: 'failed',
104+
errors: [msg],
105+
}
106+
end
107+
108+
def handle_snapshot_error(host, exception)
109+
::Foreman::Logging.exception(
110+
"Failed to create snapshot for host #{host.name} (ID: #{host.id})",
111+
exception
112+
)
113+
114+
{
115+
host_id: host.id,
116+
host_name: host.name,
117+
status: 'failed',
118+
errors: [_('Snapshot creation failed: %s') % exception.message],
119+
}
120+
end
121+
end
122+
end
123+
end

lib/foreman_snapshot_management/engine.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ class Engine < ::Rails::Engine
9191
# Include concerns in this config.to_prepare block
9292
config.to_prepare do
9393
begin
94+
::BulkHostsManager.include ForemanSnapshotManagement::Extensions::BulkHostsManager
95+
9496
::ForemanFogProxmox::Proxmox.prepend ForemanSnapshotManagement::ProxmoxExtensions
9597

9698
# Load Fog extensions

0 commit comments

Comments
 (0)