26f855f9c9
Change-Id: I2047f083af01dd0452f61d9fb807098e0514ff36
281 lines
11 KiB
Python
281 lines
11 KiB
Python
# Copyright 2018 VMware, 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.
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
from neutron.agent.l3 import router_info
|
|
from neutron.common import config as neutron_config # noqa
|
|
from neutron_lib import constants as nl_constants
|
|
from neutron_lib import context as n_context
|
|
from neutron_lib.plugins import directory
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
try:
|
|
from neutron_fwaas.common import fwaas_constants
|
|
from neutron_fwaas.services.firewall.service_drivers.agents.l3reference \
|
|
import firewall_l3_agent_v2
|
|
except ImportError:
|
|
# FWaaS project no found
|
|
from vmware_nsx.services.fwaas.common import fwaas_mocks \
|
|
as firewall_l3_agent_v2
|
|
from vmware_nsx.services.fwaas.common import fwaas_mocks \
|
|
as fwaas_constants
|
|
|
|
|
|
class DummyAgentApi(object):
|
|
def is_router_in_namespace(self, router_id):
|
|
return True
|
|
|
|
|
|
class NsxFwaasCallbacksV2(firewall_l3_agent_v2.L3WithFWaaS):
|
|
"""Common NSX RPC callbacks for Firewall As A Service - V2."""
|
|
def __init__(self, with_rpc):
|
|
# The super code needs a configuration object with the neutron host
|
|
# and an agent_mode, which our driver doesn't use.
|
|
neutron_conf = cfg.CONF
|
|
neutron_conf.agent_mode = 'nsx'
|
|
self.with_rpc = with_rpc
|
|
super(NsxFwaasCallbacksV2, self).__init__(conf=neutron_conf)
|
|
self.agent_api = DummyAgentApi()
|
|
self.core_plugin = self._get_core_plugin()
|
|
|
|
def start_rpc_listeners(self, host, conf):
|
|
# Make sure RPC queue will be created only when needed
|
|
if not self.with_rpc:
|
|
return
|
|
return super(NsxFwaasCallbacksV2, self).start_rpc_listeners(host, conf)
|
|
|
|
@property
|
|
def plugin_type(self):
|
|
pass
|
|
|
|
def _get_core_plugin(self):
|
|
"""Get the NSX core plugin"""
|
|
core_plugin = directory.get_plugin()
|
|
if core_plugin.is_tvd_plugin():
|
|
# get the plugin that match this driver
|
|
core_plugin = core_plugin.get_plugin_by_type(
|
|
self.plugin_type)
|
|
return core_plugin
|
|
|
|
# Override functions using the agent_api that is not used by our plugin
|
|
def _get_firewall_group_ports(self, context, firewall_group,
|
|
to_delete=False, require_new_plugin=False):
|
|
"""Returns in-namespace ports, either from firewall group dict if newer
|
|
version of plugin or from project routers otherwise.
|
|
|
|
NOTE: Vernacular move from "tenant" to "project" doesn't yet appear
|
|
as a key in router or firewall group objects.
|
|
"""
|
|
fwg_port_ids = []
|
|
if self._has_port_insertion_fields(firewall_group):
|
|
if to_delete:
|
|
fwg_port_ids = firewall_group['del-port-ids']
|
|
else:
|
|
fwg_port_ids = firewall_group['add-port-ids']
|
|
if (not firewall_group.get('del-port-ids') and
|
|
not firewall_group.get('add-port-ids') and
|
|
firewall_group.get('ports')):
|
|
# No change in ports, but policy changed so all ports are
|
|
# relevant
|
|
fwg_port_ids = firewall_group['ports']
|
|
# Mark to the driver that this is not port deletion
|
|
firewall_group['last-port'] = False
|
|
elif not require_new_plugin:
|
|
routers = self._get_routers_in_project(
|
|
context, firewall_group['tenant_id'])
|
|
for router in routers:
|
|
if router.router['tenant_id'] == firewall_group['tenant_id']:
|
|
fwg_port_ids.extend([p['id'] for p in
|
|
router.internal_ports])
|
|
|
|
# Return in-namespace port objects.
|
|
ports = self._get_in_ns_ports(fwg_port_ids, ignore_errors=to_delete)
|
|
# On illegal ports - change FW status to Error
|
|
if ports is None:
|
|
self.fwplugin_rpc.set_firewall_group_status(
|
|
context,
|
|
firewall_group['id'],
|
|
nl_constants.ERROR)
|
|
return ports
|
|
|
|
def _get_in_ns_ports(self, port_ids, ignore_errors=False):
|
|
"""Returns port objects in the local namespace, along with their
|
|
router_info.
|
|
"""
|
|
context = n_context.get_admin_context()
|
|
in_ns_ports = {} # This will be converted to a list later.
|
|
for port_id in port_ids:
|
|
# find the router of this port:
|
|
port = self.core_plugin.get_port(context, port_id)
|
|
# verify that this is a router interface port
|
|
if port['device_owner'] != nl_constants.DEVICE_OWNER_ROUTER_INTF:
|
|
if not ignore_errors:
|
|
LOG.error("NSX-V3 FWaaS V2 plugin does not support %s "
|
|
"ports", port['device_owner'])
|
|
return
|
|
# since this is a deletion of an illegal port, add this port
|
|
# with a dummy router so that the FWaaS plugin will notice the
|
|
# change and change the FW status.
|
|
rtr_info = 'Dummy'
|
|
else:
|
|
router_id = port['device_id']
|
|
router = self.core_plugin.get_router(context, router_id)
|
|
rtr_info = self._router_dict_to_obj(router)
|
|
if rtr_info:
|
|
if rtr_info in in_ns_ports:
|
|
in_ns_ports[rtr_info].append(port_id)
|
|
else:
|
|
in_ns_ports[rtr_info] = [port_id]
|
|
return list(in_ns_ports.items())
|
|
|
|
def delete_firewall_group(self, context, firewall_group, host):
|
|
"""Handles RPC from plugin to delete a firewall group.
|
|
|
|
This method is overridden here in order to handle routers
|
|
in Error state without ports, and make sure those are deleted.
|
|
"""
|
|
|
|
ports_for_fwg = self._get_firewall_group_ports(
|
|
context, firewall_group, to_delete=True)
|
|
if not ports_for_fwg:
|
|
# FW without ports should be deleted without calling the driver
|
|
self.fwplugin_rpc.firewall_group_deleted(
|
|
context, firewall_group['id'])
|
|
return
|
|
|
|
return super(NsxFwaasCallbacksV2, self).delete_firewall_group(
|
|
context, firewall_group, host)
|
|
|
|
def _get_routers_in_project(self, context, project_id):
|
|
return self.core_plugin.get_routers(
|
|
context,
|
|
filters={'project_id': [project_id]})
|
|
|
|
def _router_dict_to_obj(self, r):
|
|
# The callbacks expect a router-info object with an agent config
|
|
agent_conf = cfg.CONF
|
|
agent_conf.metadata_access_mark = '0x1'
|
|
return router_info.RouterInfo(
|
|
None, r['id'], router=r,
|
|
agent_conf=agent_conf,
|
|
interface_driver=None,
|
|
use_ipv6=False)
|
|
|
|
def get_port_fwg(self, context, port_id):
|
|
"""Return the firewall group of this port
|
|
|
|
if the FWaaS rules should be added to the backend router.
|
|
"""
|
|
if not self.fwaas_enabled:
|
|
return False
|
|
|
|
ctx = context.elevated()
|
|
fwg_id = self._get_port_firewall_group_id(ctx, port_id)
|
|
if fwg_id is None:
|
|
# No FWaas Firewall was assigned to this port
|
|
return
|
|
|
|
# check the state of this firewall group
|
|
fwg = self._get_fw_group_from_plugin(ctx, fwg_id)
|
|
if fwg is not None:
|
|
if fwg.get('status') in (nl_constants.ERROR,
|
|
nl_constants.PENDING_DELETE):
|
|
# Do not add rules of firewalls with errors
|
|
LOG.warning("Port %(port)s will not get rules from firewall "
|
|
"group %(fwg)s which is in %(status)s",
|
|
{'port': port_id, 'fwg': fwg_id,
|
|
'status': fwg['status']})
|
|
return
|
|
|
|
return fwg
|
|
|
|
def _get_fw_group_from_plugin(self, context, fwg_id):
|
|
# NOTE(asarfaty): currently there is no api to get a specific firewall
|
|
fwg_list = self.fwplugin_rpc.get_firewall_groups_for_project(context)
|
|
for fwg in fwg_list:
|
|
if fwg['id'] == fwg_id:
|
|
return fwg
|
|
|
|
def _get_port_firewall_group_id(self, context, port_id):
|
|
fw_plugin = directory.get_plugin(fwaas_constants.FIREWALL_V2)
|
|
if fw_plugin:
|
|
driver_db = fw_plugin.driver.firewall_db
|
|
return driver_db.get_fwg_attached_to_port(context, port_id)
|
|
|
|
def should_apply_firewall_to_router(self, context, router_id):
|
|
"""Return True if there are FWaaS rules that are attached to an
|
|
interface of the given router.
|
|
"""
|
|
if not self.fwaas_enabled:
|
|
return False
|
|
|
|
ctx = context.elevated()
|
|
router_interfaces = self.core_plugin._get_router_interfaces(
|
|
ctx, router_id)
|
|
for port in router_interfaces:
|
|
fwg_id = self._get_port_firewall_group_id(ctx, port['id'])
|
|
if fwg_id:
|
|
# check the state of this firewall group
|
|
fwg = self._get_fw_group_from_plugin(ctx, fwg_id)
|
|
if fwg is not None:
|
|
if fwg.get('status') not in (nl_constants.ERROR,
|
|
nl_constants.PENDING_DELETE):
|
|
# Found a router interface port with rules
|
|
return True
|
|
return False
|
|
|
|
def delete_port(self, context, port_id):
|
|
# Mark the FW group as inactive if this is the last port
|
|
fwg = self.get_port_fwg(context, port_id)
|
|
if (fwg and fwg.get('status') == nl_constants.ACTIVE and
|
|
len(fwg.get('ports', [])) <= 1):
|
|
self.fwplugin_rpc.set_firewall_group_status(
|
|
context, fwg['id'], nl_constants.INACTIVE)
|
|
|
|
|
|
class NsxCommonv3FwaasCallbacksV2(NsxFwaasCallbacksV2):
|
|
"""NSX-V3+Policy RPC callbacks for Firewall As A Service - V2."""
|
|
|
|
def should_apply_firewall_to_router(self, context, router_id):
|
|
"""Return True if the FWaaS rules should be added to this router."""
|
|
if not super(NsxCommonv3FwaasCallbacksV2,
|
|
self).should_apply_firewall_to_router(context,
|
|
router_id):
|
|
return False
|
|
|
|
# get all the relevant router info
|
|
ctx_elevated = context.elevated()
|
|
router_data = self.core_plugin.get_router(ctx_elevated, router_id)
|
|
if not router_data:
|
|
LOG.error("Couldn't read router %s data", router_id)
|
|
return False
|
|
|
|
# Check if the FWaaS driver supports this router
|
|
if not self.internal_driver.should_apply_firewall_to_router(
|
|
router_data):
|
|
return False
|
|
|
|
return True
|
|
|
|
def router_with_fwg(self, context, router_interfaces):
|
|
for port in router_interfaces:
|
|
fwg = self.get_port_fwg(context, port['id'])
|
|
if fwg and fwg.get('status') == nl_constants.ACTIVE:
|
|
return True
|
|
return False
|