foreman_xen 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # Foreman XEN Plugin
2
+
3
+ This plugin enables provisioning and managing XEN Server in Foreman.
4
+
5
+ ## Installation
6
+
7
+ Please see the Foreman manual for appropriate instructions:
8
+
9
+ * [Foreman: How to Install a Plugin](http://theforeman.org/manuals/latest/index.html#6.1InstallaPlugin)
10
+
11
+ The gem name is "foreman_xen".
12
+
13
+ ## Compatibility
14
+
15
+ | Foreman Version | Plugin Version |
16
+ | ---------------:| --------------:|
17
+ | >= 1.5 | 0.0.1 |
18
+
19
+ ## Latest code
20
+
21
+ You can get the develop branch of the plugin by specifying your Gemfile in this way:
22
+
23
+ gem 'foreman_xen', :git => "https://github.com/ohadlevy/foreman_xen.git"
24
+
25
+ # Copyright
26
+
27
+ Copyright (c) 2014 ooVoo LLC
28
+
29
+ This program is free software: you can redistribute it and/or modify
30
+ it under the terms of the GNU General Public License as published by
31
+ the Free Software Foundation, either version 3 of the License, or
32
+ (at your option) any later version.
33
+
34
+ This program is distributed in the hope that it will be useful,
35
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
36
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37
+ GNU General Public License for more details.
38
+
39
+ You should have received a copy of the GNU General Public License
40
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
@@ -0,0 +1,51 @@
1
+ module FogExtensions
2
+ module Xenserver
3
+ module Server
4
+
5
+ def to_s
6
+ name
7
+ end
8
+
9
+ def nics_attributes=(attrs); end
10
+
11
+ def volumes_attributes=(attrs); end
12
+
13
+ def memory_min
14
+ memory_static_min.to_i
15
+ end
16
+
17
+ def memory_max
18
+ memory_static_max.to_i
19
+ end
20
+
21
+ def memory
22
+ memory_static_max.to_i
23
+ end
24
+
25
+ def reset
26
+ reboot
27
+ end
28
+
29
+ def ready?
30
+ running?
31
+ end
32
+
33
+ def mac
34
+ vifs.first.mac
35
+ end
36
+
37
+ def state
38
+ power_state
39
+ end
40
+
41
+ def custom_template_name
42
+ template_name
43
+ end
44
+
45
+ def builtin_template_name
46
+ template_name
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,288 @@
1
+ module ForemanXen
2
+ class Xenserver < ComputeResource
3
+ validates_presence_of :url, :user, :password
4
+
5
+ def provided_attributes
6
+ super.merge(
7
+ { :uuid => :reference,
8
+ :mac => :mac
9
+ })
10
+ end
11
+
12
+ def capabilities
13
+ [:build]
14
+ end
15
+
16
+ def find_vm_by_uuid(ref)
17
+ client.servers.get(ref)
18
+ rescue Fog::XenServer::RequestFailed => e
19
+ raise(ActiveRecord::RecordNotFound) if e.message.include?('HANDLE_INVALID')
20
+ raise(ActiveRecord::RecordNotFound) if e.message.include?('VM.get_record: ["SESSION_INVALID"')
21
+ raise e
22
+ end
23
+
24
+ # we default to destroy the VM's storage as well.
25
+ def destroy_vm(ref, args = {})
26
+ find_vm_by_uuid(ref).destroy
27
+ rescue ActiveRecord::RecordNotFound
28
+ true
29
+ end
30
+
31
+ def self.model_name
32
+ ComputeResource.model_name
33
+ end
34
+
35
+ def max_cpu_count
36
+ ## 16 is a max number of cpus per vm according to XenServer doc
37
+ [hypervisor.host_cpus.size, 16].min
38
+ end
39
+
40
+ def max_memory
41
+ xenServerMaxDoc = 128*1024*1024*1024
42
+ [hypervisor.metrics.memory_total.to_i, xenServerMaxDoc].min
43
+ rescue => e
44
+ logger.debug "unable to figure out free memory, guessing instead due to:#{e}"
45
+ 16*1024*1024*1024
46
+ end
47
+
48
+ def test_connection(options = {})
49
+ super
50
+ errors[:url].empty? and hypervisor
51
+ rescue => e
52
+ disconnect rescue nil
53
+ errors[:base] << e.message
54
+ end
55
+
56
+ def new_nic(attr={})
57
+ client.networks.new attr
58
+ end
59
+
60
+ def new_volume(attr={})
61
+ client.storage_repositories.new attr
62
+ end
63
+
64
+ def storage_pools
65
+ client.storage_repositories rescue []
66
+ end
67
+
68
+ def interfaces
69
+ client.interfaces rescue []
70
+ end
71
+
72
+ def networks
73
+ client.networks rescue []
74
+ end
75
+
76
+ def templates
77
+ client.servers.templates rescue []
78
+ end
79
+
80
+ def custom_templates
81
+ tmps = client.servers.custom_templates.select { |t| !t.is_a_snapshot } rescue []
82
+ tmps.sort { |a, b| a.name <=> b.name }
83
+ end
84
+
85
+ def builtin_templates
86
+ tmps = client.servers.builtin_templates.select { |t| !t.is_a_snapshot } rescue []
87
+ tmps.sort { |a, b| a.name <=> b.name }
88
+ end
89
+
90
+ def new_vm(attr={})
91
+
92
+ test_connection
93
+ return unless errors.empty?
94
+ opts = vm_instance_defaults.merge(attr.to_hash).symbolize_keys
95
+
96
+ [:networks, :volumes].each do |collection|
97
+ nested_attrs = opts.delete("#{collection}_attributes".to_sym)
98
+ opts[collection] = nested_attributes_for(collection, nested_attrs) if nested_attrs
99
+ end
100
+ opts.reject! { |_, v| v.nil? }
101
+ client.servers.new opts
102
+ end
103
+
104
+ def create_vm(args = {})
105
+ custom_template_name = args[:custom_template_name]
106
+ builtin_template_name = args[:builtin_template_name]
107
+ raise 'you can select at most one template type' if builtin_template_name != '' and custom_template_name != ''
108
+ begin
109
+ vm = nil
110
+ if custom_template_name != ''
111
+ vm = create_vm_from_custom args
112
+ else
113
+ vm = create_vm_from_builtin args
114
+ end
115
+ vm.set_attribute('name_description', 'Provisioned by Foreman')
116
+ cpus = args[:vcpus_max]
117
+ if vm.vcpus_max.to_i < cpus.to_i
118
+ vm.set_attribute('VCPUs_max', cpus)
119
+ vm.set_attribute('VCPUs_at_startup', cpus)
120
+ else
121
+ vm.set_attribute('VCPUs_at_startup', cpus)
122
+ vm.set_attribute('VCPUs_max', cpus)
123
+ end
124
+ vm.refresh
125
+ return vm
126
+ rescue => e
127
+ logger.info e
128
+ logger.info e.backtrace.join("\n")
129
+ return false
130
+ end
131
+ end
132
+
133
+ def create_vm_from_custom(args)
134
+ mem_max = args[:memory_max]
135
+ mem_min = args[:memory_min]
136
+
137
+ raise 'Memory max cannot be lower than Memory min' if mem_min.to_i > mem_max.to_i
138
+ vm = client.servers.new :name => args[:name],
139
+ :template_name => args[:custom_template_name]
140
+
141
+ vm.save :auto_start => false
142
+
143
+ vm.provision
144
+
145
+ vm.vifs.first.destroy rescue nil
146
+
147
+ create_network(vm, args)
148
+
149
+ args['xenstore']['vm-data']['ifs']['0']['mac'] = vm.vifs.first.mac
150
+ xenstore_data = xenstore_hash_flatten(args['xenstore'])
151
+
152
+ vm.set_attribute('xenstore_data', xenstore_data)
153
+ if vm.memory_static_max.to_i < mem_max.to_i
154
+ vm.set_attribute('memory_static_max', mem_max)
155
+ vm.set_attribute('memory_dynamic_max', mem_max)
156
+ vm.set_attribute('memory_dynamic_min', mem_min)
157
+ vm.set_attribute('memory_static_min', mem_min)
158
+ else
159
+ vm.set_attribute('memory_static_min', mem_min)
160
+ vm.set_attribute('memory_dynamic_min', mem_min)
161
+ vm.set_attribute('memory_dynamic_max', mem_max)
162
+ vm.set_attribute('memory_static_max', mem_max)
163
+ end
164
+
165
+ disks = vm.vbds.select { |vbd| vbd.type == 'Disk' }
166
+ disks.sort! { |a, b| a.userdevice <=> b.userdevice }
167
+ i = 0
168
+ disks.each do |vbd|
169
+ vbd.vdi.set_attribute('name-label', "#{args[:name]}_#{i}")
170
+ i+=1
171
+ end
172
+ vm
173
+ end
174
+
175
+ def create_vm_from_builtin(args)
176
+
177
+ host = client.hosts.first
178
+ storage_repository = client.storage_repositories.find { |sr| sr.name == "#{args[:VBDs][:print]}" }
179
+
180
+ gb = 1073741824 #1gb in bytes
181
+ size = args[:VBDs][:physical_size].to_i * gb
182
+ vdi = client.vdis.create :name => "#{args[:name]}-disk1",
183
+ :storage_repository => storage_repository,
184
+ :description => "#{args[:name]}-disk_1",
185
+ :virtual_size => size.to_s
186
+
187
+ mem_max = args[:memory_max]
188
+ mem_min = args[:memory_min]
189
+ other_config = {}
190
+ if args[:builtin_template_name] != ''
191
+ template = client.servers.builtin_templates.find { |tmp| tmp.name == args[:builtin_template_name] }
192
+ other_config = template.other_config
193
+ other_config.delete 'disks'
194
+ other_config.delete 'default_template'
195
+ end
196
+ vm = client.servers.new :name => args[:name],
197
+ :affinity => host,
198
+ :pv_bootloader => '',
199
+ :hvm_boot_params => { :order => 'dn' },
200
+ :other_config => other_config,
201
+ :memory_static_max => mem_max,
202
+ :memory_static_min => mem_min,
203
+ :memory_dynamic_max => mem_max,
204
+ :memory_dynamic_min => mem_min
205
+
206
+ vm.save :auto_start => false
207
+ client.vbds.create :server => vm, :vdi => vdi
208
+
209
+ create_network(vm, args)
210
+
211
+ vm.provision
212
+ vm.set_attribute('HVM_boot_policy', 'BIOS order')
213
+ vm.refresh
214
+ vm
215
+ end
216
+
217
+ def console(uuid)
218
+ vm = find_vm_by_uuid(uuid)
219
+ raise 'VM is not running!' unless vm.ready?
220
+
221
+
222
+ console = vm.service.consoles.find { |c| c.__vm == vm.reference && c.protocol == 'rfb' }
223
+ raise "No console fore vm #{vm.name}" if console == nil
224
+
225
+ session_ref = (vm.service.instance_variable_get :@connection).instance_variable_get :@credentials
226
+ fullURL = "#{console.location}&session_id=#{session_ref}"
227
+ tunnel = VNCTunnel.new fullURL
228
+ tunnel.start
229
+ logger.info 'VNCTunnel started'
230
+ WsProxy.start(:host => tunnel.host, :host_port => tunnel.port, :password => '').merge(:type => 'vnc', :name => vm.name)
231
+
232
+ rescue Error => e
233
+ logger.warn e
234
+ raise e
235
+ end
236
+
237
+ def hypervisor
238
+ client.hosts.first
239
+ end
240
+
241
+ protected
242
+
243
+ def client
244
+ @client ||= ::Fog::Compute.new({ :provider => 'XenServer', :xenserver_url => url, :xenserver_username => user, :xenserver_password => password })
245
+ end
246
+
247
+ def disconnect
248
+ client.terminate if @client
249
+ @client = nil
250
+ end
251
+
252
+ def vm_instance_defaults
253
+ super.merge({})
254
+ end
255
+
256
+
257
+ private
258
+
259
+ def create_network(vm, args)
260
+ net = client.networks.find { |n| n.name == args[:VIFs][:print] }
261
+ net_config = {
262
+ 'MAC_autogenerated' => 'True',
263
+ 'VM' => vm.reference,
264
+ 'network' => net.reference,
265
+ 'MAC' => '',
266
+ 'device' => '0',
267
+ 'MTU' => '0',
268
+ 'other_config' => {},
269
+ 'qos_algorithm_type' => 'ratelimit',
270
+ 'qos_algorithm_params' => {}
271
+ }
272
+ client.create_vif_custom net_config
273
+ vm.reload
274
+ end
275
+
276
+ def xenstore_hash_flatten(nested_hash, key=nil, keychain=nil, out_hash={})
277
+ nested_hash.each do |k, v|
278
+ if v.is_a? Hash
279
+ xenstore_hash_flatten(v, k, "#{keychain}#{k}/", out_hash)
280
+ else
281
+ out_hash["#{keychain}#{k}"] = v
282
+ end
283
+ end
284
+ out_hash
285
+ @key = key
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,11 @@
1
+ <%= text_f f, :url, :class => "input-xlarge", :help_block => _("e.g. x.x.x.x") %>
2
+ <%= text_f f, :user %>
3
+ <%= password_f f, :password %>
4
+
5
+ <% hypervisor = f.object.hypervisor.uuid rescue nil%>
6
+ <% if hypervisor -%>
7
+ <%= f.hidden_field :uuid, :value => hypervisor %>
8
+ <% end -%>
9
+ <%= link_to_function _("Test Connection"), "testConnection(this)", :class => "btn + #{hypervisor.nil? ? "" : "btn-success"}", :'data-url' => test_connection_compute_resources_path %>
10
+
11
+ <%= image_tag('/assets/spinner.gif', :id => 'test_connection_indicator', :class => 'hide') %>
@@ -0,0 +1,4 @@
1
+ <tr>
2
+ <td><%= _("URL") %></td>
3
+ <td><%= @compute_resource.url %></td>
4
+ </tr>
@@ -0,0 +1,16 @@
1
+ <div class="fields">
2
+ <%
3
+ nat = compute_resource.networks
4
+ selected = ""
5
+ if params && params['host'] && params['host']['compute_attributes']
6
+ selected = params['host']['compute_attributes']['VIFs']['print']
7
+ end
8
+ -%>
9
+
10
+ <div id='nat' class=''>
11
+ <%= selectable_f f, :print, nat.map(&:name),
12
+ { :include_blank => nat.any? ? false : _("No networks"),
13
+ :selected => selected },
14
+ { :class => "span2", :label => _("Network") } %>
15
+ </div>
16
+ </div>
@@ -0,0 +1,30 @@
1
+ <div class="fields">
2
+ <%
3
+ selected_item_c = ''
4
+ selected_item_b = ''
5
+ if params && params['host'] && params['host']['compute_attributes']
6
+ selected_item_c = params['host']['compute_attributes']['custom_template_name']
7
+ selected_item_b = params['host']['compute_attributes']['builtin_template_name']
8
+ end
9
+ %>
10
+
11
+ <div id='templates' class=''>
12
+ <label class="control-label" for="host_compute_attributes_custom_template_name">Custom template</label>
13
+
14
+ <div class="controls">
15
+ <select class="input-xlarge" id="host_compute_attributes_custom_template_name" name="host[compute_attributes][custom_template_name]">
16
+ <%= options_for_select([[_("No template"), ""]] + compute_resource.custom_templates.map { |t| [t.name, t.name] }, selected_item_c) %>
17
+ </select>
18
+ </div>
19
+
20
+ <label class="control-label" for="host_compute_attributes_builtin_template_name">Built-in template</label>
21
+
22
+ <div class="controls">
23
+ <select class="input-xlarge" id="host_compute_attributes_builtin_template_name" name="host[compute_attributes][builtin_template_name]">
24
+ <%= options_for_select([[_("No template"), ""]] + compute_resource.builtin_templates.map { |t| [t.name, t.name] }, selected_item_b) %>
25
+ </select>
26
+ </div>
27
+
28
+ </div>
29
+
30
+ </div>