blob: db427b9e64da591ab342bf6e7b4c394bd7c103d6 [file] [log] [blame]
Steve McIntyrefdcb3782015-07-06 16:34:56 +01001#! /usr/bin/python
2
3# Copyright 2015 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 re
24
25# Netgear XSM family driver
26# Developed and tested against the XSM7224S in the Linaro LAVA lab
27
28if __name__ == '__main__':
29 import os
30 vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
31 sys.path.insert(0, vlandpath)
32 sys.path.insert(0, "%s/.." % vlandpath)
33
34from errors import InputError, PExpectError
35from drivers.common import SwitchDriver, SwitchErrors
36
37class NetgearXSM(SwitchDriver):
38
39 connection = None
40 _username = None
41 _password = None
42 _enable_password = None
43
44 _capabilities = [
45 'TrunkWildCardVlans' # Trunk ports are on all VLANs by
46 # default, so we shouldn't need to
47 # bugger with them
48 ]
49
50 # Regexps of expected hardware information - fail if we don't see
51 # this
52 _expected_manuf = re.compile('^Netgear')
53 _expected_model = re.compile('^XSM')
54
55 logfile = None
56
57 def __init__(self, switch_hostname, switch_telnetport=23, debug = False):
58 SwitchDriver.__init__(self)
59 self._systemdata = []
60 if debug:
61 self.logfile = sys.stderr
62 self.exec_string = "/usr/bin/telnet %s %d" % (switch_hostname, switch_telnetport)
63 self.errors = SwitchErrors()
64
65 ################################
66 ### Switch-level API functions
67 ################################
68
69 # Connect to the switch and log in
70 def switch_connect(self, username, password, enablepassword):
71 self._username = username
72 self._password = password
73 self._enable_password = enablepassword
74 self._switch_connect()
75
76 # Log out of the switch and drop the connection and all state
77 def switch_disconnect(self):
78 self._logout()
79 logging.debug("Closing connection: %s", self.connection)
80 self.connection.close(True)
81 self._ports = []
82 self._prompt_name = ''
83 self._systemdata = []
84 del(self)
85
86 # Save the current running config into flash - we want config to
87 # remain across reboots
88 def switch_save_running_config(self):
89 try:
90 self._cli("save")
91 self.connection.expect("Are you sure")
92 self._cli("y")
93 self.connection.expect("Configuration Saved!")
94 except (PExpectError, pexpect.EOF):
95 # recurse on error
96 self._switch_connect()
97 self.switch_save_running_config()
98
99 # Restart the switch - we need to reload config to do a
100 # roll-back. Do NOT save running-config first if the switch asks -
101 # we're trying to dump recent changes, not save them.
102 #
103 # This will also implicitly cause a connection to be closed
104 def switch_restart(self):
105 self._cli("reload")
106 index = self.connection.expect('Are you sure')
107 self._cli("y") # Yes, continue to reset
108 self.connection.close(True)
109
110 # List the capabilities of the switch (and driver) - some things
111 # make no sense to abstract. Returns a dict of strings, each one
112 # describing an extra feature that that higher levels may care
113 # about
114 def switch_get_capabilities(self):
115 return self._capabilities
116
117 ################################
118 ### VLAN API functions
119 ################################
120
121 # Create a VLAN with the specified tag
122 def vlan_create(self, tag):
123 logging.debug("Creating VLAN %d", tag)
124 try:
125 self._cli("vlan database")
126 self._cli("vlan %d" % tag)
127 self._end_configure()
128
129 # Validate it happened
130 vlans = self.vlan_get_list()
131 for vlan in vlans:
132 if vlan == tag:
133 return
134 raise IOError("Failed to create VLAN %d" % tag)
135
136 except PExpectError:
137 # recurse on error
138 self._switch_connect()
139 self.vlan_create(tag)
140
141 # Destroy a VLAN with the specified tag
142 def vlan_destroy(self, tag):
143 logging.debug("Destroying VLAN %d", tag)
144
145 try:
146 self._cli("vlan database")
147 self._cli("no vlan %d" % tag)
148 self._end_configure()
149
150 # Validate it happened
151 vlans = self.vlan_get_list()
152 for vlan in vlans:
153 if vlan == tag:
154 raise IOError("Failed to destroy VLAN %d" % tag)
155
156 except PExpectError:
157 # recurse on error
158 self._switch_connect()
159 self.vlan_destroy(tag)
160
161 # Set the name of a VLAN
162 def vlan_set_name(self, tag, name):
163 logging.debug("Setting name of VLAN %d to %s", tag, name)
164
165 try:
166 self._cli("vlan database")
167 self._cli("vlan name %d %s" % (tag, name))
168 self._end_configure()
169
170 # Validate it happened
171 read_name = self.vlan_get_name(tag)
172 if read_name != name:
173 raise IOError("Failed to set name for VLAN %d (name found is \"%s\", not \"%s\")"
174 % (tag, read_name, name))
175 except PExpectError:
176 # recurse on error
177 self._switch_connect()
178 self.vlan_set_name(tag, name)
179
180 # Get a list of the VLAN tags currently registered on the switch
181 def vlan_get_list(self):
182 logging.debug("Grabbing list of VLANs")
183
184 try:
185 vlans = []
186
187 regex = re.compile('^ *(\d+).*(Static)')
188
189 self._cli("show vlan brief")
190 for line in self._read_long_output("show vlan brief"):
191 match = regex.match(line)
192 if match:
193 vlans.append(int(match.group(1)))
194 return vlans
195
196 except PExpectError:
197 # recurse on error
198 self._switch_connect()
199 return self.vlan_get_list()
200
201 # For a given VLAN tag, ask the switch what the associated name is
202 def vlan_get_name(self, tag):
203
204 try:
205 logging.debug("Grabbing the name of VLAN %d", tag)
206 name = None
207 regex = re.compile('VLAN Name: (.*)')
208 self._cli("show vlan %d" % tag)
209 for line in self._read_long_output("show vlan"):
210 match = regex.match(line)
211 if match:
212 name = match.group(1)
213 name.strip()
214 return name
215
216 except PExpectError:
217 # recurse on error
218 self._switch_connect()
219 return self.vlan_get_name(tag)
220
221 ################################
222 ### Port API functions
223 ################################
224
225 # Set the mode of a port: access or trunk
226 def port_set_mode(self, port, mode):
227 logging.debug("Setting port %s to %s", port, mode)
228 if not self._is_port_mode_valid(mode):
229 raise InputError("Port mode %s is not allowed" % mode)
230 if not self._is_port_name_valid(port):
231 raise InputError("Port name %s not recognised" % port)
232
233 # This switch does not support specific modes, so we can't
234 # actually change the mode directly. However, we can and
235 # should deal with the PVID and memberships of existing VLANs
236 # etc.
237
238 try:
239 if mode == "trunk":
240 # We define a trunk port thus:
Steve McIntyred97ab292015-07-07 14:23:32 +0100241 # accept all frames on ingress
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100242 # exist on all current VLANs
243 # tag outgoing frames
244 # PVID should match the default VLAN (1).
245 self._configure()
246 self._cli("interface %s" % port)
Steve McIntyred97ab292015-07-07 14:23:32 +0100247 self._cli("vlan acceptframe all")
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100248 self._cli("no vlan ingressfilter")
249 self._cli("vlan tagging 1-4093")
250 self._cli("vlan pvid 1")
251 self._end_configure()
252
253 # We define an access port thus:
254 # accept only untagged frames on ingress
255 # exists on one VLAN only (1 by default)
256 # do not tag outgoing frames
257 # PVID should match the VLAN it's on (1 by default)
258 if mode == "access":
259 self._configure()
260 self._cli("interface %s" % port)
261 self._cli("vlan acceptframe untaggedonly")
262 self._cli("vlan ingressfilter")
263 self._cli("no vlan tagging 1-4093")
264 self._cli("vlan pvid 1")
265 self._end_configure()
266
267 # Validate it happened
268 read_mode = self.port_get_mode(port)
269
270 if read_mode != mode:
271 raise IOError("Failed to set mode for port %s" % port)
272
273 except PExpectError:
274 # recurse on error
275 self._switch_connect()
276 self.port_set_mode(port, mode)
277
278 # Get the mode of a port: access or trunk
279 def port_get_mode(self, port):
280 logging.debug("Getting mode of port %s", port)
281 mode = ''
282 if not self._is_port_name_valid(port):
283 raise InputError("Port name %s not recognised" % port)
284 regex = re.compile('Administrative Mode: (.*)')
285
286 try:
287 self._cli("show interfaces %s switchport" % port)
288 for line in self._read_long_output("show interfaces switchport"):
289 match = regex.match(line)
290 if match:
291 mode = match.group(1)
292 if mode == 'static access':
293 return 'access'
294 if mode == 'dynamic auto':
295 return 'trunk'
296 return mode
297
298 except PExpectError:
299 # recurse on error
300 self._switch_connect()
301 return self.port_get_mode(port)
302
303 # Set an access port to be in a specified VLAN (tag)
304 def port_set_access_vlan(self, port, tag):
305 logging.debug("Setting access port %s to VLAN %d", port, tag)
306 if not self._is_port_name_valid(port):
307 raise InputError("Port name %s not recognised" % port)
308 if not (self.port_get_mode(port) == "access"):
309 raise InputError("Port %s not in access mode" % port)
310
311 try:
312 self._configure()
313 self._cli("interface %s" % port)
314 self._cli("switchport access vlan %d" % tag)
315 self._cli("no shutdown")
316 self._end_configure()
317
318 # Finally, validate things worked
319 read_vlan = int(self.port_get_access_vlan(port))
320 if read_vlan != tag:
321 raise IOError("Failed to move access port %d to VLAN %d - got VLAN %d instead"
322 % (port, tag, read_vlan))
323
324 except PExpectError:
325 # recurse on error
326 self._switch_connect()
327 self.port_set_access_vlan(port, tag)
328
329 # Add a trunk port to a specified VLAN (tag)
330 def port_add_trunk_to_vlan(self, port, tag):
331 logging.debug("Adding trunk port %s to VLAN %d", port, tag)
332 if not self._is_port_name_valid(port):
333 raise InputError("Port name %s not recognised" % port)
334 if not (self.port_get_mode(port) == "trunk"):
335 raise InputError("Port %s not in trunk mode" % port)
336
337 try:
338 self._configure()
339 self._cli("interface %s" % port)
340 self._cli("switchport trunk allowed vlan add %d" % tag)
341 self._end_configure()
342
343 # Validate it happened
344 read_vlans = self.port_get_trunk_vlan_list(port)
345 for vlan in read_vlans:
346 if vlan == tag or vlan == "ALL":
347 return
348 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
349
350 except PExpectError:
351 # recurse on error
352 self._switch_connect()
353 self.port_add_trunk_to_vlan(port, tag)
354
355 # Remove a trunk port from a specified VLAN (tag)
356 def port_remove_trunk_from_vlan(self, port, tag):
357 logging.debug("Removing trunk port %s from VLAN %d", port, tag)
358 if not self._is_port_name_valid(port):
359 raise InputError("Port name %s not recognised" % port)
360 if not (self.port_get_mode(port) == "trunk"):
361 raise InputError("Port %s not in trunk mode" % port)
362
363 try:
364 self._configure()
365 self._cli("interface %s" % port)
366 self._cli("switchport trunk allowed vlan remove %d" % tag)
367 self._end_configure()
368
369 # Validate it happened
370 read_vlans = self.port_get_trunk_vlan_list(port)
371 for vlan in read_vlans:
372 if vlan == tag:
373 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
374
375 except PExpectError:
376 # recurse on error
377 self._switch_connect()
378 self.port_remove_trunk_from_vlan(port, tag)
379
380 # Get the configured VLAN tag for an access port (tag)
381 def port_get_access_vlan(self, port):
382 logging.debug("Getting VLAN for access port %s", port)
383 vlan = 1
384 if not self._is_port_name_valid(port):
385 raise InputError("Port name %s not recognised" % port)
386 if not (self.port_get_mode(port) == "access"):
387 raise InputError("Port %s not in access mode" % port)
388 regex = re.compile('Access Mode VLAN: (\d+)')
389
390 try:
391 self._cli("show interfaces %s switchport" % port)
392 for line in self._read_long_output("show interfaces switchport"):
393 match = regex.match(line)
394 if match:
395 vlan = match.group(1)
396 return int(vlan)
397
398 except PExpectError:
399 # recurse on error
400 self._switch_connect()
401 return self.port_get_access_vlan(port)
402
403 # Get the list of configured VLAN tags for a trunk port
404 def port_get_trunk_vlan_list(self, port):
405 logging.debug("Getting VLANs for trunk port %s", port)
406 vlans = [ ]
407 if not self._is_port_name_valid(port):
408 raise InputError("Port name %s not recognised" % port)
409 if not (self.port_get_mode(port) == "trunk"):
410 raise InputError("Port %s not in trunk mode" % port)
411 regex_start = re.compile('Trunking VLANs Enabled: (.*)')
412 regex_continue = re.compile('\s*(\d.*)')
413
414 try:
415 self._cli("show interfaces %s switchport" % port)
416
417 # Horrible parsing work - VLAN list may extend over several lines
418 in_match = False
419 vlan_text = ''
420
421 for line in self._read_long_output("show interfaces switchport"):
422 if in_match:
423 match = regex_continue.match(line)
424 if match:
425 vlan_text += match.group(1)
426 else:
427 in_match = False
428 else:
429 match = regex_start.match(line)
430 if match:
431 vlan_text += match.group(1)
432 in_match = True
433
434 vlans = self._parse_vlan_list(vlan_text)
435 return vlans
436
437 except PExpectError:
438 # recurse on error
439 self._switch_connect()
440 return self.port_get_trunk_vlan_list(port)
441
442 ################################
443 ### Internal functions
444 ################################
445
446 # Connect to the switch and log in
447 def _switch_connect(self):
448
449 if not self.connection is None:
450 self.connection.close(True)
451 self.connection = None
452
453 logging.debug("Connecting to Switch with: %s", self.exec_string)
454 self.connection = pexpect.spawn(self.exec_string, logfile = self.logfile)
455
456 self._login()
457
458 # Avoid paged output
459 self._cli("terminal length 0")
460
461 # And grab details about the switch. in case we need it
462 self._get_systemdata()
463
464 # And also validate them - make sure we're driving a switch of
465 # the correct model! Also store the serial number
466 manuf_regex = re.compile('^Manufacturer([\.\s])+(\S+)')
467 model_regex = re.compile('^Machine Model([\.\s])+(\S+)')
468 sn_regex = re.compile('^Serial Number([\.\s])+(\S+)')
469 descr = ""
470
471 for line in self._systemdata:
472 match1 = manuf_regex.match(line)
473 if match1:
474 manuf = match1.group(2)
475
476 match2 = model_regex.match(line)
477 if match2:
478 model = match2.group(2)
479
480 match3 = sn_regex.match(line)
481 if match3:
482 self.serial_number = match3.group(2)
483
484 logging.debug("manufacturer is %s", manuf)
485 logging.debug("model is %s", model)
486 logging.debug("serial number is %s", self.serial_number)
487
488 if not (self._expected_manuf.match(manuf) and self._expected_model.match(model)):
489 raise IOError("Switch %s %s not recognised by this driver: abort" % (manuf, model))
490
491 # Now build a list of our ports, for later sanity checking
492 self._ports = self._get_port_names()
493 if len(self._ports) < 4:
494 raise IOError("Not enough ports detected - problem!")
495
496 def _login(self):
497 logging.debug("attempting login with username %s, password %s", self._username, self._password)
498 if self._username is not None:
499 self.connection.expect("User:")
500 self._cli("%s" % self._username)
501 if self._password is not None:
502 self.connection.expect("Password:")
503 self._cli("%s" % self._password, False)
504 while True:
505 index = self.connection.expect(['User:', 'Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
506 if index != 4: # Any other means: failed to log in!
507 logging.error("Login failure: index %d\n", index)
508 logging.error("Login failure: %s\n", self.connection.match.before)
509 raise IOError
510
511 # else
512 self._prompt_name = re.escape(self.connection.match.group(1).strip())
513 logging.error('\n\nSet _prompt_name to "%s"\n\n\n' % self._prompt_name)
514 if self.connection.match.group(2) == ">":
515 # Need to enter "enable" mode too
516 self._cli("enable")
517 if self._enable_password is not None:
518 self.connection.expect("Password:")
519 self._cli("%s" % self._enable_password, False)
520 index = self.connection.expect(['Password:', 'Bad passwords', 'authentication failed', r'(.*) *(#|>)'])
521 if index != 3: # Any other means: failed to log in!
522 logging.error("Enable password failure: %s\n", self.connection.match)
523 raise IOError
524 return 0
525
526 def _logout(self):
527 logging.debug("Logging out")
528 self._cli("quit", False)
529 self.connection.expect("Would you like to save them now")
530 self._cli("n")
531
532 def _configure(self):
533 self._cli("configure")
534
535 def _end_configure(self):
536 self._cli("exit")
537
538 def _read_long_output(self, text):
539 longbuf = []
540 prompt = self._prompt_name + '\s*#'
541 while True:
542 try:
543 index = self.connection.expect(['--More--', prompt])
544 if index == 0: # "--More-- or (q)uit"
545 for line in self.connection.before.split('\r\n'):
546 line1 = re.sub('(\x08|\x0D)*', '', line.strip())
547 longbuf.append(line1)
548 self._cli(' ', False)
549 elif index == 1: # Back to a prompt, says output is finished
550 break
551 except (pexpect.EOF, pexpect.TIMEOUT):
552 # Something went wrong; logout, log in and try again!
553 logging.error("PEXPECT FAILURE, RECONNECT")
554 self.errors.log_error_in(text)
555 raise PExpectError("_read_long_output failed")
556 except:
557 logging.error("prompt is \"%s\"", prompt)
558 raise
559
560 for line in self.connection.before.split('\r\n'):
561 longbuf.append(line.strip())
562 return longbuf
563
564 def _get_port_names(self):
565 logging.debug("Grabbing list of ports")
566 interfaces = []
567
568 # Use "Up" or "Down" to only identify lines in the output that
569 # match interfaces that exist
570 regex = re.compile('^(1\S+)')
571
572 try:
573 self._cli("show port all")
574 for line in self._read_long_output("show port all"):
575 match = regex.match(line)
576 if match:
577 interface = match.group(1)
578 interfaces.append(interface)
579 return interfaces
580
581 except PExpectError:
582 # recurse on error
583 self._switch_connect()
584 return self._get_port_names()
585
586 def _show_config(self):
587 logging.debug("Grabbing config")
588 try:
589 self._cli("show running-config")
590 return self._read_long_output("show running-config")
591 except PExpectError:
592 # recurse on error
593 self._switch_connect()
594 return self._show_config()
595
596 def _show_clock(self):
597 logging.debug("Grabbing time")
598 try:
599 self._cli("show clock")
600 return self._read_long_output("show clock")
601 except PExpectError:
602 # recurse on error
603 self._switch_connect()
604 return self._show_clock()
605
606 def _get_systemdata(self):
607 logging.debug("Grabbing system sw and hw versions")
608
609 try:
610 self._systemdata = []
611 self._cli("show version")
612 for line in self._read_long_output("show version"):
613 self._systemdata.append(line)
614
615 except PExpectError:
616 # recurse on error
617 self._switch_connect()
618 return self._get_systemdata()
619
620 def _parse_vlan_list(self, inputdata):
621 vlans = []
622
623 if inputdata == "ALL":
624 return ["ALL"]
625 elif inputdata == "NONE":
626 return []
627 else:
628 # Parse the complex list
629 groups = inputdata.split(',')
630 for group in groups:
631 subgroups = group.split('-')
632 if len(subgroups) == 1:
633 vlans.append(int(subgroups[0]))
634 elif len(subgroups) == 2:
635 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
636 vlans.append(i)
637 else:
638 logging.debug("Can't parse group \"" + group + "\"")
639
640 return vlans
641
642 # Wrapper around connection.send - by default, expect() the same
643 # text we've sent, to remove it from the output from the
644 # switch. For the few cases where we don't need that, override
645 # this using echo=False.
646 # Horrible, but seems to work.
647 def _cli(self, text, echo=True):
648 self.connection.send(text + '\r')
649 if echo:
650 try:
651 self.connection.expect(text)
652 except (pexpect.EOF, pexpect.TIMEOUT):
653 # Something went wrong; logout, log in and try again!
654 logging.error("PEXPECT FAILURE, RECONNECT")
655 self.errors.log_error_out(text)
656 raise PExpectError("_cli failed on %s" % text)
657 except:
658 logging.error("Unexpected error: %s", sys.exc_info()[0])
659 raise
660
661if __name__ == "__main__":
662
663 import optparse
664
665 switch = 'vlandswitch05'
666 parser = optparse.OptionParser()
667 parser.add_option("--switch",
668 dest = "switch",
669 action = "store",
670 nargs = 1,
671 type = "string",
672 help = "specify switch to connect to for testing",
673 metavar = "<switch>")
674 (opts, args) = parser.parse_args()
675 if opts.switch:
676 switch = opts.switch
677
678 p = NetgearXSM(switch, 23, debug=True)
679 p.switch_connect('admin', '', None)
680
681 print "VLANs are:"
682 buf = p.vlan_get_list()
683 p.dump_list(buf)
684
685 buf = p.vlan_get_name(2)
686 print "VLAN 2 is named \"%s\"" % buf
687
688 print "Create VLAN 3"
689 p.vlan_create(3)
690
691 buf = p.vlan_get_name(3)
692 print "VLAN 3 is named \"%s\"" % buf
693
694 print "Set name of VLAN 3 to test333"
695 p.vlan_set_name(3, "test333")
696
697 buf = p.vlan_get_name(3)
698 print "VLAN 3 is named \"%s\"" % buf
699
700 print "VLANs are:"
701 buf = p.vlan_get_list()
702 p.dump_list(buf)
703
704 print "Destroy VLAN 3"
705 p.vlan_destroy(3)
706
707 print "VLANs are:"
708 buf = p.vlan_get_list()
709 p.dump_list(buf)
710
711 buf = p.port_get_mode("1/0/10")
712 print "Port 1/0/10 is in %s mode" % buf
713
714 buf = p.port_get_mode("1/0/11")
715 print "Port 1/0/11 is in %s mode" % buf
716
717 # Test access stuff
718 print "Set 1/0/9 to access mode"
719 p.port_set_mode("1/0/9", "access")
720
721 print "Move 1/0/9 to VLAN 4"
722 p.port_set_access_vlan("1/0/9", 4)
723
724 buf = p.port_get_access_vlan("1/0/9")
725 print "Read from switch: 1/0/9 is on VLAN %s" % buf
726
727 print "Move 1/0/9 back to VLAN 1"
728 p.port_set_access_vlan("1/0/9", 1)
729
730 # Test access stuff
731 print "Set 1/0/9 to trunk mode"
732 p.port_set_mode("1/0/9", "trunk")
733 print "Read from switch: which VLANs is 1/0/9 on?"
734 buf = p.port_get_trunk_vlan_list("1/0/9")
735 p.dump_list(buf)
736 print "Add 1/0/9 to VLAN 2"
737 p.port_add_trunk_to_vlan("1/0/9", 2)
738 print "Add 1/0/9 to VLAN 3"
739 p.port_add_trunk_to_vlan("1/0/9", 3)
740 print "Add 1/0/9 to VLAN 4"
741 p.port_add_trunk_to_vlan("1/0/9", 4)
742 print "Read from switch: which VLANs is 1/0/9 on?"
743 buf = p.port_get_trunk_vlan_list("1/0/9")
744 p.dump_list(buf)
745
746 p.port_remove_trunk_from_vlan("1/0/9", 3)
747 p.port_remove_trunk_from_vlan("1/0/9", 3)
748 p.port_remove_trunk_from_vlan("1/0/9", 4)
749 print "Read from switch: which VLANs is 1/0/9 on?"
750 buf = p.port_get_trunk_vlan_list("1/0/9")
751 p.dump_list(buf)
752
753# print 'Restarting switch, to explicitly reset config'
754# p.switch_restart()
755
756# p.switch_save_running_config()
757
758# p.switch_disconnect()
759# p._show_config()