From d5a9b34d9e8bab5a487a3c286ae3efa664b7d2ab Mon Sep 17 00:00:00 2001 From: Aleksandr Didenko Date: Thu, 8 Dec 2016 17:48:54 +0100 Subject: [PATCH] Add calico/routereflector support Add BGP route reflectors support in order to optimize BGP topology for deployments with Calico network plugin. Also bump version of calico/ctl for some bug fixes. --- cluster.yml | 5 ++ docs/calico.md | 79 +++++++++++++++++- docs/figures/kargo-calico-rr.png | Bin 0 -> 40710 bytes roles/download/defaults/main.yml | 11 ++- .../kubernetes/preinstall/tasks/set_facts.yml | 2 + roles/kubernetes/secrets/tasks/gen_certs.yml | 6 +- roles/network_plugin/calico/defaults/main.yml | 3 + .../calico/rr/defaults/main.yml | 7 ++ .../calico/rr/handlers/main.yml | 15 ++++ roles/network_plugin/calico/rr/meta/main.yml | 6 ++ roles/network_plugin/calico/rr/tasks/main.yml | 63 ++++++++++++++ .../calico/rr/templates/calico-rr.env.j2 | 6 ++ .../calico/rr/templates/calico-rr.service.j2 | 27 ++++++ roles/network_plugin/calico/tasks/main.yml | 43 +++++++++- 14 files changed, 264 insertions(+), 9 deletions(-) create mode 100644 docs/figures/kargo-calico-rr.png create mode 100644 roles/network_plugin/calico/rr/defaults/main.yml create mode 100644 roles/network_plugin/calico/rr/handlers/main.yml create mode 100644 roles/network_plugin/calico/rr/meta/main.yml create mode 100644 roles/network_plugin/calico/rr/tasks/main.yml create mode 100644 roles/network_plugin/calico/rr/templates/calico-rr.env.j2 create mode 100644 roles/network_plugin/calico/rr/templates/calico-rr.service.j2 diff --git a/cluster.yml b/cluster.yml index 6f8e63505..cf7efb4bb 100644 --- a/cluster.yml +++ b/cluster.yml @@ -41,6 +41,11 @@ - { role: kubernetes-apps/lib, tags: apps } - { role: kubernetes-apps/network_plugin, tags: network } +- hosts: calico-rr + any_errors_fatal: true + roles: + - { role: network_plugin/calico/rr, tags: network } + - hosts: k8s-cluster any_errors_fatal: true roles: diff --git a/docs/calico.md b/docs/calico.md index 81bf4c5b4..954cce0c6 100644 --- a/docs/calico.md +++ b/docs/calico.md @@ -50,7 +50,7 @@ or for versions prior *v1.0.0*: calicoctl endpoint show --detail ``` -##### Optionnal : Define network backend +##### Optional : Define network backend In some cases you may want to define Calico network backend. Allowed values are 'bird', 'gobgp' or 'none'. Bird is a default value. @@ -60,7 +60,7 @@ To re-define you need to edit the inventory and add a group variable `calico_net calico_network_backend: none ``` -##### Optionnal : BGP Peering with border routers +##### Optional : BGP Peering with border routers In some cases you may want to route the pods subnet and so NAT is not needed on the nodes. For instance if you have a cluster spread on different locations and you want your pods to talk each other no matter where they are located. @@ -72,6 +72,81 @@ you'll need to edit the inventory and add a and a hostvar `local_as` by node. node1 ansible_ssh_host=95.54.0.12 local_as=xxxxxx ``` +##### Optional : Define global AS number + +Optional parameter `global_as_num` defines Calico global AS number (`/calico/bgp/v1/global/as_num` etcd key). +It defaults to "64512". + +##### Optional : BGP Peering with route reflectors + +At large scale you may want to disable full node-to-node mesh in order to +optimize your BGP topology and improve `calico-node` containers' start times. + +To do so you can deploy BGP route reflectors and peer `calico-node` with them as +recommended here: + +* https://hub.docker.com/r/calico/routereflector/ +* http://docs.projectcalico.org/v2.0/reference/private-cloud/l3-interconnect-fabric + +You need to edit your inventory and add: + +* `calico-rr` group with nodes in it. At the moment it's incompatible with + `kube-node` due to BGP port conflict with `calico-node` container. So you + should not have nodes in both `calico-rr` and `kube-node` groups. +* `cluster_id` by route reflector node/group (see details +[here](https://hub.docker.com/r/calico/routereflector/)) + +Here's an example of Kargo inventory with route reflectors: + +``` +[all] +rr0 ansible_ssh_host=10.210.1.10 ip=10.210.1.10 +rr1 ansible_ssh_host=10.210.1.11 ip=10.210.1.11 +node2 ansible_ssh_host=10.210.1.12 ip=10.210.1.12 +node3 ansible_ssh_host=10.210.1.13 ip=10.210.1.13 +node4 ansible_ssh_host=10.210.1.14 ip=10.210.1.14 +node5 ansible_ssh_host=10.210.1.15 ip=10.210.1.15 + +[kube-master] +node2 +node3 + +[etcd] +node2 +node3 +node4 + +[kube-node] +node2 +node3 +node4 +node5 + +[k8s-cluster:children] +kube-node +kube-master + +[calico-rr] +rr0 +rr1 + +[rack0] +rr0 +rr1 +node2 +node3 +node4 +node5 + +[rack0:vars] +cluster_id="1.0.0.1" +``` + +The inventory above will deploy the following topology assuming that calico's +`global_as_num` is set to `65400`: + +![Image](figures/kargo-calico-rr.png?raw=true) + Cloud providers configuration ============================= diff --git a/docs/figures/kargo-calico-rr.png b/docs/figures/kargo-calico-rr.png new file mode 100644 index 0000000000000000000000000000000000000000..2dacdb5156d3a963b84d12a55138ba1b8a8680a3 GIT binary patch literal 40710 zcmc$_Wl)@J(=|G{yA#}l1a}MW1b25QxVt-q;1(dko#5`l-5o-34{qls&)(1b?){yr zQ+57)KS))EnS1)`t9$k8)lGzgyaW#Oj>Au*`G;BJ+jUFJa&)^>@2&i0Y(6Y= zL7UiwoEK3`5J)k^J?nFm?3XR^BQ9}<=bxQxjyOWze-T85Itdbc&*q$eTdhBTUM^F@ zqF+l{+0TI7HTH>rCCo{;4!s)EAnf(G_21L zZ;46r>yK$ij4psc&E59W`hIW~?sq*jUdV5FR$VULIuw@t)qq5IMNMjVayxYAHzTTUNxONXo}@=_exHpjbX*K{uVh0@7}G4 zTA%LJfCQ&ISheG)b(|0}(Nf!DgpNj^Y~t_AQx##NmHFdH)5&I7nAt4 z`mrmo(N44Ns^VKW_@U|w*~r@GG9BTwG|Wm`FP*tN+zEwR>_K_bac?q_kd5%SO48xB zW?7KLuIk$s-EbtLjd$t#QldVtdYyR*&SzA0duECJGcKPel#vwHOo~I8NOt>5b-{ZE zMUGtrhRc5kf$DeBi+(@T_x_M97=fz^gjk^ztu*@Svv1vB^b~*c=E8HFI;D;C^Ws2K zQ(NV!`3+KLw-KL{Lg)QrGy@ZA4%ZI+8JxnTZUTkV(?^v6r`yy;X$NX(BLsUWhL=p- z$~5c5sNf6S9( zZguFQB`8i$QhZb9epb=GE`WF zi-<|+X|A3~lh5Z#^Duumm=DjW@tTs}KuQWIR-* z%A}4z<`ZP2)Bq;Majs|q*ARM}WfE*Ogk@@ANL5&Q$7(A6)Y;7znrQ5cg^L&b8w*6=yt6s<%RCvcy;lR6&uC0h$5TM+HZy z$E9zu)DQ?VrfKp0g+ew%4e!2p6T*GMw)v)HVw76#q^2^Nub~|7e#r-tlLhTr0Czv-M37;l9&n2*T*028m5AA|%O zpHUK69FiQ1WSo7~eAD{7f98PUsqKfM#?tBW*U%+me`h@Cc+s@*nLD5J3RMUnwDvIZlPC4(<8neChw4$2d$_XmcXe1!&`aC>VJ z6s@uiDW6$nR>^ao8ofX;UpOcbi3sZWs@zi0H!*fF@||tQYg{8|GCmH+jdF8&lOnP6 zc#2+hGUh-h)3iqXpol9Cv9%ziTaX-sFhYUS7|E|VqG0|aW04eLb=Tcbbk)5a6)TSm zQKz?-UXrQnc|VjZot01@HD|?5Bt5^F3b_PVwi{{1>T{UeFQM&Ea>#0OR6F_<%4pXJ z<41Z_$+_}HQyBF_it;cf%(HMpC!tdES~^O4Frz5>xF=cKZGdGaM7Gb7;{HJryVdwM z!)>Bh)p4seSX;azsATY_O_L0+>oh~+N@>}Xa@1HL?CyO>6d2@^Z?@_>0?`FdkL+xU zqm#L6bd6~2L|gv%4r$9aqh!OTUuFeiN++~Fl}c(jgEORKR~Lp#WC3@LPNW>^uz@r9 z;L8b!yTi77C!uDsCyfS>6f+8(7`kN<GDxv&$u6;-N5Ggg!K@$4CY!r9OTuwCcvlD4`-F|!71qS`_z01zB-%~UjV{7Q zegpq~o5a@vHQJbQ1FVhS{cvrvTT78Y2zTGBt8_m)bM#>e_HfT4Vo#XLIK{EX)EmSk z`b&POjNdr#TV0lcY5#jj@Et{Et>!u1w7#UgjK*r1$=_fFaT@O?%?U>}e^}(oXyvUU zFOWN6Up?BSD1!)xP!{n6g8b! z#dnI;8`7l<`Aobb3HK9|eUm(t?v}$ueDdy{LYHUNArHc!TYH%aq2Q8HH+L0LllbW} zU1y8bJ;|gpY}^l0?rrQ1UqgN!Szaeu`;{JVVMf)PYbtXdMoG=NuUA``3a}iCJk}aj z`h0G(%V-8^ntDw^wUfTB>G80vj2L!}rwuw;HgyjlYw68bpD_!Vr<%m}JwUx*!tM=3 z3R^m~CGR~Wba>o^XKkf8E=Vx9YMEc_ndTn`%iN2gBKs*5i9q{1B3y-br6q&{gKw-= z95zKq%BLL32`44FkW=htWQ1^r`c(5)RAQmppI!dfo5fOtsPIyB1xywOUkI7GyvcI; z_(av7bmZoq_w?tMPv6`<0IZ;>g>VkhWAd`om(Iw$5NG~ zTjg~0J`T9{VrMgk!dlMl45f9@)MKJo28pzXetMavzB;9p8|+qpt2K=1)Ra}Y$Y`%9)gIM1o)#A=Hil3}&{zc`&^CS{0etvC zOR|gz(Zd2!3t}B<&mKRG3kVPj221013O#+e;;BsG%jDi2cJJNg9l!J@CPo#8B@G7t z*^ftA2K&!{>?SSPxI8W>%*L~06!XP%{a&8WuCDsZ8`j{7iHUhU2OSQ}8@lJ`aZm&w znZ#r8L%5xOkx)@VYn3M-OUzKxIFP+QkW_wfc(~{DTjhX=kTZ~5lu}z ze0=;b)B6j^35^Nzg(4y%5Rl+tTU%RaJt%?(BdN@~M9US}$^>_?Zjs)y8<_ zh(06l$Mb=``AXusN_~_x*?w2a-XN)ON|2C{8v!sxonNIA6|}X7T_yPg(W(EO%TX5w znc)3;yHB;*C_cU-nQq>!)#mPQ*u!ZDaTqe85pU)j@d%7R4rqezVM^t+CnEBDbolw} zR}CTZW>sb;0U`kxY7{mDcn_0J*tG3mwxGhOm10d7$n{hi_Iy#HT|^FZ2MZAnP}lQ- zVl^A_pS3dO&9uwyjrikFu2g<64)It5DdkJaxGAeTTmf&cU*kFBt~9H{e|>i~0s=zM zcAOw`l60Rdjh!iWsDa5k@7ZpF(z0KBKBKrTPB-FvA4@8tmTfa_yc6x`Na_yx(9mOl zZs}r`EqlL*H^>BBovt*xe;)=6q@u3Ao$YlZ*1RnjQis}Jb~H+L(c58{ydD~PF( zLbz+Y*tfU08z?BzZZ{}<%-^*bh}<{8K7amPMHo5#cbKIL`0#MpEOff!(p$@C_HRl~ z*}I?G`wfirBw6^mW<#+j5N{IYw4C{B;30y_V)iv2x^vD7a}O0QKwso~+)(Z}#`96W z)T@$mEALZ19-hj4{KmOk1Of*KXL9-_Ti^2$pHlu$Jhx?v=Lb|)GMcd90Uj-T>u_;L zesXr+)#Id`+rCzOZ$t0C03#4IA7E}}sg{+4sGrlZmB=a$^8ERZ5R{Xl-!*pd%fnY3 zv& zU|_Xd(l&lr*%7N;C|^w&s{EJN_IG}bW`z0sgX(+EKn&KIMoe2@;l;CyzSZ{+S}V126WvvK5(u^dky3Sz=LGLA)kAIqe3VcfwK+1^k7ulN>E~@ zgFv?r55}9_AvR%~n9`Yl0>ZOtvnOo(Xt5S}4U8a4B-J|ayf0ti?7WFM=LmyGw%nS? z;h=-0WfiR$8(YQB503Nh5ovu_%E4}E+uhh6sf@6_;D*cRo()K#L04S)zmn=}iq~Fl zn1Z5Wmn$yO#**XDf`gva`#%RT9Ll9#pkCN_s=7rZe%!2ULk=M}NZKCRiqLSEHv14; z#_q+u6(`{S@qo7VX~FNK=$c(byIyfw8DbpY1?;jRSIB?jKVI+Tdf7fqJl~KP5oO{T zx>0;nzYxM&z!rQK&@CK=`gw7KLt&*(~a+BkO>O+k0FL8e}fKllelGQ}Z z)t-TfZ#1wipW}752sO)ob23_{MrYT4X9h$u{C}f34#fpdcT}YrZ5-th|LxM)__1x{ z#iIkc^dH3BV$s=ebWDMJL=s_gSCpp?q41=x;PYDv|MUmU8WGibDpri%cP*uy&z+K` zCgGePCyG=DNO*D9h0%=@bLLU}NtSm#NwIVdsU!lpTSJ!vF*I5ggf8GLZL>rCr5vbUUXYV|8~LvmEq0- z_wC197rjCY=hl>BN;W4|Ol-dCZn)#vyAWgc^94q5ET%)Y$Zn;su^F!-JrIRuV*3!mUjKC;g1W;#klWxhp610J@K<^4h656%CT}(z1Eis*VLzf-BT8|PZ5OS?_XxMreOR#o@<004f*>8ZgHt9&cZr61l7g}!c6ga zG9uy->FX{|PIMH?{`gx9A9j zdY%CG@(}_c_CH2$?a(+}oCKj$Z|(o%*7c2zQ=; z08{U<;?~bq7)trxX@`HnlU^)3^{D_*d$VH!kaA1Z(TJ;l%IqF&u@|Y_ifvm`k zZ4g|B3#h(YgvPXeM&15p*O}Z&Tz!{`nQUEQQLpbbSFn!Sc)%Kce};pJ2TG;nYB3~A zhJK7JUk{Y#D&#rTtgWu0>v9u`%gUrPwLwwvHK+}aSAR~5y>y(@(hBxU`GgSj`%&O$uj zuMcO*=+u#AByLPqejsFp2LuU(toP?seZ0W58Byv|!3x2Onm4pQ^BlS30}V<;|FT}1 z`GJ5RCqeXpNvAg6-#AI2r6YEmIzYcx`d)uC_Ub5_y5L%OWrU3z8};);^F?>CaI%wMz5FN0=3)# zW4zkHTJ^4wNOS)CzW=>sw9m2n=M2157if(G^7Aq62NF|*hqW^X)btn{WZa0sb0w28 zFR*Q6Oc0yo{W{Z#+&uS-o~!>KM3K+$9c)37&I~WymL8ZruHf8qMuMISZ#b)*g<|p# z;BjIIMS6r0h(Oin)g9mD!Pm*Yqv#hOB;kJ4M@-9rm%GF=eK_wg%}^}Z-)kR@m=^kx zfl=s?(8%<{-Aect1_JsZP+2nBBHl$V1Ii+c1}s*8`NbxXRY#~N4-HAa3q!gOa2)}7!QbVX=d}4jPO{Z_n$nt1V za*2_8oe+!_x)6HQiG5<&1?_uV6-EA#+b!S!#s>h@r1s+V+v!`{Wj9Eu9JK)Q=7L`3#6=*e#~i4i6nC2KPB6VayCL(P ztIYvhuk}!^6;o|oFK+`?*=u(%zRHP6y!D#6mERD4&Fco~@$y?-6`RoZBR_2|ll2+v z?L}GtpqCbppWl*RB0BX{)IZ6O-pM_=Jk?jow(u7(U+PcR>B}nNT3j$)>T<^vh*z+9 zu5l97@_cK|(Tz9yx1<9LGX+8;-MQ{5>q&5JpW5Th{g3D3H_^jY8Q?wnfou)mFqAGCeKOkuk&nMoxNc0 zo-S~ay+%jr{OBl%kBkL=m)v^SZRKyltWL9Od+qPwD?6_6sN`}&(HLX(a6jxRPCXm!I8Wit?(tKjP~H;diueboJa^2Z zl=o}gv3z`pabtEI$#B2#;!H;`ycX9wKw~*lTe;k~G?~--xU;9SB4~kSRLVfp+KL?n zGtw&%aNIoDl|Qk&qo9M$*>Qq>>+24_&u(VY_ZW&K#lG62W}Yv|?!v`%L%DKt>F54) z_U-MIAi6~5fmXY|FC51G6XE42=M99!^TjV6TlKs3Btj-#N#yIBJqpayOR)rXP3D_B z?CyD|FtBeqf*6^}4<}M^t4ly3gsp<}vEsTfD}k3X%p2J+8x6u3x9AL8$^0RLNbv8y z*<-lS?6&?vid!=;$>n3XzFS}+C%b*-T8+u&HQkV*r;%Ru??M^x70N|RnG=l5&@NB% zJk6SCy50H4Mb>)l!s*OVSTLIAz|INLb(p;Mlybj~!imx=v1x=UL%$(|x$vOqhj>jV zG`4?w-nFEEzU0H$C0L03U@q4;-c(?nND$_=wAFIFXe%dWxLoXc{3JUslezSA$5`Qo z+)&@_tM{9Y?~D!TVp{%Hr046{<;!E`=eV%UA-7J;KV@mZv_rvNn{@j>vdAgdOsy^D z*y)wl_DZP)ZJS%CpIB#HyE%3lH(u#`C1_b&4|D%!KJoMA5Bw$H7l${yz;fY>sOyG0 zJ#cErEEVswJU#C-CJ+25Zz4QYpU`+&QOM-2toab3m*G|M^5n%gc>`f% zonDgSv8UZyM@tOF|A|Xe;iDlpQJcBWvC5&N>>A%))^%&LDmaAbj&TJeROK*H*2$PEPbGt^(OU| zX$q{v2-~<8F%}S4QYDVm9m#m<&%BvQYqVW1VM(` zV0SoGM{jF%4=&UsK9Eyd0y0u_A!i$(aFj1cT<%|q5?aT} zlQa^NT06yc+!d_iTLrbZ#Rs9B;9oO`W@<%?>M{ZXYbwfW< zvDH^9W5tQuvt1`q_RAYBD^uQ@BUDmh1zJLujSMyK>x^%<2kRhR{l4oe`PWbM@6*3 z>ks72_tP(iQ(xoG?-#^i2ibLQy#LDt5TVYz90=rT!S+~~Z}!n(OPNI+_k0j_40 z1iUW?IW^iQiEH(IG0Eq$&3E~o!T)Z)|A*aH)W(+bEu-}13IL=mfNy!+dWqMR#(&*u zeCtqOxa$9dYuR%94aW^vnfZ~j$qw((Cw1X6bAYozPneGNT%L+;b^p7!yZX^wAO|xe zVRU5Ok+(9^S;pF_^x6k6!Muf7z6rGt0Qy3QPD$C=6f%I{;1cJ9z6eddgc{m4kKN@g zkN$@(u>Ef;zglqxi_X@_cPSMe^GV3@wgZ=i6Dz%78h6pR_58r>v!zy!AA|OmGwQ{! zUP~Q{cO!nt_7X!~{sGM!n4c)|O?0Qtz_h5P{kP|@1vCEB0aE=g*XX6<48!tz_&ORJH`ap$9Vtc!D8xjw(RQi+%~ zU3(@E8MIzM%9bq?_(Ne(8|j9^7nJ<7w*$Lmw-tv)@c|f^wD1A>p5Wnj zsNi%|R8)~jSP(ya7rXyUAWmqdz&*aT?^?J=eyGCM48$MF<)8gc)0k2dk}*|p-X!6T z6^)QX8yZ)bP z)@Q>7-%`1ELA0{(I!qh>)?EI7^j{yOGi&7Ux7%<|g2xG8y0tM1HlE^Mf2fRlSs6D< zozk;A%vA;2y|x@=PoKQODG1c1|dqWr7n^Kjkz+PzuXZzxrR{HO<Xv5dbb{-@e{O||)4$!q z`LGR?>&+UC7~`n5UVp(Cv=8HY0Vv|_!C*c)oxIwRG@mp&Q;N_!cIaB4=gd>teDK9v zxv=#`KQC3d{g-B8znObPVSHE%P3b<0hf@yyfnDwg#Qdl+r~4%5!=?^t)oAs=WB@k7Q93>lQmm$MZQcZ`$khIm;C z9X3L^{p1;p*n)NA?TMdOhpofSgLQ+V6|!K30MA(2h2bOkUa}g`I|5HLti3l}6^f?S zthbnmwCALnT0XXtm6ZFZ+;6;QC>&A3^|+^-1OQ?QvnL#qBq{c$3sJr*yuJAUt>-!ZyS5y z?S0d0oa)*9)*EBgK8>Q02R*NyQ5|JUA#j|j_z>Iss;0zUWt1KYX4 z^H8Ju%KAlCqKeg1pZvJGytrQqIS|zt1eQ@QOtn^lI;94U*Vy zv%EZ;Rj=G+^z%V`(anf$Jia`OtOP1#J8H)_ovx(_k&h2KM16Z9=J8Z&PQIs3~+HFD9sBj6==I|7Bs1-jml7Z^QnX}T}Y!?+1cM!Ork zDm%dPqw_QpFQ1YFxg)c1)W{LXF*`d820A!6u$U=M`6J4QPxHNX$OBZAsfVgX2V4H7 znP~AWPnL2B?*2|LxW^4yDEq_$-Ud#WsYc?)A%WrJ0`gatmqmIzQ*YE?I z_Ic_>HX;B0jF{NNEAfnUkD;Q(pWU9($2XW}wo8%a+6_kA4H#9q6AOc-zkld)KqEBA zbEVN;8*Yv_P3Z4F^b7k%r~86g6;}Qr{4;KG07g51LDm~WfF=Wv>@7;n1^F_@_DE40 z(5}WpDWYrS3jIg>eAP!`h>`#3qqxI2n;9S4yVB*}FuBd3j>shfLwYi3S>$Lhr3b>KWvAi{jZM!m*FY>aLg_5n`- zlg|G!DyxOJA}fZ2{W4sUS_JIz`5d#LOfSs;w4*z)k%#G3^R4b_cyGax$7Mi4H6W9~ zeF_yFrwE!L+4t*V;?#u!T5a*@82@N6=d{bSN3Vp&Dg()BkN*mse=8JAE%lM&nS7u> z(mmsUC9mC+*izPfxls*b7c2HHNRXN;RxduC!3SqqRLI!w0SEL~DTBB^gNrb@mD>S- z{xeq}?IfUoxgU#bZ?SFjsi?8iBYN3~(f#|HPmUUSc!B6RR-_&bxx#(eFivgkCI+U% z8r)6<$)+@FU~(XvGc7VG ziR{f|Od)65lL#yJ{7=0*)^BP41u5Y7z+0wQ1zxzLrnZKl{#UgASMyt0bT}TeP-dNz z$g=bQ=J=*xHe8H+Il!IiUtu?`lHGW@@f?YsXR}7{;a+w7#)F#9bpWu@A4vrH62gC4Q^Y zBg=P>qb-I@y^|=GoZPgA-AB?{b!eOXlc_(m{!pcE{I_uP#l~B<3nyZkV86acBP#dYsf_=K^i>Gs0 zZLd(X7q*E+9d4i||LHCOJhFMU2|kWacrOC{0X9k4wzDLwSdy;j*wyQpNR|IV2keIhPWeG(IQA`}_2j>AoxA*00EkBo(iZL73eR;jT zy*-Z_pRSil69;wB866nEDMfTV?lAssVQ@Y^yZA%6)aCD%UuYPKcl{g^pE)eh9FoIc z9}ZYk_q(4*Me5Gz_EajYYmaM*QhBe+NCF;y$lm5&-pd)972UC<+RB{v)ia)k<Y{ey{}WHQZthJh)!{}~7~Nf)^cKM8&ZRXw#{hxJ&4<^$9a%-e1y}0!ZLv^b z&Ek`EI5jS}BZYj9z^E(kSDeV0n7%jmt-#p!Kciee4WE1>;LO1U;~i%AsdZs<I!9hkn?X#%oQJx$P0;PL5c5jp6$u=QM7SDskZ0&>sWSCkEhVa8=Z79&&( zEg%Cvu0DKVuqUn<7K`W?-AbvN`etClSfdWWZ>d> zFrMSPAj@o`iWd16D{VD!N8l*GNW!QOUCFFiv@7x8Y_jLn3ukUV{tRyH2R0;_tY&EH zdq~;rk$t8!c>&kTxd_UuH9(=;(Ddn$^q90I) zfORkxMWIp0iiJdbLqUKYg#~yS$l(41UCZ$CqF^=q_FU@{7b>rd!v;Dm!0)8Mfq=Js zzps!(qv+NB?a6=A{$Qt~s#;A5zb{5hN9S;}>LyaT+aD(T#}yftS5#kd=3et9+5U7f z51FvIc$$Q-0ye6i>NUezLafnQK|kIl7@1l~H_Ykjwvu(n8RAVeat{`LDncbv4+i2l zoNE|Zd!93KQ9Z2Lm1&@CIzG*auSf@WGk`@3V1eW}836F9+)h-$fE^$MK$)w)j^sIt zeEZ(h&^8WJe-9R7!%CA7@T0+NOqr{=HZcjS&(2s@adE+QFZJ8$Z;LA7aS;7DIp2>M zvzWm}%OLBWvXugovQEzMDA#)Wo@Q$h9s$AO<>^MG)aA)pQ+URT{iNko;D7In;8-X$ zwHJvo|NqKC&X-0-1*&;yECUaUvck>#-rkg^rK38^!o_o%Y4bD8U zvn5(ds6Wh44*C9>zta9^{|wfaE|pw5q@bYS>&13n>2eiK~i7Gs2J@_Vb4IjN?=_g{M}Qo#Wn z7B&$s9{!X*cbKnsY||oe&_Ab#wuM-&5&dw|g2K(sZ4)Nksv#~8eSdw75Fy$PEdBh+ zYG;w1Q?JX6ze=3EE%zr)+CCo!< zcqtw7;glds&&-~a*5i7eTP<;jU+@ECHxnYTO%^vm>_^bl)MOJT!bML_4Fds#+za5H z#*5#f_$vV$U^XL^J&+Cy9{5Td#%3#E^-eAf&}Y^v5-Ea+Poh5Q`ibrg&L16ndT;~} z==q;5AQZ_xcCXnfUa!65sAUV2N;6%vrY%`J6-d@C#^c3g zK>B}NW@By=4#VPAitkBu@nwleSeQCv9Pu&r2}CoupkUnw8`7SmMtl zQ4^r!dejq_$j5}9-74(uXkK3rrOqq1UYect_~e$#eQ%JN#{P0JW1*7c`vzJpx2;XU z55o$hL)3OF1^MNAN3-;(!zntF%**R|Ck=3=r-$pX&j(}(`*A{4f<2Tr`utfJwY*!S-!65dT()oIk2z}^4e4-yGO zt|lBq$_D}qxeE6n_vdjSpoh7r`+jdYG{vJ?^g)nD;e&MOrtoUf)DF6Tj8XV}6fEvN zwq{3sSM4b-Y{1Sg%s3t>?u^Z2S<<$ukb~mTZK9He4EVkjEj*(mqR4Se~5@$oZ`?z_Hs(6|m2Scy{a7QuOn$buyCnVAXT|6?=d(hYE0+ zo#jLTJ)Iv4+DCb`iB0E*4cHrwR`Y4##N@g$Rr(R#B;8-b1Mn>1KA8LF{UowY zH@S&mYNn~E1ApP$I`f(&J@$6u2LYiD-=`|yC(j=yZ4XZ*go)DMU}0f9gHQy*2NS9D z>+9<;aU4JZp{^=(G4Cr&eDD2i3>$|cCiZ)n;aH2=>YaQDpEVXf6q-%&J{oB$Q5}+DI&_QAP=>4gF6`K9 z8#s#iZEOH!0Z%}e2}~rhH89VYHIySy4xz}`IpWT@CoKL{0(>Y8NL*DE>jO5sHbG;z2{8m_2%xorH-wz0kgmMYs~W zV!97sfC!{0b~2c&`z(h|pD@0#k-@jI6 zZ8Mi;EbFvjA;Ek5`ntx)Q7YT-X}x`X&hK_}^1pxoe0=7K33#e8LX6t>u2vCjb8@#6 z;eiek=6-n@FN!GUn^$gPf%}tR`@1d;19s~!CPYXw$}wOnvvO)r<}pB$8I@|sh*gnT z@M@n5)_{SMdKDo%=$fPK82Q_hKM*Pj)bVbhMD58^Xf=Tv_c znWm5&${U?2`KcpxV*{kp*9K=h3gy)z#uzqRW?3sh+vhYV;lTtzXz0CnSh3G+;kwZN zGfWrCjf*6gs%e-_zJs_O#r@cXRo(0Q@vo7zAYlD?b2E&%-?m}(b(N0CbJ1bhw&Bl9 zMT+@MZYMguy}ja)Nk@UOqFX={3SL;acnd6&dO zhFNHY90Tgb2-{BsUTwxcaD*V?c|lV#Wr)(4O`?9q7rpFR<0LSC zLx9i#jf>dXBww8L$-CXiYBM){YcGw}BH9S}O$RXzg3R#nT0Y5xWnZP?&Z3_^3OVNX zmNbt@fk6-?k$%Jnk=yTL;;!QykxDg#mWFdg+-)Q?(qQU~Kb4!mMHS|Q0HVw}0dUY( zuoMmNBP8P0-Le8j$#sF;B7ZGO&q~dRlT%!BB+h8 zc9!}_*5zjap2*9;VEeWx<(d|E3P}KcAu4J%TzPiO^w4LkAlu&#+TZ{mcBm^uwmRoN z>p2nD{Ha_tjAXzo3aYA%vsx3hy1Pz(%3rAqOX6g;7A@l8$9`<{QE<3W^=g^@TCYm- z^~D(05DBnV2Z*^4aZD7=UpL4*E?9k}3Yhj`dfCU!v-_<{V+Qq=Ew2Uo*=tHWlBcoh zleX8-#Zpqf>kntwEBj~B-?Ln|Edc^0k)#sR*Ckby>~ZpAVNwSCks#q1ZCqSEjh!qU zgG%V*nIf#QILTZ6XyP80v+YwHhqvV8+mWf?=ZY*fiR+BQw(n`$5?Uh4sHnB&kJ zf{u8V1A&IF>?q?WWTHjE>Kt_R0`<)d5+YMRZ`x)m(0c4iD7c!82SAPAc}501|U^vnG#pak1kC;D~}B83P6 zd+~=f%Wn@*voxRsB=;4w^rLc#fLQ%~L7%F!bM2bgj1HuiLK1PPd^&WeS`it?JNN|B z9I}fAFj#4)E{&KXfhP{Y`(KJD2k(P zrR|lCY?)~l(4NRB2G`%~dZ2?KYSj<{xMj<$Mb79-pTW8|FD*D?A;XZaK5Gy?;&H{J zRA+pzpR#>^Xizp6J1>#&#+&zu3k=sz)x4JaaH@$pD&ce<9G51riOBJmE1x|{AS41{oVeJP z3|O?!hd1fCyt7a1y;y!FqbZ8?sQ#!`%cFN>a`!s%-+1qr%BHFS`2OO-7FCoF(Q*m= z;SOJs58zGWv&EA|!`k-t^K%pnISgWoOrZDnY~+-NoS#H;MDHtvendP$Q`IuORaO_x zC%ZQV4z}zo07_L6wy=J;Kr04pJM^+n+|x8nhL3&uwWacz(3c?clY&=Ep^btpERN!q zXluI2oxKMYBn-^^PmgsHF0x)h_u~A3gt1)tV`obdtW*&$W)BflZ28O2u*KzB)#{C~ z2Vr+_ngd6_npYNCME60MQ6@}t{!V^(b+szFI^B@n+E69=J@B4euM<(zBT30=oZM>X z$|pWbd(I(b!Pt__+90q^iHZ4 zi{(EBUy#3m?8mc;BHuuGjA;Q3ufF%K7yH-Z%ZY^&9^()Hq)b^Fq0k=ez*QHO8Uf)_ zol{y7DbzQpuGst{B&lS^+qDi+N*sNfurA!E8x#V3`Dle>`vif<%bfIHip$dw!#a?J}6ClOi*<^rbdB3*Ik#~V!Bfek^_?%GuSMOb>*)iP`Q!CNT z9m7dAZA-6&L?}Mzx(MJ`VF%NvTu0^wXgNW7?qRk)YPDXRIIcRxNs@>8I=wSA{&_SD zhKXy_6s78Ch ziLph3dVb_e>4N#*@)t#VO@A}ZSgkWGmr{)dsBsYwJO0&O#?i5w@^CXy*aO>7;ALaL zy)r0FIfjt=p(mSATG|v=ENV6i&k!@mOAekOI4`NJHisk-=rC$;B5Q752-|SL9Y%aOZeQ8hGh}fL%W}2b=5U6#!t_3Lc~p zyrBQwx_*4jBZdCGE`@;O&Xp3lT=8hE3mZX|Y=XJ(n!GpSX>blOK+j(|SgRID?20oK z$0w6?x>BZSY|~IAB4rxnGa5q2q{d2{rw{FS+J>6-#JEv#%*k)sqa}60NyC4J3S2UB ze$CJHQ&u$$#48UA`c;_xyoI%Q!y3>4=oZn%@>1v@-vN4Wi}9+U;}$JT2r`}P*DqgM z$L2aUa?y!|Y`5q?6W@jLpBt7@6%COS7BJRjcNZiUyw7C916*H)pLFtw*4%4k3ba_H zfH?Z-`VHt(Yb37MtY}suhf+6RGfz9wz}{`X9ua!`YF^^4rH@LS<Jvc$>%QqeGG%YX;rDk3Re%tk{6|qtkcr~4utp~tek8)8 zenzSlH?ikr!)&yLFQB&5uvStRR%~6})hEp+!IAP72weIr|*=}DKFp8R-)vwWJ!0fm7ZMdadfsObBXm?q9M zJtp2uHc2$(tdR_mP(*t*_2O)>M>T#UlcRSVi;(bLgMYO|FkqfpJ-f=9En+5XtHN$E zf`1h%Ut6WSvw{X@+B=|2f%O=}_sZx~*q!UJ7UqKjcm&3bDl^UM3&~J^c4)~K?(*hu z{F8SF0F@>b7om7tODd-g>!KPxj6llO+tq|*jTg@74e#5?STf$a{0;cqtYE2))8n95 zoYB_^__q9sMA>~DKMXj?D+icNnshtS0Dk}*sMhY}c*OjB`R=5XFn$bmWO#c=@KW@SX*Z(3D-4%co zT)`>1ph;ORWED?@g5#deT&>)G4*-qeij01e}@ZGm`zh`GSus;LFo!nH%Gz~#H!&2}a>9T%^W6SI1a z1U-!?|1;UL#VoT2g-)oKdc0cXsTmI%-ru-GWI0FYhi?5Q?XJPC9$NZYQI#VP%nA>ZkZ`BLFIgNkqF>Ak=<@pF+z|w zSy_ttQpa5QI8Zi-tq;Wkv;dgGu^0m+*|=`U6c&{n77*(o05OnfHT z*x->nt>&7UwZ=(M-yY8%0^g*#zLy@H=c4Hc&@t7*{x7*3^J5tIL|^C9j)>v!j-_2_ zyw)%dJHq8ex++l#@L{_Go{^^y;LfputW#rF!%*qYN6vv=mVW27tL$4bc_4S~0yf;x zQdtCHupC}{|C@IsY#qX5PC8|67?0frAK)1YaNqRU#YVx5U2wUTR0%|M$55Dxhd`jP zvCFDs4d23i>zF_YRD#DhCTZ(EMb7KLYHas+G~>UHxGsEYto=Gx^fRC1T;{CjRQzf7 zz0P0CuR z4ejdDd+4VCdo+~uc^;=(oyP&R3`3=)TEf`*8jUp9!ZJXFZvHkQ$*-m)eym7JS;-HtB*-k| zfj$kA1;n7{t~cs&Ni~%qD*R`;(BFlnAo(wFQ>T#3C-Zl7?&>=xEGW)>I0oVuQ`S~E z2S?wKQoHWj>?^rvo|)f}c%jvX>9R_%8H*oW$`?D=X_Ia>9z5}+%F6>z_6_hnz?Nc< zY!U3hJJ`A@HC*h!ek9v3d)TcINLWk&))+*6?>Ajr2P3$&qE$Rerc9m!nrQi(9+zh} zXb7j>-_v%zqyC5+!dTNLQ+SzI4nzTXu1u3VE_)<6*{^c%Nqt$Jbeah->%@UxDHxqH z>wxP;lD;$pjod(Ly!o$z(Hl=zbPrkF35u)(5E)4H7xsd<@I_66n5v?IY>d^Sr=a7L@3P9mMS+UTphgOw^4v(aSf-X;teBoik@(oh7*fVLb;@=?5o1Ibu)3OE z(8$^%bzBpdN^jAM})?P>CSJlz%S5VBdT#;4cVZYE!FF!^@*o6qM z#(I5vkFpaZwMuf>k5;OfUwYRR^{s|_tfkYwHwH6z~5LyajHo zRA_&$&G3KVesz&}`9Wz>?tfJ2LV@+U#%EQQ1B!ydM?|9#QefetG>`Y_vgd>jvfV=^ zwa3h(-oq&0uIV&dL7mb8CueO~@_`lUN?6@If|sU5Ij^g z%Q={7MB_m3GL+e<9az^o5zryZ2)PYKsnRQ+b-m-fp$0g7S0Pcq^o9pW05Q8=X;Z92 zhkdq4QFKuJFNzxQ%_Y=1nlH7`FX7v*d6X1nL;C*`GKUEWZLhvY1AvqpA8U$g#P073 zerJ9^$aaJSl9DbWv(=0iSyyFn789g7dpN1AsQxgIWZ|V9v>pF*7QkmkU;5P^L3NKx z3k`_nz#r4^`5u>rmZtVzQ`@c6?v!8Zk5iY1>Nv1E_a0mfI&Yo4tE%y6S*PqO@UX8w z{QNSzCRMc2Czw`?^g%hOE5C2Q2#+Z)Zon#X>ae0$@GpAv+Met14_e2UNA~&8aD-G0 z&i{=Y9W72@g8>7!C17XJ7RhEVd6#zulpWws=xX?i6ak?pkpJ^&5uvmFmUKOK`Q=?z zHPgiJizTOSNj~Oi9}R`b_7@uGf(#C{Qcy%%Pl!IM7fGlV9(XW%;3UE!uj1lfBS)vX zg5aA|rjIgz{KYz;q3V8g%_jwZ->4>#_qwzCb?gImM*Cz+jp7eB!}p1^#jpQ^x@Q@4 zM?{q|uwP2apBu9%hsGGW|9vc3iQh_l|6cm{@#dU&okEwsOJ=5CwIUg5DblI54vJi_VLg0QSC+wcsaSu%AFq&GSxugR(rlETOV_RAmy;Q#G^M ztoA!u!s)BXtZ;XrAf4|!9f52D`c@6)vS4H-Fnl+DlsdFw`xPPvBU{TrG}+B~R;fS+#? zqewB;6?u1|;{-A{3)oSwYvsbP!@y5`P!p z&;SPmSdre#k$g_LY;ypl%_Q-kO36ZUm|~$&{EC#e9npi=wcw8jR66&@iSpt`&Y?ml zL@RM&r}1CbJ#OUxVX;TZ(*c`E#NeE`id?KIJK84q#MIal|2pfMye+jknAx6{F0)SmA z^?bL#hbyo%!$v?DakR*7SZ>#=`7fGzm1DTn`W0xKr7Dt<0_r+S8J!HEEVdRjr^RU| zYxs~%HH6(xSzZWj_B($toyFzXC#9HGSb$%acfPR$ksQE+Y?0VF!Z38fa3O^_AUg#s z`$0Kz!+K-pa~n%(1Xs!OZ@6dPJ$g~%MOKcseDK>?q8-3%2}CQ2ek(D)_gHX03ZKfc zu3vic0=Jm^hP`SWqigfa-&-dKzKba$~CLsT>h^j*4egwT-*o(pO2Std%d!Pwk zXr*e8Gfkt)nYXytGF&v{`?W|h>A|}K?1i7J^(^@JhAgq#N~kSkaW^2YFa^gH6bUeX?We%=M4-uPc9CPNas4Q_1s{ev{uE3KZ#z;uX&{qQ@+;e1{5XCA$X z@?pW%Or~S|s2otL96y^I;x#JuwCCRSF1fjVLz72 zMYYh*$f|n_c90f3#z!gLNFWC+58iR_TH;{^(d#nds7Vi7H7x*V{)@yF;YD(%)(umK z*Ebs>k;3S$Bw~QWJt0$HM#_pM2x~w_(UVmgHtY5tzA=pKjqVK=kbkoyz<$4=QlPb? z=DQh>jy@>kgU~r+|3Yo@!y|~P<4rOhR~}!83^3iA*1c-&YC}vP-ClGukZ>ov=vYzV z24I_jDdr#n1%kF0Mm&IqZXeqA@mTs>5O0KGb3XZn=_eeH(r7MQTW$>UHUZJYWHd!% zqr^d=yrHukF!hwqG|SS@i2uV!(a8T{k1=L_n<0IPs0u8p2DQ*6&t_sw%7X!}e_@p9 zUu5u=djFtxbcoBR*ZWP);RX^?+wHR@uxB%sJ=grIBq08xt|@JTIbA?joGcls(-Q%nJSK>7+-zxt9TX23xj`% z+N;L|XNi)q=64#m2H9e9XRL*E*kRZ! zZ6~E}!muT|+iS(8bKD+j`un0Uu@^r#LW@omi|9_pXUT1;{g_|MK~;-trUP-px|IXh zcKL-QMK>1U1LaH#q#Ks6n-=l}#h1lL<9!evgg;$BZpO4qT?BWUSqI8jVzO%&LfEWM zwQ7tX(3^h1+Bz4$vaZk9J2J8ORN;M1)~{gFHI{qPQqGWo;&mIZ6hbcYbqVd?atgd# zEtjR$?B!M$7cYRK8Zs?VJ!SHOxKedyTc(3VFj5vtD=T{nM`A~<&%)4xry(M@`vpdkNzno8hUW{7&$OZ#F|7qdPr29RM2mTi^MXcxuLJ4#LYMAK6VTkon}$J3uESGt z;v?!MU@FUGX>jN1fizPPqMsW?GT2Bn|Qe;|{lwhe#Pq&Xpau@ty1mE>h3%c`agKlKZ})2HD!?nc*apvAO$g~$ zBJDp&j`kbh=+qw!%E!CXCVl5ewK`o~X4G~AnrKI*!u zKdnF%QYjWg{dzYEyr6mkKyKJrBzf(Vej6JeKb>JYCODn?D-#Va=qH$zb~RcJn6)nqqA1+ zc{5_7U?q1288vT7&?8tI(UocF^NZFkcxm%TK(k#bw3yMtk_bFnX(NB*%48wsf9chjBr#Q zh$B|wP8G}|Mp+VQ$nc@}E@AdFn@W`JQ{r ziB;KlVV%YJlyzNA0`HILP8@tL%$HlU_hl^Ul;5%N%C_z=2NA5r5<5N0l%^VsTem>C z#biP)4dbr*MCPvbU|`=A?@e-KU$W`95oOcCBq+WrJ9n+65?(BFUN7e)bMP`qzwF{2 zJ_T^$85d(BJ7c}iZum^M6RiZ6bF|+CmWUO6gxFELq&m5=)j(HK3ng{oSYWAj8pm3c z%ZN63FrzpleiKSNUezlkt-}Ls1#_p05FLRYw3Pz}i)M=J{;N__HYaWNX767D_YZi{ z$a(W#qy2f=Y#Tl;<;^AF#}1`>{@vV8uT##*5)TVV3BqkkU`=K_jlV57%5MKNUihrk zsCpxNfrDC-;#llG$v)ac*d^9nUTlXI>Pl;voF3GQ`;oGT6qA#7olT{!X!|!d7$kS%1)f*V#i(A-f~qBb?bq6Iy0~K)*J`=5vj- zB7*24Wx=xd+kNzd)RFHEAvIWJSJRNGA|e&OG~OL% z2&m;vuCMLx7&WrHN%Os=aFQ4<3o7cti%l%Ut4C!oO{wh@*5tuO{d=8B4iVlRz!AoG z`3zdaJ**50!H?(lCDKnqsi^{Qi}dI>>N@0FU_l@7Ep#SjvO1DE?Sm6OtLtthhg*LSfy*xJ3Y?w%_*FYT>PC@mcNCr3kO0cMm>~6caf|vR z!?!&Lyy5KAU2;>2R$S|5G;0s43vAtvBc{N`v|kn6i!fKH*E|iZ;RW8mv~`1{4(8HXdP19@UXcvLR(%-i<251wY{bF`pHGN zK;L(9`8ia&6p5xVU{Zh48MM+&#%GNAaai2AKTM|F=UnqLZEz043Tkk6r&uLMYJ?G` z#GhYX3gh8H5&jscyx%-Q#C#CdZME-LHv+xjrAbUJuXHH+#QePK4C*sG!>K1S7=hWa z>#+64{MB4e#4dpn9E^r(Y}p@FBM*56@4=XFnj@fIBq3+ozujlnfov>=%>u~Khj3J#j z%@a{6XDW2_G+hrzLH%@Q!Ch<(NA?Ss1#?e^tQ0HorJm01R&04ZP*Ij#mhCAi6O@m)ig&9oz zzWMDu14{R0{*_t&J%Nrx0996!gI3M4L;j?M`qA8}B7&kY08fvH!F01aW6=S7kv$6H z6kkic-PJ!wMAG9d=8Wy4iVA!VFT{G z;L(fb(==?=$SBeipgm&`QKb{EW1>W|Er@PBzy;KjhbaGY= zeSqwg-#{v37ggp+mL$supI@l5TZZBbEw?lqhAO$0(8NG!2alfl(TNDO`D){B;X>$b z)~W2<8-BTT$7mb&KVUEI_TNiXU=SH}?irL5C01s-sHYE8mz6@_RS#x{wqQBIKOq!z zt&~G%YeWx2q~wWM#Xn(jO1|#h5HQ)A*U$HFUKq(Zy5KBWFOTj1xz;L442o3$?v6;3 z%4rqZ>J-+y%6b_SA73KB!yhw_devl~OgSG^ZOpd>%~|HSM>5H7`M~3)-D@x8CvJpvEINKW;Q*_s ze2@{^i~_V;N{y=8l;3B~voSM_HhMHzl{n5=u5glg*t}rj)>To%iudgL93Om={Vw6d zg>KqdIlV{~no5r3a6R{l<3nh^5Jn}G%B*yaGuCZakEgjfsAbcd;&C)Bj8u@w?SH}T zT#R8|C$VkL#3*W+=>&V1%7_o)RKV}&>k8KVEcUe*MQr>Ce=I9CA2F)x+4BXLfxXE} z21}(5U1I-T+~tUZuiZjs`3+LIcJ`?HsgtQ#;%R#sf5dQj`B>VcO1gioNd*-psyT5z z8f-Ze38ZA;)CZUZtR{|>RolkOZ>n!)u!3(qf@zEon3Ouys@dpzs)7Ri3ZLHXW7voa z4?GHU-B>q&pQzc&m(r{-&HqXy^KOQT#4+u@m)nqh9mQVb))HuKL{MPATLRIR$FUPB zhh9e4pz~m?;_|>P=Va_Xw4+IUrGkb>N*==fa}TP)3MnkqUHFUmBcnE8yu>*nhD%oY zzK!{uC4%u54s?w6#RUK3X~&fn5)<6WaAH%gtA<@ggC^d8zBy>%Q@VxqjhcNWVRK+N z>I75y3+s&${>ZX=n1=^?dZW;(72A}HJZ3h_RXl%Ja8E88MSeE=s@pus!*sXFmh+%j?5ub_WkatC@}<&frk z!&dLUzwDUnCgi=wtKs&%-@=*?K^&j^VTppzyZCHh|M9Q`n^z5-MM)%dh)vNRR2Sn6 zk@w3pl+8G9Pxqu117i@l4VPK-2+>z0v5@F}Le|Eng;D!6H$00v)U=qw=+sKH2vfK( z>vF3ULOe2~OLgdfF~mO9FbMzX~7KE;}C7X});{S0&x1|ENJH zp=m^48X*{S^A2qD4$DUHebD12%rEVWPxFAjdNgb8{a^Sl_ZvherjLf1poJB8zh-9b zI*J~5K|FwW(^>+vQy3KV%LcAUx8gfxEV$C%DgBEFy`jJ=|iZe6AVygp5-A5N-?r{^~BPJvK@K9k(Yk^%GKgATWQP4{e`0&>I+#sv9^|Il~YpZ zL1Rh>Vi^bk!N&U;;Q8PlBa55aHr@46y7U&tzF2-Ki#hBfkM0WnzIeS_XBKpfWI1nO z`i*5Y=`Bpf(deyEfv%<#F9tFl4cg(gX}#sckXv!G-G`^~0{Fx)n(`kf73BSI3bc zNI&PGDb5%f3M+i3bXrB~?MfP`ugQZ=75l@uMyIv_q&LuiuvULP+gPp;g9@$rvFzf3 z9{4$`eTtkt_Z`%woyt~Q*Srs7NHlT}VK$o_0a6^yplk3{Md8|Ls0Sfj*O7k3@v8bE zm$#0+>I$fF-~k;sz2CX6lhwCizBKMAPnG^b>PPdDu8bV903ef3dZsWIv%RcVjZJUHh@^1-5d+WK=a(Ey%P<2rkg)|z*FL)nM^84(a zWpt3HKasrXs90$e`df?xcWdCZfXx<`9c7Ho6+AXq@1QXPfCbN(zvoU!{toA99WoeIe)W6R_WRAa6M9JQ z9^ThWcvqVlGi%>ER|X{yAI9+4jMBs(y7N@@_nR!c(~AU?8E*XZt&?#G<{Z<#zVV=$ zMzs0bU;oDal#bEyY5j?sjBCooSyGX6%7UMPZmPP@B>QRg$du=7)b}nj@Ft^OQsD6J z(mIEzuB0GsW7AEJ7==A34KoV=n0hOR;^#?$vKf-TO{^AN)@#y{vkjQBk%t_ z76U7f`;-o?$HO#jiF>3bashU@FBogB6>aUwmtRzysB_jmGRMuTs`HvukLWXV(EfC0 z06Vu^q7|h_jKhpm;)G|RSFn6|zhE`frQ{>W`j^r&NeBWHfh_f{M@Z0!yLC2+bhIkt zX|TX_4&B8S6Ak6b=kHuD(ykWJxeoO>@9Kq81~r!0Q32_}b9Q^1&t$EZ^gWZ(i}eD# z_{QufjFvSz6751IMIFL<9kwf}9Bd-5ylT@Mz!yv?V=J6D@3gxUPe>K(yXyJPuZ*vr zsEbqE;61&h`Wi}(0HHV;68bD%YdYc;Lg7DdTG61R8iJBQt$oeW!&>TRe+Z)~WFQzqA{5naxJW8Q=Q1%t(17E9rW1aIE!akj;3%c%+# zXhhDsZxZE|4fZ~LHIhqQrlY3eJ|rP;lR69K4hdkhKgGrKy?GM!!<20}atFOlz;GbIbjTAMbcn3#b&KT* zd9=D?j_RDmA6y7f8+I80)D+C!R%e*Nxg$@z!&^iNa$S#EK?1wvw(gsoA0w~LiUnvi zh=YrkjWAysRDNbQk>W!t4)z(}t*Xl=+w!i-dF)dq%?kl!b;!k2E=*9PS)DV$}29D;%R?;LVNkCcUiBh zBDqVdiAeGhdy`5{`On2>=igwIj)iX?YJAm^6W$mBDq5Et5PdiRl9g=22A>nY_+UUZae-3yXoIlYHTiVU$98?GdHc_)+~%M&61B0w z0^2z+DB5T}vJZMIZ6)J&(}9VDLU@-~X|4O%kv#toHnPh=g`?_Q`vFZDnViWuHWENZ z4)wy#pHITOJ}*8MoR}n7N<1NJjOzqSlLK#?b=u#xOj(ly(~;3m^UpQK_{AYHq;yJo zpmYgL84$%x`Y8lOpj(Jr3nZsK(x?$4az-LG9JR=835Q|p;+pQc#_ECn1|umF-N+B2vKZGaCvs{HV5gbZ$tE`@l+a z%boO{cY8urbNV$vIE8RA0u|TdTR~nvzW}k$G$($!%IIRkC96d6JgC6`?Qqi!^pRV| z`VbkO3ho#MJW>&h-a&n7W;3Si4^9b{9myXbOm%oEo!*db=aEC>#-QZtm0sv)Fo2_i zi`c+li!Wn*aZLe#+pAkMCx&WxYLCgm@OV@Pd&5tKkC5P&Zh8=yrEEaEDvHiI4$7a`V`Ll74`j?0I z#l}?0u z<)~hA^vu1R2`rv4-`4x=yt5kq0sR91r^EOfi1a&Z+jWB8fVi6HjHl7m3YWBiF59&8 z51_{v5pq}ZD@sZdQv`zBn9_VA1;*t5^41ooK~5q(ZbvC(!NNPHasK_F=~7CnEgVE@E{~U=lhTDaM1X$R=AIAUY*0uR`)#i?`>8gPSS#;(FO1NuL>b=~}JgL!4{0p~| zJ8Hp++g7SEZiS1oyvs16MmWxF4-;NwN1dq$M5iprnAAGlp_kEnk zs4r!u=x&|lhS{f$x-MWUUdYb*sSm;uu_SoY+yCd8>{!dayF=1Jj3Rf`{GF}qkaa4k zxme`Q#R@5#gCXRJI^grgqx|%hD_AanzO#SnwM;5gjtzgQw*zBc=m(PM@uD+z9Re=UX zy9y^bmw6#t#b6<}qju~WF-uw7cRug{*l6eW>aWCZq?yKjJ-~|M5%tFYlzr0z`j77F z;Znw<3)q$+B}bdA92snnl+zeiy&EC`JdMWKKZmmLz*j4&;p-ByYnIb$zt;ZyN@lpH z!OC>VbxWu!##CMCAMcPk%ew5;YIW^0HXQwYCb1sK^EAevkikb#6#+qNgMlbC>8S30 zEj~2gy-aH45O|dlc*+SPCc=v?T54A$7A(#n2^Y-|K$b>d9o7qLOm}G|tn((RMuXTQ z;R~qt=sq(slk`ZP0J2{HS_LuHy6)!k_ecD;N#>uq8G&q|xs(JWsdMk2qOl|k&e1Pa z4c2O4jcf0pqHDjFfMMMhx64T{y<2ia00eeRd7^o?wV}6~9#2IPa>nlwvGXvKRoRNw zx&y}7@+jb<)M%#QtzHQ%hM8MRd<>y#7a=3hq}2|b4gL2pU3u*cbYV^cwK(SR&v!Cw${r&9J zGx?iSs!XW!RFj83an;tSn07HZ}@VO;EuRo4gf5KcZJZP0FxsK;T@0r2grI9lTztV%S?^J z)&EBK12+c$mNa~SR`m+T+k9f6O#&*-MZ8SU2rG2-)HQg86dNIOaDQ;OWY!JQa(IN$ju z+bGp@*|7?!3={{$FA$4qXI5el&;Cb6nRs|zs$cKPmf9YbwYor`EJF75@gNc_A|;3V z-=?TJjo8y=>Nv$dg&Y zevYt-R|eC9T}bl*7IWkM4$6JrxGajT9?Sbn{qObe=hTN$Xgj zj<~9cGI{$G4$dQx+P0CGy2P??T@8QRhEc>=(t-ck-!Idi_*b*f(8mulB4~$?@^eo| z(FTj)u|RE=4Q4QEciV)JBOs-S1qzz<%z)xLE@fS@Nqhx!Go4#DuerWZEhI^tg$x|1 zP19*rkGxKW3Kx?#AwbfAjEw{tq4?=f^aEh;B_Qf{K2ocHT=!~P2Nr};Q_B`F9RzrE zcuaEP;B9JiLr>Q1oP^2KVVgk&?=K5j=)g4zm^9XS2}Wajb%w)no>(as z*UiMe8$w4<$$HjYT!%Ya)6R~YUJ|u_6rn{uyHX4l{ECQP@u+Bqs94Qng0g?#XZZ_U zi>qtM0ub~O?0|noEn+wDurgik4JEt%-h`Ku*Hz(y&gw+JCjCzIAtmj%^eduP%R(R3 z2%$}-w(p5kUMt|$g6IikesIz(i^}z+VC)HbTqU10(D71Tx2!CA!>Ks6u2{WY@3orA zB)|5ec7A#Vyi>_FIF*nY+H5(x6;BX=UCq9=rj`2qSB7VJjUz6ZP!U8+Sj4xa)p2;3 z<3bh{?>{%=OiXXNlFlk)X9CU$i~tmrOm!s2dhhYcarAt8VfSJwY(gV5Ze^h|h+5kX z&aE*|8kztPg$KY4>}Y>BLKzfvSQ%ju%!@Di@n^A}vvFE!mq@!$%dsG5*;)(F?Q5}4aV%%O{6dT0x01j`Z2n6`Tn)BNE^mhCC=*Ht5s+% zA_G8^08)jXJmc9%60!vStdIcJ!qn0>t@d1;JxD)mf z^E%Vb7=Cu`AE8M?NvI@q=~q7jM>;YXpY7WZK)kr`Pa}7HI~2+yA6TQ02R9X!Ivb<( z9r#4Mhg}(^?j?k5gZxo#^4B@}#-`6kUinp3WGmOQlf+HQDjbm@-p2*05cS9M-fj9p zW(#SIZOf}Xr1F9(RE?A$p)&4B(`CyysNo#i{%1wiSQ6sUppD8MKEP_Q=#V(#lLDs% zgq`WP3AjJ;KQu!C$+}@bbS`ujuDrLOJZ?b){~JX4ITYxDxV8~w z7nm!W-^DdA1+y>XY;LZ-Cj`7vwaZ@<(wEafP949jT`sD>0$={{zPN}85v6GyCk1kt2W zrX~e|u6S@rKp41r&??9+3Dg^!WhJm)&POe^`5DaQj)KXn>xHf2qz4MqG0EM8s{M}dIa3< zKbO{kc)q2;6|H9z@UaQ2@GIx7PfL*Q)qQ#9hQ}s6o@Z5vTi_GD!l=wU1TWME9_yA5 zaAs15a!u@TdxHkHr_pnemB$-Ap0jb2k{O0OeH1=dcDO1V1ExLLHJ^ZAjvp={>l-&d zEBAccURPLr8Ws4@kFKe!kFizG;APi%tZ#-~Vk%aVJZo3mTyL{;T^+{sd3e3kRv$4m z7+c$El5CrK=wp=O&=AaZ*u$uYx zN?BXb_qq>VB!6Do=U4&7HKH=&(pIV0wT}2yeCbsit0hrpg{E zCRo4Jd!VKI}QE^M1KN8IztbU}WMp5&jX7kzBnPQrKpi29PP@cY-fh-g~$@POmW_ux^Z+uNk%|XX-jNnneV`0HeOiWBk zWlREA`6c}X2M<7Qz-U_<|FrYn@gJ(B+;QtGHf{zn@n!e|sRx*g-z#Vuv+Uyt`OdG7 z#3{NON=!M=poVR}3l4pckZ(B>f1xgx8jYjc8?aUKi)&S^z*On%TA#q(3TJ}xIU09b zG`h4J)p3D=?T5K>+Fm3ttf~^eUGvjaLQ-rg6XxX~hk41};kb^XG2c@W<0tn|Sbod` zjDJrHmfBJ-I>#ExvFohAXF9Kl6ku$NL|H>2rQBqc4--Uth9%AN3pDOnvF1?34s&w3 zPzXQ0xfS*ka1YCB|7%p4aXTrA#h4Ub!^}wTu&##nH8rvtd1q}geo*dmDxULt>r;w0 zCpv7pSNOj$!&vq!Y%E3{-|Y)UTPN}1Gu}^FHMvu`UZ_3%twhUYkl?f0S19J zx7JPsI4xI<50E4D)2m|H4IA07oloxY;QMK*;V>lK*9h#t1P42^@L+M;!R%SO_KTs) zQtVN^@hQMX4mVy7ggmTce@mK!#PGAmNQ@)ThR{62JPm*BQr zM2d780#knY#aKTCyviSamwF37SI`lr^mucC?xwF7H`K9#z8oYaF2Bl36kCML5Z2KC zSAXB%a`-Ej=N)rX)hEuDwa4hP5|mA!GqUjQVgCtBk}BDr^R&)OaJKt6fthe4y$PK| z%Z>c0zM5qD8KN?I=!yI6Y0b0j;X;?@<&1&hZXA-p(67Q1vH)>Kc_w~l$ouMzp?ZrF zgc?43efkiAdAjUjczVb}`o6U+zwAxGa_0&P>#0wd>{GW4{g>d*2)s*-kI=kP)iZh% z2eNDF<*iZII?s$Gjn`)XB#wNWSNi&C^fAMl5J{*s6pUB*_#dHNmOS9CTh95_e&m8T zi@nNUB}=W;FRA4`+4E}P+=lyCi~{)>{HNCkITn>wmrg{ zV~Z;rmj$smkb~3cBk7X(zjC4KjF}tP6P(3FQhA4>)yPnIyFb%A7;Tb5y!jA6Q+kDd zTY4Q(dx-q7mS3;`eGvtYWLH7APnq8Xk8uCbdUiVvr-X@R%B5X`ZJ(9veh>FaSHb~} zEdliDeu#-rC^H%4+~nkxhPPxZU2&E5_4<_5?#&T8vpK~dWfiXlJjAg@pRRj7plGR0mG;DN_x||d93CM?cruPs@1oz zP|I(@`|iz^nYO9cwTaf`4)0(hV#A&6V1})G+x1ceq$k3#*Whrr()DNseQgAN$T#wL zLw8tkL#6BHBbMU@azWOTT&Oam5Kq*BgR-slt#-p*Pk`L@;QKE!VYrq1VoV7#M32`! zH%|f0qs!U&;@I0OZvu3EC}4o)WQrMvGf@3No;!e|c=cNAi{C^=s3yPXoiNJRMwXKi zNweJxC}*S;IMEXKwIx!6$xxpsvOgEYeK$8Ph$PoR>;8Dxia&+ zVsNAV>oima6aD(puAktn#cNdE{-n16@4;K;v%9bz%ed|xzJawCg72$*0kTxotZKf6 zcXU{ss1Y}pUHSaq1c&?3H!5*;yJjm5Pxo0kHsG06*DB*z+7~y7TS;R1DO6fcj?fe3 z>uAw>w3i`MCEjmT`5NGgyjhgT(Npl?&R`wnYwa5|!0mF@gU^D+VSKGdxEu_64hMhZ z&NjpiN%dm!p*nq|SAImh;TA1L*Av;>R%&EO*O&icAF;dEjXvn;P>bgzJrJ1%mYv$= z&vz4k?(I}FgBs3@E2U|p2n}H}_Z9>;ZbT@CnnS{sHd+5+TwI z!cSDBSx>e<%A+qm_jK1@n?0zPe_>tE8#Pb6AX-sWRR2VTgdz8v=wZr?JomzAY()oOThO7M z?oU^vZF+y0U;X--1CxJz#r`eQ164bJ0=8|<3*FF|;8Sl4Tyc}(CMnsQF;nWCOkD5u z-nqe@_1Lh&-9M|B^R(M*c@d-|XPGKj--bpyxR1nn!h6UKaV3E>m*Bc-*az~sbc)Pd@` zW5Z=|C38$P(K}u8eV_0NEz@)bdU6MaSb70zLM|}|N>l~wDegaQflMZvFN6 z+zI4Tz;9wzLf;b;2-z@2M$dcUNXg(WM&3Yi@wTArWp{j8es-MVE$!VlIrS>PpP6?& zRm~G7xYv7Z- zeX3tm#*-ZkAu@gZEu>o{7{)Fv=#Cg=>h$IAK+G}#CB6x9AQQY-blIP`hRscs>BDemryFgBj};LC@@B=L?EAfd=!*Th z`^rr{v?9KH!d@#*7>^DT`4x|F=J{8#>}^+GKiWS z_VQ@p%Thn6g-mN)!CK$%StV$1mTu%or-l}LU%3=*U_s?n$F3asy~_6+y}=>aM32>Z zCHTYdXP2qKU4rVUcEV0ee-(wL*Rv*TZh5zqOIyge(C0%~8qS+1{?mS|`qkLyVPR1} zBJHqrgGG1`J-XN~ogX28m}l0}H#jnnPcXi^x?+S!_pM7TGP52%y&mCjetpJ9YA{uX zDe=4lqh`;9?1uT|FIm}Gft$OUd{5GyGUoN{?b#kxzqvzZ^>5HN>8p@#6|~6D%y%t; zGOS#bFplKog2Bj3HI#pX`E@UOySqKnRhJxbb)-z2E67%O|9S9oj(NHgr2RQEUi97} zwc9vi)H^jRg@AMb6@3roM9M@PeCN%wrro&>qtMr{DBh>{nkRkYJ2%VU#Ry?XZ+c<> zXB8AD!ZxT=`TJb_nlbX%mpJkbHgG4HZqqcG-CP)&6b$Vcnh~znSvXmaW&X5MHm^j{ zh)iuZpLBBM>&KIJHQkf+rge#r$y0>1yhEEfb|W}naf7GjOAnVlZonC7X~PTQYb7l& z9rAC{LpNS}Msk+*&KOxSgkN;^2qU6&FvMvg}TMOEq<{f zV*ZZ3sAHztPy{Otz7J&MQtu=_*?!bvB^~l`c4M^e5VUlEvMZ734{G6r7bRT|Ug2ZH zHluxx!0r7f@(}O9bUvqA@%#%#8$N$MRd$^b<&(XdLi}piSi0U!rwfz!;>Z5Dme8LJ7Ly&YTsTC2B0f1Fc5wqr+^PyznfLnA08&S5W42I z6rD32`RD(){;x8eqI{dv+8I%S1Cn0ziHpmVS&h;46Ypxwj=<9O=|&$ zv@xTpL}~0w^}W|Xf!zxt%o?IWRN^qe`_^6Xa>;AplErK0UM{%|G~hoWN*C|<+a<{v z{VjWyo*CRv@s%vqf;K{`^=Su!^Yrv^vPRMI=dZmZx~=;3N!D4a_7pSdX>jhwD?@QW zD_ILRb@=jXJW2wMc^?jpV4yh{-&(OFWt>LQ_LIJEdF{XXDnIXMpM2{#zjTgVgd{7& z)AbsPT{Qa6`6K*_VEuQM#$}y@vLzyYZy@oENsLXX)?)^sKo;Lv$w=xoz3n3tTLMEK z6@D+0J8MV`nzhvq_EtD2ULZzmRVm#Q;Wuskb>r?rrP%;VN%|d$ij_=&@d^=LA|L2d zJec_`Mofp^#M${rewL@)3AJZCXn11tC z%;!ynn(|Mrc>o$#f3nz3AbCOnaRE^(M|7_uT7Jt}m)S_d2kk z3lBD(F4+2B-lF6@Y>?1NfAr;b!CY=|d5@v`ual!%MTq2@Ow6YOUE7VD>GtnPCN)<8 zdCAE4j&X6u2d{05NN-EN^|U5f%pLCt!vocg(cpkqzO;LLd;*F$%pvZ4%tHTNfMAv( zh2S~C$N{^X!*U$T{$Gs``@b8Xy`SPUThhK2WG$+Gn@s=MBcXX4A)N1tTj$R9ZC#ra z?$ga79fkMVyW^W36pH_+xGRr`a&7<9j50H5ER(UMnNdQEDeIh)WeiP3Xj9p8RMt{S z8^zehmPt;EDTdKRDJfaAg`?%vm~u)^2T_es>W3`hy`IrIr}y(Y@83Tke>@(~bzj$g zZTI%Qp2sO-;Zbu7l^ubdga0?K;@2^1u(aAj_TaPW__DoPF$K)|CpQ>8d~Y7LcG1kj z*s7&H1zFka-2*J1Uw^tZ_h8Sl>M{5l>*X)z)5o5vH5#0jQ}LQXPm(q@T#wyQV{x9g zlagSP$-lfQnd@0jeZ^Du|F}*m8_9qF8rS0UL>tp7qr0nV52KcD3hO?gp4=|n%_aQ& zZAW&-eS@d5H>}>h$Z4Iq{X5%8^dNEKQWbaPQiK0F{_^Du{wqLXo4SSYzXMd){~e!9 zOE4e)K{BzzQP8IGcWrV{l|#1qyvVgR=_*Ee`QfiiEtip#x+M=EayP#1aek_6Hyg3I zzPPpE?Hb>`on`56J`qq$7Ye(faIimz5 z1CE_ACiK37bXZH)%x$%sBOpUoW%1(EhqWi_3i=ukra#GEJmxs^Uhm>shfvLqNQog) zkReL26E0cu8s4|#@l6_QII+4JGfx_8=r8e&$5|U_xda|h)kxf2E$`rYrpqj>{C-+i z3T!pq-!rH>k)}Y&gjA^$n%k<6b2R(5#e-D!FJsbYE!SsB5{xcNZ`UniVM@PDZN+^p z*h2~ldkZx`Z8MhQix|sZWc5!|Ut?M?kg`_( zgk-dFOy7#o+%_bm#p1_p-mDc|A#IRx@+e*A>$Ffd(1qV+cC0wBY8CmSN^{*7FWPXv zM!KI7(qGOtp)dV)%*_uICy%|U)#R_-Xw!7JEMn%ygM}|;Z}meOen4jJT|dJ8SciAw%~x^-`>zaR&?;WzJv^_juxn_Qkpfg&(b&V^&MnTrwC-`FK*z=oxqQ zZuy{{Dc|SSTu-)c=A|@#sLJn!{i)%JkCTlerq>nM4M-3S#t$J6vw>ukki>&#qb6*59oC^;|0RqQxys zmq0zM6S#SY>`@s_Y~BWP&fdW?_I!i3z?!qki0hzZLsb|0=2b)Q=+Z1iMWc1+L9wss z*w|{`sgvj}S-;C2>iI?U9skhlR6ebfIz0DtlEehe>rD|hCOKZK?%G=#M;%0_Bd`uEWlqYJ1VDAG_!euU3*N*W+^+PqENQ(e@4M|DB2PrGvO z#tD>1slRih$GpIkcSRX{n7M7vV4G-OnrF=KDULL)eX0PubAyJhO%t2Tys5MEnE5=w zd$<5b6kvAq{`5D^JJxDW+;~~$XSq`t9riuVyYVnn?)LwC&k7#I@{=9ezVjQ-J+mEhv}9WaRJZ%94++6hyPg zUzZhbKdo@>dgL?B{VSI-N6fBqEk15FUg)Gze74A80Rg~)AjbYMvU8@2C$7Wp-efo6aL%$28HbKg9^`^sn`K!a$RyysQ zIC8tH#89MGrPXwMpEj%n^d=Aev{7N?ROvPS%!CO+RrkTn=x?Rni@(p>+VpWU{LsZ= zfO%6Dw|7B4%l(|9*+Pcg`p9a@0{Tz#@A=79Xn^@yM|8ply^fI-G#Z9KZU<$ecpc#|;&49YVso&57Zo`i8n^Y$wXM9}IhIc<1ty$ZKmw#tHJTo>oU) z$xDUma`uPA<(zpoN>2B-6jhM7c2>hax8#qrjUkmkmHFrwZZmlAX7(lyEgZ61^Rr4Y zIE>jx%4t7e7VREuX~>~WO{6*8Z*lKTjh-@FpejOO*(qNC1r+yHb;kto{(>Txq2w&h-1XSw42UiWRrSIi@! zp5y+ZX>X3NO?|v4fUn?Gbh?Zv2sfJj#gS8PXuID|3n?8o(FD%1Ua;r*Rs2fW?4dA{md4w7Wu z#U_?N@(zCbF8^LV$JnO6%yZ0;XyyCipZ;jKBmME5?GF~M8!>MGe6V&dREw3Fj)OvS zs!X(yHeO`EE&8p7nZ{RS(84wFlrKLh^=tqrRnD@P5DPZys(WztH7fsZwl3S<(OyqiuSZ{O+2OK$0qy`yU5Pk#k=bJ*JAPmnrw)e# zATClzS6&q0*^we+Llt<&tOCPGe(1<=h@+5ag~+KZ7$dnlR$UT; zhBqnopZjRN!Daq|VEfn3*rJTX38{D=y~q}k!^~lovvPBtWXQ}kh(5F%n!encNpUYmH*g}mEkUFJFt{&Qawe1`8iK{8mNA#%r zAc_CholO~B&-R?i&f>W1g=mjaT!V-y2gKw~7R_8+`QFeXy5nVY_1c;@+b)rDP3KxI3Q%Vc;pdyxe}urw0C_(8HnM6wA)GOxxQk|#!Z{=90kh6&!p#6-;` zfVgwluH*jOR}9Lv$rfnf`JXaGa2d}!Xpfx+G-+2ftJsB+N@JAKwvb!MX?rIbBi2K8 z{$jy~3#)&ghw_FGY>f4u+jQ(-nh3GS?#}riO^E6WbX}kKRgb?rvPSEs%QaxyF020+ z|Mqy+U&NS#4n6*_Bz^~}gwRXaa5I8BN%8A?Zss}=_4Mji$!P3AG*KZ|^wmLn`TUYH zgN|s<>Vv7hxWJ_D(uHAg;Ta_>f8w?AvdGG zQTy)D;w-SrbOnIExE`^`b|WUi6ui>o2UkgNf=Yx4 z`PVvs0qa(Lh;1@h>bwQ3$r&3_6tmxJMFh7Hqy&(U44KtxhYRkAMQ(zW8iPssglbu* zX=dWOn8k=rZVg*6i(^OvbwVe7`l5-T%7sYUi6RE4i|%@WIARDjL2HfML|tH4?*YL} z%nPMFkRqQDX)GTrdSNep#3~cIH(zRl08@Lv4g;h$3}}=1isV2>UB(ElKriLVYEnT^ zg57#CHQS^@qr`{pkpfuAxURQd7#(3?bf5s9BH)P{Qlrn+&>yYa+Ng&#X+!WaBQm^} zOCLJH&D;#%50jcOEk*ei;7Nc)1g3w3bXO8pR;Pb~bxYPj+iv!G#VRo4PB0@p6`jsg zX1GhcnjZ95SK}dDD<75esg`l7w*w_p}br`Rn{04IpASIqlcd8^c1AyN-Yhe@BZzl}C88rFiP;E3|g-Y0Oov~(HfoK|PW zH?{-d9G-}CL8#D!T5+sZQV0s$AiI$m3`MW4!+ zj-xGhO7)CsMI|heZmaLB$FZUaW3Z}-@+q3OGN3mm1?GAATs{RI#Z%UH`~P9IDyWOO z-k0pIiC?2Vi4|^Uz#l$1o`f?H9Yoaf5uFj$#?GG}X~6$-@=w9yLj(h*i82v&CyusKJqBgiq8E|#`#x-xblS$>xwClq06*IDp6G@tn}U`8 ja+DxPf4|21$V^)@KYW%UF7aN2f}ahxPRtt&Z}xuxN-D>R literal 0 HcmV?d00001 diff --git a/roles/download/defaults/main.yml b/roles/download/defaults/main.yml index 0fdb97897..3e3d42d08 100644 --- a/roles/download/defaults/main.yml +++ b/roles/download/defaults/main.yml @@ -42,13 +42,17 @@ flannel_image_tag: "{{ flannel_version }}" calicoctl_image_repo: "calico/ctl" # TODO(apanchenko): v1.0.0-beta can't execute `node run` from Docker container # for details see https://github.com/projectcalico/calico-containers/issues/1291 -calicoctl_image_tag: "v1.0.0-beta-18-gf72bc1d" +calicoctl_image_tag: "v1.0.0-rc3" calico_node_image_repo: "calico/node" calico_node_image_tag: "{{ calico_version }}" calico_cni_image_repo: "calico/cni" calico_cni_image_tag: "{{ calico_cni_version }}" calico_policy_image_repo: "calico/kube-policy-controller" calico_policy_image_tag: latest +# TODO(adidenko): switch to "calico/routereflector" when +# https://github.com/projectcalico/calico-bird/pull/27 is merged +calico_rr_image_repo: "quay.io/l23network/routereflector" +calico_rr_image_tag: "v0.1" exechealthz_version: 1.1 exechealthz_image_repo: "gcr.io/google_containers/exechealthz-amd64" exechealthz_image_tag: "{{ exechealthz_version }}" @@ -142,6 +146,11 @@ downloads: repo: "{{ calico_policy_image_repo }}" tag: "{{ calico_policy_image_tag }}" enabled: "{{ kube_network_plugin == 'canal' }}" + calico_rr: + container: true + repo: "{{ calico_rr_image_repo }}" + tag: "{{ calico_rr_image_tag }}" + enabled: "{{ peer_with_calico_rr }} and kube_network_plugin == 'calico'" pod_infra: container: true repo: "{{ pod_infra_image_repo }}" diff --git a/roles/kubernetes/preinstall/tasks/set_facts.yml b/roles/kubernetes/preinstall/tasks/set_facts.yml index cbe4d9203..f57cd702e 100644 --- a/roles/kubernetes/preinstall/tasks/set_facts.yml +++ b/roles/kubernetes/preinstall/tasks/set_facts.yml @@ -49,6 +49,8 @@ etcd_after_v3: etcd_version | version_compare("v3.0.0", ">=") - set_fact: etcd_container_bin_dir: "{% if etcd_after_v3 %}/usr/local/bin/{% else %}/{% endif %}" +- set_fact: + peer_with_calico_rr: "{{ 'calico-rr' in groups and groups['calico-rr']|length > 0 }}" - include: set_resolv_facts.yml tags: [bootstrap-os, resolvconf, facts] diff --git a/roles/kubernetes/secrets/tasks/gen_certs.yml b/roles/kubernetes/secrets/tasks/gen_certs.yml index ace2a3ba4..f951bb368 100644 --- a/roles/kubernetes/secrets/tasks/gen_certs.yml +++ b/roles/kubernetes/secrets/tasks/gen_certs.yml @@ -51,8 +51,10 @@ - name: Gen_certs | Copy certs on nodes shell: "echo '{{node_cert_data.stdout|quote}}' | base64 -d | tar xz -C {{ kube_cert_dir }}" changed_when: false - when: inventory_hostname in groups['kube-node'] and sync_certs|default(false) and - inventory_hostname != groups['kube-master'][0] + when: (inventory_hostname in groups['kube-node'] or + (peer_with_calico_rr and inventory_hostname in groups['calico-rr'])) and + sync_certs|default(false) and + inventory_hostname != groups['kube-master'][0] - name: Gen_certs | check certificate permissions file: diff --git a/roles/network_plugin/calico/defaults/main.yml b/roles/network_plugin/calico/defaults/main.yml index f0f91d39e..391e7c53e 100644 --- a/roles/network_plugin/calico/defaults/main.yml +++ b/roles/network_plugin/calico/defaults/main.yml @@ -12,6 +12,9 @@ overwrite_hyperkube_cni: true calico_cert_dir: /etc/calico/certs etcd_cert_dir: /etc/ssl/etcd/ssl +# Global as_num (/calico/bgp/v1/global/as_num) +global_as_num: "64512" + # You can set MTU value here. If left undefined or empty, it will # not be specified in calico CNI config, so Calico will use built-in # defaults. The value should be a number, not a string. diff --git a/roles/network_plugin/calico/rr/defaults/main.yml b/roles/network_plugin/calico/rr/defaults/main.yml new file mode 100644 index 000000000..116b9cc8c --- /dev/null +++ b/roles/network_plugin/calico/rr/defaults/main.yml @@ -0,0 +1,7 @@ +--- +# Global as_num (/calico/bgp/v1/global/as_num) +# should be the same as in calico role +global_as_num: "64512" + +calico_cert_dir: /etc/calico/certs +etcd_cert_dir: /etc/ssl/etcd/ssl diff --git a/roles/network_plugin/calico/rr/handlers/main.yml b/roles/network_plugin/calico/rr/handlers/main.yml new file mode 100644 index 000000000..edfb1ad71 --- /dev/null +++ b/roles/network_plugin/calico/rr/handlers/main.yml @@ -0,0 +1,15 @@ +--- +- name: restart calico-rr + command: /bin/true + notify: + - Calico-rr | reload systemd + - Calico-rr | reload calico-rr + +- name : Calico-rr | reload systemd + shell: systemctl daemon-reload + when: ansible_service_mgr == "systemd" + +- name: Calico-rr | reload calico-rr + service: + name: calico-rr + state: restarted diff --git a/roles/network_plugin/calico/rr/meta/main.yml b/roles/network_plugin/calico/rr/meta/main.yml new file mode 100644 index 000000000..38fc506cc --- /dev/null +++ b/roles/network_plugin/calico/rr/meta/main.yml @@ -0,0 +1,6 @@ +dependencies: + - role: kubernetes/secrets + - role: docker + when: ansible_os_family != "CoreOS" + - role: download + file: "{{ downloads.calico_rr }}" diff --git a/roles/network_plugin/calico/rr/tasks/main.yml b/roles/network_plugin/calico/rr/tasks/main.yml new file mode 100644 index 000000000..c43851f84 --- /dev/null +++ b/roles/network_plugin/calico/rr/tasks/main.yml @@ -0,0 +1,63 @@ +--- +# Required from inventory: +# calico_rr_ip - which specific IP to use for RR, defaults to +# "ip" from inventory or "ansible_default_ipv4.address" + +- name: Calico-rr | Set IP fact + set_fact: + rr_ip: "{{ calico_rr_ip | default(ip) | default(ansible_default_ipv4.address) }}" + +- name: Calico | Create calico certs directory + file: + dest: "{{ calico_cert_dir }}" + state: directory + mode: 0750 + owner: root + group: root + +- name: Calico | Link etcd certificates for calico-node + file: + src: "{{ kube_cert_dir }}/{{ item.s }}" + dest: "{{ calico_cert_dir }}/{{ item.d }}" + state: hard + force: yes + with_items: + - {s: "ca.pem", d: "ca_cert.crt"} + - {s: "node.pem", d: "cert.crt"} + - {s: "node-key.pem", d: "key.pem"} + +- name: Calico-rr | Create dir for logs + file: + path: /var/log/calico-rr + state: directory + mode: 0755 + owner: root + group: root + +- name: Calico-rr | Write calico-rr.env for systemd init file + template: src=calico-rr.env.j2 dest=/etc/calico/calico-rr.env + when: ansible_service_mgr == "systemd" + notify: restart calico-rr + +- name: Calico-rr | Write calico-rr systemd init file + template: src=calico-rr.service.j2 dest=/etc/systemd/system/calico-rr.service + when: ansible_service_mgr == "systemd" + notify: restart calico-rr + +- name: Calico-rr | Configure route reflector + command: |- + {{ bin_dir }}/etcdctl --peers={{ etcd_access_addresses }} \ + set /calico/bgp/v1/rr_v4/{{ rr_ip }} \ + '{ + "ip": "{{ rr_ip }}", + "cluster_id": "{{ cluster_id }}" + }' + delegate_to: "{{groups['etcd'][0]}}" + +- meta: flush_handlers + +- name: Calico-rr | Enable calico-rr + service: + name: calico-rr + state: started + enabled: yes diff --git a/roles/network_plugin/calico/rr/templates/calico-rr.env.j2 b/roles/network_plugin/calico/rr/templates/calico-rr.env.j2 new file mode 100644 index 000000000..201caecfe --- /dev/null +++ b/roles/network_plugin/calico/rr/templates/calico-rr.env.j2 @@ -0,0 +1,6 @@ +ETCD_ENDPOINTS="{{ etcd_access_endpoint }}" +ETCD_CA_CERT_FILE="{{ calico_cert_dir }}/ca_cert.crt" +ETCD_CERT_FILE="{{ calico_cert_dir }}/cert.crt" +ETCD_KEY_FILE="{{ calico_cert_dir }}/key.pem" +IP="{{ rr_ip }}" +IP6="" diff --git a/roles/network_plugin/calico/rr/templates/calico-rr.service.j2 b/roles/network_plugin/calico/rr/templates/calico-rr.service.j2 new file mode 100644 index 000000000..1a4b3e977 --- /dev/null +++ b/roles/network_plugin/calico/rr/templates/calico-rr.service.j2 @@ -0,0 +1,27 @@ +[Unit] +Description=calico-rr +After=docker.service +Requires=docker.service + +[Service] +EnvironmentFile=/etc/calico/calico-rr.env +ExecStartPre=-/usr/bin/docker rm -f calico-rr +ExecStart=/usr/bin/docker run --net=host --privileged \ + --name=calico-rr \ + -e IP=${IP} \ + -e IP6=${IP6} \ + -e ETCD_ENDPOINTS=${ETCD_ENDPOINTS} \ + -e ETCD_CA_CERT_FILE=${ETCD_CA_CERT_FILE} \ + -e ETCD_CERT_FILE=${ETCD_CERT_FILE} \ + -e ETCD_KEY_FILE=${ETCD_KEY_FILE} \ + -v /var/log/calico-rr:/var/log/calico \ + -v {{ calico_cert_dir }}:{{ calico_cert_dir }}:ro \ + {{ calico_rr_image_repo }}:{{ calico_rr_image_tag }} + +Restart=always +RestartSec=10s + +ExecStop=-/usr/bin/docker stop calico-rr + +[Install] +WantedBy=multi-user.target diff --git a/roles/network_plugin/calico/tasks/main.yml b/roles/network_plugin/calico/tasks/main.yml index 0480354e8..19d74759c 100644 --- a/roles/network_plugin/calico/tasks/main.yml +++ b/roles/network_plugin/calico/tasks/main.yml @@ -152,6 +152,16 @@ run_once: true tags: facts +- name: Calico | Set global as_num + command: "{{ bin_dir}}/calicoctl config set asNumber {{ global_as_num }}" + run_once: true + when: not legacy_calicoctl + +- name: Calico (old) | Set global as_num + command: "{{ bin_dir}}/calicoctl bgp default-node-as {{ global_as_num }}" + run_once: true + when: legacy_calicoctl + - name: Calico | Write /etc/network-environment template: src=network-environment.j2 dest=/etc/network-environment when: ansible_service_mgr in ["sysvinit","upstart"] @@ -191,8 +201,9 @@ - name: Calico | Disable node mesh shell: "{{ bin_dir }}/calicoctl config set nodeToNodeMesh off" - when: (not legacy_calicoctl and - peer_with_router|default(false) and inventory_hostname in groups['kube-node']) + when: ((peer_with_router|default(false) or peer_with_calico_rr|default(false)) + and inventory_hostname in groups['kube-node'] + and not legacy_calicoctl) run_once: true - name: Calico | Configure peering with router(s) @@ -208,10 +219,27 @@ when: (not legacy_calicoctl and peer_with_router|default(false) and inventory_hostname in groups['kube-node']) +- name: Calico | Configure peering with route reflectors + shell: > + echo '{ + "kind": "bgpPeer", + "spec": {"asNumber": "{{ local_as | default(global_as_num)}}"}, + "apiVersion": "v1", + "metadata": {"node": "{{ inventory_hostname }}", + "scope": "node", + "peerIP": "{{ hostvars[item]["calico_rr_ip"]|default(hostvars[item]["ip"]) }}"} + }' + | {{ bin_dir }}/calicoctl create --skip-exists -f - + with_items: "{{ groups['calico-rr'] | default([]) }}" + when: (not legacy_calicoctl and + peer_with_calico_rr|default(false) and inventory_hostname in groups['kube-node'] + and hostvars[item]['cluster_id'] == cluster_id) + - name: Calico (old) | Disable node mesh shell: "{{ bin_dir }}/calicoctl bgp node-mesh off" - when: (legacy_calicoctl and - peer_with_router|default(false) and inventory_hostname in groups['kube-node']) + when: ((peer_with_router|default(false) or peer_with_calico_rr|default(false)) + and inventory_hostname in groups['kube-node'] + and legacy_calicoctl) run_once: true - name: Calico (old) | Configure peering with router(s) @@ -219,3 +247,10 @@ with_items: "{{ peers|default([]) }}" when: (legacy_calicoctl and peer_with_router|default(false) and inventory_hostname in groups['kube-node']) + +- name: Calico (old) | Configure peering with route reflectors + shell: "{{ bin_dir }}/calicoctl node bgp peer add {{ hostvars[item]['calico_rr_ip']|default(hostvars[item]['ip']) }} as {{ local_as | default(global_as_num) }}" + with_items: "{{ groups['calico-rr'] | default([]) }}" + when: (legacy_calicoctl and + peer_with_calico_rr|default(false) and inventory_hostname in groups['kube-node'] + and hostvars[item]['cluster_id'] == cluster_id)