Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,88 @@
from neutron.services.ovn_l3.service_providers.user_defined import UserDefined
import logging

from neutron.services.l3_router.service_providers import base
from neutron_lib import constants as const
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory

class PaloAlto(UserDefined):
pass
LOG = logging.getLogger(__name__)


@registry.has_registry_receivers
class PaloAlto(base.L3ServiceProvider):
"""Stub L3 service provider for the Palo Alto router flavor.

Inherits from the base L3 service provider instead
of UserDefined. Routers of this flavor are detected
via their flavor's service profile driver.
"""

ha_support = base.OPTIONAL

def __init__(self, l3_plugin):
super().__init__(l3_plugin)
self._palo_alto_provider = f"{__name__}.{self.__class__.__name__}"
LOG.info(
"Palo Alto service provider initialized: driver=%r",
self._palo_alto_provider,
)

@property
def _flavor_plugin(self):
try:
return self._flavor_plugin_ref
except AttributeError:
self._flavor_plugin_ref = directory.get_plugin(plugin_constants.FLAVORS)
return self._flavor_plugin_ref

def _is_palo_alto_provider(self, context, router):
flavor_id = router.get("flavor_id")
if flavor_id is None or flavor_id is const.ATTR_NOT_SPECIFIED:
LOG.debug(
"Palo Alto flavor check skipped: router=%s name=%s project=%s "
"flavor=%s request_id=%s",
router.get("id"),
router.get("name"),
router.get("project_id"),
flavor_id,
getattr(context, "request_id", None),
)
return False
flavor = self._flavor_plugin.get_flavor(context, flavor_id)
provider = self._flavor_plugin.get_flavor_next_provider(context, flavor["id"])[
0
]
actual_driver = str(provider["driver"])
matched = actual_driver == self._palo_alto_provider
LOG.debug(
"Palo Alto flavor check: router=%s name=%s project=%s flavor=%s "
"expected_driver=%s actual_driver=%s matched=%s request_id=%s",
router.get("id"),
router.get("name"),
router.get("project_id"),
flavor_id,
self._palo_alto_provider,
actual_driver,
matched,
getattr(context, "request_id", None),
)
return matched

@registry.receives(resources.ROUTER, [events.AFTER_CREATE])
def _process_router_create(self, resource, event, trigger, payload=None):
router = payload.states[0]
context = payload.context
if not self._is_palo_alto_provider(context, router):
return
LOG.info(
"Palo Alto stub router create: no action taken for router=%s "
"name=%s project=%s flavor=%s request_id=%s",
router.get("id"),
router.get("name"),
router.get("project_id"),
router.get("flavor_id"),
getattr(context, "request_id", None),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from neutron_lib import constants as const

from neutron_understack.l3_router import palo_alto


class FakeFlavorPlugin:
def __init__(self, driver):
self.driver = driver

def get_flavor(self, _context, flavor_id):
return {"id": flavor_id}

def get_flavor_next_provider(self, _context, _flavor_id):
return [{"driver": self.driver}]


class FakePayload:
def __init__(self, router, context="context"):
self.states = (router,)
self.context = context
self.resource_id = router.get("id")


def _palo_alto_driver():
return f"{palo_alto.PaloAlto.__module__}.{palo_alto.PaloAlto.__name__}"


class TestPaloAltoProvider:
def test_flavor_plugin_is_cached(self, mocker):
plugin = FakeFlavorPlugin(_palo_alto_driver())
get_plugin = mocker.patch.object(
palo_alto.directory, "get_plugin", return_value=plugin
)
provider = palo_alto.PaloAlto(None)

assert provider._flavor_plugin is plugin
assert provider._flavor_plugin is plugin
get_plugin.assert_called_once_with(palo_alto.plugin_constants.FLAVORS)

def test_is_palo_alto_provider_returns_false_without_flavor(self, mocker):
get_plugin = mocker.patch.object(palo_alto.directory, "get_plugin")
provider = palo_alto.PaloAlto(None)

assert provider._is_palo_alto_provider("context", {"id": "router-a"}) is False
assert (
provider._is_palo_alto_provider(
"context",
{"id": "router-a", "flavor_id": const.ATTR_NOT_SPECIFIED},
)
is False
)
get_plugin.assert_not_called()

def test_is_palo_alto_provider_returns_true_for_palo_alto_driver(self, mocker):
plugin = FakeFlavorPlugin(_palo_alto_driver())
mocker.patch.object(palo_alto.directory, "get_plugin", return_value=plugin)
provider = palo_alto.PaloAlto(None)

assert (
provider._is_palo_alto_provider(
"context",
{"id": "router-a", "flavor_id": "palo-alto-flavor-id"},
)
is True
)

def test_is_palo_alto_provider_returns_false_for_different_driver(self, mocker):
plugin = FakeFlavorPlugin("neutron_understack.l3_router.vrf.Vrf")
mocker.patch.object(palo_alto.directory, "get_plugin", return_value=plugin)
provider = palo_alto.PaloAlto(None)

assert (
provider._is_palo_alto_provider(
"context",
{"id": "router-a", "flavor_id": "vrf-flavor-id"},
)
is False
)

def test_router_create_is_noop_for_palo_alto_router(self, mocker):
plugin = FakeFlavorPlugin(_palo_alto_driver())
mocker.patch.object(palo_alto.directory, "get_plugin", return_value=plugin)
l3_plugin = mocker.Mock()
provider = palo_alto.PaloAlto(l3_plugin)
payload = FakePayload({"id": "router-a", "flavor_id": "palo-alto-flavor-id"})

result = provider._process_router_create(
"router", "after_create", "trigger", payload
)

# Stub flavor: no action is taken on a matching router.
assert result is None
assert l3_plugin.mock_calls == []

def test_router_create_skips_non_palo_alto_router(self, mocker):
plugin = FakeFlavorPlugin("neutron_understack.l3_router.vrf.Vrf")
mocker.patch.object(palo_alto.directory, "get_plugin", return_value=plugin)
l3_plugin = mocker.Mock()
provider = palo_alto.PaloAlto(l3_plugin)
payload = FakePayload({"id": "router-b", "flavor_id": "vrf-flavor-id"})

result = provider._process_router_create(
"router", "after_create", "trigger", payload
)

assert result is None
assert l3_plugin.mock_calls == []
Loading