Skip to content

Commit 6126609

Browse files
committed
network: Add --evpn-vni to a Neutron router create command
The attribute has two modes: - a specified number greater tha 0 sends evpn_vni: <int> to the server - an "auto" which sends number 0 to the server, which results in the VNI auto-allocation The evpn-vni attribute on the Neutron server side is immutable for now and we do not support updates. Related-Bug: #2144617 Assisted-By: Claude Opus 4.6 Change-Id: I4b11947c3a7e25fec6b1bd55e5134bfd4ffc0bb1 Signed-off-by: Jakub Libosvar <libosvar@redhat.com>
1 parent 32cd99b commit 6126609

2 files changed

Lines changed: 180 additions & 1 deletion

File tree

openstackclient/network/v2/router.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,14 +260,31 @@ def _get_attrs(
260260
flavor = n_client.find_flavor(parsed_args.flavor, ignore_missing=False)
261261
attrs['flavor_id'] = flavor.id
262262

263-
for attr in ('enable_default_route_bfd', 'enable_default_route_ecmp'):
263+
for attr in (
264+
'enable_default_route_bfd',
265+
'enable_default_route_ecmp',
266+
'evpn_vni',
267+
):
264268
value = getattr(parsed_args, attr, None)
265269
if value is not None:
266270
attrs[attr] = value
267271

268272
return attrs
269273

270274

275+
def _parse_evpn_vni(value: str) -> int:
276+
try:
277+
vni = int(value)
278+
except ValueError:
279+
raise argparse.ArgumentTypeError(
280+
_("'%(value)s' is not a valid VNI (use a positive integer)")
281+
% {'value': value}
282+
)
283+
if vni <= 0:
284+
raise argparse.ArgumentTypeError(_("VNI must be a positive integer"))
285+
return vni
286+
287+
271288
def _parser_add_bfd_ecmp_arguments(parser: argparse.ArgumentParser) -> None:
272289
"""Helper to add BFD and ECMP args for CreateRouter and SetRouter."""
273290
parser.add_argument(
@@ -609,6 +626,24 @@ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
609626
metavar='<qos-policy>',
610627
help=_('Attach QoS policy to router gateway IPs'),
611628
)
629+
evpn_group = parser.add_mutually_exclusive_group()
630+
evpn_group.add_argument(
631+
'--evpn-vni',
632+
metavar='<vni>',
633+
default=None,
634+
type=_parse_evpn_vni,
635+
dest='evpn_vni',
636+
help=_("Associate the router with an EVPN identified by a VNI."),
637+
)
638+
evpn_group.add_argument(
639+
'--auto-evpn-vni',
640+
action='store_const',
641+
dest='evpn_vni',
642+
const=0,
643+
help=_(
644+
"Associate the router with an EVPN using an auto-assigned VNI."
645+
),
646+
)
612647

613648
return parser
614649

openstackclient/tests/unit/network/v2/test_router.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,150 @@ def test_create_with_qos_policy_no_external_gateway(self):
613613
parsed_args,
614614
)
615615

616+
def test_create_with_evpn_vni_auto(self):
617+
arglist = [
618+
'--auto-evpn-vni',
619+
self.new_router.name,
620+
]
621+
verifylist = [
622+
('name', self.new_router.name),
623+
('enable', True),
624+
('distributed', False),
625+
('ha', False),
626+
('evpn_vni', 0),
627+
]
628+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
629+
630+
columns, data = self.cmd.take_action(parsed_args)
631+
632+
self.network_client.create_router.assert_called_once_with(
633+
**{
634+
'admin_state_up': True,
635+
'name': self.new_router.name,
636+
'evpn_vni': 0,
637+
}
638+
)
639+
self.assertEqual(self.columns, columns)
640+
self.assertCountEqual(self.data, data)
641+
642+
def test_create_with_evpn_vni_explicit(self):
643+
arglist = [
644+
'--evpn-vni',
645+
'10000',
646+
self.new_router.name,
647+
]
648+
verifylist = [
649+
('name', self.new_router.name),
650+
('enable', True),
651+
('distributed', False),
652+
('ha', False),
653+
('evpn_vni', 10000),
654+
]
655+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
656+
657+
columns, data = self.cmd.take_action(parsed_args)
658+
659+
self.network_client.create_router.assert_called_once_with(
660+
**{
661+
'admin_state_up': True,
662+
'name': self.new_router.name,
663+
'evpn_vni': 10000,
664+
}
665+
)
666+
self.assertEqual(self.columns, columns)
667+
self.assertCountEqual(self.data, data)
668+
669+
def test_create_without_evpn_vni(self):
670+
arglist = [
671+
self.new_router.name,
672+
]
673+
verifylist = [
674+
('name', self.new_router.name),
675+
('enable', True),
676+
('distributed', False),
677+
('ha', False),
678+
('evpn_vni', None),
679+
]
680+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
681+
682+
_columns, _data = self.cmd.take_action(parsed_args)
683+
684+
self.network_client.create_router.assert_called_once_with(
685+
**{
686+
'admin_state_up': True,
687+
'name': self.new_router.name,
688+
}
689+
)
690+
self.assertNotIn(
691+
'evpn_vni',
692+
self.network_client.create_router.call_args[1],
693+
)
694+
695+
def test_create_with_evpn_vni_invalid_string(self):
696+
arglist = [
697+
'--evpn-vni',
698+
'foo',
699+
self.new_router.name,
700+
]
701+
verifylist = []
702+
703+
self.assertRaises(
704+
tests_utils.ParserException,
705+
self.check_parser,
706+
self.cmd,
707+
arglist,
708+
verifylist,
709+
)
710+
711+
def test_create_with_evpn_vni_zero(self):
712+
arglist = [
713+
'--evpn-vni',
714+
'0',
715+
self.new_router.name,
716+
]
717+
verifylist = []
718+
719+
self.assertRaises(
720+
tests_utils.ParserException,
721+
self.check_parser,
722+
self.cmd,
723+
arglist,
724+
verifylist,
725+
)
726+
727+
def test_create_with_evpn_vni_negative(self):
728+
arglist = [
729+
'--evpn-vni',
730+
'-1',
731+
self.new_router.name,
732+
]
733+
verifylist = []
734+
735+
self.assertRaises(
736+
tests_utils.ParserException,
737+
self.check_parser,
738+
self.cmd,
739+
arglist,
740+
verifylist,
741+
)
742+
743+
def test_create_with_evpn_vni_mutually_exclusive_args(self):
744+
arglist = [
745+
'--evpn-vni',
746+
'10000',
747+
'--auto-evpn-vni',
748+
self.new_router.name,
749+
]
750+
verifylist = []
751+
752+
self.assertRaises(
753+
tests_utils.ParserException,
754+
self.check_parser,
755+
self.cmd,
756+
arglist,
757+
verifylist,
758+
)
759+
616760

617761
class TestDeleteRouter(TestRouter):
618762
# The routers to delete.

0 commit comments

Comments
 (0)