package xCAT::VMCommon;
use Socket;
use strict;

#Functions common to virtualization management (KVM, Xen, VMware)
sub grab_table_data {    #grab table data relevent to VM guest nodes
    my $noderange = shift;
    my $cfghash   = shift;
    my $callback  = shift;
    my $vmtab     = xCAT::Table->new("vm");
    my $vpdtab    = xCAT::Table->new("vpd");
    my $hmtab     = xCAT::Table->new("nodehm");
    my $nttab     = xCAT::Table->new("nodetype");

    #my $sitetab = xCAT::Table->new("site");
    $cfghash->{site}->{genmacprefix} = $::XCATSITEVALS{genmacprefix}; #xCAT::Utils->get_site_attribute('genmacprefix');
    if ($hmtab) {
        $cfghash->{nodehm} = $hmtab->getNodesAttribs($noderange, ['serialspeed']);
    }
    if ($nttab) {
        $cfghash->{nodetype} = $nttab->getNodesAttribs($noderange, [ 'os', 'arch', 'profile' ]); #allow us to guess RTC config
                #also needed for vmware guestid and also data for vmmaster
    }
    unless ($vmtab) {
        $callback->({ data => ["Cannot open vm table"] });
        return;
    }
    if ($vpdtab) {
        $cfghash->{vpd} = $vpdtab->getNodesAttribs($noderange, ['uuid']);
    }
    $cfghash->{vm} = $vmtab->getNodesAttribs($noderange, [ 'node', 'host', 'migrationdest', 'cfgstore', 'storage', 'storagecache', 'storageformat', 'vidmodel', 'vidproto', 'vidpassword', 'storagemodel', 'memory', 'cpus', 'nics', 'nicmodel', 'bootorder', 'virtflags', 'datacenter', 'guestostype', 'othersettings', 'master' ]);
    my $mactab = xCAT::Table->new("mac",     -create => 1);
    my $nrtab  = xCAT::Table->new("noderes", -create => 1);
    $cfghash->{mac} = $mactab->getAllNodeAttribs(['mac'], 1);
    my $macs;
    my $mac;
    foreach (keys %{ $cfghash->{mac} }) {
        $macs = $cfghash->{mac}->{$_}->[0]->{mac};
        foreach $mac (split /\|/, $macs) {
            $mac =~ s/\!.*//;
            if ($cfghash->{usedmacs}->{ lc($mac) }) {
                $cfghash->{usedmacs}->{ lc($mac) } += 1;
            } else {
                $cfghash->{usedmacs}->{ lc($mac) } = 1;
            }
        }
    }
}

sub macsAreUnique { #internal function, do not call, argument format may change without warning
        #Take a list of macs, ensure that in the table view, they are unique.
        #this should be performed after the macs have been committed to
        #db
    my $cfghash = shift;
    my @macs    = @_;
    my $mactab  = xCAT::Table->new("mac", -create => 0);
    unless ($mactab) {
        return 1;
    }
    $cfghash->{mac} = $mactab->getAllNodeAttribs(['mac'], 1);
    $cfghash->{usedmacs} = {};
    my $macs;
    my $mac;
    foreach (keys %{ $cfghash->{mac} }) {
        $macs = $cfghash->{mac}->{$_}->[0]->{mac};
        foreach $mac (split /\|/, $macs) {
            $mac =~ s/\!.*//;
            if ($cfghash->{usedmacs}->{ lc($mac) }) {
                $cfghash->{usedmacs}->{ lc($mac) } += 1;
            } else {
                $cfghash->{usedmacs}->{ lc($mac) } = 1;
            }
        }
    }
    foreach $mac (@macs) {
        if ($cfghash->{usedmacs}->{ lc($mac) } > 1) {
            return 0;
        }
    }
    return 1;
}

sub requestMacAddresses {

    #This function combs through the list of nodes to assure every vm.nic declared nic has a mac address
    my $tablecfg   = shift;
    my $neededmacs = shift;
    my $mactab     = xCAT::Table->new("mac", -create => 1);
    my $node;
    my @allmacs;
    my $complete = 0;
    my $updatesneeded;
    my $vpdupdates;
    srand(); #Re-seed the rng.  This will make the mac address generation less deterministic

    while (not $complete and scalar @$neededmacs) {
        foreach $node (@$neededmacs) {
            my $nicdata = $tablecfg->{vm}->{$node}->[0]->{nics};
            unless ($nicdata) { $nicdata = "" }
            my @nicsneeded = split /,/, $nicdata;
            my $count = scalar(@nicsneeded);

            my $macdata = $tablecfg->{mac}->{$node}->[0]->{mac};
            unless ($macdata) { $macdata = "" }
            my @macs;
            my $macaddr;
            foreach $macaddr (split /\|/, $macdata) {
                $macaddr =~ s/\!.*//;
                push @macs, lc($macaddr);
            }
            $count -= scalar(@macs);
            if ($count > 0) {
                $updatesneeded->{$node}->{mac} = 1;
            }

            while ($count > 0) {    #still need more, autogen
                $macaddr = "";
                while (not $macaddr) {
                    $macaddr = lc(genMac($node, $tablecfg->{site}->{genmacprefix}));
                    push @allmacs, $macaddr;
                    if ($tablecfg->{usedmacs}->{$macaddr}) {
                        $macaddr = "";
                    }
                }
                $count--;
                $tablecfg->{usedmacs}->{$macaddr} = 1;
                if (not $macdata) {
                    $macdata = $macaddr;
                } else {
                    $macdata .= "|" . $macaddr . "!*NOIP*";
                }
                push @macs, $macaddr;
            }
            if (defined $updatesneeded->{$node}) {
                $updatesneeded->{$node}->{mac} = $macdata;
                $tablecfg->{dhcpneeded}->{$node} = 1; #at our leisure, this dhcp binding should be updated
            }

            #now that macs are done, do simple uuid... (done after to benefit from having at least one mac address)
            unless ($tablecfg->{vpd}->{$node}->[0]->{uuid}) {
                my $umac = $macs[0];
                my $uuid;
                if ($umac) {
                    $uuid = xCAT::Utils::genUUID(mac => $umac);
                } else {    #shouldn't be possible, but just in case
                    $uuid = xCAT::Utils::genUUID();
                }
                $vpdupdates->{$node}->{uuid} = $uuid;
                $tablecfg->{vpd}->{$node} = [ { uuid => $uuid } ];
                $tablecfg->{dhcpneeded}->{$node} = 1; #at our leisure, this dhcp binding should be updated
            }

            #TODO: LOCK if a distributed lock management structure goes in place, that may be a faster solution than this
            #this code should be safe though as it is, if a tiny bit slower
            #can also be sped up by doing it for a noderange in a sweep instead of once per node
            #but the current architecture has this called at a place that is unaware of the larger context
            #TODO2.4 would be either the lock management or changing this to make large scale mkvm faster
        }
        if (defined $vpdupdates) {
            my $vpdtab = xCAT::Table->new('vpd', -create => 1);
            $vpdtab->setNodesAttribs($vpdupdates);
        }
        if (defined $updatesneeded) {
            my $mactab = xCAT::Table->new('mac', -create => 1);
            $mactab->setNodesAttribs($updatesneeded);
            if (macsAreUnique($tablecfg, @allmacs)) {
                $complete = 1;
            } else {    #Throw away ALL macs and try again
                        #this currently includes manually specified ones
                foreach $node (keys %$updatesneeded) {
                    $tablecfg->{mac}->{$node}->[0]->{mac} = "";
                }
                $tablecfg->{usedmacs} = {};
            }
        }
    }

    #    $cfghash->{usedmacs}-{lc{$mac}};
}

sub getMacAddresses {
    my $tablecfg = shift;
    my $node     = shift;
    my $count    = shift;
    my $mactab   = xCAT::Table->new("mac", -create => 1);
    my $macdata  = $tablecfg->{mac}->{$node}->[0]->{mac};
    unless ($macdata) { $macdata = "" }
    my @macs;
    my $macaddr;

    foreach $macaddr (split /\|/, $macdata) {
        $macaddr =~ s/\!.*//;
        push @macs, lc($macaddr);
    }
    $count -= scalar(@macs);
    my $updatesneeded = 0;
    if ($count > 0) {
        $updatesneeded = 1;
    }

    srand(); #Re-seed the rng.  This will make the mac address generation less deterministic
    while ($count > 0) {
        while ($count > 0) {    #still need more, autogen
            $macaddr = "";
            while (not $macaddr) {
                $macaddr = lc(genMac($node, $tablecfg->{site}->{genmacprefix}));
                if ($tablecfg->{usedmacs}->{$macaddr}) {
                    $macaddr = "";
                }
            }
            $count--;
            $tablecfg->{usedmacs}->{$macaddr} = 1;
            if (not $macdata) {
                $macdata = $macaddr;
            } else {
                $macdata .= "|" . $macaddr . "!*NOIP*";
            }
            push @macs, $macaddr;
        }
        if ($updatesneeded) {
            my $mactab = xCAT::Table->new('mac', -create => 1);
            $mactab->setNodeAttribs($node, { mac => $macdata });
            $tablecfg->{dhcpneeded}->{$node} = 1; #at our leisure, this dhcp binding should be updated
        }

        #TODO: LOCK if a distributed lock management structure goes in place, that may be a faster solution than this
        #this code should be safe though as it is, if a tiny bit slower
        #can also be sped up by doing it for a noderange in a sweep instead of once per node
        #but the current architecture has this called at a place that is unaware of the larger context
        #TODO2.4 would be either the lock management or changing this to make large scale mkvm faster
        unless (macsAreUnique($tablecfg, @macs)) { #Throw away ALL macs and try again
                #this currently includes manually specified ones
            $count += scalar(@macs);
            @macs    = ();
            $macdata = "";
        }
    }

    return @macs;

    #    $cfghash->{usedmacs}-{lc{$mac}};

}

sub genMac { #Generates a mac address for a node, does NOT assure uniqueness, calling code needs to do that
    my $node   = shift;
    my $prefix = shift;
    if ($prefix) {    #Specific prefix requested, honor it
        my $tail = int(rand(0xffffff)); #With only 24 bits of space, use random bits;
        if ($prefix eq '00:50:56') { #vmware reserves certain addresses in their scheme if this prefix used
            $tail = $tail & 0x3fffff;    #mask out the two bits in question
        }
        $tail = sprintf("%06x", $tail);
        $tail =~ s/(..)(..)(..)/:$1:$2:$3/;
        return $prefix . $tail;
    }

    #my $allbutmult = 0xfeff; # to & with a number to ensure multicast bit is *not* set
    #my $locallyadministered = 0x200; # to | with the 16 MSBs to indicate a local mac
    #my $leading = int(rand(0xffff));
    #$leading = $leading & $allbutmult;
    #$leading = $leading | $locallyadministered;
    #for the header, we used to use all 14 possible bits, however, if a guest mac starts with 0xfe then libvirt will construct a bridge that looks identical
    #First thought was to go to 13 bits, but by fixing our generated mac addresses to always start with the same byte and still be unique
    #this induces libvirt to do unique TAP mac addresses
    my $leading = int(rand(0xff));
    $leading = $leading | 0x4200;

    #If this nodename is a resolvable name, we'll use that for the other 32 bits
    my $low32;
    my $n;
    if ($n = inet_aton($node)) {
        $low32 = unpack("N", $n);
    }
    unless ($low32) {    #If that failed, just do 32 psuedo-random bits
        $low32 = int(rand(0xffffffff));
    }
    my $mac;
    $mac = sprintf("%04x%08x", $leading, $low32);
    $mac =~ s/(..)(..)(..)(..)(..)(..)/$1:$2:$3:$4:$5:$6/;
    return $mac;

}
1;
