diff --git a/xCAT-vlan/debian/changelog b/xCAT-vlan/debian/changelog new file mode 100755 index 000000000..d9c1c46ad --- /dev/null +++ b/xCAT-vlan/debian/changelog @@ -0,0 +1,5 @@ +xcat-vlan (2.9-snap201304181624) unstable; urgency=low + + * Initial release (Closes: #nnnn) + + -- root Thu, 18 Apr 2013 15:40:26 +0800 diff --git a/xCAT-vlan/debian/compat b/xCAT-vlan/debian/compat new file mode 100755 index 000000000..45a4fb75d --- /dev/null +++ b/xCAT-vlan/debian/compat @@ -0,0 +1 @@ +8 diff --git a/xCAT-vlan/debian/control b/xCAT-vlan/debian/control new file mode 100755 index 000000000..eb7470a39 --- /dev/null +++ b/xCAT-vlan/debian/control @@ -0,0 +1,13 @@ +Source: xcat-vlan +Section: admin +Priority: extra +Maintainer: root +Build-Depends: debhelper (>= 8.0.0) +Standards-Version: 3.9.2 +#Vcs-Git: git://git.debian.org/collab-maint/xcat-vlan.git +#Vcs-Browser: http://git.debian.org/?p=collab-maint/xcat-vlan.git;a=summary + +Package: xcat-vlan +Architecture: all +Depends: xCAT-client +Description: xCAT-vlan provides the xCAT vlan confiuration. diff --git a/xCAT-vlan/debian/copyright b/xCAT-vlan/debian/copyright new file mode 100755 index 000000000..38512affb --- /dev/null +++ b/xCAT-vlan/debian/copyright @@ -0,0 +1,36 @@ +Format: http://dep.debian.net/deps/dep5 +Upstream-Name: xcat-vlan +Source: + +Files: * +Copyright: + +License: + + + . + + +# If you want to use GPL v2 or later for the /debian/* files use +# the following clauses, or change it to suit. Delete these two lines +Files: debian/* +Copyright: 2013 root +License: GPL-2+ + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. diff --git a/xCAT-vlan/debian/dirs b/xCAT-vlan/debian/dirs new file mode 100755 index 000000000..171b76f8b --- /dev/null +++ b/xCAT-vlan/debian/dirs @@ -0,0 +1,6 @@ +install/postscripts +opt/xcat/lib/perl/xCAT +opt/xcat/lib/perl/xCAT_plugin +opt/xcat/share/man/man1 +opt/xcat/share/doc/man1 +opt/xcat/bin diff --git a/xCAT-vlan/debian/docs b/xCAT-vlan/debian/docs new file mode 100755 index 000000000..e69de29bb diff --git a/xCAT-vlan/debian/install b/xCAT-vlan/debian/install new file mode 100755 index 000000000..93a9c0af5 --- /dev/null +++ b/xCAT-vlan/debian/install @@ -0,0 +1,5 @@ +xCAT/* opt/xcat/lib/perl/xCAT/ +xCAT_plugin/* opt/xcat/lib/perl/xCAT_plugin/ +install/postscripts/* install/postscripts/ +share/man/man1/* opt/xcat/share/man/man1 +share/doc/man1/* opt/xcat/share/doc/man1 diff --git a/xCAT-vlan/debian/postinst b/xCAT-vlan/debian/postinst new file mode 100755 index 000000000..1a2485251 --- /dev/null +++ b/xCAT-vlan/debian/postinst @@ -0,0 +1 @@ +#!/bin/sh diff --git a/xCAT-vlan/debian/rules b/xCAT-vlan/debian/rules new file mode 100755 index 000000000..4ab16dbff --- /dev/null +++ b/xCAT-vlan/debian/rules @@ -0,0 +1,39 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +build: + ./xpod2man +clean: + dh_testdir + dh_testroot + dh_clean -d +install: + pwd + dh_installdirs + dh_install -X".svn" + chmod 444 debian/xcat-vlan/opt/xcat/share/man/man1/* + chmod 644 debian/xcat-vlan/opt/xcat/share/doc/man1/* + ln -sf ../bin/xcatclientnnr debian/xcat-vlan/opt/xcat/bin/mkvlan + ln -sf ../bin/xcatclientnnr debian/xcat-vlan/opt/xcat/bin/chvla + ln -sf ../bin/xcatclientnnr debian/xcat-vlan/opt/xcat/bin/rmvlan + ln -sf ../bin/xcatclientnnr debian/xcat-vlan/opt/xcat/bin/lsvlan + +binary-indep: build install + +binary-arch: + dh_compress + dh_installdeb + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/xCAT-vlan/install/postscripts/configvlan b/xCAT-vlan/install/postscripts/configvlan new file mode 100755 index 000000000..790d5a49b --- /dev/null +++ b/xCAT-vlan/install/postscripts/configvlan @@ -0,0 +1,284 @@ +#!/bin/sh + +#------------------------------------------------------------------------------------- +# +# this script configures the vlan on the node, assuming the vlan is already creaed on the switch. +# configvlan +# if vlan_is is not specified, all the vlans defined for the +# given node will be configured. +# +#------------------------------------------------------------------------------------- +#if [[ -z "$VLANID" ]] || [[ -z "$VLANHOSTNAME" ]] || [[ -z "$VLANIP" ]]; then +# logger -t xcat "configvlan: please make sure that vlan id, vlan hostname and vlan ip are defined for this node.\nPlease use mkvlan or chvlan command." +# echo "configvlan: please make sure that vlan id, vlan hostname and vlan ip are defined for this node.\nPlease use mkvlan or chvlan command." +# exit 0 +#fi + +if [[ -z $VLANMAXINDEX ]] || [[ $VLANMAXINDEX -eq 0 ]]; then + logger -t xcat "configvlan: Nothing to do." + echo "configvlan: Nothing to do." + exit 0 +fi + +if [[ $OSTYPE = linux* ]]; then + if [[ $OSVER = sles* ]] || [[ $OSVER = suse* ]] || [[ -f /etc/SuSE-release ]]; then + nwdir="/etc/sysconfig/network" + isSLES=1 + else + nwdir="/etc/sysconfig/network-scripts" + fi +else + logger -t xcat "configvlan: Does not support AIX yet." + echo "configvlan: Does not support AIX yet." + exit 0 +fi + + + +keephostname=0 +if [ $# -eq 1 ] +then + if [ "$1" = "--keephostname"] + then + keephostname=1 + else + # the original logic + if [[ -n "$1" ]]; then + IN_VLAN=$1 #The vlan id to be configured, if not specified, all will be configured on this note. + fi + fi +fi + +if [ $# -gt 1 ] +then + if [ "$1" = "--keephostname" ] + then + logger -t xcat "configvlan: Usage: configvlan <--keephostname>. Correct the arguments order." + echo "configvlan: Usage: configvlan <--keephostname>. Correct the arguments order." + exit -1 + fi + # the original logic + IN_VLAN=$1 #The vlan id to be configured, if not specified, all will be configured on this note. + if [ "$2" != "--keephostname" ] + then + logger -t xcat "configvlan: Usage: configvlan <--keephostname>. Correct the arguments order." + echo "configvlan: Usage: configvlan <--keephostname>. Correct the arguments order." + exit -1 + fi + keephostname=1 +fi + + +index=1 +while [ $index -le $VLANMAXINDEX ]; do + nic="" + eval VLANID=\$VLANID_$index + if [[ -n $IN_VLAN ]]; then + if [[ "$VLANID" != "$IN_VLAN" ]]; then + index=$((index+1)) + continue + fi + fi + + if [[ -z $VMNODE ]]; then #for bare-metal nodes + eval VLANNIC=\$VLANNIC_$index #VLANNIC could be ethx, primary, primary:ethx or empty + + #determine if the current is primary network + PMRY=0 + if [[ -z $VLANNIC ]] || [[ $VLANNIC = primary* ]]; then + PMRY=1 + fi + + if [[ $VLANNIC = primary* ]]; then + VLANNIC=${VLANNIC#primary} + fi + if [[ -n $VLANNIC ]]; then + nic=${VLANNIC#:} + fi + else #for KVM nodes + eval VLAN_VMNICPOS=\$VLAN_VMNICPOS_$index #VLAN_VMNICPOS identifies the position of the mac address in mac, the mac has the following format: 01:02:03:04:05:0E!node5|01:02:03:05:0F!node6-eth + + PMRY=0 + if [[ $index -eq 1 ]]; then + PMRY=1 ##the first one is always primary network + fi + + if [[ -z $VLAN_VMNICPOS ]]; then + nic="eth1" + fi + fi + + + + if [[ -z $nic ]]; then #get the nic + if [[ -n $MACADDRESS ]]; then + pos=0 + #mac has the following format: 01:02:03:04:05:0E!node5|01:02:03:05:0F!node6-eth1 + for x in `echo "$MACADDRESS" | tr "|" "\n"` + do + node="" + mac="" + pos=$((pos+1)) + i=`expr index $x !` + if [[ $i -gt 0 ]]; then + node=`echo ${x##*!}` + mac_tmp=`echo ${x%%!*}` + else + mac_tmp=$x + fi + + if [[ $pos -eq 1 ]]; then + mac1=$mac_tmp + fi + + if [[ -n $VMNODE ]]; then + if [[ $pos -eq $VLAN_VMNICPOS ]]; then + mac=$mac_tmp + break + fi + else + if [[ "$PRIMARYNIC" = "$mac_tmp" ]]; then + mac=$mac_tmp + break + fi + + if [[ -z "$PRIMARYNIC" ]] || [[ "$PRIMARYNIC" = "mac" ]]; then + if [[ -z $node ]] || [[ "$node" = "$NODE" ]]; then + mac=$mac_tmp + break + fi + fi + fi + done + + if [[ -z $mac ]]; then + if [[ -n $VMNODE ]]; then + mac=$mac1 + else + if [[ -z "$PRIMARYNIC" ]] || [[ "$PRIMARYNIC" = "mac" ]]; then + mac=$mac1 #if nothing mathes, take the first mac + else + nic=$PRIMARYNIC #or the primary nic itself is the nic + fi + fi + fi + else + logger -t xcat "configvlan: no mac addresses are defined in the mac table for the node $NODE" + echo "configvlan: no mac addresses are defined in the mac table for the node $NODE" + index=$((index+1)) + continue + fi + echo "mac=$mac" + fi + + if [[ -z $nic ]]; then + #go to each nic to match the mac address + ret=`ifconfig |grep -i $mac | cut -d' ' -f 1 2>&1`; + if [ $? -eq 0 ]; then + for x in $ret + do + #ignore bridge because bridge and the nic has the same mac address + if [[ $isSLES -eq 1 ]]; then + ret1=`grep -i "TYPE='Bridge'" $nwdir/ifcfg-$x 2>&1`; + else + ret1=`grep -i "TYPE=Bridge" $nwdir/ifcfg-$x 2>&1`; + fi + if [ $? -ne 0 ]; then + nic=$x + break + fi + done + if [ -z $nic ]; then + nic=`echo $ret |head -n1` + fi + else + logger -t xcat "configvlan: The mac address for the network for $NODE is not defined." + echo "configvlan: The mac address for the network for $NODE is not defined." + index=$((index+1)) + continue + fi + fi + echo "nic=$nic" + + eval VLANID=\$VLANID_$index + eval VLANIP=\$VLANIP_$index + eval VLANNETMASK=\$VLANNETMASK_$index + + #write into the network configuration file + if [ -z "$VMNODE" ]; then + newnic="$nic.$VLANID" + else + newnic="$nic" + fi + logger -t xcat "configvlan: configuring vlan $VLANID on $newnic for $NODE..." + echo "configvlan: configuring vlan $VLANID on $newnic for $NODE..." + + + if [[ $isSLES -eq 1 ]]; then + cat >$nwdir/ifcfg-$newnic <> $nwdir/ifcfg-$newnic + fi + else + cat >$nwdir/ifcfg-$newnic <> $nwdir/ifcfg-$newnic + fi + fi + + #restart the vlan network + logger -t xcat "configvlan: starting $newnic ..." + echo "configvlan: starting $newnic ..." + ret=`ifdown $newnic boot` + sleep 2 + ret=`ifup $newnic boot` + if [ $? -ne 0 ]; then + logger -t xcat "configvlan: $ret" + echo "configvlan: $ret" + index=$((index+1)) + continue; + else + logger -t xcat "configvlan: done." + echo "configvlan: done." + fi + + #change the hostname only for primary vlan + if [[ $PMRY -eq 1 && $keephostname -eq 0 ]]; then + eval VLANHOSTNAME=\$VLANHOSTNAME_$index + hostname $VLANHOSTNAME + + #change the hostname permanently + if [ -f /etc/SuSE-release ] + then + #SLES x + echo $VLANHOSTNAME > /etc/HOSTNAME + else + #RedHat and others + fn="/etc/sysconfig/network" + grep HOSTNAME $fn + if [ $? -eq 0 ]; then + sed -i "s/HOSTNAME.*/HOSTNAME=$VLANHOSTNAME/" $fn + else + echo "HOSTNAME=$VLANHOSTNAME" >> $fn + fi + fi + logger -t xcat "configvlan: Hostname changed to $VLANHOSTNAME for $NODE." + echo "configvlan: Hostname changed to $VLANHOSTNAME for $NODE." + fi + #next + index=$((index+1)) +done +exit 0 + diff --git a/xCAT-vlan/install/postscripts/deconfigvlan b/xCAT-vlan/install/postscripts/deconfigvlan new file mode 100755 index 000000000..c8d486671 --- /dev/null +++ b/xCAT-vlan/install/postscripts/deconfigvlan @@ -0,0 +1,231 @@ +#!/bin/sh + +#------------------------------------------------------------------------------------- +# +# this script deconfigures the vlan on the node. +# deconfigvlan +# if vlan_is is not specified, all the vlans defined +# for the given node will be deconfigured. +# +#------------------------------------------------------------------------------------- +if [[ -z $VLANMAXINDEX ]] || [[ $VLANMAXINDEX -eq 0 ]]; then + logger -t xcat "deconfigvlan: Nothing to do." + echo "deconfigvlan: Nothing to do." + exit 0 +fi + +if [[ $OSTYPE = linux* ]]; then + if [[ $OSVER = sles* ]] || [[ $OSVER = suse* ]] || [[ -f /etc/SuSE-release ]]; then + nwdir="/etc/sysconfig/network" + isSLES=1 + else + nwdir="/etc/sysconfig/network-scripts" + fi +else + logger -t xcat "deconfigvlan: Does not support AIX yet." + echo "deconfigvlan: Does not support AIX yet." + exit 0 +fi + + +if [[ -n "$1" ]]; then + IN_VLAN=$1 #The vlan id to be deconfigured, if not specified, all will be deconfigured on this note. +fi + +index=1 +while [ $index -le $VLANMAXINDEX ]; do + nic="" + eval VLANID=\$VLANID_$index + if [[ -n $IN_VLAN ]]; then + if [[ "$VLANID" != "$IN_VLAN" ]]; then + index=$((index+1)) + continue + fi + fi + + if [[ -z $VMNODE ]]; then #for bare-metal nodes + eval VLANNIC=\$VLANNIC_$index #VLANNIC could be ethx, primary, primary:ethx or empty + + #determine if the current is primary network + PMRY=0 + if [[ -z $VLANNIC ]] || [[ $VLANNIC = primary* ]]; then + PMRY=1 + fi + + if [[ $VLANNIC = primary* ]]; then + VLANNIC=${VLANNIC#primary} + fi + if [[ -n $VLANNIC ]]; then + nic=${VLANNIC#:} + fi + else #for KVM nodes + eval VLAN_VMNICPOS=\$VLAN_VMNICPOS_$index #VLAN_VMNICPOS identifies the position of the mac address in mac, the mac has the following format: 01:02:03:04:05:0E!node5|01:02:03:05:0F!node6-eth + + PMRY=0 + if [[ $index -eq 1 ]]; then + PMRY=1 ##the first one is always primary network + fi + + if [[ -z $VLAN_VMNICPOS ]]; then + nic="eth1" + fi + fi + + + + if [[ -z $nic ]]; then #get the nic + if [[ -n $MACADDRESS ]]; then + pos=0 + #mac has the following format: 01:02:03:04:05:0E!node5|01:02:03:05:0F!node6-eth1 + for x in `echo "$MACADDRESS" | tr "|" "\n"` + do + node="" + mac="" + pos=$((pos+1)) + i=`expr index $x !` + if [[ $i -gt 0 ]]; then + node=`echo ${x##*!}` + mac_tmp=`echo ${x%%!*}` + else + mac_tmp=$x + fi + + if [[ $pos -eq 1 ]]; then + mac1=$mac_tmp + fi + + if [[ -n $VMNODE ]]; then + if [[ $pos -eq $VLAN_VMNICPOS ]]; then + mac=$mac_tmp + break + fi + else + if [[ "$PRIMARYNIC" = "$mac_tmp" ]]; then + mac=$mac_tmp + break + fi + + if [[ -z "$PRIMARYNIC" ]] || [[ "$PRIMARYNIC" = "mac" ]]; then + if [[ -z "$node" ]] || [[ "$node" = "$NODE" ]]; then + mac=$mac_tmp + break + fi + fi + fi + done + + if [[ -z $mac ]]; then + if [[ -n $VMNODE ]]; then + mac=$mac1 + else + if [[ -z "$PRIMARYNIC" ]] || [[ "$PRIMARYNIC" = "mac" ]]; then + mac=$mac1 #if nothing mathes, take the first mac + else + nic=$PRIMARYNIC #or the primary nic itself is the nic + fi + fi + fi + else + logger -t xcat "deconfigvlan: no mac addresses are defined in the mac table for the node $NODE" + echo "deconfigvlan: no mac addresses are defined in the mac table for the node $NODE" + index=$((index+1)) + continue + fi + echo "mac=$mac" + fi + + if [[ -z $nic ]]; then + #go to each nic to match the mac address + ret=`ifconfig |grep -i $mac | cut -d' ' -f 1 2>&1`; + if [ $? -eq 0 ]; then + for x in $ret + do + #ignore bridge because bridge and the nic has the same mac address + if [[ $isSLES -eq 1 ]]; then + ret1=`grep -i "TYPE='Bridge'" $nwdir/ifcfg-$x 2>&1`; + else + ret1=`grep -i "TYPE=Bridge" $nwdir/ifcfg-$x 2>&1`; + fi + if [ $? -ne 0 ]; then + nic=$x + break + fi + done + if [ -z $nic ]; then + nic=`echo $ret |head -n1` + fi + else + logger -t xcat "deconfigvlan: The mac address for the network for $NODE is not defined." + echo "deconfigvlan: The mac address for the network for $NODE is not defined." + index=$((index+1)) + continue + fi + fi + echo "nic=$nic" + + eval VLANID=\$VLANID_$index + eval VLANIP=\$VLANIP_$index + eval VLANNETMASK=\$VLANNETMASK_$index + + #write into the network configuration file + if [ -z "$VMNODE" ]; then + newnic="$nic.$VLANID" + else + newnic="$nic" + fi + + #stop the vlan network + logger -t xcat "dconfigvlan: de-configuring vlan $newnic for $NODE..." + echo "dconfigvlan: de-configuring vlan $newnic for $NODE..." + + if [[ -n $newnic ]]; then + ret=`ifdown $newnic 2>&1` + if [ $? -ne 0 ]; then + logger -t xcat "deconfigvlan: stopping $newnic: $ret" + echo "deconfigvlan: stopping $newnic: $ret" + fi + + #remove the network conf file for the vlan + ret=`rm $nwdir/ifcfg-$newnic 2>&1` + else + logger -t xcat "deconfigvlan: cannot find the nic that has the vlan." + echo "deconfigvlan: cannot find the nic that has the vlan" + fi + + #stop the bridge on the vm hosts, + if [ -f $nwdir/ifcfg-vl$VLANID ]; then + ret=`ifconfig vl$VLANID down 2>&1` + ret=`rm $nwdir/ifcfg-vl$VLANID 2>&1` + ret=`brctl delbr vl$VLANID 2>&1` + fi + + if [[ $PMRY -eq 1 ]]; then + logger -t xcat "dconfigvlan: changing the hostname back to $NODE..." + echo "dconfigvlan: changing the hostname back to $NODE..." + #change the hostname + hostname $NODE + + #change the hostname permanently + if [ -f /etc/SuSE-release ] + then + #SLES x + echo $NODE > /etc/HOSTNAME + else + #RedHat and others + fn="/etc/sysconfig/network" + grep HOSTNAME $fn + if [ $? -eq 0 ]; then + sed -i "s/HOSTNAME.*/HOSTNAME=$NODE/" $fn + else + echo "HOSTNAME=$NODE" >> $fn + fi + fi + fi + logger -t xcat "deconfigvlan: done." + echo "deconfigvlan: done." + + #next + index=$((index+1)) +done + +exit 0 diff --git a/xCAT-vlan/pods/man1/chvlan.1.pod b/xCAT-vlan/pods/man1/chvlan.1.pod new file mode 100755 index 000000000..5a7d3e429 --- /dev/null +++ b/xCAT-vlan/pods/man1/chvlan.1.pod @@ -0,0 +1,106 @@ +=head1 NAME + +B - It adds or removes nodes for the vlan. + +=head1 SYNOPSIS + +B I B<-n>|B<--nodes> I [B<-i>|B<--interface> I] + +B I B<-n>|B<--nodes> I B<-d>|B<--delete> + +B [B<-h>|B<--help>] + +B [B<-v>|B<--version>] + +=head1 DESCRIPTION + +The B command adds nodes to the given vlan. If -d is specified, the nodes will be removed from the vlan. + +For added security, the root guard and bpdu guard will be enabled for the ports added to this vlan. However, the guards will not be disabled if the ports are removed from the vlan using chvlan (-d) or rmvlan commands. To disable them, you need to use the switch command line interface. Please refer to the switch command line interface manual to see how to disable the root guard and bpdu guard for a port. + +=head1 Parameters + +I is a unique vlan number. + + +=head1 OPTIONS + +=over 10 + +=item B<-n|--nodes> The nodes or groups to be added or removed. It can be stand alone nodes or KVM guests. It takes the noderange format. Please check the man page for noderange for details. + +=item B<-i|--interface> (For adding only). The interface name where the vlan will be tagged on. If omitted, the xCAT management network will be assumed. For KVM, it is the interface name on the host. + +=item B<-h|--help> Display usage message. + +=item B<-v|--version> The Command Version. + +=back + +=head1 RETURN VALUE + +0 The command completed successfully. + +1 An error has occurred. + +=head1 EXAMPLES + +=over 3 + +=item 1. + +To add node1, node2 and node3 to vlan 3. + + chvlan 3 -n node1,node2,node3 + + +=item 2. + +To add node1, node2 and node3 to vlan 3 using eth1 interface. + + chvlan 3 -n node1,node2,node3 -i eth1 + + +=item 3. + +TO remove node1, node2 and node3 from vlan 3. + + chvlan -n node1,node2,node3 -d + + +=item 4. + +To add KVM guests node1 and node2 to vlan 3 + + mkdef node1 arch=x86_64 groups=kvm,all installnic=mac primarynic=mac mgt=kvm netboot=pxe nfsserver=10.1.0.204 os=rhels6 profile=compute provmethod=install serialport=0 serialspeed=115200 vmcpus=1 vmhost=x3650n01 vmmemory=512 vmnics=br0 vmstorage=nfs://10.1.0.203/vms + + mkdef node2 arch=x86_64 groups=kvm,all installnic=mac primarynic=mac mgt=kvm netboot=pxe nfsserver=10.1.0.204 os=rhels6 profile=compute provmethod=install serialport=0 serialspeed=115200 vmcpus=1 vmhost=x3650n01 vmmemory=512 vmnics=br0 vmstorage=nfs://10.1.0.203/vms + + chvlan 3 -n node1,node2 + + mkvm node1,node2 -s 20G + + rpower node1,node2 on + + rinstall node1,node2 + +=item 5. + +To remove KVM guests node1 and node2 from vlan 3 + + chvlan 3 -n node1,node2 -d + + rpower node1,node2 off + + rmvm node1,node2 + +=back + +=head1 FILES + +/opt/xcat/bin/chvlan + +=head1 SEE ALSO + +L, L, L + diff --git a/xCAT-vlan/pods/man1/chvlanports.1.pod b/xCAT-vlan/pods/man1/chvlanports.1.pod new file mode 100755 index 000000000..b93cb0ea0 --- /dev/null +++ b/xCAT-vlan/pods/man1/chvlanports.1.pod @@ -0,0 +1,72 @@ +=head1 NAME + +B - It adds or removes nodes' switch interfaces for the vlan. + +=head1 SYNOPSIS + +B I B<-n>|B<--nodes> I B<-i>|B<--interface> I + +B I B<-n>|B<--nodes> I B<-i>|B<--interface> I B<-d>|B<--delete> + +B [B<-h>|B<--help>] + +B [B<-v>|B<--version>] + +=head1 DESCRIPTION + +The B command adds nodes switch interfaces to the given vlan. If -d is specified, the nodes switch interfaces will be removed from the vlan. + +This command won't create/remove vlans on switches, it just add node's switch ports into exisitng vlan or remove them from existing vlan on switch. Before calling chvlanports, the nodes switch interfaces should be configured in table switch, and vlan must already existing in switches. +=head1 Parameters + +I is a unique vlan number. + + +=head1 OPTIONS + +=over 10 + +=item B<-n|--nodes> The nodes or groups to be added or removed. It takes the noderange format. Please check the man page for noderange for details. + +=item B<-i|--interface> The interface name where the vlan will be tagged on. + +=item B<-h|--help> Display usage message. + +=item B<-v|--version> The Command Version. + +=back + +=head1 RETURN VALUE + +0 The command completed successfully. + +1 An error has occurred. + +=head1 EXAMPLES + +=over 3 + +=item 1. + +To add node1, node2 and node3 to vlan 3 using eth1 interface. + + chvlanports 3 -n node1,node2,node3 -i eth1 + + +=item 2. + +TO remove eth1 interface of node1, node2 and node3 from vlan 3. + + chvlanports 3 -n node1,node2,node3 -i eth1 -d + + +=back + +=head1 FILES + +/opt/xcat/bin/chvlanports + +=head1 SEE ALSO + +L, L, L, L + diff --git a/xCAT-vlan/pods/man1/lsvlan.1.pod b/xCAT-vlan/pods/man1/lsvlan.1.pod new file mode 100755 index 000000000..0d8eebd28 --- /dev/null +++ b/xCAT-vlan/pods/man1/lsvlan.1.pod @@ -0,0 +1,87 @@ +=head1 NAME + +B - It lists the existing vlans for the cluster. + +=head1 SYNOPSIS + +B + +B [I] + +B [B<-h>|B<--help>] + +B [B<-v>|B<--version>] + +=head1 DESCRIPTION + +The B command lists all the vlans for the cluster. If I is specifined it will list more details about this vlan including the nodes in the vlan. + +=head1 Parameters + +I is a unique vlan number. If it is omitted, all vlans will be listed. + + +=head1 OPTIONS + +=over 10 + +=item B<-h|--help> Display usage message. + +=item B<-v|--version> Command Version. + +=back + +=head1 RETURN VALUE + +0 The command completed successfully. + +1 An error has occurred. + +=head1 EXAMPLES + +=over 3 + +=item 1. + +To list all the vlans in the cluster + + lsvlan + +Output is similar to: + vlan 3: + subnet 10.3.0.0 + netmask 255.255.0.0 + + vlan 4: + subnet 10.4.0.0 + netmask 255.255.0.0 + + +=item 2. + +TO list the details for vlan3 + + lsvlan 3 + +Output is similar to: + vlan 3 + subnet 10.3.0.0 + netmask 255.255.0.0 + + hostname ip address node vm host + v3n1 10.3.0.1 c68m4hsp06 + v3n2 10.3.0.2 x3455n01 + v3n3 10.3.0.3 x3650n01 + v3n4 10.3.0.4 x3650n01kvm1 x3650n01 + v3n5 10.3.0.5 x3650n01kvm2 x3650n01 + +=back + +=head1 FILES + +/opt/xcat/bin/lsvlan + +=head1 SEE ALSO + +L, L, L + diff --git a/xCAT-vlan/pods/man1/mkvlan.1.pod b/xCAT-vlan/pods/man1/mkvlan.1.pod new file mode 100755 index 000000000..49f8d2a23 --- /dev/null +++ b/xCAT-vlan/pods/man1/mkvlan.1.pod @@ -0,0 +1,144 @@ +=head1 NAME + +B - It takes a list of nodes and create a private tagged vlan for them. + +=head1 SYNOPSIS + +B [I] B<-n>|B<--nodes> I [B<-t>|B<--net> I] [B<-m>|B<--mask> I] [B<-p>|B<--prefix> I] [B<-i>|B<--interface> I] + +B [B<-h>|B<--help>] + +B [B<-v>|B<--version>] + +=head1 DESCRIPTION + +The B command takes a list of nodes and move them to a private vlan. + +This command will configure the switch to create a new tagged vlan on the given nic. The primary nic will be used if the nic is not specified. The new vlan ID is given by the command. However, if it is omitted, xCAT will automatically generate the new vlan ID by querying all the switches involved and finding out the smallest common number that is not used by any existing vlans. The subnet and the netmask for the vlan will be derived from the value of "vlannets" and "vlanmasks" from the I table if -t and -m are not specified. The following are the default site table entires: + + vlannets="|(\d+)|10.($1+0).0.0|"; + vlanmask="255.255.0.0"; + +The vlan network will be entered in the I table. The nodes will be added to the vlan using the vlan tagging technique. And the new IP addresses and new hostnames will be assigned to the nodes. The -p flag specifies the node hostname prefix for the nodes. If it is not specified, by default, the hostnames for the nodes are having the following format: + +vnY where Y is the node number. For example, the hostname for node 5 on vlan 10 is v10n5. + +The I will be updated with the new vlan id for the node for standaline nodes. For KVM guests, the I identifies which vlan this node belongs to. For example: vl3 means this node is in vlan 3. + +If there are more than one switches involved in the vlan, the ports that connect to the switches need to entered in I with the following format: + + :switch,:switch.... + +For example: + + "42:switch1,43:switch2" + +This command will automatically configure the cross-over ports if the given nodes are on different switches. + +For added security, the root guard and bpdu guard will be enabled for the ports in this vlan. However, the guards will not be disabled if the ports are removed from the vlan using chvlan or rmvlan commands. To disable them, you need to use the switch command line interface. Please refer to the switch command line interface manual to see how to disable the root guard and bpdu guard for a port. + +=head1 Parameters + +I is a unique vlan number. If it is omitted, xCAT will automatically generate the new vlan ID by querying all the switches involved and finding out the smallest common number that is not used by any existing vlans. Use B to find out the existing vlan ids used by xCAT. + + +=head1 OPTIONS + +=over 10 + +=item B<-n|--nodes> The nodes or groups to be included in the vlan. It can be stand alone nodes or KVM guests. It takes the noderange format. Please check the man page for noderange for details. + +=item B<-t|--net> The subnet for the vlan. + +=item B<-m|--mask> The netmask for the vlan + +=item B<-p|--prefix> The prefix the the new hostnames for the nodes in the vlan. + +=item B<-i|--interface> The interface name where the vlan will be tagged on. If omitted, the xCAT management network will be assumed. For FVM, this is the interface name on the host. + +=item B<-h|--help> Display usage message. + +=item B<-v|--version> The Command Version. + +=back + +=head1 RETURN VALUE + +0 The command completed successfully. + +1 An error has occurred. + +=head1 EXAMPLES + + +To start, the xCAT switches and switches table needs to be filled with switch and port info for the nodes. For example, the swith table will look like this: + +#node,switch,port,vlan,interface,comments,disable +"node1","switch1","10",,,, +"node1","switch2","1",,"eth1",, +"node2","switch1","11",,"primary",, +"node2","switch2","2",,"eth1",, +"node3","switch1","12",,"primary:eth0",, +"node3","switch2","3",,"eth1",, + +Please note that the interface value for the management (primary) network can be empty, the word "primary" or "primary:ethx". For other networks, the interface attribute must be specified. + + +The following is an example of the switches table + +#switch,snmpversion,username,password,privacy,auth,linkports,sshusername,sshpassword,switchtype,comments,disable +"switch1","3","username","passw0rd",,"sha","48:switch2",,,,, +"switch2","2",,,,,"43:switch1",,,,, + +=over 3 + +=item 1. + +To make a private vlan for node1, node2 and node3 + + mkvlan -n node1,node2,node3 + +The vlan will be created on eth0 for the nodes. + +=item 2. + +To make a private vlan for node1, node2 and node3 on eth1, + + mkvlan -n node1,node2,node3 -i eth1 + + +=item 3. + +TO make a private vlan for node1, node2 with given subnet and netmask. + + mkvlan -n node1,node2,node3 -t 10.3.2.0 -m 255.255.255.0 + + +=item 4. + +To make a private vlan for KVM guests node1 and node2 + + chtab key=usexhrm site.vlaue=1 + + mkdef node1 arch=x86_64 groups=kvm,all installnic=mac primarynic=mac mgt=kvm netboot=pxe nfsserver=10.1.0.204 os=rhels6 profile=compute provmethod=install serialport=0 serialspeed=115200 vmcpus=1 vmhost=x3650n01 vmmemory=512 vmnics=br0 vmstorage=nfs://10.1.0.203/vms + + mkdef node2 arch=x86_64 groups=kvm,all installnic=mac primarynic=mac mgt=kvm netboot=pxe nfsserver=10.1.0.204 os=rhels6 profile=compute provmethod=install serialport=0 serialspeed=115200 vmcpus=1 vmhost=x3650n01 vmmemory=512 vmnics=br0 vmstorage=nfs://10.1.0.203/vms + + mkvlan -n node1,node2 + + mkvm node1,node2 -s 20G + + rpower node1,node2 on + + rinstall node1,node2 + +=back + +=head1 FILES + +/opt/xcat/bin/mkvlan + +=head1 SEE ALSO + +L, L, L + diff --git a/xCAT-vlan/pods/man1/rmvlan.1.pod b/xCAT-vlan/pods/man1/rmvlan.1.pod new file mode 100755 index 000000000..5f428edfa --- /dev/null +++ b/xCAT-vlan/pods/man1/rmvlan.1.pod @@ -0,0 +1,62 @@ +=head1 NAME + +B - It remves the vlan from the cluster. + +=head1 SYNOPSIS + +B I + +B [B<-h>|B<--help>] + +B [B<-v>|B<--version>] + +=head1 DESCRIPTION + +The B command removes the given vlan ID from the cluster. It removes the vlan id from all the swithces involved, deconfigures the nodes so that vlan adaptor (tag) will be remved, cleans up /etc/hosts, DNS and database tables for the given vlan. + +For added security, the root guard and bpdu guard were enabled for the ports in this vlan by mkvlan and chvlan commands. However, the guards will not be disabled by this command. To disable them, you need to use the switch command line interface. Please refer to the switch command line interface manual to see how to disable the root guard and bpdu guard for a port. + +=head1 Parameters + +I is a unique vlan number. + + +=head1 OPTIONS + +=over 10 + +=item B<-h|--help> Display usage message. + +=item B<-v|--version> The Command Version. + +=back + +=head1 RETURN VALUE + +0 The command completed successfully. + +1 An error has occurred. + +=head1 EXAMPLES + +=over 3 + +=item 1. + +To remove vlan 3 + + rmvlan 3 + +If the nodes are KVM guest then the do the following after the vlan is removed: + rpower node1,node2 off + rmvm node1,node2 + +=back + +=head1 FILES + +/opt/xcat/bin/rmvlan + +=head1 SEE ALSO + +L, L, L diff --git a/xCAT-vlan/xCAT-vlan.spec b/xCAT-vlan/xCAT-vlan.spec new file mode 100755 index 000000000..82d174541 --- /dev/null +++ b/xCAT-vlan/xCAT-vlan.spec @@ -0,0 +1,110 @@ +Summary: Executables and data of the xCAT vlan management project +Name: xCAT-vlan +Version: %(cat Version) +Release: snap%(date +"%Y%m%d%H%M") +Epoch: 4 +License: IBM +Group: Applications/System +Source: xCAT-vlan-%{version}.tar.gz +Packager: IBM Corp. +Vendor: IBM Corp. +Distribution: %{?_distribution:%{_distribution}}%{!?_distribution:%{_vendor}} +Prefix: /opt/xcat +BuildRoot: /var/tmp/%{name}-%{version}-%{release}-root + +%ifos linux +BuildArch: noarch +%endif + + +Provides: xCAT-vlan = %{epoch}:%{version} + + +Requires: xCAT-client + +%description +xCAT-vlan provides the xCAT vlan confiuration. + +%prep +%setup -q -n xCAT-vlan +%build + +# Convert pods to man pages and html pages +./xpod2man + +%install +# The install phase puts all of the files in the paths they should be in when the rpm is +# installed on a system. The RPM_BUILD_ROOT is a simulated root file system and usually +# has a value like: /var/tmp/xCAT-vlan-2.0-snap200802270932-root +rm -rf $RPM_BUILD_ROOT + +mkdir -p $RPM_BUILD_ROOT/%{prefix}/bin +mkdir -p $RPM_BUILD_ROOT/%{prefix}/lib/perl/xCAT +mkdir -p $RPM_BUILD_ROOT/%{prefix}/lib/perl/xCAT_plugin +mkdir -p $RPM_BUILD_ROOT/install/postscripts +mkdir -p $RPM_BUILD_ROOT/%{prefix}/share/man/man1 +mkdir -p $RPM_BUILD_ROOT/%{prefix}/share/doc/man1 + + +set +x + +cp xCAT/* $RPM_BUILD_ROOT/%{prefix}/lib/perl/xCAT +cp -R xCAT_plugin/* $RPM_BUILD_ROOT/%{prefix}/lib/perl/xCAT_plugin +cp install/postscripts/* $RPM_BUILD_ROOT/install/postscripts + + +# These were built dynamically in the build phase +cp share/man/man1/* $RPM_BUILD_ROOT/%{prefix}/share/man/man1 +chmod 444 $RPM_BUILD_ROOT/%{prefix}/share/man/man1/* + +# These were built dynamically during the build phase +cp share/doc/man1/* $RPM_BUILD_ROOT/%{prefix}/share/doc/man1 +chmod 644 $RPM_BUILD_ROOT/%{prefix}/share/doc/man1/* + +# These links get made in the RPM_BUILD_ROOT/prefix area +ln -sf ../bin/xcatclientnnr $RPM_BUILD_ROOT/%{prefix}/bin/mkvlan +ln -sf ../bin/xcatclientnnr $RPM_BUILD_ROOT/%{prefix}/bin/chvlan +ln -sf ../bin/xcatclientnnr $RPM_BUILD_ROOT/%{prefix}/bin/rmvlan +ln -sf ../bin/xcatclientnnr $RPM_BUILD_ROOT/%{prefix}/bin/lsvlan +ln -sf ../bin/xcatclientnnr $RPM_BUILD_ROOT/%{prefix}/bin/chvlanports + +set -x + + +%clean +# This step does not happen until *after* the %files packaging below +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root) +#%doc LICENSE.html +# Just package everything that has been copied into RPM_BUILD_ROOT +%{prefix} +/install/postscripts + + +%changelog + +%post +%ifos linux + if [ -f "/proc/cmdline" ]; then # prevent running it during install into chroot image + if [ -f $RPM_INSTALL_PREFIX0/sbin/xcatd ]; then + /etc/init.d/xcatd reload + fi + fi +%else + #restart the xcatd on if xCAT or xCATsn is installed already + if [ -f $RPM_INSTALL_PREFIX0/sbin/xcatd ]; then + if [ -n "$INUCLIENTS" ] && [ $INUCLIENTS -eq 1 ]; then + #Do nothing in not running system + echo "Do not restartxcatd in not running system" + else + XCATROOT=$RPM_INSTALL_PREFIX0 $RPM_INSTALL_PREFIX0/sbin/restartxcatd -r + fi + fi +%endif +exit 0 + +%preun + + diff --git a/xCAT-vlan/xCAT/SwitchHandler.pm b/xCAT-vlan/xCAT/SwitchHandler.pm new file mode 100755 index 000000000..08c91db81 --- /dev/null +++ b/xCAT-vlan/xCAT/SwitchHandler.pm @@ -0,0 +1,336 @@ +#!/usr/bin/perl +package xCAT::SwitchHandler; +BEGIN +{ + $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; +} +use lib "$::XCATROOT/lib/perl"; +use strict; +use xCAT::Table; +use xCAT::Utils; +use xCAT::MsgUtils; +use xCAT::MacMap; +use IO::Select; +use IO::Handle; +use Sys::Syslog; +use Data::Dumper; +use POSIX qw/WNOHANG/; +use SNMP; + +my $sysDescr='.1.3.6.1.2.1.1.1'; + +sub new { + my $self = {}; + my $proto = shift; + my $class = ref($proto) || $proto; + $self->{switch} = shift; + + bless ($self, $class); + return $self; +} + + +sub fill_sessionparms { + my $self = shift; + my %args = @_; + my $community=$args{community}; + $self->{sessionparms}=$args{sessionents}; + if ($self->{sessionparms}->{snmpversion}) { + if ($self->{sessionparms}->{snmpversion} =~ /3/) { #clean up to accept things like v3 or ver3 or 3, whatever. + $self->{sessionparms}->{snmpversion}=3; + unless ($self->{sessionparms}->{auth}) { + $self->{sessionparms}->{auth}='md5'; #Default to md5 auth if not specified but using v3 + } + } elsif ($self->{sessionparms}->{snmpversion} =~ /2/) { + $self->{sessionparms}->{snmpversion}=2; + } else { + $self->{sessionparms}->{snmpversion}=1; #Default to lowest common denominator, snmpv1 + } + } + unless (defined $self->{sessionparms}->{password}) { #if no password set, inherit the community + $self->{sessionparms}->{password}=$community; + } +} + + + +sub setoid { + my $session = shift; + my $oid = shift; + my $offset = shift; + my $value = shift; + my $type = shift; + unless ($type) { $type = 'INTEGER'; } + my $varbind = new SNMP::Varbind([$oid,$offset,$value,$type]); + my $data = $session->set($varbind); + if ($session->{ErrorStr}) { return (1,$session->{ErrorStr}); } + return 0,$varbind; +} + +#--------------------------------------------------------- +=head3 getsnmpsession + It gets an snmp session appropriate for a switch using the switches table + for guidance on the hows. + Arguments: vlan=> $vid if needed for community string indexing (optional) +=cut +#------------------------------------------------------------ +sub getsnmpsession { + my $self = shift; + my $community=shift; + my $vlanid = shift; + my $switch = $self->{switch}; + my $session; + my $sessionparams; + + if (!$community) { $community="private"; } + + $self->{sitetab} = xCAT::Table->new('site'); + my $tmp = $self->{sitetab}->getAttribs({key=>'snmpc'},'value'); + if ($tmp and $tmp->{value}) { $community = $tmp->{value} } + + my $switchestab=xCAT::Table->new('switches',-create=>0); + my $ent = $switchestab->getNodeAttribs($switch, [qw(switch snmpversion username password privacy auth)]); + if ($ent) { + $self->fill_sessionparms(community=>$community, sessionents=>$ent); + } + + $sessionparams=$self->{sessionparms}; + + my $snmpver='1'; + if ($sessionparams) { + $snmpver=$sessionparams->{snmpversion}; + $community=$sessionparams->{password}; + } + + if ($snmpver ne '3') { + if ($vlanid) { $community .= '@'.$vlanid; } + $session = new SNMP::Session( + DestHost => $switch, + Version => $snmpver, + Community => $community, + UseNumeric => 1 + ); + } else { #we have snmp3 + my %args= ( + DestHost => $switch, + SecName => $sessionparams->{username}, + AuthProto => uc($sessionparams->{auth}), + AuthPass => $community, + Version => $snmpver, + SecLevel => 'authNoPriv', + UseNumeric => 1 + ); + if ($vlanid) { $args{Context}="vlan-".$vlanid; } + if ($sessionparams->{privacy}) { + $args{SecLevel}='authPriv'; + $args{PrivProto} = uc($sessionparams->{privacy}); + $args{PrivPass} = $community; + $args{Retries} = 4; + $args{Timeout}=1500000; + } + + #print "args=" . Dumper(%args) . "\n"; + $session = new SNMP::Session(%args); + } + + #print "switch=$switch\n"; + if (!$session) { return $session;} + + #get the the switch brand name + my $tmp = xCAT::MacMap::walkoid($session, "$sysDescr", silentfail=>1); + my $swbrand; + if ($tmp->{0}) { + #print "Desc=" . $tmp->{0} . "\n"; + my @switch_plugins=glob("$::XCATROOT/lib/perl/xCAT_plugin/vlan/*.pm"); + foreach my $fn (@switch_plugins) { + $fn =~ /.*\/([^\/]*).pm$/; + my $module = $1; + #print "fn=$fn,modele=$module\n"; + if ( ! eval { require "$fn" }) { + xCAT::MsgUtils->message("S", "Cannot load module $fn"); + next; + } else { + no strict 'refs'; + my $filter=${"xCAT_plugin::vlan::".$module."::"}{filter_string}->(); + my $descr=$tmp->{0}; + if ($descr =~ /$filter/) { + $self->{module}=$module; + #print "found it:$module\n"; + last; + } + } + } + } + + if (!exists($self->{module})) { + $self->{session}=0; + return 0; + } else { + $self->{session}=$session; + return $session; + } +} + + + +#-------------------------------------------------------------- +=head3 get_vlan_ids + + It gets the existing vlan IDs for the switch. + Returns: an array containing all the vlan ids for the switch +=cut +#------------------------------------------------------------- +sub get_vlan_ids { + my $self = shift; + my $session=$self->{session}; + if (! $self->{session}) { + $session = $self->getsnmpsession(); + } + unless ($session) { xCAT::MsgUtils->message("S","Failed to communicate with " . $self->{switch} . " or find a plugin module for the switch."); return; } + + no strict 'refs'; + return ${"xCAT_plugin::vlan::".$self->{module}."::"}{get_vlan_ids}->($session); +} + + +#-------------------------------------------------------------- +=head3 get_vlanids_for_ports + It returns a hash pointer that contains the vlan id for each given port. +=cut +#------------------------------------------------------------- +sub get_vlanids_for_ports { + my $self = shift; + my @ports=@_; + + my $session=$self->{session}; + if (! $self->{session}) { + $session = $self->getsnmpsession(); + } + unless ($session) { xCAT::MsgUtils->message("S","Failed to communicate with " . $self->{switch} . " or find a plugin module for the switch."); return; } + + no strict 'refs'; + return ${"xCAT_plugin::vlan::".$self->{module}."::"}{get_vlanids_for_ports}->($session, @ports); + +} + + +#-------------------------------------------------------------- +=head3 create_vlan + Creates a new vlan on the switch + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub create_vlan { + my $self = shift; + my $vlan_id=shift; + my $vlan_name="xcat_vlan_" . $vlan_id; + + #print "create vlan get called.\n"; + + my $session=$self->{session}; + if (! $self->{session}) { + $session = $self->getsnmpsession(); + } + #my $session = $self->getsnmpsession(); + unless ($session) { xCAT::MsgUtils->message("S","Failed to communicate with " . $self->{switch}. " or find a plugin module for the switch."); return; } + #print Dumper($self->{sessionparms}); + + no strict 'refs'; + return ${"xCAT_plugin::vlan::".$self->{module}."::"}{create_vlan}->($session, $vlan_id); +} + +#-------------------------------------------------------------- +=head3 add_ports_to_vlan + Adds the given ports to the existing vlan + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub add_ports_to_vlan { + my $self = shift; + my $vlan_id=shift; + my $portmode=shift; + my @ports=@_; + + #print "vlan=$vlan_id, ports=@ports\n"; + + my $session=$self->{session}; + if (! $self->{session}) { + $session = $self->getsnmpsession(); + } + #my $session = $self->getsnmpsession(); + unless ($session) { xCAT::MsgUtils->message("S","Failed to communicate with " . $self->{switch}. " or find a plugin module for the switch."); return; } + + no strict 'refs'; + return ${"xCAT_plugin::vlan::".$self->{module}."::"}{add_ports_to_vlan}->($session, $vlan_id, $portmode, @ports); +} + +#------------------------------------------------------- +=head3 add_crossover_ports_to_vlan + It enables the vlan on the cross-over links. + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------- +sub add_crossover_ports_to_vlan { + my $self = shift; + my $vlan_id=shift; + my @switches=@_; + + if (@switches == 0) { return (0, ""); } + + my $session=$self->{session}; + if (! $self->{session}) { + $session = $self->getsnmpsession(); + } + #my $session = $self->getsnmpsession(); + unless ($session) { xCAT::MsgUtils->message("S","Failed to communicate with " . $self->{switch}. " or find a plugin module for the switch."); return; } + + no strict 'refs'; + return ${"xCAT_plugin::vlan::".$self->{module}."::"}{add_crossover_ports_to_vlan}->($session, $vlan_id, $self->{switch}, @switches); +} + +#-------------------------------------------------------------- +=head3 remove_vlan + Remove a vlan from the switch + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub remove_vlan { + my $self = shift; + my $vlan_id=shift; + + my $session=$self->{session}; + if (! $self->{session}) { + $session = $self->getsnmpsession(); + } + #my $session = $self->getsnmpsession(); + unless ($session) { xCAT::MsgUtils->message("S","Failed to communicate with " . $self->{switch}. " or find a plugin module for the switch."); return; } + + no strict 'refs'; + return ${"xCAT_plugin::vlan::".$self->{module}."::"}{remove_vlan}->($session, $vlan_id); +} + +#-------------------------------------------------------------- +=head3 remove_ports_from_vlan + Remove ports from a vlan + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub remove_ports_from_vlan { + my $self = shift; + my $vlan_id=shift; + my @ports = @_; + + my $session=$self->{session}; + if (! $self->{session}) { + $session = $self->getsnmpsession(); + } + #my $session = $self->getsnmpsession(); + unless ($session) { xCAT::MsgUtils->message("S","Failed to communicate with " . $self->{switch}. " or find a plugin module for the switch."); return; } + + no strict 'refs'; + return ${"xCAT_plugin::vlan::".$self->{module}."::"}{remove_ports_from_vlan}->($session, $vlan_id, @ports); +} + + + +1; diff --git a/xCAT-vlan/xCAT_plugin/vlan.pm b/xCAT-vlan/xCAT_plugin/vlan.pm new file mode 100755 index 000000000..30ef5dea6 --- /dev/null +++ b/xCAT-vlan/xCAT_plugin/vlan.pm @@ -0,0 +1,2754 @@ + +#------------------------------------------------------- + +=head1 + xCAT plugin package to handle xdsh + + Supported command: + nodenetconn + ipforward (internal command) + +=cut + +#------------------------------------------------------- +package xCAT_plugin::vlan; +BEGIN +{ + $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; +} +use lib "$::XCATROOT/lib/perl"; +use strict; +use xCAT::Table; +use xCAT::Utils; +use xCAT::NetworkUtils; +use xCAT::MsgUtils; +use Getopt::Long; +use xCAT::NodeRange; +use Data::Dumper; +use Text::Balanced qw(extract_bracketed); +use xCAT::SwitchHandler; +use Safe; +my $evalcpt = new Safe; +$evalcpt->share('&mknum'); +$evalcpt->permit('require'); + +my %Switches=(); + +1; + +#------------------------------------------------------- + +=head3 handled_commands + +Return list of commands handled by this plugin + +=cut + +#------------------------------------------------------- + +sub handled_commands +{ + return { + mkvlan => "vlan", + chvlan => "vlan", + rmvlan => "vlan", + lsvlan => "vlan", + chvlanports => "vlan", + }; +} + + +#------------------------------------------------------- + +=head3 preprocess_request + + Preprocess the command + +=cut + +#------------------------------------------------------- +sub preprocess_request +{ + + my $request = shift; + my $callback = shift; + my $sub_req = shift; + my $command = $request->{command}->[0]; + $::CALLBACK = $callback; + + #if already preprocessed, go straight to request + if ((defined($request->{_xcatpreprocessed})) + && ($request->{_xcatpreprocessed}->[0] == 1)) + { + return [$request]; + } + + if ($command eq "mkvlan") { + return preprocess_mkvlan($request,$callback,$sub_req); + } elsif($command eq "chvlanports"){ + return preprocess_chvlanports($request,$callback,$sub_req); + } elsif ($command eq "chvlan") { + return preprocess_chvlan($request,$callback,$sub_req); + } elsif ($command eq "rmvlan") { + return preprocess_rmvlan($request,$callback,$sub_req); + } elsif ($command eq "lsvlan") { + return preprocess_lsvlan($request,$callback,$sub_req); + } else { + my $rsp={}; + $rsp->{error}->[0]= "$command: unsupported command."; + $callback->($rsp); + return 1; + } +} + +sub preprocess_mkvlan { + my $request = shift; + my $callback = shift; + my $sub_req = shift; + my $command = $request->{command}->[0]; + my $args = $request->{arg}; + + # parse the options + @ARGV=(); + if ($args) { + @ARGV=@{$args}; + } + Getopt::Long::Configure("bundling"); + Getopt::Long::Configure("no_pass_through"); + + my $nr; + my $net; + my $netmask; + my $prefix; + my $nic; + if(!GetOptions( + 'h|help' => \$::HELP, + 'v|version' => \$::VERSION, + 'n|nodes=s' => \$nr, + 't|net=s' => \$net, + 'm|mask=s' => \$netmask, + 'p|prefix=s' => \$prefix, + 'i|interface=s' => \$nic, + )) + { + &mkvlan_usage($callback); + return 1; + } + # display the usage if -h or --help is specified + if ($::HELP) { + &mkvlan_usage($callback); + return 0; + } + # display the version statement if -v or --verison is specified + if ($::VERSION) + { + my $rsp={}; + $rsp->{data}->[0]= xCAT::Utils->Version(); + $callback->($rsp); + return 0; + } + + my $vlan_id=0; + if (@ARGV>0) { + $vlan_id=$ARGV[0]; + } + + my @nodes=(); + if ($nr) { + #get nodes + @nodes = noderange($nr); + if (nodesmissed) { + my $rsp={}; + $rsp->{data}->[0]= "Invalid nodes in noderange:".join(',',nodesmissed); + $callback->($rsp); + return 1; + } + } else { + my $rsp={}; + $rsp->{data}->[0]= "Please specify a list of nodes to be added to the new vlan."; + $callback->($rsp); + return 1; + } + + + if ($net && (!$netmask)) { + my $rsp={}; + $rsp->{data}->[0]= "Please specify a netmask for the vlan."; + $callback->($rsp); + return 1; + } + if ($netmask && (!$net)) { + my $rsp={}; + $rsp->{data}->[0]= "Please specify a network address for the vlan."; + $callback->($rsp); + return 1; + } + + #let process _request to handle it + my $reqcopy = {%$request}; + $reqcopy->{_xcatpreprocessed}->[0] = 1; + $reqcopy->{vlanid}->[0] = $vlan_id; + $reqcopy->{node} = \@nodes; + if ($net) { $reqcopy->{net}->[0]=$net; } + if ($netmask) { $reqcopy->{netmask}->[0]=$netmask; } + if ($prefix) { $reqcopy->{prefix}->[0]=$prefix; } + if ($nic) { $reqcopy->{nic}->[0]=$nic; } + return [$reqcopy]; +} +# TODO: finish this +sub preprocess_chvlanports { + my $request = shift; + my $callback = shift; + my $sub_req = shift; + my $command = $request->{command}->[0]; + my $args = $request->{arg}; + + # parse the options + @ARGV=(); + if ($args) { + @ARGV=@{$args}; + } + Getopt::Long::Configure("bundling"); + Getopt::Long::Configure("no_pass_through"); + + my $nr; + my $delete; + my $nic; + if(!GetOptions( + 'h|help' => \$::HELP, + 'v|version' => \$::VERSION, + 'n|nodes=s' => \$nr, + 'd|delete' => \$delete, + 'i|interface=s' => \$nic, + )) + { + &chvlanports_usage($callback); + return 1; + } + # display the usage if -h or --help is specified + if ($::HELP) { + &chvlanports_usage($callback); + return 0; + } + # display the version statement if -v or --verison is specified + if ($::VERSION) + { + my $rsp={}; + $rsp->{data}->[0]= xCAT::Utils->Version(); + $callback->($rsp); + return 0; + } + if (@ARGV==0) { + my $rsp={}; + $rsp->{data}->[0]= "Please specify a vlan id."; + $callback->($rsp); + return 1; + } + + my $vlan_id=$ARGV[0]; + + my @nodes=(); + if (!$nr) { + my $rsp={}; + $rsp->{data}->[0]= "Please specify a range of nodes to be added to vlan $vlan_id."; + $callback->($rsp); + return 1; + } else { + #get nodes + @nodes = noderange($nr); + if (nodesmissed) { + my $rsp={}; + $rsp->{data}->[0]= "Invalid nodes in noderange:".join(',',nodesmissed); + $callback->($rsp); + return 1; + } + } + + if (!$nic) { + my $rsp={}; + $rsp->{data}->[0]= "Please specify a network interface for nodes"; + $callback->($rsp); + return 1; + } + + #let process _request to handle it + my $reqcopy = {%$request}; + $reqcopy->{_xcatpreprocessed}->[0] = 1; + $reqcopy->{vlanid}->[0]=$vlan_id; + $reqcopy->{node} = \@nodes; + $reqcopy->{delete}->[0] = $delete; + $reqcopy->{nic}->[0]=$nic; + return [$reqcopy]; +} + +sub preprocess_chvlan { + my $request = shift; + my $callback = shift; + my $sub_req = shift; + my $command = $request->{command}->[0]; + my $args = $request->{arg}; + + # parse the options + @ARGV=(); + if ($args) { + @ARGV=@{$args}; + } + Getopt::Long::Configure("bundling"); + Getopt::Long::Configure("no_pass_through"); + + my $nr; + my $delete; + my $nic; + if(!GetOptions( + 'h|help' => \$::HELP, + 'v|version' => \$::VERSION, + 'n|nodes=s' => \$nr, + 'd|delete' => \$delete, + 'i|interface=s' => \$nic, + )) + { + &chvlan_usage($callback); + return 1; + } + # display the usage if -h or --help is specified + if ($::HELP) { + &chvlan_usage($callback); + return 0; + } + # display the version statement if -v or --verison is specified + if ($::VERSION) + { + my $rsp={}; + $rsp->{data}->[0]= xCAT::Utils->Version(); + $callback->($rsp); + return 0; + } + if (@ARGV==0) { + my $rsp={}; + $rsp->{data}->[0]= "Please specify a vlan id."; + $callback->($rsp); + return 1; + } + + my $vlan_id=$ARGV[0]; + + my @nodes=(); + if (!$nr) { + my $rsp={}; + $rsp->{data}->[0]= "Please specify a range of nodes to be added to vlan $vlan_id."; + $callback->($rsp); + return 1; + } else { + #get nodes + @nodes = noderange($nr); + if (nodesmissed) { + my $rsp={}; + $rsp->{data}->[0]= "Invalid nodes in noderange:".join(',',nodesmissed); + $callback->($rsp); + return 1; + } + } + + #let process _request to handle it + my $reqcopy = {%$request}; + $reqcopy->{_xcatpreprocessed}->[0] = 1; + $reqcopy->{vlanid}->[0]=$vlan_id; + $reqcopy->{node} = \@nodes; + $reqcopy->{delete}->[0] = $delete; + if ($nic) { $reqcopy->{nic}->[0]=$nic; } + return [$reqcopy]; +} + +sub preprocess_rmvlan { + my $request = shift; + my $callback = shift; + my $sub_req = shift; + my $command = $request->{command}->[0]; + my $args = $request->{arg}; + + # parse the options + @ARGV=(); + if ($args) { + @ARGV=@{$args}; + } + Getopt::Long::Configure("bundling"); + Getopt::Long::Configure("no_pass_through"); + + my $nodes; + if(!GetOptions( + 'h|help' => \$::HELP, + 'v|version' => \$::VERSION, + )) + { + &rmvlan_usage($callback); + return 1; + } + # display the usage if -h or --help is specified + if ($::HELP) { + &rmvlan_usage($callback); + return 0; + } + # display the version statement if -v or --verison is specified + if ($::VERSION) + { + my $rsp={}; + $rsp->{data}->[0]= xCAT::Utils->Version(); + $callback->($rsp); + return 0; + } + + if (@ARGV==0) { + my $rsp={}; + $rsp->{data}->[0]= "Please specify a vlan id."; + $callback->($rsp); + return 1; + } + + my $vlan_id=$ARGV[0]; + + #let process _request to handle it + my $reqcopy = {%$request}; + $reqcopy->{_xcatpreprocessed}->[0] = 1; + $reqcopy->{vlanid}->[0]=$vlan_id; + return [$reqcopy]; +} + +sub preprocess_lsvlan { + my $request = shift; + my $callback = shift; + my $sub_req = shift; + my $command = $request->{command}->[0]; + my $args = $request->{arg}; + + # parse the options + @ARGV=(); + if ($args) { + @ARGV=@{$args}; + } + Getopt::Long::Configure("bundling"); + Getopt::Long::Configure("no_pass_through"); + + my $nodes; + if(!GetOptions( + 'h|help' => \$::HELP, + 'v|version' => \$::VERSION, + )) + { + &lsvlan_usage($callback); + return 1; + } + # display the usage if -h or --help is specified + if ($::HELP) { + &lsvlan_usage($callback); + return 0; + } + # display the version statement if -v or --verison is specified + if ($::VERSION) + { + my $rsp={}; + $rsp->{data}->[0]= xCAT::Utils->Version(); + $callback->($rsp); + return 0; + } + + my $vlan_id=0; + if (@ARGV>0) { + $vlan_id=$ARGV[0]; + } + + #let process _request to handle it + my $reqcopy = {%$request}; + $reqcopy->{_xcatpreprocessed}->[0] = 1; + $reqcopy->{vlanid}->[0]=$vlan_id; + return [$reqcopy]; +} + + +#------------------------------------------------------- + +=head3 process_request + + Process the command + +=cut + +#------------------------------------------------------- +sub process_request +{ + my $request = shift; + my $callback = shift; + my $sub_req = shift; + + my $command = $request->{command}->[0]; + my $args = $request->{arg}; + $::CALLBACK = $callback; + + if ($command eq "mkvlan") { + return process_mkvlan($request,$callback,$sub_req); + } elsif ($command eq "chvlanports"){ + return process_chvlanports($request,$callback,$sub_req); + } elsif ($command eq "chvlan") { + return process_chvlan($request,$callback,$sub_req); + } elsif ($command eq "rmvlan") { + return process_rmvlan($request,$callback,$sub_req); + } elsif ($command eq "lsvlan") { + return process_lsvlan($request,$callback,$sub_req); + } else { + my $rsp={}; + $rsp->{error}->[0]= "$command: unsupported command."; + $callback->($rsp); + return 1; + } + + + + return 0; +} + + +sub process_mkvlan { + my $request = shift; + my $callback = shift; + my $sub_req = shift; + + my $vlan_id=0; + if (exists($request->{vlanid})) { + $vlan_id=$request->{vlanid}->[0]; + } + my @nodes=(); + if (exists($request->{node})) { + @nodes=@{$request->{node}}; + } + + my $net; + if (exists($request->{net})) { + $net=$request->{net}->[0]; + } + my $netmask; + if (exists($request->{netmask})) { + $netmask=$request->{netmask}->[0]; + } + my $prefix; + if (exists($request->{prefix})) { + $prefix=$request->{prefix}->[0]; + } + + my $nic; + if (exists($request->{nic})) { + $nic=$request->{nic}->[0]; + } + + #check if the vlan is already defined on the table + my $nwtab=xCAT::Table->new("networks", -create =>1); + if ($vlan_id > 0) { + if ($nwtab) { + my @tmp1=$nwtab->getAllAttribs(('vlanid', 'net', 'mask')); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach(@tmp1) { + if ($vlan_id eq $_->{vlanid}) { + my $rsp={}; + $rsp->{error}->[0]= "The vlan $vlan_id is already used. Please choose another vlan id."; + $callback->($rsp); + return 1; + } + } + } + } + } + + #make sure the vlan id is not currently used by another vlan + #if no vlan id is supplied, automatically generate a new vlan id + if ($vlan_id > 0) { + if (!verify_vlanid($vlan_id, $callback)) { return 1; } + } else { + $vlan_id=get_next_vlanid($callback); + if (!$vlan_id) { return 1;} + } + + if (!$prefix) { + $prefix="v". $vlan_id . "n"; #default + } + + + ####now get the switch ports for the nodes + my %swinfo=(); # example: %swinfo=( switch1=>{ 23=>{ nodes: [node1] + # interface: eth0 + # }, + # 24=>{ nodes: [node2,node3], + # vmhost: kvm1 + # interface: eth1 + # } + # }, + # switch12=>{ 3=>{ nodes: [node4]}, + # 7=>{ nodes: [node4] + # interface: eth0 }, + # 9=>{ nodes: [node6]} + # } + # ) + + my %vminfo=(); # example: %vminfo=( kvm1=>{clients:[node1,node2...]}, + # kvm2=>{clients:[node2,node3...]}, + # ) + # + my @vmnodes=(); # vm clients + my @snodes=(); # stand alone nodes + + + #get the vm hosts + my $vmtab=xCAT::Table->new("vm", -create =>1); + my $vmtmphash = $vmtab->getNodesAttribs(\@nodes, ['host', 'nics']) ; + foreach my $node (@nodes) { + my $host; + my $ent=$vmtmphash->{$node}->[0]; + if (ref($ent) and defined $ent->{host}) { + $host = $ent->{host}; + if (exists($vminfo{$host})) { + my $pa=$vminfo{$host}->{clients}; + push(@$pa, $node); + } else { + $vminfo{$host}->{clients}=[$node]; + } + + push(@vmnodes, $node); + } + } + + + if (@vmnodes > 0) { + foreach my $node (@nodes) { + if (! grep /^$node$/, @vmnodes) { + push(@snodes, $node); + } + } + } else { + @snodes=@nodes; + } + + #get the switch and port numbers for each node + my @vmhosts=keys(%vminfo); + my @anodes=(@snodes, @vmhosts); #nodes that connects to the switch + my %swsetup=(); + my $swtab=xCAT::Table->new("switch", -create =>1); + my $swtmphash = $swtab->getNodesAttribs(\@anodes, ['switch', 'port', 'vlan', 'interface']) ; + my @missed_nodes=(); + foreach my $node (@anodes) { + my $node_enties=$swtmphash->{$node}; + if ($node_enties) { + my $i=-1; + my $use_this=0; + foreach my $ent (@$node_enties) { + $i++; + if (ref($ent) and defined $ent->{switch} and defined $ent->{port}) { + my $switch; + my $port; + $switch = $ent->{switch}; + $port = $ent->{port}; + my $interface="primary"; + if (defined $ent->{interface}) { $interface=$ent->{interface};} + # for primary nic, the interface can be empty, "primary" or "primary:eth0" + #print "***nic=$nic, interface=$interface\n"; + if ($nic) { + if ($interface =~ /primary/) { + $interface =~ s/primary(:)?//g; + } + if ($interface && ($interface eq $nic)) { $use_this=1; } + } else { + if ($interface =~ /primary/) { $use_this=1; } + } + + if (! $use_this) { + next; + } + else { + $swsetup{$node}->{port}=$port; + $swsetup{$node}->{switch}=$switch; + if (defined $ent->{vlan}) { + $swsetup{$node}->{vlan}=$ent->{vlan}; + } else { + $swsetup{$node}->{vlan}=""; + } + } + + if ($interface) { + $swinfo{$switch}->{$port}->{interface}=$interface; + } + + if (exists($vminfo{$node})) { + $swinfo{$switch}->{$port}->{vmhost}=$node; + $swinfo{$switch}->{$port}->{nodes}=$vminfo{$node}->{clients}; + } else { + $swinfo{$switch}->{$port}->{nodes}=[$node]; + } + last; + + } + } + if ( $use_this != 1 ) { + push (@missed_nodes, $node); + } + } + } + + if (@missed_nodes > 0) { + my $rsp={}; + $rsp->{error}->[0]= "Cannot proceed, please define switch and port info on the switch table for the following nodes:\n @missed_nodes\n"; + $callback->($rsp); + return 1; + } + + #print "vminfo=" . Dumper(%vminfo) . "\n"; + #print "swinfo=" . Dumper(%swinfo) . "\n"; + #print "snodes=" . Dumper(@snodes) . "\n"; + #print "anodes=" . Dumper(@anodes) . "\n"; + #print "vmnodes=" . Dumper(@vmnodes) . "\n"; + #print "swtmphash" . Dumper($swtmphash). "\n"; + + ### verify the ports are not used by other vlans + #if (!verify_switch_ports($vlan_id, \%swinfo, $callback)) { return 1;} + + ### now pick a network address for the vlan + if (!$net) { + ($net, $netmask)=get_subnet($vlan_id, $callback); + } + + + ### save the vlan on the networks table + my %key_col = (netname=>"vlan$vlan_id"); + my %tb_cols=(vlanid=>$vlan_id, net=>$net, mask=>$netmask); + $nwtab->setAttribs(\%key_col, \%tb_cols); + + + ### configure vlan on the switch + if (!create_vlan($vlan_id, \%swinfo, $callback)) { return 1;} + if (!add_ports($vlan_id, \%swinfo, $callback)) { return 1;} + my @sws=keys(%swinfo); + if (!add_crossover_ports($vlan_id, \@sws, $callback)) { return 1;} + + ### add the vlanid for the standalone nodes on the switch table + ### append the vlan id for the vmhosts on the switch table + add_vlan_to_switch_table(\%swsetup, $vlan_id); + + ### get node ip and vlan hostname from the hosts table. + #If it is not defined, put the default into the host table + my @allnodes=(@anodes, @vmnodes); + if (!add_vlan_ip_host($net, $netmask, $prefix, 1, \@allnodes, $callback)) { return 1;} + + ### for vm nodes, add an additional nic on the vm.nics + if (@vmnodes > 0) { + my %setupnics=(); + my $new_nic="vl$vlan_id"; + foreach my $node (@vmnodes) { + my $ent=$vmtmphash->{$node}->[0]; + my $nics; + if (ref($ent) and defined $ent->{nics}) { + $nics=$ent->{nics}; + my @a=split(",", $nics); + if (!grep(/^$new_nic$/, @a)) { $nics="$nics,$new_nic"; } + } else { + $nics=$new_nic; + } + $setupnics{$node}={nics=>"$nics"}; + } + $vmtab->setNodesAttribs(\%setupnics); + } + + ### populate the /etc/hosts and make the DNS server on the mn aware this change + $::CALLBACK = $callback; + my $res = xCAT::Utils->runxcmd( { + command => ['makehosts'], + }, $sub_req, 0, 1); + my $rsp = {}; + $rsp->{data}->[0] = "Running makehosts..."; + $callback->($rsp); + if ($res && (@$res > 0)) { + $rsp = {}; + $rsp->{data} = $res; + $callback->($rsp); + } + + $::CALLBACK = $callback; + my $res = xCAT::Utils->runxcmd( { + command => ['makedns'], + }, $sub_req, 0, 1); + my $rsp = {}; + $rsp->{data}->[0] = "Running makedns..."; + $callback->($rsp); + if ($res && (@$res > 0)) { + $rsp->{data} = $res; + $callback->($rsp); + } + + my $cmd = "service named restart"; + my $rc=system $cmd; + + ### now go to the nodes to configure the vlan interface + $::CALLBACK = $callback; + my $args = ["-P", "configvlan $vlan_id --keephostname"]; + my $res = xCAT::Utils->runxcmd( { + command => ['updatenode'], + node => \@snodes, + arg => $args + }, $sub_req, 0, 1); + my $rsp = {}; + $rsp->{data}->[0] = "Running updatenode..."; + $callback->($rsp); + if ($res && (@$res > 0)) { + $rsp->{data} = $res; + $callback->($rsp); + } + + ### add configvlan postscripts to the postscripts table for the node + # so that next time the node bootup, the vlan can get configured + my @pnodes=(@snodes, @vmnodes); + add_postscript($callback, \@pnodes); + + my $rsp={}; + $rsp->{data}->[0]= "The vlan is successfully configured. "; + $rsp->{data}->[1]= " vlan id: $vlan_id"; + $rsp->{data}->[2]= " vlan subnet: $net"; + $rsp->{data}->[3]= " vlan netmask: $netmask"; + #$rsp->{data}->[4]= " vlan dhcp server:"; + #$rsp->{data}->[5]= " vlan dns server:"; + #$rsp->{data}->[6]= " vlan gateway:"; + $callback->($rsp); + + + return 0; +} + +#------------------------------------------------------- +=head3 add_vlan_to_switch_table + + It adds the vlan id to the switch.vlan for the given nodes. + +=cut +#------------------------------------------------------- +sub add_vlan_to_switch_table { + my $swsetup=shift; + my $vlan_id=shift; + + my $swtab1 = xCAT::Table->new( 'switch', -create=>1, -autocommit=>0 ); + foreach my $node (keys(%$swsetup)) { + my %keyhash=(); + my %updates=(); + $keyhash{'node'} = $node; + $keyhash{'switch'}= $swsetup->{$node}->{switch}; + $keyhash{'port'} = $swsetup->{$node}->{port}; + $updates{'vlan'}=$vlan_id; + my $vlan; + if($swsetup->{$node}->{vlan}) { + $vlan=$swsetup->{$node}->{vlan}; + my @a=split(",", $vlan); + if (!grep(/^$vlan_id$/, @a)) { $vlan="$vlan,$vlan_id";} + $updates{'vlan'}=$vlan; + } + + $swtab1->setAttribs( \%keyhash,\%updates ); + } + $swtab1->commit; +} + +sub remove_vlan_from_switch_table { + my $swsetup=shift; + my $vlan_id=shift; + #remove the vlan id from the switch table for standalone nodes + my $swtab1 = xCAT::Table->new('switch', -create=>1, -autocommit=>0 ); + foreach my $node (keys(%$swsetup)) { + my %keyhash=(); + my %updates=(); + $keyhash{'node'} = $node; + $keyhash{'switch'}= $swsetup->{$node}->{switch}; + $keyhash{'port'} = $swsetup->{$node}->{port}; + $updates{'vlan'} = ""; + if($swsetup->{$node}->{vlan}) { + my @a=split(',', $swsetup->{$node}->{vlan}); + my @b=grep(!/^$vlan_id$/,@a); + if (@b>0) { + $updates{'vlan'}=join(',', @b); + } + } + $swtab1->setAttribs( \%keyhash,\%updates ); + } + $swtab1->commit; +} + + +#------------------------------------------------------- +=head3 add_postscript + + It adds the 'configvlan' postscript to the postscript table + for the given nodes. + +=cut +#------------------------------------------------------- +sub add_postscript { + my $callback=shift; + my $anodes=shift; + + my $posttab=xCAT::Table->new("postscripts", -create =>1); + if ($posttab) { + (my $ref1) = $posttab->getAttribs({node => 'xcatdefaults'}, ('postscripts', 'postbootscripts')); + #if configvlan is in xcadefaults, then do nothing + if ($ref1) { + if ($ref1->{postscripts}) { + my @a = split(/,/, $ref1->{postscripts}); + if (grep(/^configvlan$/, @a)) { next; } + } + if ($ref1->{postbootscripts}) { + my @a = split(/,/, $ref1->{postbootscripts}); + if (grep(/^configvlan$/, @a)) { next; } + } + } + + #now check for each node + my %setup_hash; + my $postcache = $posttab->getNodesAttribs($anodes,[qw(postscripts postbootscripts)]); + foreach my $node (@$anodes) { + my $ref = $postcache->{$node}->[0]; + if ($ref) { + if (exists($ref->{postscripts})) { + my @a = split(/,/, $ref->{postscripts}); + if (grep(/^configvlan$/, @a)) { next; } + } + + if (exists($ref->{postbootscripts})) { + my $post=$ref->{postbootscripts}; + my @old_a=split(',', $post); + if (grep(/^configvlan$/, @old_a)) { + next; + } else { + $setup_hash{$node}={postbootscripts=>"$post,configvlan"}; + } + } else { + $setup_hash{$node}={postbootscripts=>"configvlan"}; + } + } else { + $setup_hash{$node}={postbootscripts=>"configvlan"}; + } + } + if (keys(%setup_hash) > 0) { + $posttab->setNodesAttribs(\%setup_hash); + } + } + + return 0; +} + + + +#------------------------------------------------------- +=head3 add_vlan_ip_host + + It goes to the hosts.otherinterfaces to see if the vlan ip and hostname + is defined. If not, it will add the default in the table. + The default is vn + +=cut +#------------------------------------------------------- +sub add_vlan_ip_host { + my $subnet=shift; + my $netmask=shift; + my $prefix=shift; + my $node_number=shift; + my $nodes=shift; + my $callback=shift; + + my $hoststab = xCAT::Table->new('hosts'); + my $hostscache = $hoststab->getNodesAttribs($nodes,[qw(node otherinterfaces)]); + my %setup_hash; + foreach my $node (@$nodes) { + my $ref = $hostscache->{$node}->[0]; + my $found=0; + my $otherinterfaces; + if ($ref && exists($ref->{otherinterfaces})){ + $otherinterfaces = $ref->{otherinterfaces}; + my @itf_pairs=split(/,/, $otherinterfaces); + foreach (@itf_pairs) { + my ($name,$ip)=split(/:/, $_); + if ($name =~ /^-/ ) { + $name = $node.$name; + } + if(xCAT::NetworkUtils->ishostinsubnet($ip, $netmask, $subnet)) { + $found=1; + } + } + } + if (!$found) { + my $hostname=$prefix . "$node_number"; + my $ip=""; + if ($subnet =~ /\d+\.\d+\.\d+\.\d+/) {# ipv4 address + $subnet =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/; + my $netnum = ($1<<24)+($2<<16)+($3<<8)+$4; + my $ipnum=$netnum + $node_number; + my @a=(); + for (my $i=3; $i>-1; $i--) { + $a[$i]=$ipnum % 256; + $ipnum = int($ipnum / 256); + } + $ip= "$a[0].$a[1].$a[2].$a[3]"; + #print "ip=$ip\n"; + } else { + my $rsp={}; + $rsp->{error}->[0]= "Does not support IPV6 address yet."; + $callback->($rsp); + return 0; + } + $node_number++; + + if ($otherinterfaces) { + $setup_hash{$node}={otherinterfaces=>"$hostname:$ip,$otherinterfaces"}; + } else { + $setup_hash{$node}={otherinterfaces=>"$hostname:$ip"}; + } + } + } #foreach node + + if (keys(%setup_hash) > 0) { + $hoststab->setNodesAttribs(\%setup_hash); + } + + return 1; +} + + +#------------------------------------------------------- +=head3 get_prefix_and_nodenumber + + It gets the prefix and max node number from the current nodes + in the given vlan. + +=cut +#------------------------------------------------------- +sub get_prefix_and_nodenumber { + my $vlan_id = shift; + my $subnet = shift; + my $netmask = shift; + + #get all the nodes that are in the vlan + my $swtab=xCAT::Table->new("switch", -create =>0); + my @nodes=(); + my @tmp1=$swtab->getAllAttribs(('node', 'vlan')); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach my $ent (@tmp1) { + my @nodes_tmp=noderange($ent->{node}); + foreach my $node (@nodes_tmp) { + if ($ent->{vlan}) { + my @a=split(",", $ent->{vlan}); + if (grep(/^$vlan_id$/,@a)) { + push(@nodes, $node); + } + } + } + } + } + + + #get all the vm clients if the node is a vm host + my $vmtab=xCAT::Table->new("vm", -create =>0); + my @vmnodes=(); + if ($vmtab) { + my @tmp1=$vmtab->getAllAttribs('node', 'host'); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach(@tmp1) { + my $host = $_->{host}; + if (grep(/^$host$/, @nodes)) { + my @nodes_tmp=noderange($_->{node}); + foreach my $node (@nodes_tmp) { + push(@vmnodes, $node); + } + } + } + } + } + + @nodes=(@nodes, @vmnodes); + #print "nodes=@nodes\n"; + + #now go to hosts table to get the prefix and max node number + my $hoststab = xCAT::Table->new('hosts'); + my $hostscache = $hoststab->getNodesAttribs(\@nodes,[qw(node otherinterfaces)]); + my $max=0; + my $prefix; + foreach my $node (@nodes) { + my $ref = $hostscache->{$node}->[0]; + my $otherinterfaces; + if ($ref && exists($ref->{otherinterfaces})){ + $otherinterfaces = $ref->{otherinterfaces}; + my @itf_pairs=split(/,/, $otherinterfaces); + my @itf_pairs2=(); + foreach (@itf_pairs) { + my ($name,$ip)=split(/:/, $_); + if(xCAT::NetworkUtils->ishostinsubnet($ip, $netmask, $subnet)) { + $name =~ /^(.*)([\d]+)$/; + if ($2 > $max) { $max=$2;} + if (!$prefix) { $prefix=$1;} + #print "name=$name\n, 1=$1, 2=$2\n"; + } + } + } + } #foreach node + + return ($prefix, $max); +} + + + +#------------------------------------------------------- +=head3 remove_vlan_ip_host + + It goes to the hosts.otherinterfaces to see if the vlan ip and hostname + is defined. If it is, it will remove it. It also remove the entried in + the /etc/hosts file + +=cut +#------------------------------------------------------- +sub remove_vlan_ip_host { + my $subnet=shift; + my $netmask=shift; + my $nodes=shift; + my $callback=shift; + + my $hoststab = xCAT::Table->new('hosts'); + my $hostscache = $hoststab->getNodesAttribs($nodes,[qw(node otherinterfaces)]); + my %setup_hash; + foreach my $node (@$nodes) { + my $ref = $hostscache->{$node}->[0]; + my $otherinterfaces; + if ($ref && exists($ref->{otherinterfaces})){ + $otherinterfaces = $ref->{otherinterfaces}; + my @itf_pairs=split(/,/, $otherinterfaces); + my @itf_pairs2=(); + my $index=0; + foreach (@itf_pairs) { + my ($name,$ip)=split(/:/, $_); + if(!xCAT::NetworkUtils->ishostinsubnet($ip, $netmask, $subnet)) { + $itf_pairs2[$index]=$_; + $index++; + } else { + my $cmd="sed -i /$ip/d /etc/hosts"; + my $rc=system $cmd; + } + } + if (@itf_pairs2 > 0) { + my $new_intf=join(",", @itf_pairs2); + $setup_hash{$node}={otherinterfaces=>$new_intf} + } else { + $setup_hash{$node}={otherinterfaces=>""}; + } + } + } #foreach node + + if (keys(%setup_hash) > 0) { + $hoststab->setNodesAttribs(\%setup_hash); + } + + return 1; +} + +#------------------------------------------------------- +=head3 verify_vlanid + + It goes to all the switches to make sure that the vlan + id is not used by other vlans. +=cut +#------------------------------------------------------- +sub verify_vlanid { + my $vlan_id=shift; + my $callback=shift; + + my $switchestab=xCAT::Table->new('switches',-create=>0); + my @tmp1=$switchestab->getAllAttribs(('switch')); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach(@tmp1) { + my @switches_tmp=noderange($_->{switch}); + if (@switches_tmp==0) { push @switches_tmp, $_->{switch}; } #sometimes the switch name is not on the node list table. + foreach my $switch (@switches_tmp) { + my $swh; + if (exists($Switches{$switch})) { $swh=$Switches{$switch};} + else { + $swh=new xCAT::SwitchHandler->new($switch); + $Switches{$switch}=$swh; + } + my @ids=$swh->get_vlan_ids(); + print "ids=@ids\n"; + foreach my $id (@ids) { + if ($id == $vlan_id) { + my $rsp={}; + $rsp->{error}->[0]= "The vlan id $vlan_id already exists on switch $switch. Please choose another vlan id.\n"; + $callback->($rsp); + return 0; + } + } + } + } + } + return 1; +} + +#------------------------------------------------------- +=head3 get_next_vlanid + + It automatically generates the vlan ID. It goes to all the + switches, get the smallest common integer that is not used + by any existing vlans. +=cut +#------------------------------------------------------- +sub get_next_vlanid { + my $callback=shift; + my $switchestab=xCAT::Table->new('switches',-create=>0); + my @tmp1=$switchestab->getAllAttribs(('switch')); + my %vlanids=(); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach(@tmp1) { + my @switches_tmp=noderange($_->{switch}); + if (@switches_tmp==0) { push @switches_tmp, $_->{switch}; } #sometimes the switch name is not on the node list table. + foreach my $switch (@switches_tmp) { + my $swh; + if (exists($Switches{$switch})) { $swh=$Switches{$switch};} + else { + $swh=new xCAT::SwitchHandler->new($switch); + $Switches{$switch}=$swh; + } + my @ids=$swh->get_vlan_ids(); + foreach my $id (@ids) { + $vlanids{$id}=1; + } + } + } + } + + for (my $index=2; $index<255; $index++) { + if (! exists($vlanids{$index})) { return $index; } + } + + my $rsp={}; + $rsp->{data}->[0]= "No valid vlan ID can be used any more, Please remove unused vlans.\n"; + $callback->($rsp); + + return 0; +} + +#------------------------------------------------------- +=head3 verify_switch_ports + + It checks if the switch ports to be configured are used by other vlans. + +=cut +#------------------------------------------------------- +sub verify_switch_ports { + my $vlan_id=shift; + my $swinfo=shift; + my $callback=shift; + + my $ret=1; + foreach my $switch (keys %$swinfo) { + my $porthash=$swinfo->{$switch}; + my $swh; + if (exists($Switches{$switch})) { $swh=$Switches{$switch};} + else { + $swh=new xCAT::SwitchHandler->new($switch); + $Switches{$switch}=$swh; + } + my $port_vlan_hash=$swh->get_vlanids_for_ports(keys(%$porthash)); + print "port_vlan_hash=" . Dumper($port_vlan_hash) . "\n"; + my @error_ports=(); + foreach my $port (keys(%$port_vlan_hash)) { + my $val=$port_vlan_hash->{$port}; + foreach my $tmp_vid (@$val) { + if (($tmp_vid != $vlan_id) && ($tmp_vid != 1) && ($tmp_vid ne 'NA')) { + if (exists($porthash->{$port}->{vmhost})) { next; } #skip the vmhost, vmhost can have more than one vlans + push(@error_ports, $port); + last; + } + } + } + if (@error_ports >0) { + $ret=0; + my $error_str; + foreach(@error_ports) { + my @tmp=@{$porthash->{$_}->{nodes}}; + my $ids_tmp=$port_vlan_hash->{$_}; + $error_str .= "$_: vlan-ids=@$ids_tmp nodes=@tmp\n"; + } + my $rsp={}; + $rsp->{error}->[0]= "The following ports on switch $switch are used by other vlans.\n$error_str"; + $callback->($rsp); + } + } + + return $ret; +} + +#------------------------------------------------------- +=head3 create_vlan + + It goes to the switches and create a new vlan. + Returns: 1 -- suggessful + 0 -- fail +=cut +#------------------------------------------------------- +sub create_vlan { + my $vlan_id=shift; + my $swinfo=shift; + my $callback=shift; + my $ret=1; + foreach my $switch (keys %$swinfo) { + my $swh; + if (exists($Switches{$switch})) { $swh=$Switches{$switch};} + else { + $swh=new xCAT::SwitchHandler->new($switch); + $Switches{$switch}=$swh; + } + #check if the vlan already exists on the switch + my @ids=$swh->get_vlan_ids(); + my $vlan_exists=0; + foreach my $id (@ids) { + if ($id == $vlan_id) { + $vlan_exists=1; + last; + } + } + + if (!$vlan_exists) { + #create the vlan + print "create vlan $vlan_id on switch $switch\n"; + my @ret=$swh->create_vlan($vlan_id); + if ($ret[0] != 0) { + my $rsp={}; + $rsp->{error}->[0]= "create_vlan: $ret[1]"; + $callback->($rsp); + } + } + } + + + return 1; +} + + +#------------------------------------------------------- +=head3 add_ports + + It adds the ports to the vlan. + Returns: 1 -- suggessful + 0 -- fail +=cut +#------------------------------------------------------- +sub add_ports { + my $vlan_id=shift; + my $swinfo=shift; + my $callback=shift; + my $portmode=shift; + my $ret=1; + foreach my $switch (keys %$swinfo) { + my $porthash=$swinfo->{$switch}; + my $swh; + if (exists($Switches{$switch})) { $swh=$Switches{$switch};} + else { + $swh=new xCAT::SwitchHandler->new($switch); + $Switches{$switch}=$swh; + } + + my @ret=$swh->add_ports_to_vlan($vlan_id, $portmode, keys(%$porthash)); + if ($ret[0] != 0) { + my $rsp={}; + $rsp->{error}->[0]= "add_ports_to_vlan: $ret[1]"; + $callback->($rsp); + } + } + + return 1; +} + +#------------------------------------------------------- +=head3 add_crossover_ports + + It enables the vlan on the cross-over links. + Returns: 1 -- suggessful + 0 -- fail +=cut +#------------------------------------------------------- +sub add_crossover_ports { + my $vlan_id=shift; + my $psws=shift; + my $callback=shift; + + #now make sure the links between the switches allows this vlan to go through + my @sws=@$psws; + print "sws=@sws\n"; + if (@sws > 1) { + foreach my $switch (@sws) { + my $swh; + if (exists($Switches{$switch})) { $swh=$Switches{$switch};} + else { + $swh=new xCAT::SwitchHandler->new($switch); + $Switches{$switch}=$swh; + } + + my @sws_b=grep(!/^$switch$/, @sws); + my @ret=$swh->add_crossover_ports_to_vlan($vlan_id, @sws_b); + if ($ret[0] != 0) { + my $rsp={}; + $rsp->{error}->[0]= "add_crossover_ports: $ret[1]"; + $callback->($rsp); + } else { + if ($ret[1]) { + my $rsp={}; + $rsp->{data}->[0]= "add_crossover_ports: $ret[1]"; + $callback->($rsp); + } + } + } + } + + return 1; +} + +#------------------------------------------------------- +=head3 remove_ports + + It goes to the switches and create a new vlan. + Returns: 1 -- suggessful + 0 -- fail +=cut +#------------------------------------------------------- +sub remove_ports { + my $vlan_id=shift; + my $swinfo=shift; + my $callback=shift; + my $novmhost=shift; + my $ret=1; + foreach my $switch (keys %$swinfo) { + my $porthash=$swinfo->{$switch}; + my $swh; + if (exists($Switches{$switch})) { $swh=$Switches{$switch};} + else { + $swh=new xCAT::SwitchHandler->new($switch); + $Switches{$switch}=$swh; + } + + my @ports=(); + if ($novmhost) { #skip the vm hosts for chvlan + foreach my $port (keys(%$porthash)) { + if (!exists($porthash->{$port}->{vmhost})) { + push(@ports, $port); + } + } + }else { + @ports=keys(%$porthash); + } + my @ret=$swh->remove_ports_from_vlan($vlan_id, @ports); + if ($ret[0] != 0) { + my $rsp={}; + $rsp->{error}->[0]= "remove_ports_from_vlan: $ret[1]"; + $callback->($rsp); + } + } + + return 1; +} + + +#------------------------------------------------------- +=head3 get_subnet + + It gets the subnet address and netmask for the given + vlan ID. The pattern is defined by "vlannets" and "vlanmask" + on the site table. The default is "10.<$vlanid>.0.0"/"255.255.0.0". +=cut +#------------------------------------------------------- +sub get_subnet { + my $vlan_id=shift; + my $callback = shift; + my $net; + my $mask; + + #get vlannets and vlanidmask from the site table + my $vlannets="|(\\d+)|10.(\$1+0).0.0|"; + my $vlanmask="255.255.0.0"; + my $sitetab = xCAT::Table->new('site'); + my $sent = $sitetab->getAttribs({key=>'vlannets'},'value'); + if ($sent and ($sent->{value})) { + $vlannets=$sent->{value}; + } + $sent = $sitetab->getAttribs({key=>'vlanmask'},'value'); + if ($sent and ($sent->{value})) { + $vlanmask=$sent->{value}; + } + $mask = $vlanmask; + + if ($vlannets =~ /^\/[^\/]*\/[^\/]*\/$/) + { + my $exp = substr($vlannets, 1); + chop $exp; + my @parts = split('/', $exp, 2); + $net=$vlan_id; + $net =~ s/$parts[0]/$parts[1]/; + } + elsif ($vlannets =~ /^\|.*\|.*\|$/) + { + + my $exp = substr($vlannets, 1); + chop $exp; + my @parts = split('\|', $exp, 2); + my $curr; + my $next; + my $prev; + my $retval = $parts[1]; + ($curr, $next, $prev) = + extract_bracketed($retval, '()', qr/[^()]*/); + + unless($curr) { #If there were no paramaters to save, treat this one like a plain regex + undef $@; #extract_bracketed would have set $@ if it didn't return, undef $@ + $retval = $vlan_id; + $retval =~ s/$parts[0]/$parts[1]/; + } + while ($curr) + { + my $value = $vlan_id; + $value =~ s/$parts[0]/$curr/; + $value = $evalcpt->reval('use integer;'.$value); + $retval = $prev . $value . $next; + ($curr, $next, $prev) = + extract_bracketed($retval, '()', qr/[^()]*/); + } + undef $@; + $net = $vlan_id; + $net =~ s/$parts[0]/$retval/; + } + return ($net, $mask); +} + + +# process_chvlanports only support physical nodes +# bond not supported and multi nics are not supported +sub process_chvlanports { + my $request = shift; + my $callback = shift; + my $sub_req = shift; + + my $vlan_id=0; + my $nic = ""; + my @nodes=(); + my $delete=0; + + # validate vlan id value. + $vlan_id=$request->{vlanid}->[0]; + #debug message. + xCAT::MsgUtils->message('S',"vlanid: $vlan_id"); + if ($vlan_id <= 0) { + my $rsp={}; + $rsp->{error}->[0]= "Invalid vlan id: $vlan_id"; + $callback->($rsp); + return; + } + #Check if the vlan is defined in networks table. + my $net=""; + my $netmask=""; + my $nwtab=xCAT::Table->new("networks", -create =>1); + if ($nwtab) { + my @nwentires=$nwtab->getAllAttribs(('vlanid', 'net', 'mask')); + foreach(@nwentires) { + if ($vlan_id eq $_->{vlanid}) { + $net=$_->{net}; + $netmask=$_->{mask}; + } + } + } + if ((!$net) || (!$netmask)) { + my $rsp = {}; + $rsp->{data}->[0] = "Can not find valid network/netmask definition from table networks for vlan $vlan_id."; + $callback->($rsp); + return 1; + } + + $nic=$request->{nic}->[0]; + #debug message. + xCAT::MsgUtils->message('S',"nic is: $nic"); + + @nodes=@{$request->{node}}; + #debug message. + my $nodesstr = Dumper(@nodes); + xCAT::MsgUtils->message('S',"nodes are: $nodesstr"); + + $delete=$request->{delete}->[0]; + #debug message. + xCAT::MsgUtils->message('S',"delete flag: $delete"); + + + ####now get the switch ports for the nodes + my %swinfo=(); # example: %swinfo=( switch1=>{ 23=>{ nodes: [node1]}, + # 24=>{ nodes: [node2]}, + # }, + # switch12=>{ 3=>{ nodes: [node4], + # } + # ) + my %nodeswinfo=(); # example: %nodeswinfo=( + # node1=>{eth0=>{switch => swith1, + # port => 1, + # vlanids => [2,3]}, + # eth1=>{switch => switch1, + # port => 2, + # vlanids => [5]}} + # ) + my %swsetup=(); + + #get the switch and port numbers for each node + my $swtab=xCAT::Table->new("switch", -create =>1); + my $swtmphash = $swtab->getNodesAttribs(\@nodes, ['switch', 'port', 'vlan', 'interface']); + foreach my $node (keys (%$swtmphash)) { + my $node_enties=$swtmphash->{$node}; + foreach my $ent (@$node_enties) { + if (ref($ent) and defined $ent->{switch} and defined $ent->{port}){ + my $switch = $ent->{switch}; + my $port = $ent->{port}; + my $interface = ""; + if( defined $ent->{interface} ){ $interface = $ent->{interface}}; + my $vlan = ""; + if( defined $ent->{vlan}) {$vlan = $ent->{vlan}}; + + if ($interface){ + $nodeswinfo{$node}->{$interface}->{switch}=$switch; + $nodeswinfo{$node}->{$interface}->{port}=$port; + $nodeswinfo{$node}->{$interface}->{vlan} = $vlan; + } + } + } + } + #debug message. + my $nodesinfostr = Dumper(%nodeswinfo); + xCAT::MsgUtils->message('S',"nodeswinfo: $nodesinfostr"); + + # Validate node's switch info %nodeswinfo and build up %swinfo, %swsetup. + my @missed_switch_nodes = (); + my @missed_vlanid_nodes = (); + foreach my $node (@nodes){ + # Check whether the switch, port and interface info defined for all nodes. + if(defined $nodeswinfo{$node} && defined $nodeswinfo{$node}->{$nic}){ + } else{ + push(@missed_switch_nodes, $node); + next; + } + + if ($delete){ + # For delete mode, must make sure all node's has such a vlan ID defined for the interface. + my @vlanids = split(",",$nodeswinfo{$node}->{$nic}->{vlan}); + if (@vlanids && (grep /^$vlan_id$/, @vlanids)){ + my $switch = $nodeswinfo{$node}->{$nic}->{switch}; + my $port = $nodeswinfo{$node}->{$nic}->{port}; + + #setup swinfo and swsetup . + $swinfo{$switch}->{$port}->{hosts} = [$node]; + $swsetup{$node}->{switch} = $switch; + $swsetup{$node}->{port} = $port; + $swsetup{$node}->{vlan} = $nodeswinfo{$node}->{$nic}->{vlan}; + }else{ + push(@missed_vlanid_nodes, $node); + next; + } + } else{ + # non-delete mode, just setup swinfo directly. + my $switch = $nodeswinfo{$node}->{$nic}->{switch}; + my $port = $nodeswinfo{$node}->{$nic}->{port}; + + #setup swinfo and swsetup for add_ports, add_crossover_ports and add_vlan_to_switch_table call later. + $swinfo{$switch}->{$port}->{hosts} = [$node]; + $swsetup{$node}->{switch} = $switch; + $swsetup{$node}->{port} = $port; + $swsetup{$node}->{vlan} = $nodeswinfo{$node}->{$nic}->{vlan}; + } + } + if (@missed_switch_nodes > 0) { + my $rsp={}; + $rsp->{error}->[0]= "Cannot proceed, please define switch, port and interface info on the switch table for the following nodes:\n @missed_switch_nodes\n"; + $callback->($rsp); + return 1; + } + if (@missed_vlanid_nodes > 0) { + my $rsp={}; + $rsp->{error}->[0]= "Cannot proceed, no such vlan ID $vlan_id defined for following nodes:\n @missed_vlanid_nodes\n"; + $callback->($rsp); + return 1; + } + #debug message. + my $swinfostr = Dumper(%swinfo); + xCAT::MsgUtils->message('S',"swinfo: $swinfostr"); + my $swsetupstr = Dumper(%swsetup); + xCAT::MsgUtils->message('S',"swsetup: $swsetupstr"); + + # Do actual configurations on switches + if (!$delete) { + ### add ports to the vlan + if (!add_ports($vlan_id, \%swinfo, $callback, 1)) { return 1;} + xCAT::MsgUtils->message('S',"Adding ports to switch success!"); + + ### add the cross-over ports to the vlan + my @sws=keys(%swinfo);; + #get all the switches that are in the vlan + my $swtab=xCAT::Table->new("switch", -create =>0); + if ($swtab) { + my @tmp1=$swtab->getAllAttribs('switch', 'vlan'); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach my $item (@tmp1) { + my $vlan=$item->{vlan}; + my $sw=$item->{switch}; + if ($vlan) { + my @a=split(",",$vlan); + if (grep(/^$vlan_id$/, @a)) { + if (!grep(/^$sw$/, @sws)) { + push(@sws, $sw); + } + } + } + } + } + } + if (!add_crossover_ports($vlan_id, \@sws, $callback)) { return 1;} + xCAT::MsgUtils->message('S',"Configuring cross over ports success!"); + + #add the vlanid for the standalone nodes on the switch table + #append the vlan id for the vmhosts on the switch table + add_vlan_to_switch_table(\%swsetup, $vlan_id); + xCAT::MsgUtils->message('S',"Adding vlan to switch table success!"); + + # done + my $rsp={}; + $rsp->{data}->[0]= "The interface $nic of following nodes are added to the vlan $vlan_id:\n@nodes"; + $callback->($rsp); + + } else{ + ### remove ports from the vlan + my $novmhost=1; + if (!remove_ports($vlan_id, \%swinfo, $callback, $novmhost)) { return 1;} + xCAT::MsgUtils->message('S',"Removing ports from vlan success!"); + + #remove the vlan id from the switch table. + remove_vlan_from_switch_table(\%swsetup,$vlan_id); + xCAT::MsgUtils->message('S',"Removing ports from switch table success!"); + + # done + my $rsp={}; + $rsp->{data}->[0]= "The interface $nic of following nodes are removed from the vlan $vlan_id:\n@nodes"; + $callback->($rsp); + } + return 0; +} + +sub process_chvlan { + my $request = shift; + my $callback = shift; + my $sub_req = shift; + + my $vlan_id=0; + if (exists($request->{vlanid})) { + $vlan_id=$request->{vlanid}->[0]; + } + if ($vlan_id == 0) { + my $rsp={}; + $rsp->{error}->[0]= "Invalid vlan id: $vlan_id"; + $callback->($rsp); + return; + } + + my $nic; + if (exists($request->{nic})) { + $nic=$request->{nic}->[0]; + } + + my @nodes=(); + if (exists($request->{node})) { + @nodes=@{$request->{node}}; + } + + my $delete=0; + if (exists($request->{delete})) { + $delete=$request->{delete}->[0]; + } + my $net; + my $netmask; + #check if the vlan is already defined on the table + my $found=0; + my $nwtab=xCAT::Table->new("networks", -create =>1); + if ($vlan_id > 0) { + if ($nwtab) { + my @tmp1=$nwtab->getAllAttribs(('vlanid', 'net', 'mask')); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach(@tmp1) { + if ($vlan_id eq $_->{vlanid}) { + $found=1; + $net=$_->{net}; + $netmask=$_->{mask}; + } + } + } + } + } + if (!$found) { + my $rsp = {}; + $rsp->{data}->[0] = "The vlan $vlan_id does not exist."; + $callback->($rsp); + return 1; + } + + if ((!$net) || (!$netmask)) { + my $rsp = {}; + $rsp->{data}->[0] = "Please make sure subnet and netmask are specified on the networks table for vlan $vlan_id."; + $callback->($rsp); + return 1; + } + + ####now get the switch ports for the nodes + my %swinfo=(); # example: %swinfo=( switch1=>{ 23=>{ nodes: [node1]}, + # 24=>{ nodes: [node2,node3], + # vmhost: kvm1 + # } + # }, + # switch12=>{ 3=>{ nodes: [node4]}, + # 7=>{ nodes: [node5]}, + # 9=>{ nodes: [node6]} + # } + # ) + + my %vminfo=(); # example: %vminfo=( kvm1=>{clients:[node1,node2...]}, + # kvm2=>{clients:[node2,node3...]}, + # ) + # + my @vmnodes=(); # vm clients + my @snodes=(); # stand alone nodes + + + #get the vm hosts + my $vmtab=xCAT::Table->new("vm", -create =>1); + my $vmtmphash = $vmtab->getNodesAttribs(\@nodes, ['host','nics']) ; + foreach(@nodes) { + my $node=$_; + my $host; + my $ent=$vmtmphash->{$node}->[0]; + if (ref($ent) and defined $ent->{host}) { + $host = $ent->{host}; + if (exists($vminfo{$host})) { + my $pa=$vminfo{$host}->{clients}; + push(@$pa, $node); + } else { + $vminfo{$host}->{clients}=[$node]; + } + push(@vmnodes, $node); + } + } + + if (@vmnodes > 0) { + foreach my $node (@nodes) { + if (! grep /^$node$/, @vmnodes) { + push(@snodes, $node); + } + } + } else { + @snodes=@nodes; + } + + #get the switch and port numbers for each node + my @vmhosts=keys(%vminfo); + my @anodes=(@snodes, @vmhosts); #nodes that connects to the switch + my %swsetup=(); + my $swtab=xCAT::Table->new("switch", -create =>1); + my $swtmphash = $swtab->getNodesAttribs(\@anodes, ['switch', 'port', 'vlan', 'interface']) ; + my @missed_nodes=(); + foreach my $node (@anodes) { + my $switch; + my $port; + my $node_enties=$swtmphash->{$node}; + if ($node_enties) { + my $i=-1; + my $use_this=0; + foreach my $ent (@$node_enties) { + $i++; + if (ref($ent) and defined $ent->{switch} and defined $ent->{port}) { + $switch = $ent->{switch}; + $port = $ent->{port}; + my $interface="primary"; + if (defined $ent->{interface}) { $interface=$ent->{interface};} + # for primary nic, the interface can be empty, "primary" or "primary:eth0" + if ($delete) { + if (defined($ent->{vlan})) { + my @a=split(',', $ent->{vlan}); + if (grep(/^$vlan_id$/, @a)) { $use_this=1; } + } + } else { + if ($nic) { + if ($interface =~ /primary/) { + $interface =~ s/primary(:)?//g; + } + if ($interface && ($interface eq $nic)) { $use_this=1; } + } else { + if ($interface =~ /primary/) { $use_this=1; } + } + } + + if (! $use_this) { + next; + } else { + $swsetup{$node}->{port}=$port; + $swsetup{$node}->{switch}=$switch; + if (defined $ent->{vlan}) { + $swsetup{$node}->{vlan}=$ent->{vlan}; + } else { + $swsetup{$node}->{vlan}=""; + } + } + + if ($interface) { + $swinfo{$switch}->{$port}->{interface}=$interface; + } + + if (exists($vminfo{$node})) { + $swinfo{$switch}->{$port}->{vmhost}=$node; + $swinfo{$switch}->{$port}->{nodes}=$vminfo{$node}->{clients}; + } else { + $swinfo{$switch}->{$port}->{nodes}=[$node]; + } + last; + } + } + if ( $use_this != 1 ) { + push (@missed_nodes, $node); + } + } + } + + if (@missed_nodes > 0) { + my $rsp={}; + $rsp->{error}->[0]= "Cannot proceed, please define switch and port info on the switch table for the following nodes:\n @missed_nodes\n"; + $callback->($rsp); + return 1; + } + + #print "vminfo=" . Dumper(%vminfo) . "\n"; + #print "swinfo=" . Dumper(%swinfo) . "\n"; + #print "anodes=" . Dumper(@anodes) . "\n"; + #print "vmnodes=" . Dumper(@vmnodes) . "\n"; + + if (!$delete) { + ### verify the ports are not used by other vlans + #if (!verify_switch_ports($vlan_id, \%swinfo, $callback)) { return 1;} + + ###create the vlan if it does not exist + if (!create_vlan($vlan_id, \%swinfo, $callback)) { return 1;} + + ### add ports to the vlan + if (!add_ports($vlan_id, \%swinfo, $callback)) { return 1;} + + ### add the cross-over ports to the vlan + my @sws=keys(%swinfo);; + #get all the switches that are in the vlan + my $swtab=xCAT::Table->new("switch", -create =>0); + if ($swtab) { + my @tmp1=$swtab->getAllAttribs('switch', 'vlan'); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach my $item (@tmp1) { + my $vlan=$item->{vlan}; + my $sw=$item->{switch}; + if ($vlan) { + my @a=split(",",$vlan); + if (grep(/^$vlan_id$/, @a)) { + if (!grep(/^$sw$/, @sws)) { + push(@sws, $sw); + } + } + } + } + } + } + if (!add_crossover_ports($vlan_id, \@sws, $callback)) { return 1;} + + #add the vlanid for the standalone nodes on the switch table + #append the vlan id for the vmhosts on the switch table + add_vlan_to_switch_table(\%swsetup, $vlan_id); + + #we'll derive the prefix and the node numbers from the existing + #nodes on the vlan + my ($prefix, $start_number)=get_prefix_and_nodenumber($vlan_id, $net, $netmask); + + ### get node ip and vlan hostname from the hosts table. + #If it is not defined, put the default into the host table + my @allnodes=(@anodes, @vmnodes); + if (!add_vlan_ip_host($net, $netmask, $prefix, $start_number+1, \@allnodes, $callback)) { return 1;} + + ### for vm nodes, add an additional nic on the vm.nics + if (@vmnodes > 0) { + my %setupnics=(); + my $new_nic="vl$vlan_id"; + foreach my $node (@vmnodes) { + my $ent=$vmtmphash->{$node}->[0]; + my $nics; + if (ref($ent) and defined $ent->{nics}) { + $nics=$ent->{nics}; + my @a=split(",", $nics); + if (!grep(/^$new_nic$/, @a)) { $nics="$nics,$new_nic"; } + } else { + $nics=$new_nic; + } + $setupnics{$node}={nics=>"$nics"}; + } + $vmtab->setNodesAttribs(\%setupnics); + } + + ### populate the /etc/hosts and make the DNS server on the mn aware this change + $::CALLBACK = $callback; + my $res = xCAT::Utils->runxcmd({ + command => ['makehosts'], + }, $sub_req, 0, 1); + my $rsp = {}; + $rsp->{data}->[0] = "Running makehosts..."; + $callback->($rsp); + if ($res && (@$res > 0)) { + $rsp = {}; + $rsp->{data} = $res; + $callback->($rsp); + } + $callback->($rsp); + + $::CALLBACK = $callback; + my $res = xCAT::Utils->runxcmd({ + command => ['makedns'], + }, $sub_req, 0, 1); + + my $rsp = {}; + $rsp->{data}->[0] = "Running makedns..."; + if ($res && (@$res > 0)) { + $callback->($rsp); + $rsp->{data} = $res; + $callback->($rsp); + } + + my $cmd = "service named restart"; + my $rc=system $cmd; + + ### now go to the nodes to configure the vlan interface + $::CALLBACK = $callback; + my $args = ["-P", "configvlan $vlan_id --keephostname"]; + my $res = xCAT::Utils->runxcmd( { + command => ['updatenode'], + node => \@snodes, + arg => $args + }, $sub_req, 0, 1); + my $rsp = {}; + $rsp->{data}->[0] = "Running updatenode..."; + if ($res && (@$res > 0)) { + $callback->($rsp); + $rsp->{data} = $res; + $callback->($rsp); + } + + ### add configvlan postscripts to the postscripts table for the node + my @pnodes=(@snodes, @vmnodes); + add_postscript($callback, \@pnodes); + + # done + my $rsp={}; + $rsp->{data}->[0]= "The following nodes are added to the vlan $vlan_id:\n@nodes"; + $callback->($rsp); + } else { + ### go to the nodes to de-configure the vlan interface + if (@snodes > 0) { + my $args = ["-P", "deconfigvlan $vlan_id"]; + my $res = xCAT::Utils->runxcmd( { + command => ['updatenode'], + node => \@snodes, + arg => $args + }, $sub_req, 0, 1); + my $rsp = {}; + $rsp->{data}->[0] = "Running updatenode..."; + if ($res && (@$res > 0)) { + $callback->($rsp); + $rsp->{data} = $res; + $callback->($rsp); + } + } + + ### remove ports from the vlan + my $novmhost=1; + if (!remove_ports($vlan_id, \%swinfo, $callback, $novmhost)) { return 1;} + + #remove the vlan id from the switch table for standalone nodes + #cannot call this function because %swsetup contains vmhosts + #remove_vlan_from_switch_table(\%swsetup,$vlan_id); + #print "swsetup=". Dumper(%swsetup); + my $swtab1 = xCAT::Table->new('switch', -create=>1, -autocommit=>0 ); + foreach my $node (@snodes) { + if (exists($swsetup{$node})) { + my %keyhash=(); + my %updates=(); + $keyhash{'node'} = $node; + $keyhash{'switch'}= $swsetup{$node}->{switch}; + $keyhash{'port'} = $swsetup{$node}->{port}; + $updates{'vlan'} = ""; + if($swsetup{$node}->{vlan}) { + my @a=split(',', $swsetup{$node}->{vlan}); + my @b=grep(!/^$vlan_id$/,@a); + if (@b>0) { + $updates{'vlan'}=join(',', @b); + } + } + $swtab1->setAttribs( \%keyhash,\%updates ); + } + } + $swtab1->commit; + + #remove the vlan from the vm.nic for vm clients + if (@vmnodes > 0) { + my %setupnics=(); + my $new_nic="vl$vlan_id"; + foreach my $node (@vmnodes) { + my $ent=$vmtmphash->{$node}->[0]; + my $nics=''; + if (ref($ent) and defined $ent->{nics}) { + $nics=$ent->{nics}; + my @a=split(",", $nics); + my @b=grep(!/^$new_nic$/, @a); + if (@b>0) { $nics=join(',', @b); } + } + $setupnics{$node}={nics=>"$nics"}; + } + $vmtab->setNodesAttribs(\%setupnics); + } + + #remove the node's vlan hostname and the ip from the host table and /etc/hosts + my @pnodes=(@snodes, @vmnodes); + if (!remove_vlan_ip_host($net, $netmask, \@pnodes, $callback)) { return 1;} + + #refresh the DNS server + my $res = xCAT::Utils->runxcmd( { + command => ['makedns'], + }, $sub_req, 0, 1); + my $rsp = {}; + $rsp->{data}->[0] = "Running makedns..."; + if ($res && (@$res > 0)) { + $callback->($rsp); + $rsp->{data} = $res; + $callback->($rsp); + } + my $cmd = "service named restart"; + my $rc=system $cmd; + + # remove configvlan postscripts from the postscripts table for the node + remove_postscript($callback, \@pnodes); + + # done + my $rsp={}; + $rsp->{data}->[0]= "The following nodes are removed from the vlan $vlan_id:\n@nodes"; + $callback->($rsp); + } + return 0; +} + + + +sub process_rmvlan { + my $request = shift; + my $callback = shift; + my $sub_req = shift; + + my $vlan_id=0; + if (exists($request->{vlanid})) { + $vlan_id=$request->{vlanid}->[0]; + } + + my @anodes=(); + my %swportinfo=(); + my %swsetup=(); + my $swtab=xCAT::Table->new("switch", -create =>0); + if ($swtab) { + my @tmp1=$swtab->getAllAttribs(('node', 'switch', 'port', 'vlan')); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach my $ent (@tmp1) { + my @nodes_tmp=noderange($ent->{node}); + foreach my $node (@nodes_tmp) { + my $switch=$ent->{switch}; + my $port=$ent->{port}; + if ($ent->{vlan}) { + my @a=split(",", $ent->{vlan}); + if (grep(/^$vlan_id$/,@a)) { + push(@anodes, $node); + if (exists($swportinfo{$switch})) { + my $pa=$swportinfo{$switch}; + push(@$pa, $port); + } else { + $swportinfo{$switch}=[$port]; + } + $swsetup{$node}->{port}=$port; + $swsetup{$node}->{switch}=$switch; + $swsetup{$node}->{vlan}=$ent->{vlan}; + } + } + } + } + } + } + + my $switchestab=xCAT::Table->new('switches',-create=>0); + if ($switchestab) { + my @tmp1=$switchestab->getAllAttribs(('switch')); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach(@tmp1) { + my @switches_tmp=noderange($_->{switch}); + if (@switches_tmp==0) { push @switches_tmp, $_->{switch}; } #sometimes the switch name is not on the node list table. + foreach my $switch (@switches_tmp) { + my $ports=[]; + if (exists($swportinfo{$switch})) { + $ports = $swportinfo{$switch}; + } + + my $swh; + if (exists($Switches{$switch})) { $swh=$Switches{$switch};} + else { + $swh=new xCAT::SwitchHandler->new($switch); + $Switches{$switch}=$swh; + } + + print "switch=$switch, ports=@$ports\n"; + if (@$ports > 0) { + my @ret=$swh->remove_ports_from_vlan($vlan_id, @$ports); + if ($ret[0] != 0) { + my $rsp={}; + $rsp->{error}->[0]= "remove_ports_from_vlan: $ret[1]"; + $callback->($rsp); + } + + my @ret=$swh->remove_vlan($vlan_id); + if ($ret[0] != 0) { + my $rsp={}; + $rsp->{error}->[0]= "remove_vlan: $ret[1]"; + $callback->($rsp); + } + } else { + #check if the vlan exists on the switch + my @ids=$swh->get_vlan_ids(); + foreach my $id (@ids) { + if ($id == $vlan_id) { + #remove it if exists + my @ret=$swh->remove_vlan($vlan_id); + if ($ret[0] != 0) { + my $rsp={}; + $rsp->{error}->[0]= "remove_vlan: $ret[1]"; + $callback->($rsp); + } + last; + } + } + } + } + } + } + } + + ### now go to the nodes to de-configure the vlan interface + my $args = ["-P", "deconfigvlan $vlan_id"]; + my $res = xCAT::Utils->runxcmd( { + command => ['updatenode'], + node => \@anodes, + arg => $args + }, $sub_req, 0, 1); + my $rsp = {}; + $rsp->{data}->[0] = "Running updatenode..."; + if ($res && (@$res > 0)) { + $callback->($rsp); + $rsp->{data} = $res; + $callback->($rsp); + } + + #remove the vlan from the networks table + my $nwtab=xCAT::Table->new("networks", -create =>1); + my $sent = $nwtab->getAttribs({vlanid=>"$vlan_id"},'net','mask'); + my $net; + my $netmask; + if ($sent and ($sent->{net})) { + $net=$sent->{net}; + $netmask=$sent->{mask}; + } + + my %key_col = (vlanid=>$vlan_id); + $nwtab->delEntries(\%key_col); + + #remove the vlan from the switch table for standalone nodes and vm hosts + remove_vlan_from_switch_table(\%swsetup,$vlan_id); + + #remove the vlan nic from vm.nics for the vm clients + my @vmnodes=(); + my %vmsetup=(); + my $vmtab=xCAT::Table->new("vm", -create =>0); + if ($vmtab) { + my @tmp1=$vmtab->getAllAttribs(('node','host', 'nics')); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach(@tmp1) { + my @nodes_tmp=noderange($_->{node}); + my $nics=$_->{nics}; + my $new_nic="vl$vlan_id"; + if ($nics) { + foreach my $node (@nodes_tmp) { + my @a=split(",", $nics); + if (grep(/^$new_nic$/,@a)) { + push(@vmnodes, $node); + my @b=grep(!/^$new_nic$/,@a); + if (@b>0) { + $vmsetup{$node}={nics=>join(',', @b)}; + } else { + $vmsetup{$node}={nics=>''}; + } + } + } + } + } + } + if (keys(%vmsetup) > 0) { + $vmtab->setNodesAttribs(\%vmsetup); + } + } + + + + #remove the node's vlan hostname and the ip from the host table and /etc/hosts + my @allnodes=(@anodes, @vmnodes); + if (!remove_vlan_ip_host($net, $netmask, \@allnodes, $callback)) { return 1;} + + #refresh the DNS server + my $res = xCAT::Utils->runxcmd( { + command => ['makedns'], + }, $sub_req, 0, 1); + my $rsp = {}; + $rsp->{data}->[0] = "Running makedns..."; + if ($res && (@$res > 0)) { + $callback->($rsp); + $rsp->{data} = $res; + $callback->($rsp); + } + my $cmd = "service named restart"; + my $rc=system $cmd; + + ### remove configvlan postscripts from the postscripts table for the node + # note: if configvlan is in xcatdefaults, it will not get removed becase + # it may affect other vlans + #remove_postscript($callback, \@allnodes); --- will not remove for multi-vlan support +} + +#------------------------------------------------------- +=head3 remove_postscript + + It removes configvlan postscripts from the postscripts table for the node + Note: if configvlan is in xcatdefaults, it will not get removed becase + it may affect other vlans + +=cut +#------------------------------------------------------- +sub remove_postscript { + my $callback=shift; + my $anodes=shift; + my $posttab=xCAT::Table->new("postscripts", -create =>0); + if ($posttab) { + my %setup_hash; + my $postcache = $posttab->getNodesAttribs($anodes,[qw(postscripts postbootscripts)]); + foreach my $node (@$anodes) { + my $ref = $postcache->{$node}->[0]; + if ($ref) { + if (exists($ref->{postbootscripts})) { + my $post=$ref->{postbootscripts}; + my @old_a=split(',', $post); + my @new_a = grep (!/^configvlan$/, @old_a); + if (scalar(@new_a) != scalar(@old_a)) { + #print "newa =@new_a\n"; + $setup_hash{$node}={postbootscripts=>join(',', @new_a)}; + } + } + if (exists($ref->{postscripts})) { + my $post=$ref->{postscripts}; + my @old_a=split(',', $post); + my @new_a = grep (!/^configvlan$/, @old_a); + if (scalar(@new_a) != scalar(@old_a)) { + $setup_hash{$node}={postscripts=>join(',', @new_a)}; + } + } + } + } + if (keys(%setup_hash) > 0) { + $posttab->setNodesAttribs(\%setup_hash); + } + } +} + +sub process_lsvlan { + my $request = shift; + my $callback = shift; + my $sub_req = shift; + + my $vlan_id=0; + if (exists($request->{vlanid})) { + $vlan_id=$request->{vlanid}->[0]; + } + + my %vlans=(); + #get all the vm clients if the node is a vm host + my $nwtab=xCAT::Table->new("networks", -create =>0); + if ($nwtab) { + my @tmp1=$nwtab->getAllAttribs('net', 'mask', 'vlanid'); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach(@tmp1) { + if (exists($_->{vlanid})) { + $vlans{$_->{vlanid}}->{net}=$_->{net}; + $vlans{$_->{vlanid}}->{mask}=$_->{mask}; + } + } + } + } + + if($vlan_id !=0 && !exists($vlans{$vlan_id})) { + my $rsp={}; + $rsp->{data}->[0] = "the vlan $vlan_id is not defined for the cluster nodes."; + $rsp->{errorcode} = -1; + $callback->($rsp); + return; + } + + if ($vlan_id == 0) { #just show the existing vlan ids + my $rsp={}; + my $index=0; + if (keys(%vlans) > 0) { + foreach my $id (sort keys(%vlans)) { + $rsp->{data}->[$index] = "vlan $id:\n subnet " . $vlans{$id}->{net}. "\n netmask " . $vlans{$id}->{mask} . "\n"; + $index++; + } + } else { + $rsp->{data}->[0] = "No vlans defined for the cluster nodes." + } + $callback->($rsp); + + } else { #shows the details + #get all the nodes that are in the vlan + my $swtab=xCAT::Table->new("switch", -create =>0); + my @nodes=(); + if ($swtab) { + my @tmp1=$swtab->getAllAttribs('node', 'vlan', 'interface'); + if (defined(@tmp1) && (@tmp1 > 0)) { + foreach my $grp (@tmp1) { + my $vlan=$grp->{vlan}; + my $nic="primary"; + if ($grp->{interface}) { $nic=$grp->{interface};} + + my @nodes_tmp=noderange($grp->{node}); + if ($vlan) { + my @a=split(",",$vlan); + if (grep(/^$vlan_id$/, @a)) { + foreach my $node (@nodes_tmp) { + push(@nodes, $node); + $vlans{$vlan_id}->{node}->{$node}->{name} = $node; + $vlans{$vlan_id}->{node}->{$node}->{interface} = $nic; + } + } + } + } + } + } + + + #get all the vm clients if the node is a vm host + my $vmtab=xCAT::Table->new("vm", -create =>0); + my @vmnodes=(); + if ($vmtab) { + my @tmp1=$vmtab->getAllAttribs('node', 'host', 'nics'); + if (defined(@tmp1) && (@tmp1 > 0)) { + my $new_nic="vl$vlan_id"; + foreach(@tmp1) { + my $host = $_->{host}; + my $nics = $_->{nics}; + if ($nics) { + my @a=split(",", $nics); + if (grep(/^$new_nic$/, @a)) { + my @nodes_tmp=noderange($_->{node}); + foreach my $node (@nodes_tmp) { + push(@vmnodes, $node); + $vlans{$vlan_id}->{node}->{$node}->{name} = $node; + $vlans{$vlan_id}->{node}->{$node}->{vmhost} = $host; + } + } + } + } + } + } + + @nodes=(@nodes, @vmnodes); + + #now go to hosts table to get the host name and ip on the vlan + my $hoststab = xCAT::Table->new('hosts'); + my $hostscache = $hoststab->getNodesAttribs(\@nodes,[qw(node otherinterfaces)]); + my $max=0; + my $prefix; + foreach my $node (@nodes) { + my $ref = $hostscache->{$node}->[0]; + my $otherinterfaces; + if ($ref && exists($ref->{otherinterfaces})){ + $otherinterfaces = $ref->{otherinterfaces}; + my @itf_pairs=split(/,/, $otherinterfaces); + my @itf_pairs2=(); + foreach (@itf_pairs) { + my ($name,$ip)=split(/:/, $_); + if(xCAT::NetworkUtils->ishostinsubnet($ip, $vlans{$vlan_id}->{mask}, $vlans{$vlan_id}->{net})) { + $vlans{$vlan_id}->{node}->{$node}->{ip}=$ip; + $vlans{$vlan_id}->{node}->{$node}->{vname}=$name; + } + } + } + } #foreach node + + my $rsp={}; + $rsp->{data}->[0]="vlan $vlan_id"; + $rsp->{data}->[1]=" subnet " . $vlans{$vlan_id}->{net}; + $rsp->{data}->[2]=" netmask " . $vlans{$vlan_id}->{mask} . "\n"; + my $node_hash=$vlans{$vlan_id}->{node}; + #print Dumper($node_hash); + if ($node_hash && keys(%$node_hash) > 0) { + $rsp->{data}->[3]=" hostname\tip address\tnode \tvm host \tinterface"; + my $index=4; + foreach (sort keys(%$node_hash)) { + my $vname=$node_hash->{$_}->{vname}; + if (!$vname) { $vname=" ";} + my $ip=$node_hash->{$_}->{ip}; + if (!$ip) { $ip=" "; } + my $name=$node_hash->{$_}->{name}; + if (!$name) { $name=" ";} + my $host=$node_hash->{$_}->{vmhost}; + if (!$host) { $host=" ";} + my $nic=$node_hash->{$_}->{interface}; + $rsp->{data}->[$index] = " $vname\t$ip\t$name\t$host\t$nic"; + $index++; + } + } + $callback->($rsp); + } +} + +sub mkvlan_usage { + my $cb=shift; + my $rsp={}; + + $rsp->{data}->[0]= "Usage: mkvlan -h"; + $rsp->{data}->[1]= " mkvlan -v"; + $rsp->{data}->[2]= " mkvlan [vlanid] -n noderange [-t net -m mask] [-p node_prefix] [-i nic]"; + + $cb->($rsp); +} + +sub rmvlan_usage { + my $cb=shift; + my $rsp={}; + + $rsp->{data}->[0]= "Usage: rmvlan -h"; + $rsp->{data}->[1]= " rmvlan -v"; + $rsp->{data}->[2]= " rmvlan vlanid"; + + $cb->($rsp); +} + +sub chvlanports_usage{ + my $cb=shift; + my $rsp={}; + + $rsp->{data}->[0]= "Usage: chvlanports -h"; + $rsp->{data}->[1]= " chvlanports -v"; + $rsp->{data}->[2]= " chvlanports vlanid -n noderange -i nic"; + $rsp->{data}->[3]= " chvlanports vlanid -n noderange -i nic -d"; + $cb->($rsp); +} +sub chvlan_usage { + my $cb=shift; + my $rsp={}; + + $rsp->{data}->[0]= "Usage: chvlan -h"; + $rsp->{data}->[1]= " chvlan -v"; + $rsp->{data}->[2]= " chvlan vlanid -n noderange [-i nic]"; + $rsp->{data}->[3]= " chvlan vlanid -n noderange -d"; + $cb->($rsp); +} + +sub lsvlan_usage { + my $cb=shift; + my $rsp={}; + + $rsp->{data}->[0]= "Usage: lsvlan -h"; + $rsp->{data}->[1]= " lsvlan -v"; + $rsp->{data}->[2]= " lsvlan"; + $rsp->{data}->[3]= " lsvlan vlanid"; + + $cb->($rsp); +} + +#------------------------------------------------------- +=head3 getNodeVlanConfData + This function is called by Postage.pm to collect all the + environmental variables for setting up a vlan for a given + node. +=cut +#------------------------------------------------------- +sub getNodeVlanConfData { + my $node=shift; + if ($node =~ /xCAT_plugin::vlan/) { + $node=shift; + } + + my @scriptd=(); + my $swtab = xCAT::Table->new("switch", -create => 0); + if ($swtab) { + my $tmp_switch = $swtab->getNodesAttribs([$node], ['vlan','interface'],prefetchcache=>1); + #print Dumper($tmp_switch); + if (defined($tmp_switch) && (exists($tmp_switch->{$node})) && (defined($tmp_switch->{$node}->[0]))) { + my $tmp_node_array=$tmp_switch->{$node}; + my $index=0; + foreach my $tmp (@$tmp_node_array) { + if (exists($tmp->{vlan})) { + my $nic="primary"; + if (exists($tmp->{interface})) { $nic=$tmp->{interface};} + my @vlanid_array = split(',', $tmp->{vlan}); + foreach my $vlan (@vlanid_array) { + $index++; + push @scriptd, "VLANID_$index='" . $vlan . "'\n"; + push @scriptd, "export VLANID_$index\n"; + push @scriptd, "VLANNIC_$index='" . $nic . "'\n"; + push @scriptd, "export VLANNIC_$index\n"; + my @temp_data=getNodeVlanOtherConfData($node, $vlan, $index); + @scriptd = (@scriptd,@temp_data); + } + } + } + if ($index > 0) { + push @scriptd, "VLANMAXINDEX='" . $index . "'\n"; + push @scriptd, "export VLANMAXINDEX\n"; + } + } else { + my $vmtab = xCAT::Table->new("vm", -create => 0); + if ($vmtab) { + my $tmp1 = $vmtab->getNodeAttribs($node, ['nics','host'],prefetchcache=>1); + + my $vlan; + my $index=0; + if (defined($tmp1) && ($tmp1) && $tmp1->{nics}) + { + push @scriptd, "VMNODE='YES'\n"; + push @scriptd, "export VMNODE\n"; + push @scriptd, "VMNICS='" . $tmp1->{nics} . "'\n"; + push @scriptd, "export VMNICS\n"; + + my @nics=split(',', $tmp1->{nics}); + + #get the vlan id and interface from the host + my $host=$tmp1->{host}; + #my $host_vlan_info=get_vm_host_vlan_info($host); + my $nic_position=0; + foreach my $nic (@nics) { + $nic_position++; + if ($nic =~ /^vl([\d]+)$/) { + $vlan = $1; + $index++; + push @scriptd, "VLANID_$index='" . $vlan . "'\n"; + push @scriptd, "export VLANID_$index\n"; + push @scriptd, "VLAN_VMNICPOS_$index='" . $nic_position . "'\n"; + push @scriptd, "export VLAN_VMNICPOS_$index\n"; + #if ($host_vlan_info && (exists($host_vlan_info->{$vlan}))) { + # push @scriptd, "HOST_VLANNIC_$index='" . $host_vlan_info->{$vlan} . "'\n"; + # push @scriptd, "export HOST_VLANNIC_$index\n"; + #} + my @temp_data=getNodeVlanOtherConfData($node, $vlan, $index); + @scriptd = (@scriptd,@temp_data); + } + } #end foreach + } + if ($index > 0) { + push @scriptd, "VLANMAXINDEX='" . $index . "'\n"; + push @scriptd, "export VLANMAXINDEX\n"; + } + } + } + } + + return @scriptd; +} + +sub getNodeVlanOtherConfData { + my $node=shift; + if ($node =~ /xCAT_plugin::vlan/) { + $node=shift; + } + my $vlan=shift; + my $index=shift; + + my @scriptd=(); + my $nwtab=xCAT::Table->new("networks", -create =>0); + if ($nwtab) { + my $sent = $nwtab->getAttribs({vlanid=>"$vlan"},'net','mask'); + my $subnet; + my $netmask; + if ($sent and ($sent->{net})) { + $subnet=$sent->{net}; + $netmask=$sent->{mask}; + } + if (($subnet) && ($netmask)) { + my $hoststab = xCAT::Table->new("hosts", -create => 0); + if ($hoststab) { + my $tmp = $hoststab->getNodeAttribs($node, ['otherinterfaces'],prefetchcache=>1); + if (defined($tmp) && ($tmp) && $tmp->{otherinterfaces}) + { + my $otherinterfaces = $tmp->{otherinterfaces}; + my @itf_pairs=split(/,/, $otherinterfaces); + foreach (@itf_pairs) { + my ($name,$ip)=split(/:/, $_); + if(xCAT::NetworkUtils->ishostinsubnet($ip, $netmask, $subnet)) { + if ($name =~ /^-/ ) { + $name = $node.$name; + } + push @scriptd, "VLANHOSTNAME_$index='" . $name . "'\n"; + push @scriptd, "export VLANHOSTNAME_$index\n"; + push @scriptd, "VLANIP_$index='" . $ip . "'\n"; + push @scriptd, "export VLANIP_$index\n"; + push @scriptd, "VLANSUBNET_$index='" . $subnet . "'\n"; + push @scriptd, "export VLANSUBNET_$index\n"; + push @scriptd, "VLANNETMASK_$index='" . $netmask . "'\n"; + push @scriptd, "export VLANNETMASK_$index\n"; + last; + } + } + } + } + } + } + + return @scriptd; +} + +#------------------------------------------------------- +=head3 get_vm_host_vlan_info + This function returns a hash pointer that has the vlan id + and the interface info for the given KVM host. A host can + support more than one vlans for a interface. For example: + {1=>"eth0", 2=>"eth0", 3=>"eth1" ...} +=cut +#------------------------------------------------------- +sub get_vm_host_vlan_info { + my $host=shift; + my $host_vlan_info={}; + my $swtab = xCAT::Table->new("switch", -create => 0); + if ($swtab) { + my $tmp_switch = $swtab->getNodesAttribs([$host], ['vlan','interface'],prefetchcache=>1); + if (defined($tmp_switch) && (exists($tmp_switch->{$host}))) { + my $tmp_node_array=$tmp_switch->{$host}; + foreach my $tmp (@$tmp_node_array) { + if (exists($tmp->{vlan})) { + my $vlans = $tmp->{vlan}; + my $nic="primary"; + if (exists($tmp->{interface})) { + $nic=$tmp->{interface}; + } + foreach my $vlan (split(',',$vlans)) { + $host_vlan_info->{$vlan}=$nic; + } + } + } + } + return $host_vlan_info; + } +} diff --git a/xCAT-vlan/xCAT_plugin/vlan/BNTSwitch.pm b/xCAT-vlan/xCAT_plugin/vlan/BNTSwitch.pm new file mode 100755 index 000000000..ea373be60 --- /dev/null +++ b/xCAT-vlan/xCAT_plugin/vlan/BNTSwitch.pm @@ -0,0 +1,620 @@ +#!/usr/bin/perl +package xCAT_plugin::vlan::BNTSwitch; +BEGIN +{ + $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; +} +use lib "$::XCATROOT/lib/perl"; +use strict; +use xCAT::MacMap; +use xCAT::Table; +use Data::Dumper; +use xCAT::SwitchHandler; +use SNMP; + +my $sysDescr='.1.3.6.1.2.1.1.1'; + +#BNT uses different OIDs for different switch module and versions. The following +#assumes that the FW versions are v6.x. +#G8100 is not supported yet because it uses different method. No hw to test. +my $BNTOID={ + 'EN4093'=>{ + 'vlanInfoId'=>'.1.3.6.1.4.1.20301.2.5.2.3.5.1.1.1', + 'vlanInfoPorts'=>'.1.3.6.1.4.1.20301.2.5.2.3.5.1.1.7', + 'portInfoPhyIfDescr'=>'.1.3.6.1.4.1.20301.2.5.1.3.2.1.1.6', + 'vlanNewCfgVlanName'=>'.1.3.6.1.4.1.20301.2.5.2.1.1.3.1.2', + 'vlanNewCfgState'=>'.1.3.6.1.4.1.20301.2.5.2.1.1.3.1.4', #2:enable 3:disable + 'vlanNewCfgAddPort'=>'.1.3.6.1.4.1.20301.2.5.2.1.1.3.1.5', + 'vlanNewCfgRemovePort'=>'.1.3.6.1.4.1.20301.2.5.2.1.1.3.1.6', + 'vlanNewCfgDelete'=>'.1.3.6.1.4.1.20301.2.5.2.1.1.3.1.7', #1:other 2:delete + 'agPortNewCfgVlanTag'=>'.1.3.6.1.4.1.20301.2.5.1.1.2.3.1.3', #2:tagged 3:untagged + 'agPortNewCfgBpduGuard'=>'.1.3.6.1.4.1.20301.2.5.1.1.2.3.1.41', #1:enable 2:disable + 'agPortNewCfgStpExtGuard'=>'.1.3.6.1.4.1.20301.2.5.1.1.2.3.1.52',#1:loop, 2:root, 3:none, 0:default + 'agApplyConfig'=>'.1.3.6.1.4.1.20301.2.5.1.1.8.2', + 'agSaveConfiguration'=> '.1.3.6.1.4.1.20301.2.5.1.1.1.4', #saveActive(2), notSaveActive(3) + 'arpInfoDestIp'=>'.1.3.6.1.4.1.20301.2.5.3.3.2.1.1.1', + 'arpInfoSrcPort'=>'.1.3.6.1.4.1.20301.2.5.3.3.2.1.1.4', + }, + + 'G8000'=>{ + 'vlanInfoId'=>'.1.3.6.1.4.1.26543.2.7.1.2.3.5.1.1.1', + 'vlanInfoPorts'=>'.1.3.6.1.4.1.26543.2.7.1.2.3.5.1.1.7', + 'portInfoPhyIfDescr'=>'.1.3.6.1.4.1.26543.2.7.1.1.3.2.1.1.6', + 'vlanNewCfgVlanName'=>'.1.3.6.1.4.1.26543.2.7.1.2.1.1.3.1.2', + 'vlanNewCfgState'=>'.1.3.6.1.4.1.26543.2.7.1.2.1.1.3.1.4', #2:enable 3:disable + 'vlanNewCfgAddPort'=>'.1.3.6.1.4.1.26543.2.7.1.2.1.1.3.1.5', + 'vlanNewCfgRemovePort'=>'.1.3.6.1.4.1.26543.2.7.1.2.1.1.3.1.6', + 'vlanNewCfgDelete'=>'.1.3.6.1.4.1.26543.2.7.1.2.1.1.3.1.7', #1:other 2:delete + 'agPortNewCfgVlanTag'=>'.1.3.6.1.4.1.26543.2.7.1.1.1.2.3.1.3', #2:tagged 3:untagged + 'agPortNewCfgBpduGuard'=>'.1.3.6.1.4.1.26543.2.7.1.1.1.2.3.1.41', #1:enable 2:disable + 'agPortNewCfgStpExtGuard'=>'.1.3.6.1.4.1.26543.2.7.1.1.1.2.3.1.52',#1:loop, 2:root, 3:none, 0:default + 'agApplyConfig'=>'.1.3.6.1.4.1.26543.2.7.1.1.1.8.2', + 'agSaveConfiguration'=> '.1.3.6.1.4.1.26543.2.7.1.1.1.1.4', #saveActive(2), notSaveActive(3) + 'arpInfoDestIp'=>'.1.3.6.1.4.1.26543.2.7.1.3.3.2.1.1.1', + 'arpInfoSrcPort'=>'.1.3.6.1.4.1.26543.2.7.1.3.3.2.1.1.4', + }, + + 'G8052'=>{ + 'vlanInfoId'=>'.1.3.6.1.4.1.26543.2.7.7.2.3.5.1.1.1', + 'vlanInfoPorts'=>'.1.3.6.1.4.1.26543.2.7.7.2.3.5.1.1.7', + 'portInfoPhyIfDescr'=>'.1.3.6.1.4.1.26543.2.7.7.1.3.2.1.1.6', + 'vlanNewCfgVlanName'=>'.1.3.6.1.4.1.26543.2.7.7.2.1.1.3.1.2', + 'vlanNewCfgState'=>'.1.3.6.1.4.1.26543.2.7.7.2.1.1.3.1.4', + 'vlanNewCfgAddPort'=>'.1.3.6.1.4.1.26543.2.7.7.2.1.1.3.1.5', + 'vlanNewCfgRemovePort'=>'.1.3.6.1.4.1.26543.2.7.7.2.1.1.3.1.6', + 'vlanNewCfgDelete'=>'.1.3.6.1.4.1.26543.2.7.7.2.1.1.3.1.7', + 'agPortNewCfgVlanTag'=>'.1.3.6.1.4.1.26543.2.7.7.1.1.2.3.1.3', + 'agPortNewCfgBpduGuard'=>'.1.3.6.1.4.1.26543.2.7.7.1.1.2.3.1.41', + 'agPortNewCfgStpExtGuard'=>'.1.3.6.1.4.1.26543.2.7.7.1.1.2.3.1.52', + 'agApplyConfig'=>'.1.3.6.1.4.1.26543.2.7.7.1.1.8.2', + 'agSaveConfiguration'=> '.1.3.6.1.4.1.26543.2.7.7.1.1.1.4', #saveActive(2), notSaveActive(3) + 'arpInfoDestIp'=>'.1.3.6.1.4.1.26543.2.7.7.3.3.2.1.1.1', + 'arpInfoSrcPort'=>'.1.3.6.1.4.1.26543.2.7.7.3.3.2.1.1.4', + }, + + 'G8124'=>{ + 'vlanInfoId'=>'.1.3.6.1.4.1.26543.2.7.4.2.3.5.1.1.1', + 'vlanInfoPorts'=>'.1.3.6.1.4.1.26543.2.7.4.2.3.5.1.1.7', + 'portInfoPhyIfDescr'=>'.1.3.6.1.4.1.26543.2.7.4.1.3.2.1.1.6', + 'vlanNewCfgVlanName'=>'.1.3.6.1.4.1.26543.2.7.4.2.1.1.3.1.2', + 'vlanNewCfgState'=>'.1.3.6.1.4.1.26543.2.7.4.2.1.1.3.1.4', + 'vlanNewCfgAddPort'=>'.1.3.6.1.4.1.26543.2.7.4.2.1.1.3.1.5', + 'vlanNewCfgRemovePort'=>'.1.3.6.1.4.1.26543.2.7.4.2.1.1.3.1.6', + 'vlanNewCfgDelete'=>'.1.3.6.1.4.1.26543.2.7.4.2.1.1.3.1.7', + 'agPortNewCfgVlanTag'=>'.1.3.6.1.4.1.26543.2.7.4.1.1.2.3.1.3', + 'agPortNewCfgBpduGuard'=>'.1.3.6.1.4.1.26543.2.7.4.1.1.2.3.1.41', + 'agPortNewCfgStpExtGuard'=>'.1.3.6.1.4.1.26543.2.7.4.1.1.2.3.1.52', + 'agApplyConfig'=>'.1.3.6.1.4.1.26543.2.7.4.1.1.8.2', + 'agSaveConfiguration'=> '.1.3.6.1.4.1.26543.2.7.4.1.1.1.4', #saveActive(2), notSaveActive(3) + 'arpInfoDestIp'=>'.1.3.6.1.4.1.26543.2.7.4.3.3.2.1.1.1', + 'arpInfoSrcPort'=>'.1.3.6.1.4.1.26543.2.7.4.3.3.2.1.1.4', + }, + + #this is for G8264 and 8264E + 'G8264'=>{ + 'vlanInfoId'=>'.1.3.6.1.4.1.26543.2.7.6.2.3.5.1.1.1', + 'vlanInfoPorts'=>'.1.3.6.1.4.1.26543.2.7.6.2.3.5.1.1.7', + 'portInfoPhyIfDescr'=>'.1.3.6.1.4.1.26543.2.7.6.1.3.2.1.1.6', + 'vlanNewCfgVlanName'=>'.1.3.6.1.4.1.26543.2.7.6.2.1.1.3.1.2', + 'vlanNewCfgState'=>'.1.3.6.1.4.1.26543.2.7.6.2.1.1.3.1.4', + 'vlanNewCfgAddPort'=>'.1.3.6.1.4.1.26543.2.7.6.2.1.1.3.1.5', + 'vlanNewCfgRemovePort'=>'.1.3.6.1.4.1.26543.2.7.6.2.1.1.3.1.6', + 'vlanNewCfgDelete'=>'.1.3.6.1.4.1.26543.2.7.6.2.1.1.3.1.7', + 'agPortNewCfgVlanTag'=>'.1.3.6.1.4.1.26543.2.7.6.1.1.2.3.1.3', + 'agPortNewCfgBpduGuard'=>'.1.3.6.1.4.1.26543.2.7.6.1.1.2.3.1.41', + 'agPortNewCfgStpExtGuard'=>'.1.3.6.1.4.1.26543.2.7.6.1.1.2.3.1.52', + 'agApplyConfig'=>'.1.3.6.1.4.1.26543.2.7.6.1.1.8.2', + 'agSaveConfiguration'=> '.1.3.6.1.4.1.26543.2.7.6.1.1.1.4', #saveActive(2), notSaveActive(3) + 'arpInfoDestIp'=>'.1.3.6.1.4.1.26543.2.7.6.3.3.2.1.1.1', + 'arpInfoSrcPort'=>'.1.3.6.1.4.1.26543.2.7.6.3.3.2.1.1.4', + }, +}; + + +my %HexConv=( 0=>0x80, 1=>0x40, 2=>0x20, 3=>0x10, + 4=>0x08, 5=>0x04, 6=>0x02, 7=>0x01); + + +#-------------------------------------------------------------- +=head3 filter_string + + Every switch plugin must implement this subroutine. + + The return value will be used comare against the string + from sysDescr value from snmpget. If the latter contains + this string, then this mudule will be used to handle the + requests for vlan configuration. + +=cut +#------------------------------------------------------------- +sub filter_string { + return "BNT |Blade Network Technologies|IBM Networking Operating System|EN4093"; +} + + +#-------------------------------------------------------------- +=head3 get_vlan_ids + + Every switch plugin must implement this subroutine. + + It gets the existing vlan IDs for the switch. + Returns: an array containing all the vlan ids for the switch +=cut +#------------------------------------------------------------- +sub get_vlan_ids { + my $session=shift; + + my $swmod=getSwitchModule($session); + if (!$swmod) { return ();} + + my $vlanmap = xCAT::MacMap::walkoid($session, $BNTOID->{$swmod}->{vlanInfoId}, silentfail=>1); + + my %vlanids=(); + foreach(keys(%$vlanmap)) { + $vlanids{$_}=1; + } + + my @ret=(sort keys(%vlanids)); + print "ids=@ret\n"; + return @ret; +} + +#-------------------------------------------------------------- +=head3 get_vlanids_for_ports + + Every switch plugin must implement this subroutine. + + It returns a hash pointer that contains the vlan id for each given port. + The kay is the port, the vlaue is a pointer to an array. +=cut +#------------------------------------------------------------- +sub get_vlanids_for_ports { + my $session=shift; + my @ports=@_; + + my $swmod=getSwitchModule($session); + if (!$swmod) { return;} + + + my $namemap = xCAT::MacMap::walkoid($session, $BNTOID->{$swmod}->{portInfoPhyIfDescr}); + if ($namemap) { + my $ifnamesupport=0; + foreach (keys %{$namemap}) { + if ($namemap->{$_}) { + $ifnamesupport=1; + last; + } + } + unless ($ifnamesupport) { + $namemap=0; + } + } + unless ($namemap) { + return; + } + #print "namemap=" . Dumper($namemap) . "\n"; + + my $portsvlanmap = xCAT::MacMap::walkoid($session, $BNTOID->{$swmod}->{vlanInfoPorts}, silentfail=>1); + #print "portsvlanmap=" . Dumper($portsvlanmap) . "\n"; + foreach my $vid (keys (%$portsvlanmap)) { + my $data=$portsvlanmap->{$vid}; + my @a = split(//, $data); + foreach (@a) { + my $num=unpack("C*", $_); + $_= sprintf ("%02x",$num); + } + $portsvlanmap->{$vid}=\@a; + } + + my %ret=(); + if (defined($portsvlanmap)) { + foreach my $portid (keys %{$namemap}) { + my $switchport = $namemap->{$portid}; + foreach my $portname (@ports) { + unless (xCAT::MacMap::namesmatch($portname,$switchport)) { + next; + } + + foreach my $vid (keys (%$portsvlanmap)) { + my $data=$portsvlanmap->{$vid}; + my $index = int($portid / 8); + my $offset = $portid % 8; + my $num = hex($data->[$index]) & ($HexConv{$offset}); + if ($num != 0) { + if (exists($ret{$portname})) { + my $pa=$ret{$portname}; + push (@$pa, $vid); + } else { + $ret{$portname}=[$vid]; + } + } + } + } + } + } + + return \%ret; +} + + +#-------------------------------------------------------------- +=head3 create_vlan + + Every switch plugin must implement this subroutine. + + Creates a new vlan on the switch + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub create_vlan { + my $session=shift; + my $vlan_id=shift; + my $vlan_name="xcat_vlan_" . $vlan_id; + + my $swmod=getSwitchModule($session); + if (!$swmod) { return (1, "This BNT switch modeule is not supported.");} + + print "BNT $swmod\n"; + #name + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{vlanNewCfgVlanName}, $vlan_id, $vlan_name, 'OCTET'); + if ($ret[0] != 0) { + $ret[1]="Set vlanNewCfgVlanName " . $BNTOID->{$swmod}->{vlanNewCfgVlanName} . ".$vlan_id to $vlan_name.\n" . $ret[1]; + return @ret; + } + + my $tmp = xCAT::MacMap::walkoid($session, $BNTOID->{$swmod}->{vlanNewCfgVlanName}); + #print "tmp=" . Dumper($tmp) . "\n"; + + #change to enable state + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{vlanNewCfgState}, $vlan_id, 2, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vlanNewCfgState " . $BNTOID->{$swmod}->{vlanNewCfgState} . ".$vlan_id to 2(enable).\n" . $ret[1]; + return @ret; + } + + @ret=apply_changes($session, $swmod); + + + return @ret; +} + +#-------------------------------------------------------------- +=head3 add_ports_to_vlan + + Every switch plugin must implement this subroutine. + + Adds the given ports to the existing vlan + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub add_ports_to_vlan { + my $session=shift; + my $vlan_id=shift; + # If portmode is set, we'll set the switchport to untagged(access) mode. + my $portmode=shift; + my @ports1=@_; + + my $swmod=getSwitchModule($session); + if (!$swmod) { return (1, "This BNT switch modeule is not supported.");} + + my $port_vlan_hash=get_vlanids_for_ports($session,@ports1); + #print "port_vlan_hash=" . Dumper($port_vlan_hash) . "\n"; + my @ports=(); + foreach my $port (keys(%$port_vlan_hash)) { + my $val=$port_vlan_hash->{$port}; + my $found=0; + foreach my $tmp_vid (@$val) { + if ($tmp_vid == $vlan_id) { + $found=1; + last; + } + } + if (!$found) { + push(@ports, $port); + } + } + + if (@ports==0) { return (0, ""); } + + #print "vlan=$vlan_id, ports=@ports\n"; + my $namemap = xCAT::MacMap::walkoid($session, $BNTOID->{$swmod}->{portInfoPhyIfDescr}); + if ($namemap) { + my $ifnamesupport=0; + foreach (keys %{$namemap}) { + if ($namemap->{$_}) { + $ifnamesupport=1; + last; + } + } + unless ($ifnamesupport) { + $namemap=0; + } + } + unless ($namemap) { + return; + } + #print "namemap=" . Dumper($namemap) . "\n"; + + + foreach my $portid (keys %{$namemap}) { + my $switchport = $namemap->{$portid}; + foreach my $portname (@ports) { + unless (xCAT::MacMap::namesmatch($portname,$switchport)) { + next; + } + + print "portid=$portid, vlan_id=$vlan_id\n"; + if ($portmode){ + #change this port to untagged + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{agPortNewCfgVlanTag}, $portid, 3, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set agPortNewCfgVlanTag " . $BNTOID->{$swmod}->{agPortNewCfgVlanTag} . ".$portid to 3(untagged).\n" . $ret[1]; + return @ret; + } + } else{ + #change this port to tagged + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{agPortNewCfgVlanTag}, $portid, 2, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set agPortNewCfgVlanTag " . $BNTOID->{$swmod}->{agPortNewCfgVlanTag} . ".$portid to 2(tagged).\n" . $ret[1]; + return @ret; + } + } + + #security feature: + if ($::XCATSITEVALS{vlansecurity} eq '1') { + #enable bpdu guard on this port + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{agPortNewCfgBpduGuard}, $portid, 1, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Enable bpdu guard for port $portid. (Set " . $BNTOID->{$swmod}->{agPortNewCfgBpduGuard} . ".$portid to 1.)\n" . $ret[1]; + return @ret; + } + #enable root guard on this port + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{agPortNewCfgStpExtGuard}, $portid, 2, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Enable root guard for port $portid. (Set " . $BNTOID->{$swmod}->{agPortNewCfgStpExtGuard} . ".$portid to 2.\n" . $ret[1]; + # return @ret; #do not return because it is not supported for some switches yet. A defect will be fixed in BNT. + } + } + + #add port in one by one + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{vlanNewCfgAddPort}, $vlan_id, $portid, 'GAUGE'); + if ($ret[0] != 0) { + $ret[1]="Set vlanNewCfgAddPort" . $BNTOID->{$swmod}->{vlanNewCfgAddPort} . ".$vlan_id to $portid.\n" . $ret[1]; + return @ret; + } + } + } + + my @ret=apply_changes($session, $swmod); + + return @ret; +} + +#------------------------------------------------------- +=head3 add_crossover_ports_to_vlan + It enables the vlan on the cross-over links. + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------- +sub add_crossover_ports_to_vlan { + my $session=shift; + my $vlan_id=shift; + my $switch=shift; + my @switches=@_; + my @ret=(0, ""); + my $msg; + + if (@switches == 0 ) { return (0, ""); } + +# my $swmod=getSwitchModule($session); +# if (!$swmod) { return (1, "This BNT switch modeule is not supported.");} + + #figure out the port numbers that are connecting to the given switches +# my @ips=(); + #get the ip address for each switch +# foreach my $switch (@switches) { +# my $ip=xCAT::NetworkUtils->getipaddr($switch); +# if (!$ip) { +# $msg .= "Cannot resolve ip address for switch $switch\n"; +# } else { +# push(@ips, $ip); +# } +# } + +# my @ports=(); +# my $tmp = xCAT::MacMap::walkoid($session, $BNTOID->{$swmod}->{arpInfoDestIp}); +# my $tmp1 = xCAT::MacMap::walkoid($session, $BNTOID->{$swmod}->{arpInfoSrcPort}); +# print Dumper($tmp); +# print Dumper($tmp1); +# foreach my $tmp_id (keys %{$tmp}) { +# my $tmp_ip=$tmp->{$tmp_id}; +# if (grep /^$tmp_ip$/, @ips) { +# my $port=$tmp1->{$tmp_id}; +# if ($port) { push(@ports, $port); } +# } +# } + + #get the ports that are connects to the switches + my $switchestab=xCAT::Table->new('switches',-create=>0); + my $ent = $switchestab->getNodeAttribs($switch, [qw(switch linkports)]); + if ((!$ent) || (! $ent->{linkports})) { return (0, $msg); } + + my %linkports=(); + foreach my $item (split(',',$ent->{linkports})) { + my @a=split(':', $item); + if (@a>1) { + $linkports{$a[1]}=$a[0]; + } + } + #print Dumper(%linkports); + + my @ports=(); + foreach my $sw (@switches) { + if (exists($linkports{$sw})) { + push(@ports, $linkports{$sw}); + } + } + #print "ports=@ports\n"; + + #now add the ports to the vlan + if (@ports > 0) { + my ($code, $msg1) = add_ports_to_vlan($session, $vlan_id, @ports); + if ($msg) { + $msg1 = $msg . $msg1; + } + + return ($code, $msg1); + } + + return (0, $msg); +} + +#-------------------------------------------------------------- +=head3 remove_vlan + + Every switch plugin must implement this subroutine. + + Remove a vlan from the switch + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub remove_vlan { + my $session=shift; + my $vlan_id=shift; + + my $swmod=getSwitchModule($session); + if (!$swmod) { return (1, "This BNT switch modeule is not supported.");} + + #set to delete state + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{vlanNewCfgDelete}, $vlan_id, 2, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vlanNewCfgDelete " . $BNTOID->{$swmod}->{vlanNewCfgDelete} . ".$vlan_id to 2(delete).\n" . $ret[1]; + return @ret; + } + + @ret=apply_changes($session, $swmod); + + return @ret; +} + + +#-------------------------------------------------------------- +=head3 remove_ports_from_vlan + + Every switch plugin must implement this subroutine. + + Remove ports from a vlan + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub remove_ports_from_vlan { + my $session=shift; + my $vlan_id=shift; + my @ports=@_; + + my $swmod=getSwitchModule($session); + if (!$swmod) { return (1, "This BNT switch modeule is not supported.");} + + #print "vlan=$vlan_id, ports=@ports\n"; + my $namemap = xCAT::MacMap::walkoid($session, $BNTOID->{$swmod}->{portInfoPhyIfDescr}); + if ($namemap) { + my $ifnamesupport=0; + foreach (keys %{$namemap}) { + if ($namemap->{$_}) { + $ifnamesupport=1; + last; + } + } + unless ($ifnamesupport) { + $namemap=0; + } + } + unless ($namemap) { + return; + } + #print "namemap=" . Dumper($namemap) . "\n"; + + + foreach my $portid (keys %{$namemap}) { + my $switchport = $namemap->{$portid}; + foreach my $portname (@ports) { + unless (xCAT::MacMap::namesmatch($portname,$switchport)) { + next; + } + + print "portid=$portid, vlan_id=$vlan_id\n"; + + #remove port in one by one + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{vlanNewCfgRemovePort}, $vlan_id, $portid, 'GAUGE'); + if ($ret[0] != 0) { + $ret[1]="Set vlanNewCfgRemovePort " . $BNTOID->{$swmod}->{vlanNewCfgRemovePort} . ".$vlan_id to $portid.\n" . $ret[1]; + return @ret; + } + } + } + + my @ret=apply_changes($session, $swmod); + + return @ret; + +} + + +sub apply_changes { + my $session=shift; + my $swmod=shift; + #apply + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{agApplyConfig}, 0, 1, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set agApplyConfig " . $BNTOID->{$swmod}->{agApplyConfig} . ".0 to 1(apply).\n" . $ret[1]; + return @ret; + } + + my $state=3; + while ($state == 3) { + my $tmp = xCAT::MacMap::walkoid($session, $BNTOID->{$swmod}->{agApplyConfig}); + #print "tmp=" . Dumper($tmp) . "\n"; + if ($tmp) { + $state=$tmp->{0}; + } + sleep(3); + } + + #set apply to idle state + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{agApplyConfig}, 0, 2, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set agApplyConfig " . $BNTOID->{$swmod}->{agApplyConfig} . ".0 to 2(idle).\n" . $ret[1]; + return @ret; + } + + if ($state == 5) { + return (1, "Apply configuration failed.\n"); + } + + #save configurations. + my @ret= xCAT::SwitchHandler::setoid($session, $BNTOID->{$swmod}->{agSaveConfiguration}, 0, 2, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set agSaveConfiguration " . $BNTOID->{$swmod}->{agSaveConfiguration} . ".0 to 2(saveActive).\n" . $ret[1]; + return @ret; + } + return @ret; +} + +sub getSwitchModule { + my $session=shift; + + #get the the switch brand name + my $tmp = xCAT::MacMap::walkoid($session, "$sysDescr", silentfail=>1); + my $swmod; + my $descr=$tmp->{0}; + if ($descr) { + if ($descr =~ /G8000/) { return "G8000"; } + if ($descr =~ /G8052/) { return "G8052"; } + if ($descr =~ /G8124/) { return "G8124"; } + if ($descr =~ /G8264/) { return "G8264"; } + if ($descr =~ /EN4093/) { return "EN4093"; } + } + return 0; +} + +1; diff --git a/xCAT-vlan/xCAT_plugin/vlan/CiscoSwitch.pm b/xCAT-vlan/xCAT_plugin/vlan/CiscoSwitch.pm new file mode 100755 index 000000000..257560d90 --- /dev/null +++ b/xCAT-vlan/xCAT_plugin/vlan/CiscoSwitch.pm @@ -0,0 +1,623 @@ +#!/usr/bin/perl +package xCAT_plugin::vlan::CiscoSwitch; +BEGIN +{ + $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; +} +use lib "$::XCATROOT/lib/perl"; +use strict; +use xCAT::MacMap; +use Data::Dumper; +use xCAT::SwitchHandler; +use SNMP; + +#IF-MIB +my $ifName='.1.3.6.1.2.1.31.1.1.1.1'; +my $ifDescr='.1.3.6.1.2.1.2.2.1.2'; +my $ifOperStatus='.1.3.6.1.2.1.2.2.1.8'; + +#CISCO-VTP-MIB +my $vtpVlanEditTable='.1.3.6.1.4.1.9.9.46.1.4.2'; +my $vtpVlanEditOperation='.1.3.6.1.4.1.9.9.46.1.4.1.1.1'; +my $vtpVlanEditBufferOwner='.1.3.6.1.4.1.9.9.46.1.4.1.1.3'; +my $vtpVlanEditRowStatus='.1.3.6.1.4.1.9.9.46.1.4.2.1.11.1'; +my $vtpVlanEditType='.1.3.6.1.4.1.9.9.46.1.4.2.1.3.1'; +my $vtpVlanEditName='.1.3.6.1.4.1.9.9.46.1.4.2.1.4.1'; +my $vtpVlanEditDot10Said='.1.3.6.1.4.1.9.9.46.1.4.2.1.6.1'; +my $vtpVlanState='.1.3.6.1.4.1.9.9.46.1.3.1.1.2'; +my $vlanTrunkPortDynamicStatus='.1.3.6.1.4.1.9.9.46.1.6.1.1.14'; +my $vlanTrunkPortDynamicState='.1.3.6.1.4.1.9.9.46.1.6.1.1.13'; +my $vlanTrunkPortNativeVlan= '.1.3.6.1.4.1.9.9.46.1.6.1.1.5'; +my $vlanTrunkPortVlansEnabled='.1.3.6.1.4.1.9.9.46.1.6.1.1.4'; +my $vlanTrunkPortEncapsulationType='.1.3.6.1.4.1.9.9.46.1.6.1.1.3'; + +#CISCO-VLAN-MEMBERSHIP-MIB +my $vmVlan='.1.3.6.1.4.1.9.9.68.1.2.2.1.2'; +my $vmVlanType='.1.3.6.1.4.1.9.9.68.1.2.2.1.1'; #1:static 2:dynamic 3:trunk + +#CISCI-STP-EXTENSION-MIB +my $stpxRootGuardConfigEnabled='1.3.6.1.4.1.9.9.82.1.5.1.1.2'; #1:enable 2:disable +my $stpxFastStartPortBpduGuardMode='1.3.6.1.4.1.9.9.82.1.9.3.1.4'; #1:enable 2:disable 3:default + + +my %HexConv=( 0=>0x80, 1=>0x40, 2=>0x20, 3=>0x10, + 4=>0x08, 5=>0x04, 6=>0x02, 7=>0x01); + +#-------------------------------------------------------------- +=head3 filter_string + + Every switch plugin must implement this subroutine. + + The return value will be used comare against the string + from sysDescr value from snmpget. If the latter contains + this string, then this mudule will be used to handle the + requests for vlan configuration. + +=cut +#------------------------------------------------------------- +sub filter_string { + return "Cisco "; +} + +#-------------------------------------------------------------- +=head3 get_vlan_ids + + Every switch plugin must implement this subroutine. + + It gets the existing vlan IDs for the switch. + Returns: an array containing all the vlan ids for the switch +=cut +#------------------------------------------------------------- +sub get_vlan_ids { + my $session=shift; + + my $vlanmap = xCAT::MacMap::walkoid($session, "$vtpVlanState.1", silentfail=>1); + my %vlanids=(); + foreach(keys(%$vlanmap)) { + $vlanids{$_}=1; + } + + my @ret=(sort keys(%vlanids)); + print "ids=@ret\n"; + return @ret; +} + + +#-------------------------------------------------------------- +=head3 get_vlanids_for_ports + + Every switch plugin must implement this subroutine. + + It returns a hash pointer that contains the vlan ids for each given port. + The kay is the port, the vlaue is a pointer to an array. +=cut +#------------------------------------------------------------- +sub get_vlanids_for_ports { + my $session=shift; + my @ports=@_; + + my $namemap = xCAT::MacMap::walkoid($session, $ifName); + #namemap is the mapping of ifIndex->(human readable name) + if ($namemap) { + my $ifnamesupport=0; + foreach (keys %{$namemap}) { + if ($namemap->{$_}) { + $ifnamesupport=1; + last; + } + } + unless ($ifnamesupport) { + $namemap=0; + } + } + unless ($namemap) { #Failback to ifDescr. ifDescr is close, but not perfect on some switches + $namemap = xCAT::MacMap::walkoid($session,$ifDescr); + } + unless ($namemap) { + return; + } + #print "namemap=" . Dumper($namemap) . "\n"; + + my $iftovlanmap = xCAT::MacMap::walkoid($session, $vmVlan, silentfail=>1); + #print "iftovlanmap=" . Dumper($iftovlanmap) . "\n"; + + my $trunktovlanmap = xCAT::MacMap::walkoid($session, $vlanTrunkPortNativeVlan, silentfail=>1); #for trunk ports, we are interested in the native vlan + #print "trunktovlanmap=" . Dumper($trunktovlanmap) . "\n"; + + my %ret=(); + if (defined($iftovlanmap) or defined($trunktovlanmap)) { + foreach my $portid (keys %{$namemap}) { + my $switchport = $namemap->{$portid}; + foreach my $portname (@ports) { + unless (xCAT::MacMap::namesmatch($portname,$switchport)) { + next; + } + + if (defined $iftovlanmap->{$portid}) { + $ret{$portname}=[$iftovlanmap->{$portid}]; + } elsif (defined $trunktovlanmap->{$portid}){ + $ret{$portname}=[$trunktovlanmap->{$portid}]; + } else { + $ret{$portname}=['NA']; + } + } + } + } + + return \%ret; +} + + +#-------------------------------------------------------------- +=head3 create_vlan + + Every switch plugin must implement this subroutine. + + Creates a new vlan on the switch + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub create_vlan { + my $session=shift; + my $vlan_id=shift; + my $vlan_name="xcat_vlan_" . $vlan_id; + + print "Cisco\n"; + #Verify if the edition is in use by another NMS station or device. + #The edition is not in use if you see this message: no MIB objects contained under subtree: + my $tmp = xCAT::MacMap::walkoid($session,$vtpVlanEditTable); + #print "tmp=" . Dumper($tmp) . "\n"; + + #Set the vtpVlanEditOperation to the copy state(2). This allows to create the VLAN. + my @ret= xCAT::SwitchHandler::setoid($session, $vtpVlanEditOperation, 1, 2, 'INTEGER'); + #if ($ret[0] != 0) { return @ret; } + + #set vtpVlanEditBufferOwner in order to makethe current owner of the edit permission visible + my $username="xcat"; + my @ret=xCAT::SwitchHandler::setoid($session,$vtpVlanEditBufferOwner, 1, $username, 'OCTET'); + if ($ret[0] != 0) { + $ret[1]="Set vtpVlanEditBufferOwner $vtpVlanEditBufferOwner.1 to $username.\n" .$ret[1]; + return @ret; + } + + #my $tmp = xCAT::MacMap::walkoid($session, $vtpVlanEditOperation); + #print "tmp=" . Dumper($tmp) . "\n"; + + #my $tmp = xCAT::MacMap::walkoid($session, $vtpVlanEditBufferOwner); + #print "tmp=" . Dumper($tmp) . "\n"; + + #set vtpVlanEditRowStatus to createAndGo(4) + my @ret= xCAT::SwitchHandler::setoid($session, $vtpVlanEditRowStatus, $vlan_id, 4, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vtpVlanEditRowStatus $vtpVlanEditRowStatus.$vlan_id to 4.\n" . $ret[1]; + return @ret; + } + + #set vtpVlanEditType to ethernet (1) + my @ret= xCAT::SwitchHandler::setoid($session,$vtpVlanEditType, $vlan_id, 1, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vtpVlanEditType $vtpVlanEditType.$vlan_id to 1.\n" . $ret[1]; + return @ret; + } + + #set vtpVlanEditName to xcat_vlan_# + my @ret= xCAT::SwitchHandler::setoid($session,$vtpVlanEditName, $vlan_id, $vlan_name, 'OCTET'); + if ($ret[0] != 0) { + $ret[1]="Set vtpVlanEditName $vtpVlanEditName.$vlan_id to $vlan_name.\n" . $ret[1]; + return @ret; + } + + #Set the vtpVlanEditDot10Said. This is the VLAN number + 100000 translated to hexadecimal. + my $num=100000 + $vlan_id; + my $hex_num=sprintf("%x", $num); + my @ret= xCAT::SwitchHandler::setoid($session,$vtpVlanEditDot10Said, $vlan_id, $hex_num, 'OCTETHEX'); + #if ($ret[0] != 0) { + # $ret[1]="Set vtpVlanEditDot10Said $vtpVlanEditDot10Said.$vlan_id to $hex_num.\n" . $ret[1]; + #return @ret; + #} + + #apply the changes, set vtpVlanEditOperation.1 to apply (3) + my @ret= xCAT::SwitchHandler::setoid($session, $vtpVlanEditOperation, 1, 3, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vtpVlanEditOperation $vtpVlanEditOperation.1 to 3.\n" . $ret[1]; + return @ret; + } + + #release the edition buffer, set vtpVlanEditOperation.1 to release (4) + my @ret= xCAT::SwitchHandler::setoid($session, $vtpVlanEditOperation, 1, 4, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vtpVlanEditOperation $vtpVlanEditOperation.1 to 4.\n" . $ret[1]; + return @ret; + } + + + return (0, ""); +} + +#-------------------------------------------------------------- +=head3 add_ports_to_vlan + + Every switch plugin must implement this subroutine. + + Adds the given ports to the existing vlan + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub add_ports_to_vlan { + my $session=shift; + my $vlan_id=shift; + # If portmode is set, we'll set the switchport to untagged(access) mode. + my $portmode=shift; + my @ports=@_; + + #print "Add ports @ports to vla $vlan_id.\n"; + + #add ports to the vlan. + if (@ports > 0) { + my $namemap = xCAT::MacMap::walkoid($session,$ifName); + #namemap is the mapping of ifIndex->(human readable name) + if ($namemap) { + my $ifnamesupport=0; #Assume broken ifnamesupport until proven good... (Nortel switch) + foreach (keys %{$namemap}) { + if ($namemap->{$_}) { + $ifnamesupport=1; + last; + } + } + unless ($ifnamesupport) { + $namemap=0; + } + } + unless ($namemap) { #Failback to ifDescr. ifDescr is close, but not perfect on some switches + $namemap = xCAT::MacMap::walkoid($session, $ifDescr); + } + unless ($namemap) { + return; + } + #print "namemap=" . Dumper($namemap) . "\n"; + + my $trunkportstate = xCAT::MacMap::walkoid($session,$vlanTrunkPortDynamicState); + #print "trunkportstate=" . Dumper($trunkportstate) . "\n"; + + my $iftovlanmap = xCAT::MacMap::walkoid($session, $vmVlan, silentfail=>1); + #print "iftovlanmap=" . Dumper($iftovlanmap) . "\n"; + my $trunktovlanmap = xCAT::MacMap::walkoid($session, $vlanTrunkPortNativeVlan, silentfail=>1); + #print "trunktovlanmap=" . Dumper($trunktovlanmap) . "\n"; + + #get current vlan type (static, dynamic or trunk) + my $vlantypemap = xCAT::MacMap::walkoid($session, $vmVlanType, silentfail=>1); + #print "vlantypemap=" . Dumper($vlantypemap) . "\n"; + + + foreach my $portid (keys %{$namemap}) { + my $switchport = $namemap->{$portid}; + foreach my $portname (@ports) { + unless (xCAT::MacMap::namesmatch($portname,$switchport)) { + next; + } + + + ### set the port to trunk mode + print "portid=$portid\n"; + # TODO: need testing in cisco's env for how to set a port as access mode through snmpset? + # Can setting vlanTrunkPortDot1qTunnel or cltcDot1qTunnelMode helps? + if ($trunkportstate->{$portid} != 1) { + my @ret = xCAT::SwitchHandler::setoid($session, $vlanTrunkPortEncapsulationType, $portid, 4, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vlanTrunkPortEncapsulationType $vlanTrunkPortEncapsulationType.$portid to 4(dot1Q).\n" . $ret[1]; + return @ret; + } + my @ret = xCAT::SwitchHandler::setoid($session, $vlanTrunkPortDynamicState, $portid, 1, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vlanTrunkPortDynamicState $vlanTrunkPortDynamicState.$portid to 1.\n" . $ret[1]; + return @ret; + } + } + + ### set trunk native vlan id + my $native_vlanid=1; + if ((exists($vlantypemap->{$portid})) && ($vlantypemap->{$portid} < 3)) { #the port was set to access mode before + if ((exists($iftovlanmap->{$portid})) && ($iftovlanmap->{$portid} > 0)) { + $native_vlanid=$iftovlanmap->{$portid}; + } + } else { #the port is originally set to trunk mode + if ((exists($trunktovlanmap->{$portid})) && ($trunktovlanmap->{$portid} > 0)) { + $native_vlanid=$trunktovlanmap->{$portid}; + } elsif ((exists($iftovlanmap->{$portid})) && ($iftovlanmap->{$portid} > 0)) { + $native_vlanid=$iftovlanmap->{$portid}; + } + } + print "*** native_vlanid=$native_vlanid\n"; + my @ret = xCAT::SwitchHandler::setoid($session, $vlanTrunkPortNativeVlan, $portid, $native_vlanid, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set native vlan for port $portid to $native_vlanid ($vlanTrunkPortNativeVlan.$portid to $native_vlanid).\n" . $ret[1]; + return @ret; + } + + + ### allow this vlan on the port + my $data = $session->get([$vlanTrunkPortVlansEnabled, $portid]); + my @a = split(//, $data); + foreach (@a) { + my $num=unpack("C*", $_); + $_= sprintf ("%02x",$num); + } + if ((exists($vlantypemap->{$portid})) && ($vlantypemap->{$portid} < 3)) { + #if originally this port was in access or dynamic mode, only enable original vlan and this tagged vlan + #reset the matrix + #print "***was access mode\n"; + foreach (@a) { + $_="00"; + } + #add native vlan id in the matrix + my $index = int($native_vlanid / 8); + my $offset = $native_vlanid % 8; + my $num = hex($a[$index]) | $HexConv{$offset}; + $a[$index] = sprintf("%02x", $num); + #print "index=$index, offset=$offset\n"; + + #add current vlan id in the matrix + $index = int($vlan_id / 8); + $offset = $vlan_id % 8; + $num = hex($a[$index]) | $HexConv{$offset}; + $a[$index] = sprintf("%02x", $num); + } else { + #if this port was trunk mode before, add this tagged vlan in the matrix + # print "***was trunk mode\n"; + my $index = int($vlan_id / 8); + my $offset = $vlan_id % 8; + my $num = hex($a[$index]) | $HexConv{$offset}; + $a[$index] = sprintf("%02x", $num); + } + #print "a=@a\n"; + foreach (@a) { + $_=hex($_); + $_=pack("C*", $_); + } + my $s = join(//, @a); + my @ret = xCAT::SwitchHandler::setoid($session, $vlanTrunkPortVlansEnabled, $portid, $s, 'OCTET'); + #print "**** ret=@ret\n"; + if ($ret[0] != 0) { + $ret[1]="Allow vlan on port $portid ($vlanTrunkPortVlansEnabled.$portid=$s).\n" . $ret[1]; + return @ret; + } + + + ### security feature + if ($::XCATSITEVALS{vlansecurity} eq '1') { + #set root guard on the port + my @ret = xCAT::SwitchHandler::setoid($session, $stpxRootGuardConfigEnabled, $portname, 1, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set root guard for port $portname to enable ($stpxRootGuardConfigEnabled.$portname=1).\n" . $ret[1]; + return @ret; + } + #set bpdu guard on the port + my @ret = xCAT::SwitchHandler::setoid($session, $stpxFastStartPortBpduGuardMode, $portname, 1, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set bpdu guard for port $portname to enalbe ($stpxFastStartPortBpduGuardMode.$portname=1).\n" . $ret[1]; + return @ret; + } + } + last; + } + } + } + + return (0, ""); +} + +#------------------------------------------------------- +=head3 add_crossover_ports_to_vlan + It enables the vlan on the cross-over links. + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------- +sub add_crossover_ports_to_vlan { + my $session=shift; + my $vlan_id=shift; + my $switch=shift; + my @switches=@_; + my @ret=(0, ""); + my $msg; + + if (@switches == 0 ) { return (0, ""); } + #get the ports that are connects to the switches + my $switchestab=xCAT::Table->new('switches',-create=>0); + my $ent = $switchestab->getNodeAttribs($switch, [qw(switch linkports)]); + if ((!$ent) || (! $ent->{linkports})) { return (0, $msg); } + + my %linkports=(); + foreach my $item (split(',',$ent->{linkports})) { + my @a=split(':', $item); + if (@a>1) { + $linkports{$a[1]}=$a[0]; + } + } + #print Dumper(%linkports); + + my @ports=(); + foreach my $sw (@switches) { + if (exists($linkports{$sw})) { + push(@ports, $linkports{$sw}); + } + } + #print "ports=@ports\n"; + + #now add the ports to the vlan + if (@ports > 0) { + my ($code, $msg1) = add_ports_to_vlan($session, $vlan_id, @ports); + if ($msg) { + $msg1 = $msg . $msg1; + } + + return ($code, $msg1); + } + + return (0, $msg); +} + +#-------------------------------------------------------------- +=head3 remove_vlan + + Every switch plugin must implement this subroutine. + + Remove a vlan from the switch + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub remove_vlan { + my $session=shift; + my $vlan_id=shift; + + #Verify if the edition is in use by another NMS station or device. + #The edition is not in use if you see this message: no MIB objects contained under subtree: + my $tmp = xCAT::MacMap::walkoid($session,$vtpVlanEditTable); + #print "tmp=" . Dumper($tmp) . "\n"; + + #Set the vtpVlanEditOperation to the copy state(2). This allows to delete the VLAN. + my @ret= xCAT::SwitchHandler::setoid($session, $vtpVlanEditOperation, 1, 2, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vtpVlanEditOperation $vtpVlanEditOperation.1 to 3 (copy state).\n" . $ret[1]; + return @ret; + } + + #set vtpVlanEditBufferOwner in order to makethe current owner of the edit permission visible + my $username="xcat"; + my @ret=xCAT::SwitchHandler::setoid($session,$vtpVlanEditBufferOwner, 1, $username, 'OCTET'); + if ($ret[0] != 0) { + $ret[1]="Set vtpVlanEditBufferOwner $vtpVlanEditBufferOwner.1 to $username.\n" . $ret[1]; + return @ret; + } + + #set vtpVlanEditRowStatus to destroy(6) + my @ret= xCAT::SwitchHandler::setoid($session, $vtpVlanEditRowStatus, $vlan_id, 6, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vtpVlanEditRowStatus $vtpVlanEditRowStatus.$vlan_id to 6 (destroy).\n" . $ret[1]; + return @ret; + } + + #apply the changes, set vtpVlanEditOperation.1 to apply (3) + my @ret= xCAT::SwitchHandler::setoid($session, $vtpVlanEditOperation, 1, 3, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set $vtpVlanEditOperation.1 to 3 (apply).\n" . $ret[1]; + return @ret; + } + + #release the edition buffer, set vtpVlanEditOperation.1 to release (4) + my @ret= xCAT::SwitchHandler::setoid($session, $vtpVlanEditOperation, 1, 4, 'INTEGER'); + if ($ret[0] != 0) { + $ret[1]="Set vtpVlanEditOperation $vtpVlanEditOperation.1 to 4 (release).\n" . $ret[1]; + return @ret; + } + + #my $iftovlanmap = xCAT::MacMap::walkoid($session, $vmVlan, silentfail=>1); + #print "iftovlanmap=" . Dumper($iftovlanmap) . "\n"; + #foreach (keys(%$iftovlanmap)) { + # if($iftovlanmap->{$_} == $vlan_id) { + #my @ret= xCAT::SwitchHandler::setoid($session, $vmVlan, $_, 1, 'INTEGER'); + #if ($ret[0] != 0) { + # $ret[1]="Set $vmVlan.$_ to 1.\n" . $ret[1]; + # return @ret; + #} + # } + #} + #my $trunktovlanmap = xCAT::MacMap::walkoid($session, $vlanTrunkPortNativeVlan, silentfail=>1); #for trunk ports, we are interested in the native vlan + #print "trunktovlanmap=" . Dumper($trunktovlanmap) . "\n"; + #foreach (keys(%$trunktovlanmap)) { + # if($trunktovlanmap->{$_} == $vlan_id) { + # my @ret= xCAT::SwitchHandler::setoid($session, $vlanTrunkPortNativeVlan, $_, 1, 'INTEGER'); + # if ($ret[0] != 0) { + # $ret[1]="Set $vlanTrunkPortNativeVlan.$_ to 1.\n" . $ret[1]; + # return @ret; + # } + # } + #} + + + return (0, "");; +} + + +#-------------------------------------------------------------- +=head3 remove_ports_from_vlan + + Every switch plugin must implement this subroutine. + + Remove ports from a vlan + Returns an array. (erorcode, errormsg). When errorcode=0, means no error. +=cut +#------------------------------------------------------------- +sub remove_ports_from_vlan { + my $session=shift; + my $vlan_id=shift; + my @ports = @_; + + + if (@ports > 0) { + my $namemap = xCAT::MacMap::walkoid($session,$ifName); + #namemap is the mapping of ifIndex->(human readable name) + if ($namemap) { + my $ifnamesupport=0; + foreach (keys %{$namemap}) { + if ($namemap->{$_}) { + $ifnamesupport=1; + last; + } + } + unless ($ifnamesupport) { + $namemap=0; + } + } + unless ($namemap) { #Failback to ifDescr. ifDescr is close, but not perfect on some switches + $namemap = xCAT::MacMap::walkoid($session, $ifDescr); + } + unless ($namemap) { + return; + } + #print "namemap=" . Dumper($namemap) . "\n"; + + foreach my $portid (keys %{$namemap}) { + my $switchport = $namemap->{$portid}; + foreach my $portname (@ports) { + unless (xCAT::MacMap::namesmatch($portname,$switchport)) { + next; + } + + #remove the vlan id from the vlanTrunkPortVlansEnabled.port matrix + my $data = $session->get([$vlanTrunkPortVlansEnabled, $portid]); + my @a = split(//, $data); + foreach (@a) { + my $num=unpack("C*", $_); + $_= sprintf ("%02x",$num); + } + #print "a=@a\n"; + my $index = int($vlan_id / 8); + my $offset = $vlan_id % 8; + my $num = hex($a[$index]) & (~$HexConv{$offset}); + $a[$index] = sprintf("%02x", $num); + + #print "a=@a\n"; + + foreach (@a) { + $_=hex($_); + $_=pack("C*", $_); + } + + my $s = join(//, @a); + my @ret = xCAT::SwitchHandler::setoid($session, $vlanTrunkPortVlansEnabled, $portid, $s, 'OCTET'); + if ($ret[0] != 0) { + $ret[1]="Set vlanTrunkPortVlansEnabled $vlanTrunkPortVlansEnabled.$portid to $s.\n" . $ret[1]; + return @ret; + } + + last; + } + } + } + + return (0, "");; +} + + + +1; diff --git a/xCAT-vlan/xpod2man b/xCAT-vlan/xpod2man new file mode 100755 index 000000000..fc4646096 --- /dev/null +++ b/xCAT-vlan/xpod2man @@ -0,0 +1,213 @@ +#!/usr/bin/perl +# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html + +# First builds the xCAT summary man page from Synopsis of each man page. +# Then converts all of the pod man pages into html (including links to each other) + +# We assume that this script is run in the xCAT-vlan-2.0 dir, so everything is +# done relative to that. + +use strict; +#use lib '.'; +use Pod::Man; +use Pod::Html; + +my $poddir = 'pods'; +my $mandir = 'share/man'; +my $htmldir = 'share/doc'; +my $cachedir = '/tmp'; + +my @pods = getPodList($poddir); +#foreach (@pods) { print "$_\n"; } exit; + +# Build the cmd overview page. +#writesummarypage("$poddir/man1/xcat.1.pod", @pods); + +# Build the man page for each pod. +#mkdir($mandir) or die "Error: could not create $mandir.\n"; +print "Converting PODs to man pages...\n"; +foreach my $podfile (@pods) { + my $manfile = $podfile; + $manfile =~ s/^$poddir/$mandir/; # change the beginning of the path + $manfile =~ s/\.pod$//; # change the ending + my $mdir = $manfile; + $mdir =~ s|/[^/]*$||; # get rid of the basename part + if (system("mkdir -p $mdir")) { die "Error: could not create $mdir.\n"; } + my ($section) = $podfile =~ /\.(\d+)\.pod$/; + convertpod2man($podfile, $manfile, $section); +} + +my @dummyPods = createDummyPods($poddir, \@pods); + +# Build the html page for each pod. +#mkdir($htmldir) or die "Error: could not create $htmldir.\n"; +print "Converting PODs to HTML pages...\n"; +# have to clear the cache, because old entries can cause a problem +unlink("$cachedir/pod2htmd.tmp", "$cachedir/pod2htmi.tmp"); +foreach my $podfile (@pods) { + my $htmlfile = $podfile; + $htmlfile =~ s/^$poddir/$htmldir/; # change the beginning of the path + $htmlfile =~ s/\.pod$/\.html/; # change the ending + my $hdir = $htmlfile; + $hdir =~ s|/[^/]*$||; # get rid of the basename part + if (system("mkdir -p $hdir")) { die "Error: could not create $hdir.\n"; } + #print "$podfile, $htmlfile, $poddir, $htmldir\n"; + convertpod2html($podfile, $htmlfile, $poddir, $htmldir); +} + +# Remove the dummy pods +unlink @dummyPods; +rmdir "$poddir/man7"; + +exit; + + +# To enable linking between the cmd man pages and the db man pages, need to: +# grep thru the cmd pods searching for references (L<>) to any section 5 man page +# if that pod does not exist, create an empty one that will satisfy pod2html +# keep track of all dummy pods created, so they can be removed later +sub createDummyPods { + my ($poddir, $pods) = @_; + my $cmd = "grep -r -E 'L<.+\\([57]\\)\\|.+\\.[57]>' " . $poddir; + #print "Running cmd: ", $cmd, "\n"; + my @lines = `$cmd`; + if ($?) { print "Error running: $cmd\n"; print join('', @lines); } + #my @lines; + #system($cmd); + my @dummyPods; + foreach my $l (@lines) { + #print "$l\n"; + my @matches = $l =~ /L<([^\(]+)\(([57])\)\|\1\.[57]>/g; # get all the matches in the line + # The above line should create the array with every other entry being the man page name + # and every other entry is the section # (5 or 7) + my $cmd; + while ($cmd=shift @matches) { + #foreach my $m (@matches) { + my $section = shift @matches; + my $filename = "$poddir/man$section/$cmd.$section.pod"; + #print "$filename\n"; + if (!(grep /^$filename$/, @$pods) && !(grep /^$filename$/, @dummyPods)) { push @dummyPods, $filename; } + } + } + + + # Create these empty files + print "Creating empty linked-to files: ", join(', ', @dummyPods), "\n"; + mkdir "$poddir/man7"; + foreach my $d (@dummyPods) { + if (!open(TMP, ">>$d")) { warn "Could not create dummy pod file $d ($!)\n"; } + else { close TMP; } + } + + return @dummyPods; +} + +# Recursively get the list of pod man page files. +sub getPodList { + my $poddir = shift; + my @files; + + # 1st get toplevel dir listing + opendir(DIR, $poddir) or die "Error: could not read $poddir.\n"; + my @topdir = grep !/^\./, readdir(DIR); # / + close(DIR); + + # Now go thru each subdir (these are man1, man3, etc.) + foreach my $mandir (@topdir) { + opendir(DIR, "$poddir/$mandir") or die "Error: could not read $poddir/$mandir.\n"; + my @dir = grep !/^\./, readdir(DIR); # / + close(DIR); + foreach my $file (@dir) { + push @files, "$poddir/$mandir/$file"; + } + } + return sort @files; +} + + +# Create the xcat man page that gives a summary description of each xcat cmd. +sub writesummarypage { + my $file = shift; # relative path file name of the man page + # the rest of @_ contains the pod files that describe each cmd + + open(FILE, ">$file") or die "Error: could not open $file for writing.\n"; + + print FILE <<'EOS1'; +=head1 NAME + +B - extreme Cluster Administration Tool. + +=head1 DESCRIPTION + +Extreme Cluster Administration Toolkit (xCAT). xCAT is a scalable distributed computing management +and provisioning tool that provides a unified interface for hardware control, discovery, and +OS diskful/diskfree deployment. + + +=head1 XCAT DATABASE + +All of the cluster configuration information is in the xCAT database. See L for +descriptions of every table in the database. + +=head1 XCAT COMMANDS + +What follows is a short description of each xCAT command. To get more information about a particular +command, see its man page. Note that the commands are listed in alphabetical order B, +i.e. all the commands in section 1, then the commands in section 3, etc. + +=over 12 +EOS1 + +# extract the summary for each cmd from its man page +foreach my $manpage (@_) { + my ($sectionnum) = $manpage =~ /\.(\d+)\.pod$/; + # Suck in the whole file, then we will parse it. + open(MANPAGE, "$manpage") or die "Error: could not open $manpage for reading.\n"; + my @contents = ; + my $wholemanpage = join('', @contents); + close(MANPAGE); + # This regex matches: optional space, =head1, space, title, space, cmd, space, description, newline + my ($cmd, $description) = $wholemanpage =~ /^\s*=head1\s+\S+\s+(\S+)\s+(.+?)\n/si; + if (!defined($cmd)) { print "Warning: $manpage is not in a recognized structure. It will be ignored.\n"; next; } + if (!defined($description)) { print "Warning: $manpage does not have a description for $cmd. It will be ignored.\n"; next; } + $cmd =~ s/^.<(.+)>$/$1/; # if the cmd name has pod formatting around it, strip it off + $description =~ s/^-\s*//; # if the description has a leading hypen, strip it off + print FILE "\n=item L<$cmd($sectionnum)|$cmd.$sectionnum>\n\n".$description."\n"; +} + +# Artificially add the xcattest cmd, because the xCAT-test rpm will add this +print FILE "\n=item L\n\nRun automated xCAT test cases.\n"; + + print FILE <<"EOS3"; + +=back +EOS3 + + close FILE; +} + + +# Create the html page for one pod. +sub convertpod2html { + my ($podfile, $htmlfile, $poddir, $htmldir) = @_; + + #TODO: use --css= and --title= to make the pages look better + pod2html($podfile, + "--outfile=$htmlfile", + "--podpath=man1", + "--podroot=$poddir", + "--htmldir=$htmldir", + "--recurse", + "--cachedir=$cachedir", + ); + +} + + +# Create the man page for one pod. +sub convertpod2man { + my ($podfile, $manfile, $section) = @_; + + my $parser = Pod::Man->new(section => $section); + $parser->parse_from_file($podfile, $manfile); +}