#!/usr/bin/perl -w
#
# Copyright (c) 2007 VMware, Inc. All rights reserved.
#
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../";
use lib "/usr/lib/vmware-viperl/apps";
use lib "/usr/lib/perl5/vendor_perl/5.8.8";
use lib "/opt/vmware/vcli/apps/";
use VMware::VIRuntime;
use XML::LibXML;
use AppUtil::XMLInputUtil;
use AppUtil::HostUtil;
$Util::script_version = "1.0";
my %opts = (
filename => {
type => "=s",
help => "The location of the input config file",
required => 0,
default => "../sampledata/vmcreate.vmconf",
},
schema => {
type => "=s",
help => "The location of the schema file",
required => 0,
default => "../schema/vmcreate.xsd",
},
);
my ($annotation, $guestId, $locationId, $name, $memoryMB, $deviceChange, $numCPUs, $listofdisks, $listofnics, $cdrom, $keyboard, $videocard, $datacentre, $nextkey, $nextunitnum);
Opts::add_options(%opts);
Opts::parse();
Opts::validate();
Util::connect();
my $configfile = Opts::get_option('filename'); if(-e $configfile) {
parseconfig($configfile);
} else {
die("Couldn't read $configfile\n");
}
Util::disconnect();
exit;
# create a virtual machine
# ========================
# create virtual device config spec for controller # ================================================
sub create_conf_spec {
my $controller;
$controller = VirtualLsiLogicController->new(
key => 0,
device => [0],
busNumber => 0,
sharedBus => VirtualSCSISharing->new('noSharing'));
my $controller_vm_dev_conf_spec = VirtualDeviceConfigSpec->new(device => $controller, operation => VirtualDeviceConfigSpecOperation->new('add'));
return $controller_vm_dev_conf_spec;
}
# create virtual device config spec for disk # ==========================================
sub create_virtual_disk {
my %args = @_;
my $ds_path = $args{ds_path};
my $keynum = $args{keynum};
my $disksize = $args{disksize};
my $disk_backing_info =
VirtualDiskFlatVer2BackingInfo->new(diskMode => 'persistent',
fileName => $ds_path);
$nextunitnum += 1;
my $disk = VirtualDisk->new(backing => $disk_backing_info,
controllerKey => 0,
key => $keynum,
unitNumber => $nextunitnum,
capacityInKB => $disksize);
my $disk_vm_dev_conf_spec =
VirtualDeviceConfigSpec->new(device => $disk,
fileOperation => VirtualDeviceConfigSpecFileOperation->new('create'),
operation => VirtualDeviceConfigSpecOperation->new('add'));
return $disk_vm_dev_conf_spec;
}
# get network configuration
# =========================
sub get_network {
my %args = @_;
my $network_name = $args{network_name};
my $poweron = $args{poweron};
my $host_view = $args{host_view};
my $address_type = $args{address_type};
my $mac_address = $args{mac_address};
my $network = undef;
my $unit_num = 1; # 1 since 0 is used by disk
if($network_name) {
my $network_list = Vim::get_views(mo_ref_array => $host_view->network);
foreach (@$network_list) {
if($network_name eq $_->name) {
$network = $_;
my $nic_backing_info =
VirtualEthernetCardNetworkBackingInfo->new(deviceName => $network_name,
network => $network);
my $vd_connect_info =
VirtualDeviceConnectInfo->new(allowGuestControl => 1,
connected => 0,
startConnected => $poweron);
my $nic;
if($address_type eq "manual") {
if(! $mac_address) {
die("EXCEPTION: attempting manual allocation without mac_address\n");
}
$nic = VirtualPCNet32->new(backing => $nic_backing_info,
key => 0,
unitNumber => $unit_num,
addressType => $address_type,
macAddress => $mac_address,
connectable => $vd_connect_info);
} else {
$nic = VirtualPCNet32->new(backing => $nic_backing_info,
key => 0,
unitNumber => $unit_num,
addressType => $address_type,
connectable => $vd_connect_info);
}
my $nic_vm_dev_conf_spec =
VirtualDeviceConfigSpec->new(device => $nic,
operation => VirtualDeviceConfigSpecOperation->new('add'));
return (error => 0, network_conf => $nic_vm_dev_conf_spec);
}
}
if (!defined($network)) {
# no network found
return (error => 1);
}
}
# default network will be used
return (error => 2);
}
# check the XML file
# =====================
sub validate {
my $valid = XMLValidation::validate_format(Opts::get_option('filename'));
if ($valid == 1) {
$valid = XMLValidation::validate_schema(Opts::get_option('filename'),
Opts::get_option('schema'));
if ($valid == 1) {
$valid = check_missing_value();
}
}
return $valid;
}
# check missing values of mandatory fields # ========================================
sub check_missing_value {
my $valid = 1;
my $filename = Opts::get_option('filename');
my $parser = XML::LibXML->new();
my $tree = $parser->parse_file($filename);
my $root = $tree->getDocumentElement;
# defect 223162
if($root->nodeName eq 'Virtual-Machines') {
my @vms = $root->findnodes('Virtual-Machine');
foreach (@vms) {
if (!$_->findvalue('Name')) {
Util::trace(0, "\nERROR in '$filename':\n<Name> value missing " .
"in one of the VM specifications\n");
$valid = 0;
}
if (!$_->findvalue('Host')) {
Util::trace(0, "\nERROR in '$filename':\n<Host> value missing " .
"in one of the VM specifications\n");
$valid = 0;
}
if (!$_->findvalue('Datacenter')) {
Util::trace(0, "\nERROR in '$filename':\n<Datacenter> value missing " .
"in one of the VM specifications\n");
$valid = 0;
}
}
}
else {
Util::trace(0, "\nERROR in '$filename': Invalid root element ");
$valid = 0;
}
return $valid;
}
sub parseconfig {
my $filename = shift;
if(! open(CONFIG,$filename)) {
die("Couldn't open $filename for reading.\n");
}
while(<CONFIG>) {
# Remove carriage returns and line feeds and comments
s/[\r\n*]//g;
s/#.*//;
# Skip blank lines
if(/^\s*$/) {
next;
}
# Look for lines we recognise and capture the relevant information
if(/^datacentre\s*(.*)/i) {
$datacentre = $1;
logit(" Data Centre $datacentre\n");
next;
}
if(/^STARTVM\s*(.*)/i) {
logit("Found virtual machine $1\n");
next;
}
if(/^annotation\s*(.*)/i) {
$annotation = $1;
logit(" description $annotation\n");
next;
}
if(/^guestId\s*(.*)/i) {
$guestId = $1;
logit(" Guest OS $guestId\n");
next;
}
if(/^locationId\s*(.*)/i) {
$locationId = $1;
logit(" Target Host $locationId\n");
next;
}
if(/^memoryMB\s*(.*)/i) {
$memoryMB = $1;
logit(" memory $memoryMB\n");
next;
}
if(/^name\s*(.*)/i) {
$name = $1;
logit(" name $name\n");
next;
}
if(/^numCPUs\s*(.*)/i) {
$numCPUs = $1;
logit(" vCPUs $numCPUs\n");
next;
}
if(/^Ethernet\s*(.*)/i) {
my $params = $1;
my ($network,$mac) = split(/\s+/,$params);
my $value;
if($mac) {
$value = "$network/$mac";
} else {
$value = "$network/";
}
if($listofnics) {
$listofnics .= ",$value";
} else {
$listofnics = "$value";
}
if($mac) {
logit(" Ethernet $network using MAC $mac\n");
} else {
logit(" Ethernet $network with automatically generated MAC\n");
}
next;
}
if(/^CD-ROM/i) {
$cdrom = 1;
logit(" CD-ROM\n");
next;
}
if(/^Disk\s*(.*)/i) {
my $diskdata = $1;
my ($disksize,$datastore);
($disksize,$datastore) = split(/\s+/,$diskdata);
logit(" Disk $disksize MB on $datastore\n");
if($listofdisks) {
$listofdisks .= ",$disksize:$datastore";
} else {
$listofdisks = "$disksize:$datastore";
}
next;
}
if(/^Keyboard/i) {
$keyboard = 1;
logit(" keyboard\n");
next;
}
if(/^Videocard\s*(.*)/i) {
$videocard = $1;
logit(" videocard $videocard (currently ignored)\n");
next;
}
# Found the end of a VM, use the information collected to create that VM
if(/^ENDVM\s*(.*)/i) {
my @vm_devices;
my $failure = 0;
if(! $name) {
logit("name is a mandatory field\n");
$failure = 1;
}
if(! $locationId) {
logit("locationId is a mandatory field\n");
$failure = 1;
}
if(! $annotation) {
logit("annotation is a mandatory field\n");
$failure = 1;
}
if(! $guestId) {
logit("guestId is a mandatory field\n");
$failure = 1;
}
if(! $datacentre) {
logit("datacentre is a mandatory field\n");
$failure = 1;
}
if(! $memoryMB) {
logit("memoryMB not supplied, defaulting to 1024MB\n");
$memoryMB = 1024;
}
if(! $numCPUs) {
logit("numCPUs not supplied, defaulting to 2\n");
$numCPUs = 2;
}
if($failure) {
die("aborting due to errors\n");
}
# Get the object for the destination host
my $host_view = Vim::find_entity_view( view_type => 'HostSystem',
filter => {'name' => $locationId});
if (!$host_view) {
Util::trace(0, "\nError creating VM '$name': Host '$locationId' not found\n");
return;
}
# Create the virtual disk controller
my $controller_vm_dev_conf_spec = create_conf_spec();
push(@vm_devices, $controller_vm_dev_conf_spec);
# Get the datastore reference for the target datastore
my (@disks);
@disks = split(/,/,$listofdisks);
my $first_ds_path;
foreach my $disk (@disks) {
my ($size,$datastore) = split(/:/,$disk);
my %ds_info = HostUtils::get_datastore( host_view => $host_view,
datastore => $datastore,
disksize => $size);
# Does the datastore exist and will the VMDK fit?
if ($ds_info{mor} eq 0) {
if ($ds_info{name} eq 'datastore_error') {
Util::trace(0, "\nError creating VM '$name': Datastore $datastore not available.\n");
return;
}
if ($ds_info{name} eq 'disksize_error') {
Util::trace(0, "\nError creating VM '$name': The free space available is less than the specified disksize.\n");
return;
}
}
my $ds_path = "[" . $ds_info{name} . "]";
if(! $first_ds_path) {
$first_ds_path = $ds_path;
}
$nextkey -= 1;
my $disk_vm_dev_conf_spec = create_virtual_disk(ds_path => $ds_path, disksize => $size, keynum => $nextkey);
push(@vm_devices, $disk_vm_dev_conf_spec);
}
# Create network interfaces
my (@nics);
@nics = split(/,/,$listofnics);
foreach my $nic (@nics) {
my ($network,$mac) = split(/\//,$nic);
my %net_settings;
if($mac) {
%net_settings = get_network( network_name => $network,
poweron => 1,
address_type => "manual",
mac_address => $mac,
host_view => $host_view);
} else {
%net_settings = get_network( network_name => $network,
poweron => 1,
address_type => "generated",
host_view => $host_view);
}
if($net_settings{'error'} eq 0) {
push(@vm_devices, $net_settings{'network_conf'});
} elsif ($net_settings{'error'} eq 1) {
Util::trace(0, "\nError creating VM '$name': Network '$network' not found\n");
return;
}
}
# Set up virtual machine file information
my $files = VirtualMachineFileInfo->new(logDirectory => undef,
snapshotDirectory => undef,
suspendDirectory => undef,
vmPathName => $first_ds_path);
# Add a CD-ROM
if($cdrom) {
$nextkey -= 1;
my $cdromdev = VirtualCdrom->new(key => $nextkey,
controllerKey => 0,
unitNumber => $nextunitnum,
connectable => VirtualDeviceConnectInfo->new(
allowGuestControl => 1,
connected => 0,
startConnected => 0)
);
$nextunitnum += 1;
my $cdrom_conf_spec = VirtualDeviceConfigSpec->new(
device => $cdromdev,
operation => VirtualDeviceConfigSpecOperation->new('add'));
push(@vm_devices,$cdrom_conf_spec);
}
# Add a keyboard
if($keyboard) {
$nextkey -= 1;
my $keyboarddev = VirtualKeyboard->new(key => $nextkey);
my $keyboard_conf_spec = VirtualDeviceConfigSpec->new(
device => $keyboarddev,
operation => VirtualDeviceConfigSpecOperation->new('add'));
push(@vm_devices,$keyboard_conf_spec);
}
# Create the vmconfiguspec
my $vm_config_spec = VirtualMachineConfigSpec->new( name => $name,
annotation => $annotation,
memoryMB => $memoryMB,
files => $files,
numCPUs => $numCPUs,
guestId => $guestId,
deviceChange => \@vm_devices);
my $datacenter_views = Vim::find_entity_views (view_type => 'Datacenter',filter => { name => $datacentre});
unless (@$datacenter_views) {
Util::trace(0, "\nError creating VM '$name': " . "Datacenter '$datacentre' not found\n");
return;
}
if ($#{$datacenter_views} != 0) {
Util::trace(0, "\nError creating VM '$name': " . "Datacenter '$datacentre' not unique\n");
return;
}
my $data_center = shift @$datacenter_views;
my $vm_folder_view = Vim::get_view(mo_ref => $data_center->vmFolder);
my $comp_res_view = Vim::get_view(mo_ref => $host_view->parent);
eval {
$vm_folder_view->CreateVM(config => $vm_config_spec, pool => $comp_res_view->resourcePool);
Util::trace(0, "\nSuccessfully created virtual machine: " ."'$name' under host $locationId\n");
};
if ($@) {
Util::trace(0, "\nError creating VM '$name': ");
if (ref($@) eq 'SoapFault') {
if (ref($@->detail) eq 'PlatformConfigFault') {
Util::trace(0, "Invalid VM configuration: " . ${$@->detail}{'text'} . "\n");
} elsif (ref($@->detail) eq 'InvalidDeviceSpec') {
Util::trace(0, "Invalid Device configuration: " . ${$@->detail}{'property'} . "\n");
} elsif (ref($@->detail) eq 'DatacenterMismatch') {
Util::trace(0, "DatacenterMismatch, the input arguments had entities " . "that did not belong to the same datacenter\n");
} elsif (ref($@->detail) eq 'HostNotConnected') {
Util::trace(0, "Unable to communicate with the remote host," . " since it is disconnected\n");
} elsif (ref($@->detail) eq 'InvalidState') {
Util::trace(0, "The operation is not allowed in the current state\n");
} elsif (ref($@->detail) eq 'DuplicateName') {
Util::trace(0, "Virtual machine already exists.\n");
} else {
Util::trace(0, "\n" . $@ . "\n");
}
} else {
Util::trace(0, "\n" . $@ . "\n");
}
}
next;
}
logit("Unrecognised line: $_\n");
}
}
sub logit {
my $text = shift;
print("$text");
}