foreman_xen 0.7.1 → 1.0.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -1
  3. data/app/assets/javascripts/foreman_xen/xenserver/cache_refresh.js +31 -18
  4. data/app/controllers/foreman_xen/cache_controller.rb +8 -5
  5. data/app/helpers/xen_compute_helper.rb +8 -103
  6. data/app/models/concerns/fog_extensions/xenserver/host.rb +23 -0
  7. data/app/models/concerns/fog_extensions/xenserver/server.rb +21 -3
  8. data/app/models/concerns/fog_extensions/xenserver/storage_repository.rb +45 -0
  9. data/app/models/concerns/fog_extensions/xenserver/vdi.rb +11 -0
  10. data/app/models/concerns/foreman_xen/host_extensions.rb +17 -0
  11. data/app/models/foreman_xen/xenserver.rb +395 -172
  12. data/app/views/api/v1/compute_resources/xenserver.json.rabl +1 -1
  13. data/app/views/api/v2/compute_resources/xenserver.json.rabl +1 -1
  14. data/app/views/compute_resources/form/_xenserver.html.erb +2 -4
  15. data/app/views/compute_resources_vms/form/_hypervisors.html.erb +15 -11
  16. data/app/views/compute_resources_vms/form/_image_provisioning.html.erb +49 -0
  17. data/app/views/compute_resources_vms/form/_isos.html.erb +15 -0
  18. data/app/views/compute_resources_vms/form/_network_provisioning.html.erb +26 -0
  19. data/app/views/compute_resources_vms/form/_profile.html.erb +5 -0
  20. data/app/views/compute_resources_vms/form/_templates.html.erb +13 -34
  21. data/app/views/compute_resources_vms/form/xenserver/_base.html.erb +22 -178
  22. data/app/views/compute_resources_vms/form/xenserver/_network.html.erb +23 -0
  23. data/app/views/compute_resources_vms/form/xenserver/_volume.html.erb +15 -0
  24. data/app/views/compute_resources_vms/index/_xenserver.html.erb +1 -1
  25. data/app/views/images/form/_xenserver.html.erb +4 -0
  26. data/lib/foreman_xen/engine.rb +14 -3
  27. data/lib/foreman_xen/version.rb +1 -1
  28. data/test/test_helper.rb +1 -3
  29. metadata +14 -7
  30. data/app/assets/javascripts/foreman_xen/xenserver/populate_fields.js +0 -32
  31. data/app/views/compute_resources_vms/form/_network.html.erb +0 -20
  32. data/app/views/compute_resources_vms/form/_volume.html.erb +0 -18
  33. data/app/views/compute_resources_vms/form/_xenstore.html.erb +0 -119
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8538e8389348354506cb45bc49e560cd902642017bbd42a181612ba3296fe0ca
4
- data.tar.gz: a1eddbd74c4d0c5b70bbcb04efe1a5fefd68555b4229934b6a4169e86b08eda0
3
+ metadata.gz: ef04a811068d68a27dc16b361c4974e63aa95586ceba0d0d071ff167e3c17288
4
+ data.tar.gz: 9e198c6d56cc2f9f93bfa9b4eb998f1976ed7459f7559470512aa05d2fcf4eff
5
5
  SHA512:
6
- metadata.gz: a435f72d1db68dd8550a3dfa67a82f230cfdaccb71e36fa6821fa57b5f3f2d36037f4ba5dc9d42cf7a3e453eb0a94227980c792522cca2a5704e70d8e9fc6ffe
7
- data.tar.gz: c70951ebf49095228ee22742da35f14ae4dd68d8a47e908dce45d9b790de99617320d1a3abb66f214215580ded2e6daaeba5d1342a459d755479a4ea8dbe8244
6
+ metadata.gz: be7c66d98d7078da40393baa466e46904fd6bf2dc6484d9430c6f2261f103e8d5144d308fa170c06744602a85dd36f018af818997a1f5d26f13102e47776957e
7
+ data.tar.gz: 9f004290f17fb9c2d5b2eb69c7d7bf58831ce9a45beae9bbbf20543c5b1ceba95aea40d24cd8f13fc3e0e61c0b2b74569c4fad0918460c2f4216a15cde98c6e4
data/README.md CHANGED
@@ -13,6 +13,16 @@ Please see the Foreman manual for further instructions:
13
13
 
14
14
  * [Foreman: How to Install a Plugin](http://theforeman.org/plugins/#2.Installation)
15
15
 
16
+ ## Image based provisioning
17
+
18
+ In order to use the cloud-init functionality users need to:
19
+
20
+ - install the `genisoimage` package
21
+ - mount a "NFS ISO Library" (as XenServer calls it) which is attached to the Xen pool to a location writable by the foreman user.
22
+ - set this mount point / path as ISO library mountpoint in the compute resource
23
+
24
+ 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.
25
+
16
26
  ## Compatibility
17
27
 
18
28
  | Foreman Version | Plugin Version |
@@ -24,7 +34,8 @@ Please see the Foreman manual for further instructions:
24
34
  | >=1.13, <1.14 | 0.4.x (unmaintained) |
25
35
  | >=1.14, <1.17 | 0.5.x (unmaintained) |
26
36
  | >=1.17, <1.18 | 0.6.x (unmaintained) |
27
- | >=1.18 | 0.7.x |
37
+ | >=1.18, <1.20 | 0.7.x (unmaintained) |
38
+ | >=1.20 | 1.0.0 |
28
39
 
29
40
  ## Support
30
41
 
@@ -1,20 +1,33 @@
1
- function refreshCache(item, on_success) {
2
- tfm.tools.showSpinner();
3
- attribute_name = $(item).data('attribute')
4
- data = {
5
- type: attribute_name,
6
- compute_resource_id: $(item).data('compute-resource-id')
1
+ function refreshCache(item) {
2
+ tfm.tools.showSpinner();
3
+ attribute_name = $(item).data('attribute');
4
+ sel = $(item).closest('.input-group').children('select')
5
+ data = {
6
+ type: attribute_name,
7
+ compute_resource_id: $(item).data('compute-resource-id')
8
+ };
9
+ $.ajax({
10
+ type:'post',
11
+ url: $(item).data('url'),
12
+ data: data,
13
+ complete: function(){
14
+ tfm.tools.hideSpinner();
15
+ },
16
+ error: function(){
17
+ notify(__("Error refreshing cache for " + attribute_name), 'error', true);
18
+ },
19
+ success: function(results, textStatus, jqXHR){
20
+ var elements = sel.children()
21
+ if (elements.first().val() == "") { //include_empty
22
+ elements = elements.slice(1);
23
+ }
24
+ elements.remove();
25
+ for (var i = 0; i < results.length; i++) {
26
+ var result = results[i];
27
+ var id = ('uuid' in result) ? result['uuid'] : result['id'];
28
+ var name = ('display_name' in result) ? result['display_name'] : result['name'];
29
+ sel.append('<option value=' + id + '>' + name + '</option>');
30
+ }
7
31
  }
8
- $.ajax({
9
- type:'post',
10
- url: $(item).data('url'),
11
- data: data,
12
- complete: function(){
13
- tfm.tools.hideSpinner();
14
- },
15
- error: function(){
16
- notify(__("Error refreshing cache for " + attribute_name), 'error', true);
17
- },
18
- success: on_success
19
- })
32
+ });
20
33
  }
@@ -6,9 +6,7 @@ module ForemanXen
6
6
  def refresh
7
7
  type = params[:type]
8
8
 
9
- unless cache_attribute_whitelist.include?(type)
10
- process_error(:error_msg => "Error refreshing cache. #{type} is not a white listed attribute")
11
- end
9
+ process_error(:error_msg => "Error refreshing cache. #{type} is not a white listed attribute") unless cache_attribute_whitelist.include?(type)
12
10
 
13
11
  unless @compute_resource.respond_to?("#{type}!")
14
12
  process_error(:error_msg => "Error refreshing cache. Method '#{type}!' not found for compute resource" +
@@ -16,7 +14,12 @@ module ForemanXen
16
14
  end
17
15
 
18
16
  respond_to do |format|
19
- format.json { render :json => @compute_resource.public_send("#{type}!") }
17
+ format.json do
18
+ filtered_data = @compute_resource.public_send("#{type}!").map do |e|
19
+ e.attributes.slice(:id, :uuid, :name, :display_name)
20
+ end
21
+ render json: filtered_data
22
+ end
20
23
  end
21
24
  end
22
25
 
@@ -24,7 +27,7 @@ module ForemanXen
24
27
 
25
28
  # List of methods to permit
26
29
  def cache_attribute_whitelist
27
- %w[networks hypervisors templates custom_templates builtin_templates storage_pools]
30
+ %w[isos networks available_hypervisors hypervisors templates builtin_templates storage_pools]
28
31
  end
29
32
 
30
33
  def load_compute_resource
@@ -1,121 +1,26 @@
1
1
  module XenComputeHelper
2
- def compute_attribute_map(params, compute_resource, new)
3
- if controller_name == 'hosts'
4
- attribute_map = hosts_controller_compute_attribute_map(params, compute_resource, new)
5
- elsif controller_name == 'compute_attributes'
6
- attribute_map = compute_resource_controller_attribute_map(params, compute_resource)
7
- end
8
- attribute_map
9
- end
2
+ def compute_attributes_from_params(compute_resource)
3
+ id = params.dig('host', 'compute_profile_id') || params.dig('compute_profile_id')
4
+ return compute_resource.compute_profile_attributes_for id if id
10
5
 
11
- def init_vmdata
12
- vmdata = {
13
- :ifs => {
14
- '0' => {
15
- :ip => '',
16
- :gateway => '',
17
- :netmask => ''
18
- }
19
- },
20
- :nameserver1 => '',
21
- :nameserver2 => '',
22
- :environment => ''
23
- }
6
+ {}
24
7
  end
25
8
 
26
9
  private
27
10
 
28
- def hosts_controller_compute_attribute_map(params, compute_resource, new)
29
- attribute_map = empty_attribute_map
30
- if new.try(:new_record?)
31
- compute_attributes = compute_resource.compute_profile_attributes_for(params['host']['compute_profile_id'])
32
- attribute_map = filter_compute_attributes(attribute_map, compute_attributes)
33
- elsif new
34
- attribute_map[:cpu_count] = new.vcpus_max || nil
35
- attribute_map[:memory_min] = new.memory_static_min || nil
36
- attribute_map[:memory_max] = new.memory_static_max || nil
37
- if new.vbds
38
- vdi = new.vbds.first.vdi
39
- if vdi
40
- attribute_map[:volume_selected] = vdi.sr.uuid || nil
41
- attribute_map[:volume_size] = vdi.virtual_size ? (vdi.virtual_size.to_i / 1_073_741_824).to_s : nil
42
- end
43
- end
44
- attribute_map[:network_selected] = new.vifs.first.network.name || nil if new.vifs
45
- end
46
- attribute_map
47
- end
48
-
49
- def compute_resource_controller_attribute_map(params, compute_resource)
50
- attribute_map = empty_attribute_map
51
- if params && params['compute_profile_id']
52
- compute_attributes = compute_resource.compute_profile_attributes_for(params['compute_profile_id'])
53
- elsif params && params['host'] && params['host']['compute_profile_id']
54
- compute_attributes = compute_resource.compute_profile_attributes_for(params['host']['compute_profile_id'])
55
- end
56
- attribute_map = filter_compute_attributes(attribute_map, compute_attributes) if compute_attributes
57
- attribute_map
58
- end
59
-
60
- def empty_attribute_map
61
- { :volume_size => nil,
62
- :volume_selected => nil,
63
- :network_selected => nil,
64
- :template_selected_custom => nil,
65
- :template_selected_builtin => nil,
66
- :cpu_count => nil,
67
- :memory_min => nil,
68
- :memory_max => nil,
69
- :power_on => nil }
70
- end
71
-
72
- def filter_compute_attributes(attribute_map, compute_attributes)
73
- if compute_attributes['VBDs']
74
- attribute_map[:volume_size] = compute_attributes['VBDs']['physical_size']
75
- attribute_map[:volume_selected] = compute_attributes['VBDs']['sr_uuid']
76
- end
77
- attribute_map[:network_selected] = compute_attributes['VIFs']['print'] if compute_attributes['VIFs']
78
- attribute_map[:template_selected_custom] = compute_attributes['custom_template_name']
79
- attribute_map[:template_selected_builtin] = compute_attributes['builtin_template_name']
80
- attribute_map[:cpu_count] = compute_attributes['vcpus_max']
81
- attribute_map[:memory_min] = compute_attributes['memory_min']
82
- attribute_map[:memory_max] = compute_attributes['memory_max']
83
- attribute_map[:power_on] = compute_attributes['start']
84
- attribute_map
85
- end
86
-
87
- def xen_builtin_template_map(compute_resource)
88
- compute_resource.builtin_templates.map { |t| [t.name, t.name] }
89
- end
90
-
91
- def xen_custom_template_map(compute_resource)
92
- compute_resource.custom_templates.map { |t| [t.name, t.name] }
93
- end
94
-
95
- def xen_storage_pool_map(compute_resource)
96
- compute_resource.storage_pools.map { |item| [item[:display_name], item[:uuid]] }
97
- end
98
-
99
- def xen_hypervisor_map(compute_resource)
100
- compute_resource.available_hypervisors!.map do |t|
101
- [t.name + ' - ' + (
102
- t.metrics.memory_free.to_f / t.metrics.memory_total.to_f * 100
103
- ).round(2).to_s + '% free mem', t.name]
104
- end
105
- end
106
-
107
11
  def selectable_f_with_cache_invalidation(f, attr, array,
108
12
  select_options = {}, html_options = {}, input_group_options = {})
109
13
  unless html_options.key?('input_group_btn')
110
14
  html_options[:input_group_btn] = link_to_function(
111
15
  icon_text('refresh'),
112
- "refreshCache(this, #{input_group_options[:callback]})",
16
+ 'refreshCache(this)',
113
17
  :class => 'btn btn-primary',
114
18
  :title => _(input_group_options[:title]),
115
19
  :data => {
116
20
  :url => input_group_options[:url],
117
- :compute_resource_id => input_group_options[:computer_resource_id],
118
- :attribute => input_group_options[:attribute]
21
+ :compute_resource_id => input_group_options[:compute_resource_id],
22
+ :attribute => input_group_options[:attribute],
23
+ :select_attr => attr
119
24
  }
120
25
  )
121
26
  end
@@ -0,0 +1,23 @@
1
+ module FogExtensions
2
+ module Xenserver
3
+ module Host
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute :display_name
8
+ prepend FogExtensions::Xenserver::Host
9
+ end
10
+
11
+ def initialize(new_attributes = {})
12
+ super(new_attributes)
13
+ attributes[:display_name] = "#{name} - #{mem_free_gb} GB free memory"
14
+ end
15
+
16
+ def mem_free_gb
17
+ return metrics.memory_free.to_i / 1024 / 1024 / 1024 if metrics
18
+
19
+ 0
20
+ end
21
+ end
22
+ end
23
+ end
@@ -5,8 +5,13 @@ module FogExtensions
5
5
 
6
6
  include ActionView::Helpers::NumberHelper
7
7
 
8
- attr_accessor :start
9
- attr_accessor :memory_min, :memory_max, :custom_template_name, :builtin_template_name, :hypervisor_host
8
+ attr_accessor :start, :image_id, :hypervisor_host, :iso, :target_sr
9
+ attr_accessor :memory_min, :memory_max, :builtin_template
10
+ attr_writer :volumes, :interfaces
11
+
12
+ def id
13
+ uuid
14
+ end
10
15
 
11
16
  def to_s
12
17
  name
@@ -16,6 +21,13 @@ module FogExtensions
16
21
 
17
22
  def volumes_attributes=(attrs); end
18
23
 
24
+ def volumes
25
+ @volumes ||= []
26
+ disks = vbds.compact.select(&:disk?)
27
+ disks.sort! { |x, y| x.userdevice <=> y.userdevice }
28
+ (disks.map(&:vdi) + @volumes).uniq
29
+ end
30
+
19
31
  def memory
20
32
  memory_static_max.to_i
21
33
  end
@@ -45,12 +57,18 @@ module FogExtensions
45
57
  end
46
58
 
47
59
  def interfaces
48
- vifs
60
+ (vifs + @interfaces).uniq
49
61
  end
50
62
 
51
63
  def select_nic(fog_nics, nic)
52
64
  fog_nics[0]
53
65
  end
66
+
67
+ def user_data
68
+ return !other_config['default_template'] if is_a_template
69
+
70
+ false
71
+ end
54
72
  end
55
73
  end
56
74
  end
@@ -0,0 +1,45 @@
1
+ module FogExtensions
2
+ module Xenserver
3
+ module StorageRepository
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute :display_name
8
+ prepend FogExtensions::Xenserver::StorageRepository
9
+ end
10
+
11
+ def initialize(new_attributes = {})
12
+ super(new_attributes)
13
+ attributes[:display_name] = init_display_name
14
+ end
15
+
16
+ def free_space
17
+ physical_size.to_i - physical_utilisation.to_i
18
+ end
19
+
20
+ def free_space_gb
21
+ free_space.to_i / 1024 / 1024 / 1024
22
+ end
23
+
24
+ def physical_size_gb
25
+ physical_size.to_i / 1024 / 1024 / 1024
26
+ end
27
+
28
+ def physical_utilisation_gb
29
+ physical_utilisation.to_i / 1024 / 1024 / 1024
30
+ end
31
+
32
+ def init_display_name
33
+ srname = name
34
+ unless shared
35
+ pbd = pbds.first
36
+ srname = "#{name} - #{pbd.host.name}" unless pbd.nil?
37
+ end
38
+ format('%{n} (%{f}: %{f_gb} GB - %{u}: %{u_gb} GB - %{t}: %{t_gb} GB)',
39
+ n: srname, f: _('free'), f_gb: free_space_gb,
40
+ u: _('used'), u_gb: physical_utilisation_gb,
41
+ t: _('total'), t_gb: physical_size_gb)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ module FogExtensions
2
+ module Xenserver
3
+ module Vdi
4
+ extend ActiveSupport::Concern
5
+
6
+ def id
7
+ uuid
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module ForemanXen
2
+ module HostExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ def built(installed = true)
6
+ compute_resource.cleanup_configdrive(uuid) if compute_resource && compute_resource.type == 'ForemanXen::Xenserver'
7
+ super(installed)
8
+ end
9
+
10
+ def disassociate!
11
+ # Disassociated host object cannot be saved unless provision_method
12
+ # is supported by the default compute resource
13
+ self.provision_method = 'build'
14
+ super
15
+ end
16
+ end
17
+ end
@@ -2,19 +2,63 @@ module ForemanXen
2
2
  class Xenserver < ComputeResource
3
3
  validates :url, :user, :password, :presence => true
4
4
 
5
+ GB_BYTES = 1_073_741_824 # 1gb in bytes
6
+
5
7
  def provided_attributes
6
8
  super.merge(
7
- :uuid => :reference,
9
+ :uuid => :uuid,
8
10
  :mac => :mac
9
11
  )
10
12
  end
11
13
 
12
14
  def capabilities
13
- [:build]
15
+ %i[build image new_volume]
16
+ end
17
+
18
+ def host_compute_attrs(host)
19
+ super(host).merge(
20
+ name_description: host.comment,
21
+ is_a_template: false,
22
+ is_a_shapshot: false,
23
+ xenstore: host_xenstore_data(host),
24
+ network_data: host_network_data(host)
25
+ )
26
+ end
27
+
28
+ def user_data_supported
29
+ true
30
+ end
31
+
32
+ def iso_library_mountpoint
33
+ attrs[:iso_library_mountpoint]
34
+ end
35
+
36
+ def iso_library_mountpoint=(path)
37
+ mountpoint = path.to_s.end_with?('/') ? path.to_s : "#{path}/"
38
+ mountpoint = nil if path.to_s.strip.empty?
39
+ attrs[:iso_library_mountpoint] = mountpoint
40
+ end
41
+
42
+ def cleanup_configdrive(uuid)
43
+ iso_file_name = "foreman-configdrive-#{uuid}.iso"
44
+ begin
45
+ path = File.join(iso_library_mountpoint, iso_file_name)
46
+ exist = File.exist? path
47
+ FileUtils.rm(path) if exist
48
+ rescue
49
+ return true unless exist
50
+
51
+ return false
52
+ end
14
53
  end
15
54
 
55
+ # rubocop:disable Rails/DynamicFindBy
56
+ # Fog::XenServer::Compute (client) isn't an ActiveRecord model which
57
+ # supports find_by()
16
58
  def find_vm_by_uuid(uuid)
17
- client.servers.get(uuid)
59
+ return client.servers.find { |s| s.reference == uuid } if uuid.start_with? 'OpaqueRef:'
60
+
61
+ client.servers.find_by_uuid(uuid)
18
62
  rescue Fog::XenServer::RequestFailed => e
19
63
  Foreman::Logging.exception("Failed retrieving xenserver vm by uuid #{uuid}", e)
20
64
  raise(ActiveRecord::RecordNotFound) if e.message.include?('HANDLE_INVALID')
@@ -22,10 +66,12 @@ module ForemanXen
22
66
 
23
67
  raise e
24
68
  end
69
+ # rubocop:enable Rails/DynamicFindBy
25
70
 
26
71
  # we default to destroy the VM's storage as well.
27
72
  def destroy_vm(ref, args = {})
28
73
  logger.info "destroy_vm: #{ref} #{args}"
74
+ cleanup_configdrive(ref) if iso_library_mountpoint
29
75
  find_vm_by_uuid(ref).destroy
30
76
  rescue ActiveRecord::RecordNotFound
31
77
  true
@@ -60,23 +106,39 @@ module ForemanXen
60
106
  errors[:base] << e.message
61
107
  end
62
108
 
109
+ def available_images
110
+ custom_templates!
111
+ end
112
+
63
113
  def available_hypervisors
64
- read_from_cache('available_hypervisors', 'available_hypervisors!')
114
+ hypervisors.select(&:enabled)
65
115
  end
66
116
 
67
117
  def available_hypervisors!
68
- store_in_cache('available_hypervisors') do
118
+ hypervisors!.select(&:enabled)
119
+ end
120
+
121
+ def hypervisors
122
+ read_from_cache('hypervisors', 'hypervisors!')
123
+ end
124
+
125
+ def hypervisors!
126
+ store_in_cache('hypervisors') do
69
127
  hosts = client.hosts
70
128
  hosts.sort_by(&:name)
71
129
  end
72
130
  end
73
131
 
74
132
  def new_nic(attr = {})
75
- client.networks.new attr
133
+ client.vifs.new attr
76
134
  end
77
135
 
78
136
  def new_volume(attr = {})
79
- client.storage_repositories.new attr
137
+ size = attr[:virtual_size_gb].to_i * GB_BYTES
138
+ vdi = client.vdis.new virtual_size: size.to_s
139
+ vdi.type = 'user'
140
+ vdi.sr = storage_pools.find { |s| s.uuid == attr[:sr].to_s } if attr[:sr]
141
+ vdi
80
142
  end
81
143
 
82
144
  def storage_pools
@@ -85,34 +147,42 @@ module ForemanXen
85
147
 
86
148
  def storage_pools!
87
149
  store_in_cache('storage_pools') do
88
- results = []
89
- storages = client.storage_repositories.select { |sr| sr.type != 'udev' && sr.type != 'iso' }
90
- storages.each do |sr|
91
- subresults = {}
92
- found = false
93
-
94
- available_hypervisors.each do |host|
95
- next unless sr.reference == host.suspend_image_sr
96
-
97
- found = true
98
- subresults[:name] = sr.name
99
- subresults[:display_name] = sr.name + '(' + host.hostname + ')'
100
- subresults[:uuid] = sr.uuid
101
- break
102
- end
103
- unless found
104
- subresults[:name] = sr.name
105
- subresults[:display_name] = sr.name
106
- subresults[:uuid] = sr.uuid
107
- end
108
- results.push(subresults)
150
+ pools = client.storage_repositories.select do |sr|
151
+ sr.type != 'udev' && sr.type != 'iso'
109
152
  end
110
- results.sort_by! { |item| item[:display_name] }
153
+ pools.sort_by(&:display_name)
154
+ end
155
+ end
156
+
157
+ def isos
158
+ all_isos.reject do |iso|
159
+ iso.name =~ /foreman-configdrive/
160
+ end
161
+ end
162
+
163
+ def isos!
164
+ all_isos!.reject do |iso|
165
+ iso.name =~ /foreman-configdrive/
166
+ end
167
+ end
168
+
169
+ def all_isos
170
+ read_from_cache('isos', 'isos!')
171
+ end
172
+
173
+ def all_isos!
174
+ store_in_cache('isos') do
175
+ isos = iso_libraries.map(&:vdis).flatten
176
+ isos.sort_by(&:name)
111
177
  end
112
178
  end
113
179
 
180
+ def new_interface(attr = {})
181
+ client.vifs.new attr
182
+ end
183
+
114
184
  def interfaces
115
- client.interfaces
185
+ client.vifs
116
186
  rescue
117
187
  []
118
188
  end
@@ -186,158 +256,137 @@ module ForemanXen
186
256
  end
187
257
 
188
258
  def new_vm(attr = {})
189
- test_connection
190
- return unless errors.empty?
191
-
192
- opts = vm_instance_defaults.merge(attr.to_hash).symbolize_keys
193
-
194
- %i[networks volumes].each do |collection|
195
- nested_attrs = opts.delete("#{collection}_attributes".to_sym)
196
- opts[collection] = nested_attributes_for(collection, nested_attrs) if nested_attrs
259
+ attr = attr.to_hash.deep_symbolize_keys
260
+ %i[networks interfaces].each do |collection|
261
+ nested_attr = attr.delete("#{collection}_attributes".to_sym)
262
+ attr[collection] = nested_attributes_for(collection, nested_attr) if nested_attr
263
+ end
264
+ if attr[:volumes_attributes]
265
+ vol_attr = nested_attributes_for('volumes', attr[:volumes_attributes])
266
+ attr[:volumes] = vol_attr.map { |v| new_volume(v) }
197
267
  end
198
- opts.reject! { |_, v| v.nil? }
199
- client.servers.new opts
268
+ attr.reject! { |_, v| v.nil? }
269
+ super(attr)
270
+ end
271
+
272
+ def vm_attr_from_args(args)
273
+ {
274
+ name: args[:name],
275
+ name_description: args[:comment],
276
+ VCPUs_max: args[:vcpus_max],
277
+ VCPUs_at_startup: args[:vcpus_max],
278
+ memory_static_max: args[:memory_max],
279
+ memory_dynamic_max: args[:memory_max],
280
+ memory_dynamic_min: args[:memory_min],
281
+ memory_static_min: args[:memory_min]
282
+ }
200
283
  end
201
284
 
202
285
  def create_vm(args = {})
203
- custom_template_name = args[:custom_template_name].to_s
204
- builtin_template_name = args[:builtin_template_name].to_s
205
-
206
- if builtin_template_name != '' && custom_template_name != ''
207
- logger.info "custom_template_name: #{custom_template_name}"
208
- logger.info "builtin_template_name: #{builtin_template_name}"
209
- raise 'you can select at most one template type'
210
- end
286
+ args = args.deep_symbolize_keys
287
+ logger.debug('create_vm args:')
288
+ logger.debug(args)
211
289
  begin
212
- logger.info "create_vm(): custom_template_name: #{custom_template_name}"
213
- logger.info "create_vm(): builtin_template_name: #{builtin_template_name}"
214
- vm = custom_template_name != '' ? create_vm_from_custom(args) : create_vm_from_builtin(args)
215
- vm.set_attribute('name_description', 'Provisioned by Foreman')
216
- vm.set_attribute('VCPUs_max', args[:vcpus_max])
217
- vm.set_attribute('VCPUs_at_startup', args[:vcpus_max])
218
- vm.reload
219
- return vm
220
- rescue => e
221
- logger.info e
222
- logger.info e.backtrace.join("\n")
223
- return false
224
- end
225
- end
290
+ # Create VM Object
291
+ attr = vm_attr_from_args(args)
292
+ if args[:provision_method] == 'image'
293
+ image = available_images.find { |i| i.uuid == args[:image_id].to_s }
294
+ sr = storage_pools.find { |s| s.uuid == args[:target_sr].to_s }
295
+ vm = create_vm_from_image(image, attr, sr)
296
+ else
297
+ template = builtin_templates.find { |t| t.uuid == args[:builtin_template].to_s }
298
+ raise 'Template not found' unless template
226
299
 
227
- def create_vm_from_custom(args)
228
- mem_max = args[:memory_max]
229
- mem_min = args[:memory_min]
300
+ vm = create_vm_from_template(attr, template)
301
+ end
230
302
 
231
- host = get_hypervisor_host(args)
303
+ raise 'Error creating VM' unless vm
232
304
 
233
- logger.info "create_vm_from_builtin: host : #{host.name}"
305
+ # Set correct affinity
306
+ set_vm_affinity(vm, args[:hypervisor_host].to_s)
234
307
 
235
- raise 'Memory max cannot be lower than Memory min' if mem_min.to_i > mem_max.to_i
308
+ # Add NICs
309
+ vm.interfaces = args[:interfaces_attributes].map do |_, v|
310
+ create_interface(vm, v[:network])
311
+ end
236
312
 
237
- template = client.custom_templates.select { |t| t.name == args[:custom_template_name] }.first
238
- vm = template.clone args[:name]
239
- vm.affinity = host
313
+ # Attach ConfigDrive
314
+ create_and_attach_configdrive(vm, args) if args[:configdrive] == '1' && args[:provision_method] == 'image'
240
315
 
241
- vm.provision
316
+ # Attach ISO
317
+ unless args[:iso].empty?
318
+ iso_vdi = isos.find { |i| i.uuid == args[:iso] }
319
+ attach_iso(vm, iso_vdi)
320
+ end
242
321
 
243
- begin
244
- vm.vifs.first.destroy
245
- rescue
246
- nil
247
- end
322
+ # Add new Volumes
323
+ unless args[:volumes_attributes].nil?
324
+ vm.volumes = args[:volumes_attributes].map do |_, v|
325
+ create_volume(vm, v) unless v[:_delete] == '1'
326
+ end
327
+ end
248
328
 
249
- create_network(vm, args)
329
+ # Write XenStore data
330
+ xenstore_data = xenstore_set_mac(vm, args[:xenstore])
331
+ set_xenstore_data(vm, xenstore_data)
250
332
 
251
- args['xenstore']['vm-data']['ifs']['0']['mac'] = vm.vifs.first.mac
252
- xenstore_data = xenstore_hash_flatten(args['xenstore'])
333
+ # Fix Description
334
+ vm.set_attribute('name-description', args[:name_description])
253
335
 
254
- vm.set_attribute('xenstore_data', xenstore_data)
255
- if vm.memory_static_max.to_i < mem_max.to_i
256
- vm.set_attribute('memory_static_max', mem_max)
257
- vm.set_attribute('memory_dynamic_max', mem_max)
258
- vm.set_attribute('memory_dynamic_min', mem_min)
259
- vm.set_attribute('memory_static_min', mem_min)
260
- else
261
- vm.set_attribute('memory_static_min', mem_min)
262
- vm.set_attribute('memory_dynamic_min', mem_min)
263
- vm.set_attribute('memory_dynamic_max', mem_max)
264
- vm.set_attribute('memory_static_max', mem_max)
336
+ return vm
337
+ rescue => e
338
+ cleanup_configdrive(vm.uuid) if vm&.uuid
339
+ vm&.destroy
340
+ vm.volumes.each(&:destroy) if vm&.volumes
341
+ logger.info e
342
+ logger.info e.backtrace.join("\n")
343
+ raise e
265
344
  end
345
+ end
266
346
 
267
- disks = vm.vbds.select { |vbd| vbd.type == 'Disk' }
268
- disks.sort! { |a, b| a.userdevice <=> b.userdevice }
269
- i = 0
270
- disks.each do |vbd|
271
- vbd.vdi.set_attribute('name-label', "#{args[:name]}_#{i}")
272
- i += 1
347
+ def create_vm_from_template(attr, template)
348
+ vm_attr = template.attributes.dup.merge(attr)
349
+ %i[uuid domid reference allowed_operations].each do |a|
350
+ vm_attr.delete(a)
273
351
  end
352
+ vm_attr[:is_a_template] = false
353
+ vm_attr[:other_config].delete('default_template')
354
+ vm_attr[:other_config]['mac_seed'] = SecureRandom.uuid
355
+ vm = new_vm(vm_attr)
356
+ # Set any host affinity (required for saving) - correct later
357
+ vm.affinity = client.hosts.first
358
+ vm.save
274
359
  vm
275
360
  end
276
361
 
277
- def create_vm_from_builtin(args)
278
- mem_max = args[:memory_max]
279
- mem_min = args[:memory_min]
280
-
281
- host = get_hypervisor_host(args)
282
-
283
- logger.info "create_vm_from_builtin: host : #{host.name}"
284
-
285
- builtin_template_name = args[:builtin_template_name]
286
- builtin_template_name = builtin_template_name.to_s
287
-
288
- storage_repository = client.storage_repositories.find { |sr| sr.uuid == (args[:VBDs][:sr_uuid]).to_s }
289
-
290
- gb = 1_073_741_824 # 1gb in bytes
291
- size = args[:VBDs][:physical_size].to_i * gb
292
- vdi = client.vdis.create :name => "#{args[:name]}-disk1",
293
- :storage_repository => storage_repository,
294
- :description => "#{args[:name]}-disk_1",
295
- :virtual_size => size.to_s
362
+ def create_vm_from_image(image, attr, sr)
363
+ vm_ref = client.copy_server image.reference, attr[:name], sr.reference
364
+ client.provision_server vm_ref
365
+ vm = client.servers.find { |s| s.reference == vm_ref }
366
+ set_vm_profile_attributes(vm, attr)
367
+ rename_cloned_volumes(vm)
368
+ vm
369
+ end
296
370
 
297
- other_config = {}
298
- if builtin_template_name != ''
299
- template = client.builtin_templates.find { |tmp| tmp.name == args[:builtin_template_name] }
300
- other_config = template.other_config
301
- other_config.delete 'disks'
302
- other_config.delete 'default_template'
303
- other_config['mac_seed'] = SecureRandom.uuid
371
+ def set_vm_profile_attributes(vm, attr)
372
+ # Memory limits must satisfy:
373
+ # static_min <= dynamic_min <= dynamic_max <= static_max
374
+ mem = %w[memory_static_max memory_dynamic_max
375
+ memory_dynamic_min memory_static_min]
376
+ mem.reverse! if vm.memory_static_max.to_i > attr[:memory_static_max].to_i
377
+ # VCPU values must satisfy: 0 < VCPUs_at_startup <= VCPUs_max
378
+ cpu = %w[VCPUs_max VCPUs_at_startup]
379
+ cpu.reverse! if vm.vcpus_at_startup > attr[:VCPUs_at_startup]
380
+ (mem + cpu).each { |e| vm.set_attribute e, attr[e.to_sym] }
381
+ end
382
+
383
+ def rename_cloned_volumes(vm)
384
+ vm.volumes.each do |vol|
385
+ udev = vol.vbds.find { |v| v.vm.uuid == vm.uuid }.userdevice
386
+ name = "#{vm.name}-#{udev}"
387
+ vol.set_attribute 'name-label', name
388
+ vol.set_attribute 'name-description', name
304
389
  end
305
- vm = client.servers.new :name => args[:name],
306
- :affinity => host,
307
- :pv_bootloader => '',
308
- :hvm_boot_params => { :order => 'dnc' },
309
- :other_config => other_config,
310
- :memory_static_max => mem_max,
311
- :memory_static_min => mem_min,
312
- :memory_dynamic_max => mem_max,
313
- :memory_dynamic_min => mem_min
314
-
315
- vm.save :auto_start => false
316
- client.vbds.create :vm => vm, :vdi => vdi
317
-
318
- create_network(vm, args)
319
-
320
- if args[:xstools] == '1'
321
- # Add xs-tools ISO to newly created VMs
322
- dvd_vdi = client.vdis.find { |isovdi| isovdi.name == 'xs-tools.iso' || isovdi.name == 'guest-tools.iso' }
323
- vbdconnectcd = {
324
- 'vdi' => dvd_vdi,
325
- 'vm' => vm.reference,
326
- 'userdevice' => '1',
327
- 'mode' => 'RO',
328
- 'type' => 'cd',
329
- 'other_config' => {},
330
- 'qos_algorithm_type' => '',
331
- 'qos_algorithm_params' => {}
332
- }
333
- vm.vbds = client.vbds.create vbdconnectcd
334
- vm.reload
335
- end
336
-
337
- vm.provision
338
- vm.set_attribute('HVM_boot_policy', 'BIOS order')
339
- vm.reload
340
- vm
341
390
  end
342
391
 
343
392
  def console(uuid)
@@ -373,9 +422,10 @@ module ForemanXen
373
422
 
374
423
  def client
375
424
  @client ||= Fog::XenServer::Compute.new(
376
- :xenserver_url => url,
377
- :xenserver_username => user,
378
- :xenserver_password => password
425
+ xenserver_url: url,
426
+ xenserver_username: user,
427
+ xenserver_password: password,
428
+ xenserver_timeout: 1800
379
429
  )
380
430
  end
381
431
 
@@ -384,30 +434,94 @@ module ForemanXen
384
434
  @client = nil
385
435
  end
386
436
 
387
- def vm_instance_defaults
388
- super.merge({})
389
- end
390
-
391
437
  private
392
438
 
393
- def create_network(vm, args)
394
- net = client.networks.find { |n| n.name == args[:VIFs][:print] }
439
+ def create_volume(vm, attr)
440
+ vdi = new_volume attr
441
+ udev = find_free_userdevice(vm)
442
+ vdi.name = "#{vm.name}-#{udev}"
443
+ vdi.description = "#{vm.name}-#{udev}"
444
+ vdi.save
445
+ # Attach VDI to VM
446
+ client.vbds.create vm: vm, vdi: vdi, userdevice: udev.to_s, bootable: true
447
+ vdi
448
+ end
449
+
450
+ def create_interface(vm, network_uuid)
451
+ net = client.networks.find { |n| n.uuid == network_uuid }
452
+ devices = vm.vifs.map(&:device)
453
+ device = 0
454
+ device += 1 while devices.include?(device.to_s)
395
455
  net_config = {
396
456
  'mac_autogenerated' => 'True',
397
457
  'vm' => vm.reference,
398
458
  'network' => net.reference,
399
459
  'mac' => '',
400
- 'device' => '0',
460
+ 'device' => device.to_s,
401
461
  'mtu' => '0',
402
462
  'other_config' => {},
403
463
  'qos_algorithm_type' => 'ratelimit',
404
464
  'qos_algorithm_params' => {}
405
465
  }
406
- vm.vifs = client.vifs.create net_config
407
- vm.reload
466
+ client.vifs.create net_config
467
+ end
468
+
469
+ def attach_iso(vm, iso_vdi)
470
+ cd_drive = client.vbds.find { |v| v.vm == vm && v.type == 'CD' }
471
+ if cd_drive&.empty
472
+ client.insert_vbd cd_drive.reference, iso_vdi.reference
473
+ else
474
+ # Windows VMs expect the CDROM drive on userdevice 3
475
+ vbds = client.vbds.select { |v| v.vm == vm }
476
+ udev = vbds.map(&:userdevice).include?('3') ? find_free_userdevice(vm) : '3'
477
+ vbd = {
478
+ 'vdi' => iso_vdi,
479
+ 'vm' => vm,
480
+ 'userdevice' => udev.to_s,
481
+ 'mode' => 'RO',
482
+ 'type' => 'CD',
483
+ 'other_config' => {},
484
+ 'qos_algorithm_type' => '',
485
+ 'qos_algorithm_params' => {}
486
+ }
487
+ client.vbds.create vbd
488
+ end
489
+ true
490
+ end
491
+
492
+ def find_free_userdevice(vm)
493
+ # Find next free userdevice id for vbd
494
+ # vm.vbds is not current, vm.reload not working.
495
+ vbds = client.vbds.select { |v| v.vm == vm }
496
+ userdevices = vbds.map(&:userdevice)
497
+ udev = 0
498
+ udev += 1 while userdevices.include?(udev.to_s)
499
+ udev
500
+ end
501
+
502
+ def xenstore_set_mac(vm, xenstore_data)
503
+ xenstore_data[:'vm-data'][:ifs][:'0'][:mac] = vm.interfaces.first.mac
504
+ xenstore_data
505
+ end
506
+
507
+ def set_xenstore_data(vm, xenstore_data)
508
+ xenstore_data = xenstore_hash_flatten(xenstore_data)
509
+ vm.set_attribute('xenstore_data', xenstore_data)
408
510
  end
409
511
 
410
- def xenstore_hash_flatten(nested_hash, key = nil, keychain = nil, out_hash = {})
512
+ def host_xenstore_data(host)
513
+ p_if = host.primary_interface
514
+ subnet = p_if.subnet || p_if.subnet6
515
+ { 'vm-data' => { 'ifs' => { '0' =>
516
+ { 'ip' => p_if.ip.empty? ? p_if.ip6 : p_if.ip,
517
+ 'gateway' => subnet.nil? ? '' : subnet.gateway,
518
+ 'netmask' => subnet.nil? ? '' : subnet.mask } } },
519
+ 'nameserver1' => subnet.nil? ? '' : subnet.dns_primary,
520
+ 'nameserver2' => subnet.nil? ? '' : subnet.dns_secondary,
521
+ 'environment' => host.environment.to_s }
522
+ end
523
+
524
+ def xenstore_hash_flatten(nested_hash, _key = nil, keychain = nil, out_hash = {})
411
525
  nested_hash.each do |k, v|
412
526
  if v.is_a? Hash
413
527
  xenstore_hash_flatten(v, k, "#{keychain}#{k}/", out_hash)
@@ -418,6 +532,115 @@ module ForemanXen
418
532
  out_hash
419
533
  end
420
534
 
535
+ # rubocop:disable Rails/DynamicFindBy
536
+ # Fog::XenServer::Compute (client) isn't an ActiveRecord model which
537
+ # supports find_by()
538
+ def set_vm_affinity(vm, hypervisor)
539
+ if hypervisor.empty?
540
+ vm.set_attribute('affinity', '')
541
+ else
542
+ vm.set_attribute('affinity', client.hosts.find_by_uuid(hypervisor))
543
+ end
544
+ end
545
+ # rubocop:enable Rails/DynamicFindBy
546
+
547
+ def create_and_attach_configdrive(vm, attr)
548
+ network_data = add_mac_to_network_data(attr[:network_data], vm)
549
+ iso_name = generate_configdrive(vm.uuid,
550
+ vm_meta_data(vm).to_json,
551
+ network_data.deep_stringify_keys.to_json,
552
+ attr[:user_data],
553
+ iso_library_mountpoint)
554
+ rescan_iso_libraries
555
+ iso_vdi = all_isos!.find { |iso| iso.name == iso_name }
556
+ raise 'Unable to locate metadata iso on iso libraries' unless iso_vdi
557
+
558
+ attach_iso(vm, iso_vdi)
559
+ end
560
+
561
+ def vm_meta_data(vm)
562
+ { 'uuid' => vm.uuid, 'hostname' => vm.name }
563
+ end
564
+
565
+ # openstack configdive network_data format spec:
566
+ # https://github.com/openstack/nova-specs/blob/master/specs/liberty/implemented/metadata-service-network-info.rst
567
+ def host_network_data(host)
568
+ p_if = host.primary_interface
569
+ network_data = { links: [], networks: [], services: [] }
570
+ network = { id: 'network0', routes: [] }
571
+ if p_if.subnet
572
+ sn = p_if.subnet
573
+ network[:ip_address] = p_if.ip unless p_if.ip.empty?
574
+ network[:type] = sn.boot_mode == 'DHCP' ? 'ipv4_dhcp' : 'ipv4'
575
+ end
576
+ if p_if.subnet6
577
+ sn = p_if.subnet6
578
+ network[:ip_address] = p_if.ip6 unless p_if.ip6.empty?
579
+ network[:type] = sn.boot_mode == 'DHCP' ? 'ipv6_dhcp' : 'ipv6'
580
+ end
581
+ link = { type: 'phy' }
582
+ link[:id] = p_if.name.empty? ? 'eth0' : p_if.identifier
583
+ link[:name] = link[:id]
584
+ link[:mtu] = sn.mtu
585
+ link[:ethernet_mac_address] = p_if.mac unless p_if.mac.empty?
586
+ network_data[:links] << link
587
+ network[:netmask] = sn.mask unless sn.mask.empty?
588
+ network[:link] = link[:id]
589
+ route = { network: '0.0.0.0', netmask: '0.0.0.0' }
590
+ route[:gateway] = sn.gateway unless sn.gateway.empty?
591
+ network[:routes] << route
592
+ network_data[:networks] << network
593
+ unless sn.dns_primary.empty?
594
+ dns1 = { type: 'dns', address: sn.dns_primary }
595
+ network_data[:services] << dns1
596
+ end
597
+ unless sn.dns_secondary.empty?
598
+ dns2 = { type: 'dns', address: sn.dns_secondary }
599
+ network_data[:services] << dns2
600
+ end
601
+ network_data
602
+ end
603
+
604
+ def add_mac_to_network_data(network_data, vm)
605
+ network_data[:links][0][:ethernet_mac_address] = vm.interfaces.first.mac unless network_data[:links][0][:ethernet_mac_address]
606
+ network_data
607
+ end
608
+
609
+ def generate_configdrive(vm_uuid, meta_data, network_data, user_data, dst_dir)
610
+ Dir.mktmpdir('foreman-configdrive') do |wd|
611
+ iso_file_name = "foreman-configdrive-#{vm_uuid}.iso"
612
+ iso_file_path = File.join(wd, iso_file_name)
613
+ config_dir = FileUtils.mkdir_p(File.join(wd, 'openstack/latest')).first
614
+ meta_data_path = File.join(config_dir, 'meta_data.json')
615
+ user_data_path = File.join(config_dir, 'user_data')
616
+ network_data_path = File.join(config_dir, 'network_data.json')
617
+ File.write(meta_data_path, meta_data)
618
+ File.write(user_data_path, user_data)
619
+ File.write(network_data_path, network_data)
620
+
621
+ cmd = ['/usr/bin/genisoimage', '-output', iso_file_path,
622
+ '-volid', 'config-2', '-joliet', '-rock', wd]
623
+
624
+ raise ::Foreman::Exception, N_('ISO build failed, is the genisoimage package installed?') unless system(*cmd)
625
+
626
+ FileUtils.cp(iso_file_path, dst_dir)
627
+
628
+ return iso_file_name
629
+ end
630
+ end
631
+
632
+ def rescan_iso_libraries
633
+ iso_libraries.each do |sr|
634
+ client.scan_sr sr.reference
635
+ end
636
+ end
637
+
638
+ def iso_libraries
639
+ client.storage_repositories.select do |sr|
640
+ sr.type == 'iso'
641
+ end
642
+ end
643
+
421
644
  def get_templates(templates)
422
645
  tmps = templates.reject(&:is_a_snapshot)
423
646
  tmps.sort_by(&:name)