#!/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
#
# Tests for device-structure correctness while snapshottting.
# Test1: Compatibility volume.
# Test2: Raw EVMS volume.
# Test3: Feature EVMS volume.

use strict;
use warnings;

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

my @segments;

# Setup.
# Create two DOS segments on the disk.
sub Setup
{
	my $disk = $_[0];
	my $freespace = $disk . "_freespace1";
	my $rc;

	log_info("1. Assigning DOS to disk $disk.\n");

	$rc = assign_dos_plugin($disk);
	if ($rc) {
		log_error("Error assigning DOS to disk $disk.\n");
		goto out;
	}

	log_info("2. Creating two DOS primary segments on disk $disk.\n");

	$rc = create_dos_primary_segment($freespace, "200MB");
	if ($rc) {
		log_error("Error creating first DOS segment on disk $disk.\n");
		goto out;
	}

	$rc = create_dos_primary_segment($freespace, "250MB");
	if ($rc) {
		log_error("Error creating second DOS segment on disk $disk.\n");
		goto out;
	}

	$segments[0] = $disk . "1";
	$segments[1] = $disk . "2";

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

# Test1
# Test the device-structure correctness when taking a snapshot of a
# compatibility volume.
sub Test1
{
	my $org_child = $segments[0];
	my $org_parent = $org_child . "#origin#";
	my $org_volume = "/dev/evms/" . $org_child;
	my $snap_child = $segments[1];
	my $snap_parent = "snap1";
	my $snap_vol_name = "Snap1";
	my $snap_volume = "/dev/evms/" . $snap_vol_name;
	my (@org_volume_devnum_pre, @org_volume_devnum_post);
	my (@org_child_devnum_pre, @org_child_devnum_post);
	my ($org_child_table_pre, $org_child_table_post);
	my @org_parent_devnum;
	my $org_parent_table;
	my %options;
	my $rc;

	log_info("Test device-structure for snapshots of " .
		 "compatibility volumes.\n");

	log_info("1. Creating compatibility volume from " .
		 "segment $org_child.\n");

	$rc = create_compatibility_volume($org_child);
	if ($rc) {
		log_error("Error creating compatibility volume.\n");
		goto out;
	}

	log_info("2. Getting origin device details before snapshotting.\n");

	@org_volume_devnum_pre = get_blkdev_major_minor($org_volume);
	@org_child_devnum_pre = get_blkdev_major_minor($org_child);
	($org_child_table_pre) = dm_table($org_child);

	log_info("3. Verifying origin volume and child details match.\n");

	if (!($org_volume_devnum_pre[0] == $org_child_devnum_pre[0] &&
	      $org_volume_devnum_pre[1] == $org_child_devnum_pre[1])) {
		log_error("Error: origin volume and child have different " .
			  "device numbers.\n");
		log_error("       volume: $org_volume_devnum_pre[0]:$org_volume_devnum_pre[1], " .
			  "child: $org_child_devnum_pre[0]:$org_child_devnum_pre[1]\n");
		$rc = 1;
		goto out;
	}

	log_info("4. Creating snapshot of volume $org_volume.\n");

	$options{"original"} = $org_volume;
	$options{"snapshot"} = $snap_parent;
	$rc = create_snapshot($snap_child, \%options);
	if ($rc) {
		log_error("Error creating snapshot object $snap_parent.\n");
		goto out;
	}

	$rc = create_evms_volume($snap_parent, $snap_vol_name);
	if ($rc) {
		log_error("Error creating volume from snapshot " .
			  "object $snap_parent.\n");
		goto out;
	}

	log_info("5. Getting origin device details after snapshotting.\n");

	@org_volume_devnum_post = get_blkdev_major_minor($org_volume);
	@org_child_devnum_post = get_blkdev_major_minor($org_child);
	@org_parent_devnum = get_blkdev_major_minor($org_parent);
	($org_child_table_post) = dm_table($org_child);
	($org_parent_table) = dm_table($org_parent);

	log_info("6. Verifying origin volume device number has not changed.\n");

	if (!($org_volume_devnum_pre[0] == $org_volume_devnum_post[0] &&
	      $org_volume_devnum_pre[1] == $org_volume_devnum_post[1])) {
		log_error("Error: origin volume device number has changed.\n");
		log_error("       before: $org_volume_devnum_pre[0]:$org_volume_devnum_pre[1], " .
			  "after: $org_volume_devnum_post[0]:$org_volume_devnum_post[1]\n");
		$rc = 1;
		goto out;
	}

	log_info("7. Verifying origin parent took the child device number.\n");

	if (!($org_parent_devnum[0] == $org_child_devnum_pre[0] &&
	      $org_parent_devnum[1] == $org_child_devnum_pre[1])) {
		log_error("Error: origin parent has incorrect device number.\n");
		log_error("       has: $org_parent_devnum[0]:$org_parent_devnum[1], " .
			  "should have: $org_child_devnum_pre[0]:$org_child_devnum_pre[1]\n");
		$rc = 1;
		goto out;
	}

	log_info("8. Verifying origin child got a new device number.\n");

	if ($org_child_devnum_post[0] == $org_child_devnum_pre[0] &&
	    $org_child_devnum_post[1] == $org_child_devnum_pre[1]) {
		log_error("Error: origin child has the same device number: " .
			  "$org_child_devnum_post[0]:$org_child_devnum_post[1]\n");
		$rc = 1;
		goto out;
	}

	log_info("9. Verifying origin child has the same mapping.\n");

	if ($org_child_table_pre ne $org_child_table_post) {
		log_error("Error: origin child mapping has changed.\n");
		log_error("       before: $org_child_table_pre\n");
		log_error("       after:  $org_child_table_post\n");
		$rc = 1;
		goto out;
	}

	log_info("10. Verifying origin parent maps to origin child.\n");

	if ($org_parent_table =~ /\d+\s+\d+\s+snapshot-origin\s+(\d+):(\d+)/) {
		if (!($1 == $org_child_devnum_post[0] &&
		      $2 == $org_child_devnum_post[1])) {
			log_error("Error: origin parent maps to wrong device.\n");
			log_error("       maps to: $1:$2, should map to: " .
				  "$org_child_devnum_post[0]:$org_child_devnum_post[1]\n");
			$rc = 1;
			goto out;
		}
	} else {
		log_error("Error: origin parent has incorrect mapping.\n");
		log_error("       $org_parent_table\n");
		$rc = 1;
		goto out;
	}

out:
	delete_thing($snap_volume);
	delete_thing($snap_parent);
	delete_thing($org_volume);
	log_result($rc);
	return $rc;
}

# Test2
# Test the device-structure correctness when taking a snapshot of a
# "raw" EVMS volume. This means an EVMS volume directly on a segment
# or region.
sub Test2
{
	my $org_child = $segments[0];
	my $org_parent = $org_child . "#origin#";
	my $org_vol_name = "Org2";
	my $org_volume = "/dev/evms/" . $org_vol_name;
	my $snap_child = $segments[1];
	my $snap_parent = "snap2";
	my $snap_vol_name = "Snap2";
	my $snap_volume = "/dev/evms/" . $snap_vol_name;
	my (@org_child_devnum_pre, @org_child_devnum_post);
	my (@org_volume_devnum_pre, @org_volume_devnum_post);
	my ($org_child_table_pre, $org_child_table_post);
	my ($org_volume_table_pre, $org_volume_table_post);
	my @org_parent_devnum;
	my $org_parent_table;
	my %options;
	my $rc;

	log_info("Test device-structure for snapshots of " .
		 "\"raw\" EVMS volumes.\n");

	log_info("1. Creating EVMS volume from segment $org_child.\n");

	$rc = create_evms_volume($org_child, $org_vol_name);
	if ($rc) {
		log_error("Error creating EVMS volume $org_volume.\n");
		goto out;
	}

	log_info("2. Getting origin device details before snapshotting.\n");

	@org_volume_devnum_pre = get_blkdev_major_minor($org_volume);
	@org_child_devnum_pre = get_blkdev_major_minor($org_child);
	($org_volume_table_pre) = dm_table($org_vol_name);
	($org_child_table_pre) = dm_table($org_child);

	log_info("3. Verifying origin volume maps to child.\n");

	if ($org_volume_table_pre =~ /\d+\s+\d+\s+linear\s+(\d+):(\d+)\s+0/) {
		if (!($1 == $org_child_devnum_pre[0] &&
		      $2 == $org_child_devnum_pre[1])) {
			log_error("Error: origin volume maps to wrong device.\n");
			log_error("       maps to: $1:$2, should map to: " .
				  "$org_child_devnum_pre[0]:$org_child_devnum_pre[1]\n");
			$rc = 1;
			goto out;
		}
	} else {
		log_error("Error: origin volume has incorrect mapping.\n");
		log_error("       $org_volume_table_pre\n");
		$rc = 1;
		goto out;
	}

	log_info("4. Creating snapshot of volume $org_volume.\n");

	$options{"original"} = $org_volume;
	$options{"snapshot"} = $snap_parent;
	$rc = create_snapshot($snap_child, \%options);
	if ($rc) {
		log_error("Error creating snapshot object $snap_parent.\n");
		goto out;
	}

	$rc = create_evms_volume($snap_parent, $snap_vol_name);
	if ($rc) {
		log_error("Error creating volume from snapshot " .
			  "object $snap_parent.\n");
		goto out;
	}

	log_info("5. Getting origin device details after snapshotting.\n");

	@org_volume_devnum_post = get_blkdev_major_minor($org_volume);
	@org_child_devnum_post = get_blkdev_major_minor($org_child);
	@org_parent_devnum = get_blkdev_major_minor($org_parent);
	($org_volume_table_post) = dm_table($org_vol_name);
	($org_child_table_post) = dm_table($org_child);
	($org_parent_table) = dm_table($org_parent);

	log_info("6. Verifying origin volume device number and mapping " .
		 "have not changed.\n");

	if (!($org_volume_devnum_pre[0] == $org_volume_devnum_post[0] &&
	      $org_volume_devnum_pre[1] == $org_volume_devnum_post[1])) {
		log_error("Error: origin volume device number has changed.\n");
		log_error("       before: $org_volume_devnum_pre[0]:$org_volume_devnum_pre[1], " .
			  "after: $org_volume_devnum_post[0]:$org_volume_devnum_post[1]\n");
		$rc = 1;
		goto out;
	}

	if ($org_volume_table_pre ne $org_volume_table_post) {
		log_error("Error: origin volume mapping has changed.\n");
		log_error("       before: $org_volume_table_pre\n");
		log_error("       after:  $org_volume_table_post\n");
		$rc = 1;
		goto out;
	}

	log_info("7. Verifying origin parent took the child device number.\n");

	if (!($org_parent_devnum[0] == $org_child_devnum_pre[0] &&
	      $org_parent_devnum[1] == $org_child_devnum_pre[1])) {
		log_error("Error: origin parent has incorrect device number.\n");
		log_error("       has: $org_parent_devnum[0]:$org_parent_devnum[1], " .
			  "should have: $org_child_devnum_pre[0]:$org_child_devnum_pre[1]\n");
		$rc = 1;
		goto out;
	}

	log_info("8. Verifying origin child got a new device number.\n");

	if ($org_child_devnum_post[0] == $org_child_devnum_pre[0] &&
	    $org_child_devnum_post[1] == $org_child_devnum_pre[1]) {
		log_error("Error: origin child has the same device number: " .
			  "$org_child_devnum_post[0]:$org_child_devnum_post[1]\n");
		$rc = 1;
		goto out;
	}

	log_info("9. Verifying origin child has the same mapping.\n");

	if ($org_child_table_pre ne $org_child_table_post) {
		log_error("Error: origin child mapping has changed.\n");
		log_error("       before: $org_child_table_pre\n");
		log_error("       after:  $org_child_table_post\n");
		$rc = 1;
		goto out;
	}

	log_info("10. Verifying origin parent maps to origin child.\n");

	if ($org_parent_table =~ /\d+\s+\d+\s+snapshot-origin\s+(\d+):(\d+)/) {
		if (!($1 == $org_child_devnum_post[0] &&
		      $2 == $org_child_devnum_post[1])) {
			log_error("Error: origin parent maps to wrong device.\n");
			log_error("       maps to: $1:$2, should map to: " .
				  "$org_child_devnum_post[0]:$org_child_devnum_post[1]\n");
			$rc = 1;
			goto out;
		}
	} else {
		log_error("Error: origin parent has incorrect mapping.\n");
		log_error("       $org_parent_table\n");
		$rc = 1;
		goto out;
	}

out:
	delete_thing($snap_volume);
	delete_thing($snap_parent);
	delete_thing($org_volume);
	log_result($rc);
	return $rc;
}

# Test3
# Test the device-structure correctness when taking a snapshot of a
# "full" EVMS volume. This means an EVMS volume on an EVMS feature
# object.
sub Test3
{
	my $org_segment = $segments[0];
	my $org_child = "link3";
	my $org_parent = $org_child . "#origin#";
	my $org_vol_name = "Org3";
	my $org_volume = "/dev/evms/" . $org_vol_name;
	my $snap_child = $segments[1];
	my $snap_parent = "snap3";
	my $snap_vol_name = "Snap3";
	my $snap_volume = "/dev/evms/" . $snap_vol_name;
	my (@org_volume_devnum_pre, @org_volume_devnum_post);
	my (@org_child_devnum_pre, @org_child_devnum_post);
	my ($org_child_table_pre, $org_child_table_post);
	my @org_parent_devnum;
	my $org_parent_table;
	my @objects = ($org_segment);
	my %options;
	my $rc;

	log_info("Test device-structure for snapshots of " .
		 "\"full\" EVMS volumes.\n");

	log_info("1. Creating drivelink object from segment $org_segment.\n");

	$options{"Name"} = $org_child;
	@objects = create_object("DriveLink", \@objects, \%options);
	if (!@objects) {
		log_error("Error creating drivelink object.\n");
		$rc = 1;
		goto out;
	}

	log_info("2. Creating EVMS volume from object $org_child.\n");

	$rc = create_evms_volume($org_child, $org_vol_name);
	if ($rc) {
		log_error("Error creating EVMS volume $org_volume.\n");
		goto out;
	}

	log_info("3. Getting origin device details before snapshotting.\n");

	@org_volume_devnum_pre = get_blkdev_major_minor($org_volume);
	@org_child_devnum_pre = get_blkdev_major_minor($org_child);
	($org_child_table_pre) = dm_table($org_child);

	log_info("4. Verifying origin volume and child details match.\n");

	if (!($org_volume_devnum_pre[0] == $org_child_devnum_pre[0] &&
	      $org_volume_devnum_pre[1] == $org_child_devnum_pre[1])) {
		log_error("Error: origin volume and child have different " .
			  "device numbers.\n");
		log_error("       volume: $org_volume_devnum_pre[0]:$org_volume_devnum_pre[1], " .
			  "child: $org_child_devnum_pre[0]:$org_child_devnum_pre[1]\n");
		$rc = 1;
		goto out;
	}

	log_info("5. Creating snapshot of volume $org_volume.\n");

	$options{"original"} = $org_volume;
	$options{"snapshot"} = $snap_parent;
	$rc = create_snapshot($snap_child, \%options);
	if ($rc) {
		log_error("Error creating snapshot object $snap_parent.\n");
		goto out;
	}

	$rc = create_evms_volume($snap_parent, $snap_vol_name);
	if ($rc) {
		log_error("Error creating volume from snapshot " .
			  "object $snap_parent.\n");
		goto out;
	}

	log_info("6. Getting origin device details after snapshotting.\n");

	@org_volume_devnum_post = get_blkdev_major_minor($org_volume);
	@org_child_devnum_post = get_blkdev_major_minor($org_child);
	@org_parent_devnum = get_blkdev_major_minor($org_parent);
	($org_child_table_post) = dm_table($org_child);
	($org_parent_table) = dm_table($org_parent);

	log_info("7. Verifying origin volume device number has not changed.\n");

	if (!($org_volume_devnum_pre[0] == $org_volume_devnum_post[0] &&
	      $org_volume_devnum_pre[1] == $org_volume_devnum_post[1])) {
		log_error("Error: origin volume device number has changed.\n");
		log_error("       before: $org_volume_devnum_pre[0]:$org_volume_devnum_pre[1], " .
			  "after: $org_volume_devnum_post[0]:$org_volume_devnum_post[1]\n");
		$rc = 1;
		goto out;
	}


	log_info("8. Verifying origin parent took the child device number.\n");

	if (!($org_parent_devnum[0] == $org_child_devnum_pre[0] &&
	      $org_parent_devnum[1] == $org_child_devnum_pre[1])) {
		log_error("Error: origin parent has incorrect device number.\n");
		log_error("       has: $org_parent_devnum[0]:$org_parent_devnum[1], " .
			  "should have: $org_child_devnum_pre[0]:$org_child_devnum_pre[1]\n");
		$rc = 1;
		goto out;
	}

	log_info("9. Verifying origin child got a new device number.\n");

	if ($org_child_devnum_post[0] == $org_child_devnum_pre[0] &&
	    $org_child_devnum_post[1] == $org_child_devnum_pre[1]) {
		log_error("Error: origin child has the same device number: " .
			  "$org_child_devnum_post[0]:$org_child_devnum_post[1]\n");
		$rc = 1;
		goto out;
	}

	log_info("10. Verifying origin child has the same mapping.\n");

	if ($org_child_table_pre ne $org_child_table_post) {
		log_error("Error: origin child mapping has changed.\n");
		log_error("       before: $org_child_table_pre\n");
		log_error("       after:  $org_child_table_post\n");
		$rc = 1;
		goto out;
	}

	log_info("11. Verifying origin parent maps to origin child.\n");

	if ($org_parent_table =~ /\d+\s+\d+\s+snapshot-origin\s+(\d+):(\d+)/) {
		if (!($1 == $org_child_devnum_post[0] &&
		      $2 == $org_child_devnum_post[1])) {
			log_error("Error: origin parent maps to wrong device.\n");
			log_error("       maps to: $1:$2, should map to: " .
				  "$org_child_devnum_post[0]:$org_child_devnum_post[1]\n");
			$rc = 1;
			goto out;
		}
	} else {
		log_error("Error: origin parent has incorrect mapping.\n");
		log_error("       $org_parent_table\n");
		$rc = 1;
		goto out;
	}

out:
	delete_thing($snap_volume);
	delete_thing($snap_parent);
	delete_thing($org_volume);
	delete_thing($org_child);
	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");

	# Check for minimum-sized disk.
	$rc = check_minimum_object_size($disk, "500MB");
	if ($rc) {
		goto finish;
	}

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

	$rc = Test1();
	if ($rc) {
		goto finish;
	}

	$rc = Test2();
	if ($rc) {
		goto finish;
	}

	$rc = Test3();
	if ($rc) {
		goto finish;
	}

finish:
}

