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,471 @@
|
|
|
1
|
+
module ForemanNutanix
|
|
2
|
+
class Nutanix < ComputeResource
|
|
3
|
+
validates :cluster, presence: true
|
|
4
|
+
|
|
5
|
+
def self.model_name
|
|
6
|
+
ComputeResource.model_name
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.provider_friendly_name
|
|
10
|
+
'Nutanix'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.available?
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def capabilities
|
|
18
|
+
%i[build power]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Foreman checks this for power management support
|
|
22
|
+
def supports_power?
|
|
23
|
+
Rails.logger.info '=== NUTANIX: supports_power? called ==='
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cluster=(cluster)
|
|
28
|
+
self.url = cluster
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def cluster
|
|
32
|
+
url
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def cluster_details
|
|
36
|
+
available_clusters.find { |cluster| cluster.ext_id == self.cluster }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_label
|
|
40
|
+
"#{name} (#{provider_friendly_name})"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def shim_server_url
|
|
44
|
+
ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def provided_attributes
|
|
48
|
+
super.merge({ mac: :mac })
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Test connection to the compute resource
|
|
52
|
+
def test_connection(_options = {})
|
|
53
|
+
Rails.logger.info "=== NUTANIX: Testing connection to cluster #{cluster} ==="
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Available clusters for selection
|
|
58
|
+
def available_clusters
|
|
59
|
+
base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
|
|
60
|
+
uri = URI("#{base.chomp('/')}/api/v1/clustermgmt/list-clusters")
|
|
61
|
+
response = Net::HTTP.get_response(uri)
|
|
62
|
+
data = JSON.parse(response.body)
|
|
63
|
+
|
|
64
|
+
data.map do |cluster|
|
|
65
|
+
cluster[:name] = "#{cluster['name']} (#{cluster['arch']})"
|
|
66
|
+
OpenStruct.new(cluster)
|
|
67
|
+
end
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
Rails.logger.error "=== NUTANIX: Error fetching clusters: #{e.message} ==="
|
|
70
|
+
[]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Available networks for VMs
|
|
74
|
+
def available_networks
|
|
75
|
+
Rails.logger.info '=== NUTANIX: Fetching available networks from shim server ==='
|
|
76
|
+
base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
|
|
77
|
+
uri = URI("#{base.chomp('/')}/api/v1/networking/list-networks")
|
|
78
|
+
response = Net::HTTP.get_response(uri)
|
|
79
|
+
data = JSON.parse(response.body)
|
|
80
|
+
|
|
81
|
+
data.map do |network|
|
|
82
|
+
OpenStruct.new({
|
|
83
|
+
id: network['ext_id'],
|
|
84
|
+
ext_id: network['ext_id'],
|
|
85
|
+
name: network['name'],
|
|
86
|
+
subnet_type: network['subnet_type'],
|
|
87
|
+
cluster_name: network['cluster_name'],
|
|
88
|
+
ipv4_subnet: network['ipv4_subnet'],
|
|
89
|
+
ipv4_gateway: network['ipv4_gateway'],
|
|
90
|
+
})
|
|
91
|
+
end
|
|
92
|
+
rescue StandardError => e
|
|
93
|
+
Rails.logger.error "=== NUTANIX: Error fetching networks: #{e.message} ==="
|
|
94
|
+
[]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Networks method (alias for available_networks)
|
|
98
|
+
def networks(opts = {})
|
|
99
|
+
Rails.logger.info "=== NUTANIX: NETWORKS called with opts: #{opts} ==="
|
|
100
|
+
available_networks
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Available storage containers for VMs
|
|
104
|
+
def available_storage_containers
|
|
105
|
+
Rails.logger.info '=== NUTANIX: Fetching available storage containers from shim server ==='
|
|
106
|
+
base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
|
|
107
|
+
uri = URI("#{base.chomp('/')}/api/v1/clustermgmt/list-storage-containers")
|
|
108
|
+
response = Net::HTTP.get_response(uri)
|
|
109
|
+
data = JSON.parse(response.body)
|
|
110
|
+
|
|
111
|
+
# Filter storage containers by the cluster associated with this compute resource
|
|
112
|
+
cluster_ext_id = cluster
|
|
113
|
+
Rails.logger.info "=== NUTANIX: Storage containers - total: #{data.count}, cluster_ext_id: #{cluster_ext_id} ==="
|
|
114
|
+
filtered_data = data.select { |container| container['cluster_ext_id'] == cluster_ext_id }
|
|
115
|
+
Rails.logger.info "=== NUTANIX: Storage containers - filtered: #{filtered_data.count} ==="
|
|
116
|
+
|
|
117
|
+
result = filtered_data.map do |container|
|
|
118
|
+
OpenStruct.new({
|
|
119
|
+
id: container['ext_id'],
|
|
120
|
+
ext_id: container['ext_id'],
|
|
121
|
+
name: container['name'],
|
|
122
|
+
cluster_name: container['cluster_name'],
|
|
123
|
+
max_capacity_bytes: container['max_capacity_bytes'],
|
|
124
|
+
replication_factor: container['replication_factor'],
|
|
125
|
+
is_compression_enabled: container['is_compression_enabled'],
|
|
126
|
+
})
|
|
127
|
+
end
|
|
128
|
+
Rails.logger.info "=== NUTANIX: Storage containers returning: #{result.map { |c| { id: c.id, name: c.name } }} ==="
|
|
129
|
+
result
|
|
130
|
+
rescue StandardError => e
|
|
131
|
+
Rails.logger.error "=== NUTANIX: Error fetching storage containers: #{e.message} ==="
|
|
132
|
+
[]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Cluster resource statistics (CPU, memory, storage usage)
|
|
136
|
+
def cluster_resource_stats
|
|
137
|
+
Rails.logger.info '=== NUTANIX: Fetching cluster resource stats from shim server ==='
|
|
138
|
+
base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
|
|
139
|
+
cluster_id = cluster
|
|
140
|
+
return nil unless cluster_id
|
|
141
|
+
|
|
142
|
+
uri = URI("#{base.chomp('/')}/api/v1/clustermgmt/clusters/#{cluster_id}/stats")
|
|
143
|
+
response = Net::HTTP.get_response(uri)
|
|
144
|
+
data = JSON.parse(response.body)
|
|
145
|
+
|
|
146
|
+
OpenStruct.new(data)
|
|
147
|
+
rescue StandardError => e
|
|
148
|
+
Rails.logger.error "=== NUTANIX: Error fetching cluster stats: #{e.message} ==="
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Available machine types/flavors
|
|
153
|
+
def available_flavors
|
|
154
|
+
Rails.logger.info '=== NUTANIX: Returning available flavors ==='
|
|
155
|
+
[OpenStruct.new({ id: 'small', name: 'Small (2 CPU, 4GB RAM)' })]
|
|
156
|
+
end
|
|
157
|
+
alias_method :machine_types, :available_flavors
|
|
158
|
+
|
|
159
|
+
# Available images
|
|
160
|
+
def available_images(_opts = {})
|
|
161
|
+
Rails.logger.info '=== NUTANIX: Fetching available images from shim server ==='
|
|
162
|
+
base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
|
|
163
|
+
uri = URI("#{base.chomp('/')}/api/v1/vmm/list-images")
|
|
164
|
+
response = Net::HTTP.get_response(uri)
|
|
165
|
+
data = JSON.parse(response.body)
|
|
166
|
+
|
|
167
|
+
# Filter images by cluster if cluster_location_ext_ids is available
|
|
168
|
+
cluster_ext_id = cluster
|
|
169
|
+
filtered_data = data.select do |image|
|
|
170
|
+
# Include image if it's available on this cluster
|
|
171
|
+
cluster_locations = image['cluster_location_ext_ids'] || []
|
|
172
|
+
cluster_locations.include?(cluster_ext_id)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
filtered_data.map do |image|
|
|
176
|
+
# Convert size to GB for display
|
|
177
|
+
size_gb = image['size_bytes'] ? (image['size_bytes'].to_f / 1024**3).round(2) : 0
|
|
178
|
+
display_name = "#{image['name']} (#{size_gb} GB)"
|
|
179
|
+
|
|
180
|
+
OpenStruct.new({
|
|
181
|
+
id: image['ext_id'],
|
|
182
|
+
ext_id: image['ext_id'],
|
|
183
|
+
name: display_name,
|
|
184
|
+
description: image['description'],
|
|
185
|
+
size_bytes: image['size_bytes'],
|
|
186
|
+
type: image['type'],
|
|
187
|
+
})
|
|
188
|
+
end
|
|
189
|
+
rescue StandardError => e
|
|
190
|
+
Rails.logger.error "=== NUTANIX: Error fetching images: #{e.message} ==="
|
|
191
|
+
[]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Core provisioning method - this is what Foreman calls to create a VM
|
|
195
|
+
def create_vm(args = {})
|
|
196
|
+
Rails.logger.info "=== NUTANIX: CREATE_VM CALLED with args: #{args} ==="
|
|
197
|
+
Rails.logger.info "=== NUTANIX: CREATE_VM args class: #{args.class}, keys: #{begin
|
|
198
|
+
args.keys
|
|
199
|
+
rescue StandardError
|
|
200
|
+
'N/A'
|
|
201
|
+
end} ==="
|
|
202
|
+
Rails.logger.info "=== NUTANIX: CREATE_VM network_id: #{args[:network_id] || args['network_id']}, storage_container: #{args[:storage_container] || args['storage_container']} ==="
|
|
203
|
+
|
|
204
|
+
vm = new_vm(args)
|
|
205
|
+
Rails.logger.info '=== NUTANIX: CREATE_VM calling vm.save ==='
|
|
206
|
+
vm.save
|
|
207
|
+
|
|
208
|
+
Rails.logger.info "=== NUTANIX: CREATE_VM returning VM: #{vm} ==="
|
|
209
|
+
result_vm = find_vm_by_uuid(vm.identity)
|
|
210
|
+
|
|
211
|
+
# Auto-exit build mode since we're creating bare VMs without OS installation
|
|
212
|
+
# This prevents the "Cancel Build" button from appearing
|
|
213
|
+
if args[:provision_method] == 'image' || true # Always exit build mode for now
|
|
214
|
+
Rails.logger.info '=== NUTANIX: Auto-exiting build mode for bare VM provisioning ==='
|
|
215
|
+
# NOTE: The host object will be available in the orchestration queue
|
|
216
|
+
# and will automatically exit build mode after VM creation completes
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
result_vm
|
|
220
|
+
rescue StandardError => e
|
|
221
|
+
Rails.logger.error "=== NUTANIX: CREATE_VM ERROR: #{e.message} ==="
|
|
222
|
+
raise e
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Called by Foreman after host orchestration completes
|
|
226
|
+
# This is where we exit build mode for bare VM provisioning
|
|
227
|
+
def setHostForOrchestration(host)
|
|
228
|
+
Rails.logger.info "=== NUTANIX: setHostForOrchestration called for host: #{host.name} ==="
|
|
229
|
+
super if defined?(super)
|
|
230
|
+
|
|
231
|
+
# Auto-exit build mode for bare VM provisioning
|
|
232
|
+
# Since we're not installing an OS, the host will never callback naturally
|
|
233
|
+
if host && host.build?
|
|
234
|
+
Rails.logger.info "=== NUTANIX: Auto-exiting build mode for host #{host.name} ==="
|
|
235
|
+
host.build = false
|
|
236
|
+
host.save!
|
|
237
|
+
end
|
|
238
|
+
rescue StandardError => e
|
|
239
|
+
Rails.logger.error "=== NUTANIX: Error in setHostForOrchestration: #{e.message} ==="
|
|
240
|
+
# Don't fail the whole provisioning if this fails
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# New VM instance (not persisted)
|
|
244
|
+
def new_vm(attr = {})
|
|
245
|
+
Rails.logger.info "=== NUTANIX: NEW_VM CALLED with attr: #{attr} ==="
|
|
246
|
+
Rails.logger.info "=== NUTANIX: NEW_VM attr keys: #{attr.keys} ==="
|
|
247
|
+
Rails.logger.info "=== NUTANIX: NEW_VM storage_container value: #{attr['storage_container'] || attr[:storage_container]} ==="
|
|
248
|
+
vm_attrs = vm_instance_defaults.merge(attr.to_hash.deep_symbolize_keys)
|
|
249
|
+
vm_attrs = normalize_vm_attrs(vm_attrs)
|
|
250
|
+
Rails.logger.info "=== NUTANIX: NEW_VM merged attrs: #{vm_attrs} ==="
|
|
251
|
+
Rails.logger.info "=== NUTANIX: NEW_VM merged keys: #{vm_attrs.keys} ==="
|
|
252
|
+
|
|
253
|
+
# Use the Foreman pattern - client.servers.new returns our VM model
|
|
254
|
+
client.servers.new(vm_attrs)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Default attributes for new VMs
|
|
258
|
+
# TODO: This is almost certainly wrong, namely 'zone' is not relevent.
|
|
259
|
+
def vm_instance_defaults
|
|
260
|
+
Rails.logger.info '=== NUTANIX: VM_INSTANCE_DEFAULTS called ==='
|
|
261
|
+
{
|
|
262
|
+
zone: 'default-zone',
|
|
263
|
+
machine_type: 'small',
|
|
264
|
+
cpus: 2,
|
|
265
|
+
memory: 4,
|
|
266
|
+
}
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Normalize VM attributes from form
|
|
270
|
+
def normalize_vm_attrs(vm_attrs)
|
|
271
|
+
Rails.logger.info "=== NUTANIX: NORMALIZE_VM_ATTRS called with: #{vm_attrs} ==="
|
|
272
|
+
normalized = vm_attrs.dup
|
|
273
|
+
|
|
274
|
+
# Convert string numbers to integers
|
|
275
|
+
normalized[:cpus] = normalized[:cpus].to_i if normalized[:cpus]
|
|
276
|
+
normalized[:memory] = normalized[:memory].to_i if normalized[:memory]
|
|
277
|
+
|
|
278
|
+
normalized
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Find existing VM by UUID
|
|
282
|
+
def find_vm_by_uuid(uuid)
|
|
283
|
+
Rails.logger.info "=== NUTANIX: FIND_VM_BY_UUID CALLED with uuid: #{uuid} ==="
|
|
284
|
+
return nil if uuid.nil? || uuid.to_s.strip.empty?
|
|
285
|
+
|
|
286
|
+
client.servers.get(uuid)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Foreman might call ready? on the compute resource
|
|
290
|
+
def ready?
|
|
291
|
+
Rails.logger.info '=== NUTANIX: Nutanix::ready? called ==='
|
|
292
|
+
true
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Start VM - called by Foreman for power on
|
|
296
|
+
def start_vm(uuid)
|
|
297
|
+
Rails.logger.info "=== NUTANIX: START_VM CALLED with uuid: #{uuid} ==="
|
|
298
|
+
actual_uuid = uuid.to_s.include?(':') ? uuid.to_s.split(':').last : uuid.to_s
|
|
299
|
+
|
|
300
|
+
base = shim_server_url
|
|
301
|
+
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}/power-state")
|
|
302
|
+
|
|
303
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
304
|
+
http.use_ssl = uri.scheme == 'https'
|
|
305
|
+
|
|
306
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
307
|
+
request['Content-Type'] = 'application/json'
|
|
308
|
+
request.body = { action: 'POWER_ON' }.to_json
|
|
309
|
+
|
|
310
|
+
response = http.request(request)
|
|
311
|
+
response.is_a?(Net::HTTPSuccess)
|
|
312
|
+
rescue StandardError => e
|
|
313
|
+
Rails.logger.error "=== NUTANIX: START_VM ERROR: #{e.message} ==="
|
|
314
|
+
raise e
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Stop VM - called by Foreman for power off
|
|
318
|
+
def stop_vm(uuid)
|
|
319
|
+
Rails.logger.info "=== NUTANIX: STOP_VM CALLED with uuid: #{uuid} ==="
|
|
320
|
+
actual_uuid = uuid.to_s.include?(':') ? uuid.to_s.split(':').last : uuid.to_s
|
|
321
|
+
|
|
322
|
+
base = shim_server_url
|
|
323
|
+
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}/power-state")
|
|
324
|
+
|
|
325
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
326
|
+
http.use_ssl = uri.scheme == 'https'
|
|
327
|
+
|
|
328
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
329
|
+
request['Content-Type'] = 'application/json'
|
|
330
|
+
request.body = { action: 'POWER_OFF' }.to_json
|
|
331
|
+
|
|
332
|
+
response = http.request(request)
|
|
333
|
+
response.is_a?(Net::HTTPSuccess)
|
|
334
|
+
rescue StandardError => e
|
|
335
|
+
Rails.logger.error "=== NUTANIX: STOP_VM ERROR: #{e.message} ==="
|
|
336
|
+
raise e
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Get VM power state - called by Foreman to check power status
|
|
340
|
+
def vm_power_state(vm)
|
|
341
|
+
Rails.logger.info "=== NUTANIX: VM_POWER_STATE CALLED for vm: #{vm} ==="
|
|
342
|
+
uuid = vm.respond_to?(:identity) ? vm.identity : vm.to_s
|
|
343
|
+
actual_uuid = uuid.to_s.include?(':') ? uuid.to_s.split(':').last : uuid.to_s
|
|
344
|
+
|
|
345
|
+
base = shim_server_url
|
|
346
|
+
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}/power-state")
|
|
347
|
+
response = Net::HTTP.get_response(uri)
|
|
348
|
+
|
|
349
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
350
|
+
data = JSON.parse(response.body)
|
|
351
|
+
state = data['power_state']
|
|
352
|
+
Rails.logger.info "=== NUTANIX: VM_POWER_STATE returning: #{state} ==="
|
|
353
|
+
# Return hash that Foreman expects
|
|
354
|
+
{ state: (state == 'ON') ? 'running' : 'off' }
|
|
355
|
+
else
|
|
356
|
+
{ state: 'unknown' }
|
|
357
|
+
end
|
|
358
|
+
rescue StandardError => e
|
|
359
|
+
Rails.logger.error "=== NUTANIX: VM_POWER_STATE ERROR: #{e.message} ==="
|
|
360
|
+
{ state: 'unknown' }
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Power operations - called by Foreman's power_status API
|
|
364
|
+
def power(uuid, action)
|
|
365
|
+
Rails.logger.info "=== NUTANIX: POWER CALLED with uuid: #{uuid}, action: #{action} ==="
|
|
366
|
+
case action.to_s
|
|
367
|
+
when 'start', 'on'
|
|
368
|
+
start_vm(uuid)
|
|
369
|
+
when 'stop', 'off'
|
|
370
|
+
stop_vm(uuid)
|
|
371
|
+
when 'state', 'status'
|
|
372
|
+
vm = find_vm_by_uuid(uuid)
|
|
373
|
+
vm&.state || 'unknown'
|
|
374
|
+
else
|
|
375
|
+
Rails.logger.warn "=== NUTANIX: Unknown power action: #{action} ==="
|
|
376
|
+
false
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Destroy VM
|
|
381
|
+
def destroy_vm(uuid)
|
|
382
|
+
Rails.logger.info "=== NUTANIX: DESTROY_VM CALLED with uuid: #{uuid} ==="
|
|
383
|
+
|
|
384
|
+
return true if uuid.nil? || uuid.to_s.strip.empty?
|
|
385
|
+
|
|
386
|
+
# Extract the actual UUID if it has a prefix
|
|
387
|
+
actual_uuid = uuid.to_s.include?(':') ? uuid.to_s.split(':').last : uuid.to_s
|
|
388
|
+
|
|
389
|
+
# Call the shim server to delete the VM
|
|
390
|
+
base = shim_server_url
|
|
391
|
+
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}")
|
|
392
|
+
|
|
393
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
394
|
+
http.use_ssl = uri.scheme == 'https'
|
|
395
|
+
|
|
396
|
+
request = Net::HTTP::Delete.new(uri.path)
|
|
397
|
+
response = http.request(request)
|
|
398
|
+
|
|
399
|
+
if response.is_a?(Net::HTTPNoContent) || response.is_a?(Net::HTTPSuccess)
|
|
400
|
+
Rails.logger.info "=== NUTANIX: VM #{actual_uuid} deleted successfully ==="
|
|
401
|
+
true
|
|
402
|
+
else
|
|
403
|
+
error_message = "Failed to delete VM: #{response.code} - #{response.body}"
|
|
404
|
+
Rails.logger.error "=== NUTANIX: #{error_message} ==="
|
|
405
|
+
raise StandardError, error_message
|
|
406
|
+
end
|
|
407
|
+
rescue ActiveRecord::RecordNotFound
|
|
408
|
+
true
|
|
409
|
+
rescue StandardError => e
|
|
410
|
+
Rails.logger.error "=== NUTANIX: Error in destroy_vm: #{e.message} ==="
|
|
411
|
+
raise e
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Console access
|
|
415
|
+
# TODO: Untested, probably doesn't work
|
|
416
|
+
def console(uuid)
|
|
417
|
+
Rails.logger.info "=== NUTANIX: CONSOLE CALLED with uuid: #{uuid} ==="
|
|
418
|
+
vm = find_vm_by_uuid(uuid)
|
|
419
|
+
{
|
|
420
|
+
'output' => 'Mock console output', 'timestamp' => Time.now.utc,
|
|
421
|
+
:type => 'log', :name => vm.name
|
|
422
|
+
}
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Associate host with VM
|
|
426
|
+
def associated_host(vm)
|
|
427
|
+
Rails.logger.info "=== NUTANIX: ASSOCIATED_HOST CALLED for vm: #{vm.name} ==="
|
|
428
|
+
associate_by('ip', [vm.vm_ip_address, vm.private_ip_address])
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# User data support
|
|
432
|
+
def user_data_supported?
|
|
433
|
+
true
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# New volume creation
|
|
437
|
+
# TODO: Not sure we can create new volumes in Nutanix?
|
|
438
|
+
def new_volume(attrs = {})
|
|
439
|
+
Rails.logger.info "=== NUTANIX: NEW_VOLUME CALLED with attrs: #{attrs} ==="
|
|
440
|
+
OpenStruct.new(attrs)
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# List all VMs
|
|
444
|
+
def vms(attrs = {})
|
|
445
|
+
Rails.logger.info "=== NUTANIX: VMS CALLED with attrs: #{attrs} ==="
|
|
446
|
+
client.servers(attrs)
|
|
447
|
+
rescue StandardError => e
|
|
448
|
+
Rails.logger.error "=== NUTANIX: VMS ERROR: #{e.message} ==="
|
|
449
|
+
raise e
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# Host attributes for VM creation
|
|
453
|
+
def host_create_attrs(host)
|
|
454
|
+
Rails.logger.info "=== NUTANIX: HOST_CREATE_ATTRS CALLED for host: #{host.name} ==="
|
|
455
|
+
super
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Validate host before provisioning
|
|
459
|
+
def validate_host(host)
|
|
460
|
+
Rails.logger.info "=== NUTANIX: VALIDATE_HOST CALLED for host: #{host.name} ==="
|
|
461
|
+
super
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
private
|
|
465
|
+
|
|
466
|
+
def client
|
|
467
|
+
Rails.logger.info "=== NUTANIX: Creating client for cluster #{cluster} ==="
|
|
468
|
+
@client ||= NutanixAdapter.new(cluster)
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
end
|