aboutsummaryrefslogtreecommitdiff
path: root/drivers/Dummy.py
blob: f630184e61fad37958135056d69d7f80848f36a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#! /usr/bin/python

#  Copyright 2015 Linaro Limited
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.

import logging
import sys
import re
import pickle
import pexpect

# Dummy switch driver, designed specifically for
# testing/validation. Just remembers what it's been told and gives the
# same data back on demand.
#
# To keep track of data in the dummy switch, this code will simply
# dump out and read back its internal state to/from a Python pickle
# file as needed. On first use, if no such file exists then the Dummy
# driver will simply generate a simple switch model:
#
#  * N ports in access mode
#  * 1 VLAN (tag 1) labelled DEFAULT
#
# The "hostname" given to the switch in VLANd is important, as it will
# determine both the number of ports allocated in this model and the
# name of the pickle file used for data storage. Call the switch
# "dummy-N" in your vland.cfg file to have N ports. If you want to use
# more than one dummy switch instance, ensure you give them different
# numbers, e.g. "dummy-25", "dummy-48", etc.

if __name__ == '__main__':
    import os
    vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
    sys.path.insert(0, vlandpath)
    sys.path.insert(0, "%s/.." % vlandpath)

from errors import InputError, PExpectError
from drivers.common import SwitchDriver, SwitchErrors

class Dummy(SwitchDriver):

    connection = None
    _username = None
    _password = None
    _enable_password = None
    _dummy_vlans = {}
    _dummy_ports = {}
    _state_file = None

    _capabilities = [
    ]

    def __init__(self, switch_hostname, switch_telnetport=23, debug = False):
        SwitchDriver.__init__(self, switch_hostname, debug)
        self._systemdata = []
        self.errors = SwitchErrors()
        self._state_file = "%s.pk" % switch_hostname

    ################################
    ### Switch-level API functions
    ################################

    # Save the current running config - we want config to remain
    # across reboots
    def switch_save_running_config(self):
        pass

    # Restart the switch - we need to reload config to do a
    # roll-back. Do NOT save running-config first if the switch asks -
    # we're trying to dump recent changes, not save them.
    def switch_restart(self):
        pass

    # List the capabilities of the switch (and driver) - some things
    # make no sense to abstract. Returns a dict of strings, each one
    # describing an extra feature that that higher levels may care
    # about
    def switch_get_capabilities(self):
        return self._capabilities

    ################################
    ### VLAN API functions
    ################################

    # Create a VLAN with the specified tag
    def vlan_create(self, tag):
        logging.debug("Creating VLAN %d", tag)
        if not tag in self._dummy_vlans:
            self._dummy_vlans[tag] = "VLAN%s" % tag
        else:
            # It's not an error if it already exists, but log anyway
            logging.debug("VLAN %d already exists, name %s",
                          tag, self._dummy_vlans[tag])

    # Destroy a VLAN with the specified tag
    def vlan_destroy(self, tag):
        logging.debug("Destroying VLAN %d", tag)
        if tag in self._dummy_vlans:
            del self._dummy_vlans[tag]
        else:
            # It's not an error if it doesn't exist, but log anyway
            logging.debug("VLAN %d did not exist", tag)

    # Set the name of a VLAN
    def vlan_set_name(self, tag, name):
        logging.debug("Setting name of VLAN %d to %s", tag, name)
        if not tag in self._dummy_vlans:
            raise InputError("Tag %d does not exist")
        self._dummy_vlans[tag] = "VLAN%s" % tag

    # Get a list of the VLAN tags currently registered on the switch
    def vlan_get_list(self):
        logging.debug("Grabbing list of VLANs")
        return sorted(self._dummy_vlans.keys())

    # For a given VLAN tag, ask the switch what the associated name is
    def vlan_get_name(self, tag):
        logging.debug("Grabbing the name of VLAN %d", tag)
        if not tag in self._dummy_vlans:
            raise InputError("Tag %d does not exist")
        return self._dummy_vlans[tag]

    ################################
    ### Port API functions
    ################################

    # Set the mode of a port: access or trunk
    def port_set_mode(self, port, mode):
        logging.debug("Setting port %s to %s mode", port, mode)
        if not port in self._dummy_ports:
            raise InputError("Port %s does not exist" % port)
        self._dummy_ports[port]['mode'] = mode

    # Get the mode of a port: access or trunk
    def port_get_mode(self, port):
        logging.debug("Getting mode of port %s", port)
        if not port in self._dummy_ports:
            raise InputError("Port %s does not exist" % port)
        return self._dummy_ports[port]['mode']

    # Set an access port to be in a specified VLAN (tag)
    def port_set_access_vlan(self, port, tag):
        logging.debug("Setting access port %s to VLAN %d", port, tag)
        if not port in self._dummy_ports:
            raise InputError("Port %s does not exist" % port)
        if not tag in self._dummy_vlans:
            raise InputError("VLAN %d does not exist" % tag)
        self._dummy_ports[port]['access_vlan'] = tag

    # Add a trunk port to a specified VLAN (tag)
    def port_add_trunk_to_vlan(self, port, tag):
        logging.debug("Adding trunk port %s to VLAN %d", port, tag)
        if not port in self._dummy_ports:
            raise InputError("Port %s does not exist" % port)
        if not tag in self._dummy_vlans:
            raise InputError("VLAN %d does not exist" % tag)
        self._dummy_ports[port]['trunk_vlans'].append(tag)

    # Remove a trunk port from a specified VLAN (tag)
    def port_remove_trunk_from_vlan(self, port, tag):
        logging.debug("Removing trunk port %s from VLAN %d", port, tag)
        if not port in self._dummy_ports:
            raise InputError("Port %s does not exist" % port)
        if not tag in self._dummy_vlans:
            raise InputError("VLAN %d does not exist" % tag)
        self._dummy_ports[port]['trunk_vlans'].remove(tag)

    # Get the configured VLAN tag for an access port (tag)
    def port_get_access_vlan(self, port):
        logging.debug("Getting VLAN for access port %s", port)
        if not port in self._dummy_ports:
            raise InputError("Port %s does not exist" % port)
        return self._dummy_ports[port]['access_vlan']

    # Get the list of configured VLAN tags for a trunk port
    def port_get_trunk_vlan_list(self, port):
        logging.debug("Getting VLAN(s) for trunk port %s", port)
        if not port in self._dummy_ports:
            raise InputError("Port %s does not exist" % port)
        return sorted(self._dummy_ports[port]['trunk_vlans'])

    ################################
    ### Internal functions
    ################################

    # Connect to the switch and log in
    def _switch_connect(self):
        # Open data file if it exists, otherwise initialise
        try:
            pkl_file = open(self._state_file, 'rb')
            self._dummy_vlans = pickle.load(pkl_file)
            self._dummy_ports = pickle.load(pkl_file)
            pkl_file.close()
        except:
            # Create data here
            self._dummy_vlans = {1: 'DEFAULT'}
            match = re.match(r'dummy-(\d+)', self.hostname)
            if match:
                num_ports = int(match.group(1))
            else:
                raise InputError("Unable to determine number of ports from switch name")
            for i in range(1, num_ports+1):
                port_name = "dm%2.2d" % int(i)
                self._dummy_ports[port_name] = {}
                self._dummy_ports[port_name]['mode'] = 'access'
                self._dummy_ports[port_name]['access_vlan'] = 1
                self._dummy_ports[port_name]['trunk_vlans'] = []

        # Now build a list of our ports, for later sanity checking
        self._ports = self._get_port_names()
        if len(self._ports) < 4:
            raise IOError("Not enough ports detected - problem!")

    def _logout(self):
        pkl_file = open(self._state_file, 'wb')
        pickle.dump(self._dummy_vlans, pkl_file)
        pickle.dump(self._dummy_ports, pkl_file)
        pkl_file.close()

    def _get_port_names(self):
        logging.debug("Grabbing list of ports")
        interfaces = []
        for interface in sorted(self._dummy_ports.keys()):
            interfaces.append(interface)
            self._port_numbers[interface] = len(interfaces)
        return interfaces

if __name__ == "__main__":

    # Simple test harness - exercise the main working functions above to verify
    # they work. This does *NOT* test really disruptive things like "save
    # running-config" and "reload" - test those by hand.

    import optparse

    switch = 'dummy-48'
    parser = optparse.OptionParser()
    parser.add_option("--switch",
                      dest = "switch",
                      action = "store",
                      nargs = 1,
                      type = "string",
                      help = "specify switch to connect to for testing",
                      metavar = "<switch>")
    (opts, args) = parser.parse_args()
    if opts.switch:
        switch = opts.switch

    logging.basicConfig(level = logging.DEBUG,
                        format = '%(asctime)s %(levelname)-8s %(message)s')
    p = Dummy(switch, 23, debug=True)
    p.switch_connect('admin', '', None)

    print "VLANs are:"
    buf = p.vlan_get_list()
    p.dump_list(buf)

    buf = p.vlan_get_name(1)
    print "VLAN 1 is named \"%s\"" % buf

    print "Create VLAN 3"
    p.vlan_create(3)

    print "Create VLAN 4"
    p.vlan_create(4)

    buf = p.vlan_get_name(3)
    print "VLAN 3 is named \"%s\"" % buf

    print "Set name of VLAN 3 to test333"
    p.vlan_set_name(3, "test333")

    buf = p.vlan_get_name(3)
    print "VLAN 3 is named \"%s\"" % buf

    print "VLANs are:"
    buf = p.vlan_get_list()
    p.dump_list(buf)

    print "Destroy VLAN 3"
    p.vlan_destroy(3)

    print "VLANs are:"
    buf = p.vlan_get_list()
    p.dump_list(buf)

    buf = p.port_get_mode("dm10")
    print "Port dm10 is in %s mode" % buf

    buf = p.port_get_mode("dm11")
    print "Port dm11 is in %s mode" % buf

    # Test access stuff
    print "Set dm09 to access mode"
    p.port_set_mode("dm09", "access")

    print "Move dm9 to VLAN 4"
    p.port_set_access_vlan("dm09", 4)

    buf = p.port_get_access_vlan("dm09")
    print "Read from switch: dm09 is on VLAN %s" % buf

    print "Move dm09 back to VLAN 1"
    p.port_set_access_vlan("dm09", 1)

    print "Create VLAN 2"
    p.vlan_create(2)

    print "Create VLAN 3"
    p.vlan_create(3)

    print "Create VLAN 4"
    p.vlan_create(4)

    # Test access stuff
    print "Set dm09 to trunk mode"
    p.port_set_mode("dm09", "trunk")
    print "Read from switch: which VLANs is dm09 on?"
    buf = p.port_get_trunk_vlan_list("dm09")
    p.dump_list(buf)

    # The adds below are NOOPs in effect on this switch - no filtering
    # for "trunk" ports
    print "Add dm09 to VLAN 2"
    p.port_add_trunk_to_vlan("dm09", 2)
    print "Add dm09 to VLAN 3"
    p.port_add_trunk_to_vlan("dm09", 3)
    print "Add dm09 to VLAN 4"
    p.port_add_trunk_to_vlan("dm09", 4)
    print "Read from switch: which VLANs is dm09 on?"
    buf = p.port_get_trunk_vlan_list("dm09")
    p.dump_list(buf)

    # And the same for removals here
    p.port_remove_trunk_from_vlan("dm09", 3)
    p.port_remove_trunk_from_vlan("dm09", 2)
    p.port_remove_trunk_from_vlan("dm09", 4)
    print "Read from switch: which VLANs is dm09 on?"
    buf = p.port_get_trunk_vlan_list("dm09")
    p.dump_list(buf)

    print 'Restarting switch, to explicitly reset config'
    p.switch_restart()

    p.switch_save_running_config()

    p.switch_disconnect()