Merge "BigSwitch: Add agent to support neutron sec groups"
This commit is contained in:
commit
ba3e990714
@ -60,3 +60,20 @@ servers=localhost:8080
|
||||
# Maximum number of rules that a single router may have
|
||||
# Default is 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
|
||||
|
@ -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 ###
|
0
neutron/plugins/bigswitch/agent/__init__.py
Normal file
0
neutron/plugins/bigswitch/agent/__init__.py
Normal file
179
neutron/plugins/bigswitch/agent/restproxy_agent.py
Normal file
179
neutron/plugins/bigswitch/agent/restproxy_agent.py
Normal 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()
|
@ -24,6 +24,7 @@ This module manages configuration options
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.agent.common import config as agconfig
|
||||
from neutron.common import utils
|
||||
from neutron.extensions import portbindings
|
||||
|
||||
@ -80,8 +81,20 @@ nova_opts.append(cfg.ListOpt('vif_types',
|
||||
default=portbindings.VIF_TYPES,
|
||||
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():
|
||||
cfg.CONF.register_opts(restproxy_opts, "RESTPROXY")
|
||||
cfg.CONF.register_opts(router_opts, "ROUTER")
|
||||
cfg.CONF.register_opts(nova_opts, "NOVA")
|
||||
cfg.CONF.register_opts(agent_opts, "RESTPROXYAGENT")
|
||||
agconfig.register_root_helper(cfg.CONF)
|
||||
|
@ -45,9 +45,11 @@ on port-attach) on an additional PUT to do a bulk dump of all persistent data.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import re
|
||||
|
||||
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.rpc.agentnotifiers import dhcp_rpc_agent_api
|
||||
from neutron.common import constants as const
|
||||
@ -57,15 +59,20 @@ from neutron.common import topics
|
||||
from neutron import context as qcontext
|
||||
from neutron.db import agents_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 dhcp_rpc_base
|
||||
from neutron.db import external_net_db
|
||||
from neutron.db import extradhcpopt_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 extra_dhcp_opt as edo_ext
|
||||
from neutron.extensions import l3
|
||||
from neutron.extensions import portbindings
|
||||
from neutron import manager
|
||||
from neutron.openstack.common import excutils
|
||||
from neutron.openstack.common import importutils
|
||||
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'
|
||||
|
||||
|
||||
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'
|
||||
|
||||
@ -92,6 +118,42 @@ class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin):
|
||||
return q_rpc.PluginRpcDispatcher([self,
|
||||
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,
|
||||
external_net_db.External_net_db_mixin,
|
||||
@ -320,11 +382,21 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
|
||||
class NeutronRestProxyV2(NeutronRestProxyV2Base,
|
||||
extradhcpopt_db.ExtraDhcpOptMixin,
|
||||
agentschedulers_db.DhcpAgentSchedulerDbMixin):
|
||||
agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
sg_rpc_base.SecurityGroupServerRpcMixin):
|
||||
|
||||
supported_extension_aliases = ["external-net", "router", "binding",
|
||||
"router_rules", "extra_dhcp_opt", "quotas",
|
||||
"dhcp_agent_scheduler", "agent"]
|
||||
_supported_extension_aliases = ["external-net", "router", "binding",
|
||||
"router_rules", "extra_dhcp_opt", "quotas",
|
||||
"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):
|
||||
super(NeutronRestProxyV2, self).__init__()
|
||||
@ -340,26 +412,33 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
|
||||
# init network ctrl connections
|
||||
self.servers = servermanager.ServerPool(server_timeout)
|
||||
|
||||
# init dhcp support
|
||||
self.topic = topics.PLUGIN
|
||||
self.network_scheduler = importutils.import_object(
|
||||
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.agent_notifiers[const.AGENT_TYPE_DHCP] = (
|
||||
self._dhcp_agent_notifier
|
||||
)
|
||||
self.conn = rpc.create_connection(new=True)
|
||||
self.callbacks = RpcProxy()
|
||||
self.callbacks = RestProxyCallbacks()
|
||||
self.dispatcher = self.callbacks.create_rpc_dispatcher()
|
||||
self.conn.create_consumer(self.topic, self.dispatcher,
|
||||
fanout=False)
|
||||
# Consume from all consumers in a 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):
|
||||
"""Create a network.
|
||||
@ -390,6 +469,10 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
|
||||
self._warn_on_state_status(network['network'])
|
||||
|
||||
with context.session.begin(subtransactions=True):
|
||||
self._ensure_default_security_group(
|
||||
context,
|
||||
network['network']["tenant_id"]
|
||||
)
|
||||
# create network in DB
|
||||
new_net = super(NeutronRestProxyV2, self).create_network(context,
|
||||
network)
|
||||
@ -499,6 +582,8 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
|
||||
|
||||
# Update DB in new session so exceptions rollback changes
|
||||
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, [])
|
||||
new_port = super(NeutronRestProxyV2, self).create_port(context,
|
||||
port)
|
||||
@ -521,6 +606,8 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
|
||||
self.servers.rest_create_port(net_tenant_id,
|
||||
new_port["network_id"],
|
||||
mapped_port)
|
||||
self._process_port_create_security_group(context, new_port, sgids)
|
||||
self.notify_security_groups_member_updated(context, new_port)
|
||||
return new_port
|
||||
|
||||
def get_port(self, context, id, fields=None):
|
||||
@ -600,13 +687,16 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
|
||||
self.servers.rest_update_port(net_tenant_id,
|
||||
new_port["network_id"],
|
||||
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
|
||||
|
||||
def delete_port(self, context, port_id, l3_port_check=True):
|
||||
"""Delete a port.
|
||||
|
||||
:param context: neutron api request context
|
||||
:param id: UUID representing the port to delete.
|
||||
|
||||
@ -623,6 +713,7 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
|
||||
self.prevent_l3_port_deletion(context, port_id)
|
||||
with context.session.begin(subtransactions=True):
|
||||
self.disassociate_floatingips(context, port_id)
|
||||
self._delete_port_security_group_bindings(context, port_id)
|
||||
super(NeutronRestProxyV2, self).delete_port(context, port_id)
|
||||
|
||||
def _delete_port(self, context, port_id):
|
||||
|
@ -26,7 +26,9 @@ from neutron.plugins.bigswitch import config
|
||||
from neutron.tests.unit.bigswitch import fake_server
|
||||
|
||||
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'
|
||||
|
||||
|
||||
@ -45,7 +47,9 @@ class BigSwitchTestBase(object):
|
||||
self.httpPatch = mock.patch(HTTPCON, create=True,
|
||||
new=fake_server.HTTPConnectionMock)
|
||||
self.plugin_notifier_p = mock.patch(NOTIFIER)
|
||||
self.callbacks_p = mock.patch(CALLBACKS)
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
self.addCleanup(db.clear_db)
|
||||
self.callbacks_p.start()
|
||||
self.plugin_notifier_p.start()
|
||||
self.httpPatch.start()
|
||||
|
188
neutron/tests/unit/bigswitch/test_restproxy_agent.py
Normal file
188
neutron/tests/unit/bigswitch/test_restproxy_agent.py
Normal 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()
|
||||
])
|
46
neutron/tests/unit/bigswitch/test_security_groups.py
Normal file
46
neutron/tests/unit/bigswitch/test_security_groups.py
Normal 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
|
@ -98,6 +98,7 @@ console_scripts =
|
||||
neutron-nsx-manage = neutron.plugins.nicira.shell:main
|
||||
neutron-openvswitch-agent = neutron.plugins.openvswitch.agent.ovs_neutron_agent: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-server = neutron.server:main
|
||||
neutron-rootwrap = oslo.rootwrap.cmd:main
|
||||
|
Loading…
Reference in New Issue
Block a user