#!/usr/bin/perl -w

##########################################################################
# Copyright 2005 VMware, Inc.  All rights reserved. -- VMware Confidential
##########################################################################

#
# esxcfg-linuxnet --
#
#       This script sets up and tears down linux networking files.
#

use lib "/usr/lib/vmware/esx-perl/perl5/site_perl/5.8.0";

use VMware::CmdTool;
use VMware::Log qw(:log);
use VMware::PCI::DeviceManager;
use VMware::PCI::LinuxDeviceManager;
use VMware::Net::NetworkManager;

use warnings;
use strict;

########################################################################
#
# Usage --
#
#       Returns the usage statement as a string.
#
# Results:
#       The usage statement.
#
# Side effects:
#       None.
#
########################################################################

sub Usage
{
   return <<EOD;
esxcfg-linuxnet --setup
                --remove
                -h --help

   The --setup option cannot be combined with the --remove option.
EOD

}


########################################################################
#
# ParseOptions --
#
#       Handle all command-line processing and return a hash
#       containing the results.
#
# Results:
#       A hash holding the processed command line data.
#       Dies with the usage statement on error.
#
# Side effects:
#       Dies with the usage statement on error.
# 
########################################################################

sub ParseOptions
{
   #
   # Define command line options and set up defaults.
   #
   my $cmdHash = {
      "help" => 0,            # Help flag.
      "setup" => 0,           # Set up linux networking files
      "remove" => 0,          # Remove linux networking files
   };
   my $opts = {
      "setup" => \$cmdHash->{setup},
      "remove" => \$cmdHash->{remove},
   };
   unless (VMware::CmdTool::InitTool("esxcfg_linuxnet_cfg",
                                     "esxcfg_linuxnet_log",
                                     $opts, Usage())) {
      exit(-1);
   }

   unless (($cmdHash->{setup} and not $cmdHash->{remove}) or
           ($cmdHash->{remove} and not $cmdHash->{setup})) {
      die Usage();
   }

   return $cmdHash;
}

########################################################################
#
# LoadNetworkManager --
#       Loads the network manager.
#
# Results:
#       A VMware::Net::NetworkManager instance on success, undef otherwise.
#
# Side effects:
#       Reads esx.conf
#
########################################################################

sub LoadNetworkManager
{
   my $config = shift;  # IN: The configuration tree

   my $netmgr = new VMware::Net::NetworkManager($config->GetTree(['net']));
   unless ($netmgr) {
      LogError("Failed to load network manager");
      exit(-1);
   }

   return $netmgr;
}


########################################################################
#
# FindFirstVswitchPnic --
#       Finds the lowest-numbered pnic connected to the given vswitch.
#
# Results:
#       On success, returns the device object corresponding to the
#       lowest-numbered pnic. Otherwise, returns undef.
#
# Side effects:
#       None.
#
########################################################################

sub FindFirstVswitchPnic
{
   my $devmgr = shift;  # IN: The device manager
   my $vswitch = shift; # IN: The vswitch to search.

   my @uplinks = $vswitch->GetUplinks();
   my $uplink = undef;
   if (@uplinks) {
      $uplink = $uplinks[0];
      LogInfo("Found pnic " . $uplink->GetPnic() .
              " on vswitch " . $vswitch->GetName());
   } else {
      return undef;
   }

   # We've got the pnic vmkname. Now find its device object.
   my $device = $uplink->GetPnicDevice($devmgr);
   unless ($device) {
      LogError("Could not find device for pnic " . $uplink->GetPnic());
      return undef;
   }

   return $device;
}


########################################################################
#
# FindEthByPnic --
#       Finds the name of the eth device corresponding to the given pnic.
#
# Results:
#       On success, returns the name of the eth device.
#       Otherwise, returns undef.
#
# Side effects:
#       None.
#
########################################################################

sub FindEthByPnic
{
   my $hwconf = shift;  # IN: The hwconf to search.
   my $pnic = shift;    # IN: The pnic device object to search for.

   my $device = $hwconf->GetDevice($pnic->GetPCIString());
   unless (defined $device and $device->IsNetClassDevice()) {
      return undef;
   }

   return $device->GetDevice();
}


########################################################################
#
# MapInterfaces --
#       For every vswitch which is connected to at least one vswif and
#       one pnic, map one of its vswifs to the eth device associated with
#       one of its pnics.
#
# Results:
#       On success, returns a reference to a list of hash references, each
#       corresponding to an interface mapping. An example entry might look
#       something like:
#           my $entry = {
#              vswif => <vswif interface object>
#              pnic => <pnic device object>
#              eth   => "eth0"
#           };
#
#       Otherwise, returns undef.
#
# Side effects:
#       Reads esx.conf
#
########################################################################

sub MapInterfaces
{
   my $config = shift;  # IN: The configuration tree
   my $netmgr = shift;  # IN: The network manager

   my $devmgr = new VMware::PCI::DeviceManager($config->GetTree(['device']));
   unless ($devmgr) {
      LogError("Failed to load device manager");
      return undef;
   }

   my $hwconf = new VMware::PCI::LinuxDeviceManager();
   unless ($hwconf) {
      LogError("Failed to load linux hardware configuration database");
      return undef;
   }

   my @mappings = ();
   my %vswitches = ();
   foreach my $vswif ($netmgr->GetVswifs()) {
      my $portgroupUid = $vswif->GetPortGroup();
      unless ($portgroupUid) {
         LogError("Error: vswif " . $vswif->GetName() .
                  " does not have a portgroup associated with it");
         return undef;
      }

      my $vswitch = $netmgr->GetVSwitchByPortGroup($portgroupUid);

      # Vswitches can have multiple portgroups, and we want
      # at most one vswif per vswitch. Filter out duplicates.
      if (defined $vswitches{$vswitch->GetName()}) {
         next;
      }

      $vswitches{$vswitch->GetName()} = $vswitch;

      # Find lowest-numbered pnic attached to this portgroup
      my $pnic = FindFirstVswitchPnic($devmgr, $vswitch);
      unless (defined $pnic) {
         LogDebug("Skipping vswitch " . $vswitch->GetName() . ": No pnics");
         next;
      }
      
      # Find eth device associated with this pnic (for example, "eth0")
      my $eth = FindEthByPnic($hwconf, $pnic);
      unless (defined $eth) {
         LogError("Cannot find eth device for " . $pnic->GetVMKDeviceName());
         next;
      }

      push @mappings, {
         vswif => $vswif,
         pnic  => $pnic,
         eth   => $eth,
      };
   }

   return \@mappings;
}

########################################################################
#
# AddMapping --
#       Adds a network interface based on the provided mapping.
#
# Results:
#       1 on success, 0 otherwise.
#
# Side effects:
#       None.
#
#########################################################################

sub AddMapping
{
   my $mapping = shift; # IN: The mapping to write out.
   my $netmgr = shift;  # IN: The network manager

   LogInfo("Mapping " .  $mapping->{vswif}->GetName() .
           " to " .  $mapping->{eth});

   my $interface = $mapping->{vswif}->Copy();
   my $name = $mapping->{eth};
   $interface->SetDevice($name);
   $interface->SetAutogenerated("linuxnet");
   $interface->SetPortGroup(undef);
   $interface->SetComment($mapping->{pnic}->GetName());
   
   if ($netmgr->GetInterface($name)) {
      # Override any legacy ifcfg-ethN scripts.
      LogWarn("Warning: Removing old interface for $name");
      unless ($netmgr->RemoveInterface($name)) {
         return 0;
      }
   }

   return $netmgr->AddInterface($interface);
}


########################################################################
#
# CmdSetup --
#       Implements the "--setup" command. Generates linux networking
#       files.
#
# Results:
#       1 on success, 0 otherwise.
#
# Side effects:
#       Everything.
#
########################################################################

sub CmdSetup
{
   my $config = shift;  # IN: The configuration tree
   my $netmgr = shift;  # IN: The network manager

   my $mappings = MapInterfaces($config, $netmgr);
   unless ($mappings and @$mappings) {
      LogError("No vswif<->eth mappings found");
      return 0;
   }

   foreach my $mapping (@$mappings) {
      unless (AddMapping($mapping, $netmgr)) {
         return 0;
      }
   }

   # Disable GATEWAYDEV
   my $network = $netmgr->GetNetworkFile();
   $network->Rename("GATEWAYDEV", "GATEWAYDEV_OFF", 1);

   # Commit to disk
   unless ($netmgr->CommitInterfaces() &&
           $netmgr->CommitNetworkFile()) {
      return 0;
   }
   
   return 1;
}


########################################################################
#
# CmdRemove --
#       Implements the "--remove" command. Removes linux networking
#       files.
#
# Results:
#       1 on success, 0 otherwise.
#
# Side effects:
#       Removes all ifcfg scripts with the AUTOGENERATED=linuxnet flag.
#
########################################################################

sub CmdRemove
{
   my $config = shift;  # IN: The configuration tree
   my $netmgr = shift;  # IN: The network manager

   foreach my $interface ($netmgr->GetInterfaces()) {
      if ($interface->GetAutogenerated() eq "linuxnet") {
         LogInfo("Removing autogenerated interface " .
                 $interface->GetName());
         unless ($netmgr->RemoveInterface($interface->GetName())) {
            return 0;
         }
      }
   }

   # Re-enable GATEWAYDEV. If GATEWAYDEV is already set, do not
   # clobber it.
   my $network = $netmgr->GetNetworkFile();
   $network->Rename("GATEWAYDEV_OFF", "GATEWAYDEV", 0);

   # Commit to disk
   unless ($netmgr->CommitInterfaces() &&
           $netmgr->CommitNetworkFile()) {
      return 0;
   }

   return 1;
}


########################################################################
#
# main --
#
# Results:
#       Exit code 0 on success, non-zero otherwise.
#
# Side effects:
#       Everything.
#
########################################################################

sub main
{
   my $cmdHash = ParseOptions();

   my $config = VMware::CmdTool::LoadConfig();
   unless (defined $config) {
      VMware::CmdTool::Finish(0);
   }

   my $netmgr = LoadNetworkManager($config);

   my $success = 1;
   if ($cmdHash->{setup}) {
      $success = CmdSetup($config, $netmgr);
   } elsif ($cmdHash->{remove}) {
      $success = CmdRemove($config, $netmgr);
   }

   VMware::CmdTool::Finish($success);
}

main();
