#
# (c) Jan Gehring <jan.gehring@gmail.com>
# 
# vim: set ts=3 sw=3 tw=0:
# vim: set expandtab:

package Rex::Virtualization::LibVirt::create;

use strict;
use warnings;

use Rex::Logger;
use Rex::Commands::Gather;
use Rex::Hardware;
use Rex::Commands::Fs;
use Rex::Commands::Run;
use Rex::Commands::File;
use Rex::File::Parser::Data;
use Rex::Template;

use XML::Simple;
use Rex::Virtualization::LibVirt::hypervisor;

use Data::Dumper;

my $QEMU_IMG;
if(can_run("qemu-img")) {
   $QEMU_IMG = "qemu-img";
}
elsif(can_run("qemu-img-xen")) {
   $QEMU_IMG = "qemu-img-xen";
}

# read __DATA__ into an array
my @data = <DATA>;

sub execute {
   my ($class, $name, %opt) = @_;

   my $opts = \%opt;
   $opts->{"name"} = $name;

   unless($opts) {
      die("You have to define the create options!");
   }

   ## detect the hypervisor caps
   my $hypervisor = Rex::Virtualization::LibVirt::hypervisor->execute('capabilities');
   my $virt_type = "unknown";

   _set_defaults($opts, $hypervisor);

   if(exists $hypervisor->{"kvm"}) {
      $virt_type = "kvm";
   }
   elsif(exists $hypervisor->{"xen"}) {
      $virt_type = "xen-" . $opts->{"type"};
   }
   else {
      die("Hypervisor not supported.");
   }

   my $fp = Rex::File::Parser::Data->new(data => \@data);
   my $create_xml = $fp->read("create-${virt_type}.xml");

   my $template = Rex::Template->new;
   my $parsed_template = $template->parse($create_xml, $opts);
   
   Rex::Logger::debug($parsed_template);

   ## create storage devices
   for (@{$opts->{'storage'}}) {
      if(! exists $_->{"template"} && $_->{"size"} && $_->{"type"} eq "file") {
         my $size = $_->{'size'};
         if(!is_file($_->{"file"})) {
            Rex::Logger::debug("creating storage disk: \"$_->{file}\"");            
            run "$QEMU_IMG create -f raw $_->{'file'} $size";
            if($? != 0) {
               die("Error creating storage disk: $_->{'file'}");
            }
         }
         else {
            Rex::Logger::info("$_->{file} already exists. Using this.");
         }
      } elsif($_->{'template'} && $_->{'type'} eq "file") {
          Rex::Logger::info("building domain: \"$opts->{'name'}\" from template: \"$_->{'template'}\"");
          Rex::Logger::info("Please wait ...");
          run "$QEMU_IMG convert -f raw $_->{'template'} -O raw $_->{'file'}";
          if($? != 0) {
             die("Error building domain: \"$opts->{'name'}\" from template: \"$_->{'template'}\"\n
             Template doesn't exist or the qemu-img binary is missing")
          }
      }
   } 

   Rex::Logger::info("creating domain: \"$opts->{'name'}\"");

   $parsed_template =~ s/[\n\r]//gms;

   my $file_name = "/tmp/" . rand(100) . ".xml";

   file "$file_name",
      content => $parsed_template;

   run "virsh define $file_name";
   unlink($file_name);
   if($? != 0) {
     die("Error defining vm $opts->{name}");
   }

   return;
}

sub _set_defaults {
   my ($opts, $hyper) = @_;

   if( ! exists $opts->{"name"} ) {
      die("You have to give a name.");
   }

   if( ! exists $opts->{"storage"} ) {
      die("You have to add at least one storage disk.");
   }

   if( ! exists $opts->{"type"} ) {

      if( exists $opts->{"os"} && exists $opts->{"os"}->{"kernel"} && ! exists $hyper->{"kvm"} ) {
         $opts->{"type"} = "pvm";
      }
      else {
         $opts->{"type"} = "hvm";
      }

   }

   if( ! exists $opts->{"memory"} ) {
      $opts->{"memory"} = 512 * 1024;
   }

   if( ! exists $opts->{"cpus"} ) {
      $opts->{"cpus"} = 1;
   }

   if( ! exists $opts->{"clock"} ) {
      $opts->{"clock"} = "utc";
   }

   if( ! exists $opts->{"arch"} ) {
      if( exists $hyper->{"x86_64"} ) {
         $opts->{"arch"} = "x86_64";
      }
      else {
         $opts->{"arch"} = "i686";
      }
   }

   if( ! exists $opts->{"boot"} ) {
      $opts->{"boot"} = "hd";
   }

   if( ! exists $opts->{"emulator"} ) {
      $opts->{"emulator"} = $hyper->{"emulator"};

      if(operating_system_is("Debian") && exists $hyper->{"xen"}) {
         # fix for debian, because virsh capabilities don't give the correct
         # emulator.
         $opts->{"emulator"} = "/usr/lib/xen-4.0/bin/qemu-dm";
      }

   }

   if(exists $hyper->{"loader"} && ! exists $opts->{"loader"}) {
      $opts->{"loader"} = $hyper->{"loader"};
   }

   if( ! exists $opts->{"on_poweroff"} ) {
      $opts->{"on_poweroff"} = "destroy";
   }

   if( ! exists $opts->{"on_reboot"} ) {
      $opts->{"on_reboot"} = "restart";
   }

   if( ! exists $opts->{"on_crash"} ) {
      $opts->{"on_crash"} = "restart";
   }

   if( exists $hyper->{"xen"} && $opts->{"type"} eq "pvm" ) {

      if( ! exists $opts->{"os"}->{"type"} ) {
         $opts->{"os"}->{"type"} = "linux";
      }

      if( ! exists $opts->{"os"}->{"kernel"} ) {
         my %hw = Rex::Hardware->get(qw/ Kernel /);

         if(is_redhat()) {
            $opts->{"os"}->{"kernel"} = "/boot/vmlinuz-" . $hw{"Kernel"}->{"kernelrelease"};
         }
         else {
            $opts->{"os"}->{"kernel"} = "/boot/vmlinuz-" . $hw{"Kernel"}->{"kernelrelease"};
         }
      }

      if( ! exists $opts->{"os"}->{"initrd"} ) {
         my %hw = Rex::Hardware->get(qw/ Kernel /);

         if(is_redhat()) {
            $opts->{"os"}->{"initrd"} = "/boot/initrd-" . $hw{"Kernel"}->{"kernelrelease"} . ".img";
         }
         else {
            $opts->{"os"}->{"initrd"} = "/boot/initrd.img-" . $hw{"Kernel"}->{"kernelrelease"};
         }
      }

      if( ! exists $opts->{"os"}->{"cmdline"} ) {
         my @root_store = grep { $_->{"is_root"} && $_->{"is_root"} == 1 } @{ $opts->{"storage"} };
         $opts->{"os"}->{"cmdline"} = "root=/dev/" . $root_store[0]->{"dev"} . " ro";
      }

   }

   _set_storage_defaults($opts, $hyper);

   _set_network_defaults($opts, $hyper);

}

sub _set_storage_defaults {
   my ($opts, $hyper) = @_;

   my $store_letter = "a";
   for my $store ( @{ $opts->{"storage"} } ) {

      if( ! exists $store->{"type"} ) {
         $store->{"type"} = "file";
      }

      if( ! exists $store->{"driver_type"} ) {
         $store->{"driver_type"} = "raw";
      }

      if( ! exists $store->{"size"} && $store->{"type"} eq "file" ) {

         if($store->{"file"} =~ m/swap/) {
            $store->{"size"} = "1G";
         }
         else {
            $store->{"size"} = "10G";
         }

      }

      if( exists $store->{"file"} && $store->{"file"} =~ m/\.iso$/ && ! exists $store->{"device"} ) {
         $store->{"device"} = "cdrom";
      }

      if( ! exists $store->{"device"} ) {
         $store->{"device"} = "disk";
      }

      if( ! exists $store->{"dev"} && $store->{"device"} eq "cdrom") {
         $store->{"dev"} = "hdc"; 
      }

      if( ! exists $store->{"dev"} ) {

         if( exists $hyper->{"kvm"} ) {
            $store->{"dev"} = "vd${store_letter}";
         }
         else {
            $store->{"dev"} = "hd${store_letter}";
         }

      }

      if( ! exists $store->{"bus"} ) {

         if( exists $hyper->{"kvm"} && $store->{"device"} eq "disk" ) {
            $store->{"bus"} = "virtio";
         }
         else {
            $store->{"bus"} = "ide";
         }

      }
      
      if( exists $hyper->{"kvm"} ) {
         
         if( $store->{"bus"} eq "virtio" && ! exists $store->{"address"} ) {
            $store->{"address"} = {
               type     => "pci",
               domain   => "0x0000",
               bus      => "0x00",
               slot     => "0x05",
               function => "0x0",
            };
         }
         elsif( $store->{"bus"} eq "ide" && ! exists $store->{"address"} ) {
            $store->{"address"} = {
               type       => "drive",
               controller => 0,
               bus        => 1,
               unit       => 0,
            };
         }

      }

      if( $store->{"device"} eq "cdrom" ) {
         $store->{"readonly"} = 1;
      }

      if( is_redhat() ) {

         if( ! exists $store->{"aio"} ) {
            $store->{"aio"} = 1;
         }

      }

      $store_letter++;

   }

}

sub _set_network_defaults {
   my ($opts, $hyper) = @_;

   if( ! exists $opts->{"network"} ) {
      $opts->{"network"} = [
         {
            type   => "bridge",
            bridge => "virbr0",
         },
      ];
   }

   for my $netdev ( @{ $opts->{"network"} } ) {

      if( ! exists $netdev->{"type"} ) {

         $netdev->{"type"} = "bridge";

      }

      if( ! exists $netdev->{"bridge"} ) {

         $netdev->{"bridge"} = "virbr0";

      }

      if( exists $hyper->{"kvm"} ) {

         if( ! exists $netdev->{"model"} ) {

            $netdev->{"model"} = "virtio";

         }

         if( ! exists $netdev->{"address"} ) {

            $netdev->{"address"} = {
               type     => "pci",
               domain   => "0x0000",
               bus      => "0x00",
               slot     => "0x03",
               function => "0x0",
            };

         }

      }

   }
}


1;

__DATA__

@create-kvm.xml
<domain type="kvm">
  <name><%= $::name %></name>
  <memory><%= $::memory %></memory>
  <currentMemory><%= $::memory %></currentMemory>
  <vcpu><%= $::cpus %></vcpu>
  <os>
    <type arch="<%= $::arch %>">hvm</type>
    <boot dev="<%= $::boot %>"/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <pae/>
  </features>
  <clock offset="<%= $::clock %>"/>
  <on_poweroff><%= $::on_poweroff %></on_poweroff>
  <on_reboot><%= $::on_reboot %></on_reboot>
  <on_crash><%= $::on_crash %></on_crash>
  <devices>
    <emulator><%= $::emulator %></emulator>

    <% for my $disk (@{$::storage}) { %>
    <disk type="<%= $disk->{type} %>" device="<%= $disk->{device} %>">
      <driver name="qemu" type="<%= $disk->{driver_type} %>"/>
      <% if ($disk->{type} eq "file") { %>
      <source file="<%= $disk->{file} %>"/>
      <% } elsif ($disk->{file} eq "block") { %>
      <source dev="<%= $disk->{file} %>"/>
      <% } %>
      <% if(exists $disk->{readonly}) { %>
      <readonly/>
      <% } %>
      <target dev="<%= $disk->{dev} %>" bus="<%= $disk->{bus} %>"/>
      <address <% for my $key (keys %{$disk->{address}}) { %> <%= $key %>="<%= $disk->{address}->{$key} %>" <% } %> />
    </disk>
    <% } %>
    <controller type="ide" index="0">
      <address type="pci" domain="0x0000" bus="0x00" slot="0x01" function="0x1"/>
    </controller>
    <% for my $netdev (@{$::network}) { %>
    <interface type="<%= $netdev->{type} %>">
      <% if(exists $netdev->{mac}) { %>
      <mac address='<%= $netdev->{mac} %>'/>
      <% } %>
      <% if($netdev->{type} eq "bridge") { %>
      <source bridge="<%= $netdev->{bridge} %>"/>
      <% } %>
      <model type="<%= $netdev->{model} %>"/>
      <address <% for my $key (keys %{$netdev->{address}}) { %> <%= $key %>="<%= $netdev->{address}->{$key} %>" <% } %> />
    </interface>
    <% } %>
    <serial type="pty">
      <target port="0"/>
    </serial>
    <console type="pty">
      <target port="0"/>
    </console>
    <input type="mouse" bus="ps2"/>
    <graphics type="vnc" autoport="yes" listen="*"/>
    <video>
      <model type="cirrus" vram="9216" heads="1"/>
      <address type="pci" domain="0x0000" bus="0x00" slot="0x02" function="0x0"/>
    </video>
    <memballoon model="virtio">
      <address type="pci" domain="0x0000" bus="0x00" slot="0x06" function="0x0"/>
    </memballoon>
  </devices>
</domain>
@end

@create-xen-hvm.xml
<domain type="xen">
  <name><%= $::name %></name>
  <memory><%= $::memory %></memory>
  <currentMemory><%= $::memory %></currentMemory>
  <vcpu><%= $::cpus %></vcpu>
  <os>
    <type>hvm</type>
    <loader><%= $::loader %></loader>
    <boot dev="<%= $::boot %>"/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <pae/>
  </features>
  <clock offset="<%= $::clock %>">
    <timer name="hpet" present="no"/>
  </clock>
  <on_poweroff><%= $::on_poweroff %></on_poweroff>
  <on_reboot><%= $::on_reboot %></on_reboot>
  <on_crash><%= $::on_crash %></on_crash>
  <devices>
    <emulator><%= $::emulator %></emulator>

    <% for my $disk (@{$::storage}) { %>
    <disk type="<%= $disk->{type} %>" device="<%= $disk->{device} %>">
      <% if(exists $disk->{file}) { %>
      <driver name="file"/>
      <source file="<%= $disk->{file} %>"/>
      <% } %>
      <target dev="<%= $disk->{dev} %>" bus="<%= $disk->{bus} %>"/>
      <% if(exists $disk->{readonly}) { %>
      <readonly/>
      <% } %>
    </disk>
    <% } %>

    <% for my $netdev (@{$::network}) { %>
    <interface type="<%= $netdev->{type} %>">
      <% if(exists $netdev->{mac}) { %>
      <mac address='<%= $netdev->{mac} %>'/>
      <% } %>
      <% if($netdev->{type} eq "bridge") { %>
      <source bridge="<%= $netdev->{bridge} %>"/>
      <% } %>
    </interface>
    <% } %>
    <graphics type="vnc" autoport="yes"/>
    <serial type="pty">
      <target port="0"/>
    </serial>
    <console type="pty">
      <target port="0"/>
    </console>
  </devices>
</domain>
@end

@create-xen-pvm.xml
<domain type="xen">
  <name><%= $::name %></name>
  <memory><%= $::memory %></memory>
  <currentMemory><%= $::memory %></currentMemory>
  <vcpu><%= $::cpus %></vcpu>
  <% if(defined $::bootloader) { %>
  <bootloader><%= $::bootloader %></bootloader>
  <% } %>
  <os>
    <type><%= $::os->{type} %></type>
    <% if(exists $::os->{kernel}) { %>
    <kernel><%= $::os->{kernel} %></kernel>
    <% } %>
    <% if(exists $::os->{initrd}) { %>
    <initrd><%= $::os->{initrd} %></initrd>
    <% } %>
    <% if(exists $::os->{cmdline}) { %>
    <cmdline><%= $::os->{cmdline} %></cmdline>
    <% } %>
  </os>
  <clock offset="<%= $::clock %>"/>
  <on_poweroff><%= $::on_poweroff %></on_poweroff>
  <on_reboot><%= $::on_reboot %></on_reboot>
  <on_crash><%= $::on_crash %></on_crash>
  <devices>

    <% for my $disk (@{$::storage}) { %>
    <disk type="<%= $disk->{type} %>" device="<%= $disk->{device} %>">
      <% if($disk->{aio}) { %>
      <driver name="tap" type="aio"/>
      <% } else { %>
      <driver name="file"/>
      <% } %>
      <% if(exists $disk->{file}) { %>
      <source file="<%= $disk->{file} %>"/>
      <% } %>
      <target dev="<%= $disk->{dev} %>" bus="<%= $disk->{bus} %>"/>
    </disk>
    <% } %>

    <% for my $netdev (@{$::network}) { %>
    <interface type="<%= $netdev->{type} %>">
      <% if(exists $netdev->{mac}) { %>
      <mac address="<%= $netdev->{mac} %>"/>
      <% } %>
      <% if($netdev->{type} eq "bridge") { %>
      <source bridge="<%= $netdev->{bridge} %>"/>
      <% } %>
    </interface>
    <% } %>
    <graphics type="vnc" autoport="yes"/>
    <console type="pty">
      <target port="0"/>
    </console>
  </devices>
</domain>

@end


