From f6356641ae306eef3505d0c7907a18206fcc012b Mon Sep 17 00:00:00 2001 From: Soumya Koduri Date: Thu, 1 May 2025 02:06:21 +0530 Subject: [PATCH] rgw/cloud-restore: Support restoration of objects transitioned to Glacier/Tape endpoint Restoration of objects from certain cloud services (like Glacier/Tape) could take significant amount of time (even days). Hence store the state of such restore requests and periodically process them. Brief summary of changes * Refactored existing restore code to consolidate and move all restore processing into rgw_restore* file/class * RGWRestore class is defined to manage the restoration of objects. * Lastly, for SAL_RADOS, FIFO is used to store and read restore entries. Currently, this PR handles storing state of restore requests sent to cloud-glacier tier-type which need async processing. The changes are tested with AWS Glacier Flexible Retrieval with tier_type Expedited and Standard. Reviewed-by: Matt Benjamin Reviewed-by: Adam Emerson Reviewed-by: Jiffin Tony Thottan Reviewed-by: Daniel Gryniewicz Signed-off-by: Soumya Koduri (cherry picked from commit ef96bb0d6137bacf45b9ee2f99ad5bcd8b3b6add) --- src/common/options/rgw.yaml.in | 57 +++ src/common/subsys.h | 1 + src/rgw/.rgw_op.cc.swn | Bin 0 -> 348160 bytes src/rgw/CMakeLists.txt | 1 + src/rgw/driver/daos/rgw_sal_daos.cc | 12 +- src/rgw/driver/daos/rgw_sal_daos.h | 9 +- src/rgw/driver/motr/rgw_sal_motr.cc | 5 + src/rgw/driver/motr/rgw_sal_motr.h | 3 + src/rgw/driver/posix/rgw_sal_posix.cc | 8 +- src/rgw/driver/posix/rgw_sal_posix.h | 8 +- src/rgw/driver/rados/rgw_lc_tier.cc | 37 +- src/rgw/driver/rados/rgw_lc_tier.h | 3 +- src/rgw/driver/rados/rgw_rados.cc | 47 ++- src/rgw/driver/rados/rgw_rados.h | 23 +- src/rgw/driver/rados/rgw_sal_rados.cc | 291 ++++++++++++-- src/rgw/driver/rados/rgw_sal_rados.h | 69 +++- src/rgw/driver/rados/rgw_zone.h | 12 +- src/rgw/radosgw-admin/radosgw-admin.cc | 1 + src/rgw/rgw_appmain.cc | 5 + src/rgw/rgw_lc.h | 2 + src/rgw/rgw_object_expirer.cc | 2 +- src/rgw/rgw_op.cc | 98 +++-- src/rgw/rgw_realm_reloader.cc | 1 + src/rgw/rgw_rest_s3.cc | 127 +++--- src/rgw/rgw_restore.cc | 520 +++++++++++++++++++++++++ src/rgw/rgw_restore.h | 140 +++++++ src/rgw/rgw_sal.cc | 3 + src/rgw/rgw_sal.h | 67 +++- src/rgw/rgw_sal_dbstore.cc | 6 + src/rgw/rgw_sal_dbstore.h | 3 + src/rgw/rgw_sal_filter.cc | 67 +++- src/rgw/rgw_sal_filter.h | 56 ++- src/rgw/rgw_sal_fwd.h | 1 + src/rgw/rgw_sal_store.h | 22 +- src/rgw/rgw_zone.cc | 3 + src/test/rgw/rgw_cr_test.cc | 1 + 36 files changed, 1475 insertions(+), 236 deletions(-) create mode 100644 src/rgw/.rgw_op.cc.swn create mode 100644 src/rgw/rgw_restore.cc create mode 100644 src/rgw/rgw_restore.h diff --git a/src/common/options/rgw.yaml.in b/src/common/options/rgw.yaml.in index ade58eb67de..701d09067d3 100644 --- a/src/common/options/rgw.yaml.in +++ b/src/common/options/rgw.yaml.in @@ -230,6 +230,7 @@ options: see_also: - rgw_enable_gc_threads - rgw_enable_lc_threads + - rgw_enable_restore_threads with_legacy: true - name: rgw_enable_gc_threads type: bool @@ -246,6 +247,7 @@ options: see_also: - rgw_enable_quota_threads - rgw_enable_lc_threads + - rgw_enable_restore_threads with_legacy: true - name: rgw_enable_lc_threads type: bool @@ -263,6 +265,24 @@ options: see_also: - rgw_enable_gc_threads - rgw_enable_quota_threads + - rgw_enable_restore_threads + with_legacy: true +- name: rgw_enable_restore_threads + type: bool + level: advanced + desc: Enables the objects' restore maintenance thread. + long_desc: The objects restore maintenance thread is responsible for all the objects + restoration related maintenance work. The thread itself can be disabled, but in order + for the restore from the cloud to work correctly, at least one RGW in each zone needs + to have this thread running. Having the thread enabled on multiple RGW processes within + the same zone can spread some of the maintenance work between them. + default: true + services: + - rgw + see_also: + - rgw_enable_gc_threads + - rgw_enable_quota_threads + - rgw_enable_lc_threads with_legacy: true - name: rgw_data type: str @@ -475,6 +495,35 @@ options: services: - rgw with_legacy: true +- name: rgw_restore_max_objs + type: int + level: advanced + desc: Number of shards for restore processing + long_desc: Number of RADOS objects to use for storing restore entries which are in progress. This affects concurrency of restore maintenance, as shards can be processed in parallel. + default: 32 + services: + - rgw + with_legacy: true +- name: rgw_restore_lock_max_time + type: int + level: dev + default: 90 + services: + - rgw + see_also: + with_legacy: true +- name: rgw_restore_processor_period + type: int + level: advanced + desc: Restore cycle run time + long_desc: The amount of time between the start of consecutive runs of the restore + processing threads. If the thread runs takes more than this period, it will + not wait before running again. + fmt_desc: The cycle time for restore state processing. + default: 15_min + services: + - rgw + with_legacy: true - name: rgw_mp_lock_max_time type: int level: advanced @@ -1260,6 +1309,14 @@ options: services: - rgw with_legacy: true +- name: rgw_nfs_run_restore_threads + type: bool + level: advanced + desc: run objects' restore threads in librgw (default off) + default: false + services: + - rgw + with_legacy: true - name: rgw_nfs_run_sync_thread type: bool level: advanced diff --git a/src/common/subsys.h b/src/common/subsys.h index 419115c35ff..d756124f1a1 100644 --- a/src/common/subsys.h +++ b/src/common/subsys.h @@ -66,6 +66,7 @@ SUBSYS(rgw_access, 1, 5) SUBSYS(rgw_dbstore, 1, 5) SUBSYS(rgw_flight, 1, 5) SUBSYS(rgw_lifecycle, 1, 5) +SUBSYS(rgw_restore, 1, 5) SUBSYS(rgw_notification, 1, 5) SUBSYS(javaclient, 1, 5) SUBSYS(asok, 1, 5) diff --git a/src/rgw/.rgw_op.cc.swn b/src/rgw/.rgw_op.cc.swn new file mode 100644 index 0000000000000000000000000000000000000000..11ce8804662e90d8259c3fcf7069f380075f108d GIT binary patch literal 348160 zcmeFa34B~vdH1gX4Ft+kmI49VYXuz1vE(HSLAC|5tT-aJWn?)EA)St-ku>p0qi9B6 zViJ}RT6ha(D}~axK-pUMKTs$wdx4g{K-r;XX;}jlUMOWN|L^a4&bjx_osldjY0KN! z^YPE4xy#wlbDr%yXL9qUqkEHw^l$FsbGNRpi(kGlcl#|L>H6!tbagG&>rGug(Wh3Q zD%a-fjb^|7HC>-CR~I_GbiESXbfms?Xwy_>ac)y%X=>Bb>=B_hR8~OQ%}k zR14e+EwJ32>AUdGUFUAyVpILB?sdsM?tbuANcdC|r&{1t3!G|!Q!Q|+1x~fVsTMfZ z0;gKwR15sy(gMxJ16_}yu(t^XescIet?=C)e%~0r|5xGuS>bn77~Cx+5Yu^Jxc}SX z`w4~n4+__#4&a{)_t%H}SB3B0p};Zyv%~e7@crV#{hn}rPx!v$ZT*k>zQ5alb}fbPXBX~Y9InUteTQH3 z3-SHYaK941mkReE9|4W7YVY+w4@jrcL>-%9kcg6nuY~lW$ z{qe3ieoOajeLqaMuDCwEuW&z1r>?j@J@Edm?}zEqbu8p_L*afH?p==!_kUKnABJ<< zzK?!DYx-fhc3l(R|3=|{7>-@f2=^cP8?En$;g;6_ro#O&oVpgm`-%9ibjA7e z?!x`h-Cc2gJ}+r~KXhl;lSBG{Shyd$tLu-#{dMbF-w)l9uD^d>xF4FcE6&dgyIbE6 z&DFIW(tmB?erS%geh)gU_5D!YH2*&++)qNfj}G5=e_-qTp}1-Jo?5tn+wk5{_&)tX zt?!4+r@l_Lz^N8E)dHtl;FqKY%F|t4;;lw*!+$UVaeaF;48tFSXM-C+9b5^9!6wiT z?gwrMZVNsR1M(N(cR(F1fP+AC#F?NAdDGre}Q?h5A=Xv1OEpm=yTwm;LpLI zfv1DxU=};@aa9l`ft6#o@`6#ReS_rX)a9M}bh!A`IaB;e;To<9R$ z0UrP_2QLDD3Z4cIgS}u2I0O7EjO;hT$G````@u`Wg0L7>-%&zxxY)s@1!!zsEh`n8#HYLraV+l=b zc_z)dN^*3*mTb3r57K;%MUtu{ll5ts_eQhatWMF$q+U6?xRhA)9XPi%esJH!=-!di z_{f2=gX6;^rAvqQ(4gH9Jl;5WV7$_(FE341Cd#wj+g3>t4BBR$PIlS|aF(SC=)PnKs*)65H%WSS-~HI^qE#~O)}4S$!YP3tp^ZEOD7l06oFl2Bz~ zx;m3S+gu`d8$vz({r$b%hQM^4wY)%n$;9~3@W}qL(R~x8T_YEb?i=ZFWB>Tr&XLlt zEA|cT9UU(18Qpiu&~BcqE=<*yA#&ZyfaZ?wYjfRc*16PINF>xb_h2kWbw=g8Y;@w{ z(w>QleY>-Ir5S6i&(x~3bIsDsk{V-Sx?j0>c)+h`Ne+#Vk6l)}XwT^Gi_zF*Z%oza z7ojAL`qHN1^iRE2BAU=RR zq(5>`<&q-#!_}!uV^iZWg!)*cRG+-MwU#YUT(ewnmRGrdEn~S*9~F=~H|tAwCo7?D zgehCA&(6}vtqJGuH;ye#wcivWnlCSuXDiczAhkbJTP_q=sdBM2U2c|HGiy!Oxwc-N zI@DN($W<3R6q$-FwY9cVZIo*zn}Y4>ajX5}LcLj?IaaD1Cbd%YSf{-Fk@i<1()3xQ zR9QIO;bvuNrZiPwX2dQv+HV^bX}`=;P1lz~%JmM7(k#!iuC~9qRIhbtra080W9_f_ zfGt()?YElc4q0pbbk18hAWfa}s4s~RXwPAa8|_!;mut;xX}Z#(qwH4u8w}d^i$0*+ zuP=+AY8z+esamOVq&icWyBbY)t3&Cz-ky%yr~N9bp z*!lXxrp98e+96NB+n%q_sOjo#$CBFJ)|Wyyi}}GUexu`4&T5nvo1M#Q3)t0-;z$fx z;IesP$5iL5^M!#O-hg9VC=A_jrz}cE+mw$il$vu(^=7j;H{yd!^Tjz9?hK1qx0WX^ z35}`xVx=^@3@6q4KzLPayKZE?xP4~6xe4ZLzRZom+;w9#HZ3=+^y0Bi)rHbxjdi>< zQ=Wpp(a4L7?N3Lpr1RZ!ZMMEtZO+YaawcjME8TQ;s@$Xvm0B)yf1q@@S~-%>rNQ+| zdA{Y*cr(TAz(&&OaBrk9^>Q4_D_0l4fJag-PScj;%>! zzP!|ImM4>$=8mbwMLp5Cp)Z+7su~zR=bUW`_xg@hr<-#F7b0Z!O_m$gslNKmOrz2q z*t!kobQ*Hr$5V|yd#S1SyG;H^?v%VL`9JD!{|1@tDO3rYJpQNaH<7PwZN$s_`=WW;31a%CA4}dl-;p2a!ifi-@y2#Wwhy?1o!6tghjjpMd z-Vd?52`4U>D1xV*kYG*uK&wBUhA0 zcZE`{@tyr+dq#(^h$WYb58-`aU`RQxM`y}cYq3#!mh5tmy_D9Bx-K08y9%Yf+Rxg_wf5d-H8i6ovG^f{&ArL??MZh|wYn^6xLHRj z0)xC1RQ{D|{jsB44~N3>7hLKz>P5E$#LEdu9=CjxwR{$qYc)i!jY;|km2&MYf52tO z4$ImEnR3PQhH7o#-9;%j8Z;Jzy>GC7WC7v1I=vA+zl}t?d{?DbX;yZc#&=}FbQ)+H z1_mleD^trfa^(9<$Ep>y*vG=C^!7|PtLFI@PWncMhKENE#1>h1xUwYWOz1eI(D`bk zK@PF^%zhBmzm0SEFE@)tF>}T7E>!DMH?LIOkWDWA6$2?y6gi2P@W|T%{h6z}$Ri$0U}1E_PV{hntJeY+GUnd~mdyHpWRtcJvgvRkH=k{&A~kz!D&EFE88)>q$P1JbxK zZ6x!$MEd6Netx$22<%5a-atVpGs*-e9Vs)J84XMgNmgGO6K1Nj%Vb3tCr8SS1kD>p z3$)nX-k`ZQ-ZwU~4=aEQTx{g6VNOn!7tZE=gu%qAQj$?6qaiYg#CDesU*%JuQ0mZb zb~Qgym7mo=*|EdvP(O1xn#EGfV0a`vp(3nCA#>Eu8r{0;K{Yrr3a=YZ$~Fv;J)4WtuT4;~1< zi~KKNfKPyb0Iviu2iJjXz#_N;+zI>wJ-|=FPk?-$yO3H%PY5j+80 z0i;tH1Y5xt&;>q*u0VbQj|KZdFSr*t1N;iO9rzb?25$ks2TI^d@JR4ra364Qa1%O& zZ-IXR(k(m@YzG&D1pEj3gO7k4z+=GmpaRO^zTi{nTiyv?4_*ac39bdR;2dxda0l=` zI_AUR1>g#B2k<%U`R@a72Tuc+gMRQ&*z;coo)4Z2o(*cC3Vsv330wZN!4tvtU?=!0 zeg0waHtI4avm}s9HtL9rnY7k*cLfHplu*azOgxWN80r ze=@oNF^2ka70uu=J%H_XsWM-0RxsCA8%oYB<8zp?(Ct^Is!GZDDM(HaW_^QGa}_L5W<3fksQqorJMk%Gb)u$ygYZIV zzTJ4il!EKcvwgJ9Cdf9l6N9HEkd$W$f`Nr!_WEfmP+f2u?o>1})`1%7vr;`;7SQqW z(!|(UY0uF3?vZ>&)lw%YXEKO6T5bU)I;FYYxb)_r+86_N-=Hn3eS_kn=gUV+;&$~& zaBBz-8SNdC99N|m>Y=`&_VX~|wuX53(dX8Ejjkl&A2q_1qtu;MEtcHdbz9elP4;kX znE0&(60JsJ#ljfmSNDLgQI-B9b7fdoJ&|J!^iFX-+~O?iWmvuON*D+%g$8M0JwobO z@v1ZQo>i&4OG&kH8s@`MIf^b{i*yUCVdJ7YyMT!?smw1nk0mid6tc~;eKnt|)t9IH zn$-$w=`PD)MP3GKUY~*Y6Kk_R<7o0js5lus!IuANo;W?)8mhgi&hw@kE-#4n&~#3$ z#EF+U(1QNtBDgY5^dqR+lh{V< z_`O7)HpjdCKD_bR*D?Nt2;C=a9cLerE5`zR`<$9xJrkY0+{1E1~97r_D$qc={q@xyHM%=jIyi(I%2=gCV zhdW*$CWo1M#F=Ex7&>>)$nK#%rHjY*?1C)IVQRj97(t4xYn54q@VR;oNw(G-O5J*c z!M7mAMTh6TlDTeaeUl{hgf(tUUf!W=DERou1ROa6f3e(j9&}O*UTd+_>n){Duj=WJ zh0Z^bFLQcbMW!y^6f!1L09Zof3VYI@8=W~P51vg)ti&2US)Z>?^`xVtFsRxeN&7aJ z&}?M^#e=9A4XWj_ynt;_5{2t1g3zQU=Ph3<`vx;M0~HT0V3kz3Xe(t*vnRY$s!m&r zDaB-6WLUV`Z{*lXLRwOq3s=On0ZlEGY>&!^bUtC!mQrXEd2mi1Bof_>c_6DZ_$2zE zC}KsG6P27P1_s8Akr+m6)T~SoLq(nc&Yq7eT2#BVG;p{Wli!g{FIB0c^Cgyr44Mq6 z9x7vV1p6c_uPo2otsJH-+NZ*VC(U(`r&8l2rf~RB;cYTQa6wZ2GRm*cqSWiox86_=)Hi&UcnI&O7Ug!?g zGSY+w&Ph$9;`AkH$+mZDzIF6;kr8W4zhjf8nNLlU6J=G!Lu=F*8Kbdqc3+iFr1J?~ zf>Lx*Y*>yp%-x{9W%DZ(!-R$bYfExLjinl*A{L zAP6iPQ$adq&X{Ow1-kNDp8qto~g`9JQjd_5Ph27TbZ z;LhL-a0l=KWcYR9&fqlgbL8}&fe(Ve051d229EzJpBuMez4P zKL4)yTLHH0CanR;J3g=unv3|S^Zn!)8Lcf z6F@%u&j-%{PX~LzCE)(x8_4SoupQh6sBiyG{hNIx#N_moV%ZpvkT5ZDk^XkjEz7eJ zPaGB+o1?iwDe=+nPTT5^QKu1So%56}oTYiCXNidn#r4nEv~(fwjVpEX(41qlX|{by z9h#~o1D$VO>iP+uh?HYpwif!^tV@#1#BO-CeWeW~T$L(Qp;?#~GhnF5m;kD{-AdBc zYy5;n&>UUN+weT5j+LI2)BMrC^8B@ZPzKq|ph<{U*kbtYshDC!U7}?#=;=}9LyHM@m>n-6-Bf{Vmmr_V8ELLCkl(wP`8I2&{+&ToJwKmMLd{s6kp$3*Q+5^=fz1UK%th^mwNgn;^m{QA2))rQR z)FMWM`^;}84r(+Tr?zjRac=q;{gMn=IyZ}E@uoWJBBE7<(3pdh$(Z|>T?F@f2pJoZH3lVSOc z)`{0CRchtMhDmYi)%Mg*l-Q&>0JxU&W~HD#UV72mITqVnabTdOVzb!5TcicVrDnYj zOD|0}I_-L`Hd#i4g9EHv>F6T(L3H0PKBC7#lES{%METv0p zk-^;TxzQDDX4VXPPl#2E%F}4Bu0_dJ4=_aXVynb8_O7`-h*@ckxiOhn|3sgRbWJQS z$<}r>L<8|EYIxNcEyeW>5KCGX^Q^p@T?Guy6UvxARBW2_3yRv|vHe$MZJoGowGk|q zN!YGo>fB{?uGk->R@*%?q1|)MwYHbm+|Xtc;hi{!vG0?t{j?|TuTCP{#-zu@xedM9bL%ZLdu2U|+0HhKjFysJ0_tcKCyDg6-gqB%yqIS`!~4FZYhOet0~wjvYNg|)swwakr)ChH9l;51m@NjCz9zvT7E%lbt3}zUMA_3)tT5y#aU|WWR zKbDas4PO@}@|A~#2mM0tm5ea=$r@o7>Q(*l>47<>@lH&o=)X08e4f$|aZ2VIlB-^C zdPzppQkW-VfYeo4A8AgvcXP5KZi3aCfR28l$-dY~Qt`sT+e6b-rbp?-aDY2fWgDy( zBIC4dDeBi8mi*s`Tqt|5*FbysTmx6;J`u~3$9f17*Uk{!N z%HT?{6-YnuQ)K;*faiff09S(tgDv28;3LTP?*s1ve*vBXeh*B5aiIMGlK;Pmtp88o z6+n9e>fjNe4{QYAL)L#Ecpms8Fb#ek+zs3n{1r0))j<0IZU;V$tp9%SKJaGn67WLs z0`M&G4DfVt4XA^CU=O$i+#j3?P6J;;FYrb1Y9L$w9-#gFzY6XI{sSF>bOwI{o(!G@ zt^>!xwcuiK5zzkq4}h0~mw@Mh5pW@RAUF%$8%U3EPavDW;sjm?CczG{9c%)hM-QOA z{eKPq3cMP;61)QZK6nbKfvZ6kjDfviBiI0X!Ck?f!A*>fZvyERJ_z0q{v5mvyc8S< z(O)1_F`VG9Ore{Kwzrm4=RR$Fh6e`P#JaT#hLq_Ry$?@)i{4M$q^$-XEx~oE@Xr2o zCBgJ&iKMDKM(?R(Q?<%)-v7*KY)*pc5-}8`e*#%`6-o6!L(s`qgj9Qyjfl0&IHwx= zL=t6(h7R-Qm#LLC75|3~=RjT7*2-evVS9LIZ1UVlX|cCPr-48zCC;o_1&$LDdMBE9{d#Xn z$l-8RX6s_*}UG|?YVmW5WW-3iwP28Qm!2-6YoN7TqEh3Tt-pg(M=l5Q&SjR zB`S-J3kEIBvM7a;@sXijbeuV)T+YWHnL!g#KO(IGs-LTIrk0jICO283kbQm1z=*Zo z+?vSAWZ9wyz(2wrN+gJka4Tu2BL9_&J1kRHl%O3dD&;N>ihV|Nl{!_>606`VJgq_R zk9F4MZFQN!`>Lqx$fVk}lxWp*bk2Rt+H==Jy3X}wX3l3qBhjs9!srT(vKHOoNw38u zo^k_JB1CXX7u}W_e)DOSR4ua$+Gb=*0G~>M@3AXtbEgshKWaTux#ZD!qc;eYQl9M@ zuUwN20%>FFOZeUoW0!XmhpVKhj2!%{xGWNks^<_&8Tn50qbBC1M(B~%>tPCLo;vwjj6hsgPg}BmOjqtH z7H({PatJk3qwE$78^Db+Ls4-RuyH98M9dW0~0#D>Rl!tB(VY}Gj4CFis) z7vMcnTqX-cuT#t}OFd1%((LWv^#EmBom2NAb#wXkodpw8P_DXJU{&s1^8Z$&21y_R0fb9MQ;Iqi?p8+oh zPX&($*MsZ8<=`yv73B9%gB!sJ7y_HXS)d!71|-`*7d!zR1x+A(|M@`s{ND|p0*-=9 z!8mvXkgZ>_`#y-QuDJ43;9JP$Zvnppj)NmWvG|6-oxwYh#}~mPfi_5dA9?#G@OAJt z@FFk|&IbwjP4IQ(?!N#p0Dlag2ObSB1`h;x0k;AFflU2#p!j>T<6jBR20uiO{yg|7 zcoUF}{bX=8m;e`nhXL93w}B1d*TBEhcj~|IwA%PP3g8LOUW?n^#)@~Uc@k@1)1VVg zktr0b1;>`)c9=bF4y^AkCKaf+7&7!#zRdnU1-*y#sOsq{? z*cI^4I zQsEBDUXFBGGz*05hqYHiUUAfa2J5ng`SMrfTF}5vaQ*sFR(hxX9oR$kN{O)PpIHgBNYd^+IiPcHw|mhG0w$IXiee8TuGVYyGU3=iZ6;@U~9OTeOSU3%quJ6=yq`ouQ$k8ImC$B{q`;<%S5| zwY|@>=VU{>wHpd8a}L{D%=4&$oQP~Eac}{#5fQW2Zv&(7f_|Rk?!Ru?KpO1Po}L9b zw=Ugf6t(TeNTo}OgG&{O6cwy~la;Bm%zhF-RiRK4lA2_r>aqHAtfGq5z#z_k!zG@k ze0qg!tMkkAVJAqAsNjjRXLRo<`{6Gi9vRs+vdb3?l}=+WGICcB|NA)SkooPbr+*>?yl_V*96BfmQ*%{ zYn{qDdWM%F`4HyP?(w0069-Crhb}MeJGeJa>iA4*saGcPqWQ$-=e$&{^n_>Hu+3UD zz_v8(WP3hp)Fj!md2?4HtpNL&EM#1s1>mLK@@^Yk-&!HctqN+WX&JVfljWj&l6hkA z%Pk~p4~&E-qXDxczho#4F1EB#e(>i8$o`RWYoJb}b=Y?yVVgxuqlQh!WXnt^LrQz& z*8F5$TQTfbPcQrSeg4{lS$Hw*4hYLoR z9(L~4>x)Qm)L_#X^kawqG%X@pa;TTHN;1)YT8cLr8me!XBX3+Cy_w7SKv( zEu;k~byQ5XA}GMPAx|ZZ%(+UL5Fy^I#lhi=YA)&ATv=fxq;Afu6ad{@OWoP*$m(V4 zqarPc>pgogrW4xALr=86Z6rvS{3a^785~dbKa{82mc?{y;nZAtX@lKkKSr)MR;XB9 zdfNs`cVgQHB9UK_v@;>J`za&s>XH?9a58vsRTWX{P0nF7=+ipxn4cDTfS}bz&DkuV zK*!S-Wne{Wo+g%#DBF&=cWBQ=W8-^Ac9lpjBj}!Q`ag|J8GMu$=yp2~5-9fsYl*5M zU!J%mDE_q?e1&y~ki+;k*-jSbi=MLtsBf2wqQMkPzXOFblzI0^Mhe2MlqAPTwG~c# zNy_sL(~pyuQ$eVZ5g~SdhIrN`BQZ2^dcmihQPMzuY>?nTb3#ttg_d4MA#Im(%JO;U zx3m75dp1!!lVzWIx#D?gDBq*Z{;#vgPD75A{r^yq8b5@b|4N`e|Lef7gD)fNzZ={L z9tWnteZjrJJ;2?;H<0(g2A&HJfHS~XkncYQJ`O$x-7@cZB>7y-M$50LR+1jfN; zAm4t;{U1QSe+76UxE$;PKSQ2>FL*kTU;h-i3S0p$2M+^^+5a2hOmJWD3*>&q`}-33 zB6v5L2GR#S96T7@gzWzYAbr4tz-gchyciw86Tn7r8h9)6{xX;V7lDU??O+?YGq?#k z|BK-B;B!EJ{!ao^;6C6-$oul?{~GuzcsqC%kUroA;89>VkRAS>;5Oj(^wDd;tHBhw z7qEWg`(}Sy=0iH@HBZ*?StE;{P9bqHD+(21jax0uPFGxQS0e2ac9ZXn0Ef_9X_u7m@zRUBv?;()8oPo^M=6OYy_%m$DAUd-hwrhaBK)Q?^5HT6NPt z;beaL{Lr#m%}Q`vw(qp7V=6(6I)p~^;tkW5@lMZBKD0U9*D(xEGjZds|6Y7}x z%KX&)VoysO*Z@!?sWPR>>H@RPefl`7VPyZsC5yQ>R-#-Xy#u3Msv|E!~11cl{z%nqFJz^P?S*{ubw|Q@ExtE!GPA~-lRz@E^I&Kpf4wttSbea!Gs8}L3sdie1f?5o z3Q?$QJ!J8VY% zp6-h$Cia(x$Mz{IgD8S*f^?J>A#QcSHj*C6GZ<-SBrY1|QpPtMmD-FqENb`#DZV6| zR6T+VPxv_kI(mN4RhA@Ie zYdl8Ul5(J-=17vwTy5rWkRsE1hQ0or9-xfSK!^Uq%&)?G*PD8z#d!3T}!( zua8B(k(wX-M-r$yOH>V2gvv=PstH-xJFn2C1Lx-0COJ043>htQUSNOgc}XWoi4KO@ z$frWyP*}p@$cU9FVh2}Now(w4MkX#E+XaD~aM~DkknK}pl&I-PYjWsu$*aLJHMW{H zmKPW6OU)c3!YWRr^r;Iv|gy_vcl;_y)mbj?DNiC!9(u^zP8?iDV5s1O>Ry#+#`~5qR64%`4+;c zekfHHBZi`?X5US9etwxq^F#_W4>Q#yrX`7ccxd17$exj1Ue7{axoWL3hF!wP>}Z*L zKQg&?#l<)~IfZ97T7tM%>*j5@M=Gc6FiVaM!P(+>{bsyUb{F&y!M_4kwnNJ>-hZawoOdQ1<_I z2#@DWhD82L!E=Dl0lWnK1o{4};IrV9;DbPZ|L+6;8$1u}2RiHTCglES zf!_sB05^c0;Qru4$ofwL%b*G>;7Xvleewr*Am|3~LiT?dcszI<7y#>m`~lVh#qLuK zfV+dQp%YN-zW0JRfR}-nf~SKW;Qm1S0DcwR5u5=&fllD#paG;aI2UXM-$D=YDe!Ud zG4Ng>U4dc+JO?}*+yEXA=D?*u`h!vMALt4c)9<-Jz69q3?FHBjZbEPHHt-yvGyHag z5wHu~8QclH0^Nb)`W*sS1H}>O0@5eQzu;>?`h?GbPlK0(KLb~RJA)siCy)-|4}jtV zJPjx|fbg-mF z!~xn6f|#CBlt{L^NpfF386F!y5X8P17*)F~j{$M5wpKY|EQW{;^F!zC5Lt!=LjpW)BDg=m^f5LY6(j*|4fc(W=AG=@|HYTsocX_Hw}iLw+Wa=HZn?nfQRK?=C~H{E zSzfcdbSmJBx-Gx|uguT&oSP&K!HK;mvKG1Js@d4< zQ5O)>4&*bPT6n2Uz<|lC#p@K<)nBryZev{~Ue&MCmzog4p%|0mq&=-|m(}Xw z9XfBuImtGzbS25}j1&#-A_!exnaz7~0) zRQ*1?Hihz_(LB4e!lF2>ZXJ)jThQ$3Q5xwVwAX*>nHt(qdjhq^f9`-w2Z#?iY)&mk zww!d(ar8-E4;)F*W34>&7*)k7)2`28dD{;jv?MsN=J9y0wGfc64>8vF@(GI%1m5gY?Y zz+tc($S+_$Q2f6;fZK!5A?v>%=nTLGFbRGO+!ve<{v5gfnLx1sZvYG6OmI8!@5uH) z0IvqugF4XufDteR9t_S0=YfAimj3~G7B~hbzyWX@@Ev6NZ-Q@tFM!X1&jR@kybe4a z{62UpxB^@TegoVe+z)&KIsbD&`~QC%Yy$(}8_4;e03QW^58e&l1>Oms2c8R#g2O<* z2l6=>1UrCY1N<8J6>uByCUgNaU>y7edH*}$o09v%Ye55)!0o}O>3i+#|2y!<;E%v_ zK?!JFJPO>bPr4OslnAkVb&f645j(8bEL0R`TwYb@O3&avFDl$RU(Qf)nqUfbs(?uj zW2U=xMb5OvBD(^~MN2$7Jfg5n>GT!Z!}UJUbWbuZsu#>-8dI!JBsXP+b1x}*r5n5; z!sZCLnc^W+xp%}#+dIAe!8M9#=Zn(loAai8w!?)L)Z!~tc|{kEz=P40wCRdX*{RCb z8z=tnzd>+G5_&0emnUE@i=s=$SYm|@X0!d}V+sOv;dIO_F(*IQhgRK7t z42%v9coG96#)@xUctlG$Zh97P3iqIA^T!;2DkNH@F6)+-YZW(srAxu#a;+-A5**CX zPKROFvp!5zT{I0AH%ja|4M+Wn_!sTlrr11SX72H^J*DxHM;ye9rgSke14hOV_%_%M zMfPZgvd#q!yed*$A8xr4UTZ@^xGyf<)`D4aQb&vS;sgeGuSghi4~RHO3lcdlY^3Ug z&DM6VzR;R|>Wca_!LQ_{-fA4Ma;b6kjCW1^7%19NgEJmC~6(3`6Szi=9VxvabK4`T;3a|woX)5d^b330mnYst85$w zijnu|@e!F43VRRL_Y}J=u)|w)ypk^VgdX44rOiWVAX@sM&~)zEqZ3xtMZB#B6UM;EOJh$G6*DdHwTvf{CPEu~WOG=Zlq ziwQpU?gO+wBo%*UNv7^{8)D)z-gzf?GCeqD70J~Hqaz}LJS;OFc4cv(&H5O6lXjC9 zf-jFKrAF-O&7B^FjPK@$wTcHSn|eA*Br#;GuBU55j^|i|n84E=tSJ43LuplXx<~T= zeGu;MAQ=z&|8NlWUWUxS40=Hi&>;a)=0A?iKLq+fFX#dH2WJB92lx@P{tto9_WKz4 zD0l(*6R-m$;5Wgq0O7fk>Gxyg{vU!L0PQ1?PrysSBIpM?&rdM|zk$sEHt+{v3Fw@Fe?iuN z9e4`(J@8Pl0Vw9*&ynNb4iq~;XZKwS4uX4wdx5tg&nwp7AlL!!1ti=51CV|HQm_+5 z`F#t2Zvr~Y?+)N}@Gr>jUjbhRIv?PFgB!u4z=OftNU`(#E$<3PP+3ghzRYiqJ?G2w7MR)hQ$Y`L)?!CMe56aYOwPj*4!KxX zTC~ASmWl$8iWqVXf~Bk|OAQ@=TCUC3H>&KoFnhT@k#qY=nW6^YSv7}T!fJB3);s>x z)_x`Hak$Gh(FE;2&5F!ZH2Rj1RxD}nbmc=q);9P&=SGOZvWv3pU#iShkM4ID%cW!k zr%-R~3MbW67dVTKb$5Y~R$3x7t^+H*y09Iql9shY5XpRGzygzzdKDzAeymMGP%C8Y zF^l6A90{}WeA*$NtyximTM#8oH4hfkbmJV!trFb-Kf{np{ZqYVhtaS$ z0a4SM*-_AYM(c&egQJ(`qe`^I35ZjbzCqtwuutcq=?au|V8F)8-h+E4M)wbmPq4q= zXH7iWj2;ayqvjZjk3+jr?+c6#_SvmvoP5yNA9_m3SIofy$l2D3&Ob!>4Ny;Xn!V&&m2%|2Ki zWLbuQ&$H)&6+^KS>DF9fJeI&@rLvGjJ{#&E4t`A&rS>c#p@1`Om2{urkgQ%(6=ORv zpBxjtMrRa<8|o=%t2+=I3*42A;Pzz{*xQp(wd+B_y#5u-FL z+-3tXsuhkmR3qu8(L6i%QNOYWDMX%Zt8H>aJKX~rhq@6{mssp9+mks}Yz3j(b{~;# zngs%(dWN%wj3VnGzpPqx9QzH#VP&^{myWt>%}1*7q@~&BC5_v*J5z;S%HE7UcG;(; zH&@~9iSeP~5xHX-{fX{aDcuUGWCJ6RDU@zumDdT%;a%%Kbwc6sAa@s=(P-jo;f=zk z>0UL@N%gh*IS8Z*g39Ze!?;3`$gewB-$HRiV`OyO9$n%DCRkV}@&(GA5_Y4J_INzx zlI%14!U`)%JNI{T>`o!so25QZR{dC4onm!z2Q)=`^Gm_%NdDi2K)5Km5c&VcAROxa zziDu9@Iz$!9{}0^{|>wnJPG_RH~=059tiFNP6r=AuK#nOSpSa)*MsYT;{I2`K5%c) z1wM*Auk-)j3|4lV^-faLz)1iu0P9-00l;Q8Qx0onhDz!uO2et|5ny#SK+ zp9-!6iWx8rv=3lASO1f3bzm6m1P=mVLB@X@h<^T8@%P1GCl~_%j*R~r zpz{IOf%}3_A>)4%ybHV&D9-;RD1!^Z89=cE-wa+4UI_jO$gclb@JO%^Yz6lPrvusZ zbxz=?z$d{A!SliOU=s9!`-9tn(}3a$=zPHUfhT}PFb#ei$lkA*0_(w<;Mc*uz|ZOX zZ-RdWp9P-*e+nK4v^U@ypz%=$xA@aek8{gl=U5_I7uG?Pkw(I%Z27jX5c{TWcXs9z z)RNKYSe%D3X=7YFDUbDF%e$Sn%UdC>TydXx2c2=uGOYXUtcI4K#xczH^Z7GD!h!?~ zvpzd9r{#1VFJe~J0zX3qQdXmVh06}-j>qw^j&Z^OFU3P;Vr9HF&ZuocteFYVU<7Qj14V15tiOF&PdfA zNLclf-B%#W)*Ec-({^)Ke7{#J%@BIKRMK+=0vX}XntN?R%19j98(Ax;5G@VH__%WYRXx2)-(X8B$5oFs z37&H4DYOTt7tPp)dn+M5#wIw$m?Bz$i`l-JCpH?VzA4tWnNiv`(HQQha7&BeyRrcKlJ zsYZYK2(bOL{UpC>$o@QV?k3-^w@C-~%r2uC-DD+aZ1f?Lz`yq?V!*6+@Al6%=WAzK zl%;qt*3viElciLj%ky5T!TuyUQ1R2_xF!41@;nOf@RO)y8Qg>FGdqTH_-%M|q68 zo6U1{SF}spR;qOS0YJ zbiM3ZJ&{$-#BiKpEKJ3t79Q2qFq2XM!w{b->J)*F!?pL0$uv&Oh|{~;Q=E)$r`?@E_S$3ZD=`*wPf}3wc(+?D7hh;46r~AEs<8*M~)_j*;VxU zfh1l;00+D!a&rnRh!IT%&6mFk`RAo)X9}!LoPG%9UP6zwP*3d{l!JhwHn7Ejcv?2G z;-2?Vf(fSezyR?@@PfJW%4L-GhbpyWSG8<_oULQMF=d()l5@_n9}sj-8TCsz;2Y7K zrh?)00h=b-Oi0&2nWUEvaq68X>8XSf*PTn4v9vDGjPA?Ov5)TCZPOw4Sk4$`GXy3h zlz|}gG*`y~xRnveJ+(>tcn9t<sjG=yV8hCXLOS}hi#F11;HDi$>XgMO zBPwGX_94cqRaNEmhTuWtRc(hx+9cVC@H(aJZ#+4RRaC0ac@`CM5_0Au#06}}LnCG8 z32(=SADl8>C$5HQ4?YPsf}kXGP;m>LQ8xQ$i1JaLCd!k~$Zbh7diFdG!e>LGNrjPc zb{4nX5Oaf!WkuvyU25lGTlFOhHxV7iYWE1LJI!z77h-Z+x}6%ZD5JLavZ~PtY-$;2(dLbifP1^MU;Rw}G?4_mSNd|Njrb z0=NMD8n`X^Ir6&f`2P+*3*HW10Ggz5JQ3^xrvu6B z+Nb|IFbl@OL%{cuv)={Y0CX1L%fX+6$AU7r0BDc?Pmrw z`Pv|ae~f02$du<;Pop9PuZZvFcpx!KVf}7hECNwf{_8nqmWa~f=uW0HepqFx{e9Im zU9?tBO5#hD|DQ;52xrdn5|saW~>Z-V#rPV>H=?O6%}O`eZnT)YqGqpdc;p zVoFM1$sLJnq{^weOuC_5F?g~Yx zQIWDq!QXAm1s-1^uwJdAvrEqL{ArqJM_VM-pvo}2QHx|4*QVU+)FJUu3fjSzD5qs% zAD&aWz?z7aQ;WqFE?YyyGZz=0$YUJmEkDWY0X8V| zF^ZQOi{*u$V9#<}KxwKe?loa$&~vD8ctqwZ`7#i;uEW3$R)qA;tMjj9srIxTl0g$$ zS^5TVzP&fWeaQ~9DKI^F8B>=PjtHTB#k zX1Nb^?I$^0tuHrf$D{=@VRqUXs^DtUUcAt+S%;RVEDRWqT~mizv)3$Ku^-WZRB>p( zRtHpTy6g2Y$ETNJ!ku5uo@%4-*{$$eH|`V&E>?@Z(QhKSE-#Bw;`}C(w&M=$xDm8E z#K=dRwmDE?CoX-1nwT@9EhSd3?eI$%oL6cN_H;u$c;1R?vGtXxJ8?U+dDL%(bi*3x z8+t9;hd9ZUDYJD=3KpWVC1_9xjHOb^1uTwn%@=i%#8{^_x$((qfQXfhuBB?F(W_N1 zzQ&o+6dmIcM}624;hjzGR_WGv2nt~Y%0fd(67gfnR@Ui`nbwMP5CR3Gmm=L&^To?y z8J1|YU5%(|YhlkyhbqSgsf6vi=4!*ji17!yu*mcJT^tIDM$b$nSek8%R%6nJ)H~Hq zDJn%f*ui>rfi|Q+t1WoU(5u+Q-_)%@zV@QbE>A*IuFp=@{M%zD1$iN~6 zxH+p(Ah^CSksMcDqBHq39q$LtF=AlAc^SofGwlQ7h@U(kJ&_BNXqZAaxoJ^b3;ozg zsG#OLZk?&F3JvR#IV6~M>1vtsXe&CTt}u06N~c& zy=8EJT7y&;= zrvEpf{rG43CIq=0el>}{m;N*FbsZ5o2%Vlzp4!1F5E-F6?4R>v=|O2UJOi>n?bR3q`u5~ zVshb?W@tf5Cf3Ffl+5*J5DH5s7o#N5A+77t{k!aAP^^yOwQs>TETO_OOdF39LuHA&m=}tWYflDu;iC8}Ve# zC)Cq+3#u$cL|r94BJvsqGmRkBrrN-JbVd=9?aGn*!r9OZ*V)Ain5iuk7|l6cVl?~v zfxWEn*2^vYWxg}0(KIE15-Ibr5QH%RPf72PdX53>pQ=^LOD%enqJ-k1rZeUP>F7X{ z<|;2uo716oN(9T;-u(yBS!*kWNmR27G+2z33%fhZCfHfyFYqu80qki`2|#Hed=Wvr z7B@bL*Vcs~%=_35X$=hU2mY{7-|3!s5E6@4+i8q@Ja0EM#L}CGoihVbv%rkO-dta( zqIr$$KF9nZxXy4Z4bLK0Ig77lL(y1Jrh-bw5TcP7(X^dPSxzY5$~5P^>rN?v%01k`dWNfrt@Vg2LM>jdRvCl{3$wbx2e@WMQF9 z%jVwQnR^Q3E;2T`nUXdsZ=0sa=1gEBw1qPj6`*A;{Js=Fk&Woox}BOLKJR*=TVW{0 z^FTAbVr27@&YqyI~YJ2!g|b5EU33 zS1;JRZ6iO6UZnvga>dzQOGHseQDMx#@_`(PqV@2^%EIx!+>;V=?F@Q4RPopKlX^H^ z)aq!(56P-Id_h*vrX_T_-ql@s)i=$+W9H*x@{Ppegirn%X_O@cYGbblARE5qz`?!D zOa-&ooC?NRPVx;i-O|!-Ai2|LcbyEl&okr}kv`b&A{j`D`eRMdy*N;(Nm zk}jU>p(l!;)bP;n8;w%RTu>}W%Ys`aR-#~}-fjLI>A5O$4?$V1i6{G0O0x~5AWVy8=;<8y#V*7naC1vL~O(B<-E=~Bw{G5g{>EhJvIFrL3g(fq)|nXK zER;lUah_yUsOb?hK^ePrY_W;H-_EqrQ5J5-WCv+gw)qWyxIvxMHB>HZZ!y<7#}gF0 zl+U&rQ>yc9GgW<3B%c>dAAny$wVoWlEYSpQ?-bIAJd1n&S(0oMY>`@0jkBlr$7zwG^g2(+jF3E&2Bckl~jex3RE zzrl0CbAa~nKM*pZFbD1jv|s-pKm(M4eD<|p|HsJo z^4Gr!ya+6Thk`qTe?+c-F1Qj5gWCeh`5y!y0M7)M0maz+5e@ZtuovtB3HT^-eH~l{ zwDa#20cMvRYXan z7gtaAP)HnuRCWz-gN7MWD$^_#PVkvdOKfnH^H}BR6z-`y)y9g;5jLi6h^9J-akeL_ zjs0WCdf*6qdc6gm2bGhSMQSZJm&>}*UCb-KnFwDmp;B?#_m$q7HCkS2v7+Mc%`WN4a$lJb;GXQBU!)pVm{v6|5aX$6nm%s{^O z1LAGew^pK*Cuz;QaF$xAV0YhxYPD%9(4+H_=*)%LP6}h=V;T#bWwg+o=~>^J@+zmG zu^MmH8igUKKGW0s8cyh|9Q~#@#gW0{marhqRgUIfU+v5|Rw4PSr)_RUml-Y`8kgO* zJ39WY@Wu)1!`ALC7ufM`X{eh*BC908dsHft1qP9u()fcu$PH65bZsM5ea4f;0g@Yg zT28ZN*3(l`41Th0w(1W53k;QDz1r3VFN_kQYJ(P$LZ4Y!ZpivRDQZ^8HgndHFLM6E3M_- z_4=yl3>-8ZPVTHx@ZMPSv3*FDx6Yw`I_*!n+h7UxbZP@_H_yxMAHv&;USL~=PaIk#U z3kej>vTJb(%e0DxvS#UWqt(SEXvZ_!Z>*qA9^*$NV+d=9GC2rr;=pwy2C#@q>Em7I zpW4ZZfmKWUD>}PtxKPvCx)pXwX{nQTymQ(i=0sN{4m{Yb*PVBztc1s_O`^NW^tc4` zLMcFO91p=4)cyDzaDB;lpcH#AIJJcC3c4JTj25+r^CIxdimQO1PJL;uI$?DsvoNnH zqPgME1J`5PTIc;s^4f5U2Tf@mmznpU&0JbS3>N9ywtn?oOL++=NC>kf(N*j8mB#sM zQ^vawfou*xvKY%Z+G|x*>J`li8s_u5b~2n^X!DHEL&3kWyoj{5h|@50M1-ng5tcen zmB8(30>#N(g0>ZhTC2)DtF~tonHS@7V{uw2gYt^h0i8pLb}71yV3h@kTdA$uSGv}*pB0GS zRB2Z8gv(0O*7ULLu9RGbNOh?~+)q`snT1Dpip|Hmtmf!W4N5U1nYA&6OjRtp*YvWf z5+YNWH%vu#Hq)Wc@IIG9uFwT!WN-l4o3u#oQPU>b#Ir5%oU zso>dS6?;c9b7X1MGKju1Og*Mtwj^74oBiV>m+EZOONsP4I;PDlY#DBuf$Q^Qg~=D? z|1M-w`Trja^8b5~`Tqu74K4&XA?JS!ybQ?ye;;@_7zI1PHZTD04RjvB50Lx6555Q9 z4&Dl4OaR3I*al7qzXINY9^eh&^`HVC2JQ#`75V?W;Jx5^K=J+hKri?Tx_~c(_kpK? zDtG|+4tjvkg13U_f@gx?0XG1h@%Jz=2zG$mg44jCp(8jPbOpV9e+Iq4d%(NFUxGJ+ zzW~n!itWD$7Qj5%4zzC|0e1nn0jGi2qeplhkZ$2}uoGzCz!$-rz^j1b0M3KEfuEvF z_#yZn_$Tmj@G(x?9nz5=wL z{{`S-;6b2E;G^-zAWDOUxseP;J4X6+%bXf`wLyWw*rewy}G!)k!Oc6UR7I|vzX`IAK0;v@uuD01mu9#_9 z$P``F7R_{fl*MjlBBi@#+`!~*w~~>IyG{oqHB3v;H{!7eX!$l;*t)!-am0&5YFCeX zgnVN05zTfZ)AjP~oP3aU{w-m}#ZtBU=!ytp%OX^w&LPj9S}*=9lAEwonvTh=MNP}X z*d!IxfA)JRdM0byEt=jm8f4FhP=VobC#rZ)1hQrW#or|_B_WfS`o4c^vVyNbrc2UI zGYLq*Ai%X$MV5-)z*Jz@k+`P|7O%)oupV@_J$C(Pbc@GLx)nPl1Y+vc-nVXh<_^XU z<4E&ja9}_UtytRDPjpwO7U!T_8w9MGVSIYIePv9?dz^9NZ#z4Ore2^-GRw%4&DORd zlQZp(v5TIf25hV6W~!kGE}5M@qg!L(F(C3*OA)$IaxrDxP3xiZLl_jRSnlhY9#W(! zB}%tmc>hAO81`}Bu5gf5i)u6OXY>&dLz{-6R8?XiN#`3%Cpzz;CCh{6Y`kCcLoaz+ zJLOgilFTm?DYZZ05@4O1TTt>Z{5QsWY^nQ6tSlM3*s2px$4wmnV@Wk1 zvRk2ID{j-kKSi?bfBh~E25v?dZ0i)Y&^HQ-liJ+neRA`+cm;h@84oe^2~kuPRkPTu zm_KFRmDe5;z)YQTt8Y7&6cY-OJ|+po92dql?jKWQkh$cD6N;AkN44c-h3z9TJz6!6 zbd8L7*%$TyWQy&{f(-} zEIr3;=ZxlRT1a2G)W`j7acx*Jy8Z6xwC^|yy4my4sn`Dwge4{wxlJ! z_12(bx~ncic`}w?h>}%5)=eQmU~6{X;F0sot!6HybX?6=4~yI?>-uf`If4I(?EiV7GXfPi z;KkrYKxYP2fb;|p0N+IJ|9i>);4gvB26z&<0XzmMX5bERckmT-0UrT>1Ktka26RS1 z16%;Mg3VwP_&K_PpMrOQI_L+AAMkzf3Gh+y5%6K~2S8^99tDTMmEbaPDcA}&gH1qZ z27Vt%pD+vLYj7s`FuH>4z&h{%aDQ+&a2IfA@J;jpUk85+qCP=q1dh#>L&Wc7;>m^JLOXV16FbIpCC$`FjvFIyel=BRvrP zlzHadCtJ+|d0ct@ic1mgMajKQ)3sm9#5!rF^=uEUk8roR$eZikvsfi2x|X?(AK&1P zM0JZ)6eVj`Dt1Mol&$N(vy-#El6DY`_RssfocDrjDks^X@SnG{1$skMD+tSyj+Ho0 zR{j}Ca^QSm9} zGMg;&naH%ej5=egP&O+zBSD*QWCdbm2h%+T?yLhTv*W;ONd(O7mg6Hl3ylO5PDM&d@ebPUao zsh5pWwztH-^?Q20_bmDm*B9l!qu><2HCe5*S{e%IQ$NJ`@juq`INcwf=P$kBJ?#G{ zA)#~5(H)J&N)5lEaYn4%#(MT&(9*UdcJ<=z69Db43Rxp+W^+`=q?g(rxx&7OYvZ&Vz(Y4J|K$zTr(f*QD4SJXRum($LWIQN-d*D z0>Sid(>!y9fmFB|t60o0eJl)!ph$4Ng17nPu|>QWjo+eUoW#YAp0X$Glt_%o<-(~VwjiZ#s@QZE*;n9~Zy^PE#@X|n|#*t&UhGIoh; zCR<2G`kZY~I50APDX0CG4(=PebZB(X(9S(0_WJpo&yDszw4J-E4TNtwI4qX&WoWR6 zfK4M9+nJh2Q3f)=nlRZYsyRHUKi6ds!=E=hwKC*#yGQf6M_H-%}W8ag2pN4OVrF zNtEcbUS1>hg}{MSU?kV0Nhz3hvd5<@w(VU(17#En6^rWQhP(*V(UYS&HAXGo1CZ zUh|&q!W>w7;Ih$+CNe^t)=w|xI=CxW*ET{D*I4OxceWW1<8eGI(2+H-sGU<^bzO}e zmZ?cJ?8tFoAgDq*BofSCOa7k^Lf?Cl<(~+& z2jF6`3k-t|Kyv=Qz}J!EKLnaUX8~>mI{!~`0p11v6g(3wfro+3pa&=(;E$2j-v!+Yk-^^% zUJqUaUJb4QJHeg6H|aCQ0sJgb|9u){pGY0@lp!Tow%F|*+H=v^_}-CSB_gvsv0)Cw zp!O4XvVrd#le&9vI9yj=a#i-jBPi&2^TZctqfs`{Ty%i4z>GQVFjhH#Su~?D1aCP!owPHvb(e4G55t(!7l=i$RX5kTePRi+YV+<_g!F@#WX_Ym(t$NAg zj1R79X&V=v@QP6%Y~CgwzJpG>MKK<%%+nVCf@7`}U&>BaTW}{}amp4qn!Sm^eUkA4 z6^RifD6@vQkrT!WquJI;7j_kY!3=p4k54u-#LxmRbo7K9g3ut!g`*e_!n+~suQY^# zHWj6gO74M}dC(?F+SIWNJWR2w60a2OwehmJK$}h84${YX_~v$pzdd8SOJn<;GRC(c zGWaO;2i82BY&2Zb)~;L-QgWIi{17Y?9UF5g?#BK_ED6Z z%Tg)vstX66Zxj`RwBGiH3j!xna}EsbvavI^I2fvz&ndR&VlJ^@Y0I^YW|+wsT5hbA zvVP(WGhdyGQS;Ky%`ZeNQpcPc*ZbIXAGwi``v0awpoMVo!=fTc>*?!sVxLt)7f|kW zRO4o~G!>MC=|$*^A`g}nlRJ&aD3xqSd*;4kumc)krH{={^Q2GXHH>?t9q*fb4T@=; z=-s`xqJ;1dMfH4EAv!&DDA59G3MpVw_M}5DYVV2M69r=F?|t1gLeutoZbEbp(yMAQ za!xY*gRgaQ#f#rjJjj}AO=HU{>Pjw#jGeBYju#tk9UdrzBGA(14c3~DwT0BhT!>S7^{E-OdW%BjIy z+=tp&8+$%CM|?4E?KGcb>Ow23-AorRt(ajz+HR<}Dp7&HK^>8eRfAVi7qcB#T3VCc zPoT~XV}*t+6sd2EZfOv$Gy2lu;7Brd%4kY8p0l(2#d2qJ4_&;qXbs@;%k1~V&0HB|4-V_705|08mDeOR>mxBhZZCG+z0%GjK3v$AAAA47AQX8J;1L3#RiZq|0m$9;9tONzyWYSa3=UVvj0oL0(d02 z0?1~64!9pU6KFrc+rVqVYrs6v`2be{?FrZn`hm^~_#{vafsccyfSbgL}PG2|In>K$1v%K3Nu%O@~6E_#ENW&$mwXY7g(gj_zm(9y>1K(Gwl`XlZ zqF{-!q0#S6-|k>TbaT8H>Pr^Y+?rqEZf$RLna~WXx1td#Xt$l*7c5{H&@R(iHv}d{ z^)_3pPnK)?(>!;Gkjb_du2(qHD#;p_A>q4p*{6tKME!_|w)CQ$qPK6G#6W zKOc4`?9d4XX3_NvC6(5uiAIubWSz80(^~g_A5fm2Po2<&DHGNmV)akEStHz7>sOo| zk^c(KO#jJ&Wy2=T)|2|Iu-k<6z22)&DAp^VT4xT%?rA_va4vOC%kg@lV=6|d8^w*Lhat@TNq_r9X&oEaSrV<+6d*(=TXk?^VvS}| zsa9&!;XN-5$D|xt=(*CBaO-*KDz>)OaU;W|j#7DGz+RD6U1zU^1d`(v-rRRG>1Dln zWDWSuJl4V+&Jsw~A3}&SAN1)+)y-sw~9is(ZqDE)o~c z-O7fCaEml-apC9ocyF+0i?tk-r#%Y0Nl=u`PNN`gYY4`1aJInGuC9!!=NSyhFj`gd zd5oHxy0SRBa<&xj)uKq74l_3uT{MJM_7Mk1$47QKi;`EYdv634atteE8su=eX%I$J zBJK*{Oh*&ABU*8$EP6d>1D|^0*jDLy?1{FPraBsli0c@CyseAoi=i&mV2ibj--QqZ z7NRDXlD39;tSG%KqQ0)$=Ij~M&Uu944QLhaG?J8(u?nq)&Pnjfhw}Ss)*|MrN-bMG zx|ABGI8%hjvg-ZT?D1=`hcGj#`@_sljp#X}`I=3EE#V;{dQvs`;%+_*I`e?t@)l_v ze^m`j{M~HK7K;~kNZe1@BN3jo4)duaI+U+Z{sp!m>4<`2Lx<>;8JP;nGV6AYTr_lW z&qPVl#rGZDt4FLvo^QtKTCU-usr!nv(y4^|cG%<$c_>9IIN?OuF-hSISyG5Cn>Vkd zD=190n_-omPR+93FWqS!J2q9$gGToM2O;y{h|DPY{~0;?|E0+JF9GuTKL`{HP_Y2c z1d{*X3Z4%n`zt1Z&II@ga{X(;^T6YQVgWY6F0cih3GNB*2JQ;}13CY@KrsTp4E_!r z24ye-P6yHj$S2^#;LYH*;7Q;Ha1B@lIz!-WunznJng5$W{{9~XF9A9$P;ml&4?Gsk zfV03|fX)v5C*=S4gTDZ;1UCYmD{wu>_t2=E22a zH`oETfdL?W!%xsF{206gybVb2@L2F@a2#9!y1{RN2LS0Go&mI9;7&mP3jYV_yu$Z` zHv#P%cpZ2tcrlRw!sCI?E-V4XK71578+@B_^eymWpfU9XaKaB1jl(Mhyv;2RB}nk9_lRy8%W=lv39P$gd%;~B)i1lO4HKq6-4$fB~r9b zZwYY`R5eMC3W+dS0Gq-0i#3+}hMnrOU=gCq`PeFr8CxFEVSotkcKlIn&MEN7CpRt%Ijs zQi=mN=lq{7ST>l;OH1WrIt^`OvUO0!ZA}s;pOCf+e<+Cv2gLla^g+o{6?oOaFopFD zrY!GW8Q6F#%WCK=DZ?}@sZ(>y3y0*XKaV6KN!Nx!PSA_}$xt}71s&LKPF{cIf z#Fqwp7Ai-AztmC~5vk}eucl0@%=8^6bizF;x)Q6IlAboI(lV0b<7*m6M&z72IXg<5 zc*l9mU@0*TnABdGnlPK;rfZ6EsU{(UK}QN1*DE2wll~OZNe>RmcyGI1hzc4d1HasuLKw{@$wSBOwStvX z+f73dQ2vQam14TX*@y4faKu8Y!MgEFA*5P@x8V&U0OgieIYs z@m5-4$ePvGg;tG88&!qsYw7uAuA>ct6Lm4PHCt-YvjviC^%eEaPfkW@`?jo`Go+k* zVW6hX8nn@NCPX1m33crwEHiJ|2n*vYO3oVVP84jc(-J;S{eM(oby&!3kgm7}sRc5l zlZRY$Ew7DaQ}X1sSuy?iy+TeM*E^r*C;C4<=_LIF6}svCGAe9JAJGb_^y*7TI3@rM zq$%1Jcz3?cUX~@V#s>J2#md5AbICY z+CGkmbQ?oZx?9mSYx3Qjl05F(I}EvNC@m#X3Xu+y6=xuzO$~#^x>Ngf`6tz<*CM%= z!^+r>!vTOc+%;I%MGF&)yTz*r6%ID_IEGc3LfV!@?7Nd2BNXpuIQO+#Z@oa7<)hV* z>1`9U3I<;^gxT}K;3fHA%-m0d{r~@G?>yk+x~lvyU?JpBSy*6MAizX`BP*7iI0;3u z9b{Ry6>LjLaw=gmmPYc#YRpKn6(?aKKAi*yfdFBFg{6ef()$8SXW{?- zopbLiQ&bX%w&;^zEX}-k-+i~7d-^#q2x}ig#=jao8=MX92JQ;}8yR0Q{WD-1+y|%z zpltrn2UQ?BUwQrhfZYCD5bgZm<@fJ^tH4vhWndrJ2!4m$el?I>FQ5Mn;P=Sop9QZ5 zlHbn*d%=T%>I2>jNQQ3*Uq&wfB2Z1h7l9Xodx3j^FCd4j7T|+{>i_KqyTH$pv)>0K zf1d?p*FPN`1%8MuEnB{70X`Qz2V4Pmf>Xh*!L7hok)b~h-VNRkt_05mj{pN;2Urij zgKT{@_#k*4cqBL#Nbc?cw*hV7cgWCJf%k*Qg2w>m^*avy9Z)=g>Ia?#G-tob*!aWL zt@IysRwhD))uH{7mR@zD(9@%Xy_o+>Ws#Qs$u*ti_p&OlwyhJLR}Bsvqi9&p$(4am z){{*EZxkn$Y>k3o-mQCwdx6uk9sY_sM9Q;9oI}(;lkR2Ss-;K z^=B;Nu_iLFyHc0btkvEP1c*Ulo(a73ZpNdiUUaG-y431!9Z}mx+r1S%JZ-#Nt^{<% zzcWTb@0GbA=r9_Eev9?2SRF%Z`eBo^c?+-TMm3#s;p4PLn?8Go`AV5oi}Jv*2b+7j z*7teHw}fq?=*sAvbl}C667@#G6k<21+tzAuM9Va6Ph4vH68Silj)Sdjyi#>5NzRrU zgzl8d^*M`z8D|{I71g|*tuDGAr;f?ZNsy@`D}87S728sN5bnGYf{c@jkBCwXtet&aSi_tGm;Wx>XU1 z%$PiBZTvm+JX>(-H0LB_zcaJ5kiOyTGk2TfPioc3lVzo7CM3Hgt#42hdy7o}EN^F~ zO{*Z1S?5H+%*_T-9>wtN47?pG-snm)g`f8n+u6QyC!$Nv;LT9#HG2;n1e+%)L3lggC&%mq~W6=IjjR`oCiH-Likx) zz2pMtFc4RbzCUJ{TbnqRL`$>G#}NlThnIXKgLpldzRWV@zfgqb-Ap zbnbCwHQx`JC%Us~ZDB7*x8fSAsF$H>#l@7u6CS$kYb=|O)Uu^^PWTT&4 zfOW*P1=6Qt98fptqEDKWJ$c8?7nGo0E_Zm@5d(^!I8`k4(1v|vJ*oR&_Jy-Aq;;;< zO3|*Bk3yD^t&6GvU*zI*MS`tS)}9tacO0Kr*byf|nJ$?%%5~WBr47AAKa+eng9LZ-QZo|3ZUG6yMSr{+zosO`TyJC zU%(!)1sn%D!CIiYfY%`Re*|0sCcv@a+sOTI2M+~@zy;t`umfxd+raN6?}J4k`Tq!T zQ}8Wh{ma0G;3Oc~|L#EX{~t%*{}{L!bb+5Cb}g$WKF~o@vsr6j2SWCLmp!MgoT0RBUhq= zl@+sW+=vB^T!%%PUjp`5Bua@Y!xiPQ!`Cr%#9B4_;z1-xe9`I79rL}ujT|y?!ie!C zIubn|#y65ED~FP00$3-fzNsa*QWO1%?L4V>2RI>lkg0HBxO9pIq0pxc!=@?w!wSw? z4eGdw>{s3P2P}crF@*ZMMbb1)`R|CH ztWC@Mm+hy29xb_^H>B=Xk4mq5Xt>Y6^FGUrJcmbD8SzkAP24q9gYI!=seM;_zfd@J zWN`OZY6{`|sR%s%QarCVJJZn~JIk%+oi59^4J)>Y^I}?!-0Wt@-j1-y{v|WbgnKu| zQt(ICDNVgu?q>}p738jFU64Zs_Iaoq2K2aXqftc*H=}tpJ#lKFce1dQg}zN)$u!`q zOTUjNM!h7oBcplrcZ=#ub&m<-Y7M`Cr{Tt$C9ll$=KP~{AEMs0xAV{It@LrB%N~`^ zV%|!xVO`Qj&ucmRnA-=&AE%igphc=W=EQ@nu^k*4d#A#2<|LXVzgjA}Z|cbk`e$zQ zmkD?IQ}G|MATIGEM&{%bp>gs*NaTrpwCK7Mzr6)9_EwYO^41GDwR-#N055#VS2{(x zh}p8|G%*gMCbL3C9GAJF$xCXCf#iS0^|o&X`~O@J9{&S5U-|#e0PDeBzz4YS_2AXu zRp5!>OmGG`68r?2UbX(@`+qLj32p?gMXvuScs95Q901DQUj=)>7NGq7_XJ-+zJED* zA&?(HAJ_tJ3Y5cNa=&W+y$0+9%G0kH{|_MJzZ%H?|6-uJf652=cyIum59G(c4Qv3? z3&cGBH|2Nv^ZzSQ%>QG-V}N}6PXWI~AMg$ERqz$?a&QQo2TlMR!0o_)p$~XJQ11To zfolHsf_88ta1}a%mxHH(5;zx}1a1uEAMhdYGH?j&0e1ze^Y>Oz1!sV}gS&t`gD)fN ze+j%DJPh=J+X2nxH*!uK;ZPm5tHH2TOTXPj|HVW8mo$7pnBKST~MQ`aY3 z9CB@8ZjSmXZe6J@aLk~8<7THVHVV>ZPf?mDQ*Ul?7v(lbRA-aUnPEi4Wqx`tDo6uS z8LNeJMql~v4j*;zRl>J?esv8XPM|dnriA_Yw|D}-JW)f(R^lbI*o2bAjp>K^cG8QI zGu`{wjrxJ~T#|Somi&|NYHZJV6LLmz(cIJm0T+?{)2re?|l zGt(5N&u-n)IqH^-IZ2C4T#P+)gT{@UxXX!HkWF0KqQ+WFur5Z#P)?IwWt+*XNfJMN zZJwaQNwjH+O2@fLP6f+dWes0X+n zsG0qTb$31V$*5qEEbx`}jQAagE<*7$;Z=0ufh};}rX_~UP_LaJ*yC6sHY+||9Io`{ z*iD#)X2KV7CN*_UW>X)x(sb4LX>Di#Ga@bd#Kt+ygjlG_X49+BlPRumJ3R?S72!YQ z%j+)N0eeiK>j-+P{2u6BtGiN4V1F?Nlk@wMV=q`S)w4KuFvV)V#lR$ewZYV7@!n*` zwjMjF?tFx@JD$m@1+7RTX*f)Ac(Q1G19^75rHZU|&?A^I@@N{dE_13FgDVAsj-vg)<+hG!O z4+m617Z+6R!nQbS_O7w&;@te~WVJN64|kWjg(@~!J9>4ap03V_E@KJR`Xs5WeG)$M zL9maYpzZ?Z8_^iM8IR_q318!JrTYq^u{E9PxvO^dlYWwLU{PLmHozQD*l}SDo0zyU z48&Fv<|S_|Xq^3!CAiN+4b9y!a2-kgcWyFf)p@&~9v92P z3Xv0#vwg9L_BvXo^>(#^g5FXPvi#Y>D37q&Te!&Mnr2Q zl8nM{JbG7sngx9b*E2#R0>xFh&G z@IK`7H-i_0E5I~Z15|VGDrED^!8zb4Apd>I>dM#mU*H`;@$ydxj{}zg#mt`sL2TKom@(%-bX&&-vJ`*m^_elopM%#1tw#FK?C=Y%+ConV-;N3_cFwIl4(Z zG*H0O)Fcw@{Jv>ut9yCY+Y zG6x+&WqP_iUO@{zwHRD;bOWSnAWdCfnpxDo+KZ=P&{E^eL}@B_J&oplMg{56QX6oP zqEcA&8H{J5+D*s1VZ<|hS2D$+D`aSfnH!s(oZ&^sy9*=ad(CuKULK~U>`8c~WuBDn z2D;-IoEr2X9-rz9E(m#UkLkcf?`~r82v-O5l+anJc!<%PizSDV%~A9l3^sriDWXV~E@_ zN#>KeJmJ6fj23I&x~2tREl(~WU!;x-^_xqLV8td^m;5Mj)9UHj#)C&WMM)k=ex<(A z!QnG+@L7s48+BFcR>RKs4;3Qm+)5@K*uP>649Yt@zO`Dd!AOwtFFLXLg^@Ax1(ToO zct!4Cx0uH?ow={_f7H*Qs|}J(&BX~-4C_ao?1=B5y)fT!<6r5FZP_{4_kf~{_?@hK zN?1aRv7egtS$${4HSq-K;M{N3VY9x*ZgIPQ4gS;!^O=?1QQv|uTJM00A*v(p*&Eh= z^Bb(gMnD_4MmAO=^Jwdo8of??LT|LH?Xa8bG`%~KYIgRg8=^+?Fj7M{jDqo@nWI5o z-Ac7bD(o21vzN4m2doao7#^~&GtLtFEWPZkThw@`C7YTwm)(Val$(pvCr|ou*-=}Z zrY78eY%=5J^DARzJd?v3>QtN*+5Ad{(ca;(0L=4OwxJSl(_QHGG=0-#1joOFG=<1~ zQ7x?ih|m-pZd9rILdm%?KfuYep^S!pq<1J2XH{WE38l^`UNQ;&I)! z42jWETXfmiigu`7W|J@PAipv$UpWS)+kpeMT!jfHj86*fLu)vEO4*#xX#U3{9>L zT--G~(N(IB?IY%=yD-4VV^nk%M}dxD?<{F7W+!Ywm`n1Y#yFO0xhok)dqW?*blF&c zSat_7a4@;^2WEZpE%Yuu%Uv}T&)yctRmMprjClRN%pcg1U?-&1X`ZY8iIz~0RHQT< zXj$@@Spz|hVKma0Q*IU2O=Kb>JC~a*#LUT@4?3Z-K;g5Y0(y&$y*qC(|_RY`F z)q2*ipM;lO*xQXHyq@|uN=HAoe?7!!VtzeSv3LE3?&DABJ}!L1RTLInKV2Con7<7y-4Q} z#znM4!qY{ z2pQEY~*Z&V1c<-1|h%f#Z+ca3ZTH zpJ&JQQ_2FiOwlVJ+`vZI!RLeCs(XnjFF_<&C%p}*h3}x2D%Zwsmzr*66}1#&!Tr{> zen|nK^t!~_x`JQK{(nbg%hQoP<^TU!{?X4jkng_^R0rU*;4|QI@Jw(zP%gkt;1|gG z{{wyoUIO-j5pX;35#;>0ffoVg26zg%3_J-a9^fa)`pW(HD)1a|DYyhIfao8viQi8K zM}fP5_n`-PBDg;|1snmsj~+li0UrXFfeXPwZ~*KF6(C=M2Y><40oDNJ5BN1YgI@yW z5BMl}4|q3tK6oBb-oMkp&B52uBYYbC2lxbdGo%UICs2E(H$*=L6Lg zyf63}dW9c??}G1uFM}_D&x4l$)f|)_Liznp1~&(aEqD`<{-FXM1e8}`J5Viw`+@s{ zqrjcPuhB*P68r*O13nC13!V#<>+i{+3LXOP0*(N0Mknz^pqvBB>!(@+7l9pM8@Lhp z9&1GY1}_JDf!54vpvh02C?SVVh)?kH#JP-3o=N1qf`n-z&PLnplxb4I1ErX^>k6^J z2%3wZp04{*hr8sBP+mE|gtRxWYAbwfJk`|fEVg(kFNeFhfyip0QSI8j4OKZA$rUW&ipDN^UBWf`8m$py?;9gB%rHdW1F=}X|C|%dO+# zBuvq63MrV?Gl-)#Jhx#k5j-ZK2 zX`_uxHHtI2nt}FMd$1K1DUDkQBQ8iWB?G4;oI-yPE?rHiM`9d=cMfb^Z&oOykj$I*bD1(a=SwuiLTBETsi58dWi!> z%%hX5smid3(sli*7ctjZn{6E1<`4nl$|s6xqW{fz%wHzlZoSO06*uVML=P3Eh{lx- zZ2&&&K1R_KAapEqg*Jsbx>ja7d{pTpt~rG+YKQpb{ke>Be7{y>yn=Ou$xxAe#fEoW zSFmhO#`V=N3j0cHI``nZrp&EN<8~|wD?{`5fl(>ND{Kq%#*q zXR}n}O9lYhe&qkZ4gv0!lH-v7pB;p`w<6ms{{P>=Q^8ZfqrfA;C13)KgFes;?hkGc z{sH_7+5cPM^Wa(_y}+ZviC``G906V}< z!A-!`=mOpVUIShYUI3mCo(JZEVhbJ!2ElG1zk+`Nzd~Q|4R9HF1o&s5TmxHyVhwHv z6ld^kpnQW52C6IA4b}nG75qNBfbW7=fy=?u!PCH_!K1(n@UP(cU=ApM;Zfku;AY@==nH-eehhvHegK{co(|3ir-EC8uS!P%UJqUe z9s?Aoum}!^*Ka%`sTPT>vIK*1ZzR4`yy2jIVsc%!}0zuc}iwIp>E z=`u{bN}J7Rl=v0Uiyg6{5}f9)>4?{NA^6X>Ogy&qn7htGE}TVAb#F^bfBNET$toEK ztwLK{sJAuNH&tPR3Y}wAr(y(}{K&^m#-4Javq(<8peIX~lHSTKNA4*Yvgw6htkXKN zlu*eH7qig9A3Esx-V{n;2vinrZ^ zqXT`|sg-^p^v1Ro6u|#XbZ-8q_snhg^rKGE#lFj^x-ze$=GAUs)I)Y@HggQ<^6k&v znVTk&r0u4)-P7YWT*n|5Fenohf-a&bS*k$WTAtcsE&;m;rWmoSmp78ERGHSa@#`)q z@{&jc*zFH~avUW?;2#l~X~JT%znXStY+402&S=3tE!M!PvGzJnwU(b7R^M$w=k&z@^b5|>PKe)z<^ zr+r2t8dvV&QWUjn|4+3uTJ>i|o5K#OICAVUlutWLi@1awKGKxca)ZPUy@Xk) zz>l#O#s>6C2VE#h|BfzNyTU9f=>@LZi#a!W8}CEL_QyR@%@psC8v}luZ<9A@x&9kR zvlUOZ(t{vvF*kek#(9rW-z)huBjwlt9DY-h?+f@&Mi!{6BCt^15XFtH3kCrJxK-UIZBBCj-S8906_$zK=Zq zS#S-w8hj8aPk{0X-U&!9eAN_J=X6UH^=B%xS-d<x+cWtd8b#b&MtQ=A+!}e;H+@)Dv#3 zX-WdRT^t(RInZ||#d>!Qp4ML+>EF4nXy)hd3KFHr7-V(!8q^zpG`g$9IV|@! zHR)?O67D$r$%8V4Tvv0QoF#3UA+O9W>_r>ULE;D#`}~SzxnSnPnOIo}W+v#6JZ!@q zvC80D9CzDvSD1$IDw+G@1?dSIj#wSA88In6aMc`LATv-%u%|I7VjJHm_0HywcA5&$ zXG5DqPi2O$E9k#4$&OX#N>epvG!BKgg^Nh~US5U)b(TVYF+$)Q=-Dcx_S|=1VXmW% zY&ZDb*jP6T@U8geOunOh!ldiYG;35=@Uup*lwo{$XKf7&`paZ@N|uMlvSn|>Vq7M@ zQ~j`LDTMYDeN^(imMjmUqj`cI`N&qBk7v~OhBeg+ImSCmmC+B>Na_*jX02P>B=O<( z>wV_Ul^k7U5f2HJ6zDf}xX9`x0~9X4tfR6mBihzLVgA%35IyLCV8wM#Q?EO?%8EIK?$ugQ# zJE5*CIU9On%J#Oh)@+*xJ?@WaicXgRubLZzpw!N&x?FnkEZoRL#op1;;o_2q8OtxD zE)4f)+-U+yZB#Ny5Z^E?O_|plW(Z;* zwtCR!u*blZ<*i=V3muBu6oLC>H7c1l;^jo;UbzHG8Ne!7y%-wEf} zNTxCsqZQsoOCorIA6;wq|GOb>|06P>s?*vZ(v*2hTdHz4a72rugcKWly zS)d2}H?sRjfNBDcgVVqei2nUM_+9w{lo#+w@GfL{$@b3%j|KaHWcv}Y1>7H;0@}du zk>jrdj{@@TZwK#3c7GFi9e6Dm0w;mnfWHT_yGx#b1$a5g)(b52x8(bO0v|$#e+zh;6H$0GJl^0{{aqw zF;D~#2E$+o41#|Gn(I4&)<3BPpAbRUxa~yvSYDy&OyLd3v!s3LGZpm$9$?s)|1y}E z#$z{qomCvYVmAFeU&edA=?vDZA31Wf%8YQBq-=eGpX4}BI38RNmbXT7bzYHiJyaLV z+?oiR3{D7=GBMX&n^7od7`+C&aPoG%`|5N8(gy@RK$bgo9;IG3#g9?8=9a?V4KA7{ z%J}3LCg=tgPE%rnxs^y+XVIKP`;*a<#;*KjP>;WfK+=UBb`hv?Ijn*d-!LM;b(Mnr zRSu^h)NF9-q>!MtVD|4fp*K$3I`O&c?wCPDw4g@jY-FS&ofx9+m zb-N(E*Cr4BX{wr5&vOcSKYaa#c#rEgXsa5s)hOFeFNnup7!As`ZAHMc6YpKhz!@lmI_gk9iv(q!E!6daZ4d-EJX6yR# zx6>yuvw_7+XV6_oo^F<6t+~0ycwtg4=+<11c== zVdVUmg6Du|0onf*|GyFZBT#Js+4)}yUJA-UasH|U@EV{x0M7%@1tUOp0Dgi@|9J4v zK(YRZz=_}w$nUQM2f+#8X5f9u@6Q64fnl%{JOCU8zJ%=l5%6B{79bh_jo|U%v7iDb z!30cdWSojHC~gWXRG|Rzuqfg~#?Uq8$$EaGi{h7K&-w>d6HqRMv^5&3bfl z__~l498bA3S8>MURa3%@oQfbk96PYQWUvYWINxx+@aqSz=dyJPZtvn8x#rEk9icH; zR{Yg7PVdbvD_iW|?aF;WQ<@noCvRjMQv(!QpG+PNbph?Co(`0(pDFDnVJ)*EBcJsE ziZ6p*IJmK^rA>B)CRsg8b|Ox0eHRjn#)4bDvJ_48iuI*I)GQV!7G}nZ#Xqs~^v}Sr zC!>eYZHnQ38OAw2ibMUw1A}2J)l2TH-4t~@1`(ba4;&&#VP$qa$%j}9G2nRBUx7Xjyj9ojO zEY!b!@D5#=DIc6;n-DZ{aC)l8NCWJ!)>c9f5`R#+k`sR-Fwi$nKr} zT8Z)~IY_e|?Sskrn>+KUJ{aE^Z#eT$rQF|qb-|sSmhf{+cAZUp!}}VC=k0adKON>+ zHx~Tr+|4eksR&s$l|!VpesXBo)J%BNvW{bocjuPp5{|mT4$_dVDoxhyuDjK2p1(k= z!aG{%V0ekTX<$l*yey$#n=ziJFPiJ>lE+K^I$G^65|v%ivC_ERd=ahiNU;^r{Nzn( zf1Z{{`Txs7{+|o-|9g=6<@5h!pc(*=2Nm!@aC7ieWP8Q<{}=cgcsY0sxCktQgJ2G< z0n!7!A6Z}V{m%eT0Z#@Sz>kpi6~8Y(|Hp$z1JwsS8=M7H8}L@(*U0@>f>#682RsN= z7x13oH^~0-36Q`4zkpN0N#Gc8G?2~z&fs~-{Eq|+U>eA_|8r#eD}iME-QfPaqw2~8lZUj$AK9j`Fn!}+hjN%OovZ%(9m#mCOoS-@X?O`HIbek9i&GH zKj+S}Dg=KnS0?Y2s)zsD1@DG24@0-S;2jq_XIOGlvKoENiF2DBuS_gv5{f1Mp+OQ) zI4G<)TAHCXtQg<{bUr3kB7$i%IE`A&>xI z@Iz(A_&IU(YA4fD2vBIVPd&K6x82n2BsCf*dwO>IFJ5QhRFY1SjzP^NLuICvx|_Hg zPirzPjmI1e+=0q3`a*|ddvz~lD*TSMskKYB^Ug^;{4MyZidBaJ>#Lx~K0KL>4()H!K6M{u&aeCStzr@FiYZ`N82a%N_YKRtiLA zY>O*L)GPaU`t*$M*V$XoC}jy9#w5p+Q+?KI^%mzl8V8qmV+e{i1j_%yjHCC+micXL zr0MH4YbefHoH4F}ZZz4Z1B3CydRDm=pYMW_xeeQyI2@O5p5NEf3xRo_(3`_u&b2ui zOto#Se0RgzByDFlho))ZVZPTC=Q#* zCi(wpMD#9X$87xn?~w7Y25$gmupZn4sP>=y0Nw&r>reLoGr%ZN-v0vlC-5s|`%i%j zz$7>uoDOz@2LR>tyEV8aP#(XBg5$w);2X&Fp8y{P?*Z=u?*x~FXM*#=Jg9-q;1)n} z{#7st`oJk*Gf<7dn}92k|6c^21(d7rsbCRI0_EvD3)}%gtSZ_o|=2z(EG7km!9 z9b637fSAw!PW=8Q^aNi3@)Ot%+JNc}z5zTBTmha0o&X*X_JIk|50n#N3;26*E1>*) z&j;s%P2hN-`CkW?{8@%bXLM+XZG9H_X{6YHB$dL>@?>dh2glmsRw-l}{$r~Yl1Kkv zQz;}bz8aHl!tf@%NQN<5R->@9e>>&Nb`0*^+Crru-U&7=rj}Oln-!^ArwU*pc+sC? zJjfLMJ^Yfhha7C;ucov{i&U!|$B)vu;N}b#m)CPx%@{!Kx4&sGs3_4|d(k+M8nlKV zSnb1A!XwMbM&@KMZ#=xSA4l2r6VEw~OUw;{Q0us*Tpj5%@kR7&R=28m`oV zF?d2Gg59dTu40(Bd&(utaS3*za43Wuug-fh)AS#SVMv_+l1KmFVHg^wbJaG8Wr%6i zmo?XxWu09L?Lku(7I-3BOASx{)~K&+?!4ft`g+Y-RO{&&NZRWTIyC9aOh4t02(jY0 z6g5^@o7^k9#7b@ImqTlEDz=N3r(cK(F@$GXAIK02@@J)V69KCG`HMK~A)57?yNk^K z|2_zPr%3k0|Np`u0Dc8I|K;ErUcfxX5b6R`-%bh9C!v$uD=t(oxuMf*Z&N>2q<6wPCoGDFW^x4(^-Yd%F8fkzHjG8nt zAeD87_G}@2V8|G#N!|4=$-Gj56RRxR8%$|k{3 z@TUvC|1zPI{r{7|tu^S)(j}gr@|Nt_%h|7Vb>D!@Q>Hh6VT+)}h^|-~PbjF@ zv>}`A)ItrJ)e6#ds45M$LgwIwa+BES7is=hID5;xF9iRX)yw-?h1Ova_^m;=7yNlk z{Omi|dF1?HS|*EVakNsd6?u z zEl_@c)$iL3?h8%?^7%g=C@0{rk@0^Cz6BKjAN~I?<@ZT&8t4aoKyd)7-S-Rd3~&gX z2KIm(fgd90e;0fQd=Y#eNFVSl@C=~bfWJk)|2B9MxCA^DTnu`^O~B`n?Ug6s5SRlq zKyd;mf(_tj$n%o-UkLscJRh739t>^^{ug=v*OKYM_rdqT8^G&;VhQ92&;^w1{}$i~ zAo>4V@bBQmK)L@<2lob@Kz;!C0(S+fz4v%92)aQ#xEr`D_$2cFBfuQEBe(@P0;mpv z;srhk7C>EwkkEk_)AMEJoq5sc7h~Y*99qjl)e& zw&fDad>utZ>{e?+7hF{I=MdX6R$a7A{W+;ITxyR_H$nOmZO!zx^~5@6z4@B}$)qv6 ze_Wej&f~K#+{+I#kGdNvSK$vvD&yt;WG(b%vqWMmY`$3jvc<{96uBK@3bE? zHpj-hGxFyu+%MC@8#h!SU&^W_B0@URE9BQ!9|v zVAH;?n2!z8;-9MooWAr*MBnCW%=F!K5GBhr5xx#%s?nT|(~bl^vhED~Tgp%kivJ{; zLX%&ZmOn+x&_+$mpD1}-vYi{WS$|;?b}qGJ^TEO3?^lXS7_o_AIg+vF`gnF0A{h7Z;rBR z&e`G$79FfgmLes~mL(}&CuSFBJegQ-EHhpMIRakX(kz2gQvG|`WT36Md=D*LU2aRg zert~f@ng$|3gzC-c9P#>1ram(t6i9?(_C#?y@p->|Mx@?RDO8L|Bnnp;TMqiKMO7g zRWJjJ;2iKEa2N1Jw*0Jhk^Y3mHYpG;P&8ZD6{stgl{~LjyBFn4JpKSl10dE1%1Q&s` z!KvVW;3S}Yfp-SKLdL%ayd5ZyK=cQA9KT-(4uNrS1~?r&5S#%15gZAA%iODWz}4XW z;C(}hItqv=dy$gGg!sK!&dkYL ztHS0vqfj;LZiGyV8eLDXDj-<$jiN+1c@*}^s!gS;PzU6&P+hbOIkzOq9`Co!PAErw zXoI`wshJ`@f})v{X1Gr~xiUGUYN@g2nLSaIcYJoH+#?^(a0Xdxb^TGY7wdR|FjV4t zb&ql8+&Q4MT=8e%Sa&tWTa@|i~AXqSoX1DS6 zNAP>oN5s#wkFYy+HN#%5b=Ma5@;j-PYlUM;T%&gl9m{=xoN}NBXXeJ~TWZwRvg$xt z$(Wv=88yR0>|=Gn)X2wl9b=CU`eLn>`o7(JEz7L4ppbrg*wLM@&+QUCPAN5>xP>-WIE3OT?!Dz(}iB@H{m^=spm zjVvH9Gjlw>Nxr9NLHhMqbX6Rx@=JOR5MGrdS+>AZeOYHnO2U+mH;E^eY8Ps z-C?dfCv?~c-}aAIqWxJnMaF+O+K1?ax`9AwQ4wg64q(v6ACUmJ>Ux8XHXX{bQEg6ySJ0lE>Rm`02|zUUQdrvGU`-9 z)jH&J3_Z{y%uZ-nlx{0;p|KgCqkLs_gDJ6A#x2Q`$H>>_;3kQ4RoaB*(AeO^Qaul#Gbw z#eMVhR+N@HOoYP2=2D%O|99KxT$b6C<{ID=#en*-l@37KPSAa`)eg0{cS>^{iR`yy!_+o$r@gxa7g6!#DfP5RHZuU;6b{pdtzsJY$p{87pCmZ>fTl#Q?ykb zNgjB#U+phmX#FE!1unH&3Z)TAyl!vOK!BP5is^t!!v2__zm|hbQX?3Zo`Roq7ta&+0+UpB^X>R)Kq$r z7QLsmno1Xi&`-^ItDiAa38P)B;ahB3ERr?Yv9`N1zOHb=qzaAG({akcRxB|JKF9Fg zq(ihGE$BQ50h24P2b|%Y8;ezCLYc2VUojW(ny0hFWOt6YbD(*Y86^EizKqB@;hsTumAeq5j~ zWCmVyQfsipa#iC+c5r5H_2De4h0L~P6U&3N{;`vK?i?1(?qn4-c`R2oTx5^6J2MY7 zQf5!}(bAGxW4EdWOF0NN^VEYpv|&_^vjq>cMq+GDs!HO8X)a8YheMO0tt3rEEli<} zJ@iE1Qyf%eHRkcmjkve1jG@!eh>C6tEgbDV@~O{t-19GH=GU(jX*tBr%l$OECM|bI z%>AQM;L`u8oZLf96E1pb7MhruXsM>s_1npwa1wUv$l&fFOWD_1IB#wxt@u!go*NP4 z>2*}CZK*!^cS##PJtK^GpD85P*oIlxW`_{Xx3u?l`0b5?N@qc2Hg`rl^BK394ve(W zdHBuAl!hZc9E_@^SK86hwOOmXNRiOmd`J5(a>|w_%jEKB4cf21QhaskK>#6Uyv!ok zKl#T<{=X}7+)a`3B>&F`nNG6*bHQ`K!@wdq7n}mViLC#h;1l3UU?;dMxH(YXf5ra) z0{mR^K9Da!6%2sef!l%~Am@J)ybD|j9tkc1S)IUdk@KYscp9jHGPoP~Ci1>={mHJc z`hWKWvEJXmBkz9{{2Qo%Gr%@*GWay|{!77&fNBCf13Vo(5Znd)8rl9+;1ysFJP_Ok z+zH5+U$*^Uf!71s^pzWM2Al%A!BOCM$nYNn`@qrQ_TV<)@4>H-)xQp204@XPfpfqq zAS=_a;cxl(-vj&r`CLBzF9cIyH@Fk{F7x#j@NV!da4|Rt6x078unlYleL!>jccA4@ zMiRHW%F*g&o?|Und7;yoiQF=$Hn`%1NLywCZH~lc6q|lZ`nL%iTX(N?j3M=p?=vSG z*GJFUpsn`}AIb-2-Oas&DWO`iUQ~3Yb*3O4rnaSnmN4WJvePPYQmR)09%?gVttn8m zwcv|l)lCl=rl^IhPcUVzjS81A&XN3YTzXVC=jNA#+Sm=Bx;TNg`PnKoh*UPDQ&O=3n-j2N7|7LrsO+dGv>4X5$bC{;}f5}9Hn#av9{uhbjNdD&#xMn(sRd$;!& z`*!w@jKDzBZsdn^JgC0V^y(?O&gfx$o~&7(7ga&F5xU{02*59ndrjVsuuEdq=fthR zKNzpn=B7%E#-JL{k@YXPjS~d1(|d<^5A5FF197HSaZ*qTXVZe0Ru+#DV*b|^D6p&G zJBe(eA9>k~i~^nEVo!huFTIzncfs$K1Ot1kv-_z^oz+L!fJ@wx47z)8Ywu`p9QkB- z+RP`j6NEK}%PwH3f)9p2$EQYGK1-z-+-c*e(^O`5_1Drf+uOI(+X>OpCgd&5RA97< zbMw`Wsp%GB8NsHfXPD~ngL9j;UT7OPLSHpT+-B!=ywdYj$h2urH@gMB9I|)Q`z`eo zH&M|d>r?~cNPcujeWz?zR>IdLnuGjSS(mh&F!@lg`ReS{kgZM_S^GD=&Z#DYH+Xo$<9M!u=piDGl(d(vNUscnen zN=1z{{;6GO^_$H{YWdApN84D*l44r#lyt?l+AVf2M42>K(aDYu1>8yz7pBr2>QXt3 z_ik|n^z`UXi`k7QPp*ml0mPCq5h5;SMzi%g=gXBYNG@&BzhrUR8>LH_JA4&q*!gY>M~U}sUPXG9F7rSIr4Gd`CioZ0cL6n&7CP(Q8rmLvWD$Eg!eqx6+K65!N+P~$%6By^`=!@43&ze` z4%g20Dcx_q#KYRwearsjmSWfgI!+@Gr-i8%iI%FmTKb9@ppgsIDnnm8i{mKha+w6; zt~#No=Rg%25}&042jRRa_-)O}?(v?8Fze{h&ORuO7hF%3TPf{v@_5s(&E~+%ykq)V z!~(QW+);6|q;0Zs3}a%^zbUfqDQ0le=B8?u)CZ&ac@SdzjPy={11!Y_8H}iNB%#TfAsSqP)4&d*>t-%M7(QBX`+!Fi&`CBsk*Ma2r zFN05jkAZ&&&j7Nq-w|joK5hB?Z7QzA|C&sEvyZO#iCNDUG|;EofyzW+IVHN{+Cvz? z*i=iMLeQL{KiBTyEE3CJuvR3V>wf;7s=1lUPO}4cu!#`n=$dh1kU2jj{Qn8j%Ow_X z&c#U(3g=0)q04-R#9Kr2MzJQRj+HVpmkq5&7uHVLNx;n(W4_HhFmr43Uvq3mWCATA zy#uFu3c*`^hV-FtEE_Ca{z7kcG(B_Bv(?+mN$y)F}3dWR4&k`O0 z8<@5ec|5D9EscWJVRS_R_BwZM!)iwob?=_ux2j@daCJOuY^uW4MiYjOsA~q;5Srmc zFwmmG(4Eo#Sav#CS6f&n6x&&sN^no_GjQ&NOm#QVDSub@Y1)IV(^k-pi zYPK})yUXl7+#1{Q@3_eO6fTmf2O~9noZYJY?u?y(Ia8WeS%}ao+dgnut+gM!Vsm?} zwawwyurNE_vZTj_FI)$t9eK1rpNDi?{z*4|A3ZiZw^*!I$6Oi25k)i(GThnTSRuR5kJ+3X_{4_I8 zvD!K=w>fvXdvJ81ufHd5IlC#WDWeend$(qam$QLnk+md_@|eip?$r?2#A1j^zXK69 z8n*X52~L{A*7H7St7fyWRb86QChLt3Szk-l$(k+fcK>!@+%+Y^O(nTy8QUSs<0&T9 z*Tp8UYr+#-{xot3Eia9-Mro?2$6qPR!At6*QYL1Low1!^jF-HrK?iQSv(2dzZHZon z5vy0vEpbQl;{(0C&fF#LTcNgOi57UWkII0VYcs2F$p_28E2B^uef`oGP9Es#scFXQ zwTJaKC022u`7N2hX6ip<92lcS)@Mmu;9*Q5=RvAk^^vs*TI_IEEipLFz6v80c#J%A zkse})k-f;io9LLy<9ZeEi4m7blmlT%#^SG#?iq986H`w*mXK=g22F|&S|qbeE?w?v zwJ*c8Gc`;7T>srk>(_CEa_5TosIG0fO(p-IgoJr*kpIVmw5j<2LqK-_ok02jeuK>a z1MnU2NpKZ-4|q4Y7^n__@&Vo!{0DOXzk@e{8E`E4HZuNmz%V!o{22NE&ERF=#X!FP zPY1I1E8pKXARWL}$orRrr-Qoz+4^q1>1{kMVa{2vGM`~L`d40try z4WtM7CG!58KouxY-+CZl{x5-R!RNr^K@mI{$d|tW?hZbQ4nR5j_XEY{{}NgM8{k9W zjbI-*3Me<<55YHqbOEy8{|iuFe%16l5v&J4N8Xp;{Uu-wYyir~-v&OJ?cb#+r zTDpkLCU2Xb5X!kG<$;Z#cGj@&LuVQyAtdLGvFx%Tnc*zhx5QYa*BHC+f99l1No5T; z_!UNCW8JeLwIL1VmFzCL28&by2{w7EHzL+1wvl}0u`$`#$UU+CrKjgB({heU?)R%L z?>fcT#Lrs&rO3*+X=I~9ftxtljA@sQ;6spR#dA?2aPGz+rd!c@9Q+xk=jIo6i#HT* zah^MUP$D~K0-@5gPhCp-kC=kSLEhzwm$P^I$k2!#s#qz>X%eQ8LT~!3eQlO8K5}WU zII&l=J|c^3-ov3yvBiZB{eM$?+mV?Exp*#yK2A2$CW!(~;z{4arL@eHM_c-lfdfmM z^eIhQ08C^-e-no>G6yTtJEYqr^&rp)uIwj$gP7M}!keE(zRrC;cWsuq7?+HRzv&T{ zE}>%caR_G{EHYXj>E^kt+a)@)ON>e$<3H1p%}^m6Zv><&(?%pc)CKOCExY4V^c{&2hH2~kPa zjU>1zOB6$iOhct1lR?uySYxSpnAxZV9TLwI#7lftaZ)mu&~98dOH;KDB;7C@S>lrV zo@TbO&7%S$(1t9%D%PZ}UdP3hHsBlJI;_I;(m5S_)uY**O9nsknw zJ|n!J-!0!*pP*z0?O=+uGR`=2BnFv(oIErk0XB`MEDW^7BL>1Bar<`ct&kfeU13_~ zxewxn?DOWI*Y=?cNmV#3b3A>^J;~lhvr*r?R(WNHgk?BxmI^+3>*Fl!t>i~N5=5Cc zX(%u7Gorq+1Ra)^haI8CZxe2ZfwSs5kw za&laH($&d^DeFq46beJj*Tc?Cd}xe}tdJ~b42{s8aszK|$VT}4&poIU{#0?Gig2IOG)ZsuR*$gXohmM4(**DcL^p!2j zs`3Wo5u;Z4DG}L_y!nstW04FDePyPPyv={DNIhy?xnJ=*U<_4rYAPVf~d{Tl%$Ph zZc(>eJSay*}`ZmBIWeljG`jA&WlaOCK~ zD)Eyj(mp+?^^X=}jwOyprf|YJSrprEr>={`8U4EPD*VseK*n$uT%97CDRdKnPeA4`R`lzFG!4%?ck?8Z-oc4sTyEX@1c6Ml*T&Drd z#n<>jB)X?Zbrxt#bakFGU>cLkggg%IyUiVId{+eRJcQjAMEu^Dwe-$&a}=gc44G3k zh->Tl$$hi5l43cfNYYfE4_o6sbNm0Xh?{Q-^8YzO{(lGZ{u99yz##ZNGX2%y8K4K; z1xU`9e18e(2mcRz6S@BN;6gA8c7km{dHjw6-$S04tgoE@)8Gj36Xf{sgYSW-fy;pM z_YVWv``?Rf|7uVKw*kLLhW{1#AMi8qeee_@JAVP(9ef;_{^B6p-;ICo2(CtcKM1;j z^6kG7JP%wBo(>e3zY!b@K7kDXQE&}-CwM!!61*AIKtK2`^7|*iN5KNPFSs}Oe?YnS ze+9k--U;3Y-U==U4+jqhXMr<;a`9>W-@I};KbhA0Uqq)(9+jDWU0l~DM5U&(j7;Uy zrWnGoGi;mEL{!%LUU!$!)&h8jeF<((wCKbr>5*H}C|@&Co*&!iRx~3xq8qu7eZ$t> zi1*@jWh~m=lIBCVm(U5P6@7yIvJ^c?nU+Bt#s=}+OxzhQWy&6)DleFMB=;`O_!}dQ zrmYlP73RyR?Db)*@Ee3dJgjU}$}RnonWgK(B_&IuPHhqMHo{U=C}uxz!G~mu^mxyt zl3Mexb9O>4R8HYByghsk*>s0b>P{)iCLL@2#7dfu5ABdNk$kF4lm0j#?pr6G=B#mA z&pEBwSxX}=jg8(xGr7W^&|~~K<)g9Q!#?d`h3$gTslskKGh^Xp?pk9E5I!b}4+7!0 z46wDQQ?a{nx;YYrj2gz9uoW!yk!?`MaSrDK)Ch#Vy8Ehg5yT11R040uX{q!|g;F8S zr)73HF~_;QO#W$T-*`?qsLb2Mp6!@DAkBq}+F?KzGj{+nN^Bu~r`>>J%>AHY$g1VJ zQiWJ&@l~a`;-oT}4eiBdC5wp_OGZF8XxX%Rj&XuY;UO;BjNoF?O!LC0<(WnC$2RX-LCfcGl3Jqp;V}HfV`s>C9P5A>QpJD{u zb5dR)`$}^N&^B`>=h`TRX)&{WWgcD2BvAn71+swY&l;@TPa$rlUt#QwVQE~w#?`4X zWYg3`*+W!`#zo7*PERYAzOh|N zUiJ(p%7t=^Z8&~1AGqjj2aS7N@=gugE2k(Z*iUTKu^lG?4ir-ya=SI%Zdo7rCO;N=Z3GgI^*e~&>bmVk?Zf$faysfA z8emoLtFROzYg~~Vwbon0HTdC^jd%+y!E=dazsRo$ztLwZSQ(tE#>Nw7Clhe4pn>vjSD=c>n7M~ZB&y$$8+y)3*v?$J)pM83dl)m{^Qir;RuevKUkci5> zy?d%I_Sa6jQLHXZaSqv}nu#Faes|7!i}B?#raj7i?)o#PRY_gz-apJ*I<)c7$_z3l zi=qDEUA?=p$rp$Fw+;;V_nD`@23i9CUc6185}AO?HSeDr2oWx`A2<& zLuaalLz`b7{5<2ynbQU*7rA^+h{BvmI9n>73%XoXialhac|5%neMpRSG$uj4D9$g= zVOO$r6h(Tty??YAQfia3u2%`Q3z6WdhwD2BbW%$H;tB*$7f}B|3*6K7dQefBj40vm@mb-_l9(`kmJS}`Mnd4tt- zf|-KEPU3hIXW=}mc1|Td5pE3gL#ia!v5iWlBFDq{14T{VMT-D>97u1@R245bn-*&z zO9I?Rn5yxON^6NYJzZ|wN3KKE%Q8H0&XBs95Rf0h5I7Ym?q9k6 z?+fk&K7n4~q2NJa2RIINgD&t&^aGCt%J(-0c7S8RC(sqV8@wG{36#fw4%`9!7CpgB zzze`Lfb<7X1;1uMzXV=i)W5J`q1wirq z2f!`CmzW=$BjzW>F{#iUGi@iFCn%Gqzjt(WxH#IoeZ&?>N?3a>gQQ)|P1*esj>wbS zvMyUlr{cg$`LcShL~0qh$yRZ{vxWF7V7gD#nTo0RMp2b|g&OU{_m@8?KPi!*B=%`l!(p;w z^Ufw;<$AoTVbT~m7qoY(gJII&Qn=7XY*Ni1t50)Jj8;C~ECT6E3+5dLLi$RJRI)GQ zclp54WtW}LT#hqy-XcuA=gy7Y@V_i87!Q}yBz$5a-HD?#jRw6kZyYL|vRZ7$;{)ZJ zkyjuc&Z(_mQiw}t){%R-PxFp+kq&d!Jl6(_`>gg@6#Ao$&v{b*@& zeLE=ynJ~x$$zp6(bo-jH(QzK4VHq&k`q3`b{$J*C{c<*8?+u#1Y&8#!hM>4>rOVbS z8>B<6wl(TK#vc}BdEO2`{c6jRi*rPyS*RZyAxf*m!-K=*SZET@w+j$REUvJ#AKzeTkK8Th-#S`qS-$<^d5{_8m8*7tgPh4;1urji2guTb^IO zAXzNy*GsX*c~d`@ys`hpOZAuUUJ^B-IU+tZ>`i@R+rc*il2;F`c)1#MkNP@pfyUq2 z+PXB(R8g9veAB-9nTS9UB&k%0Jqh%I)5lvaWPZzcM|MZcXlT^sWbW3X5kB)~PA(jK zEH9KaWhaPV;cHtnY3?^6yCs93dHvwmp!t^uVhP&j$B24-J_o~Wm6}&f=K?u;&6ZrW ztZG@0U2l4Gni*lUk63I-yh8otrSegJg6j!c^16SUWFug!oaALrlsH3&eX8rHF-~`8 z)w5liokn4c#OV&)OnM?^Wz55PCavgH;(YPbv)Q>+e=_OPNCGmE=-OAVJk@XMITf1L zjA*~;?s~q_hUTxFajcf7F{Fr%(FiQ-7u7;aat7)!@Q#|JCO=FpJ%l<&$w`z}^8XrS z%9$Yl?+o()*OB*C`%gIlE(6gQppV~=1V@0Ife#_~KMp(w41k+}48EgVSMYjJ}un%;Bwcr@A27C+|e**j%xqT7b4SWST{i#4Q{JG!=a3i2t ze%0YCf+N9?kj<5&@5x{XxD&W7xCGgJ1bhw|{KMe=;N9R|paKSgV)*|DS^SmYVsJkA z9`g5sW_$~WWXy6Ap*HTM|(iC$zCPJ&q&qiOY7%r+8y}i{ZJvZq3nN7&0~?m2{gHHwSS-Lj)^OmMMMovIiJus6~;i^iE+1ABIl4(#er8#=9bXOfRS9UNG|T*H>HT5^@mM*a;}`wsnQ42+DDV6Hg0 z^f_*fs&WiLTmDU@-+6sS_v`k(yYAJW-jHG3iFl_iL6b95~^;=YRD#9Y4pi| zmT9=j!mPWa)$(e(GFN!-by%8pEib<|>uy-;HLm~Stb3Sbq>z`wDQ1)9*>FK-edfP2 zx|6P!5Kx21YVHwv>LmJ^MBcC%e1R@OeM*-_Cj=w-|^lhu#<7Qs%zxLQB z%0>t=L)YoCD}QQ!a~$T!HoRqq zeM+(qne0DLN`vx7&4a78$Oei+)2GCpUX)?XPy1z9b~jh}E>R;bw4zcXolx9AO*R~i zEoj}NB$epLOb*(|@^sC*Y0u;JB=FwXRCqEggH>g)+cS@#At^-;c9o_t=)!RX*QYKy z&r=U5mO<{K`b8Iy=t76mRq?uNbq&T2sI}EK)fTDSvV$^)R&RD1cbd}FOID;ctQSC=}AhsD0>b`i3=VT)I#ue9s{##?d?uY3^0tgdSKviGcF;Ig-^V91({ zPkj?hEiLXp#5gSfV{>4|7Vv4n!=gU7b)x>&k)myC`@lwwsym8;Z>IarnIpB93{(+5NwUZ2t;S11Eu>BiFwcyaFiS-(}#z zU;}6eZQxhP_mcg81bzU%4^->#k>C<=CO8Rv75QJe|HeQ&_%1qtE5X~qGr&KCVXzzQ z0{;lUjy^zo0_6aFCQz-vACDh zz+1qj;1cj~@DQ*b{1~0Wo56+PAW&_;bHHuEt-vk8*U&3m11<+o1ghnCI@kl`8_)$* z$M1{aBj5#KFSrBvJbH$=fG2`QApOI=faXj&0$Tj|Zc4*&68(>kB~QZTrhi-7YxvCm zB!W??z~-wgD-0mVCF=Ni!P$wM0TLyW?a5Mt%8F9AZ6q`%-^C+7b@!96r1DQ8a#Q#1P+L&3 zCU-D8`aRT?TV^c7;5!|`Pj+8VkKB4KA36NHe#snix7!@gP=Z8Jtnpz#57pTKpd@>B@wXdZ-F1*?*1`Oo%S{z%M7Y@HE1w0gFD>6i=fLna9=h2!8!3~9pgc-dMM~9G zwZhu-q(qGpE#hAnb$HkvI#Dd>pp#$BSsD@-{WqHv5N(fP;@+x8Kvpv5_Trg=bjn5S ze*bX4jX8tf?4^dPBn=>^D93u|I3dbPOG*Qy zgIfm`Pw(E%tZBcmB3qa*b07}TwygDqaRe{Obc-zeF8cu_wb<*QVi zE#CLu#~t4>?_TfCMyb#9PlQ`tT`;Snkocr@WRBS})hX}kp;hGwD$c7-TfsIxa?feJ zm{;;lLFo!!Q((Vpy5eea4>%+kT}d!uVacEDslGP#aM`<{=s`&xtiQw)EM=bRvMywg zWnZN!b<6tnj0JxX^cUlvs9c?^Omyhagml>e@JW;R>KJo(GaINT9$jNSEVHS=Eu=aa zGO(DFVK^S7luIv@%MIOiLUt&7jBlrA>S%G>o}D|3eS^D4hX;3R)hAk!N%rr2*Tllq zR2TE1aw+Zp@=Y2!9d-bs);H9o1dDJI zwaIOIqTdxf9V7Y2Ek}~4iR*S}aZ*JePZBau8lrux2$BVH>XMO~Om`dODbanW5_JB>hqm zdo{_=ks4TrG4!@;7Gb0AHNy)Qdw1#xI0^@+xd8;&Duz3Dg2#uHubpo3&kzv zij%g7=PD=-KW}(4JbC9rFWZw|*2hkI-IpbACg}_b?1=_->w0{ShL71$(IE!3x|nyv z_Fg7&0G9S)xc`BBnBt;5Ee7`_frUQBB%KMOaAaOqKlQ`Da2qEntb)i+1rNLMCShGE zzfs}`9+W|*Rci19*SqBuVw|gq+9)cTe8lw*$yasysra6)7js-o$;w}H;qN9rfj>2U zVsjl*xSk++pUn4Q!V#5hs`oDZ;FP5YwTLtU^$Q zU?0CC*)pvg9X)fX-b5k*jBI-^N&eWJ*bdQ={dgP&;+ z7HFa}*|8?m<k1qCMVY?O4+s^OAf!+8w4Q!RG5fQ$o5AKgct#D|787=;8a8344ZSgnApXpI=fV^!Ep%^En>7o#o>mmBO@M-O@or#(!{!Z}cAESc zQQ;f+noX1Wy^6^DZ0#z|R3@MYg=xK*&rY19u*Aa*WlqMW=cnn;Xe7j|oST)-OpAHR zQy9)h+f`Y;s)>Nfm<3u@uhu5$dte*H*@m(%zCO+~i-IFy9DO0l4mb|d3D491Tm%?{ ze258X@nbA=cCj@}!_(qkwo8&OyEm&ecVCkF9ghqlmMUt?H!jf4d!HyNYP__F%Y}L! zbmsPEwhCFfupb#^eGrsdn+lUI)r<8Z>rdg_4d)i{eo8XS#m%aeK!tOU_bmVlsdarf zy4pgQ*cZjO28KpSWISq|G%l)~LgQpzLF-mk&jF!y+n~ohFF**$lk>aYb7yL zNIe;blCdI-m@<>lBi66JVs*gwlN_%jK zk|Yw_W#)JZkBa0!p0hd_{HLxH`B=ns>*yJwSDB8)x6M9XYbuv+23@PC*3kn+v$fMQ zsbTDp_qKJaB~h?ua)T&K{_jVejQRhILA1OQS^uHnOmKT}OYlnM`=^2Xfp%~^@IGYw z{a_3z=6@rQtp9j$IyeQ~2izMR1!VWX5^!5zVykpC|P zhrj^%DYE~&z#;HUWdC1)uY&8B??2TE_-|Seab*Tdlw=TV)0bJkOKC^e6;8BlP3@s5 zrqL2ebzGXxT_#rF@UgGPx@`qPC(ml~RaTeX5;X?A6Xdcj}}|c>-*i2=gWNv=Mu7=`98@eJ@av4XpAK=U1aTq z#$FkBqmJ-4+0+Ul^$6`2Wl2C$uY*b9^e~@o(IjWgN{d%8vq3&fi^vOvlEbO1GA(m2 z(F!7j9Bq)UnQ@if3Q69=Kwzf{<$IkGF`~&E6~mcbgq4dXdG$`JnV@y3YX~M;>2F1D z(}jS5Gf!nSJC%uis2BmyUXaciCkAcnM~{ylQ;d)KSS6z^<$rYgl?Ib5NkBaL{fe zsf6yB^qSetI(1A<;TfnBfhsHIhG(Rc-gHegTMH>W8Q;>o!7i zF&x^DhfX@I>2Hy14`OP{yf+)<;p`G&*Js^5`7QsSz4L&N>#Fj< z=mY`;LJ56rf+8!H(i0Ggf^5rbY${WBWu(MAE-j^YVjqg+ zW`b7g8&AZd;mE*)VZsEU(`@ zFzmtI)E^CXTwzpD$^WYn7(Xl<^g+nML5zGA@_r2*8RY&~BkMm4JO${y|K;FZa2)t3 zGXK-Tk>EAR`11XK7I+GHGLRnNx5)X*@ApukbN+V)lKt-h{u3F0FE}3@4ekv-fqZ`! z_!2U`^7*|FTnU~Jy1+fb+mPqq1l|ZP0`~;6M@F1X=0Gohh z{bRsgfa3YdkN+n?GX4yh2A$wwa652Y@KWUY)4;vJt-&q8&4Kd$$$$U1;41K5@K{g* zCj$8Zd<%L14PYD`0qzd&25t)!@$ZwITEm6uJjvsBRGy!rJf?rBLi5#0VR!k%?{I8&6< z3!Ek&?3rnLSV69w7SI8WH-7}`h#V5xzcZ5C|T=GG^jOj60T=m%9nY^7Qs$3v+nd)bb{s|!_GBcpuXFQ)R* zs)93FZ2|b>(@9g-viLkRQ{S_3t1rARY_5ghw5DaAI_BuI9RBk?M@U6)hac~4zG4y<24I2?$IU&^y%K12TY>PL-gbH!WY zavAfY(qZ=$hp%ClIEvJ@I_fI-UzA%Bcw=U(9dQ*>P#z^vcknOvU)+z_X7;kwRBqW6 z619fjfBsuLbfR=}u0A<3h5at8XYThvzO?+KEW%evl-1(8ym6=8v!GZ@zc}2Ld%wC# ztQ>5zo^PQyNxPKQ_8(z>H4}h>asX*AoRCF-q11?_;o-raQ@0GSEyYQdSAd5x**G(| zBd2-IsPJkh*6OIbeM9VLm&l?;24278%O$QaQsxhd-g#rmuf~fiG5zw_5BTERbQ{0Lc2_Mep@Kav^omV$T_y!5eCL4mswNX zk!EM}(M=0ph2_T9TeakF>mz?d9RI`!E>Nw+^Y7vNj+WO z!{c$gEB?wpygV&xN^?@ZkP0RLpMr4tFl0~3|CatLZ2f+)$x!^SL zAaDxU1nvs%0{)D?K=A^;F1-PGEvN$d5-4uK8K4{78GIBy!qdTHz-6Ex90>j$y~2mU zyTQxA6+pQI&H+O}=Lzl)lsDk6KzRe+44wg=0-gk(2+jq2z$T#h0vo`3Z~#zj!Y`nU z_!m$EgJ1*rA$o=@!4xR_*(E{@FFk=9tsWuUqj#UZ{XwL zF+lMH4h6RXHw8aJ*YFMS0q}D0H1KF}DYyhY5cGqSz)ir7z>67w-9|rgKn)Wvzt0?_#4`4*W$Q_dehV*nFzhkHb7U5gte6sFzgy07_iQ+n)H!5 zs>>-$Tn@}CVNOh;sfi0ypnT5;-cgj4L|esWghC6BJl{|qv@QbOQ$ss zcX?J;T_m)_L=67K$?TbFJme-Vxkz?TiVl_95sfD*A!s^$^hk&+#YSJ>X8)qIrhR=6 z!YohRM7<~ViP4|Bhjsto=}8e$3m4~x^{E|<*=AG43MoY8~AIX6f7yogGk7V5}hPPVyd`L zl(wj#D}UP6KG(gWUIFd%EiDnVm$K6BJP3hi>~v3#LL#vC46Yy8x_)r;;08NCoF2`s zE>||@=y4q-hT_!9qnwP-7G8m(<{T$OP$fD!zcS+5jP%59-z;4||D@a4o&P_i+x7D! zZi||+<^f>-&O~7_jd#q0$W+w&mv?wPz#IHYlom}hzis*-^v&6Zm|HjEEGRE67ReSC zSfth^2aIgzAT{f?**4;)wel?^vw<|(F;>n!8l_F#CtIx6dSpG>W(gjYXQk9dO{`1m zD(g_fMOJX_pO ze8^htJT`lRBWt>GXSV5FyDcZ}ANlFh*5;vAqAbAjQ!xjAPYkZsLy@g#d?ES&W%0Hv z@!+2RES%R;Z)gN{0X9+t?93n`EbO;BEBt;c5C%6qVyvbIj4JFCRvB*9nV^;oYTZu2 z{&b`VuSiQRw=ZecF(sREeYv}u?mih67_jpJMY2Yb9c>pHCxnKxv0*ViCCvY(M1~UGY;AP-4uovtEiWhJcI0E#7 zUm){89qa`=z=Odpz%P;Ue+qsAz6HJkq#Jl0h`t3+;QKx>3C;s0@M+}zcY!f*Iv55! zz^%cTk@?>Y$4E(YV^WY74l-S(WpG`<-Vf%&(G4_#5NWeoNqS1?-9BVAKa+-Ku3eA2a|Ry@4Ij_ z`;V^~*G+iNStgu5^$rL@LBeL~!ky1JZ9SM2GlxJOLp;A?E*dFIsrX!f&q-PK+(s!X zzj?-L%`T7)cp4`R7qEg>3?C8?kBMfh6n%+(NB8_{&9?Nq%``;G^gTYlZi2nq%5f*! zR3m2h+#=b_u(?I|EAuLeJZ9|2=<0lSkO$Av5-t)&AFmd@ixq7zM|~NC;CV5)t zFy@RZRcWc#s^k56xkj?B&ot4qW);vpePME_=Pi5^Qz>?I)+ZCWo330i-{Ma}TXQP%*~6C{30^h2~ZXd?o(gsI63 zC*J&%BzXVTG95=f=jmP81~t%r)q>@m_jQ+(*3(d$0(a}S^6;+prznjUahFYpTktFk z&qtYF;gRZO`X&;+CXfYEQheRG%la1G5T+yqT&lm#BJI{Q1exwDv1RQ|vv%oZU9m+X z8J9?@satMvqr6r-SpKzX2b27N7lgW7NVY@%KR*a~A4Zn{5O_7X0z45s0Xz`=6#4xc zARE8r`x=-7hl4jF!@mJM5}Xbs*KY^60SAIlBCB5s6c?Ze90oeUA>el4HsC<;4diy& z_um5~*DGeg`CuO05&RxG{R`mpU_Y1u$`hct0jGhTU>F<%?hJIEKsNpfa651l@K7`O@e9P+tr`7Z#E0Qk~YK_j7`lSTRahxJSiX7|S&N{@0fQQXh!KgVpHHlD9o( z(QEyTTeMXh<7)|nBlb4y;Z{#(Y|93-j(3NaFaKn{HP>SgX)m=`m#yY5GI`}1qYd%0 z+1a%%-MQ;UlcR#2z03Rt`ACc zt*6p!q21Q>diumL59&@*TlZ9F`uYa<5qnt*AF_96lPi{)m8wWTiubSRzyyLX51%q{ z^l`_Rw+sz$8Q8i0lsH#4Q&+`9!R$WqYjzaU0)e@<-R5Py!(t#S1eC?GVT(~$&=k)x zkq(x1mk6*q?WaU%(BIF^>{TYkipSL+$JKIfX0g|zIjM8HiSVO*gPe9$oF6vg}UV`~^$<9YpmO81ifwr>ir$*gz4{HoQ zsx9Y+6ck}{B%wv83|c(UwZY*RN= zcvZdh@lS%K2>`|k^xA90TqI4hHf8xDj{*dV$A* z2ZAo}J9GiR0>1?R33LYFBjCf})!?Pz(O?|h8vFrWz;D2tK#WOnW4`|wUBLIje}HSi z*MM>gUI==@ZGm(K6~=rR^aG9mJwfp!rcCl9vWX=MvCv**eL-~C_l1}&3$i6Z$WsITh4smo*Jx)~>P|y4} z5zz4Q-c2c$Sz^~AmFW;2KPcp=iK#jtXMLbwLsO7)EL4vk*8Lp5v)HjClj#nu94c*S zcfP<7a+HK_ljA+IxSC+6xHKcBf#LN-Ll*V52eAatFG|Fi$|>ZrJrXL3#M(`i(jCL4 z<9my7UWS8G#B)`$vdsL9@b|@557?c7N zw%WI}W|zvt$FK?6%OvvHwD6PI@8ys%Ul6^rEu){9k0>BRD{BZO_aCIgutt z7@oNkm77U<`M6R>cv)LI>bU+W&ifV}WJCNhw|S+P77fQ#x{a&{4Y&Sg8jg@?Z$5AR5%|lR!1yweucE?65=y7F^ zX3dRG(xW4~7&Yhivn@4qB;~UE!f|t<^+)1|+D)r`_MjOecvbMFVPY*1YB|JUE0ksC z_U4@y;}JA3Ql>jEx7bG-Rja=aAtxcQQJ+uNtUzv*IZWLt3e-|xBmn^$Qxz?pN)*6&B%2unHak<`fmUd5KnX}e=ro9vHT?n0c# z&97o~wCKK}yS?kY>_FV@kGj(HmsEb3E9$KLYx1|{U&NAki$rh!jy&-z$b(jc*o;IU z8*jCIxCRP zd}?-nF%!V!@jGUm>E5?0$>vCWJvwlj;cwT*%012Bvq~ju3KT{M5xjMzBh5T}r9z8_ zSIku=Ejn~FL4DgyDCi_BLmnFF=+=bsXrQ^pcoL{!A27ACRWOw>3ZDpH`Q{JqT zq^*Dv!Y;5#rofamJpQUF>+W#DO9@rn`MDM{!M2T_ds;XKh^OFrZ1cLu4gh&>7^H#l z$?SgvImdp*u|F_8TlK=OC^MP^$NZ!9ExHXWk z|INW~kmZ#x;631F;Bp|}{&`RX4+XaZzeA?~75F94+5cC9e*upJj|G>3qrr`UWdCP^ z_25?Err^EE@{b35!TDem+#bmO|8*dL{;z?Lf;WPH1!b@qYy|7T0O$eNAiIAJd>G6F zo$nt9qhJJl4q5&)K(hV0;7o7^I3C;`=&b+mk>9@p9s#C66?B3BMt*-QcoBFWcq(`@ zcoNtQbiV)1$nFmXCGcO2+iStAfo%Q{0J8UMJpa0U+(2d>vuKkY6)A75PQqe#W9`$w zteVcTQBA1qj`izGW25H3nWne=iqE!veV9{-B z@}04%%EHOyF@1gJY#lnswHIz_B#XPLI>JoNW}XQSL5oo??_R(*S#~boR2q=f`tDk- zRiN$Se^%vS6)PH}8#E5FZP$y=!3*o@U$MR+pB0a})LiwDw)UuAQ4b~w-?qxieDKtx zf7_Sqdh!EBdeQg0BxpeG2YtAaX+gGXoKeBTXjV;KjvEQ1;nSwO6an@zqP*CbaU?6 z_Ihp0;LcOFZD{PYV`8UaO`EHtzN6&T-HE`glJ8J<*q6wquFH}8E13w!$8-Y3A4HQ$ z-{&h^eAe_|O~-i`RogLi*1*o8ZCkyDnlB~!97%6lQQTE&jC2g$)ZJcDBo}x`XroC} z%v?*uVzJ)3OB>O<(T&O!r))Dc0wwbp`YLt?QjIfpC4#Zb<8#NVB1Rx7MXI|O)p&)f z8&QfHYo2Xzcr0;y=ppk2C!P*b^IYJ^vN^4fJl9e%vpOrBe3q+X> zQVh>krOl_s-fZQQLt8me=={A@OULUJ(NL8io2=3pe_$99PQMMfX02zNcTrH_tEXrb zHLenrmie#uNtG8=G_J$L@e17y=!)I;C6vuT7 z)AHlx-k%K2^T^a?|0}N2+)tTmL7IX51(Ae$g{=w*Gl?j-^oxE_w=g4SruqV{Y~CeS zee288exAy&HvQU~EU>^f4TaT}AA+N72=;dvkII`t*j#lqYL+X$kHExQCMn*7R|#I0W>(@kJ-R7W z*p`58J7MC-?W_Z1l<$1e#zs#$CDYayHZDI%=RY%vFlCWH2rHfMZ(Y%1XVbW_$AlfH z#0UL!Egl=_>aNbaqk^tD$7a`~n!D65Kji!)tJeks$n6C!t)8!A8Jno-?TrNu@b#mp zw{kO8z5f|oVJFX2Kl-dIZ+*>~Kv4gjgfX@^T8I=1lw%V70zsqSONSp$Uood8|07a% zyhySl^8ZMX|38V${|WFK5YPSVoc|ywfqR3mBImyuya`+Z_5=9@#54Z#1Nb8{{)fQT z;41J8a4DDuhk`qRzarm%7d#P2_Fn^Z9^j7PVDKkodddH?_g@MQ2eRpZ4d@KO(}2$Q z&w=xR&I0s-KO*119y}a86r2fmft}!H;FrkqI?pfp{{>(kYz2QnhJOvX02~KagL?tl z?SF;r{xk4X@By$0wu1w}Z;;==3O))x3_b)T-}i&x(#h`wI=8K=8clP{cgS5snm^q7W7#7+-9sPA{wOTOjfC$n->|tNu~D(K?qpm}(v6INR6s z7$Q1~cK%vhnv~Oqm_V@%nr)-FTKKC;t{jTAlFT16J}6E@CYnOY6cIBW78t!`0D-gb zg|iyzRp3l{AiyYy<15_VGw5kC4&W2!DNR?vs;bF3^NX%^KtQoh;pB!wYv+E9p%5uM zS4iOFrHqsAGpH`)-hioqldz@;)NIQc2iu6fC~cb?k0?~(5Q0IeoV0!!hg0D9buenK zsx;lX$i+1>MB)^MXfuw36q8GHqwZLb^IW_}q9~?334im8=89lXMeiHPtsU`}H$#ov zLSk%gkLC$Zo|sGu?|eF<7YAq1B$g&7XGU?NGNmGo;JgcZFn-^>jjU@U)AE$!0F30! zi75)J`TiN?DxdCR{&SPRJ`S3uTrEn(M_*_Pt8XM%=W*%v{Oq(oW3c;MKMaf}4X@exx-%Sm-^p zX>;GmRIRtV8!kdVhq=|L$jVWw^tMA@hc|Dt_|SHSFod{v+N;^IDzl@pXE9stDA~ev z6;)rEpfD%eUR#JpyaQ6UZMNSvF*CDNARDXDAn_oS<-emxGDfmOZ9Vv(^;CATm;ssR z^pV|BW;&;rkuS(o%KF(FZH=mf$Q<;|AJ&_^JccVPk3Uq&=Au+?pSGqf*Dd|lSo*^E z^t3HQVQmIsIQ2e(7d-x0EReB98a}f{YQXXrsUF28W24ogUV@@|C2#KxD&V; zcsFwVW5Fe0JGd=SEdL*a?}G1uH-blk2Z2MtUBJ7L<6j6a2a4f;DcB9R1KIY!iX8u0 z@EPz}Am9D%;1sY4$d6w+1Qnm}Iba&ZIQ>WQ{lAdu-v?d=o(>)ZE(Fo8zmxArfHmM2 z;O5{L$oe|-|5>0j|1SeC0#61LU<`}`SzI@hkQ|{v?}PX{nF7^&`%E#Fn?)HHSehodhI_>4%R@%@Wi2SGSafZ> z$&u0BB8WMZ6~emdmKK{`cP63c@8|ssNC=XdV(Ua+#NP)lhidYNGyE|kLRoF<(*I0V zSBgqI+x1PNFu`SEDldU!PY==z70_1(Qx(qcPmHaUi z?x}LL2nT3+jKV+>LU<165P45p)nYp|HjvEne02&$q%kdCA%Tlel8SK(Z;tDtN9KEzI6sv*S|!1#*=PiSACq`+~yE${F+gfwA+lRT)>Y zDiEv9?1+oGvF9Q?68bJp9(qq4Z%-^GR(RPy^8)ELan$*4k98|d1-es>EA z7c>+6kJjaHH_<&jYNaoTq9G0XGHAKu@V>wcr1!6Y!^gj@xK}APmTFm;##((g`7<(a zY)CQB_-~M}oyMyl`%fBC-59UD)tO17s`S!mRJRUp_JOrE!4hM?)*9pFjO#fW4 z4QvHnKxg+Ar~h?8Is3+ea`hbpjt0L+#{V>UEKpqj&ES^cJB;0zfW}ZWsgckpa~1lr zuxChMkkTu}-0~S%uQCe?Uih4F?io8;C;$S>nG+FKFEAIryRe;j!Hxg#T=3_zZ9|)m zi(70uiV5g91nXnA-6>AWo-#WBksSM~=B)A6(!ysqHr&+3qHfyxa-Crj+k%eOe|9CE zN|xOUvj*L9w~Fr4tUpe3w(i=zS+8oX9b9MIOMfZ!hHgd%^IwP5!(Nst zz;&_3x~>!ImbkMNnFeR_!wUz@u~VGw+{~oQiKU#-x5QnsILvXjV>$V;2Is`nv<)uI zO`5S`ywc^rmCvtM_V((OJ_)GVj>Qi<3*T5$=nI>=oY7peD7Z9AYf%#Z+u0mqoEj~b zvN`({3%Y7wRDpNGB(n9i0UKIpb=exFi=9KS+}Ag)7aLO(nt*7^9lbQp)J*!`zCPd5i|zy*3HAbQu{QLtRVx3=Fho=@o$YgV za*N`CbJMOYD=XU)_J0vWfjl@OWWevHaCpO5 zXc!ol_CH>7a}8B0IJq{oTdtRy?Jnh_FjY|Hi71GVFHW%Nxr1V*aW4q7yroiXdB#55 z&*Q$Xre#9h0ht7cPYIW@flFGOoRUa#brT(ta|kVlt*e{KI3Ew(`q-^O%tWNprfbid zA3*UIOGWA95O+-aC|6Wi{DsnO01rU0lU-6S^QB>(}%PLO%QFp>#vd zHo<9u-6g`y*ye=;PH$6^>a{75((qHmKTuyYFp80nP8p^H>u9(&{b0|e{`aTSpUXYR z%*yPMy_#p*=+aXe$+vT2(V7`&UsS*n_rVjXID_LVI#p29B&T)DX*qPY{?ne0&F1^H z)37x@x~zUJ&i_mo7DL*2?U&TEiG}e_Sd~5BhAvIxAko)UnmHe_uw@tPWMd8OcjcAW zDvdwCtDrg!mWCzN$e(VasY<#wd2h5-S0!mZ|Hgu{s#s1>R6M2!TPmHb@6yv!l!%iN zP0)Am+H|?7-{Qt}#i}13)c5Q`of{5pofR8rf9U0ABJ8J^?!W|4Q%*pgaIuz#-rk;O5}V$p3EwZv^LqlfW;K{a*>r2FC-%>w7n=o*s$tz@?)Y6xRqwIV!2kCHF?or8}83F$f#8D5d|hO(a;|5kyM6o&0&~2 zg5D_Ud3Wi4y|$^m7f*^%AL?&ct;#`Oq0n~5u4?TDj}JnEvc5jm9xV(`#N# zznv&H7X>->^8im|f`WU8x6}=3Cd88_NDT$4sK6&&YY$-Fz`*%gmb&tnkBI4B#9eW| z(qWTgsZ8Zlh=}RGL+cAMF-_xx5NTS}SV(J4R-rSpxRQ!)(Nc+(3E_0R_gIsBtSSID z{79>KK5_;{YlKY#NXswqoQ3{REM?NHAF!M%Vr{9*wMlDy62wfHB|a&QW5X9}cw0TH9>RfPq|FX(=!=v*R6Wv}#wV9BFFr!W{qv8|YmIZo-F{GUBfj8njFMDm zvZDLC|8D0380|0^&J5)qk&m{dAMJBGixI!uoGy%7|)nNSiNg=d8m$Y%$@6><7OgGkNi}U-U4D zx_prx{4s+Fei43^a)QY#yD!=VGS9@$XkE%zruZ8LX{Bd1&W?$sLqoB)il>JPF*E%H zlI#;WuT=H*T)&eBb7*$ZMdpUE`}?N7Sls2DxF@QuQbi@rN)amMEel4@;B8u>mZ5jt z1}T0`=gf)?i6mGm)8hT8Q}XGL<6}O2vHy6?<$pxkWdENGa^lC4^I7zYmp=YWmiQ1B;Y^lyO=f|r2D0Lk%p2H&MI?*P%?{|vq>M!(|r9SDAg zjQ$nyW$-5OM({-N5Foq$-N3hy(LWD91g-{GftP}ZgHyph!C#Tt{{rO4e;KF&<@Or^ z4+A^F4)9=bD{xEjpUCz90;*sF>;{Sh@Ka>?J>bFMC~zMjx&F<_?=J%{0wZ7-kiY*9 za5Oj)+y=<*|LGv(2X-QEekEK8wVs(jD7pyuWTC)oA>z{+W!TE{0P%oUhoBNr3&ne4 zhMF|Tk|jY>U%VKO?$ld4kVNd1uxxYeA{;l{>)ut93nP7f zyx+^&Xg75+@fD3^77-oIb(7s?%;`=2$_%kNP9NB@b!h9RzCzM*mZ;8{!153)iezWUCLyomGmyLRGzx;A!L8d!Ip_L5 z7fU3I>8;_AvUP%y124@wAmqj7S})8q2@2+%M%n@1u$H6y1A`k5gqcF!%CqffAE5-9 z1siC2>{IK`oKzcH@PH0kXVOU6V`U0La8(>gPac*#@s;ij0VDfk#7GN<=EIp{NxJ=^ zLlib}%Psi1(Mj`|f+tqw1SiaFT}-^W^2Auod?y)v&U73#lpOOOOSN|l7i`mURBJUN zP#tmX3HLkxc-M(Ap(jS>M(IR$<;h9-ADu>wL2=DUE}MO$ft$Xw8C}L?)gTU7jL2AH zx3`9R*H3@iy$pNAo?sunP;6Bz<6@Dcw+_m4+>&3~;r82DsBi;V0STwS(* zqw$fo(v$^G#n&^yt!nB8<&O2{ekjnB+#8D=bkxE4tHtj5;>^;b4*d{7ID|CmdXf+K;cGV0_{Y7zB@5$nYl$3aKf6V(b=h2l&VU1S*fLlj}>ip zpe&t$mlYjqm-i$`Z>5{#+0+&j(++(uF*W0%P)f=fQdKbB(d^^qTH2nwU0t!%?aZLu z-CD58O51X}gr(UR__y5iPR;FoHV~?;O>%}SXIo2yl&-0ogrlB!C6OPcO*UihiWT#b zu;=|@j(!J6D1G&p9#17 zyCjojKluBiwBSYg)x0cGkUAoj&@8ZKpCUNe8X|xpq&_o9d zTB6R(`ZgyJbsP{!Lp96PSNf~Phc)6&DiF4KbN!TGQbHP7zuC6$P+oLXXd)!V;+yDNC#xEdEko-*5EzQTp=by`Hi;zuaTT7A8>f zq2OTPcWQL(vYZMc5exB?gK{NWhC~+dgfI_;+`VRi46hT_gG$N%kEq#kYvf49|GO~A z|MLC+0r)<67kECn9P9-ZFajP1wt!Q?gTN7>2OJ1)1(Y-JKhO<)3OpA)2TXtmfd_(H zfghnG_!xLIm;qH#0b@Wp1MUZo2loYkLU-^^AYX##f<;gU$`PO(fwu$KphuAZfP4q` zfm6Y~fbszT1iT+8mcZkIVgz=A`+{48-=jbH4)|~I32-IQd4ZRMOTg(s`h|Cbr+_`+ zBybn-NAw2D2k?GyHFzdC7pw=Xzz@(Fd<96S@ImlQFa<`yIiLqfuW&0MAAujEFHnAf zSAt=1b8rp1f)9Y#fmeY?gY&>@a3FX$1NpDuG2jw#5;zQeg0X!Ycms&~gvay!ao~Qy z#$GIA3csQaBqsrOqCM2#g%~>s!BQ&nbC4rt_s!zS5PwL%diSNvr%cn{iuIZkJV z;6D^lD`D)M*V|!N#oY65l6cKYe`@?{p1t>-KhnyH856muD`iCb$P~2Lk;nqSZAV@& z&?ts#KO`8l=xgW*zUA0Wt-p97(+s3OYb>xUv?G`+(qHv_@{;X|R3iObtW={o6}I4p z25U@16(rd!?R&Ag41sR}OEgkUo~bMHB?aYW5_WuVBjU3UqnaO8`S zxs0iC$+A{*u10f3^)4F;hF%*Fk@=dXYiWNVnh-7vc<7@yT5T2V85kTs`ncm)yZVUi zf2q7!oY8YOzVg7%ojc0wcbvI>XL-xE4THvgstUO=_flA6?q%3~l{HyWsb5X^r9#EH zx2sRAU%aVOi?bZ6QCn60ZQIMkI|uAMX{x4vU>y>5Zf5#0xqJi?;x54BO0Zd~9x)>t z(V;PJsJdR2o5|cX#ku~f1Q}P9ct8L)cbm|;&`G+bmW2zjq2snRzY8q6X0Kl{eL&-V zlGL0{z(!=@0IQ0vQde3`n&Nk+PS2{g;s8+A&}qz5m~^^}O)vAa>a8wnyy8QUz1ibQ ziAr;pR;7inDxv-^qE1cE#JE%0A1!Xp-DqE%-Nrl1vlC_dj#p=Yw7T^IT+)fg814;! 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%0D4d(ab~Tz?jBS9A#RSI z+x&K22=~+lUx)?fe|7?vbGfAo@xI)zx=rLL$c+8$Bz0!doYbp+3Eo-m1ZA;ewF_8- zT#}~1Y=OE++h^L39e!85+sLjL(_7mk*EP#B8NaGx;i__s6X=A%EU2K6I|*`M{3Um< z4INvQPFkuP3xDVCQ$T`rd^r0fcSpJ!&l9KmnAV!I?O%8r(@BQ9`hauMzo@+Cy|LZm zB!njBwxGzcxbiGa8wsiqF4HHTyTJa)xqb__V<#x>R$k;Ine8QwdOi1#tkXMp+w*H4 zplvU*T`J6ePG$g`d73E}ryhbh4}VAK0K2PYu-2URq!5si6vE+vU^cAIA|%UJaAah> zEyK!4*4_27KC)ff+h-oC9o`#tGsPn|L_(+%YHxIX{|CFlt6t##d`*=u^=1_6FszyvcPs*rtxJAuDnxzxVZ} z(tV|BP<(@O)pDUkW@2su8;j{jK%1+XJ2G}#Q_Cxv^hqnVGGvO1%``gFg6m5^kuS5T zmcW8X2G0?QtawjmE^%=#v| zpjj{pD~rBd)=`}WWct0}aPW0x_Gf@iK=JbxqwnEh z1`Gkk=({iY0kZg8z@=aS+yZds~T84z&FwOpG zg^1Zzg~&#YuQ=U{k_#)-+D}F%Jve^Jve4O63da!*rE7@uZ2`O|<#jECI)lOmAH@l_qw){QzLcPmAm3@R;lY}kDAwT<`*WNr!SyU9{KpIcIxaF<{#eZ2MO-1)?a>j>{H{)33) znQ)-t1Lf^I1~(3!VcF6Hx8uF2CWc$g0*xa zI7hN=?Qh=RLhC)*>l~CV=6O~3_jhI^-;T@{s%!D?RTj_Z$1cfHW*KHY)QNT^8#XC8 zdwLPAtCLzr=XR?PJ%Ym-JwZI}*}|yzbgwO)(X&1>Ai3WLrY^9*ERhU3DwL4O)@rx) zt{Ef=tYz#(vkB1)38@6#2l?)w4qfss)tJrh-VYa@_wW&JY z!5Y}eSm{-Eus)mbqa!sYimaoWv?NX|bcZz{8*;@6an+3zzeoa88_(7f(fNDU2>wHy zxpx|v^TbW}=IIoCR12!;t3(fZ6KkzrVPTgdJwguCVd9qbD5le5ig+iIi8Qxk6qaVQ zY1jr`s$Ni;+1;Ejk(23&LtX*hv?ibU zuuz1bFznJch*;n2&!m!UckCA<9%FlU_|x^0Ts%ar2cig-0-XhU5i){oSXKIRMGNy1 zy<*~CkbasR6vml_bVl=wF>fWD)6O%u50-aq+dSw}m7F~WYPE%_%8r>y=wJ=9HbVK@ zl*dTh!uofess2UMqXn&B7n^B3Q}p->jo0gV*ax$55LIaODzxs*yhq|0LNX!FO&2{^ z^7AbQBk^{TeL}ZGMwN%c`_R}u{~CrWFE)mHir9vG0qu?ney@|2 z2|UfreKzY*U82T3)y{2}TKLQmoyan&_$1B@OJuZ89CG$+PwNmI z)GN;ABs%j_->Eg1`6I7n((OxpEGNng0{I9=k8rLxNa>m1PNwln))V;KWE3>J3G*x( z(XuQnp`_lNK6I^%gcb-AV&bpXYLJBvY5zpO(%+StPK(w*+5eA0usj@DQ}X{lNtt}U ziJbpc@ULJWm;=*b5}XIl1&4uakng_=UJEV;4*~M|kMRKR!1vpOgMeZLE`XDPZ2f-# z-vD0+SAs`_Ij{vt_WwTe{%66vz+1t;fJcE#z{Nm0`|bt)jGX^H@I3G+Fb$*+*aA)f zF>c^3`TkAh{?~!0f_*^e0Ui!Ef`ftL11kRi*T9#+7lCvGRnQIAfP=tK&;$Gcd><%J z-!ff!_f-6QEcCI`iKR4gxyczZcve`~cm+3&3;16<`5u1xJD- zz^%Xm;9KYmUI!izCc$nXfBtpgP;f7BH*i<*SH|>h;7#BS;Pqe%Xso-z3O`HC`*_JE zCJ4q^NFSS&pRCgo@>;60#L;L0)Cdzm@Gd92*pf zry3#dM<(T}JHKbDudiwmV{2WLwRu!x|_tmzK*oIZ|3r!WQkrsI_wYauoJdhlv z$m<=<0@Q^TB~xd_auoJU5=kxSM+)kZ^b-A=Kwnx7UHx7F{H`d8FJ_iUuILC(O ze@L6uj;;W%Sg8mji{X76NZEN6PO?|$GAUws*SZZuN{_zd)WIFW(VtG}ifykgjE+}{ zyJ+E6*Emz&sw@skmp2a$?<}t$9$qU`0@O2KR-9vFwOV<6W^9dVTEl&oL#6-3=v54j1~n|rS?}r<=Vn-?0#M`A2R9Yr4Y2#kPP1b z4yu9=2nl+pd;FF(CnW-gIh)h`)q03<&}4(VYRrHk@pLS>2hdp*jBFI^WIlixlGmn|=ZXQI~x zMZ=ZW>{@8M2u@(2@`sSRAHG1~Yy>)GaQ9H&>AO~(Xhq#EVL{rjy;*EN|64W%aT(-L zI+JdEkNy_6tF3oF>S~UglMdK*D%C^D8@Am6_n)GeIAtdo7y)d^6@KyjV~N zNr8E6>D21FQVfgDrW5s*PHiL2O%2SwvBW2f}e6V7K!p z!Z{p&Y>&SQ|G&uPMw1@0A;HXQn9{`qL<$Y&xk!3yemQ7Bf8RH`{L7 z>T93gt@>IkE*w3|PKK5w0Q;7n>44(MQZUN@3_F&OlxvF(~Nv`f( z^3^NfI>b}wb`G2BeniWLqha${dfV_hsaHUtYi zPhOak{}EL?_6Ga^X+bFbAhNw;{woH+S>Q}?5coB6ylnrk1eb$x&v;5hIHrLEfrx9-fY=sDAoqjL<-HagQ+of)t9Owyh8 zI&Je#Dy>GKog9bx7nOm_Ydr68Q&kvLey{qHK1-TWc z7USb{ly3@k3k9PA=~^xMZvRD@Y?swgiV9p<<*+bcr2|UQ9#iz#8rr;haMQr%^5$*p zEm)aCmksaSF)*}sXL<09^@H1Yk|rWQz0J0hwB}R-!7xplQkTupsl{x5vZGba24Kr# zP2(zPu?AwNyH>+GJ(M1=4glVvX{1k6jvth6Ix>aW%B zxac8cpw*AlEw1gX={+(-1S5dUOR*M+CiD_g7AJn`4R`0@l!7LuKFugoe0GI|$qQ`p z|5Ro+OLFu0x=b{YD%cIId1>Ajh$I$;m?WafM9WVvr=Dxn;W)9P$=EGly57cV(~@|` zR<=KJoO;IR+9RH2&RsB;O=)OdNlTN^SddZD^kl;b)ss{Gy{gIWk#agwCGGS+WwJ8z zgVGkVF=nnSPLrATr)kt=?b;mFL+cZb(~>anlSEpj@5vP+663O=_E@X3>{n++b#!;r znVnyR8hL*%uQDp`1?Sb>2@%iprFXkxyQkmtndRS~U*J}QQ~@;RA~P@so=LZ%(79|S$#d?3Q1g{f){@)5&Y zWv#S=(x1ImNWPsu>#|*{PQxx#=gYh2W~REbGC~6~i(cB!)6(R?LAt*u z+`rm4+$CS!_*yN1-d1nSzfU(7UbV!Fm=peJHV7y|owsLU+dA&IwU)H%*w_ z22wu1N}j`fl~Rc`eLpuZ`+<08y;@c^up;pPHk43tM`OR(MNhacN`2z~|J?|rAD3K; z{eM)_DW5+e>;FdbK2R)xS)dqz%KJA6evM535>N$q1MfnfzX&`WOo2(T85{!c0%Xfq z48POCdLUo^W5Chi_sH#v;rDE?19XGmBfracU-GBNn8Hk-}9Vv?zwkn zB*k(2>3=@?#hN?!o_p5kJp0kPe2YN7{_^uz-n|EaI{=-}_ipf4;2Q9JAe+5%?e&7q z;4a|9=n9%Zari%H9KHdr2O5)4f%Sbffg({VHg8|d+4k+0yftSVQ^tG%af{}B+&)vQ zMb9i>t4V0n#FTSfn9&XSy32n&`g`?mAwnMmZAecY|EECd!<+*XiqBd(6AzjLiIXFI> zM^$iKreY!s7BCju&{$qjSXw1uthH;D4$Lb*IJ{@CynbEns`$zA`NihgOrV4MVtxzL z2VvB8tY7(eSBCcY%b*7zswdUms+iK}=!yt2msYC;Jy@>8==e;if*%nQ-(6fUz1S`r zBUpNixg@D=$tN&6DXET?tK1jfrO9D8iWlWZpQY{QYDq#Hx)_VR&owK&taIod^bj>m zWNS`!77P{K8^yveEzZw`DnMgve2nVX6f`PXWPWBMoQNht-0NCwa=L$ApQ4z;#cN#; z`c0|VJeh?pw0O*n)G>0!&wHBRkO+>M-cij!Ml-LB!><9qr zp51m{Ox6?LPi%p%b+B;6uv9o5bKbZlCP(Nk4Q3q9$Opv%OXQ(<=;#L0TPprG9zSf` zT{e`ZxtZ>=tx+RtjW1u0!`r!m>Hc!tO0R0lOH=tabsnQTFod_Y{!&c54QrGUmi2#s zeq?~`ZdHMnYMdTZ;B)NWbTQSQg$LzgqMc)&6V7XBC~~@ypEjT#(!xv&0ZSXi zFOfOC92%oIhAb`>w-Eiy5PJ2RoO?Jofp9f>IbI6)rsmywZ$`S}@cQ)6=FOJ8sj4E>y@c9^VU0>Ui! zt*g_N$vZ)n>F`;r2hxmjsUcDRNDafR*;K8r%@q@PEM!4T&-W8LB?vZBnwf4?S~jy( z#6p9jMAN2JztkC?^`QDrSUZbu;uRcYVns4G7<Iykd$PnRx&2WrGmU8ZIU+6mh$tie#3(wYgF~pA;A(ZW$~s<3M^~GT#2FdIw=v^w zW=mbhf8o%nG*3Omxy^QnXB!_T_yD1fT3%5n!0QoFLgP*GSFu1*!Pu*z^q{NtB2{eo zQ2MUKMg(w-UtqZP2lI~0d(%)zky7okWS!u@LlBpzXl87p+NTjYru=av%n~LA~*#ocmEy1`;qk*fpYSn2Ob6P0F;ma zzrfePSHV-j6u21d23_Fe$oldRI14-k+ymSN+!$Ylp^x9_m>Hm&*;d~xyW(zv&NX+INe zuBIuF_Qh;2WFBm#URY&1EUe;~MHp&pF{uLWT|OAC&F)Jcdf}F$x|Jp~UDFk(2thv)CuvJ2kaBxF>vPOib{9HvCpU?& z8JnD$XAvet>WS6eww5W|)e|m3*6M!KmQyis$XFSIY zFaYK|Pvr=vLC`(O{*U@oB;Epir)S{}@CJ!d`~+p-JU>-*522`JGMynBhsWkdNe*NY zLU<`FJB9SL=a``0S37V1Fe3CK;kl@KDBr3^%@vMe?+Iix>j7LNxH85!u`hDhJa}=8CNH_*ZoSrq`-oM2rr{ zec8`@H;UV;GTYH|?OosB?`yQak@hKj6P4o+k3Wwj7w$i-l@ymAmQKk+y@@BsmCnB= z!yKG33j8G{_V8E8--n_XSG0(s*DWz<&5ocNPSf&t_1PC@Y-5R4 z!|*%rZ(h>n-+Vomo^|G`{ES^|b1&PgHvB_bNMin~>d%pnD7`NUBrb60&!?Z-yxONk z>oa~m9461tZ63`M2(moLgMjo99w7bfBB@#ZA%qm?F+wsxU$Lg)vngk!$&T{XY^c@F z-#ff(D|3=$i2eN>OwJ#T%bN3Z_3l_%UWDm8Ya(GAmo0?Nxui+VZwiYw?p-t=5yy2c{B**-9HBx_~%gvw5yPJII^K29}Nqh*}z+?sPh^UaC&ot(0wxwaXS# zKjV3ie_vSUarQG~&2qGDN-tsoDoD80xw$|`qgHy15^ zYzzYN7?3b|kCtOScKoXR{}HphZj#)I{C|0nJwJxL|6-tg|MvsmMZW(I_-pWba5Xpr zB>OLeC2%Hq6!-_^{SSawf>(e&U<>F6j{=GdI0ov0Y|_* zI0P;O!{Ap`_{-qaK)C@w2A&8S-~zA-{0sVluYd{g1aK+PnSl2NcLuiuw*p<@UFZxZ z!DGSc;HT&e6gTi!;977Ecpdl?@O-czYz21#w+FWdw*p^8kML*ULU0%GYxD>I5Bvgr z3cL`=ui$K;vj9#7r-1)Nhj1;J2K#~Z3d5ie+zu!=z~{hUfFnS80XBhqg6q*Md;rML z;8oxPa69l#^a^hTPY09W{y<~=>q5UEx+BJDVWr`ju;R(y=682?m;K!Njy?jc6%9ld zFk*ld-w20&;n1GFcxN}ZZ*AbRy;0enl8}|Z2?j;Hqit07B9UyBgpnn4o9XYL<6

z`kgbl+E>`4)GO~lv+KdQ2a(mTR{o*Z{pe7G?IaAc3gpFxjmi6=^up|y*nWZxRYxaL z<^F7Qx7t9&6w+hXjM9B{B(&O#%Wra_-Tl^maLUw7d|j+Wm7RLHC3Nq;qIZ;>b1Jkq zhTb;@%kM1tz!HuI7bCkJAv(_l(&6CNd-hHyW!*=W~?E{Lz_wwuGP4k~B z`q;m7befXBg{;-}IMYnT$&NnqMSy2Q(Bgy%0XS*&Z7}|bU?>=v2WOw7@#=y=? zh@u=s z%;C?6XSEyzkPp!I$Y*ABCZ3z7b=ajz7kMrHDVJh4%e7OUE3aah3CAMoc~RUgNPC`f zaZ${+zzebq5ZebcM%1y%*C&!&FnS@4V@yCo8(*3U>@mlCa9qgTp=iC09(i!eCfAOd zWV<<*X$c)?CuZgk5;-(CPO`xy@6sraIE@@%5}~En*W`_|VDMjYvo!O&cW7{6*WeJR zR6D6m%uA-8@U;@y(U6*`ZDKYva-fTl2+k}xQ*wGxHp|?+;;M!*rC^{5=p(3Hsd|ub zj7dlAa;k}#icrN^A^DRnpan9*wk?^I`J7jv+z35GL z&F@_vpSpk~Bq^^U%XoCcQj^2e&%@QDgE*{wX(*ojO)l2>qvx20!~|k`rAwt|!(jtp zJ_p|F?5A{aryg0yC!O$>{s?Q@qefGe$r)4YgsV>wkk~6w`}bw8`z@Ks_->JVp*I1I1|ftI@GD8 z3dzSP8;Ew**BgVH&=bUmHf*lNNN?X+SQ1HMZulldVQ9eQoyluN7mGoNFSNo3dJp zck5JYx@lv+^GPS07ET}+St3j80{BavIqALFC}K1M1%~9=*13IPCv47Xd0V2FiEXZp z>CstBOY8+Hz$2EBD5k`C2x?4d+u$Z`K^7UYKm2IQP{_G{+zbhCc|!i8A|}3)gMT4& zcVb#hKzwImT}ZhB^QzyB+<-n8B5^2B){+9%SDq~`-|&hBX&bSsbf8xkh@vX)EA$vu517qabE5xNp%X^p*j2N?-)liji zOHJm6ocmMRk75BO|4Sr%U6B9Bg8Y9yGXJN+AA<|P89-Hn;?w06v9$u5S1!Atg zhw=9*;O^jVKxguO3Ooxu0(_UT_!zJ;vFU>i%d$8t9e`MLa=qbN(=adlFjWy)@_R-} z0X{umS6-epmsQlw`s5NzA+bl{)aXlkEmM=fol5z3=GCc-sPL!GoyeuBNQNb3yFEXa z=Vee{RAd%@&!qwaY1MWZn#f{N;=d@joaOG#TY|T;FRl(Q(9|8jW+UrHzH@1@%l(AcQdVd?mRZkb(WbHyQga-vs~0wQJ<19-5*V++e;Gt=XC zf3FcUeB7S=0?Idd+{dT+Z;Oy2mzt1)OAyBgz$VH;ey9Nm(0GB<-!nz)s4UURM$nDjHYw!;;R9 zvB`~1BYY{B19<>EjI_*%c^CQ+!vacw5bL1M%Bt_$h{gd-upAU$C_KV-vKAKO*!E{8 z5t^DMQiROZXE6N~d4hKB+p$B5xDpL&eO|B4o;Op*Dg~_Gx3Rg2ne<+$E}->yx#BMA zU>5x(y`A^)ag3zeDvegdYTe-G21?NGQ%-%@X{SH@j7L23QB2R(G@33lc$1H&oMNfC zrp#(YZ`#a>3d*2zOhXWEwYOI9ttc~B@9d-Q)w~;hfNZ5unUc2Adq>q@P2b2lXQmhS zHoJbYH|iHDH)z`0bjca1OIXflD0P5rmmHyTaA`{ZQ{7`Lcv4}0PHKby&HbU=i|UD7 z{nD;)LZedK!*q!K)UrUGXtX66ku0pD6b(eoRikAcXxP8q&4jGIretx}!FktE#k@w@ zp3iK_A91PJD;tLiYM`^QGU^+DDdrpg8oP!Yd-n~V7yLGIB5CAAvocffXu`Ii`HhC+ z3>yuL<*sk+M?c1?s9wo+ZO}g9_0mjV5f@17zdc@bE@-sg)Zt2-PPM3bXT_}DiT}!* z;ffLQbfH~&L4Iz4=?J_WlRlQ`rb&#{SXf%rVZlhdqci>e=h$d(2}4*Ow$dO~2C!4# zcSnPUtLn^j!-DP@FYhgELvHf+coVHXIS=Sv+Tli~WrtrX^mv^npz@l@dU)*@m56!8 zQ!lS$xlouT@p739o!HEHBt%>tut<;uzbeY9ebVA+4@|=-;cC`m9tnA|NFR%uHKcEi zLGX)R>Qm8}wWz(*`n>k}AUXB#kJLX-BnG=OjoL5ZTW%l|?t%4}p9DZUpZIvi+X~K99UFd;eR(8^ASS7JLI)Upjyb!1>^} zz`r2te-eBGJQF+y{0FlAd%^3$Yr$*4+2B#&k>EI>-2ERx#{V#QDR>Mx3!Dj*xBrXC z_+J2T0T+N%z@5RJz{ipIpAW78OP~o3f=Ms|#=&_&`2ZgZ9t^sH?Djtc-vT#)YrwO> zGl6XQcLm3R+k@MI51<=(ANVuyyWlD?0mgyy2SmHRZ2GqWvg>~myc8S+${$b%_XYO> zw*lWnKk!}fCGb)35un_I1K=if1z!eV0&fAY2G0l21IqJ114e+x_TQ!7DSa~Cmg)US z%Z3Kgt@4P}MQkxDhdNWJv@@gXA`-<6kNz0ctZABQ+q6wRjw=z9B!;u@>ou~k}nld5W38hRg3zwqLoyg95 zKKW{{YC`N)yr9C6a0Ll+z3LAu#7P_CLXVqmw37?7@oFjs&BkCciMGp~E6fS~T4e>e``k&{ zog3SxbpquU^c3TZE1Vk4%~eHCar0I=6Wc_!2g2Xs^`!zgAuij|mL%029}9Q5sk4gIJcTfxnKfu7FKC+w+_V4M9j)(757$ z)ogR9w7(_$#%ok-b&qczVXxHsiv3{4QAB*0fAyH2kuuEvA-uE)y@;bereaF^ zUB)<#4&1V|+y1NSh3*_R5tiuEUFl1069i$SPMch)+h0en>T=ahKM!ZO&Es5oq-z|( zy;)q=bm)(&9&Rp8qcEh2;f;Dr<{-wc$?Gdb!e4F{mARD~zT@V+bz1bC)NdUYx$6H};zyd8uTyHmhN7$z6_CjU!qYdz+f0 ze!mW*PIqwUe&2*3)^Uz(H1{@)(6`1 zHX11_Xg29gMrW22No!dL?9)D;IgiX%s%~2MCueKpyi@}HmWk@_S?eyhEPsZQrJ?J~ zk`V=N9{PbrUc5%Q&wIC!KceqJ$Rp;wv9B36>vs8!n69xk!KQbNx-SlVdO;*=X@SHG zD+nWn?Nw}2F1$d=y{OQ((59L?KGPdy9oO~;W4&#xjG9EJu`Z31EL2mI#|7*cj;0U-9+!_1=Ie!kwm*5d# zBRB!P2igA3;0-{s|9yhc-3El+W0A2~A+~45u zCxH6`8`H2Qu*3=%U6Qb4|69hUrKLX8(9X{d3miGcG>QJSg%FR&>v&^!;^FKBb^KQ0 zljf!m;C3Usj4XUIn__PA+Ym5sNf*;qJ!Ht&PEviyPZ^44jLue^+DM_T76M13PvM zUt}~HZjYQEoV3a|Eyd){tq-KYp_KxTjDLdE1?uy;K z%FTGQK1}fg#X~x{bq1ZQ#^Z9Mt@g0Lv2c=$QqEdVcUfX*7sh+HpkrdZQ9k%g zV2kVTPXd~&0^|?e9ABJXkiFc)EGaQ(aSr$GZ|%gZ{IH!X4OQGY+ndMhoDeQI+)i+2buXo&{U zj!Z$Awxvb!Gw9lya#Z+kj~bX?+!EC;!N1XrJ*7L?m&Rl`%%$Qq0U9Ioi#iwk$oQBd zLa%T&$inpG_@w@9)l*nQwDI9}V|jrl;4oycvavw1zT^eg4%-1HA$~lZ8;JV^UPGucEP1DUJ`!$2kw_7(zR@Axtvh_gtF>BLc7UyKzi&Dxf#PS05q zE@_o4H?uQ)&gE_hjTdXXA+_I2&E`=0MNB%yu)d(>q0Nov)M$etGe$9cC>GuB5&b5G ztQOenwT~TZ2OwG1p_)w{Y@&z&lV)0LbO564jnES4u{u+A%{7w5);IQUF{d3VX;|O2 z-Yu>-j4oA{@!q0U;zZffWos!-p#FZ96+0lzU0W0#W@2d`M*ZbHp~@cODG8pMvB%6D zw3Sopl)!PRv(#Yj(SpxQ864iZo4v+1o^VuqHWK!LH&Gi0DI!wP)0QJw)jg{MV&}%% zL)(|SW583SLW{I}joSXBO-wc=%N~Vi*HTE&P$O|8<|e{dEGt0M9?^|WeDKJRw~QWA zVqB?-d1ESF5;d_3G;4_6f8U zJ*9%w&Bm1ESC5!OEQH}$RJ__EIa73_4w;yr#gpeqn3c8leSDm=A=Pv*j4ak;UEyG{ zqL=qf9`NN5Zk=BwxuHe|j!nvEC>V#Z;bh0D4V$|d_mI7qktYkLsGj(g>_s)bh&2}5 z;ejn_c*0c4%Rx!7ruWhSOve;YfKjf%ZBgYkywO_nqo1? z#P$sJ?CKwheSRbi&ssY~^McQeAOBK&bgieJ?vpZt*#j9Ns~meJt{B#(tG`CSR!d)x z!mAd?uPZY#N*@?33U20!d__;_2bFB3Jk^?xtG?cYd?EIpYkTLwuI<}kqkY&bo!+yF zk~8~IO~VkrOKqi;mx;RfAi#?Z5zFiOQnU@hT|xML6hnW-IYQmS6Mbl&52h_s+m@zV z#rCObXOyc_I>q?Osr+7=UO1)|;Ev$O$oroG9|Xz|Am0Gx1Q-SP2GRv6 zC*TjkKZEPRUxIgmDWDtxZ%3wo5*Ppv0LO#lz#YLg$o7iwe>~U^ZV#?Qj$Z-eK9KpGA)U1o$}kLvR2np8uXeWA@J)xAlEAn^=Pr2)0GOTxQ^=h4Lkzcj%9N zBlh6WgY!C)Q=A1yi7XJ@0wW<+l`$H6KFhDG4M+!ME*gUoUH{UK(-=S0T%K)+6_kP6 z-vXCaT=g1M9|-$c`F>pt+mSDzxQBTeaapxnNzseGSVNG}jA=4mhTIR>N)Y62CB?-< z5~3hGjxQ|eP*f8q>%IQFM>cN6rnopi21iWJC5X+0@4TT`wNNeCmps^4mkb zTTl-Tu!)-0j~JP}*nPWq3=eE2fd%qsWMRW85Mso`TnqD{n7w+NI;>Du2Z6k&vbreY zIDI;lu9k-xf>V4H^nK^7)~9O84L#7MGK}qPE3WU1t=eZtTX2+({j2q&kw`BId^>eu z(?fOau}9W{dU5E&9KGPPlW68p+-C;_N5~uy`w7ly_xkaj)ivm>Pt8p78!$f3I4V*Y&?(Hx3?M z*1_4Jq+Vh&#zPoF-c}ek9A|XHVGd+A*+FJkUpb1T7K5oV-%zYP*F><>o#(kz!vqQr zUXrEQ?<{F*Ez2tFa+tAmY22C7#Uv=mMKq6Q(rHOw8?xr)*DPm_f^koL{3$2;CdT$M zH?GEIs$LoxzmemMkls#3g*l3PKEFRK?#|=bx>dyO(gTDoy;XhngoW7_u*PCz*j%Ft z8z7@q!;AOHV1L_y2A*7+KteRu_IoEr-!dczLl|l;l68BsWO>Vt&aEhQAq+~G8RJvS zbB9o+J4K|AWJR-d9pRTqnkm>1NI5iz-Czn3jTHk({OP`8om4m7Q$zSs8WB|YA<1DJ zBN>`+c8G$b8>5S}@Q?5v-X#Ucl}#sAw(=>r{*2ShEo_XP zaXNMrRuXfkSPHaVzr={v)KaU+@|;zra<}@mr~I(YIP$45UUf;iYX{%r#RHE67!dA zDXFJdEeyp@&-~0(V{&1BoaNpVUYGc=a(mR4RBnf)7q$T;YO{KF3f)_1j(z_o&xED^ zP8*T$%t76sy*&gV(tNr-Y-7e7tutFvu*Ei3NtI;U65Yo=jK8+b}RImm74)_sr{FlKy!E?cr z!R0{y|Mv%y@4pB>36uxm$zT}B=6^4s{CK2^61WlB{?*`*z%<`2bD>4*G*>pu@Z1AZ5r3BJnMXnfuoWqnO5&CX2O zV+wdvS7V}{?Djb3sC749CEkpGqFK6-NAp8gMKYKpO`q~gF4H&wuS&Y;Cbp2EH!GeE zQ`&hU6mpmFz`2X_%L_f(nJ*^6bWU>Pn+MZ#G%#tqj-%^M+&JD)z6VU7M9?&(nY^Qfk~qozL!*oMtEF{ZhQrT zOXyQ+>dGzg97CS`6?5_dG`7)w$9W55+Bdm9JIO;qQQ7S6RakQP#*t~nB6+BuKY2*zl{`TWV{dO7gtDgj(4xk zfziw7v0zO%o6GQVo<$8WanhB`)k5yJq#h?L*3bbdC(_ej0SNmydZYoa!d`{-xT%c?kk~owiVG)+*-6Xm3v4*5=;9F!Bnv zcYBaav+?@;qKrUp^-(##$fCitY(ZiT=fNVez-W@ISCxCt>J{5sY0zt%(z;30sxVrx zP*E-U9Ze+?4=3-cTRezA_g!<-ibbEqWl6*Y)or~g#H>Vh7sbIM+PzE`7vMfv&q>q_NAmqpr{S)GZaI@%^oDlT`-kInRPuFoofCUJtK;6Kvkcd&a7k6pGi`qa%9bJeD*b{N^j z>Mdq3D=>~t5qZOFyofkd-d=7f_+RevyTmlCwr1 zWT}O%7V4DxhMWLK{g63OZpfcC^66vi!x#rKliITWt`2IH)e;#mq9r@)Y?zPj#w{&} zHifa`OwaAZbyZMpS5)4Fn-xMFPsoMAROzk=7lk#Q9q%XPB6^Zl`qvX$H7rNaVr8$1=XiBZ9IRE3nwRqxD@;vcpf{ri+H6R+RPBu04+nl@&podJ=82Mna+(i&+Y3oue zvgdJkOpn#Gi5V%5ZA!)UVoErD(9e_>CvCV9@(0^#&Q_7wq&j%7_KYtp<|N-X86^L$ zWqf#n4~pfoHu@+{GaqZoI!4F*Lyl^6;>1?Bl{W6|K+=#9Fr&TgyJa$*)UACj912uV zOt&?NS0tsabQVhu(kbPp9=nE~D0CM0Yb;%-T;tEO zI_L&JK*s+Z_!M|5kiY*!!SUc$;3j1I?}NVup9FsjUICs4_JcFPeSmWN{|$IEcr|!A zcp10~JONw`wt+ihJF2k;@V0-8X&1NjHk!FSOOd=k7Dyc4_w{2%Z_Z~-_UoCIzU{tey0cY*u_ z{t3JlNQa;pffXP$;I-fi zAU(rIpxgnUL7(sj&;a)ZzhR8O0$v5=L!dE#IJo&A&3k>su0h?S2aM0l7RQ(_yEokK zf}70t<-4nri#^n7T`1$HI6z}S5?>@=%RNJTbpU}qEv~7#zunKWuMmRmBp9I;&l#nR z*G^5`j>TrH&aowyC{Df##(*p5d{Vv42MbePeZSr8p?N9s7Cib24z;-GRHOJFZ;HE2 ztW4@{)yKA}v~Yc(s5kr^zMAlvEu*yG%xPnZ6e zF}7jIMEkQB5_?j7!?{p-|6E9p+X3ZRRLC}+NnD?e%!*3Scb|^4&79{voGMtRGA7E| zBcIG|gdJ5jUXJ>s#8G7%`)YPCaUPO;-Qq=S$}Hg;Y}>?qWAt$IbUZhgrmSmlJ7HdE zUQv{(0;o_7(NSv3)uMwMIOb>5&I|Di$w?sAx2}u^T4dP+%tWs0Vzm*~&416(WB1|Q z*BIHpb7*+qh#paAZYW#n6naE~(&q7&vV~CmbDI~fIwY>&5W8&WAk>dsrECe_4CcRi zxyo|r+BX7(fchTx>ezQWt5h?BZEgQ6I&lSXSf(Q{jbG(i8 zQ&D|emiz3;(oSYgJzVsYeRKP7BCx7g$r={My?D(SZWo7~+9$@aR#z9}u8#nG#41(o z%})I#!BO=<|KsEXq1bv!`(a$`6@5oq@-~F(0@YeINH(#ey3(e_(blcDfXY+5^1C&~ zreq&~VjPdVh91soTnnw}(jD0-rp9@~6NCs$bYes3J;=?L5k&%qtLbK^=gzWgTk2MI zTZ!@*f~v%r6a|*p$~8(R{T-2qWEL%rN*o7oPO3`wa`jp!Cs(BdT%Ap7O$3@$cJQmJ68gRzM09#LX7!X<;nhKVA3-}F**a^?UF z(gru)IzMM^Q+ZjtVo7c}(T}o|L~Dx3Ldn11nb7JOMJ>URE_*)w4O>{e}VD>z6JaN7zblu2e=#fH8TI(z@LCu0i74{GB62#j(qi> z;0mw=P5{3~w*M-4DUi(nB5)NL1v;E!${tz4jb#OfRTV(gog13QXApd}Kz`ejd!LN|nzX+}Y ze*m5W9u2mD2ZFnSJAvB+s9md^C5&w$Dr+`aGp@+BHLlgmmA55k?o}|w}=K99W52wk`KnRs$6Yw5Hu}G1*%82x&M_ zQk)*{&1&85u~_k#%pfevO;OD!(j@MGdXA0F&sbKilqq0-5y@FbCFuLq^ccy?;t3r* z6c6OyBtl0*aT=r>X4%N?C~v);RB7ZRu%e{T(3$nxE-;N#x{ewP_OKxQiAD27j-1QOI9m25D2ypj ztj~NJnEG2k`iN=7^{vf~6Lu~SxA~pvYksTGfr7PZx96rEm+isw!^j^*!7H(~g%!Xe zbOZB|zh;f|Rbw0JQLj+k--?Q{EFw#{tXenh3L8|oW2(Cci5_>PVr+DPzS z_LZ2s?t0qYaVoqV^QAQR8UV`#ndyWATJ__1U4n(bZ4!_DH#}8CPi3>l~?+dW`@E~ ztBf9vUCYBhRz9gkk@B`^BcN*SC+*P4z`6OHjkA^bX%`9Wa?&rf81~IQS&YP1$zhe| z?Ax__&+s6s?SXT43^j&!jcgxzT-vFk&$tSeHq2ke3>#e}z%~EX4x=!22QBe;wr`KN z;TT+-w=4RTWP5L55;nyRQ)xTg@uV%Mko}k%5b_3v9$u|p_+T{l4DH?__CUuB%OJRK zZ(c(@T{&hnaR%XQ9EI-PL+}>6&W#DeGiD&%87mc%ka=#?MRjZWO0>x0LTql6(UZV6 zohBDazKTIYztBJ%3snUG5>tvf4Y zpSITAcgzk!EVnZ5up$|fB@n{t?D!tHCFooylzTnpC+)WA8el0{&#|nXduhS$d2+d~ zc+8Evy4i(B=xkF#f^ z$&z5@{DwL^zk5}pF)+9zpShOA$~ju^N+S7<|MnlUUdg$WRM!^X#9Nove+YHZOh$t; zKD$tlu>({h5(&kE#5#+bu;GW)U-o*ION>FmX5Kl@1Nk&O26aoMCJtuBEC-*z2Z|<> zT*2+Lg5!o#b2B3jhaAR&>M`mjEWBNOci9LSimMpITPIEQkSUoGq1RD;O0~TQvuo-a zUAA3lY=#;K{Rt}~%$0DVeQ2h&NpmFJ<>keGiRM%{AC|47eyuM!Kb9x4YQz;}oG9ZL z7RGWZt-qW6e;)+I+e%JE{$CEl;yaP`|1WqYxB^@NZUuA>z)j$LpbtpiKMvd$bb;R> z(_af-1h#=8aBCo&|0lrvz%m#Fo4`%T@}C1Q04rb?OoJ(KI=Bt^5%T=E!CQdN1$-*F z9LxgA`-%k^06HJwL*Rozu>fa-&EPZ0@y`NJ0|&q)P#nOi;I`lg$njqRvg>~od<481 zyb?SIJOex(DBs@`z@=a#I1&5?8UKUe17IAC0mb~^h%A3S_#5yU@Iml?pz{I#1iTjL ze1Ie1N}zKFcY^x^oel759SAfevH~2ZS{%65^z@LLRfj5FDfNpSqa3kaR4sZ>) z1e^$N2Q$CsknYHm(qJyQT4o78&cOO(M2$RKbymOK`i8CP8dpW06k#*FnvKKG_J z;A5EUy*MKxj)RM(d5Z36p*mLQerkQQ+6x1*zMSoPfRz+)nK4*S8f5N`QcUUbA_=i1 z6Qsk@*=F4nJ42=RNWOrCNd+m>sOvWMZTIlrk%FYP_QW(92AQ_zE&=_+b|x~17TMVU z+KMKsG?r=g6ssZ_z?=g@E3h;_Z$XM8vWxRGGh?HO_&UgPa1lc7YI zmNd#B*enLt{xGV34P5Qnl%nVxnp!EG=JR;+5qp znvGZD9~R!^;uZH3|MckifO_90iavWE_J)J!UKC zAs;E8BA-xPpkXQ}ACNX)2+2Dq`fh1|n07h5>_y~Td$9SD2gozMk%)DC=?WJPlyJxs zFZsQ7QDAd$h>jF=YBU=5*Lu>=bR%5D^Z`;oqdwk@ZHL27{crtXin7hfzESm0F{EF! z*Abjd+M;;iIrG~GA~j1+Tb>Fj2u)$N_%i7UizX`|2;3?rO21-{T)C$nF-7zh7|v<; zk#a&2TX)vU&aPNO*>!fh(q-H8Exn%Ju7i|Ldl;t$#U`aJSIN(Uo;gJwl1@UjwA4F> z&ut9v)~sOVTfnC|j#<)A_2SN$w8!u8}4^V{B5T3F|kpq}j_BZ-NXlp>+Lo;RC% zGrQN7P=Q|1!f8xWB;Pmk6aFzOq}CKET1@h$FTLIEm7pQuYtPV#6t2U&cxkdmN}tC{ zCgxNw0)@V@bUm3UN>|&eMXPrt`jEdTR~M{W0H@t%+R^>Vu=G{;EZuDR?1x0eBKP9ef8l|LH)!|KCBje+PI0xC$%-<@}f5zhwRo zgX@57`g_2M;J=XR<>&uP@NsZ8coH}dJOIeI|KE}4e+K>yyd69l908Yr2ZL^KEAY3- z_wNR82CoOt1j_ZN_}Jz`TjXzFE|sN1e7m8a{t}H@j&wb7lF~m!uoIjMwt-&oTc8X4 zn(_U2@GbBm@Otonz@LIAg7d*yAjSk}K5PJ@5beCDMn)uf8KV6*Z zspAFkHEyR}ey0R8U3Hf{^-5j;gU>0AbgrQ@qpoi9lB1%^lg!#4U1O z#ko~(T58xaazQm#$T-OU{y~|1@eS2c9p(w313QM27~%cJ|GhaCk5gwgLP^fFD;bJp zPfYVtyV-$)n@)zAwU;6TK`dI*Q5k;1{Vm~yeF)CwplBz1s zDcvA;6Ct!Yzqnq{M_%9&l1I8!K!*>qZ!B7Tj+}o>@^E~tDt;cNGh|p{Rd>kDA#yM^ zv?W1*wW4X#h(c9=EHdRdQ>BLF-T^K5mo=c(pC!4JV>QFuE1jGM5Z9Z}UB zD>6OaDXJRM6$GU$o1 za+QpWPAS9OTvyJ;HoDQ!IpQmhHtV8lTXG*zra~rs=1^d{3)f7&m^Iq!bIg-3c5Z*Q zvcU*ZZ9=XGR;ydAP3t>EOOM)0s+6C&@1r11*;-pot}?wCi;bn0(x*!PzZX*8zbLmH z^8bQFK0cp7&i^>L7Q7YAfnDG+pdUO691s2x9l$q$@&f!mcnR1IWF^7nrWcoEnKhQTgy2KXItJu<)I{GS1Iw%-uA6Hw0o zF9OB*EARi+;1aM2D8~OL?_@&$MkcqMoVcpi8z*ahSZAb)@P0O)+bdGI)(xc(uq z6&wfd2)>2fubBS70(-y!_$G4vtHCRPZ2BwUq2TY3-Jb@o28z$W5cGiCfv+H&zaPk^ z|8DSXa1iu^2Laji?+5+`S^T{~X8^neTnhTZ>EIO52YSIz7)Ooek2RhxpTc;vytESq zt0V{x(3I&-aldQZak?VeinDYHMO0&Xai*bzwDzCsUObA;kFLR*>!R#LtGP#FipL)r z+PQn*o*j(~h938@jio@$_JyBvl!e2L7Ezv<=EV)l4-_{j>e*9M?WB=Koz1x_`qm7K z(d(fM^X9YEhaIXpS(hb0uy<`*uV1C9;2jgu;ZsbD`a!>mx64YLOBqY-$NaYgggO*S zpGv)z8U3=dRXFK20UoRxZ5Y4qaR^;?)`bb{hH%p)Qm^aW8|_eUio zGE23o;8c6U$(3oXXzUpj?swKsCQ_4PciVD zGn;6%C`VU;|KA%$3qx~f!zdaiY>+v4W9c4Cewwv12h>~g6Vxna&a(O1*W zx5L@Pk@ClX*V1aX3C3YPTik#z%uW#j*3laCaC^IG57dz@K{&PA%?rcf{`CanWZY@n zP@G7l%<$Z^R_ubei`mRr$VOE}qr?E97?RUQ4bztl<*SQm39n5NyD1rLfl=a5*4zJK$I8-Wo_|75IR-97I!|=S{V2a z9a$tP(hf#*Exb`lhXPYUkV)vk){I0n-!JyvvYbRkIZ>5W_zcsx#YLyg#rJAfqN1Qx z^AZ)e#iEivagEGGP~~;yCbIiN;IwCVT|GMy1KTk_k&UkfEer;IBegB%Ckl78sN_>p zDVrThSt+mk*l=ZxYM-Mh^46WAj0T5y$rRlfdED-yeC~`dWfo|9T3=(tOP~Ozx{d6p zd_jyZ_|@Q4&Kb{X0=Bfv$}IGh8_Fdx)0YSWiKtNLshyvm^q^6C$EZR2J6}O zOQ&0eyNu4J?r06vJ=A9Mm8Mmo?)6X;+ZDKOo3;*kj%7Ez9 zynEWjW|Xv6>yOTm{4Y^avZLhxX9Ss1_J7&?H-Qs?^8bGYd<fp!7{XYV) z2jf8J0o)th7AObcZ;=1xEAUhB6L2l~3m_fAlYrs`W;2Izu!3)6i!BfERf~&x}K<5eQoWL8<2YehTC*WTKC8@{s<^% zfb#!c4wN(CF<=XL2$284Z9y0K4Z4JPf!BeTftP?6gL!Z|cpy+t0r?Hw6?}?$aU|## zZ0<1!H<{1T)cpKJU(FIM6OgSG?T4plW+dX+&pP29QE!%zojDG`gC5G46v@aE#KByL z(%@2tY`aSb*~aj0%wCHM*Cf~b?985_^2(lxBpkMz4lK`&%K@&KwQVZfXXkhIsji+p5eA^(dv$`d z-C(?jhA3UZ%~c+bq&jfM0!raAqxNolh&&77mUymL%OLQs$%SZd^wk}A?RJ%oIvm$$ zE{skua^^5(s2rYX*Tb30&J~$n(Id9=d)2wV{Q(cZ;L%lAM- zP@|PwQqZYYS8ZyjrCirY#A#-|PRH)HHSNldu}OSUijJKeU7R7eeP36N2is}mhChv+o|SXP}FOm+|Z_ga&G1!q`19VeORLC|G6Ff+{k16O#Fo?y=H z8y^pw7~urkp>!e~liW^KKR_&2?cns~^ar$Hc!9M`Uxg(jr+6(HZ65Rsa<=FQi^HDx zMdHU~+44CVUD)Kzj54rH;5k+-86QJ(i-^=jP4R}dv&7g$!n&YZIAS*sY12tWYTLdY zI~o!Z_6+Yx(`jG^ZL4Y-CR0sndR%~?v^-6HYWXwmW)`&?7$omNs#aKDwwMb-w@%_& z*=(wXGfP=OQ++Y+$HXML|E|o=FE@SvY)607L~B|5od|H~@>4Q3dbs4IIX1Vt>(*BP z^t2;gDk~BEOj&I7%jtR0#+7u=9Pbtgqx5skx)J7PQiQ(mXYzSez zfzuI{;NGHRv@_@2hE>T8c}LSTsVsEF!GxCWJG}X53axOUM_G!I^H!6mN5e7GwVi7> zIIye}&}{Y4r<mIKZh`ly2JxtpW8l@`Vz3Vkf|J2* zfN}u*8r%rJ0Nw+h4-^;hH1KF}6Y~7m!K=V5m;rV0PssGL`Ckgo0N+4{R~*1!g13P` z2g(Pq1Kb)Y*Pr46z6_Mxe_I(cL)E8 z%>GmGWAG!O`2QCG`2@^@X`r|O`3KyHT>fX^B|v%q=VnJ_7Fp^7+3E zjDR}=`St%C`~>_fkZ-_?zzc!$0A2(h1s)1c0KWw^)^`D|pPRKLW#&s%u(B-EKD`H$ zh-`z;R_ki(z!0V~p&!va82$R4B6*Zgk9{HYjPG1-PYkE7cQUTAZk*`&|EuPus{h#k z{>(C_I?*nt$i7um?%l%eGaC$zb=VqWEMDLqLYj0@CNZ9*Cp7mySYlJji~i_AOK<2x zXc~}0l*#y|-l24Qn@a#_bb_(~j(^(2g6npAQdU2->-K9*|8FFhL#*b+9C2=278VfL zzSB=Ry|#;3;%#`sSQK;K^?0(#g!(3016Yb`{x|>&pE~lMgj;GExpD$_HxXU}5wV*xMwsz0tC^@K2U#}`Q z9u;Q;`*CyMQ=uI^o(-l}zMuV6R}irmUo7tdo$=vKj4Uj{J#7WjVe=dPZ_!J|qHnQY ziV?(3Hp?ooPSMJ&Uxj|BHZy$)apCavfu;W4h5F2^8JUIMW->whN{shpAH9yD&D6Ow z^Ye$I7$L zoik3n#wTARQQr!)<+7A-4V;lk;1q&84ixhZO`#A*99K_Zk};Oa6^zvjt+E6-$(^C} z;1P=x*2|0g?NHtHB3Ne&Ikn`5G4HGAB(UimE9%pkIh!l%RIn@6O~Mni7+KEnlh`}d z3_24P*$mo)cP{-$t)9SNWV6NAyv zrIi@tVt!QN&h~=o+J&dFg=k+iIYv?*ss#s}NOKg1)gX?nYhR)*o(2;)IJwiX`i69| z5zmOlza$txm0GiH@3cNdS({p~#-0#OZv8a~YoX39>Iz+NN6d9SEtpLDUrYVazA5%p z>R%URGNo2Y^Je0|Ue9ge96Fz|VG~Z%6)sX1A`X;wHAb_h^wUh~E_+(fGxcNcC8oQJ zI-a5;myy`wQEs8%lT8_CNN$`j%D8Cnx!Z}^oS2xIJS>&>w49sL2Hmf$&I%SY^@^R{ zro79+I-moW^J$rL^2amQtj$zPAPjd&R4@R@UDL;-YIT@ARi$tjOTzN6Ie{Dta z65d+YxO8~oakRw@T*OS-9^5}Wxp;6g8koF{#^Pa9;+h+z6IIS@Bn^W9%fW+HJJu(hTFUYR8EI7?LW|8FA*o+Nn? z`~OpdQ1~@u{2RbO0ObMr6YzNOKyW8;dvH7O@5uST1pg2GJ@^uMCwM!!7DyLxH8=pa zf=7bW!KvT`@J;jqe*=^=;1aM4oDWU~$|3M>^aDB@pbzwd2ZI`T5cqp^1789k0IvhD z1V_Ln;CS#~=m?_jK)wO*0at@d!FHh7fvwq;6xzZ!oPx-g9)Hq0`~^;6Zi}8LU0fq0KMS8;GWW>nUi=$>{K>T--IvNjOg*-Ty zOQzrhqe`VS1+TRPrE}iH(s`B++Ym#+t=fz&1GZZ?R2}`|g}I+vkwqyQ)-aiL1)rO? zo3qMI;cL2?yInUhwj!gbvEuC4frAa}wAdL6Yw_sf9CTP|UA7PGWZ|E7jumjOn8CSG z@>cDM`9cC^hTRT^nA$2gn=0~(-qG1Bd%Z@z2UCft&_fE6Roh4piC;NXYAE}ywXZ0x zp&0o<8B3bk4o0B0`T|}RgILTb%V?E)XRE0!Tq3#9xva$AyC|yzw4`;9{tEjGJvCuR`Nm39l_aF4e!UsO zSztO>)t#3n4i7{{2Y>pqErJ6oVt>1;Rn5&EG=`yKZR~mF8pON{5CoVZexuu*R^fx; zfA|??iqx4!i6VOdgJIwL7D&a>P)54?!sybJjjR6X)AC|ZZJmc)bmNKL7M6=e8`+f2 zE~uC2)^nC;4*9_J_ro$PNgqcxLK^u;x4?exaVry*$88v%P5F=!2V=_XNP+z*r6}n5 zpmb;j<-%r`8=`=&aBRI*0XNRR=JFW-)SHtF@G+c5h`XruL3lW84Lz|+0X&S{XbH?h zQN6G{eoZA>eb(#@yUW{^)C9dl7Aosx#CRvUSGx;pWh8)u5QVq-`rP4zv8PBllQ|&Q-a68vDrh~&gwi8TYO!#==-qi z?;74YWWEfXL863lMaUfkLZ0@9+#|+&V)v{0HB)I+-sX!TdaHz7dbMVpcwy~o!oXcd!Z*k?|a#|cJ&kVCS_$nOh8eV3MBnRiKzj}nQcmgInj#l_h2 zu*hQn6Dl{OA+d!OkH536z^~I%ypzYrCQ;crX`!JU*05c!S+S7zRNiwIH*`}Z7WE=7 zuZMEU#M3TwCmrSV)dsrSIWd)Mu=6mBJ!S|d&-BsAEg@~B&H#Z_a>25do18trpt4h5 zNq2)2T|Voq!~wFIns=O&Yu9CHppsT2v}}B4etBXEPbDeK>kw~O1;$BZa&O8DXZrdT zG9RvRW5^Zf#$UTnGb+~BIZM0jUEWsttQzI>$){4My0dRg z8$7RZ&c4A5hDOvEs#&2JbhgRF@IqA|rqJ-QOq-mSc~r{z+Nz(NQ!QS_6c4cw*6OuI z2N=;1W5=UD-!zAnDl>6KMe)a!W0U-UcLY7Ah2TlxuHcKv_g?@{1BbxL-~r&b z!3p5!$oL-wuLk?UBfvwz-N0?ZuaWV82(AN91y=%{A)s6V4*_=s-$b@oo&cQ*DBu5| zBF8IV;9J3~z>9&-1$-8m2HU}dz+J&@faLxkA=m!}xDq@8{0B1pPryF{`T9%Fe=~R$ zP~L#&1Nr}74DJcOgS`IN;2Gdj&?c*DanI zx{qla^Shu}U0Q~3>Y)B_xzp*SO0AR`m_$o`lNZ;pD|r4n*=%`EbSitu{*1R)xBh}W zLrJv#^g9gEvP*Q*1pkAcpcg9N~Zku)=8<%MAyL zROS{YaWtCP9rnU>$iyB=uagtLFylgA>%N_%i-#r`2QfSl5@5O@wJYml8z0QMQnqm} zb}-DL?mdwx?*ziazFe- z5?J2(8f>}HvMQ~baaqKFi`2U&TN;2 z9`U4}Gbyg!EIGU?tWWzn?Z2g&-A+c4z#UO9 znem52?#umJru?dAs^l6yt%hp9oJ8e1DpPv$?NKTEH6`W_Zd;NCyYypTg?w~cE0h0f zO8}9wkGwTa4h3LNcQebzuyhi)fK@f@6w&~97c-(XBxpo}k~a#bUZ2!xFT=ISOlLW- zP8Ma^;M#svzC-Z|Or#@SUK+1N?bfa+Ux(8NR-$*8DjU_7Z3QtT4i!;h5GAZa`Utx--8P}YsZj4R{Dh=&D)u-r@E>+(uQMAE-GTy`W zxt1fyuO{9zTLQL5)zupc{$x0a1I`zuMdD0PG*kJY=fj-_=IAvW&gby{W9NLB6?t>? z8k}FfbK3xCs6KYz_B}&e&oui#VrJLpl>`9!e>Mn)*CD(AK6oa0I(Qlw20Ot?K(_ze zf`34ceYgPT;-B@P7b?!7gw=aBCo${@dW| z;A`Moa0qmRe?W$R8TdVL8JGa$;CkfrSA$1^M}l7=r+)>!9b5|zf;#vn^7w0k?DER@ zuQ-4Qfct{`0LAD3Gx!YnGkK<_yyzjui(qz z3qa#{JM>o^bv_d@rvpKZ} zF+N|+k->=#6_IKGF^Umv`1rCq**Injy{`5zO;l~=PWO<&)?+U#UcLHnLE`lIwu!SI8ABCcv;%h{iu+ZumPB8+&3B4K{<%d&+K*& z_|`_7R2aH6%xW{ZPm14bnbobjlJ)v+#HHh}N`x?TxLIYa>?fLBou~MJ7M?e2Jx`}` zSm&DSfzDm)Lrp9!+p>=S~JPePP*?sKZ@BdE_>8Tb{WqQT5FQr<`&4FIruAIk^SY}jqtGgmbik+X`ip5T$cz=S zpn@DiR`{Y#Ag%=?M%g~#_G|joc7s4ByHu49bgXEC9EpQwE0UJ@Zz3}J5Of}dP+|5f z7xSgXN6FN%Amh5;m~t7#p$;S^ra0=GoX>{D%hTq? zQS?XXcQ12tvJyp5SSak75NHsbEGJ+yN6m5H*_Nn~m{Ope?;nwMZrew#45&_g(8qeg zMvu)o5x!NG=`u;=kLO(ycTX$JsA*5BpZiK<;1jf%SK4NwM1iEY*<(`$b5Y$(Njj8r z54CQzMpuiG_Y7F2N`~22A>?u=KwLH720L~#YOvbeSKhsSN^l$c1P=EM6-4tuY9n*C zyprIJNS}Rr&>EIk;1(1Yf{#o+Jg5*2ZTpsMxy#fIrIVhWGuV<1t(d=6)}1Z5R%s_} zcSR3&3VbU>AX-%3{nTkbKeE-6@zAt$#B$Jh43`Dd*s?OuKz~vZG>V*IjcAydATk~@ z>t+AHKVsN}ktrqrKbe2@xgObG^8aPv;ouHHKK~yfkr z>;dxQ-wFo7W-yCv|4{H($nk#x-UQwVo((Ps3t%HqZoj*OyMb>Z+rJS^f_s5`g5MzD z%Z~p;@Xz1_;E7m`B+3%kY6yyJJa5}g>xE=UsbO5sFzaP90ycEm<#rp3B1K@0M7I+}I zBlsuu1b+)Y3tj`1!%uny+4`Rao(V>P@&IfG_W?RH;8vgud>4Jf-+}AEhrkh_vjdbH zU^f^BcLpD4Y+nwZ3MRn-I1|*tLx9F!dWBA(NEX{D4C1bm=uZbFS!si*s&HE=tElQK zAESppv2zpWDo<2?3MLEQT*!2!*&1W`%4nv@b1v)vl50 zePRPv^&FU=nQFB49_Uib)_NH?m0sl@9(MLOKlvE0Rfbt{QYlcvVNx(w{j;^LwGzSV zUG3ZqsYj@R)YzH)g}4a^;kstHkkQ41O{0C?!S1?!0&T=Da$iOE!|pI;Roz8)7-BsH z`NJ@m9D;G;)&b};qeOR78g<^t$nF8|wnw6ZVgoK1@!ouksrFs6duXgxZ#(xQn4z^N z@j$#xQ}F$4K#ZefwLFt!4&H2Hw%*8rQrsJx8+xtuZWYBMb4|HBJ?m5iyPRGdeVVNXgOY+1+YneJRCJ; z*MBrIp!`cl(#4rx^XygdBN5hk;P{EnIQ5&Z31i?2mN0pEKJ>J0rn)F*rn#zTQ%*0+ zhR1)7&Cz*(5%M9;)7GB0~HIqjV9z}5z5@@z70?lQX!3nl4u zZKV-Zi8@fMnK_h1XY{n#tj;|)ofuNcq-c)QokUVH@BPxi#Vtm2XP(m&X@Wv;QQyoc z7z4@{vRV>;L*jHoz1>g<$AhDVaXrdv$qV(V`Y#8++u+>2u#BH|)6yN3oHX1mceA@_ znHP8RSvGIx=nLa^b4`Ql5Q;jnEIwvcf7q3*cLU(VxLD^tC>~WnS=aomrcJyUxrgJn*IH#JjXdZ zhr@!!aEJoK)=`XJEI^}IozNj7^CHw}+jpJYU)xFvBSXb1 z2`&r7t9tc%t0m6X;pJ6kaY%(=yy6^A7LTV7&ncznvjV@Tugi_e!|M^^$jty4?HHOq$?5)HAe zY>ToUg`(f0$q?~pmN2RSe5u9 z=UV!_8PS!L7V=Qh6&)T*d_6ZCf8XK0sp*4LMp35CBxOnc%vUH#4T1P~czStGhFTIl Ovpd4n72nS6`u_kP{st`o literal 0 HcmV?d00001 diff --git a/src/rgw/CMakeLists.txt b/src/rgw/CMakeLists.txt index de0489e33ed..f6ae85fd4e5 100644 --- a/src/rgw/CMakeLists.txt +++ b/src/rgw/CMakeLists.txt @@ -88,6 +88,7 @@ set(librgw_common_srcs rgw_ldap.cc rgw_lc.cc rgw_lc_s3.cc + rgw_restore.cc rgw_metadata.cc rgw_multi.cc rgw_multi_del.cc diff --git a/src/rgw/driver/daos/rgw_sal_daos.cc b/src/rgw/driver/daos/rgw_sal_daos.cc index f60c5a11208..c90a8770514 100644 --- a/src/rgw/driver/daos/rgw_sal_daos.cc +++ b/src/rgw/driver/daos/rgw_sal_daos.cc @@ -1028,15 +1028,13 @@ int DaosObject::transition_to_cloud( int DaosObject::restore_obj_from_cloud(Bucket* bucket, rgw::sal::PlacementTier* tier, - rgw_placement_rule& placement_rule, - rgw_bucket_dir_entry& o, CephContext* cct, RGWObjTier& tier_config, uint64_t olh_epoch, std::optional days, + bool& in_progress, const DoutPrefixProvider* dpp, - optional_yield y, - uint32_t flags) + optional_yield y) { return DAOS_NOT_IMPLEMENTED_LOG(dpp); } @@ -2321,6 +2319,12 @@ std::unique_ptr DaosStore::get_lifecycle(void) { return 0; } +std::unique_ptr DaosStore::get_restore(const int n_objs, + const std::vector& obj_names) { + DAOS_NOT_IMPLEMENTED_LOG(nullptr); + return 0; +} + bool DaosStore::process_expired_objects(const DoutPrefixProvider *dpp, optional_yield y) { DAOS_NOT_IMPLEMENTED_LOG(nullptr); diff --git a/src/rgw/driver/daos/rgw_sal_daos.h b/src/rgw/driver/daos/rgw_sal_daos.h index 19cff1d5798..65ecbdcbb28 100644 --- a/src/rgw/driver/daos/rgw_sal_daos.h +++ b/src/rgw/driver/daos/rgw_sal_daos.h @@ -654,15 +654,13 @@ class DaosObject : public StoreObject { optional_yield y) override; virtual int restore_obj_from_cloud(Bucket* bucket, rgw::sal::PlacementTier* tier, - rgw_placement_rule& placement_rule, - rgw_bucket_dir_entry& o, CephContext* cct, RGWObjTier& tier_config, uint64_t olh_epoch, std::optional days, + bool& in_progress, const DoutPrefixProvider* dpp, - optional_yield y, - uint32_t flags) override; + optional_yield y) override; virtual bool placement_rules_match(rgw_placement_rule& r1, rgw_placement_rule& r2) override; virtual int dump_obj_layout(const DoutPrefixProvider* dpp, optional_yield y, @@ -937,6 +935,8 @@ class DaosStore : public StoreDriver { virtual std::string zone_unique_trans_id(const uint64_t unique_num) override; virtual int cluster_stat(RGWClusterStat& stats) override; virtual std::unique_ptr get_lifecycle(void) override; + virtual std::unique_ptr get_restore(const int n_objs, + const std::vector& obj_names) override; virtual bool process_expired_objects(const DoutPrefixProvider *dpp, optional_yield y) override; virtual std::unique_ptr get_notification( rgw::sal::Object* obj, rgw::sal::Object* src_obj, struct req_state* s, @@ -953,6 +953,7 @@ class DaosStore : public StoreDriver { std::string& _req_id, optional_yield y) override; virtual RGWLC* get_rgwlc(void) override { return NULL; } + virtual RGWRestore* get_rgwrestore(void) override { return NULL; } virtual RGWCoroutinesManagerRegistry* get_cr_registry() override { return NULL; } diff --git a/src/rgw/driver/motr/rgw_sal_motr.cc b/src/rgw/driver/motr/rgw_sal_motr.cc index dee118243f7..3cb654fe41b 100644 --- a/src/rgw/driver/motr/rgw_sal_motr.cc +++ b/src/rgw/driver/motr/rgw_sal_motr.cc @@ -3335,6 +3335,11 @@ std::unique_ptr MotrStore::get_lifecycle(void) return 0; } +std::unique_ptr MotrStore::get_restore(const int n_objs, + const std::vector& obj_names) { + return 0; +} + bool MotrStore::process_expired_objects(const DoutPrefixProvider *dpp, optional_yield y) { diff --git a/src/rgw/driver/motr/rgw_sal_motr.h b/src/rgw/driver/motr/rgw_sal_motr.h index e95ab3252d6..709b77c34a5 100644 --- a/src/rgw/driver/motr/rgw_sal_motr.h +++ b/src/rgw/driver/motr/rgw_sal_motr.h @@ -1006,6 +1006,8 @@ class MotrStore : public StoreDriver { virtual int list_all_zones(const DoutPrefixProvider* dpp, std::list& zone_ids) override; virtual int cluster_stat(RGWClusterStat& stats) override; virtual std::unique_ptr get_lifecycle(void) override; + virtual std::unique_ptr get_restore(const int n_objs, + const std::vector& obj_names) override; virtual bool process_expired_objects(const DoutPrefixProvider *dpp, optional_yield y) override; virtual std::unique_ptr get_notification(rgw::sal::Object* obj, rgw::sal::Object* src_obj, req_state* s, rgw::notify::EventType event_type, optional_yield y, const std::string* object_name=nullptr) override; @@ -1020,6 +1022,7 @@ class MotrStore : public StoreDriver { std::string& _req_id, optional_yield y) override; virtual RGWLC* get_rgwlc(void) override { return NULL; } + virtual RGWRestore* get_rgwrestore(void) override { return NULL; } virtual RGWCoroutinesManagerRegistry* get_cr_registry() override { return NULL; } virtual int log_usage(const DoutPrefixProvider *dpp, std::map& usage_info) override; diff --git a/src/rgw/driver/posix/rgw_sal_posix.cc b/src/rgw/driver/posix/rgw_sal_posix.cc index df625c8c078..798a18bfb9e 100644 --- a/src/rgw/driver/posix/rgw_sal_posix.cc +++ b/src/rgw/driver/posix/rgw_sal_posix.cc @@ -3063,15 +3063,11 @@ int POSIXObject::transition_to_cloud(Bucket* bucket, int POSIXObject::restore_obj_from_cloud(Bucket* bucket, rgw::sal::PlacementTier* tier, - rgw_placement_rule& placement_rule, - rgw_bucket_dir_entry& o, CephContext* cct, - RGWObjTier& tier_config, - uint64_t olh_epoch, std::optional days, + bool& in_progress, const DoutPrefixProvider* dpp, - optional_yield y, - uint32_t flags) + optional_yield y) { return -ERR_NOT_IMPLEMENTED; } diff --git a/src/rgw/driver/posix/rgw_sal_posix.h b/src/rgw/driver/posix/rgw_sal_posix.h index 3af36d02a4c..b31acb3b860 100644 --- a/src/rgw/driver/posix/rgw_sal_posix.h +++ b/src/rgw/driver/posix/rgw_sal_posix.h @@ -697,15 +697,11 @@ public: optional_yield y) override; virtual int restore_obj_from_cloud(Bucket* bucket, rgw::sal::PlacementTier* tier, - rgw_placement_rule& placement_rule, - rgw_bucket_dir_entry& o, CephContext* cct, - RGWObjTier& tier_config, - uint64_t olh_epoch, std::optional days, + bool& in_progress, const DoutPrefixProvider* dpp, - optional_yield y, - uint32_t flags) override; + optional_yield y) override; virtual bool placement_rules_match(rgw_placement_rule& r1, rgw_placement_rule& r2) override; virtual int dump_obj_layout(const DoutPrefixProvider *dpp, optional_yield y, Formatter* f) override; virtual int swift_versioning_restore(const ACLOwner& owner, const rgw_user& remote_user, bool& restored, diff --git a/src/rgw/driver/rados/rgw_lc_tier.cc b/src/rgw/driver/rados/rgw_lc_tier.cc index cf057f6fae8..c536d6fc10c 100644 --- a/src/rgw/driver/rados/rgw_lc_tier.cc +++ b/src/rgw/driver/rados/rgw_lc_tier.cc @@ -260,6 +260,7 @@ int rgw_cloud_tier_restore_object(RGWLCCloudTierCtx& tier_ctx, uint64_t& accounted_size, rgw::sal::Attrs& attrs, std::optional days, RGWZoneGroupTierS3Glacier& glacier_params, + bool& in_progress, void* cb) { RGWRESTConn::get_obj_params req_params; std::string target_obj_name; @@ -276,25 +277,23 @@ int rgw_cloud_tier_restore_object(RGWLCCloudTierCtx& tier_ctx, target_obj_name += get_key_instance(tier_ctx.obj->get_key()); } - if (glacier_params.glacier_restore_tier_type != GlacierRestoreTierType::Expedited) { - //XXX: Supporting STANDARD tier type is still in WIP - ldpp_dout(tier_ctx.dpp, -1) << __func__ << "ERROR: Only Expedited tier_type is supported " << dendl; - return -1; - } + if (!in_progress) { // first time. Send RESTORE req. - rgw_obj dest_obj(dest_bucket, rgw_obj_key(target_obj_name)); + rgw_obj dest_obj(dest_bucket, rgw_obj_key(target_obj_name)); + ret = cloud_tier_restore(tier_ctx.dpp, tier_ctx.conn, dest_obj, days, glacier_params); - ret = cloud_tier_restore(tier_ctx.dpp, tier_ctx.conn, dest_obj, days, glacier_params); - - ldpp_dout(tier_ctx.dpp, 20) << __func__ << "Restoring object=" << dest_obj << "returned ret = " << ret << dendl; - - if (ret < 0 ) { - ldpp_dout(tier_ctx.dpp, -1) << __func__ << "ERROR: failed to restore object=" << dest_obj << "; ret = " << ret << dendl; - return ret; + ldpp_dout(tier_ctx.dpp, 20) << __func__ << "Restoring object=" << target_obj_name << "returned ret = " << ret << dendl; + + if (ret < 0 ) { + ldpp_dout(tier_ctx.dpp, -1) << __func__ << "ERROR: failed to restore object=" << dest_obj << "; ret = " << ret << dendl; + return ret; + } + in_progress = true; } // now send HEAD request and verify if restore is complete on glacier/tape endpoint - bool restore_in_progress = false; + static constexpr int MAX_RETRIES = 10; + uint32_t retries = 0; do { ret = rgw_cloud_tier_get_object(tier_ctx, true, headers, nullptr, etag, accounted_size, attrs, nullptr); @@ -304,8 +303,14 @@ int rgw_cloud_tier_restore_object(RGWLCCloudTierCtx& tier_ctx, return ret; } - restore_in_progress = is_restore_in_progress(tier_ctx.dpp, headers); - } while(restore_in_progress); + in_progress = is_restore_in_progress(tier_ctx.dpp, headers); + + } while(retries++ < MAX_RETRIES && in_progress); + + if (in_progress) { + ldpp_dout(tier_ctx.dpp, 20) << __func__ << "Restoring object=" << target_obj_name << " still in progress; returning " << dendl; + return 0; + } // now do the actual GET ret = rgw_cloud_tier_get_object(tier_ctx, false, headers, pset_mtime, etag, diff --git a/src/rgw/driver/rados/rgw_lc_tier.h b/src/rgw/driver/rados/rgw_lc_tier.h index 60006da8914..20d6e0675d0 100644 --- a/src/rgw/driver/rados/rgw_lc_tier.h +++ b/src/rgw/driver/rados/rgw_lc_tier.h @@ -61,8 +61,9 @@ int rgw_cloud_tier_restore_object(RGWLCCloudTierCtx& tier_ctx, std::map& headers, real_time* pset_mtime, std::string& etag, uint64_t& accounted_size, rgw::sal::Attrs& attrs, - std::optional days, + std::optional days, RGWZoneGroupTierS3Glacier& glacier_params, + bool& in_progress, void* cb); int cloud_tier_restore(const DoutPrefixProvider *dpp, diff --git a/src/rgw/driver/rados/rgw_rados.cc b/src/rgw/driver/rados/rgw_rados.cc index 501d5d8fad1..c057aca97a9 100644 --- a/src/rgw/driver/rados/rgw_rados.cc +++ b/src/rgw/driver/rados/rgw_rados.cc @@ -38,6 +38,7 @@ #include "rgw_datalog.h" #include "rgw_putobj_processor.h" #include "rgw_lc_tier.h" +#include "rgw_restore.h" #include "cls/rgw/cls_rgw_ops.h" #include "cls/rgw/cls_rgw_client.h" @@ -1141,6 +1142,12 @@ void RGWRados::finalize() rgw::notify::shutdown(); v1_topic_migration.stop(); } + + if (use_restore_thread) { + restore->stop_processor(); + } + delete restore; + restore = NULL; } /** @@ -1240,6 +1247,10 @@ int RGWRados::init_complete(const DoutPrefixProvider *dpp, optional_yield y) if (ret < 0) return ret; + ret = open_restore_pool_ctx(dpp); + if (ret < 0) + return ret; + ret = open_objexp_pool_ctx(dpp); if (ret < 0) return ret; @@ -1363,6 +1374,12 @@ int RGWRados::init_complete(const DoutPrefixProvider *dpp, optional_yield y) if (use_lc_thread) lc->start_processor(); + restore = new RGWRestore(); + restore->initialize(cct, this->driver); + + if (use_restore_thread) + restore->start_processor(); + quota_handler = RGWQuotaHandler::generate_handler(dpp, this->driver, quota_threads); bucket_index_max_shards = (cct->_conf->rgw_override_bucket_index_max_shards ? cct->_conf->rgw_override_bucket_index_max_shards : @@ -1484,6 +1501,11 @@ int RGWRados::open_lc_pool_ctx(const DoutPrefixProvider *dpp) return rgw_init_ioctx(dpp, get_rados_handle(), svc.zone->get_zone_params().lc_pool, lc_pool_ctx, true, true); } +int RGWRados::open_restore_pool_ctx(const DoutPrefixProvider *dpp) +{ + return rgw_init_ioctx(dpp, get_rados_handle(), svc.zone->get_zone_params().restore_pool, restore_pool_ctx, true, true); +} + int RGWRados::open_objexp_pool_ctx(const DoutPrefixProvider *dpp) { return rgw_init_ioctx(dpp, get_rados_handle(), svc.zone->get_zone_params().log_pool, objexp_pool_ctx, true, true); @@ -5353,13 +5375,11 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx, RGWObjectCtx& obj_ctx, RGWBucketInfo& dest_bucket_info, const rgw_obj& dest_obj, - rgw_placement_rule& dest_placement, RGWObjTier& tier_config, - uint64_t olh_epoch, std::optional days, + bool& in_progress, const DoutPrefixProvider *dpp, - optional_yield y, - bool log_op){ + optional_yield y) { //XXX: read below from attrs .. check transition_obj() ACLOwner owner; @@ -5381,6 +5401,7 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx, dest_obj_bi.key.instance.clear(); } + uint64_t olh_epoch = 0; // read it from attrs fetched from cloud below rgw::putobj::AtomicObjectProcessor processor(aio.get(), this, dest_bucket_info, nullptr, owner, obj_ctx, dest_obj_bi, olh_epoch, tag, dpp, y, no_trace); @@ -5394,11 +5415,9 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx, } boost::optional compressor; CompressorRef plugin; - dest_placement.storage_class = tier_ctx.restore_storage_class; + rgw_placement_rule dest_placement(dest_bucket_info.placement_rule, tier_ctx.restore_storage_class); RGWRadosPutObj cb(dpp, cct, plugin, compressor, &processor, progress_cb, progress_data, [&](map obj_attrs) { - // XXX: do we need filter() like in fetch_remote_obj() cb - dest_placement.inherit_from(dest_bucket_info.placement_rule); processor.set_tail_placement(dest_placement); ret = processor.prepare(rctx.y); @@ -5422,6 +5441,9 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx, return ret; } + // For Permanent restore, `log_op` depends on flag set on the bucket->get_info().flags + bool log_op = (dest_bucket_info.flags & rgw::sal::FLAG_LOG_OP); + uint64_t accounted_size = 0; string etag; real_time set_mtime; @@ -5432,7 +5454,7 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx, RGWZoneGroupTierS3Glacier& glacier_params = tier_config.tier_placement.s3_glacier; ret = rgw_cloud_tier_restore_object(tier_ctx, headers, &set_mtime, etag, accounted_size, - attrs, days, glacier_params, &cb); + attrs, days, glacier_params, in_progress, &cb); } else { ldpp_dout(dpp, 20) << "Fetching object:" << dest_obj << "from the cloud" << dendl; ret = rgw_cloud_tier_get_object(tier_ctx, false, headers, @@ -5445,6 +5467,11 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx, return ret; } + if (in_progress) { + ldpp_dout(tier_ctx.dpp, 20) << "Restoring object:" << dest_obj << " still in progress; returning " << dendl; + return ret; + } + if (!cb_processed) { ldpp_dout(dpp, 20) << "Callback not processed, object:" << dest_obj << dendl; return -EIO; @@ -5470,6 +5497,8 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx, std::optional olh_ep = ceph::parse(rgw_bl_str(aiter->second)); if (olh_ep) { olh_epoch = *olh_ep; + // update in tier_ctx too + tier_ctx.o.versioned_epoch = *olh_ep; } attrs.erase(aiter); } @@ -5549,6 +5578,8 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx, attrs[RGW_ATTR_RESTORE_TYPE] = std::move(bl); ldpp_dout(dpp, 20) << "Permanent restore, object:" << dest_obj << dendl; } + // bucket->get_info().flags doesn't seem to be having rgw::sal::FLAG_LOG_OP // set by default. Hence set it explicity + // XXX: Do we need to check if sync is disabled for bucket. log_op = true; } diff --git a/src/rgw/driver/rados/rgw_rados.h b/src/rgw/driver/rados/rgw_rados.h index 7e8b3da8afb..01979125d93 100644 --- a/src/rgw/driver/rados/rgw_rados.h +++ b/src/rgw/driver/rados/rgw_rados.h @@ -358,6 +358,7 @@ class RGWRados int open_root_pool_ctx(const DoutPrefixProvider *dpp); int open_gc_pool_ctx(const DoutPrefixProvider *dpp); int open_lc_pool_ctx(const DoutPrefixProvider *dpp); + int open_restore_pool_ctx(const DoutPrefixProvider *dpp); int open_objexp_pool_ctx(const DoutPrefixProvider *dpp); int open_reshard_pool_ctx(const DoutPrefixProvider *dpp); int open_notif_pool_ctx(const DoutPrefixProvider *dpp); @@ -372,9 +373,11 @@ class RGWRados rgw::sal::RadosStore* driver{nullptr}; RGWGC* gc{nullptr}; RGWLC* lc{nullptr}; + RGWRestore* restore{nullptr}; RGWObjectExpirer* obj_expirer{nullptr}; bool use_gc_thread{false}; bool use_lc_thread{false}; + bool use_restore_thread{false}; bool quota_threads{false}; bool run_sync_thread{false}; bool run_reshard_thread{false}; @@ -449,6 +452,7 @@ protected: librados::IoCtx gc_pool_ctx; // .rgw.gc librados::IoCtx lc_pool_ctx; // .rgw.lc + librados::IoCtx restore_pool_ctx; // .rgw.restore librados::IoCtx objexp_pool_ctx; librados::IoCtx reshard_pool_ctx; librados::IoCtx notif_pool_ctx; // .rgw.notif @@ -500,6 +504,10 @@ public: return gc; } + RGWRestore *get_restore() { + return restore; + } + RGWRados& set_run_gc_thread(bool _use_gc_thread) { use_gc_thread = _use_gc_thread; return *this; @@ -510,6 +518,11 @@ public: return *this; } + RGWRados& set_run_restore_thread(bool _use_restore_thread) { + use_restore_thread = _use_restore_thread; + return *this; + } + RGWRados& set_run_quota_threads(bool _run_quota_threads) { quota_threads = _run_quota_threads; return *this; @@ -534,6 +547,10 @@ public: return &lc_pool_ctx; } + librados::IoCtx* get_restore_pool_ctx() { + return &restore_pool_ctx; + } + librados::IoCtx& get_notif_pool_ctx() { return notif_pool_ctx; } @@ -1258,13 +1275,11 @@ int restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx, RGWObjectCtx& obj_ctx, RGWBucketInfo& dest_bucket_info, const rgw_obj& dest_obj, - rgw_placement_rule& dest_placement, RGWObjTier& tier_config, - uint64_t olh_epoch, std::optional days, + bool& in_progress, const DoutPrefixProvider *dpp, - optional_yield y, - bool log_op = true); + optional_yield y); int check_bucket_empty(const DoutPrefixProvider *dpp, RGWBucketInfo& bucket_info, optional_yield y); diff --git a/src/rgw/driver/rados/rgw_sal_rados.cc b/src/rgw/driver/rados/rgw_sal_rados.cc index 99bb6b07136..0c324f3acc7 100644 --- a/src/rgw/driver/rados/rgw_sal_rados.cc +++ b/src/rgw/driver/rados/rgw_sal_rados.cc @@ -60,6 +60,7 @@ #include "rgw_tools.h" #include "rgw_tracer.h" #include "rgw_zone.h" +#include "rgw_restore.h" #include "services/svc_bilog_rados.h" #include "services/svc_bi_rados.h" @@ -2025,6 +2026,12 @@ std::unique_ptr RadosStore::get_lifecycle(void) return std::make_unique(this); } +std::unique_ptr RadosStore::get_restore(const int n_objs, + const std::vector& obj_names) +{ + return std::make_unique(this, n_objs, obj_names); +} + bool RadosStore::process_expired_objects(const DoutPrefixProvider *dpp, optional_yield y) { @@ -3031,15 +3038,13 @@ int RadosObject::transition(Bucket* bucket, int RadosObject::restore_obj_from_cloud(Bucket* bucket, rgw::sal::PlacementTier* tier, - rgw_placement_rule& placement_rule, - rgw_bucket_dir_entry& o, CephContext* cct, RGWObjTier& tier_config, uint64_t olh_epoch, std::optional days, + bool& in_progress, const DoutPrefixProvider* dpp, - optional_yield y, - uint32_t flags) + optional_yield y) { /* init */ rgw::sal::RadosPlacementTier* rtier = static_cast(tier); @@ -3051,7 +3056,27 @@ int RadosObject::restore_obj_from_cloud(Bucket* bucket, string bucket_name = rtier->get_rt().t.s3.target_path; const rgw::sal::ZoneGroup& zonegroup = store->get_zone()->get_zonegroup(); int ret = 0; - string src_storage_class = o.meta.storage_class; // or take src_placement also as input + + auto& attrs = get_attrs(); + RGWObjTier tier_config; + + auto attr_iter = attrs.find(RGW_ATTR_MANIFEST); + if (attr_iter != attrs.end()) { + RGWObjManifest m; + try { + using ceph::decode; + decode(m, attr_iter->second); + m.get_tier_config(&tier_config); + } catch (const buffer::end_of_buffer&) { + //empty manifest; it's not cloud-tiered + ldpp_dout(dpp, -1) << "Error reading manifest of object:" << get_key() << dendl; + return -EIO; + } catch (const std::exception& e) { + ldpp_dout(dpp, -1) << "Error reading manifest of object:" << get_key() << dendl; + return -EIO; + } + } + // update tier_config in case tier params are updated tier_config.tier_placement = rtier->get_rt(); @@ -3060,11 +3085,22 @@ int RadosObject::restore_obj_from_cloud(Bucket* bucket, "-cloud-bucket"; boost::algorithm::to_lower(bucket_name); } + + rgw_bucket_dir_entry ent; + ent.key.name = get_key().name; + ent.key.instance = get_key().instance; + ent.meta.accounted_size = ent.meta.size = get_obj_size(); + ent.meta.etag = "" ; + + if (!ent.key.instance.empty()) { // non-current versioned object + ent.flags |= rgw_bucket_dir_entry::FLAG_VER; + } + /* Create RGW REST connection */ S3RESTConn conn(cct, id, { endpoint }, key, zonegroup.get_id(), region, host_style); // save source cloudtier storage class - RGWLCCloudTierCtx tier_ctx(cct, dpp, o, store, bucket->get_info(), + RGWLCCloudTierCtx tier_ctx(cct, dpp, ent, store, bucket->get_info(), this, conn, bucket_name, rtier->get_rt().t.s3.target_storage_class); tier_ctx.acl_mappings = rtier->get_rt().t.s3.acl_mappings; @@ -3074,46 +3110,34 @@ int RadosObject::restore_obj_from_cloud(Bucket* bucket, tier_ctx.restore_storage_class = rtier->get_rt().restore_storage_class; tier_ctx.tier_type = rtier->get_rt().tier_type; - ldpp_dout(dpp, 20) << "Restoring object(" << o.key << ") from the cloud endpoint(" << endpoint << ")" << dendl; + ldpp_dout(dpp, 20) << "Restoring object(" << get_key() << ") from the cloud endpoint(" << endpoint << ")" << dendl; if (days && days == 0) { - ldpp_dout(dpp, 0) << "Days = 0 not valid; Not restoring object (" << o.key << ") from the cloud endpoint(" << endpoint << ")" << dendl; + ldpp_dout(dpp, 0) << "Days = 0 not valid; Not restoring object (" << get_key() << ") from the cloud endpoint(" << endpoint << ")" << dendl; return 0; } - // Note: For non-versioned objects, below should have already been set by the callers- - // o.current should be false; this(obj)->instance should have version-id. - - // set restore_status as RESTORE_ALREADY_IN_PROGRESS - ret = set_cloud_restore_status(dpp, y, RGWRestoreStatus::RestoreAlreadyInProgress); - if (ret < 0) { - ldpp_dout(dpp, 0) << " Setting cloud restore status to RESTORE_ALREADY_IN_PROGRESS for the object(" << o.key << ") from the cloud endpoint(" << endpoint << ") failed, ret=" << ret << dendl; - return ret; - } - /* Restore object from the cloud endpoint. * All restore related status and attrs are set as part of object download to * avoid any races */ ret = store->getRados()->restore_obj_from_cloud(tier_ctx, *rados_ctx, - bucket->get_info(), get_obj(), placement_rule, - tier_config, - olh_epoch, days, dpp, y, flags & FLAG_LOG_OP); + bucket->get_info(), get_obj(), + tier_config, days, in_progress, dpp, y); if (ret < 0) { //failed to restore - ldpp_dout(dpp, 0) << "Restoring object(" << o.key << ") from the cloud endpoint(" << endpoint << ") failed, ret=" << ret << dendl; - auto reset_ret = set_cloud_restore_status(dpp, y, RGWRestoreStatus::RestoreFailed); + ldpp_dout(dpp, 0) << "Restoring object(" << get_key() << ") from the cloud endpoint(" << endpoint << ") failed, ret=" << ret << dendl; rgw_placement_rule target_placement; target_placement.inherit_from(tier_ctx.bucket_info.placement_rule); target_placement.storage_class = tier->get_storage_class(); /* Reset HEAD object as CloudTiered */ - reset_ret = write_cloud_tier(dpp, y, tier_ctx.o.versioned_epoch, + int reset_ret = write_cloud_tier(dpp, y, tier_ctx.o.versioned_epoch, tier, tier_ctx.is_multipart_upload, target_placement, tier_ctx.obj); if (reset_ret < 0) { - ldpp_dout(dpp, 0) << " Reset to cloud_tier of object(" << o.key << ") from the cloud endpoint(" << endpoint << ") failed, ret=" << reset_ret << dendl; + ldpp_dout(dpp, 0) << " Reset to cloud_tier of object(" << get_key() << ") from the cloud endpoint(" << endpoint << ") failed, ret=" << reset_ret << dendl; } return ret; } @@ -3200,21 +3224,6 @@ int RadosObject::transition_to_cloud(Bucket* bucket, return ret; } -int RadosObject::set_cloud_restore_status(const DoutPrefixProvider* dpp, - optional_yield y, - rgw::sal::RGWRestoreStatus restore_status) -{ - int ret = 0; - set_atomic(true); - - bufferlist bl; - using ceph::encode; - encode(restore_status, bl); - ret = modify_obj_attrs(RGW_ATTR_RESTORE_STATUS, bl, y, dpp, false); - - return ret; -} - /* * If the object is restored temporarily and is expired, delete the data and * reset the HEAD object as cloud-transitioned. @@ -4624,6 +4633,208 @@ std::unique_ptr RadosLifecycle::get_serializer(const std::string& return std::make_unique(store, oid, lock_name, cookie); } +RadosRestoreSerializer::RadosRestoreSerializer(RadosStore* store, const std::string& _oid, const std::string& lock_name, const std::string& cookie) : + StoreRestoreSerializer(_oid), + ioctx(*store->getRados()->get_restore_pool_ctx()), + lock(lock_name) +{ + lock.set_cookie(cookie); +} + +int RadosRestoreSerializer::try_lock(const DoutPrefixProvider *dpp, utime_t dur, optional_yield y) +{ + lock.set_duration(dur); + return lock.lock_exclusive((librados::IoCtx*)(&ioctx), oid); +} + +std::unique_ptr RadosRestore::get_serializer( + const std::string& lock_name, + const std::string& oid, + const std::string& cookie) +{ + return std::make_unique(store, oid, lock_name, cookie); +} + +int RadosRestore::add_entry(const DoutPrefixProvider* dpp, optional_yield y, + int index, const RGWRestoreEntry& entry) { + bufferlist bl; + + encode(entry, bl); + + auto ret = push(dpp, y, index, std::move(bl)); + if (ret < 0) { + ldpp_dout(dpp, -1) << "ERROR: push() returned " << ret << dendl; + return ret; + } + + return 0; +} + + +int RadosRestore::add_entries(const DoutPrefixProvider* dpp, optional_yield y, + int index, const std::list& restore_entries) { + std::vector ent_list; + + for (auto& entry : restore_entries) { + bufferlist bl; + + encode(entry, bl); + ent_list.push_back(std::move(bl)); + + } + + int ret = push(dpp, y, index, std::move(ent_list)); + + if (ret < 0) { + ldpp_dout(dpp, -1) << "ERROR: push() returned " << ret << dendl; + return ret; + } + + return 0; +} + +int RadosRestore::push(const DoutPrefixProvider *dpp, optional_yield y, + int index, std::vector&& items) { + auto r = fifos[index].push(dpp, items, y); + if (r < 0) { + ldpp_dout(dpp, -1) << __PRETTY_FUNCTION__ + << ": unable to push to FIFO: " << obj_names[index] + << ": " << cpp_strerror(-r) << dendl; + } + return r; +} + +int RadosRestore::push(const DoutPrefixProvider *dpp, optional_yield y, + int index, ceph::buffer::list&& bl) { + auto r = fifos[index].push(dpp, std::move(bl), y); + if (r < 0) { + ldpp_dout(dpp, -1) << __PRETTY_FUNCTION__ + << ": unable to push to FIFO: " << obj_names[index] + << ": " << cpp_strerror(-r) << dendl; + } + return r; +} + +struct rgw_restore_fifo_entry { + std::string id; + ceph::real_time mtime; + RGWRestoreEntry entry; + rgw_restore_fifo_entry() {} + + void encode(ceph::buffer::list& bl) const { + ENCODE_START(1, 1, bl); + encode(id, bl); + encode(mtime, bl); + encode(entry, bl); + ENCODE_FINISH(bl); + } + + void decode(ceph::buffer::list::const_iterator& bl) { + DECODE_START(1, bl); + decode(id, bl); + decode(mtime, bl); + decode(entry, bl); + DECODE_FINISH(bl); + } + + void dump(ceph::Formatter* f) const; + void decode_json(JSONObj* obj); +}; +WRITE_CLASS_ENCODER(rgw_restore_fifo_entry) + +int RadosRestore::list(const DoutPrefixProvider *dpp, optional_yield y, + int index, const std::string& marker, std::string* out_marker, + uint32_t max_entries, std::vector& entries, + bool* truncated) +{ + std::vector restore_entries; + bool more = false; + + auto r = fifos[index].list(dpp, max_entries, marker, &restore_entries, &more, y); + + if (r < 0) { + ldpp_dout(dpp, -1) << __PRETTY_FUNCTION__ + << ": unable to list FIFO: " << obj_names[index] + << ": " << cpp_strerror(-r) << dendl; + return r; + } + + entries.clear(); + + for (const auto& entry : restore_entries) { + rgw_restore_fifo_entry r_entry; + r_entry.id = entry.marker; + r_entry.mtime = entry.mtime; + + auto liter = entry.data.cbegin(); + try { + decode(r_entry.entry, liter); + } catch (const buffer::error& err) { + ldpp_dout(dpp, -1) << __PRETTY_FUNCTION__ + << ": failed to decode restore entry: " + << err.what() << dendl; + return -EIO; + } + RGWRestoreEntry& e = r_entry.entry; + entries.push_back(std::move(e)); + } + + if (truncated) + *truncated = more; + + if (out_marker && !restore_entries.empty()) { + *out_marker = restore_entries.back().marker; + } + + return 0; +} + +int RadosRestore::trim_entries(const DoutPrefixProvider *dpp, optional_yield y, + int index, const std::string_view& marker) +{ + assert(index < num_objs); + + int ret = trim(dpp, y, index, marker); + return ret; +} + +int RadosRestore::trim(const DoutPrefixProvider *dpp, optional_yield y, + int index, const std::string_view& marker) { + auto r = fifos[index].trim(dpp, marker, false, y); + if (r < 0) { + ldpp_dout(dpp, -1) << __PRETTY_FUNCTION__ + << ": unable to trim FIFO: " << obj_names[index] + << ": " << cpp_strerror(-r) << dendl; + } + + return r; +} + +std::string_view RadosRestore::max_marker() { + static const std::string mm = rgw::cls::fifo::marker::max().to_string(); + return std::string_view(mm); +} + +int RadosRestore::is_empty(const DoutPrefixProvider *dpp, optional_yield y) { + std::vector restore_entries; + bool more = false; + + for (auto shard = 0u; shard < fifos.size(); ++shard) { + auto r = fifos[shard].list(dpp, 1, {}, &restore_entries, &more, y); + if (r < 0) { + ldpp_dout(dpp, -1) << __PRETTY_FUNCTION__ + << ": unable to list FIFO: " << obj_names[shard] + << ": " << cpp_strerror(-r) << dendl; + return r; + } + if (!restore_entries.empty()) { + return 0; + } + } + + return 1; +} + int RadosNotification::publish_reserve(const DoutPrefixProvider *dpp, RGWObjTags* obj_tags) { return rgw::notify::publish_reserve(dpp, *store->svc()->site, event_types, res, obj_tags); diff --git a/src/rgw/driver/rados/rgw_sal_rados.h b/src/rgw/driver/rados/rgw_sal_rados.h index 66c73a1199f..d0628b7c0a9 100644 --- a/src/rgw/driver/rados/rgw_sal_rados.h +++ b/src/rgw/driver/rados/rgw_sal_rados.h @@ -27,6 +27,7 @@ #include "rgw_putobj_processor.h" #include "services/svc_tier_rados.h" #include "cls/lock/cls_lock_client.h" +#include "rgw_log_backing.h" namespace rgw { namespace sal { @@ -282,6 +283,8 @@ class RadosStore : public StoreDriver { virtual int list_all_zones(const DoutPrefixProvider* dpp, std::list& zone_ids) override; virtual int cluster_stat(RGWClusterStat& stats) override; virtual std::unique_ptr get_lifecycle(void) override; + virtual std::unique_ptr get_restore(const int n_objs, + const std::vector& obj_names) override; virtual bool process_expired_objects(const DoutPrefixProvider *dpp, optional_yield y) override; virtual std::unique_ptr get_notification(rgw::sal::Object* obj, rgw::sal::Object* src_obj, req_state* s, rgw::notify::EventType event_type, optional_yield y, const std::string* object_name=nullptr) override; virtual std::unique_ptr get_notification( @@ -343,6 +346,7 @@ class RadosStore : public StoreDriver { optional_yield y, const DoutPrefixProvider* dpp) override; virtual RGWLC* get_rgwlc(void) override { return rados->get_lc(); } + virtual RGWRestore* get_rgwrestore(void) override { return rados->get_restore(); } virtual RGWCoroutinesManagerRegistry* get_cr_registry() override { return rados->get_cr_registry(); } virtual int log_usage(const DoutPrefixProvider *dpp, std::map& usage_info, optional_yield y) override; @@ -640,15 +644,11 @@ class RadosObject : public StoreObject { optional_yield y) override; virtual int restore_obj_from_cloud(Bucket* bucket, rgw::sal::PlacementTier* tier, - rgw_placement_rule& placement_rule, - rgw_bucket_dir_entry& o, CephContext* cct, - RGWObjTier& tier_config, - uint64_t olh_epoch, std::optional days, + bool& in_progress, const DoutPrefixProvider* dpp, - optional_yield y, - uint32_t flags) override; + optional_yield y) override; virtual bool placement_rules_match(rgw_placement_rule& r1, rgw_placement_rule& r2) override; virtual int dump_obj_layout(const DoutPrefixProvider *dpp, optional_yield y, Formatter* f) override; @@ -952,6 +952,63 @@ public: const std::string& cookie) override; }; +class RadosRestoreSerializer : public StoreRestoreSerializer { + librados::IoCtx& ioctx; + ::rados::cls::lock::Lock lock; + +public: + RadosRestoreSerializer(RadosStore* store, const std::string& oid, const std::string& lock_name, const std::string& cookie); + + virtual int try_lock(const DoutPrefixProvider *dpp, utime_t dur, optional_yield y) override; + virtual int unlock() override { + return lock.unlock(&ioctx, oid); + } +}; + +class RadosRestore : public Restore { + RadosStore* store; + const int num_objs; + const std::vector& obj_names; + librados::IoCtx& ioctx; + ceph::containers::tiny_vector fifos; + +public: + RadosRestore(RadosStore* _st, const int n_objs, const std::vector& o_names) : store(_st), + num_objs(n_objs), obj_names(o_names), + ioctx(*store->getRados()->get_restore_pool_ctx()), + fifos(num_objs, + [&](const size_t ix, auto emplacer) { + emplacer.emplace(ioctx, std::string(obj_names[ix])); + }) {} + + ~RadosRestore() override = default; + + virtual int add_entry(const DoutPrefixProvider* dpp, optional_yield y, + int index, const RGWRestoreEntry& r_entry) override; + virtual int add_entries(const DoutPrefixProvider* dpp, optional_yield y, + int index, const std::list& restore_entries) override; + virtual int list(const DoutPrefixProvider *dpp, optional_yield y, + int index, + const std::string& marker, std::string* out_marker, + uint32_t max_entries, std::vector& entries, + bool* truncated) override; + virtual int trim_entries(const DoutPrefixProvider *dpp, optional_yield y, + int index, const std::string_view& marker) override; + virtual std::unique_ptr get_serializer( + const std::string& lock_name, + const std::string& oid, + const std::string& cookie) override; + /** Below routines deal with actual FIFO */ + int is_empty(const DoutPrefixProvider *dpp, optional_yield y); + std::string_view max_marker(); + int trim(const DoutPrefixProvider *dpp, optional_yield y, + int index, const std::string_view& marker); + int push(const DoutPrefixProvider *dpp, optional_yield y, + int index, ceph::buffer::list&& bl); + int push(const DoutPrefixProvider *dpp, optional_yield y, + int index, std::vector&& items); +}; + class RadosNotification : public StoreNotification { RadosStore* store; /* XXX it feels incorrect to me that rgw::notify::reservation_t is diff --git a/src/rgw/driver/rados/rgw_zone.h b/src/rgw/driver/rados/rgw_zone.h index 7860bad50f8..eacb3058487 100644 --- a/src/rgw/driver/rados/rgw_zone.h +++ b/src/rgw/driver/rados/rgw_zone.h @@ -127,6 +127,8 @@ struct RGWZoneParams : RGWSystemMetaObj { JSONFormattable tier_config; + rgw_pool restore_pool; + RGWZoneParams() : RGWSystemMetaObj() {} explicit RGWZoneParams(const std::string& name) : RGWSystemMetaObj(name){} RGWZoneParams(const rgw_zone_id& id, const std::string& name) : RGWSystemMetaObj(id.id, name) {} @@ -154,7 +156,7 @@ struct RGWZoneParams : RGWSystemMetaObj { const std::string& get_compression_type(const rgw_placement_rule& placement_rule) const; void encode(bufferlist& bl) const override { - ENCODE_START(16, 1, bl); + ENCODE_START(17, 1, bl); encode(domain_root, bl); encode(control_pool, bl); encode(gc_pool, bl); @@ -184,11 +186,12 @@ struct RGWZoneParams : RGWSystemMetaObj { encode(account_pool, bl); encode(group_pool, bl); encode(dedup_pool, bl); + encode(restore_pool, bl); ENCODE_FINISH(bl); } void decode(bufferlist::const_iterator& bl) override { - DECODE_START(16, bl); + DECODE_START(17, bl); decode(domain_root, bl); decode(control_pool, bl); decode(gc_pool, bl); @@ -271,6 +274,11 @@ struct RGWZoneParams : RGWSystemMetaObj { } else { dedup_pool = name + ".rgw.dedup"; } + if (struct_v >= 17) { + decode(restore_pool, bl); + } else { + restore_pool = log_pool.name + ":restore"; + } DECODE_FINISH(bl); } void dump(Formatter *f) const; diff --git a/src/rgw/radosgw-admin/radosgw-admin.cc b/src/rgw/radosgw-admin/radosgw-admin.cc index a28f3bd1e53..d5f80ce2e0f 100644 --- a/src/rgw/radosgw-admin/radosgw-admin.cc +++ b/src/rgw/radosgw-admin/radosgw-admin.cc @@ -4635,6 +4635,7 @@ int main(int argc, const char **argv) false, false, false, + false, false, false, // No background tasks! null_yield, diff --git a/src/rgw/rgw_appmain.cc b/src/rgw/rgw_appmain.cc index 939768f0524..4a2142a7951 100644 --- a/src/rgw/rgw_appmain.cc +++ b/src/rgw/rgw_appmain.cc @@ -234,6 +234,10 @@ int rgw::AppMain::init_storage() (g_conf()->rgw_enable_lc_threads && ((!nfs) || (nfs && g_conf()->rgw_nfs_run_lc_threads))); + auto run_restore = + (g_conf()->rgw_enable_restore_threads && + ((!nfs) || (nfs && g_conf()->rgw_nfs_run_restore_threads))); + auto run_quota = (g_conf()->rgw_enable_quota_threads && ((!nfs) || (nfs && g_conf()->rgw_nfs_run_quota_threads))); @@ -250,6 +254,7 @@ int rgw::AppMain::init_storage() site, run_gc, run_lc, + run_restore, run_quota, run_sync, g_conf().get_val("rgw_dynamic_resharding"), diff --git a/src/rgw/rgw_lc.h b/src/rgw/rgw_lc.h index a813a7c28ac..73305c6f19c 100644 --- a/src/rgw/rgw_lc.h +++ b/src/rgw/rgw_lc.h @@ -566,6 +566,7 @@ class RGWLC : public DoutPrefixProvider { CephContext *cct; rgw::sal::Driver* driver; std::unique_ptr sal_lc; + std::unique_ptr sal_restore; int max_objs{0}; std::string *obj_names{nullptr}; std::atomic down_flag = { false }; @@ -664,6 +665,7 @@ public: CephContext *get_cct() const override { return cct; } rgw::sal::Lifecycle* get_lc() const { return sal_lc.get(); } + rgw::sal::Restore* get_restore() const { return sal_restore.get(); } unsigned get_subsys() const; std::ostream& gen_prefix(std::ostream& out) const; diff --git a/src/rgw/rgw_object_expirer.cc b/src/rgw/rgw_object_expirer.cc index aa71315aa76..f12737feffd 100644 --- a/src/rgw/rgw_object_expirer.cc +++ b/src/rgw/rgw_object_expirer.cc @@ -105,7 +105,7 @@ int main(const int argc, const char **argv) exit(1); } - driver = DriverManager::get_storage(&dp, g_ceph_context, cfg, context_pool, site, false, false, false, false, false, false, true, null_yield); + driver = DriverManager::get_storage(&dp, g_ceph_context, cfg, context_pool, site, false, false, false, false, false, false, false, true, null_yield); if (!driver) { std::cerr << "couldn't init storage provider" << std::endl; return EIO; diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index e2113110867..1bd8824fa45 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -68,6 +68,7 @@ #include "rgw_iam_managed_policy.h" #include "rgw_bucket_sync.h" #include "rgw_bucket_logging.h" +#include "rgw_restore.h" #include "services/svc_zone.h" #include "services/svc_quota.h" @@ -986,13 +987,13 @@ void handle_replication_status_header( */ int handle_cloudtier_obj(req_state* s, const DoutPrefixProvider *dpp, rgw::sal::Driver* driver, rgw::sal::Attrs& attrs, bool sync_cloudtiered, std::optional days, - bool restore_op, optional_yield y) + bool read_through, optional_yield y) { int op_ret = 0; ldpp_dout(dpp, 20) << "reached handle cloud tier " << dendl; auto attr_iter = attrs.find(RGW_ATTR_MANIFEST); if (attr_iter == attrs.end()) { - if (restore_op) { + if (!read_through) { op_ret = -ERR_INVALID_OBJECT_STATE; s->err.message = "only cloud tier object can be restored"; return op_ret; @@ -1005,7 +1006,7 @@ int handle_cloudtier_obj(req_state* s, const DoutPrefixProvider *dpp, rgw::sal:: decode(m, attr_iter->second); if (!m.is_tier_type_s3()) { ldpp_dout(dpp, 20) << "not a cloud tier object " << s->object->get_key().name << dendl; - if (restore_op) { + if (!read_through) { op_ret = -ERR_INVALID_OBJECT_STATE; s->err.message = "only cloud tier object can be restored"; return op_ret; @@ -1030,26 +1031,45 @@ int handle_cloudtier_obj(req_state* s, const DoutPrefixProvider *dpp, rgw::sal:: auto iter = bl.cbegin(); decode(restore_status, iter); } - if (attr_iter == attrs.end() || restore_status == rgw::sal::RGWRestoreStatus::RestoreFailed) { - // first time restore or previous restore failed - rgw::sal::Bucket* pbucket = NULL; - pbucket = s->bucket.get(); - + if (restore_status == rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress) { + if (read_through) { + op_ret = -ERR_REQUEST_TIMEOUT; + ldpp_dout(dpp, 5) << "restore is still in progress, please check restore status and retry" << dendl; + s->err.message = "restore is still in progress"; + return op_ret; + } else { + // for restore-op, corresponds to RESTORE_ALREADY_IN_PROGRESS + return static_cast(rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress); + } + } else if (restore_status == rgw::sal::RGWRestoreStatus::CloudRestored) { + // corresponds to CLOUD_RESTORED + return static_cast(rgw::sal::RGWRestoreStatus::CloudRestored); + } else { // first time restore or previous restore failed. + // Restore the object. std::unique_ptr tier; rgw_placement_rule target_placement; - target_placement.inherit_from(pbucket->get_placement_rule()); - attr_iter = attrs.find(RGW_ATTR_STORAGE_CLASS); + target_placement.inherit_from(s->bucket->get_placement_rule()); + auto attr_iter = attrs.find(RGW_ATTR_STORAGE_CLASS); if (attr_iter != attrs.end()) { target_placement.storage_class = attr_iter->second.to_str(); } op_ret = driver->get_zone()->get_zonegroup().get_placement_tier(target_placement, &tier); - ldpp_dout(dpp, 20) << "getting tier placement handle cloud tier" << op_ret << - " storage class " << target_placement.storage_class << dendl; if (op_ret < 0) { - s->err.message = "failed to restore object"; + ldpp_dout(dpp, -1) << "failed to fetch tier placement handle, ret = " << op_ret << dendl; return op_ret; + } else { + ldpp_dout(dpp, 20) << "getting tier placement handle cloud tier for " << + " storage class " << target_placement.storage_class << dendl; } - if (!restore_op) { + + if (!tier->is_tier_type_s3()) { + ldpp_dout(dpp, -1) << "ERROR: not s3 tier type - " << tier->get_tier_type() << + " for storage class " << target_placement.storage_class << dendl; + s->err.message = "failed to restore object"; + return -EINVAL; + } + + if (read_through) { if (tier->allow_read_through()) { days = tier->get_read_through_restore_days(); } else { //read-through is not enabled @@ -1058,56 +1078,30 @@ int handle_cloudtier_obj(req_state* s, const DoutPrefixProvider *dpp, rgw::sal:: return op_ret; } } - // fill in the entry. XXX: Maybe we can avoid it by passing only necessary params - rgw_bucket_dir_entry ent; - ent.key.name = s->object->get_key().name; - ent.key.instance = s->object->get_key().instance; - ent.meta.accounted_size = ent.meta.size = s->obj_size; - ent.meta.etag = "" ; - uint64_t epoch = 0; - op_ret = get_system_versioning_params(s, &epoch, NULL); - if (!ent.key.instance.empty()) { // non-current versioned object - ent.flags |= rgw_bucket_dir_entry::FLAG_VER; - } - ldpp_dout(dpp, 20) << "getting versioning params tier placement handle cloud tier" << op_ret << dendl; - if (op_ret < 0) { - ldpp_dout(dpp, 20) << "failed to get versioning params, op_ret = " << op_ret << dendl; - s->err.message = "failed to restore object"; - return op_ret; - } - op_ret = s->object->restore_obj_from_cloud(pbucket, tier.get(), target_placement, ent, - s->cct, tier_config, epoch, - days, dpp, y, s->bucket->get_info().flags); + + op_ret = driver->get_rgwrestore()->restore_obj_from_cloud(s->bucket.get(), + s->object.get(), tier.get(), days, y); + if (op_ret < 0) { - ldpp_dout(dpp, 0) << "object " << ent.key.name << " fetching failed" << op_ret << dendl; + ldpp_dout(dpp, 0) << "Restore of object " << s->object->get_key() << " failed" << op_ret << dendl; s->err.message = "failed to restore object"; return op_ret; } - ldpp_dout(dpp, 20) << "object " << ent.key.name << " fetching succeed" << dendl; - /* Even if restore is complete the first read through request will return but actually downloaded - * object asyncronously. + + ldpp_dout(dpp, 20) << "Restore of object " << s->object->get_key() << " succeed" << dendl; + /* Even if restore is complete the first read through request will return + * but actually downloaded object asyncronously. */ - if (!restore_op) { //read-through + if (read_through) { //read-through op_ret = -ERR_REQUEST_TIMEOUT; ldpp_dout(dpp, 5) << "restore is still in progress, please check restore status and retry" << dendl; s->err.message = "restore is still in progress"; } return op_ret; - } else if (restore_status == rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress) { - if (!restore_op) { - op_ret = -ERR_REQUEST_TIMEOUT; - ldpp_dout(dpp, 5) << "restore is still in progress, please check restore status and retry" << dendl; - s->err.message = "restore is still in progress"; - return op_ret; - } else { - return 1; // for restore-op, corresponds to RESTORE_ALREADY_IN_PROGRESS - } - } else { - return 2; // corresponds to CLOUD_RESTORED } } catch (const buffer::end_of_buffer&) { //empty manifest; it's not cloud-tiered - if (restore_op) { + if (!read_through) { op_ret = -ERR_INVALID_OBJECT_STATE; s->err.message = "only cloud tier object can be restored"; } @@ -2590,7 +2584,7 @@ void RGWGetObj::execute(optional_yield y) if (get_type() == RGW_OP_GET_OBJ && get_data) { std::optional days; - op_ret = handle_cloudtier_obj(s, this, driver, attrs, sync_cloudtiered, days, false, y); + op_ret = handle_cloudtier_obj(s, this, driver, attrs, sync_cloudtiered, days, true, y); if (op_ret < 0) { ldpp_dout(this, 4) << "Cannot get cloud tiered object: " << *s->object <<". Failing with " << op_ret << dendl; diff --git a/src/rgw/rgw_realm_reloader.cc b/src/rgw/rgw_realm_reloader.cc index 32c35b2bb43..d63c8fe3c5d 100644 --- a/src/rgw/rgw_realm_reloader.cc +++ b/src/rgw/rgw_realm_reloader.cc @@ -125,6 +125,7 @@ void RGWRealmReloader::reload() *env.site, cct->_conf->rgw_enable_gc_threads, cct->_conf->rgw_enable_lc_threads, + cct->_conf->rgw_enable_restore_threads, cct->_conf->rgw_enable_quota_threads, cct->_conf->rgw_run_sync_thread, cct->_conf.get_val("rgw_dynamic_resharding"), diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 7b15308073d..7e413ed5b58 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -565,41 +565,45 @@ int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, } } } /* checksum_mode */ - auto attr_iter = attrs.find(RGW_ATTR_RESTORE_TYPE); - if (attr_iter != attrs.end()) { - rgw::sal::RGWRestoreType rt; - bufferlist bl = attr_iter->second; - auto iter = bl.cbegin(); - decode(rt, iter); + + rgw::sal::RGWRestoreStatus restore_status; + auto r_iter = attrs.find(RGW_ATTR_RESTORE_STATUS); + if (r_iter != attrs.end()) { + bufferlist rbl = r_iter->second; + auto iter = rbl.cbegin(); + decode(restore_status, iter); - rgw::sal::RGWRestoreStatus restore_status; - attr_iter = attrs.find(RGW_ATTR_RESTORE_STATUS); - if (attr_iter != attrs.end()) { - bufferlist bl = attr_iter->second; - auto iter = bl.cbegin(); - decode(restore_status, iter); - } - //restore status if (restore_status == rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress) { - dump_header(s, "x-amz-restore", "ongoing-request=\"true\""); - } - if (rt == rgw::sal::RGWRestoreType::Temporary) { - auto expire_iter = attrs.find(RGW_ATTR_RESTORE_EXPIRY_DATE); - ceph::real_time expiration_date; - - if (expire_iter != attrs.end()) { - bufferlist bl = expire_iter->second; + dump_header(s, "x-amz-restore", "ongoing-request=\"true\""); + } else { + auto attr_iter = attrs.find(RGW_ATTR_RESTORE_TYPE); + if (attr_iter != attrs.end()) { + rgw::sal::RGWRestoreType rt; + bufferlist bl = attr_iter->second; auto iter = bl.cbegin(); - decode(expiration_date, iter); - } - //restore status - dump_header_if_nonempty(s, "x-amz-restore", "ongoing-request=\"false\", expiry-date=\""+ dump_time_to_str(expiration_date) +"\""); - // temporary restore; set storage-class to cloudtier storage class - auto c_iter = attrs.find(RGW_ATTR_CLOUDTIER_STORAGE_CLASS); + decode(rt, iter); - if (c_iter != attrs.end()) { - attrs[RGW_ATTR_STORAGE_CLASS] = c_iter->second; + if (rt == rgw::sal::RGWRestoreType::Temporary) { + auto expire_iter = attrs.find(RGW_ATTR_RESTORE_EXPIRY_DATE); + ceph::real_time expiration_date; + + if (expire_iter != attrs.end()) { + bufferlist bl = expire_iter->second; + auto iter = bl.cbegin(); + decode(expiration_date, iter); + } + + //restore status + dump_header_if_nonempty(s, "x-amz-restore", "ongoing-request=\"false\", expiry-date=\""+ dump_time_to_str(expiration_date) +"\""); + + // temporary restore; set storage-class to cloudtier storage class + auto c_iter = attrs.find(RGW_ATTR_CLOUDTIER_STORAGE_CLASS); + + if (c_iter != attrs.end()) { + attrs[RGW_ATTR_STORAGE_CLASS] = c_iter->second; + } + } } } } @@ -3663,35 +3667,40 @@ void RGWRestoreObj_ObjStore_S3::send_response() if (restore_ret == 0) { s->err.http_ret = 202; // OK - } else if (restore_ret == 1) { - restore_ret = -ERR_RESTORE_ALREADY_IN_PROGRESS; - set_req_state_err(s, restore_ret); - dump_header(s, "x-amz-restore", "ongoing-request=\"true\""); - } else if (restore_ret == 2) { - rgw::sal::Attrs attrs; - ceph::real_time expiration_date; - rgw::sal::RGWRestoreType rt; - attrs = s->object->get_attrs(); - auto expire_iter = attrs.find(RGW_ATTR_RESTORE_EXPIRY_DATE); - auto type_iter = attrs.find(RGW_ATTR_RESTORE_TYPE); - - if (expire_iter != attrs.end()) { - bufferlist bl = expire_iter->second; - auto iter = bl.cbegin(); - decode(expiration_date, iter); - } - - if (type_iter != attrs.end()) { - bufferlist bl = type_iter->second; - auto iter = bl.cbegin(); - decode(rt, iter); - } - if (rt == rgw::sal::RGWRestoreType::Temporary) { - s->err.http_ret = 200; // OK - dump_header(s, "x-amz-restore", "ongoing-request=\"false\", expiry-date=\""+ dump_time_to_str(expiration_date) +"\""); - } else { - s->err.http_ret = 200; - dump_header(s, "x-amz-restore", "ongoing-request=\"false\""); + } else { + rgw::sal::RGWRestoreStatus st = static_cast(restore_ret); + + if (st == rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress) { + restore_ret = -ERR_RESTORE_ALREADY_IN_PROGRESS; + set_req_state_err(s, restore_ret); + dump_header(s, "x-amz-restore", "ongoing-request=\"true\""); + } else if (st == rgw::sal::RGWRestoreStatus::CloudRestored) { + rgw::sal::Attrs attrs; + ceph::real_time expiration_date; + rgw::sal::RGWRestoreType rt; + attrs = s->object->get_attrs(); + auto expire_iter = attrs.find(RGW_ATTR_RESTORE_EXPIRY_DATE); + auto type_iter = attrs.find(RGW_ATTR_RESTORE_TYPE); + + if (expire_iter != attrs.end()) { + bufferlist bl = expire_iter->second; + auto iter = bl.cbegin(); + decode(expiration_date, iter); + } + + if (type_iter != attrs.end()) { + bufferlist bl = type_iter->second; + auto iter = bl.cbegin(); + decode(rt, iter); + } + + if (rt == rgw::sal::RGWRestoreType::Temporary) { + s->err.http_ret = 200; // OK + dump_header(s, "x-amz-restore", "ongoing-request=\"false\", expiry-date=\""+ dump_time_to_str(expiration_date) +"\""); + } else { + s->err.http_ret = 200; + dump_header(s, "x-amz-restore", "ongoing-request=\"false\""); + } } } diff --git a/src/rgw/rgw_restore.cc b/src/rgw/rgw_restore.cc new file mode 100644 index 00000000000..fb734b33010 --- /dev/null +++ b/src/rgw/rgw_restore.cc @@ -0,0 +1,520 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "include/scope_guard.h" +#include "include/function2.hpp" +#include "common/Formatter.h" +#include "common/containers.h" +#include "common/split.h" +#include +#include "include/random.h" +#include "cls/lock/cls_lock_client.h" +#include "rgw_perf_counters.h" +#include "rgw_common.h" +#include "rgw_bucket.h" +#include "rgw_restore.h" +#include "rgw_zone.h" +#include "rgw_string.h" +#include "rgw_multi.h" +#include "rgw_sal.h" +#include "rgw_lc_tier.h" +#include "rgw_notify.h" +#include "common/dout.h" + +#include "fmt/format.h" + +#include "services/svc_sys_obj.h" +#include "services/svc_zone.h" +#include "services/svc_tier_rados.h" + +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_rgw_restore + + +constexpr int32_t hours_in_a_day = 24; +constexpr int32_t secs_in_a_day = hours_in_a_day * 60 * 60; + +using namespace std; +using namespace rgw::sal; + +void RGWRestoreEntry::dump(Formatter *f) const +{ + encode_json("Bucket", bucket, f); + encode_json("Object", obj_key, f); + if (days) { + encode_json("Days", days, f); + } else { + encode_json("Days", 0, f); + } + encode_json("Zone_id", zone_id, f); + encode_json("Status", static_cast(status), f); +} + +void RGWRestoreEntry::decode_json(JSONObj *obj) +{ + JSONDecoder::decode_json("Bucket", bucket, obj); + JSONDecoder::decode_json("Object", obj_key, obj); + JSONDecoder::decode_json("Days", days, obj); + JSONDecoder::decode_json("Zone_id", zone_id, obj); + int st; + JSONDecoder::decode_json("Status", st, obj); + status = static_cast(st); +} + +void RGWRestoreEntry::generate_test_instances(std::list& l) +{ + auto p = new RGWRestoreEntry; + rgw_bucket bk("tenant1", "bucket1"); + rgw_obj_key obj("object1"); + rgw_obj_key obj2("object2"); + uint64_t days1 = 3; + std::optional days; + std::string zone_id = "zone1"; + rgw::sal::RGWRestoreStatus status = rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress; + + p->bucket = bk; + p->obj_key = obj; + p->zone_id = zone_id; + p->days = days; + p->status = status; + l.push_back(p); + + p = new RGWRestoreEntry; + days = days1; + status = rgw::sal::RGWRestoreStatus::CloudRestored; + p->bucket = bk; + p->obj_key = obj2; + p->zone_id = zone_id; + p->days = days; + p->status = status; + l.push_back(p); + + l.push_back(new RGWRestoreEntry); +} + +void RGWRestore::initialize(CephContext *_cct, rgw::sal::Driver* _driver) { + cct = _cct; + driver = _driver; + + /* max_objs indicates the number of shards or objects + * used to store Restore Entries */ + max_objs = cct->_conf->rgw_restore_max_objs; + if (max_objs > HASH_PRIME) + max_objs = HASH_PRIME; + + for (int i = 0; i < max_objs; i++) { + obj_names.push_back(fmt::format("{}.{}", restore_oid_prefix, i)); + } + sal_restore = driver->get_restore(max_objs, obj_names); +} + +void RGWRestore::finalize() +{ + obj_names.clear(); +} + +static inline std::ostream& operator<<(std::ostream &os, RGWRestoreEntry& ent) { + os << ""; + + return os; +} + +void RGWRestore::RestoreWorker::stop() +{ + std::lock_guard l{lock}; + cond.notify_all(); +} + +bool RGWRestore::going_down() +{ + return down_flag; +} + +void RGWRestore::start_processor() +{ + worker = std::make_unique(this, cct, this); + worker->create("rgw_restore"); +} + +void RGWRestore::stop_processor() +{ + down_flag = true; + if (worker) { + worker->stop(); + worker->join(); + } + worker.reset(nullptr); +} + +unsigned RGWRestore::get_subsys() const +{ + return dout_subsys; +} + +std::ostream& RGWRestore::gen_prefix(std::ostream& out) const +{ + return out << "restore: "; +} + +/* Hash based on both */ +int RGWRestore::choose_oid(const RGWRestoreEntry& e) { + int index; + const auto& name = e.bucket.name + e.obj_key.name + e.obj_key.instance; + index = ((ceph_str_hash_linux(name.data(), name.size())) % max_objs); + return static_cast(index); +} + +void *RGWRestore::RestoreWorker::entry() { + do { + utime_t start = ceph_clock_now(); + int r = 0; + r = restore->process(this, null_yield); + if (r < 0) { + ldpp_dout(dpp, 0) << "ERROR: restore process() returned error r=" << r << dendl; + } + if (restore->going_down()) + break; + utime_t end = ceph_clock_now(); + end -= start; + int secs = cct->_conf->rgw_restore_processor_period; + + if (secs <= end.sec()) + continue; // next round + + secs -= end.sec(); + std::unique_lock locker{lock}; + cond.wait_for(locker, std::chrono::seconds(secs)); + } while (!restore->going_down()); + + return NULL; + +} + +int RGWRestore::process(RestoreWorker* worker, optional_yield y) +{ + int max_secs = cct->_conf->rgw_restore_lock_max_time; + + const int start = ceph::util::generate_random_number(0, max_objs - 1); + for (int i = 0; i < max_objs; i++) { + int index = (i + start) % max_objs; + int ret = process(index, max_secs, y); + if (ret < 0) + return ret; + } + return 0; +} + +/* + * Given an index, fetch a list of restore entries to process. After each + * iteration, trim the list to the last marker read. + * + * While processing the entries, if any of their restore operation is still in + * progress, such entries are added back to the list. + */ +int RGWRestore::process(int index, int max_secs, optional_yield y) +{ + ldpp_dout(this, 20) << "RGWRestore::process entered with Restore index_shard=" + << index << ", max_secs=" << max_secs << dendl; + + /* list used to gather still IN_PROGRESS */ + std::list r_entries; + + std::unique_ptr serializer = + sal_restore->get_serializer(std::string(restore_index_lock_name), + std::string(obj_names[index]), + worker->thr_name()); + utime_t end = ceph_clock_now(); + + /* max_secs should be greater than zero. We don't want a zero max_secs + * to be translated as no timeout, since we'd then need to break the + * lock and that would require a manual intervention. In this case + * we can just wait it out. */ + + if (max_secs <= 0) + return -EAGAIN; + + end += max_secs; + utime_t time(max_secs, 0); + int ret = serializer->try_lock(this, time, null_yield); + if (ret == -EBUSY || ret == -EEXIST) { + /* already locked by another lc processor */ + ldpp_dout(this, 0) << "RGWRestore::process() failed to acquire lock on " + << obj_names[index] << dendl; + return -EBUSY; + } + if (ret < 0) + return 0; + + std::unique_lock lock(*(serializer.get()), std::adopt_lock); + std::string marker; + std::string next_marker; + bool truncated = false; + + do { + int max = 100; + std::vector entries; + + ret = sal_restore->list(this, y, index, marker, &next_marker, max, entries, &truncated); + ldpp_dout(this, 20) << + "RGWRestore::process sal_restore->list returned with returned:" << ret << + ", entries.size=" << entries.size() << ", truncated=" << truncated << + ", next_marker='" << next_marker << "'" << dendl; + + if (entries.size() == 0) { + lock.unlock(); + return 0; + } + + if (ret < 0) + goto done; + + marker = next_marker; + std::vector::iterator iter; + for (iter = entries.begin(); iter != entries.end(); ++iter) { + RGWRestoreEntry entry = *iter; + + ret = process_restore_entry(entry, y); + + if (entry.status == rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress) { + r_entries.push_back(entry); + } + + ///process all entries, trim and re-add + utime_t now = ceph_clock_now(); + if (now >= end) { + goto done; + } + + if (going_down()) { + // leave early, even if tag isn't removed, it's ok since it + // will be picked up next time around + goto done; + } + } + ret = sal_restore->trim_entries(this, y, index, marker); + if (ret < 0) { + ldpp_dout(this, 0) << "RGWRestore::process() failed to trim entries on " + << obj_names[index] << dendl; + } + + if (!r_entries.empty()) { + ret = sal_restore->add_entries(this, y, index, r_entries); + if (ret < 0) { + ldpp_dout(this, 0) << "RGWRestore::process() failed to add entries on " + << obj_names[index] << dendl; + } + } + + r_entries.clear(); + } while (truncated); + +done: + lock.unlock(); + + return 0; +} + +int RGWRestore::process_restore_entry(RGWRestoreEntry& entry, optional_yield y) +{ + int ret = 0; + std::unique_ptr bucket; + std::unique_ptr obj; + std::unique_ptr tier; + std::optional days = entry.days; + // Ensure its the same source zone processing temp entries as we do not + // replicate temp restored copies + if (days) { // temp copy + auto& zone_id = entry.zone_id; + if (driver->get_zone()->get_id() != zone_id) { + // XXX: Do we need to check if the zone is still valid + return 0; // skip processing this entry in this zone + } + } + + // fill in the details from entry + // bucket, obj, days, state=in_progress + ret = driver->load_bucket(this, entry.bucket, &bucket, null_yield); + if (ret < 0) { + ldpp_dout(this, 0) << "Restore:get_bucket for " << bucket->get_name() + << " failed" << dendl; + return ret; + } + obj = bucket->get_object(entry.obj_key); + + ret = obj->load_obj_state(this, null_yield, true); + + if (ret < 0) { + ldpp_dout(this, 0) << "Restore:get_object for " << entry.obj_key + << " failed" << dendl; + return ret; + } + + rgw_placement_rule target_placement; + target_placement.inherit_from(bucket->get_placement_rule()); + + auto& attrs = obj->get_attrs(); + auto attr_iter = attrs.find(RGW_ATTR_RESTORE_STATUS); + rgw::sal::RGWRestoreStatus restore_status = rgw::sal::RGWRestoreStatus::None; + if (attr_iter != attrs.end()) { + bufferlist bl = attr_iter->second; + auto iter = bl.cbegin(); + decode(restore_status, iter); + } + if (restore_status == rgw::sal::RGWRestoreStatus::CloudRestored) { + // XXX: Check if expiry-date needs to be update + ldpp_dout(this, 20) << "Restore of object " << obj->get_key() << " already done" << dendl; + entry.status = rgw::sal::RGWRestoreStatus::CloudRestored; + return 0; + } + + attr_iter = attrs.find(RGW_ATTR_STORAGE_CLASS); + if (attr_iter != attrs.end()) { + target_placement.storage_class = attr_iter->second.to_str(); + } + ret = driver->get_zone()->get_zonegroup().get_placement_tier(target_placement, &tier); + + if (ret < 0) { + ldpp_dout(this, -1) << "failed to fetch tier placement handle, ret = " << ret << dendl; + return ret; + } else { + ldpp_dout(this, 20) << "getting tier placement handle cloud tier for " << + " storage class " << target_placement.storage_class << dendl; + } + + if (!tier->is_tier_type_s3()) { + ldpp_dout(this, -1) << "ERROR: not s3 tier type - " << tier->get_tier_type() << + " for storage class " << target_placement.storage_class << dendl; + return -EINVAL; + } + + // now go ahead with restoring object + // XXX: first check if its already restored? + bool in_progress = true; + ret = obj->restore_obj_from_cloud(bucket.get(), tier.get(), cct, days, in_progress, + this, y); + if (ret < 0) { + ldpp_dout(this, -1) << "Restore of object(" << obj->get_key() << ") failed" << ret << dendl; + auto reset_ret = set_cloud_restore_status(this, obj.get(), y, rgw::sal::RGWRestoreStatus::RestoreFailed); + entry.status = rgw::sal::RGWRestoreStatus::RestoreFailed; + if (reset_ret < 0) { + ldpp_dout(this, -1) << "Setting restore status ad RestoreFailed failed for object(" << obj->get_key() << ") " << reset_ret << dendl; + } + return ret; + } + + if (in_progress) { + ldpp_dout(this, 20) << "Restore of object " << obj->get_key() << " still in progress" << dendl; + entry.status = rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress; + } else { + ldpp_dout(this, 20) << "Restore of object " << obj->get_key() << " succeeded" << dendl; + entry.status = rgw::sal::RGWRestoreStatus::RestoreFailed; + } + return ret; +} + +time_t RGWRestore::thread_stop_at() +{ + uint64_t interval = (cct->_conf->rgw_restore_debug_interval > 0) + ? cct->_conf->rgw_restore_debug_interval : secs_in_a_day; + + return time(nullptr) + interval; +} + +int RGWRestore::set_cloud_restore_status(const DoutPrefixProvider* dpp, + rgw::sal::Object* pobj, optional_yield y, + const rgw::sal::RGWRestoreStatus& restore_status) +{ + int ret = -1; + + if (!pobj) + return ret; + + pobj->set_atomic(); + + bufferlist bl; + using ceph::encode; + encode(restore_status, bl); + + ret = pobj->modify_obj_attrs(RGW_ATTR_RESTORE_STATUS, bl, y, dpp, false); + + return ret; +} + +int RGWRestore::restore_obj_from_cloud(rgw::sal::Bucket* pbucket, + rgw::sal::Object* pobj, + rgw::sal::PlacementTier* tier, + std::optional days, optional_yield y) +{ + int ret = 0; + + if (!pbucket || !pobj) { + ldpp_dout(this, -1) << "ERROR: Invalid bucket/object. Restore failed" << dendl; + return -EINVAL; + } + + // set restore_status as RESTORE_ALREADY_IN_PROGRESS + ret = set_cloud_restore_status(this, pobj, y, rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress); + if (ret < 0) { + ldpp_dout(this, 0) << " Setting cloud restore status to RESTORE_ALREADY_IN_PROGRESS for the object(" << pobj->get_key() << " failed, ret=" << ret << dendl; + return ret; + } + + // now go ahead with restoring object + bool in_progress = false; + ret = pobj->restore_obj_from_cloud(pbucket, tier, cct, days, in_progress, this, y); + + if (ret < 0) { + ldpp_dout(this, 0) << "object " << pobj->get_key() << " fetching failed" << ret << dendl; + auto reset_ret = set_cloud_restore_status(this, pobj, y, rgw::sal::RGWRestoreStatus::RestoreFailed); + + if (reset_ret < 0) { + ldpp_dout(this, -1) << "Setting restore status ad RestoreFailed failed for object(" << pobj->get_key() << ") " << reset_ret << dendl; + } + + return ret; + } + + ldpp_dout(this, 20) << "Restore of object " << pobj->get_key() << " succeeded" << dendl; + if (in_progress) { + // add restore entry to the list + RGWRestoreEntry entry; + entry.bucket = pbucket->get_key(); + entry.obj_key = pobj->get_key(); + entry.status = rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress; + entry.days = days; + entry.zone_id = driver->get_zone()->get_id(); + + int index = choose_oid(entry); + ret = sal_restore->add_entry(this, y, index, entry); + + if (ret < 0) { + ldpp_dout(this, -1) << "Adding restore entry of object(" << pobj->get_key() << ") failed" << ret << dendl; + } + } + + ldpp_dout(this, 20) << "Restore of object " << pobj->get_key() << " succeeded" << dendl; + return ret; +} diff --git a/src/rgw/rgw_restore.h b/src/rgw/rgw_restore.h new file mode 100644 index 00000000000..662745d240b --- /dev/null +++ b/src/rgw/rgw_restore.h @@ -0,0 +1,140 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +#pragma once + +#include +#include +#include +#include + +#include "common/debug.h" + +#include "include/types.h" +#include "include/rados/librados.hpp" +#include "common/ceph_mutex.h" +#include "common/Cond.h" +#include "common/iso_8601.h" +#include "common/Thread.h" +#include "rgw_common.h" +#include "cls/rgw/cls_rgw_types.h" +#include "rgw_sal.h" + +#include +#include + +#define HASH_PRIME 7877 +#define MAX_ID_LEN 255 +static constexpr std::string_view restore_oid_prefix = "restore"; +static constexpr std::string_view restore_index_lock_name = "restore_process"; + +/** Single Restore entry state */ +struct RGWRestoreEntry { + rgw_bucket bucket; + rgw_obj_key obj_key; + std::optional days; + std::string zone_id; // or should it be zone name? + rgw::sal::RGWRestoreStatus status; + + RGWRestoreEntry() {} + + void encode(ceph::buffer::list& bl) const { + ENCODE_START(1, 1, bl); + encode(bucket, bl); + encode(obj_key, bl); + encode(days, bl); + encode(zone_id, bl); + encode(status, bl); + ENCODE_FINISH(bl); + } + + void decode(ceph::buffer::list::const_iterator& bl) { + DECODE_START(1, bl); + decode(bucket, bl); + decode(obj_key, bl); + decode(days, bl); + decode(zone_id, bl); + decode(status, bl); + DECODE_FINISH(bl); + } + void dump(ceph::Formatter* f) const; + void decode_json(JSONObj* obj); + static void generate_test_instances(std::list& l); +}; +WRITE_CLASS_ENCODER(RGWRestoreEntry) + +class RGWRestore : public DoutPrefixProvider { + CephContext *cct; + rgw::sal::Driver* driver; + std::unique_ptr sal_restore; + int max_objs{0}; + std::vector obj_names; + std::atomic down_flag = { false }; + + class RestoreWorker : public Thread + { + const DoutPrefixProvider *dpp; + CephContext *cct; + RGWRestore *restore; + ceph::mutex lock = ceph::make_mutex("RestoreWorker"); + ceph::condition_variable cond; + + public: + + using lock_guard = std::lock_guard; + using unique_lock = std::unique_lock; + + RestoreWorker(const DoutPrefixProvider* _dpp, CephContext *_cct, RGWRestore *_restore) : dpp(_dpp), cct(_cct), restore(_restore) {} + RGWRestore* get_restore() { return restore; } + std::string thr_name() { + return std::string{"restore_thrd: "}; // + std::to_string(ix); + } + void *entry() override; + void stop(); + + friend class RGWRados; + }; // RestoreWorker + + std::unique_ptr worker; + +public: + ~RGWRestore() { + stop_processor(); + finalize(); + } + + friend class RGWRados; + + RGWRestore() : cct(nullptr), driver(nullptr), max_objs(0) {} + + void initialize(CephContext *_cct, rgw::sal::Driver* _driver); + void finalize(); + + bool going_down(); + void start_processor(); + void stop_processor(); + + CephContext *get_cct() const override { return cct; } + rgw::sal::Restore* get_restore() const { return sal_restore.get(); } + unsigned get_subsys() const; + + std::ostream& gen_prefix(std::ostream& out) const; + + int process(RestoreWorker* worker, optional_yield y); + int choose_oid(const RGWRestoreEntry& e); + int process(int index, int max_secs, optional_yield y); + int process_restore_entry(RGWRestoreEntry& entry, optional_yield y); + time_t thread_stop_at(); + + /** Set the restore status for the given object */ + int set_cloud_restore_status(const DoutPrefixProvider* dpp, rgw::sal::Object* pobj, + optional_yield y, + const rgw::sal::RGWRestoreStatus& restore_status); + + /** Given , restore the object from the cloud-tier. In case the + * object cannot be restored immediately, save that restore state(/entry) + * to be procesed later by RestoreWorker thread. */ + int restore_obj_from_cloud(rgw::sal::Bucket* pbucket, rgw::sal::Object* pobj, + rgw::sal::PlacementTier* tier, + std::optional days, optional_yield y); +}; diff --git a/src/rgw/rgw_sal.cc b/src/rgw/rgw_sal.cc index 03aa37e4343..acb4f14f352 100644 --- a/src/rgw/rgw_sal.cc +++ b/src/rgw/rgw_sal.cc @@ -74,6 +74,7 @@ rgw::sal::Driver* DriverManager::init_storage_provider(const DoutPrefixProvider* const rgw::SiteConfig& site_config, bool use_gc_thread, bool use_lc_thread, + bool use_restore_thread, bool quota_threads, bool run_sync_thread, bool run_reshard_thread, @@ -94,6 +95,7 @@ rgw::sal::Driver* DriverManager::init_storage_provider(const DoutPrefixProvider* .set_use_gc(use_gc) .set_run_gc_thread(use_gc_thread) .set_run_lc_thread(use_lc_thread) + .set_run_restore_thread(use_restore_thread) .set_run_quota_threads(quota_threads) .set_run_sync_thread(run_sync_thread) .set_run_reshard_thread(run_reshard_thread) @@ -121,6 +123,7 @@ rgw::sal::Driver* DriverManager::init_storage_provider(const DoutPrefixProvider* .set_use_datacache(true) .set_run_gc_thread(use_gc_thread) .set_run_lc_thread(use_lc_thread) + .set_run_restore_thread(use_restore_thread) .set_run_quota_threads(quota_threads) .set_run_sync_thread(run_sync_thread) .set_run_reshard_thread(run_reshard_thread) diff --git a/src/rgw/rgw_sal.h b/src/rgw/rgw_sal.h index ef798b585fe..a30b945f421 100644 --- a/src/rgw/rgw_sal.h +++ b/src/rgw/rgw_sal.h @@ -36,6 +36,7 @@ struct RGWBucketEnt; class RGWRESTMgr; class RGWLC; +class RGWRestore; struct rgw_user_bucket; class RGWUsageBatch; class RGWCoroutinesManagerRegistry; @@ -51,6 +52,7 @@ class RGWZonePlacementInfo; struct rgw_pubsub_topic; struct RGWOIDCProviderInfo; struct RGWRoleInfo; +struct RGWRestoreEntry; using RGWBucketListNameFilter = std::function; @@ -159,7 +161,7 @@ static constexpr uint32_t FLAG_PREVENT_VERSIONING = 0x0002; // delete object where head object is missing) static constexpr uint32_t FLAG_FORCE_OP = 0x0004; -enum RGWRestoreStatus : uint8_t { +enum class RGWRestoreStatus : uint8_t { None = 0, RestoreAlreadyInProgress = 1, CloudRestored = 2, @@ -472,6 +474,9 @@ class Driver { virtual int cluster_stat(RGWClusterStat& stats) = 0; /** Get a @a Lifecycle object. Used to manage/run lifecycle transitions */ virtual std::unique_ptr get_lifecycle(void) = 0; + /** Get a @a Restore object. Used to manage/run restore objects */ + virtual std::unique_ptr get_restore(const int n_objs, + const std::vector& obj_names) = 0; /** Reset the temporarily restored objects which are expired */ virtual bool process_expired_objects(const DoutPrefixProvider *dpp, optional_yield y) = 0; @@ -563,6 +568,8 @@ class Driver { const DoutPrefixProvider* dpp) = 0; /** Get access to the lifecycle management thread */ virtual RGWLC* get_rgwlc(void) = 0; + /** Get access to the tier restore management thread */ + virtual RGWRestore* get_rgwrestore(void) = 0; /** Get access to the coroutine registry. Used to create new coroutine managers */ virtual RGWCoroutinesManagerRegistry* get_cr_registry() = 0; @@ -1265,15 +1272,11 @@ class Object { optional_yield y) = 0; virtual int restore_obj_from_cloud(Bucket* bucket, rgw::sal::PlacementTier* tier, - rgw_placement_rule& placement_rule, - rgw_bucket_dir_entry& o, CephContext* cct, - RGWObjTier& tier_config, - uint64_t olh_epoch, std::optional days, + bool& in_progress, const DoutPrefixProvider* dpp, - optional_yield y, - uint32_t flags) = 0; + optional_yield y) = 0; /** Check to see if two placement rules match */ virtual bool placement_rules_match(rgw_placement_rule& r1, rgw_placement_rule& r2) = 0; /** Dump driver-specific object layout info in JSON */ @@ -1667,6 +1670,53 @@ public: const std::string& cookie) = 0; }; +/** @brief Abstraction of a serializer for Restore + */ +class RestoreSerializer : public Serializer { +public: + RestoreSerializer() {} + virtual ~RestoreSerializer() = default; +}; + +/** + * @brief Abstraction for restore processing + * + * The Restore class is designed to manage the restoration of objects + * from cloud tier storage back into the Ceph cluster. This is particularly used + * for objects stored in cold storage solutions like AWS Glacier or Tape-based systems, + * where retrieval operations are asynchronous and can take a significant amount of time. + */ +class Restore { + +public: + Restore() = default; + virtual ~Restore() = default; + /** Add a single restore entry state */ + virtual int add_entry(const DoutPrefixProvider* dpp, optional_yield y, + int index, const RGWRestoreEntry& r_entry) = 0; + /** Add list of restore entries */ + virtual int add_entries(const DoutPrefixProvider* dpp, optional_yield y, + int index, const std::list& restore_entries) = 0; + /** List all known entries given a marker */ + virtual int list(const DoutPrefixProvider *dpp, optional_yield y, + int index, + const std::string& marker, std::string* out_marker, + uint32_t max_entries, std::vector& entries, + bool* truncated) = 0; + + /** Trim restore entries upto the marker */ + virtual int trim_entries(const DoutPrefixProvider *dpp, optional_yield y, + int index, const std::string_view& marker) = 0; + /* Check if the list is empty */ + virtual int is_empty(const DoutPrefixProvider *dpp, optional_yield y) = 0; + + /** Get a serializer for restore processing */ + virtual std::unique_ptr get_serializer( + const std::string& lock_name, + const std::string& oid, + const std::string& cookie) = 0; +}; + /** * @brief Abstraction for a Notification event * @@ -1885,6 +1935,7 @@ public: const rgw::SiteConfig& site_config, bool use_gc_thread, bool use_lc_thread, + bool use_restore_thread, bool quota_threads, bool run_sync_thread, bool run_reshard_thread, @@ -1897,6 +1948,7 @@ public: site_config, use_gc_thread, use_lc_thread, + use_restore_thread, quota_threads, run_sync_thread, run_reshard_thread, @@ -1923,6 +1975,7 @@ public: const rgw::SiteConfig& site_config, bool use_gc_thread, bool use_lc_thread, + bool use_restore_thread, bool quota_threads, bool run_sync_thread, bool run_reshard_thread, diff --git a/src/rgw/rgw_sal_dbstore.cc b/src/rgw/rgw_sal_dbstore.cc index fae28f8438f..077f9583831 100644 --- a/src/rgw/rgw_sal_dbstore.cc +++ b/src/rgw/rgw_sal_dbstore.cc @@ -1920,6 +1920,12 @@ namespace rgw::sal { return std::make_unique(store, oid, lock_name, cookie); } + std::unique_ptr DBStore::get_restore(const int n_objs, + const std::vector& obj_names) + { + return nullptr; + } + std::unique_ptr DBStore::get_notification( rgw::sal::Object* obj, rgw::sal::Object* src_obj, req_state* s, rgw::notify::EventType event_type, optional_yield y, diff --git a/src/rgw/rgw_sal_dbstore.h b/src/rgw/rgw_sal_dbstore.h index 0173baf297c..51f0f2f7c64 100644 --- a/src/rgw/rgw_sal_dbstore.h +++ b/src/rgw/rgw_sal_dbstore.h @@ -893,6 +893,8 @@ public: virtual int list_all_zones(const DoutPrefixProvider* dpp, std::list& zone_ids) override; virtual int cluster_stat(RGWClusterStat& stats) override; virtual std::unique_ptr get_lifecycle(void) override; + virtual std::unique_ptr get_restore(const int n_objs, + const std::vector& obj_names) override; virtual bool process_expired_objects(const DoutPrefixProvider *dpp, optional_yield y) override; virtual std::unique_ptr get_notification( @@ -925,6 +927,7 @@ public: const std::string& topic_queue) override; virtual RGWLC* get_rgwlc(void) override; + virtual RGWRestore* get_rgwrestore(void) override { return NULL; } virtual RGWCoroutinesManagerRegistry* get_cr_registry() override { return NULL; } virtual int log_usage(const DoutPrefixProvider *dpp, std::map& usage_info, optional_yield y) override; virtual int log_op(const DoutPrefixProvider *dpp, std::string& oid, bufferlist& bl) override; diff --git a/src/rgw/rgw_sal_filter.cc b/src/rgw/rgw_sal_filter.cc index 43fdb006c96..a16fe882051 100644 --- a/src/rgw/rgw_sal_filter.cc +++ b/src/rgw/rgw_sal_filter.cc @@ -440,6 +440,13 @@ bool FilterDriver::process_expired_objects(const DoutPrefixProvider *dpp, return next->process_expired_objects(dpp, y); } +std::unique_ptr FilterDriver::get_restore(const int n_objs, + const std::vector& obj_names) +{ + std::unique_ptr restore = next->get_restore(n_objs, obj_names); + return std::make_unique(std::move(restore)); +} + std::unique_ptr FilterDriver::get_notification(rgw::sal::Object* obj, rgw::sal::Object* src_obj, req_state* s, rgw::notify::EventType event_type, optional_yield y, @@ -487,6 +494,11 @@ RGWLC* FilterDriver::get_rgwlc() return next->get_rgwlc(); } +RGWRestore* FilterDriver::get_rgwrestore() +{ + return next->get_rgwrestore(); +} + RGWCoroutinesManagerRegistry* FilterDriver::get_cr_registry() { return next->get_cr_registry(); @@ -1135,18 +1147,14 @@ int FilterObject::transition_to_cloud(Bucket* bucket, int FilterObject::restore_obj_from_cloud(Bucket* bucket, rgw::sal::PlacementTier* tier, - rgw_placement_rule& placement_rule, - rgw_bucket_dir_entry& o, CephContext* cct, - RGWObjTier& tier_config, - uint64_t olh_epoch, std::optional days, - const DoutPrefixProvider* dpp, - optional_yield y, - uint32_t flags) + bool& in_progress, + const DoutPrefixProvider* dpp, + optional_yield y) { return next->restore_obj_from_cloud(nextBucket(bucket), nextPlacementTier(tier), - placement_rule, o, cct, tier_config, olh_epoch, days, dpp, y, flags); + cct, days, in_progress, dpp, y); } bool FilterObject::placement_rules_match(rgw_placement_rule& r1, rgw_placement_rule& r2) @@ -1441,6 +1449,49 @@ std::unique_ptr FilterLifecycle::get_serializer( return std::make_unique(std::move(ns)); } +int FilterRestoreSerializer::try_lock(const DoutPrefixProvider *dpp, utime_t dur, + optional_yield y) +{ + return next->try_lock(dpp, dur, y); +} + +std::unique_ptr FilterRestore::get_serializer(const std::string& lock_name, + const std::string& oid, + const std::string& cookie) { + std::unique_ptr ns; + ns = next->get_serializer(lock_name, oid, cookie); + return std::make_unique(std::move(ns)); +} + +int FilterRestore::add_entry(const DoutPrefixProvider* dpp, optional_yield y, + int index, const RGWRestoreEntry& r_entry) { + return next->add_entry(dpp, y, index, r_entry); +} + +int FilterRestore::add_entries(const DoutPrefixProvider* dpp, optional_yield y, + int index, + const std::list& restore_entries) { + return next->add_entries(dpp, y, index, restore_entries); +} + +/** List all known entries */ +int FilterRestore::list(const DoutPrefixProvider *dpp, optional_yield y, + int index, const std::string& marker, std::string* out_marker, + uint32_t max_entries, std::vector& entries, + bool* truncated) { + return next->list(dpp, y, index, marker, out_marker, max_entries, + entries, truncated); +} + +int FilterRestore::trim_entries(const DoutPrefixProvider *dpp, optional_yield y, + int index, const std::string_view& marker) { + return next->trim_entries(dpp, y, index, marker); +} + +int FilterRestore::is_empty(const DoutPrefixProvider *dpp, optional_yield y) { + return next->is_empty(dpp, y); +} + int FilterNotification::publish_reserve(const DoutPrefixProvider *dpp, RGWObjTags* obj_tags) { diff --git a/src/rgw/rgw_sal_filter.h b/src/rgw/rgw_sal_filter.h index 4b0b97fe220..7de61ef50ce 100644 --- a/src/rgw/rgw_sal_filter.h +++ b/src/rgw/rgw_sal_filter.h @@ -292,6 +292,8 @@ public: } virtual int cluster_stat(RGWClusterStat& stats) override; virtual std::unique_ptr get_lifecycle(void) override; + virtual std::unique_ptr get_restore(const int n_objs, + const std::vector& obj_names) override; virtual bool process_expired_objects(const DoutPrefixProvider *dpp, optional_yield y) override; virtual std::unique_ptr get_notification(rgw::sal::Object* obj, @@ -383,6 +385,7 @@ public: return next->get_bucket_topic_mapping(topic, bucket_keys, y, dpp); } virtual RGWLC* get_rgwlc(void) override; + virtual RGWRestore* get_rgwrestore(void) override; virtual RGWCoroutinesManagerRegistry* get_cr_registry() override; virtual int log_usage(const DoutPrefixProvider *dpp, std::map days, + bool& in_progress, const DoutPrefixProvider* dpp, - optional_yield y, - uint32_t flags) override; + optional_yield y) override; virtual bool placement_rules_match(rgw_placement_rule& r1, rgw_placement_rule& r2) override; virtual int dump_obj_layout(const DoutPrefixProvider *dpp, optional_yield y, Formatter* f) override; @@ -1059,6 +1058,51 @@ public: const std::string& cookie) override; }; +class FilterRestoreSerializer : public RestoreSerializer { + +protected: + std::unique_ptr next; + +public: + FilterRestoreSerializer(std::unique_ptr _next) : next(std::move(_next)) {} + virtual ~FilterRestoreSerializer() = default; + virtual int try_lock(const DoutPrefixProvider *dpp, utime_t dur, optional_yield y) override; + virtual int unlock() override { return next->unlock(); } + virtual void print(std::ostream& out) const override { return next->print(out); } +}; + +class FilterRestore : public Restore { + +protected: + std::unique_ptr next; + +public: + FilterRestore(std::unique_ptr _next) : next(std::move(_next)) {} + ~FilterRestore() override = default; + + virtual int add_entry(const DoutPrefixProvider* dpp, optional_yield y, + int index, const RGWRestoreEntry& r_entry) override; + virtual int add_entries(const DoutPrefixProvider* dpp, optional_yield y, + int index, + const std::list& restore_entries) override; + + /** List all known entries */ + virtual int list(const DoutPrefixProvider *dpp, optional_yield y, + int index, + const std::string& marker, std::string* out_marker, + uint32_t max_entries, std::vector& entries, + bool* truncated) override; + virtual int trim_entries(const DoutPrefixProvider *dpp, optional_yield y, + int index, const std::string_view& marker) override; + virtual int is_empty(const DoutPrefixProvider *dpp, optional_yield y); + + /** Get a serializer for lifecycle */ + virtual std::unique_ptr get_serializer( + const std::string& lock_name, + const std::string& oid, + const std::string& cookie) override; +}; + class FilterNotification : public Notification { protected: std::unique_ptr next; diff --git a/src/rgw/rgw_sal_fwd.h b/src/rgw/rgw_sal_fwd.h index 566a933f8ca..dbdcbf98b21 100644 --- a/src/rgw/rgw_sal_fwd.h +++ b/src/rgw/rgw_sal_fwd.h @@ -39,6 +39,7 @@ namespace sal { class Object; class MultipartUpload; class Lifecycle; + class Restore; class Notification; class Writer; class PlacementTier; diff --git a/src/rgw/rgw_sal_store.h b/src/rgw/rgw_sal_store.h index da98eb41f99..0364c84d1ac 100644 --- a/src/rgw/rgw_sal_store.h +++ b/src/rgw/rgw_sal_store.h @@ -370,15 +370,11 @@ class StoreObject : public Object { } virtual int restore_obj_from_cloud(Bucket* bucket, rgw::sal::PlacementTier* tier, - rgw_placement_rule& placement_rule, - rgw_bucket_dir_entry& o, CephContext* cct, - RGWObjTier& tier_config, - uint64_t olh_epoch, std::optional days, + bool& in_progress, const DoutPrefixProvider* dpp, - optional_yield y, - uint32_t flags) override { + optional_yield y) override { return -1; } jspan_context& get_trace() override { return trace_ctx; } @@ -459,6 +455,20 @@ public: virtual void print(std::ostream& out) const override { out << oid; } }; +class StoreRestoreSerializer : public RestoreSerializer { + +protected: + std::string oid; + +public: + StoreRestoreSerializer() {} + StoreRestoreSerializer(std::string _oid) : oid(_oid) {} + + virtual ~StoreRestoreSerializer() = default; + virtual void print(std::ostream& out) const override { out << oid; } +}; + + class StoreNotification : public Notification { protected: Object* obj; diff --git a/src/rgw/rgw_zone.cc b/src/rgw/rgw_zone.cc index ed693b15f9d..90ff7cef106 100644 --- a/src/rgw/rgw_zone.cc +++ b/src/rgw/rgw_zone.cc @@ -305,6 +305,7 @@ void RGWZoneParams::decode_json(JSONObj *obj) JSONDecoder::decode_json("placement_pools", placement_pools, obj); JSONDecoder::decode_json("tier_config", tier_config, obj); JSONDecoder::decode_json("realm_id", realm_id, obj); + JSONDecoder::decode_json("restore_pool", restore_pool, obj); } void RGWZoneParams::dump(Formatter *f) const @@ -333,6 +334,7 @@ void RGWZoneParams::dump(Formatter *f) const encode_json("placement_pools", placement_pools, f); encode_json("tier_config", tier_config, f); encode_json("realm_id", realm_id, f); + encode_json("restore_pool", restore_pool, f); } int RGWZoneParams::init(const DoutPrefixProvider *dpp, @@ -1280,6 +1282,7 @@ int init_zone_pool_names(const DoutPrefixProvider *dpp, optional_yield y, info.dedup_pool = fix_zone_pool_dup(pools, info.name, ".rgw.dedup", info.dedup_pool); info.gc_pool = fix_zone_pool_dup(pools, info.name, ".rgw.log:gc", info.gc_pool); info.lc_pool = fix_zone_pool_dup(pools, info.name, ".rgw.log:lc", info.lc_pool); + info.restore_pool = fix_zone_pool_dup(pools, info.name, ".rgw.log:restore", info.restore_pool); info.log_pool = fix_zone_pool_dup(pools, info.name, ".rgw.log", info.log_pool); info.intent_log_pool = fix_zone_pool_dup(pools, info.name, ".rgw.log:intent", info.intent_log_pool); info.usage_log_pool = fix_zone_pool_dup(pools, info.name, ".rgw.log:usage", info.usage_log_pool); diff --git a/src/test/rgw/rgw_cr_test.cc b/src/test/rgw/rgw_cr_test.cc index 3adfda594eb..f7be1abd2d4 100644 --- a/src/test/rgw/rgw_cr_test.cc +++ b/src/test/rgw/rgw_cr_test.cc @@ -349,6 +349,7 @@ int main(int argc, const char **argv) false, false, false, + false, false, true, true, null_yield, false)); -- 2.39.5