Skip to content

Commit 60fb0fe

Browse files
Feat add interface vpp support (#255)
* Adding interface VPP backend * Adding interface VPP frontend * Removing unused vars
1 parent 9ba57dd commit 60fb0fe

16 files changed

Lines changed: 3880 additions & 7 deletions

File tree

backend/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
# Import routers
1515
from routers.session import session as session_router
16-
from routers.interfaces import ethernet, dummy, bonding, bridge, geneve, input, l2tpv3, loopback, macsec, openvpn, pppoe, pseudo_ethernet, sstpc, virtual_ethernet
16+
from routers.interfaces import ethernet, dummy, bonding, bridge, geneve, input, l2tpv3, loopback, macsec, openvpn, pppoe, pseudo_ethernet, sstpc, virtual_ethernet, vpp
1717
from routers.firewall import groups
1818
from routers.firewall import ipv4 as firewall_ipv4
1919
from routers.firewall import ipv6 as firewall_ipv6
@@ -294,6 +294,7 @@ async def get_permissions(request: Request) -> dict:
294294
app.include_router(pseudo_ethernet.router)
295295
app.include_router(sstpc.router)
296296
app.include_router(virtual_ethernet.router)
297+
app.include_router(vpp.router)
297298
app.include_router(groups.router)
298299
app.include_router(firewall_ipv4.router)
299300
app.include_router(firewall_ipv6.router)
@@ -370,6 +371,7 @@ async def read_root() -> dict:
370371
"pseudo-ethernet-interface",
371372
"sstpc-interface",
372373
"virtual-ethernet-interface",
374+
"vpp-interface",
373375
"firewall-groups",
374376
"firewall-ipv4",
375377
"firewall-ipv6",

backend/routers/interfaces/vpp.py

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
"""
2+
VPP Interface Configuration Endpoints
3+
4+
All VPP (Vector Packet Processing) interface endpoints for VyOS 1.5+.
5+
VPP supports seven interface types under `interfaces vpp`:
6+
bonding (vppbondN), bridge (vppbrN), gre (vppgreN), ipip (vppipipN),
7+
loopback (vpploN), vxlan (vppvxlanN), xconnect (vppxconN)
8+
9+
Multi-parameter builder operations encode extra parameters as colon-separated
10+
values in the `value` field, e.g. "vlan_id:address" for vif address ops.
11+
"""
12+
13+
import inspect
14+
import logging
15+
from typing import Dict, List, Optional, Any
16+
17+
from fastapi import APIRouter, HTTPException, Request
18+
from pydantic import BaseModel, Field
19+
from starlette.concurrency import run_in_threadpool
20+
21+
from fastapi_permissions import require_read_permission, require_write_permission
22+
from rbac_permissions import FeatureGroup
23+
from session_vyos_service import get_session_vyos_service
24+
25+
logger = logging.getLogger(__name__)
26+
27+
router = APIRouter(prefix="/vyos/vpp", tags=["vpp-interface"])
28+
29+
30+
# =============================================================================
31+
# Request / Response Models
32+
# =============================================================================
33+
34+
35+
class BatchOperation(BaseModel):
36+
op: str = Field(..., description="Builder method name")
37+
value: Optional[str] = Field(None, description="Value (colon-separated for multi-param ops)")
38+
39+
40+
class BatchRequest(BaseModel):
41+
interface: str = Field(..., description="Interface name (e.g., vppbond0, vppgre1)")
42+
operations: List[BatchOperation]
43+
44+
45+
class VyOSResponse(BaseModel):
46+
success: bool
47+
data: Optional[Dict[str, Any]] = None
48+
error: Optional[str] = None
49+
50+
51+
# ---- Per-type config models -------------------------------------------------
52+
53+
54+
class VifConfig(BaseModel):
55+
vlan_id: str
56+
description: Optional[str] = None
57+
disabled: bool = False
58+
addresses: List[str] = Field(default_factory=list)
59+
mtu: Optional[str] = None
60+
61+
62+
class BridgeMember(BaseModel):
63+
interface: str
64+
bvi: bool = False
65+
66+
67+
class BondingConfig(BaseModel):
68+
name: str
69+
description: Optional[str] = None
70+
disabled: bool = False
71+
mode: Optional[str] = None
72+
hash_policy: Optional[str] = None
73+
mac: Optional[str] = None
74+
mtu: Optional[str] = None
75+
addresses: List[str] = Field(default_factory=list)
76+
members: List[str] = Field(default_factory=list)
77+
vif: List[VifConfig] = Field(default_factory=list)
78+
79+
80+
class BridgeConfig(BaseModel):
81+
name: str
82+
description: Optional[str] = None
83+
members: List[BridgeMember] = Field(default_factory=list)
84+
85+
86+
class GreConfig(BaseModel):
87+
name: str
88+
description: Optional[str] = None
89+
disabled: bool = False
90+
addresses: List[str] = Field(default_factory=list)
91+
mtu: Optional[str] = None
92+
remote: Optional[str] = None
93+
source_address: Optional[str] = None
94+
tunnel_type: Optional[str] = None
95+
key: Optional[str] = None
96+
97+
98+
class IpipConfig(BaseModel):
99+
name: str
100+
description: Optional[str] = None
101+
disabled: bool = False
102+
addresses: List[str] = Field(default_factory=list)
103+
mtu: Optional[str] = None
104+
remote: Optional[str] = None
105+
source_address: Optional[str] = None
106+
107+
108+
class LoopbackConfig(BaseModel):
109+
name: str
110+
description: Optional[str] = None
111+
disabled: bool = False
112+
addresses: List[str] = Field(default_factory=list)
113+
mtu: Optional[str] = None
114+
vif: List[VifConfig] = Field(default_factory=list)
115+
116+
117+
class VxlanConfig(BaseModel):
118+
name: str
119+
description: Optional[str] = None
120+
disabled: bool = False
121+
addresses: List[str] = Field(default_factory=list)
122+
mtu: Optional[str] = None
123+
remote: Optional[str] = None
124+
source_address: Optional[str] = None
125+
vni: Optional[str] = None
126+
127+
128+
class XconnectConfig(BaseModel):
129+
name: str
130+
description: Optional[str] = None
131+
disabled: bool = False
132+
members: List[str] = Field(default_factory=list)
133+
134+
135+
class VppConfigResponse(BaseModel):
136+
bonding: List[BondingConfig] = Field(default_factory=list)
137+
bridge: List[BridgeConfig] = Field(default_factory=list)
138+
gre: List[GreConfig] = Field(default_factory=list)
139+
ipip: List[IpipConfig] = Field(default_factory=list)
140+
loopback: List[LoopbackConfig] = Field(default_factory=list)
141+
vxlan: List[VxlanConfig] = Field(default_factory=list)
142+
xconnect: List[XconnectConfig] = Field(default_factory=list)
143+
total: int = 0
144+
145+
146+
# =============================================================================
147+
# Endpoints
148+
# =============================================================================
149+
150+
151+
@router.get("/capabilities")
152+
async def get_capabilities(request: Request) -> Dict[str, Any]:
153+
"""Return version-aware feature capabilities for VPP interfaces."""
154+
await require_read_permission(request, FeatureGroup.INTERFACES)
155+
service = get_session_vyos_service(request)
156+
from vyos_builders.interfaces.vpp import VppInterfaceBuilderMixin
157+
builder = VppInterfaceBuilderMixin(version=service.get_version())
158+
return builder.get_capabilities()
159+
160+
161+
@router.get("/config", response_model=VppConfigResponse)
162+
async def get_config(http_request: Request, refresh: bool = False) -> VppConfigResponse:
163+
"""Get all VPP interface configurations from VyOS."""
164+
await require_read_permission(http_request, FeatureGroup.INTERFACES)
165+
try:
166+
service = get_session_vyos_service(http_request)
167+
full_config = await run_in_threadpool(service.get_full_config, refresh)
168+
raw_config = full_config.get("interfaces", {}).get("vpp", {})
169+
170+
from vyos_mappers.interfaces.vpp_versions import get_vpp_mapper
171+
mapper = get_vpp_mapper(service.get_version())
172+
parsed = mapper.parse_all_vpp_interfaces(raw_config)
173+
return VppConfigResponse(**parsed)
174+
except Exception:
175+
logger.exception("Unhandled error in get_config")
176+
raise HTTPException(status_code=500, detail="Internal server error")
177+
178+
179+
@router.post("/batch", response_model=VyOSResponse)
180+
async def batch_configure(http_request: Request, request: BatchRequest) -> VyOSResponse:
181+
"""
182+
Configure a VPP interface using batch operations.
183+
184+
Operation names match builder methods exactly, e.g.:
185+
set_bonding_description, set_gre_remote, delete_vxlan_vni
186+
187+
Multi-parameter operations use colon-separated `value`:
188+
set_bonding_vif_address → value="100:192.0.2.1/24" (vlan_id:address)
189+
set_bonding_vif_mtu → value="100:1500" (vlan_id:mtu)
190+
set_loopback_vif_address → value="10:10.0.0.1/24" (vlan_id:address)
191+
"""
192+
await require_write_permission(http_request, FeatureGroup.INTERFACES)
193+
194+
try:
195+
service = get_session_vyos_service(http_request)
196+
batch = service.create_vpp_batch()
197+
198+
for op in request.operations:
199+
if op.op in batch._INTERNAL_BUILDER_METHODS:
200+
raise HTTPException(
201+
status_code=400,
202+
detail=f"Operation '{op.op}' is not a valid interface operation",
203+
)
204+
205+
method = getattr(batch, op.op, None)
206+
if method is None:
207+
raise HTTPException(
208+
status_code=400,
209+
detail=f"Unsupported operation: {op.op}",
210+
)
211+
212+
sig = inspect.signature(method)
213+
params = [p for p in sig.parameters.keys() if p != "self"]
214+
215+
if len(params) == 1:
216+
method(request.interface)
217+
elif len(params) == 2:
218+
if op.value is None:
219+
raise HTTPException(
220+
status_code=400,
221+
detail=f"Operation '{op.op}' requires a value",
222+
)
223+
method(request.interface, op.value)
224+
elif len(params) == 3:
225+
if op.value is None:
226+
raise HTTPException(
227+
status_code=400,
228+
detail=f"Operation '{op.op}' requires a value in 'param1:param2' format",
229+
)
230+
parts = op.value.split(":", 1)
231+
if len(parts) != 2:
232+
raise HTTPException(
233+
status_code=400,
234+
detail=f"Operation '{op.op}' requires value in 'param1:param2' format",
235+
)
236+
method(request.interface, parts[0], parts[1])
237+
elif len(params) == 4:
238+
if op.value is None:
239+
raise HTTPException(
240+
status_code=400,
241+
detail=f"Operation '{op.op}' requires a value in 'param1:param2:param3' format",
242+
)
243+
parts = op.value.split(":", 2)
244+
if len(parts) != 3:
245+
raise HTTPException(
246+
status_code=400,
247+
detail=f"Operation '{op.op}' requires value in 'param1:param2:param3' format",
248+
)
249+
method(request.interface, parts[0], parts[1], parts[2])
250+
else:
251+
raise HTTPException(
252+
status_code=400,
253+
detail=f"Operation '{op.op}' has unexpected signature",
254+
)
255+
256+
response = service.execute_batch(batch)
257+
return VyOSResponse(
258+
success=response.status == 200,
259+
data=response.result if isinstance(response.result, dict) else None,
260+
error=response.error if response.error else None,
261+
)
262+
except HTTPException:
263+
raise
264+
except Exception:
265+
logger.exception("Unhandled error in batch_configure")
266+
raise HTTPException(status_code=500, detail="Internal server error")

backend/vyos_builders/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Each builder includes all necessary operations for its feature type.
66
"""
77

8-
from .interfaces import EthernetInterfaceBuilderMixin, DummyInterfaceBuilderMixin, BondingInterfaceBuilderMixin, GeneveInterfaceBuilderMixin, InputInterfaceBuilderMixin, L2TPv3InterfaceBuilderMixin, LoopbackInterfaceBuilderMixin, MacsecInterfaceBuilderMixin, OpenvpnInterfaceBuilderMixin, PppoeInterfaceBuilderMixin, PseudoEthernetInterfaceBuilderMixin, SstpcInterfaceBuilderMixin, VirtualEthernetInterfaceBuilderMixin
8+
from .interfaces import EthernetInterfaceBuilderMixin, DummyInterfaceBuilderMixin, BondingInterfaceBuilderMixin, GeneveInterfaceBuilderMixin, InputInterfaceBuilderMixin, L2TPv3InterfaceBuilderMixin, LoopbackInterfaceBuilderMixin, MacsecInterfaceBuilderMixin, OpenvpnInterfaceBuilderMixin, PppoeInterfaceBuilderMixin, PseudoEthernetInterfaceBuilderMixin, SstpcInterfaceBuilderMixin, VirtualEthernetInterfaceBuilderMixin, VppInterfaceBuilderMixin
99
from .firewall import FirewallGroupsBatchBuilder, FirewallIPv4BatchBuilder, FirewallIPv6BatchBuilder, BridgeFirewallBatchBuilder, FirewallZonesBatchBuilder
1010
from .nat import NATBatchBuilder
1111
from .nat64 import NAT64BatchBuilder
@@ -52,6 +52,7 @@
5252
PseudoEthernetBatchBuilder = PseudoEthernetInterfaceBuilderMixin
5353
SstpcBatchBuilder = SstpcInterfaceBuilderMixin
5454
VirtualEthernetBatchBuilder = VirtualEthernetInterfaceBuilderMixin
55+
VppBatchBuilder = VppInterfaceBuilderMixin
5556

5657
__all__ = [
5758
"EthernetBatchBuilder",
@@ -103,4 +104,5 @@
103104
"PseudoEthernetBatchBuilder",
104105
"SstpcBatchBuilder",
105106
"VirtualEthernetBatchBuilder",
107+
"VppBatchBuilder",
106108
]

backend/vyos_builders/interfaces/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .pseudo_ethernet import PseudoEthernetInterfaceBuilderMixin
1919
from .sstpc import SstpcInterfaceBuilderMixin
2020
from .virtual_ethernet import VirtualEthernetInterfaceBuilderMixin
21+
from .vpp import VppInterfaceBuilderMixin
2122

2223
__all__ = [
2324
"EthernetInterfaceBuilderMixin",
@@ -34,4 +35,5 @@
3435
"PseudoEthernetInterfaceBuilderMixin",
3536
"SstpcInterfaceBuilderMixin",
3637
"VirtualEthernetInterfaceBuilderMixin",
38+
"VppInterfaceBuilderMixin",
3739
]

0 commit comments

Comments
 (0)