blob: f630184e61fad37958135056d69d7f80848f36a7 [file] [log] [blame]
Steve McIntyre96f779b2015-10-09 16:32:00 +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
Steve McIntyre96f779b2015-10-09 16:32:00 +010021import sys
22import re
23import pickle
Steve McIntyre4267c6a2018-01-24 16:57:28 +000024import pexpect
Steve McIntyre96f779b2015-10-09 16:32:00 +010025
26# Dummy switch driver, designed specifically for
27# testing/validation. Just remembers what it's been told and gives the
28# same data back on demand.
29#
30# To keep track of data in the dummy switch, this code will simply
31# dump out and read back its internal state to/from a Python pickle
32# file as needed. On first use, if no such file exists then the Dummy
33# driver will simply generate a simple switch model:
34#
35# * N ports in access mode
36# * 1 VLAN (tag 1) labelled DEFAULT
37#
38# The "hostname" given to the switch in VLANd is important, as it will
39# determine both the number of ports allocated in this model and the
40# name of the pickle file used for data storage. Call the switch
41# "dummy-N" in your vland.cfg file to have N ports. If you want to use
42# more than one dummy switch instance, ensure you give them different
43# numbers, e.g. "dummy-25", "dummy-48", etc.
44
45if __name__ == '__main__':
46 import os
47 vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
48 sys.path.insert(0, vlandpath)
49 sys.path.insert(0, "%s/.." % vlandpath)
50
51from errors import InputError, PExpectError
52from drivers.common import SwitchDriver, SwitchErrors
53
54class Dummy(SwitchDriver):
55
56 connection = None
57 _username = None
58 _password = None
59 _enable_password = None
60 _dummy_vlans = {}
61 _dummy_ports = {}
62 _state_file = None
63
64 _capabilities = [
65 ]
66
67 def __init__(self, switch_hostname, switch_telnetport=23, debug = False):
68 SwitchDriver.__init__(self, switch_hostname, debug)
69 self._systemdata = []
70 self.errors = SwitchErrors()
71 self._state_file = "%s.pk" % switch_hostname
72
73 ################################
74 ### Switch-level API functions
75 ################################
76
77 # Save the current running config - we want config to remain
78 # across reboots
79 def switch_save_running_config(self):
80 pass
81
82 # Restart the switch - we need to reload config to do a
83 # roll-back. Do NOT save running-config first if the switch asks -
84 # we're trying to dump recent changes, not save them.
85 def switch_restart(self):
86 pass
87
88 # List the capabilities of the switch (and driver) - some things
89 # make no sense to abstract. Returns a dict of strings, each one
90 # describing an extra feature that that higher levels may care
91 # about
92 def switch_get_capabilities(self):
93 return self._capabilities
94
95 ################################
96 ### VLAN API functions
97 ################################
98
99 # Create a VLAN with the specified tag
100 def vlan_create(self, tag):
101 logging.debug("Creating VLAN %d", tag)
102 if not tag in self._dummy_vlans:
103 self._dummy_vlans[tag] = "VLAN%s" % tag
104 else:
105 # It's not an error if it already exists, but log anyway
106 logging.debug("VLAN %d already exists, name %s",
107 tag, self._dummy_vlans[tag])
108
109 # Destroy a VLAN with the specified tag
110 def vlan_destroy(self, tag):
111 logging.debug("Destroying VLAN %d", tag)
112 if tag in self._dummy_vlans:
113 del self._dummy_vlans[tag]
114 else:
115 # It's not an error if it doesn't exist, but log anyway
Steve McIntyree187fbe2015-10-13 18:34:27 +0100116 logging.debug("VLAN %d did not exist", tag)
Steve McIntyre96f779b2015-10-09 16:32:00 +0100117
118 # Set the name of a VLAN
119 def vlan_set_name(self, tag, name):
120 logging.debug("Setting name of VLAN %d to %s", tag, name)
121 if not tag in self._dummy_vlans:
122 raise InputError("Tag %d does not exist")
123 self._dummy_vlans[tag] = "VLAN%s" % tag
124
125 # Get a list of the VLAN tags currently registered on the switch
126 def vlan_get_list(self):
127 logging.debug("Grabbing list of VLANs")
128 return sorted(self._dummy_vlans.keys())
129
130 # For a given VLAN tag, ask the switch what the associated name is
131 def vlan_get_name(self, tag):
132 logging.debug("Grabbing the name of VLAN %d", tag)
133 if not tag in self._dummy_vlans:
134 raise InputError("Tag %d does not exist")
135 return self._dummy_vlans[tag]
136
137 ################################
138 ### Port API functions
139 ################################
140
141 # Set the mode of a port: access or trunk
142 def port_set_mode(self, port, mode):
143 logging.debug("Setting port %s to %s mode", port, mode)
144 if not port in self._dummy_ports:
145 raise InputError("Port %s does not exist" % port)
146 self._dummy_ports[port]['mode'] = mode
147
148 # Get the mode of a port: access or trunk
149 def port_get_mode(self, port):
150 logging.debug("Getting mode of port %s", port)
151 if not port in self._dummy_ports:
152 raise InputError("Port %s does not exist" % port)
153 return self._dummy_ports[port]['mode']
154
155 # Set an access port to be in a specified VLAN (tag)
156 def port_set_access_vlan(self, port, tag):
157 logging.debug("Setting access port %s to VLAN %d", port, tag)
158 if not port in self._dummy_ports:
159 raise InputError("Port %s does not exist" % port)
160 if not tag in self._dummy_vlans:
161 raise InputError("VLAN %d does not exist" % tag)
162 self._dummy_ports[port]['access_vlan'] = tag
163
164 # Add a trunk port to a specified VLAN (tag)
165 def port_add_trunk_to_vlan(self, port, tag):
166 logging.debug("Adding trunk port %s to VLAN %d", port, tag)
167 if not port in self._dummy_ports:
168 raise InputError("Port %s does not exist" % port)
169 if not tag in self._dummy_vlans:
170 raise InputError("VLAN %d does not exist" % tag)
171 self._dummy_ports[port]['trunk_vlans'].append(tag)
172
173 # Remove a trunk port from a specified VLAN (tag)
174 def port_remove_trunk_from_vlan(self, port, tag):
175 logging.debug("Removing trunk port %s from VLAN %d", port, tag)
176 if not port in self._dummy_ports:
177 raise InputError("Port %s does not exist" % port)
178 if not tag in self._dummy_vlans:
179 raise InputError("VLAN %d does not exist" % tag)
180 self._dummy_ports[port]['trunk_vlans'].remove(tag)
181
182 # Get the configured VLAN tag for an access port (tag)
183 def port_get_access_vlan(self, port):
184 logging.debug("Getting VLAN for access port %s", port)
185 if not port in self._dummy_ports:
186 raise InputError("Port %s does not exist" % port)
187 return self._dummy_ports[port]['access_vlan']
188
189 # Get the list of configured VLAN tags for a trunk port
190 def port_get_trunk_vlan_list(self, port):
191 logging.debug("Getting VLAN(s) for trunk port %s", port)
192 if not port in self._dummy_ports:
193 raise InputError("Port %s does not exist" % port)
194 return sorted(self._dummy_ports[port]['trunk_vlans'])
195
196 ################################
197 ### Internal functions
198 ################################
199
200 # Connect to the switch and log in
201 def _switch_connect(self):
202 # Open data file if it exists, otherwise initialise
203 try:
204 pkl_file = open(self._state_file, 'rb')
205 self._dummy_vlans = pickle.load(pkl_file)
206 self._dummy_ports = pickle.load(pkl_file)
207 pkl_file.close()
208 except:
209 # Create data here
210 self._dummy_vlans = {1: 'DEFAULT'}
Steve McIntyre959b8722015-10-13 18:33:42 +0100211 match = re.match(r'dummy-(\d+)', self.hostname)
Steve McIntyre96f779b2015-10-09 16:32:00 +0100212 if match:
213 num_ports = int(match.group(1))
214 else:
215 raise InputError("Unable to determine number of ports from switch name")
216 for i in range(1, num_ports+1):
217 port_name = "dm%2.2d" % int(i)
218 self._dummy_ports[port_name] = {}
219 self._dummy_ports[port_name]['mode'] = 'access'
220 self._dummy_ports[port_name]['access_vlan'] = 1
221 self._dummy_ports[port_name]['trunk_vlans'] = []
222
223 # Now build a list of our ports, for later sanity checking
224 self._ports = self._get_port_names()
225 if len(self._ports) < 4:
226 raise IOError("Not enough ports detected - problem!")
227
228 def _logout(self):
229 pkl_file = open(self._state_file, 'wb')
230 pickle.dump(self._dummy_vlans, pkl_file)
231 pickle.dump(self._dummy_ports, pkl_file)
232 pkl_file.close()
233
234 def _get_port_names(self):
235 logging.debug("Grabbing list of ports")
236 interfaces = []
237 for interface in sorted(self._dummy_ports.keys()):
238 interfaces.append(interface)
239 self._port_numbers[interface] = len(interfaces)
240 return interfaces
241
242if __name__ == "__main__":
243
Steve McIntyreda9e9c12018-01-24 16:58:13 +0000244 # Simple test harness - exercise the main working functions above to verify
245 # they work. This does *NOT* test really disruptive things like "save
246 # running-config" and "reload" - test those by hand.
247
Steve McIntyre96f779b2015-10-09 16:32:00 +0100248 import optparse
249
250 switch = 'dummy-48'
251 parser = optparse.OptionParser()
252 parser.add_option("--switch",
253 dest = "switch",
254 action = "store",
255 nargs = 1,
256 type = "string",
257 help = "specify switch to connect to for testing",
258 metavar = "<switch>")
259 (opts, args) = parser.parse_args()
260 if opts.switch:
261 switch = opts.switch
262
263 logging.basicConfig(level = logging.DEBUG,
264 format = '%(asctime)s %(levelname)-8s %(message)s')
265 p = Dummy(switch, 23, debug=True)
266 p.switch_connect('admin', '', None)
267
268 print "VLANs are:"
269 buf = p.vlan_get_list()
270 p.dump_list(buf)
271
272 buf = p.vlan_get_name(1)
273 print "VLAN 1 is named \"%s\"" % buf
274
275 print "Create VLAN 3"
276 p.vlan_create(3)
277
278 print "Create VLAN 4"
279 p.vlan_create(4)
280
281 buf = p.vlan_get_name(3)
282 print "VLAN 3 is named \"%s\"" % buf
283
284 print "Set name of VLAN 3 to test333"
285 p.vlan_set_name(3, "test333")
286
287 buf = p.vlan_get_name(3)
288 print "VLAN 3 is named \"%s\"" % buf
289
290 print "VLANs are:"
291 buf = p.vlan_get_list()
292 p.dump_list(buf)
293
294 print "Destroy VLAN 3"
295 p.vlan_destroy(3)
296
297 print "VLANs are:"
298 buf = p.vlan_get_list()
299 p.dump_list(buf)
300
301 buf = p.port_get_mode("dm10")
302 print "Port dm10 is in %s mode" % buf
303
304 buf = p.port_get_mode("dm11")
305 print "Port dm11 is in %s mode" % buf
306
307 # Test access stuff
308 print "Set dm09 to access mode"
309 p.port_set_mode("dm09", "access")
310
311 print "Move dm9 to VLAN 4"
312 p.port_set_access_vlan("dm09", 4)
313
314 buf = p.port_get_access_vlan("dm09")
315 print "Read from switch: dm09 is on VLAN %s" % buf
316
317 print "Move dm09 back to VLAN 1"
318 p.port_set_access_vlan("dm09", 1)
319
320 print "Create VLAN 2"
321 p.vlan_create(2)
322
323 print "Create VLAN 3"
324 p.vlan_create(3)
325
326 print "Create VLAN 4"
327 p.vlan_create(4)
328
329 # Test access stuff
330 print "Set dm09 to trunk mode"
331 p.port_set_mode("dm09", "trunk")
332 print "Read from switch: which VLANs is dm09 on?"
333 buf = p.port_get_trunk_vlan_list("dm09")
334 p.dump_list(buf)
335
336 # The adds below are NOOPs in effect on this switch - no filtering
337 # for "trunk" ports
338 print "Add dm09 to VLAN 2"
339 p.port_add_trunk_to_vlan("dm09", 2)
340 print "Add dm09 to VLAN 3"
341 p.port_add_trunk_to_vlan("dm09", 3)
342 print "Add dm09 to VLAN 4"
343 p.port_add_trunk_to_vlan("dm09", 4)
344 print "Read from switch: which VLANs is dm09 on?"
345 buf = p.port_get_trunk_vlan_list("dm09")
346 p.dump_list(buf)
347
348 # And the same for removals here
349 p.port_remove_trunk_from_vlan("dm09", 3)
350 p.port_remove_trunk_from_vlan("dm09", 2)
351 p.port_remove_trunk_from_vlan("dm09", 4)
352 print "Read from switch: which VLANs is dm09 on?"
353 buf = p.port_get_trunk_vlan_list("dm09")
354 p.dump_list(buf)
355
356 print 'Restarting switch, to explicitly reset config'
357 p.switch_restart()
358
359 p.switch_save_running_config()
360
361 p.switch_disconnect()