~ubuntu-branches/ubuntu/vivid/neutron/vivid-updates

« back to all changes in this revision

Viewing changes to neutron/callbacks/manager.py

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2015-03-30 11:17:19 UTC
  • mfrom: (1.1.21)
  • Revision ID: package-import@ubuntu.com-20150330111719-h0gx7233p4jkkgfh
Tags: 1:2015.1~b3-0ubuntu1
* New upstream milestone release:
  - d/control: Align version requirements with upstream.
  - d/control: Add new dependency on oslo-log.
  - d/p/*: Rebase.
  - d/control,d/neutron-plugin-hyperv*: Dropped, decomposed into
    separate project upstream.
  - d/control,d/neutron-plugin-openflow*: Dropped, decomposed into
    separate project upstream.
  - d/neutron-common.install: Add neutron-rootwrap-daemon and 
    neutron-keepalived-state-change binaries.
  - d/rules: Ignore neutron-hyperv-agent when installing; only for Windows.
  - d/neutron-plugin-cisco.install: Drop neutron-cisco-cfg-agent as
    decomposed into separate project upstream.
  - d/neutron-plugin-vmware.install: Drop neutron-check-nsx-config and
    neutron-nsx-manage as decomposed into separate project upstream.
  - d/control: Add dependency on python-neutron-fwaas to neutron-l3-agent.
* d/pydist-overrides: Add overrides for oslo packages.
* d/control: Fixup type in package description (LP: #1263539).
* d/p/fixup-driver-test-execution.patch: Cherry pick fix from upstream VCS
  to support unit test exection in out-of-tree vendor drivers.
* d/neutron-common.postinst: Allow general access to /etc/neutron but limit
  access to root/neutron to /etc/neutron/neutron.conf to support execution
  of unit tests in decomposed vendor drivers.
* d/control: Add dependency on python-neutron-fwaas to neutron-l3-agent
  package.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
2
#    not use this file except in compliance with the License. You may obtain
 
3
#    a copy of the License at
 
4
#
 
5
#         http://www.apache.org/licenses/LICENSE-2.0
 
6
#
 
7
#    Unless required by applicable law or agreed to in writing, software
 
8
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
9
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
10
#    License for the specific language governing permissions and limitations
 
11
#    under the License.
 
12
 
 
13
import collections
 
14
import weakref
 
15
 
 
16
from oslo_log import log as logging
 
17
from oslo_utils import reflection
 
18
 
 
19
from neutron.callbacks import events
 
20
from neutron.callbacks import exceptions
 
21
from neutron.callbacks import resources
 
22
from neutron.i18n import _LE, _LI
 
23
 
 
24
LOG = logging.getLogger(__name__)
 
25
 
 
26
 
 
27
class CallbacksManager(object):
 
28
    """A callback system that allows objects to cooperate in a loose manner."""
 
29
 
 
30
    def __init__(self):
 
31
        self.clear()
 
32
 
 
33
    def subscribe(self, callback, resource, event):
 
34
        """Subscribe callback for a resource event.
 
35
 
 
36
        The same callback may register for more than one event.
 
37
 
 
38
        :param callback: the callback. It must raise or return a boolean.
 
39
        :param resource: the resource. It must be a valid resource.
 
40
        :param event: the event. It must be a valid event.
 
41
        """
 
42
        LOG.debug("Subscribe: %(callback)s %(resource)s %(event)s",
 
43
                  {'callback': callback, 'resource': resource, 'event': event})
 
44
        if resource not in resources.VALID:
 
45
            raise exceptions.Invalid(element='resource', value=resource)
 
46
        if event not in events.VALID:
 
47
            raise exceptions.Invalid(element='event', value=event)
 
48
 
 
49
        callback_id = _get_id(callback)
 
50
        self._callbacks[resource][event][callback_id] = weakref.proxy(callback)
 
51
        # We keep a copy of callbacks to speed the unsubscribe operation.
 
52
        if callback_id not in self._index:
 
53
            self._index[callback_id] = collections.defaultdict(set)
 
54
        self._index[callback_id][resource].add(event)
 
55
 
 
56
    def unsubscribe(self, callback, resource, event):
 
57
        """Unsubscribe callback from the registry.
 
58
 
 
59
        :param callback: the callback.
 
60
        :param resource: the resource.
 
61
        :param event: the event.
 
62
        """
 
63
        LOG.debug("Unsubscribe: %(callback)s %(resource)s %(event)s",
 
64
                  {'callback': callback, 'resource': resource, 'event': event})
 
65
 
 
66
        callback_id = self._find(callback)
 
67
        if not callback_id:
 
68
            LOG.debug("Callback %s not found", callback_id)
 
69
            return
 
70
        if resource and event:
 
71
            del self._callbacks[resource][event][callback_id]
 
72
            self._index[callback_id][resource].discard(event)
 
73
            if not self._index[callback_id][resource]:
 
74
                del self._index[callback_id][resource]
 
75
                if not self._index[callback_id]:
 
76
                    del self._index[callback_id]
 
77
        else:
 
78
            value = '%s,%s' % (resource, event)
 
79
            raise exceptions.Invalid(element='resource,event', value=value)
 
80
 
 
81
    def unsubscribe_by_resource(self, callback, resource):
 
82
        """Unsubscribe callback for any event associated to the resource.
 
83
 
 
84
        :param callback: the callback.
 
85
        :param resource: the resource.
 
86
        """
 
87
        callback_id = self._find(callback)
 
88
        if callback_id:
 
89
            if resource in self._index[callback_id]:
 
90
                for event in self._index[callback_id][resource]:
 
91
                    del self._callbacks[resource][event][callback_id]
 
92
                del self._index[callback_id][resource]
 
93
                if not self._index[callback_id]:
 
94
                    del self._index[callback_id]
 
95
 
 
96
    def unsubscribe_all(self, callback):
 
97
        """Unsubscribe callback for all events and all resources.
 
98
 
 
99
 
 
100
        :param callback: the callback.
 
101
        """
 
102
        callback_id = self._find(callback)
 
103
        if callback_id:
 
104
            for resource, resource_events in self._index[callback_id].items():
 
105
                for event in resource_events:
 
106
                    del self._callbacks[resource][event][callback_id]
 
107
            del self._index[callback_id]
 
108
 
 
109
    def notify(self, resource, event, trigger, **kwargs):
 
110
        """Notify all subscribed callback(s).
 
111
 
 
112
        Dispatch the resource's event to the subscribed callbacks.
 
113
 
 
114
        :param resource: the resource.
 
115
        :param event: the event.
 
116
        :param trigger: the trigger. A reference to the sender of the event.
 
117
        """
 
118
        errors = self._notify_loop(resource, event, trigger, **kwargs)
 
119
        if errors and event.startswith(events.BEFORE):
 
120
            abort_event = event.replace(
 
121
                events.BEFORE, events.ABORT)
 
122
            self._notify_loop(resource, abort_event, trigger)
 
123
            raise exceptions.CallbackFailure(errors=errors)
 
124
 
 
125
    def clear(self):
 
126
        """Brings the manager to a clean slate."""
 
127
        self._callbacks = collections.defaultdict(dict)
 
128
        self._index = collections.defaultdict(dict)
 
129
        for resource in resources.VALID:
 
130
            for event in events.VALID:
 
131
                self._callbacks[resource][event] = collections.defaultdict()
 
132
 
 
133
    def _notify_loop(self, resource, event, trigger, **kwargs):
 
134
        """The notification loop."""
 
135
        LOG.info(_LI("Notify callbacks for %(resource)s, %(event)s"),
 
136
                 {'resource': resource, 'event': event})
 
137
 
 
138
        errors = []
 
139
        # TODO(armax): consider using a GreenPile
 
140
        for callback_id, callback in self._callbacks[resource][event].items():
 
141
            try:
 
142
                LOG.info(_LI("Calling callback %s"), callback_id)
 
143
                callback(resource, event, trigger, **kwargs)
 
144
            except Exception as e:
 
145
                LOG.exception(_LE("Error during notification for "
 
146
                                  "%(callback)s %(resource)s, %(event)s"),
 
147
                              {'callback': callback_id,
 
148
                               'resource': resource,
 
149
                               'event': event})
 
150
                errors.append(exceptions.NotificationError(callback_id, e))
 
151
        return errors
 
152
 
 
153
    def _find(self, callback):
 
154
        """Return the callback_id if found, None otherwise."""
 
155
        callback_id = _get_id(callback)
 
156
        return callback_id if callback_id in self._index else None
 
157
 
 
158
 
 
159
def _get_id(callback):
 
160
    """Return a unique identifier for the callback."""
 
161
    # TODO(armax): consider using something other than names
 
162
    # https://www.python.org/dev/peps/pep-3155/, but this
 
163
    # might be okay for now.
 
164
    return reflection.get_callable_name(callback)