From 717923b3bb08e25f7006e1cea24c7328e58075f1 Mon Sep 17 00:00:00 2001 From: Niels Baumgartner Date: Wed, 12 Dec 2018 14:55:47 +0100 Subject: [PATCH] Fixes #17860,#12051,#22277,#22621 Various big rewrites - implement image provisioning - improve volume support - add cloud-init support - UI improvements - heavy refactoring --- README.md | 10 + .../foreman_xen/xenserver/cache_refresh.js | 49 +- .../foreman_xen/xenserver/populate_fields.js | 32 - .../foreman_xen/cache_controller.rb | 9 +- app/helpers/xen_compute_helper.rb | 111 +--- .../concerns/fog_extensions/xenserver/host.rb | 23 + .../fog_extensions/xenserver/server.rb | 24 +- .../xenserver/storage_repository.rb | 45 ++ .../concerns/fog_extensions/xenserver/vdi.rb | 11 + .../concerns/foreman_xen/host_extensions.rb | 17 + app/models/foreman_xen/xenserver.rb | 557 ++++++++++++------ .../v1/compute_resources/xenserver.json.rabl | 2 +- .../v2/compute_resources/xenserver.json.rabl | 2 +- .../form/_xenserver.html.erb | 6 +- .../form/_hypervisors.html.erb | 25 +- .../form/_image_provisioning.html.erb | 48 ++ .../compute_resources_vms/form/_isos.html.erb | 14 + .../form/_network.html.erb | 20 - .../form/_network_provisioning.html.erb | 25 + .../form/_profile.html.erb | 5 + .../form/_templates.html.erb | 46 +- .../form/_volume.html.erb | 18 - .../form/_xenstore.html.erb | 119 ---- .../form/xenserver/_base.html.erb | 200 +------ .../form/xenserver/_network.html.erb | 22 + .../form/xenserver/_volume.html.erb | 14 + app/views/images/form/_xenserver.html.erb | 4 + lib/foreman_xen/engine.rb | 15 +- 28 files changed, 755 insertions(+), 718 deletions(-) delete mode 100644 app/assets/javascripts/foreman_xen/xenserver/populate_fields.js create mode 100644 app/models/concerns/fog_extensions/xenserver/host.rb create mode 100755 app/models/concerns/fog_extensions/xenserver/storage_repository.rb create mode 100644 app/models/concerns/fog_extensions/xenserver/vdi.rb create mode 100644 app/models/concerns/foreman_xen/host_extensions.rb create mode 100644 app/views/compute_resources_vms/form/_image_provisioning.html.erb create mode 100644 app/views/compute_resources_vms/form/_isos.html.erb delete mode 100644 app/views/compute_resources_vms/form/_network.html.erb create mode 100644 app/views/compute_resources_vms/form/_network_provisioning.html.erb create mode 100644 app/views/compute_resources_vms/form/_profile.html.erb delete mode 100644 app/views/compute_resources_vms/form/_volume.html.erb delete mode 100644 app/views/compute_resources_vms/form/_xenstore.html.erb create mode 100644 app/views/compute_resources_vms/form/xenserver/_network.html.erb create mode 100644 app/views/compute_resources_vms/form/xenserver/_volume.html.erb create mode 100644 app/views/images/form/_xenserver.html.erb diff --git a/README.md b/README.md index ff3e368..7b09cc5 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,16 @@ Please see the Foreman manual for further instructions: * [Foreman: How to Install a Plugin](http://theforeman.org/plugins/#2.Installation) +## Image based provisioning + +In order to use the cloud-init functionality users need to: + +- install the `genisoimage` package +- mount a "NFS ISO Library" (as XenServer calls it) which is attached to the Xen pool to a location writable by the foreman user. +- set this mount point / path as ISO library mountpoint in the compute resource + +foreman_xen then creates a network configuration file, renders the user_data template, puts them in an ISO, copies this ISO to the attached ISO-library and attaches it to the created VM, where cloud-init can use the data provided to initialize the VM. + ## Compatibility | Foreman Version | Plugin Version | diff --git a/app/assets/javascripts/foreman_xen/xenserver/cache_refresh.js b/app/assets/javascripts/foreman_xen/xenserver/cache_refresh.js index a037fd7..25518ee 100644 --- a/app/assets/javascripts/foreman_xen/xenserver/cache_refresh.js +++ b/app/assets/javascripts/foreman_xen/xenserver/cache_refresh.js @@ -1,20 +1,33 @@ -function refreshCache(item, on_success) { - tfm.tools.showSpinner(); - attribute_name = $(item).data('attribute') - data = { - type: attribute_name, - compute_resource_id: $(item).data('compute-resource-id') +function refreshCache(item) { + tfm.tools.showSpinner(); + attribute_name = $(item).data('attribute'); + sel = $(item).closest('.input-group').children('select') + data = { + type: attribute_name, + compute_resource_id: $(item).data('compute-resource-id') + }; + $.ajax({ + type:'post', + url: $(item).data('url'), + data: data, + complete: function(){ + tfm.tools.hideSpinner(); + }, + error: function(){ + notify(__("Error refreshing cache for " + attribute_name), 'error', true); + }, + success: function(results, textStatus, jqXHR){ + var elements = sel.children() + if (elements.first().val() == "") { //include_empty + elements = elements.slice(1); + } + elements.remove(); + for (var i = 0; i < results.length; i++) { + var result = results[i]; + var id = ('uuid' in result) ? result['uuid'] : result['id']; + var name = ('display_name' in result) ? result['display_name'] : result['name']; + sel.append(''); + } } - $.ajax({ - type:'post', - url: $(item).data('url'), - data: data, - complete: function(){ - tfm.tools.hideSpinner(); - }, - error: function(){ - notify(__("Error refreshing cache for " + attribute_name), 'error', true); - }, - success: on_success - }) + }); } diff --git a/app/assets/javascripts/foreman_xen/xenserver/populate_fields.js b/app/assets/javascripts/foreman_xen/xenserver/populate_fields.js deleted file mode 100644 index 4ec6f8f..0000000 --- a/app/assets/javascripts/foreman_xen/xenserver/populate_fields.js +++ /dev/null @@ -1,32 +0,0 @@ -function xenPopulateNetworks(network_list){ - $('#host_compute_attributes_VIFs_print').children().remove(); - for (var i = 0; i < network_list.length; i++) { - network = network_list[i]; - $('#host_compute_attributes_VIFs_print').append(''); - } -} - -function xenPopulateStoragePools(results){ - $('#host_compute_attributes_VBDs_sr_uuid').children().remove(); - for (var i = 0; i < results.length; i++) { - result = results[i]; - $('#host_compute_attributes_VBDs_sr_uuid').append(''); - } -} - -function xenPopulateCustomTemplates(custom_templates){ - xenPopulateTemplates(custom_templates, '#host_compute_attributes_custom_template_name'); -} - -function xenPopulateBuiltinTemplates(builtin_templates){ - xenPopulateTemplates(builtin_templates, '#host_compute_attributes_builtin_template_name'); -} - -function xenPopulateTemplates(results, selector){ - $(selector).children().remove(); - $(selector).append(''); - for (var i = 0; i < results.length; i++) { - result = results[i]; - $(selector).append(''); - } -} \ No newline at end of file diff --git a/app/controllers/foreman_xen/cache_controller.rb b/app/controllers/foreman_xen/cache_controller.rb index c7832d3..5ff99e9 100644 --- a/app/controllers/foreman_xen/cache_controller.rb +++ b/app/controllers/foreman_xen/cache_controller.rb @@ -14,7 +14,12 @@ def refresh end respond_to do |format| - format.json { render :json => @compute_resource.public_send("#{type}!") } + format.json do + filtered_data = @compute_resource.public_send("#{type}!").map do |e| + e.attributes.slice(:id, :uuid, :name, :display_name) + end + render json: filtered_data + end end end @@ -22,7 +27,7 @@ def refresh # List of methods to permit def cache_attribute_whitelist - %w[networks hypervisors templates custom_templates builtin_templates storage_pools] + %w[isos networks available_hypervisors hypervisors templates builtin_templates storage_pools] end def load_compute_resource diff --git a/app/helpers/xen_compute_helper.rb b/app/helpers/xen_compute_helper.rb index e51f65f..e90f076 100644 --- a/app/helpers/xen_compute_helper.rb +++ b/app/helpers/xen_compute_helper.rb @@ -1,121 +1,26 @@ module XenComputeHelper - def compute_attribute_map(params, compute_resource, new) - if controller_name == 'hosts' - attribute_map = hosts_controller_compute_attribute_map(params, compute_resource, new) - elsif controller_name == 'compute_attributes' - attribute_map = compute_resource_controller_attribute_map(params, compute_resource) - end - attribute_map - end + def compute_attributes_from_params(compute_resource) + id = params.dig('host', 'compute_profile_id') || params.dig('compute_profile_id') + return compute_resource.compute_profile_attributes_for id if id - def init_vmdata - vmdata = { - :ifs => { - '0' => { - :ip => '', - :gateway => '', - :netmask => '' - } - }, - :nameserver1 => '', - :nameserver2 => '', - :environment => '' - } + {} end private - def hosts_controller_compute_attribute_map(params, compute_resource, new) - attribute_map = empty_attribute_map - if new.try(:new_record?) - compute_attributes = compute_resource.compute_profile_attributes_for(params['host']['compute_profile_id']) - attribute_map = filter_compute_attributes(attribute_map, compute_attributes) - elsif new - attribute_map[:cpu_count] = new.vcpus_max || nil - attribute_map[:memory_min] = new.memory_static_min || nil - attribute_map[:memory_max] = new.memory_static_max || nil - if new.vbds - vdi = new.vbds.first.vdi - if vdi - attribute_map[:volume_selected] = vdi.sr.uuid || nil - attribute_map[:volume_size] = vdi.virtual_size ? (vdi.virtual_size.to_i / 1_073_741_824).to_s : nil - end - end - attribute_map[:network_selected] = new.vifs.first.network.name || nil if new.vifs - end - attribute_map - end - - def compute_resource_controller_attribute_map(params, compute_resource) - attribute_map = empty_attribute_map - if params && params['compute_profile_id'] - compute_attributes = compute_resource.compute_profile_attributes_for(params['compute_profile_id']) - elsif params && params['host'] && params['host']['compute_profile_id'] - compute_attributes = compute_resource.compute_profile_attributes_for(params['host']['compute_profile_id']) - end - attribute_map = filter_compute_attributes(attribute_map, compute_attributes) if compute_attributes - attribute_map - end - - def empty_attribute_map - { :volume_size => nil, - :volume_selected => nil, - :network_selected => nil, - :template_selected_custom => nil, - :template_selected_builtin => nil, - :cpu_count => nil, - :memory_min => nil, - :memory_max => nil, - :power_on => nil } - end - - def filter_compute_attributes(attribute_map, compute_attributes) - if compute_attributes['VBDs'] - attribute_map[:volume_size] = compute_attributes['VBDs']['physical_size'] - attribute_map[:volume_selected] = compute_attributes['VBDs']['sr_uuid'] - end - attribute_map[:network_selected] = compute_attributes['VIFs']['print'] if compute_attributes['VIFs'] - attribute_map[:template_selected_custom] = compute_attributes['custom_template_name'] - attribute_map[:template_selected_builtin] = compute_attributes['builtin_template_name'] - attribute_map[:cpu_count] = compute_attributes['vcpus_max'] - attribute_map[:memory_min] = compute_attributes['memory_min'] - attribute_map[:memory_max] = compute_attributes['memory_max'] - attribute_map[:power_on] = compute_attributes['start'] - attribute_map - end - - def xen_builtin_template_map(compute_resource) - compute_resource.builtin_templates.map { |t| [t.name, t.name] } - end - - def xen_custom_template_map(compute_resource) - compute_resource.custom_templates.map { |t| [t.name, t.name] } - end - - def xen_storage_pool_map(compute_resource) - compute_resource.storage_pools.map { |item| [item[:display_name], item[:uuid]] } - end - - def xen_hypervisor_map(compute_resource) - compute_resource.available_hypervisors!.map do |t| - [t.name + ' - ' + ( - t.metrics.memory_free.to_f / t.metrics.memory_total.to_f * 100 - ).round(2).to_s + '% free mem', t.name] - end - end - def selectable_f_with_cache_invalidation(f, attr, array, select_options = {}, html_options = {}, input_group_options = {}) unless html_options.key?('input_group_btn') html_options[:input_group_btn] = link_to_function( icon_text('refresh'), - "refreshCache(this, #{input_group_options[:callback]})", + 'refreshCache(this)', :class => 'btn btn-primary', :title => _(input_group_options[:title]), :data => { :url => input_group_options[:url], - :compute_resource_id => input_group_options[:computer_resource_id], - :attribute => input_group_options[:attribute] + :compute_resource_id => input_group_options[:compute_resource_id], + :attribute => input_group_options[:attribute], + :select_attr => attr } ) end diff --git a/app/models/concerns/fog_extensions/xenserver/host.rb b/app/models/concerns/fog_extensions/xenserver/host.rb new file mode 100644 index 0000000..d6f4d29 --- /dev/null +++ b/app/models/concerns/fog_extensions/xenserver/host.rb @@ -0,0 +1,23 @@ +module FogExtensions + module Xenserver + module Host + extend ActiveSupport::Concern + + included do + attribute :display_name + prepend FogExtensions::Xenserver::Host + end + + def initialize(new_attributes = {}) + super(new_attributes) + attributes[:display_name] = "#{name} - #{mem_free_gb} GB free memory" + end + + def mem_free_gb + return metrics.memory_free.to_i / 1024 / 1024 / 1024 if metrics + + 0 + end + end + end +end diff --git a/app/models/concerns/fog_extensions/xenserver/server.rb b/app/models/concerns/fog_extensions/xenserver/server.rb index 56d4a5d..570af14 100644 --- a/app/models/concerns/fog_extensions/xenserver/server.rb +++ b/app/models/concerns/fog_extensions/xenserver/server.rb @@ -5,8 +5,13 @@ module Server include ActionView::Helpers::NumberHelper - attr_accessor :start - attr_accessor :memory_min, :memory_max, :custom_template_name, :builtin_template_name, :hypervisor_host + attr_accessor :start, :image_id, :hypervisor_host, :iso, :target_sr + attr_accessor :memory_min, :memory_max, :builtin_template + attr_writer :volumes, :interfaces + + def id + uuid + end def to_s name @@ -16,6 +21,13 @@ def nics_attributes=(attrs); end def volumes_attributes=(attrs); end + def volumes + @volumes ||= [] + disks = vbds.compact.select(&:disk?) + disks.sort! { |x, y| x.userdevice <=> y.userdevice } + (disks.map(&:vdi) + @volumes).uniq + end + def memory memory_static_max.to_i end @@ -45,12 +57,18 @@ def vm_description end def interfaces - vifs + (vifs + @interfaces).uniq end def select_nic(fog_nics, nic) fog_nics[0] end + + def user_data + return !other_config['default_template'] if is_a_template + + false + end end end end diff --git a/app/models/concerns/fog_extensions/xenserver/storage_repository.rb b/app/models/concerns/fog_extensions/xenserver/storage_repository.rb new file mode 100755 index 0000000..a29c010 --- /dev/null +++ b/app/models/concerns/fog_extensions/xenserver/storage_repository.rb @@ -0,0 +1,45 @@ +module FogExtensions + module Xenserver + module StorageRepository + extend ActiveSupport::Concern + + included do + attribute :display_name + prepend FogExtensions::Xenserver::StorageRepository + end + + def initialize(new_attributes = {}) + super(new_attributes) + attributes[:display_name] = init_display_name + end + + def free_space + physical_size.to_i - physical_utilisation.to_i + end + + def free_space_gb + free_space.to_i / 1024 / 1024 / 1024 + end + + def physical_size_gb + physical_size.to_i / 1024 / 1024 / 1024 + end + + def physical_utilisation_gb + physical_utilisation.to_i / 1024 / 1024 / 1024 + end + + def init_display_name + srname = name + unless shared + pbd = pbds.first + srname = "#{name} - #{pbd.host.name}" unless pbd.nil? + end + format('%{n} (%{f}: %{f_gb} GB - %{u}: %{u_gb} GB - %{t}: %{t_gb} GB)', + n: srname, f: _('free'), f_gb: free_space_gb, + u: _('used'), u_gb: physical_utilisation_gb, + t: _('total'), t_gb: physical_size_gb) + end + end + end +end diff --git a/app/models/concerns/fog_extensions/xenserver/vdi.rb b/app/models/concerns/fog_extensions/xenserver/vdi.rb new file mode 100644 index 0000000..cd8cf03 --- /dev/null +++ b/app/models/concerns/fog_extensions/xenserver/vdi.rb @@ -0,0 +1,11 @@ +module FogExtensions + module Xenserver + module Vdi + extend ActiveSupport::Concern + + def id + uuid + end + end + end +end diff --git a/app/models/concerns/foreman_xen/host_extensions.rb b/app/models/concerns/foreman_xen/host_extensions.rb new file mode 100644 index 0000000..49f768e --- /dev/null +++ b/app/models/concerns/foreman_xen/host_extensions.rb @@ -0,0 +1,17 @@ +module ForemanXen + module HostExtensions + extend ActiveSupport::Concern + + def built(installed = true) + compute_resource.cleanup_configdrive(uuid) if compute_resource && compute_resource.type == 'ForemanXen::Xenserver' + super(installed) + end + + def disassociate! + # Disassociated host object cannot be saved unless provision_method + # is supported by the default compute resource + self.provision_method = 'build' + super + end + end +end diff --git a/app/models/foreman_xen/xenserver.rb b/app/models/foreman_xen/xenserver.rb index 6b7623c..1ddd56f 100644 --- a/app/models/foreman_xen/xenserver.rb +++ b/app/models/foreman_xen/xenserver.rb @@ -2,19 +2,58 @@ module ForemanXen class Xenserver < ComputeResource validates :url, :user, :password, :presence => true + GB_BYTES = 1_073_741_824 # 1gb in bytes + def provided_attributes super.merge( - :uuid => :reference, + :uuid => :uuid, :mac => :mac ) end def capabilities - [:build] + %i[build image new_volume] + end + + def host_compute_attrs(host) + super(host).merge( + name_description: host.comment, + is_a_template: false, + is_a_shapshot: false, + xenstore: host_xenstore_data(host), + network_data: host_network_data(host) + ) + end + + def user_data_supported + true + end + + def iso_library_mountpoint + attrs[:iso_library_mountpoint] + end + + def iso_library_mountpoint=(path) + mountpoint = path.to_s.end_with?('/') ? path.to_s : "#{path}/" + mountpoint = nil if path.to_s.strip.empty? + attrs[:iso_library_mountpoint] = mountpoint + end + + def cleanup_configdrive(uuid) + iso_file_name = "foreman-configdrive-#{uuid}.iso" + begin + path = File.join(iso_library_mountpoint, iso_file_name) + exist = File.exist? path + FileUtils.rm(path) if exist + rescue + return true unless exist + + return false + end end def find_vm_by_uuid(uuid) - client.servers.get(uuid) + client.servers.find_by(uuid: uuid) rescue Fog::XenServer::RequestFailed => e Foreman::Logging.exception("Failed retrieving xenserver vm by uuid #{uuid}", e) raise(ActiveRecord::RecordNotFound) if e.message.include?('HANDLE_INVALID') @@ -26,6 +65,7 @@ def find_vm_by_uuid(uuid) # we default to destroy the VM's storage as well. def destroy_vm(ref, args = {}) logger.info "destroy_vm: #{ref} #{args}" + cleanup_configdrive(ref) if iso_library_mountpoint find_vm_by_uuid(ref).destroy rescue ActiveRecord::RecordNotFound true @@ -60,23 +100,39 @@ def test_connection(options = {}) errors[:base] << e.message end + def available_images + custom_templates! + end + def available_hypervisors - read_from_cache('available_hypervisors', 'available_hypervisors!') + hypervisors.select(&:enabled) end def available_hypervisors! - store_in_cache('available_hypervisors') do + hypervisors!.select(&:enabled) + end + + def hypervisors + read_from_cache('hypervisors', 'hypervisors!') + end + + def hypervisors! + store_in_cache('hypervisors') do hosts = client.hosts hosts.sort_by(&:name) end end def new_nic(attr = {}) - client.networks.new attr + client.vifs.new attr end def new_volume(attr = {}) - client.storage_repositories.new attr + size = attr[:virtual_size_gb].to_i * GB_BYTES + vdi = client.vdis.new virtual_size: size.to_s + vdi.type = 'user' + vdi.sr = storage_pools.find { |s| s.uuid == attr[:sr].to_s } if attr[:sr] + vdi end def storage_pools @@ -85,34 +141,42 @@ def storage_pools def storage_pools! store_in_cache('storage_pools') do - results = [] - storages = client.storage_repositories.select { |sr| sr.type != 'udev' && sr.type != 'iso' } - storages.each do |sr| - subresults = {} - found = false - - available_hypervisors.each do |host| - next unless sr.reference == host.suspend_image_sr - - found = true - subresults[:name] = sr.name - subresults[:display_name] = sr.name + '(' + host.hostname + ')' - subresults[:uuid] = sr.uuid - break - end - unless found - subresults[:name] = sr.name - subresults[:display_name] = sr.name - subresults[:uuid] = sr.uuid - end - results.push(subresults) + pools = client.storage_repositories.select do |sr| + sr.type != 'udev' && sr.type != 'iso' end - results.sort_by! { |item| item[:display_name] } + pools.sort_by(&:display_name) + end + end + + def isos + all_isos.reject do |iso| + iso.name =~ /foreman-configdrive/ + end + end + + def isos! + all_isos!.reject do |iso| + iso.name =~ /foreman-configdrive/ + end + end + + def all_isos + read_from_cache('isos', 'isos!') + end + + def all_isos! + store_in_cache('isos') do + isos = iso_libraries.map(&:vdis).flatten + isos.sort_by(&:name) end end + def new_interface(attr = {}) + client.vifs.new attr + end + def interfaces - client.interfaces + client.vifs rescue [] end @@ -186,158 +250,137 @@ def find_snapshots end def new_vm(attr = {}) - test_connection - return unless errors.empty? - - opts = vm_instance_defaults.merge(attr.to_hash).symbolize_keys - - %i[networks volumes].each do |collection| - nested_attrs = opts.delete("#{collection}_attributes".to_sym) - opts[collection] = nested_attributes_for(collection, nested_attrs) if nested_attrs + attr = attr.to_hash.deep_symbolize_keys + %i[networks interfaces].each do |collection| + nested_attr = attr.delete("#{collection}_attributes".to_sym) + attr[collection] = nested_attributes_for(collection, nested_attr) if nested_attr end - opts.reject! { |_, v| v.nil? } - client.servers.new opts + if attr[:volumes_attributes] + vol_attr = nested_attributes_for('volumes', attr[:volumes_attributes]) + attr[:volumes] = vol_attr.map { |v| new_volume(v) } + end + attr.reject! { |_, v| v.nil? } + super(attr) + end + + def vm_attr_from_args(args) + { + name: args[:name], + name_description: args[:comment], + VCPUs_max: args[:vcpus_max], + VCPUs_at_startup: args[:vcpus_max], + memory_static_max: args[:memory_max], + memory_dynamic_max: args[:memory_max], + memory_dynamic_min: args[:memory_min], + memory_static_min: args[:memory_min] + } end def create_vm(args = {}) - custom_template_name = args[:custom_template_name].to_s - builtin_template_name = args[:builtin_template_name].to_s - - if builtin_template_name != '' && custom_template_name != '' - logger.info "custom_template_name: #{custom_template_name}" - logger.info "builtin_template_name: #{builtin_template_name}" - raise 'you can select at most one template type' - end + args = args.deep_symbolize_keys + logger.debug('create_vm args:') + logger.debug(args) begin - logger.info "create_vm(): custom_template_name: #{custom_template_name}" - logger.info "create_vm(): builtin_template_name: #{builtin_template_name}" - vm = custom_template_name != '' ? create_vm_from_custom(args) : create_vm_from_builtin(args) - vm.set_attribute('name_description', 'Provisioned by Foreman') - vm.set_attribute('VCPUs_max', args[:vcpus_max]) - vm.set_attribute('VCPUs_at_startup', args[:vcpus_max]) - vm.reload - return vm - rescue => e - logger.info e - logger.info e.backtrace.join("\n") - return false - end - end + # Create VM Object + attr = vm_attr_from_args(args) + if args[:provision_method] == 'image' + image = available_images.find { |i| i.uuid == args[:image_id].to_s } + sr = storage_pools.find { |s| s.uuid == args[:target_sr].to_s } + vm = create_vm_from_image(image, attr, sr) + else + template = builtin_templates.find { |t| t.uuid == args[:builtin_template].to_s } + raise 'Template not found' unless template - def create_vm_from_custom(args) - mem_max = args[:memory_max] - mem_min = args[:memory_min] + vm = create_vm_from_template(attr, template) + end - host = get_hypervisor_host(args) + raise 'Error creating VM' unless vm - logger.info "create_vm_from_builtin: host : #{host.name}" + # Set correct affinity + set_vm_affinity(vm, args[:hypervisor_host].to_s) - raise 'Memory max cannot be lower than Memory min' if mem_min.to_i > mem_max.to_i + # Add NICs + vm.interfaces = args[:interfaces_attributes].map do |_, v| + create_interface(vm, v[:network]) + end - template = client.custom_templates.select { |t| t.name == args[:custom_template_name] }.first - vm = template.clone args[:name] - vm.affinity = host + # Attach ConfigDrive + create_and_attach_configdrive(vm, args) if args[:configdrive] == '1' && args[:provision_method] == 'image' - vm.provision + # Attach ISO + unless args[:iso].empty? + iso_vdi = isos.find { |i| i.uuid == args[:iso] } + attach_iso(vm, iso_vdi) + end - begin - vm.vifs.first.destroy - rescue - nil - end + # Add new Volumes + unless args[:volumes_attributes].nil? + vm.volumes = args[:volumes_attributes].map do |_, v| + create_volume(vm, v) unless v[:_delete] == '1' + end + end - create_network(vm, args) + # Write XenStore data + xenstore_data = xenstore_set_mac(vm, args[:xenstore]) + set_xenstore_data(vm, xenstore_data) - args['xenstore']['vm-data']['ifs']['0']['mac'] = vm.vifs.first.mac - xenstore_data = xenstore_hash_flatten(args['xenstore']) + # Fix Description + vm.set_attribute('name-description', args[:name_description]) - vm.set_attribute('xenstore_data', xenstore_data) - if vm.memory_static_max.to_i < mem_max.to_i - vm.set_attribute('memory_static_max', mem_max) - vm.set_attribute('memory_dynamic_max', mem_max) - vm.set_attribute('memory_dynamic_min', mem_min) - vm.set_attribute('memory_static_min', mem_min) - else - vm.set_attribute('memory_static_min', mem_min) - vm.set_attribute('memory_dynamic_min', mem_min) - vm.set_attribute('memory_dynamic_max', mem_max) - vm.set_attribute('memory_static_max', mem_max) + return vm + rescue => e + cleanup_configdrive(vm.uuid) if vm&.uuid + vm&.destroy + vm.volumes.each(&:destroy) if vm&.volumes + logger.info e + logger.info e.backtrace.join("\n") + raise e end + end - disks = vm.vbds.select { |vbd| vbd.type == 'Disk' } - disks.sort! { |a, b| a.userdevice <=> b.userdevice } - i = 0 - disks.each do |vbd| - vbd.vdi.set_attribute('name-label', "#{args[:name]}_#{i}") - i += 1 + def create_vm_from_template(attr, template) + vm_attr = template.attributes.dup.merge(attr) + %i[uuid domid reference allowed_operations].each do |a| + vm_attr.delete(a) end + vm_attr[:is_a_template] = false + vm_attr[:other_config].delete('default_template') + vm_attr[:other_config]['mac_seed'] = SecureRandom.uuid + vm = new_vm(vm_attr) + # Set any host affinity (required for saving) - correct later + vm.affinity = client.hosts.first + vm.save vm end - def create_vm_from_builtin(args) - mem_max = args[:memory_max] - mem_min = args[:memory_min] - - host = get_hypervisor_host(args) - - logger.info "create_vm_from_builtin: host : #{host.name}" - - builtin_template_name = args[:builtin_template_name] - builtin_template_name = builtin_template_name.to_s - - storage_repository = client.storage_repositories.find { |sr| sr.uuid == (args[:VBDs][:sr_uuid]).to_s } - - gb = 1_073_741_824 # 1gb in bytes - size = args[:VBDs][:physical_size].to_i * gb - vdi = client.vdis.create :name => "#{args[:name]}-disk1", - :storage_repository => storage_repository, - :description => "#{args[:name]}-disk_1", - :virtual_size => size.to_s + def create_vm_from_image(image, attr, sr) + vm_ref = client.copy_server image.reference, attr[:name], sr.reference + client.provision_server vm_ref + vm = client.servers.find { |s| s.reference == vm_ref } + set_vm_profile_attributes(vm, attr) + rename_cloned_volumes(vm) + vm + end - other_config = {} - if builtin_template_name != '' - template = client.builtin_templates.find { |tmp| tmp.name == args[:builtin_template_name] } - other_config = template.other_config - other_config.delete 'disks' - other_config.delete 'default_template' - other_config['mac_seed'] = SecureRandom.uuid + def set_vm_profile_attributes(vm, attr) + # Memory limits must satisfy: + # static_min <= dynamic_min <= dynamic_max <= static_max + mem = %w[memory_static_max memory_dynamic_max + memory_dynamic_min memory_static_min] + mem.reverse! if vm.memory_static_max.to_i > attr[:memory_static_max].to_i + # VCPU values must satisfy: 0 < VCPUs_at_startup <= VCPUs_max + cpu = %w[VCPUs_max VCPUs_at_startup] + cpu.reverse! if vm.vcpus_at_startup > attr[:VCPUs_at_startup] + (mem + cpu).each { |e| vm.set_attribute e, attr[e.to_sym] } + end + + def rename_cloned_volumes(vm) + vm.volumes.each do |vol| + udev = vol.vbds.find { |v| v.vm.uuid == vm.uuid }.userdevice + name = "#{vm.name}-#{udev}" + vol.set_attribute 'name-label', name + vol.set_attribute 'name-description', name end - vm = client.servers.new :name => args[:name], - :affinity => host, - :pv_bootloader => '', - :hvm_boot_params => { :order => 'dnc' }, - :other_config => other_config, - :memory_static_max => mem_max, - :memory_static_min => mem_min, - :memory_dynamic_max => mem_max, - :memory_dynamic_min => mem_min - - vm.save :auto_start => false - client.vbds.create :vm => vm, :vdi => vdi - - create_network(vm, args) - - if args[:xstools] == '1' - # Add xs-tools ISO to newly created VMs - dvd_vdi = client.vdis.find { |isovdi| isovdi.name == 'xs-tools.iso' || isovdi.name == 'guest-tools.iso' } - vbdconnectcd = { - 'vdi' => dvd_vdi, - 'vm' => vm.reference, - 'userdevice' => '1', - 'mode' => 'RO', - 'type' => 'cd', - 'other_config' => {}, - 'qos_algorithm_type' => '', - 'qos_algorithm_params' => {} - } - vm.vbds = client.vbds.create vbdconnectcd - vm.reload - end - - vm.provision - vm.set_attribute('HVM_boot_policy', 'BIOS order') - vm.reload - vm end def console(uuid) @@ -373,9 +416,10 @@ def hypervisor def client @client ||= Fog::XenServer::Compute.new( - :xenserver_url => url, - :xenserver_username => user, - :xenserver_password => password + xenserver_url: url, + xenserver_username: user, + xenserver_password: password, + xenserver_timeout: 1800 ) end @@ -384,30 +428,94 @@ def disconnect @client = nil end - def vm_instance_defaults - super.merge({}) - end - private - def create_network(vm, args) - net = client.networks.find { |n| n.name == args[:VIFs][:print] } + def create_volume(vm, attr) + vdi = new_volume attr + udev = find_free_userdevice(vm) + vdi.name = "#{vm.name}-#{udev}" + vdi.description = "#{vm.name}-#{udev}" + vdi.save + # Attach VDI to VM + client.vbds.create vm: vm, vdi: vdi, userdevice: udev.to_s, bootable: true + vdi + end + + def create_interface(vm, network_uuid) + net = client.networks.find { |n| n.uuid == network_uuid } + devices = vm.vifs.map(&:device) + device = 0 + device += 1 while devices.include?(device.to_s) net_config = { 'mac_autogenerated' => 'True', 'vm' => vm.reference, 'network' => net.reference, 'mac' => '', - 'device' => '0', + 'device' => device.to_s, 'mtu' => '0', 'other_config' => {}, 'qos_algorithm_type' => 'ratelimit', 'qos_algorithm_params' => {} } - vm.vifs = client.vifs.create net_config - vm.reload + client.vifs.create net_config end - def xenstore_hash_flatten(nested_hash, key = nil, keychain = nil, out_hash = {}) + def attach_iso(vm, iso_vdi) + cd_drive = client.vbds.find { |v| v.vm == vm && v.type == 'CD' } + if cd_drive&.empty + client.insert_vbd cd_drive.reference, iso_vdi.reference + else + # Windows VMs expect the CDROM drive on userdevice 3 + vbds = client.vbds.select { |v| v.vm == vm } + udev = vbds.map(&:userdevice).include?('3') ? find_free_userdevice(vm) : '3' + vbd = { + 'vdi' => iso_vdi, + 'vm' => vm, + 'userdevice' => udev.to_s, + 'mode' => 'RO', + 'type' => 'CD', + 'other_config' => {}, + 'qos_algorithm_type' => '', + 'qos_algorithm_params' => {} + } + client.vbds.create vbd + end + true + end + + def find_free_userdevice(vm) + # Find next free userdevice id for vbd + # vm.vbds is not current, vm.reload not working. + vbds = client.vbds.select { |v| v.vm == vm } + userdevices = vbds.map(&:userdevice) + udev = 0 + udev += 1 while userdevices.include?(udev.to_s) + udev + end + + def xenstore_set_mac(vm, xenstore_data) + xenstore_data[:'vm-data'][:ifs][:'0'][:mac] = vm.interfaces.first.mac + xenstore_data + end + + def set_xenstore_data(vm, xenstore_data) + xenstore_data = xenstore_hash_flatten(xenstore_data) + vm.set_attribute('xenstore_data', xenstore_data) + end + + def host_xenstore_data(host) + p_if = host.primary_interface + subnet = p_if.subnet || p_if.subnet6 + { 'vm-data' => { 'ifs' => { '0' => + { 'ip' => p_if.ip.empty? ? p_if.ip6 : p_if.ip, + 'gateway' => subnet.nil? ? '' : subnet.gateway, + 'netmask' => subnet.nil? ? '' : subnet.mask } } }, + 'nameserver1' => subnet.nil? ? '' : subnet.dns_primary, + 'nameserver2' => subnet.nil? ? '' : subnet.dns_secondary, + 'environment' => host.environment.to_s } + end + + def xenstore_hash_flatten(nested_hash, _key = nil, keychain = nil, out_hash = {}) nested_hash.each do |k, v| if v.is_a? Hash xenstore_hash_flatten(v, k, "#{keychain}#{k}/", out_hash) @@ -418,6 +526,111 @@ def xenstore_hash_flatten(nested_hash, key = nil, keychain = nil, out_hash = {}) out_hash end + def set_vm_affinity(vm, hypervisor) + if hypervisor.empty? + vm.set_attribute('affinity', '') + else + vm.set_attribute('affinity', client.hosts.find_by(uuid: hypervisor)) + end + end + + def create_and_attach_configdrive(vm, attr) + network_data = add_mac_to_network_data(attr[:network_data], vm) + iso_name = generate_configdrive(vm.uuid, + vm_meta_data(vm).to_json, + network_data.deep_stringify_keys.to_json, + attr[:user_data], + iso_library_mountpoint) + rescan_iso_libraries + iso_vdi = all_isos!.find { |iso| iso.name == iso_name } + raise 'Unable to locate metadata iso on iso libraries' unless iso_vdi + + attach_iso(vm, iso_vdi) + end + + def vm_meta_data(vm) + { 'uuid' => vm.uuid, 'hostname' => vm.name } + end + + # openstack configdive network_data format spec: + # https://github.com/openstack/nova-specs/blob/master/specs/liberty/implemented/metadata-service-network-info.rst + def host_network_data(host) + p_if = host.primary_interface + network_data = { links: [], networks: [], services: [] } + network = { id: 'network0', routes: [] } + if p_if.subnet + sn = p_if.subnet + network[:ip_address] = p_if.ip unless p_if.ip.empty? + network[:type] = sn.boot_mode == 'DHCP' ? 'ipv4_dhcp' : 'ipv4' + end + if p_if.subnet6 + sn = p_if.subnet6 + network[:ip_address] = p_if.ip6 unless p_if.ip6.empty? + network[:type] = sn.boot_mode == 'DHCP' ? 'ipv6_dhcp' : 'ipv6' + end + link = { type: 'phy' } + link[:id] = p_if.name.empty? ? 'eth0' : p_if.identifier + link[:name] = link[:id] + link[:mtu] = sn.mtu + link[:ethernet_mac_address] = p_if.mac unless p_if.mac.empty? + network_data[:links] << link + network[:netmask] = sn.mask unless sn.mask.empty? + network[:link] = link[:id] + route = { network: '0.0.0.0', netmask: '0.0.0.0' } + route[:gateway] = sn.gateway unless sn.gateway.empty? + network[:routes] << route + network_data[:networks] << network + unless sn.dns_primary.empty? + dns1 = { type: 'dns', address: sn.dns_primary } + network_data[:services] << dns1 + end + unless sn.dns_secondary.empty? + dns2 = { type: 'dns', address: sn.dns_secondary } + network_data[:services] << dns2 + end + network_data + end + + def add_mac_to_network_data(network_data, vm) + network_data[:links][0][:ethernet_mac_address] = vm.interfaces.first.mac unless network_data[:links][0][:ethernet_mac_address] + network_data + end + + def generate_configdrive(vm_uuid, meta_data, network_data, user_data, dst_dir) + Dir.mktmpdir('foreman-configdrive') do |wd| + iso_file_name = "foreman-configdrive-#{vm_uuid}.iso" + iso_file_path = File.join(wd, iso_file_name) + config_dir = FileUtils.mkdir_p(File.join(wd, 'openstack/latest')).first + meta_data_path = File.join(config_dir, 'meta_data.json') + user_data_path = File.join(config_dir, 'user_data') + network_data_path = File.join(config_dir, 'network_data.json') + File.write(meta_data_path, meta_data) + File.write(user_data_path, user_data) + File.write(network_data_path, network_data) + + cmd = ['/usr/bin/genisoimage', '-output', iso_file_path, + '-volid', 'config-2', '-joliet', '-rock', wd] + + raise ::Foreman::Exception, N_('ISO build failed, is the genisoimage package installed?') unless system(*cmd) + + FileUtils.cp(iso_file_path, dst_dir) + + return iso_file_name + end + end + + def rescan_iso_libraries + iso_libraries.each do |sr| + client.scan_sr sr.reference + end + end + + def iso_libraries + client.storage_repositories.select do |sr| + sr.type == 'iso' + end + end + def get_templates(templates) tmps = templates.reject(&:is_a_snapshot) tmps.sort_by(&:name) diff --git a/app/views/api/v1/compute_resources/xenserver.json.rabl b/app/views/api/v1/compute_resources/xenserver.json.rabl index 1a54a69..7eff929 100644 --- a/app/views/api/v1/compute_resources/xenserver.json.rabl +++ b/app/views/api/v1/compute_resources/xenserver.json.rabl @@ -1 +1 @@ -attributes :user +attributes :user, :iso_library_mountpoint diff --git a/app/views/api/v2/compute_resources/xenserver.json.rabl b/app/views/api/v2/compute_resources/xenserver.json.rabl index 1a54a69..7eff929 100644 --- a/app/views/api/v2/compute_resources/xenserver.json.rabl +++ b/app/views/api/v2/compute_resources/xenserver.json.rabl @@ -1 +1 @@ -attributes :user +attributes :user, :iso_library_mountpoint diff --git a/app/views/compute_resources/form/_xenserver.html.erb b/app/views/compute_resources/form/_xenserver.html.erb index ba661a9..0361da3 100644 --- a/app/views/compute_resources/form/_xenserver.html.erb +++ b/app/views/compute_resources/form/_xenserver.html.erb @@ -1,11 +1,9 @@ <%= text_f f, :url, :class => "input-xlarge", :help_block => _("e.g. x.x.x.x") %> <%= text_f f, :user %> <%= password_f f, :password %> - +<%= text_f f, :iso_library_mountpoint %> <% hypervisor = f.object.hypervisor.uuid rescue nil %> <% if hypervisor -%> <%= f.hidden_field :uuid, :value => hypervisor %> <% end -%> -<%= link_to_function _("Test Connection"), "testConnection(this)", :class => "btn + #{hypervisor.nil? ? "btn-default" : "btn-success"}", :'data-url' => test_connection_compute_resources_path %> - -<%= hidden_spinner('', :id => 'test_connection_indicator') %> \ No newline at end of file +<%= test_connection_button_f(f, !hypervisor.nil?) %> diff --git a/app/views/compute_resources_vms/form/_hypervisors.html.erb b/app/views/compute_resources_vms/form/_hypervisors.html.erb index 43a864b..20d808e 100644 --- a/app/views/compute_resources_vms/form/_hypervisors.html.erb +++ b/app/views/compute_resources_vms/form/_hypervisors.html.erb @@ -1,12 +1,15 @@ -
-
- <%= selectable_f f, :hypervisor_host, - [[_("Automatic allocation"), ""]] + xen_hypervisor_map(compute_resource), - {}, - { :class => 'form-control span2', - :disabled => (controller_name != 'hosts'), - :label => 'Hypervisor' - } - %> -
+
+ <%= selectable_f_with_cache_invalidation(f, :hypervisor_host, + options_from_collection_for_select( + compute_resource.available_hypervisors, :uuid, :display_name + ), + { include_blank: 'Automatic allocation' }, + { class: "span2", + disabled: (controller_name != 'hosts'), + label: _("Hypervisor"), }, + { title: 'Refresh available hypervisors', + url: '/foreman_xen/cache/refresh', + compute_resource_id: compute_resource.id, + attribute: 'available_hypervisors' } + ) %>
diff --git a/app/views/compute_resources_vms/form/_image_provisioning.html.erb b/app/views/compute_resources_vms/form/_image_provisioning.html.erb new file mode 100644 index 0000000..b0db72b --- /dev/null +++ b/app/views/compute_resources_vms/form/_image_provisioning.html.erb @@ -0,0 +1,48 @@ + +<%= javascript_tag(" +$(document).on('change', '#host_provision_method_image', function(){ + if ($('#host_provision_method_image').is(':checked')) { + $('#target_sr_selection').appendTo($('#image_provisioning')); + $('#target_sr_selection').show(); + } +}); +"); %> diff --git a/app/views/compute_resources_vms/form/_isos.html.erb b/app/views/compute_resources_vms/form/_isos.html.erb new file mode 100644 index 0000000..92d22ba --- /dev/null +++ b/app/views/compute_resources_vms/form/_isos.html.erb @@ -0,0 +1,14 @@ +
+ <%= selectable_f_with_cache_invalidation(f, :iso, + options_from_collection_for_select( + compute_resource.isos, :uuid, :name, compute_attributes.dig(:iso) + ), + { prompt: 'Select ISO to attach' }, + { class: "span2", + label: _('Attach ISO'), }, + { title: 'Refresh available storage repositories', + url: '/foreman_xen/cache/refresh', + compute_resource_id: compute_resource.id, + attribute: 'isos' } + ) %> +
diff --git a/app/views/compute_resources_vms/form/_network.html.erb b/app/views/compute_resources_vms/form/_network.html.erb deleted file mode 100644 index 49d762d..0000000 --- a/app/views/compute_resources_vms/form/_network.html.erb +++ /dev/null @@ -1,20 +0,0 @@ -
-
- <%= selectable_f_with_cache_invalidation f, :print, - compute_resource.networks.map(&:name), - { :include_blank => compute_resource.networks.any? ? false : _('No networks'), - :selected => attribute_map[:network_selected] - }, - { :class => 'span2', - :label => _('Network') - }, - { - :callback => 'xenPopulateNetworks', - :title => 'Refresh available networks', - :url => '/foreman_xen/cache/refresh', - :computer_resource_id => compute_resource.id, - :attribute => 'networks' - } - %> -
-
\ No newline at end of file diff --git a/app/views/compute_resources_vms/form/_network_provisioning.html.erb b/app/views/compute_resources_vms/form/_network_provisioning.html.erb new file mode 100644 index 0000000..23158aa --- /dev/null +++ b/app/views/compute_resources_vms/form/_network_provisioning.html.erb @@ -0,0 +1,25 @@ + + +<%= javascript_tag(" +$(document).ready(function(){ + if (!$('#network_provisioning').has('#vm_template_selection').length) { + $('#vm_template_selection').appendTo($('#network_provisioning')); + $('#vm_template_selection').show(); + } +}); +") %> diff --git a/app/views/compute_resources_vms/form/_profile.html.erb b/app/views/compute_resources_vms/form/_profile.html.erb new file mode 100644 index 0000000..178adc1 --- /dev/null +++ b/app/views/compute_resources_vms/form/_profile.html.erb @@ -0,0 +1,5 @@ +
+ <%= counter_f f, :vcpus_max, :label => _('vCPUs'), :label_size => 'col-md-2', :'data-soft-max' => compute_resource.max_cpu_count %> + <%= byte_size_f f, :memory_min, :label => _('Memory Min'), :label_size => "col-md-2", :'data-soft-max' => compute_resource.max_memory %> + <%= byte_size_f f, :memory_max, :label => _('Memory Max'), :label_size => "col-md-2", :'data-soft-max' => compute_resource.max_memory %> +
diff --git a/app/views/compute_resources_vms/form/_templates.html.erb b/app/views/compute_resources_vms/form/_templates.html.erb index 6ea30b6..cfc156a 100644 --- a/app/views/compute_resources_vms/form/_templates.html.erb +++ b/app/views/compute_resources_vms/form/_templates.html.erb @@ -1,39 +1,17 @@
-
-
- <%= selectable_f_with_cache_invalidation f, - :custom_template_name, - [[_("No template"), ""]] + xen_custom_template_map(compute_resource), - { :selected => attribute_map[:template_selected_custom] }, - { :class => 'form-control span2', - :label => 'Custom Template' - }, - { - :callback => 'xenPopulateCustomTemplates', - :title => 'Refresh available custom templates', - :url => '/foreman_xen/cache/refresh', - :computer_resource_id => compute_resource.id, - :attribute => 'custom_templates' - } - %> -
- +
- <%= selectable_f_with_cache_invalidation f, - :builtin_template_name, - [[_("No template"), ""]] + xen_builtin_template_map(compute_resource), - { :selected => attribute_map[:template_selected_builtin] }, - { :class => 'form-control span2', - :label => 'Builtin Template' - }, - { - :callback => 'xenPopulateBuiltinTemplates', - :title => 'Refresh available builtin templates', - :url => '/foreman_xen/cache/refresh', - :computer_resource_id => compute_resource.id, - :attribute => 'builtin_templates' - } - %> + <%= selectable_f_with_cache_invalidation(f, :builtin_template, + options_from_collection_for_select( + compute_resource.builtin_templates, :uuid, :name + ), + { prompt: 'Select a Template'}, + { class: 'span2', label: 'Builtin Template' }, + { title: 'Refresh available builtin templates', + url: '/foreman_xen/cache/refresh', + compute_resource_id: compute_resource.id, + attribute: 'builtin_templates' } + ) %>
diff --git a/app/views/compute_resources_vms/form/_volume.html.erb b/app/views/compute_resources_vms/form/_volume.html.erb deleted file mode 100644 index 22dd542..0000000 --- a/app/views/compute_resources_vms/form/_volume.html.erb +++ /dev/null @@ -1,18 +0,0 @@ -
- <%= selectable_f_with_cache_invalidation f, :sr_uuid, - xen_storage_pool_map(compute_resource), - { :selected => attribute_map[:volume_selected] }, - { :class => "span2", - :label => _("Storage Repository"), - }, - { - :callback => 'xenPopulateStoragePools', - :title => 'Refresh available storage repositories', - :url => '/foreman_xen/cache/refresh', - :computer_resource_id => compute_resource.id, - :attribute => 'storage_pools' - } - %> - - <%= text_f f, :physical_size, :class => "input-mini", :label => _("Size (GB)"), :value => attribute_map[:volume_size] %> -
\ No newline at end of file diff --git a/app/views/compute_resources_vms/form/_xenstore.html.erb b/app/views/compute_resources_vms/form/_xenstore.html.erb deleted file mode 100644 index 195a9c1..0000000 --- a/app/views/compute_resources_vms/form/_xenstore.html.erb +++ /dev/null @@ -1,119 +0,0 @@ -<% - - if params[:host] && params[:host][:compute_attributes] && params[:host][:compute_attributes][:xenstore] - vmdata = params[:host][:compute_attributes][:xenstore]['vm-data'] - else - vmdata = init_vmdata - end - - disabled = 'readonly' - -%> - -
- -
- -
- - -
-
- id="xenstore-vm-data-ifs-0-ip" type="text" name="host[compute_attributes][xenstore][vm-data][ifs][0][ip]" class="form-control input-mini" value="<%= vmdata[:ifs]['0'][:ip] %>"> -
-
-
- -
- - -
-
- id="xenstore-vm-data-ifs-0-gateway" type="text" name="host[compute_attributes][xenstore][vm-data][ifs][0][gateway]" class="form-control input-mini" value="<%= vmdata[:ifs]['0'][:gateway] %>"> -
-
-
- -
- - -
-
- id="xenstore-vm-data-ifs-0-netmask" type="text" name="host[compute_attributes][xenstore][vm-data][ifs][0][netmask]" class="form-control input-mini" value="<%= vmdata[:ifs]['0'][:netmask] %>"> -
-
-
- -
- - -
- id="xenstore-vm-data-nameserver1" type="text" name="host[compute_attributes][xenstore][vm-data][nameserver1]" class="form-control input-mini" value="<%= vmdata[:nameserver1] %>"> -
-
- -
- - -
- id="xenstore-vm-data-nameserver2" type="text" name="host[compute_attributes][xenstore][vm-data][nameserver2]" class="form-control input-mini" value="<%= vmdata[:nameserver2] %>"> -
-
- -
- - -
- id="xenstore-vm-data-environment" type="text" name="host[compute_attributes][xenstore][vm-data][environment]" class="form-control input-mini" value="<%= vmdata[:environment] %>"> -
-
-
-
- - - \ No newline at end of file diff --git a/app/views/compute_resources_vms/form/xenserver/_base.html.erb b/app/views/compute_resources_vms/form/xenserver/_base.html.erb index 46af487..11503cd 100644 --- a/app/views/compute_resources_vms/form/xenserver/_base.html.erb +++ b/app/views/compute_resources_vms/form/xenserver/_base.html.erb @@ -1,196 +1,40 @@ -<% new = f.object - attribute_map = compute_attribute_map(params, compute_resource, new) - - hide_raw = '' - if params && params['host'] && params['host']['compute_attributes'] && params['host']['compute_attributes']['custom_template_name'] != '' - hide_raw = 'display:none;' - end - - if controller_name == 'hosts' - fields_enabled = true - start_enable = true - elsif controller_name == 'compute_attributes' - fields_enabled = true - compute_attributes = compute_resource.compute_profile_attributes_for(params['compute_profile_id']) - name = compute_attributes['name'] - else - fields_enabled = false - end - --%> +<% compute_attributes = compute_attributes_from_params(compute_resource) -%> +<%= javascript_include_tag 'foreman_xen/xenserver/cache_refresh' %> <%= javascript_tag("$(document).on('ContentLoad', tfm.numFields.initAll)"); %> - -
-
- VM Profile -
- - <%= counter_f f, :vcpus_max, :disabled => !fields_enabled, :label => _('vCPUs'), :label_size => 'col-md-2', :'data-soft-max' => compute_resource.max_cpu_count %> - <%= byte_size_f f, :memory_min, :disabled => !fields_enabled, :label => _('Memory Min'), :label_size => "col-md-2", :'data-soft-max' => compute_resource.max_memory %> - <%= byte_size_f f, :memory_max, :disabled => !fields_enabled, :label => _('Memory Max'), :label_size => "col-md-2", :'data-soft-max' => compute_resource.max_memory %> -
-
-
- +
- <%= field_set_tag 'VM Template', :id => 'xen_templates', :title => _('Template') do -%> - <%= render 'compute_resources_vms/form/templates', :f => f, :compute_resource => compute_resource, :new => new, :attribute_map => attribute_map %> - <% end -%> -
- - -
- <%= field_set_tag 'Xen Store Data', :id => 'xen_store_data', :title => _('Xen Store Data') do -%> - <%= render 'compute_resources_vms/form/xenstore', :f => f, :compute_resource => compute_resource, :new => new, :attribute_map => attribute_map %> - <% end %> -
- - -
- <%= field_set_tag 'Storage', :id => 'storage_volumes', :title => _('Storage') do -%> - <%= f.fields_for :VBDs do |i| %> - <%= render 'compute_resources_vms/form/volume', :f => i, :compute_resource => compute_resource, :new => new, :attribute_map => attribute_map %> - <% end -%> + <%= field_set_tag 'VM Profile', :id => 'vm_profile', :title => _('VM Profile') do -%> + <%= render 'compute_resources_vms/form/profile', :f => f, :compute_resource => compute_resource %> <% end -%> - <%= checkbox_f f, :xstools, :checked => false, :label => _('Insert XS Tools ISO Drive') %>
- -
- <%= field_set_tag 'Network interfaces', :id => 'network_interfaces', :title => _('Networks') do -%> - <%= f.fields_for :VIFs do |i| %> - <%= render 'compute_resources_vms/form/network', :f => i, :compute_resource => compute_resource, :new => new, :attribute_map => attribute_map %> - <% end -%> + +
+ <%= field_set_tag 'Attach ISO', :id => 'xen_isos', :title => _('ISOs') do -%> + <%= render 'compute_resources_vms/form/isos', :f => f, :compute_resource => compute_resource, compute_attributes: compute_attributes %> <% end -%>
-
+ +
<%= field_set_tag 'Start on server', :id => 'xen_hypervisors', :title => _('Start on server') do -%> - <%= render 'compute_resources_vms/form/hypervisors', :f => f, :compute_resource => compute_resource, :new => new, :attribute_map => attribute_map %> + <%= render 'compute_resources_vms/form/hypervisors', :f => f, :compute_resource => compute_resource %> <% end -%>
-
+<% start_checked = compute_attributes.dig(:start) == '0' ? false : true -%> +
<%= field_set_tag 'VM Startup Options', :id => 'vm_startup_options', :title => _('Power ON VM') do -%> - <%= checkbox_f f, :start, :checked => (attribute_map[:power_on] == '1' || start_enable), :label => _("Power ON VM") %> + <%= checkbox_f f, :start, :checked => start_checked, :label => _("Power ON VM") %> <% end -%>
- - -<%= compute_specific_js(compute_resource, 'foreman_xen/cache_refresh') %> -<%= compute_specific_js(compute_resource, 'foreman_xen/populate_fields') %> + +<% + arch ||= nil ; os ||= nil + images = possible_images(compute_resource, arch, os) +%> +<%= render 'compute_resources_vms/form/image_provisioning', f: f, compute_resource: compute_resource, images: images %> +<%= render 'compute_resources_vms/form/network_provisioning', f: f, compute_resource: compute_resource %> diff --git a/app/views/compute_resources_vms/form/xenserver/_network.html.erb b/app/views/compute_resources_vms/form/xenserver/_network.html.erb new file mode 100644 index 0000000..1d527fa --- /dev/null +++ b/app/views/compute_resources_vms/form/xenserver/_network.html.erb @@ -0,0 +1,22 @@ +
+
+ <%= selectable_f_with_cache_invalidation f, :network, + options_from_collection_for_select( + compute_resource.networks, :uuid, :name + ), + { include_blank: compute_resource.networks.any? ? false : _('No networks') }, + { class: 'span2', + label: _('Network'), + label_size: 'col-md-3', + sizer: 'col-md-8', + disabled: (controller_name != 'hosts') }, + { title: 'Refresh available networks', + url: '/foreman_xen/cache/refresh', + compute_resource_id: compute_resource.id, + attribute: 'networks' } + %> +
+
+<%= javascript_tag(" +$(document).ready(function(){ $('#network_interfaces').hide(); }); +") if controller_name != 'hosts' %> diff --git a/app/views/compute_resources_vms/form/xenserver/_volume.html.erb b/app/views/compute_resources_vms/form/xenserver/_volume.html.erb new file mode 100644 index 0000000..a71b8a4 --- /dev/null +++ b/app/views/compute_resources_vms/form/xenserver/_volume.html.erb @@ -0,0 +1,14 @@ +<%= selectable_f_with_cache_invalidation f, :sr, + options_from_collection_for_select( + compute_resource.storage_pools, :uuid, :display_name, (f.object.sr.uuid rescue nil) + ), + {}, + { class: 'span2', + label: _("Storage Repository") }, + { title: 'Refresh available storage repositories', + url: '/foreman_xen/cache/refresh', + compute_resource_id: compute_resource.id, + attribute: 'storage_pools' } +%> + +<%= text_f f, :virtual_size_gb, :class => "input-mini", :label => _("Size (GB)"), :value => (f.object.virtual_size.to_i / 1_073_741_824) %> diff --git a/app/views/images/form/_xenserver.html.erb b/app/views/images/form/_xenserver.html.erb new file mode 100644 index 0000000..1bd9af3 --- /dev/null +++ b/app/views/images/form/_xenserver.html.erb @@ -0,0 +1,4 @@ +<%= 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") %> +<%= checkbox_f f, :user_data, :help_inline => _("Does this image support user data input (e.g. via cloud-init)?") %> +<%= password_f f, :password, :help_inline => _("Password to authenticate with - used for SSH finish step.") %> +<%= image_field(f, :label => _("Image UUID"), :help_inline => _("UUID to template")) %> diff --git a/lib/foreman_xen/engine.rb b/lib/foreman_xen/engine.rb index fd18992..9df9d03 100644 --- a/lib/foreman_xen/engine.rb +++ b/lib/foreman_xen/engine.rb @@ -20,7 +20,7 @@ class Engine < ::Rails::Engine requires_foreman '>= 1.18' # Register xen compute resource in foreman compute_resource ForemanXen::Xenserver - parameter_filter(ComputeResource, :uuid) + parameter_filter(ComputeResource, :uuid, :iso_library_mountpoint) end end @@ -43,11 +43,22 @@ class Engine < ::Rails::Engine begin # extend fog xen server and image models. require 'fog/xenserver/compute/models/server' + require 'fog/xenserver/compute/models/host' + require 'fog/xenserver/compute/models/vdi' + require 'fog/xenserver/compute/models/storage_repository' require File.expand_path('../../app/models/concerns/fog_extensions/xenserver/server', __dir__) + require File.expand_path('../../app/models/concerns/fog_extensions/xenserver/host', __dir__) + require File.expand_path('../../app/models/concerns/fog_extensions/xenserver/vdi', __dir__) + require File.expand_path('../../app/models/concerns/fog_extensions/xenserver/storage_repository', __dir__) require File.expand_path('../../app/models/concerns/foreman_xen/host_helper_extensions', __dir__) + require File.expand_path('../../app/models/concerns/foreman_xen/host_extensions', __dir__) - Fog::XenServer::Compute::Server.send(:include, ::FogExtensions::Xenserver::Server) + Fog::XenServer::Compute::Models::Server.send(:include, ::FogExtensions::Xenserver::Server) + Fog::XenServer::Compute::Models::Host.send(:include, ::FogExtensions::Xenserver::Host) + Fog::XenServer::Compute::Models::Vdi.send(:include, ::FogExtensions::Xenserver::Vdi) + Fog::XenServer::Compute::Models::StorageRepository.send(:include, ::FogExtensions::Xenserver::StorageRepository) ::HostsHelper.send(:include, ForemanXen::HostHelperExtensions) + ::Host::Managed.send(:prepend, ForemanXen::HostExtensions) rescue => e Rails.logger.warn "Foreman-Xen: skipping engine hook (#{e})" end