blob: dbdd1048043e44b5930837be1b6a44bf129489a7 [file] [log] [blame]
Steve McIntyre59ffe892018-01-24 16:46:00 +00001#! /usr/bin/python
2
3# Copyright 2018 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 sys
22import re
23import pexpect
24
25# Mellanox MLNX-OS driver
26# Developed and tested against the SN2100 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
Steve McIntyre4ed20f32018-01-24 17:21:37 +000037class Mellanox(SwitchDriver):
Steve McIntyre59ffe892018-01-24 16:46:00 +000038
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 # Regexp of expected hardware information - fail if we don't see
51 # this
52 _expected_descr_re = re.compile(r'MLNX-OS')
53
54 def __init__(self, switch_hostname, switch_telnetport=23, debug = False):
55 SwitchDriver.__init__(self, switch_hostname, debug)
56 self._systemdata = []
57 self.exec_string = "/usr/bin/telnet %s %d" % (switch_hostname, switch_telnetport)
58 self.errors = SwitchErrors()
59
60 ################################
61 ### Switch-level API functions
62 ################################
63
64 # Save the current running config into flash - we want config to
65 # remain across reboots
66 def switch_save_running_config(self):
67 try:
68 self._configure()
69 self._cli("configuration write")
70 self._end_configure()
71 except (PExpectError, pexpect.EOF):
72 # recurse on error
73 self._switch_connect()
74 self.switch_save_running_config()
75
76 # Restart the switch - we need to reload config to do a
77 # roll-back. Do NOT save running-config first if the switch asks -
78 # we're trying to dump recent changes, not save them.
79 #
80 # This will also implicitly cause a connection to be closed
81 def switch_restart(self):
82 self._cli("reload noconfirm")
83
84 # List the capabilities of the switch (and driver) - some things
85 # make no sense to abstract. Returns a dict of strings, each one
86 # describing an extra feature that that higher levels may care
87 # about
88 def switch_get_capabilities(self):
89 return self._capabilities
90
91 ################################
92 ### VLAN API functions
93 ################################
94
95 # Create a VLAN with the specified tag
96 def vlan_create(self, tag):
97 logging.debug("Creating VLAN %d", tag)
98 try:
99 self._configure()
100 self._cli("vlan %d" % tag)
101 self._end_vlan()
102 self._end_configure()
103
104 # Validate it happened
105 vlans = self.vlan_get_list()
106 for vlan in vlans:
107 if vlan == tag:
108 return
109 raise IOError("Failed to create VLAN %d" % tag)
110
111 except PExpectError:
112 # recurse on error
113 self._switch_connect()
114 self.vlan_create(tag)
115
116 # Destroy a VLAN with the specified tag
117 def vlan_destroy(self, tag):
118 logging.debug("Destroying VLAN %d", tag)
119
120 try:
121 self._configure()
122 self._cli("no vlan %d" % tag)
123 self._end_configure()
124
125 # Validate it happened
126 vlans = self.vlan_get_list()
127 for vlan in vlans:
128 if vlan == tag:
129 raise IOError("Failed to destroy VLAN %d" % tag)
130
131 except PExpectError:
132 # recurse on error
133 self._switch_connect()
134 self.vlan_destroy(tag)
135
136 # Set the name of a VLAN
137 def vlan_set_name(self, tag, name):
138 logging.debug("Setting name of VLAN %d to %s", tag, name)
139
140 try:
141 self._configure()
142 self._cli("vlan %d name %s" % (tag, name))
143 self._end_configure()
144
145 # Validate it happened
146 read_name = self.vlan_get_name(tag)
147 if read_name != name:
148 raise IOError("Failed to set name for VLAN %d (name found is \"%s\", not \"%s\")"
149 % (tag, read_name, name))
150 except PExpectError:
151 # recurse on error
152 self._switch_connect()
153 self.vlan_set_name(tag, name)
154
155 # Get a list of the VLAN tags currently registered on the switch
156 def vlan_get_list(self):
157 logging.debug("Grabbing list of VLANs")
158
159 try:
160 vlans = []
161
162 regex = re.compile(r'^ *(\d+)')
163
164 self._cli("show vlan")
165 for line in self._read_long_output("show vlan"):
166 match = regex.match(line)
167 if match:
168 vlans.append(int(match.group(1)))
169 return vlans
170
171 except PExpectError:
172 # recurse on error
173 self._switch_connect()
174 return self.vlan_get_list()
175
176 # For a given VLAN tag, ask the switch what the associated name is
177 def vlan_get_name(self, tag):
178
179 try:
180 logging.debug("Grabbing the name of VLAN %d", tag)
181 name = None
182
183 # Ugh, the output here is messy. VLAN names can include spaces, and
184 # there are no delimiters in the output, e.g.:
185 # VLAN Name Ports
186 # ---- ----------- --------------------------------------
187 # 1 default Eth1/1/1, Eth1/1/2, Eth1/2, Eth1/3/1, Eth1/3/2,
188 # Eth1/4, Eth1/5, Eth1/6, Eth1/7, Eth1/8,
189 # Eth1/10, Eth1/12, Eth1/13, Eth1/14, Eth1/15,
190 # Eth1/16
191 # 102 mdev testing
192 # 103 vpp 1 performance testing Eth1/1/3, Eth1/9
193 # 104 vpp 2 performance testing Eth1/1/4, Eth1/11
194 #
195 # Simplest strategy:
196 # 1. Match on a leading number and grab all the text after it
197 # 2. Drop anything starting with "Eth" to EOL
198 # 3. Strip leading and trailing whitespace
199 #
200 # Not perfect, but it'll have to do. Anybody including "Eth" in a
201 # VLAN name deserves to lose...
202
203 regex = re.compile(r'^ *\d+\s+(.+)')
204 self._cli("show vlan id %d" % tag)
205 for line in self._read_long_output("show vlan id"):
206 match = regex.match(line)
207 if match:
208 name = re.sub(r'Eth.*$',"",match.group(1)).strip()
209 return name
210
211 except PExpectError:
212 # recurse on error
213 self._switch_connect()
214 return self.vlan_get_name(tag)
215
216
217 ################################
218 ### Port API functions
219 ################################
220
221 # Set the mode of a port: access or trunk
222 def port_set_mode(self, port, mode):
223 logging.debug("Setting port %s to %s", port, mode)
224 if not self._is_port_mode_valid(mode):
225 raise InputError("Port mode %s is not allowed" % mode)
226 if not self._is_port_name_valid(port):
227 raise InputError("Port name %s not recognised" % port)
228
229 try:
230 self._configure()
231 self._cli("interface ethernet %s" % port)
232 self._cli("switchport mode %s" % mode)
233 if mode == "trunk":
234 # Put the new trunk port on all VLANs
235 self._cli("switchport trunk allowed-vlan all")
236 self._end_interface()
237 self._end_configure()
238
239 # Validate it happened
240 read_mode = self._port_get_mode(port)
241 if read_mode != mode:
242 raise IOError("Failed to set mode for port %s" % port)
243
244 # And cache the result
245 self._port_modes[port] = mode
246
247 except PExpectError:
248 # recurse on error
249 self._switch_connect()
250 self.port_set_mode(port, mode)
251
252 # Set an access port to be in a specified VLAN (tag)
253 def port_set_access_vlan(self, port, tag):
254 logging.debug("Setting access port %s to VLAN %d", port, tag)
255 if not self._is_port_name_valid(port):
256 raise InputError("Port name %s not recognised" % port)
257 if not (self.port_get_mode(port) == "access"):
258 raise InputError("Port %s not in access mode" % port)
259
260 try:
261 self._configure()
262 self._cli("interface ethernet %s" % port)
263 self._cli("switchport access vlan %d" % tag)
264 self._end_interface()
265 self._end_configure()
266
267 # Validate things worked
268 read_vlan = int(self.port_get_access_vlan(port))
269 if read_vlan != tag:
270 raise IOError("Failed to move access port %s to VLAN %d - got VLAN %d instead"
271 % (port, tag, read_vlan))
272
273 except PExpectError:
274 # recurse on error
275 self._switch_connect()
276 self.port_set_access_vlan(port, tag)
277
278 # Add a trunk port to a specified VLAN (tag)
279 def port_add_trunk_to_vlan(self, port, tag):
280 logging.debug("Adding trunk port %s to VLAN %d", port, tag)
281 if not self._is_port_name_valid(port):
282 raise InputError("Port name %s not recognised" % port)
283 if not (self.port_get_mode(port) == "trunk"):
284 raise InputError("Port %s not in trunk mode" % port)
285
286 try:
287 self._configure()
288 self._cli("interface ethernet %s" % port)
289 self._cli("switchport trunk allowed-vlan add %d" % tag)
290 self._end_interface()
291 self._end_configure()
292
293 # Validate it happened
294 read_vlans = self.port_get_trunk_vlan_list(port)
295 for vlan in read_vlans:
296 if vlan == tag or vlan == "ALL":
297 return
298 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
299
300 except PExpectError:
301 # recurse on error
302 self._switch_connect()
303 self.port_add_trunk_to_vlan(port, tag)
304
305 # Remove a trunk port from a specified VLAN (tag)
306 def port_remove_trunk_from_vlan(self, port, tag):
307 logging.debug("Removing trunk port %s from VLAN %d", port, tag)
308 if not self._is_port_name_valid(port):
309 raise InputError("Port name %s not recognised" % port)
310 if not (self.port_get_mode(port) == "trunk"):
311 raise InputError("Port %s not in trunk mode" % port)
312
313 try:
314 self._configure()
315 self._cli("interface ethernet %s" % port)
316 self._cli("switchport trunk allowed-vlan remove %d" % tag)
317 self._end_interface()
318 self._end_configure()
319
320 # Validate it happened
321 read_vlans = self.port_get_trunk_vlan_list(port)
322 for vlan in read_vlans:
323 if vlan == tag:
324 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
325
326 except PExpectError:
327 # recurse on error
328 self._switch_connect()
329 self.port_remove_trunk_from_vlan(port, tag)
330
331 # Get the configured VLAN tag for an access port (tag)
332 def port_get_access_vlan(self, port):
333 logging.debug("Getting VLAN for access port %s", port)
334 if not self._is_port_name_valid(port):
335 raise InputError("Port name %s not recognised" % port)
336 if not (self.port_get_mode(port) == "access"):
337 raise InputError("Port %s not in access mode" % port)
338
339 regex = re.compile(r'^Eth%s\s+access\s+(\d+)' % port)
340
341 try:
342 self._cli("show interfaces switchport")
343 for line in self._read_long_output("show interfaces switchport"):
344 match = regex.match(line)
345 if match:
346 vlan = match.group(1)
347 return int(vlan)
348
349 except PExpectError:
350 # recurse on error
351 self._switch_connect()
352 return self.port_get_access_vlan(port)
353
354 # Get the list of configured VLAN tags for a trunk port
355 def port_get_trunk_vlan_list(self, port):
356 logging.debug("Getting VLANs for trunk port %s", port)
357 vlans = []
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 regex_start = re.compile(r'^Eth%s\s+trunk\s+N/A\s+(.*)' % port)
363 regex_continue = re.compile(r'^(\d.*)')
364
365 try:
366 self._cli("show interfaces switchport")
367
368 # Complex parsing work - VLAN list may extend over several lines, e.g.:
369 #
370 # Eth1/16 trunk N/A 1, 102, 103, 104, 1000, 1001, 1002
371 # 1003, 1004
372 #
373 in_match = False
374 vlan_text = ''
375
376 for line in self._read_long_output("show interfaces switchport"):
377 if in_match:
378 match = regex_continue.match(line)
379 if match:
380 vlan_text += ', ' # Make a consistently-formed list
381 vlan_text += match.group(1)
382 else:
383 in_match = False
384 if not in_match:
385 match = regex_start.match(line)
386 if match:
387 vlan_text += match.group(1)
388 in_match = True
389
390 vlans = self._parse_vlan_list(vlan_text)
391 return vlans
392
393 except PExpectError:
394 # recurse on error
395 self._switch_connect()
396 return self.port_get_trunk_vlan_list(port)
397
398 ################################
399 ### Internal functions
400 ################################
401
402 # Connect to the switch and log in
403 def _switch_connect(self):
404
405 if not self.connection is None:
406 self.connection.close(True)
407 self.connection = None
408
409 logging.debug("Connecting to Switch with: %s", self.exec_string)
410 self.connection = pexpect.spawn(self.exec_string, logfile=self.logger)
411 self._login()
412
413 # Avoid paged output as much as possible
414 self._cli("terminal length 999")
415 # Don't do silly things with ANSI codes
416 self._cli("terminal type dumb")
417 # and disable auto-logout after delay
418 self._cli("no cli session auto-logout")
419
420 # And grab details about the switch. in case we need it
421 self._get_systemdata()
422
423 # And also validate them - make sure we're driving a switch of
424 # the correct model! Also store the serial number
425 descr_regex = re.compile(r'Product name:\s*(\S+)')
426 sn_regex = re.compile(r'System serial num:\s*(\S+)')
427 descr = ""
428
429 for line in self._systemdata:
430 match = descr_regex.match(line)
431 if match:
432 descr = match.group(1)
433 match = sn_regex.match(line)
434 if match:
435 self.serial_number = match.group(1)
436
437 logging.debug("serial number is %s", self.serial_number)
438 logging.debug("system description is %s", descr)
439
440 if not self._expected_descr_re.match(descr):
441 raise IOError("Switch %s not recognised by this driver: abort" % descr)
442
443 # Now build a list of our ports, for later sanity checking
444 self._ports = self._get_port_names()
445 if len(self._ports) < 4:
446 raise IOError("Not enough ports detected - problem!")
447
448 def _login(self):
449 logging.debug("attempting login with username %s, password %s", self._username, self._password)
450 if self._username is not None:
451 self.connection.expect("login:")
452 self._cli("%s" % self._username)
453 if self._password is not None:
454 self.connection.expect("Password:")
455 self._cli("%s" % self._password, False)
456 while True:
457 index = self.connection.expect(['User:', 'Password:', 'Login incorrect', 'authentication failed', r'(.*?)(#|>)'])
458 if index != 4: # Any other means: failed to log in!
459 logging.error("Login failure: index %d\n", index)
460 logging.error("Login failure: %s\n", self.connection.match.before)
461 raise IOError
462
463 # else
464
465 # Add a couple of newlines to get past the "last login" etc. junk
466 self._cli("")
467 self._cli("")
468 self.connection.expect(r'^(.*?) (#|>)')
469 self._prompt_name = re.escape(self.connection.match.group(1).strip())
470 logging.info("Got outer prompt \"%s\"", self._prompt_name)
471 if self.connection.match.group(2) == ">":
472 # Need to enter "enable" mode too
473 self._cli("enable")
474 if self._enable_password is not None:
475 self.connection.expect("Password:")
476 self._cli("%s" % self._enable_password, False)
477 index = self.connection.expect(['Password:', 'Login incorrect', 'authentication failed', r'(.*) *(#|>)'])
478 if index != 3: # Any other means: failed to log in!
479 logging.error("Enable password failure: %s\n", self.connection.match)
480 raise IOError
481 self._cli("")
482 self._cli("")
483 self.connection.expect(r'^(.*?) (#|>)')
484 self._prompt_name = re.escape(self.connection.match.group(1).strip())
485 logging.info("Got enable prompt \"%s\"", self._prompt_name)
486 return 0
487
488 def _logout(self):
489 logging.debug("Logging out")
490 self._cli("quit", False)
491 try:
492 self.connection.expect("Would you like to save them now")
493 self._cli("n")
494 except (pexpect.EOF):
495 pass
496 self.connection.close(True)
497
498 def _configure(self):
499 self._cli("configure terminal")
500
501 def _end_configure(self):
502 self._cli("exit")
503
504 def _end_interface(self):
505 self._cli("exit")
506
507 def _end_vlan(self):
508 self._cli("exit")
509
510 def _read_long_output(self, text):
511 longbuf = []
512 prompt = self._prompt_name + r'\s*#'
513 while True:
514 try:
515 index = self.connection.expect([r'lines \d+-\d+', prompt])
516 if index == 0: # "lines 45-50"
517 for line in self.connection.before.split('\r\n'):
518 longbuf.append(line.strip())
519 self._cli(' ', False)
520 elif index == 1: # Back to a prompt, says output is finished
521 break
522 except (pexpect.EOF, pexpect.TIMEOUT):
523 # Something went wrong; logout, log in and try again!
524 logging.error("PEXPECT FAILURE, RECONNECT")
525 self.errors.log_error_in(text)
526 raise PExpectError("_read_long_output failed")
527 except:
528 logging.error("prompt is \"%s\"", prompt)
529 raise
530
531 for line in self.connection.before.split('\r\n'):
532 longbuf.append(line.strip())
533 return longbuf
534
535 def _get_port_names(self):
536 logging.debug("Grabbing list of ports")
537 interfaces = []
538
539 # Look for "Eth1" at the beginning of the output lines to just
540 # match lines with interfaces - they have names like
541 # "Eth1/15". We do not care about Link Aggregation Groups (lag)
542 # here.
543 regex = re.compile(r'^Eth(\S+)')
544
545 try:
546 self._cli("show interfaces switchport")
547 for line in self._read_long_output("show interfaces switchport"):
548 match = regex.match(line)
549 if match:
550 interface = match.group(1)
551 interfaces.append(interface)
552 self._port_numbers[interface] = len(interfaces)
553 logging.debug(" found %d ports on the switch", len(interfaces))
554 return interfaces
555
556 except PExpectError:
557 # recurse on error
558 self._switch_connect()
559 return self._get_port_names()
560
561 # Get the mode of a port: access or trunk
562 def _port_get_mode(self, port):
563 logging.debug("Getting mode of port %s", port)
564 mode = ''
565 if not self._is_port_name_valid(port):
566 raise InputError("Port name %s not recognised" % port)
567 regex = re.compile('Switchport mode: (.*)')
568
569 try:
570 self._cli("show interfaces ethernet %s" % port)
571 for line in self._read_long_output("show interfaces ethernet"):
572 match = regex.match(line)
573 if match:
574 mode = match.group(1)
575 if mode == 'access':
576 return 'access'
577 if mode == 'trunk':
578 return 'trunk'
579 return mode
580
581 except PExpectError:
582 # recurse on error
583 self._switch_connect()
584 return self.port_get_mode(port)
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 # Borrowed from the Catalyst driver. Over-complex for our needs here, but
621 # it's already tested and will do the job.
622 def _parse_vlan_list(self, inputdata):
623 vlans = []
624
625 if inputdata == "ALL":
626 return ["ALL"]
627 elif inputdata == "NONE":
628 return []
629 elif inputdata == "":
630 return []
631 else:
632 # Parse the complex list
633 groups = inputdata.split(',')
634 for group in groups:
635 subgroups = group.split('-')
636 if len(subgroups) == 1:
637 vlans.append(int(subgroups[0]))
638 elif len(subgroups) == 2:
639 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
640 vlans.append(i)
641 else:
642 logging.debug("Can't parse group \"" + group + "\"")
643
644 return vlans
645
646 # Wrapper around connection.send - by default, expect() the same
647 # text we've sent, to remove it from the output from the
648 # switch. For the few cases where we don't need that, override
649 # this using echo=False.
650 # Horrible, but seems to work.
651 def _cli(self, text, echo=True):
652 self.connection.send(text + '\r')
653 if echo:
654 try:
655 self.connection.expect(text)
656 except (pexpect.EOF, pexpect.TIMEOUT):
657 # Something went wrong; logout, log in and try again!
658 logging.error("PEXPECT FAILURE, RECONNECT")
659 self.errors.log_error_out(text)
660 raise PExpectError("_cli failed on %s" % text)
661 except:
662 logging.error("Unexpected error: %s", sys.exc_info()[0])
663 raise
664
665if __name__ == "__main__":
666
667 # Simple test harness - exercise the main working functions above to verify
668 # they work. This does *NOT* test really disruptive things like "save
669 # running-config" and "reload" - test those by hand.
670
671 import optparse
672
673 switch = '172.27.16.6'
674 parser = optparse.OptionParser()
675 parser.add_option("--switch",
676 dest = "switch",
677 action = "store",
678 nargs = 1,
679 type = "string",
680 help = "specify switch to connect to for testing",
681 metavar = "<switch>")
682 (opts, args) = parser.parse_args()
683 if opts.switch:
684 switch = opts.switch
685
686 logging.basicConfig(level = logging.DEBUG,
687 format = '%(asctime)s %(levelname)-8s %(message)s')
688 p = MlnxOS(switch, 23, debug=False)
689 p.switch_connect('admin', 'admin', None)
690
691 print "VLANs are:"
692 buf = p.vlan_get_list()
693 p.dump_list(buf)
694
695 buf = p.vlan_get_name(102)
696 print "VLAN 102 is named \"%s\"" % buf
697
698 print "Create VLAN 1003"
699 p.vlan_create(1003)
700
701 buf = p.vlan_get_name(1003)
702 print "VLAN 1003 is named \"%s\"" % buf
703
704 print "Set name of VLAN 1003 to test333"
705 p.vlan_set_name(1003, "test333")
706
707 buf = p.vlan_get_name(1003)
708 print "VLAN 1003 is named \"%s\"" % buf
709
710 print "VLANs are:"
711 buf = p.vlan_get_list()
712 p.dump_list(buf)
713
714 print "Destroy VLAN 1003"
715 p.vlan_destroy(1003)
716
717 print "VLANs are:"
718 buf = p.vlan_get_list()
719 p.dump_list(buf)
720
721 buf = p.port_get_mode("1/15")
722 print "Port 1/15 is in %s mode" % buf
723
724 buf = p.port_get_mode("1/16")
725 print "Port 1/16 is in %s mode" % buf
726
727 # Test access stuff
728 print "Set 1/15 to access mode"
729 p.port_set_mode("1/15", "access")
730
731 print "Move 1/15 to VLAN 4"
732 p.port_set_access_vlan("1/15", 4)
733
734 buf = p.port_get_access_vlan("1/15")
735 print "Read from switch: 1/15 is on VLAN %s" % buf
736
737 print "Move 1/15 back to VLAN 1"
738 p.port_set_access_vlan("1/15", 1)
739
740 print "Create VLAN 1002"
741 p.vlan_create(1002)
742
743 print "Create VLAN 1003"
744 p.vlan_create(1003)
745
746 print "Create VLAN 1004"
747 p.vlan_create(1004)
748
749 # Test access stuff
750 print "Set 1/15 to trunk mode"
751 p.port_set_mode("1/15", "trunk")
752 print "Read from switch: which VLANs is 1/15 on?"
753 buf = p.port_get_trunk_vlan_list("1/15")
754 p.dump_list(buf)
755
756 # The adds below are NOOPs in effect on this switch - no filtering
757 # for "trunk" ports
758 print "Add 1/15 to VLAN 1002"
759 p.port_add_trunk_to_vlan("1/15", 1002)
760 print "Add 1/15 to VLAN 1003"
761 p.port_add_trunk_to_vlan("1/15", 1003)
762 print "Add 1/15 to VLAN 1004"
763 p.port_add_trunk_to_vlan("1/15", 1004)
764 print "Read from switch: which VLANs is 1/15 on?"
765 buf = p.port_get_trunk_vlan_list("1/15")
766 p.dump_list(buf)
767
768 # And the same for removals here
769 p.port_remove_trunk_from_vlan("1/15", 1003)
770 p.port_remove_trunk_from_vlan("1/15", 1002)
771 p.port_remove_trunk_from_vlan("1/15", 1004)
772 print "Read from switch: which VLANs is 1/15 on?"
773 buf = p.port_get_trunk_vlan_list("1/15")
774 p.dump_list(buf)
775
776# print 'Restarting switch, to explicitly reset config'
777# p.switch_restart()
778
779# p.switch_save_running_config()
780
781# p.switch_disconnect()
782# p._show_config()