blob: ba9c97a5acaa7f99ed910bbf109900654524889f [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 urllib2
19import 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.
32notifications_inited = False
33batch_level = 0
34actions = None
35
36
37def 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.
50def 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
76def 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
102def do_modify_notification(sender, instance):
103 do_action(sender, instance, 'MODIFY')
104
105def do_delete_notification(sender, instance):
106 do_action(sender, instance, 'DELETE')
107
108def 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
114def 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
120def 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