blob: 25c854ba049f34a5b435917b798e52be66fed251 [file] [log] [blame]
Steve McIntyree5043dd2014-12-10 16:49:28 +00001#! /usr/bin/python
2
Steve McIntyre67940882018-01-31 17:07:39 +00003# Copyright 2014-2018 Linaro Limited
Steve McIntyree5043dd2014-12-10 16:49:28 +00004#
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# Main VLANd module
21#
22
Steve McIntyre1cb0b482014-12-22 17:18:11 +000023import os, sys
Steve McIntyree5043dd2014-12-10 16:49:28 +000024import time
25import logging
26
27vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
28sys.path.insert(0, vlandpath)
29
Steve McIntyree5043dd2014-12-10 16:49:28 +000030from config.config import VlanConfig
31from db.db import VlanDB
Steve McIntyre6c44fb22014-12-12 22:11:45 +000032from ipc.ipc import VlanIpc
Steve McIntyreb01959f2016-03-22 17:02:39 +000033from errors import InputError, NotFoundError, SocketError
Steve McIntyref1c04f92014-12-16 18:23:15 +000034from util import VlanUtil
Steve McIntyre71aa0ac2015-09-23 18:34:02 +010035from visualisation.visualisation import Visualisation
Steve McIntyree5043dd2014-12-10 16:49:28 +000036
Steve McIntyre231354b2014-12-16 19:22:12 +000037class DaemonState:
38 """ Simple container for stuff to make for nicer syntax """
Steve McIntyree5043dd2014-12-10 16:49:28 +000039
Steve McIntyre231354b2014-12-16 19:22:12 +000040state = DaemonState()
Steve McIntyrec03d68d2016-03-24 17:38:34 +000041state.version = "0.6"
Steve McIntyre231354b2014-12-16 19:22:12 +000042state.banner = "Linaro VLANd version %s" % state.version
43state.starttime = time.time()
Steve McIntyre71aa0ac2015-09-23 18:34:02 +010044state.vis = None
45
46os.environ['TZ'] = 'UTC'
47time.tzset()
Steve McIntyre231354b2014-12-16 19:22:12 +000048
49print '%s' % state.banner
Steve McIntyree5043dd2014-12-10 16:49:28 +000050
51print 'Parsing Config...'
Steve McIntyre231354b2014-12-16 19:22:12 +000052state.config = VlanConfig(filenames=('./vland.cfg',))
53print ' Config knows about %d switches' % len(state.config.switches)
Steve McIntyree5043dd2014-12-10 16:49:28 +000054
Steve McIntyre71aa0ac2015-09-23 18:34:02 +010055if state.config.visualisation.enabled:
56 state.vis = Visualisation(state)
57
Steve McIntyref1c04f92014-12-16 18:23:15 +000058util = VlanUtil()
59
Steve McIntyre67940882018-01-31 17:07:39 +000060loglevel = util.set_logging_level(state.config.logging.level)
61
62# Should we log to stderr?
63if state.config.logging.filename is None:
64 logging.basicConfig(level = loglevel,
65 format = '%(asctime)s %(levelname)-8s %(message)s')
66else:
67 print "Logging to %s" % state.config.logging.filename
68 logging.basicConfig(level = loglevel,
69 format = '%(asctime)s %(levelname)-8s MAIN %(message)s',
70 datefmt = '%Y-%m-%d %H:%M:%S %Z',
71 filename = state.config.logging.filename,
72 filemode = 'a')
73
74logging.info('%s main daemon starting up', state.banner)
75logging.info('Connecting to DB...')
Steve McIntyre283d5f22014-12-17 13:12:04 +000076state.db = VlanDB(db_name=state.config.database.dbname,
Steve McIntyreea343aa2015-10-23 17:46:17 +010077 username=state.config.database.username, readonly=False)
Steve McIntyree5043dd2014-12-10 16:49:28 +000078
Steve McIntyre283d5f22014-12-17 13:12:04 +000079switches = state.db.all_switches()
Steve McIntyre67940882018-01-31 17:07:39 +000080logging.info('DB knows about %d switches', len(switches))
Steve McIntyre283d5f22014-12-17 13:12:04 +000081ports = state.db.all_ports()
Steve McIntyre67940882018-01-31 17:07:39 +000082logging.info('DB knows about %d ports', len(ports))
Steve McIntyre283d5f22014-12-17 13:12:04 +000083vlans = state.db.all_vlans()
Steve McIntyre67940882018-01-31 17:07:39 +000084logging.info('DB knows about %d vlans', len(vlans))
Steve McIntyrec4890132015-08-07 15:19:11 +010085trunks = state.db.all_trunks()
Steve McIntyre67940882018-01-31 17:07:39 +000086logging.info('DB knows about %d trunks', len(trunks))
Steve McIntyree5043dd2014-12-10 16:49:28 +000087
Steve McIntyreae286192014-12-12 22:10:43 +000088# Initial startup sanity chacking
Steve McIntyree5043dd2014-12-10 16:49:28 +000089
Steve McIntyrec12f6312014-12-15 15:00:12 +000090# For sanity, we need to know the vlan_id for the default vlan (tag
91# 1). Make sure we know that before anybody attempts to create things
92# that depend on it.
Steve McIntyre283d5f22014-12-17 13:12:04 +000093state.default_vlan_id = state.db.get_vlan_id_by_tag(state.config.vland.default_vlan_tag)
94if state.default_vlan_id is None:
Steve McIntyrec12f6312014-12-15 15:00:12 +000095 # It doesn't exist - create it and try again
Steve McIntyre283d5f22014-12-17 13:12:04 +000096 state.default_vlan_id = state.db.create_vlan("DEFAULT",
97 state.config.vland.default_vlan_tag,
98 True)
Steve McIntyrec12f6312014-12-15 15:00:12 +000099
Steve McIntyre231354b2014-12-16 19:22:12 +0000100if len(switches) != len(state.config.switches):
101 print 'You have configured access details for %d switch(es), ' % len(state.config.switches)
Steve McIntyre96855db2014-12-15 16:52:00 +0000102 print 'but have %d switch(es) registered in your database.' % len(switches)
Steve McIntyreae286192014-12-12 22:10:43 +0000103 print 'You must fix this difference for VLANd to work sensibly.'
Steve McIntyre4ba7b412016-03-12 11:58:23 +0000104 print 'HINT: Running admin.py auto_import_switch --name <switch_name>'
Steve McIntyrebd7ce372014-12-17 16:27:41 +0000105 print 'for each of your switches may help!'
Steve McIntyreae286192014-12-12 22:10:43 +0000106 print
Steve McIntyree5043dd2014-12-10 16:49:28 +0000107
Steve McIntyrebc06c932018-01-31 17:04:29 +0000108# Now we're done starting up, let the visualisation code talk to the database
109if state.config.visualisation.enabled:
110 logging.info('sending db_ok signal')
111 state.vis.signal_db_ok()
112
Steve McIntyre6c44fb22014-12-12 22:11:45 +0000113# Now start up the core loop. Listen for command connections and
114# process them
Steve McIntyre06fe6422015-01-23 17:55:43 +0000115state.running = True
Steve McIntyre6c44fb22014-12-12 22:11:45 +0000116ipc = VlanIpc()
Steve McIntyre231354b2014-12-16 19:22:12 +0000117ipc.server_init('localhost', state.config.vland.port)
Steve McIntyre06fe6422015-01-23 17:55:43 +0000118while state.running:
Steve McIntyref1c04f92014-12-16 18:23:15 +0000119 try:
120 ipc.server_listen()
121 json_data = ipc.server_recv()
122 except SocketError as e:
123 print e
Steve McIntyre7cf80982015-02-12 07:03:40 +0000124 logging.debug('Caught IPC error, ignoring')
Steve McIntyref1c04f92014-12-16 18:23:15 +0000125 continue
Steve McIntyref1c04f92014-12-16 18:23:15 +0000126 except:
127 ipc.server_close()
128 raise
Steve McIntyre6c44fb22014-12-12 22:11:45 +0000129
Steve McIntyre5fa22652015-04-01 18:01:45 +0100130 logging.debug("client %s sent us:", json_data['client_name'])
Steve McIntyreeb0a4b82015-02-12 07:06:56 +0000131 logging.debug(json_data)
Steve McIntyre6c44fb22014-12-12 22:11:45 +0000132
Steve McIntyref1c04f92014-12-16 18:23:15 +0000133 response = {}
134
Steve McIntyre0ebf5832014-12-16 19:23:10 +0000135 # Several types of IPC message here, with potentially different
136 # access control and safety
137
138 # First - simple queries to the database only. Should be safe!
139 if json_data['type'] == 'db_query':
140 response['type'] = 'response'
Steve McIntyref1c04f92014-12-16 18:23:15 +0000141 try:
Steve McIntyre208241a2014-12-17 13:12:26 +0000142 response['data'] = util.perform_db_query(state, json_data['command'], json_data['data'])
Steve McIntyre0ebf5832014-12-16 19:23:10 +0000143 response['response'] = 'ACK'
144 except InputError as e:
145 print e
146 response['response'] = 'ERROR'
147 response['error'] = e.__str__()
Steve McIntyreb01959f2016-03-22 17:02:39 +0000148 except NotFoundError as e:
149 print e
150 response['response'] = 'NOTFOUND'
151 response['error'] = e.__str__()
Steve McIntyre0ebf5832014-12-16 19:23:10 +0000152
153 # Next - simple queries about daemon state only. Should be safe!
154 if json_data['type'] == 'daemon_query':
155 response['type'] = 'response'
156 try:
157 response['data'] = util.perform_daemon_query(state, json_data['command'], json_data['data'])
Steve McIntyref1c04f92014-12-16 18:23:15 +0000158 response['response'] = 'ACK'
159 except InputError as e:
160 print e
161 response['response'] = 'ERROR'
162 response['error'] = e.__str__()
Steve McIntyreb01959f2016-03-22 17:02:39 +0000163 except NotFoundError as e:
164 print e
165 response['response'] = 'NOTFOUND'
166 response['error'] = e.__str__()
Steve McIntyref1c04f92014-12-16 18:23:15 +0000167
Steve McIntyre995a10c2014-12-17 13:12:58 +0000168 # Next, calls that manipulate objects in the database only
169 # (switches and ports). These are safe and don't need actual
Steve McIntyrec3b659b2014-12-17 16:28:37 +0000170 # co-ordinating with hardware directly.
Steve McIntyre995a10c2014-12-17 13:12:58 +0000171 #
172 # As/when/if we add authentication, use of this function will need
173 # it.
174 if json_data['type'] == 'db_update':
175 response['type'] = 'response'
176 try:
177 response['data'] = util.perform_db_update(state, json_data['command'], json_data['data'])
178 response['response'] = 'ACK'
179 except InputError as e:
180 print e
181 response['response'] = 'ERROR'
182 response['error'] = e.__str__()
Steve McIntyreb01959f2016-03-22 17:02:39 +0000183 except NotFoundError as e:
184 print e
185 response['response'] = 'NOTFOUND'
186 response['error'] = e.__str__()
Steve McIntyre995a10c2014-12-17 13:12:58 +0000187
Steve McIntyre50767152014-12-17 16:29:01 +0000188 # Next, calls that may manipulate switch state *as well* as state
189 # in the database - changes to VLAN setup.
190 #
191 # As/when/if we add authentication, use of this function will need
192 # it.
193 if json_data['type'] == 'vlan_update':
194 response['type'] = 'response'
195 try:
196 response['data'] = util.perform_vlan_update(state, json_data['command'], json_data['data'])
197 response['response'] = 'ACK'
198 except InputError as e:
199 print e
200 response['response'] = 'ERROR'
201 response['error'] = e.__str__()
Steve McIntyreb01959f2016-03-22 17:02:39 +0000202 except NotFoundError as e:
203 print e
204 response['response'] = 'NOTFOUND'
205 response['error'] = e.__str__()
Steve McIntyre50767152014-12-17 16:29:01 +0000206
207 # Finally, IPC interface for more complex API calls.
208 # NOT IMPLEMENTED YET
209 if json_data['type'] == 'vland_api':
210 response['type'] = 'response'
211 response['response'] = 'ERROR'
212 response['error'] = 'VLANd API not yet implemented...'
213
Steve McIntyre7cf80982015-02-12 07:03:40 +0000214 logging.debug("sending reply:")
Steve McIntyre72f1a5c2015-02-12 07:05:39 +0000215 logging.debug(response)
Steve McIntyre6c44fb22014-12-12 22:11:45 +0000216
217 ipc.server_reply(response)
218
Steve McIntyre06fe6422015-01-23 17:55:43 +0000219# We've been asked to shut down. Do that as cleanly as we can
220ipc.server_close()
Steve McIntyrec5fc28b2015-07-13 17:50:10 +0100221
Steve McIntyre71aa0ac2015-09-23 18:34:02 +0100222if state.config.visualisation.enabled:
223 state.vis.shutdown()
224
Steve McIntyrec5fc28b2015-07-13 17:50:10 +0100225logging.info('%s shutting down', state.banner)
226switches = state.db.all_switches()
227logging.info(' DB knows about %d switches', len(switches))
228ports = state.db.all_ports()
229logging.info(' DB knows about %d ports', len(ports))
230vlans = state.db.all_vlans()
231logging.info(' DB knows about %d vlans', len(vlans))
Steve McIntyrec4890132015-08-07 15:19:11 +0100232trunks = state.db.all_trunks()
233logging.info(' DB knows about %d trunks', len(trunks))
Steve McIntyrec5fc28b2015-07-13 17:50:10 +0100234
Steve McIntyredbe6aa52015-02-12 07:48:22 +0000235logging.shutdown()