From 5f7efbf47c27061464d3ac5c28154f7afe0a6076 Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Thu, 10 Jan 2013 16:24:54 -0800 Subject: [PATCH] Implements quantum security groups support on OVS plugin implements bp quantum-security-groups-iptables-ovs - Adding [SECURITYGROUP] firewall_driver to the conf - Adding NoopFirewallDriver - Adding OVSHybridIptablesFirewallDriver - Refactoring security group code to support OVS plugin Change-Id: I7aabc296265afc47d938121543ace57cce6cc521 --- .../openvswitch/ovs_quantum_plugin.ini | 4 + quantum/agent/firewall.py | 30 +++ quantum/agent/linux/iptables_firewall.py | 37 +++- quantum/agent/securitygroups_rpc.py | 25 ++- .../versions/3cb5d900c5de_security_groups.py | 3 +- quantum/db/securitygroups_rpc_base.py | 42 ++++ .../agent/linuxbridge_quantum_agent.py | 3 +- .../plugins/linuxbridge/db/l2network_db_v2.py | 2 +- .../plugins/linuxbridge/lb_quantum_plugin.py | 43 ++-- .../openvswitch/agent/ovs_quantum_agent.py | 38 +++- quantum/plugins/openvswitch/ovs_db_v2.py | 29 +++ .../plugins/openvswitch/ovs_quantum_plugin.py | 112 ++++++++-- .../linuxbridge/test_lb_security_group.py | 55 +---- .../openvswitch/test_openvswitch_plugin.py | 2 +- .../openvswitch/test_ovs_quantum_agent.py | 13 +- .../openvswitch/test_ovs_security_group.py | 93 ++++++++ quantum/tests/unit/test_iptables_firewall.py | 3 +- .../tests/unit/test_security_groups_rpc.py | 204 +++++++++++++----- 18 files changed, 548 insertions(+), 190 deletions(-) create mode 100644 quantum/tests/unit/openvswitch/test_ovs_security_group.py diff --git a/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini b/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini index a6308467e4..d0e1527b84 100644 --- a/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini +++ b/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini @@ -97,6 +97,10 @@ reconnect_interval = 2 # Agent's polling interval in seconds polling_interval = 2 +[SECURITYGROUP] +# Firewall driver for realizing quantum security group function +# firewall_driver = quantum.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver + #----------------------------------------------------------------------------- # Sample Configurations. #----------------------------------------------------------------------------- diff --git a/quantum/agent/firewall.py b/quantum/agent/firewall.py index 4795ed3932..71c2610fbf 100644 --- a/quantum/agent/firewall.py +++ b/quantum/agent/firewall.py @@ -103,3 +103,33 @@ class FirewallDriver(object): yield finally: self.filter_defer_apply_off() + + +class NoopFirewallDriver(FirewallDriver): + """ Noop Firewall Driver. + + Firewall driver which does nothing. + This driver is for disabling the firewall functionality. + """ + + def prepare_port_filter(self, port): + pass + + def apply_port_filter(self, port): + pass + + def update_port_filter(self, port): + pass + + def remove_port_filter(self, port): + pass + + def filter_defer_apply_on(self): + pass + + def filter_defer_apply_off(self): + pass + + @property + def ports(self): + return {} diff --git a/quantum/agent/linux/iptables_firewall.py b/quantum/agent/linux/iptables_firewall.py index a0f342e415..057ee9685f 100644 --- a/quantum/agent/linux/iptables_firewall.py +++ b/quantum/agent/linux/iptables_firewall.py @@ -18,7 +18,9 @@ import netaddr from quantum.agent import firewall +from quantum.agent.linux import iptables_manager from quantum.common import constants +from quantum.openstack.common import cfg from quantum.openstack.common import log as logging @@ -28,16 +30,18 @@ INGRESS_DIRECTION = 'ingress' EGRESS_DIRECTION = 'egress' CHAIN_NAME_PREFIX = {INGRESS_DIRECTION: 'i', EGRESS_DIRECTION: 'o'} -IPTABLES_DIRECTION = {INGRESS_DIRECTION: 'physdev-out', - EGRESS_DIRECTION: 'physdev-in'} +LINUX_DEV_LEN = 14 class IptablesFirewallDriver(firewall.FirewallDriver): """Driver which enforces security groups through iptables rules.""" + IPTABLES_DIRECTION = {INGRESS_DIRECTION: 'physdev-out', + EGRESS_DIRECTION: 'physdev-in'} - def __init__(self, iptables_manager): - self.iptables = iptables_manager - + def __init__(self): + self.iptables = iptables_manager.IptablesManager( + root_helper=cfg.CONF.AGENT.root_helper, + use_ipv6=True) # list of port which has security group self.filtered_ports = {} self._add_fallback_chain_v4v6() @@ -121,6 +125,9 @@ class IptablesFirewallDriver(firewall.FirewallDriver): for rule in ipv6_rules: self.iptables.ipv6['filter'].add_rule(chain_name, rule) + def _get_device_name(self, port): + return port['device'] + def _add_chain(self, port, direction): chain_name = self._port_chain_name(port, direction) self._add_chain_by_name_v4v6(chain_name) @@ -131,16 +138,16 @@ class IptablesFirewallDriver(firewall.FirewallDriver): # We accept the packet at the end of SG_CHAIN. # jump to the security group chain - device = port['device'] + device = self._get_device_name(port) jump_rule = ['-m physdev --physdev-is-bridged --%s ' - '%s -j $%s' % (IPTABLES_DIRECTION[direction], + '%s -j $%s' % (self.IPTABLES_DIRECTION[direction], device, SG_CHAIN)] self._add_rule_to_chain_v4v6('FORWARD', jump_rule, jump_rule) # jump to the chain based on the device jump_rule = ['-m physdev --physdev-is-bridged --%s ' - '%s -j $%s' % (IPTABLES_DIRECTION[direction], + '%s -j $%s' % (self.IPTABLES_DIRECTION[direction], device, chain_name)] self._add_rule_to_chain_v4v6(SG_CHAIN, jump_rule, jump_rule) @@ -278,3 +285,17 @@ class IptablesFirewallDriver(firewall.FirewallDriver): def filter_defer_apply_off(self): self.iptables.defer_apply_off() + + +class OVSHybridIptablesFirewallDriver(IptablesFirewallDriver): + OVS_HYBRID_TAP_PREFIX = 'tap' + + def _port_chain_name(self, port, direction): + #Note (nati) make chain name short less than 28 char + # with extra prefix + # ( see comment in iptables_manager ) + return '%s%s' % (CHAIN_NAME_PREFIX[direction], + port['device'][0:10]) + + def _get_device_name(self, port): + return (self.OVS_HYBRID_TAP_PREFIX + port['device'])[:LINUX_DEV_LEN] diff --git a/quantum/agent/securitygroups_rpc.py b/quantum/agent/securitygroups_rpc.py index e947c6628d..889d90e655 100644 --- a/quantum/agent/securitygroups_rpc.py +++ b/quantum/agent/securitygroups_rpc.py @@ -16,14 +16,21 @@ # under the License. # -from quantum.agent.linux import iptables_firewall -from quantum.agent.linux import iptables_manager from quantum.common import topics +from quantum.openstack.common import cfg +from quantum.openstack.common import importutils from quantum.openstack.common import log as logging LOG = logging.getLogger(__name__) SG_RPC_VERSION = "1.1" +security_group_opts = [ + cfg.StrOpt( + 'firewall_driver', + default='quantum.agent.firewall.NoopFirewallDriver') +] +cfg.CONF.register_opts(security_group_opts, 'SECURITYGROUP') + class SecurityGroupServerRpcApiMixin(object): """A mix-in that enable SecurityGroup support in plugin rpc @@ -42,6 +49,8 @@ class SecurityGroupAgentRpcCallbackMixin(object): """A mix-in that enable SecurityGroup agent support in agent implementations. """ + #mix-in object should be have sg_agent + sg_agent = None def security_groups_rule_updated(self, context, **kwargs): """ callback for security group rule update @@ -51,7 +60,7 @@ class SecurityGroupAgentRpcCallbackMixin(object): security_groups = kwargs.get('security_groups', []) LOG.debug( _("Security group rule updated on remote: %s"), security_groups) - self.agent.security_groups_rule_updated(security_groups) + self.sg_agent.security_groups_rule_updated(security_groups) def security_groups_member_updated(self, context, **kwargs): """ callback for security group member update @@ -61,14 +70,14 @@ class SecurityGroupAgentRpcCallbackMixin(object): security_groups = kwargs.get('security_groups', []) LOG.debug( _("Security group member updated on remote: %s"), security_groups) - self.agent.security_groups_member_updated(security_groups) + self.sg_agent.security_groups_member_updated(security_groups) def security_groups_provider_updated(self, context, **kwargs): """ callback for security group provider update """ LOG.debug(_("Provider rule updated")) - self.agent.security_groups_provider_updated() + self.sg_agent.security_groups_provider_updated() class SecurityGroupAgentRpcMixin(object): @@ -78,10 +87,8 @@ class SecurityGroupAgentRpcMixin(object): def init_firewall(self): LOG.debug(_("Init firewall settings")) - ip_manager = iptables_manager.IptablesManager( - root_helper=self.root_helper, - use_ipv6=True) - self.firewall = iptables_firewall.IptablesFirewallDriver(ip_manager) + self.firewall = importutils.import_object( + cfg.CONF.SECURITYGROUP.firewall_driver) def prepare_devices_filter(self, device_ids): if not device_ids: diff --git a/quantum/db/migration/alembic_migrations/versions/3cb5d900c5de_security_groups.py b/quantum/db/migration/alembic_migrations/versions/3cb5d900c5de_security_groups.py index d14a5391aa..2478ff9eb0 100644 --- a/quantum/db/migration/alembic_migrations/versions/3cb5d900c5de_security_groups.py +++ b/quantum/db/migration/alembic_migrations/versions/3cb5d900c5de_security_groups.py @@ -31,7 +31,8 @@ down_revision = '48b6f43f7471' migration_for_plugins = [ 'quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2', - 'quantum.plugins.nicira.nicira_nvp_plugin.QuantumPlugin.NvpPluginV2' + 'quantum.plugins.nicira.nicira_nvp_plugin.QuantumPlugin.NvpPluginV2', + 'quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2', ] from alembic import op diff --git a/quantum/db/securitygroups_rpc_base.py b/quantum/db/securitygroups_rpc_base.py index cbb7ba7950..8d00a736c9 100644 --- a/quantum/db/securitygroups_rpc_base.py +++ b/quantum/db/securitygroups_rpc_base.py @@ -18,8 +18,10 @@ import netaddr from quantum.common import constants as q_const +from quantum.common import utils from quantum.db import models_v2 from quantum.db import securitygroups_db as sg_db +from quantum.extensions import securitygroup as ext_sg from quantum.openstack.common import log as logging LOG = logging.getLogger(__name__) @@ -59,6 +61,46 @@ class SecurityGroupServerRpcMixin(sg_db.SecurityGroupDbMixin): self.notifier.security_groups_rule_updated(context, [rule['security_group_id']]) + def update_security_group_on_port(self, context, id, port, + original_port, updated_port): + """ update security groups on port + + This method returns a flag which indicates request notification + is required and does not perform notification itself. + It is because another changes for the port may require notification. + """ + need_notify = False + if ext_sg.SECURITYGROUPS in port['port']: + # delete the port binding and read it with the new rules + port['port'][ext_sg.SECURITYGROUPS] = ( + self._get_security_groups_on_port(context, port)) + self._delete_port_security_group_bindings(context, id) + self._process_port_create_security_group( + context, + id, + port['port'][ext_sg.SECURITYGROUPS]) + need_notify = True + self._extend_port_dict_security_group(context, updated_port) + return need_notify + + def is_security_group_member_updated(self, context, + original_port, updated_port): + """ check security group member updated or not + + This method returns a flag which indicates request notification + is required and does not perform notification itself. + It is because another changes for the port may require notification. + """ + need_notify = False + if (original_port['fixed_ips'] != updated_port['fixed_ips'] or + not utils.compare_elements( + original_port.get(ext_sg.SECURITYGROUPS), + updated_port.get(ext_sg.SECURITYGROUPS))): + self.notifier.security_groups_member_updated( + context, updated_port.get(ext_sg.SECURITYGROUPS)) + need_notify = True + return need_notify + class SecurityGroupServerRpcCallbackMixin(object): """A mix-in that enable SecurityGroup agent diff --git a/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py b/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py index 75ba36324e..11d849f0dc 100755 --- a/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py +++ b/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py @@ -400,6 +400,7 @@ class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin): def __init__(self, context, agent): self.context = context self.agent = agent + self.sg_agent = agent def network_delete(self, context, **kwargs): LOG.debug(_("network_delete received")) @@ -418,7 +419,7 @@ class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin): return if 'security_groups' in port: - self.agent.refresh_firewall() + self.sg_agent.refresh_firewall() if port['admin_state_up']: vlan_id = kwargs.get('vlan_id') diff --git a/quantum/plugins/linuxbridge/db/l2network_db_v2.py b/quantum/plugins/linuxbridge/db/l2network_db_v2.py index 133f2a3cdd..bd82e41e56 100644 --- a/quantum/plugins/linuxbridge/db/l2network_db_v2.py +++ b/quantum/plugins/linuxbridge/db/l2network_db_v2.py @@ -18,9 +18,9 @@ from sqlalchemy.orm import exc from quantum.common import exceptions as q_exc import quantum.db.api as db -from quantum import manager from quantum.db import models_v2 from quantum.db import securitygroups_db as sg_db +from quantum import manager from quantum.openstack.common import log as logging # NOTE (e0ne): this import is needed for config init from quantum.plugins.linuxbridge.common import config diff --git a/quantum/plugins/linuxbridge/lb_quantum_plugin.py b/quantum/plugins/linuxbridge/lb_quantum_plugin.py index 89b3d7a015..b098284b1a 100644 --- a/quantum/plugins/linuxbridge/lb_quantum_plugin.py +++ b/quantum/plugins/linuxbridge/lb_quantum_plugin.py @@ -499,37 +499,23 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, def update_port(self, context, id, port): original_port = self.get_port(context, id) session = context.session - port_updated = False + need_port_update_notify = False + with session.begin(subtransactions=True): - # delete the port binding and read it with the new rules - if ext_sg.SECURITYGROUPS in port['port']: - port['port'][ext_sg.SECURITYGROUPS] = ( - self._get_security_groups_on_port(context, port)) - self._delete_port_security_group_bindings(context, id) - self._process_port_create_security_group( - context, - id, - port['port'][ext_sg.SECURITYGROUPS]) - port_updated = True - - port = super(LinuxBridgePluginV2, self).update_port( + updated_port = super(LinuxBridgePluginV2, self).update_port( context, id, port) - self._extend_port_dict_security_group(context, port) + need_port_update_notify = self.update_security_group_on_port( + context, id, port, original_port, updated_port) - if original_port['admin_state_up'] != port['admin_state_up']: - port_updated = True + need_port_update_notify |= self.is_security_group_member_updated( + context, original_port, updated_port) - if (original_port['fixed_ips'] != port['fixed_ips'] or - not utils.compare_elements( - original_port.get(ext_sg.SECURITYGROUPS), - port.get(ext_sg.SECURITYGROUPS))): - self.notifier.security_groups_member_updated( - context, port.get(ext_sg.SECURITYGROUPS)) + if original_port['admin_state_up'] != updated_port['admin_state_up']: + need_port_update_notify = True - if port_updated: - self._notify_port_updated(context, port) - - return self._extend_port_dict_binding(context, port) + if need_port_update_notify: + self._notify_port_updated(context, updated_port) + return self._extend_port_dict_binding(context, updated_port) def delete_port(self, context, id, l3_port_check=True): @@ -544,8 +530,9 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, port = self.get_port(context, id) self._delete_port_security_group_bindings(context, id) super(LinuxBridgePluginV2, self).delete_port(context, id) - self.notifier.security_groups_member_updated( - context, port.get(ext_sg.SECURITYGROUPS)) + + self.notifier.security_groups_member_updated( + context, port.get(ext_sg.SECURITYGROUPS)) def _notify_port_updated(self, context, port): binding = db.get_network_binding(context.session, diff --git a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py index 6e058eeef0..6123ab13a4 100644 --- a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py +++ b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py @@ -29,6 +29,7 @@ from quantum.agent.linux import ip_lib from quantum.agent.linux import ovs_lib from quantum.agent.linux import utils from quantum.agent import rpc as agent_rpc +from quantum.agent import securitygroups_rpc as sg_rpc from quantum.common import config as logging_config from quantum.common import topics from quantum.common import utils as q_utils @@ -37,6 +38,7 @@ from quantum.openstack.common import cfg from quantum.openstack.common import log as logging from quantum.openstack.common.rpc import dispatcher from quantum.plugins.openvswitch.common import config +from quantum.extensions import securitygroup as ext_sg from quantum.plugins.openvswitch.common import constants @@ -95,7 +97,20 @@ class Port(object): return hash(self.id) -class OVSQuantumAgent(object): +class OVSPluginApi(agent_rpc.PluginApi, + sg_rpc.SecurityGroupServerRpcApiMixin): + pass + + +class OVSSecurityGroupAgent(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 OVSQuantumAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): '''Implements OVS-based tunneling, VLANs and flat networks. Two local bridges are created: an integration bridge (defaults to @@ -128,8 +143,10 @@ class OVSQuantumAgent(object): # Upper bound on available vlans. MAX_VLAN_TAG = 4094 - # Set RPC API version to 1.0 by default. - RPC_API_VERSION = '1.0' + # history + # 1.0 Initial version + # 1.1 Support Security Group RPC + RPC_API_VERSION = '1.1' def __init__(self, integ_br, tun_br, local_ip, bridge_mappings, root_helper, @@ -162,11 +179,16 @@ class OVSQuantumAgent(object): self.setup_rpc(integ_br) + # Security group agent supprot + self.sg_agent = OVSSecurityGroupAgent(self.context, + self.plugin_rpc, + root_helper) + def setup_rpc(self, integ_br): mac = utils.get_interface_mac(integ_br) self.agent_id = '%s%s' % ('ovs', (mac.replace(":", ""))) self.topic = topics.AGENT - self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN) + self.plugin_rpc = OVSPluginApi(topics.PLUGIN) # RPC network init self.context = context.get_admin_context_without_session() @@ -175,7 +197,8 @@ class OVSQuantumAgent(object): # Define the listening consumers for the agent consumers = [[topics.PORT, topics.UPDATE], [topics.NETWORK, topics.DELETE], - [constants.TUNNEL, topics.UPDATE]] + [constants.TUNNEL, topics.UPDATE], + [topics.SECURITY_GROUP, topics.UPDATE]] self.connection = agent_rpc.create_consumers(self.dispatcher, self.topic, consumers) @@ -203,6 +226,9 @@ class OVSQuantumAgent(object): vif_port = self.int_br.get_vif_port_by_id(port['id']) if not vif_port: return + + if ext_sg.SECURITYGROUPS in port: + self.sg_agent.refresh_firewall() network_type = kwargs.get('network_type') segmentation_id = kwargs.get('segmentation_id') physical_network = kwargs.get('physical_network') @@ -549,6 +575,7 @@ class OVSQuantumAgent(object): def treat_devices_added(self, devices): resync = False + self.sg_agent.prepare_devices_filter(devices) for device in devices: LOG.info(_("Port %s added"), device) try: @@ -578,6 +605,7 @@ class OVSQuantumAgent(object): def treat_devices_removed(self, devices): resync = False + self.sg_agent.remove_devices_filter(devices) for device in devices: LOG.info(_("Attachment %s removed"), device) try: diff --git a/quantum/plugins/openvswitch/ovs_db_v2.py b/quantum/plugins/openvswitch/ovs_db_v2.py index a19e875c45..3485756a6f 100644 --- a/quantum/plugins/openvswitch/ovs_db_v2.py +++ b/quantum/plugins/openvswitch/ovs_db_v2.py @@ -21,6 +21,9 @@ from sqlalchemy.orm import exc from quantum.common import exceptions as q_exc import quantum.db.api as db from quantum.db import models_v2 +from quantum.db import securitygroups_db as sg_db +from quantum.extensions import securitygroup as ext_sg +from quantum import manager from quantum.openstack.common import cfg from quantum.openstack.common import log as logging from quantum.plugins.openvswitch.common import constants @@ -300,6 +303,32 @@ def get_port(port_id): return port +def get_port_from_device(port_id): + """Get port from database""" + LOG.debug(_("get_port_with_securitygroups() called:port_id=%s"), port_id) + session = db.get_session() + sg_binding_port = sg_db.SecurityGroupPortBinding.port_id + + 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 == port_id) + port_and_sgs = query.all() + if not port_and_sgs: + return None + port = port_and_sgs[0][0] + plugin = manager.QuantumManager.get_plugin() + port_dict = plugin._make_port_dict(port) + port_dict[ext_sg.SECURITYGROUPS] = [ + 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 + + def set_port_status(port_id, status): session = db.get_session() try: diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index 9045f7bbdc..ce489b7442 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -22,6 +22,7 @@ import sys +from quantum.agent import securitygroups_rpc as sg_rpc from quantum.api.v2 import attributes from quantum.common import constants as q_const from quantum.common import exceptions as q_exc @@ -33,8 +34,10 @@ from quantum.db import l3_db from quantum.db import l3_rpc_base # NOTE: quota_db cannot be removed, it is for db model from quantum.db import quota_db +from quantum.db import securitygroups_rpc_base as sg_db_rpc from quantum.extensions import portbindings from quantum.extensions import providernet as provider +from quantum.extensions import securitygroup as ext_sg from quantum.openstack.common import cfg from quantum.openstack.common import log as logging from quantum.openstack.common import rpc @@ -49,10 +52,14 @@ LOG = logging.getLogger(__name__) class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin, - l3_rpc_base.L3RpcCallbackMixin): + l3_rpc_base.L3RpcCallbackMixin, + sg_db_rpc.SecurityGroupServerRpcCallbackMixin): - # Set RPC API version to 1.0 by default. - RPC_API_VERSION = '1.0' + # history + # 1.0 Initial version + # 1.1 Support Security Group RPC + + RPC_API_VERSION = '1.1' def __init__(self, notifier): self.notifier = notifier @@ -65,6 +72,13 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin, ''' return q_rpc.PluginRpcDispatcher([self]) + @classmethod + def get_port_from_device(cls, device): + port = ovs_db_v2.get_port_from_device(device) + if port: + port['device'] = device + return port + def get_device_details(self, rpc_context, **kwargs): """Agent requests device details""" agent_id = kwargs.get('agent_id') @@ -143,7 +157,8 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin, return entry -class AgentNotifierApi(proxy.RpcProxy): +class AgentNotifierApi(proxy.RpcProxy, + sg_rpc.SecurityGroupAgentRpcApiMixin): '''Agent side of the openvswitch rpc API. API version history: @@ -191,7 +206,8 @@ class AgentNotifierApi(proxy.RpcProxy): class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, - l3_db.L3_NAT_db_mixin): + l3_db.L3_NAT_db_mixin, + sg_db_rpc.SecurityGroupServerRpcMixin): """Implement the Quantum abstractions using Open vSwitch. Depending on whether tunneling is enabled, either a GRE tunnel or @@ -213,7 +229,8 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, # bulk operations. Name mangling is used in order to ensure it # is qualified by class __native_bulk_support = True - supported_extension_aliases = ["provider", "router", "binding", "quotas"] + supported_extension_aliases = ["provider", "router", + "binding", "quotas", "security-group"] network_view = "extension:provider_network:view" network_set = "extension:provider_network:set" @@ -425,6 +442,11 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, network['network']) session = context.session + #set up default security groups + tenant_id = self._get_tenant_id_for_create( + context, network['network']) + self._ensure_default_security_group(context, tenant_id) + with session.begin(subtransactions=True): if not network_type: # tenant network @@ -521,32 +543,68 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, def create_port(self, context, port): # Set port status as 'DOWN'. This will be updated by agent port['port']['status'] = q_const.PORT_STATUS_DOWN - port = super(OVSQuantumPluginV2, self).create_port(context, port) + session = context.session + with session.begin(subtransactions=True): + self._ensure_default_security_group_on_port(context, port) + sgids = self._get_security_groups_on_port(context, port) + port = super(OVSQuantumPluginV2, self).create_port(context, port) + self._process_port_create_security_group( + context, port['id'], sgids) + self._extend_port_dict_security_group(context, port) + #Note(nati): In order to allow dhcp packets, + # changes for dhcp ip should be notifified + if port['device_owner'] == q_const.DEVICE_OWNER_DHCP: + self.notifier.security_groups_provider_updated(context) + else: + self.notifier.security_groups_member_updated( + context, port.get(ext_sg.SECURITYGROUPS)) return self._extend_port_dict_binding(context, port) def get_port(self, context, id, fields=None): - port = super(OVSQuantumPluginV2, self).get_port(context, id, fields) - return self._fields(self._extend_port_dict_binding(context, port), - fields) + with context.session.begin(subtransactions=True): + port = super(OVSQuantumPluginV2, self).get_port(context, + id, fields) + self._extend_port_dict_security_group(context, port) + self._extend_port_dict_binding(context, port) + return self._fields(port, fields) def get_ports(self, context, filters=None, fields=None): - ports = super(OVSQuantumPluginV2, self).get_ports(context, filters, - fields) - return [self._fields(self._extend_port_dict_binding(context, port), - fields) for port in ports] + with context.session.begin(subtransactions=True): + ports = super(OVSQuantumPluginV2, self).get_ports( + context, filters, fields) + #TODO(nati) filter by security group + for port in ports: + self._extend_port_dict_security_group(context, port) + self._extend_port_dict_binding(context, port) + return [self._fields(port, fields) for port in ports] def update_port(self, context, id, port): - original_port = super(OVSQuantumPluginV2, self).get_port(context, - id) - port = super(OVSQuantumPluginV2, self).update_port(context, id, port) - if original_port['admin_state_up'] != port['admin_state_up']: + session = context.session + + need_port_update_notify = False + with session.begin(subtransactions=True): + original_port = super(OVSQuantumPluginV2, self).get_port( + context, id) + updated_port = super(OVSQuantumPluginV2, self).update_port( + context, id, port) + need_port_update_notify = self.update_security_group_on_port( + context, id, port, original_port, updated_port) + + need_port_update_notify |= self.is_security_group_member_updated( + context, original_port, updated_port) + + if original_port['admin_state_up'] != updated_port['admin_state_up']: + need_port_update_notify = True + + if need_port_update_notify: binding = ovs_db_v2.get_network_binding(None, - port['network_id']) - self.notifier.port_update(context, port, + updated_port['network_id']) + self.notifier.port_update(context, updated_port, binding.network_type, binding.segmentation_id, binding.physical_network) - return self._extend_port_dict_binding(context, port) + + return self._extend_port_dict_binding(context, updated_port) def delete_port(self, context, id, l3_port_check=True): @@ -554,5 +612,13 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, # and l3-router. If so, we should prevent deletion. if l3_port_check: self.prevent_l3_port_deletion(context, id) - self.disassociate_floatingips(context, id) - return super(OVSQuantumPluginV2, self).delete_port(context, id) + + session = context.session + with session.begin(subtransactions=True): + self.disassociate_floatingips(context, id) + port = self.get_port(context, id) + self._delete_port_security_group_bindings(context, id) + super(OVSQuantumPluginV2, self).delete_port(context, id) + + self.notifier.security_groups_member_updated( + context, port.get(ext_sg.SECURITYGROUPS)) diff --git a/quantum/tests/unit/linuxbridge/test_lb_security_group.py b/quantum/tests/unit/linuxbridge/test_lb_security_group.py index 8712c2747d..39f528c210 100644 --- a/quantum/tests/unit/linuxbridge/test_lb_security_group.py +++ b/quantum/tests/unit/linuxbridge/test_lb_security_group.py @@ -22,6 +22,8 @@ from quantum.api.v2 import attributes from quantum.extensions import securitygroup as ext_sg from quantum.plugins.linuxbridge.db import l2network_db_v2 as lb_db from quantum.tests.unit import test_extension_security_group as test_sg +from quantum.tests.unit import test_security_groups_rpc as test_sg_rpc + PLUGIN_NAME = ('quantum.plugins.linuxbridge.' 'lb_quantum_plugin.LinuxBridgePluginV2') @@ -53,56 +55,9 @@ class LinuxBridgeSecurityGroupsTestCase(test_sg.SecurityGroupDBTestCase): class TestLinuxBridgeSecurityGroups(LinuxBridgeSecurityGroupsTestCase, - test_sg.TestSecurityGroups): - - def test_security_group_rule_updated(self): - name = 'webservers' - description = 'my webservers' - with self.security_group(name, description) as sg: - with self.security_group(name, description) as sg2: - security_group_id = sg['security_group']['id'] - direction = "ingress" - source_group_id = sg2['security_group']['id'] - protocol = 'tcp' - port_range_min = 88 - port_range_max = 88 - with self.security_group_rule(security_group_id, direction, - protocol, port_range_min, - port_range_max, - source_group_id=source_group_id - ): - pass - self.notifier.assert_has_calls( - [call.security_groups_rule_updated(mock.ANY, - [security_group_id]), - call.security_groups_rule_updated(mock.ANY, - [security_group_id])]) - - def test_security_group_member_updated(self): - with self.network() as n: - with self.subnet(n): - with self.security_group() as sg: - security_group_id = sg['security_group']['id'] - res = self._create_port(self.fmt, n['network']['id']) - port = self.deserialize(self.fmt, res) - - data = {'port': {'fixed_ips': port['port']['fixed_ips'], - 'name': port['port']['name'], - ext_sg.SECURITYGROUPS: - [security_group_id]}} - - req = self.new_update_request('ports', data, - port['port']['id']) - res = self.deserialize(self.fmt, - req.get_response(self.api)) - self.assertEquals(res['port'][ext_sg.SECURITYGROUPS][0], - security_group_id) - self._delete('ports', port['port']['id']) - self.notifier.assert_has_calls( - [call.security_groups_member_updated( - mock.ANY, [mock.ANY]), - call.security_groups_member_updated( - mock.ANY, [security_group_id])]) + test_sg.TestSecurityGroups, + test_sg_rpc.SGNotificationTestMixin): + pass class TestLinuxBridgeSecurityGroupsXML(TestLinuxBridgeSecurityGroups): diff --git a/quantum/tests/unit/openvswitch/test_openvswitch_plugin.py b/quantum/tests/unit/openvswitch/test_openvswitch_plugin.py index 9ce64e0edb..7d7300f877 100644 --- a/quantum/tests/unit/openvswitch/test_openvswitch_plugin.py +++ b/quantum/tests/unit/openvswitch/test_openvswitch_plugin.py @@ -43,7 +43,7 @@ class TestOpenvswitchPortsV2(test_plugin.TestPortsV2, test_bindings.PortBindingsTestCase): VIF_TYPE = portbindings.VIF_TYPE_OVS - HAS_PORT_FILTER = False + HAS_PORT_FILTER = True def test_update_port_status_build(self): with self.port() as port: diff --git a/quantum/tests/unit/openvswitch/test_ovs_quantum_agent.py b/quantum/tests/unit/openvswitch/test_ovs_quantum_agent.py index a759a0b27e..c3ba7f7b44 100644 --- a/quantum/tests/unit/openvswitch/test_ovs_quantum_agent.py +++ b/quantum/tests/unit/openvswitch/test_ovs_quantum_agent.py @@ -21,6 +21,10 @@ from quantum.openstack.common import cfg from quantum.plugins.openvswitch.agent import ovs_quantum_agent +NOTIFIER = ('quantum.plugins.openvswitch.' + 'ovs_quantum_plugin.AgentNotifierApi') + + class CreateAgentConfigMap(unittest.TestCase): def test_create_agent_config_map_succeeds(self): @@ -38,6 +42,11 @@ class TestOvsQuantumAgent(unittest.TestCase): def setUp(self): self.addCleanup(cfg.CONF.reset) + self.addCleanup(mock.patch.stopall) + notifier_p = mock.patch(NOTIFIER) + notifier_cls = notifier_p.start() + self.notifier = mock.Mock() + notifier_cls.return_value = self.notifier # Avoid rpc initialization for unit tests cfg.CONF.set_override('rpc_backend', 'quantum.openstack.common.rpc.impl_fake') @@ -48,9 +57,7 @@ class TestOvsQuantumAgent(unittest.TestCase): with mock.patch('quantum.agent.linux.utils.get_interface_mac', return_value='000000000001'): self.agent = ovs_quantum_agent.OVSQuantumAgent(**kwargs) - self.agent.plugin_rpc = mock.Mock() - self.agent.context = mock.Mock() - self.agent.agent_id = mock.Mock() + self.agent.sg_agent = mock.Mock() def mock_port_bound(self, ofport=None): port = mock.Mock() diff --git a/quantum/tests/unit/openvswitch/test_ovs_security_group.py b/quantum/tests/unit/openvswitch/test_ovs_security_group.py new file mode 100644 index 0000000000..32bb96b931 --- /dev/null +++ b/quantum/tests/unit/openvswitch/test_ovs_security_group.py @@ -0,0 +1,93 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013, Nachi Ueno, NTT MCL, 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. + +import mock + +from quantum.api.v2 import attributes +from quantum.extensions import securitygroup as ext_sg +from quantum import manager +from quantum.tests.unit import test_extension_security_group as test_sg +from quantum.tests.unit import test_security_groups_rpc as test_sg_rpc + +PLUGIN_NAME = ('quantum.plugins.openvswitch.' + 'ovs_quantum_plugin.OVSQuantumPluginV2') +AGENT_NAME = ('quantum.plugins.openvswitch.' + 'agent.ovs_quantum_agent.OVSQuantumAgent') +NOTIFIER = ('quantum.plugins.openvswitch.' + 'ovs_quantum_plugin.AgentNotifierApi') + + +class OpenvswitchSecurityGroupsTestCase(test_sg.SecurityGroupDBTestCase): + _plugin_name = PLUGIN_NAME + + def setUp(self, plugin=None): + self.addCleanup(mock.patch.stopall) + notifier_p = mock.patch(NOTIFIER) + notifier_cls = notifier_p.start() + self.notifier = mock.Mock() + notifier_cls.return_value = self.notifier + self._attribute_map_bk_ = {} + for item in attributes.RESOURCE_ATTRIBUTE_MAP: + self._attribute_map_bk_[item] = (attributes. + RESOURCE_ATTRIBUTE_MAP[item]. + copy()) + super(OpenvswitchSecurityGroupsTestCase, self).setUp(PLUGIN_NAME) + + def tearDown(self): + super(OpenvswitchSecurityGroupsTestCase, self).tearDown() + attributes.RESOURCE_ATTRIBUTE_MAP = self._attribute_map_bk_ + + +class TestOpenvswitchSecurityGroups(OpenvswitchSecurityGroupsTestCase, + test_sg.TestSecurityGroups, + test_sg_rpc.SGNotificationTestMixin): + def test_security_group_get_port_from_device(self): + with self.network() as n: + with self.subnet(n): + with self.security_group() as sg: + security_group_id = sg['security_group']['id'] + res = self._create_port(self.fmt, n['network']['id']) + port = self.deserialize(self.fmt, res) + fixed_ips = port['port']['fixed_ips'] + data = {'port': {'fixed_ips': fixed_ips, + 'name': port['port']['name'], + ext_sg.SECURITYGROUPS: + [security_group_id]}} + + req = self.new_update_request('ports', data, + port['port']['id']) + res = self.deserialize(self.fmt, + req.get_response(self.api)) + port_id = res['port']['id'] + plugin = manager.QuantumManager.get_plugin() + port_dict = plugin.callbacks.get_port_from_device(port_id) + self.assertEqual(port_id, port_dict['id']) + self.assertEqual([security_group_id], + port_dict[ext_sg.SECURITYGROUPS]) + self.assertEqual([], port_dict['security_group_rules']) + self.assertEqual([fixed_ips[0]['ip_address']], + port_dict['fixed_ips']) + self._delete('ports', port_id) + + def test_security_group_get_port_from_device_with_no_port(self): + plugin = manager.QuantumManager.get_plugin() + port_dict = plugin.callbacks.get_port_from_device('bad_device_id') + self.assertEqual(None, port_dict) + + +class TestOpenvswitchSecurityGroupsXML(TestOpenvswitchSecurityGroups): + fmt = 'xml' diff --git a/quantum/tests/unit/test_iptables_firewall.py b/quantum/tests/unit/test_iptables_firewall.py index 92621382dd..e870a2420f 100644 --- a/quantum/tests/unit/test_iptables_firewall.py +++ b/quantum/tests/unit/test_iptables_firewall.py @@ -44,7 +44,8 @@ class IptablesFirewallTestCase(unittest.TestCase): self.iptables_inst.ipv6 = {'filter': self.v6filter_inst} iptables_cls.return_value = self.iptables_inst - self.firewall = IptablesFirewallDriver(self.iptables_inst) + self.firewall = IptablesFirewallDriver() + self.firewall.iptables = self.iptables_inst def tearDown(self): self.iptables_cls_p.stop() diff --git a/quantum/tests/unit/test_security_groups_rpc.py b/quantum/tests/unit/test_security_groups_rpc.py index e64c112ebd..34177ef3bc 100644 --- a/quantum/tests/unit/test_security_groups_rpc.py +++ b/quantum/tests/unit/test_security_groups_rpc.py @@ -28,6 +28,8 @@ from quantum.agent import rpc as agent_rpc from quantum.agent import securitygroups_rpc as sg_rpc from quantum import context from quantum.db import securitygroups_rpc_base as sg_db_rpc +from quantum.extensions import securitygroup as ext_sg +from quantum.openstack.common import cfg from quantum.openstack.common.rpc import proxy from quantum.tests.unit import test_extension_security_group as test_sg from quantum.tests.unit import test_iptables_firewall as test_fw @@ -371,23 +373,23 @@ class SGServerRpcCallBackMixinTestCaseXML(SGServerRpcCallBackMixinTestCase): class SGAgentRpcCallBackMixinTestCase(unittest.TestCase): def setUp(self): self.rpc = sg_rpc.SecurityGroupAgentRpcCallbackMixin() - self.rpc.agent = mock.Mock() + self.rpc.sg_agent = mock.Mock() def test_security_groups_rule_updated(self): self.rpc.security_groups_rule_updated(None, security_groups=['fake_sgid']) - self.rpc.agent.assert_has_calls( + self.rpc.sg_agent.assert_has_calls( [call.security_groups_rule_updated(['fake_sgid'])]) def test_security_groups_member_updated(self): self.rpc.security_groups_member_updated(None, security_groups=['fake_sgid']) - self.rpc.agent.assert_has_calls( + self.rpc.sg_agent.assert_has_calls( [call.security_groups_member_updated(['fake_sgid'])]) def test_security_groups_provider_updated(self): self.rpc.security_groups_provider_updated(None) - self.rpc.agent.assert_has_calls( + self.rpc.sg_agent.assert_has_calls( [call.security_groups_provider_updated()]) @@ -582,16 +584,16 @@ IPTABLES_FILTER_1 = """:%(bn)s-(%(chains)s) - [0:0] -A OUTPUT -j %(bn)s-OUTPUT -A FORWARD -j %(bn)s-FORWARD -A %(bn)s-sg-fallback -j DROP --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-i_port1 -A %(bn)s-i_port1 -m state --state INVALID -j DROP -A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port1 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2 -A %(bn)s-i_port1 -j RETURN -p tcp --dport 22 -A %(bn)s-i_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP -A %(bn)s-o_port1 -p udp --sport 68 --dport 67 -j RETURN -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP @@ -619,17 +621,17 @@ IPTABLES_FILTER_1_2 = """:%(bn)s-(%(chains)s) - [0:0] -A OUTPUT -j %(bn)s-OUTPUT -A FORWARD -j %(bn)s-FORWARD -A %(bn)s-sg-fallback -j DROP --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-i_port1 -A %(bn)s-i_port1 -m state --state INVALID -j DROP -A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port1 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2 -A %(bn)s-i_port1 -j RETURN -p tcp --dport 22 -A %(bn)s-i_port1 -j RETURN -s 10.0.0.4 -A %(bn)s-i_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP -A %(bn)s-o_port1 -p udp --sport 68 --dport 67 -j RETURN -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP @@ -661,17 +663,17 @@ IPTABLES_FILTER_2 = """:%(bn)s-(%(chains)s) - [0:0] -A OUTPUT -j %(bn)s-OUTPUT -A FORWARD -j %(bn)s-FORWARD -A %(bn)s-sg-fallback -j DROP --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-i_port1 -A %(bn)s-i_port1 -m state --state INVALID -j DROP -A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port1 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2 -A %(bn)s-i_port1 -j RETURN -p tcp --dport 22 -A %(bn)s-i_port1 -j RETURN -s 10.0.0.4 -A %(bn)s-i_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP -A %(bn)s-o_port1 -p udp --sport 68 --dport 67 -j RETURN -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP @@ -680,17 +682,17 @@ IPTABLES_FILTER_2 = """:%(bn)s-(%(chains)s) - [0:0] -A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-o_port1 -j RETURN -A %(bn)s-o_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port2 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port2 -j %(bn)s-i_port2 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port2 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port2 -j %(bn)s-i_port2 -A %(bn)s-i_port2 -m state --state INVALID -j DROP -A %(bn)s-i_port2 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port2 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2 -A %(bn)s-i_port2 -j RETURN -p tcp --dport 22 -A %(bn)s-i_port2 -j RETURN -s 10.0.0.3 -A %(bn)s-i_port2 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port2 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-o_port2 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-o_port2 -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP -A %(bn)s-o_port2 -p udp --sport 68 --dport 67 -j RETURN -A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP @@ -720,16 +722,16 @@ IPTABLES_FILTER_2_2 = """:%(bn)s-(%(chains)s) - [0:0] -A OUTPUT -j %(bn)s-OUTPUT -A FORWARD -j %(bn)s-FORWARD -A %(bn)s-sg-fallback -j DROP --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-i_port1 -A %(bn)s-i_port1 -m state --state INVALID -j DROP -A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port1 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2 -A %(bn)s-i_port1 -j RETURN -p tcp --dport 22 -A %(bn)s-i_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP -A %(bn)s-o_port1 -p udp --sport 68 --dport 67 -j RETURN -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP @@ -738,17 +740,17 @@ IPTABLES_FILTER_2_2 = """:%(bn)s-(%(chains)s) - [0:0] -A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-o_port1 -j RETURN -A %(bn)s-o_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port2 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port2 -j %(bn)s-i_port2 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port2 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port2 -j %(bn)s-i_port2 -A %(bn)s-i_port2 -m state --state INVALID -j DROP -A %(bn)s-i_port2 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port2 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2 -A %(bn)s-i_port2 -j RETURN -p tcp --dport 22 -A %(bn)s-i_port2 -j RETURN -s 10.0.0.3 -A %(bn)s-i_port2 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port2 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-o_port2 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-o_port2 -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP -A %(bn)s-o_port2 -p udp --sport 68 --dport 67 -j RETURN -A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP @@ -778,8 +780,8 @@ IPTABLES_FILTER_2_3 = """:%(bn)s-(%(chains)s) - [0:0] -A OUTPUT -j %(bn)s-OUTPUT -A FORWARD -j %(bn)s-FORWARD -A %(bn)s-sg-fallback -j DROP --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-i_port1 -A %(bn)s-i_port1 -m state --state INVALID -j DROP -A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port1 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2 @@ -787,9 +789,9 @@ IPTABLES_FILTER_2_3 = """:%(bn)s-(%(chains)s) - [0:0] -A %(bn)s-i_port1 -j RETURN -s 10.0.0.4 -A %(bn)s-i_port1 -j RETURN -p icmp -A %(bn)s-i_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP -A %(bn)s-o_port1 -p udp --sport 68 --dport 67 -j RETURN -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP @@ -798,8 +800,8 @@ IPTABLES_FILTER_2_3 = """:%(bn)s-(%(chains)s) - [0:0] -A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-o_port1 -j RETURN -A %(bn)s-o_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port2 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port2 -j %(bn)s-i_port2 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port2 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port2 -j %(bn)s-i_port2 -A %(bn)s-i_port2 -m state --state INVALID -j DROP -A %(bn)s-i_port2 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port2 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2 @@ -807,9 +809,9 @@ IPTABLES_FILTER_2_3 = """:%(bn)s-(%(chains)s) - [0:0] -A %(bn)s-i_port2 -j RETURN -s 10.0.0.3 -A %(bn)s-i_port2 -j RETURN -p icmp -A %(bn)s-i_port2 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port2 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-o_port2 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-o_port2 -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP -A %(bn)s-o_port2 -p udp --sport 68 --dport 67 -j RETURN -A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP @@ -855,14 +857,14 @@ IPTABLES_FILTER_V6_1 = """:%(bn)s-(%(chains)s) - [0:0] -A OUTPUT -j %(bn)s-OUTPUT -A FORWARD -j %(bn)s-FORWARD -A %(bn)s-sg-fallback -j DROP --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-i_port1 -A %(bn)s-i_port1 -m state --state INVALID -j DROP -A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP -A %(bn)s-o_port1 -p icmpv6 -j RETURN -A %(bn)s-o_port1 -m state --state INVALID -j DROP @@ -890,27 +892,27 @@ IPTABLES_FILTER_V6_2 = """:%(bn)s-(%(chains)s) - [0:0] -A OUTPUT -j %(bn)s-OUTPUT -A FORWARD -j %(bn)s-FORWARD -A %(bn)s-sg-fallback -j DROP --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port1 -j %(bn)s-i_port1 -A %(bn)s-i_port1 -m state --state INVALID -j DROP -A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port1 -j %(bn)s-o_port1 -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP -A %(bn)s-o_port1 -p icmpv6 -j RETURN -A %(bn)s-o_port1 -m state --state INVALID -j DROP -A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-o_port1 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port2 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port2 -j %(bn)s-i_port2 +-A %(bn)s-FORWARD %(physdev)s --physdev-INGRESS tap_port2 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-INGRESS tap_port2 -j %(bn)s-i_port2 -A %(bn)s-i_port2 -m state --state INVALID -j DROP -A %(bn)s-i_port2 -m state --state ESTABLISHED,RELATED -j RETURN -A %(bn)s-i_port2 -j %(bn)s-sg-fallback --A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port2 -j %(bn)s-sg-chain --A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2 --A %(bn)s-INPUT %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2 +-A %(bn)s-FORWARD %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-sg-chain +-A %(bn)s-sg-chain %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-o_port2 +-A %(bn)s-INPUT %(physdev)s --physdev-EGRESS tap_port2 -j %(bn)s-o_port2 -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP -A %(bn)s-o_port2 -p icmpv6 -j RETURN -A %(bn)s-o_port2 -m state --state INVALID -j DROP @@ -936,11 +938,25 @@ IPTABLES_FILTER_V6_EMPTY = """:%(bn)s-(%(chains)s) - [0:0] -A %(bn)s-sg-fallback -j DROP """ % IPTABLES_ARG +FIREWALL_BASE_PACKAGE = 'quantum.agent.linux.iptables_firewall.' + class TestSecurityGroupAgentWithIptables(unittest.TestCase): + FIREWALL_DRIVER = FIREWALL_BASE_PACKAGE + 'IptablesFirewallDriver' + PHYSDEV_INGRESS = 'physdev-out' + PHYSDEV_EGRESS = 'physdev-in' + def setUp(self): self.mox = mox.Mox() + agent_opts = [ + cfg.StrOpt('root_helper', default='sudo'), + ] + cfg.CONF.register_opts(agent_opts, "AGENT") + cfg.CONF.set_override( + 'firewall_driver', + self.FIREWALL_DRIVER, + group='SECURITYGROUP') self.addCleanup(mock.patch.stopall) self.addCleanup(self.mox.UnsetStubs) @@ -1018,6 +1034,8 @@ class TestSecurityGroupAgentWithIptables(unittest.TestCase): 'security_group1']} def _regex(self, value): + value = value.replace('physdev-INGRESS', self.PHYSDEV_INGRESS) + value = value.replace('physdev-EGRESS', self.PHYSDEV_EGRESS) value = value.replace('\n', '\\n') value = value.replace('[', '\[') value = value.replace(']', '\]') @@ -1093,3 +1111,71 @@ class TestSecurityGroupAgentWithIptables(unittest.TestCase): self.agent.security_groups_rule_updated(['security_group1']) self.mox.VerifyAll() + + +class SGNotificationTestMixin(): + def test_security_group_rule_updated(self): + name = 'webservers' + description = 'my webservers' + with self.security_group(name, description) as sg: + with self.security_group(name, description) as sg2: + security_group_id = sg['security_group']['id'] + direction = "ingress" + source_group_id = sg2['security_group']['id'] + protocol = 'tcp' + port_range_min = 88 + port_range_max = 88 + with self.security_group_rule(security_group_id, direction, + protocol, port_range_min, + port_range_max, + source_group_id=source_group_id + ): + pass + self.notifier.assert_has_calls( + [call.security_groups_rule_updated(mock.ANY, + [security_group_id]), + call.security_groups_rule_updated(mock.ANY, + [security_group_id])]) + + def test_security_group_member_updated(self): + with self.network() as n: + with self.subnet(n): + with self.security_group() as sg: + security_group_id = sg['security_group']['id'] + res = self._create_port(self.fmt, n['network']['id']) + port = self.deserialize(self.fmt, res) + + data = {'port': {'fixed_ips': port['port']['fixed_ips'], + 'name': port['port']['name'], + ext_sg.SECURITYGROUPS: + [security_group_id]}} + + req = self.new_update_request('ports', data, + port['port']['id']) + res = self.deserialize(self.fmt, + req.get_response(self.api)) + self.assertEquals(res['port'][ext_sg.SECURITYGROUPS][0], + security_group_id) + self._delete('ports', port['port']['id']) + self.notifier.assert_has_calls( + [call.security_groups_member_updated( + mock.ANY, [mock.ANY]), + call.security_groups_member_updated( + mock.ANY, [security_group_id])]) + + +class TestSecurityGroupAgentWithOVSIptables( + TestSecurityGroupAgentWithIptables): + + FIREWALL_DRIVER = FIREWALL_BASE_PACKAGE + 'OVSHybridIptablesFirewallDriver' + + def _regex(self, value): + #Note(nati): tap is prefixed on the device + # in the OVSHybridIptablesFirewallDriver + + value = value.replace('tap_port', 'taptap_port') + value = value.replace('o_port', 'otap_port') + value = value.replace('i_port', 'itap_port') + return super( + TestSecurityGroupAgentWithOVSIptables, + self)._regex(value)