From 4d9c0fc9d3dc0aeac88799e2d9c4dde2500e6aeb Mon Sep 17 00:00:00 2001 From: Techassi Date: Wed, 3 Jun 2026 08:42:14 +0200 Subject: [PATCH 01/10] docs: Update supported versions --- docs/modules/nifi/partials/supported-versions.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/modules/nifi/partials/supported-versions.adoc b/docs/modules/nifi/partials/supported-versions.adoc index 4b80fee5..c68a18df 100644 --- a/docs/modules/nifi/partials/supported-versions.adoc +++ b/docs/modules/nifi/partials/supported-versions.adoc @@ -3,9 +3,8 @@ // Stackable Platform documentation. * 2.9.0 -* 2.7.2 +* 2.7.2 (Deprecated) ** Please note that NiFi starting with 2.7.2 supports Iceberg, but only with S3 and Iceberg REST catalog (no Hive metastore or HDFS support). Please read on xref:nifi:usage_guide/writing-to-iceberg-tables.adoc[] for details. * 2.6.0 (LTS) -* 1.28.1 (Deprecated) For details on how to upgrade your NiFi version, refer to xref:nifi:usage_guide/updating.adoc[]. From 9bb8f42fe32db651f726eb92f929e49b5d4d4233 Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 16 Jun 2026 15:33:57 +0200 Subject: [PATCH 02/10] test: Update NiFi version in integration tests --- .../20-install-nifi.yaml.j2 | 8 +- .../30_install-nifi.yaml.j2 | 12 +-- .../{smoke_v1 => smoke}/00-patch-ns.yaml.j2 | 0 .../{smoke_v1 => smoke}/00-range-limit.yaml | 0 .../{smoke_v1 => smoke}/10-assert.yaml.j2 | 0 ...tor-aggregator-discovery-configmap.yaml.j2 | 0 .../{smoke_v2 => smoke}/20-assert.yaml.j2 | 0 .../20-install-zookeeper.yaml.j2 | 0 .../kuttl/{smoke_v1 => smoke}/30-assert.yaml | 0 .../30-install-nifi.yaml.j2 | 8 +- .../{smoke_v1 => smoke}/31-assert.yaml.j2 | 0 .../kuttl/{smoke_v1 => smoke}/32-assert.yaml | 0 .../kuttl/{smoke_v1 => smoke}/33-assert.yaml | 0 .../{smoke_v2 => smoke}/34-assert.yaml.j2 | 0 .../{smoke_v2 => smoke}/35-assert.yaml.j2 | 0 .../kuttl/{smoke_v1 => smoke}/40-assert.yaml | 0 .../40-scale-up-nifi.yaml.j2 | 0 .../kuttl/{smoke_v1 => smoke}/50-assert.yaml | 0 .../50-install-test-nifi.yaml.j2 | 0 .../{smoke_v1 => smoke}/60-assert.yaml.j2 | 0 .../60-prepare-test-nifi.yaml | 0 .../kuttl/{smoke_v1 => smoke}/cacert.pem | 0 .../kuttl/{smoke_v1 => smoke}/test_nifi.py | 0 .../{smoke_v2 => smoke}/test_nifi_metrics.py | 0 tests/templates/kuttl/smoke_v1/20-assert.yaml | 12 --- .../smoke_v1/20-install-zookeeper.yaml.j2 | 22 ----- .../kuttl/smoke_v1/30-install-nifi.yaml.j2 | 88 ------------------- .../kuttl/smoke_v1/50-install-test-nifi.yaml | 28 ------ .../kuttl/smoke_v1/test_nifi_metrics.py | 62 ------------- .../kuttl/smoke_v2/00-patch-ns.yaml.j2 | 9 -- .../kuttl/smoke_v2/00-range-limit.yaml | 11 --- .../kuttl/smoke_v2/10-assert.yaml.j2 | 10 --- ...tor-aggregator-discovery-configmap.yaml.j2 | 9 -- tests/templates/kuttl/smoke_v2/30-assert.yaml | 27 ------ .../kuttl/smoke_v2/31-assert.yaml.j2 | 6 -- tests/templates/kuttl/smoke_v2/32-assert.yaml | 19 ---- tests/templates/kuttl/smoke_v2/33-assert.yaml | 7 -- tests/templates/kuttl/smoke_v2/40-assert.yaml | 12 --- .../kuttl/smoke_v2/40-scale-up-nifi.yaml.j2 | 8 -- tests/templates/kuttl/smoke_v2/50-assert.yaml | 12 --- .../kuttl/smoke_v2/60-assert.yaml.j2 | 7 -- .../kuttl/smoke_v2/60-prepare-test-nifi.yaml | 7 -- tests/templates/kuttl/smoke_v2/cacert.pem | 20 ----- tests/templates/kuttl/smoke_v2/test_nifi.py | 85 ------------------ tests/test-definition.yaml | 29 ++---- 45 files changed, 19 insertions(+), 499 deletions(-) rename tests/templates/kuttl/{smoke_v1 => smoke}/00-patch-ns.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/00-range-limit.yaml (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/10-assert.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/10-install-vector-aggregator-discovery-configmap.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v2 => smoke}/20-assert.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v2 => smoke}/20-install-zookeeper.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/30-assert.yaml (100%) rename tests/templates/kuttl/{smoke_v2 => smoke}/30-install-nifi.yaml.j2 (90%) rename tests/templates/kuttl/{smoke_v1 => smoke}/31-assert.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/32-assert.yaml (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/33-assert.yaml (100%) rename tests/templates/kuttl/{smoke_v2 => smoke}/34-assert.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v2 => smoke}/35-assert.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/40-assert.yaml (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/40-scale-up-nifi.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/50-assert.yaml (100%) rename tests/templates/kuttl/{smoke_v2 => smoke}/50-install-test-nifi.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/60-assert.yaml.j2 (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/60-prepare-test-nifi.yaml (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/cacert.pem (100%) rename tests/templates/kuttl/{smoke_v1 => smoke}/test_nifi.py (100%) rename tests/templates/kuttl/{smoke_v2 => smoke}/test_nifi_metrics.py (100%) delete mode 100644 tests/templates/kuttl/smoke_v1/20-assert.yaml delete mode 100644 tests/templates/kuttl/smoke_v1/20-install-zookeeper.yaml.j2 delete mode 100644 tests/templates/kuttl/smoke_v1/30-install-nifi.yaml.j2 delete mode 100644 tests/templates/kuttl/smoke_v1/50-install-test-nifi.yaml delete mode 100755 tests/templates/kuttl/smoke_v1/test_nifi_metrics.py delete mode 100644 tests/templates/kuttl/smoke_v2/00-patch-ns.yaml.j2 delete mode 100644 tests/templates/kuttl/smoke_v2/00-range-limit.yaml delete mode 100644 tests/templates/kuttl/smoke_v2/10-assert.yaml.j2 delete mode 100644 tests/templates/kuttl/smoke_v2/10-install-vector-aggregator-discovery-configmap.yaml.j2 delete mode 100644 tests/templates/kuttl/smoke_v2/30-assert.yaml delete mode 100644 tests/templates/kuttl/smoke_v2/31-assert.yaml.j2 delete mode 100644 tests/templates/kuttl/smoke_v2/32-assert.yaml delete mode 100644 tests/templates/kuttl/smoke_v2/33-assert.yaml delete mode 100644 tests/templates/kuttl/smoke_v2/40-assert.yaml delete mode 100644 tests/templates/kuttl/smoke_v2/40-scale-up-nifi.yaml.j2 delete mode 100644 tests/templates/kuttl/smoke_v2/50-assert.yaml delete mode 100644 tests/templates/kuttl/smoke_v2/60-assert.yaml.j2 delete mode 100644 tests/templates/kuttl/smoke_v2/60-prepare-test-nifi.yaml delete mode 100644 tests/templates/kuttl/smoke_v2/cacert.pem delete mode 100755 tests/templates/kuttl/smoke_v2/test_nifi.py diff --git a/tests/templates/kuttl/cluster-sync-cs-bug/20-install-nifi.yaml.j2 b/tests/templates/kuttl/cluster-sync-cs-bug/20-install-nifi.yaml.j2 index f47dc621..61f43bcd 100644 --- a/tests/templates/kuttl/cluster-sync-cs-bug/20-install-nifi.yaml.j2 +++ b/tests/templates/kuttl/cluster-sync-cs-bug/20-install-nifi.yaml.j2 @@ -166,12 +166,12 @@ metadata: name: nifi spec: image: -{% if test_scenario['values']['nifi-v2'].find(",") > 0 %} - custom: "{{ test_scenario['values']['nifi-v2'].split(',')[1] }}" - productVersion: "{{ test_scenario['values']['nifi-v2'].split(',')[0] }}" +{% if test_scenario['values']['nifi'].find(",") > 0 %} + custom: "{{ test_scenario['values']['nifi'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['nifi'].split(',')[0] }}" {% else %} custom: null - productVersion: "{{ test_scenario['values']['nifi-v2'] }}" + productVersion: "{{ test_scenario['values']['nifi'] }}" {% endif %} pullPolicy: IfNotPresent clusterConfig: diff --git a/tests/templates/kuttl/custom-components-git-sync/30_install-nifi.yaml.j2 b/tests/templates/kuttl/custom-components-git-sync/30_install-nifi.yaml.j2 index 0e716577..b1f43454 100644 --- a/tests/templates/kuttl/custom-components-git-sync/30_install-nifi.yaml.j2 +++ b/tests/templates/kuttl/custom-components-git-sync/30_install-nifi.yaml.j2 @@ -148,7 +148,7 @@ data: "bundle": { "group": "org.apache.nifi", "artifact": "nifi-prometheus-nar", - "version": "1.28.1" + "version": "2.9.0" }, "properties": { "prometheus-reporting-task-metrics-endpoint-port": "8081", @@ -190,7 +190,7 @@ data: "bundle": { "group": "org.apache.nifi", "artifact": "nifi-standard-nar", - "version": "1.28.1" + "version": "2.9.0" }, "properties": { "Regular Expression": "(?s)(^.*$)", @@ -235,7 +235,7 @@ data: "bundle": { "group": "org.apache.nifi", "artifact": "nifi-standard-nar", - "version": "1.28.1" + "version": "2.9.0" }, "properties": { "HTTP Context Map": "fd3b17c4-0196-1000-0000-000076ed786c", @@ -311,7 +311,7 @@ data: "bundle": { "group": "org.apache.nifi", "artifact": "nifi-standard-nar", - "version": "1.28.1" + "version": "2.9.0" }, "properties": { "multipart-request-max-size": "1 MB", @@ -475,7 +475,7 @@ data: "bundle": { "group": "org.apache.nifi", "artifact": "nifi-http-context-map-nar", - "version": "1.28.1" + "version": "2.9.0" }, "properties": { "Request Expiration": "1 min", @@ -488,7 +488,7 @@ data: "bundle": { "group": "org.apache.nifi", "artifact": "nifi-standard-services-api-nar", - "version": "1.28.1" + "version": "2.9.0" } } ], diff --git a/tests/templates/kuttl/smoke_v1/00-patch-ns.yaml.j2 b/tests/templates/kuttl/smoke/00-patch-ns.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v1/00-patch-ns.yaml.j2 rename to tests/templates/kuttl/smoke/00-patch-ns.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v1/00-range-limit.yaml b/tests/templates/kuttl/smoke/00-range-limit.yaml similarity index 100% rename from tests/templates/kuttl/smoke_v1/00-range-limit.yaml rename to tests/templates/kuttl/smoke/00-range-limit.yaml diff --git a/tests/templates/kuttl/smoke_v1/10-assert.yaml.j2 b/tests/templates/kuttl/smoke/10-assert.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v1/10-assert.yaml.j2 rename to tests/templates/kuttl/smoke/10-assert.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v1/10-install-vector-aggregator-discovery-configmap.yaml.j2 b/tests/templates/kuttl/smoke/10-install-vector-aggregator-discovery-configmap.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v1/10-install-vector-aggregator-discovery-configmap.yaml.j2 rename to tests/templates/kuttl/smoke/10-install-vector-aggregator-discovery-configmap.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v2/20-assert.yaml.j2 b/tests/templates/kuttl/smoke/20-assert.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v2/20-assert.yaml.j2 rename to tests/templates/kuttl/smoke/20-assert.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v2/20-install-zookeeper.yaml.j2 b/tests/templates/kuttl/smoke/20-install-zookeeper.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v2/20-install-zookeeper.yaml.j2 rename to tests/templates/kuttl/smoke/20-install-zookeeper.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v1/30-assert.yaml b/tests/templates/kuttl/smoke/30-assert.yaml similarity index 100% rename from tests/templates/kuttl/smoke_v1/30-assert.yaml rename to tests/templates/kuttl/smoke/30-assert.yaml diff --git a/tests/templates/kuttl/smoke_v2/30-install-nifi.yaml.j2 b/tests/templates/kuttl/smoke/30-install-nifi.yaml.j2 similarity index 90% rename from tests/templates/kuttl/smoke_v2/30-install-nifi.yaml.j2 rename to tests/templates/kuttl/smoke/30-install-nifi.yaml.j2 index ec39df14..b55c8c63 100644 --- a/tests/templates/kuttl/smoke_v2/30-install-nifi.yaml.j2 +++ b/tests/templates/kuttl/smoke/30-install-nifi.yaml.j2 @@ -42,12 +42,12 @@ metadata: name: nifi spec: image: -{% if test_scenario['values']['nifi-v2'].find(",") > 0 %} - custom: "{{ test_scenario['values']['nifi-v2'].split(',')[1] }}" - productVersion: "{{ test_scenario['values']['nifi-v2'].split(',')[0] }}" +{% if test_scenario['values']['nifi'].find(",") > 0 %} + custom: "{{ test_scenario['values']['nifi'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['nifi'].split(',')[0] }}" {% else %} custom: null - productVersion: "{{ test_scenario['values']['nifi-v2'] }}" + productVersion: "{{ test_scenario['values']['nifi'] }}" {% endif %} pullPolicy: IfNotPresent clusterConfig: diff --git a/tests/templates/kuttl/smoke_v1/31-assert.yaml.j2 b/tests/templates/kuttl/smoke/31-assert.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v1/31-assert.yaml.j2 rename to tests/templates/kuttl/smoke/31-assert.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v1/32-assert.yaml b/tests/templates/kuttl/smoke/32-assert.yaml similarity index 100% rename from tests/templates/kuttl/smoke_v1/32-assert.yaml rename to tests/templates/kuttl/smoke/32-assert.yaml diff --git a/tests/templates/kuttl/smoke_v1/33-assert.yaml b/tests/templates/kuttl/smoke/33-assert.yaml similarity index 100% rename from tests/templates/kuttl/smoke_v1/33-assert.yaml rename to tests/templates/kuttl/smoke/33-assert.yaml diff --git a/tests/templates/kuttl/smoke_v2/34-assert.yaml.j2 b/tests/templates/kuttl/smoke/34-assert.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v2/34-assert.yaml.j2 rename to tests/templates/kuttl/smoke/34-assert.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v2/35-assert.yaml.j2 b/tests/templates/kuttl/smoke/35-assert.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v2/35-assert.yaml.j2 rename to tests/templates/kuttl/smoke/35-assert.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v1/40-assert.yaml b/tests/templates/kuttl/smoke/40-assert.yaml similarity index 100% rename from tests/templates/kuttl/smoke_v1/40-assert.yaml rename to tests/templates/kuttl/smoke/40-assert.yaml diff --git a/tests/templates/kuttl/smoke_v1/40-scale-up-nifi.yaml.j2 b/tests/templates/kuttl/smoke/40-scale-up-nifi.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v1/40-scale-up-nifi.yaml.j2 rename to tests/templates/kuttl/smoke/40-scale-up-nifi.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v1/50-assert.yaml b/tests/templates/kuttl/smoke/50-assert.yaml similarity index 100% rename from tests/templates/kuttl/smoke_v1/50-assert.yaml rename to tests/templates/kuttl/smoke/50-assert.yaml diff --git a/tests/templates/kuttl/smoke_v2/50-install-test-nifi.yaml.j2 b/tests/templates/kuttl/smoke/50-install-test-nifi.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v2/50-install-test-nifi.yaml.j2 rename to tests/templates/kuttl/smoke/50-install-test-nifi.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v1/60-assert.yaml.j2 b/tests/templates/kuttl/smoke/60-assert.yaml.j2 similarity index 100% rename from tests/templates/kuttl/smoke_v1/60-assert.yaml.j2 rename to tests/templates/kuttl/smoke/60-assert.yaml.j2 diff --git a/tests/templates/kuttl/smoke_v1/60-prepare-test-nifi.yaml b/tests/templates/kuttl/smoke/60-prepare-test-nifi.yaml similarity index 100% rename from tests/templates/kuttl/smoke_v1/60-prepare-test-nifi.yaml rename to tests/templates/kuttl/smoke/60-prepare-test-nifi.yaml diff --git a/tests/templates/kuttl/smoke_v1/cacert.pem b/tests/templates/kuttl/smoke/cacert.pem similarity index 100% rename from tests/templates/kuttl/smoke_v1/cacert.pem rename to tests/templates/kuttl/smoke/cacert.pem diff --git a/tests/templates/kuttl/smoke_v1/test_nifi.py b/tests/templates/kuttl/smoke/test_nifi.py similarity index 100% rename from tests/templates/kuttl/smoke_v1/test_nifi.py rename to tests/templates/kuttl/smoke/test_nifi.py diff --git a/tests/templates/kuttl/smoke_v2/test_nifi_metrics.py b/tests/templates/kuttl/smoke/test_nifi_metrics.py similarity index 100% rename from tests/templates/kuttl/smoke_v2/test_nifi_metrics.py rename to tests/templates/kuttl/smoke/test_nifi_metrics.py diff --git a/tests/templates/kuttl/smoke_v1/20-assert.yaml b/tests/templates/kuttl/smoke_v1/20-assert.yaml deleted file mode 100644 index 49ba7437..00000000 --- a/tests/templates/kuttl/smoke_v1/20-assert.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 600 ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: zookeeper-server-default -status: - readyReplicas: 1 - replicas: 1 diff --git a/tests/templates/kuttl/smoke_v1/20-install-zookeeper.yaml.j2 b/tests/templates/kuttl/smoke_v1/20-install-zookeeper.yaml.j2 deleted file mode 100644 index 2080faf6..00000000 --- a/tests/templates/kuttl/smoke_v1/20-install-zookeeper.yaml.j2 +++ /dev/null @@ -1,22 +0,0 @@ ---- -apiVersion: zookeeper.stackable.tech/v1alpha1 -kind: ZookeeperCluster -metadata: - name: zookeeper -spec: - image: - productVersion: "{{ test_scenario['values']['zookeeper'] }}" - pullPolicy: IfNotPresent - clusterConfig: -{% if lookup('env', 'VECTOR_AGGREGATOR') %} - vectorAggregatorConfigMapName: vector-aggregator-discovery -{% endif %} - servers: - config: - logging: - enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} - roleConfig: - listenerClass: {{ test_scenario['values']['listener-class'] }} - roleGroups: - default: - replicas: 1 diff --git a/tests/templates/kuttl/smoke_v1/30-install-nifi.yaml.j2 b/tests/templates/kuttl/smoke_v1/30-install-nifi.yaml.j2 deleted file mode 100644 index ee1798af..00000000 --- a/tests/templates/kuttl/smoke_v1/30-install-nifi.yaml.j2 +++ /dev/null @@ -1,88 +0,0 @@ ---- -apiVersion: authentication.stackable.tech/v1alpha1 -kind: AuthenticationClass -metadata: - name: nifi-users -spec: - provider: - static: - userCredentialsSecret: - name: nifi-users ---- -apiVersion: v1 -kind: Secret -metadata: - name: nifi-users -stringData: - admin: > - passwordWithSpecialCharacter\@<&>"' ---- -apiVersion: v1 -kind: Secret -metadata: - name: nifi-sensitive-property-key -stringData: - nifiSensitivePropsKey: mYsUp3rS3cr3tk3y ---- -apiVersion: zookeeper.stackable.tech/v1alpha1 -kind: ZookeeperZnode -metadata: - name: nifi-znode -spec: - clusterRef: - name: zookeeper ---- -# We need to create the NifiCluster last, so that the ConfigMaps/Secrets it mounts are already -# existing to prevent unnecessary Pod restarts. -apiVersion: nifi.stackable.tech/v1alpha1 -kind: NifiCluster -metadata: - name: nifi -spec: - image: -{% if test_scenario['values']['nifi-v1'].find(",") > 0 %} - custom: "{{ test_scenario['values']['nifi-v1'].split(',')[1] }}" - productVersion: "{{ test_scenario['values']['nifi-v1'].split(',')[0] }}" -{% else %} - custom: null - productVersion: "{{ test_scenario['values']['nifi-v1'] }}" -{% endif %} - pullPolicy: IfNotPresent - clusterConfig: - zookeeperConfigMapName: nifi-znode - authentication: - - authenticationClass: nifi-users - hostHeaderCheck: - allowAll: false - additionalAllowedHosts: - - example.com:1234 - sensitiveProperties: - keySecret: nifi-sensitive-property-key -{% if lookup('env', 'VECTOR_AGGREGATOR') %} - vectorAggregatorConfigMapName: vector-aggregator-discovery -{% endif %} - nodes: - envOverrides: - COMMON_VAR: role-value # overridden by role group below - ROLE_VAR: role-value # only defined here at role level - configOverrides: - "nifi.properties": - "nifi.diagnostics.on.shutdown.enabled": "true" - "nifi.diagnostics.on.shutdown.verbose": "false" - # Quicker startup, and we only have a single node - "nifi.cluster.flow.election.max.wait.time": "10 secs" - config: - logging: - enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} - roleConfig: - listenerClass: {{ test_scenario['values']['listener-class'] }} - roleGroups: - default: - replicas: 2 - envOverrides: - COMMON_VAR: group-value # overrides role value - GROUP_VAR: group-value # only defined here at group level - configOverrides: - "nifi.properties": - "nifi.diagnostics.on.shutdown.enabled": "false" - "nifi.diagnostics.on.shutdown.max.filecount": "20" diff --git a/tests/templates/kuttl/smoke_v1/50-install-test-nifi.yaml b/tests/templates/kuttl/smoke_v1/50-install-test-nifi.yaml deleted file mode 100644 index dc8a0b39..00000000 --- a/tests/templates/kuttl/smoke_v1/50-install-test-nifi.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: test-nifi - labels: - app: test-nifi -spec: - replicas: 1 - selector: - matchLabels: - app: test-nifi - template: - metadata: - labels: - app: test-nifi - spec: - containers: - - name: test-nifi - image: oci.stackable.tech/sdp/testing-tools/nifi:0.3.0-stackable0.0.0-dev - command: ["sleep", "infinity"] - resources: - requests: - memory: "128Mi" - cpu: "100m" - limits: - memory: "128Mi" - cpu: "400m" diff --git a/tests/templates/kuttl/smoke_v1/test_nifi_metrics.py b/tests/templates/kuttl/smoke_v1/test_nifi_metrics.py deleted file mode 100755 index 86fd42a4..00000000 --- a/tests/templates/kuttl/smoke_v1/test_nifi_metrics.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -import argparse -import requests -import time -from requests.exceptions import ConnectionError - -if __name__ == "__main__": - # Construct an argument parser - all_args = argparse.ArgumentParser() - # Add arguments to the parser - all_args.add_argument( - "-m", - "--metric", - required=False, - default="nifi_amount_bytes_read", - help="The name of a certain metric to check", - ) - all_args.add_argument( - "-n", "--namespace", required=True, help="The namespace the test is running in" - ) - all_args.add_argument( - "-p", - "--port", - required=False, - default="8081", - help="The port where metrics are exposed", - ) - all_args.add_argument( - "-t", - "--timeout", - required=False, - default="120", - help="The timeout in seconds to wait for the metrics port to be opened", - ) - - args = vars(all_args.parse_args()) - metric_name = args["metric"] - namespace = args["namespace"] - port = args["port"] - timeout = int(args["timeout"]) - - url = f"http://nifi-node-default-metrics.{namespace}.svc.cluster.local:{port}/metrics/" - - # wait for 'timeout' seconds - t_end = time.time() + timeout - while time.time() < t_end: - try: - response = requests.get(url) - response.raise_for_status() - if metric_name in response.text: - print("Test metrics succeeded!") - exit(0) - else: - print( - f"Could not find metric [{metric_name}] in response:\n {response.text}" - ) - time.sleep(timeout) - except ConnectionError: - # NewConnectionError is expected until metrics are available - time.sleep(10) - - exit(-1) diff --git a/tests/templates/kuttl/smoke_v2/00-patch-ns.yaml.j2 b/tests/templates/kuttl/smoke_v2/00-patch-ns.yaml.j2 deleted file mode 100644 index 67185acf..00000000 --- a/tests/templates/kuttl/smoke_v2/00-patch-ns.yaml.j2 +++ /dev/null @@ -1,9 +0,0 @@ -{% if test_scenario['values']['openshift'] == 'true' %} -# see https://github.com/stackabletech/issues/issues/566 ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' - timeout: 120 -{% endif %} diff --git a/tests/templates/kuttl/smoke_v2/00-range-limit.yaml b/tests/templates/kuttl/smoke_v2/00-range-limit.yaml deleted file mode 100644 index 8fd02210..00000000 --- a/tests/templates/kuttl/smoke_v2/00-range-limit.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: v1 -kind: LimitRange -metadata: - name: limit-request-ratio -spec: - limits: - - type: "Container" - maxLimitRequestRatio: - cpu: 5 - memory: 1 diff --git a/tests/templates/kuttl/smoke_v2/10-assert.yaml.j2 b/tests/templates/kuttl/smoke_v2/10-assert.yaml.j2 deleted file mode 100644 index 50b1d4c3..00000000 --- a/tests/templates/kuttl/smoke_v2/10-assert.yaml.j2 +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -{% if lookup('env', 'VECTOR_AGGREGATOR') %} ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: vector-aggregator-discovery -{% endif %} diff --git a/tests/templates/kuttl/smoke_v2/10-install-vector-aggregator-discovery-configmap.yaml.j2 b/tests/templates/kuttl/smoke_v2/10-install-vector-aggregator-discovery-configmap.yaml.j2 deleted file mode 100644 index 2d6a0df5..00000000 --- a/tests/templates/kuttl/smoke_v2/10-install-vector-aggregator-discovery-configmap.yaml.j2 +++ /dev/null @@ -1,9 +0,0 @@ -{% if lookup('env', 'VECTOR_AGGREGATOR') %} ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: vector-aggregator-discovery -data: - ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} -{% endif %} diff --git a/tests/templates/kuttl/smoke_v2/30-assert.yaml b/tests/templates/kuttl/smoke_v2/30-assert.yaml deleted file mode 100644 index 9a31b9e7..00000000 --- a/tests/templates/kuttl/smoke_v2/30-assert.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 1200 ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: nifi-node-default - labels: - restarter.stackable.tech/enabled: "true" -spec: - template: - spec: - terminationGracePeriodSeconds: 300 -status: - readyReplicas: 2 - replicas: 2 ---- -apiVersion: policy/v1 -kind: PodDisruptionBudget -metadata: - name: nifi-node -status: - expectedPods: 2 - currentHealthy: 2 - disruptionsAllowed: 1 diff --git a/tests/templates/kuttl/smoke_v2/31-assert.yaml.j2 b/tests/templates/kuttl/smoke_v2/31-assert.yaml.j2 deleted file mode 100644 index 7b2eacd4..00000000 --- a/tests/templates/kuttl/smoke_v2/31-assert.yaml.j2 +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 30 -commands: -- script: kubectl get cm -n $NAMESPACE nifi-node-default -o yaml | grep -- 'nifi.web.proxy.host=.*example.com:1234' | xargs test ! -z diff --git a/tests/templates/kuttl/smoke_v2/32-assert.yaml b/tests/templates/kuttl/smoke_v2/32-assert.yaml deleted file mode 100644 index 960b4789..00000000 --- a/tests/templates/kuttl/smoke_v2/32-assert.yaml +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 600 -commands: - # - # Test envOverrides - # - - script: | - kubectl -n $NAMESPACE get sts nifi-node-default -o yaml | yq -e '.spec.template.spec.containers[] | select (.name == "nifi") | .env[] | select (.name == "COMMON_VAR" and .value == "group-value")' - kubectl -n $NAMESPACE get sts nifi-node-default -o yaml | yq -e '.spec.template.spec.containers[] | select (.name == "nifi") | .env[] | select (.name == "GROUP_VAR" and .value == "group-value")' - kubectl -n $NAMESPACE get sts nifi-node-default -o yaml | yq -e '.spec.template.spec.containers[] | select (.name == "nifi") | .env[] | select (.name == "ROLE_VAR" and .value == "role-value")' - # - # Test configOverrides - # - - script: | - kubectl -n $NAMESPACE get cm nifi-node-default -o yaml | yq -e '.data."nifi.properties"' | grep "nifi.diagnostics.on.shutdown.enabled=false" - kubectl -n $NAMESPACE get cm nifi-node-default -o yaml | yq -e '.data."nifi.properties"' | grep "nifi.diagnostics.on.shutdown.verbose=false" - kubectl -n $NAMESPACE get cm nifi-node-default -o yaml | yq -e '.data."nifi.properties"' | grep "nifi.diagnostics.on.shutdown.max.filecount=20" diff --git a/tests/templates/kuttl/smoke_v2/33-assert.yaml b/tests/templates/kuttl/smoke_v2/33-assert.yaml deleted file mode 100644 index 271a8bec..00000000 --- a/tests/templates/kuttl/smoke_v2/33-assert.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -# This test checks if the containerdebug-state.json file is present and valid -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 600 -commands: - - script: kubectl exec -n $NAMESPACE --container nifi nifi-node-default-0 -- cat /stackable/log/containerdebug-state.json | jq --exit-status '"valid JSON"' diff --git a/tests/templates/kuttl/smoke_v2/40-assert.yaml b/tests/templates/kuttl/smoke_v2/40-assert.yaml deleted file mode 100644 index c3bcd76b..00000000 --- a/tests/templates/kuttl/smoke_v2/40-assert.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 1200 ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: nifi-node-default -status: - readyReplicas: 3 - replicas: 3 diff --git a/tests/templates/kuttl/smoke_v2/40-scale-up-nifi.yaml.j2 b/tests/templates/kuttl/smoke_v2/40-scale-up-nifi.yaml.j2 deleted file mode 100644 index 37ded261..00000000 --- a/tests/templates/kuttl/smoke_v2/40-scale-up-nifi.yaml.j2 +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - script: >- - kubectl --namespace $NAMESPACE - patch nificlusters.nifi.stackable.tech nifi - --type=merge --patch '{"spec":{"nodes": {"roleGroups": {"default": {"replicas": 3}}}}}' diff --git a/tests/templates/kuttl/smoke_v2/50-assert.yaml b/tests/templates/kuttl/smoke_v2/50-assert.yaml deleted file mode 100644 index f56ce239..00000000 --- a/tests/templates/kuttl/smoke_v2/50-assert.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 1200 ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: test-nifi -status: - readyReplicas: 1 - replicas: 1 diff --git a/tests/templates/kuttl/smoke_v2/60-assert.yaml.j2 b/tests/templates/kuttl/smoke_v2/60-assert.yaml.j2 deleted file mode 100644 index 1d531af0..00000000 --- a/tests/templates/kuttl/smoke_v2/60-assert.yaml.j2 +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 300 -commands: - - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi.py -u admin -p 'passwordWithSpecialCharacter\@<&>"'"'" -n $NAMESPACE -c 3 - - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi_metrics.py -n $NAMESPACE diff --git a/tests/templates/kuttl/smoke_v2/60-prepare-test-nifi.yaml b/tests/templates/kuttl/smoke_v2/60-prepare-test-nifi.yaml deleted file mode 100644 index c3ac9b79..00000000 --- a/tests/templates/kuttl/smoke_v2/60-prepare-test-nifi.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - script: kubectl cp -n $NAMESPACE ./test_nifi_metrics.py test-nifi-0:/tmp - - script: kubectl cp -n $NAMESPACE ./test_nifi.py test-nifi-0:/tmp - - script: kubectl cp -n $NAMESPACE ./cacert.pem test-nifi-0:/tmp diff --git a/tests/templates/kuttl/smoke_v2/cacert.pem b/tests/templates/kuttl/smoke_v2/cacert.pem deleted file mode 100644 index ebe73910..00000000 --- a/tests/templates/kuttl/smoke_v2/cacert.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDVTCCAj2gAwIBAgIJAJ8/0entaUgnMA0GCSqGSIb3DQEBCwUAMCYxJDAiBgNV -BAMMG3NlY3JldC1vcGVyYXRvciBzZWxmLXNpZ25lZDAeFw0yMjAxMTIxNDU3NDVa -Fw0yNDAxMTIxNTAyNDVaMCYxJDAiBgNVBAMMG3NlY3JldC1vcGVyYXRvciBzZWxm -LXNpZ25lZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALLKNGhq5gE+ -mL9zFCLqtc22CLk8BSbjesjUEhBK3kxDvFDa2ou5atH0eUFjtOSszay2oBrCTVWK -wZBsdUkL0HkW/wq9A8EUkQ8EownXnsxpI61CLNGLPpBZc+CRHhyWDD6BqwGvEHEv -W546mh6k49//7zCiYfTK9/LCKBCFdDV6Sb7mNJ8HbNUj54uwC6iOgH25OCRDh4Bt -zXoSrV9GLAm6AM25ZFo+ONOUBMtv7pavaR0CFMnAixl2NKV2wyLBYAYnJgdJFzGD -8mP6HwuR7e2g7PkcyC01EnX4iOIuuKHT/Xl9ynut4nHI7g6popotgashrQ5Jf8MS -Kf98O12LzSMCAwEAAaOBhTCBgjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRf -U9OxCBwCqYiUjWqY05sz3a6cmjBABgNVHSMEOTA3oSqkKDAmMSQwIgYDVQQDDBtz -ZWNyZXQtb3BlcmF0b3Igc2VsZi1zaWduZWSCCQCfP9Hp7WlIJzAOBgNVHQ8BAf8E -BAMCAYYwDQYJKoZIhvcNAQELBQADggEBAA8Flk1XOb1pH33Mbie5ronP2xw/xf6t -Ox3PBEZ+5/jSPdIwoSaRp9JoP0L9Rg68jzcl5QMa4pOYWe+C1q8aZP0tjfq1eJfO -UD5ik2DQgEuoF1ELgW1xoM38vkd8wgE711swDHK2zAsOudSzO4XZ4rQ6kaXXtoej -2kFhxDYcC+na90LdkJM0kAqrjxlFaP7WgUK+HA2iN00CFSOI9FVdppLtootbcb+y -+WfXxM7gA9Exg4f2vKGVx7UxB/k4AbPvogBQZvK8VoAQocAhWrw7o2rqAesAw6JD -WwQjM69TlEfbHYXtTfMbi01Wi5TtVhFCjyXK6KDsqSgU+9McExIy70k= ------END CERTIFICATE----- diff --git a/tests/templates/kuttl/smoke_v2/test_nifi.py b/tests/templates/kuttl/smoke_v2/test_nifi.py deleted file mode 100755 index 5c72c1a6..00000000 --- a/tests/templates/kuttl/smoke_v2/test_nifi.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -import requests -import json -import argparse -import urllib3 -from time import sleep - - -def get_token(nifi_host, username, password): - nifi_headers = { - "content-type": "application/x-www-form-urlencoded; charset=UTF-8", - } - data = {"username": username, "password": password} - - # TODO: handle actual errors when connecting properly - nifi_url = nifi_host + "/nifi-api/access/token" - response = requests.post( - nifi_url, headers=nifi_headers, data=data, verify=False - ) # , cert='./tmp/cacert.pem') - - if response.ok: - nifi_token = response.content.decode("utf-8") - return "Bearer " + nifi_token - else: - print(f"Failed to get token: {response.status_code}: {response.content}") - exit(-1) - - -if __name__ == "__main__": - # Construct an argument parser - all_args = argparse.ArgumentParser() - - # Add arguments to the parser - all_args.add_argument("-u", "--user", required=True, help="Username to connect as") - all_args.add_argument( - "-p", "--password", required=True, help="Password for the user" - ) - all_args.add_argument( - "-n", "--namespace", required=True, help="Namespace the test is running in" - ) - all_args.add_argument( - "-c", "--count", required=True, help="The expected number of Nodes" - ) - args = vars(all_args.parse_args()) - - # disable warnings as we have specified non-verified https connections - urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - - host = f"https://nifi-node-default-1.nifi-node-default-headless.{args['namespace']}.svc.cluster.local:8443" - token = get_token(host, args["user"], args["password"]) - headers = {"Authorization": token} - node_count = int(args["count"]) - - x = 0 - while x < 15: - url = host + "/nifi-api/controller/cluster" - cluster = requests.get( - url, headers=headers, verify=False - ) # , cert='/tmp/cacert.pem') - if cluster.status_code != 200: - print("Waiting for cluster...") - else: - cluster_data = json.loads(cluster.content.decode("utf-8")) - nodes = cluster_data["cluster"]["nodes"] - if len(nodes) != node_count: - print( - f"Cluster should have {node_count} nodes at this stage, but has: {len(nodes)}" - ) - else: - connected = True - for node in nodes: - if node["status"] != "CONNECTED": - print( - f"Node {node['nodeId']} is in state {node['status']} but should have been CONNECTED" - ) - connected = False - if connected: - print("Test succeeded!") - exit(0) - print("Retrying...") - x = x + 1 - sleep(10) - - print("Test failed") - exit(-1) diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index 38e1b6ec..5e30e658 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -2,7 +2,6 @@ dimensions: - name: nifi values: - - 1.28.1 - 2.6.0 - 2.7.2 - 2.9.0 @@ -11,7 +10,7 @@ dimensions: # - 2.6.0,oci.stackable.tech/sandbox/nifi:2.6.0-stackable0.0.0-dev - name: nifi_old values: - - 1.28.1 + - 2.6.0 - name: nifi_new values: - 2.9.0 @@ -27,16 +26,6 @@ dimensions: values: - 2.7.2 - 2.9.0 - # TODO: Can be removed once NiFi 1.x.x is removed - only for separate smoke tests - - name: nifi-v1 - values: - - 1.28.1 - # TODO: Can be removed once NiFi 1.x.x is removed - only for separate smoke tests - - name: nifi-v2 - values: - - 2.6.0 - - 2.7.2 - - 2.9.0 # Use Kubernetes manager if set to false - name: use-zookeeper-manager values: @@ -99,17 +88,9 @@ tests: - nifi - zookeeper-latest - openshift - # TODO: Can be removed once NiFi 1.x.x is removed - only for separate smoke tests - - name: smoke_v1 + - name: smoke dimensions: - - nifi-v1 - - zookeeper - - listener-class - - openshift - # TODO: Can be renamed to smoke once NiFi 1.x.x is removed - only for separate smoke tests - - name: smoke_v2 - dimensions: - - nifi-v2 + - nifi - zookeeper - use-zookeeper-manager - listener-class @@ -136,7 +117,7 @@ tests: - openshift - name: cluster-sync-cs-bug dimensions: - - nifi-v2 + - nifi - openshift - name: oidc-opa dimensions: @@ -187,7 +168,7 @@ suites: expr: "true" - name: smoke-latest select: - - smoke_v2 + - smoke patch: - dimensions: - expr: last From c43a8232e2e0cb62b830effc78cbb6f7388b32f3 Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 16 Jun 2026 15:54:42 +0200 Subject: [PATCH 03/10] test: Remove template gates from upgrade test --- tests/templates/kuttl/upgrade/04-assert.yaml.j2 | 7 ------- tests/templates/kuttl/upgrade/04-prepare-test-nifi.yaml.j2 | 4 ---- tests/templates/kuttl/upgrade/07-assert.yaml.j2 | 3 --- 3 files changed, 14 deletions(-) diff --git a/tests/templates/kuttl/upgrade/04-assert.yaml.j2 b/tests/templates/kuttl/upgrade/04-assert.yaml.j2 index 1f66464c..f7fb1390 100644 --- a/tests/templates/kuttl/upgrade/04-assert.yaml.j2 +++ b/tests/templates/kuttl/upgrade/04-assert.yaml.j2 @@ -4,13 +4,6 @@ kind: TestAssert timeout: 300 commands: - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi.py -u admin -p supersecretpassword -n $NAMESPACE -c 3 -{% if test_scenario['values']['nifi_old'].split(',')[0].startswith('1.') %} - - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi_metrics.py -n $NAMESPACE -{% endif %} -{% if test_scenario['values']['nifi_old'].split(',')[0] == '2.0.0' %} - - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "python /tmp/flow.py -e https://test-nifi-node-default-0.test-nifi-node-default-headless.$NAMESPACE.svc.cluster.local:8443 run json /tmp/generate-and-log-flowfiles.json > /tmp/old_input" -{% else %} - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "python /tmp/flow.py -e https://test-nifi-node-default-0.test-nifi-node-default-headless.$NAMESPACE.svc.cluster.local:8443 run template /tmp/generate-and-log-flowfiles.xml > /tmp/old_input" -{% endif %} # This tests if the output contains an Error or zero flow files are queued, which also indicates that something went wrong - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "cat /tmp/old_input | grep -Eov 'Error|\b0\b'" diff --git a/tests/templates/kuttl/upgrade/04-prepare-test-nifi.yaml.j2 b/tests/templates/kuttl/upgrade/04-prepare-test-nifi.yaml.j2 index fc488c1e..9fe76b7b 100644 --- a/tests/templates/kuttl/upgrade/04-prepare-test-nifi.yaml.j2 +++ b/tests/templates/kuttl/upgrade/04-prepare-test-nifi.yaml.j2 @@ -5,9 +5,5 @@ commands: - script: kubectl cp -n $NAMESPACE ./test_nifi_metrics.py test-nifi-0:/tmp - script: kubectl cp -n $NAMESPACE ./test_nifi.py test-nifi-0:/tmp - script: kubectl cp -n $NAMESPACE ./flow.py test-nifi-0:/tmp -{% if test_scenario['values']['nifi_old'].split(',')[0] == '2.0.0' %} - - script: kubectl cp -n $NAMESPACE ./generate-and-log-flowfiles.json test-nifi-0:/tmp -{% else %} - script: kubectl cp -n $NAMESPACE ./generate-and-log-flowfiles.xml test-nifi-0:/tmp -{% endif %} - script: kubectl cp -n $NAMESPACE ./cacert.pem test-nifi-0:/tmp diff --git a/tests/templates/kuttl/upgrade/07-assert.yaml.j2 b/tests/templates/kuttl/upgrade/07-assert.yaml.j2 index 6f10d137..dae1ecfc 100644 --- a/tests/templates/kuttl/upgrade/07-assert.yaml.j2 +++ b/tests/templates/kuttl/upgrade/07-assert.yaml.j2 @@ -6,9 +6,6 @@ metadata: timeout: 300 commands: - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi.py -u admin -p supersecretpassword -n $NAMESPACE -c 3 -{% if test_scenario['values']['nifi_new'].split(',')[0].startswith('1.') %} - - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi_metrics.py -n $NAMESPACE -{% endif %} - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "python /tmp/flow.py -e https://test-nifi-node-default-0.test-nifi-node-default-headless.$NAMESPACE.svc.cluster.local:8443 query > /tmp/new_input" # This tests if the output contains an Error or zero flow files are queued, which also indicates that something went wrong - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "cat /tmp/new_input | grep -Eov 'Error|\b0\b'" From b4048816981503ed140df6b457696283c42756f0 Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 16 Jun 2026 21:59:25 +0200 Subject: [PATCH 04/10] test: Adjust flow.py file --- .../kuttl/upgrade/03-install-test-nifi.yaml | 5 + .../templates/kuttl/upgrade/04-assert.yaml.j2 | 2 +- .../upgrade/04-prepare-test-nifi.yaml.j2 | 2 +- tests/templates/kuttl/upgrade/flow.py | 227 +++++------------- 4 files changed, 73 insertions(+), 163 deletions(-) mode change 100755 => 100644 tests/templates/kuttl/upgrade/flow.py diff --git a/tests/templates/kuttl/upgrade/03-install-test-nifi.yaml b/tests/templates/kuttl/upgrade/03-install-test-nifi.yaml index 243b09dd..7992be3d 100644 --- a/tests/templates/kuttl/upgrade/03-install-test-nifi.yaml +++ b/tests/templates/kuttl/upgrade/03-install-test-nifi.yaml @@ -20,3 +20,8 @@ spec: image: oci.stackable.tech/sdp/testing-tools/nifi:0.3.0-stackable0.0.0-dev stdin: true tty: true + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace diff --git a/tests/templates/kuttl/upgrade/04-assert.yaml.j2 b/tests/templates/kuttl/upgrade/04-assert.yaml.j2 index f7fb1390..9297b0f7 100644 --- a/tests/templates/kuttl/upgrade/04-assert.yaml.j2 +++ b/tests/templates/kuttl/upgrade/04-assert.yaml.j2 @@ -4,6 +4,6 @@ kind: TestAssert timeout: 300 commands: - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi.py -u admin -p supersecretpassword -n $NAMESPACE -c 3 - - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "python /tmp/flow.py -e https://test-nifi-node-default-0.test-nifi-node-default-headless.$NAMESPACE.svc.cluster.local:8443 run template /tmp/generate-and-log-flowfiles.xml > /tmp/old_input" + - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "python /tmp/flow.py > /tmp/old_input" # This tests if the output contains an Error or zero flow files are queued, which also indicates that something went wrong - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "cat /tmp/old_input | grep -Eov 'Error|\b0\b'" diff --git a/tests/templates/kuttl/upgrade/04-prepare-test-nifi.yaml.j2 b/tests/templates/kuttl/upgrade/04-prepare-test-nifi.yaml.j2 index 9fe76b7b..f2b48bcf 100644 --- a/tests/templates/kuttl/upgrade/04-prepare-test-nifi.yaml.j2 +++ b/tests/templates/kuttl/upgrade/04-prepare-test-nifi.yaml.j2 @@ -5,5 +5,5 @@ commands: - script: kubectl cp -n $NAMESPACE ./test_nifi_metrics.py test-nifi-0:/tmp - script: kubectl cp -n $NAMESPACE ./test_nifi.py test-nifi-0:/tmp - script: kubectl cp -n $NAMESPACE ./flow.py test-nifi-0:/tmp - - script: kubectl cp -n $NAMESPACE ./generate-and-log-flowfiles.xml test-nifi-0:/tmp + - script: kubectl cp -n $NAMESPACE ./generate-and-log-flowfiles.json test-nifi-0:/tmp - script: kubectl cp -n $NAMESPACE ./cacert.pem test-nifi-0:/tmp diff --git a/tests/templates/kuttl/upgrade/flow.py b/tests/templates/kuttl/upgrade/flow.py old mode 100755 new mode 100644 index 61834b43..8ef0cf43 --- a/tests/templates/kuttl/upgrade/flow.py +++ b/tests/templates/kuttl/upgrade/flow.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# This script is used to create a flow from a template, run it for a couple -# of seconds, stop it and then query the number of queued flow files. import nipyapi from nipyapi.canvas import ( @@ -11,25 +9,19 @@ recurse_flow, ) from nipyapi.security import service_login -from nipyapi.templates import get_template, upload_template, deploy_template -from nipyapi.nifi.models import FlowEntity +from nipyapi.nifi.api_client import ApiClient -from typing import Union, List +from typing import Union from dataclasses import dataclass -import re import os -import sys import time -import argparse import urllib3 +ENDPOINT = f"https://test-nifi-node-default-0.test-nifi-node-default-headless.{os.environ['NAMESPACE']}.svc.cluster.local:8443" USERNAME = "admin" -PASSWORD = "supersecretpassword" -TEMPLATE_NAME = "generate-and-log-flowfiles" -TEMPLATE_FILE = f"{TEMPLATE_NAME}.xml" -JSON_NAME = "generate-and-log-flowfiles" -JSON_FILE = f"{JSON_NAME}.json" +PASSWORD = "supersecretpassword" # Can we use open("/nifi-users/admin").read() here? +FLOWFILE = "/tmp/generate-and-log-flowfiles.json" CONNECTION_NAME = "output-connection" @@ -38,170 +30,83 @@ class Error: message: str -def parse_args(args: List[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Setup a flow and query the status.") - parser.add_argument( - "-e", - "--endpoint", - help="Nifi endpoint URL (must be https). As of 2022-08-29 we cant use https://nifi:8443 here because Nifi's check for the host header validity fails. The error message looks like:

The request contained an invalid host header [nifi:8443] in the request [/nifi-api]. Check for request manipulation or third-party intercept.

", - required=True, - ) - parser.add_argument( - "-u", - "--user", - help="Nifi login user.", - ) - parser.add_argument( - "-p", - "--password", - help="Nifi login password.", - ) - subparsers = parser.add_subparsers(dest="subcommand", required=True) - run_parser = subparsers.add_parser( - "run", - help="Install and run a flow.", - ) - format_parser = run_parser.add_subparsers(dest="formatcommand") - template_parser = format_parser.add_parser( - "template", help="Use the template format for the flow." - ) - template_parser.add_argument( - "template", - help="Nifi template file.", - ) - json_parser = format_parser.add_parser( - "json", help="Use the json format for the flow." - ) - json_parser.add_argument( - "json", - help="Nifi json file.", - ) - subparsers.add_parser( - "query", - help="Query the flow.", - ) - return parser.parse_args(args) - - -def login(endpoint: str, user: str, passwd: str) -> bool: - nipyapi.config.nifi_config.host = f"{endpoint}/nifi-api" - nipyapi.config.nifi_config.verify_ssl = False - return service_login(username=user, password=passwd) - - -def flow_from_template( - pg_id: str, template_file: str, template_name: str -) -> FlowEntity: - upload_template(pg_id, template_file) - template_id = get_template(template_name).id - return deploy_template(pg_id, template_id, 200, 0) +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def schedule(pg_id: str, toggle: bool) -> bool: for controller in list_all_controllers(pg_id): schedule_controller(controller, scheduled=toggle) + return schedule_process_group(pg_id, scheduled=toggle) def flow_files_queued(connection_name: str) -> Union[str, Error]: """Returns the current input record number for the given component""" flow = recurse_flow() - c = [ - c - for c in flow.process_group_flow.flow.connections - if c.status.name == connection_name - ] - if c: - return c[0].status.aggregate_snapshot.flow_files_queued - else: - # for nifi 2.x the flow is loaded via api into a new process group - # we need to look for the connection in that process group instead - if flow.process_group_flow.flow.process_groups: - inner_flow = recurse_flow(flow.process_group_flow.flow.process_groups[0].id) - inner_c = [ - inner_c - for inner_c in inner_flow.process_group_flow.flow.connections - if inner_c.status.name == connection_name - ] - if inner_c: - return inner_c[0].status.aggregate_snapshot.flow_files_queued - else: - return Error(f"No connection named '{connection_name}' found.") + + if flow.process_group_flow.flow.process_groups: + inner_flow = recurse_flow(flow.process_group_flow.flow.process_groups[0].id) + conn = [ + conn + for conn in inner_flow.process_group_flow.flow.connections + if conn.status.name == connection_name + ] + if conn: + return conn[0].status.aggregate_snapshot.flow_files_queued else: return Error(f"No connection named '{connection_name}' found.") + else: + return Error(f"No connection named '{connection_name}' found.") def main(): - # turn this on to debug Nifi api calls. Also need to import logging - # logging.getLogger().setLevel(logging.INFO) - - args = parse_args(sys.argv[1:]) - - urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - - username = args.user or USERNAME - password = args.password or PASSWORD - - if args.subcommand == "run": - login(args.endpoint, username, password) - pg_id = get_root_pg_id() - - if args.formatcommand == "template": - template_file = args.template or TEMPLATE_FILE - template_name = re.sub(r"\..+$", "", os.path.basename(template_file)) - - flow_from_template(pg_id, template_file, template_name) - - elif args.formatcommand == "json": - json_file = args.json or JSON_FILE - json_name = re.sub(r"\..+$", "", os.path.basename(json_file)) - - if not nipyapi.config.nifi_config.api_client: - nipyapi.config.nifi_config.api_client = nipyapi.nifi.ApiClient() - - header_params = {} - header_params["Accept"] = ( - nipyapi.config.nifi_config.api_client.select_header_accept( - ["application/json"] - ) - ) - header_params["Content-Type"] = ( - nipyapi.config.nifi_config.api_client.select_header_content_type( - ["multipart/form-data"] - ) - ) - - nipyapi.config.nifi_config.api_client.call_api( - "/process-groups/{pg_id}/process-groups/upload", - "POST", - path_params={"pg_id": pg_id}, - header_params=header_params, - _return_http_data_only=True, - post_params=[ - ("id", pg_id), - ("groupName", json_name), - ("positionX", 100), - ("positionY", 10), - ("clientId", nipyapi.nifi.FlowApi().generate_client_id()), - ], - files={"file": json_file}, - auth_settings=["tokenAuth"], - ) - - # get the process group id of the newly created process group to pass it to the schedule() function - # for nifi 2.x we need to schedule the inner process group and instead of the root process group - flow = recurse_flow() - inner_flow_id = flow.process_group_flow.flow.process_groups[0].id - pg_id = inner_flow_id - - schedule(pg_id, True) # start - time.sleep(5) # give the flow 5 seconds to run - schedule(pg_id, False) # stop - print(flow_files_queued(CONNECTION_NAME)) - - elif args.subcommand == "query": - login(args.endpoint, username, password) - print(flow_files_queued(CONNECTION_NAME)) + nipyapi.config.nifi_config.host = f"{ENDPOINT}/nifi-api" + nipyapi.config.nifi_config.verify_ssl = False + + print(f"Logging in as {USERNAME}") + service_login(username=USERNAME, password=PASSWORD) + print("Logged in") + + pg_id = get_root_pg_id() + print(f"Got root process group id: {pg_id}") + + if not nipyapi.config.nifi_config.api_client: + nipyapi.config.nifi_config.api_client = ApiClient() + + header_params = {} + header_params["Accept"] = ( + nipyapi.config.nifi_config.api_client.select_header_accept(["application/json"]) + ) + header_params["Content-Type"] = ( + nipyapi.config.nifi_config.api_client.select_header_content_type( + ["multipart/form-data"] + ) + ) + + print("Uploading process group") + nipyapi.config.nifi_config.api_client.call_api( + "/process-groups/{pg_id}/process-groups/upload", + "POST", + path_params={"pg_id": pg_id}, + header_params=header_params, + _return_http_data_only=True, + post_params=[ + ("id", pg_id), + ("groupName", "Upgrade Test"), + ("positionX", 100), + ("positionY", 10), + ("clientId", nipyapi.nifi.FlowApi().generate_client_id()), + ], + files={"file": FLOWFILE}, + auth_settings=["tokenAuth"], + ) + print("Process group uploaded") + + schedule(pg_id, True) # Start the flow + time.sleep(5) # Let the flow run for 5 seconds + schedule(pg_id, False) # Stop the flow + + print(flow_files_queued(CONNECTION_NAME)) if __name__ == "__main__": From 91df0df6a9a542e3950c39e6e9468b17251fc8bf Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 18 Jun 2026 11:00:12 +0200 Subject: [PATCH 05/10] test: Add sleep, move assertions This brief sleep ensures that the Pod rotation has started before the assertion (for readyReplicas) is run. Previously, the assertion succeeded before the version upgrade was done and as such, the 07 assertion failed. --- tests/templates/kuttl/upgrade/04-assert.yaml.j2 | 1 + .../upgrade/{05-assert.yaml.j2 => 06-assert.yaml.j2} | 0 tests/templates/kuttl/upgrade/06-sleep.yaml | 8 ++++++++ tests/templates/kuttl/upgrade/07-assert.yaml.j2 | 1 + 4 files changed, 10 insertions(+) rename tests/templates/kuttl/upgrade/{05-assert.yaml.j2 => 06-assert.yaml.j2} (100%) create mode 100644 tests/templates/kuttl/upgrade/06-sleep.yaml diff --git a/tests/templates/kuttl/upgrade/04-assert.yaml.j2 b/tests/templates/kuttl/upgrade/04-assert.yaml.j2 index 9297b0f7..00922463 100644 --- a/tests/templates/kuttl/upgrade/04-assert.yaml.j2 +++ b/tests/templates/kuttl/upgrade/04-assert.yaml.j2 @@ -3,6 +3,7 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert timeout: 300 commands: + # - script: kubectl wait -n $NAMESPACE --for='jsonpath={.status.readyReplicas}=3' sts/test-nifi-node-default --timeout=60s - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi.py -u admin -p supersecretpassword -n $NAMESPACE -c 3 - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "python /tmp/flow.py > /tmp/old_input" # This tests if the output contains an Error or zero flow files are queued, which also indicates that something went wrong diff --git a/tests/templates/kuttl/upgrade/05-assert.yaml.j2 b/tests/templates/kuttl/upgrade/06-assert.yaml.j2 similarity index 100% rename from tests/templates/kuttl/upgrade/05-assert.yaml.j2 rename to tests/templates/kuttl/upgrade/06-assert.yaml.j2 diff --git a/tests/templates/kuttl/upgrade/06-sleep.yaml b/tests/templates/kuttl/upgrade/06-sleep.yaml new file mode 100644 index 00000000..5030b27a --- /dev/null +++ b/tests/templates/kuttl/upgrade/06-sleep.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # Sleep for a little while to ensure the pod rotation of the NiFi cluster has started. Otherwise, + # the assertion in 06 will immediately succeed because it is run _before_ the pod rotation has + # started. + - script: sleep 15 diff --git a/tests/templates/kuttl/upgrade/07-assert.yaml.j2 b/tests/templates/kuttl/upgrade/07-assert.yaml.j2 index dae1ecfc..21054b7c 100644 --- a/tests/templates/kuttl/upgrade/07-assert.yaml.j2 +++ b/tests/templates/kuttl/upgrade/07-assert.yaml.j2 @@ -5,6 +5,7 @@ metadata: name: test-nifi-updated timeout: 300 commands: + # - script: kubectl wait -n $NAMESPACE --for='jsonpath={.status.readyReplicas}=3' sts/test-nifi-node-default --timeout=60s - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi.py -u admin -p supersecretpassword -n $NAMESPACE -c 3 - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "python /tmp/flow.py -e https://test-nifi-node-default-0.test-nifi-node-default-headless.$NAMESPACE.svc.cluster.local:8443 query > /tmp/new_input" # This tests if the output contains an Error or zero flow files are queued, which also indicates that something went wrong From de384ecd12548cfe2c494a8e403ad1a7dbfd4e9e Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 18 Jun 2026 11:04:15 +0200 Subject: [PATCH 06/10] test: Add comment in flow.py --- tests/templates/kuttl/upgrade/flow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/templates/kuttl/upgrade/flow.py b/tests/templates/kuttl/upgrade/flow.py index 8ef0cf43..1853a126 100644 --- a/tests/templates/kuttl/upgrade/flow.py +++ b/tests/templates/kuttl/upgrade/flow.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +# This script is used to create a process group, run it for a couple of seconds, stop it and then +# query the number of queued flow files. import nipyapi from nipyapi.canvas import ( From 770a118a94036545f1732743062e7ee5b8435cb8 Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 18 Jun 2026 11:12:48 +0200 Subject: [PATCH 07/10] docs: Remove NiFi 1 section on Iceberg table page --- .../assets/images/put-iceberg-processor.png | Bin 87445 -> 0 bytes .../writing-to-iceberg-tables.adoc | 88 ------------------ 2 files changed, 88 deletions(-) delete mode 100644 docs/modules/nifi/assets/images/put-iceberg-processor.png diff --git a/docs/modules/nifi/assets/images/put-iceberg-processor.png b/docs/modules/nifi/assets/images/put-iceberg-processor.png deleted file mode 100644 index 92e1154c0a049a3619031724e3d51c9b32f1c5e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87445 zcmd?QWmMZu*Dnmki)(SW7I%k>;$DioySqC@in~K`THM_wI0cFZmtcY5PB>h5@8|pb z@3=$X^N*FmwadmG)@Vb{b*+K~P0Yy)e3|!bF-86f z5vqDINhufv%LIV8pTFi@78^~pq2nKl4pTnX>)uVrfvXgW8eOJ+ZCo#dR)*g#%H!1F4I5;k>sSi_b6_=G{K60VrMm9i=rdM@PBtoIc7Jz~2MdI~9DMe`3%dOzlVL zxIiJZ;No9Z(aIFTAuRm4*Qxx}4#vyHj6GYQeLM1ZzX=Ziy_64o{8Lmw$`)r1t%F#2 zdp?iOrK{z~_t`QMP2#dU?lrycun60Tw-x-!q4$7r`$2K%M1&x#PB|4NqNfoIzuWdB z$5$Eq<)4kqRa+N3IsCg}?Cb8_3$wUM6CN~FK;S^c(uVm#Ca6wIX}`=agPm)#uTHw& zSed|SiLB_rI?0T)N|RZXZ9`?TIc8qo;S=>6T_(8rO0^Z(BF7=#5;>mgjZa1Z?o$IU~0u_wEpg za;=M6lCieb`i5$}sA;!QCJ>D!g%e9O{n4oN-1Tf}j%Bu^x*N#3*AH=Mus!5W~9TPW@7aHQz*S-N}}z(VT+2c4pjixQ5} zwHl7kRTLK-+5yL25**5>t*9Kf)BHUGuQLI?ohuj5X2OPN2zG)?L4ib9xiT3%>-*0S z+uh-;x}y(jyHVD_@tOQpg-R4-tn}2ux%PIv5=mTq^Q|)oNLGn1r{&bl=t)+{pFe+^ z({M!~FLW418&<{@=jY<50nLrcI2|*`Ltej?^VWlvytZJb@AGmPzYc0Kt1VA{1hw!m zXEjH3Sg(|=&7A9A3627Xa z9~RKm!mXyM)1Uq#W9=Cbb(t&QO*Yy7{AX1?9x$-yR7yshn@*L9I$<Zi=v%(dFeDhzR>p8>W=QfC$DG%_b(ck&7QWKCyD%BVEE#p8dwuM#bj37O^^D zHae9+#lV+SUE4h}AL$!EZ)QhB1tIOp<;l9A6~_5|x%aL{C4K<9XJ>yb3_=W`G|57} z-?8wajd!#sJQ8Spus0<|LIfb*_iUpCHJa8!HWsh}yFxvli+z$iqkaCs}Ek3CSq6li1qB9U(OuJ9-e%yfR zm(Y{qqqQ-5lMWZ}SSeGd1gi!D?o(SW!sIT6Mz@OhSZ2YNTIzvoc zk~sxny@pLbJq5V`S#qu@EI4m6-yuUZ#v&pa`DcoX_pG+CY)1UEJu(IynjZg{G+fRv z-wRmLJ4y$o>bBw(S$E>#v47SB4Ob0ELVnSZRtbK#iHwUaaRwtA-+t|QVqc&s1SE}@ zYB3x8Mv0kc_K0Vnko<)3AtxwNTI`v^(X;IPt*opmtS3o#NT`iQyBNcO{-Mdan3nf>&{dq zfAD}Ui`PG?-@L9ddTv!&KmN-A83CFdGSQLp@vYYUr6|fMj!@Q>Ytc*p?9y1i13q{B z1RpOw{7iAi z1a;QtH4sr>;+|u-rlV#?&N11FQZ&)E77`lobcT)K%B!r=Q8Z}_%IrNl+aFCrjKBWJ zx^4c|KreUbkyyvC4VQud$I57w%xyOU(tY=P_KS?p308dFm(Xfs^mru>g%4pshtL< zq0IXXqt@8QytuAz6QL8#Ruj32-2t8YCbFs$lYyxBbt%pFIHFpliZ{~^T`iT_1Xjc` zAP~-`;mAgdnN#Gy_~JfqF&`XNmpE%EVC2O|R{ne2oS(rZ@In^ra<;>Yr#G-MT@*Jq z(Y>Y}`WYmn!w$a46=F;AGORKG(Hr*tJ=6R&tEF!RY^Hm9*Vs#C0z1qo&VHC_=y zxUi^H+MQTLVx^RLn#g5sk_fu;r~$@TUbJTQdK)b`nqv3rpGmoDsfwfLy@2;q*~%_^ z&JI)LE#3FRDruO;rc~5V_B~CU8EqUQV_U_w)k%Xa66|#DU^JMnFaveX=HE8T3_d%G zUDqu<4V%g1-@j$ui!GK^nlh};Y@paTuJLvEDN z-id#_dKho!l5HU5=PFkv^~r1%ME^RUa&9EVal{l5`KqTCCYC{JYL$@^WRxCH2KOqX zt=#TE{&5L7a{*EIkI^^kt z*kOI_VwN4&)N;%2{2q`R^r=VFFmTVrFq(Nwp>;Zs|BjI+w`fHte5rTBX>%Fk1pF@O zJ6N+HZ=*J5Xry9ujQ5?|Z{_-0ccbc#Pus?dU59VV*s>#x3~A)fl4Ke!eKM-V;^|ZV z+FZm3#-GR3{7}yHy;5A)!`~l`y8iV!9%u#PsDOQzBwX5yyW|+e41lC`x#{!YYV*LM z$7+vQclNB8=B}Q~EP(@hlR-*at`vJcx~Gl#e$%qAoYH1|H!w8_`rkeg1j&K2{VKP(T2jF~8cF3dzyB zr&`AoW!#{HImVx_Um+&SHDqv1AEP<6<&S097`xbjw2tjp7>-%|0hzVsxgT1%UBTEB zqQ5*Hch@5}iW%;t@qBbt2L~&aIu!2e+8xa8gt^Z)hNpLE3Brj8)2%P*WiEYlGHg~{ z28HV7oL0zro8!UoqS>G9Y;q_RLJ`nnLJ;cctk@mAn_xHSH&#}J1)n`C>az#C5oo0d z2^R_HaI1bF4W8bLJx}*h@^j?nMhV`cZ&I#nH5M$jG~t@wei74OQTya1neW@JCzRmg zH^i}ey*mID_h>{_@a5;=;A+1~Z!ggk2K!#9l8btHT20wQ$s{Jgi4Sk!x4cVGyoB?(xvCU(GYFx{{t+is=9gTzVW8VysG4_Sq6 z?>pUtVuHuknY}qsD93x>`L2x$JYC}NDB2tRtc?`ru+Vh_qUYuAh;R8RlD!f`P-n0E zPIZLxCXKGj*N#mi%y`0Z9FuK=9+|Im(?16UeqdHU-dc%VstdsStcKcdYXj>u>-nh1 z#ucYM;mg=!UWN;h|Ng z;|GbdkOfXxB6qZ$jP&oW3#^kT=cR^fV2M+YNEk1RGl0I)8#6hydqpN<(xL-c=Xweu z>K~GNjPws1`^RnK91b8VPY(*8;ifK%BB}n7HxMH>)%_uXg=3)ah;#*tIU(a^fHG6F zF|y&Zx#A5PiffimWcQtKONbb0xwwP)u2$IG)KgBr*N#ev>odJb=6~GcPbn*FaVgwb zY6>$FYr@Qd_116Wi25E)d}e`IHKu>l4s5rbo66@{aV0{qp1MS~BJ zAfNbsx$_w_Yv<&Tr~20Abxgv-NkbQGK6A7>hk3!7DilRna+&d8< zpuC3B{BdAK`;KgDe@=5evJZJ?J7NNqyskHWyi|GgUU?gQWtn72RPyE{DJQLrE%I$z zp0q`ao72jOABetwBQ3}gWeY~AT=ZB*6jVb;9cqnwO$w3pA%%-v_vF5AUxWB8#~-FnF;yrWh8DB?RGc~5g%Xp-rqmm zRi^N84y?^LrHF|-xh~9ozAQc^BtWVkBO0Q&Vt1O>M+8=S@7~09X&hiOm#ahJ7M^ye zLl-QEEklP1KI?Aip@5)Earip7~B29*zy;l?~;))E+R8pRFB_%@#+*+uolhpB{nfIL8YsTd@xjN}icj=Y1-IG# zG{XGH?}C$hWD-ryH|)lb)|=h&VotVXXDGa~(`=MWi<1*zvteBI9U93*A`pDUMEW^c~Cm8LRI__1-z!FO$BUtS>{1mU_lCqJWWHLZ+upsm>PeV6UT9%TL_1V++lUm9EGACLt zM@swscVl6duAvT$t3Ts+{)xUpT{V%q=bUan`?4)lFIuX`|bRn7BBC@%<``>?{($9_t?_-Nlg?#W&;qI2-KeTNbD(m zzC&HYtys3w1-8{|gF@mULb|g53gDakA!<1YJUy1VYG%Tw6B10%m|iLDyQ?rqeH+>{ z$0-hD?+Sp|QxXJrI^#y-vk#ccp>tFalLgvigts|ij^wr_+obDM*EdjVnd^&B;NLRE`qoZfF z6-KVJS4`zdx__?ADENI4El2>d;=bNHp+>vRY7-F}jNtHOp}xmOemA*6Q?;(6;w8f; zVM$GeNJzjS?Zol~@Vq0FkeyQpl}8%40hgOQb3K~(s|G3gsS0vxb7!ux;0BZax%eZ? zYkxH4^J%{|FxWR?@+tGO`ShTEY2xrYWagx(vNFbT>F6N0U6>Vdgie$1bgCMP z(~l8ajcDTY;nK**Y$CXJJ@9#!90ao-VC;d29WL(t1uKFlH?4JJWZVUzy9vnR+%;+s zkA2L|{`4r27x{58YjQXvUrBk>5L#FEEHc=krZ z>C0$&x=IL~)A{}M^zOo!e@|zGznY4~OT@|Xr`6(|dTxaH{h!8%NdakYab!M zS^1Y-L1@15(wm|~w^TjkX1ABi6=8@bbK+xv0~mPe)AFPN+Uh|b==iU{Ibz9wodD03 zl?D*C=gS%y+jF&upSyoijmgncT!EZY^3M#{XzwY82-3%|;Ysl0$ooD^+EQNtP+=JQ zNvq6^@*>w1Ne~IcXh_U-;}HjIQFdC_g!pX zS8ZfO0~yo=lW-+acAA^4{pw_x)8cQg`rv$jw3KS)(HlRc4QMU>8$Vlf%6N>^H`Z@pEj0PMy?TDmg^I>n?jvR|EMjXbA0?* z5`od%GB!WnQLS+eM_@&pGRGxLMkeF<7EV`oe1?hCi*O#H=c1tlA3AH;;R&dz9SP3K zHWG;Gn_H)gEJH zv`q5Jtj1s0F2)@ji>GmK`lZa-z>swMv$(;`yW-ah;CAXsCsk8689(;7Vom#lE@**w z{;UBD2VcSRGk<(fnERsrk85vK#G!+v~ z-X4cRgzC?fF{htpRXtYx?)Md&-6QqYn*V9@;`6g)EoJ=Ehdj2|kyC5x=^!LQb+K21)~h^8T6DdI?F;Kxk-gN)lhVpR zyD5~#o58l3F69%z@DdSr*akmW9O|6*!01;v**iH3H_Tkm+~bVhGRWeNMkHCHXyLNz z9FqyBzWB(do~!$SsR>E3n@?b9>@6Ul|Ew;_NbuZUA??hlikl!^7CkmvIUNruX)eq% zw4-mg@$}Sq^fIL}|5VW9gHh93usznG`6;b#c78*w_ewr->y(wM_ofninTN-|i{JO%g%z4#_xNGU+h9=B&resHq;{wmnWa z>6~^B$he!|Lm12&pZhk}W&A!+ktFNT9F#mSc{cQzbf&sxsqcyw=O~y-%hkq`z1Xd~&|EohfV()K=A**H zK>G@90?OfSl;r#JQd3R76qi{#eGaf*L zC8ecsD6nH4>Iw`i1NhlK+p7R&Z~xrc2>4yYh*s~P^n58ze(chQ%>Y2D#k{>^tdl|Chx}xEL1{G zB-PnjT7VSx2ZB8(zri2XmstA%AGQQ+j=Y%-ch;B`iTDV6n#?A!y(L5EN_XuAJY=dl zQScR~$6FbZqqzG4k;=sePYJc$#tQ+R24#J{e_J|iY(-8Ih=!Jvl9_3ZLoGcJC1-Ss z0fj8UaVO}Mb&bg{rb-q?WJJeB(^CmsdzXYomL+=#ca&Q2duLzs{+Y$gl{fs#&fB+x zOvTC@v6m@Zl*`K;JiI!y9Ux?4qa5UeE8aZWAFK3;W&pkc34x=dDTWe2uh&CsJQ zFaNw_HNsMv*hcJ#r8G_9%;XY*m5~vN%ziw)yO-f`zthd)J`6Q<&dT*= z<)`;CvGq-aYS#`ms|tnrL1!s9a4q(8ocAHs z)`qn``@0S%)QgCC*fR?dH}sd(mS&&g=e3<;d=3jkZqPYz@0w11s}}fM3xIfJZ&zae zYM5`-m{olWP_c;M3h`;2i^^ZFi?yGL{@fyNE=_)=mIrarGy34ynt&OepO5I7$~$KV zkBvhbK5vOILwP!NW53yv#IQ(SX7ib$sIxPlEK>KdmH&BSnPrRUM`>Yp+{x1AQZ;Vt zjFQ{Fi6>uY9TEcePSb8(s9jw`S%j`9cx`;%DfJ82v%s4&Xvt8YKjf@iIprtgwI{&U z3c|dR`J~h8$T9=AC-O4cRUaSxx%l*F^;+G3E73t|_jpEsDA^iST2@%<8%NVAtJU7A z_HKJc<;h4^@^I+D&po>3ip~0%?-h!!YRPhBReS5Ju!>qAPNjtr=F8c+c*1Wl1#wv0 z;#HJT`|H9vN{v5lqdzeo5pKy^&`8V@O%|lhbX5`BSCo*)km;Vknpy zs=`_wSR@l27ujzHdh((Fvxw8i2&JV* z@M`>7k~7(ab*yl!Cdgq7S+66Q2e$(P>ES~5s zhPT3VScdmAPna z5xoL^MN_JR1%}c!-co1#NQ&ar<+I&Jy9a-9sIYWD!zt zkC?{8g@mSJ4lA45a@T+?1*QGS!bVxg=>oj9YmaQoT-~c0qx?AuB@d>zyOza?I}pz! zXx$cbB_n<VY z{%%4wK@wMT$a)=a3eUHlOk?{qL)-Dq?SvBy4$#8NzxLP_@yjR%rJetK9c8!}Z27OQ zjB>l6$HSV9@nVu=(M6R&f~5I=nX7&!VRz^GA)@~biBNqT;-rA0QhmYhWlOAsqAH1? zchQb$?c`wo3dLC@vo1TxgiQP`Y)Rq@A@`^McN{e}4kf_*W6HnFeE1w4+&{x_BPyw%b;+xp-C^5W7K3l?x$l&c$3?9a*x z=5UGl%k5FILQJ9tBp1l$m~oibPHc*4cxZs_JKme}nE!MbCE6Ms*1HP3Hv0QJDmP2i zT;=x21Y!U)0$%K1mxE&m*=!cJPVnj^6R* zvOOHCM(^~Obpk2=;Lk0Pb3HI41kMiUp*1LN_!|cyncwpH7G!1V%XkY9(&(%D#?bJ; zj_?~nw?#eQic6MaQ2Ua^K~38Bh*)Z}7jz%CHr7M1cMGpEy)GA(6M{nK81Cn_mfRNC zH!YOE_kWp^Zs|Hlw7^$F)1i*e^W?p%(HFOjMemB#zI%CEo+q0~E%iYIugaTchpRnd=>w}`v?lC025s$@;agJJ2@5th!ip%5^Lu8bY6bPaHAi1!Zg-~i=5bUn3}q@ z0d3Zv`D=w66R2>{Nll)@?ixMU0|lS_wc}trn0V{A`!^dZ`%&Ahy|={|t3+g%fqPt4|*MVc0W`5CDFRyJ}AR$X=$6}1&d@%%RfV} z=Za#@|IVXCpns_@Bw6BY_Ovg109^aRz_X^w~=7OFK6V=JBtCQFy#Jz zMjpW4-9#a%_(of6l zDW_J(Dvaifao;Ap%JL$-j4TI#-Ec2efle2=+8@Tpa6iB_78C~dAyaYtm*^r+JHLv| zf&O6&1B@R;q)?W>28qA{+U-`4hqhrNA_$3HIezm+MMt;Vh#_TCH>3NFj8CI6k*v zf9HZfBsTvsgQ^Xi(tk#-2xCPk4jAvW#0(IDzBVIZmY+sPbR1E@$5MudlJbX!v7hf` zUcI*6a1Ha*ZeL-xcOg=Q0$$LsVFG-3C&LeT;`%V@Yu`C7}qb*L0u z4#24qF_DR+KwJfX`ip+Vvew-1-Fcb8t?fx?W;Z4Oqd0fX76GpPB{kWTk z-`0VV6X?>{!XOqoz<|jsAd_6z@Kqpa6)Sf87;uVUw~AxAw_51qt>(`{djGPX`fd5* z8gpw%!cDA=Lih` zv4y{n#VRT@=xdlUP<&oiz*rnp6zWZM9*o1rQ@9*ZaT!bVS%w8?O+t4Ja$B2%+hm1$ zs4(O2Nz05bEYjKH2m6%g#@U>==Z+-#5f2^7sa?0_U2)Uu{smEQFv|SR+@@o4MEPJX zk*Lh-oo4gz?n!>Yc6lJRr_lIwM{c@)2QgDA56hid2Q*|sPDk$+dWYu4bfjXE06wn| z8kYkJ9yG20HCTS#d=~jQ4DqFW*gSjNa8jx=>?1Px|D8%wx$(_;Jv1lG4_7|G{NZQL@2|=Kr;!@KK-tcQu-1mkGEO{oPAcuHeo8fhGL^yVqrU zW#p36$O(GoKurZ?O}|$*VoauvE|5c1TEFJ~6b5WgYZHE3^ouq;?gw zX>0q-$p14(`Tw1d4m;sNj;t6?fe{ont9@mUwNl?1F~!o(MR+e?@|Ov zn}t2z=DCQYz&TA%Jy66kzCirG$m!hl@e`|eL#$a^9lieKuY$d+(Y)TRg`H0R^Wl}c zQ`oYXOR}Qr_82}t6 zd38B8z05^qs7c{4QJ8kl*E9@*x&+pHveA(_=FSr3cJ9gdc=>P~@xr8>{qlHzKGO2; zX$s*rV$^BBYQHKwY3E%>F>rDj)PnxA&;V)N29bgXD?LuV6GQ!4U7<^_8JqZgZm)M| zAOLaH1EJ2$9#1@aK__zRT=_zlXS>38MFSf$@b%ZL%-0*5~n= z@!9CfB=G#y&zteA5#04e`icArQL8K8)188FFyK&i8oEu~aONG%ka2xp30a>CTC%!& zp?DE8QoMQ9pW0|%doFz~^=~4lH^6tgG*amvP>9Sv7uiuyGN!eq6Uuoa$Eky<1djp+m9!MaTBZH*?(M4K=vDMe^{5gui?=wp^ z=BE+`X48xHKv(tOxwWcKgBC;XXS-0bm#kNxJ1h-2UYt2A=XsBU{ilNxT*1I3uI!Z? z0zUu!AfE|zD1iV$0+`_lWf~ADDMQ~o9R`) zHeME_r<~1knG=}xX>({Um?bCZbxcIR>jf$Is%fk>=u)C5*%a0YiNw(7yHQtt%DMw< z=x&2{Aar4^TR1`TMAm@Z)NHO)_->FS3wO^b?^U}c%);v>9_jn(eFw(nQ?KwajnKFN zr!@_$g`k#!C+?!`483f4CYzR_iv%M-FmO z!SUL-a)Z)1<^@sQ(qDXoyu?!M#XhIO`8TiKQEYCPr?EeQjLvuH>g`1~1ZLyJ_90_4 z$w5VKnMhEC%`mUa=G}b0_2Ce<=^wfWY}A)19?~ykB#b`EBtcJtcn1AJMj}~>XK=#L z*SKqI#+S!;n)j$H4fjE>t~@b$GzvtLrDcifuju5RzmY6)gZ%b54ABG%o@#QWxQ!F0 z=+}Kv6H0Ek2W7^xH?QWC)z6+UEhZa7u~YwvFZD71W_Dkp_lhhg%9K%nAMpscq-`l? zNAbeZ+AT1o!{oTtI$um!wK7y&@Fc=_4S#j}0ff`_D(RsscgA4J9Ga1Q0NHP`63Pl1 zn|fApBos+=y1LT1b4xkd^m!Q}t%|d|2M37^)U-7|99L^`WFZ>*ZbSTU0C~$nm-scA zZR5CgbOnPdj@dGEqZyX`S*f*XJd-)IAGgreR=y4K-~W2CT{Xv<0^*2 zKckZgr)@i1=-zVxOK9_%MC@^PJvHbq5ZXj(d#@HM04eLd_l-xNpC6uX&EEmsy&_dp z_3qC4!-Va9E7}+G~9}Mvb+D8(5^;*sC-N(Ed-0;&E zEGD9z?v5@fND19OGMS06dRQocSWrG1l24L}1?{!kFPnaB5P!&QCe2A0S32sI_?bM`}6d^ZL6C4QV`m+SKLA$f(U3I4o$ww&V(ep3dBxSbQqz z0LA1|9BiM1G@hUPU(09?%b#5rhY*xEW_OF_j3&sT=iqt&W6_kQKxYBa6KQ#5Et3Dc zOm0UgMUAg9Nue9-ZvF%(vMq2>A}A>QBu7az$Vxz`N0+?o4)9zd7|*1&F`%^*vD~>W zb@sHMQ1N&iba^6>RBU(U;dBt~u+D5m%JH0YquOjElA{mu1JFKZ-#@Ls_zPLlQw^r* zr=570jf4qYG(j>CBhXSn$(0m;)wl6xtKXzy3(l@{>1lP9%m zdO}Y)tNWPWgTtPIAl6TQibb%ovX`ha+Pw7))Gh=cVK!_%p$mP;DrR!39Am}9)a{hm zePM1HWU>Z9O!4=*@{i zMqM8BSD{ z$(N&l1zdyPmHPx2-Q%|#Zl>QD6x@%{&6*C5H zpC|+1K4h@80Xr@%Bzq4?!HQPlWo~C(K6~0QAoiaYNHO`q;nfy9#&_&RnF4;SOoQmg z81)N~x}BS^p4WqW0W;@UaCP7bvAtP*7wurZ^gZLX2bRFAJ=-3USHT9Dy5w}OjI^s4 zq>wZ&^cKelI;S&6fCy=WM9Nj0H}rZ|6*rJPXjF8Xmo%y?Nl)!%KUeS6INIcP&8Lb~ zCwfJsp@?I|X@YTvwGtka&uq_1??r46y5tfU^BO>MeF>>WctB`sCtRm=;G z4(sHx)nE-^Sz|HEi{CsUFDUikMWUrOyL?GwXR-h=Fi!UDVSr%N;qw0Ww>klS={xo< z-57CH8#SwX=Ae@OfK)OARLdpHz~-{}ipJjQ6tBsPJ4)7EiI$Tbf6&c=_gS)&TO+LT z7FxrX$GO30%V`mRt_coHmf+TWgM~2oeWQW9i~=dfx-NEtrQcVnynMSBpC8oNq4)eL zu`ME1{vH%Lg6X(E1H0}RDPkXyXdu|+2F>4Eu9@y9@|F^<;4%jlRd^1BUkHL~I}?L! z0`rkD2HRUVr<^iIjA3hZ65}rNC@v!pHb4`@8C)oRI1--zVke6ZWsb9)b7-%R1%_JU z=3hC_(m4uZ(qi}ciCep;PWbR$bt+)nR=PZKd0c)fI@%HI-M1Y`*3dZbxDk?kTwxG& z;{55W^RI~-^FVxQ=aOa0l}Rd&7lUq_1on1+w`~0;7xN~CP8*ZoXT#bP3Ja+o5s{8a zNJ}>_>U8xC|78WgJ`_QSI_jEQUPk^&-V+^s5t!Uf8PBxl+iK?~iZ*(CEk0x%PJI`7}{!jz>vze8$|_s1w<+l|zS zWS20FVk0U-A%Fk={iw%YJ)Z;lX7%(4j#>_4h~r}Irep+Nh@Gw!nV#1zyf1G|U$+^Y z!j%H5lK%8NS27G&SBy&lSgOhySV5-taF}M7P#Gm08K(dCl7U6o=iQ7>FSO5__psOb zw=?Q@=d56!=o7Z;&$G#Fhp+p!lPfuqz>Q3+0>5ZTP51IU&-yt?kmEEg!KA?mq6&;0 z&xC!WDfFwGHOPQ`CdHiqX!+p8~B;)TO_ba|q((BM#q5 zvuDsTV1G#+nL8~75QSx81hh9Evu?clbDng`oa&y;(g+YAe226Yid3IOCJ zR$3m}ZDtg_g3v#}ej0%=V8?ZjO8xq1Pbc zrrtgrZ~0QYbY)L+LqQ(TwbJCnq?0b_&f=GQTRE5(_i0;sb5dT$#snsBl@7PdhjsUs z0Fo%B2V(a$(s*pC+~px2{vq$`C)lQWxjr|!)4~!&x5+kEc2w$6aaHa(IQA&X02$29 zePp9{b?N-^sNK>=?ti6E=#-E*Bm{wuptg)Ro%~K=aBBOmX+oElTS>@yj}ehNt#+zg z_G~1UotK8)j2P&${?NO@bjWeWwPF48PGmxvm5~xt?DM;-;K)C6GxI;Mdz^YfU3{j< zFUpTutZzlelxGc*nS}gFuQ}{t{WA&uQL;a@mPoui{LFrlNbTWg5%$BvXXvpY1_sjI zt-RYZ0W`fPvsc@b^)ZSq&t=`x7@R%UyHl%XU|KAQpieA(v_Qw852tRp)W<7exoG}U zb0(J9e6nG-eAw>Z-a3iIjXLHLyiK9~+@8loD13+>ILQZ#dLc5g85CIW>Sz+!gxnB9OcM;gsko}wu=`%y2r>+3Un}7Mpk36_b zS^ncXRgrwN$DeC6eUbg0oPE#-8sA|P7JVdd*_?AcY+U(I#s{kmJ-Y`J3N0fj8HG*I zEXH2uW#Y@*^4beoMaFJ&|GbYpJv*h*6wbq#5Nv~T^t*LeAqk*{m|*Jljw0WC7 z;ZerBI)E+~ET)1av`&7XIeV(IpiXf9lq&^Bu^&KHDmpZ`lU^^%3z1M;*)vyk!L}DLw^%A&QXL1MUoOIy4#qmq+W8HH-Ju@TnATwoaow;6)ly-`s%ggf;5m%5< znB%XJHG!BFtzsrV&CTfyL#ybRcR9a%hnW#)bS9^j*O3iHBZ;KYeR?eD0`v8I&Y~YB z(P5MqA_cys+>K4|=}}R;LfiYYg~RG}$$X$jWIpsk6fqgoJJl4@ZW6kzDMp6|`orm? zGzsei_;ey@gXB^JIwGBp#U?XCKF%vwT~90Crpe!xrPp#?$ZZ8e$!4<#BDmlzHvs}Njo3$WewnD_Th#~8{{M`Hr~WT$~> zzh;78&7EPGHG-Z6Rc=?_h=E2>RxQ1oIX~Z+SBTcn5tOiBOl%pXVDkH@IUfDVLHLiG#aUdqD+ES&5 zxK5yL{C{cz*0P8RP@&?ge_|>>x1lq3KP_k$loxcSoGsp<`RyyDkMNdSRm1ct$$o($ zP$AEHpeH?j&Ne^TH`6sm5z-})Z51jw$co@))(k4D{6k4K5D%OA4TfMxaC2s<+cphs zM^Lo$T!@QO^^e3)HLvD+84fu&s>((c@`{%^H?V48C_2riqVV+PY zLG?#ezx{`b>n94p212p-p;s7)ar$^kJ9m-ALD<#CK3Ct z8c;NzFkUa^sxuShs*}*U-0MImrc5+Q0)a+0MH=o8rqgM3#e48;Uhe4iZt+yK<<9y1 z(nAA>Bn<(i)s7B}^7r^f>iVVJOaXOIxW<`#AMl_)C3rB1{UNa0ll~24^zfuYi+g(q zX2crEEG%4k{F9PrV64FpSD=RbUy?mW`6@X%f>;M@;AXFEJiLe5QZl=yAk+v8|r~Pa;yel^s0J zuP>yqKKIoUcTRr2KCx|XTHD#S=Q4AK=b6fp1O(4SO{USxC%pZ(CO z)8fw)sZ^)+OL<=Kf>!-gv|IU87AxZP+vy+a)KE0$ze5Wx#ytYwIU+I;f%Th{<1F!% zb|o&r8+}ESW+$cA6MRi%?1?>%obfeYuTj}epH`s%PHZ3oR;9tKnB(N~6BHCUa%e&tTs93>WBP$! zndYny!DU_>;?F;}V`n(V^sa=&D1;k#n~Y9rvw<=9X2ZJiWaFgF`t4co{K#|{FNu$h z;petTf}9nW2{wp(bGwpFmu}_OZoyZ=?c5*c&x>vIG=;q(arzE3d#(6@z>kAZ+DjdV zXxke?4Hf)|_W6!)}FB_y=; ze`qEf2p?dU{yxW$TPmy?-B_y)FRf-uZ03^(Dg`cnG6>s@H|D^ZukpO2b5E==?u|Wh`v3-4RT-_*nG(x+I+&Pn)f~Y1^LfS9vOd;-f6Is~VFu{bu9~UPZZ{S9$yO+%qmCI^GYx z-lX0I>}opeU08Q`H*>Vij#B0h-&LJzP8zG*+WF((lnlnHae3Bt>~25~#sf7kd3iKd z;!|ya@ftOrbGFPYNtap+8>HE?LA2xKs&v5a%zv^hLr(=BuMPmmy z?%u&y9K^>Htb6(HcPmFe7`@q_pI~{coUR~rCQ4OikDw+9n|c<7_u zwdjw!ZNr%Y#>wQSh7(oH4(IDWJW*ReFYah8Q@usa(Q{*Zw_R#_L7KI24D)Ew~Z z{2m=z)8b@Sm*9p>b3NVRSm9U1j))tRjlv2kmn7HxXFFCPy(XA0i>y%R6LzA{WS)Fg zzl%ztw)cY%Qg*71XK4pX49Hvk!lxx)X)fQEBxW;|w|Ewq5FV!cOY!h4Ae*HK@F@0T z`W(J!F26ero3^JVR+KOm%>4w)lY?M@bo_I@-CB9QJ^fh5amF8&=3|q+pJY_Hf9K)k zF*54yS@#?H*WI|%mCr+L8?V;~v4g`Mv@ojA+emL0Z>PsNmWI6j7A=9en^9%g zlw@PC5zhnsAFJ5LS5UU*-$)`tP1G+Y4waVi?|V@~V$#Sv z4CGb5x|el@ZN(IB=AvU?h+VV~NPAsfm~ILk`I6de)IJniF*v>4RMT#)vU>C0i%H^X zhLtS%^TXX)SdM;;dDDyq`C10CUk-1#IY(?ZR*SotYIX`g?9L_)j^y%#Unc|G1kI6z zeLzBF*ehX0x*1G9WZU63)>-?nPcIhYu6xDjCaD7F@F9inTb+pYfR|tmgm-I>3x$U; z)@F6%oecKdv{k^#j0yS_KPS4^fuCsQqglim$)nR5hVck?_f_(?d}Df`mO=Ya85{L| z0Vv^KmRXGP<9e7b({ZRsy~E6TuSw;1pR+Z};`!S)Dmm$AW2R9M(m1`dM8&`<@2% z*Z8>7jn4ym8>%eHRVQzr z|I%^c!Rf}}{|62v{KjBpGI)7h$DI1p;*ae3fWYnLxWL*qPop#b_U&2bn4FDDbA^gY z0I%{8T-Db`A6h1o#;mfKl<8aX=Nyob-VANJZmB%U)`L%K0kT|tl(^dBGbe<)PQ9}> z5IkmFEzp>-jru>;F6y8EO|3rsH@h1a``?uB|7qb;8?txtz8E?HFMLR1FIdOnjwwEJ z4!Ln7V!A!lqZ*vI9O5-n%-Yf0V-K-PFYx&NZ%UXK6%X&9#Pt>Czf@D0WdG%#`oAd5 z@c8l1LuHbId*qIZuP`3#1c0a3WEQl`Tv+*UQ)UhlE>-~bToucfJJwr|iXv*F9Iy9p zVgL8p@v8Vwc7CXTfWde(MuB0YA!B_xIQ1T*q<^8{j@m5@nWfDpM@1r(o5ko6jcv{F zA$S&y!%h!TD>Gw|`@0+zTo7u!Jz+XM#`@y%V(Yt_61&R4Ki&1=YZ_Ws#s0GI`Z2Z| za zw&wearPsH|XOMmml+n2!EgR|#486^Pa{)N(*n4~MqFoI8g=e^3+~)l%;uk{1$f1<+!K6JQM)x4%fI-7z1}SdZ8|{1LwAr343#5pM0GrDd(f zJ15>fx(Hmo zeF+&OQkBBv>o;i5D^BA6NNl=mEDV|qa9af*DYMjOh0TWIqxaq}SzoQS+Dmh&?cK}d zjSTE*LN#n@Mib<>HUUUIStAzcZwcHQNoau(hi{DewnF0l?W#Z5NhbaBX0G6yvmp1xXKq$oW^ zXofSh+yK}!wkVdBTi<}sAR3ir2zEb`%BbwgADtelR{)@=@-=Y{=JbR5o0Az8?%LUE zx?~KZVSFcmq7t;378pNp@Ppc1v91W&qPZL6r?-OwBg!?vVg&VhMq-i=OT zur|lHQm-+_nY`lq6}#FKoZe3)Qv4Kp0glA6?yZS%(HCaA>cjd#p{^ZeCFj$5JFA5z z0-)F0u$-{n#|v@(_O!vLi=2YZ{b3HPg*c+@mpFw-+7gDpT`nFrd%~3pZq+1AZ+2e- z$Ux4d)#HzG%x&7sZd~XQw;A4i4 zJ+@wD!(=L1K=Pd@=)gCF;PEogyJqEw)e?e0X3voY-Y#SgO1(SNW4s3oYxIa_IZdd! z&>#?9$uM@XixfZ1$r7tpN^oQ#3Ny;@(Yfb7dUyDFa}UIT9>NIWGLJ8b+sw-QhSVrY zJ5$h4qh7J5$Ys>W?I68It#-wJpHgXgP_uW(-wcXm#5SV*I3y{#0@VR8dkp ziMWe;@rmOac(~;&nRImuo6lf!dtXzSZ@pj1Og6MNIdj3U7t0%@b&IW2_{3MnjEBi5{TO1JWPgms{)QWM5JN>jF$a#VXyF$l$R2&Opl#in-S%{onGMF2(78eD zll%H3#u{`6!_XVH2d!?rM8%2msSMk|O`bcUyfYcDCiu{S0WV0GZN>(J3TpYo%sr|c(YiN0U zC1)a;0;_7K%E*Ue=m5l=lQ&w$ee^i?93{NRgjXd8UD0oh5x?})oU6;VmfGh+t0SrL zCqw8>BIL8F^Yz<#s0G>P6;s(8F!OVzv)QZ0Xrd;9-FI}c=lPx!{n1=uw4H&EA3b0x z-)nJMmNhJ4*<%@r8f&?oq2bpJ?L1LmnWKBGzqHdJ!VrBXCh2~~)5NOS@5oqDUPQ{i zoz$vZN0t0oF}`^{QVW+VHB?QI3MjUfAI+O-;aJlQKX%5_=wD$el3K-O)xC}YZgfSx zfQ&&$`EM7?Bwj$-xreJ87*>TL&{}vs$6VZR*6O!Urc^jv^eJhd8gjKteEM|*@3Bn9 ztZV{sR-8JuH@Q-9V!5U6+)iP<+@7s zqC10|DQ^-)G{mBSbZ=*@Wwjpf|7sVT4g+QG%S6D0RfR!}@98d9g(-OhSC62VAQ=NV zaN4K}nQ0IyN5XWZ?_o2vd4o~uY!0;MmvlOHtE}C*B&HeT@7Wj3V~N7(5k(!kEPRdZ zWIH(+(I;B$?Qj5-$|MSX>?0@obo`0c&HTyT$Cj4-RC ztnD2h02H3-QA9<>s_+fVxqdPi3X7Erud4&i6_(!#$%8@&9Y5u)wbkBgy8a#46<67^ zI#E3klzFsjHTqJfwewF`Y<4~ldxr}N+fvn15d+|D{o52ywYH&9=V;36R z#(!jF@(7x-WdQs!-+jXhxk-u2NT;5R8ic0fe&$ibh0KR^-j!v;@z`~=PMtt6JopND z>M^J*!PN>8kFQX4C5xZ#AuyO@$_e>p5VRse;)N!AckMDOnsp9}x(0^yX?C|8QJ5rK zYmmJtg)}8=e44O+2)Zup`(_r$70^ows*hzFCwGUHhjsCLuBl zObpr(Z?O9_{uoGkF!8L>5-?$1rb&I|U-JF(<1YY@_+Bo?Tv!F_8Gfp1q5OVnJ1L8( z<>ouDZ_DF{p6Fe8+p)kG7B|*Vd6qJ_;}wpVGyP#Eqw2?Phej;N+sX%{rG5Mcw%N#2 z)kJ?(90pS{7T|e%C|CE^$`d0c&H11hM#`%T3y=~ubJcfeyHsbk#EqU0)jLJTL})8C z+TK>d{+B_*7f1V@n^pdqlKDTk09sh7w4Zv#5;`ZVsB6yyH(O?mR?7|Cy$2R915UFG ziGBhLf!6Bi&*lP37hH^1>n(!ZFCN5LLXYv(X!P|J&B2P8a21R>*Rk0?P1T2-0<3qJ zX`Lx}Hoy$ed;`6gae3TMH+G4NW?@NO2EbSHT z;Wn-B2@W2f!sP#Ok}^q4Qu(wr7M4JoZ^P+78d97_Ku`vuz2@wu_}y=ZOX<^*RXN3R z-I!!64fpna6lmse>{G8g()^*;#A#OSD~P_KiB(FQ(M%Rg(GR(x{fhrKzPh$pk6GPK z6GOp2pMGb2d5QjL0Zp%Y>NLoI!CK^^*8`-H)Kq!eoQVr#%bCzVHUJRSXU&H`sXKx_Z-ocLvMRo`&V?>oj-;UYJ}=?e;3 z$5P5cwK|)7{;riXEl7_|O77nxg=|(q_g(20n>@mPbpJXs#Y=?fuuH`*2 zg>Uhb3oL)ie!@on;Gi6@WdNpT&TnYGZ+A*sVv2n%YWp)^C%%5p;x4YO8zA}C3hE{4HIP}U)#n%ihb(!DSJKoG?LQLwd+9Rdl zw4*jLA2UChu+5~wUl_Rsc;e^rYY3J{CfGDM(DMGy_PDZQ-gQcQ23!n_pe%nq$UbX$ zHwMjSjjSd3m@C$0b~y8${)bd|!X!D23x({SDoNib1;k~xJF(hdCzF@THQZ{>D(5KR zA$W=H;Lg+T@3Vl9S!b`EaXGFt?3#h!fWTy<-8eRNA90vj!Tisz^3iLFLg(tF%Hx>V z2j%_s(c^%`af^w&V!3OFZ2S*N<@WKLU~`SL1U{N=A&}9b`W^S;I^r|{b=enaVB8&& z(iZtoQ!wc_eB!$&?z<)RsIqvbd6Qo4JwFqbe+Tc$24EWE|i|7W9rSk1ZG=!K`obJ)xF z6ag-;KGys6&lw5}#oqq^?>E^idjoU1ZIB-8QT=lrXT|o%qxB65p*;G3fp9|q1;Xu6 z{uc=Me@|$9!^E>%#<_AOzX_+x7L(MR1#3#W(mR4f|L8W0i4?2w0Zbcr8YQdi>tgVI zK-&?RI$7mJG%Un7Evnmk3TRS*k(@^BAmU8ehc!F6GY^_ME+9JAL!I$`k3(1AGyJ~z z*H$QUfww&#$f_Vv5Z=%1U$n%hJKc`wK`mu?SE2bsv$THuSnB*>yjx=`pMtpv%z|NJ zUTc49B;e1;71IJ~RzUEZYOW**_R~szmI5vE(qBzkc-d$$EHqLdcp;oNd@agywq1AR z%=vl}s$A&CPFVID%{kbdX~){-Qo)3W4;KQc*Z^1UzY!*g>3CE%>Y44l7)zI!n@FT1 zLr^vm%1q;SNob!iU5`xu)!)vN6M92Ty$mC!{X5ZhO4bUD?;~?DHB}W=pJUgO>U%3jO#8-j*U(Nw~c1_a?8z2bw{ zOEs!mWudjj$qZ*i`P-Qz89Qd|BM>>an>@7&R-;E@SgI164OTBJ`}b28s8%++Owu*;G8`9C zNscx7<1r$MVi|)7!-v-ZCh&x?BpK*(_zLl-m|BTq$)mAFgwaiZJdmwdAYO`AfAfQM zlfQU3k*r}5Yh~SQxE(HpkP~}(A)uGQ{PGO=SpG6jYTin1(yOSt(LBx&riQ4 z7+#G0HDETLkk?^1tC#4ftJFT!ml@)c7%gI=6QhnPx|i#AG25UgWEn^YDk>D)aSv}+ zr-J;n21UGx7uP*GffT3H<;U6!@5+2R8Fn`VH`CX)W3s4ChYjvjk~A}1_OQnU;jVUs zKjwXc0K0`Otm`tSyG$q@xwv;3v>$4H)s7<#56%p)E9sXLfgf)inMSV-j})TAoi9uV z9t^aAaB`A_t*eqMlqGzKzv+4L38h4T{m^QPoac{>=|~2Xbjgw47oKl@Ih8iuSx z=TTOjp9i;Q9bWTAiq7iMzn(CwWR=yi#_Epdz7=B8{-9D=eibdL_yeBUQG8T9fuO1R z!8Y8`%0#-4opjl;(j6T+46HhRN3I@%@|})TFSX$kG%F+}MLfRnC(>q%__US~z$zMU zCmJ#6w{7+qNtw`8JYn@T)jR?fV;SLgNVv(MdxboX^uM(LTx5qE&X}0I+ybE&U@hOFE|4#r3lO6*`YQWLqW$F*RNy(4GIsk2g(-Ug7upv&Ny}WaL;f>*z0{(2@Ks5)u;F z*nF&zO80z1FUFWwe&1uFlLj^_w-KXk7_XJ(B=48QM=(shGBLZf)`3~RU^M3fZr zTo@+nYH`0HenvcpFuzAr2f75V=X{BjdAql>od*8pRbrmI&=xE*@ZZ#mb^4*s#mW>$ z84*X^t@|Vmd#Tv%7(2^1y9=QEm57B|xr2l4otT#Qt#1Hl&k zF^T-j;D5M2%c4~U@!*=ZoQ=F?aoz@1=7#*2>$Q(l5c41eq#J!f?M>zP-21B!u zQfYG&H+DQ`9ZIUw$qS9mqFX)Py;4fN9Zf)}2oIWJn9{blKG#VVhQ2?L!Hr)hD&FRql+Bt&SuDbe2yYx7~5#b+(v}Mbk`y zE2ARlslmr(3tycukY?WPwhe$XZcOhUkBKYi8Gt6DSH16&Gr{bXX zN)G!kL?P2haBhZ{3>JjXes<+DpG`z z^3xJeWD3|Z%iDARlfsGX90cR6f#4j^;j-gZ#4v))&IBZ|n9LAT33pWeLcQxB)`4b( z&!*1-Ry$mR*!!eLz<7ZCX1SB!Ul9`p1zsMq!@n@MA(_na?VUj{-~YfC!(Pz;-v`Y9 zi<&62910hr2COK(tzgQ*y|PxDDd?yt$DqGbSwmWdTt_dCxF(m&sLQ5kH%#F0k@pn0 z5cJzF+G*o27Fmm|m0vgF53?K8LV4Ref7u28{QrxWxh|l<-mNq1cza6(UI)F|5<y zb|7a7qPG>iG=@nBYix@)xI7wHcsgP|3sS>|{4n?eF`r=EeBWTcHqVOv?(CupLvt$h z7EB~O+P6>*eY064m%4bXKuPQLS}5h1l8BFs3~AI&)lk&m<&{2D8xvi-kj1bxuvL1% z)=M(5o})kbGsFUz1qQZ8mHsraD7ZPf4IC5N8Zg1dBl3OIUEvC$t*D%+uRe5~N}^4f z2E`ukjFL&CUY;%13wCXOk65>1X*oG-fn^R*yn(%P*Xt za&HPAv1sP@T+D540Tl*7lLcRH(%CNKTEj;C)Cp1CiPi2qrAlK*+39JlL_Fl)UrneC zkL*!9qNCGnRfL)C3AKNirW!QCy=z<2M83)#qhWz!_}a6o$yrPvNcjANKj*W7gV{6h znH^y5z~Dr4iLlZ1>1AiK{^&;CHHziZ^xSN&skf<(UW3XWoe%XRh0yM2!@aP(7mGY( zZn)|2`G;%a@*#rtn1D$gHc&+g2P#bM$5_l>?(3D9$YTW4ufPKPGHo9JJ7oB(^OS|U zJ7@;-wYRJCGK0TmHAl-lFKq`erFL^Us#6UQbMkBUITeUtH_)$kR4~>0l`aa2TlZxW z2yG|L$fs@fP}!-MXU{H>@3jxeOtEU?e*g^0N40rajOAdo9$P)-_^2DZCJf< z;pKn_zh?7bxPdEnmV@O)!mCMG4OEkKR&%;X$HoBJDzRq|-E@l%C21#rb_=Ax4cR-8 zKcNy0&S@OTxh!nPUTUJvvfnF*pxqvWx4F`lX{XpjwD8;FHtn8^L7YQ+hb(KMX3GNp zxxp;XM4hIGWq7kC3nZ*Nx`(rDO87=qr?3?lnm+Yg_KFi36!+@=*eyFSCfs)}fDCT7 zV-ExRb9)j^fvDRIwGlsL0H1_3VeuerIoFW^QloZYd@@UM<-L<8lW&W;cUPJ4LO!{1;dN1toA9oz#4_22uFI*MsyWpW(4I22w=2&(+(mC$6iLAF@E z=DoU&!Eq9#@^7Iy)==2!adiY+8(AZi=nTe+aGI}nOM;JOS4zAvhE@>1ScG0mhGMqWBh{HeLL{{&w9(xFz*PS;)q`+<2-*H^grthMq#s zV6=Be`9{;a+K^iin^Ju#4$CAH6UDe1d^&c;(xRF@6@H2p)} z%A87lw1VtO8ld&zR)s}Dx-Rq7M?!p$;krqS&kj1P7Aw_T$?PQC2_dfb7)ATarN6J* zft&2LDimt=W04ci$*Me?pTYQ0y&^-k8vu0pmOs||@TGnXvCYk1(kZshP*(tayFEA|Fqc0BS#3z8BIgYHeI9bUCw%v`je zdN+c|zLe6T%G|a(kD(Mxw)X<<|fV%LhyZ3${BGBRl&VDNe(e(D_fH zj(W}rm~fsoNMOqB>lFSRY8<1XCfbz8o0zRG(a=z@%R8@i{AXhdRO=8!Il(XKrC|o0 zn!O_eMRN@}o$?ohGl&{foyk8e3_h_^O3Oz8?l`xUb&&g+RtL#pSSLWEY^odX%EGHKY?hamwdR=jJ#3#rK5*#*k zWcR(^KRzY;$sKc+V#FRB&9;rZ`g)<$BexlRo%j>sHIerzi(JEd_-7t0+`^ExfVZE( zbVw*b?mo_j@Zpg+_2$_40BS-If)vee+;Hp_m?^dx6M}d(4RAE z=+=jU{|K_PsGM_s0go%|b?z#CN2fMu2afPFmB_y7-HdbVATw^{O zXEilsxa~T-4?$+jS9~=4yc*2=BXTLppIH&?=E8pah*r0t*IELE>`|bu(b%q|dm+x~ z*}7b7%;eApaLQ9TJ8>Y+11Q*>U&5p7zog5!tCLq`NiC(?1Q?iAbP7da^nT1&Ly=h+ zKl^mN47dkcD7PSnpe`;%x3P^u)cO+>r9NueuG!E0HeEL69#Ii@M? z@@G??;ZPVW8u7j}rg+Uoi9oZse1Sh6b4i|KRdBvuOVyk_3o5A8f-1tX7_4deNo~Nm z75(DLN5#G0O184&05RbcrFYzC@-V&K0&@b3kAX4juOAmh;1pJu2u*w&MC{XCf?D#G zwyVoHHrGlaP)17L**P;cvpam%*$&%OX?^sB0*i9#qtA+Sb_SZcFC=bP?G1v?HT$w> zM8kmI+Sto}!^5w3p~^|(p;P~3*SM^0DmE^BWI&S(^6*vDvS$g`d=_A&VVcCC#?x1= z>2ZRuEX>7O^ z;%>|$AA6Oe)71Ew^LBT>;~}Z<$}y+S4`iu|1YHPW;hq;c_a=YG7F|3ve|qXH;;t&# z4f?0v5S%_!*MGq1iv^n%~f`7$+zW2&VrZdl3Qe1y&n zHZ_IzTuP`(s{U+tSod|y(PYQ?W^cdkT7pn+Hj8I`=1(02O+j9mK!mWye$}qu^w=3x zje!4PcZpVU86E$$W1NBJK?2dBA1Y?tdzw+6EW(!Cf?%!_&XYJnsup}Ra9JU0oB^vB zHSnma_CY1Ia6EP*kpJq-0F)$?qQ#!haTEti&&k4ec*=^iNwjE<3q)kr(a;QT&qsKVBdY8LEw-Zyjx zZ|ebj$pHK`R2HQ|g}cn|Yh!hPA84Wy+5f=^0ccP_Bs>`WbE{)^TvZhX`EuA z+=IV4i8UAyS1JNU9iBz>$ykzg8uuj{CP zfZB46*F#u>^dRSN+>eVDX8rvZ1nH$0Y|mKnHgM(pd+w5wW2vi4 z%y+}7ASa;QNv^lcz3WH(VqQ zhV36I+;Qi46ZL^a)AccAr5z7hvFf-=+@fdwIjj3rmA5Wl$PHN55DlM5-%8*?i&y6h zO=+yXrPAVk8n)f8(p79PKTF3daq&$ZR(dJ(*sDkH2&q4AC{>P| z`8J3I3Z*zJArqz2NKl7<7uLCY)}b)*yv!0pSp)b>$br~Qdf*CdWq1D=ZBzMSD0t#K zR)s||p#P!$5=;EK3tCfDE~e8$Svrg)5^^E+$@|g7MjId;u;%7pr&7l`v2WG?{t#(b zomLx2uEbzu96~l;=`aoiiaIiBcdMAhVr%eZaousONP4X3SAGEdfoPGtmN-=qlMoa7 z^l3TFpv3humRs}h>tzSB{V~-SM&sCn7=D`%_E%M2*K`BjYXnWM)B+(sFcY( zj?lqgvjE`cs8*_-ak*A7IJsG1BdlU^u+_J>mH7vSm?uu?U3>80M9m7*uG{t8;_QKP z&m_Ll3QM^B)=(Qt#$Npx1_VaL8VD29|LicNJw<}3{h4(PTj;h>oLH~^0sS64Za&icg%co_)#~4^w6o|O?Cq!zUE1ZU9vF0S=;U*iL8C1j zBhuN;wZt$tY(Zl&@f9Nd#UhxoZf0**r(P;Ls)^jh_OP9x+Q1|4h*y0-2rIzJ)N1^m zm>Q-FVSC$Df^@Y|qdZJiV&t|#pc&fu{37fHBH5h16Lx%Y6Xy1!AP+CMJQGL2q>u!* zOO+;S|B;kSvhFORShHc2r7V`x~b(o*zB;wzt@sqJZ`A+AhVX!ZWlxjj+a~u1K1N-{< zpli2WQXls48!2|Z`czK^E5M0|)qD!eM3dbboqZy0c+OJ(16LA$_lcbUrUPZ9h#yqlaQ~ z-qy-Nk_it=?{L*78R%n=8#}%uip(k}C&$Ut+IN+4fYSax^Oi?!n~6VAk)w{qqDM_V zAs%5Ltdt;{=spy1NULxAbj&npVgJ!lX61^|6!nwDV2%1DdY*vUM*};D06Y@I=X!Qm zOP|<)21MPaN6*BaaV@L|Cz+9Vr&MTeCKj_=ZWSV)ug!tzI3P0fi*fo}dtYb^Dd{No z-^E%(tDnfSHYX+BIWwQ}6sHOv)iQT>n(#nP)d}%sf$ezZL`^kUiGf zVb3(#eACeX;ch%jlQz zKFbb9a2f7OtRpShYLX2!G1)Auti?4&qBm83ZhAhjj?geEH!k3ZXk~P#5j*eK?d($r_7 znD3E(J+0GsMoCGreEWmzh6mfpDhwSJf#pQ&v~xS5>djZ~wW93lPD4PE+R0M(iZm=Z z6l`mi=;qhWLT^%{I&l&&W)H%<2mfMVjZyTEw&>ETE#dy6>ui#U>7bnx*?n~!t;@WA z>or^yUL-bSSAvsvhfrR&5cqrjTies%bpu-n!ZxIt+BmEd-eAF-Nk<06Ie`uNbWHh0 zYkN=LBYYKA=j2aahm@tW<2v0PnPdr_4B}SkNqq>E>iv;d61xmVN8;--)Y^@?u7@R2 z_WKh0tE%?vvfJ&FLLA@EQ%33biuA)JcD=2>jv(SmP!1a^U2T1cA~Z<;((2lZMBcah zOH-xQ$LT=B$&=5A?|RED;uh)8W$LpoEA*$O{h@(;$Y86}&?sVQYhOW#vWpYpHk z=uV>|d6TGY;3L!+hx>|gitjcxf67(=7QILx1L~yudpBqnTBqvOV9ZhY^FrSwr~!P+ z0KYpiBj?LtA0~@t=I$GMF;c!nsVIJTW%4gXI;heF|C6Ye{HxAib65053|r5y#aVqI zlGI3x0xZZkIe02&@qODBlAFW_q?QC4e=IFzA1~RO>Cq2l`-taBVF5nVIH@uiA-b?0&&C?zmI*FKsEAc*YAy7oI>Zn)#gM59{?Bn33WZF=e~uC2M5u=U z`tje2Y++Q({~n9ODZA<|G9t4_m59Bk9wHUzKZEha|H&7i|QGHwg z=hdz{`$lR$Qc~k5FhqiN!Wz$SlZX--5;y;+g+#2TXL(;|gz{9N$N5#0c0cyg;b!z(6#C}*zoS9Zy>aNpA_HE>T41<5p|4Q2HnEt$c{f%Nu#qvEhl zI6M^X%+9;eZXfmqWRgSMeh!Qq2<2y1qVKmd>~8DCZ2M`Q;NXI+Cl}ed9B&6G-N!c zSJufic6fMg1WwO8pUK(oj=mj`MT%)4PuZ@~+>`S5bVOrWDo+s#awRq+qrHtQ!;mO= z=?S+7xQsibUhjUDR3!R>BQ**H`}X9t-c)WQY|6?0-$q^K(mv)f>HUFba^bohfsA^k z$}=J&+#7}m57SYdan^tkzope8DNg%J;{Gucr>#A)=bXi=(=Cw(CKAQo7j;VyE;jcG z<+oOoX9AJknTwkxlHW#`Mcp3sAm_^=Bd*DlfLLgJW`>=Va%De6I(jAr43*zEFmjztT}g0lWkAq$is`KODdcX0(j%> zMdlc&g`6u9>9dkp$O{U;$ih$EfTX|0$md@CdS|Jc#E!|Y*HgIwnt$))nk!X}xyC7( z&K<;mYf3&Bv)&FGdrwwG4J_7V@+VYiWp%#muY1N84decEb2LQ}LK52>6=1g!!tmTc zX@_jl3rS+Tj#xGyIKNK6Q|xcw)|dLY+MG0el0J8PXL$|nd-^Sgt;3h&@lk$d$ccg< z9#(mwkz*e2q7UJyy6_PgYR^$gDjf{QU7tLV$SLM47%kY>n7w|EYLU$b!$KH%gqiNq znsADhCecR?%Pj@g8Z#nkJi(|Q!JaUE03OP}wE*dA1TDg_Q%xN0u#j$&PIEB%Yi6KZ zNbhgLVPD>_RqdAIe#?QxkiuHQr^r5^1@5@(6DHP3Z^#idMSIaM1d}_lMSUJfI zv@T%EXgF_GTD`XQf%3h<#pNqE2s7`|%k9fF?{{TRD0-jakapp3jZx@kbhqYWW4q|N zdKu%PWLw|} zJ9(BZmy2dLQMrdX^;h(<%og5#o4{t8JGEWW}|6yRd!I z%JC>Kr8OmQioL=>7vmcnKRh2aawA@SlW9)+a@L0R2iqkOYZ%6hUcU@^+%hwUhwm>)Yn>fWMgAP43|&n>-eZd__#U8vQov zVB&t6Kjo#k*6tm}DSSt@^2R;D<*WqyF+)Xc_AO=d;m(>(lXuIKWHQUdhG277Aa3?x zc6AbSw_4_wpzh~sv0+s;<@Ljdf}o=#rMv(fiIJ!6Hf#E7DVm#o=`2#<$u}AgTsqgs zY$PS-0<7j;uaMJ$A%tPcR`|xd}GIo9_~?%D$9NpzP7?#qRARN5VUuzqOo*>H$FtYG?Ant zsXR%g<$cAnT%vKW7fC^_@i9^|N<{!Wq0uV03j_7*-3%4k?jnH~Ab(Y*=El3>e0?+- zkoDQB^2T!0)fZJkvb8RvAKrwuw7E$5J7p_6m|GgeIeZm?Z#y)f=>R`Z9$P*T7}$Af z7s@whI~PpmE7(=*<{K3>mNC)&X^2oD45t;X%H!Eqh^{RDyB&4peg7Z+JZK3WREztX z{U|%VD5P%RrO$+*SknCpbDC>SghdY6(+}M+=8fKG=I0ez{Vl6BD33a?ZtP_ZLR%Z6cj_UX7 zFhxih4;X}fHnV1S-5tjpKHj|||CE4q8527fNTaS;r@4Ldb28SwW`fhW?w$k<$C8Z~ zW*+XBn&DrTsRmQfS*D2c;O6D&p>H3Fq4rPbY+c9n+2#zts377yb8QtyKQA>pu;&pS z^!;>9|1%mJmx&>kDFYvf#iTe1kI}yotqZu0=Wiss7x?bRFxzIu?#uv;(1Ap1KJ^>i zBhyjg;S6%iIL|F5<#@89mP;u&R5d|YbzunQBKv5E!9&81&NZ>sXfup2V9oBmPI`Z) zHF&u~iXUHD<^A;i+@|_W|An9XTla5QblfwZp^Yr$hq@-l$(CW9nz@$h2NsXU%~ zTb^}7^+J(Qht&Grr&?HrdSr&2nz(0kc<)wbKfp18rMjPtMw8~j1OlZ~ zUNaQQ%;!?Di!-WduKk8F`<_D*@>GsHLt5WlliHH#Rl3-4Pp8kG%n1x~CJ_bGDO_^wMHLyRfG}2z!`0-8K%E6cm29LJVe0wfL zb?~r5zDhA`5ki+x&W1Y>?tgYIlLcAWN&7)?m=I4pRe0{HZkI^fj_r!SDq(Y{%L|HQ zLAW4=S5K#|Id7Z$H|_Bl$KMIN@mq3>@xFb9wjm>|qx+GN`(?O+zRLHNzwQP9S5Jt|&y4JY+gSwqiPq|L07B5%Oc!rSq zSe;O!OUppq6aokK*#|{7zd7D7vqjan*xx5Be}i8H6Smgb8S zABKk0^ZZO#&p0K#3LidNDwNyUppX#Z)P#Xqk6M1|IfC*}Z~Y(=OzZTt^I24h`rElL z0{0k_yRRC8BbU>?<0r(HfOq z#3nqy3TZyzB}p%2lHz;%BqPwti)$*k_FP~WkyN&>RIljIib>y#a!N!5f4?7oH2f4l_8vK3!@+;1zy-eqCEgqs>|Hj?Z z9JQcjGUm}YYj8~A$BIcrBxbrc+UjB;&3&^q!WtvK@LScsIF&6@KE;&3nGf11b$3^W zCGdHc)RO&kI3kseq1kCzyTS*J1x3@dOjP1l9I2AG{O!E!l3U$Kp0T!RtOvVEiJs_{ zTETfdvj0KaTSmndL|ej0fCLTh5Q4kA1`Y1+?(R;|;O_43?m-%NcXtg=qtoQgy!Xww z)>~`V%)h2D_jXm?s#9n0eNLVH!2uK`iiKr6M;XgkiYTLT6UYLPK^N)}>fLo6Uvu1g zOMbJaUOGf5THn&+J9+R!#Wi7e>6Mtsd`=`hP%;D@UI`B#`K3PRo~q-gy{RSc^%orS znLjn9?x@6$kXCRy-Leo75piVv6jts0QkDRR9!{dkj*^~}o4<9olb~Yu&GXoUg&slg zz{U(d4e{Vn@=V8@a=pio^NV2}VwCL+7nfOCzPB9aJeHS9D$v`pmAh zKPP6d>iETKy9d|05;fpf{@87=rYC%*DAO%$r$QRCoY_9rhow-T%e4Hk53B8W>^|q} zABqE_n3SpFM8t{6R(@k=?xn-1Io<$;&<#RF#fz4kw}pJJLrKM)srkx9zHRE%741xQo;bP*8sU zUhLTtyg$32#D6nwasxK!W&2Wlh-DeB`qxHix+UORiZT>RfV~eBh*V%Rh&gzSTF4ah zD~W$+_-% z@&FC;Tb6XSyNbB)QyqwCco(-X)M>B&>tXcf=>0-<8iSQ!}Mj;gDc@#_s(njB+Nm z4>f%Vs!P?*fH;D_W1Df{Vn2xY_ zx+?Z)v>{E9!6TBQ18Zsum1q40t0R_jeC>Vi&sV#`BNd23LtEEa)%{d;MZ#7 z#ApFqn4%DV^|O=OmK$84#l@Go-^^B#;>miDVa`NU7(UZ~txWP1N*unro9G@)k(fYa9yL@WaBQm4XyYS_`6Z6iO z!+#m{!&T;kjonzIV7D#!5hu^6>3JD{&H4PA5*^~(d{Kmwv>Vp$7y-X%CR8X$P;p<~ zdgQ@)sWK=)A^>IveukZc$~U?!G5O(+8g+fHo(9>FI?@5ycO6G;rFm@2W`nt7-oW3e z(pbJsX|v=ud8{ry>fjY&Gv3SJx!#XE57As+^yL#o?WtrFnGnyG9K-G$zqb~a8da+W z?JTIttU~~>P069YqyCn4t7A+ZN@xH5v+29##agwm3Wzb;R&v0gl9U6>v`>{^_CZAe0+SlcUU)F{_g?i8AxF%ZFUt}QBGPvD33*5~OHc(9PtD6YS z<{O8U7NeC*m*uOFtaJnMVZYF!sZ=j! zgHcPLhi33<3FW^kti7^Xdr?Jk%?=E{7WH6yV)C2c)u>G_&X$KlIq)Nr)t0(xtu>l= z2Q%$Co{|8o8b3U1vasP~P%WW(w{dWNu!h#^uTg$~-Kn-KFsY>4Q_Q|(8&jzR=XX9E zDoNTOdeCxG7}Dv&ke<;A86H!*q*59wHE473SDfZj0hB2)hJe=;xuwo%(M3fsvQKc4Vp=io$Pp!(eng(ji$;+NqEMa#7m?lDnkmc(XU`wh-Ck>Sx|}R;y>jsQQ+b-a%wo%zi(Kj#b>;Gz z{L;+Nzr38;qG~2#qSXHwrM&2EVqO@(L2ZKeHHxTv-3{>k*KbEcAaQ01d9rjDlM%a4 zAy=Xlb0Tf<#M?`fBQIr_qs|-&8K+=PXSBwp(tOtCtK~)czgc$`tuU-w#akZYocxJ9 ze#A=_K>7RP)e`l3oXn>k>vs<;=LRCyp5Nk! zr0ZBzK#kkN^Mfp!mX>IHLS^Hye8}Jew>T*-wf#JoQK}XyM5VL2Og~;?4m(3uUDg*! zaxut(7Gah~2KO|T;u~twgn6DHpU^YLQp3sQ9OX-|pXq_GInWNQM?YT+y@9VM25s|N z;l!c+*yC6{Co_3z+$!qnSL!#7w>l1qG2-WsdR5FWMQ$i@J)Bf2MfsNTq&LmJL0?k? zg=#S(p0}_no7RX)X-&mLqxTN_LQ1ynk4@sJOO~k^>a@XZE(P%ol@qokIMwR*_;9E; zdJ_M#fKjx}zePl=;u}7dI?^2J#BXNceZ9!JB{7w*B$W% zPlOm`g+d(gW+fY%=hV4y`8uAat1YYg*2l{Y z3{)n0wj19?VV)({AIt2;9hTG$clSgwLU=J_s#W8xuJ@NG8S zr%{%vTGOLU4ju@Rl^Ce8B4Y}_p|2tu9vmI)$%6-AB6Mf>V>*Y&_US;izs#9wo?e9& zg@yGPG(Zd3(@9oy!ae3n@{XVlg*lfY$6K>qKeOj8_H%mS?}b5>xN9M>uFEA?9 zD~!fsyVEQ)Rur%=T1&1^S?Q*HgQ_#gj0;>Y{Q9f1TYtp0k)#Jh@P##unUxE{#N@7n zgA$A@`d+rkso5ss$sToa3=GzVU>}>i1%`HgGf`q77$uQ<+XSov78xz!S^cm`Tb?hI zDgAN4k5bbbI_fzCDXc~RHyrUoDNknm{D2VDO+fmZd(X~! zSUkUXk?i)32j5f@>**qWl#dckbE9u2N|syrfT4K^eZ#3jHIy%qn2_h9E(={6crgmwc? z6}dSccvj%Nq2~)K)Jz_Tu#tv6$9^#jV>afuiL54B0_hPbO?wZOqq1GjKyh|t%w)gi z2`JU{?ku%%-(W@~6i67_L(pmUS)6#{UXOlDMl~a=zwezLe)@wHx*gxB{;mkUA@KlP zpB%?Z6eDJlzYISB`D&~y2&v;OY*0Ga1Pvgi?~p=;qrFAK|23+Xel?R2rlCNPK+~Z2 zySOuz!16#R(ER`mBKze;@1->QAW zaUWVA=)HiX(14Elc>q#om!3Mw|NKhuB_Bcpqn2ib!It&kqu;)S$mqJ2_b7sHaKjG$ zAy^dtU&`VC_N@O$?XL@SXGrR5-o0Wb25{NLbVFzLrxlNqcwH(2v0=(=Vf>NJ2vL9}L> zm!1gv)$8MmdJ=VlYeAev)5e^ZW_HpQda^OAR^E7qkb3(U8W}8FcF@x3PA6~;CW7QI zUKoqCYPapLpvJ1Os$rx!qD019>hdGY_8mS9A&z|${di>^)J(c@nKjwjA=L*(!va++ zLTX(P9OObxjLoXFZ6#d_$s+R}4hyJLw{Sf)*3v4Wvml!Nh~9!8F5` zP6gu)In6hlbBj2YaYNZjoJS6dqXA1PY*zp5szQydAOA*Lal}d7Wv7SL3UO8r&mZ{G zzVNtb_h*6>3BCQc;8$^2mQzF~O#U=D?apUMn8th;C|!x9>tc&h`V{+bc#$M*cU02n zjKvJ#r6IuztwyLtItyUL$%~N(^+n1Qnq{B5emNhrc(Ep<(Aw;AidI?SiA@$MUp-KY zq>e-$@p(Y1&PMW`P7gj7>N5LFCigcR7(R_*$-2IOcS|-K?0v32Mw^Pn%jV3AM5a%N zt48(opzPyf&56?$mw1AjE_vo?+K_PXSau5BRu13RuE6L^qSv=ew@9GfmkfH!W`1}S zqI=~FjOP1WtVHQ+;eyw95i)rp7R@;vM}%LhIvuURw_gg}7Qn#sWb@XJ<^}B7uF5n2 z3E3fi`E0S27<)<=|G4ICIpul#b;Wy2mNMI6&gvZ!*S2UKx7tHSHYJpUDFocwLT;k? z6CXG$@Uh)EBhvfcyW@f{=<6j)=Fsa6HXcqmHq`2iv^sAG9HD3XwjW~C@|Uv35PlEU z0ZH(6w_k*&w%Z53`}`d;N=H^tEMM}RDEZ5LZh6yZ=1pbq#XB0JvfJ3nS}KtE-jvNZ zT&bQ3c&g?bMitE!*3$F9U)5?%K4s6*ghb17TDUx=dOz6KTRV}Te0o$UlTu!RSDzd& zeud$xHTBsInLzkVr3g#-Ue<8L9JKyW5b!Yr+>(vjf+lS;It4ng=ODTsjpVbeYwq(G zz;hWU86RzpRj^W5T+?f%zHuIf^ z7541?{zgIq_sXS(Dd_gDCS+&ssLwPxewT+(M?vRi0)2|&_BBUMf~oJt|{jE zxqB#T`IW<+HgsPxEjHb=;;{^u|2(RvqGaiKaf#`TCk;_eGi%a+{yBlpBN?^0nbH14 zzs&p4wiRY&9B_NWkr7}|wG7)Kdyz7#wZ<6V)y(3fy_`KGo0SJ-lMI3H(=!Z@OA}t( zuZ%c$Nx-(B#(DTS<8$18k#7Qer}Oy z{qv^2z+%$zqLT0zdp^9$M-i5kn)nt!pTK*hUgmEpCy1VEDLJUF9=MX13;F^V&8zHI zUEN{T{J5i4Sj=IG_)o+8V=~iI?idV>tJFucZ`>~Y|Dlr+*qQ7Kx9Q60_1L~rCN^ag zbjHR)qFY%hE5ygQw%T%er_=UeW%?yX%1F%cMuE^0p+thA#|@qt=HXQ1D^?UBz~VAc z``TN^Hcj|J(KY3E^>bN!VR-0=WgKeQu(8~?{T&%40s~K1)xzWIs?y?iK&so-;nt?6 z&()d4nykRsqGY<1=3Jq{pF9zyGs6 zo2Gjic=2XTnO|dhbLY+_7hGm4LnN?`doFD?zc=>ms2pF@VGM>!R-hF%Wwm*^;vPgn zI~%z*1U@NO=q^MvvBh|JGlLY#nRCmnS6Pg@_a(-AX>^q4|_-*FX?cPDjQ%97MED(oOe;l$9tDKV*kNGcj*412FdL zOsd$Hl&i;QT7}O)Ia`YkNMd?bt#uy1e5pObrQ-N;GM`AppK+UZ=maMnZLnIQ%W2B8 zCGShK8bz=_6~>ofKNY%CpAp$CUEMwDwCVSC2R~msC<5;Oyr>{~M%3h#?8zj;y~k;5 zFWqHq5Ga3)Wlp(OYm&_#4t0ZCln7AsWC)DWt~a~M3IlFv_~>}a>rkSArpN|zstt*z zd_r+#s_Hm=0~K?OwJ9`24z^)v*towc9Xg$5zvy7cqZ%L+qh59j;!lR4pcER}!&(dO zH#W+oBa&Cyb;OlB;=o{YJ?*q2!lYhx`9H<@w4)Q-a~lkTQCh?^H)!mva&YWhyGR{1 z&>dcqJB3!?&w#U?G1gbIk9k)XZSO8gt%i$oz2vVdiT>K3iMj6oS1f?4T^Y5XsDl>E zvpwlKz7ZCGKEm3WT6HrtNbg^8tc|jMMjKU zu7CRW>6s;&+_dPF4n?pdAq%8z7(|1V4b$r0YQydVXP3l;zxgkwV+!|0Me_%3qFRC| zT=&N)8xgMCXRItrJmrlA!O7^bL7)HL^xIL;x-V4O{k7x3fpxhWOnrUD7C8J-2kQZm zOur^bo#Mj8O5fFWQ4%INtpHW4LKlCc95o`6DW~KahJN>_hC6Dng(FV-ki&@u5hPs~ zs?bsxRr`5=J5NJ11}@ENaXTc~IU@5ER-uC4JyYk0B+$+UiNr8Ch-@2tCMy;&UO++? zV+29XErw?m*K+%;=9AGC6_Z|Da3VWnk*HdDsPXv2BoeD{C-czV%!>!=E7%=i@Tiut zA3UrP_kzw`ni~*mwQS|5WRRa9>MA7wqvVQI_nrwIvm+=Mo%P9@V63V$J{83<9B~pS zjsXAeYHc!6Y&a!*t?c?*wu}N68fxm!l$-bX&(sg{pp+xwu_KbrrNI$gStRd{ z^6B5xBgq82T?ajW*~?yb;QaV%eg<|Vx{U-8FSW#l_*wkWV1O@}AgIBJU#fn@Jb}VO*J-o&GhA*<~DM=#;kCQVWb5=;V zb|?i1s_K0)9Ox}tr+PM2qhdUa+?sV6QoS3s-(`-rNYeuLpqL*TwMdi>;ei($JM8CQ zUk)cj7e(R6d4fD`GIT{Y+fR$tfpcX%NQ(CYWEqU+r}KQVW1*Wo?!&B~$UOx5O+K?1 zxh0*_06p-y4QDd93TIf4vJ3;_>uF&rITo{rs%ly78`hw5_6DWYHY3`L5$(7BAga(l zW$vyHFn!A=#O{X~)Mj}le z?i*(x{up98gc8-sP(g@&L3OR+WL4p0M1IM3@gwmQuuZboC;OXix)Z5wNPT>mf6{{Z zBShHjE*JGkQkYOPbFuP0Kk3`bejf(uG0(YKgPt8}csYc=y|75}H3vkBJKEJ}=4jnq zho+904`jnsi0Lx-9*+lcdh@FtoYx0aQM7VbL5iv)E7 zoXeue!^wQp&jK9EzSgWA3qG1ZivU+70-Hg5=dtvYQvfr#b^C0c3PWM1RhH$Ry24E| zfIcKF3uh#(@bITF`Su!<3W2l;dy8U`)sHoarx;zR^J@#AWfvsWQXU@fC>VNk$<7q? z%jQe5a2^cowyZy?sLA5f2yV*HfwJjd0)+-P6xD6!aP&5_>WXz4gi&EKYxmev;+p5& zzzam5CkJ>K11BjlQMu7>n*B5x;)`h*_&1_wQn+&rG#OhMG-6DZ|EJFUtU z{Du%h7-8rSBrndB!y~xe3Q-gXYKT>v;w{jW%|3_eNz!I(75vnfW!0Usps?J5*tWYe zc>!-eOQ##_-oPqt?9CAD_7lr*@ayy;#}fdhqajXPhyo z<%V?^kfNn9Yd^w$ufBu!Cfs5@52Uie+_%%#raFEEm3!YNY5N@EV(^9$(|}wvCgad-IcyVk;oSVmy9nw1E$~cDlR+$L(Gj%O^)*1;jpt@Ap|e7~8zBBx-op>2-J} z9~m)Il#{0P4YLR&k%%bUHHBg^9G%ZTcsaj_44%W=oIaHG+`4{CKX zcf;%U!ci5RFl6FDm0L#gyx`+Y%o+hM6QVSXeGxLC2_>tcT?u2`#~;>r8+TNwQ`ntt za=T0)4r=r+>6(Qq@5e%sf>rm|n9a#Szi`)a%szK`FRWJod^PDvF&#YIC}iGnMMr6_ zZYrVmX|b|~+YAA0&Ga5PO_>$vs` z&r9r`5b1tm@P@&N%3AnLCsN74nl-ojJd0R1m+e^tnc+m8b0oH{C_+Oi)ZyfTyM*e0 z9jUKN{fNk=#bgdop-KPNsP??^o_J{``%~qVlwjnu(id;(G}C8F7V9pF6}l~EV*~Zw zuer!j-{kW3W_`T02B1Y`_wOUyt99&Ee3yT+>8WO1)MNbZ8|-m2Z5EQ00ubqPrg^gl z*jqaq*3e6DF3$qVdj& zB4eMNKTXaK&q-Bc5wzx>IJGOy?x2)!P^4*pg&1?V)968^(vRd(`Tro za#Q|7nHcwyY8E+o@#s(W!?_s)@T(!8+w-L)h#RzyYxS{$AZJeC!fH=Mynh)3f9}V~>Z|ey4*qmlW{(q2_@Q;}MkNf`j zWQhMW2J*jU4GoOdLG+y9%i{5WpOirUkw@{{Hwu(J;9FsIHo$v^$ZW7ch)K<6lK@vC zvUz||l0AD5H2l{qrdla>Z7;XGnMdGL$4jvlH^$lw4U!~z5WX&q+`wY=Q+~8z-5R`J zS6?rp@zo8jWHI1@LDR+;vXqFpGAIOw zuxK#bus??0{C3pDg^$5t0Uch2Z3zqQHsL4YrG`}c8zhkF7Fi(?7Bo;h+8^=c0X=?Y zWzoLyu&_B4=Dw=RO7nkqXQX=ZdXk16IjtMkKz-jS9Md05X9$Zu+?it7QYqkL78~E` zsVfwwOY2Fhnx{C<$-%W{aC|Wv*!Arql z$}(vENMrc=PL@hHOwe#gK_nK9>~Q;eR?|ZdtOByz(DJBua6ja5md@6s%^H99ZeL2x zUPx2})P~3-90?DnyU$Lq6uH+ZLT8 zJgQ?S|;421jT$W?XX5Ql8rvB>>G#S!CJ|keQ^4n{ z%WT2aXbnZ9UT}gFSc1325`$_mt}Bq~-`Uu7Z#}FwSI=d~-En5%-)o+DQ+n~>rUcXA zAiC);RP58EN`lJ~owbS3@{a8%PpriFF+>juEU+Pk7cD|vX*&08T9Yudn+8|r-Zh*Q~6V79^f!^q_e+T z2A*pTUGK)49T??RT^P7@SF1Ul1H|iH4qc(0-h!dp>5=snhh6^GCp2_>zclXgb*9mo;KJ1W8J5v8nujW>%CVQk#6-`%kxugO9Cor_ zmrr{;4@ zWgZlFRPFilvrFuE&KqfM2?2+*0@ZO&9x(jAYatKGIs*}*OPA0Lc287M1#ODV4bWAk z(^e@rY_CNZB6p=Soj6@f)P4()?^AMR?x&Uf8voW8O0-r?CT?Q}I=SN%R@&9uWccG| zUbS0k90n^W&>?+7b4-bPY4Kw80d(}c@he^y(v*-1h#=zC=(lnq^E?7l2Y@$+oBo`j4J?58Jvl&tM z7~xNA+#$~TC}+l`Xp`Bk_g?mj^`=bzBBz1`6+nUoncI=A)ml?(_7hCL>KFuI-8q*+!CtgNWSUjLYh0d z;7-m*H$Y5yvmLECP#qGG8UJl?JJi`LiFr*Y`OpF9N6DO6poA%rxFA>&ny`naBymVs z+EZyl0WO2X5;#@c$j_hk`{3kim!w=D3JM}crq=V4Rf=M%kZ`BnUJN|3T$Hv1yPu%j zuT_E9wGKeu&3~(j5a%Br6$cJ`=aSI)uY7=eR(d7tt=o2CNrC(=Aitqa9n_M?6qp1z*Z}9e%jnccsD; zG{%Ipfj1DcL|`Q1b6nqXdv(6cAoa_O%ZiIYu|y%dKkv{`%3#66k}S{Y^-J(w9=I>o zlj3BPA8p>4y7(dka)sQ@-F2%#W?;kkv6->Nm@t3xPn41?Z(kl#Q9UOh&u^7#ZJI_e zAioD1idZIE)?nV0)~<$6_%o3NiI_&Sxo8kRHHS4-$@W?R7ekjTEsH5eo%1Aip3TL% z{9jm0AliDhxIW z{6se%@(WLP|GH_(YY-kP-7g~Q04T`2vy`kRI5)%5_#zZ5Uy|CN0+})LiIq1^`P7gx zw=lsMjBeqTMH|dnNNc)8^xm6400Fc9mln$X#KEiV`?m|3t7mDh7f<*CNA+@%eG6zY zq(K8E)&z@{cua0S(LRd{3g7ZE9(M9mxM{_Do6m@05-Zt`shRIicW08mOwLP6E`@0B zabE=!J|nVb88}GlTG5;s8frk8?(T44W?R(8Y5h3eh^eJ#`P0{S(M8W<#_E?Q?x28? zv!&EdpnL~gL_G?$@F4-o{IjyP24f6ep(B9}&JkwlGVvzWSM9BAv{@H}C9YXH2`Z!y zJDhas;3DbWD3n3{d+lamAHLnykbA#LU89>yWmxe&gKA!Pj_RN>N1@i1g9Ty^DS7+f z{H{90u^h1*TRW0~IVsd7sF_m5Z(MYkT!?sPFW0y%OD~kGlQbqj2j@9BErz7V{}dqP zXf0xN=zz{)t)t}#ydJ@gz8d`POtNt#M2YyQN*`@=Do*#dlufs)HPqo0x@W~q(Q1Ty z*;rq=HQhwd>$^Hx(I=MrE^6t+$RXf?lWM}9=b!z(!829N8cpQ$m2NJU+1>SLlk|b> zgD1y=QmVz~aS|iV+<+Qt^ns<^SGSd$U&2c{v|>RH8VMyz3tfvD=cBm|F+)d_Y*Zoz#Q=t$05M3ok2VHN=;1?>yEk8OKHTGy}2ARD(UIlK( z81HnCH_UFze>>-E%pnWIWyp4HSGAOqWk`N)u%Fk?vT`lUxSf7=YoiWH2$NWW$II6f zJ!?9{;_;pdh6@H2&GzbEnzY~N-k@~J`%8W@P+@^6-SUd;g$uF3^EX0RS|6Z3Im~UP z73$7~dD&0fSqJDvN63fy!><^>XiV#DbTNN62ixnu;nR}fZxorszIgA!+M58?Bh6-r z4K^G!0r}gOM8v#BIH6^(V{M(%N3aJLOX};rRokyR(|(Z~i0rPA9sq&Y=EJ{$cI_{B zjzYF$y=P~#`KFtPH%O~nW%fnGHOmp6g;2`)RVb~7NQY?rz1i=9^kOW7%HV(-`zIs| zA-{B54^7iGZL3QUP0%d=OhkYXR<^+w_&hknT)8p*u`R7drAb;?aG7wMYSAplZUzj+ zn|{8oHp~1-`(AUMf#$^l<#1QThm8|AX=#1v{z-X#TW1EDRR3o0&sg*iWcl%#NSHD9 z+kfV1Q;?zj7q$Zk`~PR;MqbL#nEy-$M??McUzzF#4!WE1oy<%FT;l)piGgkd3 zWvJL5Scg79BtdXyEaOAt)kkWtjo8XJqEWzqCb}DZ|M8!R?($M3|Nj49yTU?0{a2#< z|4kb+o&7&n>Yvi(e%(%CD=jKAF{Q)d%^235J? z=PJNUaLM}nO;)Mr2PFs3!_=o0*LQ-;?Q-A+a3*LKb{9IB8_(S?~9n9~#Uh#n! z-|b21hoA*=Z&3-E>KuWBk?jV%LOC)-g$@+@=85)=L(hY^&TiHgx+j|o z-rdQnc|BufeKSN4>mKYLn#~vSMu52wE~ZPW%0uDr(iv)xe5gpDnBB%4mFD;H50&Jh zQ$REHx2L>8fYso2W9aV^b)({u#=a1#Q@zY5By*n91rGnuo~IF>`DIjL28`>VVaxLa z!a98i(wc1~jw4*)pFN0T*MsFWVT>+K0V^D3!Yl_*7l{>|tMRI@6`HiCbsAG- zYmu0%k^8=*lT%lX5@skWjP|``R$NlMgSC}^$85Q>j`0ROWjg_nl&5BFwFp(Ns|9Pd z+KzcHZmBQ6hdVU@jD0g~f8y^Yx`Lk2pf%Z_zk`fdFk!q|JpRRsJ(Za3w8P5+N6Z&r zy1EIWL>m-ynT*mI_<6+Ej4z5b%m8~B4d*w^Or0uIZF#!WADn}oxyO<*e`ol`vC*2=1*625!j>d%q7s)(>4d?%~;N z29AT_3S4kY$`v1s=ZYx3+n z_iXg5=8Ky*%`sIqwp)>nSK37AS8`CHN&SZA)iLtUr<>POe*|lb5fh)~opEMDDLtb_ z3HN%8)>@hu2UG&>4a2n-SNFBA!_Aj`VvFqWVk-5`k4`gU9cufq-BEbMvWM4I^oE0K z9te-2fo--7J{LTEzTLDVX~?(fUD;OJ+E4Uqy&dfS)2rYdiyXPNjfXnk{8ik1S>qa4=AE;*nZ|z2OgPA(-J>RXSmP zXIo+A1m6O*sTUpByP;^y7V?czRb?tPhOfGjjmwWQTVlYfE^N$(_Jxh$Ir$pDP#S8- zTT&9@1*Ttd$QQqf_B0QHy!!z7@a(aQHD9)^kA3QQw~_&7P-^6IfvFLiR!-gzTg#BQ z>R>1)*X!|QZU&xLR%U3{Kj3C6t4XugFtvKi?kjgycImM}FB88-E`_F73~b9uAO3oo zRD|5m@|l`RVxr~9o4MDu^G&KcHvQc*mA;XwK`{J#n17^0b%`n^@3$kBzCG*KoCxk$ z)Rnw^wZ@a-F=?We-LcgoVu#_W5oP+Y4eycU8d!(_XeQ%o5e&6e1Qw`=+nTVk+GM|? z6=LD@meP49j|SNV4z3i#yDa!yEc6(@UDo6f#*?b3;O9WZ(YV?eMb-TAYz~tRY)X8U z!-m^#M*UgTcyOIal{cMBl6A~-%FnYR&IombRfSm|Us zLqdalq$%Rhe)D_<&lA~v04&TXlyT}%i$e-&JXsAL`|lt4Wx1ru2?a`a&h!qPEd$F4 zmG%W3hLy>Zvh2WjV zz(f4TfjT|yz@v9WMoSDWConO~;yWuZ9J*&Xxx*%XbJLHog;YP6OJ+wi4w;eyEA5_x zxiFqZ9^0BZz6Mg{2}!m3YX)1WAh{aPH6!ID|43V6olM50lCli)mT`)Q7ZjdoK|Bh`k5Dp^_-THtHsDR+(2 zxCA01EYa-&EHQ#{d&C5_q_p94!;i6X3xVh04d8h?bU-+G-t`KKYB0<}cQ6hT}@xbrv0WB%?Y{QR=u^`hwJ$XilHzO2RIh1Mu3c$Oj{wh1Q>dmuR$fRM_U&L6}Hsbg1~5 zawW{5sE!gU3uI75d}#>k`w_jOow8Dlq?l#-6ChX_@9&!L!m}@r(kqtu zh(NuRsvUZPiutz>i68A{jPQr$BF94IQoy{$WBS?Y{m#7Tas|);e9!?LjYrcdr*@qP z!>ch>IGjscdaZXYJCBj&R3NY(;;2L+A2w}i+^SWtwQRH>K%35)TPkSR2*djIpQpP@ zx$w6}n1N@YoL;qfaEu+x=xlN}cVjPOeawH-cksRv;I#obtr7z|H|pf*)|0~f`hs29 zK9F@NQNj46^mb79z^BZhiG3S>D)1~K=iq@KM85&*HT)uQcEZzrqlI%Z$?+&Uyc}NG zIy(-{yaJ>4(Fza>@L(xBxVpq;L>7Fpntq~VTi=%Z&Y)j4ont~xzXs5Ul7 zWyaf^y4G%=yQs;_azI_7S)06uV|#7TE(hCFk+B zm%rPO{{3iT&gFt4OO|2pJn{C??JI9wRRu7R|~FqLgsUW6?VI z10lU-`Rc|*BFyV#0v4lCQ*YM?Z(ne44&4(!nwqK07xHK(MF2kD^HXF@_P2?CK4Ly{y?Q^s3L`@%jO;s<>>cN{?_l zogL@FbYSm+^Z9y_CJqG2t|;U)%%7hIy^^09t6%Mtl$1&JZ_vuD>72g0@I|J)^ay0# zv=y(kFQw8~8Uw&TW6xy4Hdl}~7s?zWD!tefQ8yLMyfB`=@H92*>@<>f|J_c$;asu! z#(Kv^#mAjV*wJl#u(p}Xdcr+peLh`av`sMWhjRf%SkS^dAa-LaZ!?no!gj}dP?Ta% zh5NIh1M(-5ks!B=LGky!z^(V8=8Gp8s`Ec$Bh;?X3t!925FVd zg;VNw3Au%)jC>>?s1m%g{d`e;?@j%{q0xw%FDFiV7MX}r%8ugV;(b<^{^jGoyg767 zZSyc=50yjx0J;Q6V6`u3{ecXs&_DD_8UQ*28Zggw$1m0Mbtzu{*TOOAY9Ph15pMNI zn?H=e{|)Zk>*^x+G&PEF{s+)Mo^mC(KIxjex%gjb`jNi1`f^xia;#3~L+ZR}|L;(G zikY(N9tJyZG;7m z;QAA9s_%qRW;*$+ET0vDV5SHC+FMF{fsSTRd!+`GkugM>v&k1DE~cOfXG{6w`$~QU zm`ajar(i-IbJ0P@)!|Qbr`^-lKHkvPQq6QHwu-Z|brvUJ9t{Y^`mfMD_rkIp&j$FW zOgBh1Pf{QiQ?hmO(GjTXbhMZ*;SDqU(uZUYbm52a=#&XR4p;M`Tz!go9`#BTDo?7j zxUp9lQ*pOIVZ=?Yy^!NsP)CxwS9hV+o!EeDUEuupPKhMWq?#-u4Cc!eZ;wy~wp-y7 z6DmiS6O8CKh#x(c~*dVJcHJ_jAND%X)s8ThEGI2(1nyks8{E(0il zpfGdta5}(?82IG!3WQvDwLWb8L5!t4xn4%w(k&%U&gszvukRBTEt4*1kkZoOiVoud zX+E9vy=mctI%u!kOV$5;4?!#Dx_xSh za(4J)DpZOSX*MTkn+Yx;{QgCF@BKM@Vj^9JX1{oyEG;r?_&K_L&zXzRQv7DbB+JW_ zji2D`PrEZOtT*W7yR{@B)lE^9;n}p(SQvD)#rb1}swvlVyP<8zKomdb_5A~G=om-e z871mp{2RtgC7QOXc0v{$)GvckcGJy##^)qs|Kb8RRWlrW06)bN55_4S^%1C%u;KPo zs;dn53GgE|*#1m2^`T8>xf8p!8Y<$1EEJo!XGOd)#wno-kecs)Ca7MFLt-O4gvl^J z!<&tIibgOn?pU_qY)qBpzs=JxM^bxyHtkrb{Sw*R>z=oWrIIPVKUQ|tG2Qwl@`Sy> z`az#2LSmGjIdLEIWY+EvlY*^|8X~tSYkm*h4TmX&;w^j@yl32(?$_v8-qjfrnh29j z9n+Nq>H^{S-lOD|X_g8;>6Ol*6yxHS!=Sh#MX&Wv0$NdP@&9*m-w&WI&Y^xk-o<`13<-i=o`7np8BFohq4WKO9 zFZPZAN?~h51!Rsdlg1U?Sf;VIWQ-R0)Qe)VX3%pqRhes(_Xy=`jb5DPi`Ls)$Nj5X z0TQBTCMs220vRIn)@?QE%A>9hZwKO31E;lkD{WD%)JgRweJ)X2E#s^ZAXXoLbv%h* zrN#jWyI{RCe$_H27|?hy#vyaI-v5+1s@`cr?3q^`qYur>nZ}O zil*aR0p#nT0wv#2*^2Y>wpMOwHzJ1iqf<9gQlU5*;ZnL-g1D-YiXv5~bptr{NgXlE zc@}KZ@t=AbWM1LEGZ$6%s9m zRM0q!i~;W(N)X`tojQ-6nBOy-;7wJX=>XM-+c%qw+ydg1-zQSPn@s-lj!_Ed6~(8v z9-HM>Yy#7szRd!eB*rztn_u;H{*Z(rBA7-k1 z%DQUT7FFxzq1vYcdX9rnR#7@zf9u8yAD^m~Q<~B^RkD=LIY*A)1`fR?X&A#w{zC<9 zsE$UF+BMnUrb47%^Gbh)%H~BPC@Dqp#c6i=l+*RNECb;jHxj0~uAx9UD2fcjL2cg1)t- zRfiM38ICW*F{PPahnn+hwu%&ZGjnlxFwvaKKrTWid?TmMprhWO~<+Yl~8*WPsKOR6s{dmB>#XZu7 z-$!tL`~h}~$Y3b=nJ^rnv{HY5P@juyesTHM1BtrxkmB8bm?TejJ5uj-A-k zMwwS}CJwza#D&GbCRD}@9!3m{jLGimUEO88Wk@#kHl8I};JrCuU9~s~{WxvkK0H-PEm>)#bYD&h5ev3V?aP_K+b%BR z;fMGVML6LqnqEXwn3UPYW1J?UHfv12l+_RDSh>i}h1cidbln`-dPe0`)XlX{$9S*& zs;eU%EF}lbEwT0vvSBI)l>?&xW+4=;O7AUra1*?xRfGz5R0_$^WUO_DkWx}%C=jU~ zsWacwR8p219nB>+sa)NLd${QvE&qAh$`_~6dE3J7QQ>-e*>l^c7fY~R*v)0;>5wz5 z5R>SsvHp-L#Gk&h@0}ulz5je#_giPNd!ga`Fu(3C6H9v6&K9M(GvenKTdY*JAN2^k z+t=UI+Jscj9Of~(SHCL?v6x0_PM=?iptv?JWuJzTQ?9^56R?aDZE5H6%%sfZW&3}0 z-7=RpzABs|y9-*k88;|b`oKeo1ikR?MY}1mce->0BSg-_gR`b8Cr{~tK+8dy^|6te zR>P|av2uVLfQdFBi-)ZJu%i=MbO>G}h z>?Kex1E&6I*bz-`z2c90H2>j^;?O@jO7T_&XMgm06^(-1l)D|9-g<=K z2pQK9jRaekdjP=f$((}iCeR877vP1q1PsqBqL^ekl_yz6!N;M2df52o99l3{SnpZK zwSy1G~#Kj zcNOpmdbV75;7~=2Gb!7UWz8tfyiHcf53f%#*G^d`QJO-Uf~d?kvVdCR_evG#x3I8g zKym;EA-}h8fHEmX8jeaQzI<07HdC}KJ={zW3{VT??Phwcvr;)ELD3lO@xH=%v69@E zu9K9tWe7EsN;hW>lvA}P%d+tpEma?RQ^VC1mvuQZ<6Mo@Eq5o8Ku_+4VJ2S_{4q0# zJFjRR1OnST%Hw;oa}@U|3pw^komJ0-<}#a8&>GQfab8L7Z1pyYnrTA;GHXelVQdIZ zE}^UblH3hfk0#2BU_~Y~LgQ|uYdi%iIkAXs<>Bpe*owT{OwnST!ihr>XmY~M1i3Y% zL9sKXy21Ju_0@-U-d!uJr1J&K>DevZMRp&G-c(7&qL9?*t%GSh&kc+k6LE>gtu%x$ z#3rE-jFeC;^sl2>+E#42Ir-RZZX2(YL|;Gw}Xi*13Co zQ>hf%sReP~l7*|@492W<=)QTF3s=TeF$!onV_(eM>f0&D2O-aN7o?zv(-i`UJ`Noa zw71K*{`Pr>rXgD`0vDt_3>R&!j%MLh1jz*@HT?EE4}L8{$2W#7$ZSBp_^x#PliqB_ zf>E|iDs%BFN=ZU$gReOcT%zHL+eg{9{=6r272lSmoQhEH3&|3qea_eH#K8Ie`OlM1 z8t&}4d`TKR)XP^E23dDr+PQ4!{1Xe&G(A?IJz4~8tYEt?6BgS2nb7sO zdFDvEH^mJ+{Z`pgtQh^J?QMj7Q}Y#7Axgx`P&MaeVsj@KZsQ8RSwx$qc#Bl{k$szQ z|A#*A0W!0zSX0Kc{CH~7?VqrT|SkD+CrQUe?V%wyQV&0x_ z0sG9-O{zyhslkx}&8zX}C$-CAIi-oUO75pHZ0Q`~d~>@C6!P&o%Z)lieaYF(wTqN9*~_phu*_n z0!gope08*6uAZQoa$_^N>ONsfagY?4Z#%PX^j(i7N7FIbL6%N`Gs>RT)dQKNZph@I zn~RXawMdh>qGLiKii_E|o4^Z^E$;5qmVa3DD>i+uzu$#8PnU@dmY=`>mCwgQ<+hJl zEtfYbT9Y~}BV(X;lat0ylr;qJ$b`$1|3O}e02Twc35Gjtd79>a>yK><=1+lwCS_9^bt%y;WsiM>5J z`Hi+)VP_E$OGP2tM|abSG4-FC`$fMC7SH)|r5vZIR7*OmcCb@p;w&$-8UMzwbaCn$ zp?~dLfD@za>x4}Gu~aa8&+gwdW`I4a@TcBq2NcO(d!~1;bL4yHOTM6HQDkt zy3Y#`f&hJFA5eAKJ1w)P&T30s8Rr4G457ITY!zL}r(}9*cJ_R9uhrtgzx^&ZGQPM! z_w=WmOV_a!evke24~N#2f|Vx8quqh9`?=&iO)nBr!0t6MD^0PT(;9I2egj{p{qZLb zjizjxIrY+;b8bA+b{NNlL#-I)p^9B6rc1!gnvYzG8i8(a2O+qF;&0#Ze{V%P2 z6q#cqF2~OT!j(N!?c8xq*ICzR!gu>J9uGIRg5)XRBTF8VfjRgr&wak1u(0Y8zPw-& zTJrX6$3MF5MoHmb`7{3)zh=BQU$cZvZM=!Y_!t2RUbU0g4jy^Oa?{5aFfm7;t)Is6 z@?vWpu`mQ-<3!`SKM)#4zUv2fwk}!EPj&M1iF}JPGFM-Zx?!C&0+3Ezmv@TGas zb;;C@{037koCY7<*q=DlJAz@x_7faUIT3H)9qSOSM2W$yM056`C!GZXi}NiTsTpNg zKDUInY1z1}o0xJGRS2w^3-9uYwXC6e+kyMx!^EfctxtINmD;0K9ZM?^P*B1>MX~(t z3Bt{e%Gb9&nanY+7o3#fmcq3fXj^h(g%F&&CD+z5t(gb5l|FQq6 zWBN?mAorhojAxH0q4N8!yT%c1_XhO@H%3-hC)cRxK2a8%4lzWPm2(uI(7h#!^J}*w zBN7%H4eqE6|B+#8Po8HYY~%g&zrgQ%a($Q9Ss!?!HkW_Zx8MK#QQp3L8@Q$6$%d3F zsZ?tKq`3A7fmPdy$MS0wbE$YcXPG@^#BF#C^Fu6UTrp$ZTWK1>WA^w6qVO45-Ut)# zj0nA6WcE}ffvX6-t5-s-ad-PvEo0;cdghu`jyqa1MXxFy7o3lSH##2o<~`9c-g#Z8 zf~J91-QAqs!hTF-<5WS;@nsLqsSKJ2X^WigOB^gl6l%_HAR{zh#fKBfZm*winO1U3 z1-a-`u?+_W^VtW}YC`H_3B$4w?@7l;(_xk9#TVexpb@6_ZX_BJwjer}nVc6N3l9(Y z{nFl309QQJeeX-%TrZ;==h;+k;&v+5f|~!ZPmMMI3)KsgGV)|cx-N8dfM_ifTKRUA zB(m<4)WVB{xQ=Jog`;3V|6jlH`AMZ`h-WEs{>TY7YH27XaC~>&@lb*|U^E4UZq_FSqgho@g~G z-$~N9X*GgT&|;5T_5Ij5|FE`LL`#Ok!h^(;nfyOdyZ;MH%tSH1Is{_ETSH>{uk{TWBO{~0>0Pg`%6 z>waZtzyjw`#z`i0&goit#lAn?_Ylf=&QEXDrxM2U8^I!RhnN-W8NLn(qZAv(baJ%_%M^# zL*T$chUm-knFo&#{o41~9c&pJN7b`jw+YGKekK^JnO4>Ee*TsivzHpsGFn}Q9?xD+ z^o@kci@wlld1&|gKs8wVjKV^(J9BlP#bMonrlF)n6{v_&Xo3ho%a-1o>#KvwSDS}3 z$%b6wmNn@)BTn2DQSV{K)6kHsC7g?)JoT!Q>bEuse*F=HV~j&in+$|*?TcBi^JKYp z`gl@`^48l92nCQ_eY4ti^LMYhl9){BgwxB014*DKjMf=b+kt%gQ1ydZ0fDN$Gu%?J z7yB%F2B!gXxL#yX82G$aj=Rl;R}wl+i6v{hmG>j6(+=|T0J)3%201(*n!cuhN68#! zG#sa2)k)~4@11+|H1?_1^pK6+^9frihi1ZMwU74{1l;;qRFthwi4CJ7q7wV4bR(So z#(Gh_i62^NT>}zm-ltfsy&~+Moee)`JqYbRv9SSn4TdRXsj1dECl2Ku@}H(SAP zBCWBaoY4T6q9~yERicM8ZJ0|+;Zh--H@jQkn-f|`mM~>QVj>)Jx^jU=kS11WgXjk~ z{~<9HR6LKsZR-lBi!4QXST-*r+d8M+aN2IdaIzao7SU3SRqu{zIJyX72eRThVsa6v z`JtE$(u=S+FFS(&1CyPrGKM7rp_z#i+N%E#7r;@1p;Sz6=UN{oj2AF>J7#tVqA%2I z#x^L`+XS0%#OEb7a<;<=W_jTXl%m)OTTL8RKX0#cUOf<-qU;NO7iX&N;sMy5>Y~$A z(pHUDm`)NnB9;r6a?9*<+p#y7CtY1$CfK{U`QUeD*b1HeQd5u3{76xDlGJy;r!|%7 z%;c2pocmf6pcsQ|tK0Q;+XRT+c-EJnC(&e)f5>mY_5NUP1WR14^Q6@o=95eAn% zCsp28^;`~myq4L@PbrEk+1P_<_lWooHU0zZ~0n zbH*gMZ?pKDJo~|UzF_c^Ooxcp3gK1Vzyf%n&|f-K!%PAjk6%m3eM|d5pE(k|GT13A zR*WN;Te1iltP?x5R)|S!RGG3yGIm<;VZ@v6d0M8P;lB`bvnAyoIiqE~a-^iVhR;k~ zblav&M%boN3wR@_sX%2w%UEn=exNCgK2mpOZYTEsfcXeNU34Hb+9>?JM`JFSqu@8; z+F-92j>6zfp2BxwKTij)>LWSlw2=mH)kh7=ku4cb;gDO3GTcBy+ClKc z8LrT6mhb`bCTt?- z$zs(wiPfg)u6uj#JlvOM2dp*hQrISw(m=_>o3VYqb&s-7?Y;Airsslc95>RQ5giC) z2D@h~<*1uqSkeq`2accG#junV$&5t*<}J?Grq&>2g8T;?sP$!=2Jcg@J2jm9OmSlE z*Qjx6=JJxQpea$lgIWvdnHv*ALfU@nd=tipQTpE?W8(Fe#fxk(qaz-|Cqd}U*~qz-srJn*s-p+*QS0zM-fDBLw42>agZT=# zMIUO7J@7 zVUR?sA42~8^b$@FIqVNwrU(_X8nM|niWJvMBMO!gHj|MGdntu?$9dn1{15r4AxGB> z81In74xX8-L8|A!`O-w{Bs?Fk@!P$*GQbdC_gQZBj zoOW+bz1@*v= z3~+4UJb-I)Z25LSM+s#I*Ch~7ssaYsx-1rM;F_FlM38Qutj3lHIP^~cod2C0#-08E zlc`~&C0kF_?Ps!QF}H;9L8n$NiFd=PXgkByawb$#kKUjwTKjNr!hpLrX=KmKe1iLY z_Em61O`ZRdWAg~b=mlnMJqH3mXahW*uI8;VaFCzAvrlwZ4>(0JZY)IIteZxRDJ4!U zc#}Pqn|cZy*rZ=i8iud+^@kuV#DE1@^OD77#)NuaI&8)!8Wh>cepC}gf!4o6C9BcD zWw?iuuA0joRGnf&g4yq4Zv}lA((fFxP=q&%RkR1?XStF=2{8pb2cnBRL%+FWv&uYl zSB}5dCa81;Cj8L{%_}HGpM~{@NS(}rN-j)7bSg=SlUHVpwB)v>0 zbEVzgCfP`bJ%7suvX1y*=08MEmnb2flyQ09?XWnfwaC_a39mj_GMv@^{dO<57Do=1 zpF9Szx!1&&Igqo;7qXoMI1C(in@#1y-5O*%!>D@ZP_O7leqEQf0?dT*499L_BpEKff_EpbqjyM+SA>-Iea2@3^FqyCNR#MXgfqs~spxcltq z?y>lQeg9P1dQAkdQFk!Cb6sG$q>)t+l4uoB**zW;XE2uSm-hksz^-(ewq$3Uh^i8ks7>_DjjEypLABld?$R+m0Q3j>>8=we841M2k zyc*O?W5#1TL+~ZCxZuXIxArJ?3GDdm9iGnsb}mg3!X?OZ_Z$pol>&xoJkvLoL#CTG zmGV2*t-mqng5wP+$eHm#z22)fxWmnXkyB`ishWQsT91_#tFe~(uHqk8GUuB&OwD$c z4OxovKCV!V3g3HDAmp_Sq@vNJ3zoWFq-FgrI&&MkJZ_hh`ZJ`*H0MpB_wtvzTv|?4 zt%-q1m#C9x40=PmWDP6xmaO7r=u@3nQq~;uc~?O_9)~CFPVsXT4j4m%z31wx*y(=Z zkToS3lf`!KcJg7WD2}9h&U(9Bi}-lK5%FwS7?$R1Z*QWgW~5(-KGpQXEcc`01fDqK z?jx2Era&6W#tcysnp%UbOo-;-pYofm2!Y{4vfA+*WD&i<;G%l|hohINEe!QA2OJkv zw|*$Sp|GmT9%xe09C&QD7%mYGvG~R(ofM)S$v6Ds zl`>FkBj#Vl{g)fcrtov}r?@K1X4d&e^SAT|JDIokqtbZiMrLUjGq5^NWuM%kkysz} zuETX{$e(HHmXtuYl1A3KTk;E}p=dVa92!z-ayQbNGI= zn*5dwCp{w63Io+QnLFVyz``7rhQLrqlaHaqwB=kqm1Rn1$A^%refd$Sr;%dirUMN}Y$9x6coFHDy-mqT4H8-l%jgfXggkk8f`n(F`js zC9CLA8(u91tlgCOqkd>abe676w?Qc0OGFZD$j1w7?^4;0cXVN0X}N+vDnUxttuTd2 zqs@r^?KG&6n^W^QWTw7}4b-mVWc6o{9>>Y4$q8Uvm^O7}ahRmEBJ>UU3e&1KIfw$U zX(SCuH&=$*at}*AlRVkr&WzZLu6%y^mED~np*%lZjY?Jmhw|?fbs}Lam6sI+1q%z7 zHE53JbhhGMoAflaGxt-crXbf`;1bZpO=37H1`6r_RMK*OTetai@M+oe>HKQw{;EVM z_b~1hZ5YB_yBW8uv=8Fi)%grQ=&<&pf=#|-4rgwAs=I)=KH+Fj_vh}taS&if3pHD!0#MzU-Ljv zq_EWWbEvr<#Cw}!@z>R_E-TJ=K2=z_L%oUpWQ|a~uS$XtMIh=X z;bz80HQ}~u|8b#&C#4>`fRg-IiIxGdrlUwy$CI!O)v1oq82iV-h*IyO(P%AA))&PPQe4Y@^%5tg8^fvA`U`KUY{v@5ZehTc_!#4%Wk(VFhmJ=HEy8d$>)ua3VVhP&T|IRZ{SgPJJ?{lKp}??gM{{kE@iKD zBlPy_FXAvULq2XXs{9f6(Jq62^K4>1fl9t<E(;kr}G2UHeOYK z+frIE+9i*`3B730+>+9>>0Fg_Wa-uGy8J#rvR-vZqEob&K*ZJ(+rTGeJQ2onL-n}Q zP~m#fEB-MOLGbQovDAtXnD#FnCBEVuZ01*?`tPggG zd~P4z>`OafJ?03eEQ-$@>^d&yRZcB#w)fid+@<6BF~JRylM-Aocp4k!wDgKO7c{`6wxsVSf%-K@9uh7 zn<(T6aK>C>a<8@atF`7zOf|K*2gJypU6X|yr&C_xzWcF`oO8=Er9b3a>2C9}EB1u# z%^`e9=_Gwg3o5LSG7vS&-%twa^@C+EO!bE2*Q9TY3$4;Rl`+&yat<;u@)fj8iD#ln zS-`+%)|Qo?z+!u4sx8YTPak)99Jy4|Qe!sD*n#U9ww`$O9yY^lBj$txA^Q5`A5*in zye8I)4Y+Q882O`U2@2to-oAHg_APxz-{9-m@w9otk{Uq0W!tz{n#f$8O4-KYg()*m zk_3s$A}4znS#t8BkSZ)GL#wvY1#p7=kcWI>L#Uw^I?fgngP0gP7EzZI2+mTO9UTW{ zXPH*1^8<7cORS56I46ljcZm3?j!Z{UI1WTxoLz$vITB|)eUOqW-9-LZPBgd3c|;Ey zSB^jRF~T?O8eAK@s)S0~togF3WQU$~2hKYeHu6DL&NP|&bfz*6!xpj`t$w;q6t*tB z{%;baS6vh@6!cg8(6S?u;t(z?o>!{}iZ6@|COhkkjWpC8oUD~R0kb|NF4% zL?o3Wk>z;N8KvjqT*fAs(}(9?iI+R)kJYn;+tx*Fxt44@_w{Rg*SogrW>6RQLl6P2 zHGX<9v;DQ+riQnpMo%1J3)pBVr4J2uau+)|FApTL-aaAz9A=YGz)Qr+e1VaqPg16&Ct^1Xoc0=T1+S63!|Bf{lzqDp8q-2I~% zj!(}V0y`=pIAo%f-Nqehqc!1ZD|=OcCiB}{XcL;&o@0BC+MuT)%^3eJ0R(6cP}MPl z#cXeG&7%u<*d3!HTxj&6fAeX#Fg&Ftl}{l=z4?CfWd_ByJ;+5??z**3jmBLFlO8XX ztHt+BwajQ>0;D}M{jD-DAO&Y-(Pd*L|Kr8r)K@ho4`EW+zc&@q|Mah1jW8kEIPc2Z zJK&@Y8F_9dUK(|}2jRW&FZlPfftIF0~b_`tCo=D5}d)Z8_&z-%7{x z+n%*oI8j@PZ|!V7!v_m|=IeV>{t0`a(NwEEq4!ANuxqaiAk(k4@)ceGJ_$Ma-MuJ} zsa!Wmd{*m`{G~~{YGWy$uJ|O<{YOWuWOnOs)y^xfS63p0p&kJ|$BYvV6^2xDY&L>t z2VxCFsw*3_Zkq{LYV{0A1ogW~ZKR2<&mZyP0hrLw_*w!!$-5&>%+6w`ASKv~acO&N zEk5w5|AXSmNLJ0uHb%ND5S2}j=v&IbkUG(oc-ihpCNOSo8o6GpRJmFny5%5`iX7hn zi5_yM*3q|*kSP=Qaj%u@`n;N}*6&q*heG2ZctfvANRT&5T9c_T*RF~0(N54iSM1mL z%eqq^$uta}i(~UXAN0k?v4 z55MgKLG7$ItcI2lgtGAh(jUGVQ1p@g{YU2N3P;x> z)(DH8IC8l3EJ<9iORlCwvOf*mS}s>4>rNKBddc<1b3?Iq5v=arPqW0Px*ikMr_NUn zq;c8e3v<%5hW*o{+3D}B9}SQRxw}u1K>K?~A9iW26JNzPBAuC1CJ>4L-XHRadhrKd zv|4?I)CjyosG%LsU`=Q20yF1qZcc<~Y?*=X6Ue(6lLvmjlwQ=SjZbAKU7vh!i+D-# zn=3($_hw7D&a$U+cBYD-f&fhe>E`jHZ1+Y81Z>I8_bf$g?FAdr2D3A6FkQdDO$u)e zs5D~M-}fE|7H^rHoj>oMgp?u>5Im-su5EQF@e#zNT5u7NJ+LO64K7iT4SLLwCey^=%} z7Y|yU&Y8oh#(h?_!sqacx*ovMcv(Od^}0mPcqRrHlkp_Q z=!)e?|8{4c1>(^}yeY)B`EoXfm)Pjuw`ig^#_l!2y~R}#Pj50jB&a1xox7c_wifkj zvYUPVBgw5Be|6>xI1{Y#DcQ>zzme2tAHnlEyg~Wbo?Bc2A9^bRVB`srLfz=~I_dY6 zj=>zrG+sjak`?o%T(lm{hyc={;FzIzdE=cNZ4)DZbx}yU2jUk4t$XiT2+i;Uv!pGE zM9!P8d_usyVUAf$I{xTkWqI`wLv%NtR>?4)cGTkFUxn(z+a-_bHZCny*rC0j24-*h zLe3$-u#9jespCB-3HwSgp3a?p@}TnlI)W!C0ujViFK05VjpH_)EQix1I$I$18eAJ3<2h9(PWTRQWO9`lc>8mNQp7j^b2%N`ERt8Pjb~ zW2@@aWgjFW#Zm;RMQz$Om(xquCoSmOq4Kh6ksEM7A5DK;bu&Wmpw?%??wWN>sz6!; zY|o2Oy!^$ZPjfJxVK`q(v@c+AcE4|nN^U;b*daH{m=M{;0=b6Cxo ze)Ud~4JgZ5!e?4`I}cuCNq#vi-;K%BH)751&M~v#SBI`K3yfYDXTp4ftN9X%P-%z9>-Hcc_ko2&LmKkimZmkY>7OE33CZn} zNEJAoR2A{aDDqWAmT8gq9&_g2T^rZ_M?Rh5>~D5nZEp`2Se?+~LV6jps`L>#tp3Eb z?@)miq&!|7Q(?oOt|I6$&$35eg!_jOl-Pv>auUQ{-e)zSUvfDyzVD-_&Pr@-@y1(1 zXGP~2GZYl3Dbg~dC_(y4WzL7u+>n*`vuz!u4R8Y^_}tgjoYy~3ufy;p! zFsm&DP~K@gAegTN(MqiX!@)5^{mTp105;?G*a==O6)41pTf(uw2WOMh%)r~w_+;@a z37A@3*`CYUQ_LKM9a`0FAt;=1vTCnUZ6PJhRQ&caMlI*6=P?UjwV5Bz7D#K1d`VWW zZITfQdzJQcw(Ooufh^EOpsJr^=R4Lbwf=gmHyxT2;+zh@3be<9kBSJ=+I7bwb(RvB znxUat6(?z?&iYC}L6sU9V~AlsLw5vq_CK08`hmi3?`-cd8VY{I2|VY^nAw5yJ%g-w zbEBOtI6ydOq&jL$4-Z?mxodlzftyo4@HtJYqELv$eD@BA;@nLu{Nk1i@rdbF!MR~U z6Z@vLoie+y@v>S&; z&jl};+tAFYa)O0em+l&`vJh<@=mGAHtyRp7$5AQ$ycjE_NV9wx!={{N+sdL&3y;Oa zFVh9AyE0p({yO@5tLp4?;d{U>uOOgJALo~o0?cr?SUQ<3XiWE~A=!BkhxwKjxO#k< zbrYFnJNzxD?1GGul`2Hg_fSVK^B|nBM24%=*ABzxNNd!EQba-ilqAIOfb%ixcP2G5 z>){REOgKC)l5a9;sK zj65J9cKbN%gaeOe^JL3UP@zC2pJrB)OvB`8s=F08v?Dl3%wjszm<6oVu;wvxN^3+w zn)1MMsp{vKWlXy2nGeYziQRnWk@^H2aiff5DRsXxtma<833dK5rhGCfc#{V3Hw@ys z#A$ochIr2p5n_T`1| zFDv>KeT*ZZ$qKL=?iIABjbz+QWH7vKay&Xnt?dY)=3p6T#?G902XIk0FQ_L4*+WSq z9#Jwc^2X*HJ)o<>))S2@pmRHAOg0a=hmk+>DxmnnHQ8$8o#33-j~t4P(eV<7LiqUH zcxEoum)5H#lA7EHE?Tn0+|+LY#y>PWuV*WSOQaMhCEl#2(POSWf*)l={V{qF;f-^D zrCL&qWwGT($Jl->si>Bq;L?DZK}(3?AmxkOB$vwkxcmE4%%z6QTT5Y8xh#$xNIH)p zHO9~t)_?VRc1}-y&Y(OopoWK5fpTP(5mqZcpR8Y-tZqX3rI@+pIJQA}Yu-2xE(8CD z(B#b`P(@Al$c`C}K%|9B`En5ktRDTVI9$HO-kt^@qOR6ScA@a7zKtc*YT>9zeW~B+ zA0xC6>%QrSW*r9bR(J5_z~B>%S^kRVfmY?BFtn9RM8TDT?3bON$f0~U&g`6=i910dHS@NGdkVkb$eYtM^XSuBe5fiz%P8>O{B~h@iwFU%F7@>=fz)>s{Q=TcQ9ZRFx%+MTz3kEwfk zQR~TcM)kA7$Xa?>YnE!3&0G=Cjwa~Cft8?GhRYe~{ER=xW+&g__>3RIT|h3iV#|c} zwxaF!nH0#R(HDu(J-d{K8u}pGk)_Uz2~`8y_)d1TZ!r--$k?A!fY9$5|7+cKII*F| z4GG3}pNgHWKxHO!@%qrPeE+v~N>@p%eWGu8qKz~@bBNcy6V#qm*6(tko2&Kw*{Wl^ zulRVWTCJ&kh1{Y3Bu;z?^T>t;l1s?C^E9TTbkE6dFbvVAgX<3=Gt$ooOcctJpV)i5 zX4BmHd*rW`onPxhL3o+u#D)utN{P(!1MLjCXCPHx>lW?x1x#UC6gTua41`&5R4E7J zwN*&9C57n>VtfVwau$kPwkWx^!(CCxdCU<4Z*VTREamMiP=o*3CEIpiU-ql#0A=_C z8$Q@M&;(ffTcnyjd|o3Jn#kkgbiUsFG&3__Z~ci+V35AMJqhw;vHo|}7a*yU#M!9y z{!+pPBWtEZx`TL@a@UO`y#^+JE@tT5b$$_6qO7(4N+By#A6kt!Nnvs*SWd?hvts_A zLbH8JAcsbu`>U`x)^aK$o?JPVr$ypyCnyOGeYDf}KOLQE&azHeL}o| z@CeTSnmu{f-1ke+t8#z9OP&f}y_jXw;vp{X&*Jc<f(T|Bnh`w@sIwI%xK*zBJx#eDb6E;<9WnZ)Xo!^+W;@O~2ZRC0aCW8! zmAveT&ik5Y&wFE~S;VJkF0bOXJ(E^=hLeXWqYYV8(N9-f%b%OVEq#ZcBbCN38&u1J z{qco1L(beP7c|{vU-9&YdiMGTQ*?p#W?Ml=4K?=&O=nN>hU|D``KDR%wYf(5Xsk9z zL$}TKl}^$qx|(MpTXCq!xqd1=qO3*kXY}ZsYs0HYZf$OxOe-ASzmk+Vjq|!eH-{>S zl>?2UF=p*%8$$_@_SEpq?D_?ZajWzR=NEaXrhhHF40DpUV;voHa8^9nM$Njz|84H1 zPN&Q?l&KnOd{X$^+%ozA$|oI^r0Ko+C45DB>)R7a-a2o6*CBJQ-b%&ksPk7xsU5)B zx02O~+v^VpNnN)F#4+yyhYfN@%Y1lpnRDWlG%oGT-oY0o+*_f0zccloz}bvp)6FBf zb%oyKa02|uxR0(kT}hsEV;EXPPq&68!tDnjylY8X-XShSD--`#DRZz+ka?8lcG_ z7yOFa6J1vxVXT@W$rV+wuf4<;*8~Z@4zMN4XZrxv%z||9zWrI3N-|2dAb0Fh*2#44D|&fDtXfx)JeZji~)CIjbo4v?^plu zH7ctAMD70np=)lxGtL(Y@D2Wfc>fCGRrOsZj+Y4BjfP&hOnYYm|GR<6NY0b&4Jp#Q zhhK1?0nogg)F$hF$~YB06%0&=HJk zmWKN0-c~lMs3aU0-79>Lsl&JB&Mv#@Bkt!W2w(nS4((zoSv)R=#h&m4i*^b8KG1rm zTH8`d_N)zv!vWCk-K8W-aMs3=^dW675QAaachZXT)AjH7+jtk|QGjdEH3t{`pqswv zW=tA!;`au6dc81l*NHv1*e5fuF=7(mD8 zTruELJ67JC421&;6tmHz6*sd>8f1~a=;Ua@!8oO3P40L#kC!MUwXjXqWjAYSv9{l( z2|TC#{u&t;NhB3TA@%u0p|)QyyDh3xP;61L(L?ak!YlN@I?%Kf=F2$bH8L3 zjc`r5sh+JmP{dCz%hX)Z&!O2*vOivJ`BesN?^}A@1P%JO`x48Y1J_t;JZLo?iT~hS zaiH1L)1L8(H4h%5dEM{l>!iw#GyUr9A@w;|d96?MV96(>{(q~9c7>6~t!0?mdcie( zX$CFLiih3q^Gem)LUM1HDZuUc`)>G&=z^bhTu0bkzUNp2qZvyfkUsP!^ZOf?iPtsl zFR`wk;Jx-b@RB1ofBop1UTPV5|3uR}!ux0I@T##nh?5=n;*A9aX=*5!w%vrBD2q-Q z?~9yhBn%fZ@d04=HM@Tr%bK^f&}}VA4+3ViZxn&0X?M=vc!aCI( zWzu=jPR%p|Z#vPCR00<%QduornTnh!M{hEk$LYwU2Ti}y741)eH#ds4Bg%BOceW`C zarJL;zgF1)^W#rNEW7HNh6WSJK3n9esq$*0E$>NXHJS!Ikc#(kzzE*p`YTe-TH^Kc z=MXGNFFTjpx1^?{#AjQz>M2%4+8L1l61ntBEcB6>KPXj!wt1V4rqlU(QzVNkHNqoL|~0o}TyQy~Vkq{!-VsPjU&oaz9-Z-Ew`QtewEe^MOfmxYL= zM?CsFgQNOnNOgV{&E)7U3oUrcwBph|rc4)^YIf&fz7{SrwD(#VCYTk6ENaJrV&S9oI=AB~~$^d7Sct zA2cX;Ctu^rIa5DE8!;cVFrYQr!b1u(Fzw7$*4U3Gsql=&C}iX;&Sx9vf;}xs^^iEb zhqpgXTH7$0sVX}{do3LN#-H(iIJ)DhL2LjI}Kw!xp}8q2D{eH%xXGq>Hv!zZPU z1A{rpr?c>ab6>fziZ?K@J0-waY=>?bK4bwE>)^!naZWT7A+@kAm3(w)zQ7f|o1K%M zz47BAgU#ACT{zW0ikC*-rII^yw@8>Aoq|g$kuS6Claw-eqOBrO4z0esSN4cu6vlp2lH;d zh%Q9+IJ>OxqlfBoj=2r+6835#0IT_B4wP+yfWwYcK_?_;I)WSgoO((lwT^_JY)Y^^ zHEAULu%x5AC=_*!NkCxB`x1jVO-x(s9--*;=AGu@>4qFD%WYvdTKjk3Sy7Q$_4wS#|7IA0p~sU zVLg0~h1nnz8Hu;o19_Uho=Q+pgpR!~k8L~`MMDS>Jh%l21ef3tJa}*o z?$&64;MNe_-QC^Y8+UgnXyfkCa5}&L%-p$a&ABsc-4FMEID7Rcx~QtRdRNtcpZ)B; z9s0gwrUFK{%tCzZXI?J`GGIS0a;9d?p_s9Fov&?SHD_3>6yF@mAMHT28W1 zo8O0P_q_BxJs1^{&i~y-Pu%fG`GV*mVx>bim%=ZJZKJ>0=uQ0PU>tS4S>p5%yh< zoEXliV%pdA@!}|>RRt-9vp&Fi{Ztb+;c&cVw5TpeuDYwjKo8;c-Nn-(@_s;#Luy@0 zJiRK*L30g$ov2eeDtVV-At9YW&wjG6xfMDMr5V2m8XLkq{dGbi!m{3;Ev{!{2>w`x z+u8GQMo-{ktH=AB6ZS9V857GRmsO4Tc+3*Df~-#$=+Fo7V>LC_-9*0D2f?5R6KdKqcHx~Wti4<39TID6pzYgf9khge0G7XtJ*+W1k!3Bg=86nyl-=|s0NB?-B* zTF!OsBh2iANG2YK%40=Y3FrNdAutZ%J9%-uK8q_VA@1|}E$u$pD%KW>$}seNsa&~f z@jmSm)+HJ+Zmhwy30Lc>n*FIQyViucUy+a^I>w6iy1ln{B-5qfCvQ9!n2-u9lgF!~`0{gR;+_pE0NPu&wNpgN4 zK05i(uodlED!2N*?dOzPC6#ko=*dP9JD9K+!vO#IGmOS_?L?4w`7+dlY6_YU|bjlG5;UVJ!xiVe0AfN*vX3eY1^Rs{sM?BzP^pBMQy`Cx~l` z{#dS6IJdAcRZqyIuot}+)V!QeN1Jl&Nyt#Cf4 zpMpTwy+=znR0~`0b?=C}8ZTj>;KixSLFR|oZy{Lu#5jX9>Lm&}KP@vDO}>FT!V}@; zjhAk;^Xm=6OKTzJBfEAr4db+0j*$68S_ejV9UM!dGUO!i3z98*&8^WS7wIq8d*`x^ z+9Mo{hU^^%@C8R9IAt%%POd-@i(tT7*!K0b~ee<|eQ@m87k=trs&eE3efqwWyG^^8Rrw`p;4biy7RiJ0OgPl{O6QA0zf$eg7ZhepVcQQD__^*Ph? zLtDADuXayNTg-a((LmY@CjB4r1sQ10tKKo)FMrmO*<1n3mCwmhjSNaBv`2!oWEZw1CxskoWhLuBx&Hi8>3yndn)PRYEmgAAxypRVp#%Tq)NZnNk<+ zo+t|(b2=5pGrXDPM?uWwM&CUWq$E_7x&3n$Vk>9?Ji$o7UI+fP41qRpM~uxLX|JEu zQ2*F@&CRFI35*}|2H4`_Zw9i}n`n!RlaM_+MGO`eu6xXR_9OYPT%zKG2g+3M~yC*`^h zRyg0pavuba3Jeus_{05<4id0PlUvJUvxWH&6S=%;ee|f9wr5d^M}t2;nZNrC6!?r| zdcm~W87p63P(o%Cni;C6Ms;&G(Fmo-NoD+!%68bMc3t1iVD$6C^2fY6R9-`p8F)3m z35J4qHP$C2O6)iQklSn>^fho~cU}G^KfwG|eHmf4u&Z{%zAqPb0`$P0dG~9;yMK2d zS?s(v;;&MIHaxq_2jpp|e4KtSiM&I_$;e7~?t)*!X=sJZ2nc~;FVg?Ul4+=0**{yk z;deUYOY0)xF2qJyG=KCN{+_4%rrl$q6$e=FX?8to3ykE3Z~dorIHI3V7k{ z>q0)GRclGGs$Jif}xA?>JGItZypSU+8QRkfZ@39k+}AqxOBlqpwtR z(cXJ6_>6$R)=lqcwH;rpILtb;g%9q1-~1a+&VhgCU%Rt5IH{8FFMiW^IWVW$ZzRR<61(?Kcrb- zd5upi1oP<16?tdZF?Hblaw>_Zc05|ldSkK6yB^^S)m1i1N zk&ttX1;H0Qf+ds=*Lc>Vk#oR5=SE)sG4ARoMhzTjbh&noPsuE&QiA=5$w`|D8VSw6 zWTYfPuV+v1AKMMp4KF?{gF1p_mw4naPp{7i6nFvO-Gwf(7x_ZBHtnbZ^l~^v_VkKTWRmuxk9ZO?GxjC`hD1RnQ+WdMu&jB|Uj(BU!Q~I4=*I zJ(sP|KJqg(;V5(jgnu|{qElJwn>*U8tp60T+aeVsS@@4yh5xTXF|h^5HP-5?W7O~} zng8gOr1YB&`beJe)py_H**b?2ioXb9H1dB1G>)?tPY&At4bJ;J;}1Fid$!;I3^)A0 zvGP$;^k zH*<5UG29mR?LQ&7)CTZ0FYTpE{4=}v^uM4bSJ1-peL+kwz>ar(9KSzn;1XPa1$wbX zxdA)wpXR`G{@^Kw8a|YnV7Rua0+tg52J&eu?)oBDEH7hPtaZ2Fg_`d$7mMI@+|Z2< zAHPrkqAL*_^F0Hk`{CG3HB~Udq=Tq><6_e~!sRt*`#|fpGJ-&RA=>8$-A^~qhtp2i zBnm4;~9fZJKEA22QCAo9_v3fkVqEV8huZ%E6VlLlcqdXX9mnL7UGE! zUT6sxo)H;oU^@6n2b&ugP}-6if4&mGHkd>opmj@)o&e5csdLl3@e=fM8~vIrFQ(Zw zH4}SDXQoCTEHf-{xwp?8QD~Nx4pw&&zJ1Uakk~npap+6YbX+~-l@XEV6S7soEKU@D zANm)!`cFK*tKe`Pu|9p~)kkG0gr{JWUyP3p2Z?~i1LM z`fcRtcHATX>gh}PDPAC-?w+*O2Ie@gqh~ezH?Z5JDH^myHHSo3^_ql?vX*VV2b(63 zx*U^_(%m`a{zn>DaZ^1c%8t|_r(lgE;tNS%P%^^%3&-kB*Ut-0?l$j81ZxQn% zZ6b68|Dsto(F{%zNm#^zNIM}|2P|sg$k?TFTFzocQJv{;<=%;bY$3Zo3`IXDE|%B< zXa(~*j{Nk4)V&kZ`PviB$EV2PM>A6)N+@(ee$F&EIAdgz zN{WkgH7MZ$ADXy+i+@{~veq$Q1%99pF`3>;{q&_UeWw&I)pau#O{HbSAA*+-IdNCb zm=G?%M!_=ml94Vn0}?(X#op=}Y?rLHZREhd`f2^Mr${n$uz&9w*fPpw`Me-&_JKHWm^+h0k337@57(5EF^ z8;Idm=TAXA3c#JDo6 zbM5q@$#}ko^CvP@IZl7euqODPTR`mMILb2)fT5^$v-NFjuUUoz9x9W1fgal$zQT_= zL?IGGE-DxPdwL#rsuz>l^2?l6i-mZ#jrc3Z#J0{b3GV>XyIbOK1FZ1Z*SW`wiAlsg zrn<)9V~p#ARUazUx8_gBZ~7SpZk8lO1?YPZ^M9F((F^@HTix-V zqa=@f)dzsJYYga~E5;fAn?oPat~7$$H$(OL*L_2Qq|x zH`%TdcYvJYQ+=|0Rp(*fABV|&;>ddlCU!w`5Tr=jr7kkXBkN~$^GgmjT-W|p245bWgL5ZIMpOZ{TPup@w;hd zcfhmKh(Gy!qn|m;l0M}fdsbg;J3;fGPYWb9@Bdygz1zwTt&7L7W=i(3pp%XM`Kqq_ zN6l>NQ^S!i_E}b9=-(MZx-H2X)ITBAC?A`DyYxk0a?r=xafa|q3+&3IV1YcAeW*8% zySCcxgO-_NDce1ZtMy?9rg74=ZooghJ|7Q3wj2M4%(>F^-DBrMoj;8%+X~iJiZ#=c z!OPt{&JoN^l+g}aL^8?dX&bfP6I4&th7Qu98CYz9$V`eKup%a;ZRz$LTeXmZCSY7 z-Qj03^d`_pR{8Uu6-Y8}8WJ>^GZp*GM~xvh-|?7wTaxzDk(I-EDZ1@vqFi5qtxCE zd%BYgs}1^7uswERik@c}o-d*kC}1 z(=@O4Bpz#{JnD|rUwi0<`?B~8Him{1yBU#px>K~ZR-}X2h!fQ6@NmDLa{kYou{VpMynEu$w{fBOLwvJx*k#51&|Xv5~Z&EY`g?1pyi zeZ;>-qvM!+wQ#glZAoPBzR>9{E~RPm2x*GX%HjyK+6v0q_;jy;pFmu7SD>`L>uR-H zpNhKSlpQ8CkXE+I=E1GyeDWu;(X&dG9W&x1#bE@TB?YyyAW;zy_hu59MsriWfEh zSW$TIojfDN+pMo-d-^rR-`*f8jTBS_#=(b;P(y145;mLL(DOya}=kNd~fb=9< zLZp=>B4vElZt444241Mq{a!H-XDP#qpC8sJmij&Oss2L4m?%og=))L7Q+C4c!92$l zV6Vi2_KQP$M`FbrO)9P!Oy=ge0WtrbFk?(Mhbl(7kp*{UzzBtKL}0YvZ`KKvJiyq~ z@mmqGHcxG48l?I|l|Id2ic%}jlqNh87W|8InsCB68Pi`#&b`)m#v&XW%U_G>QUKO; zCG^e0KM2{2{nB%krgEtwq~0Ua4srLi*efew@6Ovk@KSB$U^w4@0?6>dM^d|H3rV6N zshmB&l?5CdSPx^M`K6vu43#HT_*SMehGf6htxr>o=vN#qM05-#Qeby(qa#$uDveD` zi#g(Y9fqP;?e5kk_zkvOqmpv|OCO@Ml`0}g!#%pIUd=ymW|PeZg$$G}DvXrk!uMUg zfJF@kCg3hVJ<*3&c_K*8;gpqcBc@!ywR)F@^5|0uTvFI+z7adDb|*s;w#!pt@o&o% zQlL3q@%d5#f9;pvcH2dvO0nNKLfA)$M?Es&M^au^JMyrTo1ex(bOGE})~$%&fC%S1 z^iwbJ0eP5oK&eagiiLCR|ET`@mRea`O*3d3{+3$8~bsIYOH7+T6QD!hP-r12b@ElJ|u2_ z`O0FClh)g8;gsv%hrBw_TTUFK2GAJzb4vzs@-`IHTp8t*53knHGGu3D!`j%2C zpAU=4iVVrC1AoQ+*r+7kW3q+Ie6BU5_9>bPkKTZ+@&$r}o6cT5{j|~c%lRv%-Unla zmzF|TySwkYFQ&vG5B3y20oHQ{ydiIDy}hZ^E()DLU&p^oQlfUwWO}nckk$#@?7N7x zch`ml1r7elxel;$rqWPlK541%b-yqKdO|*9&dAWA?+bmNkWn>VPLuSUS1jOrlBZ&r z_*6IcA)}+3c31wT+p=TJ^)5D-NRHafZF0qW|ywB&n4hhqUU6(MWPCkRZ-xE*8c_-m6>Ua3tY+)U2x_aKxG* ztQ~*wybq`&YDul9SF3qmIJ-Y>4ENwV{L!5nM!EBLpxPbm*Y@;tCoeh%&IRU_0_}ub zoaeVnu^PdvAYegERJ0c+k&x+Cl(WGEbG3t>(UT$KbV)uyBYu31{JgiH&SPK>TEKHP zEx`J5W^2#AIa|v;hu#(EfUJwBHx5zIcG6lYUzsTf^2xDJR3KcIo^L^n7eI4)*WJ0d z*kIk;N(R0cZ}`Yq+}LD{mul{*c~fA|jm2xtLfXz9yEeqVQ$TF6TS&@$jG$ z;xnHuL+l8UJ?swls3RxgigGk2+s+^y7=8u>gldO-e1^X?Z5(#NL5f@|Mm!d@^MNwr z{KaF4nuZ%T<3aTWtC<+=_P19vgI>&bMuaBaf#;7$zD0V!@SI4FzHSK421lH@y-05?i%Dvt&PVQp~GPqgv3&9KhFwY@ZI?X%^PI7>U=bY0(8OTg+f&r z7}2G}4_K17p(46bei~=4pG$R#4ked#ca^;LGQ zd9CvaCNWrrT(qug<|;HcXk<98<+^$;3FC%!#IJavQvbOZpU?X(al=_xd?M7$gU1u0 z#kwCs_58R;h#N`rE%Cx+P*U>q$}Qz(|BI+japWfq42%a6sn>^Q44Kvn#&Z^Jm4oSU zamsY7q&b57E&A1^3N#@na%xt(X_MZyx;w5;&mhQCbRf zjc163G8c2W21a_%@HkC*kn>NM5!CprZAcv%y7vk6p&Q)mWvm9_McXj=Be$9aTdYO=6>C#Me>BtZS6zW1^xI_Ni$rvn$@mNFu z&$A5{++9!FCCv7E#6Av?%r<*|#L8%i-s5<#p0>ZJz!cj6MS{m#yJ@n`HAF>4rC54i zL}(AU@=UPZ#Y3UjQ1fDQUS6JoD*~p9%f|=OD)T!5@}oIAT?TUUUrPtP`-gwP_8muE zFI<#2$k6uB4=Ctw-};6mLa(qO?H}yRD$q`$(o2T4ME);9Qyj#F>IGEnf`l8O&HjoA zb`A6Z{wp7Iq8R_xa4Tx$G3^*9wE>R_^I!kaoTDkh$)LNFG(z~dd4(VJOTBp}dFU}= zKjj={*3|T`-u~G4NmW) z(gfO;Fz6WHX;lvcLcwJo8EBgZe*XR#Q;sO5_90{X0~c%jEbJ)ApBc(n8i8(aN*5Wt zL{A&!@uwSTPtG3G(oeUPrRljYEUB)hJs}6HbOD1&^A4chvo>yfyn_eolKe4Jx6JZw z4@U{3?(WM&v7R6rO^4%=s6qk#@v{T%YMpwlq$ew;l*Xn6MPxah=93T2QcJX{1UIwq z-GJ<|%@S-?Rw>!H%zS0t>w#2-adM8U^}$7hNPeBB3j@bEtYK>|}77dzD;UZ8qG1 zh06kPEa>^&Pvv>X!%3Zo(P+-^&K4@q6wa^(78uFm%>aWy*4y6=;g<*szg7WpNnb2K z+pJ<%=k;9(GEakRt6gJS!|gr8RZ?C1md#t zPU<46T~A&>JLlbu~3wK>{wJN;5&H|=q>{}XVt(s^*iZid`*RToh!6s zuK=HY;)ggh44hfbhY08UU%HFN$?%&I9v)J~^4^^4Y_Qt>Oq>eo^0hu2jP!l^^X~XsbkF#{Fj8L5&yO z9#Y~VK~k60UG6{Y9KT`0yWiVDXkvjRJv#iFn8a%_B+j6*m|TS9r^mJHpTr_6wd1Rt z7CcY(Z(@>J`AXDnz(NA8bm>eTMG2~D)dGIC!ejW*572z*y^ZLjy zr0av_bDP?=kaKc0Cyn;ifxL>p6y)??*gz!CM)yv}z4ncmI$hA}9EWx7XJi$nJKf{j zBR(_yZA*db_b<7t%k3O2(JXZ*Y;j=Fny~Jd`ry~cFXTPlK`m#WO*{D0EGvV_N;L4` zLD%#M5sZLt5r`QPmZZ!Rk&D$OC+nM+KR|5P%o#Q2k{*sQ?Ksq?C4mmo=yW~u+v|;| zH0}Ez)_s#|89B}xSa;O~9xS{tykIWz1|({RHrR>EQJWOW-~=IiYYV(l*}XcNYU94!~O;Uj|tP=d~T&da3EV5||D8RW_}`c5$ecZ}_A*H`%1 zvtY*vxyW07IK%S`MB~`Ov8_lFCGlC%c&>0#(zW35!wdH1)%$-DtBzfI9-}!u_C#d~ z&o!IcRV8_%NbtkdlZT#66>&;LH5b-m1Goa}fqN_fW!=LXEIc-M6L{17aR)2)uMvyA zV;&)`B$$6SD}~fqk*t{zme^kdT|)Y+OieaI!GL@(9LCD5vYO`_1Vs{B#_IvC!52&_BsL zSntM6lcQW#VQN0sp4F!!TX}{R--+r-?I**p_8tGlV&r?5XW`qz9mAAG7l_)~ms(_>fTr!VcEJwe_2Wjg~KD@an+>+%AuuPG{7&cV#|~+Eg%e zL>R0B^<$D8q4bhe^ij9u;bSb!q)LVA2rr)`RFF|7b!vUC9J~&=J_n5*q z{fAu5cbj*~t!#nAvYOMKToLtxktT~(=t~L6#x~oz2F@4HJNeJR;qIRAo1H171ojiK z9xWr#J$SW&vC3h=I&w$llPI|y?I`030g^mhEx>)Lhn{-Tg31S|OWf;qr%OnrM<1^C zev;v(=JnME%W@U3)?=YElqwtn-y(Zy4@&lgy4`tnhh-M9?D|){+uB=9OD-FSuM~Ga zS2+8H&%N^SeDLIIr24i&?W>6Rb-o9p!2$b9*|7HovPP{FoE}5^L;<}rZ1xj0Ty5UOYjd6&hb93`KeTct6u*Xb%~XA@r%KQP zlrNHVvz#q|V`C2LB=-_Se@j1?WjaQvf^^;4ZqIrq!|$_O(qoO~Wps16`^;BEEF?~M zB;b+R>>r>(M3(MqpvqurRCr|uRRmQk+Fkg|gBXr7ExrsOj?8h|NttG*mk#;4;7K5T z8ktQ;&*XRX&MdcAyyW|S{q8SrtMy#j*{;;x$%uA&+DcqiOyTgLVLWBv-kOjQgMc2y zPH)F)d1&9r4D(IcI5)U>(k(I@?ACxdy7dL|x$bRQZ6ydohfecTTS4JTBTV+T)mX~_cj?|xV+)!ei(~|2Z*Z>6#Rx+!+BVPDhRQqsb9J#Ot7 zhTct|CKHrtV~sFPIA@nd_xIZrMs48!wQ2Le6{9XWnc{X3yG;)dZ2f8Dwi=T(y*S3n zOO8KS=0~B=oh(kk814){T555`njtm+E;mj4OQaOT!Y4gh#;^cY{QVk9Q>d9uY93?i zVxp1KMGM9=ETJEY(9&lAF_y>TN{} z6fvJ>D6$kOIAlA21acBvY+1V~vgN@1hF>fmqV$u1?HT0Fh8?$`Hpgh>N!YNr)<&%` zQb_&PghG0V{!w(`4J^GQDt#k%T5>YZXLF@`lmInHLN1j4&QG6OV8=wX@qcf!S8Tpr zp`zrjpR{~oda{Fa<=jj_c$^|{6W|z8Yqy0o<2P$*R8zr?oae4F!WM&#{vfpnfKcBL zn1E}jolxxx+3GgsU9S4yrNF~PPt9JvDN~7&o=c_pm5l#eVB=PqI8Gi5&xUDk#mn_3^`4%^aDrj`Rl2n0eV}?U#eVXuJQY8B{InFs5 zV)j5iwKnpm<6Za}570oP0?>#x2O}qADCHdI`FR+)@#1yw0K9HGPDW&l1+qsNxGgjA z^zq+Z3Ojn7%L&^Q8w0)?Uti_A{8Vqf;>7*?hLm~q7V&DhnvHD;>Ga!#ssMvFd*l-R zk-fecp=o^TK7mQ~myv=jbeL7Ukq4Ji^$yLda4dpTpAY+V?-?sJ`44bdA{;I4J5jPd z&p6-db_WNWWA`qzS#n6zw2B58?3DL6AY)z~e9$Q>@)r80@WvK}p2J@Sc{uanNBy9j z{AV^izP5HrNvWZui!WLkHHE1vd?QLa+zh44eaIiFk3h> zc7r@Y_s{M+I2m#R#F+a}Rs$?O${?foY`^=8seZKHkPoxPj&`?)&SP|VU6S>Wtp=<) z&@i=Kk&(VU@s!{Yji)xBN>gQa?9WN*&*zzcWTZ9H9fabf*oQg-U_P*YHQ9(*`8o45 zitX9jV8X1hv`OVwyg<3x)Fq*~j3fm!#Ru3bO!*iGH$8hf%uM6ooAS1ua3!54Ymu+v z=}mDDmNiwVHaS}C{iO;bos`zMV)L2EtiJEjSUaUQRkjXb+^!`X-n_w{kQ5bC$;GAK zmN$&%s(URP=ZywY2tHn)2wSU7!RBa>zUfCL>eupJRwx~mh!yg;d}OX#-4*n{Wl9~N zEuK+tZyiL`46Ge7iBZqKV?FGJ?j6nw3=zYqLaxAu?y z+u1~hduzhXc9xjd*7<%m1Ae`5kKoL0z6*33-`_%i>bEsojSzT|AXX0s_>R7?7YKur zln>We1Z%+`7puchcq8H}qJ^PL?`21sjueN9+pt2BXN0c#r2g^2lk~upqvggbj2ic} z+u3z!ZKx&Oe9`nxIRYwpFN4@zX{mHSe6Y;dQWJ^ zdifBB_wu5PzxZ*j#s4eU&PT0@0_mplTf?60GmeF6#@Svuok90XIU|4)TC;he#_d$A zjNobixrOM8stKa?bQ%v7I!%J+(!#19G~9@@(TK4yy64Jw?R`LC)85%j+cms{)lPaa z;M`R)L|kA^=%v~=W5^VEU^klvfggq8@vJ&{t`E!e_4Djk-@uMy2 z(Vk`Hz~Xyo(i<7x_`VG}13g^z;L>}yWwh0%qPaF&9O(-T>Px_-wFlAJZHbRs_G(%> z=EVB;z>+h0vJbrviuUgE#olCtpYBW8*lDKjpWp$K?PxGa-!cUC)J1hvqfe433@M)0 zhR~1QQceebaE&wcfS0wCc%VCZM6zDS$1p_^bU#qy7*=^^DH4i%az9|xRo8rrMnd}Z zJ{^L3JnDNacuHPlqWp}K8s|!SxnA>i_1paBL)&d-HG_1G-JFG#j0P4j02MUK_YLjm z)?S3cp!dtF?Q5acO!@@WR+b9iDtwzCfiWFTQtqk2EQngG$>k=R!T$k0l1G&N+EmI4 zbB6S!uK)>m0F5$g6JC4C*kp;K(lax-bcQU8|4xuC#qD)%_#_kRfOMu}%bVj@Vd@ya zs-BorJ`z(oGQF48`hMvH*k9i=`M*Hdv%w9CTa!McnWd{9Bkj8$Be)q9J;DvhyT)0E z%^I8SY)h}5=;GES2Y>_+$ssx;4vM<=qO@m{bivd92kI(4mV#qv7xh_hU2KG4;bJ~V z?Lc#@NFfxGxhf7N&@?yyWFSJxgkKfdLu`3R@C{b89>rif@}i8$ow!`!oSc^BAB7JgLGnV zl0cf$(NXgws8aUa`jSh;~C4 z_Y04`mnZn2X&6F(E>L?5d?dmf>PnA<%-RE+pH}ADYJqH-C?|tzzkyLH!U4Nu1xZ@w zkjD8{J7+f2j+PMr+{eksa#myN!f5L!OwW;UcG{bby}z577>yb<()m8yC{p#F@Jor~ znUJw0HKr*leETESol}`~m1l?%LHz#3gn`2)6se7Be`>hsyEp&Xs6E@UD^yXrB@&z6 zbMVnPs(F)bJymO;I}dL=jg2S{^rF#^VlH;`2AyFgkcXdi>o!4Gtm2iQ`bo*nTbAy| zHMvp%7)PWvUyv3_O|ssl5_;IhUn#dIh|V>{JH;k^a4`)IBVCUTlFBeH(0Dg*)i*W) z><_%-%J~PWeXTY)OROU$>0O1OI5(7qWESE9vv<#V!Ko%j&Gv(Kt>^T5fqM)5y{Q|( zzL}GeJrL-{;c>wd1q$WCQ{E^DIzYXmA2D)@}iQgQ0vP}-%Esa4);Gm7}%rD9ZFIP$m4Z@7zxBydU+)We58hlJ?UjbsPIVp#_3Qo~fHs zu^lN%^0~kr7hSRcWZtEBwFl=O8qYk-B5A81$AD+hK1-T2m4LW3X=f`&k6=dcJ{d70 z{aH9KKf*=|WAnuYx~shchYVklGuYkZadoV&gut7Z5ihfZMf-I>P^O8i&F`oaidF4M zu^bD=|9 zyAPay;0kU^1CB=XR61O*3`o|FMb4hYuMAT4!=6oI5<0|YBDrQZ*DpMl=b0+`p73i; zUpk+p8>=8Hoa@>32g?`m8OIoMaCkeXk0EkQ6NVtiM>x5wEC{j9g=;;51JIT8^yg;{ zzM|i6hj75MGr&gXA~`DQj;9RTz%{s7Z4P|)GK8_=v@cadV{+j8?2TlQ3*(Xe-rM!M zkG4dOvpmYVRljP#hY60B;8c&Uq=pdrA~8N*n6>J2%0PUWyqsa-aZ>aKK+A!@JRY$7 z%fAjC&?y(Fx@YWfyouC7cD3$5%gfNXniF!=gr0k%J9Ng><(n6Y8{1F4`kIZ0qG#SH zoE17dCtVQ*#&eEPV z2(Mga$$Q?TPsp6TGkMn5JYr==M=i$M@t@7~#>DzFP#L&^^R_{yJ{3d%_$8Z{o6uRE zJ^7q$k>R;UkQd*W+G71YTMqlOD=U{yaAR4`>qMzl+-L3EGBf(MKWxzxaHG0a@?G=? z9`ByV7~X&s5?6DXM&s{ywMei${$Mz(H)@oR4kt2|{X#obiFIjlngOKYJ#?GVZGo9; zB;+fVR4j9$T%p*2SciQYy9CMRs7`x$sk@=vW7&E;I5@>qZ8ift!)S2UpB_oU=-2uF0ATXd zhZ$Ak01MfJwIYJ{L;PdC_83S1FYR!hMV;2L?vu;_gl#YNF2vSF2VQ>&^>p><&?LGqawkg@T{S9 zEZC+l?%S4&#-?BeC@8jh**`JT2`z4ZmBt38eRw|{VO7B%&E(4}^UU3n+i!^Dl7ieU zL;vLn9-O1%3S?M;PdqByT#HUr?tEbFW_a3IC$LoZxL~f_SmB0f@KS{hwraot)kus2-@)=0KSized&1DzR$CtNm z503Hm7Wg*b&sB)1SI1eq#F2gd8a4Un6Wxzu>5W^|_hjKG?pw$lX)b?A@kPySKkbUp z&y+wj)4FT*p76W;2DSE8hZ8Nv9Z2?=ZmMv^Xfz)>$4s=!95{9Li2OYzM5PE4sWb5Z zI4Msv<|FOY7??1)(+>Qx_<}|v`1?n_fMb}(bIUJ;&pm{jIxbEr-u)9AqZEZ(o+0wLeMX`}ME(?xXz}U+r5(DH0mwJ}snD^TznDqVB3q;Le?PAYoP| z^j!UJ)v;tIPnCf4WRPcO&@dr%r-S$N1cL>*pxS;(*z|bf_l%bTR0UT#qXYnZ!<4{< zUmaoL13I?I9hgf5PsyN7?CA257<>KCp%J?6?Tfa*MgHwU$kezk646QVr~1!i^oRpjtXU4ysgqR9aQ2pQ%9`pi(&fk?UgkljDcG3xfcmOF1IFzKRDyP z(*NZcK&AQ6Wjg2X+hf_IH>m)L$gfsh(Z{?R;2h>2AbvXlm|kSFS49Dhxd|WUTWn`r zz-Gq#RLi=D$ zHFAjF85NuuFSYO=GqAVm+a$Ntm23JZ@pP1}t16tL#D?5_K2T~<(43IehXjaF;KXU) z{KutY`rUHfZ93FVP-gpSmn}JSS8O+BijQ^6lq3wXcW`-dwmvv%w=&y(E@z_G=|@nu zRy?VzeGd{)+D5@xlf>6s9O9a@Wbx8bqOg!x$|^$Qf%#f2$ZWSOysi}=>h-{L-TI)L zaXj_GOAZ~OQF56hb0LHeYsJ^JvF=Zc8#w8-WH>0usSb(f>0xb)LG6J@G!Q?}dS#-+ zaQjaAj#%I8r{iJ39EI7_0nE0XogzmY_YdUw#e)^@wu4u-K~Ad`0lh-dU#o6VuRnYH zqAyfb>Uv&HIRr2GjhkQMzg}yCjQ%T5%m1fVG>dr0ZG^Q3Zo*2qiN<1szx9f~Z+QBh z7e@3e*QLOQoL9fbqW+S41N7UnrDo)+gVj3o3%I|Ri&T91)9tV49a`bHuti$UNCoEl z`i4pH@cQ`T>tX%LUcE2@2KxHm(XU=Tz3j3wiO4hT4}zePyxS_LX7V>skra~?tq|7t F`Cmhe9F+h7 diff --git a/docs/modules/nifi/pages/usage_guide/writing-to-iceberg-tables.adoc b/docs/modules/nifi/pages/usage_guide/writing-to-iceberg-tables.adoc index 9531a1be..154a693e 100644 --- a/docs/modules/nifi/pages/usage_guide/writing-to-iceberg-tables.adoc +++ b/docs/modules/nifi/pages/usage_guide/writing-to-iceberg-tables.adoc @@ -33,91 +33,3 @@ Please read on https://github.com/stackabletech/nifi-iceberg-bundle[its document You don't need any special configs on the `NiFiCluster` in case you are using S3 and no Kerberos. HDFS and Kerberos are also supported, please have a look at the https://github.com/stackabletech/nifi-operator/tree/main/tests/templates/kuttl/iceberg[Iceberg integration test] for that. - -== NiFi 1 - -Starting with `1.19.0`, NiFi supports writing to Iceberg tables. - -The following example shows an example NiFi setup using the Iceberg integration. - -[source,yaml] ----- -apiVersion: nifi.stackable.tech/v1alpha1 -kind: NifiCluster -metadata: - name: nifi -spec: - clusterConfig: - # ... - extraVolumes: - # Will be mounted at /stackable/userdata/nifi-hive-s3-config/ - - name: nifi-hive-s3-config - secret: - secretName: nifi-hive-s3-config ---- -apiVersion: v1 -kind: Secret -metadata: - name: nifi-hive-s3-config -stringData: - core-site.xml: | - - - fs.s3a.aws.credentials.provider - org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider - - - - fs.s3a.endpoint - http://minio:9000 - - - - fs.s3a.access.key - xxx - - - - fs.s3a.secret.key - xxx - - - - fs.s3a.path.style.access - true - - - - fs.s3a.connection.ssl.enabled - false - Enables or disables SSL connections to S3. - - ----- - -Please fill in the correct endpoint, access key and secret key for your S3 store, this is a classic Hadoop config file. - -Use e.g. Trino to create a table for Nifi to write into using something like - -[source,sql] ----- -CREATE SCHEMA IF NOT EXISTS lakehouse.demo WITH (location = 's3a://lakehouse/demo/'); - -CREATE TABLE IF NOT EXISTS lakehouse.demo.test ( - test varchar -); ----- - -In NiFi you need to create a `HiveCatalogService` first which allows you to access the Hive Metastore storing the Iceberg metadata. -Set `Hive Metastore URI` to something like `thrift://hive-iceberg.default.svc.cluster.local:9083`, -`Default Warehouse Location` to `s3a://lakehouse` -and `Hadoop Configuration Resources` to `/stackable/userdata/nifi-hive-s3-config/core-site.xml`. - -Afterwards you can create the `PutIceberg` processor and configure the `HiveCatalogService`. -Also set `Catalog Namespace` to your schema name and the `Table Name`. - -For the `File Format` it is recommended to use `PARQUET` or `ORC` rather than `AVRO` for performance reasons, but you can leave it empty or choose your desired format. - -You should end up with the following `PutIceberg` processor: - -image::put-iceberg-processor.png[] From c20259d9b9f6628c2c7aa9a9cf61ad07d20d55eb Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 18 Jun 2026 11:59:51 +0200 Subject: [PATCH 08/10] test: Remove leftover assertions --- tests/templates/kuttl/upgrade/04-assert.yaml.j2 | 1 - tests/templates/kuttl/upgrade/07-assert.yaml.j2 | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/templates/kuttl/upgrade/04-assert.yaml.j2 b/tests/templates/kuttl/upgrade/04-assert.yaml.j2 index 00922463..9297b0f7 100644 --- a/tests/templates/kuttl/upgrade/04-assert.yaml.j2 +++ b/tests/templates/kuttl/upgrade/04-assert.yaml.j2 @@ -3,7 +3,6 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert timeout: 300 commands: - # - script: kubectl wait -n $NAMESPACE --for='jsonpath={.status.readyReplicas}=3' sts/test-nifi-node-default --timeout=60s - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi.py -u admin -p supersecretpassword -n $NAMESPACE -c 3 - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "python /tmp/flow.py > /tmp/old_input" # This tests if the output contains an Error or zero flow files are queued, which also indicates that something went wrong diff --git a/tests/templates/kuttl/upgrade/07-assert.yaml.j2 b/tests/templates/kuttl/upgrade/07-assert.yaml.j2 index 21054b7c..dae1ecfc 100644 --- a/tests/templates/kuttl/upgrade/07-assert.yaml.j2 +++ b/tests/templates/kuttl/upgrade/07-assert.yaml.j2 @@ -5,7 +5,6 @@ metadata: name: test-nifi-updated timeout: 300 commands: - # - script: kubectl wait -n $NAMESPACE --for='jsonpath={.status.readyReplicas}=3' sts/test-nifi-node-default --timeout=60s - script: kubectl exec -n $NAMESPACE test-nifi-0 -- python /tmp/test_nifi.py -u admin -p supersecretpassword -n $NAMESPACE -c 3 - script: kubectl exec -n $NAMESPACE test-nifi-0 -- sh -c "python /tmp/flow.py -e https://test-nifi-node-default-0.test-nifi-node-default-headless.$NAMESPACE.svc.cluster.local:8443 query > /tmp/new_input" # This tests if the output contains an Error or zero flow files are queued, which also indicates that something went wrong From bfaa871063992e5943ae359010be1d82b00e3820 Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 18 Jun 2026 12:00:28 +0200 Subject: [PATCH 09/10] test: Remove old, unused files --- .../upgrade/generate-and-log-flowfiles.xml | 327 ------------------ .../kuttl/upgrade/test_nifi_metrics.py | 62 ---- 2 files changed, 389 deletions(-) delete mode 100644 tests/templates/kuttl/upgrade/generate-and-log-flowfiles.xml delete mode 100755 tests/templates/kuttl/upgrade/test_nifi_metrics.py diff --git a/tests/templates/kuttl/upgrade/generate-and-log-flowfiles.xml b/tests/templates/kuttl/upgrade/generate-and-log-flowfiles.xml deleted file mode 100644 index 547e69f4..00000000 --- a/tests/templates/kuttl/upgrade/generate-and-log-flowfiles.xml +++ /dev/null @@ -1,327 +0,0 @@ - -