From e3bbac0656eed5f6d4e60dc2670589450d54f360 Mon Sep 17 00:00:00 2001 From: Christian Manivong Date: Tue, 18 Aug 2026 16:22:21 +0700 Subject: [PATCH] fix(api): change an existing VLAN membership instead of re-creating it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- napalm_hpe_aruba_procurve.egg-info/PKG-INFO | 142 ++++++++++++++++++ .../SOURCES.txt | 12 ++ .../dependency_links.txt | 1 + .../entry_points.txt | 2 + .../requires.txt | 12 ++ .../top_level.txt | 1 + .../__pycache__/api_client.cpython-312.pyc | Bin 28858 -> 30223 bytes .../__pycache__/parsers.cpython-312.pyc | Bin 33477 -> 34818 bytes .../__pycache__/procurve.cpython-312.pyc | Bin 56947 -> 59999 bytes napalm_procurve/procurve.py | 53 ++++--- ... test_driver.cpython-312-pytest-9.1.1.pyc} | Bin 83173 -> 91887 bytes tests/unit/test_driver.py | 69 +++++++++ 12 files changed, 272 insertions(+), 20 deletions(-) create mode 100644 napalm_hpe_aruba_procurve.egg-info/PKG-INFO create mode 100644 napalm_hpe_aruba_procurve.egg-info/SOURCES.txt create mode 100644 napalm_hpe_aruba_procurve.egg-info/dependency_links.txt create mode 100644 napalm_hpe_aruba_procurve.egg-info/entry_points.txt create mode 100644 napalm_hpe_aruba_procurve.egg-info/requires.txt create mode 100644 napalm_hpe_aruba_procurve.egg-info/top_level.txt rename tests/unit/__pycache__/{test_driver.cpython-312-pytest-9.0.3.pyc => test_driver.cpython-312-pytest-9.1.1.pyc} (69%) diff --git a/napalm_hpe_aruba_procurve.egg-info/PKG-INFO b/napalm_hpe_aruba_procurve.egg-info/PKG-INFO new file mode 100644 index 0000000..40b9e52 --- /dev/null +++ b/napalm_hpe_aruba_procurve.egg-info/PKG-INFO @@ -0,0 +1,142 @@ +Metadata-Version: 2.4 +Name: napalm-hpe-aruba-procurve +Version: 0.2.0 +Summary: NAPALM driver for HPE/Aruba ProCurve switches with auto-detecting transport (REST API, SSH, legacy SSH, Telnet) +Author: Christian Manivong +License: Apache-2.0 +Project-URL: Repository, https://github.com/chrismanivong/napalm-hpe-aruba-procurve +Classifier: Topic :: Utilities +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: MacOS +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +Requires-Dist: napalm>=4.0.0 +Requires-Dist: netmiko>=4.0.0 +Requires-Dist: paramiko>=5.0.0 +Requires-Dist: netaddr +Requires-Dist: requests>=2.25.0 +Requires-Dist: urllib3 +Provides-Extra: dev +Requires-Dist: pytest; extra == "dev" +Requires-Dist: pytest-cov; extra == "dev" +Requires-Dist: black; extra == "dev" +Requires-Dist: ruff; extra == "dev" + +# napalm-hpe-aruba-procurve + +NAPALM driver for **HPE / Aruba ProCurve** switches — from ancient 2520G-8-PoE +to modern 2530/2540 series. + +## Transport auto-detection + +The driver probes transports in order and uses the first one that succeeds: + +| Priority | Transport | Notes | +|----------|-----------|-------| +| 1 | REST API (HTTPS → HTTP, v7 → v6) | Newer 2530/2540 with `rest-interface` enabled | +| 2 | SSH (standard) | hp_procurve netmiko driver | +| 3 | SSH (legacy KEX) | Forces older kex/cipher negotiation for 2520G-8-PoE etc. | +| 4 | Telnet | Oldest devices without SSH or with broken SSH | + +Override the transport with `optional_args={"transport": "ssh_legacy"}` to skip +auto-detection. + +## Switch prerequisites + +### REST API (newer switches, e.g. 2530, 2540) + +``` +web-management ssl +rest-interface +rest-interface session-idle-timeout 120 +``` + +### SSH (all ProCurve) + +``` +crypto key generate ssh rsa +ip ssh +``` + +Telnet is enabled by default on most ProCurve switches. + +## Installation + +```bash +pip install napalm napalm-hpe-aruba-procurve +``` + +Or from source: + +```bash +git clone https://github.com/chrismanivong/napalm-hpe-aruba-procurve +pip install -e napalm-hpe-aruba-procurve/ +``` + +## Quick start + +```python +from napalm import get_network_driver + +Driver = get_network_driver("procurve") + +with Driver( + "10.0.0.1", + "manager", + "secret", + optional_args={ + # "transport": "ssh", # force transport (api/ssh/ssh_legacy/telnet) + # "port": 22, + # "ssl_verify": False, # disable SSL cert check for API + # "api_version": "v7", # API version hint (v3/v6/v7) + # "secret": "enablepassword", # enable password for CLI + }, +) as dev: + print(dev.get_facts()) + print(dev.get_interfaces()) +``` + +## Supported NAPALM methods + +| Method | API | SSH/Telnet | +|--------|-----|------------| +| `open()` | ✅ | ✅ | +| `close()` | ✅ | ✅ | +| `is_alive()` | ✅ | ✅ | +| `get_facts()` | ✅ | ✅ | +| `get_interfaces()` | ✅ | ✅ | +| `get_interfaces_ip()` | ✅ | ✅ | +| `get_arp_table()` | ✅ | ✅ | +| `get_mac_address_table()` | ✅ | ✅ | +| `get_lldp_neighbors()` | ✅ | ✅ | +| `get_lldp_neighbors_detail()` | ✅ | ✅ | +| `get_config()` | ✅ | ✅ | +| `get_ntp_servers()` | ✅ | ✅ | +| `get_environment()` | ❌ | ✅ | +| `get_users()` | ❌ | ✅ | +| `get_snmp_information()` | ❌ | ✅ | +| `ping()` | ✅ | ✅ | +| `cli()` | ✅ | ✅ | +| `load_merge_candidate()` | ❌ | ✅ | +| `load_replace_candidate()` | ❌ | ✅ | +| `compare_config()` | ❌ | ✅ | +| `commit_config()` | ❌ | ✅ | +| `discard_config()` | ❌ | ✅ | +| `rollback()` | ❌ | ✅ | + +## Tested devices + +- HP ProCurve 2520G-8-PoE (J9565A) — SSH legacy / Telnet +- HP ProCurve 2920-48G — SSH +- Aruba 2530-8-PoE+ — SSH + REST API + +## License + +Apache 2.0 diff --git a/napalm_hpe_aruba_procurve.egg-info/SOURCES.txt b/napalm_hpe_aruba_procurve.egg-info/SOURCES.txt new file mode 100644 index 0000000..f15b5c3 --- /dev/null +++ b/napalm_hpe_aruba_procurve.egg-info/SOURCES.txt @@ -0,0 +1,12 @@ +README.md +pyproject.toml +napalm_hpe_aruba_procurve.egg-info/PKG-INFO +napalm_hpe_aruba_procurve.egg-info/SOURCES.txt +napalm_hpe_aruba_procurve.egg-info/dependency_links.txt +napalm_hpe_aruba_procurve.egg-info/entry_points.txt +napalm_hpe_aruba_procurve.egg-info/requires.txt +napalm_hpe_aruba_procurve.egg-info/top_level.txt +napalm_procurve/__init__.py +napalm_procurve/api_client.py +napalm_procurve/parsers.py +napalm_procurve/procurve.py \ No newline at end of file diff --git a/napalm_hpe_aruba_procurve.egg-info/dependency_links.txt b/napalm_hpe_aruba_procurve.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/napalm_hpe_aruba_procurve.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/napalm_hpe_aruba_procurve.egg-info/entry_points.txt b/napalm_hpe_aruba_procurve.egg-info/entry_points.txt new file mode 100644 index 0000000..04560d5 --- /dev/null +++ b/napalm_hpe_aruba_procurve.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[napalm.drivers] +procurve = napalm_procurve:ProcurveDriver diff --git a/napalm_hpe_aruba_procurve.egg-info/requires.txt b/napalm_hpe_aruba_procurve.egg-info/requires.txt new file mode 100644 index 0000000..bcdacea --- /dev/null +++ b/napalm_hpe_aruba_procurve.egg-info/requires.txt @@ -0,0 +1,12 @@ +napalm>=4.0.0 +netmiko>=4.0.0 +paramiko>=5.0.0 +netaddr +requests>=2.25.0 +urllib3 + +[dev] +pytest +pytest-cov +black +ruff diff --git a/napalm_hpe_aruba_procurve.egg-info/top_level.txt b/napalm_hpe_aruba_procurve.egg-info/top_level.txt new file mode 100644 index 0000000..51a2d42 --- /dev/null +++ b/napalm_hpe_aruba_procurve.egg-info/top_level.txt @@ -0,0 +1 @@ +napalm_procurve diff --git a/napalm_procurve/__pycache__/api_client.cpython-312.pyc b/napalm_procurve/__pycache__/api_client.cpython-312.pyc index 73b8162beb4dab1822ad90ab114d3f00c0e81422..76db29ab7ce36bf97f73b24c6a99af0b9993b8be 100644 GIT binary patch delta 3510 zcmai0Yj9h|6~24*vgDU!%a&hC80QB*AfP+&Hmp$0o5A2SwJswq?tb*ek`@ zk>UXPVagOR+m3+-aG=bDgchhKGnsZe&;ZjHrKSe#Tv8})Y0G1Z8$+hhY3VsvaRTj+ zuI7IC?6+sn*?V@+S^elea{tF9?N_O(1`gWAmgdnG;rX=B33-Pl@itP!?0gleWrz4W z>sBH4q}r7#=tYf?29H5d4QYjR;L?Px@n`u`LbkCtRqp8w927M}YM~ScOF%Y>8qqYw z=YoqNUf2L^slsZ%!j=KMLD(p26~k)54*jzKnY#(tjY6HUS@Dr9>cCIJu3Fe~Qsc7g zxq3af_7my_2Pl@qQvrr`@KnN611vk$a)Kp zC8q+58mnOyt8#AmVbKg>zb&@prV8qFJn+zB$%0Nu^YgKkXv(OLy_cSuo_>y88%vF* zj_O(Wy3EYv>s(qijhdoqqsgdiF|P3eBB*2OFy@3gsW@g*N0YC6F@kl4%`^$>sPP=G z3R>(spPC_L-r(8Fu(?DquNen5BjuyU0Es5YOaUHI$cmbx>Fk-}e3H$6UR;=Nx$#k9 z6UVVvi>=liA$M&FYqp{~A@7vFhqLFi>JmHYVFM+x{c>uE28D<>>>dvaqTe}{JutYj zeLy%?8#}bi;W~V*YD;y@J}~$JMrKMkuyH`zG+>|74oJtUx5Q*(n^M~x8|_p2Z*;ZV zcG1Y7hpm>>P3vXtQ86F{sjN}#%E=)Q4Z8!8@j;Q&bg(6>hi(@FvLPtBkBU_C1p`z< zS{n(4edD5><_m;HI^^++Zof|o+x4_u8TN!iU|8mA*5clX8|#=DGaY`~~Z_MN{33b~!V5ZpZA7`P{|K%`-`tHrG5b zG8dQ)%y++$U3MX3Cizlw=CV0YF+%4X7R`>C)@8G0u5GsMq4qQUnbt*f^$ljvyreKo z=K9yo^%vG>FPfWXS}$3PW?Rqb?`i$L**ep@qU9__%UOBL`5Tz2>?VDt!Dv|F40;3m zNnw6GRQ6YmaSXnM`Vqzv0&K)lr~M9!qwFb1dCDUo-a@~dAl`LklL))&=pd(9OZm;@ zNp`Z_Ngij50|cJIWlS_*r@vTj_&6D-jmuzyu|k}u;O zH62>*JE-+8n{TMqzJ%h-Ol~M8OAj`dX)j~{56sc%CT;O=H9oXhJ3>|k7Nt^ zp^VtV;vL~Fwm>j!YwziFmS3OF>>W1N-BDt(6It&b7Q=4Tq+PIJ3D(822RkZnIuDYx z8G0wUl@ugOhiT{qR(*4k^^1T{Pqgc85W z<_QSb!m30S4t6LQkVM;HP?-FBaLZ{2EQ9s~IB6M*E`%J6q}@RCu~UGcWC1Rj9zr;b zP>C>rP=TPF+1o&pGyuvrCu-6b2qZh(RMi{<9_Eo z&V~H?3&j`0OE-7EeskwS_pM9auGhO=3y0hb1MY?HBX6X5pyC=c*bCMo`#)DSMBng* zWCee-JDWM~r^X7vi=4iBlj@@0(p;*#SgHXzp6gT*+bXE7<*$+fI!UB>Q!1H7KVsWE zZy~S7@9w-W$^ItjP9-1pdjgUp6r^E^z6A_jLKsC@2C%E4f;aYUg`yMcJD7A9DHVN& z{ibg{d6s?BSHoxXtfaq^)UjLpi?!R}t7#oO-d~xz6-0YVBC6-vkNZoz(s1w_C>wA9 zrNankS=3)Z>f#UkV_$u?**=8NIa xjwa(w@hZMna@YrfKamOcTCkN*B`hn{NY1jZP!5@9zEE>|Oiv6~I7Fq;{s-J~tv&z% delta 2569 zcmaKud2AHd8Nm0=?#v#(7ZW?aSc9+efv+`~;usr<2?S#on;1;kWxO+M*7Y8|*);)5 zj4G?@K~3#1l0%@>O`|qZ)Dk-Vqe`0usZHHBZQ8_2i-t-iY3^3UgkFgh)bE=$A@q-~ zHNW}Z_rBvD-+SZJe`n9W&az+1%JSIoCl}U_{X2Lu`y<9)2AAMtEg%cctQGbP>r4G= z)|6v7OLc3anvIV~wTC4&2X)!1-?$)DGS&vK*@KfFEl(4*ys&ULh71d8C7M*Iq?JCS ztwL?3TCGV|ActCm?JCRav%WHFtJQUCEhW@4G^Zvm$91T6Q{r%q+jjd;U9Z+_VvX8> z#%nRK6Q4$Wn($d=1;`8Ms?F#oAGOQv#@`%Y@-q3mu>z4DgLuhm$j7)l?`MjK)=M9QIeO&vlv7gceid z+!WKw3sr@ID8@6TR3aIPYG!sMmehDy32DLch@O1P&evMqN+N-drWhWND@i%SS{GB& zkLpP+%0rfk8jmWGnCVQvb)JJ-&Y`!uTGTU82BZ2O)(+RJ-|~8F1*~=^$RR?6Fa|F*u9v1r41=p_?V7Km*u%eO zDG=}AgTJL0mIbtn!@j0o_I3Dv(tg<>d+dNTr8uh#Q86MYUZ$Y4~k1ZKLZ0nVzm&xt-z&iZWk4Q9NV@D-h0Hvc^ z`YkD5h3SqUYd5ZSJT9>3U}jSn+XJs|8fI@mL)YWpgJ>h$(`VuhvVR+1@9Jd3hOhfO zEM#d4N&N`I^h6RtMOC?`>!w{9)p(q|9j5!!3N!6dC1eVb1pk1%Ca8b;#Jx`uTnbMF zRVAt5LMbET8aJV7%VriZ^euBjE+yesIxF!46!nysQVxEK0y+70l9vd7B79_ny*>V1 zOWQ|^BZMd5Tu;f$e~@S*Oea^41aVmC4ZrKz&7y{H+qAHOQpiHOq-nL6&jq)Zkpexn zJn%oF%-=+q_SgvLBD}MGr!<9SpijXEy|qSYhb*+)sVp1>kwB9$J)uL2u1EAB?k8n4 za}#l%462$Q;t@QX@ferj7du~+oaEwy)4juN7T)h&^oVF}-7~Xrv85Q!cV@v&xty*T z=--e5ha0SSrs`0|b9THhq+{!#ux}mA_4x!-f{T5%rTniLlVHBUvXZ(vw5|kZ0`8X@%i>AcP|z+UB0t*cky+ngU-0a z?cqL*W;`%pXFeyMXzTG#RmM}AE@!7t#%t8^6M|I? zRD7m=0=Lzg%p^pE71DFmT?cc)E$mt2N{|a|!PqtOFDAUlphs<$eog&DFsU||TX)DV zYVRd95?qK|+qD3%sNJmGSfO2ECI7pU=^=-s}#1*>`! zO}~rqE<72zzr35oZFgoIri_mC1UEzeSeR`zj*o4%7jGlu`w3q1xDO@Mk%+`bF_;;B zApaU_O*>EGousEiA-oOvH(+aQX!7=Ky2x=Y;UO{>xe~+cf|geHrZZYeC^X;xX{H?{ zS&8Yg;BON)5U7H9ANBSV_7e6Hh6xWMOcvCkC|)A;GRRw1;K0%L83XZp#s=VI!k;lf z5egApTt3EIb414Ac@xQoFwVzguf8pBD_bqK=>zt(EvPz z0zxTaHKCobkzlQT4@td*Gw}T35>{bcIeda;`b5v8{u?%YEo}u=H}YMm*gC&rJ~Ur= zX4efHHkb0DGx;VPg*B;rg->iSmik#e)>P{9Pi@Xo+|2W# JjcA?7{{m$;t&0Ev diff --git a/napalm_procurve/__pycache__/parsers.cpython-312.pyc b/napalm_procurve/__pycache__/parsers.cpython-312.pyc index 1cd1b72a038b1c284b73ec7dd3eed03966e3d5f7..e7329a4d2b0b27ae29a3e1ca4c123985d0e2e4f3 100644 GIT binary patch delta 4318 zcmZ`+dr(tX8qY~`Z$bijLC_$C%R5m(9%!f?e5x%yM1h@?XKJIZXYvR+^uFheeFN3ebH@aJL9x7`+YaO>~=Hq zyXSnb^PTT?udkiZ9v{6l=bEoweLliHY7nF+8)bJ&60B^jbEYCR8b;qQO>2B(;b zd=@rk4T+29jh)27e}eb5Zu9MEOc=vKU!Py;Iz;PHCj(&}!tDr^0CGBjfIs%B zy~MN)c3vcZ8P9UK$dE04Y}=Hr;2m4TWAbGER7oAIo~Oet-&!;l+i3ep`{l(erqU{h zo32@%qp^|L*}YTNlHum-CCkS=XUfORC+t@p1s8Sn`^=H_TMoi?gv^~*|h z^R9LGN&f zt?Y_7gB{E;^3Xmc#R0k)yZO<6a>%lAd#SCD@4t_Yy~$0ttrPu}4b$RkQQ92Unl4dThTJlKcJ>^Mt`Or|PUpOLBIS>j8vI6}s3vj_hLVio1zehq+ zUwBQv0NkL|FHu1?>Md6dQGAyUI$@@G>en>4Mw?q3qe*^8r&p_TlTZV4G#`aCU^Lw# zJLHGS^c2C4@b}1rrjE{JfIB+rO4M3~Pz&Hqp#``uL^yz;b_Nau+JJzQh+=oqMgXso z=E5VvCo@i$;)YX0l`tl!kFSkx)ntJ@&Ypy!t5@;|B~doQPE7~9DijR$!0r|JlfM8M z)=X!?T!Cp~%Y4_Ccg2 zgkx&x$8!oatoG6Fk?zaR(#g&XMK94;loyn%mW{)ku30iidq;X+->vpy%V~L-BXsbcP&*{EI7n0b;FxJG&{z&TsG%k*5^{UMiW1t zw?}8&0e7c60Y2q$btk(~__R~aff{c$C2BTyv!{ykU{1YRRGcQGr~;7C%6+H9i`WAm zTbk_aqyed$-V5~a;+4fq2|330mwe?uiqa7PD4ZNzm*QHlCXpUvhdnj?Nuan%{7KLE z97p}IVNaKrkT83ve9MXvRCy3ptP9FSEeX{z^fSRjpU^;9tq-KuO6D$_tlJQB^of)}P?2c2ze}P#Q0T*KTeX{PV13nv*)1vo0a*!?6jbPdPZ z_;D0d!_tN;{JfhfaQXxiu)3-0it6Ao+&+!) zcChPf-y;8H=hm%gQQzID?@e0}$RG8a>XQfUL;EEAjV{4eHo_Rf<4mY8f=Xzrzm3G% zef1UGOF(`m{(Swfcz#A=e^Va0$cCCWkWb>5nii8PjK-jSUq#t!e_3}!S(5T?^I|484KOy7K z2p8E8&DGpxSbh*++2Ymbs0ph=mYOR)y#nim-nX@(l|ICxTV3Y2k%^G4jZbXds)c&` za=V*P))RBI7IU8fb7g#OtEA`eMBy--+*49ofMq+Y3m5z16H_?5jFM(!YcPwa2C+KFW$4SPM3!T zpDy6OI?!;7O{*`*xfl#k7eXdNG6Sr^w~lke{#^Wi zUkyigvKPf>@(R;Qd&qUxA)PehwDuCZk=>9slaE+U_o|BdQH8fm@X}F53W~{U#Y|x@ zfyo4i)g{w^u}8W+q=3EFUB)c~`d{MTb*CAPr%+NyPq5Tb&Cm(h4B8Jwf{MSVM-n^a zWuEe7-a@(@enc$?Djh_9bF$LBu>SF@OwE9r3#=K6CntEQvxp>9)yq^`iZl+U z1A8^(hCF=`+RN1e?M8fU_z(KxTkq-RlGC#i5C1)yoC%>TQp~LbVqJV=2DEmOi~2?x-VmMFx EU&y3S7ytkO delta 3137 zcmZuzeNa@_6@Pbk-`i!`GPN!YK&#`-K=+*G?giLnjc-g^3Y0x4k9I|vfO@hLP za^ZMJDoccoEZzQ+iPQz5I|-^yX<13Alq_2nNlwO}sF(*OIR$wsB#lo+Ds{<3{EC;@ z{OT3Z$I`56OEQ>Y_KbAHlBie|a~3M16B%eB11_=xmI=2IvtW*;v23!QmW#5S`4wi= z&BJ*vO0p*NmT?P^n=iWz?<|TmZ?_P+1#(f$XT^nno8~3OC@EZ6j}mE7;zmi)qC`S3 zW7ZZ{mY}>?E>+B~C4&?aMN(XgM#@mht^d*V7b)3_M>ON_-Z;kei1U;Hzse|p_6Ota z`2QfYV-q(_2K@%Jj5B&Z>DCkOh@9egjq+yZO4Y^*Pff4EnbJz>oo^kU@WcL zo?ZSAr_~|N30D1b+DS2IBQA`JgZ_|L^H}*7W2S{P+_M?voLc)PTKuA{nU@b1U zwTycMBYtH)JW^aV>H6gG)2_b0n!1|mtyK?J?FhB>_1Rtc;dB8xUD}DP)*U@+Wlv`% zVmS^T96QH++Q4JHh~)0_#`R4+-0zLF?jTutk36r1f|z2&Q8o);6i2}%%(cq~Xr zdd$242Mn-}s~T@cTBWjulO@HIJCXPiaV8KB3UhX0b=@oe7yT~>zq4hg{b>7B+uwJT z{Mu16>nOXxZaXS|T6D1Gj?F$3cQo!+%BqXvW&WCWNt?Ab9Blf~YWpBDX=d!`*s(oN zP293&eY&7m|9ahxqMy63yJu}%zpQ676hDY}>OU^+F(++9CwLpepG2ZsA;02>XODw4PL!O9B80&slg-}8q^wj04?;(0``B-&yG zga_bkRh>k+yf5eMYs*+am}=YBL}=N5T9){g7%zo5Dp6#8BT=&& z?uP@lGi(|X>b8nc<1&lhUH4ye+2YZR)N?Nxv_#{4K~3R<-T{S6w9gD2sLy8`;Y__d zmlDNi2uKvQD?yF#?bY^cQFK8(wpd)AB}N5-;ez2`!5=^AinzusZOs>|uhmts{1ja{YU9n?tHhq@Gt)Jn?z z@ZQc*_B&|r-WvZYF@8hvTR7X@$L_)x-8qOkyV@H*MmE-xo2148V^@}@MxKp=+dxyp z-?&(NGAqGR?Dyw)tzv(M+q){n&rzdHXFaoKwhi9vEoQsme(##PrOGjkEi2rp7{;ec zT^2quY^3Vn0`&G(i};WVkLoA-9wLLc1`607m>Xz{UuF;*yCXWB6fq75azQRU4KUj~tbhaZL%z zv6`l&VUSYMWK^VbQ~CSwi{WyX0skDX5(|+3obL8JZMJ=+Y35^4uhp?J2x%D@3oNn3 zM>X&6-HJ>P!>US;n-?LM`$;=KR$|80GSaYgMkDx(k>j*aRKfxaD=Ig9a&Q-Xrj_DN zbA)$_t5JSa9|~Wwlz#a%9x>J|N@>#L-*8l(j diff --git a/napalm_procurve/__pycache__/procurve.cpython-312.pyc b/napalm_procurve/__pycache__/procurve.cpython-312.pyc index 0f6c04e97b959b2e36808d98d7687d8123495f47..c435f0fcf472d045e20f888b6eda29170fcf4c97 100644 GIT binary patch delta 12839 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 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") diff --git a/tests/unit/__pycache__/test_driver.cpython-312-pytest-9.0.3.pyc b/tests/unit/__pycache__/test_driver.cpython-312-pytest-9.1.1.pyc similarity index 69% rename from tests/unit/__pycache__/test_driver.cpython-312-pytest-9.0.3.pyc rename to tests/unit/__pycache__/test_driver.cpython-312-pytest-9.1.1.pyc index 21ee7cd47a5af657a205ec74ce40b55b59fb0fa1..e59bb8eb3ff834a73c75b314540f202e0519a39f 100644 GIT binary patch delta 17159 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 delta 10378 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 diff --git a/tests/unit/test_driver.py b/tests/unit/test_driver.py index 46956cf..950fb09 100644 --- a/tests/unit/test_driver.py +++ b/tests/unit/test_driver.py @@ -651,3 +651,72 @@ class TestDriverGetVlans: # Ports 1-4 are untagged in VLAN 1 assert pvids["1"] == 1 assert pvids["4"] == 1 + + +# =========================================================================== +# set_interface over the REST API — changing an existing VLAN membership +# =========================================================================== + + +def _api_driver(post_status: int = 400, post_body: str = '{"message":"Association exists"}'): + """Driver on the REST transport with a scripted API client.""" + with patch("napalm_procurve.procurve.ConnectHandler"): + drv = ProcurveDriver(hostname="192.168.0.1", username="manager", password="secret") + drv._transport = "api" + api = MagicMock() + post_resp = MagicMock(ok=post_status < 400, status_code=post_status, text=post_body) + api.post.return_value = post_resp + api.put.return_value = MagicMock(ok=True, status_code=200, text="{}") + drv._api = api + return drv, api + + +class TestApiSetInterfaceExistingMembership: + """A port that already carries the VLAN needs its mode changed, not a new row. + + Reproduced on a 2530-24G-PoEP (REST v7): port 10 holds VLAN 10 untagged + alongside 30/40/50 tagged. Posting the tagged membership answers + `400 {"message":"Association exists"}` — only `409` was treated as + "already there". The resource is `{vlan_id}-{port_id}` and takes a PUT. + """ + + def test_trunk_falls_back_to_put_on_association_exists(self): + drv, api = _api_driver(post_status=400) + drv.set_interface("10", {"mode": "trunk", "trunk_vlans": [10]}) + api.put.assert_called_once() + path = api.put.call_args[0][0] + assert path == "vlans-ports/10-10" + assert api.put.call_args[1]["json"]["port_mode"] == "POM_TAGGED_STATIC" + + def test_access_falls_back_to_put_on_association_exists(self): + drv, api = _api_driver(post_status=400) + drv.set_interface("10", {"mode": "access", "access_vlan": 20}) + assert api.put.call_args[0][0] == "vlans-ports/20-10" + assert api.put.call_args[1]["json"]["port_mode"] == "POM_UNTAGGED" + + def test_conflict_status_also_falls_back(self): + """Other firmware answers 409 for the same situation.""" + drv, api = _api_driver(post_status=409) + drv.set_interface("10", {"mode": "trunk", "trunk_vlans": [10]}) + api.put.assert_called_once() + + def test_a_new_membership_still_uses_post_alone(self): + drv, api = _api_driver(post_status=200) + drv.set_interface("11", {"mode": "trunk", "trunk_vlans": [30]}) + api.post.assert_called_once() + api.put.assert_not_called() + + def test_a_failing_put_reports_the_response_body(self): + """The message used to drop it, so "Association exists" never surfaced.""" + drv, api = _api_driver(post_status=400) + api.put.return_value = MagicMock(ok=False, status_code=403, text='{"message":"denied"}') + with pytest.raises(Exception) as exc: + drv.set_interface("10", {"mode": "trunk", "trunk_vlans": [10]}) + assert "denied" in str(exc.value) + + def test_an_unrelated_post_failure_reports_the_response_body(self): + drv, api = _api_driver(post_status=500, post_body='{"message":"boom"}') + with pytest.raises(Exception) as exc: + drv.set_interface("10", {"mode": "trunk", "trunk_vlans": [10]}) + assert "boom" in str(exc.value) + api.put.assert_not_called()