File Coverage

blib/lib/Device/Cisco/NXAPI.pm
Criterion Covered Total %
statement 172 225 76.4
branch 5 14 35.7
condition 3 14 21.4
subroutine 27 35 77.1
pod 9 10 90.0
total 216 298 72.4


line stmt bran cond sub pod time code
1             package Device::Cisco::NXAPI;
2              
3 6     6   195219 use 5.020;
  6         15  
4 6     6   22 use strict;
  6         5  
  6         100  
5 6     6   19 use warnings;
  6         11  
  6         132  
6              
7 6     6   2765 use Moose;
  6         1831052  
  6         33  
8 6     6   32759 use Modern::Perl;
  6         46857  
  6         32  
9 6     6   4733 use LWP::UserAgent;
  6         190105  
  6         202  
10 6     6   46 use HTTP::Request;
  6         7  
  6         141  
11 6     6   3887 use Data::Dumper;
  6         29616  
  6         334  
12 6     6   624 use JSON;
  6         8373  
  6         43  
13 6     6   730 use Carp;
  6         6  
  6         279  
14 6     6   3010 use List::MoreUtils qw( natatime );
  6         54051  
  6         31  
15 6     6   5556 use Params::Validate qw(:all);
  6         13343  
  6         1072  
16 6     6   31 use URI;
  6         5  
  6         126  
17              
18 6     6   2779 use Device::Cisco::NXAPI::Test;
  6         12  
  6         14536  
19              
20              
21             =head1 NAME
22              
23             Device::Cisco::NXAPI - Interact with the NX-API (Nexus 9K Switches)
24              
25             =head1 VERSION
26              
27             Version 0.02
28              
29             =cut
30              
31             our $VERSION = '0.02';
32              
33              
34             =head1 SYNOPSIS
35              
36             This module provides methods to make API calls and extract information from devices that support the NX-API.
37             This is predominantly the Nexus 9K range of switches in NXOS mode (not in ACI mode).
38              
39             use Device::Cisco::NXAPI;
40              
41             my $switch_api = Device::Cisco::NXAPI->new(uri => "https://192.168.1.1:8080", username => "admin", password => "admin");
42              
43             my @route_info = $switch_api->routes(vrf => "CustVRF");
44             my %version_info = $switch_api->version();
45              
46             =cut
47              
48             has 'user_agent' => ( is => 'rw', isa => 'LWP::UserAgent', default => sub { LWP::UserAgent->new });
49             has 'http_request' => ( is => 'rw', isa => 'HTTP::Request');
50             has 'uri' => ( is => 'ro', isa => 'Str', required => 1);
51             has 'username' => ( is => 'ro', isa => 'Str', required => 1);
52             has 'password' => ( is => 'ro', isa => 'Str', required => 1);
53             has 'debug' => ( is => 'ro', isa => 'Bool', default => 0);
54              
55             =head1 CONSTRUCTOR
56            
57             This method constructs a new C<Device::Cisco::NXAPI> object.
58            
59             my $switch_api = Device::Cisco::NXAPI->new(
60             # Mandatory parameters:
61             uri => '', # URI of the switch to connect to.
62             username => '', # Username to logon to the switch
63             password => '', # Password to logon to the switch
64              
65             # Optional Parameters
66             debug => (0 | 1), # Output debugging information to stderr
67             );
68              
69             =cut
70              
71             sub BUILD {
72 5     5 0 15458 my $self = shift;
73            
74 5         146 my $uri = URI->new($self->uri);
75 5 50 33     27829 croak "Only http:// or https:// supported." if !($uri->scheme eq 'http' or $uri->scheme eq 'https');
76              
77 5         398 $self->http_request(HTTP::Request->new(POST => $uri->scheme.$uri->host_port."/ins"));
78 5         129 $self->http_request()->content_type("application/json-rpc");
79 5         335 $self->user_agent()->credentials($uri->host_port, 'Secure Zone', $self->username, $self->password);
80             }
81              
82             =head1 METHODS
83            
84             =head2 tester()
85              
86             Returns a B<Device::Cisco::NXAPI::Test> object for the switch. This object can be used to run test
87             cases against the switch.
88              
89             =cut
90              
91             sub tester {
92 5     5 1 1238 my $self = shift;
93 5         63 return Device::Cisco::NXAPI::Test->new(switch => $self);
94             }
95              
96             =head2 version()
97              
98             my %version_info = $switch->version()
99              
100             Returns a hash consisting of system information. There are no arguments to this method.
101              
102             The structure returned is as follows:
103              
104             (
105             'kern_uptm_secs' => 17,
106             'kickstart_ver_str' => '7.0(3)I2(2b)',
107             'kick_file_name' => 'bootflash:///nxos.7.0.3.I2.2b.bin',
108             'rr_ctime' => ' Mon Dec 19 04:57:51 2016',
109             'kern_uptm_days' => 0,
110             'kick_tmstmp' => '02/29/2016 05:21:45',
111             'host_name' => 'switch',
112             'cpu_name' => 'Intel(R) Core(TM) i3- CPU @ 2.50GHz',
113             'kern_uptm_hrs' => 0,
114             'manufacturer' => 'Cisco Systems, Inc.',
115             'rr_sys_ver' => '11.3(2h)',
116             'mem_type' => 'kB',
117             'bootflash_size' => 7906304,
118             'kern_uptm_mins' => 5,
119             'bios_cmpl_time' => '10/12/2015',
120             'bios_ver_str' => '07.41',
121             'proc_board_id' => 'SAL1911BCSU',
122             'kick_cmpl_time' => ' 2/28/2016 21:00:00',
123             'header_str' => 'Cisco Nexus Operating System (NX-OS) Software',
124             'rr_reason' => 'Reset Requested by CLI command reload',
125             'memory' => 16401952,
126             'chassis_id' => 'Nexus9000 C9372PX chassis',
127             'rr_usecs' => 832622,
128             'rr_service' => 'PolicyElem Ch reload'
129             );
130              
131             =cut
132              
133             sub version {
134 0     0 1 0 my $self = shift;
135              
136 0         0 my $ret = $self->_send_cmd("show version");
137 0         0 _fixup_returned_structure($ret);
138 0         0 return %{ $ret };
  0         0  
139             }
140              
141             =head2 routes( %options )
142              
143             my @routes = $switch->routes(
144             vrf => '',
145             af => 'ipv4 | ipv6',
146             );
147              
148             my $first_route = $routes[0]->{prefix};
149              
150             Returns a list of HASHREFs with information on the routes present in the a VRFs routing table. The 'vrf =>' argument
151             determines the VRF, and if not specified the global routing table is used. The 'vrf => all' will return routes from
152             all routing tables on the switch.
153              
154             The structure returned is as follows:
155              
156             (
157             {
158             'prefix' => '1.1.1.0/24' # The prefix of the route
159             'vrf' => 'other_vrf', # VRF the route is in.
160             'paths' => [] # Paths to next-hop (multiple paths in the case of ECMP)
161             {
162             'clientname' => 'direct', # Protocol (e.g. direct, local, static, ospf)
163             'uptime' => 'P28DT19H43M28S', # Time the route has been in the routing table
164             'ipnexthop' => '2.2.2.1', # Next hop IP for the path
165             'ifname' => 'Eth1/1' # Egress interface for the path
166             }
167             ],
168             },
169             )
170              
171             =cut
172              
173             sub routes {
174 12     12 1 12 my $self = shift;
175 12         166 my %args = validate(@_,
176             {
177             vrf => { default => 'default', type => SCALAR | UNDEF },
178             af => { default => 'ipv4', type => SCALAR | UNDEF },
179             }
180             );
181              
182             my $per_af_command = {
183             ipv4 => "show ip route vrf $args{vrf}",
184             ipv6 => "show ipv6 route vrf $args{vrf}",
185 12   33     84 }->{ $args{af} } // croak "Unknown address-family: $args{af}";
186              
187 12         27 my $ret = $self->_send_cmd($per_af_command);
188 12         988 _fixup_returned_structure($ret);
189 12         17 return _modify_returned_route_structure($ret);
190             }
191              
192             sub _modify_returned_route_structure {
193 12     12   11 my $route_structure = shift;
194 12         10 my @ret_routes;
195              
196 12         6 for my $vrf (@{ $route_structure->{vrf} }) {
  12         23  
197 24         38 my $vrf_name = $vrf->{'vrf-name-out'};
198            
199 24         23 for my $addr_family (@{ $vrf->{addrf} }) {
  24         30  
200 24         26 my $address_family = $addr_family->{addrf};
201              
202 24         19 for my $prefix (@{ $addr_family->{prefix} }) {
  24         35  
203 120         78 my %prefix_info;
204              
205 120         162 $prefix_info{vrf} = $vrf_name;
206 120         145 $prefix_info{prefix} = $prefix->{ipprefix};
207              
208             # The format of the paths structure is not great. It's a single array of hashrefs,
209             # with 2 hashrefs for every IPv4 path and 3 HASHREFs for every IPv6 path.
210             #
211             # We first need to decide on how we iterate through the array:
212             my $path_iteration_num = {
213             ipv4 => 2,
214             ipv6 => 3,
215 120         180 }->{ $address_family };
216              
217             # Create the iterator
218 120         116 my $path_iterate = natatime $path_iteration_num, @{ $prefix->{path} };
  120         295  
219 120         258 while (my @path = $path_iterate->()) {
220              
221             # Merge either the 2 or 3 HASHREFs into a single HASH
222 124         105 my %merged_path_entry = map { %{ $_ } } @path;
  248         153  
  248         766  
223              
224             # Take a slice of the keys and vals that we want
225 124         340 my %path_entry = %merged_path_entry{ 'ipnexthop', 'uptime', 'ifname', 'clientname' };
226              
227 124         78 push @{ $prefix_info{paths} }, \%path_entry;
  124         459  
228             }
229              
230 120         282 push @ret_routes, \%prefix_info;
231             }
232              
233             }
234             }
235 12         222 return @ret_routes;
236             }
237              
238             =head2 arp( %options )
239              
240             my @arp_table = $switch->arp(
241             vrf => '',
242             );
243              
244             Returns a list of HASREFs containing the ARP table information. The B<vrf> argument specifies the VRF to
245             retrieve the ARP entries from. If no argument is specified the global routing table is used. If B<all> is
246             specified as the VRF, ARP entries from all routing tables are returned.
247              
248             The structure returned is as follows:
249              
250             (
251             {
252             'ifname' => 'mgmt0', # Egress interface
253             'vrf' => 'management', # VRF
254             'mac' => '0009.0fe9.9b39', # MAC address
255             'ip' => '10.47.64.4', # IP address
256             'time-stamp' => '00:01:20' # Entry timeout
257             }
258             )
259              
260             =cut
261              
262             sub arp {
263 6     6 1 7 my $self = shift;
264 6         52 my %args = validate(@_,
265             {
266             vrf => { default => 'default', type => SCALAR | UNDEF },
267             }
268             );
269              
270 6         27 my $ret = $self->_send_cmd("show ip arp vrf $args{vrf}");
271 6         137 _fixup_returned_structure($ret);
272              
273 6         10 return _modify_returned_arp_structure($ret);
274             }
275              
276             sub _modify_returned_arp_structure {
277 6     6   5 my $arp_structure = shift;
278 6         5 my @ret_arp;
279              
280 6         6 for my $vrf (@{ $arp_structure->{vrf} }) {
  6         7  
281 6         12 my $vrf_name = $vrf->{'vrf-name-out'};
282              
283 6         7 for my $adjacency (@{ $vrf->{adj} }) {
  6         8  
284             # Add the VRF and rename some of the keys to
285             # consistent values
286 30         31 $adjacency->{vrf} = $vrf_name; # Add the VRF name
287 30         40 $adjacency->{ip} = delete $adjacency->{'ip-addr-out'};
288 30         37 $adjacency->{ifname} = delete $adjacency->{'intf-out'};
289              
290 30         38 push @ret_arp, $adjacency;
291             }
292             }
293 6         29 return @ret_arp;
294             }
295              
296            
297              
298             =head2 vlans()
299              
300             my @vlan_info = $switch->vlans();
301              
302             Returns a list of HASHREFs containing information on the current layer 2 VLANs configured on the device.
303             This method has no arguments.
304              
305             The data structure returned is as follows:
306              
307             (
308             {
309             'id' => '1',
310             'utf_id' => '1',
311             'name' => 'default',
312             'admin_state' => 'noshutdown',
313             'vlan_state' => 'active'
314             'interfaces' => [
315             'Ethernet1/3-22',
316             'Ethernet1/26-44',
317             'Ethernet1/47-54'
318             ],
319             },
320             )
321              
322              
323             =cut
324              
325             sub vlans {
326 0     0 1 0 my $self = shift;
327              
328 0         0 my $ret = $self->_send_cmd("show vlan");
329 0         0 _fixup_returned_structure($ret);
330 0         0 return _modify_returned_vlan_structure($ret);
331             }
332              
333             sub _modify_returned_vlan_structure {
334 0     0   0 my $vlan_structure = shift;
335 0         0 my @ret_vlans;
336              
337 0         0 for my $vlan (@{ $vlan_structure->{vlanbrief} }) {
  0         0  
338 0         0 my @vlan_keys = (
339             ['vlanshowbr-shutstate', 'admin_state'],
340             ['vlanshowbr-vlanstate', 'vlan_state'],
341             ['vlanshowbr-vlanid', 'id'],
342             ['vlanshowbr-vlanname', 'name'],
343             ['vlanshowplist-ifidx', 'interfaces'],
344             ['vlanshowbr-vlanid-utf', 'utf_id'],
345             );
346              
347             # Rename the keys
348 0         0 my %renamed_vlan = map { $_->[1] => $vlan->{$_->[0]} } @vlan_keys;
  0         0  
349              
350             # The interfaces are in comma seperated form - split this out into an array
351 0         0 my @split_interfaces = split ',', $renamed_vlan{interfaces};
352 0         0 $renamed_vlan{interfaces} = \@split_interfaces;
353              
354 0         0 push @ret_vlans, \%renamed_vlan;
355             }
356 0         0 return @ret_vlans;
357             }
358              
359             =head2 physical_interfaces()
360              
361             my @interface_info = $switch->physical_interfaces();
362              
363             Returns a list of HASHREFs containing information on the physical interfacee state.
364              
365             The structure returned is as follows:
366            
367             (
368             {
369             'name' => 'Ethernet1/5',
370             'mac' => '84b8.020f.15d4',
371             'speed' => 'auto-speed',
372             'admin_state' => 'up',
373             'op_state' => 'down',
374             'fps_in' => '0',
375             'fps_out' => '0',
376             'bps_in' => '0',
377             'bps_out' => '0',
378             'bytes_in' => 0,
379             'bytes_out' => 0,
380             'packets_in' => 0,
381             'packets_out' => 0,
382             'last_link_flap' => 'never'
383             'errors' => {
384             'ignored_frames' => '0',
385             'bad_protocol' => '0',
386             'runts' => 0,
387             'crc_errors' => '0',
388             'no_carrier' => '0',
389             'in_errors' => '0',
390             'collisions' => '0',
391             'lost_carrier' => '0',
392             'dribbles' => '0',
393             'overruns' => '0',
394             'bad_frames' => '0',
395             'no_buffer' => 0,
396             'late_collisions' => '0',
397             'underruns' => '0',
398             'out_errors' => '0',
399             'babbles' => '0',
400             'out_discards' => '0',
401             'in_discards' => '0'
402             },
403             }
404             )
405              
406             =cut
407              
408             sub physical_interfaces {
409 11     11 1 10 my $self = shift;
410              
411 11         21 my $ret = $self->_send_cmd('show interfaces');
412 11         2319 _fixup_returned_structure($ret);
413 11         22 return _modify_returned_phy_int_structure($ret);
414             }
415              
416             sub _modify_returned_phy_int_structure {
417 11     11   6 my $int_structure = shift;
418 11         8 my @ret_interfaces;
419              
420 11         10 for my $interface (@{ $int_structure->{interface} }) {
  11         16  
421             # The following structure is used to rename the keys
422             # in the returned structure to better names.
423 66         297 my @eth_info_keys = (
424             ['interface', 'name'],
425             ['admin_state', 'admin_state'],
426             ['state', 'op_state'],
427             ['eth_inbytes', 'bytes_in'],
428             ['eth_outbytes', 'bytes_out'],
429             ['eth_inpkts', 'packets_in'],
430             ['eth_outpkts', 'packets_out'],
431             ['eth_outrate1_bits', 'bps_out'],
432             ['eth_outrate1_pkts', 'fps_out'],
433             ['eth_inrate1_bits', 'bps_in'],
434             ['eth_inrate1_pkts', 'fps_in'],
435             ['eth_bia_addr', 'mac'],
436             ['eth_speed', 'speed'],
437             ['eth_link_flapped', 'last_link_flap'],
438             );
439              
440 66         347 my @eth_err_keys = (
441             ['eth_bad_eth', 'bad_frames'],
442             ['eth_overrun', 'overruns'],
443             ['eth_runts', 'runts'],
444             ['eth_nobuf', 'no_buffer'],
445             ['eth_lostcarrier', 'lost_carrier'],
446             ['eth_ignored', 'ignored_frames'],
447             ['eth_coll', 'collisions'],
448             ['eth_crc', 'crc_errors'],
449             ['eth_nocarrier', 'no_carrier'],
450             ['eth_outerr', 'out_errors'],
451             ['eth_inerr', 'in_errors'],
452             ['eth_indiscard', 'in_discards'],
453             ['eth_outdiscard', 'out_discards'],
454             ['eth_babbles', 'babbles'],
455             ['eth_latecoll', 'late_collisions'],
456             ['eth_underrun', 'underruns'],
457             ['eth_dribble', 'dribbles'],
458             ['eth_bad_proto', 'bad_protocol'],
459             );
460              
461             # We extract out the relevant keys and translate them to better names
462             # We also move the interface errors to a sub-tree
463 66   50     79 my %renamed_info = map { $_->[1] => $interface->{$_->[0] // ''} } @eth_info_keys;
  924         2051  
464 66         139 my %renamed_errors = map { $_->[1] => $interface->{$_->[0]} } @eth_err_keys;
  1188         1843  
465 66         177 $renamed_info{errors} = \%renamed_errors;
466            
467 66         272 push @ret_interfaces, \%renamed_info;
468             }
469              
470 11         659 return @ret_interfaces;
471             }
472              
473             =head2 bgp_peers( %options )
474              
475             my @bgp_peers = $switch->bgp_peers(
476             vrf => '',
477             af => 'ipv4 | ipv6'
478             );
479              
480             This function retrieves information on the BGP peers configured on the device. If B<vrf> is not specified,
481             the peer info relating to the default routing table is retrieved. If B<vrf> is specified as 'all', peer info
482             from all VRFs (including the global routing table) is returned.
483              
484             The structure returned is as follows:
485              
486             (
487             {
488             'capabilitiessent' => '0',
489             'state' => 'Idle',
490             'updatesrecvd' => '0',
491             'up' => 'false',
492             'index' => '1',
493             'updatessent' => '0',
494             'keepaliverecvd' => '0',
495             'holdtime' => '180',
496             'resettime' => 'never',
497             'neighbor' => '1.1.1.1',
498             'lastread' => 'never',
499             'opensrecvd' => '0',
500             'peerresettime' => 'never',
501             'bytesrecvd' => '0',
502             'notificationsrcvd' => '0',
503             'msgrecvd' => '0',
504             'rtrefreshrecvd' => '0',
505             'rtrefreshsent' => '0',
506             'version' => '4',
507             'firstkeepalive' => 'false',
508             'remoteas' => '65001',
509             'keepalivesent' => '0',
510             'notificationssent' => '0',
511             'bytessent' => '0',
512             'remote-id' => '0.0.0.0',
513             'keepalivetime' => '60',
514             'peerresetreason' => 'No error',
515             'restarttime' => '00:00:01',
516             'lastwrite' => 'never',
517             'connsestablished' => '0',
518             'connsdropped' => '0',
519             'resetreason' => 'No error',
520             'recvbufbytes' => '0',
521             'connattempts' => '0',
522             'elapsedtime' => '00:05:24',
523             'sentbytesoutstanding' => '0',
524             'msgsent' => '0',
525             'openssent' => '0'
526             },
527             )
528              
529             =cut
530              
531             sub bgp_peers {
532 1     1 1 2 my $self = shift;
533 1         19 my %args = validate(@_,
534             {
535             vrf => { default => 'default', type => SCALAR | UNDEF },
536             af => { default => 'ipv4', type => SCALAR | UNDEF, regex => qr{(ipv4|ipv6)} }
537             }
538             );
539            
540 1         17 my $user_args = "vrf $args{vrf} $args{af}";
541              
542 1         4 my $ret = $self->_send_cmd("show bgp $user_args neighbors");
543 1         78 _fixup_returned_structure($ret);
544 1         3 return _modify_returned_bgp_peer_structure($ret);
545             }
546              
547             sub _modify_returned_bgp_peer_structure {
548 1     1   1 my $bgp_peer_structure = shift;
549 1         1 my @ret_bgp_peers;
550              
551 1         2 for my $bgp_peer (@{ $bgp_peer_structure->{neighbor} }) {
  1         2  
552 1         7 my @extracted_keys = (
553             'up',
554             'state',
555             'resettime',
556             'resetreason',
557             'peerresetreason',
558             'neighbor',
559             'remoteas',
560             'remote-id',
561             'version',
562             'holdtime',
563             'keepalivetime',
564             'connsdropped',
565             'connsestablished',
566             'restarttime',
567             'firstkeepalive',
568             'sentbytesoutstanding',
569             'msgsent',
570             'msgrecvd',
571             'bytessent',
572             'bytesrecvd',
573             'updatessent',
574             'updatesrecvd',
575             'openssent',
576             'opensrecvd',
577             'notificationssent',
578             'notificationsrcvd',
579             'rtrefreshsent',
580             'keepaliverecvd',
581             'connattempts',
582             'lastread',
583             'rtrefreshrecvd',
584             'index',
585             'peerresettime',
586             'recvbufbytes',
587             'capabilitiessent',
588             'elapsedtime',
589             'lastwrite',
590             'keepalivesent',
591             );
592              
593 1         1 my %peer_info = %{ $bgp_peer }{ @extracted_keys };
  1         28  
594 1         5 push @ret_bgp_peers, \%peer_info;
595             }
596 1         12 return @ret_bgp_peers;
597             }
598              
599              
600             =head2 bgp_rib( %options )
601              
602             my $bgp_rib_ref = $switch->bgp_rib(
603             vrf => '',
604             af => 'ipv4 | ipv6'
605             );
606              
607             Returns information on the BGP Routinng Information Base (RIB). If B<vrf =>> is not specified, the global routing table is returned.
608             If B<vrf =>> is set to 'all', the RIB for all VRFs, including the global routing table, is returned.
609              
610             If B<af =>> is not specied, the RIB for the IPv4 address family is returned.
611              
612             The structure returned is as follows:
613              
614             (
615             {
616             'prefix' => '1.2.3.0/24',
617             'paths' => [
618             {
619             'pathnr' => '0',
620             'ipnexthop' => '0.0.0.0',
621             'weight' => '32768',
622             'best' => '>',
623             'metric' => '',
624             'origin' => 'i',
625             'aspath' => '',
626             'localpref' => '100',
627             'type' => 'l',
628             'status' => '*'
629             }
630             ],
631             'vrf' => 'default'
632             },
633             )
634              
635              
636             =cut
637             sub bgp_rib {
638 4     4 1 5 my $self = shift;
639 4         52 my %args = validate(@_,
640             {
641             vrf => { default => 'default', type => SCALAR | UNDEF },
642             af => { default => 'ipv4', type => SCALAR | UNDEF, regex => qr{(ipv4|ipv6)} }
643             }
644             );
645              
646 4         58 my ($vrf, $addr_family);
647              
648 4         10 my %address_families = (
649             ipv4 => "ip unicast",
650             ipv6 => "ipv6 unicast",
651             all => "all",
652             );
653              
654 4         6 $vrf = "vrf ".$args{vrf};
655 4         6 $addr_family = $address_families{ $args{af} };
656              
657 4         11 my $ret = $self->_send_cmd("show bgp $vrf $addr_family");
658 4         146 _fixup_returned_structure($ret);
659 4         6 return _modify_returned_bgp_rib_structure($ret);
660             }
661              
662             sub _modify_returned_bgp_rib_structure {
663 4     4   5 my $bgp_structure = shift;
664 4         3 my @ret_bgp_rib;
665              
666 4         3 for my $vrf (@{ $bgp_structure->{vrf} }) {
  4         7  
667 4         6 my $vrf_name = $vrf->{'vrf-name-out'};
668              
669 4         18 for my $afi (@{ $vrf->{afi} }) {
  4         6  
670 4         2 for my $safi (@{ $afi->{safi} }) {
  4         5  
671 4         3 for my $rd (@{ $safi->{rd} }) {
  4         5  
672 4         4 for my $prefix (@{ $rd->{prefix} }) {
  4         6  
673 8         6 my %bgp_prefix;
674              
675 8         11 $bgp_prefix{vrf} = $vrf_name;
676 8         12 $bgp_prefix{prefix} = $prefix->{ipprefix};
677 8         8 $bgp_prefix{paths} = $prefix->{path};
678              
679              
680 8         21 push @ret_bgp_rib, \%bgp_prefix;
681             }
682             }
683             }
684             }
685             }
686 4         27 return @ret_bgp_rib;
687             }
688              
689              
690             =head2 cdp_neighbours()
691              
692             Returns a list of HASHREFs containing the current CDP information visible on the switch.
693              
694             The structure returned is as follows:
695              
696             (
697             {
698             'platform_id' => 'cisco WS-C2960X-24TD-L',
699             'intf_id' => 'mgmt0',
700             'port_id' => 'GigabitEthernet1/0/3',
701             'ifindex' => 83886080,
702             'ttl' => 179,
703             'device_id' => 'hostname',
704             'capability' => 'IGMP_cnd_filtering'
705             }
706             )
707              
708             =cut
709              
710             sub cdp_neighbours {
711 0     0 1 0 my $self = shift;
712              
713 0         0 my $ret = $self->_send_cmd("show cdp neighbors detail");
714 0         0 _fixup_returned_structure($ret);
715 0         0 return _modify_returned_cdp_structure($ret);
716             }
717              
718             sub _modify_returned_cdp_structure {
719 0     0   0 my $cdp_structure = shift;
720              
721 0         0 return @{ $cdp_structure->{cdp_neighbor_brief_info} };
  0         0  
722             }
723              
724              
725             sub _send_cmd {
726 0     0   0 my $self = shift;
727 0         0 my $command = shift;
728              
729 0         0 my $json = $self->_gen_cmd($command);
730 0 0       0 if ($self->debug) {
731 0         0 say "[DEBUG]: $json";
732             }
733 0         0 $self->http_request()->content($json);
734 0         0 my $response = $self->user_agent()->request( $self->http_request );
735 0         0 return $self->_check_and_return_response($response)->{result}->{body};
736             }
737              
738             sub _gen_cmd {
739 0     0   0 my $self = shift;
740 0         0 my $command = shift;
741              
742 0         0 my $json_ref = [{
743             jsonrpc => "2.0",
744             method => "cli",
745             params => {
746             cmd => "",
747             version => 1,
748             },
749             id => "1",
750             }];
751              
752 0         0 $json_ref->[0]->{params}->{cmd} = $command;
753 0         0 return encode_json($json_ref);
754             }
755              
756             sub _check_and_return_response {
757 0     0   0 my $self = shift;
758 0         0 my $response = shift;
759 0         0 my $json_content;
760             my $json_error_code;
761 0         0 my $json_error_msg;
762            
763 0         0 $json_content = eval { decode_json($response->content) };
  0         0  
764              
765 0 0       0 if ($json_content->{error}) {
766 0   0     0 $json_error_code = $json_content->{error}->{code} // "<No Code>";
767 0   0     0 $json_error_msg = $json_content->{error}->{data}->{msg} // "";
768             }
769              
770 0   0     0 $json_error_msg //= "";
771              
772 0 0       0 croak "HTTP Error (".$response->code()."): ".$response->status_line()." ".$json_error_msg if $response->is_error();
773              
774 0 0       0 croak "NX-API Error($json_error_code}): $json_error_msg" if $json_content->{error};
775              
776 0         0 return $json_content;
777             }
778              
779             sub _fixup_returned_structure {
780 589     589   403 my $structure_ref = shift;
781              
782             # Find all of the keys which a prefixed with 'TABLE_'
783 589         372 my @table_keys = grep { m{TABLE_}sxm } keys %{ $structure_ref };
  6338         5938  
  589         1119  
784              
785 589 100       1386 return $structure_ref if (@table_keys == 0);
786              
787 231         230 for my $table_key (@table_keys) {
788             # Rename the TABLE_ key
789 231         535 my ($new_key) = $table_key =~ m{TABLE_(\w+)}sxm;
790              
791             # Generate the ROW_ key name
792 231         235 my $row_key = "ROW_".$new_key;
793              
794             # If the row is a HASHREF, it means there's only one element.
795             # We change this to an ARRAYREF of one HASHREF so that the table
796             # is always an ARRAYREF, even if it's only one element.
797 231 100       385 if (ref($structure_ref->{ $table_key }->{ $row_key }) eq 'HASH') {
798 65         110 $structure_ref->{ $new_key } = [ $structure_ref->{ $table_key }->{ $row_key } ];
799             } else {
800 166         197 $structure_ref->{ $new_key } = $structure_ref->{ $table_key }->{ $row_key };
801             }
802              
803 231         298 delete $structure_ref->{ $table_key };
804              
805             # Go through each hash item and recursively fix them up
806 231         154 for my $row (@{ $structure_ref->{ $new_key } }) {
  231         285  
807 555         596 _fixup_returned_structure($row);
808             }
809             }
810             }
811            
812              
813              
814             =head1 AUTHOR
815              
816             Greg Foletta, C<< <greg at foletta.org> >>
817              
818             =head1 BUGS
819              
820             Please report any bugs or feature requests to C<bug-switch-nxapi at rt.cpan.org>, or through
821             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Switch-NXAPI>. I will be notified, and then you'll
822             automatically be notified of progress on your bug as I make changes.
823              
824              
825              
826              
827             =head1 SUPPORT
828              
829             You can find documentation for this module with the perldoc command.
830              
831             perldoc Device::Cisco::NXAPI
832              
833              
834             You can also look for information at:
835              
836             =over 4
837              
838             =item * RT: CPAN's request tracker (report bugs here)
839              
840             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Switch-NXAPI>
841              
842             =item * AnnoCPAN: Annotated CPAN documentation
843              
844             L<http://annocpan.org/dist/Switch-NXAPI>
845              
846             =item * CPAN Ratings
847              
848             L<http://cpanratings.perl.org/d/Switch-NXAPI>
849              
850             =item * Search CPAN
851              
852             L<http://search.cpan.org/dist/Switch-NXAPI/>
853              
854             =back
855              
856              
857             =head1 ACKNOWLEDGEMENTS
858              
859              
860             =head1 LICENSE AND COPYRIGHT
861              
862             Copyright 2016 Greg Foletta.
863              
864             This program is free software; you can redistribute it and/or modify it
865             under the terms of the the Artistic License (2.0). You may obtain a
866             copy of the full license at:
867              
868             L<http://www.perlfoundation.org/artistic_license_2_0>
869              
870             Any use, modification, and distribution of the Standard or Modified
871             Versions is governed by this Artistic License. By using, modifying or
872             distributing the Package, you accept this license. Do not use, modify,
873             or distribute the Package, if you do not accept this license.
874              
875             If your Modified Version has been derived from a Modified Version made
876             by someone other than you, you are nevertheless required to ensure that
877             your Modified Version complies with the requirements of this license.
878              
879             This license does not grant you the right to use any trademark, service
880             mark, tradename, or logo of the Copyright Holder.
881              
882             This license includes the non-exclusive, worldwide, free-of-charge
883             patent license to make, have made, use, offer to sell, sell, import and
884             otherwise transfer the Package with respect to any patent claims
885             licensable by the Copyright Holder that are necessarily infringed by the
886             Package. If you institute patent litigation (including a cross-claim or
887             counterclaim) against any party alleging that the Package constitutes
888             direct or contributory patent infringement, then this Artistic License
889             to you shall terminate on the date that such litigation is filed.
890              
891             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
892             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
893             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
894             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
895             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
896             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
897             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
898             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
899              
900              
901             =cut
902              
903             1; # End of Device::Cisco::NXAPI