From 3651818c46ea9e2ce9d00a1323405f3c37335749 Mon Sep 17 00:00:00 2001 From: Sergey Nikitin Date: Sat, 15 Oct 2022 10:54:49 +0400 Subject: [PATCH 1/3] Added LACP replies for LAG ports Related to #2882 Related to #3439 --- .../floodlightkilda.properties.tmpl | 5 +- confd/vars/main.yaml | 3 + .../023-add-lacp-reply-into-lag-class.yaml | 22 ++ docker/db-migration/migrations/root.yaml | 3 + .../lag/create/create-lag-port-fsm.png | Bin 40206 -> 51670 bytes .../lag/create/create-lag-port-fsm.puml | 7 +- .../lag/create/h&s-create-lag-port.png | Bin 51152 -> 60357 bytes .../lag/create/h&s-create-lag-port.puml | 12 +- .../lag/delete/delete-lag-port-fsm.png | Bin 36861 -> 57096 bytes .../lag/delete/delete-lag-port-fsm.puml | 17 +- .../lag/delete/h&s-delete-lag-port.png | Bin 51003 -> 59830 bytes .../lag/delete/h&s-delete-lag-port.puml | 14 +- docs/design/multi-table-pipelines/cookies.md | 2 + .../api/request/rulemanager/OfCommand.java | 29 ++ .../org/openkilda/floodlight/KildaCore.java | 2 + .../openkilda/floodlight/KildaCoreConfig.java | 24 +- .../floodlight/service/lacp/LacpService.java | 242 ++++++++++++++++ .../floodlight/shared/packet/Lacp.java | 268 ++++++++++++++++++ .../shared/packet/SlowProtocols.java | 80 ++++++ .../floodlightkilda.properties.example | 5 +- .../service/lacp/LacpServiceTest.java | 119 ++++++++ .../floodlight/service/lacp/LacpTest.java | 116 ++++++++ .../org/openkilda/model/LagLogicalPort.java | 15 +- .../java/org/openkilda/model/MacAddress.java | 1 + .../java/org/openkilda/model/MeterId.java | 1 + .../openkilda/model/cookie/CookieBase.java | 1 + .../model/cookie/PortColourCookie.java | 3 +- .../openkilda/model/cookie/ServiceCookie.java | 1 + .../LagLogicalPortRepository.java | 5 + .../ferma/frames/LagLogicalPortFrame.java | 9 + .../FermaLagLogicalPortRepository.java | 21 ++ .../repositories/FermaLagLogicalPortTest.java | 89 ++++-- .../repositories/FermaPhysicalPortTest.java | 2 +- .../nbtopology/response/LagPortDto.java | 5 +- .../wfm/share/mappers/LagPortMapperTest.java | 4 +- .../services/SwitchOperationsServiceTest.java | 3 +- .../dto/v2/switches/LagPortRequest.java | 5 + .../dto/v2/switches/LagPortResponse.java | 1 + .../service/impl/SwitchServiceImpl.java | 9 +- .../converter/LagPortMapperTest.java | 8 +- .../openkilda/rulemanager/ProtoConstants.java | 1 + .../org/openkilda/rulemanager/Constants.java | 2 + .../openkilda/rulemanager/DataAdapter.java | 4 + .../openkilda/rulemanager/RuleManager.java | 2 + .../rulemanager/RuleManagerConfig.java | 14 + .../rulemanager/RuleManagerImpl.java | 38 ++- .../adapter/InMemoryDataAdapter.java | 13 + .../adapter/PersistenceDataAdapter.java | 14 + .../factory/ServiceRulesGeneratorFactory.java | 20 ++ .../DropSlowProtocolsLoopRuleGenerator.java | 67 +++++ .../service/lacp/LacpReplyRuleGenerator.java | 114 ++++++++ .../RuleManagerServiceRulesTest.java | 33 ++- .../java/org/openkilda/rulemanager/Utils.java | 22 +- ...ropSlowProtocolsLoopRuleGeneratorTest.java | 76 +++++ .../lacp/LacpReplyRuleGeneratorTest.java | 134 +++++++++ .../stats/service/MeterStatsHandler.java | 11 +- .../topology/stats/service/TagsFormatter.java | 6 +- .../stats/service/MeterStatsHandlerTest.java | 33 +++ .../request/CreateLagPortRequest.java | 1 + .../request/UpdateLagPortRequest.java | 1 + .../swmanager/response/LagPortResponse.java | 1 + .../request/CreateLagPortRequestTest.java | 2 +- .../response/LagPortResponseTest.java | 2 +- .../bolt/HeavyOperationBolt.java | 2 + .../switchmanager/bolt/SwitchManagerHub.java | 13 +- .../switchmanager/fsm/CreateLagPortFsm.java | 44 ++- .../switchmanager/fsm/DeleteLagPortFsm.java | 52 +++- .../switchmanager/fsm/SwitchSyncFsm.java | 16 +- .../switchmanager/model/LagRollbackData.java | 30 ++ .../service/CreateLagPortService.java | 28 +- .../service/DeleteLagPortService.java | 25 +- .../service/LagPortOperationService.java | 68 +++-- .../service/SwitchRuleService.java | 21 +- .../service/UpdateLagPortService.java | 26 +- .../configs/LagPortOperationConfig.java | 12 +- .../service/handler/LagPortUpdateHandler.java | 64 ++++- .../mappers/LogicalPortMapperTest.java | 2 +- .../mappers/ValidationMapperTest.java | 2 +- .../service/UpdateLagPortServiceTest.java | 20 +- .../handler/LagPortUpdateHandlerTest.java | 26 +- .../impl/ValidationServiceImplTest.java | 6 +- .../openkilda/testing/tools/KafkaUtils.java | 28 +- 82 files changed, 2026 insertions(+), 223 deletions(-) create mode 100644 docker/db-migration/migrations/023-add-lacp-reply-into-lag-class.yaml create mode 100644 src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/service/lacp/LacpService.java create mode 100644 src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/Lacp.java create mode 100644 src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/SlowProtocols.java create mode 100644 src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpServiceTest.java create mode 100644 src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpTest.java create mode 100644 src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGenerator.java create mode 100644 src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGenerator.java create mode 100644 src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGeneratorTest.java create mode 100644 src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGeneratorTest.java create mode 100644 src-java/stats-topology/stats-storm-topology/src/test/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandlerTest.java create mode 100644 src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/model/LagRollbackData.java diff --git a/confd/templates/floodlight-modules/floodlightkilda.properties.tmpl b/confd/templates/floodlight-modules/floodlightkilda.properties.tmpl index 3d0cfeef714..d4ff63af289 100644 --- a/confd/templates/floodlight-modules/floodlightkilda.properties.tmpl +++ b/confd/templates/floodlight-modules/floodlightkilda.properties.tmpl @@ -80,8 +80,9 @@ org.openkilda.floodlight.pathverification.PathVerificationService.hmac256-secret org.openkilda.floodlight.pathverification.PathVerificationService.verification-bcast-packet-dst={{ getv "/kilda_floodlight_broadcast_mac_address" }} org.openkilda.floodlight.KildaCore.flow-ping-magic-src-mac-address={{ getv "/kilda_floodlight_flow_ping_magic_src_mac_address" }} org.openkilda.floodlight.KildaCore.server42-flow-rtt-udp-port-offset={{ getv "/kilda_floodlight_server42_flow_rtt_udp_port_offset" }} -org.openkilda.floodlight.KildaCore.server42-isl-rtt-udp-port-offset={{ getv "/kilda_floodlight_server42_isl_rtt_udp_port_offset" }} -org.openkilda.floodlight.KildaCore.server42-isl-rtt-magic-mac-address={{ getv "/kilda_floodlight_server42_isl_rtt_magic_mac_address" }} +org.openkilda.floodlight.KildaCore.lacp-system-id={{ getv "/kilda_floodlight_lacp_system_id" }} +org.openkilda.floodlight.KildaCore.lacp-system-priority={{ getv "/kilda_floodlight_lacp_system_priority" }} +org.openkilda.floodlight.KildaCore.lacp-port-priority={{ getv "/kilda_floodlight_lacp_port_priority" }} org.openkilda.floodlight.switchmanager.SwitchManager.environment-naming-prefix={{ getv "/kilda_environment_naming_prefix" }} org.openkilda.floodlight.switchmanager.SwitchManager.connect-mode=AUTO org.openkilda.floodlight.switchmanager.SwitchManager.broadcast-rate-limit=200 diff --git a/confd/vars/main.yaml b/confd/vars/main.yaml index a743f47151f..a4d4dd233a6 100644 --- a/confd/vars/main.yaml +++ b/confd/vars/main.yaml @@ -51,6 +51,9 @@ kilda_floodlight_flow_ping_magic_src_mac_address: "00:26:E1:FF:FF:FE" kilda_floodlight_server42_flow_rtt_udp_port_offset: 5000 kilda_floodlight_server42_isl_rtt_udp_port_offset: 10000 kilda_floodlight_server42_isl_rtt_magic_mac_address: "00:26:E1:FF:FF:FD" +kilda_floodlight_lacp_system_id: "00:00:00:00:00:01" +kilda_floodlight_lacp_system_priority: 1 +kilda_floodlight_lacp_port_priority: 1 kilda_floodlight_ovs_meters_enabled: true diff --git a/docker/db-migration/migrations/023-add-lacp-reply-into-lag-class.yaml b/docker/db-migration/migrations/023-add-lacp-reply-into-lag-class.yaml new file mode 100644 index 00000000000..f4985300d44 --- /dev/null +++ b/docker/db-migration/migrations/023-add-lacp-reply-into-lag-class.yaml @@ -0,0 +1,22 @@ +databaseChangeLog: + - changeSet: + id: tag + author: snikitin + changes: + - tagDatabase: + tag: 023-add-lacp-reply-into-lag-class + + - changeSet: + id: add_lacp_reply_into_lag_class + author: snikitin + changes: + - sql: "CREATE PROPERTY lag_logical_port.lacp_reply IF NOT EXISTS BOOLEAN" + - sql: "UPDATE lag_logical_port SET lacp_reply = true" + - sql: "CREATE INDEX lag_logical_port.lacp_reply NOTUNIQUE_HASH_INDEX" + - sql: "DROP INDEX lag_logical_port_unique" + - sql: "CREATE INDEX lag_logical_port_unique on lag_logical_port (switch_id, logical_port_number, lacp_reply) UNIQUE_HASH_INDEX" + rollback: + - sql: "DROP INDEX lag_logical_port.lacp_reply" + - sql: "DROP INDEX lag_logical_port_unique" + - sql: "CREATE INDEX lag_logical_port_unique on lag_logical_port (switch_id, logical_port_number) UNIQUE_HASH_INDEX" + - sql: "DROP PROPERTY lag_logical_port.lacp_reply IF EXISTS" diff --git a/docker/db-migration/migrations/root.yaml b/docker/db-migration/migrations/root.yaml index c362ca380b0..279a88b027b 100644 --- a/docker/db-migration/migrations/root.yaml +++ b/docker/db-migration/migrations/root.yaml @@ -78,3 +78,6 @@ databaseChangeLog: - include: relativeToChangelogFile: true file: 022-add-port-class.yaml + - include: + relativeToChangelogFile: true + file: 023-add-lacp-reply-into-lag-class.yaml diff --git a/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.png b/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.png index a3402d6cd9613d32457d2e164230b7e1995655af..f929c543aeadd0ff52dc15c887eb92a4d0320873 100644 GIT binary patch literal 51670 zcmeFZc{r8*`!2jxnlvhu6cv?(k~uUfW0ZLwip(i8j}4NPc?e0FXJRcvhBTp&VHuVT z4U~{FX5QzmXZZepd%wqf?7jbbkK^r+r>8aC_vik6uHn4S>%5lVStXgZt65i*NTjuL zveGIf($a?{(vr6;m*ABh?KQ{n7x&duT30XFUUsoIy?B)*V|vNd&iJaS3CDRC4)d#5 zFN^c@U$!>Bbk)Jen(uR*{r;P*m}Q8rqTbL0CTXVt52Xqqr7UW_rHnK&$XRdDCfrJuj&JYH+6oO_(Ui+#f@ zwSe5q$+_G<*QGhsSBAc4527nt_)WU?YkOtYs(9D;5`qa=dc=ZRtj%%b-l11@qzT!UtdP z@AzD{waj*-jjNx8ePpjjzqO5Aqy#WN+x7!XvV~ zW3pMPbfdB(7jCpBxY*{Rz3>HjvkwSClcct>J|=+lH1vWH~mRLM=u zj`M?5wQp_DE{uGkhrq_hX zwU6i8Z>F)wMnx7_oLDQ)wYO<}iHWw~vYn@t`GamTiR4X^lRmEIY%tjA;;v}6uyExFg+gg*Im>%ugI|`K zd;;aPx=m8*eszVjr94-5Pf(It;?;P5s4-==Xt?oi)K%x6YTvyw*hZRH?&plVy=bg~ z-02e+1?>CAXLko4Zv85*StoYbEPGP1PBE`@1?+0g&KMg0B8l#a=OzAm-JAh`-Qb|X zgey317vLWUSN`w6m6@TPf3ZE!lt+i0s^h#5e>zUe1ny8zFTB(lE#)!&mcM*(qMP`rQe zIh88pH9t$w!0i6fho#GNW@zb(b^8W1($)IAO5JoWoH%hJO(lsuk*!~1d5;uC!6Rf2 z1UG2Jr#~d$TDRTO;LhEp$nWfM@QcsF0$(lQ75(!pHgts(LL`X=;JB#L16bA>5Q73P|F;H z(o5ZCydwCUb@S%Lq1$DbetanEHN&n^P7m=lvHBH|2A4H3dpl?c6nuUY82LTFUTPaF zYkKMKl8d#hSyy{&n6i>Q=ccFGpYt{~CYtohhwqoG3-#V7((O9Ho3(#R>#Yxe=~&3@ zYwPOavzOZQUb%O=PmNT6?|mB?851I}5Vr5JP>Vu=;}Ffuv;CyU%mnoouS(+W2!W+b zmky^0KN6hy(RoDIj?KCu-lygGuKuXQX5`15Hg6*os^7ekIUV|)f&W<${0bKlba~FnrH8CD zEjpKQI`2b)jcyj%rlY{RddZR{WaesR9sm2nHnbOq<{!U0B4^>XFjx2?OiJf#=w9Xd z@`cF-a_{pSD-^i-`08H0I(13d{C$PHzTAtQJ9pZtx{ZzmN76kIcaVSE&G?9-9-oj< zAA7_JXEt8i9xgKWg6{&Rebe1@EE88gS5R2YL zWnd(qVamT)+sHo=MCyEcyjLO@My&c zq&sjcOKtg?7n7Af4EbE;E4bU0E?w#@R?i67A*nNP)b5My`t|Fx8cvXHy30x(M+1+} zzZZuUOnWWN#1%ZV?Uu^gzwzv)zUt67?wzfMUiu|za$EN?O`G+QNtS%Q-hJ%%4{Mft zdSt!0PS5br?T1)WG0c1>&8B*{jH*^Z!HIyoIXSlD->DP@>6azLWmaX`(j5b>IV^m7 zqHt_6`@XQq4XakI`p+d#c6%B6pH<$=qm}!}(f;+_tt$6!-+tdE<380JIbI&qUF@JR zaA7B{Y^pDYRy#K{X%$+0Psxr5ddQ`Ty)NOLd#fyFKqEsYZ z$5IQ*w~Jp5GB&G!nB_44qh$C;Ndj6!wg_Q$sa0$Rl1K5yuHN=?W*u%1;=cBb_6 z>wBRw|Ex@<^rh;OrhD5&Rx|BS^x-^pk5!25&_T9Hpp1Q|4$MxCGG4~UrBk$S7x*x+ ztXsc6GN!DoOy}!Dq@r$t`|%TjJ0#z|E;(vawZYI;y>CvxvnXs^49GeH_P zfx+Ajj?b^t)Alp=aJE@QsXxXWDhV+$F(y)@cysA@TQ!BmOSn>!e0ZMZhsgKu&$jO< z;+C=g{qv(hbc?EgLE0rhp9YmYHXDVA18HlMJSLQO=j07cZu=w@hLx)Xp}OlAZ7JpA07%?gruaTEYpy?$N%i-34dnZvbl+l zZ6cTM=cG0?eQA4gfh$90VQj(qB&atafp88*Y2DySLcY|EbI9#jka- zlpNI!dzBs>%&{c%FW5ObMb;`z^%~ZA=dDGQ#8J9-J##S)|3r13p1P;^M96i|L?w~;WXIx1ex{s)YMuw(X*a4dF0es zlEfh*|7(m5DZo`xyf!Gjm*?!P5;F5%%pY8 zIBpOdV3If4<)(ZuyPv}PM0Z@)F7W9r!y9UNRZDL*-0n|DJ*L7j#vNz$VI~wnJwCpuKPZl>d0%2anvRQU*{>8-o9?eve1mEIkufj&T6e6C%xaCvBnJpsI zidR z>+xH(reYx1EK6#7h?PS4ey*dqWU~_K;TFR9be(&{HRU7uc#AX;VRV)UZ6z+&R_WC3 zp_y=c?c$ZF0^aZbm^DCWf_^9muE4vrYR#g3noN)NW39n)mUPGVz>-NnR|W?MA3uH^ zD}6X-a{3-?@25|@j#5u7uZ}pNUp)5dmH+o!OOyf$H;%r3mV&DKwnjD3;J z`PsTH_xL}zG5Yk<&r(Tn)7GuP^>skDY4nGilFz)25y_!h3g{QvC0+hliA}=2w~1bU z_YLk{(8ec{Gmv-KS*P_Ov`Av+bYV9N6_3RN}SW#JZ zvWZ_c#~zHQOLyo{EQgRJ;DDvB<0HR*`Q5um&4^a>rFfe&q!5w4=h~RGu07!9)JX5) zg>uuhi|5l%U6a`rVW9zD1JiDq4 z4;e--ZdC+o$7-@2&c+YHbQm`8yF5K+QTi&3mK(9r<|CQ#V+kWM8h{ezVr_JYfXJl} zTiDnn5Rl*$F@`!u^1L^e)6*m24tyxG@4I*5^<8dku;a&Tj5epRnKP5awp}51T5oUv zz0=Et&=-`ew2ifz5^ z2`KWjVp$i3o{jDQ{kzh-6%GHF3pse2T79R<1h(MB z$&=L7Z|2}f($&%g?7mcGXy&B5t=#hGA?xCfmYX;1651~wU4L{z;=q9e92}1jKj7C- z?RxJN$9Z2_Txt(UmfbP|Y6#r$w*iRRk70T(`2`oQpoyLYcDx$q_++ z?Y3%)Qqp+m%7%spFbhr&4r^Dg3^Jvx*+Rbc^*m3+YH&uGC}~mH5cws7 zPUbXDpMz=iYyCohN?h|wi%6w7DMzJF@o;RBFu9* zI{*9g?_Zz!4W6Z5lq=cD#6&TAe%+!eS-WiA^XoTOtS5>P)P%HRhYX#iY#WHh3%+{& zTIqr4m60#iZWDv&>`fyO78gK)@m0^fi#q)MwAqIUUR?v1-f*Q@ai3ea(uFD>6Jb}j zDPa4N@6BnOSXc^;U);h?&QR9paUrNr%?y`QYdw}AOZcORUE(M5op z2813fU5c_egZ{HXlp-A+oqC2wtfZUci4)g#mJjYA)}?m9uL(xZNHR>jOB9)m>Pb)b@DtLE{uuaX^)OSQIH!Syb_})A< z-fD=OnJr!{E-^9j_>JWhi>B8m0%<}Y%FEp*e`p%GB!Nc+S8&$KyOVIY%SU(a{D=a= z>uapjrLJdbEkA?Uk`QS@iH-a7m-WnSGKwRvXEuq?IAI5I@0XOZmFw3JHzdd)J*;P! z%53q@Bn&{5^7QFbEEJWnAh@`!oi<13##C{FukUS}Y+LxulDiz?qQd3W>C>)mZm8aM zcsaJm$Hz;}{(Nz3?KW|$7sWf42o(?1oSmI-EMI%^-TlYRK^wttI5|17i`j8Ta=+L? z9A_N?+a5lAh!fLbsKQ;J(k)%yoMY%!|4`C$Z7z`#D*9)!_d-@Jde|SGicM|LT=IQ= zeap*_E_|TVzK!T}nGbZ*rAwE#30t3Ka6XTU-)mvsz`y`WLDkCp3~?VNlYy435G$u4 zS`u@R3-&}kONT-r5c-Pg#a(&qVMs**oy{E7URha*tU^svh_q_WP7|t#CO(joYu%oQ zmkCp(*OEDZ{?nK0(9X`zLYt1{7E%U*zV6z>hmaYLc+Ef4@eW5?YHex)%NFePC zZzf_L%SEnylIG{nxqW*jg%oozN|D|D=e5GZ$@Y9RDr-C>kH@e1GrdqU;1=%TJ+;|@mu{{0&@`}jaB|Di+YvUX-v#|i*fEnmy} z(}wBKA)h^e{ydiQd}Ml^{Lu}v5HQ|+C;{ORQ? zC#Ok(FI=YZ<;$1KQd5a=QD70vwTo3^#YeDIsQ|X{_j3%FHz2tY2&2gUYx9zz-R7o4U>dnHFId>^)h>`$mcTR9<9c z>r1~))Fnadh+BM)lJanc(eoi3G8?S~qs^;ZFg7_^?lk;RXXybVAF%nE#DYx)@nTrw zcqYqqxw3LSutjQCZV^sIq}$ydWyq|in8`|FNe2-@I0x{?eZ@VL2)Ww@6QBEPW3Ut7 zT65SJve)zzDO>c`&6_s?I2?X_5Th>WspnrjgD{_bCPpJeqqerz1{Jf8o4fn7oSYng ze}&*(r(nzu^Rq4%&%h4&F>)C>lcM(_Zmv!nG^L$;;k5u7ye z$v`=8>g#jhcr^B>BDP(6sb?P}rSWU$B?6Ui+_+K4tBXPru?utp21gLoi}D^LqS|0= zwFyw&RsoaSK7~IxX?6DxozxBR^Q(^Fjd|Jr^XFylreIiU3up_EpO(k0Ts zRc=Z4RJj`mjrO>s&=zdYm7hJ&-P6|u68Wl)QdK2uYisNG*z#jHC+$1SmJi@f8HdtT zQyhPMh(cXZb5S0o`+|)3?c28j;GJDu<_@oJXff!73&MxuJg3uiNb{h`Nx6m}KYm=x zD)_!TfWkpF-^R?mr$M7c{`eKpts-5{3Hl|D;QVx+IX~$#NKx7zpXE^uTg%YM+M{^? z5i{v9VK1@buq3c{E>@rr{(IGK#f36x#` zLZhPi%+!?Rmd%cPgoO)WOvvzR3>7DdD@sm})*t1{n*aG?z2(le7N-bW*=wYFFTn`{ zE zgW;F}5s-ht(w-I-!Sjyuk|u|JZ{2E1J@*-WtttbcA<4+L;8N%QB1ga>OFC!s?lKQ* zhUQV1kwhKuCL$>eRw*ByQ%g8~4>>c9zNcFwTTdID;SHOCmL_s`}#}}4#N`!G&3H%dc zV<%$sDn7R(Zh*&gcDOl%Ek34EZ|Pco`G^s|tN)vYQ%uV!Gdx3`}GyV$(s!75@O zRpA#%V?gyP3>BMZKKin=id=ff#aDNiu)J6VKWwZms8~z7Do)tNb}_p|+UgXr+N8EB zzfF6AI4IWb0u++Tj?n5aKYo;XEqG#!2|l5%^6^Pu%jVQ4Xi?$)0w5eA%jVl&6$1~5 z9F);Pv5NdViW+T)OQ1`WPFuZ5v1zp;$8)wK-lWo3SQ70Ra8QsiuKmCY4UdlB zfNxVsZ8c!8NHIh*&d9L3dKIzyIDy;)cT;U8WMyT)p=tz%o1dStr7Snuh*){$^Xs&% zgZr?Eo-1p%96V-Gg!93!9l*AA!-hu%mA4l^r;89}UoV-`efQ2COBy*X(HAK?YJADyD;%G` zzJ70tj`v3*vuy3_?M;&DK)M&QZu^Ng;*1kZMcMC}8F2jCHa>qovx$*W1zlEN8qwMU zHYVKu2%Y2U9T`AC=qjr)93+Tmjti&_-zUm#@+*LU+dDZKmbn+vmgXwpu?-8XqiCzq zC&F%W{Na)Jt0v!-?m#V@qg$vnMAw#qkCg+o>TSGaO_=-g1A0$$u|f4ouV1}N@>-Y& zuuyHK8+>h*Q@V#v)5ho8;sd--J(o}u&J(kjh6amfj=>z_P~g3L>TXLNPaDJ#T>4+1 z>}O(R{Pg2THWv*+r|9X^XP~&BonctP$i&2zQ+w&d)5pw4n~58{uHAC5f=UG*OGnnm zR_?TIs@zyTa7b##+z5qZ3!mQ8pS2?0is%(H_MAme@*x@$z^UtTv#jTS)%opV+^Q%} zkH}0Z7XTrew`RxERwp322!|j7q9uoVnNNqjPf)OVnP@cn8*dp7Qi^n!u%ZEcjRtP-ExxA0R(kcN!P1LvC|Uf%An>p zpyqskXA=H}-3^`1VG+D2Pj-9217`H;5yBv#Zybo2oh zQk7>(M&_vcLN4VPmS;=trTGFGW2b&8JFP`@(rUm-wPtE3NcArD-UJBGe)!_uX|lNp z(Z@zm%g7?Cz6u>cHzLnSwJ-H<3W}U`oJq^uicfAoqG99n8G*7z0Q4aoZ9HlR>A`6| zJ-uVcyr;jIvx6JdrC25vRQ`g;WIRP^0obLcxQJfQH>-yppqywLjrQ^C7n2JMoz9*O z()iw(q)=c2R&~E_fpwge2Z4QZQZ*gmFRto?Gzt&6Mx0-hE}vmpW@h{VcmnS27xpJE z5L{J(RG8gTg3=!yANL>{MLGV!9k6A@h?>5%xp(&$_-u~M&CZT}$H6A`Vll4%m9s`Q zw${z54R^O5M&nr3aYMbt=(~)Zvf>MG*5%XnY$=-^QJ9%6({+i+)_U>@y8=82G9vd5 z(C%@w|M z_D#rY!f3pQ?GjzbDwwL4rZRoz?UNBWC}Ss%xWr*V_;SxNinRb#5xmGF@(I0%RP)|< zS1u=C>J;xa@V|e5OVXE>6s7Q#$$8cgNG^dN;Gp# zeXoB&xOni%lI2(7u<}$rV4rbI)Ua>vZ8MV(c*S@d8jJCeJz3^f#_z&ZYW<_h!bT`f z+viJLx#YvDqB4P0TrbFTqcIMYNYv*mC^HfLsZ|@)D z(5RZtiA*+4tcTUp`c8$uS}R-B3Y!h`ux?D0gHeyY4pX^TU}wS^2wB7UlLVVzpI%XV z`7XB7pfX89B^Yvvq7d{^YfzZhg{ldn6Ii#*P7JWm@MhmZK_Cx1y>Nv~FU4YZD zBf9CR+lRE}ag}#wR0k`rN&~bczgAS&nd7xSro+Ms+N19fjt0mNd&tgDtFg`0FDWR< zyc(pr5$P;*D)L~(xi%Xz)5w<4NOwyfHZYR8tK+k7Sc1OB>cZEnL6yNr4GhjQC@nsA zZ3&37mP^UeRK@CrD`Zp(jMpcl(?IP2sMVHe^V6j4mSW4oM=qEIZns(Z{W%cG(8^f+ zWP>A4k}(h49AmkHmsXSO%E0C$Dbw*#(k&Yq&czA!qq2PvC$0=O>rjI9J>*3R${!`;?umx&w6cuXb z5#e0EEH`({@|bEx*;QcM%~!(DDm$9$$PC-0i9w=xx6Ed}Y)(YDblF#ct)rvY5 zei%WBr>2IDdq{&~wE4;M1%0x4Fl{x##u3Kx6eQeSgA98(zo8Vc04fU0mdti!B8p0l zIpkn^)t6~Pnffa3UwDu}8oT08`R_RseLshb^NPGLyVvYFTR9$DviLAC)=J1d{ zLpI|weh|+iV`JpYTLlNBBxQ76m_&>#mr93nlSabaub!Tob=d@&a6f?9qbFa@-2hvF zOpqV{%d1ESP@ARw^(ty3DLN858xrj4%E1#~ioQNnjpuykw~cYpzVW5o1-V>*@5BZhV}-vL1rk1jc)~4G zv;trp7zuYI)Al2dgI%R1wAB?GOAT{HTml?8>oj-ms?xo{!ax`_m{ZN>7GG5C;ozxV$G@l1hxWre&Xzmbh4?%+>1H6~V^jMMeulTZT7Yc&J$y;lRT*uQj z#N8&Wk@$GjQaN5k@afY!I*#nyCu`Uc)aWuVg$G~UlwEHVhL9ne_k=nyG_T6A z)$C)ej(z_FR(4x# z8iNU=H5K<#+e7ORh@&h-zVOo1)d+(k`tO$O}ow1o)g_HUYgkXVLoQFo# z-SXd`{E2pt2tq*_@yL#7smsE8W)^>lOCHf3CLtaEz zKK9DBy3!jTOGn|R3JoPhsjV5BkOf_cC~SGK20-cM9VVWdjX{x$ptc%mhsiQg^Qq2X z<}xI#P4Z1YkH^TXE6K62uxS7M^-E3+l20?rXtSP<@2ioer*|X)LWa8?|IrTk1Jam~ z4UKH!LrWTb;En_{ofR6}IH_jaLSL=Ws`0%e)w6r!UoL|Dg-{vl=Yn3PK<%>S%WKv= z0biBH8G28#&Cp{!T(JV&ufR^Z6nfyaj=TneJOWMIkPTpEV9Am#{yXRw_)6e z5frmHgHN4~kPK-aNAV8MPE&b3_wY&v+hfUt*2a^)Kjp7aq0RE;XU&fob_0e6;Vin| z)@lz)l)xaz*u}5PI;t`&G8eXdKr4gYx=LBX_W$WiMj#___3NLkB zY|`r0q$eX~jW|b?I)BM6Ca?4FU2%6XAafu8YffggGCDgtbUkA~gvHq%T$monxjR9= zY{VW7e}fI`veaG)KYm8Nu_)c-Dfmeg`%OkJ`7eserRTDOKDR$QzH~16 zP#r zt)btx$6q(2=@!*Krf6(9_)|4IQ-6^cW%5Q67jiaDw-Pm9>&(FV{eB+k#O&80o5Ke= zMfbl*=E{08Qnljl)b93IuX+)QTHToW4fM(*B{X_j3{iCx#Z)nI1(d51QN73W58amOZFX^Ve zg?DbooG;6q$8G;6m%l+_o+{0*=9_&_1zprgjG)D?jz=QjzXVP6H_4P2fq2xOLqI|f z4D~@*?t2o@Pz?vZaHR5tmYTS3o5#>k`KtFv6pp!=kSPZr-Q4MNUv9HiXE7!533zbJ z0i8CTPRQBvlY$)@%Vm<^d8#FToe%dt1DKR}1y~FCJY@Hy^4K)>{3v=u?Ew%o+`eZN zbbNb**WX8D=UWo;Q1uOsa&2J!Ia~VvVT+Fy$>Y`+KF*#QcpReiSHip#7AH3iFEydP z?-o5*WdembR6L5pAuJTr(N$6>Z|vioOpNYQthTM+%%|rJ+z6Oh7tYfPM0BP)R2zDG zx3OMTo)>xQS+I; z7+Xs&hNYHhuk;NM4}bi)lb>KfE%TmI#BJKbw$_COOV*6ZoO1nP*lVm>?m35k+knV; z56>Yrw z`gnS?h4Sr!<)9cd&!dG`6IwD6Sp)8udnlT`XPfHCSIsezb%zCSf9j1iY+2Y5t%4%j zPYXCflZQv#f9JLwpNYxrHRp+m1Q!nq&A=*hih`n^)@<&wxHAw8U%h%2 z*@G){P)HT$ico2_-NZ`Ob4S$)GBvxMIt!Fam9k5m^Uz-444s%Xoztbl7yFK+p8aD< z$qvV_7Zw&yiRQKUv2Z4eKZ*wyp%v0;wdv^Vc)5s}ngnM4`YVHI6_hme zayx(g_)${JhSnx9kfXGKuS@-*3$Md?G?n}Z5y~TfXM2b>|5X7J|# zOL-^dw<}sI^UvsfwbfZ4J{Hs&mal>`d#|+a4FU>ZL0lm^fc3xjgub(9WG8S3+5B9T z5e_$tqI=&Ojr$hF@u=o}$=N;0SJ$1tgxv@6tgc%tyCWn@*Ynin%eh_qHp+XRgK%lT zX7(&X?pNrogBKwXDnuT=rIhX|F063%0#RquD0PA623rOt?$I-|b5ROUWK z_Rm+SGR(4+ma_}9YEmfRjba$u??+h zXfZ-~Gy?6OO5ET_$g(Rglt6I?FQTTLnmPH$`SgJ;LbU^9k! zCe>&g?O9T111Y!VCbl4nm@^M0rZBtF4c48SA|Z2Zap|Nj5t8-oh=OBdV-JL^Gzclk zQtPUR%9|tzjl-6R*!p&~_z+0OMn>$O9yNX;J2)$N{4@%(id?qybzK0D0L1Pd9trXB z&8$Wq>#js|%%R*g(h3kT+sn!M?*5?{ot+*qTQCx;Z@q=YYAmi_1y=Aj7HfIn0WlEt zo`rA&4yTvC$LuaJ5{9!(L$^PMCL$qq3i1W?E+?f49Y$Avz`}SiQWQ-Bre#Ycm8~Qd3iZ>4TQ9U z^}&`Dzvqi4az9^Hr}(M`&UyU~iB#sEOwluI2noooR}W&-QTY}X6+ujCz+1un@70d* zLaCIcp3#DWmAd4?`hTy*`xLbtDs8~<>efA?V*jpA_o6@)u{yNgx{K^(vYuO9{%3VF z!HkTIgw$x=cJW!5j^*0AYfUc^sbDZsQf6+hu6){g(l!p4L8i=o2~`F`$h2)+MoZ5! zfjPnBEsMToI{m<=$~JHhG`W@h4GW4jSdc3qBIEO4{NTJZ8eGnEVX#V~YYFqg<^p_x< zXiYyYfYc2`^Y!sbqu;{17#OkMzJ?aeaS8~iM3iG@3ygq)mDfO4A!maD>Pp-au@tSZ z#xg!5%o~Dt6Zf3`4K`Um;=tC88y}C0{46Pmwk@A)fn5M>ThhHg^>o#PSfg>9Zucxzixe!=5)>TE1-Av#~}6#>>Eo>68yn*Ufn%_zjhc9rpi8 z>fXjtGP-k1Zh$e7icv)`jG12fBSY-=-$A{$Vf-k&ubbq+$TGo zLN1{M&tNu^{`(@Bj5IbjhHO4#^JW~12gY0xmNcQigkug2;ks9Dne$w~ex3789ArYg z6_@`+bJ7b;M41*5f(RP9AzSXYpWhTLV$-HgrY?x9XfC1A&{pQ*_V|Pk5<+?wPb|7b zWYZUDLTMu|QUY6^SNwe`R}(@aI=wf}eWa3sfAayY_vmDz6N?Tst{*DuHrRG3YXMOc z%i48FGXe|ceT$xwA|WwMe`3)uEFq!CTXF2~>z%Ljw1aHz()&lp)zrdtywCk>p+{5| zW5p?${3R5abhj%0@yNk<0`OJXEocfE-ilxU;UxJVO5F;Tt{@$f8*p|+^?)n+{QLP9$ZLuaj}{;h$CYmU`;Krf zE`t^vn#*bQqAUN610AmcG(eC$E@~@e{@qFI4p~LTv0uNUgsdWTym$BfvnK_$($dm^ zdqncnWXN^_UwWOQ4^Y1fgS9W)Ix3;1eUi#^ui#taAM#&DX%4COaIaF<2e?8h2 z=qYMulLgFp)V1mXwpj(ufrB0~8+rX@UZ3wD_3{KB_H1&miy)>~jBS8H9>Aq(tN%h> zq%AUPYHCm$j~(?HurDCXmZ&ZCujVOn{&gOi;6-I6ly-k09LZ=O>U}zg0G|+O!#wT{?A{Ha9AzG{GE;Ke~y=&LW#$c6ZNo5V9?Xzdkgst1KyuYeX zQi27Vlu-hvXtujV<@cr>Q1GX%5`F?G9ckzpRUg`ZI6iAJW0;e9tm53NX20vU8K9y= zl-ovOa=ft!MM(!AL23A{K_h}HVEqjTjT#C_OpPIT{a=xT&I92D3czqNU1CJ$mW{{n zkG`OD)pU*7|Jtaf#r9tVFeUu19fLQx4naEL@%{b%RT*-Z{&k&09`jRQ(MgQJsEqc< zqYG2@=r2$;+UWkQvMr~os_N0BN7Z0CPP`_{JRZ^w(V@AreCIE_eUzjpae7AV`OD+w*77@$u);$AMv?mZK5s&Lw%9|D=mSSuHK7 zmR_JO{OA?~M~27Tcq<{CWzXB%4EZ zdM>^M{{XG%@2@5pg(gY@(!_Z<%rirVBZVl@iGGpC;6-U5F64sjf?WfD>WvCMbinLA zc-bmZcK|#z%Umu-jD7p|?Eo-0_VwnUi#Zy@moPVCd7Cs?k4iS>OiZ%W^~3QH3}6sd zO*i<|pREfT$A0414l?%KeuuyyW@y+AaCr!L4_t4CZXp4NxRTAO;4VxQB2c6gLk;~+ zU%_Ba7xz(7|E}LE@CD&Sryq4KRNXn?>(}T4o!HtAKYGOB@k&kGfJ;!2jB3B}A#mVS zdxFe*i-rtZM3r*ssWq5mW;J~&(npEp|94=x{K4c;XBJGHNk)fP4W|-XJb3JM#0VC_3}9vbU`R z-#PwIjSLgj|3xE%xiP#~9mtb^iV7vyZ@{{Vp^iy{r%hK^z#N;deu4DFF0=<-_eC;s zao2eFHB1D12ojXt21BSZv4Js#Z&&68f{YY(_#W00zf69}yownpxU0Ln9MFM~Iezv` zH}INs0M9R0V0W_>Y$fEv#4IUKwf7$e1T$g$rqdA;V(r8{SZd(ss|t#WVI}H2&{@{L z_QyoDZHb{K$0}+ymO)*(_=qnt$;P=- zUTe4|tE1xD(s4)Ck(D{66FQKiaLriq%0Ci3%6cAvg&^`Fj&VsSLNEYpT^A#w%*&}D z_K2^kkW&?E=Fd=Msj|P>`U`S&NH5c}s#gE;wJKLg6+$+1{kTi08}2up43U0^yB8G~ z!%+B4)tkz_yrR}&NwZbPOc9#FLt(R#dE$gXw!g55yic~WqMb*Lu>0j0#CW{kIbv+@?3Q;-uy9MFz0sOU_Uw0)(WV4hih84B~UWi^O7Johk`X1-Nsgv{n5 z7J~LnE3G8)X^Y17rv`biBP5FU1{T;YO6DwitONcs^LiRuIt+|7vc-iRHnk^JAlWBM zq3-4z!_e!GA5F7*5wPXZiQx&^2+amQSy}BfF2}DQAm}+HVPa&SNSCT87EZ8Nd+BJk zevyq3IC}>%n$QOZUsDM%K>!Xv%>edc6hCO-;mk*+6B2FI-B1{D#@b3nmeo7T_yfrA}XzFT2wNbN8BP zH-q5vjD>UudD`6YK1aJpP832fW=8b39w!F=P$#y;U)H&1N9Zovxvs~E=m_Jd7FIRo zIVCqQZ*DhI*rA*(-9bp80CKTM{(tY$^2KI9;|2=<0ye4%QOm40nu`!l==g^1OoIo| zMBG6Po4+8!?%(VrC%?}82>;s*Hc3~t2$WVuFd`xcXgvWXc5-TjU-G-$l9qCJ5vqBN zMjX;XwO2?A#jzH@`(lzoetYnp%m(}bMjhJ1E{~ea?Fc~z<8Ah-a9x#<*l1Ml5Srb% zaRXVHf6AbIS-ip>2FAodgPIS#)d!*&ZQ!j$fkq4$k+%IIvW~UnA15<`6W)5z=om5> z>a`d^I5{0iD;m4m&uk^&4-dC|39FE)x)Zb%=;Nni(NK$VqkjguPD{3ahU2uz+YF3F z#lub-`TAA&=xawXm!?Zg>gzPLJm^Y6TZoWFqyusUSAjOhh~Hg~ZF;#z$48Gv@oK}^ zAv2^QUtw@D#_RbB`H=g*Yq$0F_fJ94U3D%CDe?mgYB3#k_OpAPg8q4+SBQ+zd-712 zgryfNNbgLu7;^@b_;+^-ATV)9#n!CRw&4#B$-|`7y1n`F#NZ_XB?vS2we>X$=-igFY`NkIrSGt|=Z&c9mS+XsTM z+vRI4>TF9xD$DlmvEOxcc3Wdgl4AhVotFhfXm{|dO4-`^r)_-3`x_>*3cEt2(Ju|h zPT?mbfIbJ%@|+AkX$d<(*w1uOy(I69DmLqxD24^0-O&cusyehh23$;)>*DBS#pAKT zc2y6yqfx-2z*8Anxl+8eTfV+#7)1tgqH{q-MFpyyK(rW+iX|#V3(5`KdX56@=4ohY z-FF$jamsy6Ts4ZeB{XY z?%q{Pw}>-YDMPeO=_=99lkn&XXHuuhBj4-ksCF)=a0CS$Z1 z8SRYg|GJu|Ei_7^#-@+kGL+E7TH)w7Z$^kU@gw_<=P^vtT(NK4}7w93AHYgI@+}u{s)A#eRhWOlg zaIVF_0J9@g$SG6}t+jtSpFzv%4%BLCXZEOKv(arqZJ)xNxhJdH0W(;uGQrhGp`Y-t zVD;@or6_?@0~{X-1`@FXqKj+St_dh(pibRTKWF)h6_8RMhQ^%HY~1)2R7z-o62J`8 zIB4Tb%g782uqx}Um^!4zPy2!Fjyz<^lliA3LDJ?&Kh;DGj*8z8lHHu4l2ilyHP#3* z@BS$Q1Hz3$_e~(nD42z|cZ9O8vX5BN-&sWKV`AUXA_^V!nc^dggA+xbs%bS5(2-SL z4-8bUF(ybY+lNXzdz2n%K?8)rpsFf?vKBu6Rmi{i-5%~C&??jDypE}xcCC-PxWS>+xe|dXU+n5|TV!kKLgn_MLrv!Y-0x-tY-sJ3;m@ht}DpJDE;H z+PDXL^K>=29Vo3@j{g;tBL!%f0apz91U$(39C8@>-&)02(=ebhMd*e%8EM#KsA@yVBgE2D;gPl%#o6RPVx(l(+?MZf(x$uh5v_Exa{4R~n zH@4rq)As86ojWW2ADuh!M4D^glg>M(8_qRr`K>w3U@ z;B8C$==_b=A4M0lV`HN_r`{brvh-Kgt5@_)#}Fk1`m3jnRN%;Si}2DR}MA;~SB zo!bl2kbE$=vsK7)^Gp5)^O%ST2)*^C;sU={w*6e~vM#x%qT&&@CO+#f>Y?ZUmD|$t zbX%Z6G|B@tz_x9h8#qkaIK}M(eA3mJUal~BIo98V-f#WuGtR9CHyOPIO1u3Ek~7E4 zm%Srzr2J&*bBB#P_}+9`nM2>Sfr+ViaPZydUn8TV?MmrKLb-V5+qwB^OK$fEJ-ZUV zZ^bFyfXC(vE6*L>Y;*YV;hm1h*o=9)x)6~kFYY)QfaFf<5WM>BJ$g%r0(}x}!edDV za$#ji3I@iwiLIT^3S9C;tW8ldfFjrAH*T+3cK%J?d2kUI6{Bn?1LS!*9AcSwAp+zU*X# z&$G7@K591R=2dm>&aIU$I?Bp9(3TAbK_YSzGc69jo0qH74^INTyi*msnAdhnn^*o` zL9Y3?0+anG8zL_3qcNUnUVn=4{U=|Ua)GyAkWU0eL2`TKC(7;E2M_MQsca~yQA&Ie zzHQq!hTwg>cQ2*xw0>@Nym_01^TEVYC@of-IPZL-x^2^@r8fWo0&T9#?o}X{ddvt* zizD@*HQdv-&f1%h}~l>6AXbIEc6Q3o8) zY51!o*toiiKXWQxcHix7KXk#b)EP z-d#smcV=cLU%9=9;gI0r!=c;)ozv6Pc{k)LpB<5Fz)$d%SkuR6{d{8i(P?ccq{@sF z+<|Xyh`p1DxFRbX&#Obe?BL*m#VaYTGns@81`#`MtKyHSyUbj=IV#7GUsFlo{f4Oy zfLNFz-G7*2l-G5Xo2yu~L+tK88c8{acyeKfX2zyOu=0cDw4~g;JZKU$CHuT{Zier-oD)B>T263cvadG+Cn6AYD1E#c&bxL{7v(4RAoL;qR zaDCtXO^j_;N!44l=mpkKeW~8GG7UmP#aOY&@ipc=Gm00Pxw7Q}Hc|)82R?t^#Kv|v zLweOdn%$aHx+iW=0}M`rSgLHG8(GVl4*!G8i0t1_i0KC@*_^#w4x(Mlz`*crKbFAH zuRNi2lWXr@FbKr#^V8!TaEV(OCP9t|5$5dt8}w~Gr%}71tg|IRwS}c+jb0dO$p=U) z_Xs<@dh-S&6nBe$w}SM_(JNZQznW=6a81L{)!dRRh!Sg3r0*;5++JYb(m!=!K{HZey)Sz z$dMBCE!DQ#(1)1iC7$9=0BAuEb{1h6`xl+BTOFTQ5*>__&iB24yzlRKj(6{8-_Lzt!&>WFx8=Sc zJFx^l5Lco4qS(=s?V$`3dZe8Y78Zto;&#%%V^OEcJU=&A1)L9&iBbFGV*-PNIeCe< z%4>EMM68g;arBAlPk;RQ(c9abV}&J9xQ?L_(qKz%HHP7YzClovcdOJIN?lYBskEi) z{pghf*1`56EIoeUCef#02tw|#?&mSbYHBO(pm=1q4PDMT4?#HM%&nP>k?yhTH9lP} z?pB4YP9}@g3rMlg?hgdBUQ@%aOwzyT*}0kBhhCVc@%i(cb^|)0eEj@Wt5*4Cr$;pG zTCHbaPa_p|=gzhl{?@Di?w9QpY*Zv>zr;PXlXjOVM(vtFHVV$1NqFug3JK{qAXhjC zJb%tX4|}+V}6veomDMH8if(i z9R?L}_)R?)hTVaKD1-i=(5qFMM&B3WU7H(X`Ym9t{TF_V!u2*wP+MUzyq z7Yf6!!9kw?d=1$hMc?f3txq`EelZMvaU{5Qz zd2{HG#m&A#UW9WEI-zk%Q@-R{K4cG)PT%>Onn0As=)5`~KD_TUgL7~WhaP`Zh63ub zj;Z9|Ked zzt231HbGtBJqikn_IeY{Tp$iKTav}lxdXc9t6*Jy;m>x{n@$td^!4@zy&A)-XlZ1W zoRMLA{I~{w8~Ot&4D#Z&>->G%fYqy4+uGWSiiwTUbYsYqgG7of(=!1Cn3JEMQl`i9 z@0&YaT`jLsh@4cIkr6edxX;S!^o0w~c6L1YU^$c>NZg~6rqq9*IEzf7gb?LJ0y7sK zHN|8najGHh$4cNR9zp9&F=CJUyS}Y4Ti?#ks-qn^b&7SkWE Zq*yE}8lDLRSte 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 K&8kd1qyGh4xs=ZU literal 40206 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 QWCM77ybhq+JdV)+3!x)~YXATM diff --git a/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.puml b/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.puml index a2149dd9a6e..567ca22bdd7 100644 --- a/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.puml +++ b/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.puml @@ -10,10 +10,15 @@ CREATE_LAG_IN_DB --> GRPC_COMMAND_SEND : next CREATE_LAG_IN_DB --> FINISHED_WITH_ERROR : error CREATE_LAG_IN_DB : enter / validate LAG port request, create GRPC commands -GRPC_COMMAND_SEND --> FINISHED : lag_installed +GRPC_COMMAND_SEND --> SPEAKER_COMMAND_SEND : lag_installed GRPC_COMMAND_SEND --> FINISHED_WITH_ERROR : error GRPC_COMMAND_SEND : enter / send GRPC commands +SPEAKER_COMMAND_SEND --> FINISHED : skip_speaker_commands_installation +SPEAKER_COMMAND_SEND --> FINISHED : speaker_entities_installed +SPEAKER_COMMAND_SEND --> FINISHED_WITH_ERROR : error +SPEAKER_COMMAND_SEND : enter / send speaker commands + FINISHED : enter / send-successful-response FINISHED_WITH_ERROR : enter / [LAG port created in DB] delete created LAG port, send-error-response diff --git a/docs/design/hub-and-spoke/lag/create/h&s-create-lag-port.png b/docs/design/hub-and-spoke/lag/create/h&s-create-lag-port.png index b6f979b3881443b5d1936abfb877fa1276c2793c..4c6931e9af2040b679d68a010bbb93034c23bc2e 100644 GIT binary patch literal 60357 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�YTZ_=I%6yF(^d!zoX|GipGh_f}b1 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 hzp{Hc`Su#ORphq|dSz6&ipdn{2Wq;i<#!*x_> participant SpeakerWorker participant GRPC +participant Floodlight database DB User -> NB : Create LAG @@ -25,7 +26,16 @@ GRPC -> SpeakerWorker : CreateLogicalPortResponse activate SpeakerWorker SpeakerWorker -> SwitchManager : CreateLogicalPortResponse deactivate SpeakerWorker -SwitchManager ->> NB: LagPortResponse +SwitchManager -> SpeakerWorker : OfCommands +activate SpeakerWorker +SpeakerWorker -> Floodlight : OfCommands +deactivate SpeakerWorker +Floodlight -> Floodlight : Installing rules\nand meter +Floodlight -> SpeakerWorker : OfResponse +activate SpeakerWorker +SpeakerWorker -> SwitchManager : OfResponse +deactivate SpeakerWorker +SwitchManager -> NB : LagPortResponse deactivate SwitchManager NB -> User: LagPortDto deactivate NB diff --git a/docs/design/hub-and-spoke/lag/delete/delete-lag-port-fsm.png b/docs/design/hub-and-spoke/lag/delete/delete-lag-port-fsm.png index 22107796e915c58394497b9f593b0187c6ac39e7..aff4cea650c2a3dc321caf3f7f69e4804467d928 100644 GIT binary patch literal 57096 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 BRz&~+ literal 36861 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%0Dfo^#_syWeE&vO7jI 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 dqTnwpPZCli4EC=Z+91F`abX#u5 START -START --> CREATE_GRPC_COMMAND : next +START --> VALIDATE_REMOVE_REQUEST : next START --> FINISHED_WITH_ERROR : error -CREATE_GRPC_COMMAND --> REMOVE_LAG_FROM_DB : lag_removed -CREATE_GRPC_COMMAND --> FINISHED_WITH_ERROR : error -CREATE_GRPC_COMMAND : enter / validate delete LAG port request, create GRPC command +VALIDATE_REMOVE_REQUEST --> SPEAKER_COMMAND_SEND : next +VALIDATE_REMOVE_REQUEST --> FINISHED_WITH_ERROR : error +VALIDATE_REMOVE_REQUEST : enter / validate delete LAG port request, create GRPC command + +SPEAKER_COMMAND_SEND --> GRPC_COMMAND_SEND : skip_speaker_entities_removal +SPEAKER_COMMAND_SEND --> GRPC_COMMAND_SEND : speaker_entities_removed +SPEAKER_COMMAND_SEND --> FINISHED_WITH_ERROR : error +SPEAKER_COMMAND_SEND : enter / send remove commands to speaker + +GRPC_COMMAND_SEND --> REMOVE_LAG_FROM_DB : lag_removed +GRPC_COMMAND_SEND --> FINISHED_WITH_ERROR : error +GRPC_COMMAND_SEND : enter / send command to GRPC REMOVE_LAG_FROM_DB --> FINISHED : next REMOVE_LAG_FROM_DB --> FINISHED_WITH_ERROR : error diff --git a/docs/design/hub-and-spoke/lag/delete/h&s-delete-lag-port.png b/docs/design/hub-and-spoke/lag/delete/h&s-delete-lag-port.png index 2a7a59d977040dde06dce16d69d64ae3ae4870ad..4e3868e2d4a057f5c0e2527f640d64ae8d53a2e5 100644 GIT binary patch literal 59830 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 F{{U%l7mWY_ literal 51003 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 participant SpeakerWorker +participant Floodlight participant GRPC database DB @@ -14,8 +15,15 @@ activate NB NB -> SwitchManager : DeleteLagPortRequest activate SwitchManager SwitchManager -> SwitchManager : Request validation -SwitchManager -> DB : Delete LAG from DB -DB -> SwitchManager +SwitchManager -> SpeakerWorker : OfCommands +activate SpeakerWorker +SpeakerWorker -> Floodlight : OfCommands +deactivate SpeakerWorker +Floodlight -> Floodlight : Remove rules\nand meter +Floodlight -> SpeakerWorker : OfResponse +activate SpeakerWorker +SpeakerWorker -> SwitchManager : OfResponse +deactivate SpeakerWorker SwitchManager -> SpeakerWorker : DeleteLogicalPortRequest activate SpeakerWorker SpeakerWorker -> GRPC : DeleteLogicalPortRequest @@ -25,6 +33,8 @@ GRPC -> SpeakerWorker : DeleteLogicalPortResponse activate SpeakerWorker SpeakerWorker -> SwitchManager : DeleteLogicalPortResponse deactivate SpeakerWorker +SwitchManager -> DB : Delete LAG from DB +DB -> SwitchManager SwitchManager ->> NB: LagPortResponse deactivate SwitchManager NB -> User: LagPortDto diff --git a/docs/design/multi-table-pipelines/cookies.md b/docs/design/multi-table-pipelines/cookies.md index ddd361e27bc..b2df1f12ced 100644 --- a/docs/design/multi-table-pipelines/cookies.md +++ b/docs/design/multi-table-pipelines/cookies.md @@ -153,6 +153,7 @@ Constraints: |`0x8000_0000_0000_001C`|`SERVER_42_ISL_RTT_OUTPUT_COOKIE`|Sends ISL RTT packet back to server42| |`0x8000_0000_0000_001D`|`SERVER_42_ISL_RTT_TURNING_COOKIE`|Sends ISL RTT packet back to IN_PORT| |`0x8000_0000_0000_001E`|`SERVER_42_FLOW_RTT_VXLAN_TURNING_COOKIE`|Catches flow RTT packet for VXLAN Flows, swaps ETH src and dst and sends back to IN_PORT| +|`0x8000_0000_0000_001F`|`DROP_SLOW_PROTOCOLS_LOOP_COOKIE`| Catches and drops LACP reply packet if ETH_SRC of this packet is equal to switch ID (packet returned to switch which sent it. It means we have a loop)| |`0x8010_0000_XXXX_XXXX`|`LLDP_INPUT_CUSTOMER`|Marks LLDP packets from port XXX by metadata| |`0x8020_0000_XXXX_XXXX`|`MULTI_TABLE_ISL_VLAN_EGRESS`|Moves Vlan packets received from ISL port XXX from input table to egress table| |`0x8030_0000_XXXX_XXXX`|`MULTI_TABLE_ISL_VXLAN_EGRESS`|Moves VXLAN packets received from ISL port XXX from input table to egress table| @@ -164,6 +165,7 @@ Constraints: |`0x8090_0000_XXXX_XXXX`|`SERVER_42_FLOW_RTT_INPUT`|Receives server42 flow RTT packet from port XXX, puts timestamp into packet (if switch has such support) and move packet to pre-ingress table| |`0x80A0_0000_0000_0000`|`APPLICATION_MIRROR_FLOW`|Flow mirror traffic for application purposes.| |`0x80D0_0000_XXXX_XXXX`|`SERVER_42_ISL_RTT_INPUT`|Forwards server42 ISL RTT packet to ISL port XXX| +|`0x80E0_0000_XXXX_XXXX`|`LACP_REPLY_INPUT`|Catches LACP request packet from port XXX, sends it to Floodlight for modiffication and sending back to port| |`0x4000_0000_000X_XXXX`|`INGRESS_FORWARD`|Receives Customer packets, push transit encapsulation if needed and sends to port. Path direction - forward, XXX - path unmasked cookie| |`0x2000_0000_000X_XXXX`|`INGRESS_REVERSE`|Receives Customer packets, push transit encapsulation if needed and sends to port. Path direction - reverse, XXX - path unmasked cookie| |`0x4008_0000_000X_XXXX`|`FLOW_LOOP_FORWARD`|Makes flow loop for forward direction (sends all customer traffic back to IN_PORT). XXX - path unmasked cookie| diff --git a/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/floodlight/api/request/rulemanager/OfCommand.java b/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/floodlight/api/request/rulemanager/OfCommand.java index e9b910beb6b..db4034fe350 100644 --- a/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/floodlight/api/request/rulemanager/OfCommand.java +++ b/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/floodlight/api/request/rulemanager/OfCommand.java @@ -15,13 +15,23 @@ package org.openkilda.floodlight.api.request.rulemanager; +import static java.lang.String.format; + import org.openkilda.model.SwitchId; +import org.openkilda.rulemanager.FlowSpeakerData; +import org.openkilda.rulemanager.GroupSpeakerData; +import org.openkilda.rulemanager.MeterSpeakerData; +import org.openkilda.rulemanager.SpeakerData; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + @JsonTypeInfo(use = Id.NAME, property = "clazz") @JsonSubTypes({ @Type(value = FlowCommand.class, @@ -38,4 +48,23 @@ public abstract class OfCommand { public abstract void buildModify(OfEntityBatch builder, SwitchId switchId); public abstract void buildDelete(OfEntityBatch builder, SwitchId switchId); + + + /** + * Converts SpeakerData to OfCommand. + */ + public static OfCommand toOfCommand(SpeakerData speakerData) { + if (speakerData instanceof FlowSpeakerData) { + return new FlowCommand((FlowSpeakerData) speakerData); + } else if (speakerData instanceof MeterSpeakerData) { + return new MeterCommand((MeterSpeakerData) speakerData); + } else if (speakerData instanceof GroupSpeakerData) { + return new GroupCommand((GroupSpeakerData) speakerData); + } + throw new IllegalStateException(format("Unknown speaker data type %s", speakerData)); + } + + public static List toOfCommands(Collection speakerData) { + return speakerData.stream().map(OfCommand::toOfCommand).collect(Collectors.toList()); + } } diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCore.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCore.java index 83d7b343641..b731305b5bb 100644 --- a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCore.java +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCore.java @@ -20,6 +20,7 @@ import org.openkilda.floodlight.service.FeatureDetectorService; import org.openkilda.floodlight.service.IService; import org.openkilda.floodlight.service.connected.ConnectedDevicesService; +import org.openkilda.floodlight.service.lacp.LacpService; import org.openkilda.floodlight.service.of.InputService; import org.openkilda.floodlight.service.session.SessionService; import org.openkilda.floodlight.service.zookeeper.ZooKeeperService; @@ -55,6 +56,7 @@ public KildaCore() { .put(SessionService.class, new SessionService()) .put(FeatureDetectorService.class, new FeatureDetectorService()) .put(ConnectedDevicesService.class, new ConnectedDevicesService()) + .put(LacpService.class, new LacpService()) .put(ZooKeeperService.class, new ZooKeeperService()) .build(); } diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCoreConfig.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCoreConfig.java index da6dbf2e54b..e8efd201f18 100644 --- a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCoreConfig.java +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCoreConfig.java @@ -22,6 +22,9 @@ import com.sabre.oss.conf4j.annotation.Default; import com.sabre.oss.conf4j.annotation.Key; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; + public interface KildaCoreConfig { @Key("command-processor-workers-count") @Default("4") @@ -58,15 +61,18 @@ public interface KildaCoreConfig { @Converter(EnumLowerCaseConverter.class) FloodlightRole getRole(); - /** - * This offset is used for encoding ISL in_port number into udp_src port of Server 42 ISL RTT packets. - */ - @Key("server42-isl-rtt-udp-port-offset") - @Default("10000") - int getServer42IslRttUdpPortOffset(); + @Key("lacp-system-id") + @Default("00:00:00:00:00:01") + @NotBlank + String getLacpSystemId(); - @Key("server42-isl-rtt-magic-mac-address") - @Default("00:26:E1:FF:FF:FD") - String getServer42IslRttMagicMacAddress(); + @Key("lacp-system-priority") + @Min(1) + @Default("1") + int getLacpSystemPriority(); + @Key("lacp-port-priority") + @Min(1) + @Default("1") + int getLacpPortPriority(); } diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/service/lacp/LacpService.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/service/lacp/LacpService.java new file mode 100644 index 00000000000..8f51ef64306 --- /dev/null +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/service/lacp/LacpService.java @@ -0,0 +1,242 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.floodlight.service.lacp; + +import org.openkilda.floodlight.KafkaChannel; +import org.openkilda.floodlight.KildaCore; +import org.openkilda.floodlight.KildaCoreConfig; +import org.openkilda.floodlight.command.Command; +import org.openkilda.floodlight.command.CommandContext; +import org.openkilda.floodlight.model.OfInput; +import org.openkilda.floodlight.service.IService; +import org.openkilda.floodlight.service.kafka.KafkaUtilityService; +import org.openkilda.floodlight.service.of.IInputTranslator; +import org.openkilda.floodlight.service.of.InputService; +import org.openkilda.floodlight.shared.packet.Lacp; +import org.openkilda.floodlight.shared.packet.Lacp.ActorPartnerInfo; +import org.openkilda.floodlight.shared.packet.SlowProtocols; +import org.openkilda.model.SwitchId; +import org.openkilda.model.cookie.Cookie; +import org.openkilda.model.cookie.CookieBase.CookieType; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.internal.IOFSwitchService; +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.IPacket; +import net.floodlightcontroller.util.OFMessageUtils; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LacpService implements IService, IInputTranslator { + private static final Logger logger = LoggerFactory.getLogger(LacpService.class); + + private IOFSwitchService switchService; + private MacAddress systemId; + private int systemPriority; + private int portPriority; + + static { + try { + logger.info("Force loading of {}", Class.forName(SlowProtocols.class.getName())); + } catch (ClassNotFoundException e) { + logger.error(String.format("Couldn't load class SlowProtocols %s", e.getMessage()), e); + } + } + + @Override + public Command makeCommand(CommandContext context, OfInput input) { + return new Command(context) { + @Override + public Command call() { + handlePacketIn(input); + return null; + } + }; + } + + @VisibleForTesting + Lacp deserializeLacp(Ethernet eth, SwitchId switchId, long cookie) { + try { + IPacket payload = eth.getPayload(); + + if (payload instanceof SlowProtocols) { + SlowProtocols slowProtocol = ((SlowProtocols) payload); + if (slowProtocol.getPayload() instanceof Lacp) { + return (Lacp) slowProtocol.getPayload(); + } else { + if (logger.isTraceEnabled()) { + logger.trace("Got unknown slow protocol packet {} on switch {}", + slowProtocol.getSubtype(), switchId); + } + return null; + } + } + } catch (Exception e) { + logger.info(String.format("Could not deserialize lacp packet %s on switch %s. Deserialization failure: %s", + eth, switchId, e.getMessage()), e); + return null; + } + logger.info("Got invalid lacp packet: {} on switch {}. Cookie {}", eth, switchId, cookie); + return null; + } + + private void handlePacketIn(OfInput input) { + U64 rawCookie = input.packetInCookie(); + + if (rawCookie == null) { + return; + } + + Cookie cookie = new Cookie(rawCookie.getValue()); + SwitchId switchId = new SwitchId(input.getDpId().getLong()); + + if (cookie.getType() == CookieType.LACP_REPLY_INPUT) { + if (logger.isDebugEnabled()) { + logger.debug("Receive LACP packet from {} OF-xid:{}, cookie: {}", + input.getDpId(), input.getMessage().getXid(), cookie); + } + handleLacp(input, switchId, cookie.getValue(), getInPort((OFPacketIn) input.getMessage())); + } + } + + private void handleLacp(OfInput input, SwitchId switchId, long cookie, OFPort inPort) { + Ethernet ethernet = input.getPacketInPayload(); + Lacp lacpRequest = deserializeLacp(ethernet, switchId, cookie); + if (lacpRequest == null) { + return; + } + if (logger.isDebugEnabled()) { + logger.debug("Switch {} received LACP request from port {}. Request: {}", switchId, inPort, lacpRequest); + } + Lacp lacpReply = modifyLacpRequest(lacpRequest); + sendLacpReply(DatapathId.of(switchId.getId()), inPort, lacpReply); + } + + @VisibleForTesting + Lacp modifyLacpRequest(Lacp lacpRequest) { + lacpRequest.setPartner(new ActorPartnerInfo(lacpRequest.getActor())); + + lacpRequest.getActor().getState().setActive(false); + lacpRequest.getActor().getState().setSynchronised(true); + lacpRequest.getActor().getState().setAggregatable(true); + lacpRequest.getActor().getState().setExpired(false); + lacpRequest.getActor().getState().setDefaulted(false); + lacpRequest.getActor().setPortPriority(portPriority); + lacpRequest.getActor().setSystemPriority(systemPriority); + lacpRequest.getActor().setSystemId(systemId); + return lacpRequest; + } + + OFPacketOut generateLacpPacket(IOFSwitch sw, Lacp lacp, OFPort outPort) { + try { + SlowProtocols slowProtocols = new SlowProtocols(); + slowProtocols.setSubtype(SlowProtocols.LACP_SUBTYPE); + slowProtocols.setPayload(lacp); + + Ethernet l2 = new Ethernet().setSourceMACAddress(new SwitchId(sw.getId().getLong()).toMacAddress()) + .setDestinationMACAddress(org.openkilda.model.MacAddress.SLOW_PROTOCOLS.getAddress()) + .setEtherType(EthType.SLOW_PROTOCOLS); + l2.setPayload(slowProtocols); + + byte[] data = l2.serialize(); + OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut() + .setBufferId(OFBufferId.NO_BUFFER) + .setActions( + Lists.newArrayList( + sw.getOFFactory().actions().buildOutput().setPort(outPort).build())) + .setData(data); + OFMessageUtils.setInPort(pob, OFPort.CONTROLLER); + + return pob.build(); + } catch (Exception e) { + logger.error(String.format("Error during generation of LACP packet: %s", e.getMessage()), e); + } + return null; + } + + private void sendLacpReply(DatapathId dpId, OFPort port, Lacp lacpReply) { + try { + IOFSwitch sw = switchService.getSwitch(dpId); + SwitchId switchId = new SwitchId(dpId.getLong()); + if (sw != null && sw.getPort(port) != null) { + OFPacketOut ofPacketOut = generateLacpPacket(sw, lacpReply, port); + boolean result = false; + if (ofPacketOut != null) { + if (logger.isDebugEnabled()) { + logger.debug("Sending LACP reply out {}/{}: {}", switchId, port.getPortNumber(), lacpReply); + } + result = sw.write(ofPacketOut); + } else { + logger.warn("Received null from generateLacpPacket. switch: {}, port: {}", switchId, port); + } + + if (!result) { + logger.error("Failed to send PACKET_OUT(LACP reply packet) via {}-{}. Packet {}", + sw.getId(), port.getPortNumber(), lacpReply); + } + } else { + logger.error("Couldn't find switch {} to send LACP reply", switchId); + } + } catch (Exception exception) { + logger.error(String.format("Unhandled exception in %s", getClass().getName()), exception); + } + } + + private static OFPort getInPort(OFPacketIn packetIn) { + if (packetIn.getVersion().compareTo(OFVersion.OF_12) < 0) { + return packetIn.getInPort(); + } + + if (packetIn.getMatch().supports(MatchField.IN_PHY_PORT)) { + OFPort inPort = packetIn.getMatch().get(MatchField.IN_PHY_PORT); + if (inPort != null) { + return inPort; + } + } + return packetIn.getMatch().get(MatchField.IN_PORT); + } + + @Override + public void setup(FloodlightModuleContext context) { + logger.info("Stating {}", LacpService.class.getCanonicalName()); + KafkaChannel kafkaChannel = context.getServiceImpl(KafkaUtilityService.class).getKafkaChannel(); + logger.info("region: {}", kafkaChannel.getRegion()); + + switchService = context.getServiceImpl(IOFSwitchService.class); + + KildaCoreConfig coreConfig = context.getServiceImpl(KildaCore.class).getConfig(); + systemId = MacAddress.of(coreConfig.getLacpSystemId()); + systemPriority = coreConfig.getLacpSystemPriority(); + portPriority = coreConfig.getLacpPortPriority(); + + InputService inputService = context.getServiceImpl(InputService.class); + inputService.addTranslator(OFType.PACKET_IN, this); + } +} diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/Lacp.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/Lacp.java new file mode 100644 index 00000000000..0b04ba229ce --- /dev/null +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/Lacp.java @@ -0,0 +1,268 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.floodlight.shared.packet; + +import static java.lang.Short.toUnsignedInt; +import static java.lang.String.format; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.floodlightcontroller.packet.BasePacket; +import net.floodlightcontroller.packet.IPacket; +import net.floodlightcontroller.packet.PacketParsingException; +import org.projectfloodlight.openflow.types.MacAddress; + +import java.nio.ByteBuffer; + +@Slf4j +@Data +@EqualsAndHashCode(callSuper = false) +public class Lacp extends BasePacket { + public static byte LACP_VERSION = 0x01; + public static byte ACTOR_TYPE = 0x01; + public static byte PARTNER_TYPE = 0x02; + public static byte TERMINATOR_PAD_COUNT = 52; + public static byte LACP_LENGTH = 109; + + private ActorPartnerInfo actor; + private ActorPartnerInfo partner; + private CollectorInformation collectorInformation; + + @Override + public byte[] serialize() { + byte[] data = new byte[LACP_LENGTH]; + + ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(LACP_VERSION); + bb.put(actor.serialize(ACTOR_TYPE)); + bb.put(partner.serialize(PARTNER_TYPE)); + bb.put(collectorInformation.serialize()); + for (int i = 0; i < TERMINATOR_PAD_COUNT; i++) { + bb.put((byte) 0); + } + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) throws PacketParsingException { + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + if (bb.remaining() < LACP_LENGTH) { + throw new PacketParsingException(format("Data %s is too short for LACP. " + + "Length of it must be at least %s", bytesToHex(data, offset, length), LACP_LENGTH)); + } + byte version = bb.get(); + if (version != LACP_VERSION) { + throw new PacketParsingException(format("Unknown LACP version %02X. Expected LACP version %02X", + version, LACP_VERSION)); + } + + byte[] actorBytes = new byte[ActorPartnerInfo.LENGTH]; + bb.get(actorBytes); + actor = new ActorPartnerInfo(); + actor.deserialize(actorBytes); + + byte[] partnerBytes = new byte[ActorPartnerInfo.LENGTH]; + bb.get(partnerBytes); + partner = new ActorPartnerInfo(); + partner.deserialize(partnerBytes); + + byte[] collectorBytes = new byte[CollectorInformation.LENGTH]; + bb.get(collectorBytes); + collectorInformation = new CollectorInformation(); + collectorInformation.deserialize(collectorBytes); + + return this; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class ActorPartnerInfo { + public static final byte TLV_TYPE_ACTOR = 0x01; + public static final byte TLV_TYPE_PARTNER = 0x02; + public static final byte LENGTH = 20; + private static final int RESERVED_LENGTH_BYTES = 3; + + private int systemPriority; + private MacAddress systemId; + private int key; + private int portPriority; + private int portNumber; + private State state; + + public ActorPartnerInfo(ActorPartnerInfo info) { + this(info.systemPriority, MacAddress.of(info.systemId.getLong()), info.key, info.portPriority, + info.portNumber, new State(info.state)); + } + + byte[] serialize(byte type) { + byte[] data = new byte[LENGTH]; + ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(type); + bb.put(LENGTH); + bb.putShort((short) systemPriority); + bb.put(systemId.getBytes()); + bb.putShort((short) key); + bb.putShort((short) portPriority); + bb.putShort((short) portNumber); + bb.put(state.serialize()); + for (int i = 0; i < RESERVED_LENGTH_BYTES; i++) { + bb.put((byte) 0); + } + return data; + } + + void deserialize(byte[] data) throws PacketParsingException { + if (LENGTH > data.length) { + throw new PacketParsingException(format("Data %s is too short for ActorPartnerInfo. " + + "Length of it must be at least %s", bytesToHex(data, 0, data.length), LENGTH)); + } + ByteBuffer bb = ByteBuffer.wrap(data); + bb.get(); // Actor or Partner type + byte length = bb.get(); + if (length != LENGTH) { + throw new PacketParsingException(format("Invalid length %s for ActorPartnerInfo. It must be %s", + length, LENGTH)); + } + systemPriority = toUnsignedInt(bb.getShort()); + + byte[] macAsBytes = new byte[MacAddress.FULL_MASK.getLength()]; + bb.get(macAsBytes); + systemId = MacAddress.of(macAsBytes); + key = toUnsignedInt(bb.getShort()); + portPriority = toUnsignedInt(bb.getShort()); + portNumber = toUnsignedInt(bb.getShort()); + + state = new State(); + state.deserialize(bb.get()); + } + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class State { + private static final int ACTIVE_SHIFT = 0; + private static final int SHORT_TIME_SHIFT = 1; + private static final int AGGREGATABLE_SHIFT = 2; + private static final int SYNCHRONISED_SHIFT = 3; + private static final int COLLECTING_SHIFT = 4; + private static final int DISTRIBUTING_SHIFT = 5; + private static final int DEFAULTED_SHIFT = 6; + private static final int EXPIRED_SHIFT = 7; + + private boolean active; + private boolean shortTimeout; + private boolean aggregatable; + private boolean synchronised; + private boolean collecting; + private boolean distributing; + private boolean defaulted; + private boolean expired; + + public State(State state) { + this(state.active, state.shortTimeout, state.aggregatable, state.synchronised, state.collecting, + state.distributing, state.defaulted, state.expired); + } + + byte serialize() { + byte data = 0; + data = setField(data, active, ACTIVE_SHIFT); + data = setField(data, shortTimeout, SHORT_TIME_SHIFT); + data = setField(data, aggregatable, AGGREGATABLE_SHIFT); + data = setField(data, synchronised, SYNCHRONISED_SHIFT); + data = setField(data, collecting, COLLECTING_SHIFT); + data = setField(data, distributing, DISTRIBUTING_SHIFT); + data = setField(data, defaulted, DEFAULTED_SHIFT); + data = setField(data, expired, EXPIRED_SHIFT); + return data; + } + + void deserialize(byte data) { + active = getField(data, ACTIVE_SHIFT); + shortTimeout = getField(data, SHORT_TIME_SHIFT); + aggregatable = getField(data, AGGREGATABLE_SHIFT); + synchronised = getField(data, SYNCHRONISED_SHIFT); + collecting = getField(data, COLLECTING_SHIFT); + distributing = getField(data, DISTRIBUTING_SHIFT); + defaulted = getField(data, DEFAULTED_SHIFT); + expired = getField(data, EXPIRED_SHIFT); + } + + private byte setField(byte data, boolean value, int shift) { + if (value) { + data |= 1 << shift; + } + return data; + } + + private boolean getField(byte data, int shift) { + return (data & (1 << shift)) != 0; + } + } + + @Data + public static class CollectorInformation { + public static final byte TLV_TYPE_COLLECTOR_INFO = 0x03; + + public static final byte LENGTH = 16; + private static final int RESERVED_LENGTH_BYTES = 12; + + private byte type; + private int maxDelay; + + byte[] serialize() { + byte[] data = new byte[LENGTH]; + ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(type); + bb.put(LENGTH); + bb.putShort((short) maxDelay); + for (int i = 0; i < RESERVED_LENGTH_BYTES; i++) { + bb.put((byte) 0); + } + return data; + } + + void deserialize(byte[] data) throws PacketParsingException { + if (LENGTH > data.length) { + throw new PacketParsingException(format("Data %s is too short for CollectorInformation. " + + "Length of it must be at least %s", bytesToHex(data, 0, LENGTH), LENGTH)); + } + ByteBuffer bb = ByteBuffer.wrap(data, 0, LENGTH); + type = bb.get(); + byte length = bb.get(); + if (length != LENGTH) { + throw new PacketParsingException(format("Invalid length %s for ActorPartnerInfo. It must be %s", + length, LENGTH)); + } + maxDelay = toUnsignedInt(bb.getShort()); + } + } + + private static String bytesToHex(byte[] bytes, int offset, int length) { + StringBuilder sb = new StringBuilder(); + for (int i = offset; i < offset + length && i < bytes.length; i++) { + if (i > offset) { + sb.append(' '); + } + sb.append(format("%02X", bytes[i])); + } + return sb.toString(); + } +} diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/SlowProtocols.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/SlowProtocols.java new file mode 100644 index 00000000000..b7dc8d8e34d --- /dev/null +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/SlowProtocols.java @@ -0,0 +1,80 @@ +/* Copyright 2020 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.floodlight.shared.packet; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import net.floodlightcontroller.packet.BasePacket; +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.IPacket; +import org.projectfloodlight.openflow.types.EthType; + +import java.nio.ByteBuffer; + +@Slf4j +@Data +@EqualsAndHashCode(callSuper = false) +public class SlowProtocols extends BasePacket { + public static final int SLOW_PROTOCOL_LENGTH_IN_BYTES = 1; + public static final byte LACP_SUBTYPE = 0x01; + + private byte subtype; + + static { + Ethernet.etherTypeClassMap.put((short) EthType.SLOW_PROTOCOLS.getValue(), SlowProtocols.class); + } + + @Override + public byte[] serialize() { + byte[] payloadData = new byte[0]; + if (payload != null) { + payload.setParent(this); + payloadData = payload.serialize(); + } + ByteBuffer bb = ByteBuffer.allocate(SLOW_PROTOCOL_LENGTH_IN_BYTES + payloadData.length); + bb.put(subtype); + bb.put(payloadData); + return bb.array(); + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + if (length == 0) { + return null; + } + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + subtype = bb.get(); + if (subtype == LACP_SUBTYPE) { + try { + payload = new Lacp().deserialize(data, bb.position(), bb.limit() - bb.position()); + } catch (Exception e) { + if (log.isTraceEnabled()) { + log.trace(String.format("Failed to parse Slow Protocols with subtype LACP: %s", e.getMessage()), e); + } + } + } + if (payload == null) { + byte[] buf = new byte[bb.remaining()]; + bb.get(buf); + payload = new net.floodlightcontroller.packet.Data(buf); + } + + payload.setParent(this); + return this; + } +} diff --git a/src-java/floodlight-service/floodlight-modules/src/main/resources/floodlightkilda.properties.example b/src-java/floodlight-service/floodlight-modules/src/main/resources/floodlightkilda.properties.example index 7681a094741..bb15dd11bf1 100644 --- a/src-java/floodlight-service/floodlight-modules/src/main/resources/floodlightkilda.properties.example +++ b/src-java/floodlight-service/floodlight-modules/src/main/resources/floodlightkilda.properties.example @@ -64,8 +64,9 @@ org.openkilda.floodlight.KildaCore.role = management #org.openkilda.floodlight.KildaCore.command-processor-idle-workers-keep-alive-seconds = 300 #org.openkilda.floodlight.KildaCore.flow-ping-magic-src-mac-address=00:26:E1:FF:FF:FE org.openkilda.floodlight.KildaCore.server42-flow-rtt-udp-port-offset=5000 -org.openkilda.floodlight.KildaCore.server42-isl-rtt-udp-port-offset=10000 -org.openkilda.floodlight.KildaCore.server42-isl-rtt-magic-mac-address=00:26:E1:FF:FF:FD +org.openkilda.floodlight.KildaCore.lacp-system-id=00:00:00:00:00:01 +org.openkilda.floodlight.KildaCore.lacp-system-priority=1 +org.openkilda.floodlight.KildaCore.lacp-port-priority=1 org.openkilda.floodlight.KafkaChannel.environment-naming-prefix= org.openkilda.floodlight.KafkaChannel.bootstrap-servers=kafka.pendev:9092 org.openkilda.floodlight.KafkaChannel.zookeeper-connect-string=zookeeper.pendev/kilda diff --git a/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpServiceTest.java b/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpServiceTest.java new file mode 100644 index 00000000000..b7cafca8ca1 --- /dev/null +++ b/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpServiceTest.java @@ -0,0 +1,119 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.floodlight.service.lacp; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.openkilda.floodlight.KafkaChannel; +import org.openkilda.floodlight.KildaCore; +import org.openkilda.floodlight.KildaCoreConfig; +import org.openkilda.floodlight.service.kafka.KafkaUtilityService; +import org.openkilda.floodlight.service.of.InputService; +import org.openkilda.floodlight.shared.packet.Lacp; +import org.openkilda.floodlight.shared.packet.Lacp.ActorPartnerInfo; +import org.openkilda.floodlight.shared.packet.Lacp.CollectorInformation; +import org.openkilda.floodlight.shared.packet.Lacp.State; +import org.openkilda.floodlight.test.standard.PacketTestBase; + +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.projectfloodlight.openflow.types.MacAddress; + +public class LacpServiceTest extends PacketTestBase { + public static final int ACTOR_SYSTEM_PRIORITY = 1; + public static final MacAddress ACTOR_SYSTEM_ID = MacAddress.of(2); + public static final int ACTOR_KEY = 3; + public static final int ACTOR_PORT_PRIORITY = 4; + public static final int ACTOR_PORT_NUMBER = 5; + + public static final int PARTNER_SYSTEM_PRIORITY = 6; + public static final MacAddress PARTNER_SYSTEM_ID = MacAddress.of(7); + public static final int PARTNER_PORT_PRIORITY = 8; + public static final byte COLLECTOR_TYPE = 9; + public static final byte COLLECTOR_MAX_DELAY = 10; + + LacpService service; + + @Before + public void setUp() { + service = new LacpService(); + KafkaChannel kafkaChannel = mock(KafkaChannel.class); + when(kafkaChannel.getRegion()).thenReturn("region"); + + KafkaUtilityService kafkaUtilityService = mock(KafkaUtilityService.class); + when(kafkaUtilityService.getKafkaChannel()).thenReturn(kafkaChannel); + + FloodlightModuleContext context = mock(FloodlightModuleContext.class); + when(context.getServiceImpl(Mockito.eq(KafkaUtilityService.class))).thenReturn(kafkaUtilityService); + + KildaCoreConfig kildaCoreConfig = mock(KildaCoreConfig.class); + when(kildaCoreConfig.getLacpSystemId()).thenReturn(PARTNER_SYSTEM_ID.toString()); + when(kildaCoreConfig.getLacpSystemPriority()).thenReturn(PARTNER_SYSTEM_PRIORITY); + when(kildaCoreConfig.getLacpPortPriority()).thenReturn(PARTNER_PORT_PRIORITY); + + KildaCore kildaCore = mock(KildaCore.class); + when(kildaCore.getConfig()).thenReturn(kildaCoreConfig); + when(context.getServiceImpl(Mockito.eq(KildaCore.class))).thenReturn(kildaCore); + + InputService inputService = mock(InputService.class); + doNothing().when(inputService).addTranslator(any(), any()); + when(context.getServiceImpl(Mockito.eq(InputService.class))).thenReturn(inputService); + + service.setup(context); + } + + @Test + public void serializeEthWithLacpTest() { + State actorState = new State(true, true, true, false, true, false, true, true); + ActorPartnerInfo actorInfo = new ActorPartnerInfo( + ACTOR_SYSTEM_PRIORITY, ACTOR_SYSTEM_ID, ACTOR_KEY, ACTOR_PORT_PRIORITY, ACTOR_PORT_NUMBER, actorState); + + ActorPartnerInfo emptyPartnerInfo = new ActorPartnerInfo(0, MacAddress.of(0), 0, 0, 0, new State()); + Lacp.CollectorInformation collectorInformation = new CollectorInformation(); + collectorInformation.setType(COLLECTOR_TYPE); + collectorInformation.setType(COLLECTOR_MAX_DELAY); + + Lacp lacpRequest = new Lacp(); + lacpRequest.setActor(actorInfo); + lacpRequest.setPartner(emptyPartnerInfo); + lacpRequest.setCollectorInformation(collectorInformation); + + Lacp lacpReply = service.modifyLacpRequest(lacpRequest); + + // we must copy Actor data to partner + assertEquals(ACTOR_SYSTEM_PRIORITY, lacpReply.getPartner().getSystemPriority()); + assertEquals(ACTOR_SYSTEM_ID, lacpReply.getPartner().getSystemId()); + assertEquals(ACTOR_KEY, lacpReply.getPartner().getKey()); + assertEquals(ACTOR_PORT_PRIORITY, lacpReply.getPartner().getPortPriority()); + assertEquals(ACTOR_PORT_NUMBER, lacpReply.getPartner().getPortNumber()); + assertEquals(new State(true, true, true, false, true, false, true, true), lacpReply.getPartner().getState()); + + // we must keep key and port number equal for actor and partner + assertEquals(ACTOR_KEY, lacpReply.getActor().getKey()); + assertEquals(ACTOR_PORT_NUMBER, lacpReply.getActor().getPortNumber()); + + assertEquals(PARTNER_SYSTEM_PRIORITY, lacpReply.getActor().getSystemPriority()); + assertEquals(PARTNER_SYSTEM_ID, lacpReply.getActor().getSystemId()); + assertEquals(PARTNER_PORT_PRIORITY, lacpReply.getActor().getPortPriority()); + assertEquals(new State(false, true, true, true, true, false, false, false), lacpReply.getActor().getState()); + } +} diff --git a/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpTest.java b/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpTest.java new file mode 100644 index 00000000000..6a14ca7c401 --- /dev/null +++ b/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpTest.java @@ -0,0 +1,116 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.floodlight.service.lacp; + +import static org.junit.Assert.assertEquals; + +import org.openkilda.floodlight.shared.packet.Lacp; +import org.openkilda.floodlight.test.standard.PacketTestBase; + +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.PacketParsingException; +import org.junit.Assert; +import org.junit.Test; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.MacAddress; + +public class LacpTest extends PacketTestBase { + private final byte[] srcAndDstMacAddresses = new byte[] { + 0x1, (byte) 0x80, (byte) 0xC2, 0x0, 0x0, 0xE, // src mac address + 0x10, 0x4E, (byte) 0xF1, (byte) 0xF9, 0x6D, (byte) 0xFA // dst mac address + }; + + private final byte[] lacpPacket = new byte[] { + 0x01, // LACP version + 0x01, // TLV type: actor + 0x14, // TLV length + (byte) 0x91, (byte) 0xf4, // Actor system priority + 0x00, 0x04, (byte) 0x96, 0x1f, 0x50, 0x6a, // Actor system ID + (byte) 0x80, 0x00, // Actor key + 0x00, 0x11, // Actor port priority + 0x00, 0x12, // Actor port + 0x47, // Actor state + 0x00, 0x00, 0x00, // Reserved + 0x02, // TLV type: partner + 0x14, // TLV length + 0x12, 0x34, // Partner system priority + 0x12, 0x34, 0x56, 0x78, (byte) 0x9a, 0x77, // Partner system ID + 0x44, 0x55, // Partner key + 0x11, 0x22, // Partner port priority + 0x00, 0x17, // Partner port + 0x3b, // Partner state + 0x00, 0x00, 0x00, // Reserved + 0x03, // TLV type: collector information + 0x10, // TLV length + 0x00, 0x02, // Max delay + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Reserved + 0x00, // TLV type: terminator + 0x00, // TLV length + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Zeros + + }; + + LacpService service = new LacpService(); + + @Test + public void deserializeLacpTest() throws PacketParsingException { + Lacp lacp = buildLacpPacket(lacpPacket); + assertLacp(lacp); + Assert.assertArrayEquals(lacpPacket, lacp.serialize()); + } + + @Test + public void serializeLacpTest() throws PacketParsingException { + Lacp lacp = buildLacpPacket(lacpPacket); + lacp.getPartner().setPortPriority(0xffff); + Lacp lacp1 = (Lacp) new Lacp().deserialize(lacp.serialize(), 0, lacp.serialize().length); + assertEquals(lacp, lacp1); + } + + @Test + public void serializeEthWithLacpTest() { + Ethernet ethernet = buildEthernet(srcAndDstMacAddresses, + ethTypeToByteArray(EthType.SLOW_PROTOCOLS), new byte[] {0x01}, lacpPacket); + + Lacp lacp = service.deserializeLacp(ethernet, null, 0); + assertLacp(lacp); + } + + private void assertLacp(Lacp lacp) { + assertEquals(0x91f4, lacp.getActor().getSystemPriority()); + assertEquals(MacAddress.of("00:04:96:1f:50:6a"), lacp.getActor().getSystemId()); + assertEquals(0x8000, lacp.getActor().getKey()); + assertEquals(0x11, lacp.getActor().getPortPriority()); + assertEquals(0x12, lacp.getActor().getPortNumber()); + + assertEquals(0x1234, lacp.getPartner().getSystemPriority()); + assertEquals(MacAddress.of("12:34:56:78:9a:77"), lacp.getPartner().getSystemId()); + assertEquals(0x4455, lacp.getPartner().getKey()); + assertEquals(0x1122, lacp.getPartner().getPortPriority()); + assertEquals(0x17, lacp.getPartner().getPortNumber()); + + assertEquals(3, lacp.getCollectorInformation().getType()); + assertEquals(2, lacp.getCollectorInformation().getMaxDelay()); + } + + static Lacp buildLacpPacket(byte[] packet) throws PacketParsingException { + Lacp lacp = new Lacp(); + lacp.deserialize(packet, 0, packet.length); + return lacp; + } +} diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java b/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java index 580b459cad1..7406b91f6b1 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java @@ -73,8 +73,9 @@ public LagLogicalPort(@NonNull LagLogicalPort entityToClone) { data = LagLogicalPortCloner.INSTANCE.deepCopy(entityToClone.getData(), this); } - public LagLogicalPort(@NonNull SwitchId switchId, int logicalPortNumber, Collection physicalPortNumbers) { - this(switchId, logicalPortNumber, new ArrayList()); + public LagLogicalPort(@NonNull SwitchId switchId, int logicalPortNumber, Collection physicalPortNumbers, + boolean lacpReply) { + this(switchId, logicalPortNumber, new ArrayList(), lacpReply); if (physicalPortNumbers != null) { data.setPhysicalPorts(physicalPortNumbers.stream() .map(port -> new PhysicalPort(switchId, port, this)) @@ -83,10 +84,12 @@ public LagLogicalPort(@NonNull SwitchId switchId, int logicalPortNumber, Collect } @Builder - public LagLogicalPort(@NonNull SwitchId switchId, int logicalPortNumber, List physicalPorts) { + public LagLogicalPort(@NonNull SwitchId switchId, int logicalPortNumber, List physicalPorts, + boolean lacpReply) { data = LagLogicalPortDataImpl.builder() .switchId(switchId) .logicalPortNumber(logicalPortNumber) + .lacpReply(lacpReply) .build(); // The reference is used to link physical ports back to the LAG port. See {@link #setPhysicalPorts(List)}. ((LagLogicalPortDataImpl) data).lagLogicalPort = this; @@ -135,6 +138,10 @@ public interface LagLogicalPortData { List getPhysicalPorts(); void setPhysicalPorts(List physicalPorts); + + boolean isLacpReply(); + + void setLacpReply(boolean lacpReply); } /** @@ -149,6 +156,8 @@ static final class LagLogicalPortDataImpl implements LagLogicalPortData, Seriali int logicalPortNumber; @NonNull SwitchId switchId; + boolean lacpReply; + @Builder.Default @ToString.Exclude @EqualsAndHashCode.Exclude diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/MacAddress.java b/src-java/kilda-model/src/main/java/org/openkilda/model/MacAddress.java index 577c49abbd4..6e02d0ade03 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/MacAddress.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/MacAddress.java @@ -25,6 +25,7 @@ public class MacAddress implements Serializable { private static final long serialVersionUID = 5506319046146663699L; private static final String MAC_ADDRESS_REGEXP = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"; + public static final MacAddress SLOW_PROTOCOLS = new MacAddress("01:80:C2:00:00:02"); String address; diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/MeterId.java b/src-java/kilda-model/src/main/java/org/openkilda/model/MeterId.java index 05e6f5d9fcf..25ef9b088de 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/MeterId.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/MeterId.java @@ -78,6 +78,7 @@ public final class MeterId implements Comparable, Serializable { */ public static final int MAX_SYSTEM_RULE_METER_ID = 31; + public static final MeterId LACP_REPLY_METER_ID = new MeterId(31); public static final long VERIFICATION_BROADCAST_METER_ID = defaultCookieToMeterId(VERIFICATION_BROADCAST_RULE_COOKIE); public static final long VERIFICATION_UNICAST_METER_ID = defaultCookieToMeterId(VERIFICATION_UNICAST_RULE_COOKIE); diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/CookieBase.java b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/CookieBase.java index e6d7535aa59..7583f087136 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/CookieBase.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/CookieBase.java @@ -156,6 +156,7 @@ public enum CookieType implements NumericEnumField { EXCLUSION_FLOW(0x0B), SERVER_42_FLOW_RTT_INGRESS(0x00C), SERVER_42_ISL_RTT_INPUT(0x00D), + LACP_REPLY_INPUT(0x00E), // This do not consume any value from allowed address space - you can define another field with -1 value. // (must be last entry) diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/PortColourCookie.java b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/PortColourCookie.java index 83daa70f9d6..0241e11035f 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/PortColourCookie.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/PortColourCookie.java @@ -32,7 +32,8 @@ public class PortColourCookie extends CookieBase implements Comparable { Collection findAll(); @@ -29,4 +32,6 @@ public interface LagLogicalPortRepository extends Repository { Optional findBySwitchIdAndPortNumber(SwitchId switchId, int portNumber); Optional findUnassignedPortInRange(SwitchId switchId, int portFirst, int portLast); + + Map> findBySwitchIds(Set switchIds); } diff --git a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/frames/LagLogicalPortFrame.java b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/frames/LagLogicalPortFrame.java index 17ae79996e0..c7b59e1e313 100644 --- a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/frames/LagLogicalPortFrame.java +++ b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/frames/LagLogicalPortFrame.java @@ -41,6 +41,7 @@ public abstract class LagLogicalPortFrame extends KildaBaseVertexFrame implement public static final String COMPRISES_PHYSICAL_PORT_EDGE = "comprises"; public static final String SWITCH_ID_PROPERTY = "switch_id"; public static final String LOGICAL_PORT_NUMBER_PROPERTY = "logical_port_number"; + public static final String LACP_REPLY_PROPERTY = "lacp_reply"; private List physicalPorts; @@ -54,6 +55,14 @@ public abstract class LagLogicalPortFrame extends KildaBaseVertexFrame implement @Convert(SwitchIdConverter.class) public abstract void setSwitchId(@NonNull SwitchId switchId); + @Override + @Property(LACP_REPLY_PROPERTY) + public abstract boolean isLacpReply(); + + @Override + @Property(LACP_REPLY_PROPERTY) + public abstract void setLacpReply(boolean lacpReply); + @Override @Property(LOGICAL_PORT_NUMBER_PROPERTY) public abstract int getLogicalPortNumber(); diff --git a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java index ea6438ec826..ef60ea1cec9 100644 --- a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java +++ b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java @@ -15,6 +15,9 @@ package org.openkilda.persistence.ferma.repositories; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toSet; + import org.openkilda.model.LagLogicalPort; import org.openkilda.model.LagLogicalPort.LagLogicalPortData; import org.openkilda.model.SwitchId; @@ -32,9 +35,12 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; /** @@ -127,6 +133,21 @@ public Optional findUnassignedPortInRange(SwitchId switchId, int portFi return Optional.empty(); } + @Override + public Map> findBySwitchIds(Set switchIds) { + Set graphSwitchIds = switchIds.stream() + .map(SwitchIdConverter.INSTANCE::toGraphProperty) + .collect(toSet()); + List result = new ArrayList<>(); + framedGraph().traverse(g -> g.V() + .hasLabel(LagLogicalPortFrame.FRAME_LABEL) + .has(LagLogicalPortFrame.SWITCH_ID_PROPERTY, P.within(graphSwitchIds))) + .toListExplicit(LagLogicalPortFrame.class).stream() + .map(LagLogicalPort::new) + .forEach(result::add); + return result.stream().collect(groupingBy(LagLogicalPort::getSwitchId, Collectors.toList())); + } + @Override protected LagLogicalPortFrame doAdd(LagLogicalPortData data) { LagLogicalPortFrame frame = KildaBaseVertexFrame.addNewFramedVertex(framedGraph(), diff --git a/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortTest.java b/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortTest.java index 06722f7562a..3dade063f2d 100644 --- a/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortTest.java +++ b/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortTest.java @@ -27,6 +27,7 @@ import org.openkilda.persistence.repositories.PhysicalPortRepository; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import org.junit.Before; import org.junit.Test; @@ -34,17 +35,20 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Optional; public class FermaLagLogicalPortTest extends InMemoryGraphBasedTest { static final SwitchId SWITCH_ID_1 = new SwitchId(1); static final SwitchId SWITCH_ID_2 = new SwitchId(2); static final SwitchId SWITCH_ID_3 = new SwitchId(3); + static final SwitchId SWITCH_ID_4 = new SwitchId(4); static final int LOGICAL_PORT_NUMBER_1 = 1; static final int LOGICAL_PORT_NUMBER_2 = 2; - static final int LOGICAL_PORT_NUMBER_3 = 2; - static final int PHYSICAL_PORT_NUMBER_1 = 4; - static final int PHYSICAL_PORT_NUMBER_2 = 5; + static final int LOGICAL_PORT_NUMBER_3 = 3; + static final int LOGICAL_PORT_NUMBER_4 = 4; + static final int PHYSICAL_PORT_NUMBER_1 = 5; + static final int PHYSICAL_PORT_NUMBER_2 = 6; LagLogicalPortRepository lagLogicalPortRepository; PhysicalPortRepository physicalPortRepository; @@ -57,9 +61,9 @@ public void setUp() { @Test public void createLogicalPortWithoutPhysicalPortsTest() { - createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1); - createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_2); - createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_3); + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, true); + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_2, false); + createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_3, true); List ports = new ArrayList<>(lagLogicalPortRepository.findAll()); @@ -77,11 +81,15 @@ public void createLogicalPortWithoutPhysicalPortsTest() { assertEquals(0, ports.get(0).getPhysicalPorts().size()); assertEquals(0, ports.get(1).getPhysicalPorts().size()); assertEquals(0, ports.get(2).getPhysicalPorts().size()); + + assertTrue(ports.get(0).isLacpReply()); + assertFalse(ports.get(1).isLacpReply()); + assertTrue(ports.get(2).isLacpReply()); } @Test public void createLogicalPortWithPhysicalPortsTest() { - LagLogicalPort logicalPort = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, + LagLogicalPort logicalPort = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, true, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); List ports = new ArrayList<>(lagLogicalPortRepository.findAll()); @@ -96,11 +104,13 @@ public void createLogicalPortWithPhysicalPortsTest() { assertEquals(logicalPort, ports.get(0).getPhysicalPorts().get(0).getLagLogicalPort()); assertEquals(logicalPort, ports.get(0).getPhysicalPorts().get(1).getLagLogicalPort()); + + assertTrue(ports.get(0).isLacpReply()); } @Test public void createLogicalPortAndSetPhysicalPortsTest() { - LagLogicalPort logicalPort = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1); + LagLogicalPort logicalPort = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, false); assertEquals(0, lagLogicalPortRepository.findAll().iterator().next().getPhysicalPorts().size()); PhysicalPort physicalPort1 = createPhysicalPort(SWITCH_ID_1, PHYSICAL_PORT_NUMBER_1, logicalPort); @@ -123,15 +133,15 @@ public void createLogicalPortAndSetPhysicalPortsTest() { @Test public void removeLogicalPortTest() { - LagLogicalPort logicalPort1 = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, + LagLogicalPort logicalPort1 = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, true, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); - LagLogicalPort logicalPort2 = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1); + LagLogicalPort logicalPort2 = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_2, false); transactionManager.doInTransaction(() -> lagLogicalPortRepository.remove(logicalPort2)); List ports = new ArrayList<>(lagLogicalPortRepository.findAll()); assertEquals(1, ports.size()); - assertEquals(2, ports.get(0).getPhysicalPorts().size()); + basicLagAssert(LOGICAL_PORT_NUMBER_1, 2, true, ports.get(0)); assertEquals(2, physicalPortRepository.findAll().size()); transactionManager.doInTransaction(() -> lagLogicalPortRepository.remove(logicalPort1)); @@ -142,44 +152,64 @@ public void removeLogicalPortTest() { @Test public void findBySwitchIdTest() { - createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); - createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_2); - createLogicalPort(SWITCH_ID_3, LOGICAL_PORT_NUMBER_3); + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, false, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_2, true); + createLogicalPort(SWITCH_ID_3, LOGICAL_PORT_NUMBER_3, false); List foundPorts = new ArrayList<>(lagLogicalPortRepository.findBySwitchId(SWITCH_ID_1)); foundPorts.sort(Comparator.comparingInt(LagLogicalPort::getLogicalPortNumber)); - assertEquals(LOGICAL_PORT_NUMBER_1, foundPorts.get(0).getLogicalPortNumber()); - assertEquals(LOGICAL_PORT_NUMBER_2, foundPorts.get(1).getLogicalPortNumber()); - assertEquals(2, foundPorts.get(0).getPhysicalPorts().size()); - assertEquals(0, foundPorts.get(1).getPhysicalPorts().size()); + basicLagAssert(LOGICAL_PORT_NUMBER_1, 2, false, foundPorts.get(0)); + basicLagAssert(LOGICAL_PORT_NUMBER_2, 0, true, foundPorts.get(1)); assertEquals(0, lagLogicalPortRepository.findBySwitchId(SWITCH_ID_2).size()); } + @Test + public void findBySwitchIdsTest() { + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, true, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); + createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_2, false); + createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_3, true); + createLogicalPort(SWITCH_ID_3, LOGICAL_PORT_NUMBER_4, false); + + Map> foundPorts = lagLogicalPortRepository.findBySwitchIds( + Sets.newHashSet(SWITCH_ID_1, SWITCH_ID_2)); + + assertEquals(2, foundPorts.size()); + + assertEquals(1, foundPorts.get(SWITCH_ID_1).size()); + basicLagAssert(LOGICAL_PORT_NUMBER_1, 2, true, foundPorts.get(SWITCH_ID_1).get(0)); + + assertEquals(2, foundPorts.get(SWITCH_ID_2).size()); + foundPorts.get(SWITCH_ID_2).sort(Comparator.comparingInt(LagLogicalPort::getLogicalPortNumber)); + basicLagAssert(LOGICAL_PORT_NUMBER_2, 0, false, foundPorts.get(SWITCH_ID_2).get(0)); + basicLagAssert(LOGICAL_PORT_NUMBER_3, 0, true, foundPorts.get(SWITCH_ID_2).get(1)); + + assertEquals(0, lagLogicalPortRepository.findBySwitchIds(Sets.newHashSet(SWITCH_ID_4)).size()); + } + @Test public void findBySwitchIdAndPortNumberTest() { - createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); - createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_2); + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, true, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); + createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_2, false); Optional foundPort1 = lagLogicalPortRepository.findBySwitchIdAndPortNumber( SWITCH_ID_1, LOGICAL_PORT_NUMBER_1); assertTrue(foundPort1.isPresent()); - assertEquals(LOGICAL_PORT_NUMBER_1, foundPort1.get().getLogicalPortNumber()); - assertEquals(2, foundPort1.get().getPhysicalPorts().size()); + basicLagAssert(LOGICAL_PORT_NUMBER_1, 2, true, foundPort1.get()); Optional foundPort2 = lagLogicalPortRepository.findBySwitchIdAndPortNumber( SWITCH_ID_2, LOGICAL_PORT_NUMBER_2); assertTrue(foundPort2.isPresent()); - assertEquals(LOGICAL_PORT_NUMBER_2, foundPort2.get().getLogicalPortNumber()); - assertEquals(0, foundPort2.get().getPhysicalPorts().size()); + basicLagAssert(LOGICAL_PORT_NUMBER_2, 0, false, foundPort2.get()); assertFalse(lagLogicalPortRepository.findBySwitchIdAndPortNumber( SWITCH_ID_3, LOGICAL_PORT_NUMBER_3).isPresent()); } - private LagLogicalPort createLogicalPort(SwitchId switchId, int logicalPortNumber, Integer... physicalPorts) { - LagLogicalPort port = new LagLogicalPort(switchId, logicalPortNumber, Arrays.asList(physicalPorts)); + private LagLogicalPort createLogicalPort( + SwitchId switchId, int logicalPortNumber, boolean lacpReply, Integer... physicalPorts) { + LagLogicalPort port = new LagLogicalPort(switchId, logicalPortNumber, Arrays.asList(physicalPorts), lacpReply); lagLogicalPortRepository.add(port); return port; } @@ -189,4 +219,11 @@ private PhysicalPort createPhysicalPort(SwitchId switchId, int physicalPortNumbe physicalPortRepository.add(port); return port; } + + private void basicLagAssert( + int expectedPortNumber, int expectedPhysPortCount, boolean expectedLacpReply, LagLogicalPort port) { + assertEquals(expectedPortNumber, port.getLogicalPortNumber()); + assertEquals(expectedPhysPortCount, port.getPhysicalPorts().size()); + assertEquals(expectedLacpReply, port.isLacpReply()); + } } diff --git a/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaPhysicalPortTest.java b/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaPhysicalPortTest.java index 4bab45e560d..15a3e143dc1 100644 --- a/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaPhysicalPortTest.java +++ b/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaPhysicalPortTest.java @@ -120,7 +120,7 @@ public void findPortNumbersBySwitchIdTest() { } private LagLogicalPort createLogicalPort(SwitchId switchId, int logicalPortNumber) { - LagLogicalPort port = new LagLogicalPort(switchId, logicalPortNumber, new ArrayList()); + LagLogicalPort port = new LagLogicalPort(switchId, logicalPortNumber, new ArrayList(), true); lagLogicalPortRepository.add(port); return port; } diff --git a/src-java/nbworker-topology/nbworker-messaging/src/main/java/org/openkilda/messaging/nbtopology/response/LagPortDto.java b/src-java/nbworker-topology/nbworker-messaging/src/main/java/org/openkilda/messaging/nbtopology/response/LagPortDto.java index df68f21ea9e..4265d6182c7 100644 --- a/src-java/nbworker-topology/nbworker-messaging/src/main/java/org/openkilda/messaging/nbtopology/response/LagPortDto.java +++ b/src-java/nbworker-topology/nbworker-messaging/src/main/java/org/openkilda/messaging/nbtopology/response/LagPortDto.java @@ -27,6 +27,7 @@ public class LagPortDto implements Serializable { private static final long serialVersionUID = 998335534425508496L; - private int logicalPortNumber; - private List portNumbers; + int logicalPortNumber; + List portNumbers; + boolean lacpReply; } diff --git a/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/LagPortMapperTest.java b/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/LagPortMapperTest.java index d97d1d43c17..fd02c455f73 100644 --- a/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/LagPortMapperTest.java +++ b/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/LagPortMapperTest.java @@ -16,6 +16,7 @@ package org.openkilda.wfm.share.mappers; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.openkilda.messaging.nbtopology.response.LagPortDto; import org.openkilda.model.LagLogicalPort; @@ -33,11 +34,12 @@ public class LagPortMapperTest { @Test public void mapLagPortToDtoTest() { LagLogicalPort lagLogicalPort = new LagLogicalPort(SWITCH_ID, LAG_PORT, - Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2)); + Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2), true); LagPortDto dto = LagPortMapper.INSTANCE.map(lagLogicalPort); assertEquals(LAG_PORT, dto.getLogicalPortNumber()); assertEquals(PHYSICAL_PORT_1, dto.getPortNumbers().get(0).intValue()); assertEquals(PHYSICAL_PORT_2, dto.getPortNumbers().get(1).intValue()); + assertTrue(dto.isLacpReply()); } } diff --git a/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/SwitchOperationsServiceTest.java b/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/SwitchOperationsServiceTest.java index 0c15e7649df..12061b4686d 100644 --- a/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/SwitchOperationsServiceTest.java +++ b/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/SwitchOperationsServiceTest.java @@ -650,7 +650,7 @@ public void shouldReturnLagPorts() throws SwitchNotFoundException { createSwitch(TEST_SWITCH_ID); LagLogicalPort lagLogicalPort = new LagLogicalPort(TEST_SWITCH_ID, LAG_LOGICAL_PORT, - Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2)); + Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2), true); lagLogicalPortRepository.add(lagLogicalPort); Collection ports = switchOperationsService.getSwitchLagPorts(TEST_SWITCH_ID); @@ -659,6 +659,7 @@ public void shouldReturnLagPorts() throws SwitchNotFoundException { assertEquals(LAG_LOGICAL_PORT, ports.iterator().next().getLogicalPortNumber()); assertEquals(PHYSICAL_PORT_1, ports.iterator().next().getPhysicalPorts().get(0).getPortNumber()); assertEquals(PHYSICAL_PORT_2, ports.iterator().next().getPhysicalPorts().get(1).getPortNumber()); + assertTrue(ports.iterator().next().isLacpReply()); } @Test(expected = SwitchNotFoundException.class) diff --git a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortRequest.java b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortRequest.java index 2bcca8dc78b..8d4362bd642 100644 --- a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortRequest.java +++ b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortRequest.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Builder.Default; import lombok.Data; import lombok.NoArgsConstructor; @@ -27,6 +29,9 @@ @NoArgsConstructor @AllArgsConstructor @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +@Builder public class LagPortRequest { private Set portNumbers; + @Default + private Boolean lacpReply = true; } diff --git a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortResponse.java b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortResponse.java index d85e63a31fc..e4e6e690e8c 100644 --- a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortResponse.java +++ b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortResponse.java @@ -30,4 +30,5 @@ public class LagPortResponse { private int logicalPortNumber; private List portNumbers; + private boolean lacpReply; } diff --git a/src-java/northbound-service/northbound/src/main/java/org/openkilda/northbound/service/impl/SwitchServiceImpl.java b/src-java/northbound-service/northbound/src/main/java/org/openkilda/northbound/service/impl/SwitchServiceImpl.java index f66197c3ba4..5fc895efbfe 100644 --- a/src-java/northbound-service/northbound/src/main/java/org/openkilda/northbound/service/impl/SwitchServiceImpl.java +++ b/src-java/northbound-service/northbound/src/main/java/org/openkilda/northbound/service/impl/SwitchServiceImpl.java @@ -587,9 +587,11 @@ public CompletableFuture getSwitchConnections(SwitchI @Override public CompletableFuture createLag(SwitchId switchId, LagPortRequest lagPortDto) { - logger.info("Create Link aggregation group on switch {}, ports {}", switchId, lagPortDto.getPortNumbers()); + logger.info("Create Link aggregation group on switch {}, ports {}, LACP reply {}", + switchId, lagPortDto.getPortNumbers(), lagPortDto.getLacpReply()); - CreateLagPortRequest data = new CreateLagPortRequest(switchId, lagPortDto.getPortNumbers()); + CreateLagPortRequest data = new CreateLagPortRequest(switchId, lagPortDto.getPortNumbers(), + lagPortDto.getLacpReply()); CommandMessage request = new CommandMessage(data, System.currentTimeMillis(), RequestCorrelationId.getId()); return messagingChannel.sendAndGet(switchManagerTopic, request) @@ -617,7 +619,8 @@ public CompletableFuture updateLagPort( SwitchId switchId, int logicalPortNumber, LagPortRequest payload) { logger.info("Updating LAG logical port {} on {} with {}", logicalPortNumber, switchId, payload); - UpdateLagPortRequest request = new UpdateLagPortRequest(switchId, logicalPortNumber, payload.getPortNumbers()); + UpdateLagPortRequest request = new UpdateLagPortRequest(switchId, logicalPortNumber, payload.getPortNumbers(), + payload.getLacpReply()); CommandMessage message = new CommandMessage(request, System.currentTimeMillis(), RequestCorrelationId.getId()); return messagingChannel.sendAndGet(switchManagerTopic, message) .thenApply(org.openkilda.messaging.swmanager.response.LagPortResponse.class::cast) diff --git a/src-java/northbound-service/northbound/src/test/java/org/openkilda/northbound/converter/LagPortMapperTest.java b/src-java/northbound-service/northbound/src/test/java/org/openkilda/northbound/converter/LagPortMapperTest.java index 71a5f6115c4..99b31e89d58 100644 --- a/src-java/northbound-service/northbound/src/test/java/org/openkilda/northbound/converter/LagPortMapperTest.java +++ b/src-java/northbound-service/northbound/src/test/java/org/openkilda/northbound/converter/LagPortMapperTest.java @@ -16,6 +16,8 @@ package org.openkilda.northbound.converter; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import org.openkilda.messaging.nbtopology.response.LagPortDto; import org.openkilda.messaging.swmanager.response.LagPortResponse; @@ -41,23 +43,25 @@ public class LagPortMapperTest { @Test public void mapLagPortDtoTest() { LagPortDto response = new LagPortDto(LOGICAL_PORT_NUMBER_1, - Lists.newArrayList(PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2)); + Lists.newArrayList(PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2), true); org.openkilda.northbound.dto.v2.switches.LagPortResponse dto = lagMapper.map(response); assertEquals(LOGICAL_PORT_NUMBER_1, dto.getLogicalPortNumber()); assertEquals(PHYSICAL_PORT_NUMBER_1, dto.getPortNumbers().get(0).intValue()); assertEquals(PHYSICAL_PORT_NUMBER_2, dto.getPortNumbers().get(1).intValue()); + assertTrue(dto.isLacpReply()); } @Test public void mapLagResponseTest() { LagPortResponse response = new LagPortResponse(LOGICAL_PORT_NUMBER_1, - Sets.newHashSet(PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2)); + Sets.newHashSet(PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2), false); org.openkilda.northbound.dto.v2.switches.LagPortResponse dto = lagMapper.map(response); assertEquals(LOGICAL_PORT_NUMBER_1, dto.getLogicalPortNumber()); assertEquals(PHYSICAL_PORT_NUMBER_1, dto.getPortNumbers().get(0).intValue()); assertEquals(PHYSICAL_PORT_NUMBER_2, dto.getPortNumbers().get(1).intValue()); + assertFalse(dto.isLacpReply()); } @TestConfiguration diff --git a/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/ProtoConstants.java b/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/ProtoConstants.java index 24276a3d750..4708f1042fa 100644 --- a/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/ProtoConstants.java +++ b/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/ProtoConstants.java @@ -30,6 +30,7 @@ public static final class EthType { public static final long IPv4 = 0x0800; public static final long LLDP = 0x88CC; public static final long ARP = 0x0806; + public static final long SLOW_PROTOCOLS = 0x8809; } public static final class IpProto { diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/Constants.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/Constants.java index 7b0af324af9..5d8acf60881 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/Constants.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/Constants.java @@ -82,6 +82,8 @@ public static final class Priority { public static final int DEFAULT_FLOW_PRIORITY = FLOW_PRIORITY - 1; public static final int DEFAULT_FLOW_VLAN_STATS_PRIORITY = FLOW_PRIORITY - 10; public static final int DOUBLE_VLAN_FLOW_PRIORITY = FLOW_PRIORITY + 10; + public static final int LACP_RULE_PRIORITY = INGRESS_CUSTOMER_PORT_RULE_PRIORITY_MULTITABLE + 200; + public static final int DROP_LOOP_SLOW_PROTOCOLS_PRIORITY = LACP_RULE_PRIORITY + 10; public static final int MIRROR_FLOW_PRIORITY = FLOW_PRIORITY + 50; public static final int MIRROR_DOUBLE_VLAN_FLOW_PRIORITY = MIRROR_FLOW_PRIORITY + 10; diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/DataAdapter.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/DataAdapter.java index 24fa3efef04..35dd02a427e 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/DataAdapter.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/DataAdapter.java @@ -19,12 +19,14 @@ import org.openkilda.model.FlowPath; import org.openkilda.model.FlowTransitEncapsulation; import org.openkilda.model.KildaFeatureToggles; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.PathId; import org.openkilda.model.Switch; import org.openkilda.model.SwitchId; import org.openkilda.model.SwitchProperties; import org.openkilda.model.YFlow; +import java.util.List; import java.util.Map; import java.util.Set; @@ -44,5 +46,7 @@ public interface DataAdapter { Set getSwitchIslPorts(SwitchId switchId); + List getLagLogicalPorts(SwitchId switchId); + YFlow getYFlow(PathId pathId); } diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManager.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManager.java index 66e5ba7e0e3..7d7ee0b9a63 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManager.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManager.java @@ -57,4 +57,6 @@ List buildRulesForFlowPath(FlowPath flowPath, boolean filterOutUsed * Build all required service rules for ISL on specified port. */ List buildIslServiceRules(SwitchId switchId, int port, DataAdapter adapter); + + List buildLacpRules(SwitchId switchId, int logicalPort, DataAdapter adapter); } diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerConfig.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerConfig.java index be54838d4d2..f735fdc3f22 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerConfig.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerConfig.java @@ -46,6 +46,10 @@ public interface RuleManagerConfig extends Serializable { @Default("1") int getArpRateLimit(); // rate in packets per second + @Key("lacp-rate-limit") + @Default("100") + int getLacpRateLimit(); // rate in packets per second + // Size of packets used for discovery. Used to calculate service meters rate in KBPS @Key("disco-packet-size") @Default("250") @@ -59,6 +63,10 @@ public interface RuleManagerConfig extends Serializable { @Default("100") int getArpPacketSize(); // rate in bytes + @Key("lacp-packet-size") + @Default("150") + int getLacpPacketSize(); // rate in bytes + @Key("flow-meter-burst-coefficient") @Default("1.05") @Description("This coefficient is used to calculate burst size for flow meters. " @@ -92,6 +100,12 @@ public interface RuleManagerConfig extends Serializable { @Description("This is burst size for ARP rule meters in packets.") long getArpMeterBurstSizeInPackets(); + @Key("lacp-meter-burst-size-in-packets") + @Default("4096") + @Min(0) + @Description("This is burst size for LACP rule meters in packets.") + long getLacpMeterBurstSizeInPackets(); + @Key("flow-ping-magic-src-mac-address") @Default("00:26:E1:FF:FF:FE") String getFlowPingMagicSrcMacAddress(); diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerImpl.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerImpl.java index 62426b61e1d..a1b7dceb8fe 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerImpl.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerImpl.java @@ -35,6 +35,7 @@ import org.openkilda.model.FlowPath; import org.openkilda.model.FlowTransitEncapsulation; import org.openkilda.model.KildaFeatureToggles; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.MacAddress; import org.openkilda.model.MeterId; import org.openkilda.model.PathId; @@ -52,6 +53,7 @@ import org.openkilda.rulemanager.utils.Utils; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -200,6 +202,22 @@ List getServiceRuleGenerators(SwitchId switchId, DataAdapter adap islPorts.forEach(islPort -> generators.addAll(getIslServiceRuleGenerators(islPort))); } + List lacpPorts = adapter.getLagLogicalPorts(switchId) + .stream().filter(LagLogicalPort::isLacpReply).collect(toList()); + + if (!lacpPorts.isEmpty()) { + generators.add(serviceRulesFactory.getDropSlowProtocolsLoopRuleGenerator()); + + // we need to create only one meter for all Lacp reply rules. So first generator will receive parameter + // switchHasOtherLacpPackets = false + generators.add(serviceRulesFactory.getLacpReplyRuleGenerator( + lacpPorts.get(0).getLogicalPortNumber(), false)); + for (int i = 1; i < lacpPorts.size(); i++) { + generators.add(serviceRulesFactory.getLacpReplyRuleGenerator( + lacpPorts.get(i).getLogicalPortNumber(), true)); + } + } + Integer server42Port = switchProperties.getServer42Port(); Integer server42Vlan = switchProperties.getServer42Vlan(); MacAddress server42MacAddress = switchProperties.getServer42MacAddress(); @@ -436,9 +454,23 @@ public List buildIslServiceRules(SwitchId switchId, int port, DataA generators.add(serviceRulesFactory .getServer42IslRttInputRuleGenerator(switchProperties.getServer42Port(), port)); } - return generators.stream() - .flatMap(g -> g.generateCommands(sw).stream()) - .collect(toList()); + return generateRules(sw, generators); + } + + @Override + public List buildLacpRules(SwitchId switchId, int logicalPort, DataAdapter adapter) { + boolean switchHasOtherLacpPorts = adapter.getLagLogicalPorts(switchId).stream() + .filter(LagLogicalPort::isLacpReply) + .anyMatch(port -> port.getLogicalPortNumber() != logicalPort); + + List generators = Lists.newArrayList( + serviceRulesFactory.getLacpReplyRuleGenerator(logicalPort, switchHasOtherLacpPorts)); + if (!switchHasOtherLacpPorts) { + generators.add(serviceRulesFactory.getDropSlowProtocolsLoopRuleGenerator()); + } + + Switch sw = adapter.getSwitch(switchId); + return postProcessCommands(generateRules(sw, generators)); } private List buildSharedEndpointYFlowCommands(List flowPaths, DataAdapter adapter) { diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/InMemoryDataAdapter.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/InMemoryDataAdapter.java index 49b45260ce0..ba2c9b91021 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/InMemoryDataAdapter.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/InMemoryDataAdapter.java @@ -21,6 +21,7 @@ import org.openkilda.model.FlowPath; import org.openkilda.model.FlowTransitEncapsulation; import org.openkilda.model.KildaFeatureToggles; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.PathId; import org.openkilda.model.Switch; import org.openkilda.model.SwitchId; @@ -31,6 +32,8 @@ import lombok.Builder; import lombok.Value; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; @@ -44,6 +47,7 @@ public class InMemoryDataAdapter implements DataAdapter { Map switches; Map switchProperties; Map> switchIslPorts; + Map> switchLagPorts; KildaFeatureToggles featureToggles; Map yFlows; @@ -103,4 +107,13 @@ public Set getSwitchIslPorts(SwitchId switchId) { } return result; } + + @Override + public List getLagLogicalPorts(SwitchId switchId) { + List result = switchLagPorts.getOrDefault(switchId, Collections.emptyList()); + if (result == null) { + throw new IllegalStateException(format("Switch lag ports for '%s' not found.", switchId)); + } + return result; + } } diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/PersistenceDataAdapter.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/PersistenceDataAdapter.java index 30d39b4aed4..378dc4766e0 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/PersistenceDataAdapter.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/PersistenceDataAdapter.java @@ -20,6 +20,7 @@ import org.openkilda.model.FlowPath; import org.openkilda.model.FlowTransitEncapsulation; import org.openkilda.model.KildaFeatureToggles; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.PathId; import org.openkilda.model.Switch; import org.openkilda.model.SwitchFeature; @@ -32,6 +33,7 @@ import org.openkilda.persistence.repositories.FlowRepository; import org.openkilda.persistence.repositories.IslRepository; import org.openkilda.persistence.repositories.KildaFeatureTogglesRepository; +import org.openkilda.persistence.repositories.LagLogicalPortRepository; import org.openkilda.persistence.repositories.RepositoryFactory; import org.openkilda.persistence.repositories.SwitchPropertiesRepository; import org.openkilda.persistence.repositories.SwitchRepository; @@ -43,6 +45,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -60,6 +63,7 @@ public class PersistenceDataAdapter implements DataAdapter { private final TransitVlanRepository transitVlanRepository; private final VxlanRepository vxlanRepository; private final IslRepository islRepository; + private final LagLogicalPortRepository lagLogicalPortRepository; private final KildaFeatureTogglesRepository featureTogglesRepository; private final Set pathIds; @@ -71,6 +75,7 @@ public class PersistenceDataAdapter implements DataAdapter { private Map switchCache; private Map switchPropertiesCache; private Map> switchIslPortsCache; + private Map> switchLagPortsCache; private KildaFeatureToggles featureToggles; private Map yFlowCache; @@ -88,6 +93,7 @@ public PersistenceDataAdapter(PersistenceManager persistenceManager, transitVlanRepository = repositoryFactory.createTransitVlanRepository(); vxlanRepository = repositoryFactory.createVxlanRepository(); islRepository = repositoryFactory.createIslRepository(); + lagLogicalPortRepository = repositoryFactory.createLagLogicalPortRepository(); featureTogglesRepository = repositoryFactory.createFeatureTogglesRepository(); this.pathIds = pathIds; @@ -178,6 +184,14 @@ public Set getSwitchIslPorts(SwitchId switchId) { return switchIslPortsCache.getOrDefault(switchId, Collections.emptySet()); } + @Override + public List getLagLogicalPorts(SwitchId switchId) { + if (switchLagPortsCache == null) { + switchLagPortsCache = lagLogicalPortRepository.findBySwitchIds(switchIds); + } + return switchLagPortsCache.getOrDefault(switchId, Collections.emptyList()); + } + @Override public YFlow getYFlow(PathId pathId) { if (yFlowCache == null) { diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/ServiceRulesGeneratorFactory.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/ServiceRulesGeneratorFactory.java index dd1cccb60b1..7152d2a0d45 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/ServiceRulesGeneratorFactory.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/ServiceRulesGeneratorFactory.java @@ -35,6 +35,8 @@ import org.openkilda.rulemanager.factory.generator.service.isl.EgressIslVlanRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.isl.EgressIslVxlanRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.isl.TransitIslVxlanRuleGenerator; +import org.openkilda.rulemanager.factory.generator.service.lacp.DropSlowProtocolsLoopRuleGenerator; +import org.openkilda.rulemanager.factory.generator.service.lacp.LacpReplyRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpIngressRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpInputPreDropRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpPostIngressOneSwitchRuleGenerator; @@ -337,4 +339,22 @@ public TransitIslVxlanRuleGenerator getTransitIslVxlanRuleGenerator(int islPort) .islPort(islPort) .build(); } + + /** + * Get LACP reply rule generator. + */ + public LacpReplyRuleGenerator getLacpReplyRuleGenerator(int inPort, boolean switchHasOtherLacpPorts) { + return LacpReplyRuleGenerator.builder() + .config(config) + .inPort(inPort) + .switchHasOtherLacpPorts(switchHasOtherLacpPorts) + .build(); + } + + /** + * Get drop slow protocol loop rule generator. + */ + public DropSlowProtocolsLoopRuleGenerator getDropSlowProtocolsLoopRuleGenerator() { + return DropSlowProtocolsLoopRuleGenerator.builder().build(); + } } diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGenerator.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGenerator.java new file mode 100644 index 00000000000..ab0ad105e8f --- /dev/null +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGenerator.java @@ -0,0 +1,67 @@ +/* Copyright 2021 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.rulemanager.factory.generator.service.lacp; + +import static org.openkilda.rulemanager.Constants.Priority.DROP_LOOP_SLOW_PROTOCOLS_PRIORITY; +import static org.openkilda.rulemanager.OfVersion.OF_12; + +import org.openkilda.model.MacAddress; +import org.openkilda.model.Switch; +import org.openkilda.model.cookie.ServiceCookie; +import org.openkilda.model.cookie.ServiceCookie.ServiceCookieTag; +import org.openkilda.rulemanager.Field; +import org.openkilda.rulemanager.FlowSpeakerData; +import org.openkilda.rulemanager.Instructions; +import org.openkilda.rulemanager.OfTable; +import org.openkilda.rulemanager.OfVersion; +import org.openkilda.rulemanager.ProtoConstants.EthType; +import org.openkilda.rulemanager.SpeakerData; +import org.openkilda.rulemanager.factory.RuleGenerator; +import org.openkilda.rulemanager.match.FieldMatch; + +import com.google.common.collect.Sets; +import lombok.Builder; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +@Builder +public class DropSlowProtocolsLoopRuleGenerator implements RuleGenerator { + + @Override + public List generateCommands(Switch sw) { + OfVersion ofVersion = OfVersion.of(sw.getOfVersion()); + if (ofVersion == OF_12) { + return Collections.emptyList(); + } + + Set match = Sets.newHashSet( + FieldMatch.builder().field(Field.ETH_TYPE).value(EthType.SLOW_PROTOCOLS).build(), + FieldMatch.builder().field(Field.ETH_SRC).value(sw.getSwitchId().toLong()).build(), + FieldMatch.builder().field(Field.ETH_DST).value(MacAddress.SLOW_PROTOCOLS.toLong()).build()); + + return Collections.singletonList(FlowSpeakerData.builder() + .switchId(sw.getSwitchId()) + .ofVersion(ofVersion) + .cookie(new ServiceCookie(ServiceCookieTag.DROP_SLOW_PROTOCOLS_LOOP_COOKIE)) + .table(OfTable.INPUT) + .priority(DROP_LOOP_SLOW_PROTOCOLS_PRIORITY) + .match(match) + .instructions(Instructions.builder().build()) + .build()); + } +} diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGenerator.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGenerator.java new file mode 100644 index 00000000000..78d681a48cf --- /dev/null +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGenerator.java @@ -0,0 +1,114 @@ +/* Copyright 2021 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.rulemanager.factory.generator.service.lacp; + +import static org.openkilda.model.MeterId.LACP_REPLY_METER_ID; +import static org.openkilda.rulemanager.Constants.Priority.LACP_RULE_PRIORITY; +import static org.openkilda.rulemanager.OfTable.INPUT; + +import org.openkilda.model.MacAddress; +import org.openkilda.model.MeterId; +import org.openkilda.model.Switch; +import org.openkilda.model.cookie.CookieBase.CookieType; +import org.openkilda.model.cookie.PortColourCookie; +import org.openkilda.rulemanager.Field; +import org.openkilda.rulemanager.FlowSpeakerData; +import org.openkilda.rulemanager.Instructions; +import org.openkilda.rulemanager.OfVersion; +import org.openkilda.rulemanager.ProtoConstants.EthType; +import org.openkilda.rulemanager.ProtoConstants.PortNumber; +import org.openkilda.rulemanager.ProtoConstants.PortNumber.SpecialPortType; +import org.openkilda.rulemanager.RuleManagerConfig; +import org.openkilda.rulemanager.SpeakerData; +import org.openkilda.rulemanager.action.PortOutAction; +import org.openkilda.rulemanager.factory.generator.service.MeteredServiceRuleGenerator; +import org.openkilda.rulemanager.match.FieldMatch; + +import com.google.common.collect.Sets; +import lombok.Builder; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class LacpReplyRuleGenerator extends MeteredServiceRuleGenerator { + + private final int inPort; + private final boolean switchHasOtherLacpPorts; + + @Builder + public LacpReplyRuleGenerator(RuleManagerConfig config, int inPort, boolean switchHasOtherLacpPorts) { + super(config); + this.inPort = inPort; + this.switchHasOtherLacpPorts = switchHasOtherLacpPorts; + } + + @Override + public List generateCommands(Switch sw) { + List commands = new ArrayList<>(); + Instructions instructions = Instructions.builder() + .applyActions(new ArrayList<>()) + .build(); + FlowSpeakerData flowCommand = buildRule(sw, instructions); + if (flowCommand == null) { + return Collections.emptyList(); + } else { + commands.add(flowCommand); + } + + MeterId meterId = LACP_REPLY_METER_ID; + SpeakerData meterCommand = generateMeterCommandForServiceRule(sw, meterId, config.getLacpRateLimit(), + config.getLacpMeterBurstSizeInPackets(), config.getLacpPacketSize()); + if (meterCommand != null) { + if (!switchHasOtherLacpPorts) { // meter was already created by other rules + commands.add(meterCommand); + } + addMeterToInstructions(meterId, sw, instructions); + } + instructions.getApplyActions().add(new PortOutAction(new PortNumber(SpecialPortType.CONTROLLER))); + if (meterCommand != null && !switchHasOtherLacpPorts) { + flowCommand.getDependsOn().add(meterCommand.getUuid()); + } + + return commands; + } + + private FlowSpeakerData buildRule(Switch sw, Instructions instructions) { + OfVersion ofVersion = OfVersion.of(sw.getOfVersion()); + if (ofVersion == OfVersion.OF_12) { + return null; + } + + return FlowSpeakerData.builder() + .switchId(sw.getSwitchId()) + .ofVersion(ofVersion) + .cookie(new PortColourCookie(CookieType.LACP_REPLY_INPUT, inPort)) + .table(INPUT) + .priority(LACP_RULE_PRIORITY) + .match(buildMatch()) + .instructions(instructions) + .dependsOn(new ArrayList<>()) + .build(); + } + + private Set buildMatch() { + return Sets.newHashSet( + FieldMatch.builder().field(Field.IN_PORT).value(inPort).build(), + FieldMatch.builder().field(Field.ETH_TYPE).value(EthType.SLOW_PROTOCOLS).build(), + FieldMatch.builder().field(Field.ETH_DST).value(MacAddress.SLOW_PROTOCOLS.toLong()).build()); + } +} diff --git a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/RuleManagerServiceRulesTest.java b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/RuleManagerServiceRulesTest.java index e7e21963566..cc512b4659a 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/RuleManagerServiceRulesTest.java +++ b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/RuleManagerServiceRulesTest.java @@ -19,10 +19,12 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.openkilda.rulemanager.Utils.LAG_PORTS; import static org.openkilda.rulemanager.Utils.buildSwitch; import static org.openkilda.rulemanager.Utils.buildSwitchProperties; import org.openkilda.model.KildaFeatureToggles; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.Switch; import org.openkilda.model.SwitchId; import org.openkilda.model.SwitchProperties; @@ -42,6 +44,8 @@ import org.openkilda.rulemanager.factory.generator.service.arp.ArpPostIngressRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.arp.ArpPostIngressVxlanRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.arp.ArpTransitRuleGenerator; +import org.openkilda.rulemanager.factory.generator.service.lacp.DropSlowProtocolsLoopRuleGenerator; +import org.openkilda.rulemanager.factory.generator.service.lacp.LacpReplyRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpIngressRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpInputPreDropRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpPostIngressOneSwitchRuleGenerator; @@ -92,9 +96,9 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInSingleTableMode() { SwitchProperties switchProperties = buildSwitchProperties(sw, false); List generators = ruleManager.getServiceRuleGenerators( - switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false)); + switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false, LAG_PORTS)); - assertEquals(7, generators.size()); + assertEquals(10, generators.size()); assertTrue(generators.stream().anyMatch(g -> g instanceof TableDefaultRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof BroadCastDiscoveryRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof UniCastDiscoveryRuleGenerator)); @@ -102,6 +106,8 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInSingleTableMode() { assertTrue(generators.stream().anyMatch(g -> g instanceof BfdCatchRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof RoundTripLatencyRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof UnicastVerificationVxlanRuleGenerator)); + assertTrue(generators.stream().anyMatch(g -> g instanceof DropSlowProtocolsLoopRuleGenerator)); + assertEquals(2, generators.stream().filter(g -> g instanceof LacpReplyRuleGenerator).count()); } @Test @@ -111,9 +117,9 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInMultiTableMode() { SwitchProperties switchProperties = buildSwitchProperties(sw, true); List generators = ruleManager.getServiceRuleGenerators( - switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false)); + switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false, LAG_PORTS)); - assertEquals(18, generators.size()); + assertEquals(21, generators.size()); assertTrue(generators.stream().anyMatch(g -> g instanceof BroadCastDiscoveryRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof UniCastDiscoveryRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof DropDiscoveryLoopRuleGenerator)); @@ -123,6 +129,8 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInMultiTableMode() { assertEquals(4, generators.stream().filter(g -> g instanceof TableDefaultRuleGenerator).count()); assertEquals(2, generators.stream().filter(g -> g instanceof TablePassThroughDefaultRuleGenerator).count()); + assertEquals(1, generators.stream().filter(g -> g instanceof DropSlowProtocolsLoopRuleGenerator).count()); + assertEquals(2, generators.stream().filter(g -> g instanceof LacpReplyRuleGenerator).count()); assertTrue(generators.stream().anyMatch(g -> g instanceof LldpPostIngressRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof LldpPostIngressVxlanRuleGenerator)); @@ -139,7 +147,7 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInMultiTableModeWithSw SwitchProperties switchProperties = buildSwitchProperties(sw, true, true, true); List generators = ruleManager.getServiceRuleGenerators( - switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false)); + switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false, null)); assertEquals(24, generators.size()); assertTrue(generators.stream().anyMatch(g -> g instanceof BroadCastDiscoveryRuleGenerator)); @@ -171,15 +179,18 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInMultiTableModeWithAl SwitchProperties switchProperties = buildSwitchProperties(sw, true, true, true, true, RttState.ENABLED); List generators = ruleManager.getServiceRuleGenerators( - switchId, buildAdapter(switchId, switchProperties, Sets.newHashSet(ISL_PORT), true)); + switchId, buildAdapter(switchId, switchProperties, Sets.newHashSet(ISL_PORT), true, LAG_PORTS)); - assertEquals(34, generators.size()); + assertEquals(37, generators.size()); assertTrue(generators.stream().anyMatch(g -> g instanceof BroadCastDiscoveryRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof UniCastDiscoveryRuleGenerator)); assertEquals(4, generators.stream().filter(g -> g instanceof TableDefaultRuleGenerator).count()); assertEquals(2, generators.stream().filter(g -> g instanceof TablePassThroughDefaultRuleGenerator).count()); + assertEquals(4, generators.stream().filter(g -> g instanceof TableDefaultRuleGenerator).count()); + assertEquals(2, generators.stream().filter(g -> g instanceof TablePassThroughDefaultRuleGenerator).count()); + assertTrue(generators.stream().anyMatch(g -> g instanceof LldpPostIngressRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof LldpPostIngressVxlanRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof LldpPostIngressOneSwitchRuleGenerator)); @@ -206,11 +217,16 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInMultiTableModeWithAl } private DataAdapter buildAdapter( - SwitchId switchId, SwitchProperties switchProperties, Set islPorts, boolean server42) { + SwitchId switchId, SwitchProperties switchProperties, Set islPorts, boolean server42, + List lagLogicalPorts) { Map switchPropertiesMap = new HashMap<>(); switchPropertiesMap.put(switchId, switchProperties); Map> islMap = new HashMap<>(); islMap.putIfAbsent(switchId, islPorts); + Map> lagMap = new HashMap<>(); + if (lagLogicalPorts != null) { + lagMap.put(switchId, lagLogicalPorts); + } return InMemoryDataAdapter.builder() .switchProperties(switchPropertiesMap) .switchIslPorts(islMap) @@ -218,6 +234,7 @@ private DataAdapter buildAdapter( .server42FlowRtt(server42) .server42IslRtt(server42) .build()) + .switchLagPorts(lagMap) .build(); } } diff --git a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/Utils.java b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/Utils.java index 01dddc82518..1aabe8617ed 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/Utils.java +++ b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/Utils.java @@ -18,6 +18,7 @@ import static java.lang.String.format; import static java.util.stream.Collectors.toSet; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.MacAddress; import org.openkilda.model.Switch; import org.openkilda.model.SwitchFeature; @@ -28,9 +29,11 @@ import org.openkilda.rulemanager.match.FieldMatch; import org.openkilda.rulemanager.utils.RoutingMetadata; +import com.google.common.collect.Lists; import lombok.Value; import org.junit.Assert; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -42,6 +45,23 @@ public final class Utils { public static final int SERVER_42_PORT = 42; public static final int SERVER_42_VLAN = 142; public static final MacAddress SERVER_42_MAC_ADDRESS = new MacAddress("42:42:42:42:42:42"); + public static final SwitchId SWITCH_ID = new SwitchId(1L); + public static final int LAG_PORT_NUMBER_1 = 1; + public static final int LAG_PORT_NUMBER_2 = 2; + public static final int LAG_PORT_NUMBER_3 = 3; + public static final int PHYS_PORT_1 = 3; + public static final int PHYS_PORT_2 = 3; + public static final int PHYS_PORT_3 = 3; + public static final int PHYS_PORT_4 = 3; + + public static final LagLogicalPort LAG_PORT_1 = new LagLogicalPort(SWITCH_ID, LAG_PORT_NUMBER_1, + Lists.newArrayList(PHYS_PORT_1, PHYS_PORT_2), true); + public static final LagLogicalPort LAG_PORT_2 = new LagLogicalPort(SWITCH_ID, LAG_PORT_NUMBER_2, + new ArrayList(), true); + public static final LagLogicalPort LAG_PORT_3 = new LagLogicalPort(SWITCH_ID, LAG_PORT_NUMBER_3, + Lists.newArrayList(PHYS_PORT_3, PHYS_PORT_4), false); + public static final List LAG_PORTS = Lists.newArrayList(LAG_PORT_1, LAG_PORT_2, LAG_PORT_3); + /** * Build switch object for tests. @@ -57,7 +77,7 @@ public static Switch buildSwitch(SwitchId switchId, String version, Set features) { - return buildSwitch(new SwitchId(1L), version, features); + return buildSwitch(SWITCH_ID, version, features); } public static Switch buildSwitch(SwitchId switchId, Set features) { diff --git a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGeneratorTest.java b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGeneratorTest.java new file mode 100644 index 00000000000..8daa7bfb86c --- /dev/null +++ b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGeneratorTest.java @@ -0,0 +1,76 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.rulemanager.factory.generator.service.lacp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.openkilda.rulemanager.Constants.Priority.DROP_LOOP_SLOW_PROTOCOLS_PRIORITY; +import static org.openkilda.rulemanager.Utils.assertEqualsMatch; +import static org.openkilda.rulemanager.Utils.buildSwitch; +import static org.openkilda.rulemanager.Utils.getCommand; + +import org.openkilda.model.MacAddress; +import org.openkilda.model.Switch; +import org.openkilda.model.cookie.ServiceCookie; +import org.openkilda.model.cookie.ServiceCookie.ServiceCookieTag; +import org.openkilda.rulemanager.Field; +import org.openkilda.rulemanager.FlowSpeakerData; +import org.openkilda.rulemanager.Instructions; +import org.openkilda.rulemanager.OfTable; +import org.openkilda.rulemanager.ProtoConstants.EthType; +import org.openkilda.rulemanager.SpeakerData; +import org.openkilda.rulemanager.match.FieldMatch; + +import com.google.common.collect.Sets; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class DropSlowProtocolsLoopRuleGeneratorTest { + private DropSlowProtocolsLoopRuleGenerator generator; + + @Before + public void setup() { + generator = DropSlowProtocolsLoopRuleGenerator.builder().build(); + } + + @Test + public void shouldBuildCorrectRule() { + Switch sw = buildSwitch("OF_13", Collections.emptySet()); + List commands = generator.generateCommands(sw); + + assertEquals(1, commands.size()); + + FlowSpeakerData flowCommandData = getCommand(FlowSpeakerData.class, commands); + assertEquals(sw.getSwitchId(), flowCommandData.getSwitchId()); + assertEquals(sw.getOfVersion(), flowCommandData.getOfVersion().toString()); + assertTrue(flowCommandData.getDependsOn().isEmpty()); + + assertEquals(new ServiceCookie(ServiceCookieTag.DROP_SLOW_PROTOCOLS_LOOP_COOKIE), flowCommandData.getCookie()); + assertEquals(OfTable.INPUT, flowCommandData.getTable()); + assertEquals(DROP_LOOP_SLOW_PROTOCOLS_PRIORITY, flowCommandData.getPriority()); + assertEquals(Instructions.builder().build(), flowCommandData.getInstructions()); + + Set expectedMatch = Sets.newHashSet( + FieldMatch.builder().field(Field.ETH_TYPE).value(EthType.SLOW_PROTOCOLS).build(), + FieldMatch.builder().field(Field.ETH_SRC).value(sw.getSwitchId().toLong()).build(), + FieldMatch.builder().field(Field.ETH_DST).value(MacAddress.SLOW_PROTOCOLS.toLong()).build()); + assertEqualsMatch(expectedMatch, flowCommandData.getMatch()); + } +} diff --git a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGeneratorTest.java b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGeneratorTest.java new file mode 100644 index 00000000000..7a2d9aa0e61 --- /dev/null +++ b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGeneratorTest.java @@ -0,0 +1,134 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.rulemanager.factory.generator.service.lacp; + +import static com.google.common.collect.Lists.newArrayList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.openkilda.model.SwitchFeature.METERS; +import static org.openkilda.model.SwitchFeature.PKTPS_FLAG; +import static org.openkilda.rulemanager.Constants.Priority.LACP_RULE_PRIORITY; +import static org.openkilda.rulemanager.Utils.assertEqualsMatch; +import static org.openkilda.rulemanager.Utils.buildSwitch; +import static org.openkilda.rulemanager.Utils.getCommand; + +import org.openkilda.model.MacAddress; +import org.openkilda.model.MeterId; +import org.openkilda.model.Switch; +import org.openkilda.model.cookie.CookieBase.CookieType; +import org.openkilda.model.cookie.PortColourCookie; +import org.openkilda.rulemanager.Field; +import org.openkilda.rulemanager.FlowSpeakerData; +import org.openkilda.rulemanager.Instructions; +import org.openkilda.rulemanager.MeterFlag; +import org.openkilda.rulemanager.MeterSpeakerData; +import org.openkilda.rulemanager.OfTable; +import org.openkilda.rulemanager.ProtoConstants.EthType; +import org.openkilda.rulemanager.ProtoConstants.PortNumber; +import org.openkilda.rulemanager.ProtoConstants.PortNumber.SpecialPortType; +import org.openkilda.rulemanager.RuleManagerConfig; +import org.openkilda.rulemanager.SpeakerData; +import org.openkilda.rulemanager.action.PortOutAction; +import org.openkilda.rulemanager.match.FieldMatch; + +import com.google.common.collect.Sets; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public class LacpReplyRuleGeneratorTest { + public static final int LOGICAL_PORT = 1234; + private static RuleManagerConfig config; + + @BeforeClass + public static void init() { + config = mock(RuleManagerConfig.class); + when(config.getLacpPacketSize()).thenReturn(150); + when(config.getLacpRateLimit()).thenReturn(100); + when(config.getLacpMeterBurstSizeInPackets()).thenReturn(1000L); + } + + @Test + public void shouldBuildCorrectRuleWithoutOtherLacpPorts() { + LacpReplyRuleGenerator generator = buildGenerator(false); + Switch sw = buildSwitch("OF_13", Sets.newHashSet(METERS, PKTPS_FLAG)); + List commands = generator.generateCommands(sw); + + assertEquals(2, commands.size()); + + FlowSpeakerData flowCommandData = getCommand(FlowSpeakerData.class, commands); + MeterSpeakerData meterCommandData = getCommand(MeterSpeakerData.class, commands); + + assertLacpReplyFlow(sw, flowCommandData, newArrayList(meterCommandData.getUuid())); + + assertEquals(sw.getSwitchId(), meterCommandData.getSwitchId()); + assertEquals(MeterId.LACP_REPLY_METER_ID, meterCommandData.getMeterId()); + assertEquals(config.getLacpRateLimit(), meterCommandData.getRate()); + assertEquals(config.getLacpMeterBurstSizeInPackets(), meterCommandData.getBurst()); + assertEquals(3, meterCommandData.getFlags().size()); + assertTrue(Sets.newHashSet(MeterFlag.BURST, MeterFlag.STATS, MeterFlag.PKTPS) + .containsAll(meterCommandData.getFlags())); + assertTrue(meterCommandData.getDependsOn().isEmpty()); + } + + @Test + public void shouldBuildCorrectRuleWithOtherLacpPorts() { + LacpReplyRuleGenerator generator = buildGenerator(true); + Switch sw = buildSwitch("OF_13", Sets.newHashSet(METERS, PKTPS_FLAG)); + List commands = generator.generateCommands(sw); + + assertEquals(1, commands.size()); + + FlowSpeakerData flowCommandData = getCommand(FlowSpeakerData.class, commands); + assertLacpReplyFlow(sw, flowCommandData, newArrayList()); + } + + private static void assertLacpReplyFlow(Switch sw, FlowSpeakerData flowCommandData, List expectedDependsOn) { + assertEquals(sw.getSwitchId(), flowCommandData.getSwitchId()); + assertEquals(sw.getOfVersion(), flowCommandData.getOfVersion().toString()); + assertEquals(expectedDependsOn, new ArrayList<>(flowCommandData.getDependsOn())); + + assertEquals(new PortColourCookie(CookieType.LACP_REPLY_INPUT, LOGICAL_PORT), flowCommandData.getCookie()); + assertEquals(OfTable.INPUT, flowCommandData.getTable()); + assertEquals(LACP_RULE_PRIORITY, flowCommandData.getPriority()); + + Instructions expectedInstructions = Instructions.builder() + .goToMeter(MeterId.LACP_REPLY_METER_ID) + .applyActions(newArrayList(new PortOutAction(new PortNumber(SpecialPortType.CONTROLLER)))) + .build(); + assertEquals(expectedInstructions, flowCommandData.getInstructions()); + + Set expectedMatch = Sets.newHashSet( + FieldMatch.builder().field(Field.IN_PORT).value(LOGICAL_PORT).build(), + FieldMatch.builder().field(Field.ETH_TYPE).value(EthType.SLOW_PROTOCOLS).build(), + FieldMatch.builder().field(Field.ETH_DST).value(MacAddress.SLOW_PROTOCOLS.toLong()).build()); + assertEqualsMatch(expectedMatch, flowCommandData.getMatch()); + } + + private LacpReplyRuleGenerator buildGenerator(boolean hasOtherLacpPorts) { + return LacpReplyRuleGenerator.builder() + .inPort(LOGICAL_PORT) + .config(config) + .switchHasOtherLacpPorts(hasOtherLacpPorts) + .build(); + } +} diff --git a/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandler.java b/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandler.java index a3c69fbe787..c57c05f7f12 100644 --- a/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandler.java +++ b/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandler.java @@ -21,6 +21,7 @@ import org.openkilda.model.FlowPathDirection; import org.openkilda.model.MeterId; import org.openkilda.model.SwitchId; +import org.openkilda.model.cookie.CookieBase.CookieType; import org.openkilda.model.cookie.FlowSegmentCookie; import org.openkilda.model.cookie.ServiceCookie; import org.openkilda.wfm.share.utils.MetricFormatter; @@ -100,8 +101,12 @@ public void handleStatsEntry(StatVlanDescriptor descriptor) { @Override public void handleStatsEntry(DummyMeterDescriptor descriptor) { + //TODO(snikitin) Need to find some way find cookie by meterId TagsFormatter tags = initTags(); - if (isMeterIdOfDefaultRule(statsEntry.getMeterId())) { + if (statsEntry.getMeterId() == MeterId.LACP_REPLY_METER_ID.getValue()) { + tags.addCookieHexTag(getCookieTagForPortColorCookie(CookieType.LACP_REPLY_INPUT)); + emitServiceMeterPoints(tags); + } else if (isMeterIdOfDefaultRule(statsEntry.getMeterId())) { tags.addCookieHexTag(new ServiceCookie(new MeterId(statsEntry.getMeterId()))); emitServiceMeterPoints(tags); } else { @@ -157,4 +162,8 @@ private TagsFormatter initTags() { tags.addMeterIdTag(statsEntry.getMeterId()); return tags; } + + static String getCookieTagForPortColorCookie(CookieType cookieType) { + return String.format("0x8%02X00000XXXXXXXX", cookieType.getValue()); + } } diff --git a/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/TagsFormatter.java b/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/TagsFormatter.java index 43ad3113ade..1107417f07c 100644 --- a/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/TagsFormatter.java +++ b/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/TagsFormatter.java @@ -64,8 +64,12 @@ public void addCookieTag(Long cookie) { tags.put("cookie", cookie != null ? String.valueOf(cookie) : UNKNOWN_COOKIE_TAG_VALUE); } + public void addCookieHexTag(String cookieTag) { + tags.put("cookieHex", cookieTag); + } + public void addCookieHexTag(CookieBase cookie) { - tags.put("cookieHex", cookie != null ? String.valueOf(cookie) : UNKNOWN_COOKIE_TAG_VALUE); + addCookieHexTag(cookie != null ? String.valueOf(cookie) : UNKNOWN_COOKIE_TAG_VALUE); } public void addCookieTypeTag(CookieType type) { diff --git a/src-java/stats-topology/stats-storm-topology/src/test/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandlerTest.java b/src-java/stats-topology/stats-storm-topology/src/test/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandlerTest.java new file mode 100644 index 00000000000..5e598ebc458 --- /dev/null +++ b/src-java/stats-topology/stats-storm-topology/src/test/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandlerTest.java @@ -0,0 +1,33 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.wfm.topology.stats.service; + +import static org.junit.Assert.assertEquals; + +import org.openkilda.model.cookie.CookieBase.CookieType; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class MeterStatsHandlerTest { + @Test + public void shouldRefreshCommonFlowsCookieCache() { + assertEquals("0x80E00000XXXXXXXX", MeterStatsHandler.getCookieTagForPortColorCookie( + CookieType.LACP_REPLY_INPUT)); + } +} diff --git a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequest.java b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequest.java index ba797f991ed..c23aaf7b931 100644 --- a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequest.java +++ b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequest.java @@ -28,5 +28,6 @@ public class CreateLagPortRequest extends CommandData { SwitchId switchId; Set portNumbers; + boolean lacpReply; } diff --git a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/UpdateLagPortRequest.java b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/UpdateLagPortRequest.java index b7ff5058100..d6ac4743c20 100644 --- a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/UpdateLagPortRequest.java +++ b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/UpdateLagPortRequest.java @@ -29,4 +29,5 @@ public class UpdateLagPortRequest extends CommandData { SwitchId switchId; int logicalPortNumber; Set targetPorts; + boolean lacpReply; } diff --git a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/response/LagPortResponse.java b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/response/LagPortResponse.java index 9779eed9e57..cbddad1bd12 100644 --- a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/response/LagPortResponse.java +++ b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/response/LagPortResponse.java @@ -32,4 +32,5 @@ public class LagPortResponse extends InfoData { int logicalPortNumber; Set portNumbers; + boolean lacpReply; } diff --git a/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequestTest.java b/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequestTest.java index b98ba31f9bf..2db9ec8e649 100644 --- a/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequestTest.java +++ b/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequestTest.java @@ -39,6 +39,6 @@ public void serializeLoop() throws Exception { } public static CreateLagPortRequest makeRequest() { - return new CreateLagPortRequest(new SwitchId(1), Sets.newHashSet(1, 2, 3)); + return new CreateLagPortRequest(new SwitchId(1), Sets.newHashSet(1, 2, 3), true); } } diff --git a/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/response/LagPortResponseTest.java b/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/response/LagPortResponseTest.java index cf24ccce9c7..ce6e8e2f304 100644 --- a/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/response/LagPortResponseTest.java +++ b/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/response/LagPortResponseTest.java @@ -38,6 +38,6 @@ public void serializeLoop() throws Exception { } public static LagPortResponse makeResponse() { - return new LagPortResponse(1, Sets.newHashSet(1, 2, 3)); + return new LagPortResponse(1, Sets.newHashSet(1, 2, 3), false); } } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/HeavyOperationBolt.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/HeavyOperationBolt.java index f38efcdf644..2187f6d818a 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/HeavyOperationBolt.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/HeavyOperationBolt.java @@ -74,6 +74,8 @@ private void handleBuildExpectedEntities(Tuple input) throws PipelineException { SwitchEntities expectedEntities = new SwitchEntities(validationService.buildExpectedEntities(switchId)); message = new InfoMessage(expectedEntities, getCommandContext().getCorrelationId(), messageCookie); } catch (Exception e) { + log.error(String.format("Enable to build expected switch entities for switch %s. Error: %s", + switchId, e.getMessage()), e); ErrorData errorData = new ErrorData( ErrorType.INTERNAL_ERROR, "Enable to build expected switch entities.", e.getMessage()); message = new ErrorMessage(errorData, getCommandContext().getCorrelationId(), messageCookie); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java index cea99ddd850..16c20f68eba 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java @@ -140,8 +140,8 @@ public void init() { super.init(); LagPortOperationConfig config = new LagPortOperationConfig( - persistenceManager.getRepositoryFactory(), persistenceManager.getTransactionManager(), - topologyConfig.getBfdPortOffset(), topologyConfig.getBfdPortMaxNumber(), + persistenceManager, topologyConfig.getBfdPortOffset(), + topologyConfig.getBfdPortMaxNumber(), topologyConfig.getLagPortOffset(), topologyConfig.getLagPortMaxNumber(), topologyConfig.getLagPortPoolChunksCount(), topologyConfig.getLagPortPoolCacheSize()); log.info("LAG logical ports service config: {}", config); @@ -166,11 +166,14 @@ public void init() { serviceRegistry, "switch-rules", this, carrier -> new SwitchRuleService(carrier, persistenceManager, ruleManager)); createLagPortService = registerService( - serviceRegistry, "lag-create", this, carrier -> new CreateLagPortService(carrier, config)); + serviceRegistry, "lag-create", this, carrier -> new CreateLagPortService( + carrier, config, ruleManager)); updateLagPortService = registerService( - serviceRegistry, "lag-update", this, carrier -> new UpdateLagPortService(carrier, config)); + serviceRegistry, "lag-update", this, carrier -> new UpdateLagPortService( + carrier, config, ruleManager)); deleteLagPortService = registerService( - serviceRegistry, "lag-delete", this, carrier -> new DeleteLagPortService(carrier, config)); + serviceRegistry, "lag-delete", this, carrier -> new DeleteLagPortService( + carrier, config, ruleManager)); } @Override diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java index b87d85c45b2..09593ba0cab 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java @@ -20,18 +20,23 @@ import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent.ERROR; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent.LAG_INSTALLED; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent.NEXT; +import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent.SKIP_SPEAKER_COMMANDS_INSTALLATION; +import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent.SPEAKER_ENTITIES_INSTALLED; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.CREATE_LAG_IN_DB; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.FINISHED; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.FINISHED_WITH_ERROR; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.GRPC_COMMAND_SEND; +import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.SPEAKER_COMMAND_SEND; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.START; +import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.messaging.command.grpc.CreateOrUpdateLogicalPortRequest; import org.openkilda.messaging.info.InfoMessage; import org.openkilda.messaging.model.grpc.LogicalPort; import org.openkilda.messaging.swmanager.request.CreateLagPortRequest; import org.openkilda.messaging.swmanager.response.LagPortResponse; import org.openkilda.model.SwitchId; +import org.openkilda.wfm.topology.switchmanager.bolt.SwitchManagerHub.OfCommandAction; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; import org.openkilda.wfm.topology.switchmanager.error.SwitchManagerException; @@ -52,6 +57,7 @@ import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.util.HashSet; +import java.util.List; @Slf4j public class CreateLagPortFsm extends AbstractStateMachine< @@ -97,9 +103,16 @@ public static StateMachineBuilder targetPorts = new HashSet<>(request.getPortNumbers()); - lagLogicalPortNumber = lagPortOperationService.createLagPort(switchId, targetPorts); + lagLogicalPortNumber = lagPortOperationService.createLagPort(switchId, targetPorts, request.isLacpReply()); String ipAddress = lagPortOperationService.getSwitchIpAddress(switchId); grpcRequest = new CreateOrUpdateLogicalPortRequest( @@ -133,13 +146,35 @@ void sendGrpcRequest(CreateLagState from, CreateLagState to, CreateLagEvent even carrier.sendCommandToSpeaker(key, grpcRequest); } + void sendSpeakerCommands(CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { + if (!request.isLacpReply()) { + log.info("Skip sending OF commands to switch {} because LAG port {} doesn't require LACP. Key={}", + switchId, lagLogicalPortNumber, key); + fire(CreateLagEvent.SKIP_SPEAKER_COMMANDS_INSTALLATION); + return; + } + + log.info("Creating LACP commands for switch {} LAG port {}. Key={}", switchId, lagLogicalPortNumber, key); + List commands = lagPortOperationService + .buildLacpSpeakerCommands(switchId, lagLogicalPortNumber); + + log.info("Sending LACP commands {} to switch {} LAG port {}. Key={}", + commands, switchId, lagLogicalPortNumber, key); + carrier.sendOfCommandsToSpeaker(key, commands, OfCommandAction.INSTALL, switchId); + } + void lagInstalled(CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { log.info("LAG {} successfully installed on switch {}. Key={}", context.createdLogicalPort, switchId, key); } + void speakerCommandsInstalled( + CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { + log.info("LAG {} successfully installed on switch {}. Key={}", context.createdLogicalPort, switchId, key); + } + void finishedEnter(CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { LagPortResponse response = new LagPortResponse( - grpcRequest.getLogicalPortNumber(), grpcRequest.getPortNumbers()); + grpcRequest.getLogicalPortNumber(), grpcRequest.getPortNumbers(), request.isLacpReply()); InfoMessage message = new InfoMessage(response, System.currentTimeMillis(), key); carrier.response(key, message); } @@ -191,6 +226,7 @@ public enum CreateLagState { START, CREATE_LAG_IN_DB, GRPC_COMMAND_SEND, + SPEAKER_COMMAND_SEND, FINISHED_WITH_ERROR, FINISHED } @@ -198,6 +234,8 @@ public enum CreateLagState { public enum CreateLagEvent { NEXT, LAG_INSTALLED, + SKIP_SPEAKER_COMMANDS_INSTALLATION, + SPEAKER_ENTITIES_INSTALLED, ERROR } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java index cb82912eac2..77a68efca34 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java @@ -19,13 +19,17 @@ import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent.ERROR; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent.LAG_REMOVED; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent.NEXT; -import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.CREATE_GRPC_COMMAND; +import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent.SKIP_SPEAKER_ENTITIES_REMOVAL; +import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent.SPEAKER_ENTITIES_REMOVED; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.FINISHED; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.FINISHED_WITH_ERROR; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.GRPC_COMMAND_SEND; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.REMOVE_LAG_FROM_DB; +import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.SPEAKER_COMMAND_SEND; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.START; +import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.VALIDATE_REMOVE_REQUEST; +import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.messaging.command.grpc.DeleteLogicalPortRequest; import org.openkilda.messaging.info.InfoMessage; import org.openkilda.messaging.swmanager.request.DeleteLagPortRequest; @@ -33,6 +37,7 @@ import org.openkilda.model.LagLogicalPort; import org.openkilda.model.PhysicalPort; import org.openkilda.model.SwitchId; +import org.openkilda.wfm.topology.switchmanager.bolt.SwitchManagerHub.OfCommandAction; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; import org.openkilda.wfm.topology.switchmanager.error.LagPortNotFoundException; @@ -54,6 +59,7 @@ import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.util.Collections; +import java.util.List; import java.util.stream.Collectors; @Slf4j @@ -68,6 +74,7 @@ public class DeleteLagPortFsm extends AbstractStateMachine< private final DeleteLagPortRequest request; private final SwitchManagerCarrier carrier; private DeleteLogicalPortRequest grpcRequest; + private LagLogicalPort initialLagPort; private LagLogicalPort removedLagPort; public DeleteLagPortFsm(SwitchManagerCarrier carrier, String key, DeleteLagPortRequest request, @@ -94,12 +101,18 @@ public static StateMachineBuilder commands = lagPortOperationService + .buildLacpSpeakerCommands(switchId, initialLagPort.getLogicalPortNumber()); + + log.info("Sending LACP commands {} to switch {} LAG port {}. Key={}", + commands, switchId, initialLagPort.getLogicalPortNumber(), key); + carrier.sendOfCommandsToSpeaker(key, commands, OfCommandAction.DELETE, switchId); + } + void sendGrpcRequest(DeleteLagState from, DeleteLagState to, DeleteLagEvent event, DeleteLagContext context) { log.info("Sending delete LAG request {} to switch {}. Key={}", grpcRequest, switchId, key); carrier.sendCommandToSpeaker(key, grpcRequest); @@ -157,12 +188,12 @@ void finishedEnter(DeleteLagState from, DeleteLagState to, DeleteLagEvent event, if (removedLagPort != null) { response = new LagPortResponse( removedLagPort.getLogicalPortNumber(), removedLagPort.getPhysicalPorts().stream() - .map(PhysicalPort::getPortNumber).collect(Collectors.toSet())); + .map(PhysicalPort::getPortNumber).collect(Collectors.toSet()), removedLagPort.isLacpReply()); } else { // dummy response entity // TODO(surabujin): weird behaviour, can we be more correct? - response = new LagPortResponse(request.getLogicalPortNumber(), Collections.emptySet()); + response = new LagPortResponse(request.getLogicalPortNumber(), Collections.emptySet(), false); } InfoMessage message = new InfoMessage(response, System.currentTimeMillis(), key); @@ -202,7 +233,8 @@ protected void afterTransitionCausedException(DeleteLagState fromState, DeleteLa public enum DeleteLagState { START, REMOVE_LAG_FROM_DB, - CREATE_GRPC_COMMAND, + VALIDATE_REMOVE_REQUEST, + SPEAKER_COMMAND_SEND, GRPC_COMMAND_SEND, FINISHED_WITH_ERROR, FINISHED @@ -211,6 +243,8 @@ public enum DeleteLagState { public enum DeleteLagEvent { NEXT, LAG_REMOVED, + SKIP_SPEAKER_ENTITIES_REMOVAL, + SPEAKER_ENTITIES_REMOVED, ERROR } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java index fb2df371db8..4934bea2dae 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java @@ -40,9 +40,6 @@ import static org.openkilda.wfm.topology.switchmanager.fsm.SwitchSyncFsm.SwitchSyncState.SEND_MODIFY_COMMANDS; import static org.openkilda.wfm.topology.switchmanager.fsm.SwitchSyncFsm.SwitchSyncState.SEND_REMOVE_COMMANDS; -import org.openkilda.floodlight.api.request.rulemanager.FlowCommand; -import org.openkilda.floodlight.api.request.rulemanager.GroupCommand; -import org.openkilda.floodlight.api.request.rulemanager.MeterCommand; import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.messaging.command.grpc.CreateOrUpdateLogicalPortRequest; import org.openkilda.messaging.command.grpc.DeleteLogicalPortRequest; @@ -616,7 +613,7 @@ List> cleanupDependenciesAndBuildCommandBatches(List(); } - currentBatch.addAll(group.stream().map(this::toCommand).collect(Collectors.toList())); + currentBatch.addAll(OfCommand.toOfCommands(group)); } if (!currentBatch.isEmpty()) { batches.add(currentBatch); @@ -625,17 +622,6 @@ List> cleanupDependenciesAndBuildCommandBatches(List physicalPorts; + boolean lacpReply; +} diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/CreateLagPortService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/CreateLagPortService.java index 9da75f6b962..acf70b495f4 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/CreateLagPortService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/CreateLagPortService.java @@ -15,11 +15,15 @@ package org.openkilda.wfm.topology.switchmanager.service; +import org.openkilda.floodlight.api.response.SpeakerResponse; +import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; import org.openkilda.messaging.MessageCookie; import org.openkilda.messaging.error.ErrorData; +import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.info.InfoData; import org.openkilda.messaging.info.grpc.CreateOrUpdateLogicalPortResponse; import org.openkilda.messaging.swmanager.request.CreateLagPortRequest; +import org.openkilda.rulemanager.RuleManager; import org.openkilda.wfm.error.MessageDispatchException; import org.openkilda.wfm.error.UnexpectedInputException; import org.openkilda.wfm.share.utils.FsmExecutor; @@ -52,8 +56,8 @@ public class CreateLagPortService implements SwitchManagerHubService { private boolean active = true; - public CreateLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config) { - this.lagPortOperationService = new LagPortOperationService(config); + public CreateLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config, RuleManager ruleManager) { + this.lagPortOperationService = new LagPortOperationService(config, ruleManager); this.builder = CreateLagPortFsm.builder(); this.fsmExecutor = new FsmExecutor<>(CreateLagEvent.NEXT); this.carrier = carrier; @@ -89,7 +93,7 @@ public boolean isAllOperationsCompleted() { @Override public void timeout(@NonNull MessageCookie cookie) throws MessageDispatchException { - OperationTimeoutException error = new OperationTimeoutException("LAG create operation timeout"); + OperationTimeoutException error = new OperationTimeoutException("Create operation timeout"); fireFsmEvent(cookie, CreateLagEvent.ERROR, CreateLagContext.builder().error(error).build()); } @@ -103,6 +107,24 @@ public void dispatchWorkerMessage(InfoData payload, MessageCookie cookie) } } + @Override + public void dispatchWorkerMessage(SpeakerResponse payload, MessageCookie cookie) + throws UnexpectedInputException, MessageDispatchException { + if (payload instanceof SpeakerCommandResponse) { + SpeakerCommandResponse response = (SpeakerCommandResponse) payload; + if (response.isSuccess()) { + fireFsmEvent(cookie, CreateLagEvent.SPEAKER_ENTITIES_INSTALLED, CreateLagContext.builder().build()); + } else { + ErrorData errorData = new ErrorData(ErrorType.INTERNAL_ERROR, "OpenFlow commands failed", + response.getFailedCommandIds().values().toString()); + fireFsmEvent(cookie, CreateLagEvent.ERROR, + CreateLagContext.builder().error(new SpeakerFailureException(errorData)).build()); + } + } else { + throw new UnexpectedInputException(payload); + } + } + @Override public void dispatchErrorMessage(ErrorData payload, MessageCookie cookie) throws MessageDispatchException { diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/DeleteLagPortService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/DeleteLagPortService.java index fba6b5659f7..6076cd9aa04 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/DeleteLagPortService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/DeleteLagPortService.java @@ -15,12 +15,15 @@ package org.openkilda.wfm.topology.switchmanager.service; +import org.openkilda.floodlight.api.response.SpeakerResponse; +import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; import org.openkilda.messaging.MessageCookie; import org.openkilda.messaging.error.ErrorData; import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.info.InfoData; import org.openkilda.messaging.info.grpc.DeleteLogicalPortResponse; import org.openkilda.messaging.swmanager.request.DeleteLagPortRequest; +import org.openkilda.rulemanager.RuleManager; import org.openkilda.wfm.error.MessageDispatchException; import org.openkilda.wfm.error.UnexpectedInputException; import org.openkilda.wfm.share.utils.FsmExecutor; @@ -53,8 +56,8 @@ public class DeleteLagPortService implements SwitchManagerHubService { private boolean active = true; - public DeleteLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config) { - this.lagOperationService = new LagPortOperationService(config); + public DeleteLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config, RuleManager ruleManager) { + this.lagOperationService = new LagPortOperationService(config, ruleManager); this.builder = DeleteLagPortFsm.builder(); this.fsmExecutor = new FsmExecutor<>(DeleteLagEvent.NEXT); this.carrier = carrier; @@ -88,6 +91,24 @@ public void dispatchWorkerMessage(InfoData payload, MessageCookie cookie) } } + @Override + public void dispatchWorkerMessage(SpeakerResponse payload, MessageCookie cookie) + throws UnexpectedInputException, MessageDispatchException { + if (payload instanceof SpeakerCommandResponse) { + SpeakerCommandResponse response = (SpeakerCommandResponse) payload; + if (response.isSuccess()) { + fireFsmEvent(cookie, DeleteLagEvent.SPEAKER_ENTITIES_REMOVED, DeleteLagContext.builder().build()); + } else { + ErrorData errorData = new ErrorData(ErrorType.INTERNAL_ERROR, "OpenFlow commands failed", + response.getFailedCommandIds().values().toString()); + fireFsmEvent(cookie, DeleteLagEvent.ERROR, + DeleteLagContext.builder().error(new SpeakerFailureException(errorData)).build()); + } + } else { + throw new UnexpectedInputException(payload); + } + } + @Override public void dispatchErrorMessage(ErrorData payload, MessageCookie cookie) throws MessageDispatchException { DeleteLagContext context = DeleteLagContext.builder() diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java index eaeeda00fda..37f2606d62b 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java @@ -18,6 +18,7 @@ import static java.lang.String.format; import static org.openkilda.model.SwitchFeature.BFD; +import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.model.Flow; import org.openkilda.model.FlowMirrorPath; import org.openkilda.model.IpSocketAddress; @@ -41,11 +42,16 @@ import org.openkilda.persistence.repositories.SwitchPropertiesRepository; import org.openkilda.persistence.repositories.SwitchRepository; import org.openkilda.persistence.tx.TransactionManager; +import org.openkilda.rulemanager.DataAdapter; +import org.openkilda.rulemanager.RuleManager; +import org.openkilda.rulemanager.adapter.PersistenceDataAdapter; import org.openkilda.wfm.share.utils.PoolManager; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; import org.openkilda.wfm.topology.switchmanager.error.LagPortNotFoundException; import org.openkilda.wfm.topology.switchmanager.error.SwitchNotFoundException; +import org.openkilda.wfm.topology.switchmanager.model.LagRollbackData; +import org.openkilda.wfm.topology.switchmanager.model.LagRollbackData.LagRollbackDataBuilder; import org.openkilda.wfm.topology.switchmanager.service.configs.LagPortOperationConfig; import com.google.common.collect.Sets; @@ -78,13 +84,17 @@ public class LagPortOperationService { private final FlowRepository flowRepository; private final FlowMirrorPathRepository flowMirrorPathRepository; private final PortRepository portRepository; + private final RuleManager ruleManager; private final LagPortOperationConfig config; private final LRUMap> portNumberPool; - public LagPortOperationService(LagPortOperationConfig config) { + + + public LagPortOperationService(LagPortOperationConfig config, RuleManager ruleManager) { this.transactionManager = config.getTransactionManager(); + this.ruleManager = ruleManager; @NonNull RepositoryFactory repositoryFactory = config.getRepositoryFactory(); this.switchRepository = repositoryFactory.createSwitchRepository(); @@ -104,23 +114,28 @@ public LagPortOperationService(LagPortOperationConfig config) { /** * Create LAG logical port. */ - public int createLagPort(SwitchId switchId, Set targetPorts) { + public int createLagPort(SwitchId switchId, Set targetPorts, boolean lacpReply) { verifyTargetPortsInput(targetPorts); LagLogicalPort port = transactionManager.doInTransaction( - newCreateRetryPolicy(switchId), () -> createTransaction(switchId, targetPorts)); + newCreateRetryPolicy(switchId), () -> createTransaction(switchId, targetPorts, lacpReply)); return port.getLogicalPortNumber(); } /** * Update LAG logical port. */ - public Set updateLagPort(SwitchId switchId, int logicalPortNumber, Set targetPorts) { + public LagRollbackData updateLagPort( + SwitchId switchId, int logicalPortNumber, Set targetPorts, boolean lacpReply) { verifyTargetPortsInput(targetPorts); - Set targetsBeforeUpdate = new HashSet<>(); + LagRollbackDataBuilder rollbackDataBuilder = LagRollbackData.builder(); transactionManager.doInTransaction( newUpdateRetryPolicy(switchId), - () -> targetsBeforeUpdate.addAll(updateTransaction(switchId, logicalPortNumber, targetPorts))); - return targetsBeforeUpdate; + () -> { + LagRollbackData data = updateTransaction(switchId, logicalPortNumber, targetPorts, lacpReply); + rollbackDataBuilder.physicalPorts(data.getPhysicalPorts()); + rollbackDataBuilder.lacpReply(data.isLacpReply()); + }); + return rollbackDataBuilder.build(); } /** @@ -224,18 +239,31 @@ public String getSwitchIpAddress(SwitchId switchId) throws InvalidDataException, format("Switch %s has invalid IP address %s", sw, sw.getSocketAddress()))); } + /** + * Builds LACP commands witch will be sent to speaker. + */ + public List buildLacpSpeakerCommands(SwitchId switchId, int port) { + DataAdapter dataAdapter = PersistenceDataAdapter.builder() + .switchIds(Collections.singleton(switchId)) + .pathIds(Collections.emptySet()) + .persistenceManager(config.getPersistenceManager()) + .build(); + return OfCommand.toOfCommands(ruleManager.buildLacpRules(switchId, port, dataAdapter)); + } + private void verifyTargetPortsInput(Set targetPorts) { if (targetPorts == null || targetPorts.isEmpty()) { throw new InvalidDataException("Physical ports list is empty"); } } - private LagLogicalPort createTransaction(SwitchId switchId, Set targetPorts) { + private LagLogicalPort createTransaction(SwitchId switchId, Set targetPorts, boolean lacpReply) { Switch sw = querySwitch(switchId); // locate switch first to produce correct error if switch is missing ensureLagDataValid(sw, targetPorts); ensureNoLagCollisions(sw.getSwitchId(), targetPorts); - LagLogicalPort port = queryPoolManager(switchId).allocate(entityId -> newLagLogicalPort(switchId, entityId)); + LagLogicalPort port = queryPoolManager(switchId).allocate(entityId -> newLagLogicalPort( + switchId, entityId, lacpReply)); replacePhysicalPorts(port, switchId, targetPorts); log.info("Adding new LAG logical port entry into DB: {}", port); @@ -243,28 +271,32 @@ private LagLogicalPort createTransaction(SwitchId switchId, Set targetP return port; } - private Set updateTransaction(SwitchId switchId, int logicalPortNumber, Set targetPorts) { + private LagRollbackData updateTransaction( + SwitchId switchId, int logicalPortNumber, Set targetPorts, boolean lacpReply) { Switch sw = querySwitch(switchId); ensureLagDataValid(sw, targetPorts); ensureNoLagCollisions(switchId, targetPorts, logicalPortNumber); ensureTargetPortsBandwidthValid(sw, targetPorts, logicalPortNumber); LagLogicalPort port = queryLagPort(sw.getSwitchId(), logicalPortNumber); - Set targetsBeforeUpdate = port.getPhysicalPorts().stream() + LagRollbackData rollbackData = new LagRollbackData(port.getPhysicalPorts().stream() .map(PhysicalPort::getPortNumber) - .collect(Collectors.toSet()); + .collect(Collectors.toSet()), port.isLacpReply()); log.info( - "Updating LAG logical port #{} on {} entry desired target ports set {}, current target ports set {}", + "Updating LAG logical port #{} on {} entry desired target ports set {}, current target ports set {}. " + + "Desired LACP reply {}, current LACP reply {}", logicalPortNumber, switchId, - formatPortNumbersSet(targetPorts), formatPortNumbersSet(targetsBeforeUpdate)); + formatPortNumbersSet(targetPorts), formatPortNumbersSet(rollbackData.getPhysicalPorts()), + lacpReply, rollbackData.isLacpReply()); replacePhysicalPorts(port, switchId, targetPorts); + port.setLacpReply(lacpReply); - return targetsBeforeUpdate; + return rollbackData; } private LagLogicalPort deleteTransaction(SwitchId switchId, int logicalPortNumber) { LagLogicalPort port = ensureDeleteIsPossible(switchId, logicalPortNumber); - log.info("Removing LAG logical port entry into DB: {}", port); + log.info("Removing LAG logical port entry from DB: {}", port); lagLogicalPortRepository.remove(port); return port; } @@ -346,8 +378,8 @@ private Switch querySwitch(SwitchId switchId) throws SwitchNotFoundException { return switchRepository.findById(switchId).orElseThrow(() -> new SwitchNotFoundException(switchId)); } - private LagLogicalPort newLagLogicalPort(SwitchId switchId, long portNumber) { - return new LagLogicalPort(switchId, (int) portNumber, Collections.emptyList()); + private LagLogicalPort newLagLogicalPort(SwitchId switchId, long portNumber, boolean lacpReply) { + return new LagLogicalPort(switchId, (int) portNumber, Collections.emptyList(), lacpReply); } private LagLogicalPort queryLagPort(SwitchId switchId, int logicalPortNumber) { diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/SwitchRuleService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/SwitchRuleService.java index 5632bf3bb6a..c6106b58cbb 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/SwitchRuleService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/SwitchRuleService.java @@ -22,8 +22,6 @@ import static org.openkilda.wfm.topology.switchmanager.service.impl.PredicateBuilder.isInstallServiceRulesRequired; import org.openkilda.floodlight.api.request.rulemanager.FlowCommand; -import org.openkilda.floodlight.api.request.rulemanager.GroupCommand; -import org.openkilda.floodlight.api.request.rulemanager.MeterCommand; import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.floodlight.api.response.SpeakerResponse; import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; @@ -46,8 +44,6 @@ import org.openkilda.persistence.repositories.SwitchRepository; import org.openkilda.rulemanager.DataAdapter; import org.openkilda.rulemanager.FlowSpeakerData; -import org.openkilda.rulemanager.GroupSpeakerData; -import org.openkilda.rulemanager.MeterSpeakerData; import org.openkilda.rulemanager.RuleManager; import org.openkilda.rulemanager.SpeakerData; import org.openkilda.rulemanager.adapter.PersistenceDataAdapter; @@ -155,7 +151,7 @@ public void deleteRules(String key, SwitchRulesDeleteRequest request) { RuleManagerHelper.reverseDependencies(speakerData); List removeCommands = speakerData.stream() .filter(data -> toRemove.contains(data.getUuid())) - .map(this::toCommand) + .map(OfCommand::toOfCommand) .collect(Collectors.toList()); CommandsBatch deleteCommandsBatch = new CommandsBatch(OfCommandAction.DELETE, removeCommands); List commandsBatches = Lists.newArrayList(deleteCommandsBatch); @@ -167,7 +163,7 @@ public void deleteRules(String key, SwitchRulesDeleteRequest request) { .collect(Collectors.toList()); List installCommands = speakerData.stream() .filter(data -> toInstall.contains(data.getUuid())) - .map(this::toCommand) + .map(OfCommand::toOfCommand) .collect(Collectors.toList()); CommandsBatch installCommandsBatch = new CommandsBatch(OfCommandAction.INSTALL_IF_NOT_EXIST, installCommands); @@ -205,7 +201,7 @@ public void installRules(String key, SwitchRulesInstallRequest request) { List commands = speakerData.stream() .filter(data -> toInstall.contains(data.getUuid())) - .map(this::toCommand) + .map(OfCommand::toOfCommand) .collect(Collectors.toList()); CommandsBatch commandsBatch = new CommandsBatch(OfCommandAction.INSTALL_IF_NOT_EXIST, commands); CommandsQueue commandsQueue = new CommandsQueue(switchId, singletonList(commandsBatch)); @@ -234,17 +230,6 @@ private void processCommandsBatch(String key, CommandsQueue commandsQueue) { commandsQueue.getSwitchId()); } - private OfCommand toCommand(SpeakerData speakerData) { - if (speakerData instanceof FlowSpeakerData) { - return new FlowCommand((FlowSpeakerData) speakerData); - } else if (speakerData instanceof MeterSpeakerData) { - return new MeterCommand((MeterSpeakerData) speakerData); - } else if (speakerData instanceof GroupSpeakerData) { - return new GroupCommand((GroupSpeakerData) speakerData); - } - throw new IllegalStateException(format("Unknown speaker data type %s", speakerData)); - } - private void handleRulesResponse(String key, SwitchRulesResponse response) { carrier.cancelTimeoutCallback(key); InfoMessage message = new InfoMessage(response, System.currentTimeMillis(), key); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortService.java index 581c5dcfeeb..ebf6e7cb09d 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortService.java @@ -15,12 +15,15 @@ package org.openkilda.wfm.topology.switchmanager.service; +import org.openkilda.floodlight.api.response.SpeakerResponse; +import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; import org.openkilda.messaging.MessageCookie; import org.openkilda.messaging.error.ErrorData; import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.info.InfoData; import org.openkilda.messaging.info.grpc.CreateOrUpdateLogicalPortResponse; import org.openkilda.messaging.swmanager.request.UpdateLagPortRequest; +import org.openkilda.rulemanager.RuleManager; import org.openkilda.wfm.error.MessageDispatchException; import org.openkilda.wfm.error.UnexpectedInputException; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; @@ -49,8 +52,8 @@ public class UpdateLagPortService implements SwitchManagerHubService { private boolean active = true; - public UpdateLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config) { - this(carrier, new LagPortOperationService(config)); + public UpdateLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config, RuleManager ruleManager) { + this(carrier, new LagPortOperationService(config, ruleManager)); } public UpdateLagPortService(SwitchManagerCarrier carrier, LagPortOperationService operationService) { @@ -98,9 +101,26 @@ public void dispatchWorkerMessage(InfoData payload, MessageCookie cookie) } } + @Override + public void dispatchWorkerMessage(SpeakerResponse payload, MessageCookie cookie) + throws UnexpectedInputException, MessageDispatchException { + if (payload instanceof SpeakerCommandResponse) { + SpeakerCommandResponse response = (SpeakerCommandResponse) payload; + if (response.isSuccess()) { + process(cookie, (h, nested) -> h.dispatchFloodlightResponse(payload, nested)); + } else { + ErrorData errorData = new ErrorData(ErrorType.INTERNAL_ERROR, "OpenFlow commands failed", + response.getFailedCommandIds().values().toString()); + dispatchErrorMessage(errorData, cookie); + } + } else { + throw new UnexpectedInputException(payload); + } + } + @Override public void dispatchErrorMessage(ErrorData payload, MessageCookie cookie) throws MessageDispatchException { - process(cookie, (h, nested) -> h.dispatchGrpcResponse(payload, nested)); + process(cookie, (h, nested) -> h.dispatchErrorResponse(payload, nested)); } @Override diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/configs/LagPortOperationConfig.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/configs/LagPortOperationConfig.java index 6b96f37c6a9..9d81bd3e66b 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/configs/LagPortOperationConfig.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/configs/LagPortOperationConfig.java @@ -15,6 +15,7 @@ package org.openkilda.wfm.topology.switchmanager.service.configs; +import org.openkilda.persistence.PersistenceManager; import org.openkilda.persistence.repositories.RepositoryFactory; import org.openkilda.persistence.tx.TransactionManager; import org.openkilda.wfm.share.utils.PoolManager; @@ -26,6 +27,9 @@ @Value public class LagPortOperationConfig { + @NonNull + PersistenceManager persistenceManager; + @NonNull RepositoryFactory repositoryFactory; @@ -41,11 +45,11 @@ public class LagPortOperationConfig { @Builder public LagPortOperationConfig( - @NonNull RepositoryFactory repositoryFactory, @NonNull TransactionManager transactionManager, - int bfdPortOffset, int bfdPortMaxNumber, + @NonNull PersistenceManager persistenceManager, int bfdPortOffset, int bfdPortMaxNumber, int portNumberFirst, int portNumberLast, int poolChunksCount, int poolCacheSize) { - this.repositoryFactory = repositoryFactory; - this.transactionManager = transactionManager; + this.persistenceManager = persistenceManager; + this.repositoryFactory = persistenceManager.getRepositoryFactory(); + this.transactionManager = persistenceManager.getTransactionManager(); Preconditions.checkArgument(0 < bfdPortOffset, String.format( "BFD logical port offset bfdPortOffset==%d must be greater than 0", bfdPortOffset)); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandler.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandler.java index 3f9689c5388..8a3af5a9c5c 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandler.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandler.java @@ -15,8 +15,9 @@ package org.openkilda.wfm.topology.switchmanager.service.handler; +import org.openkilda.floodlight.api.request.rulemanager.OfCommand; +import org.openkilda.floodlight.api.response.SpeakerResponse; import org.openkilda.messaging.MessageCookie; -import org.openkilda.messaging.MessageData; import org.openkilda.messaging.command.grpc.CreateOrUpdateLogicalPortRequest; import org.openkilda.messaging.error.ErrorData; import org.openkilda.messaging.error.ErrorType; @@ -24,7 +25,9 @@ import org.openkilda.messaging.model.grpc.LogicalPortType; import org.openkilda.messaging.swmanager.request.UpdateLagPortRequest; import org.openkilda.messaging.swmanager.response.LagPortResponse; +import org.openkilda.wfm.topology.switchmanager.bolt.SwitchManagerHub.OfCommandAction; import org.openkilda.wfm.topology.switchmanager.error.SwitchManagerException; +import org.openkilda.wfm.topology.switchmanager.model.LagRollbackData; import org.openkilda.wfm.topology.switchmanager.service.LagPortOperationService; import org.openkilda.wfm.topology.switchmanager.service.SwitchManagerCarrier; @@ -34,6 +37,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -55,7 +59,7 @@ public class LagPortUpdateHandler { private final UpdateLagPortRequest goal; @VisibleForTesting - Set rollbackTargets; + LagRollbackData rollbackData; private final Set pendingSpeakerRequests = new HashSet<>(); @@ -73,14 +77,15 @@ public LagPortUpdateHandler( */ public void start() { Set targetPorts = new HashSet<>(goal.getTargetPorts()); - rollbackTargets = operationService.updateLagPort(goal.getSwitchId(), goal.getLogicalPortNumber(), targetPorts); + rollbackData = operationService.updateLagPort( + goal.getSwitchId(), goal.getLogicalPortNumber(), targetPorts, goal.isLacpReply()); CreateOrUpdateLogicalPortRequest request = newGrpcRequest(targetPorts); MessageCookie cookie = newMessageCookie(); log.info( - "Going to update {}, target ports set: {}", - formatLagPortReference(), formatTargetPorts(goal.getTargetPorts())); + "Going to update {}, target ports set: {}, target LACP reply {}", + formatLagPortReference(), formatTargetPorts(goal.getTargetPorts()), goal.isLacpReply()); carrier.sendCommandToSpeaker(request, cookie); pendingSpeakerRequests.add(cookie); } @@ -94,14 +99,42 @@ public void dispatchGrpcResponse(CreateOrUpdateLogicalPortResponse response, Mes return; } - log.info("{} have been updated", formatLagPortReference()); - carrier.response(requestKey, new LagPortResponse(goal.getLogicalPortNumber(), goal.getTargetPorts())); + if (rollbackData.isLacpReply() == goal.isLacpReply()) { + sendResponseToNorthbound(); + } else { + sendCommandsToFloodlight(); + } + } + + private void sendCommandsToFloodlight() { + List commands = operationService.buildLacpSpeakerCommands( + goal.getSwitchId(), goal.getLogicalPortNumber()); + MessageCookie floodlightMessageCookie = newMessageCookie(); + OfCommandAction action = goal.isLacpReply() ? OfCommandAction.INSTALL : OfCommandAction.DELETE; + + log.info("Sending {} commands {} to switch {} to update LAG port {} from {} to {}", + action, commands, goal.getSwitchId(), goal.getLogicalPortNumber(), rollbackData, goal); + carrier.sendOfCommandsToSpeaker(commands, action, goal.getSwitchId(), floodlightMessageCookie); + pendingSpeakerRequests.add(floodlightMessageCookie); + } + + /** + * Handle Floodlight response. + */ + public void dispatchFloodlightResponse(SpeakerResponse response, MessageCookie cookie) { + if (!pendingSpeakerRequests.remove(cookie)) { + logUnwantedResponse(response); + return; + } + + log.info("OpenFLow rules for {} have been updated", formatLagPortReference()); + sendResponseToNorthbound(); } /** - * Handle GRPC error response. + * Handle error response. */ - public void dispatchGrpcResponse(ErrorData response, MessageCookie cookie) { + public void dispatchErrorResponse(ErrorData response, MessageCookie cookie) { if (!pendingSpeakerRequests.remove(cookie)) { logUnwantedResponse(response); return; @@ -127,6 +160,12 @@ public boolean isCompleted() { return pendingSpeakerRequests.isEmpty(); } + private void sendResponseToNorthbound() { + log.info("{} have been updated", formatLagPortReference()); + carrier.response(requestKey, new LagPortResponse( + goal.getLogicalPortNumber(), goal.getTargetPorts(), goal.isLacpReply())); + } + private void fail(ErrorType type, String description) { String errorMessage; if (rollback()) { @@ -143,7 +182,8 @@ private void fail(ErrorType type, String description) { private boolean rollback() { try { - operationService.updateLagPort(goal.getSwitchId(), goal.getLogicalPortNumber(), rollbackTargets); + operationService.updateLagPort(goal.getSwitchId(), goal.getLogicalPortNumber(), + rollbackData.getPhysicalPorts(), rollbackData.isLacpReply()); } catch (SwitchManagerException e) { log.error("Unable to rollback DB update for {}: {}", formatLagPortReference(), e.getMessage()); return false; @@ -151,8 +191,8 @@ private boolean rollback() { return true; } - private void logUnwantedResponse(MessageData payload) { - log.info("Got unwanted/outdated GRPC response: {}", payload); + private void logUnwantedResponse(Serializable response) { + log.info("Got unwanted/outdated GRPC response: {}", response); } private MessageCookie newMessageCookie() { diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/LogicalPortMapperTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/LogicalPortMapperTest.java index 19d6ac360e0..933cd4c0a63 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/LogicalPortMapperTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/LogicalPortMapperTest.java @@ -37,7 +37,7 @@ public class LogicalPortMapperTest { @Test public void mapLagLogicalPortTest() { LagLogicalPort lagLogicalPort = new LagLogicalPort(SWITCH_ID, LAG_PORT, - Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2)); + Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2), false); LogicalPortInfoEntry port = INSTANCE.map(lagLogicalPort); assertEquals(LAG_PORT, port.getLogicalPortNumber().intValue()); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/ValidationMapperTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/ValidationMapperTest.java index bf7b8785b32..fd291053f71 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/ValidationMapperTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/ValidationMapperTest.java @@ -204,7 +204,7 @@ private static MeterSpeakerData initializeMeterSpeakerData(MeterId uniqueMeterId private static LagLogicalPort initializeLogicalPortData(SwitchId uniqueSwitchIdField) { return new LagLogicalPort(uniqueSwitchIdField, LAG_PORT, - Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2)); + Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2), true); } private static List missingGroups = new LinkedList<>(); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortServiceTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortServiceTest.java index ed83fa60a52..3d8700cd027 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortServiceTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortServiceTest.java @@ -20,8 +20,7 @@ import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.swmanager.request.UpdateLagPortRequest; import org.openkilda.model.SwitchId; -import org.openkilda.persistence.repositories.RepositoryFactory; -import org.openkilda.persistence.tx.TransactionManager; +import org.openkilda.persistence.PersistenceManager; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; import org.openkilda.wfm.topology.switchmanager.error.SwitchNotFoundException; @@ -47,10 +46,7 @@ public class UpdateLagPortServiceTest { private LagPortOperationService operationService; @Mock - RepositoryFactory repositoryFactory; - - @Mock - TransactionManager transactionManager; + PersistenceManager persistenceManager; @Test public void testKeepHandlerOnRequestKeyCollision() { @@ -61,13 +57,13 @@ public void testKeepHandlerOnRequestKeyCollision() { Assert.assertFalse(subject.activeHandlers.containsKey(requestKey)); UpdateLagPortRequest request = new UpdateLagPortRequest( - new SwitchId(1), (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3)); + new SwitchId(1), (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3), true); subject.update(requestKey, request); LagPortUpdateHandler origin = subject.activeHandlers.get(requestKey); Assert.assertNotNull(origin); UpdateLagPortRequest request2 = new UpdateLagPortRequest( - new SwitchId(2), (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3)); + new SwitchId(2), (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3), true); Assert.assertThrows(InconsistentDataException.class, () -> subject.update(requestKey, request2)); Assert.assertSame(origin, subject.activeHandlers.get(requestKey)); } @@ -82,7 +78,7 @@ public void testHandlerRemoveOnException() { String requestKey = "test-key"; UpdateLagPortRequest request = new UpdateLagPortRequest( - switchId, (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3)); + switchId, (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3), true); subject.update(requestKey, request); Mockito.verify(carrier).errorResponse( Mockito.eq(requestKey), Mockito.eq(ErrorType.NOT_FOUND), Mockito.anyString(), Mockito.anyString()); @@ -99,9 +95,9 @@ public void testInvalidTargetPortsBandwidthException() { int logicalPortNumber = (int) config.getPoolConfig().getIdMinimum(); Set targetPorts = Sets.newHashSet(1, 2); - UpdateLagPortRequest request = new UpdateLagPortRequest(switchId, logicalPortNumber, targetPorts); + UpdateLagPortRequest request = new UpdateLagPortRequest(switchId, logicalPortNumber, targetPorts, true); - Mockito.when(operationService.updateLagPort(switchId, logicalPortNumber, targetPorts)).thenThrow( + Mockito.when(operationService.updateLagPort(switchId, logicalPortNumber, targetPorts, true)).thenThrow( new InvalidDataException(format("Not enough bandwidth for LAG port %s.", logicalPortNumber))); subject.update(requestKey, request); @@ -112,6 +108,6 @@ public void testInvalidTargetPortsBandwidthException() { private LagPortOperationConfig newConfig() { return new LagPortOperationConfig( - repositoryFactory, transactionManager, 1000, 1999, 2000, 2999, 10, 100); + persistenceManager, 1000, 1999, 2000, 2999, 10, 100); } } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandlerTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandlerTest.java index 9771bfc17dc..6f1f1d9f307 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandlerTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandlerTest.java @@ -27,6 +27,7 @@ import org.openkilda.model.SwitchId; import org.openkilda.wfm.topology.switchmanager.error.SwitchManagerException; import org.openkilda.wfm.topology.switchmanager.error.SwitchNotFoundException; +import org.openkilda.wfm.topology.switchmanager.model.LagRollbackData; import org.openkilda.wfm.topology.switchmanager.service.LagPortOperationService; import org.openkilda.wfm.topology.switchmanager.service.SwitchManagerCarrier; @@ -41,7 +42,6 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.HashSet; -import java.util.Set; @RunWith(MockitoJUnitRunner.class) public class LagPortUpdateHandlerTest { @@ -76,7 +76,8 @@ public void testHappyPath() { Mockito.verifyNoMoreInteractions(operationService); Mockito.verify(carrier).response( Mockito.eq(requestKey), - Mockito.eq(new LagPortResponse(request.getLogicalPortNumber(), request.getTargetPorts()))); + Mockito.eq(new LagPortResponse(request.getLogicalPortNumber(), request.getTargetPorts(), + request.isLacpReply()))); Mockito.verifyNoMoreInteractions(carrier); Assert.assertTrue(subject.isCompleted()); @@ -93,10 +94,11 @@ public void testGrpcErrorResponse() { // GRPC error response ErrorData error = new ErrorData(ErrorType.INTERNAL_ERROR, "Dummy error message", "Dummy error description"); - subject.dispatchGrpcResponse(error, grpcRequestCookie); + subject.dispatchErrorResponse(error, grpcRequestCookie); Mockito.verify(operationService).updateLagPort( Mockito.eq(request.getSwitchId()), Mockito.eq(request.getLogicalPortNumber()), - Mockito.eq(subject.rollbackTargets)); + Mockito.eq(subject.rollbackData.getPhysicalPorts()), + Mockito.eq(subject.rollbackData.isLacpReply())); Mockito.verifyNoMoreInteractions(operationService); Mockito.verify(carrier).errorResponse( Mockito.eq(requestKey), Mockito.eq(error.getErrorType()), Mockito.anyString(), Mockito.anyString()); @@ -118,7 +120,8 @@ public void testTimeout() { subject.timeout(); Mockito.verify(operationService).updateLagPort( Mockito.eq(request.getSwitchId()), Mockito.eq(request.getLogicalPortNumber()), - Mockito.eq(subject.rollbackTargets)); + Mockito.eq(subject.rollbackData.getPhysicalPorts()), + Mockito.eq(subject.rollbackData.isLacpReply())); Mockito.verifyNoMoreInteractions(operationService); Mockito.verify(carrier).errorResponse( Mockito.eq(requestKey), Mockito.eq(ErrorType.OPERATION_TIMED_OUT), @@ -142,10 +145,12 @@ public void testExceptionOnErrorFromOperationService() { private MessageCookie verifyStartHandler( LagPortUpdateHandler subject, UpdateLagPortRequest request, String swAddress) { - Set existingTargets = ImmutableSet.of(3, 4, 5); + LagRollbackData existingTargets = LagRollbackData.builder() + .physicalPorts(ImmutableSet.of(3, 4, 5)).lacpReply(true).build(); Mockito.when(operationService.getSwitchIpAddress(request.getSwitchId())).thenReturn(swAddress); Mockito.when(operationService.updateLagPort( - Mockito.eq(request.getSwitchId()), Mockito.eq(request.getLogicalPortNumber()), Mockito.any())) + Mockito.eq(request.getSwitchId()), Mockito.eq(request.getLogicalPortNumber()), Mockito.any(), + Mockito.eq(request.isLacpReply()))) .thenReturn(existingTargets); Mockito.verifyNoInteractions(carrier); @@ -154,12 +159,13 @@ private MessageCookie verifyStartHandler( // DB update and GRPC request subject.start(); Assert.assertFalse(subject.isCompleted()); - Assert.assertEquals(existingTargets, subject.rollbackTargets); + Assert.assertEquals(existingTargets, subject.rollbackData); Mockito.verify(operationService).getSwitchIpAddress(Mockito.eq(request.getSwitchId())); Mockito.verify(operationService).updateLagPort( Mockito.eq(request.getSwitchId()), Mockito.eq(request.getLogicalPortNumber()), - Mockito.eq(new HashSet<>(request.getTargetPorts()))); + Mockito.eq(new HashSet<>(request.getTargetPorts())), + Mockito.eq(request.isLacpReply())); Mockito.verifyNoMoreInteractions(operationService); ArgumentCaptor grpcRequestCookie = ArgumentCaptor.forClass(MessageCookie.class); @@ -173,6 +179,6 @@ private MessageCookie verifyStartHandler( } private UpdateLagPortRequest newRequest() { - return new UpdateLagPortRequest(new SwitchId(1), 2001, Sets.newHashSet(1, 2, 3)); + return new UpdateLagPortRequest(new SwitchId(1), 2001, Sets.newHashSet(1, 2, 3), true); } } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java index 052f09c1533..b4d6c3a56bd 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java @@ -389,11 +389,11 @@ private PersistenceManager build() { when(repositoryFactory.createSwitchRepository()).thenReturn(switchRepository); LagLogicalPort lagLogicalPortA = new LagLogicalPort(SWITCH_ID_A, LOGICAL_PORT_NUMBER_1, - Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2)); + Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2), true); LagLogicalPort lagLogicalPortB = new LagLogicalPort(SWITCH_ID_A, LOGICAL_PORT_NUMBER_2, - Lists.newArrayList(PHYSICAL_PORT_3, PHYSICAL_PORT_4)); + Lists.newArrayList(PHYSICAL_PORT_3, PHYSICAL_PORT_4), false); LagLogicalPort lagLogicalPortC = new LagLogicalPort(SWITCH_ID_A, LOGICAL_PORT_NUMBER_3, - Lists.newArrayList(PHYSICAL_PORT_5, PHYSICAL_PORT_6)); + Lists.newArrayList(PHYSICAL_PORT_5, PHYSICAL_PORT_6), true); when(lagLogicalPortRepository.findBySwitchId(SWITCH_ID_A)).thenReturn(Lists.newArrayList( lagLogicalPortA, lagLogicalPortB, lagLogicalPortC)); diff --git a/src-java/testing/test-library/src/main/java/org/openkilda/testing/tools/KafkaUtils.java b/src-java/testing/test-library/src/main/java/org/openkilda/testing/tools/KafkaUtils.java index 4e95c27e17f..d35e63828ac 100644 --- a/src-java/testing/test-library/src/main/java/org/openkilda/testing/tools/KafkaUtils.java +++ b/src-java/testing/test-library/src/main/java/org/openkilda/testing/tools/KafkaUtils.java @@ -15,27 +15,18 @@ package org.openkilda.testing.tools; -import static java.lang.String.format; - -import org.openkilda.floodlight.api.request.rulemanager.FlowCommand; -import org.openkilda.floodlight.api.request.rulemanager.GroupCommand; import org.openkilda.floodlight.api.request.rulemanager.InstallSpeakerCommandsRequest; -import org.openkilda.floodlight.api.request.rulemanager.MeterCommand; import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.messaging.AbstractMessage; import org.openkilda.messaging.MessageContext; import org.openkilda.model.FlowPathDirection; import org.openkilda.model.cookie.Cookie; import org.openkilda.model.cookie.FlowSegmentCookie; -import org.openkilda.rulemanager.FlowSpeakerData; -import org.openkilda.rulemanager.GroupSpeakerData; -import org.openkilda.rulemanager.MeterSpeakerData; import org.openkilda.rulemanager.SpeakerData; import java.util.Collections; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; /** * Helper class to build kafka messages. @@ -56,27 +47,10 @@ public static AbstractMessage buildMessage(List speakerData) { .messageContext(new MessageContext()) .switchId(speakerData.get(0).getSwitchId()) .commandId(UUID.randomUUID()) - .commands(toCommands(speakerData)) + .commands(OfCommand.toOfCommands(speakerData)) .build(); } - private static List toCommands(List speakerData) { - return speakerData.stream() - .map(KafkaUtils::toCommand) - .collect(Collectors.toList()); - } - - private static OfCommand toCommand(SpeakerData speakerData) { - if (speakerData instanceof FlowSpeakerData) { - return new FlowCommand((FlowSpeakerData) speakerData); - } else if (speakerData instanceof MeterSpeakerData) { - return new MeterCommand((MeterSpeakerData) speakerData); - } else if (speakerData instanceof GroupSpeakerData) { - return new GroupCommand((GroupSpeakerData) speakerData); - } - throw new IllegalStateException(format("Unknown speaker data type %s", speakerData)); - } - /** * Build flow segment cookie. */ From 7dfb3165bee136d76c194e63fe8726981dfc9edf Mon Sep 17 00:00:00 2001 From: Sergey Nikitin Date: Sun, 16 Oct 2022 20:35:23 +0400 Subject: [PATCH 2/3] Added functional tests for LACP feature Related to #2882 Related to #3439 --- .../org/openkilda/model/cookie/Cookie.java | 2 + .../switchmanager/fsm/CreateLagPortFsm.java | 3 +- .../helpers/SwitchHelper.groovy | 37 +- .../spec/switches/LagPortSpec.groovy | 416 +++++++++++++++++- 4 files changed, 445 insertions(+), 13 deletions(-) diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/Cookie.java b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/Cookie.java index 8797b76be81..30178b14973 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/Cookie.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/Cookie.java @@ -86,6 +86,8 @@ public class Cookie extends CookieBase implements Comparable { ServiceCookie.ServiceCookieTag.SERVER_42_ISL_RTT_TURNING_COOKIE).getValue(); public static final long SERVER_42_FLOW_RTT_VXLAN_TURNING_COOKIE = new ServiceCookie( ServiceCookieTag.SERVER_42_FLOW_RTT_VXLAN_TURNING_COOKIE).getValue(); + public static final long DROP_SLOW_PROTOCOLS_LOOP_COOKIE = new ServiceCookie( + ServiceCookieTag.DROP_SLOW_PROTOCOLS_LOOP_COOKIE).getValue(); @JsonCreator diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java index 09593ba0cab..0e259d251cf 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java @@ -169,7 +169,8 @@ void lagInstalled(CreateLagState from, CreateLagState to, CreateLagEvent event, void speakerCommandsInstalled( CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { - log.info("LAG {} successfully installed on switch {}. Key={}", context.createdLogicalPort, switchId, key); + log.info("Rules for LAG {} successfully installed on switch {}. Key={}", + context.createdLogicalPort, switchId, key); } void finishedEnter(CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy index ca3891218c9..93c245b1ff1 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy @@ -47,6 +47,8 @@ import org.openkilda.model.SwitchId import org.openkilda.model.cookie.Cookie import org.openkilda.model.cookie.CookieBase.CookieType import org.openkilda.model.cookie.PortColourCookie +import org.openkilda.model.cookie.ServiceCookie +import org.openkilda.model.cookie.ServiceCookie.ServiceCookieTag import org.openkilda.northbound.dto.v1.switches.MeterInfoDto import org.openkilda.northbound.dto.v1.switches.SwitchDto import org.openkilda.northbound.dto.v1.switches.SwitchPropertiesDto @@ -164,6 +166,7 @@ class SwitchHelper { def devicesRules = [] def server42Rules = [] def vxlanRules = [] + def lacpRules = [] def toggles = northbound.get().getFeatureToggles() if (swProps.multiTable) { multiTableRules = [MULTITABLE_PRE_INGRESS_PASS_THROUGH_COOKIE, MULTITABLE_INGRESS_DROP_COOKIE, @@ -235,21 +238,30 @@ class SwitchHelper { if (sw.features.contains(NOVIFLOW_PUSH_POP_VXLAN) || sw.features.contains(KILDA_OVS_PUSH_POP_MATCH_VXLAN)) { vxlanRules << VERIFICATION_UNICAST_VXLAN_RULE_COOKIE } + def lacpPorts = northboundV2.get().getLagLogicalPort(sw.dpId).findAll { + it.lacpReply + } + if (!lacpPorts.isEmpty()) { + lacpRules << new ServiceCookie(ServiceCookieTag.DROP_SLOW_PROTOCOLS_LOOP_COOKIE).getValue() + lacpPorts.each { + lacpRules << new PortColourCookie(CookieType.LACP_REPLY_INPUT, it.logicalPortNumber).getValue() + } + } if (sw.noviflow && !sw.wb5164) { return ([DROP_RULE_COOKIE, VERIFICATION_BROADCAST_RULE_COOKIE, VERIFICATION_UNICAST_RULE_COOKIE, DROP_VERIFICATION_LOOP_RULE_COOKIE, CATCH_BFD_RULE_COOKIE, ROUND_TRIP_LATENCY_RULE_COOKIE] - + vxlanRules + multiTableRules + devicesRules + server42Rules) + + vxlanRules + multiTableRules + devicesRules + server42Rules + lacpRules) } else if ((sw.noviflow || sw.nbFormat().manufacturer == "E") && sw.wb5164) { return ([DROP_RULE_COOKIE, VERIFICATION_BROADCAST_RULE_COOKIE, VERIFICATION_UNICAST_RULE_COOKIE, DROP_VERIFICATION_LOOP_RULE_COOKIE, CATCH_BFD_RULE_COOKIE] - + vxlanRules + multiTableRules + devicesRules + server42Rules) + + vxlanRules + multiTableRules + devicesRules + server42Rules + lacpRules) } else if (sw.ofVersion == "OF_12") { return [VERIFICATION_BROADCAST_RULE_COOKIE] } else { return ([DROP_RULE_COOKIE, VERIFICATION_BROADCAST_RULE_COOKIE, VERIFICATION_UNICAST_RULE_COOKIE, DROP_VERIFICATION_LOOP_RULE_COOKIE] - + vxlanRules + multiTableRules + devicesRules + server42Rules) + + vxlanRules + multiTableRules + devicesRules + server42Rules + lacpRules) } } @@ -284,6 +296,13 @@ class SwitchHelper { result << MeterId.createMeterIdForDefaultRule(ARP_TRANSIT_COOKIE) //20 result << MeterId.createMeterIdForDefaultRule(ARP_INGRESS_COOKIE) //21 } + def lacpPorts = northboundV2.get().getLagLogicalPort(sw.dpId).findAll { + it.lacpReply + } + if (!lacpPorts.isEmpty()) { + result << MeterId.LACP_REPLY_METER_ID //31 + } + return result*.getValue().sort() } @@ -471,6 +490,18 @@ class SwitchHelper { } } + /** + * Verifies that specified logical port sections in the validation response are empty. + */ + static void verifyLogicalPortsSectionsAreEmpty(SwitchValidationExtendedResult switchValidateInfo, + List sections = ["missing", "excess", "misconfigured"]) { + def assertions = new SoftAssertions() + sections.each { String section -> + assertions.checkSucceeds { assert switchValidateInfo.logicalPorts."$section".empty } + } + assertions.verify() + } + static SwitchProperties getDummyServer42Props() { return new SwitchProperties(true, 33, "00:00:00:00:00:00", 1, null) } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/LagPortSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/LagPortSpec.groovy index bee699a2976..1b17e9a1bbf 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/LagPortSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/LagPortSpec.groovy @@ -3,6 +3,8 @@ package org.openkilda.functionaltests.spec.switches import static groovyx.gpars.GParsPool.withPool import static org.junit.jupiter.api.Assumptions.assumeTrue import static org.openkilda.functionaltests.extension.tags.Tag.HARDWARE +import static org.openkilda.model.MeterId.LACP_REPLY_METER_ID +import static org.openkilda.model.cookie.Cookie.DROP_SLOW_PROTOCOLS_LOOP_COOKIE import static org.openkilda.testing.Constants.NON_EXISTENT_SWITCH_ID import static org.openkilda.testing.service.floodlight.model.FloodlightConnectMode.RW @@ -14,6 +16,9 @@ import org.openkilda.messaging.error.MessageError import org.openkilda.messaging.model.grpc.LogicalPortType import org.openkilda.model.FlowPathDirection import org.openkilda.model.SwitchId +import org.openkilda.model.cookie.Cookie +import org.openkilda.model.cookie.CookieBase.CookieType +import org.openkilda.model.cookie.PortColourCookie import org.openkilda.northbound.dto.v1.flows.PingInput import org.openkilda.northbound.dto.v2.flows.FlowEndpointV2 import org.openkilda.northbound.dto.v2.flows.FlowMirrorPointPayload @@ -36,6 +41,9 @@ import javax.inject.Provider @Narrative("Verify that flow can be created on a LAG port.") @Tags(HARDWARE) class LagPortSpec extends HealthCheckSpecification { + public static final long LACP_METER_ID = LACP_REPLY_METER_ID.value + public static final String LACP_COOKIE = Cookie.toString(DROP_SLOW_PROTOCOLS_LOOP_COOKIE) + @Autowired @Shared GrpcService grpc @@ -48,20 +56,21 @@ class LagPortSpec extends HealthCheckSpecification { Integer lagOffset = 2000 @Tidy - def "Able to CRUD LAG port on #sw.hwSwString"() { + def "Able to CRUD LAG port with lacp_reply=#lacpReply on #sw.hwSwString"() { given: "A switch" - def portsArrayCreate = topology.getAllowedPortsForSwitch(sw)[-2, -1] - def portsArrayUpdate = topology.getAllowedPortsForSwitch(sw)[1, -1] + def portsArrayCreate = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set + def portsArrayUpdate = topology.getAllowedPortsForSwitch(sw)[1, -1] as Set assert portsArrayCreate.sort() != portsArrayUpdate.sort() when: "Create a LAG" - def payloadCreate = new LagPortRequest(portNumbers: portsArrayCreate) + def payloadCreate = new LagPortRequest(portNumbers: portsArrayCreate, lacpReply: lacpReply) def createResponse = northboundV2.createLagLogicalPort(sw.dpId, payloadCreate) then: "Response reports successful creation of the LAG port" with(createResponse) { logicalPortNumber > 0 portNumbers.sort() == portsArrayCreate.sort() + it.lacpReply == lacpReply } def lagPort = createResponse.logicalPortNumber @@ -117,9 +126,7 @@ class LagPortSpec extends HealthCheckSpecification { with(northbound.validateSwitch(sw.dpId)) { it.verifyRuleSectionsAreEmpty(["missing", "excess", "misconfigured"]) it.verifyMeterSectionsAreEmpty() - it.logicalPorts.misconfigured.empty - it.logicalPorts.missing.empty - it.logicalPorts.excess.empty + it.verifyLogicalPortsSectionsAreEmpty() } when: "Delete the LAG port" @@ -142,7 +149,10 @@ class LagPortSpec extends HealthCheckSpecification { lagPort && !lagPortIsDeleted && northboundV2.deleteLagLogicalPort(sw.dpId, lagPort) where: - sw << getTopology().getActiveSwitches().unique(false) { it.hwSwString } + [sw, lacpReply] << [ + getTopology().getActiveSwitches().unique(false) { it.hwSwString }, // switches + [false, true] // lacp reply + ].combinations() } @Tidy @@ -586,6 +596,392 @@ class LagPortSpec extends HealthCheckSpecification { lagPort && northboundV2.deleteLagLogicalPort(sw.dpId, lagPort) } + @Tidy + def "Able to create and delete single LAG port with lacp_reply=#data.portLacpReply"() { + given: "A switch" + def sw = topology.getActiveSwitches().first() + def portsArrayCreate = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set + + when: "Create a LAG port" + def createResponse = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(portsArrayCreate, data.portLacpReply)) + + then: "Response reports successful creation of the LAG port" + with(createResponse) { + logicalPortNumber > 0 + portNumbers.sort() == portsArrayCreate.sort() + lacpReply == data.portLacpReply + } + def portNumber = createResponse.logicalPortNumber + + and: "Correct rules and meters are on the switch" + assertSwitchHasCorrectLacpRulesAndMeters( + sw, data.mustContainCookies(portNumber), data.mustNotContainCookies(portNumber), data.mustContainLacpMeter) + + when: "Delete the LAG port" + def deleteResponse = northboundV2.deleteLagLogicalPort(sw.dpId, portNumber) + + then: "Response reports successful delete of the LAG port" + with(deleteResponse) { + logicalPortNumber == portNumber + portNumbers.sort() == portsArrayCreate.sort() + lacpReply == data.portLacpReply + } + + and: "No LACP rules and meters on the switch" + assertSwitchHasCorrectLacpRulesAndMeters(sw, [], [LACP_COOKIE, getLagCookie(portNumber)], false) + + cleanup: "Remove all LAG ports" + deleteAllLagPorts(sw.dpId) + + where: + data << [ + [ + portLacpReply : false, + mustContainCookies : { int port -> [] }, + mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainLacpMeter : false + ], + [ + portLacpReply : true, + mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustNotContainCookies : { int port -> [] }, + mustContainLacpMeter : true + ] + ] + } + + @Tidy + def "Able to create and delete LAG port with #data.description"() { + given: "A switch with LAG port" + def sw = topology.getActiveSwitches().first() + def physicalPortsOfLag1 = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set + def physicalPortsOfLag2 = topology.getAllowedPortsForSwitch(sw)[-4, -3] as Set + def portNumber1 = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(physicalPortsOfLag1, data.existingPortLacpReply)).logicalPortNumber + + when: "Create a LAG port" + def createResponse = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(physicalPortsOfLag2, data.newPortLacpReply)) + + then: "Response reports successful creation of the LAG port" + with(createResponse) { + logicalPortNumber > 0 + portNumbers.sort() == physicalPortsOfLag2.sort() + lacpReply == data.newPortLacpReply + } + def portNumber2 = createResponse.logicalPortNumber + + and: "Correct rules and meters are on the switch" + assertSwitchHasCorrectLacpRulesAndMeters( + sw, data.mustContainCookies(portNumber1, portNumber2), + data.mustNotContainCookies(portNumber1, portNumber2), data.mustContainLacpMeter) + + when: "Delete created LAG port" + def deleteResponse = northboundV2.deleteLagLogicalPort(sw.dpId, portNumber2) + + then: "Response reports successful delete of the LAG port" + with(deleteResponse) { + logicalPortNumber == portNumber2 + portNumbers.sort() == physicalPortsOfLag2.sort() + lacpReply == data.newPortLacpReply + } + + and: "No LACP rules and meters of second LAG port on the switch" + if (data.existingPortLacpReply) { // Switch must contain LACP rules and meter for first LAG port + assertSwitchHasCorrectLacpRulesAndMeters(sw, + [LACP_COOKIE, getLagCookie(portNumber1)], [getLagCookie(portNumber2)], true) + } else { // Switch must not contain any LACP rules and meter + assertSwitchHasCorrectLacpRulesAndMeters(sw, + [], [LACP_COOKIE, getLagCookie(portNumber1), getLagCookie(portNumber2), ], false) + } + + cleanup: "Remove all LAG ports" + deleteAllLagPorts(sw.dpId) + + where: + data << [ + [ + description: "disabled LACP replies, near to LAG port with disabled LACP replies", + existingPortLacpReply : false, + newPortLacpReply : false, + mustContainCookies : { int oldPort, newPort -> [] }, + mustNotContainCookies : { int oldPort, newPort -> [ + LACP_COOKIE, getLagCookie(oldPort), getLagCookie(newPort)] }, + mustContainLacpMeter : false + ], + [ + description: "enabled LACP replies, near to LAG port with disabled LACP replies", + existingPortLacpReply : false, + newPortLacpReply : true, + mustContainCookies : { int oldPort, newPort -> [LACP_COOKIE, getLagCookie(newPort)] }, + mustNotContainCookies : { int oldPort, newPort -> [getLagCookie(oldPort)] }, + mustContainLacpMeter : true + ], + [ + description: "disabled LACP replies, near to LAG port with enabled LACP replies", + existingPortLacpReply : true, + newPortLacpReply : false, + mustContainCookies : { int oldPort, newPort -> [LACP_COOKIE, getLagCookie(oldPort)] }, + mustNotContainCookies : { int oldPort, newPort -> [getLagCookie(newPort)] }, + mustContainLacpMeter : true + ], + [ + description: "enabled LACP replies, near to LAG port with enabled LACP replies", + existingPortLacpReply : true, + newPortLacpReply : true, + mustContainCookies : { int oldPort, newPort -> [ + LACP_COOKIE, getLagCookie(oldPort), getLagCookie(newPort)] }, + mustNotContainCookies : { int oldPort, newPort -> [] }, + mustContainLacpMeter : true + ] + ] + } + + @Tidy + def "Able to update #data.description for single LAG port"() { + given: "A switch" + def sw = topology.getActiveSwitches().first() + def physicalPortsOfCreatedLag = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set + def physicalPortsOfUpdatedLag = topology.getAllowedPortsForSwitch(sw)[-3, -2] as Set + + and: "A LAG port" + def createResponse = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(physicalPortsOfCreatedLag, data.oldlacpReply)) + with(createResponse) { + assert logicalPortNumber > 0 + assert portNumbers.sort() == physicalPortsOfCreatedLag.sort() + } + def portNumber = createResponse.logicalPortNumber + + when: "Update the LAG port" + def updatedPhysicalPorts = data.updatePorts ? physicalPortsOfUpdatedLag : physicalPortsOfCreatedLag + def updateResponse = northboundV2.updateLagLogicalPort( + sw.dpId, portNumber, new LagPortRequest(updatedPhysicalPorts, data.newlacpReply)) + + then: "Response reports successful update of the LAG port" + with(updateResponse) { + logicalPortNumber == portNumber + portNumbers.sort() == updatedPhysicalPorts.sort() + lacpReply == data.newlacpReply + } + + and: "Correct rules and meters are on the switch" + assertSwitchHasCorrectLacpRulesAndMeters( + sw, data.mustContainCookies(portNumber), data.mustNotContainCookies(portNumber), data.mustContainLacpMeter) + + cleanup: "Remove all LAG ports" + deleteAllLagPorts(sw.dpId) + + where: + data << [ + [ + description: "physical ports of LAG with disabled LACP", + oldlacpReply : false, + newlacpReply : false, + updatePorts: true, + mustContainCookies : { int port -> [] }, + mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainLacpMeter : false, + ], + [ + description: "physical ports of LAG with enabled LACP", + oldlacpReply : true, + newlacpReply : true, + updatePorts: false, + mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustNotContainCookies : { int port -> [] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from false to true, physical ports are same", + oldlacpReply : false, + newlacpReply : true, + updatePorts: false, + mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustNotContainCookies : { int port -> [] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from true to false, physical ports are same", + oldlacpReply : true, + newlacpReply : false, + updatePorts: false, + mustContainCookies : { int port -> [] }, + mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainLacpMeter : false, + ], + [ + description: "lacp_reply from false to true and update physical ports", + oldlacpReply : false, + newlacpReply : true, + updatePorts: true, + mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustNotContainCookies : { int port -> [] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from true to false and update physical ports", + oldlacpReply : true, + newlacpReply : false, + updatePorts: true, + mustContainCookies : { int port -> [] }, + mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainLacpMeter : false, + ] + ] + } + + @Tidy + def "Able to update #data.description near to existing LAG port with lacp_reply=#data.existingPortLacpReply"() { + given: "A switch" + def sw = topology.getActiveSwitches().first() + def physicalPortsOfLag1 = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set + def physicalPortsOfCreatedLag2 = topology.getAllowedPortsForSwitch(sw)[-4, -3] as Set + def physicalPortsOfUpdatedLag2 = topology.getAllowedPortsForSwitch(sw)[-5, -4] as Set + + and: "LAG port 1" + def portNumber1 = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(physicalPortsOfLag1, data.existingPortLacpReply)).logicalPortNumber + + and: "LAG port 2" + def createResponse = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(physicalPortsOfCreatedLag2, data.oldlacpReply)) + with(createResponse) { + assert logicalPortNumber > 0 + assert portNumbers.sort() == physicalPortsOfCreatedLag2.sort() + assert lacpReply == data.oldlacpReply + } + def portNumber2 = createResponse.logicalPortNumber + + when: "Update the LAG port" + def updatedPhysicalPorts = data.updatePorts ? physicalPortsOfUpdatedLag2 : physicalPortsOfCreatedLag2 + def updateResponse = northboundV2.updateLagLogicalPort( + sw.dpId, portNumber2, new LagPortRequest(updatedPhysicalPorts, data.newlacpReply)) + + then: "Response reports successful update of the LAG port" + with(updateResponse) { + logicalPortNumber == portNumber2 + portNumbers.sort() == updatedPhysicalPorts.sort() + lacpReply == data.newlacpReply + } + + and: "Correct rules and meters are on the switch" + assertSwitchHasCorrectLacpRulesAndMeters( + sw, data.mustContainCookies(portNumber1, portNumber2), + data.mustNotContainCookies(portNumber1, portNumber2), data.mustContainLacpMeter) + + cleanup: "Remove all LAG ports" + deleteAllLagPorts(sw.dpId) + + where: + data << [ + [ + description: "physical ports of LAG with disabled LACP", + existingPortLacpReply : false, + oldlacpReply : false, + newlacpReply : false, + updatePorts: true, + mustContainCookies : { int port1, port2 -> [] }, + mustNotContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, + mustContainLacpMeter : false, + ], + [ + description: "physical ports of LAG with enabled LACP", + existingPortLacpReply : false, + oldlacpReply : true, + newlacpReply : true, + updatePorts: true, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port2)] }, + mustNotContainCookies : { int port1, port2 -> [getLagCookie(port1)] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from false to true", + existingPortLacpReply : false, + oldlacpReply : false, + newlacpReply : true, + updatePorts: false, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port2)] }, + mustNotContainCookies : { int port1, port2 -> [getLagCookie(port1)] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from true to false", + existingPortLacpReply : false, + oldlacpReply : true, + newlacpReply : false, + updatePorts: false, + mustContainCookies : { int port1, port2 -> [] }, + mustNotContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, + mustContainLacpMeter : false, + ], + [ + description: "physical ports of LAG with disabled LACP", + existingPortLacpReply : true, + oldlacpReply : false, + newlacpReply : false, + updatePorts: true, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1)] }, + mustNotContainCookies : { int port1, port2 -> [getLagCookie(port2)] }, + mustContainLacpMeter : true, + ], + [ + description: "physical ports of LAG with enabled LACP", + existingPortLacpReply : true, + oldlacpReply : true, + newlacpReply : true, + updatePorts: true, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, + mustNotContainCookies : { int port1, port2 -> [] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from false to true", + existingPortLacpReply : true, + oldlacpReply : false, + newlacpReply : true, + updatePorts: false, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, + mustNotContainCookies : { int port1, port2 -> [] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from true to false", + existingPortLacpReply : true, + oldlacpReply : true, + newlacpReply : false, + updatePorts: false, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1)] }, + mustNotContainCookies : { int port1, port2 -> [getLagCookie(port2)] }, + mustContainLacpMeter : true, + ] + ] + } + + private void assertSwitchHasCorrectLacpRulesAndMeters( + Switch sw, mustContainCookies, mustNotContainsCookies, mustContainLacpMeter) { + // validate switch + with(northbound.validateSwitch(sw.dpId)) { + it.verifyRuleSectionsAreEmpty(["missing", "excess", "misconfigured"]) + it.verifyMeterSectionsAreEmpty() + it.verifyLogicalPortsSectionsAreEmpty() + } + + // check cookies + def hexCookies = northbound.getSwitchRules(sw.dpId).flowEntries*.cookie.collect { Cookie.toString(it) } + assert hexCookies.containsAll(mustContainCookies) + assert hexCookies.intersect(mustNotContainsCookies).isEmpty() + + // check meters + def meters = northbound.getAllMeters(sw.dpId).meterEntries*.meterId + if (mustContainLacpMeter) { + assert LACP_REPLY_METER_ID.value in meters + } else { + assert LACP_REPLY_METER_ID.value !in meters + } + } + @Tidy def "Unable decrease bandwidth on LAG port lower than connected flows bandwidth sum"() { given: "Flows on a LAG port with switch ports" @@ -641,8 +1037,10 @@ class LagPortSpec extends HealthCheckSpecification { logicalPortNumber == lagPort portNumbers.sort() == portsArray.sort() } + } - + def getLagCookie(portNumber) { + new PortColourCookie(CookieType.LACP_REPLY_INPUT, portNumber).toString() } void deleteAllLagPorts(SwitchId switchId) { From 84d12888d2435190c052b9180a409aa19a2109b4 Mon Sep 17 00:00:00 2001 From: Sergey Nikitin Date: Tue, 18 Oct 2022 12:48:46 +0400 Subject: [PATCH 3/3] Updated CHANGELOG.md (release 1.125.0) --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3289e044be2..706ad942cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## v1.125.0 (19/10/2022) + +### Features: +- [#4958](https://github.com/telstra/open-kilda/pull/4958) Added LACP replies for LAG ports [**floodlight**][**northbound**][**storm-topologies**] +- [#4959](https://github.com/telstra/open-kilda/pull/4959) Added functional tests for LACP feature (Issues: [#2882](https://github.com/telstra/open-kilda/issues/2882) [#3439](https://github.com/telstra/open-kilda/issues/3439)) [**tests**] + +For the complete list of changes, check out [the commit log](https://github.com/telstra/open-kilda/compare/v1.124.0...v1.125.0). + +### Affected Components: +nb, swmanager, fl + +### Upgrade notes: +OrientDB schema have been changed in this release. You need to apply schema migration. Please follow [migration instructions](https://github.com/telstra/open-kilda/tree/develop/docker/db-migration/migrations). + +--- + ## v1.124.0 (07/09/2022) ### Features: