#!/usr/bin/perl -w
#
# Copyright 2006 VMware, Inc.  All rights reserved.
#
# USAGE:
#
# vicfg-vswitch.pl [GENERAL_VIPERL_OPTIONS] [ADDITIONAL_OPTIONS]
# where acceptable ADDITIONAL_OPTIONS are the following:
#
# --list                              list vswitches and port groups
# --add <vswitch>                     add vswitch name
# --delete <vswitch>                  delete vswitch
# --link pnic <vswitch>               Sets a pnic as an uplink for the switch
# --unlink pnic <vswitch>             Removes a pnic from the uplinks for the switch
# --check <vswitch>                   check if vswitch exists (return 0 if no; 1 if yes)
# --add-pg <pgname> <vswitch>         adds port group
# --del-pg <pgname> <vswitch>         deletes port group
# --add-pg-uplink pnic --pg <pgname>  add an uplink for portgroup
# --del-pg-uplink pnic --pg <pgname>  delete an uplink for portgroup
# --mtu num <vswitch>                 sets the mtu of the vswitch
# --vlan <#> --pg <pgname> <vswitch>  Updates vlan id for port group
# --check-pg --pg <pgname>            check if port group exists (return 0 if no; 1 if yes)
# --check-pg --pg <pgname> <vswitch>  check if port group exists on a particular vswitch 
# 
# Example:
#
# vicfg-vswitch.pl --add-pg foo vSwitch0
# vicfg-vswitch.pl --mtu 9000 vSwitch0
#

my @options = (
    ['list'],                               # esxcfg-vswitch --list
    ['add'],                                # esxcfg-vswitch --add vswitch
    ['delete'],                             # esxcfg-vswitch --delete vswitch
    ['link', '_default_'],                  # esxcfg-vswitch --link pnic vswitch
    ['unlink', '_default_'],                # esxcfg-vswitch --unlink pnic vswitch
    ['check'],                              # esxcfg-vswitch --check vswitch    
    ['add-pg', '_default_'],                # esxcfg-vswitch --add-pg pgname vswitch
    ['del-pg', '_default_'],                # esxcfg-vswitch --del-pg pgname vswitch
    ['add-pg-uplink', 'pg', '_default_'],   # esxcfg-vswitch --add-pg-uplink pnic pgname vswitch
    ['del-pg-uplink', 'pg', '_default_'],   # esxcfg-vswitch --del-pg-uplink pnic pgname vswitch
    ['add-dvp-uplink', 'dvp', '_default_'], # esxcfg-vswitch --add-dvp-uplink pnic dvp dvsname
    ['del-dvp-uplink', 'dvp', '_default_'], # esxcfg-vswitch --del-dvp-uplink pnic dvp dvsname    
    ['vlan', 'pg', '_default_'],            # esxcfg-vswitch --vlan n --pg name vswitch
    ['check-pg', '_default_'],              # esxcfg-vswitch --check-pg pgname vswitch 
    ['mtu', '_default_'],                   # esxcfg-vswitch --mtu num vswitch
    ['get-cdp'],                            # esxcfg-vswitch --get-cdp vswitch
    ['set-cdp', '_default_'],               # esxcfg-vswitch --set-cdp value vswitch
    ['check-pg']                            # esxcfg-vswitch --check-pg pgname        
    );

use strict;
use warnings;
use Getopt::Long;

use VMware::VIRuntime;
use VMware::VILib;
use VMware::VIExt;

my %opts = (
   vihost => {
      alias => "h",
      type => "=s",
      help => qq!    The host to use when connecting via a vCenter Server. !,
      required => 0,
   },
   'list' => {
      alias => "l",
      type => "",
      help => qq!    List vswitches and port groups!,
      required => 0,
   },
   'add' => {
      alias => "a",
      type => "=s",
      help => qq!    Add a new virtual switch!,
      required => 0,
   },
   'delete' => {
      alias => "d",
      type => "=s",
      help => qq!    Delete the virtual switch!,
      required => 0,
   },
   'link' => {
      alias => "L",
      type => "=s",
      help => qq!    Sets a pnic as an uplink for the virtual switch!,
      required => 0,
   },
   'unlink' => {
      alias => "U",
      type => "=s",
      help => qq!    Removes a pnic from the uplinks for the virtual switch!,
      required => 0,
   },
   'check' => {
      alias => "c",
      type => "=s",
      help => qq!    Check to see if virtual switch exists!,
      required => 0,
   },
   'add-pg' => {
      alias => "A",
      type => "=s",
      help => qq!    Add a portgroup to a virtual switch!,
      required => 0,
   },
   'del-pg' => {
      alias => "D",
      type => "=s",
      help => qq!    Delete the portgroup from the virtual switch!,
      required => 0,
   },
   'add-pg-uplink' => {
      alias => "M",
      type => "=s",
      help => qq!    Add an uplink adapter (pnic) to a portgroup (valid for vSphere 4.0 and later)!,
      required => 0,
   },
   'del-pg-uplink' => {
      alias => "N",
      type => "=s",
      help => qq!    Delete an uplink adapter from a portgroup (valid for vSphere 4.0 and later)!,
      required => 0,
   },   
   'add-dvp-uplink' => {
      alias => "P",
      type => "=s",
      help => qq!    Add an uplink adapter (pnic) to a DVPort (valid for vSphere 4.0 and later)!,
      required => 0,
   },
   'del-dvp-uplink' => {
      alias => "Q",
      type => "=s",
      help => qq!    Delete an uplink adapter from a DVPort (valid for vSphere 4.0 and later)!,
      required => 0,
   },      
   'vlan' => {
      alias => "v",
      type => "=s",
      help => qq!    Set vlan id for portgroup specified by -p!,
      required => 0,
   },
   'check-pg' => {
      alias => "C",
      type => "=s",
      help => qq!    Check to see if a portgroup exists!,
      required => 0,
   },
   'mtu' => {
      alias => "m",
      type => "=i",
      help => qq!    Set MTU for the virtual switch!,
      required => 0,
   },
   'get-cdp' => {
      alias => "b",
      type => "=s",
      help => qq!    Print the current CDP setting for this virtual switch (valid for vSphere 4.0 and later)!,
      required => 0,
   },
   'set-cdp' => {
      alias => "B",
      type => "=s",
      help => qq!    Set the CDP status for a given virtual switch (valid for vSphere 4.0 and later).  
          To set pass "down", "listen", "advertise", or "both"!,
      required => 0,
   },   
   'pg' => {
      alias => "p",
      type => "=s",
      help => qq!    The name of the portgroup!,
      required => 0,
   },
   'dvp' => {
      alias => "V",
      type => "=s",
      help => qq!    The name of the DVPort (valid for vSphere 4.0 and later)!,
      required => 0,
   },         
   '_default_' => {
      type => "=s",
      argval => "vswitch",
      help => qq!    The name of the vswitch!,
      required => 0,
   },
);

Opts::add_options(%opts);
Opts::parse();
Opts::validate();

my $login = 0;

CheckValues();
Util::connect();

$login = 1;
my $exitStatus = 1;                     # assume success

my $host_view = VIExt::get_host_view(1, ['configManager.networkSystem']);
Opts::assert_usage(defined($host_view), "Invalid host.");

#
# find the host
#

my $network_system = Vim::get_view (mo_ref => $host_view->{'configManager.networkSystem'});

   #
   # cycle through various operations
   #
   if (defined OptVal('list')) {
      ListVirtualSwitch ($network_system);
   }
   elsif (defined OptVal('add')) {
      # bug 373817
      my @arr = split(':', OptVal('add'));
      if(defined $arr[1]) {
         my $hostVirtualSwitchSpec = HostVirtualSwitchSpec->new(numPorts => $arr[1]);
         eval {$network_system->AddVirtualSwitch ('vswitchName' => $arr[0],
                                                  'spec' => $hostVirtualSwitchSpec);};
         if ($@) {
            VIExt::fail($@->fault_string);
         }
      }
      else {
         eval {$network_system->AddVirtualSwitch ('vswitchName' => OptVal('add'));};
         if ($@) {
            VIExt::fail($@->fault_string);
         }
      }
   }
   elsif (defined OptVal('delete')) {
      eval { $network_system->RemoveVirtualSwitch ('vswitchName' => OptVal('delete')); };
      if ($@) {
         VIExt::fail($@->fault_string);
      }
   }
   elsif (defined OptVal('link')) {
      UpdateUplinks ($network_system, OptVal('_default_'), OptVal('link'), 1);
   }
   elsif (defined OptVal('unlink')) {
      UpdateUplinks ($network_system, OptVal('_default_'), OptVal('unlink'), 0);
   }
   elsif (defined OptVal('vlan')) {
      UpdatePortGroupVlan ($network_system, OptVal('_default_'), OptVal('pg'), OptVal('vlan'));
   }
   elsif (defined OptVal('add-pg-uplink')) {
      UpdatePortGroupAddUplink ($network_system, OptVal('_default_'), OptVal('pg'), OptVal('add-pg-uplink'));
   }
   elsif (defined OptVal('del-pg-uplink')) {
      UpdatePortGroupDelUplink ($network_system, OptVal('_default_'), OptVal('pg'), OptVal('del-pg-uplink'));
   } 
   elsif (defined OptVal('add-dvp-uplink')) {
      UpdateDVPAddUplink ($network_system, OptVal('_default_'), OptVal('add-dvp-uplink'), OptVal('dvp'));
   }
   elsif (defined OptVal('del-dvp-uplink')) {
      UpdateDVPDelUplink ($network_system, OptVal('_default_'), OptVal('del-dvp-uplink'), OptVal('dvp'));
   }
   elsif (defined OptVal('check')) {
      $exitStatus = (defined FindVSwitchbyName ($network_system, OptVal('check'))) ? 1 : 0; 
      print "$exitStatus\n";
   }
   elsif (defined OptVal('add-pg')) {
      AddPortGroup ($network_system, OptVal('add-pg'), OptVal('_default_'), OptVal('vlan'));
   }
   elsif (defined OptVal('del-pg')) {
      RemovePortGroup ($network_system, OptVal('del-pg'));
   }
   elsif (defined OptVal('check-pg')) {
      $exitStatus = (defined FindPortGroupbyName ($network_system, OptVal('_default_'), 
                                                  OptVal('check-pg'))) ? 1 : 0;
      print "$exitStatus\n";
   }
   elsif (defined OptVal('mtu')) {
      UpdateMTU ($network_system, OptVal('_default_'), OptVal('mtu'));
   }
   elsif (defined OptVal('get-cdp')) {
      GetCDP ($network_system, OptVal('get-cdp'));      
   }
   elsif (defined OptVal('set-cdp')) {
      SetCDP ($network_system, OptVal('_default_'), OptVal('set-cdp'));      
   }

Util::disconnect();

sub OptVal {
  my $opt = shift;
  return Opts::get_option($opt);
}

# Retrieve the set of non viperl-common options for further validation
sub GetSuppliedOptions {
  my @optsToCheck = 
     qw(list add check delete link unlink add-pg del-pg add-pg-uplink del-pg-uplink check-pg vlan pg mtu add-dvp-uplink del-dvp-uplink get-cdp set-cdp dvp _default_);
  my %supplied = ();

  foreach (@optsToCheck) {
     if (defined(Opts::get_option($_))) {
        $supplied{$_} = 1;
     }
  }

  return %supplied;
}

use Data::Dumper;

sub getPnicName {
   my ($network_system, $pNics) = @_;
   
   my $pNicName = "";            
   my $pNicKey = "";
   foreach (@$pNics) {
      $pNicKey = $_; 

      if ($pNicKey ne "") {
         $pNics = $network_system->networkInfo->pnic;
         foreach my $pNic (@$pNics) {
            if ($pNic->key eq $pNicKey) {
               $pNicName = $pNicName ? ("$pNicName," . $pNic->device) : $pNic->device;
            }
         }
      }
   }
   return $pNicName;
}

sub ListVirtualSwitch {
   my ($network_system) = @_;
   my $vSwitches = $network_system->networkInfo->vswitch;
   my $pSwitches = undef;
   
   # eval to support pre-K/L version
   eval {
      $pSwitches = $network_system->networkInfo->proxySwitch;
   };
   
   foreach my $vSwitch (@$vSwitches) {
      my $mtu = "";
      my $pNicName = getPnicName($network_system, $vSwitch->pnic);
      my $sNicName = $pNicName;
      
      $mtu = $vSwitch->{mtu} if defined($vSwitch->{mtu});
 
      print "Switch Name     Num Ports       Used Ports      Configured Ports    MTU     Uplinks\n";

      printf("%-16s%-16s%-16s%-20s%-8s%-16s\n\n", 
             $vSwitch->name, 
             $vSwitch->numPorts, 
             $vSwitch->numPorts - $vSwitch->numPortsAvailable,
             $vSwitch->numPorts,
             $mtu, 
             $pNicName);
             
      my $portGroups = $vSwitch->portgroup;
      print "   PortGroup Name                VLAN ID   Used Ports      Uplinks\n";
      foreach my $port (@$portGroups) {         
         my $pg = FindPortGroupbyKey ($network_system, $vSwitch->key, $port);
         next unless (defined $pg);
         my $usedPorts = (defined $pg->port) ? $#{$pg->port} + 1 : 0;         
          
         if (defined($pg->spec->policy->nicTeaming) &&
             defined($pg->spec->policy->nicTeaming->nicOrder)) {
            $pNicName = "";
            my $pNics = $pg->spec->policy->nicTeaming->nicOrder->activeNic;
            foreach my $pNic (@$pNics) {
               $pNicName = $pNicName ? ("$pNicName," . $pNic) : $pNic;
            }
         } else {
            $pNicName = $sNicName;
         }
         printf("   %-30s%-10s%-16s%-16s\n", 
                $pg->spec->name, 
                $pg->spec->vlanId, 
                $usedPorts, 
                $pNicName);
      }
      print "\n";
   }
   
   # bug 371093
   if (defined($pSwitches)) {
      print "DVS Name                 Num Ports   Used Ports  Configured Ports  Uplinks\n";
      my $service_content = Vim::get_service_content();
      my $dvs_manager = $service_content->dvSwitchManager;
      foreach my $pSwitch (@$pSwitches) {
         my $pNicName = getPnicName($network_system, $pSwitch->pnic);         
         printf("%-25s%-12s%-12s%-18s%-16s\n\n",
                $pSwitch->dvsName,
                $pSwitch->numPorts,
                $pSwitch->numPorts - $pSwitch->numPortsAvailable,
                $pSwitch->numPorts,
                $pNicName);
         my $dvsSwitch = Vim::get_view(mo_ref=>$dvs_manager)->QueryDvsByUuid(uuid=>$pSwitch->dvsUuid);
         #bug - 529155
         #my $dvs_port_criteria = DistributedVirtualSwitchPortCriteria->new(connected => 1);

         #my $dvPorts = Vim::get_view(mo_ref=>$dvsSwitch)->FetchDVPorts(criteria => $dvs_port_criteria);
         my $dvPorts = Vim::get_view(mo_ref=>$dvsSwitch)->FetchDVPorts();
         print "   DVPort ID           In Use      Client\n";
         foreach my $dvPort (@$dvPorts) {
            my $client = "";
            if (defined($dvPort->connectee)) {
               # bug 469804
               if ($dvPort->connectee->type eq 'vmVnic') {
                  if (defined($dvPort->state->runtimeInfo->linkPeer)) {
                     $client = $dvPort->state->runtimeInfo->linkPeer;
                  }
               } elsif ($dvPort->connectee->connectedEntity->type eq 'HostSystem') {
                  # bug 371093 - only need to quantify host info when go through vCenter
                  if (defined(Opts::get_option('vihost'))) {
                     my $host = Vim::get_view (mo_ref => $dvPort->connectee->connectedEntity,
                                               properties => ['name']);
                     $client = $dvPort->connectee->nicKey . " - " . $host->name;
                  } else {
                     $client = $dvPort->connectee->nicKey;
                  }
               } else {
                  $client = $dvPort->connectee->nicKey;
               }
            }
            #bug - 529155
            if (defined($dvPort->state) && defined($dvPort->state->runtimeInfo)) {
              printf("   %-20s%-12s%-16s\n",
                      $dvPort->key, $dvPort->state->runtimeInfo->linkUp ? "1" : 0, $client);
            } else {
               printf("   %-20s%-12s%-16s\n",
                      $dvPort->key, "0", "");
            }
         }
      }
   }
}

sub UpdateVirtualSwitch {
   my ($network, $vSwitch, $pgName, $vlan) = @_;
   my $hostNetPolicy = new HostNetworkPolicy();
   my $hostPGSpec = new HostPortGroupSpec (name => $pgName, 
                                           policy => $hostNetPolicy,
                                           vlanId => $vlan, 
                                           vswitchName => $vSwitch);
   eval {
       $network->AddPortGroup (_this => $network->networkInfo, portgrp => $hostPGSpec);};
       if ($@) {
          VIExt::fail($@->fault_string);
       }
   return;
}

sub UpdateMTU { 
   my ($network, $vswitchName, $mtu) = @_;;
   my $vs = FindVSwitchbyName($network, $vswitchName);
   if ($vs) {
      my $numPorts = $vs->{numPorts};
      $numPorts = 64 unless (defined($numPorts));

      $vs->{spec}->{mtu} = $mtu; 
      eval {
         $network->UpdateVirtualSwitch(vswitchName => $vswitchName, spec => $vs->spec);
      };
      if ($@) {
         VIExt::fail($@->fault_string);
      }
   } else {
      VIExt::fail("No such virtual switch: $vswitchName");
   }
}

sub UpdateUplinks { 
   my ($network, $vswitchName, $pnic, $add) = @_;;
   my $vs = FindVSwitchbyName($network, $vswitchName);
   if ($vs) {
      # Create a new bridge when configuring a vswitch that
      # currently has zero uplinks.
      unless (defined($vs->spec->bridge)) {
         if ($add) {
            $vs->{spec}->{bridge} = new HostVirtualSwitchBondBridge();
         } else {
            VIExt::fail("No such uplink: $pnic");
            return;
         }
      }

      my $bridge = $vs->spec->bridge;

      # Not setting policy 
      #   => retains existing settings, except adjusting the bridge changes.
      delete $vs->{spec}->{policy};
      # bug 376670
      if ($bridge->isa('HostVirtualSwitchBondBridge')) {
         if ($add) {
            push (@{$bridge->{nicDevice}}, $pnic);
         } else {
            my $size = @{$bridge->{nicDevice}};
            for (my $i=0; $i<$size; $i++) {
               if ($bridge->{nicDevice}->[$i] eq "$pnic") {
                  splice(@{$bridge->{nicDevice}}, $i, 0);
                  last;
               }
            }
            if (@{$bridge->{nicDevice}} == 1) {
               delete $vs->{spec}->{bridge};
            }
         }
      }
      elsif ($bridge->isa('HostVirtualSwitchSimpleBridge')) {
         if ($add) {
            $bridge->{nicDevice} = $pnic;
         } else {
            $bridge->{nicDevice} = undef;
         }
      } else {
         VIExt::fail("Operation not valid for this vswitch.");
      }
      eval {
         $network->UpdateVirtualSwitch(vswitchName => $vswitchName, spec => $vs->spec);
         print "Updated uplinks: " . join(", ", @{$bridge->{nicDevice}}) . "\n";
      };
      if ($@) {
         VIExt::fail($@->fault_string);
      }
   } else {
      VIExt::fail("No such virtual switch: $vswitchName");
   }
}

sub AddPortGroup {
   my ($network, $pgName, $vSwitch, $vlan) = @_;
   my $hostNetPolicy = new HostNetworkPolicy();
   $vlan = 0 unless (defined $vlan);
   my $hostPGSpec = new HostPortGroupSpec (name => $pgName, 
      policy => $hostNetPolicy,
      vlanId => $vlan, 
      vswitchName => $vSwitch);
   eval {$network->AddPortGroup (_this => $network, portgrp => $hostPGSpec); };
   if ($@) {
      VIExt::fail($@->fault_string);
   }
   return;
}

sub UpdatePortGroupVlan {
   my ($network, $vSwitch, $pgName, $vlan) = @_;
   my $pg = FindPortGroupbyName ($network, $vSwitch, $pgName);
   VIExt::fail("Port Group $pgName on VSwitch $vSwitch is not found") unless (defined $pg);
   my $hostPGSpec = new HostPortGroupSpec (name => $pgName, 
                                           policy => $pg->spec->policy,
                                           vlanId => $vlan, 
                                           vswitchName => $vSwitch);
   eval {
      $network->UpdatePortGroup (pgName => $pgName, portgrp => $hostPGSpec);
   };
   if ($@) {
      VIExt::fail($@->fault_string);
   }
   return;
}

# bug 373828
sub UpdatePortGroupAddUplink {
   my ($network, $vSwitchName, $pgName, $vnic) = @_;
   my $pg = FindPortGroupbyName ($network, $vSwitchName, $pgName);
   VIExt::fail("Port Group $pgName on VSwitch $vSwitchName is not found") unless (defined $pg);
   my $activeNics = ();
   if (defined($pg->spec->policy->nicTeaming) &&
      defined($pg->spec->policy->nicTeaming->nicOrder)) {
      $activeNics = $pg->spec->policy->nicTeaming->nicOrder->activeNic;
   }

   my $found = 0;
   my @newNics = ();

   foreach (@$activeNics) {
      if ($_ eq $vnic) {
         $found = 1;
      }
      push @newNics, $_;
   }
   if (! $found) {
      push @newNics, $vnic;
      if (!defined($pg->spec->policy->nicTeaming)) {
         $pg->spec->policy->nicTeaming(new HostNicTeamingPolicy());
      }
      if (!defined($pg->spec->policy->nicTeaming->nicOrder)) {
         $pg->spec->policy->nicTeaming->nicOrder(new HostNicOrderPolicy(activeNic => \@newNics));
      } else {
         $pg->spec->policy->nicTeaming->nicOrder->activeNic(\@newNics);
      }
      
      my $hostPGSpec = new HostPortGroupSpec (name => $pgName, 
                                              policy => $pg->spec->policy,
                                              vlanId => $pg->spec->vlanId, 
                                              vswitchName => $vSwitchName);
      eval {
         $network->UpdatePortGroup (pgName => $pgName, portgrp => $hostPGSpec);
         print "Added uplink adapter successfully.\n";
      };
      if ($@) {
         VIExt::fail($@->fault_string);
      }
   }
   else {
      VIExt::fail("Uplink adapter: $vnic already configured with portgroup $pgName");
   }
   return;
}

sub UpdatePortGroupDelUplink {
   my ($network, $vSwitchName, $pgName, $vnic) = @_;
   my $pg = FindPortGroupbyName ($network, $vSwitchName, $pgName);
   VIExt::fail("Port Group $pgName on VSwitch $vSwitchName is not found") unless (defined $pg);

   my $activeNics;
   if (defined($pg->spec->policy->nicTeaming) && 
       defined($pg->spec->policy->nicTeaming->nicOrder)) {
       $activeNics = $pg->spec->policy->nicTeaming->nicOrder->activeNic;
   } else {
       my $vSwitch = FindVSwitchbyName($network, $vSwitchName);
       my $pNicName = getPnicName($network, $vSwitch->pnic);
       @$activeNics = split(/,/, $pNicName);
   }

   my $found = 0;
   my @newNics = (); 

   foreach (@$activeNics) {
      if ($_ ne $vnic) {
         push @newNics, $_;
      } else {
         $found = 1;
      }
   }

   if ($found) {
      if (!defined($pg->spec->policy->nicTeaming)) {
         $pg->spec->policy->nicTeaming(new HostNicTeamingPolicy());
      }
      if (!defined($pg->spec->policy->nicTeaming->nicOrder)) {
         $pg->spec->policy->nicTeaming->nicOrder(new HostNicOrderPolicy(activeNic => \@newNics));
      } else {
         $pg->spec->policy->nicTeaming->nicOrder->activeNic(\@newNics);
      }
      my $hostPGSpec = new HostPortGroupSpec (name => $pgName, 
                                              policy => $pg->spec->policy,
                                              vlanId => $pg->spec->vlanId, 
                                              vswitchName => $vSwitchName);
      eval {
         $network->UpdatePortGroup (pgName => $pgName, portgrp => $hostPGSpec);
         print "Deleted uplink adapter successfully.\n";
      };
      if ($@) {
         VIExt::fail($@->fault_string);
      }
   } else {
      VIExt::fail("No such uplink adapter: $vnic");
   }
   return;
}


sub UpdateDVPDelUplink {
   my ($network, $dvSwitch, $vnic, $portKey) = @_;
   my $pSwitch = FindPSwitchbyName($network, $dvSwitch);
   VIExt::fail("DVSwitch $dvSwitch is not found") unless (defined $pSwitch);

   my $found = 0;
   my @newPnicSpecs = ();
   my $pnicSpecs = $pSwitch->spec->backing->pnicSpec;
   
   foreach (@$pnicSpecs) {
      if ($_->pnicDevice ne $vnic && $_->uplinkPortKey ne $portKey) {
         push @newPnicSpecs, $_;
      } else {
         if ($_->pnicDevice eq $vnic && $_->uplinkPortKey eq $portKey) {
            $found = 1;
         }
      }
   }
   
   if ($found) {
      $pSwitch->spec->backing->pnicSpec(\@newPnicSpecs);
   
      my $pSwitchConfig = new HostProxySwitchConfig(changeOperation => "edit",
                                                    spec => $pSwitch->spec,
                                                    uuid => $pSwitch->dvsUuid);
      my $config = new HostNetworkConfig(proxySwitch => [$pSwitchConfig]);
      eval {
         $network->UpdateNetworkConfig (changeMode => "modify",
                                        config => $config);
         print "Deleted uplink adapter successfully.\n";
      };
      if ($@) {
         VIExt::fail($@->fault_string);
      }
   } else {
      VIExt::fail("No such uplink adapter: $vnic");
   }
   return;
}

sub UpdateDVPAddUplink {
   my ($network, $dvSwitch, $vnic, $portKey) = @_;
   my $pSwitch = FindPSwitchbyName($network, $dvSwitch);
   VIExt::fail("DVSwitch $dvSwitch is not found") unless (defined $pSwitch);
   
   my $pnicSpecs = $pSwitch->spec->backing->pnicSpec;
   my $newSpec = new DistributedVirtualSwitchHostMemberPnicSpec(pnicDevice => $vnic,
                                                                uplinkPortKey => $portKey);
   push @$pnicSpecs, $newSpec;
   $pSwitch->spec->backing->pnicSpec($pnicSpecs);
   
   my $pSwitchConfig = new HostProxySwitchConfig(changeOperation => "edit",
                                                 spec => $pSwitch->spec,
                                                 uuid => $pSwitch->dvsUuid);
   my $config = new HostNetworkConfig(proxySwitch => [$pSwitchConfig]);
   eval {
      $network->UpdateNetworkConfig (changeMode => "modify",
                                     config => $config);
      print "Added uplink adapter successfully.\n";                               
   };
   if ($@) {
      VIExt::fail($@->fault_string);
   }
   return;
}

sub GetCDP {
   my ($network, $vswitchName) = @_;;
   my $vs = FindVSwitchbyName($network, $vswitchName);
   if ($vs) {
      eval {
         my $value = $vs->spec->bridge->linkDiscoveryProtocolConfig->operation;
         if ($value eq "none") {
            # map to match with COS CLI
            $value = "down";
         }
         print $value . "\n";
      }
   } else {
      VIExt::fail("No such virtual switch: $vswitchName");
   }
}

sub SetCDP {
   my ($network, $vswitchName, $value) = @_;;
   my $vs = FindVSwitchbyName($network, $vswitchName);
   if ($vs) {
      eval {
         if ($value eq "down") {
            # map to match with COS CLI
            $value = "none";
         }
         
         eval {
            my $linkConfig = new LinkDiscoveryProtocolConfig(protocol => "cdp",
                                                             operation => $value);
            if (!defined($vs->spec->bridge)) {
               VIExt::fail("There is no physical network adapter bridged to the virtual switch.");
            } else {
               $vs->spec->bridge->linkDiscoveryProtocolConfig($linkConfig);
            }
         };
         if ($@) {
            VIExt::fail("Setting of link protocol is not supported on this platform.");
         } else {
            $network->UpdateVirtualSwitch(vswitchName => $vswitchName, spec => $vs->spec);
         }
      };
      if ($@) {
         VIExt::fail("Error: Invalid CDP status string $value");
      }
   } else {
      VIExt::fail("No such virtual switch: $vswitchName");
   }
}

sub RemovePortGroup {
   my ($network, $pgName) = @_;
   eval {$network->RemovePortGroup (pgName => $pgName);};
   if ($@) {
      VIExt::fail($@->fault_string);
   }
}

sub FindVSwitchbyName {
   my ($network, $name) = @_;
   my $vSwitches = $network->networkInfo->vswitch;
   foreach my $vSwitch (@$vSwitches) {
      return $vSwitch if ($name eq $vSwitch->name);
   }
   return undef;
}

sub FindPSwitchbyName {
   my ($network, $name) = @_;
   my $pSwitches = $network->networkInfo->proxySwitch;
   foreach my $pSwitch (@$pSwitches) {
      return $pSwitch if ($name eq $pSwitch->dvsName);
   }
   return undef;
}

sub FindPortGroupbyName {
   my ($network, $vSwitch, $pgName) = @_;
   my $name = $vSwitch;
   my $portGroups = $network->networkInfo->portgroup;

   foreach my $pg (@$portGroups) {
      my $spec = $pg->spec;
      #
      # handle the case where any switch name will do
      #
      $name = (defined $vSwitch) ? $vSwitch : $spec->vswitchName;		
      return $pg if (($spec->vswitchName eq $name) && ($spec->name eq $pgName));
   }
   return undef;
}

sub FindPortGroupbyKey {
   my ($network, $vSwitch, $key) = @_;
   my $portGroups = $network->networkInfo->portgroup;
   foreach my $pg (@$portGroups) {
      return $pg if (($pg->vswitch eq $vSwitch) && ($key eq $pg->key));
   }
   return undef;
}

sub CheckValues {
   my %locals = GetSuppliedOptions();
   my $masterMap = BuildBits (keys %locals);	# build the master list

   foreach (@options) {
      my $bitmap = BuildBits ( @$_);
      return 1 if ($bitmap == $masterMap);
   }
   
   print "The options are invalid.\n";
   Opts::usage();
   exit(1);
}

sub BuildBits {
   my (@arr) = @_;
   my %list;
   foreach (@arr) {
      $list{$_}++; 
   } 
   my $bit = 0;
   foreach (sort keys %opts) {
      $bit = ($bit | 1) if (defined $list{$_}); 
      $bit = $bit << 1;
   }
   return $bit;
}

__END__

=head1 NAME

vicfg-vswitch - create and configure virtual switches and port groups

=head1 SYNOPSIS

 vicfg-vswitch [<connection_options>] 
    [ --add <switch_name> |
      --check <vswitch_name> |
      --delete <vswitch_name> |
      --get-cdp <vswitch_name> |
      --help |
      --link <physical_nic> <vswitch_name> |
      --list |
      --mtu <vswitch_name> |
      --set-cdp <vswitch_name> |
      --unlink <physical_nic> <vswitch_name> |
      --vihost <esx_host ]     

 vicfg-vswitch [<connection_options>]
    [--add_pg <portgroup> <vswitch_name> |
     --check-pg <port_group> <vswitch_name> |
     --del-pg <port_group> <vswitch_name> |
     --help |
     --list |
     --vihost <esx_host |
     --vlan --pg <port_group> ]     

 vicfg-vswitch [<connection_options>]
    [--add-dvp-uplink <adapter_name> --dvp <DVPort_id> <dvswitch_name> |
     --del-dvp-uplink <adapter_name> --dvp <DVPort_id> <dvswitch_name> |
     --help |
     --vihost <esx_host ]   

 vicfg-vswitch [<connection_options>]
    [--add-pg-uplink <adapter_name> --pg <port_group> <vswitch_name> |
     --del-pg-uplink <adapter_name> --pg <port_group> <vswitch_name> |
     --help |
     --vihost <esx_host ]
     


=head1 DESCRIPTION

The vicfg-vswitch command adds or removes virtual switches or modifies 
virtual switch settings. A virtual switch is an abstracted network device. 
It can route traffic internally between virtual machines and link to external networks. 
The I<ESX Configuration Guide> and the I<ESXi Configuration Guide> discuss virtual switches, 
vNetwork Distributed Switches (vDS), port groups, and vDS port groups. The vSphere CLI manual 
presents some sample scenarios. 

By default,each ESX/ESXi host has a single virtual switch called vSwitch0.

=head1 OPTIONS

=over

=item B<--add | -a E<lt>switch_nameE<gt>>

Adds a new virtual switch.

=item B<--add-pg | -A E<lt>portgroupE<gt> E<lt>vswitch_nameE<gt>>

Adds a port group to the specified virtual switch.

=item B<--add-dvp-uplink | -P>

Adds an uplink adapter to a distributed virtual port (DVP).

=item B<--add-pg-uplink | -M>

Adds an uplink adapter to a port group (valid for vSphere 4.0 and later).
This command fails silently if the uplink adapter does not exist. 

=item B<--check | -c E<lt>vswitch_nameE<gt>>

Checks whether a virtual switch exists. Prints 1 if the switch exists and prints 0 otherwise. 
Use the virtual switch name, e.g. vSwitch0 or vSwitch1, to specify the virtual switch. 

=item B<--check-pg | -C E<lt>port_groupE<gt> E<lt>vswitch_nameE<gt>>

Checks whether the specified port group exists or not.

=item B<connection_options>

Specifies the target server and authentication information if required. Run C<vicfg-vswitch --help>
for a list of all connection options.

=item B<--delete | -d E<lt>vswitch_nameE<gt>>

Deletes a virtual switch. Running the command with this option fails 
if any ports on the virtual switch are in use by VMkernel networks, vswifs, 
or virtual machines.

=item B<--del-pg | -D E<lt>port_groupE<gt> E<lt>vswitch_nameE<gt>>

Deletes a port group from the virtual switch. Running the command with 
this option fails if the port group is in use, for example, by a virtual machine or 
a VMkernel network.

=item B<--del-dvp-uplink | -Q  E<lt>adapter_name>E<gt> --dvp E<lt>DVPort_idE<gt> E<lt>dvswitch_nameE<gt> >

Deletes an uplink adapter from a port on a DVS (distributed virtual switch),
also called vNetwork Distributed Switch (vDS). Valid for vSphere 4.0 and later.

=item B<--del-pg-uplink | -N E<lt>adapter_name>E<gt> E<lt>port_groupE<gt> E<lt>dvswitch_nameE<gt> >

Deletes an uplink adapter from a port group. Valid for vSphere 4.0 and later.

=item B<--dvp | -V>

Name of a distributed virtual port. Used in conjunction with other options. 
Valid for vSphere 4.0 and later.

=item B<--get-cdp | -b E<lt>vswitch_nameE<gt>>

Prints the current CDP (Cisco Discovery Protocol) setting for this virtual switch (valid for vSphere 4.0 and later).

=item B<--help>

Prints a help message for each command-specific and each connection option. 
Calling the script with no arguments or with C<--help> has the same effect.

=item B<--link | -L E<lt>physical_nicE<gt> E<lt>vswitch_nameE<gt>>

Adds an uplink adapter (physical NIC) to a virtual switch. 
Running the command with this option attaches a new unused physical network adapter to 
a virtual switch.

=item B<--list | -l>

Lists all virtual switches and their port groups.

=item B<--mtu | -m E<lt>vswitch_nameE<gt>>

Sets the MTU (maximum transmission unit) of the virtual switch. 
This option affects all physical NICs assigned to the virtual switch. 

=item B<--pg | -p E<lt>port_groupE<gt>>

Provides the name of the port group for the --vlan option. 
Specify ALL to set VLAN IDs on all port groups of a virtual switch.

=item B<--set-cdp | -B E<lt>vswitch_nameE<gt> [down | listen | advertise | both]>

Sets the CDP status for a given virtual switch (valid for vSphere 4.0 and later).  
To set, specify C<down>, C<listen>, C<advertise>, or C<both>.

=item B<--unlink | -U E<lt>physical_nicE<gt>  E<lt>vswitch_nameE<gt>>

Removes an uplink adapter from a virtual switch. An uplink adapter corresponds to 
a physical Ethernet adapter to which the virtual switch is connected. 
If you remove the last uplink adapter, you lose physical network connectivity for that switch.

=item B<--vihost | -h  E<lt>esx_hostE<gt>>

When you run a vSphere CLI command with the C<--server> option pointing to a 
vCenter Server system, use 
C<--vihost> to specify the ESX/ESXi host to run the command against. 

=item B<--vlan | -v  --pg E<lt>port_groupE<gt>>

Sets the VLAN ID for a specific port group of a virtual switch. 
Setting the option to 0 disables the VLAN for this port group. 
If you specify this option, you must also specify the C<--pg> option. 


=back

=head1 EXAMPLES

The following examples assume you are specifying connection options, either 
explicitly or, for example, by specifying the server, user name, and password. 
Run C<vicfg-vswitch --help> for a list of common options including connection options.

Add a new virtual switch:

 vicfg-vswitch <conn_options> -a <vswitch name>

Delete the virtual switch. This will fail if any ports on the 
virtual switch are still in use by VMkernel networks, vswifs, or virtual machines:

 vicfg-vswitch <conn_options> -d <vswitch name>

List all virtual switches and their portgroups:

 vicfg-vswitch <conn_options -l

Add an uplink adapter to a virtual switch:

 vicfg-vswitch <conn_options> -L <physical adapter name> <vswitch name>

Remove an uplink adapter from a virtual switch:

 vicfg-vswitch <conn_options> -U <physical adapter name> <vswitch name>

Check whether a virtual switch exists:

 vicfg-vswitch <conn_options> --check <vswitch name>

Add a new portgroup to the virtual switch:

 vicfg-vswitch <conn_options> -A <port group name> <vswitch name>

Delete a portgroup from the virtual switch:

 vicfg-vswitch <conn_options> -D <port group name> <vswitch name>

Check whether a port group exists:

 vicfg-vswitch <conn_options> -C <valid portgroup name> <vswitch name>

Add an uplink adapter to a port group:

 vicfg-vswitch <conn_options> -M <physical adapter name> -p <port group name> <vswitch name>

Remove an uplink adapter from a port group:

 vicfg-vswitch <conn_options> -N <physical adapter name> -p <port group name> <vswitch name>

Print the current CDP setting for the virtual switch:

 vicfg-vswitch <conn_options> --get-cdp <vswitch name>

=cut   

