From f12d05ba3c121d9e97f61985e053d4233a1188f7 Mon Sep 17 00:00:00 2001 From: chungen0126 Date: Wed, 14 May 2025 15:11:44 +0800 Subject: [PATCH 1/2] HDDS-5984. S3 Event Notification design doc --- .../content/design/s3-event-notification.md | 284 ++++++++++++++++++ .../content/design/s3-event-notification.png | Bin 0 -> 44538 bytes 2 files changed, 284 insertions(+) create mode 100644 hadoop-hdds/docs/content/design/s3-event-notification.md create mode 100644 hadoop-hdds/docs/content/design/s3-event-notification.png diff --git a/hadoop-hdds/docs/content/design/s3-event-notification.md b/hadoop-hdds/docs/content/design/s3-event-notification.md new file mode 100644 index 000000000000..5338668b6883 --- /dev/null +++ b/hadoop-hdds/docs/content/design/s3-event-notification.md @@ -0,0 +1,284 @@ +--- +title: S3 Event Notifications +summary: S3 Event Notifications support, similar like AWS +date: 2025-05-14 +jira: HDDS-5984 +status: design +author: Chung En Lee, Wei-Chiu Chuang + +--- + + +# S3 Event Notifications +## Overview + +This document proposes the design of an event notification system for Apache Ozone. The goal is to enable external consumers to subscribe to and consume events that occur within the Ozone cluster. This requirement closely resembled the functionality provided by ([AWS S3 Event Notifications](https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventNotifications.html)). + +## Motivation + +Apache Ozone doesn’t support event notifications, which limits its integration with downstream systems like real-time data pipelines, auditing tools, or metadata indexers. Adding this feature will enable external services to react to key operations (e.g., PUT, DELETE) in near real-time, supporting use cases such as analytics, compliance, and monitoring. It also helps bring Ozone closer to feature parity with object stores like Amazon S3. + +## Use Cases + +- **Triggering downstream data processing:** e.g., trigger a Spark or Flink job on object creation. +- **Data Backup and Replication** +- **Monitoring and Alerts** + +## Design Principles + +- **Cloud-Agnostic Integration:** Inspired by MinIO and Ceph, this design avoids dependence on managed services like AWS SNS/SQS, and instead supports self-managed targets (e.g., Kafka, RabbitMQ). +- **Separation of Concerns:** Event delivery is decoupled from core Ozone operations. Ozone handles event generation and persistence; external systems handle delivery and processing. + +## Goals & Non-Goals + +These design choices are guided by two main considerations: + +1. Refer to MinIO and Ceph: The design favors deployment in on-premises or cloud-agnostic environments by avoiding reliance on managed cloud services. +2. Decoupling event delivery from Ozone internals. + +### Goals + +- Provide Java APIs to configure bucket-level notifications and target configurations. +- Support CLI tools to manage notification settings. +- Support initial delivery targets: Kafka and RabbitMQ. +- Enable filtering of notifications via object prefixes. +- Support event types: object creation, deletion, and tagging. +- Support for AWS-compatible REST APIs (e.g., GET/PUT Bucket Notification). + +### Non-Goals + +- Only a subset of Ozone-supported events can be configured for notifications. +- The design does not guarantee any delivery semantics (except for synchronous failure responses). + +## Event Types + +### Support + +Currently, Apache Ozone can publish notifications for the following events: +- New object created events +- Object removal events +- Object tagging events + +### Not Support + +Those can be published by Amazon S3 but are not supported in Ozone. See [S3 Event Notification](https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventNotifications.html#notification-how-to-overview). + +- Reduced Redundancy Storage (RRS) object lost events +- Replication events +- Restore object events +- S3 Lifecycle expiration/transition events +- S3 Intelligent-Tiering automatic archival events +- Object ACL PUT events + +### Mapping Table +Here is the mapping between OM audit actions and S3 event types. + +| Ozone Manager Action | S3 Event Type | Notes | +|-------------------------------|------------------------------------------|--------------------------------| +| COMMIT_KEY (via PUT/Copy) | `s3:ObjectCreated:Put`, `Copy` | No distinction between them | +| COMPLETE_MULTIPART_UPLOAD | `s3:ObjectCreated:CompleteMultipartUpload`| | +| DELETE_KEY | `s3:ObjectRemoved:Delete` | | +| PUT_OBJECT_TAGGING | `s3:ObjectTagging:Put` | | +| DELETE_OBJECT_TAGGING | `s3:ObjectTagging:Delete` | | + +--- + +## Design + +### Overview + +A callback is introduced post-Ratis commit and pre-client response to handle event notification logic. + +#### Component + +The implementation of this S3 trigger feature needs both S3 gateway and OzoneManager support. +- **S3 gateway**: Provide get and put notification s3 apis +- **Ozone Manager**: Provide get and put notification Java apis +Provide get and put target configuration Java apis +Persist notification & target configuration into DB +Notify OM each operation that may trigger an event notification. + +#### Java Apis + +This is the group of api to access target config: + +```java +public List listTargetInfo (); +public void putTargetInfo(String volumeName, String bucketName, TargetInfo targetInfo); +public TargetInfo getTargetInfo(String volumeName, String bucketName, String targetId); +``` + +This is the group of api to add/delete/list event notifications from a bucket + +```java +public String addS3Notification (String bucketName, Event event, String targetId, String prefix); +public List listS3NotificationInfo (String bucketName); +public void deleteS3Notification (String bucketName, String targetId); +``` + +### Details + +The following diagram illustrates a write flow originating from the S3 Gateway. The red lines highlight the newly introduced steps added by this event notification design. The design introduces a callback in the execution flow, which is triggered after the request is sent to Ratis but before responding to the client. This callback is responsible for handling event notification logic. +Add `NotificationInfo` to determine which event should be notified and where to send. +![s3-event-notification.png](s3-event-notification.png) + +#### BucketTable + +Add NotificationInfo to the Bucket table. It is used to determine which event should be notified and where to send (refer to [Rpc Message](#rpc-message)). + +#### TargetTable + +A new table in rocksDB to store the details of the target. +- **Key**: A target ID corresponds to TargetConfig. +- **Value**: TargetConfig (refer to [Rpc Message](#rpc-message)) + +#### NotificationSenderManager + +The NotificationSenderManager is responsible for caching client instances for different targets to avoid unnecessary resource wastage caused by repeatedly creating new client instances. + +#### Rpc message + +This is the structure to determine which event should be sent and where to send. + +```protobuf +message NotificationInfo { +required string targetId = 1; +required Event event = 2; +} + + +enum Event { +S3TEST = 1; +S3ObjectCreate = 2; +S3ObjectCreatePut = 3; +S3ObjectCreatePost = 4; +S3ObjectCreateCopy = 5; +S3ObjectCreateCompleteMultipartUpload = 6; +S3ObjectRemovedDelete = 7; +S3ObjectTagging = 8; +S3ObjectTaggingPut = 9; +S3ObjectTaggingDelete = 10; +} +``` + + +This is the structure to store the details of the target. +```protobuf +message TargetConfig { +required Target target = 1; +optional KafkaNotificationConfig kafkaNotificationConfig = 3; +optional RabbitMQNotificationConfig rabbitMQNotificationConfig = 4; +} + + +enum Target { +Kafka = 1; +RabbitMQ = 2; +} + + +message KafkaNotificationConfig { +repeated string endpoints = 1; +required string topic = 2; +optional string saslUsername = 3; +optional string saslPassword = 4; +optional string saslMechanism = 5; +optional string tlsSkipVerify = 6; +optional string clientTlsCert = 7; +optional string clientTlsKey = 8; + + +} + + +message RabbitMQNotificationConfig { +required string url = 1; +required string exchange = 2; +required string exchangeType = 3; +required string routingKey = 4; +} +``` + +## Performance + +The performance impact of this design comes mainly from two things: + +### OM Metadata Read Overhead + +This design introduces additional read operations to OM's RocksDB metadata store. However, since most of the metadata is cached, the performance impact on OM is expected to be minimal. + +### Notification Delivery + +The speed of notification delivery directly affects performance, especially when operating in synchronous mode. This design plans to support both synchronous and asynchronous delivery options: +- **Asynchronous mode (default)**: Notifications are enqueued and handled by a background thread. This minimizes impact on client-facing operations. However, as delivery only occurs once the client has received a response, any notification failures (e.g. target unavailable or delivery timeout) will not affect the original operation and may therefore go unnoticed by the client. +- **Synchronous mode**: Notifications are delivered before completing the client request. If the delivery target (e.g., Kafka or RabbitMQ) is slow or unavailable, it may increase the end-to-end latency of the original operation. Providing both modes allows users to balance between reliability and performance according to their use case. + +Furthermore, the creation and reuse of notification client instances (such as Kafka or RabbitMQ producers) within the Ozone Manager must be carefully controlled. Poor resource management in this area could increase memory or thread usage and degrade OM performance over time. + +### Metrics + +To help monitor the performance and reliability of the S3 event notification system, the following metrics are proposed. These metrics are grouped into two major points. + +OM Metadata Read Metrics: + +- **Notification config lookup latency**: +Latency of reading notification configuration from the BucketTable. +- **Target config lookup latency**: +Latency of reading target configuration from the TargetTable. +- **Total number of notification evaluations** + +Senders Metrics: +Each client implementation for the supported targets (e.g., Kafka, RabbitMQ) will expose a set of metrics to monitor, debug, and tune. These metrics will help users understand the behavior and efficiency of the notification system under various conditions. + +## Configuration + +This is the configuration to enable event notification. + +```xml + + ozone.s3.event.notification.enabled + true + +``` + +## Compatibility & Migration + +This design introduces a new feature that does not impact existing system behavior or data structures. + +## Testing Plan + +- **Unit Testing**: +Unit tests will be developed to verify the core logic of the new feature. These tests will focus on individual components and ensure that each part of the feature functions correctly in isolation. +- **Integration Testing**: +Once the individual components have been tested, integration testing will be performed to ensure that the new feature integrates smoothly with existing systems and components. +A Kafka & RabbitMQ test server will be required to validate that notifications are correctly delivered to targets. +In addition, when using synchronous notification delivery, the system must ensure that clients receive an appropriate response if a notification fails to be delivered. +- **Performance Testing** +To validate notification behavior in a production environment, system tests will be extended based on the existing om-bucket-read-write-key-ops. Additional parameters will be introduced to enable and configure event notifications as part of key operations. + +## Future Works +### Moving Callback to S3 Gateway + +Once Apache Ozone supports read from follower OM, the notification logic (currently located in the OM) can be moved to the S3 Gateway. This shift offers several benefits: +Reduce workloads on the OM leader. + + +Better reflects S3 behavior. As the S3 Gateway handles S3 protocol requests, it is the ideal place to send notifications related to S3 operations. + +### Strengthening Delivery Semantics + +While the current design provides delivery, it does not persist notifications until they are sent. To support stronger semantics, such as guaranteed at-least-once delivery, it is necessary to persist each notification to durable storage. + +## References + +[AWS S3 Event Notifications](https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventNotifications.html)) diff --git a/hadoop-hdds/docs/content/design/s3-event-notification.png b/hadoop-hdds/docs/content/design/s3-event-notification.png new file mode 100644 index 0000000000000000000000000000000000000000..f943df2cf2c7ed790bcc812e28499c7d9e4103e4 GIT binary patch literal 44538 zcmeFZc|6qX|35rRq#czIDoI&VvV@RR5weth*CIPv#y&`$NKIi-Se*d`txbMgPyT6aqqd%C%`~AMI*R?#iE85slXYVed zT@VOlukQ7$w;+)1EfC1IzMWj)6^B5ACQ+>EL9zwVfSm&B1+W%-zgPe>b?||2h9I>+91*)mfu-^v?5=Tlj6p zcN(DNwY&O9aL>}dp+aeBB)HcWxfAqp+Sb+XYoD|CUXM)RZB@KuF2(Y=ybS`m>>0K1 z(0S=Y5lY86^t2D)o>>*IS8?!wZy|Y9O$l=;b;I-WWVJW6WE1 zYsq;#U(P*FpV-&Q&ozZ#f;Wsaa;{wK*8Aro|1{VCo$4Bd&wb-D(zfxc)mQKy+aN}N zdgJ$1#8c_s)0w!Z+4@NtEMhMPFYIwtB;x0n&PuyA{FrNPTFriSO<7B<+0a-E*U8Hr zJ0}{7M*SBiF^371gL^Guj@94q+vo_`Ur-9!VnlFGxv6+f{BU8sWnA4&a~Kgp`>oFM zmsfWRVQxxh^P|?5#G;nbt*M&pN+C-qMAX!`X1egVNKP=F;5kLyEd1%Un)N@-#%dQf zcWbLH)I0e9&~?^Pn^Wq}Y=M%ovZ_;?U3^n!0qsOuxr~ihzS)}qf@{dnYZf}{A!~b5 z0?o|YPlW!{XY+WPhKZyt$8nxQ0fdBLbtJvn3C zn9D8Z_gwAfw}4djJXeR!mzK0mHHWMEGANHhRA^uzUxCvQ-?+div){Egvp{XW0>Q9t zY$`2TNZ+Fq+0WYw#SDINLs&V-aKX_xS4Wj25He!ns~wahgWX3MqU4Ig_yf_aViXRH2%=hMHsXIrf zScT`$5WQK4%TiTmZ`th`_EDzMQ%|;dM=)_SlxVqYwd(60t3TY)JXkb!$|2Wzbh8y! zQ=`UOO0~0^w`h7(Q$9OPw@t?BmJ-b^OvV(!Me@a;kuQZ!4DGjT%&qYt6-MZlHBNV& z!E~0EABt^WdFxoe=Fm}2w6{fGtLjk-t)Ai2-=TS|jR?)&PFyRoYkT|Z_I!3&SSNQh z)I@_0^Pd9cwFB38KX(89S z5SC!M*5(`$M+sZqQ42%%F43A+8pVhK)$vQTAY}O^g4>Xl{80`t!LE8iT}u7%hh%rC z1iQbJ%nmG8)4d37EpoIQ;XZ}>&S%POwBBaxxoh=vo_FletU!$jzKOw|x|@$@=sxjv zQv&hLvLy63zGUC@02ED2$F$Sc?M|3lW@ftOeL}PJWx>}&XI(&J$e8_Bc++D3X<&*~ z$hD^;5h2D{l8&8Xh&%4H`rRMN@R3<@#7^vIGkF*OK^=#XTKQ|LloI!H6G7e4mB>$g zv{iofNxz3_8NTBN1UwSw370*)_Lk4T&_0_T z!W0k8lb*-D4n4&+rO1CR?~P8jua`pT!YJi_gwRy=)Pxu6lW{Go-9B8-(n8Te19IxBMDdL_1` zYBxJe8w53$da=|m_^h*`OI@dow(!iwpbCwsi0%jR}S;_baflSb3XI5}2f|)lgmd9+=uWr?VxyX+h zs~k9Hg`?x4^TTPJmXc1)!&VBf?*w9ywRJRmKLV42G>WVxKoM6}G9SAIhrKJ%MQ~`# zWavGiZbgu`Sncpjwzpf{FbQ2Usg_C`%&}_>u>f0t++8`YDdv=DCGDu6skW+nJ-vl$ z8_fJ7-~jK^+^qgtr*ZSmdMAT|5{Dm249YH0=)K_JkQ&Bt8iI3}FPCr*7+PUHM3RRx z3#s(?{5=nNN{rq}Hp>&sEket$4x_zWla=eIG+vE)etQsj;r)8e)Z6yVpPjC1A?5nE ztJa!A>+(YC*u96K7tS$QGM)?@uqkxoKQN+v%wk?cOMCQ+an`kN_?9sYoVc=;qrLaxdL1VVOck}!k z|1orz=$pM* zBKKbNaO+S2G_x9gC1BI~r$nmNC{YH3Zl=JW+NG6xXCCm7)F+URLLkSWoueNVsERG= z_X`(_-8<$Q>Xuw1MI>7Ivta5MXRExBOgOyEC{l+QWHGVgf1+e6U3)t96=eItl&v8W zv)t=m3b#*YzEHB6+E6L3EV)KUxwJpFTs0kE7GM~iSzyeVZ5~=pef2HF=FD+y)nk;< zE3^8bZJS?y1dd#X!^u=eVXJvyK0F!XUQ(kCWrmi_CR)XwmaBfS)d}NQ8onb;dvn*$ zJlFFLbV<>x+Ik@UsTlOu)CI**9qPL?gDh1?^(TgTZS=s+ke`#i4Sli$v7c70=Nl6i z<4ARwnxP$F>LdA2?E{;|#&r7c_G2akKju^shB^M_cx&=L-z~~%j_~!*wZsq!T)@4L zWW?^$lP&j4j`N(8^fs1Kz}v(ggRd9|YGKuP28_|h^(u=q^f##K$XlKzHE?e;D+7xf za#K1gSHAsCK=}K!aP*lZqew-e@>iC2+sj&4IxA>(H74ZhcqE@A@>57ze~7T>;p+0y zbtl=-xrieD>&Y{u3q~3=zS^+aDRX~uqB4;9YD$}oJfN6H$%7ksGk&kg=)B~b4kMos6egHi1!KkPLAEvl|ZPa@RW!Pj#!NZddX z%jEGFKaE=B$%OYJ34Pa#vmTDx6HZgFA1BV+52R#=Pfj|bXrqM%C;1#DP$A{Q7N5IZ zw+6#W+L7UD29fz*9{HGW>s2xN_GE2ylxIW5UVeKLnD_N*o$8Q>Xt~8l@39Ode!ti=Fd4b_C#${AxibHp zbn@nyS)emEscBtRq9~;McgnL8XID2X9iI1t2IUGUN=XInR88Sj2m-}p?#$ z(VUoRHJ_>NC5K|gwSL`F*lUBMO3j+XW+QnFL3ZD43FF)19s z#4DMZ9E1*c^v~vR3>Y@@!LGW&0<&l0nkTg^%m-3MKaBU@)V@DlESr~)cG6R z%FgsOgd0k}6cX$^cxkJtYyK(4pw@*3#pFPo$yM>G!W!6H+Dl;H z9JLwmTuz>;U$!cSrSU!iT|fJ)>pMx0Z^UzBu77EvD*H;RHu%I)z8sY+Oq4g!(b!rR zmhST3TY-wR6c^L8R*W^4G->E6Drn!}zg$fIQZ1{No^+7y$WJ%c4`2>XelZO&c|$?kGyloA_?-@L;nuMT_DBvd)+<#jlg&q8*<-&x(- zP#lSWl3-@w2M<^|qwa1=SnV~Zkt(!H#2BKmz}T}0HJA5<8hAD~wpl^MmVmK)H}5?6 zp74Gon<;F;zE3b^?``nq=Jzl9g^`Wrqw3n=#o^yneTw&Uz2)p4}eX^mPgtW z^T35#n_4f;8c5P>ih9T@YdhEj?q^DauX4P7 z1USCyVss1e)os6V?}b2KYOEr_S9d(#Cj>48`{zRcYntwyL+RF#e4aOzg{YYVU2F`w z9?5>VyJ4jc)!j1=Tlh85<&a(5c+P>+2=N#piCN!YMMSuUg0g_@c*uE#U3nVrRJ0AV z6AoUA3_H%jE}v9>Yi5M!7B{0_VFPVuiI(z1B!ZXM z9bYvqqge~C;Z<~=3ld3xrm@b{$L}Qort@+@-_GrjL8Ax0G0!aZk-{~{IUtd9 zIlNquqU`Y>hj}Yf+>|}8fRZ2965j(c*8I>Rd*Y7lkk_{-V0x~6qzx_?9+hZwwbiZi7qn79NeIX6HK%A`YUhMBsxeh+^?9A|imF)@SkW{EZx&QPG^-;)>KJ|2bBvixRe z^y;G#O=4vWq#m>*x^--XfYOO~`JJQmnzOw>bd^Qh+Br&d4+E@IOb>dq%`sT?Y|W2; z3?OazW#pnfBY+a|h3BstiOJOtN#=wujH=^9p1#HWj(ZW?_98+?753v0##egZ1Ih31 z-tQ}fMQRpgE0;KfdbjV}jSZy|!|LxFL{4WpkIsBa?12Y;sVLM|qcnRD20r}V7Iie} z2hRsacUZqvi3h(Ly*;G+U0Z1yJ*x<+K@J(?sMDG3i_3*!nlw#;J#e z#OpqxZHW>RSvVr~y|C4#iFZ}GH}AGxTiB$uw>n>1AIZP~ehs=Ms_f(IKiDx{SINdk z?=^4bVWbf9Y+}g2{ZsD_r?72dY}#haB7+M?dUg|Y6h^P(y^~zQvv|Qfe1dd6JCBKT zZeAMB=68alm=9;-bg}}+Iu-_aob3*JbcHUMn2c2WCJ)dCT^dQ>QcOhXy&8doREnS3 zLf%5qxznM&i6*&sjl{lp#?{@qp+FtbJcT8P&Me4-pX#?tN7maN0?2L>E&aGy4I6|v zY&?z40$w!Z797@sWNKFW_`d>xB|B^WiYWpqxzIltW5su5Hewi;>?D3wRA51r8066A9P zEBF3?)zWywBQ6~xP?8?_8}U48ev}}B5^Wj^VEt&yg^QUdltpN+ZYo$%KGT|gJE~Hs zHur9(dd!T)$RTg6t|#r6B3SULb;+PcOl`_>&*tY)p^XvVwPgEf7^Vg(8#_W08v32l zdjv&vW7zRW1I|h;Z8MHgtG=eGux_sxRwLG6RK4}Ry|HrXotds1*jVJLOsA_^2P2VE z0}ln}YOk-35V9>gUDMawGWHj5ULV&h%hjoO?kyy_J*9{>+SbCmA)Ct%%~A`{|+ zdP>Q!OAS-(y*?KKhO6!*G~W#OBE`lvSx-gcMMD6=(~q@-7*0~hvj7`ueJ1ep*GaoH zB2DexGOb_XUOD+R$Eef7cH50c(NGL(^bR3&B)qJ$t);Uj>5E$?p8?aZ8r>y>zNJ8z zLlI{x!d(^#ei!zId?lrn!Z#aqy3Ju6pDO99KrQU& zo35coR&xo$h7wV7>*o{fNjow%u&=pWeId*%^h&^pEU)Cr7Qm>B{x$I*{!srjt6k!F zKt1&11w?(KPsWQjb@GI}Z5nf*dQGKAIh4Ep@r}HY@HB1r>6r_K`W~BKkx39TN>4j% zc@q$uLgf`}bgz1cf(%L5h~&o_s~>HWJ>!}}%JiS@Q>RpS%4b6~)#mwAgQT=xNT%tP ztcu3U6F4n(tN2tT=u7uz**eD5KFy_W@X2b=sufYMY~&4UK4*AzerkEriZY?UsMw*v z)4Q=N%d1}OHjG-k#rKsE7)}#^xMtU-;9GjqW@>`xJ)-ump&DBLdD&IpVke6S&{aUK z);Vc#nEzIU{+%=s--OkRRFp@cli|E;xJYRZb5wbK50{tlT}s-Fb|GNJQW<+BUbFnn z#Du+SkkSa>@W!En%ZP@bcrU4DJ`3~>Xzo30=-gI7>X?5m<0$jN+n_%ASb4y2MbI*- zS3bRS&2D&SNgdb8p@zrGfab|zI=_T$hg=T*Yi-Y{)!Ui$+C`xkUM2OmFK{vMiMfFf zc4oT3#@j)+b@ieI%6aTc98ahFNh@;LojLyc7@e_{(}5k(+QzALrE*-VeT0rpz2HFp zq*vu?f!Q4hiQz|+L!5!KoxfQmQV!k$L)UCoA+W+|2wx1P5* zbhAd*ycOeG#Y?u(vueut9AmR6)$PrPsE;0M7g5C{SH+ud@w~4&Bz8MR&rLCRKG6YD zpeHRzbWD`xbDHE?T$i->A-s?*u$Q1&7cRWRJ=bDpLYW~=Z|*qN-u{%=v9NVkb#8rt z7^!8 zk1B%2Yac`Q#DAl6ev4lXQFJSs()dmcU4KPECJ@%`Vy3Lgo-}vFsY11r4EYlD`gKGg4z|zViFrET0{ZU9Oo)H z3HKz3L-2B}f~TkV$0|hQe{U3%2E&0kJ!?cWG)hg7Ew4pS>77l!JezJyys6UM6TfWU zxb(7M7ECJEfc|^1+29zw0yC&VyNllHiFzK6w&abQu9&& z-lZ?`PH%7$0qPAc9E{ZQ*_g$!rJK<5+1?@TNNVj;1i5qPgJc~C@Wyj3)G2@~BG>-f z^4IJnd9|erm0`ZR#lLzLG8e~J89dv887vL!_jq1aJwTV7lthAp z{)$Z?A>oNg!l1vK1WB)(ia*to{=BT!pi2>Dft#e$@SK)BN%jOC(OE8gYf-eICHRgV z+Gak)-C*9Venk$AL17h~K&ez%D9_)5F*q%8s$|AYRdQrom)mn>!p#T)lD1^aEoB)K zUpE9t_{Jk|qFwf+E}66)r+-73Ht=Tu#fis`jm_bkGsp=;oDiXSN<+!N{Jqz$f(QmL zt1ew0ZndAq{EyLnjTodipvRy(bW1p}@Fua9ZjUW-Pe~13>(mMsMos9<(ARALLSkKb ztAD0eM%}9q?p}@PWqSz;y_}@SbTt{!!REfmmfvnwwxAL0O`O28pq?_7Ht^S=l=>46R$$ced3FnDN^}DKJ64rDY(EjJ( z^Q(k8k8LuF4aN6vbtN~stm3^dWZ9bQM=FXC&~hvl@Sw2>e37nuZIhusif+zJFd9m1E)9oZ#B(O5HpgsAvZ`-X@rETJ#O`kTMJ9J^ zGTYjtX<`8%1>rrbnbeE2w7JO*fg2-f*5V&nDlK>c=6cTj>{;9;Zh*8^xBmrNubJT9 zxvd&>MDwL3_BK|N45E`R%~V`OzHs0#BYPwVxn|&N`1nIT3p&ZLhPVsOw#wN4F3o@L`iw5szGrlie4^ICAb1V8VGr$sCC`rgm~EA)Wxtj_KUmZtTZr*6 zkj4cdd-YCDk`;HAz={Lrt3`S9duzK0COBiVk7+=?g{DVQbG4jam|1+yivkaKy($Xp zD0-!eq^Ehy^v+aPwAsQB8L0RLW>8LL!{|t_>a$WYcWvAA_uIx6NA&r6B115OaJemb=rqKxPhl+(Wa``@I#QxSMGh6)S-`jrsoy`dKy*mDjm^pSK z3eA0IRO+l}&Tn_$eq=K=GhYt;Vsid}{+PP8Uc_*ZG)&xy69JtRV&hi%j^z{AHU8d+ zf3mcn#s7D|TDev(x5X!0IWu8qcXytecR|};5Jmes_K23BUsK+zy7+}Z3QAR-j}gly zh^3nbqdrCsMJ_o^sDCZ5%E6=Ecnq-qg9gkR(>TM#kn4Bq3KTup~uGacV$OR$ISGT7akBA z*woK6go85RPqcw`Cy6k6%En`IbPL}ealOd_ctxZuhSsLc?k*V2AzlDj)FV|0V-qM> z>#cbf^W;!A8+(Qd?XnB;(`m210(dHgc{(A$-O4v zAb+~CcQMa)_>35{xpM9awl%Q1d3W8EUyoufBO+U6+LxySYoQ;R{JLkfF|fK{%KZ=S z#%Tjc(}$>YD*C{VM{}VBPx~#{czx!;n3zP6v195>64^~=rU@RjDio(>2UW^_k3ac- zre{DQs~jc>Yk!pNtHCkqLxbgkc~jf3_W~l6=#CxEUC|~QpRthTz*0K^RZzYxh?;)= zi5a?RPPpbBwl?kPu=2i~a_kD2D*u#SPK#AHEBVMN+C6z1m@TRye>n|G6)0yXGS`rjtmKnrgqv2I;}83UHVK8b0X zIp%NKiiuxh$ z4%;@8wOe*XQ)p|wE1Kr7#G%2)qh#RMOF3PLaUkBN|LKf~9q+*u#uJ*2d{Mw9H7L

TYcs@N5WVz7+hg0~a3$=fD;f6zp(MuW-N)a|R6`G7reB0k|#ueP^D`SM%4n zXnCQBYfgB-FNTehH5NwO%HCOIs{>w3nlKMG!4#`zdaXfZDbdlLsP%|Q?O^|A)HuHE zh%W!*%ASmEg6w7RzmA|3={=TTW4q~`#ysz}pLo_2pX?1JECawcIgg8|$az!1NABsP z*BV)xt<57?vUo<0_z1bntFG!6>+N)5z0=d|p!@G--9dkOUCy5!75ZJ{XZ1>+jabmB z5Mad|QH>ao%7BmytO5vn69RU3&d|I8N>n3-(C`k*wx-DV3m^F_qyFnWL;$XwR^vc# zY~~xLyn$zyJ0&=(l9l!W3^G9VjXZ;b<9Q9UDd8lxMa3(V&mX|F zMP#k!%hQFtx96v^YI~pv(0>^Lvq(4%nf+GSdp`8bdCJoD06VOknDb-Fdpp2RE zC04RU?<{nCL0yBAnMnxJUNp^4g(@h#8rXy|XXL{N6gZI|!Q!q4GXC|8wB!e!GZDp_lmj=oUk*&GuQPzkAvw`5h z$$Kk!cnx;^Kc^cJ;tp%6ihvDOVz#{0Qlu(FT?JY^Jz-F zG?ysS6J=Yl#?^0$w>}jOOxHh1ZE_@D){9r`4^@;GmMfi}phSH0fzQ2v6mNLxux`9q zf9Rb5o!GJl=8@p3?#eON57E$fH?2M0(O2TjPv-kLge^!1a=Vw)#6S6X2amhf&N~-M zh^f4r9c|!Au92nA-8SejS4pN3`X|#@t*3hH=Tt3#Q)b_84gCePdbW{7LubLHA3+oK z>9>Nj=E8sG|7)B3@7w?T=J%gh{{#X5XS9O|9Z46Hy+Ai#h^Ud}j@;x)_%DzG@~&O6 zJbY9>FM|1YUJ-Ke(Xoxcqa2`uyHwMrQv{i0bS;W2^3H*iVtBvKVP>1R;}1bz zN^33u9X>%K{g?7uUa+DkTJ=|`fb3owlPjgxmIFO#{Lf?Gb&Yd22?CjqGhjBOv zGVvyoE>>{wQxMUz{d>jdYy#udiBm&9clIB3QH`qmdkbFP06C|tH%ca3MKAXGbNc+f zg(8>PZm-v-?a}HFa%Oite{m1~occkF5FoCNSDC>U>y`6xnj5%r z^{?aVMuyRC25*ka(3(M9#a6^1Y>Me@JaYBxVm1m5mi&s?L7k!C9e0ERJ*Z_iSq1Og!CG1i8yS^!v{_?KT z-nB;c!f<-KV<;BHiN-rAN*+P)dsKr;=W_uZ5R{RN@udE_+56+x^s^xg5$j7Ib}k-L zj*sW^t}nMi_v9Ev(E<)NLdl2cfe01tbh2fmbRqGgTaF;l#0fb`qb*ezap$!BRCnpQ zWe~7A6BIE@sibjGUN7@34tR&X`8M6<-9RXJ2>fL5Xkl{91XycbPO_ODFk%OrR(Tfd z^Wgij!O5E?-FM~C0%hb~q{dee0V7!Uy4H3B-2(#GDsg31*Q60O=I@)H;W@vly5bAH zm^F=af@S0vCtLcivpFR85Dx}mxx)*!b z%(TI6xZ0t37Ny0)1u}!yK7*9o{G5i#R(wRk;FlN;{cKHo9Cw~)!00FDmz;*MpI?ol zdP6{{>Ffuyel`DZS{Q(E3A3D*&~pA;AU)%O5DN9a!(?Nv`7S`Q&fL*5Imx#57ZzMn zvi@7Uu~F!F$F%(fzU|s!yGzz=gKK%<5h3En%n zn`Ih5c4C&L5kew?Z96cJo$dWJu^R!MYHr;ZafeA&=GM@!GczW=F{7KCPdn+K3@?gX zvti8n{I$ih7zEE+z4^AX00V+0;8BRqGRV1IUTRBX*MJ_=Jm9t+7kk+5PO%FBCXM9S zNkVA~q*Ts0bEDTdLviByd3bCkBe9mA)p723?JEF){5f3W_#88nPOA(m+$Oqo$f4KP zxJNp&6V6E>fvS3(H0*cSLMiK+24G*5WeCpyytJYU6z7Y9(a-T*NS_jZrsGuY`ML2t zVkHtQ@dn~Tk0+xgXFxw4%A)X0fXR$~*<2qR67ZwWOWC04m+7}(Z&=G$aPufg=qk}~ZuWH$g_j+0bhUHPUb#tNNu}R&fGt&;{ z!Dxs3{Eje96gGYzo|_>^XV94g?kc3V#u0h7UD>Igj2&MM*prbph=N$o(_95?vn=7m zN37v#(&1@wp0Wd>tN7j=475(~z@#{;*O1YQ^pRTN9|sV(4K)fId-SNcf4?=Iw_C0> zaMvoP@C`X-EQppDJtc(1E`v3J1UL&z-SADrKDWlCgL1VKk)84x5V44#*J-1#EP7p1 zrMlSp9Ndkx_2s4^jry6#-8q1dW^clNI_U&HvV7J`QBDqgbHK6Z4?!6P~y za08!{TL`Y-Ll7XHa$^SAf+i&kuT{u$y{+e&$h{7*DYW|xM|IU{sdhk-fV_c>8<2+I zXq!^EztxVNpB4%0KI0_}VBUV)5Kn?aJ;|C}SJR1xD|47<__mApa!_wzj95ZY$GQm{=Gxy`5aDWH3BdJPaz^ zH(7=tD0OSTwv*1-KbQg0XFe!maJyW!@{B(V#q579g$UIwdI5q}u;J!0+6CN506J8} zC~}C))y6GxF7RcEGAxUDKa1434*A{V-&GZggSa2O+u@WFC~2|QrmM=)AL{=#Mcryi zV{?Mwg-`F?n62?qR5vDvyKbA)xomEA*Ksjzbba+Fn@q9GD|4S+)uu2&`t!4N#ni>(?!0+G7GDwSB1eK?$2&Obs1Rt!!%vVyfIjp**&^`m?( zP&?>1b2cN$Mh7dEU>(%}6glOISpz28FmBT~vt?F&srd6y6Rr5neAav$nt#G(T0c!z!W83lz0d7rgE>(776zi%>g%R11=Mo zF?gypU0;-vW8EcMR5@@RNLY3<=aF58f6g~^gd@oErKyJKoD{P5t#B(O=eN{>^PZZ$ z4$@@cCD5BVs}fCg8ZCXSz-k!fwAAz`jdIm^VkORjZojtPJ3tPKT2TL97BkYo$O<1q z)tKZ^x94kD%`-Lw<|~_q0+%>;4gYJ(?y}~2e~I+NZzP2Hr9-Z9;^&fAnyC7{E^lCd zqQF1tc|bo&5v#8SipLtMnfFiWTnAQ5c61fT(!ODzeF@l5B#lEvj2uyo3xUrX6HEB~ zh?lgtu&nm3nIA^Dwl;LUc0JVjM^L>(I>f!z22pBPTX-hUdfJGk3i!XD=kc|GpUOO4 zs5-!Nw;XsO1W8DB*P&3V3!i}l&YjQk^hGV(gB@;wMtoT)wr^57JCXGhx)gfCR6jCb zI{;c7MQNE!J;OVwE?B)c8^Z9+ir9#arWr9;jl6bM4*Y*Q#^H<|s`>VmabL{7I2toJXG z;omzHnb{vYPG=jx8`j>4F84}*cay`ey(Cd2vq%KEZ?)v73RTJPj0Q@eI2Wc(*E=Jy zwB5^`nR#pwJ@I4Kq4DNzb;|XriFmF4qNiT)d}lk-RU<-Qbj~P3XIJqHLk+N-NR+XQ zDNH;k@mlANe+nThyGBy+V|k_W80|5`R+ddPH)j|=fj)KHsyj`5t<6)h1#f7!?owmV z1vX=5;-^cTB|fJ|6*++*rMkYUNe(os{LFVc$8TMmU)wjG6CqbV{FG7HUHbC?<`D0yQPi5v?#Rgq;? zCcNcZ^E{=U0Pfa39ohVdbOonE)_#({kt=+6)^Bq!XH0Ihk;HGu`TxDszX`k-_#FOY zv8Qire5HA%8P081g-v<^ALEB4#mr-~@4lpBzLaIDl}3PBgv4bL^7x5d;Z4I#&e^dg zu*_C#pPP3kV~#IPkGm;oWgn8uTX>7Xtk1kCI7z3n+Ae^FGDRHT)DgSsQjh)~YbdH% zQVz^CD-H<|celJO!fZB18hRx&uHdqMEDOJYNbFco^8Z_LhVL2lLBMG6i;mFL*KYon z;%~!+d_zIJQasN8t|l^?e%>9Bf2AYedV5d3YRG;VelR8?OO(SIaojwio^7h34pHc% z_S?$DRu!Uz$C}rMH7AKDdFh7q zk#*MWJDUaMz*%T+d7=}u?``8_^vOW8utpZi~ z{V%YO&HwE@VB^#KiJV8& zZY|yZlW=n3+ONj)7uaejyOL@+b3d{{;1T>de9a@E+nnj?@yj5<-jFF}gzQP69Ckwp zXs`Z@%2IeFv@x`o3%d6`URNck%kz;=*=>z~8w>tn)Bd3g|6k%mZ2{owji~K@w!QV^ zMIZ!jRqc?!b_j%~rhX!1%~Z{({-vYjoH@qvq*AK|eBh6CpTdrlEyhYaEq|%gkp+>QM>@GUK(6JFh+p5qmyj#N z#~j%SFm^I9eH)l3g*jz@IT9|LT;8_F=}{yI6aEnoblG)1@~y{#_rLUFhye%Bxum`S zT%6;dX8#`w&VL3+iU65H8`Rct4FGywefT;m2o5G~)@*2&uYvgC^PPXD3~dKU44}{8 zyb1Q(A^s}X!u9Uf%nDf!R;Jy4W79taidXVbnn-p)aTo*?bGk4}yEer<^U#~$xbuI7 z68%rCM_8jqK`3!Xulf{UZEY;X`!}TKNRJG@{wjU6GCis{5 zaa>FNuT@^UQmA4{dV>r6CgBOaUJcZ> zWWTiV7#X3jAXt}-V~2D@R#wFL7F1SpOQp$w1R*j;c`aTPC&fs50-pV63UmSUDU%yd z3r@AYA8g?T(rtUO(6&;qdO0DGh5WGWkjLd*Z3HZCzrBZxD?%nAwt$Ym8Uxu)1zTi1+C+5&^;xYDFgpa1sZ4QYLv4G84 z3JP%lYt#bXP#bNxt+32Qh=2_<_P&DH=ls)ZM@;R9%ab9YI`-<=WReH5Dq<=z|Mq#GGi&eCq?I3xAP zLe%Ny%r`E)5I&e(G2n4l6qezBae7`2ok6JpPRFb4bJLtkdiG9=Mw{Dv%F^s53fvB| zl@s=9hV>C+^p*?Bp2VduE0oku75b_xw8v#g5f@+*IFVfo+>P~~$UuYc*Guu&1nfff z@1*hFwbQ9$v(Q}ndpCf;P|N_3PRa5Ohp#d&02v_{IOgL2kf+-4WF3a6?~Q7pt&Xgo zWLPmoLdr6*7uZUZyPa@u+zn{X89W^gpTZy-S?VA)jAFbYQ$j8ZRSr-neuL*xK(CIP zPc>u0_;k8&9Io7*bB5>7rQMYo|Fb3Q8)>?nRbiG1tHm|{#zQz9JJX|r8(|Avh+qTK zpwZTmuS%ib%8K~-RXXQ7n?;>=P(@TMn4}Gd(&7vo$9*kofXwmOI4^n4UXFe=Rz|wR zWZ9kHaeiPi&(L>TRfu;;C520O^E8r$SP_9W_mD?a%n453__g3XuV+{wpD91UgvP1Z z=9Z;BT$5MIT<`?GMEIMt!2`5Q;IPdRS~T#IW&`!`6HGGG=IaHAF&FAX@-=qWGXDi% z$PjY05#IvMLp>FiQ&7_dR0xsA;PD|B&p2=0t zX`)%fAV&gs;C|F%ijA|k?-b2o+6Sbjjm-V!Y+FaDvGf z_1>z)0+Y2ABX3#H*vxSHjZ~r7ZvUQqC;Zdh@P=VoG~0UKEX{*+o_S-WeB9(j6G(TP zkrcBa3{~Vnjw0bEW3H=>e_Yy2bfHHF3J;}!ksd)oHxC9%ke7U|YgT=w8^n$%)a5SZ zi)pRiy>jWy7_h$DS}`+`>Yf%Is1)Yb+UWL?l-$cWum0nYcAePuQR~LD=g)I;Ikk3Z z8S^|pc9TPkgX0kIA)Z~k_^!S@re&OP{on!4ec)%u);&f1{@U&5R>su)7P{vnP{dL! z9Bv(24!amChP7ul_pW4CH8wPW|E1;;c5l-zNVYbT*0@VjWo`E_KS@1tDJG$|Rh_yi z9pa;M__nZjvlBHpYW=ml{pH1@!V|BQ=N>taS$NdlgzoR}5eJErk*&wyfjZCLeMo6niLR$b{eIoLd2YP02&rkO1F_8U&3@qRz& z1(UDg)t@nWHBEnhce<=vEZ}T7%H zw7Ti<7iv@=Q_0)DUJxyyt7)=ecNu+=SM+7NDmV(1#oOfOxnk?{d7r^}m6cj9Ij?l5 zuhDlCzg_YE93W@^s; zj^R&mKRRY1VCNT#yg`1@WOQF>dN?EN^>G;wVM^)X{%)>UtUIm$S9|Xr)ztd6i^4`h z1q4JObOi-PN+?PPm8KFv1Ox(vUPX$4^rCbUKtv!266q*SI)qT9mq@SDiImVohfvPK zz4!U{@7(Wv=iV{yx#QkDe)+?}7_O``Gv|Eg^UPbfA z4Hg`<6{l6StnGpuKR;%tayldn*zcW*G?JN+n<#0dAxnEd(|Thfz9S$WHrZ9oMzvv) zzl!_3RyLBFUgvgtGQ3^;QSo%NS6+i_SNM_t+M*Dv2Kb9`iDdQq6JQwmwtS z!4Z+9x8YNYR^BP!k~xH@>rLqiCLN`g<}HPO5#X@0Sj^@BfY#MYrIn^9TUsh7{S$>v ze-upozR;3QqS;%glhcZHd4juF_pQv2OYw=7w36f=68dsc=!fF^UNxD^&sN>r=yVL_ z)Y@O!2MDWgr@^ZqaK8HGJa_4iT&5iaWEEf=G%d;N8kkjNKBx0Mzd>J}8r&^!n_$ya z!Z?wLw_H!*MUd?HrChsD_gU;phpa2yy0X==SzIBJ}nWfFkn zcz1zYT|(&ELy)H@u`&AYEMbRX^OM6kzqzaMvKV7C;i*l}MRH`SKYy#TWY}`Y4t4jy zIE4(ZOBu)7RK>{Mm2(5!i5V^MGx{uC%y~5897o!6HOsO2EvP~@!R*p`o_FGIEAR-3 zro;%+tZ#k3Z^4T0%xKip@Ql_KUdX!i>#6Lo_4q70z6X5ICdHmT$ZW{iYKivvP7+!$ zRK@H~5{SiMiU{k$nv5Q??;x$EU>x(g5{-m_q z)m-AcMGbHgj$&SVWlmA)#FOmBcwGC3Nzo@?ve1?{)3Kpf+s>W+unpe?sf^Hq4BZk< zmOi(D__9i59IRv&Yb>5D-&z}-YNP_oPU&%ab@tvwT0)=dM^S@ABCQ2dQu`LzucEJn z;~J!i@}d`J=7l;9(vt3qa`w%D00&bCkb{g7gYFJ z-fDDysGth2x22R`z8GD7FI}|GOKrzcf6kJ(u8O>go2Efn-^$Pl!)_+XF?5~`@x?mP z`*uzFb&ZFUrU68wvC#~N$!M|k_x1c+R@H~h)1MXOPw;l^JQ6QS{g$OCjq=msUFu{T{af4b6pBbMcdO#RRp}uVR#(M^>p?aM7UDE>WYG_Zdb7**ELqUB$u20D zwVc|lK?|@5*2Uh#A%k)!X|mY&ZqsU~?zna^wP?NT1dk(rR@HsceXYpxuAGFcGztrh zL-qUTrMN4klMmlQEUZ}v2)BuS?kWCp1L`WefF`89t^7h7)jF3pK(b}1K5W(CF_hWL z6ZuSxBPQM?!1g;&*GVYH=m_T%YA}A*QqfDzJlMJRm*hlgzi$_h^ICJ5DBne_h-3MS zccd*J`XZ4#grxM!XOAO=8Ey%DH}&KrM=~yyRyXgsALob%iur3z?64o=l%3*ZbN2zWvqf zvvii0d}B^IBz6vG|8!q{{qu}*Y5XfcWHW6%iwU}_j}Ojl4!hD45VBD61!Pj5YRP2Y z=0aaX(ugjl6r<+E?k8o@oA%8*=YAp0q7=7H&AhiN1@~-jBZwPSNOD4qyb$0a3D7i{ zPJ5Aq<0|@oEAF`#8D!7ei_qH^<453jeREl18AyGVJlHt;7S=5vh=*N(B*#Igh4Ho* zB6>@adV`RSG8Wj?n&hVtE=NK4K*uM+YeuqCZPd1+-YJZ~yN09Xz`Y*H~M{Td+Q zPRRtzBK!C0<+X(CQwNb36FnTl7i1q(gB1qC8o`ycA%c}lV}o+D&o!W2xOSvX;!SnC zHNk3p%F-yE3wwhEXG+a%smaI2cU2|LFp_tER_5_0Wd!N#BtYtIQym5Mwj_io?CL3OO!{ves(IvJ0^7wM=9rNWeUH zsq*m`($y*5JyMjZn0{^P5Q-X#5MZ5o61Wd_6!Hxx*_@@U? zO`mECm&ZADYj?OZrG5*>;~SGozxnfZ_qcK0t;bAceCwN4-LphXYE^0JNm@Okz&SHJ zb(gz#r*fI1T)9(Cu5h>$u2CO>OgivuSU=&$(%H>GdMjrRs{p$Ei6(u*ZH*sRNi8EG z>xID%KOqheGF$SzXG+eqn8Xlr72-ZRXsY+U z8#=wPjz7_(-6Wwfr?75N@CM+|?L63^~ zKzJGQ-AGS+HBZ; zw(Q^`BeYG>L)M_{R-Cp!zkFNVrQ^G$t%La_79@fBF*|}!s?x*MuwD%nuRsJ}rhjvt zF9cp#oz4`>=&(yy5fYt*UJw*v{;R%z&&bC7BtE+2F%?sKv#89S>)5HJsokiS1?Ipg z*}0V^PcGLgrP4)H?Zk>3YcPZ`k07~75Mg4}Rf5SX-lM4f0jquK<4g&JSnnnakKw}= zQA7f@8Jh@s?9NQTR4KN_c;oZ()`ICJ*ZQoB2=u$ z1fyTK8dd!q7pKs8z8mu$8$4tMoxxREgLgqTSjpClC@fXC6W--oE6>q;*7c>}UYB>@ zD!oNK>EvdvMs)}2l_t|CFp)N;$Ary+L!`r$KWsCRXMJET~LU} zgM%lT7*sTjIcFlx(|3tb84aR{d~=yNx25Lea7Vp6g!Lt~=22Lq6<{IpzTf%kMYRWa z+-oBkt)?X5bumjsE=Y zOddt%$CQze6#rT<{!DV|&%QF^8f*_NYm;e}adff%y#mrHm3eAulaKDa*EB!#!jxxr z5C60K<;%1Sc4X`yOe(5zb#9^Ue<{OmJjoNoYpWz;_uC>`Pt?Lh-ZQeqgsr4#KDnzT z`Is__>Sm`(=0Ffg3kx#gkyn7Mu|!umr00nDMOX}g&d^BqhmW+DHw47E=>FC1p4`6s zXn^&9eeMq|H>48@^CNi z{71@>zX9@-FBdhH`yO&%iJ3SnHE;3Seve7DILP_%EKso~=J}2Emb1Jn13cd?ZrfD4 z@U}JPOl_?Xv8B^(Pm6uDpL}`nf6A-_*EAh77}|4$&*}(`+jp;bJc8ONaus_k-yXa= zcSArz%lr0g(#Tk(6WO+7UICOQACrL(;T54d;a0TFNomVHr0gtr zx$k0yfA=n2{b19w#sfJ?TZ^ln>X)9pV_ZVWKrdMPx9v(s%-*ao?i851VBlV}lgo6; zb{s!&xq3xs4hJ_b_ltLbU`RH*R=)l~XWW4EP^}y_nzQD6b4w*Y`o9RZ#M`}_)jvyy z`4bH7@S8zGuy^@&^HHJFJZlGYX>dWPAK}i_VlV|68)8docs%7L92n$nebt;T=76b%(>s`#bKZl|X;4FfQ9@V`pJ?-AzY)=8nSw%$z;Hd$893I9qxDxbDD(fdg$FTM&8A+q?=}-1&UQ{b-{?XWxPx zx!P5^_;o3`ac=6+p*+RKRGux=9OZowId{?8P!A@%p31-G3Yw2 zc_Qv$`|IZNqfU3fBVx-vLxYBX#zSfUO&Xp3!06+-_s6y8Pyr&)iX8%A;{2 zR^rGSjbtY;hS!tBb%P^oz=Hd=frj{tFetLXu}-G-!~s^^1; zk8;7}v5fNodp82F=*@D>Y2|nCBi0z7s17!>#I#2!rE67;eLj1hOvbuBv%~kY9LDb8_^@I< z7b`i#`)OxzQZA4*i$VH_yz3tK={P)z#blFOw07PlWlUP7fB_G4Eo$xnUZbJK(9%h> zDe3pPf%4*RGiZ&r^~&YRns13$TZJtA+Bg5Pip4xKS)kbQXK;glc;;Jrbm`8C z`*jh?QJNme1+C^HBwo@eVQFAXuyA&*>XagPZiJ+NHBPY4S=P1ws&av`~`%iv)f>yISvmosGZvFn&{XW z^4GCc&$bKDtP`#WSd$%3fIt%%)_buEV|iaP zlXzMFun9IkK;lwR`qhQAsnt}r1$nVlFUwnEEO^Kra#x7irmE$Z%kf2dV&8JEhwFOO zp19UhN;;OpCEjpJK0V8mPWt{ z7Lxbs`5g~K#KqAM%pt<{$n*!~`vHw|ut?ce-j>WPyvZ{Gw2nFEq3TOoTT@ghr^~a! zGV!6^>J|O`MoeFGVqIvNwt6=3dnLSlJyker2{LVH&yB2}Km?MWEEb_0n<(*q3mnAV zhPR|Vh{PPNW|S4c9^~VF()iS9xCD@fSgF>V-HGhUZrLgRN|wouR8yw$c=x(JG98W# zAqJ}_Y}K@MF+;fMH(_lnyxt?Ii^8N=jr{Ok9}85K_DuVNZ(B$LfWz$`z)ju}G5UPq z{f;!H12nDYy4>tj^m2TM+4PT#oHx*h6r$*L3i*$#@6;#7^_PV^+bPIOI znY8JR1nL?h&R#?FG`JsKGgmHcLHu~zt?)}&SSg!7m7=1R{Al9|B;28(R4z-DEW3fk zR3_0QVh$k01mcBrT{T+7dd=Ff&3Q@cWJmKAHR*Q8nQ+00kGSxBiF-tg6+0=$dtuJs zr!Wh=IvCeKvb6!KvFO_a$T{WxBha)eu-!vk@ssUu`A7x3(XcfK^h69mu)le ztfg|Ey6%X#@aNIc8}#~0O~gB(DL9?&?-nr%Bw$YvZ`5(la-}W6`wqEl+H3YdJY!oB zHI^v6gyJMa#AK*CFV3?qcNnY}?pE|Wtmnrw#gon-C64nU$FIToHeTAGOGCeH^abeW z9iRjWLH!fS-hl}hF^?{yLZBcn_F%x$9RFeIizSWwMp5m00IyVK%9V370g7H)VuAW= z%9BChLZ(O)sGY(-um~m9?v?OSBlPFSDt)X%hg6}Tgx@upVuVGNw)9a3wWpnvqVT6lcq_SGkK*`+OkmV)f~t(!44xD}waw5S}8jK*CV z7r*YHU9clhMTugfmi=GZTV#azfs#PRs1+CQDo3NV|C>NdiHB_pr|k0~xLc1}gdQ=5 zNH|>Q;>vI3B%_NN)!6+eAKMUQx+x)OlrZc`=pjFYS>8 z{mtNw2jm_|n;*?9edm)(eRmGS!;1>g$8S1Fw%Dh^ge$Gh-(p|4q}SpF#&q+0T@$TD z^1ca>M$~3? zB}_Zfr1lf$410#KDjdY5%MZgVzEOeAcovtAq&$lz)T^J1nv`JhtQB2Wx$}obf(V_< zP|HDUof#Dlf9Pv$qV^sK9bHWPe~)2f(BV(E5a(L`#e;rZ-*0?lV<^?3;{AGeooZMo% ziZ0K4M(qY0*=)Fz7o-7s4Y5u5k}a`%{TMG9Y)9xl;0lIEU*`g#}>l0n$@ah zw)z5IsBygW+Z3B%>?I3Wt$OV&VFc`>?&F010f~48@B~04asC}izJEcWu&_tOjn?~$ z7Q3;sD|gN)O8|i?D*g`J!ebp zc4k(Fq)YtK-anR-zb^qWSN~jRH9m)hQQahxKKm!^{SRm=_Xb#rKCDnY`p*H~!MTVq z05Ni2`_IwiFZx?tTng=e0slEvjrsS*|9@w8oQBe;N0}d z^Rn{oL+9BK2^tA1zH116pRee3-P50Xax1Z|31^^(dnN6~LfqJD>5GGoVSB?hmmZZ4 zQNddDli`}Wee?1Z+*xgBK6%_GZ|R(Epxf>JZG10~Q=nqxFn>-t zsitV0FtL7H-g{oRV_p8xQGYx*b9wE^)a~S~kSuq2z%WHNbwF6p#eD{v_%d+TD-a37dzJR}qcm%OF|b<9X!TmxI>D+Y z4-4&rWO0ePI?Cew{=_%NajF6}kgEMzRqfVMU^}u7qp7VEKBFza4Uh6}d{5cb!GpMh zLO|~%WPjH3jXKCD`lU3Z)Y<*8kLS$bLNLMNHm%Wc4x@lnB9AIp-^km=TxZp zC1Z#O{yvMG@6Uu1Hx2E*qZOJtgV!UOUU<5Og1Tju)T4y%6k@V{(luDeJC&v}=i!=? z9Xq?F`7+?QN4)>wpsNoC5@dUxI z*ynn32ji-_I8NvB_L^^Jq_Ex1C~+=L*HfV?LV>-sYmZ8F0W5-6h3u$lP+*FS zi%ay$E?LG|UhcakrbIwT3E~&!aZa}H=5TK>{fUosbiH3!ZHshj&) zOEj+GF#yVa>j0(@x&0#CS6ttOlu_utpWD@#7`UeZP?10q7QTp)Ce8sluOIm$7!5naXCx5lQ zrJOLV&|~mLCk5xJ0k^t5nNR1QzrI5?uAru%y0h?`ndP%t!A@7lPr!Qdl_9#ImQ!je zh>uMRGxu8hxxXXkAbw6u7h3g`qV+llIsOb?9J8~2ALI1P^J!fpe$epz`9$YvF5WnG z+_qvb-d%pPyG8;r<-6xC|CHHl_Oo?Cx$@np?eDE8(B@H`FGa!~xj2KuTc{I+_4A3k zi}L$n=0J=bP8WI;aXu14UD5lcP?6_}TQtsXLgmt%`~7@@ou0G_uC)tfoJ;IXrswqc z+PnRVm>A(hJwkqStaI-Ea=n*+oQ$s9b;C-2z7D@R{~^h~Nat>p#3W9Qy9O^2gL0y(sxypRO&KUOuwYEVip6`cYWd&~{aC&Tr1KVzNZmfv!x-=3M7j zb0eq03U303$F|TMP@L))9Oez?9>)k7U_7hsF|u=S%==eSTc%+o!@N-?*OpR%Ns9l7 zD%X=4nv+7r`i(FFVh^-;T^w0!BG_F*Ygz2n!Ru35`K zANjzWXtc{ONls7G?EH)ji!|a`8jT812H!57B?M@^SpOt{5X#5Fb6(+1%;y(`GlDX> z)jU8$_B5hn+{qAen*e@+2bn+wvmDwp;|m}+Cvx1!_C9xjF;=kxUnLNxE%oM4z*j+4 zX9SHUcw|^5>co(NCibl)w|$*LR1OvQ4DfZVc~0K7vdnPdrchx6jpSw}Y;t0|2Bk9=tBfU%Cisdb-QZ^kNjkPKIquSd%6qLW zbe`#f3ck&1b-Y;fsAQrsS3!@+&u?n+`hF3qvacRAa9?W19eI!jJm7u#>mXNr64PNY z+d>W^QvuB%28jyh@hAYKflw~Y-sGHuOjJq7*Y2-W;#P;j%LTfL4PHK;rtJne7*QLKET6@`?45I7Xu*laX_gE-4jPM69hhrDo$*6K_B#P+{ru6#p zigc5uh{)Q_wcER`p6_|>yN?-XjXZYp2g}EqIEmZ02jK{&nr()fwQ=W8IpPhq4|fV! z=OSKhmByvOCMd3Rm}15+@SL1qu62c0&%_oyR8+JG`MU&@I^`ejJ!GGGU9h^?&3^2j z4t9f>B(gSYBinz3T+sT+GN_FlOAoma>+_w~w0wb@F6+4Hdc#q|1C<~5C$6xXKsAJ) zB0)UimjF*dIOF;fO zxS12T>YP;vm-MG5mEy|J#DJEJ{TIK16Pspaa8A%KwwBHIAz&&eFC&lJ8yGj3VJ0`^ zRS2O8*_>aQOeQd6&q=jGJ#}n*SATf9!bcT1QmP(%vQXnR0n-EjEPdPROyTy1huzRP zD&}?Ie2S^;!Gm#tN=oatFm>*f;`)@##FI2<67bl7O{mm0w6u@QfIZzj4d+oh-6uVB zQ^?;^2tiB)dvOPWu}kA+U*7U;rs(JDtFJ3EB3FF;BNmX1h<1Z5`+ql-N*@S&6dcs@Mtmb6P zzQh*a^*5XaLq`64(Ycloo;@|2V;UB#0DY@*mvQRDzNEE+A@d7>EU3vXI>B@FKKzdJ z`u>b#?l+Twe!+?BY7`tNUtpk#WSql77~|~7V*hNnWUWiIFDrpIzj{R!WP%PVVPl=U zwi&TTO!YAhu+3D*sU@KI;{Tbc{t`C4<-1!S7Oxh?08zCMSU>%Nx)k$vzP@O7nB0ENN#J5Cn?e2F>%!k~jEVNx+>agH$Yu zap1K3x7K;PV#Q1c@DZ%&bNzNdYK3Q!8G6WDL+9xuNLxy?iItx26^{!kw-!)uY*4o>DY2L5#|~Z-b_LxLCUqInEh6o}^$&Tnbk(!^E!+9Rv@@ zt80mXJqWdkPdrCwT^1pPer0&Hb0(`B$T}*8_?Ah>`5{fCtaKLa%cUIZ~?2DxPp0i-QbJfiiiFLzs*0WL7e z6zKHJLs7yULVlx%gF*TTB4%@lct=6BjvP5O(oO|7hlaDVn(&-g#We$xrgkK>g(92q z_Tlg-ccn;X+_$t&$xZb(q*XJ)R0572pt}k^hM$D*2Ghf}jLv$OLP6~0;)m~xSI$< z1%|||;U0Q$>*00KbfKZI0YhkeU*DDmzqb#+*VCs3eMb=t(B!HLoVN>F2@Q~3G0Z&w z0!&-a>&TNK!0D#XuA>jt%P7UNEF#N>)&u}*%Zg0^|ET)Ds9&sLEt0;)pqM&Vr`Z|2 zimau+_cMd(9s?N`+ZyZkCaRiMV~~U!de1ZU{>Q$`TgVb{loYbY50%X&ID^o!JQF=x z!mcSb3I+zl@C} zwaT&{3U+7(@)`=jmRr&57Te^*>>g!;L3@3y=TS^XcwlILM^s zMX}G4XM*@HgVk-T*OL{sxQW3jJg>z=CIdj#izK;i&jiRk+2Vm$koUEmNH{hKPx5z1 zlTI#tj@~X;vp&pZ6*Q{Q=R^!JGa2a!gkG$83pNMY!j#W(OX!fX@$8wAFG+A@z25+& zWOOY}yS|X#PzJu`yjYje47!yL2YTxa7@6 zjw}WD3*vYsd90EgOGN^~Q_pX9ow1`Y6>>$5!tsoq;%^=^nLo$1=YY$anm>!gfA|LU zazql(g|8muU0Gn3OK+6SKt1>fq=BF-%05u0cBakC3Xm6~GvD>Np%{DtL;}?|<09qb z73xs+>qVQEm<}1%e#%EVT(}yfC8yg|+lZX?=)1KU4PK+)+yhwNe5*VjL_zEINucGn ztRlsn-RWsc0wmAAriH8v3x^>aK+8bw5wCgmK@)=z_dfqs9uXgZ$x;r$OSiZJEH|Tw z5tcj$W>gaSN8{3|3#7RGDu?k3-c?a*RS$ZK4hPmqqtjji!WaU2PXmsPMfc z@US+CcmEwq{nm)RjVSnd%%gs3_6-MpauoBNbe28blg9{&`m{GLlyrW7)6uu$kV9~u z$H$615Dh?q-C2`*333$0B~y@E16bxd0$8J-UFD%OYq$j+nbk)zqnNQ5S}m^Ju9Zim zrrwk)z1(*`al3?OR#ZV`>=gt89pU8JdnvsRON8x)A2jSY85sym+qa5aDCp4&gs?i4 z+Hxbd&17tqEKv^-23F*YrAgvO$?ef5@94~*_s84T4vsluMR4JdjgjYB2v-7M(AFMz zW~gNwpM~XwkWr8@VP*sqRv*;rTq)?oDIt5W;nr3|fXuHFb!-9F*`_8M*ZWq(V|*g% zh?oC_QE2aC>m~ek&Vza3iRp0GzLtetz4_{Hg7b*{%vqmAh)R@QPDRn(6MLuClppLI z_(eGbrSZm@+Jm{Cs%HiM66cLvhM*Oe!4uh@F|IAmy5fBbo{PizUg&`zyl2Vke_+w> z5|x%;vt%i2ZS&QWWY3r$Nt%22yJ?g^()~|iJYObb%A%LL##@i0d$RP4vuYKar8|CC zf6FxhtZ<;+mBGmGVqhSJ)RSGCIeAaRLn~H*PIgf zj8fM}3Qkyo{A$S?^eK*LFY5D1=Q?X&(*eba!+#Vf|CE4S0UiwhuUS|n^Dlbvf793I zHI$A6(?5Jvg!31-p(qoLi$e|maY;JnJ5PXJ>*7{sp8qX+0iIT|-pz@|Md?WY}{a-{vj<@n@)+ADq_9I&Ywb# z{X=e~7iK%nZ#EhgwX~>)kC0pG>s9Hizn-T#Ks1&&5>}e*?QQL=*KgvL{wYVDOq%7z zKBbRcJGy#i;BvwZ=4m%6ug^Kr$5ZUGLxZQQArphW{oX|jG#r!Ni;MNH&HU4Zs5JBOMAJqNWejN$sm4fAIT%mPKV&vcuQJakQ04S?yA(p1edzZ zW9!#TBJYWfCiUau&mm1&MST3zbrOyEEy#|N(;OyhWQ}6(%RmOukM~2ACfs%%l_uW$ zxT_ebEFTbwO1|oKOExeS4fs$traXO2>d=_|7@#QD7Ri(*H;>*&6#$bqO6=>kX&f3X zTSIAbljBi>{7Y*!8HA`P7z3A^eqMT`)SY^jYe zA`VAb8QgxJ)T9f!55Mf`{h>6W6DK%t-(AGCTCh$buw|G&m;UXh6go5gPC9rR>b&w2 zei1;pQl&YCq@D5tn_{S^140UBDHFX3Qin#Gj(eSHi9;pQxGt;g&&HLTXO zt*+boHi^p{;c0T0iN{)+#tS=X5`iOMa;le4YjUcePmT1dvI;LPKdE(=cR~R3LwDHo z8C90PxvHqV-hQM>L%E-jw$@Q+c9j<>@4` zoYP6OM^0$#hD$+ZqqGNP^($egBfb6%JHnt`b-xTD+&zIMuTacP?R7y@;c=Ytx(z&S zl6OHiX@PvnLOx+u*o0Xbh1Ath+_qB}}mz>Yr33}{qOe@_T?#P)Kq`G$1V51CtC zh_05&HV+WCM=ESyGFO5Y3M{RHt7-f0vvtMEm|n^9=d-`Bs^Y z1n1}RI-cg|{J4!9Z=^)+!)^$9O4d^*rAK)AFEF`58{l*%X_ek^sr5?ZU4Fa%;8bf= zBQUQ-jy$^IFZ>y#(Lu7HfaYAb=~5i1HDU8N8rYMG#R=n?_T+?IZzUz)5wez`*lq7j zA+Zd`9K6&trtCWCSMrs8)Gu0109k#n(p2~SvRxG=2ZLbpMoU;}Uh^;Eo~AVHRDWh| z=`5~)uXJn{zAYNVNw_tZk+i819Zy2MQ5SxCk|vm7NLFw+OQwEj`Y?uR{7#d_ z5ly)OY@$~2t=`5-2I@LbLG?$=N>sdMS0AW$Hbqwv79D|1!TebgaVdZ{hE2K;vlHyTBI)9xdmLr=E%+lm* z=`IN}>Fa3p2%xZUFgJgc((4t{n@9yMv9W}60=qw_GVnWun;FY5p0HDa$psu{e4cB{ zPn#qE0H#QG5I*4s%WW#V(c#mF!l*WNR;Iod^{rW%raM<7>q&uqZ(*#MqeND17*Zw@jC2L$zBx(E`ABBsF*=(rRj zCq2XT&5Oh9bDE%_64=WBrM_h|y)qS{OLIp!P@xA!bb`rF|_+lRd!Sh)t(_ZwGi z5@3jrF6fz5wjxf5I5Bds9Zrgs5qj02`u3f5a>7~WhA>$kzbpGDQIZqA_t8};lmZ?; z`Z>_!9R<{MG%Eh%2lfar_R~gf>WwwvauI23(1~ckJ(K;Er+1WUF5@nJC+TmXZ>(y; zr)EaY4NggR4^BPlE(5PKFf7q}NE3NHvu&*oY{+LB(trPzyvImuhz6?`X!cG&*F z-8@H5^8A)v0)>Me$awx{)~S(Ou;F(CheQ1FPo0^EH3)hNwo^h+?P z)T2*InQ}N-$#;#G>a~q675V;$wO3SDQummONL#d>YJ@)OO(Xy=wG1j?nrST8pau^| zhy@*`#>F^{VfI0aizi3#U6nX1Pmp(`ew^6$(zH?(R#wG}903&IATp^q49(Auc14-2*@ybdytE7)Ta#SC3{2@zi z^COGRJma)4G7y9&`A)JWA{bn7=voprGdZ79S)3DkrD>QZkaFOGVW6Lr`WdS^E6;e~ zcAa0{8eD(JEkWt5K9~A!WhJoPG1apt_5pkRORFCH@^)8V4n;}>1V;Ti;maE;%}s#< z0$KOrI(q8SOr<(!29x>L3XCR|zAYSw3zO3HekZ9b&B4*dbBKn;-@R}U;kizc{~qxr zBkq&O;+WE;8ABl9CI`;I7XQnuXHFD@_?)zKUg>y);{y49#w~tu8;OwCz?jj9!59i4 zN(A7ZY2+XqqlxsK>e5ibNu>Oyt4N#oY_EuHSTuv1Oc#Cs1)G3XcZY%r} zt`P-)Xi}wi`l-eW^km?d*SukD&c|&uFpeSt;st=*^U~N`zq*!b5+JWVF!ss#*YJ&7 z+sYToB~}@(oxu~zhx+ms0{k{^mhZ1atx^*6RKuw^SR)Hf?WV63voTN z^;yG`lB_A+SU;Kx`(n|KtugR6%~a}5x3A>;jT3Wz6ZM=zK=X7#iK9pDZZIIMvH`6F z+)#^N7Eh#4>(Q0d!FKcqPR26Zrc%)qhLIFk;d`+Zr&`rB8;aE6jXn0>R{?_cAR6?X zdIf1qkIN6T73=D>HAWeIILC_7K*kL%@DrFp+g*u;w+hu2@2 z7OQA-(z`G~pPG3r%9rQh&XHlsq-ZLTIoqbk<7rW`fJwl<{)D%nZr*bQMROZ zSC7xfzMB3aK@W!puB?~8QFXR(#bi%U{0!< zNsqA(5D*RAGYEW-sgZ*o)`#UG!&06@-^$+Lx;E+el^mI%(4^{}+hZOe^~P=HJd3%P zzIy8}tcRM092DDmOt{~h+(X%>I=lG?jadDeTZuk6b1QqBn~gr#pxpQrZ*5I?=N6|J z9*C}tB*O&r#RXEg)-(qON&#VUWrEEh_lmBPtfW?5QTcsJ{?_beQF8N=w39q4b6#dR zEA)eKC5Q&X8S-Yx20_|#B~YqPpxy~8PPG-~oSh^r@CX?`LK3SC`Mw^3rd+U6`lKPV z;^H?m;~_$ZZ4?vmkr`QkHZT^e4&&MQgX?9{lhMyPd+_qe28Bc62!b4A6G{KamNNgm zoJh`LCft2F7a62F8I|$vrB#V8RybN(njG=1N6kY-oqx%LiO~((ZoHXDq`uh^Czh{KRK1p9sVIuhoIqk8OpTdHhT)d>thaVAFzma!VhDir`iKq_9ba+Kmx5w zSTrIjz2H*k9POLN8cl@bUgeEi1URk<#``u_-NB)0H%G zG|MImHJ8e{_2dOCpr|OC;UF*H^#KnX8Ed`A#(fSR{B;^Ry#dtg#|8|x;j^ZRF4qO; z6Fgjts7=x&l|j*L3Lq=-j88sQrJ%|4?BMv)=f_z{WH@Mqw-s&>{Dx7mA#gM-QCG~s zfSDp34^Q1md4B3t0HQ{ygGWKHcB4lT*FOcJl7q508h*vtBo^c&jn~rO$tS#|) z@9QLqWiGENwDEg&sWIxyrDa>m47Z&)rB5(egGC>&C)OgLj!^4z7xCN8%;#=CJ|7=aV5LfH^vzWE( zG;kW3S|(QS`AuN%P6*SJAo=QQ1pef?aLllwR$nWnFysa3Nxd)jbt;2Qh&N3?+h+z8 z@#acwyf+NeBY^U4YUBq!bB1Vg!rd^2o;sS23AMC8tC9O_V1S#v0(M|H(bilKJwAic zZLXR_G$tDNWH5dCu3vljimg~GNo2}wqRxM!LhO#4ZT!;Q>eNQu+IVQ<5aa$SnGd;h z(2nDc&5Xd;)M~xKgd5vnX+X85a388=b8cxwmC`BGeT{Q#2~|pRwUwM&HB2hZRBPHD zn@9CKs>Tt`J#n81wYM=nseYDLOMXsu=gEYAF9d;f!a(SjFnA?Djqcl#na4Ot2%Mb# zkH{sP03}7n3p@_#)p8RB_;NpGO@Ffp^xaR2s=rHn$XU!&QW@nxX3_+wK3=?jq&@ldbCSD%>b_$fJ+Ueiy_kd9YWJv|MW>N%|TH}w0p*d0e? zf2BCcca$iUtn0U)aM~Bpq!VhXY-G}dqXI%6Nb6NLO&x*iD+OZ zY;(NR5!*+!2;0tJGWhM(89yvqY_-LJiXJP79x?kpwPy&7X?Dgvhh>U*=gNBj*enm_ zTx~XRE3-~8us=7^^7j&ct3SzEUp^Bk1axYhXVSnj`PnP4C*#ETb_vae zKIw)I2Mis6Ry{9j=Rh&bId*S>$+k{TV@h zH_r^^N4}psJf+Fp1MG5Hr z`@HhRX4p7RL%vSr`wBNOT(ahyVQ4jKnJd;K1p&nxt{$H>e)T}ft#;O|uKw|7Fp3bCpT`t)Yf-cbgfy0~SR20m?zvCP?;z{Cq%P_M=K8t_6iXxX8e zjbwuRO09;_Wpa;C2^dTfyFgo$^shEtu`h>r$0#)!X6A)>E;OQ10;l%^7(Ud2JKF>g z0bSp-4?-@{nS}vp%KJFFPto|58Ul*e!0)*&6An8=pAK0}S`J&rPzQrAzIv$m%@3@2 zWcwTxhmiWspqJy|0<|7xr|qu53r|uba{uc7F#C$&#P|T+2@Nnh$Y=?J z+QDa9Vx1Z3)!j*$A=L(;f&4jgawZU(?Ttv{Sf}Sro&?upS1`c*-gBdr{>m?Zq699k zbPcOyWu;@PjZSe7wKE;Eg>tM0i?g8g8roeb4@H{Qx!M2R%WLqi)ZKFIJQmpX!YWU= z6SOM9@zaX6vVOY3o9l7*dP7H4^@{I( zhSyz|;$m{m+ZJEF<^I+8kj#n9r=26?Om+6hcCrEu)vGQhRfjAtolJSeKG-;*xXCXs zT@yqvrpfkqRs>W`I1ewapDAC21Y_+9k;XijkJY- z&{QHQ*j>55WKX=-LXC3n)tNi5>syUE7588BRXrPDuA^Iz;yfvrS#q(~ZcX3WTTw0S z-VGMS91d-6n5*;2?`6V^-84cZ68(0{!#U{t%{rB~6P%l}V^Rq}vG*uPH!Sowc#i!y$|gHN9i vC_j!&WHhobs=NAU82o?i-;xifoHs(sZdgZ04FcQVlRQ>ZS1fpF^7g+0rx;Gz literal 0 HcmV?d00001 From caeacaf4a1cc76c30d040842e288c94a2e92cc82 Mon Sep 17 00:00:00 2001 From: chungen0126 Date: Fri, 23 May 2025 04:22:10 +0800 Subject: [PATCH 2/2] address comments --- .../content/design/s3-event-notification.md | 74 ++++++++++--------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/hadoop-hdds/docs/content/design/s3-event-notification.md b/hadoop-hdds/docs/content/design/s3-event-notification.md index 5338668b6883..9a8039a9b3c2 100644 --- a/hadoop-hdds/docs/content/design/s3-event-notification.md +++ b/hadoop-hdds/docs/content/design/s3-event-notification.md @@ -97,7 +97,7 @@ Here is the mapping between OM audit actions and S3 event types. ### Overview -A callback is introduced post-Ratis commit and pre-client response to handle event notification logic. +A callback is introduced after the Ratis request is completed and before the response is returned to the client, to handle event notification logic. #### Component @@ -152,22 +152,22 @@ This is the structure to determine which event should be sent and where to send. ```protobuf message NotificationInfo { -required string targetId = 1; -required Event event = 2; + required string targetId = 1; + required Event event = 2; } enum Event { -S3TEST = 1; -S3ObjectCreate = 2; -S3ObjectCreatePut = 3; -S3ObjectCreatePost = 4; -S3ObjectCreateCopy = 5; -S3ObjectCreateCompleteMultipartUpload = 6; -S3ObjectRemovedDelete = 7; -S3ObjectTagging = 8; -S3ObjectTaggingPut = 9; -S3ObjectTaggingDelete = 10; + S3TEST = 1; + S3ObjectCreate = 2; + S3ObjectCreatePut = 3; + S3ObjectCreatePost = 4; + S3ObjectCreateCopy = 5; + S3ObjectCreateCompleteMultipartUpload = 6; + S3ObjectRemovedDelete = 7; + S3ObjectTagging = 8; + S3ObjectTaggingPut = 9; + S3ObjectTaggingDelete = 10; } ``` @@ -175,37 +175,38 @@ S3ObjectTaggingDelete = 10; This is the structure to store the details of the target. ```protobuf message TargetConfig { -required Target target = 1; -optional KafkaNotificationConfig kafkaNotificationConfig = 3; -optional RabbitMQNotificationConfig rabbitMQNotificationConfig = 4; + required Target target = 1; + oneof config { + optional KafkaNotificationConfig kafkaNotificationConfig = 2; + optional RabbitMQNotificationConfig rabbitMQNotificationConfig = 3; + } } - enum Target { -Kafka = 1; -RabbitMQ = 2; + Kafka = 1; + RabbitMQ = 2; } message KafkaNotificationConfig { -repeated string endpoints = 1; -required string topic = 2; -optional string saslUsername = 3; -optional string saslPassword = 4; -optional string saslMechanism = 5; -optional string tlsSkipVerify = 6; -optional string clientTlsCert = 7; -optional string clientTlsKey = 8; - - + repeated string endpoints = 1; + required string topic = 2; + optional string saslUsername = 3; + optional string saslPassword = 4; + optional string saslMechanism = 5; + optional string tlsSkipVerify = 6; + optional string clientTlsCert = 7; + optional string clientTlsKey = 8; } -message RabbitMQNotificationConfig { -required string url = 1; -required string exchange = 2; -required string exchangeType = 3; -required string routingKey = 4; +message AMQPNotificationConfig { + required string amqpVersion = 1; + required string url = 2; + optional string exchange = 3; + optional string exchangeType = 4; + optional string routingKey = 5; + optional string queue = 6; } ``` @@ -235,7 +236,10 @@ OM Metadata Read Metrics: Latency of reading notification configuration from the BucketTable. - **Target config lookup latency**: Latency of reading target configuration from the TargetTable. -- **Total number of notification evaluations** +- **Total number of notification events triggered** +Total number of notification events generated. +- **Total number of notification events push failed** + Number of events that failed to be delivered to a target. Senders Metrics: Each client implementation for the supported targets (e.g., Kafka, RabbitMQ) will expose a set of metrics to monitor, debug, and tune. These metrics will help users understand the behavior and efficiency of the notification system under various conditions.