foreman_nutanix 0.1.4 → 0.1.5
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 +4 -4
- data/Rakefile +0 -6
- data/app/lib/foreman_nutanix/nutanix_adapter.rb +14 -10
- data/app/models/foreman_nutanix/nutanix.rb +24 -63
- data/app/models/foreman_nutanix/nutanix_compute.rb +42 -41
- data/app/views/compute_resources_vms/show/_nutanix.html.erb +2 -1
- data/lib/foreman_nutanix/shim_client.rb +93 -0
- data/lib/foreman_nutanix/version.rb +1 -1
- data/lib/foreman_nutanix.rb +1 -0
- data/test/README.md +45 -0
- data/test/nutanix_compute_test.rb +71 -0
- data/test/shim_client_test.rb +257 -0
- data/test/test_helper.rb +5 -0
- metadata +12 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4f699c1534db4e1dc7d31dab1ed7af4ea04f69ec364f1ade64b243c8b3ab19d
|
|
4
|
+
data.tar.gz: 5c82e86ec3fdd45fc9a282cdf7d5c688b16931fa656b7dda6407175bf48d551d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c5b08a2a38277497d102b4b383daf31c5a5b407acfd33df940499e86bfe39016dea843faa21b98596a703e94f5280f55b655c0c72df2821f6e00684e485ea6b5
|
|
7
|
+
data.tar.gz: 32e6620c3dff62d75fe1dc17a101648d076f11928c21e1f571642c26286b53fa254e4e643ae165fa2ac1637c3e20c2d25d3786120ba01fff073bd6238601c8aa
|
data/Rakefile
CHANGED
|
@@ -20,8 +20,6 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
|
20
20
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
|
|
24
|
-
|
|
25
23
|
Bundler::GemHelper.install_tasks
|
|
26
24
|
|
|
27
25
|
require 'rake/testtask'
|
|
@@ -43,7 +41,3 @@ rescue StandardError => _e
|
|
|
43
41
|
rescue LoadError
|
|
44
42
|
puts 'Rubocop not loaded.'
|
|
45
43
|
end
|
|
46
|
-
|
|
47
|
-
task :default do
|
|
48
|
-
Rake::Task['rubocop'].execute
|
|
49
|
-
end
|
|
@@ -33,15 +33,13 @@ module ForemanNutanix
|
|
|
33
33
|
Rails.logger.info "=== NUTANIX: ServersCollection::get called with uuid: #{uuid} ==="
|
|
34
34
|
|
|
35
35
|
# Extract the actual UUID if it has a prefix
|
|
36
|
-
actual_uuid =
|
|
36
|
+
actual_uuid = ShimClient.normalize_uuid(uuid)
|
|
37
37
|
|
|
38
38
|
# Fetch full VM details from shim server
|
|
39
|
-
|
|
40
|
-
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}")
|
|
41
|
-
response = Net::HTTP.get_response(uri)
|
|
39
|
+
response = shim.get("/api/v1/vmm/vms/#{actual_uuid}")
|
|
42
40
|
|
|
43
|
-
if response.
|
|
44
|
-
data =
|
|
41
|
+
if response.success?
|
|
42
|
+
data = response.json
|
|
45
43
|
|
|
46
44
|
total_cpus = (data['num_sockets'] || 1) * (data['num_cores_per_socket'] || 1)
|
|
47
45
|
memory_gb = data['memory_size_bytes'] ? (data['memory_size_bytes'].to_f / 1024**3).round : 4
|
|
@@ -82,10 +80,7 @@ module ForemanNutanix
|
|
|
82
80
|
Rails.logger.info "=== NUTANIX: ServersCollection::all called with opts: #{opts} ==="
|
|
83
81
|
|
|
84
82
|
# Fetch VMs from shim server (now includes MAC and IP)
|
|
85
|
-
|
|
86
|
-
uri = URI("#{base.chomp('/')}/api/v1/vmm/list-vms")
|
|
87
|
-
response = Net::HTTP.get_response(uri)
|
|
88
|
-
data = JSON.parse(response.body)
|
|
83
|
+
data = shim.get('/api/v1/vmm/list-vms').json
|
|
89
84
|
|
|
90
85
|
# Filter VMs by cluster
|
|
91
86
|
filtered_data = data.select { |vm| vm['cluster_ext_id'] == @cluster }
|
|
@@ -106,6 +101,7 @@ module ForemanNutanix
|
|
|
106
101
|
ip_addresses: vm_data['ip_addresses'] || [],
|
|
107
102
|
create_time: vm_data['create_time'],
|
|
108
103
|
disk_size_gb: vm_data['disk_size_bytes'].to_i / (1024**3),
|
|
104
|
+
gpus: vm_data['gpus'],
|
|
109
105
|
})
|
|
110
106
|
vm.instance_variable_set(:@persisted, true)
|
|
111
107
|
vm
|
|
@@ -114,6 +110,14 @@ module ForemanNutanix
|
|
|
114
110
|
Rails.logger.error "=== NUTANIX: Error fetching VMs: #{e.message} ==="
|
|
115
111
|
[]
|
|
116
112
|
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
# ServersCollection holds no reference to the ComputeResource, so it builds
|
|
117
|
+
# its own client off the shim server address in the environment.
|
|
118
|
+
def shim
|
|
119
|
+
@shim ||= ShimClient.new
|
|
120
|
+
end
|
|
117
121
|
end
|
|
118
122
|
end
|
|
119
123
|
end
|
|
@@ -40,8 +40,9 @@ module ForemanNutanix
|
|
|
40
40
|
"#{name} (#{provider_friendly_name})"
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
# Kept public: app/views/compute_resources/show/_nutanix.html.erb renders it.
|
|
43
44
|
def shim_server_url
|
|
44
|
-
|
|
45
|
+
ShimClient.default_base_url
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
def provided_attributes
|
|
@@ -56,10 +57,7 @@ module ForemanNutanix
|
|
|
56
57
|
|
|
57
58
|
# Available clusters for selection
|
|
58
59
|
def available_clusters
|
|
59
|
-
|
|
60
|
-
uri = URI("#{base.chomp('/')}/api/v1/clustermgmt/list-clusters")
|
|
61
|
-
response = Net::HTTP.get_response(uri)
|
|
62
|
-
data = JSON.parse(response.body)
|
|
60
|
+
data = shim.get('/api/v1/clustermgmt/list-clusters').json
|
|
63
61
|
|
|
64
62
|
data.map do |cluster|
|
|
65
63
|
cluster[:name] = "#{cluster['name']} (#{cluster['arch']})"
|
|
@@ -80,10 +78,7 @@ module ForemanNutanix
|
|
|
80
78
|
# Available networks for VMs
|
|
81
79
|
def available_networks
|
|
82
80
|
Rails.logger.info '=== NUTANIX: Fetching available networks from shim server ==='
|
|
83
|
-
|
|
84
|
-
uri = URI("#{base.chomp('/')}/api/v1/networking/list-networks")
|
|
85
|
-
response = Net::HTTP.get_response(uri)
|
|
86
|
-
data = JSON.parse(response.body)
|
|
81
|
+
data = shim.get('/api/v1/networking/list-networks').json
|
|
87
82
|
|
|
88
83
|
# Filter networks by the cluster associated with this compute resource
|
|
89
84
|
cluster_ext_id = cluster
|
|
@@ -114,10 +109,7 @@ module ForemanNutanix
|
|
|
114
109
|
# Available storage containers for VMs
|
|
115
110
|
def available_storage_containers
|
|
116
111
|
Rails.logger.info '=== NUTANIX: Fetching available storage containers from shim server ==='
|
|
117
|
-
|
|
118
|
-
uri = URI("#{base.chomp('/')}/api/v1/clustermgmt/list-storage-containers")
|
|
119
|
-
response = Net::HTTP.get_response(uri)
|
|
120
|
-
data = JSON.parse(response.body)
|
|
112
|
+
data = shim.get('/api/v1/clustermgmt/list-storage-containers').json
|
|
121
113
|
|
|
122
114
|
# Filter storage containers by the cluster associated with this compute resource
|
|
123
115
|
cluster_ext_id = cluster
|
|
@@ -146,13 +138,10 @@ module ForemanNutanix
|
|
|
146
138
|
# Cluster resource statistics (CPU, memory, storage usage)
|
|
147
139
|
def cluster_resource_stats
|
|
148
140
|
Rails.logger.info '=== NUTANIX: Fetching cluster resource stats from shim server ==='
|
|
149
|
-
base = ENV['NUTANIX_SHIM_SERVER_ADDR'] || 'http://localhost:8000'
|
|
150
141
|
cluster_id = cluster
|
|
151
142
|
return nil unless cluster_id
|
|
152
143
|
|
|
153
|
-
|
|
154
|
-
response = Net::HTTP.get_response(uri)
|
|
155
|
-
data = JSON.parse(response.body)
|
|
144
|
+
data = shim.get("/api/v1/clustermgmt/clusters/#{cluster_id}/stats").json
|
|
156
145
|
|
|
157
146
|
OpenStruct.new(data)
|
|
158
147
|
rescue StandardError => e
|
|
@@ -163,10 +152,7 @@ module ForemanNutanix
|
|
|
163
152
|
# Available images
|
|
164
153
|
def available_images(_opts = {})
|
|
165
154
|
Rails.logger.info '=== NUTANIX: Fetching available images from shim server ==='
|
|
166
|
-
|
|
167
|
-
uri = URI("#{base.chomp('/')}/api/v1/vmm/list-images")
|
|
168
|
-
response = Net::HTTP.get_response(uri)
|
|
169
|
-
data = JSON.parse(response.body)
|
|
155
|
+
data = shim.get('/api/v1/vmm/list-images').json
|
|
170
156
|
|
|
171
157
|
# Filter images by cluster if cluster_location_ext_ids is available
|
|
172
158
|
cluster_ext_id = cluster
|
|
@@ -286,20 +272,10 @@ module ForemanNutanix
|
|
|
286
272
|
# Start VM - called by Foreman for power on
|
|
287
273
|
def start_vm(uuid)
|
|
288
274
|
Rails.logger.info "=== NUTANIX: START_VM CALLED with uuid: #{uuid} ==="
|
|
289
|
-
actual_uuid =
|
|
275
|
+
actual_uuid = ShimClient.normalize_uuid(uuid)
|
|
290
276
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
295
|
-
http.use_ssl = uri.scheme == 'https'
|
|
296
|
-
|
|
297
|
-
request = Net::HTTP::Post.new(uri.path)
|
|
298
|
-
request['Content-Type'] = 'application/json'
|
|
299
|
-
request.body = { action: 'POWER_ON' }.to_json
|
|
300
|
-
|
|
301
|
-
response = http.request(request)
|
|
302
|
-
response.is_a?(Net::HTTPSuccess)
|
|
277
|
+
response = shim.post("/api/v1/vmm/vms/#{actual_uuid}/power-state", { action: 'POWER_ON' })
|
|
278
|
+
response.success?
|
|
303
279
|
rescue StandardError => e
|
|
304
280
|
Rails.logger.error "=== NUTANIX: START_VM ERROR: #{e.message} ==="
|
|
305
281
|
raise e
|
|
@@ -308,20 +284,10 @@ module ForemanNutanix
|
|
|
308
284
|
# Stop VM - called by Foreman for power off
|
|
309
285
|
def stop_vm(uuid)
|
|
310
286
|
Rails.logger.info "=== NUTANIX: STOP_VM CALLED with uuid: #{uuid} ==="
|
|
311
|
-
actual_uuid =
|
|
312
|
-
|
|
313
|
-
base = shim_server_url
|
|
314
|
-
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}/power-state")
|
|
315
|
-
|
|
316
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
317
|
-
http.use_ssl = uri.scheme == 'https'
|
|
287
|
+
actual_uuid = ShimClient.normalize_uuid(uuid)
|
|
318
288
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
request.body = { action: 'POWER_OFF' }.to_json
|
|
322
|
-
|
|
323
|
-
response = http.request(request)
|
|
324
|
-
response.is_a?(Net::HTTPSuccess)
|
|
289
|
+
response = shim.post("/api/v1/vmm/vms/#{actual_uuid}/power-state", { action: 'POWER_OFF' })
|
|
290
|
+
response.success?
|
|
325
291
|
rescue StandardError => e
|
|
326
292
|
Rails.logger.error "=== NUTANIX: STOP_VM ERROR: #{e.message} ==="
|
|
327
293
|
raise e
|
|
@@ -331,14 +297,12 @@ module ForemanNutanix
|
|
|
331
297
|
def vm_power_state(vm)
|
|
332
298
|
Rails.logger.info "=== NUTANIX: VM_POWER_STATE CALLED for vm: #{vm} ==="
|
|
333
299
|
uuid = vm.respond_to?(:identity) ? vm.identity : vm.to_s
|
|
334
|
-
actual_uuid =
|
|
300
|
+
actual_uuid = ShimClient.normalize_uuid(uuid)
|
|
335
301
|
|
|
336
|
-
|
|
337
|
-
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}/power-state")
|
|
338
|
-
response = Net::HTTP.get_response(uri)
|
|
302
|
+
response = shim.get("/api/v1/vmm/vms/#{actual_uuid}/power-state")
|
|
339
303
|
|
|
340
|
-
if response.
|
|
341
|
-
data =
|
|
304
|
+
if response.success?
|
|
305
|
+
data = response.json
|
|
342
306
|
state = data['power_state']
|
|
343
307
|
Rails.logger.info "=== NUTANIX: VM_POWER_STATE returning: #{state} ==="
|
|
344
308
|
# Return hash that Foreman expects
|
|
@@ -375,19 +339,12 @@ module ForemanNutanix
|
|
|
375
339
|
return true if uuid.nil? || uuid.to_s.strip.empty?
|
|
376
340
|
|
|
377
341
|
# Extract the actual UUID if it has a prefix
|
|
378
|
-
actual_uuid =
|
|
342
|
+
actual_uuid = ShimClient.normalize_uuid(uuid)
|
|
379
343
|
|
|
380
344
|
# Call the shim server to delete the VM
|
|
381
|
-
|
|
382
|
-
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}")
|
|
383
|
-
|
|
384
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
385
|
-
http.use_ssl = uri.scheme == 'https'
|
|
386
|
-
|
|
387
|
-
request = Net::HTTP::Delete.new(uri.path)
|
|
388
|
-
response = http.request(request)
|
|
345
|
+
response = shim.delete("/api/v1/vmm/vms/#{actual_uuid}")
|
|
389
346
|
|
|
390
|
-
if response.
|
|
347
|
+
if response.no_content? || response.success?
|
|
391
348
|
Rails.logger.info "=== NUTANIX: VM #{actual_uuid} deleted successfully ==="
|
|
392
349
|
true
|
|
393
350
|
else
|
|
@@ -455,6 +412,10 @@ module ForemanNutanix
|
|
|
455
412
|
|
|
456
413
|
private
|
|
457
414
|
|
|
415
|
+
def shim
|
|
416
|
+
@shim ||= ShimClient.new(shim_server_url)
|
|
417
|
+
end
|
|
418
|
+
|
|
458
419
|
def client
|
|
459
420
|
Rails.logger.info "=== NUTANIX: Creating client for cluster #{cluster} ==="
|
|
460
421
|
@client ||= NutanixAdapter.new(cluster)
|
|
@@ -46,8 +46,10 @@ module ForemanNutanix
|
|
|
46
46
|
@boot_method = args[:boot_method]
|
|
47
47
|
@secure_boot = args[:secure_boot]
|
|
48
48
|
|
|
49
|
-
# GPUs
|
|
50
|
-
|
|
49
|
+
# GPUs. Defaults to [] rather than nil: the shim's /list-vms response
|
|
50
|
+
# carries no gpus field, so VMs built by ServersCollection#all never
|
|
51
|
+
# receive one, and the index view calls #gpus.empty? on every row.
|
|
52
|
+
@gpus = args[:gpus] || []
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
# Required by Foreman - indicates if VM exists
|
|
@@ -99,20 +101,10 @@ module ForemanNutanix
|
|
|
99
101
|
Rails.logger.info "=== NUTANIX: Provisioning VM with request: #{provision_request} ==="
|
|
100
102
|
|
|
101
103
|
# Call the shim server to provision the VM
|
|
102
|
-
|
|
103
|
-
uri = URI("#{base.chomp('/')}/api/v1/vmm/provision-vm")
|
|
104
|
+
response = shim.post('/api/v1/vmm/provision-vm', provision_request)
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
request = Net::HTTP::Post.new(uri.path)
|
|
109
|
-
request['Content-Type'] = 'application/json'
|
|
110
|
-
request.body = provision_request.to_json
|
|
111
|
-
|
|
112
|
-
response = http.request(request)
|
|
113
|
-
|
|
114
|
-
if response.is_a?(Net::HTTPSuccess)
|
|
115
|
-
result = JSON.parse(response.body)
|
|
106
|
+
if response.success?
|
|
107
|
+
result = response.json
|
|
116
108
|
Rails.logger.info "=== NUTANIX: VM provisioned successfully: #{result} ==="
|
|
117
109
|
|
|
118
110
|
# Update identity with the real ext_id from Nutanix
|
|
@@ -176,22 +168,12 @@ module ForemanNutanix
|
|
|
176
168
|
return false unless persisted?
|
|
177
169
|
|
|
178
170
|
# Extract actual UUID (handle ZXJnb24=:uuid format)
|
|
179
|
-
actual_uuid =
|
|
171
|
+
actual_uuid = ShimClient.normalize_uuid(@identity)
|
|
180
172
|
|
|
181
173
|
# Call the shim server to power on the VM
|
|
182
|
-
|
|
183
|
-
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}/power-state")
|
|
184
|
-
|
|
185
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
186
|
-
http.use_ssl = uri.scheme == 'https'
|
|
187
|
-
|
|
188
|
-
request = Net::HTTP::Post.new(uri.path)
|
|
189
|
-
request['Content-Type'] = 'application/json'
|
|
190
|
-
request.body = { action: 'POWER_ON' }.to_json
|
|
174
|
+
response = shim.post("/api/v1/vmm/vms/#{actual_uuid}/power-state", { action: 'POWER_ON' })
|
|
191
175
|
|
|
192
|
-
response
|
|
193
|
-
|
|
194
|
-
if response.is_a?(Net::HTTPSuccess)
|
|
176
|
+
if response.success?
|
|
195
177
|
Rails.logger.info "=== NUTANIX: VM #{actual_uuid} powered on successfully ==="
|
|
196
178
|
@power_state = 'ON'
|
|
197
179
|
true
|
|
@@ -211,22 +193,12 @@ module ForemanNutanix
|
|
|
211
193
|
return false unless persisted?
|
|
212
194
|
|
|
213
195
|
# Extract actual UUID (handle ZXJnb24=:uuid format)
|
|
214
|
-
actual_uuid =
|
|
196
|
+
actual_uuid = ShimClient.normalize_uuid(@identity)
|
|
215
197
|
|
|
216
198
|
# Call the shim server to power off the VM
|
|
217
|
-
|
|
218
|
-
uri = URI("#{base.chomp('/')}/api/v1/vmm/vms/#{actual_uuid}/power-state")
|
|
219
|
-
|
|
220
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
221
|
-
http.use_ssl = uri.scheme == 'https'
|
|
222
|
-
|
|
223
|
-
request = Net::HTTP::Post.new(uri.path)
|
|
224
|
-
request['Content-Type'] = 'application/json'
|
|
225
|
-
request.body = { action: 'POWER_OFF' }.to_json
|
|
226
|
-
|
|
227
|
-
response = http.request(request)
|
|
199
|
+
response = shim.post("/api/v1/vmm/vms/#{actual_uuid}/power-state", { action: 'POWER_OFF' })
|
|
228
200
|
|
|
229
|
-
if response.
|
|
201
|
+
if response.success?
|
|
230
202
|
Rails.logger.info "=== NUTANIX: VM #{actual_uuid} powered off successfully ==="
|
|
231
203
|
@power_state = 'OFF'
|
|
232
204
|
true
|
|
@@ -240,6 +212,20 @@ module ForemanNutanix
|
|
|
240
212
|
raise e
|
|
241
213
|
end
|
|
242
214
|
|
|
215
|
+
# Human-readable GPU labels for the views, e.g. "NVIDIA Tesla T4".
|
|
216
|
+
# The shim returns each GPU as a hash; a shim older than 0.1.5 omits the
|
|
217
|
+
# field on /list-vms entirely, which #initialize turns into [].
|
|
218
|
+
def gpu_labels
|
|
219
|
+
@gpus.map do |gpu|
|
|
220
|
+
next gpu.to_s unless gpu.is_a?(Hash)
|
|
221
|
+
|
|
222
|
+
name = gpu['name']
|
|
223
|
+
name = gpu['device_id'] if blank_gpu_field?(name)
|
|
224
|
+
label = [gpu['vendor'], name].reject { |part| blank_gpu_field?(part) }.join(' ')
|
|
225
|
+
label.empty? ? 'GPU' : label
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
243
229
|
# Required by Foreman - CPU count
|
|
244
230
|
def cpu
|
|
245
231
|
Rails.logger.info '=== NUTANIX: NutanixCompute::cpu called ==='
|
|
@@ -377,11 +363,26 @@ module ForemanNutanix
|
|
|
377
363
|
yield if block_given?
|
|
378
364
|
end
|
|
379
365
|
|
|
366
|
+
# Deliberately not ActiveSupport's #blank?: this class is loaded without
|
|
367
|
+
# Rails in test/nutanix_compute_test.rb.
|
|
368
|
+
def blank_gpu_field?(value)
|
|
369
|
+
value.nil? || value.to_s.strip.empty?
|
|
370
|
+
end
|
|
371
|
+
private :blank_gpu_field?
|
|
372
|
+
|
|
380
373
|
# Required by Foreman - destroy VM
|
|
381
374
|
def destroy
|
|
382
375
|
Rails.logger.info '=== NUTANIX: NutanixCompute::destroy called ==='
|
|
383
376
|
@persisted = false
|
|
384
377
|
true
|
|
385
378
|
end
|
|
379
|
+
|
|
380
|
+
private
|
|
381
|
+
|
|
382
|
+
# NutanixCompute holds no reference to the ComputeResource, so it builds its
|
|
383
|
+
# own client off the shim server address in the environment.
|
|
384
|
+
def shim
|
|
385
|
+
@shim ||= ShimClient.new
|
|
386
|
+
end
|
|
386
387
|
end
|
|
387
388
|
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'net/http'
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module ForemanNutanix
|
|
6
|
+
# Single HTTP seam for every call the plugin makes to the nutanix-shim-server.
|
|
7
|
+
#
|
|
8
|
+
# Deliberately plain Ruby: no Rails, no ActiveSupport, no Rails.logger. It must
|
|
9
|
+
# stay require-able from a bare minitest process with no Rails booted.
|
|
10
|
+
#
|
|
11
|
+
# Callers get back a Response value object rather than parsed data so that each
|
|
12
|
+
# one keeps its own (and today wildly inconsistent) success/failure branching.
|
|
13
|
+
class ShimClient
|
|
14
|
+
DEFAULT_BASE_URL = 'http://localhost:8000'.freeze
|
|
15
|
+
|
|
16
|
+
# Thin value object over a Net::HTTPResponse.
|
|
17
|
+
class Response
|
|
18
|
+
def initialize(raw)
|
|
19
|
+
@raw = raw
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def success?
|
|
23
|
+
@raw.is_a?(Net::HTTPSuccess)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Net::HTTPNoContent is the 204 response class. Exposed separately because
|
|
27
|
+
# destroy_vm historically tested for it explicitly alongside success?.
|
|
28
|
+
def no_content?
|
|
29
|
+
@raw.is_a?(Net::HTTPNoContent)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def code
|
|
33
|
+
@raw.code
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def body
|
|
37
|
+
@raw.body
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Intentionally does NOT rescue: a non-JSON body (an HTML 500 page, say)
|
|
41
|
+
# must keep raising JSON::ParserError out to the caller, exactly as the
|
|
42
|
+
# inline JSON.parse(response.body) calls did before.
|
|
43
|
+
def json
|
|
44
|
+
JSON.parse(body)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.default_base_url
|
|
49
|
+
ENV['NUTANIX_SHIM_SERVER_ADDR'] || DEFAULT_BASE_URL
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Strips the "prefix:" from identities of the form "ZXJnb24=:<uuid>".
|
|
53
|
+
def self.normalize_uuid(uuid)
|
|
54
|
+
uuid.to_s.include?(':') ? uuid.to_s.split(':').last : uuid.to_s
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
attr_reader :base_url
|
|
58
|
+
|
|
59
|
+
# http: is an internal seam for tests only; production code never passes it.
|
|
60
|
+
def initialize(base_url = nil, http: Net::HTTP)
|
|
61
|
+
@base_url = (base_url || self.class.default_base_url).to_s.chomp('/')
|
|
62
|
+
@http = http
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def get(path)
|
|
66
|
+
execute(Net::HTTP::Get, path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def post(path, body = nil)
|
|
70
|
+
execute(Net::HTTP::Post, path, body)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def delete(path)
|
|
74
|
+
execute(Net::HTTP::Delete, path)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def execute(verb_class, path, body = nil)
|
|
80
|
+
uri = URI("#{@base_url}#{path}")
|
|
81
|
+
connection = @http.new(uri.host, uri.port)
|
|
82
|
+
connection.use_ssl = uri.scheme == 'https'
|
|
83
|
+
|
|
84
|
+
request = verb_class.new(uri.request_uri)
|
|
85
|
+
unless body.nil?
|
|
86
|
+
request['Content-Type'] = 'application/json'
|
|
87
|
+
request.body = body.to_json
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
Response.new(connection.request(request))
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
data/lib/foreman_nutanix.rb
CHANGED
data/test/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Tests
|
|
2
|
+
|
|
3
|
+
Standalone minitest. No Rails, no Foreman, no database, no `test/dummy`.
|
|
4
|
+
|
|
5
|
+
Run with:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
bundle exec rake test
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`test_helper.rb` requires only `lib/foreman_nutanix/shim_client.rb`, which is
|
|
12
|
+
deliberately plain Ruby (net/http, json, uri). That is the whole reason the
|
|
13
|
+
suite boots in milliseconds with nothing installed but minitest and rake.
|
|
14
|
+
|
|
15
|
+
## What this covers
|
|
16
|
+
|
|
17
|
+
`ForemanNutanix::ShimClient` — base URL resolution, trailing-slash chomping,
|
|
18
|
+
verb and path construction, JSON body encoding, `success?` / `no_content?` /
|
|
19
|
+
`code` / `body` / `json` on the response value object, and `normalize_uuid`.
|
|
20
|
+
HTTP is faked through the `http:` keyword seam; no network, no webmock.
|
|
21
|
+
|
|
22
|
+
## What cannot be tested here
|
|
23
|
+
|
|
24
|
+
Everything that needs a booted Foreman:
|
|
25
|
+
|
|
26
|
+
- `ForemanNutanix::Nutanix` — it is a `ComputeResource` subclass, and `cluster`
|
|
27
|
+
is stored in the inherited `url` column, so instantiating it at all needs
|
|
28
|
+
ActiveRecord, the Foreman schema, and the compute-resource STI machinery.
|
|
29
|
+
- `ForemanNutanix::HostManagedExtensions` — a concern mixed into `Host::Managed`.
|
|
30
|
+
- The `.erb` views under `app/views/`.
|
|
31
|
+
- The API controller extensions under `app/controllers/`.
|
|
32
|
+
- Anything touching the database, `Rails.logger`, or `Foreman::Plugin`.
|
|
33
|
+
|
|
34
|
+
`NutanixCompute` and `NutanixAdapter` are plain objects but still call
|
|
35
|
+
`Rails.logger` on nearly every method, so they are not reachable from here
|
|
36
|
+
either without stubbing Rails.
|
|
37
|
+
|
|
38
|
+
## What a full harness would need
|
|
39
|
+
|
|
40
|
+
1. A `test/dummy` Rails app generated against a Foreman core checkout.
|
|
41
|
+
2. That Foreman core pinned to a known version (the plugin requires >= 3.14.0),
|
|
42
|
+
since plugin tests load Foreman's own `test_helper`.
|
|
43
|
+
3. A database created and migrated in CI before the suite runs.
|
|
44
|
+
|
|
45
|
+
Not built. The standalone suite above is the part that pays for itself today.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
# NutanixCompute is a plain class, but it logs in #initialize. Stub just enough
|
|
4
|
+
# Rails to load it standalone; see test/README.md.
|
|
5
|
+
require 'logger'
|
|
6
|
+
module Rails
|
|
7
|
+
def self.logger
|
|
8
|
+
@logger ||= Logger.new(IO::NULL)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require 'ostruct'
|
|
13
|
+
require_relative '../app/models/foreman_nutanix/nutanix_compute'
|
|
14
|
+
|
|
15
|
+
class NutanixComputeTest < Minitest::Test
|
|
16
|
+
# Regression: ServersCollection#all builds VMs without a :gpus key, because
|
|
17
|
+
# the shim's /list-vms payload has no gpus field. The VM index template calls
|
|
18
|
+
# vm.gpus.empty? on every row, so a nil default raised NoMethodError there.
|
|
19
|
+
def test_gpus_defaults_to_an_empty_array_when_absent
|
|
20
|
+
vm = ForemanNutanix::NutanixCompute.new('cluster-1', name: 'vm1')
|
|
21
|
+
assert_equal [], vm.gpus
|
|
22
|
+
assert_empty vm.gpus
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_gpus_passes_through_when_supplied
|
|
26
|
+
vm = ForemanNutanix::NutanixCompute.new('cluster-1', name: 'vm1', gpus: %w[gpu-a gpu-b])
|
|
27
|
+
assert_equal %w[gpu-a gpu-b], vm.gpus
|
|
28
|
+
refute_empty vm.gpus
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_gpus_explicit_nil_still_yields_an_empty_array
|
|
32
|
+
vm = ForemanNutanix::NutanixCompute.new('cluster-1', name: 'vm1', gpus: nil)
|
|
33
|
+
assert_equal [], vm.gpus
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class NutanixComputeGpuLabelsTest < Minitest::Test
|
|
38
|
+
def build(gpus)
|
|
39
|
+
ForemanNutanix::NutanixCompute.new('cluster-1', name: 'vm1', gpus: gpus)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_no_gpus_gives_no_labels
|
|
43
|
+
assert_equal [], build(nil).gpu_labels
|
|
44
|
+
assert_equal [], build([]).gpu_labels
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_vendor_and_name_are_joined
|
|
48
|
+
labels = build([{ 'vendor' => 'NVIDIA', 'name' => 'Tesla T4', 'device_id' => 7864 }]).gpu_labels
|
|
49
|
+
assert_equal ['NVIDIA Tesla T4'], labels
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_falls_back_to_device_id_when_name_is_missing_or_blank
|
|
53
|
+
assert_equal ['NVIDIA 7864'], build([{ 'vendor' => 'NVIDIA', 'device_id' => 7864 }]).gpu_labels
|
|
54
|
+
assert_equal ['NVIDIA 7864'], build([{ 'vendor' => 'NVIDIA', 'name' => ' ', 'device_id' => 7864 }]).gpu_labels
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_name_alone_is_enough
|
|
58
|
+
assert_equal ['Tesla T4'], build([{ 'name' => 'Tesla T4' }]).gpu_labels
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_entirely_empty_gpu_still_gets_a_label
|
|
62
|
+
assert_equal ['GPU'], build([{}]).gpu_labels
|
|
63
|
+
assert_equal ['GPU'], build([{ 'vendor' => nil, 'name' => nil, 'device_id' => nil }]).gpu_labels
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_label_count_always_matches_gpu_count
|
|
67
|
+
labels = build([{ 'name' => 'A' }, {}, { 'vendor' => 'AMD' }]).gpu_labels
|
|
68
|
+
assert_equal 3, labels.size
|
|
69
|
+
refute_includes labels, ''
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class ShimClientTest < Minitest::Test
|
|
4
|
+
# --- Fakes injected through the http: keyword -----------------------------
|
|
5
|
+
|
|
6
|
+
# Stands in for a Net::HTTP connection object.
|
|
7
|
+
class FakeConnection
|
|
8
|
+
attr_reader :host, :port, :requests
|
|
9
|
+
attr_accessor :use_ssl
|
|
10
|
+
|
|
11
|
+
def initialize(host, port, response)
|
|
12
|
+
@host = host
|
|
13
|
+
@port = port
|
|
14
|
+
@response = response
|
|
15
|
+
@requests = []
|
|
16
|
+
@use_ssl = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def request(req)
|
|
20
|
+
@requests << req
|
|
21
|
+
@response
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Stands in for the Net::HTTP class itself.
|
|
26
|
+
class FakeHttp
|
|
27
|
+
attr_reader :connections
|
|
28
|
+
|
|
29
|
+
def initialize(response)
|
|
30
|
+
@response = response
|
|
31
|
+
@connections = []
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def new(host, port)
|
|
35
|
+
conn = FakeConnection.new(host, port, @response)
|
|
36
|
+
@connections << conn
|
|
37
|
+
conn
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def last_connection
|
|
41
|
+
@connections.last
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def last_request
|
|
45
|
+
last_connection.requests.last
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def stub_response(klass = Net::HTTPOK, code: '200', body: '{}')
|
|
50
|
+
response = klass.new('1.1', code, 'Stub')
|
|
51
|
+
response.instance_variable_set(:@body, body)
|
|
52
|
+
response.instance_variable_set(:@read, true)
|
|
53
|
+
response
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def client(base_url = 'http://shim.example:8000', response: stub_response)
|
|
57
|
+
http = FakeHttp.new(response)
|
|
58
|
+
[ForemanNutanix::ShimClient.new(base_url, http: http), http]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def with_env(value)
|
|
62
|
+
previous = ENV['NUTANIX_SHIM_SERVER_ADDR']
|
|
63
|
+
had_key = ENV.key?('NUTANIX_SHIM_SERVER_ADDR')
|
|
64
|
+
if value.nil?
|
|
65
|
+
ENV.delete('NUTANIX_SHIM_SERVER_ADDR')
|
|
66
|
+
else
|
|
67
|
+
ENV['NUTANIX_SHIM_SERVER_ADDR'] = value
|
|
68
|
+
end
|
|
69
|
+
yield
|
|
70
|
+
ensure
|
|
71
|
+
if had_key
|
|
72
|
+
ENV['NUTANIX_SHIM_SERVER_ADDR'] = previous
|
|
73
|
+
else
|
|
74
|
+
ENV.delete('NUTANIX_SHIM_SERVER_ADDR')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# --- Base URL -------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
def test_base_url_defaults_to_localhost_when_env_is_unset
|
|
81
|
+
with_env(nil) do
|
|
82
|
+
assert_equal 'http://localhost:8000', ForemanNutanix::ShimClient.new.base_url
|
|
83
|
+
assert_equal 'http://localhost:8000', ForemanNutanix::ShimClient.default_base_url
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_base_url_comes_from_env_when_set
|
|
88
|
+
with_env('http://shim.internal:9000') do
|
|
89
|
+
assert_equal 'http://shim.internal:9000', ForemanNutanix::ShimClient.new.base_url
|
|
90
|
+
assert_equal 'http://shim.internal:9000', ForemanNutanix::ShimClient.default_base_url
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_explicit_base_url_wins_over_env
|
|
95
|
+
with_env('http://shim.internal:9000') do
|
|
96
|
+
assert_equal 'http://explicit:1234', ForemanNutanix::ShimClient.new('http://explicit:1234').base_url
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_trailing_slash_is_chomped
|
|
101
|
+
shim = ForemanNutanix::ShimClient.new('http://shim.example:8000/')
|
|
102
|
+
assert_equal 'http://shim.example:8000', shim.base_url
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_trailing_slash_from_env_is_chomped
|
|
106
|
+
with_env('http://shim.internal:9000/') do
|
|
107
|
+
assert_equal 'http://shim.internal:9000', ForemanNutanix::ShimClient.new.base_url
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# --- Verbs and path construction -----------------------------------------
|
|
112
|
+
|
|
113
|
+
def test_get_builds_a_get_request_at_the_joined_path
|
|
114
|
+
shim, http = client
|
|
115
|
+
shim.get('/api/v1/vmm/list-images')
|
|
116
|
+
|
|
117
|
+
request = http.last_request
|
|
118
|
+
assert_instance_of Net::HTTP::Get, request
|
|
119
|
+
assert_equal '/api/v1/vmm/list-images', request.path
|
|
120
|
+
assert_equal 'shim.example', http.last_connection.host
|
|
121
|
+
assert_equal 8000, http.last_connection.port
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_post_builds_a_post_request_at_the_joined_path
|
|
125
|
+
shim, http = client
|
|
126
|
+
shim.post('/api/v1/vmm/vms/abc/power-state', { action: 'POWER_ON' })
|
|
127
|
+
|
|
128
|
+
assert_instance_of Net::HTTP::Post, http.last_request
|
|
129
|
+
assert_equal '/api/v1/vmm/vms/abc/power-state', http.last_request.path
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_delete_builds_a_delete_request_at_the_joined_path
|
|
133
|
+
shim, http = client
|
|
134
|
+
shim.delete('/api/v1/vmm/vms/abc')
|
|
135
|
+
|
|
136
|
+
assert_instance_of Net::HTTP::Delete, http.last_request
|
|
137
|
+
assert_equal '/api/v1/vmm/vms/abc', http.last_request.path
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_path_is_joined_to_a_base_url_that_had_a_trailing_slash
|
|
141
|
+
http = FakeHttp.new(stub_response)
|
|
142
|
+
ForemanNutanix::ShimClient.new('http://shim.example:8000/', http: http).get('/api/v1/vmm/list-vms')
|
|
143
|
+
|
|
144
|
+
assert_equal '/api/v1/vmm/list-vms', http.last_request.path
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# --- Body encoding --------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
def test_post_json_encodes_the_body_and_sets_content_type
|
|
150
|
+
shim, http = client
|
|
151
|
+
shim.post('/api/v1/vmm/provision-vm', { name: 'vm-1', num_sockets: 2 })
|
|
152
|
+
|
|
153
|
+
assert_equal 'application/json', http.last_request['Content-Type']
|
|
154
|
+
assert_equal({ 'name' => 'vm-1', 'num_sockets' => 2 }, JSON.parse(http.last_request.body))
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_post_without_a_body_sends_no_body_and_no_content_type
|
|
158
|
+
shim, http = client
|
|
159
|
+
shim.post('/api/v1/vmm/noop')
|
|
160
|
+
|
|
161
|
+
assert_nil http.last_request.body
|
|
162
|
+
assert_nil http.last_request['Content-Type']
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def test_get_sends_no_body_and_no_content_type
|
|
166
|
+
shim, http = client
|
|
167
|
+
shim.get('/api/v1/vmm/list-vms')
|
|
168
|
+
|
|
169
|
+
assert_nil http.last_request.body
|
|
170
|
+
assert_nil http.last_request['Content-Type']
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# --- Scheme ---------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
def test_https_is_inferred_from_the_scheme
|
|
176
|
+
http = FakeHttp.new(stub_response)
|
|
177
|
+
ForemanNutanix::ShimClient.new('https://shim.example', http: http).get('/api/v1/vmm/list-vms')
|
|
178
|
+
|
|
179
|
+
assert_equal true, http.last_connection.use_ssl
|
|
180
|
+
assert_equal 443, http.last_connection.port
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def test_http_scheme_does_not_enable_ssl
|
|
184
|
+
shim, http = client
|
|
185
|
+
shim.get('/api/v1/vmm/list-vms')
|
|
186
|
+
|
|
187
|
+
assert_equal false, http.last_connection.use_ssl
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# --- Response -------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
def test_success_is_true_for_200
|
|
193
|
+
shim, = client(response: stub_response(Net::HTTPOK, code: '200'))
|
|
194
|
+
response = shim.get('/whatever')
|
|
195
|
+
|
|
196
|
+
assert_equal true, response.success?
|
|
197
|
+
assert_equal '200', response.code
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def test_success_is_false_for_404
|
|
201
|
+
shim, = client(response: stub_response(Net::HTTPNotFound, code: '404', body: 'not found'))
|
|
202
|
+
response = shim.get('/whatever')
|
|
203
|
+
|
|
204
|
+
assert_equal false, response.success?
|
|
205
|
+
assert_equal '404', response.code
|
|
206
|
+
assert_equal 'not found', response.body
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def test_success_is_false_for_500
|
|
210
|
+
error = stub_response(Net::HTTPInternalServerError, code: '500', body: '<html>boom</html>')
|
|
211
|
+
shim, = client(response: error)
|
|
212
|
+
response = shim.get('/whatever')
|
|
213
|
+
|
|
214
|
+
assert_equal false, response.success?
|
|
215
|
+
assert_equal '500', response.code
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def test_no_content_is_true_only_for_204
|
|
219
|
+
shim, = client(response: stub_response(Net::HTTPNoContent, code: '204', body: nil))
|
|
220
|
+
assert_equal true, shim.get('/whatever').no_content?
|
|
221
|
+
|
|
222
|
+
shim_ok, = client(response: stub_response(Net::HTTPOK, code: '200'))
|
|
223
|
+
assert_equal false, shim_ok.get('/whatever').no_content?
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def test_json_parses_the_body
|
|
227
|
+
shim, = client(response: stub_response(Net::HTTPOK, body: '[{"ext_id":"abc"}]'))
|
|
228
|
+
|
|
229
|
+
assert_equal [{ 'ext_id' => 'abc' }], shim.get('/whatever').json
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def test_json_propagates_parser_error_on_non_json_bodies
|
|
233
|
+
error = stub_response(Net::HTTPInternalServerError, code: '500', body: '<html>boom</html>')
|
|
234
|
+
shim, = client(response: error)
|
|
235
|
+
|
|
236
|
+
assert_raises(JSON::ParserError) { shim.get('/whatever').json }
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# --- normalize_uuid -------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
def test_normalize_uuid_strips_a_prefix
|
|
242
|
+
assert_equal 'the-uuid', ForemanNutanix::ShimClient.normalize_uuid('ZXJnb24=:the-uuid')
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def test_normalize_uuid_leaves_a_bare_uuid_alone
|
|
246
|
+
assert_equal 'the-uuid', ForemanNutanix::ShimClient.normalize_uuid('the-uuid')
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def test_normalize_uuid_coerces_non_strings
|
|
250
|
+
assert_equal '', ForemanNutanix::ShimClient.normalize_uuid(nil)
|
|
251
|
+
assert_equal '42', ForemanNutanix::ShimClient.normalize_uuid(42)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def test_normalize_uuid_keeps_only_the_last_segment
|
|
255
|
+
assert_equal 'c', ForemanNutanix::ShimClient.normalize_uuid('a:b:c')
|
|
256
|
+
end
|
|
257
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman_nutanix
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Miles Granger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rdoc
|
|
@@ -54,10 +54,15 @@ files:
|
|
|
54
54
|
- config/routes.rb
|
|
55
55
|
- lib/foreman_nutanix.rb
|
|
56
56
|
- lib/foreman_nutanix/engine.rb
|
|
57
|
+
- lib/foreman_nutanix/shim_client.rb
|
|
57
58
|
- lib/foreman_nutanix/version.rb
|
|
58
59
|
- lib/tasks/foreman_nutanix_tasks.rake
|
|
59
60
|
- locale/foreman_nutanix.pot
|
|
60
61
|
- package.json
|
|
62
|
+
- test/README.md
|
|
63
|
+
- test/nutanix_compute_test.rb
|
|
64
|
+
- test/shim_client_test.rb
|
|
65
|
+
- test/test_helper.rb
|
|
61
66
|
- webpack/global_index.js
|
|
62
67
|
- webpack/global_test_setup.js
|
|
63
68
|
- webpack/index.js
|
|
@@ -94,4 +99,8 @@ rubygems_version: 3.4.19
|
|
|
94
99
|
signing_key:
|
|
95
100
|
specification_version: 4
|
|
96
101
|
summary: Nutanix plugin for Foreman
|
|
97
|
-
test_files:
|
|
102
|
+
test_files:
|
|
103
|
+
- test/README.md
|
|
104
|
+
- test/nutanix_compute_test.rb
|
|
105
|
+
- test/shim_client_test.rb
|
|
106
|
+
- test/test_helper.rb
|