# ssl_ca_file = /path/to/cafile
# ======== end of WSGI parameters related to the API server ==========
+
+# ======== neutron nova interactions ==========
+# Send notification to nova when port status is active.
+# notify_nova_on_port_status_changes = True
+
+# URL for connection to nova (Only supports one nova region currently).
+# nova_url = http://127.0.0.1:8774
+
+# Name of nova region to use. Useful if keystone manages more than one region
+# nova_region_name =
+
+# Username for connection to nova in admin context
+# nova_admin_username =
+
+# The uuid of the admin nova tenant
+# nova_admin_tenant_id =
+
+# Password for connection to nova in admin context.
+# nova_admin_password =
+
+# Authorization URL for connection to nova in admin context.
+# nova_admin_auth_url =
+
+# Number of seconds between sending events to nova if there are any events to send
+# send_events_interval = 2
+
+# ======== end of neutron nova interactions ==========
+
[quotas]
# Default driver to use for quota checks
# quota_driver = neutron.db.quota_db.DbQuotaDriver
help=_("The hostname Neutron is running on")),
cfg.BoolOpt('force_gateway_on_subnet', default=False,
help=_("Ensure that configured gateway is on subnet")),
+ cfg.BoolOpt('notify_nova_on_port_status_changes', default=True,
+ help=_("Send notification to nova when port status changes")),
+ cfg.StrOpt('nova_url',
+ default='http://127.0.0.1:8774',
+ help=_('URL for connection to nova')),
+ cfg.StrOpt('nova_admin_username',
+ help=_('Username for connecting to nova in admin context')),
+ cfg.StrOpt('nova_admin_password',
+ help=_('Password for connection to nova in admin context'),
+ secret=True),
+ cfg.StrOpt('nova_admin_tenant_id',
+ help=_('The uuid of the admin nova tenant')),
+ cfg.StrOpt('nova_admin_auth_url',
+ default='http://localhost:5000/v2.0',
+ help=_('Authorization URL for connecting to nova in admin '
+ 'context')),
+ cfg.StrOpt('nova_region_name',
+ help=_('Name of nova region to use. Useful if keystone manages'
+ ' more than one region.')),
+ cfg.IntOpt('send_events_interval', default=2,
+ help=_('Number of seconds between sending events to nova if '
+ 'there are any events to send.')),
]
core_cli_opts = [
import netaddr
from oslo.config import cfg
+from sqlalchemy import event
from sqlalchemy import orm
from sqlalchemy.orm import exc
from neutron.db import models_v2
from neutron.db import sqlalchemyutils
from neutron import neutron_plugin_base_v2
+from neutron.notifiers import nova
from neutron.openstack.common import excutils
from neutron.openstack.common import log as logging
from neutron.openstack.common import uuidutils
def __init__(self):
db.configure_db()
+ if cfg.CONF.notify_nova_on_port_status_changes:
+ # NOTE(arosen) These event listners are here to hook into when
+ # port status changes and notify nova about their change.
+ self.nova_notifier = nova.Notifier()
+ event.listen(models_v2.Port, 'after_insert',
+ self.nova_notifier.send_port_status)
+ event.listen(models_v2.Port, 'after_update',
+ self.nova_notifier.send_port_status)
+ event.listen(models_v2.Port.status, 'set',
+ self.nova_notifier.record_port_status_changed)
@classmethod
def register_dict_extend_funcs(cls, resource, funcs):
device_id = sa.Column(sa.String(255), nullable=False)
device_owner = sa.Column(sa.String(255), nullable=False)
+ def __init__(self, id=None, tenant_id=None, name=None, network_id=None,
+ mac_address=None, admin_state_up=None, status=None,
+ device_id=None, device_owner=None, fixed_ips=None):
+ self.id = id
+ self.tenant_id = tenant_id
+ self.name = name
+ self.network_id = network_id
+ self.mac_address = mac_address
+ self.admin_state_up = admin_state_up
+ self.device_owner = device_owner
+ self.device_id = device_id
+ # Since this is a relationship only set it if one is passed in.
+ if fixed_ips:
+ self.fixed_ips = fixed_ips
+
+ # NOTE(arosen): status must be set last as an event is triggered on!
+ self.status = status
+
class DNSNameServer(model_base.BASEV2):
"""Internal representation of a DNS nameserver."""
--- /dev/null
+# Copyright (c) 2014 OpenStack Foundation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import novaclient.v1_1.client as nclient
+from novaclient.v1_1.contrib import server_external_events
+from oslo.config import cfg
+from sqlalchemy.orm import attributes as sql_attr
+
+from neutron.common import constants
+from neutron.openstack.common import log as logging
+from neutron.openstack.common import loopingcall
+
+
+LOG = logging.getLogger(__name__)
+
+VIF_UNPLUGGED = 'network-vif-unplugged'
+VIF_PLUGGED = 'network-vif-plugged'
+NEUTRON_NOVA_EVENT_STATUS_MAP = {constants.PORT_STATUS_ACTIVE: 'completed',
+ constants.PORT_STATUS_ERROR: 'failed',
+ constants.PORT_STATUS_DOWN: 'completed'}
+
+
+class Notifier(object):
+
+ def __init__(self):
+ # TODO(arosen): we need to cache the endpoints and figure out
+ # how to deal with different regions here....
+ bypass_url = "%s/%s" % (cfg.CONF.nova_url,
+ cfg.CONF.nova_admin_tenant_id)
+ self.nclient = nclient.Client(
+ username=cfg.CONF.nova_admin_username,
+ api_key=cfg.CONF.nova_admin_password,
+ project_id=None,
+ tenant_id=cfg.CONF.nova_admin_tenant_id,
+ auth_url=cfg.CONF.nova_admin_auth_url,
+ bypass_url=bypass_url,
+ region_name=cfg.CONF.nova_region_name,
+ extensions=[server_external_events])
+ self.pending_events = []
+ event_sender = loopingcall.FixedIntervalLoopingCall(self.send_events)
+ event_sender.start(interval=cfg.CONF.send_events_interval)
+
+ 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.
+ """
+ # clear out previous _notify_event
+ port._notify_event = None
+ # If there is no device_id set there is nothing we can do here.
+ if not port.device_id:
+ LOG.debug(_("device_id is not set on port yet."))
+ return
+
+ if not port.id:
+ LOG.warning(_("Port ID not set! Nova will not be notified of "
+ "port status change."))
+ return
+
+ # We only want to notify about nova ports.
+ if (not port.device_owner or
+ not port.device_owner.startswith('compute:')):
+ 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):
+ event_name = VIF_UNPLUGGED
+
+ # We only notify nova when a vif is plugged which only occurs
+ # when the status goes from:
+ # NO_VALUE/DOWN/BUILD -> ACTIVE/ERROR.
+ elif (previous_port_status in [sql_attr.NO_VALUE,
+ constants.PORT_STATUS_DOWN,
+ constants.PORT_STATUS_BUILD]
+ and current_port_status in [constants.PORT_STATUS_ACTIVE,
+ constants.PORT_STATUS_ERROR]):
+ event_name = VIF_PLUGGED
+ # All the remaining state transitions are of no interest to nova
+ else:
+ LOG.debug(_("Ignoring state change previous_port_status: "
+ "%(pre_status)s current_port_status: %(cur_status)s"
+ " port_id %(id)s") %
+ {'pre_status': previous_port_status,
+ 'cur_status': current_port_status,
+ 'id': port.id})
+ return
+
+ port._notify_event = (
+ {'server_uuid': port.device_id,
+ 'name': event_name,
+ 'status': NEUTRON_NOVA_EVENT_STATUS_MAP.get(current_port_status),
+ 'tag': port.id})
+
+ def send_port_status(self, mapper, connection, port):
+ event = getattr(port, "_notify_event", None)
+ if event:
+ self.pending_events.append(event)
+ port._notify_event = None
+
+ def send_events(self):
+ batched_events = []
+ for event in range(len(self.pending_events)):
+ batched_events.append(self.pending_events.pop())
+
+ if not batched_events:
+ return
+
+ LOG.debug(_("Sending events: %s"), batched_events)
+ try:
+ response = self.nclient.server_external_events.create(
+ batched_events)
+ except Exception:
+ LOG.exception(_("Failed to notify nova on events: %s"),
+ batched_events)
+ else:
+ if not isinstance(response, list):
+ LOG.error(_("Error response returned from nova: %s"),
+ response)
+ return
+ response_error = False
+ for event in response:
+ try:
+ status = event['status']
+ except KeyError:
+ response_error = True
+ if status == 'failed':
+ LOG.warning(_("Nova event: %s returned with failed "
+ "status"), event)
+ else:
+ LOG.info(_("Nova event response: %s"), event)
+ if response_error:
+ LOG.error(_("Error response returned from nova: %s"),
+ response)
--- /dev/null
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from sqlalchemy.orm import attributes as sql_attr
+
+from neutron.common import constants
+from neutron.db import models_v2
+from neutron.notifiers import nova
+from neutron.tests import base
+
+
+class TestNovaNotify(base.BaseTestCase):
+ def setUp(self, plugin=None):
+ super(TestNovaNotify, self).setUp()
+
+ self.nova_notifier = nova.Notifier()
+
+ def test_notify_port_status_all_values(self):
+ states = [constants.PORT_STATUS_ACTIVE, constants.PORT_STATUS_DOWN,
+ constants.PORT_STATUS_ERROR, constants.PORT_STATUS_BUILD,
+ sql_attr.NO_VALUE]
+ # test all combinations
+ for previous_port_status in states:
+ for current_port_status in states:
+ port = models_v2.Port(id='port-uuid', device_id='device-uuid',
+ device_owner="compute:",
+ status=current_port_status)
+ self._record_port_status_changed_helper(current_port_status,
+ previous_port_status,
+ port)
+
+ def test_port_without_device_owner_no_notify(self):
+ port = models_v2.Port(id='port-uuid', device_id='device-uuid',
+ status=constants.PORT_STATUS_ACTIVE)
+ self._record_port_status_changed_helper(constants.PORT_STATUS_ACTIVE,
+ sql_attr.NO_VALUE,
+ port)
+
+ def test_port_without_device_id_no_notify(self):
+ port = models_v2.Port(id='port-uuid', device_owner="network:dhcp",
+ status=constants.PORT_STATUS_ACTIVE)
+ self._record_port_status_changed_helper(constants.PORT_STATUS_ACTIVE,
+ sql_attr.NO_VALUE,
+ port)
+
+ def test_port_without_id_no_notify(self):
+ port = models_v2.Port(device_id='device-uuid',
+ device_owner="compute:",
+ status=constants.PORT_STATUS_ACTIVE)
+ self._record_port_status_changed_helper(constants.PORT_STATUS_ACTIVE,
+ sql_attr.NO_VALUE,
+ port)
+
+ def test_non_compute_instances_no_notify(self):
+ port = models_v2.Port(id='port-uuid', device_id='device-uuid',
+ device_owner="network:dhcp",
+ status=constants.PORT_STATUS_ACTIVE)
+ self._record_port_status_changed_helper(constants.PORT_STATUS_ACTIVE,
+ sql_attr.NO_VALUE,
+ port)
+
+ def _record_port_status_changed_helper(self, current_port_status,
+ previous_port_status, port):
+
+ if not (port.device_id and port.id and port.device_owner and
+ port.device_owner.startswith('compute:')):
+ return
+
+ if (previous_port_status == constants.PORT_STATUS_ACTIVE and
+ current_port_status == constants.PORT_STATUS_DOWN):
+ event_name = nova.VIF_UNPLUGGED
+
+ elif (previous_port_status in [sql_attr.NO_VALUE,
+ constants.PORT_STATUS_DOWN,
+ constants.PORT_STATUS_BUILD]
+ and current_port_status in [constants.PORT_STATUS_ACTIVE,
+ constants.PORT_STATUS_ERROR]):
+ event_name = nova.VIF_PLUGGED
+
+ else:
+ return
+
+ status = nova.NEUTRON_NOVA_EVENT_STATUS_MAP.get(current_port_status)
+ self.nova_notifier.record_port_status_changed(port,
+ current_port_status,
+ previous_port_status,
+ None)
+
+ event = {'server_uuid': 'device-uuid', 'status': status,
+ 'name': event_name, 'tag': 'port-uuid'}
+ self.assertEqual(event, port._notify_event)
def setUp(self, plugin=None, service_plugins=None,
ext_mgr=None):
- super(NeutronDbPluginV2TestCase, self).setUp()
+ super(NeutronDbPluginV2TestCase, self).setUp()
+ cfg.CONF.set_override('notify_nova_on_port_status_changes', False)
# Make sure at each test according extensions for the plugin is loaded
PluginAwareExtensionManager._instance = None
# Save the attributes map in case the plugin will alter it