BigSwitch: Add agent to support neutron sec groups

Adds a BigSwitch Agent responsible for supporting
neutron security groups on the compute node. Adds
the mixin classes to the plugin to support the
security group calls.

Implements: blueprint bsn-neutron-sec-groups
Change-Id: I3a09888a3ba7d565c2dce8293821919c1e5d0d15
This commit is contained in:
Kevin Benton 2014-02-10 19:36:22 -08:00
parent cb7fac25b7
commit 901b303f1e
10 changed files with 649 additions and 15 deletions

View File

@ -60,3 +60,20 @@ servers=localhost:8080
# Maximum number of rules that a single router may have # Maximum number of rules that a single router may have
# Default is 200 # Default is 200
# max_router_rules=200 # max_router_rules=200
[restproxyagent]
# Specify the name of the bridge used on compute nodes
# for attachment.
# Default: br-int
# integration_bridge=br-int
# Change the frequency of polling by the restproxy agent.
# Value is seconds
# Default: 5
# polling_interval=5
# Virtual switch type on the compute node.
# Options: ovs or ivs
# Default: ovs
# virtual_switch_type = ovs

View File

@ -0,0 +1,95 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2014 OpenStack Foundation
#
# 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.
#
"""bsn_security_groups
Revision ID: f44ab9871cd6
Revises: e766b19a3bb
Create Date: 2014-02-26 17:43:43.051078
"""
# revision identifiers, used by Alembic.
revision = 'f44ab9871cd6'
down_revision = 'e766b19a3bb'
# Change to ['*'] if this migration applies to all plugins
migration_for_plugins = [
'neutron.plugins.bigswitch.plugin.NeutronRestProxyV2',
]
from alembic import op
import sqlalchemy as sa
from neutron.db import migration
def upgrade(active_plugins=None, options=None):
if not migration.should_run(active_plugins, migration_for_plugins):
return
### commands auto generated by Alembic - please adjust! ###
op.create_table(
'securitygroups',
sa.Column('tenant_id', sa.String(length=255), nullable=True),
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('description', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table(
'securitygrouprules',
sa.Column('tenant_id', sa.String(length=255), nullable=True),
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('security_group_id', sa.String(length=36), nullable=False),
sa.Column('remote_group_id', sa.String(length=36), nullable=True),
sa.Column('direction',
sa.Enum('ingress', 'egress',
name='securitygrouprules_direction'),
nullable=True),
sa.Column('ethertype', sa.String(length=40), nullable=True),
sa.Column('protocol', sa.String(length=40), nullable=True),
sa.Column('port_range_min', sa.Integer(), nullable=True),
sa.Column('port_range_max', sa.Integer(), nullable=True),
sa.Column('remote_ip_prefix', sa.String(length=255), nullable=True),
sa.ForeignKeyConstraint(['security_group_id'], ['securitygroups.id'],
ondelete='CASCADE'),
sa.ForeignKeyConstraint(['remote_group_id'], ['securitygroups.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_table(
'securitygroupportbindings',
sa.Column('port_id', sa.String(length=36), nullable=False),
sa.Column('security_group_id', sa.String(length=36), nullable=False),
sa.ForeignKeyConstraint(['port_id'], ['ports.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['security_group_id'], ['securitygroups.id']),
sa.PrimaryKeyConstraint('port_id', 'security_group_id')
)
### end Alembic commands ###
def downgrade(active_plugins=None, options=None):
if not migration.should_run(active_plugins, migration_for_plugins):
return
### commands auto generated by Alembic - please adjust! ###
op.drop_table('securitygroupportbindings')
op.drop_table('securitygrouprules')
op.drop_table('securitygroups')
### end Alembic commands ###

View File

@ -0,0 +1,179 @@
# Copyright 2014 Big Switch Networks, Inc.
# All Rights Reserved.
#
# Copyright 2011 Nicira Networks, Inc.
# 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.
# @author: Kevin Benton, kevin.benton@bigswitch.com
import eventlet
import sys
import time
from oslo.config import cfg
from neutron.agent.linux import ovs_lib
from neutron.agent.linux import utils
from neutron.agent import rpc as agent_rpc
from neutron.agent import securitygroups_rpc as sg_rpc
from neutron.common import config
from neutron.common import topics
from neutron import context as q_context
from neutron.extensions import securitygroup as ext_sg
from neutron.openstack.common import excutils
from neutron.openstack.common import log
from neutron.openstack.common.rpc import dispatcher
from neutron.plugins.bigswitch import config as pl_config
LOG = log.getLogger(__name__)
class IVSBridge(ovs_lib.OVSBridge):
'''
This class does not provide parity with OVS using IVS.
It's only the bare minimum necessary to use IVS with this agent.
'''
def run_vsctl(self, args, check_error=False):
full_args = ["ivs-ctl"] + args
try:
return utils.execute(full_args, root_helper=self.root_helper)
except Exception as e:
with excutils.save_and_reraise_exception() as ctxt:
LOG.error(_("Unable to execute %(cmd)s. "
"Exception: %(exception)s"),
{'cmd': full_args, 'exception': e})
if not check_error:
ctxt.reraise = False
def get_vif_port_set(self):
port_names = self.get_port_name_list()
edge_ports = set(port_names)
return edge_ports
def get_vif_port_by_id(self, port_id):
# IVS in nova uses hybrid method with last 14 chars of UUID
name = 'qvo%s' % port_id[:14]
if name in self.get_vif_port_set():
return name
return False
class PluginApi(agent_rpc.PluginApi,
sg_rpc.SecurityGroupServerRpcApiMixin):
pass
class SecurityGroupAgent(sg_rpc.SecurityGroupAgentRpcMixin):
def __init__(self, context, plugin_rpc, root_helper):
self.context = context
self.plugin_rpc = plugin_rpc
self.root_helper = root_helper
self.init_firewall()
class RestProxyAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
RPC_API_VERSION = '1.1'
def __init__(self, integ_br, polling_interval, root_helper, vs='ovs'):
super(RestProxyAgent, self).__init__()
self.polling_interval = polling_interval
self._setup_rpc()
self.sg_agent = SecurityGroupAgent(self.context,
self.plugin_rpc,
root_helper)
if vs == 'ivs':
self.int_br = IVSBridge(integ_br, root_helper)
else:
self.int_br = ovs_lib.OVSBridge(integ_br, root_helper)
def _setup_rpc(self):
self.topic = topics.AGENT
self.plugin_rpc = PluginApi(topics.PLUGIN)
self.context = q_context.get_admin_context_without_session()
self.dispatcher = dispatcher.RpcDispatcher([self])
consumers = [[topics.PORT, topics.UPDATE],
[topics.SECURITY_GROUP, topics.UPDATE]]
self.connection = agent_rpc.create_consumers(self.dispatcher,
self.topic,
consumers)
def port_update(self, context, **kwargs):
LOG.debug(_("Port update received"))
port = kwargs.get('port')
vif_port = self.int_br.get_vif_port_by_id(port['id'])
if not vif_port:
LOG.debug(_("Port %s is not present on this host."), port['id'])
return
LOG.debug(_("Port %s found. Refreshing firewall."), port['id'])
if ext_sg.SECURITYGROUPS in port:
self.sg_agent.refresh_firewall()
def _update_ports(self, registered_ports):
ports = self.int_br.get_vif_port_set()
if ports == registered_ports:
return
added = ports - registered_ports
removed = registered_ports - ports
return {'current': ports,
'added': added,
'removed': removed}
def _process_devices_filter(self, port_info):
if 'added' in port_info:
self.sg_agent.prepare_devices_filter(port_info['added'])
if 'removed' in port_info:
self.sg_agent.remove_devices_filter(port_info['removed'])
def daemon_loop(self):
ports = set()
while True:
start = time.time()
try:
port_info = self._update_ports(ports)
if port_info:
LOG.debug(_("Agent loop has new device"))
self._process_devices_filter(port_info)
ports = port_info['current']
except Exception:
LOG.exception(_("Error in agent event loop"))
elapsed = max(time.time() - start, 0)
if (elapsed < self.polling_interval):
time.sleep(self.polling_interval - elapsed)
else:
LOG.debug(_("Loop iteration exceeded interval "
"(%(polling_interval)s vs. %(elapsed)s)!"),
{'polling_interval': self.polling_interval,
'elapsed': elapsed})
def main():
eventlet.monkey_patch()
cfg.CONF(project='neutron')
config.setup_logging(cfg.CONF)
pl_config.register_config()
integ_br = cfg.CONF.RESTPROXYAGENT.integration_bridge
polling_interval = cfg.CONF.RESTPROXYAGENT.polling_interval
root_helper = cfg.CONF.AGENT.root_helper
bsnagent = RestProxyAgent(integ_br, polling_interval, root_helper,
cfg.CONF.RESTPROXYAGENT.virtual_switch_type)
bsnagent.daemon_loop()
sys.exit(0)
if __name__ == "__main__":
main()

View File

@ -24,6 +24,7 @@ This module manages configuration options
from oslo.config import cfg from oslo.config import cfg
from neutron.agent.common import config as agconfig
from neutron.common import utils from neutron.common import utils
from neutron.extensions import portbindings from neutron.extensions import portbindings
@ -80,8 +81,20 @@ nova_opts.append(cfg.ListOpt('vif_types',
default=portbindings.VIF_TYPES, default=portbindings.VIF_TYPES,
help=_('List of allowed vif_type values.'))) help=_('List of allowed vif_type values.')))
agent_opts = [
cfg.StrOpt('integration_bridge', default='br-int',
help=_('Name of integration bridge on compute '
'nodes used for security group insertion.')),
cfg.IntOpt('polling_interval', default=5,
help=_('Seconds between agent checks for port changes')),
cfg.StrOpt('virtual_switch_type', default='ovs',
help=_('Virtual switch type.'))
]
def register_config(): def register_config():
cfg.CONF.register_opts(restproxy_opts, "RESTPROXY") cfg.CONF.register_opts(restproxy_opts, "RESTPROXY")
cfg.CONF.register_opts(router_opts, "ROUTER") cfg.CONF.register_opts(router_opts, "ROUTER")
cfg.CONF.register_opts(nova_opts, "NOVA") cfg.CONF.register_opts(nova_opts, "NOVA")
cfg.CONF.register_opts(agent_opts, "RESTPROXYAGENT")
agconfig.register_root_helper(cfg.CONF)

View File

@ -45,9 +45,11 @@ on port-attach) on an additional PUT to do a bulk dump of all persistent data.
""" """
import copy import copy
import re
from oslo.config import cfg from oslo.config import cfg
from neutron.agent import securitygroups_rpc as sg_rpc
from neutron.api import extensions as neutron_extensions from neutron.api import extensions as neutron_extensions
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.common import constants as const from neutron.common import constants as const
@ -57,15 +59,20 @@ from neutron.common import topics
from neutron import context as qcontext from neutron import context as qcontext
from neutron.db import agents_db from neutron.db import agents_db
from neutron.db import agentschedulers_db from neutron.db import agentschedulers_db
from neutron.db import api as db
from neutron.db import db_base_plugin_v2 from neutron.db import db_base_plugin_v2
from neutron.db import dhcp_rpc_base from neutron.db import dhcp_rpc_base
from neutron.db import external_net_db from neutron.db import external_net_db
from neutron.db import extradhcpopt_db from neutron.db import extradhcpopt_db
from neutron.db import l3_db from neutron.db import l3_db
from neutron.db import models_v2
from neutron.db import securitygroups_db as sg_db
from neutron.db import securitygroups_rpc_base as sg_rpc_base
from neutron.extensions import external_net from neutron.extensions import external_net
from neutron.extensions import extra_dhcp_opt as edo_ext from neutron.extensions import extra_dhcp_opt as edo_ext
from neutron.extensions import l3 from neutron.extensions import l3
from neutron.extensions import portbindings from neutron.extensions import portbindings
from neutron import manager
from neutron.openstack.common import excutils from neutron.openstack.common import excutils
from neutron.openstack.common import importutils from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging from neutron.openstack.common import log as logging
@ -84,7 +91,26 @@ SYNTAX_ERROR_MESSAGE = _('Syntax error in server config file, aborting plugin')
METADATA_SERVER_IP = '169.254.169.254' METADATA_SERVER_IP = '169.254.169.254'
class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin): class AgentNotifierApi(rpc.proxy.RpcProxy,
sg_rpc.SecurityGroupAgentRpcApiMixin):
BASE_RPC_API_VERSION = '1.1'
def __init__(self, topic):
super(AgentNotifierApi, self).__init__(
topic=topic, default_version=self.BASE_RPC_API_VERSION)
self.topic_port_update = topics.get_topic_name(
topic, topics.PORT, topics.UPDATE)
def port_update(self, context, port):
self.fanout_cast(context,
self.make_msg('port_update',
port=port),
topic=self.topic_port_update)
class RestProxyCallbacks(sg_rpc_base.SecurityGroupServerRpcCallbackMixin,
dhcp_rpc_base.DhcpRpcCallbackMixin):
RPC_API_VERSION = '1.1' RPC_API_VERSION = '1.1'
@ -92,6 +118,42 @@ class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin):
return q_rpc.PluginRpcDispatcher([self, return q_rpc.PluginRpcDispatcher([self,
agents_db.AgentExtRpcCallback()]) agents_db.AgentExtRpcCallback()])
def get_port_from_device(self, device):
port_id = re.sub(r"^tap", "", device)
port = self.get_port_and_sgs(port_id)
if port:
port['device'] = device
return port
def get_port_and_sgs(self, port_id):
"""Get port from database with security group info."""
LOG.debug(_("get_port_and_sgs() called for port_id %s"), port_id)
session = db.get_session()
sg_binding_port = sg_db.SecurityGroupPortBinding.port_id
with session.begin(subtransactions=True):
query = session.query(
models_v2.Port,
sg_db.SecurityGroupPortBinding.security_group_id
)
query = query.outerjoin(sg_db.SecurityGroupPortBinding,
models_v2.Port.id == sg_binding_port)
query = query.filter(models_v2.Port.id.startswith(port_id))
port_and_sgs = query.all()
if not port_and_sgs:
return
port = port_and_sgs[0][0]
plugin = manager.NeutronManager.get_plugin()
port_dict = plugin._make_port_dict(port)
port_dict['security_groups'] = [
sg_id for port_, sg_id in port_and_sgs if sg_id]
port_dict['security_group_rules'] = []
port_dict['security_group_source_groups'] = []
port_dict['fixed_ips'] = [ip['ip_address']
for ip in port['fixed_ips']]
return port_dict
class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2, class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
external_net_db.External_net_db_mixin, external_net_db.External_net_db_mixin,
@ -320,11 +382,21 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
class NeutronRestProxyV2(NeutronRestProxyV2Base, class NeutronRestProxyV2(NeutronRestProxyV2Base,
extradhcpopt_db.ExtraDhcpOptMixin, extradhcpopt_db.ExtraDhcpOptMixin,
agentschedulers_db.DhcpAgentSchedulerDbMixin): agentschedulers_db.DhcpAgentSchedulerDbMixin,
sg_rpc_base.SecurityGroupServerRpcMixin):
supported_extension_aliases = ["external-net", "router", "binding", _supported_extension_aliases = ["external-net", "router", "binding",
"router_rules", "extra_dhcp_opt", "quotas", "router_rules", "extra_dhcp_opt", "quotas",
"dhcp_agent_scheduler", "agent"] "dhcp_agent_scheduler", "agent",
"security-group"]
@property
def supported_extension_aliases(self):
if not hasattr(self, '_aliases'):
aliases = self._supported_extension_aliases[:]
sg_rpc.disable_security_group_extension_if_noop_driver(aliases)
self._aliases = aliases
return self._aliases
def __init__(self, server_timeout=None): def __init__(self, server_timeout=None):
super(NeutronRestProxyV2, self).__init__() super(NeutronRestProxyV2, self).__init__()
@ -340,26 +412,33 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
# init network ctrl connections # init network ctrl connections
self.servers = servermanager.ServerPool(server_timeout) self.servers = servermanager.ServerPool(server_timeout)
# init dhcp support
self.topic = topics.PLUGIN
self.network_scheduler = importutils.import_object( self.network_scheduler = importutils.import_object(
cfg.CONF.network_scheduler_driver cfg.CONF.network_scheduler_driver
) )
# setup rpc for security and DHCP agents
self._setup_rpc()
if cfg.CONF.RESTPROXY.sync_data:
self._send_all_data()
LOG.debug(_("NeutronRestProxyV2: initialization done"))
def _setup_rpc(self):
self.conn = rpc.create_connection(new=True)
self.topic = topics.PLUGIN
self.notifier = AgentNotifierApi(topics.AGENT)
# init dhcp agent support
self._dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI() self._dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
self.agent_notifiers[const.AGENT_TYPE_DHCP] = ( self.agent_notifiers[const.AGENT_TYPE_DHCP] = (
self._dhcp_agent_notifier self._dhcp_agent_notifier
) )
self.conn = rpc.create_connection(new=True) self.callbacks = RestProxyCallbacks()
self.callbacks = RpcProxy()
self.dispatcher = self.callbacks.create_rpc_dispatcher() self.dispatcher = self.callbacks.create_rpc_dispatcher()
self.conn.create_consumer(self.topic, self.dispatcher, self.conn.create_consumer(self.topic, self.dispatcher,
fanout=False) fanout=False)
# Consume from all consumers in a thread # Consume from all consumers in a thread
self.conn.consume_in_thread() self.conn.consume_in_thread()
if cfg.CONF.RESTPROXY.sync_data:
self._send_all_data()
LOG.debug(_("NeutronRestProxyV2: initialization done"))
def create_network(self, context, network): def create_network(self, context, network):
"""Create a network. """Create a network.
@ -390,6 +469,10 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
self._warn_on_state_status(network['network']) self._warn_on_state_status(network['network'])
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
self._ensure_default_security_group(
context,
network['network']["tenant_id"]
)
# create network in DB # create network in DB
new_net = super(NeutronRestProxyV2, self).create_network(context, new_net = super(NeutronRestProxyV2, self).create_network(context,
network) network)
@ -499,6 +582,8 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
# Update DB in new session so exceptions rollback changes # Update DB in new session so exceptions rollback changes
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
self._ensure_default_security_group_on_port(context, port)
sgids = self._get_security_groups_on_port(context, port)
dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, []) dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, [])
new_port = super(NeutronRestProxyV2, self).create_port(context, new_port = super(NeutronRestProxyV2, self).create_port(context,
port) port)
@ -521,6 +606,8 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
self.servers.rest_create_port(net_tenant_id, self.servers.rest_create_port(net_tenant_id,
new_port["network_id"], new_port["network_id"],
mapped_port) mapped_port)
self._process_port_create_security_group(context, new_port, sgids)
self.notify_security_groups_member_updated(context, new_port)
return new_port return new_port
def get_port(self, context, id, fields=None): def get_port(self, context, id, fields=None):
@ -600,13 +687,16 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
self.servers.rest_update_port(net_tenant_id, self.servers.rest_update_port(net_tenant_id,
new_port["network_id"], new_port["network_id"],
mapped_port) mapped_port)
agent_update_required = self.update_security_group_on_port(
context, port_id, port, orig_port, new_port)
agent_update_required |= self.is_security_group_member_updated(
context, orig_port, new_port)
# return new_port # return new_port
return new_port return new_port
def delete_port(self, context, port_id, l3_port_check=True): def delete_port(self, context, port_id, l3_port_check=True):
"""Delete a port. """Delete a port.
:param context: neutron api request context :param context: neutron api request context
:param id: UUID representing the port to delete. :param id: UUID representing the port to delete.
@ -623,6 +713,7 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
self.prevent_l3_port_deletion(context, port_id) self.prevent_l3_port_deletion(context, port_id)
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
self.disassociate_floatingips(context, port_id) self.disassociate_floatingips(context, port_id)
self._delete_port_security_group_bindings(context, port_id)
super(NeutronRestProxyV2, self).delete_port(context, port_id) super(NeutronRestProxyV2, self).delete_port(context, port_id)
def _delete_port(self, context, port_id): def _delete_port(self, context, port_id):

View File

@ -26,7 +26,9 @@ from neutron.plugins.bigswitch import config
from neutron.tests.unit.bigswitch import fake_server from neutron.tests.unit.bigswitch import fake_server
RESTPROXY_PKG_PATH = 'neutron.plugins.bigswitch.plugin' RESTPROXY_PKG_PATH = 'neutron.plugins.bigswitch.plugin'
NOTIFIER = 'neutron.plugins.bigswitch.plugin.RpcProxy' NOTIFIER = 'neutron.plugins.bigswitch.plugin.AgentNotifierApi'
CALLBACKS = 'neutron.plugins.bigswitch.plugin.RestProxyCallbacks'
CERTFETCH = 'neutron.plugins.bigswitch.servermanager.ServerPool._fetch_cert'
HTTPCON = 'httplib.HTTPConnection' HTTPCON = 'httplib.HTTPConnection'
@ -45,7 +47,9 @@ class BigSwitchTestBase(object):
self.httpPatch = mock.patch(HTTPCON, create=True, self.httpPatch = mock.patch(HTTPCON, create=True,
new=fake_server.HTTPConnectionMock) new=fake_server.HTTPConnectionMock)
self.plugin_notifier_p = mock.patch(NOTIFIER) self.plugin_notifier_p = mock.patch(NOTIFIER)
self.callbacks_p = mock.patch(CALLBACKS)
self.addCleanup(mock.patch.stopall) self.addCleanup(mock.patch.stopall)
self.addCleanup(db.clear_db) self.addCleanup(db.clear_db)
self.callbacks_p.start()
self.plugin_notifier_p.start() self.plugin_notifier_p.start()
self.httpPatch.start() self.httpPatch.start()

View File

@ -0,0 +1,188 @@
# Copyright 2014 Big Switch Networks, Inc.
# 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.
#
# @author: Kevin Benton, Big Switch Networks
from contextlib import nested
import mock
from neutron.openstack.common import importutils
from neutron.tests import base
OVSBRIDGE = 'neutron.agent.linux.ovs_lib.OVSBridge'
PLUGINAPI = 'neutron.plugins.bigswitch.agent.restproxy_agent.PluginApi'
CONTEXT = 'neutron.context'
CONSUMERCREATE = 'neutron.agent.rpc.create_consumers'
SGRPC = 'neutron.agent.securitygroups_rpc'
SGAGENT = 'neutron.plugins.bigswitch.agent.restproxy_agent.SecurityGroupAgent'
AGENTMOD = 'neutron.plugins.bigswitch.agent.restproxy_agent'
NEUTRONCFG = 'neutron.common.config'
PLCONFIG = 'neutron.plugins.bigswitch.config'
class BaseAgentTestCase(base.BaseTestCase):
def setUp(self):
super(BaseAgentTestCase, self).setUp()
self.addCleanup(mock.patch.stopall)
self.mod_agent = importutils.import_module(AGENTMOD)
class TestRestProxyAgentOVS(BaseAgentTestCase):
def setUp(self):
super(TestRestProxyAgentOVS, self).setUp()
self.plapi = mock.patch(PLUGINAPI).start()
self.ovsbridge = mock.patch(OVSBRIDGE).start()
self.context = mock.patch(CONTEXT).start()
self.rpc = mock.patch(CONSUMERCREATE).start()
self.sg_rpc = mock.patch(SGRPC).start()
self.sg_agent = mock.patch(SGAGENT).start()
def mock_agent(self):
mock_context = mock.Mock(return_value='abc')
self.context.get_admin_context_without_session = mock_context
return self.mod_agent.RestProxyAgent('int-br', 2, 'helper')
def mock_port_update(self, **kwargs):
agent = self.mock_agent()
agent.port_update(mock.Mock(), **kwargs)
def test_port_update(self):
port = {'id': 1, 'security_groups': 'default'}
with mock.patch.object(self.ovsbridge.return_value,
'get_vif_port_by_id',
return_value=1) as get_vif:
self.mock_port_update(port=port)
get_vif.assert_called_once_with(1)
self.sg_agent.assert_has_calls([
mock.call().refresh_firewall()
])
def test_port_update_not_vifport(self):
port = {'id': 1, 'security_groups': 'default'}
with mock.patch.object(self.ovsbridge.return_value,
'get_vif_port_by_id',
return_value=0) as get_vif:
self.mock_port_update(port=port)
get_vif.assert_called_once_with(1)
self.assertFalse(self.sg_agent.return_value.refresh_firewall.called)
def test_port_update_without_secgroup(self):
port = {'id': 1}
with mock.patch.object(self.ovsbridge.return_value,
'get_vif_port_by_id',
return_value=1) as get_vif:
self.mock_port_update(port=port)
get_vif.assert_called_once_with(1)
self.assertFalse(self.sg_agent.return_value.refresh_firewall.called)
def mock_update_ports(self, vif_port_set=None, registered_ports=None):
with mock.patch.object(self.ovsbridge.return_value,
'get_vif_port_set',
return_value=vif_port_set):
agent = self.mock_agent()
return agent._update_ports(registered_ports)
def test_update_ports_unchanged(self):
self.assertIsNone(self.mock_update_ports())
def test_update_ports_changed(self):
vif_port_set = set([1, 3])
registered_ports = set([1, 2])
expected = dict(current=vif_port_set,
added=set([3]),
removed=set([2]))
actual = self.mock_update_ports(vif_port_set, registered_ports)
self.assertEqual(expected, actual)
def mock_process_devices_filter(self, port_info):
agent = self.mock_agent()
agent._process_devices_filter(port_info)
def test_process_devices_filter_add(self):
port_info = {'added': 1}
self.mock_process_devices_filter(port_info)
self.sg_agent.assert_has_calls([
mock.call().prepare_devices_filter(1)
])
def test_process_devices_filter_remove(self):
port_info = {'removed': 2}
self.mock_process_devices_filter(port_info)
self.sg_agent.assert_has_calls([
mock.call().remove_devices_filter(2)
])
def test_process_devices_filter_both(self):
port_info = {'added': 1, 'removed': 2}
self.mock_process_devices_filter(port_info)
self.sg_agent.assert_has_calls([
mock.call().prepare_devices_filter(1),
mock.call().remove_devices_filter(2)
])
def test_process_devices_filter_none(self):
port_info = {}
self.mock_process_devices_filter(port_info)
self.assertFalse(
self.sg_agent.return_value.prepare_devices_filter.called)
self.assertFalse(
self.sg_agent.return_value.remove_devices_filter.called)
class TestRestProxyAgent(BaseAgentTestCase):
def mock_main(self):
cfg_attrs = {'CONF.RESTPROXYAGENT.integration_bridge': 'integ_br',
'CONF.RESTPROXYAGENT.polling_interval': 5,
'CONF.RESTPROXYAGENT.virtual_switch_type': 'ovs',
'CONF.AGENT.root_helper': 'helper'}
with nested(
mock.patch(AGENTMOD + '.cfg', **cfg_attrs),
mock.patch(NEUTRONCFG),
mock.patch(PLCONFIG),
) as (mock_conf, mock_log_conf, mock_pluginconf):
self.mod_agent.main()
mock_log_conf.assert_has_calls([
mock.call(mock_conf),
])
def test_main(self):
agent_attrs = {'daemon_loop.side_effect': SystemExit(0)}
with mock.patch(AGENTMOD + '.RestProxyAgent',
**agent_attrs) as mock_agent:
self.assertRaises(SystemExit, self.mock_main)
mock_agent.assert_has_calls([
mock.call('integ_br', 5, 'helper', 'ovs'),
mock.call().daemon_loop()
])

View File

@ -0,0 +1,46 @@
# Copyright 2014, Big Switch Networks
# 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 neutron import manager
from neutron.tests.unit.bigswitch import test_base
from neutron.tests.unit import test_extension_security_group as test_sg
from neutron.tests.unit import test_security_groups_rpc as test_sg_rpc
class RestProxySecurityGroupsTestCase(test_sg.SecurityGroupDBTestCase,
test_base.BigSwitchTestBase):
plugin_str = ('%s.NeutronRestProxyV2' %
test_base.RESTPROXY_PKG_PATH)
def setUp(self, plugin=None):
test_sg_rpc.set_firewall_driver(test_sg_rpc.FIREWALL_HYBRID_DRIVER)
self.setup_config_files()
self.setup_patches()
self._attribute_map_bk_ = {}
super(RestProxySecurityGroupsTestCase, self).setUp(self.plugin_str)
plugin = manager.NeutronManager.get_plugin()
self.notifier = plugin.notifier
self.rpc = plugin.callbacks
class TestSecServerRpcCallBack(test_sg_rpc.SGServerRpcCallBackMixinTestCase,
RestProxySecurityGroupsTestCase):
pass
class TestSecurityGroupsMixin(test_sg.TestSecurityGroups,
test_sg_rpc.SGNotificationTestMixin,
RestProxySecurityGroupsTestCase):
pass

View File

@ -96,6 +96,7 @@ console_scripts =
neutron-nsx-manage = neutron.plugins.nicira.shell:main neutron-nsx-manage = neutron.plugins.nicira.shell:main
neutron-openvswitch-agent = neutron.plugins.openvswitch.agent.ovs_neutron_agent:main neutron-openvswitch-agent = neutron.plugins.openvswitch.agent.ovs_neutron_agent:main
neutron-ovs-cleanup = neutron.agent.ovs_cleanup_util:main neutron-ovs-cleanup = neutron.agent.ovs_cleanup_util:main
neutron-restproxy-agent = neutron.plugins.bigswitch.agent.restproxy_agent:main
neutron-ryu-agent = neutron.plugins.ryu.agent.ryu_neutron_agent:main neutron-ryu-agent = neutron.plugins.ryu.agent.ryu_neutron_agent:main
neutron-server = neutron.server:main neutron-server = neutron.server:main
neutron-rootwrap = oslo.rootwrap.cmd:main neutron-rootwrap = oslo.rootwrap.cmd:main