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 urllib2 |
| 19 | import json |
| 20 | |
| 21 | # FIXME: It's not thread-safe to use globals here, |
| 22 | # but we currently only allow a single thread per Django process, |
| 23 | # so this shouldn't cause a problem. If/when we support multiple |
| 24 | # threads we could fix this by using thread local variables. |
| 25 | # To support true batch notifications across multiple REST calls |
| 26 | # we'd need to actually store the batched up actions in the DB, |
| 27 | # since the individual REST calls would be dispatched to |
| 28 | # multiple processes, so we couldn't use in-memory batching of |
| 29 | # the actions like we do now. The current batch support only |
| 30 | # works for single REST updates/deletes with query parameters to |
| 31 | # select the records that can affect multiple records. |
| 32 | notifications_inited = False |
| 33 | batch_level = 0 |
| 34 | actions = None |
| 35 | |
| 36 | |
| 37 | def begin_batch_notification(): |
| 38 | global batch_level |
| 39 | global actions |
| 40 | |
| 41 | if batch_level == 0: |
| 42 | actions = [] |
| 43 | batch_level += 1 |
| 44 | |
| 45 | |
| 46 | # FIXME: Could generalize this so it's not hard-coded for SDNPlatform, but |
| 47 | # since this is supposed to be a short-term mechanism for triggering the |
| 48 | # storage notifications in SDNPlatform it's not clear if it makes sense to |
| 49 | # invest time in cleaning this up. |
| 50 | def end_batch_notification(reset=False): |
| 51 | global batch_level |
| 52 | global actions |
| 53 | |
| 54 | if reset: |
| 55 | batch_level = 0 |
| 56 | elif batch_level > 0: |
| 57 | batch_level -= 1 |
| 58 | |
| 59 | if batch_level == 0: |
| 60 | if actions: |
| 61 | url = 'http://localhost:8080/wm/storage/notify/json' |
| 62 | post_data = json.dumps(actions) |
| 63 | actions = None |
| 64 | request = urllib2.Request(url, post_data, {'Content-Type':'application/json'}) |
| 65 | try: |
| 66 | response = urllib2.urlopen(request) |
| 67 | # FIXME: Log error if request had an error |
| 68 | _response_text = response.read() |
| 69 | except Exception, _e: |
| 70 | # SDNPlatform may not be running, but we don't want that to be a fatal |
| 71 | # error, so we just ignore the exception in that case. |
| 72 | pass |
| 73 | actions = None |
| 74 | |
| 75 | |
| 76 | def do_action(sender, instance, action): |
| 77 | # If we're not already in a batch operation, start a local one |
| 78 | # for just this one change. This is so the code that actually |
| 79 | # sends the notifications to SDNPlatform can be bottle-necked through |
| 80 | # end_batch_notification |
| 81 | local_batch = batch_level == 0 |
| 82 | if local_batch: |
| 83 | begin_batch_notification() |
| 84 | |
| 85 | last_action = actions[-1] if actions else None |
| 86 | if (last_action is None or |
| 87 | # pylint: disable=W0212 |
| 88 | last_action['tableName'] != sender._meta.db_table or |
| 89 | last_action['action'] != action): |
| 90 | # pylint: disable=W0212 |
| 91 | last_action = {'tableName': sender._meta.db_table, 'action': action, 'keys': []} |
| 92 | actions.append(last_action) |
| 93 | |
| 94 | keys = last_action['keys'] |
| 95 | if instance.pk not in keys: |
| 96 | keys.append(instance.pk) |
| 97 | |
| 98 | if local_batch: |
| 99 | end_batch_notification() |
| 100 | |
| 101 | |
| 102 | def do_modify_notification(sender, instance): |
| 103 | do_action(sender, instance, 'MODIFY') |
| 104 | |
| 105 | def do_delete_notification(sender, instance): |
| 106 | do_action(sender, instance, 'DELETE') |
| 107 | |
| 108 | def notification_post_save_handler(sender, **kwargs): |
| 109 | from sdncon.rest.config import is_config_model |
| 110 | if not kwargs['raw'] and (kwargs['using'] == "default") and not is_config_model(sender): |
| 111 | do_modify_notification(sender, kwargs['instance']) |
| 112 | |
| 113 | |
| 114 | def notification_post_delete_handler(sender, **kwargs): |
| 115 | from sdncon.rest.config import is_config_model |
| 116 | if kwargs['using'] == 'default' and not is_config_model(sender): |
| 117 | do_delete_notification(sender, kwargs['instance']) |
| 118 | |
| 119 | |
| 120 | def init_notifications(): |
| 121 | global notifications_inited |
| 122 | if not notifications_inited: |
| 123 | post_save.connect(notification_post_save_handler) |
| 124 | post_delete.connect(notification_post_delete_handler) |
| 125 | notifications_inited = True |
| 126 | |