foreman_nutanix 0.0.1
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.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +32 -0
- data/Rakefile +49 -0
- data/app/assets/javascripts/foreman_nutanix/locale/en/foreman_nutanix.js +55 -0
- data/app/controllers/concerns/foreman/controller/parameters/compute_resource_extension.rb +17 -0
- data/app/controllers/foreman_nutanix/api/v2/apipie_extensions.rb +16 -0
- data/app/controllers/foreman_nutanix/api/v2/compute_resources_extensions.rb +29 -0
- data/app/controllers/foreman_nutanix/api/v2/hosts_controller_extensions.rb +50 -0
- data/app/lib/foreman_nutanix/nutanix_adapter.rb +105 -0
- data/app/lib/nutanix_compute/compute_collection.rb +23 -0
- data/app/lib/nutanix_extensions/attached_disk.rb +22 -0
- data/app/models/concerns/foreman_nutanix/host_managed_extensions.rb +38 -0
- data/app/models/foreman_nutanix/nutanix.rb +471 -0
- data/app/models/foreman_nutanix/nutanix_compute.rb +370 -0
- data/app/views/compute_resources/form/_nutanix.html.erb +9 -0
- data/app/views/compute_resources/show/_nutanix.html.erb +238 -0
- data/app/views/compute_resources_vms/form/nutanix/_base.html.erb +72 -0
- data/app/views/compute_resources_vms/form/nutanix/_volume.html.erb +1 -0
- data/app/views/compute_resources_vms/index/_gce.html.erb +41 -0
- data/app/views/compute_resources_vms/index/_nutanix.html.erb +41 -0
- data/app/views/compute_resources_vms/show/_gce.html.erb +18 -0
- data/app/views/compute_resources_vms/show/_nutanix.html.erb +81 -0
- data/config/initializers/zeitwerk.rb +1 -0
- data/config/routes.rb +2 -0
- data/lib/foreman_nutanix/engine.rb +56 -0
- data/lib/foreman_nutanix/version.rb +3 -0
- data/lib/foreman_nutanix.rb +4 -0
- data/lib/tasks/foreman_nutanix_tasks.rake +31 -0
- data/locale/foreman_nutanix.pot +131 -0
- data/package.json +41 -0
- data/webpack/global_index.js +6 -0
- data/webpack/global_test_setup.js +11 -0
- data/webpack/index.js +7 -0
- data/webpack/legacy.js +16 -0
- data/webpack/src/Extends/index.js +15 -0
- data/webpack/src/Router/routes.js +5 -0
- data/webpack/src/reducers.js +7 -0
- data/webpack/test_setup.js +17 -0
- metadata +100 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
module ForemanNutanix
|
|
2
|
+
class NutanixCompute
|
|
3
|
+
attr_reader :identity, :name, :hostname, :cluster, :args
|
|
4
|
+
attr_accessor :zone, :machine_type, :network, :image_id, :associate_external_ip, :cpus, :memory, :power_state, :subnet_ext_id, :storage_container_ext_id, :num_sockets, :num_cores_per_socket, :disk_size_bytes, :description, :network_id, :storage_container, :disk_size_gb, :power_on, :mac_address, :vm_ip_addresses, :create_time
|
|
5
|
+
|
|
6
|
+
def initialize(cluster = nil, args = {})
|
|
7
|
+
Rails.logger.info "=== NUTANIX: NutanixCompute::initialize cluster=#{cluster} args=#{args} ==="
|
|
8
|
+
@cluster = cluster
|
|
9
|
+
@args = args
|
|
10
|
+
@name = args[:name] || "nutanix-vm-#{Time.now.to_i}"
|
|
11
|
+
@identity = args[:identity] || args[:uuid] || @name
|
|
12
|
+
@hostname = args[:hostname] || @name
|
|
13
|
+
@zone = args[:zone] || 'default-zone'
|
|
14
|
+
@machine_type = args[:machine_type] || args[:flavor_id] || 'small'
|
|
15
|
+
@network = args[:network] || args[:network_id] || 'default-network'
|
|
16
|
+
@image_id = args[:image_id]
|
|
17
|
+
@associate_external_ip = args[:associate_external_ip] || true
|
|
18
|
+
@cpus = args[:cpus] || 2
|
|
19
|
+
@memory = args[:memory] || 4
|
|
20
|
+
@power_state = args[:power_state]
|
|
21
|
+
@persisted = false
|
|
22
|
+
|
|
23
|
+
# Provisioning-specific attributes
|
|
24
|
+
@network_id = args[:network_id] || args[:network]
|
|
25
|
+
@storage_container = args[:storage_container]
|
|
26
|
+
@disk_size_gb = args[:disk_size_gb] || 50
|
|
27
|
+
@power_on = args.key?(:power_on) ? args[:power_on] : true # Default to true
|
|
28
|
+
@subnet_ext_id = args[:subnet_ext_id] || @network_id
|
|
29
|
+
@storage_container_ext_id = args[:storage_container_ext_id] || @storage_container
|
|
30
|
+
@num_sockets = args[:num_sockets] || 1
|
|
31
|
+
@num_cores_per_socket = args[:num_cores_per_socket] || @cpus
|
|
32
|
+
@disk_size_bytes = args[:disk_size_bytes] || (@disk_size_gb.to_i * 1024**3)
|
|
33
|
+
@description = args[:description] || ''
|
|
34
|
+
|
|
35
|
+
# VM details from Nutanix
|
|
36
|
+
@mac_address = args[:mac_address]
|
|
37
|
+
@vm_ip_addresses = args[:ip_addresses] || []
|
|
38
|
+
@create_time = args[:create_time]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Required by Foreman - indicates if VM exists
|
|
42
|
+
def persisted?
|
|
43
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::persisted? called ==='
|
|
44
|
+
@persisted
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Required by Foreman - save the VM (actually create it)
|
|
48
|
+
def save
|
|
49
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::save called ==='
|
|
50
|
+
Rails.logger.info "=== NUTANIX: VM attributes - network_id: #{@network_id}, storage_container: #{@storage_container}, subnet_ext_id: #{@subnet_ext_id}, storage_container_ext_id: #{@storage_container_ext_id} ==="
|
|
51
|
+
|
|
52
|
+
# Build the provision request payload
|
|
53
|
+
# Convert memory from GB to bytes (1 GB = 1024^3 bytes)
|
|
54
|
+
memory_bytes = (@memory || 4).to_i * 1024**3
|
|
55
|
+
|
|
56
|
+
# Use form values, falling back to internal values
|
|
57
|
+
actual_subnet = @subnet_ext_id || @network_id
|
|
58
|
+
actual_storage = @storage_container_ext_id || @storage_container
|
|
59
|
+
actual_disk_bytes = @disk_size_bytes || (@disk_size_gb.to_i * 1024**3)
|
|
60
|
+
|
|
61
|
+
# Validate required fields
|
|
62
|
+
if actual_subnet.nil? || actual_subnet.to_s.strip.empty?
|
|
63
|
+
raise StandardError, 'Network/Subnet is required for VM provisioning'
|
|
64
|
+
end
|
|
65
|
+
if actual_storage.nil? || actual_storage.to_s.strip.empty?
|
|
66
|
+
raise StandardError, 'Storage Container is required for VM provisioning'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
provision_request = {
|
|
70
|
+
name: @name,
|
|
71
|
+
cluster_ext_id: @cluster,
|
|
72
|
+
subnet_ext_id: actual_subnet,
|
|
73
|
+
storage_container_ext_id: actual_storage,
|
|
74
|
+
num_sockets: @num_sockets.to_i,
|
|
75
|
+
num_cores_per_socket: @num_cores_per_socket.to_i,
|
|
76
|
+
memory_size_bytes: memory_bytes,
|
|
77
|
+
disk_size_bytes: actual_disk_bytes.to_i,
|
|
78
|
+
description: @description || '',
|
|
79
|
+
power_on: @power_on.nil? || @power_on, # Default to true if not set
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Rails.logger.info "=== NUTANIX: Provisioning VM with request: #{provision_request} ==="
|
|
83
|
+
|
|
84
|
+
# Call the shim server to provision the VM
|
|
85
|
+
base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
|
|
86
|
+
uri = URI("#{base.chomp('/')}/api/v1/vmm/provision-vm")
|
|
87
|
+
|
|
88
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
89
|
+
http.use_ssl = uri.scheme == 'https'
|
|
90
|
+
|
|
91
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
92
|
+
request['Content-Type'] = 'application/json'
|
|
93
|
+
request.body = provision_request.to_json
|
|
94
|
+
|
|
95
|
+
response = http.request(request)
|
|
96
|
+
|
|
97
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
98
|
+
result = JSON.parse(response.body)
|
|
99
|
+
Rails.logger.info "=== NUTANIX: VM provisioned successfully: #{result} ==="
|
|
100
|
+
|
|
101
|
+
# Update identity with the real ext_id from Nutanix
|
|
102
|
+
@identity = result['ext_id']
|
|
103
|
+
@persisted = true
|
|
104
|
+
true
|
|
105
|
+
else
|
|
106
|
+
error_message = "Failed to provision VM: #{response.code} - #{response.body}"
|
|
107
|
+
Rails.logger.error "=== NUTANIX: #{error_message} ==="
|
|
108
|
+
raise StandardError, error_message
|
|
109
|
+
end
|
|
110
|
+
rescue StandardError => e
|
|
111
|
+
Rails.logger.error "=== NUTANIX: Error in save: #{e.message} ==="
|
|
112
|
+
raise e
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Required by Foreman - VM status
|
|
116
|
+
# Returns true if VM is powered on and ready to use
|
|
117
|
+
def ready?
|
|
118
|
+
is_ready = persisted? && @power_state == 'ON'
|
|
119
|
+
Rails.logger.info "=== NUTANIX: NutanixCompute::ready? called, persisted=#{persisted?}, power_state=#{@power_state}, returning #{is_ready} ==="
|
|
120
|
+
is_ready
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Power state accessor that Foreman might call directly
|
|
124
|
+
def power_state
|
|
125
|
+
Rails.logger.info "=== NUTANIX: NutanixCompute::power_state called, returning #{@power_state} ==="
|
|
126
|
+
@power_state
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Required by Foreman - VM status
|
|
130
|
+
def state
|
|
131
|
+
Rails.logger.info "=== NUTANIX: NutanixCompute::state called, power_state=#{@power_state} ==="
|
|
132
|
+
return 'pending' unless persisted?
|
|
133
|
+
|
|
134
|
+
# Map Nutanix power states to Foreman-friendly states
|
|
135
|
+
result = case @power_state
|
|
136
|
+
when 'ON'
|
|
137
|
+
'running'
|
|
138
|
+
when 'OFF'
|
|
139
|
+
'stopped'
|
|
140
|
+
when 'PAUSED'
|
|
141
|
+
'paused'
|
|
142
|
+
else
|
|
143
|
+
'unknown'
|
|
144
|
+
end
|
|
145
|
+
Rails.logger.info "=== NUTANIX: NutanixCompute::state returning '#{result}' ==="
|
|
146
|
+
result
|
|
147
|
+
end
|
|
148
|
+
alias_method :status, :state
|
|
149
|
+
|
|
150
|
+
# Required by Foreman - reload VM state
|
|
151
|
+
def reload
|
|
152
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::reload called ==='
|
|
153
|
+
self
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Required by Foreman - start VM
|
|
157
|
+
def start(args = {})
|
|
158
|
+
Rails.logger.info "=== NUTANIX: NutanixCompute::start called with args: #{args} ==="
|
|
159
|
+
return false unless persisted?
|
|
160
|
+
|
|
161
|
+
# Extract actual UUID (handle ZXJnb24=:uuid format)
|
|
162
|
+
actual_uuid = @identity.to_s.include?(':') ? @identity.to_s.split(':').last : @identity.to_s
|
|
163
|
+
|
|
164
|
+
# Call the shim server to power on the VM
|
|
165
|
+
base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
|
|
166
|
+
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}/power-state")
|
|
167
|
+
|
|
168
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
169
|
+
http.use_ssl = uri.scheme == 'https'
|
|
170
|
+
|
|
171
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
172
|
+
request['Content-Type'] = 'application/json'
|
|
173
|
+
request.body = { action: 'POWER_ON' }.to_json
|
|
174
|
+
|
|
175
|
+
response = http.request(request)
|
|
176
|
+
|
|
177
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
178
|
+
Rails.logger.info "=== NUTANIX: VM #{actual_uuid} powered on successfully ==="
|
|
179
|
+
@power_state = 'ON'
|
|
180
|
+
true
|
|
181
|
+
else
|
|
182
|
+
error_message = "Failed to power on VM: #{response.code} - #{response.body}"
|
|
183
|
+
Rails.logger.error "=== NUTANIX: #{error_message} ==="
|
|
184
|
+
raise StandardError, error_message
|
|
185
|
+
end
|
|
186
|
+
rescue StandardError => e
|
|
187
|
+
Rails.logger.error "=== NUTANIX: Error in start: #{e.message} ==="
|
|
188
|
+
raise e
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Required by Foreman - stop VM
|
|
192
|
+
def stop(args = {})
|
|
193
|
+
Rails.logger.info "=== NUTANIX: NutanixCompute::stop called with args: #{args} ==="
|
|
194
|
+
return false unless persisted?
|
|
195
|
+
|
|
196
|
+
# Extract actual UUID (handle ZXJnb24=:uuid format)
|
|
197
|
+
actual_uuid = @identity.to_s.include?(':') ? @identity.to_s.split(':').last : @identity.to_s
|
|
198
|
+
|
|
199
|
+
# Call the shim server to power off the VM
|
|
200
|
+
base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
|
|
201
|
+
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}/power-state")
|
|
202
|
+
|
|
203
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
204
|
+
http.use_ssl = uri.scheme == 'https'
|
|
205
|
+
|
|
206
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
207
|
+
request['Content-Type'] = 'application/json'
|
|
208
|
+
request.body = { action: 'POWER_OFF' }.to_json
|
|
209
|
+
|
|
210
|
+
response = http.request(request)
|
|
211
|
+
|
|
212
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
213
|
+
Rails.logger.info "=== NUTANIX: VM #{actual_uuid} powered off successfully ==="
|
|
214
|
+
@power_state = 'OFF'
|
|
215
|
+
true
|
|
216
|
+
else
|
|
217
|
+
error_message = "Failed to power off VM: #{response.code} - #{response.body}"
|
|
218
|
+
Rails.logger.error "=== NUTANIX: #{error_message} ==="
|
|
219
|
+
raise StandardError, error_message
|
|
220
|
+
end
|
|
221
|
+
rescue StandardError => e
|
|
222
|
+
Rails.logger.error "=== NUTANIX: Error in stop: #{e.message} ==="
|
|
223
|
+
raise e
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Required by Foreman - CPU count
|
|
227
|
+
def cpu
|
|
228
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::cpu called ==='
|
|
229
|
+
@cpus.to_s
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Required by Foreman - memory in GB
|
|
233
|
+
def memory
|
|
234
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::memory called ==='
|
|
235
|
+
@memory
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Required by Foreman - string representation
|
|
239
|
+
def to_s
|
|
240
|
+
@name
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Required by Foreman - creation timestamp
|
|
244
|
+
def creation_timestamp
|
|
245
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::creation_timestamp called ==='
|
|
246
|
+
return nil unless @create_time
|
|
247
|
+
|
|
248
|
+
begin
|
|
249
|
+
if @create_time.is_a?(String)
|
|
250
|
+
Time.parse(@create_time)
|
|
251
|
+
else
|
|
252
|
+
@create_time
|
|
253
|
+
end
|
|
254
|
+
rescue StandardError => e
|
|
255
|
+
Rails.logger.error "=== NUTANIX: Error parsing create_time: #{e.message} ==="
|
|
256
|
+
nil
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Required by Foreman - image name for display
|
|
261
|
+
def pretty_image_name
|
|
262
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::pretty_image_name called ==='
|
|
263
|
+
# We don't track the source image yet
|
|
264
|
+
nil
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Required by Foreman - public IP address
|
|
268
|
+
def vm_ip_address
|
|
269
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::vm_ip_address called ==='
|
|
270
|
+
return nil unless persisted?
|
|
271
|
+
@vm_ip_addresses&.first
|
|
272
|
+
end
|
|
273
|
+
alias_method :public_ip_address, :vm_ip_address
|
|
274
|
+
|
|
275
|
+
# Required by Foreman - private IP address
|
|
276
|
+
def private_ip_address
|
|
277
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::private_ip_address called ==='
|
|
278
|
+
return nil unless persisted?
|
|
279
|
+
# Return second IP if available, otherwise same as public
|
|
280
|
+
(@vm_ip_addresses&.length.to_i > 1) ? @vm_ip_addresses[1] : @vm_ip_addresses&.first
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Required by Foreman - all IP addresses
|
|
284
|
+
def ip_addresses
|
|
285
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::ip_addresses called ==='
|
|
286
|
+
persisted? ? (@vm_ip_addresses || []) : []
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Required by Foreman - MAC address
|
|
290
|
+
def mac
|
|
291
|
+
Rails.logger.info "=== NUTANIX: NutanixCompute::mac called, persisted=#{persisted?}, mac_address=#{@mac_address} ==="
|
|
292
|
+
persisted? ? @mac_address : nil
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Required by Foreman - MAC addresses hash for VM association
|
|
296
|
+
def mac_addresses
|
|
297
|
+
Rails.logger.info "=== NUTANIX: NutanixCompute::mac_addresses called, mac_address=#{@mac_address} ==="
|
|
298
|
+
return {} unless @mac_address
|
|
299
|
+
{ 'nic0' => @mac_address }
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Required by Foreman - VM description
|
|
303
|
+
def vm_description
|
|
304
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::vm_description called ==='
|
|
305
|
+
pretty_machine_type
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Required by Foreman - pretty machine type
|
|
309
|
+
def pretty_machine_type
|
|
310
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::pretty_machine_type called ==='
|
|
311
|
+
"#{@cpus} CPUs, #{memory}GB RAM"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Required by Foreman - volumes/disks
|
|
315
|
+
def volumes
|
|
316
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::volumes called ==='
|
|
317
|
+
[OpenStruct.new({ name: 'disk-1', size_gb: 20 })]
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Required by Foreman - volumes_attributes setter
|
|
321
|
+
def volumes_attributes=(_attrs)
|
|
322
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::volumes_attributes= called ==='
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Required by Foreman - network interfaces
|
|
326
|
+
def interfaces
|
|
327
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::interfaces called ==='
|
|
328
|
+
[OpenStruct.new({
|
|
329
|
+
name: 'eth0',
|
|
330
|
+
network: @network,
|
|
331
|
+
mac: @mac_address,
|
|
332
|
+
ip: @vm_ip_addresses&.first,
|
|
333
|
+
})]
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Required by Foreman - network interfaces access
|
|
337
|
+
def network_interfaces
|
|
338
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::network_interfaces called ==='
|
|
339
|
+
interfaces
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Required by Foreman - select matching NIC from compute resource
|
|
343
|
+
# This is called by Foreman's match_macs_to_nics to assign MAC addresses
|
|
344
|
+
def select_nic(fog_nics, nic)
|
|
345
|
+
Rails.logger.info "=== NUTANIX: NutanixCompute::select_nic called with fog_nics=#{fog_nics.inspect}, nic=#{nic.inspect} ==="
|
|
346
|
+
# Return the first available NIC (we only have one for now)
|
|
347
|
+
fog_nics.shift
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Required by Foreman - console/serial output
|
|
351
|
+
def serial_port_output
|
|
352
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::serial_port_output called ==='
|
|
353
|
+
"Mock serial console output for #{@name}"
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Required by Foreman - wait for condition
|
|
357
|
+
def wait_for
|
|
358
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::wait_for called ==='
|
|
359
|
+
yield if block_given?
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Required by Foreman - destroy VM
|
|
363
|
+
def destroy
|
|
364
|
+
Rails.logger.info '=== NUTANIX: NutanixCompute::destroy called ==='
|
|
365
|
+
@persisted = false
|
|
366
|
+
true
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<h4>Cluster Information</h4>
|
|
2
|
+
<table class="table table-striped table-condensed">
|
|
3
|
+
<% if @compute_resource.cluster_details %>
|
|
4
|
+
<% @compute_resource.cluster_details.to_h.each do |key, value| %>
|
|
5
|
+
<tr>
|
|
6
|
+
<td><%= key %></td>
|
|
7
|
+
<td><%= value %></td>
|
|
8
|
+
</tr>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% else %>
|
|
11
|
+
<tr>
|
|
12
|
+
<td colspan="2">No cluster details available</td>
|
|
13
|
+
</tr>
|
|
14
|
+
<% end %>
|
|
15
|
+
</table>
|
|
16
|
+
|
|
17
|
+
<hr style="margin: 30px 0;">
|
|
18
|
+
|
|
19
|
+
<h4>Debug Information</h4>
|
|
20
|
+
<table class="table table-striped table-condensed">
|
|
21
|
+
<tr>
|
|
22
|
+
<td>Provider</td>
|
|
23
|
+
<td><%= @compute_resource.provider_friendly_name %></td>
|
|
24
|
+
</tr>
|
|
25
|
+
<tr>
|
|
26
|
+
<td>Capabilities</td>
|
|
27
|
+
<td><%= @compute_resource.capabilities.join(", ") %></td>
|
|
28
|
+
</tr>
|
|
29
|
+
<tr>
|
|
30
|
+
<td>User Data Support</td>
|
|
31
|
+
<td><%= @compute_resource.user_data_supported? ? "Yes" : "No" %></td>
|
|
32
|
+
</tr>
|
|
33
|
+
<tr>
|
|
34
|
+
<td>Shim Server URL</td>
|
|
35
|
+
<td><code><%= @compute_resource.shim_server_url %></code></td>
|
|
36
|
+
</tr>
|
|
37
|
+
<tr>
|
|
38
|
+
<td>Cluster ID</td>
|
|
39
|
+
<td><code><%= @compute_resource.cluster || "Not set" %></code></td>
|
|
40
|
+
</tr>
|
|
41
|
+
<tr>
|
|
42
|
+
<td>Cluster Name</td>
|
|
43
|
+
<td><%= @compute_resource.cluster_details&.name || "N/A" %></td>
|
|
44
|
+
</tr>
|
|
45
|
+
<tr>
|
|
46
|
+
<td>Resource Counts</td>
|
|
47
|
+
<td>
|
|
48
|
+
<strong>Networks:</strong>
|
|
49
|
+
<%= @compute_resource.available_networks.count %>
|
|
50
|
+
|
|
|
51
|
+
<strong>Storage Containers:</strong>
|
|
52
|
+
<%= @compute_resource.available_storage_containers.count %>
|
|
53
|
+
|
|
|
54
|
+
<strong>Images:</strong>
|
|
55
|
+
<%= @compute_resource.available_images.count %>
|
|
56
|
+
|
|
|
57
|
+
<strong>Flavors:</strong>
|
|
58
|
+
<%= @compute_resource.available_flavors.count %>
|
|
59
|
+
</td>
|
|
60
|
+
</tr>
|
|
61
|
+
</table>
|
|
62
|
+
|
|
63
|
+
<hr style="margin: 30px 0;">
|
|
64
|
+
|
|
65
|
+
<h4>Available Resources</h4>
|
|
66
|
+
<div class="row">
|
|
67
|
+
<div class="col-md-6">
|
|
68
|
+
<h5>Networks</h5>
|
|
69
|
+
<table class="table table-striped table-condensed">
|
|
70
|
+
<thead>
|
|
71
|
+
<tr>
|
|
72
|
+
<th>Name</th>
|
|
73
|
+
<th>Type</th>
|
|
74
|
+
<th>Subnet</th>
|
|
75
|
+
<th>Gateway</th>
|
|
76
|
+
</tr>
|
|
77
|
+
</thead>
|
|
78
|
+
<tbody>
|
|
79
|
+
<% @compute_resource.available_networks.each do |network| %>
|
|
80
|
+
<tr>
|
|
81
|
+
<td><%= network.name %></td>
|
|
82
|
+
<td><%= network.subnet_type %></td>
|
|
83
|
+
<td><%= network.ipv4_subnet %></td>
|
|
84
|
+
<td><%= network.ipv4_gateway %></td>
|
|
85
|
+
</tr>
|
|
86
|
+
<% end %>
|
|
87
|
+
<% if @compute_resource.available_networks.empty? %>
|
|
88
|
+
<tr>
|
|
89
|
+
<td colspan="4">No networks available</td>
|
|
90
|
+
</tr>
|
|
91
|
+
<% end %>
|
|
92
|
+
</tbody>
|
|
93
|
+
</table>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="col-md-6">
|
|
96
|
+
<h5>Storage Containers</h5>
|
|
97
|
+
<table class="table table-striped table-condensed">
|
|
98
|
+
<thead>
|
|
99
|
+
<tr>
|
|
100
|
+
<th>Name</th>
|
|
101
|
+
<th>Capacity (GB)</th>
|
|
102
|
+
<th>Replication</th>
|
|
103
|
+
<th>Compression</th>
|
|
104
|
+
</tr>
|
|
105
|
+
</thead>
|
|
106
|
+
<tbody>
|
|
107
|
+
<% @compute_resource.available_storage_containers.each do |container| %>
|
|
108
|
+
<tr>
|
|
109
|
+
<td><%= container.name %></td>
|
|
110
|
+
<td><%= (container.max_capacity_bytes.to_f / 1024**3).round(2) %></td>
|
|
111
|
+
<td><%= container.replication_factor %>x</td>
|
|
112
|
+
<td><%= container.is_compression_enabled ? "Yes" : "No" %></td>
|
|
113
|
+
</tr>
|
|
114
|
+
<% end %>
|
|
115
|
+
<% if @compute_resource.available_storage_containers.empty? %>
|
|
116
|
+
<tr>
|
|
117
|
+
<td colspan="4">No storage containers available</td>
|
|
118
|
+
</tr>
|
|
119
|
+
<% end %>
|
|
120
|
+
</tbody>
|
|
121
|
+
</table>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<hr style="margin: 30px 0;">
|
|
126
|
+
|
|
127
|
+
<h4>Cluster Resource Availability</h4>
|
|
128
|
+
<% stats = @compute_resource.cluster_resource_stats %>
|
|
129
|
+
<% if stats %>
|
|
130
|
+
<table class="table table-striped table-condensed">
|
|
131
|
+
<thead>
|
|
132
|
+
<tr>
|
|
133
|
+
<th>Resource</th>
|
|
134
|
+
<th>Capacity</th>
|
|
135
|
+
<th>Used</th>
|
|
136
|
+
<th>Available</th>
|
|
137
|
+
<th>Usage</th>
|
|
138
|
+
</tr>
|
|
139
|
+
</thead>
|
|
140
|
+
<tbody>
|
|
141
|
+
<tr>
|
|
142
|
+
<td><strong>CPU</strong></td>
|
|
143
|
+
<td><%= (stats.cpu_capacity_hz.to_f / 1_000_000_000).round(2) %>
|
|
144
|
+
GHz /
|
|
145
|
+
<%= stats.cpu_cores_total %>
|
|
146
|
+
cores</td>
|
|
147
|
+
<td><%= (stats.cpu_usage_hz.to_f / 1_000_000_000).round(2) %>
|
|
148
|
+
GHz /
|
|
149
|
+
<%= stats.cpu_cores_usage %>
|
|
150
|
+
cores</td>
|
|
151
|
+
<td><%= ((stats.cpu_capacity_hz - stats.cpu_usage_hz).to_f / 1_000_000_000).round(2) %>
|
|
152
|
+
GHz /
|
|
153
|
+
<%= stats.cpu_cores_total - stats.cpu_cores_usage %>
|
|
154
|
+
cores</td>
|
|
155
|
+
<td>
|
|
156
|
+
<div class="progress" style="margin-bottom: 0;">
|
|
157
|
+
<div
|
|
158
|
+
class="progress-bar <%= stats.cpu_usage_percent > 80 ? 'progress-bar-danger' : stats.cpu_usage_percent > 60 ? 'progress-bar-warning' : 'progress-bar-success' %>"
|
|
159
|
+
role="progressbar"
|
|
160
|
+
aria-valuenow="<%= stats.cpu_usage_percent %>"
|
|
161
|
+
aria-valuemin="0"
|
|
162
|
+
aria-valuemax="100"
|
|
163
|
+
style="width: <%= stats.cpu_usage_percent %>%"
|
|
164
|
+
>
|
|
165
|
+
<%= stats.cpu_usage_percent %>%
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</td>
|
|
169
|
+
</tr>
|
|
170
|
+
<tr>
|
|
171
|
+
<td><strong>Memory</strong></td>
|
|
172
|
+
<td><%= (stats.memory_capacity_bytes.to_f / 1024**3).round(2) %>
|
|
173
|
+
GB</td>
|
|
174
|
+
<td><%= (stats.memory_usage_bytes.to_f / 1024**3).round(2) %>
|
|
175
|
+
GB</td>
|
|
176
|
+
<td><%= ((stats.memory_capacity_bytes - stats.memory_usage_bytes).to_f / 1024**3).round(
|
|
177
|
+
2,
|
|
178
|
+
) %>
|
|
179
|
+
GB</td>
|
|
180
|
+
<td>
|
|
181
|
+
<div class="progress" style="margin-bottom: 0;">
|
|
182
|
+
<div
|
|
183
|
+
class="progress-bar <%= stats.memory_usage_percent > 80 ? 'progress-bar-danger' : stats.memory_usage_percent > 60 ? 'progress-bar-warning' : 'progress-bar-success' %>"
|
|
184
|
+
role="progressbar"
|
|
185
|
+
aria-valuenow="<%= stats.memory_usage_percent %>"
|
|
186
|
+
aria-valuemin="0"
|
|
187
|
+
aria-valuemax="100"
|
|
188
|
+
style="width: <%= stats.memory_usage_percent %>%"
|
|
189
|
+
>
|
|
190
|
+
<%= stats.memory_usage_percent %>%
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</td>
|
|
194
|
+
</tr>
|
|
195
|
+
<tr>
|
|
196
|
+
<td><strong>Storage</strong></td>
|
|
197
|
+
<td><%= (stats.storage_capacity_bytes.to_f / 1024**4).round(2) %>
|
|
198
|
+
TB</td>
|
|
199
|
+
<td><%= (stats.storage_usage_bytes.to_f / 1024**4).round(2) %>
|
|
200
|
+
TB</td>
|
|
201
|
+
<td><%= (
|
|
202
|
+
(stats.storage_capacity_bytes - stats.storage_usage_bytes).to_f / 1024**4
|
|
203
|
+
).round(2) %>
|
|
204
|
+
TB</td>
|
|
205
|
+
<td>
|
|
206
|
+
<div class="progress" style="margin-bottom: 0;">
|
|
207
|
+
<div
|
|
208
|
+
class="progress-bar <%= stats.storage_usage_percent > 80 ? 'progress-bar-danger' : stats.storage_usage_percent > 60 ? 'progress-bar-warning' : 'progress-bar-success' %>"
|
|
209
|
+
role="progressbar"
|
|
210
|
+
aria-valuenow="<%= stats.storage_usage_percent %>"
|
|
211
|
+
aria-valuemin="0"
|
|
212
|
+
aria-valuemax="100"
|
|
213
|
+
style="width: <%= stats.storage_usage_percent %>%"
|
|
214
|
+
>
|
|
215
|
+
<%= stats.storage_usage_percent %>%
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</td>
|
|
219
|
+
</tr>
|
|
220
|
+
</tbody>
|
|
221
|
+
</table>
|
|
222
|
+
<% else %>
|
|
223
|
+
<p class="text-muted">Unable to fetch cluster resource statistics</p>
|
|
224
|
+
<% end %>
|
|
225
|
+
|
|
226
|
+
<hr style="margin: 30px 0;">
|
|
227
|
+
|
|
228
|
+
<h4>Images</h4>
|
|
229
|
+
<% if @compute_resource.available_images.empty? %>
|
|
230
|
+
<p class="text-muted">No images available</p>
|
|
231
|
+
<% else %>
|
|
232
|
+
<ul>
|
|
233
|
+
<% @compute_resource.available_images.each do |image| %>
|
|
234
|
+
<li><%= image.name %>
|
|
235
|
+
(<%= image.id %>)</li>
|
|
236
|
+
<% end %>
|
|
237
|
+
</ul>
|
|
238
|
+
<% end %>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<%= compute_specific_js(compute_resource, "host_edit") %>
|
|
2
|
+
<%= javascript "hosts", "host_edit", "host_edit_interfaces" %>
|
|
3
|
+
|
|
4
|
+
<%= select_f f,
|
|
5
|
+
:image_id,
|
|
6
|
+
compute_resource.available_images,
|
|
7
|
+
:id,
|
|
8
|
+
:name,
|
|
9
|
+
{ include_blank: _("Select Image") },
|
|
10
|
+
{
|
|
11
|
+
label: _("Image"),
|
|
12
|
+
help_inline: _("Operating system image to use (optional)"),
|
|
13
|
+
} %>
|
|
14
|
+
|
|
15
|
+
<%= select_f f,
|
|
16
|
+
:network_id,
|
|
17
|
+
compute_resource.available_networks,
|
|
18
|
+
:id,
|
|
19
|
+
:name,
|
|
20
|
+
{ include_blank: _("Select Network") },
|
|
21
|
+
{ label: _("Network"), help_inline: _("Network/subnet for the VM") } %>
|
|
22
|
+
|
|
23
|
+
<%= select_f f,
|
|
24
|
+
:storage_container,
|
|
25
|
+
compute_resource.available_storage_containers,
|
|
26
|
+
:id,
|
|
27
|
+
:name,
|
|
28
|
+
{ include_blank: _("Select Storage Container") },
|
|
29
|
+
{
|
|
30
|
+
label: _("Storage Container"),
|
|
31
|
+
help_inline: _("Storage container for VM disks"),
|
|
32
|
+
} %>
|
|
33
|
+
|
|
34
|
+
<%= select_f f,
|
|
35
|
+
:machine_type,
|
|
36
|
+
compute_resource.available_flavors,
|
|
37
|
+
:id,
|
|
38
|
+
:name,
|
|
39
|
+
{ include_blank: _("Select Flavor") },
|
|
40
|
+
{ label: _("Machine Type"), help_inline: _("VM size template (optional)") } %>
|
|
41
|
+
|
|
42
|
+
<%= counter_f f,
|
|
43
|
+
:cpus,
|
|
44
|
+
{ label: _("CPUs"), help_inline: _("Number of CPU cores"), min: 1, max: 32 } %>
|
|
45
|
+
|
|
46
|
+
<%= counter_f f,
|
|
47
|
+
:memory,
|
|
48
|
+
{
|
|
49
|
+
label: _("Memory (GB)"),
|
|
50
|
+
help_inline: _("Amount of memory in GB"),
|
|
51
|
+
min: 1,
|
|
52
|
+
max: 64,
|
|
53
|
+
} %>
|
|
54
|
+
|
|
55
|
+
<%= text_f f,
|
|
56
|
+
:disk_size_gb,
|
|
57
|
+
{
|
|
58
|
+
label: _("Disk Size (GB)"),
|
|
59
|
+
help_inline: _("Size of the boot disk in GB"),
|
|
60
|
+
value: 50,
|
|
61
|
+
} %>
|
|
62
|
+
|
|
63
|
+
<%= checkbox_f f,
|
|
64
|
+
:power_on,
|
|
65
|
+
{
|
|
66
|
+
label: _("Power On After Creation"),
|
|
67
|
+
help_inline:
|
|
68
|
+
_(
|
|
69
|
+
"Automatically power on the VM after it is created. If power-on fails, the VM will be deleted.",
|
|
70
|
+
),
|
|
71
|
+
checked: true,
|
|
72
|
+
} %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= text_f f, :size_gb, class: "col-md-2", label: _("Size (GB)"), label_size: "col-md-2", onchange: 'tfm.computeResource.capacityEdit(this)' %>
|