srikanth | 116e6e8 | 2014-08-19 07:22:37 -0700 | [diff] [blame] | 1 | # |
| 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 | |
| 17 | from django.db.models.signals import post_save, post_delete |
| 18 | import json |
| 19 | import os |
| 20 | import traceback |
| 21 | from django.core import serializers |
| 22 | import 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 | |
| 28 | pre_save_instance = None |
| 29 | config_handlers = [] |
| 30 | config_models = set() |
| 31 | |
| 32 | |
| 33 | def 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 | |
| 63 | last_config_state_path = None |
| 64 | |
| 65 | def 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 | |
| 74 | def 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 | |
| 82 | def 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 | |
| 96 | def 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 | |
| 108 | def 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 | |
| 118 | def 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 | |
| 141 | def 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 | |
| 151 | def 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 | |
| 162 | def 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 | |
| 206 | def 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 | |
| 210 | def config_post_delete_handler(sender, **kwargs): |
| 211 | if kwargs['using'] == 'default' and sender in config_models: |
| 212 | config_check_state() |
| 213 | |
| 214 | def is_config_model(model): |
| 215 | return model in config_models |
| 216 | |
| 217 | def init_config(): |
| 218 | post_save.connect(config_post_save_handler) |
| 219 | post_delete.connect(config_post_delete_handler) |