foreman_xen 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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>