diff --git a/changelogs/unreleased/7512-qiuming-best b/changelogs/unreleased/7512-qiuming-best new file mode 100644 index 000000000..0cbc1762d --- /dev/null +++ b/changelogs/unreleased/7512-qiuming-best @@ -0,0 +1,2 @@ +Make parallel restore configurable + diff --git a/config/crd/v1/bases/velero.io_restores.yaml b/config/crd/v1/bases/velero.io_restores.yaml index 8e53be642..138f88445 100644 --- a/config/crd/v1/bases/velero.io_restores.yaml +++ b/config/crd/v1/bases/velero.io_restores.yaml @@ -422,6 +422,10 @@ spec: description: UploaderConfig specifies the configuration for the restore. nullable: true properties: + parallelFilesDownload: + description: ParallelFilesDownload is the concurrency number setting + for restore. + type: integer writeSparseFiles: description: WriteSparseFiles is a flag to indicate whether write files sparsely or not. diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index 212837b73..2ea591be9 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -36,7 +36,7 @@ var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VM\x93\xdb6\f\xbd\xfbW`\xa6\x87\xb43\x91\x9c\xb4\x97\x8eo\xad\x93\xc3N\xd24\xb3N\xf7NS\xb0\xc4.E\xb2\x04\xe8\xcd\xf6\xd7w@J\xfe\x94\xbd\xdeCu\x13\t\x82\x8f\x0f\x0f\x8f\xac\xaaj\xa6\x82y\xc0Hƻ\x05\xa8`\xf0;\xa3\x93?\xaa\x1f\x7f\xa5\xda\xf8\xf9\xf6\xfd\xecѸf\x01\xcbD\xec\xfb{$\x9f\xa2\xc6\x0f\xb81ΰ\xf1n\xd6#\xabF\xb1Z\xcc\x00\x94s\x9e\x95\f\x93\xfc\x02h\xef8zk1V-\xba\xfa1\xadq\x9d\x8cm0\xe6\xe4\xe3\xd6\xdbw\xf5\xfb\x9f\xebw3\x00\xa7z\\@㟜\xf5\xaa\x89\xf8OBb\xaa\xb7h1\xfa\xda\xf8\x19\x05Ԓ\xbb\x8d>\x85\x05\xec'\xca\xdaa߂\xf9Ð澤\xc93\xd6\x10\x7f\x9a\x9a\xfdl\x86\x88`ST\xf6\x1cD\x9e$\xe3\xdadU<\x9b\x9e\x01\x90\xf6\x01\x17\xf0E`\x04\xa5\xb1\x99\x01\fG̰\xaa\xe1t\xdb\xf7%\x95\xee\xb0W\x05/\x80\x0f\xe8~\xfbz\xf7\xf0\xcb\xeah\x18\xa0A\xd2\xd1\x04\xceD\x9d`\x06C\xa0`@\x00\xecw\xa0@9P\x91\xcdFi\x86M\xf4=\xac\x95~La\x97\x15\xc0\xaf\xffF\xcd@\xec\xa3j\xf1-P\xd2\x1d(\xc9WB\xc1\xfa\x166\xc6b\xbd[\x14\xa2\x0f\x18ٌ,\x97\xef@C\a\xa3'\xc0\xdf\xc8\xd9J\x144\"\x1e$\xe0\x0eG~\xb0\x19\xe8\x00\xbf\x01\xee\fA\xc4\x10\x91\xd0\x159\x1d%\x06\tRn8A\r+\x8c\x92\x06\xa8\xf3\xc96\xa2\xb9-F\x86\x88ڷ\xce\xfc\xbb\xcbM\u0090lj\x15\x8fr\xd8\x7f\xc61F\xa7,l\x95M\xf8\x16\x94k\xa0W\xcf\x101\xf3\x94\xdcA\xbe\x1cB5\xfc\xe1#\x82q\x1b\xbf\x80\x8e9\xd0b>o\r\x8f\xbd\xa3}\xdf'g\xf8y\x9e\xdb\xc0\xac\x13\xfbH\xf3\x06\xb7h\xe7d\xdaJE\xdd\x19F\xcd)\xe2\\\x05Se\xe8.\xf7O\xdd7?ġ\xdb\xe8\xcd\x11V~\x16\x99\x11G\xe3ڃ\x89\xac\xf9+\x15\x10\xd5\x17\xc1\x94\xa5\xe5\x14{\xa2eHع\xff\xb8\xfa\x06\xe3ֹ\x18\xa7\xec\x17\xe5\xec\x16Ҿ\x04B\x98q\x1b\x8c\xa5\x88Yy\x92\x13]\x13\xbcq\x9c\x7f\xb45\xe8N駴\xee\r\xd3(f\xa9U\r\xcbl(\xb0FH\xa1Q\x8cM\rw\x0e\x96\xaaG\xbbT\x84\xff{\x01\x84i\xaa\x84\xd8\xdbJp腧\xc1\x85\xb5\x83\x89\xd1\xc9.\xd4\xeb\xa4\xd5W\x01\xb5TO\b\x94\x95fctn\r\xd8\xf8\bj\xdf\xf9\x03\x81\xf5Q\xe6\xe9\xce\xcd\xe0Tl\x91OGO\xb0|\xcbA\xb2\xfdS\xa7\x8e\x8d\xe6G\xac\xdbZ\xbc\x82\x06 \xc5=~\xaa\xcf2^\xc6\x00\x93\xea\x9dD2\x8aXh\x10^\xc5\nĤ\x0e1\x9do-\x1f\xba\xd4OoP\xc1\xef\x19\xf3g\xdf^\x9d_z\xc7\"\xf7\xabA\x0fަ\x1eWN\x05\xea\xfc\v\xb1w\x8c\xfd\x9f\x01c\xb91\xaf\x86\x8e\x17\xef\ue5ba\x12\x98\xec\xc5}\xefQ\xfc\x1e/\x9ft\b\xb8)\xcb\r\x98\x86ț\x0e\xba\\ݽ\x86\xc2\v\xe1\xaf(ҝ\xdb\xf8\xe9\xb8\v\xed=~\xf9\x1a\x7fY\xab\xf2\x10\x18\xb5*K\xca݆\xf0)\xad1:d\xa4\xbd\xcd>\x19\xee&3\x02}S\xe6\x84H\xf9\x01\xa4\xd5\xe9\xd3K\xbe5B\x83\x16\x19\x1bX?\x97\x1b\xe9\x99\x18\xfbs\xdc\x1b\x1f{\xc5\v\x90˻b3!#\x97\xacUk\x8b\v\xe0\x98.\xa9l\xf2\xe0\xa1S4цGg\xfe*1S\xc2\xd85\xe3Ue\xc0\xc5{\xa3\x82/\xf841\xfa5z\x8dDx\xdeF\x17O2\xd9\x04g\x83$/\xac急\xe1\xe1>\x8c\xfc\x17\x00\x00\xff\xff\t\x15i;\xcd\r\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\x1b7\xf2\xbf\xebSt9\a\xfdSe\x0ec\xff\xb7\xb6\xb6x\xb3\xe5͖v\x13Yeʾ\xa4r\x00\a=3\x88f\x00\x04\xc0P\xe2\xa6\xf2ݷ\x1a\x0fr\x1e )\xa9\xd6\u07b9H\x04\x1a\x8d\x1f\x1a\xfd\xc6b\xb1\xb8`Z|Ac\x85\x92+`Z\xe0\xa3CI\xbflq\xff7[\b\xb5ܾ\xb9\xb8\x17\x92\xafષNu\x9fЪޔ\xf8\x01+!\x85\x13J^t\xe8\x18g\x8e\xad.\x00\x98\x94\xca1\x1a\xb6\xf4\x13\xa0T\xd2\x19նh\x165\xca\xe2\xbe\xdf\xe0\xa6\x17-G㙧\xad\xb7?\x14o\xde\x16?\\\x00H\xd6\xe1\n\xb4\xe2[\xd5\xf6\x1dnXy\xdfk[l\xb1E\xa3\n\xa1.\xacƒx\xd7F\xf5z\x05\x87\x89\xb06\xee\x1b0\xdf*\xfeųy\xef\xd9\xf8\x99VX\xf7\xaf\xdc\xecO\xc2:O\xa1\xdbްv\x0e\xc2OZ!\xeb\xbeef6}\x01`K\xa5q\x057\x04C\xb3\x12\xf9\x05@<\xa2\x87\xb5\x00ƹ\x17\x1ako\x8d\x90\x0e\xcd\x15qH\xc2Z\x00G[\x1a\xa1\x9d\x17ʭ\xe2\x10\x00B@\b\xd61\xd7[\xb0}\xd9\x00\xb3p\x83\x0f\xcbkykTm\xd0\x06x\x00\xbfY%o\x99kVP\x04\xf2B7\xccb\x9c\r\xe2]\xfb\x898\xe4v\x04\xda:#d\x9d\x83q':\x84\x87\x06%\xb8FX\b\xa7\x85\af\t\x8eq\xfe\x94\xf9\x8d\xfd<-\xb7\x8euz\x84\xe0\xca ;,\r\x108s\x98\x03\xb0\x97'\xa8\n\\\x83$y\xafXLH!k?\x14n\x02\x9c\x82\rz\x88ȡ\xd7\x19d\x1a\xcbB+^\xc8\xc4t\x04\xebf2zN6D\xff\xdfF5\x02t\xab\xf8\v\xa0\x98\x9d\xb8\x01\ng ,\xb0\xb84\x9c\xe2 \xe8\xe4\x8e>\xfd}}\aik\x7f\x19S\xe9{\xb9\x1f\x16\xda\xc3\x15\x90\xc0\x84\xacȬ\xe9\x12+\xa3:\xcf\x13%\xd7JH\xe7\x7f\x94\xad@9\x15\xbf\xed7\x9dpt\xef\xbf\xf7h\x1d\xddU\x01W>S \xf7\xd4k\xd2\\^\xc0\xb5\x84+\xd6a{\xc5,~\xf5\v I\xdb\x05\t\xf6iW0Lr\xa6\xc4Aj\x83\x89\x94\xa2\x1c\xb9\xafIޱ\xd6X\xd2\xed\x91\x00i\xa5\xa8D\xf4P\x952\xc0\xa6\xe4ňq\xdep\xe9\xcbz\xa7)\xd1\x04\xd9\xfbܚ\x84M\x0e|jr\x98\x81r\xc6\x14\xa0\x9dz\xd9\xfd\x1a\x83ZY\xe1\x94\xd9\x11\xe3\xe0`\x8b\x19\x87#\xd7@\x9fT\x1cϜ\xe3Fq\xcc\xc1\xa6\xa5\xe0\x1a\x16\xb4\x95\xf2+\xf2G\xbd\x94\xf3]\xe8S\xf2Y\xc0\xb4\xe2gp\xc5\x1d\x19\x18\xacР,19\xaeS\xc9C\x06\xd90\xac\xcf1\x1eW\n8\xe1ճ\x88\xdf\xdd^'O\x9e\x84\x18\xb1\xbb\xf9\xbeg\xe4C_%\xb0\xe5>Н\xdf\xfb\xf2\xba\n\x9by\x9f\xe6\x140\xd0\x02C\x1a\xb8\x0f\x12 \xa4u\xc88\xa8*ˑj\x12 \xc37\x18W\xbc\x0e\x1e,\xba\xcaCh!\xd9\x03#\xdf)8\xfcs\xfd\xf1f\xf9\x8f\x9c\xe8\xf7\xa7\x00V\x96h}\x16\xec\xb0C\xe9^\xef\x13s\x8eV\x18\xe4\x94fc\xd11)*\xb4\xae\x88{\xa0\xb1\xbf\xbc\xfd5/=\x80\x1f\x95\x01|d\x9dn\xf15\x88 \xf1\xbd[NJ#l\x10Ǟ#<\b\u05c8i0\xddK\x80\xd4+\x1e\xfb\xc1\x1fױ{\x04\x15\x8f\xdb#\xb4\xe2\x1eW\xf0ʧ5\a\x98\x7f\x90\xed\xfc\xf9\xea\b\xd7\xff\v\xa6\xfd\x8a\x88^\x05p\xfb8<4\xba\x03\xc8`yF\xd45\x1e\xb2\xaa\xe9\xe7\x83\n\xb9\xea\xefA\x19\x92\x80T\x03\x16\x9e1\xdd^p\x94\xc8g\xa0\x7fy\xfb\xebQ\xc4cy\x81\x90\x1c\x1f\xe1-\x88X\xdahſ/\xe0\xcek\xc7N:\xf6H;\x95\x8d\xb2xL\xb2J\xb6\xbb\x90\xe7n\x11\xac\xa2B\t\xdbv\x11\xf2 \x0e\x0flGRH\x17G\xfa\xc6@3\xe3Njk\xca~\xee>~\xf8\xb8\n\xc8H\xa1j\xef\x89)jV\x82\xb2\x19JcB,\xf6\xda8\v\xe6\xe9\xb3}P\x1f\xa7\xa0l\x98\xac1\x9c\x17\xa1\xea):\x16\x97/\xb1\xe3yJ\x92\xbeLj2u\x1c\xff\xb3\xe0\xfe\xc4\xc3\xf9\f\xfa\t\x87\x1bV\x19'\x0fw\xdfo\xd0Ht\xe8\xcf\xc7Ui\xe9h%jg\x97j\x8bf+\xf0a\xf9\xa0̽\x90\xf5\x82Ts\x11t\xc0.}\x99\xba\xfc\xce\xffy\xf1Y|E\xfb\xd4\x03\x8d*\xed\xafy*\xda\xc7._t\xa8\x94\xc3>=\x8e]\xaecf5]Kf\xf1Ј\xb2I\xc5I\xf4\xb1G\x8cIP&̃kfr\xf7\xd5U\x99\x04\xda\x1bB\xb4[\xc4^ڂIN\xff[a\x1d\x8d\xbfH\x82\xbdx\x92\xf9~\xbe\xfe\xf0m\x14\xbc\x17/\xb2\xd5#\tx\xf8\x1e\x17\aX\x8b\x8e\xe9E\xa0fNu\xa2\x9cPSVz\xcdI\xf0\x95@s&\x8d\xfb4\"N\x89f&\xbf\xdd\xd3<+\x8ft\xac\xce$n\xc3\xd6\xe1\xa9\xf4\ue93cƍ\x1bV[`\x06\x81A\xc74\xdd\xf3=\xee\x16!!\xd0LP4\xa7\x80\xbd\xef\x8a\x00Ӻ\x15\xd9\xc0\x1d\xc3~LY\xa3$\xa8,g\xb5=v\xf6쭥.\xd0\x1a\x1d\x95\xad\xdfD\x0e\x9f'{>Y&\xf9\xc4z$\x8a\xaa\x90\xbb\xaa\xbdoy\x96\xa9\x8e\x9f\xb4\xcfA\x1d\x11\x9f\x89B\xf11=\x17\x83֨\x99!K\xf7\xaf.W\xd3g\xc1\xd7`\x85o%Sf\x1aR\xd5\xd0\xe8\xb1\x14\x9c(\xb5R\x063.\x13\xe6ae\x14D\xc6\xf0\xbfe\xfc\xc8\xea\xc9l\xd0#\xe7\x03\xde\xf19b8\xd2o\xf6Om+\xf8\xe3ϋ\xff\x04\x00\x00\xff\xff{ŋW\xf4\"\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs#\xb7\x11\xbe\xf3Wt\xad\x0f\x8a\xabv\x86\xdeM*\x95\xe2mW\x8aSJl\xadj\xa9\u074b\xcb\apМ\x819\x03\xc0x\x90b\\\xfe\xef\xa9\x06\x06\xe4\xbcHJ\xaaȞˮ\x80F\xe3Ç~\xa1\x99eٌi\xf1\x15\x8d\x15J.\x80i\x81\x8f\x0e%\xfde\xf3\xcd?l.\xd4|\xfbn\xb6\x11\x92/\xe0\xda[\xa7\x9a\xcfh\x957\x05\xde\xe0ZHᄒ\xb3\x06\x1d\xe3̱\xc5\f\x80I\xa9\x1c\xa3aK\x7f\x02\x14J:\xa3\xea\x1aMV\xa2\xcc7~\x85+/j\x8e&(O[o\xbf\xcb߽Ͽ\x9b\x01H\xd6\xe0\x02\xb4\xe2[U\xfb\x06\rZ\xa7\f\xda|\x8b5\x1a\x95\v5\xb3\x1a\vR^\x1a\xe5\xf5\x02\x8e\x13qq\xbbq\x04}\xaf\xf8נ\xe7s\xd4\x13\xa6ja\xdd\x7f&\xa7\x7f\x10\xd6\x05\x11]{\xc3\xea\t\x1ca\xd6\nY\xfa\x9a\x99\xf1\xfc\f\xc0\x16J\xe3\x02\xee\b\x8af\x05\xf2\x19@{\xce\x00-\x03\xc6y`\x8e\xd5\xf7FH\x87\xe6\x9aT$\xc62\xe0h\v#\xb4\v\xcc\x1c\xf4\x80Z\x83\xab\x90\xb6\f\xac2!\x85,\xc3P\x84\x00N\xc1\n\xa1E\u00832\x80_\xac\x92\xf7\xccU\vȉ\xb8\\+\x9eˤ\xb3\x95\x89\x9c\xdf\rFݞ\xcea\x9d\x11\xb2<\x85\xec\xff\f\xaa\x87\xe7^\xf1'\"y\xa80\xc8$4^\u05caq4\xb4y\xc5$\xaf\x11\xc8@\xc1\x19&\xed\x1a\xcd\t\x14i\xd9\xc3^\xf7\x91|I\xfa:3\xcfa\xe79TD\xd9\xde\xf6_\xbbC\x97\xf6\xbdW\xbc]\x00\xadQ\x83u\xccy\v\xd6\x17\x150\vw\xb8\x9b\xdf\xca{\xa3J\x83\xd6N\xc0\b\u2e6e\x98\xed\xe3X\x86\x89\xd7űV\xa6an\x01B\xba\xbf\xff\xed4\xb6vQ\xee\x94c\xf5ǽC\xdbC\xfa0\x1c\x8eh\xc9\xd9\xca\xf6\xfa\xff\x14\xb8+\x82t\xa3d\x9f\u05cf\x83\xd1)\xb0\x1d\xa5)\xde\xe6\x85\xc1\x10j\x1fD\x83ֱF\xf7\xb4~(\xfb\xfa8sq No\xdf\xc5PVTذE+\xa94\xca\x0f\xf7\xb7_\xff\xba\xec\r\x03h\xa34\x1a'Rt\x8d_'ytF\xa1\xcf\xec\x15)\x8cR\xc0)k\xa0\x8dN\x11ǐ\xb7\x18\xa2\xb3\b\v\x06\xb5A\x8b2摞b !&A\xad~\xc1\xc2\xe5\xb0DCj\xc0V\xca\xd7!\x02m\xd180X\xa8R\x8a\xff\x1et[\xf2=ڴf\x0e\xdb\x10\x7f\xfcB\f\x96\xac\x86-\xab=\xbe\x05&94l\x0f\x06i\x17\xf0\xb2\xa3/\x88\xd8\x1c~$\v\x11r\xad\x16P9\xa7\xedb>/\x85KI\xb3PM\xe3\xa5p\xfby\xc8\x7fb\xe5\x9d2v\xceq\x8b\xf5܊2c\xa6\xa8\x84\xc3\xc2y\x83s\xa6E\x16\xa0ː8\xf3\x86\x7fc\xda4k\xafzXGN\x17\xbf\x90\xeb\xce\xdc\x00%;\x10\x16X\xbb4\x9e\xe2Ht\nٟ\xff\xb9|\x80\xb4u\xb8\x8c!\xfb\x81\xf7\xe3B{\xbc\x02\"L\xc85\x05]\xbaĵQMЉ\x92k%\xa4\v\x7f\x14\xb5@9\xa4\xdf\xfaU#\x1c\xdd\xfb\xaf\x1e\xad\xa3\xbb\xca\xe1:T\x12\x14/\xbd&\xcb\xe59\xdcJ\xb8f\r\xd6\xd7\xcc\xe2\xab_\x001m3\"\xf6iW\xd0-\x82\x86\u0091\xb5\xceD\xaa`N\xdcװ*Yj,\xe8\xfa\x88AZ*֢\b\xbeA\xe1\a\xd8H>艹v]\xfaV\xac\xd8x\xbdtʰ\x12\x7fPQ\xe7Ph\x80\xed\xe3Ԛ\x04Nvr^T\x0e6J\x8e\x94\x02\xd4i\xf1\xaeB\x83\xdd5\x06\xb5\xb2\xc2)\xb3'\xc51[\xe6#\r'.\"\x1cY\xf1\vǠp\x1f\x1c\xc2\xe0\x1a\r\xca\x02S\x848W\xc9L\x9c\xa2\x93\xd0\xc7\x10OS\x0fg\xa2\xe7$\xe0\x0f\xf7\xb7)b&\x86[\xe8n\xbc\xef\x05z\xe8[\v\xacyH(\x97\xf7\xbe\xba]\xc7\xcdB\xecp\n\x18h\x81\xb1\"=\x04c\x10\xd2:d\x1c\xd4zR#\xbd\r\x80\x1c\xcc`\xbb\xe2m\x8c\x14mH:\x86p\xa2\x1e\x18\xc5(\xc1\xe1\xdf\xcbOw\xf3\x7fM1\x7f8\x05\xb0\xa2@kC\xbe\xc6\x06\xa5{{\xc8\xd9\x1c\xad0ȩp\xc1\xbcaR\xacѺ\xbc\xdd\x03\x8d\xfd\xe9\xfd\xcf\xd3\xec\x01|\xaf\f\xe0#kt\x8doAD\xc6\x0f\xe1/ٌ\xb0\x91\x8e\x83F\xd8\tW\x89a\xd2:0@\xd6\xd5\x1e{\x17\x8e\xeb\xd8\x06A\xb5\xc7\xf5\b\xb5\xd8\xe0\x02ބJ\xf0\b\xf37r\xac\xdfߜ\xd0\xfa\x97\xe8@oH\xe8M\x04w\xc8w]\x8f<\x82t\x15s\xe0\x8c(K<\x16\xa2\xc3/\x04o\n\x89߂2ĀT\x1d\x15A1\xdd^\x8cG\xc8G\xa0\x7fz\xff\xf3I\xc4}\xbe@H\x8e\x8f\xf0\x1e\x84\x8c\xdchſ\xcd\xe1!X\xc7^:\xf6H;\x15\x95\xb2x\x8aY%\xeb}\xac\xf6\xb7\bV5\b;\xac\xeb,\xd6\x1b\x1cvlO,\xa4\x8b#{c\xa0\x99qg\xad5U\x19\x0f\x9fn>-\"22\xa82\xc4;\xcaNkAU\x03\x95\v1\xe7\x05k\x1c%\xcd\xf4Y\x1f\xcd\xc7)(*&K\x8c\xe7EX{\xcaB\xf9\xd5K\xfcx\x9c\xfa\xd37Q\x02\f\x03ǟ\x96D\x9fx\xb8P\xa9>\xe1pݷ\xd6\xd9\xc3m\xfc\n\x8dD\x87\xe1|\\\x15\x96\x8eV\xa0vv\xae\xb6h\xb6\x02w\xf3\x9d2\x1b!ˌL3\x8b6`\xe7\xe1\xc9<\xff&\xfc\xf3Ⳅ\xd7\xf5S\x0f\xd4{\xf4\xbf\xe6\xa9h\x1f;\x7fѡR\xad\xf8\xf4\x8bE\x96f\x82*$*\x82b\xd59]\xd73\xadk*l\xdaR\xea\xd0T,\x94\\\x8bқ\xf0z\x19\x93\"}]\xb3U\x8d\vpƏ\xe9<\xe3)\xdd\xfe\xe3\x85\xcb\xfd\xd2\x11M7{\xa1\x03ꪩ\xbb\xed\xf5ELJA\xe9\x9b1\x94\f6J\v61Nv=\xf2i\x9ax3\xae\x05\xcf\\lt\x9a\v\x1c\xb4\xed\xba\x89\xb7g\xebs\xb1\x16\x0f#\xf4\xde\v\x9e7\x9dĞ\xeb\x8b\x06\x7f\xf5\xf4\xb0\xe8#̦_\xd4\x03\x19\xad\xf8lHZ7\x8c\r&\x8fAh8\xd1\xf7\xef\xc1l\xaf\x8d\xdc=\u0378\x19\x11z\x94\xcfiGľh\xcb{̊.uK\xe9I\xf8\xe2\x86D\xa1\xe8\xb9\xd5ki^\xb0\x81\xeb\xf1\x8a\xd0\xfd3\xbc\xf5\t\xd1`x\xe5ǖ\xee\x8eٴ\xc9\xd4}CG_\\\x1a*\x11R\x87<<\x86譶f\xa2F\x0e\x87_\xa6\xc2\xcf\x0f6\xb4\xc1\xae\xa6j\xff\xa4\xc8[\xe4!\xd6N\x80\x1e\xafK\x9de\xce\x1cf\xa4\xe2e\x81fҽ\x1a\xb4\x96\x95\x97\xfc\xeb\xc7(\x15\xfb$\xed\x12`+\xe5ݡQ\xd2:ZKŕm\xad\xe0y͚\x8a\xd9KP\xeeIf\xca\xe2\x0e.\x7f\xde\xe4\xe0L(\xbb\xc3\xdd\xc4\xe8\xa8\xd3ߝ\xbcN&41\xf7}\xb0\x8eg\x11\xd0nt\x89\x83V\f*U'\xebV\x8e\x12\xb9oVh\x88\x88\xf0\xf3Bb$\x05\x8e\xa9\xceSx\xb1\x1e\x99|\xed\xf2\x19\xd3C\xec#\xde;\xccג\xc0\"\x8c\x89\x1d(GD\xe46\v\x13DYK&\f\xfeQp\x06b\x88~\xddl+f,\xdd\x7fk@[\x86\x96k\xf2\x01e\a\xd9\x02i\xea\x92\x1a(\xd7d#\xc8\aZ\x01\xff@5\xbc:\x01,\xa6\xf5\xca\"6\x8f\x04]\xb17\xec\xec\xb0\xd6\xf9!\b\xaf\x11z\xf9\xd3\xffPC\xd1;1v\x18\xdb\xf9cNvR\xf5\x84\x83\x1d\xb2\xee\x01M\x1fZ\xdb\xdc\xe9\xb7\x12l\xf8\xcb`)\xff\x19;Z\xfe\xb1\x8bh\x04\xfb\xad\x01\x14q\xee\xc4\u0099H9\x03I\xc2\xfa\x90-\xd6g\xbf\x8f\xe0\xd46\xf8^\xf0\xa6\x842J۳\xbd\fV\xfc\xf1l\x00j\x1dʄ\xe5\x7f+\xfe\xed\xb2E\xfb\xab\x15\xa7\x89\x15S\x05\xc4r \x13\x0e\x1ea\x027\x9bĴm\xcc@\x95X\xdc\xe4\xee\b\x11\r\xe7t\xcb\xe1\x86\x18\xd5\xc0\bf\xa8R\xf44\x82\x98\xa0\x82s\xf1\x12\xfb{\x81\xc0Y\x01]E\xe1P\xe3\x94\fU\xe7+\"?8V\x98\xb6\xe2,\xec\xf2NrV\x9cfQ\x93\x1a\x14\x8e\x9b?|\x81\x83\xb7p\xa0G&UbK\xf6Dڮ\x1dE\xda\nSie\x99\aR^\xb6\xe1$\xb2\x0eR>\xce\xd1\xfegۧ\x95ڤ@\xe3-n\xc5S\xdb+\xd1-\x10\xf8\x0eEc\x12\xcb$\xa4lP\x81HEj\xa9\xcd8\xdd\xc7e\x0fq\xe2`\x8ci\xc9\x14Ӝ\xed̋\xca@9\xbbўؔ\x02\xecZ+K\xb9\xb6\xaf\x92\x8d\xeb;\xd4o\x1d\x8c\xa71B\xb6TCI\xa4\xe7\xfa\x86\x83\xf6s\x95H\xfeV\xae\\\x8f\x82\x8e\x9bw\x96\x06\xa7[\xe0D\x03\x87\xc2Hu\x8e\xc9\x1c|\xba\x96#+G𘐚}\xf6o76\x01\x92X6\x7f:\xb0\xe2\xe0\x8c\x00˛\b\x87\x94\x124\n\x0ek\xa8\x9e\xc66I\xe6h\xef'\x99\x12\x1dm\x9b9SCx)qҶ\fq۶\x19\xc1{&X\xfc\xf7\xa4\xe6l\xdb?&b\x83&\xb9\x80i7gC_\x96iѩ\xb2\xc6\xfefG\xa0\xaa\xcd\xe9\x9a0\x13\xbe\xceA\xa4\x9cw\xe6\xff\x03\x13f9\xc7o\x86#_\x94\xe3'\xa92\a\xd1R%N\xff\a$\n*\x8b\a\xaf+\xb2\t\xf2\x97\xee\xa8k\xc2v\x91 \xe55\xd91n@\r(\xf3\xac\xf3\xf2\x12\xc8\xc8\xd1w\xb6U\xd4\x14\x87\x8f߭e\xa3\xdb8S&^\x86\x83\x9dI\x1c|\x84\xbeb\x9e\x81K\xd0}e\n*\xe7\x16\x7fEl\xb6_П\xb8\xfd\xfc\x13\x94S\xe8!y\x9cw\xb6\x91\xdb\xc1b\xbbS{;?w\x1b\xde\xf4\x89>\x93\vx\\\x13J\x1e\xe1\xe4,\x16*\x88%\x0e5h\xef&\xbd\xa7s\xe4`\xe4\x05\x99\xec\x11N\bƇRfG粂k\x8f\x900\xf7S\xad\x87@\xbb&\xef\xe0:L\xda\x0f\x88\bt\xbc\xf3\x91G0,\x16d\xd1\xfc\xe6H\xbe \t-\xe0\xfe\x82mF\xb2u\u0087HطڑȞ\x82\x03\xab37\x8a\xd1C\rxZB`\xec\x1b嬌\x139\xbe߈qk\xb8\xdf>K\xb3\x11\xd7\xce#\xd3\xc8%?IП\xa5\xc1/\xaf\x82N\xb7\xf0\v\x90\xe9\x06\xe2\xf1\x12Nl[\xf6\x15\x85\xb1\xb5\x00w\x06\x94\x0f\xfe9\x01\x1d<\x87g\xfaTs\xc1\xbd\x18أ1 k\x11<\xc3M.U\x92\xb3\xc4%֦\xc5\xcbB;\xfd\xe3\xf7NlҞl\xfbww#/m\r\x17\xb2\xaa\xe809\x98\xb5\xd4\x0fnd\xe0i\x0f\xc8Q_\xed\x1b<\xcf\xf9fb\xe0!L\v>1s`\x82\xd0 6@y\x86\xa2\xa4\x96\xf3\x12̵\x03\xd5d\v bL\xfdG\xd0\xf3\x15\x13\x1b\x9c\x80\xbc\x7fq\xbb\x80\xb4躈\x9c\x01Ց\xa0\xf1\x03j\xaa\\\x93J\x96\xe4\xe9\x00\nz\\q\x1e(\xb7\x96f&H!M7\x1ea\xe1ֲ|\xabɎ)m\xba\v\xcde\xb8F\xe7\xb2\xc3B\n\xdb\xdd}e\x15\xc8\xc6\\@\x83\x8f\xed\xe8^^\xb7\xa2\xdfY\xd5T\x84V\xb2\xc90\n\\\xb3\xfa\x85U1\xf9\xea)\xf0D\x99\x89y(\x8c\xcc\x18i\xa9Ts0\xb9$\xde\xc2Ί\xa3B\n\xcdJP\xa18\xc0Q\x96I{pw\x94\xf1&\x95\xf6I\xb5\xa5\xee\xad\xf8\xa8\xd4E\xde\xed\x177\xb2\x13m<ȧ>\x82\xb2Qp\xa0G lG\x98! \nK\x17PNd\xe3\x14\x1e\x19\x88\x9al\xb6\xcc\x13\xf0\xb6\x81h\xaa<\x04\xac\xf0d31\x19L\xebv\xffD\x19\x7f\r\xb2Y\xce\xfb$\xd5=\xd0\xf2\x92\x00̯\x9d\xe1\x04\x84n\x14&\xee\x9dxyb\x8fv\xd9!ζ9To\xa5\xe4@\x87\x05O\xa9fq}\xb9\x18\xfa\xb5\x1d\xfd\xbb\x88\xa1H\x81|sa\v\x9eT^\x16Qc\xa0\xaa\xddy\x93D5\xa2\xab}^A\n-\xf1\xc1\xfd*^ҹf\x82e\x10v\x90sa\xa6kYZ\x10\xafjY\xda\t\xa2QqI\xf8l\xd3\x03`OgpRp\xed\x91k\x16X\x99[ \xb4,\xa1t\x81Ik\xaax\x9fŕ\x97\x8d\x94*$w\xb7\xdcL̢lh=\x8f\x14C\xb1\xea\b\xabF<\n\xf9$V\xe8\xc9\xeb\xc5\x02$?4\xf8\xa2ӛ\x8b%\xd1\xef)\x85\xfa\xfc\x9a\xcfS\xc1xz\x05)\x93\xcd7\x8b\xa2!S\\0'\xd7\\\xe9\xf2ȏ\xb3\xab\x98\x9a\x7fb\xb0O4\x7fp5ǹ\xf5l\x9b\xf4\xa8\x8e\xf1\xf7t\x00s\x00\x15\x8a\x99WX\xb7\x9d\x92\xd3m>\xba\xf5cb\x81\x9b\xe5\x9f`\n\xbb\xc2\xcbA\xc9[\xdaѱV\xc0\xb5el\xdap\xe3ʏU\x93`\xa2\xac¯\xb4e\x90S91W/ѯ\x01\x8c\xf5\n\xa1\bP\x86I\x12;t\xb4t\x95\xbe\xddd|\xbf\xf0\x01C~a\xa5\x7f\xf7\xf2\xc0\x8c\x9a\x86\x99J\x86\xe9\xa2\xc9)|\x9d\xb3M\x17c-\x0f\xfa~\xbe\x9a\xf6\xc7B\x9f\x81\xeaK\xed\xcf\xc1\xa8\x01\xda\xc7`bȠ\x1c\x04%\xb7uٱ\xb0\x80\xb2\x94p\xb1\xa7Ї\x03-\xc4\xdb\x02O\xa2\f\x805\x86\x9a\xfdi\xf3\xd5\xedL\x93\x7f'\a\xd9$J\xea&\xb03S`1^V\xe1\x93\b`\xe8\xf1\xfd\xba\xff\x8b\x91\xbe\xc8\x02#_\x89ݡ\xa3\xd2FS\x99(ّ\x95\r\xe5\xbdC\xd6a\x8b\x96{\x88TD0\x9eʯZ\xb6\n\xe3{lD\xbe\xd4.ϲX\x1cM\x9b\x88y\xb5\x18\x17W`\xf4+,F\x94\xd4ҔC~\xa9i~\x8d\xc5tQĒʊa\xdd\xc4(\xd0\xf9z\x8a\x1c\xeb~\xa6v₊\x89\xccj\xb9g'Hrj\".\xaa\x84\x98-(ˬ\x7f\xe8W6L\x83\\P\xf5\x90\x85\x9c\xf9\n\x87\xc5u\r\xbe\x8e`r\x1f\xd9\xd5\f\x89:\x85I\xc0\xa35\fS\xd5\t\xd3(OT.\xe4\xd7$L\x82\xc6z\x85\xf9J\x84\x97\xab7|\t/`\\\xd4\xccV\x13<\xcbKȨ\x17XR%0\x8b\xb1\v+\x02b\xc6\x7fdޥu\x00\xfd<\xff\bМ\xec\xffHv\x7f\x04\xe2d\xce?7\xa7?\x02{F\xedNr\xc9\xe4\x8fKr\xf9\xd1\r\xf9\x85\xd65\x13\xfbs>\xc9\xe5\xa6IN:+\x04\xe8\xce\xd9c\xa5\xae\xb7\xd0\xf3\xb3RS\xba[\xb9\t\x9f,\x84\xf5\x980rMn\xc5\xe9\f.^\tH\xfa \xfdk[vYO\x8c\xf3\xee\xdd$\x04\xdb\x05\xe5o\xf9\xe9td\xc0v\x1c\xb3\xb0\x93$\x94\xaag\x1dϹ`_\x06ݻ\x81\xc2ik;eh3s\xb8\xd0ڮ\x1anX\x9d<\xf2\xb5\x92G\x86a\xc7\x03\x9c\">\xff*\xf1V\xd0\xf6\x84\x90\xbe\xdc\xc7Ӹ\x1e8\x0e4u\x86\x9e\x80sB\xf5\xf9\xf6\vw1\xb6\x90+\xbc\xebf)\x19\xf8\xc1_\xa0\xbd\xc6\x13\x9b\xf2\xd8E\xb8\xb2YY0x\xb9V'\"\"\xa3\xbah\xda\x1ev\xa6;~\xfb\xad\x01u\"\xf2\x889}o ͔\xdd;\xb9\xa2\xad\xff\x16$\x9d\x17\x97\xee:\xf6\xc0Oh\xe5\v\xb9\x15Nc'\xc1\x0eֈp\xac\x88k}#+ͭ\xdb3\xd25\tU\xc88:\xcd\x0f\x93\x8a)\xb7f\xfdu=\xa5\xe5\xbeҬ\x95\xf2*\xfe\xd2\xe5\x1e\xd3\x04\xc8\xdc\x1a\xf4\xbc\x9c\xc8l\xcd\xf9kyNs\xbeS\xb6јWS\xfe\x1a\xb5\xe4\vj\xc8\x17\xf8P˼\xa8l4\xe5Ԋ\xbf\x8a/\xf5\x8a\xde\xd4k\xf8S\x97yT3 \a5\xe09\xd5\xddYi\xbc\xec\x9cMN\x96m>s<]\xb5\x9dQ\xad\x9d\x91\r\x9a[iFU\xf6\xb2j\xec\f\x1c\xbe\x92\xaf\xf5J\xde\xd6k\xf8[\xaf\xebq\xcd\xfa\\\xb3\x9c3\xf3\xf3\xb2*ꋓ\f!\x1d\xfdY\x96p'\x95\x99s\x10\xee\x86\xfd\x13)\xc0\x8e\xd3$yID\xe8\x9a\xca4X\xdb\xdf\xdb\xfd\x97m*\x9d\xad\v\xe6\xef/\xb2\xb4k\x9b\xcb-\xdc\x0f\xba\x9f]\xa1݁\x02\xe1\x1e\x96\xf8\xef\x87/\x9f#\xfc\x94=\xea\x8d\xde\xc1\x9b\x06\xce\xc0(=r|\xf6\xc9\x17\xdc8l\xa1\x0e\x7f\xe1$\x01\xad\xd9\x7f\xe1\x9b]\xf3\x01\x99ۻ\rv\r\xd6\x12\xbe\xf5\x15\x13\xfa1\xf7\xb6\x05\xab=\"FF\xb9\x7f\xb3\xebAL\x94\x9d\xc6?\t\xbe\x98\x14\xb4\x17\x1b\xab\xc9rEH\xf6\xd4\xddm\xdc\xea\xd6\xe4\x935\xddĉH\xc7x\a\xa6\xcaUM\x959!w\xe8븆\xf1\xa0L\xd0!S\xa1\x93QQ{\xfe\x16T\x12\xb7\xe1I(L\xc0\x9d\xea~6s\x88\xd1K\xd61~{b\xf6\xde\xc4\v\xaec\\\x1d\xaf\x10S\x89\xcf\xc9\n\x88\x17\vIy1t\xf7mN\xac\xddǎ\xd3\xf2\xccz\xb2!\xac\x93\xc0\x8f\x1d\x8f\"M\vZ\xebC\"*\xf4<\x99\x86\x0fU\x19j\x9a\xcc\xfd\xb8\xbe\xbd-\xb1\xe2\xd0\x11@O\x10D\x94\xea<\xdb7X\x93=\xab\x0e\x10\xaaa4\xa1\x05\xe3\xd7\x1d\xc7\xfc\xf7Iyf>\vr\xf1\x83 \x0e=#\xa2\x02#MV\x8c\x05^h\xf1rA\xb2sք\xcb(l\x9d\xb6;3_\x94\xb8\xf8-\x89yd%\x105\xf6\x8cD\xceS\x11\x7fW|N\x88$]\x1c\xa0l8d<\xf0\xf6\xd0\xe9:\xff\xc4[\x00\x9c:\x93\xb2\xffț\xc5kG\xbdZ\x83\xb7\xff\x98\x9cG\xba\x87\xf0{\x86\x88Oݾ>\xdc\xe9p\xe0^\x93\xa1\xae\xbe\x0f߃5,:\x98#\x8a\xc4μH<\x1f\xa4|̲B\x7f\x8e\x1d\xdbh\v\x13\x8e\x8f\xf0j\xdbV6\xa6k_ʱ\xd8,\xbe\xbb\xf8\xc2\xf2\x1aa\u07ba\xbb.c!\xc4\xf3\x87\x1d\xe3\x80\xe8\xcfIC9\x11M\xb5\x05\x85\f\x19;\x1c&\xee{?\x84\xd7e9?]\x0f!\x0f\x9e\x9bnaOAD\xd2{\x19й\x03\x1cb_\x03 \xae\xfb\xc4ӓ\x04\xdf\n\x0e*\x7f챻)\x8e\x8e8\xfe\x84\x8b\xcaE\xb0\xeb=\x86]\xb7x\xe7ƀH{a$\xe4\x9eñ\xb8`\xe9\x13\xf6^}\xa0z\xceл\xb3}\xe2\xe5ߎN\x8a6\xde\xfdȑL_F\\\x91\xcf\xf0\x94\xf8ꐅ\x19\x9c\xb4&Y\x91\x8d\xb8Sr\xaf@\x9f3\xce\no\xa71\xb1\xff$\xd5\x1do\xf6L\xc4\xe2\xe1e\x9d\xef\xa82̲\xb2[Ob쇠\xcb\x12\xbf͏\x1e\xff\x81\t\xca\xd9\xdfRF\x7f\xf7ǹ\x19&\x84]\xed\x917\x1b\x17u\xdd\xe6\x04\x9d\x97\xc4ou\xabxR1D\x0fmM>K\x03!]\xc9\xfa@\x99&[\xd0f\x05\xbb\x9dTƅ\xb1W+\xc2v\xde\xe0I\x85[)\xe3\xe8n\xb9g\xbe\xad\x0f\x16\v\xec\xa2\xf1\xefCj\n\xb5\v\xfai\x15=\xb9\xc8\x1c-\nkK\xc3;mhʦ\x7f\x96\xfb}\x84\xa6\xb6\xc1\xf7\x827%\x94Q۞\xade\x80\xf1dz\x01hu(\x13V\xfe\xad\xfa\xb7h\x8b\xf6W\xabN\x13\x18S\x05\xc4J \x13\x0e\x1ea\x02\x17\x9b\xa4\xb4m\xcc@\x95@nru\x84\x88\x86s\xba\xe5pC\x8cj`\x842T)z\x1a!L0\xc1\xb9t\x89\xfd\xbdB\u0b00\xae\xa1p\xa4qF\x86\xaas\x8c\xc8\x0fN\x15\xa6\xad:\v\xab\xbc\x93\x9c\x15\xa7YҤ\x06\x85\xed\xe67_\x90\xe0-\x1c\xe8\x91I\x95X\x92ݑ\xb6kǐ\xb6\xcaTZ]恔\x97-8I\xac\x83\x94\x8fs\xbc\xff\xd9\xf6i\xb56)\xd0y\x8bK\xf1\xdc\xf6Ft\v\x04\xbeCј\x04\x9a\x84\x94\r\x1a\x10\xa9H-\xb5\x19\xe7\xfb\xb8\xee!N\x1d\x8c\t-\x99\x12\x9a\xb3\x95yU\x198g\x17\xdaS\x9bR\x80ŵ\xb2\x9ck\xfb*ٸ\xbeC\xfb֡x\x9a\"dK5\x94Dz\xa9o8h?W\x89\xeco\xf5\xca\xf5(\xe8\xb8x\xe7ip\xba\x05N4p(\x8cT\xe7\x94̡\xa7k9\xbar\x84\x8e\t\xad\xd9\x17\xffva\x13 \x89\x15\xf3\xa7\x03+\x0e\xce\t\xb0\xb2\x89pH)A\xa3Ⰾ\xeail\x91d\x8e\xf7~\x92)\xd5Ѷ\x99=5\x84\x97R'm\xcbP\xb7m\x9bQ\xbcg\x8a\xc5\x7fOZζ\xfdc\x126X\x92\v\x84vs6\xf4e\x85\x16\x83*\xeb\xecov\x04\xaaڜ\xae\t3\xe1\xeb\x1cD\xcayg\xfe?0c\x96K\xfcf8\xf2E%~\x92+s\x10-W\xe2\xf4\x7f@\xa6\xa0\xb1x\xf0\xb6\"\x9b!\x7f鎺&l\x17\x19R^\x93\x1d\xe3\x06Ԁ3\xcf\xda//A\x8c\x1c{g[EMq\xf8\xf8\xddz6\xba\xcd3e\xd2e8ع\xc4!F\xe8\x1b\xe6\x19\xb8\x04\xc3W\xa6\xa0ra\xf1W\xa4f\xfb\x05\xe3\x89\xdb\xcf?A9E\x1e\x92'yg\v\xb9\x1d \u06dd\xda\xfb\xf9\xb9\xcb\xf0\xaeO\x8c\x99\\\xc2\xe3\x9aP\xf2\b'\xe7\xb1PA,s\xa8A\x7f7\x19=\x9d\x13\a3/(d\x8fpB0>\x952;:W\x14\\{\x84\x84\xbb\x9fj=\x02Z\x9c|\x80\xeb(i? !0\xf0\xce'\x1e\xc1\xb4X\xd0E\xf3\x8b#\xf9\x8a$\xb4@\xfb\v\x96\x19\xd9\xd6I\x1f\"c\xdfj\xc7\"\xbb\v\x0e\xac\xce\\(f\x0f5\xe0n\t\x89\xb1o\x94\xb32N\xe4\xe4~#ƽ\xe1~\xfb,\xcdF\\\xbb\x88L\xa3\x94\xfc$A\x7f\x96\x06\xbf\xbc\n9\x1d\xe2\x17\x10\xd3\r\xc4\xed%\x9cڶt\xe8f\xd82\x84۵\x8dK\xa4D\xf60M6\xc2\x06.\x9e\x1e\x98/u\xd3Mۇ~\xab\x1a\x8d)4!\xc5\nM\xe5:5\x93#v&H\xa9z\x1c9G-N\xea&\xcc\x04\xfb\xd5Z\x127\xdee\x809-\xa0\f\xd1&\xe6-\xa9\x81=+H\x05j?e8\xba\xad\xb6\xfa=\x0f\x85L\xad\xeb\xdaB\t\xcb3\xed\xa1y\xd5]\xce#\xb3\xb2;7\xa3W`\xf6lבt\xe5x\xd7\xf9\x15\xa1\x89E\xffc\x96\xba\xb4,\xf1\b\x89\xf2\xbb\x05\x1a\x7f\x01/\xcem\xbfC\xccYȊ\xd6v\xff\xfe\xaf5s(\xd0\xffGj\xcaT\xc6\x1e\xbe\xc5c\"\x0e\xbd\xb1>1֝\xc6\xce\xc04\xb1\xfc=R~\x9e\bO,NZ\xdd\x02\xdc\x19r\xb9;\xf3X\xae\xc9\xd3AjgSw\fx*e\xd3oL\x937\x8fpzs}\xa6\a\xdel\xc4\x1bg\xe0\x17\xab\x9b\xe8-H\xc1O\xe4\r\x8e}\xf3\x1c'(S\x123\xbb}_=Ɣܪ\xa2\xf5\xcaK\xaf\x91\x15+Fljdz\xbcm=q\xea\xa6\xc8\xdbܸw\x8f\xa7V\x9b%\xbf\xb5\xd4\xe6\xe7t\xa2o\x04\x9f\xbb0\xa2\xef\xd3&\xf2e\xb3\xbe\xbe\xcf}Eel=\xc0\x9d\x01\xe5\x93\x7fNA\x87\xc8\xe1\x991\xd5\\r/&\xf6hL\xc8Z\x02\xcfH\x93;*\xc9Aq\x89\xb7i\xe9\xb2\xd0O\xff\xf8\xbd\x93\x9b\xb4;\xdb\xfe\xdd]\xc8K{Å\xac*:<\x1c\xccB\xf5\x83\x1b\x19d\xda\x03r\xdcW\xfb\x06\xf7s\xbe\x9b\x18d\b\x8f\x05\x9f\x9890AhP\x1b\xa0\xbc@QR\xcby\r\xe6ځj\xb2\x05\x101\xa7\xfe#\xd8\xf9\x8a\x89\rN@\u07bf\xb8_@Zr]\xc4\xce@\xea\xc8\xd0\xf8\x01-U\xaeK%K\xf2t\x00\x05=\xa98O\x94[O3\x13\xa4\x90\xa6\x9b\x8f\xb0pkY\xbe\xd5dǔ6]Ds\x05\xaeѹⰐ\xc3vu_Y\x05\xb21\x17\xf0\xe0c;\xbaw\xae[\xd1\xef\xacj*B+\xd9d8\x05\xaeY\xfbªx\xf8\xea9\xf0D\x99\x89\xe7P\x98\x991\xd2r\xa9\xe6`rY\xbc\x85\x9dUG\x85\x14\x9a\x95\xa0Bq\x80\xe3,\x93v\xe3\xee(\xe3M\xea\xd8'Ֆ\x86\xb7\xe2\xa3R\x17E\xb7_\xdc\xc8N\xb6\xf1 \x9f\xfa\x04\xca&\xc1\x81\x1e\x81\xb0\x1da\x86\x80(,_@9\x95\x8dSxb i\xb2\xc52O\xc1\xdb\x06\xa2\xa9\xf2\b\xb0\u009d\xcd\xc4d2\xad\xdb\xfd\x13e\xfc5\xd8f%\xef\x93T\xf7@\xcbK\x120\xbfv\x86\x13\x10\xbaQxp\xef\xd4\xcb\x13\xe3y8[\xce\x11N\x1bQ\x1c\x00\xf5\x94\xe8\xa9\x0f\xe2\xc03\xa1\r\xd0\\Y\xb0^S#\x04\x13\xfb<\xdee\xa78\xdb\xe6H\xbd\x95\x92\x03\x1d\x16<\xa5\x9a\xa5\xf5\xe5j\xe8\xd7v\xf4\uf886\"\a\xf2݅-xVy]D\x8d\x81\xaav\xfbM\x12Ո\xae\xf5y\x05-\xb4$\x06\xf7X\xbcdp\xcd\x04\xcb`\xec\xe0̅\x99\xaegiA\xbc\xaagi'\x88N\xc5%\xe9\xb3M\x0f\x80ݝ!HAܣ\xd4,\xf02\xb7@hYB\xe9\x12\x93\xd6U\xf11\x8b+/\x1b)UH\xaen\xb9\x9b\x98\xc5\xd9\xd0z\x11)\xa6b\xd5\x11V\x8dx\x14\xf2I\xac0\x92\u05cb\x15H~j\xf0E\xa77\x17k\xa2\xdfS\v\xf5\xe55_\xa6\x82\xf3\xf4\nZ&[n\x16eC\xa6\xa4`N\xaf\xb9\xd2\xe5\x91\x1fg\xb1\x98\x9a\x7fb\xb0?h\xfe\xe0j\x8es\xeb\xd96\xe9Q\x1d\xe7\xef\xe9\x00\xe6\x00*\x143\xaf\xb0n;\xa5\xa7\xdb\xf3\xe86\x8e\x89\x05nV~\x82+\xec\n/\a%o\xe9@\xc7z\x01\xd7V\xb0iÍ+?VMB\x88\xb2\n\xbfҞAN\xe5\xc4\\\xbdD\xbf\x060\xd6+\x84\"@\x19&I\xac\xd0\xf1\xd2U\xfav\x0f\xe3\xfb\x85\x0f\x98\xf2\v\x98\xfe\xdd\xcb\x033j\x1af*\x19\xa6\x8b&\xa7\xe8u.6]\x8a\xb52\xe8\xfb\xf9j\xda\x1f\x8b|\x06\xaa/\xb5\xdf\a\xa3\x0eh\x9f\x82\x89!\x83r\x10\xd4\xdc6d\xc7\xc2\x02\xcaR\xca\xc5\xeeB\x9f\x0e\xb4\x10o\v܉2\x00֘j\xf6\xbb\xcdW\xb73M\xfe\x9d\x1cd\x93(\xa9\x9b\xa0\xceL\x81\xc5xY\x85?D\x00C\x8f\xef\xd7\xfd_\x8c\xf4E\x16\x98\xf9J\xac\x0e\x03\x956\x9b\xcaDɎ\xacl(\xefm\xb2\x8eX\xb4\xd2C\xa4\"\x82\xf1\xd4\xf9\xaa\x15\xab0\xbe'F\xe4K\xed\xceY\x16\xab\xa3i\x171\xaf\x16\xe3\xe2\n\x8c~\x85ň\x91Zz\xe4\x90_j\x9a_c1]\x14\xb1\xa4\xb2bX71\nt\xbe\x9e\"ǻ\x9f\xa9\x9d\xb8\xa0b\"\xb3Z\xee\xd9\a$95\x11\x17UB\xcc\x16\x94e\xd6?\xf4+\x1b\xa6A.\xa8z\xc8\"\xce|\x85\xc3\xe2\xba\x06_G0\xb9\x8e\xecj\x86D\x9d\xc2$\xe0\xd1\x1a\x86\xa9\xea\x84i\x92'*\x17\xf2k\x12&Ac\xbd\xc2|%\xc2\xcb\xd5\x1b\xbeD\x140\xaejf\xab\t\x9e\x15%d\xd4\v,\xa9\x12\x98\xa5\u0605\x15\x01\xf1\xc4\x7fdޥu\x00\xfds\xfe\x11\xa09\xa7\xff#\xa7\xfb#\x10'\xcf\xfcs\xcf\xf4G`Ϙ\xddI)\x99\xfcq\xc9Y~\fC~\xa1u\xcd\xc4\xfe\\Nr\xa5iR\x92\xce\n\x01\xbas\xf6D\xa9\x1b-\xf4\xe2\xacԔ\xeeVn\"&\vi=&\x8c\\\x93[q:\x83\x8bW\x02\x921H\xffږE\xeb\x89q\u07bd\x9b\x84`\xbb\xa0\xfc-?\x9d\xce\f؎c\x1ev\x92\x85R\xf5\xbc\xe3\xb9\x10\xecˠ{7Q8\xedm\xa7\x1cmf\x0e\x17z\xdbU\xc3\r\xab\x93[\xbeV\xf2\xc80\xedx\x80S\xa4\xe7_%\xde\nڞ\x10җ\xfb\xb8\x1b׃\xc0\x81\xa6\xf6\xd0\x13pN\xa8>_~\xe1.\xc6\x16r\x85w\xdd,'\x83<\xf8\v\xb4\u05f8cS\x11\xbb\bW6+\v\x06/\xd7\xeaDFd\xd4\x16M\xfb\xc3\xceu\xc7o\xbf5\xa0ND\x1e\xf1L\xdf;H3e\xf7N\xafh\x1b\xbf\x05M\xe7ե\xbb\x8e=\x88\x13Z\xfdBn\x85\xb3\xd8I\xb0\x03\x1c\x11\x8eUqmld\xb5\xb9\r{F\xba&\xa1\n\x19G\xa7\xe5a\xd20\xe5֬\xbfn\xa4\xb4d\b\xc7џe\twR\x99\xb9\x00\xe1n\xd8?q\x04\xd8\t\x9a$/\x89\b]S'\r\xd6\xf7\xf7~\xffe\x8bJ\x9f\xd6\x05\xf7\xf7\x17YZ\xdc\xe6\xce\x16\xee\a\xddϮ\xd0\xee@\x81p\x0fK\xfc\xf7×\xcf\x11~\xca\x1f\xf5N\xef\xe0M\x03\xe7`\x94\x9e8\xfe\xf4\xc9\x17\xdc8j\xa1\r\x7f\xe1C\x02Z\xb3\xff\xc27\xbb\xe6\x132\xb7w\x1b\xec\x1a\xbc%|\xeb+\x1e\xe8dz\xb7-X\xeb\x11)2*\xfd\x9b]\x0fb\xa2\xec4\xfeI\xf0Ť`\xbd\xd8XM\x96+B\xb2\xbb\xeen\xe3\xb0[\x93O\xd6u\x13'\"\x9d\xe0\x1d\x98*W5U\xe6\x84ҡ\xaf#\x0e\xe3I\x99`C\xa6R'\xa3\xaa\xf6\xfc-\xa8$mÓPx\x00w\xaa\xfb\xa7\x99C\x8a^\x82\xc7\xf8\xed\x89\xd9{\x13/\x88Ǹ9^!\xa5\x12\x9f\x93\x15\x10/\x96\x92\xf2j\xe8\xeeۜZ\xbb\x8f\x1d\xa7\xf5\x99\x8ddCZ'A\x1f;\x1eU\x9a\x16\xb4ևDV\xe8y:\r\x1f\xaa2\xd44\x99\xebq}{Kbš\xa3\x80\x9e \xa8(\xd5y\xb6o\x80\x93ݫ\x0e\x10\x9aat\xa1\x05\xe3ם\xc0\xfc\xf79\xf2\xcc|\x16\xe4\xe2\aA\x1cyFT\x05f\x9a\xac\x1a\v\xb2\xd0\xd2\xe5\x82\xc3\xceY\x17.\xa3\xb0u\xda\xef\xcc|Q\xe2\xe2\xb7$払 \xd4\xd83\x129OE\xfc]\xe99\xa1\x92tq\x80\xb2\xe1\x90\xf1\xc0\xdbC\xa7\xeb\xfc\x13o\x01pjO\xca\xfe#o\x96\xae\x1d\xf3j\x1d\xde\xfecr\x9e\xe8\x1e\xf2H\x89w\x17\xa4\xf3\xecݫS\x85\xf5\xe3uS\x14\xa0\xf5\xae\xe1\xa1Z\xa6P@\r\x94\xa1{\xb22?\xacaQYHSsIKP\x1f\xa4ر\xc49B\x8f\xa6\xff\xd3\xeb<\x10\xd8\x02?6\xaa}\xc4o\xf2\xfd\xb4gi\xa7\x9a*\xca9\xf0O\x8c\x83\xfeI>\t\x8bWƦ\xbbK\x8d\xeb\\\x1c*\x1ae\r\U00089226\xdaZ\xdf\x14\x8c\x19\x0f\xfcvRM\x17\xdf:\xba3a`\x9f\xce\xda+fࡦJ\x03b\x94\xb1\x82_\aC\\\x90\xb9\xe3t\uf2b4JVP\x03ў\xe2\fc\xe8\xe3x\x8d\xb0\xb8\xab\xb7\x91#٤\xec\x1d=V\xea?\xba\xa7\xd3~̪\xf3\x90\xe3U\x06\x1c\x9d0\xd4\x13F\xba\xa0\xb5\xc1{\x18\xc8v\xe4\xb9\tvW\xee\xce\x1e\x9f\xec\x81\x1d\x17L_P\xeb\xcb\xc1\xb4\xa1U\"\x16\x18^E\x1c\x8e\xc0'^U\xd9) \xeb\xbe\xd1\x17\xeb\xc2R\xc2Du\xac\xe9-\xd7\x1d\xd8\x0e\f\xba\xdf\x164\x94\x04\x8e \x88ݤ\x94q(\xa7d\xf8+&n\xd5\x11\xd4[\x1d\xe1`I\x9b\x15\xfe\aC\x95\x89\xa8\x9f뤝T\x1557\xa4\xa4\x06Vv\xf4e>Z\xfa\xfdL\xa5\xe6\xcf\xd8\xf0ڔ\xdf#x\xd7\t\xd9˹\xbf\xecT\x81\xd6t\x1f\x02\xc8'P@\xf6 ,\x89\xa7\x9e\bl\xef\x8by\x1b\x12+\x1f-\xb5ha\x1a\xea'p\xbeZ<}L\x80\xf4\xef\xcebL\xba\x1f\xd5\xdci\r\xe2\xef\xaa\xdd\x03\xd5\xc3w\x8a\xcf\b\xf1\xa9\xdb\xd7gm\x1d\rܣ8ԕ)\u2cf6\x86\xc58y\xc4\x1eڙ\x17Y\x99\x83\x94\x8fY\xce\xf4ϱc\x9b4b\xc2\xc9\x11\xde\xd0\xdb\xca\xc6t\xddd9\x96b\xc6\xe7#_\xd8\xec \xcc[weg,\x13z\xfe>e\x1c\x10\xc3Ri(\x0f&\xc6\nd\xecp\x98\xb8\xb6\xfe\x10\x1e\xc9\xe5\xfct=\x84\\\xe4\xcc&\xeeuN\x11#\xb9ިJ/Yo\x1c\x9c\xbfޮ\x17\xd0\xc6$K\x16?\x9e1y\x01r\x8cy\x17\xf3\xb4\x98\xf64p}\t\xcc\x17\xb1\xbb\xeb\xa9\x04\x9f$\x95\x9f\xb5^\xca2Z\xe8^\xb46\x97>\xebu~^T\x8a\x13ۘ\xf4Ǎ&\x8f\xd1\x1f\xfa\x98\x13W~\x1bt\x1fܞ\xb3\x11f\v\xd1ǂ\t\xe2\xfc3ۅ\x7fp\xb3\xe5\xf0/g=~\xe7[pOT\t&\xf6s\x8b\xff\xd5wK\x84\xd5\x1eB\"\xb0N,\"\x86ڋ\x02\xeb\x80\xe4\xc8\xffp\x88\xc1\xf63B\xeb\xa499\xfb\x88\x82\\v\x88\xecg\xf2_\xfe?\x00\x00\xff\xff8\x18\xae\x9avj\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Mw\xdc8rw\xfd\n<\xe5\xe0\xddaW\x15\xcf \xbf`\xcc\xe3O]\xaf\x18\xcfs\xa2\b/n\xb5\x90\x16\xf4\x8d*\xea2Pb\xc5r0\x99\x16\x95\xa5\x11\xdfYnk\xc3Ԏ\xd9=t\xfb\xc1\xf2\x8bQ\xf2\x96\xdb\xfd5[\x1b\xaa\xb7\xae\xf6܄_\x1d\x89\x1c\x00\xff\xc9\x1e\x117c\xb5\x90\x0fc\xbd\xbdg7ZI\x06\xdf*\r\x06Qf91P>\xb0\xa7=Hf\x15ӵ$T\xfe\x83g\x8fu5\x82H\x05\xd9z\x80\xa7Ǥ\xffq\x0e\x97\xfb=\xb0\x82\x1bˬ(\x81q\xdf!{\xe2\x86p\xd8)\xcd\xec^\x98y\x9a \x90\x1e\xb6\x0e\x9d\x9f\x86\x9f\x1dB9\xb7\xe0\xd1\xe9\x80\n»\xce4\x90\xdcދ\x12\x8c\xe5e\x1f\xe6\xfb\aH\x00F$\xaaxmH8\xdaַ\xddO\x0e\xc0V\xa9\x02\xb8\xbch+\x1d\xde9\xd9\xcb\xf6P\xf2k_YU \xdf\xdfn\xbe\xfe\xeb]\xef3\x1bȒ\xa7\x14\x13\x86q\xf6\x95&\x06\xd3~\xa62\xbb\xe7\x96i@\u0383\xb4X\xa3Ұ\n\xd4\xcd\x1b\x90\x8c)\xcd*\xd0B\xe5\"\v\\\xa1\xc6f\xaf\xea\"g[@\x06\xad\x9b\x06\x95V\x15h+\xc2\xd4s\xa5\xa3Q:_\a\x18\xbf\xc1A\xb9ZN\x12\xc1\x90\xf0\xf9\t\x05\xb9\xa7\x83\x9b\x1f´\xf8\x13\x93z\x80\x19V⒩\xed/\x90\xd95\xbb\x03\x8d`\x02֙\x92\a\xd0H\x81L=H\xf1?\rl\x83RoI\x18-x}\xd0\x16\x9a\xc0\x92\x17\xec\xc0\x8b\x1a\xae\x18\x979+\xf9\x91i\xc0^X-;\xf0\xa8\x8aY\xb3\x9f\x95\x06&\xe4N]\xb3\xbd\xb5\x95\xb9~\xfb\xf6AؠI3U\x96\xb5\x14\xf6\xf8\x96\x94\xa2\xd8\xd6Vi\xf36\x87\x03\x14o\x8dxXq\x9d텅\xcc\xd6\x1a\xde\xf2J\xac\buI\xdat]\xe6\xff\x148j\xde\xf4p=\x99o\xae\x90\"\x9c\xe0\x00jD'0\xae\xa9\x1bEKh\xfc\x84\xd4\xf9\xf2\xf1\xee\xbe+L\xc2\f\xa9Ot\xefHX\xcb\x02$\x98\x90;\xf03z\xa7UI0A\xe6\x95\x12\xd2\xd2\x1fY!@\x0e\xc9o\xeam),\xf2\xfd\xaf5\x18\x8b\xbcZ\xb3\x1b2/(\x87u\x8530_\xb3\x8dd7\xbc\x84\xe2\x86\x1bxq\x06 \xa5\xcd\n\t\x9bƂ\xaee\x1cVvT\xeb\xfc\x10\xcc[\x84_a\x8e\xdfU\x90\xf5\xa6\f\xb6\x13;\x91\xd1\xc4 \xed٨\x80\x81\x06ue|\xd6\xd2/\xa4\xa6\x86_\ax8]\x16z\x05\x83\xf6\xc3\xee\x89í\x19C\xb9r\xd0P\xa7H5\xe4\xee\x98\x16\xecP\xc2C\x99\xc1\xa4\xaf\xf5R\xed\xdb\tL\xe6U\xdd:\x82\xe3\tW\t\xc5GQm\xca\x12r\xc1-\x14\xc7\x19L\xdf\xdc\xf5\xab\x8fQO\x11L\xb6ujW\xecF\xf0\xecR7\xaf\x81\x89\x0eD\x9aZ\x7f\t5N-\xe4_\xc8\xdav\r[\xb7\x10\x8d\xba\xe0kٲO\xecz?IxZ\xb3͎Y\x8djq\xdb5\xb4=\x90\xa2(p\xa6\xe2\xa8*\xc8{\xc8ƻ\x13;&\xac\x1f\xdf\b\xd0-'\x9c$[;\xefg\xdd\xda\xfa\xc6n#\xca\x03|\x9d\xf6F\x8cF`\xa2\\p\xcb$|\xb3m;$\x16\x8dr\xc7\v\xd3\f\xd3\rʫ ?\xb0\x11\x88IC\xbdb\xdb\xda:\x80c\x18\x8c\x80mp\x82\xb2\xb2\xc7+\xd7v\xa7\x8aB=1C\n\x17m\xddN<\xd4\xda\xe9\x82?\xe4\xb0\xe3ua\xaf\xdd(\xfe\xb8~\xb3h\x1aZ(+4\x8d3\xc2}\xef\xab\xe1\xe0P\a\xe4Md\x10\x9c\xdb\xe0J(\xefA\xb0\x13\x03N\xdd\xed\x01u\xd3A\xe4\xdeB\x8f\x92!\xae\xbd\xb0dF\xdcI^\x99\xbd\xb2(\x0f\xaa\xb6c\xb5\x06\x03\xb8\xb9\xdb\f\x1au\xe6'bE~*\x89\xa7U쉋Sm\xe6\n\xeaޛ\xbb\r\xfb\x8an?\x04\x98\xcc\xcdEfk-\xc9\xe5\xf8\x02\xbe\xec\xd2\x16]\x17`<*\u0381m\xf5\xceU\xcb)\x17\xc6\x17|\v\x053P@f\x95\x8e\x93'E\b\\I՟\x11ʎh\xd2~\x104\xabDۂ\x01\xe6^d{\xe7n\xa2\x94\x11,\x96+0\xa41xU\x15\x11+ԖY\xc9\xf0\x9d\xcd)\x8d\xb6$\xa8\x8f!ܘ\"iK\xa2\x0enˌ6\xeeS\xbd\x11\x9bW\xa2\xf7Д\xdf%웓\xe6\xcf/\xecHn\x01\xa6\xbb\xd6%l\xf8\x9a\x02\xb5\xe7\a\x9a\x7f0Ɲ7[6\xc3\xd6\xcf>[\x9e\x85k\r\x1a\xff L#cu\xe7m\xd5\"\x86\xfd\xd4myE\x8b\xf0\x9ea\xf9\x15ۉ\xc2\x02\xf9Rs\x88v\x1c\x9dY\xce='\x81Rm/\x96\x92\xdbl\xff\xb1ٺIh1\xa0\xd5\x10\x80\xf3\xcbC\fC)\x8b\xff|\xfc&\f\xa2(s\xf6A\x81\xf9\xa4,}yQ\x12\xbbA\x9cI`ט\xa6\xa5tf\x01鲨\xff\x16\a\xb7\xbd\xba\x87\x96m°\x8d\xc4\xf8\xcc\xd1g\t\x9b\xf6\x10\x90sh\x95\xb5\xa1\xedK\xa9\xe4\xca-i\xf9\xde\x16\x00\xed\xe2\xe5Y\xa5t\x8fSW\v!\x8e\xa2\xe8ѻGk\xe5~9\xc9=\x99*\x1a\xaa\x82g\x90\x87]6Jt\xe1\x16\x1eD\xc6J\xd0\x0f\xc0*\xb4\x1b\xe9B\xb5@\x93\xbbr\x86\x14\xa6\xbb\x16\xa1x\xb30\x92\xb71VV8\xeb\x13k\x066'U\x8fd\xb5LWO\x1b%\x99w\U000874a8\xdfM\xc3\\fY\x16\xf2\xeb\xd4\aqH:\xf7\xa3\xe4\xb4\xf1\xf474\xaf$\xde\x7fO\xb3\x86\\h\xb3f\xef)\t\xb5\x80n\xfb\xb0J\xd8\xe9*\t$b\"\fC99\xf0\x02\xdd\aTޒA\xe1\x9c\t\xb5;\xf1\xa0\xd2T\xcc\xd3^\x19g\U000db371\xcbG8\xfa\xcdٮ\x96\xb8\xdc\xc8\xe8\xaa}\xbf\xa0\xce?QZ\x8dעdqd\x97\xf4\xdb%9fK\xa6\xc8\x19\xce\xdb\x02\xa9^P\xf5\xdb\xea\xb1ނ\x96`\xc1\xacJ^\xad\xfcl\xb0\xaa\x8c\xeeq\xbaB\xa9\xa2K\xc2\b\x8cӃǃ\x8d\x9b\x84Jt\xff\xe7(\x90<\x1f*e\"\x99\x16\x11\xb4n\x95\xb1n\xf1\xb0窏\xac.\xa6D\x8e~ő\xf1\x9d\x05͌U:$/\xa2\xca\x1e,\xae\xa3Ԙy\xb9q\xfbD~%\xd3\x01\xc6\x00\xf5\xb2\xd5.\xce\x1e\\\xba\xbd*\xfc\xff<̌\x1c-\x82]i\x95\x81\x89f#\xb4%\xd1\xea\xcc,\xf66\v\xbd\xdc\x05~\xbb$\xb5\x9e\xb2\f\x1d\xca27\x1eI{FP\xf4\xf1[g\xcd\x1aU\x18\xfe\x9d\"\xca\xe7\xe0\xc8\xe8\xfcBY\xf2a\"m2\xba7\xaeu\x98\x80\x1e\x98\v\xb6\xf4CM\ni\x99\xcf\xedE\xf2\xb7洔Bn\xa8#\xf6\xee\xc5\x1c\x1d\x16\xcc@,#i\xac\f\xd8\xe1۷\fi>\xa4ƾ,\xa4\xaa)\xda\xe7\xd1\xd0\xe3\xec\xe9.H:\xa7\x18:\xe2R\xd9\xeeB\x8f\xef\xe9\x8da;\xa1\x8dm\x11^\x00U\x98\x89\xac\xa7\xd1\xe1\x9d\x11\x9fʏZ\x9f\x1d\x9e~v\xad;K\x92{\xf5䓘\x97\x04\xe5\x81\xf8{~\x00\x9fJ\n2S\xb5\xa4\xc52T\x17\xd8\xcd\x02\x88\x8e\x89Θ$\xda\xccNcY\x97\xe9\x04Y\x91t\n9\xbb\xb2\xd6m\xf2#\x17i+[\xec<\xb6ک$ʱ\xd2\xcf\f\xf5ٔ\xddl\xf5\x92\x7f\x13e]2^\"[\x96Ĝ;\x97\x87\x19R\xdb\x1d\xaf\x9f\xb8\xb0\xfeĐ۔]\xa6M3UV\x05X\b\x19\x96\x99\x92F\xe4и\x0f\x9e\xff\xa3\xf9\xaa\xb1\xc2َ\x8b\xa2\xd6\vt\xf4b\xce,\x8d\xf9\xbczz\xfe@.\x1d\x91\x15\x113q\xc1~\x81\xc3=o?*\xbd\xcce\xbe\xd5\xf0\xfc\xaei\xa5\x85rY\xfe\xd3\xde\xe9,L\xf2^\xfbީ\x17^.\x8f1\xf7t\x16*a\xf2\xea\x9e6\xe5\xd5=}uO_\xdd\xd3AyuO_\xdd\xd3W\xf7t\xbc\xbc\xba\xa7\x9d\xf2\xea\x9e&ۏ\x14\fW\xb4r;Q!\t\xab\xc4\xf4\x8d9\xb4g\xfa\xf2YJ\xfe,Ȓ\xec\xea\xcdxˑ\xb3@\x8bΐ\x98\x8e\xd1kҭqJ\x86\xc9\xe4\xceJ&x\xe1\xcfp\xd6& p\xf6Y\x9b\xcd$\x80g\xf0\x9d\xdf\x19\xf9\xae\xec\xd4\xe7?\a\xf3\x9c\xeb\x16ӹ\xa6I\x19\xa6Ik\x1b\xf38'\xe5\x90.\xcd\x1cM\xa2\xea\xd2,\xd1&\x03t\xa2\xe3\xa4\xdc\xd0Ӽϩ\xa1\xccf\x84Ƴ=\xa7\xc0\x8e\xe5\x81&\xe4xN\x80\xecf\x7f.v\x03f\xa5i\xb6\xc2\xd2\xdc\xcd\xf1;\x00C\x99\xb7\xceů!\xb3\xdfK&\xa5{NsJp\xf7y\xd0\x04\xa5%\xf8\x89c\x8ex\xc5\xfb^r\xfc\xf1%\x82\xab\xe7\b\xaf\x92\x1c\x91\x97\b\xb1^*\xc8Z\x1af-I\xdeH:\xbe\xf8\x12\xc1֒pk\x91Ϙ~<\xf1\xa5\x8e%\xbe@\xd8uvൈt\xa9\xc7\x0e\x17\x87_\t\xe3\x9b9fx\xe2\xa3%\x80\x8c\x1e/\x1c\x0f\xc1\x12 \x9e\x1c+\x9c\r\xc2R\xe6\xc10L\xfb\xeeC\x82ɉL\x8bv\xd3S\x93\x90\xd26\xba\xe7\x0f\xff%\x1e\xfaK\xdc\x06O\xc1>\xf1p\xdf\xf2C}\x89t>3<\x9b\xec:\xf1\xf0ޢ\x00\xed\xcc\x10m\x12\xe2\xd4a\xbd\xe9 mz\x01nxH\xef\fw\"A\xc2\x12\xaa,?h\xf7ݛ1J\xe7\xa0g\xf7\xb5\x96\x88\xf3\xac \x0f\xe2\xa8~\xff\x83\x1d\x9dp#*\xd6\xea\xee\x99\xc58\xaa\x9a{G2\xf6'!\xfdn=\nn\xc7'\xe9m\xbc\xb5\x0eS|_\xa7\xf5R\xfd\xe3\x01n\xc7\xce@\xc55\xed\xe3o\x8f.)Ȭ\xd9G\x9e\xed\x9b\x1e\" \xa9\xdf=7l\xa7t\xc9-\xbbl\xb6Bߺ\x0e\xf0\xef\xcb5c?\xaa&}\xa4s\xafX\x04\xaa\x11eU\x1c1bb\x97]0\xdf'8Q\x81\r\xf8ܪBd\x11\x0fq\xf4J:\xd7\xe0\xe4B\x1f\xba4/\xebdA\xc4b\x14l.\xfc\x85\x85\x83K\x85\xdd\xf5\xe2g.]\xf0J\xfc'\xbdՓ\xb66\xf6\xfevCՃT\xd1;?M\xf6\\#c[\x98V\xe8\xed\xc0\xc9\xf5\xe8B\x1d\xc9^m\xfe\x9c\x80H/e\x04?ë\xf1L\xa1\x16\xbb\xdd8,\xd7$X\\\x1e\x99\xf2o!\b\x9d\xaf*\xae\xa3\x9bz\xcc˃\xb9\xeaa\x18\xec\xf8\xdc\n֤Y;}\xf9\xa3[z4\x0f\x8f\x80\xd0f\xef\xb1\xeao\xa3\x13\xa5;\xf4\xfc\x1e\x9c\xa6\x0f.\xcf\x1eY~\x01\x9c\xa6]\xa6\x15Q1\xf2S4\x1d\xef\xd9W\x0f\x8d\xbfD\xfegu\x80\x0f\xd1U\xc4\xfe\xb3\x18\x83&#\tt\x01\xeaԵ\xe9m\xd6\\\xfc:\xebgȈ\v\xa8\xf8\x8b\xaf\x17\x8cϷ\x18\x7fK\x83\xee\xff\x0e\xb0'l\x1bN\xd9ۯ\x14n6\xea\xd2\xcfp\x1fL\x86\xa5\xc2\xc1U\xad\x11\x90\xb1\xa7D\x9e\x8bZVi\xfe\x00?)\xf7\xdaK\n\xb5\xfa-z\x0f\xfexg.d\x13\xfb\xb9\x16S\xe6~lC\x80\xed!\x83\x93\xeb\xe8\x11\xdb3o۷\xb6H\x18\xdc\xfd\xfdOn@\xf44\xc6\a\xff\xee\x05\xea]\x03H\xe90P\xd7h\x1b\xd7N{\xf5D\xf7\xb9w\x9f\xab\xe83\x1a\xfe\xd0{\xfa#L\x92\x14\xb5\xf6u\xbce\xc7[\xeeLשdA\xb5\x8b\xc2\xe2ƨL\x90\x83M\xbb#tj\xe4\xe5\ue71e\n\x94&\xe8X\x1b\xf8\xfc$A\x7f\t*\xd9ld쭍\xfe\xac9i\x18}g\xc3*r\xeb\a\xd5\xc7\xc29\xe9\td\xdc+-a\x9bG\x98\xe6\xfd\xa9S\xd2\xcd̫\xb8\x96\x1f\xf7IV\xe3\xcfᬚ\x17z.\x12(\xeb^\xa1\xe9\x03\x1e\x7f`\xcc=W\x93\xf1\xca\xd6:\xa8\x9cZ\xd3\x05\xdc\b\x04\xdc\xfd\xd4\xe7=1\xd6>H5\xc3\xcb\xf6a\xa7v\xe1{\xf6\xc9\xc8\x11\xfe5\x8f\x84E_\x14r!\xa3{\xd2q\x85\xf0\xcfc\xe7\xe8<@\x9c\xef\xdc;Q\t\xe3\xbd\vOe\x9d\x0e\xb8\x19\x06\x0e9\xf6\xf2\xd4K\x8e\x84\xae^\x9f\x19\xc3-\xd6i\xce.z\x91\xa1\x86\xe1\xca\xf6\xbb\x18\x13\xc6\x0f\xa3\xad\xd8'8\r>W\xec\xa3\xc4A\x9c\x12\xc0\x9d8\x83\x9c\x16\xfe\xc7\x1e\x8a\x9c\x1c\xe2\xa1iE\xc7\xfdF\xf4^_a\x0f\xaa\x0f\x92\x91\xe9\t\x9e\xa6\x8a;\xda7&\xa0\x7f\x10;\xb7+\x93\xe1\x98\xfexR#\xaa\x82'\xd5oL\xf5\x8e*\x87\x93\x8f\xf4\x9aX\xde\x11\x12\xefwv\xbf\xd4\xdb\xf6\x06}\xf6\xb7\xbf_\xfco\x00\x00\x00\xff\xff}\xef\x8a\xf1Mx\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4V\xcfo\xeb6\f\xbe\xe7\xaf \xb0û\xcc\xce\xebv\x19r\x1b\xba\x1d\x8am\x0fE\xf3л\"\xd3\tWY\xf2H*]\xf6\xd7\x0f\x92\xec&\xb1\x9d\xb5\x1b0\xdd\"\xf1\xc7Ǐ\xe4\xe7TU\xb52==#\v\x05\xbf\x01\xd3\x13\xfe\xa9\xe8\xd3/\xa9_~\x90\x9a\xc2\xfax\xb7z!\xdfl\xe0>\x8a\x86\xee\t%D\xb6\xf8\x13\xb6\xe4I)\xf8U\x87j\x1a\xa3f\xb3\x020\xde\a5\xe9Z\xd2O\x00\x1b\xbcrp\x0e\xb9ڣ\xaf_\xe2\x0ew\x91\\\x83\x9c\x83\x8f\xa9\x8f\x9f\xeb\xbb\xef\xea\xcf+\x00o:܀ \xa775\x1a\x85\U0004f222R\x1f\xd1!\x87\x9a\xc2Jz\xb4)\xfe\x9eC\xec7p~(\xfeC\xee\x82{\x9bCms\xa8\xa7\x12*\xbf:\x12\xfd\xe5\x96ů4X\xf5.\xb2qˀ\xb2\x81\x1c\x02\xeb\x97s\xd2\nD\xb8\xbc\x90\xdfGgx\xd1y\x05 6\xf4\xb8\x81\xec\xdb\x1b\x8b\xcd\n` $Ǫ\x06.\x8ew%\x9c=`gJ\x12\x80У\xff\xf1\xf1\xe1\xf9\xfb\xed\xd55@\x83b\x99zʹ.T\x06$``@\x01\x1a\xc0X\x8b\"`#3z\x85\x82\x12ȷ\x81\xbb\xdcɷ\xd0\x00f\x17\xa2\x82\x1e\x10\x9e3\xe5Ce\xf5\x9bIϡGV\x1a\xd9\x18\xdc\xceCvq;\xc1\xfa)\x95S\xac\xa0IӅ\x923\r\x94`30\x00\xa1\x05=\x90\x00c\xcf(\xe8u\x8a2\xf3ӂ\xf1\x10v\xbf\xa3\xd5z\xe0AR\xb3\xa2k\xd2P\x1e\x91\x15\x18m\xd8{\xfa\xeb-\xb6$BRRgt\x9c\x93\xf3!\xaf\xc8\xde88\x1a\x17\xf1[0\xbe\x81Μ\x801e\x81\xe8/\xe2e\x13\xa9\xe1\xb7\xc0\x98\xc9\xdc\xc0A\xb5\x97\xcdz\xbd'\x1d\x97ˆ\xae\x8b\x9e\xf4\xb4\xce{B\xbb\xa8\x81e\xdd\xe0\x11\xddZh_\x19\xb6\aR\xb4\x1a\x19צ\xa7*C\xf7y\xc1\xea\xae\xf9\x86\x87u\x94OWX\xf5\x94&K\x94\xc9\xef/\x1e\xf2B\xfcC\a\xd2:\x94\xf9(\xae\xa5\x8a3\xd1\xe9*\xb1\xf3\xf4\xf3\xf6+\x8c\xa9s3\xa6\xecg\xdeώrnA\"\x8c|\x8b\\\x9a\xd8r\xe8rL\xf4M\x1fȗ鲎\xd0O闸\xebHe\x9c\xddԫ\x1a\xee\xb3\xe2\xc0\x0e!\xf6\x8dQljx\xf0po:t\xf7F\xf0\x7fo@bZ\xaaD\xec\xc7Zp)\x96S\xe3\xc2\xda\xc5\xc3(s7\xfa\xb5\xb0\xdd\xdb\x1em\xea`\"1ySK6\xaf\a\xb4\x81\xc1,\xb9\xd4\x1fB\x92=\xfe%\x96AI\n\x9a\x89\xbe\xa4\xfd|\x1fͲ\x9c䗃\x11\x9c^N0=&\x9bi~G-ړuXB\x145\xc1\xf7\xa1\xa4\x83>v\xf3\x9c\x15|\xc1ׅ\xdbG\x0eIY\xb3\xae_\x9f\x1b\xb3\x01\xe5{\xb3'?+wZY\xb1\xca߰K\xa9\xbe\x10\xe8!\x10p\xf4>\xed\xedL!3\x90\xa9\x92\xcflH\xb1[@\xb3\x88\xe7\xc1\xb7!\x7f\xf0MJl\xb4\xec\x13\x0e\xcd\x1e\xf2\x14\\\v\x01o\xf7\xba\x9c\xb9x}\x88\xd0r\xf2\x97\xf4\xbf9'\xb9!\xc6\xc5\xdcUF\xb5\xf8\x902.1\xbe\xbc_\x03\xca\xe8\x9c\xd99܀r\x9c{\x17_\xc3lNө\x19G\xed+u(j\xba\xfe\xbd\x01\x9a9\xa4=y=\xa0\xbf\xb5\r\xf0j\xa6*\x7f\x95\x19v\xa7[\xae\xf7o\xff\x01\xe7+UFw\x03I\xbb+\xa5\x05\xce>D\xcab\xf7\xcaH/\xfe\xf3\x98\x11\xb2\xbd\xb4\x1d5\xe3j5\xc6?\"\xf3\x1anBXl\xf6\xec2\x87o.\xca\x13\rl\xf6c\xc1\x7f\a\x00\x00\xff\xff\xb1J-\xe7\xa6\v\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VA\x93\xdb6\x0f\xbd\xfbW`&\x87\xbdDr\xf2}\x97\x8e/\x9d̦\x87L\x93f'N\xf7N\x8b\x90\x8d\x9a\"U\x10\xd4\xc6\xfd\xf5\x1d\x90\xd2\xdak\xcb\xc9n\xa7\xd5\xc5c\n\x04\x1f\xde\xc3\x03UU\xd5\xc2\xf4t\x8f\x1c)\xf8\x15\x98\x9e\xf0\x9b\xa0\xd7\x7f\xb1\xde\xff\x14k\n\xcb\xe1\xedbOޮ\xe06E\t\xdd\x17\x8c!q\x83\xef\xb1%OB\xc1/:\x14c\x8d\x98\xd5\x02\xc0x\x1f\xc4\xe8rԿ\x00M\xf0\xc2\xc19\xe4j\x8b\xbeާ\rn\x129\x8b\x9c\x93OG\x0fo\xea\xb7\xff\xab\xdf,\x00\xbc\xe9p\x05Cp\xa9\xc3\xe8M\x1fwA\\hJ\xcez@\x87\x1cj\n\x8b\xd8c\xa3Gl9\xa4~\x05\xc7\x17%\xc5x|\x81~\x9f\xb3\xad\xc7l\x1f\xc7l9\xc0Q\x94_\xbf\x13\xf4\x91\xa2\xe4\xc0\xde%6\xee*\xb2\x1c\x13w\x81\xe5\xb7\xe3\xe9\x15\fѕ7\xe4\xb7\xc9\x19\xbe\xb6\x7f\x01\x10\x9b\xd0\xe3\n\xf2\xf6\xde4h\x17\x00#?9]5Q\xf3\xb6dlvؙr\x0e@\xe8ѿ\xbb\xfbp\xff\xff\xf5\x93e\x00\x8b\xb1a\xea%\xb3<_\"P\x04\x03\x13\x12x\xd8!#\xdcg>!J`\x8c#\xe8Ǥ\x00\x13\xfeX?.\xf6\x1czd\xa1\xa9\xf8\xf2\x9c\xf4\xd7\xc9\xea\x19\xae\x1b\x85^\xa2\xc0jca\x04\xd9\xe1T>ڱZ\b-Ȏ\"0\xf6\x8c\x11\xbd\x1c\x85<>\xa1\x05\xe3!l\xfe\xc0FjX#k\x1a\xd5&9\xab\xfd8 \v06a\xeb\xe9\xaf\xc7\xdc\x11$\xe4C\x9d\x11\x1c5?>\xe4\x05\xd9\x1b\a\x83q\t_\x83\xf1\x16:s\x00F=\x05\x92?ɗCb\r\x9f\x02#\x90o\xc3\nv\"}\\-\x97[\x92\xc9WM\xe8\xba\xe4I\x0e\xcbl\x11\xda$\t\x1c\x97\x16\at\xcbH\xdb\xcap\xb3#\xc1F\x12\xe3\xd2\xf4Te\xe8\xbe\xf8\xa0\xb3\xafxtb\xbcy\x82U\x0e\xdaEQ\x98\xfc\xf6\xe4E6\xc2w\x14P\x0f\x94F([K\x15G\xa2uI\xd9\xf9\xf2\xcb\xfa+LGg1\xce\xd9ϼ\x1f7ƣ\x04J\x18\xf9\x16\xb9\x88\xd8r\xe8rN\xf4\xb6\x0f\xe4%\xffi\x1c\xa1?\xa7?\xa6MG\xa2\xba\xff\x990\x8ajU\xc3m\x1e6\xb0AH\xbd5\x82\xb6\x86\x0f\x1enM\x87\xee\xd6D\xfc\xcf\x05P\xa6c\xa5\xc4>O\x82\xd39y\x1e\\X;5\xd88ޮ\xe85\xef\xe4u\x8f\xcd\x13\x03i\x16jitv\x1b\xf8\x8cW3\xf9|>_\xfd$|\xde\xe0P\x86|K\xdb\xf3U\x00cm\xbe\"\x8c\xbb\xbb\xba\xf7;\x84\xcd\xd4}\x9bO\xd2Fm\x03+\xa2\x81,r5\xd59\"I<\x16L\xe8l\xac/R^\xe1<\x97\xc2hUc\xe3.\x81>E\xf2\x18\x98\xef8C\xbeP~L\x90[\x8f\xbbq\xc6zAo\xf3P\xbf@\x13r\x0fG\xb4\xf0@\xb2+\xe6p\xa7\x97\xd4\xf3T\xd0g\x8f\x87\xb9\xe53\xec_w\xa8\x91e\x9c\"Dl\x18EqDtj^uf\r\xf0)\xc5l/3\x9b\x11tD\x90\x9dv\xef\xf1pI4\xfcH\xdc\xf1\xbe\xff1\xe4\x1b\xbd\x17'\xc0\x8c-2z\x99\xb5\xb8~b\xb0G\xc1\xecr\x1b\x9a\xa8\x06o\xb0\x97\xb8\f\x03\xf2@\xf8\xb0|\b\xbc'\xbf\xad\x94\xf0\xaa4B\\\xe6\xef\x86\xe5\xab\xfcs\xa5䯟\xdf\x7f^\xc1;k!\xc8\x0eYUk\x93\x9b\x1a\xed\xe4\xb6{\x9d'\xeekHd\x7f\xbe\xf9'\xbc\x84\xbe8\xe7\x19ܬs\xf7\x1f\xf4\xe6Π\x94\xa2uQ%0\xe8\xdcT\xb1\xbbQ\xcd2\x1f\xe6\x1aq´\t\xc1\xa1\xb9l=\x9d\xbe\xc4h/!Uz\xc2Kl\x06\xf0\xad:\nUu\xa6\xafJ\xb4\x91\xd0Qs\x16=\xf9\xfc\a\x96\xbc\x1b\xc3t<(\aӶ\xa9m\xcaWL\xfe\xa61[\xbc6\x16f\x14\x99/\xbcz<\xe0Y\x03]\x8c\xa4\xf8\U000917b7\x8d\x91\x9bq\xac7\x89\xb5\xfdǜ3\x9f?\xff\xceX\xefw&\xcex\xf3\x19\xa8\xeft\xe7$\x83\xa3\x16\x9bC\xe3\xb0$\x84\xd0\xce\xf4ދ \xeb\x83>us\x8d\xf8n0\xe4\xcc\xc6\xe1̻߽\xb9\xfa\xf6\xaa\xf8\xb3z^,F\xfdƱ+\x10N%\xf7\xd8e\xe3\xca\xdf\x01\x00\x00\xff\xff\xec\xa0\xe0\xa1k\r\x00\x00"), diff --git a/design/Implemented/velero-uploader-configuration.md b/design/Implemented/velero-uploader-configuration.md index d57ee56c0..08ac8150d 100644 --- a/design/Implemented/velero-uploader-configuration.md +++ b/design/Implemented/velero-uploader-configuration.md @@ -177,5 +177,54 @@ Roughly, the process is as follows: 4. Each respective controller within the CRs calls the uploader, and the WriteSparseFiles from map in CRs is passed to the uploader. 5. When the uploader subsequently calls the Kopia API, it can use the WriteSparseFiles to set the WriteSparseFiles parameter, and if the uploader calls the Restic command it would append `--sparse` flag within the restore command. +### Parallel Restore +Setting the parallelism of restore operations can improve the efficiency and speed of the restore process, especially when dealing with large amounts of data. + +### Velero CLI +The Velero CLI will support a --parallel-files-download flag, allowing users to set the parallelism value when creating restores. when no value specified, the value of it would be the number of CPUs for the node that the node agent pod is running. +```bash + velero restore create --parallel-files-download $num +``` + +### UploaderConfig +below the sub-option parallel is added into UploaderConfig: + +```go + type UploaderConfigForRestore struct { + // ParallelFilesDownload is the number of parallel for restore. + // +optional + ParallelFilesDownload int `json:"parallelFilesDownload,omitempty"` + } +``` + +#### Kopia Parallel Restore Policy + +Velero Uploader can set restore policies when calling Kopia APIs. In the Kopia codebase, the structure for restore policies is defined as follows: + +```go + // first get concurrrency from uploader config + restoreConcurrency, _ := uploaderutil.GetRestoreConcurrency(uploaderCfg) + // set restore concurrency into restore options + restoreOpt := restore.Options{ + Parallel: restoreConcurrency, + } + // do restore with restore option + restore.Entry(..., restoreOpt) +``` + +#### Restic Parallel Restore Policy + +Configurable parallel restore is not supported by restic, so we would return one error if the option is configured. +```go + restoreConcurrency, err := uploaderutil.GetRestoreConcurrency(uploaderCfg) + if err != nil { + return extraFlags, errors.Wrap(err, "failed to get uploader config") + } + + if restoreConcurrency > 0 { + return extraFlags, errors.New("restic does not support parallel restore") + } +``` + ## Alternatives Considered To enhance extensibility further, the option of storing `UploaderConfig` in a Kubernetes ConfigMap can be explored, this approach would allow the addition and modification of configuration options without the need to modify the CRD. diff --git a/pkg/apis/velero/v1/restore_types.go b/pkg/apis/velero/v1/restore_types.go index 2ad74d16e..eb37dd5d4 100644 --- a/pkg/apis/velero/v1/restore_types.go +++ b/pkg/apis/velero/v1/restore_types.go @@ -136,6 +136,9 @@ type UploaderConfigForRestore struct { // +optional // +nullable WriteSparseFiles *bool `json:"writeSparseFiles,omitempty"` + // ParallelFilesDownload is the concurrency number setting for restore. + // +optional + ParallelFilesDownload int `json:"parallelFilesDownload,omitempty"` } // RestoreHooks contains custom behaviors that should be executed during or post restore. diff --git a/pkg/builder/restore_builder.go b/pkg/builder/restore_builder.go index e7d5f8ef3..bad4327e9 100644 --- a/pkg/builder/restore_builder.go +++ b/pkg/builder/restore_builder.go @@ -171,9 +171,3 @@ func (b *RestoreBuilder) ItemOperationTimeout(timeout time.Duration) *RestoreBui b.object.Spec.ItemOperationTimeout.Duration = timeout return b } - -// WriteSparseFiles sets the Restore's uploader write sparse files -func (b *RestoreBuilder) WriteSparseFiles(val bool) *RestoreBuilder { - b.object.Spec.UploaderConfig.WriteSparseFiles = &val - return b -} diff --git a/pkg/cmd/cli/restore/create.go b/pkg/cmd/cli/restore/create.go index 398f49771..289decb47 100644 --- a/pkg/cmd/cli/restore/create.go +++ b/pkg/cmd/cli/restore/create.go @@ -99,6 +99,7 @@ type CreateOptions struct { ItemOperationTimeout time.Duration ResourceModifierConfigMap string WriteSparseFiles flag.OptionalBool + ParallelFilesDownload int client kbclient.WithWatch } @@ -151,6 +152,8 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) { f = flags.VarPF(&o.WriteSparseFiles, "write-sparse-files", "", "Whether to write sparse files during restoring volumes") f.NoOptDefVal = cmd.TRUE + + flags.IntVar(&o.ParallelFilesDownload, "parallel-files-download", 0, "The number of restore operations to run in parallel. If set to 0, the default parallelism will be the number of CPUs for the node that node agent pod is running.") } func (o *CreateOptions) Complete(args []string, f client.Factory) error { @@ -200,6 +203,10 @@ func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Facto return errors.New("existing-resource-policy has invalid value, it accepts only none, update as value") } + if o.ParallelFilesDownload < 0 { + return errors.New("parallel-files-download cannot be negative") + } + switch { case o.BackupName != "": backup := new(api.Backup) @@ -324,7 +331,8 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { Duration: o.ItemOperationTimeout, }, UploaderConfig: &api.UploaderConfigForRestore{ - WriteSparseFiles: o.WriteSparseFiles.Value, + WriteSparseFiles: o.WriteSparseFiles.Value, + ParallelFilesDownload: o.ParallelFilesDownload, }, }, } diff --git a/pkg/cmd/cli/restore/create_test.go b/pkg/cmd/cli/restore/create_test.go index 090697477..6a68018c4 100644 --- a/pkg/cmd/cli/restore/create_test.go +++ b/pkg/cmd/cli/restore/create_test.go @@ -85,7 +85,7 @@ func TestCreateCommand(t *testing.T) { allowPartiallyFailed := "true" itemOperationTimeout := "10m0s" writeSparseFiles := "true" - + parallel := 2 flags := new(pflag.FlagSet) o := NewCreateOptions() o.BindFlags(flags) @@ -108,6 +108,7 @@ func TestCreateCommand(t *testing.T) { flags.Parse([]string{"--allow-partially-failed", allowPartiallyFailed}) flags.Parse([]string{"--item-operation-timeout", itemOperationTimeout}) flags.Parse([]string{"--write-sparse-files", writeSparseFiles}) + flags.Parse([]string{"--parallel-files-download", "2"}) client := velerotest.NewFakeControllerRuntimeClient(t).(kbclient.WithWatch) f.On("Namespace").Return(mock.Anything) @@ -144,6 +145,7 @@ func TestCreateCommand(t *testing.T) { require.Equal(t, allowPartiallyFailed, o.AllowPartiallyFailed.String()) require.Equal(t, itemOperationTimeout, o.ItemOperationTimeout.String()) require.Equal(t, writeSparseFiles, o.WriteSparseFiles.String()) + require.Equal(t, parallel, o.ParallelFilesDownload) }) t.Run("create a restore from schedule", func(t *testing.T) { diff --git a/pkg/cmd/util/output/restore_describer.go b/pkg/cmd/util/output/restore_describer.go index ad5325fe1..8b805c354 100644 --- a/pkg/cmd/util/output/restore_describer.go +++ b/pkg/cmd/util/output/restore_describer.go @@ -178,10 +178,7 @@ func DescribeRestore(ctx context.Context, kbClient kbclient.Client, restore *vel d.Println() d.Printf("Preserve Service NodePorts:\t%s\n", BoolPointerString(restore.Spec.PreserveNodePorts, "false", "true", "auto")) - if restore.Spec.UploaderConfig != nil && boolptr.IsSetToTrue(restore.Spec.UploaderConfig.WriteSparseFiles) { - d.Println() - DescribeUploaderConfigForRestore(d, restore.Spec) - } + describeUploaderConfigForRestore(d, restore.Spec) d.Println() describeRestoreItemOperations(ctx, kbClient, d, restore, details, insecureSkipTLSVerify, caCertFile) @@ -199,10 +196,18 @@ func DescribeRestore(ctx context.Context, kbClient kbclient.Client, restore *vel }) } -// DescribeUploaderConfigForRestore describes uploader config in human-readable format -func DescribeUploaderConfigForRestore(d *Describer, spec velerov1api.RestoreSpec) { - d.Printf("Uploader config:\n") - d.Printf("\tWrite Sparse Files:\t%T\n", boolptr.IsSetToTrue(spec.UploaderConfig.WriteSparseFiles)) +// describeUploaderConfigForRestore describes uploader config in human-readable format +func describeUploaderConfigForRestore(d *Describer, spec velerov1api.RestoreSpec) { + if spec.UploaderConfig != nil { + d.Println() + d.Printf("Uploader config:\n") + if boolptr.IsSetToTrue(spec.UploaderConfig.WriteSparseFiles) { + d.Printf("\tWrite Sparse Files:\t%v\n", boolptr.IsSetToTrue(spec.UploaderConfig.WriteSparseFiles)) + } + if spec.UploaderConfig.ParallelFilesDownload > 0 { + d.Printf("\tParallel Restore:\t%d\n", spec.UploaderConfig.ParallelFilesDownload) + } + } } func describeRestoreItemOperations(ctx context.Context, kbClient kbclient.Client, d *Describer, restore *velerov1api.Restore, details bool, insecureSkipTLSVerify bool, caCertPath string) { diff --git a/pkg/cmd/util/output/restore_describer_test.go b/pkg/cmd/util/output/restore_describer_test.go index dff7aa96d..0c297204d 100644 --- a/pkg/cmd/util/output/restore_describer_test.go +++ b/pkg/cmd/util/output/restore_describer_test.go @@ -12,6 +12,7 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/builder" "github.com/vmware-tanzu/velero/pkg/itemoperation" + "github.com/vmware-tanzu/velero/pkg/util/boolptr" "github.com/vmware-tanzu/velero/pkg/util/results" ) @@ -181,3 +182,58 @@ func TestDescribePodVolumeRestores(t *testing.T) { }) } } +func TestDescribeUploaderConfigForRestore(t *testing.T) { + cases := []struct { + name string + spec velerov1api.RestoreSpec + expected string + }{ + { + name: "UploaderConfigNil", + spec: velerov1api.RestoreSpec{}, // Create a RestoreSpec with nil UploaderConfig + expected: "", + }, + { + name: "test", + spec: velerov1api.RestoreSpec{ + UploaderConfig: &velerov1api.UploaderConfigForRestore{ + WriteSparseFiles: boolptr.True(), + ParallelFilesDownload: 4, + }, + }, + expected: "\nUploader config:\n Write Sparse Files: true\n Parallel Restore: 4\n", + }, + { + name: "WriteSparseFiles test", + spec: velerov1api.RestoreSpec{ + UploaderConfig: &velerov1api.UploaderConfigForRestore{ + WriteSparseFiles: boolptr.True(), + }, + }, + expected: "\nUploader config:\n Write Sparse Files: true\n", + }, + { + name: "ParallelFilesDownload test", + spec: velerov1api.RestoreSpec{ + UploaderConfig: &velerov1api.UploaderConfigForRestore{ + ParallelFilesDownload: 4, + }, + }, + expected: "\nUploader config:\n Parallel Restore: 4\n", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + d := &Describer{ + Prefix: "", + out: &tabwriter.Writer{}, + buf: &bytes.Buffer{}, + } + d.out.Init(d.buf, 0, 8, 2, ' ', 0) + describeUploaderConfigForRestore(d, tc.spec) + d.out.Flush() + assert.Equal(t, tc.expected, d.buf.String(), "Output should match expected") + }) + } +} diff --git a/pkg/uploader/kopia/snapshot.go b/pkg/uploader/kopia/snapshot.go index 1a0956256..a924fe7ac 100644 --- a/pkg/uploader/kopia/snapshot.go +++ b/pkg/uploader/kopia/snapshot.go @@ -411,6 +411,8 @@ func Restore(ctx context.Context, rep repo.RepositoryWriter, progress *Progress, IgnorePermissionErrors: true, } + restoreConcurrency := runtime.NumCPU() + if len(uploaderCfg) > 0 { writeSparseFiles, err := uploaderutil.GetWriteSparseFiles(uploaderCfg) if err != nil { @@ -419,9 +421,17 @@ func Restore(ctx context.Context, rep repo.RepositoryWriter, progress *Progress, if writeSparseFiles { fsOutput.WriteSparseFiles = true } + + concurrency, err := uploaderutil.GetRestoreConcurrency(uploaderCfg) + if err != nil { + return 0, 0, errors.Wrap(err, "failed to get parallel restore uploader config") + } + if concurrency > 0 { + restoreConcurrency = concurrency + } } - log.Debugf("Restore filesystem output %v", fsOutput) + log.Debugf("Restore filesystem output %v, concurrency %d", fsOutput, restoreConcurrency) err = fsOutput.Init(ctx) if err != nil { @@ -436,7 +446,7 @@ func Restore(ctx context.Context, rep repo.RepositoryWriter, progress *Progress, } stat, err := restoreEntryFunc(kopiaCtx, rep, output, rootEntry, restore.Options{ - Parallel: runtime.NumCPU(), + Parallel: restoreConcurrency, RestoreDirEntryAtDepth: math.MaxInt32, Cancel: cancleCh, ProgressCallback: func(ctx context.Context, stats restore.Stats) { diff --git a/pkg/uploader/provider/restic.go b/pkg/uploader/provider/restic.go index 041f6eab5..5878461f4 100644 --- a/pkg/uploader/provider/restic.go +++ b/pkg/uploader/provider/restic.go @@ -246,5 +246,9 @@ func (rp *resticProvider) parseRestoreExtraFlags(uploaderCfg map[string]string) extraFlags = append(extraFlags, "--sparse") } + if restoreConcurrency, err := uploaderutil.GetRestoreConcurrency(uploaderCfg); err == nil && restoreConcurrency > 0 { + return extraFlags, errors.New("restic does not support parallel restore") + } + return extraFlags, nil } diff --git a/pkg/uploader/provider/restic_test.go b/pkg/uploader/provider/restic_test.go index 06486a27f..c653ee1b5 100644 --- a/pkg/uploader/provider/restic_test.go +++ b/pkg/uploader/provider/restic_test.go @@ -434,6 +434,13 @@ func TestParseUploaderConfig(t *testing.T) { }, expectedFlags: []string{}, }, + { + name: "RestoreConcorrency", + uploaderConfig: map[string]string{ + "Parallel": "5", + }, + expectedFlags: []string{}, + }, } for _, testCase := range testCases { diff --git a/pkg/uploader/util/uploader_config.go b/pkg/uploader/util/uploader_config.go index 12ff54ed9..5584ffbce 100644 --- a/pkg/uploader/util/uploader_config.go +++ b/pkg/uploader/util/uploader_config.go @@ -27,6 +27,7 @@ import ( const ( ParallelFilesUpload = "ParallelFilesUpload" WriteSparseFiles = "WriteSparseFiles" + RestoreConcurrency = "ParallelFilesDownload" ) func StoreBackupConfig(config *velerov1api.UploaderConfigForBackup) map[string]string { @@ -42,6 +43,10 @@ func StoreRestoreConfig(config *velerov1api.UploaderConfigForRestore) map[string } else { data[WriteSparseFiles] = strconv.FormatBool(false) } + + if config.ParallelFilesDownload > 0 { + data[RestoreConcurrency] = strconv.Itoa(config.ParallelFilesDownload) + } return data } @@ -68,3 +73,15 @@ func GetWriteSparseFiles(uploaderCfg map[string]string) (bool, error) { } return false, nil } + +func GetRestoreConcurrency(uploaderCfg map[string]string) (int, error) { + restoreConcurrency, ok := uploaderCfg[RestoreConcurrency] + if ok { + restoreConcurrencyInt, err := strconv.Atoi(restoreConcurrency) + if err != nil { + return 0, errors.Wrap(err, "failed to parse RestoreConcurrency config") + } + return restoreConcurrencyInt, nil + } + return 0, nil +} diff --git a/pkg/uploader/util/uploader_config_test.go b/pkg/uploader/util/uploader_config_test.go index 2666fefe2..593bce4f0 100644 --- a/pkg/uploader/util/uploader_config_test.go +++ b/pkg/uploader/util/uploader_config_test.go @@ -78,6 +78,16 @@ func TestStoreRestoreConfig(t *testing.T) { WriteSparseFiles: "false", // Assuming default value is false for nil case }, }, + { + name: "Parallel is set", + config: &velerov1api.UploaderConfigForRestore{ + ParallelFilesDownload: 5, + }, + expectedData: map[string]string{ + RestoreConcurrency: "5", + WriteSparseFiles: "false", + }, + }, } for _, tc := range testCases { @@ -180,3 +190,53 @@ func TestGetWriteSparseFiles(t *testing.T) { }) } } + +func TestGetRestoreConcurrency(t *testing.T) { + testCases := []struct { + Name string + UploaderCfg map[string]string + ExpectedResult int + ExpectedError bool + ExpectedErrorMsg string + }{ + { + Name: "Valid Configuration", + UploaderCfg: map[string]string{RestoreConcurrency: "10"}, + ExpectedResult: 10, + ExpectedError: false, + }, + { + Name: "Missing Configuration", + UploaderCfg: map[string]string{}, + ExpectedResult: 0, + ExpectedError: false, + }, + { + Name: "Invalid Configuration", + UploaderCfg: map[string]string{RestoreConcurrency: "not_an_integer"}, + ExpectedResult: 0, + ExpectedError: true, + ExpectedErrorMsg: "failed to parse RestoreConcurrency config: strconv.Atoi: parsing \"not_an_integer\": invalid syntax", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + result, err := GetRestoreConcurrency(tc.UploaderCfg) + + if tc.ExpectedError { + if err.Error() != tc.ExpectedErrorMsg { + t.Errorf("Expected error message %s, but got %s", tc.ExpectedErrorMsg, err.Error()) + } + } else { + if err != nil { + t.Errorf("Expected no error, but got %v", err) + } + } + + if result != tc.ExpectedResult { + t.Errorf("Expected result %d, but got %d", tc.ExpectedResult, result) + } + }) + } +}