#!/usr/bin/perl
#
# (C) Copyright IBM Corp. 2004
#
# This program is free software;  you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY;  without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
# the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program;  if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Engine tests for on-line replace

use strict;
use warnings;

use Evms::Common;
use Evms::Log;
use Evms::Dos;
use Evms::Volume;
use Evms::Object;


# Setup
# Assign the DOS Segment Manager to the disk.
# Make three segments of size n, n*2, and n*3, where n <= 100MB.
sub Setup
{
	my $disk = $_[0];
	my $rc;
	my $freespace;
	my $n;
	my $max_size = "100MB";
	my $sector_size;
	my $size;

	log_info("Assign DOS Segment Manager to disk $disk.\n");
	$rc = assign_dos_plugin($disk);
	if ($rc) {
		log_error("Error assigning DOS Segment Manager to disk $disk.\n");
		goto setup_out;
	}

	# Make four segments each no bigger than the max size.
	$freespace = "$disk" . "_freespace1";
	
	$n = size_to_sectors(get_object_size($freespace)) / 6;
	
	if ($n > size_to_sectors($max_size)) {
		$n = size_to_sectors($max_size);
	}

	$size = sectors_to_size($n);
	log_info("Create a $size segment.\n");
	$rc = create_dos_logical_segment($freespace, $size);
	if ($rc) {
		log_error("Error creating logical segment from $freespace.\n");
		goto setup_out;
	}

	$size = sectors_to_size($n * 2);
	log_info("Create a $size segment.\n");
	$freespace = "$disk" . "_freespace2";
	$rc = create_dos_logical_segment($freespace, $size);
	if ($rc) {
		log_error("Error creating logical segment from $freespace.\n");
		goto setup_out;
	}

	$size = sectors_to_size($n * 3);
	log_info("Create a $size segment.\n");
	$freespace = "$disk" . "_freespace2";
	$rc = create_dos_logical_segment($freespace, $size);
	if ($rc) {
		log_error("Error creating logical segment from $freespace.\n");
		goto setup_out;
	}

setup_out:
	log_result($rc);
	return $rc;
}

# Replace the object of an EVMS volume.
#
# Expect object[1] >= object[0]
sub Test1
{
	my @object = @_;
	my $volume_name = "Gurgle";
	my $evms_volume = "/dev/evms/$volume_name";
	my $evms_volume_sectors;
	my $pid;
	my @output;
	my $line;
	my $rc = 0;
	my $filesystem = "";
	my $child = "";
	my $i;
	my @lines;

	log_info("Replace the object of an EVMS volume.\n");

	if (@object < 2) {
		log_error("This test requires 2 objects to work with.  It was given @object object(s).\n");
		return 1;
	}
	
	# The second object must be at least as big as the first in order
	# for the replace to work.
	if (get_blkdev_sectors($object[1]) < get_blkdev_sectors($object[0])) {
		log_error("This test requires the second object to be at least as big as the first object.\n");
		log_error("1st object $object[0] has " . get_blkdev_sectors($object[0]) . " sectors.\n");
		log_error("2nd object $object[1] has " . get_blkdev_sectors($object[1]) . " sectors.\n");
		return 1;
	}

	log_info("1. Create an EVMS volume named $volume_name from $object[0].\n");
	$rc = create_evms_volume($object[0], $volume_name);
	if ($rc) {
		log_error("Failed to create volume $evms_volume.\n");
		goto test1_out;
	}

	$evms_volume_sectors = get_blkdev_sectors($evms_volume);

	log_info("2. Write zeros to $evms_volume.\n");
	`dd if=/dev/zero of=$evms_volume bs=16384 2>&1`;

	log_info("3. Start a process writing a data pattern to $evms_volume.\n");
	$pid = fork();
	if (!defined($pid)) {
		log_error("fork() failed.\n");
		goto test1_out;
	}
	if ($pid == 0) {
		# Child process
		write_sequence($evms_volume, $evms_volume_sectors);
		exit 0;
	}

	log_info("4. Replace $object[0] with $object[1].\n");
	$rc = run_evms_command("replace:$object[0],$object[1]", \@output);
	if ($rc) {
		log_error("Error replacing $object[0] with $object[1].  Return code was $rc.\n");
		goto test1_out;
	}

	log_info("5. Verify that $object[1] is the child of volume $evms_volume.\n");
	$rc = run_evms_command("query:children,$evms_volume", \@output);
	if ($rc) {
		log_error("Error querying children of $evms_volume.\n");
		$rc = 1;
		goto test1_out;
	}

	($line) = grep(/Name:/, @output);
	if ($line =~ /Name:\s+(\S+)/) {
		if ($1 ne $object[1]) {
			log_error("$object[1] is not a child of $evms_volume.\n");
			log_error("$1 is the child of volume $evms_volume.\n");
			$rc = 1;
			goto test1_out;
		}
	}

	log_info("6. Wait for child process to exit.\n");
	waitpid($pid, 0);

	log_info("7. Verify the data pattern on $evms_volume.\n");
	validate_sequence($evms_volume, $evms_volume_sectors);
	if ($rc) {
		log_error("Error validating the data pattern on $evms_volume.  Return code was $rc.\n");
		goto test1_out;
	}

	clean_objects(@object);

test1_out:
	log_result($rc);
	return $rc;
}


# Replace the EVMS object of an EVMS volume with a segment.
#
# Expect input objects to have the sizes:
#   object[0]  n
#   object[1] 2n
#   object[2] 3n
sub Test2
{
	my @object = @_;
	my $volume_name = "Bubbles";
	my $evms_volume = "/dev/evms/$volume_name";
	my $evms_volume_sectors;
	my $drive_link_name = "Deb";
	my $drive_link;
	my $plugin = "DriveLink";
	my @create_objects;
	my %create_options = ("name" => $drive_link_name);
	my $drive_link_sectors;
	my $pid;
	my @output;
	my $line;
	my $rc = 0;
	my $filesystem = "";
	my $child = "";
	my $i;
	my @lines;

	log_info("Replace the EVMS object of an EVMS volume with a segment.\n");

	if (@object < 3) {
		log_error("This test requires 3 objects to work with.  It was given @object object(s).\n");
		return 1;
	}
	
	log_info("1. Create a drive link named $drive_link_name from objects $object[0] and $object[1].\n");
	$create_objects[0] = $object[0];
	$create_objects[1] = $object[1];
	($drive_link) = create_object($plugin, \@create_objects, \%create_options);

	if (!defined($drive_link) || ($drive_link ne $drive_link_name)) {
		log_error("Failed to create drive link object.\n");
		$rc = 1;
		goto test1_out;
	}

	$drive_link_sectors = get_blkdev_sectors($drive_link);

	# The third object must be at least as big as the drive link object
	# in order for the replace to work.
	if (get_blkdev_sectors($object[2]) < $drive_link_sectors) {
		log_error("This test requires the third object to be at least as big as the drive link created from the first and second objects.\n");
		log_error("1st object $object[0] has " . get_blkdev_sectors($object[0]) . " sectors.\n");
		log_error("2nd object $object[1] has " . get_blkdev_sectors($object[1]) . " sectors.\n");
		log_error("3rd object $object[2] has " . get_blkdev_sectors($object[2]) . " sectors.\n");
		log_error("DriveLink object $drive_link ($object[0], $object[1]) has " . get_blkdev_sectors($drive_link) . " sectors.\n");
		return 1;
	}

	log_info("2. Create an EVMS volume named $volume_name from $drive_link.\n");
	$rc = create_evms_volume($drive_link, $volume_name);
	if ($rc) {
		log_error("Failed to create volume $evms_volume.\n");
		goto test2_out;
	}

	log_info("3. Write zeros to $evms_volume.\n");
	`dd if=/dev/zero of=$evms_volume bs=16384 2>&1`;

	log_info("4. Start a process writing a new data pattern to $evms_volume.\n");
	$pid = fork();
	if (!defined($pid)) {
		log_error("fork() failed.\n");
		goto test1_out;
	}
	if ($pid == 0) {
		# Child process
		write_sequence($evms_volume, $evms_volume_sectors);
		exit 0;
	}

	log_info("5. Replace $drive_link with $object[2].\n");
	$rc = run_evms_command("replace:$drive_link,$object[2]", \@output);
	if ($rc) {
		log_error("Error replacing $drive_link with $object[2].  Return code was $rc.\n");
		goto test2_out;
	}

	log_info("6. Verify that $object[2] is the child of volume $evms_volume.\n");
	$rc = run_evms_command("query:children,$evms_volume", \@output);
	if ($rc) {
		log_error("Error querying children of $evms_volume.\n");
		$rc = 1;
		goto test2_out;
	}

	($line) = grep(/Name:/, @output);
	if ($line =~ /Name:\s+(\S+)/) {
		if ($1 ne $object[2]) {
			log_error("$object[2] is not a child of $evms_volume.\n");
			log_error("$1 is the child of volume $evms_volume.\n");
			$rc = 1;
			goto test2_out;
		}
	}


	log_info("7. Wait for child process to exit.\n");
	waitpid($pid, 0);

	log_info("8. Verify the data pattern on $evms_volume.\n");
	validate_sequence($evms_volume, $evms_volume_sectors);
	if ($rc) {
		log_error("Error validating the data pattern on $evms_volume.  Return code was $rc.\n");
		goto test1_out;
	}

	clean_objects(@object);

test2_out:
	log_result($rc);
	return $rc;
}


# Replace the segment of an EVMS volume with an EVMS object.
#
# Expect input objects to have the sizes:
#   object[0]  n
#   object[1] 2n
#   object[2] 3n
sub Test3
{
	my @object = @_;
	my $volume_name = "Jacques";
	my $evms_volume = "/dev/evms/$volume_name";
	my $evms_volume_sectors;
	my $drive_link_name = "Flo";
	my $drive_link;
	my $plugin = "DriveLink";
	my @create_objects;
	my %create_options = ("name" => $drive_link_name);
	my $drive_link_sectors;
	my $pid;
	my @output;
	my $line;
	my $rc = 0;
	my $filesystem = "";
	my $child = "";
	my $i;
	my @lines;

	log_info("Replace the segment of an EVMS volume with an EVMS object.\n");

	if (@object < 3) {
		log_error("This test requires 3 objects to work with.  It was given @object object(s).\n");
		return 1;
	}
	
	log_info("1. Create a drive link named $drive_link_name from objects $object[0] and $object[2].\n");
	$create_objects[0] = $object[0];
	$create_objects[1] = $object[2];
	($drive_link) = create_object($plugin, \@create_objects, \%create_options);

	if (!defined($drive_link) || ($drive_link ne $drive_link_name)) {
		log_error("Failed to create drive link object.\n");
		$rc = 1;
		goto test1_out;
	}

	$drive_link_sectors = get_blkdev_sectors($drive_link);

	# The drive link object must be at least as big as the third object
	# in order for the replace to work.
	if ($drive_link_sectors < get_blkdev_sectors($object[1])) {
		log_error("This test requires the drive link created from the first and third objects to be at least as big as the second object.\n");
		log_error("1st object $object[0] has " . get_blkdev_sectors($object[0]) . " sectors.\n");
		log_error("2nd object $object[1] has " . get_blkdev_sectors($object[1]) . " sectors.\n");
		log_error("3rd object $object[2] has " . get_blkdev_sectors($object[2]) . " sectors.\n");
		log_error("DriveLink object $drive_link ($object[0], $object[2]) has " . get_blkdev_sectors($drive_link) . " sectors.\n");
		return 1;
	}

	log_info("2. Create an EVMS volume named $volume_name from $object[1].\n");
	$rc = create_evms_volume($object[1], $volume_name);
	if ($rc) {
		log_error("Failed to create volume $evms_volume.\n");
		goto test3_out;
	}

	log_info("3. Write zeros to $evms_volume.\n");
	`dd if=/dev/zero of=$evms_volume bs=16384 2>&1`;

	log_info("4. Start a process writing a new data pattern to $evms_volume.\n");
	$pid = fork();
	if (!defined($pid)) {
		log_error("fork() failed.\n");
		goto test1_out;
	}
	if ($pid == 0) {
		# Child process
		write_sequence($evms_volume, $evms_volume_sectors);
		exit 0;
	}

	log_info("5. Replace $object[1] with $drive_link.\n");
	$rc = run_evms_command("replace:$object[1],$drive_link", \@output);
	if ($rc) {
		log_error("Error replacing $object[1] with $drive_link.  Return code was $rc.\n");
		goto test3_out;
	}

	log_info("6. Verify that $drive_link is the child of volume $evms_volume.\n");
	$rc = run_evms_command("query:children,$evms_volume", \@output);
	if ($rc) {
		log_error("Error querying children of $evms_volume.\n");
		$rc = 1;
		goto test3_out;
	}

	($line) = grep(/Name:/, @output);
	if ($line =~ /Name:\s+(\S+)/) {
		if ($1 ne $drive_link) {
			log_error("$drive_link is not a child of $evms_volume.\n");
			log_error("$1 is the child of volume $evms_volume.\n");
			$rc = 1;
			goto test3_out;
		}
	}


	log_info("7. Wait for child process to exit.\n");
	waitpid($pid, 0);

	log_info("8. Verify the data pattern on $evms_volume.\n");
	validate_sequence($evms_volume, $evms_volume_sectors);
	if ($rc) {
		log_error("Error validating the data pattern on $evms_volume.  Return code was $rc.\n");
		goto test1_out;
	}

	clean_objects(@object);

test3_out:
	log_result($rc);
	return $rc;
}


MAIN:
{
	my $disk;
	my $rc;

	# Only use the first disk specified.
	$disk = $ARGV[0];
	$disk || die("USAGE: $0 disk\n");

	$rc = Setup($disk);
	if ($rc) {
		goto finish;
	}

	$rc = Test1($disk.5, $disk.6);
	if ($rc) {
		goto finish;
	}
	
	$rc = Test2($disk.5, $disk.6, $disk.7);
	if ($rc) {
		goto finish;
	}
	
	$rc = Test3($disk.5, $disk.6, $disk.7);
	if ($rc) {
		goto finish;
	}
	
	clean_object($disk);

finish:
}


