blob: 7365a73c7f1562cc46ffbff79335192879e6e99c [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#
Steve McIntyre6c7ca7f2018-03-05 18:15:51 +000020# Top-level VLANd daemon
Steve McIntyree5043dd2014-12-10 16:49:28 +000021#
22
Steve McIntyre1cb0b482014-12-22 17:18:11 +000023import os, sys
Steve McIntyree5043dd2014-12-10 16:49:28 +000024import time
25import logging
26
Steve McIntyre6c7ca7f2018-03-05 18:15:51 +000027from Vland.config.config import VlanConfig
28from Vland.db.db import VlanDB
29from Vland.ipc.ipc import VlanIpc
30from Vland.errors import InputError, NotFoundError, SocketError
31from Vland.util import VlanUtil
32from Vland.visualisation.visualisation import Visualisation
Steve McIntyree5043dd2014-12-10 16:49:28 +000033
Steve McIntyre231354b2014-12-16 19:22:12 +000034class DaemonState:
35 """ Simple container for stuff to make for nicer syntax """
Steve McIntyree5043dd2014-12-10 16:49:28 +000036
Steve McIntyre231354b2014-12-16 19:22:12 +000037state = DaemonState()
Steve McIntyrea6b485d2018-02-02 16:49:55 +000038state.version = "0.7"
Steve McIntyre231354b2014-12-16 19:22:12 +000039state.banner = "Linaro VLANd version %s" % state.version
40state.starttime = time.time()
Steve McIntyre71aa0ac2015-09-23 18:34:02 +010041state.vis = None
42
43os.environ['TZ'] = 'UTC'
44time.tzset()
Steve McIntyre231354b2014-12-16 19:22:12 +000045
46print '%s' % state.banner
Steve McIntyree5043dd2014-12-10 16:49:28 +000047
48print 'Parsing Config...'
Steve McIntyre231354b2014-12-16 19:22:12 +000049state.config = VlanConfig(filenames=('./vland.cfg',))
50print ' Config knows about %d switches' % len(state.config.switches)
Steve McIntyree5043dd2014-12-10 16:49:28 +000051
Steve McIntyre71aa0ac2015-09-23 18:34:02 +010052if state.config.visualisation.enabled:
53 state.vis = Visualisation(state)
54
Steve McIntyref1c04f92014-12-16 18:23:15 +000055util = VlanUtil()
56
Steve McIntyre67940882018-01-31 17:07:39 +000057loglevel = util.set_logging_level(state.config.logging.level)
58
59# Should we log to stderr?
60if state.config.logging.filename is None:
61 logging.basicConfig(level = loglevel,
62 format = '%(asctime)s %(levelname)-8s %(message)s')
63else:
64 print "Logging to %s" % state.config.logging.filename
65 logging.basicConfig(level = loglevel,
66 format = '%(asctime)s %(levelname)-8s MAIN %(message)s',
67 datefmt = '%Y-%m-%d %H:%M:%S %Z',
68 filename = state.config.logging.filename,
69 filemode = 'a')
70
71logging.info('%s main daemon starting up', state.banner)
72logging.info('Connecting to DB...')
Steve McIntyre283d5f22014-12-17 13:12:04 +000073state.db = VlanDB(db_name=state.config.database.dbname,
Steve McIntyreea343aa2015-10-23 17:46:17 +010074 username=state.config.database.username, readonly=False)
Steve McIntyree5043dd2014-12-10 16:49:28 +000075
Steve McIntyre283d5f22014-12-17 13:12:04 +000076switches = state.db.all_switches()
Steve McIntyre67940882018-01-31 17:07:39 +000077logging.info('DB knows about %d switches', len(switches))
Steve McIntyre283d5f22014-12-17 13:12:04 +000078ports = state.db.all_ports()
Steve McIntyre67940882018-01-31 17:07:39 +000079logging.info('DB knows about %d ports', len(ports))
Steve McIntyre283d5f22014-12-17 13:12:04 +000080vlans = state.db.all_vlans()
Steve McIntyre67940882018-01-31 17:07:39 +000081logging.info('DB knows about %d vlans', len(vlans))
Steve McIntyrec4890132015-08-07 15:19:11 +010082trunks = state.db.all_trunks()
Steve McIntyre67940882018-01-31 17:07:39 +000083logging.info('DB knows about %d trunks', len(trunks))
Steve McIntyree5043dd2014-12-10 16:49:28 +000084
Steve McIntyreae286192014-12-12 22:10:43 +000085# Initial startup sanity chacking
Steve McIntyree5043dd2014-12-10 16:49:28 +000086
Steve McIntyrec12f6312014-12-15 15:00:12 +000087# For sanity, we need to know the vlan_id for the default vlan (tag
88# 1). Make sure we know that before anybody attempts to create things
89# that depend on it.
Steve McIntyre283d5f22014-12-17 13:12:04 +000090state.default_vlan_id = state.db.get_vlan_id_by_tag(state.config.vland.default_vlan_tag)
91if state.default_vlan_id is None:
Steve McIntyrec12f6312014-12-15 15:00:12 +000092 # It doesn't exist - create it and try again
Steve McIntyre283d5f22014-12-17 13:12:04 +000093 state.default_vlan_id = state.db.create_vlan("DEFAULT",
94 state.config.vland.default_vlan_tag,
95 True)
Steve McIntyrec12f6312014-12-15 15:00:12 +000096
Steve McIntyre231354b2014-12-16 19:22:12 +000097if len(switches) != len(state.config.switches):
98 print 'You have configured access details for %d switch(es), ' % len(state.config.switches)
Steve McIntyre96855db2014-12-15 16:52:00 +000099 print 'but have %d switch(es) registered in your database.' % len(switches)
Steve McIntyreae286192014-12-12 22:10:43 +0000100 print 'You must fix this difference for VLANd to work sensibly.'
Steve McIntyre53787d22018-02-02 17:08:04 +0000101 print 'HINT: Running vland-admin auto_import_switch --name <switch_name>'
Steve McIntyrebd7ce372014-12-17 16:27:41 +0000102 print 'for each of your switches may help!'
Steve McIntyreae286192014-12-12 22:10:43 +0000103 print
Steve McIntyree5043dd2014-12-10 16:49:28 +0000104
Steve McIntyrebc06c932018-01-31 17:04:29 +0000105# Now we're done starting up, let the visualisation code talk to the database
106if state.config.visualisation.enabled:
107 logging.info('sending db_ok signal')
108 state.vis.signal_db_ok()
109
Steve McIntyre6c44fb22014-12-12 22:11:45 +0000110# Now start up the core loop. Listen for command connections and
111# process them
Steve McIntyre06fe6422015-01-23 17:55:43 +0000112state.running = True
Steve McIntyre6c44fb22014-12-12 22:11:45 +0000113ipc = VlanIpc()
Steve McIntyre231354b2014-12-16 19:22:12 +0000114ipc.server_init('localhost', state.config.vland.port)
Steve McIntyre06fe6422015-01-23 17:55:43 +0000115while state.running:
Steve McIntyref1c04f92014-12-16 18:23:15 +0000116 try:
117 ipc.server_listen()
118 json_data = ipc.server_recv()
119 except SocketError as e:
120 print e
Steve McIntyre7cf80982015-02-12 07:03:40 +0000121 logging.debug('Caught IPC error, ignoring')
Steve McIntyref1c04f92014-12-16 18:23:15 +0000122 continue
Steve McIntyref1c04f92014-12-16 18:23:15 +0000123 except:
124 ipc.server_close()
125 raise
Steve McIntyre6c44fb22014-12-12 22:11:45 +0000126
Steve McIntyre5fa22652015-04-01 18:01:45 +0100127 logging.debug("client %s sent us:", json_data['client_name'])
Steve McIntyreeb0a4b82015-02-12 07:06:56 +0000128 logging.debug(json_data)
Steve McIntyre6c44fb22014-12-12 22:11:45 +0000129
Steve McIntyref1c04f92014-12-16 18:23:15 +0000130 response = {}
131
Steve McIntyre0ebf5832014-12-16 19:23:10 +0000132 # Several types of IPC message here, with potentially different
133 # access control and safety
134
135 # First - simple queries to the database only. Should be safe!
136 if json_data['type'] == 'db_query':
137 response['type'] = 'response'
Steve McIntyref1c04f92014-12-16 18:23:15 +0000138 try:
Steve McIntyre208241a2014-12-17 13:12:26 +0000139 response['data'] = util.perform_db_query(state, json_data['command'], json_data['data'])
Steve McIntyre0ebf5832014-12-16 19:23:10 +0000140 response['response'] = 'ACK'
141 except InputError as e:
142 print e
143 response['response'] = 'ERROR'
144 response['error'] = e.__str__()
Steve McIntyreb01959f2016-03-22 17:02:39 +0000145 except NotFoundError as e:
146 print e
147 response['response'] = 'NOTFOUND'
148 response['error'] = e.__str__()
Steve McIntyre0ebf5832014-12-16 19:23:10 +0000149
150 # Next - simple queries about daemon state only. Should be safe!
151 if json_data['type'] == 'daemon_query':
152 response['type'] = 'response'
153 try:
154 response['data'] = util.perform_daemon_query(state, json_data['command'], json_data['data'])
Steve McIntyref1c04f92014-12-16 18:23:15 +0000155 response['response'] = 'ACK'
156 except InputError as e:
157 print e
158 response['response'] = 'ERROR'
159 response['error'] = e.__str__()
Steve McIntyreb01959f2016-03-22 17:02:39 +0000160 except NotFoundError as e:
161 print e
162 response['response'] = 'NOTFOUND'
163 response['error'] = e.__str__()
Steve McIntyref1c04f92014-12-16 18:23:15 +0000164
Steve McIntyre995a10c2014-12-17 13:12:58 +0000165 # Next, calls that manipulate objects in the database only
166 # (switches and ports). These are safe and don't need actual
Steve McIntyrec3b659b2014-12-17 16:28:37 +0000167 # co-ordinating with hardware directly.
Steve McIntyre995a10c2014-12-17 13:12:58 +0000168 #
169 # As/when/if we add authentication, use of this function will need
170 # it.
171 if json_data['type'] == 'db_update':
172 response['type'] = 'response'
173 try:
174 response['data'] = util.perform_db_update(state, json_data['command'], json_data['data'])
175 response['response'] = 'ACK'
176 except InputError as e:
177 print e
178 response['response'] = 'ERROR'
179 response['error'] = e.__str__()
Steve McIntyreb01959f2016-03-22 17:02:39 +0000180 except NotFoundError as e:
181 print e
182 response['response'] = 'NOTFOUND'
183 response['error'] = e.__str__()
Steve McIntyre995a10c2014-12-17 13:12:58 +0000184
Steve McIntyre50767152014-12-17 16:29:01 +0000185 # Next, calls that may manipulate switch state *as well* as state
186 # in the database - changes to VLAN setup.
187 #
188 # As/when/if we add authentication, use of this function will need
189 # it.
190 if json_data['type'] == 'vlan_update':
191 response['type'] = 'response'
192 try:
193 response['data'] = util.perform_vlan_update(state, json_data['command'], json_data['data'])
194 response['response'] = 'ACK'
195 except InputError as e:
196 print e
197 response['response'] = 'ERROR'
198 response['error'] = e.__str__()
Steve McIntyreb01959f2016-03-22 17:02:39 +0000199 except NotFoundError as e:
200 print e
201 response['response'] = 'NOTFOUND'
202 response['error'] = e.__str__()
Steve McIntyre50767152014-12-17 16:29:01 +0000203
204 # Finally, IPC interface for more complex API calls.
205 # NOT IMPLEMENTED YET
206 if json_data['type'] == 'vland_api':
207 response['type'] = 'response'
208 response['response'] = 'ERROR'
209 response['error'] = 'VLANd API not yet implemented...'
210
Steve McIntyre7cf80982015-02-12 07:03:40 +0000211 logging.debug("sending reply:")
Steve McIntyre72f1a5c2015-02-12 07:05:39 +0000212 logging.debug(response)
Steve McIntyre6c44fb22014-12-12 22:11:45 +0000213
214 ipc.server_reply(response)
215
Steve McIntyre06fe6422015-01-23 17:55:43 +0000216# We've been asked to shut down. Do that as cleanly as we can
217ipc.server_close()
Steve McIntyrec5fc28b2015-07-13 17:50:10 +0100218
Steve McIntyre71aa0ac2015-09-23 18:34:02 +0100219if state.config.visualisation.enabled:
220 state.vis.shutdown()
221
Steve McIntyrec5fc28b2015-07-13 17:50:10 +0100222logging.info('%s shutting down', state.banner)
223switches = state.db.all_switches()
224logging.info(' DB knows about %d switches', len(switches))
225ports = state.db.all_ports()
226logging.info(' DB knows about %d ports', len(ports))
227vlans = state.db.all_vlans()
228logging.info(' DB knows about %d vlans', len(vlans))
Steve McIntyrec4890132015-08-07 15:19:11 +0100229trunks = state.db.all_trunks()
230logging.info(' DB knows about %d trunks', len(trunks))
Steve McIntyrec5fc28b2015-07-13 17:50:10 +0100231
Steve McIntyredbe6aa52015-02-12 07:48:22 +0000232logging.shutdown()