# License for the specific language governing permissions and limitations
# under the License.
+import copy
import netaddr
import webob.exc
from neutron.api.v2 import resource as wsgi_resource
from neutron.common import constants as const
from neutron.common import exceptions
+from neutron.notifiers import nova
from neutron.openstack.common import log as logging
from neutron.openstack.common.notifier import api as notifier_api
from neutron import policy
agent_notifiers.get(const.AGENT_TYPE_DHCP) or
dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
+ self._nova_notifier = nova.Notifier()
self._member_actions = member_actions
self._primary_key = self._get_primary_key()
if self._allow_pagination and self._native_pagination:
else:
kwargs.update({self._resource: body})
obj = obj_creator(request.context, **kwargs)
+
+ self._nova_notifier.send_network_change(
+ action, {}, {self._resource: obj})
return notify({self._resource: self._view(request.context,
obj)})
notifier_api.CONF.default_notification_level,
{self._resource + '_id': id})
result = {self._resource: self._view(request.context, obj)}
+ self._nova_notifier.send_network_change(action, {}, result)
self._send_dhcp_notification(request.context,
result,
notifier_method)
'default' not in value)]
orig_obj = self._item(request, id, field_list=field_list,
parent_id=parent_id)
+ orig_object_copy = copy.copy(orig_obj)
orig_obj.update(body[self._resource])
try:
policy.enforce(request.context,
self._send_dhcp_notification(request.context,
result,
notifier_method)
+ self._nova_notifier.send_network_change(
+ action, orig_object_copy, result)
return result
@staticmethod
from sqlalchemy.orm import attributes as sql_attr
from neutron.common import constants
+from neutron import context
+from neutron import manager
from neutron.openstack.common import log as logging
from neutron.openstack.common import loopingcall
event_sender = loopingcall.FixedIntervalLoopingCall(self.send_events)
event_sender.start(interval=cfg.CONF.send_events_interval)
+ def _is_compute_port(self, port):
+ try:
+ if (port['device_id'] and
+ port['device_owner'].startswith('compute:')):
+ return True
+ except (KeyError, AttributeError):
+ pass
+ return False
+
+ def _get_network_changed_event(self, device_id):
+ return {'name': 'network-changed',
+ 'server_uuid': device_id}
+
+ @property
+ def _plugin(self):
+ # NOTE(arosen): this cannot be set in __init__ currently since
+ # this class is initalized at the same time as NeutronManager()
+ # which is decorated with synchronized()
+ if not hasattr(self, '_plugin_ref'):
+ self._plugin_ref = manager.NeutronManager.get_plugin()
+ return self._plugin_ref
+
+ def send_network_change(self, action, original_obj,
+ returned_obj):
+ """Called when a network change is made that nova cares about.
+
+ :param action: the event that occured.
+ :param original_obj: the previous value of resource before action.
+ :param returned_obj: the body returned to client as result of action.
+ """
+
+ if not cfg.CONF.notify_nova_on_port_data_changes:
+ return
+
+ event = self.create_port_changed_event(action, original_obj,
+ returned_obj)
+ if event:
+ self.pending_events.append(event)
+
+ def create_port_changed_event(self, action, original_obj, returned_obj):
+ port = None
+ if action == 'update_port':
+ port = returned_obj['port']
+
+ elif action in ['update_floatingip', 'create_floatingip',
+ 'delete_floatingip']:
+ # NOTE(arosen) if we are associating a floatingip the
+ # port_id is in the returned_obj. Otherwise on disassociate
+ # it's in the original_object
+ port_id = (returned_obj['floatingip'].get('port_id') or
+ original_obj.get('port_id'))
+
+ if port_id is None:
+ return
+
+ ctx = context.get_admin_context()
+ port = self._plugin.get_port(ctx, port_id)
+
+ if port and self._is_compute_port(port):
+ return self._get_network_changed_event(port['device_id'])
+
def record_port_status_changed(self, port, current_port_status,
previous_port_status, initiator):
"""Determine if nova needs to be notified due to port status change.
return
# We only want to notify about nova ports.
- if (not port.device_owner or
- not port.device_owner.startswith('compute:')):
+ if not self._is_compute_port(port):
return
# We notify nova when a vif is unplugged which only occurs when
# the status goes from ACTIVE to DOWN.
if (previous_port_status == constants.PORT_STATUS_ACTIVE and
- current_port_status == constants.PORT_STATUS_DOWN):
+ current_port_status == constants.PORT_STATUS_DOWN):
event_name = VIF_UNPLUGGED
# We only notify nova when a vif is plugged which only occurs
response_error = False
for event in response:
try:
- status = event['status']
+ code = event['code']
except KeyError:
response_error = True
- if status == 'failed':
+ continue
+ if code != 200:
LOG.warning(_("Nova event: %s returned with failed "
"status"), event)
else:
# under the License.
+import mock
from sqlalchemy.orm import attributes as sql_attr
+from oslo.config import cfg
+
from neutron.common import constants
from neutron.db import models_v2
from neutron.notifiers import nova
def setUp(self, plugin=None):
super(TestNovaNotify, self).setUp()
+ class FakePlugin(object):
+ def get_port(self, context, port_id):
+ return {'device_id': 'instance_uuid',
+ 'device_owner': 'compute:None'}
+
self.nova_notifier = nova.Notifier()
+ self.nova_notifier._plugin_ref = FakePlugin()
def test_notify_port_status_all_values(self):
states = [constants.PORT_STATUS_ACTIVE, constants.PORT_STATUS_DOWN,
event = {'server_uuid': 'device-uuid', 'status': status,
'name': event_name, 'tag': 'port-uuid'}
self.assertEqual(event, port._notify_event)
+
+ def test_update_fixed_ip_changed(self):
+ returned_obj = {'port':
+ {'device_owner': u'compute:dfd',
+ 'id': u'bee50827-bcee-4cc8-91c1-a27b0ce54222',
+ 'device_id': u'instance_uuid'}}
+
+ expected_event = {'server_uuid': 'instance_uuid',
+ 'name': 'network-changed'}
+ event = self.nova_notifier.create_port_changed_event('update_port',
+ {}, returned_obj)
+ self.assertEqual(event, expected_event)
+
+ def test_create_floatingip_notify(self):
+ returned_obj = {'floatingip':
+ {'port_id': u'bee50827-bcee-4cc8-91c1-a27b0ce54222'}}
+
+ expected_event = {'server_uuid': 'instance_uuid',
+ 'name': 'network-changed'}
+ event = self.nova_notifier.create_port_changed_event(
+ 'create_floatingip', {}, returned_obj)
+ self.assertEqual(event, expected_event)
+
+ def test_create_floatingip_no_port_id_no_notify(self):
+ returned_obj = {'floatingip':
+ {'port_id': None}}
+
+ event = self.nova_notifier.create_port_changed_event(
+ 'create_floatingip', {}, returned_obj)
+ self.assertFalse(event, None)
+
+ def test_delete_floatingip_notify(self):
+ returned_obj = {'floatingip':
+ {'port_id': u'bee50827-bcee-4cc8-91c1-a27b0ce54222'}}
+
+ expected_event = {'server_uuid': 'instance_uuid',
+ 'name': 'network-changed'}
+ event = self.nova_notifier.create_port_changed_event(
+ 'delete_floatingip', {}, returned_obj)
+ self.assertEqual(expected_event, event)
+
+ def test_delete_floatingip_no_port_id_no_notify(self):
+ returned_obj = {'floatingip':
+ {'port_id': None}}
+
+ event = self.nova_notifier.create_port_changed_event(
+ 'delete_floatingip', {}, returned_obj)
+ self.assertEqual(event, None)
+
+ def test_associate_floatingip_notify(self):
+ returned_obj = {'floatingip':
+ {'port_id': u'5a39def4-3d3f-473d-9ff4-8e90064b9cc1'}}
+ original_obj = {'port_id': None}
+
+ expected_event = {'server_uuid': 'instance_uuid',
+ 'name': 'network-changed'}
+ event = self.nova_notifier.create_port_changed_event(
+ 'update_floatingip', original_obj, returned_obj)
+ self.assertEqual(expected_event, event)
+
+ def test_disassociate_floatingip_notify(self):
+ returned_obj = {'floatingip': {'port_id': None}}
+ original_obj = {'port_id': '5a39def4-3d3f-473d-9ff4-8e90064b9cc1'}
+
+ expected_event = {'server_uuid': 'instance_uuid',
+ 'name': 'network-changed'}
+
+ event = self.nova_notifier.create_port_changed_event(
+ 'update_floatingip', original_obj, returned_obj)
+ self.assertEqual(expected_event, event)
+
+ def test_no_notification_notify_nova_on_port_data_changes_false(self):
+ cfg.CONF.set_override('notify_nova_on_port_data_changes', False)
+
+ with mock.patch.object(self.nova_notifier,
+ 'send_events') as send_events:
+ self.nova_notifier.send_network_change('update_floatingip',
+ {}, {})
+ self.assertFalse(send_events.called, False)
+
+ def test_nova_send_events_returns_bad_list(self):
+ with mock.patch.object(
+ self.nova_notifier.nclient.server_external_events,
+ 'create') as nclient_create:
+ nclient_create.return_value = 'i am a string!'
+ self.nova_notifier.send_events()
+
+ def test_nova_send_events_raises(self):
+ with mock.patch.object(
+ self.nova_notifier.nclient.server_external_events,
+ 'create') as nclient_create:
+ nclient_create.side_effect = Exception
+ self.nova_notifier.send_events()
+
+ def test_nova_send_events_returns_non_200(self):
+ with mock.patch.object(
+ self.nova_notifier.nclient.server_external_events,
+ 'create') as nclient_create:
+ nclient_create.return_value = [{'code': 404,
+ 'name': 'network-changed',
+ 'server_uuid': 'uuid'}]
+ self.nova_notifier.pending_events.append(
+ {'name': 'network-changed', 'server_uuid': 'uuid'})
+ self.nova_notifier.send_events()
+
+ def test_nova_send_events_return_200(self):
+ with mock.patch.object(
+ self.nova_notifier.nclient.server_external_events,
+ 'create') as nclient_create:
+ nclient_create.return_value = [{'code': 200,
+ 'name': 'network-changed',
+ 'server_uuid': 'uuid'}]
+ self.nova_notifier.pending_events.append(
+ {'name': 'network-changed', 'server_uuid': 'uuid'})
+ self.nova_notifier.send_events()
+
+ def test_nova_send_events_multiple(self):
+ with mock.patch.object(
+ self.nova_notifier.nclient.server_external_events,
+ 'create') as nclient_create:
+ nclient_create.return_value = [{'code': 200,
+ 'name': 'network-changed',
+ 'server_uuid': 'uuid'},
+ {'code': 200,
+ 'name': 'network-changed',
+ 'server_uuid': 'uuid'}]
+ self.nova_notifier.pending_events.append(
+ {'name': 'network-changed', 'server_uuid': 'uuid'})
+ self.nova_notifier.pending_events.append(
+ {'name': 'network-changed', 'server_uuid': 'uuid'})
+ self.nova_notifier.send_events()