blob: aefb729404563f22de493107bc2a19e351cdcedf [file] [log] [blame]
Steve McIntyred6759dd2014-08-12 18:10:00 +01001#! /usr/bin/python
2
3# Copyright 2014 Linaro Limited
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18# MA 02110-1301, USA.
19
20import logging
21import pexpect
22import sys
23import time
24import re
25from common import SwitchDriver
26
27class CiscoCatalyst(SwitchDriver):
28
29 connection = None
Steve McIntyre3f287882014-08-18 19:02:15 +010030
31 _capabilities = [
32 'TrunkWildCardVlans' # Trunk ports are on all VLANs by
33 # default, so we shouldn't need to
34 # bugger with them
35 ]
36
Steve McIntyred6759dd2014-08-12 18:10:00 +010037 # Regexp of expected hardware information - fail if we don't see
38 # this
Steve McIntyre3f287882014-08-18 19:02:15 +010039 _expected_descr_re = re.compile('WS-C\S+-\d+P')
Steve McIntyred6759dd2014-08-12 18:10:00 +010040
41 logfile = sys.stderr
42 logfile = None
43
44 def __init__(self, switch_hostname, switch_telnetport=23):
45 self.exec_string = "/usr/bin/telnet %s %d" % (switch_hostname, switch_telnetport)
46
47 ################################
48 ### Switch-level API functions
49 ################################
50
51 # Connect to the switch and log in
52 def SwitchConnect(self, username, password, enablepassword):
53 logging.debug("Connecting to Switch with: %s" % self.exec_string)
54 self.connection = pexpect.spawn(self.exec_string, logfile = self.logfile)
55 self._login(username, password, enablepassword)
56
57 # Try to avoid paged output
58 self.connection.setwinsize(132,1000)
59
60 # And grab details about the switch. in case we need it
Steve McIntyre3f287882014-08-18 19:02:15 +010061 self._get_systemdata()
Steve McIntyred6759dd2014-08-12 18:10:00 +010062
63 # And also validate them - make sure we're driving a switch of
64 # the correct model! Also store the serial number
65 descr_regex = re.compile('^cisco\s+(\S+)')
66 sn_regex = re.compile('System serial number\s+:\s+(\S+)')
67 descr = ""
68
Steve McIntyre3f287882014-08-18 19:02:15 +010069 for line in self._systemdata:
Steve McIntyred6759dd2014-08-12 18:10:00 +010070 match = descr_regex.match(line)
71 if match:
72 descr = match.group(1)
73 match = sn_regex.match(line)
74 if match:
75 self.serial_number = match.group(1)
76
77 print "serial number is %s" % self.serial_number
78 print "system description is %s" % descr
79
Steve McIntyre3f287882014-08-18 19:02:15 +010080 if not self._expected_descr_re.match(descr):
Steve McIntyred6759dd2014-08-12 18:10:00 +010081 raise IOError("Switch %s not recognised by this driver: abort" % descr)
82
83 # Now build a list of our ports, for later sanity checking
Steve McIntyre3f287882014-08-18 19:02:15 +010084 self._ports = self._get_port_names()
85 if len(self._ports) < 4:
Steve McIntyred6759dd2014-08-12 18:10:00 +010086 raise IOError("Not enough ports detected - problem!")
87
88 # Log out of the switch and drop the connection and all state
89 def SwitchDisconnect(self):
90 self._logout()
91 logging.debug("Closing connection: %s" % self.connection)
92 self.connection.close(True)
93 del(self)
94
95 # Save the current running config into flash - we want config to
96 # remain across reboots
97 def SwitchSaveRunningConfig(self):
98 self._cli("copy running-config startup-config")
Steve McIntyree1bf11a2014-08-14 17:56:25 +010099 self.connection.expect("startup-config")
100 self._cli("startup-config")
101 self.connection.expect("OK")
Steve McIntyred6759dd2014-08-12 18:10:00 +0100102
Steve McIntyre3f287882014-08-18 19:02:15 +0100103 # List the capabilities of the switch (and driver) - some things
104 # make no sense to abstract. Returns a dict of strings, each one
105 # describing an extra feature that that higher levels may care
106 # about
107 def SwitchGetCapabilities(self):
108 return self._capabilities
Steve McIntyred6759dd2014-08-12 18:10:00 +0100109
110 ################################
111 ### VLAN API functions
112 ################################
113
114 # Create a VLAN with the specified tag
115 def VlanCreate(self, tag):
116 logging.debug("Creating VLAN %d" % tag)
117 self._configure()
118 self._cli("vlan %d" % tag)
119 self._end_configure()
120
121 # Validate it happened
122 vlans = self.VlanGetList()
123 for vlan in vlans:
124 if vlan == tag:
125 return
126 raise IOError("Failed to create VLAN %d" % tag)
127
128 # Destroy a VLAN with the specified tag
129 def VlanDestroy(self, tag):
130 logging.debug("Destroying VLAN %d" % tag)
131 self._configure()
132 self._cli("no vlan %d" % tag)
133 self._end_configure()
134
135 # Validate it happened
136 vlans = self.VlanGetList()
137 for vlan in vlans:
138 if vlan == tag:
139 raise IOError("Failed to destroy VLAN %d" % tag)
140
141 # Set the name of a VLAN
142 def VlanSetName(self, tag, name):
143 logging.debug("Setting name of VLAN %d to %s" % (tag, name))
144 self._configure()
145 self._cli("vlan %d" % tag)
146 self._cli("name %s" % name)
147 self._end_configure()
148
149 # Validate it happened
150 read_name = self.VlanGetName(tag)
151 if read_name != name:
152 raise IOError("Failed to set name for VLAN %d (name found is \"%s\", not \"%s\")"
153 % (tag, read_name, name))
154
155 # Get a list of the VLAN tags currently registered on the switch
156 def VlanGetList(self):
157 logging.debug("Grabbing list of VLANs")
158 vlans = []
159
160 regex = re.compile('^ *(\d+).*(active)')
161
162 self._cli("show vlan brief")
163 for line in self._read_paged_output():
164 match = regex.match(line)
165 if match:
166 vlans.append(int(match.group(1)))
167 return vlans
168
169 # For a given VLAN tag, ask the switch what the associated name is
170 def VlanGetName(self, tag):
171 logging.debug("Grabbing the name of VLAN %d" % tag)
172 name = None
173 regex = re.compile('^ *\d+\s+(\S+).*(active)')
174 self._cli("show vlan id %d" % tag)
175 for line in self._read_paged_output():
176 match = regex.match(line)
177 if match:
178 name = match.group(1)
179 name.strip()
180 return name
181
182
183 ################################
184 ### Port API functions
Steve McIntyree1bf11a2014-08-14 17:56:25 +0100185 ################################
Steve McIntyred6759dd2014-08-12 18:10:00 +0100186
187 # Set the mode of a port: general or trunk
188 def PortSetMode(self, port, mode):
189 logging.debug("Setting port %s to %s" % (port, mode))
190 if not self._is_port_mode_valid(mode):
191 raise IndexError("Port mode %s is not allowed" % mode)
192 if not self._is_port_name_valid(port):
193 raise IndexError("Port name %s not recognised" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100194
Steve McIntyred6759dd2014-08-12 18:10:00 +0100195 self._configure()
196 self._cli("interface %s" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100197 if mode == "general":
198 self._cli("switchport mode access")
199 else:
200 self._cli("switchport mode dynamic auto")
Steve McIntyred6759dd2014-08-12 18:10:00 +0100201 self._end_configure()
202
203 # Validate it happened
204 read_mode = self.PortGetMode(port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100205
Steve McIntyred6759dd2014-08-12 18:10:00 +0100206 if read_mode != mode:
207 raise IOError("Failed to set mode for port %s" % port)
208
Steve McIntyred6759dd2014-08-12 18:10:00 +0100209 # Get the mode of a port: general or trunk
210 def PortGetMode(self, port):
211 logging.debug("Getting mode of port %s" % port)
212 mode = ''
213 if not self._is_port_name_valid(port):
214 raise IndexError("Port name %s not recognised" % port)
Steve McIntyreb7adc782014-08-13 00:22:21 +0100215 regex = re.compile('Administrative Mode: \S+ (\S+)')
216 self._cli("show interfaces %s switchport" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100217 for line in self._read_paged_output():
218 match = regex.match(line)
219 if match:
220 mode = match.group(1)
Steve McIntyre3f287882014-08-18 19:02:15 +0100221
Steve McIntyreb7adc782014-08-13 00:22:21 +0100222 # Munge the names to match what we expect
Steve McIntyre3f287882014-08-18 19:02:15 +0100223 if mode == "access":
224 return "general"
225 elif mode == "auto":
226 return "trunk"
227 return mode
Steve McIntyred6759dd2014-08-12 18:10:00 +0100228
Steve McIntyre3f287882014-08-18 19:02:15 +0100229 # Set a general port to be in a specified VLAN (tag)
230 def PortSetGeneralVlan(self, port, tag):
231 logging.debug("Setting general port %s to VLAN %d" % (port, tag))
Steve McIntyred6759dd2014-08-12 18:10:00 +0100232 if not self._is_port_name_valid(port):
233 raise IndexError("Port name %s not recognised" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100234 if not (self.PortGetMode(port) == "general"):
235 raise IndexError("Port %s not in general mode" % port)
236
Steve McIntyred6759dd2014-08-12 18:10:00 +0100237 self._configure()
238 self._cli("interface %s" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100239 self._cli("switchport access vlan %d" % tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100240 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100241
Steve McIntyre3f287882014-08-18 19:02:15 +0100242 # Finally, validate things worked
243 read_vlan = int(self.PortGetGeneralVlan(port))
Steve McIntyred6759dd2014-08-12 18:10:00 +0100244 if read_vlan != tag:
Steve McIntyre3f287882014-08-18 19:02:15 +0100245 raise IOError("Failed to move general port %d to VLAN %d - got VLAN %d instead"
Steve McIntyred6759dd2014-08-12 18:10:00 +0100246 % (port, tag, read_vlan))
247
Steve McIntyred6759dd2014-08-12 18:10:00 +0100248 # Add a trunk port to a specified VLAN (tag)
249 def PortAddTrunkToVlan(self, port, tag):
250 logging.debug("Adding trunk port %s to VLAN %d" % (port, tag))
251 if not self._is_port_name_valid(port):
252 raise IndexError("Port name %s not recognised" % port)
253 if not (self.PortGetMode(port) == "trunk"):
254 raise IndexError("Port %s not in trunk mode" % port)
255 self._configure()
256 self._cli("interface %s" % port)
257 self._cli("switchport trunk allowed vlan add %d" % tag)
258 self._end_configure()
259
260 # Validate it happened
261 read_vlans = self.PortGetTrunkVlanList(port)
262 for vlan in read_vlans:
Steve McIntyre3f287882014-08-18 19:02:15 +0100263 if vlan == tag or vlan == "ALL":
Steve McIntyred6759dd2014-08-12 18:10:00 +0100264 return
Steve McIntyre3f287882014-08-18 19:02:15 +0100265 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
Steve McIntyred6759dd2014-08-12 18:10:00 +0100266
267 # Remove a trunk port from a specified VLAN (tag)
268 def PortRemoveTrunkFromVlan(self, port, tag):
269 logging.debug("Removing trunk port %s from VLAN %d" % (port, tag))
270 if not self._is_port_name_valid(port):
271 raise IndexError("Port name %s not recognised" % port)
272 if not (self.PortGetMode(port) == "trunk"):
273 raise IndexError("Port %s not in trunk mode" % port)
274 self._configure()
275 self._cli("interface %s" % port)
276 self._cli("switchport trunk allowed vlan remove %d" % tag)
277 self._end_configure()
278
279 # Validate it happened
280 read_vlans = self.PortGetTrunkVlanList(port)
281 for vlan in read_vlans:
282 if vlan == tag:
Steve McIntyre3f287882014-08-18 19:02:15 +0100283 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
Steve McIntyred6759dd2014-08-12 18:10:00 +0100284
285 # Get the configured VLAN tag for an general port (tag)
286 def PortGetGeneralVlan(self, port):
287 logging.debug("Getting VLAN for general port %s" % port)
288 vlan = 1
289 if not self._is_port_name_valid(port):
290 raise IndexError("Port name %s not recognised" % port)
291 if not (self.PortGetMode(port) == "general"):
292 raise IndexError("Port %s not in general mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100293 regex = re.compile('Access Mode VLAN: (\d+)')
294 self._cli("show interfaces %s switchport" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100295 for line in self._read_paged_output():
296 match = regex.match(line)
297 if match:
298 vlan = match.group(1)
299 return int(vlan)
300
301 # Get the list of configured VLAN tags for a trunk port
302 def PortGetTrunkVlanList(self, port):
303 logging.debug("Getting VLANs for trunk port %s" % port)
304 vlans = [ ]
305 if not self._is_port_name_valid(port):
306 raise IndexError("Port name %s not recognised" % port)
307 if not (self.PortGetMode(port) == "trunk"):
308 raise IndexError("Port %s not in general mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100309 regex_start = re.compile('Trunking VLANs Enabled: (.*)')
310 regex_continue = re.compile('\s*(\d.*)')
311 self._cli("show interfaces %s switchport" % port)
312
313 # Horrible parsing work - VLAN list may extend over several lines
314 in_match = False
315 vlan_text = ''
316
Steve McIntyred6759dd2014-08-12 18:10:00 +0100317 for line in self._read_paged_output():
Steve McIntyre3f287882014-08-18 19:02:15 +0100318 if in_match:
319 match = regex_continue.match(line)
320 if match:
321 vlan_text += match.group(1)
322 next
323 else:
324 in_match = False
325 next
326 else:
327 match = regex_start.match(line)
328 if match:
329 vlan_text += match.group(1)
330 in_match = True
331
332 vlans = self._parse_vlan_list(vlan_text)
333
Steve McIntyred6759dd2014-08-12 18:10:00 +0100334 return vlans
335
336 ################################
337 ### Internal functions
338 ################################
339
340 def _login(self, username, password, enablepassword):
341 logging.debug("attempting login with username %s, password %s" % (username, password))
342 self.connection.expect('User Access Verification')
343 if username is not None:
344 self.connection.expect("User Name:")
345 self._cli("%s" % username)
346 if password is not None:
347 self.connection.expect("Password:")
348 self._cli("%s" % password, False)
349 while True:
350 index = self.connection.expect(['User Name:', 'Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
351 if index != 4: # Any other means: failed to log in!
352 logging.error("Login failure: index %d\n" % index)
353 logging.error("Login failure: %s\n" % self.connection.match.before)
354 raise IOError
355
356 # else
Steve McIntyre3f287882014-08-18 19:02:15 +0100357 self._prompt_name = self.connection.match.group(1).strip()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100358 if self.connection.match.group(2) == ">":
359 # Need to enter "enable" mode too
360 self._cli("enable")
361 if enablepassword is not None:
362 self.connection.expect("Password:")
363 self._cli("%s" % enablepassword, False)
364 index = self.connection.expect(['Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
365 if index != 3: # Any other means: failed to log in!
366 logging.error("Enable password failure: %s\n" % self.connection.match)
367 raise IOError
368 return 0
369
370 def _logout(self):
371 logging.debug("Logging out")
372 self._cli("exit", False)
373
374 def _configure(self):
375 self._cli("configure terminal")
376
377 def _end_configure(self):
378 self._cli("end")
379
380 def _read_paged_output(self):
381 buf = []
Steve McIntyre3f287882014-08-18 19:02:15 +0100382 prompt = self._prompt_name + '#'
Steve McIntyred6759dd2014-08-12 18:10:00 +0100383 while True:
384 index = self.connection.expect([' -*More-*', prompt])
385 if index == 0: # More: <space>
386 for line in self.connection.before.split('\r\n'):
387 line1 = re.sub('(\x08|\x0D)*', '', line.strip())
388 buf.append(line1)
389 self._cli(' ', False)
390 elif index == 1: # Back to a prompt, says output is finished
391 break
392
393 for line in self.connection.before.split('\r\n'):
394 line1 = re.sub('(\x08|\x0D)*', '', line.strip())
395 buf.append(line1)
396
397 return buf
398
399 def _get_port_names(self):
400 logging.debug("Grabbing list of ports")
401 interfaces = []
402
403 # Use "Up" or "Down" to only identify lines in the output that
404 # match interfaces that exist
405 regex = re.compile('^\s*([a-zA-Z0-9_/]*).*(connect)(.*)')
406 regex1 = re.compile('.*Not Present.*')
407
408 self._cli("show interfaces status")
409 for line in self._read_paged_output():
410 match = regex.match(line)
411 if match:
412 interface = match.group(1)
413 junk = match.group(3)
414 match1 = regex1.match(junk) # Deliberately drop things
415 # marked as "Not Present"
416 if not match1:
417 interfaces.append(interface)
418 return interfaces
419
Steve McIntyred6759dd2014-08-12 18:10:00 +0100420 def _show_config(self):
421 logging.debug("Grabbing config")
422 self._cli("show running-config")
423 return self._read_paged_output()
424
425 def _show_clock(self):
426 logging.debug("Grabbing time")
427 self._cli("show clock")
428 return self._read_paged_output()
429
430 def _show_clock(self):
431 logging.debug("Grabbing ")
432 self._cli("show clock")
433 return self._read_paged_output()
434
435 def _get_systemdata(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100436 logging.debug("Grabbing system sw and hw versions")
437 self._cli("show version")
438 for line in self._read_paged_output():
Steve McIntyre3f287882014-08-18 19:02:15 +0100439 self._systemdata.append(line)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100440
Steve McIntyre3f287882014-08-18 19:02:15 +0100441 def _parse_vlan_list(self, input):
442 vlans = []
443
444 if input == "ALL":
445 return ["ALL"]
446 elif input == "NONE":
447 return []
448 else:
449 # Parse the complex list
450 groups = input.split(',')
451 for group in groups:
452 subgroups = group.split('-')
453 if len(subgroups) == 1:
454 vlans.append(int(subgroups[0]))
455 elif len(subgroups) == 2:
456 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
457 vlans.append(i)
458 else:
459 print "Can't parse group \"" + group + "\""
460
461 return vlans
Steve McIntyred6759dd2014-08-12 18:10:00 +0100462
463 # Wrapper around connection.send - by default, expect() the same
464 # text we've sent, to remove it from the output from the
465 # switch. For the few cases where we don't need that, override
466 # this using echo=False.
467 # Horrible, but seems to work.
468 def _cli(self, text, echo=True):
469 self.connection.send(text + '\r')
470 if echo:
471 self.connection.expect(text)
472
473if __name__ == "__main__":
474 p = CiscoCatalyst('lngswitch02', 23)
475 p.SwitchConnect(None, 'lngvirtual', 'lngenable')
476
477 print "VLANs are:"
478 buf = p.VlanGetList()
479 p._dump_list(buf)
480
481 buf = p.VlanGetName(2)
482 print "VLAN 2 is named \"%s\"" % buf
483
484 print "Create VLAN 3"
485 p.VlanCreate(3)
486
487 buf = p.VlanGetName(3)
488 print "VLAN 3 is named \"%s\"" % buf
489
490 print "Set name of VLAN 3 to test333"
491 p.VlanSetName(3, "test333")
492
493 buf = p.VlanGetName(3)
494 print "VLAN 3 is named \"%s\"" % buf
495
496 print "VLANs are:"
497 buf = p.VlanGetList()
498 p._dump_list(buf)
499
500 print "Destroy VLAN 3"
501 p.VlanDestroy(3)
502
503 print "VLANs are:"
504 buf = p.VlanGetList()
505 p._dump_list(buf)
506
Steve McIntyreb7adc782014-08-13 00:22:21 +0100507 buf = p.PortGetMode("Gi1/0/10")
508 print "Port Gi1/0/10 is in %s mode" % buf
509
510 buf = p.PortGetMode("Gi1/0/11")
511 print "Port Gi1/0/11 is in %s mode" % buf
Steve McIntyred6759dd2014-08-12 18:10:00 +0100512
513 # Test general stuff
Steve McIntyre3f287882014-08-18 19:02:15 +0100514 print "Set Gi1/0/9 to general mode"
515 p.PortSetMode("Gi1/0/9", "general")
516
517 print "Move Gi1/0/9 to VLAN 4"
518 p.PortSetGeneralVlan("Gi1/0/9", 4)
519
520 buf = p.PortGetGeneralVlan("Gi1/0/9")
521 print "Read from switch: Gi1/0/9 is on VLAN %s" % buf
522
523 print "Move Gi1/0/9 back to VLAN 1"
524 p.PortSetGeneralVlan("Gi1/0/9", 1)
525
526 # Test general stuff
527 print "Set Gi1/0/9 to trunk mode"
528 p.PortSetMode("Gi1/0/9", "trunk")
529 print "Read from switch: which VLANs is Gi1/0/9 on?"
530 buf = p.PortGetTrunkVlanList("Gi1/0/9")
531 p._dump_list(buf)
532 print "Add Gi1/0/9 to VLAN 2"
533 p.PortAddTrunkToVlan("Gi1/0/9", 2)
534 print "Add Gi1/0/9 to VLAN 3"
535 p.PortAddTrunkToVlan("Gi1/0/9", 3)
536 print "Add Gi1/0/9 to VLAN 4"
537 p.PortAddTrunkToVlan("Gi1/0/9", 4)
538 print "Read from switch: which VLANs is Gi1/0/9 on?"
539 buf = p.PortGetTrunkVlanList("Gi1/0/9")
540 p._dump_list(buf)
541
542 p.PortRemoveTrunkFromVlan("Gi1/0/9", 3)
543 p.PortRemoveTrunkFromVlan("Gi1/0/9", 3)
544 p.PortRemoveTrunkFromVlan("Gi1/0/9", 4)
545 print "Read from switch: which VLANs is Gi1/0/9 on?"
546 buf = p.PortGetTrunkVlanList("Gi1/0/9")
547 p._dump_list(buf)
548
549
Steve McIntyred6759dd2014-08-12 18:10:00 +0100550 #print "Remove fa6 from VLAN 1 (default)"
551 #p.PortRemoveGeneralFromVlan("fa6", 1)
552 #print "Move fa6 to VLAN 2"
553 #p.PortAddGeneraltoVlan("fa6", 2)
554# buf = p.PortGetGeneralVlan("fa6")
555# print "Read from switch: fa6 is on VLAN %s" % buf
556# print "Remove fa6 from VLAN 2"
557# p.PortRemoveGeneralFromVlan("fa6", 2)
558# print "And re-enable the default VLAN for fa6"
559# p.PortAddGeneraltoVlan("fa6", 1)
560 #print "And move fa6 back to a trunk port"
561 #p.PortSetMode("fa6", "trunk")
562 #buf = p.PortGetMode("fa6")
563 #print "Port fa6 is in %s mode" % buf
564
Steve McIntyred6759dd2014-08-12 18:10:00 +0100565 # print "Adding lots of ports to VLANs"
566 # p.PortAddTrunkToVlan("fa1", 2)
567 # p.PortAddTrunkToVlan("fa3", 2)
568 # p.PortAddTrunkToVlan("fa5", 2)
569 # p.PortAddTrunkToVlan("fa7", 2)
570 # p.PortAddTrunkToVlan("fa9", 2)
571 # p.PortAddTrunkToVlan("fa11", 2)
572 # p.PortAddTrunkToVlan("fa13", 2)
573 # p.PortAddTrunkToVlan("fa15", 2)
574 # p.PortAddTrunkToVlan("fa17", 2)
575 # p.PortAddTrunkToVlan("fa19", 2)
576 # p.PortAddTrunkToVlan("fa21", 2)
577 # p.PortAddTrunkToVlan("fa23", 2)
578 # p.PortAddTrunkToVlan("gi4", 2)
579
580# p.SwitchSaveRunningConfig()
581
582# p.SwitchDisconnect()
583# p._show_config()
584