#!/usr/bin/perl

# HostCheck
# May 21, 2013
# DrWaterman

use Getopt::Std;
use Data::Dumper;
use File::Basename;

use strict;
use warnings;
use Scalar::Util qw(looks_like_number);

#TODO
#-----------------------------------------------------
#  - How do we evaluate the RAID device?
#  - What is the required firmware revision for RAID device?
#  - What is the required firmware revision for DL380 server?
#  - Design easy upgrade method for supported hardware BOM
#  - How do we evaluate ILO version?
#  - How do we evalutate HP-ACU, version, etc?
#  - How do we evaluate the number of SAS ports
#  - What is the best method to evaluate HA-ready?
#  - How do we determine revision of ESXi server?
#  - What is the recommended vCPU clock speed?

#
# Program constants
#
use constant PROGRAM_NAME           => "HostCheck";
use constant PROGRAM_VERSION        =>  0.020;
use constant LOGFILE_NAME           => "/root/hostcheck.log";
use constant TRUE                   =>  1;
use constant FALSE                  =>  0;
use constant STATUS_SUCCESS         =>  0;  # Success
use constant STATUS_FAILURE         => 255;  # script failed
use constant STATUS_PRIVS_REQUIRED  => 254;  # sudo priv's required to run script
use constant STATUS_USAGE           => 253;  # script usage displayed
use constant STATUS_SYSTEM_COMMANDS => 252;  # Required system commands not found
use constant STATUS_INTERNAL_ERROR  => 251;  # Internal program error

# Program command line options:
#   D = print Discovered inventory
#   L = Do not create logFile
#   h = print usage
#   p = required argument <product-name>
#
use constant PROGRAM_OPTIONS  => 'DLhp:VS';

# These constants are used to set the bomMatch variable and
# provide an index into the bomMATCH list
use constant 	 CONFIG_STANDARD         => 0;
use constant     CONFIG_SUPPORTED        => 1;
use constant     CONFIG_UNSUPPORTED      => 2;
use constant     CONFIG_FAILED           => 3;
use constant     CONFIG_UNKNOWN          => 4;

use constant bomMATCH      =>
    qw/Standard Supported Unsupported Unacceptable Unknown/;

use constant REQUIRED_CMDS =>
    qw/hpacucli ifconfig cat parted nproc fdisk lspci vmstat lscpu biosdecode dmidecode virt-what/;

use constant PARAMETER_OVERRIDE_KEYWORDS =>
    qw/VIRTUAL_MACHINE CORES CPU_MODEL MEMORY_SIZE DRIVES DRIVE_SIZE NICS POWER_SUPPLIES WATTS/;

# KVM, OpenStack, RHEV only applicable for SBC Cloud deployments 
# TO DO: reconsider this list before merging to a release branch
# AWS gives Virtual for all platforms except C5 Instances.
use constant SUPPORTED_HARDWARE_HOSTS =>
    qw/DL380 VMware KVM Standard OpenStack RHEV RHEL HVM Virtual c5 c6i c6in R740 m5 Google/;

use constant  SLOT => 0;
use constant  CLASS => 1;
use constant  VENDOR => 2;
use constant  DEVICE => 3;

#
# Program globals
#
our %parameterOverrideDB;  # Allow override of inventory for debugging
our @overrideKeywordList = PARAMETER_OVERRIDE_KEYWORDS;
our $overrideInventory   = 0;

our $exitStatus = STATUS_SUCCESS; # Final status
our $bomValue   = CONFIG_UNKNOWN;
our $logFile    = LOGFILE_NAME;

our %TARGET_APPS = (
    PSX             => { config => 2 },
    PSXHA           => { config => 0 },
    EMS             => { config => 1 },
    EMSLEGACYHA     => { config => 3 },
    EMSRAC          => { config => 9 },
    SBC             => { config => 111 }, # TODO - Don't know what to do here...
    SWELITE         => { config => 112 }, 
    OAM             => { config => 113 },
    DSI             => { config => 1 },
    DSIHA           => { config => 3 },
    ADS             => { config => 2 },
    ASX             => { config => 11 },
    ADSRAC          => { config => 9 },
    SGX             => { config => 10 },
    NETSCORE        => { config => 1 },
);
our @targetApps = sort(keys %TARGET_APPS);

our @bomReport;
our @inventoryReport;
our %cmdlineOptions;
our $bomRef;           # Reference to configuration
our %systemCommand;    # System commands with discovered paths

our $licenseKey;

our $machine;
our $server;

#
# Program DataBases populated during discovery
#

our %cpuFlagsDB = (
    'FPU'    => { nm => "Floating-point unit on-chip",              value => 0 },
    'VME'    => { nm => "Virtual mode extension",                   value => 0 },
    'DE'     => { nm => "Debugging extension",                      value => 0 },
    'PSE'    => { nm => "Page size extension",                      value => 0 },
    'TSC'    => { nm => "Time stamp counter",                       value => 0 },
    'MSR'    => { nm => "Model specific registers",                 value => 0 },
    'PAE'    => { nm => "Physical address extension",               value => 0 },
    'MCE'    => { nm => "Machine check exception",                  value => 0 },
    'CX8'    => { nm => "CMPXCHG8 instruction supported",           value => 0 },
    'APIC'   => { nm => "On-chip APIC hardware supported",          value => 0 },
    'SEP'    => { nm => "Fast system call",                         value => 0 },
    'MTRR'   => { nm => "Memory type range registers",              value => 0 },
    'PGE'    => { nm => "Page global enable",                       value => 0 },
    'MCA'    => { nm => "Machine check architecture",               value => 0 },
    'CMOV'   => { nm => "Conditional move instruction supported",   value => 0 },
    'PAT'    => { nm => "Page attribute table",                     value => 0 },
    'PSE-36' => { nm => "36-bit page size extension",               value => 0 },
    'CLFSH'  => { nm => "CLFLUSH instruction supported",            value => 0 },
    'DS'     => { nm => "Debug store",                              value => 0 },
    'ACPI'   => { nm => "ACPI supported",                           value => 0 },
    'MMX'    => { nm => "MMX technology supported",                 value => 0 },
    'FXSR'   => { nm => "Fast floating-point save and restore",     value => 0 },
    'SSE'    => { nm => "Streaming SIMD extensions",                value => 0 },
    'SSE2'   => { nm => "Streaming SIMD extensions 2",              value => 0 },
    'SS'     => { nm => "Self-snoop",                               value => 0 },
    'HTT'    => { nm => "Hyper-threading technology",               value => 0 },
    'TM'     => { nm => "Thermal monitor supported",                value => 0 },
    'PBE'    => { nm => "Pending break enabled",                    value => 0 },
);

our %processorDB;

our %memoryDB = (
    'MemTotal'  => { nm => 'Total Memory',     value => 0 }, # GigaBytes
    'SwapTotal' => { nm => 'Total Swap space', value => 0 }, # MegaBytes
);

our @hdDB;          # List of References to HD devices
our @lanDB;         # List of ETH devices
our @pwrDB;         # List of Power Supplies
our @pciDB;         # List of PCI devices
our @raidDB;        # List of references to RAID drive information
our $raidDBext;     # Reference to hash of extended RAID information

our %dmidecodeDB = (   # Miscellaneous dmidecode information
    'bios-vendor'               => { nm => 'BIOS Vendor',                value => 'Unknown' },
    'bios-version'              => { nm => 'BIOS Version',               value => 'Unknown' },
    'bios-release-date'         => { nm => 'BIOS Release Date',          value => 'Unknown' },
    'system-manufacturer'       => { nm => 'System Manufacturer',        value => 'Unknown' },
    'system-product-name'       => { nm => 'System Product Name',        value => 'Unknown' },
    'system-version'            => { nm => 'System Version',             value => 'Unknown' },
    'system-serial-number'      => { nm => 'System Serial Number',       value => 'Unknown' },
    'system-uuid'               => { nm => 'System UUID',                value => 'Unknown' },
    'baseboard-manufacturer'    => { nm => 'Baseboard Manufacturer',     value => 'Unknown' },
    'baseboard-product-name'    => { nm => 'Baseboard Product Name',     value => 'Unknown' },
    'baseboard-version'         => { nm => 'Baseboard Version',          value => 'Unknown' },
    'baseboard-serial-number'   => { nm => 'Baseboard Serial Number',    value => 'Unknown' },
    'baseboard-asset-tag'       => { nm => 'Baseboard Asset Tag',        value => 'Unknown' },
    'chassis-manufacturer'      => { nm => 'Chassis Manufacturer',       value => 'Unknown' },
    'chassis-type'              => { nm => 'Chassis Type',               value => 'Unknown' },
    'chassis-version'           => { nm => 'Chassis Version',            value => 'Unknown' },
    'chassis-serial-number'     => { nm => 'Chassis Serial Number',      value => 'Unknown' },
    'chassis-asset-tag'         => { nm => 'Chassis Asset Tag',          value => 'Unknown' },
    'processor-family'          => { nm => 'Processor Family',           value => 'Unknown' },
    'processor-manufacturer'    => { nm => 'Processor Manufacturer',     value => 'Unknown' },
# TODO This fails to return anything printable on VMware and on
# vmlab1-1 an extra blank line is added to the end. Skip
# this one for now.
#   'processor-version'         => { nm => 'Processor Version',          value => 'Unknown' },
    'processor-frequency'       => { nm => 'Processor Frequency',        value => 'Unknown' },
);

our %dmidecodeBios = (
    'SMBIOS'            =>  { value => 'Unknown' },
    'Vendor'            =>  { value => 'Unknown' },
    'Version'           =>  { value => 'Unknown' },
    'Release Date'      =>  { value => 'Unknown' },
    'BIOS Revision'     =>  { value => 'Unknown' },
    'Firmware Revision' =>  { value => 'Unknown' },
    'ACPI'              =>  { value => 'Unknown' },
    'USB'               =>  { value => 'Unknown' },
);

########## Suporting Routines ##########

# Provide usage information...
#
sub Usage {
    my $apps;

    foreach my $prd (@targetApps) {
        $apps .= $prd;
        # Print separator character unless this is the last item in the list
        $apps .= " | " unless ($prd eq $targetApps[$#targetApps]);
    }

    print "Usage: HostCheck -L -D -V -S -h -p <product-name>\n";
    print "    -L Inhibit creation of $logFile\n";
    print "    -D Display discovered inventory\n";
    print "    -V Display version information and exit\n";
    print "    -S Apply simulation overrides\n";
    print "    -h Display this help text and exit\n";
    print "    -p <required product-name>\n";
    print "\n";
    print "    product-name: [$apps]\n";
    print "    Returns:\n";
    print "    0   STATUS_SUCCESS\n";
    print "    255 STATUS_FAILURE\n";
    print "    254 STATUS_PRIVS_REQUIRED\n";
    print "    253 STATUS_USAGE\n";
    print "    252 STATUS_SYSTEM_COMMANDS\n";
    print "    251 STATUS_INTERNAL_ERROR\n";
    exit STATUS_USAGE;
}

sub Version {
    printf "%.3f\n", PROGRAM_VERSION;
    exit STATUS_SUCCESS;
}

# Validate requested target application by name...
#
sub ValidApplication {
    my $reqApp = $_[0];

    # Test for exact match of application name...
    #
    return 0 if (! defined $reqApp);
    return scalar grep /^$reqApp$/, @targetApps;
}

# Display the Discovered inventory...
#
sub DiscoveredInventory {
    my $config = $_[0];
    my @report;
    my ($m, $n);

    push @report, "HostCheck";
    push @report, "----------------------------------";
    push @report, "Script version:";
    push @report, sprintf "    %s",PROGRAM_VERSION;

    if ($overrideInventory > 0) {
        push @report, "Inventory simulation overrides applied: [$overrideInventory]";
        while ( my ($keyword, $value) = each %parameterOverrideDB ) {
            push @report, "    $keyword : $value";
        }
    }

    push @report, "Target Application:";
    push @report, "    $cmdlineOptions{p}";

    push @report, "Selected Bill of Materials:";
    push @report, "    $$config{configName}";

    push @report, "System BIOS:";
    while ( (my $key) = each %dmidecodeBios ) {
        if ($dmidecodeBios{$key}{value} ne 'Unknown') {
            push @report, "    $key : $dmidecodeBios{$key}{value}";
        }
    }

    push @report, "Hardware Platform:";
    while ( (my $key) = each %dmidecodeDB ) {
        if ($dmidecodeDB{$key}{value} ne 'Unknown') {
            push @report, "    $dmidecodeDB{$key}{nm} : $dmidecodeDB{$key}{value}";
        }
    }

    push @report, sprintf "Total Power Supplies discovered: %d",scalar @pwrDB;

    $n = 1;
    foreach my $psRef (@pwrDB) {
        push @report, sprintf "Power supply %d:", $n;
        while ( (my $key) = each %$psRef ) {
            push @report, "    $key: $$psRef{$key}{value}";
        }
        $n++;
    }

    push @report, "Processor Data Sheet:";
    while (my ($key, $value) = each %processorDB) {
        if ($value ne 'Unknown') {
            push @report, "    $key : $value";
        }
    }

    $n = 0;
    $m = keys %cpuFlagsDB;
    while ( (my $key) = each %cpuFlagsDB ) {
        $n++ if ($cpuFlagsDB{$key}{value} == TRUE);
    }

    push @report, "    CPU Flags [$n of $m]:";
    while ( (my $key) = each %cpuFlagsDB ) {
        push @report, sprintf "    %8s:\t%d  %s",$key,$cpuFlagsDB{$key}{value},$cpuFlagsDB{$key}{nm};
    }

    push @report, "System Memory:";
    push @report, sprintf "    Total Memory: %d GB",$memoryDB{'MemTotal'}{value};
    push @report, sprintf "    Swap Size: %d MB",$memoryDB{'SwapTotal'}{value};

    push @report, sprintf "Total HBAs discovered: %d",scalar @pciDB;

    foreach my $hba (@pciDB) {
        my $text;
        if ($$hba{Type} eq 'RAID') {
            $text = "    $$hba{Type}: $$hba{Class}, $$hba{Vendor}";
        }
        else {
            $text = "    $$hba{Type}: $$hba{Device}, $$hba{Vendor}";
        }
        push @report, $text;
    }

    push @report, "RAID controller information:";

    if ((scalar keys %$raidDBext) > 0) {
        while (my ($key, $value) = each %$raidDBext) {
            my $text = "    $key: $value";
            push @report, $text;
        }
    }
    else {
        push @report, "    N/A";
    }

    if ((scalar @raidDB) > 0) {
        push @report, sprintf "Total Drives discovered: %d",scalar @raidDB;
        foreach my $hd (@raidDB) {
            push @report, "    $$hd{hdName}  size: $$hd{hdSize} GB  speed: $$hd{hdRPM} RPM  bay: $$hd{hdBay} status: $$hd{hdStatus}";
        }
    }
    else {
        push @report, sprintf "Total Drives discovered: %d",scalar @hdDB;
        foreach my $hd (@hdDB) {
            push @report, "    $$hd{hdName}  size: $$hd{hdSize} GB";
        }
    }

    push @report, sprintf "Total Ethernet NICs discovered: %d",scalar @lanDB;
    if ((scalar @lanDB) > 0) {
        foreach my $port (@lanDB) {
            push @report, "    $$port{name}  hwaddr: $$port{hwaddr}";
        }
    }

    push @report, "----------------------------------";

    return @report;
}

# Display the Report
#
sub PrintReport {
    my $report = $_[0];

    foreach my $line (@$report) {
        &LogPrint($line);
    }

    return;
}

# Apply the parameter overrides
# Keywords are:
#   VIRTUAL_MACHINE
#   CORES
#   CPU_MODEL
#   MEMORY_SIZE
#   DRIVES
#   DRIVE_SIZE
#   NICS
#   POWER_SUPPLIES
#   WATTS
#
sub ApplyParameterOverrides {

    # Note - the override for VIRTUAL_MACHINE is applied in the 
    # ThisIsVirtualMachine subroutine. -DrW

    # The order is important. If we're adding hardware we may also
    # want to adjust the size.

    if (exists $parameterOverrideDB{CORES}) {
        $processorDB{'Core(s) per socket'} = $parameterOverrideDB{CORES};
    }

    if (exists $parameterOverrideDB{CPU_MODEL}) {
        $processorDB{'Model name'} = $parameterOverrideDB{CPU_MODEL};
    }

    if (exists $parameterOverrideDB{MEMORY_SIZE}) {
        $memoryDB{'MemTotal'}->{value} = $parameterOverrideDB{MEMORY_SIZE};
    }

    if (exists $parameterOverrideDB{DRIVE_SIZE}) {
        foreach my $hdRef (@hdDB) {
            $hdRef->{hdSize} = $parameterOverrideDB{DRIVE_SIZE};
        }
    }

    if (exists $parameterOverrideDB{DRIVES}) {
        my $value = $parameterOverrideDB{DRIVES};

        if ($value < (scalar @hdDB)) { # Reduce the number found
            while ((scalar (@hdDB)) > $value) {
                my $tmp = pop @hdDB;
            }
        }
        else { # Increase the number found by duplicating last entry in DB
            my $tmp = $hdDB[$#hdDB];
            while ((scalar (@hdDB)) < $value) {
                push @hdDB, $tmp;
            }
        }

        if ($value < (scalar @raidDB)) { # Reduce the number found
            while ((scalar (@raidDB)) > $value) {
                my $tmp = pop @raidDB;
            }
        }
        else { # Increase the number found by duplicating last entry in DB
            my $tmp = $raidDB[$#raidDB];
            while ((scalar (@raidDB)) < $value) {
                push @raidDB, $tmp;
            }
        }
    }

    # Did we find any NICs???
    #
    if ((scalar @lanDB) == 0) {
        if (exists $parameterOverrideDB{NICS}) {
            # Create an item for database...
            my $item = { name => 'ETHx', hwaddr => 'DC:DC:DC:DC:DC:DC' };
            push @lanDB, $item;
        }
    }

    if (exists $parameterOverrideDB{NICS}) {
        my $value = $parameterOverrideDB{NICS};

        if ($value < (scalar @lanDB)) { # Reduce the number found
            while ((scalar (@lanDB)) > $value) {
                my $tmp = pop @lanDB;
            }
        }
        else { # Increase the number found by duplicating last entry in DB
            my $tmp = $lanDB[$#lanDB];
            while ((scalar (@lanDB)) < $value) {
                push @lanDB, $tmp;
            }
        }
    }

    # Did we find any power supplies???
    #
    if ((scalar @pwrDB) == 0) {
        if ((exists $parameterOverrideDB{WATTS}) ||
           (exists $parameterOverrideDB{POWER_SUPPLIES})) {
            # Create an item for database...
            my $item = {
                'Manufacturer'          => { value => 'Unknown' },
                'Serial Number'         => { value => 'DRW-111-000-111' },
                'Model Part Number'     => { value => 'DRW-222-111' },
                'Max Power Capacity'    => { value => int 1000 },
                'Hot Replaceable'       => { value => 'Yes' },
                'Status'                => { value => 'Present' },
            };

            push @pwrDB, $item;
        }
    }

    if (exists $parameterOverrideDB{WATTS}) {
        foreach my $psRef (@pwrDB) {
            $psRef->{'Max Power Capacity'}->{value} = $parameterOverrideDB{WATTS};
        }
    }

    if (exists $parameterOverrideDB{POWER_SUPPLIES}) {
        my $value = $parameterOverrideDB{POWER_SUPPLIES};

        if ($value < (scalar @pwrDB)) { # Reduce the number found
            while ((scalar (@pwrDB)) > $value) {
                my $tmp = pop @pwrDB;
            }
        }
        else { # Increase the number found by duplicating last entry in DB
            my $tmp = $pwrDB[$#pwrDB];
            while ((scalar (@pwrDB)) < $value) {
                push @pwrDB, $tmp;
            }
        }
    }

    return;
}

# Parameter overrides are used for debugging.
# The script uses these values to override the value of the actual hardware
# value. The override values are read from the DATA section at the end of
# this script. -DrW
#
sub LoadParameterOverrides {
    my @info;

    chomp( @info = grep /\S/, <DATA> ); # Read in parameter overrides

    @info = grep !/^#/, @info; # Remove comments

    foreach my $line (@info) {
        my @data = split /=/, $line;
        s{^\s+|\s+$}{}g foreach @data; # Remove Leading\Trailing whitespace
        my $keyword = $data[0];

        if ((scalar grep /^$keyword$/, @data) != 0) {
                $parameterOverrideDB{$keyword} = $data[$#data];
                $overrideInventory++;
        }
    }

    #print Dumper( \%parameterOverrideDB );

    return;
}

# Use this routine to perform any initialization we may
# want done at script start-up...
#
sub Init {
    LoadParameterOverrides();
    return;
}

# Put learning algorithms and heuristics here...
sub Learn {

    GetMemoryConfig( \%memoryDB );
    GetProcessorConfig( \%processorDB );
    GetProcFlags( \%cpuFlagsDB );
    GetDmiDecodeByKeyword( \%dmidecodeDB );
    GetDmiDecodeBios( \%dmidecodeBios );

    @hdDB   = &GetHDConfig;
    @lanDB  = &GetLANConfig;
    @pwrDB  = &GetPWRConfig;
    @pciDB  = &GetPCIConfig;
    @raidDB = &GetRAIDConfig;

    $raidDBext = &GetRAIDConfig_ext;

    return;
}

sub MapSystemCommands {
    my $sysCmd       = $_[0];
    my @cmdsRequired = REQUIRED_CMDS;

    foreach my $cmd (@cmdsRequired) {
        my @data  = `which $cmd 2>&1`;
        my $retval = $?;
        chomp @data;

        if ($retval == 0) {
            $$sysCmd{$cmd} = $data[$#data];
        }
    }

    return;
}

# Relevant system commands
#   fdisk -l <dev node>   // This command requires sudo!
sub GetSDList {
    my @lines;
    my @sdaList;
    my $cmd;
    my $sd;
    my $drive = "a";

    return unless exists $systemCommand{fdisk};

LOOP:while (1) {
        $sd  = "/dev/sd".$drive;
        $cmd = "$systemCommand{fdisk} -l $sd 2>/dev/null";
        @lines = `$cmd`;

        last LOOP if ((scalar @lines) == 0);

        push @sdaList, $sd;
        ++$drive;
    }

    # Returns list of discovered SDx devices
    return @sdaList;
}

# Write to output log and to STDOUT
#
sub LogPrint {
    my $textLine = $_[0];

    print LOGFILE "$textLine\n" unless ($cmdlineOptions{L});
    print "$textLine\n";

    return;
}

########## SubRoutines ##########

# Relevant system commands
#   cat /proc/meminfo
#   vmstat -s -S <arg>
#   -S  followed by k or K or m or M switches outputs between 1000, 1024,
#    1000000, or 1048576 bytes
#           <arg> k = 1000
#           <arg> K = 1024
#           <arg> m = 1000000
#           <arg> M = 1048576
#
sub GetMemoryConfig {
    my $memRef = shift;

    my @lines;
    my @data;

    return unless exists $systemCommand{vmstat};
    return unless exists $systemCommand{dmidecode};

    foreach my $key (keys %$memRef) {
        $$memRef{$key}{value} = 0;
    }

    @lines = `$systemCommand{vmstat} -s -S m`;
    chomp @lines;
    s{^\s+|\s+$}{}g foreach @lines; # Remove Leading\Trailing whitespace

    @data = grep /total swap/, @lines;

    if ((scalar @data) != 0) {
        my ($value) = $data[$#data] =~ /\d+/g;
        $$memRef{'SwapTotal'}{value} = $value;
    }

    @lines = `$systemCommand{dmidecode} -t memory`;
    chomp @lines;
    s{^\s+|\s+$}{}g foreach @lines; # Remove Leading\Trailing whitespace
    my ($k) = 0;
    my ($host) = 'Standard';
    $k += grep /$host/,&SystemProductName;
    my($my_mem) = grep /^Maximum Capacity:/,@lines;
    if (! defined $my_mem) #'dmidecode -t memory' does not always return for AWS (e.g. c5)
    {
        $my_mem = `free -g | grep Mem | awk '{print \$2}'`;
    }
    my ($my_value) = $my_mem =~ /\d+/g;
    if ($k == 1)
    {
	$$memRef{'MemTotal'}{value} = $my_value;
	return;
    }

    @data = grep /^Size:/,@lines;
    @data = grep !/No Module Installed/, @data;

    if ((scalar @data) != 0) {
        my $totalRAM = 0;
        my $divisor  = 2**10;   # 1024

        ## Few AWS hardwares return memory with differnent units (dmidecode -t memory)
        ## GB and GiB are interchangeably used, consider both as same.

        foreach my $line (@data) {
            my ($line_unit) = $line;
            $line_unit =~ s/[0-9+]//g;
            $line_unit =~ s/\s//g;
            my ($value) = $line =~ /\d+/g;

            if ($line_unit =~ "KB" || $line_unit =~ "KiB") {
                $totalRAM += $value/1024;
            }
            elsif ($line_unit =~ "GB" || $line_unit =~ "GiB") {
                $totalRAM += $value*1024;
            }
            else {
                $totalRAM += $value;
            }
       }

        $divisor = 1E3 if (&SystemProductName eq 'KVM');

        $$memRef{'MemTotal'}{value} = $totalRAM / $divisor;
    }
    else
    {
        my($my_mem) = `free -g | grep Mem | awk '{print \$2}'`; 
        my ($my_value) = $my_mem =~ /\d+/g;
        $$memRef{'MemTotal'}{value} = $my_value;
    }

    return;
}

# Relevant system commands
#   dmidecode
#
sub GetProcFlags {
    my $cpuRef = $_[0];
    my @lines;
    my @data;
    my $totalFlags;
    my $flagsFound;

    $totalFlags = keys %$cpuRef;
    $flagsFound = 0;

    return unless exists $systemCommand{cat};

    @lines = `$systemCommand{cat} /proc/cpuinfo`;
    chomp @lines;
    @data = grep /^flags/, @lines;

    if ((scalar @data) != 0) {
        @data = split /\s+/,$data[0];

        while ( (my $key) = each %$cpuRef ) {
            my $flag = lc $key;

            if (scalar grep /$flag/, @data) {
                $$cpuRef{$key}{value} = TRUE;
                $flagsFound++;
            }
        }
    }

    #say "FLAGS: Total = $totalFlags  Found = $flagsFound";
    return;
}

# Relevant system commands
#   lscpu
#
sub GetProcessorConfig {
    my $procRef = shift;

    my @lines;
    my @data;
    my $nm;
    my $value;
    my $max_socket;
	my $i;

    return unless exists $systemCommand{cat};

    if ($cmdlineOptions{p} ne "SGX")
    {
		# lscpu does not exist in RedHat5.7 used by SGX
	    return unless exists $systemCommand{lscpu};
	    @lines = `$systemCommand{lscpu}`;
	    chomp @lines;
	    s{^\s+|\s+$}{}g foreach @lines; # Remove Leading\Trailing whitespace
	
	    foreach my $line (@lines) {
	        ($nm, $value) = split /:\s+/, $line;
	        $$procRef{$nm} = $value;
	    }
    }

    # Get the CPU model name. We will likely need this to
    # differentiate them later... -DrW

    @lines = `$systemCommand{cat} /proc/cpuinfo`;
    chomp @lines;

    if ($cmdlineOptions{p} eq "SGX")
    {
		# Collect vendor information for SGX as not available from lscpu
    	($value) = grep /vendor_id/, @lines;
     	@data   = split /\s+/, $value;
    
    	$value = $data[2]; 
    	$value = 'Unknown' if (! defined $value);

    	$$procRef{'Vendor ID'} = $value;
    }

    $max_socket = 0;
    foreach my $coreline (@lines)
    {
        if ($coreline =~ "physical id")
		{
	    	@data = split /\s+/, $coreline;
	     	$value = $data[3]; 
	     	if (defined $value)
	     	{
	        	if ($value > $max_socket)
		  		{
					$max_socket = $value;
		  		}
             }
		}
    }
    $$procRef{'Socket count'} = $max_socket + 1;
    
    ($value) = grep /model name/, @lines;
    @data   = split /\s+/, $value;
    for ($i = 0; $i < scalar @data; $i++)
    {
		if ($data[$i] =~ "E5-")
		{
			$value = $data[$i] . " " . $data[$i + 1];
			last;
		}
    }
    $value = 'Unknown' if (! defined $value);

    $$procRef{'Model name'} = $value;

    #print Dumper( \$procRef );

    return;
}

# Relevant system commands
#   parted -l
#
# grep'd output:
# Disk /dev/sda: 322GB
sub GetHDConfig {
    my $skipDiskName = "";
    my @sdDB;
    my @hdDevInfo = `parted -ls 2>/dev/null | grep "Disk \/dev\/[xshvn].*[vd].*[a-z]" | awk '{ print \$2,\$3 }'`;

    # In Azure, VM can be mounted with temporary resource disk
    # do not count this as part of calculations as it emptied
    # when VM is deprovisioned.
    if ($dmidecodeDB{'system-manufacturer'}->{value} =~ /Microsoft/ && $dmidecodeDB{'system-product-name'}->{value} =~ /Virtual/) {
        $skipDiskName = basename(`readlink -fn /dev/disk/cloud/azure_resource`);
    }

    foreach my $elem (@hdDevInfo) {
        chomp $elem;
        my ($diskName, $diskSize) = split /\s+/, $elem;
        chop($diskName);
        $diskName=basename($diskName);
        chop($diskSize);
        chop($diskSize);
        my $item = {
            hdName => $diskName,
            hdSize => $diskSize,
            hdRPM  => 0,
        };
        push @sdDB, $item if($diskName ne $skipDiskName);
    }
    return @sdDB;
}

# Relevant system commands
#   lspci
#
sub GetHBAConfig {
    my @lines;
    my @data;

    return unless exists $systemCommand{lspci};

    # Acquire System information...
    @lines = `$systemCommand{lspci} -mm`;
    chomp @lines;

    # Discover Gigabit Ethernet devices...
    @data = grep /SCSI controller/, @lines;
    @data = grep /SCSI storage controller/, @lines if ((scalar @data) == 0);

    return @data;
}

# Relevant system commands
#   ifconfig
#
sub GetLANConfig {
    my @lines;
    my @data;
    my @db;

    return unless exists $systemCommand{ifconfig};

    # Acquire System information...
    #
    @lines = `$systemCommand{ifconfig} -a`;
    chomp @lines;
    @data = grep  /^\b\D+\d+\b/ | /^\b\D+\d{1}\_[ps]{1}\b/, @lines;
    s{^\s+|\s+$}{}g foreach @data;

    # Learn Ethernet devices...
    #
    foreach my $line (@data) {
        my @info = split /\s+/,$line;
        my $item = { name => $info[0], hwaddr => $info[$#info] };
        push @db, $item;

    }

    return @db;
}

# Relevant system commands
#   lscpi
#
sub GetPCIConfig {
    my @lines;
    my @data;
    my @db;
    my @searchDevices = ("RAID", "Fibre Channel", "SATA", "SAS");

    return unless exists $systemCommand{lspci};

    # Acquire System information...
    @lines = `$systemCommand{lspci} -vmm`;
    chomp @lines;

    while (@lines) {
        my $line;
        my ($nm, $type, $slot, $class, $vendor, $device);
        my $duplicate = 0;

        $type = 'Unknown';

        $line = shift @lines;
        next unless ($line =~ m/^Slot:\s/);

        ($nm,$slot) = split /^Slot:/, $line;
        $slot =~ s{^\s+|\s+$}{}g;

        $line = shift @lines;
        ($nm,$class) = split /^Class:/, $line;
        $class =~ s{^\s+|\s+$}{}g;
    
        $line = shift @lines;
        ($nm,$vendor) = split /^Vendor:/, $line;
        $vendor =~ s{^\s+|\s+$}{}g;

        $line = shift @lines;
        ($nm,$device) = split /^Device:/, $line;
        $device =~ s{^\s+|\s+$}{}g;

        $type = 'RAID' if ($class  =~ m/RAID/);
        $type = 'SATA' if ($device =~ m/SATA/);
        $type = 'SAS'  if ($device =~ m/SAS/);
        $type = 'Fibre Channel' if ($class =~ m/Fibre Channel/);

        # The Fibre Channel HBA is dual port so it shows up twice in the
        # listing. Remove duplicates...
        if ($type eq 'Fibre Channel') {
            my ($bus,$function) = split /\./,$slot;
            if ($function ne '0') {
                #say "BUGBUG remove duplicates ($type) ($bus) ($function)";
                $duplicate++;
            }
        }

        next unless (scalar grep /$type/, @searchDevices);

        my $item = {
            Type   => $type,
            Slot   => $slot,
            Class  => $class,
            Vendor => $vendor,
            Device => $device,
        };

        push @db, $item unless ($duplicate);
    }

	if (-e "/opt/iphwae/tools/iphDumpCardList")
	{
		@lines = `/opt/iphwae/tools/iphDumpCardList`;
		chomp @lines;

		if (@lines)
		{
	    	if (grep /Card 0/, @lines) 
			{
		        my $item = {
		            Type   => 'SS7 Card',
		            Slot   => 'NA',
		            Class  => 'NA',
		            Vendor => 'Interphase',
		            Device => 'T1/E1 Signaling card',
		        };

		        push @db, $item;
			}

	    	if (grep /Card 1/, @lines) 
			{
		        my $item = {
		            Type   => 'SS7 Card',
		            Slot   => 'NA',
		            Class  => 'NA',
		            Vendor => 'Interphase',
		            Device => 'T1/E1 Signaling card',
		        };

		        push @db, $item;
			}
		}
	}
    	
    return @db;
}

sub GetRAIDConfig_ext {
    my @lines;
    my %dataBase;

    return unless exists $systemCommand{hpacucli};

    chomp(
        @lines = grep /\S/,
        `$systemCommand{hpacucli} ctrl all show detail`
    );

    s{^\s+|\s+$}{}g foreach @lines; # Remove Leading\Trailing whitespace

    # Remove header
    #
    if (grep /^Bus Interface:/, @lines) {
        shift @lines until ($lines[0] =~ m/^Bus Interface:/);
    }

    foreach my $line (@lines) {
        my ($nm, $value) = split /:\s+/, $line;
        $dataBase{$nm} = $value;
    }

    #print Dumper("dataBase", \%dataBase);
    return \%dataBase;
}

sub GetRAIDConfig {
    my @lines;
    my @data;
    my @db;
    my $slot;
    my $drvCnt;

    return unless exists $systemCommand{hpacucli};

    chomp(
        @lines = grep /\S/,
        `$systemCommand{hpacucli} ctrl all show`
    );

    # Retrieve the Slot number
    # TODO - improve this to deal with multiple slots
    #
    @data = split /Slot/,$lines[$#lines];
    s{^\s+|\s+$}{}g foreach @data; # Remove Leading\Trailing whitespace
    ($slot) = $data[$#data] =~ /\d+/g;

    # Retrieve Physical drive information
    #
    chomp(
        @lines = grep /\S/,
        `$systemCommand{hpacucli} ctrl slot=$slot pd all show detail`
    );

    s{^\s+|\s+$}{}g foreach @lines; # Remove Leading\Trailing whitespace

    $drvCnt = scalar grep /physicaldrive/, @lines;

    foreach my $index (0..($drvCnt - 1)) {
        my $driveName;
        my $driveStatus;
        my $driveSize;
        my $driveRPM;
        my $driveBay;

        $driveName = 'Drive ' . ($index + 1);

        @data = grep /^Status:/, @lines;
        @data = split /\s+/, $data[$index];
        $driveStatus = $data[$#data];

		@data = grep /^Bay:/, @lines;
        ($driveBay) = $data[$index] =~ /\d+/g;

        if ($driveStatus eq 'OK') {
            @data = grep /^Size:/, @lines;
            ($driveSize) = $data[$index] =~ /\d+/g;

            @data = grep /^Rotational Speed:/, @lines;
            ($driveRPM) = $data[$index] =~ /\d+/g;
        }
        else {
            $driveSize = 0;
            $driveRPM  = 0;
        }

        my $item = {
            hdName   => $driveName,
            hdStatus => $driveStatus,
            hdSize   => $driveSize,
            hdRPM    => $driveRPM,
            hdBay    => $driveBay,
        };

        push @db, $item;
    }

    return @db;
}

# Relevant system commands
#   dmidecode
#
sub GetPWRConfig {
    my @lines;
    my @db;
    my $found = 0;
    my @keywords = (    # Keywords for learning power supply characteristics
        'Manufacturer',
        'Serial Number',
        'Model Part Number',
        'Max Power Capacity',
        'Hot Replaceable',
        'Status',
    );

    return unless exists $systemCommand{dmidecode};

    # Acquire System information...
    @lines = `$systemCommand{dmidecode} --type 39`;
    chomp @lines;
    s{^\s+|\s+$}{}g foreach @lines; # Remove Leading\Trailing whitespace

    $found = grep /^System Power Supply$/, @lines;
    goto DONE unless ($found > 0);

KEY:foreach my $key (@keywords) {
        my @data = grep /$key/, @lines;
        next KEY if ((scalar @data) == 0);
        foreach my $k (0..($found - 1)) {
            my ($nm, $value) = split /:\s+/, $data[$k];
            if (defined $value) { 
                $db[$k]->{$key}->{value} = $value;
            } else {
                $db[$k]->{$key}->{value} = "Unknown";
            }
        }
    }

    # Clean up the Power capacity field. Change KWatts to Watts
    foreach my $psRef (@db) {
        my $value;
        ($value) = split /\s+/, $psRef->{'Max Power Capacity'}->{value};
        my $watts;
        if ( looks_like_number($value)) 
		{
	    	if ($value =~ /\./)
	    	{
	            $watts = int (1000 * $value);
	    	}
	    	else
			{
				$watts = $value;
			}
        } else {
            $watts = -1;
        } 
        $psRef->{'Max Power Capacity'}->{value} = $watts;
    }

DONE:
    #print Dumper( \@db );
    return @db;
}

# Relevant system commands
#   dmidecode   // Requires priv's...
#
##DMI TYPES
#       The SMBIOS specification defines the following DMI types:
#
#       Type   Information
#          0   BIOS
#          1   System
#          2   Baseboard
#          3   Chassis
#          4   Processor
#          5   Memory Controller
#          6   Memory Module
#          7   Cache
#          8   Port Connector
#          9   System Slots
#         10   On Board Devices
#         11   OEM Strings
#         12   System Configuration Options
#         13   BIOS Language
#         14   Group Associations
#         15   System Event Log
#         16   Physical Memory Array
#         17   Memory Device
#         18   32-bit Memory Error
#         19   Memory Array Mapped Address
#         20   Memory Device Mapped Address
#         21   Built-in Pointing Device
#         22   Portable Battery
#         23   System Reset
#         24   Hardware Security
#         25   System Power Controls
#         26   Voltage Probe
#         27   Cooling Device
#         28   Temperature Probe
#         29   Electrical Current Probe
#         30   Out-of-band Remote Access
#         31   Boot Integrity Services
#         32   System Boot
#         33   64-bit Memory Error
#         34   Management Device
#         35   Management Device Component
#         36   Management Device Threshold Data
#         37   Memory Channel
#         38   IPMI Device
#         39   Power Supply
#         40   Additional Information
#         41   Onboard Devices Extended Information
#         42   Management Controller Host Interface
#

# Relevant system commands
#   biosdecode
#   dmidecode
#
sub GetDmiDecodeBios {
    my $dmiRef = $_[0];
    my @lines;
    my @data;

    return unless exists $systemCommand{dmidecode};

    @lines = `$systemCommand{dmidecode} --type bios`;  # System
    chomp @lines;

KEY:while ( (my $key) = each %$dmiRef ) {

            if ($key =~ 'SMBIOS') {
                @data = grep /^$key/, @lines;
                next KEY if (scalar @data == 0);
                @data = split /\s+/,$data[0];
                $$dmiRef{$key}{value} = $data[1];
                next KEY;
            }
            elsif ($key =~ 'ACPI') {
                @data = grep /$key/, @lines;
                next KEY if (scalar @data == 0);
                @data = split /$key/,$data[0];
                $data[$#data] =~ s/^\s+//;   # Remove leading whitespace
                $$dmiRef{$key}{value} = $data[$#data];
                next KEY;
            }
            elsif ($key =~ 'USB') {
                @data = grep /$key/, @lines;
                next KEY if (scalar @data == 0);
                @data = split /$key/,$data[0];
                $data[$#data] =~ s/^\s+//;   # Remove leading whitespace
                $$dmiRef{$key}{value} = $data[$#data];
                next KEY;
            }
            else 
			{
                @data = grep /$key/, @lines;
                next KEY if (scalar @data == 0);
                @data = split /:/,$data[$#data];
                $data[$#data] =~ s/^\s+//;  # Remove leading whitespace
                $$dmiRef{$key}{value} = $data[$#data];
            }
    }

    return;
}

# Relevant system commands
#   dmidecode
#
sub GetDmiDecodeByKeyword {
    my $dmiRef = $_[0];
    my @data;
    my $text;

    return unless exists $systemCommand{dmidecode};

KEY:while ( (my $key) = each %$dmiRef ) {
        @data = `$systemCommand{dmidecode} --string $key`;  # System
        chomp @data;

        $text = $data[$#data];

        next KEY unless defined $text;

            if ($text =~ /^\#/) {
                $$dmiRef{$key}{value} = 'Unknown';
            }
            elsif ($text =~ /^000000000000/) {
                $$dmiRef{$key}{value} = 'Unknown';
            }
            elsif ($text =~ /^Not Specified/) {
                $$dmiRef{$key}{value} = 'Unknown';
            }
            elsif ($text =~ /^None/) {
                $$dmiRef{$key}{value} = 'Unknown';
            }
            else {
                $$dmiRef{$key}{value} = $text;
            }
    }

    return;
}

sub GenerateLicenseKeyByUUID {
    my $licenseKey;

    $licenseKey = $dmidecodeDB{'system-uuid'}->{value};

    if ($licenseKey eq 'Unknown') {
        $licenseKey = 'Not available';
    }
    else {
        $licenseKey =~ tr/-//d;
    }

    return $licenseKey;
}

sub GenerateLicenseKeyByNIC {
    my $licenseKey;

    if ((scalar @lanDB) == 0) {
        $licenseKey = 'Not available';
    }
    else {
        $licenseKey = $lanDB[0]->{hwaddr};
        $licenseKey =~ tr/://d;
    }

    return $licenseKey;
}

sub GetLicenseKeyForTargetApp {
    my $targetApp  = $_[0];
    my @appList    = qw/EMS EMSLEGACYHA EMSRAC/;
    my $licenseKey;

    if ((scalar grep /^$targetApp$/, @appList) != 0) {
        if ( &ThisIsVirtualMachine == TRUE ) {
            $licenseKey = &GenerateLicenseKeyByUUID;
        }
        else {
            $licenseKey = &GenerateLicenseKeyByNIC;
        }
    }

    return $licenseKey;
}

sub ThisIsVirtualMachine {
    my $value;

    if (exists $systemCommand{'virt-what'}) {
        $value = &DetermineVMbyVirtWhat;
    }
    else {
        $value = &DetermineVMbyProductNameAndHostType;
    }

    # Check for value override
    #
    if (exists $cmdlineOptions{S}) {
	    if (exists $parameterOverrideDB{VIRTUAL_MACHINE}) {
	        if ($parameterOverrideDB{VIRTUAL_MACHINE} != 0) {
	            $value = TRUE;
	        }
	        else {
	            $value = FALSE;
	        }
	    }
    }

    return $value;
}

sub DetermineVMbyVirtWhat {

    return FALSE unless exists $systemCommand{'virt-what'};

    my $value = `$systemCommand{'virt-what'}`;

    $value = ( (($? == 0) && ($value ne "")) ? TRUE : FALSE );

    return $value;
}

sub DetermineVMbyProductNameAndHostType {
    my $value = FALSE;
    my $pn;

    $pn = &SystemProductName;

    $value = TRUE if grep /KVM/,$pn;
    $value = TRUE if grep /VMware/,$pn;
    $value = TRUE if grep /Virtual/,$pn;
    $value = TRUE if &ThisIsConnexIP5000;

    return $value;
}

# Relevant system commands
# dmidecode --string system-product-name
#
sub SystemProductName {
    my $nm = 'Unknown';

    if (exists $systemCommand{dmidecode}) {
        my @data;
        my $text;

        @data = `$systemCommand{dmidecode} dmidecode --string system-product-name`;
        chomp (@data);

        $text = $data[$#data];
        $nm = $text if defined $text;
    }

    return $nm;
}

sub ThisIsConnexIP5000 {
    my $vmHostType   = 'unknown';
    my $hostType     = 'unknown';
    my $hostTypeFile = '/etc/hostType';
    
    my $value;

    # For VSBC...
    #
    if (-e $hostTypeFile) {
        open HFILE, $hostTypeFile;
        my @data = <HFILE>;
        close HFILE;
        chomp @data; 
        @data = grep /\S/,@data;  # Remove blank lines...
        $vmHostType = shift @data if (scalar @data);
    }

    $value = ($vmHostType =~ m/ConnexIP5000/) ? TRUE : FALSE;

    return $value;
}

sub isCloud{

    my $value = FALSE;
    my $hostType;
    
    if ( -e "/etc/hostSubType")
    {
       
	$hostType = `cat /etc/hostSubType`;
        chomp($hostType);

        if ( $hostType eq "virtualCloud")
        {
          $value = TRUE;

  	}
        
    }
    else
    {
         $value = FALSE;
    } 
    
    return $value;
}

# Required Configuration Info
# -----------------------------------------------------------------------------
# System memory is equal or more than the minimum required
# CPU speed is equal or more than required
# Number of cpu cores are equal or more than required(Duly considering hyperthreaded cores)
# Size of Hard disk is equal or more than minimum required
# Number of Ethernet ports are equal or more than minimum required
# Number of Power supply kits are equal or more than minimum required
# Number of SAS ports are equal or more than minimum required (for HA)
# ILO version is one of the supported version
# BIOS version is one of the supported version
# HP-ACU version is one of the supported version
# External RAID is available (For HA) [ for Use case 3 only]
# SWAP size is equal or more than the minimum required [ For Use  case 3 only]
#

#
# HP BOM Configuration Requirements
# Processor
# Name                  Family      Model       Codename
# ----------------------------------------------------------
# IvyBridge             6           0x3A (58)
# SandyBridge           6           0x2A (42)
# SandyBridge           6           0x2D (45)        SandyBridge-E
# Westmere              6           0x25 (37)        Arrondale
# Westmere              6           0x2C (44)        Gulftown
# Westmere              6           0x2F (47)        Westmere-EX
# Nehalem               6           0x1E (30)        Clarksfield
# Nehalem               6           0x1A (26)        Bloomfield
# Nehalem               6           0x2E (46)        Nehalem-EX
# Penryn                6           0x17 (23)        Yorkfield
# Penryn                6           0x1D (29)        Dunnington
# Merom                 6           0x0F (15)        Clovertown
# Merom                 6           0x16 (22)        Merom Conroe
# Presler               6           0x06
# Prescott              6           0x03/0x04
# Dothan                6           0x0D(13)
#
# __in__ Requested App: [PSX PSXHA EMS EMSLEGACYHA EMSRAC SBC OAM DSI DSIHA ADS ASX ADSRAC]
#
sub GenerateBOMForTargetApp {
    my $targetApp = $_[0];

    my $configName  = 'Unknown';
    my $smbios      = '2.7';
    my $smVendor    = 'HP';
    my $smVersion   = 'P70';
    my $acpiSupport = 'is supported';
    my $usbLegacy   = 'is supported';
    my $sockets     = 1;
    my $cpuVendor   = 'GenuineIntel';
    my $cpuModel    = 45;
    my $diskDrives  = 4;
    my $vdiskDrives = 1;
    my $diskSize    = 450;
    my $vdiskSize   = 450;
    my $diskRPM     = 10000;
    my $hbaCount    = 0;
    my $hbaSAS      = 0;
    my $hbaSATA     = 0;
    my $hbaRAID     = 0;
    my $hbaFC       = 0;
    my $pwrUnits    = 2;
    my $pwrType     = 'AC';
    my $pwrWattage  = 750;

    my $cpuName = 'Unknown';
    my $cores   = -1;
    my $vcores  =  0;
    my $memory  = -1;
    my $vmemory = -1;
    my $nics    = -1;
    my $vnics   =  1;
    my $model   =  0;
    my $family   =  0;
    my $checkfamily   =  0;

    my %bom = (
        configName  => 'Unknown',
#       ------------------------------------------------
#       ITEM             STD       VIRTUAL   MINIMUM
#       ------------------------------------------------
        smbios      => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        smVendor    => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        smVersion   => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        acpiSupport => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        usbLegacy   => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        sockets     => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        cores       => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        cpuVendor   => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        cpuModel    => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', }, # Number like 45
        cpuName     => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', }, # Name like E5-2658
        memory      => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        diskDrives  => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        diskSize    => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        diskRPM     => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        hbaCount    => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        hbaSAS      => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        hbaSATA     => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        hbaRAID     => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        hbaFC       => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        nics        => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        pwrUnits    => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        pwrType     => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
        pwrWattage  => { pm => 'Unknown',  vm => 'Unknown',  min => 'Unknown', },
    );

    my $targetConfig;

    if ((scalar grep /^$targetApp$/, @targetApps) == 0) {
        print "Unrecognized Target Application requested [$targetApp] at line:" . __LINE__ . "\n";
        exit STATUS_INTERNAL_ERROR;
    }

    $targetConfig = $TARGET_APPS{$targetApp}->{config};
    $configName   = "Config_" . $targetConfig;


        # PSXHA
        if ( $targetConfig == 0 ) { 
            $hbaSAS = 1; $cores = 8; $nics = 8; $vnics = 4; 
            $memory = 64; $vmemory = 16;

            $vcores = $bom{cores}->{min} = 2;

            $bom{nics}->{min}   = $nics;
            $bom{memory}->{min} = 16;
        }

        # EMS, DSI, Netscore
        elsif ( $targetConfig == 1 ) {
            $cores = 6; $nics = 8; 
            $memory = 32; $vmemory = 8;

            $vcores = $bom{cores}->{min} = 6;

            $bom{nics}->{min}   = $nics;
            $bom{memory}->{min} = 8;
        }

        # PSX, ADS
        elsif ( $targetConfig == 2 ) {
            $cores = 8; $nics = 8; 
            $memory = 64; $vmemory = 16;

            $vcores = $bom{cores}->{min} = 2;

            $vdiskSize = 180;

            $bom{nics}->{min}   = $nics;
            $bom{memory}->{min} = 16;
        }

        # EMSLEGACYHA, DSIHA
        elsif ( $targetConfig == 3 ) { 
            $hbaSAS = 1; $cores = 6; $nics = 8; $vnics = 4; 
            $memory = 32; $vmemory = 8;

            $vcores = $bom{cores}->{min} = $cores;

            $bom{nics}->{min}   = $nics;
            $bom{memory}->{min} = $memory;
        }

        # EMSRAC, ADSRAC
        elsif ( $targetConfig == 9 ) { 
            $hbaFC = 1; $cores = 6; $nics = 8; 
            $memory = 32; $vmemory = 8;
            $diskDrives = 4; $vdiskDrives = 3;

            $vcores = $bom{cores}->{min} = 2;

            $bom{nics}->{min}   = $nics;
            $bom{memory}->{min} = 32;
        }

        # SGX
        elsif ( $targetConfig == 10 ) {
            $hbaFC = 0; $cores = 12; $nics = 8;
            $memory = 32; $vmemory = 32;
            $diskDrives = 2; $vdiskDrives = 2;

            $vcores = $bom{cores}->{min} = 12;

            $bom{nics}->{min}   = $nics;
            $bom{memory}->{min} = 32;
	    	$sockets = 2;
        }

        # ASX
        elsif ( $targetConfig == 11 ) {
            $cores = 6; $nics = 8; 
            $memory = 32; $vmemory = 16;
            $diskDrives = 2; $vdiskDrives = 2;

            $vcores = $bom{cores}->{min} = 2;

            $vdiskSize = 180;

            $bom{nics}->{min}   = $nics;
            $bom{memory}->{min} = 16;
        }

        # SBC & OAM
        elsif ( $targetConfig == 111 || $targetConfig == 113 ) { 
            $cores      = 2; # No minimum requirement
            $memory     = 6;
            $vmemory    = 6;
            $nics       = 3;
            $vnics      = 3;
            $diskDrives = 1;

            # Adjust values for OAM node
            if ( $targetConfig == 113 )
            {
                $nics       = 2;
                $vnics      = 2;
            }

            # In case of SWe, since qcow2 and vmdk size for root partition is 25GB and DRBD partition will come
            # as a separate disk, so setting the minimum disk size to be 25GB.
            # CHECKTHIS : Need to check for SWe N:1 case, will it still be minimum 25GB disk?
            if ( &ThisIsConnexIP5000 == TRUE && &isCloud == FALSE )
            {
                $diskSize = $vdiskSize = 25; 
            }
            else
            {
                # In case of Cloud, minimum disk size should be 65GB.
                $diskSize = $vdiskSize = 65; 
            }

            $vcores = $bom{cores}->{min} = $cores;
            my $value    = $processorDB{'Vendor ID'};
            if ( $value eq 'AuthenticAMD') {    
                $model = 2;
                #Supported Min AMD CPU family
                $family = 21;
                $cpuVendor   = 'AuthenticAMD';
            }
            else {
                $model = 26;
                $family = 6;
            }
            $checkfamily = 1;
            $bom{nics}->{min}   = $nics;
            $bom{memory}->{min} = $memory;
        }
        # SWeLite
        elsif ( $targetConfig == 112 ) { 
            $cores      = 2; # No minimum requirement
            $nics       = 4; 
            $vnics      = 4; 
            $diskDrives = 1; 

            # In case of SWe, since qcow2 and vmdk size for root partition is 25GB and DRBD partition will come
            # as a separate disk, so setting the minimum disk size to be 25GB.
            # CHECKTHIS : Need to check for SWe N:1 case, will it still be minimum 25GB disk?
            if ( &ThisIsConnexIP5000 == TRUE && &isCloud == FALSE )
            {
                $diskSize = $vdiskSize = 25; 
            }
            else
            {
                # In case of Cloud, minimum disk size should be 65GB.
                $diskSize = $vdiskSize = 65; 
            }

            $vcores = $bom{cores}->{min} = $cores;
	        my $value    = $processorDB{'Vendor ID'};
            if ( $value eq 'AuthenticAMD') {
                $model = 2;
                #Supported Min AMD CPU family
                $family = 21;
                $cpuVendor   = 'AuthenticAMD';
            }
            else {
                $model = 26;
                $family = 6;
            }
	        $checkfamily = 1;
            $bom{nics}->{min}   = $nics;
            $bom{memory}->{min} = $memory;
        }

        else {
            # Shouldn't get here since we already checked...
            print "Unrecognized Target Application requested [$targetConfig] at line:" . __LINE__ . "\n";
            exit STATUS_INTERNAL_ERROR;
        }

    if ($cores == 8) {
        $cpuName = 'E5-2658 0 | E5-2660 0'; # 2658=NEBS, 2660=non-NEBS
    }
    elsif ($cores == 6) {
        $cpuName = 'E5-2620 0';   # NEBS
    }
    elsif ($cores == 12) {
        $cpuName = 'E5-2620 0';   # NEBS
    }
    else {
        $cpuName = 'Unknown';
    }

    $bom{configName} = $configName;

    $bom{smbios}->{pm}  = $smbios;
    $bom{smbios}->{vm}  = 'Unknown';

    $bom{smVendor}->{pm}  = $smVendor;
    $bom{smVendor}->{vm}  = 'Unknown';

    $bom{smVersion}->{pm}  = $smVersion;
    $bom{smVersion}->{vm}  = 'Unknown';

    $bom{acpiSupport}->{pm}  = $acpiSupport;
    $bom{acpiSupport}->{vm}  = 'Unknown';

    $bom{usbLegacy}->{pm}  = $usbLegacy;
    $bom{usbLegacy}->{vm}  = 'Unknown';

    $bom{sockets}->{pm} = $sockets;
    $bom{sockets}->{vm} = 1;  # Need at least one...

    $bom{cores}->{pm} = $cores;
    $bom{cores}->{vm} = $vcores;

    $bom{cpuVendor}->{pm} = $cpuVendor;
    $bom{cpuVendor}->{vm} = $cpuVendor;

    $bom{cpuModel}->{pm} = $cpuModel;
    $bom{cpuModel}->{vm} = 'Unknown';

    $bom{cpuName}->{pm} = $cpuName;
    $bom{cpuName}->{vm} = 'Unknown';

    $bom{memory}->{pm} = $memory;
    $bom{memory}->{vm} = $vmemory;

    $bom{diskDrives}->{pm} = $diskDrives;
    $bom{diskDrives}->{vm} = $vdiskDrives;   # TODO - How many drives needed for VM?

    $bom{diskSize}->{pm} = $diskSize;
    $bom{diskSize}->{vm} = $vdiskSize;

    $bom{diskRPM}->{pm} = $diskRPM;
    $bom{diskRPM}->{vm} = $diskRPM;

    $bom{hbaCount}->{pm} = $hbaCount;

    $bom{hbaSAS}->{pm} = $hbaSAS;

    $bom{hbaSATA}->{pm} = $hbaSATA;

    $bom{hbaRAID}->{pm} = $hbaRAID;

    $bom{hbaFC}->{pm} = $hbaFC;

    $bom{nics}->{pm} = $nics;
    $bom{nics}->{vm} = $vnics;

    $bom{pwrUnits}->{pm} = $pwrUnits;

    $bom{pwrType}->{pm} = $pwrType;

    $bom{pwrWattage}->{pm} = $pwrWattage;

    $bom{model}->{pm} = $model;
    $bom{model}->{vm} = $model;
    
    $bom{family}->{pm} = $family;
    $bom{family}->{vm} = $family;
    $bom{checkfamily}->{vm} = $checkfamily;
    #print Dumper( \%bom );
    return \%bom;
}

sub ValidateHardware {
    my $bomRef = $_[0];
    my $configStatus; # STANDARD | SUPPORTED | UNSUPPORTED | FAILED | UNKNOWN
    my $retValue;     # SUCCESS | FAILURE
    my $text;
    my @report;
    my $requiredValue;
    my $auditValue;
    my $minValue;
    my $failureCounter     = 0;
    my $unknownCounter     = 0;
    my $unsupportedCounter = 0;
    my $supportedCounter   = 0;
    my $k; # General purpose register, counter, etc.
    my $dbRef;
    my @telcolines;
    my @chars;
    my $line; 

    $machine = (&ThisIsVirtualMachine == TRUE) ? 'vm' : 'pm';

    # TODO
    # This logic needs to be improved...
    # Make this logic match what the LintelHostCheckerDSD.doc states
    # about each Configuration...

    if (exists $cmdlineOptions{S}) {
        push @report, "Inventory simulation overrides applied: [$overrideInventory]";

        while ( my ($keyword, $value) = each %parameterOverrideDB ) {
            push @report, "    $keyword : $value";
        }
    }

    $auditValue = &SystemProductName;

    $k = 0;
    foreach my $host (SUPPORTED_HARDWARE_HOSTS) {
        $k += grep /$host/,$auditValue;
    }

    if ($k == 0) {
        $text = "Unknown Hardware Host:";
        $text .= " [".$auditValue."]";
        $text .= " Fail";
        $failureCounter++;
        push @report, $text;
    }

    # Validate BIOS
    #
    $requiredValue = $$bomRef{smbios}{$machine};
    $auditValue    = $dmidecodeBios{SMBIOS}->{value};

    if ($requiredValue ne 'Unknown') {
        if ($auditValue ne $requiredValue) {
            $text = "Required SMBIOS: ".$requiredValue;
            $text .= " [Found: ".$auditValue."]";

            $text .= " Fail";
            $failureCounter++;

            push @report, $text;
        }
    }

    $requiredValue = $$bomRef{smVendor}{$machine};
    $auditValue    = $dmidecodeDB{'bios-vendor'}->{value};

    if ($requiredValue ne 'Unknown') {
	    if ($auditValue ne $requiredValue) {
	        $text = "Required SMBIOS Vendor: ".$requiredValue;
	        $text .= " [Found: ".$auditValue."]";
	
	        $text .= " Fail";
	        $failureCounter++;
	
	        push @report, $text;
	    }
    }

    $requiredValue = $$bomRef{smVersion}{$machine};
    $auditValue    = $dmidecodeDB{'bios-version'}->{value};

    if ($requiredValue ne 'Unknown') {
	    if ($auditValue ne $requiredValue) {
	        $text = "Required SMBIOS Version: ".$requiredValue;
	        $text .= " [Found: ".$auditValue."]";
	
	        $text .= " Fail";
	        $failureCounter++;
	
	        push @report, $text;
	    }
    }

    $requiredValue = $$bomRef{acpiSupport}{$machine};
    $auditValue    = $dmidecodeBios{'ACPI'}->{value};

    if ($requiredValue ne 'Unknown') {
	    if (!($auditValue =~ /$requiredValue/)) {
	        $text = "Required ACPI Support: ".$requiredValue;
	        $text .= " [Found: ".$auditValue."]";
	
	        $text .= " Fail";
	        $failureCounter++;
	
	        push @report, $text;
	    }
    }

    $requiredValue = $$bomRef{usbLegacy}{$machine};
    $auditValue    = $dmidecodeBios{'USB'}->{value};

    if ($requiredValue ne 'Unknown') {
	    if (!($auditValue =~ /$requiredValue/)) {
	        $text = "Required USB Legacy Support: ".$requiredValue;
	        $text .= " [Found: ".$auditValue."]";
	
	        $text .= " Fail";
	        $failureCounter++;
	
	        push @report, $text;
	    }
    }

    # Must be GenuineIntel or AuthenticAMD CPU
    #
    $requiredValue = $$bomRef{cpuVendor}{$machine};
    $auditValue    = $processorDB{'Vendor ID'};

    if ($auditValue ne $requiredValue) {
	    $text = "Required CPU Vendor: ".$requiredValue;
	    $text .= " [Found: ".$auditValue."]";
	
	    $text .= " Fail";
	    $failureCounter++;
	
	    push @report, $text;
    }

    # If this is a VM then we currently care only about the
    # number of cores. If this is a PM then we want an exact
    # match on the CPU model.

    if (&ThisIsVirtualMachine == FALSE) {
        # This is a physical machine...
	    # Validate processors (super paranoid version)
	    # Exact match on CPU Model name
	    #
	    $requiredValue = $$bomRef{cpuName}{$machine};
	    $auditValue    = $processorDB{'Model name'};
	
	    # Test for exact match on CPU model.
	    #
	    if ($requiredValue ne 'Unknown') {
	        if ($requiredValue !~ /$auditValue/) {
	            $text = "Required CPU Model: ".$requiredValue;
	            $text .= " [Found: ".$auditValue."]";
	
	            $text .= " Unsupported";
	            $unsupportedCounter++;
	
	            push @report, $text;
	        }
	    }
	    $requiredValue = $$bomRef{sockets}{$machine}; 
	    $auditValue    = $processorDB{'Socket count'};

	    # Test for socket count.
	    #
	    if ($requiredValue ne 'Unknown') {
	        if ($requiredValue !~ /$auditValue/) {
	            $text = "Required Socket Count: ".$requiredValue;
	            $text .= " [Found: ".$auditValue."]";
	
	            $text .= " Unsupported";
	            $unsupportedCounter++;
	
	            push @report, $text;
	        }
	    }
    }
    else {
	    my $check = $$bomRef{checkfamily}{$machine};
        if ( $check == 1 ) { 
        # This is a virtual machine...
	    # Validate processors (super paranoid version)
	    # Exact match on CPU Model name
	    #
        my $cpuVendorName = $processorDB{'Vendor ID'};

        if ($cpuVendorName eq 'AuthenticAMD') {
	   
            # CPU Model is not checked for AMD as there is no clarity
            # on model number.
            #
            $requiredValue = $$bomRef{family}{$machine};
	        $auditValue    = $processorDB{'CPU family'};
	        if ($auditValue < $requiredValue) {
		        $text = "Min Required CPU Family: ".$requiredValue;
		        $text .= " [Found: ".$auditValue."]";

		        $text .= " Unsupported";
		        $unsupportedCounter++;

		        push @report, $text;
	        }
        } else {

	        # Test for exact match on CPU model.
	        #
            $requiredValue = $$bomRef{model}{$machine};
            $auditValue    = $processorDB{'Model'};
	        if ($requiredValue > $auditValue) {
		        $text = "Required CPU Model: ".$requiredValue;
		        $text .= " [Found: ".$auditValue."]";

		        $text .= " Unsupported";
		        $unsupportedCounter++;

		        push @report, $text;
	        }
	    
            $requiredValue = $$bomRef{family}{$machine};
	        $auditValue    = $processorDB{'CPU family'};
	        if ($requiredValue !~ /$auditValue/) {
		        $text = "Required CPU Family: ".$requiredValue;
		        $text .= " [Found: ".$auditValue."]";

		        $text .= " Unsupported";
		        $unsupportedCounter++;

		        push @report, $text;
	        }
        }


        # Check for correct number of cores
        #
        $requiredValue = $$bomRef{cores}{$machine};
        $auditValue    = $processorDB{'CPU(s)'};

        if ($auditValue != $requiredValue) {
            $text = "Required CPU Cores: ".$requiredValue;
            $text .= " [Found: ".$auditValue."]";
	        if ($auditValue < $requiredValue) {
	            $text .= " Fail";
	            $failureCounter++;
	        }
	        else {
	            $text .= " Supported";
	            $supportedCounter++;
	        }

            push @report, $text unless ($requiredValue == 0);
        }
      }
    }

    # Validate CPU & MEM resource
    if (&SystemProductName eq 'VMware Virtual Platform')
    {
       if (-e "/usr/bin/vmware-toolbox-cmd")
       {

          my $fCPURES = `vmware-toolbox-cmd stat cpures | awk '{ print \$1 }'`;
          chomp($fCPURES);
          # ignore fractions,system calculates based on decimal number
          #my $cpu_mhz= int (`vmware-toolbox-cmd stat speed | awk '{ print \$1}'`);
          my $cpu_mhz= int (`cat /proc/cpuinfo  | grep -m 1 "cpu MHz" | awk -F" " '{print \$4}'`);
          chomp($cpu_mhz);
          $cpu_mhz= ((int($cpu_mhz /10)) *10) - 5;
          my $ncpu=`grep "processor" /proc/cpuinfo | wc -l`;
          chomp($ncpu);
          my $rCPURES = int (($cpu_mhz * $ncpu) / 2);
          if ($fCPURES < $rCPURES) {
              push @report, sprintf "Required CPU Resource in MHz : %d [Found: %d ] Fail",$rCPURES,$fCPURES;
              # print "Required CPU Resource in MHz : $rCPURES [Found: $fCPURES] Fail\n";
              $failureCounter++;
          }
          else {
              push @report, sprintf "Required CPU Resource in MHz : %d [Found: %d ] Supported",$rCPURES,$fCPURES;
              $supportedCounter++;
          }
       }
       else
       {
           push @report, "vmware tool not installed !!!";
       }
    }
    else
    {
        push @report, "Non-VMware Instance !!!";
    }

    #check if SWe is running on 1vCPU"
    my $ncpu=`grep "processor" /proc/cpuinfo | wc -l`;
    my $hw=`cat /etc/hostType`;
    chomp($hw);
    if($hw eq "ConnexIP5000" && $ncpu < 2)
    {
        push @report, sprintf "NO support of below 2vCPU on SWE : Fail";
        $failureCounter++;
    }

    # Validate memory
    #
    $requiredValue = $$bomRef{memory}{$machine};
    $auditValue    = $memoryDB{'MemTotal'}{value};

    $text = "Required GB RAM: ".$requiredValue;
    $text .= " [Found: ".$auditValue."]";

    if ($auditValue < $requiredValue) {
        $text .= " Fail";
        $failureCounter++;
    }
    else {
        if (&SystemProductName eq 'VMware Virtual Platform')
        {
            if (-e "/usr/bin/vmware-toolbox-cmd")
            {
                my $fMemRes =`vmware-toolbox-cmd stat memres| awk '{print \$1}'`;
                if ($fMemRes < ($requiredValue * 1000)) {
                    push @report, sprintf "Required Memory Reservation in GB : %d [Found: %d MB] Fail",$requiredValue,$fMemRes;
                    $failureCounter++;
                }
                else
                {
                    $text .= " Supported";
                    $supportedCounter++;
                }
            }
            else
            {
                $text .= " Supported";
                $supportedCounter++;
            }
        }
        else
        {
            $text .= " Supported";
            $supportedCounter++;
        }
    }

    push @report, $text;

  
    # Validate hard drives
    #
    $requiredValue = $$bomRef{diskDrives}{$machine};

    # Did we find any RAID drives?
    if ((scalar @raidDB) > 0) {
        $auditValue = scalar @raidDB;
    }
    else {
        $auditValue = scalar @hdDB;
    }

    if ($auditValue != $requiredValue) {
        $text = "Required Hard Drives: ".$requiredValue;
        $text .= " [Found: ".$auditValue."]";

        if ($auditValue > $requiredValue) {
            $text .= " Supported";
            $supportedCounter++;
        }
        else {
            $text .= " Fail";
            $failureCounter++;
        }

        push @report, $text;
    }

    # Did we find any RAID drives?
    if ((scalar @raidDB) > 0) {
        $dbRef = \@raidDB;
    }
    else {
        $dbRef = \@hdDB;
    }

    # if using the repave utility, there is no mirror drive
    my $mirrorDiskPath = `ls -l /dev/disk/by-id/*RIBBON_MIRROR_DRIVE 2>/dev/null | sed -e 's/.* -> //'`;
    my $mirrorDiskName = basename($mirrorDiskPath);
    chomp($mirrorDiskName);

    foreach my $drive (@$dbRef) {
        my $logDirMounted = "false";

        $requiredValue = $$bomRef{diskSize}{$machine};
        # Set the minimum required drive size to 30GB if external volume is attached to the SBC cloud instance and mounted on /var/log 
        if ( &isCloud == TRUE ) {
            `mountpoint -q /var/log`;
            if( $? eq 0) {
                my $extDevice = `findmnt -n -m -o SOURCE --target /var/log`;
                my $extDeviceName = `basename $extDevice`;
                chomp($extDeviceName);
                if ( $$drive{hdName} eq $extDeviceName) {
                    $logDirMounted = "true";
                    $requiredValue = 30;
                }
            }
        }
        $auditValue = $$drive{hdSize};

        next if (&isCloud == TRUE && $logDirMounted eq "false" && $$drive{hdName} !~ /da$/ && $$drive{hdName} !~ /nvme/);

        # skip checking DRBD/mirror disk size as that is user provided and usually less than root disk size
        next if ("$mirrorDiskName" eq $$drive{hdName});

        if ($auditValue != $requiredValue) {
            $text = "Required Drive size: ".$requiredValue;
            $text .= " [Found: ".$auditValue."]";

            if ($auditValue > $requiredValue) {
                $text .= " Supported";
                $supportedCounter++;
            }
            else {
                $text .= " Fail";
                $failureCounter++;
            }

            push @report, $text;
        }
    }

	# For SGX4000 we specifically need to check that the disks are in slots 1 and 2
    if ($cmdlineOptions{p} eq "SGX")
	{
		my $sgxDriveCount = 0;

	    foreach my $drive (@$dbRef) {
			if (($$drive{hdBay} eq 1) || ($$drive{hdBay} eq 2))
			{
				if ($$drive{hdStatus} eq "OK")
				{
					$sgxDriveCount++;
				}
			}
		}

		if ($sgxDriveCount ne 2)
		{
			push @report, "SGX drives must be in slots 1 and 2 - Fail";
            $failureCounter++;
		}
	}

    $requiredValue = $$bomRef{diskRPM}{$machine};

    foreach my $drive (@$dbRef) {
        $auditValue = $$drive{hdRPM};

        # If RPM is zero then the information was not available.
        next if ($auditValue == 0);

        if ($auditValue != $requiredValue) {
            $text = "Required Drive Rotation Speed: ".$requiredValue;
            $text .= " [Found: ".$auditValue."]";

            if ($auditValue > $requiredValue) {
                $text .= " Supported";
                $supportedCounter++;
            }
            else {
                $text .= " Fail";
                $failureCounter++;
            }

            push @report, $text;
        }
    }
  

    # Validate Ethernet NICs
    # Special case for NICs - we allow more NICs than what
    # is required by the BOM without reporting the
    # exception. -DrW
    #
    $requiredValue = $$bomRef{nics}{$machine};
    $auditValue    = scalar @lanDB;

    if ($requiredValue ne 'Unknown') {
        $text = "Required Ethernet NICs: ".$requiredValue;
        $text .= " [Found: ".$auditValue."]";

        # Audit value will be one more than required value
        # because of the lo nic, so use '>' instead of '>='
        if ($auditValue > $requiredValue) {
            $text .= " Supported";
            $supportedCounter++;
        }
        else {
            $text .= " Fail";
            $failureCounter++;
        }

        push @report, $text;
    }

    # Validate Power
    #
    $requiredValue = $$bomRef{pwrUnits}{$machine};
    $auditValue    = scalar @pwrDB;

    if ($requiredValue ne 'Unknown') {
	    if ($auditValue != $requiredValue) {
	        $text = "Required Power Supply Units: ".$requiredValue;
	        $text .= " [Found: ".$auditValue."]";
	
	        if ($auditValue > $requiredValue) {
	            $text .= " Supported";
	            $supportedCounter++;
	        }
	        else {
	            $text .= " Fail";
	            $failureCounter++;
	        }
	
	        push @report, $text;
	    }
    }

    if ((scalar @pwrDB) > 0) {
        $requiredValue = $$bomRef{pwrWattage}{$machine};
        $k = 0;
        foreach my $psRef (@pwrDB) {
            $k++;

            $auditValue = $psRef->{'Max Power Capacity'}->{value};

            if ($requiredValue ne 'Unknown') {
	            if ($auditValue != $requiredValue) {
	                $text = "Required Power Supply Wattage Unit $k: ".$requiredValue;
	                $text .= " [Found: ".$auditValue."]";
	
	                if ($auditValue > $requiredValue) {
	                    $text .= " Supported";
	                    $supportedCounter++;
	                }
	                else {
	                    $text .= " Fail";
	                    $failureCounter++;
	                }
	
	                push @report, $text;
	            }
            }
        }
    }

    # Validate SAS
    #
    $requiredValue = $$bomRef{hbaSAS}{$machine};
    $auditValue    = 0;
    foreach my $hbaRef (@pciDB) {
        $auditValue++ if ($hbaRef->{Type} eq 'SAS');
    }

    if ($requiredValue ne 'Unknown') {
	    if ($auditValue != $requiredValue) {
	        $text = "Required SAS HBAs: ".$requiredValue;
	        $text .= " [Found: ".$auditValue."]";
	
	        if ($auditValue > $requiredValue) {
	            $text .= " Supported";
	            $supportedCounter++;
	        }
	        else {
	            $text .= " Fail";
	            $failureCounter++;
	        }
	
	        push @report, $text;
	    }
    }

    # Validate FC
    #
    $requiredValue = $$bomRef{hbaFC}{$machine};
    $auditValue    = 0;
    foreach my $hbaRef (@pciDB) {
        $auditValue++ if ($hbaRef->{Type} eq 'Fibre Channel');
    }

    if ($requiredValue ne 'Unknown') {
	    if ($auditValue != $requiredValue) {
	        $text = "Required FC HBAs: ".$requiredValue;
	        $text .= " [Found: ".$auditValue."]";
	
	        if ($auditValue > $requiredValue) {
	            $text .= " Supported";
	            $supportedCounter++;
	        }
	        else {
	            $text .= " Fail";
	            $failureCounter++;
	        }
	
	        push @report, $text;
	    }
    }

	# Check for SS7 cards installed
	# this is optional so just print the value
	#
	if (-e "/opt/iphwae/tools/iphDumpCardList")
	{
	    $auditValue    = 0;
	    foreach my $hbaRef (@pciDB) {
	        $auditValue++ if ($hbaRef->{Type} eq 'SS7 Card');
	    }

		if ($auditValue > 0)
		{
			$text = "Interphase SS7 cards: " . $auditValue;
			push @report, $text;
		}
	}
	elsif ($cmdlineOptions{p} eq "SGX")
	{
		push @report, "Interphase SS7 cards: Need to install SGX software and rerun to check for cards\n";
	}

	# Check for NEBS Telco switch setting
	# this is optional so just print the value
	# The ipmitool returns a byte value and if bit
    # 7 (x below) is set to 0 then telco switch is on
    # otherwise telco switch is off. 
    # 1x111111 
    #
    # Execute ipmicmd only if HwWare is not SWe as SWe does not support ipmitool cmds.
	if ( !ThisIsConnexIP5000 )
	{
		if (-e "/usr/bin/ipmitool")
		{
			@telcolines = `ipmitool raw 0x36 0x11 0x01 0x02 >& /dev/null`;
			if ($? eq 0)
			{
				@telcolines = `ipmitool raw 0x36 0x11 0x01 0x02`;
				chomp @telcolines;
				$line = $telcolines[0];
				my @chars = $line =~ /./sg;
				if ($chars[1] =~ /f|e|d|c|7|6|5|4/)
				{
					push @report, "Telco switch  - off";
				}
				else
				{
					push @report, "Telco switch  - on";
				}
			}
			else
			{
				push @report, "Telco switch - Cannot read state";
				push @report, "Telco switch - a) run manually after reboot";
				push @report, "Telco switch - b) ILO version needs to be 1.20 or later";
			}
		}
		else
		{
			push @report, "Telco switch - Cannot read state - ipmitool not installed";
		}
	}
    # retValue     = [STANDARD | SUPPORTED | UNSUPPORTED | FAILED | UNKNOWN]
    # configStatus = [SUCCESS | FAILURE]

    if ($failureCounter != 0) {
        $retValue     = STATUS_FAILURE;
        $configStatus = CONFIG_FAILED;
    }
    elsif ($unknownCounter != 0) {
        $retValue     = STATUS_FAILURE;
        $configStatus = CONFIG_UNKNOWN;
    }
    elsif ($unsupportedCounter != 0) {
        $retValue     = STATUS_FAILURE;
        $configStatus = CONFIG_UNSUPPORTED;
    }
    elsif ($supportedCounter != 0) {
        $retValue     = STATUS_SUCCESS;
        $configStatus = CONFIG_SUPPORTED;
    }
    else {
        $retValue     = STATUS_SUCCESS;
        $configStatus = CONFIG_STANDARD;
    }

    return ($retValue, $configStatus, @report);
}

########## Main #################

# Parse command lines arguments...
# usage: HostCheck -p product-name
#   product-name: psx psxha ems emsrac sbc oam dsi dsiha
# returns: 0 install may proceed "success", -1 install may not proceed "failure"
#
&Usage   if (!(scalar @ARGV));
&Usage   unless getopts(PROGRAM_OPTIONS,\%cmdlineOptions);
&Usage   if ($cmdlineOptions{h});
&Version if ($cmdlineOptions{V});
$cmdlineOptions{p} = uc $cmdlineOptions{p}; # Uppercase option...
&Usage   if (!(&ValidApplication($cmdlineOptions{p})));

# Root privs required for this script!
#
unless ( $< == 0 ) {
    printf "This script requires priv's to run!\n";
    $exitStatus = STATUS_PRIVS_REQUIRED;
    goto OUT;
}

# Initialize program state, etc.
#
&Init;

# Determine if relevant commands are present\installed
# Save paths...
#
MapSystemCommands \%systemCommand;

# Open our log file if directed to do so. Note that this operation will overwrite
# any existing log...
#
open LOGFILE, ">$logFile" or die "Error opening $logFile: $!" unless (exists $cmdlineOptions{L});

# Discover hardware configuration...
#
&Learn;

# Examples of how to debug data structures using Dumper:
#print Dumper( \$logFile );             # Dump a scalar
#print Dumper( \REQ_CMDS );             # Dump a constant
#print Dumper( \%processorDB );         # Dump a hash
#print Dumper( \@lanDB );               # Dump a list
#print Dumper( \@pciDB );
#print Dumper( \%memoryDB );

&ApplyParameterOverrides if (exists $cmdlineOptions{S});;

# The order of these is important...
#
$bomRef = GenerateBOMForTargetApp  $cmdlineOptions{p};
@inventoryReport = DiscoveredInventory $bomRef;

$licenseKey = GetLicenseKeyForTargetApp $cmdlineOptions{p};

($exitStatus, $bomValue, @bomReport) = ValidateHardware $bomRef;

PrintReport( \@inventoryReport ) if (exists $cmdlineOptions{D});

# This line must be printed first...
&LogPrint((bomMATCH)[$bomValue]." Configuration for $cmdlineOptions{p}");

# This line must be printed second...
$server = $dmidecodeDB{'system-product-name'}{value} ;

if ($machine eq 'pm') {
    &LogPrint("Server: $server (Physical machine)");
}
else {
    &LogPrint("Server: $server (Virtual machine)");
}

# This line must be printed third...
&LogPrint("License Key: $licenseKey") if (defined $licenseKey);

if (scalar @bomReport)
{
	PrintReport( \@bomReport );
}

close LOGFILE unless (exists $cmdlineOptions{L});

# Returns:
#    0 = Standard Configuration, exact BOM match
#    0 = Supported Configuration, some BOM variance
#    0 = Unsupported Configuation, configuration not recognized
#   -1 = Failed Configuration, configuration cannot support application
#

########## End Program ##########
OUT:
exit $exitStatus;

__END__

# This section provides an override for hardware learned via discovery.
#
# Valid Keywords are:
#

#CORES     = 0
#CPU_MODEL = 0

#MEMORY_SIZE = 0

#DRIVES     = 0
#DRIVE_SIZE = 0

#NICS = 0

#POWER_SUPPLIES = 0
#WATTS          = 0

# TRUE or FALSE
#VIRTUAL_MACHINE = 0
