From d0d143e119dd66a7346cd5f4aa66cc1bc4a7d5c9 Mon Sep 17 00:00:00 2001 From: Thejas Babu Date: Thu, 23 Jul 2020 00:10:39 +0530 Subject: [PATCH] Add StartTimestamp and CompletionTimestamp in Restore Status (#2748) Signed-off-by: thejas --- changelogs/unreleased/2748-thejasbabu | 2 + config/crd/bases/velero.io_restores.yaml | 13 ++++ config/crd/crds/crds.go | 2 +- pkg/apis/velero/v1/restore.go | 13 ++++ pkg/apis/velero/v1/zz_generated.deepcopy.go | 8 +++ pkg/builder/restore_builder.go | 14 +++++ pkg/cmd/util/output/restore_describer.go | 14 +++++ pkg/cmd/util/output/restore_printer.go | 4 ++ pkg/controller/restore_controller.go | 5 ++ pkg/controller/restore_controller_test.go | 69 ++++++++++++++------- 10 files changed, 121 insertions(+), 23 deletions(-) create mode 100644 changelogs/unreleased/2748-thejasbabu diff --git a/changelogs/unreleased/2748-thejasbabu b/changelogs/unreleased/2748-thejasbabu new file mode 100644 index 000000000..493e0671a --- /dev/null +++ b/changelogs/unreleased/2748-thejasbabu @@ -0,0 +1,2 @@ +Adds Start and CompletionTimestamp to RestoreStatus +Displays the Timestamps when issued a print or describe \ No newline at end of file diff --git a/config/crd/bases/velero.io_restores.yaml b/config/crd/bases/velero.io_restores.yaml index ee3cc3aee..d4866666f 100644 --- a/config/crd/bases/velero.io_restores.yaml +++ b/config/crd/bases/velero.io_restores.yaml @@ -143,6 +143,13 @@ spec: status: description: RestoreStatus captures the current status of a Velero restore properties: + completionTimestamp: + description: CompletionTimestamp records the time the restore operation + was completed. Completion time is recorded even on failed restore. + The server's time is used for StartTimestamps + format: date-time + nullable: true + type: string errors: description: Errors is a count of all error messages that were generated during execution of the restore. The actual errors are stored in object @@ -162,6 +169,12 @@ spec: - PartiallyFailed - Failed type: string + startTimestamp: + description: StartTimestamp records the time the restore operation was + started. The server's time is used for StartTimestamps + format: date-time + nullable: true + type: string validationErrors: description: ValidationErrors is a slice of all validation errors (if applicable) diff --git a/config/crd/crds/crds.go b/config/crd/crds/crds.go index 8bf3926cd..4e2136a61 100644 --- a/config/crd/crds/crds.go +++ b/config/crd/crds/crds.go @@ -36,7 +36,7 @@ var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4Y\xdfs۸\xf1\u007f\xd7_\xb1\x93{\xf0\xf7fB\xea\x92\xfbN\xa7\xa3\xb7\x8b\xddt\xdc\xde9\x9eȗ\x97L\x1eV\xc4JD\r\x02(\x16\x94\xa2v\xfa\xbfw\x16 %Q\xa2e\xf9\xdaK\xf9b\x13\\,\xf6\xf7~\x16\x9a\x14E1A\xaf?Q`\xed\xec\f\xd0k\xfa\x1a\xc9\xca\x1b\x97\x8f\u007f\xe4R\xbb\xe9\xfa͂\"\xbe\x99\x94+\xd74\xad\xd5q;M\x01\xa9\x17mt\x81\xa7\x8a\xd6d\xa6\xacW\x05\x86\xaa֑\xaa\xd8\x06\x9a\xa2\xd7E\x12ܦH.\x1b\xf5]\xe8➯\x0e$\x8d[q\x1bǠ\xedj\xb7\x9c\x02\xecI\xbbK\x80\x81f\xc0n[\x96\u007fo^Y\x12\xab|\xfc\xd3\xfc\x01\xfaC\x93\v\x866O\xd6\xdeo\xe3\xbd\xe1\xc5P\xda.)d\xc7-\x83k\x12G\xb2\xca;mcz\xa9\x8c&;4:\xb7\x8bFG\xf1\xf4\xdf[\xe2(\xfe)\xe1:%4,\bZ\xaf0\x92*\xe1\xd6\xc256d\xae\x91\xe9w7\xbbX\x98\v1\xe9\xf3\x86?\xacCC\xc2l\xad\xddr_(F=t\x94\xfbsO\x95\xf8K\x8c&\xfb\xf4RW)\x05`\xe9\x02\xe01yy\xc0v,5\xe5\xc9Ua\x1e]\xc0\x15\xfd쪃$\u007fB\xa6wc;z\xa9\xa4\xb6\xe54\xa5\x8e5p\xa6\xf1\xacE\xe4YJ\x97\xb9\xc7X?{\xea\xd5\xed2\x1f\x93*Rt\x80\xe05U4(\xed\xa0-GB\x95\x17GX\x02H\xe2\x06\xea\xe8_\xe7\xfaӕ\xb9};\x10[\x03\xe6\xfe\x06\u007f\x99\u007f\xb8\x9b\xfe\xd9eYGybU\x11\v\x1b\x8cԐ\x8d\xaf\x81۪\x06dQA\aRs\xf9R6h\xf5\x928\x96\xdd\t\x14\xf8\xf3\xdb/c6\x03x\xef\x02\xd0Wl\xbc\xa1נ\xb3\x95w\x05\xb5\x0f\x10\xcd\xd9\x10;~\xb0ѱ\xd6㊣\x04R\xa7\xf0&)\x1a\xf1\x91\xc0u\x8a\xb6\x04F?\xd2\f^I\t9\x10\xf1\x9f\x92\r\xffz5\xca\xf3\xffr\x92\xbe\x12\x92WY\xb0]\xcfr\x0eO_\xacN\x8f+/\xedJW\xf3\x0e\xf9\x1c\uf514\xd8Ժ\xaa\xfb!a_=Gs\xa4A\x95K.\xda\xed\xef\x1e\xb6b\xc86\x88<ۢ\x1bC\v\xb4J\xfeg\xcdQ\xd6_l\xb9V_\x90\xa4\xbf\xde\xde|\x9b`n\xf5\x8b3r\x14\x10\xe7\x98\xf0\xeeV\x89\xf9\x96\x9a\xc2Y8\xf5q@\xda\x03\xbb\x11$\xb9\xa3\xb9\x18\xc9E\\\x9d\x00(T*]4\xa0\xb9?\x03\xb2\xce\xe8<\x10\xfe\x01W\f\x18\b\x10\x1a\xf4\xe2\xa7G\xda\x16\xb9I{\xd4\xd2c\xa5\x8dvxeA\x80\xde\x1b=\xd2N\xbbV\xdc\xc1\xc5\x0ey\xcbX\x8b+\x1e\xd7w\xc4\xeay\xf7Yk\xe7\xf1b\f>wGg\\\xb2\x83\xd0\xd1\xed\x81\xeai\xfc\x9e\x00\xd7'\xec&S\xa0\xa0\xabCъ\xf1\xd1e@!\x90~\xb0\xe0\x9d\x1a\xbc\x0f\xe3l\xf0)\xeb\xf3\xec\xf4\x161\xb6|\xf1\xfc\x96\xa8{\xeb\xe5z\x10;\x1e\t+\xfc\x96\t\xaer\x82\x1d\x87\xd7T\xe7\\x}J\x9f.D\x82\xcabE\xddH\xbd~\xc94y>\xec6\x00.\\\x1bw\x03\xe2 ů\xb8\x8b\x9e˧ӑ\x11l\x18\xb2(\x80\x99;\xf8hL\xdaq\x98\xd6\xfbK\xd4$ς\xc4-\xffi\x86\x03\xf8\x1a\xf9\xbcq\xee\x85b,yv5\xe8L\xf6@\x9a\b\xdb\xe6\xf8\x84\x02\xeehs\xb2vk\xef\x83[\x05\xe2\xe3\xd0(\xfa\xf89Q\xb6\x80\xf7)\xce/ַ;\xe0\xbc\xca\x1d\x11\xd4\xce\xf4\xe9\xe9\"\x1a\xb0m\xb3\xa0 z/\xb6\x91xX\x84Og\xfe4E\xec\x8dv\xb0\xbb\xbfB\xc8|\xba\xa1\xa8B\x9bn\xd9$g\xa2\x03\xa5\xd9\x1b<\x9d\x8az\x15\x12\x92\x90\x94\x91\x94\xdeGk\x9f\xa6\x9eB\xfa\xf4\x92[\x8a$͍\xb3\xa3\x18\xb7\xcfOm\xe3\x1f\xfe\xffIġm\xa4ՠ\xa8w_ŀ\xef\x84\xff\u007f\x9b\xf7\x93\x8d\x95-z\xae]\xbc\xbd9\xeb\xed\xf9\x8e\xac\x8f\xf2=hI\xb5+\xdd\xfbuD\xbdˇ--?9\f.N=\x8e\x18\xe2e\xcdc> }\xa6o$\xbe\xa4J\x98\x93ǀ\xf140\xd3}\xf0\xf5\xf1\xaf,\xaf\x81u\xba\x16\x13\xec\x93\xc1P\x1euYډ@;\x17r\xac\x9er\x1c4\x82A\xe1\x1f\x8a\xfe-j\xfeH<\x1c-\xed\u007frz\xb3\u007fKqYt?1\xa5\x0f\x9dZ\xea\xe0\xf0\xeeV\xb5[\xd9\xc3\x10\xac\x04\xb2\x93\xba;\xfe\x91\xe9U\xbe*\xe9\u007f5J\xaf\x95\xb3\x19\xcd\xf2\f>\u007f\x99@w\xd7\xfa\xa9\x97C\x16\xff\x1d\x00\x00\xff\xff\x81\x16-\x05\x9e\x1b\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4Y_s۸\x11\u007fק\xd8\xf1=\xb87\x13R\x97\\\xa7\xd3\xd1\u06dd\xddt\xdc\xde9\x9eȗ\x97L\x1e b)\xa2&\x01\x16\xbb\x90\xacv\xfa\xdd;\v\x90\x92(Q\xb2\x9c\xe9\xa5zI\b,\x16\xbf\xfd\xed\x1f,\xe0I\x96e\x13՚O\xe8\xc98;\x03\xd5\x1a|f\xb4\xf2E\xf9ӟ)7n\xbaz\xbb@Vo'O\xc6\xea\x19\xdc\x04b\xd7|Dr\xc1\x17x\x8b\xa5\xb1\x86\x8d\xb3\x93\x06Yi\xc5j6\x01P\xd6:V2L\xf2\tP8\xcb\xde\xd55\xfal\x896\u007f\n\v\\\x04Sk\xf4q\x87~\xff\xd5\x0f\xf9\x8f\xf9\x0f\x13\x80\xc2c\\\xfeh\x1a$VM;\x03\x1b\xeaz\x02`U\x833h\x9d^\xb9:4\xe8\x91\xd8y\xa4|\x855z\x97\x1b7\xa1\x16\v\xd9u\xe9]hg\xb0\x9bH\x8b;Dɚ\a\xa7?E=\x1f\x93\x9e8U\x1b⿏N\xffb\x88\xa3H[\a\xaf\xea\x11\x1cq\x96\x8c]\x86Z\xf9\xe3\xf9\t@\xeb\x91Я\xf07\xfbd\xddھ7Xk\x9aA\xa9j\x92i*\\\x8b3\xb8\x17\xa4\xad*PO\x00V\xaa6:\U00091c3b\x16\xedO\x0fw\x9f~\x9c\x17\x156*\r\x8afעgӛ(\xbf=\xefn\xc7\x004R\xe1M\x1b5µ\xa8J2\xa0şH\xc0\x15B\xe7\x15\xd4@q\x1bp%pe\bE\xe5\b-8[o\xc6\xd1:\xa8\xd4\n\x81\\\x83\xb0ƺ\xceR\xaf\xa0a\xad6b\u007f\xef.\x890\x05\xad\xf2<\xec\x06F\xb5>~\xb8\xfd0K\xa8$\x84\x96\xb1\x8e\xc9)S\x1a9\xf3\xe5\xb0O'\x97\xc4d\xa4#\xa4\xe0`\aE\xa5\xecHY\x83\xd84Dv\xcb gI~\xfd\xdal=<\xb6\xfb\xdf\xc8\xf1}X\x18\xfeO\x87\xe0Ef\xc5\xd6\xf9E\xb3\xee\xf7\xe2\xf9\xacY\xd2\xc4{\x8b\x8c\xd12\xed\n\x12\xa3\nl\x99\xa6n\x85~ep=];\xffd\xec2\x93@\xccR$\xd04\xb6\xe1\xd3\xef\xe2?_eE\xec\x8c/3%\x8a~\v{d\x1f\x9a\xbeڜ\xbe\xaf\xbb\xf4T\xba\x9ew\x8d\xc7\xe1JI\x89ue\x8a\xaao\xd2w\xd5s4G\x1a\xa5S\xc9Uv\U000fb1ed\x10\x19\xbc\xe0\xd9d\xdd]0SV\xcb\xff\xc9\x10\xcb\xf8\xab\x99\v\xe6\x82$\xfd\xed\xee\xf6\xdb\x04s0\xaf\xce\xc8ц4\xc5D\xeb\xee\xb4\xd0W\x1a\xf4g\xbb\xa9\x8f\x03Ѿ\v\x1c\xe9\xe3\xb62\x177rdUK\x95\xe3\xbb۳\b\xe6[\xb1~\xf7\x1d\xe5]\xfb\xd6k\x92\x10=ӷ\x9dD\x92ԜE\x91\xfa\xee\xb1.\xb8Ð:\x868\"\x1d\xe8W!\x91됴9\xfbH\xb2\xf1\x0e~ \xd1:=\xf8\x1e\xfaw0\xb5#}0\x9c\x8cx\xf12Ê\x03]~\x9d\x89\xe2=g)?\xb9S\x12\xcf\uebfa\xd0\x14N\x9a\xb9\xe1\xe3\xcd9\xcf\xdd\x1c\xcb\xc7\x17\x02\xaf\x13.6\r\xc6\xdbBD\x00kE\xfd\x16\xc7~\x83=mia\xac\x84\xa2\ful\xb6\xa4\x0f,\x95\xa9Q\xc3\xf6\xe9\b\x1e\xe5>\x17\xaf\xcc\xd7ǵ\xb2W\x13\bu\xbc\xe7\x8d\x00>\\U:\xdf(\x9e\x81\\\x933Qp0oC]\xabE\x8d3`\x1f\x0e'O\xa6A\x83Djy>\x0f~M2\xe9\x86\xd5-\x00\xb5p\x81\xb7W\xac.!:\xf3\xaf\xa9\xf3\xf8\xe5\x17\xbcJ\xd1y\x10\x0f\"1\x16Wۤ<\x17X\x10o/\xa19\xdc\"\x83{\\\x1f\x8d\xdd\xd9\a\xef\x96\x1e\xe9\xd0\aY﨣\xf6;\x83\xf71\x02.6\xb8\xdb\xe0\xbc͝\x10T\xae\xee#ױ\xaa\xc1\x86f\x81^\f_l\x18\xa9g\xa0O\xf4\xe3\x1bj\xecyw\xbc\xed\xd6\xf7\xd5*)\xea:\xf8B\xd9\xf8$#\xd1\xc9\x0e\xb4\xa1\xb6V\xc7-|oC<\xf6$8%Cvq\xd1g\x97\xa4t\x9c{͝:¹uv\xb4#\xebS\xc1X\xfe\xd3\x1fO\x9e\x8f\xc62.\a\xa5\xb0\x9b\x15\n\u007f\x16\xfd\xffk\xdd'\x0f_b\xe5\xf9\xb2\xd25\x1f\x88\xbeT\xb5\xa2ⱚ\xb5_~\x8e\xcb\xcdp\x93oQiF\xa89\x18\xda=ؿ\xdd}E\x17e\xdd\x03}\x9c\x80d\x96\xdeۼ{\x8c\xeaFv\a\x96*\xa4\xd7B}\u007f\xf8B\u007fu5xp\x8f\x9f\x85\xb3ڤ\xbf.\xc0\xe7/\x13螨>\xf58d\xf0\xbf\x01\x00\x00\xff\xff\x98\xaaEc\xdc\x18\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4W\xcdn\xe36\x10\xbe\xfb)\x06\xdb\xc3^*y\x83\xbd\x14\xba\xb5i\x17\b\x9a\x04\vg\x9bK\xd1\x03E\x8d\xeci(\x92\xe5\f\x9d\xbaO_\x90\x92lٖ\xbd\xc1\x02\xab\x1b\x87Ùo\xbe\xf9!\xb5(\x8ab\xa1<=c`r\xb6\x02\xe5\t\xff\x15\xb4i\xc5\xe5\xcbO\\\x92[noj\x14u\xb3x!\xdbTp\x1bY\\\xb7Bv1h\xfc\x15[\xb2$\xe4\xec\xa2CQ\x8d\x12U-\x00\x94\xb5NT\x12sZ\x02hg%8c0\x14k\xb4\xe5K\xac\xb1\x8ed\x1a\f\xd9\xc3\xe8\u007f\xfb\xa1\xfcX~X\x00\xe8\x80\xf9\xf8\x17\xea\x90Eu\xbe\x02\x1b\x8dY\x00X\xd5a\x05\x01YH\a\xf4\x8eI\\ \xe4r\x8b\x06\x83+\xc9-أNn\xd7\xc1E_\xc1a\xa3?=@\xea\xc3YeC\xab\xd1\xd0.o\x19b\xf9}v\xfb\x9eX\xb2\x8a71(3\a$o3\xd9u4*\x9c)$\a> c\xd8\xe2\x1f\xf6źW\xfb\x89\xd04\\A\xab\f\xe3\x02\x80\xb5\xf3X\xc1c\x82\xea\x95\xc6f\x01\xb0U\x86\x9a\xccH\x0f\xdey\xb4?\u007f\xbe{\xfe\xf8\xa47ة^\x98,;\x8fAh\x8c1}\x93\xfc\xeee\x00\r\xb2\x0e\xe4\xb3Ex\x9fL\xf5:Ф\x8c\"\x83l\x10\x86\xbc`\x03\x9c݀kA6\xc4\x100\xc7`\xfb\x1cO\xccBRQ\x16\\\xfd7j)\xe1)\xc5\x19\x18x\xe3\xa2iR\x19l1\b\x04\xd4nm\u9ffde\x06q٥Q\x82\x03\xc5\xe3GV0Xe\x12\t\x11\u007f\x04e\x1b\xe8\xd4\x0e\x02&\x1f\x10\xed\xc4ZV\xe1\x12\x1e\\@ ۺ\n6\"\x9e\xab\xe5rM2V\xb4v]\x17-\xc9n\x99\xeb\x92\xea(.\xf0\xb2\xc1-\x9a%ӺPAoHPK\f\xb8T\x9e\x8a\f\xdc\xe6\x82.\xbb\xe6\x870\x94?\xbf\x9f \x95]J\x1bK \xbbދs\x95]\xe4=\x15\x19\x10\x83\x1a\x8e\xf5\xf8\x0f\xf4&Qbe\xf5\xdb\xd3\x17\x18\x9d\xe6\x14\x1cs\x9e\xd9>\x1c\xe3\x03\xf1\x89(\xb2-\x86>qmp]\xb6\x88\xb6\xf1\x8e\xac\xe4\x856\x84\xf6\x98t\x8euG\x922\xfdOD\x96\x94\x9f\x12ns_C\x8d\x10}\xa3\x04\x9b\x12\xee,ܪ\x0eͭb\xfc\xee\xb4'\x86\xb9H\x94~\x9d\xf8\xe98:V\xec\xd9ڋ\xc7i1\x9b\xa1\xd3\xfe\u007f\xf2\xa8S\xc2\x12k\xe9 \xb5\xa4s\x0f@\xeb\x02\xa83\xfdrbx\xae9\xd3W+\xfd\x12\xfd\x93\xb8\xa0\xd6x\xef\xf4\xa4\xcd/\xa0\xfae\xee\xc4\b+\x8d\xb8\xbeQq^\xf1\xc42\x80l\x94L:T\x14\xd9}\x9b\xcf\xc4q\x91\xf2L\xbbJ\xedj\x95\xd5\xf8)\u05ceջ\xab\xb1<\xcc\x1cH\xa1l\xdc+\xb8V\xd0NM\x8e(k<\v\"D\xfbf\x90\xfdL\xbekRi\xb5\x84\xe1*\xc0Չ\xf2\xc8s\x1b\x8d\x19,\x15\xdau^\t\xd5\x06\xc7Fn]8\x83H\xbd\x8d]\xdf\xd5\xdf\xc6\xef֙\xd8\xe1\xfen\xb8\x8a\xfc\xf9XwZ \xbd`\x00\x91B\x80p|\x05N\xbf\xa1&\x18\xbck\x06\x00C\xd1r\x8a\xf3\x8d\xd8Sr)\xe0\xd14,\xe6\x8b\xffHc\xae\xa2\x8e\x14N\xb3y\xb4y\xc2\xd7W\x87\x81(\x89\xfc\xf6q\x90\xd5Gbu\f\x01\xad\fF\xf2M\xf8M\x03\xc1(\x96I[\xa47\xd0\xd5<ߟ돐\x92)\x90$\x98vѫ\xe2\xb9~i]\xe8\x94T\x90F{\x91\x0e\x9d\xec\xa7\x17\x98\xaa\rV !\x9en^\x9e\bȬ\xd6\xd7#x\xe8u\xfa\xabp8\x00\xaavQ.\x10\x9b/\xc5+\xd4^E\xe47\x8a\xaf\xe3\xf9\x9c4\xe6Ҋou\x8e6v\xa7.\nx\xc4\xd73\xd9\nUs\xdas\x05<:\x99۸\x10\xd3L-\x9f\x88\x0eO\xec\x9b\xc3*\xd7]1<\xa9\xf3\x06@~\x996\x93\x14sߛ\x83\xe4\xd0 Jk\xf4\x82\xcd\xe3\xe9\x93\xfaݻ\xa3\x17r^jg\x1b\xea\xff\a\xe0Ͽ\x16\xbdUl\x9eG\x1cI\xf8\u007f\x00\x00\x00\xff\xfflC\xbf\xee\x8e\f\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xd4YMo3\xb7\xf1\xbf\xebS\f\xfc?<\t`\xad\xf3 \x97?ts\xfd8\x80\x91\xc45\xec\xa7\xee\xa1\xe8\x81\xda\x1dI\xac\xb9\xe4\x86/\xb2բ߽\x98!\xb9/\xda\xd5JA\x1b\xa4\u074b \xeep8\xf3\xe3\xbc\xefb\xb9\\.D#_\xd1:i\xf4\nD#\xf1ã\xa6\u007f\xaex\xfb\u007fWHs\xb3\xff\xbcF/>/ޤ\xaeVp\x17\x9c7\xf53:\x13l\x89_p#\xb5\xf4\xd2\xe8E\x8d^T\u008b\xd5\x02@hm\xbc\xa0eG\u007f\x01J\xa3\xbd5J\xa1]nQ\x17oa\x8d\xeb U\x85\x96O\xc8\xe7\xef\xbf+\xbe/\xbe[\x00\x94\x16y\xfbWY\xa3\xf3\xa2nV\xa0\x83R\v\x00-j\\\x81E\xe7\x8dEW\xecQ\xa15\x854\v\xd7`I\x87m\xad\t\xcd\n\xba\x17qO\x12$*\xf1\x1c\xb7\xf3\x8a\x92\xce\xff\xd8_\xfdI:\xcfo\x1a\x15\xacP\xdda\xbc\xe8\xa4\xde\x06%l\xbb\xbc\x00h,:\xb4{\xfc\x93~\xd3\xe6]\xff QUn\x05\x1b\xa1\x1c\xbdv\xa5ip\x05\x8f$E#J\xac\x16\x00{\xa1d\xc5*F\xb9L\x83\xfa\xf6\xe9\xe1\xf5\xfb\x97r\x87\xb5\x88\x8b\x00\x15\xba\xd2ʆ\xe9\xb2| \x1d\bxe\xfdH\b\xbe\b\xf0;\xe1\xc1\"\x8b\xa2\xbd\x03\xbfC\x10M\xa3dɧ\x80\xd9$\x96\xd0\xeeq\xb0\xb1\xa6\xeex\xadE\xf9\x16\x1a\xf0\x06\x04xa\xb7\xe8\xe1ǰF\xabѣ\x83R\x05\xe7\xd1\x16\x89McM\x83\xd6\xcb\f,==Sj\u05cet\xf8DJF\x1a\xa8\xc8x0\x8a\x9aL\x00+p\f\x00\x98\r\xf8\x9dt\x9dJ\xacF\x8f-\x10\x89\xd0`\xd6\u007f\xc3\xd2\x17\xf0B7`\x1d\xb8\x9d\t\xaa\"\x8bۣ%HJ\xb3\xd5\xf2\xef-gG\nґJxL7\x9d\x1f\xa9=Z-\x14]O\xc0k\x10\xba\x82Z\x1c\xc0\"\x9d\x01A\xf7\xb81\x89+\xe0g\xbe\x12\xbd1+\xd8y߸\xd5\xcd\xcdV\xfa\xec<\xa5\xa9력?ܰ\v\xc8u\xf0ƺ\x9b\n\xf7\xa8n\x9c\xdc.\x85-w\xd2c\xe9\x83\xc5\x1b\xd1\xc8%\v\xae\xd9w\x8a\xba\xfa\xbf\xf6\xb2>\xf5$\xf5\a2(\xe7\xad\xd4\xdbv\x99M\xfb$\xeed\xe2\xd1r\xe2\xb6(\u007f\a/-\x11*\xcf\xf7/_\xfbV%\xdd\x10sF\xbbgh\x1d\xf0\x04\x94\xd4\x1b\xb4\xf1\xe2ض\x88#\xea\xaa1R{\xfeS*\x89z\b\xba\v\xebZz\xba\xe9_\x02:2]S\xc0\x1d\x87\x10X#\x84\xa6\x12\x1e\xab\x02\x1e4܉\x1a՝p\xf8\x9b\xc3N\b\xbb%Az\x1e\xf8~\xe4\x1b\x12F\xb4\xda\xe5\x1c\xa2&o(y\xf7K\x83\xe5\xc03h\x93\xdcd7\xde\x18;p~\xdaR\xf4XN\xb9%=ѷ)\x04\r\u05cf\x84\xf8CKF\xb6B\xc7\a-\u007f\t\xc8!4\xfa$\x8eÅ\xed\x85\xd3\xfeC&P\x1c\xadN\"H\x0f~\x94*TX\xb5a\xd2\xcdJz?\"\xe7$#\xa4&\x1b\xa7\xa0N\xe2\xea\xee-\aH1!%ٙԑ\x1bH\xcd*N K\x8f\xf4X\x8fĚ\xd1\t8k\x89\xb5\xc2\x15x\x1b\x8eώ\xfb\x84\xb5\xe20\tEβ\x97!\xd1R'7W\xb2\xe4+k\x9d\x99\xc1\xf8_\xc2!Is\x173\xcfeh\xa6!\x1bF\xd8^\x80\xd4\xda\x18\x85BOau\xa9\xfb<\x8cȏ\xac\xa6\xf5\x9cl6&\x1f1\xd2+F\xb9\x18\xf1Iq\xc0\xba\xf1\x87k\x10J\xf5\x1dP\xd8\x0e\xc0\xdfנ.t\xac\x87c\xea3\x8eu\x1a\xa1\xb1q\xf41\xea,-ѥ\xc8\xfa_\x00\x98\x12kT/\xa8\xb0\xf4\xc6\u0382\xf5S\x9f2\x02Eyq\xff\xb9\x18\xbe\xf1\x066Ry\xb4\xf0.\xfdn\xa4\xc0\xfb\x0eu\u0089*\x12\xa9+\xb9\x97U\x10j`e=\x94:0\xc1X\xd0R]\x8fx\x12\xc6y\xf7\x00S\xf8#\v/ԯ\xf2\xc1S)\x96\x9eZ\xf8rw\xffAő뺟\x19؎7D\xe4r\xfab\xf8\xc1e\xec\xa8B\x92\x16k\xae\xbb&8\x03|e+\xeb\xa8X\xdf\xdb\xc7/c\x03\x82\xd3F4\x12\xf2vF\x90\xe4\x13\xed\xf5Rvɉx\x923\xa4\xca\xf9\x1a\x04\xbc\xe1!\x16\xd9T\xc77\x14J3\v\x8b\\\x9e\xf3E\xbf၉R\xc5=\xc9u\xeeR\xe2\xf3\x86\x87S\xaf\x8eԥ\xf3R\xf5\x13\xf5\xa6\x05\x96\x8a뱬*wWxJIz\xbc\x99\x16\x16\xe6=5?\x19\x91\v\xc5n\x01\xecu\x81\f\xf1'\x17\xe1$\xfb\xda\xc9\xd8\xe0\xcdH\xed\x90m/\xf77\xafԩ\xb6̣E=\xe8kx4\x9e~\xee?$\xd5\xecB\x8f3h\xf7|1\xe8\x1e\x8dg\xda\u007f\v\x92(ԅ\x80Db6P\x1dc\x1b\xe9\xd5o\u007f\x1cG\x0f\xbaլߌ\x12\xd2Q\vbl֜\xdb\xd6xDd^\a\xc7\x1d\x8b6z\xc9\x11)s\x9fa\xda^\x9at\x19Jc\ax\x9d8h\x86\xe7\x1a!\x1d\xff\x95\x1a\xb1\xb8'\xb6\xd2J\x94XA\x15\x18\x02n\x05\x85ǭ,\xa1F\xbb\x9d\x93\xb3\xa18u\xfa\xeaf\"I|.\xb8\xdb\xd3Y(?)\xecT\xd3\a-\xc9\xd6O\xbc\x99\xbd\xde\xc9^\xed2\xa98|s\x82\x9b\xd4^T\x95\x8c\x19\xe6\xe9L|:\x83\xcf8g\xc4CS\xa2\x15\rY\xf6?(\x9c\xb2\xa1\xfc\x13\x1a!\xad+\xe0\x96\xe7Uj\xfaf\xfb\xf4\xa9\xf2\xe8\xb3&\xae\xd2\x01a\xbe\x17\x8aB=\x05\x0e\r\xa88\xf0O\xb24\x9bQF\xbb\x86\xf7\x9dq1\x8ao$*\x9eB\\\xbd\xe1\xe1\xeaz\xe0y \xa7C\xe9Ճ\xbe\x8aIb\xe4\am\xc3g\xb4:\xc0\x15\xbf\xbb*FIp\x92\xedlb\x9c\xb1\x88\x93\xaf\xdaJ\xf7g\xd14Ro\x8f\xef\xf92[\x98\xb1\x83\x81\r<\x1e\x9d60\x84~Y:(\xe1\xc7\xc7\xc51\xdfD\xb1o\xf3\xb0Q{S\xc0\xad>\x8c\xb8:\xea\x18'J\xdda\aI\"\xbdK\xa5(*%\x9e\x153\xed3J\x83\x05'\xea\xc8\u007fzh0\x01z\xe2\xf8\xf4:_\xc9?\xb7d\x13}`OY\xaa\x14[\x05\x9e^ǖ\xc3ŧӢq;\xe3\u16fd\x14i\xaaeB\xd5X\xb3\xa7~\xf0\xdb\xffPG\xe7\xca\x1dVA\xe1١\xcdK\x8f\xf0\xfc\xd8&\xb3\x1d\xdbB\x87C\xdb\xc9e\xb4\xaa\xe8\x81\xc3\xf1Pja\x12_\xba\xe4\xa9&\xbae\x18\xcd\xc18\x9e\xc9R8q\xa1,ѹMP\xb9\xe3\xe1\xb1?\xb5ב\\\xbaV\xda\v\xc7HS\x19b\xd9\x1b~\x9d\x9d\xd0y\xe1\x83;;\xa3c*(E\xe3\x83MEj\x19\xace\xa5\xe2;\xb3\x19\x8d\xe9.\x98ҡ\xb5ƞ\x99\xf60It\xf7\xd2\x04͵\x1a\xd9-\xef\x85\x1a\x9d\x13\xdb<\xe6yG\x8b\xb0EM\x11vbđ\xea\x00\xfc\xc02\xa4\x8f\x04\xc3>\x95\"\xa9(=\xb5_Q4\x0e\x9b\xad\x13\x9fʙD \xb6'\xeeLj\x8f[\x1cf⍐*X|F\xe1\x86\xdf\x0eF\xea\xffЧL\xa5]\xd4\x11E\xf6\xbf\xbe9\xb4\x1e\xf8<)\v\xeaP\x1f3^\xc2#\xbe\x8f\xd6Hy\xac^\xdbOG#\x82\a\xfdd͖2\xef\xe8՝\xa9\x1b\x85c+X\u0093\xb0^\n\xa5\x0e\x91\xfd\x89S/ũ\xfb\xb0u\u007fޘ_\x8f\x88\x8f\x06,d\xd6\x1d\xbfl\x82\xdf\xc8\xf1h-}\xe9Z+\xfc\xf6\xf7\x19\x94\xbc\v\xab\xa5\xdeΫ\xfb\xe7D4\xe1\xbdi\xffo\xe7\xbfY\xc0\xa1\a\x9f\x98\xe5\xfdZ\x0f\x9e\x88\xa5GK\xdd'\xde\xcf\xdd?Fk\x99>\xe9\xf2\v\xea>\xed\x1e\xab\x1e\xf6I\x94\xb4\xd2\x05hQ\x96\xd8\xf84\xc1\xec\u007fܽ\xba\xe2?\xf9\xeb-\xff-\x8d\x8e\u0557[\xc1_\xfe\xba\x80\x84\xc0k\x96\x83\x16\xff\x15\x00\x00\xff\xff\xba\xf0V\x1f\x0e\x1f\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xd4ZKo#\xb9\x11\xbe\xebW\x14\x9c\xc3\xec\x02\x96\xbc\x83\xbd\x04\xba9\x1e\x0f`\xec\xaec\xd8\x13\xe7\x10\xe4@u\x97$\xc6l\xb2\x97\x0f\xc9J\x90\xff\x1eT\x91쇺\xd5\xd2 Y\xec\xa4/\x86\xd8\xc5b\xd5\xc7z\xb7g\xf3\xf9|&j\xf9\x8a\xd6I\xa3\x97 j\x89\xef\x1e5\xfdr\x8b\xb7?\xba\x8547\xbb\x8f+\xf4\xe2\xe3\xecM\xear\tw\xc1yS=\xa33\xc1\x16\xf8\t\xd7RK/\x8d\x9eU\xe8E)\xbcX\xce\x00\x84\xd6\xc6\vZv\xf4\x13\xa00\xda[\xa3\x14\xda\xf9\x06\xf5\xe2-\xacp\x15\xa4*\xd1\xf2\t\xf9\xfc\xdd\x0f\x8b\x1f\x17?\xcc\x00\n\x8b\xbc\xfd\x8b\xac\xd0yQ\xd5K\xd0A\xa9\x19\x80\x16\x15.\xc1\xa2\xf3Ƣ[\xecP\xa15\vif\xaeƂ\x0e\xdbX\x13\xea%\xb4/\xe2\x9e$HT\xe29n\xe7\x15%\x9d\xff\xa9\xbb\xfa\xb3t\x9e\xdf\xd4*X\xa1\xda\xc3x\xd1I\xbd\tJ\xd8fy\x06P[thw\xf8\x17\xfd\xa6\xcd^\u007f\x96\xa8J\xb7\x84\xb5P\x8e^\xbb\xc2Ը\x84G\x92\xa2\x16\x05\x963\x80\x9dP\xb2d\x15\xa3\\\xa6F}\xfb\xf4\xf0\xfa\xe3K\xb1\xc5J\xc4E\x80\x12]ae\xcdtY>\x90\x0e\x04\xbc\xb2~$\x04_\x04\xf8\xad\xf0`\x91E\xd1ށ\xdf\"\x88\xbaV\xb2\xe0S\xc0\xac\x13Kh\xf68X[S\xb5\xbcV\xa2x\v5x\x03\x02\xbc\xb0\x1b\xf4\xf0SX\xa1\xd5\xe8\xd1A\xa1\x82\xf3h\x17\x89MmM\x8d\xd6\xcb\f,=\x1dSj֎t\xf8@JF\x1a(\xc9x0\x8a\x9aL\x00Kp\f\x00\x985\xf8\xadt\xadJ\xacF\x87-\x10\x89\xd0`V\xff\xc0\xc2/\xe0\x85n\xc0:p[\x13TI\x16\xb7CK\x90\x14f\xa3\xe5?\x1bΎ\x14\xa4#\x95\xf0\x98n:?R{\xb4Z(\xba\x9e\x80\xd7 t\t\x958\x80E:\x03\x82\xeepc\x12\xb7\x80_\xf8J\xf4\xda,a\xeb}\xed\x9677\x1b\xe9\xb3\xf3\x14\xa6\xaa\x82\x96\xfep\xc3. W\xc1\x1b\xebnJܡ\xbaqr3\x17\xb6\xd8J\x8f\x85\x0f\x16oD-\xe7,\xb8f\xdfYT\xe5\x1f\x9a\xcb\xfaБ\xd4\x1fȠ\x9c\xb7Ro\x9ae6퓸\x93\x89Gˉۢ\xfc-\xbc\xb4D\xa8<߿|\xe9Z\x95t}\xcc\x19펡\xb5\xc0\x13PR\xaf\xd1Ƌc\xdb\"\x8e\xa8\xcb\xdaH\xed\xf9G\xa1$\xea>\xe8.\xac*\xe9\xe9\xa6\u007f\r\xe8\xc8t\xcd\x02\xee8\x84\xc0\n!ԥ\xf0X.\xe0AÝ\xa8P\xdd\t\x87\xbf9섰\x9b\x13\xa4\xe7\x81\xefF\xbe>aD\xabY\xce!j\xf4\x86\x92w\xbf\xd4X\xf4<\x836\xc9uv㵱=\xe7\xa7-\x8b\x0e\xcb1\xb7\xa4'\xfa6\x85\xa0\xfe\xfa\x91\x10\u007fj\xc8\xc8V\xe8\xf8\xa0\xe5\xaf\x019\x84F\x9f\xc4a\xb8\xb0\x9dp\xda}\xc8\x04\x16G\xab\xa3\b҃\xef\x85\n%\x96M\x98t\x93\x92\xde\x0f\xc89\xc9\b\xa9\xc9\xc6)\xa8\x93\xb8\xba}\xcb\x01R\x8cHIv&u\xe4\x06R\xb3\x8a#\xc8\xd2#=V\x03\xb1&t\x02\xceZb\xa5p\tކ\xe3\xb3\xe3>a\xad8\x8cB\x91\xb3\xeceH4\xd4\xc9͕,\xf8\xca\x1agf0\xfe\x9fpH\xd2\xdc\xc5\xccs\x19\x1a\x0f\xe3{\xb2\x1b\xa1\x83\xfd\x16\xfd\x16mNhsN\xcf\xe5@\x996K\xa6\x8c\xb2\xc2\x16\x1er\xc3\xc2h'K\xb4\xd11\x8f\x00\x83\x87\xf5\x10\xe0\xa0\xd45\xf9\xb6\b\xca\xc74d\xc3\x00\xdb\v\x90Z\x19\xa3P\xe81\xac.u\x9f\x87\x01\xf9\x91\xd54\x9e\x93\xcd\xc6\xe4#\x06z\xc5(\x17#>)\x0eX\xd5\xfep\rB\xa9\xae\x03\n\xdb\x02\xf8\xfb\x1aԅ\x8e\xf5pL}ƱN#44\x8e.F\xad\xa5%\xba\x14Y\xbf\x01\xc0\x94X\xa1zA\x85\x857v\x12\xac\x9f\xbb\x94\x11(ʋ\xbb\x8f\x8b\xfe\x1bo`-\x95G\v{\xe9\xb7\x03\x05\xf6[\xd4\t'\xaaH\xa4.\xe5N\x96A\xa8\x9e\x95uPj\xc1\x04cAKu=\xe0I\x18\xe7\xdd=L\xe1\xcf,\xbcP_僧R,=\x95\xf0\xc5\xf6\xfe\x9d\x8a#\xd7v?\x13\xb0\x1do\x88\xc8\xe5\xf4\xc5\xf0\x83\xcb\xd8Q\x85$-V\\w\x8dp\x06\xf8\xc2V\xd6R\xb1\xbe\xb7\x8f\x9f\x86\x06\x04\xa7\x8dh \xe4\xed\x84 \xc9'\x9a\xeb\xa5\xec\x92\x13\xf1(gH\x95\xf35\bx\xc3C,\xb2\xa9\x8e\xaf)\x94f\x16\x16\xb9<\xe7\x8b~\xc3\x03\x13\xa5\x8a{\x94\xebԥ\xc4\xe7\r\x0f\xa7^\x1d\xa9K\xe7\xa5\xea'\xeaM\v,\x15\xd7cYU\xee\xae\xf0\x94\x92\xf4x3.,L{j~2\"\x17\x8a\xdd\x00\xd8\xe9\x02\x19\xe2\x0f.\xc2I\xf6\xb5\x95\xb1\xc1\x9b\x90\xda!\xdb^\xeeo^\xa9Sm\x98G\x8bz\xd0\xd7\xf0h<\xfd\xb9\u007f\x97T\xb3\v=̠\xed\xf3ɠ{4\x9ei\xff+H\xa2P\x17\x02\x12\x89\xd9@u\x8cm\xa4W\xb7\xfdq\x1c=\xe8V\xb3~\x13JHG-\x88\xb1Ysn[\xe3\x11\x91y\x15\x1cw,\xda\xe89G\xa4\xcc}\x82isi\xd2e(\x8d\xed\xe1u\xe2\xa0\t\x9e+\x84t\xfc\x17j\xc4\xe2\x9e\xd8J+Q`\te`\b\xb8\x15\x14\x1e7\xb2\x80\n\xedfJΚ\xe2\xd4髛\x88$\xf1\xb9\xe0nOg\xa1\xfc\xa4\xb0S\x8e\x1f4'[?\xf1f\xf2zG{\xb5ˤ\xe2\xf0\xcd\tnT{Q\x962f\x98\xa73\xf1\xe9\f>Ü\x11\x0fM\x89V\xd4d\xd9\xff\xa2pʆ\xf2o\xa8\x85\xb4n\x01\xb7<\xafR\xe37ۥO\x95G\x975q\x95\x0e\b\xf3\x9dP\x14\xea)ph@Ł\u007f\x94\xa5Y\x0f2\xda5\xec\xb7\xc6\xc5(\xbe\x96\xa8x\nq\xf5\x86\x87\xab\xeb\x9e\xe7\x81\x1c\x0f\xa5W\x0f\xfa*&\x89\x81\x1f4\r\x9f\xd1\xea\x00W\xfc\xeej1H\x82\xa3l'\x13\xe3\x84E\x9c|\xd5T\xba\xbf\x88\xba\x96zs|ϗ\xd9\u0084\x1d\xf4l\xe0\xf1贞!t\xcb\xd2^\t?<.\x8e\xf9F\x8a}\x9b\x87\x8dڛ\x05\xdc\xeaÀ\xab\xa3\x8eq\xa4\xd4\xedw\x90$\xd2^*EQ)\xf1,\x99i\x97Q\x1a,8QE\xfe\xe3C\x83\x11\xd0\x13ǧ\xd7\xe9J\xfe\xb9!\x1b\xe9\x03;\xcaR\xa5\xd8(\xf0\xf4:\xb4\x1c.>\x9d\x16\xb5\xdb\x1a\x0f\xdf\xed\xa4HS-\x13\xcaښ\x1d\xf5\x83\xdf\xff\x8f::Wl\xb1\f\n\xcf\x0em^:\x84\xe7\xc76\x99\xed\xd0\x16Z\x1c\x9aN.\xa3UF\x0f쏇R\v\x93\xf8\xd2%\x8f5\xd1\r\xc3h\x0e\xc6\xf1L\x96\u0089\vE\x81έ\x83\xca\x1d\x0f\x8f\xfd\xa9\xbd\x8e\xe4\xd25\xd2^8F\x1a\xcb\x10\xf3\xce\xf0\xeb\xec\x84\xce\v\x1f\xdc\xd9\x19\x1dSA!j\x1fl*R\x8b`-+\x15ߙ\xf5`Lw\xc1\x94\xae0U\xad\xb0\xff\xe1c\xea\xe6\xef\x86\xf4<\xf0\xb6e\x14\xca\xcb\n\xbb\x9dg\n\xb6ǃtz\xf6\xc2\xe5\xe3\xa93j9G&\\>\x10c,\x01w\xa8\xc1hX\v\xa9\xb0<5#\x8a\xcd\b\u007f\x17\xb1\x1f\\\xc3%\xb84>y\xf1\xc2\xfaF\xeccW[\x1b[\t\xbf\x84Rx\x9c\xd3ޯ\xf7\xa9\xb1\x19\xa3\xb5ƞ\x99\xa61I\f\xa7\x85\t\x9aka\x8a\v\xbc\x17*tNl\xf2\x18m\x8f\x16a\x83\x9a@\x1d\x19!\xa5:\v߱\b\xe9#L\u007f\x0e@\b\x89\xc2S{\x1bE\xe3\xb4\xd4\x04\xc9S5\t\x11\x88\xcd\t\x9f\x90\xda\xe3\x06\xfb\x95\x0e\xddT\xb0\xf8\x8c\xc2\xf5\xbf\xcd\f\xd4\xffܥL\xa5s\xd4\x83\xb9\xbd\xb0Z\xeaʹ\xba\u007fMD#\xd1,\xed\xff\xed\xe2Y\x16\xb0\x1f\xd1N̎\xbf6\xa2\x8d\xe4\ue8e5\xf6_\n>\xb6\xbf\x18\xady\xfa\x17\x02~\x01њ\xcb\x0e\xf6I\x94\xb4\xd2\x16\x04\xa2(\xb0\xf6ib\xde\xfdg\x82\xab+\xfe\x91\xff[\x80\u007f\x16F\xc7j\xdf-\xe1o\u007f\x9fAB\xe05\xcbA\x8b\xff\t\x00\x00\xff\xff#K\xa5\xea~!\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec\x1c\xcbn#\xb9\xf1\xee\xaf(8\x87I\x00K\xde\xc1^\x02\xddf=^\xc4\xc8dv\xb0v|\tr\xa0\xbaK\x16\xe3n\xb2C\xb2e+A\xfe=\xa8\"\xfb\xa9~\xb0\xbdv\xb0Y\x98'\x9b\"\x8b\xc5z\xb3X\xec\xb3\xd5ju&\ny\x8f\xc6J\xad6 \n\x89\xcf\x0e\x15\xfdg\u05cf\u007f\xb4k\xa9/\x0f\x1f\xb7\xe8\xc4dzG\xa9\xd2\r\\\x95\xd6\xe9\xfcg\xb4\xba4\t~ƝT\xd2I\xad\xcert\"\x15Nl\xce\x00\x84R\xda\t\xea\xb6\xf4/@\xa2\x953:\xcbЬ\x1eP\xad\x1f\xcb-nK\x99\xa5hx\x85j\xfd\xc3w\xeb\xef\xd7ߝ\x01$\x06y\xfa\x9d\xcc\xd1:\x91\x17\x1bPe\x96\x9d\x01(\x91\xe3\x06l\xb2Ǵ\xccЮ\x0f\x98\xa1\xd1k\xa9\xcfl\x81\t\xad\xf6`tYl\xa0\xf9\xc1O\n\x98\xf8]܆\xf9ܕI\xeb\xfe\xdc\xe9\xfe\"\xad㟊\xac4\"k\xadǽV\xaa\x872\x13\xa6\xe9?\x03(\fZ4\a\xfc\xabzT\xfaI\xfd(1K\xed\x06v\"\xb3\xf4\xb3Mt\x81\x1b\xf8J\x98\x14\"\xc1\xf4\f\xe0 2\x99\xf2>=n\xba@\xf5\xe9\xdb\xcd\xfd\xf7\x84G.|'@\x8a61\xb2\xe0q5\x8a -\b\xb8\xe7M\x82\t\xec\x00\xb7\x17\x0e\f2.\xcaш\xc2\xe0\xaa\xc22\x05m\x02L\x80\x02\x8dԩL\xe0\a\x91<\x96\x85\x9fj\xf7\xba\xccR\xd8\"\x98R\xad\xc3\xd8\xc2\xe8\x02\x8d\x93\x15\t\xa9\xb5\xa4\xa6\xee\xeba\xfa\x81\xb6\xe2\xc7@Jr\x82\x16\xdc\x1e!p\x1bS\xa6^.@\xef\xc0\xed\xa5m\xf0f\x92\xb4\xc0\x02\r\x11\n\xf4\xf6\x1f\x98\xb85\xdc\x12\x9d\x8d\xad\xb0M\xb4:\xa0\xa1}'\xfaA\xc9\u007fՐ-8\xcdKf\xc2a\xe0hդrh\x94Ȉ\t%^\x80P)\xe4\xe2\b\x06i\r(U\v\x1a\x0f\xb1k\xf8\x8b6\bR\xed\xf4\x06\xf6\xce\x15vsy\xf9 ]\xa5'\x89\xce\xf3RIw\xbcdi\x97\xdb\xd2ic/S<`vi\xe5\xc3J\x98d/\x1d&\xae4x)\n\xb9b\xc4\x15\xab\xc9:O\u007fWq\xd1~ha\xea\x8e$6\xd6\x19\xa9\x1e\xean\x16\xe2Q\xba\x93,{\xf1\xf0\xd3<\xfe\ry\xa9\x8b\xa8\xf2\xf3\xf5\xed][t\xa4\xedҜ\xa9ݒ\xa6\x86\xf0D(\xa9vh<\xe3vF\xe7\f\x11UZh\xa9\x1c\xff\x93d\x12U\x97\xe8\xb6\xdc\xe6\xd2\x11\xa7\xffY\xa2uğ5\\\xb1\xb5 \x99+\x8bT8L\xd7p\xa3\xe0J\xe4\x98]\t\x8boNv\xa2\xb0]\x11I\xe7\t\xdf6r݁\x9eZuwe\x8c\x069T\xe9\xf0m\x81IG5h\x96\xdcɄ\x15\x00v\xda4*n\xdb\x16k\\/y\xed0\xb4\xdb;iG\xae\x8cV\x80\xcf\xc4\xe9F_IN\x9e\xf6\xa8H\x8bL\xa9\b\xc3\x1eD\b\xc6c\xdd\xeb\x1f\xa4\x1d\xff\x80yA\xca8\x89\xda]\x18D\xa8\x11U\xd2\xda\xc9xS\x81\xb5\xc9\xd2\xc1R\x81\x1eƮ0\xfa \xd3`\x0fzԛ\xa2\xa0Gi'\xca\xcc\xdd\xeb\xac\xcc\xd1\xde\xe9\x9f\xd1:\x99\x9c\x8e\xeb!\xffypZ\xc5Y\xb4DQ\xb7GC\x8a\xc7?\xb0\r\x1b\x80\n\xac\x11\x16S6b\xe2\x11A\xc0\xd6\uf6eca\x96A\xa1S8\xf8u`{\xac\x10\xee\xf3\xa2\xe1\xc7V\xeb\f\x85:\xf9\x1d\x9f\x93\xacL1\xad}\xd3\x005z\xbb\xbc>\x99\xc2.^HE\xd2D\x0e\x95\x90Tͯ\xe4]\x067)\f\x02\xa9\xbfT\x1e\"Hfe\xd8\xea\xd0f\xa4\xc3|\x10\xc3\t\xb9\xf3\x8dB\b\xb1\xcdp\x03Δ\xa7\xd2R\xcd\x17ƈ\xe3(\x95\xaa\xd0'\x9eH\xf5\x8c`\x943\x99 \x91\xa76\xbdL\xa7\xdf\x00\x89\xf6Z?Γ\xe5O4\xaaq+\x90pD\t[܋\x83\xd4\xc6\xf6#\x11|Ƥt8\xac#\xc2A*w;4\x04\xa9\xd8\v\x8b\xb62\x12\xe3\xe4\x99R{jf\x8a\xc5'\xfbi\xd8K\x8cb\x1a\x8cm\x81\xcd\xe9\bL`\x84\xc9\xe6\x96\x05H\x95ʃLK\x91\x81T\xd6\t\x95\xf8}\x89\x1a\xb7\xa1}\xc14\xebO0\xf7f\xb4\u009f\xf8\xd2\xf1HZ!h\x039\xb9\xdfӡvt\r\x18\xdd\xfeV\x90=\xf3\xc6\x1a\f\xc5\xd3a\xb1\x94\x9d]c/.&\x80\xd7\xdc\xf1A[&\xb6\x98\x81\xc5\f\x13\xa7\xcd\x18Y\xe6\x99\xee[\x9c-\x1c\xa1\xe7\x80Ul\xec>m\xb9\xd9\xe0$P \x93\xff\xb4\x97\xc9\xde\xc7W$S\f\tR\x8d\x96m\x81(\x8a\xec8\xbeY\x98\x97\x84\xb0д9hڬa\xe8\xc3\x1c6\x11M\x8b\xb2\xa7M\x9b\xb1\xac]:\xd7\"\xf2N\xe6\xcai\xbcH\xa0oN&\xbf\xb6@\x13\x81%\x9d\xadnv\x80y\xe1\x8e\x17 ]\xd5;\x0f\x93\u00a0\x06\x87\xdf\x04\xa3^\xa2\x0f7\xfd\xb9\xaf\xac\x0f\xaf\xc0\xa5\x1a\x85\xffk&\xb1\xb3\xb9\r\xbef\x01\x83\xbe\xb4\xe7]\x80\xdc\xd5\fJ/`'3\x87\x1c\xf7L\xa3\xd8r}\xb3\x9cz-\xb2\xc4yMj\xb9p\xc9\xfe\xba>BΎ\xefQ\xa8?\xdd\xc7\xca\xd5I\xa2\xeb\xe4g!\x03g\x18\xa4\xc1\xdc\xe7-\xeeX\a\x9a\x1e\x8e\xd4>}\xfd\x8c\xe94\xa1 V\"O\xb6\xf3\xa9\x87r{\xf9p\f\x88\xdfL\b\xa8\xea\x13\x96\xcfG]\x80\x80G<\xfa(H( F\tZj\xf4 qJ$N\x8c\xb1\x89x\xc4#\x03\n\xb9\xae\x88\xf9\xf1\xa2\xe1\xdb#\x1e\xe3\x06\xf6HI\x98\x85L\x80\xa7)u0A8e\xb2\x84\x8c\xc0\x99K\xd6\x10p:f\x93\xb0\xc4\xdcT\xad\xe2ċ\xb6[\xb3\xb1\x93\xc6}\xc4\xe3\a\xeb\x19Fڱ\x97E\xf4\x86\xc9\x00\x83E֣*\x93y/2\x99\xd6Ky}\xb8QSQw\xb7}\xd5\xeeF]\xc0\xf5\xb3\xb4\x84\x9eJ\xe1\xb3F\xfbU;\xeey3\xc2z\xf4_DV?\x95UOy3O\xf4h'H\xa3\x84\u07b7\x1b\u007f¬Y%-\xdc(:+\x05\xbap\x9a\x9baƋ%\xa3\x94\x97\x963\xa1J\xab\x15;\xda\xf5\xc0Z\xd10\x03{\xb4\xe9p\xa7\x8d^k\xd9h\xa8t\xa0\xf3\xa8ݑ\xef\xf1\x10|\xfa>\x13\t\xa6\x90\x96LT\x11\r\xd1:#\x1c>\xc8\x04r4\x0f\b\x05\xf9\x82XnD\xdbg\xdf\x16\xcb\\lhP\xb5`\xe8\xd3\x18\x94V\xa4\xd7Q\xe3*\xf6G\f\x1e\xccGO\x0f\x8e\xd9\x1b;h\x8ec\"\xa8-Ҕ\x13\xb6\"\xfb\xb6\xc8K,\xe2\xcei\xfc\xe0\xd1\xf3\xfe5\x17\x9c(\xfd7\xb9H\x16\xf6\xff@!\xa4\x89\xd2\xf2O|ŗagvȺ\xb5\x17\xa25\xa4\x05\xe2\xf8Ad\xfdێ\x91-j\xb2@\x98\xf9P@\xefN\"\x9f\vx\xdak\xeb=\xf2N\xe2Hr\xb8ۤ\x85\xf3G<\x9e_\x9cإ\xf3\x1bu\xeeC\x84\xbe\xd6G\x80\xad#\x0e\xad\xb2#\x9c\xf3\xec\xf3_\x16NEKg\xe4@\xbe\x0f\x8e\x0f\xc4\xe9$[E\x134\xb5\xbe|\xa4\x10z\x1a\xfbH\xd9,\xb4u\v\x10\xfa\xa6\xad\xf3\x19\xd1N\xc0\xbb,\xdf\x06^\xaeB\x9e\r\xc4Ρ\x01봩\xae\xfa\xc8H\xf6\xd2\xc6\xc4E;w\xe0 \xc6\xd6\xd9;\x0f\x96\x0es\xe7\x8d~{\xfb{\xee\xef\x00\xe9\xef9\x88\t\a0\f\xb90:Ak\xe7\xc4&\xca\xc2\xcf$6뤦\xf0\x87%\xbe[\x9b\x15ֹdkՖ\x84\xc2D\xce\xc5lj\xeb\xe7V^\x96\xcc\a\xfd?/\xb2˱\x03\xd6\xfa<\x17*ʁ\x9d z\xe5\xe7V*\x16@\xf9#\x8ay(\xd9\\,\x89\\\x83\xf0\xfdz\x82\x81\\\xaa\x1b^\x04>\xbeI\xf8P\x1b]|\xd9\xf1\u1a9aݰ\xa0\xee\x18\xbe$\x1dk\x85\xe6\xfb\n\x83\x1dN\x9ef\xf5\x17\x85\xcdJ\xbbv\xea\x83 \x17:\xfd`a'\x8du\r\xb2\xd10\xa5\xe5Kҷ;˩kc^x\x94\xfb\xc9\xcfm%\xe3\xf6\xfa\xa9\xbe\xd0\x1f\xbf\xf8\x1dj|=\x86 w \x1d\xa0Jt\xa98iDƀ\x17\xf1\xec\x88\x17d\x88\xf5{MCU汄X\xb1$J5\x93_jO\xf8Q\xc8\xec\xad\xd8\xe8d\x8e\xba\x9cq\xccM\xeb\xd6#\xf8\xb9\x9dJ\x8d\\<˼\xccA\xe4Ĉh\x92S\xb4!s\xec\xca\x00<\t\xe9\xd8#\x11dvONG\x83Lt^d\xe8\x10\xb6\xb8ӆ\xf5\xdd\xca\x14k\xd7\x1f\xe4B\x8f\xdf;\xf6\x9b\x80\x9d\x90Yi\xa2\xad\xeeBn,;!\x05\xc3\xf3\xba\a\x9fX\x14VL\xbe\xa8tttH;\xe7\t\n\xb3$\xa0\xfdf\xf0\xb5\xc3\xc7\xc2H\x92E=\x17A\xce@\xe4\xf8\xb2\x1bA\x06\x11\x15\xea8\x16B\xce\xc0d,\xdeC\xc8\xf7\x102\x0e\xee{\b\xf9\x1eBη\xf7\x10\xf2=\x84\x8c\x99\xf0\x1eB\xbe\x87\x90\xcbQ\xf8߇\x90\xf3\x98\xad8\xf79\xfas\x046Q%\x04\xd3\xc8N\xae\x12\xaaa\xae\xb2\xd2:4\xf15\xb77\xc3\xf3\x06\xea\xaf\x13?d\xc5\x0fs\x86e\xa3)\xb7h\\V]\x84K\xcaV)\x8a\xafן\x8d\x8e#\x8bk\xc7\xea\xb4\xe3J\xb9\xe6\n\xb8\xba5\xc8u\xf1TU\x849\xaa\xc4j\xa6\xb0j\xba\x84[Wk\x8cP\xb1\x93\x80\xef\x92\xcftʈ\u007f\x85ԛ\xad}\x1a\xafx\n7r\xe8\xc4\xe1\xe3\xba\xfb\x8bӡ\xfe\t\x9e\xa4\xdb\x0fn\x8aߠ\xd0qQ=\xb4\v\xa3+Y\fo\xbc\xfaT\x05m@\xc9l\xb8\xa6\x81\b^\xcd\xef\x90\x1b~*\xfc\xa1\xf4E\xfa;wL\x8a\xad\x91zqeT\xb7\xeei\xd4\xc0/\xbf\xc0[RV\x1e_\xfb4W\xaa\xb4\xa4\xe2\xa9]\xcd4\x012\xb6\xce)\xee\xc4;[\xd3\xf4\x82J\xa6\xe8\xba\xcaW\xb9\xae\x8c\xabUz\x8b\n\xa5\x05uI\xddz\xa3\x19\xb8˪\x91\"\xc9\x14Sy\xb4\xb8\xde(\xd4\xf6\xcc\xec'\xa2\xcah\xb4zh\x06\xf4@\x1d\xd3|\xcd\xd0\x1c\xf9;\xa8\xbcJ\xa5\xd0\v\xea\x83^\xb7\x8e\xf8\xb5\xa2\xee\xa9j\x9f\x88\x1a\x9f\x88\xb8|\x0eӈ*\x9ee\xb5;\x114|a\x9dN]\x853\xba\xf6\xd2\xea\x9cn\xed\xcd(ؘ\x9a\x9c\x91\x8a\x9bQ\x98\x93\x958\xb1u6\xa3\xd0g\xdd\xf7\x8c\xe4L\xfel\x95(\xec^WOZg#\xbf\xdb\xee\xf8\x81\xa3W\xf5\xa05\xc9t\x99\xd6\xf0\x87\xb7\xc7/\xff\x8f\xf0힝\v?\xf4K\x9a'\x90\xc1}T\xa1\\\xff\x85\xe4\xf0\xebd\xf8\xe5G1\xeb\xb4\x11\x0f\xf8E'\xad\xcf7LѤ;\xbe\xf3:?0\xbfJ\xb6\x84\xaa\xa4\xe1\xb05\xec\xa8\x0f\xaeɱ\x86\x17\xc2\xcdy\x950\x1d\x96\x8bI\xcdu.\x9b\xdd\xd4\xdd\xdd\x17\xbf\x11's\\\u007f.\xfd1xU\bc\x91h[m\xd0Oڎ\x19\x88\xbd~\x82L\x87\xdd\xff\xd0\xc7\xdf gs\xf9\xbc\xbdx\x17\xfeut%\x90\x15\xb9\xe6E\xf8~x^+\xf2n1\x8d\x8f~c\xb2;\x06IX\xab\x13)\xf8M\xa8t\xe1\x19\xc7[<\xe8\x1dw\b#J?\xe4\xc7VC\x0f\xe8W\xf5k\xfe\xb3\x19\xa0\xd6\tW\xda\xf9\xcf!\xf00HD\xe1J\x13\x92\xa4Ii\xf8i/\x81@\xff\x02v\xf9\a\x112a\x9d\x17\xac\xc9\xef\x0e|\xa9\x875Q\xbau>\xdbZi\x1e<\t\xcb_\x1c\xf0\xb9Vi\xbb\x9f\x92i\xb7\x91o\x0e\xec\xb4Ʌ\xdb@*\x1c\xae\bv\xef\xf7I\xcb4\xcal~\xfa<\xb9\xbbo4\xa2\xbe\xd1\td\xe5iՃ鑝\f\xa5\xecW\xf0\x15\x9fN\xfa\xae\x15!\xdeϥ\xf9\xac<\xa6\xf7\xf5\xd7nb7\xd5|\x1f\x87/AN\x14\xa0\xab\xb3\xbd\xc1\xbdL\r\x9d\xf8\x1bx\xfe\xc2\xc3\xc2\xef婡\xe5\xe3WB;\xf9C\xef\xb7\x11-\x9c\xd0\xc0a\xed\x1bP\x92^W\xf3E\xa4\x8f\xcd\u007f\xbc\xf4*|\x01\xe9\xe0\xef\x0e\xf9\x93CiKV\x82g\n=\x8d\xe6\x89$\xc1\u0085L`\xfbSH\xe7\xe7\xfcO\xf5\xa5#\xfe7\xd1\xcaǀv\x03\u007f\xfb\xfb\x19\x04/r_\xe1A\x9d\xff\r\x00\x00\xff\xfff\xf9\xa5\xc0=J\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VMo\xe36\x10\xbd\xebW\f\xb6\x87\xbdT\xf2\x06{)t+\xd2\x16\b\xda\x06A\xbcͥ\xe8\x81&G\xf64\x14\xa9r\x86N\xdd__\x90\x94b[\x967\xbb\x87\xd5M\xa3\xf9x|\xf3fĪ\xae\xebJ\r\xf4\x84\x81ɻ\x16\xd4@\xf8\xaf\xa0Ko\xdc<\xff\xc0\r\xf9\xd5\xfef\x83\xa2n\xaagr\xa6\x85\xdb\xc8\xe2\xfbGd\x1f\x83Ɵ\xb0#GB\xdeU=\x8a2JT[\x01(缨d\xe6\xf4\n\xa0\xbd\x93\xe0\xad\xc5Po\xd15\xcfq\x83\x9bH\xd6`\xc8\x15\xa6\xfa\xfb\x0f\xcd\xc7\xe6C\x05\xa0\x03\xe6\xf0O\xd4#\x8b\xea\x87\x16\\\xb4\xb6\x02p\xaa\xc7\x16\x18C\n\x12%\x91\x03\xfe\x13\x91\x85\x9b=Z\f\xbe!_\xf1\x80:\x15\xde\x06\x1f\x87\x16\x8e\x1fJ\xfc\b\xaa\x1ch\x9dS\xads\xaaǒ*\u007f\xb5\xc4\xf2\xeb5\x8f\xdfh\xf4\x1al\f\xca.\x03\xca\x0eLn\x1b\xad\n\x8b.\x15\xc0\x100\u007f\xf8\xc3=;\xff\xe2~!\xb4\x86[\xe8\x94e\xac\x00X\xfb\x01[\xb8O\xa8\a\xa5\xd1T\x00{e\xc9dz\xca9\xfc\x80\xeeLJ\xbb\xa7\x8fk\xbd\xc3^\x15#\x80Aց\x86\xec\xb7t\x06 \x06\x05#\x12\x10\x0fJkd\x06\x1dC@'P\x90\x02\xb9·>\x97\x1b\x13\x03\xa8\x8d\x8f\x02\xb2Cx\xcaԎgkF\x87!\xf8\x01\x83\xd0Dt\x0e9\xca\xec\xd56\xc3\xf8>\x1d\xa2\xf8\x80I\xc2B\xce5Fy\xa0\x01\xce\a\x04߁\xec\x88!`f\xcf\xc99\xba\xccI\aʁ\xdf\xfc\x8dZ\x9a\xf1\xf4\f\xbc\xf3њ\xa4\xc6=\x06\x81\x80\xdao\x1d\xfd\xf7\x9a\x99\x13\r\xa9\xa4U2\xe9`z\xc8\t\x06\xa7l\xa2?\xe2\xf7\xa0\x9c\x81^\x1d `\xaa\x01ѝd\xcb.\xdc\xc0\xef>`&\xb0\x85\x9d\xc8\xc0\xedj\xb5%\x99\x06K\xfb\xbe\x8f\x8e\xe4\xb0\xca\xe3A\x9b(>\xf0\xca\xe0\x1e\xed\x8ai[\xab\xa0w$\xa8%\x06\\\xa9\x81\xea\f\xdc\xe5\xb9jz\xf3]\x18\xa7\x90ߟ \x95C\x12\fK \xb7}5g\xa9_\xe5=ɼ\xa8\xa1\x84\x15\xfcGz\x93)\xb1\xf2\xf8\xf3\xfa\x13LEs\v\xce9\xcfl\x1f\xc3\xf8H|\"\x8a\\\x87\xa14\xae\v\xbe\xcf\x19љ\xc1\x93+ZҖН\x93\xceqӓ\xf0\xa4\xd2ԟ\x06n\xf3z\x81\rB\x1c\x8c\x124\r\xdc9\xb8U=\xda[\xc5\xf8\xcdiO\fs\x9d(}\x9b\xf8ӭx\xeeX\xd8z5O+k\xb1C\vӻ\x1eP\xa7\x9e%\xe2R,u\xa4\xf3\x18@\xe7\x03\xa8\xa5\x90\xe6M\f\xd9\xfb\xabP\x8c;\xa2\xe0\x98m\x8e4\x83o\xe1XZ\x15پS\x8c\xe7\xa6\x19\x9a\x87\xe41\xafl\xa9C}\xd0\x16K\x82\xb2)\xf0-\x10\xe9A\x17\xfby\xbd\x1a\xee\xf1\xe5\xc2\xf6\x10|ړy\x15\x9f>\x8b\xfd\x87\xf2\x8fؒ\xe3ϟ\xa6\xf8\xe4\xbf\xce\xe9\xca=Y\xb5c\x1a\bѹ4\x91\xde%\xf3,)\x9co\xe4\xd9W\x12\xec/p,\"\xb9s\x9d\xcf\u007fm\x95J*)s\x82cS\xc7\x1a\x05\xd1E\xbak=-\xcf|\x15}\x01\x81\xe5\xc9\u007f\xfe\xaf\x0fL\xab\x83\x02.Ԭ3\x96\x05s\xaata^\x9c\x98\x11Y\xb4Vm,\xb6 !\xce#K\x9c\nA\x1d\xceU1\xc9\xe8x\xc7\xf9\xac@.ܓ\xf6_v\xe8\xae)\x1c^\x14/\xf5\xa6\xa4\x81\xcd\xe1Z\xe0\xed\xebem>$E\x96-\xa4\xad[\v]\xb0\xf4\x05D,t\xa9Hu\xe1vpA\xc2\xfa\xd4s\x9a\xfd3\xc1O\x97\x859\xf2+\xc5\x17\x9a:3\x1d\xef\xa67Ƿ,\xecz\xbc\x8b\xe6\x0f\xe3)\xcc\xc9\xc9Y|Pۉ\x8b\xe3nM\u05ecA\xd0\xdc\xcfo\xa2\xefޝ])\xf3\xab\xf6\xceP\xb9Hß\u007fU%+\x9a\xa7\tG2\xfe\x1f\x00\x00\xff\xff\xee@m\x0f\xc7\v\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4V\xc1\x8e\xdc6\f\xbd\xfb+\x88\xf4\x90K\xed\xc9\"\x97·`\xdb\x02A\xd3`\x91M\xe6R\xf4\xa0\x91\xe8\x19veI\x15)\xa7ۯ/$\xcb;\xe3ٙ\xa4E\x11\xdfDS\xe4\xe3\xe3#\xa1\xa6m\xdbF\x05\xdabd\xf2\xae\a\x15\b\xff\x12t\xf9\xc4\xdd\xc3\x0fܑ\xdfL7;\x14u\xd3<\x903=\xdc&\x16?~@\xf6)j\xfc\x11\ar$\xe4]3\xa2(\xa3D\xf5\r\x80r\u038b\xcaf\xceG\x00\xed\x9dDo-\xc6v\x8f\xae{H;\xdc%\xb2\x06cɰ\xe4\x9f^u\xaf\xbbW\r\x80\x8eX\xae\u007f\xa4\x11Y\xd4\x18zp\xc9\xda\x06\xc0\xa9\x11{\x98\xbcM#\xb2S\x81\x0f^\xac\xd7s\xb2nB\x8b\xd1w\xe4\x1b\x0e\xa8s\xee}\xf4)\xf4p\xfc1\x87\xa8\xb8暶%\xda}\x8d\xf6\xaeF+\x0e\x96X~\xf9\x82\xd3;b)\x8e\xc1\xa6\xa8\xecUdŇ\xc9\xed\x93U\xf1\x9aW\x03\x10\"2\xc6\t?\xb9\a\xe7?\xbb\x9f\t\xad\xe1\x1e\x06e\x19\x1b\x00\xd6>`\x0f\xefs\x05Ai4\r\xc0\xa4,\x99r\u007f\xae\xc9\ato\xee\xden_\xdf\xeb\x03\x8ej6\x02\x18d\x1d)\x14\xbf+\xc5\x001(X\xd0\xc0\xe7\x03F\x84ma\x0eX|D\xae\xc0kH\x80\xa5\x02\xee\xaa)D\x1f0\n-\x04\xe7\xefDaO\xb63_\x9b\xf1\x1f\xe9ͦ\xccʇ\x9f\xee?\u0092\xb4\xb4`\xcdya\xfbx\x8d\x8f\xc4g\xa2\xc8\r\x18\xe7\xc6\rя%\":\x13<9)\am\tݚtN\xbb\x91$w\xfaτ,\xb9?\x1dܖ\xcd\x02;\x84\x14\x8c\x124\x1d\xbcup\xabF\xb4\xb7\x8a\xf1\x9bӞ\x19\xe66S\xfau\xe2O\x17\xe2\xdaqf\xeb8DuU]\xec\xd0\xe5I\xbd\x0f\xa8W\x83\x92c\xd0@ur\a\x1fA\xadجS|9Zw\xe2zi\x80a\xde\xe0\x03\xed\xd76\x00eL\xd9\xfe\xca\xde]\xb9w\x95\x9e\v\xb5ޖ\x1cY\x8e\xb9\x80\x10\xfdD\x06c\xbb\xd4V1\xa4X\x8b,\xbb\xb1k.\xe5:c\xb8\x16V\u009d\xc3[!\xb8\xabN\x19C\xa6u\xb94\xef\x1d\xac\xeb\xaf,C\xb5\xc7˹\x9fՙ\x15L\x11WS\xd8>\x85\xfe\xaa:DI\xe2\xff\xaa\x8fr\xa9z\xee\xaaFt\x8a\x11\x9dԈ\xe0\x87\x15|\xf5\xff5\x12\x0e\x8a\xf1\x8b\xfc^\x8e}\x97\xef-\x94[\x1aP?Z\x9c\xa3\x95m\xfeLP\xff\x1ai\xfeХ\xf1\x1cT\vo&EV\xed,>\xfb\xf3ɩ+\xff\xae\xf4\xf7B\xdb\xceL\xc7\a\xce\xcd\xf1T\xc8k\x97\a\xcd\xcd\xfcB\xc8K\xd3\xf4 1\xcdɫҪ\xe5\xa8\x05\xa55\x06A\xf3\xfe\xfc-\xf3\xe2\xc5\xea9R\x8eڻyL\xb9\x87\xdf~o\xe6\xa8h\xb6\v\x8el\xfc'\x00\x00\xff\xff\xbcn\x89\xa9\f\n\x00\x00"), diff --git a/pkg/apis/velero/v1/restore.go b/pkg/apis/velero/v1/restore.go index db4b4d203..4b65fee6e 100644 --- a/pkg/apis/velero/v1/restore.go +++ b/pkg/apis/velero/v1/restore.go @@ -137,6 +137,19 @@ type RestoreStatus struct { // FailureReason is an error that caused the entire restore to fail. // +optional FailureReason string `json:"failureReason,omitempty"` + + // StartTimestamp records the time the restore operation was started. + // The server's time is used for StartTimestamps + // +optional + // +nullable + StartTimestamp *metav1.Time `json:"startTimestamp,omitempty"` + + // CompletionTimestamp records the time the restore operation was completed. + // Completion time is recorded even on failed restore. + // The server's time is used for StartTimestamps + // +optional + // +nullable + CompletionTimestamp *metav1.Time `json:"completionTimestamp,omitempty"` } // +genclient diff --git a/pkg/apis/velero/v1/zz_generated.deepcopy.go b/pkg/apis/velero/v1/zz_generated.deepcopy.go index 7bf9796b9..d6ab48720 100644 --- a/pkg/apis/velero/v1/zz_generated.deepcopy.go +++ b/pkg/apis/velero/v1/zz_generated.deepcopy.go @@ -1146,6 +1146,14 @@ func (in *RestoreStatus) DeepCopyInto(out *RestoreStatus) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.StartTimestamp != nil { + in, out := &in.StartTimestamp, &out.StartTimestamp + *out = (*in).DeepCopy() + } + if in.CompletionTimestamp != nil { + in, out := &in.CompletionTimestamp, &out.CompletionTimestamp + *out = (*in).DeepCopy() + } return } diff --git a/pkg/builder/restore_builder.go b/pkg/builder/restore_builder.go index 2a1e55db3..e505b798a 100644 --- a/pkg/builder/restore_builder.go +++ b/pkg/builder/restore_builder.go @@ -17,6 +17,8 @@ limitations under the License. package builder import ( + "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" @@ -133,3 +135,15 @@ func (b *RestoreBuilder) RestorePVs(val bool) *RestoreBuilder { b.object.Spec.RestorePVs = &val return b } + +// StartTimestamp sets the Restore's start timestamp. +func (b *RestoreBuilder) StartTimestamp(val time.Time) *RestoreBuilder { + b.object.Status.StartTimestamp = &metav1.Time{Time: val} + return b +} + +// CompletionTimestamp sets the Restore's completion timestamp. +func (b *RestoreBuilder) CompletionTimestamp(val time.Time) *RestoreBuilder { + b.object.Status.CompletionTimestamp = &metav1.Time{Time: val} + return b +} diff --git a/pkg/cmd/util/output/restore_describer.go b/pkg/cmd/util/output/restore_describer.go index 10b1f83bb..37878df45 100644 --- a/pkg/cmd/util/output/restore_describer.go +++ b/pkg/cmd/util/output/restore_describer.go @@ -48,6 +48,20 @@ func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestor d.Printf("Phase:\t%s%s\n", restore.Status.Phase, resultsNote) + d.Println() + // "" output should only be applicable for restore that failed validation + if restore.Status.StartTimestamp == nil || restore.Status.StartTimestamp.IsZero() { + d.Printf("Started:\t%s\n", "") + } else { + d.Printf("Started:\t%s\n", restore.Status.StartTimestamp) + } + + if restore.Status.CompletionTimestamp == nil || restore.Status.CompletionTimestamp.IsZero() { + d.Printf("Completed:\t%s\n", "") + } else { + d.Printf("Completed:\t%s\n", restore.Status.CompletionTimestamp) + } + if len(restore.Status.ValidationErrors) > 0 { d.Println() d.Printf("Validation errors:") diff --git a/pkg/cmd/util/output/restore_printer.go b/pkg/cmd/util/output/restore_printer.go index 4ab557ff0..782eb3485 100644 --- a/pkg/cmd/util/output/restore_printer.go +++ b/pkg/cmd/util/output/restore_printer.go @@ -30,6 +30,8 @@ var ( {Name: "Name", Type: "string", Format: "name"}, {Name: "Backup"}, {Name: "Status"}, + {Name: "Started"}, + {Name: "Completed"}, {Name: "Errors"}, {Name: "Warnings"}, {Name: "Created"}, @@ -60,6 +62,8 @@ func printRestore(restore *v1.Restore) []metav1.TableRow { restore.Name, restore.Spec.BackupName, status, + restore.Status.StartTimestamp, + restore.Status.CompletionTimestamp, restore.Status.Errors, restore.Status.Warnings, restore.CreationTimestamp.Time, diff --git a/pkg/controller/restore_controller.go b/pkg/controller/restore_controller.go index a0c7383b8..62c116b83 100644 --- a/pkg/controller/restore_controller.go +++ b/pkg/controller/restore_controller.go @@ -34,6 +34,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/clock" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/cache" @@ -90,6 +91,7 @@ type restoreController struct { defaultBackupLocation string metrics *metrics.ServerMetrics logFormat logging.Format + clock clock.Clock newPluginManager func(logger logrus.FieldLogger) clientmgmt.Manager newBackupStore func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) @@ -125,6 +127,7 @@ func NewRestoreController( defaultBackupLocation: defaultBackupLocation, metrics: metrics, logFormat: logFormat, + clock: &clock.RealClock{}, // use variables to refer to these functions so they can be // replaced with fakes for testing. @@ -235,6 +238,7 @@ func (c *restoreController) processRestore(restore *api.Restore) error { restore.Status.Phase = api.RestorePhaseFailedValidation c.metrics.RegisterRestoreValidationFailed(backupScheduleName) } else { + restore.Status.StartTimestamp = &metav1.Time{Time: c.clock.Now()} restore.Status.Phase = api.RestorePhaseInProgress } @@ -268,6 +272,7 @@ func (c *restoreController) processRestore(restore *api.Restore) error { c.metrics.RegisterRestoreSuccess(backupScheduleName) } + restore.Status.CompletionTimestamp = &metav1.Time{Time: c.clock.Now()} c.logger.Debug("Updating restore's final status") if _, err = patchRestore(original, restore, c.restoreClient); err != nil { c.logger.WithError(errors.WithStack(err)).Info("Error updating restore's final status") diff --git a/pkg/controller/restore_controller_test.go b/pkg/controller/restore_controller_test.go index ed17701b0..f41673ef8 100644 --- a/pkg/controller/restore_controller_test.go +++ b/pkg/controller/restore_controller_test.go @@ -32,6 +32,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/clock" core "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" @@ -232,6 +233,12 @@ func TestProcessQueueItem(t *testing.T) { defaultStorageLocation := builder.ForBackupStorageLocation("velero", "default").Provider("myCloud").Bucket("bucket").Result() + now, err := time.Parse(time.RFC1123Z, time.RFC1123Z) + require.NoError(t, err) + now = now.Local() + timestamp := metav1.NewTime(now) + assert.NotNil(t, timestamp) + tests := []struct { name string restoreKey string @@ -241,6 +248,8 @@ func TestProcessQueueItem(t *testing.T) { restorerError error expectedErr bool expectedPhase string + expectedStartTime *metav1.Time + expectedCompletedTime *metav1.Time expectedValidationErrors []string expectedRestoreErrors int expectedRestorerCall *velerov1api.Restore @@ -282,13 +291,15 @@ func TestProcessQueueItem(t *testing.T) { expectedValidationErrors: []string{"Either a backup or schedule must be specified as a source for the restore, but not both"}, }, { - name: "valid restore with schedule name gets executed", - location: defaultStorageLocation, - restore: NewRestore("foo", "bar", "", "ns-1", "", velerov1api.RestorePhaseNew).Schedule("sched-1").Result(), - backup: defaultBackup().StorageLocation("default").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "sched-1")).Phase(velerov1api.BackupPhaseCompleted).Result(), - expectedErr: false, - expectedPhase: string(velerov1api.RestorePhaseInProgress), - expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Schedule("sched-1").Result(), + name: "valid restore with schedule name gets executed", + location: defaultStorageLocation, + restore: NewRestore("foo", "bar", "", "ns-1", "", velerov1api.RestorePhaseNew).Schedule("sched-1").Result(), + backup: defaultBackup().StorageLocation("default").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "sched-1")).Phase(velerov1api.BackupPhaseCompleted).Result(), + expectedErr: false, + expectedPhase: string(velerov1api.RestorePhaseInProgress), + expectedStartTime: ×tamp, + expectedCompletedTime: ×tamp, + expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Schedule("sched-1").Result(), }, { name: "restore with non-existent backup name fails", @@ -307,17 +318,21 @@ func TestProcessQueueItem(t *testing.T) { expectedErr: false, expectedPhase: string(velerov1api.RestorePhaseInProgress), expectedFinalPhase: string(velerov1api.RestorePhasePartiallyFailed), + expectedStartTime: ×tamp, + expectedCompletedTime: ×tamp, expectedRestoreErrors: 1, expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Result(), }, { - name: "valid restore gets executed", - location: defaultStorageLocation, - restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Result(), - backup: defaultBackup().StorageLocation("default").Result(), - expectedErr: false, - expectedPhase: string(velerov1api.RestorePhaseInProgress), - expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Result(), + name: "valid restore gets executed", + location: defaultStorageLocation, + restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Result(), + backup: defaultBackup().StorageLocation("default").Result(), + expectedErr: false, + expectedPhase: string(velerov1api.RestorePhaseInProgress), + expectedStartTime: ×tamp, + expectedCompletedTime: ×tamp, + expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Result(), }, { name: "restoration of nodes is not supported", @@ -385,6 +400,8 @@ func TestProcessQueueItem(t *testing.T) { restore: NewRestore(velerov1api.DefaultNamespace, "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Result(), expectedPhase: string(velerov1api.RestorePhaseInProgress), expectedFinalPhase: string(velerov1api.RestorePhaseFailed), + expectedStartTime: ×tamp, + expectedCompletedTime: ×tamp, backupStoreGetBackupContentsErr: errors.New("Couldn't download backup"), backup: defaultBackup().StorageLocation("default").Result(), }, @@ -431,7 +448,7 @@ func TestProcessQueueItem(t *testing.T) { c.newBackupStore = func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) { return backupStore, nil } - + c.clock = clock.NewFakeClock(now) if test.location != nil { require.NoError(t, fakeClient.Create(context.Background(), test.location)) } @@ -555,9 +572,11 @@ func TestProcessQueueItem(t *testing.T) { } type StatusPatch struct { - Phase velerov1api.RestorePhase `json:"phase"` - ValidationErrors []string `json:"validationErrors"` - Errors int `json:"errors"` + Phase velerov1api.RestorePhase `json:"phase"` + ValidationErrors []string `json:"validationErrors"` + Errors int `json:"errors"` + StartTimestamp *metav1.Time `json:"startTimestamp"` + CompletionTimestamp *metav1.Time `json:"completionTimestamp"` } type Patch struct { @@ -588,6 +607,10 @@ func TestProcessQueueItem(t *testing.T) { } } + if test.expectedStartTime != nil { + expected.Status.StartTimestamp = test.expectedStartTime + } + velerotest.ValidatePatch(t, actions[0], expected, decode) // if we don't expect a restore, validate it wasn't called and exit the test @@ -602,16 +625,18 @@ func TestProcessQueueItem(t *testing.T) { expected = Patch{ Status: StatusPatch{ - Phase: velerov1api.RestorePhaseCompleted, - Errors: test.expectedRestoreErrors, + Phase: velerov1api.RestorePhaseCompleted, + Errors: test.expectedRestoreErrors, + CompletionTimestamp: test.expectedCompletedTime, }, } // Override our default expectations if the case requires it if test.expectedFinalPhase != "" { expected = Patch{ Status: StatusPatch{ - Phase: velerov1api.RestorePhase(test.expectedFinalPhase), - Errors: test.expectedRestoreErrors, + Phase: velerov1api.RestorePhase(test.expectedFinalPhase), + Errors: test.expectedRestoreErrors, + CompletionTimestamp: test.expectedCompletedTime, }, } }