foreman_kubevirt 0.1.0
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 +619 -0
- data/README.md +156 -0
- data/Rakefile +44 -0
- data/app/assets/javascripts/foreman_kubevirt/kubevirt.js +31 -0
- data/app/assets/javascripts/foreman_kubevirt/nic_info.js +9 -0
- data/app/controllers/foreman_kubevirt/concerns/api/compute_resources_controller_extensions.rb +26 -0
- data/app/models/concerns/fog_extensions/kubevirt/network.rb +11 -0
- data/app/models/concerns/fog_extensions/kubevirt/server.rb +55 -0
- data/app/models/concerns/fog_extensions/kubevirt/vmnic.rb +12 -0
- data/app/models/concerns/fog_extensions/kubevirt/volume.rb +26 -0
- data/app/models/foreman_kubevirt/kubevirt.rb +370 -0
- data/app/views/api/v2/compute_resources/kubevirt.json.rabl +1 -0
- data/app/views/compute_resources/form/_kubevirt.html.erb +14 -0
- data/app/views/compute_resources/show/_kubevirt.html.erb +14 -0
- data/app/views/compute_resources_vms/form/kubevirt/_base.html.erb +27 -0
- data/app/views/compute_resources_vms/form/kubevirt/_network.html.erb +15 -0
- data/app/views/compute_resources_vms/form/kubevirt/_volume.html.erb +21 -0
- data/app/views/compute_resources_vms/index/_kubevirt.html.erb +29 -0
- data/app/views/compute_resources_vms/show/_kubevirt.html.erb +55 -0
- data/app/views/images/form/_kubevirt.html.erb +4 -0
- data/config/routes.rb +2 -0
- data/lib/foreman_kubevirt/engine.rb +75 -0
- data/lib/foreman_kubevirt/version.rb +3 -0
- data/lib/foreman_kubevirt.rb +4 -0
- data/lib/tasks/foreman_kubevirt_tasks.rake +47 -0
- data/locale/Makefile +60 -0
- data/locale/en/foreman_kubevirt.po +19 -0
- data/locale/foreman_kubevirt.pot +19 -0
- data/locale/gemspec.rb +2 -0
- data/test/factories/compute_resource_factories.rb +10 -0
- data/test/factories/host_factories.rb +49 -0
- data/test/factories/nic_factories.rb +31 -0
- data/test/models/compute_resources/kubevirt_test.rb +69 -0
- data/test/test_plugin_helper.rb +7 -0
- data/test/unit/foreman_kubevirt_test.rb +45 -0
- metadata +127 -0
@@ -0,0 +1,370 @@
|
|
1
|
+
require 'foreman/exception'
|
2
|
+
|
3
|
+
module ForemanKubevirt
|
4
|
+
class Kubevirt < ComputeResource
|
5
|
+
alias_attribute :hostname, :url
|
6
|
+
alias_attribute :token, :password
|
7
|
+
alias_attribute :namespace, :user
|
8
|
+
validates :hostname, :api_port, :namespace, :token, :presence => true
|
9
|
+
validate :test_connection
|
10
|
+
|
11
|
+
def ca_cert
|
12
|
+
attrs[:ca_cert]
|
13
|
+
end
|
14
|
+
|
15
|
+
def ca_cert=(key)
|
16
|
+
attrs[:ca_cert] = key
|
17
|
+
end
|
18
|
+
|
19
|
+
def api_port
|
20
|
+
attrs[:api_port]
|
21
|
+
end
|
22
|
+
|
23
|
+
def api_port=(key)
|
24
|
+
attrs[:api_port] = key
|
25
|
+
end
|
26
|
+
|
27
|
+
def capabilities
|
28
|
+
%i[build image new_volume]
|
29
|
+
end
|
30
|
+
|
31
|
+
def provided_attributes
|
32
|
+
{ :uuid => :name, :mac => :mac }
|
33
|
+
end
|
34
|
+
|
35
|
+
def available_images
|
36
|
+
[]
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.provider_friendly_name
|
40
|
+
'KubeVirt'
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_label
|
44
|
+
"#{name} (#{provider_friendly_name})"
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.model_name
|
48
|
+
ComputeResource.model_name
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_connection(_options = {})
|
52
|
+
client&.valid? && client&.virt_supported?
|
53
|
+
rescue StandardError => e
|
54
|
+
errors[:base] << e.message
|
55
|
+
end
|
56
|
+
|
57
|
+
def networks
|
58
|
+
client.networkattachmentdefs.all
|
59
|
+
rescue StandardError => e
|
60
|
+
logger.warn("Failed to retrieve network attachments definition from KubeVirt, make sure KubeVirt has CNI provider and NetworkAttachmentDefinition CRD deployed: #{e.message}")
|
61
|
+
[]
|
62
|
+
end
|
63
|
+
|
64
|
+
def find_vm_by_uuid(uuid)
|
65
|
+
super
|
66
|
+
rescue Fog::Kubevirt::Errors::ClientError => e
|
67
|
+
Foreman::Logging.exception("Failed retrieving KubeVirt vm by uuid #{uuid}", e)
|
68
|
+
raise ActiveRecord::RecordNotFound
|
69
|
+
end
|
70
|
+
|
71
|
+
def volumes
|
72
|
+
client.volumes.all
|
73
|
+
end
|
74
|
+
|
75
|
+
def storage_classes
|
76
|
+
client.storageclasses.all
|
77
|
+
end
|
78
|
+
|
79
|
+
def storage_classes_for_select
|
80
|
+
storage_classes.map { |sc| OpenStruct.new(id: sc.name, description: "#{sc.name} (#{sc.provisioner})") }
|
81
|
+
end
|
82
|
+
|
83
|
+
def new_volume(attr = {})
|
84
|
+
return unless new_volume_errors.empty?
|
85
|
+
|
86
|
+
Fog::Kubevirt::Compute::Volume.new(attr)
|
87
|
+
end
|
88
|
+
|
89
|
+
def new_volume_errors
|
90
|
+
errors = []
|
91
|
+
errors.push _('no Persistent Volumes available on provider') if storage_classes.empty?
|
92
|
+
errors
|
93
|
+
end
|
94
|
+
|
95
|
+
def cni_providers
|
96
|
+
[[_("multus"), :multus], [_("genie"), :genie], [_("pod"), :pod]]
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param args[Hash] contains VM creation parameters
|
100
|
+
# cpu_cores[str] - the number of cpu cores
|
101
|
+
# memory[str] - the memory for the VM
|
102
|
+
# start[bool] - indicates if the vm should be started
|
103
|
+
# name[str] - the name of the VM
|
104
|
+
# interfaces_attributes[Hash] - the attributes for the interfaces, i.e.:
|
105
|
+
# {
|
106
|
+
# "0" => {
|
107
|
+
# "network" => "ovs-foreman",
|
108
|
+
# "boot" => "1",
|
109
|
+
# "cni_provider" => "multus"
|
110
|
+
# },
|
111
|
+
# "1" => {
|
112
|
+
# "cni_provider" => "pod"
|
113
|
+
# "boot" => "0"
|
114
|
+
# }
|
115
|
+
# }
|
116
|
+
#
|
117
|
+
# volumes_attributes[Hash] - the attributes for the persistent volume claims:
|
118
|
+
# {
|
119
|
+
# "1554394214729" => {
|
120
|
+
# "storage_class" => "local-storage",
|
121
|
+
# "name" => "alvin-hinojosa1",
|
122
|
+
# "capacity" => "3",
|
123
|
+
# "bootable"=>"true"
|
124
|
+
# },
|
125
|
+
# "1554394230987" => {
|
126
|
+
# "storage_class" => "local-storage",
|
127
|
+
# "name" => "alvin-hinojosa",
|
128
|
+
# "capacity"=>"2"
|
129
|
+
# }
|
130
|
+
# }
|
131
|
+
def create_vm(args = {})
|
132
|
+
options = vm_instance_defaults.merge(args.to_hash.deep_symbolize_keys)
|
133
|
+
logger.debug("creating VM with the following options: #{options.inspect}")
|
134
|
+
volumes = []
|
135
|
+
|
136
|
+
image = args["image_id"]
|
137
|
+
volumes_attributes = args["volumes_attributes"]
|
138
|
+
raise "VM should be created based on Persistent Volume Claim or Image" unless (volumes_attributes.present? || image)
|
139
|
+
|
140
|
+
# Add image as volume to the virtual machine
|
141
|
+
image_provision = args["provision_method"] == "image"
|
142
|
+
if image_provision
|
143
|
+
volume = Fog::Kubevirt::Compute::Volume.new
|
144
|
+
raise "VM should be created based on an image" unless image
|
145
|
+
|
146
|
+
volume.info = image
|
147
|
+
volume.boot_order = 1
|
148
|
+
volume.type = 'containerDisk'
|
149
|
+
volumes << volume
|
150
|
+
end
|
151
|
+
|
152
|
+
volumes_attributes&.each_with_index do |(_, v), index|
|
153
|
+
# Add PVC as volumes to the virtual machine
|
154
|
+
pvc_name = options[:name].gsub(/[._]+/, '-') + "-claim-" + (index + 1).to_s
|
155
|
+
capacity = v["capacity"]
|
156
|
+
storage_class = v["storage_class"]
|
157
|
+
bootable = v["bootable"] && !image_provision
|
158
|
+
|
159
|
+
volume = create_vm_volume(pvc_name, capacity, storage_class, bootable)
|
160
|
+
volumes << volume
|
161
|
+
end
|
162
|
+
|
163
|
+
# FIXME: Add cloud-init support
|
164
|
+
# init = { 'userData' => "#!/bin/bash\necho \"fedora\" | passwd fedora --stdin"}
|
165
|
+
|
166
|
+
interfaces = []
|
167
|
+
networks = []
|
168
|
+
|
169
|
+
args["interfaces_attributes"].values.each do |iface|
|
170
|
+
if iface["cni_provider"] == 'pod'
|
171
|
+
nic = {
|
172
|
+
:bridge => {},
|
173
|
+
:name => 'pod'
|
174
|
+
}
|
175
|
+
|
176
|
+
net = { :name => 'pod', :pod => {} }
|
177
|
+
else
|
178
|
+
nic = {
|
179
|
+
:bridge => {},
|
180
|
+
:name => iface["network"]
|
181
|
+
}
|
182
|
+
|
183
|
+
cni = iface["cni_provider"].to_sym
|
184
|
+
net = {
|
185
|
+
:name => iface["network"],
|
186
|
+
cni => { :networkName => iface["network"] }
|
187
|
+
}
|
188
|
+
end
|
189
|
+
|
190
|
+
# TODO: Consider replacing with 'free' boot order, also verify uniqueness
|
191
|
+
# there is a bug with bootOrder https://bugzilla.redhat.com/show_bug.cgi?id=1687341
|
192
|
+
# therefore adding to the condition not to boot from netwotk device if already asked
|
193
|
+
# to boot from disk
|
194
|
+
if iface["provision"] == true && volumes.select { |v| v.boot_order == 1 }.empty?
|
195
|
+
nic[:bootOrder] = 1
|
196
|
+
end
|
197
|
+
nic[:macAddress] = iface["mac"] if iface["mac"]
|
198
|
+
interfaces << nic
|
199
|
+
networks << net
|
200
|
+
end
|
201
|
+
|
202
|
+
begin
|
203
|
+
client.vms.create(:vm_name => options[:name],
|
204
|
+
:cpus => options[:cpu_cores].to_i,
|
205
|
+
:memory_size => options[:memory].to_i / 2**20,
|
206
|
+
:volumes => volumes,
|
207
|
+
# :cloudinit => init,
|
208
|
+
:networks => networks,
|
209
|
+
:interfaces => interfaces)
|
210
|
+
client.servers.get(options[:name])
|
211
|
+
rescue Fog::Kubevirt::Errors::ClientError => e
|
212
|
+
delete_pvcs(volumes)
|
213
|
+
raise e
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def create_new_pvc(pvc_name, capacity, storage_class)
|
218
|
+
client.pvcs.create(:name => pvc_name,
|
219
|
+
:namespace => namespace,
|
220
|
+
:storage_class => storage_class,
|
221
|
+
:access_modes => ['ReadWriteOnce'],
|
222
|
+
:requests => { :storage => capacity + "G" })
|
223
|
+
end
|
224
|
+
|
225
|
+
def delete_pvcs(volumes)
|
226
|
+
volumes.each do |volume|
|
227
|
+
begin
|
228
|
+
client.pvcs.delete(volume.info) if volume.type == "persistentVolumeClaim"
|
229
|
+
rescue StandardError => e
|
230
|
+
logger.error("The PVC #{volume.info} couldn't be delete due to #{e.message}")
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def create_vm_volume(pvc_name, capacity, storage_class, bootable)
|
236
|
+
create_new_pvc(pvc_name, capacity, storage_class)
|
237
|
+
|
238
|
+
volume = Fog::Kubevirt::Compute::Volume.new
|
239
|
+
volume.type = 'persistentVolumeClaim'
|
240
|
+
volume.info = pvc_name
|
241
|
+
volume.boot_order = 1 if bootable == "true"
|
242
|
+
volume
|
243
|
+
end
|
244
|
+
|
245
|
+
def destroy_vm(vm_uuid)
|
246
|
+
vm = find_vm_by_uuid(vm_uuid)
|
247
|
+
delete_pvcs(vm.volumes)
|
248
|
+
vm.destroy
|
249
|
+
rescue ActiveRecord::RecordNotFound
|
250
|
+
true
|
251
|
+
end
|
252
|
+
|
253
|
+
def host_compute_attrs(host)
|
254
|
+
attrs = super
|
255
|
+
attrs[:interfaces_attributes].each_value { |nic| nic["network"] = nil if nic["cni_provider"] == "pod" }
|
256
|
+
attrs
|
257
|
+
end
|
258
|
+
|
259
|
+
#
|
260
|
+
# Since 'name' is the identity/UUID of server/vm, we need to override
|
261
|
+
# default values that assign also value for 'name' so vm_exists? will
|
262
|
+
# return 'false'
|
263
|
+
def vm_instance_defaults
|
264
|
+
{
|
265
|
+
:memory => 1024.megabytes,
|
266
|
+
:cpu_cores => '1'
|
267
|
+
}
|
268
|
+
end
|
269
|
+
|
270
|
+
def new_interface(attr = {})
|
271
|
+
Fog::Kubevirt::Compute::VmNic.new attr
|
272
|
+
end
|
273
|
+
|
274
|
+
#
|
275
|
+
# Overrding base class implementation since 'mac' is required for the created interface
|
276
|
+
#
|
277
|
+
def host_interfaces_attrs(host)
|
278
|
+
host.interfaces.select(&:physical?).each.with_index.reduce({}) do |hash, (nic, index)|
|
279
|
+
hash.merge(index.to_s => nic.compute_attributes.merge(ip: nic.ip, mac: nic.mac, provision: nic.provision))
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
#
|
284
|
+
# Overrding base class implementation since 'pvc' is required
|
285
|
+
#
|
286
|
+
def set_vm_volumes_attributes(vm, vm_attrs)
|
287
|
+
volumes = vm.volumes.collect do |vol|
|
288
|
+
next unless vol.type == 'persistentVolumeClaim'
|
289
|
+
|
290
|
+
begin
|
291
|
+
vol.pvc = client.pvcs.get(vol.info)
|
292
|
+
vol
|
293
|
+
rescue StandardError => e
|
294
|
+
# An import of a VM where one of its PVC doesn't exist
|
295
|
+
Foreman::Logging.exception("Import VM fail: The PVC #{vol.info} does not exist for VM #{vm.name}", e)
|
296
|
+
nil
|
297
|
+
end
|
298
|
+
end.compact
|
299
|
+
vm_attrs[:volumes_attributes] = Hash[volumes.each_with_index.map { |volume, idx| [idx.to_s, volume.attributes] }]
|
300
|
+
|
301
|
+
vm_attrs
|
302
|
+
end
|
303
|
+
|
304
|
+
def vm_compute_attributes(vm)
|
305
|
+
vm_attrs = super
|
306
|
+
interfaces = vm.interfaces || []
|
307
|
+
vm_attrs[:interfaces_attributes] = interfaces.each_with_index.each_with_object({}) do |(interface, index), hsh|
|
308
|
+
interface_attrs = {}
|
309
|
+
interface_attrs[:compute_attributes] = {}
|
310
|
+
interface_attrs[:mac] = interface.mac
|
311
|
+
interface_attrs[:compute_attributes][:network] = interface.network
|
312
|
+
interface_attrs[:compute_attributes][:cni_provider] = interface.cni_provider
|
313
|
+
hsh[index.to_s] = interface_attrs
|
314
|
+
end
|
315
|
+
|
316
|
+
vm_attrs
|
317
|
+
end
|
318
|
+
|
319
|
+
def associated_host(vm)
|
320
|
+
associate_by("mac", vm.mac)
|
321
|
+
end
|
322
|
+
|
323
|
+
# TODO: max supported values should be fetched according to namespace
|
324
|
+
# capabilities: kubectl get limits namespace_name
|
325
|
+
def max_cpu_count
|
326
|
+
16
|
327
|
+
end
|
328
|
+
|
329
|
+
def max_socket_count
|
330
|
+
16
|
331
|
+
end
|
332
|
+
|
333
|
+
def max_memory
|
334
|
+
64.gigabytes
|
335
|
+
end
|
336
|
+
|
337
|
+
protected
|
338
|
+
|
339
|
+
def client
|
340
|
+
return @client if @client
|
341
|
+
|
342
|
+
@client ||= Fog::Kubevirt::Compute.new(
|
343
|
+
:kubevirt_hostname => hostname,
|
344
|
+
:kubevirt_port => api_port,
|
345
|
+
:kubevirt_namespace => namespace,
|
346
|
+
:kubevirt_token => token,
|
347
|
+
:kubevirt_log => logger,
|
348
|
+
:kubevirt_verify_ssl => ca_cert.present?,
|
349
|
+
:kubevirt_ca_cert => ca_cert
|
350
|
+
)
|
351
|
+
rescue OpenSSL::X509::CertificateError
|
352
|
+
raise_certification_failure_exception
|
353
|
+
rescue StandardError => e
|
354
|
+
if e.message =~ /SSL_connect.*certificate verify failed/ ||
|
355
|
+
e.message =~ /Peer certificate cannot be authenticated with given CA certificates/
|
356
|
+
raise_certification_failure_exception
|
357
|
+
else
|
358
|
+
raise e
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def raise_certification_failure_exception
|
363
|
+
raise Foreman::FingerprintException.new(
|
364
|
+
N_("The remote system presented a public key signed by an unidentified certificate authority.
|
365
|
+
If you are sure the remote system is authentic, go to the compute resource edit page, press the 'Test Connection' button and submit"),
|
366
|
+
ca_cert
|
367
|
+
)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
attributes :hostname, :api_port, :namespace
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<%= text_f f, :hostname, :label => _("Hostname") %>
|
2
|
+
<%= text_f f, :api_port, :label => _("API Port"), :required => true %>
|
3
|
+
<%= text_f f, :namespace, :label => _("Namespace"), :required => true %>
|
4
|
+
<%= password_f f, :password, :label => _("Token"), :required => true,
|
5
|
+
:keep_value => true, :unset => unset_password?,
|
6
|
+
:help_block => _("e.g. eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9..."),
|
7
|
+
:help_inline => documentation_button('5.2.10PasswordEncryption') %>
|
8
|
+
|
9
|
+
<%= textarea_f f, :ca_cert, :label => _("X509 Certification Authorities"),
|
10
|
+
:placeholder => _("Optionally provide a CA, or a correctly ordered CA chain or a path to a file. If left blank - insecure.") %>
|
11
|
+
|
12
|
+
<div class="col-md-offset-2">
|
13
|
+
<%= test_connection_button_f(f, true) %>
|
14
|
+
</div>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<tr>
|
2
|
+
<td><%= _("Hostname") %></td>
|
3
|
+
<td><%= @compute_resource.hostname %></td>
|
4
|
+
</tr>
|
5
|
+
|
6
|
+
<tr>
|
7
|
+
<td><%= _("API Port") %></td>
|
8
|
+
<td><%= @compute_resource.api_port %></td>
|
9
|
+
</tr>
|
10
|
+
|
11
|
+
<tr>
|
12
|
+
<td><%= _("Namespace") %></td>
|
13
|
+
<td><%= @compute_resource.user %></td>
|
14
|
+
</tr>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<%= javascript_tag("$(document).on('ContentLoad', tfm.numFields.initAll)"); %>
|
2
|
+
|
3
|
+
<%= text_f f, :name, :label => _('Name'), :label_size => "col-md-2", :disabled => !new_vm if show_vm_name? %>
|
4
|
+
|
5
|
+
<%= counter_f f, :cpu_cores, :disabled => !new_vm, :label => _('CPUs'), :label_size => 'col-md-2', :'data-soft-max' => compute_resource.max_cpu_count %>
|
6
|
+
|
7
|
+
<%= byte_size_f f, :memory, :disabled => !new_vm, :label => _('Memory'), :label_size => "col-md-2", :'data-soft-max' => compute_resource.max_memory %>
|
8
|
+
|
9
|
+
<% checked = params[:host] && params[:host][:compute_attributes] && params[:host][:compute_attributes][:start] || '1' %>
|
10
|
+
<%= checkbox_f f, :start, { :checked => (checked == '1'), :help_inline => _("Power ON this machine"), :label => _('Start'), :label_size => "col-md-2"} if new_vm && controller_name != "compute_attributes" %>
|
11
|
+
|
12
|
+
|
13
|
+
<% unless local_assigns[:hide_image] %>
|
14
|
+
<%
|
15
|
+
arch ||= nil ; os ||= nil
|
16
|
+
images = possible_images(compute_resource, arch, os)
|
17
|
+
-%>
|
18
|
+
|
19
|
+
<div id='image_selection'>
|
20
|
+
<%= select_f f, :image_id, images, :uuid, :name,
|
21
|
+
{ :include_blank => (images.empty? || images.size == 1) ? false : _('Please select an image') },
|
22
|
+
{ :disabled => images.empty?, :label => _('Image'), :label_size => "col-md-2" } %>
|
23
|
+
</div>
|
24
|
+
<% end %>
|
25
|
+
|
26
|
+
<%= javascript_include_tag("foreman_kubevirt/nic_info.js") %>
|
27
|
+
<%= javascript_include_tag("foreman_kubevirt/kubevirt.js") %>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<%= selectable_f f, :cni_provider, compute_resource.cni_providers, {},
|
2
|
+
:class => "col-md-3 kubevirt-cni-provider",
|
3
|
+
:disabled => !new_vm,
|
4
|
+
:label => _('CNI Provider'),
|
5
|
+
:label_size => "col-md-3",
|
6
|
+
:onchange => "cniProviderSelected(this)" %>
|
7
|
+
|
8
|
+
<%= select_f f, :network, compute_resource.networks,
|
9
|
+
:id,
|
10
|
+
:name,
|
11
|
+
{},
|
12
|
+
:disabled => !new_vm || f.object.cni_provider == 'pod',
|
13
|
+
:class => "col-md-3 kubevirt-network",
|
14
|
+
:label => _('Network'),
|
15
|
+
:label_size => "col-md-3" %>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<% if f.object.type.nil? || f.object.type == 'persistentVolumeClaim' %>
|
2
|
+
<% storage_classes = compute_resource.storage_classes %>
|
3
|
+
|
4
|
+
<%= select_f f, :storage_class,
|
5
|
+
compute_resource.storage_classes_for_select,
|
6
|
+
:id,
|
7
|
+
:description,
|
8
|
+
{ },
|
9
|
+
:label => _('Storage Class'),
|
10
|
+
:label_size => "col-md-2",
|
11
|
+
:disabled => !new_vm,
|
12
|
+
:class => "col-md-2" %>
|
13
|
+
|
14
|
+
<%= text_f f,:capacity, :label => _('Size (GB)'), :label_size => "col-md-2", :disabled => !new_vm, :class => "col-md-2 pvc-size" %>
|
15
|
+
|
16
|
+
<% bootable = f.object.boot_order.nil? ? false : f.object.bootable %>
|
17
|
+
<%= field(f, :bootable, :label => _('Bootable'), :label_size => "col-md-2") do
|
18
|
+
radio_button_f f, :bootable, {:disabled => !new_vm, :checked => bootable, :onclick => 'bootableRadio(this)',
|
19
|
+
:text => _('Only one volume can be bootable')}
|
20
|
+
end %>
|
21
|
+
<% end %>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<thead>
|
2
|
+
<tr>
|
3
|
+
<th><%= _('Name') %></th>
|
4
|
+
<th><%= _('CPUs') %></th>
|
5
|
+
<th><%= _('Memory') %></th>
|
6
|
+
<th><%= _('Power') %></th>
|
7
|
+
<th><%= _('Actions') %></th>
|
8
|
+
</tr>
|
9
|
+
</thead>
|
10
|
+
<tbody>
|
11
|
+
<% @vms.each do |vm| %>
|
12
|
+
<tr>
|
13
|
+
<td><%= link_to_if_authorized vm.name, hash_for_compute_resource_vm_path(:compute_resource_id => @compute_resource, :id => vm.identity).merge(:auth_object => @compute_resource, :auth_action => 'view', :authorizer => authorizer) %></td>
|
14
|
+
<td><%= vm.cpu_cores %></td>
|
15
|
+
<td><%= vm.memory %></td>
|
16
|
+
<td>
|
17
|
+
<span <%= vm_power_class(vm.ready?) %>>
|
18
|
+
<%= vm_state(vm) %></span>
|
19
|
+
</td>
|
20
|
+
<td>
|
21
|
+
<%=
|
22
|
+
action_buttons(vm_power_action(vm, authorizer),
|
23
|
+
vm_import_action(vm),
|
24
|
+
display_delete_if_authorized(hash_for_compute_resource_vm_path(:compute_resource_id => @compute_resource, :id => vm.identity).merge(:auth_object => @compute_resource, :authorizer => authorizer)))
|
25
|
+
%>
|
26
|
+
</td>
|
27
|
+
</tr>
|
28
|
+
<% end %>
|
29
|
+
</tbody>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<% title @vm.name %>
|
2
|
+
|
3
|
+
<div class='col-md-12'>
|
4
|
+
<table class="<%= table_css_classes %>">
|
5
|
+
<thead>
|
6
|
+
<tr><th colspan="2"><%=_('Properties') %></th></tr>
|
7
|
+
</thead>
|
8
|
+
<tbody>
|
9
|
+
<tr>
|
10
|
+
<td><%= _('Name') %></td>
|
11
|
+
<td><%= @vm.identity %></td>
|
12
|
+
</tr>
|
13
|
+
<tr>
|
14
|
+
<td><%= _('VCPU(s)') %></td>
|
15
|
+
<td><%= @vm.cpu_cores %></td>
|
16
|
+
</tr>
|
17
|
+
<tr>
|
18
|
+
<td><%= _('UUID') %></td>
|
19
|
+
<td><%= @vm.uid %></td>
|
20
|
+
</tr>
|
21
|
+
<tr>
|
22
|
+
<td><%= _('Memory') %></td>
|
23
|
+
<td><%= number_to_human_size @vm.memory %>
|
24
|
+
</td>
|
25
|
+
</tr>
|
26
|
+
<tr>
|
27
|
+
<td><%= _('Namespace') %></td>
|
28
|
+
<td><%= @vm.namespace %></td>
|
29
|
+
</tr>
|
30
|
+
<tr>
|
31
|
+
<td><%= _('Type') %></td>
|
32
|
+
<td><%= @vm.machine_type %></td>
|
33
|
+
</tr>
|
34
|
+
|
35
|
+
<% @vm.interfaces.each do |nic| %>
|
36
|
+
<tr>
|
37
|
+
<td><%= _('NIC') %></td>
|
38
|
+
<td><%= "#{nic.name} - #{nic.mac_address} (#{nic.type})" %></td>
|
39
|
+
</tr>
|
40
|
+
<% end %>
|
41
|
+
|
42
|
+
<% @vm.volumes.each do |vol| %>
|
43
|
+
<tr>
|
44
|
+
<td><%= _('Disk') %></td>
|
45
|
+
<td><%= "#{vol.name} - #{vol.type} (#{vol.info})" %></td>
|
46
|
+
</tr>
|
47
|
+
<% end %>
|
48
|
+
|
49
|
+
<tr>
|
50
|
+
<td><%= _('Running on') %></td>
|
51
|
+
<td><%= link_to @compute_resource, compute_resource_path(@compute_resource) %></td>
|
52
|
+
</tr>
|
53
|
+
</tbody>
|
54
|
+
</table>
|
55
|
+
</div>
|
@@ -0,0 +1,4 @@
|
|
1
|
+
<%= text_f f, :username, :value => @image.username || "root", :help_inline => _("The user that is used to ssh into the instance, normally cloud-user, ec2-user, ubuntu, root etc") %>
|
2
|
+
<%= password_f f, :password, :help_inline => _("Password to authenticate with - used for SSH finish step.") %>
|
3
|
+
<%= checkbox_f f, :user_data, :help_inline => _("Does this image support user data input (e.g. via cloud-init)?") %>
|
4
|
+
<%= image_field(f, :label => _("Image"), :help_inline => _("The name of the image in the registry.")) %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
module ForemanKubevirt
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
engine_name "foreman_kubevirt"
|
4
|
+
config.autoload_paths += Dir["#{config.root}/app/models/concerns"]
|
5
|
+
initializer "foreman_kubevirt.register_plugin", :before => :finisher_hook do |_app|
|
6
|
+
Foreman::Plugin.register :foreman_kubevirt do
|
7
|
+
requires_foreman ">= 1.7"
|
8
|
+
compute_resource(ForemanKubevirt::Kubevirt)
|
9
|
+
|
10
|
+
parameter_filter(ComputeResource, :hostname, :url)
|
11
|
+
parameter_filter(ComputeResource, :namespace, :user)
|
12
|
+
parameter_filter(ComputeResource, :token, :password)
|
13
|
+
parameter_filter(ComputeResource, :ca_cert)
|
14
|
+
parameter_filter(ComputeResource, :api_port)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
assets_to_precompile =
|
19
|
+
Dir.chdir(root) do
|
20
|
+
Dir['app/assets/javascripts/**/*', 'app/assets/stylesheets/**/*'].map do |f|
|
21
|
+
f.split(File::SEPARATOR, 4).last
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
initializer 'foreman_kubevirt.assets.precompile' do |app|
|
26
|
+
app.config.assets.precompile += assets_to_precompile
|
27
|
+
end
|
28
|
+
|
29
|
+
initializer 'foreman_kubevirt.filter_parameters' do |app|
|
30
|
+
app.config.filter_parameters += [:token]
|
31
|
+
end
|
32
|
+
|
33
|
+
initializer 'foreman_kubevirt.configure_assets', group: :assets do
|
34
|
+
SETTINGS[:foreman_kubevirt] = { assets: { precompile: assets_to_precompile } }
|
35
|
+
end
|
36
|
+
|
37
|
+
initializer "foreman_kubevirt.add_rabl_view_path" do
|
38
|
+
Rabl.configure do |config|
|
39
|
+
config.view_paths << ForemanKubevirt::Engine.root.join('app', 'views')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Include concerns in this config.to_prepare block
|
44
|
+
config.to_prepare do
|
45
|
+
begin
|
46
|
+
require "fog/kubevirt"
|
47
|
+
require "fog/kubevirt/compute/models/server"
|
48
|
+
require File.expand_path("../../app/models/concerns/fog_extensions/kubevirt/server", __dir__)
|
49
|
+
|
50
|
+
::Api::V2::ComputeResourcesController.send :include, ForemanKubevirt::Concerns::Api::ComputeResourcesControllerExtensions
|
51
|
+
Fog::Kubevirt::Compute::Server.send(:include, ::FogExtensions::Kubevirt::Server)
|
52
|
+
|
53
|
+
require "fog/kubevirt/compute/models/volume"
|
54
|
+
require File.expand_path("../../app/models/concerns/fog_extensions/kubevirt/volume", __dir__)
|
55
|
+
Fog::Kubevirt::Compute::Volume.send(:include, ::FogExtensions::Kubevirt::Volume)
|
56
|
+
|
57
|
+
require "fog/kubevirt/compute/models/vmnic"
|
58
|
+
require File.expand_path("../../app/models/concerns/fog_extensions/kubevirt/vmnic", __dir__)
|
59
|
+
Fog::Kubevirt::Compute::VmNic.send(:include, ::FogExtensions::Kubevirt::VmNic)
|
60
|
+
|
61
|
+
require "fog/kubevirt/compute/models/networkattachmentdef"
|
62
|
+
require File.expand_path("../../app/models/concerns/fog_extensions/kubevirt/network", __dir__)
|
63
|
+
Fog::Kubevirt::Compute::Networkattachmentdef.send(:include, ::FogExtensions::Kubevirt::Network)
|
64
|
+
rescue StandardError => e
|
65
|
+
Rails.logger.warn "Foreman-Kubevirt: skipping engine hook (#{e})"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
initializer "foreman_kubevirt.register_gettext", after: :load_config_initializers do |_app|
|
70
|
+
locale_dir = File.join(File.expand_path("../..", __dir__), "locale")
|
71
|
+
locale_domain = "foreman_kubevirt"
|
72
|
+
Foreman::Gettext::Support.add_text_domain locale_domain, locale_dir
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|