summaryrefslogtreecommitdiffstats
path: root/spec/unit/util/network_device/cisco/device_spec.rb
blob: 31aec920ee96f5217c63d5884747a1397c63059c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
#!/usr/bin/env ruby

require File.dirname(__FILE__) + '/../../../../spec_helper'

require 'puppet/util/network_device/cisco/device'

describe Puppet::Util::NetworkDevice::Cisco::Device do
  before(:each) do
    @transport = stub_everything 'transport', :is_a? => true, :command => ""
    @cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23/")
    @cisco.transport = @transport
  end

  describe "when creating the device" do
    it "should find the enable password from the url" do
      cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23/?enable=enable_password")
      cisco.enable_password.should == "enable_password"
    end

    it "should find the enable password from the options" do
      cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23/?enable=enable_password", :enable_password => "mypass")
      cisco.enable_password.should == "mypass"
    end
  end

  describe "when connecting to the physical device" do
    it "should connect to the transport" do
      @transport.expects(:connect)
      @cisco.command
    end

    it "should attempt to login" do
      @cisco.expects(:login)
      @cisco.command
    end

    it "should tell the device to not page" do
      @transport.expects(:command).with("terminal length 0")
      @cisco.command
    end

    it "should enter the enable password if returned prompt is not privileged" do
      @transport.stubs(:command).yields("Switch>").returns("")
      @cisco.expects(:enable)
      @cisco.command
    end

    it "should find device capabilities" do
      @cisco.expects(:find_capabilities)
      @cisco.command
    end

    it "should execute given command" do
      @transport.expects(:command).with("mycommand")
      @cisco.command("mycommand")
    end

    it "should yield to the command block if one is provided" do
      @transport.expects(:command).with("mycommand")
      @cisco.command do |c|
        c.command("mycommand")
      end
    end

    it "should close the device transport" do
      @transport.expects(:close)
      @cisco.command
    end

    describe "when login in" do
      it "should not login if transport handles login" do
        @transport.expects(:handles_login?).returns(true)
        @transport.expects(:command).never
        @transport.expects(:expect).never
        @cisco.login
      end

      it "should send username if one has been provided" do
        @transport.expects(:command).with("user", :prompt => /^Password:/)
        @cisco.login
      end

      it "should send password after the username" do
        @transport.expects(:command).with("user", :prompt => /^Password:/)
        @transport.expects(:command).with("password")
        @cisco.login
      end

      it "should expect the Password: prompt if no user was sent" do
        @cisco.url.user = ''
        @transport.expects(:expect).with(/^Password:/)
        @transport.expects(:command).with("password")
        @cisco.login
      end
    end

    describe "when entering enable password" do
      it "should raise an error if no enable password has been set" do
        @cisco.enable_password = nil
        lambda{ @cisco.enable }.should raise_error
      end

      it "should send the enable command and expect an enable prompt" do
        @cisco.enable_password = 'mypass'
        @transport.expects(:command).with("enable", :prompt => /^Password:/)
        @cisco.enable
      end

      it "should send the enable password" do
        @cisco.enable_password = 'mypass'
        @transport.stubs(:command).with("enable", :prompt => /^Password:/)
        @transport.expects(:command).with("mypass")
        @cisco.enable
      end
    end
  end

  describe "when finding network device capabilities" do
    it "should try to execute sh vlan brief" do
      @transport.expects(:command).with("sh vlan brief").returns("")
      @cisco.find_capabilities
    end

    it "should detect errors" do
      @transport.stubs(:command).with("sh vlan brief").returns(<<eos)
Switch#sh vlan brief  
% Ambiguous command:  "sh vlan brief"
Switch#
eos

      @cisco.find_capabilities
      @cisco.should_not be_support_vlan_brief
    end
  end


  {
    "Fa 0/1" => "FastEthernet0/1",
    "Fa0/1" => "FastEthernet0/1",
    "FastEth 0/1" => "FastEthernet0/1",
    "Gi1" => "GigEthernet1",
    "Di9" => "Dialer9",
    "Ethernet 0/0/1" => "Ethernet0/0/1",
    "E0" => "Ethernet0",
    "ATM 0/1.1" => "ATM0/1.1",
    "VLAN99" => "VLAN99"
  }.each do |input,expected|
    it "should canonicalize #{input} to #{expected}" do
      @cisco.canonalize_ifname(input).should == expected
    end
  end

  describe "when updating device vlans" do
    describe "when removing a vlan" do
      it "should issue the no vlan command" do
        @transport.expects(:command).with("no vlan 200")
        @cisco.update_vlan("200", {:ensure => :present, :name => "200"}, { :ensure=> :absent})
      end
    end

    describe "when updating a vlan" do
      it "should issue the vlan command to enter global vlan modifications" do
        @transport.expects(:command).with("vlan 200")
        @cisco.update_vlan("200", {:ensure => :present, :name => "200"}, { :ensure=> :present, :name => "200"})
      end

      it "should issue the name command to modify the vlan description" do
        @transport.expects(:command).with("name myvlan")
        @cisco.update_vlan("200", {:ensure => :present, :name => "200"}, { :ensure=> :present, :name => "200", :description => "myvlan"})
      end
    end
  end

  describe "when parsing interface" do

    it "should parse interface output" do
      @cisco.expects(:parse_interface).returns({ :ensure => :present })

      @cisco.interface("FastEthernet0/1").should == { :ensure => :present }
    end

    it "should parse trunking and merge results" do
      @cisco.stubs(:parse_interface).returns({ :ensure => :present })
      @cisco.expects(:parse_trunking).returns({ :native_vlan => "100" })

      @cisco.interface("FastEthernet0/1").should == { :ensure => :present, :native_vlan => "100" }
    end

    it "should return an absent interface if parse_interface returns nothing" do
      @cisco.stubs(:parse_interface).returns({})

      @cisco.interface("FastEthernet0/1").should == { :ensure => :absent }
    end

    it "should parse ip address information and merge results" do
      @cisco.stubs(:parse_interface).returns({ :ensure => :present })
      @cisco.expects(:parse_interface_config).returns({ :ipaddress => [24,IPAddr.new('192.168.0.24'), nil] })

      @cisco.interface("FastEthernet0/1").should == { :ensure => :present, :ipaddress => [24,IPAddr.new('192.168.0.24'), nil] }
    end

    it "should parse the sh interface command" do
      @transport.stubs(:command).with("sh interface FastEthernet0/1").returns(<<eos)
Switch#sh interfaces FastEthernet 0/1
FastEthernet0/1 is down, line protocol is down 
  Hardware is Fast Ethernet, address is 00d0.bbe2.19c1 (bia 00d0.bbe2.19c1)
  MTU 1500 bytes, BW 100000 Kbit, DLY 100 usec, 
     reliability 255/255, txload 1/255, rxload 1/255
  Encapsulation ARPA, loopback not set
  Keepalive not set
  Auto-duplex , Auto Speed , 100BaseTX/FX
  ARP type: ARPA, ARP Timeout 04:00:00
  Last input never, output 5d04h, output hang never
  Last clearing of "show interface" counters never
  Queueing strategy: fifo
  Output queue 0/40, 0 drops; input queue 0/75, 0 drops
  5 minute input rate 0 bits/sec, 0 packets/sec
  5 minute output rate 0 bits/sec, 0 packets/sec
     580 packets input, 54861 bytes
     Received 6 broadcasts, 0 runts, 0 giants, 0 throttles
     0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored
     0 watchdog, 1 multicast
     0 input packets with dribble condition detected
     845 packets output, 80359 bytes, 0 underruns
     0 output errors, 0 collisions, 1 interface resets
     0 babbles, 0 late collision, 0 deferred
     0 lost carrier, 0 no carrier
     0 output buffer failures, 0 output buffers swapped out
Switch#
eos

      @cisco.parse_interface("FastEthernet0/1").should == { :ensure => :absent, :duplex => :auto, :speed => :auto }
    end

    it "should be able to parse the sh vlan brief command output" do
      @cisco.stubs(:support_vlan_brief?).returns(true)
      @transport.stubs(:command).with("sh vlan brief").returns(<<eos)
Switch#sh vlan brief
VLAN Name                             Status    Ports
---- -------------------------------- --------- -------------------------------
1    default                          active    Fa0/3, Fa0/4, Fa0/5, Fa0/6,
                                                Fa0/7, Fa0/8, Fa0/9, Fa0/10,
                                                Fa0/11, Fa0/12, Fa0/13, Fa0/14,
                                                Fa0/15, Fa0/16, Fa0/17, Fa0/18,
                                                Fa0/23, Fa0/24
10   VLAN0010                         active    
100  management                       active    Fa0/1, Fa0/2
Switch#
eos

      @cisco.parse_vlans.should == {"100"=>{:status=>"active", :interfaces=>["FastEthernet0/1", "FastEthernet0/2"], :description=>"management", :name=>"100"}, "1"=>{:status=>"active", :interfaces=>["FastEthernet0/3", "FastEthernet0/4", "FastEthernet0/5", "FastEthernet0/6", "FastEthernet0/7", "FastEthernet0/8", "FastEthernet0/9", "FastEthernet0/10", "FastEthernet0/11", "FastEthernet0/12", "FastEthernet0/13", "FastEthernet0/14", "FastEthernet0/15", "FastEthernet0/16", "FastEthernet0/17", "FastEthernet0/18", "FastEthernet0/23", "FastEthernet0/24"], :description=>"default", :name=>"1"}, "10"=>{:status=>"active", :interfaces=>[], :description=>"VLAN0010", :name=>"10"}}
    end

    it "should parse trunk switchport information" do
      @transport.stubs(:command).with("sh interface FastEthernet0/21 switchport").returns(<<eos)
Switch#sh interfaces FastEthernet 0/21 switchport
Name: Fa0/21
Switchport: Enabled
Administrative mode: trunk
Operational Mode: trunk
Administrative Trunking Encapsulation: dot1q
Operational Trunking Encapsulation: dot1q
Negotiation of Trunking: Disabled
Access Mode VLAN: 0 ((Inactive))
Trunking Native Mode VLAN: 1 (default)
Trunking VLANs Enabled: ALL
Trunking VLANs Active: 1,10,100
Pruning VLANs Enabled: 2-1001

Priority for untagged frames: 0
Override vlan tag priority: FALSE
Voice VLAN: none
Appliance trust: none
Self Loopback: No
Switch#
eos

      @cisco.parse_trunking("FastEthernet0/21").should == { :mode => :trunk, :encapsulation => :dot1q, :allowed_trunk_vlans=>:all, }
    end

    it "should parse trunk switchport information with allowed vlans" do
      @transport.stubs(:command).with("sh interface GigabitEthernet 0/1 switchport").returns(<<eos)
c2960#sh interfaces GigabitEthernet 0/1 switchport 
Name: Gi0/1
Switchport: Enabled
Administrative Mode: trunk
Operational Mode: trunk
Administrative Trunking Encapsulation: dot1q
Operational Trunking Encapsulation: dot1q
Negotiation of Trunking: On
Access Mode VLAN: 1 (default)
Trunking Native Mode VLAN: 1 (default)
Administrative Native VLAN tagging: enabled
Voice VLAN: none
Administrative private-vlan host-association: none 
Administrative private-vlan mapping: none 
Administrative private-vlan trunk native VLAN: none
Administrative private-vlan trunk Native VLAN tagging: enabled
Administrative private-vlan trunk encapsulation: dot1q
Administrative private-vlan trunk normal VLANs: none
Administrative private-vlan trunk associations: none
Administrative private-vlan trunk mappings: none
Operational private-vlan: none
Trunking VLANs Enabled: 1,99
Pruning VLANs Enabled: 2-1001
Capture Mode Disabled
Capture VLANs Allowed: ALL

Protected: false
Unknown unicast blocked: disabled
Unknown multicast blocked: disabled
Appliance trust: none
c2960#
eos

      @cisco.parse_trunking("GigabitEthernet 0/1").should == { :mode => :trunk, :encapsulation => :dot1q, :allowed_trunk_vlans=>"1,99", }
    end

    it "should parse access switchport information" do
      @transport.stubs(:command).with("sh interface FastEthernet0/1 switchport").returns(<<eos)
Switch#sh interfaces FastEthernet 0/1 switchport  
Name: Fa0/1
Switchport: Enabled
Administrative mode: static access
Operational Mode: static access
Administrative Trunking Encapsulation: isl
Operational Trunking Encapsulation: isl
Negotiation of Trunking: Disabled
Access Mode VLAN: 100 (SHDSL)
Trunking Native Mode VLAN: 1 (default)
Trunking VLANs Enabled: NONE
Pruning VLANs Enabled: NONE

Priority for untagged frames: 0
Override vlan tag priority: FALSE
Voice VLAN: none
Appliance trust: none
Self Loopback: No
Switch#
eos

      @cisco.parse_trunking("FastEthernet0/1").should == { :mode => :access, :native_vlan => "100" }
    end

    it "should parse ip addresses" do
      @transport.stubs(:command).with("sh running-config interface Vlan 1 | begin interface").returns(<<eos)
router#sh running-config interface Vlan 1 | begin interface
interface Vlan1
 description $ETH-SW-LAUNCH$$INTF-INFO-HWIC 4ESW$$FW_INSIDE$
 ip address 192.168.0.24 255.255.255.0 secondary
 ip address 192.168.0.1 255.255.255.0
 ip access-group 100 in
 no ip redirects
 no ip proxy-arp
 ip nbar protocol-discovery
 ip dns view-group dow
 ip nat inside
 ip virtual-reassembly
 ip route-cache flow
 ipv6 address 2001:7A8:71C1::/64 eui-64
 ipv6 enable
 ipv6 traffic-filter DENY-ACL6 out
 ipv6 mtu 1280
 ipv6 nd prefix 2001:7A8:71C1::/64
 ipv6 nd ra interval 60
 ipv6 nd ra lifetime 180
 ipv6 verify unicast reverse-path
 ipv6 inspect STD6 out
end

router#
eos
      @cisco.parse_interface_config("Vlan 1").should == {:ipaddress=>[[24, IPAddr.new('192.168.0.24'), 'secondary'],
                                                                      [24, IPAddr.new('192.168.0.1'), nil],
                                                                      [64, IPAddr.new('2001:07a8:71c1::'), "eui-64"]]}
    end

    it "should parse etherchannel membership" do
      @transport.stubs(:command).with("sh running-config interface Gi0/17 | begin interface").returns(<<eos)
c2960#sh running-config interface Gi0/17 | begin interface
interface GigabitEthernet0/17
 description member of Po1
 switchport mode access
 channel-protocol lacp
 channel-group 1 mode passive
 spanning-tree portfast
 spanning-tree bpduguard enable
end

c2960#
eos
      @cisco.parse_interface_config("Gi0/17").should == {:etherchannel=>"1"}
    end
  end
end

# static access
# Switch#sh interfaces FastEthernet 0/1 switchport  
# Name: Fa0/1
# Switchport: Enabled
# Administrative mode: static access
# Operational Mode: static access
# Administrative Trunking Encapsulation: isl
# Operational Trunking Encapsulation: isl
# Negotiation of Trunking: Disabled
# Access Mode VLAN: 100 (SHDSL)
# Trunking Native Mode VLAN: 1 (default)
# Trunking VLANs Enabled: NONE
# Pruning VLANs Enabled: NONE
# 
# Priority for untagged frames: 0
# Override vlan tag priority: FALSE
# Voice VLAN: none
# Appliance trust: none
# Self Loopback: No
# Switch#

# c2960#sh interfaces GigabitEthernet 0/1 switchport 
# Name: Gi0/1
# Switchport: Enabled
# Administrative Mode: trunk
# Operational Mode: trunk
# Administrative Trunking Encapsulation: dot1q
# Operational Trunking Encapsulation: dot1q
# Negotiation of Trunking: On
# Access Mode VLAN: 1 (default)
# Trunking Native Mode VLAN: 1 (default)
# Administrative Native VLAN tagging: enabled
# Voice VLAN: none
# Administrative private-vlan host-association: none 
# Administrative private-vlan mapping: none 
# Administrative private-vlan trunk native VLAN: none
# Administrative private-vlan trunk Native VLAN tagging: enabled
# Administrative private-vlan trunk encapsulation: dot1q
# Administrative private-vlan trunk normal VLANs: none
# Administrative private-vlan trunk associations: none
# Administrative private-vlan trunk mappings: none
# Operational private-vlan: none
# Trunking VLANs Enabled: 1,99
# Pruning VLANs Enabled: 2-1001
# Capture Mode Disabled
# Capture VLANs Allowed: ALL
# 
# Protected: false
# Unknown unicast blocked: disabled
# Unknown multicast blocked: disabled
# Appliance trust: none
# c2960#

# c2960#sh interfaces GigabitEthernet 0/2 switchport 
# Name: Gi0/2
# Switchport: Enabled
# Administrative Mode: static access
# Operational Mode: static access
# Administrative Trunking Encapsulation: dot1q
# Operational Trunking Encapsulation: native
# Negotiation of Trunking: Off
# Access Mode VLAN: 99 (MGMT)
# Trunking Native Mode VLAN: 1 (default)
# Administrative Native VLAN tagging: enabled
# Voice VLAN: none
# Administrative private-vlan host-association: none 
# Administrative private-vlan mapping: none 
# Administrative private-vlan trunk native VLAN: none
# Administrative private-vlan trunk Native VLAN tagging: enabled
# Administrative private-vlan trunk encapsulation: dot1q
# Administrative private-vlan trunk normal VLANs: none
# Administrative private-vlan trunk associations: none
# Administrative private-vlan trunk mappings: none
# Operational private-vlan: none
# Trunking VLANs Enabled: ALL
# Pruning VLANs Enabled: 2-1001
# Capture Mode Disabled
# Capture VLANs Allowed: ALL
# 
# Protected: false
# Unknown unicast blocked: disabled
# Unknown multicast blocked: disabled
# Appliance trust: none
# c2960#

# c877#sh interfaces FastEthernet 1 switchport 
# Name: Fa1
# Switchport: Enabled
# Administrative Mode: trunk
# Operational Mode: trunk
# Administrative Trunking Encapsulation: dot1q
# Operational Trunking Encapsulation: dot1q
# Negotiation of Trunking: Disabled
# Access Mode VLAN: 0 ((Inactive))
# Trunking Native Mode VLAN: 1 (default)
# Trunking VLANs Enabled: ALL
# Trunking VLANs Active: 1
# Protected: false
# Priority for untagged frames: 0
# Override vlan tag priority: FALSE
# Voice VLAN: none 
# Appliance trust: none


# c2960#sh etherchannel summary 
# Flags:  D - down        P - bundled in port-channel
#         I - stand-alone s - suspended
#         H - Hot-standby (LACP only)
#         R - Layer3      S - Layer2
#         U - in use      f - failed to allocate aggregator
# 
#         M - not in use, minimum links not met
#         u - unsuitable for bundling
#         w - waiting to be aggregated
#         d - default port
# 
# 
# Number of channel-groups in use: 1
# Number of aggregators:           1
# 
# Group  Port-channel  Protocol    Ports
# ------+-------------+-----------+-----------------------------------------------
# 1      Po1(SU)         LACP      Gi0/17(P)   Gi0/18(P)   
# 
# c2960#