Steve McIntyre | 96f779b | 2015-10-09 16:32:00 +0100 | [diff] [blame] | 1 | #! /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 | |
| 20 | import logging |
Steve McIntyre | 96f779b | 2015-10-09 16:32:00 +0100 | [diff] [blame] | 21 | import sys |
| 22 | import re |
| 23 | import pickle |
Steve McIntyre | 4267c6a | 2018-01-24 16:57:28 +0000 | [diff] [blame] | 24 | import pexpect |
Steve McIntyre | 96f779b | 2015-10-09 16:32:00 +0100 | [diff] [blame] | 25 | |
| 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 | |
| 45 | if __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 | |
| 51 | from errors import InputError, PExpectError |
| 52 | from drivers.common import SwitchDriver, SwitchErrors |
| 53 | |
| 54 | class 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 McIntyre | e187fbe | 2015-10-13 18:34:27 +0100 | [diff] [blame] | 116 | logging.debug("VLAN %d did not exist", tag) |
Steve McIntyre | 96f779b | 2015-10-09 16:32:00 +0100 | [diff] [blame] | 117 | |
| 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 McIntyre | 959b872 | 2015-10-13 18:33:42 +0100 | [diff] [blame] | 211 | match = re.match(r'dummy-(\d+)', self.hostname) |
Steve McIntyre | 96f779b | 2015-10-09 16:32:00 +0100 | [diff] [blame] | 212 | 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 | |
| 242 | if __name__ == "__main__": |
| 243 | |
Steve McIntyre | da9e9c1 | 2018-01-24 16:58:13 +0000 | [diff] [blame] | 244 | # 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 McIntyre | 96f779b | 2015-10-09 16:32:00 +0100 | [diff] [blame] | 248 | 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() |