Commit 682bca22 authored by Mike Hibler's avatar Mike Hibler

Support for RESTCONF API used by new Dell OS10-based switches (S5248F-ON).

Could be vastly improved, but it works for what we need to do.
parent eb8b1b87
# #
# Copyright (c) 2000-2018 University of Utah and the Flux Group. # Copyright (c) 2000-2019 University of Utah and the Flux Group.
# #
# {{{EMULAB-LICENSE # {{{EMULAB-LICENSE
# #
...@@ -40,8 +40,9 @@ LIB_STUFF = snmpit_intel.pm \ ...@@ -40,8 +40,9 @@ LIB_STUFF = snmpit_intel.pm \
snmpit_nortel.pm snmpit_hp.pm snmpit_apcon.pm \ snmpit_nortel.pm snmpit_hp.pm snmpit_apcon.pm \
snmpit_arista.pm snmpit_arista_switch_daemon.py \ snmpit_arista.pm snmpit_arista_switch_daemon.py \
snmpit_mellanox.pm MLNX_XMLGateway.pm \ snmpit_mellanox.pm MLNX_XMLGateway.pm \
snmpit_force10.pm force10_expect.pm snmpit_h3c.pm \ snmpit_force10.pm force10_expect.pm \
snmpit_libNetconf.pm snmpit_netscout.pm snmpit_dellrest.pm dell_rest.pm \
snmpit_h3c.pm snmpit_libNetconf.pm snmpit_netscout.pm
# #
# Force dependencies on the scripts so that they will be rerun through # Force dependencies on the scripts so that they will be rerun through
......
#!/usr/bin/perl -w
#
# Copyright (c) 2019 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/>.
#
# }}}
#
#
# Module for Dell OS10 Enterprise RESTCONF API.
# XXX taken from FreeNAS REST API support and probably very similar to other
# REST APIs...
#
package dell_rest;
use strict;
use English;
use HTTP::Tiny;
use JSON::PP;
use MIME::Base64;
use Data::Dumper;
use Socket;
$| = 1; # Turn off line buffering on output
sub new($$$$)
{
# The next two lines are some voodoo taken from perltoot(1)
my $proto = shift;
my $class = ref($proto) || $proto;
my $name = shift;
my $debugLevel = shift;
my $userpass = shift; # username and password
#
# Create the actual object
#
my $self = {};
#
# Set the defaults for this object
#
if (defined($debugLevel)) {
$self->{DEBUG} = $debugLevel;
} else {
$self->{DEBUG} = 0;
}
$self->{NAME} = $name;
($self->{USERNAME}, $self->{PASSWORD}) = split(/:/, $userpass);
if (!$self->{USERNAME} || !$self->{PASSWORD}) {
warn "dell_rest: ERROR: must pass in username AND password!\n";
return undef;
}
if ($self->{DEBUG}) {
print "dell_rest initializing for $self->{NAME}, " .
"debug level $self->{DEBUG}\n" ;
}
# Make it a class object
bless($self, $class);
return $self;
}
#
# Make a request via the RESTCONF API.
# $method is "GET", "PUT", "POST", or "DELETE"
# $path is the resource path, e.g., "interfaces/ethernet"
# $datap is a reference to a hash of KEY=VALUE input content (default is ())
# $exstat is the expected success status code if not the method default
# $errorp is a reference to a string, used to return error string if !undef
# Return value is the decoded (as a hash) JSON KEY=VALUE returned by request
# Returns undef on failure.
#
sub call($$$;$$$$)
{
my ($self,$method,$path,$datap,$exstat,$errorp,$raw) = @_;
my %data = $datap ? %$datap : ();
my ($datastr,$paramstr);
my %status = (
"GET" => 200,
"PUT" => 200,
"POST" => 201,
"DELETE" => 204,
"PATCH" => 204
);
my $auth = $self->{USERNAME} . ":" . $self->{PASSWORD};
my $server = $self->{NAME};
if (%data > 0) {
$datastr = encode_json(\%data);
} else {
$datastr = "";
}
my $url = "https://$server/restconf/data/$path";
# we want to know with basic debugging whenever we go to the switch
print STDERR "dell_rest: make RESTAPI call to $server\n"
if ($self->{DEBUG});
print STDERR "$server: REQUEST: method=$method URL=$url\nCONTENT=$datastr\n"
if ($self->{DEBUG} > 3);
my %headers = (
"Accept" => "application/json",
"Authorization" => "Basic " . MIME::Base64::encode_base64($auth, "")
);
if ($method eq "POST" || $method eq "PATCH") {
$headers{"Content-Type"} = "application/json";
}
my $http = HTTP::Tiny->new("timeout" => 10);
my %options = ("headers" => \%headers, "content" => $datastr);
my $res = $http->request($method, $url, \%options);
print STDERR "$server: RESPONSE: ", Dumper($res), "\n"
if ($self->{DEBUG} > 3);
$exstat = $status{$method}
if (!defined($exstat));
if ($res->{'success'} && $res->{'status'} == $exstat) {
if (exists($res->{'headers'}{'content-type'}) &&
($res->{'headers'}{'content-type'} eq "application/json" ||
$res->{'headers'}{'content-type'} eq "application/yang-data+json")) {
return $raw ?
$res->{'content'} : JSON::PP->new->decode($res->{'content'});
}
if (!exists($res->{'content'})) {
return {};
}
if (!ref($res->{'content'})) {
return { "content" => $res->{'content'} };
}
my $msg = "Unparsable content: " . Dumper($res->{'content'});
if ($errorp) {
$$errorp = $msg;
} else {
warn("*** ERROR: dell_rest: $msg");
}
return undef;
}
if ($res->{'reason'}) {
my $content;
if (exists($res->{'content'}) &&
exists($res->{'headers'}{'content-type'})) {
my $ctype = $res->{'headers'}{'content-type'};
if ($ctype eq "text/plain") {
$content = $res->{'content'};
} elsif ($ctype eq "application/json" ||
$ctype eq "application/yang-data+json") {
my $cref =
JSON::PP->new->decode($res->{'content'});
if ($cref && ref $cref) {
if (exists($cref->{'ietf-restconf:errors'}) &&
exists($cref->{'ietf-restconf:errors'}->{'error'})) {
$content = $cref->{'ietf-restconf:errors'}->{'error'};
$content = @{$content}[0]->{'error-message'};
}
} elsif ($cref) {
$content = $cref;
} else {
$content = $res->{'content'};
}
}
}
my $msg = "Request failed: " . $res->{'reason'};
if ($content) {
$msg .= "\nRESTCONF error: $content";
}
if ($errorp) {
$$errorp = $msg;
} else {
warn("*** ERROR: dell_rest: $msg");
}
return undef;
}
my $msg = "Request failed: " . Dumper($res);
if ($errorp) {
$$errorp = $msg;
} else {
warn("*** ERROR: dell_rest: $msg");
}
return undef;
}
#
# Create a perl hash (suitable for JSON encoding) representing a new VLAN.
#
sub makeVlanSpec($$$)
{
my ($self,$tag,$name) = @_;
my $vname = "vlan$tag";
my $vlanhash = {
"interface" => [{
"type" => "iana-if-type:l2vlan",
"enabled" => JSON::PP::true,
"description" => "$name",
"name" => "$vname"
}]
};
return $vlanhash;
}
sub addPortsVlanSpec($$$$)
{
my ($self,$tag,$uportref,$tportref) = @_;
my $vname = "vlan$tag";
my @uports = ($uportref ? @{$uportref} : ());
my @tports = ($tportref ? @{$tportref} : ());
my $vlanhash = {
"interface" => [{
"name" => "$vname",
}]
};
if (@uports) {
$vlanhash->{"interface"}->[0]->{"dell-interface:untagged-ports"} = [@uports];
}
if (@tports) {
$vlanhash->{"interface"}->[0]->{"dell-interface:tagged-ports"} = [@tports];
}
return $vlanhash;
}
sub trunkPortSpec($$)
{
my ($self,$iface) = @_;
my $porthash = {
"interface" => [{
"name" => "$iface",
"dell-interface:mode" => "MODE_L2HYBRID"
}]
};
return $porthash;
}
sub enablePortSpec($$$)
{
my ($self,$iface,$state) = @_;
my $porthash = {
"interface" => [{
"name" => "$iface",
"enabled" => $state
}]
};
return $porthash;
}
#!/usr/bin/perl #!/usr/bin/perl
# #
# Copyright (c) 2000-2018 University of Utah and the Flux Group. # Copyright (c) 2000-2019 University of Utah and the Flux Group.
# #
# {{{EMULAB-LICENSE # {{{EMULAB-LICENSE
# #
...@@ -391,6 +391,11 @@ foreach my $name (keys %portMap) { ...@@ -391,6 +391,11 @@ foreach my $name (keys %portMap) {
$device = new snmpit_mellanox($name); $device = new snmpit_mellanox($name);
last; last;
}; };
/dellrest/ && do {
require snmpit_dellrest;
$device = new snmpit_dellrest($name);
last;
};
/force10/ && do { /force10/ && do {
require snmpit_force10; require snmpit_force10;
$device = new snmpit_force10($name); $device = new snmpit_force10($name);
...@@ -407,6 +412,11 @@ foreach my $name (keys %portMap) { ...@@ -407,6 +412,11 @@ foreach my $name (keys %portMap) {
goto skip; goto skip;
} }
if (!$device) {
warn "ERROR: could not create object for switch type '$type', skipping some ports\n";
goto skip;
}
my @results = $device->getFields(\@ports,\@oids); my @results = $device->getFields(\@ports,\@oids);
foreach my $result (@results) { foreach my $result (@results) {
......
#!/usr/bin/perl -w #!/usr/bin/perl -w
# #
# Copyright (c) 2000-2017 University of Utah and the Flux Group. # Copyright (c) 2000-2019 University of Utah and the Flux Group.
# #
# {{{EMULAB-LGPL # {{{EMULAB-LGPL
# #
...@@ -1596,8 +1596,8 @@ sub doListPorts($) { ...@@ -1596,8 +1596,8 @@ sub doListPorts($) {
# #
our ($port,$enabled,$up,$speed,$duplex); our ($port,$enabled,$up,$speed,$duplex);
print << "END"; print << "END";
Port Enabled Up Speed Duplex Port Enabled Up Speed Duplex
-------------------------------------------- ------------------------------------------------
END END
# make port field much longer because we use the 'triple' format of port string # make port field much longer because we use the 'triple' format of port string
format portlist = format portlist =
...@@ -1859,12 +1859,12 @@ sub doGetStats($) { ...@@ -1859,12 +1859,12 @@ sub doGetStats($) {
# See perlform(1) for help with formats # See perlform(1) for help with formats
# #
print << "END"; print << "END";
In InUnicast InNUnicast In In Unknown Out OutUnicast OutNUcast Out Out OutQueue In InUnicast InNUnicast In In Unknown Out OutUnicast OutNUcast Out Out OutQueue
Port Octets Packets Packets Discards Errors Protocol Octets Packets Packets Discards Errors Length Port Octets Packets Packets Discards Errors Protocol Octets Packets Packets Discards Errors Length
--------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------
END END
format stats = format stats =
@<<<<<<<< @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>>
$port, $inoctets, $inunicast,$innunicast,$indiscards,$inerr, $inunk, $outoctets,$outunicast,$outnunicast,$outdiscards,$outerr,$outq $port, $inoctets, $inunicast,$innunicast,$indiscards,$inerr, $inunk, $outoctets,$outunicast,$outnunicast,$outdiscards,$outerr,$outq
. .
$FORMAT_NAME = 'stats'; $FORMAT_NAME = 'stats';
......
This diff is collapsed.
#!/usr/bin/perl -w #!/usr/bin/perl -w
# #
# Copyright (c) 2000-2015, 2018 University of Utah and the Flux Group. # Copyright (c) 2000-2019 University of Utah and the Flux Group.
# Copyright (c) 2004-2009 Regents, University of California. # Copyright (c) 2004-2009 Regents, University of California.
# #
# {{{EMULAB-LGPL # {{{EMULAB-LGPL
...@@ -1212,12 +1212,15 @@ sub setVlanOnTrunks2($$$$@) { ...@@ -1212,12 +1212,15 @@ sub setVlanOnTrunks2($$$$@) {
my $value = shift; my $value = shift;
my $trunkref = shift; my $trunkref = shift;
my @trunks = @_; my @trunks = @_;
my $act = ($value ? "set" : "clear");
# #
# Now, we go through the list of trunks that need to be modifed, and # Now, we go through the list of trunks that need to be modifed, and
# do it! We have to modify both ends of the trunk, or we'll end up wasting # do it! We have to modify both ends of the trunk, or we'll end up wasting
# the trunk bandwidth. # the trunk bandwidth.
# #
# XXX trying to remove a VLAN that doesn't exist should not be fatal.
#
my $errors = 0; my $errors = 0;
foreach my $trunk (@trunks) { foreach my $trunk (@trunks) {
my ($src,$dst) = @$trunk; my ($src,$dst) = @$trunk;
...@@ -1246,7 +1249,8 @@ sub setVlanOnTrunks2($$$$@) { ...@@ -1246,7 +1249,8 @@ sub setVlanOnTrunks2($$$$@) {
} else { } else {
if (!$self->{DEVICES}{$src}-> if (!$self->{DEVICES}{$src}->
setVlansOnTrunk($trunkIndex,$value,$vlan_number)) { setVlansOnTrunk($trunkIndex,$value,$vlan_number)) {
warn "ERROR - unable to set trunk on switch $src\n"; warn "ERROR - unable to $act vlan $vlan_number ".
"on trunk on switch $src\n";
$errors += 1; $errors += 1;
} }
} }
...@@ -1267,7 +1271,8 @@ sub setVlanOnTrunks2($$$$@) { ...@@ -1267,7 +1271,8 @@ sub setVlanOnTrunks2($$$$@) {
} else { } else {
if (!$self->{DEVICES}{$dst}-> if (!$self->{DEVICES}{$dst}->
setVlansOnTrunk($trunkIndex,$value,$vlan_number)) { setVlansOnTrunk($trunkIndex,$value,$vlan_number)) {
warn "ERROR - unable to set trunk on switch $dst\n"; warn "ERROR - unable to $act vlan $vlan_number ".
"on trunk on switch $dst\n";
$errors += 1; $errors += 1;
} }
} }
...@@ -1675,6 +1680,12 @@ sub snap($) { ...@@ -1675,6 +1680,12 @@ sub snap($) {
$device = new snmpit_mellanox($devicename,$self->{DEBUG}); $device = new snmpit_mellanox($devicename,$self->{DEBUG});
last; last;
}; # /mellanox.*/ }; # /mellanox.*/
(/dellrest/)
&& do {
require snmpit_dellrest;
$device = new snmpit_dellrest($devicename,$self->{DEBUG});
last;
}; # /Dell RESTCONF switch.*/
(/force10/) (/force10/)
&& do { && do {
require snmpit_force10; require snmpit_force10;
......
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