Class: Chef::Knife::XenserverVmCreate

Inherits:
Chef::Knife show all
Includes:
XenserverBase
Defined in:
lib/chef/knife/xenserver_vm_create.rb

Instance Method Summary collapse

Methods included from XenserverBase

#bytes_to_megabytes, #connection, included, #locate_config_value

Instance Method Details

#bootstrap_for_node(vm) ⇒ Object



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/chef/knife/xenserver_vm_create.rb', line 324

def bootstrap_for_node(vm)
  bootstrap = Chef::Knife::Bootstrap.new
  bootstrap.name_args = [@ssh_ip]
  bootstrap.config[:run_list] = config[:run_list]
  bootstrap.config[:ssh_user] = config[:ssh_user] 
  bootstrap.config[:identity_file] = config[:identity_file]
  bootstrap.config[:chef_node_name] = config[:chef_node_name] || vm.name
  bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
  bootstrap.config[:distro] = locate_config_value(:distro)
  # bootstrap will run as root...sudo (by default) also messes up Ohai on CentOS boxes
  bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
  bootstrap.config[:template_file] = locate_config_value(:template_file)
  bootstrap.config[:environment] = config[:environment]
  bootstrap.config[:host_key_verify] = config[:host_key_verify]
  bootstrap.config[:ssh_password] = config[:ssh_password]
  bootstrap
end

#create_extra_vdis(vm) ⇒ Object



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/chef/knife/xenserver_vm_create.rb', line 342

def create_extra_vdis(vm)
  # Return if no extra VDIs were specified
  return unless config[:extra_vdis]
  count = 0

  vdis = config[:extra_vdis].strip.chomp.split(',')
  vdis.each do |vdi|
    count += 1
    # if options is "Storage Repository":size
    if vdi =~ /.*:.*/
      sr, size = vdi.split(':')
    else #only size was specified
      sr = nil
      size = vdi
    end
    unless size =~ /^\d+$/
      raise "Invalid VDI size. Not numeric."
    end
    # size in bytes
    bsize = size.to_i * 1024 * 1024

    # If the storage repository has been omitted,
    # use the default SR from the first pool, othewise
    # find the SR required
    if sr.nil?
      sr = connection.pools.first.default_sr 
      ui.warn "No storage repository defined for extra VDI #{count}."
      ui.warn "Using default SR from Pool: #{sr.name}"
    else
      found = connection.storage_repositories.find { |s| s.name == sr }
      unless found
        raise "Storage Repository #{sr} not available"
      end
      sr = found
    end
    
    # Name of the VDI
    name = "#{config[:vm_name]}-extra-vdi-#{count}"

    puts "Creating extra VDI (#{size} MB, #{name}, #{sr.name || 'Default SR'})"
    vdi = connection.vdis.create :name => name,
                                 :storage_repository => sr,
                                 :description => name,
                                 :virtual_size => bsize.to_s

    begin
      # Attach the VBD
      connection.vbds.create :server => vm, 
                             :vdi => vdi,
                             :userdevice => count.to_s,
                             :bootable => false
    rescue => e
      ui.error "Could not attach the VBD to the server"
      # Try to destroy the VDI
      vdi.destroy rescue nil
      raise e
    end
  end
end

#create_nics(networks, macs, vm) ⇒ Object



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/chef/knife/xenserver_vm_create.rb', line 402

def create_nics(networks, macs, vm)
  net_arr = networks.split(/,/).map { |x| { :network => x } }
  nics = []
  if macs
    mac_arr = macs.split(/,/)
    nics = net_arr.each_index { |x| net_arr[x][:mac_address] = mac_arr[x] if mac_arr[x] and !mac_arr[x].empty? }
  else
    nics = net_arr
  end
  networks = connection.networks
  highest_device = -1
  vm.vifs.each { |vif| highest_device = vif.device.to_i if vif.device.to_i > highest_device }
  nic_count = 0
  nics.each do |n|
    net = networks.find { |net| net.name == n[:network] }
    if net.nil?
      raise "Network #{n[:network]} not found"
    end
    nic_count += 1
    c = {
     'MAC_autogenerated' => n[:mac_address].nil? ? 'True':'False',
     'VM' => vm.reference,
     'network' => net.reference,
     'MAC' => n[:mac_address] || '',
     'device' => (highest_device + nic_count).to_s,
     'MTU' => '0',
     'other_config' => {},
     'qos_algorithm_type' => 'ratelimit',
     'qos_algorithm_params' => {}
    }
    connection.create_vif_custom c
  end
end

#runObject



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/chef/knife/xenserver_vm_create.rb', line 184

def run
  $stdout.sync = true

  unless config[:vm_template]
    raise "You have not provided a valid template name. (--vm-template)"
  end
  
  vm_name = config[:vm_name]
  if not vm_name
    raise "Invalid Virtual Machine name (--vm-name)"
  end

  unless config[:vm_gateway]
    if config[:vm_ip]
      last_octet = Chef::Config[:knife][:xenserver_default_vm_gateway_last_octet] || 1
      config[:vm_gateway] = config[:vm_ip].gsub(/\.\d+$/, ".#{last_octet}")
    end
  end

  config[:vm_netmask] ||= Chef::Config[:knife][:xenserver_default_vm_netmask]
  config[:vm_dns] ||= Chef::Config[:knife][:xenserver_default_vm_dns]
  config[:vm_domain] ||= Chef::Config[:knife][:xenserver_default_vm_domain]

  template = connection.servers.templates.find do |s| 
    (s.name == config[:vm_template]) or (s.uuid == config[:vm_template])
  end

  if template.nil?
    raise "Template #{config[:vm_template]} not found."
  end

  puts "Creating VM #{config[:vm_name].yellow}..."
  puts "Using template #{template.name.yellow} [uuid: #{template.uuid}]..."
  
  vm = connection.servers.new :name => config[:vm_name],
                              :template_name => config[:vm_template]
  vm.save :auto_start => false
  # Useful for the global exception handler
  @created_vm = vm

  if not config[:keep_template_networks]
    vm.vifs.each do |vif|
      vif.destroy
    end 
    vm.reload
  end
  if config[:vm_networks]
    create_nics(config[:vm_networks], config[:mac_addresses], vm)
  end
  mem = (config[:vm_memory].to_i * 1024 * 1024).to_s
  vm.set_attribute 'memory_limits', mem, mem, mem, mem
  vm.set_attribute 'VCPUs_max', config[:vm_cpus]
  vm.set_attribute 'VCPUs_at_startup', config[:vm_cpus]

  # network configuration through xenstore
  attrs = {}
  (attrs['vm-data/ip'] = config[:vm_ip]) if config[:vm_ip]
  (attrs['vm-data/gw'] = config[:vm_gateway]) if config[:vm_gateway]
  (attrs['vm-data/nm'] = config[:vm_netmask]) if config[:vm_netmask]
  (attrs['vm-data/ns'] = config[:vm_dns]) if config[:vm_dns]
  (attrs['vm-data/dm'] = config[:vm_domain]) if config[:vm_domain]
  if !attrs.empty?
    puts "Adding attributes to xenstore..."
    vm.set_attribute 'xenstore_data', attrs
  end

  if config[:vm_tags]
    vm.set_attribute 'tags', config[:vm_tags].split(',')
  end

  vm.provision
  # Create additional VDIs (virtual disks)
  create_extra_vdis(vm)
  vm.start
  vm.reload

  puts "#{ui.color("VM Name", :cyan)}: #{vm.name}"
  puts "#{ui.color("VM Memory", :cyan)}: #{bytes_to_megabytes(vm.memory_static_max)} MB"

  if !config[:skip_bootstrap]
    # wait for it to be ready to do stuff
    print "\n#{ui.color("Waiting server... ", :magenta)}"
    timeout = 180
    found = connection.servers.all.find { |v| v.name == vm.name }
    servers = connection.servers
    if config[:vm_ip]
      vm.refresh
      print "\nTrying to #{'SSH'.yellow} to #{config[:vm_ip].yellow}... "
      print(".") until tcp_test_ssh(config[:vm_ip]) do
        sleep @initial_sleep_delay ||= 10; puts(" done")
        @ssh_ip = config[:vm_ip]
      end
    else
      loop do
        begin
          vm.refresh
          if not vm.guest_metrics.nil? and not vm.guest_metrics.networks.empty?
            networks = []
            vm.guest_metrics.networks.each do |k,v|
              networks << v
            end
            networks = networks.join(",")
            puts
            puts "\n#{ui.color("Server IPs:", :cyan)} #{networks}"
            break
          end
        rescue Fog::Errors::Error
          print "\r#{ui.color('Waiting a valid IP', :magenta)}..." + "." * (100 - timeout)
        end
        sleep 1
        timeout -= 1
        if timeout == 0
          puts
          raise "Timeout trying to reach the VM. Couldn't find the IP address."
        end
      end
      print "\n#{ui.color("Waiting for sshd... ", :magenta)}"
      vm.guest_metrics.networks.each do |k,v|
        print "\nTrying to #{'SSH'.yellow} to #{v.yellow}... "
        print(".") until tcp_test_ssh(v) do
          sleep @initial_sleep_delay ||= 10; puts(" done")
          @ssh_ip = v
        end
        break if @ssh_ip
      end
    end

    bootstrap_for_node(vm).run 
    puts "\n"
    puts "#{ui.color("Name", :cyan)}: #{vm.name}"
    puts "#{ui.color("IP Address", :cyan)}: #{@ssh_ip}"
    puts "#{ui.color("Environment", :cyan)}: #{config[:environment] || '_default'}"
    puts "#{ui.color("Run List", :cyan)}: #{config[:run_list].join(', ')}"
    puts "#{ui.color("Done!", :green)}"
  else
    ui.warn "Skipping bootstrapping as requested."
  end

end

#tcp_test_ssh(hostname) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/chef/knife/xenserver_vm_create.rb', line 165

def tcp_test_ssh(hostname)
  tcp_socket = TCPSocket.new(hostname, 22)
  readable = IO.select([tcp_socket], nil, nil, 5)
  if readable
    Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
    yield
    true
  else
    false
  end
rescue Errno::ETIMEDOUT, Errno::EPERM
  false
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH
  sleep 2
  false
ensure
  tcp_socket && tcp_socket.close
end