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
#
......@@ -40,8 +40,9 @@ LIB_STUFF = snmpit_intel.pm \
snmpit_nortel.pm snmpit_hp.pm snmpit_apcon.pm \
snmpit_arista.pm snmpit_arista_switch_daemon.py \
snmpit_mellanox.pm MLNX_XMLGateway.pm \
snmpit_force10.pm force10_expect.pm snmpit_h3c.pm \
snmpit_libNetconf.pm snmpit_netscout.pm
snmpit_force10.pm force10_expect.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
......
#!/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
#
# Copyright (c) 2000-2018 University of Utah and the Flux Group.
# Copyright (c) 2000-2019 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -391,6 +391,11 @@ foreach my $name (keys %portMap) {
$device = new snmpit_mellanox($name);
last;
};
/dellrest/ && do {
require snmpit_dellrest;
$device = new snmpit_dellrest($name);
last;
};
/force10/ && do {
require snmpit_force10;
$device = new snmpit_force10($name);
......@@ -407,6 +412,11 @@ foreach my $name (keys %portMap) {
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);
foreach my $result (@results) {
......
#!/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
#
......@@ -1597,7 +1597,7 @@ sub doListPorts($) {
our ($port,$enabled,$up,$speed,$duplex);
print << "END";
Port Enabled Up Speed Duplex
--------------------------------------------
------------------------------------------------
END
# make port field much longer because we use the 'triple' format of port string
format portlist =
......@@ -1861,10 +1861,10 @@ sub doGetStats($) {
print << "END";
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
---------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------
END
format stats =
@<<<<<<<< @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>>
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>>
$port, $inoctets, $inunicast,$innunicast,$indiscards,$inerr, $inunk, $outoctets,$outunicast,$outnunicast,$outdiscards,$outerr,$outq
.
$FORMAT_NAME = 'stats';
......
This diff is collapsed.
#!/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.
#
# {{{EMULAB-LGPL
......@@ -1212,12 +1212,15 @@ sub setVlanOnTrunks2($$$$@) {
my $value = shift;
my $trunkref = shift;
my @trunks = @_;
my $act = ($value ? "set" : "clear");
#
# 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
# the trunk bandwidth.
#
# XXX trying to remove a VLAN that doesn't exist should not be fatal.
#
my $errors = 0;
foreach my $trunk (@trunks) {
my ($src,$dst) = @$trunk;
......@@ -1246,7 +1249,8 @@ sub setVlanOnTrunks2($$$$@) {
} else {
if (!$self->{DEVICES}{$src}->
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;
}
}
......@@ -1267,7 +1271,8 @@ sub setVlanOnTrunks2($$$$@) {
} else {
if (!$self->{DEVICES}{$dst}->
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;
}
}
......@@ -1675,6 +1680,12 @@ sub snap($) {
$device = new snmpit_mellanox($devicename,$self->{DEBUG});
last;
}; # /mellanox.*/
(/dellrest/)
&& do {
require snmpit_dellrest;
$device = new snmpit_dellrest($devicename,$self->{DEBUG});
last;
}; # /Dell RESTCONF switch.*/
(/force10/)
&& do {
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