aboutsummaryrefslogtreecommitdiff
path: root/vland.py
blob: 25c854ba049f34a5b435917b798e52be66fed251 (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
#! /usr/bin/python

#  Copyright 2014-2018 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.
#
#  Main VLANd module
#

import os, sys
import time
import logging

vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
sys.path.insert(0, vlandpath)

from config.config import VlanConfig
from db.db import VlanDB
from ipc.ipc import VlanIpc
from errors import InputError, NotFoundError, SocketError
from util import VlanUtil
from visualisation.visualisation import Visualisation

class DaemonState:
    """ Simple container for stuff to make for nicer syntax """

state = DaemonState()
state.version = "0.6"
state.banner = "Linaro VLANd version %s" % state.version
state.starttime = time.time()
state.vis = None

os.environ['TZ'] = 'UTC'
time.tzset()

print '%s' % state.banner

print 'Parsing Config...'
state.config = VlanConfig(filenames=('./vland.cfg',))
print '  Config knows about %d switches' % len(state.config.switches)

if state.config.visualisation.enabled:
    state.vis = Visualisation(state)

util = VlanUtil()

loglevel = util.set_logging_level(state.config.logging.level)

# Should we log to stderr?
if state.config.logging.filename is None:
    logging.basicConfig(level = loglevel,
                        format = '%(asctime)s %(levelname)-8s %(message)s')
else:
    print "Logging to %s" % state.config.logging.filename
    logging.basicConfig(level = loglevel,
                        format = '%(asctime)s %(levelname)-8s MAIN %(message)s',
                        datefmt = '%Y-%m-%d %H:%M:%S %Z',
                        filename = state.config.logging.filename,
                        filemode = 'a')

logging.info('%s main daemon starting up', state.banner)
logging.info('Connecting to DB...')
state.db = VlanDB(db_name=state.config.database.dbname,
                  username=state.config.database.username, readonly=False)

switches = state.db.all_switches()
logging.info('DB knows about %d switches', len(switches))
ports = state.db.all_ports()
logging.info('DB knows about %d ports', len(ports))
vlans = state.db.all_vlans()
logging.info('DB knows about %d vlans', len(vlans))
trunks = state.db.all_trunks()
logging.info('DB knows about %d trunks', len(trunks))

# Initial startup sanity chacking

# For sanity, we need to know the vlan_id for the default vlan (tag
# 1). Make sure we know that before anybody attempts to create things
# that depend on it.
state.default_vlan_id = state.db.get_vlan_id_by_tag(state.config.vland.default_vlan_tag)
if state.default_vlan_id is None:
    # It doesn't exist - create it and try again
    state.default_vlan_id = state.db.create_vlan("DEFAULT",
                                                 state.config.vland.default_vlan_tag,
                                                 True)

if len(switches) != len(state.config.switches):
    print 'You have configured access details for %d switch(es), ' % len(state.config.switches)
    print 'but have %d switch(es) registered in your database.' % len(switches)
    print 'You must fix this difference for VLANd to work sensibly.'
    print 'HINT: Running admin.py auto_import_switch --name <switch_name>'
    print 'for each of your switches may help!'
    print

# Now we're done starting up, let the visualisation code talk to the database
if state.config.visualisation.enabled:
    logging.info('sending db_ok signal')
    state.vis.signal_db_ok()

# Now start up the core loop. Listen for command connections and
# process them
state.running = True
ipc = VlanIpc()
ipc.server_init('localhost', state.config.vland.port)
while state.running:
    try:
        ipc.server_listen()
        json_data = ipc.server_recv()
    except SocketError as e:
        print e
        logging.debug('Caught IPC error, ignoring')
        continue
    except:
        ipc.server_close()
        raise

    logging.debug("client %s sent us:", json_data['client_name'])
    logging.debug(json_data)

    response = {}

    # Several types of IPC message here, with potentially different
    # access control and safety

    # First - simple queries to the database only. Should be safe!
    if json_data['type'] == 'db_query':
        response['type'] = 'response'
        try:
            response['data'] = util.perform_db_query(state, json_data['command'], json_data['data'])
            response['response'] = 'ACK'
        except InputError as e:
            print e
            response['response'] = 'ERROR'
            response['error'] = e.__str__()
        except NotFoundError as e:
            print e
            response['response'] = 'NOTFOUND'
            response['error'] = e.__str__()

    # Next - simple queries about daemon state only. Should be safe!
    if json_data['type'] == 'daemon_query':
        response['type'] = 'response'
        try:
            response['data'] = util.perform_daemon_query(state, json_data['command'], json_data['data'])
            response['response'] = 'ACK'
        except InputError as e:
            print e
            response['response'] = 'ERROR'
            response['error'] = e.__str__()
        except NotFoundError as e:
            print e
            response['response'] = 'NOTFOUND'
            response['error'] = e.__str__()

    # Next, calls that manipulate objects in the database only
    # (switches and ports). These are safe and don't need actual
    # co-ordinating with hardware directly.
    #
    # As/when/if we add authentication, use of this function will need
    # it.
    if json_data['type'] == 'db_update':
        response['type'] = 'response'
        try:
            response['data'] = util.perform_db_update(state, json_data['command'], json_data['data'])
            response['response'] = 'ACK'
        except InputError as e:
            print e
            response['response'] = 'ERROR'
            response['error'] = e.__str__()
        except NotFoundError as e:
            print e
            response['response'] = 'NOTFOUND'
            response['error'] = e.__str__()

    # Next, calls that may manipulate switch state *as well* as state
    # in the database - changes to VLAN setup.
    #
    # As/when/if we add authentication, use of this function will need
    # it.
    if json_data['type'] == 'vlan_update':
        response['type'] = 'response'
        try:
            response['data'] = util.perform_vlan_update(state, json_data['command'], json_data['data'])
            response['response'] = 'ACK'
        except InputError as e:
            print e
            response['response'] = 'ERROR'
            response['error'] = e.__str__()
        except NotFoundError as e:
            print e
            response['response'] = 'NOTFOUND'
            response['error'] = e.__str__()

    # Finally, IPC interface for more complex API calls.
    # NOT IMPLEMENTED YET
    if json_data['type'] == 'vland_api':
        response['type'] = 'response'
        response['response'] = 'ERROR'
        response['error'] = 'VLANd API not yet implemented...'

    logging.debug("sending reply:")
    logging.debug(response)

    ipc.server_reply(response)

# We've been asked to shut down. Do that as cleanly as we can
ipc.server_close()

if state.config.visualisation.enabled:
    state.vis.shutdown()

logging.info('%s shutting down', state.banner)
switches = state.db.all_switches()
logging.info('  DB knows about %d switches', len(switches))
ports = state.db.all_ports()
logging.info('  DB knows about %d ports', len(ports))
vlans = state.db.all_vlans()
logging.info('  DB knows about %d vlans', len(vlans))
trunks = state.db.all_trunks()
logging.info('  DB knows about %d trunks', len(trunks))

logging.shutdown()