r1179 + VLAN configuration gateway is almost working
authorDenis Ovsienko <infrastation@yandex.ru>
Wed, 10 Oct 2007 14:41:46 +0000 (14:41 +0000)
committerDenis Ovsienko <infrastation@yandex.ru>
Wed, 10 Oct 2007 14:41:46 +0000 (14:41 +0000)
gateways/switchvlans/cisco.connector
gateways/switchvlans/main
inc/interface.php

index ae3d367..b9bc6ea 100755 (executable)
@@ -4,10 +4,10 @@
 
 ENDPOINT=$1
 COMMAND=$2
-VLANINFO=$3
-PORTINFO=$4
+FILE1=$3
+FILE2=$4
 
-prepare_load_commands()
+prepare_fetch_commands()
 {
        [ $# = 1 ] || exit 1
        local skip=yes cval
@@ -45,10 +45,21 @@ prepare_load_commands()
        printf 'term len 0\nshow int status\nquit\n' >> $CMDS2
 }
 
-do_load()
+prepare_push_commands()
 {
-       nc $ENDPOINT 23 < $CMDS1 | fgrep active | cut -d' ' -f1 > $VLANINFO
-       nc $ENDPOINT 23 < $CMDS2 | egrep '^(Et|Fa|Gi|Te)' | sed -E 's/ +(notconnect|connected) +/=/;s/=(trunk|([0-9]+)) .*$/=\1/' > $PORTINFO
+}
+
+do_fetch()
+{
+       nc $ENDPOINT 23 < $CMDS1 | fgrep active | cut -d' ' -f1 > $FILE1
+       nc $ENDPOINT 23 < $CMDS2 | egrep '^(Et|Fa|Gi|Te)' | sed -E 's/ +(notconnect|connected) +/=/;s/=(trunk|([0-9]+)) .*$/=\1/' > $FILE2
+}
+
+do_push()
+{
+       echo "E!this is a stub" >> "$FILE2"
+       echo "W!this is another stub" >> "$FILE2"
+       echo "I!have a good time!" >> "$FILE2"
 }
 
 # This one is for VLAN list.
@@ -63,13 +74,13 @@ if ! [ -f "$CMDS2" ]; then
 fi
 
 case $COMMAND in
-       load)
-               prepare_load_commands $ENDPOINT
-               do_load
+       fetch)
+               prepare_fetch_commands $ENDPOINT
+               do_fetch
        ;;
-       save)
-               rm -f "$CMDS1" "$CMDS2"
-               exit 1
+       push)
+               prepare_push_commands $ENDPOINT
+               do_push
        ;;
        *)
                rm -f "$CMDS1" "$CMDS2"
index 736b710..e7edddb 100755 (executable)
 # VLAN ID and tagged ports will be shown as 'trunk' regardless of
 # how many VLANs they are members of.
 
+endpoint=
+hw=
+sw=
+user=
+CONNECTED=0
+
+
 authorized()
 {
        local endp=$1 user=$2 action=$3 arg1=$4 arg2=$5 skip=yes cval
@@ -50,7 +57,7 @@ authorized()
                cval=`echo "$line" | cut -s -d' ' -f2 | cut -s -d'@' -f2`
                [ -z "${endp##$cval}" ] || continue
 
-               if [ "$caction" = "change" ]; then
+               if [ "$action" = "change" ]; then
                        [ -z "$arg1" -o -z "$arg2" ] && return 1
                        cval=`echo "$line" | cut -s -d' ' -f4`
                        [ -z "${arg1##$cval}" ] || continue
@@ -73,10 +80,10 @@ authorized()
 do_connect()
 {
        # sanity checks
-       local endpoint=`echo $args | cut -s -d' ' -f1`
-       local hw=`echo $args | cut -s -d' ' -f2`
-       local sw=`echo $args | cut -s -d' ' -f3`
-       local user=`echo $args | cut -s -d' ' -f4`
+       endpoint=`echo $args | cut -s -d' ' -f1`
+       hw=`echo $args | cut -s -d' ' -f2`
+       sw=`echo $args | cut -s -d' ' -f3`
+       user=`echo $args | cut -s -d' ' -f4`
        if [ -z "$endpoint" -o -z "$hw" -o -z "$sw" -o -z "$user" ]; then
                echo 'ERR!too few arguments to connect'
                return
@@ -103,7 +110,7 @@ do_connect()
        # get the data
        case "$sw" in
                Cisco+IOS+12.2)
-                       `dirname $0`/cisco.connector $endpoint load "$VLANINFO" "$PORTINFO"
+                       `dirname $0`/cisco.connector $endpoint fetch "$VLANINFO" "$PORTINFO"
                        ret=$?
                        if [ $ret = 0 ]; then
                                CONNECTED=1
@@ -139,10 +146,69 @@ do_listfile()
 
 do_set()
 {
-       echo 'OK!'
-}
+       # sanity checks
+       local setline=$1
+       if [ -z "$setline" ]; then
+               echo 'ERR!missing set argument'
+               return
+       fi
+       local brand
+       case "$sw" in
+               Cisco+IOS+12.2)
+                       brand=cisco
+               ;;
+               *)
+                       echo "ERR!Don't know how to handle $sw at $endpoint"
+                       return
+               ;;
+       esac
+       local REQUESTS=`mktemp /tmp/racktables.XXXX`
+       local REPLIES=`mktemp /tmp/racktables.XXXX`
+       echo $1 | tr ';' '\n' | while read setexpr; do
+               portname=`echo $setexpr | cut -s -d'=' -f1`
+               newvlan=`echo $setexpr | cut -s -d'=' -f2`
+               curvlan=`egrep "^$portname=" $PORTINFO | cut -s -d'=' -f2`
+               if [ -z "$curvlan" ]; then
+                       echo "E!Could not find port $portname" >> "$REPLIES"
+                       continue
+               fi
+               if [ "$curvlan" = "trunk" ]; then
+                       echo "E!Port $portname is a trunk" >> "$REPLIES"
+                       continue
+               fi
+               # Authorize user for each change.
+               if ! authorized $endpoint $user change $curvlan $newvlan; then
+                       echo "E!User $user is not authorized to assign port $portname@$endpoint from VLAN $curvlan to VLAN $newvlan" >> "$REPLIES"
+                       continue
+               fi
+               echo "$portname $newvlan" >> "$REQUESTS"
+       done
+       nr=`egrep -c '^.' "$REPLIES"`
+       if [ "$nr" -ge 1 ]; then
+               echo "W!$nr request(s) have been ignored" >> "$REPLIES"
+       fi
 
-CONNECTED=0
+       nq=`egrep -c '^.' "$REQUESTS"`
+       if [ "$nq" -gt 1 ]; then
+               # Go!
+               sh -x `dirname $0`/$brand.connector $endpoint push "$REQUESTS" "$REPLIES"
+               local ret=$?
+
+               if [ $ret != 0 ]; then
+                       echo "ERR!Failed to configure $endpoint, connector returned code $ret"
+                       return
+               fi
+               echo "I!$nq port(s) have been reconfigured" >> "$REPLIES"
+       fi
+       echo -n 'OK!'
+       local SEMICOLON=
+       while read reply; do
+               echo -n $SEMICOLON$reply
+               SEMICOLON=';'
+       done < "$REPLIES"
+       echo
+       rm -f "$REQUESTS" "$REPLIES"
+}
 
 # main loop
 while read cmd args; do
index 84b6c44..8f0b214 100644 (file)
@@ -2837,8 +2837,128 @@ function renderUIConfig ()
        echo "Here be dragons :-P";
 }
 
-// This function queries the backend about current VLAN configuration and
-// renders a form suitable for submit.
+// This functions returns an array for VLAN list, and an array for port list (both
+// form another array themselves).
+// The ports in the latter array are marked with either VLAN ID or 'trunk'.
+// We don't sort the port list, as the gateway is believed to have done this already
+// (or at least the underlying switch software ought to). This is important, as the
+// port info is transferred to/from form not by names, but by numbers.
+function getSwitchVLANs ($object_id = 0)
+{
+       global $remote_username;
+       if ($object_id <= 0)
+       {
+               showError ('Invalid object_id in getSwitchVLANs()');
+               return;
+       }
+       $endpoints = findAllEndpoints ($object_id);
+       if (count ($endpoints) == 0)
+       {
+               showError ('Can\'t find any mean to reach current object. Please either set FQDN attribute or assign an IP address to the object.');
+               return NULL;
+       }
+       if (count ($endpoints) > 1)
+       {
+               showError ('More than one IP address is assigned to this object, please configure FQDN attribute.');
+               return NULL;
+       }
+       $hwtype = $swtype = 'unknown';
+       foreach (getAttrValues ($object_id) as $record)
+       {
+               if ($record['name'] == 'SW type' && !empty ($record['value']))
+                       $swtype = str_replace (' ', '+', $record['value']);
+               if ($record['name'] == 'HW type' && !empty ($record['value']))
+                       $hwtype = str_replace (' ', '+', $record['value']);
+       }
+       $data = queryGateway
+       (
+               'switchvlans',
+               array ("connect ${endpoints[0]} $hwtype $swtype ${remote_username}", 'listvlans', 'listports')
+       );
+       if ($data == NULL)
+       {
+               showError ('Failed to get any response from queryGateway() or the gateway died');
+               return NULL;
+       }
+       if (strpos ($data[0], 'OK!') !== 0)
+       {
+               showError ("Gateway failure: returned code ${data[0]}.");
+               return NULL;
+       }
+       if (count ($data) != 3)
+       {
+               showError ("Gateway failure: mailformed reply.");
+               return NULL;
+       }
+       // Now we have VLAN list in $data[1] and port list in $data[2]. Let's sort this out.
+       $vlanlist = array_unique (explode (',', substr ($data[1], strlen ('OK!'))));
+       sort ($vlanlist);
+       if (count ($vlanlist) == 0)
+       {
+               showError ("Gateway succeeded, but returned no VLAN records.");
+               return NULL;
+       }
+       $portlist = array();
+       foreach (explode (',', substr ($data[2], strlen ('OK '))) as $pair)
+       {
+               list ($portname, $vlanid) = explode ('=', $pair);
+               $portlist[] = array ('portname' => $portname, 'vlanid' => $vlanid);
+       }
+       if (count ($portlist) == 0)
+       {
+               showError ("Gateway succeeded, but returned no port records.");
+               return NULL;
+       }
+       return array ($vlanlist, $portlist);
+}
+
+function setSwitchVLANs ($object_id = 0, $setcmd)
+{
+       global $remote_username;
+       $log = array();
+       if ($object_id <= 0)
+               return array (array ('code' => 'error', 'message' => 'Invalid object_id in setSwitchVLANs()'));
+       $endpoints = findAllEndpoints ($object_id);
+       if (count ($endpoints) == 0)
+               return array (array ('code' => 'error', 'message' => 'Can\'t find any mean to reach current object. Please either set FQDN attribute or assign an IP address to the object.'));
+       if (count ($endpoints) > 1)
+               return array (array ('code' => 'error', 'message' => 'More than one IP address is assigned to this object, please configure FQDN attribute.'));
+       $hwtype = $swtype = 'unknown';
+       foreach (getAttrValues ($object_id) as $record)
+       {
+               if ($record['name'] == 'SW type' && !empty ($record['value']))
+                       $swtype = strtr ($record['value'], ' ', '+');
+               if ($record['name'] == 'HW type' && !empty ($record['value']))
+                       $hwtype = strtr ($record['value'], ' ', '+');
+       }
+       $data = queryGateway
+       (
+               'switchvlans',
+               array ("connect ${endpoints[0]} $hwtype $swtype ${remote_username}", $setcmd)
+       );
+       if ($data == NULL)
+               return array (array ('code' => 'error', 'message' => 'Failed to get any response from queryGateway() or the gateway died'));
+       if (strpos ($data[0], 'OK!') !== 0)
+               return array (array ('code' => 'error', 'message' => "Gateway failure: returned code ${data[0]}."));
+       if (count ($data) != 2)
+               return array (array ('code' => 'error', 'message' => 'Gateway failure: mailformed reply.'));
+       // Finally we can parse the response into message array.
+       $ret = array();
+       foreach (split (';', substr ($data[1], strlen ('OK!'))) as $text)
+       {
+               if (strpos ($text, 'I!') === 0)
+                       $code = 'success';
+               elseif (strpos ($text, 'W!') === 0)
+                       $code = 'warning';
+               else // All improperly formatted messages must be treated as error conditions.
+                       $code = 'error';
+               $ret[] = array ('code' => $code, 'message' => substr ($text, 2));
+       }
+       return $ret;
+}
+
+// This function queries the gateway about current VLAN configuration and
+// renders a form suitable for submit. Ah, and it does submit processing as well.
 function renderVLANMembership ($object_id = 0)
 {
        global $root, $pageno, $tabno, $remote_username;
@@ -2847,106 +2967,97 @@ function renderVLANMembership ($object_id = 0)
                showError ('Invalid object_id in renderVLANMembership()');
                return;
        }
-       showMessageOrError();
-       $endpoints = findAllEndpoints ($object_id);
-       if (count ($endpoints) == 0)
-               echo 'Can\'t find any mean to reach current object. Please either set FQDN attribute or assign an IP address to the object.';
-       elseif (count ($endpoints) > 1)
-               echo 'More than one IP address is assigned to this object, please configure FQDN attribute.';
-       else
+
+       // Handle probable pending submit.
+       if (isset ($_REQUEST['portcount']))
        {
-               // FIXME: find actual HW and SW data and pass to the gateway.
-               $hwtype = $swtype = 'unknown';
-               foreach (getAttrValues ($object_id) as $record)
+               $data = getSwitchVLANs ($object_id);
+               if ($data === NULL)
                {
-                       if ($record['name'] == 'SW type' && !empty ($record['value']))
-                               $swtype = str_replace (' ', '+', $record['value']);
-                       if ($record['name'] == 'HW type' && !empty ($record['value']))
-                               $hwtype = str_replace (' ', '+', $record['value']);
-               }
-               $data = queryGateway
-               (
-                       $tabno,
-                       array ("connect ${endpoints[0]} $hwtype $swtype ${remote_username}", 'listvlans', 'listports')
-               );
-               if ($data == NULL)
-               {
-                       showError ('Failed to get any response from queryGateway() or the gateway returned');
-                       return;
-               }
-               if (strpos ($data[0], 'OK!') !== 0)
-               {
-                       showError ("Gateway failure: returned code ${data[0]}.");
-                       return;
-               }
-               if (count ($data) != 3)
-               {
-                       showError ("Gateway failure: mailformed reply.");
-                       return;
-               }
-               // Now we have VLAN list in $data[1] and port list in $data[2]. Let's sort this out.
-               $vlanlist = array_unique (explode (',', substr ($data[1], strlen ('OK!'))));
-               sort ($vlanlist);
-               if (count ($vlanlist) == 0)
-               {
-                       showError ("Gateway succeeded, but returned no VLAN records.");
+                       showError ('getSwitchVLANs() failed in renderVLANMembership() during submit processing');
                        return;
                }
-               $portlist = array();
-               foreach (explode (',', substr ($data[2], strlen ('OK '))) as $pair)
+               list ($vlanlist, $portlist) = $data;
+               // Here we just build up 1 set command for the gateway with all of the ports
+               // included. The gateway is expected to filter unnecessary changes silently
+               // and to provide a list of responses with either error or success message
+               // for each of the rest.
+               assertUIntArg ('portcount');
+               $nports = $_REQUEST['portcount'];
+               $prefix = 'set ';
+               $log = array();
+               $setcmd = '';
+               for ($i = 0; $i < $nports; $i++)
+                       if
+                       (
+                               !isset ($_REQUEST['portname_' . $i]) ||
+                               !isset ($_REQUEST['vlanid_' . $i]) ||
+                               $_REQUEST['portname_' . $i] != $portlist[$i]['portname']
+                       )
+                               $log[] = array ('code' => 'error', 'message' => "Ignoring mailformed record #${i} in form submit");
+                       elseif
+                       (
+                               $_REQUEST['vlanid_' . $i] == $portlist[$i]['vlanid'] ||
+                               $portlist[$i]['vlaind'] == 'TRUNK'
+                       )
+                               continue;
+                       else
+                       {
+                               $setcmd .= $prefix . $_REQUEST['portname_' . $i] . '=' . $_REQUEST['vlanid_' . $i];
+                               $prefix = ';';
+                       }
+               printLog ($log);
+               // Feed the gateway and interpret its (non)response.
+               if ($setcmd != '')
+                       printLog (setSwitchVLANs ($object_id, $setcmd));
+       }
+
+       // Reload and render.
+       $data = getSwitchVLANs ($object_id);
+       if ($data === NULL)
+               return;
+       list ($vlanlist, $portlist) = $data;
+       startPortlet ('Current configuration');
+       echo "<table class=widetable cellspacing=0 cellpadding='5' align=center><tr>";
+       echo "<form method=post>";
+       echo "<input type=hidden name=page value='${pageno}'>";
+       echo "<input type=hidden name=tab value='${tabno}'>";
+       echo "<input type=hidden name=object_id value=${object_id}>";
+       echo "<input type=hidden name=portcount value=" . count ($portlist) . ">\n";
+       $portno = 0;
+       foreach ($portlist as $port)
+       {
+               // Don't let wide forms break our fancy pages.
+               if ($portno % PORTS_PER_ROW == 0)
                {
-                       list ($portname, $vlanid) = explode ('=', $pair);
-                       $portlist[$portname] = $vlanid;
+                       if ($portno > 0)
+                               echo "</tr>\n";
+                       echo "<tr><th>" . ($portno + 1) . "-" . ($portno + PORTS_PER_ROW) . "</th>";
                }
-               if (count ($portlist) == 0)
+               echo '<td>' . $port['portname'] . '<br>';
+               echo "<input type=hidden name=portname_${portno} value=" . $port['portname'] . '>';
+               if ($port['vlanid'] == 'trunk')
                {
-                       showError ("Gateway succeeded, but returned no port records.");
-                       return;
+                       echo "<input type=hidden name=vlanid_${portno} value='trunk'>";
+                       echo "<select disabled multiple='multiple' size=1><option>TRUNK</option></select>";
                }
-               // We don't sort the port list, as the gateway is believed to have done this already
-               // (or at least the underlying switch software ought to).
-               // Here the visual part of the work comes. Top row, then bottom.
-               startPortlet ('Current configuration');
-
-               echo "<table class=widetable cellspacing=0 cellpadding='5' align=center><tr>";
-               echo "<form action='${root}process.php' method=post>";
-               echo "<input type=hidden name=page value='${pageno}'>";
-               echo "<input type=hidden name=tab value='${tabno}'>";
-               echo "<input type=hidden name=op value=submit>";
-               echo "<input type=hidden name=object_id value=${object_id}>";
-               echo "<input type=hidden name=portcount value=" . count ($portlist) . ">\n";
-               $portno = 0;
-               foreach ($portlist as $portname => $vlanid)
+               else
                {
-                       // Don't let big boxes break our fancy pages.
-                       if (($portno) % PORTS_PER_ROW == 0)
-                       {
-                               if ($portno > 0)
-                                       echo "</tr>\n";
-                               echo "<tr><th>" . ($portno + 1) . "-" . ($portno + PORTS_PER_ROW) . "</th>";
-                       }
-                       echo "<td>${portname}<br>";
-                       if ($vlanid == 'trunk')
-                               echo '<select disabled multiple="multiple" size=1><option>[trunk]</option></select>';
-                       else
+                       echo "<select name=vlanid_${portno}>";
+                       foreach ($vlanlist as $dummy => $v)
                        {
-                               echo "<input type=hidden name=portname_${portno} value='${portname}'>";
-                               echo "<select name=vlanid_${portno}>";
-                               foreach ($vlanlist as $dummy => $v)
-                               {
-                                       echo "<option value=${v}";
-                                       if ($v == $vlanid)
-                                               echo ' selected';
-                                       echo ">${v}</option>";
-                               }
-                               echo "</select>";
+                               echo "<option value=${v}";
+                               if ($v == $port['vlanid'])
+                                       echo ' selected';
+                               echo ">${v}</option>";
                        }
-                       $portno++;
-                       echo "</td>";
+                       echo "</select>";
                }
-               echo "</tr><tr><td colspan=" . (PORTS_PER_ROW + 1) . "><input type=submit value='Save changes'></form></td></tr></table>";
-               finishPortlet();
+               $portno++;
+               echo "</td>";
        }
+       echo "</tr><tr><td colspan=" . (PORTS_PER_ROW + 1) . "><input type=submit value='Save changes'></form></td></tr></table>";
+       finishPortlet();
 }
 
 ?>