]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
vArmour gateway agent and FWaaS driver
authorgaryduan <garyduan@gmail.com>
Sun, 25 Aug 2013 00:34:00 +0000 (17:34 -0700)
committergaryduan <garyduan@gmail.com>
Thu, 5 Sep 2013 04:03:25 +0000 (21:03 -0700)
This patch enables vArmour's routing and firewall services to be deployed in
openstack environment.
- as gateway for internal networks
- support SNAT and DNAT (floating IP)
- FWaaS services

Implements: blueprint varmour-fwaas-driver
Change-Id: I6ddfa3137ed7e2a3fcf16a764d1340a8eae9359a

neutron/services/firewall/agents/varmour/__init__.py [new file with mode: 0755]
neutron/services/firewall/agents/varmour/varmour_api.py [new file with mode: 0755]
neutron/services/firewall/agents/varmour/varmour_router.py [new file with mode: 0755]
neutron/services/firewall/agents/varmour/varmour_utils.py [new file with mode: 0755]
neutron/services/firewall/drivers/varmour/__init__.py [new file with mode: 0755]
neutron/services/firewall/drivers/varmour/varmour_fwaas.py [new file with mode: 0755]
neutron/tests/unit/services/firewall/agents/varmour/__init__.py [new file with mode: 0755]
neutron/tests/unit/services/firewall/agents/varmour/test_varmour_router.py [new file with mode: 0644]
neutron/tests/unit/services/firewall/drivers/varmour/__init__.py [new file with mode: 0755]
neutron/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py [new file with mode: 0644]

diff --git a/neutron/services/firewall/agents/varmour/__init__.py b/neutron/services/firewall/agents/varmour/__init__.py
new file mode 100755 (executable)
index 0000000..5e8da71
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
diff --git a/neutron/services/firewall/agents/varmour/varmour_api.py b/neutron/services/firewall/agents/varmour/varmour_api.py
new file mode 100755 (executable)
index 0000000..86cb46f
--- /dev/null
@@ -0,0 +1,147 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 vArmour 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: Gary Duan, gduan@varmour.com, vArmour Networks
+
+import base64
+
+import httplib2
+from oslo.config import cfg
+
+from neutron.openstack.common import jsonutils as json
+from neutron.openstack.common import log as logging
+from neutron.services.firewall.agents.varmour import varmour_utils as va_utils
+
+OPTS = [
+    cfg.StrOpt('director', default='localhost',
+               help=_("vArmour director ip")),
+    cfg.StrOpt('director_port', default='443',
+               help=_("vArmour director port")),
+    cfg.StrOpt('username', default='varmour',
+               help=_("vArmour director username")),
+    cfg.StrOpt('password', default='varmour', secret=True,
+               help=_("vArmour director password")), ]
+
+cfg.CONF.register_opts(OPTS, "vArmour")
+
+LOG = logging.getLogger(__name__)
+
+REST_URL_PREFIX = '/api/v1.0'
+
+
+class vArmourAPIException(Exception):
+    message = _("An unknown exception.")
+
+    def __init__(self, **kwargs):
+        try:
+            self.err = self.message % kwargs
+
+        except Exception:
+            self.err = self.message
+
+    def __str__(self):
+        return self.err
+
+
+class AuthenticationFailure(vArmourAPIException):
+    message = _("Invalid login credential.")
+
+
+class vArmourRestAPI(object):
+
+    def __init__(self):
+        LOG.debug(_('vArmourRestAPI: started'))
+        self.user = cfg.CONF.vArmour.username
+        self.passwd = cfg.CONF.vArmour.password
+        self.server = cfg.CONF.vArmour.director
+        self.port = cfg.CONF.vArmour.director_port
+        self.timeout = 3
+        self.key = ''
+
+    def auth(self):
+        headers = {}
+        enc = base64.b64encode(self.user + ':' + self.passwd)
+        headers['Authorization'] = 'Basic ' + enc
+        resp = self.rest_api('POST', va_utils.REST_URL_AUTH, None, headers)
+        if resp and resp['status'] == 200:
+            self.key = resp['body']['auth']
+            return True
+        else:
+            raise AuthenticationFailure()
+
+    def commit(self):
+        self.rest_api('POST', va_utils.REST_URL_COMMIT)
+
+    def rest_api(self, method, url, body=None, headers=None):
+        url = REST_URL_PREFIX + url
+        if body:
+            body_data = json.dumps(body)
+        else:
+            body_data = ''
+        if not headers:
+            headers = {}
+            enc = base64.b64encode('%s:%s' % (self.user, self.key))
+            headers['Authorization'] = 'Basic ' + enc
+
+        LOG.debug(_("vArmourRestAPI: %(server)s %(port)s"),
+                  {'server': self.server, 'port': self.port})
+
+        try:
+            action = "https://" + self.server + ":" + self.port + url
+
+            LOG.debug(_("vArmourRestAPI Sending: "
+                        "%(method)s %(action)s %(headers)s %(body_data)s"),
+                      {'method': method, 'action': action,
+                       'headers': headers, 'body_data': body_data})
+
+            h = httplib2.Http(timeout=3,
+                              disable_ssl_certificate_validation=True)
+            resp, resp_str = h.request(action, method,
+                                       body=body_data,
+                                       headers=headers)
+
+            LOG.debug(_("vArmourRestAPI Response: %(status)s %(resp_str)s"),
+                      {'status': resp.status, 'resp_str': resp_str})
+
+            if resp.status == 200:
+                return {'status': resp.status,
+                        'reason': resp.reason,
+                        'body': json.loads(resp_str)}
+        except Exception:
+            LOG.error(_('vArmourRestAPI: Could not establish HTTP connection'))
+
+    def del_cfg_objs(self, url, prefix):
+        resp = self.rest_api('GET', url)
+        if resp and resp['status'] == 200:
+            olist = resp['body']['response']
+            if not olist:
+                return
+
+            for o in olist:
+                if o.startswith(prefix):
+                    self.rest_api('DELETE', url + '/"name:%s"' % o)
+            self.commit()
+
+    def count_cfg_objs(self, url, prefix):
+        count = 0
+        resp = self.rest_api('GET', url)
+        if resp and resp['status'] == 200:
+            for o in resp['body']['response']:
+                if o.startswith(prefix):
+                    count += 1
+
+        return count
diff --git a/neutron/services/firewall/agents/varmour/varmour_router.py b/neutron/services/firewall/agents/varmour/varmour_router.py
new file mode 100755 (executable)
index 0000000..0916533
--- /dev/null
@@ -0,0 +1,347 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 vArmour 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: Gary Duan, vArmour Networks Inc.
+#
+
+import eventlet
+import netaddr
+from oslo.config import cfg
+
+from neutron.agent.common import config
+from neutron.agent import l3_agent
+from neutron.agent.linux import external_process
+from neutron.agent.linux import interface
+from neutron.agent.linux import ip_lib
+from neutron.common import constants as l3_constants
+from neutron.common import legacy
+from neutron.common import topics
+from neutron.openstack.common import log as logging
+from neutron.openstack.common import service
+from neutron import service as neutron_service
+from neutron.services.firewall.agents.l3reference import firewall_l3_agent
+from neutron.services.firewall.agents.varmour import varmour_api
+from neutron.services.firewall.agents.varmour import varmour_utils as va_utils
+
+
+LOG = logging.getLogger(__name__)
+
+
+class vArmourL3NATAgent(l3_agent.L3NATAgent,
+                        firewall_l3_agent.FWaaSL3AgentRpcCallback):
+    def __init__(self, host, conf=None):
+        LOG.debug(_('vArmourL3NATAgent: __init__'))
+        self.rest = varmour_api.vArmourRestAPI()
+        super(vArmourL3NATAgent, self).__init__(host, conf)
+
+    def _destroy_router_namespaces(self, only_router_id=None):
+        return
+
+    def _destroy_router_namespace(self, namespace):
+        return
+
+    def _create_router_namespace(self, ri):
+        return
+
+    def _router_added(self, router_id, router):
+        LOG.debug(_("_router_added: %s"), router_id)
+        ri = l3_agent.RouterInfo(router_id, self.root_helper,
+                                 self.conf.use_namespaces, router)
+        self.router_info[router_id] = ri
+        super(vArmourL3NATAgent, self).process_router_add(ri)
+
+    def _router_removed(self, router_id):
+        LOG.debug(_("_router_removed: %s"), router_id)
+
+        ri = self.router_info[router_id]
+        if ri:
+            ri.router['gw_port'] = None
+            ri.router[l3_constants.INTERFACE_KEY] = []
+            ri.router[l3_constants.FLOATINGIP_KEY] = []
+            self.process_router(ri)
+
+            name = va_utils.get_snat_rule_name(ri)
+            self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, name)
+
+            name = va_utils.get_dnat_rule_name(ri)
+            self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, name)
+
+            name = va_utils.get_trusted_zone_name(ri)
+            self._va_unset_zone_interfaces(name, True)
+
+            name = va_utils.get_untrusted_zone_name(ri)
+            self._va_unset_zone_interfaces(name, True)
+
+            del self.router_info[router_id]
+
+    def _spawn_metadata_proxy(self, router_info):
+        return
+
+    def _destroy_metadata_proxy(self, router_info):
+        return
+
+    def _set_subnet_info(self, port):
+        ips = port['fixed_ips']
+        if not ips:
+            raise Exception(_("Router port %s has no IP address") % port['id'])
+            return
+        if len(ips) > 1:
+            LOG.warn(_("Ignoring multiple IPs on router port %s"), port['id'])
+        prefixlen = netaddr.IPNetwork(port['subnet']['cidr']).prefixlen
+        port['ip_cidr'] = "%s/%s" % (ips[0]['ip_address'], prefixlen)
+
+    def _va_unset_zone_interfaces(self, zone_name, remove_zone=False):
+        # return True if zone exists; otherwise, return False
+        LOG.debug(_("_va_unset_zone_interfaces: %s"), zone_name)
+        resp = self.rest.rest_api('GET', va_utils.REST_URL_CONF_ZONE)
+        if resp and resp['status'] == 200:
+            zlist = resp['body']['response']
+            for zn in zlist:
+                if zn == zone_name:
+                    commit = False
+
+                    if 'interface' in zlist[zn]:
+                        for intf in zlist[zn]['interface']:
+                            self.rest.rest_api('DELETE',
+                                               va_utils.REST_URL_CONF +
+                                               va_utils.REST_ZONE_NAME % zn +
+                                               va_utils.REST_INTF_NAME % intf)
+                            commit = True
+                    if remove_zone:
+                        self.rest.rest_api('DELETE',
+                                           va_utils.REST_URL_CONF +
+                                           va_utils.REST_ZONE_NAME % zn)
+                        commit = True
+
+                    if commit:
+                        self.rest.commit()
+
+                    return True
+
+        return False
+
+    def _va_pif_2_lif(self, pif):
+        return pif + '.0'
+
+    def _va_set_interface_ip(self, pif, cidr):
+        LOG.debug(_("_va_set_interface_ip: %(pif)s %(cidr)s"),
+                  {'pif': pif, 'cidr': cidr})
+
+        lif = self._va_pif_2_lif(pif)
+        obj = va_utils.REST_INTF_NAME % pif + va_utils.REST_LOGIC_NAME % lif
+        body = {
+            'name': lif,
+            'family': 'ipv4',
+            'address': cidr
+        }
+        self.rest.rest_api('PUT', va_utils.REST_URL_CONF + obj, body)
+
+    def _va_get_port_name(self, port_list, name):
+        if name:
+            for p in port_list:
+                if p['VM name'] == name:
+                    return p['name']
+
+    def _va_config_trusted_zone(self, ri, plist):
+        zone = va_utils.get_trusted_zone_name(ri)
+        LOG.debug(_("_va_config_trusted_zone: %s"), zone)
+
+        body = {
+            'name': zone,
+            'type': 'L3',
+            'interface': []
+        }
+
+        if not self._va_unset_zone_interfaces(zone):
+            # if zone doesn't exist, create it
+            self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body)
+            self.rest.commit()
+
+        # add new internal ports to trusted zone
+        for p in ri.internal_ports:
+            if p['admin_state_up']:
+                dev = self.get_internal_device_name(p['id'])
+                pif = self._va_get_port_name(plist, dev)
+                if pif:
+                    lif = self._va_pif_2_lif(pif)
+                    if lif not in body['interface']:
+                        body['interface'].append(lif)
+
+                        self._va_set_interface_ip(pif, p['ip_cidr'])
+
+        if body['interface']:
+            self.rest.rest_api('PUT', va_utils.REST_URL_CONF_ZONE, body)
+            self.rest.commit()
+
+    def _va_config_untrusted_zone(self, ri, plist):
+        zone = va_utils.get_untrusted_zone_name(ri)
+        LOG.debug(_("_va_config_untrusted_zone: %s"), zone)
+
+        body = {
+            'name': zone,
+            'type': 'L3',
+            'interface': []
+        }
+
+        if not self._va_unset_zone_interfaces(zone):
+            # if zone doesn't exist, create it
+            self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body)
+            self.rest.commit()
+
+        # add new gateway ports to untrusted zone
+        if ri.ex_gw_port:
+            LOG.debug(_("_va_config_untrusted_zone: gw=%r"), ri.ex_gw_port)
+            dev = self.get_external_device_name(ri.ex_gw_port['id'])
+            pif = self._va_get_port_name(plist, dev)
+            if pif:
+                lif = self._va_pif_2_lif(pif)
+
+                self._va_set_interface_ip(pif, ri.ex_gw_port['ip_cidr'])
+
+                body['interface'].append(lif)
+                self.rest.rest_api('PUT', va_utils.REST_URL_CONF_ZONE, body)
+                self.rest.commit()
+
+    def _va_config_router_snat_rules(self, ri, plist):
+        LOG.debug(_('_va_config_router_snat_rules: %s'), ri.router['id'])
+
+        prefix = va_utils.get_snat_rule_name(ri)
+        self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, prefix)
+
+        if not ri.enable_snat:
+            return
+
+        for idx, p in enumerate(ri.internal_ports):
+            if p['admin_state_up']:
+                dev = self.get_internal_device_name(p['id'])
+                pif = self._va_get_port_name(plist, dev)
+                if pif:
+                    net = netaddr.IPNetwork(p['ip_cidr'])
+                    body = {
+                        'name': '%s_%d' % (prefix, idx),
+                        'ingress-context-type': 'interface',
+                        'ingress-index': self._va_pif_2_lif(pif),
+                        'source-address': [
+                            [str(netaddr.IPAddress(net.first + 2)),
+                             str(netaddr.IPAddress(net.last - 1))]
+                        ],
+                        'flag': 'interface translate-source'
+                    }
+                    self.rest.rest_api('POST',
+                                       va_utils.REST_URL_CONF_NAT_RULE,
+                                       body)
+
+        if ri.internal_ports:
+            self.rest.commit()
+
+    def _va_config_floating_ips(self, ri):
+        LOG.debug(_('_va_config_floating_ips: %s'), ri.router['id'])
+
+        prefix = va_utils.get_dnat_rule_name(ri)
+        self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, prefix)
+
+        # add new dnat rules
+        for idx, fip in enumerate(ri.floating_ips):
+            body = {
+                'name': '%s_%d' % (prefix, idx),
+                'ingress-context-type': 'zone',
+                'ingress-index': va_utils.get_untrusted_zone_name(ri),
+                'destination-address': [[fip['floating_ip_address'],
+                                         fip['floating_ip_address']]],
+                'static': [fip['fixed_ip_address'], fip['fixed_ip_address']],
+                'flag': 'translate-destination'
+            }
+            self.rest.rest_api('POST', va_utils.REST_URL_CONF_NAT_RULE, body)
+
+        if ri.floating_ips:
+            self.rest.commit()
+
+    def process_router(self, ri):
+        LOG.debug(_("process_router: %s"), ri.router['id'])
+        super(vArmourL3NATAgent, self).process_router(ri)
+
+        self.rest.auth()
+
+        # read internal port name and configuration port name map
+        resp = self.rest.rest_api('GET', va_utils.REST_URL_INTF_MAP)
+        if resp and resp['status'] == 200:
+            try:
+                plist = resp['body']['response']
+            except ValueError:
+                LOG.warn(_("unable to parse interface mapping."))
+                return
+        else:
+            LOG.warn(_("unable to read interface mapping."))
+            return
+
+        if ri.ex_gw_port:
+            self._set_subnet_info(ri.ex_gw_port)
+        self._va_config_trusted_zone(ri, plist)
+        self._va_config_untrusted_zone(ri, plist)
+        self._va_config_router_snat_rules(ri, plist)
+        self._va_config_floating_ips(ri)
+
+    def _handle_router_snat_rules(self, ri, ex_gw_port, internal_cidrs,
+                                  interface_name, action):
+        return
+
+    def _send_gratuitous_arp_packet(self, ri, interface_name, ip_address):
+        return
+
+    def external_gateway_added(self, ri, ex_gw_port,
+                               interface_name, internal_cidrs):
+        LOG.debug(_("external_gateway_added: %s"), ri.router['id'])
+
+        if not ip_lib.device_exists(interface_name,
+                                    root_helper=self.root_helper,
+                                    namespace=ri.ns_name()):
+            self.driver.plug(ex_gw_port['network_id'],
+                             ex_gw_port['id'], interface_name,
+                             ex_gw_port['mac_address'],
+                             bridge=self.conf.external_network_bridge,
+                             namespace=ri.ns_name(),
+                             prefix=l3_agent.EXTERNAL_DEV_PREFIX)
+        self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
+                            namespace=ri.ns_name())
+
+    def _update_routing_table(self, ri, operation, route):
+        return
+
+
+class vArmourL3NATAgentWithStateReport(vArmourL3NATAgent,
+                                       l3_agent.L3NATAgentWithStateReport):
+    pass
+
+
+def main():
+    eventlet.monkey_patch()
+    conf = cfg.CONF
+    conf.register_opts(vArmourL3NATAgent.OPTS)
+    config.register_agent_state_opts_helper(conf)
+    config.register_root_helper(conf)
+    conf.register_opts(interface.OPTS)
+    conf.register_opts(external_process.OPTS)
+    conf(project='neutron')
+    config.setup_logging(conf)
+    legacy.modernize_quantum_config(conf)
+    server = neutron_service.Service.create(
+        binary='neutron-l3-agent',
+        topic=topics.L3_AGENT,
+        report_interval=cfg.CONF.AGENT.report_interval,
+        manager='neutron.services.firewall.agents.varmour.varmour_router.'
+                'vArmourL3NATAgentWithStateReport')
+    service.launch(server).wait()
diff --git a/neutron/services/firewall/agents/varmour/varmour_utils.py b/neutron/services/firewall/agents/varmour/varmour_utils.py
new file mode 100755 (executable)
index 0000000..7290cb6
--- /dev/null
@@ -0,0 +1,74 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 vArmour 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: Gary Duan, gduan@varmour.com, vArmour Networks
+
+ROUTER_OBJ_PREFIX = 'r-'
+OBJ_PREFIX_LEN = 8
+TRUST_ZONE = '_z_trust'
+UNTRUST_ZONE = '_z_untrust'
+SNAT_RULE = '_snat'
+DNAT_RULE = '_dnat'
+ROUTER_POLICY = '_p'
+
+REST_URL_CONF = '/config'
+REST_URL_AUTH = '/auth'
+REST_URL_COMMIT = '/commit'
+REST_URL_INTF_MAP = '/operation/interface/mapping'
+
+REST_URL_CONF_NAT_RULE = REST_URL_CONF + '/nat/rule'
+REST_URL_CONF_ZONE = REST_URL_CONF + '/zone'
+REST_URL_CONF_POLICY = REST_URL_CONF + '/policy'
+REST_URL_CONF_ADDR = REST_URL_CONF + '/address'
+REST_URL_CONF_SERVICE = REST_URL_CONF + '/service'
+
+REST_ZONE_NAME = '/zone/"name:%s"'
+REST_INTF_NAME = '/interface/"name:%s"'
+REST_LOGIC_NAME = '/logical/"name:%s"'
+REST_SERVICE_NAME = '/service/"name:%s"/rule'
+
+
+def get_router_object_prefix(ri):
+    return ROUTER_OBJ_PREFIX + ri.router['id'][:OBJ_PREFIX_LEN]
+
+
+def get_firewall_object_prefix(ri, fw):
+    return get_router_object_prefix(ri) + '-' + fw['id'][:OBJ_PREFIX_LEN]
+
+
+def get_trusted_zone_name(ri):
+    return get_router_object_prefix(ri) + TRUST_ZONE
+
+
+def get_untrusted_zone_name(ri):
+    return get_router_object_prefix(ri) + UNTRUST_ZONE
+
+
+def get_snat_rule_name(ri):
+    return get_router_object_prefix(ri) + SNAT_RULE
+
+
+def get_dnat_rule_name(ri):
+    return get_router_object_prefix(ri) + DNAT_RULE
+
+
+def get_router_policy_name(ri):
+    return get_router_object_prefix(ri) + ROUTER_POLICY
+
+
+def get_firewall_policy_name(ri, fw, rule):
+    return get_firewall_object_prefix(ri, fw) + rule['id'][:OBJ_PREFIX_LEN]
diff --git a/neutron/services/firewall/drivers/varmour/__init__.py b/neutron/services/firewall/drivers/varmour/__init__.py
new file mode 100755 (executable)
index 0000000..5e8da71
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
diff --git a/neutron/services/firewall/drivers/varmour/varmour_fwaas.py b/neutron/services/firewall/drivers/varmour/varmour_fwaas.py
new file mode 100755 (executable)
index 0000000..bcdf2f9
--- /dev/null
@@ -0,0 +1,207 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 vArmour 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: Gary Duan, gduan@varmour.com, vArmour Networks
+
+from neutron.openstack.common import log as logging
+from neutron.services.firewall.agents.varmour import varmour_api
+from neutron.services.firewall.agents.varmour import varmour_utils as va_utils
+from neutron.services.firewall.drivers import fwaas_base
+
+LOG = logging.getLogger(__name__)
+
+
+class vArmourFwaasDriver(fwaas_base.FwaasDriverBase):
+    def __init__(self):
+        LOG.debug(_("Initializing fwaas vArmour driver"))
+
+        self.rest = varmour_api.vArmourRestAPI()
+
+    def create_firewall(self, apply_list, firewall):
+        LOG.debug(_('create_firewall (%s)'), firewall['id'])
+
+        return self.update_firewall(apply_list, firewall)
+
+    def update_firewall(self, apply_list, firewall):
+        LOG.debug(_("update_firewall (%s)"), firewall['id'])
+
+        if firewall['admin_state_up']:
+            return self._update_firewall(apply_list, firewall)
+        else:
+            return self.apply_default_policy(apply_list, firewall)
+
+    def delete_firewall(self, apply_list, firewall):
+        LOG.debug(_("delete_firewall (%s)"), firewall['id'])
+
+        return self.apply_default_policy(apply_list, firewall)
+
+    def apply_default_policy(self, apply_list, firewall):
+        LOG.debug(_("apply_default_policy (%s)"), firewall['id'])
+
+        self.rest.auth()
+
+        for ri in apply_list:
+            self._clear_policy(ri, firewall)
+
+        return True
+
+    def _update_firewall(self, apply_list, firewall):
+        LOG.debug(_("Updating firewall (%s)"), firewall['id'])
+
+        self.rest.auth()
+
+        for ri in apply_list:
+            self._clear_policy(ri, firewall)
+            self._setup_policy(ri, firewall)
+
+        return True
+
+    def _setup_policy(self, ri, fw):
+        # create zones no matter if they exist. Interfaces are added by router
+        body = {
+            'type': 'L3',
+            'interface': []
+        }
+
+        body['name'] = va_utils.get_trusted_zone_name(ri)
+        self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body)
+        body['name'] = va_utils.get_untrusted_zone_name(ri)
+        self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body)
+        self.rest.commit()
+
+        servs = dict()
+        addrs = dict()
+        for rule in fw['firewall_rule_list']:
+            if not rule['enabled']:
+                continue
+
+            if rule['ip_version'] == 4:
+                service = self._make_service(ri, fw, rule, servs)
+                s_addr = self._make_address(ri, fw, rule, addrs, True)
+                d_addr = self._make_address(ri, fw, rule, addrs, False)
+
+                policy = va_utils.get_firewall_policy_name(ri, fw, rule)
+                z0 = va_utils.get_trusted_zone_name(ri)
+                z1 = va_utils.get_untrusted_zone_name(ri)
+                body = self._make_policy(policy + '_0', rule,
+                                         z0, z0, s_addr, d_addr, service)
+                self.rest.rest_api('POST', va_utils.REST_URL_CONF_POLICY, body)
+                body = self._make_policy(policy + '_1', rule,
+                                         z0, z1, s_addr, d_addr, service)
+                self.rest.rest_api('POST', va_utils.REST_URL_CONF_POLICY, body)
+                body = self._make_policy(policy + '_2', rule,
+                                         z1, z0, s_addr, d_addr, service)
+                self.rest.rest_api('POST', va_utils.REST_URL_CONF_POLICY, body)
+
+                self.rest.commit()
+            else:
+                LOG.warn(_("Unsupported IP version rule."))
+
+    def _clear_policy(self, ri, fw):
+        prefix = va_utils.get_firewall_object_prefix(ri, fw)
+        self.rest.del_cfg_objs(va_utils.REST_URL_CONF_POLICY, prefix)
+        self.rest.del_cfg_objs(va_utils.REST_URL_CONF_ADDR, prefix)
+        self.rest.del_cfg_objs(va_utils.REST_URL_CONF_SERVICE, prefix)
+
+    def _make_service(self, ri, fw, rule, servs):
+        prefix = va_utils.get_firewall_object_prefix(ri, fw)
+
+        if rule.get('protocol'):
+            key = rule.get('protocol')
+            if rule.get('source_port'):
+                key += '-' + rule.get('source_port')
+            if rule.get('destination_port'):
+                key += '-' + rule.get('destination_port')
+        else:
+            return
+
+        if key in servs:
+            name = '%s_%d' % (prefix, servs[key])
+        else:
+            # create new service object with index
+            idx = len(servs)
+            servs[key] = idx
+            name = '%s_%d' % (prefix, idx)
+
+            body = {'name': name}
+            self.rest.rest_api('POST',
+                               va_utils.REST_URL_CONF_SERVICE,
+                               body)
+            body = self._make_service_rule(rule)
+            self.rest.rest_api('POST',
+                               va_utils.REST_URL_CONF +
+                               va_utils.REST_SERVICE_NAME % name,
+                               body)
+            self.rest.commit()
+
+        return name
+
+    def _make_service_rule(self, rule):
+        body = {
+            'name': '1',
+            'protocol': rule.get('protocol')
+        }
+        if 'source_port' in rule:
+            body['source-start'] = rule['source_port']
+            body['source-end'] = rule['source_port']
+        if 'destination_port' in rule:
+            body['dest-start'] = rule['destination_port']
+            body['dest-end'] = rule['destination_port']
+
+        return body
+
+    def _make_address(self, ri, fw, rule, addrs, is_src):
+        prefix = va_utils.get_firewall_object_prefix(ri, fw)
+
+        if is_src:
+            key = rule.get('source_ip_address')
+        else:
+            key = rule.get('destination_ip_address')
+
+        if not key:
+            return
+
+        if key in addrs:
+            name = '%s_%d' % (prefix, addrs[key])
+        else:
+            # create new address object with idx
+            idx = len(addrs)
+            addrs[key] = idx
+            name = '%s_%d' % (prefix, idx)
+
+            body = {
+                'name': name,
+                'type': 'ipv4',
+                'ipv4': key
+            }
+            self.rest.rest_api('POST', va_utils.REST_URL_CONF_ADDR, body)
+            self.rest.commit()
+
+        return name
+
+    def _make_policy(self, name, rule, zone0, zone1, s_addr, d_addr, service):
+        body = {
+            'name': name,
+            'action': 'permit' if rule.get('action') == 'allow' else 'deny',
+            'from': zone0,
+            'to': zone1,
+            'match-source-address': [s_addr or 'Any'],
+            'match-dest-address': [d_addr or 'Any'],
+            'match-service': [service or 'Any']
+        }
+
+        return body
diff --git a/neutron/tests/unit/services/firewall/agents/varmour/__init__.py b/neutron/tests/unit/services/firewall/agents/varmour/__init__.py
new file mode 100755 (executable)
index 0000000..5e8da71
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
diff --git a/neutron/tests/unit/services/firewall/agents/varmour/test_varmour_router.py b/neutron/tests/unit/services/firewall/agents/varmour/test_varmour_router.py
new file mode 100644 (file)
index 0000000..410821e
--- /dev/null
@@ -0,0 +1,322 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 vArmour 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: Gary Duan, vArmour Networks Inc.
+#
+
+
+import mock
+from oslo.config import cfg
+
+from neutron.agent.common import config as agent_config
+from neutron.agent import l3_agent
+from neutron.agent.linux import interface
+from neutron.common import config as base_config
+from neutron.common import constants as l3_constants
+from neutron.openstack.common import uuidutils
+from neutron.services.firewall.agents.varmour import varmour_router
+from neutron.services.firewall.agents.varmour import varmour_utils
+from neutron.tests import base
+
+_uuid = uuidutils.generate_uuid
+HOSTNAME = 'myhost'
+FAKE_DIRECTOR = '1.1.1.1'
+
+
+class TestVarmourRouter(base.BaseTestCase):
+
+    def setUp(self):
+        super(TestVarmourRouter, self).setUp()
+        self.conf = cfg.ConfigOpts()
+        self.conf.register_opts(base_config.core_opts)
+        self.conf.register_opts(varmour_router.vArmourL3NATAgent.OPTS)
+        agent_config.register_root_helper(self.conf)
+        self.conf.register_opts(interface.OPTS)
+        self.conf.set_override('interface_driver',
+                               'neutron.agent.linux.interface.NullDriver')
+        self.conf.root_helper = 'sudo'
+
+        self.device_exists_p = mock.patch(
+            'neutron.agent.linux.ip_lib.device_exists')
+        self.device_exists = self.device_exists_p.start()
+
+        self.utils_exec_p = mock.patch(
+            'neutron.agent.linux.utils.execute')
+        self.utils_exec = self.utils_exec_p.start()
+
+        self.external_process_p = mock.patch(
+            'neutron.agent.linux.external_process.ProcessManager')
+        self.external_process = self.external_process_p.start()
+
+        self.dvr_cls_p = mock.patch('neutron.agent.linux.interface.NullDriver')
+        driver_cls = self.dvr_cls_p.start()
+        self.mock_driver = mock.MagicMock()
+        self.mock_driver.DEV_NAME_LEN = (
+            interface.LinuxInterfaceDriver.DEV_NAME_LEN)
+        driver_cls.return_value = self.mock_driver
+
+        self.ip_cls_p = mock.patch('neutron.agent.linux.ip_lib.IPWrapper')
+        ip_cls = self.ip_cls_p.start()
+        self.mock_ip = mock.MagicMock()
+        ip_cls.return_value = self.mock_ip
+
+        self.looping_call_p = mock.patch(
+            'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall')
+        self.looping_call_p.start()
+
+        self.addCleanup(mock.patch.stopall)
+
+    def _create_router(self):
+        router = varmour_router.vArmourL3NATAgent(HOSTNAME, self.conf)
+        router.rest.server = FAKE_DIRECTOR
+        router.rest.user = 'varmour'
+        router.rest.passwd = 'varmour'
+        return router
+
+    def _del_all_internal_ports(self, router):
+        router[l3_constants.INTERFACE_KEY] = []
+
+    def _del_internal_ports(self, router, port_idx):
+        del router[l3_constants.INTERFACE_KEY][port_idx]
+
+    def _add_internal_ports(self, router, port_count=1):
+        self._del_all_internal_ports(router)
+        for i in range(0, port_count):
+            port = {'id': _uuid(),
+                    'network_id': _uuid(),
+                    'admin_state_up': True,
+                    'fixed_ips': [{'ip_address': '10.0.%s.4' % i,
+                                   'subnet_id': _uuid()}],
+                    'mac_address': 'ca:fe:de:ad:be:ef',
+                    'subnet': {'cidr': '10.0.%s.0/24' % i,
+                               'gateway_ip': '10.0.%s.1' % i}}
+            router[l3_constants.INTERFACE_KEY].append(port)
+
+    def _del_all_floating_ips(self, router):
+        router[l3_constants.FLOATINGIP_KEY] = []
+
+    def _del_floating_ips(self, router, port_idx):
+        del router[l3_constants.FLOATINGIP_KEY][port_idx]
+
+    def _add_floating_ips(self, router, port_count=1):
+        self._del_all_floating_ips(router)
+        for i in range(0, port_count):
+            fip = {'id': _uuid(),
+                   'port_id': router['gw_port']['id'],
+                   'floating_ip_address': '172.24.4.%s' % (100 + i),
+                   'fixed_ip_address': '10.0.0.%s' % (100 + i)}
+            router[l3_constants.FLOATINGIP_KEY].append(fip)
+
+    def _prepare_router_data(self, enable_snat=None):
+        router_id = _uuid()
+        ex_gw_port = {'id': _uuid(),
+                      'network_id': _uuid(),
+                      'fixed_ips': [{'ip_address': '172.24.4.2',
+                                     'subnet_id': _uuid()}],
+                      'subnet': {'cidr': '172.24.4.0/24',
+                                 'gateway_ip': '172.24.4.1'},
+                      'ip_cidr': '172.24.4.226/28'}
+        int_ports = []
+
+        router = {
+            'id': router_id,
+            l3_constants.INTERFACE_KEY: int_ports,
+            'routes': [],
+            'gw_port': ex_gw_port}
+        if enable_snat is not None:
+            router['enable_snat'] = enable_snat
+
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+        return ri
+
+    def test_agent_add_internal_network(self):
+        router = self._create_router()
+        try:
+            router.rest.auth()
+        except Exception:
+            # skip the test, firewall is not deployed
+            return
+
+        ri = self._prepare_router_data(enable_snat=True)
+        router._router_added(ri.router['id'], ri.router)
+
+        url = varmour_utils.REST_URL_CONF_NAT_RULE
+        prefix = varmour_utils.get_snat_rule_name(ri)
+
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
+
+        self._add_internal_ports(ri.router, port_count=1)
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 1, 'prefix %s' % prefix)
+
+        router._router_removed(ri.router['id'])
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
+
+    def test_agent_remove_internal_network(self):
+        router = self._create_router()
+        try:
+            router.rest.auth()
+        except Exception:
+            # skip the test, firewall is not deployed
+            return
+
+        ri = self._prepare_router_data(enable_snat=True)
+        router._router_added(ri.router['id'], ri.router)
+
+        url = varmour_utils.REST_URL_CONF_NAT_RULE
+        prefix = varmour_utils.get_snat_rule_name(ri)
+
+        self._add_internal_ports(ri.router, port_count=2)
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 2, 'prefix %s' % prefix)
+
+        self._del_internal_ports(ri.router, 0)
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 1, 'prefix %s' % prefix)
+
+        self._del_all_internal_ports(ri.router)
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
+
+        router._router_removed(ri.router['id'])
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
+
+    def test_agent_add_floating_ips(self):
+        router = self._create_router()
+        try:
+            router.rest.auth()
+        except Exception:
+            # skip the test, firewall is not deployed
+            return
+
+        ri = self._prepare_router_data(enable_snat=True)
+        self._add_internal_ports(ri.router, port_count=1)
+        router._router_added(ri.router['id'], ri.router)
+
+        url = varmour_utils.REST_URL_CONF_NAT_RULE
+        prefix = varmour_utils.get_dnat_rule_name(ri)
+
+        self._add_floating_ips(ri.router, port_count=1)
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 1, 'prefix %s' % prefix)
+
+        self._add_floating_ips(ri.router, port_count=2)
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 2, 'prefix %s' % prefix)
+
+        router._router_removed(ri.router['id'])
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
+
+    def test_agent_remove_floating_ips(self):
+        router = self._create_router()
+        try:
+            router.rest.auth()
+        except Exception:
+            # skip the test, firewall is not deployed
+            return
+
+        ri = self._prepare_router_data(enable_snat=True)
+        self._add_internal_ports(ri.router, port_count=1)
+        self._add_floating_ips(ri.router, port_count=2)
+        router._router_added(ri.router['id'], ri.router)
+
+        url = varmour_utils.REST_URL_CONF_NAT_RULE
+        prefix = varmour_utils.get_dnat_rule_name(ri)
+
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 2, 'prefix %s' % prefix)
+
+        self._del_floating_ips(ri.router, 0)
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 1, 'prefix %s' % prefix)
+
+        self._del_all_floating_ips(ri.router)
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
+
+        router._router_removed(ri.router['id'])
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
+
+    def test_agent_external_gateway(self):
+        router = self._create_router()
+        try:
+            router.rest.auth()
+        except Exception:
+            # skip the test, firewall is not deployed
+            return
+
+        ri = self._prepare_router_data(enable_snat=True)
+        router._router_added(ri.router['id'], ri.router)
+
+        url = varmour_utils.REST_URL_CONF_ZONE
+        prefix = varmour_utils.get_untrusted_zone_name(ri)
+
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 1, 'prefix %s' % prefix)
+
+        del ri.router['gw_port']
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 1, 'prefix %s' % prefix)
+
+        router._router_removed(ri.router['id'])
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
+
+    def test_agent_snat_enable(self):
+        router = self._create_router()
+        try:
+            router.rest.auth()
+        except Exception:
+            # skip the test, firewall is not deployed
+            return
+
+        ri = self._prepare_router_data(enable_snat=True)
+        router._router_added(ri.router['id'], ri.router)
+
+        url = varmour_utils.REST_URL_CONF_NAT_RULE
+        prefix = varmour_utils.get_snat_rule_name(ri)
+
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
+
+        ri.router['enable_snat'] = False
+        router.process_router(ri)
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
+
+        router._router_removed(ri.router['id'])
+        n = router.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0, 'prefix %s' % prefix)
diff --git a/neutron/tests/unit/services/firewall/drivers/varmour/__init__.py b/neutron/tests/unit/services/firewall/drivers/varmour/__init__.py
new file mode 100755 (executable)
index 0000000..5e8da71
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
diff --git a/neutron/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py b/neutron/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py
new file mode 100644 (file)
index 0000000..95674f1
--- /dev/null
@@ -0,0 +1,290 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 vArmour 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: Gary Duan, vArmour Networks Inc.
+#
+
+
+import mock
+from oslo.config import cfg
+
+from neutron.agent.common import config as agent_config
+from neutron.agent import l3_agent
+from neutron.agent.linux import interface
+from neutron.common import config as base_config
+from neutron.common import constants as l3_constants
+from neutron.openstack.common import uuidutils
+from neutron.services.firewall.agents.varmour import varmour_router
+from neutron.services.firewall.agents.varmour import varmour_utils
+from neutron.services.firewall.drivers.varmour import varmour_fwaas
+from neutron.tests import base
+
+_uuid = uuidutils.generate_uuid
+HOSTNAME = 'myhost'
+FAKE_DIRECTOR = '1.1.1.1'
+
+
+class TestBasicRouterOperations(base.BaseTestCase):
+
+    def setUp(self):
+        super(TestBasicRouterOperations, self).setUp()
+        self.conf = cfg.ConfigOpts()
+        self.conf.register_opts(base_config.core_opts)
+        self.conf.register_opts(varmour_router.vArmourL3NATAgent.OPTS)
+        agent_config.register_root_helper(self.conf)
+        self.conf.register_opts(interface.OPTS)
+        self.conf.set_override('interface_driver',
+                               'neutron.agent.linux.interface.NullDriver')
+        self.conf.root_helper = 'sudo'
+
+        self.device_exists_p = mock.patch(
+            'neutron.agent.linux.ip_lib.device_exists')
+        self.device_exists = self.device_exists_p.start()
+
+        self.utils_exec_p = mock.patch(
+            'neutron.agent.linux.utils.execute')
+        self.utils_exec = self.utils_exec_p.start()
+
+        self.external_process_p = mock.patch(
+            'neutron.agent.linux.external_process.ProcessManager')
+        self.external_process = self.external_process_p.start()
+
+        self.dvr_cls_p = mock.patch('neutron.agent.linux.interface.NullDriver')
+        driver_cls = self.dvr_cls_p.start()
+        self.mock_driver = mock.MagicMock()
+        self.mock_driver.DEV_NAME_LEN = (
+            interface.LinuxInterfaceDriver.DEV_NAME_LEN)
+        driver_cls.return_value = self.mock_driver
+
+        self.ip_cls_p = mock.patch('neutron.agent.linux.ip_lib.IPWrapper')
+        ip_cls = self.ip_cls_p.start()
+        self.mock_ip = mock.MagicMock()
+        ip_cls.return_value = self.mock_ip
+
+        self.looping_call_p = mock.patch(
+            'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall')
+        self.looping_call_p.start()
+
+        self.addCleanup(mock.patch.stopall)
+
+    def _create_router(self):
+        router = varmour_router.vArmourL3NATAgent(HOSTNAME, self.conf)
+        router.rest.server = FAKE_DIRECTOR
+        router.rest.user = 'varmour'
+        router.rest.passwd = 'varmour'
+        return router
+
+    def _create_fwaas(self):
+        fwaas = varmour_fwaas.vArmourFwaasDriver()
+        fwaas.rest.server = FAKE_DIRECTOR
+        fwaas.rest.user = 'varmour'
+        fwaas.rest.passwd = 'varmour'
+        return fwaas
+
+    def _del_all_internal_ports(self, router):
+        router[l3_constants.INTERFACE_KEY] = []
+
+    def _del_internal_ports(self, router, port_idx):
+        del router[l3_constants.INTERFACE_KEY][port_idx]
+
+    def _add_internal_ports(self, router, port_count=1):
+        self._del_all_internal_ports(router)
+        for i in range(0, port_count):
+            port = {'id': _uuid(),
+                    'network_id': _uuid(),
+                    'admin_state_up': True,
+                    'fixed_ips': [{'ip_address': '10.0.%s.4' % i,
+                                   'subnet_id': _uuid()}],
+                    'mac_address': 'ca:fe:de:ad:be:ef',
+                    'subnet': {'cidr': '10.0.%s.0/24' % i,
+                               'gateway_ip': '10.0.%s.1' % i}}
+            router[l3_constants.INTERFACE_KEY].append(port)
+
+    def _del_all_floating_ips(self, router):
+        router[l3_constants.FLOATINGIP_KEY] = []
+
+    def _del_floating_ips(self, router, port_idx):
+        del router[l3_constants.FLOATINGIP_KEY][port_idx]
+
+    def _add_floating_ips(self, router, port_count=1):
+        self._del_all_floating_ips(router)
+        for i in range(0, port_count):
+            fip = {'id': _uuid(),
+                   'port_id': router['gw_port']['id'],
+                   'floating_ip_address': '172.24.4.%s' % (100 + i),
+                   'fixed_ip_address': '10.0.0.%s' % (100 + i)}
+            router[l3_constants.FLOATINGIP_KEY].append(fip)
+
+    def _prepare_router_data(self, enable_snat=None):
+        router_id = _uuid()
+        ex_gw_port = {'id': _uuid(),
+                      'network_id': _uuid(),
+                      'fixed_ips': [{'ip_address': '172.24.4.2',
+                                     'subnet_id': _uuid()}],
+                      'subnet': {'cidr': '172.24.4.0/24',
+                                 'gateway_ip': '172.24.4.1'},
+                      'ip_cidr': '172.24.4.226/28'}
+        int_ports = []
+
+        router = {
+            'id': router_id,
+            l3_constants.INTERFACE_KEY: int_ports,
+            'routes': [],
+            'gw_port': ex_gw_port}
+        if enable_snat is not None:
+            router['enable_snat'] = enable_snat
+
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+        return ri
+
+    def _add_firewall_rules(self, fw, rule_count=1):
+        rules = []
+        for i in range(0, rule_count):
+            rule = {'id': _uuid(),
+                    'enabled': True,
+                    'action': 'deny' if (i % 2 == 0) else 'allow',
+                    'ip_version': 4,
+                    'protocol': 'tcp',
+                    'source_ip_address': '10.0.0.%s/24' % (100 + i),
+                    'destination_port': '%s' % (100 + i)}
+            rules.append(rule)
+        fw['firewall_rule_list'] = rules
+
+    def _prepare_firewall_data(self):
+        fw = {'id': _uuid(),
+              'admin_state_up': True,
+              'firewall_rule_list': []}
+        return fw
+
+    def test_firewall_without_rule(self):
+        router = self._create_router()
+        fwaas = self._create_fwaas()
+        try:
+            router.rest.auth()
+        except Exception:
+            # skip the test, firewall is not deployed
+            return
+
+        ri = self._prepare_router_data(enable_snat=True)
+        self._add_internal_ports(ri.router, port_count=1)
+        self._add_floating_ips(ri.router, port_count=1)
+        router._router_added(ri.router['id'], ri.router)
+
+        rl = [ri]
+
+        fw = self._prepare_firewall_data()
+        fwaas.create_firewall(rl, fw)
+
+        url = varmour_utils.REST_URL_CONF_POLICY
+        prefix = varmour_utils.get_firewall_object_prefix(ri, fw)
+
+        n = fwaas.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0)
+
+        fwaas.delete_firewall(rl, fw)
+        n = fwaas.rest.count_cfg_objs(url, prefix)
+        self.assertEqual(n, 0)
+
+        router._router_removed(ri.router['id'])
+
+    def test_firewall_with_rules(self):
+        router = self._create_router()
+        fwaas = self._create_fwaas()
+        try:
+            router.rest.auth()
+        except Exception:
+            # skip the test, firewall is not deployed
+            return
+
+        ri = self._prepare_router_data(enable_snat=True)
+        self._add_internal_ports(ri.router, port_count=1)
+        self._add_floating_ips(ri.router, port_count=1)
+        router._router_added(ri.router['id'], ri.router)
+
+        rl = [ri]
+
+        fw = self._prepare_firewall_data()
+        self._add_firewall_rules(fw, 2)
+        fwaas.create_firewall(rl, fw)
+
+        prefix = varmour_utils.get_firewall_object_prefix(ri, fw)
+        pol_url = varmour_utils.REST_URL_CONF_POLICY
+        serv_url = varmour_utils.REST_URL_CONF_SERVICE
+        addr_url = varmour_utils.REST_URL_CONF_ADDR
+
+        # 3x number of policies
+        n = fwaas.rest.count_cfg_objs(pol_url, prefix)
+        self.assertEqual(n, 6)
+        n = fwaas.rest.count_cfg_objs(addr_url, prefix)
+        self.assertEqual(n, 2)
+        n = fwaas.rest.count_cfg_objs(serv_url, prefix)
+        self.assertEqual(n, 2)
+
+        fwaas.delete_firewall(rl, fw)
+        n = fwaas.rest.count_cfg_objs(pol_url, prefix)
+        self.assertEqual(n, 0)
+
+        router._router_removed(ri.router['id'])
+
+    def test_firewall_add_remove_rules(self):
+        router = self._create_router()
+        fwaas = self._create_fwaas()
+        try:
+            router.rest.auth()
+        except Exception:
+            # skip the test, firewall is not deployed
+            return
+
+        ri = self._prepare_router_data(enable_snat=True)
+        self._add_internal_ports(ri.router, port_count=1)
+        self._add_floating_ips(ri.router, port_count=1)
+        router._router_added(ri.router['id'], ri.router)
+
+        rl = [ri]
+
+        fw = self._prepare_firewall_data()
+        self._add_firewall_rules(fw, 2)
+        fwaas.create_firewall(rl, fw)
+
+        prefix = varmour_utils.get_firewall_object_prefix(ri, fw)
+        pol_url = varmour_utils.REST_URL_CONF_POLICY
+        serv_url = varmour_utils.REST_URL_CONF_SERVICE
+        addr_url = varmour_utils.REST_URL_CONF_ADDR
+
+        # 3x number of policies
+        n = fwaas.rest.count_cfg_objs(pol_url, prefix)
+        self.assertEqual(n, 6)
+        n = fwaas.rest.count_cfg_objs(addr_url, prefix)
+        self.assertEqual(n, 2)
+        n = fwaas.rest.count_cfg_objs(serv_url, prefix)
+        self.assertEqual(n, 2)
+
+        self._add_firewall_rules(fw, 1)
+        fwaas.create_firewall(rl, fw)
+        n = fwaas.rest.count_cfg_objs(pol_url, prefix)
+        self.assertEqual(n, 3)
+        n = fwaas.rest.count_cfg_objs(addr_url, prefix)
+        self.assertEqual(n, 1)
+        n = fwaas.rest.count_cfg_objs(serv_url, prefix)
+        self.assertEqual(n, 1)
+
+        fwaas.delete_firewall(rl, fw)
+        n = fwaas.rest.count_cfg_objs(pol_url, prefix)
+        self.assertEqual(n, 0)
+
+        router._router_removed(ri.router['id'])