blob: 7c0de5c2f1bc19dc5324c8f4a438274fcd94d3e2 [file] [log] [blame]
srikanth116e6e82014-08-19 07:22:37 -07001#
2# Copyright (c) 2013 Big Switch Networks, Inc.
3#
4# Licensed under the Eclipse Public License, Version 1.0 (the
5# "License"); you may not use this file except in compliance with the
6# License. You may obtain a copy of the License at
7#
8# http://www.eclipse.org/legal/epl-v10.html
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied. See the License for the specific language governing
14# permissions and limitations under the License.
15#
16
17from django.db.models.signals import post_save, post_delete
18import json
19import os
20import traceback
21from django.core import serializers
22import sdncon
23
24# FIXME: This code is not thread-safe!!!
25# Currently we don't run Django with multiple threads so this isn't a problem,
26# but if we were to try to enable threading this code would need to be fixed.
27
28pre_save_instance = None
29config_handlers = []
30config_models = set()
31
32
33def add_config_handler(dependencies, handler):
34 """
35 The dependencies argument is a dictionary where the key is a model
36 (i.e. the actual model, not the name of the mode) and the value of the
37 key is a tuple (or list) of field names whose modification should
38 trigger the config handler. The value can also be None to indicate
39 that the config handler should be triggered when any field in the
40 model is modified.
41
42 The calling convention of a config handler is:
43
44 def my_config_handler(op, old_instance, new_instance, modified_fields)
45
46 The 'op' argument is one of 'INSERT', 'UPDATE' or 'DELETE', corresponding
47 to an insertion, update or deletion of a row in the model.
48 The 'old_instance' argument is the instance of the model before the changes.
49 For an 'INSERT' op, old_instance and modified_fields are both None.
50 The 'new_instance' argument is the instance of the model after the changes.
51 For a 'DELETE' op, new_instance and modified_fields are both None.
52 the 'modified_fields' is a dictionary containing the fields that were changed.
53 The dictionary key is the name of the field and the value is the new value.
54 """
55 assert dependencies != None
56 assert handler != None
57
58 for model in dependencies.keys():
59 config_models.add(model)
60
61 config_handlers.append((dependencies, handler))
62
63last_config_state_path = None
64
65def get_last_config_state_path():
66 global last_config_state_path
67 if not last_config_state_path:
68 last_config_state_dir = "%s/run/" % sdncon.SDN_ROOT
69 if not os.path.exists(last_config_state_dir):
70 last_config_state_dir = '/tmp'
71 last_config_state_path = os.path.join(last_config_state_dir, 'last-config-state')
72 return last_config_state_path
73
74def reset_last_config_state():
75 path = get_last_config_state_path()
76 try:
77 os.unlink(path)
78 except Exception, _e:
79 pass
80
81
82def config_read_state():
83 last_config_state = None
84 f = None
85 try:
86 f = open(get_last_config_state_path(), 'r')
87 last_config_state_text = f.read()
88 last_config_state = json.loads(last_config_state_text)
89 except Exception, _e:
90 pass
91 finally:
92 if f:
93 f.close()
94 return last_config_state
95
96def config_write_state(config_state):
97 f = None
98 try:
99 config_state_text = json.dumps(config_state)
100 f = open(get_last_config_state_path(), 'w')
101 f.write(config_state_text)
102 except Exception, e:
103 print "Error writing last config state: %s" % str(e)
104 finally:
105 if f:
106 f.close()
107
108def config_do_insert(sender, new_instance):
109 for config_handler in config_handlers:
110 dependencies = config_handler[0]
111 if sender in dependencies:
112 handler = config_handler[1]
113 try:
114 handler(op='INSERT', old_instance=None, new_instance=new_instance, modified_fields=None)
115 except Exception, _e:
116 traceback.print_exc()
117
118def config_do_update(sender, old_instance, new_instance):
119 for config_handler in config_handlers:
120 dependencies = config_handler[0]
121 if sender in dependencies:
122 handler = config_handler[1]
123 modified_fields = {}
124 fields = dependencies.get(sender)
125 if not fields:
126 # If no fields were specified for the model then check all of the fields.
127 fields = [field.name for field in sender._meta.fields]
128
129 for field in fields:
130 old_value = getattr(old_instance, field)
131 new_value = getattr(new_instance, field)
132 if new_value != old_value:
133 modified_fields[field] = new_value
134 if modified_fields:
135 try:
136 handler(op='UPDATE', old_instance=old_instance, new_instance=new_instance, modified_fields=modified_fields)
137 except Exception, _e:
138 traceback.print_exc()
139
140
141def config_do_delete(sender, instance):
142 for config_handler in config_handlers:
143 dependencies = config_handler[0]
144 if sender in dependencies:
145 handler = config_handler[1]
146 try:
147 handler(op='DELETE', old_instance=instance, new_instance=None, modified_fields=None)
148 except Exception, _e:
149 traceback.print_exc()
150
151def model_instances_equal(instance1, instance2):
152 if instance1 == None:
153 return instance1 == instance2
154
155 for field in instance1._meta.fields:
156 value1 = field.value_from_object(instance1)
157 value2 = field.value_from_object(instance2)
158 if value1 != value2:
159 return False
160 return True
161
162def config_check_state():
163 from sdncon.controller.notification import do_modify_notification, do_delete_notification
164 last_config_state = config_read_state()
165 try:
166 last_config_instances = last_config_state.get('instances')
167 except Exception, _e:
168 last_config_instances = {}
169
170 current_config_instances = {}
171
172 for config_model in config_models:
173 try:
174 serialized_old_instances = json.dumps(last_config_instances.get(config_model.__name__,[]))
175 old_instance_info = serializers.deserialize('json', serialized_old_instances)
176 old_instances = [info.object for info in old_instance_info]
177 except Exception, _e:
178 old_instances = []
179
180 new_instances = config_model.objects.all()
181
182 for new_instance in new_instances:
183 for index, old_instance in enumerate(old_instances):
184 if new_instance.pk == old_instance.pk:
185 if not model_instances_equal(new_instance, old_instance):
186 config_do_update(config_model, old_instance, new_instance)
187 do_modify_notification(config_model, new_instance)
188 del old_instances[index]
189 break
190 else:
191 config_do_insert(config_model, new_instance)
192 do_modify_notification(config_model, new_instance)
193
194 for deleted_instance in old_instances:
195 config_do_delete(config_model, deleted_instance)
196 do_delete_notification(config_model, deleted_instance)
197
198 try:
199 serialized_new_instances = serializers.serialize("json", new_instances)
200 current_config_instances[config_model.__name__] = json.loads(serialized_new_instances)
201 except:
202 print 'Failed to serialize', config_model.__name__
203
204 config_write_state({'instances': current_config_instances})
205
206def config_post_save_handler(sender, **kwargs):
207 if not kwargs['raw'] and (kwargs['using'] == "default") and sender in config_models:
208 config_check_state()
209
210def config_post_delete_handler(sender, **kwargs):
211 if kwargs['using'] == 'default' and sender in config_models:
212 config_check_state()
213
214def is_config_model(model):
215 return model in config_models
216
217def init_config():
218 post_save.connect(config_post_save_handler)
219 post_delete.connect(config_post_delete_handler)