From fb4fa5d8113784c7b3f96921d515f444dc37cd0b Mon Sep 17 00:00:00 2001 From: Ahmed Karic Date: Thu, 26 Sep 2024 16:25:48 +0200 Subject: [PATCH] test: add a test to verify all interfaces The test ensures that all interface types can be successfully created and retrieved from the operational datastore. It also functions as a regression test for issue #618 and will apply to all future interface variants. Fixes #622 --- .../case/ietf_interfaces/ietf_interfaces.yaml | 3 + .../verify_all_interface_types/test.py | 235 ++++++++++++++++++ .../verify_all_interface_types/topology.dot | 25 ++ .../verify_all_interface_types/topology.png | Bin 0 -> 8751 bytes 4 files changed, 263 insertions(+) create mode 100755 test/case/ietf_interfaces/verify_all_interface_types/test.py create mode 100644 test/case/ietf_interfaces/verify_all_interface_types/topology.dot create mode 100644 test/case/ietf_interfaces/verify_all_interface_types/topology.png diff --git a/test/case/ietf_interfaces/ietf_interfaces.yaml b/test/case/ietf_interfaces/ietf_interfaces.yaml index 2141ccbf..3b748522 100644 --- a/test/case/ietf_interfaces/ietf_interfaces.yaml +++ b/test/case/ietf_interfaces/ietf_interfaces.yaml @@ -49,3 +49,6 @@ - name: vlan_qos case: vlan_qos/test.py + +- name: verify_all_interface_types + case: verify_all_interface_types/test.py diff --git a/test/case/ietf_interfaces/verify_all_interface_types/test.py b/test/case/ietf_interfaces/verify_all_interface_types/test.py new file mode 100755 index 00000000..f4775d9d --- /dev/null +++ b/test/case/ietf_interfaces/verify_all_interface_types/test.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +""" +``` + lo br-0 br-Q.40 br-D br-X + | | | | | + o o eth-Q.10 br-Q veth0a.20 eth-X.30 + \ / \ | | + eth-Q veth0b veth0a eth-X + `---------' +``` +Verify that all interface types can be created: +1. Ethernet/Etherlike (ethX) +2. Loopback (lo) +3. Empty bridge (br-0) +4. Ethernet/Etherlike (ethQ) as a bridge port in br-Q +5. VETH pair: veth0a <--> veth0b, veth0b as a bridge port in br-Q +6. VLAN: + 1. ethQ.10 (VLAN 10) on top of an Ethernet/Etherlike interface (ethQ) + 2. br-Q.40 (VLAN 40) on top of a bridge (br-Q) + 3. veth0a.20 (VLAN 20) on top of a VETH interface (veth0a) as a bridge port in br-D + 4. ethX.30 (VLAN 30) as a bridge port in br-X +""" + +import infamy +import infamy.iface as iface + + +def verify_interface(target, interface, expected_type): + assert iface.interface_exist(target, interface), f"Interface <{interface}> does not exist." + + expected_type = f"infix-if-type:{expected_type}" + actual_type = iface._iface_get_param(target, interface, "type") + + if expected_type == "infix-if-type:etherlike" and actual_type == "infix-if-type:ethernet": + return # Allow 'etherlike' to match 'ethernet' + + assert actual_type == expected_type, f"Assertion failed! expected tpye: {expected_type}, actual type {actual_type}" + + +with infamy.Test() as test: + with test.step("Initialize"): + env = infamy.Env() + target = env.attach("target", "mgmt") + + _, eth_Q = env.ltop.xlate("target", "ethQ") + _, eth_X = env.ltop.xlate("target", "ethX") + + eth_Q_10 = f"{eth_Q}.10" + eth_X_30 = f"{eth_X}.30" + + br_0 = "br-0" + br_X = "br-X" + br_D = "br-D" + br_Q = "br-Q" + + veth_a = "veth0a" + veth_b = "veth0b" + + veth_a_20 = f"{veth_a}.20" + br_Q_40 = f"{br_Q}.40" + + with test.step("Configure an empty bridge"): + target.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [ + { + "name": br_0, + "type": "infix-if-type:bridge", + "enabled": True, + } + ] + } + }) + + with test.step("Configure bridge brX and associated interfaces"): + target.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [ + { + "name": br_X, + "type": "infix-if-type:bridge", + "enabled": True + }, + { + "name": eth_X_30, + "type": "infix-if-type:vlan", + "enabled": True, + "vlan": { + "lower-layer-if": eth_X, + "id": 30 + }, + "infix-interfaces:bridge-port": { + "bridge": br_X + } + } + ] + } + }) + + with test.step("Configure VETH pair"): + target.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [ + { + "name": veth_a, + "type": "infix-if-type:veth", + "enabled": True, + "infix-interfaces:veth": { + "peer": veth_b + } + }, + { + "name": veth_b, + "type": "infix-if-type:veth", + "enabled": True, + "infix-interfaces:veth": { + "peer": veth_a + } + } + ] + } + }) + + with test.step("Configure bridge brD and associated interfaces"): + target.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [ + { + "name": br_D, + "type": "infix-if-type:bridge", + "enabled": True, + }, + { + "name": veth_a_20, + "type": "infix-if-type:vlan", + "enabled": True, + "vlan": { + "lower-layer-if": "veth0a", + "id": 20 + }, + "infix-interfaces:bridge-port": { + "bridge": br_D + } + } + ] + } + }) + + with test.step("Configure br-Q and associated interfaces"): + target.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [ + { + "name": br_Q, + "type": "infix-if-type:bridge", + "enabled": True, + }, + { + "name": eth_Q, + "enabled": True, + "infix-interfaces:bridge-port": { + "bridge": br_Q + } + }, + { + "name": veth_b, + "type": "infix-if-type:veth", + "enabled": True, + "infix-interfaces:bridge-port": { + "bridge": br_Q + } + }, + { + "name": eth_Q_10, + "type": "infix-if-type:vlan", + "enabled": True, + "vlan": { + "lower-layer-if": eth_Q, + "id": 10 + } + }, + { + "name": br_Q_40, + "type": "infix-if-type:vlan", + "enabled": True, + "vlan": { + "lower-layer-if": br_Q, + "id": 40 + } + } + ] + } + }) + + + with test.step("Verify loopback interface"): + verify_interface(target, "lo", "loopback") + + with test.step("Verify Ethernet/Etherlike interface (ethX) with VLAN ethX.10"): + verify_interface(target, eth_X, "etherlike") + + with test.step("Verify Ethernet/Etherlike interface (ethQ) as a bridge port"): + verify_interface(target, eth_Q, "etherlike") + + with test.step("Verify empty bridge br-0"): + verify_interface(target, br_0, "bridge") + + with test.step("Verify bridge br-Q with VLAN interface br-Q.40 on top"): + verify_interface(target, br_Q, "bridge") + + with test.step("Verify bridge br-X with VLAN interface ethX.10 as bridge port"): + verify_interface(target, br_X, "bridge") + + with test.step("Verify bridge br-D with VLAN interface veth0a.20 as bridge port"): + verify_interface(target, br_D, "bridge") + + with test.step("Verify VETH interface veth0b as a bridge port in br-Q and VETH peer"): + verify_interface(target, veth_b, "veth") + + with test.step("Verify VETH interface veth0a with VLAN veth0a.20 and VETH peer"): + verify_interface(target, veth_a, "veth") + + with test.step("Verify VLAN interface veth0a.20 on top of veth0a as bridge port"): + verify_interface(target, veth_a_20, "vlan") + + with test.step("Verify VLAN interface ethQ.10 on top of Ethernet/Etherlike ethQ"): + verify_interface(target, eth_Q_10, "vlan") + + with test.step("Verify VLAN interface ethX.30 on top of Ethernet/Etherlike ethX as bridge port"): + verify_interface(target, eth_X_30, "vlan") + + with test.step("Verify VLAN interface br-Q.40 on top of bridge br-Q"): + verify_interface(target, br_Q_40, "vlan") + + test.succeed() diff --git a/test/case/ietf_interfaces/verify_all_interface_types/topology.dot b/test/case/ietf_interfaces/verify_all_interface_types/topology.dot new file mode 100644 index 00000000..29671098 --- /dev/null +++ b/test/case/ietf_interfaces/verify_all_interface_types/topology.dot @@ -0,0 +1,25 @@ +graph "1x3" { + layout="neato"; + overlap="false"; + esep="+80"; + + node [shape=record, fontname="monospace"]; + edge [color="cornflowerblue", penwidth="2"]; + + host [ + label="host | { tgt | dummy0 | dummy1 }", + pos="0,12!", + kind="controller", + ]; + + target [ + label="{ mgmt | Dport | Qport } | target", + pos="10,12!", + + kind="infix", + ]; + + host:tgt -- target:mgmt [kind=mgmt] + host:dummy0 -- target:ethQ [color=black] + host:dummy1 -- target:ethX [color=black] +} \ No newline at end of file diff --git a/test/case/ietf_interfaces/verify_all_interface_types/topology.png b/test/case/ietf_interfaces/verify_all_interface_types/topology.png new file mode 100644 index 0000000000000000000000000000000000000000..85f31d89fcad5ba25bce960a90a085cad1a84400 GIT binary patch literal 8751 zcmZvC2RN1g-~XKv>1!sEomJT?dqqO_CVOO;>}*0R*>P;yLWE>HBBAV=tn9t_JkQ(r z|NO7(d7kTccU^TZ9ryX%@6Y@FTA%YqRaush;5q?1;>M$~r;I;y_*BT{Mz2QV-ws`2VBpz4 zRWm+vx!8OG?_$F7abTaJhK5f~SNgi9yOsb82|mIYL=w%%7ftl<3#8o-pD`$lbWS3O zM5-5mn_dc^(T2Ijpsk~0tlSQjC3XMw+u)#EN8-KcaIExi)|*q!PoF-8*Mu~eFNYFR z^LH0u7BI1~u_=O%W)0G05_AzQ)0@n+!84mLYDY&6NB^!FP6@rGbHgt7~~!SVy(%vi7IPT54*J zdngeDh$9t;^ii*q~&xT8%KrN&H z?5E>x8*{&Z|K8%slb(W|t-f59#m4yfYZ4L?rODCJ>Z+=$+S(5#%;n!xkVpk;OG`^@ zYimDwGMggej{Kc$3dzsUUtC;7B2g&!lREVF`ZE&dwea3^(nPz$9nn z`THYmY`H&w{_O7VPE5QmBO^n4UKQWlw{LH5Zq~T1{%mjmQd*jUq8IbLYuO$TZ_}@M zMs@vqkulo$U}F;AB;vkCUKgyE`S6K_1=3J%H#Asecsy*C=HcNH<&cn&K)6#xWD}3^ zG(9VTwPuD^z@FFSXSE;?tJ?6Da~h3&%%QFR!M28=l+=c;>rHc^HNNdKKW(Y zkvK3qnw*eO<$buNS7v#SpC7g}E;crd>dsti6cYgf!F*TRP+uQ5;_klx=g%LgZs<6c z&>a^yH!S~vfD5TNk1owgINs0^wd_h&Le-w`jhO#~1p^YCPC zmWBuqc{Wl1)XGZs5v-U_+`HAR%>qyx%w~xA|4b{x^@l{>aM8$~qe3 zdwR5ss2Uj5I!x4y-<1)Lf_VvNDD;VEbohXP1}XL;cF0NNbyQT3uOLn?O6* z+uNI(rsw8TQd8TRnceV&Sr!-@GWH`xsQK-?2L@6^JxZY$0s`*S|KYuJM?+g%+;J+X zzW%KLqehhhm$MO$r%ycQ$ZYm!?D-mCHL>hErKP0{^w#7uDZ;L{FiU`D00v=UVR*!J z7A3lXPdC2$lX&(CiHNj(|DKnhABRHC&(Dhp3tz;=y&$>2E!hQQmywa7sj2x$L7@z; z($eQ=X)h*syXv8lTRS_sI5|1Fxz|@#EIP-hp9{hYSy)&G1_p9+a$rP4DY*xGdPwU4 zdOqB<>CJlg?jNTSv#psnDk`cNRwXQioxK2l=IrdOzS^fEVOaTm684~fV1P|Ao`jV2 z0>bq09Z4N-PSaN2UY+wk9k8mhpwjAFROaanBAI5PR3Omin!T+CKSjW6cNJ>g7D|3bpGBPsq@Hm8(PEAk4gE1^tl1O1& z_2nofCMLSCk8^TxtSl^SY;F$MxEE&yN;Q7QuvtR=K7RaIUS7VxKB1$bafg>TeI7_G z>pRNWw+e z(m;WZf-u3Q;uQ)T{Q-!-GtCl>fV)|0Xw|HoxHDU~-wKqm^naJ#q#x z3o>d^QBl!EGxWm3!snakd-8%J^ohMa4F$!I`E3?jaw+x6spe2}88vbiCZ;CKGU)RP zhl%muzi%#9Mqc}8>CD*5icLoCj#cM-7Y4Gq)zvzW?Kzl%FBKINXg|y^ZuH5?cGvlh zdSw_$p#6Z)i_vF)8XKp+1>#}TH~k2szV)`+bJrI0^oE#NN=6=3>}=%w@)e!PKw%6c z0)udNx<4^&@8;?0X=eumobmSU&+$4hhtW#L#cq;l3g`iEaWwQH3rq8lA32qk2h$M} zaW1M0uBKwv1f6DHXGu%Ty;UV|wk)%1k9VAG z#06IVh2l1@c9HMBpFXtp_z<*3S7)cPnp#gogMV@kK=acH@9i+C*~^zNDKx!t)Er#n|<)PPJ~E=9Ct-n8AG>)6;23lQ_#i%&|5xW)@xEX2kp ztD@qt^1H;UEA?u&l#EPpZEY<8N4MKhp`Nswrm`~8@C^ibE9A^&ZL|uBOiE%OmkuBy z3x<}is1QK1va_SeWMyTcU4H!dfqR((aLGxK#?KD}4L?`GWn3?6{^K7)LPF>PTia@% z<2`tIOZe>@PHzi}i}$y;sd>$U!oxd3szAf2YiP7xdcHjP2{Ey;kxwH_6Zg3tmtt#Y z$3&={CQgeCe$tn9nEL+aN|v&Y&d_j~)!5h=fW}pF@|~R>Zy%q~(9qz--kzQWLB|Y9 z29PWCJG;C0?%fj;6$OfK&4p%#ra!NeY{Yn#v(-K$C4f{C|5H9*UeglY$B!SUrlx`_ z$iN#G!FmO6B8aV3y?_4#!eLmgX=s@8O!nc!oDU!TH9>)l(F^2eWxY>JQxTY2U!M?* z9~{))TKr{1S7X-vR_7HP3BKsRmyzBobR>Lc`(9fg-~P(5^zF#`5I~{uGlDi@Mn*>9 zxfwS8B&GU-f&x2xd&Ri&FJG#wtBv&TGmvoTe)iAEgYNH6vUYG78XQc$YeS}|rJ{4yMy~J(jU!Op$C_54n5w+V^RaM=(b&Fe+9!nC`AG^DATQmz7 z7nj%&bgIuVYdF1x^195v3e*%(IY9}8$&dE-l@SN@6YY_DpBhPqO0WGQAd~L<$d6kU zB_*3F?&G9?g5u&Rgrwh+Srms$PaOM&5YrR=^ABKoVSfJO+84ZOUAZ;>Z{EDIv9V#2 z4Oe{h2t)?#*B0#Q-pa6!v5$=3DWLlG>(|ex!p6qqXvfCZHa|0y>GZoTT?CXGdIxFf z=m;aysb#u@KAcMeEdx3g1rs$i)Y9C{!pge$HEOXhSAd`Y=@B)f44-a zXA#M@Yd1;o_tqy=1g_E3V|Erbs&hw1M-_doypLBaVDCstNu8XW;DsO-A)`E_gzfV3 z@?td1B*;UQyuO>2$OX=KreOPj z{>q8%dn=$>lLQ=! zK7M5EBYbeFROjgIqzhK>2TIjK2)M61T^eq|STHm6Gn`h$z|io5q>RjL#f0y#&d$!( zR_muvwKO$x5SVxny<8}ECZ^1+teKUS(NjJX^wjnt)B~ohq@;wInVvokJ`{vrh0bba zbu~1Nu!sm?NmsIvhL+YqPYmY!9UUFx zFV>`Wm$u42WM)b;kaXXnf8bps~ zVnA4@V3wAa@(T)Xy;WCN$NG^XxV2bj)g@PTZC7j}GDa{gXj?lh@+z; zRsaJ7ga0!>5TQ_1C}55P)E(vz7~=R#3x4{LmaZ;70#c5^eoS#mFc^ciY&~R{oSP_hib#bAbgE;N#0U*9C{$ zT@o-n(rnEdxys)p{OtD{e*6IWdCx3@DxL9fa9x7VIhDHC20c8Sotp#9jGnEn6-B!H z`U25iA)<7}1qGpNSkX}~Sy)%9*7o^kmQP(CdC#&wdh{qqF`gxj={WvXfCDymi510cI0)4da>BeN!gdgS87l!)Z{d`aaIp#bvG|X^5VeX(wlUbCa~LRFWaD zxEKvX8^@_nC;Gy~&Q1iDX$iB_07;9p^Y-#kaq;7F0tAABf6dS5&!1bny7u??Cn_B? zb8_Z@fZ8_XC@E1!xQY*JPWDC|pu41WjGKe)r$AGMQgHV6^_ja3$yq{y6ZxM$2B!F^ zk=yG14{2L#Yh`66jfk5)UiDA(2zSANSM`>loYTg zVy;V?&dztXJ2i>#?-4okSQpPp;NaoixeW3sM>*wDoUtJ0zM5JT-PfH)(?Sl4WsoIO zBV@wALfy}rz(EDiNkr;D*)bUn4<|~u+sxu_PR2Y3ru{ZC9Ysb)hJ$s1>I@{t)=z?V zlhLuU3zAAot02Lo9No|Xlq4h!Kc@X7UC4aS0waM$Q19P&ySAR-ZE-7t4ZT5m370zo z!vite+^dB{Vlf=&;nxon}EquOq9)^wQ~?K~ge zeGRq2i;$kcxNpyQb#-*yyL0D??d=;k%z83pzzzCzZE)`o>N`BK zv{X*wSCW^1(=-_r9L#A{8?SdCKp9l|-rgSE0P+!DUf$Co)Qz$~^BqYoG!fD?!Y0J0^t)i9o;i~`*yPuMMXt3b94DUvN1qNkQeZtoV+}+C9NA9&$nJ!QH09e z2IMxZcDZ%;Za>6(pd6s%e*UzD8I@`x^TIE6k8e}`em3!UpLNd*%7}{qzmc0KIZQc9 zS~tAz#azK%#MJqUoM!2qqAz^4>0?FY6cniapAxEKh<6{!%C>+5yFy0RN-HiO5=bI- z+T7ec3rukJ>eU&dDWFTx2CcPi0Ylu=#*quKb_EG{7u60|8GOc)Uu|vjN=i@S&XLWP z+kEfp9_Au0QAnB3v0Hjg1U}>4@5v(W&pMNZXox~Y$!8k^u-kU#sBcq_B6K^%X z_l9JNYWpg0p4Hc{UuPC41263T@8tD==E!#~TF~!ND0&e$#a=ZzT@R0{*TwQuas-!f zUIHMdhcFO{LGCk_c01Sve;$(K`Po?(NQR3Yie(OFEs&e~5-N8AZ;@9H2PvGp(c` zY2cUTTegUSM^sc)(4M=u)4Bjpm`;t9g1Q?ZmjIFZ11OSJF`fsk)luxHg^u9d9=}RJ z0062l<>h!)YXlQ&Xxg*Avr@G1Cjjx z{TrZlqS_VIN@GilEDR|8lw8Kc)AKevJG%5dp48Or7g?``5P@dSjoY|BIWBH)a+23^ z>H)H`vqKYq?qT5(;3_OGUSC^Negr8v5K+DDKz@I}rhtF|A_$~mW3vcT^lZE~Au%x@ zbnMcSFqp0MoEM^SngG#3A3(6HD}3A&-^cp;)TE@QFxg8>kJZ(q6BBPk)HXNAZ`+>- zin}R<7+ccKjURa+E6c{p8tvc%A{}7=)Q991$DBD?~K0dC!rR7BgQs^l$B7Au^7M5%saSRJ1m~Y`WPnk_3HzOTmT2^YHI>7K|ZZKKUaSCt~47X94We4~T01@DKu)vU}F)*0yE)9S^bfNqV#LLe9YiJ17VHxV| zKq^(IV`xZ-NLA3a-adRyf{$}{b_O#4Vam65H5qt z$e5Ti2(&y;F1wu>g=o&B)BMh;5TI}fg%J|~1b%-0^Cz^mfJ_;jjqmcO3i$j!*X2P7 za+TtFOb@qaA#g1&=$yPu?Aj-?i`l{1!25%9H`MtUrKfT`C&JG)a_ggaRvCxxx zi~BA1W`o0LC;(5{Tx3{7L?ih5$*(wYTgHB;u8;#kD~|RoEG;F9dAX^p_m-HqoSdA* zbEXM7gSfP4dPQha4k=+>T}soW{dg@E0w&AZ$qC*w?zLWbHu7YVvF@-qJA3v#07IYh z%lje?W+mc>GcgcJ@8#2Oy-bJ0j!T>Zckf1qUK7(q;6P(HLgGviR$g0>S5UAFPz+va zz5cjJ*Da=HdU91d$S5x}b2`?wRTRCq=Vmzxy}TG<-9AwW+XL#b+HLhZG79-=Rn>5F zb1<_65*ZsyNI(GN@UyFn@=tPV>iD_oFDBR}ih3dMaAs#~`x31|s$^YWv&NJfLP&iP zVIGKjvQ?3p8K`&A_^+R7F!nuU;pcY)Dh5yiEF!vm*=zOm*l%`rwk3j|QpXOTkR_w` zswo`fAgX$LZ5+%6Qj{_YRuNH5vW*jH$F_FEbSf`6E=gzS(bLs!ZThI%Xf;U1YleeBf)h0WfkS+J{24wgq<$bELWpP|DJt%4Zl=rn)rr$!-gN)M z9~iu(mdw*?A zL5GC4YU;7J_F#cSIx1>>ISu(cKVJb!DnJ4_RoHllERK#mEpcI{sHhU?{Z2{>3l&vV zxS5y&4fZK(gq-J~6EEW6z=0Hiz=MT8Y&@d(adB9ZT3S~TZwL_}MFA+X8!B3jFd>mr zgcrLYn0I!7Lnh&oVD90tNX+NRg$t!H1gYqo&`@>`j=KT^;31CuFvrFajclPg*x2^Z zT@Z-iKYtSGFnX%04VGnIUJhrUD#o!8h#w(01%ujf=vOS8zikZKDWXNhbI01ifChnR zBq%7TTVn_DSJc5LCT6SXhJc~5sR=?*&ON#FsO)&93~b7|f;4b26$aHkhKK79DGe49 z()8^lq#Mz#_deWR9?HzfXnli)h#&k7gy4#2S(W@M6;+kVx0heO+&w?T!~k&u4!7ol z`>655_&Pc9je$=R_dOA8GtF>@Q`&zmL&WRX3@iZw0dORwn7^F{5)V#=>dl%7=ptlO zF%b0Al94rfAGqr3#+Q|CL%0uV!|LQD1Vq$iWKzES5b_2G1wm&Z4RGivEhW{u{k+YB z77kb8C?l1fCy-HRug3ihX=rH)IaG*Ig~VvS1P-!sWYi!Sf&9t?jv2vaKy}i(*2Vey z;QIcZnUiyI;dUW{T=;$dU4SrA%o8xrJF?KE5RZt`aSI5be9tadam$&P4NI}5t%1Xb zz&ANL*&A-kcZ?M?vHSt&o#}OOS=(D%WVN<`fBY2y3xK=`{2|7F?;aI3_0YfoP`vKD zzVV65Y5#6Xfamac?+mJ4Y>Mk=*Va-aBR|^?+}`rOi9C04X@{f{u8?cZ@F2Mm-3STk z^#;i4?oTreoT9%m!08rTkqaj6@w_uX=%lQ9`UVGq9^gE)ab`cK>4eP`&bT3xq9L*xul0b)Dw+t7amRMO zcALm~U}WUj7;}WmdU@aH)uBowJTY9%w761v>$P=taN@Fbc`*eO4qpaH{7+v7$fSJd z#~70{VLcNyC~YEZbS0vttfVB$g&d#I=vz5#;B%P2o6k}`SuU!@IO(f|Me literal 0 HcmV?d00001