Commit abe2c7c7 authored by Kirk Webb's avatar Kirk Webb

Create a new directory for the "portfix" version of snmpit.

In order to test the portfix changes to snmpit, a new version of
the tool and its modules has been split off here.  This new version
will be selected via a site variable / emulab feature.  This also
reverts changes to the mainline Ports module and snmpit modules
in the snmpit_test subdirectory.  The modified Port module will
go by the name of Ports_portfix.pm, and will ultimately disappear
once these changes have been vetted at the Utah Emulab site.
parent 08f71970
......@@ -55,15 +55,10 @@ use vars qw(@ISA @EXPORT);
@EXPORT = qw ( );
use libdb;
use EmulabConstants;
use English;
use Data::Dumper;
use overload ('""' => 'Stringify');
# Local constants
my $WIRE_END_NODE = "node";
my $WIRE_END_SWITCH = "switch";
# Cache of port instances
# node:iface OR node:card.port => Port Instance
my %allports = ();
......@@ -350,11 +345,6 @@ sub fake_IfaceString2TripleTokens($;$)
# This is useful when a port string is derived from the switch, but
# it is not stored in DB. (eg. listing all ports on a switch)
#
# Note from RPR: We have never represented the switch side of
# [interfaces] (except trunks) in the database. Most 'wires' table
# entries have a "real" [interface] on the PC side, but are just
# dangling references on the switch side.
#
sub LookupByStringForced($$)
{
my ($class, $str) = @_;
......@@ -391,34 +381,25 @@ sub LookupByStringForced($$)
}
if (defined($port)) {
my $irowref = {};
my $wrowref = {};
my $rowref = {};
$inst->{"RAW_STRING"} = $str;
$inst->{"FORCED"} = 1;
$inst->{"HAS_FIELDS"} = 1;
$irowref->{'iface'} = $iface;
$irowref->{'node_id'} = $nodeid;
$irowref->{'card'} = $card;
$irowref->{'port'} = $port;
$irowref->{'mac'} = "";
$irowref->{'IP'} = "";
$irowref->{'role'} = "";
$irowref->{'interface_type'} = "";
$irowref->{'mask'} = "";
$irowref->{'uuid'} = "";
$irowref->{'trunk'} = 0;
$irowref->{'trunk_mode'} = "equal";
$inst->{"INTERFACES_ROW"} = $irowref;
# XXX: Incomplete, but if the port isn't in the wires table,
# what are we to do? We know nothing about the other end.
$wrowref->{'type'} = TBDB_WIRETYPE_UNUSED();
$wrowref->{'node_id1'} = $nodeid;
$wrowref->{'card1'} = $card;
$wrowref->{'port1'} = $port;
$inst->{"WIRES_ROW"} = $wrowref;
$rowref->{'iface'} = $iface;
$rowref->{'node_id'} = $nodeid;
$rowref->{'card'} = $card;
$rowref->{'port'} = $port;
$rowref->{'mac'} = "";
$rowref->{'IP'} = "";
$rowref->{'role'} = "";
$rowref->{'interface_type'} = "";
$rowref->{'mask'} = "";
$rowref->{'uuid'} = "";
$rowref->{'trunk'} = 0;
$rowref->{'trunk_mode'} = "equal";
$inst->{"INTERFACES_ROW"} = $rowref;
}
else {
$inst->{"RAW_STRING"} = $str;
......@@ -430,7 +411,7 @@ sub LookupByStringForced($$)
# We should determine this according to the query result
# in nodes table by nodeid.
#
$inst->{"WIRE_END"} = $WIRE_END_SWITCH;
$inst->{"WIRE_END"} = "switch";
bless($inst, $class);
return $inst;
......@@ -486,27 +467,24 @@ sub LookupByIface($$;$)
# wire mapping
$query_result =
DBQueryWarn("select * from wires ".
"where (node_id1='$nodeid' AND card1='$card' AND port1='$port') OR (node_id2='$nodeid' AND card2='$card' AND port2='$port')");
"where node_id1='$nodeid' AND card1='$card' AND port1='$port'");
return undef
if (!$query_result or !$query_result->numrows);
if (!$query_result);
$rowref = $query_result->fetchrow_hashref();
if ($rowref->{'type'} eq TBDB_WIRETYPE_NODE() ||
$rowref->{'type'} eq TBDB_WIRETYPE_CONTROL()) {
if ($rowref->{'node_id1'} eq $nodeid) {
$inst->{"WIRE_END"} = $WIRE_END_NODE;
} else {
$inst->{"WIRE_END"} = $WIRE_END_SWITCH;
}
} elsif ($rowref->{'type'} eq TBDB_WIRETYPE_TRUNK()) {
$inst->{"WIRE_END"} = $WIRE_END_SWITCH;
} elsif ($rowref->{'node_id2'} eq $nodeid) {
$inst->{"WIRE_END"} = $WIRE_END_SWITCH;
} else {
# XXX: Other cases are unhandled for now...
return undef;
$inst->{"WIRE_END"} = "pc";
if (!$query_result->numrows) {
$query_result =
DBQueryWarn("select * from wires ".
"where node_id2='$nodeid' AND card2='$card' AND port2='$port'");
return undef
if (!$query_result);
return undef
if (!$query_result->numrows);
$inst->{"WIRE_END"} = "switch";
}
$rowref = $query_result->fetchrow_hashref();
$inst->{"WIRES_ROW"} = $rowref;
$inst->{"FORCED"} = 0;
$inst->{"HAS_FIELDS"} = 1;
......@@ -544,36 +522,28 @@ sub LookupByTriple($$;$$)
return $allports{$strtriple};
}
my $inst = {};
# wire mapping:
my $query_result =
DBQueryWarn("select * from wires ".
"where (node_id1='$nodeid' AND card1='$card' AND port1='$port') OR (node_id2='$nodeid' AND card2='$card' AND port2='$port')");
"where node_id1='$nodeid' AND card1='$card' AND port1='$port'");
return undef
if (!$query_result or !$query_result->numrows);
if (!$query_result);
my $rowref = $query_result->fetchrow_hashref();
if ($rowref->{'type'} eq TBDB_WIRETYPE_NODE() ||
$rowref->{'type'} eq TBDB_WIRETYPE_CONTROL()) {
# Emulab is consistent about using the node_id1, etc. fields for the
# endpoint for the above wire types. If it were not, we would need
# to consult the 'nodes' table to see what role the node has.
if ($rowref->{'node_id1'} eq $nodeid) {
$inst->{"WIRE_END"} = $WIRE_END_NODE;
} else {
$inst->{"WIRE_END"} = $WIRE_END_SWITCH;
}
} elsif ($rowref->{'type'} eq TBDB_WIRETYPE_TRUNK()) {
$inst->{"WIRE_END"} = $WIRE_END_SWITCH;
} elsif ($rowref->{'node_id2'} eq $nodeid) {
# This is a failsafe case for wire types that are 'exotic'.
$inst->{"WIRE_END"} = $WIRE_END_SWITCH;
} else {
# XXX: Other cases are unhandled for now...
return undef;
}
my $inst = {};
$inst->{"WIRE_END"} = "pc";
if (!$query_result->numrows) {
$query_result =
DBQueryWarn("select * from wires ".
"where node_id2='$nodeid' AND card2='$card' AND port2='$port'");
return undef
if (!$query_result);
return undef
if (!$query_result->numrows);
$inst->{"WIRE_END"} = "switch";
}
my $rowref = $query_result->fetchrow_hashref();
$inst->{"WIRES_ROW"} = $rowref;
$query_result =
......@@ -581,10 +551,6 @@ sub LookupByTriple($$;$$)
"where node_id='$nodeid' AND card='$card' AND port='$port'");
return undef
if (!$query_result);
# Note: The code will almost always fall into this conditional
# block for switch ports because we typically do not have entries
# for them in the 'interfaces' table.
if (!$query_result->numrows) {
$rowref = {};
my $iface = fake_CardPort2Iface($card, $port);
......@@ -680,10 +646,10 @@ sub trunk($) { return field($_[0], 'trunk'); }
sub trunk_mode($) { return field($_[0], 'trunk_mode'); }
sub wire_end($) { return $_[0]->{'WIRE_END'}; }
sub is_switch_side($) { return $_[0]->wire_end() eq $WIRE_END_SWITCH; }
sub is_switch_side($) { return $_[0]->wire_end() eq "switch"; }
sub wire_type($) { return $_[0]->{'WIRES_ROW'}->{'type'}; }
sub is_trunk_port($) { return $_[0]->wire_type() eq TBDB_WIRETYPE_TRUNK(); }
sub is_trunk_port($) { return $_[0]->wire_type() eq "Trunk"; }
sub is_forced($) { return $_[0]->{"FORCED"};}
sub has_fields($) { return $_[0]->{"HAS_FIELDS"};}
......@@ -732,8 +698,7 @@ sub switch_iface($)
sub pc_node_id($)
{
my $self = shift;
if (!$self->is_switch_side() ||
$self->is_trunk_port()) {
if (!$self->is_switch_side()) {
return $self->node_id();
} else {
return $self->other_end_node_id();
......@@ -743,8 +708,7 @@ sub pc_node_id($)
sub pc_card($)
{
my $self = shift;
if (!$self->is_switch_side() ||
$self->is_trunk_port()) {
if (!$self->is_switch_side()) {
return $self->card();
} else {
return $self->other_end_card();
......@@ -754,8 +718,7 @@ sub pc_card($)
sub pc_port($)
{
my $self = shift;
if (!$self->is_switch_side() ||
$self->is_trunk_port()) {
if (!$self->is_switch_side()) {
return $self->port();
} else {
return $self->other_end_port();
......@@ -765,8 +728,7 @@ sub pc_port($)
sub pc_iface($)
{
my $self = shift;
if (!$self->is_switch_side() ||
$self->is_trunk_port()) {
if (!$self->is_switch_side()) {
return $self->iface();
} else {
return $self->other_end_iface();
......@@ -788,7 +750,7 @@ sub other_end_node_id($)
}
}
if ($self->node_id() eq $self->{'WIRES_ROW'}->{'node_id1'}) {
if ($self->wire_end() eq "pc") {
return $self->{'WIRES_ROW'}->{'node_id2'};
} else {
return $self->{'WIRES_ROW'}->{'node_id1'};
......@@ -810,7 +772,7 @@ sub other_end_card($)
}
}
if ($self->node_id() eq $self->{'WIRES_ROW'}->{'node_id1'}) {
if ($self->wire_end() eq "pc") {
return $self->{'WIRES_ROW'}->{'card2'};
} else {
return $self->{'WIRES_ROW'}->{'card1'};
......@@ -832,7 +794,7 @@ sub other_end_port($)
}
}
if ($self->node_id() eq $self->{'WIRES_ROW'}->{'node_id1'}) {
if ($self->wire_end() eq "pc") {
return $self->{'WIRES_ROW'}->{'port2'};
} else {
return $self->{'WIRES_ROW'}->{'port1'};
......@@ -854,7 +816,7 @@ sub other_end_iface($)
}
}
if ($self->node_id() eq $self->{'WIRES_ROW'}->{'node_id1'}) {
if ($self->wire_end() eq "pc") {
return Port->LookupByTriple(
$self->{'WIRES_ROW'}->{'node_id2'},
$self->{'WIRES_ROW'}->{'card2'},
......@@ -949,10 +911,7 @@ sub getOtherEndPort($) {
}
#
# get the PC side of a port instance. It is bogus to call this on an
# inter-switch trunk port, but we return the local ("this") side
# anyway in this case since some snmpit code using this method doesn't
# check the link type.
# get the PC side of a port instance
#
sub getPCPort($) {
my $self = $_[0];
......@@ -961,8 +920,7 @@ sub getPCPort($) {
return $self;
}
if (!$self->is_switch_side() ||
$self->is_trunk_port()) {
if ($self->wire_end() eq "pc") {
return $self;
} else {
return $self->getOtherEndPort();
......@@ -970,9 +928,7 @@ sub getPCPort($) {
}
#
# get the switch side of a port instance. This call is ambiguous in
# the case of an inter-switch trunk port, and will always return "this"
# port.
# get the switch side of a port instance
#
sub getSwitchPort($) {
my $self = $_[0];
......@@ -981,7 +937,7 @@ sub getSwitchPort($) {
return $self;
}
if ($self->is_switch_side()) {
if ($self->wire_end() ne "pc") {
return $self;
} else {
return $self->getOtherEndPort();
......
This diff is collapsed.
#
# Copyright (c) 2000-2013 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file 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 Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
SRCDIR = @srcdir@
TESTBED_SRCDIR = @top_srcdir@
OBJDIR = ../..
SUBDIR = tbsetup/snmpit_test
ISMAINSITE = @TBMAINSITE@
SYSTEM := $(shell uname -s)
include $(OBJDIR)/Makeconf
SUBDIRS =
BIN_STUFF = snmpit_test
LIB_STUFF = portstats snmpit_intel.pm \
snmpit_cisco.pm snmpit_lib.pm \
snmpit_cisco_stack.pm snmpit_intel_stack.pm \
snmpit_foundry.pm snmpit_stack.pm snmpit_remote.pm \
snmpit_nortel.pm snmpit_hp.pm snmpit_apcon.pm \
snmpit_arista.pm snmpit_arista_switch_daemon.py \
snmpit_mellanox.pm MLNX_XMLGateway.pm
#
# Force dependencies on the scripts so that they will be rerun through
# configure if the .in file is changed.
#
all: $(LIB_STUFF) $(BIN_STUFF)
include $(TESTBED_SRCDIR)/GNUmakerules
install: all script-install
script-install: $(addprefix $(INSTALL_LIBDIR)/snmpit_test/, $(LIB_STUFF)) \
$(addprefix $(INSTALL_BINDIR)/, $(BIN_STUFF))
control-install:
fs-install:
tipserv-install:
clrhouse-install:
clean:
rm -f snmpit_arista_switch_daemon.py snmpit_remote.pm \
snmpit_test portstats snmpit_arista.pm
$(INSTALL_DIR)/lib/snmpit_test/%: %
@echo "Installing $<"
-mkdir -p $(INSTALL_DIR)/lib/snmpit_test
$(INSTALL) $< $@
$(INSTALL_DIR)/lib/snmpit_test/portstats: portstats
@echo "Installing $<"
-mkdir -p $(INSTALL_DIR)/lib/snmpit_test
$(INSTALL_PROGRAM) $< $@
#!/usr/bin/perl -w
#
# Copyright (c) 2013 University of Utah and the Flux Group.
#
# {{{EMULAB-LGPL
#
# This file is part of the Emulab network testbed software.
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or (at
# your option) any later version.
#
# This file 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 Lesser General Public
# License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
# Simple test harness for the MLNX-gateway module.
use MLNX_XMLGateway;
use Getopt::Std;
use strict;
my @get_test1 = (
["name", "Basic 'get' Test #1"],
["get","/mlnxos/v1/api_version"],
["get","/mlnxos/v1/chassis/model"],
["get","/mlnxos/v1/chassis/pn"],
["get","/mlnxos/v1/chassis/fans/FAN/1/speed"],
["get","/mlnxos/v1/vsr/default_vsr/vlans/*"],
["submit"]
);
my @get_test2 = (
["name","Interface name 'get' Test #2"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces_by_name/*"],
["submit"]
);
my @pget_test1 = (
["name", "Port 'get' Test (Eth1/8) #1"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/enabled"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/type"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/mtu"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/pvid"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/mode"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/allowed/*"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/physical_location"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/supported_speed"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/configured_speed"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/actual_speed"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/lag/membership"],
["submit"]
);
my @pget_test2 = (
["name", "Port 'get' Test (Po1) #2"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/13826/enabled"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/13826/type"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/13826/vlans/pvid"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/13826/vlans/mode"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/13826/vlans/allowed/*"],
["submit"]
);
my @vlan_test1 = (
["name", "Vlan Creation Test #1"],
["action","/mlnxos/v1/vsr/default_vsr/vlans/add",{vlan_id => 666}],
["set-modify","/mlnxos/v1/vsr/default_vsr/vlans/666/name=testvlan"],
["get","/mlnxos/v1/vsr/default_vsr/vlans/*"],
["submit"],
["action","/mlnxos/v1/vsr/default_vsr/vlans/delete",{vlan_id => 666}],
["get","/mlnxos/v1/vsr/default_vsr/vlans/*"],
["submit"]
);
my @port_test1 = (
["name", "Port Toggle Test (Eth1/8) #1"],
["set-modify","/mlnxos/v1/vsr/default_vsr/interfaces/101/enabled=false"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/enabled"],
["submit"],
["set-modify","/mlnxos/v1/vsr/default_vsr/interfaces/101/enabled=true"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/enabled"],
["submit"]
);
my @vport_test1 = (
["name", "Vlan + Port Test (Eth1/8) #1"],
["action","/mlnxos/v1/vsr/default_vsr/vlans/add",{vlan_id => 666}],
["set-modify","/mlnxos/v1/vsr/default_vsr/vlans/666/name=testvlan"],
["set-modify","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/pvid=666"],
["get","/mlnxos/v1/vsr/default_vsr/vlans/*"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/pvid"],
["submit"],
["action","/mlnxos/v1/vsr/default_vsr/vlans/delete",{vlan_id => 666}],
["set-modify","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/pvid=53"],
["get","/mlnxos/v1/vsr/default_vsr/vlans/*"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/pvid"],
["submit"]
);
my @trunk_test1 = (
["name", "Trunk Test (Eth1/8) #1"],
["action","/mlnxos/v1/vsr/default_vsr/vlans/add",{vlan_id => 666}],
["set-modify","/mlnxos/v1/vsr/default_vsr/vlans/666/name=testvlan1"],
["action","/mlnxos/v1/vsr/default_vsr/vlans/add",{vlan_id => 777}],
["set-modify","/mlnxos/v1/vsr/default_vsr/vlans/666/name=testvlan2"],
["set-modify","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/mode=trunk"],
["action","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/allowed/add",{vlan_ids => "666"}],
["action","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/allowed/add",{vlan_ids => "777"}],
["submit"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/allowed/*"],
["get","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/mode"],
["submit"],
["action","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/allowed/delete",{vlan_ids => "666"}],
["action","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/allowed/delete",{vlan_ids => "777"}],
["set-modify","/mlnxos/v1/vsr/default_vsr/interfaces/101/vlans/mode=access"],
["action","/mlnxos/v1/vsr/default_vsr/vlans/delete",{vlan_id => 666}],
["action","/mlnxos/v1/vsr/default_vsr/vlans/delete",{vlan_id => 777}],
["submit"]
);
# List the tests to run here.
my @testsets = (\@pget_test1,);
my %opts = ();
if (!getopts("a:d:",\%opts)) {
print "Usage: $0 -a <uri_auth_string> -d <level>\n";
exit 1;
}
my $auth = "";
my $debug = 0;
$auth = $opts{'a'} or die "Must specify an auth string!";
$debug = $opts{'d'} if $opts{'d'};
my $gateway = MLNX_XMLGateway->new($auth);
$gateway->debug($debug) if $debug;
foreach my $tlist (@testsets) {
my @cmdset = ();
my @results = ();
my $testname = "unnamed";
foreach my $cmd (@{$tlist}) {
TESTSW1: for ((@{$cmd})[0]) {
/^name$/ && do {
$testname = (@{$cmd})[1];
print "========== Running Test: $testname ==========\n";
last TESTSW1;
};
/^submit$/ && do {
push @results, $gateway->call(\@cmdset);
@cmdset = ();
last TESTSW1;
};
# Default
push @cmdset, $cmd;
}
}
print "--- Results:\n";
my $i = 1;
foreach my $reslist (@results) {
print "* Submission $i:\n";
foreach my $res (@$reslist) {
print "@$res\n";
}
++$i;
}
print "\n";
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -1364,21 +1364,9 @@ sub listVlans($) {
$modport =~ s/\./\//;
$node = Port->LookupByStringForced($self->{NAME} . ".$modport");
}
# Let's be clear about what kind of connection this is and
# get the right port object. If there is an endpoint here,
# get that. If this is a trunk, then the member we want to
# put in the list is the "local" side (this switch's port).
my $mbrport;
if ($node->is_trunk_port()) {
$mbrport = $node;
} else {
# getOtherEndPort() will return the object upon which the
# method is invoked if it fails to lookup the other side.
$mbrport = $node->getOtherEndPort();
}
push @{$Members{$vlan_number}}, $mbrport;
$self->debug("$devicename:$vlan_number $node:$mbrport\n", 3);
my $pcport = $node->getPCPort();
$self->debug("$devicename:$vlan_number $node:$pcport\n", 3);
push @{$Members{$vlan_number}}, $pcport;
if (!$Names{$vlan_number}) {
$self->debug("listVlans: WARNING: port $node in non-existant " .
"VLAN $vlan_number\n", 1);
......
......@@ -1213,7 +1213,7 @@ sub listVlans($) {
push @vlifindexes, $ifindex;
}
}
my @ports = map {$_->is_trunk_port() ? $_ : $_->getOtherEndPort()}
my @ports = map {$_->getOtherEndPort()}
$self->convertPortFormat($PORT_FORMAT_PORT, @vlifindexes);
push @list, [$vlname, $vlnum, \@ports];
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment