Skip to content

Commit 41f976b

Browse files
authored
Merge pull request #2085 from rackerlabs/svi-l3-provider-foundation
feat(neutron): Make SVI a standalone L3 service provider
2 parents 98f2cb4 + fbf680d commit 41f976b

2 files changed

Lines changed: 119 additions & 3 deletions

File tree

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,50 @@
1-
from neutron.services.ovn_l3.service_providers.user_defined import UserDefined
1+
import logging
22

3+
from neutron.services.l3_router.service_providers import base
4+
from neutron_lib import constants as const
5+
from neutron_lib.plugins import constants as plugin_constants
6+
from neutron_lib.plugins import directory
37

4-
class Svi(UserDefined):
5-
pass
8+
LOG = logging.getLogger(__name__)
9+
10+
# Full dotted path as stored in the flavor profile's service_providers table.
11+
# Must match the value in understack/components/neutron/values.yaml.
12+
# SVI_DRIVER = "neutron_understack.l3_router.svi.Svi"
13+
14+
15+
class Svi(base.L3ServiceProvider):
16+
ha_support = base.OPTIONAL
17+
18+
def __init__(self, l3_plugin):
19+
super().__init__(l3_plugin)
20+
# self._svi_provider = SVI_DRIVER
21+
self._svi_provider = f"{self.__module__}.{self.__class__.__name__}"
22+
LOG.info("SVI service provider initialized: driver=%r", self._svi_provider)
23+
24+
@property
25+
def _flavor_plugin(self):
26+
try:
27+
return self._flavor_plugin_ref
28+
except AttributeError:
29+
self._flavor_plugin_ref = directory.get_plugin(plugin_constants.FLAVORS)
30+
return self._flavor_plugin_ref
31+
32+
# ATTR_NOT_SPECIFIED “the API field was not provided.”
33+
def _is_svi_flavor(self, context, router):
34+
flavor_id = router.get("flavor_id")
35+
if flavor_id is None or flavor_id is const.ATTR_NOT_SPECIFIED:
36+
return False
37+
38+
flavor = self._flavor_plugin.get_flavor(context, flavor_id)
39+
provider = self._flavor_plugin.get_flavor_next_provider(context, flavor["id"])[
40+
0
41+
]
42+
is_svi = str(provider["driver"]) == self._svi_provider
43+
LOG.debug(
44+
"SVI flavor check: router %s flavor %s driver %s is_svi=%s",
45+
router.get("id"),
46+
flavor_id,
47+
provider["driver"],
48+
is_svi,
49+
)
50+
return is_svi
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from neutron_lib import constants as const
2+
3+
from neutron_understack.l3_router import svi
4+
5+
6+
class FakeFlavorPlugin:
7+
def __init__(self, driver):
8+
self.driver = driver
9+
10+
def get_flavor(self, _context, flavor_id):
11+
return {"id": flavor_id}
12+
13+
def get_flavor_next_provider(self, _context, _flavor_id):
14+
return [{"driver": self.driver}]
15+
16+
17+
def _svi_driver():
18+
return f"{svi.Svi.__module__}.{svi.Svi.__name__}"
19+
20+
21+
class TestSviProvider:
22+
def test_flavor_plugin_is_cached(self, mocker):
23+
plugin = FakeFlavorPlugin(_svi_driver())
24+
get_plugin = mocker.patch.object(
25+
svi.directory, "get_plugin", return_value=plugin
26+
)
27+
provider = svi.Svi(None)
28+
29+
assert provider._flavor_plugin is plugin
30+
assert provider._flavor_plugin is plugin
31+
get_plugin.assert_called_once_with(svi.plugin_constants.FLAVORS)
32+
33+
def test_is_svi_flavor_returns_false_without_flavor(self, mocker):
34+
get_plugin = mocker.patch.object(svi.directory, "get_plugin")
35+
provider = svi.Svi(None)
36+
37+
assert provider._is_svi_flavor("context", {"id": "router-a"}) is False
38+
assert (
39+
provider._is_svi_flavor(
40+
"context",
41+
{"id": "router-a", "flavor_id": const.ATTR_NOT_SPECIFIED},
42+
)
43+
is False
44+
)
45+
get_plugin.assert_not_called()
46+
47+
def test_is_svi_flavor_returns_true_for_svi_driver(self, mocker):
48+
plugin = FakeFlavorPlugin(_svi_driver())
49+
mocker.patch.object(svi.directory, "get_plugin", return_value=plugin)
50+
provider = svi.Svi(None)
51+
52+
assert (
53+
provider._is_svi_flavor(
54+
"context",
55+
{"id": "router-a", "flavor_id": "svi-flavor-id"},
56+
)
57+
is True
58+
)
59+
60+
def test_is_svi_flavor_returns_false_for_different_driver(self, mocker):
61+
plugin = FakeFlavorPlugin("neutron_understack.l3_router.vrf.Vrf")
62+
mocker.patch.object(svi.directory, "get_plugin", return_value=plugin)
63+
provider = svi.Svi(None)
64+
65+
assert (
66+
provider._is_svi_flavor(
67+
"context",
68+
{"id": "router-a", "flavor_id": "vrf-flavor-id"},
69+
)
70+
is False
71+
)

0 commit comments

Comments
 (0)