#!/usr/bin/perl -w
###############################################################################
# $Id: $ 
###############################################################################
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################

=head1 NAME

VCL::Provisioning::one - VCL module to support OpenNebula Cloud

=head1 SYNOPSIS

 Needs to be written

=head1 DESCRIPTION

 This module provides VCL support for OpenNebula Cloud

=cut

##############################################################################
package VCL::Module::Provisioning::one;

# Specify the lib path using FindBin
use FindBin;
use lib "$FindBin::Bin/../../..";

# Configure inheritance
use base qw(VCL::Module::Provisioning);

# Specify the version of this module
our $VERSION = '2.4.2';

# Specify the version of Perl to use
use 5.008000;

use strict;
use warnings;
use diagnostics;
use English qw( -no_match_vars );

use VCL::utils;
use Fcntl qw(:DEFAULT :flock);
use Frontier::Client;
use XML::Simple;
use Data::Dumper;

my %one;
my $xml;

=head2 initialize

 Parameters  :
 Returns     :
 Description :

=cut

sub initialize {
	my $self = shift;
	unless (ref($self) && $self->isa('VCL::Module')) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $one_username = $self->data->get_vmhost_profile_username();
	my $one_password = $self->data->get_vmhost_profile_password();
	my $one_server_url = $self->data->get_vmhost_profile_resource_path();
	
	if (defined($one_username) and defined($one_password) and defined($one_server_url)) {
	
		$one{'server_url'} = $one_server_url;
		$one{'auth'} = "$one_username:$one_password"; 
		$one{'server'} = Frontier::Client->new(url => $one{'server_url'});
		$one{'false'} = $one{'server'}->boolean(0);
		$one{'true'} = $one{'server'}->boolean(1);
	
		$xml = XML::Simple->new();
	
		notify($ERRORS{'DEBUG'}, 0, "Module ONE initialized with following parameters: \n one_server_url -> $one{'server_url'}, one_username:one_password -> $one{'auth'}\n");
	
		return 1;
	} else {
		notify($ERRORS{'CRITICAL'}, 0,"one_username, one_password, one_server_url not defined in VM Host profile. Abort.");
		return 0;
	}
}

#/////////////////////////////////////////////////////////////////////////////

=head2 provision

 Parameters  : hash
 Returns     : 1(success) or 0(failure)
 Description : loads node with provided image

=cut

sub load {
	my $self = shift;
	if (ref($self) !~ /one/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}
	
	
	my $reservation_id = $self->data->get_reservation_id();
	my $computer_id = $self->data->get_computer_id();
	my $image_name = $self->data->get_image_name();
	my $eth0_ip = $self->data->get_computer_private_ip_address();
	my $image_os_type = $self->data->get_image_os_type();
	my $one_network_0 = $self->data->get_vmhost_profile_virtualswitch0();
	my $one_network_1 = $self->data->get_vmhost_profile_virtualswitch1();
	my $vm_name = $self->data->get_image_prettyname();
	my $cpu_count = $self->data->get_image_minprocnumber() || 1;
	my $image_arch = $self->data->get_image_architecture();
	if ($image_arch ne "x86_64") {$image_arch = "i686";}
	my $memory = $self->data->get_image_minram();
	my $computer_name = $self->data->get_computer_hostname();
	if ($memory < 512) {
		$memory = 512;
		#$memory = $cpu_count * 2048;
	}
	my $one_vm_name = "$computer_name ($image_name)";

	#delete running VM, if present
	notify($ERRORS{'OK'}, 0, "Checking if computer $computer_name already loaded on ONE...");
	my $one_computer_id = $self->_one_get_object_id("computer",$computer_name);
	if ($one_computer_id) {
		$self->_one_delete_vm($one_computer_id);
		notify($ERRORS{'OK'}, 0, "Computer $computer_name was running on ONE ... deleted.");
		# sleep for 2sec to allow ONE process the request:
		sleep 2;
	}
	
	# check if there is ONE template already exsist for the image
	# and create VM based on the template. If no template create 'manually'
	
	my $one_template_id = $self->_one_get_template_id($image_name);
	if ($one_template_id) {
		my @template_info = $one{'server'}->call('one.template.info',$one{'auth'},$one_template_id);
		if ($template_info[0][0]->value()) {
			my $data = XMLin($template_info[0][1]);
			my $template;
			$template = $data->{TEMPLATE};
			$template->{NAME} = $one_vm_name;
			$template->{NIC}[0]{IP} = $eth0_ip;
			
			my $one_new_vmid = $self->_one_create_vm(XMLout($template,NoAttr => 1,RootName=>'TEMPLATE',));	
			if ($one_new_vmid) {
				notify($ERRORS{'OK'}, 0, "New VM $template->{NAME} deployed with ID $one_new_vmid using template ID $one_template_id");
				insertloadlog($reservation_id, $computer_id, "vmsetupconfig", "defined $computer_name");
				insertloadlog($reservation_id, $computer_id, "startvm", "powered on $computer_name");
			} else {
				notify($ERRORS{'CRITICAL'}, 0, "Could't create requested VM. Abort.");
				return 0;
			}
		} else {
			notify($ERRORS{'CRITICAL'}, 0, "Error while making one.template.info call: $template_info[0][1]");
		}
	} else {
		
		# No template, create VM manually:
		my $template = {};
		my $one_network_0_id = $self->_one_get_object_id("network",$one_network_0);
		my $one_network_1_id = $self->_one_get_object_id("network",$one_network_1);
		my $one_image_id = $self->_one_get_object_id("image",$image_name);
		my $one_virtio = $self->_one_get_virtio($one_image_id);
		my $virtio = 0;
		if ($self->_one_get_image_tag_value($image_name,"DEV_PREFIX") eq "vd") {
			$virtio = 1;
		}
		
		$template->{NAME} = $one_vm_name;
		$template->{CPU} = $cpu_count;
		$template->{VCPU} = $cpu_count;
		$template->{MEMORY} = $memory;
		$template->{OS}{ARCH} = $image_arch; 
		$template->{INPUT}{BUS} = "usb";
		$template->{INPUT}{TYPE} = "tablet";
		$template->{GRAPHICS}{TYPE} = "VNC";
		$template->{GRAPHICS}{LISTEN} = "0.0.0.0";
		$template->{REQUIREMENTS} = "CLUSTER_ID=\"100\"";
		#$template->{REQUIREMENTS} = "CLUSTER_ID=\"100\" | CLUSTER_ID=\"108\"";
		$template->{DISK}[0]{IMAGE_ID} = $one_image_id;
		$template->{NIC}[0]{NETWORK_ID} = $one_network_0_id;
		$template->{NIC}[0]{IP} = $eth0_ip;
		$template->{NIC}[0]{MODEL} = "virtio" if ($virtio);
		$template->{NIC}[1]{NETWORK_ID} = $one_network_1_id;
		$template->{NIC}[1]{MODEL} = "virtio" if ($virtio);
			
		# Check if SWAP disk needed. Does image have SWAP=<size_MB> attribute?
		my $swap_disk = '';
		my $swap_size = $self->_one_get_image_tag_value($image_name,"SWAP");
		if ($swap_size) {
			$template->{DISK}[1]{TYPE} = "swap";
			$template->{DISK}[1]{SIZE} = $swap_size;
			$template->{DISK}[1]{DEV_PREFIX} = "vd" if ($virtio); 
		}
		
		# create VM
		
		my $one_new_vmid = $self->_one_create_vm(XMLout($template,NoAttr => 1,RootName=>'TEMPLATE',));	
		if ($one_new_vmid) {
			notify($ERRORS{'OK'}, 0, "New VM $template->{NAME} deployed with ID $one_new_vmid");
			insertloadlog($reservation_id, $computer_id, "vmsetupconfig", "defined $computer_name");
			insertloadlog($reservation_id, $computer_id, "startvm", "powered on $computer_name");
		}	
	}
	# VM is created and loading, execute "post_load"
	if ($self->os->can("post_load")) {
		if ($self->os->post_load()) {
			insertloadlog($reservation_id, $computer_id, "loadimagecomplete", "performed OS post-load tasks for $computer_name");
		}
		else {
			notify($ERRORS{'WARNING'}, 0, "failed to perform OS post-load tasks on $computer_name");
			return;
		}
	}
	else {
		insertloadlog($reservation_id, $computer_id, "loadimagecomplete", "OS post-load tasks not necessary $computer_name");
	}
	
	return 1;
}


#/////////////////////////////////////////////////////////////////////////////

=head2 does_image_exist

 Parameters  : imagename
 Returns     : 0 or 1
 Description : scans  our image local image library for requested image
					returns 1 if found or 0 if not
					attempts to scp image files from peer management nodes

=cut

sub does_image_exist {
	
	my $self = shift;
	unless (ref($self) && $self->isa('VCL::Module')) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method");
		return;	
	}

	my $image_name = $self->data->get_image_name();
	if (!$image_name) {
		notify($ERRORS{'WARNING'}, 0, "unable to determine image name");
		return;
	}
	
	my $one_image_id = $self->_one_get_object_id("image",$image_name);
	if ($one_image_id) {
		notify($ERRORS{'DEBUG'}, 0, "Found image $image_name with id $one_image_id");
		return $one_image_id;
	} 

	notify($ERRORS{'DEBUG'}, 0, "Image $image_name NOT found on ONE");
	return 0;
} 

#/////////////////////////////////////////////////////////////////////////////



#/////////////////////////////////////////////////////////////////////////////

=head2 capture

 Parameters  : 
 Returns     : 
 Description : 

=cut

sub capture {
	my $self = shift;
	unless (ref($self) && $self->isa('VCL::Module')) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $old_image_name;
	my $image_name = $self->data->get_image_name();
	
	my $image_id = $self->data->get_image_id();
	my $imagerevision_id = $self->data->get_imagerevision_id();
	my $image_type = $self->data->get_imagetype_name();
	my $computer_name = $self->data->get_computer_hostname();
	my $one_new_image_id = 0;
	
	$self->data->set_imagemeta_sysprep(0);
	
	notify($ERRORS{'OK'}, 0, "ONE module starting image capture.");
	
	my $vmid = $self->_one_get_object_id("computer",$computer_name);
	if ($vmid) {
		# get {TEMPLATE}{DISK}{IMAGE} of $vmid, the name of current image
		
		$old_image_name = $self->_one_get_vm_disk($vmid);
		
		my @savedisk = $one{'server'}->call('one.vm.savedisk', $one{'auth'},$vmid,0,$image_name,'OS',$one{'false'});
		if ( $savedisk[0][0]->value() ) {
			notify($ERRORS{'OK'}, 0, "VM $vmid will be captured as $image_name");
			
		} else {
			notify($ERRORS{'CRITICAL'}, 0, $savedisk[0][1]);
			return;
		}
	} else {
		notify($ERRORS{'CRITICAL'}, 0, "Couldn't find vmid for $computer_name. Abort.");
		return 0;
	}

	# Call the OS module's pre_capture() subroutine (don't shutdown at the end)
	if ($self->os->can("pre_capture")) {
	 	if (!$self->os->pre_capture({end_state => 'on'})) {
			notify($ERRORS{'CRITICAL'}, 0, "failed to complete OS module's pre_capture tasks");
			return;
		} else {
			notify($ERRORS{'OK'}, 0, "OS's pre_capture complited OK.");
		}
	} else {
		notify($ERRORS{'CRITICAL'}, 0, "OS module doesn't implement pre_capture(). Abort.");
		return;
	}
	
	# pre_capture was called with {end_state => 'on'}. Need to shutdown VM via ACPI.
	if(!$self->power_off()) {
		notify($ERRORS{'CRITICAL'}, 0, "Couldn't shutdown $computer_name with power_off()");
		return 0;
	} else {
		# notify($ERRORS{'DEBUG'}, 0, "Sent 'shutdown' to computer $computer_name via provisioning module");
		# make sure VM enters STATE=ACTIVE (3) & LCM_STATE=EPILOG(11)
		# if VM doesn't reach EPILOG, wait for ACTIVE/RUNNING (3/3) and then send 'shutdown-hard', check for EPILOG again.
		my $sleep = 5;
		my $wait_time = 5 * 60; # how long to wait for LCM_STATE = EPILOG
		my $flag = 0;
		my $state;
		my $lcm_state;
		
		notify($ERRORS{'OK'}, 0, "Wait for the VM $vmid to enter ACTIVE/EPILOG (3/11) state...");
		EPILOG: while (1) {
			$state = $self->_one_get_vm_state($vmid);
			notify($ERRORS{'OK'}, 0, "VM $vmid is in $state state");
			if ($state == 3) {
				$lcm_state = $self->_one_get_vm_lcm_state($vmid);
				notify($ERRORS{'OK'}, 0, "VM $vmid is in $lcm_state lcm_state");
				if ($lcm_state == 11) {
					notify($ERRORS{'OK'}, 0, "VM $vmid is in EPILOG state. OK");
					last EPILOG;
				} else {
					notify($ERRORS{'OK'}, 0, "VM $vmid is in $state / $lcm_state state...");
				}
			} else {
				notify($ERRORS{'DEBUG'}, 0, "VM $vmid should be in ACTIVE (3) state, but it's in $state state");
				return 0;
			}
			sleep $sleep;
			$wait_time = $wait_time - $sleep;
			notify($ERRORS{'OK'}, 0, "Waiting for VM $vmid to enter ACTIVE/EPILOG state, $wait_time sec left ...");
			
			if ($wait_time <= $sleep) {
				notify($ERRORS{'DEBUG'}, 0, "VM $vmid never reached EPILOG state. Wait for ACTIVE/RUNNING (3/3) and send 'shutdown-hard'");
				my $sleep = 15;
				my $wait_time = 20 * 60; #how long to wait for ACTIVE/RUNNING (3/3)
				
				while (1) {
					$state = $self->_one_get_vm_state($vmid);
					if ($state == 3) {
						$lcm_state = $self->_one_get_vm_lcm_state($vmid);
						if ($lcm_state == 3) {
							notify($ERRORS{'OK'}, 0, "VM $vmid is in $state / $lcm_state state. Wait $sleep sec and send 'shutdown-hard'");
							sleep $sleep;
							if (!$self->power_off('hard')) {
								notify($ERRORS{'CRITICAL'}, 0, "Couldn't shutdown $computer_name with power_off('hard')");
								return 0;
							} 
							last EPILOG;
						} else {
							notify($ERRORS{'OK'}, 0, "VM $vmid is in $state / $lcm_state state...");
						}
					} else {
						notify($ERRORS{'OK'}, 0, "VM $vmid is in $state state...");
					}
					sleep $sleep if ($wait_time > 0);
					$wait_time = $wait_time - $sleep;
					notify($ERRORS{'OK'}, 0, "Waiting for VM $vmid to enter ACTIVE/RUNNING state, $wait_time sec left ...");
					if ($wait_time <= $sleep) {
						notify($ERRORS{'CRITICAL'}, 0, "VM $vmid is in $state / $lcm_state after $wait_time sec ... Fail!");
						return 0;
					}
				}
			}
		}
	}

	# Check that we have new_image_name created on ONE (it will be in LOCKED state until disk_save is done).
	# just procation, image stub should be created already.
	my $sleep = 5;
	my $wait_time = 20 * 60; # in min * 60 = seconds
	while (1) {
		$one_new_image_id = $self->_one_get_object_id("image",$image_name);
		last if ($one_new_image_id);
		$wait_time = $wait_time - $sleep;
		if ($wait_time <= 0) {
			notify($ERRORS{'CRITICAL'}, 0, "Could not locate new disk id for $image_name. disk_save wasn't successfull.");
			last;
		}
		sleep $sleep;
	}
	
	# wait until disk_save is done
	
	$wait_time = 30 * 60; # in min * 60 = seconds
	while (1) {
		notify($ERRORS{'OK'}, 0, "check status for new image id $one_new_image_id, $wait_time sec left...");
		my $one_image_state = $self->_one_get_image_state($one_new_image_id);
		if ($one_image_state == 4) {
			notify($ERRORS{'OK'}, 0, "disk save in pregress, image id $one_new_image_id is LOCKED");
		}
		if ($one_image_state == 5) {
			notify($ERRORS{'CRITICAL'}, 0, "disk save failed, image id $one_new_image_id is ERROR");
			return 0;
		}
		if ($one_image_state == 1) {
			notify($ERRORS{'OK'}, 0, "disk save OK, image id $one_new_image_id is READY");
			# check if template exists for the old image and create template for the new image.
			my $one_template_id = $self->_one_get_template_id($old_image_name); 
			if ($one_template_id) {
				notify($ERRORS{'OK'}, 0, "Found existing template id $one_template_id for $old_image_name");
				my @template_info = $one{'server'}->call('one.template.info',$one{'auth'},$one_template_id);
				if ($template_info[0][0]->value()) {
					my $data = XMLin($template_info[0][1]);
					my $template = $data->{TEMPLATE};
					$template->{NAME} = $image_name;
					
					if ( (ref($template->{DISK})) eq "ARRAY" ) { # template has multiple disks, update [0]
						$template->{DISK}[0]{IMAGE_ID} = $one_new_image_id;
					} else { #template has one disk
						$template->{DISK}{IMAGE_ID} = $one_new_image_id;
					}	
					
					if (!$self->_one_create_template(XMLout($template,NoAttr => 1,RootName=>'TEMPLATE',))) {
						notify($ERRORS{'CRITICAL'}, 0, "Could't create $image_name template. Abort.");
					}
				} else {
					notify($ERRORS{'CRITICAL'}, 0, "Error while making one.template.info call: $template_info[0][1]");
				}
			} else {
				notify($ERRORS{'OK'}, 0, "No template exists for $old_image_name");
			}
			return 1;
		}
		if ( $wait_time <= 0 ) {
			notify($ERRORS{'CRITICAL'}, 0, "disk save failed, image id $one_new_image_id is NOT READY. Fail.");
			return 0;
		}	
		sleep $sleep;
		$wait_time = $wait_time - $sleep;
	}
	
	return 0;
	
}



=head2 power_off

 Parameters  : 'hard' (optional), execute shutdown-hard
 Returns     : 
 Description : send 'shutdown' to VM via controller.
				* need to add check if VM is OFF. Sometimes VM won't power off *

=cut

sub power_off {
	my $self = shift;
	unless (ref($self) && $self->isa('VCL::Module')) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $action = shift;
	my $computer_name = $self->data->get_computer_hostname();
	my $vmid = $self->_one_get_object_id("computer",$computer_name);
	my @poweroff;
	
	if (defined($action) and $action eq 'hard') {
		@poweroff = $one{'server'}->call('one.vm.action', $one{'auth'},'shutdown-hard',$vmid);
	} else {
		@poweroff = $one{'server'}->call('one.vm.action', $one{'auth'},'shutdown',$vmid);
	}
	
	if ( $poweroff[0][0]->value() ) {
		notify($ERRORS{'OK'}, 0, "Sent shutdown signal to VM $vmid");
		return 1;
	} else {
		notify($ERRORS{'DEBUG'}, 0, $poweroff[0][1]);
		return 0;
	}
	
} ## end sub power_off

=head2 power_reset
=cut

sub power_reset() {
	my $self = shift;
	unless (ref($self) && $self->isa('VCL::Module')) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $computer_name = $self->data->get_computer_hostname();
	my $vmid = $self->_one_get_object_id("computer",$computer_name);
	my @poweroff = $one{'server'}->call('one.vm.action', $one{'auth'},'reboot',$vmid);
	
	if ( $poweroff[0][0]->value() ) {
		notify($ERRORS{'OK'}, 0, "Sent reboot signal to VM $vmid");
		return 1;
	} else {
		notify($ERRORS{'CRITICAL'}, 0, $poweroff[0][1]);
		return 0;
	}
}



#/////////////////////////////////////////////////////////////////////////////

=head2 power_status

 Parameters  : $domain_name (optional)
 Returns     : string
 Description : Determines the power state of the domain. A string is returned
               containing one of the following values:
                  * 'on'
                  * 'off'
                  * 'suspended'
=cut

sub power_status {
	my $self = shift;
	unless (ref($self) && $self->isa('VCL::Module')) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $computer_name = $self->data->get_computer_hostname();
	my $vmid = $self->_one_get_object_id("computer",$computer_name);
	
	my @result = $one{'server'}->call('one.vm.info', $one{'auth'},$vmid);
	if ( $result[0][0]->value() ) {
		my $data = $xml->XMLin($result[0][1]);
		if ($data->{STATE} == 3) {
			if ($data->{LCM_STATE} == 3) {
				notify($ERRORS{'OK'}, 0, "vm $vmid is RUNNING, STATE=3 and LCM_STATE=3");
				return 'on';
			}
		}
	} else {
		notify($ERRORS{'CRITICAL'}, 0, $result[0][1]);
		return 0;
	}

	return 'off';
	
} ## end sub power_status

=head2 power_on

 Parameters  : $domain_name (optional)
 Returns     : string
 Description : powers on VM
=cut

sub power_on {
	my $self = shift;
	unless (ref($self) && $self->isa('VCL::Module')) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	my $computer_name = $self->data->get_computer_hostname();
	my $vmid = $self->_one_get_object_id("computer",$computer_name);
	#later
	return 1;
	
} ## end sub power_on
#/////////////////////////////////////////////////////////////////////////////

=head2 new

 Parameters  : 
 Returns     : 
 Description : 
=cut


sub get_image_size {
	my $self = shift;
	unless (ref($self) && $self->isa('VCL::Module')) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	return $self->_one_get_image_tag_value($self->data->get_image_name(),"SIZE");

}

sub _one_get_image_tag_value {
	my $self = shift;
	my $image_name = shift;
	my $tag = shift;
	
	my $imid = $self->_one_get_object_id("image",$image_name);
	my @result = $one{server}->call('one.image.info',$one{'auth'},$imid);
	if ( $result[0][0]->value() ) {
		my $data = $xml->XMLin($result[0][1]);
		
		if ($tag eq 'SIZE') {
			if (defined($data->{$tag})) {
				return $data->{$tag};
			} else {
				return 0;
			}
		}
		
		if ($tag eq 'SWAP') {
			if (defined($data->{TEMPLATE}{$tag})) {
				return $data->{TEMPLATE}{$tag};
			} else {
				return 0;
			}
		}
		
		if ($tag eq 'DEV_PREFIX') {
			if (defined($data->{TEMPLATE}{$tag})) {
				return $data->{TEMPLATE}{$tag};
			} else {
				return 0;
			}
		}
		
	} else {
		notify($ERRORS{'CRITICAL'}, 0, "Error while making one.image.info call: $result[0][1]");
		return 0;
	}
	notify($ERRORS{'CRITICAL'},0,"requested tag = $tag, don't know how to get it...");
	return 0;
}

sub _one_get_template_id {
	# in: $template_name
	# out: $template_id or 0
	my $self = shift;
	my $template_name = shift;
	
	my @templatepool_info = $one{'server'}->call('one.templatepool.info',$one{'auth'},-1,-1,-1);
	if ($templatepool_info[0][0]->value()) {
		my $data = XMLin($templatepool_info[0][1]);
		
		#print Dumper($data);
		
		if (ref($data->{VMTEMPLATE}) eq "ARRAY") {
			foreach (@{$data->{VMTEMPLATE}}) {
				notify($ERRORS{'OK'}, 0, "Looking for template $template_name in template ID ".$_->{ID}.", name ".$_->{NAME}."...");
				if ($_->{NAME} eq $template_name) {
					notify($ERRORS{'OK'}, 0, "Found template ".$template_name." with ID ".$_->{ID});
					return $_->{ID};
				}
			}
		} else { #HASH, single entry
			
			unless (defined($data->{VMTEMPLATE}{NAME})) {
				notify($ERRORS{'WARNING'}, 0, "Template not found: $template_name");
				return 0;
			}
			
			if ($data->{VMTEMPLATE}{NAME} eq $template_name) {
				notify($ERRORS{'OK'}, 0, "Found template ".$template_name." with ID ".$data->{VMTEMPLATE}{ID});
				return $data->{VMTEMPLATE}{ID};
			}
		}
	} else {
		notify($ERRORS{'WARNING'}, 0, "Error while making one.templatepool.info call: $templatepool_info[0][1]");
	}
	notify($ERRORS{'WARNING'}, 0, "No value found after one.templatepool.info call for template $template_name");
	return 0;
}



#/////////////////////////////////////////////////////////////////////////////
=head2 _one_get_virtio

 Parameters  : imagename
 Returns     : '' or MODEL="virtio"
 Description : 

=cut

sub _one_get_virtio {
	my $self = shift;
	my $one_image_id = shift;
	
	my @reply = $one{'server'}->call('one.image.info',$one{'auth'},$one_image_id);
	
	if ( $reply[0][0]->value() ) {
		my $data = $xml->XMLin($reply[0][1]);
		if ($data->{TEMPLATE}{DEV_PREFIX} eq 'vd' ) {
			return ',MODEL="virtio"';
		}
	} else {
		notify($ERRORS{'WARNING'}, 0, "couldn't get image configuration for image_id $one_image_id, won't use VIRTIO driver.");
		return '';
	}
	
	return '';
}

#/////////////////////////////////////////////////////////////////////////////

=head2 one_wait_for_vm_status

 Parameters  : 
 Returns     : 
 Description : 

=cut

sub _one_wait_for_vm_state {
	my $self = shift;
	my $vmid = shift;
	my $state = shift;
	my $wait = shift;
	my $sleep = 15;
	my $num_state = 0;
	
	# 6 - POWEROFF
	
	$num_state = 6 if ($state eq "SHUTDOWN");
	
	
	if (!$num_state) {
		notify($ERRORS{'CRITICAL'}, 0, "Unknown vm_state $state requested");
		return 0;
	}
	
	while (1) {
		notify($ERRORS{'OK'}, 0, "Check state of VM $vmid ...");
		my $ttime;
		my $one_vm_state = $self->_one_get_vm_state($vmid);
		if ( $self->_one_get_vm_state($vmid) == $num_state ) {
			notify($ERRORS{'OK'}, 0, "VM $vmid is in $state state");
			return 1;
		} else {
			notify($ERRORS{'OK'}, 0, "VM $vmid is NOT in $state state. Waiting $sleep sec...");
			sleep $sleep;
			$ttime = $ttime + $sleep;
			if ($ttime >= $wait) {
				notify($ERRORS{'CRITICAL'}, 0, "VM $vmid is still NOT in $state state after $wait sec, abort");
				last;
			}
		}
	}
	
	return 0;
	
} ## end sub one_wait_for_vm_status

# 
sub _one_get_vm_state {
	my $self = shift;
	my $vmid = shift;
	
	my @result = $one{'server'}->call('one.vm.info', $one{'auth'},$vmid);
	if ( $result[0][0]->value() ) {
		my $data = $xml->XMLin($result[0][1]);
		return $data->{STATE}; 
	} else {
		notify($ERRORS{'CRITICAL'}, 0, $result[0][1]);
		return 0;
	}
}

# gets LCM_STATE values, this sub-state is relevant only when STATE is ACTIVE (3)
sub _one_get_vm_lcm_state {
	my $self = shift;
	my $vmid = shift;
	
	my @result = $one{'server'}->call('one.vm.info', $one{'auth'},$vmid);
	if ( $result[0][0]->value() ) {
		my $data = $xml->XMLin($result[0][1]);
		if ($data->{STATE} == 3) {
			return $data->{LCM_STATE}; 
		} else {
			notify($ERRORS{'DEBUG'}, 0, "Cannot return LCM_STATE of VM $vmid, VM's STATE is not ACTIVE");
			return;
		}
	} else {
		notify($ERRORS{'CRITICAL'}, 0, $result[0][1]);
		return 0;
	}
}

sub _one_get_image_state {
	my $self = shift;
	my $image_id = shift;
	
	my @status = $one{'server'}->call('one.image.info',$one{'auth'},$image_id);
	if ( $status[0][0]->value() ) {
		my $data = $xml->XMLin($status[0][1]);
		return $data->{STATE};
	} else {
		notify($ERRORS{'CRITICAL'}, 0, $status[0][1]);
	}
	
}

=head2 _one_get_object_id

 Parameters  : $o_type, $o_name
 Returns     : ONE Object ID (INT)
 Description : 

=cut

sub _one_get_object_id {
	my $self = shift;	
	my $o_type = shift;
	my $o_name = shift;
	
	if ($o_type eq "computer") {
		notify($ERRORS{'OK'}, 0, "Searching for running VM $o_name ...");
		my @reply = $one{'server'}->call('one.vmpool.info',$one{'auth'},-3,-1,-1,-1);
		if ( $reply[0][0]->value() ) {
			
			my $data = $xml->XMLin($reply[0][1]);
			
			if ( (ref($data->{VM})) eq "ARRAY"){
				foreach (@{$data->{VM}}) {
					if ($_->{NAME} =~ /^$o_name\s/) {
						notify($ERRORS{'OK'}, 0, "Found ".$_->{NAME}." matching $o_name in ARRAY");
						return $_->{ID};
					}
			  	}
			} else { #HASH, found only one entry
				unless ( defined($data->{VM}{NAME}) ) {return 0;}
				
				if ($data->{VM}{NAME} =~ /^$o_name\s/) {
					notify($ERRORS{'OK'}, 0, "Found ".$data->{VM}{NAME}." matching $o_name in HASH");
					return $data->{VM}{ID};
			    }
			}
		} else {
			notify($ERRORS{'CRITICAL'}, 0, $reply[0][1]);
			return 0;
		}	
	} elsif ($o_type eq "image") {
		my @reply = $one{'server'}->call('one.imagepool.info', $one{'auth'},-3,-1,-1);
		if ( $reply[0][0]->value() ) {
			
			my $rs_data = $xml->XMLin($reply[0][1]);
				if ( (ref($rs_data->{IMAGE})) eq "ARRAY" ) {
					foreach (@{$rs_data->{IMAGE}}) {
						if ($_->{NAME} eq $o_name) {
 							return $_->{ID};
						}
					}
					} else { #HASH, only one entry
						if ($rs_data->{IMAGE}{NAME} eq $o_name) {
							return $rs_data->{IMAGE}{ID};
						}
					}
		} else {
			notify($ERRORS{'CRITICAL'}, 0, $reply[0][1]);
			return 0;
		}
	} elsif ($o_type eq "network") {
		my @reply = $one{'server'}->call('one.vnpool.info',$one{'auth'},-1,-1,-1);
		if ($reply[0][0]->value()) {
			my $rs_data = $xml->XMLin($reply[0][1]);
			# don't check if ARRAY or HASH since we always have more then 1 network
			foreach (@{$rs_data->{VNET}}) {
				# if $o_name is in VLAN_ID= then lookup by VLAN_ID, not NAME
				
				if ($o_name =~ /^VLAN_ID=/i) {
					my @vlan_id = split('=',$o_name);
					if ($_->{VLAN_ID} == $vlan_id[1]) {
						return $_->{ID};
					}
				} else {
					if ($_->{NAME} eq $o_name) {
						return $_->{ID};
					}
				}
			}
		} else {
			notify($ERRORS{'CRITICAL'},0,$reply[0][1]);
			return 0;
		}
	} else {
		notify($ERRORS{'CRITICAL'}, 0, "$o_type is UNKNOWN type");
		return 0;
	} 
	
	return 0;	
} 

#/////////////////////////////////////////////////////////////////////////////

=head2 _one_delete_vm

 Parameters  : $vmid
 Returns     : 
 Description : one.vm.action

=cut

sub _one_delete_vm {
	my $self = shift;
	my $vmid = shift;
	my @reply;
	
	@reply = $one{'server'}->call('one.vm.action', $one{'auth'},'delete',$vmid);
	if ( $reply[0][0]->value() ) {
		notify($ERRORS{'OK'}, 0, "ONE VM $vmid deleted");
	} else {
		notify($ERRORS{'CRITICAL'}, 0, $reply[0][1]);
	}
	
} 

sub _one_create_vm {
	# in: $VM_TEMPLATE in XML
	# out: new VM ID | 0
	my $self = shift;
	my $VM_TEMPLATE = shift;
	my @vm_allocate = $one{'server'}->call('one.vm.allocate',$one{'auth'},$VM_TEMPLATE,$one{'false'});
	if ( $vm_allocate[0][0]->value() ) {	
		return $vm_allocate[0][1];
	} else {
		notify($ERRORS{'CRITICAL'}, 0, "Error while making one.vm.allocate call : $vm_allocate[0][1]");
		return 0;
	}

}

sub _one_create_template {
	# in: VM_TEMPLATE
	# OUT: template_id | 0
	# http://opennebula.org/documentation:rel4.2:api#onetemplateallocate
	my $self = shift;
	my $VM_TEMPLATE = shift;
	
	my @template_allocate = $one{'server'}->call('one.template.allocate',$one{'auth'},$VM_TEMPLATE);
	if ($template_allocate[0][0]->value()) {
		notify($ERRORS{'OK'}, 0, "New template created with id $template_allocate[0][1]");
	} else {
		notify($ERRORS{'CRITICAL'}, 0, "Error while making one.template.allocate call : $template_allocate[0][1]");
	}	
}

sub _one_get_vm_disk {
	my $self = shift;
	my $vmid = shift;
	
	my @vm_info = $one{'server'}->call('one.vm.info', $one{'auth'},$vmid);
	if ($vm_info[0][0]->value()) {
		my $data = $xml->XMLin($vm_info[0][1]);
		
		if ( (ref($data->{TEMPLATE}{DISK})) eq "ARRAY" ) { # template has multiple disks, return [0]
			return $data->{TEMPLATE}{DISK}[0]{IMAGE};
		} else { #template has one disk
			return $data->{TEMPLATE}{DISK}{IMAGE};
		}
		
		
		
	} else {
		notify($ERRORS{'CRITICAL'}, 0, "Error while making one.vm.info call : $vm_info[0][1]");
	}
}
1;
__END__

