Skip to content

Commit a17df3a

Browse files
authored
Merge pull request #122 from ATIX-AG/add_snapshot_bulkaction
Add snapshot bulk actions
2 parents 5b57aac + 4c48f27 commit a17df3a

File tree

21 files changed

+1195
-336
lines changed

21 files changed

+1195
-336
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# frozen_string_literal: true
2+
3+
module Api
4+
module V2
5+
class BulkSnapshotsController < V2::BaseController
6+
SNAPSHOT_MODES = %w[full include_ram quiesce].freeze
7+
8+
before_action :validate_hosts, :validate_snapshot, :validate_mode, only: :create
9+
10+
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
12+
13+
param :snapshot, Hash, desc: N_('Snapshot parameters'), required: true do
14+
param :name, String, desc: N_('Name of the snapshot'), required: true
15+
param :description, String, desc: N_('Description of the snapshot'), required: false
16+
end
17+
18+
param :mode,
19+
SNAPSHOT_MODES,
20+
desc: N_('Snapshot mode: full (default), include_ram, or quiesce'),
21+
required: false
22+
23+
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
29+
30+
render_results(results)
31+
end
32+
33+
private
34+
35+
def validate_mode
36+
mode = params[:mode]
37+
return true if mode.blank? || SNAPSHOT_MODES.include?(mode)
38+
39+
render(
40+
json: {
41+
error: _('Invalid mode'),
42+
valid_modes: SNAPSHOT_MODES,
43+
},
44+
status: :unprocessable_entity
45+
)
46+
false
47+
end
48+
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+
87+
def validate_snapshot
88+
snap = params[:snapshot] || {}
89+
@snapshot_name = snap[:name].to_s.strip
90+
@snapshot_description = snap[:description].to_s
91+
92+
return true if @snapshot_name.present?
93+
94+
render(
95+
json: { error: _('snapshot.name is required') },
96+
status: :unprocessable_entity
97+
)
98+
false
99+
end
100+
101+
def render_results(results)
102+
failed_hosts = results.select { |r| r[:status] == 'failed' }
103+
failed_count = failed_hosts.length
104+
105+
status =
106+
if failed_count.zero?
107+
:ok
108+
else
109+
:unprocessable_entity
110+
end
111+
112+
render(
113+
json: {
114+
results: results.map { |r| r.except(:errors) },
115+
total: results.length,
116+
success_count: results.length - failed_count,
117+
failed_count: failed_count,
118+
failed_hosts: failed_hosts,
119+
},
120+
status: status
121+
)
122+
end
123+
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+
184+
def resource_class
185+
::ForemanSnapshotManagement::Snapshot
186+
end
187+
188+
def resource_name
189+
'snapshot'
190+
end
191+
end
192+
end
193+
end

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:defaults => { :apiv => 'v2' },
77
:apiv => /v1|v2/,
88
:constraints => ApiConstraints.new(:version => 2, :default => true) do
9+
post 'snapshots/bulk/create_snapshot', :to => 'bulk_snapshots#create'
910
constraints(:host_id => /[^\/]+/) do
1011
resources :hosts, :only => [] do
1112
constraints(:id => /[^\/]+/) do

lib/foreman_snapshot_management/engine.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class Engine < ::Rails::Engine
2525
permission :create_snapshots, {
2626
:'foreman_snapshot_management/snapshots' => [:create, :select_multiple_host, :create_multiple_host],
2727
:'api/v2/snapshots' => [:create],
28+
:'api/v2/bulk_snapshots' => [:create],
2829
}, :resource_type => 'Host'
2930

3031
permission :edit_snapshots, {

0 commit comments

Comments
 (0)