fix(api): change an existing VLAN membership instead of re-creating it
set_interface() always POSTed to vlans-ports, treating every membership as
new. Moving a port that already carries the VLAN from untagged to tagged is
not a create, though, and the switch says so:
POST vlans-ports {"vlan_id":10,"port_id":"10","port_mode":"POM_TAGGED_STATIC"}
-> 400 {"message":"Association exists"}
Only 409 was handled as "already there"; v7 firmware answers 400. The
membership is its own resource named {vlan_id}-{port_id} and takes a PUT:
PUT vlans-ports/10-10 -> 200
So the exact case anyone hits first failed outright — a port holding VLAN 10
untagged alongside 30/40/50 tagged, with VLAN 10 to become tagged too.
The response body is now carried into both error messages. The ports PUT just
above already did this; here it was dropped, so "Association exists" — which
names the cause outright — never reached the caller. The message read
"vlans-ports POST HTTP 400" and nothing more.
Verified against a 2530-24G-PoEP on REST v7: set_interface("10", trunk
vlan 30), where that membership already exists, now completes and leaves the
port exactly as it was.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
+33
-20
@@ -919,28 +919,41 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
|
||||
mode = config.get("mode")
|
||||
if mode == "trunk":
|
||||
for vid in config.get("trunk_vlans", []):
|
||||
payload = {
|
||||
"vlan_id": vid,
|
||||
"port_id": interface,
|
||||
"port_mode": "POM_TAGGED_STATIC",
|
||||
}
|
||||
resp = self._api.post("vlans-ports", json=payload)
|
||||
if not resp.ok and resp.status_code != 409:
|
||||
raise ConnectionException(
|
||||
f"set_interface({interface}): vlans-ports POST HTTP {resp.status_code}"
|
||||
)
|
||||
self._api_set_port_vlan(interface, vid, "POM_TAGGED_STATIC")
|
||||
elif mode == "access":
|
||||
if "access_vlan" in config:
|
||||
payload = {
|
||||
"vlan_id": config["access_vlan"],
|
||||
"port_id": interface,
|
||||
"port_mode": "POM_UNTAGGED",
|
||||
}
|
||||
resp = self._api.post("vlans-ports", json=payload)
|
||||
if not resp.ok and resp.status_code != 409:
|
||||
raise ConnectionException(
|
||||
f"set_interface({interface}): vlans-ports POST HTTP {resp.status_code}"
|
||||
)
|
||||
self._api_set_port_vlan(interface, config["access_vlan"], "POM_UNTAGGED")
|
||||
|
||||
# Statuses the switch uses to say "that association is already there".
|
||||
# v7 firmware answers 400 with {"message":"Association exists"}; others
|
||||
# use the more conventional 409.
|
||||
_ASSOCIATION_EXISTS = (400, 409)
|
||||
|
||||
def _api_set_port_vlan(self, interface: str, vid: int, port_mode: str) -> None:
|
||||
"""Put *interface* into VLAN *vid* with *port_mode*.
|
||||
|
||||
Creating the membership and changing an existing one are different
|
||||
operations here. A port that already carries the VLAN — untagged, say,
|
||||
while it is meant to become tagged — cannot be POSTed again: the switch
|
||||
refuses the duplicate. The membership is its own resource, named
|
||||
``{vlan_id}-{port_id}``, and changing it is a PUT.
|
||||
"""
|
||||
payload = {"vlan_id": vid, "port_id": interface, "port_mode": port_mode}
|
||||
resp = self._api.post("vlans-ports", json=payload)
|
||||
if resp.ok:
|
||||
return
|
||||
if resp.status_code not in self._ASSOCIATION_EXISTS:
|
||||
raise ConnectionException(
|
||||
f"set_interface({interface}): vlans-ports POST HTTP {resp.status_code}"
|
||||
f" – {resp.text[:200]}"
|
||||
)
|
||||
|
||||
resp = self._api.put(f"vlans-ports/{vid}-{interface}", json=payload)
|
||||
if not resp.ok:
|
||||
raise ConnectionException(
|
||||
f"set_interface({interface}): vlans-ports/{vid}-{interface} PUT HTTP "
|
||||
f"{resp.status_code} – {resp.text[:200]}"
|
||||
)
|
||||
|
||||
def _cli_set_interface(self, interface: str, config: InterfaceConfigDict) -> None:
|
||||
mode = config.get("mode")
|
||||
|
||||
Reference in New Issue
Block a user