blob: 7c0de5c2f1bc19dc5324c8f4a438274fcd94d3e2 [file] [log] [blame]
#
# Copyright (c) 2013 Big Switch Networks, Inc.
#
# Licensed under the Eclipse Public License, Version 1.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.eclipse.org/legal/epl-v10.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.
#
from django.db.models.signals import post_save, post_delete
import json
import os
import traceback
from django.core import serializers
import sdncon
# FIXME: This code is not thread-safe!!!
# Currently we don't run Django with multiple threads so this isn't a problem,
# but if we were to try to enable threading this code would need to be fixed.
pre_save_instance = None
config_handlers = []
config_models = set()
def add_config_handler(dependencies, handler):
"""
The dependencies argument is a dictionary where the key is a model
(i.e. the actual model, not the name of the mode) and the value of the
key is a tuple (or list) of field names whose modification should
trigger the config handler. The value can also be None to indicate
that the config handler should be triggered when any field in the
model is modified.
The calling convention of a config handler is:
def my_config_handler(op, old_instance, new_instance, modified_fields)
The 'op' argument is one of 'INSERT', 'UPDATE' or 'DELETE', corresponding
to an insertion, update or deletion of a row in the model.
The 'old_instance' argument is the instance of the model before the changes.
For an 'INSERT' op, old_instance and modified_fields are both None.
The 'new_instance' argument is the instance of the model after the changes.
For a 'DELETE' op, new_instance and modified_fields are both None.
the 'modified_fields' is a dictionary containing the fields that were changed.
The dictionary key is the name of the field and the value is the new value.
"""
assert dependencies != None
assert handler != None
for model in dependencies.keys():
config_models.add(model)
config_handlers.append((dependencies, handler))
last_config_state_path = None
def get_last_config_state_path():
global last_config_state_path
if not last_config_state_path:
last_config_state_dir = "%s/run/" % sdncon.SDN_ROOT
if not os.path.exists(last_config_state_dir):
last_config_state_dir = '/tmp'
last_config_state_path = os.path.join(last_config_state_dir, 'last-config-state')
return last_config_state_path
def reset_last_config_state():
path = get_last_config_state_path()
try:
os.unlink(path)
except Exception, _e:
pass
def config_read_state():
last_config_state = None
f = None
try:
f = open(get_last_config_state_path(), 'r')
last_config_state_text = f.read()
last_config_state = json.loads(last_config_state_text)
except Exception, _e:
pass
finally:
if f:
f.close()
return last_config_state
def config_write_state(config_state):
f = None
try:
config_state_text = json.dumps(config_state)
f = open(get_last_config_state_path(), 'w')
f.write(config_state_text)
except Exception, e:
print "Error writing last config state: %s" % str(e)
finally:
if f:
f.close()
def config_do_insert(sender, new_instance):
for config_handler in config_handlers:
dependencies = config_handler[0]
if sender in dependencies:
handler = config_handler[1]
try:
handler(op='INSERT', old_instance=None, new_instance=new_instance, modified_fields=None)
except Exception, _e:
traceback.print_exc()
def config_do_update(sender, old_instance, new_instance):
for config_handler in config_handlers:
dependencies = config_handler[0]
if sender in dependencies:
handler = config_handler[1]
modified_fields = {}
fields = dependencies.get(sender)
if not fields:
# If no fields were specified for the model then check all of the fields.
fields = [field.name for field in sender._meta.fields]
for field in fields:
old_value = getattr(old_instance, field)
new_value = getattr(new_instance, field)
if new_value != old_value:
modified_fields[field] = new_value
if modified_fields:
try:
handler(op='UPDATE', old_instance=old_instance, new_instance=new_instance, modified_fields=modified_fields)
except Exception, _e:
traceback.print_exc()
def config_do_delete(sender, instance):
for config_handler in config_handlers:
dependencies = config_handler[0]
if sender in dependencies:
handler = config_handler[1]
try:
handler(op='DELETE', old_instance=instance, new_instance=None, modified_fields=None)
except Exception, _e:
traceback.print_exc()
def model_instances_equal(instance1, instance2):
if instance1 == None:
return instance1 == instance2
for field in instance1._meta.fields:
value1 = field.value_from_object(instance1)
value2 = field.value_from_object(instance2)
if value1 != value2:
return False
return True
def config_check_state():
from sdncon.controller.notification import do_modify_notification, do_delete_notification
last_config_state = config_read_state()
try:
last_config_instances = last_config_state.get('instances')
except Exception, _e:
last_config_instances = {}
current_config_instances = {}
for config_model in config_models:
try:
serialized_old_instances = json.dumps(last_config_instances.get(config_model.__name__,[]))
old_instance_info = serializers.deserialize('json', serialized_old_instances)
old_instances = [info.object for info in old_instance_info]
except Exception, _e:
old_instances = []
new_instances = config_model.objects.all()
for new_instance in new_instances:
for index, old_instance in enumerate(old_instances):
if new_instance.pk == old_instance.pk:
if not model_instances_equal(new_instance, old_instance):
config_do_update(config_model, old_instance, new_instance)
do_modify_notification(config_model, new_instance)
del old_instances[index]
break
else:
config_do_insert(config_model, new_instance)
do_modify_notification(config_model, new_instance)
for deleted_instance in old_instances:
config_do_delete(config_model, deleted_instance)
do_delete_notification(config_model, deleted_instance)
try:
serialized_new_instances = serializers.serialize("json", new_instances)
current_config_instances[config_model.__name__] = json.loads(serialized_new_instances)
except:
print 'Failed to serialize', config_model.__name__
config_write_state({'instances': current_config_instances})
def config_post_save_handler(sender, **kwargs):
if not kwargs['raw'] and (kwargs['using'] == "default") and sender in config_models:
config_check_state()
def config_post_delete_handler(sender, **kwargs):
if kwargs['using'] == 'default' and sender in config_models:
config_check_state()
def is_config_model(model):
return model in config_models
def init_config():
post_save.connect(config_post_save_handler)
post_delete.connect(config_post_delete_handler)