Class: KnifeCloudstack::CsServerCreate

Inherits:
Chef::Knife show all
Includes:
Chef::Knife::KnifeCloudstackBase, Chef::Knife::WinrmBase
Defined in:
lib/chef/knife/cs_server_create.rb

Constant Summary collapse

BOOTSTRAP_DELAY =

Seconds to delay between detecting ssh and initiating the bootstrap

20
WINRM_BOOTSTRAP_DELAY =

The machine will reboot once so we need to handle that

200
SSH_POLL_INTERVAL =

Seconds to wait between ssh pings

10

Instance Method Summary collapse

Methods included from Chef::Knife::KnifeCloudstackBase

included

Instance Method Details

#bootstrap(server, public_ip) ⇒ Object



570
571
572
573
574
575
576
577
578
# File 'lib/chef/knife/cs_server_create.rb', line 570

def bootstrap(server, public_ip)
  if @windows_image
    Chef::Log.debug("Windows Bootstrapping")
    bootstrap_for_windows_node(server, public_ip)
  else
    Chef::Log.debug("Linux Bootstrapping")
    bootstrap_for_node(server, public_ip)
  end
end

#bootstrap_common_params(bootstrap) ⇒ Object



614
615
616
617
618
619
620
621
622
623
624
625
# File 'lib/chef/knife/cs_server_create.rb', line 614

def bootstrap_common_params(bootstrap)
  bootstrap.config[:run_list] = config[:run_list]
  bootstrap.config[:prerelease] = config[:prerelease]
  bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
  bootstrap.config[:distro] = locate_config_value(:distro)
  bootstrap.config[:template_file] = locate_config_value(:template_file)
  bootstrap.config[:first_boot_attributes] = locate_config_value(:first_boot_attributes)
  bootstrap.config[:environment] = locate_config_value(:environment)
  bootstrap.config[:bootstrap_wget_options] = locate_config_value(:bootstrap_wget_options)
  bootstrap.config[:bootstrap_proxy] = locate_config_value(:bootstrap_proxy)
  bootstrap
end

#bootstrap_for_node(server, fqdn) ⇒ Object



627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
# File 'lib/chef/knife/cs_server_create.rb', line 627

def bootstrap_for_node(server,fqdn)
  bootstrap = Chef::Knife::Bootstrap.new
  bootstrap.name_args = [fqdn]
  if locate_config_value(:cloudstack_password)
    bootstrap.config[:ssh_user] = locate_config_value(:ssh_user) || 'root'
  else
    bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
  end
  locate_config_value(:cloudstack_password) ? bootstrap.config[:ssh_password] = server['password'] : bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
  bootstrap.config[:ssh_port] = locate_config_value(:ssh_port) || 22
  bootstrap.config[:identity_file] = locate_config_value(:identity_file)
  bootstrap.config[:secret_file] = locate_config_value(:secret_file)
  bootstrap.config[:secret] = locate_config_value(:secret)
  bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || server["name"]
  bootstrap.config[:use_sudo] = true unless locate_config_value(:ssh_user) == 'root'

  # may be needed for vpc_mode
  bootstrap.config[:host_key_verify] = config[:host_key_verify]
  bootstrap_common_params(bootstrap)
end

#bootstrap_for_windows_node(server, fqdn) ⇒ Object



580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
# File 'lib/chef/knife/cs_server_create.rb', line 580

def bootstrap_for_windows_node(server, fqdn)
  if locate_config_value(:bootstrap_protocol) == 'winrm'
    bootstrap = Chef::Knife::BootstrapWindowsWinrm.new
    if locate_config_value(:kerberos_realm)
      #Fetch AD/WINS based fqdn if any for Kerberos-based Auth
      private_ip_address = connection.get_server_default_nic(server)["ipaddress"]
      fqdn = locate_config_value(:fqdn) || fetch_server_fqdn(private_ip_address)
    end
    bootstrap.name_args = [fqdn]
    bootstrap.config[:winrm_user] = locate_config_value(:winrm_user) || 'Administrator'
    locate_config_value(:cloudstack_password) ? bootstrap.config[:winrm_password] = server['password'] : bootstrap.config[:winrm_password] = locate_config_value(:winrm_password)
    bootstrap.config[:winrm_transport] = locate_config_value(:winrm_transport)
    bootstrap.config[:winrm_port] = locate_config_value(:winrm_port)
  elsif locate_config_value(:bootstrap_protocol) == 'ssh'
    bootstrap = Chef::Knife::BootstrapWindowsSsh.new
    if locate_config_value(:cloudstack_password)
      bootstrap.config[:ssh_user] = locate_config_value(:ssh_user) || 'Administrator'
    else
      bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
    end
    locate_config_value(:cloudstack_password) ? bootstrap.config[:ssh_password] = server['password'] : bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
    bootstrap.config[:ssh_port] = locate_config_value(:ssh_port)
    bootstrap.config[:identity_file] = locate_config_value(:identity_file)
    bootstrap.config[:no_host_key_verify] = locate_config_value(:no_host_key_verify)
  else
    ui.error("Unsupported Bootstrapping Protocol. Supported : winrm, ssh")
    exit 1
  end
  bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || server["name"]
  bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:secret)
  bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:secret_file)
  bootstrap_common_params(bootstrap)
end

#create_firewall_rules(ip_address, connection) ⇒ Object



485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/chef/knife/cs_server_create.rb', line 485

def create_firewall_rules(ip_address, connection)
  Chef::Log.debug("Creating Firewall Rule")
  rules = locate_config_value(:fw_rules)
  return unless rules
  icmptype={
    '0' => {'code' => [0]},
    '8' => {'code' => [0]},
    '3' => {'code' => [0, 1]}
  }
  rules.each do |rule|
    args = rule.split(':')
    protocol = args[0]
    cidr_list = (args[1].nil? || args[1].length == 0) ? "0.0.0.0/0" : args[1]
    startport = args[2]
    endport = args[3] || args[2]
    if protocol == "ICMP"
      icmptype.each do |type, value|
        value['code'].each do |code_id|
          Chef::Log.debug("Creating Firewall Rule for
            #{ip_address['ipaddress']} with protocol: #{protocol}, icmptype: #{type}, icmpcode: #{code_id}, cidrList: #{cidr_list}")
          connection.create_firewall_rule(ip_address['id'], protocol, type, code_id, cidr_list)
        end
      end
    else
      Chef::Log.debug("Creating Firewall Rule for
        #{ip_address['ipaddress']} with protocol: #{protocol}, startport: #{startport}, endport: #{endport}, cidrList: #{cidr_list}")
      connection.create_firewall_rule(ip_address['id'], protocol, startport, endport, cidr_list)
    end
  end
end

#create_ip_forwarding_rules(ip_address, connection) ⇒ Object



468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'lib/chef/knife/cs_server_create.rb', line 468

def create_ip_forwarding_rules(ip_address, connection)
  Chef::Log.debug("Creating IP Forwarding Rule")
  rules = locate_config_value(:ipfwd_rules)
  return unless rules
  rules.each do |rule|
    args = rule.split(':')
    startport = args[0]
    endport = args[1] || args[0]
    protocol = args[2] || "TCP"
    if locate_config_value :static_nat
      Chef::Log.debug("Creating IP Forwarding Rule for
          #{ip_address['ipaddress']} with protocol: #{protocol}, startport: #{startport}, endport: #{endport}")
      connection.create_ip_fwd_rule(ip_address['id'], protocol, startport, endport)
    end
  end
end

#create_port_forwarding_rules(ip_address, server_id, connection) ⇒ Object



437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/chef/knife/cs_server_create.rb', line 437

def create_port_forwarding_rules(ip_address, server_id, connection)
  Chef::Log.debug("Creating IP Forwarding Rule")
  rules = locate_config_value(:port_rules) || []
  if config[:bootstrap]
    if @bootstrap_protocol == 'ssh'
      rules += ["#{locate_config_value(:ssh_port)}"] #SSH Port
    elsif @bootstrap_protocol == 'winrm'
      rules +=[locate_config_value(:winrm_port)]
    else
      puts("\nUnsupported bootstrap protocol : #{@bootstrap_protocol}")
      exit 1
    end
  end
  return if rules.empty?
  rules.each do |rule|
    args = rule.split(':')
    public_port = args[0]
    private_port = args[1] || args[0]
    protocol = args[2] || "TCP"
    if locate_config_value :static_nat
      Chef::Log.debug("Creating IP Forwarding Rule for
        #{ip_address['ipaddress']} with protocol: #{protocol}, public port: #{public_port}")
      connection.create_ip_fwd_rule(ip_address['id'], protocol, public_port, public_port)
    else
      Chef::Log.debug("Creating Port Forwarding Rule for #{ip_address['id']} with protocol: #{protocol},
        public port: #{public_port} and private port: #{private_port} and server: #{server_id}")
      connection.create_port_forwarding_rule(ip_address['id'], private_port, protocol, public_port, server_id)
    end
  end
end

#fetch_server_fqdn(ip_addr) ⇒ Object



360
361
362
363
# File 'lib/chef/knife/cs_server_create.rb', line 360

def fetch_server_fqdn(ip_addr)
    require 'resolv'
    Resolv.getname(ip_addr)
end

#find_or_create_public_ip(server, connection) ⇒ Object



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/chef/knife/cs_server_create.rb', line 416

def find_or_create_public_ip(server, connection)
  nic = connection.get_server_default_nic(server) || {}
  if (config[:public_ip] == false)
    nic['ipaddress']
  else
    puts("\nAllocate ip address, create forwarding rules")
    ip_address = connection.associate_ip_address(server['zoneid'], locate_config_value(:cloudstack_networks))
    puts("\nAllocated IP Address: #{ip_address['ipaddress']}")
    Chef::Log.debug("IP Address Info: #{ip_address}")

    if locate_config_value :static_nat
      Chef::Log.debug("Enabling static NAT for IP Address : #{ip_address['ipaddress']}")
      connection.enable_static_nat(ip_address['id'], server['id'])
    end
    create_port_forwarding_rules(ip_address, server['id'], connection)
    create_ip_forwarding_rules(ip_address, connection)
    create_firewall_rules(ip_address, connection)
    ip_address['ipaddress']
  end
end

#is_image_windows?Boolean

Returns:

  • (Boolean)


365
366
367
368
369
370
371
372
373
374
# File 'lib/chef/knife/cs_server_create.rb', line 365

def is_image_windows?
    template_name = locate_config_value(:cloudstack_template)
    template = connection.get_template(template_name, locate_config_value(:cloudstack_zone))
    template = connection.get_iso(template_name, locate_config_value(:cloudstack_zone)) unless template
    if !template
      ui.error("Template: #{template_name} does not exist!")
      exit 1
    end
    return template['ostypename'].scan('Windows').length > 0
end

#is_platform_windows?Boolean

Returns:

  • (Boolean)


566
567
568
# File 'lib/chef/knife/cs_server_create.rb', line 566

def is_platform_windows?
  return RUBY_PLATFORM.scan('w32').size > 0
end

#is_ssh_open?(ip) ⇒ Boolean

noinspection RubyArgCount,RubyResolve

Returns:

  • (Boolean)


538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
# File 'lib/chef/knife/cs_server_create.rb', line 538

def is_ssh_open?(ip)
  s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
  sa = Socket.sockaddr_in(locate_config_value(:ssh_port), ip)

  begin
    s.connect_nonblock(sa)
  rescue Errno::EINPROGRESS
    resp = IO.select(nil, [s], nil, 1)
    if resp.nil?
      sleep SSH_POLL_INTERVAL
      return false
    end

    begin
      s.connect_nonblock(sa)
    rescue Errno::EISCONN
      Chef::Log.debug("sshd accepting connections on #{ip}")
      yield
      return true
    rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
      sleep SSH_POLL_INTERVAL
      return false
    end
  ensure
    s && s.close
  end
end

#runObject



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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/chef/knife/cs_server_create.rb', line 249

def run
  validate_base_options

  Chef::Log.debug("Validate hostname and options")
  hostname = @name_args.first
  unless /^[a-zA-Z0-9][a-zA-Z0-9-]*$/.match hostname then
    ui.error "Invalid hostname. Please specify a short hostname, not an fqdn (e.g. 'myhost' instead of 'myhost.domain.com')."
    exit 1
  end
  validate_options

  # This little peace of code sets the Chef node-name to the VM name when a node-name is not specifically given
  unless locate_config_value :chef_node_name
    config[:chef_node_name] = @name_args.first
  end

  if @windows_image and locate_config_value(:kerberos_realm)
    Chef::Log.debug("Load additional gems for AD/Kerberos Authentication")
    if @windows_platform
      require 'em-winrs'
    else
      require 'gssapi'
    end
  end

  $stdout.sync = true

  Chef::Log.info("Creating instance with
    service : #{locate_config_value(:cloudstack_service)}
    template : #{locate_config_value(:cloudstack_template)}
    disk : #{locate_config_value(:cloudstack_disk)}
    zone : #{locate_config_value(:cloudstack_zone)}
    project: #{locate_config_value(:cloudstack_project)}
    network: #{locate_config_value(:cloudstack_networks)}")

  print "\n#{ui.color("Waiting for Server to be created", :magenta)}"
  params = {}
  params['hypervisor'] = locate_config_value(:cloudstack_hypervisor) if locate_config_value(:cloudstack_hypervisor)

  params['keypair'] = locate_config_value :keypair  if locate_config_value :keypair
  params['affinitygroupnames'] = locate_config_value :aag if locate_config_value :aag
  params['displayname'] = if locate_config_value :set_display_name and locate_config_value :chef_node_name then locate_config_value :chef_node_name else hostname end
  params['ipaddress'] = locate_config_value(:ik_private_ip) if locate_config_value(:ik_private_ip)
  params['size'] = locate_config_value(:size) if locate_config_value(:size)

  server = connection.create_server(
      hostname,
      locate_config_value(:cloudstack_service),
      locate_config_value(:cloudstack_template),
      locate_config_value(:cloudstack_disk),
      locate_config_value(:cloudstack_zone),
      locate_config_value(:cloudstack_networks),
      params
  )

  zone_name = locate_config_value(:cloudstack_zone)
  zone = zone_name ? connection.get_zone(zone_name) : connection.get_default_zone

  config[:public_ip] = false if zone['networktype'] == 'Basic'
  public_ip = find_or_create_public_ip(server, connection)

  object_fields = []
  object_fields << ui.color("Name:", :cyan)
  object_fields << server['name'].to_s
  object_fields << ui.color("Password:", :cyan) if locate_config_value(:cloudstack_password)
  object_fields << server['password'] if locate_config_value(:cloudstack_password)
  object_fields << ui.color("Public IP:", :cyan)
  object_fields << public_ip

  puts "\n"
  puts ui.list(object_fields, :uneven_columns_across, 2)
  puts "\n"

  return unless config[:bootstrap]

  if @bootstrap_protocol == 'ssh'
    print "\n#{ui.color("Waiting for sshd on: #{public_ip}", :magenta)}"

    print(".") until is_ssh_open?(public_ip) {
      sleep BOOTSTRAP_DELAY
      puts "\n"
    }
  elsif @bootstrap_protocol == 'winrm'
    print "\n#{ui.color("Waiting for winrm to be active on: #{public_ip}", :magenta)}"
    print(".") until tcp_test_winrm(public_ip,locate_config_value(:winrm_port)) {
      sleep WINRM_BOOTSTRAP_DELAY
      puts("\n")
    }
  else
    puts "Cannot determine the bootstrap protocol."
    puts "Please specify either ssh or winrm as bootstrap protocol."
    exit 1
  end

  object_fields = []
  object_fields << ui.color("Name:", :cyan)
  object_fields << server['name'].to_s
  object_fields << ui.color("Public IP:", :cyan)
  object_fields << public_ip
  object_fields << ui.color("Environment:", :cyan)
  object_fields << (config[:environment] || '_default')
  object_fields << ui.color("Run List:", :cyan)
  object_fields << config[:run_list].join(', ')

  puts "\n"
  puts ui.list(object_fields, :uneven_columns_across, 2)
  puts "\n"

  bootstrap(server, public_ip).run
end

#tcp_test_winrm(hostname, port) ⇒ Object



516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/chef/knife/cs_server_create.rb', line 516

def tcp_test_winrm(hostname, port)
  TCPSocket.new(hostname, port)
  return true
  rescue SocketError
    sleep 2
    false
  rescue Errno::ETIMEDOUT
    false
  rescue Errno::EPERM
    false
  rescue Errno::ECONNREFUSED
    sleep 2
    false
  rescue Errno::EHOSTUNREACH
    sleep 2
    false
  rescue Errno::ENETUNREACH
    sleep 2
    false
end

#validate_optionsObject



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
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/chef/knife/cs_server_create.rb', line 376

def validate_options
  unless locate_config_value :cloudstack_template
    ui.error "Cloudstack template not specified"
    exit 1
  end
  @windows_image = is_image_windows?
  @windows_platform = is_platform_windows?

  unless locate_config_value :cloudstack_service
    ui.error "Cloudstack service offering not specified"
    exit 1
  end
  if config[:bootstrap]
    if locate_config_value(:bootstrap_protocol) == 'ssh'
      identity_file = locate_config_value :identity_file
      ssh_user = locate_config_value :ssh_user
      ssh_password = locate_config_value :ssh_password
      unless identity_file || (ssh_user && ssh_password) || locate_config_value(:cloudstack_password)
        ui.error("You must specify either an ssh identity file or an ssh user and password")
        exit 1
      end
      @bootstrap_protocol = 'ssh'
    elsif locate_config_value(:bootstrap_protocol) == 'winrm'
      if not @windows_image
        ui.error("Only Windows Images support WinRM protocol for bootstrapping.")
        exit 1
      end
      winrm_user = locate_config_value :winrm_user
      winrm_password = locate_config_value :winrm_password
      winrm_transport = locate_config_value :winrm_transport
      winrm_port = locate_config_value :winrm_port
      unless (winrm_user && winrm_transport && winrm_port) && (locate_config_value(:cloudstack_password) || winrm_password)
        ui.error("WinRM User, Password, Transport and Port are compulsory parameters")
        exit 1
      end
      @bootstrap_protocol = 'winrm'
    end
  end
end