Browse Source

Add log validator for FWaaS v2 logging

This patch adds a method to validate logging request for FWaaS.
It also introduces get_fwg_attached_to_port method in db_v2 as
helper function for logging feature.

Co-Authored-By: Nguyen Phuong An <AnNP@vn.fujitsu.com>
Co-Authored-By: Kim Bao Long <longkb@vn.fujitsu.com>
Partial-Bug: #1720727
Change-Id: I723b48a75ba3f19476785bec0c5d5295d04aac95
tags/13.0.0.0rc1
Cuong Nguyen 2 years ago
committed by Yushiro FURUKAWA
parent
commit
dd9aec1745
7 changed files with 345 additions and 0 deletions
  1. +8
    -0
      neutron_fwaas/db/firewall/v2/firewall_db_v2.py
  2. +0
    -0
      neutron_fwaas/services/logapi/__init__.py
  3. +21
    -0
      neutron_fwaas/services/logapi/constants.py
  4. +34
    -0
      neutron_fwaas/services/logapi/exceptions.py
  5. +125
    -0
      neutron_fwaas/services/logapi/fwg_validate.py
  6. +0
    -0
      neutron_fwaas/tests/unit/services/logapi/__init__.py
  7. +157
    -0
      neutron_fwaas/tests/unit/services/logapi/test_fwg_validate.py

+ 8
- 0
neutron_fwaas/db/firewall/v2/firewall_db_v2.py View File

@@ -844,6 +844,14 @@ class FirewallPluginDb(common_db_mixin.CommonDbMixin):
return default_fwg.id
return None

def get_fwg_attached_to_port(self, context, port_id):
"""Return a firewall group ID is associated to a port"""
fwg_port = self._model_query(context, FirewallGroupPortAssociation).\
filter_by(port_id=port_id).first()
if fwg_port:
return fwg_port.firewall_group_id
return None

def _ensure_default_firewall_group(self, context, tenant_id):
"""Create a default firewall group if one doesn't exist for a tenant



+ 0
- 0
neutron_fwaas/services/logapi/__init__.py View File


+ 21
- 0
neutron_fwaas/services/logapi/constants.py View File

@@ -0,0 +1,21 @@
# Copyright (c) 2018 Fujitsu Limited
# 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.


# Firewall group logging resource type
FIREWALL_GROUP = 'firewall_group'

# Target logging resource type
TARGET_RESOURCE = 'port which is associated with the firewall group'

+ 34
- 0
neutron_fwaas/services/logapi/exceptions.py View File

@@ -0,0 +1,34 @@
# Copyright (c) 2018 Fujitsu Limited
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from neutron._i18n import _
from neutron_lib import exceptions as n_exc

# TODO(annp or longkb): move to neutron-lib


class FWGIsNotReadyForLogging(n_exc.InvalidInput):
message = _("Firewall group %(fwg_id)s is not ready for logging "
"because of %(fwg_status)s status.")


class TargetResourceNotAssociated(n_exc.InvalidInput):
message = _("Target resource %(target_id)s is not associated with "
"any firewall group.")


class PortIsNotReadyForLogging(n_exc.InvalidInput):
message = _("Target resource %(target_id)s is not ready for logging "
"because of %(port_status)s status.")

+ 125
- 0
neutron_fwaas/services/logapi/fwg_validate.py View File

@@ -0,0 +1,125 @@
# Copyright (c) 2018 Fujitsu Limited
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from neutron.objects import ports
from neutron.services.logapi.common import exceptions as log_exc
from neutron.services.logapi.common import validators
from neutron_lib import constants as nl_const
from neutron_lib.plugins import directory
from sqlalchemy.orm import exc as orm_exc

from neutron_fwaas.common import fwaas_constants
from neutron_fwaas.services.logapi import constants as log_const
from neutron_fwaas.services.logapi import exceptions as fwg_log_exc

fwg_plugin = None


def _check_fwg(context, fwg_id):
try:
fwg = fwg_plugin.get_firewall_group(context, id=fwg_id)
except orm_exc.NoResultFound:
raise log_exc.ResourceNotFound(resource_id=fwg_id)

if fwg['status'] != nl_const.ACTIVE:
raise fwg_log_exc.FWGIsNotReadyForLogging(
fwg_id=fwg_id, fwg_status=fwg['status'])


def _check_fwg_port(context, port_id):

# Checking port exists
port = ports.Port.get_object(context, id=port_id)
if not port:
raise log_exc.TargetResourceNotFound(target_id=port_id)

device_owner = port.get('device_owner', '')
# Checking supported firewall group logging for vm port
if device_owner.startswith(nl_const.DEVICE_OWNER_COMPUTE_PREFIX):
if not validators.validate_log_type_for_port(
log_const.FIREWALL_GROUP, port):
raise log_exc.LoggingTypeNotSupported(
log_type=log_const.FIREWALL_GROUP,
port_id=port_id)
# Checking supported firewall group for router interface, DVR interface,
# and HA replicated interface
elif device_owner not in nl_const.ROUTER_INTERFACE_OWNERS:
raise log_exc.LoggingTypeNotSupported(
log_type=log_const.FIREWALL_GROUP, port_id=port_id)

# Checking port status
port_status = port.get('status')
if port_status != nl_const.PORT_STATUS_ACTIVE:
raise fwg_log_exc.PortIsNotReadyForLogging(target_id=port_id,
port_status=port_status)

# Checking whether router port or vm port binding with any firewall group
fwg_id = fwg_plugin.driver.firewall_db.get_fwg_attached_to_port(
context, port_id=port_id)

if not fwg_id:
raise fwg_log_exc.TargetResourceNotAssociated(target_id=port_id)

fwg = fwg_plugin.get_firewall_group(context, id=fwg_id)

if fwg['status'] != nl_const.ACTIVE:
raise fwg_log_exc.FWGIsNotReadyForLogging(fwg_id=fwg_id,
fwg_status=fwg['status'])


def _check_target_resource_bound_fwg(context, fwg_id, target_id):
ports = fwg_plugin.driver.firewall_db.get_ports_in_firewall_group(
context=context, firewall_group_id=fwg_id)
if target_id not in ports:
raise log_exc.InvalidResourceConstraint(
resource=log_const.FIREWALL_GROUP,
resource_id=fwg_id,
target_resource=log_const.TARGET_RESOURCE,
target_id=target_id)


@validators.ResourceValidateRequest.register(log_const.FIREWALL_GROUP)
def validate_firewall_group_request(context, log_data):
"""Validate a log request

This method validates log request is satisfied or not.

A ResourceNotFound will be raised if resource_id in log_data not exists or
a TargetResourceNotFound will be raised if target_id in log_data not
exists. Beside, FWGIsNotReadyForLogging will be raised in the case of
queried firewall group is not in ACTIVE state. PortIsNotReadyForLogging
exception will be raised if port is not in ACTIVE status. Besides,
TargetResourceNotAssociated exception will be raised if a given port does
not have any firewall group attach to. This method will also raise a
LoggingTypeNotSupported, if there is no log_driver supporting for
resource_type in log_data.

In addition, if log_data specify both resource_id and target_id. A
InvalidResourceConstraint will be raised if there is no constraint between
resource_id and target_id.

"""

global fwg_plugin
if not fwg_plugin:
fwg_plugin = directory.get_plugin(fwaas_constants.FIREWALL_V2)
resource_id = log_data.get('resource_id')
target_id = log_data.get('target_id')
if resource_id and target_id:
_check_target_resource_bound_fwg(context, resource_id, target_id)
if resource_id:
_check_fwg(context, resource_id)
if target_id:
_check_fwg_port(context, target_id)

+ 0
- 0
neutron_fwaas/tests/unit/services/logapi/__init__.py View File


+ 157
- 0
neutron_fwaas/tests/unit/services/logapi/test_fwg_validate.py View File

@@ -0,0 +1,157 @@
# Copyright (c) 2018 Fujitsu Limited
# 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 neutron.objects import ports
from neutron.services.logapi.common import exceptions as log_exc
from neutron.services.logapi.common import validators
from neutron.tests import base
from neutron_lib import constants as nl_const
from sqlalchemy.orm import exc as orm_exc

from neutron_fwaas.services.logapi import exceptions as fwg_log_exc
from neutron_fwaas.services.logapi import fwg_validate


class TestFWGLogRequestValidations(base.BaseTestCase):
"""Test validator for a log creation request"""

def setUp(self):
super(TestFWGLogRequestValidations, self).setUp()
fwg_validate.fwg_plugin = mock.Mock()
fwg_validate.fwg_plugin.driver = mock.Mock()
fwg_validate.fwg_plugin.driver.firewall_db = mock.Mock()

def test_validate_fwg_request(self):
m_context = mock.Mock()
fake_data = {
'resource_type': 'firewall_group',
'resource_id': 'fake_fwg_id'
}
with mock.patch.object(fwg_validate, '_check_fwg'):
fwg_validate.validate_firewall_group_request(m_context, fake_data)
fwg_validate._check_fwg.\
assert_called_with(m_context, fake_data['resource_id'])
fake_data = {
'resource_type': 'firewall_group',
'resource_id': 'fake_fwg_id',
'target_id': 'fake_port_id'
}
with mock.patch.object(fwg_validate,
'_check_target_resource_bound_fwg'):
with mock.patch.object(fwg_validate, '_check_fwg'):
with mock.patch.object(fwg_validate, '_check_fwg_port'):
fwg_validate.validate_firewall_group_request(m_context,
fake_data)
fwg_validate._check_target_resource_bound_fwg.\
assert_called_with(m_context,
fake_data['resource_id'],
fake_data['target_id'])
fwg_validate._check_fwg. \
assert_called_with(m_context,
fake_data['resource_id'])
fwg_validate._check_fwg_port. \
assert_called_with(m_context,
fake_data['target_id'])

def test_validate_request_fwg_id_not_exists(self):

with mock.patch.object(fwg_validate.fwg_plugin, 'get_firewall_group',
side_effect=orm_exc.NoResultFound):
self.assertRaises(
log_exc.ResourceNotFound,
fwg_validate._check_fwg,
mock.ANY,
'fake_fwg_id')

def test_validate_request_fwg_not_active(self):
fake_fwg = {'id': '1234', 'status': 'PENDING'}
with mock.patch.object(fwg_validate.fwg_plugin, 'get_firewall_group',
return_value=fake_fwg):
self.assertRaises(
fwg_log_exc.FWGIsNotReadyForLogging,
fwg_validate._check_fwg,
mock.ANY,
'fake_fwg_id')

def test_validate_request_router_or_port_id_not_exists(self):
with mock.patch.object(ports.Port, 'get_object', return_value=None):
self.assertRaises(
log_exc.TargetResourceNotFound,
fwg_validate._check_fwg_port,
mock.ANY,
'fake_port_id')

def test_validate_request_unsupported_fwg_log_on_vm_port(self):

fake_port = {'device_owner': "compute:"}
with mock.patch.object(ports.Port, 'get_object',
return_value=fake_port):
with mock.patch.object(validators, 'validate_log_type_for_port',
return_value=False):
self.assertRaises(
log_exc.LoggingTypeNotSupported,
fwg_validate._check_fwg_port,
mock.ANY,
'fake_port_id')

def test_validate_request_router_port_is_not_active(self):

non_active_status = [nl_const.PORT_STATUS_DOWN,
nl_const.PORT_STATUS_ERROR,
nl_const.PORT_STATUS_NOTAPPLICABLE,
nl_const.PORT_STATUS_BUILD]
fake_port = [{'device_owner': nl_const.DEVICE_OWNER_ROUTER_INTF,
'status': status}
for status in non_active_status]
with mock.patch.object(ports.Port, 'get_object',
side_effect=fake_port):
for status in non_active_status:
self.assertRaises(
fwg_log_exc.PortIsNotReadyForLogging,
fwg_validate._check_fwg_port,
mock.ANY,
'fake_port_id')

def test_validate_request_router_port_was_not_associated_fwg(self):

fake_port = {'device_owner': nl_const.DEVICE_OWNER_ROUTER_INTF,
'status': nl_const.PORT_STATUS_ACTIVE}

with mock.patch.object(ports.Port, 'get_object',
return_value=fake_port):
with mock.patch.object(fwg_validate.fwg_plugin.driver.firewall_db,
'get_fwg_attached_to_port',
return_value=None):
self.assertRaises(
fwg_log_exc.TargetResourceNotAssociated,
fwg_validate._check_fwg_port,
mock.ANY,
'fake_port_id')

def test_validate_request_target_resource_not_bound_fwg(self):

fake_ports_in_fwg = ['fake_port_id1, fake_port_id2']
with mock.patch.object(
fwg_validate.fwg_plugin.driver.firewall_db,
'get_ports_in_firewall_group',
return_value=fake_ports_in_fwg):

self.assertRaises(
log_exc.InvalidResourceConstraint,
fwg_validate._check_target_resource_bound_fwg,
mock.ANY,
mock.ANY,
'fake_target_id')

Loading…
Cancel
Save