From 23c69f46abb7174f715fc8b526a70aed47ce2d14 Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Fri, 8 Jul 2022 14:58:33 -0400 Subject: [PATCH 01/20] Item action progress monitoring design This design combines the requirements for the previously-merged Upload Progress Monitoring design with the requirements for the (not submitted but discussed in meetings and slack) proposed asynchronous item action plugins into one integrated proposal. Signed-off-by: Scott Seago --- design/AsyncActionFSM.graffle | Bin 0 -> 28721 bytes design/AsyncActionFSM.png | Bin 0 -> 76790 bytes design/general-progress-monitoring.md | 515 ++++++++++++++++++++++++++ 3 files changed, 515 insertions(+) create mode 100644 design/AsyncActionFSM.graffle create mode 100644 design/AsyncActionFSM.png create mode 100644 design/general-progress-monitoring.md diff --git a/design/AsyncActionFSM.graffle b/design/AsyncActionFSM.graffle new file mode 100644 index 0000000000000000000000000000000000000000..7e8e7ed210910de1adae3f1f506fc81ff5c20eff GIT binary patch literal 28721 zcmZ^}Q;;w`7p2>_ZQHiH-?nYrwr$(CZQHhOoAdp1cTUx$Dy#Oz+R0r~S$X88fI(0I z001BWV8hf^{wn~0{wMzp^#7)ylZUO5ppmnMovo;n44s*yfvKsr2|X}?{QsxzhL!6V zmI?p>)e``K@c-RrY~XA_XK!ucYGOPg&O{m;vl&w4^Q&GVih(GC80L=R50n4|TA+^-D5QV^C59*-L9_tD zf)pcEw19yUQm6nE`Okp@k2GH-IXH1aodqsN40&FJ1+pYxIXQGu0c=u4Lp+3xm~}Cq z)12-*3%u{+UJF7DS{{<>ADS}YA5FO;m!>~jA=SmCW;4V}!ALot;@M!zwI1=diLgw( zBGFIk{Db|h&=NXY*Nv=QZqtNH84l~1jg8%e3#)VUMz(I$%uPdAw##HVT-?m{>uOi^ zt>~Qh)+CpSaZpvwj?I&MWOj$^91!4QM%7%W{l7U^b$26?&7n4?w<9|jU zlir<<;nnmtQ=8TXZ7OK(m{+o{86DsaboHGX*Rtd}2q$j!wt>r##vhgP6sM+j-dE4C z8qrCjv6X}8!5NyhU1k+jSF<9kj_q{uu$7~nmcpHr*VG#8HVKcoV6HA4T$-y5y0`4^ zjWLNFH6AfZl~(sH97P46HtG|qDVM1hPn#AQ*D%;yizd$!Xi?Zn3tJ7r-tA(rHrQIa z7Va$E)wx~@KBjt7M9@_g>tZAk5M@hD-P;acL?AR20ZL6eB z(@+w3wWOiQd$lTAOfxq&4lXR4jnz=n8CwRGqGL@j;3>V{hGpN*>#&V^u+`xr*aRJU z8kJ`DI-xYBNL2HGy)LBgqFmga{Vmg@m;7s&*3RqSx$4ULHm;l9J+T$Ej#~v1zi#^C z9Z=9*)o@*ICCPHtD14@2JsT5_*ckk*p!*V8UCkq-j>&qltu7k7)~2gi6N)}3Ft+jz zgiqEn*)F|RfQ1c}7~fg2rCLCoqJdsEa8Wz0-OHyYGsY$9+}(8MwN*n}3x-Z;EnC^G zqW9*0qbIUmX&Hq=kN#6XaEnB^i^PDK@!`?q!KDS5=44Do+APqW5(X83J3uj(f=W}^`7ZJ=lh#dd&!ZhrIMCEAmRcsQMO1dRQ`LI$Jp^^m zs!<&!<=V97m(-`6SeZ`yK1K>1rIjVVwIc?hPn=D`jZ8P)B`T;PmQK1OG)@-?#BfPf$*p3P zLWELkGIC1Qs8yvfhiWp8zL??!`$c9D~bUFn#3!DBX8 zPHj(>;$0$g`@X~p|6wc3W>K4LbBx{E9S7Uh{Lr@OY60{_aCT7zwdd=;fW2mP#C5!x zU)Z?6t&>T~o;K_ND@||>b1|_9Ld60s`D&_4^dh1z^X#2j)jdtDEf3~vZyribn~F>d zg{jE(m=)cE62F{ETLI)6Ao_dPci6x#0Ti??A;&+h#j0>Zh!RggpexZV%FGEp+t|b-UwRFhn^2Hr|pE$c4X`XsS(DOd~~GUm`=}ioFF$b zX+_9Pv&68s4~1zmNh7K_R1_4&FwGBcg}sj5^e0z9&4{;aK(O`3?$u%5^4>Q})^ z;p*7Q;^EccQh&hn1mF$F6M`=in=d+NeCF^3Qm+8vjmQmLD?32{MA$wxCU&o=*||*h z)-`b5G>~mw;V7=pItiv|dC>*iH<)VU%<7?yU(|PFhiPvR_0-chb3P7P+C8at{qkRO z#7GnLtM{n-G)rF;O>_H>R;%sxL~geEmag%;$7CN0o7b64IQMuaj~!UDCw<+gzQ=vX z{3hg+7EQou46i1IPgPFIflu zeN;Z{$^qI zSuGEfgd$6V=r9*anaE#(#41WIHHzVI2tegLA*~)xVr%Z>@>&>^&yfRb&R!8_z%0NM zZP_I_m)1;Ib4Do9Op;`{1g()JF2Nigc-0KzQxd%s(h~>nPdle>u7eR@I=H*N@6$2o z|HSeEHu$2=)S3`VK-?BO6>Zvr75>*-v$0vGW}&@4YC{IE83T@w{YMH?<}|wigKWC`Xsa;otHs1 zR>oA7snXByjf3s$XAn5#0D>1Xwr}oS4E_^?IGVWg?>zUui1nP5gaL~=$ps9JTR(K# zxCh0^AxEDF6+@?vT|yVhB{69ki#Ccz+KV9!*uEY`K3&X9Hq|Mw$(}$pAKqR*hpA*q zA84(Tkchx8hyp_&U^PNCMZ!oCyJepUkt+WN%P{0i3Syu@UTA$ntA7%GMKfgpBmn>u z80CUMiGVm|vz*OBgaW5hpDA|{$!Z?ZZ&;r+c9>WefyNPIf5E)KF_|C89wo}3FOHgd zPZ)h)oh|Nw1Bc!Vk$hhyJDA)EcGVCOw_j*Nuj5BzK57qZFY@!^Q3pKb& zSH4L@rU-O$sT@{EClv<+6Ss9YlkA8kyBC?t10rR=q3eNz2Tx9b|0u#8Np_&(ah~6I zbWM%khphSu+tvQIls*~05%|?fCSz$GeaH7)Lr+yJbZK(gG$8-iYTy5 z{w_-ntIWJ#_e*RhnTt%)5?mcDt!ln^9al%)9?B+{wNh6g_kr%YgZ;xYR$Gk%H3aL7 zZPv^L+$An9n|q3E`H68$a(Sw)bRFOah~?DkdfLcxCK&wrL<%eHbs|=}IhIv7HAoQF zZ{31<2|5Ix`@*38kZ!c)YvGbaT0g-Nwt#%fW5En@OL6{?6)0DV)lyP=vbVLRp2t1xo(L~8dC9xGoJki{@Kn-SKcjg9G0 z8%TNTpduS=cg@1(bKoKYmW)zqAQtUhlI^%dOG{TagOU_91L}zfDy?fBtnN(gD;l*i zbx+-c*Dw`N;bW@n)UYclIe7H>ekMneeqzP-!R!Y${E~6J* zbLq6E9#rg`|KyDpF@RVw1tbQuoo)_O<(x>6))F&6|HrUd+|oBZR8%B-GKJLCBrR%6 z3UpPam1UKfGHtC=EcKj`D`kCceVa?O1j2}^q!Y4MRy;UJI`MJT=d2LTbB@yFnWIfE zSX&RSC$xGdAWOG+p%|aeX()wZg1xe=k4IC2YNdtWuF4~3D(10b=OQpx2JLN5fG`Akj54y|k6BKk&8@g(~o`YE!4z>%u6rU^=bUrGzT-*4? z4s{~CTqw-!d1Z)r!t@bP+GcBZ$7C80hlHm8Lzs|W2-eB0av2U|{?He40?@xgbr!%9 zLRusRyvZ<+W)$ac@a0brf3vlq*IvxxS+;qaH~&_5Ak(eC{2`2^1P-WDcG}#?rUxGF zSoA|n?Wikz;MN0E>oF;SBekQb2iAApZwgsKpiq53ikuLzAN#9TecV+eKx`7u>WfN!dW}pvsj|?DqsUpB3m5X~2Yy51+(zUq z5{I|zz>}kCX2i-Afx-uo@9;7sX1e~6b`)uK!0&LmA?ADOP+`#*hjyJZ1qlH%EX+G> zQd{JgLB{P-fV>AISNPe1Ci~p!xQ=Lixl||qy_r!b|GQJBQFNI=NvmZD(ISF#|8*Dw zNzfX>60{erBvpXnFoor1)?=WnNTwHcdb;5Gwj0H4 zhV!_>4c&4y7XKK}q+bfg2=o^L`bNX7>&6kL$2^KGU#-<6uzHgsl1Ub>6is<(QH42G zLQvMObz{W}8O?qh&Rpyor4kT`;K=YZ^&L0y;<#rfV6`X8V(sQ zPBv$O2u17k&!8n^!$CEAaoWZ57ta&`)n!XiQ)a;_k(&I)p#psXkE*r7sN?Ds-gjqj zu-A#%%jB=M2yAiNGp2Kawkr??;6AVz)x~|RB1tnANU-vBiLwjSC}B$> zKSHyErh<5<-zc_;w8q&Q=6=fe6+x5+R=}7^0FU!!|2eQCeWX8ZsT(1ydyoC`m3Qy% z(#G|ZhNcPyPT%in;5M?xll%m~FF((sL!oXoJ?Bcg&U$t#I&2)&q}Z59>Ibmxptk)Q zckJGre7B%KI?H+ZA9vhAcTkrEe6dW*a3~L6hc42G%R-&kVNavIY^lebyH*hcfS`-cyt%o^|MU*w-TwW5}0o zf%1LO{GoaMa))L1n7LFH4)xeo@#}S?Ev2=!yPDfE`2Fy&llNYqD$hOdeBS&@3*;#K zKAT?!zg>8vaR75L|GfC&Hn+H|d;aRI>`Ghhe$uLL){)um)b-%6t_*yB{;GR@@qJ*F zj^onD1R3sR`ZWmn%MLi*`NoHdy8S0a`c_*3FKP$T0av!+p4N}vQNOW#q3;r$U%}s* zzj=Q_|3d!=l0lLdjA_iTBS=Dw6E}?UEJ&s%NRRm*$v&dJVX?mCumqXQ15W3?HN~SY zgfwNWEO1*EJ>}PNpP@QqYs=QYTwZCNsm*V=&z+umJJWon`pWnn0ksj(=VHKPaz~=TFU77a zO`D-6z>-7_Thl!6r`}b*L#M6lU{W@8hqeOBbzEiW zS%JPRML67`%&)5S{1EL9+w#M4fvKkvWQx$7sR#s<%u!ea$Cc}M%@qU8=dbp&&6hJp zmK0{RgWM>SyadhReX;wPjd8y85n^$#qT}w%-ud4Fej)#&{Y+-pqxeVjvbB{jO{$fL z3Sw_WUMzR#Jd0fl>P+;!8I@)cuhyl{VJz=%05r4U7w$yrl ziFyb2gb?>&%jv0Q$Rh==$XXJPIapxyl-@Qz<>!{%lh2O&nt9JFk zk{;TPavrdMlRy#>3P}=-Nt{OS-r>E2ThKIJrL9;C5ih|A(le@8>Mbo-$jI1@8K2bP zf3x%lrDh4lT}LAtFPcyYgA3sy#-@u{Gn73MVSs7M83T6)Fa;v0Y7jf4b?X={UXSQXaD;>TGLEmTN9kA5t59|9{VAq@bThy`~^%q;ct zSC=S%t;n2V$dQ}QRogia>62*Dw668CQ%ZTDR2D$Q88Ba-ikQgjclH2E9YMc~-sU9T z-f{nWoxQEmbT&Pc#m?W?kA4`XFAXEWgCy?JY!^*GiGCCHFmg);rzfwdsY-mJyG&;x zKtZ)f%3lHNN#p%JZQ*##@T5hR$Y%2xa-(b|-~)4bOa+K!g-)AH4btJV2IYAB;z;-L zt6kho##!U7uUVC?%FHmM2V7+*kf|lx1lw(!k-f@v-??2*Wi}0r^;+YhW_H)p#?`SqDsu!}?7%kat zFKyXpuBW%5*J0=F?S1qM$;aR?jg)J@!CfDb- z9jWHaE7BRu1<1u+aRkLt{dk|ptoiS`<`q&rtRSCK8Zklcl0i>`$K+{iZ|R3p!XLP zA1s$U@Q(9>EL&`r zJS_qC*z&)KNXVAhExv1f=$P^m#v|66ylWiq5$+@S8kq2%_9Nn3@R!6-2^3;boGWp3 z>A{&urofU)5jjWF$pTMDU`l|XF-D#Grnu^S<=Hhp@QX7x>JX_TB0F{J1kFjR9d%n{ zn{(S^n{?Y`8+Dt@mbWdSD`IQ3XSipuXY}Uy)&9=@&H>-it3$4Ru6@XxgFi8Exa?u+ z9mf|>uOI!vq&vqAhm=qcgNE>NJr_QMQr*&QcI6UIPKm3B&5@-wh*Cas!*z3OHhgBDTN~ zRx?aj3s=8cO9U?v6~FN0bLHO~p zg-U*UbpM5DjzSsQ`iQ6?>E*_2ErI1fV2G;PzdM<8dKW;-07WUe)zaNDs~628Nar9h zbEIOPBowR^!DYaZ%a%gbGB!+EgG^R;=ej?d;y~qGNrPu9J4I%70}>cz3>Ra@1|h#$ z)lnD(v=`%zhQUHW!`=iIMD_>&`t$fg-7%7QL3%tr^>qPt8ay<#zxaCnxiMII)R|$y zO;543)L(}|S)%Jr01lVN8ZMxe?BGjI@0y?I84nk#@YT26VRr==&1_ zs@gI3L#_LrcgAm|-IX@#=ZI!wKZw6^w404vgkL4i>ylvd0FvzC1PPLqV&Frd>O>0y zYDaO#@JLAk7;I^E*>qD?Bz(@SY$-ajt8vf91Z%RkM4MyRM*xp(9*JDCy7;zn*~Tny zkzNA5gr8#sPHA6qF$y_h3l5bG{TOEf z#Jz7rDWMWjOA;g_7sjX$FS4IGKTB_z#ZlRu%@Tjrzg@Fn4sN#yBqNtc;*qS!K#m6r ziG~E`x*)$j!Q1fpoTdQgx{{u;pfgkbNNGvb6EVif{-$ll(1K;$kmkC8`4;$KGky>( zKY3&J)jW|LYY9~otUUv6Dt;I>}{aCx?Js~P~{|xfj1m;xSVg1is z0CL+7ZQgYSUA?lw<0p`{KxcLF@zcK2c0Q}G5{BuzHnGx!l zq57HO>Y0K2otX5`U;zHxZ3)PK7VAQ=Ow_XMZ2sxO)RW&Q2Jcv?gTxjoZA9ka5s;S` zAQxSHzaw{RTxUpUY^Q%`d}m+>dc7lPYov#s@T_bbeOqnY_ZA!P!RVbscf{&l+m}yo zFzpG|8@4a`Ef!mhA1Y;+z??y?L-={|T*r6}RW(9Y1 zCe&w9kQ~NzYPTCdNZq}(EhX-4?F|xvTn@G-W$tzF1;-<|n<4qXPO1Lpmm%pdct%>U z*i2r89Z>m<-@3rb$=yo$reg*vxc|v7e8X|jIpls;+xJxZ-5afuZ%FSL2KBvpb_ggB z?%W`0I}H6Zy%8Ib9360`c2wQ)RR9_LQH?;7+`igpDkDEoAn@Tqu37MJ@dx!8v$r~(tS}I}f7V1r>KhyeXRgz!Z5K~wq2QIhTN4qjRc6tPbCEJ)x0B%+9 zBk9BK8u>)k$Mh0-Gkb*2m4us`(hn5;>^^vCQ-1@-;S4=E$M;FCBU=Xovr0DY;luh3 z#UTLqvUuO}t^JAp4)f+`!Qxx?TZ7N%^5t5gtGcfXY~nI0w(bR7#V&0&ko>>$g^oH8P9i%h%7Z*NGP1Zv#rEl@q2rFpuo>dc+Y5+cll8m;FSnoe zaNDc1`t#0DzF~sJtJ1YsI)rFbuPwZ}>)p{^k=Zi4p}X}pPj!ZU&C8iB z(ow(R%DtQO1*ko7`LgNtr#+y43;8169i-(1F0M}6zh3o|HjEd&Y50NPeg@$bK;_r! zXLPQoQJc$zCS5oENBwZP5B`IsfcJmwcwGB8wBF~PrmVq#=Yi;&Mls?Jdtk%y5{jN zmd+lPy!48Q(+x>wM=T?VHq^pWUVQ$!A?rrUja5C6zB_uu_QKf>xb25MJiXIiOtq|^ z&G8TJ?cd$Mk-vX?gg=MuTu%)z3nYjd5-A0cLi-a{J=4=EYF^2MUtGTg_l;y9M`^L(sH!O+Sy7}fn&BTLLEF}wSRRxr{@8xvZv!{gEAIG4-bTG$L0@j3qp9u z%=d$tAld+Ug=-AVmZ~LS4_s(V;yu=4xL2gH;2!Y50_el^=AVo^H6kT0dDQ4mMgQ;YOsqRWe|pic6zvUZd&2!@>P_m4?ThFO z*&E#(;h*K7;GgLm^qaUlf`1Hq$A8ZQN(M{-Gy*CFLIO?%QUqQEVgjy(ZD-x@^%n!y zg>t9f5Ap{F3(f#yPR zCgI@W;N+lo7q$1b|7Y)FZ)C4k&%BQ8t#gk&FbCd;+D-ocvp*Nq2MGy*iC+k{5F!md z0XYFr30VQ9fwqY1C@~%yFGE-tat+OifQ6Nbkcp*<)k)DzxQuoQr-SDydahBB7HkW# zgX3v+{!;)S%!m1@eGXl~ExZT&L-E`+Zzl{N@{8fg@%+X>M?*tHL_=q-sV=Loqi(gH zSsyjD8lr>$D!yK2z#l?AbTr63bQa<@R0pw#&`auV^`3JO7s8MCEA%dP&^5#l^~Vty z1rZw_DIS@QWDrXcT^?>8VIE^I!a-cE8j86JbEMSL6MPILGXzD6unG*~z) zLMkE?JsG_yCKH#5%gSC!P|0-}UG7xKCbon3;<$XJ&{f12`AN*hq8UvWr;F3W@_Be3 zR^%quhyJ7T+_nH-^cVe0O~yb*VJa=HIK7^Jo!*V1CG90`7EOQpojzq~8|Oh}>PLf) zjhKa#g=iJgGRijMX{a{rI--lw#pbGWeY$Sb&^FeK*TwxBw$6W~c+`0K?67oHd4%JD zeTWzC#bqnDo$#Lgp7`GRp8CG(F87W3fIh5;3PkkV7s9i|O$lXBEK;vLwsATA5=w+yOcsa}(;YQM% z-A?XccNj1{B*8(+K}lqxyf{pvl9$bRya{6ooK_MQ0XW@&zTt}J}{yc|*y zQ&FpIXK8lXQIVy1C-KAZVvi+*osyc&Mqk5ILsdgIlY1)tRQ$0xE7`?hBfMGKeA|4s zs4MeDbtAu-;GA769w$8~$xpkJ>#O@|a*?%UC+A20)Aa0fp;z{o_$vlB2{8pR7e1LD zpOC`HNz7hEP()G0RK#{P9bLuT*%a6~YD{WU(Il<0q4Crd*)-Xd*{H2S-8j3rSK}x0 z-{ZpLr|gp$2yHNgf>321LMa%>pjMuM1}QVd)u6<_wO)0dLYv+hx(&h&!VbbKSU40y zI8@LE0hSq-HI!{gbm%8ECv*UY(9{=4bp?K=E={CfSm_&Q@_Wg~`@+wt}EdWthU z$|=Z6>{RrO?6}f#WILn7Vx^|=~3#j>PhQy>v88}>ZxwZkK*s+d+q7%^lpkj{ZH#x542fdSx{)8IB)`VbP#*8 zE$xnSPph}?+e)gPbjGlLx*kOfP4kjgismL2Zpw11BsC^=CeFrTJ77x;m}GCbuko&-&+hNP3{%E(#u`=AtPQP2wN+Cq zOEaxGcfnKlinIn>lby4ke5dG1`f2)c`gQsZ#hoVCRW9vJXioD_S?pLXlhM8MY5U#p z|HqOF+n8KCiU0txn*jh2|G!yMdq)#j3lldwOM4Tu<8E+urHxhoZeeI5BzgIGBv0g$ za7p-ol|Tg=3IYO%VX*`hAc6u25dQ=a#myH%Km;Q}!$lu-0t_q0E=JtTo~}f-9Py+%r7o5_hY&-+!Xs3M5lb7H#f65H$Ojb@dG~< z`NEyApSwYiTQXcTHUvO-HcairN*JLm($ts+WNUx@6B!vMB{_-1 z;VsQCFLu$^5>u1g>!02}T)tp7o7>pj(Ca<4Us)J>>P&ls#ox>49w01m8yYk^x*a1Y z;d4(<&P~X8;ov4`uRGST*m!!3oUCNGyV_l0@3yo4T$eciT>klxnww`oydOVbe;+;` zJY0O7yxja8J$>yi{|x?_d%x{oFMld5C@LxmvYUezJ?i-ZW#jS$)Ck?dBD2SC;|+9d z!`xssT8y-EgTQ6WIcq=O9cWfl_Z2rb@UrBey`Gbyx#!Q^;3s>X^Y8)H)SrtB%KQ8y z;OGAnTnSn(uq^N7g`vmz<)HiK(e}d;+#+e08JIkHofn--2jZe;C-W6*OG8%~(EtH)q!aPlQhx|kFt zD_aPw(3s95H|OGWNlPJD%{x;7D)mU5{@yRufXgLAU|TPMWy$H40o$n7PUfJA>*#Q` zQye%E*1YA&B|MUd=azeSnE}m>Nlz}6F%C#1(&?|1q22RIANLCY`|Ani|E0xBttOy{ zO}bLH9fg@TV_%ENf+w9Lh`5sO<=m8}H(+&dk6`0|FG-&&>5&uY@qUqH_u9$!FJ+A& zr=XpZ+Z}b6!f2r-jAVfBI3nQH{_fK-r_Aw-dJH_JNf@hvK&j6-!ya^#H3Maaadh(i zO$kT#N_OFR%XL1jdF#O;$j(JkNMGf19F47U`*Z2HE&M4m&rc67AbkJk7$tZqIJPz> z3b<)kT0umArprg8i!J-0M*|7DUrR~%*w}7X$zfAdVQMgYU*HX4z1H?Xrdu}3`7q`F zF%B3PuQ(WQ5`a2y@>HiqFgrwNX5#XIYV!14zwF^QZ6h8K$+F-|7v%g2MVQGX6!BJu6GR=gpspRAGH4Wkm+6p1Gnk*~rnhzbnB zy}Yio(kUGtKjwgfm`hLnr%Y?uhBL-?D9gQ{!FLjC9G+W05IK_Lc&sfquZvioIwG51 zV6b^@zMiqJ*`YfHZO2q)h_%X9v-f1;S>xly7i;E_>}S&Wwb~v+K-@ku$iR!ilfuJo zq$#-n-dLs~@JE+7CqU!aUvhCTX`s&`p9VNZt-*gKA{KDFR8wRHp3Y&`VOG>mBNS7E zs8lu@%*ro!*$?@YXbzwb!C+L!Qv5G@q8aef7hIpzXI622-nYMittrk?okC%L&A49K5e-8A#)G(bB*s zZhbYPq)_4Cu@0QszXJEoPR&6qZ3ci2-nBob0Hw~~z@C4k-*&eT2{62b@f#r z$A(u6ZG^gPg~s2T2-&mWJR}_&2^^Piwa+-*sVV*V-+@|exS{2ZdA)At2 zPAhCO?k?U4f)v;7J%p_Lc2Tc=3zt3TF1TOd@}{!2RddN6QRL$FKNlT86uKKfANJDXARfy9{K8wI7cZ`-EH~pGZ%*bCxX~f= zqh^#hS-RiD?8f%>zVxb@CUv&JDn7;dA)qd+qBT?!omN+xAWT}DyelJ7nD$M2$Lyq10;k9P#hv@D*k8bvs0UVbNU$G{6 z0Bf$hUZc1~Xs{bH+7uSsCJy-@f{;I=fHELMm~+`Gq^)UnUaU)u9T^B|VLAk(HiTTq z@`vhS+0I6APY%jvjBGh$p2-i8C=D(`Z#+Wj#}^!02;&i5aiwZaYtkcfB;PdOc9{#_ zM?T|47v^p~9ZjNtBV2i304n~WUcH)eNPAZP+use#I<(o*L%)~CXUy=ClW(EvKCZj! z6yAyY&^hCa46TP!>d)vjYgegW0spq_{oea+MgwQU<IzeEf> zk%qX{|G`u&xJ!70^Zi%b9*3Z&GH2*0?wfdgXWRdm6!?M6BhC~Eq^lMJU!7HhTGM4p z6Ekz>cHylK&66Mq2#yb#z`ZNryIlWx=|@?;xHrwYfrmSpcoVyP@NdOB12y{A$Y+s6 z>!3E$Uh6E$d0Jpr@`*4E?|nPyG)xFxfpY)w&*lC1u0!s&=HhIIvo?zDiwwJ6TvF$) zh4^jKNfTiGIUs$KF75Jcpy7qoEi9`zc-1cm*Gc7Tohu_b?(YyRR-)-u^+Bk*^CV9H!GPq}5R zJ_R!XM$fLyiwVzJJjc!iAFl0izR~tZFJa`>bq~jFVKb;ChQuhvD9c1`_41&|_L5uA z%R5eJPwz>XpW3zqKV59b;=VKHp!1QTU=FeP%Y?ruVNw7A->_Z zun%RwQ%JD-9nEP*KW0ej0Q(zx@s4>8H`Pz&np6#+|LoPW;p(y()vQK_GYoERzpB9M zxED3M30CxeP-Myeb*47(`>cZ?dffn)}Y=iwa+)t+8!Ui)luTNd37=342 zjpk!0CT2tS+^&s-PPZMpJQO@)&+v648ahi=H~&SP!s%aN{%^>;I8|;Nw*;V%bMU(k zY*s^MZTZk;GzNQGsE38IKyCbkAl$pRzX*||qd_C2OY+gUT=3wz$m>R~??bqL6!XHR zmP;F?7MjevTqC}1_02?8XPr$)xq+q_VB@9N3fBRA*jcr4df8rEj`nQCPVvDcO0vXK z+M+^JgoO+9okb>PF|^BVq+GAcS!KYpach^N`7y=)x*}J>YwHlUX(;eSVa}b0ZD++@ zT$j5p@Q1#F=D3f-3~5(Cg{1mc4KYB9A$G;9Ot)No&G*9Yr|uEiTPOwEPHKh|E|MoB z1T>2z(y(nE6rM*t8mB&_hTw=P$VDgIw&pn)B{%_m@4xAYBBTUi(^K#QwQyQW%4e8wgzbWZu7zwSUu6aweRz_$j`=OJlTLMdO z;$*%KqSHtE%;NR<7NXlx@Z zag+Cb@J~?i$G!xsXxz?jikMlwHt0|%&ZjjDGgHK;uK{*`3Y!mfRX^8t_U zF#R-luG_y&v@(HvhQ!U3Vt`DEa800beG=&ch+WYOF?7zrN|{RMDOX-di=K9vU#HFW_*4Ml3$KfdOsUe5!m zBw|Utd!Ff$e3id?cV~Lc1`@Xafs=VKq7;-D1^e2@+p4gwto~E5lLHo}^pD7$02$v{ z4f5gVrg~#LjyO{%1$0S(FMN)7p?!F5&lhj9?Tj_AzzN&0loy@T^|I{~nYPboHRO3^ zF$g0cvaj5_bRUk*_PLFCG^C{l| zFwEeeDvWNerDyAxO)1tF=KvLLav^Wl+MWCgJhXUK!}f$s@-t+sXFZy)Mv%+Q0|AoU zZ6(01#7Xd2D4W^%y}+GbO?cisQtsA|hm&>kM-R{^3A?DmCy>lR^|`6wl{TE}WtFma!}!Dd z38z=(6SByCFQPp@?e(4ROdC)}r<-v@Yx%V6B%^RdQ85Zjw36dCh8VoC3cm95Phf?% zQj^czxBe{YQLX2byd98D{a~#%cxodzr_k&bs*Ln#N>5D{?Og}OhbyS1X};(W?Ih0< z4_3hD&h>ILd2F%Y1YO(R|6QYU^thkL8>Z~>Ldg?CeQ~c83dw&>t;?|BL+0PbFpFb- zPvz3}t1XWX1uu@eY!x-LN}7V>@mMnF)|e_pS%=9}qUX`j>C(g^v4qr+c4_)Nc$x4N z_I_n66rO8rv58pZM}yUG=~*ZUEOUR+2IAEK@?4orB+~j1?GK+7B-=#{Y-U0ntS11- z77su>eI=5boz{S6JA;(ooDWn0dSG}Rs#bnK)k!topOy{?g=p4t7RfJd;kI#2@$vgG zBW)g6j@?P9|sgMS z$$19T;0FC$|G95bg84w`Q$p97Bt-&!n+hFDSmcpHmgpLnTX30s3CX_t!lIibGa<7m z-Q#ybGLP*?8D5q04p(Rt{f|lBJmr#H&7@@G$NDvx$Y>g9ZObVrE~Z+h(G(-5HWQE1 z$D$Z_OP)5BMDqgJbPG2dq`V|T>( z?_q08;ggA4g}VX0F~IhQ-^;?V;?&}~?+ombpyPS?EPrAWy&sh+W=LqnwX$2eHMv>R4 zOHC{?bP-wU5v~SyH(=EU{PlAQM7hOCk3N4CU)O*_cb&W7*h43peU$lQy&m4T?E7tB z|Lp+-7}OL*Q%uWZYsJx(uk%npJ60-~4JBdWy5e=IoN|kI(F!vNg+btDT<8id>5wIQ zc9mnG2)h7{iYZI*&WXIurh~BJ5x=#Y<9?rdB{gr!aA}lr8GzKrkWm@+(AIF+NhfT{ z6qob$VZu*b!#67tDmnHd?bVArc4J#yY~*tLKej+Sz0eW7q^XTouD6@NqVkH`nzTyg z&Tc7KD^qdL$dRe>RyU+i{;PDuE|9jmKJ_Pr{00$k3;vBK>5ZhFW(MOwvWe4r=S4$l z2vka?&)F#Z+%rcq1V(XR)-Laa21=@=Ebb3zgFpX%x{^ck7Nuv zBj*f}@e3WUUcR5j^^xpUKuMPa8(>->0}^K~cKO(BP-|#&#_^Z6^q4eL)!QW?p-c(E z>M{wqfUcK7@=UYD-~yEmkSW2$n%W$>k!vA2P3+%QP<;~NV1U@532^1uuqWi3K|QP) zMze&rfY97DgeekI3g7Ewfv_I>x>_}PhxhsQd^6`>hCusT0B>tYws=#fHrxzOlB?-c zkXB59?kM~?J7dD=Wwnj-uJuwI(RLoiiL6!iF-r7k2XCFV_@w-CfYuBlS;`M>_7lCi z?=ZJA2~Mqua#>B~Es)vL63`8G;EfEC>OQAXKsXet(_kG%OGg*lSJ;_r*$0vSybGe` zdLEkGZ{@cu*B8M5sp}k~GXb`C-BG7w+qP}ncG9tJCmq|ioiDbNj&0k?7n`^Ldk6nr zXRT8;*sBKhuA0=YLGAs#5Sw1LJha=w9r>2QZw>w3<-fii2aibk`6gH22{S;`gK7)q zr;XD)D{GfVq)!_sO7=+x8Z{mv#C70;T8PtzLcKose8AJ~J0EZPi{srh|8)YxIFcAI3H^RCFwwhG9a8jO zD#D95l^uAS^l4H}H3n(GS!PXsD6Dpr;d{meFu}NW$?|A>)9mq-b4yNpg8$3HK=+UI zZKRKE_p3;50(*3Q%voOC*m2`iE41`p=Objx-FxNElR3p2MC7o&w0Ty{AdPdj4e!(; zWa*{2ez#F?M1Vesv6*j&%>E;8y1H@Y!Z#i2hJa3X0mpDMQ5RGr=jhWZHxU?-txTdS zPFRDtfVObFIn{iYO_)k=AS}t?oj^Dk?&1*&O#{l*WBiV%3Ts^8pdz=f>3Gluc6iQb~9p_Xg?7s;ltp_E(sjJ=$c<4(a<(%}=;jU8!KvO)&ID z47EkGG-@8$+zy7zmU@X3WQfaF)Y80EKXxnDF!DOQ8QJM?_oaq6^!yw@&z6~FWB zFK|?+_WR!2csWrxw{2g>iuOyJ*qM<`-QK8wEZ06jej~kv4AsyHvzFp{%4`dX8LKx| zU>&5tT|WHiZ^UXTp@KN*en&Cdr^~6vN{hRE7Z&wuOLCv3mscF5@EU9j8?VG>w68Ow z%+r?y;SSpAU+VvjmCsE@$K&wK>UM<*+&^4GnDp9CM9QKmPewVn8Ne3b=`uBEvc-k; zSJI9TsO_#Z;1>m{R~>TV>XjvMMImXz77rTjU1~UcfHeCumRud7R(3}jBQQm>DoCAE z@O2rxuhF41eRT13iarnp9WE}*zc|)QF|{kQtC7BPgZ?!ob=_ay0GrZ0qiY}z_qZ*( z5aw^McWH!u$k{k6XMgr0?6Cd=Z+WN4QBJAy2erjuHMYj2Y|SCy2KAy+H}yAT*$-Xo zuUCXWUh-j@v>EqIetutAOUCE#U)4O{S==#xdW;R3tLCTTMxC?@Awo8YS8lcU;Ubxt zOJ+fR_{{a8swt=w8GY$}{JH&KpQ4pi5ZN>B6@SC9SO<0!ATaLawF;CKi;vRxO{*-Z zzx{8`aa$q^On|vbCQmdfEEmDdG3Rk1?lxnoF5W92JHk!DHTEEd|DG5S{x+&<@X7U} zHnjVbcz0_?_gdA#<0oi3!~qzKMg$8%$KSH z?PGL7=f#(3tfLzb!%Oet_d{4>t_f1x^&WZeDsk=^a9vy7^*4a+_)$lf?s)macqhgX+Q#I`38iD&!=nU5&0#edv<|tfHC$tqCQ6m_-FNXm z%Ehy5R$WB*-L?KTyZIP|18a!|HNT*z46J)E)|;#&hgkUx(<}o&=+p({mX_YNKK18v z&Vv0BiCy; zOY^8tUc}6KVd)=La^Q=ciCkEcGQi6w_|sRbC+w1OX{VW&){~UR_kqRmJb%6!WZ56t97@G{{Vz& z%ak6}nV(;kp~_^s3ksG$)3wE8Rj_r)l)-ZX)KCh+`Wb~k1+|o>=Z!9z!fGN{0!kU) z0c3HG8ZhDPuVUBAHF*8V3@I5s%`CVBFJhY2k8KIeOZ=&A=&W zi5T%h5pbsN9Q~xGgT`vkbi&dL4RFJ+Er`zTL*VdP33#4)7XHMdy5rA3>Y`jW=#9h1 z8V$9IhKer5J=nWJiekEh6&jvsuq1RjF=HOO^`1~@PKQY!V1}lgEd2r0_;+88OS7X{ zs#=57{YK)l#d~5=vd{>h)a>D#_Td{PCT-X@^%D%#dnb-zHuP=0gnj*YFl#hyA(l(M zxn{PM6qP3H66N~z9jo9^l?o&n+tv-gIuMBLhD2mI*RxnA6=w=F-Glp<(}ZN~`*1(C zJPUF-5#hP#oe@g7e2_d4cns5E{*-~bYLd3AqZ%RJS_^&rIk#ZM{avin;>)st2EU#p zcRk<*E&$uuWz=@N%K7XwIGxrp^@MZ{lHI5-8J%>2=WxpD%_xyavBb+ea7wbEl#}Br z1J`v`84JTOp5dYIya^_rt|&II0-5QK+re_*z~?TU8JZ$}jAP+^?8Pji{@(3=xmH7A z1AshnU8?ehj2;e=gLxzPT*aa6-oi-_h{K7kKM`Yv`yJECv3{27kJ2-HT{gq9m31%v zNECr`E*r%%UOkOzd{9X1SrfSi>1;(+AW}N8!nq?jKx6dTW^k7CIxG>RSetD{AnM0n z{Ksnb6dEJ!h)vE3%hDvPQB-twhekV|zAzw4tHXTlDD|k{o>w$2iYK^8@*!s$9$ zy0dkmnKID8!Z}B8GX@S=2etvn)Gu_6S$(D8M5BpBM4UgtY!sqN&IMjegC(fVt9N8y zLX$?QCpX6fy2#-Swug<0Q-3J#MxuD}qTBXA2ZaL?Ujv#K`cOseyLCLBL>7m2J$9P` zaa|^HetX%etS4)1xFvg+7f(H6FPiP0d*K6);ou|Ma}00ZpLoW`J5 zg4GCsgobvVxcUtcV~ti$`on@jg#+%t9`@vD5tFh@a%!0h7r93BB@E;WDBck$WwO0+ zX!;+d7+;SF6S6NM4iPh4#r5x2UY963x{c4|8 z-c1s*3u_%|3wC^a+fBQ79|P{`I=xN?jV77agK%h_CatO=*$;dMjozRHL%PZ)Se`UG zGXuLVSB5@c7bEsyJo@awgmD)!0r#WL2ir2wlfymoELP5i_;9(yw-5M<7(AKE!@j== zNj+R(bu~!-0Ldw4hIG+Ry}YuA2qM!v&-6(qADUHiTxf11^Dkj^L}7bF3$gXH$ZvPBjWKo_ zRkxgcZ8wk^KQc#Nswp%qU@k()v6^ObjO1aKc&o|m7|tfpO>V)=*L;sEc>=0o4EnDh zKBlL8vSmOFXO!RNqQardOXq#GVydQ}c%O|oDSwm(;w<$@4@&*($PxrAGLCYac< zrByD#I|~WHeI-sQ%-HE##_&dZFcuc|t+5;Jv4&@Hb#MHgmeuW3I|i1NtPZkVUz!_G z`xjJsT3U_p;J)co)^4Yr3Zmo>ISzy!cy6mC_YwZ=k6@ndcnUgtmkFJUHiD0_P0f=% z5}3-3_j2i}`Dwdpv(m(iv>=bDdR1)8?Osr-e;jOJpeYtW?3zR+FXmZ(KMy_b=r_lh zGPhfw!-zyh$v6Efr$ZS7rQZ~5{nvfDUE{p^a1mrLrV6Tt z!(;f51H_*buX8?+2WHy^dv`M|71y;<+h;J;_`@>C>eu8SQW`e8NNmN^OfGHRGPKj1I6Bt0kQ3mnq5HB6zV+Ql)vx`IZO*4E8bvS4h`VQ<3txd+yo^Y zg+xAy6Pih4I&=!(IOfSRr$?)w=~gJrnp)h>bd&`z8XZm zBA>I?XPu~#9Fje54kKlHp^2Y$)MIbN)r+$7-sOtwBV7d##b_zQrJ)dDA3^kI#4~V?f6LVxEA&aKRS1= z=SlyO#z-$S&U)E&Q4wV)!x|23VM;=mis7RJwZBpR5|UCVhSU*w6lZ5NzltzzRl^Zq zcvduI;=`1URo~4l+nCphK;A)P>RKb@2x$U^xYsWA)dbu`P zd{r|lEt@AmuJ&$}i^a+{*6jL_C?mqF1=;+KK*jmN(Y{x_oNXMrYsTZMrrHB1>&A=S zmjv)?+diZ?6A9e# zeMzSJy>uJmIUM>>u4K*Ws7&vjLB2a{zwzvWc4YVy?%6oz1eTI82rM#fK-DlfO|5~4 z=Y%M#n`%)fV^m&VO(e@WORv4!3ys?6rTpzjaSQqk-X;+V{`W!8-p<8CzXS^=C=uW`f5BVU&%%cF~SQdoC<& zr;P>RR2LL4Gr#7>(nc4%hnXXGOZF~fq{}O8(?(3rId^j$5q$Uz+6?&`l4F&D;!0}ioH=Bat<%(eHg#>%}FV^a-xm&nW$5pVW2D7iM>wzh1diG4P8)l zJ7Pf3+@hDw@>y@(wa+nz zG)61UYpk{F1oJ7l`77sg*mPrVt3d{~IXluB<@#_4r8WfD*Q@nHz4|SXA;ySgAh*~W z9>a>;S8zNHU@aa-5)S=n=UirSM?BOo#~Ng)p8eL9i>Ojlk{g>XF_fZUs`@5PLg6e2 z>`T_eytrT@Z_mydiEhS$Nsq+6M@Ih>*!?qMR^Hr|E)(=FZ=}}>SQzjGIU2V*uYcXp z=E(#7lG?h}#eCo6b*>Cb&4BKD=*yo~5-NOOP*@KA*PVh^d}2Jw+!!;ITR3Cssx8W% z1-kNaG_`F1%`bqYxrGNj`s_kJ$TEfa_!7~B;Z35U&(Tha^-Lygy;AoPH|na`1coPC z;%dz2I1K&|?ARRPgx}l=7X)pymEp>C9PvhcKgJiK@8d+Vx8JRymMug~t0WCh1K6d> z&<)B`<-TL{;>g(C8I^Gny|d4fNg79nU^W%l$AZS}=+U(oXb{H9CUx&~pQQVghZft6 z1}-}Ji@qTvbnie9R_#jpCf0ZqCG~XV{H}e{`w+hT^9%AVNrzzaaIwPLv45f+f zToMGI2q09(y;&&$Xc;CzOMRG;fZUBvwOe|E7Jh|UIN>;4jm@t|*yh9mD-UW+({#!B z?BEKASaQqoTJjNy^>|) z4f)u3+P&8K(z|ZgP1Yi+tvw=vcKPSW2a?h{B%5vD<<`5EZ@I`bfhkU}to5jjcu!e? zMjh%wK^D zAq2K1Kk@{FpBP<2Na3@R7He`ILfFPzf0JbzNUKc2apiUD;Pc>^+KBfp9r(fAHw`_V zK4@q+?~zYVB0#Q3zM!)eY2sSolC6sTF3_ff1R1I2&a@Y{A?A0n0ate)%5N!PuWP-m zXEmYcL>zYZVdm{!oWjZ4l)ZRr_s@c|wCy>7HD4o;7dZ$D&d&=q5>O z%X8JcGYut466%Z7N$FB9>5xm$-T5cxM02eBeyX@J{B~HufRNE5E@MR4fY#{$%2SVG z+BmZ$1L%Mma;0rS<=j5xq`KULGq_-@)h>A60ia7E^i6~K+q}ca(VA#(gt5>_ zAv2NAfvfe6G!K<=B#}KK&~#CB$7xtLF`|)5On@^G{|+qG^@JUTl6*fU_+MN{v?fE% z&11}&mWHv@fW6eJyIs$62DprdVpMtz${_!Y0GY2y| zw()NiR!VUOn{(M`vxr$EEM&XM8?iN}cklJ{BFlPw?aTejJ8m^0GaZMW#*^}&`cFWL zvBR{4hrqBrlrpLImTKqDkcbIk_i{>DY2W zp`5J<*_jaPyNL$_9wm;}e4rOo->=V(T;c>%B{YYWfKs(F%k&ZU5>Wh7fhW}HKRCLU zl_~0~>4V9c_`N8y)nvnhqmp9y-A$sCNj8Z|6MDuH++RO>ij>eO#Gf+5%;%TK{PW(@^b9JRb3LE9u~T_ zHnvzS8TwUD@kiU4mbs(rj2Q(o$OuhUVh$@gFf>4_x)4aIeP=~KLG_4lc=4U+0YP*6 z$_h8bOa5^~D_0t*0y5=-__eoh&qEUlj?kZtPwRNRMxDF56P`1G$Y&&u1j4nOd9o@% z16u|Z&@?BXS&ipOL=48hY0ind9^6JL=qL`LzT+A^$CMna0uyIs!bd-H3;hj${{>J6 z$l0j$;M0?N3AjZC#BzQm1GlKl$kLQV3Yj2uC#MVE%I*X~*b&dNEH!n60JZ`nPy{XgH;gG6dq2=rL0{5d~)fNd-%hv)l_ z5EV8a@6_jR>aSqNnMu%}9!Brj%$S&8&}*f(s9Yqi^TCp>dZSVydXU8g>lAi$>LSwm zsEm5h_it79;|G9^cH9)Xe_t!rw>9MrU;~dA-D@8?p5E7?v{2M}T6=%pl4^4s*sXSt z?F=@G2Z{_dE{QXlc|Cr2gJ10pb;d(9F*e7hy9D?yzBi=vH#9?Kb#(;9Jq#R29#y6s zZyxxFfMlTrUoVH!{{Dwa=K65sBCa7+};9)A-%iaVn&3u~#K2J~+Ay7J4G8rkEBls;NQdwHG!$o=>zA zf4CtewL8t)#iG?>$9BAVQJjv|H^#U4piu&c#Q^0A!Hqrsb!oT!I2T2!hN4%)jq*!I zn^`CD0vXYQn}e{$Yo$Q)0vH?AtMs0*8l550?}#MYyJ|*qR7HzFSnh=ZGkSblE2+_Z z#jQ3jJd;eNy?{J}2q~!IGrViRrdS&vl3o`2hy@ zi1_FhRRW7i?`fCHh}M|^5YuHzcGSNYDVye=i6t9qfi`r%ewUquGzQIoA3`K5tlOEg z34caUx<{R}ym#W;GO2LrLDG4&p@8P#wSf_i5iNR_c zYnZV(*eBRtBG6>p7kvV3n3X;c@utSN_b_N#OkM=^vEI5r5ZKbA0l*v8thdr|VRb_b zoNyKjMpwvUDH*2C`|dCf28`@g`_Y=Iorh!s?nEDE$L|`2C|QpynD51yPnu^Z zIq`Qz0t=<_5xCQ|x1|c3z33fq!i;@|)aMQL_?VR`neU-LS#Ea8z~QDd-GV<8kW5*} zTc!LGGMS34?UfMGZ-kf|} zD<-kr)rN&^;0(ne=Qi92LZHcgtLi5y*(SdtU~5ca-*A&(oumrzZ|o${jv?f*>8yL=N{N7J)v4aiLN(uTRBLk2O}i!F;lOqE z-L`dN_fM8>?A*ZpZt56!uH5*QSC8|%AalwTPXe?CKf}FV;CR}h5zHOcQx+M&2yHV; zIid*VaBP&uKadZdqD(JRvNDVDLad$@*Rqc>-`CHkws7ube8SXRRJ4CptG2_Z<|UBk z5;w=51TqFgHt?d6?2(FnYkv##ybczzvIO?wp|;3#RC^-&qSK|0BXk_*Hz7U7erYNU z;2ds(?l@%veD$3Cte^JH=Z$uGZ9ga{YU~z?SIZTjhaJCa;6J{)ZT+dsM|+QPDul1l7H zqLr=S>*(bDKMEZ8ce)d;PHpYhFQox${~CRm<3PJ;I4b@nH&y~adgBN-=*$N;SLwji zQ*{QTNy4X*k$Rh#;WM98@na3sJIbQ-o;J8m-dsPKqw(y|3t4DR7Xh{RGn3Uf+bsrB z)MLUX!pup6U91Qs#hvDoKR!XvDgl$eqKtYBh%GYVcAI5GXPykU#XsFd{o8^;@48>G z#S_HJLHqcux|{$5trmEVT$Wc@wln*bbTY+1Cekno2oATTBAo9Wt=;wB@qa5<=&C02 zaho^>FTCEj3D}KZ?$q2X{A`0A|MR@>#y5TT2GFBJEhX|B@ z+IHwGt@8Tr;Pr@az`}cAvm-$>{Y@5txq9^!^@ZNb&e1Prr87cV(HuJc_iGj8SNe0$ z6w14JT$eI@{enCzt=V-K$jXJj3Bp+!8Bo_;eBp)CM$A`}U;{Jea`GyS?QqICxSU8i zqY}~5X!6+N&T4@fb zoeJHlef7(?GHGGy(@ckAUkI4mS;TbUS%PkyHp)p?yszYb@>^`ed>S3w0_Q#|RjU~8 z7tz4laU;ci^@);ci|`O)Hvggxw%YgVz=v=yEZ;1?WnlmnsqF|{0|S~x`_Bfi1q_Fhi zs;O%<#}lRI>89_C_(O=!ycq)&TFdGEdi|EPFuc8duIdH*RNSUg0S@3Z4D$y;F7@;M zE6*A_z7C2VsraPQxh$h}pYDkDSZ-s%%s8fs;Eu5sHR@y|1~lvj(*0e}fK3ht!Stjo zGp3o{l<*I5;4hq?s)6ea?|#oP+nyBz9Ac08tt(C=x6xm3IX4j7+R&>Eht1Za{y>O% zaAlgM&LGu9J)<$g7o$HJXWYU~>$Ho)QZz4kTyd`XGO!0dHU}I${sd-r1Pmg(UIh1E z4x+8)$Q^N+jaI>{{J&WVU!iJ|g>1rEP#5Kp!yJ7*XX|E6fO9J6i`)YE{bn$L@FO!A zX)dQ1X1#B;u7%6CrGEwWQ{Gsv(*s`xfxe2osgj(9>Fm_%&z+PRUO@zlRx_ z#WS(O1zsbh|N>t*bEz1K94wN8Gx<~X0p@`D@kJj1po!K z9MY`Igjvh-K}dOLlem8j=eN!#4o4w?-W8{k23kk*uQxn0fgHYL`>fZimY6#T*E`#0v1)%o#r4M|Hxxj+)(c5|kAy!o zJWc@0_4dlDW3|s$PTU~u6?RxRLPrt->v70f}t%F5t=zN^{@_>IRNYYAXqQkDk9SV=kF$G6izY+BK| zB;dK3;QU=cI%uNPw|DnYe*Pkfj(^jSjg3^fxvhF@YFChbZf&{9aglk+PA$mw3{j+Q ziZ5fSRA_n@PNr^}n^F+1xHpP8?^@T@z2=Slu!E8)n?u>I>Vj33@GjZSDbsqe-k`pV z<2L@>iJ)WK)NoeEoUj9Z(UaQ1R7M%D;NA-`M_r{nGw0W0W268^sk54SFBs|#%ypL$|#Py$Dze>KyW^7=ACwB6jb(Jo8dw-M{J?nPg$9e`9j3`VpFiSf$V1Pl*l z;B7}TcA8KDJ-xk1*klSd-m9{P01y}gE%F(DbOcKp&h^sz$~*qS84`U~PgsRKyi{~9 zIiNoeLDY&~4aNv|=p*^3>+uQFX$;;*ER+1>4$~9t_nHbCNVEwcsq#@J#ENMSe#krx zh0q@#*LN3*w^nr=@XwZvcw{Cvd#_wUjPdj`9Wjr768*Sl9X*JxtSBhzTu(!He$Eq0A}O2;}cEz4lvs1ObG&k>CPbd0cW%C?nMOKHqAnXD_Cvy{_=DN_aX|p%GCj(wuw)Q zC0}Y2@a)4=948Oekwr=S176E2V+xwaTkTC~`$K?pp8(7jzH$;FfXva)ZULN(=?@vT zu_h&96r8rjRaN3uH7S?oV_p+=dkPbqAadF5r$o!1fc0Yw3=SHj7pMEYDlMuIW~!EY zjtgyZz<3+F#q}&rH61C!GM$VWBZC1khGguCa8u1rc7?z}Wme;-vzk>F1nJm&k+Fbo*DO9KFEQ z@50Bbwl*}i2o{CaKXS@hs&Fa67@d3d{6@iU9q;}yHy_Ow;p-ig9?Q<#4=5W za;vtL`fFv-;}vwlb#oS!7;3I0TfX;6i?v^A|Q4P0jqIlpWJeT#@y!~fm z9x*dcN;6;0Qm0O%B+O#Gos~2qGaiS6sWTUK%!#>cy*{+b&#=EJs`eGHz#?c>0^$)Q#%mmb$sz<}FkO|~nSxc$DlzmB{g+u-GG$_#Vs#s#4cw%BAY4wo| z=dBA`ctt5F-XCc3d7w}65c-nD<%d&!!4Xet2V_T1X*M{%lpPHC&4Q#do6zc(+=2Ha zWY|9rfX>^FRhq`9DVYN}olI0aZ3-FwKJ()SDWmos<7y*LKY2`3cpE!4A{TVQmQH&zTfx0L~h0k5We&wj{U@s^5V!>x<2vqzu#XNa=&5K?>jIv z70$mm*7T-EzV275v0$UxVOP`6k=?^}C41seEcx9jV2OcsH<( z2nnF1H{P6v{}9ww648^8K%s+w$3#K(G(^EbJ_3Jkz#sT}3@Qpb{6t0mE(Y!N|Dun^ zpr8Ibnlti2SE3<1_;J%vP)ZJdq9K0=U*m&++&=z^`TzyRULm5NBoPILA4Np)p`0D+ zT;!R;VBL*^q6qUj0bIE|nN_$KE(ozyxk+a^KCQmaT_G*>*upgGYQQG1?`YQ8mA7WxU?x zzgK5C95;-^IZQrFMBHnx`anE;(7t#-eYHM>G3R$UXZXS+G89xyeiSt1%Vb0HXm3jI z->Eu-is|83k*;21e6Y98Vcd0Rwj(W^-K3{2Sw2N?eYUgU12K28cA4X5zB~H4VxuZw zBK@D9*aZ(?pAGvk8zmWeU-|XfOMT7~pYLYDLyCxloq;mP)picfhbLP>b`=wY@wDoKsQ)fGihCTUut>=I z?tA9%nK#&9DEX1_Mr4=j{r>Dz7?Po0(vct;`GVJRCl)8JQXE6E6C@^&57dkVdYw!1ed30`fBHYZlDT{j#bar)Qp*iA9At3C(2J>wU!-<=)xxSO7udDAMQ-LDL) zKSSycu6OE%{0}i>dBkq^XSKui#QV!Km6HeQT}# z;mBu^FIE`A!ItIU@;Qh0fIQQrm&2$tJ(6CQI>q9X&gMdYN4yw;QG0SQ?Guv!_p7qu zTY*$n>o{(1jOxX0%0I6Xk?2V?6p7}4wBT)sMw__&cQY$Ouo>6B!V73$sP5P9yO$ghuuMywr$4f(B|d-Bs{A;J*OjI1nP<@a;}_Qd z+W`+m&@!e42^vnO+4t?LU?$lSR?7RR?PM7$dC3qbcXl2l&-#116-}_^?d#x_ELb(` zD(5GncpbGgs%sB%{RIZHb;XF-U*HS>{WP+mU|5jc-3Z7IdW{j&3?r8RJ;9?P!+r%Z z-`kcXd!_sx{XZKJcnrrRSN~gw-2WbG@T^QX6BW&)0GdqeS%nnEj3{Sty4t}8;=hJN zL35{ZVidC zLfCv?oU9teyMEWee|@HXwt(gy+CN`Vfph($?m&IneGu;xr&*L3&)ch;1;*zs1qaST z+AM%z!;d3ec=Nxb#l%4kT1H`{mURaYzCHX2FCUji1IN30vA@(lr#(rwn!~j3`u?rP zfAWFHTR2;Tkk9V~$T-|dcpbV5Ek?C8e|4kbe!6QIV7<^+#BXBG_CMqC-+|CLr%fDo z2IIWov+iu&^r)@>UZ1mi317cTAaHu}?iJw9*mEtc zESSG&GHn+%LgkF1hAcBrcn9Gk&e@)>-V1pkIGe)|^M8q}LKFOLKTpHw8X7GXS&AK` zN!cE1U#cfDu7?L4HgjFSm?r4{m#5q-AWvcU1+bE1Qa<6&8Uf4Xup4zy+Mp}O2f2wx zB(;UT@Td64dxj~%d%m;lSK*i={%J_eZR7Fe8rDll;@=N;W*I)XrT^od*@$c0YnAFI!r=YK*L2>1eH1cVX)&_Jp0y{-ccd!k%w2vp zK6_J^R;FykI3>*TRzo_%T8ll(j5a{cD^5_qn`E z3+c^{&w^_0W$biM(EqnBe@WQZbevc(&eC;M+F_Y0+(lf2zQU|D)q+MxbZx;2e`ZL)?$xB>k60rY1`NtzT0Uo2@;^af31`^>M zm+(j^??9`vJMx~H=N{|Zf5O-QGO~sk(Qurl+wM<%Z*(U}g!9}lHpdpT7=`+2SV(@y z@vjfz+(PW2Vw`Zp6z$yQo0(D%wtva#-(03fZ6_Kmb1dLISN-iDL&%82GUD$UUPK$9 ziWz$bnS6Btg)l77xVr`Ha0ct|b5Y!JaKJbJyT$ex)2m`{dlgQ=v|{OwJMDR+EkUx>#}_(w=@1aL@Rd?z7qB z!~f5YUP-}@wu~AsIzQp>(4^JP)@ll4tAWM7&S^xCYjA zzG?qVI`W_lhQpBp*otOCik)#r=Tb*8yK&c2D9bAPKbtW}lAx_gSXectFs{OrRp?4p z_IK9j=L-h&y|2>um=yHu#ft`K%Sv)p9&Apu3z&$4RdswQTZP4Ee3)*M(U zU6m}EjB=d&;z^RdyI4NBP`0U2viN;BRjtTU(!Ok?$7mkhH$Q+%tOtuG;+ZF@OQF$Z zfEbg-rsB7!|K*$XPO#9G6MqO=MzXyx=YWCrY=AMq;b({BgKZsp)%;Z6ovGM_t;wD| zBUR|>jJqb&+b$F07Fbla-yxhL0|z3s_p&fhtT85$e^og=;Hhf2H3 zT(%ZqJ>5dp{Y7Wb!6fAFi?%7n{e`ZqpVZ=?hO}xAx0_sxCEil_@wAw%7-)z#B>G;nX24sPhb#8F&PbD>pnAj(MtKwObW+~w%fa7fl_@v2C=`e znRdJ_I`ss2n$;2|!Y4-R^Oj{t^e^nZqlq#ABv7zg`02j&4D<5Fa?c3QUmtPfw}E7$ z@5x=Zs`t$+pufOq3K932- z)zlLog2QKuF_*R38}?aL6DRzBPb-@c6ES&?Zu4*Xa=(jo%@k^%H%Q&PbM#ds?^{PW zt~Lvinn1$J$+;_vx>j=gvMOOs8B#KNH~qq-%4k*?=ON zW}|VD81aLZg{S!kHTvq&7mCXMZB)Xjpg1ZItWDsey}!n2iPa(ZK%8mHn{ne2kM+Z) zq|4j>I7yc7moMaqH_5KI1N;u1HkDwSucM9GS*QD% zi05?a#@#zqjDr7cy9Fu8ylRH+6d0Fp-g_ovZd$%?!#(VAMeUNlGARXnGB0a`&&5Zk zbw%}hx^dd~MLxN)CS@q+P!G?xT6}r&soq-MdirXyMdek+LOd(z_9caEbFu!`djT=Y_hDk-KY{B9lZe6^qC!&frXo)RAPoVj+YIW|%& zy~R+}C-Ks2VQNQa-@vTDo`PDAK-yk!mVr+gLF;>2)g)F5OQ9MLwG&Ozq^mobkwLfC zNB#KTn)H1CwD|TCky?md#$^Q!YV>oLUmlhRg{hB^;BVmM3VLdcHx-^9111TS%%L>H z+xl^Fipfb%J%)+Ofz%Sl04ctV;h42p@+tVf{N;!t7nKI(Yk)|JghJ*L0L+qJ=gs~} zj_*;f2ll%=vl**}0gwI~3I(khLQRO`r-dfY_4_Zs1=Gq_HS%t%8MP+();f3!(m>zQ zUu^x}1yw)$Nem>RiE4+9^H@ppDIviO$}Qln@WR~qNlq4M1|MNIE!n%9WXi`_6h9v^s(Ir9?sUx2( zx2)DpBlYt=>%I2(3IW`|xG^3a?pdkk8;9AR4SM^LLGydxfYYi(=+Zo74q!?W;=d4l z_V^#%fkt&9I`4eHur6&7cX8?qASaxsCZZd$I3bLn%MWa1GT?rQmfWpgVA}s@j2FQ- z)2p@+5j7UoVUkAzl4|Ln+IE#07U6OvD?S^%NJp?2mRNCqn%W4aZ zIvbHnJsIEv@NV(a9WsiZ%;&G$Gc@_ga+Wvm22C-{S+pV4+iZGCBbB49){}$9-Cefd zQ>Um$*ay&nRzxA)Z;Y|GF7=EBT~`&=o?A^ch*+83WkxgU&Ce^cnna^`kMVtXfqQo@ zrz!g$nwd4D)F&d-!v7li4xMJ(lCFpr6@+*;pE{tw7PN-6XGN`Quly0S{dnuKS|7u; ztg-F!%@2>?U;BbNL7?Hi(E7FpT3DGj74y#@XAh8Ghe|9Y_cHPQE`Y4B*|_xn=Y$7* z(62l=uS}8Yyldk)msLI2Qpc^&c9X`n6&pbp{|~=|gMM^s@8&yc9VDG#=*Uj7>bN7vr~`N&+t4JON$q zm=J83SO*f^?&>8r-OxQ~!Nxd(nIDxtTDWyXu4mIFsZjW-rY_@Lw9MXFN``fcI-y2%_@sG6zhMbIP zPp+YT{bc1zG=qY+4*+MBXihZ zxTmgy;*J4lNQ3EJzakVPpQ6xc8Si1@Re*Joko8xMQse+T@L-?T7YF&uB0X}~5p0J< zI6(e5d=+6nbaA5NlN;g$k8@FFA@$vSjs3vf`juj;(r~P_r|mcIGI`bl@GK7kfgAei zhv!6l68f-&9WbFIZ>bBW*GHQA>DI)=#49TQBWq|8GWC}K-Bs74Qg@YJA=fCqs0tXG zEmxYY+Et9W1}8{a36#phGnm4tp|>O@HrrTy+hsb_)zv%JAtU2D;9kI=JWT*KbYR!i z5p0b~ZkxGGKfZ%5I=OHBA==SQi(AX15?K8;=~2aJ=K6|qd~b5GT3>3BQA2~K&ocZK zm1t1r@K4X`vZxEE4|C&BfFslYZ0EMyn{N`1R0|E~b$NU)(u|y-B_x0#9Xaz{SiGpd zjVD(pZk_$H;mUOuEsse1iLea_V7y!Y_IqomJS_i3I69Ugb2BV&#j`MlH z%tl0;@cvYJ_azfBs*H8B>^y%~AIfao-O6b;Fy}dZ*2X6pZ)tC}srL+l+A})Ff2t2N z^y8a9=(e0LTAnML%dVeGD;QYW7QgNyi3YT*^?0LP6QM|yTU{tnLgB1>wXtHo#NJej zVU@tP$ERW6ICV7?v{Q<7T!g>2=loT=u~bQWjf#hwSNwGq*Q?_{QM|jN1bn}khfx3Q zuQ1&iQL5&W5pVjrD8~gB`!%w^6_!jDoHpn21C;%`;B6!b0HZOX~PZuCy2J$s90X(2O49Y{L0Ntp%NT8h9;-#fV^&&wKY%&&6Saa~oD!4En9W zo)nxa!JI3+gw=oyC_Y)1J^6a&sd1rNtMso}ww0DJ@<(sG0FS(APf?`#FDmrf+tW5h zKmam1<%P&PLlPdlKZrj{@vh6!g~v1cUGaaT#$F>`v#Rk8+`Ul81SrX-U9=Al0Ttjt z2Al(SvHh0yjD=~AmT#_~U^}ux*yrn#e8ceWSic7|GhVXEv?gA>y_s4uy4zIOb~?p! zjk{S1RLh_J_X^pcCeBl?ayt2BrP=_hZR1_7%IlQJg_`U-@}R5_gyEI58Re#=E?Zyb z2g~`iom)N@VAilQUEoUZI#eaFzj(WrSb4H+o$Gg#0pt;F1Q9QfR!>gaES#nw6f_Yi z_)_N;3z<9KC(o>z4u0qDBl}cF8>YHC@jDIjZa%b1Pw|@H${il;r@=@EkbCh{R?Lw& zWSmeJZ4H&pt5sX_)S7m{brpJE`&s`ejK@CP@nI!zcYR7wcNQ(hI!uAmEJmvm3VZLo z3t9vgNM-Q5^D-XR&=1$mep{Q3Zv9?B<)jteEL5j-Oet8PkDh*b*kW(#CpH4EcUS6} zd3i+NhA^Vxa?V3@5T<4JnhELSEmm{%n=bFjJ>o3~LaN)kQ(YgMcg;V^0powQN(QOT z?SkkXQEL7fr7NO|P8h~7#A)7OPf^PHO6zzZ&0P?)A)K>mrZdCLd1oyv zlG~Qr^8I8+H&9sbFJ-6}q>HH#oSiM7B%u0<4wT_%D^<iZDJSn&g()l;Bl zy#$#ZEUdEhgze7{U_2sL(kwP-JDYSrH1QX_97ab2gkR7q$^H8q&Eo4yEw2-^lOjpw zzgxNvUUfcc9wtB+{(jlHM!=5-8Pz;w#mlk;JX?y|K{j%!X2P9eIB4m@kSEI-*_oca zl&g#?Z4-@=;jcOj&`vAu9fX5hoe>Ii++G=yA@@ zzT`Ws&+M^>d@adcrmQT1*&EY#pQ5JoW2GnFU1y8J;4hS+!!cLJUb)4_d3?0|XA$U( zk{w;V1?g68V-1L@9m6i4c72Q>U#GdQtd2O5E1N>s41>=HzPG#oxMP9KYNBvw3Tb4J z5(l`(C)C)aH8c>1@w#1$GMG><*Kr(Vy zI{xy)<N}f8lKE)(U5rhQy;!HcCb5m*aLiA z)Yc_6Ecc67p7vW771WQEX;q#l$l!K8aN5jp>9mME8jOZfK85F&Ed999Kgk@p-$HPT zKMnvGEiA5!)lLEJWttdadET^y_KOH0asKOArJDneOoLUx{r$M-JaIaR=6?VipF~&j zHwD7MFVgj3yRn~BcvXuD@k~~UAz)(idQD4yZ!8W*Pp3~~pPUyeDMZ8O@Yi_DnqYdx zeEleU>R^eMYp@XaKX`US?&ngWP52VYcg6Fhvm&#Ag7Plsd|$A40ZisHX`f#^PAg|< zG95Qf>E)AyfVj*Y@@+;t-Hkf}^QOjqd5fUCD{g4Z${0DG>`*>>24yRKtb1wrXJC=9 z(P_7jNeE8k&uFJpob2ZaIHOmJZy%rQSNTzp`QJjjLL>R|9ZS-~s9Q02Px@s{GWZG# z(~DBtq&3+rtx98P^p=^%kC~#eKz@L}X*#t5`H;k%@+(YE!W9!E3~+_NB4=G6)EAw) zo%cs|livYrZ<6mY3s^mhvdBO8PdD-%88FrB)*r?I6)~wsBhnM%`nC@t2gsz+fOpJ> zDl`CvIe3au?c7QC%`Xgg@znW3(ONqEmIwmv!TY_slbz@tLCdSOGQWUT4w{HEfB`6? zUjW$!!V0v$xN4p%+G8`H*Lf}Ts_Bsh+;xtcPHzHcQ{XlwHx$zU8U|4bpciKrC0Y+v zpduK^Ex(eicNDpbwNbb(5et;#&}+vq;9xLHUN)NA&o0q2ZOeeI4%8$pD!$XY*kG(5PRezeO-_t@o7~iD@FG7 zeWSO)%r?H^8k5@fr7%`rWbFs!=0GdSEHv=dR|=o_&B^3uz;F!`Wj2B!xY4yJj~@Rl zMIkL4(8p+cqdw~KA>$zM4^?9iCR{y8w@$B+in+uPb_3a^E1m&ZQ5~q?!jxUB?d$Bv zTaT%Lk0@6@=##kaV9u0l+_y=~&F{_vb&)PNEe- ziFRxBKS~%&eD34*q1@Qiy2?VQi4v0l9M~=0HkeL+ETRU^7NLwy56CwNj+WD)+JceAldS!`yVk_^sP-?`b5YFdv|{ zlkklP$r{7B;=gDHwo@9T0_?j%ep|gb7eS@ZaVm8$y*T$ql@Hq)RBLj8<;gf?*= zpaK5;+NQh}5=nSurXz94Ii2QECiNFEvtgq$Kb8_zlw)knb!#U^+I)uZ|1J-FHg>#4 zRLoyMf|3w?s86nk?i~OSegGgz52M?~T-jF;UI}e$`$`3U7I33#K z8D-+UaJ8?yRqC8ap65bs4nxwTXl;oTSvrn>$R|g^oS8|d|MmBqBYl&k9$*(BbA6Vz z@i=i-U`7D;g?7W|G;EMifF#oAIjfPP!05PKN#K0X^6hcSKSTbs%)#1m_zeLmW&06wKU%~IXxPGa^(4Fk=~|tLA2U1!xerkn|myMctG6I>RajrY3$5cD_kHslRjV z1pOG)k`Z1h4#niH4~{DcnJ*klD<)^K#310Z4w#0?afqlMC6*y=G#H!)BHcg;v*yC6 z@|~9_fntaFq}}}P+O#&(ME952_QIUN2xyhnB?H2vf`zH`xni}6&kRJ3O)m@9{x%=y zAvq+XXx;yeNO7*x$<=(|SrJb3f?;S;N30NbFQ`ZE7spB*8<8B9YMlXwsn|k|{-+@a z{xPNhY`P`hX02I>`;D4t(ZNlt29||EKw0)L+b&)>@(oszXNv=63*By$a@0w!Cv7Yl zJM2~VC(C%+Tz5{j%Vx>oz9?a+fXfdonl5OrWd}$v9ht_B4=V_!(g0w_oBm^aak6P7 zJ2=`cV*~lFNjoZZ)560S^8QZ>J%gju@;5|-!krI1KSyhS3+eIcqM)6D4o!pKZMw9~ zp!sY0?NM*y47ntk`z5g^r(1l(ewA6H&yzyG=IA*n?&bioHkWoZEo-SH3HjzSf6o25 zz?=*76+64!iWuco@{{rnHll@mzh9aD1*UFAQTgJhU-EeSWSqxzHe`gn~oS?@I6 zHl8GbmQC?w(-~k3nnEW_qR`+HfVmy%>fDF`CMkdRV1M_DF}Zc1i7PoS*nLl_c7l81GLx(oO{ z?Y@tZvQX-Cft35y*CXHaglQSN?b^m?Q&*3DQh?w7$%p$hw*$`+obozHO#fuo6B*O1 zhw2#`yptc8I<5G;@TzOye1|mF7`zYTu3Q+FY(Jm!!99A64F3?YqXnRm;5ftBjBy%A z(*W5H4za{O$~V{o#TzlZnQPc8b7MDh;Y~yT0TA<%@|w%zqL`DRm)`WP4%r@^l$R8L zc*e>QAgQ2T5S&aZeGzzL3=@E5vx)P^M~Q-w4Mf(S{+#!|LSJ5XNc~r`N}k(aG0ZJ9 z6t$v}0VIenUa_)I9OqWPD^HWeX$O$t-&N$BZHhivqftyl7e01-R7|@-{%^q+v`buPr1iia~>d5;IGlKTZisA(fZM+U^nE>yL#OFjY5w;l9Ob4}>BlwkNt1R2cbrE!yuLPY`0|NU z6kvJC5GtNsW6`df2{Dss4Zm2M3%99!Z;tC z!ps-IhBk6W4FlVD;yO@3)5q`Lk ztzHv5uQf}O^MRhm7$_(bBJ!Vq!61$M)7zujdvp&}nd!E(sKum%$5pLMb-#qXFCq}M z7|i}mzo4$%D&HcW*Bp2?*ix3!x~N7rnnT$^4UwKz5Mkv0rvha@(Wjm@Dq2FttieP2 zw3B#9wp3Rb@TGPo#b>0}D@cY)Q#NEB;Xq;d#}lcmZZVtPWArlyv~nV5*QeqoHfdw` zfVd5KSebX^j6=9+=w~lxLZ&{NCj0q#JwJIg8ICW1Ot6J6wPM1W4r4cHek@I=vg4SQ zvng+IOOb@;?LoG&SvBp=PumBeC*mtBGB#GnY{YdO)CUwGYvb%Qe|>ni(d(orlT+X* z>8k1e&}oO9X8S{ot}+jfo{6>>+B>}OP45Zn)TNbs#;=lziLi={2YE;m4qpxbe+qs? z4$D7-g&|28>R2RdTT5381htE8fMxIWew9YYftaefU+WHJx8GirHzgN$?F7DeN#SW__!qDp)f zOd{yMBt{v^}@`K6qW#Xct~k zOnu(lRcvhhF*BZJbBONsZBqAlS7?e0mjqwDr+@Ch1q9GVr=xE?ML~18ybc_KbK(c+ z`c}EbAQs1>@>Y^RKX7&sXzD{78b0lmZewn#5Grc16t&EE=g=%@(FPW3)xm_5XtO6% zJ?0l*T||_y#ENh?TY0FgPzBH9;g{PW=9U6ssI`J=D+p42>eX1xpzoBt_uj7WEV`Zh3-6SCyIafevi)z@ zJKPEYG&q zHYN7bpSOB`$qRP@Eg`aWE#$F&)UmP2(7wx51v@L$Mb{Zu;sy9rehuv6qPiwyS!%~J z2EC$1=Qbmjv=)Bhc_eLFV(3sQT=!Ee03IZ)?>B0piY^-*kO`0%+OZrcv&UQsKUo=u zQ@l?KwG9u!y`nR8^(pw4anCSg9BK23pV}E7Imo*)aj_%wQv7p;RojydIOdYxrV*YLdsB2nDf83P5 zesa>)l8RHXPqn#+0I-S-jIk^#vs>*Kg${)H2|LG=GcauaKChf==z^|V$6LVwZxO6e zR>}U>L8g~3ep6>;=dRwu5d+|8Ae!ol6cJ1!cS!F=JVc22yti$%QPEx%^aeH5_G&U} z$CqxNQz8$mefIV;@eM0y{P09#yP_nDB&rx4JBP5I5?iyjx4Mr*<%QSljs)J%$z$Sl zdom+9%`}t}BG!0p7y1m(vxW0I+O%w1p6HFqYA|>*d=8-`N!EUd$E36W6jTX%<}>zb z+4l%fH36@@=ya;Z0p0ms{0nj6TynM~9O zw4FNs4E=W_3nw~!ekCkB1wv9_mJr3oMDe$?8$e=fYy7NjyK?=`GdEvSz6W|gy)OjG z94#L6ygR@u?v#5Xbh0^LHC^}f5HLl#FMdf<<+zI?#aoJFWlAxEFqc@bjIG@qbX^oR zl$PbW@nww|fg<$dRsagWMSg3Lf`P`@sL3Npz=!)3i;Xulb1cE8y%_j9MCkm2D4zYK z7nZ`blOa5W`-9@jMcN{u%pa{{sxFSUIzthoI!t{P*ioE5D|&Pl_hy71So$Q951f_b={#EJ3bY_!6%fc(B^4Syux@ypi`v`SmLMrzbTAjjRzKQDT zT^oT%6|!}OYHhkAzEq+&5t)cms5#SXj35Iktj({rz^M#r=*31JcVf(VUFELjp!sN0 zNLQP#96n@0^-Q8PkV3NwkWF>kN!Qw$R#w< z+(Zw@<}u*DEz%~N7sinm&)pao6x+#&pi{NqGhZ{9QGNm>2uGO!=PX*3OcmT~F@SOI zyEw}pO&sXDW_=!g0fX#>)l0G@D;~DRDr=S z`gVcEy~h}!wKW6ESyI~Qbey=vgxUmHBQ-frIaQGB5)nr({ILtw6Ba@QHnPM62H7ys z4U9)e!fNOo8iL$GzP0uIoBPe!0rVT3rp697Cw!4R*_9<9R;KzFsjSM-3tH()y(+!# z5*e<>wZ3vgLXPb_FkioRgwTIkK+Ryoi1Wy?|Fl2;^lOLGB1ShUIfbx?8yW+8FpL~BG^_0r16x3v4bXKf8bh+FM2>Ln_KywpyWJv&nIZa@>Zkzat#CL z`%5@KE*y2uZj)GcPY6k&s@Fzxeg2k^$7<^+0#}fYVR$fMc5oY2;_gSH?=x{?avMf~ z#hRIZuNIQ<12MXOroSifE4I_C;v_@4uXShEPMm3v>~gl|FBJu$#&YG8P38dXykDQMgTzW7Wjl zDG8z??wN0~bGGHXk^7*2f5}G*#J$Z*EsZLSbW;W2;VES6h#>RwG+^~?K%%V_#;Tk8 za=In`5^>4Y*Y|U;E-;zTM>=nGFGPX5wI7C%Gd+i`(FbEa2%?U&0E$U_k2%!8!Q%fUp02Cf4>2cpSNBQe_{RVt0^-q{5sCquXp7RPp0xJbS$W@JYmRp|K(xQA=2V4|E2z3*)5 z@U&jM;QN)&LAFr=zv_N4#eS%w81;S8K~M0J16#vx_zZI2XAU&pwxS0x{T!S4=KC5h zm3NuK7|C($qY$Eo%Ld@43~DEJLUj}2BWG?AsbH%s4C+=2GYS^hpPmRxgLemTT|?dl z&=Nnb^YLY`Ox5}s5{WhQDYz`y`z|;oUvRwpR&Apt{iwHRXdcfd&-e&C(1t{yAU1(o z4N0D152WedZ-4lzj~*X-a0P(wh*u-+%Wq879Nvn45x}D$8<&f+cj)jAA7F`cn4vA1 zP6&?}9-4~vsbL#VbH1Jm_`%4oKb$zQT ziZPa-VdtP6w>}VOdzQ9CKhiqH3X55zv@pk93=6pu4VP-WX+L|HhsfUqiQ)k7`eh2E zBD@O<$#S&|<%cdMGs!7)H57g?nbs`ZL*>=O$*0V9mv z@P-3$K>LLr;D?JNjci9I%g3%wJm$2n3{Hx&yDL3%HQ?6c*q@Ni$a*o>cvGE790te+ z$nO9emURia^KK<`s_bEurDd3c0nAA^IJrlC*yg+_KA0=0lxf%Ot86Ph`EYZZXJ%_o zZ&LQFZ2`oK3iKyMNGTTZZts_mC~R# zXQI1ZBo2B+hM8^SNPpJ!EsCO0p#wpirS%e4A3hjJHL@~w-VxD&B1y6C1{VN-FuMkQ z6U>s#{+jYqNw9P#;k*5+UT7B;Yh&Nxi`V1)c7Mx&0%W9DA&f6sKkSe1+#cG0u4-Ove)Wc(uhrb)x)u}f-IZ~>Fd9# zm02A~W8qXX(1y_7s;gRfTwhp4vv{WH>XSf3t!p)Ksky2S&V9%gqa#Ceks(mD<=YYw zl|X>}LWruVMVKFGxy89*3NMcJQS#m}27xAFZ3M%ckrpv#>#&f;;#sBwt|X4QHPIq3 zdHY+fd%=I`CT&d;b`#4>v2If}vm)X&^%+3Xht8k^cgce_{c`|}zdoE- zT$ZL28D@W%;6tA;TxU&*>Rso4J6$*+R9EeMDqgaIt=iX5cXT(~(q|xL8G-g`k=djS zeniPvjeotGa?zJzG;0N|uyz&3PX1TXEPdrysGT*-R_f?&faK+_EH)qhu`nq_>f9#p z>R%F{tSM|SFY%1FZKX>(iEN*Kk&O1r%H@DO<-tLRZMgs;XZ^Z7g=>gbrai8Po+|+V z3hix~miSCzi`v|8i+#0hY`kZ8>cjaVX{mipD{XRE{DzlT7b|hiB)C(iHp9!Yh#~I& zgNaE>quc5OswKug!bDL(4Vn*T>}a&<=}~(6UzE?HThq}lT>}Uc7Xm)J3{889UATel z`_P*VJm-ovSjX{(X(X##9i3qjCyxmtnh!>6&;Rr#y#XHrzBo zZgQ&4ef&@lucZjFXeadz){!CuM_OXAuukGM8I#qcw4bj+mClbK=M1> zzVo3Zm+^Yf&?4HBbd#`7<)*Y+T+O$=fKrbZTJJn~Cxn9n2WksfMocc_EJjQR>-gSFBR)Yl^IT zXd08L+iH)?#w@$z1SJ$ljGq47`?>Ys>oRx&+T#X~WaQ(R;r+211szy@-4K2;`RoRe{?n!Xxh!#e=XMzAS@*b*Xt=S-=%VBSWB-?7bog=SDQ;A zSAKP%eJ-n^J$4=AmB2H2?M?cEN%Pmo>L6yePq_UtK0v+yWvhfFi|azsq>H@ut#0<4 zpt*lPOB?&4Nfl>+nl5Ch?`P{Fu3t9cfX$g{6 z;ZJTHobtU2%Ae)`dj^Zb8l0`Zpvx|=r&TGJj34KsaMOXc1*X!nSd70)FoJr?h!qT| zuD9tw33*J_xKporFoJiTPJ~?H$)XF%b>#sJ{E^r~%Qv?S*{190GhXWImIGEOH5HyTPDa)E~@R)*ptyz5P(Tv0QYa5hLWGz8AkY=L3>UE>77R-|7FoPY(iGb zIivCIZF+PkhWB!%#xd@Z)Rouf|Lr5AB{?vGWsyBK5%b-8fhnQa^@|E8Bp`lCH}?- zyT)Mdn^~#Sccx2n0q&;j^nxs=lZQ7IpGFPf|82&&h0N?czwK_(t5Q1giXB@uq;EFA z)jjEY+|&tIXem7&HYxG?;d7@1IQRHVPnW{TiTWiU!9%hsjIob&i{!x)DWErVtk!$P zI+fD6S1JmjS%`OOx{~$t)PJ9ovZ*aM<@&lju&%hUM;wBFlgV*0B$Y z9UD9Tq1j64kQvb?aj!P8^^>;iga}1;ioJ5U&h_X5Dr#`Qypk-u03}WM<1GcaOTMD- z@vJ}WYXM><#dpNIBOj>gLgTZQ!tmy=A@2bxpOIWJ$#xObp$d)HIPWM})R5$3Wc3`B z%jJE1@X`q)nBL@=!aaPe0;>A#8I;i5(m&ei+a+}uHeNOj{rB26erfhQf)Ft{y)zUg z>cqYAW^3?pC3L>*KpXfqasqHWj;9+P?$qumAgRm;&O5(~KDXNA`d?LBsP&F^0u6|I z{xC@2OXj-m)uZdQxnQ!zrC@o7sL>7d~D(LuD+( zjz^nPWK?Ms`|#SGqQsea)uzkuqC~X5rz?-*gG?LlQ zi7Mc-s-6mqmDm$LYY0X9yZ5AhNF7V_HH=q+{D+&ma`&NeR2$*6pEm9(T}_iK~6Lgfa3Vzr}vAWQlPo^3F~CaIl`?$`vZ6 zTPMe(BlutYg-$>dBdHJQetuncg(EUG4%-rYXC3s6=JRbPk*qY}eEn~tn&{BxONCkF z4PV{X5KX_D-YCK^nk=dt>9kV2FfMy->ZO>Z?p|J}hE;coZhR-3zdkeR-u<_)W;g^B@t8?s5c`7Zg2(swj)c{qB2U6X-Wfpe>Sco{cifDC>xnbzM38 zN339EI@np6sx%ijZDYV;meDp+)TuUn2o$E4?@-tGJ_>R#NG41(n><)s=vZ zInwtYF2jY3jrYM^8A&tlDUti)UK%b|-`i>)t20XlKptHREzAMAY)R;C;wCY|PIPte zTm}NX%8z2TA8wB}_tecNkVsdpTgF^(_Uetq??dXXa^w0BGQ}!k*2_g16ECf8(S=7f zdorg$M%+fC)4vEzO4}+z%(;m1i@K?`TwP6ex+eSrOE^sa?Q>e|>5x7ExielhW%uzW zD0S6B%uPy#pNHIXX<(`T6vR(E>Pc+V#jK$MM8+I*6LnsDk(GYp-C2E;u9+hIkD!G=WJ7qlGe@)Og-LO>sLkigF1c9ldpLg%9ZkMZTV*UJ2%E<3!SVW-bqL*e*Z?0Fy#?2IW_+HmXXabs;B8rbi?bQC*iIyw3(!` zryX@DlIc^CYz|cxB=qZ0xT~w?{7vw&WDykV{`vrtC3xq>Hgn=wOY)^R;v%IgOE;oz zRB)l8dVs2Iy53Ljcav60cm}IxR#>!EUQt~au1t$KpDR*nNb{Lo+X!gqUP=AKCJ?qf z-W|u6J@W)lTlD{EI`43--}nD>ccwDwXpOfx^!9nxeIO3O}jJmCJcIzgV?j|F?VT zJ!!u6dlBnefn88iH~c+3H+AH!#o_jr8~CJXi_IOS_F=0NFTbqOlCUKn4DbF$_IVmh zW2eVef?54@JPA&MuAT|B4LmRjWA$3^4;(GgWqDt7gZn8D{H0i!h%8p#-9Xwk{T}7@ z`J793+8H?FidThvZmWHrP`6YNd>b{;qP+wMhB~=RN7S>+Q03sd0c!7%v-aYak(V@S zM0BDY_M)MgUVm+tQ{USt?n6fA>Of#&xb;LSS0u>Y*m|NskzQy{I)?WW6eLfomsg{z zl0Q7tvXop@th4ANTdUx+6kW?vQ6w=MUcuw#uFQTO@jON076XlX*?F|XdcPj~o7U_R z-|zF3@}80^;rFV=E&ts+qM$)p^rg6)UVZME+-++-d{*u-!NMFUF+``etz8ufS9b8UM z=7`79%^zQ@>0<4AIc3=rwuZaJqrC%>iMox`whUD>@#^nq$5_Z*^zXf>*5kOUlOK(0 zq}?>jc7#cEvR7#|n^#}#0~u)PE3h-#r?;9Po?lcY#%)pAWYx1|W7RV*!YO*72Qx(D zdkPeCtoCmbCBn1scv-1hm?dS@8;{*oc0|L+$C7O$QE463o{J@T8P8m~V{i27&>?QU z@in!ZRAd4Db1K+gy8_qBsrcShWhX>fzY20HDz(=ZeltYwm~6B3VB|qCQE$TS#tRmf z_b|q#dPSMlyp@uUpSQ9)2pQ1XIK7N|TC%3JGt}d2+9Axr51|n!yN{MEh*GagJQ7YX z2kK*&iQ=uCm?f_&-Kd#-#Yp1=uZ6M5Sh9u`9A8&pye?&n!CC5Pvm`&A<8to(hFg{Y?o@s-c1v*1^)EtiBbkd22yrwTdEA zM?P=d_E+Pdr`VNpXB*{f&H}&Yd)*lKl)lfu<&%oFWQ7qfsikH2qP6VBw?&D{x3-TmhZr3i#Xe7Ozjo+3IkP2o8}EV-UhOZggtB9IPkjr)AAEyb zWFd`j1cS)PxPugE719nLU%v2gmD(GX4JSj6_+2IU8^PsNrqe4ESKP6j8K!v5GRl7& zVA~?FH;s3AU?=ZlC;qT--%d24r#3F1y3rOKH^Vmcsb?clBZ_OSWx-hG@+55u=zWnL7} zk9a=e^1CZ%;COABwfgFSJp>QpVAoeip%<0Mo4-YaFZnU$hc&seQ!*cBi!}YrX1Vs|xL~ZdEFNjs+JG4Iz30J$oD3$c-zd^wlLUKg z#d=rHk}9CoI7&}1?zefI9ZgBCjL|538|?dt42ODZa=FY)OnIh@j)pI3^=%lS*poXC zJi^@h){v2|9<{-5Lze~)q74%hM~_~K@=TU0g^cEzABxSYvb=lu2NZJ7BGT@zUvjE0 zHPT+Wu48&_rAIg9H#Aiy6E7*NE>q-RwHK1U$&yPcLq57DpM8}*+atf_{NG;zsYhp$ z+TB+IPRVu?1uMkXJtDH~XuDnS4DeyL2d?8LnbU|m8Gp%G!J|^eqc~#Ll4ZoA6KUnI z7Q>7p?08qWve8cRje>ElH@*0Wdm+1^{#%jk9ARLN?fr0jdx0yXp?Y;q>m>QFgs*nC zzlgGhC;ltGN;v~DUg zxZ0dc=@r$kJ!)FTm)_XfGk;}geCuO-$ z8u9KKGE3RMyw^{=sEHzAS_eDu=a>3;&p4OGx8AbPVMIGwbD!g~5*cuOf|lJDPj3JA zIo#D7I$eB;tkDkCV}TcRB&$K{CZ8ZB8> z5tzNXaO>uyi=1m(uMshhqa{nDfWRZ-gb(+s^t9Q+xE~~=9m3i9Z#y0LpUN_lD53Oz z6o%=?2%NpC+$|#HYknu2QA@ze7{z#IpX5Ls!RH7UhU)h#o-HEA%SCd@N0NIMEAznL z()_xEn9%QSRzdNM-5Bau^C-}7Ip6wl3TpWeAPXC$efgMzL`4W!n`^1x7e*#?se3P6 z&p;q*p1wEgZHi9B)xYb~f}>2{_@a9t>iSR982Ppxz#B_}oXmY* zAC2e3AfQ$ssyEV&B6iD~0kVidrZkGs1B_!D;rH^x7d0qeMXlUKgcMW5Me*1!GNV+G zh+f?y8@>%1C=1ei-eu*^Dku0QPCtRummD7ha>SPt!mL>RDHRFH^25M~pjSbCXnpOJ zT$`BSPj*;FFo4ub@gMtqjIuqjhJ&h{$1(#Y1tb0I1N~~&VZ$`fBO8}D24(i;h?7njR->mHk1YNogwKl}_c=|OuZI`s>< zMx94p)ar@lc3=&t;$5Iaqm&W%@3v)J`9_-2{S~6IZe=cV*S&dMZ&9{Z_VFM58_?Ew!5ELxuU&Y48Vv_M$P73I~=X*d&||i zEF>60T+av&ZhlWP!J0)^bHFLrA`%+T9i6&Z1V6U_%$8pqwI7iXn&jGBs1A1hEs9(1 zbDtH(q<;336ljhN@O~KNwC=y)&u;2v_~I>Msrb-Ya^e)WBED7UBE$e^<-Dz>@r9{@ zpP;?DZhz$$h-M2Ne@j~RFx@J>U#XT!rA{$L$v5Ct{#X1G(7~qb>{IlKk}gvB5d|P7 zyt^Q(uYFqlV;F2e+Uoxar;FiwO0TCSIO(1q!-k)>?PMdPi8hco6=zv)uju?(MK(}u zixJK+L+#7y4~N14e(X>Ph5L~llvov6_7fDZ>MpSTru-Sd@enJ8J`5~-7XbaS3fRdH z_@gd{y9F>a+7H4o*lq+MO1Y~mD}5!VwWiHSJ_%RiCQL@iS6XGOl@W$(@sF8}t7q3+ z!F8pr?(>`82BjgJ z!S4revWaA4HZcZ$s?tW)->?~AcS+;ut$DaHA%a>$6f>%*>TALvIRkq$>4BEiZsL)M zKA*P0r4Ch$g}=WVuZh=2(Xw(c&Xx+*E2&?pZ{+FzNSU021Y&BXbs8$Grl#CEYBU9L zW`C!*K-0;}7uXXY5%|dl-j7!0R1$_$>Ji4QUOY`7}|dd2RJU;5n$0ub4$ z91WGh#1DGFLTR+BUAqekseaSW!o*os0k8zz8_ZaaMlOk9{Z$>@62lM3CD9qux7=ql zz2U|UKA(pvSU&werlh}tLvw>~pA@pQI*hOW1;YwUsNgQ2xQqfJ& z~BdG+OeOv;e~v0m3-&NrfZ{3tFKX4YGG ze?2|Q6|}-ia#7oF|02d^P2|xl#Hg|eHCVV_znkgN>|A7UMRkOPGZ-7B#yWBAcmy3L zz-?2Vw+kEV*4f?BU&kktMD^qD0qW*H0H2YW6V3M@_zb#FhE+x)1TFigVf%ZeRuLiF zMvV3@h$1W&^yE%^(A=;K?P9U1O${TR(|4nNF)kplaMEY_wi?RdJahW!y|_`k@s2Wz zAgS?qF_P0Pd7i~sh0MJ;nkuZTdqZHcwTKJ(`!rQT~bBA^(x zg~k5tQqe!(FY|-}i7~X|29k?PsnUaGH!;Uh(bm@4OFPfEHK5@QwSxI`-Y*Mdy^k%j z1ob>}TsO$o4}+%w;%K+u9oX3@OFHWGAs)p9wK=!#-eQ*{2;&jyIV=(}FUEdKL zmS-eE+d&`!82a63-$l!6OtK`5hI|KP=o1)E_RfS^s3yd#xb`3NenNzCx~3Y*!i|Ch zYBA^>Sx^ERdzKSRAN%k8!r}c5U&0a|#r@{`1u>~&3KCSSes|XyB1}sD&!b|@%Tw>? zD0D2C83&TS+4k-iri!;vbde_|j1t-vC;aH7rCMVG7BZ;F>V<~IIu?_Dt1EvubJDk5 zLWlt6EbFt4ZgSrvjjoqyU&^(9)t=S7e-2!@ZP#pf>8`$a3E13apOb8HfLR(}$;3Wc zbA3gwGi?bLMH70j_IpyThD`L?d`T{ElPJ1@ecV-dS3%GR3XfacbxdbO4Yp6_nkHE7 z^;>>a2u*Bfnu^4^5S^CFz{0>cQ5#cJIQ`$uVDJm>mh0zd9| zuBG%mySQH)^Rzj9Kt@q^A;^8O+TI#78YM%;Da zi01YnKRAv8)3gm#y{*3wz8f#8u} zgT+Q%7keDFch?p-Q-~lD9E2ATf!&N{iiwh#QV7)p+Ly3b9T?lD1NQC#3><_sSQ_T~ zwr+24d{r5ydSwT8K%*^>Sd_Qv9nVCUGnie^|CM>PRqnOy)mjVlwMRnc627eS_Ljz@ zMTWrUk~E`wUuE8NAI9iBrhU90d2IxjvIWdT^gHipv1QvV6(E91i{4m|6jugR0Ap_X zu-bAV+Em2~{}h_y zcxTS<{A{?+6PDk?Mbfr3dzRhun&D~?v0Ze2rqTBV?Oho~*f1Nh?8UqyNQ#tx`f_y# zG)i&7AAn%#M9vb|@Whj{r1=%u!iF^(b(=l6GbqK_HN4JgPGVjEX!dmZQ{nw>(Fp=}TH}WoP3)0kzpbHyY8;Z45fLom zJTE(ItN!OIOA>!Ut+uhTF$oC@z!9!j=id=#7g9w9v7nzNwa8Sp zaV*KbE#&4JDryQ04>KhQA-=v{-U>DbwdJedO<8{TgNZ}pl|Q5wunQA&Zb4o0oKK5m z5ua%(ABSZ0d7$8CH+L4bf2o%f0Tw2#cPC249Dr~tk0o623s z9RIy16UK5y80phB8pj%RbJ*mkC>eDep%P01^9;Sa!nln{BRoc>Ne9`l;dWJJgUcSv z8HL9Y-E$EW=NCmMN zZVMmi&q}XO>hv)3R*GCoQhZo60$Q8vot%Q~#N?PvLEg!kmPEoP<2U4#Etq~+o+}khJoLIO}s&lBfda%REv-w7(7QcCyU zdC(_UCQvEMZkKR9XWqGntw9&&A_yUo)wT3--$WI?QGg!)aXGf7uPL9`Gdz@>bo;8E z?;`VS+T8b=p!pCZZt!Ve!X}K64=334@kswvb6Xn9{GN3~9JjkG$27I)81!A5DLobZ zJ0^yf_qBhqsv30{C2q+SJyt0yd|{HPp;ED~*Pc{#_v*HVLYtSZXB=oD+vj6|`^T!{M}C;6uNei^rn8`rwjiC<9!+AEo@hSPY|SKs|( zdCv1$7v-|_f-D@e*gg60CSXc9n6uKyNnx?n*ZbN96F_#`IVQ^sxp_LIm|8&wBh?A~ ze?XEXuTW%Y)IRT@ctC8?V!`Qqzb>?8xt%xo1>DwnTOVXx0T+g*1cP?=g@}~|LZ+%$ z;`efLI^<--ukSRjWxvyyayu~aL{NGjPR4y#RFWxLIyJ$!_VvMq=Pir8V3yeysX3Dn zpGf$)nr?q8;oY6ZgqnHkb-LH{^Vfe*-djit2nkCz^|9jydz4*mw2NmUQTdplo?|&0nk^u4})r-1=X}(?}qN#kjF?t1D4f!f7uyKkP;2mZO%6ls);L) z;bQtHq^v0@g6~dz18i3fh7I6(d#x!xrS*nqUoX|NB^gy=C^FHK_&EFe*lIgy#@~V+ z{pw-0)1QS7sRhQ|ogkQBCP(!d8E?ZC)CkB;HDZBZp1;+c0iI2RwPtELR|Ij&B_r5@ zP`dk?3|CHkWkG=y0Fe1lp8Bybu)2@Gebn+~0g{OYcH{8h{XgzOd3xO}kyygi#p%Ln zuHh!eczxX%jx{e*U82J3YQ&h^l?l+QPeyC*K3atJ#@|-#D&ozXlw3k*qEqsD2HvaL zEE^ny;mGI_VSP9D{TXNy+34??lH8po1XYYq{W3y+vhN@m@~G`IEL4f}e!}HNpSaj_ zWO56jWo^R()p&7adHv&yMLHF|$NLABT;;PKOVNKClU~Sap2SbK;%bRb{AHgafNZIl#%*s6P*Slf)aK#D9tb)r|9heKm zitb#l41&F>etmX%~|Mj5HxIHufKFZGo+ul?Zunp5}Y_2mnW z3iI(9JW>_J*KqG#5kv4iaE{;3NPn_=07ZFI^`llAXKB19uuMJVdb-|7A{=*7CtUtc zu>43lW4K5sB_RYR7@^euvD%l(VdEZ=jCPliwJcUML>(P|=8=0FWzPgw@inqRj>ra{ zuW9}1yaTt37l(>G2J|Y2`~e;BxpB~A$QXMQ1;WHAM89jaCG&%RfOZ{bK%%TC1TQ&y z!kUYhQX6+V)Boz-^_v=hZoc{YD^$_9 zHtD-Oa~`Ya1G&$q92y;=N;Ot9a_Z?MdA16d&!eIQ+hg0ikB(aZ4zGC+cMlH_yJi1v z%=v9%zb3inx&61v^!KyvHLr@TR&W`;w>3}@&kMpz^1wxbT)|rG?E-YF%Ajc3D;0*S;$$Igu;8`a~Xc3ExS@Dlt2|g6s9MQkZp|>;qJT z-(YWVES&zgHchMFvPmwgG71Ul>*?u9n5a$H5Vx`g+(ij@KT!2J3(<{Ku;aI@s-&yy zcAvA3-j&75wRc>=zPRPKN+eYHVu<~Fyev2FvWu*Vy>y`+E7|GyUOjqBO3KrKo3y0g z=Tu6gO?wtCWBf$a-kSlyTJ>G16Uy;DL~lV=>A^b}J5-oQFO=ov0$o#|=Fys#k>U>8 zK7O3Pyu56KrBrlb#=W+bm01S`XKB!r-N%e95>by5-+qdwuq(emLBbrS(ab^na@Wnm zQn~n^N9xo4Lc=1NzpMeWB(jFi_wV0lYEKLNKGEcrW+aCJv#;jh{C8(;ZqR;9cQEjtEg;hj_h3OR8 z`?Q9!+iA{&zly7$@OzpAcAgI&@L&92cY3*3vDnL|A|Aw#GMB8b<4MG~Em+}+-YK#C zHa*S3fqP-3?zJ+#&paOC`KZe3_~>ZvFV7D0SEI5&?7J@0G^?<(vT{X{K3vzD>G^iC zvpErsQo^ArmzS4s3}$(xaJGd}WhMRAGbw=UJ+g$$PbBYs&O$&ydjA_Me^u-J4y`@@ zWa1>AJgxneNd}9zJtuk(9=!SGo06Pd_G8=dp)-r;pMA@^xxOn;yx%lSoGBULRGZbr z$ui1-+LG5FN3y;`YZpFZ;G+T!N;eCfTm~YvQw^( zHQhG{i<~ahtC>Z>cIq6to3*jj@H)NI`Guz>73EC5RPyqQe72~vAMWrgl{RHmuYZ&8 znb-A^RolCysZuZM`=6NIIWPVlDfOQRCMuKd&Ye44${5#-N0&d{54_3B$EW>P;Lo2w z{r){OFK=SD2wmCQ2(M`0iht*MZKt?qE6TUDv{cX3^jn&`2QU7y0?v8p*w|Q<-C=mL zkd|Ch)Vp{5Nf$b4$n@2+Fr(D(ZAIp*a-aXlc1$>Tkkjh>Og-!aMcedF<6M%G96$Ko$-rf!EZkP!ADuW;E{}#Qt zxVQ|#MfLq{pA=b%LG#AgA^4{!S1JGr5v#9<-|GarzrWu=#K~aLOz0(!T6R}YkNWM~ z0W<`cPqUmjsKSZ1j-}050%G99-w7j;VaFwN=E3#qI!bFQEX3Xpq!l3>O=|;^ z`W-y4EXzU%D^mY^l3ZMBT-@A4dV?!7podqSNK&345FtCJg>~gu&@rOokET=iH+AtT zzQTH4UQW(n$lj+gAB)|6i0b9WeR8$=z5^D*-%4Z{NJ*JAH8nrcC7T;*;1sculam)U zHhx`Ei~%xaVbRfdZYG_3pky%D0ssfB2K&Tl_mV7jo;Uz}LIauN zZl{fjQ?ars_>cysR9#c^VV3yTXe7>LD;V9@St*LfB5L1p_(( zgBmQu7qHgeD+l)=KdUDEtS!ZSJUklE+bT%WNen?tVhrxw>FW$9mx|eC#fP4gr)T4Q z9!W>~%aL#Wq}QdrISelu6@60cAO4xq-Qczs6Q6*CUn}o#y7w%1+M}enSP7|w3ea@v zXbCocaDIsUoILGe-zOq_mny5I6!K%HZeUS&+UFfU1r?S0)2FqJsK98nBz$PU3qMW4 z`x(P7TiIZ$-vE@j?UZqQHI8Vi03bW}L7>Tc^x6)mk~7Uh_A zE`}9XCd4OAKqM+sgYiE9XF^6sW&u4FD^4ZHCI$a)kCffL$7GDSu)et~#vQ`E&ra?@ z#0X1{jNha>znVPioL$Gn9u50n36G z>+Gjc<4iAHB;ieq$&urdMPYGJFtH+|UUE1g@y`yA0I!HS-MU3E~9NEN5&GIFs{G1O+F|t9A72r?dv1OZke;d zW1Z5%T6C5SfB|(MTn1KpUR{rw>cUFGFCO84YMsI!_J!`;C!;vcN>a0Mfva~#s6CbzW+;1q@bjX zyNE9qh||18N=iCdz8&^;vpIHXXs8Ukg=CBQ2~_e2!s28pj#G>EzlGmM`wb9Zwl+4- z>^RYN!~Om75Ta(w!=&iO5&)SRgz-|GF zQWUj{zwX99A_{;re<1K8VboX;Rxuy+%V11lD^)O|Q<;a1%>C`TTdd-CBB3E6YH(U! zxS7}M#5M{CKioR7gUIHhT+) zi>U(m9=56geJpLxw9?ikOW57r9Z~($K!oKCI|Dg+$L`aqGs@J0-NmlRw308G8x95d z+qOD(dkAn^dI`4%7Om(D-%@&fYW3Xm@~DO4^xnGPl}6Mq}ZA7@MX&+XQb&n!w8X^mDZ1zk(l$pe?JXo z*8IM|9}^VS28|Y*E7Hf04!)oN6SYY1k)H$U>}V}`;TR$(xGtyy zOaEdqxMqvNMf(TFK;O?rU^gNv9M)4qN3e`Gx!bz|-;(lqYBsR|?za6fIK_NILyoT% zzwruxsy({L%!p!+wMQXdMvyP~R7{MXs$!g3_Q2_Y^}I_Q>HizOIr-L}znMJRCF%gK zum6?buj_wtQTQdsLv|jlTd>t63Vi%`^5#;3eQz#_ArLS*0XDes8he7ah*CbkW*^K< z!ihrC**G7zHh+aDDx9P&>PqA_nSlW?tD5@f`*KSDBbXkN9!w>0_JB_EwD&HPyW*n< zC}@W@!pk~Rue~0pLdw__744{qqqJFlwTYT$Dfsc&OL+X-tUAC%JAfX~r8_OtTXj}L z*W5;LU+T(!iL)GocupO{PGW9t+o&b;*rtm+QNG{rVW6y!h)f}ilIi^63} z0~9!}DmH+FBO3J4y=HhBaq|wukm++ZAcU*>blG~1Ng=T zZtn4zhTHQ;+qYFS1=nl80_*}OlJ_lxd z@2idjshi`5s(l|*z<1xjLoi|&oos`U-FlEIWin}`j1D1$Foat;MVq${ydyyna73Iz z(3{YBz9JzshG;wRbU~-_!@4I`zRgfJt7|UaG0bjR)vBUSfnu5MkyZFfq6 zggzwk`m(i%J3?c&*ZBSOzo{tjP(w{R=d6FSlJYPevE3rQ9q4Ei*EiKBP-#gJDxD(X z6d>`liT+dBdeYAWvB{%XT|iGV`DPkPS$=2U@Ebhav5y1dhKDK@(19BMhmlghraMld zTYvkmep91DE;Ir|PF+-m@q4LXt-4Q?DKNUCk>o7+^Q_*t+;)T#i(xE-5Tcl0&-1qD z0*ZPNNy(-GRF*AZoo>|rf_~o`O_%SA>OTM-TS4c=GXrP6oef7<3<`@o);Po!Eg^&1 zZAb*8XiD{URTgv>4&_ASH#JSnNm9Rt49!xS%eWD|+0tZfONt93%~tXAWb+|rz%14~ zlym0O9lqI-p-Jr3J_sMM-LA)_vH339o(nR-$vBv?uS`QiSV;cBEO~%7?ciPw5$LSZC0)<0^G#8kMH2j#-z41bsoJ5`V4*SrUO!BxXzOHrd6H-hKb zWqG;9XFOW_`if)Pko16m3gXmpl7|R7(HqIc1Wy1Q0z(08>4L-P>16~+=wip(nWktw zsbw(+u`9MEc`V^qaA}st8k6sbV-(`sV5vDS)I;x#C4MXNb3PuR94i`E(~&ixnt-#T zFJj5jiV#zUfWt>V@tfC{rf@j6M>R&A!Ou%pU7?K+#h&#v9s+8UncfC2zKmZdpRXwf zAT5Cy2TR`LBG* zxFy3rQ?I)lt%57Uoj8?|^XgxJOk12XL)r*Q56A0-V5~H{qG6HX?;o-pO6BK8|9^YJ zNCiL}c{+A#ln={BCI<_9};j%tWVK z56`Rd#Ko5opP7L5`bG&Ul3C}u`{`0aefjiTV_&Up1`2K{EvLvc!OAQ;oa)z9N8MV#xf4@IY+~q5-3_3a5%J{I| z#9jR6Rd=;zl=6~<9iXW=QHSbp;TBssNpaK_ORIkKq3TmiEVG=UJKQS_C}&p&)Oybm zt2SiU4Nc$6eNN+89)iRn4}yu&`WL8y4BtM8JPU0D49%l3wFj->H^_5DnGoxV=V&&AdQ5;sz<~smi{iA&aWu)vpz5%HFc!0mS>!tR| z5A#Ta4j?1vlDXgI)E^Z~QZE2NT;clylvXF4K{Ne^Y%K!y(1tnj-x6;u&n$ z#h@l!1;mKvmLxqL5uBwjUFZvgAyWCAJon}#5H+w2Of0f*vVbcRtYKgJAQne4ub227 z{w1Mw(MKulE{qg2kPTP`P5gRnl!#oe}f6d{0LtsEQpU%wE;s!FJT~xT4=KP&qh5@#A&(FEah0+pY9BlOBpb^Dsjv_ zKX#K~!P^JelEc_%*r5lMiXR#3R|d?WVAl|FNQ}Fp@Nfg}urq;(`5gZa;*us+o_r~ZJBtAgQ!FxD|F(0bE%qEy>6JqL#&o1e#tWZZhkHxm zTZ}?Fjwl&YB0|04SU5$~)wK{%3L*fCl&g_9eU2AGi{XEMDM?=?yt2+L(9fUD-#^hK zl}pvfT4$qt6R0gO-tIZO4gJYDBxznW#jkt=ve8s95Ofj&_NQ2-865A$5Y`?tf}5QX zlpqTl3{5Co+DpBo>XHQ*mLe;QB4tN0I*mpTv9~{Pm2QlKsml$J!>^yciy@%El{3V5 zXWh|>e}SS__fWn>M9uh8@^?zdoy#!%ebVv;zL~JM)#4twLAFL+n#_3a*Y+ZHh}X(j z>Nfj|5%fL2k83aYB=TdqGPvS*VQ*&0wrAB;WZXydwjAows!{z3R&owF)bxeiK<0*O z8@}+-dvZvK8Qss|5C?aP<#tBk_B`zsPHDP`KW|%Hj2FjFvB`3t!M~qNu&@p2s+xs8 zgAMo%zCX%;qhhR6Y3SR66)73&&7ln`qVSU8m^} zH@Dz<>m{*?oFCj=+U#TVoaAi>n$!~A9Ex9EO-Q9I zyG*2G)5&Y=m)(L%d{mt=bkC;ERsm3hiIMwUr~@mJuq*$PqAdCp>pW>>od%QU6Y-)| z*R(cyVkrxGVH1YC9gQlFAS8LxIoB{B>y7q3KYRWl`UNyd7xbkcUmK5!Qn~2rCh9mt zQ8i&%PiLCqP)_c706U$vNZ7Dz63)*9*qk>uQ?CvBz;~OoE+d8S_L)(ZVHq}jPHmHG zgMl9FAR=%8WE6?QMB(^%G$!lk=c33zFc0iv=|lsjiZo_wFx}I51m*Z#2ay=0Jl;eYmmAm9arW9}(Dvpi0L6MwI-u3~BaPX9V#kQO5-~Od# zCFAEh^}=CJQcw;~HzU&)nIFExx8|=(=glyQwEJX4bOM`M1|oyXbQR&pHnNPqGn01% z3F(R!?vODLeCaOc&$#fVP030C%7f4{Q}k^du49mRG?Adm&{AQ=bTsBkw|<@P*@^JT zmxbF{|M!ubo3*bKj^!W01ihYe9`5byBSEC8a8>cGy73JzNLD3nynUAgpbwi08c1r_ zzpS5-b(A`#ct2d02jeWH8rH;x4n2330Zy#QQ=wDt@NWbNFA||Ywgh*1C?dh&o)C}o zSfeecDB;`fJDV^bwh`i%Uly_+uFX(}%c*Aw%X+N?zT|G(g<^QPuU85Vq?j4aXIb7| zyNg!H^+58?L=g$a-lfg3HgXRTP&yhuo~MjX3-l;NZpStsu~0!rDcA`k3Yjq@gSNYJ z|Aa2iASvl#ym1y<{v~wz;_Iu7dpT`znbc@>T}d=ukU2@TN)*T@W246+b!h;C{X+7r zOo`qK_^{uc$=WKMK6`T z)rMS_BGQ{066gwW4~&=UzkaGURwa#G$jNOq;X)ppgP26@4^k=evvbr9&%NMD9setT zw|QN$1t}rM;kw$P4L7*a|BUXunQN(6NPvdP@LkR4&n=>rsBlbcAd7>PCEm8Etrc{g zJeS6EGG}lO4WC)S9j{ooKxsJVztciqG%X5qCyD$oF=DECli?a+o2UKB&m(F)E^tZM zX?9xY-2iF(X$_}u%FjPB48doB>&)Jms(fgsy-`JkK{WBTb`$+2IuG!6^(2E)K~gV% z2{)g!{v#7R`l3Gsky>Y21~ruGvfUn4VNd;`O+M|2cd-`}K$5XsmS|u8QtgzOJsp&u2z`=@0Zzh9IIo z`#I5{hT8Cc1BvI`4m~V~f_v9>MK@GFxxm<*6B4)-JFL*IoutaCAZ;xcvS>92vAtQs zHs0-4+=%0s&8r~xnizei9#4-e5^+t~<`G8>`(APzsIspQq%S&=onsJmTqcm*{%Mw^ z>A|Zh$*Litf>vnLMAc4D!|8R0qDQktoFUR!Rx&aE5s4v0AR#@OyZz2f&-yuEFGssT zd*wghx)hUVM&HbrnH~=^BYg%(g3=40b})_aJ<!yADl;y)BI)?QOaI~2i2u18XQf*p$T^T;ct_s8D(XN3`H;mw3nV7 zATjl!&!Su=V-5ME_s0GsmGlMsz9^D(aB^#+pz*5nyf-txVF%zT_7&;H%q{lGO-~bG zmDqQzM$R}`4fD?l3G}`=+AL}k*a#e0^vF-RA~uinmnNx;Ut+=%wo4PdvIPZMbOie- zTtnSpbzk+%jlo#2wdz!(+)QpAS*tEYbB(IHSgBECka51BD91zx#;ewB~*Gi@wLXr|qe zWFa@?Kv;^oHSAn7Oy_+!)43gg6@1RZ$XT1U2B`u(U&5YE{+YPKyksfX}6M8%{-%WPO0@UAU#J)ZD;xy+OmW*%j3bL-Wf*f%H1bqwA=vru{~sFp1& znl`!QUDTc`l8wN1d}#9&Kqti1~!AAwa#Tk?npfQQ6d+*Q7~{q zG)+Ds2vD=~p3gX?q&7fPZv>mG-YrArKXS6&4ScCLp;0Q!8~}F$t`PxJ6|`^9F`^}8 zeiy&Xhg<0e9jnXPGY%IEs7C}DmZ9#QS%CjoaWj)Q`9PEKYfs7MP)HOD7=x<5-S44Z z+Y2+uHLe7Xq5N3$NyBWR_#)%Wp^O4jk0tx^fY-UmNd>+Jjrve@?v9 zxaa>~=^$y{bN|vjeBrc>CRcie8&6%olbO+{9n}Xz9xGS7@)5I+P~V?06^ghSYRvmB zT#b=SX;BI{KW19T1Za>*H!=~^ORbHcaD@z))HxCdPY&jtmxEogV@$HT)N;wJjqI`> z!gwLH`*3ZXp4F1`$dYrUKJj=e(6;koNBY)fr6Cy5XaW;;5WqPWQu9Hl#pY4aie=We zY+nLsyqVM*b$z%VsA?q5GcThgkB4R0>CS+#F}SvY)$soCv%cnRQlS1T#u;sX3YWE7 z`^P2-#4X!t?T>=FM4CX1?vPRd3tVjZzEI0-6eeWK?|V)jFbwWeiD+PaQVQWvA=G0( z?%9c-C1rFBl3otCWo#U0I}w6+W5iCCAAUUIZui)$%p|m^-$cbs=>pocZtnyx*W+3i7(O z2xBswSwuqIQ9q2%JBYECG#VCLn5XWH$tCX=QxfkMqs5P*NEX_b>>07>7R&psl`8S= zuYx)Cg-ZSr4?%NjmsSnm&`b1O*iv#vK{H5`6ztSjUfqaOD0>uJQ^mHIjAOdj@@s~h zZ?X5kufkp~(B-94RvC%($26$nXkY7U#f~xilde)dKd{;i$ZNF}ggxQdzz5(t@ zUw2))u~C_)yWko8&jA81wd7lC&7&Suli~GsKgbu*ViIC6&q^Q&DW$TCGCxl6YX#Zz zEn>6faI;N#z1DL4zQtaV&l5$zdS7D{B=8N1E)zR1vWqhJ?r7)aRc`%Y9wh@of~!DC ztaso)-kZL)6Kc`z{dm|jTae~opmj>wrj~_lNJrM zej{rDSf?g1BwMl+3SKL~S)`WwG#1XH2k735Me_;R`)K~A-79LY>Nu5pK+ZM3gW|hs zx}&hL15_;W9VQ}L$s2U)sS?4nB}&;gQ3grs%@Liz0G}M3Wwhc`{&Ueq>3bK z8*u-ktL!NeiZ^l@w>h2SQ~Yu=4&@iOm7i^l?k6TorS1j1=&oU~{#oTM-so7`G2qk= z77j_F^}-G-`f}gNB@ce)2yuk{2kU^z8}ce6f3gZ; ztqq7PtRPK}XRh+?#buE@Vq(dq*>b`a^X({ok}c}3!FI0bfN>>N&zK-g{Vpp;8Oy&ST696!Ul=T^1fkVT6RuDx)w5kPbyeMlH9bQA zVu%M-#Vq@Y&Q)zfj;$%h2deP8I~4^V+UF~YDyzy!FP%$x7MX!DbrAafn-jZv4DSKC z=E+H%XR4x+_*w@We3dhkV=o40&JlXMKjzTa8V%u4jZ`eW0Eg-94J@wea$yY-j~Maq_Az)B_mp_26DqIcdaAjU7gA==(oA#D@dEPmIh_uk3m zKkHK{9r%HA6OhqCb?a&!W;d1zA$MxKthC&jKr z62)9_ITi@YR{O`)wiOyi>hdR4HPyF(p=0@@y_rfgQ`;J)BUhFa`M1G*-AJgOnmHuA zw4i#TL>;amuExBXYHSnQsd42h+;w&oo(JGQq`A|v$K1&3q0yjP5c?UV7>R_3%Tz z!J($ZS@JK7;7tF8_SASkjLz#q=m`C<&8Fy2cMj#>%}Rawl0@S0YQ%qlOaSEa9S^@G z3NW;$|8rA3F3jIDWcRQwROpjHTmjCnZSrQ_H6Wd#kU6}%< zB&KBsrILL8z~r)nX44{7pD@Zyp3T+29L0mZV>g20#75_>Dg}hCQ?9 z;{ml2t5lb*VwYGgXMuYLi^o%r{Z6IZk9B#63;bu0nP4H_iFmu@pa9%8Z!x^O)h%|h zmClU5e0;BX(ImJF&clN`LpNT6Mk+$QyV z)?7;KYfQnxOU+y;M^~X!K~x`4I(dZ!352L9k9A(t*Osaqt%9m4X-fWY?#nBJAk)8j z&%`|mMi=(T{N_NihPtKpc=5~+VHW{eczSOeg8L3;-9~7de}HdJs5WB;#)X4m*s-bj z_|NOXT#E1Fm(*O7ZwTlX0Kk?#?nG0_j4Y;)?K=egS@fifyA{*tWTx1UH^ z_|4++vCtayb{!2bG?^DtT16d913&GtC_R6fw)hH#d>%`bWB$d=KxJt0Rb@YopsJRh ze9_AknhJ-IbWUsEq0brFYD z(Uc@T@;6?cw<>!BOy_Cv!F^rZeS95SB9pQ!#!+`)Ya+^X zP3URwcb7o0r0LIqPW~f>4$7Sa7!@Qxzmjo@dJfYq7CcBIIdd9)DZ{hGWZ~&k#zV>r za{}E)LT@Yi3u(3xikdn94&tBnTa$@#nS3PxO_SeiD|Lv+)RR^({Dkd+`J#X)xy2r# z_}TK5Ugu?&rw}|knV|hbOoVa~`$fkFL{hCK;bL!P?CeQuW7is+mY$$_&L#OBpDCDmeOprF4g7nj<7{j=*5LC|67v;jB&BGR& z*Esj=KbrOdf!5OOpBPie<}Q!%(&G^&9D8TU;ury~h5G+v>aC-qT)+2!7)J>~Nf}Z) zm5?s!lnw(B2?^;E5K%xvVknbtK~O?Lq)Ql7Iz*5T=?(#@-+nyr&v*US@}G0oSsr+1 zp8MW=U)S~8AR7!vZZoW5RQGLJFz0s+L0W>vtGe715Z018)e_D}s*766&BcgJUL@RXT z)Hw-effRCU86maA5ha&-!7!pZ&et*<7a*5J^Z6uZGxJ0xLY9X#cA`ouDY%O%{+|3yg0`MQTG^HJP|mpdKxe+7NM zkzPdM5W8@Bx=Ho5kU550mJ>!Lboh$0JiP1?SMg=AzQi1rEb=-W`#)TW9!$(!mpDy`r6 z{^GLf09~Do8$j#kXb&R7_%dfeMksTDpvHtPIUJ~5x%&i!Kn@$34`KRtn zwH)*hvFN>Srnce!swf%-2XW; zA$hrnm~PrvaQBy^fGK;l;Qh$rRG_DMV-W;!=Lu}@{LL4xdg4y}c?C+Yfq5?0hWu;s z?U=kgadQtZ%R@>XiN@J78apf$hvOF#q+!T*`6)tqr<+tpQ;Fb)`~xXlmMKVA?mBA8 z;P@rlPR8Jg`?LHdX~ijIDb&Gn`0Fi!m@BdB)w`09EOcCd7Ns^hpYjwPF%D^pi{Rm1 zjVbC0_xJdv5g8^2mE6IXTczRhmx$t&{DCyk>apT}TLgE*>YTfoAKe zE(`Ks1uW;NEcv)GlOeg*4A%V@IDJzXb^R>6wlMS(o%;;Q4t%^#y@s*_iOHHP5o1ns z8(EL1$nyx3*kX!0$MX-{y`uB6D-24bcy_|g53uPW-w<+W*t_S~I&<+=76v7lO#+{| z>XQ%XcASEt*t&@}_I<=GujgmpztM(rN>dkM;(7dGK#P@38f7G%YxMyXTYFKJJNIB3 zyG~{8HHrV{8iVyn@xF+z2g=DxD)bYfIpi-@hpgGp1<7_c@)sx^(Db6Tg#) z7KHtl+v1K@$n4>Ws_Xc`dhUlI2)Dwi*P~$q^cAxk`^lCfXplUEYaQb9$ou`Eu`DI4 zC9ydG1#zFu%in*`J&dU<(A1wW?f|O;gFAxfKZZ}PmrWJ?T&1&ZhD&a?8cs%W1$h+pF>`B8uob~ zNab<1$>RUwP^r}PTqWZA8CZUM=DMN#O$s>_WIctDIF7?)4!hnS*5A)S=c~dA4EI0M z^>sbZr|-@; z=4in)8!^`oxk_M0Gx+!yRONpHJUS9QQLrRK2-$hBeICb}Qwx{TEU3n3Rd#kD5H+|P z=tw4_XF(Jv&h*BFKmpZ}3nYj8{MrhTv_3%W^B0s7Yu6V9+3Ou-Eue@ujw z6MCKzb&bN%#fiKOM%=a{szthg!6HLi*h72_?Ig%!TA&NaMaY=y{A=X_C|9bD(ISPfd5xfV~i)D zSXO0t{&bAqAUF0$ju0-&A++LNAE&V2w!KoiObd zV}!KuJ5WZPTQh9v(;>~&@iBIoKP7_ z)W0IuO|tVJ*mmE9GZMui{6_ zsbs1ctZSTPvrG;l@#9B$uI`wqo76T~Sik+#@7T3wL)8;>;^C<{qjuapE;)vsZW`A8 z3c9(?z>e<^mCa5@(20CWfxW-w>a=712qZJ+0W8oM9A{J-mlP+F8_G!#b51HQ_?X!P z1}IOYGy|JTAEKWA+&`-$2ECnx0cy2tyjuL?9AhxS`9BW^_O!XIWgh=D zunYD2&PVoL!^PfvLSXp#|P@ z)l$*EqVg!_hII%yTF61kJ{Q|W-dzU6`Tfl|&|Kc$0-s)}gAHaHDXC}brB4{9BU9!x zm7daJe^WiUF16F8e!Q{Y0;0|N89rO(aPPs$Th!FKkCBZQdLElql?v~{ zgkyr+6KZ96PLrz>Ud6U;3%Ju{P~UaN^-dS7CmIPJ8_KULRnoZWi5+ zI=+Z+K=qa7<(^ug|J;+7cW*_sK71@u*T?N46J6NWW${|kcnu_=Uy3f@WTufJ=}wL( zDvZu~+&)yf(-$mFn{hQTCYlA^PIRQ74R(1bzZDaDo2KI^F7WQ{T;ul%8uNf0lwJ^H z`5kcioyn#OAkO^mlB4PjYTb3L?xo{a$6O$y?Z05YR6lWWc_Xm-)k7+}YnSXbh>31< zU7;TPd3yLn)M-#~Ws*3i55}c6+Rv4b`k3}kbiaD1=67D(4bPI|&yk9W;Y(=%!+$$q z53cV$_v@#I-}!dqTLl;qFrcWEvhI|y*P$zfn%FouV)2;Fv7O49;pX6mrMyVIy{1At z4YP-;AxN(}nP0J7^u4|RUc(lPh$(tM(ygBOdUr}Ky;qFh02|=9S!GDG2hBp!as`RK zXIA;$49Rk?u zCOn_ESI6sXUJzhc-Dx!EzVs_k>dr`vHCsMFk@v-3OyL@qcD_THumndFgdxu$7TQ;t z@zI>p#_QrR=Zug;zh%-HbIKyC+K1Vo>a3K7gmf_vHWz#l?EF5-9fC_TfFzh^KtRDLOP0d;%@h7(7cCKhkNFjcKvZ<7P>=#(oAK>F~4IIoOo$;(MENN1-)P zd2%P}b2?QdAZ0m<9VAq}^hYVWqrhx7$M6W_1MX)rREYg)neou*FSb;c;}JNCc{LlUg*L@t~pKr5UvSw92C}L1OxgRA%qYDnEWJzag1SL520= zb4^%xu9+mn3H~Lw^Z*6tSF5is!him=3r9x_1JF;zegqMpC_6F72vvJ67??80JqZr^ zYp@oGdSWJxgT=f5$swyXTRLr7$K(4ugkhKSX_fDr-Tz@+{+9W+W?U22{-AmMje2vlQd(nuO#_*&o0QCXw5PtkV^}^ceXvWj zl<|n-%;nG>L9ae)R(6I{f}@^irI~ca<7F}Xm{!QTe;PB#>de5wc?Alh_eYFt$B3~x z;0}lxqQ3iVJqedFxoP$}6c<)~pMmE#iKbJB`vkNor`F%~KnD9xMi*;=MN;{;C-t=} zopQu^o8|%#;;wtVym5KiwKPOaZ( z@F-H@T`G&byFU(UkYY8@%iq0^q*KEQ%so3DTR%rwUx^2^vYGv5L-Zjt(nYyG|Gd~* zlf>qW9@MA@2icRnn`qG_k$cZ}ia1Q^CX2TpzfyjR_cP_seSLD5P3U_WlRxX$Z$e5c zw?+i}0n}>g-_wSYB~asur7)~r!AUAcqevXH9;aA6$qB20fy}=OBB)>ls5mPoQbir#QA=_&zY9Iu;zQ5Dk;pUG)Ju)_*0x-6&kg#7@zcf zRVt_=Equ}=D}5>1S>=y&cnc1;q#C;Uc|+oM*(E>4=ugI8Pio;qg-99ZC#CCC{r#2> z0qR-ceV1Bxw>--0jy$K-tIfjB7)%pe%SM$EEUkK3auO4SSwSSAmg&87T0b#%sYvDG zb;^z8aT*tcObn>q|c#q|X!gQh^4+-1LNyG~xhyt?Di#wn*r;V1L5+Ylpu@Y$;Z`;5Rv>e8y%J`${d961O>M8zO zE}7IFSYMWWb`=}tj-8`ryU-}C#UhobPM2&IlFAl=yA;}fPHLVRkyEU4D9-2-N;=}! zxpu21tQln2-)2iI21#U*%o_w+U=5u*;*Fx+H>dK;V)WF{35eq)wix~9GsUuk=ECuq z#m8K&Q!4Mt-WbUxZPmEK)vzRcOUh_Adr=Geqn}Gi13`_N$(m$|3lCHZ1PCGPGv8|& zu;e?8sj-EK-ZpV&=7M-p+HNe~>Bd2$MtTg5aX#L8@{kJ&2Z;v_EpIgyrQ{E~dX_hUQH1WuJu zWgfima@Fo0t&Bc1v?uvaQWZONVUn4DRft|5-n;d4Yc1=kZ|^OF)0-7x;AT&{O^{5R zGpwHx>xC`|H+)eHPAjph@DYD8#-{$`5^m0gtF23c9UKwW$YnVSM6|Awnh<-z zl+47J3E$;z#e;a?Yk_scWxG5&Z!NU>E}774G^quacttvg*4mrq&g5V1bWBZKho!mS zgs*5seEZEwnK>Tg9yT|)FWYzDgUPx3Yp74nYDjWluAp(_ypQLXGsT`+H%5DcM;;F> z8GWn=)>z?K=how+oCFqJIh!(7oTqL9{u%Mg72g%2*YzNPw&S9?mhd5isOxGyS^HuO&@tU8I%VEPVA^Nehe~O{panW6AQj2cN_8O zaKzE6qNlV17vF^}X(%(ttFr?rzoKU?O%M@le5_9}91Ah5$iZF>V!xwkEGZY5q*=AD0g~h6aZ*IYY0~f(V@E@n|G=Vv2U$T-!Im+$g zlSwRB^7nDf*i5ydZo>-cB{3<%iQ!LEHtx4n zc++>>pqAHHApeYxGcif_TcYH1%8C<@y!+Z#IJvg7!tj~wW!fq!n)Z0i)pUJV)jWE> zyTeMbfnl9(9Q-V{caohl$m6M~Sq!o*SQ_5Gzx;YBPAKi#-y~Bst72e^anl(N);RS* z-qsmS%^EiOdHYae>fWS4dfu_(8C}gMx`E05O>T(U(%;52gJtX+l*_Xgw%W8*D5^v@ zBN=YRuQJr@#kGG-Ox@GgT^41P72ev&{|afAh5B&RIzL{Sxg0H6@j`%UxGmjlGGyRc z-s^<6*V`7d@#Oe2PmHv5Zg7+RfIyQ{BbSC7kw0{51JaQS{5&ZQNNezd$B;O{AQtxo zFY^#AD#SA;F!H%Jyz)f55AjEZZF4P=kbON(r5CA~al_1AB}i?@P7KXzgEf?YB13Aj zxE|XbCs;pGT3I4>1AWLku}!n=kYDA=vLqD)43{k=+-) z9x_cH8>!a8*CcG)E(ovmmV<9JJqi`_&G6IJuZqePEJw(IrSPrp?La}|PA4+LiGRMlVScaT{O$V3wXSG)We&PLf1N$ade^f@ClPSLQViDo zZMv!F6&Ie1^tMw8lty5$y<_6ylze2nea?akz#xS&c4O2^9C0I=QAvdT2E zDzkQfF`qgY)Id9pX*cp~sqQEeG#9TKgn&Y=G#~$6D(|yGt3xf#O5LzB)v<&!l7$?f zoI6P#ByYtX4ufrn?f1iIqvj6hto_z(k2RQ!Y{?}w-kD+dRHFYu|A!=QW`>Ft58^w; zB}*dnl(?=VQ@xP&X{B?L?CExNql0C*dBwrKEG9hiBwy9D?}!?z!e`i5ddbC@1g}Cx zb+2pAU93-V(f;AnEuvV7mM@5@YN?wwlv2&es`Ydyt*C=h-LCXp{KqGTkq7Hf^l8g< z?=lq(5@?jWj5+|QlD5CT9m4hklT=*0C(TR@3LLY(a@5uZh|Img_3YbU$zPjPn~o8ECrin3lzazk&mNaOB}yt@kBA zC+p^P$blhfH|q5_A+f6&#Y{uI z$zbf^Yd>|WmZ>^S@ixGN<9`}5J?(}B&%FrnauG`BCxX59_Lwb$xp$fxm`*?}-Tgqo z=%r^~%bukVG_bB3DxIskz9flK_Z`x9pYu+1h7qLsncP3EGANT1W@Ln3RlXj#IALSt zR$kNH^+5Zs^!1Uht_Y!+o>2gr9<*#BHmb9!M2n{_?c(+1u^wV%9t)M{dD#}I}&3&#N>ii_I5zn`&RhP5zGU%9gQYw?SdP~vy?Z!vyJSo!qnD# zD^=*pwh2eai>yw43Ik&9cA@|Wv(x%u)z9=3V2C(j z^>rfx&KI|T41V!G{iZk>v2h-LeMD6CP0ji!7BBq(nR2;RXa%mZx*XP$ZC-#Y^|BH^ zZxa{CDkXFMYky1P{sLcfZ-3}1DH3AvF#4gN=-H3(tm$;G;`Sh_o9x;X5}=zR(Npq4 zzUP|ERK$t;>ly!6&yI&LIwTw}eaQSW-9kHU#MtVlOSPyoaSMmBQ)++?B&Ahd%{q7$ z0#@Z)Xb$B>R69~m_Q&+=vc6E~fB=;FgqZ3=L*tof_J^oC%rs^cax5P$m22X&nfXai! zPsfCtF$zQZr2yKj3uZF0f)^#16odkY1lil=3YwNC5NY=$+=TeqdX<5u;h!7Lp4KU& zQt*!ov@OZrSE(vlvQX9D<)r}%4k1vAMeT)9>JY{@^@`-72|pXYaq$sogoV5Irq3#+ z7t6EbqcrikNIacoT-&%t)hYy7TWO2)ni)|wV*4~ylXTNI{E&$jZ4yeVr11vx9L$G5 zh4ynNT`#exA|(=5p91VvJ65UofZvXRYk%;gc{_Qrtffx5c5>ob&l;%bTYB<+SBwrsykRaA==>yEp7RnM z97E*m?U+_a)p>O=X<50SQhj`G?3CS{V0BwosS5KR+P9XbUG{9i`aXJ&_&*^oo$FL-<62e;ltuxrn00LX(Tcpy63 zW&;cHOJEAL>=A$K*E=9|>qAY(VoIU8*8?f&dYlTB%B^i4UiCh|5Sj0UXCr;%MVHSg zhU-O`0K=xW&+6Z}ZQpEe8C72^v-%~&-|8;>N7)e;>Md=FixpBXrYOru3FfYWg@f7^ z&29ApwEYj&8@Uyb6L-RmLH+o^dDWw30->I+?0JX=P(f4~lQ$=L`NESCny%ug*_puw z)fR!Nq*h&&lSsTcCwY(&5gCVyJInnH^_Mq$TJ<$50UMxlab}}5!~___9bPNHM2mlZ zXf+J8-Gg;d$q>;U)?DyiV;`erzAMJ9(o%D$&^8cHWi!Rf%bl&sX920f!VTJOs5~IT zR=z+&_SrFh#qYKdj;N6i($GhWoxjhrnqpki>uuUD9H=n7B;It{>hYcAkmtpLF9kDp zT74^pOaf-2&Iv9$oUwyBz60E=ROn0(VM(kL&QOkne?K|&L*6K*xm)iQK@#d`<`@oF zCbm$kiH0CVZ9AnF*llm}>GYd8Gk&~{eZ_zWymNDrr+6eHjut!{S(ZI*ox$>04|nmO zHnlra!*vYn=5E1?>gmepEX9e;r%WH|we@es4osd8LM$|z^~dcOR&D`Eo~mMO1vVsq zPV;CArMYbKqBQx7nc0IH&uyDLJBc4Lp7*U&qCOGW=n&83fArKV3fuKtfAHm@j+GX~ z^PC2y#GZ~f*^PqnA?;^m*V=xz;GF@S#G@-9KaAd73!&LnWUi0VyBbyc*Q4!B)V6TMO%=3fG$Rye__~tX(AZ)a| zw;~8o)YZIUiI!qwM$v9`ziOe`Y=`U^>+TI69?QDO%AV2unu4D?e-0rE?!3iOdhuH| zIq(XcI8R0o662i3R)-qSKKyaJcayLs?};!ytAAeKss;rKs%A{Em%WEmo|idoCo32W<=8U{ZpoTT`r+p8lXZc2)dlo7D*q5*Iu z9mx8E>pO3JVu=2gYCq%HrBOinEKQaENZ(Rlij<|<^MV!tLA?hGugiQa!*9ya=%Atp6tjAXmRCTIx4*%MmUp(mzjrn^;W6l&t$T%133awRh}B` zGV|Pq;;t0tG0gcdp7CQ!w!d-M1g$C7%o;UvMn`4kDUf9a49d7kHLoYCNim(NsKQ52 z5>pqR(m5~nOFiqF1<*UoZ`*p^NErPt_!VZ`HscJr{zZMGY&4QfL-EA8p?mbx%eIE1 zqRW}KmCarJ(&EhDS-O53caJ1eWats<3n9T+yfZrRWP~M+=MLrYWtgC*Ep-()F@Mtf zT;u#-i2vM*qn%!E0_?|9 zSdE;9H+t_0*ZAo#W~?YnR%AjQ(#uA}iPIl=X(r~~{oAtrkk|TOBtG#x*@SRqBZA-{ z4RZs9rO^El>`5383fSWAf~50h2LO^FTIRzRkUY&pr)DaoIKKb5ML3e+G32VknjAtB zb(2KCm0P|+39I}>u*u?hdk-NASdKabQq(CewSqT_fd{-;Q2}vxgC3<;Xhpl^9Ik6b zat0Q9kGQwqsxF*o8+~v=_YiKBiB1b4dJY@w6ngHOO8-iY5tfG4%i=WOojsoBscxj< ztvuv4dOLBXoW#1qHWbvkIQq@G+xFNy0g zD+p00c){KzlS>gcitDY&`r-+>#M6wUB+(;`0?@PXIN3Y}|GVH|fT0Y&pafs}Bw6bwGs~)(7%BRQ}z>7IoTpN_Xm_7ZGP0(XT zeTZ#{MMQkm>v<)#a~~Nv_7{}-oAc8&JSY}q4ReI*06$~;&1mCfL7`*Dp7s3Th9O#! zbK5KCB3k{1B%oLi6Er=W1MyNN51pzhsB?@m?F_X5=Ab8oU$DBnHeSDX>b`OYQC#mg z*(-7!iIfeA9 zH-D8oJBF*rg+sua?3eNqa|Cd30!haap30w4Z|j;0tU~(AyW%=2{h6srUi?n&6MF);gdLKKQWEg^9Qv>Yl1~+~PC2c7{g^qhWuu2 zfxAFA(0exv&QRjmCYD|=h*Zx)QpQV`I7$7*mvhpWl-WIs9WrsgkW}vgFOp~RZ7orY z-B*V|d_spd0GWWqxr3c`f-?wMAehZI2$%pLMHnmNpTE@`&pJVt@pBWT>=d~j;R!VN zbq&YgAV={}B!%6OO(}*4`T5q7Pn3kYQKD5 zZ6d&O`Yzyff_@4w6K|$jJbztf?4kiufBW9X^e7$w=CqSF7Ad%FA#fLe$G4kC0+2Am z0C>xI$8%(*7+Av^8rd>qbx@<_(FVQoeu{wY$EXgW4j&TV~~h$)L_ z6M&-|c6Ijs3(nLhd1lI@SvtlrPByz1H1+T($+0Mc`||GVmt7b;F7O>ItafFQ3$x0f z)(CaA4bkDMRL4872+)78=_yu2(w*}`$OZYX(NJa%ZG)dY!4xwI*OfQO)JBG$>Qe@^z*4qIxtH)NO2 zA!&5}Ks#{Mbc_K`Ee?DG&VuWON!O@Fe42?;Ad~j@{k2KDzTWrXD(7LySAr)R$%+6e zhTV3+{z-K|A#E$M0l*+ecf0InBl;?bR^P*JvjW#=grCF5w_pHFxh?SK(<^oi8mab_ z?vGq5Aj7`Yh!P~vNLqlCm6r468^^DNanBHd!c#5BlOG(*x;>7^o?JUtN%Tzf{3Qs!iUU`?2IywmTdvjQTp9;DMd;buxY7%{=)1U@903sL9# zSgY?Z^q^y8|J*DE+F925-cq=1cqlVs z?*p=>1Li}|HYKIHAEA_gm)rL=IsGd8cGM_pWr7Ax+zJ_3K40OajyHqcwU*;*1yhP0 z_B?<%+SBFaiWpGUF1!W0sZydPe>FbLQ)Zmsh6b7GDAXZTl0!be0ccVZc~a;{9uDGP z9jUh8u^%<5T1+)Z)xH}pRG=DXR;Z^s84a(GM;9cl!3|^sLSyWoe2@I*;e`eh$ z_frKhfFTNMSYe2RxPSWTkz3WgAXDJ`oVax^nOy??_Abbj`|pVt3U60mCwJvBH*m!E zRU0{%8L-c8SsGz-M^iR&kIob?T*8+*16C>;Qu6f_yi9jweem2?$o(fm*0=<)J8{2X zyyi+yD1!ehWoKphhA6FnjpiWJcDZ?(eSB=GLxB-dcWv<6jFSo8E0{I-@BFxtuR}XL z{a#4N9$~-Hn-VJA`FD~*zWqs2?sgP2zV~}XA0{l)x<*WgANds4?W7SKYk63je0Fh0 zs7UNxiOLY?9L>?2YX5qKc7Nb>#+El=!%RX)pU>7}NB=Z|!|(VsYqa0;vvL zNWOLR)HW8*uK~1R6VZ(TFs6~HGVc5jp70@ zrm4>17T6uJl*hT=sK(l5fBO&D_{j0)i1dmrK(iGJIS&}B^C;7DCn4A{a=m3;(P9l| zd*5sMLZX-`@QeR}=>F7pmbW~=@i)}j%;Ss3qwJE`pur&+2T#bi(`CKwquy-cL38mW z$rHJh(Th5~Fn;p5$8dhgiO3&bJykc=x-Knp2Z*2sko-9_8{HMymT zQ!;_28srw#Z<2tjg*P57@!lwex4P|G2v=xUJru6XXL^SuS>%l_)V2yoQ2nDWd1Kin zgD7f*U? z|2N~R_g<003ANk~uA11!>juA6BJoaVBjkyE(R)ITT=IMtTGUMh6jqd&JiA&{N>#u1_|?U2|>9guXFu0V^+7)7_g z^jaMmLI7A{aW{XJLzCTV1T8=ffC5?yltb&MG-s^`h3nXimPoCIL?n3ZzEwv4T@b~0 zkwTb>Gx+OCWjFyWUhPW04eS2JAm9@wB24C|@Mhr=A~;C88Hr#}eCY6^pJbz)+o%-C zQkEvmIhDip;w~HK@oY_d87CR{+{l9M1;|JsD|U1J7PcL3Cb@AED3D62@AK?U5Pm!H z{Y3Q8JtsrRd>tb*1*Fw76@qy?^)B_R+rnXSXC}?U#<;r| z1k9ac@6Y}@fC5!E+8c9}%QU_po*)TZBB##E^L6nA0Yrk1Rha#RO+Kp8%jI#iHux({ zQB)u40EV{mS{CSUu3Tpu=bc7kF+xq76KN&@QdC~d?JFWNC7Fvcpng`*sfoY`=~BL( z-}sZ*gmt&AfyJ8I@F}j$I9(ts_wAD1O+Yg8eq9fM)-SM(!7d;KF~m=5<~?Ln?yEc{ ziCA|5my=xyhM$gGcBwYNrIZ-PMbMCJrT_n<_nC$5fDO5~^Q|SockA1m7vX^yVH7*+ z_J#DZ)*DG>3wo6Qnb&Y2PXt9)OX%}DabAPF{)x&2oHt@nD+`BA;e`C+yBt#d$}WAe z%vBGvsJ&X-9k#~m^brywIWmVq1Wfkeu}jARTsJzmN7zp`S;Ui=%J8`+dMen`94gfmZNnu$ zjyYdYaJV)hu~vai;?J^R(PO03hJru+r!$7^5L3fZCx(ci`Wx|j=?aS z0JYRmcg=0@p%yom)BvMj&M;O;6N+g}T~3(7ZU9wp=5lJv8RF~lPjx!2vOtvQ1c9xP zh2p+;zjl38;4qrMV&X~4WB!U-rf8b0C?~3Ph?Ptlj@U$3F67@T&27tekN|)2XQGKq zSsr`P&NvvdXV3Grx~ts0x;rsa-=;D1=N-4qv~i)yk2uBTNQ)byu$JSclJx^EA`j`W zTVG#NL2S+zAptBur9Cr`U~Ilv5Qe%R$M3HARz^wXN?4~D9K}dL=q%WNX@>A(2}D+{ zb)wyyGGIBx(%YPL)(IYbVlTa51y)ogtNb3@f`a>a2oyT`nNJF#)n{fvhYzr4eFTYM zz>y)es|3N7hDqrdJqv&nax2UDi@QqB8ya*;t6PkLT&4t~uOLanNa;)EU9Xu(q+c7G zoP{oVy!&R_Isw+>n9;qNd=~a?e5Q$r(jnYmtRLo(!1?O8mN@zVG~xGci#Y}+=1aAL z*8ZU9mzyw$&X9RndNAhdyxI49cyR0YPC8+ZM&K?py#>^%qWFKGTVZjx<^^;xZ;_wY z3|%44vGW18mHoNca}{w$bg&MRy|Lc}#L@C2!E)3t0R_1m;rdZO)B|%}IdaBS5nL%e9dL65bZYlP@vn!w%s~Ys-B#u1$t{fFbw1``oVuyFJ40TNxHlgVzfc>C}Q6 zz_xC2_MWQc<;0>)xD0I}x0je!v<2@NN*-^n6X6wqa^HNfLH@>2mn_t(<@Eg{_?`y8 zE%5p-gwK!-W_-J=$%TS12NQlF2{4aM_S8OWose3U#s1PdQYw9} z?&y!Hu=6jGhnP%BdNHRG>K{?kU2St4ol7dq9G1~5WXvQh@VEOM_v->}3;*ickVv#A z_4TTtc~uEJ0R`<#s3HpWZL#O3W1k#v4ENYWEhG6(IVSt*pZfkh`@Y=h`>LTGQEQ7T zy%zV#GX{}RMwIN+Xq!*9d<=#}-m~Ta3~5S_UdW#Lz6w4?vF#@#36MNZ1PJUb_RJCe ze)lWn__3c;i$+0<8iW_Gf!OMw@YXHKr;=4gsKaB)Qp2_ zKPS!ruhW^U9dam6b>gbzmUB*ZTd|ry5sr~Vi}ISy?0)2W4A5Q&)_?J8+S5jBa}2$b zCt_k9yHkt5^@T;Q8m8IhCU=WW#oUx|tNF@^8?#q~Ye+HTaXXxYFunj7%V!?V7Dk$u zx%`)kcBc@xz%~)t_iN!2@rju4Iqn9+!C%$IzCIy1?I&nkO=_yUs^=wR$dLQGgdI<_Bq zRk#xwNXSl>@rPW87VElbZ($J&B)PDchHsJvK4S*^1De9~0OS~TuT-_#JkoB`;xAl~ zz!NQIonXRemHEmj@LjsoaR(G>PuE_;=)2Jy03-V~T1HsNXK*c1!aa-BI1K>IlNOhWmYSupIyB; z$Ms&Qb9D$#$fqX&`J z)@9c=kyZfqSX)L_=nv^rTtZRBX8#EJ_x1Q;d}Am@SZ=nLfi>=25J)7wA`lQrHB@|b z$yuuH1rOz-sQQEd{@y*Zj5y}X>2EdDh$SjhFrQrx-x0e7j;>Fp@fUT-j+3N)iomdQ zlgudooDj1cKtG2g-wWL=c!jxw0^*iPD}$%j*RoEt+q}H#Ak`$E&k%;W6TA<~uy*BN z9v1@1#Hl-gRT2YR8{c-Ngj|9h7JvbrT-4rG-A^(gSOCj`ZrWL$&0;|XajZkMrX_c03rz)Etue3vCX0U--pQj8cnEy#DqWVPXgXY z6lg$$vl|j}aa|Y!Wuy>CHa**M2HiYB5ZJJ{nvw8!jcgURh={o9Z$ zf`UL6Z4VtLNtmNkG#58aK3%83{?GsS0E+#lg2x~HcA+H#U4yZODGxzjAb*(OqTi|* zf#mMLh$K#OVejBRQ`Eu5VSwMX;xB-AGg>-1r7!@lf?TK>!m|9}H4kn8zg7D;uJq+r zqa-EtM@}T^=m|ZJt*nk7L573OX=4tUyC8qauA`JodY)~=`P5~KTIh!75HL3Wwz;vy0$M9g=9a&QvIXe0VS*|VgnKH1qiyhjMvE6jVhQi6SOAvE7K~5gPE2b0NZ#F4 zPc+i8FX_LZe@ZC>x}#Yz1+|;5gQcwls6;PzKR?#6!ny(s=eqGR#4(${+M!BX*;xNv z4UXOSf+4Z-pkDi(Qr#`DAU~mh(1xlS}>mu|2y+gb~aGZ1^g<$c0 zG4oA`9*VqOoK-GXl%2M}g5=@m3n0F1RGJ3<{`%(*<%5(6Yu!m%YgvI%-X5W>iae`c z0xxG24-AF(VV6C9k|T@C|9xv{@mU2-ft;Cs3>8#|j`T{-S_S&1M)U^M0Rw)mFQwDu z+otcpa#Q-YL%49ad^t%4Q2Rs-U<|f}Q*!@e{~cB`3zYKtmvAuxC|9h(B)9k%2yl0G zWHy?&b^B@m?_-chZOwIwBTlR|TW|zkL1e_SarYCBe4%p*J=tn%RaVa3{8y>HD#@RR z2d1NEBDS8F&w4y>Ksb};4}MdNKW*Q?Syom9!*GwHr&Rx)F;MCfBFgInF%Cxkx4N9r z@v%m`%akD@sQtf7#Vu{ts4}6Yk;>90F`3!-lb<=5bw|pzw$&vF+mpow#$6stl<}Li zAznKqDm)*&{r-Q~PDr>skiyy_X6TBb;Xsa4D}~T^sQN=!fAcL%aV|GPJvUpD|J-nD$#MLHNiZO;`3N-+X2 zP<((BnDh0k>UD`iSyT8B!PVD{Acmb_CGlvGXcA$LdIr#BY{AaN!&LZ1nfWG~TTFBK z1veGF4QciA$EeA$1 zBh4L2mk&j00haPJjQ&4t{)c3mjKIv;}QSxqtW&qU>^NK2oS`-gCBGwG} z?`(g9ejBCI)(Q0N`r{peQph3+0|4dAmiwz0(eBKo5DM)cHhv}>1S8EhkS;pfQzV5C z8bzKE-c=9$$#TTBaKKF-R6;TFZ~_4_LcNgFasBbnPEpND(Tk$PEi?Fd3M_@pOO7qxZjvM zFfo>WS8%s_K?`=SE`_xVG7Nkq8LtpCG%ykb%VM5~%kvXq86{H+-Gw8%A`A6P_O;_rEZ{+0fT1?BU?(kk#U}$ZwiOAHN#DW>wS6@ zS~Pk)Z`#5~$RZ9nO=m%kY@hc}Wko?f=e>40Tk$FSxyUOkhV=0c%&p-{-Wn)AEyHLG%xJhd`D=HXyYvL%3FV z1R3ladW4J*AzY*XJ68#VkZEXf!Z7$D>B$N`@>H+mBpfJB0u_#df;#I$jh~*# z5CJcIN7ycbnQ=aiFbb*l#QLnfJF;ucYTCAikHAZ<__-Y+Q;z^vJhRV~7?5emhafM7 zek-l&`xh35JM67SRo`{So{EcX$0Q%VnBfw%>tuk{7S}?z=32ArQS$$Hznl-`G*Cc~ zu(^TtCULeSxmr9!m+eQPsrU=OgAld8|Gu%x;SHic&8(Gv{B9S;QCp|{#6fxEbXCOM z;@b%!WcLVi;ia>Mr=!S<3SwD@vgFq7dG~-{_wr7w3h=O^T*&r7TWyCTZxyFk7o%_^ zxMa5afLQZ+#J`_BBn?k>4}zz*_S$ql?j0Bc_Wf?ib%!BX+5N77V@lIjtF4$#a!bL(TABikhEOC z{CN3!x|O<{;V~KF*d$WY$7~KiH~|`ui!CI3t{}Ae4ulvD)89D)&W`_%XyB}oAu02f zGr}zzAb>iizVQG20e`gYo*}YcgkDxlEgiOzBq8M5^urGKoINpcs7PVMudMdv#R7Q% zn+Ur~R_3YST<9sHpYEjQFj@yTr6{t1V@q!)fZ~--wfOH&wPS;)enC_n>LR%PM=+Nw zla9+YPk2ESbiyUitD1Pl26g8hR7Z5fH8egzrSXJ--;&AyUUs?qZ~-c zeTC6s>MaKQm8IJmD0O{i;Ud&@)b_<~yfEBeLFi^silrg+%R(Vtkw3`P3ee4@_v4y) z(`cvh@co{iV_bGoPhg?;aYxSUKFHx8Ou22kM@g%7MadTt#sOm#JIyl}tj&tjXO(9W z_Z|hZ&gh~FlC-|@y z>!I-dMWa$EqUG_1Y95q+lGJ#~*?t72&v_NodtK4)bBkodU7e?^FMwu7dSCJC%agA` z%p~b&kKh!OZiAr)p$xva&^P@Syav9jP!2aNT{^A8Oo>8}oRto~N%t5*jI7ZL+gN5$ zTtgf@GS>d^z^_+@+m(ry{C75Rq6sgzE`-NNF^a==wgq>z-IW)va}%PiL?f`WrutqW zf$ZCsmPf)k#=le&+219;?J1m@o=@J}f&xrf$9xZDeCvvC3UWevd;?WMykpo!p9o@H znD(OuK9CxHLB-O7+N=YZYEEAo;S2EhMc2-fd}M_rz4c5TqFw*1y9v?Zww${30-jLj zIDOh;o_|?%E~L-;v;I#S;AAj`je;b#n1NY|_cXL8d2osD$ zk;S({3`D3Gp4KC`$R7NhVe3|lAue4uvq(6&m70P=`x>neW`BW^gA>oOSKGR@W3MhW zCwLdrItl3WDQ8-xE;<49O(bLvS+f+@biwJ$X;C}e`Rhq+OlaR!7z%8Z`}kxjtcLC; z(y>x)%^bBHI6?DWPQdGY6FK?DV`st)#ckkc95%ySjD`JkPRXr1|GVj-Q^8|P=+r}& z-51hHo!My$J&WYH+L6TVLEP-%u>+j< zN2!Mw|H^g3l4}&S?_DmFD_-irVOvf}%+`AE5+YMkC_ z#N&&+%@Am&eFMsqZK-PlF-ZCFDE*mdtF{VAn9DSB@fJpgbJF3-c*Qb!v;sG|{yQ0! z)9`j*>DY0Tj5?v&XuG7yyGA^pWk-|8kVk)c+2P<+;}x=|Dn{Jx43tW5r&XW*V^~UL zK64;RpIIXDm*QC$u5z40-MWSu&1?eUeBzB*!-IspHDDtCn_!%g%hC8_t+h!2-uQIf+|OmU%iYr=bGNt3^a zdJ5vS?KG|GzON;vID1jRO+Rf)`;qn+?L0cYfojvDd^(ITms`AtM|%5}*Y^{LMITX^ zV9y0smv$}~ca}3u*SMbO@~?lkIq3;<9*1VjJo!dz8#b_(7O65@lf}MHQ^hYs?TDvj ztDA|_nsPfA*rbl$=5(04fXV(GZJ2LRR3ehxZmt7E>Zm9`Ik#a(R$VIG=YxNBuu6cI znmYMHBrRLY5o3r@AG_V*hw?SmI;z*tfZ%sK$p9eOtSMXL`$t>4Z>o8X?PLBP+*HVM zW3xj`xw_^3I@G?PHUnyWta$O03pk%EWOJ)KJ}(~cnQKuF_dM}3FEamfyIV#2Xl4}a zm+u}jOe;WEkUJ9bsIS>06UpG8-$#40LlVKl;mAXDD!G`M7lf zQeJHQt~-g>8kCDF{4N|7-zn02tGJ6J3b1SSIhzPZE`>B=mk2PVg3o`{@|d^sEW_`h zncnO)eZTU>VN~h=zsg(4@zjiW>{+!NM*3NO&!7y_y`LOkgy|xI>8tN`W`~EbvOVf5 ztvrZTV zf;8y)4Id$k(U88cQDT`+UZP))L%A|L{y(Be;qZjfCasA3K?e#psWY~Hh&A)X0h_y5?u>e> z95mP38)icFw1pfahT-X{Z*N9<8xgfR|CtE}`cr{#+o=K|cVo~!09urI&6 zuabla`NjzCcO_@jvtJu{%Av5yGYiDHkeG`PWB!F{nMybSPd5Uw2}gycQYI}aKfGeP zIG+ozIs$k01Zy^J*UwH}OKgs?9l^HM=9hE9f^9Z2EviR%kGJu9b1iNj_v#U?O|%z@3G@lP%IoWo2@IB0 z!PUU0nCkwO?!=^llha0nyelzNx1Yb-4^!SZU9us=JAbd&5t}i9R-7_>0Sn=!KY9TwM6l$ZR7BsZq zYv<_0VK6+DJTwoENRPvI8rk({^R`@Rk zU;-6tY?IP*9Ofo=QAH$Fuqa$BWqh|!@2d!LNldXK&D>wMq)WGRINxGUi_T+sJVM}+ zwYNK2+Bu^P40GkTbMD0Zd*1G3(2yb!LCvH@0v>Jnp_v;4L-y)^?8io?vmMcnY^(Y&)x>$4-OZYJHN`g({7Eg(2vgO2mM+)OYc=$EEOboo+M~ zSxFo~0xd&xy+{mwXyV8?W%Z17&T6hX>?&h^qg_5V2X4C>^9;JbRy1R;UZlsK z-k|c?Jp!uAcd*V-oQRuB*YiT=uPq9)JEF^Wp506)l;Bb-(K634w^x-|8b{v*80+}4 z(I;nWZ;&d|QIFr=anS24yj=LK6R3Q#=G`YGdUQN7>%^IQQ|@b7Y(&~h@8BCM4-M4> zO<*VOUbWD8VG~G%r0N>HDKaFP?n`1_q~R4?MxrAi&lIIfOinko;74R4CvD5Uz_)BZ z_-!?~@DKV(f;ksuC{c0IrarPMhllapdu;i8p74b80>3r!qU2_>Vb;DTIs?Y9Mn#gn zf%#(2ZQ*^t85WYvs8g@>cO_G!ajto9@3+&&3E?F+hRr@mNO5wHnx$P(Z1JKlqfe-GKsN%$axCePmD9mSLfGnYiRV5)eW}=Jt6dZdPjV7(&<)vld=1?}VFGX%s zOZgJv!Vo&CUm#YRA9&xmT^I8v#-^1zFfIkA(r;12IZUFXE|PjX4mVMcR* zRuApPw=2FNzI?x_VK^(HN}=XvWQ1faB$eAqYQIBqAe}QUUefHee>??_QWJQsq%j}I zgZ-}t0F60%Wt^`D)XF$l`~VMDg%ELzlasG~Ai6shy`A-OJL|D|<~`3@<$xi*`=i|s zhPeW5j`6`)_XH!$~u54tjylH z68-yD#_aV2kM-#{9`dbystB)fXQ0$(V|_IHgosdEy#LOg(wA{pO}c8erI<@?c5k#{Rpe_<2_oF@*n$>-th8{w`3&d!%LUs{N(k)@iChkeo z8CGXp00s7(DOdOLwpqC{uO_o>h;(mBitJMsur?Tf*fKeul63qNWv@GG+vocCxZd65 zaGNTqOngkJXQX9CL4vH0b^f>j=b7`=9S{O9u9u&>laKtS(eq`%nTtsM8H6LOAu+o) zJi)}km;+iZ|c=$Ggh}a52=1Aenw@u;(>4Q4*at2O*okOCv7> zZ<1&mzy2|X^*dpZ8;OLWjOuu%Npo%B4t;2S;$r1cWQLK5h;g`BLCUo^5DY1 z@-}+zJ*iM@e@yMAD~fBd8;2fM6u!oW<~BDhBnef59>Wkzul5Oc+bl3NY(+5cqHnDQ zk&Q8Nr05Lt#fCGR?ch|g-YZ^Xy7&3NS8~SIB$-$@ ziWGI53Ybp>C`Z3Pi8h+I@QN1<2SQys$H?o2;yS>oHpu&E`VQ{z7X5Hc$Wo8bK@ZaT zH2NS*jMO*>XR#;U*2TVzyS)=Qh*JFi!VMEz8j$@ij}t5I*@s0$!d-rqX|*Oy-8V0n zXO@V^6>3*Jw2oJ>jQkf?R%3D4>xyqGw#b<+HJ!8<-yXAIOBjGA*NzvBH~*ckEF! zS0D%n|0Y}xDfTi(3-}z8M_nsMIzmtDy)+T9qZ|}UhT_7S1n~_SBz_6uDN1TAfa%`j z=Z^cbeAI7%mrDzVJLEwN)6Sdf8wrJda(JZLP-uV(tADi=r*wKHcl&zW?xj|&$f@|^hm*=eXA9Zza%Sh>WK=ULv|-_wAQ z!=rtp_|&MlL9lE!J%we-L6u^MJ~2?kjd}jN5UZ6m0msIG2E^=&YIv0SRn@1~@UMlI zFCe-QT_})Pe1E|pRTalVLijR@o~h~M`r3xT-5XgkXp$Xd<)(R}y^$Y!T?en-Eo}X9 z@xFAcflZu;02~k8?a%-(r2HyNudC?-2_r*sE|$xrry*B^cuU=smscCIsi0-~2D{_3 znb;#a1C;55XxR#?5W^inca*)nwhy1|_U0yKm150{C}*}LE1Q6g_{L_nJ>C*PYS{{;jw2$X3F!;A3>cngIivCGl;97 z3_lpBn^om?3OD8P6*Iih`P&pKI3ey-yFfzK%Y*ZQoMXe?t?(i4%Mb{|tA)=rh=cae z){YYmyr@V`NI*+Q>=zCKg>2Kqr|Ad1&-#%>;mu&xXnQ69etGVwMi|1Wx%Dgl7MNSx zvuV|KXdPegzP~)%cI8qz7H|Z(jw;<|W{~nR*Y01$#SxQ!mM1YnFB{uGvz`+AgEC+C zT+6Q_)QrA&d2{M=;0Z-cf>uESfd3SBJ5(M%RO+R8wUkJUAAxmd34CVDgg1W|g@&^V z0(f*`lrkR`@1P_O+f@=Iv$!L`tb0QusMq%|k#Zh#BBJ=X)Uf(d6!Ou$e5(SCvVDrg zGJ@$!JWt+iQ|}C=%cSm4#j}&1Y|(%5p9xo=)sXyTDmITzFJz=zXG^A!t{>WTzc;mw zOX=ou&IMTVA9sx2!4S7x0e-z%Jvi_ZUaDFiR87N2NbOMs*%T>qXh1@{+XB7ela1XQ+byVl1l zygz64=o&!~By5FaT(p+xmF6MD3R6IeSJAiI^+K>dwSQNO7vzna;;n1}-vla6-Z-_l zs%D_A)&*EJ=``)SE#BDW5C>MPdIsL{VM)mL^SPI;Mr)KPJ*XHVPi&*>8+&m)F#7h@ z?TcxUYway|3<|_qVa9;c<`<>Q?Dn!ogaV?<~M}ZJrH=A5k~bU^(_0{e>toq93k?x`VgZ= z8&s9|0$L`!Q(`e&BLT=i372@jGk5*&W4|~X#R(W<8lPk0!HeWBeAk;q*?DM)SpgS> z__1_mRuttp|5GwNgR@Ifqb+}Kroa~3Zmy#YrD$zP4d`r~DJ{78EWi0LYt|*hqkU`m z;uK<0G^`?v>2{m|Hh3tvK%GtouKwaXc~Ks$Ia?)){>DQg3^g_1Kl|#h1-L~eKo%p?3LTls)z>D>AZ&YZ&wV;NR%N}QW+>NUZ z|I|zzPak}FRD)UN2RYZ+lH+t@mW=f*_%UxMwA}(rH^u8z#87v6=7uKO9u2*ahD1o9K{W$&E{w|;W(1XADszRw&;aBC=+o zX3a}b>sG}PR<56xeI@qxgJ~@ej?k{UlEPg_lNPyur4}|4rzbp4C{_QHtfO76%;4An z6qb)e_fbFtw_y%oan)Fo)aY0ULK{C42Bhf2=T~)O^QHPvo3g#`wivi^;R3rPRj#9Qjg>r)LA zW#~`il?`4Hz@PeM32-<)#M2(rzFj~JKcrIg-{<1Gu;@X3P|O^8Bnhi91SCoNG0Uf$ z%x*%V_dRqX8Y$XuYzpO>@dvOVs6;m;?@#Qp$>-^Yu>=KU90OARtV-(tEg$&sxSs>L zp=#5kGxqPcvn|1A_9G#`k!4`gT$+n}-={k(|KBRpL$-0E>29`v&3dm7zc`aYSdotJLJ!9^+UhEwrQvvQJDbaRa)HqHyI)b)4v^)mfF7i+e3{i>(!pBn|Wrh{d z9UKP={^x#-gtEX!SF$V&yF zDK~-L_JJ)Op1H~z`&FeOXcmdxMAdgs!+4BDc2RMFt|3b;^ZhwpH$|)Tq2L@i;+&D0 zATx~pTd4Ml{qy)GwvnI;h4!-Dt>E_{bC;Kb`)duTGHW%gg3;-R^qE1Q!Aq?j7eLC8 z^6nZJeZ+aUyM~fiqpWwwRPrq;pZLnSn z`w_fO3hp0_I_YgC{~y+)T)q?!{ZC`=8I?nIr=qTo1N73Cv{2<6fInTdd!XnGL+pEk zxYk3q^EjucvX@~^+JH2e4JI7ooLOVq_;a^KDxdPdvX4Fnr(U_Sa&`Lmp;+ls| z+ctM}z3Y;;wQ2Zj-ZM&8tDl64^q^+VPe6pagky1OgfAw@6bQeq@EZbeAQym9tWB+K zP=V?)WGIJdzHxB^Y}-%ZH;*BLA@MYunqw=L!6h}3b!lKFQCZH=OaFJmpUV7WU{HFg zEdHRPY6J(jzsAS*0i!w)Z4;DvDQptNP{4mT^io105%5q(Jr#KHwQWv^Ai35bAmSbdD34=#eUALi|+Ba;m?32zRXr}k!37+wBo+` zQ$L(lMI*jaKV)2=HeA@$pB-PZ*kiX3%rEc_}tz<7W#FJPI1Uv3!>(wg*UkgEsJV~Hr*Ub)+f~I0aT%co#HW% z4*B1Sndj^$-47ZQ&cSf}NK*Mj=Hv?q@@Mgd530@9L~$GFspzkI6JJfxDI5iJRXp=s zU?X`X4-EtUu@om?8^`=IU@cA$6>s<|`Jbt5zSV>}7jPElWk8!kbOyt9u+L@`3qRY# z2xWkfc#k&h6++Q*)CYL&pmOFC;EU@9%1tHUJ{^HloD63&a80wiE%Y zpTP-LVr00SU%=!(ZI0DAk4Q7O0{@v4E6xc`cA2cg*xr!aKGVA@7x+v*zQk)y#B8oo z68O@(zrLyVhg?;?M!M>`HFzClvIOL~^;LQI>*fK`>hM0NGZW3HizFilb>O}-gYj^E z*mycr%K7qQ0GOq(_3VXdyah8@VX_W%{ z5MZIl3ofwE`UQfg<1S`t7z5^9kU{Es@ffF-5AChfeoF}O7?ikW_$fM3S8M3&Mu?V7ReW`K>sJnp!ddr|zab7$ z{XLny=PMA{1y6@**)TXy1I*ro^?EOk%R8A?5E4e!TCth{X5}2*EmmEze*3xe0xwR! z`W15V{p4+cc{lDvJ8Erbz1U66)RMhe(IGF%p58AIu~0bthv41dR2BiU>-O`YLUsEZv(Ol8PCwo7|O0C%cc}F zb=&-?t)n{wA)x;fyqf0P<4i^x`;0qk*t3kDR{@7L3Qz(hOM`<49}M()X3=Y?`X>Y! zv^@$u__^fi&Q!QDgz#T#p?QUO!qKj6I#&10>+Rp1mw*Kq>s(*WuR!+#6h+40WgJy& z5Az8=Jn_3&GtJy~sCEIq>8VFJ(|YYCxXc!3ktX{&$mi&(gUr*x!f)o>bN7X;TLCJ} zUV8Z4{-v|@x})b;qYz%Ei?iuJ+EW*s9dPbi(ymZ#^S71~%@jPXj+W08EIT71AVtW3IMsZ}8yk+(4epF)P(Dy8JDcsPP z9Oy)0&Nbj*HJalV1KKgAeEStaA;p$x1ZRxL!cV#9F(%`!M_AP?s(enXpMI*j$ z?|)@2ejrYKsw^3!xJ&=+q!kJA73vSSu%m1-;w|XH9X}&OAdCJ~Rfw~YA;}%<#{N!N zyG9aXKc7&Mq*yH6Grhl1)=H)+d)!RU33m7y1<5&71!uQog$=p?{$e!QVrn}?ET!Bj zJ=SNQEuVLeme_}kwcq;EK&`Goa*p82#RwYGX~+>Z#&KC2w*=)opiE#%E&HDxNRn6l zD!4O%Cd`;phV}gr=iLu$1_fbPOK#xDGnZa4Jkb-9G`h8VKv%;3iUlZlP$ z>5Q`2f1N@vMgQ%m)2Y``!c@Jy7+%rWVvfvS0?$nnE9vH+dO|qTez;A262iC{fA#9~ z9dX_0ki=B+LM#YXD3b*pG+nrQYlu1Q9jWk;z~eX2DY=diLOBi4Xwj;)rGkHavd8x` z&yC(`QI6w}sZb+n!dx?Jg z9sLsP!N1HOGOQ)U?t3uAq-SWpcb#dN8CM%m*c$(wERo)rw01sOMI%(W<7HvzL};>i zvbO}Xx<`NSUsib2_R39%-TDKWST=!yqse07siPR|4_s)lBamHQdL@*QI2Ei$=h2cE z!in_9Tl-oX-~-(ats^A%FtrhtP)1+y2$;*nqFYMD>hI8TErnjWnLpLv@>`lpP>rhG zNhkBuo6kJzv)AQR=?K@d`BqaUb+(`|zs>7g3deP&)Ye+$y6tj*PI}2F+(Fl*ps{E& z%stbM%5SO4M23J7^+gc6{a9(tw?k^mym%t9^r`Rg?ITqxqZkSW#qYdSNuw$oPM6ZsFL7T%Pr;;7 zWJAOBi!bE^gP)*4ng|%gS!8Mqk2KbuowrU1Ef!TrQ z8NlZYd2-w#mvJdOQMn&AFals69));KBfS^1)1IF1+Rr;0-~p-0&~>p zs24i{$IfxLUCkCjo=U-=dGgIVf!udl@d;Ve>3`QHwqLWCW5h@Edgt>OSbdibkY9Y> zbT8Euq^(`Nr9upKl0B;J<&ZlY>(%m`0=>iUJnNnVBY}#uK>X#_T);un=aTsBD2dyc z=#5yW2d9ZPc+>B%29Aab*~3nzug>V`uaTKGfdk2^_l?8ojgM&pBME>xBpND zDNhGArIm~df!!UvZBUcQb-T0pBGiL)_6!vD(KMtBEyKyN@&foLlD>U-||X zkg;cSBhek^&053}x0zl#w>TjeT?mn%fF8+n5nJy}n7qV(aQ5DZU1!`jx7X8rvkT`v zC!xEqtJEr-kd`FD2b!j*`YuhlS5xBLvCfNX4~@HPY#Z~h&F}wYif^* zjnn5FuLss!9n<>xv2n}=0v^IK>SR9~94?-x$IpxsAY zOZczjfRW#V#y#&i8{pt}txsk@P()a2HJil>1?zyKn`3U-|>jzW4x(`totM; z_|w5={u#k5I${Sb*=d7yFLSAIkKzTm2227~r7y@Gsb?#m&f=P?S~2F6YUXM-%>%he zm;au&jM`^p!QA|S&d|6czuJTNj!yL-aA-tlf6m{ZEBezy(ep=FTIKETESu-b>t;m3qph)Cl|1T7SedmzEfovG3FFed!C16)xPf$+Jfvn*QERe*M1i8r~XRs8XPy2?GWGSGL+BZjC`3 zb{8ClS&pNRG2P6=naQU{Clmop=+vjJAetUXg>%zNmgU{*Yev3O#%*DiAOD|NnY~_r$~Rj$%G2` zwMR=0iNaH)9QL-1c+!JD)JiSak)Cf~*8r~*NF7|c1Dz{< zv6+_#p~NwEBL4rkF|1Z?ut4+8*Ag3f8p(@EKcL@Cc28t=n9^W=KoX2R9k`zw&B2w6xi7-}9-BcUKNm~gs)lBz*xi+IY9NPw0Ly&Y zIEjT`8lDkawHc#o?pxQ&j5K;y=hlBfD{ONv)tU^Sgp359Zy7YR#q@PWY?NM1Z6v0l z9+;TNyVAD?e)rxQCMU@lZW<|hH1T(=fMCX*GHm>im9_#38@ea!(Q=;-`2e7#L!8!b z@L}~@*kD`v}#bL66-1Q+uV{bbeR@bl8IyUwP zV389w&K67N_!mrmL4aO1$Pj;14Rk){hf9po9~CU#sC4A`+l|mA!OIiRC;gai&Ea?s zgaI~w7H@&#UO21K$C;5bJ+Df?{S4jewz3NI9}G+N<8OXPM3<1)2>k}6lNcuDd)^8Z zR{odnqtZyw_g+_JHSIRo^yRewG)7&3%axCQAnA8Ii820v5q6(2V64L0dpa8Mk(bLp zr2DQG@V`dkka6mh7zfV1xnr0NrZt)q48b_FsZSSMdYToCho2U${#2bli5zjkQ{H=<(32^I+0qw>qBg$Gp z&*1&O;!?mf?kl+c%0(th-LEvmw(~w{KFo_M?E5htt?X~S3}%mL*PO1M?RYH_>NH$B zgU&rs00+lFuc}|b%oK!N7qzTAQ1RD$@P~8>fdcc!rL^EoJvFfc8uwpsN0$+g7Nux= zM?;%}jvMq7Vak+*%M>KT+c<72uhi*0kOdpjia5PBH#Uasod8$#6}Hx)Y0!+}zKV&O zY@h@yLz#xgYAB9-g}Jl`p^}yUqVe+n3#t4`AL1y$I^vkW(EK-tGJt>y;U-5fCUybH zMROnKNE-1&ey}<5*H~V1D)lw3KVf&YmG&FTp>a1i*}`+&Vv8dW>M!P%6%Y#)-H(F} zR3CLb^FD#XoGCQ$;n(d>j*wf|B)^uqWn6Vp-u?Z*-NAG1O_vk9wt0ilkR$FxN>wZXng_w1^`>-zQQIDuE&EHw#6bvCr zm~$t&%t7XCy&@3+us=QTJEVa7iv=&sQ8^^XZC-&pi^YT3dClII9dDi~*Y3=KtjqQq zp+SQK*pz_kRbOE-SUy!{|N631r;78np_EM8hpd}n>yPj1I_|vpJYU^BNItaGgyQ}M zDhX&Ae{kC{7x!ADM=?l(4nmrMel;UsiRQ^X=%LoE1%oMAurkBz2%HROQxj72^+u=V zf?xVe&hblIUumkXlJDLx>hTa#2gZ3Tn1@5M3Uo*De3w0D3FtR~bgu@G>R^MAl(o-k^-83!D&@u6u4M>mySoM zF!Gc5gj0mPEH6ZlKcLGKwB+AEu`h+=A+5XtxJhBnB|2o__&qEd^**OmLqW~ZQCf{Y z>%Yb^9>nYyC(#eCc|OJad_`oqQGMI=Cm8Qvb8Vn6uBc-lt%y)@&Q*fsv$9{PB2?(0 zbOxt#Ld|rf+VjfrH21tgCqbF^mfk5ov?PU*!S12A`CKn?_*?*AI982JF`%p)Ki z_4X(#?=i*f%AMc!T)Gxic%ZBG;TtbuO!PhiPY0&_0C$rU{Asi9tHdn> zc-JTM9QD&ke{8_-U#9y*RwUQy`*wSd0RL(L^X6>S9GD&zSc2Znf;-Ck-*g)Z(Jk9) z+XMtA8>4DD#AI7JI}bVT+g3ns9`nZC4+l1yH?tom>RgW2^^DlyVLOY0&CI~zNhRim zF2NN-LUJL~<5+BfLhb86Yu3vd@X8k7Bc6aiQm=pW662C2VF#Smqs+g!7ys`GWg%g- z^~*Vt^<-?CDRqw1jXpGedL|H{0!=sfkyzO(NAp;tZah-$5>Hlf4~jv(CM>*nZ1v&= zg_7WL%0@oP{?YIG2Cpcgs4{*}<2}}&XUnnPF~7(idA*-d@yO!6|~&HIizeP&46G9~UTWey9_ zg;)iTe}&Z_N%Bsv^3Ef?>wFMl4ZCdeGS0*0$f`|%>xHB>x6kD#{kBPniiFtWFrbcb*u>?7$X;dX{rSa;+SAP!!i z+m*Eb0)@@Oc6}%*Zs4WvI+A3&)7Ea|)L=fsTJ{m6iT~pEANWkI)~1Ka@#yRfjL^pm5{ ze$@Xcue(vWuTDZtHYPcfdvhv8Q{>R#x1f;$=5^s-ACgSfKr^mn_wz3cc*X0 zNkl9ev&;MO4e?&i50#D~GnSYl*fsV32AN_AQY(x%Q6af5Slp#Dxu~ zqx(|Wf$GZ-b)Zpb(elo-U6aBB+rCoebF#}VKGSe1XjQ*9l0vCY7ptIt_7M-ax}XA+7;#P#%V&Rx_K?X*&s?htp*1MYMQr)*Z08j}GH}#G{^+6>EB_B9elW zon*SDHU4euDd^0oSp$`(PaSZMt{}7Zt~C(XGl)%~reT|(Qj|1SI!(jw(6&|TT!%I7 zsceeR{*VoDPa(XH;$hL46>Ui*j1TOa;^JPNVIs;sNLRE!P;GJ%!F6R$h)FpDZ=N|J?x&gek+8&862!c~T=|zf{NbuVTZpepsIp}& z^e30P6fEq!IYD%eOXYaL`X3c_C35NuCaLG=juy$EQwnLnb@E{U9_XeSWRujj)`Lsp zc!?cFzRKV)zz1Q`LNZD#{r=n`j)C6fl9Nt~gj!sQCqVxQU+2@$i=qiH=ox}SQk;a= zr%0ZIi-2?RCU);9)QpH0lHtHtDYU@K?7OdbXS3KIdFM)#^pC7M9g!XMx|Lcl1 zs~|RpqVLJm30W4!HAl0^$&LpZKgdJ84T{3L+jNd*GUKn2#31Xk_;N8|s`TsB2$}~& zxLpa%rS0YG#`omPX8bm|VsyX-p;KazPiWR`=K5&i8%1bwl&)$Y6pdrPEM(&7Cjjd$ zlOtg1`VEcyqxw-nt5c#^ki;BRS>u0{#{Yo=@Bxa6ZMf2qiRn`QQqNNTQQ)Af?o?$l zxgh_Y7RuHOQ0ih=rgoSuzqu{~x{~p5dVZ9(vesEOQ2@C4JG9zpHCc%~wfLXB!|^dM zL)VtSrJ$gqU`|6z=oPbXsDR?3)*-Gkn4Kwv%W>1`S?@F>SkhjI}T0TyeL{@0`Qng*1ID@viM>7FwOWfVqEj ztA}?gRKLAqifLn75<<;qV5V_~K|R3@31yTJ70S-C$4P({mRodidQ7@M7<58*AWEO= z7QfdE>esG?BXaO;4*>@{aLG)sdF!f6Q%={tK$_AOGIWdKt;?z5s3P^fZ}+GL|CPQr z{CFTm4G7CQ1+(WT%-rW}u=-1+6ZaCJ9(U&V^b3*fpqon@PggAgM~t&rTIufi#<}=3 zg6zqSfPgw8!2GzjMMEKGJ5Q)@}-ij3OtOXYbF?{ z7Au_^eL_bi347ha_%CumYLCOmYarc{o+nm`1M`eA zWR!nvS0b4F7{^t{D_-Ev)C>8kTMZ57vSVz_0+(Cr!OfSUx`TxHs@d%}T2k?OdAT=0 zRIQISQ0&)M;%&EzQQT=allOJ1pH>#H+g_)Hx7Rom9b8w|??x|=&x%VM zJg(Pf)M(@+r;K^W`-gpKLE|@jHBHV9zL(NI7FM7TEWiwbiiFZT?9f&S&!Bw=ZSger znwr7!BZ|hUQW|?Z)4k15F-8^7?y5FSzAICXt{SnqIosdXJ1)}SgHDRdyq&Ro77>dzX*+QT(HrY&{0G5M)_+e6IOGi*$EI~=`nL=VtuYc#nVsHOm% zL;$rIdos(wKpk{I8p}LftsF_sA!O+k^L4~w4?yUnKHzhfn=hKF*}%=uF{0`ic|JUY zRw0L-UoqFB%s^jTjN_3Rjbp^jh3czz2JI=vJzf>TXS!1a_`&}sgBW>w+?v>Jl*M^_ z7So!KMrLlzi;6>&^2foRf6SYD9{wsR7E-4ZAT~^|-hpP_rL4-}fF$`MYy4+O11oY$ zV(tYtE(BB1U}{>#U*&TOkt?<&K;@n#=;dgvPx#%y>ee@L=adt1r^FLK2e7l0pJjZX z$LeP7X{gZWuQUxw9-`^Q$$EqS_htNv13;64p;st|on%Hyfh;GEAbcbp)Dv&#yGGQ( z#Ae0ejR1Zq4z(y>s9}S>!5sJX0DWz>o~(Jz++)~y5G%$*IB9l#%76+Ni)_jIz^!+P z1}v#a9M_Qgo5d4`sBeP)<}21y!dK4}h97st9Z<4CD$RE=ilT~WFY0FRo^sDut`)|b zXa7o#n=nKw(cBTJ&CFeDM@mgi(aY#QwVn5&r7M6)a8s{nzOdEyt||yF9iMV!NvZG* zMmwbfAUw9(yi*ctynLQY)cH9Yab+RD)PvALKX#=fJy&UN_7oHOyqp`X1Oz)0$+*UO zJwZ>sJ)^uBiP{0+ols(*M#b@)FL5YacsRZ_NWMDLnL@fx3vPaLhB042A*7lvUibx1~s8D1963k=Gnin zC% zPJflyidg|yUIk|f$*RG?s}9=El&|-ciUs8apU01=97~%xsljKTblyDjOe)iT z9IvF7{lAR|#$Q<}t$s0m$u>8KW4H0*t+gTa@Vo}ELiGVJ0GIhDb#h449iQ#9m%(n8 z;K3oSb+7*2*CxPwq>Uy~$m(6_JuUCt+Drg?^Y5+j(M}IXic1^E` z*zj$3;pW&u2p}kfa_)pyvab?Eo&w}OxAY!m#2sq$?LUIHv6}7Wyb?1E`!3lVdoO)k zl*)(dq4XAQk+__>XM@3HICTIZq>|=JAYTBf6=7zAXZ44xud0>Yr3JiJthqQ(`7di4 zP@y9*fK6#DC$4R9=L2(l(`_tAQH;1(SY2MUhe*6BZO+NO)Ye0+J?pSwOs-o6K|8Sy znRvJOR37w#=PSljs=Pa`dKX%FJjkqfnEKhBei?bj3zHNQZ zGv{7IysBscN4Px)7+n>fCj9h~#0@+sxtPX2kS>bO(N?=Lvf@)_PTLgG8AK}DC5&n} zJioI_nx6~(G9~iFo(8WR3VR=OkLjKC?36O`-Ye)e?9)Y>y{J_vZOb+^ZtuH~rO7Xj zEOIMMUwPAPDw_G2&%%!;I=zSM->Z;^vWH`xFNiSmSD;O1Q3(-+G0KuAo1D-{KZD_& zs@(0Ybxn-?Bn%Bik-j@(VPeG_o-So;qMWCSlF%s+)G>7Jx?*4HT+I;I-yr}XzbVLf zqwj3ywF|+ZuvLwxtkk_&)YsM+PAp7>DpA{&magy$4(V&{#}mC8-jEm8u^r5uhQWy}<@!lTKKcfSEP8gcpb zHu!k@?GsMPBJJa>{&+F0F(|UH?8S@dt=0K*MZI!M158dLrPLedi}ByE!SY6gyfQBK zq1vdKW@y(-#_c-jQ^aHjc{gF{S0;VA|GoSD5P$5>)b>ChDoYU3pCQnq!?WU_<4;rC z{Y7lbVU*82wB0o<2PLP}eSfrMIQ&@{j)pkjvS55}$^^v=4CVm`?Gm>pu(V*E!!5*q z%~I;Q8yx?h?cck~QirP1IrdKu8qf#fL`X2Z>?$DBbEqh+;-U7yhl+8-q${QTzl{D+&pKn~yK&Sn%gj(Xbf$XknS9iSniS#L{-P+i4(tj6Tt9hL0hJJqW@3C)R3Q1DgNxXE)~ zzhsn8c?*V1W!;^qN11k)Vv+GF$6Zx<&kSL!h@!3U(iV;I&7N}$=EtMIXr+ICGg^(j zlgY@zg3sXI3H0utk=Q9G%i7J9{D}MC;@={|<(Z4FOQ+WYe3@c*AnaezBc9>5tGSa_ zQDa(<-YJvpxHR3$r~wt}t%8paCpN3wjRYWqCh$jr*-;5k-#sW5&2Wf%@(s8bCee{g zP3?nr8g1}v;m~^p>4U^V2P%JYJ>Ub?*tTDAvmfeKCyub-fFE^bZKd}pv%voY-MS6{ literal 0 HcmV?d00001 diff --git a/design/general-progress-monitoring.md b/design/general-progress-monitoring.md new file mode 100644 index 000000000..43731c08d --- /dev/null +++ b/design/general-progress-monitoring.md @@ -0,0 +1,515 @@ +# Plugin Progress Monitoring + +This is intended as a replacement for the previously-approved Upload Progress Monitoring design +([Upload Progress Monitoring](upload-progress.md)) in order to expand the supported use cases beyond +snapshot uploads to include what was previously called Async Backup/Restore Item Actions. This +updated design should handle the combined set of use cases for those previously separate designs. + +Volume snapshotter plug-in are used by Velero to take snapshots of persistent volume contents. +Depending on the underlying storage system, those snapshots may be available to use immediately, +they may be uploaded to stable storage internally by the plug-in or they may need to be uploaded after +the snapshot has been taken. We would like for Velero to continue on to the next part of the backup as quickly +as possible but we would also like the backup to not be marked as complete until it is a usable backup. We'd also +eventually like to bring the control of upload under the control of Velero and allow the user to make decisions +about the ultimate destination of backup data independent of the storage system they're using. + +We would also like any internal or third party Backup or Restore Item Action to have the option of +making use of this same ability to run some external process without blocking the current backup or +restore operation. Beyond Volume Snapshotters, this is also needed for data mover operations on both +backup and restore, and potentially useful for other third party operations -- for example +in-cluster registry image backup or restore could make use of this feature in a third party plugin). + +### Glossary +- BIA: BackupItemAction +- RIA: RestoreItemAction + +## Examples +- AWS - AWS snapshots return quickly, but are then uploaded in the background and cannot be used until EBS moves +the data into S3 internally. + +- vSphere - The vSphere plugin takes a local snapshot and then the vSphere plugin uploads the data to S3. The local +snapshot is usable before the upload completes. + +- Restic - Does not go through the volume snapshot path. Restic backups will block Velero progress +until completed. However, with the more generalized approach in the revised design, restic/kopia +backup and restore *could* make use of this framework if their actions are refactored as +Backup/RestoreItemActions. + +- Data Movers + - Data movers are asynchronous processes executed inside backup/restore item actions that applies to a specific kubernetes resources. A common use case for data mover is to backup/restore PVCs whose data we want to move to some form of backup storage outside of using velero kopia/restic implementations. + - Workflow + - User takes velero backup of PVC A + - BIA plugin applies to PVCs with compatible storage driver + - BIA plugin triggers data mover + - Most common use case would be for the plugin action to create a new CR which would + trigger an external controller action + - Another possible use case would be for the plugin to run its own async action in a + goroutine, although this would be less resilient to plugin container restarts. + - BIA plugin returns + - Velero backup process continues + - Main velero backup process monitors running BIA threads via gRPC to determine if process is done and healthy + + +## Primary changes from the original Upload Progress Monitoring design + +The most fundamental change here is that rather than proposing a new special-purpose +SnapshotItemAction, the existing BackupItemAction plugin will be modified to accommodate an optional +snapshot (or other item operation) ID return. The primary reasons for this change are as follows: + +1. The intended scope has moved beyond snapshot processing, so it makes sense to support +asynchronous operations in other backup or restore item actions. + +2. We expect to have plugin API versioning implemented in Velero 1.10, making it feasible to +implement changes in the existing plugin APIs now. + +3. We will need this feature on both backup and restore, meaning that if we took the "new plugin +type" approach, we'd need two new plugin types. + +4. Other than the snapshot/operation ID return, the rest of the plugin processing is identical to +Backup/RestoreItemActions. With separate plugin types, we'd have to repeat all of that logic +(including dealing with additional items, etc.) twice. + +The other major change is that we will be applying this to both backups and restores, although the +Volume Snapshotter use case only needs this on backup. This means that everything we're doing around +backup phase and workflow will also need to be done for restore. + +Then there are various minor changes around terminology to make things more generic. Instead of +"snapshotID", we'll have "operationID" (which for volume snapshotters will be a snapshot +ID). + +## Goals + +- Enable monitoring of backup/restore item action operations that continue after snapshotting and other operations have completed +- Keep non-usable backups and restores (upload/persistence has not finished, etc.) from appearing as completed +- Make use of plugin API versioning functionality to manage changes to Backup/RestoreItemAction interfaces +- Enable vendors to plug their own data movers into velero using BIA/RIA plugins + +## Non-goals +- Today, Velero is unable to recover from an in progress backup when the velero server crashes (pod is deleted). This has an impact on running asynchronous processes, but it’s not something we intend to solve in this design. + +## Models + +### Internal configuration and management +In this model, movement of the snapshot to stable storage is under the control of the snapshot +plug-in. Decisions about where and when the snapshot gets moved to stable storage are not +directly controlled by Velero. This is the model for the current VolumeSnapshot plugins. + +### Velero controlled management +In this model, the snapshot is moved to external storage under the control of Velero. This +enables Velero to move data between storage systems. This also allows backup partners to use +Velero to snapshot data and then move the data into their backup repository. + +## Backup and Restore phases + +Velero currently has backup/restore phases "InProgress" and "Completed". A backup moves to the +Completed phase when all of the volume snapshots have completed and the Kubernetes metadata has been +written into the object store. However, the actual data movement may be happening in the background +after the backup has been marked "Completed". The backup is not actually a stable backup until the +data has been persisted properly. In some cases (e.g. AWS) the backup cannot be restored from until +the snapshots have been persisted. + +Once the snapshots have been taken, however, it is possible for additional backups or restores (as +long as they don't use not-yet-completed backups) to be made without interference. Waiting until +all data has been moved before starting the next backup will slow the progress of the system without +adding any actual benefit to the user. + +A new backup/restore phase, "WaitingForPluginOperations" will be introduced. When a backup or +restore has entered this phase, Velero is free to start another backup/restore. The backup/restore +will remain in the "WaitingForPluginOperations" phase until all BIA/RIA operations have completed +(for example, for a volume snapshotter, until all data has been successfully moved to persistent +storage). The backup/restore will not fail once it reaches this phase. If the backup is deleted +(cancelled), the plug-ins will attempt to delete the snapshots and stop the data movement - this may +not be possible with all storage systems. + +### State progression + +![image](AsyncActionFSM.png) +### New +When a backup/restore request is initially created, it is in the "New" phase. + +The next state is either "InProgress" or "FailedValidation" + +### FailedValidation +If the backup/restore request is incorrectly formed, it goes to the "FailedValidation" phase and +terminates + +### InProgress +When work on the backup/restore begins, it moves to the "InProgress" phase. It remains in the +"InProgress" phase until all pre/post execution hooks have been executed, all snapshots have been +taken and the Kubernetes metadata and backup/restore info is safely written to the object store +plug-in. + +In the current implementation, Restic backups will move data during the "InProgress" phase. In the +future, it may be possible to combine a snapshot with a Restic (or equivalent) backup which would +allow for data movement to be handled in the "WaitingForPluginOperations" phase, + +The next phase is either "Completed", "WaitingForPluginOperations", "Failed" or "PartiallyFailed". +Backups/restores which would have a final phase of "Completed" or "PartiallyFailed" may move to the +"WaitingForPluginOperations" or "WaitingForPluginOperationsPartialFailure" state. A backup/restore +which will be marked "Failed" will go directly to the "Failed" phase. Uploads may continue in the +background for snapshots that were taken by a "Failed" backup/restore, but no progress will not be +monitored or updated. If there are any operations in progress when a backup is moved to the "Failed" +phase (although with the current workflow, that shouldn't happen), Cancel() should be called on +these operations. When a "Failed" backup is deleted, all snapshots will be deleted and at that point +any uploads still in progress should be aborted. + +### WaitingForPluginOperations (new) +The "WaitingForPluginOperations" phase signifies that the main part of the backup/restore, including +snapshotting has completed successfully and uploading and any other asynchronous BIA/RIA plugin +operations are continuing. In the event of an error during this phase, the phase will change to +WaitingForPluginOperationsPartialFailure. On success, the phase changes to Completed. Backups +cannot be restored from when they are in the WaitingForPluginOperations state. + +### WaitingForPluginOperationsPartialFailure (new) +The "WaitingForPluginOperationsPartialFailure" phase signifies that the main part of the +backup/restore, including snapshotting has completed, but there were partial failures either during +the main part or during any async operations, including snapshot uploads. Backups cannot be +restored from when they are in the WaitingForPluginOperationsPartialFailure state. + +### Failed +When a backup/restore has had fatal errors it is marked as "Failed" Backups in this state cannot be +restored from. + +### Completed +The "Completed" phase signifies that the backup/restore has completed, all data has been transferred +to stable storage (or restored to the cluster) and any backup in this state is ready to be used in a +restore. When the Completed phase has been reached for a backup it is safe to remove any of the +items that were backed up. + +### PartiallyFailed +The "PartiallyFailed" phase signifies that the backup/restore has completed and at least part of the +backup/restore is usable. Restoration from a PartiallyFailed backup will not result in a complete +restoration but pieces may be available. + +## Workflow + +When a Backup or Restore Action is executed, any BackupItemAction, RestoreItemAction, or +VolumeSnapshot plugins will return operation IDs (snapshot IDs or other plugin-specific +identifiers). The plugin should be able to provide status on the progress for the snapshot and +handle cancellation of the operation/upload if the snapshot is deleted. If the plugin is restarted, +the operation ID should remain valid. + +When all snapshots have been taken and Kubernetes resources have been persisted to the ObjectStorePlugin +the backup will either have fatal errors or will be at least partially usable. + +If the backup/restore has fatal errors it will move to the "Failed" state and finish. If a +backup/restore fails, the upload or other operation will not be cancelled but it will not be +monitored either. For backups in any phase, all snapshots will be deleted when the backup is +deleted. Plugins will cancel any data movement or other operations and remove snapshots and other +associated resources when the VolumeSnapshotter DeleteSnapshot method or DeleteItemAction Execute +method is called. + +Velero will poll the plugins for status on the operations when the backup/restore exits the +"InProgress" phase and has no fatal errors. + +If any operations are not complete, the backup/restore will move to either WaitingForPluginOperations +or WaitingForPluginOperationsPartialFailure or Failed. + +Post-snapshot and other operations may take a long time and Velero and its plugins may be restarted +during this time. Once a backup/restore has moved into the WaitingForPluginOperations or +WaitingForPluginOperationsPartialFailure phase, another backup/restore may be started. + +While in the WaitingForPluginOperations or WaitingForPluginOperationsPartialFailure phase, the +snapshots and item actions will be periodically polled. When all of the snapshots and item actions +have reported success, the backup/restore will move to the Completed or PartiallyFailed phase, +depending on whether the backup/restore was in the WaitingForPluginOperations or +WaitingForPluginOperationsPartialFailure phase. + +The Backup resources will not be written to object storage until the backup has entered a final phase: +Completed, Failed or PartialFailure + +## Reconciliation of InProgress backups + +InProgress backups will not have a `velero-backup.json` present in the object store. During +reconciliation, backups which do not have a `velero-backup.json` object in the object store will be +ignored. + +## Plug-in API changes + +### OperationProgress struct + + type OperationProgress struct { + Completed bool // True when the operation has completed, either successfully or with a failure + Err string // Set when the operation has failed + Completed, Total int64 // Quantity completed so far and the total quanity associated with the operaation in operationUnits + // For data mover and volume snapshotter use cases, this would be in bytes + // On successful completion, completed and total should be the same. + OperationUnits string // Units represented by completed and total -- for data mover and item + // snapshotters, this will usually be bytes. + Description string // Optional description of operation progress + Started, Updated time.Time // When the upload was started and when the last update was seen. Not all + // systems retain when the upload was begun, return Time 0 (time.Unix(0, 0)) + // if unknown. + } + +### VolumeSnapshotter changes + +Two new methods will be added to the VolumeSnapshotter interface: + + Progress(snapshotID string) (OperationProgress, error) + Cancel(snapshotID string) (error) + +Open question: Does VolumeSnapshotter need Cancel, or is that only needed for BIA/RIA? + +Progress will report the current status of a snapshot upload. This should be callable at +any time after the snapshot has been taken. In the event a plug-in is restarted, if the operationID +(snapshot ID) continues to be valid it should be possible to retrieve the progress. + +`error` is set if there is an issue retrieving progress. If the snapshot is has encountered an +error during the upload, the error should be returned in OperationProgress and error should be nil. + +### BackupItemAction and RestoreItemAction plug-in changes + +Currently CSI snapshots and the Velero Plug-in for vSphere are implemented as BackupItemAction +plugins. While the majority of BackupItemAction plugins do not take snapshots or upload data, this +functionality is useful for any longstanding plugin operation managed by an external +process/controller so we will modify BackupItemAction and RestoreItemAction to optionally return an +operationID in addition to the modified item. + +Velero can attempt to cancel an operation by calling the Cancel API call on the BIA/RIA. The plugin +can then take any appropriate action as needed. Cancel will be called on backup deletion, and +possibly upon reaching timeouts, if timeout support is included in this feature. Cancel is not +intended to be used to delete/remove the results of completed actions. + +_AsyncOperationsNotSupportedError_ should only be returned (by either Cancel or by Progress) if the +Backup/RestoreItemAction plugin should not be handling the item. If the Backup/RestoreItemAction +plugin should handle the item but, for example, the item/snapshot ID cannot be found to report +progress, Progress or Cancel will return an InvalidOperationIDError error rather than a populated +OperationProgress struct. If the item action does not start an asynchronous operation, then +operationID will be empty. + +Two new methods will be added to the BackupItemAction interface, and the Execute() return signature +will be modified: + + // Execute allows the ItemAction to perform arbitrary logic with the item being backed up, + // including mutating the item itself prior to backup. The item (unmodified or modified) + // should be returned, an optional operationID, along with an optional slice of ResourceIdentifiers + // specifying additional related items that should be backed up. If operationID is specified + // then velero will wait for this operation to complete before the backup is marked Completed. + Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, operationID string, + []ResourceIdentifier, error) + + // Progress + Progress(input *BackupItemActionProgressInput) (OperationProgress, error) + // Cancel + Cancel(input *BackupItemActionProgressInput) error + + // BackupItemActionProgressInput contains the input parameters for the BackupItemAction's Progress function. + type BackupItemActionProgressInput struct { + // Item is the item that was stored in the backup + Item runtime.Unstructured + // OperationID is the operation ID returned by BackupItemAction Execute + operationID string + // Backup is the representation of the backup resource processed by Velero. + Backup *velerov1api.Backup + } + +Two new methods will be added to the RestoreItemAction interface, and the +RestoreItemActionExecuteOutput struct will be modified: + + // Execute allows the ItemAction to perform arbitrary logic with the item being restored, + // including mutating the item itself prior to restore. The item (unmodified or modified) + // should be returned, an optional OperationID, along with an optional slice of ResourceIdentifiers + // specifying additional related items that should be restored, a warning (which will be + // logged but will not prevent the item from being restored) or error (which will be logged + // and will prevent the item from being restored) if applicable. If OperationID is specified + // then velero will wait for this operation to complete before the restore is marked Completed. + Execute(input *RestoreItemActionExecuteInput) (*RestoreItemActionExecuteOutput, error) + + + // Progress + Progress(input *RestoreItemActionProgressInput) (OperationProgress, error) + + // Cancel + Cancel(input *RestoreItemActionProgressInput) error + + // RestoreItemActionExecuteOutput contains the output variables for the ItemAction's Execution function. + type RestoreItemActionExecuteOutput struct { + // UpdatedItem is the item being restored mutated by ItemAction. + UpdatedItem runtime.Unstructured + + // AdditionalItems is a list of additional related items that should + // be restored. + AdditionalItems []ResourceIdentifier + + // SkipRestore tells velero to stop executing further actions + // on this item, and skip the restore step. When this field's + // value is true, AdditionalItems will be ignored. + SkipRestore bool + + // OperationID is an identifier which indicates an ongoing asynchronous action which Velero will + // continue to monitor after restoring this item. If left blank, then there is no ongoing operation + OperationID string + } + + // RestoreItemActionProgressInput contains the input parameters for the RestoreItemAction's Progress function. + type RestoreItemActionProgressInput struct { + // Item is the item that was stored in the restore + Item runtime.Unstructured + // OperationID is the operation ID returned by RestoreItemAction Execute + operationID string + // Restore is the representation of the restore resource processed by Velero. + Restore *velerov1api.Restore + } + +## Changes in Velero backup format + +No changes to the existing format are introduced by this change. As part of the backup workflow changes, a +`-itemoperations.json.gz` file will be added that contains the items and operation IDs +(snapshotIDs) returned by VolumeSnapshotter and BackupItemAction plugins. Also, the creation of the +`velero-backup.json` object will not occur until the backup moves to one of the terminal phases +(_Completed_, _PartiallyFailed_, or _Failed_). Reconciliation should ignore backups that do not +have a `velero-backup.json` object. + +The Backup/RestoreItemAction plugin identifier as well as the ItemID and OperationID will be stored +in the `-itemoperations.json.gz`. When checking for progress, this info will be used +to select the appropriate Backup/RestoreItemAction plugin to query for progress. Here's an example +of what a record for a datamover plugin might look like: +``` + { + "itemOperation": { + "plugin": "velero.io/datamover-backup", + "itemID": "", + "operationID": "" + } + } +``` + +The cluster that is creating the backup will have the Backup resource present and will be able to +manage the backup before the backup completes. + +If the Backup resource is removed (e.g. Velero is uninstalled) before a backup completes and writes +its `velero-backup.json` object, the other objects in the object store for the backup will be +effectively orphaned. This can currently happen but the current window is much smaller. + +### `-itemoperations.json.gz` +The itemoperations file is similar to the existing `-itemsnapshots.json.gz` Each snapshot taken via +BackupItemAction will have a JSON record in the file. Exact format TBD. + +This file will be uploaded to object storage at the end of processing all of the items in the +backup, before the phase moves away from `InProgress`. + +## Changes to Velero restores + +A `-itemoperations.json.gz` file will be added that contains the items and operation +IDs returned by RestoreItemActions. The format will be the same as the +`-itemoperations.json.gz` generated for backups. + +This file will be uploaded to object storage at the end of processing all of the items in the +restore, before the phase moves away from `InProgress`. + +## CSI snapshots + +For systems such as EBS, a snapshot is not available until the storage system has transferred the +snapshot to stable storage. CSI snapshots expose the _readyToUse_ state that, in the case of EBS, +indicates that the snapshot has been transferred to durable storage and is ready to be used. The +CSI BackupItemAction.Progress method will poll that field and when completed, return completion. + +## vSphere plug-in + +The vSphere Plug-in for Velero uploads snapshots to S3 in the background. This is also a +BackupItemAction plug-in, it will check the status of the Upload records for the snapshot and return +progress. + +## Backup workflow changes + +The backup workflow remains the same until we get to the point where the `velero-backup.json` object +is written. At this point, we will queue the backup to a finalization go-routine. The next backup +may then begin. The finalization routine will run across all of the +VolumeSnapshotter/BackupItemAction operations and call the _Progress_ method on each of them. + +If all snapshot and backup item operations have finished (either successfully or failed), the backup +will be completed and the backup will move to the appropriate terminal phase and upload the +`velero-backup.json` object to the object store and the backup will be complete. + +If any of the snapshots or backup items are still being processed, the phase of the backup will be +set to the appropriate phase (_WaitingForPluginOperations_ or +_WaitingForPluginOperationsPartialFailure_). In the event of any of the progress checks return an +error, the phase will move to _WaitingForPluginOperationsPartialFailure_. The backup will then be +requeued and will be rechecked again after some time has passed. + +## Restore workflow changes + +The restore workflow remains the same until velero would currently move the backup into one of the +terminal states. At this point, we will queue the restore to a finalization go-routine. The next +restore may then begin. The finalization routine will run across all of the RestoreItemAction +operations and call the _Progress_ method on each of them. + +If all restore item operations have finished (either successfully or failed), the restore will be +completed and the restore will move to the appropriate terminal phase and the restore will be +complete. + +If any of the restore items are still being processed, the phase of the restore will be set to the +appropriate phase (_WaitingForPluginOperations_ or _WaitingForPluginOperationsPartialFailure_). In +the event of any of the progress checks return an error, the phase will move to +_WaitingForPluginOperationsPartialFailure_. The restore will then be requeued and will be rechecked +again after some time has passed. + +## Restart workflow + +On restart, the Velero server will scan all Backup/Restore resources. Any Backup/Restore resources +which are in the _InProgress_ phase will be moved to the _Failed_ phase. Any Backup/Restore +resources in the _WaitingForPluginOperations_ or _WaitingForPluginOperationsPartialFailure_ phase +will be treated as if they have been requeued and progress checked and the backup/restore will be +requeued or moved to a terminal phase as appropriate. + +## Notes on already-merged code which may need updating + +Since this design is modifying a previously-approved design, there is some preparation work based on +the earlier upload progress monitoring design that may need modification as a result of these +updates. Here is a list of some of these items: + +1. Consts for the "Uploading" and "UploadingPartialFailure" phases have already been defined. These + will need to be removed when the "WaitingForPluginOperations" and + "WaitingForPluginOperationsPartialFailure" phases are defined. + - https://github.com/vmware-tanzu/velero/pull/3805 +1. Remove the ItemSnapshotter plugin APIs (and related code) since the revised design will reuse + VolumeSnapshotter and BackupItemAction plugins. + - https://github.com/vmware-tanzu/velero/pull/4077 + - https://github.com/vmware-tanzu/velero/pull/4417 +1. UploadProgressFeatureFlag shouldn't be needed anymore. The current design won't really need a + feature flag here -- the new features will be added to V2 of the VolumeSnapshotter, + BackupItemAction, and RestoreItemAction plugins, and it will only be used if there are plugins which + return operation IDs. + - https://github.com/vmware-tanzu/velero/pull/4416 +1. Adds -itemsnapshots.gz file to backup (when provided) -- this is still part of the + revised design, so it should stay. + - https://github.com/vmware-tanzu/velero/pull/4429 +1. Upload Progress Monitoring and Item Snapshotter basic support: This PR is not yet merged, so + nothing will need to be reverted. While the implementation here will be useful in informing the + implementation of the new design, several things have changed in the design proposal since the PR + was written. + - https://github.com/vmware-tanzu/velero/pull/4467 + +# Implementation tasks + +VolumeSnapshotter new plugin APIs +BackupItemAction new plugin APIs +RestoreItemAction new plugin APIs +New backup phases +New restore phases +Defer uploading `velero-backup.json` +AWS EBS plug-in Progress implementation +Operation monitoring +Implementation of `-itemoperations.json.gz` file +Implementation of `-itemoperations.json.gz` file +Restart logic +Change in reconciliation logic to ignore backups/restores that have not completed +CSI plug-in BackupItemAction Progress implementation +vSphere plug-in BackupItemAction Progress implementation (vSphere plug-in team) + + +# Open Questions + +1. Do we need a Cancel operation for VolumeSnapshotter? + - From feedback, I'm thinking we probably don't need it. The only real purpose of Cancel + here is to tell the plugin that Velero won't be waiting anymore, so if there are any + required custom cancellation actions, now would be a good time to perform them. For snapshot + uploads that are already in proress, there's not really anything else to cancel. +2. Should we actually write the backup *before* moving to the WaitingForPluginOperations or + WaitingForPluginOperationsPartialFailure phase rather than waiting until all operations + have completed? The operations in question won't affect what gets written to object storage + for the backup, and since we've already written the list of operations we're waiting for to + object storage, writing the backup now would make the process resilient to Velero restart if + it happens during WaitingForPluginOperations or WaitingForPluginOperationsPartialFailure + From 73a5ee41fa3e6ca1402ed1e0e2f851781113f8fd Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Thu, 22 Sep 2022 12:25:02 -0400 Subject: [PATCH 02/20] BackupItemAction v2 design This includes necessary changes to support async item action monitoring. Signed-off-by: Scott Seago --- design/biav2-design.md | 89 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 design/biav2-design.md diff --git a/design/biav2-design.md b/design/biav2-design.md new file mode 100644 index 000000000..4294e53cc --- /dev/null +++ b/design/biav2-design.md @@ -0,0 +1,89 @@ +# Design for BackupItemAction v2 API + +## Abstract +This design includes the changes to the BackupItemAction (BIA) api design as required by the [Item Action Progress Monitoring](general-progress-monitoring.md) feature. +The BIA v2 interface will have two new methods, and the Execute() return signature will be modified. +If there are any additional BIA API changes that are needed in the same Velero release cycle as this change, those can be added here as well. + +## Background +This API change is needed to facilitate long-running plugin actions that may not be complete when the Execute() method returns. +It is an optional feature, so plugins which don't need this feature can simply return an empty operation ID and the new methods can be no-ops. +This will allow long-running plugin actions to continue in the background while Velero moves on to the next plugin, the next item, etc. + +## Goals +- Allow for BIA Execute() to optionally initiate a long-running operation and report on operation status. + +## Non Goals +- Allowing velero control over when the long-running operation begins. + + +## High-Level Design +As per the [Plugin Versioning](plugin-versioning.md) design, a new BIAv2 plugin `.proto` file will be created to define the GRPC interface. +v2 go files will also be created in `plugin/clientmgmt/backupitemaction` and `plugin/framework/backupitemaction`, and a new PluginKind will be created. +The velero Backup process will be modified to reference v2 plugins instead of v1 plugins. +An adapter will be created so that any existing BIA v1 plugin can be executed as a v2 plugin when executing a backup. + +## Detailed Design + +### proto changes (compiled into golang by protoc) + +The v2 BackupItemAction.proto will be like the current v1 version with the following changes: +ExecuteResponse gets a new field: +``` +message ExecuteResponse { + bytes item = 1; + repeated generated.ResourceIdentifier additionalItems = 2; + string operationID = 3; +} +``` +The BackupItemAction service gets two new rpc methods: +``` +service BackupItemAction { + rpc AppliesTo(BackupItemActionAppliesToRequest) returns (BackupItemActionAppliesToResponse); + rpc Execute(ExecuteRequest) returns (ExecuteResponse); + rpc Progress(BackupItemActionProgressRequest) returns (BackupItemActionProgressResponse); + rpc Cancel(BackupItemActionCancelRequest) returns (google.protobuf.Empty); +} +``` +To support these new rpc methods, we define new request/response message types: +``` +message BackupItemActionProgressRequest { + string plugin = 1; + string operationID = 2; + bytes backup = 3; +} + +message BackupItemActionProgressResponse { + generated.OperationProgress progress = 1; +} + +message BackupItemActionCancelRequest { + string plugin = 1; + string operationID = 2; + bytes backup = 3; +} + +``` +One new shared message type will be added, as this will also be needed for v2 RestoreItemAction and VolmeSnapshotter: +``` +message OperationProgress { + bool completed = 1; + string err = 2; + int64 completed = 3; + int64 total = 4; + string operationUnits = 5; + string description = 6; + google.protobuf.Timestamp started = 7; + google.protobuf.Timestamp updated = 8; +} +``` + +A new PluginKind, `BackupItemActionV2`, will be created, and the backup process will be modified to use this plugin kind. +See [Plugin Versioning](plugin-versioning.md) for more details on implementation plans, including v1 adapters, etc. + + +## Compatibility +The included v1 adapter will allow any existing BackupItemAction plugin to work as expected, with an empty operation ID returned from Execute() and no-op Progress() and Cancel() methods. + +## Implementation +This will be implemented during the Velero 1.11 development cycle. From 8a10b9a9e419ab9db61fca977f14c81ec630414c Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Wed, 28 Sep 2022 13:58:15 -0400 Subject: [PATCH 03/20] RestoreItemAction v2 design Signed-off-by: Scott Seago --- design/riav2-design.md | 109 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 design/riav2-design.md diff --git a/design/riav2-design.md b/design/riav2-design.md new file mode 100644 index 000000000..f42af2750 --- /dev/null +++ b/design/riav2-design.md @@ -0,0 +1,109 @@ +# Design for RestoreItemAction v2 API + +## Abstract +This design includes the changes to the RestoreItemAction (RIA) api design as required by the [Item Action Progress Monitoring](general-progress-monitoring.md) feature. +It also includes changes as required by the [Wait For Additional Items](wait-for-additional-items.md) feature. +The BIA v2 interface will have three new methods, and the RestoreItemActionExecuteOutput() struct in the return from Execute() will have three optional fields added. +If there are any additional RIA API changes that are needed in the same Velero release cycle as this change, those can be added here as well. + +## Background +This API change is needed to facilitate long-running plugin actions that may not be complete when the Execute() method returns. +It is an optional feature, so plugins which don't need this feature can simply return an empty operation ID and the new methods can be no-ops. +This will allow long-running plugin actions to continue in the background while Velero moves on to the next plugin, the next item, etc. +The other change allows Velero to wait until newly-restored AdditionalItems returned by a RIA plugin are ready before moving on to restoring the current item. + +## Goals +- Allow for RIA Execute() to optionally initiate a long-running operation and report on operation status. +- Allow for RIA to allow Velero to call back into the plugin to wait until AdditionalItems are ready before continuing with restore. + +## Non Goals +- Allowing velero control over when the long-running operation begins. + + +## High-Level Design +As per the [Plugin Versioning](plugin-versioning.md) design, a new RIAv2 plugin `.proto` file will be created to define the GRPC interface. +v2 go files will also be created in `plugin/clientmgmt/restoreitemaction` and `plugin/framework/restoreitemaction`, and a new PluginKind will be created. +Changes to RestoreItemActionExecuteOutput will be made to the existing struct. +Since the new fields are optional elements of the struct, the new enlarged struct will work with both v1 and v2 plugins. +The velero Restore process will be modified to reference v2 plugins instead of v1 plugins. +An adapter will be created so that any existing RIA v1 plugin can be executed as a v2 plugin when executing a restore. + +## Detailed Design + +### proto changes (compiled into golang by protoc) + +The v2 RestoreItemAction.proto will be like the current v1 version with the following changes: +RestoreItemActionExecuteOutput gets three new fields (defined in the current (v1) RestoreItemAction.proto file: +``` +message RestoreItemActionExecuteResponse { + bytes item = 1; + repeated ResourceIdentifier additionalItems = 2; + bool skipRestore = 3; + string operationID = 4; + bool waitForAdditionalItems = 5; + google.protobuf.Duration additionalItemsReadyTimeout = 6; +} + +``` +The RestoreItemAction service gets three new rpc methods: +``` +service RestoreItemAction { + rpc AppliesTo(RestoreItemActionAppliesToRequest) returns (RestoreItemActionAppliesToResponse); + rpc Execute(RestoreItemActionExecuteRequest) returns (RestoreItemActionExecuteResponse); + rpc Progress(RestoreItemActionProgressRequest) returns (RestoreItemActionProgressResponse); + rpc Cancel(RestoreItemActionCancelRequest) returns (google.protobuf.Empty); + rpc AreAdditionalItemsReady(RestoreItemActionItemsReadyRequest) returns (RestoreItemActionItemsReadyResponse); +} + +``` +To support these new rpc methods, we define new request/response message types: +``` +message RestoreItemActionProgressRequest { + string plugin = 1; + string operationID = 2; + bytes restore = 3; +} + +message RestoreItemActionProgressResponse { + generated.OperationProgress progress = 1; +} + +message RestoreItemActionCancelRequest { + string plugin = 1; + string operationID = 2; + bytes restore = 3; +} + +message RestoreItemActionItemsReadyRequest { + string plugin = 1; + bytes restore = 2; + repeated ResourceIdentifier additionalItems = 3; +} +message RestoreItemActionItemsReadyResponse { + bool ready = 1; +} + +``` +One new shared message type will be needed, as defined in the v2 BackupItemAction design: +``` +message OperationProgress { + bool completed = 1; + string err = 2; + int64 completed = 3; + int64 total = 4; + string operationUnits = 5; + string description = 6; + google.protobuf.Timestamp started = 7; + google.protobuf.Timestamp updated = 8; +} +``` + +A new PluginKind, `RestoreItemActionV2`, will be created, and the restore process will be modified to use this plugin kind. +See [Plugin Versioning](plugin-versioning.md) for more details on implementation plans, including v1 adapters, etc. + + +## Compatibility +The included v1 adapter will allow any existing RestoreItemAction plugin to work as expected, with no-op AreAdditionalItemsReady(), Progress(), and Cancel() methods. + +## Implementation +This will be implemented during the Velero 1.11 development cycle. From 82c6ca730468f51064d66194f3ffdbef3f86f847 Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Wed, 5 Oct 2022 16:51:46 -0400 Subject: [PATCH 04/20] VolumeSnapshotter v2 API design Signed-off-by: Scott Seago --- design/vsv2-design.md | 84 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 design/vsv2-design.md diff --git a/design/vsv2-design.md b/design/vsv2-design.md new file mode 100644 index 000000000..a8ca1fc95 --- /dev/null +++ b/design/vsv2-design.md @@ -0,0 +1,84 @@ +# Design for VolumeSnapshotter v2 API + +## Abstract +This design includes the changes to the VolumeSnapshotter api design as required by the [Item Action Progress Monitoring](general-progress-monitoring.md) feature. +The VolumeSnapshotter v2 interface will have two new methods. +If there are any additional VolumeSnapshotter API changes that are needed in the same Velero release cycle as this change, those can be added here as well. + +## Background +This API change is needed to facilitate long-running plugin actions that may not be complete when the Execute() method returns. +The existing snapshotID returned by CreateSnapshot will be used as the operation ID. +This will allow long-running plugin actions to continue in the background while Velero moves on to the next plugin, the next item, etc. + +## Goals +- Allow for VolumeSnapshotter CreateSnapshot() to initiate a long-running operation and report on operation status. + +## Non Goals +- Allowing velero control over when the long-running operation begins. + + +## High-Level Design +As per the [Plugin Versioning](plugin-versioning.md) design, a new VolumeSnapshotterv2 plugin `.proto` file will be created to define the GRPC interface. +v2 go files will also be created in `plugin/clientmgmt/volumesnapshotter` and `plugin/framework/volumesnapshotter`, and a new PluginKind will be created. +The velero Backup process will be modified to reference v2 plugins instead of v1 plugins. +An adapter will be created so that any existing VolumeSnapshotter v1 plugin can be executed as a v2 plugin when executing a backup. + +## Detailed Design + +### proto changes (compiled into golang by protoc) + +The v2 VolumeSnapshotter.proto will be like the current v1 version with the following changes: +The VolumeSnapshotter service gets two new rpc methods: +``` +service VolumeSnapshotter { + rpc Init(VolumeSnapshotterInitRequest) returns (Empty); + rpc CreateVolumeFromSnapshot(CreateVolumeRequest) returns (CreateVolumeResponse); + rpc GetVolumeInfo(GetVolumeInfoRequest) returns (GetVolumeInfoResponse); + rpc CreateSnapshot(CreateSnapshotRequest) returns (CreateSnapshotResponse); + rpc DeleteSnapshot(DeleteSnapshotRequest) returns (Empty); + rpc GetVolumeID(GetVolumeIDRequest) returns (GetVolumeIDResponse); + rpc SetVolumeID(SetVolumeIDRequest) returns (SetVolumeIDResponse); + rpc Progress(VolumeSnapshotterProgressRequest) returns (VolumeSnapshotterProgressResponse); + rpc Cancel(VolumeSnapshotterCancelRequest) returns (google.protobuf.Empty); +} +``` +To support these new rpc methods, we define new request/response message types: +``` +message VolumeSnapshotterProgressRequest { + string plugin = 1; + string snapshotID = 2; +} + +message VolumeSnapshotterProgressResponse { + generated.OperationProgress progress = 1; +} + +message VolumeSnapshotterCancelRequest { + string plugin = 1; + string operationID = 2; +} + +``` +One new shared message type will be needed, as defined in the v2 BackupItemAction design: +``` +message OperationProgress { + bool completed = 1; + string err = 2; + int64 completed = 3; + int64 total = 4; + string operationUnits = 5; + string description = 6; + google.protobuf.Timestamp started = 7; + google.protobuf.Timestamp updated = 8; +} +``` + +A new PluginKind, `VolumeSnapshotterV2`, will be created, and the backup process will be modified to use this plugin kind. +See [Plugin Versioning](plugin-versioning.md) for more details on implementation plans, including v1 adapters, etc. + + +## Compatibility +The included v1 adapter will allow any existing VolumeSnapshotter plugin to work as expected, with no-op Progress() and Cancel() methods. + +## Implementation +This will be implemented during the Velero 1.11 development cycle. From 807ba7e90263f577fbd99e9656dd525e84d5582e Mon Sep 17 00:00:00 2001 From: Lukas Hass Date: Wed, 30 Nov 2022 15:22:06 +0100 Subject: [PATCH 05/20] Fix links to compatibility matrix Signed-off-by: Lukas Hass --- site/content/docs/main/basic-install.md | 2 +- site/content/docs/main/upgrade-to-1.10.md | 4 ++-- site/content/docs/v1.10/basic-install.md | 2 +- site/content/docs/v1.10/upgrade-to-1.10.md | 4 ++-- site/content/docs/v1.4/basic-install.md | 2 +- site/content/docs/v1.4/upgrade-to-1.4.md | 2 +- site/content/docs/v1.7/basic-install.md | 2 +- site/content/docs/v1.7/upgrade-to-1.7.md | 2 +- site/content/docs/v1.9/basic-install.md | 2 +- site/content/docs/v1.9/upgrade-to-1.9.md | 4 ++-- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/site/content/docs/main/basic-install.md b/site/content/docs/main/basic-install.md index bb8bd7f38..b84afcdde 100644 --- a/site/content/docs/main/basic-install.md +++ b/site/content/docs/main/basic-install.md @@ -8,7 +8,7 @@ Refer [this document](customize-installation.md) to customize your installation. ## Prerequisites -- Access to a Kubernetes cluster, v1.16 or later, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero [compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatabilty-matrix). +- Access to a Kubernetes cluster, v1.16 or later, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero [compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix). - `kubectl` installed locally Velero uses object storage to store backups and associated artifacts. It also optionally integrates with supported block storage systems to snapshot your persistent volumes. Before beginning the installation process, you should identify the object storage provider and optional block storage provider(s) you'll be using from the list of [compatible providers][0]. diff --git a/site/content/docs/main/upgrade-to-1.10.md b/site/content/docs/main/upgrade-to-1.10.md index 1eb8056ab..9887f6fb1 100644 --- a/site/content/docs/main/upgrade-to-1.10.md +++ b/site/content/docs/main/upgrade-to-1.10.md @@ -15,7 +15,7 @@ If you're not yet running at least Velero v1.6, see the following: - [Upgrading to v1.8][4] - [Upgrading to v1.9][5] -Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatabilty-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. +Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. ## Instructions @@ -94,4 +94,4 @@ If upgraded from v1.9.x, there still remains some resources left over in the clu [2]: https://velero.io/docs/v1.6/upgrade-to-1.6 [3]: https://velero.io/docs/v1.7/upgrade-to-1.7 [4]: https://velero.io/docs/v1.8/upgrade-to-1.8 -[5]: https://velero.io/docs/v1.9/upgrade-to-1.9 \ No newline at end of file +[5]: https://velero.io/docs/v1.9/upgrade-to-1.9 diff --git a/site/content/docs/v1.10/basic-install.md b/site/content/docs/v1.10/basic-install.md index bb8bd7f38..b84afcdde 100644 --- a/site/content/docs/v1.10/basic-install.md +++ b/site/content/docs/v1.10/basic-install.md @@ -8,7 +8,7 @@ Refer [this document](customize-installation.md) to customize your installation. ## Prerequisites -- Access to a Kubernetes cluster, v1.16 or later, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero [compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatabilty-matrix). +- Access to a Kubernetes cluster, v1.16 or later, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero [compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix). - `kubectl` installed locally Velero uses object storage to store backups and associated artifacts. It also optionally integrates with supported block storage systems to snapshot your persistent volumes. Before beginning the installation process, you should identify the object storage provider and optional block storage provider(s) you'll be using from the list of [compatible providers][0]. diff --git a/site/content/docs/v1.10/upgrade-to-1.10.md b/site/content/docs/v1.10/upgrade-to-1.10.md index 1eb8056ab..9887f6fb1 100644 --- a/site/content/docs/v1.10/upgrade-to-1.10.md +++ b/site/content/docs/v1.10/upgrade-to-1.10.md @@ -15,7 +15,7 @@ If you're not yet running at least Velero v1.6, see the following: - [Upgrading to v1.8][4] - [Upgrading to v1.9][5] -Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatabilty-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. +Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. ## Instructions @@ -94,4 +94,4 @@ If upgraded from v1.9.x, there still remains some resources left over in the clu [2]: https://velero.io/docs/v1.6/upgrade-to-1.6 [3]: https://velero.io/docs/v1.7/upgrade-to-1.7 [4]: https://velero.io/docs/v1.8/upgrade-to-1.8 -[5]: https://velero.io/docs/v1.9/upgrade-to-1.9 \ No newline at end of file +[5]: https://velero.io/docs/v1.9/upgrade-to-1.9 diff --git a/site/content/docs/v1.4/basic-install.md b/site/content/docs/v1.4/basic-install.md index a553bafa9..912beb225 100644 --- a/site/content/docs/v1.4/basic-install.md +++ b/site/content/docs/v1.4/basic-install.md @@ -8,7 +8,7 @@ Refer [this document](customize-installation.md) to customize your installation. ## Prerequisites -- Access to a Kubernetes cluster, v1.10-v1.21, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero [compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatabilty-matrix). +- Access to a Kubernetes cluster, v1.10-v1.21, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero [compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix). - `kubectl` installed locally Velero uses object storage to store backups and associated artifacts. It also optionally integrates with supported block storage systems to snapshot your persistent volumes. Before beginning the installation process, you should identify the object storage provider and optional block storage provider(s) you'll be using from the list of [compatible providers][0]. diff --git a/site/content/docs/v1.4/upgrade-to-1.4.md b/site/content/docs/v1.4/upgrade-to-1.4.md index 7559b1791..a173d67ad 100644 --- a/site/content/docs/v1.4/upgrade-to-1.4.md +++ b/site/content/docs/v1.4/upgrade-to-1.4.md @@ -15,7 +15,7 @@ If you're not yet running at least Velero v1.3, see the following: - [Upgrading to v1.2][2] - [Upgrading to v1.3][3] -Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatabilty-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. +Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. ## Instructions diff --git a/site/content/docs/v1.7/basic-install.md b/site/content/docs/v1.7/basic-install.md index efa082119..c74398745 100644 --- a/site/content/docs/v1.7/basic-install.md +++ b/site/content/docs/v1.7/basic-install.md @@ -8,7 +8,7 @@ Refer [this document](customize-installation.md) to customize your installation. ## Prerequisites -- Access to a Kubernetes cluster, v1.12 or later, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero [compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatabilty-matrix). +- Access to a Kubernetes cluster, v1.12 or later, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero [compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix). - `kubectl` installed locally Velero uses object storage to store backups and associated artifacts. It also optionally integrates with supported block storage systems to snapshot your persistent volumes. Before beginning the installation process, you should identify the object storage provider and optional block storage provider(s) you'll be using from the list of [compatible providers][0]. diff --git a/site/content/docs/v1.7/upgrade-to-1.7.md b/site/content/docs/v1.7/upgrade-to-1.7.md index 6cf7748c6..2e3a37907 100644 --- a/site/content/docs/v1.7/upgrade-to-1.7.md +++ b/site/content/docs/v1.7/upgrade-to-1.7.md @@ -16,7 +16,7 @@ If you're not yet running at least Velero v1.6, see the following: - [Upgrading to v1.5][5] - [Upgrading to v1.6][6] -Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatabilty-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. +Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. ## Instructions diff --git a/site/content/docs/v1.9/basic-install.md b/site/content/docs/v1.9/basic-install.md index 080b27b2f..db54dcc55 100644 --- a/site/content/docs/v1.9/basic-install.md +++ b/site/content/docs/v1.9/basic-install.md @@ -8,7 +8,7 @@ Refer [this document](customize-installation.md) to customize your installation. ## Prerequisites -- Access to a Kubernetes cluster, v1.16 or later, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero [compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatabilty-matrix). +- Access to a Kubernetes cluster, v1.16 or later, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero [compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix). - `kubectl` installed locally Velero uses object storage to store backups and associated artifacts. It also optionally integrates with supported block storage systems to snapshot your persistent volumes. Before beginning the installation process, you should identify the object storage provider and optional block storage provider(s) you'll be using from the list of [compatible providers][0]. diff --git a/site/content/docs/v1.9/upgrade-to-1.9.md b/site/content/docs/v1.9/upgrade-to-1.9.md index 25186dcd2..cfec63277 100644 --- a/site/content/docs/v1.9/upgrade-to-1.9.md +++ b/site/content/docs/v1.9/upgrade-to-1.9.md @@ -18,7 +18,7 @@ If you're not yet running at least Velero v1.6, see the following: - [Upgrading to v1.7][7] - [Upgrading to v1.8][8] -Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatabilty-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. +Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. ## Instructions @@ -91,4 +91,4 @@ After upgrading, if there is a previously created backup storage location with t [6]: https://velero.io/docs/v1.6/upgrade-to-1.6 [7]: https://velero.io/docs/v1.7/upgrade-to-1.7 [8]: https://velero.io/docs/v1.8/upgrade-to-1.8 -[9]: https://velero.io/docs/v1.9/locations \ No newline at end of file +[9]: https://velero.io/docs/v1.9/locations From 2f3732fa44b14407dfa6e2e546f794a9fdb62df7 Mon Sep 17 00:00:00 2001 From: Ming Date: Fri, 2 Dec 2022 06:47:41 +0000 Subject: [PATCH 06/20] Fix CVEs scanned by trivy Signed-off-by: Ming --- Makefile | 2 +- changelogs/unreleased/5653-qiuming-best | 1 + go.mod | 8 ++++---- go.sum | 14 ++++++++------ 4 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 changelogs/unreleased/5653-qiuming-best diff --git a/Makefile b/Makefile index 1525a7b11..4b42e42d6 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ see: https://velero.io/docs/main/build-from-source/#making-images-and-updating-v endef # The version of restic binary to be downloaded -RESTIC_VERSION ?= 0.13.1 +RESTIC_VERSION ?= 0.14.0 CLI_PLATFORMS ?= linux-amd64 linux-arm linux-arm64 darwin-amd64 darwin-arm64 windows-amd64 linux-ppc64le BUILDX_PLATFORMS ?= $(subst -,/,$(ARCH)) diff --git a/changelogs/unreleased/5653-qiuming-best b/changelogs/unreleased/5653-qiuming-best new file mode 100644 index 000000000..ea1a97185 --- /dev/null +++ b/changelogs/unreleased/5653-qiuming-best @@ -0,0 +1 @@ +Fix CVEs scanned by trivy diff --git a/go.mod b/go.mod index a6a7e6129..8107fe7a0 100644 --- a/go.mod +++ b/go.mod @@ -34,8 +34,8 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.1 github.com/vmware-tanzu/crash-diagnostics v0.3.7 - golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 - golang.org/x/net v0.0.0-20220615171555-694bf12d69de + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 + golang.org/x/net v0.0.0-20220906165146-f3363e06e74c golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/api v0.74.0 @@ -132,9 +132,9 @@ require ( go.uber.org/zap v1.21.0 // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect golang.org/x/exp v0.0.0-20210916165020-5cb4fee858ee // indirect - golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 // indirect + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/text v0.3.8 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect diff --git a/go.sum b/go.sum index 520335524..e5f0f99e9 100644 --- a/go.sum +++ b/go.sum @@ -842,8 +842,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -904,8 +905,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220615171555-694bf12d69de h1:ogOG2+P6LjO2j55AkRScrkB2BFpd+Z8TY2wcM0Z3MGo= -golang.org/x/net v0.0.0-20220615171555-694bf12d69de/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo= +golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1027,8 +1028,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 h1:PgOr27OhUx2IRqGJ2RxAWI4dJQ7bi9cSrB82uzFzfUA= -golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1043,8 +1044,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 3a9ff2256b9e8d3bc600dc84818ba0e7db0fa8e6 Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Mon, 5 Dec 2022 17:40:38 +0800 Subject: [PATCH 07/20] Update Velero and k8s version compatibility matrix. Due to CSIDriver is checked for Restic volume mounting path, and CSIDriver is GA and moved to storage v1 group in k8s v1.18, so update Velero v1.8, v1.9 and v1.10 compatible k8s version to 1.18-latest. Signed-off-by: Xun Jiang --- 1 | 0 README.md | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 1 diff --git a/1 b/1 deleted file mode 100644 index e69de29bb..000000000 diff --git a/README.md b/README.md index 92e496d3b..3b3d5475e 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ The following is a list of the supported Kubernetes versions for each Velero ver | Velero version | Expected Kubernetes version compatibility| Tested on Kubernetes version| |----------------|--------------------|--------------------| -| 1.10 | 1.16-latest | 1.22.5, 1.23.8, 1.24.6 and 1.25.1 | -| 1.9 | 1.16-latest | 1.20.5, 1.21.2, 1.22.5, 1.23, and 1.24 | -| 1.8 | 1.16-latest | | +| 1.10 | 1.18-latest | 1.22.5, 1.23.8, 1.24.6 and 1.25.1 | +| 1.9 | 1.18-latest | 1.20.5, 1.21.2, 1.22.5, 1.23, and 1.24 | +| 1.8 | 1.18-latest | | | 1.6.3-1.7.1 | 1.12-latest || | 1.60-1.6.2 | 1.12-1.21 || | 1.5 | 1.12-1.21 || From 55873c1c37c663847a4abaaa5bf9ecbe20185d31 Mon Sep 17 00:00:00 2001 From: Dylan Murray Date: Tue, 13 Dec 2022 00:45:52 -0500 Subject: [PATCH 08/20] Prevent nil panic on exec restore hooks (#5675) * Prevent nil panic on exec restore hooks Signed-off-by: Dylan Murray --- changelogs/unreleased/5675-dymurray | 1 + pkg/controller/restore_controller.go | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/5675-dymurray diff --git a/changelogs/unreleased/5675-dymurray b/changelogs/unreleased/5675-dymurray new file mode 100644 index 000000000..a7162e48a --- /dev/null +++ b/changelogs/unreleased/5675-dymurray @@ -0,0 +1 @@ +Prevent nil panic on exec restore hooks diff --git a/pkg/controller/restore_controller.go b/pkg/controller/restore_controller.go index 038e97109..9e2eb31d7 100644 --- a/pkg/controller/restore_controller.go +++ b/pkg/controller/restore_controller.go @@ -342,10 +342,12 @@ func (c *restoreController) validateAndComplete(restore *api.Restore, pluginMana } for _, resource := range restoreHooks { for _, h := range resource.RestoreHooks { - for _, container := range h.Init.InitContainers { - err = hook.ValidateContainer(container.Raw) - if err != nil { - restore.Status.ValidationErrors = append(restore.Status.ValidationErrors, err.Error()) + if h.Init != nil { + for _, container := range h.Init.InitContainers { + err = hook.ValidateContainer(container.Raw) + if err != nil { + restore.Status.ValidationErrors = append(restore.Status.ValidationErrors, err.Error()) + } } } } From 69da593f3735ae875014d73c3105ecc73debbf88 Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Thu, 15 Dec 2022 16:32:18 -0500 Subject: [PATCH 09/20] Minor updates to async action monitoring design: 1) clarification around Cancel() return values 2) updates to itemoperation json upload to account for progress 3) update to OperationProgress struct to avoid duplicate parameter 4) update new B/R phase name to WaitingForPluginOperationsPartiallyFailed for consistency Signed-off-by: Scott Seago --- design/general-progress-monitoring.md | 67 ++++++++++++++++----------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/design/general-progress-monitoring.md b/design/general-progress-monitoring.md index 43731c08d..cb627d822 100644 --- a/design/general-progress-monitoring.md +++ b/design/general-progress-monitoring.md @@ -145,7 +145,7 @@ allow for data movement to be handled in the "WaitingForPluginOperations" phase, The next phase is either "Completed", "WaitingForPluginOperations", "Failed" or "PartiallyFailed". Backups/restores which would have a final phase of "Completed" or "PartiallyFailed" may move to the -"WaitingForPluginOperations" or "WaitingForPluginOperationsPartialFailure" state. A backup/restore +"WaitingForPluginOperations" or "WaitingForPluginOperationsPartiallyFailed" state. A backup/restore which will be marked "Failed" will go directly to the "Failed" phase. Uploads may continue in the background for snapshots that were taken by a "Failed" backup/restore, but no progress will not be monitored or updated. If there are any operations in progress when a backup is moved to the "Failed" @@ -157,14 +157,14 @@ any uploads still in progress should be aborted. The "WaitingForPluginOperations" phase signifies that the main part of the backup/restore, including snapshotting has completed successfully and uploading and any other asynchronous BIA/RIA plugin operations are continuing. In the event of an error during this phase, the phase will change to -WaitingForPluginOperationsPartialFailure. On success, the phase changes to Completed. Backups +WaitingForPluginOperationsPartiallyFailed. On success, the phase changes to Completed. Backups cannot be restored from when they are in the WaitingForPluginOperations state. -### WaitingForPluginOperationsPartialFailure (new) -The "WaitingForPluginOperationsPartialFailure" phase signifies that the main part of the +### WaitingForPluginOperationsPartiallyFailed (new) +The "WaitingForPluginOperationsPartiallyFailed" phase signifies that the main part of the backup/restore, including snapshotting has completed, but there were partial failures either during the main part or during any async operations, including snapshot uploads. Backups cannot be -restored from when they are in the WaitingForPluginOperationsPartialFailure state. +restored from when they are in the WaitingForPluginOperationsPartiallyFailed state. ### Failed When a backup/restore has had fatal errors it is marked as "Failed" Backups in this state cannot be @@ -203,20 +203,20 @@ Velero will poll the plugins for status on the operations when the backup/restor "InProgress" phase and has no fatal errors. If any operations are not complete, the backup/restore will move to either WaitingForPluginOperations -or WaitingForPluginOperationsPartialFailure or Failed. +or WaitingForPluginOperationsPartiallyFailed or Failed. Post-snapshot and other operations may take a long time and Velero and its plugins may be restarted during this time. Once a backup/restore has moved into the WaitingForPluginOperations or -WaitingForPluginOperationsPartialFailure phase, another backup/restore may be started. +WaitingForPluginOperationsPartiallyFailed phase, another backup/restore may be started. -While in the WaitingForPluginOperations or WaitingForPluginOperationsPartialFailure phase, the +While in the WaitingForPluginOperations or WaitingForPluginOperationsPartiallyFailed phase, the snapshots and item actions will be periodically polled. When all of the snapshots and item actions have reported success, the backup/restore will move to the Completed or PartiallyFailed phase, depending on whether the backup/restore was in the WaitingForPluginOperations or -WaitingForPluginOperationsPartialFailure phase. +WaitingForPluginOperationsPartiallyFailed phase. The Backup resources will not be written to object storage until the backup has entered a final phase: -Completed, Failed or PartialFailure +Completed, Failed or PartiallyFailed ## Reconciliation of InProgress backups @@ -231,7 +231,7 @@ ignored. type OperationProgress struct { Completed bool // True when the operation has completed, either successfully or with a failure Err string // Set when the operation has failed - Completed, Total int64 // Quantity completed so far and the total quanity associated with the operaation in operationUnits + NCompleted, NTotal int64 // Quantity completed so far and the total quanity associated with the operaation in operationUnits // For data mover and volume snapshotter use cases, this would be in bytes // On successful completion, completed and total should be the same. OperationUnits string // Units represented by completed and total -- for data mover and item @@ -267,14 +267,17 @@ process/controller so we will modify BackupItemAction and RestoreItemAction to o operationID in addition to the modified item. Velero can attempt to cancel an operation by calling the Cancel API call on the BIA/RIA. The plugin -can then take any appropriate action as needed. Cancel will be called on backup deletion, and -possibly upon reaching timeouts, if timeout support is included in this feature. Cancel is not -intended to be used to delete/remove the results of completed actions. +can then take any appropriate action as needed. Cancel will be called for unfinished operations on +backup deletion, and possibly reaching timeout. Cancel is not intended to be used to delete/remove +the results of completed actions and will have no effect on a completed action. Cancel has no return +value apart from the standard Error return, but this should only be used for unexpected +failures. Under normal operations, Cancel will simply return a nil error (and nothing else), whether +or not the plugin is able to cancel the operation. -_AsyncOperationsNotSupportedError_ should only be returned (by either Cancel or by Progress) if the +_AsyncOperationsNotSupportedError_ should only be returned (by Progress) if the Backup/RestoreItemAction plugin should not be handling the item. If the Backup/RestoreItemAction plugin should handle the item but, for example, the item/snapshot ID cannot be found to report -progress, Progress or Cancel will return an InvalidOperationIDError error rather than a populated +progress, Progress will return an InvalidOperationIDError error rather than a populated OperationProgress struct. If the item action does not start an asynchronous operation, then operationID will be empty. @@ -368,9 +371,17 @@ of what a record for a datamover plugin might look like: ``` { "itemOperation": { - "plugin": "velero.io/datamover-backup", - "itemID": "", - "operationID": "" + "plugin": "velero.io/datamover-backup", + "itemID": "", + "operationID": "", + "completed": true, + "err": "", + "NCompleted": 12345, + "NTotal": 12345, + "OperationUnits": "byte", + "Description": "", + "Started": "2022-12-14T12:01:00Z", + "Updated": "2022-12-14T12:11:02Z" } } ``` @@ -424,8 +435,8 @@ will be completed and the backup will move to the appropriate terminal phase and If any of the snapshots or backup items are still being processed, the phase of the backup will be set to the appropriate phase (_WaitingForPluginOperations_ or -_WaitingForPluginOperationsPartialFailure_). In the event of any of the progress checks return an -error, the phase will move to _WaitingForPluginOperationsPartialFailure_. The backup will then be +_WaitingForPluginOperationsPartiallyFailed_). In the event of any of the progress checks return an +error, the phase will move to _WaitingForPluginOperationsPartiallyFailed_. The backup will then be requeued and will be rechecked again after some time has passed. ## Restore workflow changes @@ -440,16 +451,16 @@ completed and the restore will move to the appropriate terminal phase and the re complete. If any of the restore items are still being processed, the phase of the restore will be set to the -appropriate phase (_WaitingForPluginOperations_ or _WaitingForPluginOperationsPartialFailure_). In +appropriate phase (_WaitingForPluginOperations_ or _WaitingForPluginOperationsPartiallyFailed_). In the event of any of the progress checks return an error, the phase will move to -_WaitingForPluginOperationsPartialFailure_. The restore will then be requeued and will be rechecked +_WaitingForPluginOperationsPartiallyFailed_. The restore will then be requeued and will be rechecked again after some time has passed. ## Restart workflow On restart, the Velero server will scan all Backup/Restore resources. Any Backup/Restore resources which are in the _InProgress_ phase will be moved to the _Failed_ phase. Any Backup/Restore -resources in the _WaitingForPluginOperations_ or _WaitingForPluginOperationsPartialFailure_ phase +resources in the _WaitingForPluginOperations_ or _WaitingForPluginOperationsPartiallyFailed_ phase will be treated as if they have been requeued and progress checked and the backup/restore will be requeued or moved to a terminal phase as appropriate. @@ -459,9 +470,9 @@ Since this design is modifying a previously-approved design, there is some prepa the earlier upload progress monitoring design that may need modification as a result of these updates. Here is a list of some of these items: -1. Consts for the "Uploading" and "UploadingPartialFailure" phases have already been defined. These +1. Consts for the "Uploading" and "UploadingPartiallyFailed" phases have already been defined. These will need to be removed when the "WaitingForPluginOperations" and - "WaitingForPluginOperationsPartialFailure" phases are defined. + "WaitingForPluginOperationsPartiallyFailed" phases are defined. - https://github.com/vmware-tanzu/velero/pull/3805 1. Remove the ItemSnapshotter plugin APIs (and related code) since the revised design will reuse VolumeSnapshotter and BackupItemAction plugins. @@ -507,9 +518,9 @@ vSphere plug-in BackupItemAction Progress implementation (vSphere plug-in team) required custom cancellation actions, now would be a good time to perform them. For snapshot uploads that are already in proress, there's not really anything else to cancel. 2. Should we actually write the backup *before* moving to the WaitingForPluginOperations or - WaitingForPluginOperationsPartialFailure phase rather than waiting until all operations + WaitingForPluginOperationsPartiallyFailed phase rather than waiting until all operations have completed? The operations in question won't affect what gets written to object storage for the backup, and since we've already written the list of operations we're waiting for to object storage, writing the backup now would make the process resilient to Velero restart if - it happens during WaitingForPluginOperations or WaitingForPluginOperationsPartialFailure + it happens during WaitingForPluginOperations or WaitingForPluginOperationsPartiallyFailed From 3b903e678fe91c858c1f7d2726e9090d746278e1 Mon Sep 17 00:00:00 2001 From: Ming Date: Tue, 20 Dec 2022 02:40:29 +0000 Subject: [PATCH 10/20] [Cherry-Pick]Fix error with Restic backup empty volumes Signed-off-by: Ming --- changelogs/unreleased/5713-qiuming-best | 1 + pkg/uploader/provider/restic.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/5713-qiuming-best diff --git a/changelogs/unreleased/5713-qiuming-best b/changelogs/unreleased/5713-qiuming-best new file mode 100644 index 000000000..c63b47030 --- /dev/null +++ b/changelogs/unreleased/5713-qiuming-best @@ -0,0 +1 @@ +Fix error with Restic backup empty volumes diff --git a/pkg/uploader/provider/restic.go b/pkg/uploader/provider/restic.go index 00fe42057..70ae4823c 100644 --- a/pkg/uploader/provider/restic.go +++ b/pkg/uploader/provider/restic.go @@ -137,7 +137,7 @@ func (rp *resticProvider) RunBackup( summary, stderrBuf, err := restic.RunBackup(backupCmd, log, updater) if err != nil { - if strings.Contains(err.Error(), "snapshot is empty") { + if strings.Contains(stderrBuf, "snapshot is empty") { log.Debugf("Restic backup got empty dir with %s path", path) return "", true, nil } From 32eb8655cc05350e7a35699d67f677994e0c587a Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Wed, 21 Dec 2022 12:27:50 +0800 Subject: [PATCH 11/20] fix issue 5696 Signed-off-by: Lyndon-Li --- changelogs/unreleased/5715-Lyndon-Li | 1 + pkg/repository/manager.go | 10 ++++++++ pkg/repository/provider/provider.go | 5 ++++ pkg/repository/provider/restic.go | 4 ++++ pkg/repository/provider/unified_repo.go | 31 +++++++++++++++++++++++++ 5 files changed, 51 insertions(+) create mode 100644 changelogs/unreleased/5715-Lyndon-Li diff --git a/changelogs/unreleased/5715-Lyndon-Li b/changelogs/unreleased/5715-Lyndon-Li new file mode 100644 index 000000000..05d9c8db1 --- /dev/null +++ b/changelogs/unreleased/5715-Lyndon-Li @@ -0,0 +1 @@ +Fix issue 5696, check if the repo is still openable before running the prune and forget operation, if not, try to reconnect the repo \ No newline at end of file diff --git a/pkg/repository/manager.go b/pkg/repository/manager.go index fae6877dc..c8cabdd81 100644 --- a/pkg/repository/manager.go +++ b/pkg/repository/manager.go @@ -172,6 +172,11 @@ func (m *manager) PruneRepo(repo *velerov1api.BackupRepository) error { if err != nil { return errors.WithStack(err) } + + if err := prd.BoostRepoConnect(context.Background(), param); err != nil { + return errors.WithStack(err) + } + return prd.PruneRepo(context.Background(), param) } @@ -207,6 +212,11 @@ func (m *manager) Forget(ctx context.Context, snapshot SnapshotIdentifier) error if err != nil { return errors.WithStack(err) } + + if err := prd.BoostRepoConnect(context.Background(), param); err != nil { + return errors.WithStack(err) + } + return prd.Forget(context.Background(), snapshot.SnapshotID, param) } diff --git a/pkg/repository/provider/provider.go b/pkg/repository/provider/provider.go index 6579386d6..4b76830f5 100644 --- a/pkg/repository/provider/provider.go +++ b/pkg/repository/provider/provider.go @@ -43,6 +43,11 @@ type Provider interface { // is already initialized, or do nothing if the repository is already connected PrepareRepo(ctx context.Context, param RepoParam) error + // BoostRepoConnect is used to re-ensure the local connection to the repo, + // so that the followed operations could succeed in some environment reset + // scenarios, for example, pod restart + BoostRepoConnect(ctx context.Context, param RepoParam) error + // PruneRepo does a full prune/maintenance of the repository PruneRepo(ctx context.Context, param RepoParam) error diff --git a/pkg/repository/provider/restic.go b/pkg/repository/provider/restic.go index 65038f0f2..a77c950be 100644 --- a/pkg/repository/provider/restic.go +++ b/pkg/repository/provider/restic.go @@ -62,6 +62,10 @@ func (r *resticRepositoryProvider) PrepareRepo(ctx context.Context, param RepoPa return nil } +func (r *resticRepositoryProvider) BoostRepoConnect(ctx context.Context, param RepoParam) error { + return nil +} + func (r *resticRepositoryProvider) PruneRepo(ctx context.Context, param RepoParam) error { return r.svc.PruneRepo(param.BackupLocation, param.BackupRepo) } diff --git a/pkg/repository/provider/unified_repo.go b/pkg/repository/provider/unified_repo.go index 2ba1c7c0d..9161e0621 100644 --- a/pkg/repository/provider/unified_repo.go +++ b/pkg/repository/provider/unified_repo.go @@ -200,6 +200,37 @@ func (urp *unifiedRepoProvider) PrepareRepo(ctx context.Context, param RepoParam return nil } +func (urp *unifiedRepoProvider) BoostRepoConnect(ctx context.Context, param RepoParam) error { + log := urp.log.WithFields(logrus.Fields{ + "BSL name": param.BackupLocation.Name, + "repo name": param.BackupRepo.Name, + "repo UID": param.BackupRepo.UID, + }) + + log.Debug("Start to boost repo connect") + + repoOption, err := udmrepo.NewRepoOptions( + udmrepo.WithPassword(urp, param), + udmrepo.WithConfigFile(urp.workPath, string(param.BackupRepo.UID)), + udmrepo.WithDescription(repoConnectDesc), + ) + + if err != nil { + return errors.Wrap(err, "error to get repo options") + } + + bkRepo, err := urp.repoService.Open(ctx, *repoOption) + if err == nil { + if c := bkRepo.Close(ctx); c != nil { + log.WithError(c).Error("Failed to close repo") + } + + return nil + } + + return urp.ConnectToRepo(ctx, param) +} + func (urp *unifiedRepoProvider) PruneRepo(ctx context.Context, param RepoParam) error { log := urp.log.WithFields(logrus.Fields{ "BSL name": param.BackupLocation.Name, From 544df59f58fbbade440bc4bfee3d3cdc24b6a387 Mon Sep 17 00:00:00 2001 From: "haining.cao" Date: Wed, 21 Dec 2022 18:54:45 +0800 Subject: [PATCH 12/20] fix json serialize name Signed-off-by: haining.cao --- pkg/volume/snapshot.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/volume/snapshot.go b/pkg/volume/snapshot.go index eb115e8cb..747440114 100644 --- a/pkg/volume/snapshot.go +++ b/pkg/volume/snapshot.go @@ -37,7 +37,7 @@ type SnapshotSpec struct { Location string `json:"location"` // PersistentVolumeName is the Kubernetes name for the volume. - PersistentVolumeName string `json:persistentVolumeName` + PersistentVolumeName string `json:"persistentVolumeName"` // ProviderVolumeID is the provider's ID for the volume. ProviderVolumeID string `json:"providerVolumeID"` From 2abb176bd84da96686746e908a190ca46ed12a44 Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Fri, 16 Dec 2022 17:22:02 -0500 Subject: [PATCH 13/20] backup/restore phases for async plugin operations Signed-off-by: Scott Seago --- changelogs/unreleased/5710-sseago | 4 ++++ config/crd/v1/bases/velero.io_backups.yaml | 2 ++ config/crd/v1/bases/velero.io_restores.yaml | 2 ++ config/crd/v1/crds/crds.go | 4 ++-- pkg/apis/velero/v1/backup.go | 24 ++++++++++++-------- pkg/apis/velero/v1/restore.go | 15 +++++++++++- site/content/docs/main/api-types/backup.md | 4 +++- site/content/docs/main/api-types/restore.md | 4 +++- site/content/docs/main/api-types/schedule.md | 3 ++- 9 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 changelogs/unreleased/5710-sseago diff --git a/changelogs/unreleased/5710-sseago b/changelogs/unreleased/5710-sseago new file mode 100644 index 000000000..3146c1185 --- /dev/null +++ b/changelogs/unreleased/5710-sseago @@ -0,0 +1,4 @@ +new backup and restore phases to support async plugin operations: +- WaitingForPluginOperations +- WaitingForPluginOperationsPartiallyFailed + diff --git a/config/crd/v1/bases/velero.io_backups.yaml b/config/crd/v1/bases/velero.io_backups.yaml index 773940789..515a7d051 100644 --- a/config/crd/v1/bases/velero.io_backups.yaml +++ b/config/crd/v1/bases/velero.io_backups.yaml @@ -455,6 +455,8 @@ spec: - New - FailedValidation - InProgress + - WaitingForPluginOperations + - WaitingForPluginOperationsPartiallyFailed - Completed - PartiallyFailed - Failed diff --git a/config/crd/v1/bases/velero.io_restores.yaml b/config/crd/v1/bases/velero.io_restores.yaml index 181a4e5fa..20fe68d66 100644 --- a/config/crd/v1/bases/velero.io_restores.yaml +++ b/config/crd/v1/bases/velero.io_restores.yaml @@ -412,6 +412,8 @@ spec: - New - FailedValidation - InProgress + - WaitingForPluginOperations + - WaitingForPluginOperationsPartiallyFailed - Completed - PartiallyFailed - Failed diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index c83d1071f..65a337806 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -30,13 +30,13 @@ import ( var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WAo\xdc6\x13\xbd\xebW\f\xf2\x1dr\xf9\xa4M\xd0C\v\xddR\xb7\x05\x82&\x86a\a\xbe\x14=P\xe4\xec.c\x8ad\xc9\xe1\xa6ۢ\xff\xbd\x18R\xf2j%\xd9\x1b\a\xa8n\"\x87of\xde\xcc\x1bQU]ו\xf0\xfa\x1eC\xd4ζ \xbc\xc6?\t-\xbf\xc5\xe6\xe1\x87\xd8h\xb79\xbc\xad\x1e\xb4U-\\\xa5H\xae\xbf\xc5\xe8R\x90\xf8\x13n\xb5դ\x9d\xadz$\xa1\x04\x89\xb6\x02\x10\xd6:\x12\xbc\x1c\xf9\x15@:K\xc1\x19\x83\xa1ޡm\x1eR\x87]\xd2Fa\xc8\xe0\xa3\xebÛ\xe6\xfb\xe6M\x05 \x03\xe6\xe3\x9ft\x8f\x91D\xef[\xb0ɘ\n\xc0\x8a\x1e[\xe8\x84|H>\xa0wQ\x93\v\x1acs@\x83\xc15\xdaUѣd\xb7\xbb\xe0\x92o\xe1\xb4QN\x0f!\x95t~\xcc@\xb7#\xd01o\x19\x1d\xe9\xd7\xd5\xed\x0f:R6\xf1&\x05a\xd6\x02\xc9\xdbQ\xdb]2\",\f\xd8A\x94\xcec\v\xd7\x1c\x8b\x17\x12U\x050P\x90c\xabA(\x95I\x15\xe6&hK\x18\xae\x9cI\xfdHf\r\x9f\xa3\xb37\x82\xf6-4#\xed͂\xb2l;\x12\xf6n\x87\xc3;\x1dٹ\x12\x84K0f\xae9\xc5\xfa\xe9\xe8\xf1\f\xe5D\x04L\xf6\nb\xa4\xa0\xed\xae:\x19\x1f\xde\x16*\xe4\x1e{\xd1\x0e\xb6Σ}w\xf3\xfe\xfe\xbb\xbb\xb3e\x00\x1f\x9c\xc7@z,Oy&}9Y\x05P\x18eОr\u05fcf\xc0b\x05\x8a\x1b\x12#\xd0\x1eGNQ\r1\x80\xdb\x02\xedu\x84\x80>`D[Z\xf4\f\x18\xd8HXp\xddg\x94\xd4\xc0\x1d\x06\x86\x81\xb8w\xc9(\xee\xe3\x03\x06\x82\x80\xd2\xed\xac\xfe\xeb\x11;\x02\xb9\xec\xd4\b¡GNO\xae\xa1\x15\x06\x0e\xc2$\xfc?\b\xab\xa0\x17G\b\xc8^ \xd9\t^6\x89\r|t\x01AۭkaO\xe4c\xbb\xd9\xec4\x8dz\x94\xae\xef\x93\xd5t\xdcdi\xe9.\x91\vq\xa3\xf0\x80f\x13\xf5\xae\x16A\xee5\xa1\xa4\x14p#\xbc\xaes\xe86k\xb2\xe9\xd5\xff\u00a0\xe0\xf8\xfa,\xd6E-˓\xc5\xf2L\x05X-\xa0#\x88\xe1h\xc9\xe2D4/1;\xb7?\xdf}\x82\xd1u.Ɯ\xfd\xcc\xfb\xe9`<\x95\x80\t\xd3v\x8b\xa1\x14q\x1b\\\x9f1\xd1*ﴥ\xfc\"\x8dF;\xa7?\xa6\xae\xd7\xc4u\xff#a$\xaeU\x03WyHA\x87\x90<\xabA5\xf0\xde\u0095\xe8\xd1\\\x89\x88\xffy\x01\x98\xe9X3\xb1_W\x82\xe9|\x9d\x1b\x17\xd6&\x1b\xe3\b|\xa2^\xf3\xb1v\xe7Qr\xf9\x98A>\xaa\xb7Zfm\xc0\xd6\x05\x10\v\xfb\xe6\fz]\xba\xfc\x94\xe1wG.\x88\x1d~p\x05sn\xb4\x1a\xdb\xec\xcc\x18\x1cO\x96\"c\\7\\`\x03\xd0^\xd0D\xbf$\xb4}\x1c\x03\xab\xf90\x92-S\bhi\x80\xc97\x88o\x1e\x99FD\x9a\x8c\v\xbe\xcd]\xe8\x80\x0f\xcb\x13c`\f\x06\xc4\v\xd3\xf9\xf2E̿\xba\xb9hk\x93e\xebB/\xa8\\\x17k\x06ZX\xf0\xb5\\t\x06[\xa0\x90\x96\xdb\xcf\xcdQ\x8cQ\xec.e\xf7\xb1X\x95\xcb\xc5p\x04D\xe7\x12=A=\xed\x97Q\xc0\x85r\\\x88\xd4\xefE\xbc\x14\xe7\r۬5\xc4\xec{\xf5\\\bO\xcd\xcck\xfc\xb2\xb2z\x8bB-u\\õ\xa3\xf5\xad'3\\U\xc5b1\xf2=LM\xea\x1c\x8b\x90\xa7+\xa9{\xbcW\xb6\xf0\xf7?\xd5IXBJ\xf4\x84\xeaz\xfe\a6\xcc\xf7\xf1\x87*\xbfJg\xcb\x0fPl\xe1\xb7߫\xe2\n\xd5\xfd\xf8\x93ċ\xff\x06\x00\x00\xff\xff\xc8p\x98۸\x0e\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Msܸrw\xfd\x8a.\xe5\xe0\xf7\xaa4\xa3u吔n^\xd9[Q\xedƫ\xb2\xf4\x94C\x92\x03\x86\xec\x99\xc1\n\x04\xf8\x00p\xe4I*\xff=\xd5\x00\xf8\r\x92\x98\xb1\xf4\xde\xee+\xe3b\x8b\x04\x1a@w\xa3\xbf\xd0\xec\xb9X\xadV\x17\xac\xe4O\xa8\rW\xf2\x06X\xc9\xf1\xabEI\u007f\x99\xf5\xf3\xbf\x9a5Wׇ\xf7\x17\xcf\\\xe67p[\x19\xab\x8a/hT\xa53\xfc\x88[.\xb9\xe5J^\x14hY\xce,\xbb\xb9\x00`R*\xcb象?\x012%\xadVB\xa0^\xedP\xae\x9f\xab\rn*.r\xd4\x0ex=\xf5\xe1\x87\xf5\xbf\xac\u007f\xb8\x00\xc84\xbaᏼ@cYQހ\xac\x84\xb8\x00\x90\xac\xc0\x1bذ\xec\xb9*\xcd\xfa\x80\x02\xb5ZsuaJ\xcch\xae\x9dVUy\x03\xed\v?$\xac\xc3\xef\xe1G7\xda=\x10\xdc؟;\x0f\u007f\xe1ƺ\x17\xa5\xa84\x13\xcdL\xee\x99\xe1rW\t\xa6\xeb\xa7\x17\x00&S%\xde\xc0g\x9a\xa2d\x19\xe6\x17\x00a;n\xcaUX\xf0\u1f47\x90\xed\xb1`~-\x00\xaaD\xf9\xe1\xfe\xee\xe9\x9f\x1fz\x8f\x01r4\x99\xe6\xa5uH\xf1\v\x03n\x80\xc1\x93\xdb\x16\xe8\x80~\xb0{fAc\xa9Ѡ\xb4\x06\xec\x1e!c\xa5\xad4\x82\xda\xc2\xcf\xd5\x06\xb5D\x8b\xa6\x01\r\x90\x89\xcaX\xd4`,\xb3\b\xcc\x02\x83Rqi\x81K\xb0\xbc@\xf8Ӈ\xfb;P\x9b\xdf0\xb3\x06\x98́\x19\xa32\xce,\xe6pP\xa2*Џ\xfd\xf3\xba\x81ZjU\xa2\xb6\xbcƳo\x1d\xae\xea<\x1dl\xef\x1da\xc0\xf7\x82\x9c\xd8\t\xfd6\x02\x161\x0fH\xa3\xfd\xd8=7\xedv\x1d\x87\xf4\x00\x03ub2,~\r\x0f\xa8\t\f\x98\xbd\xaaDN\\x@M\b\xcb\xd4N\xf2\xffi`\x1b\xb0\xcaM*\x98\xc5\xc0\x00m\xe3Ң\x96L\xc0\x81\x89\n\xaf\x1cJ\nv\x04\x8d4\vT\xb2\x03\xcfu1k\xf8w\xa5\x11\xb8ܪ\x1b\xd8[[\x9a\x9b\xeb\xeb\x1d\xb7\xf5i\xcaTQT\x92\xdb\xe3\xb5;\x18|SY\xa5\xcdu\x8e\a\x14׆\xefVLg{n1#B^\xb3\x92\xaf\xdcҥ;Q\xeb\"\xff\xa7\x9a\x01̻\xdeZ푘\xd1X\xcd\xe5\xae\xf3\xc2q\xfd\f\x05\xe8\x00x\xfe\xf2C\xfd.ZD\xd3#\xc2ΗO\x0f\x8f]\xde\xe3f\x88}\x87\xf7\x0eC\xb6$ \x84q\xb9E퉸ժp0Q\xe6\x9e\xfb\x1c\xeb\n\x8er\x88~Sm\nn\x89\xee\u007f\xad\xd0\x10\x93\xab5\xdc:\x11\x03\x1b\x84\xaả3\xd7p'\xe1\x96\x15(n\x99\xc17'\x00aڬ\b\xb1i$\xe8J\xc7ag\x8f\xb5\u038bZ\x96M\xd0\xcb\v\x84\x87\x12\xb3ށ\xa1Q|\xcb3w,`\xabt+/\xbc\xb8Z\xf7@Ə,\xb5\xcc\xf0\a\xc9J\xb3W\x96䯪\xec\xb0\xc7`A\xb7\x0fw\x83\x01\xf5b\xc2ҜX\xa9\f\xe6t\xce^\x18\xb7\xb4\xbc\x11L @\xf0\xe4$L\r\xcfI\x9aʀ\xad\xb4t\xa7\xf4\v\xb2\xfc\xf8\xa8\xfeb\x10\xf2\xca1k\xad+\xae`\x83[\xa51\x02W#\x8d\xa7Ψ5!Ƹ%\xa9ʮ\xe1q\x8f\x84FV\t\x1b\xf8\x9e\x1bx\xff\x03\x14\\V\x16\xd7#h\x13\x04\xf6Hq`\xfc\x0ẹ\xfa\xc9xR-\xa0\xef\xe3İ\x0e\x12_\xf6h\xf7\xa8\xa1T\xb5\b\x8e\xecr\xcb\x05\x829\x1a\x8bE\xa0x-\xf86\x01\xfb\x8e)\x84\b \fl\x8e\xf5\x9a\xc7\xfb$}\xcb6\x02o\xc0\xeaj<\x9dG\xc3F)\x81l(\x84\x87x\xf8\x82\xc6\xf2l\x01\v\x97C4\xf8Q\x11$\xe8\xf0\xc2\xed-\x82\x87M\xcbk\x96=#\xb0\x1a\x1b\xa4\x1c\x84\xe8 \xb1\x87\x01\xf8/\t\x1fIre$Oƫ\x85 \xb98\n'-\xa5\x02\xa1\xe4\x0e\xb5\x9f\x8d\xb4\xc2\v\x17\x82\xa6\xd7X\xa8\x03\xe6@\x02C\xa3 \xc9\aۊd\xc9\x18\xcf\x00\xc4˓<\xc0\xa5\xb1\xc8\xf2\xf5\xe5k\x12\b\xbff\xa2\xca1o̖\x91\f\x18\x10\xe7\xd3h\x803\xe9\x18\x97\xa45Ȉ\"\xe4\xca\xf6-\x19&\x91\xad2\x8d@r\x9bK\x0f\xcf\xd9\x1c{\x8c\n(j\xdcb\x11Y\xdb\xec)L\xc4\fӚ\x1d'\xf0R\x9b\xb7\xa9hi\xfa\a-*x\xe6\xec\xafFW:\xccxk\x8dE%\xd4\xef\x18){\xa5\x9e\x97\x10\xf1oԧ\xd5\xfb\x909/\x016\xb8g\a\xaet\xd8z+\x8d\xf0+f\x95\xc5\xd8\xe9e\x16r\xbeݢ&8\xe5\x9e\x194\xde\xf4\x9bFȴ*\x03'\xfc'\x899\xdaGKH\xe2T\xb7\U000e99538\x1a\x9e\xab\xba\xd1BI۸\xf3\x9b\xf3\x03\xcf+&\xdcQf2\xf3\xfbaͺb\"a\x86ȣ5{AQ\xaf\x9c(\xd13\r\x94DP\x1a\n\xb2\x87\xc6]\x87\x16\\ۦ\xb6\xbda$\xed\x94gQ]\t4a*\xaf^Z\x19p5\t\xba\xa1\x88\xb7\xa5\x05۠\x00\x83\x023\xabt\x1c\x1dKD\xf6-E\xaeM`1\"\xe1\xfa6L\xbb\xb1\x19\x90\xe0\f\x9c=\xcf\xf6^Y\x10\a98\x90+4\ue533\xb2\x14ǩM\xc2\x12\xe5\xc3$s\a\xbdm\vG~\b/v\xf8ۖ \x1b۶ %\xfb\x98m\xd8\x01\xac\x9a\xdd\xf6?&bk\xb1\u007f\x06\xd3ލ\x86\xbe.\xd3\x12J9\xb9\xb3w[\xc0\xa2\xb4\xc7+\xe0\xb6~\xba\x04\x91L\xadv\xfe?0aN\xe7\xf8\xbb\xe1\xc8W\xe5\xf8Y\xaa,A$\xaa4\xd3\xff\x01\x89\xe2\x94\xc5C\xd0\x15\xc9\x04\xf9\xa5;\xea\n\xf8\xb6!H~E~\x93E=\xa0\xcc7\x9d\x97\xd7@F\x8a\xbe\xa3V0\x9b\xed?}%\xcb˴\x01\xd7D\xbc\f\a{\xfb\xb5\xb6\xe7\xfb\x8ay\x01.\xb8\x00\r\xd7X\xf8\xc0ϣ\xc3f\xfb\xc4YT\x1f>\u007f\xc4|\x0e=\x90\xc6y\xa3\x8d|\x18,\xb6;u0\xcaS\xb7\x11L\x9fƿ\xf1!\xbd+`\xf0\x8cGo\xb10\tD\x1cF\x13Mx:c\xe4\xb8آc\xb2g<:0!X\xb88:\x95\x15|{\xc6cJ\xb7\x01\x02iM܄ (a\x92\x1e8D\xb8\xd8R:\xf2\xc0\x05~kY\xb4\xbc9H\x17$u\xabq\u007f\xc66\x1b\xb2u\x82掰\xef\x8c'\x11\x9d\x82=/\x137Jj\x0e\f\xba\xd3R\x87~\x9f\x98\xe0y3\x91\xe7\xfb;9m\r\xf7\xdbge\xef\xe4\x15|\xfa\xcaM\x88\xbe\u007fTh>+랼\t:\xfd\xc2\xcf@\xa6\x1f莗\xf4b\x9b\xf0Ѝ!'0\xb7ow\xde\xc3k\xc8\xc3\r\xdcI\xf2[\x02>܍\x80\x9fn^?\xf4[Q\x19\x17$\x96J\xae\x9c\xaa\\\xc7f\xf2\xc8N\x04\xa9t\x8f\"\xe3\xa55\x93\xfa\t\x13\xc1>\x92&\xf1\xe3\xfd\x1d\x87`\x19\xe6u\x8c\xd3E\xe6\x99\xc5\x1dϠ@\xbd\x9bS\x1c\xddV\x92|O[B\xa2\xd4\xf5\xedD\x0eKS\xedu\v\xa2;\x1a\x82\xeb\xb7\x15\x9d܄^5\xb1\x17\xbbN\x04䧻.\xefȩXg\u007f,b\x97幻Ke\xe2\xfe\x04\x89\u007f\x02-ƺ\xdf/\xcckȂ\xb9\x10\xe9\xff\x92\x9as\f\xfd\u007fP2\xae\x13\xce\xf0\aw5*\xb076D\xb1\xba\xd3\xd0\f\xdc\x00\xd1\xf7\xc0\xc4\xf8\xaa'\xb29E\xb2\x05\x85W\xe4j;\xb2X\xae\xe0e\xaf\x8cש.4\xbb\b\x92\x1b\xb8|\xc6\xe3\xe5\xd5H\x0e\\\xde\xc9K\xaf\xe0O\x167\x8d\xb5\xa0\xa48¥\x1b{\xf9-FP\"'&usWЩ\xa62\xf9\x92\xb5%@\x03\x9b{W2s\xe7V\x9dć\xa52\x91ۤ\x89\xa5\xdc+c}d\xb1g\x96\x9e\x12\xc5\x02\xcfC!z\x05l\xebo\xbe\x95\xae\xef4I\xec\r\x02\xaeD53/a\x89\x8cMD\xcc\x03%\xc7\xea\xb2=\xc1^\x9e^\xfa\x8bN7\t˜q\xb1\b\xb7\xd4*Cc\xe6Y$AZ/\x04\t\x9b\x00!\xf3\x0e\x8c\xbf0\x9c\x0fJ\xd6-\xdd %$\x9dh\xca\u007f\xfaډ^\xd2᧿\x97\x98\xef\xd4u\x81;\xb3E\xc1\x867\xe3IK\xbc\xf5#\xebc\x12\x00y\xd7@\xef*w\xd4\xd3-\xc8\xc0H\xbf\a5]py\xe7&\x80\xf7\xaf\xae\xd6\x1b!\x89\xe7\x18\xee\xb7\xf5\xd8\x16\xe9\xcd\x03wzS-\"\xe5\"\xf7\x1a{\x94\x1bǹ\xc9PL\x04)\x95\xed\x86\x13\bn\xa9\xf2w\x06\xb6\\\x1b\xdb]h*ST\v\xa7\xbfm\xa7zN\xf2\x93\xd6g9N\xbf\xfa\x91\x9d@\xd6^\xbd\xd4\xf9\x05\x93W\xb1\xb1\xe6.\x85\x10\xf8\x16\xb8\x05\x94\x99\xaa\xa4\v\xbf\xd0QwSx\x12x\x01\x9d\x8c\xb24\x01A\reU\xa4!`帎\xcb\xd98M\xb7\xfbO\x8c\x8b\xb7 \x9b\x9dJÈ\xb5\x1e\xd9\xea|\x8cn\xa2H\xc1\xbe\xf2\xa2*\x80\x15\x84\xfaT\xb7g\xeb\xb38z\x14or9\x1c\\\xa7F\xac\xa2CU\n\xb4\xa9'\xd2gm\xd011<\xc7F1\a.P\x12\x18l\x19\x17\x13\x97\xe7\xe3v\x12nO\xf15\x82\xb0x='\"m\xf2\x95CEB 6\xd1X\x9c\x97֥N7\x15\xef5\xa6\x99gKA\xe9\xda<+5'^R\xafm\xa1\x05\x16c\xf2\xf8\xddD\x1b\xb5\xef&\xdaB\xfbn\xa2M\xb6\xef&\xdar\xfbn\xa2\x85\xf6\xddD\xab\xdbw\x13\xed\xbb\x896\xd7mNZ/\xad\xc8\u007fq2\xf1rq\x15\t\xd7\xd3sK\x9c\x81\x1f\xb2)n\xfd\xd7'\xa9\x19\x96w\xf1Q\x91\xac\xe0\xf0Y\xcb\xca}\x91\x13\xe3\x806\xe9\xa2U%M\xca%\x1d\x90\x9a\xbd}\x02\xfdB\x12\xe67dߦ$\xfc,\xa5\xf9\xf4\xf3L\x9b4\x9b:\xd1TՓD\xf0P\u007f\xd9Cfo7\x87\xa4\x9f\xaf\xe3\xec\xdcz\xa5\u007f\xf7\x1cԄT\x9c\x85\x04\x9c\xf9\xc4\xdc9|\r\\\x8f>\xc2t/a\xf4w\x83\xaf\x85,\x99\xe9ܘp\x13\x84\x96\x1dޯ\xfbo\xac\n\x992\xf0\xc2\xed>\xb2\x95\x97=Jw\x87%wݴך\xdf\xc2'VC<\x82\xd2 \xb9p\xe8\x9c\xe1\xd6\x1ez\xe1\xd7һp'\x9f\xcby\xf7#-\x97\xe6\xec\f\x9a~\x86̄\x88>\xf5\xca(=Q8=Gf>\xa9\xe5\x94̘a\xde\xcb$\xd0\xe5|\x98\x14\xcfq!\xf7匌\x97\xc4l\xc7o\xbe\x18K\xc9i9+\x93e1!01\u007f\xa5\x9f\x992\x0f\U00084b15$\xe4,g\xa8\x9c\x9c\x97\x12\xf2@f\xf7\x91\x9c\x8d\x12\xc93\x99\x05<\x99\x832\x97]\xb2\x10\x95\x1ag\x9e\xa4\xe7\x94̂v\xf9&˙$\xaf\x97/\xfa\x1a6\xf0\xb4\xa8Y\xcc\x06Y\xb4\x91\xe7\u05f7\x98\xefqJ\x96\xc7\"\xc6\xce\xcc\xe8h26&\xe6=5\x8f\xa3\x9f\xa71\x014%{c\";c\x02\xe2l\xceFjN\xc6\x04\xec\x05\xb5;\xcb%3/\xe3\x1f\x02â~\x13\u007f+\x8e:wcJ\xf7\xcc\xc5%\v\xfd\xd7Aw\xa2em5͛\x9f1˓\xdb\xfd\xe9\xe6gQ\t\xcbK\xe1\xc2\xf9\a\x9eG\x9dF\xbb\xc7c\xf3Y\xe7o\xca}\xe6\xb49:H\xbf~i\xd8s=0\xa2\x99\x81\x17\x14\x02X\x8c\xb9F;\xcf\xfc\xb7\xec\x99Z!\xc9|:p\xe1\x83\xd5\xf0\xc9\xfb\x95\xe7`\xf7%W,\xe2i\xf7X\x10\x94\xfa\xcb\xd7\x13\u070fy\x03\xd1۲\xee\xd9_+\xd4GP\aԭŰ\xf0\x1d\x81?h\xa6\x12m\xe2V\x90\x1f\xbe\x82\xc2\xc0pn\x0f\x1c|\x90^\x85E\xc1\x0e\xd6\xe8\xe0Й\x17\r\xadI\xbc\x91\x1f0\xd15\x1e\xf8P\xcd\xe8\xc8\xfb%\xdb35\t\xffm]\x87ӝ\x87E\xb5\xfd&\x0e\xc4\xf9.\xc4\f\xc8Ԥ\xfa\xb4\v\xa8\xc5$\xfa\xb7r%\x96\x9c\x89d+*-I\xfe-\x92\xe3OH\x8a?\xc1\xa98ͭHFSJ\xf2\xfb\x9b8\x17o\xe8^\xbc\x85\x83q\x9e\x8b\xb1\x00r\x90Ԟ\x92\xae\x9et\xb9\x9a|\xbf\x90r9\xba|\x050\x9f\x86\x9e\x90~\x9ep9\xb0\xb4҄4\xf3\xd3\xd2\xcb\x13p\xf8F\xce\xc7\x1b\xb9\x1foြ\xad\v\xb2\xe8\x84,r\xce\xec볣\xcbJ\xe7\xa8g\x83\xf1\xa9\xac6\xcbd\x03\u007f\xa1?\xe7\xe0\x8bں\xc2\v\xf5Ꙧ\xb1\x90r\xf3\xf5g\x06?s\x99{z\x10Su\xf4x\uf1a05,\xe2\t\x02\xad\xd5\x16*`\xf9k\x05\x83%Ӯl\xda\xe6\xe8\xaf&\xcd\x1a>\xb1l?\x80\xbe\x8f\xfa\t[\xa5\vfᲹ\x93\xb9\xf6\xc0\xe9\xef\xcb5\xc0O\xaa\xb9\xf4\xeaVT0\xbc(ő\xfc\x80\b\xcc\xcb.\x88\xf3\x18\"\xcaL&\x94k\n\xf5k\x16|\xbf\x87~\xef\xc8e^]\xba'\x13\xaa\xca\x1b\xe8\x13\xc4d\xf2\b\xf7O\xceFqeC\xb2\xb6\x84J\xb0Bj\x1fpXa\xe5\xc7\u05ff\xdc3Vi\xb6\xc3_\x94\xafõ\x84\x89~\xef^\x11\xb6 ;\xea\xcb\xf6\xfa[\x8c\x98N\r\x15\xc1\x06\xc0\xda\x1c\x9aQ5(ZeL\xa8̜Gk\xc5\xc2f\x1e\x1f\u007f\xf1\x1b\xb0\xbc\xc0\xf5\xc7\xca_\xa4\xaeJ\xa6\r\x126\xeb\x8d\xf9A\x1b\xfa\xef^\xbd\xc4b\x1d*\xec\xf9\xc7\xe1\xba5\xba<\x1dw_{\xd2\xea\x0f\xbd\xaab5\x8a\x96\x18\xf5)>\xaa\xe3\xa8u\x88\xe4\xcf|\xd4A\x9f\x82\xd3)\xac\xe8B\x18\xee;\x9b\xd7-\xfb3%ŧJϹrk\xcb\xc5\xe7|U\xb6Pj2d{U\xda\xd5\xec\t\x15\xdb\\\x8d\x9b\xf3\xea\xcf\xf9\xe4\x94^\xf9\xcfy:ݎG\xb8\"\x8f:\xefԟkʀ\xbd0\xd3$\xc0D\x15k\vΏt\x96-A\xc3\x1c\xf0\x80\x12\x94t\xf9.\xae\x1a\x8e/D:\x1c\x13\x81څ\x12\x12j\xaaR(\x96\xd7'\xbc\xd6a\xa1x壓_\xfa\x80\xfa\x9d\x99\x81\xd9\x14v\x8b a,0\xbdR\xb9\x81\x9cY\\E\x81&ɾ(\xb3e\x86\xf7\x19\xdd|\xb0\x96\xfc\x84\x98\xed<, 85\xb2\xd6\xc7VY&@V\xc5\xc6+xVw\x88\xd1oTFЄ\f\xa8\x99\xe3\xe57ƥ\xc5\xdd(\xc68\xde\xd9m\xcd?'\xef\xac\x199\xb53Se\x19\x1a\xb3\xad\x84\x88\x99\xfa\r\xe7\xbe\xfe6]n\xdfb\xcd3\xd7ɋ@\x97\x18X\x97\xd5\xf3\x99\x81\x05\x1a\xc3vu\xb1\xb3\x17\xd2@;\x94\xe8̠X\xfc\xd1;\x8am&Y\xbfԗ\x8fh\xb1\xccV,LPg\x02tz\xbd\x8b\xd9\x05B\xed|AD^\x97\x83\xadU\xf3\x898\xf9Zr\x9d\xa2\xca?5\x1d\t7.\x18\xed\bі\xefE\xc1w\x9c\xf4 \x11i\xc7\xf4\x86\xedp\x95)!Х\x9d\x8f\xd7\xf5\x96\x875\xe4\xeb}Af\x16\xb7\xf6S\xb7o\x88|xj\xfbJ\x19̗[t\xd5\\-\xd7ؖG\x1e-H\xb9\x89OR\xdd\x1e\v\xd1B\xc2\xe3\x95v\xfb\xd6\a,\xc8\xd5`O\x87\xba\xc2W\xc1\x18\x8c{\xb7\x05\xfbM\xe9+(\xb8\xa4\u007f\xc8\xfaw\xa1\x89z\xf0I\xebw5\xec\x16\xd6}O}\x9a\xb4\xe9\x8e\"\xc5\xfa@L\x99\xaa\xf1T\xd9\x15|Ʊe\xe5\xb3_1w\xc1\xb8X\xf5d\xear'\xef\xb5ڑ\u007f\x1cy\xd9\b\xafȻ{\xa6-gB\x1c\xfd$\x93\xb3G^|DR\\\x93\xd6K\x1c\xada\x95K\x98\r\xddZW\x9cK\xcf\t.ou\xa3*\xdb\x13%\xad(\x8a_\x038`k\xf8\xac,\xd6\x11^އI\xc2\x17\x8d]\xe1v\xab\xb4\xf5\x9e\xffj\x05|\x1b\xac\xa1\x98\xabǸp7T\xbe\x981p\xdb^ҷ\xdc\xeb\x1c\x1d\xed\x0e\xa1\xab\xf8T\xb0\xa3\xcfadYF\xc66^\x1b\xcbDD\xbe}SN\x943;\x89\xfb0\xffK\xc4\x0e\x1b!\xfc\xaeۿ\xf9\x90\xbc\xd1n\x0e\x9cǜ\xcb1\xf7\xb2=\xaa\xe9\xc0e\x1e\xa3\x84\x17ͭ%yڽ\xc2\x03K\x12T\b0$S&\xca\x06\xceIv\xf7\x9et\xef\xddtH\xb1\xef\xdf4\x9d\xa7Tw\u061c\"\xb2l\x1c\n&\xb6\xe5\xbfa\xe2\xa6\x1eK\xa4\xcc\xf6L\ue229\xb4\xaav\xfb\x9a/'4\xe3TD\xae\xa2EA)\xaa\x1d\xb1z\xb8>\xb1\x95\x96\x9d\x90L\xb8P\xc9;\xcbe\xd9\xf3\xe4JC\x88\xb8.\xa8\u007f\x1d\n\x01\xae\xb6Z\x15\xab@\vw\xebq\x15\xc2$\x9a+\xb2\xffɑ\x9f\x00\xdaV\xdcrlP\x96(\x81\x99\xb0\x9e\x84\x0f\xac\xe6\xc9:\x17\xa7\xb0L\xdbT\xaf\xe2\xa1\xd7y\xc1\xa1p\x90\xe3\xeb}\ba \xff\xa1\xd9\xed\xf0\xa7\r\xae\xc0pY\xd7\xf2\xf7A&\xcf\n\x86\xfc\f\x8d\xceW\x8f^h\x8d<\x84\x9e?\xd0_\xfe\xdf\xd6\x1584\x1a\xe6S\x8aM\xf94\xe8>\xc8\xd6u%\xab\x9b.\xc1\x0e\x8c\xe0\xe3O|\xeb\xef\xd82Z\xf5\x9f\xff\xeeY\xb8\x87$\x9b\xe5ݬ\xb9\xe2,\x91\xc6\xeeX(P}/\x90\xec\b\x83ط\x84ޝd\xf2\x1e\xces\xe2^Ӄ\xab\u007ff\xe2u\xfc\x9a\xc3y\xbeۛ9n\xaf\xbb\xbb\x17\xe6J\xdb/\x9d\xb1\xff\b\xdd\"\x9e[\x80\x10\xf1\xdd\"\xdbh\xbc\xb9E߭\xe3\xba\xd5k\x9c\xa8~=p\xe7^\xc9y\x8b\xea\x81\xd1C'@\xf3\xce\xd9\x0e3\x85'm@\x8ce\x19\x12\xbb~\x1e\xfe\x9c̥\xaf\xcd^\xffb\x8c\xfb3Sҫ[s\x03\xff\xf9\xdf\x17\x10\"\xaeO\xf5O\xc3\xd0\xc3\xff\x0f\x00\x00\xff\xff\xe8ٕ\xadzg\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Ms\xdc:rw\xfd\x8a.\xe5\xe0\xdd*\xcd\xe8\xb9rHJ7?Y\xae\xa8ދ\xad\xb2\xb4\xdaC\x92\x03\x86\xec\x99\xc1\x13\bp\x01p\xe4I*\xff=\xd5\x00\xf8\r\x92\x18Y\xda}/e\\l\x91@\x03\xe8n\xf4\x17\x9a=g\xab\xd5ꌕ\xfc\x11\xb5\xe1J^\x01+9~\xb3(\xe9/\xb3~\xfaW\xb3\xe6\xea\xf2\xf0\xfe\xec\x89\xcb\xfc\n\xae+cU\xf1\x15\x8d\xaat\x86\x1fq\xcb%\xb7\\ɳ\x02-˙eWg\x00LJe\x19=6\xf4'@\xa6\xa4\xd5J\bԫ\x1d\xca\xf5S\xb5\xc1M\xc5E\x8e\xda\x01\xaf\xa7>\xfc\xb4\xfe\x97\xf5Og\x00\x99F7\xfc\x81\x17h,+\xca+\x90\x95\x10g\x00\x92\x15x\x05\x1b\x96=U\xa5Y\x1fP\xa0Vk\xae\xceL\x89\x19͵Ӫ*\xaf\xa0}ᇄu\xf8=\xfc\xecF\xbb\a\x82\x1b\xfbK\xe7\xe1\xaf\xdcX\xf7\xa2\x14\x95f\xa2\x99\xc9=3\\\xee*\xc1t\xfd\xf4\f\xc0d\xaa\xc4+\xf8LS\x94,\xc3\xfc\f l\xc7M\xb9\n\v>\xbc\xf7\x10\xb2=\x16̯\x05@\x95(?\xdc\xdd>\xfe\xf3}\xef1@\x8e&Ӽ\xb4\x0e)~a\xc0\r0xt\xdb\x02\x1d\xd0\x0fv\xcf,h,5\x1a\x94ր\xdd#d\xac\xb4\x95FP[\xf8\xa5ڠ\x96h\xd14\xa0\x012Q\x19\x8b\x1a\x8ce\x16\x81Y`P*.-p\t\x96\x17\b\u007f\xfapw\vj\xf3\x1bf\xd6\x00\x9390cTƙ\xc5\x1c\x0eJT\x05\xfa\xb1\u007f^7PK\xadJԖ\xd7x\xf6\xad\xc3U\x9d\xa7\x83\xed\xbd#\f\xf8^\x90\x13;\xa1\xdfF\xc0\"\xe6\x01i\xb4\x1f\xbb\xe7\xa6ݮ\xe3\x90\x1e`\xa0NL\x86ů\xe1\x1e5\x81\x01\xb3W\x95ȉ\v\x0f\xa8\ta\x99\xdaI\xfe\xdf\rl\x03V\xb9I\x05\xb3\x18\x18\xa0m\\ZԒ\t80Q\xe1\x85CI\xc1\x8e\xa0\x91f\x81Jv\xe0\xb9.f\r\xff\xae4\x02\x97[u\x05{kKsuy\xb9\xe3\xb6>M\x99*\x8aJr{\xbct\a\x83o*\xab\xb4\xb9\xcc\xf1\x80\xe2\xd2\xf0݊\xe9l\xcf-fD\xc8KV\xf2\x95[\xbat'j]\xe4\xffT3\x80y\xd7[\xab=\x123\x1a\xab\xb9\xdcu^8\xae\x9f\xa1\x00\x1d\x00\xcf_~\xa8\xdfE\x8bhzD\xd8\xf9zs\xff\xd0\xe5=n\x86\xd8wx\xef0dK\x02B\x18\x97[Ԟ\x88[\xad\n\a\x13e\xee\xb9ϱ\xae\xe0(\x87\xe87զ\xe0\x96\xe8\xfe\xb7\n\r1\xb9Zõ\x131\xb0A\xa8ʜ8s\r\xb7\x12\xaeY\x81\xe2\x9a\x19|s\x02\x10\xa6͊\x10\x9bF\x82\xaet\x1cv\xf6X뼨e\xd9\x04\xbd\xbc@\xb8/1\xeb\x1d\x18\x1aŷ\xd8V$K\xc6x\x06 ^\x9e\xe4\x01.\x8dE\x96\xaf\xcf_\x93@\xf8-\x13U\x8eyc\xb6\x8cd\xc0\x8087\xa3\x01Τc\\\x92\xd6 #\x8a\x90+۷d\x98D\xb6\xca4\x02\xc9m.=\x97\xf3\xeeGZ.͋3h\xfa\x192\x13\"\xfa\xd4+\xa3\xf4D\xe1\xf4\x1c\x99\xf9\xa4\x96S2c\x86y/\x93@\x97\xf3aR<Džܗ\x17d\xbc$f;~\xf7\xc5XJNˋ2Y\x16\x13\x02\x13\xf3W\xfa\x99)\xf3 O\xc8ZIB\xcer\x86\xca\xc9y)!\x0fdv\x1f\xc9\xd9(\x91<\x93Y\xc0\x939(s\xd9%\vQ\xa9q\xe6IzN\xc9,h\x97o\xb2\x9cI\xf2z\xf9\xa2\xafa\x03O\x8b\x9a\xc5l\x90E\x1by~}\x8b\xf9\x1e\xa7dy,b\xec\x85\x19\x1dM\xc6\xc6ļ\xa7\xe6q\xf4\xf34&\x80\xa6doLdgL@\x9c\xcd\xd9H\xcdɘ\x80\xbd\xa0vg\xb9d\xe6e\xfcC`X\xd4o\xe2\xef\xc5Q/ݘ\xd2=sq\xc9B\xff2\xe8N\xb4\xac\xad\xa6y\xf33fyr\xbb?\xdd\xfc,*ay)\\8\xff\xc0\xf3\xa8\xd3h\xf7xl>\xeb\xfcM\xb9Ϝ6G\a\xe9\xcb׆=\xd7\x03#\x9a\x19xF!\x80Řk\xb4\xf3\xcc\u007f˞\xa9\x15\x92̧\x03\x17>X\r\x9f\xbc_x\x0ev_r\xc5\"\x9ev\x8f\x05A\xa9\xbf|=\xc1\xfd\x987\x10\xbd-\xeb\x9e\xfd\xadB}\x04u@\xddZ\f\v\xdf\x11\xf8\x83f*\xd1&n\x05\xf9\xe1+(\f\f\xe7\xf6\xc0\xc1\a\xe9UX\x14\xec`\x8d\x0e\x0e\x9dy\xd1К\xc4\x1b\xf9\x01\x13]\xe3\x81\x0fՌ\x8e\xbc_\xb2=S\x93\xf0\xdf\xd6u8\xddyXT\xdbo\xe2@\xbc܅\x98\x01\x99\x9aT\x9fv\x01\xb5\x98D\xffV\xaeĒ3\x91lE\xa5%ɿEr\xfc\tI\xf1'8\x15\xa7\xb9\x15\xc9hJI~\u007f\x13\xe7\xe2\r\u074b\xb7p0^\xe6b,\x80\x1c$\xb5\xa7\xa4\xab']\xae&\xdf/\xa4\\\x8e._\x01̧\xa1'\xa4\x9f'\\\x0e,\xad4!\xcd\xfc\xb4\xf4\xf2\x04\x1c\xbe\x91\xf3\xf1F\xee\xc7[8 o\xeb\x82,:!\x8b\x9c3\xfb\xfa\xc5\xd1e\xa5sԳ\xc1\xf8TV\x9be\xb2\x81\xbfПs\xf0Em]\xe1\x85z\xf5L\xd3XH\xb9\xf9\xfa3\x83_\xb8\xcc==\x88\xa9:z\xbcwC\xd0\x1a\x16\xf1\x04\x81\xd6j\v\x15\xb0\xfc\xb5\x82\xc1\x92iW6ms\xf4W\x93f\r7,\xdb\x0f\xa0\xef\xa3~\xc2V\xe9\x82Y8o\xeed.=p\xfa\xfb|\r\xf0I5\x97^݊\n\x86\x17\xa58\x92\x1f\x10\x81y\xde\x05\xf12\x86\x882\x93\t\xe5\x9aB\xfd\x9a\x05\xdf\xef\xbe\xdf;r\x99W\x97\xeeɄ\xaa\xf2\x06\xfa\x041\x99<\xc2ݣ\xb3Q\\ِ\xac-\xa1\x12\xac\x90\xda\a\x1cVX\xf9\xf9\xf5/\xf7\x8cU\x9a\xed\xf0W\xe5\xebp-a\xa2\u07fbW\x84-Ȏ\xfa\xb2\xbd\xfe\x16#\xa6SCE\xb0\x01\xb06\x87fT\r\x8aV\x19\x13*3\xe7\xd1Z\xb1\xb0\x99\x87\x87_\xfd\x06,/p\xfd\xb1\xf2\x17\xa9\xab\x92i\x83\x84\xcdzc~І\xfe\xbbWϱX\x87\n{\xfey\xb8n\x8d.O\xc7\xddמ\xb4\xfaC\xaf\xaaX\x8d\xa2%F}\x8c\x8f\xea8j\x1d\"\xf93\x1fuЧ\xe0t\n+\xba\x10\x86\xfb\xce\xe6u\xcb\xfeLI\xf1\xa9\xd2s\xae\xdc\xdar\xf19_\x95-\x94\x9a\f\xd9^\x95v5{B\xc56W\xe3\xe6e\xf5\xe7|rJ\xaf\xfc\xe7<\x9d\xae\xc7#\\\x91G\x9dw\xea\xcf5e\xc0\x9e\x99i\x12`\xa2\x8a\xb5\x05\xe7G:˖\xa0a\x0ex@\tJ\xba|\x17W\r\xc7\x17\"\x1d\x8e\x89@\xedB\t\t5U)\x14\xcb\xeb\x13^\xeb\xb0P\xbc\xf2\xc1\xc9/}@\xfd\xce\xcc\xc0l\n\xbbE\x900\x16\x98^\xa9\\A\xce,\xae\xa2@\x93d_\x94\xd92\xc3\xfb\x8cn>XK~B\xccv\x1e\x16\x10\x9c\x1aY\xebc\xab,\x13 \xabb\xe3\x15<\xab;\xc4\xe87*#hB\x06\xd4\xcc\xf1\xf2\x1b\xe3\xd2\xe2n\x14c\x1c\xef\xec\xba染w\u058c\x9cڙ\xa9\xb2\f\x8d\xd9VB\xc4L\xfd\x86s_\u007f\x9b.\xb7o\xb1\xe6\x99\xeb\xe4E\xa0K\f\xac\xcb\xea\xf9\xcc\xc0\x02\x8da\xbb\xba\xd8\xd93i\xa0\x1dJtfP,\xfe\xe8\x1d\xc56\x93\xac_\xea\xcbG\xb4Xf+\x16&\xa83\x01:\xbd\xde\xc5\xec\x02\xa1v\xbe \"\xaf\xcb\xc1֪\xf9D\x9c|+\xb9NQ\xe57MG\u008d\vF;B\xb4\xe5{Q\xf0\x1d'=HD\xda1\xbda;\\eJ\bti\xe7\xe3u\xbd\xe5a\r\xf9z_\x91\x99ŭ}\xea\xf6\r\x91\x0fOm_)\x83\xf9r\x8b\xae\x9a\xab\xe5\x1a\xdb\xf2ȣ\x05)7\xf1I\xaa\xdbc!ZHx\xbc\xd2n\xdf\xfa\x80\x05\xb9\x1a\xec\xe9PW\xf8\"\x18\x83q\xef\xb6`\xbf)}\x01\x05\x97\xf4\x0fY\xff.4Q\x0f>i\xfd\xae\x86\xddº\xef\xa8O\x936\xddQ\xa4X\x1f\x88)S5\x9e*\xbb\x82\xcf8\xb6\xac|\xf6+\xe6.\x18\x17\xab\x9eL]n\xe5\x9dV;\xf2\x8f#/\xffʸ\xe5r\xf7I\xe9;Q\xed\xb8\xfcR\x86t\xb9\xd3:\xdf1m9\x13\xe2\xe8\xd7\x13\x19\xdbH\xc9Ȼ\xe5ѓ/>\"i\xc8I3)N\xbf\x80\x8e%\x12\x86n\xad\xcfϥg9\x97 \xbbQ\x95\xedɬV\xe6\xc5\xef\x1b\x1c\xb05|V\x16\xebP2\xef\xc3$)\x8fƮp\xbbU\xda\xfa\x10\xc3j\x05|\x1b̮\x98OɸpWa\xbej2p\xdbf\x03\xb4\xc7\xc4yTڝvWZ\xaa`G\x9f,ɲ\x8c\xacz\xbc4\x96\x89\x88 \xfd\xae\xe4+g\xdf\x12\x9bc\xfe\x97\x88\xc17B\xf8m\xb7\u007f\xf3\xc5z\xa3F\x1d8\x8f9\x97\xcc\xee\x95HT\xa5\x82KqF\tϚ[K\x82\xbb{W\b\x96D\xb5\x10`HxM\xd4'\x9cS!\xee=)\xf9\xdb\xe9\xd8eߑj:O\xd9\bas\x8aȲq(\x98ؖ\xffX\x8a\x9bz,\x912\xdb3\xb9#\xa6Ҫ\xda\xedk\xbe\x9cP\xc1S\xa1\xbf\x8a\x16\x05\xa5;ئ\xbe\xa7\xb1\x95\x96\x9d\xd8O\xb8\xb9\xc9;\xcbe\xd9\xd3\xe4JC,\xba\xae\xdc\u007f\x19*\x0e\xae\xb6Z\x15\xab@\vw\xbdr\x11\xe21\x9a+r4\xec>\x8ar\xf0%\x91Ci/\xc7\x06e\x89\x12\x98\t\xebI\xf8\x92k\x9e\xacs\x01\x11˴Mu_\xee{\x9d\x17<\x17\a9\xbe\xde\xfb\x10o\xf2_\xb4]\x0f\u007fC\xe1\x02\f\x97\xf5\x8f\x06\xf8h\x96g\x05C\x0e\x8dF\x17\x14\x88ޜ\x8d\\\x91\x9e\xe3\xd1_\xfe\xdf\xd7\xe784\xaa\xec&\xc5x}\x1ct\x1f\xa4\x05\xbb\xda\xd8M\x97`pF\xf0\xf1'\xbe\xf5\x97y\x19\xad\xfa\xcf\xff\xf0t\xdfC\x92q\xf4n\xd6.r&Oc\xe0,T¾\x13H\x06\x8bA\xec\x9b\\\xefN\xb2\xad\x0f/\xf3\x16_\xd3U\xac\u007f\xcf\xe2u\x1c\xa8\xc3˜\xc47\xf3\x10_ww\xcf\xcc\xd5\xd0_:c\u007f\r\xdd\".b\x80\x10q\x12#\xdbh\xdc\xc6E'\xb1\xe3#\xd6k\x9c(\xb3=\xf0\x1b_\xc9K\x8c\xea\x81\xd1C'@\xf3\xce\xd9\x0e3\x85'm\xe4\x8de\x19\x12\xbb~\x1e\xfen\u0379/\x02_\xff4\x8d\xfb3Sҫ[s\x05\xff\xf1_g\x10B\xbb\x8f\xf5o\xd0\xd0\xc3\xff\v\x00\x00\xff\xfflA\x99e\xe3g\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKo#\xb9\x11\xbe\xebW\x14f\x0f\xbe\x8cZ\xb3\xc9!\x81.\x81F\x93\x00\x83x\xd6\xc6\xc8q\x0eI\x80\xa5Ȓ\xc45\x9b\xec\xf0!\xad\x12\xe4\xbf\aŇ\xba\xd5ݲ\xe4A\xb2ˋ->\x8aU_\xbdٓ\xe9t:a\x8d|F\xeb\xa4\xd1s`\x8dğ=j\xfa媗\u07fbJ\x9a\xd9\xfe\xfbɋ\xd4b\x0e\xcb༩\xbf\xa23\xc1r\xfc\x84\x1b\xa9\xa5\x97FOj\xf4L0\xcf\xe6\x13\x00\xa6\xb5\xf1\x8c\xa6\x1d\xfd\x04\xe0F{k\x94B;ݢ\xae^\xc2\x1a\xd7A*\x816\x12/W\xef?T\xbf\xab>L\x00\xb8\xc5x\xfcI\xd6\xe8<\xab\x9b9\xe8\xa0\xd4\x04@\xb3\x1a\xe7\xb0f\xfc%4\xce\x1b˶\xa8\fOwU{ThM%\xcd\xc45\xc8\xe9\xea\xad5\xa1\x99C\xbb\x90(d\xb6\x92H\x1f#\xb1U\"v\x9f\x89\xc5u%\x9d\xff\xf3\xe5=\xf7\xd2\xf9\xb8\xafQ\xc12u\x89\xad\xb8\xc5\xed\x8c\xf5?\xb4WOa\xedTZ\x91z\x1b\x14\xb3\x17\x8eO\x00\x1c7\r\xce!\x9en\x18G1\x01ȘEjS`BD-0\xf5h\xa5\xf6h\x97F\x85Z\x9f\xee\x12踕\x8d\x8f('Y \v\x03E\x1ap\x9e\xf9\xe0\xc0\x05\xbe\x03\xe6`\xb1gR\xb1\xb5\xc2\xd9_4+\xffGz\x00?9\xa3\x1f\x99\xdf͡J\xa7\xaaf\xc7\\YM:z\xec\xcc\xf8#\t༕z;\xc6\xd2=s\xfe\x99))NZ\a\xe9\xc0\xef\x10\x14s\x1e\x8c\xb6e,\x1e?\x97ț\x1c(\xfb[ƪ\x82E\xf6\\\xb3\x81\x0f \xa4\xa3\x02\xc0E\xa2C\xb0\xa8<\xa3\xf59x\x1b\xde$>7z#\xb7C\xa1\xbb5\xcd%\x8b\xb9B\xba\x87\xdc2\xdeD\xa1\x89\xac\xa3\xb1f/\x05\xda)\xf9\x87\xdcH\x9e9\t6e\xae\x8dD%\xdcP\xd2\v^\x16E\xb1(ȫ\x99\xba\xa2\xc3\xe5ic,\x8d\x99\xd4ɂ[\x021\xd8\xd8:\xa7T\xedQ\x8bS5rƍ\x89Qˡ\x80\x83\xf4\xbb\x14\x0e\u0558\xdf\xc1\xab\xbeG\xe3\x05\x8fc\xd3=ޟvH;S\x02Ep\xc8-\xfahm\xa8\xc8|Ȕ*\x80/\xc1ŀڏ\x13e\xc4B\xad\x9c~\xc1\xe3\x10h\xb8\xa6\xdc\\\xc2\\g\xf9\x8eJ\xe7°\xc5\rZ\xd4~4\xa8Sgb5z\x8cq]\x18\xee(\xa4sl\xbc\x9b\x99=ڽ\xc4\xc3\xec`\xec\x8b\xd4\xdb)\x01>\xcd\x1e4\x8bm\xc5\xec\xbb\xf8\xe7\x82\xc8O\x0f\x9f\x1e\xe6\xb0\x10\x02\x8cߡ%\xadm\x82*\x86֩o\xde\xc7\x1c\xfb\x1e\x82\x14\u007f\xb8\xfb\x16\\L\x93<\xe7\x06lV\xd1\xfa\x8fT\xa8E\xa6\b\xa2UҊ\xb1@\x99\x92\x94]gm\xa6X3f\x88c\x15fwP`\xa2\f2\x16Q_p\x18L_q\xb3\\\xec^\xf1\xb1RHK-$\xa7B\xec\xdc7J\x83!\xce\xea\xed\x11\xc1\xfa\x15\xf8\xa5\x880.x\x12 \xe7\xc3+\x1c?t\xf7\xb6mY\nO9\xc79\xf4T@9\xd0H9\x90\xd9!r1(p\xa35y\xa37\xc0N\xa1\xee\xce\xf5c\xfc\x1b#\xc4:\xf0\x17\x1c\x01~ \xcaǸ\xb1`\x9c\x8e\x11/\xc1a\f\xbe\xd7\u0600\xeb6\xce\xd9\x12\xed-\xbc,\x17\xb4\xf1\x94&\x19,\x17\xb0\x0eZ(,\x1c\x1dv\xa8\xa9C\x90\x9b\xe3\xf8]4\x9e\xeeW\x05\xd5Xa\xe4\x1a\xbf`;.C\x8a\xe1sX\x1fGj\x82\x1b\x84l,n\xe4\xcf7\b\xf9\x187\x16\xc0\x1b\xe6w \xb5\x93\x02\x81\x8d\xc0\x9f\x8a\xb5\v\x82\x9e\xf2\xffC\x8e\"ߠ\x9e\u05fc=\xb1\xf3\x16\x87/\x18_\xf1\x9fǼ\xed\x84B\xf9\x9d#\xffy-xɏG%ڟ\x1e\f\xfe\x94*,>\x92*Ϙy\x1e\x9ex\xa5R+\xcf\x16c\xceLu\x81\xb1\x16]c\xb4\xa0\xe6\xe9\xb6:\xade\xf9\u007fW\xad\x8d\xabuz\x1e\xe5zkE\v7\xb5*\xf1\x89\xe6\xcd\xcdJz\xb8\xea\xb6\x02f\xed\xa8Sl\xfb\x95\x9e\x8c\xbfH\x9b\xf2\xaeӧP?\xac!\xe8X\xa9Ō_\xc1\xdf5|\xa2ޖ\xb2\x93\x98\x13\xdfv\xcc\x00\xa4\x03m\x0et\xbcC/\x92\x00\xa3S\xbe\xa6n\x8di\x91\x9b\xe1\xb8t\x90JQƶX\x9b\xfdhƦBӢ:\x02sd:\xfb\xdfT\x1f\xaaw\xbfZ\x17\xa4\x98\xf3\xd4Ԡ\xf8\x8a{9|\xe5\x19\xa2{?8Q\x1c\xff\xe4\x0e\xf4\xe3\xc7\xd2,\xcfl\xde\xf6\xe3\b\x18\x1b\xa9\xa8\x16\x1c\x89\x13m\xc50|\x8f\xfc\xb8\xba\xbfs\xb1\x84G\xed\xc7ʾ\x03Z\x8c\x1d\x13\n\xaa\xe2M~\x97\bΣ\x1d1\x80\x93\xf6\xa2\xceA\x19\xbd\xed9N\x1a\xf9\x95\x82*\xb4dPƂ@O\xa9Io\x81\xef\x98\xdeb\xfb\n\x95\xf9\u007f\x9dS2\x9f\x9eʹ\x16\"\xf5%\xf3\xb8I\xa3Or\xacL\x1f\xbc\x00\xb7\x9b\xc7_\u007f\v\xf7E\xb3\x17ۜ+\xb8\x0f\xf6\x97,M\xa0N}\xfb\"\u070eooo\x87\xcf\xcd7 \xf1ַ\xf0W\xde5\xe0\xc0\\\xfb*\xfe\xeb\xe1PS\xb5z\xb5\x04\xfe\x92v\xa5\xe7\xc3|\x04\xd8\xda\x04\xff\x9agލ\x19t~\xee\u007f\v\x8f\xf1#Ƶ\"\x83\xf6\x14\x8d\xf0`\xa9\x95l_\xc5bP\x18\xcb-\xb7?/-z\xdfZ\xbak\xc3/17\xc85\x9ak\a\x93)_v\xf4\x9aA\xee΄\xf5\xe9\xa5x\x0e\xff\xfeϤMה\x13\x1b\x8f\xe2\x87\xfeǵw)d\x94/d\xf1'\xa7:&}\x1d\x84\xbf\xfdc\x92\xaeB\xf1\\>i\xd1\xe4\u007f\x03\x00\x00\xff\xff\x1d\r\x93\v\x97\x1c\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4\x96Ms\xe36\x0f\xc7\xef\xfa\x14\x98}\x0e{y$\xefN\x0f\xed\xe8\xd6\xcd\xee!\xd36\xe3I2\xb9tz\xa0I\xd8\xe2F\"Y\x00t\xeav\xfa\xdd;$%\xbf\xc8v6=\x947\x91 \xf0\xe7\x0f\x04Ī\xae\xebJ\x05\xfb\x84\xc4ֻ\x16T\xb0\xf8\x87\xa0K_\xdc<\xff\xc0\x8d\xf5\x8b\xed\xc7\xea\xd9:\xd3\xc2Md\xf1\xc3=\xb2\x8f\xa4\xf13\xae\xad\xb3b\xbd\xab\x06\x14e\x94\xa8\xb6\x02P\xceyQi\x9a\xd3'\x80\xf6N\xc8\xf7=R\xbdA\xd7<\xc7\x15\xae\xa2\xed\rRv>\x85\xde~h\xbeo>T\x00\x9a0o\u007f\xb4\x03\xb2\xa8!\xb4\xe0b\xdfW\x00N\r\u0602\xc1\x1e\x05WJ?\xc7@\xf8{D\x16n\xb6\xd8#\xf9\xc6\xfa\x8a\x03\xea\x14xC>\x86\x16\x0e\ve\xff(\xaa\x1c\xe8sv\xf5)\xbb\xba/\xae\xf2joY~\xbaf\xf1\xb3\x1d\xadB\x1fI\xf5\x97\x05e\x03\xb6n\x13{E\x17M*\x00\xd6>`\vwIVP\x1aM\x050\xf2\xc82kP\xc6dª_\x92u\x82t\xe3\xfb8Ldk0Țl\x90L\xf0\xb1\xc3|D\xf0k\x90\x0e\xa1\x84\x03\xf1\xb0\xc2Q\x81\xc9\xfb\x00\xbe\xb2wK%]\vM\xe2\xd5\x14\xd3$d4(\xa8?ͧe\x97\x04\xb3\x90u\x9bk\x12X\x94D\x9eD\xe4\xb8\xd6;\xa0#\xbe\xa7\x02\xb2}\x13:ŧ\xd1\x1f\xf2µ\xc8\xc5f\xfb\xb1\x90\xd6\x1d\x0e\xaa\x1dm}@\xf7\xe3\xf2\xf6黇\x93i8\xd5z!\xb5`\x19Ԥ4\x81+\xd4\xc0;\x04O0x\x9a\xa8r\xb3w\x1a\xc8\a$\xb1\xd3\xd5*㨪\x8efg\x12\xde'\x95\xc5\nL*'\xe4\fm\xbc\x04hƃ\x15\x98\x96\x810\x102\xbaR`'\x8e!\x19)\a~\xf5\x15\xb54\xf0\x80\x94\xdc\x00w>\xf6&U\xe1\x16I\x80P\xfb\x8d\xb3\u007f\xee}s:g\n\xda+9\xe4g\x1a\xf9\xd29\xd5\xc3V\xf5\x11\xff\x0f\xca\x19\x18\xd4\x0e\bS\x14\x88\xee\xc8_6\xe1\x06~I\x98\xac[\xfb\x16:\x91\xc0\xedb\xb1\xb12u\x13\xed\x87!:+\xbbEn\fv\x15\xc5\x13/\fn\xb1_\xb0\xddԊtg\x05\xb5D\u0085\n\xb6\xce\xd2]\xee(\xcd`\xfeGc\xff\xe1\xf7'Z\xcf.H\x19\xb9\xd0_\xc9@*\xf3\x92\xf6\xb2\xb5\x9c\xe2\x00:M%:\xf7_\x1e\x1ea\n\x9d\x931\xa7\x9f\xb9\x1f6\xf2!\x05\t\x98uk\xa4\x92\xc45\xf9!\xfbDg\x82\xb7N\xf2\x87\xee-\xba9~\x8e\xab\xc1\nOW2媁\x9b\xdcbSQ\xc7`\x94\xa0i\xe0\xd6\xc1\x8d\x1a\xb0\xbfQ\x8c\xffy\x02\x12i\xae\x13ط\xa5\xe0\xf8\xef07.Ԏ\x16\xa6\xf6}%_\x17\x8a\xf6!\xa0N\x19L\x10\xd3n\xbb\xb6:\x97\a\xac=\xc1Kgu7\x15\xed\x8c\xee\xbe\xc0\x9b\x93\x85\xcb\x05\x9dơM\xceW\xae\x1e\x1er\xee,\xe1\xec\x16\xd6p\xd6s_璛\xe1\xbf$S:\xf1\xc8FG\"trԟեMoe\x81D\x9e\xcefg\xa2\xbed\xa3\xfc\x04P\xd61(\xb7\x1b7\x82tJ\xe0\x05)\x95\x81\xf61\xf5\x194`\xe2\x19\xbf\x11\xcb\xf1\xbf$\x90\xd7\xc8ܜ\xd9Y\xc1ႦW\xb2\x93Fz^\xa8U\x8f-\bE\xbc\x92YE\xa4v\xb3\xb5\xfc\xcf\xfa\x06\x82e\xb2\xb9\x94\x83\xfd\u007f\xfa\x9bIȸ]\x1c\xce#\xd5p\x87/\x17foݒ\xfc\x86\x90\xe7W>-.\v\xbd\xfdc\xe0\r\x94.^ʳIN\xfd\xce\x1cQd\xf1\xa46\xc7\\9\xae\xf6\xfd\xbb\x85\xbf\xfe\xae\x0e\xf7Zi\x8dA\xd0\xdc\xcd_i\xefޝ<\xb7\xf2\xa7\xf6\xae\xbc\x8c\xb8\x85_\u007f\xabJ(4O\xd3\xeb)M\xfe\x13\x00\x00\xff\xff--\nM\xde\n\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WM\x8f\xdb6\x10\xbd\xfbW\f\xd2CZ \x92\x13\xf4\xd0·v\x93âi\x10\xd8\xe9^\x8a\x1ehj,\xb1K\x91,g\xe8\xcd\xf6\xd7\x17CJ\xfe\x90\xe5\xdd͡\xbc\x89\x1c\x0e\x1f\x1f\xdf.\xbd\u007f[\xffT\xbf]\x00\xe8\x88y\xfa\x17\xd3#\xb1\xea\xc3\n\\\xb2v\x01\xe0T\x8f+h\xfc\x83\xb3^5\x11\xffIHL\xf5\x1e-F_\x1b\xbf\xa0\x80Z\x16m\xa3Oa\x05ǁ2w\x00T6\xf3~H\xb3.i\xf2\x885Ŀ͍~4CD\xb0)*{\t\"\x0f\x92qm\xb2*^\f/\x00H\xfb\x80+\xf8$0\x82\xd2\xd8,\x00\x86\xbdgXհ\xbb\xfd\xbb\x92Jwث\x82\x17\xc0\at\xbf|\xbe\xbd\xfbqs\xd6\r\xd0 \xe9h\x02g\x06'\x98\xc1\x10(\x18\x10\x00\xfb\x03(P\x0eTd\xb3S\x9aa\x17}\x0f[\xa5\xefS8d\x05\xf0ۿQ3\x10\xfb\xa8Z|\x03\x94t\aJ\xf2\x95P\xb0\xbe\x85\x9d\xb1X\x1f&\x85\xe8\x03F6#˥\x9d\x88\xeb\xa4w\x02\xfc\xb5\xec\xadDA#\xaaB\x02\xeep\xe4\a\x9b\x81\x0e\xf0;\xe0\xce\x10D\f\x11\t]\xd1\xd9Yb\x90 \xe5\x86\x1d\u0530\xc1(i\x80:\x9fl#b\xdccd\x88\xa8}\xeb̿\x87\xdc$\fɢV\xf1(\x87c3\x8e1:ea\xafl\xc27\xa0\\\x03\xbdz\x84\x88\x99\xa7\xe4N\xf2\xe5\x10\xaa\xe1w\x1f\x11\x8c\xdb\xf9\x15t́V\xcbekx,*\xed\xfb>9Ï\xcb\\\x1ff\x9b\xd8GZ6\xb8G\xbb$\xd3V*\xea\xce0jN\x11\x97*\x98*Cw\xb9\xb0\xea\xbe\xf9.\x0eeH\xafϰ\xf2\xa3Ȍ8\x1aמ\fd\xcd?q\x02\xa2\xfa\"\x982\xb5\xec\xe2H\xb4t\t;\xeb\x0f\x9b/0.\x9d\x0fc\xca~Q\xcea\"\x1d\x8f@\b3n\x87\xb1\x1cbV\x9e\xe4D\xd7\x04o\x1c\xe7\x0fm\r\xba)\xfd\x94\xb6\xbda\x1a\xc5,gU\xc3Mv\x1a\xd8\"\xa4\xd0(Ʀ\x86[\a7\xaaG{\xa3\b\xff\xf7\x03\x10\xa6\xa9\x12b_v\x04\xa7&9\r.\xac\x9d\f\x8cNv\xe5\xbc&\xa5\xbe\t\xa8\xe5\xf4\x84@\x99ivF\xe7Ҁ\x9d\x8f\xa0\x8e\x95?\x10X\x9fe\x9e\xaf\xdc\fN\xc5\x16y\xda;\xc1\xf2%\a\xc9\xf2\x0f\x9d:7\x9a\xef\xb1nk\xf1\n\x1a\x80\x14\xf7\xf8\xa1\xbe\xc8x\x1d\x03̪w\x16\xc9(b\xa1Ax\x15+\x10\x93:\xc5t\xb9\xb44t\xa9\x9f_\xa0\x82_3揾}r\xfc\xc6;\x16\xb9?\x19t\xe7m\xeaq\xe3T\xa0\xce?\x13{\xcbؿ,r\xbc\x90\x0f\x97\xd4e\xe0\x1a\xc5\xca\xf1\xfa&\x86\x805R\xb2W\x97\xbb\xd9\xdc~\xcb>\xae\x84?\xc9ԕ\xda\x19[\xbe#\x9f\x17\x82ܲ\xa3\x10dJ\xb98\x10\xe4\xed\x11\x1d2\xd2\xd1\xc3\x1e\fw\xb3\x19\x01\x1e:\xa3\xbb<1\xabH\xec\x91\xc8k\x93\xcd\xe6\xdb\xe1K\xf1\x99\x883J\xae\xb2\xc2g\xba\x05\xfcE\xf7\x15˸\xb6@5\x94\xf1\x8bl\x87\x15'\xfa\x06\xe3\xc9\xf1#\xd5:ň\x8e\x87,\xf9\"\x9eNx\xa9\xf3\x8c\xe5\xfa\xc7\xfa\xe33\xf6\xf3\xfe\x18\x99\x9f\x9aʸ\x82&D\xacȴ\xf2|\x9011\xa0l\f\x97d\x94v\xfe\x9c9'j\xf6D\xf1k01\xdb\xec3\x10?\x1c\x02\x8bK\xa2+7\xe0\xf4\xc1\x96\x13\"\xe5ׅV\xd3w\x8d\xb4-B\x83\x16\x19\x1b\xd8>\x16\xbb\u007f$\xc6\xfe\x12\xf7\xce\xc7^\xf1\n\xe4f\xac\xd8\xcc\xc8H\x1e\xd5jkq\x05\x1c\xd35\x95\xcdn\xf38\xfa\x01\xed\xe4\xfa\x8b\x99\xdb\x1e\xd1~[aޞ\xc3\xf4\xee\xfb\xe0@\xcb\x1a[\xb6\x8e+\x95F\xf9\xf6\xf6\xfa\xfe\xcfw\xa3a\x00m\x94F\xe3Dr\xe8\xe1\x1bı\xc1(\x8cY}A\x04\xc3*\xe0\x14\xc0\xd0\x06\xab\bc\xc8#\x86 \x0eaIu\rZ\x94nȒ\xf4\xa9-0\tj\xf3\v\x96\xae\x80;4D&\t\xa6Tr\x87Ɓ\xc1RUR\xfc\xb7\xa7mI\xd7\xe8І9\x8cq\xe5\xf0y\xd7/Y\x03;\xd6t\xf8\x1a\x98\xe4в=\x18\xa4S\xa0\x93\x03z~\x89-\xe0ge\x10\x84ܪ5\xd4\xcei\xbb^\xad*\xe1R\xfc.U\xdbvR\xb8\xfdʇb\xb1\xe9\x9c2v\xc5q\x87\xcdʊj\xc9LY\v\x87\xa5\xeb\f\xae\x98\x16K\x0f]\xfa\x18^\xb4\xfc\x1b\x13#\xbe\xbd\x18a\x9d)F\xf8|x=!\x01\n\xb0 ,\xb0\xb85\xdc\xe2\xc0\xe8\xe4 \xdf\xff\xe3\xee\x03\xa4\xa3\xbd0\xa6\xdc\xf7|?l\xb4\a\x11\x10Ä\xdcbt0[\xa3ZO\x13%\xd7JH\xe7\u007f\x94\x8d@9e\xbf\xed6\xadp$\xf7\xffth\x1dɪ\x80K\x9fԐ\xc3\xec4i./\xe0Z\xc2%k\xb1\xb9d\x16\xbf\xba\x00\x88\xd3vI\x8c}\x9a\b\x86\xf9\xd8tq\xe0\xda`\"%MG\xe45Ʉ\xee4\x96$=b \xed\x14[\x11=\x14\xb9s6]^\x8c\b\xe7\r\x97\xbe\xacw\x9a.\x82\\p\x99\xecI\xd8\xe4\xc0\xa7&\x87\x19VΈ\x024S/\xdb\xef\x19F.\x1b\x1dl1\xa3pD\f\xf4I\xc5\xf1\xcc=n\x14\xc7\x1cl\xda\n\xaefA[)\xe3#\u007f\xd4I9?\x85>%\x9f\x05L+~\x06W<\x91\x81\xc1-\x1a\x94%&\xc7u*\x9d\xc9 \x1b&\x1as\x8cǕ\x02Nx\xf5,ⷷ\xd7ɓ'&F\xecn~\xee\x19\xfeз\x15\xd8p\x1f\xe8Ο}q\xbd\r\x87y\x9f\xe6\x140\xd0\x02Cb\xda\a\t\x10\xd2:d\x1c\xd46K\x91\xca' \xc37\x18w\xbc\x0e\x1e,\xba\xcaCh!\xde\x03#\xdf)8\xfc\xeb\xee\xdd\xcd\xea\x9f9\xd6\xf7\xb7\x00V\x96h}^\xee\xb0E\xe9^\xf7\xa5\x02G+\frJ\xfc\xb1h\x99\x14[\xb4\xae\x88g\xa0\xb1\x9f\xde|\xces\x0f\xe0Ge\x00\xbf\xb0V7\xf8\x1aD\xe0x\uf593\xd2\b\x1b\xd8\xd1S\x84G\xe1j1\r\xa6=\aH\xbd\xe2\xb5\x1f\xfdu\x1d{@P\xf1\xba\x1dB#\x1ep\r\xaf|Zs\x80\xf9+\xd9\xceo\xaf\x8eP\xfdS0\xedW\xb4\xe8U\x00\xd7\xc7\xe1\xa1\xd1\x1d@\x06\xcb3\xa2\xaa\xf0\x90UM?\x1fT\xc8U\u007f\v\xca\x10\a\xa4\x1a\x90\xf0\x84Iz\xc1Q\"\x9f\x81\xfe\xf4\xe6\xf3Q\xc4c~\x81\x90\x1c\xbf\xc0\x1b\x10\xb1\xd8Ҋ\u007f[\xc0\a\xaf\x1d{\xe9\xd8\x17:\xa9\xac\x95\xc5c\x9cU\xb2ه\xf6\x881\tʄyp\xcdL\ueffa*\x13C;C\x88\xf6\xcb\xd8\xf6[2\xc9\xe9\xffVXG\xe3/\xe2`'\x9ed\xbe\x1f\xaf\xaf~\x1f\x05\xefċl\xf5H\x02\x1etd\xd8\xe58\x93\x98\xbd\x1f-N\xa9c&c\xed\xd7<+3t\xacʤb\xc3\xf6䩄\xed$\aƭ\x18VY`\x06\x81A\xcb4I\xee\x01\xf7\xcb\x10\xe25\x13\x14\x9f)\x04\xf7}\x0e`Z7\"\x1b\x8ac \x8fIh\xe4\x04\x15ڬ\xb2\xc7\ue795ð\xafsF\n\x1f\aK\x93\f\xcet\x96\\\x9d\xb3\xd4Q\xbfi\x8e\x16e\xd7Ρ,\xe1Ai\xc12\xe3\x06\xad\x13ef\xe2\xd5<\xd38!\xac\xc0\xcb3<\x88-\xe8L\xf1\x12E\x112\xbd\xbe\x80\xf1]\xc7\\\x85p\xbc<8\n\x91*t\xca[\xc7\x10\x97\xf9Rr\xb2\x86J\xabɐV|1ed\xa6\xf3\x98&G\x9d\xd1!\xd2y}\xed\x1b\xdeϨ\xb0C#?\xf24\xf8S\x97\xda\xfbTL\xbc\xb4\xc6.\x15\xe5\xe9㧕\xd3⽜\xef\xf0\xed,ã\xba\x8b\x96\xacw\xd0\xf6\x8fg\xe4\x8ad\x18\x90\v;}\x04#j\xc8}\x12M9\xfe\x96\x89\x069\xa4\xb7\x9d\xe9\x9e\f\xd5!\x95\rn\xc9\xdd\a\xd3K\xa5i\x84\xd7'\xaa5\x82\xf5}\xa2\v{\x82fg\x91\xfb\x9eF\x86\t\xf3\xe4u\xabL\xcb\\\xe8k.\xb3De\xd74l\xd3\xe0\x1a\x9c\xe9\xe6\xd3',\xb1EkYu\xce\x14\u007f\x0e\xabB\xc5\x1e\xb7\x00ۨ\xce\xf5%\xfb\xc8=^بS\xcf\xeb\x1ad\x8b\xe1\xb1:3*VlLڛ\xc6\xef\x19:\x82Ã\xa0G\xb5\xc1|\xd0\u007f\x89O\x00\xf0\x0fZ\xe7\x10Қ\x9c\x81\xf5\xde뤅\xc1\t\xa7|\x83\x8f\x99\xd1\xd9C\xdcp\xf22\x99Lf\xeeGo\rϺ\u007f<\xe8\x1c\v\xe22\xa8U\x93\x8cY9ր\xec\xda\r\x1a\xe2\xc3f\xefЎ\xddy\xae?\xe3\xeb\xba\x03\x1b\a\xfb\x93\xfc\x02\xa5X\xaa\x96L\xfa>*Y\x97S\xc0\x85\xd5\r\xdbg\b\xa7\x8b\xf8܍\x8c\x8b\\\xc0A\x9f\x93Qk4~\xea\xb9}%\x8f\xe9J\xc9#\x95F\xb2g!\xdd_\xffr\"\xd3\x13\xd2a5\t\x0eq\x9e\xd8\xf9\x03\x9d\xf2uN8\x91\xc4Xɴ\xad\x95\xbb\xbe:\xa3\x05w\xfd\xc2d\r\xb3\xe79\xec\xa9EUȉ\xaa\xf7-\xcf2\xd5\xf1\x13\xf09\xa8\xa3\xc5g\xa2P||\xceŠ;\xd4̐\xa5\xfb7\x81\xcb\xe9\xa3\xd5k\xb0\xc27:)\xf3\f\xa9hhCX\nN\x94Z)\x83\x19\x97\t\xf3\xb02\n\"c\xf8\xbfg\xfc\xc8\xea\xc9l\xd0#\xe7\x03ڱY>\x1c\xe96\xfdC\xd0\x1a~\xfdmqHlXI\xc5\x13\xf2\x9b\xe9\x1fYĔ3\xfdՄ\xffY*\x19*\t\xbb\x86O\x9f\x17\xe9\xd9\xf2>\xfd1\x04\r\xfe/\x00\x00\xff\xff\xb0\xddǼ\x99\"\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\x1b\xb9\x11\xbe\xf3Wti\x0f\xcaV\x99\xc3]'\x95\xa4x\xb3\xa5lJɮ\xac2e]\\>\x80\x83\xe6\f\x963\x00\x02`H3[\xfb\xdfS\x8d\a9/\x92\xa2*Z\xcf\xc5\x16\xd0h|\xf8\xd0/4'\xd3\xe9t´xBc\x85\x92s`Z\xe0W\x87\x92\xfe\xb2\xd9\xfa\xef6\x13j\xb6\xf9q\xb2\x16\x92\xcfᦱN\xd5\x1fѪ\xc6\xe4x\x8b+!\x85\x13JNjt\x8c3\xc7\xe6\x13\x00&\xa5r\x8c\x86-\xfd\t\x90+錪*4\xd3\x02e\xb6n\x96\xb8lD\xc5\xd1x\xe5i\xeb\xcd\x0f\xd9߲\x1f&\x00\xb9A\xbf\xfcQ\xd4h\x1d\xab\xf5\x1cdSU\x13\x00\xc9j\x9c\x83V|\xa3\xaa\xa6F\x83\xd6)\x836\xdb`\x85FeBM\xacƜv-\x8cj\xf4\x1c\x0e\x13aqD\x14N\xf3\xa0\xf8\x93\xd7\xf31\xe8\xf1S\x95\xb0\xeeߣ\xd3?\v뼈\xae\x1aê\x11\x1c~\xd6\nY4\x153\xc3\xf9\t\x80͕\xc69\xdc\x13\x14\xcdr\xe4\x13\x80H\x80\x876\x05ƹ\xa7\x94U\x0fFH\x87\xe6\x86T$*\xa7\xc0\xd1\xe6Fh\xe7)\xdb\xeb\x01\xb5\x02W\"m\xe9\xe9fB\nY\xf8\xa1\x00\x01\x9c\x82%BD½2\x80_\xad\x92\x0f̕sȈ\xb8L+\x9eɤ3\xca\x04\xce\xef{\xa3nG\xe7\xb0\xce\bY\x1cC\xf6\u007f\x06\xd5\xc1\xf3\xa0\xf83\x91<\x96\xe8e\x12\x9aFW\x8aq4\xb4y\xc9$\xaf\x10\xc8r\xc1\x19&\xed\n\xcd\x11\x14i\xd9\xe3Nw\x91|J\xfaZ3\x97\xb0s\t\x15A\xb6\xb3\xfdS{\xe8ܾ\x0f\x8a\xc7\x05\x10\x8d\x1a\xacc\xae\xb1`\x9b\xbc\x04f\xe1\x1e\xb7\xb3;\xf9`Ta\xd0\xda\x11\x18^<\xd3%\xb3]\x1c\v?\xf1\xba8V\xca\xd4\xcc\xcdAH\xf7\u05ff\x1c\xc7\x16\x17eN9V\xbd\xdf9\xb4\x1d\xa4\x8f\xfdဖ\x9c\xad\x88\xd7\xffM\xe0.\tҭ\x92]^\xdf\xf7F\xc7\xc0\xb6\x94\xa6@\x9c\r\x82hG뻢\xab\x8f3\x17\x06\xc2\xf4\xe6\xc7\x10\xca\xf2\x12k6\x8f\x92J\xa3|\xf7p\xf7\xf4\xe7Eg\x18@\x1b\xa5\xd18\x91\xa2k\xf8ZY\xa55\n]f\xafIa\x90\x02N\xe9\x04mp\x8a0\x86\xa3f5\xff\xce\xc4\xfck\xaf;X\aN\x17>\x9f\xebN\xdc\x00%;\x10\x16X\\\x1aNq :\x85\xec\x8f\xffX\xdc\xcf\xfe9\xc6\xfc\xfe\x14\xc0\xf2\x1c\xad\xf5\xf9\x1ak\x94\xee\xcd>gs\xb4\xc2 \xa7\xc2\x05\xb3\x9aI\xb1B벸\a\x1a\xfb\xf9\xed\x97q\xf6\x00~R\x06\xf0+\xabu\x85o@\x04\xc6\xf7\xe1/ٌ\xb0\x81\x8e\xbdF\xd8\nW\x8a~\xd2\xda3@\xd6\x15\x8f\xbd\xf5\xc7ul\x8d\xa0\xe2q\x1b\x84J\xacq\x0eW\xbe\x12<\xc0\xfc\x8d\x1c\xeb\xf7\xab#Z\xff\x14\x1c芄\xae\x02\xb8}\xbek{\xe4\x01\xa4+\x99\x03gDQ\xe0\xa1\x10\xed\u007f>xSH\xfc\x1e\x94!\x06\xa4j\xa9\xf0\x8a\xe9\xf6B\x00\xfd\xf9헣\x88\xbb|\x81\x90\x1c\xbf\xc2[\x102p\xa3\x15\xff>\x83Go\x1d;\xe9\xd8W\xda)/\x95\xc5c\xcc*Y\xedB\xb5\xbfA\xb0\xaaF\xd8bUMC\xbd\xc1a\xcbv\xc4B\xba8\xb27\x06\x9a\x19w\xd2ZS\x95\xf1\xf8\xe1\xf6\xc3< #\x83*|\xbc\xa3\xec\xb4\x12T5P\xb9\x10r\x9e\xb7\xc6A\xd2L\x9fm\x82\xf98\x05y\xc9d\x81\xe1\xbc\b\xab\x86\xb2Pv\xfd\x12?\x1e\xa6\xfe\xf4\x8d\x94\x00\xfd\xc0\xf1͒\xe83\x0f\xe7+\xd5g\x1c\xae\xfd\xd6:y\xb8u\xb3D#ѡ?\x1fW\xb9\xa5\xa3娝\x9d\xa9\r\x9a\x8d\xc0\xedl\xab\xccZ\xc8bJ\xa69\r6`g\xfe\xc9<\xfb\xce\xff\xf3\xe2\xb3\xf8\xd7\xf5s\x0f\xd4y\xf4\xbf\xe6\xa9h\x1f;{ѡR\xad\xf8\xfc2q5\xacTN0\x11\f\xe0\f\a\xb1\x994\xf22\x8a\xf6\x13*E?B\xaf\x11oE\xe3!\xf6R\xbb\xa2\x874\x95\xbd]\x84\xd3\xf1\xf7^OF+>\xe9\x93\xd6v\xc9\xde\xe4\xc1\xa1\xfa\x13][\xed\xcdv\x9a\x9c\xed\xd3\f\x9fʾ\x83v\xc9c9t\xed\"\xef!f\xbb\xd4ˣ\aˋ\x9f˹\xa2\xc7@\xf7W\x8b\xd36p3\\\xe1{S\x86G\x9f\x105\xfa7hh8n\x99M\x9b\x8c\xdd7\xb4\xf4\x85\xa5>O\x92:\xe4\xbeT\xa7\x97Ċ\x89\n9\xec\u007f7\xf1\xcdq\xeb\x9b4\xd7c\x95iR\xd4X\xe4>n\x8c\x80\x1e\xaeK}O\xce\x1cNI\xc5@B6UŖ\x15\xce\xc1\x99f8}½j\xb4\x96\x15\xe7\xfc\xeb\x97 \x15^\xf1q\t\xb0\xa5j\xdc\xfe\x19\x1f\x1d-Rqm\xa3\x15\\\xd6J(\x99=\a\xe5\x81d\xc6,n\xef\xf2\xa7M\x0eN\x84\xb2{\u070e\x8c\x0e\xfa\xd0\xedɛdB#s?y븈\x80\xb8\xd19\x0e\xa2\x18\x94\xaaJ֭\x1c%\xa5\xa6^\xa2!\"|\xf3;1\x92\x02\xc7X_Ŀ\xa7\x0eL\x1e4\xa4X\x18T\xc5\x17bΤo\x13\x92\xfd:\x05\\X]\xb1݈\xdet\x12_2\x91\xf9\x92\x1f\x1d,&y!\xb9\xbf\x9f\xbb\xb4\x9f\xb3o\xee\x8f\x17tc?\x15\x8c\xddB\xbb\xefߛ\xdf\xff\xaa\xf1:;\x9c(\xe2\xacc\xc6=7\xec-:\xc2\xe7\"\x9eW=\x1e\xefڡk\x18\xa8\xba\xdb\xfc\x911j\x94\xa8\xc1\xa0G\xce[\xbac/\xb4=\xd2,\xf7\x9d\xfe9\xfc\xf6\xfb\xe4\x90\xeeXNU;\xf2\xfb\xfeOڱVI\xbfP\xfb?s%\xc3O\xcav\x0e\x9f\xbfL 6M\x9f\xd2\xcf\xce4\xf8\xbf\x00\x00\x00\xff\xffe\xe5\xd5&\b \x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc\x18\x93Z\x98\x92\x9a\x01fAW֬#D{zSI\xa4\xbd\x16ĺv\xacV\x95\x1f{h\xe0:\x14\x8fS\x04V\xcc`\x0e*\x88}%Є\xb5r\xc7\xfeV\xb1\\\x8d\x82n\x90\xf7\xae\x86`+\x14`P`f\x95\x1eR2\x85\x9e\xfeIQ\x96#t\x8c\xa8;\xfc\xb7\x88M\x80\x04\x12\xf3\xe7-϶\xde\v \xd9tp Wh\x9c\xe6 Ou?\x86$\xcc\xf1>,2\xa5;\xdag\xe6L\x1d\u008b\xe9\x93\xf6Iз\xed3\xa3y\a\x9a%\xbc\x8f\x9a\xce\xf6\xf9\xf7$lmJN\x10ڻ\xc1\xd4\xd7\x15Z\x17U\x91\xb7\u007f\xb7\x06,J\xbb\xbf\x02n\xeb\xb7s\x10\x99\x10\x9d\xf5\xffƌ9^\xe2\xef\x0eg\xbe\xaa\xc4Ore\x0e\"q\xa5Y\xfeo\xc8\x14g,\x1e\x83\xadHfȯ\xddYW\xc0\xd7\rC\xf2+XsaQ\x1fp\xe6E\xe7\xe55\x88\x91b\xef\xe8)\x98Ͷ\uffd0gc\xda\fT\"]\x0e'{\x9f\xb8\x0e\x12\xfa\x86y\x06.\xb8\xf8\x95k,|\\\xfc\xc9Q\xb3}\xe3\x02\x8aw\x1f~\xc4|\x8a<\x90&y\x03D\xde\x1dl\xb6\xbbtp\xf4S\xd1\b\xaeO\x134\xf9\x8c\xc7\x150x½\xf7X\x98\x04b\x0e\xb3\xceߍ\x86OC\xe2\xb8ԋw\x8fq\xef\xc0\x84\\\xca\xec\xecTQ\xf0\xcf\x13F\xfc\xfd\xd8\xd3# \xed)D\xb8\x9e\x92\xf4\xc2\x11\xc2E\xde\xe9\xc4\x03\x97\x17\xabu\xd1\xb7]o\x87@\x9c\xd5ۡ\x05\x1aCwJj\xe6\xae\a\x80Le\xed8\xbb\xbd7Rs\x84\xe7\xb3B\nP1\xf7I/2\x9f\xc1\x8f\xf6\xbdK#e\xf0(vǻ.I\x9c\xedNH\xcf\x12}Y\xb4\xed\n\v\x97\x14\xd4;\\T\xf2I\xaag\xb9p1\xa5\x99\xcd\xd67\x8b\x9f\xac8\xbe\xa6\xd2\xe8\x8bW\xba\b\xd4\xf6\xf7\fJ!\x99\xcdG\x05\xc6SR0\xa7\x86|\x1b\xebȟ\xb3\xbb\x98Z\u007fbr\xa89\xde\xfa\xfe\xd3\xd4ަ\xbb\xf8\xac\x8e\xff\xf0\xbcE\xbbE]7\xb6.\\\x0foL\xad\xb6\xa5\xc9\xd6\x15n\x9a\x9dH~jo\xca7\xe1\x1d\xb4?\xc5}eY\tqE\x82\xcd*a}+\xaa\xae\"B\x94\xd4\x03\xb4RJ ;l\x8bM)\xa2ϕ\xce\xfb\xfd`M\xe9\xban\bS\xf5\"\x11\f=/}\xd7g\xb7.ۯ\x81\xbb\xecO\xbdӿ\xbcU,\xa1\xbc=SԞn\xa0\x9b\xa2\xd7Pl\xba\x14ke0\x8c\v\x9d\x95\xdf\x14\xf9f\n\xd1\xe3\xe5\xe7\x90lE\xcbvo\x97\xfd\u007f\xac\n\xc5h\x97Y\x88\xa0\xf2\xbcm\xf2\x04\xce\xf2ʜ\xefx^1ѓ\xc0\x0e\xcdZ҂\xd2 \xb9\x88ա\x88\xe6\xf5\xfc\x1e\x8d\xe1c\xe9\xf3\xd1G\x9f\xd5iw'\xadf}r\xa5\xba_\x89\x1e\xd1\xe0Ǧf\xd3[\xf2\xd2k\xd1\xd3\xc5\xe3c*Ї\xf5\xe5Q\xa0\xf3u\xe7\x14Ou\xa6\xc6|Be9\xb1\xab\xe8\xc5\t\xe8\x94\xda\xf1I\x15\xe3\xd9ƛ\xc4:q\xbf\x02<\r\xf2\x88\xeap\x12q\xe6+\xc1G\xd7\u007fC\xbdu\x12\x8f\xe4\xaao\xa4\x9e;\tx\xb4\xd6;Uŝ&y\xa4\u009b^\xbb\x9d\x04\xed\xea\xba\xf3\x15\xdb\xd7\xeb\xcbz\r\x17y\\\xd5\xccV]_\xe4B'\xd4U\x8f\xa9\xa6\xceR\xec\xc4\xcaiS\x19\x1dY\xf7\xd8zi\xbf\x1e:\x024\xa5J:R\x05\x1d\x818Y\x1bM\xad}\x8e\xc0\x9e1\xbb\x93R2\xf1g\xe3u\xff\xc6ʒ\xcb͐\xf3\xa9\xf21)\x1b\x83\xd2iw͞pt\x9d\xe3^X\x11[\xd2\u007f\x90\x18\tA\xea\xa4\x13\x97V-\xe1\x9d\xdc\x0f\xe0\xbaf\xe8\xa8\xcb\xdd\xffb\x85\xb6\xf5̅\xe8~\x95\xe1\xc0vA\x85\x0f\x9cL<\x10\xa6\x81c\x1f3E\x99\xa2t\xcfߝ\x8b8>\x1e\f鈴\xa6\xfd\xe7\x98\xeb\xcc\xed\xf6D\xff\xb9\xa8\x84\xe5e\xf4\x10\x97Z\xed\xb8K\x8amq\xdf\xd0\xf3\x0f徇X\xed\x1d\xa4\x8f\x0f\xcd\xf9Z\x1e\x84\x02,v*\x9eQ\b`f\x88~\xe6\xbf\t\xcc\xd4\xc2}\xe6C\x9c\xac\xe5!|;x\xe5\xce`,@\x95\xf5\xd7j\x05\x81q\xdf\x15\x9aH\x02`ԺL{\xb8\xde\x19w\xef\xfe\xacP\xefA\xed\\\x154\xb8<3\r\xc7^S\x98J\xb4\x1d\x1eA\x01\xfa/Q\x0f<\xffVc\xc0;\xe9mp\x14\xec\xc1\x1e\x1d\x1cRZm\xb4C\xfa\x99\x02\x99\x91\xa1Q\xa8R5\xb3\xe3\xf20ijR\xbbu\xcf\x1b\xfb\x1c\x1f\xfd\xcc\xfa\x1dg\x89\x80N\x8f\x81&@\xa6vߦe\xecg\xbbm\xcf\x15\v\xcdEC\xc9n`Z7\xed9\xbah\x8f\xe8\x9e=\"*:..J&SJ\x97\xecY\xa2\xa33\xc6G爐N\x8b\x91f@\x1et\xbf\xa6\xf4\xb5&\x15\x99\x92K\x14)E\xa5\xf9\xba\xe6t\xbfjB\x9fjB\xf1cn\xa7\t\xfd\xa8\xc7\xf5\xa1&\xd0\xf0L\xd1ә\xe2\xa7sDP獡f\xa3\xa8Yə\xfc\xfb\xe4\x1cy]M\xfd\xa0r\xbcW\xda\xce9\xfc\xf7\x87\xe3#\x15\xacN\x10\xa4D\x0e\xb2\x1e\x1aA\xca\xf9\xf2\xc1\x8f?\r\xa9x\xb1)\xac\u007f\xffy\x0e\x9f\x87f\xe04\"\xe4\x92\xd6\xf1Y\x04\x0f\x9a\xefp1\x92\x95f\xab,|\xb7\xe3,\\(\xa2\xaa<\x04!\xfa\xfbs`\xf9h\x99\xad\x12\x11\xf5c{\xb8\xf2l۩\xe7G\x10\xb2\x91\x99\x15\xae\fe%J\a<.\xbd\xfbi\xf9o˟.\x00\n\x89n\xfagZ\xa1Ҥ\xaao\x80\x1b\xc6.\x008\xa9\xf0\x06$*-$\xaa\xe5\x0e\x19J\xb1\xa4\xe2B\xd5X\xd8\xc56R\x98\xfa\x06\xda?\xfc\x9c\xb0\x11\x8fă\x9f\xee\xde0\xaa\xf4\xafݷ\xbfQ\xa5\xdd?53\x92\xb0v1\xf7RQ\xbe1\x8c\xc8\xe6\xf5\x05\x80*D\x8d7\xf0\xd1.S\x93\x02\xcb\v\x80\x80\x93[v\x11v\xbd{\xebA\x14[\xac\x88\xdf\x0f\x80\xa8\x91\xbf\xbb\xbf\xfb\U000af3fd\xd7\x00%\xaaB\xd2Z;ʄ\xbd\x01U@\xe0\x8b\xc3\xcdn\xc01\x01\xf4\x96h\x90XKTȵ\x02\xbdE u\xcdh\xe1\x88\xd8@\x04\x10\xebf\x96\x82\xb5\x14U\vmE\x8a'S\x83\x16@@\x13\xb9A\r\xbf\x9a\x15J\x8e\x1a\x15\x14\xcc(\x8dr\xd9\xc0\xaa\xa5\xa8Qj\x1a\t럎\x1cu\xde\x1e\xe0\xf2Ƣ\xebGAi\x05\b\xfd\x96\x03ɰ\f\x14\xb2\xbb\xd5[\xaaZ\xd4\x0e\xd1\t(\x11\x0eb\xf5'\x16z\t\x8f(-\x18P[aXi\xe5n\x87\xd2\x12\xa7\x10\x1bN\xff\xbb\x81\xad,\xa2vQF4\x06~\xb7\x0f\xe5\x1a%'\fv\x84\x19\xbc\x02\xc2K\xa8\xc8\x1e$\xdaU\xc0\xf0\x0e<7D-\xe1w\xc7\x1e\xbe\x167\xb0պV7\xd7\xd7\x1b\xaa\xe3\xf9)DU\x19N\xf5\xfe\xda\x1d\x05\xba2ZHu]\xe2\x0eٵ\xa2\x9b\x05\x91Ŗj,\xb4\x91xMj\xbap[\xe7\xee\f-\xab\xf2_\x1a\xb6\xbd\xe9\xedU\xef\xad\xe4)-)\xdft\xfepb>\xc1\x01+\xf0^\x96\xfcT\x8fEKh\xfb\xcaR\xe7\xe1\xfd\xe3箜QuH}G\xf7\x8e\xf0\xb5,\xb0\x04\xa3|\x8d\xd23\xd1I\x9b\x85\x89\xbc\xac\x05\xe5\xda\xfd(\x18E~H~eV\x15Ֆ\xef\u007f\x19TV\xa0\xc5\x12n\x9dR\x81\x15\x82\xa9K\xa2\xb1\\\xc2\x1d\x87[R!\xbb%\n\xcf\xce\x00Ki\xb5\xb0\x84\xcdcAW\x1f\x1e\x0e\xf6T\xeb\xfc\x11\x95\xd7\b\xbf\xc2\xe9\u007f\xac\xb1\xe8\x9d\x18;\x8d\xae\xc31\x87\xb5\x90=\xe5`\xa7,{@Ӈ\xd6>\xfe\xf4[\rv\xf8\xcf\xc1V\xfe\xa3\x19h\xe5\xc7n\xc2p\xfa\x97A\xa7\xe2\xfc\x89ŁJ\x19\x80\x84\xb8?'\x16\xcb\xc1\xff#4\xb5\x0f~-\x98)\xb1l\xb4\xed\x00\x97\x83\x1d\xbf\x1fLp\xe6\x88Pn\xe5ߪ\u007f\xbbm\xde\xfek\xd5ib\xc7D\"X\t\xa4\xdc\xc3\x03\xca\x1d\xb2IJۇj\xac\x12\x9b\x9b\xc4\x0e\x9c\x9d#+\x867\xa0\xa5\xc1\x11\xca\x10)\xc9~\x840\xd16\xe7ҥ\x19\x1f\x14\x02\xa3\x05v\r\x85'\x8d72D\x0ew\x04\xdf9U\xa8\xb2\xea,by/\x18-\xf6\xb3\xa4IM\x8a\xc7-\x1c\xbe(\xc1+ܒ\x1d\x15F&p\xb2GҎ}j-i\xabM\x85Uf\x01Jy\x1a\xc6Ijm\x85x\x9ac\xfe/vL\xab\xb6\xa1pn]\xc4E\x06v\a+\xbaB\xc0\xafX\x18\x9d\xd8&@i\x9c\x05\x11\x12j\xa1\xf48\xe3Ǖ\x0fx}0&\xb50%5\x03̂\xae\x8c\xac\xb3\x88\xf6\xf4\xa6\xe0h\xf7ZYֵc\xa50~졁\xebPi\x9f\f}\xdb>3\x9aw\xa0Y\xc2\xfb\xa4\xe9l\x9f\xff\x9f\x84\x8d\xa6\xe4\x04\xa1\xbd\x1bL}]\xa1uQ\x95\xf5\xf6\xefրU\xad\xf7W@u|;\a\x910\xd6Y\xff\x1f̘\xe3%\xfe\xeep\xe6\xabJ\xfc$W\xe6 Z\xae4\xcb\xff\x03\x99\xe2\x8c\xc5c\xb0\x15\xd9\f\xf9\xad;\xeb\n\xe8\xbaaHy\x05k\xca4\xca\x03μ輼\x061r\xec\x9d}*\xa2\x8b\xed\xfb\xafֳQm\x06*\x93.\x87\x93\xbdO\x1c\x83\x84\xbea\x9e\x81\v.~\xa5\x12+\x1f\x17\u007fv\xd4l߸\x80\xe2\xddǟ\xb1\x9c\"\x0f\xe4I\xde\x00\x91w\a\x9b\xed.\x1d\x1c\xfd\\4\x82\xeb\xd3\x04M>\xe3q\x05\x04\x9ep\xef=\x16\xc2\xc12\x87h\xe7\xef&ç!q\\\xeaŻǸw`B.evv\xae(\xf8\xe7\t\x13\xfe~\xea\xe9\x11\xd0\xee)D\xb8\x9e\x92\xf6\x85#\x84\x8b\xbc\xf3\x89\a./\x16u\xd1{\xc8a5J\xd5zrk\x8d2$\U0007c88d\x11\xc0\vc\xa3\xb9$]\x93\xa0#Mf\xd5\x12xF*|\xcd#g\x8b\xc7x\x8d\x96.G\xfa\xdb\xef\xbfvr\x8c\xf6\x84\xda\xdf]D^۫-DU\x91\xc3*_\xd6Vo\xfd\xcc(\xd3\x01\x90\xe7\xbe\xdc\x18w.\xf3ݽ(C\xae\xbe\xf7L\xf5\x96r \xf1\xf8\xa3\f\x02E\xa0\x16\xf3\x9a\xc8?[\xa2`\x85ț\xdc\xf8\xf7`\xaf+\xca\xef\xdc\x02\xf0\xf6\xd5\xed;\xb4\xe4:\x89\x9d\x91\xd4\rC\x9b\x17\xce\xe2\xe4\xbaF\xa2\x84\xe7-J\xecI\xc50\xe1m=\xc6L\x90\\\xe8n^\xc1\u00adE\xf9F\xc1\x9aJ\xa5\xbb\x1b\xcd\x158\xa3r\xc5\xe1H\x0e[\xec>\xd3\n\x85\xd1'\xf0\xe0};\xbbW\xa0\xad\xc8WZ\x99\nH%L\x86q\xf7\x8f\xb5/\xb4j\xaa\xa8\x81\x03τꦞ\xe42,ZX.\xd5\fu.\x8bW\xb8\xb6\xea\xa8\x10\\\xd1\x12e\xac\xf2{\xceRa\x0f\xee\x9aPfR\xe5\x9b\xd4sl\x98\xca\xdfKyR\x94\xfa\xc9\xcf\xecd\r\xb7\xe2\xb9O\xa0l\x12l\xc9\x0e\x81\xae\x81j@^X\xbe\xa0\xf4*\xdb-\x11\x88\xe1H\x93-\x96y\n\xde>\xc8M\x95G\x80\x85;ٔO&ź\xc3?\x10\xca\xce\xc16+y\xa7\x1f\x8d?\xda\xd9\xdf\xe4h4J%߄\xad\x10\x1e\x90\x94\xfbx>\x88\xd66Tu2 @\x1a\xdeՈg8\x19\xc7\xc4wa\x17\xaf\x19\xb8QN3\x18{\x90ϧ\xba\xeb\xedX\x10g\xf5v\xec\x02\x8d\xa1;%5s\xd7\x03`Met\x9c\xdd\xde\x1b\xa99\xc2\xf3Y\xa1\rP\xb1\xf4I/k>\x83\x1f\xed{\x97F\xca\xe0I\xec\x8ew]\xb28\u06dd\x90\x9f%\xfa\xbah\xdb\x15\x16.)(w\xb80\xfc\x89\x8bg\xbep1\xa5\x9a\xcd\xd67\x8b\x9f\xac8\xbe\xa5\xd2\xe8\x8bW\xbe\bD\xfb{\x06\xa5\x90\xcd\xe6\xa3\x02\xe3))\x98SC\xbe\x8du\xe4\xcf\xd9]L\xad?19\xd4\x1co}\xffino\xd3]zV\xc7\u007fxޢޢ\x8c\x8d\xad\v\xd7ÛR\xabmi\xb2u\x85\x9bf'+?ћ\xf2Mx\a\xedOi_\x99\x1bƮ\xac`\x13ôoE\x95&!DY=@+!\x18\x92ö\u061c\"\xfa\\\xe9\xbc\xdf\x0f֔\xaecC\x98\x88\x8b$0\xf4\xbc\xf4]\x9fݺl\xbf\x06\xee\xb2?q\xa7\u007f{\xabXFy{\xa6\xa8=\xdd@7E\xaf\xa1\xd8t)\xd6\xca`\x18\x17:+\xbf+\xf2\xcd\x14\xa2\xc7\xcb\xcf!ي\x9a\xec\xde.\xfb\xffh\x11\x8a\xd1.\xb3\x90@\xe5y\xdb\xe4\t\x9c\xe5\xe5%\xdd\xd1\xd2\x10֓\xc0\x0e\xcdZ҂\x90\xc0)Kա,\xcd\xe3\xfc\x1e\x8d\xe1S\xed\xf3\xd1G\x9f\xd5iw'\xaff}r\xa5\xba_\x89\x1e\xd1\xe0Ǧf\xf3[\xf2\xf2k\xd1\xd3\xc5\xe3c*Ї\xf5\xe5Q\xa0\xf3u\xe7\x1cOu\xa6\xc6|Be9\xb3\xab\xe8\xc5\t\xe8\x9c\xda\xf1I\x15\xe3\xd9ƛ\xcc:q\xbf\x02<\r\xf2\x88\xeap\x16q\xe6+\xc1G\xd7\u007fC\xbdu\x12\x8f\xec\xaao\xa2\x9e;\tx\xb4\xd6;Uŝ&y\xa2\u009b_\xbb\x9d\x04\xed\xea\xba\xf3\x15\xdb\xd7\xeb\xcbz\r\x17y\\\xd5\xccV]_\xe4Bg\xd4U\x8f\xa9\xa6\xceR\xec\xc4\xcaiS\x19\x1dY\xf7\xd8zi\xbf\x1e:\x024\xa7J:R\x05\x1d\x818Y\x1bͭ}\x8e\xc0\x9e1\xbb\x93R2\xf1g\xe3u\xffN\xea\x9a\xf2͐\xf3\xb9\xf21)\x1b\x83\xd2iw͞pt\x9d\xe3^X\x91Z\xd2\u007f\x90\x98\bAb҉r-\x96\xf0\x8e\xef\ap]3t\xd2\xe5\xee\u007f\xb1b\xb7\xf5L\x19\xeb~\x95\xe1\xc0vA\x85\x0f\x9cT:\x10\xb6\x03\xc7>fJ2EȞ\xbf;\x17q|:\x18\xdeMcM\xfb\xcf)י\xea\xed\x89\xfese\x98\xa6u\xf2\x10\xd7R\xec\xa8K\x8amq\xdf\xd0\xf3OᾇX\xed\x1d\xa4O\x0f\xcd\xf9Z\x1e\x84\x02$u*\x9e\x911 j\x88~\xe1\xbf\t,\xc4\xc2}\xe6c9\x19\xe5!|;x\xe5\xce`*@\xe5\xf1k\xb5ʂq\xdf\x15\xaaD\x02`ԺL{\xb8\xde\x19w\xef\xfe2(\xf7 v\xae\n\x1a\\\x9e\x99\x86c\xaf)\x94am\x87GP\x80\xfeK\xd4\x03Ͽ\xd5\x18\xf0\x8e{\x1b\x9c\x04{\xb0G\a\xc7*\xad6ڱ\xfa\xd9\x062#C\x93P\xb9hf\xa7\xe5a\xd2\xd4\xe4v\xeb\x9e7\xf69>\xfa\x99\xf5;\xce\x12\x01\x9d\x1e\x03M\x80\xcc\xed\xbe\xcd\xcb\xd8\xcfv۞+\x16\x9a\x8b\x86\xb2\xdd\xc0\xbcn\xdast\xd1\x1e\xd1={DTt\\\\\x94M\xa6\x9c.ٳDGg\x8c\x8f\xce\x11!\x9d\x16#̀<\xe8~\xcd\xe9k\xcd*2e\x97(r\x8aJ\xf3u\xcd\xe9~Ռ>Ռ\xe2\xc7\xdcN3\xfaQ\x8f\xebC͠ᙢ\xa73\xc5O爠\xce\x1bC\xcdFQ\xb3\x923\xf9\xf7\xc99\xf2XM\xfd(J\xbc\x17R\xcf9\xfc\xf7\x87\xe3\x13\x15\xacN\x10$X\t<\x0eM \xe5|\xf9\xe0ǟ\x86T\xba\xd8\x14ֿ\xff2\x87\xcfC3p\x1a\x11\xeb\x92\xc6\xf8,\x81\x87\x9d\xefpQ\x9c\xd4j+4\xfc\xb0\xa3$\\(\"L\x19\x82\x10\xf9\xe39\xb0|\xd4D\x9bLD\xfd\xd8\x1e\xae\xb4\xd8v\xea9\xcf\x18ˊ\xb2s'\xd1\xc1\x9e\xac'\xe4\x01\xb9\x83\xe6\x8c$\xa7\xec\xaa\xe3z\u007f\x9b2E\xe6'\xcf'\u007f\xec\xec\xc93r`],iý($-]N(P\xcc*\xe9\x8c\xef\x12\xa7-K\xe6ײ'\u007f';O\xac\x04\xa1\xc6>\x91\xcd\xf9\f\xf6o\xa5\xe7\x84>V\xc5\x16K\xc30\xe3\xf6\x9a\xc7\xce\xd0\xf9\xfbk\"\xe0ԙ\x14\xfd\x1bl,];\x9d\xae֤\xf5o\xca\tD\x0f\x90-{\x12P\xbb \xbd\xed\xf67j\x14\xd6R+S\x14\xa8\xd4ڰX\xfeu\xb7\xa4a\x19\x87'\xbb\x15#\x0eGܱ\x93v\xdb\x16\x9d;\x82.28\xa3\x12jrBE\x16\xa4\xd6F\x061/\x8c\x94\x0ee\xff\x9fX\x0f\xee5\xea\x81\x1dWZ\xa1?\xa7w\x93ܴ\x84\xdc\x0eg\xb8\xdb\xc3d\xe9\xb7\xe6:\x89\xba\u05ffx?gx/\x19\xb8\xceIմ\b\x95\xcb\x0el\x0f\xc6\xf9\xfd\x164\x96\x80;\xe4 \xb8\xeb\xbf\xc5r\xaa}\xec\xb3\v\x8c\xe5\x0e\xe5\x1b\xd5\xc01*t\xa3\x12\u007f\xfeA\xa8\xa6|\xf3A\xc8{f6\x94\u007f\x8ag\xf2\xb8\xc1\xf7DjJ\x18\xdb\xfb\xfd$\xe6\xde\xc6Ü\xf8o~\xf6\xc8\x1fSL\n8φ\t~X\x1b5Q\xee\x0f\xbak\xea_\t\xa3\xbb\xa7\xe2\x8dj\x0fL:e\xeb\xa0-\xe1\xa3\xd0\x18\xb3q\xb4\x0f\x94*X\xa1\xd2\v\\\xaf\x85\xd4>J[,\x80\xae\x83\xa2NE\x1f\x842\xe7k\xf8\v\xfc\xac\x03\xd2T\x84\x1b\xcb'$\x10\xbe\a\xe9N\x85sR*\xb2\xf7=w\xa4(\x8c\xd5\x03\xd7J\x93\x94A{\x91k뜛 \xcd#\t\x8b\xbe\xa7\xd6\x1d\xdf|\x1eh\xaa\x15J\xd7Tl\xff\xf6\xa4s\xdf\x1bx\x15\x94\xacD\x80\xfb*\xa1\xf3\xb9\x13({\x9cӁ\xf3\x94\xf2q\xff\vM\xd8ݸ\xa3\xd6\xef\xb0m\x06G\x04\xdc\xf4!\x1a\xbd\x9b\xca\xc6\xfb\x87\xa8\x8aS-ϊ-\xe1\x1b+>R\x98\xcd6\x8a\xe0\x98\xa6\x1eK\x94\x18wQZ\xedN\xaa\x8aYmm$\xef$eB\x9e\xbbl\xb7;\x05t\x9a\x84S~fϴ\xcey\x9a\xbd\xc1/s!\xdc\xc2ց\xf8~M\xff\xae\xd1\xdd\xefs\x9c\x80/\a\xc3\x0f:'\xad;\xd0B\f\x86;A\x9c\x1f\xe8:^t\xbbb\xf8\xe3`\xc47\xee\x80|&\x92S\xbe\x99C\xfe\x8f0,\xe1\x03\x05\b\t/(\x81D\xe3\x17\x1d\xe5\x05\xc5M\x8e\xdc\xe5\xd8xF/\xf0\x83\x92gh\xf0\xd2\tr\xd9!rX)\xbci\xe3\aR\x14X\xebЙܽ\xc6\xf9\xf2\xd2\xfd\x88\xf74\xbb\x9f\x85\xe0^-\xa8\x1b\xf8\xcf\xff\xba\x88\b}\x89\xd71ۗ\xff\x17\x00\x00\xff\xffF\xa1\x18'\xf3Z\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=]s\x1c7l\xef\xfa\x15\x18\xf5\xc1\xed\x8c\xee\x14O\x1f\xdaћ++SM\\\xdbc)zi\xfb\xc0\xdb\xc5\xe9\x18\xed\x92\x1b\x92{\xf25\x93\xff\xde\x01\xc8\xfd\xbc\xfd\xe0\x9e\xa5i\x9a\x11_\x12\xed\x91 \b\x80\xf8 A\xf8l\xb5Z\x9d\x89B>\xa0\xb1R\xab+\x10\x85\xc4\xef\x0e\x15\xfde\xd7O\xffj\xd7R_\xeeߟ=I\x95^\xc1ui\x9dο\xa1եI\xf0#n\xa5\x92Nju\x96\xa3\x13\xa9p\xe2\xea\f@(\xa5\x9d\xa0ϖ\xfe\x04H\xb4rFg\x19\x9a\xd5#\xaa\xf5S\xb9\xc1M)\xb3\x14\r\x03\xaf\xa6\xde\xff\xb4\xfe\x97\xf5Og\x00\x89A\x1e~/s\xb4N\xe4\xc5\x15\xa82\xcb\xce\x00\x94\xc8\xf1\nl\xb2ô\xccЮ\xf7\x98\xa1\xd1k\xa9\xcfl\x81\t\xcd\xf6htY\\A\xf3\x83\x1f\x140\xf1\xab\xb8\v\xe3\xf9S&\xad\xfb\xa5\xf3\xf9\x93\xb4\x8e\u007f*\xb2҈\xac5\x1f\u007f\xb5R=\x96\x990\xcd\xf73\x00\x9b\xe8\x02\xaf\xe03MU\x88\x04\xd33\x80\xb00\x9ez\x05\"M\x99T\"\xfbj\xa4rh\xaeuV\xe6\x15\x89V\x90\xa2M\x8c,\x1c\x93\xe2\xce\tWZ\xd0[p;l\xcfC\xed7\xab\xd5W\xe1vW\xb0\xb6\xdco]섭~\xf5$\xf2\x00\xc2'w ܬ3R=\x0e\xcd\xf6\x01\xae\x8dV\x80\xdf\v\x83\x96P\x86\x949\xab\x1e\xe1y\x87\n\x9c\x06S*F\xe5\xdfD\xf2T\x16\x03\x88\x14\x98\xac{x\x06L\xba\x1f\xe7p\xb9\xdf!d\xc2:p2G\x10aBx\x16\x96q\xd8j\x03n'\xedh\x1ao`%2؋\xac\xc4\v\x10*\x85\\\x1c\xc0 \xcd\x02\xa5j\xc1\xe3.v\r\xff\xa1\r\x82T[}\x05;\xe7\n{uy\xf9(]\xa5b\x13\x9d祒\xeep\xc9\xdaRnJ\xa7\x8d\xbdLq\x8f٥\x95\x8f+a\x92\x9dt\x98\xb8\xd2\xe0\xa5(\xe4\x8aQW\xacf\xd7y\xfa\x0f\x15G\xed\xbb\x0e\xaeG\xfb\xcd7V\x84\x13\x1c \x8d\xe8\x05\xc6\x0f\xf5\xabh\bM\x9f\x88:\xdfn\xee\xee\xdb\xc2$m\x9f\xfaL\xf7\x96\x845, \x82I\xb5Ű\xa3\xb7F\xe7\f\x13UZh\xa9\x1c\xff\x91d\x12U\x9f\xfc\xb6\xdc\xe4\xd2\x11\xdf\u007f/\xd1:\xe2\xd5\x1a\xae\xd9\xee\x90\x1c\x96\x05\xed\xc0t\r\xb7\n\xaeE\x8eٵ\xb0\xf8\xea\f J\xdb\x15\x116\x8e\x05m\x93\xd9\xef\xec\xa9\xd6\xfa\xa12o#\xfc\xaa\xf6\xf8]\x81Ig\xcb\xd08\xb9\x95\to\f֞\xb5\n\xe8iP߆w-\xff\xc2j\xaa\xff\xb5\x87\x87\xd7eլh\xc9~\xb8\x1ds\xb81c$W\x1e\x1a\xe9\x14\xa5\xfb\xdc\x1d҂-J\x04(3\x98t\xb5^\xac};\x82\tAխGp<\xe2*\xff\x84yAjc\x06\xc5\xfbЍP$\xfa\xa4\xb5;U\x19\xfeJ\xcd\xea\xa0]\xe1H\xb9\xf1t;$\xbe\xede\x1a\xb4\xd7\x11Wa\x92\xb3\xd4\x12+\xef\x94(\xecN;\xb2q\xbatC\xbdz\v\xb8\xbe\xbb\xed\rjq\x9e\xb0b\x1bΌv\x1a\x9e\x85<\xe6\xb4o$\x97\xd7w\xb7\xf0@.\x11V0\xc1[rp\xa5Q\xac\x8e\xbf\xa1H\x0f\xf7\xfaW\x8b\x90\x96\xac\x95*\xbb|1\x02x\x83[\xda\xf4\x06\t\x06\r@ch\x0fXFM\x97n\xcd\x0eG\x8a[Qf.(9i\xe1\xfdO\x90KU:<\xe6;L\xf3\xde\x13\x89\xc1\xf9\xd5\xd8{\xfd\xb3\xf5\x8c\x8c \xe9Ǒ\xa1\x03[\xaa\xd0)\xec\xb9\xdf\x18Ue\x86`\x0f\xd6a\x0e\x9b\x00\xa5\xb6\xd5\xcc\x15\xd6\aY\x16\xc0X\xd8\x1c*܇\xd7M^\xb8\xd8dx\x05Δ\xc3\xd3Nm\xdd!\xda|C\xebd\x12A\x99\xf3>i\xfc\xc8\x01\xc2\x18\xfea\x84(=\n\x90\x91\x17O\xe4h\x06\n\x91\xb7\x90e-\xe2\xceS\x05\xe0\xbf\x14|$\x03\x97\x90ٹ\n\xe6Lb\xc6&TiȴzD\xe3g$W\xe1Yf\x19oi\xcc\xf5\xbe\xe3d\xb5\x1b\xd9\x16\x83\x19\x19Iؖdv\xd6@\xb2?*#RY\x87\"]\x9f\xbf\x16\xf3\xf0{\x92\x95)\xa6u\x983\xa8Kz\x8c\xbb9\x1a\xc4\x01\xa1\x90\x8a43\x85_DtU\xff:B\r\xf65\x85A2\x18 \x95\x87I\xa4!E\xb3\x19Q\xd2Ԥ\xc3|\x04\xcfٝ\xbc\x80j\xc2\x18q\x98\xa0Y\x154/!Y=&\xb8b\x99L\x90\x88U;\\L5&\xcd\xc8\xfa\xfe\x1f\x12l\xa7\xf5S\f\x91\xfe\x9d\xfa5\x8e%$|6\x01\x1b܉\xbd\xd4\xc6\xf6\xa3\x13\xfc\x8eI\xe9Fw\x9bp\x90\xca\xed\x16\r\xc1›\x8e\xbf\xa7\x885mV\xa9\x99i\xc6\x1f\xad\xaba:1\x8f\xa91\xb6\x14v_F\xa1\x02#NV\x8fuC*\xf72-E\xc6jB\xa8įO\xd4\xf8\x8d)\xb7\x19\x818\xc2\xdf+\xa3j\x15ĥ\x8eW\xaa\x15\x92ۗk3f\xb7|;\x063N\x86\x8d`gr̅k\x9a)3\xb4\x01\x15o\xfe\x1a\xbds\xd1p\xca\at\x99\xd8`\x06\x163L\x9c6\xe3\xe4\x89\x11\x02\xdfb\xf5\xe7\be\a4i\xd7ߚU\xa2M#\x87l'\x93\x9d7V$e\f\vR\x8d\x965\x86(\x8a\xec0\xb5h\x88\x91\x8c0ٜ\xd2hZ\x84\xfa\xe8\xc3\x1dS$M\x8b\xd4\xc1M\x9b\xd1\xc6]\xaa\xd7b\xf3F\xf4\x0e\x9aꇄ\xfd\xf6h\xf8\xcb\v;\x91[\xa2]\xc3\xed\x160/\xdc\xe1\x02\xa4\xab\xbe\xc6@%W\xb1\xc1\xe3oƸ\xd3v\xcbm\u007f\xf4\x8b\xef\x96\x17\xe1Z\x8d\xc6߄il\xac\ue0adZİO\xed\x91\x17 \xb75\xc3\xd2\v\x8a!\x1d\xb2/5\x87h\xcbљ\xe5\xdcK\x12(\xd6\xf6R˅Kv7\xf51PĈ\x1e\xad\xfa\x00\xbc_^\xc50̃\b\x90P;\x15|\x82)\r\xe6\xfed\xf4\x9e\xf7G\xf3\x85=\xc0\x0f\x9f?b:G2\x88\x97ԣE}\xe8y:m\x14x\x81Q [\x8bb7\xad\x8e\xf1\xfc\xf9\xf7\x05\bx\u0083\xf7\xac\x06\x83ˡF\xac\x155H\x83|\x18\xcfj\xe4\t\x0f\f*\x9c\xaeG\xc1[\"*\xbe=\xe1!\xb6k\x8f\xa8\x84_8\xd7\xf3ԥ\x0f\xbc\x8a\x98\xadԴ\x9a\xa8a\xef\x80\xd3q\x8b\x85eJ\xa9j\x15\xc5O\\vͰΕ\xd2\x13\x1e\xdeY\xcf>\xda5;Y,\xa0\x00)l\xb0\xc8;\xac\xbaKy\x10\x99L\xeb\xc9x\x9f,\x80x\xab.\xe0\xb3v\xf4\x9f\x9b\xef\xd2\x12\x8a*\x85\x8f\x1a\xedg\xed\xf8˫\x92\xd8/\xe2D\x02\xfb\xc1\xbc-\x957\vD\x97E\xf378\xb0\t%\x11\xad\xd9&-\xdc*\x8a\xcf<}\x96\xb0i\x87\x15r\x1e\xad\xbc\xb4|\x1b\xa3\xb4Z\xb1\x99\xaef[\x00\xb4\x8dW`\x956\x1dN],\x848\x88b@\uf7ac\x95\xff\xe5\xe8\x1ek\xaa\x19,2\x91`Z\x9dJ\xf3\xa5\x99p\xf8(\x13\xc8\xd1<\"\x14d7\xe2\x85j\x81&\xf7\xed\x04)\x8cw-\xaa\x16\xcc\xc2\xc0\x1d\xd0P[Ѯ\x8f\xecY\xb19\xaa\xfb\xc8\r\xd9t\xf7\xb8U\xb2yg\u007f(\x8a\xfa픎e\x96e!\xbf\x8e}\x10\x8f\xa4w?r\xc1\xc7\xd6\u007f\x90ye\xf1\xfe3\xce\x1a\ni\xec\x1a>pBK\x86\xed\xf1\xd5)ak\xaa(\x90\x84\x89\xb4@r\xb2\x17\x19\xb9\x0f\xa4\xbc\x15`\xe6\x9d\t\xbd=\xf2\xa0\xe2T\xcc\xf3N[o\xf3\xebc\xf5\xf3'<\x9c_\x1ci\xaf\xf3[u\x1e\a\x93t\xfe\x91Ҫ\xbd\x16\xad\xb2\x03\x9c\xf3o\xe7\xec\x98-\xd9\"'8o\v\xa4:\xba+\xa7\x8e,\t\x05(֮\xbc\x16\x1a\\'X\x90\v?\xb7\x8ah\x99.\xb4\x1d\xb9]\x1cA뫶\xce\x1f\x00v\xdc\xed\x81\x13\u0098\xe8/\x9c\x1a\x82\xd8:4`\x9d6U2\x03\xa9\xdd\xde\x019q\xde\xce\xf3\x9eX]\x9fFz\xc0\x14d\x9e7\x1a\xc2\xeb\xf4s\x9f\xe5@\xff?\x0f3ag\x89a\x17F'h\xed\xbc(EZ\x8e\x99\x03\xdb\xfa\xb0V\xf8\xe0m\x1b\xa5\x9ac\x8e\x92\xab\xb6\xcc\x15'Ҟ\x10\xd8\xdc|o\x9d;\x93\x1a\xa2\xbfcD\xf9\x14\x1c\x81\x13\x1d\xf3\\\xf4\x13k\xa2ѽ\xf6\xa3\xab\r\x18\x80\xf9\x80\xc9<\x96\xacT\x96\xf9\xcdA$\xffj\x8eG.\xd5-O\x04\xef_\xcdY\x81J\x95㩡\xccu5\xbeaH\xfd!6~\x85*=C\xf3]\x8d\xc1\x0eg\x8fo2\xe29\x05\xe4L+\xedڇ5a\xa6w\x16\xb6\xd2X\xd7 \xbc\x00\xaa\xb4|M\xfd\xba1\xa6\xba1\xe6\xe4\x10\xf3\x8b\x1f\xdd:V\xdc\xe9\xe7\x90Դ$\xb0\xae\x88\xbf\x13{\x04\xb9\x05\xe9\x00U\xa2K\xc5\a^\xa4.h\x9a\x05\x10=\x13\xbd1\x89\xb4\x99\xad\xc1\xaa\xcc\xe3\t\xb2b\xe9\x94j\xf6t\xac=\xe4g!\xe3N\xa7\xe04\xb6\xba\xa9ġ\xa1\xd6͆\n\x19D\xed\xec\xb5\\|\x97y\x99\x83ȉ-K\xe2ƭ\xcf=\xaaR\xdd<\xaf\x9f\x85t!\x83\xd8_\xac.Ӧ\x89\u038b\f\x1dVYE\x89VV\xa6X\xbb\x0f\x81\xff\x839ZcM\xc0VȬ4\vt\xf4b\xce,\x8dۂzz\xf9`,\x1e\x91\x15\x133\xf2\xd0}\x81\xd3\x1f\xa8\xc5\xe7\xa5-\xcdF\xab\xf3\x87\xe6\xad\xca\x0f\xe4\xa0-{ 0\x9bo\x16\x834\xc4d\x99\r\xe7\x8f\xcd@]\x92[\x16\x1b\x83G\xe4\x91\xc5g\x8fő\a\xf8\xb5}l\xceX\xb4\xd7\x16\x9b\x1f\xf6:Ya\x91\xb9`\xad\f\xafY\x90'f\x80E\x13,.\xdb+:ǫ\x95\xb95O\xad\x89̮\xe1|\xadY\x90C\xf9\\1YZQ\xb8F\xe7f\xd5\x19W\xf3'\x89?\x94\x91\xf5\xf2\xb9\xdf/\xe9\xe7O\xe7WEeUE\xc5\x02\xf38G\xe5M-͖\x8a\xa2\xea\xd2̨:\xebib\xe2\xa8|\xa8\xe3\\\xa7\xa9\xa5\xccfA\x8dg8M\x81\x1d\xca}\x8a\xc8k\x9a\x00\xd9\xcexZ\xec\x06\xccJ\xd3L\x87\xe1\x8a\x18U\x9b\xb7\xb5\xd9\xff\x85\x04\xfe袵\xe9\xb8\xc01Qɗ\xde\x10\xe2}\xe5\xf5\r\xb9\xd5\xe31\x9ew\xb6Op\xabG@\xden!/3'\x8b\xacU\x92\xc2\xed\xf0P?y\xffM\xf3\xd3\xcb́\xa1}\xf9V\v\xf0\x18\xc8n\x80 ,\xe8\a]?\xfc\xcc%\xfa\x1a\u007f\xd1]R\xec\x15|\xdc5\xcf\xfc\xf3\x95\xc8g+\x91\x97@1\xd8G>OY\xfe,%\x92\xce'\x06[\x93SG>?Y\x14n\x9d\x18pMB\x9czn2\x1drM\x1f\xa7\xf5\x9f\x99\x9c\xe0NDH\xd8l\x97\x1f\xbe\x11\xd0&E3{\xb9\xb2D4g\x85\xb2\x17\x13u\xe7\xefU\x1d\xa8\xaa{Q\xaf\xf6\xc5\xcd\x18wt\xfd\n>\x81_\xa4J=oH\b[\xfeE\xe7\xf6\xa7q~\xc6\xee\x80\xda\x1eg(\x8b鯍,\x16\x82\x14)\x87E|\xbdm\xd7p#\x92]=\xc3\bH\x9ew',l\xb5Ʌ\x83\xf3\xfa>\xee\xd2O@\u007f\x9f\xaf\x01~\xd6\xf5Eh\xab\xca\xcd\bT+\xf3\";P\xf4\x03\xe7m0?&8\xa3\xc2gC\xb9\xbfP\xcf,\"\xfe\xbd\xeb\x8e\x18*7\x19ʺ%\x99.\xd3z\x86\tf\vu\x80\xaf\x0f\xecKq\t\xa8\xa4)\x95\x15<\xa5*\x0e\xeeU\xd2\x1a\x019V5r\x11\xc9Ư\x86\xad\xd3F<\xe2'\xed\v{\xc6Ь;\xa2S\xdb5h\xaa*Q$\xbc\xfc\x1aYYU\x92\xb9\x0f\xb0\xc9\x1f;\xaa5H؎\xa9\xb0\x99]\xee\\\x16\xb1\xb8\xfb\xfbO~AN\xe6\xb8\xfeX\xfak\xfaU!\x8cE\xa2t\xb5P?h3n\xe5v\xfa\x99\x8b\xf5\xb5\xabo\xb6\xea\x1b#\xe7\xabqF\xc0I\xab\xd9w\xea[V\xa4\x8b\x11\xf9\x87\xe1\x91-\xa5\xd2b\xe2\xd4žގ\xc2\x12\xd6\xeaD\xb2\x1e\xe2\x03!N\x13{\xbdBqS\xf6dBe\x94\x16\xbf<+4ߪ\x8djo\xd5Xy\xcd\x0e\t\u007f=\x1a8ZZ\xd3i\xd6~\xbd\xeeCVO\x05\x02Y_\x8a\xb4:ْ\xb6.@{L\xba\x99\xfd?\xbe\xf7\x87=\xd7\xd5p\xcd\xd7U]\x86\xf6,\x82\xb2\xbe\xd4jL\x85a_\x935\x11\x85+M0\xaeIi\xb8j\x1e\x01A_T\xee\xb4\x1a\xc3M\xcd\xf6\x19^6UܛX\u007f\xb6f\xfc\x00\xff\xea*\xc1\xa3es\xbde\xf55\xddW\x04\xff4v\x0e\xee\x03\xae28WQ\x99\xfa\xd4)\xbe\x81\xd0<\xb0\xaaNx7\x86\xfap\xce\xe6\n>\xe3\xf3\xc0\xd7\x1bE\x8b8\xbeQ\U000c9658\xf2\t\xc1P}\xf5\xc9%\xee\xebQ\x9c\x15;\xa0-\xbaj\xae\u05fd\x97nõj\xeb.>\x03v\x88\xad\xff(\xb7\xfe\xf8&\xa15\xfd\xd3Q\x8fQ\xc55\xa9\xb4\xc6\x14\xd6\xe0\x96:\xfah\xd1\xec\xb98l%$\xc1\x86\xb7\xbf\x94\x9b\xa6X$\xfc\xf1\xe7Y\xb3+E\x92`\xe1BZW\xfb߲8\xf7\xc5^\xab\u007f\xaa\x82\xffL\xb4\xf2n\xb6\xbd\x82\xff\xfc\xef3\b\x06\xf8\xa1\xfa\xf7(\xe8\xe3\xff\x06\x00\x00\xff\xff6\x91\a\xfc\xf9c\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VO\x8f\xeb4\x10\xbf\xe7S\x8c\x1e\x87w!\xe9{\xe2\x00\xca\r\x15\x0e+`\xb5\xda>\xed\x05qp\x9di;\xacc\x9b\xf1\xb8K\xf9\xf4\xc8v\xb2m\x93\x94]\x90\xf0-\xf6\xfc\xf9\xcdo\xfed\xaa\xba\xae+\xe5\xe9\t9\x90\xb3-(O\xf8\xa7\xa0M_\xa1y\xfe.4\xe4V\xc7\xcf\xd53ٮ\x85u\f\xe2\xfaG\f.\xb2\xc6\x1fpG\x96\x84\x9c\xadz\x14\xd5)Qm\x05\xa0\xacu\xa2\xd2uH\x9f\x00\xdaYag\fr\xbdG\xdb<\xc7-n#\x99\x0e9\x1b\x1f]\x1f?5\xdf6\x9f*\x00͘տP\x8fAT\xef[\xb0ј\n\xc0\xaa\x1e[\b\xc8II\x94\xc4\xc0\xf8G\xc4 \xa19\xa2Av\r\xb9*x\xd4\xc9\xf1\x9e]\xf4-\x9c\x1f\x8a\xfe\x00\xaa\x04\xb4ɦ6\xd9\xd4c1\x95_\r\x05\xf9\xe9\x96\xc4\xcf4Hy\x13Y\x99e@Y \x1c\x1c\xcb\xfd\xd9i\r!py!\xbb\x8fF\xf1\xa2r\x05\x10\xb4\xf3\xd8B\xd6\xf5JcW\x01\fLe[\xf5\xc0\xc5\xf1s1\xa7\x0fث\xe2\x04\xc0y\xb4\xdf?\xdc=}\xb3\xb9\xba\x06\xe80h&/\x99\xef\x85Ȁ\x02(\x18P\x808PZc\b\xa0#3Z\x81\x82\x12\xc8\xee\x1c\xf79G\xaf\xa6\x01\xd4\xd6E\x019 d\x05\xd9*\x03Ge\"~\r\xcavЫ\x130&/\x10텽,\x12\x1a\xf8\xc51f2[8\x88\xf8ЮV{\x92\xb1\xeb\xb4\xeb\xfbhIN\xab\xdc@\xb4\x8d\xe28\xac:<\xa2Y\x05\xda\u05ca\xf5\x81\x04\xb5Dƕ\xf2Tg\xe86w^\xd3w_\xf1Ч\xe1\xe3\x15V9\xa5\xca\n\xc2d\xf7\x17\x0f\xb9!\xfe!\x03\xa9\x1dJ}\x14\xd5\x12ř\xe8t\x95\xd8y\xfcq\xf3\x05F\xd79\x19S\xf63\xefg\xc5pNA\"\x8c\xec\x0e\xb9$qǮ\xcf6\xd1vޑ-ե\r\xa1\x9d\xd2\x1f\xe2\xb6'\tc\xed\xa6\\5\xb0Σ\b\xb6\b\xd1wJ\xb0k\xe0\xce\xc2Z\xf5h\xd6*\xe0\xff\x9e\x80\xc4t\xa8\x13\xb1\xefK\xc1\xe5\x14\x9d\n\x17\xd6.\x1e\xc61w#_\vݽ\xf1\xa8S\x06\x13\x89I\x9bv\xa4s{\xc0\xce1\xa8%\x95\xe6]H\xb2ƿ\xc42L\x92\x82f2_R\u007f\xbe\x8dfy\x9c䗃\n8\xbd\x9c`zH2S\xff\x86v\xa8O\xda`1Q\xa6\t\xbe\r%\x1d\xb4\xb1\x9f\xfb\xac\xe1\x1e_\x16n\x1fإɚ\xe7\xfa\xf5\xb9Q\x1bP\xfe7{\xb2\xb3p\xa7\x91\x15\xa9\xfc\x0f\xbb\x1c\xd5\x17\x03z0\x04\x1c\xadM};\x9b\x90\x19\xc8t\x92\xcfdH\xb0_@\xb3\x88\xe7\xce\xee\\\xde\x04Tr\xac\xa4\xf4\x13\x0e\xc9\x1e\xfc\x14\\\v\x06o纜\xf9\xf0z\x17\xa1\xe5\xe4?\xe9\u007fSN\xe3\x86\x18\x17}\xd7\x19\xd5\xe2C\xf2\xb8\xc4\xf8r\u007f\r(\xa31jk\xb0\x05\xe18\xd7.\xba\x8aY\x9d\xa6U3\x96\xday\x9fz\xa3\x80f\n\xa9O^\x0ehou\x03\xbc\xa8锿\xf2\f\xdb\xd3-\xd5\xf5\xebr8o\xa9R\xba-\xa4\xd9]\v-p\xf6.R\x16\xb3WJzq\xf3\x98\x11\xb2\xb9\x94\x1dg\xc6Uk\x8c\x8b\xc8<\x86\x9b\x10\x16\x93=\xbb\xcc滋\xf0\x828V\xfb1\xe0\xf3\xe8M\x9b\x9a\x17\xec\xee\xa7+\xee\x87\x0fW\xbbj\xfe\xd4\xcevT6t\xf8\xf5\xb7\xaaX\xc5\xeei\\0\xd3\xe5\xdf\x01\x00\x00\xff\xff\xfb\xb1p\x12\x1b\f\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WO\x8f۶\x13\xbd\xfbS\f\x92\xc3^\"9\xc1\xef\xf0+t)\x82M\x0fA\xf3g\x11o\xf7R\xf4@\x93#\x8b]\x8aTgHm\xddO_\f)\xad\xbd\xb6\x9cl\x8aV\x17C\x149|\xf3\u07bc!\xbd\xaa\xaaj\xa5\x06{\x87\xc46\xf8\x06\xd4`\xf1ψ^\u07b8\xbe\xff\x81k\x1b\xd6\xe3\x9bս\xf5\xa6\x81\xeb\xc41\xf4_\x90C\"\x8dﰵ\xdeF\x1b\xfc\xaaǨ\x8c\x8a\xaaY\x01(\xefCT2\xcc\xf2\n\xa0\x83\x8f\x14\x9cC\xaav\xe8\xeb\xfb\xb4\xc5m\xb2\xce \xe5\xe0\xf3\xd6\xe3\xeb\xfa\xff\xf5\xeb\x15\x80&\xcc\xcbom\x8f\x1cU?4\xe0\x93s+\x00\xafzl`\f.\xf5\xc8^\r܅\xe8\x82.\x9b\xd5#:\xa4P۰\xe2\x01\xb5콣\x90\x86\x06\x0e\x1fJ\x88\tW\xc9\xe9.G\xdbL\xd1>L\xd1\xf2\x04g9\xfe\xfc\x95I\x1f,\xc7\xe8\xcdK\x9a,\xcaWO\xb0ƽT\x11G\xb2~w\xf4!\x1b\xe1+\n\x88\aJ!\x94\xa5%\x8b\x03\xd12$\xec|\xf9is\v\xf3\xd6Y\x8cS\xf63\uf1c5|\x90@\b\xb3\xbeE*\"\xb6\x14\xfa\x1c\x13\xbd\x19\x82\xf51\xbfhgџ\xd2\xcfi\xdb\xdb(\xba\xff\x91\x90\xa3hU\xc3u\xeeB\xb0EH\x83Q\x11M\r\xef=\\\xab\x1eݵb\xfc\xcf\x05\x10\xa6\xb9\x12b\x9f'\xc1q\x03=\x9d\\X;6\xd8\xd4\xde.\xe8\xb5\xec\xe4̀\xfa\x89\x81$\x8am\xed\xe4\xec6\xd0\t\xafj\xf6\xf9r\xbc\xfa\xc9\xf4e\x83C\xe9\xfe\xadݝ\x8e\x02(c\xf2١\xdc\xcdŵ_!l!\xef뼓\x14j\x1bH\x10\x8d\xd6 Us\x9e\x13\x92DS\xc2\x16\x9d\xe1\xfa,\xe4\x05\xces*\x84F4V\xee\x1c\xe8S$\x8f\x13\xf3᧬/\x94\x1f\x02\xe4ң~\xea\xb1>\xa27\xb9\xa9\x9f\xa1\t\xb9\x86\x19\r<\xd8\xd8\x15s\xb8\xe3C\xeay*\xc8s\x8f\xfb\xa5\xe1\x13\xec\xb7\x1d\xca\xcc\xd2N\x11\x185a\x14\x1c\x8cN\xcc+ά\x01>&\xce\xf6R\x8b\x11AZ\x845\xf3\xea{ܟ\x13\r\xdf\x12w:\xef\xbf\r\xf9J\xce\xc5\x190a\x8b\x84>.Z\\\xee\x1e\xe41bv\xb9\t\x9a\xc5\xe0\x1a\x87\xc8\xeb0\"\x8d\x16\x1f\xd6\x0f\x81\xee\xad\xdfUBxU\n\x81\xd7\xf9ް~\x99\u007f.\xa4|\xfb\xf9\xdd\xe7\x06\xde\x1a\x03!vH\xa2Z\x9b\xdc\\hG\xa7ݫ\xdcq_A\xb2\xe6ǫ\u007f\xc2K\x18\x8as\x9e\xc1\xcd&W\xff^N\xee\fJ(\xda\x14U\x02\x81\xf4M\x11\xbb\x9f\xd4,\xfda\xa9\x10gL\xdb\x10\x1c\xaa\xf3ғ\xeek\t\xcd9\xa4Jv\xf8\x1e\x9b\xcd\xce\xfd\x86\xc9n\xa6ibx\xc9j^6\x17B\xb9\x97\xe4[\x8a\xda\xe1%\xa3/p\xbc\x9cJ\xf5\xb8\xc1\xb3ZtT1\xf1\xf77\xe9\xbcl\x9a\xb9\x9d\x1a\xb5N$\x05=\xc5\\\xb8\xd0\xfc;\x8dz\xe8\x14/\xb8\xed\x19\xa8od\xe5,\x83\xb3-\xea\xbdvX\x02Bh\x17\xaa\xe9\xbb ˃>\xf5K\xa5\xf5vT֩\xadÅo\xbfxu\xf1\xebE\xf1\x17\xf5<\x1bd\xb9\xb5\x98\x06\"\xa5\x12{\xaa\xb2i䠾\xd2\xd2\\\xd0|:\xfd\xdb\xf1\xe2œ\u007f\x0e\xf9U\a_\xceDn\xe0\xd7\xdfV%*\x9a\xbb\xf9\xa2/\x83\u007f\a\x00\x00\xff\xff\xe4\xf3S\x85\xb2\r\x00\x00"), diff --git a/pkg/apis/velero/v1/backup.go b/pkg/apis/velero/v1/backup.go index 6568f830f..ee3ce9340 100644 --- a/pkg/apis/velero/v1/backup.go +++ b/pkg/apis/velero/v1/backup.go @@ -221,7 +221,7 @@ const ( // BackupPhase is a string representation of the lifecycle phase // of a Velero backup. -// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;Completed;PartiallyFailed;Failed;Deleting +// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;WaitingForPluginOperations;WaitingForPluginOperationsPartiallyFailed;Completed;PartiallyFailed;Failed;Deleting type BackupPhase string const ( @@ -236,16 +236,20 @@ const ( // BackupPhaseInProgress means the backup is currently executing. BackupPhaseInProgress BackupPhase = "InProgress" - // BackupPhaseUploading means the backups of Kubernetes resources - // and creation of snapshots was successful and snapshot data - // is currently uploading. The backup is not usable yet. - BackupPhaseUploading BackupPhase = "Uploading" + // BackupPhaseWaitingForPluginOperations means the backup of + // Kubernetes resources, creation of snapshots, and other + // async plugin operations was successful and snapshot data is + // currently uploading or other plugin operations are still + // ongoing. The backup is not usable yet. + BackupPhaseWaitingForPluginOperations BackupPhase = "WaitingForPluginOperations" - // BackupPhaseUploadingPartialFailure means the backup of Kubernetes - // resources and creation of snapshots partially failed (final phase - // will be PartiallyFailed) and snapshot data is currently uploading. - // The backup is not usable yet. - BackupPhaseUploadingPartialFailure BackupPhase = "UploadingPartialFailure" + // BackupPhaseWaitingForPluginOperationsPartiallyFailed means + // the backup of Kubernetes resources, creation of snapshots, + // and other async plugin operations partially failed (final + // phase will be PartiallyFailed) and snapshot data is + // currently uploading or other plugin operations are still + // ongoing. The backup is not usable yet. + BackupPhaseWaitingForPluginOperationsPartiallyFailed BackupPhase = "WaitingForPluginOperationsPartiallyFailed" // BackupPhaseCompleted means the backup has run successfully without // errors. diff --git a/pkg/apis/velero/v1/restore.go b/pkg/apis/velero/v1/restore.go index f36e7348c..9f2fbd58e 100644 --- a/pkg/apis/velero/v1/restore.go +++ b/pkg/apis/velero/v1/restore.go @@ -220,7 +220,7 @@ type InitRestoreHook struct { // RestorePhase is a string representation of the lifecycle phase // of a Velero restore -// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;Completed;PartiallyFailed;Failed +// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;WaitingForPluginOperations;WaitingForPluginOperationsPartiallyFailed;Completed;PartiallyFailed;Failed type RestorePhase string const ( @@ -235,6 +235,19 @@ const ( // RestorePhaseInProgress means the restore is currently executing. RestorePhaseInProgress RestorePhase = "InProgress" + // RestorePhaseWaitingForPluginOperations means the restore of + // Kubernetes resources and other async plugin operations was + // successful and plugin operations are still ongoing. The + // restore is not complete yet. + RestorePhaseWaitingForPluginOperations RestorePhase = "WaitingForPluginOperations" + + // RestorePhaseWaitingForPluginOperationsPartiallyFailed means + // the restore of Kubernetes resources and other async plugin + // operations partially failed (final phase will be + // PartiallyFailed) and other plugin operations are still + // ongoing. The restore is not complete yet. + RestorePhaseWaitingForPluginOperationsPartiallyFailed RestorePhase = "WaitingForPluginOperationsPartiallyFailed" + // RestorePhaseCompleted means the restore has run successfully // without errors. RestorePhaseCompleted RestorePhase = "Completed" diff --git a/site/content/docs/main/api-types/backup.md b/site/content/docs/main/api-types/backup.md index a93da8e27..65bf8104a 100644 --- a/site/content/docs/main/api-types/backup.md +++ b/site/content/docs/main/api-types/backup.md @@ -145,7 +145,9 @@ status: version: 1 # The date and time when the Backup is eligible for garbage collection. expiration: null - # The current phase. Valid values are New, FailedValidation, InProgress, Completed, PartiallyFailed, Failed. + # The current phase. + # Valid values are New, FailedValidation, InProgress, WaitingForPluginOperations, + # WaitingForPluginOperationsPartiallyFailed, Completed, PartiallyFailed, Failed. phase: "" # An array of any validation errors encountered. validationErrors: null diff --git a/site/content/docs/main/api-types/restore.md b/site/content/docs/main/api-types/restore.md index a6495c825..69eb78010 100644 --- a/site/content/docs/main/api-types/restore.md +++ b/site/content/docs/main/api-types/restore.md @@ -179,7 +179,9 @@ spec: onError: Continue # RestoreStatus captures the current status of a Velero restore. Users should not set any data here. status: - # The current phase. Valid values are New, FailedValidation, InProgress, Completed, PartiallyFailed, Failed. + # The current phase. + # Valid values are New, FailedValidation, InProgress, WaitingForPluginOperations, + # WaitingForPluginOperationsPartiallyFailed, Completed, PartiallyFailed, Failed. phase: "" # An array of any validation errors encountered. validationErrors: null diff --git a/site/content/docs/main/api-types/schedule.md b/site/content/docs/main/api-types/schedule.md index 4b759501a..3c2a2d163 100644 --- a/site/content/docs/main/api-types/schedule.md +++ b/site/content/docs/main/api-types/schedule.md @@ -143,7 +143,8 @@ spec: post: # Same content as pre above. status: - # The current phase of the latest scheduled backup. Valid values are New, FailedValidation, InProgress, Completed, PartiallyFailed, Failed. + # The current phase. + # Valid values are New, Enabled, FailedValidation. phase: "" # Date/time of the last backup for a given schedule lastBackup: From 62c00ba841931791075391f3ec646a66e13dc54e Mon Sep 17 00:00:00 2001 From: yanggang Date: Tue, 3 Jan 2023 10:37:19 +0800 Subject: [PATCH 14/20] fix log info : pkg/util/kube/isProvisionedByCSI Signed-off-by: yanggang --- pkg/util/kube/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/util/kube/utils.go b/pkg/util/kube/utils.go index 73603b059..d23597397 100644 --- a/pkg/util/kube/utils.go +++ b/pkg/util/kube/utils.go @@ -187,7 +187,7 @@ func isProvisionedByCSI(log logrus.FieldLogger, pv *corev1api.PersistentVolume, } for _, driver := range list.Items { if driverName == driver.Name || migratedDriver == driver.Name { - log.Debugf("the annotation %s or %s equals to %s indicates the volume is provisioned by a CSI driver", KubeAnnDynamicallyProvisioned, KubeAnnMigratedTo, driverName) + log.Debugf("the annotation %s or %s equals to %s indicates the volume is provisioned by a CSI driver", KubeAnnDynamicallyProvisioned, KubeAnnMigratedTo, driver.Name) return true, nil } } From 95fcd8f63cfe71c7acbb6168c215a115dd134c46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 11:00:05 +0800 Subject: [PATCH 15/20] Bump github.com/Azure/azure-sdk-for-go (#5709) Bumps [github.com/Azure/azure-sdk-for-go](https://github.com/Azure/azure-sdk-for-go) from 61.4.0+incompatible to 67.2.0+incompatible. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/v61.4.0...v67.2.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8107fe7a0..54e1e44b2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( cloud.google.com/go/storage v1.21.0 github.com/Azure/azure-pipeline-go v0.2.3 - github.com/Azure/azure-sdk-for-go v61.4.0+incompatible + github.com/Azure/azure-sdk-for-go v67.2.0+incompatible github.com/Azure/azure-storage-blob-go v0.14.0 github.com/Azure/go-autorest/autorest v0.11.21 github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 diff --git a/go.sum b/go.sum index e5f0f99e9..7f3beb463 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKu dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-sdk-for-go v61.4.0+incompatible h1:BF2Pm3aQWIa6q9KmxyF1JYKYXtVw67vtvu2Wd54NGuY= -github.com/Azure/azure-sdk-for-go v61.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v67.2.0+incompatible h1:Uu/Ww6ernvPTrpq31kITVTIm/I5jlJ1wjtEH/bmSB2k= +github.com/Azure/azure-sdk-for-go v67.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1 h1:qoVeMsc9/fh/yhxVaA0obYjVH/oI/ihrOoMwsLS9KSA= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3 h1:E+m3SkZCN0Bf5q7YdTs5lSm2CYY3CK4spn5OmUIiQtk= From b10503b351097a4719c42cb4f0f7bab056c71a1b Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Thu, 5 Jan 2023 17:29:05 +0800 Subject: [PATCH 16/20] Add Trivy nightly scan. Signed-off-by: Xun Jiang --- .github/workflows/nightly-trivy-scan.yml | 36 ++++++++++++++++++++++++ changelogs/unreleased/5740-jxun | 1 + 2 files changed, 37 insertions(+) create mode 100644 .github/workflows/nightly-trivy-scan.yml create mode 100644 changelogs/unreleased/5740-jxun diff --git a/.github/workflows/nightly-trivy-scan.yml b/.github/workflows/nightly-trivy-scan.yml new file mode 100644 index 000000000..f4b2d9694 --- /dev/null +++ b/.github/workflows/nightly-trivy-scan.yml @@ -0,0 +1,36 @@ +name: Trivy Nightly Scan +on: + schedule: + - cron: '0 2 * * *' # run at 2 AM UTC + +jobs: + nightly-scan: + name: Trivy nightly scan + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # maintain the versions of Velero those need security scan + versions: [main] + # list of images that need scan + images: [velero, velero-restore-helper] + permissions: + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: 'docker.io/velero/${{ matrix.images }}:${{ matrix.versions }}' + severity: 'CRITICAL,HIGH,MEDIUM' + format: 'template' + template: '@/contrib/sarif.tpl' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: 'trivy-results.sarif' \ No newline at end of file diff --git a/changelogs/unreleased/5740-jxun b/changelogs/unreleased/5740-jxun new file mode 100644 index 000000000..c1e359da9 --- /dev/null +++ b/changelogs/unreleased/5740-jxun @@ -0,0 +1 @@ +Add Trivy nightly scan. \ No newline at end of file From 9a541422571ab1ed6322afc0c4172060f7c2230e Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Thu, 1 Sep 2022 14:36:35 -0400 Subject: [PATCH 17/20] BackupItemAction v2 API implementation Signed-off-by: Scott Seago --- changelogs/unreleased/5442-sseago | 1 + design/biav2-design.md | 4 +- hack/build-image/Dockerfile | 4 + hack/update-2proto.sh | 2 +- pkg/backup/backup.go | 12 +- pkg/backup/backup_test.go | 109 ++- pkg/backup/item_backupper.go | 4 +- pkg/backup/request.go | 2 +- pkg/controller/backup_controller.go | 4 +- pkg/controller/backup_controller_test.go | 12 +- .../v2/restartable_backup_item_action.go | 171 ++++ .../v2/restartable_backup_item_action_test.go | 164 ++++ pkg/plugin/clientmgmt/manager.go | 46 + pkg/plugin/clientmgmt/manager_test.go | 110 +++ .../clientmgmt/process/client_builder.go | 16 +- .../clientmgmt/process/client_builder_test.go | 16 +- pkg/plugin/framework/action_resolver.go | 39 + .../backupitemaction/v2/backup_item_action.go | 45 + .../v2/backup_item_action_client.go | 169 ++++ .../v2/backup_item_action_server.go | 212 +++++ .../v2/backup_item_action_test.go | 202 +++++ pkg/plugin/framework/common/plugin_kinds.go | 8 +- pkg/plugin/framework/server.go | 72 +- pkg/plugin/generated/Shared.pb.go | 239 ++++- .../v2/BackupItemAction.pb.go | 851 ++++++++++++++++++ pkg/plugin/mocks/manager.go | 48 + pkg/plugin/proto/Shared.proto | 15 +- .../v2/BackupItemAction.proto | 50 + .../backupitemaction/v2/backup_item_action.go | 63 ++ .../backupitemaction/v2/BackupItemAction.go | 127 +++ pkg/plugin/velero/shared.go | 28 +- 31 files changed, 2694 insertions(+), 151 deletions(-) create mode 100644 changelogs/unreleased/5442-sseago create mode 100644 pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go create mode 100644 pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go create mode 100644 pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go create mode 100644 pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto create mode 100644 pkg/plugin/velero/backupitemaction/v2/backup_item_action.go create mode 100644 pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go diff --git a/changelogs/unreleased/5442-sseago b/changelogs/unreleased/5442-sseago new file mode 100644 index 000000000..ce6c9a01f --- /dev/null +++ b/changelogs/unreleased/5442-sseago @@ -0,0 +1 @@ +BackupItemAction v2 API implementation diff --git a/design/biav2-design.md b/design/biav2-design.md index 4294e53cc..a638b6b14 100644 --- a/design/biav2-design.md +++ b/design/biav2-design.md @@ -69,8 +69,8 @@ One new shared message type will be added, as this will also be needed for v2 Re message OperationProgress { bool completed = 1; string err = 2; - int64 completed = 3; - int64 total = 4; + int64 nCompleted = 3; + int64 nTotal = 4; string operationUnits = 5; string description = 6; google.protobuf.Timestamp started = 7; diff --git a/hack/build-image/Dockerfile b/hack/build-image/Dockerfile index c543a932a..50997c659 100644 --- a/hack/build-image/Dockerfile +++ b/hack/build-image/Dockerfile @@ -48,6 +48,10 @@ RUN apt-get update && apt-get install -y unzip RUN wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-x86_64.zip && \ unzip protoc-3.14.0-linux-x86_64.zip && \ mv bin/protoc /usr/bin/protoc && \ + mv include/google /usr/include && \ + chmod a+x /usr/include/google && \ + chmod a+x /usr/include/google/protobuf && \ + chmod a+r -R /usr/include/google && \ chmod +x /usr/bin/protoc RUN go install github.com/golang/protobuf/protoc-gen-go@v1.4.3 diff --git a/hack/update-2proto.sh b/hack/update-2proto.sh index 43afa8d4c..8ad696e16 100755 --- a/hack/update-2proto.sh +++ b/hack/update-2proto.sh @@ -19,6 +19,6 @@ HACK_DIR=$(dirname "${BASH_SOURCE}") echo "Updating plugin proto" echo protoc --version -protoc pkg/plugin/proto/*.proto --go_out=plugins=grpc:pkg/plugin/generated/ --go_opt=module=github.com/vmware-tanzu/velero/pkg/plugin/generated -I pkg/plugin/proto/ +protoc pkg/plugin/proto/*.proto pkg/plugin/proto/*/*/*.proto --go_out=plugins=grpc:pkg/plugin/generated/ --go_opt=module=github.com/vmware-tanzu/velero/pkg/plugin/generated -I pkg/plugin/proto/ -I /usr/include echo "Updating plugin proto - done!" diff --git a/pkg/backup/backup.go b/pkg/backup/backup.go index cdb86bf65..9db76b1b5 100644 --- a/pkg/backup/backup.go +++ b/pkg/backup/backup.go @@ -44,7 +44,7 @@ import ( velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1" "github.com/vmware-tanzu/velero/pkg/kuberesource" "github.com/vmware-tanzu/velero/pkg/plugin/framework" - biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" "github.com/vmware-tanzu/velero/pkg/podexec" "github.com/vmware-tanzu/velero/pkg/podvolume" @@ -63,9 +63,9 @@ const BackupFormatVersion = "1.1.0" type Backupper interface { // Backup takes a backup using the specification in the velerov1api.Backup and writes backup and log data // to the given writers. - Backup(logger logrus.FieldLogger, backup *Request, backupFile io.Writer, actions []biav1.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error + Backup(logger logrus.FieldLogger, backup *Request, backupFile io.Writer, actions []biav2.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error BackupWithResolvers(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, - backupItemActionResolver framework.BackupItemActionResolver, itemSnapshotterResolver framework.ItemSnapshotterResolver, + backupItemActionResolver framework.BackupItemActionResolverV2, itemSnapshotterResolver framework.ItemSnapshotterResolver, volumeSnapshotterGetter VolumeSnapshotterGetter) error } @@ -174,8 +174,8 @@ type VolumeSnapshotterGetter interface { // back up individual resources that don't prevent the backup from continuing to be processed) are logged // to the backup log. func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, - actions []biav1.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error { - backupItemActions := framework.NewBackupItemActionResolver(actions) + actions []biav2.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error { + backupItemActions := framework.NewBackupItemActionResolverV2(actions) itemSnapshotters := framework.NewItemSnapshotterResolver(nil) return kb.BackupWithResolvers(log, backupRequest, backupFile, backupItemActions, itemSnapshotters, volumeSnapshotterGetter) @@ -184,7 +184,7 @@ func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Req func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, - backupItemActionResolver framework.BackupItemActionResolver, + backupItemActionResolver framework.BackupItemActionResolverV2, itemSnapshotterResolver framework.ItemSnapshotterResolver, volumeSnapshotterGetter VolumeSnapshotterGetter) error { gzippedData := gzip.NewWriter(backupFile) diff --git a/pkg/backup/backup_test.go b/pkg/backup/backup_test.go index 56cbfe214..aae4dfecd 100644 --- a/pkg/backup/backup_test.go +++ b/pkg/backup/backup_test.go @@ -47,7 +47,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/discovery" "github.com/vmware-tanzu/velero/pkg/kuberesource" "github.com/vmware-tanzu/velero/pkg/plugin/velero" - biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" "github.com/vmware-tanzu/velero/pkg/podvolume" "github.com/vmware-tanzu/velero/pkg/test" @@ -1139,23 +1139,32 @@ type recordResourcesAction struct { ids []string backups []velerov1.Backup additionalItems []velero.ResourceIdentifier + operationID string } -func (a *recordResourcesAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { +func (a *recordResourcesAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { metadata, err := meta.Accessor(item) if err != nil { - return item, a.additionalItems, err + return item, a.additionalItems, a.operationID, err } a.ids = append(a.ids, kubeutil.NamespaceAndName(metadata)) a.backups = append(a.backups, *backup) - return item, a.additionalItems, nil + return item, a.additionalItems, a.operationID, nil } func (a *recordResourcesAction) AppliesTo() (velero.ResourceSelector, error) { return a.selector, nil } +func (a *recordResourcesAction) Progress(operationID string, backup *velerov1.Backup) (velero.OperationProgress, error) { + return velero.OperationProgress{}, nil +} + +func (a *recordResourcesAction) Cancel(operationID string, backup *velerov1.Backup) error { + return nil +} + func (a *recordResourcesAction) ForResource(resource string) *recordResourcesAction { a.selector.IncludedResources = append(a.selector.IncludedResources, resource) return a @@ -1362,7 +1371,7 @@ func TestBackupActionsRunForCorrectItems(t *testing.T) { h.addItems(t, resource) } - actions := []biav1.BackupItemAction{} + actions := []biav2.BackupItemAction{} for action := range tc.actions { actions = append(actions, action) } @@ -1388,7 +1397,7 @@ func TestBackupWithInvalidActions(t *testing.T) { name string backup *velerov1.Backup apiResources []*test.APIResource - actions []biav1.BackupItemAction + actions []biav2.BackupItemAction }{ { name: "action with invalid label selector results in an error", @@ -1404,7 +1413,7 @@ func TestBackupWithInvalidActions(t *testing.T) { builder.ForPersistentVolume("baz").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ new(recordResourcesAction).ForLabelSelector("=invalid-selector"), }, }, @@ -1422,7 +1431,7 @@ func TestBackupWithInvalidActions(t *testing.T) { builder.ForPersistentVolume("baz").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &appliesToErrorAction{}, }, }, @@ -1453,7 +1462,15 @@ func (a *appliesToErrorAction) AppliesTo() (velero.ResourceSelector, error) { return velero.ResourceSelector{}, errors.New("error calling AppliesTo") } -func (a *appliesToErrorAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { +func (a *appliesToErrorAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + panic("not implemented") +} + +func (a *appliesToErrorAction) Progress(operationID string, backup *velerov1.Backup) (velero.OperationProgress, error) { + panic("not implemented") +} + +func (a *appliesToErrorAction) Cancel(operationID string, backup *velerov1.Backup) error { panic("not implemented") } @@ -1466,16 +1483,16 @@ func TestBackupActionModifications(t *testing.T) { // method modifies the item being passed in by calling the 'modify' function on it. modifyingActionGetter := func(modify func(*unstructured.Unstructured)) *pluggableAction { return &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { obj, ok := item.(*unstructured.Unstructured) if !ok { - return nil, nil, errors.Errorf("unexpected type %T", item) + return nil, nil, "", errors.Errorf("unexpected type %T", item) } res := obj.DeepCopy() modify(res) - return res, nil, nil + return res, nil, "", nil }, } } @@ -1484,7 +1501,7 @@ func TestBackupActionModifications(t *testing.T) { name string backup *velerov1.Backup apiResources []*test.APIResource - actions []biav1.BackupItemAction + actions []biav2.BackupItemAction want map[string]unstructuredObject }{ { @@ -1495,7 +1512,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.SetLabels(map[string]string{"updated": "true"}) }), @@ -1512,7 +1529,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("should-be-removed", "true")).Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.SetLabels(nil) }), @@ -1529,7 +1546,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.Object["spec"].(map[string]interface{})["nodeName"] = "foo" }), @@ -1547,7 +1564,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.SetName(item.GetName() + "-updated") item.SetNamespace(item.GetNamespace() + "-updated") @@ -1588,7 +1605,7 @@ func TestBackupActionAdditionalItems(t *testing.T) { name string backup *velerov1.Backup apiResources []*test.APIResource - actions []biav1.BackupItemAction + actions []biav2.BackupItemAction want []string }{ { @@ -1601,16 +1618,16 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPod("ns-3", "pod-3").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ selector: velero.ResourceSelector{IncludedNamespaces: []string{"ns-1"}}, - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.Pods, Namespace: "ns-2", Name: "pod-2"}, {GroupResource: kuberesource.Pods, Namespace: "ns-3", Name: "pod-3"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1633,15 +1650,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPod("ns-3", "pod-3").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.Pods, Namespace: "ns-2", Name: "pod-2"}, {GroupResource: kuberesource.Pods, Namespace: "ns-3", Name: "pod-3"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1663,15 +1680,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1696,15 +1713,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1726,15 +1743,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1757,15 +1774,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1787,16 +1804,16 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPod("ns-3", "pod-3").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ selector: velero.ResourceSelector{IncludedNamespaces: []string{"ns-1"}}, - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.Pods, Namespace: "ns-4", Name: "pod-4"}, {GroupResource: kuberesource.Pods, Namespace: "ns-5", Name: "pod-5"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -2727,12 +2744,12 @@ func TestBackupWithPodVolume(t *testing.T) { // function body at runtime. type pluggableAction struct { selector velero.ResourceSelector - executeFunc func(runtime.Unstructured, *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) + executeFunc func(runtime.Unstructured, *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) } -func (a *pluggableAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { +func (a *pluggableAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { if a.executeFunc == nil { - return item, nil, nil + return item, nil, "", nil } return a.executeFunc(item, backup) @@ -2742,6 +2759,14 @@ func (a *pluggableAction) AppliesTo() (velero.ResourceSelector, error) { return a.selector, nil } +func (a *pluggableAction) Progress(operationID string, backup *velerov1.Backup) (velero.OperationProgress, error) { + return velero.OperationProgress{}, nil +} + +func (a *pluggableAction) Cancel(operationID string, backup *velerov1.Backup) error { + return nil +} + type harness struct { *test.APIServer backupper *kubernetesBackupper diff --git a/pkg/backup/item_backupper.go b/pkg/backup/item_backupper.go index aefbb85ce..efa4303ae 100644 --- a/pkg/backup/item_backupper.go +++ b/pkg/backup/item_backupper.go @@ -320,7 +320,9 @@ func (ib *itemBackupper) executeActions( } log.Info("Executing custom action") - updatedItem, additionalItemIdentifiers, err := action.Execute(obj, ib.backupRequest.Backup) + // Note: we're ignoring the operationID returned from Execute for now, it will be used + // with the async plugin action implementation + updatedItem, additionalItemIdentifiers, _, err := action.Execute(obj, ib.backupRequest.Backup) if err != nil { return nil, errors.Wrapf(err, "error executing custom action (groupResource=%s, namespace=%s, name=%s)", groupResource.String(), namespace, name) } diff --git a/pkg/backup/request.go b/pkg/backup/request.go index 69dbeca70..a94faa2d7 100644 --- a/pkg/backup/request.go +++ b/pkg/backup/request.go @@ -45,7 +45,7 @@ type Request struct { NamespaceIncludesExcludes *collections.IncludesExcludes ResourceIncludesExcludes *collections.IncludesExcludes ResourceHooks []hook.ResourceHook - ResolvedActions []framework.BackupItemResolvedAction + ResolvedActions []framework.BackupItemResolvedActionV2 ResolvedItemSnapshotters []framework.ItemSnapshotterResolvedAction VolumeSnapshots []*volume.Snapshot PodVolumeBackups []*velerov1api.PodVolumeBackup diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index 2765d076a..67874c7e5 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -620,7 +620,7 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error { defer pluginManager.CleanupClients() backupLog.Info("Getting backup item actions") - actions, err := pluginManager.GetBackupItemActions() + actions, err := pluginManager.GetBackupItemActionsV2() if err != nil { return err } @@ -645,7 +645,7 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error { return errors.Errorf("backup already exists in object storage") } - backupItemActionsResolver := framework.NewBackupItemActionResolver(actions) + backupItemActionsResolver := framework.NewBackupItemActionResolverV2(actions) itemSnapshottersResolver := framework.NewItemSnapshotterResolver(itemSnapshotters) var fatalErrs []error diff --git a/pkg/controller/backup_controller_test.go b/pkg/controller/backup_controller_test.go index 696397b72..56d31ab90 100644 --- a/pkg/controller/backup_controller_test.go +++ b/pkg/controller/backup_controller_test.go @@ -53,7 +53,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt" "github.com/vmware-tanzu/velero/pkg/plugin/framework" pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks" - biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" velerotest "github.com/vmware-tanzu/velero/pkg/test" "github.com/vmware-tanzu/velero/pkg/util/boolptr" "github.com/vmware-tanzu/velero/pkg/util/logging" @@ -63,13 +63,13 @@ type fakeBackupper struct { mock.Mock } -func (b *fakeBackupper) Backup(logger logrus.FieldLogger, backup *pkgbackup.Request, backupFile io.Writer, actions []biav1.BackupItemAction, volumeSnapshotterGetter pkgbackup.VolumeSnapshotterGetter) error { +func (b *fakeBackupper) Backup(logger logrus.FieldLogger, backup *pkgbackup.Request, backupFile io.Writer, actions []biav2.BackupItemAction, volumeSnapshotterGetter pkgbackup.VolumeSnapshotterGetter) error { args := b.Called(logger, backup, backupFile, actions, volumeSnapshotterGetter) return args.Error(0) } func (b *fakeBackupper) BackupWithResolvers(logger logrus.FieldLogger, backup *pkgbackup.Request, backupFile io.Writer, - backupItemActionResolver framework.BackupItemActionResolver, itemSnapshotterResolver framework.ItemSnapshotterResolver, + backupItemActionResolver framework.BackupItemActionResolverV2, itemSnapshotterResolver framework.ItemSnapshotterResolver, volumeSnapshotterGetter pkgbackup.VolumeSnapshotterGetter) error { args := b.Called(logger, backup, backupFile, backupItemActionResolver, itemSnapshotterResolver, volumeSnapshotterGetter) return args.Error(0) @@ -1070,11 +1070,11 @@ func TestProcessBackupCompletions(t *testing.T) { formatFlag: formatFlag, } - pluginManager.On("GetBackupItemActions").Return(nil, nil) + pluginManager.On("GetBackupItemActionsV2").Return(nil, nil) pluginManager.On("CleanupClients").Return(nil) pluginManager.On("GetItemSnapshotters").Return(nil, nil) - backupper.On("Backup", mock.Anything, mock.Anything, mock.Anything, []biav1.BackupItemAction(nil), pluginManager).Return(nil) - backupper.On("BackupWithResolvers", mock.Anything, mock.Anything, mock.Anything, framework.BackupItemActionResolver{}, framework.ItemSnapshotterResolver{}, pluginManager).Return(nil) + backupper.On("Backup", mock.Anything, mock.Anything, mock.Anything, []biav2.BackupItemAction(nil), pluginManager).Return(nil) + backupper.On("BackupWithResolvers", mock.Anything, mock.Anything, mock.Anything, framework.BackupItemActionResolverV2{}, framework.ItemSnapshotterResolver{}, pluginManager).Return(nil) backupStore.On("BackupExists", test.backupLocation.Spec.StorageType.ObjectStorage.Bucket, test.backup.Name).Return(test.backupExists, test.existenceCheckError) // Ensure we have a CompletionTimestamp when uploading and that the backup name matches the backup in the object store. diff --git a/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go new file mode 100644 index 000000000..b3739796f --- /dev/null +++ b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go @@ -0,0 +1,171 @@ +/* +Copyright 2018 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime" + + api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + biav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process" + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" +) + +// AdaptedBackupItemAction is a v1 BackupItemAction adapted to implement the v2 API +type AdaptedBackupItemAction struct { + Kind common.PluginKind + + // Get returns a restartable BackupItemAction for the given name and process, wrapping if necessary + GetRestartable func(name string, restartableProcess process.RestartableProcess) biav2.BackupItemAction +} + +func AdaptedBackupItemActions() []AdaptedBackupItemAction { + return []AdaptedBackupItemAction{ + { + Kind: common.PluginKindBackupItemActionV2, + GetRestartable: func(name string, restartableProcess process.RestartableProcess) biav2.BackupItemAction { + return NewRestartableBackupItemAction(name, restartableProcess) + }, + }, + { + Kind: common.PluginKindBackupItemAction, + GetRestartable: func(name string, restartableProcess process.RestartableProcess) biav2.BackupItemAction { + return NewAdaptedV1RestartableBackupItemAction(biav1cli.NewRestartableBackupItemAction(name, restartableProcess)) + }, + }, + } +} + +// restartableBackupItemAction is a backup item action for a given implementation (such as "pod"). It is associated with +// a restartableProcess, which may be shared and used to run multiple plugins. At the beginning of each method +// call, the restartableBackupItemAction asks its restartableProcess to restart itself if needed (e.g. if the +// process terminated for any reason), then it proceeds with the actual call. +type RestartableBackupItemAction struct { + Key process.KindAndName + SharedPluginProcess process.RestartableProcess +} + +// NewRestartableBackupItemAction returns a new RestartableBackupItemAction. +func NewRestartableBackupItemAction(name string, sharedPluginProcess process.RestartableProcess) *RestartableBackupItemAction { + r := &RestartableBackupItemAction{ + Key: process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name}, + SharedPluginProcess: sharedPluginProcess, + } + return r +} + +// getBackupItemAction returns the backup item action for this restartableBackupItemAction. It does *not* restart the +// plugin process. +func (r *RestartableBackupItemAction) getBackupItemAction() (biav2.BackupItemAction, error) { + plugin, err := r.SharedPluginProcess.GetByKindAndName(r.Key) + if err != nil { + return nil, err + } + + backupItemAction, ok := plugin.(biav2.BackupItemAction) + if !ok { + return nil, errors.Errorf("%T (returned for %v) is not a BackupItemActionV2!", plugin, r.Key) + } + + return backupItemAction, nil +} + +// getDelegate restarts the plugin process (if needed) and returns the backup item action for this restartableBackupItemAction. +func (r *RestartableBackupItemAction) getDelegate() (biav2.BackupItemAction, error) { + if err := r.SharedPluginProcess.ResetIfNeeded(); err != nil { + return nil, err + } + + return r.getBackupItemAction() +} + +// AppliesTo restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) AppliesTo() (velero.ResourceSelector, error) { + delegate, err := r.getDelegate() + if err != nil { + return velero.ResourceSelector{}, err + } + + return delegate.AppliesTo() +} + +// Execute restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + delegate, err := r.getDelegate() + if err != nil { + return nil, nil, "", err + } + + return delegate.Execute(item, backup) +} + +// Progress restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) { + delegate, err := r.getDelegate() + if err != nil { + return velero.OperationProgress{}, err + } + + return delegate.Progress(operationID, backup) +} + +// Cancel restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) Cancel(operationID string, backup *api.Backup) error { + delegate, err := r.getDelegate() + if err != nil { + return err + } + + return delegate.Cancel(operationID, backup) +} + +type AdaptedV1RestartableBackupItemAction struct { + V1Restartable *biav1cli.RestartableBackupItemAction +} + +// NewAdaptedV1RestartableBackupItemAction returns a new v1 RestartableBackupItemAction adapted to v2 +func NewAdaptedV1RestartableBackupItemAction(v1Restartable *biav1cli.RestartableBackupItemAction) *AdaptedV1RestartableBackupItemAction { + r := &AdaptedV1RestartableBackupItemAction{ + V1Restartable: v1Restartable, + } + return r +} + +// AppliesTo delegates to the v1 AppliesTo call. +func (r *AdaptedV1RestartableBackupItemAction) AppliesTo() (velero.ResourceSelector, error) { + return r.V1Restartable.AppliesTo() +} + +// Execute delegates to the v1 Execute call, returning an empty operationID. +func (r *AdaptedV1RestartableBackupItemAction) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + updatedItem, additionalItems, err := r.V1Restartable.Execute(item, backup) + return updatedItem, additionalItems, "", err +} + +// Progress returns with an error since v1 plugins will never return an operationID, which means that +// any operationID passed in here will be invalid. +func (r *AdaptedV1RestartableBackupItemAction) Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) { + return velero.OperationProgress{}, biav2.AsyncOperationsNotSupportedError() +} + +// Cancel just returns without error since v1 plugins don't implement it. +func (r *AdaptedV1RestartableBackupItemAction) Cancel(operationID string, backup *api.Backup) error { + return nil +} diff --git a/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go new file mode 100644 index 000000000..24e3c12a6 --- /dev/null +++ b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go @@ -0,0 +1,164 @@ +/* +Copyright 2018 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/vmware-tanzu/velero/internal/restartabletest" + v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process" + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + mocksv2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks/backupitemaction/v2" +) + +func TestRestartableGetBackupItemAction(t *testing.T) { + tests := []struct { + name string + plugin interface{} + getError error + expectedError string + }{ + { + name: "error getting by kind and name", + getError: errors.Errorf("get error"), + expectedError: "get error", + }, + { + name: "wrong type", + plugin: 3, + expectedError: "int (returned for {BackupItemActionV2 pod}) is not a BackupItemActionV2!", + }, + { + name: "happy path", + plugin: new(mocksv2.BackupItemAction), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := new(restartabletest.MockRestartableProcess) + defer p.AssertExpectations(t) + + name := "pod" + key := process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name} + p.On("GetByKindAndName", key).Return(tc.plugin, tc.getError) + + r := NewRestartableBackupItemAction(name, p) + a, err := r.getBackupItemAction() + if tc.expectedError != "" { + assert.EqualError(t, err, tc.expectedError) + return + } + require.NoError(t, err) + + assert.Equal(t, tc.plugin, a) + }) + } +} + +func TestRestartableBackupItemActionGetDelegate(t *testing.T) { + p := new(restartabletest.MockRestartableProcess) + defer p.AssertExpectations(t) + + // Reset error + p.On("ResetIfNeeded").Return(errors.Errorf("reset error")).Once() + name := "pod" + r := NewRestartableBackupItemAction(name, p) + a, err := r.getDelegate() + assert.Nil(t, a) + assert.EqualError(t, err, "reset error") + + // Happy path + p.On("ResetIfNeeded").Return(nil) + expected := new(mocksv2.BackupItemAction) + key := process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name} + p.On("GetByKindAndName", key).Return(expected, nil) + + a, err = r.getDelegate() + assert.NoError(t, err) + assert.Equal(t, expected, a) +} + +func TestRestartableBackupItemActionDelegatedFunctions(t *testing.T) { + b := new(v1.Backup) + + pv := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "color": "blue", + }, + } + + oid := "operation1" + + pvToReturn := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "color": "green", + }, + } + + additionalItems := []velero.ResourceIdentifier{ + { + GroupResource: schema.GroupResource{Group: "velero.io", Resource: "backups"}, + }, + } + + restartabletest.RunRestartableDelegateTests( + t, + common.PluginKindBackupItemAction, + func(key process.KindAndName, p process.RestartableProcess) interface{} { + return &RestartableBackupItemAction{ + Key: key, + SharedPluginProcess: p, + } + }, + func() restartabletest.Mockable { + return new(mocksv2.BackupItemAction) + }, + restartabletest.RestartableDelegateTest{ + Function: "AppliesTo", + Inputs: []interface{}{}, + ExpectedErrorOutputs: []interface{}{velero.ResourceSelector{}, errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{velero.ResourceSelector{IncludedNamespaces: []string{"a"}}, errors.Errorf("delegate error")}, + }, + restartabletest.RestartableDelegateTest{ + Function: "Execute", + Inputs: []interface{}{pv, b}, + ExpectedErrorOutputs: []interface{}{nil, ([]velero.ResourceIdentifier)(nil), "", errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{pvToReturn, additionalItems, "", errors.Errorf("delegate error")}, + }, + restartabletest.RestartableDelegateTest{ + Function: "Progress", + Inputs: []interface{}{oid, b}, + ExpectedErrorOutputs: []interface{}{velero.OperationProgress{}, errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{velero.OperationProgress{}, errors.Errorf("delegate error")}, + }, + restartabletest.RestartableDelegateTest{ + Function: "Cancel", + Inputs: []interface{}{oid, b}, + ExpectedErrorOutputs: []interface{}{errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{errors.Errorf("delegate error")}, + }, + ) +} diff --git a/pkg/plugin/clientmgmt/manager.go b/pkg/plugin/clientmgmt/manager.go index b94d986fa..4e3343d6a 100644 --- a/pkg/plugin/clientmgmt/manager.go +++ b/pkg/plugin/clientmgmt/manager.go @@ -25,12 +25,14 @@ import ( "github.com/sirupsen/logrus" biav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v1" + biav2cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process" riav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/restoreitemaction/v1" vsv1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/volumesnapshotter/v1" "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" "github.com/vmware-tanzu/velero/pkg/plugin/velero" biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1" riav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v1" vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" @@ -50,6 +52,12 @@ type Manager interface { // GetBackupItemAction returns the backup item action plugin for name. GetBackupItemAction(name string) (biav1.BackupItemAction, error) + // GetBackupItemActionsV2 returns all v2 backup item action plugins (including those adapted from v1). + GetBackupItemActionsV2() ([]biav2.BackupItemAction, error) + + // GetBackupItemActionV2 returns the backup item action plugin for name. + GetBackupItemActionV2(name string) (biav2.BackupItemAction, error) + // GetRestoreItemActions returns all restore item action plugins. GetRestoreItemActions() ([]riav1.RestoreItemAction, error) @@ -218,6 +226,44 @@ func (m *manager) GetBackupItemAction(name string) (biav1.BackupItemAction, erro return nil, fmt.Errorf("unable to get valid BackupItemAction for %q", name) } +// GetBackupItemActionsV2 returns all v2 backup item actions as RestartableBackupItemActions. +func (m *manager) GetBackupItemActionsV2() ([]biav2.BackupItemAction, error) { + list := m.registry.List(common.PluginKindBackupItemActionV2) + + actions := make([]biav2.BackupItemAction, 0, len(list)) + + for i := range list { + id := list[i] + + r, err := m.GetBackupItemActionV2(id.Name) + if err != nil { + return nil, err + } + + actions = append(actions, r) + } + + return actions, nil +} + +// GetBackupItemActionV2 returns a v2 restartableBackupItemAction for name. +func (m *manager) GetBackupItemActionV2(name string) (biav2.BackupItemAction, error) { + name = sanitizeName(name) + + for _, adaptedBackupItemAction := range biav2cli.AdaptedBackupItemActions() { + restartableProcess, err := m.getRestartableProcess(adaptedBackupItemAction.Kind, name) + // Check if plugin was not found + if errors.As(err, &pluginNotFoundErrType) { + continue + } + if err != nil { + return nil, err + } + return adaptedBackupItemAction.GetRestartable(name, restartableProcess), nil + } + return nil, fmt.Errorf("unable to get valid BackupItemActionV2 for %q", name) +} + // GetRestoreItemActions returns all restore item actions as restartableRestoreItemActions. func (m *manager) GetRestoreItemActions() ([]riav1.RestoreItemAction, error) { list := m.registry.List(common.PluginKindRestoreItemAction) diff --git a/pkg/plugin/clientmgmt/manager_test.go b/pkg/plugin/clientmgmt/manager_test.go index 17b51a52b..541613c73 100644 --- a/pkg/plugin/clientmgmt/manager_test.go +++ b/pkg/plugin/clientmgmt/manager_test.go @@ -28,6 +28,7 @@ import ( "github.com/vmware-tanzu/velero/internal/restartabletest" biav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v1" + biav2cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process" riav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/restoreitemaction/v1" vsv1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/volumesnapshotter/v1" @@ -203,6 +204,23 @@ func TestGetBackupItemAction(t *testing.T) { ) } +func TestGetBackupItemActionV2(t *testing.T) { + getPluginTest(t, + common.PluginKindBackupItemActionV2, + "velero.io/pod", + func(m Manager, name string) (interface{}, error) { + return m.GetBackupItemActionV2(name) + }, + func(name string, sharedPluginProcess process.RestartableProcess) interface{} { + return &biav2cli.RestartableBackupItemAction{ + Key: process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name}, + SharedPluginProcess: sharedPluginProcess, + } + }, + false, + ) +} + func TestGetRestoreItemAction(t *testing.T) { getPluginTest(t, common.PluginKindRestoreItemAction, @@ -363,6 +381,98 @@ func TestGetBackupItemActions(t *testing.T) { } } +func TestGetBackupItemActionsV2(t *testing.T) { + tests := []struct { + name string + names []string + newRestartableProcessError error + expectedError string + }{ + { + name: "No items", + names: []string{}, + }, + { + name: "Error getting restartable process", + names: []string{"velero.io/a", "velero.io/b", "velero.io/c"}, + newRestartableProcessError: errors.Errorf("NewRestartableProcess"), + expectedError: "NewRestartableProcess", + }, + { + name: "Happy path", + names: []string{"velero.io/a", "velero.io/b", "velero.io/c"}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + logger := test.NewLogger() + logLevel := logrus.InfoLevel + + registry := &mockRegistry{} + defer registry.AssertExpectations(t) + + m := NewManager(logger, logLevel, registry).(*manager) + factory := &mockRestartableProcessFactory{} + defer factory.AssertExpectations(t) + m.restartableProcessFactory = factory + + pluginKind := common.PluginKindBackupItemActionV2 + var pluginIDs []framework.PluginIdentifier + for i := range tc.names { + pluginID := framework.PluginIdentifier{ + Command: "/command", + Kind: pluginKind, + Name: tc.names[i], + } + pluginIDs = append(pluginIDs, pluginID) + } + registry.On("List", pluginKind).Return(pluginIDs) + + var expectedActions []interface{} + for i := range pluginIDs { + pluginID := pluginIDs[i] + pluginName := pluginID.Name + + registry.On("Get", pluginKind, pluginName).Return(pluginID, nil) + + restartableProcess := &restartabletest.MockRestartableProcess{} + defer restartableProcess.AssertExpectations(t) + + expected := &biav2cli.RestartableBackupItemAction{ + Key: process.KindAndName{Kind: pluginKind, Name: pluginName}, + SharedPluginProcess: restartableProcess, + } + + if tc.newRestartableProcessError != nil { + // Test 1: error getting restartable process + factory.On("NewRestartableProcess", pluginID.Command, logger, logLevel).Return(nil, errors.Errorf("NewRestartableProcess")).Once() + break + } + + // Test 2: happy path + if i == 0 { + factory.On("NewRestartableProcess", pluginID.Command, logger, logLevel).Return(restartableProcess, nil).Once() + } + + expectedActions = append(expectedActions, expected) + } + + backupItemActions, err := m.GetBackupItemActionsV2() + if tc.newRestartableProcessError != nil { + assert.Nil(t, backupItemActions) + assert.EqualError(t, err, "NewRestartableProcess") + } else { + require.NoError(t, err) + var actual []interface{} + for i := range backupItemActions { + actual = append(actual, backupItemActions[i]) + } + assert.Equal(t, expectedActions, actual) + } + }) + } +} + func TestGetRestoreItemActions(t *testing.T) { tests := []struct { name string diff --git a/pkg/plugin/clientmgmt/process/client_builder.go b/pkg/plugin/clientmgmt/process/client_builder.go index aaa56661a..ffadcc95f 100644 --- a/pkg/plugin/clientmgmt/process/client_builder.go +++ b/pkg/plugin/clientmgmt/process/client_builder.go @@ -27,6 +27,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/features" "github.com/vmware-tanzu/velero/pkg/plugin/framework" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" ) @@ -68,13 +69,14 @@ func (b *clientBuilder) clientConfig() *hcplugin.ClientConfig { HandshakeConfig: framework.Handshake(), AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC}, Plugins: map[string]hcplugin.Plugin{ - string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(b.clientLogger)), - string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(b.clientLogger)), - string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(b.clientLogger)), - string(common.PluginKindPluginLister): &framework.PluginListerPlugin{}, - string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(b.clientLogger)), - string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(b.clientLogger)), - string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindBackupItemActionV2): biav2.NewBackupItemActionPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindPluginLister): &framework.PluginListerPlugin{}, + string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(b.clientLogger)), }, Logger: b.pluginLogger, Cmd: exec.Command(b.commandName, b.commandArgs...), //nolint diff --git a/pkg/plugin/clientmgmt/process/client_builder_test.go b/pkg/plugin/clientmgmt/process/client_builder_test.go index 2a3f6df61..c70cf29bf 100644 --- a/pkg/plugin/clientmgmt/process/client_builder_test.go +++ b/pkg/plugin/clientmgmt/process/client_builder_test.go @@ -27,6 +27,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/features" "github.com/vmware-tanzu/velero/pkg/plugin/framework" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" "github.com/vmware-tanzu/velero/pkg/test" ) @@ -61,13 +62,14 @@ func TestClientConfig(t *testing.T) { HandshakeConfig: framework.Handshake(), AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC}, Plugins: map[string]hcplugin.Plugin{ - string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(logger)), - string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(logger)), - string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(logger)), - string(common.PluginKindPluginLister): &framework.PluginListerPlugin{}, - string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(logger)), - string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(logger)), - string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(logger)), + string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(logger)), + string(common.PluginKindBackupItemActionV2): biav2.NewBackupItemActionPlugin(common.ClientLogger(logger)), + string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(logger)), + string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(logger)), + string(common.PluginKindPluginLister): &framework.PluginListerPlugin{}, + string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(logger)), + string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(logger)), + string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(logger)), }, Logger: cb.pluginLogger, Cmd: exec.Command(cb.commandName, cb.commandArgs...), diff --git a/pkg/plugin/framework/action_resolver.go b/pkg/plugin/framework/action_resolver.go index 5664d08c2..f9ad62ab5 100644 --- a/pkg/plugin/framework/action_resolver.go +++ b/pkg/plugin/framework/action_resolver.go @@ -17,6 +17,7 @@ limitations under the License. package framework import ( + "github.com/pkg/errors" "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -25,6 +26,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/discovery" "github.com/vmware-tanzu/velero/pkg/plugin/velero" biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1" riav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v1" "github.com/vmware-tanzu/velero/pkg/util/collections" @@ -109,6 +111,17 @@ func NewBackupItemActionResolver(actions []biav1.BackupItemAction) BackupItemAct } } +type BackupItemResolvedActionV2 struct { + biav2.BackupItemAction + resolvedAction +} + +func NewBackupItemActionResolverV2(actions []biav2.BackupItemAction) BackupItemActionResolverV2 { + return BackupItemActionResolverV2{ + actions: actions, + } +} + func NewRestoreItemActionResolver(actions []riav1.RestoreItemAction) RestoreItemActionResolver { return RestoreItemActionResolver{ actions: actions, @@ -155,6 +168,32 @@ func (recv BackupItemActionResolver) ResolveActions(helper discovery.Helper, log return resolved, nil } +type BackupItemActionResolverV2 struct { + actions []biav2.BackupItemAction +} + +func (recv BackupItemActionResolverV2) ResolveActions(helper discovery.Helper, log logrus.FieldLogger) ([]BackupItemResolvedActionV2, error) { + var resolved []BackupItemResolvedActionV2 + for _, action := range recv.actions { + log.Debugf("resolving BackupItemAction for: %v", action) + resources, namespaces, selector, err := resolveAction(helper, action) + if err != nil { + log.WithError(errors.WithStack(err)).Debugf("resolveAction error, action: %v", action) + return nil, err + } + res := BackupItemResolvedActionV2{ + BackupItemAction: action, + resolvedAction: resolvedAction{ + ResourceIncludesExcludes: resources, + NamespaceIncludesExcludes: namespaces, + Selector: selector, + }, + } + resolved = append(resolved, res) + } + return resolved, nil +} + type RestoreItemResolvedAction struct { riav1.RestoreItemAction resolvedAction diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action.go new file mode 100644 index 000000000..a4efa9a2e --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action.go @@ -0,0 +1,45 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + plugin "github.com/hashicorp/go-plugin" + "golang.org/x/net/context" + "google.golang.org/grpc" + + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" +) + +// BackupItemActionPlugin is an implementation of go-plugin's Plugin +// interface with support for gRPC for the backup/ItemAction +// interface. +type BackupItemActionPlugin struct { + plugin.NetRPCUnsupportedPlugin + *common.PluginBase +} + +// GRPCClient returns a clientDispenser for BackupItemAction gRPC clients. +func (p *BackupItemActionPlugin) GRPCClient(_ context.Context, _ *plugin.GRPCBroker, clientConn *grpc.ClientConn) (interface{}, error) { + return common.NewClientDispenser(p.ClientLogger, clientConn, newBackupItemActionGRPCClient), nil +} + +// GRPCServer registers a BackupItemAction gRPC server. +func (p *BackupItemActionPlugin) GRPCServer(_ *plugin.GRPCBroker, server *grpc.Server) error { + protobiav2.RegisterBackupItemActionServer(server, &BackupItemActionGRPCServer{mux: p.ServerMux}) + return nil +} diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go new file mode 100644 index 000000000..925cf037c --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go @@ -0,0 +1,169 @@ +/* +Copyright 2017, 2019 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "encoding/json" + + "github.com/pkg/errors" + "golang.org/x/net/context" + "google.golang.org/grpc" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" +) + +// NewBackupItemActionPlugin constructs a BackupItemActionPlugin. +func NewBackupItemActionPlugin(options ...common.PluginOption) *BackupItemActionPlugin { + return &BackupItemActionPlugin{ + PluginBase: common.NewPluginBase(options...), + } +} + +// BackupItemActionGRPCClient implements the backup/ItemAction interface and uses a +// gRPC client to make calls to the plugin server. +type BackupItemActionGRPCClient struct { + *common.ClientBase + grpcClient protobiav2.BackupItemActionClient +} + +func newBackupItemActionGRPCClient(base *common.ClientBase, clientConn *grpc.ClientConn) interface{} { + return &BackupItemActionGRPCClient{ + ClientBase: base, + grpcClient: protobiav2.NewBackupItemActionClient(clientConn), + } +} + +func (c *BackupItemActionGRPCClient) AppliesTo() (velero.ResourceSelector, error) { + req := &protobiav2.BackupItemActionAppliesToRequest{ + Plugin: c.Plugin, + } + + res, err := c.grpcClient.AppliesTo(context.Background(), req) + if err != nil { + return velero.ResourceSelector{}, common.FromGRPCError(err) + } + + if res.ResourceSelector == nil { + return velero.ResourceSelector{}, nil + } + + return velero.ResourceSelector{ + IncludedNamespaces: res.ResourceSelector.IncludedNamespaces, + ExcludedNamespaces: res.ResourceSelector.ExcludedNamespaces, + IncludedResources: res.ResourceSelector.IncludedResources, + ExcludedResources: res.ResourceSelector.ExcludedResources, + LabelSelector: res.ResourceSelector.Selector, + }, nil +} + +func (c *BackupItemActionGRPCClient) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + itemJSON, err := json.Marshal(item.UnstructuredContent()) + if err != nil { + return nil, nil, "", errors.WithStack(err) + } + + backupJSON, err := json.Marshal(backup) + if err != nil { + return nil, nil, "", errors.WithStack(err) + } + + req := &protobiav2.ExecuteRequest{ + Plugin: c.Plugin, + Item: itemJSON, + Backup: backupJSON, + } + + res, err := c.grpcClient.Execute(context.Background(), req) + if err != nil { + return nil, nil, "", common.FromGRPCError(err) + } + + var updatedItem unstructured.Unstructured + if err := json.Unmarshal(res.Item, &updatedItem); err != nil { + return nil, nil, "", errors.WithStack(err) + } + + var additionalItems []velero.ResourceIdentifier + + for _, itm := range res.AdditionalItems { + newItem := velero.ResourceIdentifier{ + GroupResource: schema.GroupResource{ + Group: itm.Group, + Resource: itm.Resource, + }, + Namespace: itm.Namespace, + Name: itm.Name, + } + + additionalItems = append(additionalItems, newItem) + } + + return &updatedItem, additionalItems, res.OperationID, nil +} + +func (c *BackupItemActionGRPCClient) Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) { + backupJSON, err := json.Marshal(backup) + if err != nil { + return velero.OperationProgress{}, errors.WithStack(err) + } + req := &protobiav2.BackupItemActionProgressRequest{ + Plugin: c.Plugin, + OperationID: operationID, + Backup: backupJSON, + } + + res, err := c.grpcClient.Progress(context.Background(), req) + if err != nil { + return velero.OperationProgress{}, common.FromGRPCError(err) + } + + return velero.OperationProgress{ + Completed: res.Progress.Completed, + Err: res.Progress.Err, + NCompleted: res.Progress.NCompleted, + NTotal: res.Progress.NTotal, + OperationUnits: res.Progress.OperationUnits, + Description: res.Progress.Description, + Started: res.Progress.Started.AsTime(), + Updated: res.Progress.Updated.AsTime(), + }, nil +} + +func (c *BackupItemActionGRPCClient) Cancel(operationID string, backup *api.Backup) error { + backupJSON, err := json.Marshal(backup) + if err != nil { + return errors.WithStack(err) + } + req := &protobiav2.BackupItemActionCancelRequest{ + Plugin: c.Plugin, + OperationID: operationID, + Backup: backupJSON, + } + + _, err = c.grpcClient.Cancel(context.Background(), req) + if err != nil { + return common.FromGRPCError(err) + } + + return nil +} diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go new file mode 100644 index 000000000..2de33ed7c --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go @@ -0,0 +1,212 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "encoding/json" + + "github.com/pkg/errors" + "golang.org/x/net/context" + "google.golang.org/protobuf/types/known/emptypb" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + + api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + proto "github.com/vmware-tanzu/velero/pkg/plugin/generated" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" +) + +// BackupItemActionGRPCServer implements the proto-generated BackupItemAction interface, and accepts +// gRPC calls and forwards them to an implementation of the pluggable interface. +type BackupItemActionGRPCServer struct { + mux *common.ServerMux +} + +func (s *BackupItemActionGRPCServer) getImpl(name string) (biav2.BackupItemAction, error) { + impl, err := s.mux.GetHandler(name) + if err != nil { + return nil, err + } + + itemAction, ok := impl.(biav2.BackupItemAction) + if !ok { + return nil, errors.Errorf("%T is not a backup item action", impl) + } + + return itemAction, nil +} + +func (s *BackupItemActionGRPCServer) AppliesTo( + ctx context.Context, req *protobiav2.BackupItemActionAppliesToRequest) ( + response *protobiav2.BackupItemActionAppliesToResponse, err error) { + defer func() { + if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil { + err = recoveredErr + } + }() + + impl, err := s.getImpl(req.Plugin) + if err != nil { + return nil, common.NewGRPCError(err) + } + + resourceSelector, err := impl.AppliesTo() + if err != nil { + return nil, common.NewGRPCError(err) + } + + return &protobiav2.BackupItemActionAppliesToResponse{ + ResourceSelector: &proto.ResourceSelector{ + IncludedNamespaces: resourceSelector.IncludedNamespaces, + ExcludedNamespaces: resourceSelector.ExcludedNamespaces, + IncludedResources: resourceSelector.IncludedResources, + ExcludedResources: resourceSelector.ExcludedResources, + Selector: resourceSelector.LabelSelector, + }, + }, nil +} + +func (s *BackupItemActionGRPCServer) Execute( + ctx context.Context, req *protobiav2.ExecuteRequest) (response *protobiav2.ExecuteResponse, err error) { + defer func() { + if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil { + err = recoveredErr + } + }() + + impl, err := s.getImpl(req.Plugin) + if err != nil { + return nil, common.NewGRPCError(err) + } + + var item unstructured.Unstructured + var backup api.Backup + + if err := json.Unmarshal(req.Item, &item); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + if err := json.Unmarshal(req.Backup, &backup); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + + updatedItem, additionalItems, operationID, err := impl.Execute(&item, &backup) + if err != nil { + return nil, common.NewGRPCError(err) + } + + // If the plugin implementation returned a nil updatedItem (meaning no modifications), reset updatedItem to the + // original item. + var updatedItemJSON []byte + if updatedItem == nil { + updatedItemJSON = req.Item + } else { + updatedItemJSON, err = json.Marshal(updatedItem.UnstructuredContent()) + if err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + } + + res := &protobiav2.ExecuteResponse{ + Item: updatedItemJSON, + OperationID: operationID, + } + + for _, item := range additionalItems { + res.AdditionalItems = append(res.AdditionalItems, backupResourceIdentifierToProto(item)) + } + + return res, nil +} + +func (s *BackupItemActionGRPCServer) Progress( + ctx context.Context, req *protobiav2.BackupItemActionProgressRequest) ( + response *protobiav2.BackupItemActionProgressResponse, err error) { + defer func() { + if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil { + err = recoveredErr + } + }() + + impl, err := s.getImpl(req.Plugin) + if err != nil { + return nil, common.NewGRPCError(err) + } + + var backup api.Backup + if err := json.Unmarshal(req.Backup, &backup); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + + progress, err := impl.Progress(req.OperationID, &backup) + if err != nil { + return nil, common.NewGRPCError(err) + } + + res := &protobiav2.BackupItemActionProgressResponse{ + Progress: &proto.OperationProgress{ + Completed: progress.Completed, + Err: progress.Err, + NCompleted: progress.NCompleted, + NTotal: progress.NTotal, + OperationUnits: progress.OperationUnits, + Description: progress.Description, + Started: timestamppb.New(progress.Started), + Updated: timestamppb.New(progress.Updated), + }, + } + return res, nil +} + +func (s *BackupItemActionGRPCServer) Cancel( + ctx context.Context, req *protobiav2.BackupItemActionCancelRequest) ( + response *emptypb.Empty, err error) { + defer func() { + if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil { + err = recoveredErr + } + }() + + impl, err := s.getImpl(req.Plugin) + if err != nil { + return nil, common.NewGRPCError(err) + } + + var backup api.Backup + if err := json.Unmarshal(req.Backup, &backup); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + + err = impl.Cancel(req.OperationID, &backup) + if err != nil { + return nil, common.NewGRPCError(err) + } + + return &emptypb.Empty{}, nil +} + +func backupResourceIdentifierToProto(id velero.ResourceIdentifier) *proto.ResourceIdentifier { + return &proto.ResourceIdentifier{ + Group: id.Group, + Resource: id.Resource, + Namespace: id.Namespace, + Name: id.Name, + } +} diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go new file mode 100644 index 000000000..d9e2d2cea --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go @@ -0,0 +1,202 @@ +/* +Copyright 2018 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "encoding/json" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + proto "github.com/vmware-tanzu/velero/pkg/plugin/generated" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + mocks "github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks/backupitemaction/v2" + velerotest "github.com/vmware-tanzu/velero/pkg/test" +) + +func TestBackupItemActionGRPCServerExecute(t *testing.T) { + invalidItem := []byte("this is gibberish json") + validItem := []byte(` + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "namespace": "myns", + "name": "myconfigmap" + }, + "data": { + "key": "value" + } + }`) + var validItemObject unstructured.Unstructured + err := json.Unmarshal(validItem, &validItemObject) + require.NoError(t, err) + + updatedItem := []byte(` + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "namespace": "myns", + "name": "myconfigmap" + }, + "data": { + "key": "changed!" + } + }`) + var updatedItemObject unstructured.Unstructured + err = json.Unmarshal(updatedItem, &updatedItemObject) + require.NoError(t, err) + + invalidBackup := []byte("this is gibberish json") + validBackup := []byte(` + { + "apiVersion": "velero.io/v1", + "kind": "Backup", + "metadata": { + "namespace": "myns", + "name": "mybackup" + }, + "spec": { + "includedNamespaces": ["*"], + "includedResources": ["*"], + "ttl": "60m" + } + }`) + var validBackupObject v1.Backup + err = json.Unmarshal(validBackup, &validBackupObject) + require.NoError(t, err) + + tests := []struct { + name string + backup []byte + item []byte + implUpdatedItem runtime.Unstructured + implAdditionalItems []velero.ResourceIdentifier + implOperationID string + implError error + expectError bool + skipMock bool + }{ + { + name: "error unmarshaling item", + item: invalidItem, + backup: validBackup, + expectError: true, + skipMock: true, + }, + { + name: "error unmarshaling backup", + item: validItem, + backup: invalidBackup, + expectError: true, + skipMock: true, + }, + { + name: "error running impl", + item: validItem, + backup: validBackup, + implError: errors.New("impl error"), + expectError: true, + }, + { + name: "nil updatedItem / no additionalItems", + item: validItem, + backup: validBackup, + }, + { + name: "same updatedItem / some additionalItems", + item: validItem, + backup: validBackup, + implUpdatedItem: &validItemObject, + implAdditionalItems: []velero.ResourceIdentifier{ + { + GroupResource: schema.GroupResource{Group: "v1", Resource: "pods"}, + Namespace: "myns", + Name: "mypod", + }, + }, + }, + { + name: "different updatedItem", + item: validItem, + backup: validBackup, + implUpdatedItem: &updatedItemObject, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + itemAction := &mocks.BackupItemAction{} + defer itemAction.AssertExpectations(t) + + if !test.skipMock { + itemAction.On("Execute", &validItemObject, &validBackupObject).Return(test.implUpdatedItem, test.implAdditionalItems, test.implOperationID, test.implError) + } + + s := &BackupItemActionGRPCServer{mux: &common.ServerMux{ + ServerLog: velerotest.NewLogger(), + Handlers: map[string]interface{}{ + "xyz": itemAction, + }, + }} + + req := &protobiav2.ExecuteRequest{ + Plugin: "xyz", + Item: test.item, + Backup: test.backup, + } + + resp, err := s.Execute(context.Background(), req) + + // Verify error + assert.Equal(t, test.expectError, err != nil) + if err != nil { + return + } + require.NotNil(t, resp) + + // Verify updated item + updatedItem := test.implUpdatedItem + if updatedItem == nil { + // If the impl returned nil for its updatedItem, we should expect the plugin to return the original item + updatedItem = &validItemObject + } + + var respItem unstructured.Unstructured + err = json.Unmarshal(resp.Item, &respItem) + require.NoError(t, err) + + assert.Equal(t, updatedItem, &respItem) + + // Verify additional items + var expectedAdditionalItems []*proto.ResourceIdentifier + for _, item := range test.implAdditionalItems { + expectedAdditionalItems = append(expectedAdditionalItems, backupResourceIdentifierToProto(item)) + } + assert.Equal(t, expectedAdditionalItems, resp.AdditionalItems) + }) + } +} diff --git a/pkg/plugin/framework/common/plugin_kinds.go b/pkg/plugin/framework/common/plugin_kinds.go index 2e0119e2f..c6e25e5b6 100644 --- a/pkg/plugin/framework/common/plugin_kinds.go +++ b/pkg/plugin/framework/common/plugin_kinds.go @@ -35,6 +35,9 @@ const ( // PluginKindBackupItemAction represents a backup item action plugin. PluginKindBackupItemAction PluginKind = "BackupItemAction" + // PluginKindBackupItemActionV2 represents a v2 backup item action plugin. + PluginKindBackupItemActionV2 PluginKind = "BackupItemActionV2" + // PluginKindRestoreItemAction represents a restore item action plugin. PluginKindRestoreItemAction PluginKind = "RestoreItemAction" @@ -51,7 +54,9 @@ const ( // If there are plugin kinds that are adaptable to newer API versions, list them here. // The older (adaptable) version is the key, and the value is the full list of newer // plugin kinds that are capable of adapting it. -var PluginKindsAdaptableTo = map[PluginKind][]PluginKind{} +var PluginKindsAdaptableTo = map[PluginKind][]PluginKind{ + PluginKindBackupItemAction: {PluginKindBackupItemActionV2}, +} // AllPluginKinds contains all the valid plugin kinds that Velero supports, excluding PluginLister because that is not a // kind that a developer would ever need to implement (it's handled by Velero and the Velero plugin library code). @@ -60,6 +65,7 @@ func AllPluginKinds() map[string]PluginKind { allPluginKinds[PluginKindObjectStore.String()] = PluginKindObjectStore allPluginKinds[PluginKindVolumeSnapshotter.String()] = PluginKindVolumeSnapshotter allPluginKinds[PluginKindBackupItemAction.String()] = PluginKindBackupItemAction + allPluginKinds[PluginKindBackupItemActionV2.String()] = PluginKindBackupItemActionV2 allPluginKinds[PluginKindRestoreItemAction.String()] = PluginKindRestoreItemAction allPluginKinds[PluginKindDeleteItemAction.String()] = PluginKindDeleteItemAction allPluginKinds[PluginKindItemSnapshotter.String()] = PluginKindItemSnapshotter diff --git a/pkg/plugin/framework/server.go b/pkg/plugin/framework/server.go index 20c5e48e0..d9a1af387 100644 --- a/pkg/plugin/framework/server.go +++ b/pkg/plugin/framework/server.go @@ -25,6 +25,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/pflag" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" "github.com/vmware-tanzu/velero/pkg/util/logging" ) @@ -46,6 +47,13 @@ type Server interface { // RegisterBackupItemActions registers multiple backup item actions. RegisterBackupItemActions(map[string]common.HandlerInitializer) Server + // RegisterBackupItemActionV2 registers a v2 backup item action. Accepted format + // for the plugin name is /. + RegisterBackupItemActionV2(pluginName string, initializer common.HandlerInitializer) Server + + // RegisterBackupItemActionsV2 registers multiple v2 backup item actions. + RegisterBackupItemActionsV2(map[string]common.HandlerInitializer) Server + // RegisterVolumeSnapshotter registers a volume snapshotter. Accepted format // for the plugin name is /. RegisterVolumeSnapshotter(pluginName string, initializer common.HandlerInitializer) Server @@ -85,15 +93,16 @@ type Server interface { // server implements Server. type server struct { - log *logrus.Logger - logLevelFlag *logging.LevelFlag - flagSet *pflag.FlagSet - backupItemAction *BackupItemActionPlugin - volumeSnapshotter *VolumeSnapshotterPlugin - objectStore *ObjectStorePlugin - restoreItemAction *RestoreItemActionPlugin - deleteItemAction *DeleteItemActionPlugin - itemSnapshotter *ItemSnapshotterPlugin + log *logrus.Logger + logLevelFlag *logging.LevelFlag + flagSet *pflag.FlagSet + backupItemAction *BackupItemActionPlugin + backupItemActionV2 *biav2.BackupItemActionPlugin + volumeSnapshotter *VolumeSnapshotterPlugin + objectStore *ObjectStorePlugin + restoreItemAction *RestoreItemActionPlugin + deleteItemAction *DeleteItemActionPlugin + itemSnapshotter *ItemSnapshotterPlugin } // NewServer returns a new Server @@ -101,14 +110,15 @@ func NewServer() Server { log := newLogger() return &server{ - log: log, - logLevelFlag: logging.LogLevelFlag(log.Level), - backupItemAction: NewBackupItemActionPlugin(common.ServerLogger(log)), - volumeSnapshotter: NewVolumeSnapshotterPlugin(common.ServerLogger(log)), - objectStore: NewObjectStorePlugin(common.ServerLogger(log)), - restoreItemAction: NewRestoreItemActionPlugin(common.ServerLogger(log)), - deleteItemAction: NewDeleteItemActionPlugin(common.ServerLogger(log)), - itemSnapshotter: NewItemSnapshotterPlugin(common.ServerLogger(log)), + log: log, + logLevelFlag: logging.LogLevelFlag(log.Level), + backupItemAction: NewBackupItemActionPlugin(common.ServerLogger(log)), + backupItemActionV2: biav2.NewBackupItemActionPlugin(common.ServerLogger(log)), + volumeSnapshotter: NewVolumeSnapshotterPlugin(common.ServerLogger(log)), + objectStore: NewObjectStorePlugin(common.ServerLogger(log)), + restoreItemAction: NewRestoreItemActionPlugin(common.ServerLogger(log)), + deleteItemAction: NewDeleteItemActionPlugin(common.ServerLogger(log)), + itemSnapshotter: NewItemSnapshotterPlugin(common.ServerLogger(log)), } } @@ -132,6 +142,18 @@ func (s *server) RegisterBackupItemActions(m map[string]common.HandlerInitialize return s } +func (s *server) RegisterBackupItemActionV2(name string, initializer common.HandlerInitializer) Server { + s.backupItemActionV2.Register(name, initializer) + return s +} + +func (s *server) RegisterBackupItemActionsV2(m map[string]common.HandlerInitializer) Server { + for name := range m { + s.RegisterBackupItemActionV2(name, m[name]) + } + return s +} + func (s *server) RegisterVolumeSnapshotter(name string, initializer common.HandlerInitializer) Server { s.volumeSnapshotter.Register(name, initializer) return s @@ -216,6 +238,7 @@ func (s *server) Serve() { var pluginIdentifiers []PluginIdentifier pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindBackupItemAction, s.backupItemAction)...) + pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindBackupItemActionV2, s.backupItemActionV2)...) pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindVolumeSnapshotter, s.volumeSnapshotter)...) pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindObjectStore, s.objectStore)...) pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindRestoreItemAction, s.restoreItemAction)...) @@ -227,13 +250,14 @@ func (s *server) Serve() { plugin.Serve(&plugin.ServeConfig{ HandshakeConfig: Handshake(), Plugins: map[string]plugin.Plugin{ - string(common.PluginKindBackupItemAction): s.backupItemAction, - string(common.PluginKindVolumeSnapshotter): s.volumeSnapshotter, - string(common.PluginKindObjectStore): s.objectStore, - string(common.PluginKindPluginLister): NewPluginListerPlugin(pluginLister), - string(common.PluginKindRestoreItemAction): s.restoreItemAction, - string(common.PluginKindDeleteItemAction): s.deleteItemAction, - string(common.PluginKindItemSnapshotter): s.itemSnapshotter, + string(common.PluginKindBackupItemAction): s.backupItemAction, + string(common.PluginKindBackupItemActionV2): s.backupItemActionV2, + string(common.PluginKindVolumeSnapshotter): s.volumeSnapshotter, + string(common.PluginKindObjectStore): s.objectStore, + string(common.PluginKindPluginLister): NewPluginListerPlugin(pluginLister), + string(common.PluginKindRestoreItemAction): s.restoreItemAction, + string(common.PluginKindDeleteItemAction): s.deleteItemAction, + string(common.PluginKindItemSnapshotter): s.itemSnapshotter, }, GRPCServer: plugin.DefaultGRPCServer, }) diff --git a/pkg/plugin/generated/Shared.pb.go b/pkg/plugin/generated/Shared.pb.go index 1c84177dc..55a829927 100644 --- a/pkg/plugin/generated/Shared.pb.go +++ b/pkg/plugin/generated/Shared.pb.go @@ -10,6 +10,7 @@ import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) @@ -323,47 +324,171 @@ func (x *ResourceSelector) GetSelector() string { return "" } +type OperationProgress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Completed bool `protobuf:"varint,1,opt,name=completed,proto3" json:"completed,omitempty"` + Err string `protobuf:"bytes,2,opt,name=err,proto3" json:"err,omitempty"` + NCompleted int64 `protobuf:"varint,3,opt,name=nCompleted,proto3" json:"nCompleted,omitempty"` + NTotal int64 `protobuf:"varint,4,opt,name=nTotal,proto3" json:"nTotal,omitempty"` + OperationUnits string `protobuf:"bytes,5,opt,name=operationUnits,proto3" json:"operationUnits,omitempty"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` + Started *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=started,proto3" json:"started,omitempty"` + Updated *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=updated,proto3" json:"updated,omitempty"` +} + +func (x *OperationProgress) Reset() { + *x = OperationProgress{} + if protoimpl.UnsafeEnabled { + mi := &file_Shared_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OperationProgress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OperationProgress) ProtoMessage() {} + +func (x *OperationProgress) ProtoReflect() protoreflect.Message { + mi := &file_Shared_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OperationProgress.ProtoReflect.Descriptor instead. +func (*OperationProgress) Descriptor() ([]byte, []int) { + return file_Shared_proto_rawDescGZIP(), []int{5} +} + +func (x *OperationProgress) GetCompleted() bool { + if x != nil { + return x.Completed + } + return false +} + +func (x *OperationProgress) GetErr() string { + if x != nil { + return x.Err + } + return "" +} + +func (x *OperationProgress) GetNCompleted() int64 { + if x != nil { + return x.NCompleted + } + return 0 +} + +func (x *OperationProgress) GetNTotal() int64 { + if x != nil { + return x.NTotal + } + return 0 +} + +func (x *OperationProgress) GetOperationUnits() string { + if x != nil { + return x.OperationUnits + } + return "" +} + +func (x *OperationProgress) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *OperationProgress) GetStarted() *timestamppb.Timestamp { + if x != nil { + return x.Started + } + return nil +} + +func (x *OperationProgress) GetUpdated() *timestamppb.Timestamp { + if x != nil { + return x.Updated + } + return nil +} + var File_Shared_proto protoreflect.FileDescriptor var file_Shared_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x36, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x12, 0x2d, 0x0a, 0x06, 0x66, - 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x46, 0x72, 0x61, - 0x6d, 0x65, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x50, 0x0a, 0x0a, 0x53, 0x74, - 0x61, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x78, 0x0a, 0x12, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x2e, 0x0a, 0x12, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x65, - 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x78, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x61, 0x6e, 0x7a, 0x75, 0x2f, 0x76, - 0x65, 0x6c, 0x65, 0x72, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x36, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x12, 0x2d, 0x0a, 0x06, + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x46, 0x72, + 0x61, 0x6d, 0x65, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x50, 0x0a, 0x0a, 0x53, + 0x74, 0x61, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6c, 0x69, 0x6e, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x78, 0x0a, + 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x2e, 0x0a, 0x12, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x12, + 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x78, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x22, 0xb1, 0x02, 0x0a, 0x11, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x6e, 0x43, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, + 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x54, + 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x54, 0x6f, 0x74, + 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, + 0x6e, 0x69, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x07, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x65, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x61, + 0x6e, 0x7a, 0x75, 0x2f, 0x76, 0x65, 0x6c, 0x65, 0x72, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -378,21 +503,25 @@ func file_Shared_proto_rawDescGZIP() []byte { return file_Shared_proto_rawDescData } -var file_Shared_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_Shared_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_Shared_proto_goTypes = []interface{}{ - (*Empty)(nil), // 0: generated.Empty - (*Stack)(nil), // 1: generated.Stack - (*StackFrame)(nil), // 2: generated.StackFrame - (*ResourceIdentifier)(nil), // 3: generated.ResourceIdentifier - (*ResourceSelector)(nil), // 4: generated.ResourceSelector + (*Empty)(nil), // 0: generated.Empty + (*Stack)(nil), // 1: generated.Stack + (*StackFrame)(nil), // 2: generated.StackFrame + (*ResourceIdentifier)(nil), // 3: generated.ResourceIdentifier + (*ResourceSelector)(nil), // 4: generated.ResourceSelector + (*OperationProgress)(nil), // 5: generated.OperationProgress + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp } var file_Shared_proto_depIdxs = []int32{ 2, // 0: generated.Stack.frames:type_name -> generated.StackFrame - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 6, // 1: generated.OperationProgress.started:type_name -> google.protobuf.Timestamp + 6, // 2: generated.OperationProgress.updated:type_name -> google.protobuf.Timestamp + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_Shared_proto_init() } @@ -461,6 +590,18 @@ func file_Shared_proto_init() { return nil } } + file_Shared_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OperationProgress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -468,7 +609,7 @@ func file_Shared_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_Shared_proto_rawDesc, NumEnums: 0, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go b/pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go new file mode 100644 index 000000000..bbcaa50e6 --- /dev/null +++ b/pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go @@ -0,0 +1,851 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.23.0 +// protoc v3.14.0 +// source: backupitemaction/v2/BackupItemAction.proto + +package v2 + +import ( + context "context" + proto "github.com/golang/protobuf/proto" + generated "github.com/vmware-tanzu/velero/pkg/plugin/generated" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type ExecuteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` + Item []byte `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` + Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"` +} + +func (x *ExecuteRequest) Reset() { + *x = ExecuteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecuteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecuteRequest) ProtoMessage() {} + +func (x *ExecuteRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecuteRequest.ProtoReflect.Descriptor instead. +func (*ExecuteRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{0} +} + +func (x *ExecuteRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *ExecuteRequest) GetItem() []byte { + if x != nil { + return x.Item + } + return nil +} + +func (x *ExecuteRequest) GetBackup() []byte { + if x != nil { + return x.Backup + } + return nil +} + +type ExecuteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item []byte `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + AdditionalItems []*generated.ResourceIdentifier `protobuf:"bytes,2,rep,name=additionalItems,proto3" json:"additionalItems,omitempty"` + OperationID string `protobuf:"bytes,3,opt,name=operationID,proto3" json:"operationID,omitempty"` +} + +func (x *ExecuteResponse) Reset() { + *x = ExecuteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecuteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecuteResponse) ProtoMessage() {} + +func (x *ExecuteResponse) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecuteResponse.ProtoReflect.Descriptor instead. +func (*ExecuteResponse) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{1} +} + +func (x *ExecuteResponse) GetItem() []byte { + if x != nil { + return x.Item + } + return nil +} + +func (x *ExecuteResponse) GetAdditionalItems() []*generated.ResourceIdentifier { + if x != nil { + return x.AdditionalItems + } + return nil +} + +func (x *ExecuteResponse) GetOperationID() string { + if x != nil { + return x.OperationID + } + return "" +} + +type BackupItemActionAppliesToRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` +} + +func (x *BackupItemActionAppliesToRequest) Reset() { + *x = BackupItemActionAppliesToRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionAppliesToRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionAppliesToRequest) ProtoMessage() {} + +func (x *BackupItemActionAppliesToRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionAppliesToRequest.ProtoReflect.Descriptor instead. +func (*BackupItemActionAppliesToRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{2} +} + +func (x *BackupItemActionAppliesToRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +type BackupItemActionAppliesToResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ResourceSelector *generated.ResourceSelector `protobuf:"bytes,1,opt,name=ResourceSelector,proto3" json:"ResourceSelector,omitempty"` +} + +func (x *BackupItemActionAppliesToResponse) Reset() { + *x = BackupItemActionAppliesToResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionAppliesToResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionAppliesToResponse) ProtoMessage() {} + +func (x *BackupItemActionAppliesToResponse) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionAppliesToResponse.ProtoReflect.Descriptor instead. +func (*BackupItemActionAppliesToResponse) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{3} +} + +func (x *BackupItemActionAppliesToResponse) GetResourceSelector() *generated.ResourceSelector { + if x != nil { + return x.ResourceSelector + } + return nil +} + +type BackupItemActionProgressRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` + OperationID string `protobuf:"bytes,2,opt,name=operationID,proto3" json:"operationID,omitempty"` + Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"` +} + +func (x *BackupItemActionProgressRequest) Reset() { + *x = BackupItemActionProgressRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionProgressRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionProgressRequest) ProtoMessage() {} + +func (x *BackupItemActionProgressRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionProgressRequest.ProtoReflect.Descriptor instead. +func (*BackupItemActionProgressRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{4} +} + +func (x *BackupItemActionProgressRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *BackupItemActionProgressRequest) GetOperationID() string { + if x != nil { + return x.OperationID + } + return "" +} + +func (x *BackupItemActionProgressRequest) GetBackup() []byte { + if x != nil { + return x.Backup + } + return nil +} + +type BackupItemActionProgressResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Progress *generated.OperationProgress `protobuf:"bytes,1,opt,name=progress,proto3" json:"progress,omitempty"` +} + +func (x *BackupItemActionProgressResponse) Reset() { + *x = BackupItemActionProgressResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionProgressResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionProgressResponse) ProtoMessage() {} + +func (x *BackupItemActionProgressResponse) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionProgressResponse.ProtoReflect.Descriptor instead. +func (*BackupItemActionProgressResponse) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{5} +} + +func (x *BackupItemActionProgressResponse) GetProgress() *generated.OperationProgress { + if x != nil { + return x.Progress + } + return nil +} + +type BackupItemActionCancelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` + OperationID string `protobuf:"bytes,2,opt,name=operationID,proto3" json:"operationID,omitempty"` + Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"` +} + +func (x *BackupItemActionCancelRequest) Reset() { + *x = BackupItemActionCancelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionCancelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionCancelRequest) ProtoMessage() {} + +func (x *BackupItemActionCancelRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionCancelRequest.ProtoReflect.Descriptor instead. +func (*BackupItemActionCancelRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{6} +} + +func (x *BackupItemActionCancelRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *BackupItemActionCancelRequest) GetOperationID() string { + if x != nil { + return x.OperationID + } + return "" +} + +func (x *BackupItemActionCancelRequest) GetBackup() []byte { + if x != nil { + return x.Backup + } + return nil +} + +var File_backupitemaction_v2_BackupItemAction_proto protoreflect.FileDescriptor + +var file_backupitemaction_v2_BackupItemAction_proto_rawDesc = []byte{ + 0x0a, 0x2a, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x69, 0x74, 0x65, 0x6d, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, 0x32, + 0x1a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x54, 0x0a, 0x0e, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x22, 0x90, 0x01, 0x0a, 0x0f, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x47, 0x0a, 0x0f, 0x61, 0x64, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x52, 0x0f, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x49, 0x74, 0x65, + 0x6d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x3a, 0x0a, 0x20, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, + 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x73, 0x54, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x22, 0x6c, 0x0a, 0x21, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x10, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x73, + 0x0a, 0x1f, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x62, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x22, 0x5c, 0x0a, 0x20, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, + 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x22, 0x71, 0x0a, 0x1d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, + 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x62, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x32, 0xbc, 0x02, 0x0a, 0x10, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x58, 0x0a, 0x09, 0x41, 0x70, 0x70, + 0x6c, 0x69, 0x65, 0x73, 0x54, 0x6f, 0x12, 0x24, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x76, + 0x32, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x12, + 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x23, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, + 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, + 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x21, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x42, 0x49, 0x5a, 0x47, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x61, 0x6e, 0x7a, 0x75, 0x2f, 0x76, + 0x65, 0x6c, 0x65, 0x72, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x69, 0x74, 0x65, 0x6d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_backupitemaction_v2_BackupItemAction_proto_rawDescOnce sync.Once + file_backupitemaction_v2_BackupItemAction_proto_rawDescData = file_backupitemaction_v2_BackupItemAction_proto_rawDesc +) + +func file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP() []byte { + file_backupitemaction_v2_BackupItemAction_proto_rawDescOnce.Do(func() { + file_backupitemaction_v2_BackupItemAction_proto_rawDescData = protoimpl.X.CompressGZIP(file_backupitemaction_v2_BackupItemAction_proto_rawDescData) + }) + return file_backupitemaction_v2_BackupItemAction_proto_rawDescData +} + +var file_backupitemaction_v2_BackupItemAction_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_backupitemaction_v2_BackupItemAction_proto_goTypes = []interface{}{ + (*ExecuteRequest)(nil), // 0: v2.ExecuteRequest + (*ExecuteResponse)(nil), // 1: v2.ExecuteResponse + (*BackupItemActionAppliesToRequest)(nil), // 2: v2.BackupItemActionAppliesToRequest + (*BackupItemActionAppliesToResponse)(nil), // 3: v2.BackupItemActionAppliesToResponse + (*BackupItemActionProgressRequest)(nil), // 4: v2.BackupItemActionProgressRequest + (*BackupItemActionProgressResponse)(nil), // 5: v2.BackupItemActionProgressResponse + (*BackupItemActionCancelRequest)(nil), // 6: v2.BackupItemActionCancelRequest + (*generated.ResourceIdentifier)(nil), // 7: generated.ResourceIdentifier + (*generated.ResourceSelector)(nil), // 8: generated.ResourceSelector + (*generated.OperationProgress)(nil), // 9: generated.OperationProgress + (*emptypb.Empty)(nil), // 10: google.protobuf.Empty +} +var file_backupitemaction_v2_BackupItemAction_proto_depIdxs = []int32{ + 7, // 0: v2.ExecuteResponse.additionalItems:type_name -> generated.ResourceIdentifier + 8, // 1: v2.BackupItemActionAppliesToResponse.ResourceSelector:type_name -> generated.ResourceSelector + 9, // 2: v2.BackupItemActionProgressResponse.progress:type_name -> generated.OperationProgress + 2, // 3: v2.BackupItemAction.AppliesTo:input_type -> v2.BackupItemActionAppliesToRequest + 0, // 4: v2.BackupItemAction.Execute:input_type -> v2.ExecuteRequest + 4, // 5: v2.BackupItemAction.Progress:input_type -> v2.BackupItemActionProgressRequest + 6, // 6: v2.BackupItemAction.Cancel:input_type -> v2.BackupItemActionCancelRequest + 3, // 7: v2.BackupItemAction.AppliesTo:output_type -> v2.BackupItemActionAppliesToResponse + 1, // 8: v2.BackupItemAction.Execute:output_type -> v2.ExecuteResponse + 5, // 9: v2.BackupItemAction.Progress:output_type -> v2.BackupItemActionProgressResponse + 10, // 10: v2.BackupItemAction.Cancel:output_type -> google.protobuf.Empty + 7, // [7:11] is the sub-list for method output_type + 3, // [3:7] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_backupitemaction_v2_BackupItemAction_proto_init() } +func file_backupitemaction_v2_BackupItemAction_proto_init() { + if File_backupitemaction_v2_BackupItemAction_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionAppliesToRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionAppliesToResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionProgressRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionProgressResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionCancelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_backupitemaction_v2_BackupItemAction_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_backupitemaction_v2_BackupItemAction_proto_goTypes, + DependencyIndexes: file_backupitemaction_v2_BackupItemAction_proto_depIdxs, + MessageInfos: file_backupitemaction_v2_BackupItemAction_proto_msgTypes, + }.Build() + File_backupitemaction_v2_BackupItemAction_proto = out.File + file_backupitemaction_v2_BackupItemAction_proto_rawDesc = nil + file_backupitemaction_v2_BackupItemAction_proto_goTypes = nil + file_backupitemaction_v2_BackupItemAction_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// BackupItemActionClient is the client API for BackupItemAction service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type BackupItemActionClient interface { + AppliesTo(ctx context.Context, in *BackupItemActionAppliesToRequest, opts ...grpc.CallOption) (*BackupItemActionAppliesToResponse, error) + Execute(ctx context.Context, in *ExecuteRequest, opts ...grpc.CallOption) (*ExecuteResponse, error) + Progress(ctx context.Context, in *BackupItemActionProgressRequest, opts ...grpc.CallOption) (*BackupItemActionProgressResponse, error) + Cancel(ctx context.Context, in *BackupItemActionCancelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type backupItemActionClient struct { + cc grpc.ClientConnInterface +} + +func NewBackupItemActionClient(cc grpc.ClientConnInterface) BackupItemActionClient { + return &backupItemActionClient{cc} +} + +func (c *backupItemActionClient) AppliesTo(ctx context.Context, in *BackupItemActionAppliesToRequest, opts ...grpc.CallOption) (*BackupItemActionAppliesToResponse, error) { + out := new(BackupItemActionAppliesToResponse) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/AppliesTo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *backupItemActionClient) Execute(ctx context.Context, in *ExecuteRequest, opts ...grpc.CallOption) (*ExecuteResponse, error) { + out := new(ExecuteResponse) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/Execute", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *backupItemActionClient) Progress(ctx context.Context, in *BackupItemActionProgressRequest, opts ...grpc.CallOption) (*BackupItemActionProgressResponse, error) { + out := new(BackupItemActionProgressResponse) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/Progress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *backupItemActionClient) Cancel(ctx context.Context, in *BackupItemActionCancelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/Cancel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// BackupItemActionServer is the server API for BackupItemAction service. +type BackupItemActionServer interface { + AppliesTo(context.Context, *BackupItemActionAppliesToRequest) (*BackupItemActionAppliesToResponse, error) + Execute(context.Context, *ExecuteRequest) (*ExecuteResponse, error) + Progress(context.Context, *BackupItemActionProgressRequest) (*BackupItemActionProgressResponse, error) + Cancel(context.Context, *BackupItemActionCancelRequest) (*emptypb.Empty, error) +} + +// UnimplementedBackupItemActionServer can be embedded to have forward compatible implementations. +type UnimplementedBackupItemActionServer struct { +} + +func (*UnimplementedBackupItemActionServer) AppliesTo(context.Context, *BackupItemActionAppliesToRequest) (*BackupItemActionAppliesToResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AppliesTo not implemented") +} +func (*UnimplementedBackupItemActionServer) Execute(context.Context, *ExecuteRequest) (*ExecuteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Execute not implemented") +} +func (*UnimplementedBackupItemActionServer) Progress(context.Context, *BackupItemActionProgressRequest) (*BackupItemActionProgressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Progress not implemented") +} +func (*UnimplementedBackupItemActionServer) Cancel(context.Context, *BackupItemActionCancelRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Cancel not implemented") +} + +func RegisterBackupItemActionServer(s *grpc.Server, srv BackupItemActionServer) { + s.RegisterService(&_BackupItemAction_serviceDesc, srv) +} + +func _BackupItemAction_AppliesTo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackupItemActionAppliesToRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).AppliesTo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/AppliesTo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).AppliesTo(ctx, req.(*BackupItemActionAppliesToRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BackupItemAction_Execute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExecuteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).Execute(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/Execute", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).Execute(ctx, req.(*ExecuteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BackupItemAction_Progress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackupItemActionProgressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).Progress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/Progress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).Progress(ctx, req.(*BackupItemActionProgressRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BackupItemAction_Cancel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackupItemActionCancelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).Cancel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/Cancel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).Cancel(ctx, req.(*BackupItemActionCancelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _BackupItemAction_serviceDesc = grpc.ServiceDesc{ + ServiceName: "v2.BackupItemAction", + HandlerType: (*BackupItemActionServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AppliesTo", + Handler: _BackupItemAction_AppliesTo_Handler, + }, + { + MethodName: "Execute", + Handler: _BackupItemAction_Execute_Handler, + }, + { + MethodName: "Progress", + Handler: _BackupItemAction_Progress_Handler, + }, + { + MethodName: "Cancel", + Handler: _BackupItemAction_Cancel_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "backupitemaction/v2/BackupItemAction.proto", +} diff --git a/pkg/plugin/mocks/manager.go b/pkg/plugin/mocks/manager.go index c99e1bf28..5031729a1 100644 --- a/pkg/plugin/mocks/manager.go +++ b/pkg/plugin/mocks/manager.go @@ -10,6 +10,8 @@ import ( v1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + v2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" + velero "github.com/vmware-tanzu/velero/pkg/plugin/velero" volumesnapshotterv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" @@ -48,6 +50,29 @@ func (_m *Manager) GetBackupItemAction(name string) (v1.BackupItemAction, error) return r0, r1 } +// GetBackupItemActionV2 provides a mock function with given fields: name +func (_m *Manager) GetBackupItemActionV2(name string) (v2.BackupItemAction, error) { + ret := _m.Called(name) + + var r0 v2.BackupItemAction + if rf, ok := ret.Get(0).(func(string) v2.BackupItemAction); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(v2.BackupItemAction) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetBackupItemActions provides a mock function with given fields: func (_m *Manager) GetBackupItemActions() ([]v1.BackupItemAction, error) { ret := _m.Called() @@ -71,6 +96,29 @@ func (_m *Manager) GetBackupItemActions() ([]v1.BackupItemAction, error) { return r0, r1 } +// GetBackupItemActionsV2 provides a mock function with given fields: +func (_m *Manager) GetBackupItemActionsV2() ([]v2.BackupItemAction, error) { + ret := _m.Called() + + var r0 []v2.BackupItemAction + if rf, ok := ret.Get(0).(func() []v2.BackupItemAction); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]v2.BackupItemAction) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetDeleteItemAction provides a mock function with given fields: name func (_m *Manager) GetDeleteItemAction(name string) (velero.DeleteItemAction, error) { ret := _m.Called(name) diff --git a/pkg/plugin/proto/Shared.proto b/pkg/plugin/proto/Shared.proto index c1c298e3a..dd12a7ee7 100644 --- a/pkg/plugin/proto/Shared.proto +++ b/pkg/plugin/proto/Shared.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package generated; option go_package = "github.com/vmware-tanzu/velero/pkg/plugin/generated"; +import "google/protobuf/timestamp.proto"; + message Empty {} message Stack { @@ -27,4 +29,15 @@ message ResourceSelector { repeated string includedResources = 3; repeated string excludedResources = 4; string selector = 5; -} \ No newline at end of file +} + +message OperationProgress { + bool completed = 1; + string err = 2; + int64 nCompleted = 3; + int64 nTotal = 4; + string operationUnits = 5; + string description = 6; + google.protobuf.Timestamp started = 7; + google.protobuf.Timestamp updated = 8; +} diff --git a/pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto b/pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto new file mode 100644 index 000000000..5f7bfe7d3 --- /dev/null +++ b/pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; +package v2; +option go_package = "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2"; + +import "Shared.proto"; +import "google/protobuf/empty.proto"; + + +message ExecuteRequest { + string plugin = 1; + bytes item = 2; + bytes backup = 3; +} + +message ExecuteResponse { + bytes item = 1; + repeated generated.ResourceIdentifier additionalItems = 2; + string operationID = 3; +} + +service BackupItemAction { + rpc AppliesTo(BackupItemActionAppliesToRequest) returns (BackupItemActionAppliesToResponse); + rpc Execute(ExecuteRequest) returns (ExecuteResponse); + rpc Progress(BackupItemActionProgressRequest) returns (BackupItemActionProgressResponse); + rpc Cancel(BackupItemActionCancelRequest) returns (google.protobuf.Empty); +} + +message BackupItemActionAppliesToRequest { + string plugin = 1; +} + +message BackupItemActionAppliesToResponse { + generated.ResourceSelector ResourceSelector = 1; +} + +message BackupItemActionProgressRequest { + string plugin = 1; + string operationID = 2; + bytes backup = 3; +} + +message BackupItemActionProgressResponse { + generated.OperationProgress progress = 1; +} + +message BackupItemActionCancelRequest { + string plugin = 1; + string operationID = 2; + bytes backup = 3; +} diff --git a/pkg/plugin/velero/backupitemaction/v2/backup_item_action.go b/pkg/plugin/velero/backupitemaction/v2/backup_item_action.go new file mode 100644 index 000000000..a4a70234e --- /dev/null +++ b/pkg/plugin/velero/backupitemaction/v2/backup_item_action.go @@ -0,0 +1,63 @@ +/* +Copyright 2017 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + + "github.com/pkg/errors" + + api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" +) + +// BackupItemAction is an actor that performs an operation on an individual item being backed up. +type BackupItemAction interface { + // AppliesTo returns information about which resources this action should be invoked for. + // A BackupItemAction's Execute function will only be invoked on items that match the returned + // selector. A zero-valued ResourceSelector matches all resources. + AppliesTo() (velero.ResourceSelector, error) + + // Execute allows the BackupItemAction to perform arbitrary logic with the item being backed up, + // including mutating the item itself prior to backup. The item (unmodified or modified) + // should be returned, along with an optional slice of ResourceIdentifiers specifying + // additional related items that should be backed up. + Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) + + // Progress allows the BackupItemAction to report on progress of an asynchronous action. + // For the passed-in operation, the plugin will return an OperationProgress struct, indicating + // whether the operation has completed, whether there were any errors, a plugin-specific + // indication of how much of the operation is done (items completed out of items-to-complete), + // and started/updated timestamps + Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) + + // Cancel allows the BackupItemAction to cancel an asynchronous action (if possible). + // Velero will call this if the wait timeout for asynchronous actions has been reached. + // If operation cancel is not supported, then the plugin just needs to return. No error + // return is expected in this case, since cancellation is optional here. + Cancel(operationID string, backup *api.Backup) error +} + +func AsyncOperationsNotSupportedError() error { + return errors.New("Plugin does not support asynchronous operations") +} + +func InvalidOperationIDError(operationID string) error { + return errors.New(fmt.Sprintf("Operation ID %v is invalid.", operationID)) +} diff --git a/pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go b/pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go new file mode 100644 index 000000000..63be81d4d --- /dev/null +++ b/pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go @@ -0,0 +1,127 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package v2 + +import ( + mock "github.com/stretchr/testify/mock" + runtime "k8s.io/apimachinery/pkg/runtime" + + v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + + velero "github.com/vmware-tanzu/velero/pkg/plugin/velero" +) + +// BackupItemAction is an autogenerated mock type for the BackupItemAction type +type BackupItemAction struct { + mock.Mock +} + +// AppliesTo provides a mock function with given fields: +func (_m *BackupItemAction) AppliesTo() (velero.ResourceSelector, error) { + ret := _m.Called() + + var r0 velero.ResourceSelector + if rf, ok := ret.Get(0).(func() velero.ResourceSelector); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(velero.ResourceSelector) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Cancel provides a mock function with given fields: operationID, backup +func (_m *BackupItemAction) Cancel(operationID string, backup *v1.Backup) error { + ret := _m.Called(operationID, backup) + + var r0 error + if rf, ok := ret.Get(0).(func(string, *v1.Backup) error); ok { + r0 = rf(operationID, backup) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Execute provides a mock function with given fields: item, backup +func (_m *BackupItemAction) Execute(item runtime.Unstructured, backup *v1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + ret := _m.Called(item, backup) + + var r0 runtime.Unstructured + if rf, ok := ret.Get(0).(func(runtime.Unstructured, *v1.Backup) runtime.Unstructured); ok { + r0 = rf(item, backup) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(runtime.Unstructured) + } + } + + var r1 []velero.ResourceIdentifier + if rf, ok := ret.Get(1).(func(runtime.Unstructured, *v1.Backup) []velero.ResourceIdentifier); ok { + r1 = rf(item, backup) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]velero.ResourceIdentifier) + } + } + + var r2 string + if rf, ok := ret.Get(2).(func(runtime.Unstructured, *v1.Backup) string); ok { + r2 = rf(item, backup) + } else { + r2 = ret.Get(2).(string) + } + + var r3 error + if rf, ok := ret.Get(3).(func(runtime.Unstructured, *v1.Backup) error); ok { + r3 = rf(item, backup) + } else { + r3 = ret.Error(3) + } + + return r0, r1, r2, r3 +} + +// Progress provides a mock function with given fields: operationID, backup +func (_m *BackupItemAction) Progress(operationID string, backup *v1.Backup) (velero.OperationProgress, error) { + ret := _m.Called(operationID, backup) + + var r0 velero.OperationProgress + if rf, ok := ret.Get(0).(func(string, *v1.Backup) velero.OperationProgress); ok { + r0 = rf(operationID, backup) + } else { + r0 = ret.Get(0).(velero.OperationProgress) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, *v1.Backup) error); ok { + r1 = rf(operationID, backup) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/plugin/velero/shared.go b/pkg/plugin/velero/shared.go index a0d3d884c..6316a1e0b 100644 --- a/pkg/plugin/velero/shared.go +++ b/pkg/plugin/velero/shared.go @@ -20,7 +20,11 @@ limitations under the License. // plugins of any type can be implemented. package velero -import "k8s.io/apimachinery/pkg/runtime/schema" +import ( + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" +) // ResourceSelector is a collection of included/excluded namespaces, // included/excluded resources, and a label-selector that can be used @@ -63,3 +67,25 @@ type ResourceIdentifier struct { Namespace string Name string } + +// OperationProgress describes progress of an asynchronous plugin operation. +type OperationProgress struct { + // True when the operation has completed, either successfully or with a failure + Completed bool + // Set when the operation has failed + Err string + // Quantity completed so far and the total quantity associated with the operation + // in OperationUnits. For data mover and volume snapshotter use cases, this will + // usually be in bytes. On successful completion, NCompleted and NTotal should be + // the same + NCompleted, NTotal int64 + // Units represented by NCompleted and NTotal -- for data mover and item + // snapshotters, this will usually be bytes. + OperationUnits string + // Optional description of operation progress (i.e. "Current phase: Running") + Description string + // When the operation was started and when the last update was seen. Not all + // systems retain when the upload was begun, return Time 0 (time.Unix(0, 0)) + // if unknown. + Started, Updated time.Time +} From 700d9dcc36fa5084bd9bb30673059dd5a0c8a3ee Mon Sep 17 00:00:00 2001 From: Daniel Jiang Date: Tue, 10 Jan 2023 17:25:19 +0800 Subject: [PATCH 18/20] Update the default reviewers (#5751) Update the group members of "maintainers" and "tech-writer" to reflect the change in the team. As for the group "tech-writer" I just selected a few members from maintains team who has been working on velero for a relatively longer time. Signed-off-by: Daniel Jiang --- .github/auto-assignees.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/auto-assignees.yml b/.github/auto-assignees.yml index 8f5a75a57..d98cefe57 100644 --- a/.github/auto-assignees.yml +++ b/.github/auto-assignees.yml @@ -9,7 +9,6 @@ reviewers: groups: maintainers: - - dsu-igeek - sseago - reasonerjt - ywk253100 @@ -19,7 +18,10 @@ reviewers: - Lyndon-Li tech-writer: - - a-mccarthy + - sseago + - reasonerjt + - ywk253100 + - Lyndon-Li files: 'site/**': From 7d916485ec799209f19e1d65b48e29158ac213a6 Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Tue, 13 Dec 2022 18:09:11 +0800 Subject: [PATCH 19/20] Add Restic builder in Dockerfile. Signed-off-by: Xun Jiang --- Dockerfile | 41 ++++++++++++++++---------- changelogs/unreleased/5685-blackpiglet | 1 + hack/build-restic.sh | 1 + hack/modify_acces_denied_code.txt | 13 ++++++++ 4 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 changelogs/unreleased/5685-blackpiglet create mode 100644 hack/modify_acces_denied_code.txt diff --git a/Dockerfile b/Dockerfile index 757b03c07..e2987ecc4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,48 +11,59 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -FROM --platform=$BUILDPLATFORM golang:1.18 as builder-env + +# Setup build environment +ARG GOLANG_VERSION=1.18.8 +FROM --platform=$BUILDPLATFORM golang:${GOLANG_VERSION} as builder-env ARG GOPROXY +ARG BIN ARG PKG ARG VERSION +ARG REGISTRY ARG GIT_SHA ARG GIT_TREE_STATE -ARG REGISTRY +ARG RESTIC_VERSION +ARG TARGETOS +ARG TARGETARCH +ARG TARGETVARIANT ENV CGO_ENABLED=0 \ GO111MODULE=on \ GOPROXY=${GOPROXY} \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ + GOARM=${TARGETVARIANT} \ LDFLAGS="-X ${PKG}/pkg/buildinfo.Version=${VERSION} -X ${PKG}/pkg/buildinfo.GitSHA=${GIT_SHA} -X ${PKG}/pkg/buildinfo.GitTreeState=${GIT_TREE_STATE} -X ${PKG}/pkg/buildinfo.ImageRegistry=${REGISTRY}" WORKDIR /go/src/github.com/vmware-tanzu/velero COPY . /go/src/github.com/vmware-tanzu/velero +# Velero binary build section +ARG GOLANG_VERSION=1.18.8 FROM --platform=$BUILDPLATFORM builder-env as builder -ARG TARGETOS -ARG TARGETARCH -ARG TARGETVARIANT -ARG PKG -ARG BIN -ARG RESTIC_VERSION - -ENV GOOS=${TARGETOS} \ - GOARCH=${TARGETARCH} \ - GOARM=${TARGETVARIANT} - RUN mkdir -p /output/usr/bin && \ export GOARM=$( echo "${GOARM}" | cut -c2-) && \ - bash ./hack/build-restic.sh && \ go build -o /output/${BIN} \ -ldflags "${LDFLAGS}" ${PKG}/cmd/${BIN} -FROM gcr.io/distroless/base-debian11:nonroot +# Restic binary build section +ARG GOLANG_VERSION=1.19.4-bullseye +FROM --platform=$BUILDPLATFORM builder-env as restic-builder + +RUN mkdir -p /output/usr/bin && \ + bash /go/src/github.com/vmware-tanzu/velero/hack/build-restic.sh + +# Velero image packing section +FROM gcr.io/distroless/base-debian11@sha256:99133cb0878bb1f84d1753957c6fd4b84f006f2798535de22ebf7ba170bbf434 LABEL maintainer="Nolan Brubaker " COPY --from=builder /output / +COPY --from=restic-builder /output / + USER nonroot:nonroot diff --git a/changelogs/unreleased/5685-blackpiglet b/changelogs/unreleased/5685-blackpiglet new file mode 100644 index 000000000..13df2faf7 --- /dev/null +++ b/changelogs/unreleased/5685-blackpiglet @@ -0,0 +1 @@ +Add Restic builder in Dockerfile, and keep the used built Golang image version in accordance with upstream Restic. \ No newline at end of file diff --git a/hack/build-restic.sh b/hack/build-restic.sh index 5859414fd..c6a19c02c 100755 --- a/hack/build-restic.sh +++ b/hack/build-restic.sh @@ -50,6 +50,7 @@ fi mkdir ${build_path}/restic git clone -b v${RESTIC_VERSION} https://github.com/restic/restic.git ${build_path}/restic pushd ${build_path}/restic +git apply /go/src/github.com/vmware-tanzu/velero/hack/modify_acces_denied_code.txt go run build.go --goos "${GOOS}" --goarch "${GOARCH}" --goarm "${GOARM}" -o ${restic_bin} chmod +x ${restic_bin} popd diff --git a/hack/modify_acces_denied_code.txt b/hack/modify_acces_denied_code.txt new file mode 100644 index 000000000..5c2577b69 --- /dev/null +++ b/hack/modify_acces_denied_code.txt @@ -0,0 +1,13 @@ +diff --git a/internal/backend/s3/s3.go b/internal/backend/s3/s3.go +index 0b3816c06..eec10f9c7 100644 +--- a/internal/backend/s3/s3.go ++++ b/internal/backend/s3/s3.go +@@ -164,7 +164,7 @@ func isAccessDenied(err error) bool { + debug.Log("isAccessDenied(%T, %#v)", err, err) + + var e minio.ErrorResponse +- return errors.As(err, &e) && e.Code == "Access Denied" ++ return errors.As(err, &e) && e.Code == "AccessDenied" + } + + // IsNotExist returns true if the error is caused by a not existing file. From 01c4e9b0c9f52c7c69af8062a11469bf7515c34d Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Thu, 12 Jan 2023 15:41:48 +0800 Subject: [PATCH 20/20] Fix Dockerfile issue. Signed-off-by: Xun Jiang --- Dockerfile | 30 ++++++++++++------- .../{5685-blackpiglet => 5764-blackpiglet} | 0 2 files changed, 19 insertions(+), 11 deletions(-) rename changelogs/unreleased/{5685-blackpiglet => 5764-blackpiglet} (100%) diff --git a/Dockerfile b/Dockerfile index e2987ecc4..598fbf38a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,9 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Setup build environment -ARG GOLANG_VERSION=1.18.8 -FROM --platform=$BUILDPLATFORM golang:${GOLANG_VERSION} as builder-env +# Velero binary build section +FROM --platform=$BUILDPLATFORM golang:1.18.8 as velero-builder ARG GOPROXY ARG BIN @@ -23,7 +22,6 @@ ARG VERSION ARG REGISTRY ARG GIT_SHA ARG GIT_TREE_STATE -ARG RESTIC_VERSION ARG TARGETOS ARG TARGETARCH ARG TARGETVARIANT @@ -40,18 +38,28 @@ WORKDIR /go/src/github.com/vmware-tanzu/velero COPY . /go/src/github.com/vmware-tanzu/velero -# Velero binary build section -ARG GOLANG_VERSION=1.18.8 -FROM --platform=$BUILDPLATFORM builder-env as builder - RUN mkdir -p /output/usr/bin && \ export GOARM=$( echo "${GOARM}" | cut -c2-) && \ go build -o /output/${BIN} \ -ldflags "${LDFLAGS}" ${PKG}/cmd/${BIN} # Restic binary build section -ARG GOLANG_VERSION=1.19.4-bullseye -FROM --platform=$BUILDPLATFORM builder-env as restic-builder +FROM --platform=$BUILDPLATFORM golang:1.19.4-bullseye as restic-builder + +ARG BIN +ARG TARGETOS +ARG TARGETARCH +ARG TARGETVARIANT +ARG RESTIC_VERSION + +env CGO_ENABLED=0 \ + GO111MODULE=on \ + GOPROXY=${GOPROXY} \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ + GOARM=${TARGETVARIANT} + +COPY . /go/src/github.com/vmware-tanzu/velero RUN mkdir -p /output/usr/bin && \ bash /go/src/github.com/vmware-tanzu/velero/hack/build-restic.sh @@ -61,7 +69,7 @@ FROM gcr.io/distroless/base-debian11@sha256:99133cb0878bb1f84d1753957c6fd4b84f00 LABEL maintainer="Nolan Brubaker " -COPY --from=builder /output / +COPY --from=velero-builder /output / COPY --from=restic-builder /output / diff --git a/changelogs/unreleased/5685-blackpiglet b/changelogs/unreleased/5764-blackpiglet similarity index 100% rename from changelogs/unreleased/5685-blackpiglet rename to changelogs/unreleased/5764-blackpiglet