././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7144327 magnum_capi_helm-1.4.0/0000775000175000017500000000000015212012201013605 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/.coveragerc0000664000175000017500000000014115212012064015731 0ustar00zuulzuul[run] branch = True source = magnum_capi_helm [report] ignore_errors = True show_missing = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/.stestr.conf0000664000175000017500000000007015212012064016062 0ustar00zuulzuul[DEFAULT] test_path=./magnum_capi_helm/tests top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011584.0 magnum_capi_helm-1.4.0/AUTHORS0000664000175000017500000000146015212012200014655 0ustar00zuulzuulDale Smith Dale Smith Ghanshyam Maan Grzegorz Bialas Hemanth Nakkina Ivan Anfimov Jake Yip Jasleen John Garbutt Matt Crees Matt Pryor Matthew Northcott Michal Nasiadka Mohammed Naser Sam Morrison Travis Holton William Travis Holton max.bed4d scrungus <33693738+scrungus@users.noreply.github.com> scrungus sd109 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011584.0 magnum_capi_helm-1.4.0/ChangeLog0000664000175000017500000001140515212012200015357 0ustar00zuulzuulCHANGES ======= 1.4.0 ----- * Add prelude for 1.4.0 release notes * Pass network\_driver template option as CNI type * CI: set network\_driver in the calico jobs * Support adding multiple extra networks * Fix: allow deprecated label names to be used * Add oslo.config-based cluster label registry with auto-docs * CI: Rename tempest jobs to include CNI * Fix tests due to upgrade of requests 2.34+ * Expand documentation * CI: Add v1.35 jobs * CI: Add v1.34 jobs for 2025.1/2 * Rework base job to be easily consumed in magnum and m-t-p * Add master based CI job * CI: Add magnum-capi initial CI job * CI: Add --diff to black * Adapt to Magnum latest changes * Drop support for Python 3.6, 3.7, 3.8 and 3.9 * Drop lower-constraints.txt and its testing * Replace dashes with underscores * Remove outdated comment in setup.py * Start using csi\_cinder\_volume\_binding\_mode config * CI: Reorganise .zuul.yaml into multiple files * Add support for HelmChartProxy resource * Generalize kubernetes control plane resource * Use openstackcluster v1beta1 * Support credential API * CI: Fix py39 testing, add py313 1.3.0 ----- * Use API LB floating IPs according to template and label * Support token based kubeconfig files for the capi management cluster * Pin OpenStack release version in Devstack script * Fix resize default workers via terraform * Implement control plane resize 1.2.1 ----- * Improve node group deletion unit test * Fix deletion of non-default node groups * CI: Add linting to releasenotes * CI: Stop running py38, add py312 * Intro docs for Magnum-CAPI-helm 1.2.0 ----- * typo in release note * Helm chart repo changes 1.1.0 ----- * Improve default min/max node count behaviour when autoscaling is enabled * Implement boot\_volume\_size label to override config file 1.0.0 ----- * Allow autoscaling of non-default node groups * Support autoscaling for the default node group * Add initial release note * Add initial docs * Fix cluster deletion on trust creation failure * Allow additional load balancer customisation * Change allowed\_cidr to expect a comma separated list instead of semicolons * Fix typo in upper constraints * Use availability zone instead of Region for storage classes * Re-use oslo\_utils functionality where possible * Disable keystone-auth by default * Fix Helm value snippet formatting for Keystone auth webhook * Add skip\_install = true to tox formatting jobs * Make boolean cluster labels case insensitive * Fail on bad formatting * add cluster-api janitor to devstack script * Remove outdated reference to nova\_client config * Add app\_cred\_interface\_type config file option * CI: Add cover jobs * CI: Add publishing branch tarball * Update .gitreview and remove .github, add basic CI * Appcred authtype (#49) * Sanitise storageclasses (#48) * Pin to 2023.2 upper requirements (#46) * Move to capi-helm-charts 0.4.0 by default (#45) * Allowed cidr list for API load balancer (#44) * Add config for k8s-keystone-auth webhook (#34) * Retrieve volume types from cinder and use to make storageclasses (#32) * Update README.rst os\_distro ubuntu (#31) * Update to black<25.0.0 (#43) * Update chart default to 0.3.0 (#42) * Support etcd on separate block device (#40) * change devstack script to use CI versions/values (#39) * Query nova client for allowed flavors based on config (#33) * Add capi monitor to update health\_status (#26) * Refactor k8s temp file cleanup (#37) * Clean up temp files created by kubeconfig certficates (#36) * fix devstack script to match os\_distro change (#35) * Allow customisation of healthcheck. (#23) * Add support for Flatcar OS (#22) * Test on latest magnum, now its not broken (#29) * Move back to os\_distro Ubuntu (#27) * Move default helm to 0.1.4 (#28) * Refactor create\_cluster tests to share expected values (#24) * Apply boot disk values from Magnum config (#20) * Improve the README (#18) * Rename config option namespace\_suffix to namespace\_prefix (#17) * Fix CI by pinning to working Magnum (#19) * script changes (#15) * Add missing label to cluster secrets (#16) * add a development environment (#10) * Update the default chart version (#14) * Fix incorrect call get\_certificate\_string\_data (#13) * Remove limiting roles (#11) * Remove dead code (#12) * Cluster API: Support basic networking parameters (#9) * Cluster API: Implement upgrade to a new template (#8) * Implement cluster update and resize (#7) * ClusterAPI: implement update\_cluster\_status (#6) * ClusterAPI: write secrets into capi mgr k8s * Add kubernetes client to add secrets for helm * Get appcred and cacert inspired by vexhost driver (#4) * Cluster API: use helm client in driver (#3) * Add Helm client (#2) * Add a stub cluster\_api driver (#1) * Update setup.cfg * Add automatic black formatting * Attempt to add tox tests * Attempt to import from openstack cookiecutter * Initial commit ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/LICENSE0000664000175000017500000002363715212012064014634 0ustar00zuulzuul Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7144327 magnum_capi_helm-1.4.0/PKG-INFO0000644000175000017500000000424715212012201014707 0ustar00zuulzuulMetadata-Version: 2.4 Name: magnum-capi-helm Version: 1.4.0 Summary: OpenStack Magnum driver using helm to create k8s clusters with Cluster API. Home-page: https://www.openstack.org/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.10 License-File: LICENSE Requires-Dist: pbr>=2.0 Requires-Dist: oslo_log Requires-Dist: oslo_utils Requires-Dist: magnum Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: license-file Dynamic: requires-dist Dynamic: requires-python Dynamic: summary =============================== magnum-capi-helm =============================== OpenStack Magnum driver using Helm to create k8s clusters with Cluster API. The driver uses `capi-helm-charts `_ to create the k8s resources needed to provision a k8s cluster using Cluster API, including various useful add-ons like a CNI and a monitoring stack. Note, the above Helm charts are intended to be a way to share a reference method to create K8s on OpenStack. The charts are not expected or intended to be specific to Magnum. The hope is they can also be used by ArgoCD, Flux or Azimuth to create k8s clusters on OpenStack. Work on this driver started upstream around October 2021. After failing to get merged during Bobcat, we created this downstream repo as a stop-gap to help those wanting to use this driver now. https://specs.openstack.org/openstack/magnum-specs/specs/bobcat/clusterapi-driver.html ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/README.rst0000664000175000017500000000167015212012064015307 0ustar00zuulzuul=============================== magnum-capi-helm =============================== OpenStack Magnum driver using Helm to create k8s clusters with Cluster API. The driver uses `capi-helm-charts `_ to create the k8s resources needed to provision a k8s cluster using Cluster API, including various useful add-ons like a CNI and a monitoring stack. Note, the above Helm charts are intended to be a way to share a reference method to create K8s on OpenStack. The charts are not expected or intended to be specific to Magnum. The hope is they can also be used by ArgoCD, Flux or Azimuth to create k8s clusters on OpenStack. Work on this driver started upstream around October 2021. After failing to get merged during Bobcat, we created this downstream repo as a stop-gap to help those wanting to use this driver now. https://specs.openstack.org/openstack/magnum-specs/specs/bobcat/clusterapi-driver.html ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.6974328 magnum_capi_helm-1.4.0/devstack/0000775000175000017500000000000015212012201015411 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.6974328 magnum_capi_helm-1.4.0/devstack/lib/0000775000175000017500000000000015212012201016157 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/devstack/lib/magnum-capi-helm0000664000175000017500000000375315212012064021242 0ustar00zuulzuul#!/bin/bash # # lib/magnum-capi-helm # Functions to control the configuration and operation of the **magnum_capi_helm** driver # Dependencies: # # - ``functions`` file # - ``DEST``, ``DATA_DIR``, ``STACK_USER`` must be defined # - ``SERVICE_{TENANT_NAME|PASSWORD}`` must be defined # ``stack.sh`` calls the entry points in this order: # # - install_magnum_capi_helm # Save trace setting XTRACE=$(set +o | grep xtrace) set +o xtrace # Defaults # -------- # Set up default directories MAGNUM_CAPI_HELM_REPO=${MAGNUM_CAPI_HELM_REPO:-${GIT_BASE}/openstack/magnum-capi-helm.git} MAGNUM_CAPI_HELM_BRANCH=${MAGNUM_CAPI_HELM_BRANCH:-master} MAGNUM_CAPI_HELM_DIR=$DEST/magnum-capi-helm MAGNUM_CAPI_HELM_ADDON_PROVIDER_VERSION=${MAGNUM_CAPI_HELM_ADDON_PROVIDER_VERSION:-"0.12.0"} MAGNUM_CAPI_HELM_CAPI_JANITOR_OPENSTACK_VERSION=${MAGNUM_CAPI_HELM_CAPI_JANITOR_OPENSTACK_VERSION:-"0.13.0"} # install_magnum_capi_helm() - Collect source and prepare function install_magnum_capi_helm { setup_develop $MAGNUM_CAPI_HELM_DIR } # install_helm() - Collect helm CLI and install function install_helm { curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash } # install_azimuth_cluster_api_addon_provider() function install_azimuth_cluster_api_addon_provider { helm upgrade cluster-api-addon-provider cluster-api-addon-provider \ --install \ --repo https://azimuth-cloud.github.io/cluster-api-addon-provider \ --version $MAGNUM_CAPI_HELM_ADDON_PROVIDER_VERSION \ --namespace capi-addon-system \ --create-namespace \ --wait \ --timeout 10m } # install_azimuth_cluster_api_janitor_openstack() function install_azimuth_capi_janitor_openstack { helm upgrade cluster-api-janitor-openstack cluster-api-janitor-openstack \ --install \ --repo https://azimuth-cloud.github.io/cluster-api-janitor-openstack \ --version $MAGNUM_CAPI_HELM_CAPI_JANITOR_OPENSTACK_VERSION \ --namespace capi-janitor-system \ --create-namespace \ --wait \ --timeout 10m } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/devstack/plugin.sh0000664000175000017500000000163215212012064017254 0ustar00zuulzuul# magnum-capi-helm plugin.sh - Devstack extras script to install magnum # Save trace setting XTRACE=$(set +o | grep xtrace) set -o xtrace echo_summary "magnum's plugin.sh was called..." source $DEST/magnum-capi-helm/devstack/lib/magnum-capi-helm (set -o posix; set) if is_service_enabled magnum-api magnum-cond; then if [[ "$1" == "stack" && "$2" == "install" ]]; then echo_summary "Installing helm binary" install_helm echo_summary "Installing magnum-capi-helm" install_magnum_capi_helm elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then if is_service_enabled k8s-capi; then echo_summary "Installing Azimuth CAPI addon provider" install_azimuth_cluster_api_addon_provider echo_summary "Installing Azimuth CAPI janitor OpenStack" install_azimuth_capi_janitor_openstack fi fi fi # Restore xtrace $XTRACE ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781011584.698433 magnum_capi_helm-1.4.0/doc/0000775000175000017500000000000015212012201014352 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/requirements.txt0000664000175000017500000000064115212012064017646 0ustar00zuulzuul# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. doc8>=0.6.0 # Apache-2.0 openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD # needed for apidoc support xattr>=0.9.2;sys_platform!='win32' # MIT oslo.config>=8.1.0 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781011584.698433 magnum_capi_helm-1.4.0/doc/source/0000775000175000017500000000000015212012201015652 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781011584.698433 magnum_capi_helm-1.4.0/doc/source/_static/0000775000175000017500000000000015212012201017300 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/source/_static/capi_helm.conf.sample0000664000175000017500000001513315212012064023362 0ustar00zuulzuul[DEFAULT] [capi_helm] # # From capi_helm # # Path to a kubeconfig file for a management cluster,for use in the Cluster API # driver. Defaults to the environment variable KUBECONFIG, or if not defined # ~/.kube/config (string value) #kubeconfig_file = # Resources for each openstack cluster are created in a separate namespace # within the CAPI Management cluster specified by the configuration: # [capi_helm]/kubeconfig_file You should modify this prefix when two magnum # deployments want to share a single CAPI management cluster. (string value) #namespace_prefix = magnum # Reference to the helm chart repository for the cluster API driver. Note that # if helm_chart_name starts with oci:// you will want this to set this to the # empty string. (string value) #helm_chart_repo = https://azimuth-cloud.github.io/capi-helm-charts # Name of the helm chart to use from the repo specified by the config: # capi_driver.helm_chart_repo (string value) #helm_chart_name = openstack-cluster # Version of the helm chart specified by the config: capi_driver.helm_chart_repo # and capi_driver.helm_chart_name. A cluster label can override this. (string # value) #default_helm_chart_version = 0.10.1 # Minimum RAM for flavor used to create a Kubernetes node. (integer value) #minimum_flavor_ram = 2048 # Minimum VCPUS for flavor used to create a Kubernetes node. (integer value) #minimum_flavor_vcpus = 2 # Default StorageClass volume type for persistent volumes. (string value) #csi_cinder_default_volume_type = # Select the Nodes where the application Pods may be scheduled based on Node # labels. (list value) #csi_cinder_allowed_topologies = # The value to use in the interface field of generated application credentials. # (string value) #app_cred_interface_type = public # # # Dictionary of cluster api resources to modify api_version # and plural names in string format. # # # Example:: # # '{ # "K8sControlPlane": { # "api_version": "controlplane.cluster.x-k8s.io/v1beta1", # "plural_name": "kubeadmcontrolplanes" # }, # "OpenstackCluster": { # "api_version": # "infrastructure.cluster.x-k8s.io/v1beta1", # }, # }' # # # (string value) #api_resources = {} # List of conditions to check for kubernetes control plane resource to consider # as ready. (list value) #k8s_control_plane_resource_conditions = MachinesReady,Ready,EtcdClusterHealthy,ControlPlaneComponentsHealthy [capi_helm_cluster_labels] # # From capi_helm_cluster_labels # # Size of the etcd block device in GB. 0 means use the default ephemeral disk. # (integer value) #etcd_blockdevice_size = 0 # Storage type for the etcd block device. 'volume' uses a Cinder volume; 'local' # uses an ephemeral disk. (string value) # Possible values: # volume - # local - #etcd_blockdevice_type = volume # Cinder volume type for the etcd block device. (string value) #etcd_blockdevice_volume_type = # Availability zone for the etcd Cinder volume. (string value) #etcd_blockdevice_volume_az = # Deprecated: use etcd_blockdevice_size instead. (integer value) # Deprecated group/name - [capi_helm]/etcd_volume_size #etcd_volume_size = 0 # Deprecated: use etcd_blockdevice_volume_type instead. (string value) # Deprecated group/name - [capi_helm]/etcd_volume_type #etcd_volume_type = # Enable the monitoring addon on the cluster. (boolean value) #monitoring_enabled = false # Enable the Kubernetes Dashboard addon. (boolean value) #kube_dashboard_enabled = true # Enable auto-healing for cluster nodes. (boolean value) #auto_healing_enabled = true # Enable the cluster autoscaler. (boolean value) #auto_scaling_enabled = false # Minimum node count for autoscaling. Defaults to the nodegroup node_count when # unset. (integer value) #min_node_count = # Maximum node count for autoscaling. Defaults to the nodegroup node_count when # unset. (integer value) #max_node_count = # Enable the Keystone authentication webhook. (boolean value) #keystone_auth_enabled = false # Default availability zone for Cinder volumes. (string value) # Deprecated group/name - [capi_helm]/csi_cinder_availability_zone #csi_cinder_availability_zone = nova # Reclaim policy for dynamically provisioned persistent volumes. Can be 'Retain' # or 'Delete'. (string value) # Possible values: # Retain - # Delete - # Deprecated group/name - [capi_helm]/csi_cinder_reclaim_policy #csi_cinder_reclaim_policy = Retain # Controls when volume binding and dynamic provisioning occur. Can be # 'WaitForFirstConsumer' or 'Immediate'. (string value) # Possible values: # WaitForFirstConsumer - # Immediate - # Deprecated group/name - [capi_helm]/csi_cinder_volume_binding_mode #csi_cinder_volume_binding_mode = WaitForFirstConsumer # Filesystem type for persistent volumes. (string value) # Deprecated group/name - [capi_helm]/csi_cinder_fstype #csi_cinder_fstype = ext4 # Allow users to resize persistent volumes by editing the PVC. (boolean value) # Deprecated group/name - [capi_helm]/csi_cinder_allow_volume_expansion #csi_cinder_allow_volume_expansion = true # Octavia load balancer provider (e.g. 'amphora' or 'ovn'). (string value) #octavia_provider = amphora # Load balancer algorithm. When unset, defaults to SOURCE_IP_PORT for the ovn # provider and ROUND_ROBIN for all others. (string value) #octavia_lb_algorithm = # Enable health checks on the load balancer. (boolean value) #octavia_lb_healthcheck = true # Associate a floating IP with the API load balancer. (boolean value) #master_lb_floating_ip_enabled = true # CIDR for the node network when no fixed_subnet is specified. (string value) #fixed_subnet_cidr = 10.0.0.0/24 # Comma-separated list of CIDRs allowed to reach the API load balancer. Empty # means all CIDRs are allowed. (string value) #api_master_lb_allowed_cidrs = # Name of an additional Neutron network to attach to each node. (string value) #extra_network_name = # Root volume type. Falls back to the cinder config option # default_boot_volume_type when unset. (string value) #boot_volume_type = # Root volume size in GB. Falls back to the cinder config option # default_boot_volume_size when unset. (integer value) #boot_volume_size = # Helm chart version to use. Can only be set via the cluster template label, not # overridden per cluster. Falls back to capi_helm.default_helm_chart_version # when unset. (string value) #capi_helm_chart_version = ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/source/conf.py0000664000175000017500000000700015212012064017155 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys sys.path.insert(0, os.path.abspath("../..")) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "sphinx.ext.autodoc", "openstackdocstheme", "oslo_config.sphinxext", "oslo_config.sphinxconfiggen", ] # openstackdocstheme options openstackdocs_repo_name = "openstack/magnum-capi-helm" openstackdocs_pdf_link = True openstackdocs_use_storyboard = False # sphinxcontrib.apidoc options config_generator_config_file = [ ("../../etc/oslo-config-generator/capi_helm.conf", "_static/capi_helm"), ] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = ".rst" # The master toctree document. master_doc = "index" # General information about the project. project = "magnum-capi-helm" copyright = "2013-present, OpenStack Foundation" current_release = "2023.1" # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = "native" # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] html_theme = "openstackdocs" # html_static_path = ['static'] # Output file base name for HTML help builder. htmlhelp_basename = "%sdoc" % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ( "index", "doc-%s.tex" % project, "%s Documentation" % project, "OpenStack Foundation", "manual", ), ] # If false, no module index is generated. latex_domain_indices = False latex_elements = { "makeindex": "", "printindex": "", "preamble": r"\setcounter{tocdepth}{3}", "maxlistdepth": 10, } # Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 latex_use_xindy = False # Example configuration for intersphinx: refer to the Python standard library. # intersphinx_mapping = {'http://docs.python.org/': None} # openstackdocstheme options openstackdocs_repo_name = "openstack/magnum-capi-helm" openstackdocs_pdf_link = True openstackdocs_bug_project = "magnum" openstackdocs_bug_tag = "magnum-capi-helm" openstackdocs_auto_name = False openstackdocs_projects = [ "magnum", ] # Substitutions loader rst_prolog = """ .. |current_release| replace:: {current_release} """.format( # noqa: E501 current_release=current_release, ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.6994328 magnum_capi_helm-1.4.0/doc/source/configuration/0000775000175000017500000000000015212012201020521 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/source/configuration/config-reference.rst0000664000175000017500000000072715212012064024471 0ustar00zuulzuul======================= Configuration reference ======================= The ``[capi_helm]`` section controls driver-level settings (Helm chart location, flavour constraints, etc.). The ``[capi_helm_cluster_labels]`` section controls the **default values** for all supported cluster labels. Every option here can be overridden per-cluster by the user via Magnum cluster (or template) labels. .. show-options:: :config-file: etc/oslo-config-generator/capi_helm.conf ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/source/configuration/index.rst0000664000175000017500000000310515212012064022370 0ustar00zuulzuul=================== Configuration Guide =================== For complete reference of configuration options for this driver used in magnum.conf plesese refer to: .. toctree:: :maxdepth: 1 Configuration Reference Features ======== The driver currently supports create, delete and upgrade operations as well as updates to node groups and their sizes. The Kubernetes versions against which the CAPI Helm charts are currently being tested can be found `here `__. The driver supports cluster labels to customise cluster behaviour. The full list of supported labels, their types, defaults, and descriptions is maintained in the :doc:`Configuration Reference ` under the ``[capi_helm_cluster_labels]`` section. Default values for all labels can be set operator-wide in ``magnum.conf`` under ``[capi_helm_cluster_labels]`` and overridden per cluster or template by the user via Magnum labels. .. note:: ``capi_helm_chart_version`` can only be set via a cluster template label and cannot be overridden per cluster. When unset it falls back to ``[capi_helm] default_helm_chart_version``. Tip & Tricks ============= Currently, all clusters use the Calico CNI. While Cilium is also supported in the Helm charts, it is not currently regularly tested. We have found that cluster upgrades with ClusterAPI don't work well without using a load balancer, even with a single node control plane, so we currently ignore the "master-lb-enabled" flag. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.6994328 magnum_capi_helm-1.4.0/doc/source/contributor/0000775000175000017500000000000015212012201020224 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/source/contributor/index.rst0000664000175000017500000000072615212012064022101 0ustar00zuulzuul================= Contributor Guide ================= DevStack Setup ============== Did you want to try this driver in DevStack? Please try the setup script which is included in the repo `here `_. The above DevStack script will also install k3s on the host and will install the required components on the k3s cluster for it to act as a Cluster API management cluster. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/source/index.rst0000664000175000017500000000446615212012064017534 0ustar00zuulzuul.. Copyright 2014-2015 OpenStack Foundation All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ======================================================= Welcome to the Magnum CAPI Helm Driver's documentation! ======================================================= Magnum CAPI Helm is an OpenStack Magnum driver which uses Helm to create Kubernetes (k8s) clusters with Cluster API. The driver uses a standard set of Helm charts to create the k8s resources required to provision and manage a k8s cluster using Cluster API, including various useful add-ons like a CNI and a monitoring stack. These Helm charts currently live at https://github.com/azimuth-cloud/capi-helm-charts. The Helm charts are intended to be a way to share a reference method to create k8s clusters on OpenStack. The charts are not expected or intended to be specific to Magnum. The hope is they can also be used by ArgoCD, Flux or Azimuth to create clusters outside of Magnum if desired. * **Free software:** under the `Apache license `_ * **Source:** https://opendev.org/openstack/magnum-capi-helm * **Blueprints:** https://blueprints.launchpad.net/magnum * **Bugs: (use magnum-capi-helm tag)** https://bugs.launchpad.net/magnum * **Magnum Source:** https://opendev.org/openstack/magnum * **Magnum REST Client:** https://opendev.org/openstack/python-magnumclient User Documentation ------------------ .. toctree:: :maxdepth: 2 Understanding Concepts Installation Guide ------------------ .. toctree:: :maxdepth: 2 Installation Guide Configuration Reference ----------------------- .. toctree:: :maxdepth: 2 configuration/index Contributor Guide ----------------- .. toctree:: :maxdepth: 2 contributor/index ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.6994328 magnum_capi_helm-1.4.0/doc/source/install/0000775000175000017500000000000015212012201017320 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/source/install/index.rst0000664000175000017500000000757615212012064021207 0ustar00zuulzuul================== Installation Guide ================== For a Kayobe-based deployment, you can follow `this `__ guide. The relevant sub-sections of the same guide can also be adapted for Kolla-Ansible-based deployments. If you install this Python package within your Magnum virtual environment, it should be picked up by Magnum:: git clone https://opendev.org/openstack/magnum-capi-helm.git cd magnum-capi-helm pip install -e . We currently run the unit tests against the 2023.1 version of Magnum. The driver requires access to a Cluster API management cluster. For more information, please see: https://cluster-api.sigs.k8s.io/user/quick-start To access the above Cluster API management cluster, you need to add Magnum configuration to tell the driver where the management cluster's kubeconfig file lives:: [capi_helm] kubeconfig_file = /etc/magnum/kubeconfig .. note:: We currently support two authentication methods to access the Cluster API management cluster: - Certificate-Based Authentication: This method uses a client certificate and private key embedded in the kubeconfig to authenticate with the Kubernetes API server. It is the default method used by Cluster API when generating the kubeconfig Secret for workload clusters. - Token-Based Authentication: A Kubernetes ServiceAccount is bound to a ClusterRole, and its associated token is used to authenticate to the cluster. Tokens generated via Kubernetes service accounts are long-lived and, by default, do not expire unless explicitly configured with an expiration time. They are thus useful where persistent and uninterrupted access to the cluster is required unlike client certificate-based kubeconfigs, with a one-year validity and periodic manual regeneration. Once the driver installation is complete, to create a cluster you first need an image that has been built to include Kubernetes. There are community-maintained packer build pipelines here: https://image-builder.sigs.k8s.io/capi/capi.html Alternatively, you can grab pre-built images from StackHPC's `Azimuth image releases `__. Images are available in the `manifest.json` file and are named in the format `ubuntu---`. Since Magnum distinguishes which driver to use based on the properties of the images used in the cluster template, the above image needs to have the correct os-distro property set when uploaded to Glance. For example:: curl -fo ubuntu.qcow 'https://object.arcus.openstack.hpc.cam.ac.uk/azimuth-images/ubuntu-jammy-kube-v1.28.3-231030-1102.qcow2?AWSAccessKeyId=c5bd0fa15bae4e08b305a52aac97c3a6&Expires=1730200795&Signature=gs9Fk7y06cpViQHP04TmHDtmkWE%3D' openstack image create ubuntu-jammy-kube-v1.28.3 \ --file ubuntu.qcow2 \ --disk-format qcow2 \ --container-format bare \ --public openstack image set ubuntu-jammy-kube-v1.28.3 --os-distro ubuntu --os-version 22.04 After uploading a suitable image, you can now create a Magnum cluster template and then a cluster based on this template:: openstack coe cluster template create new_driver \ --coe kubernetes \ --label octavia_provider=ovn \ --image $(openstack image show ubuntu-jammy-kube-v1.28.3 -c id -f value) \ --external-network public \ --master-flavor ds2G20 \ --flavor ds2G20 \ --public \ --master-lb-enabled openstack coe cluster create test-cluster \ --cluster-template new_driver \ --master-count 1 \ --node-count 2 openstack coe cluster list Once the cluster has been created, you can get the cluster's kubeconfig file and (optionally) run Sonoboy to test the created cluster:: openstack coe cluster config test-cluster export KUBECONFIG=~/config kubectl get nodes sonobuoy run --mode quick --wait ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7014327 magnum_capi_helm-1.4.0/doc/source/user_docs/0000775000175000017500000000000015212012201017640 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/source/user_docs/capi-architecture-diagram.png0000664000175000017500000101273615212012064025365 0ustar00zuulzuul‰PNG  IHDR¦5°yª¾sRGB®ÎéDeXIfMM*‡i  ¦ 5¨Ý�6@IDATxìÝ€EºÀñ�%,$ç QD<PDPÞ�Ê)*"‚èpœg@Å“ÓÁt*ð  ¨§ '" ‚ŠŠJ„%çœóòækè¡ÓÌÎîÎÌNø×{sÝ]]]]õë^Àý¦ªòœõ%!!€ € € € € € € !�”ÕKµ € € € € € € €¦y@@@@@@@"*@`:¢¼TŽ € € € € € € @`šw@@@@@@@ ¢¦#ÊKå € € € € € € €¦y@@@@@@@"*@`:¢¼TŽ € € € € € € @`šw@@@@@@@ ¢¦#ÊKå € € € € € € €¦y@@@@@@@"*@`:¢¼TŽ € € € € € € @`šw@@@@@@@ ¢¦#ÊKå € € € € € € €¦y@@@@@@@"*@`:¢¼TŽ € € € € € € @`šw@@@@@@@ ¢¦#ÊKå € € € € € € €¦y@@@@@@@"*@`:¢¼TŽ € € € € € € @`šw@@@@@@@ ¢¦#ÊKå € € € € € € €¦y@@@@@@@"*@`:¢¼TŽ € € € € € € @`šw@@@@@@@ ¢¦#ÊKå € € € € € € €¦y@@@@@@@"*@`:¢¼TŽ € € € € € € �@@�Øذaƒ<ú裶Föïß_ZµjeËã@@@@ˆUÓ±údh €@BlÚ´I-Z$»wï6>ÇŽ“’%KJéÒ¥¥zõêÒ²eKIMMMˆ¾Ò‰È ìÝ»W¾ÿþ{Û ¶lÙb;æ@@@@ˆeÓ±üth €@\ ?~\Þ~ûmùè£�$===h ,(mÛ¶•H�5‚–åd`�iӦɞ={üªV­*mÚ´ñ³ƒ € € € € �»¦sן»#€$˜À_|!O?ý´lß¾=¤žiû¿ÿý¯Ìš5K(wß}wH×QÈ.0lØ0Ù¸q£?óÊ+¯$0í×`@@@@È}�”Üo-@@ 1Þÿ}¹ï¾ûBJ[{}êÔ)yöÙgeúôéÖlö@@@@@HFL'Äc¤ €¹-ðé§Ÿ#žµ£B… Ò¨Q#)S¦ŒüüóϲbÅ ÉÈȰ?{ö¬¬[·Î–Ç € € € € €‰ @`:ž"}@ÈU�mÛ¶Ów{5â÷¿ÿ½ }üÇì € € € € € �(¦åIÒ@�\xþùçåàÁƒ¶ûçÍ›Wüq¹ÿþû%Ož<¶szP¬X1yî¹ç䢋.’_|Qzè!0`€« € € € € €$‚�éDxŠô@ ×víÚ%Ó¦MsÝ_׋îÑ£‡+ß™ñ§?ýIÚ·o/uëÖuž2Ž·lÙ"G�õ¬Y³F–-[&¬×�^_ @)Z´¨T®\YÊ—/ŸI‹ì§ÓÓÓEÛhMGޱÊ�ä·ß~³åyÔ®]Ûó‹^eÃíb½‡¶wéÒ¥òË/¿ÈÞ½{�öõêÕ“–-[fÙÛZ/û € € € € €@¬dí7˱ÒjÚ� €@Œ|ðÁrúôi[kªU«&ݺu³å;”Ök:wî,[·nõ¼¼F�2wî\ãܤI“DGnïß¿ßU6--Í•}Ýu×¹ÎY34¸;hÐ ™:uªœ8qÂzÊ¿¯Aå{î¹G{ì1#¸ì?dgÊ”)òÚk¯,¡�é&MšH›6mäöÛo7‚×� >|XÚ¶mè´?ùòåÒ®];ÿq õSÇ`)R.zO]S¼oß¾FàÞ« Õ«W—aƉ~ �„ € € € €ij@J<7ž¶#€ �Û ,p5á/ùKÐàªë‚ gÏž xvóæÍ’‘‘!3fÌ0¦÷ JëÅ:’ZƒÉŸ~úiÀº–,Y":t��>ú(`PZ/Ö ü˜1c¤cÇŽ²bÅŠ€õYOèÜÁÒöíÛå¿ÿý¯Ñ=þÕW_,Ì#àEANdV_$]æÍ›'7ÝtSÀ ´6{ýúõÒ»woÙ¸qc�^p @@@@ˆ}Ó±ÿŒh! €@Œ hPø§Ÿ~²µ®bÅŠF°Ñ–¡ƒS§NKåJÒQË^�X ®ëÈì¬?uŠï®]»zŽÐv¶EÏ¡&�~»gÏžòõ×_‡zIÄÊEÒåÛo¿•^½z¹Ö&÷êÌ¡C‡ä½÷Þó:E € € € € 7Lå7�І"€Äš€rujikÒ5‹SRÂ÷½/�~{ÇŽÆ-tzmekM3gÎ3ð[¨P!¹üòËå’K.‘µk×ʬY³¬E�5—5à{õÕWûó5P=xð`×täZ@דÖ�àuä®®ílMº.²»ÿú׿Z³]ûeÊ”‘ ÈÉ“'ý ¶:×…6/Ô6é¨óÏ?ÿ\trkJMM5Fv;טž3gŽmÝé"EŠÈ•W^i½Ôs¿D‰žù‘vyõÕWmíÕFè{sÙe—ÏP­V¯^- .4¾L°hÑ"Ïv’‰ € € € €Ä‹�éxyR´@ ætŠlgªR¥Š3+GÇC† ñ_¯Á\ :[ÓôéÓ�C½¯NÃ]¡Bÿi½V§Ý¶¦•+WÚÓºžô²eˬE¤téÒ2tèP#l=¡ehå;nÜ8c„sÕªU­Emû¯¼òŠíØ<ЩÇ7lØ`¬i­m×@·™4¯í9r¤™el ( £G�¶åé�Û­#¾7n,ÿ÷ÿç*jF$]~üñGùî»ïlMѵ»_zé%¹ùæ›mù:²ú‘G ¸Î¸­0 € € € € €@ „oHW w’¦!€  k Õ¬?Ü�i³Þ@[�J¼T©R2iÒ$[PZËß{ï½®ËÌÑ׿ gàW¤'Nt¥µ¼®‡(O<ñ„-ϳ­3@Ÿ“µ¾#éR±bEWWÖ¯_ïÊ#@@@@H4¦òN´'J@¢*Ð¥K×TÔÇ�—çŸ^r2j7�Ð iÁ‚m·?~¼lÛ¶Í–—�¯µ¶Õ*Xš;wn°ÓAÏU«VÍv^ûpøða[^¨‘tÑ©ËK•*ekÊ”)SäÔ©S¶<çÁ¦M›œY#€ € € € €q%@`:®�Eˆ5�2eÊÈõ×_ïjÖäÉ“å¶Ûn“X «Óh·k×ÎÖö�;wJ·nÝd÷îݶü¬xå5`(i yâĉ�Ngš_½zu[™ŒŒ 9r¤-/ÔƒHºh5jdkÊž={äã�?¶å9f̘áÌâ@@@@ˆ+Óqõ¸h, €@, <õÔSR¼xqWÓ–,Yb­ÇŽ+Ë–-]Ú™¶oß.ãÆ�]ÛyÚ´iÎÓ?4h�+VÌv�‚»mÛ¶2lذ€£§u„ïêÕ«%ДÙuëÖµÕ©:ŠÜkdðºuëŒ þÆ�]×lݺUöïßïÊwfÔ¨QÙe¸êÔá^£·5p®ukPØ+EÊEï¥Sš;ÓÀ�eÒ¤IÎlÙµk—<óÌ3¹òn¸C € € € € ��tèP×µ:b¹Aƒ®`¼¶õÆo�ÒZƒ®¼_³f�ß fÍš®µ©µr]CúŠ+®�wß}×u/ÍPO ¦k�^I¯×õ³5iYu×ö÷îÝ[ž~úi¯K$.æ�t4ýâÅ‹ÍCÿVGàk�]§ûÞ±c‡,]ºTNŸ>-úî˜ï‰Yø…^�;î¸ÃüðCiÒ¤I¶š©A[g�9[eñ"�;zôhãS±bÅ,]¬½}úô‘k¯½6¤ú´ �Ö‘ÙÙM7Ýt“ôêÕË]j�¾$ ×GÊEënÙ²¥1u·Ž�Î,•-[ÖI¯£ãI € € € € €@¼ ˜Ž×'G»@bRàòË/—)S¦ëë”ÚTÌ,Õ©SGî»ï>™8q¢1Í·Y^Gg'e÷º:Èœ9sŒQÇ�52¦ÏÎìþ^kF›×h`WGG�5Jj×®mf»¶Í›7—ñãÇÓR.\Øu>+Ï>û¬Lž]´þ´´43Û¶U]ƒ\§!/_¾¼í € € € € €@< °Æt<=-ÚŠ —:ªxçÎ�²oß>cºï“'OŠŽ”Õ�®u çXM:­tzzº±®ôáÇEGíjðX¥U«V5öCm»�uêl�j\§úÖ`¬®¹m¥mNÙ�šš*úÑ »u«×…št�ëU«V‰N‰®ëJk=H×ö«¹Þ[×¼ÎJ�æ½ÃébÖ©í\±b…ñŽ-ZÔXkÚ:‚\Ÿƒûu½lýh?ô˜„ € € € €ă�éxxJ´@@@@@@ˆc�ЇÅq'i: € € € € € € €@î ˜Î={îŒ € € € € € €$…�é¤xÌt@@@@@@È=Ó¹gÏ�@@@@@@@�¤ 0��™N"€ € € € € € €¹'@`:÷ì¹3 € € € € € € �¦“â1ÓI@@@@@@@ ÷Lçž=wF@@@@@@’B€ÀtRùD&NœÒ対úªT¬X1¤²B@@@@�ä 0�Ï™^"€8räˆôë×Oöïßï/±téR4h�Œ3ÆŸÇNøöíÛ'[¶lqUX°`A©]»¶+?™2Âñ>.X°@´3•/_ÞÜÍÑ6==]¾ÿþû�êП'Ó!QQ@@@@�¤ 0�4�šŽ"€x üú믶 ´Yæ»ï¾3wÙ†Y`Ô¨Qƈtgµ… –ŸþY (à<•4Ǽ�Ió¨é( € € € €I'ÀÓI÷Èé0 €€U 55Õzèß”ï/ÀN¶¾üòKÏk�=*Éþ…€@ï] |OÈe¦¤ðÏÆÑR- € € € €I!Ào“â1ÓI@�@õêÕ“Zµj¹Nßpà ®<2r.°iÓ&Y³fMÀŠ­^�`'bù}ìÓ§�1•·Nçmý<üðà öè € € € €DB€Àt$T©@ nòåË'ï¼óŽ\uÕUR¤H)Q¢„ôèÑCž|òɸéC<54³Àsfç㩯Ùik,¿�:j»\¹r®OZZZvºÊ5 € € € € �d¬1�dœî"€¸ªU«&&L�³gÏJžyú� € € € €@b 0b:1Ÿ+½BÂ. £‰�kWªTI .ð^ûöí“Ý»wûÏçÏŸ?K#aO�:%¿þú«ìÝ»W´.ýèþ±cǤX±bR²dIÛç’K.‘¼yóúïçÜÑõ��?îÌö<®\¹²*TÈóœ3sãÆ�¢AW3Y]ÔìÇ”mÛ¶ÉÑ£G¥fÍšF ¶nݺAí̺¼¶�¥K—Ê/¿übxèôʺ6qË–-E§‚Ö´aÃ9yò¤ÿò¬ôÇQ˜w¾ûî;ãÙ™ÕêÔÐÍš5“«¯¾ZV¯^mf‹ŽªîÕ«—ÿ8ÐN´Ýãå} ä¥ù‹-’•+Wï£þì6hÐÀøT¨P!Øe¹zîÌ™3Æû¬í^µj•Ñ–‹/¾XôgHžÌw>WÉÍ@@@@@ SÓ™Q@@4@Ü¡CÆßÿþwùãÿh˳ >\&NœèÏÒ :Ms°túôi™?¾|öÙg2sæL9xð`°â¶s¸ (¿ñÆEƒº¡¤#FH—.]B)j¸9rÄ_vôèÑÒªU+ùóŸÿ,³fÍ2¦÷Ÿ<¿Sºtiyþùç¥}ûöÎS�ušë¾}ûʲeË<Ëèôׯ 3Ô]»v•­[·úËe¥?þ‹Â¼ãœÆûŠ+® Nk`ú­·ÞòßMGUë—2ûb€¾�‘v�Ç÷ÑiÙÙ¾}»<øàƒÆ—$,Ùþ]}Ÿ{î9騱£?/·wôÙ4H¦N�jû⇵]”¾çž{ä±Ç“ XO±� € € € €1&ÀTÞ1ö@h «:bÚ™¼ò¬eœç�ÇÖ²º¿e˹馛ŒÑ²ÿþ÷¿³”.Q¢DР´ÖŸÙýµŒ™²RÖ¼ÆÜîÚµKúõë'_|ñEÀ{îÙ³Gî»ï>2dˆyYÐí¼yó ›@Ai½Xƒþ½{÷6F—ZGªë¹œôG¯Gr¦5 ­©yóæ¶Q¯^S~‡rÿp»'Êû¨Ï¾OŸ>ƒÒj«ï£–ùë_ÿjiŠ{$Ê,Y²ÄøÂÇG}0(­÷Õ/Œ3ƨ¯X±"M¡N@@@@“�é0AR  €@Îtšä=zÈòå˳U‘NŸ+I§îž3gNHÍ;vlÀÐfß~û­¬eôø¡C‡dÔ¨Q1\4Û¯[í­�sk2Ó:ʽiÓ¦ÖSÆtÞ¶ŒÂéžHï£N'¿xñâEÆ�/ýû÷©l¤ éˆùÎ�;‹NÕjÒ)¾u–€ýû÷‡z å@@@@@ ÊLåepn‡ à-ðÁÈÚµk=O,XÐXCY×”.R¤ˆ1ý³Ž”Ô‘µˆÒu§6lèy­5óšk®1Ö©¶æé¾ŽÕiÀÕ>ÿüsUtÕõpÓÒÒŒ5º7oÞì?§;:šuðàÁ2iÒ$[¾õàÕW_]gךRRRä²Ë.“Ë/¿ÜBëÍ .4ê›6mšµhLì;GKëÔÑõë×÷·M§>×5¨Íä,oæÛ†Ó=‘ÞGë»­_àÐ/èšãºæº�u¤¹5MŸ>Ýø²„®Ùídþ<èÏ·3ézÒúÉÈÈ0¾ä�žžn+¢Óô¿öÚkƨoÛ @@@@@ &LÇÄc  €?ýô“ ¡^½zòì³Ï�4]K6§éŸÿü§g:mp§N�<Ïe'Ó\÷øŽ;î0ÖÈÕ`º& ¨éZʈ¶& È?~\4ïL: ذÕójñÒK/ÉÍ7ßl+®#«yäÛÚÒ¶¹xà 4k Z×7“Žž9r¤yhMu¤oíÚµýy™í„Ó=‘ÞGuSë{ï½×X‹Y×õ6“ŽÂ×5š-ZdfÛ_|QÞyç[^4t=içtõú%†¡C‡ºÖ¸×²´Mù?nÜ8éÙ³§T­Z5Íå € € € € �¦òÎE@"'`ÕiÞEc-Z´°­?lž‹õ­�_xác„·ÙVå¬k@wèÐÁÌ2¶:J4дÅo¾ù¦­¬<ýôÓ® ´æ_yå•òúë¯ënL¥£G�º‚똶¦F�‰®nMÎ`¶õ\ ýp¹'Úûø‡?üÁxo¬Ai5,^¼¸¼ÿþûR¡B©NE¯ÓcG;Y¿œ ÷Ö/aLœ8Ñõ3£çt=z�¶Þšt öÉ“'[³ØG@@@@� 0#‚f €É. Ór;“NO�IÐ?üpÀ¦·oßÞuιþ²Y@GL[“޾֑Ø�R“&M¤]»v�NçJ¾N}òäIÛ½Íõ¥Í̼yó�uóX·Y L‡Ó=ÑÞGI(éHýîÝ»»Nÿú믮¼Hfè”úÎ/hè9êÖ­ð¶­[·ç»ä¬#àÅœ@@@@@�¨ ˜Ž*77C$ ë0;Óý÷ß/úÓŸDGošÓ4;ËÄâqùòå¥F�›V¥J×¹}ûö¹òtôçîÝ»mù;v]·:XÒQæ±”œæZµj¹Fèj{�Æï¿ÿ>KÏ=\îÚ–DzË•+'Õ«W×nL�;wv�Ó5¨£™¼Ê^ír¶I׎·¦ 6XÙG@@@@�Èù‚�1Òš� ß\š>}º­ºîò§Ÿ~j|tJßK.¹D®¸â cd­_ *d++•+WÚ”P×ËÞºu«è4ßÖlô¨Y.³û›å¢µÕ/X“3mžsæk`þ›o¾‘ßÿþ÷f‘ ÛÌúª»Þ$™ÞGí¯Nå] @ÛÈöÍ›7멨%¯À´Ž¤4›€Ù0çψW=fY¶ € € € € �{¦sÏž;#€XºvíjLÝ € € € €Ä¡�é8|h4È  U«VÍvk fÝ{ï½¢k@›II}úô‘©S§šY¶­Ž¢Ö`˜3éôÜ_|ñ…¬^½ZŽ;æ<í?^³f�üãÿ�çž{ΟgîxMlž‹×íƒ>èjúÀ�eÒ¤I®|]{û™gž‘iÓ¦¹ÎE;CGÌ;§roÕªUHÍhÚ´©8§‡v¹Cª(…’á}ܸq£téÒEÆ�g“Ê—/ŸŒ1Bôg>”ä˜þæ›oäÙgŸkÀþÔ©S¢Óí¿ÿþû2kÖ,Ϫ $ÅŠ³�Ó)ûÛ¶m+Æ “mÛ¶ÙΙZ·þÙ±yóf3‹- € € € €ĘSyÇØ¡9 €@, ´oß^V­Zekâ‚ ¤M›6R¦Lc„¤Žè5S•*U\ÓJ·nÝÚ˜†ZGÏê4½fÒ “–̤Á)ýèôÏ)))rèÐ!cúnk Ë,«Û¼yóÊW\aÍòïë´ß�:u 8‚Sëv& |�5Ê™m¬aýâ‹/ºò#•ѲeKÑ@íâÅ‹ý·ÐÄ �‘#Gkoëtß^ºtiXÖtöß(;^�䫯¾:¤ušrY=wî\yÝ×õÅC –ú/ÌæN"½� .4¾,R¤HãK!¼Õiø�£¤Mª¾}ûJýúõÍÃL·úszÕUW‰£­éÝwß• &ˆNÑ] @ã ,@ÖÔ¡CcJ~kyÝ/[¶¬<õÔSÆtõÖs:•»Î Ÿ%J£¹uj÷£G�.èÔáú…—îÝ»#À­×²� € € € €±!@`:6ž­@âB@GïêڿΑ°Úx­kM<êÚµ«1òÒš¯ûœr®ƒìé¨Áb¯€±³.óX§û­Y³¦yhÛêšµÖQݶ“tŠr¯iÊuJóh§G}Tîºë.WÐYÍ�îÚ6µ?pà@´›i»Ÿ30­ÁÉ@ÏÇváù]m Lk?—-[&Ωͽ® G^"½�úóúÙgŸ…Ä¢#“ûõëRYk!�%Á˜Öó'Ož4‚àÖ²ºïüù·ž¿óÎ;嫯¾’@SÒë»èý¶î½õì#€ € € € €@ôR¢Kîˆ ¯º†óðáÃ]Sí:û££—_~ùe 5ˆ»wï^c䣳žPŽu­muÚàDM:jZ§îÖQé™%qúä“OºŠEsým þøã�¶6„:�·y‘×èjg°Û,îm2¾�úÅ�Ñ£G‹ŽrÖ©¼³štÄ´smð`u LëÏ´¶E?Ú®¬$]*€„ € € € €±)@`:6Ÿ ­BbV@GTΜ9Ó˜¾[§Øv&;vìX¹îºë¤dÉ’ÎÓžÇÈ %èj½øâ‹/–Ç{L¾þúk2dˆ1•·õ¼u_§†öj«µL¨û:%±39óœÇÎò©©©Î,cªcW¦%£Y³f2}útc ä´´4Ë™ »:ýµ®œ‘‘q!óüžN«­¤ëK;×÷ 4kOݺu墋.²Ñõ‰­Ééì<¶–ÕýPÝí}t:˜Ç:öe—]&ýû÷—9sæï–y.;ÛW^yE^zé%)]ºtÐË5جSùëÔìÁ’N÷­íÒÜ:R^§"Ï,™S…gVŽó € € € € }�<¾_ ÿ­`ôÛÄ@âD@§È^³f�l۶͘fºZµj¢E1­I׃ÖŒÔ�­[g°XËë”Û;wî”'NÓk IRz­®¥¬A/ý„¤ŠÆl5S×Ô]¾|¹1Ýy¹r匵¦+UªdÔ¥ë#ëZ¼Ö´dÉÃÏšÇ~p�x}õçR×zבßûöí=Ö�þLéÏfÕªU�÷%³rp�àguêu]3^ƒüú3¬ÓËk \ÿŒÈÉ}µÞôôtc]i�j¿`Á‚¢³T¨PÁèW4g.ÀY@@@@p ˜vŠpŒ €@ è÷Ín¸áÑQËfÒ ¾õØÌg‹ € € € € €@´Üs°FëÎÜ@Â.ðÙgŸ¹‚Ð:B–„ € € € € €@n ˜ÎM}î� €@f̘!Ï<óŒ«Æ[o½Õ•G € € € € €Ñ`*ïhjs/@ �‘‘!ÇŽ]cW×ÛÕµ¦¿ýö[™={¶«v-=gÎÉŸ?¿ë € € € € €DK _´nÄ}@@ {={ö4ÐGŽ‘£G�ÊñãÇE×’%=õÔS¥C�¢  € € € € €@DLG”—Ê@ȹÀÖ­[eãÆ�Yª(_¾|rÿý÷Ëõ×_Ÿ¥ë(Œ € € € € €@$LGB•:@£@Ñ¢E³T[Ó¦MeèСR·nÝ,]Ga@@@@@"%@`:R²Ô‹ €@˜Š+°¦””Ñu¤5]§Niܸ±´k×NòäÉðN € € € € € m�<¾5*C[¤2Ú-ã~ € `üðògÏIMMµ} *$*T�‚ "… € € € € €@L ˜ŽéÇCã@@@@@@@�øH‰ÿ.Ð@@@@@@@�X 0ËO‡¶!€ € € € € € € @`:"]@@@@@@@bY€Àt,?Ú† € € € € € €$€�éxˆt@@@@@@ˆeÓ±üth € € € € € € �¦à!Ò@H|�S§NɆ d×®]ræÌ™Äï0=D@@@@�„È—P½¡3 €„I@€�>ú¨­¶þýûK«V­ly )�­[·ÊøñãeÍš5²víZÙ¸q£œ>}Ú¸]JJФ¥¥I™2ed̘1R­ZµH5ƒz@@@@@�°˜ #• €$šÀÞ½{åûï¿·ukË–-¶ãHlß¾]vïÞ¶Ûh0³Aƒa«�Š"#pöìY™0a‚ 2DŽ9ây“ŒŒ Ù³g�ñ)Z´¨g2@@@@@�X 0KOƒ¶ € `xöÙgeúôé–œœï.\¸PÊ—/ŸóЍ!"'Nœ�^½zÉ·ß~Rý©©©Rºté�ÊR@@@@ÈMÖ˜ÎM}î� €XÞ}÷Ý�ƒÒzY¥J•,W³‹ € € € €Ä®#¦c÷ÙÐ2@H"�ýû÷Ë?ÿùOW�uªn]ßüÒK/•‹/¾X ( ‡’�;wŠNéMB@@@@âA€Àt<<%Úˆ �”… –üùóìû©S§\ç‚•×5¦ƒ�wUFFT4(}ðàAÛ=ëÕ«'cÇŽ•ªU«Úò‹+&+V´åq€ € € € €IJ�éX~:´ @ ©^|ñEÑO ôÆoȰaÃü§Ë”)#‹/ö³_³gÏv5xðàÁ® ´« € € € € €@°Æt<$šˆ €‰-pöìYÙ¼y³­“-Z´�fÍšÙò8@@@@@âU€Óñúäh7 �KkÖ¬‘eË–IÞ¼y�O¾|ùŒ5ouÜÊ•+Kùòå³Õ²�7ʉ'ü×VªTIt*kMzÏüQ¶mÛ&G�•š5kJ�:u¤nݺþ2þ CÜ9pà€,]ºT~ùåÙ»w¯”+WNtÚä–-[Šö)™Ó™3gdÆ ²råJYµj•A¡k«·Ú‡ê³eËãyyY,XPªT©â?µoß>c´÷ºuëd×®]Æ9}Æ 6�¶Ú™"Y·y¯H½ëfýÖíŽ;ääÉ“Ö,ã]´eäà@ëþí·ßdÅŠÆÏS©R¥¤~ýúÒ A¹è¢‹B®9’îúìOŸ>ío‹¾kú猶yîܹÆþ�7Þhü9cúòË/�ŸácÇŽïæUW]%úg @@@@bO ¹ó{σ!€1/0eÊyíµ×¶SÓMš4‘6mÚÈí·ßn“¶œèСƒ9rÄŸ3zôhiÕª•üùÏ–Y³f‰Ž(u¦Ò¥KËóÏ?/íÛ·wž x¬Á¯¾}ûÁu¯BÕ«W7¦ÇÖÀi²%õ4h�L�:Õö%«ƒ¥ï¹çyì±ÇŒ/$XÏ9÷;wî,[·nufÇ5jÔ0‚�z0iÒ$ã9îß¿ßU6--͘Îüºë®³�‹dÝæ�"õ®›õ[·úÅ g ÇÒ»wï6žÕ¼yólA_ë½ôKûÛßä†n°f{îGÊ]¿p¢fXÓÿû_éÓ§�¤§§û³‡*<ð€<ùä“F¿>üðCÿ9ÝÑ/³Œ9R4€MB@@@@�Ø`*ïØz´ˆyµ,mß¾]4 4`À#`üÕW_+𜎚íׯŸ|ñÅžAi½pÏž=rß}÷É�!CÖc=¡Á¹›nº)`PZË®_¿^z÷î-^�Bk]‰¶¿dÉÑ/|ôÑGƒÒÚgÑ:fÌ騱£1’5˜ƒ×— Ìò:muFF†Ì˜1ÃxW¼‚ÒZVGRk üÓO?5/5¶‘¬Û¼Q´Þu½ŸW?§�éùóç?ƒsæÌ ”Ö{ëhí|PôÙkÙh¸ë}4é—S¬Aés¹"o½õ–hßþýï›Yþ­¸Ÿyæ9~ü¸?�@@@@ˆ Ó±ñh 7x5éÔÁ={ö”¯¿þ:ÔKüåtên ¨…’ÆŽ4جu|ûí·Ò«W/9xð`¦U:tHÞ{ï½LË%J� ˆŽ„ÍJ0^§øîÚµ« (gfsêÔ)ã~:B;”¤£ôƒE­u„«îh½ëÚvç4ÞšgNe¯ûYM3gΔ=zˆŽ˜5½ÿþûòÇ?þ1Ôâ®rár7+Öë^Iï£_VÐ/6x%íóÂ… ½N‘‡ € € € €¹(@`:ñ¹5 �eÊ”1Ö¥­]»¶T­ZÕXSºH‘"»¢ÁÄ¿üå/ÆÈ×€…Ý8®R¥Š1…x… üçõZ�2ÜšV®\)W_}µ‘ɺÍ{Fâ]×QÉ^£Ìøáó¶þí„ Œ÷ÑŸáØ©S§Žüþ÷¿wäŠ|üñDzvíZWþO¶¦ZµjÉ'Ÿ|bÔa –ëôgëú믷i?œîÍš5óß³lÙ²þ}ÝiР�”Öý‹/¾X7¶hšo[!@@@@@ ªLåUnn†$‡@µjÕäñÇ·uvË–-bŽ‚¶�p è‡~8ÀY‘öíۻέ_¿Þ•§:bÚšt”¥ŽÄ”š4iâ9�t òñš¯£J�ëJkÐß+(möQ§ä6G-›yÎ:Ìü̶úe‚Ûn»M¼¦g/W®œè´ÓÖ”•Àt$ë¶¶)ﺵ¾œîkÐ{ëÖ­¶jŠ+fŒj·ež?Pç.]º¸NºÞ @IDATýüóÏ®¼P2Âé®#÷%ë9¯‘Ý�®#@@@@rO€Ó¹gÏ�@�„¸ì²Ë\ý[½zµhÐ7”T¾|y©Q£FÀ¢:ý³3íÛ·Ï™%:¢w÷îݶüŽ;Š®[,é:¼³fÍ V$îÏy”;wîœi¿tMßùóçûËéîÙM:²>PÒóÖÑÅ¡¾;f}‘¬Û¼‡nsú®[ëÊéþ¦M›\U¨C°÷½{÷î®Ñé^õ¸*�-÷·'@@@@bT€ÀtŒ>š…ĺ€NÙ«ÓsoÛ¶Í¡©[]ãY§9Ö€rZZš« Z&Ôà¢×(Zk…:åt(IG�ê(Nk 6"Ø,—ÙýÍrñ¼õ LçÍ›W�<7ûêôôªÇ,l[±bEiذaÀ"ÎuÄô8κÃù®ëÍÖõ×ͦóÍ72eÊóÐØêèu¯©éÍBÕ«W7wý[¯g‘Ùû®õ¤¦¦Ê‰'‚Öã?d'œîAnÃ)@@@@ˆC�Ð~«‡£É €‘˜={¶Œ=Z.\ð?üðƒç9g@Ó³ÐùLçš²ÁÊ;çœÖXˆR·NqœèÉk¤s›6m²Üí�;wŠ®é«Ó¯g%9וÎʵ™• GÝ‘x×½¦ 7ûâ Lßpà rùå—›§CÚz�tÖÙ2Kú¾[ƒÚ^õdV‡ž‡{(÷¡  € € € €ÄŸ�éø{f´È5�áÇË믿•ûg5ȨQÖ© Í2::4³” ëÖzM}ž™‹×ye��çuÑEyU–¼œÖÍw=,>_ÉÞ½{]Õ9×êvðe8Ëì߿ߘi «?9u÷jy € € € € �YÚ”}¦ €Ù˜4iRР´N­]¨P! uŠíl4![—”(QÂu]VFn».N  ¯uº³Ó=}îÙI‘ bæ¤îx}×õx�ô÷ V;Ÿ—³L™2e$«Ai­3'îÎ6qŒ € € € €‰%ÀˆéÄzžôˆˆ€ruúngêÔ©“tíÚUêׯ/6Y;vì�ŸþYt�ÜÜN^kEkûH"µjÕr1tïÞ=ËÓ1.\ØUO(EŠ ¥X¶Êd·îx~×ÊëËÛ·oj¨Ó°ëtìÖäU�õ| ý캪�|@@@@HÓ‰ó,é  1�+VHzzº­þ|Pžxâ [žy £6½Âæùhn+V¬èºÝúõë]yɘQ»vmW·u�à~ýú¹ò“%#žßu}FU«Vu=ª%K–H¯^½\ùfƲeËäôéÓæ¡±Ín`ÚV  € € € € €€E€©¼-ì"€x lÙ²Åu¢K—.®þøc[žó`ÆŒά„;Ö©×Ûµkgë—NéÜ­[7Ù½{·-?Yâý]/Y²¤4hÐÀö¸Nž<)#GޝµÕ—.]*Ÿ~ú©­¼\uÕU®<2@@@@@�œ˜Î‰×"€I"P·n]WOŸþyÏQÇëÖ­“Ûn»M6nÜèºfëÖ­²ÿ~W~¤3tÚqg8p Lš4É™-»ví’gžyF¦M›æ:—ˆƒ ’bÅŠÙº¦Ó¶·mÛV† pô´Ž8_½zµlÞ¼Ùvm¼Äû»®þ�?þ¸ë1L˜0AyäÑŸAMúÅýòÅwÜ!'Nœ°•oÕª•´hÑ–Ç € € € € �S֘Ω ×#€I P½zuÑ)±�;æïí¼yóŒQ•7Þx£qNºº–íš5kü#3kÖ¬i[›zðàÁ¢Ÿ"EŠÈW\!ï¾û®¿¾Hî´lÙRš6m*‹/ößF×Ô0`€1’´F�Ætß;vìAªçt4±×S ²S¶lYyê©§äÉ'Ÿ´õèàÁƒòÆoŸ%JH¥J•¤xñârôèQãË:嵎.îÞ½» :Ôv­èsž9s¦?ßkêt�6üóÏ?÷—Ñ�›o¾Ù Ú2‘¬;Þßu¥Ò/èÏ×÷ßo“ÓYôS @ÉÈÈp­+mö lë¹Hº›÷f‹ € € € €‰+@`:qŸ-=CÂ&�’’"}ûö•#FØêÔ@îÛo¿mË34ø¥SBk0Ë™Ž9"6lpfGôøÑG•»îºËŒÓ€º~œIGŒ.X°À™��ÇwÞy§|õÕWÆZ¯8p@ôã•Ö¯_ï•-¸Îìë¨]g™PÖ·Žd݉ð®ëÑôçOÖœI§ö”ºví*�7ö<IwÏ’‰ € € € € %ÀTÞ õ8é  9�>}úȵ×^Ò t´±¤u”m¬$5­Sw—)S&Ó&é(â—_~Y ,˜iÙD( ÏkôèÑÆ§bÅŠYê’3°œ¥‹c´p¼¿ëÊzÙe—ÉôéÓŹ¾z rý"ÉsÏ='ÇT„|@@@@@ G¦sÄÇÅ €@òhðRGG�5Jj×®°ãÍ›7�¢Y×®-\¸pÀrγ&ç±õœî§¦¦:³Œ)Š]™–ŒfÍšÁº:HZZšåÌ…]mÿ¸qã¤B… R¾|ù 'bpÏiä<Îj“ÕeΜ9Ò¯_?# ©S®g–¼¦èÖk²Û–P® ¥ŒW»C½.ÒïºWÛ²ó>{ÕcÍÓiÉ'Ož,ºÆz­Zµ$oÞ¼ÖÓÆ¾þ´nÝZ>ýôSéÙ³§ë¼5#T?ë5ºÊu:R=_>ûD>Öë¬ûZ§ÕK¯ÓgfMÖóÖ|ö@@@@@ ÷òøÖÏ<›{·çÎ €ñ*°{÷nÙ´i“èÔËTÒW•*UÄ:âÖœ²[ƒDúÑà’u«×åfÒ©‰W¬Xa¬•\´hQѵ¦­£¼ÓÓÓ�€—hõ£�vg,7Û�{ë4çê°ÿ~9|ø°1Š\4p_µjÕ,}ù í�Ä=á]W�:}ÕªU¢SðçÏŸ_êׯó_¾ˆÄó¤N@@@@ÈÓ¹ãÎ]@@@@@@@�¤ÈÝ¡jIÃLG@@@@@@@�ä 0�¼Ïžž#€ € € € € € €Q 0fn‚ € € € € € €$¯�éä}öô@@@@@@ˆŠ�é¨0s@@@@@@@ yL'ï³§ç € € € € € € €@TLG…™› € € € € € € €É+@`:yŸ==G@@@@@@¢"@`:*ÌÜ@@@@@@H^ÓÉûìé9 € € € € € € ÓQaæ& € € € € € € €@ò ˜NÞgOÏ@@@@@@@�¨˜Ž 37A@@@@@@’W€Àtò>{zŽ € € € € € €DE€ÀtT˜¹  € € € € € € �¼¦“÷ÙÓs@@@@@@@ *¦£ÂÌM@ȹÀÒ¥Kåþûï—x@–/_žó ©@@@@@�( ä9ëKQº·A@�T«VÍuýúõeÆŒþcv@@@@@ˆeFLÇòÓ¡m €œxñÅmk×®µs€ € € € € €@, ˜Žå§CÛ@ð è´ÝÿøÇ?l=zô°s€ € € € € €@, 0•w,?Ú† €€O gÏž2wî\¿EÅŠ�ãÔÔT; € € € € €IJ#¦cùéÐ6@�¤˜4i’-(­  ‚ÒIÿj€ € € € €Ä•#¦ãêqÑX@�d8pà€´iÓFöîÝëïv‡dôèÑþcv@@@@@ˆFLÇÃS¢� €I)0|øp[PZt´4 @@@@@�x 0oOŒö"€$…Àüùóå_ÿú—­¯�?þ¸Ô¬YÓ–Ç € € € € €ñ ÀTÞñð”h# €@Ò tìØQ–.]êïw£F�ä³Ï>ó³ƒ € € € € €@< 0b:žžmEH �Q£FÙ‚ÒÚi-MB@@@@@ ^LÇë“£Ý € )�žž./¼ð‚­oݺu“Ö­[Ûò8@@@@@@ žLÇÓÓ¢­ € /0|øp[K•*% °åq€ € € € € €@¼ ˜Ž·'F{@V`Ê”)2cÆ [ÿt ï’%KÚò8@@@@@@ Þòœõ¥xk4íE@ ÑNœ8!mÚ´‘­[·ú»¦ÇãÆ�ó³ƒ € € € € €@¼ 0b:^ŸíFH(�ÂÛ”ÖÎéhi € € € € €‰ @`:ž"}@ˆk�E‹É[o½eëC¿~ý¤aƶ<@@@@@ˆW¦òŽ×'G»@F sç΢Ái3Õ®][fÏžm²E@@@@@ î1÷�� €ij€Ž”¶¥µ/ ˆç.Ñv@@@@@\Œ˜v‘�� €@ttMé6mÚȉ'ü7¼õÖ[åå—_ö³ƒ € € € € €@"0b:ž"}@ˆK�áÇۂ҅ b´t\>I� € € € € �™�éÌ„8� €@f̘!S¦L±Õ¬Sx—/_Þ–Ç € € € € €‰ ÀTÞ‰ðé €@Ü ´mÛVÒÓÓýínÞ¼¹|øá‡þcv@@@@@H$FL'ÒÓ¤/ €q!ð /Ø‚ÒÚh-MB@@@@@ QL'ê“¥_ €1)°téR5j”­m½{÷–ßýîw¶<@@@@@H$¦òN¤§I_@b^à®»î’ùóçûÛY¹reùòË/¥@�þv:�  ÎÀô¥—^šm JŸ:uÊ}Ñ¢EeÀ€þcv@@@@@Hf�|ÉÜyúŽ �Üá LOŸ>]¦N�jÃÔ)¼Ë–-kËã 2gÏW{Ö·sVÿÇ—ŒÿÕcÿ¹ ùZ$Þ<’7%Ïù³9Û>qFNœÊ°ÜKï{þÎö�¯}çîek§/«hÁ¼R¼`xþY¶uÿ 9vòÜ”ôçogÜÔyOÍ4Ûcì¥Îeõµ¥rZª™“£ío;ŽÊ1Ÿ�&ëýŒcÓÉ8ë>odû:¡>—+|¾TÎ6+¶‘£'ϸÚb­ÕêfÍ7÷‹¤æ•†‹˜‡9Ú.Ý|ØhON*Ñö\R©hNªð_û˦Ãrü´{IƒÌ~ZòX õµ§^…ðø¬ö½?G|?cÎd½ŸžËãû?g2ËÌŸ"uÂôþlØsÜ÷óei�y£ Z`ɶµ,�ïD‘Ô©P"¾}»tß+ѶéɾÀÇíMËIÉÂÙÿ«Ì˜ž¿»”,ÿè7ËýõVÚ¢ çoo´K[“ákàÜWk¶?µlÙRzöìé?ÎÊδ_vËßïðbÎ9\h‡íØÀ08|-9ïæ»™–I+’O’üOÃRY¹½«ì„ï¶ËÛ_o•Ç|ÏËWï¹ûŸ»—6š ù–ûùÖ<ß~ñBùäîVä�6•]÷ÈJÆ«_l”1_m‘ý¾ö˜÷ÉÊõfÙÔ|)r›JòÄ Õͬlmýð7Q£p$mÏ ›j横?MX%ÿ^¼3Gu˜woQ^Ft¹Ø<ÌÖöÞwWÊtß;ŽÔ¹iYùG·º9ª*œí騸"Ó³~ŽÚÓë­2sùžÕa^Ü¡Qiyûîæa¶¶]ß\*óVïÏֵ΋n¹¼¬Œº+gÏ«Ïû¿ÊdßßáH.ñùÜ“3Ÿp¶'?_�ùþüùW˜þüùß«+Êßo©•#ê�Ÿ¬•w|_ä4*�"¶­,�¶¯–£ªFÌØ oÏß*úå!3è­;æ¾?@þ. 7ãáæô¸„ïï¯û®©d|rÒ Qs6ûž×6ß¿Ç2Œ6Xïåßw´ïB[/ܹtÑüÒ»u%¹¹I™ ™ÙØ›¸p»ñï ýâ�ÞÇßß‘¹¯Õê¾ZYÛr®ü¹ýwØÝ­*Jë:Ù[ ÅlúW«öíQÿ}-÷7òÎÿ�õþçö}m<_Vÿ½¡?_9ý²Ž~iHÿüÑ/êuûÛ¢÷º`¤ç´€™g¶Ã|¿ôýéÖ¼œïßeùµd¶“~áã£Å;äðqßûì¸_°ûŸ+{ÎGËé‡:5)k{ÆÙm”>3ãçËÑgûôXox~s~{îXÿ=ÿ»jŲÛÛuúß;'Ìç¥m²ÜS Z�ϵñ|žÑþsûú%}fáH§}ÿ�púÌYÃú\ßÏõùÜó’s_À9ïpÜ�:@@ˆ�<¾_¸û~]LB�XÐ(:îŒïGVu óýw®4;ãÛ7󌭯ŒžÓ2ô3Ïéø‡cÄ�Ö÷é’]ç�çê?ëË4îyþ~fÀÓl›qìû=“¿=¾rÅ|#òîôý¢¦TQ³0ý€:èûE�Y¿¶Çè·ÙßÖ÷ÿþ¼sûfÞ¹²Ö_dið#'éÅÏ7Èøo·�E{ηÃt ¥~ÑÙ¯]•ÿb¶·/0¤ÁÎp¤kë—’÷{7ÌvUêÑñÕŸå§�‡²]‡õÂj¥ Êô‡›HZ6‚Ó»ví’¦M›^¨Î÷ªS·ŒóýV*k«\ä]ö�¤¬úÏ…z|{S¦L‘&MšØòB9øú·ýÒå�¥¡ ¹ÌÜ¿ËöϽþlÝòOû:Ü!ß8@A}ô=ÊNš½r¯ô»<;—¼æ_ ª½/`•�ôÞ‚m2à£5Ù¹4à5ïù|®Ë¦Ï$ßþ2iuÀº³sâ-_ óz_À3;iü·Û䉇×GÁÎNŠ„�ʳûw†5°Îôâûþ^-Ÿ­*ßør³üý?ë²um ‹Þüc=¹é²ìÏô LùÓáLCn­eвSg$ޟѬ/¸,{ïó'?씾ÿZ•�®¼fl¯úrã¥Ùkχ‹vÈŸ'†÷ÏŸ÷{_âûû"-`{ƒ�øìçÝr߸•ÁŠdù܇6’Vg/ø:õ§]òÀøð¾Ï³½\dsæ†/V앞ÿÞ¿Oç=ÑTj—-”eW½àÇ ‡|ÿ>ü)[×z]¤³H|ùØï¤r©ìÍL Aé//1þÛÁ«þ¬æÕ,SHf>ÒÄøÒiV¯Õòú߀7¼²DVm?š�Ë]×è{3õO�³Ý­ðÎÑËDÓáHmë¥É¿î»$GUÝýö ù|Yx¾è¥_DùÛÍ9û¢à_'ëu¶ÿmJÇô»ÈfàÜ \ëq)ßMõK�Ú¦œ¤±ó¶Èû ¶ÿýn ’zßÿœËÓ6ø>úÆöü±å¼~åßQnÈæßfôYéßún_¸×¹›AÚ`:éuú%‚-+HÃJ9›¥e˾2ñûíÆ?´�çÚ¤³w¸ ÌsÚ}îKz¬¿O¸åò2R¦X³«ÙÚêïPôï ÓGg¹p?ï}k} z¬_D¹:‡_2;°Ò7ã�1#Ši⻉ßçüóÒcmèù�Ñs_·:RNmÌöè—>Nù>†‹¯r£ÿçnïË»`d–g‹ €±%ž¯‚ÆVŸhM˜Œàžïô¿ç‚Œºc?Õ75¢þc7IÿÃV§j4ªz/#{>˜iÜSƒ{ÿÏÞyHQdÿÿI†%ç´ArP 1 =1Ëy¦3¢§xzž=Åtæ8ÅtL€‘ ‘œaÉ,þõí¡g»«kRwÏìÌò}:LwuuUõ§{zfû[ï½è8""§=ÔÅX‚zPâXà-/‚Í:UÓìqYËŽ1Y£5Öý¢ktÛ>©¦þpƒÇ¢ß‡Ö6WüaûœòøÃà]ñä…m|7µIy¹žùØo2{ÅVßm8w„çÈg7ê;„$Є)œAˆ«_­œï�c”`ÿÐg‹�‡è{h=¬Ú:By)û}ú²†Â¥q _)açÏ;¬ž¯ã‚–(� ¤ê;ê¡Æktoé}Õ›«?¤S¥Zÿ‡G”>ªß`_¢4Žç«Yá<àC[¶MPb·ß )?-Ød7Úû¯êá³_azºzP¶ÍR÷2¿ÂôÜ�;�iþšmJ˜v–$¿¼hí¶ä+'YsɺíIÖôVÃC¾°m… Sî×Vlô¿o¬>Wh3Ⱦ±Æ³jÓÎX›–¯ÜèßX�isy¶±Æ³4À5ä³k<‹ÖùÿÌ.HÃçÞ†~mv?û¦KÃ÷Å”%›}ÿûq~øß§øÍêW˜þ6$AÑy½ào9¿ÂtX‘#ìñàoÊO§¯•+”g¹ƒ§´úó/4Ãw;ÚÄX?ö¦ú­–(�þ‘Ò"¥_±‚bX¢4Æ3nözyaü ¹¤W¬¦lˆ¤–(�ÎÑÄ<¿ù(Õ¿ÝÕã„HX"×Ñï|�þã¿ó¥SãÊrD ‘«Àæî1ó]-Y™0o£|~ó¡¾Åà‰lL$Ë0Qkܭݤ‘Ï4*%F¿O±X‡1¦§¾^*ÝÐÅwÊŽM**TÿǦ†ö™?¦u yýÊì™ø�‰÷ lõíïΓÕ=#ƒpí­ÕŽ(‡H~Íq�å¢#ýÝ{ìþ�å5õüОH`‰åáõ¢ÔÆÈä‹ÈPÙŽˆge­ ”˜¬ÄðûàõU6•z­C¨×û´Æ¨þ‰lWuT={ŒÖ˜T"H êQ/p:¥5›wZß�V*œýc±&18a$ÖU¿ÎÉ'ö˜0>8¶œ©&~„aãçm�íöÄp@¿öä<²¶í¯Y�ÔÇóç°RMáÙ3RMa Ñó…1¡_k\û—Q¶L6ë]•!MÆJ# �\!p@ Óø­o‰�û…M­ k ®ö6g9BÉ5©Y!ðùÄx~ ÔÕ%6ADµû³×¢r%pª±ÙãÂ)v]„&ܳ¾´ ˜·^^øñ´¾pwDèµÆé72¶¢åD”c<÷ êólå‘÷ƒúñ†afò'7vñ‚kªÊ­ˆP–¡˜š´�°ª-”÷@×&þB”½«B²¦ú‡m¼±ÃÓdûË_VžÀa‰ÒçµÛÕ�ÔÕ¾ÿûAÆô�š˜àWž¬D·° ù5ýŽ'ÈCøXÇ�?Þý~ü‡m~Ÿz„é§<´ÒÓßrí³/¯®œséu®²TVêV æÑÔW£þ¿Ãàá¶µ®_Éw“íCÊ¡ë@ç|÷f´Ñ£yÕPïÏV›Íª:‡—ÒrÏ–ÕåÑ/–¤´O¢ÊG¶ô÷íâ¡îc_†;ž ^Ç´ª!|²(Ñ!§´ûüÚ±mj†6‘ÉñÆÓGy¦Â£*L Ú7Ⱦ±Ž!H›½ÔçkäÿÇjÚWyÏþ¼oÑYX©5œï®îi~-¬P¼Îþ;åWv®¦´Ü! ÷œ©³6õý{ä…õàÒ9®¦µüG7ðÛÀ9çr½ªþ=kŒžä‡½ŒØ~ yéöÒžãY@Øf >Ý ž!„m.üZ:&-P“ ÄçDAÿa&Ûø¦Ó11¢Ž_/å/TĆ0 ÏØ>Uâ;RRø1x&™H§÷‰¶Þúyµ\¯"Ÿù±—Õ¤‘0'¢` &K ò9±üE5Ñ=̉xÙ[ý�ÐÏg$쟬( þxž‰gœEV´Œ{¢;uQÏê0ùÃ�}2­@îTŽ$a>ßþ­›šèåïofLê ÓqÏÿ>¿¥«ïçâ˜|Ú#áMüÀß*^ßűý20ê7Á›0ìLåø3*€ãÆV*DlrLC¹í”f�íßêy&Z B‚.‚cÝÄm�•E„s¬×T©gþ¬&M-ç�7UD +õŒ¤zü¤ŠLdˆ÷‘õH9–Qêœè€eKOQƒþ&_³y—J�¦R«¨ïžè¬>ÝJEǹ\ŠStƒª�è>g«ÔWeT¤Ì 6^]Ï[wì¶Ž;œU)2n×y;H*ªß¾~'¼êãGÚ™">‘ã¶XE™±°Æ»¿<2ö/ünÆ:­dðÿךÆa†ßU31ËÇQma5òW­e%ÌÚÛP˶@\I Áç©„‡x0‚¡!‡×sß-“�ê�&xºÚcÒ†�Ô*ÕUèÈËÕ¹’£j¿ µ•Þk¦þœã3m·Çdç0m`bÞ¡*‡%B“¿¢há÷ª“‡Ý�óÝ�ËYl-ã[xWtôù �@ÈCÎãWÕï(û|y:J² ø¼¹~ j�ÜFà™§ E�ðbÈùÌc.UžoAή=„ÚÇÃGä¸×ÍÙ¶!Ͻnv¤2ù“úû"ÈDLÒ@èv|¾6￞­~íN´Î�ÅΑ¡¼ZÅÒÖ½5ˆ˜‹Ï&†¾8~¹ •‰mv–&»O×8öÚ¬ìm¸^¦èéÓ}ãûâo§6S©LŠî?vû8;0{]›5Uh—cV÷áå†a"‰_ý_¨î§ø›;,Á³½úû`@¿W1ëÁ·J·$jÚ± “ïûùL³€6Nj_KF|´Pð (,óMýãï�0'r£ÍÍýOÌ ò]Œ¾Mäû“äö /שþßïUÕï ¿V¶tøQìïb?c ú�eê3Èd’°î;Îqá™�_KËÄåœâW˜ž ¾KÃ68Jø¦ÃœD€ãÚ¬"&~¨&o\s\¾¯ÃÄ$�0'~LV¿¥^SQøðw�ÃwEX¢4úGÄÄãÕ3 ¿Ï¤01&¬g-¸w<úùõ÷i9Õg*.üöù×Ç ý 5î3^=×úN¥zñû=ôËÂM2ø™éƶý"Úâ—*Õ ¢dú±µÊI©UŠ ÷Ô×j¢…J­‘Ú¯�OX‘ñûî…ËÛùеßÿSQ^F«È3A “D‘:äÎ~Í5…g ϯþ^VÏðüÇžDa½«–QfO²°–Õ4}RÊ1ᜥ&1D!}[iVVj Õú²5{¢ƒÝ¿=VUÅ£süx>6XE i¦Ò[1D܃³ 5Eu€¾ìqDú‹<´Ç„k÷»reÂùíŠ0�‡�È Ö¬>„‰ýr衾½‚¿T3Ìþ9vA�óâÚ÷m5æ˜ÖÕålŸâBB‡%Jc`ø0}ªþp‡XàÇ~NChÖßWùÿa¹¾Ð¿÷¥ŸãO´O:fì7«íÿFqhSÿÞ+±Ž5ˆÇf¶>¦fÐaBIïÉ�r:ã)~(’�‡«å„×7Œ/¾xæüB±¿lp£Ç²uÃW_˜ax•zpØU‰~ ®ñ !´ñCÕnýëc°¿h¬Y|ªÃȺ=žý3 Õ_�×ÕË«œv]"¡òÕw‡óK/Ú¿*µÇ‡¯Åhù~6`�1BˆÁÄ¡ZŠ“›6Íáús�•úMZD¿ð1¶È8Ü_ÎèkËÆ¹÷ÚwÄäøðcN–·FþÅÏP\û ‡%r-nWû¢cP5ìób�ɱŽJ‘²ýܬm‘NÈÁÔ�“uhߦVªgßh×^·Æ þ‰®G·í/Sc¬X¶´úñ‚# f7�ÜDnT/KÁÀ±k.ðÞן/xe‹A|Ã+[ ¿�üþ~JÇ1 _¶ßœÙéO_•߯l1ˆKa…È ã˜fßo¨ý0ú×Û@´¿gô¶ÂXÇo ¿æÂè_oã¯Ê; ¯l±ÛOk&S/D¢ŠþjU ö²-­;ÅóH)Ät¤.*ƒ#í‘ó[Yyk!æ¡õ¨P¯\cØßÆg*¯¢øýíã<„—®h/ˆÊ³Cyĸƣ֬±íï<²Í;›_eõ ÂkÞ÷¯ídM̳EŒÁ—ÕûþuÚåX²—íwü> 2‰Ç�ß«_ªÞÈU¾mWdâGÌñ¨ú‘mnö8ì1WQ-ð÷A�kç{¬òƒÇ&V9ûÃx�ëú8±nÕÙÿÎU¡YƒDÅÁß¶ï_ÓI^Q_àÙD䘽Ço�ìëÙ.³×ó”‡×ùʃ)ÈD z~S×è턱^Y Üçî_\ c Î6(ïžNTó²Ä0q6ÈäÙ°â«ßÞa�íÝ¢&u╬aη-ÖÛÂ5ÖË(oÞ˜·°dWõFž×Jþ®¼Ž�r§Õúô,;ʬmîºÈ©Ú$àÄWü}ü®šØ€ã˜øa ÷XÀ²ÅB�Ì.·Þ÷o‹ŒcŒlÇçÔo.pux–A¸ýô¦.VøíÍщ‘>"票¿èXÔžEãS ÁR½*•/e…fm¨&·ø5LÿH=ðÅó8áŒEúÅ8Ð�s}ÿ²*w^C‘úû¤ªš8„ lA&¢ôTÑTžº¨�奊‰10´ïWdö¢uÀÅ1~<ÿÁó6¤Còk˜„0_¥ƒC¤èDŒ\ö�Íþ,aœñ ×ó_OÌ—îR)!âÌÏjâRæ…a˜xd-¥¦¬(a&žýIÝ_ý&iÀic­ †ÕTÌi�ëønêDåÝvjž£Tº(¿Ö#Àµ«O¿Þÿh/¿ ƒ¤� c"¦Î âüž+‡m˜ ê×Ö§a¢Åò þS«ü±¦Ðï¡ÄÜoºŠ€uv·˜›ãnøeaøú œâü ÓaG´€·óG¿øž¨ŽH^©ŠÒ N¸ˆàÔB¦ƒ†4Du5³Ø¯µ �ÿ+VŸæ×ÎTnþÏ` Cè§MÌ â­�P„¹N6Üþp@t#<Ô±EN{?äà]dÆ,B5¾su'yCÍ/T(á/«}Õ/„Eçrd,‘ñØËv}¬ã‡.~¨"$jƒÇ"»`&^DV}ªqEÅ`Õ)ú³Æ‡wõ#ˆ�x�l‹xt†á�}­òÆÃ+[ ?t‚üØIÇqñOÇxðn™Òþð¤cLl³ˆ€Æ»S§NE,}õÕWòÖ[o¹jÝzë­Ò¤Iö<,t Ž+$@$@$@$@$@YKÏ4¬ÙÞ®†û‚Ž nà]ЕÃö/Ní[ßÏoà9�-†T1AÒÅ„}ðâÆ+[ì¶Sšª<ºM“Ž%¢DkˆèåU4/œû ö¤Ê1|×Í-?ôg½”LnOXˆN&PY:ê¨e{lX�4…ˆ(­ÔÄ„ öžšøñõìõÖÄôcê32N§˜_$î£>Æ„T8[kÝŸ| aÿ?½±«¼§BÅnÝ¡¦3¨¶#Ç�ŒÔrdLîñ:Ç>ˆØ€‰Z~ ©^¿²ƒzþ¼*2ñ‡êˆ¸Æ�Qî¯c�Ok=Rc«T-A"là9æˆsZZá¼�H\׎ê3²¾lÖºwœÏêYǨË�n(S—n¶&ƨnRÙI�ObˆyaEØ8\Ek’êZ Ò‘.VùéÃ0¤/ê¯ò”û5DÃ@Äà]!EØÀ8�Þ˯¥c¢E�ÏWË,Kebýó 7Æ~HÏã×poókÎTb~ÛÀ~©p€aDº~ø‹åÁO‡k_ä—~ÿÚÎÞßå®ZñW†¸À €ÜÒ¶áç„E„ÑŠ«Ñëîòˆ¸ˆrÜ$.V3ÎŽ)ˆ!”7fý�Z%hÔN¤êxyâJ٨”£G»ýè„ 5†è2Ƥe½cø–)á‚å OÚg¾].à„ã/ê[-›Æ‡ñì/Ǹl†Ï%*ÂF²“�зÉþþþI;fšöw–a‚"ˆøµmÊ;~à¨ß¬Ô�~ÛpîÏmLDòk˜`qš å] r§§j·ªÉaaDrJ›0 ïN[P…Øi‹±ÑexBø„𪜠Ë(×SÔÇó¸¶5Såa¬�¸»v#üfÁE`cE’ � PÎ;ï<™8qbô˜Ÿxâ 9ýôÓ£ë±yä9r¤kóo¼!={öt•q…H€H€H€H€H€H€H€H€H€H x Ò³s"ATWÃÁ¤(õ‘‰‘<º¬Ê#uEà™$-†óÈ¿U©H‘u×–�LúØ«:w–£ÿȺ=–È{å¸Ú¯smg³¾–—mØ!c&­‰òA#‘ãw3­"A;ív•röº"û‹w¼D&�Ú²J šBNçÁ…µ á»L9ÓèÂê�í� � ä"?¡¼çÌ™ã¥/¾øbŠÒ¹xpÌ$@$@$@$@$@$@$@$@$@%–œ`ñÊ;Z…ðÏkT½¼\s\ã¸Ã®ÂÅëGã0Ì ò0zg$@$@$�a˜·nÝíµvíÚI凾ï¾û¢û`¡nݺrÛm·¹Ê¸B$@$@$@$@$@$@$@$@$@$@¹L`/\¹5S�¯C±Pš1‰äp5§‘ � �@¶ðã-ýöÛoË—_~é:ˆÒyyy®²dW¾ýö[éÓ§�´nÝÚã…�l¬G$@$@$@$@$@$@$@$@$@$@a@ÞoݲÊcÚä¼í²~\'  �ÌHU˜Þ²e‹Üÿý®�žp 2`ÀWY²+Ï>û¬\xá…2þ|Ù¾}» oõ’%K’Ý�õH€H€H€H€H€H€H€H€H€H€H mö é´f¥Túä0,”Óa „m� � �@&¤*L#„÷êÕ«]CóÂû½ñÆ®¶¸B$@$@$@$@$@$@$@$@$@$@ÙB`�!”wépti G˜6ÄòÞgpóΠ � �À�I`Ïž=’Š0=aÂy饗\°nºé&iÕª•«,ÑʼyódèС2iÒ$OÕþýûK~~¾§œ$@$@$@$@$@$@$@$@$@$@$�i¦PÞô˜ÎôY`$@$@9O¢ôÞ½EÓ½š4i"uêÔ‰y\ð–vZÛ¶måúë¯w%\þøã�-Q!Á�V±bEyà�¤_¿~Îb.“ � � � � � � � � � �@±0 Ó'e? Åc:$ïm?ãç>$@$@$�4�T¼¥Ÿzê)™2eŠ«íTCx?úè£òðûÚÀJ§N�,QºM›6žm,           �â"` å]*œÑ„"LW©PÚ3šÊ†2O%� � �@ $+L/Z´Hî¿ÿ~×È $}úôq•ÅZ),,´¼¤ÇŽë©rÎ9çȃ>(¥J…ôMîé�$@$@$@$@$@$@$@$@$@$@$à�€Écºt©pÜ”Cy*>°G=iY·RôèòkV�A=êG×¹@$@$@Ù@@¦;vìhBx#µmU«V•[o½Õ^�û>uêT9óÌ3Å$Jß~ûí–5E鸹‘H€H€H€H€H€H€H€H€H€H ˜ìÙ»ÏÓs©l å]¹|iùäÆ.òöÏ«c=«[]©Q)glÏ�³€H€H€üؼy³Ì�;×µ+Bjëöá‡ÊG}ä*FïÚµk»ÊL+o¿ý¶å)½oŸû‹»V­ZVèîã�?Þ´ËH€H€H€H€H€H€H€H€H€H€H +ti ÉaZBS�ó”8}Iï†YŒƒ   �€î-ݺuk©\¹²«ÚîÝ»=!¼{÷î-ƒvÕ3­Œ1BF�íÙÔ³gOK”ÎÏÏ÷lc � � � � � � � � � � d“ÇtX¡¼C¦³ ÇB$@$@:]˜6yK#¯ôâÅ‹]»& á½fÍËKzܸq®ý°rñÅË=÷Üã)g � � � � � � � � � � d#³Çt89¦)Lgãç˜H€H€B'�H˜ž4i’Çãùꫯ–Î�;ÇË„ ,QzéÒ¥ž:Ç— /¼ÐSÎ          ÈVÊ–ò ­Ryo™§Rá´’DG¬B$@$@ÅI`Ú´i®îu�ixK;­yóæÏ[ú…^�óÏ?_tQºiÓ¦òÖ[oQ”vÂä2 � � � � � � � � � �@Nؽ®kœ]šT‘ãÛÖt•ù]¡Ç´_rÜ�H€H g¬^½Z–,YoéÒ¥Å)L¿øâ‹2qâÄèv,ÜvÛmrÐAæð$Æ “W^yÅU+'œp‚•OºfÍp¾¤=°€H€H€H€H€H€H€H€H€H€p‹ ¶ËoK¶HÅr¥ä°æU¥jEÊ�a^½©.c¯ï"ŸL+�ê•ÊÈeG5 ­yž©ÐP²!  �l%` ã]ªT$hȪU«ä¾ûîs ½ÿþrÊ)§¸Ê°²páB+t÷O?ýäÙ†°ß³i$@$@$@$@$@$@$”À¿¿X"ÿ¿<©f^ÒAÚ5ÌKª.+¥‡À”Å›åÒÿÌt5>ì´æ2@ó8sUà �@ ËÖï�3›*{÷í‹¶3ä¨Frõq�£ë\(™žùf™ÜóáÙ³?ríÊeå¥+Ú ¼ziá8´iÁ+l£06Q¶G$@$�uL´=H„ðÞºu«½*åÊ•3†ðþüóÏå–[n‘ 6Dëb¡L™2òàƒÊYg�å*ç � � �Ò@IDAT� � � �@ò¾˜¹N¶íÚ›ü†š=šU•úÕʶä^ÑÊM;eÕÆ�I |Cáî¤ê±Rú¬Wç@?_k6'wþÒ7ªHË…;÷ÈW³ÖËd%ž¯V×Õº­»¹CëV)'MkW�“Û×’æu*¦{\û»ö쓉l”…k·ÉâuÛe±òîܺc�ò<,+Ñ0™¤OÛÖy8àà„tÀ›·ï–v¸Zý3Ûm·º6>›Q õØ#-­‚86¬^^šÕ®hy«Æ®yàmY¢>ONQÖnÙ%·½3O>»©ë�$�˜Âtž4™H€H 5±„iˆÍï¼óŽ«1ä•nÜØ=³ò‰'ž=5vj×®�º»C‡®6¸B$@$@$@$@$@$�<�ÝêéüEÏÎH~‡5ÿÞ¯¹\ÕÇý÷\Œª,Î0�Ç¿\"Jµ­E�J2¸g}{•ïi °]Môxð³EòüwË˱ìžH7åwҡQåXÕXž$ˆ�ïýºZüt‘%HÇÛ äú´©!ÃNo.m$õ€Ÿ¥xDscÛ¼Õ…2ä…YI!ª›Õª };Ö’+�m,ÕÄ’Ù¦-Ýõ”vr˜±l‹à÷D™RæÔŒÎº¹¾œë÷ Ó¹~rü$@$@ Ħu±¹{÷î2dÈ�h{»ví²¼¤ÇŒ-³Î<óLK”†‡5�H€H€H€H€H€H€Â$P¶„=XbÜRÙèðì†Ja:Ì+ÆÝ¼H<9M¬ÙæÞcí×E›åÔ‘Säž³ZÈ%½ĨÅâDà=PqGh÷d ¨áÍþõìõrý‰Mä–¾M%‘¤ÆÏR2dKV�MÛvËoJŒÅëÅ +ä¶¾ÍäÜÃêI ûšHú¤å•/m¬[¡li)}€@Éõû…iã%ÌB  �’By¡ ¢‡S¹reiÕª•<üðÃ2wîÜh9à-mÛŒ3¬|Òx×mèСríµ×êÅ\'      �PÜqz3¹Ê�'uÔWKåÙo—…Ò)™6*ëüÑÓ“¥m ð4öÞ<©Q©ŒôïZÇ.æ{ nzcnÒ¢´³YxY�üßb™·ªPžº¸mBqÚ¹/—,HpÓ›såËYëä™KÚX¿ÿh»«´ T˜s=Œûi�jñ³“#WDÎ Óß}÷�¬ZµJöîÝk½öìÙ]¶Ë’yß§¦'¡žóËɬÛõð³×MËV…ýuìízY¬u»ü ÄøÐL/s®ÛËö;vµ—íw»Ì^w¾cY_wÖOe›Ý–þ®·çÜŽm¥J•²Æ —;×c-Ûm›ÚÀ>övÓþñ¶ù©«=½Ü¹î\¶ûÔËb­;ËíeûÝyìv™ýno³×�ïÎe»žþnªã,òmö¾±ÖQ¯Ž¾Ín'ìwûómj×´M/‹·îÜf/ÛïèÏ´l—éïÎúú6çz²Ë¨ççe�#Ö¾úö ëÎ}׬Y#G}´´m[ôÃÌä-=sæLyôÑG±kÔ.»ì29üðíõ÷ßߥá1í´êÕ«[^Ò'�t’³˜Ë$@$@$@$@$@$@àé B•nS¹puCžVÝÊ"ù¦ÁªTÈùG�Ñ£*_¦”Ô¯ê�ÐU3¯äcô`¹*�cÊ\%p𠹤»6©båÃþeá&Ù±Ûâ�·‡¾õ»ô>¤ºÔRy�iÉøtz�|8u­q‡Ã®&­êU²Â ÛzÎÊ­ÆüÂóVoSÏáð,ÔØ K8û» ßzÈCÏ>úm­¼ñÓ*9OyNh�éW†´—¿©œÒ³–oUÚÑArJ‡ZVć�E®oNÿ’9r¤<òÈ#¹Êžã& H�#FÈK/½$ÇsŒÕºI˜ÖCx7hÐ ê-�mÈ)­[�=,Qºyóæú&®“ � � � � �  €Ð›³ÿÙÓØÂÿû`¾ŒþºÈC¤¿÷Hc]’À�N¹k_ûq¥CCå]øæ_:J‹º£Ûv*Qúÿ>\ Ï©ÔNÛ¢ÂQ?¤¼wGœÝÂYÌå>Ÿ±ÎS£q�òòÜ¥í¤ccwînxµãÞöÆ�«¢û´©_IÞºªãž9 â]xZyÊŸÞ¹vôèñ9\¸v›ü®&™Œ·L�?Y·»ÞÿCz¶¨&MUþéÍ�“ý¿×uÈ÷œÇ‘{g?§³¤S”ν Ž#& �LxòÉ'£ÝèÂô–-[dܸqÑíX@ï;vÈW\a¥/¸àyç�w„¢´ WH€H€H€H€H€H€H€²ˆÀKVÊÄ…vXmåù ÁÓ)Jcs9å•�œÒƒ —oþ´R¶ír{S;šä¢�À×sÖ{JŸ5ˆÒ¨TMEˆxxP+yþ²vR3¯¬åMýÖÕ�è¥î!xàTV“°:4ª,gZW>»©«üÓ0Qâõ¾wO,9ЈQ”ÎÍ3žÓÓ�5’eËŠfLææ)à¨I€H€Â&à�§M›æjþ£�>r­Ÿ|òÉÒ¸qcéß¿¿ µnÿøÇ?äÒK/Õ‹¹N$@$@$@$@$@$�ã–®ß!¿ªpÆë¶îR¯Ý²¾ï»THñ½R¥Bi©^©¬z•±rîÖPË'u¨)•Ê•NxÔ(O·™*ÄèŒe[å�5Û¬vÚ7Ì“vÖ«²Õ~†2Táûß7ÈO 6Iå¹^Z¹1Áƒ½œZ¨¬ÀÓ¶‹ ý ‘$ƒ&úõìõqtcán×î+7©³ÂÓº†Øám�ÛLË�c—®Û.³WÊÌ[­pɨ‡Ê­ëç àyúÖB¦è 5“jçî}rF—ÚÖLŸô ;Ò²žû³W¯^RXX(Û¶ms½¶oß.zÝt��í“ �@"È��—žÿ\_7Õ±÷M´ÍYÏÏ2ŽÁÞ/Õe{?ûݹ¿]f¿ÇÚ«ÜÞÏù>fÌT·l÷îȼ³fͲ¾ìòŠ+Êúõî¬ÈG=pà@»Jô“ŸxàÁw �H€H€H€H€H€H dX½y§¼÷ëjùpÊZ™¬Ä‰Tì—»K(LCð¸êåÙ²¨ HäÓû€ÈðךÈÍ'7±D`}{¦×ÿ;y�¼úƒ[ìpŽã…¸{´‰¯;>?®Wç²õÛeð3Ó�»—×nÙ%?7øÍYøáõ],qÐYfZ^ Äÿ«_™m‰Ç¦ív„î‘çµ’úÕ¼ù»í:Î÷¯f­Wçs–lÞnyþ©ò9ßuFsɯYü¡t!PAv&Xœ ¾x†‰ýÔóüQ_-uUû LC€=÷I·À}[ÊIíkÉOO·&c8ùø·¢µ³»Õ•‡’´Øæ9�5îÞJüì A_&Ãd�ëOÌW¯&ÖÄ S”Ùy¡ñnÛF% îÜ“ðž�sd²L–¼˜ŽeÐ�^¿\îýh¡ÀÛ7ž�Kÿ®udØéÍ-óxuSÝv‹Ê£AÖiãní¦&°TreÝò]g,c&­q±›m¦?=úöLPUyë#urXÿC…“qü ÏD"ì‡kÿæ¾M­{>¾bYº¾ól“ ŸIüáî1�_ÐÚYw9Ì{Œ©£0®÷LßLÇ‘Ž²P„iÜoO}d²ü¦f�Áûb‰|p}gi¤fÔ¥ÓvîÜéjþñÇ—š5Í_´) XC¤v¾ºëx7½ÐÊñ®¿ v£ÌùŽ~°Ž—½Œwç ÛöìÙc•íMeʇëh¹’�l‘ÌñîgYß'ŒuS(3•ã82µÍÔ—]f¿ëc´ËS}w¶cÚ×Tfïo›©Ž³~¬ål¼†suLÈ)í¦q�…éa¼ñ=à´®]»Š)-ıÇk‰ÒuëÖuVç2 � � � � � �@€Ù�fI�ES5ˆwñ<>ñ|tô×Ke„\ðÀ?žáaõ#Ÿ/V^ªdÔ…m,¯äxõÓ½måF÷3V½?ŒwöÊBëõºÊ_|ƒé®êÓX¯VlëÈÓ{§Êµ 0‘}£Â-÷yàW¹OyŸÑ¥NÜêOŽ[*ÿ»ÀÓbUDŸw¼;OñÈ�U%cå&�êdå‘‹�݉¬¿b¡ Óð?Ó#ôéûbBÈ|%#|uý^æé:§Î1­Ø°SMŽ˜S”F]Ú}¶ØŠ(€ݱ ^ý ª•·<®í:ð´ÿb溄י]¿¸ßÓ}˜£î!·¼9W~]”Ü„ L�wð8�ù—»ªÈ a¼Wç*Om§A„m®¼ä³ÝàUñÜɞ߈ô��ðÉØ&•ß9Î_š°Bž×òË;÷ǵßÇ e—ºŽoQµnø–ËÕï<Kºï1Ùr½ëç-[ÖC¦ßþyUT”Æ�­P¡X^W³ìLl˜�×ieËÆþð•)SFªT©b½œû÷2„i[¤Æ»þ²·›ÞQ¦¿ö©iY(3½£,ÑËæ�z0û]_¶6jÿ@|sš½ïݹ ˦u»<Þ»SD=çºs9Ö6Ô±ë¥òî¬k/;p™H 3œ÷*ôK˜vŽ&//O&Ožì,²–ÿüç?˰aÃ<å,      È]ðÐ…x¦‡”Nöˆàa q:–=©¼M‡+3Cøìþ�M•ñ·wOJ\ Ê–²BË&»_ºê½axXR/vžYx =öÕ#5´×Få"^«Bˆ/4„÷†è‚PªCŽidÜ?…�UNQ8sÀïê�:ga}÷|°@ŽTa�õзÈË}JÇZ*4¬{�ÿÍ(p@ä?^åtMd ã„܆·òCŸšÅ•¦µ*^�‘wZg�œ¨Ã•ðüâí=CÀ¹âKwXk»òmÃsûCàúYM.€Å õmï—‰÷k¼áãók&ÑáÈ�¡À‘ów�ºn몼¿ñl’öž:_‡7¯¦D¾ƒ¬pÃÈ]�Ï‡Ó üÛZ9Ý ò¦ëœ:û·—�¹y1Ñ“j¨œò¿«\»z¾hì3BM<9MåÜŽ5?e`÷zÛiW}ýks¬ÖC•שþyqÖÕ—3ùYBßaÞôcAT�X¢t5Å©0aäwum8áÛí ¢Q< ò¡µO§¹ïEh¯}£ÊA›ÍØþë•w´nÕTxn§!4ÿú­‘Ï݆Â]–Ç¿sû˜É«­ï7”áûîP•K æ®D¾i÷5¼A�„y?ï°zÑ&2ñ�‡I¦ï{[vìVQG6Ú«I¿gâæõžéû@Ò Vt_±>Óe­VL7ŸÍwƒ‘ŠÇ´±’ � ”Hv*=”w¬ƒmÕª•%JwéÒ%V–“ � � � � � ä0�iK#)�‡ÐLyž�8§…ôR+BñúµQ*ä³Éù˜o;µYTÈš¾l‹ ÷'D§ýûË%ò§#êKž±‹Ã^¸¼�±[„‡EÎwUæw~YíÊk ñßß½¦“k_ä…}îRo{mïœèbÔV‰õ±úu5g婯—yX"êÿ�ÕÂã¡úšŠîyטù®pߟ«0Ë6ŽlYÍÕËJ„Y¬Äl§áù÷½ç´”‹Žlà,–Ïg¬“[Þš«\¯X䪘�ɺ%Þû™ênEní$#(_س�•oÛyCà rRë9œW×¼I˜N×9Õ¹8×Òy²mqàMª{”"o<Žžå&;GyEcÌo†{ „øO¦­•ÛÕR9{K·fUMM¸Ê2ùYBDZ>�~îÎÁýcâ^±±âøÈù­]Ÿ?|޾T¹Íï|Ïš¿b¹RÒû�êÎf}/�™´Ú³o2“d<;CüÍgj9¥ëT)+ ´”º#ÏkÝu/;|øÏÑu,ŒUÞÿ0¤§xOÝÛ×)šxu³òòGʧé‘2ñ�‡Ï£é»Ä¾G»Üý£½šô{ºï1a_$ 2`E·ÿ}ÀÆ2¹».JÃë¸téâùñ–Éãf_$@$@^&�édEéÓN;ÍÊOMQÚË•%$@$@$@$@$@$PR G²n£·–c[×$JÃûêÙo½aœ¯>®±Ü~Z‘(�¾;(�´1íl…ÁvŽÙN³Í TB@¡Ùÿªqã¹Ó¦,Ù7ÿ²³nØËðfJMpÄãW†´÷ˆÒ¨áÿáó¼Þ–c•`¨Ûèo–éErÓIM=¢4*�ؾ¦Œ<¯µ§~qÀ;W·Z•c§¾ôÔ5„IÞšDÞn´sŒúAÜuŠÒ(‡`†É �ñöG•óÝié<§Î~œË}ÚÔ�§.j¥± ù†áݬOXÀ¶…Ûðf4Lmyæ’¶.‘ÏYŽ|ˆÐïßSe  g>u‰w²Œ³~¶,¹@HEdÝyàË¡Ý<Œ!²Â3÷µ+;¢MÀàÉþñ ]¥×ø9áõ>Lëc&­ñDOÀµyR‡ÄÑLíeºì95ñaÕF÷Ħ.ùIÎq Ÿ5ð}󪎞ëõφèÎ>sù;/Ý÷˜l»Þ§Ü÷âÌå[åÕå›9ë}·aÚÑý�`ª‘D™i>aº=¦KBï$в � �€È1=mÚ´„{Þxã�2jÔ(A¾i � � � � � �@É%°Ç�ÛôÇù‘0ÌAŽzáÚí./\´…ÐàW÷ill¶™ /}Ö¡Þ|³ºœqçb,„‡óõ'6q�¡¾uÏbW…4®À{Ub/Pâs÷8ž¨ý”glç|wÈÞŚШœe­ÝΡÛþ’^nOiçöãÚÖ�Ú'ö€ueFN«T.–ÞYo¹feopU“¶© Lˆeõ•Wæ�½¢âŒeîké:§±Æ…rб¬o‡ZžMø¼Ç3„Cÿïu�¥G‚ëaü¼ rê#“å¯*Ì÷J•8W,Õû<ÌWŽïÿÎ<Ø3ÑÅÉŸ§×ÿÒQy—çË'7vI)º³ç2¢…;¸©ÿt—¥ÃQ,™�D½N�Ý‚¿~¬çî=7Ë•hé´t�SgÎexŒÆõš¨I$ºéù²õíX¯­¼ÔÇ(qú± Z{B,;ëã|!D~_u2…ÿvÖͦåTîóU*Ý0‘#™PæÝÔýÂ�'/Þ,§Žœ"Kî4xÇßx’{Â�s{q.㳡‚ôÅÏÍ�sUHü�*…‚Ó�º`�#÷³s[¢åsºy'HaŸÒŠ ¶�ª¾Çì" Øfú®Ê•ï¼tßc²åz·ÏUÐ÷§¿qG%Áo¥�Û"¹Ëƒ¶í�å£EcŽií¤²‹Ê»lÙäÃ’¤Òë’ � ää˜þøã��ïÚµ«•Oú�C¼!¼Œ;°�H€H€H€H€H€H ç d7ðë6Cå}ÆKd�T«TÆò|EÎi„&nm³õýá�¥[›^ÜY§�ò>ÖÍÔŽ^'�ë»”ûžÊûã‚�²|ýY¾a§¬Ø¸C�K¹>›«|ÜÊz}œŠ+·òµ^Á !Ñ¿NnTç ‘lÁeêØuk“ÄuÐDyÊ·éa´áA�óW^yð'cë¶x‡¼$£ÂÃ/4Ü+°/&CœýøoòÊŸÛËïóV{?§Lb0ñL¥ b.„þÕÊSB$Âõ#l¼iâÆ`•3>™û|*ýû­{õ˳Uní"�  >¿ñìÞsZHC-¿t¼úö6¤e8R}ÏŲGÿ;=�~E¹ò�—î{Lq\ï±ÎaåøNÕmóö=®Ôúöd×C¦“í,Ìz¦Ã¤É¶H€H · èÓ›6m=²ŽpРArÿý÷çöÁrô$@$@$@$@$@$@)¸òØF–hi§íÆ6î–Ïg¬³^(CØÚ‹Ugä'†g�ÉL¡¬ë©ÐÅñ¬¢xáéæô¾\©ò†ÂS·Œ �Iƒðñ¬Ê[úìwË=¹Kíqàá´éµ½½8ÞM"ߥÏÏLy(â6î’ûó+C”×­n‚ó‰úÉÔÑÛ {ÝÎÉëlwíæ]Ò(�k×_»ÅRÚÔ¦]ß~Ot½£&7 ĽÓãs™æ1�®sj�SÏÄ9ÃdŽK{7T÷‘†ò±Háq8iÑf}(V:€[Þü]¾¾µ[ÆïLºî¬.ôkÓZ=ea|¤8ã•ÈÚ5Ì“ÛOm–¨ZƶïV3d0I!Y;[M|0¥…HfÿzUËG'ã$SßY'—¿óÒ}�)ŽëÝynÂ^Ö‚‡XÍÇø)”r×þ§ý8º2zL'çÃÑFª‹º0]¦LÎjì©:ë“ � $ Pª”÷ëíÎ;ï¤(�€7“ � � � � �@I%€ð¤¯ý¹ƒ6QXf›Á,¦úoïÌ“STXÓÃxÔsŠËö~Ɉyz�=J”Ðs&Ûí¥ëBø�fÉ?UÞÒUJÏ%Û²ÝëÝëwüeÅ&qVímê×Wq[eå©[�ºXßî\7M>¨\!ñ3÷RI{u%N;Mï/]çÔÙ§s9Ùq;÷ñ» D§«œ¾c¯ï"o]ÕQêWóN^Aà—&¬ðÛ…ïýÒy0Ý‘³½¸­b¹Rò¦Êa Ïá\3LyäüVV¨x¿c¯S¥È3;Õ6LçTÿ>3µ©×)Žï¼tßcLl²áz7��dÊöò›„õ]ç}rŸÌˆ² Žî ÇPÞYpR8 Ȧ»téb�æ 5{ê¼óΓ!C†dÉè8       �â oäëŽÏ—_ï:LF_ÜVt¯+É< G¨ï«^š-ðhÓ žgº%#ê¢Â-#”x&í_/”/f®‹Ù%@CÀ ëAtÌŽ|lÈ1t¶3‡­)78<}sÁ®ãõD�o¥l:\Úz¾mˆvÉ|>Lí™Ê6i“ j湯÷t�SÓXг¬÷!Õå7*=šWõ câ=eé.Hç} ±ásj �îcÔÛßµ{Ÿ¬5„)ÖëeË:¾»ðùþë ùòýíÝåÜõ$ˆ¼�<è~-—¿óÒ}�ÉÖëÝï¹6üäQžöA®¼¢‘¸ïþEå)-døìïµ”MPY÷˜¦0�7“ �@ & ‡òÞ³g�|ðÁ%øˆyh$@$@$@$@$@$@~ @hì§¼ñ‚ý¾ªPÆÏÛ(æmP¯�²Îàe:yñfùhêZéßµŽ«Û¦µ¼¹…–;žmP!ÃõÜ¡MT;~÷îÚ?©i,Ûví•W~XéÙtJÇZr¡Ê»ÚYå�­®Â[cLxÊ‹¼­`týks<ûø)p†tö³˺Þ<Þ'´«)‡6­’RsÑîtø5…½^¥rÕæ‚™råþwÊ9óP÷5k:–” ª‡nUÏËØ´o2eÈKº]]sNk¤åÆM×9uö™-ˇöm*ç>9Í5¤y†Ð×® †• Ÿ¥tßZ&K,Pžá™4xêêÑ(0ÉèöwçÉ{×tÊäPâö…°ÜG´(š¬PZ €¸5«]Q�=Ì BÉDBˆ5ØløÎ‹5¶Dåé¾Ççõä>‹¼Úu+Ò<­P„i}p™X×…i†òÎuöA$@¹AÂ4�H€H€H€H€H€H€’!pˆàðºD哯sXä'ý¿æËR-ßðôe[=Â4eÝÆÿ¾Á�ôr{}¼Àukbð,Ôë`9ku[‘@×ëcB¤vtpÏúrÿÀC<Õ!N7T"¢îáê©§ ¯\i•˹(üö’õÛ-Áۯ߲ž×;9ƒo8±IœQ$ÞÔ¸†÷|Î]¹5áŽ{÷yà'Ü)ä Uö´øÕ¬uÖyFNóxöþ¤ÕžÍ¦ö<•T�)Ü«^oŽ�¡î]˜®sª�%[Ö�8¸š%6:ÅŸåZÞmÓXÃü,¥û>`òâÿߌË[9ˆ×®‰‹]vF—:rë)M­ 'µ*—³Âu»@F}µÔ®b½ãØßþeµ T‘3²Á0)áÞ³Ý2ý�&�tßc2y½‡yˆÅØô½–Ç´÷—L¬QÄ)7yo§û»X¦é1çq � ”pºÇôîÝEì–ðCçá‘ � � � � � „H޳𤾤wCO« ×z=ýLéZ°If.�-f¾ð½7�l¾Aàö @4¨æ ¾LÐMûéeð€Öm`÷zz‘ký«Yë]멬4«í|·íÜ+󔧺_k¡<¦õgÒïýºZ~Y¸Éo“Ö~·6×iŸN�iÎ2}ù�$Cfëû…¹Q,œ¶KåýÇUÎ"Ï2"ŒU“1të«„²dì�$<`_�èõ·¨ÓÒuN�}dÓ2¼x�¢4ÆÖÈ01Bs˜Ÿ¥tßpNuƒ÷üˆ�èÅ¡­ã3ŒÏ<�íÒ˜°‚Ü̺a’>AG¯Ãu7�Lç¹{¶–î{L&¯÷0ﱨîu¹°ª9#ŒÄÚ/™òP„éd: »…é°‰²= (9è1]rÎ%�„H€H€H€H€H€Šƒ€I°©R±´g(ùJ\3…6½kÌR¸ÓÍ â©Écú(•w6«_Í+®¼§¼]!öµ�ÛbOò†àûÎ/ñÎxýC$ÒíîÿÎ׋’^¯¨<Ç‘«×i}ÁÓÓeÒ¢ÍÎâ”–ñÐ]÷†¸ûø—Kb¶ƒ°ìc&¯‰¹=“ìðôÎ>á-ú¡ Co2œóAOMwy³£^u•ï¼—Æ×´?Ê01â{% –A´GHqÝ:ªpñNK×9uö‘®e\sCßú]ÆÍ^/»Õõ’Œ=øÙ"Oµ�òÈÙúçgɬ[­Päx. 1"ø?”øûì·ËØ L­D¼~ÿžj‰yk6ïŠî�¼·ðN‡Ø9[S,»o@KOhs°î÷ï)rñ³3ä;uM­×ò…ãx�ÇÇË»úÚã{ºÄõs‡ÊI«OøqþF¹ð™2Eå Ï»¸WC�ç4xå^óòl+”1xâ7ƒTŽãêêvUŸÆ‚üÛÉÚåÿ™)/Œ_áâÍŸkœ=¿ôá*Œu÷fEùtí~ÒuNíöÓõ>uÉfyUålÇĈNwÿ 7¿ù»|>c�u­9�ì§.Ù"×¼2Gž7ÜcÚ7t‹õ¦ñ†ùYÊÄ}à^õ9Õ¯%DÛ½O §Œœ"ÿU“:pï²säâºÁçlÎÊBùbæ:yi Áý!¨ P!»»rУ}œZr2ý�—ܨ’¯•î{L¦®÷0ï±èí1„ÅFîó0,~r‰d{g,ÉöfÕ£ÇtJ¸ÒVyÑ¢ErË-·¸Ú¿þúë¥wïÞ®2®� �@& P˜Î$möE$@$@$@$@$@Á@>O ŸÉÚ¥ÏÏ4Vý{¿æAÍd�)�×å§[¹2¥¬\¤NñH¯c¯÷lYÍ^t½Ÿyh]y\å/ÕEÔÉJ¬âŸ?ËÑ­"žÈ‡~S‚ <‚mƒÀäôr„¨ƒ ÞÜ“î>Ü®êzУ®ò�\ì.QáWå‰ 1†ÚxÌ eÛÎéVW» µ½êz‡ÇØM'5Qa�ºÊñ,ýs%há«X®”TS9– Uøð-Ûw[â=Ê1à¹KÛaÑe§tª-ì Œ9 Âë‹êX‘Ûa¨W)±L¾�õ‹c¹N•²rý‰ùòϱ ]ÝãœÁs/´ƒæ`mËWרŸ�q_/Ö†8ÿàZ�p·Š€|äÕ¾UE ÀD“ݨΛÉÒuNM}…Y¶xÝöhsý_ÿq¥õ² ‘O÷š5j‡óú¶·ã^êÉä󳔉û@ÛyÖõ4jœ;Ç3Ž“"®R“&`¸&Ë©{—ó~cmPÿ …Á‰íkÚ«¾Þqo~vK9õ‘É®k÷ÀÛÕµ;öú.Ö=ÑWãY´Ó%ÏÍäñŽgúõ‰ºg©ï±'›ïµz[™üÎÓûºžî{L¦®÷0ﱘCy‡âꬾïcu¸¼è÷Cà¦L èùCÃÈ1½lÙ2Y¸p¡,Y²D/^,[¶l‘5jHÍš5¥S§NÒ¹sg)U*$ò¦ƒÊÁ²uëÖÉO?ýä98fʶoß.óæÍsu×´iS©R¥Š«,ÑÊìÙ³ÅyMáœ7lÉ%„ãY¿Þ›C§Zµj’ŸŸïiº°°PæÏ7‡"j×®¯!1�@øœŸçð[g‹$@$@$@$@$@$@¹D¸&QÇ`{é%:ž3ºÔ‘®MÌÏ›àXzÏ™ËEÊK7�ÛÙ×iJ=¦u gQÂåëŽÏ·<¤M!²èÇäÍì}*)qõÊcË“šX�âX¹¤qœ÷j`ô&G»±7l«[¥œÜ~ZsK¼ÄºÉœìövÓØímxÿ‹šˆð�ò¬†wt,ƒHºM‹¾‰º Ö‰‰Î}!`ݦ& \¡¼€�Â=ê@ÌEnàÙšh ¡z©�\ßÎ~ÃZ¾ªO¾š¸°U>0„϶ûˆ%JWSâè —·“òJDMÖ ¸Ú×®�Ef®v{ÁnO~°Ëœïé8§ÎöÓ±¼8Á1#dt<ÃgëÉ Û¨ÓåãU³¶…ùYÊÔ}ও›XÞóˆ°ËpMšDiÔOtˆÕ¦^Žèƒ«'ohy×ñà•‰+ä¢#è»äܺ)…D2‘Ê~™þÎKfü©ÔI÷=&×{˜÷�X쌡¼³ÉcºbYwxf¢¥Ót�é ¡¼§OŸ.÷ß¿|óÍ7q‡ ‘úÜsÏ•k®¹F JÒŠŸÀ�?þ(]t‘k wÜq‡\yå•®²x+6l�SN9Eö:¦€ôíÛWF�mívë­·Ê÷ßïi¢mÛ¶òé§ŸzÊÇŽ+C‡õ”£àóÏ?—V­Z·±�H <ô˜�%["     �\'°ÄáÍèçXÎV^»:$î®Û>¾¡‹a»<�o=¥©\£DæTíT%fÃ{¡‚“±U›vZÂ!D§Ý~Z3+ 7ÂQ'cw*�ô*üû9Aô�ôÖÏÉçªN$H�ã›é¨ò_¯–á.°Â±'s,¨³¨`[̪};Ô’WþÜA®VÞœð€�g1·½œðà¤xÕ2¶ ¢ÑãÊËùN_Sž»±Dh}@ÍëT”QƒÛ<þR±Ã®jMJøá�Ä×Ñù‡×—{Ïi·ùt�Ó¸�Ü//s¢¦k)oêÛÕDˆT&¨„ùYÊÄ}øÇ7v±Ò <øéb½ÀìI‹U"á?Ö~¦ò;Ô™�TÎu=,ÿ½*´8& á|ÐÈäwži4ˆ€¡[¹2˜V”ØÒ}�ÉÔõæ}ÀDMŸ˜…:ø~ ÃÜ¿F|¶Ø¿k9¤^QŽ„]AÌþtš.Lûñ˜FNRˆŽ§Ÿ~zBQǯYˆ•Gu”|òÉ'é<<¶�$�îÝ»‹~îM"r¼æ&Nœè¥Q÷È#�Œ·‹µ ^ÖÞ°©öŸ°#V H™…锑q     (6xΆ“ › ³©Š ÷y³òò{wKäÓE]S?m” ÷ÉM]åò£ D>Ó\ˆ˜½ZV—÷¯í$×*QÚÏ3^´ûâåíeÄ9-íųz*¼öIík½™ñpþ�«;Ê#ç·’¦µ*ÄlžâÏ_ÖNþ¢<¬+pF¸Ñ×ÛªÏcÛÔðäB6 `ýÖø¢°½žE§ÎB¹·oTY�Ó|-Øõñg+x?Dzc•'ûgê|ß¶¦TU¡ÀMv¨ÊYûòí¥� �ŽPÌÙbx>ÿÀ¹‡È{×t’“•È^¡ll � [~‡š¤0nè¡�ÒT 9g_U">D’<-¿5ÚÂvô�ñ<¤&x$óYÂ~ažSÓ=&Ñ5bºÖcíó&G\ÝÉúŒ¬>û‰ N‡-ëV’©üË?ÿý0ùÓõíâÚæg)S÷ôƒ{È7·u³Î-8¡,‘�9®!“UPÛôM¦óæÜaÕo>¹©³ÈZÞ¨&  ßu&Ì<îÄ÷¬dÆëM´¯Ÿý2õ�gûRijZyåLUc–…y�Ñ;IÇõ®÷�k?]ß©èk�A™ŽõYÔÇ–hý %Ϊ Ám½úà"Ü~è�Ñ¥¶à¢L§½þúëò·¿ý-ÚE¿~ýäñÇ�®'³ðïÿ[zè¡dªzꤾ=à ïéÙ&Ož,gžy¦ ¼Ï ä*KçÊùçŸ/&LˆvQ±bEùí·ß¤\¹änDÆ “W^y%º?¾ýö[AHpØ\`ô˜Æ¶Ç{LÎ8ã ,F­[·n²víÚèºs�ÓN\&�ð`ÒЈ#¢ "?îO4      ° lÚ¶[�Gaurá‡ñÜù‡a5•`»V^YËÛÔÞÏï;¼g­(ô‹0Õ/“ Õ›j+7îTù�·Zâóf•C"*Ž©±Ê\¿jrÏÇð€xµò¬†w9<¬‘ë"c~ÍòêU$ZãXP§¼:!0"ä³õºCSyh�‡Þ3—o•w(F{,aã‡@‚c@>í*_±.<%Ë\þXS(;víSç|§%Dcr 7QB|eƒˆ¯m„¨çRê¹pYu¬MÕä…fAžò¥Õ¶<婌¶ÁÏ,§Åë%=ÛÎ9Бãçh‹Ê ][å£cçùMÔ;rU7¹å{W5L2xM Ó0+̹êg…b_¨Dÿ–Ê™­½ºîã ã®Æ¬„}Ntç{3rkã>ƒW�ºöÀeÈ•Œ\ìxÇä�°,¬ÏR¦ï¸/.T‘ þX½ÍbÏsÜOp½`¢GÓZyÓi¹A Sßy ñÚ+å–·~w�¹ëŒæÖäWaŠ+é¼Ç¤ûzë>dømÔøæï\ôð]¼ô¡£\e~WB›ÆUCÝ(�ß$SÔczÆŒ2räHÏp!8÷ìÙSZ¶liyÑN™2EôüÃØ zþ×_}À Ó€ÅPpì±Çº„ém۶ɯ¿þj�Çd†£{87oÞ<*J'Úû:…é9sæÄ¥µÅí$@á Çtx,Ù � � � � � ”;»ÄÈ�Žc„ÀÚMyÔ¦Û táÄ ‘ÕS"6^ñ ᑆA¬ëظ²õ £=½�0¸8Û„Wy<ÏòVŽˆ¢Îý²a¹¢úbåHs|ä»7«f“®¶Â>§®ÆC\Á¤¼â]/!vg‰ºa|–2}€8Ïq¼h¹O Sßy õþ$¯w;&|µtÞcÒ}½‡ù�ºÊ´f¥ L‡d¡ Ó!�'éfta:ÕÓŸ}ö™'|ó!‡"£F�òäÞºu«<ðÀò /X‚4ÙµkWùÏþ“ôxY1}Ž9æ—§$z‚`Œ ‰léÒ¥²páBWµ£�>ÚµoEµõõxûr �@úP˜N[¶L$@$@$@$@$@$@$@$@$@ÅAàËYëdü¼ ®®!Êݪº«Œ+þ ì5ÚF´�°,vr‰°zHS;A…i„jv„ígžyÆ#J£N^^žüãÿ�—_~YêÕ«'�:u²–+Wgv s\N�@›6m¤~}wŽdâñãÇ{:„v<«[·(ú²eË\¶³ß† Æk†ÛH€ÒH€Âtá²i         È øð>ÿÝrùó‹³<½öh^Uj¨4´pÀ;b¿ÓòT*Œ°,g=¦wïÞíbP¶ljÝï¿»ãÏ|ðÁ‚Îñì¨£Ž’qãÆI©R¥yŒS1ˆ$‹-’Y³f Â=Ãà¡ÝºukAß©z|ë}§«ý�7Ê´iÓ¬œÍëÖ­³„yÁðF:fý‚¬ÃËù­·ÞŠ6�1oÚ´IªV�:Æ)$cgä¥Näi}â‰'Ê«¯¾í m4kÖLpMþøã�Ñò>}ú¸êE7p�H íôwÈH€H€H€H€H€H€H€H€H€H€ر{¯¬Ý¼KÖlÞ)sVʯ‹6ÉÄ?6ZùÈM�_Ú‹N‚&.~ËÊ(Qúâ^ ¬‰vÙÀ^ üž³ÂtP�iäˆvX”!Çt<ƒ÷t*†0àwß}·|ðÁ²cÇã®x/»ì2:t¨%Œ+Å(LWû ,�k¯½V¦OŸnìBì¿þõ/©P¡‚q{¦ áåì¦!ÔOœ8QN>ùä˜CÁùÖ=¦;ì°„“4h`å Ÿ7ožÕ6„éÁƒËäÉ“çÃ6„w Øv9ßI€ÒO€ÓégÌH€H€H€H€H€H€H€H€H€H Lþþƒ¬Ûº+é&t¯+ýºÔNº>+&G`øY-¤mƒ<™±l«tmRYö¨—ÜŽIÔ Ï÷:‰Î¬T˜nÒ¤‰k8«V­’)S¦¸Ê‚®@¨ìÛ·¯¼ýöÛ1EiôϾ§Ÿ~ZN?ýt™9sfÒݦ«}„9?ãŒ3bŠÒ ò22D/^œôxÓY±wïÞRºtiWº7´k£Z�÷zAA�«8Qo»2DgÛ €ïÝ»×Êkm—A°ïÑ£‡½Êw � ÀgRŸ€”á!°;          �ìÞëv*�·kߎµäÞsZÆ«Âm\pD}qN‹PEi 'g…i=Lkªa¥[´há97Þx£²Ú³ÁGÄʤ$Ü"Ä÷yç�'6¸·›ºOWû&L�K.¹Ä ƒmê×Y¶yóf+×¶³¬¸–«U«&]ºtquŸH˜Ö½¥±³Spv5¦­ t¸m8_ð,wöÏëlñ&·ÇÉw(ÉL"4½¦Kò籑 � � � � � � � � ”4•Ë»MÇW¯Z9¹à!òü¥í$/‰ú¦6XV|rV˜ê1}Î9çx¨#|õYg�%#GŽ”-[¶x¶'[�døðá–'´¾òIŸp rÜqÇY¹¥õí)þØc�éÅ®õt¶ÿè£�Š.æ §ö¡‡*W\q…\tÑErÄGDCžÿüóÏ®±çŠîí<þ|Y¾|yÌ!9…dTjذ¡´jÕ*f}ç†Ã?ÜvýÓO?uyÜ;…kç~\&Èý^–¹žÙ � � � � � � � ”<H‚Y¥BiAþQûU…¢PÉ;Ñ<"(F¸ÇèV¶ôAÒ9¿²\¢ò¿xy{ùåï‡Éàžõõj\ÏlŽiýzõòä†'ö#�<"Ï=÷œ%Â"÷síکŧG>i=7s­ZµdĈVhoçµ�ºÃ† sy(¿øâ‹rñÅ‹nÜÞ/]íOš4I~øá»ëžè?ü°ôïßßUÏê›o¾9®ðëÚ!+ðv~衇\=Á+zàÀ�®2¬`bÃO?ýä*OEL®X±¢ªÛöº~æ™g\RiË5®� ø"pÐAøÓÈm¦Ý<¸F$@$@$@$@$@$@$@A”V‚ôœGi‚û’ �@\ÃNo.;vï•ê•ÊH�Je¥Vå²R§J9Q·Z !�³Óz(ï²e˦|Jàܶm[ã~SýÄOÈ‘G)wÝu—'±q§ý…>ø k3ÄÝ×_Ý#J£r9£§A4}ÿý÷�E®åtµÿÔSO¹úÁÊ�wÞé¥Q.£F�ÂbÖXÇŽ¥fÍš®ñè^ÑöFˆð………öªõ®{\»6Vœa¿wîÜ­Q¿~}iݺut� $@ÅC€Âtñpg¯$@$@$@$@$@$@$@$@$@$à‡À íjÊi�jK¯–Õ¥]Ã<©W•¢´ŽÙ¼OÎ ÓACyã¤Ô©SGÞ~ûm‰çݺcÇ�3êŒ=Zœ¤éÄxòJ2$®P‰¶�:ê(Ws‹/v­Û+élb­ÓòòòdРAÎ"×r×®]­°ä®Âb\AÈq�£íѬK¬1y wïÞzµ¸ëz_våXåöv¾“ d†…éÌpf/$@$@$@$@$@$@$@$@$@$@$� �œ¦u�i‹~¬J•*òòË/[¢sÓ¦Mc6�œÓÅ=`À€¸ÞÓ&Aû$2§÷-ê.Z´È¸KºÚ‡Ð¿víZWŸ§Ÿ~ºTªTÉU¦¯ ßt6YŸ>}\ÃY³f�Ì™3ÇU†]˜Fm\ ©¼í1¹A·xôº\'H ÓécË–I€H€H€H€H€H€H€H€H€H€H UþÔÜT{ICý0<¦�ÃêÛ·¯üñòÞ{ïÉÓO?-óæÍsnŽ.O�:Õ mýÚk¯s@›„ãÒ¥KËÂ… £m˜öíÛç*6µƒ ¦ò0Ú_¾|¹ècH&uãÆ�]ã.îx+#׬óX B;�aÚûí7×Põ‰®�1VÐúÃ5c¼¶Sõ¼¶÷å; �@¸(L‡Ë“­‘ � � � � � � � � � �@9+L‡å1턇<Õ]}î¹çÊW_}e Ô?üðƒ³Šµ¼dÉ6l˜åi­o4y:§š»m®^½ZöîÝ+:�–®ö!LëV·n]½È³^¯^=OYqÔ®][:tè Ó¦M‹Âôå—_]Ç9Õ¯?ç ž}öÙ²mÛ¶hÛ 4ð乎nä �@F P˜Î(nvF$@$@$@$@$@$@$@$@$@$@q „*L¿4a…lÙ¾GNéTKš×®·ã uaÑo(oÓ8à ïi¼�wù†nð„ÖþöÛo-ñú¸ãŽs5±~ýz׺ßxAë¢4ÚJWûy­[ùòåõ"Ï:Xe›Adv Ó?þø£%DÛ׈Æbvûöí}<¦™SÚ:îDi'@a:íˆÙ � � � � � � � � � � $MÀ펛ônÞŠý›*{gž »@Ž`’L[ºÅ[)Ä’t ÓÎa"÷ðرcÅ”7xܸqΪÖr~~¾§ÌOAÅŠfa?]íW«VÍ3Lg8lÏÆ,.ÐÃroݺU&Ož±.LãÜf£À0H€0}†)L'ÄÆ $@$@$@$@$@$@$@$@$@$@$�1¡xL6½@~^°):èí»öÊk?®”{·Œ–…½vŽéxã«ZµªÜ~ûí/i§™òP·hÑÂYÅZ¾à‚ !žS±J•*«§«}S®èU«VÇ�í…˜LP¥JA.iÛ F÷èÑCV®\éÉî7Œ·Ý6ßI€²“…éìUiœ_¿BþXãvÚ J!a:è üì¯ ÓeÊ$•|êÔ©2|øpéÕ«—ôïßß©çÏŸ/z›×úõëå‰'ž�¿ÿýïžaš„iTºûî»­ÇÎÐ~Ÿ>}ä_ÿúWLïix'Ï�;7¡`ž®ö¯ºê*ç�­åaÆÉo¼á)_³f�Üu×]òÑGy¶eC<Ë[µjåÊþóÑófëžÕ®¸B$�30iG7Ó=]¯Ãu           �"7¼>W.{~¦Üñî<9êÞ_äë9ë‹6\J^Í�Ó‘IØ'éu™Ö‡T„igøi„xÆ B5ò ׫WO5j$åÊ•x:/^¼XvîÜé9úúõëËñÇï)GAݺuåŽ;î�Ûo¿Ýµ}Ó¦Mòä“OZ¯jÕªYýT­ZU eÆ ²lÙ2�‡ß\ #FŒpíë\IWû={ö”îÝ»Ë/¿üíœo»í6yðÁ¥yóæVØkˆ»Ó¦M³„|œû}év��Ž&µxCCè·í»ï¾³­wœƒ.]º¸Ê¸B$PrìÝ»·ä �„H€H€H€H€H€H€H€H€H€H€ÒL`îªByëçU®^žÿn¹Ûº†«ÌïJ(´ßÎƒì§ Ó©†ò6õ ¹ åƒ.[¶¬<õÔSR«V-S3VÙùçŸ/ß|ó�Ä u½qãFÁËd .4»ÊÒÕþ-·Ü"ƒöx�ÃC/ÝŽ8â™8q¢^œëð†FÈöXÖ»woI庉ÕËI€²“Cygçyá¨H€H€H€H€H€H€H€H€H€H€²“À¦m»=ÛPè-óTJ² ”PÞ†ªÊ‹6Éø¬¦ Ó©xL¯[·Îg¯byT?üðÃÒµk׸mÀ“xôèÑÖ a¥S±E‹%¬ž®öá5�ÐÝuêÔI8xn�9R*T¨�°nqT8ì°Ã¤R¥J1»fï˜h¸�rŽ€)r…éœ;�0 � � � � � � � � � �@1ØkÐwKy3iúa(´ïÞìD˜5j”¼÷Þ{rõÕW{ò›†„ßmÛ¶µÂY�?^Î8ã S5cYß¾}嫯¾’ë®»N:vì(yyyÆzÎBäšNÖÒÑ~�=äã�?´]£†Ù5ÿð×_|Q4h kž�†pìðèŽeõ�Œ¡§•/_Þ¹s×�>aBo+æÎÜ@$˜…éÀÙ � � � � � � � � � �ÀD`¯Áó¸TˆÊôA*?°AûN�ð'¯‘«^žíÚ©×:òä…m\ea®@úè#éСƒ½šÒ;r<ÈÚµk­wˆÞ4 ¸Ú¯0C>#$öüùó­¼Ò[¶l±<ŽáÙ ‘·I“&q½|“9°°ÛGîë™3gZL*W®låšFnÛp,ðV„莎Åä½h×ç; � „Mà¹çž“{î¹ÇÕ쫯¾*ÙO#          HL`¼�2`Ôo®ŠG¶¬&ï\ÝÉUæw¥Ää˜Ö=SS!¯üüüTvó]a²“ •í·ƒ°Û‡í¢õq|ðÁz×I€H Ø ÐcºØO@$@$@$@$@$@$@$@$@$@$�CŒÓ¦œÎ>�)”PÞ¦ñ÷ÃŽDAByÇo™[I€H€r�€)J…é\;‹/ � � � � � � � � � �@q0å˜.b(ïP„é⤠a†Ú.ŽãaŸ$@$@áØ»wo¸ ²5          (Áö”i“ƒ²_¡ÓÉAžþ'®ö´è. Ç´›×H€Hà@&@�éùìóØI€H€H€H€H€H€H€H€H€H€ �¡¼Ã8ÐTÛÐ…izL§J�õI€H dÐ#k”ì£åÑ‘ � � � � � � � � � �@0‡i 1’·„ã1íu˜–}iL2�¶uÁ¡L™2ÁHso  �E@ÿž(Qǃ!          �� ì1(Ó|Ži“Ø@�é�¯<6G$@9DÀÊ›9¦sèr¨$@$@$@$@$@$@$@$@$@$@ÅNÀ KKÖå˜Î+_ÚÊTæ©ä³@ã�fè1í&w# �JÀ4‰©„*‹H€H€H€H€H€H€H€H€H€H€0å˜.¢2J(ï>mjÈáWsìù‡Õw­‡¹bè1&a¶E$@¹EÀä1mú®È­£âhI€H€H€H€H€H€H€H€H€H€H söîõöfŽéÐ3¿wm'yáû岡p·ôíPKÚ6ÌóŽ<¤“Ø@�é�ಠ(!Ê»„œH � � � � � � � � � �@F˜<¦K…¨L‡&L¤p\Ú»aF ˜ByÓc:#èÙ � d%zLgåiá H€H€H€H€H€H€H€H€H€H€rˆ€)Çtˆº´„Ê;Ó�P„éçoŒŠÒ@²{ï>yi ӹ}ðH€H g Ðc:gO‡À/“§Éߌ—}ûöYµ*U¬(Ïø„>÷нž×ª|ÜVל3%;¾Öæ€ðxñk?k*;YAµb.P»v4d`?j'®ÉN<Ç${»Û¾ êèB]�£B/^lt”Îi¢xÿ�Fó.SRZF PäååˆÃqE�°×7<x�0—Ï0±@ ÞßÛc�I2ÔÏýÊëi2Œ0n#�ظ"˜ö{D6ÏÊ>¦{2÷Œ#Àx� ¦½@•Ëaî¾âô:UQI'+NSÃÅF­œÂ®]~«s²³´ú±m4�I}WÅ‹áÁÃǨ\ÔU+L�65]¦üN 'È»Qgq¯"ä;"ê¿zåŠéñEÁ«‘j/ÔÑ¡#ÇŒQŠûvÔ¯O/JI1ŸË,?UAõõ �ôór_]á“§*©®¾>ÎÌ̤‚g�PwEåi=¨]ûôêI0ÝŒÎ�¯¥ÓUg¨RôÁé3ÕZÒ®]ò© Kgê*p±ÃDU¦ãõú5f"/àÑ�1ƒzbE)©Öqå†&¦[ãñØñtùrëÉên…š)eàuøèq:Q~ŠÎ×Öjs~ÿÅEÔ­¨00ßDŠk4c&X�nárøØqÓ<Ö»gºrõ 9VJ˜cÛµk§Í»ýz÷$¸©jnÙ¾[ÌUÚ=æÌ_}zõÆ®é™[¼—ž(§ÆK—Leë�´´4O˜ÏŽŠ1p¶ºF›W;vÌ¥®�ó©g÷bÂüfG˜ÃJËNjmÀsuFzº¦‘[,Æ\X$ yõ;�eû1vï;hb.EÐ7Fº,Þ-ŽIlã<`L«ßŸ9[CÕ55zPô}Š6Þo0÷ìÚ{€ªÎV‹y楋ñ‰wš¢Â®TXÐ…2;DfÆ=Q~Kvóo¼Î�±\¯ÝêS»¡‰òËNVPCÃEñ~-þĵ^üanÏsÆb‡Ú533ƒõïò½Ñ®.·ã½äÝ-ܽZ—°váÏHἣȿÁvb-з·±8íÞ¯÷v½âææ+bŽ=Ggª« óí1G^ßbø~ÌÍÎßù4 _RªÒ˰»ºÕ§våGïçwž›ýËù˜{µnDÛŸœŸ`üEàŠBÀÛ^|«¹EÖ� ·Jö°YØÀ¦¼=›‹fF A�׊m³ÝFxnñRÇ-Ÿ;c M?Úqz$„�äõ·Þ·6|¼jÝ0s �¹n-^úŽIÐn}gÅFÈKKÞÒ6DTLB1uâXš6iœ&Œ0¦ÁÆ2ò†"–_~ýíPÉè‘ûï2 •‘aÉ›ïiØzæCѷܨé½�W65tê*Ç_|äSzP¸¿ûÑŠ@7wß1ŸègŠÓÀcé²Äa€J=Jy…êÖ›çP®ð‘м/¨ß¯1ÍxŸ~ŒÔ+R ¡#ÙHÔùwsc×Фq£i®ø& EÄAÕç_yS$3ª—µ£o>öYí‚1¿ïí¨ÂÅ-b]^¾z=]ºüÀsº86|È@š3c2amEn÷i¨ú"}îçwž›ý‹ùQÇ}é·»^_F 9P ¦Ýk›Ye$Âr­Ÿ–æ¥8Âbm³ÉÂ|è21Œ#À0FäµÂøŒï�¶ŒÀúMÛèe!h°Ó€6ÐÖ~÷£•š²Qû1\Üš…°bñëËl…Ò(¿ÕUë6 ÁUht¸õ;IŸ�Õ¢®§­3hO#Fª9wÞ¤ uu¦0v>7·‹Í®'Ÿ}9¤Pe@‹ò�ϼH{Åg¬É¯1“ã%Ö}! ¡!`‰ô[È�ñ!„EФ F»÷ g_|�°±y1füÀe¹8ü£š[-¡•k7Ú 5ÖlØB°Ò`G~ðn¬ûbc#áo�XW6mÛ©J#=æùOÖlÐæzc~ý>X›�›ë§Ïœ¥ [¶ÓoŸ|Ž0'Å3¹ù;�—vnݱÛ �Ä]Z¨KÞ|7ˆP\^¥µâ÷ó×——Šu»Õ Šÿ‰ú[2¶‡bÜ~óc{¢½÷’whðþñ™—è£klço;þ±Ž;9„h—?Úx¯y÷wUÛ£Y—† ìo)ÒxˆÎòÐq@X†’wÂ{÷,¶¥ Y<½­kæ3/.Ñ؆Jƒ‘KÂ*ˆýIŒcù`œÌ¨ß}*×NØÏï¼pøŠ×´^¼WÇk[™/F€�€Ú”·Jº,UŠ„”èʆH7cT€p#À0Œ@r ¯ÉÑ*n#[wì¡�V®µK‡ `ÕºÍÆ¨°ïaÆ&ãœÌßí;pØIRWÓäæš5TŒ‚鯯Kš Fc…&…(ª n˜)— Ø/{ïc‹Æ™œÎ†Éò×…v5LîÅŠü3‰0^bÕz½²&fªÂ´·ž6ØÕ¯ñóÝ¡¬è|"Ýs¯,u4?éyÜ3~á"xÑÛƒ+êØÌéÃT¤Šüâ]®}°z½³µbó¶]š)[¹ М6ϱŽm‚ðx%·~§ñÒ>¬Ï8`$À‚…‘x 2ÍE€³1TZV®YpÆw"ÿ–Œí:(en¾ƒÅ c›"½÷’wXíYüÆ;ÂTw«[˜pø„PÚ©8å:Ië5ï^â¬}‘®Kýûö¢ÌL³ÉH=%\…"•;Vs$„ÒOþu±fN>ßòshê.^ú¶åûGO«>Õë÷ê×w^¸|Åkz·ß«ãµ�Ì#À8C@œ-¶Pú˜¶Jʱ™éý¢6åíÒÑ•ÛØx™­¦ÖÒ…ï±|¡Ía'ÑÕÀ¹F€°G€ÓöØð“øG`�0­ò1 ?^ÁÁZ†ßĺM[•Ià#²{q¡ÐjZiÂï1üÀ‚œ ””…ŠH£F`ø¾™á£¹BøM…F°L0!ÿÖúLÉ Ú ²�郒?i¤(|£…¢¼\«Yl9ÎhÔkÄëÚ-²†'LÌÂ/ª‘àS{åº�ƨÀ}§Žy”/þ`&é<úWÒÈÆ;5„0÷.\È#ßx1^P‡ßc&Úñžý3¨'V”šböÄ$kf:áËëñhäÁh?Gø1ìÕ£Xó í�ãBà šMF‚eÿ¡£4TÌNÈ�1£×ã'.¨sÜõ#©¿Þ´bõmNÔùÀóÞÈaƒ„†ð:!|/ ›Æ2yÉû°Á4ÿ¨¨³Aô—ÌÏaºVïŒÇîbž/*èJ؇¿i#Á×ê^a¶ûºC�ÑÔ17—Ú‰ÿàŸßúøÃÁøcµ£�V¬Õü[ýB¼‘¿ÓxiÞ ^®?dš2alÄÖä²¢ ÷žôy‚s¼3•œ( Œ_½XpÀA¸¡blË”è¿%c{*Eßé„CL0‡Ÿ)üW‰yVuD~Óóâê.~­×^ðnÄfãÖÚ|eŒÓï±W ?çÀ¦’qhïVM—1ÇÁïtué»9ÌKÞ½ÄÝ«u JO0g�CTFÚwð0u+*0F™îñ.ƒw#¡ïUs Òxõޮ׫Wò¾¹þ ¾Í1`�Å\ ky#L»/_µžn¹q–žM»zÙ§¦Š\ øñ�§³ëf¿ú5?ê¼ëWý}áH¾ÝõrøÊ0É�€Ê”w;å{æ��ÁL6°ÆtüuÜŠµGèëß�ÎÖ´˜É5¬˜žøŸ{¨¸(/þ˜uÀQɉjúû˜}æ}ýK3hú¤~rs’DEàÕ·vÒ󯪅8r›~ùãEÔ=AÇ·Ü–d ËkE²´‹ÛÑ6¸Gø(VÑá:R“×{Å&¬l†š„ `þ 34_ÒÆú Áï²÷? aÓ˜ÃþBcøD…i]àŒÔ»öÐ|¬s‚?h#린á_òîÛç“h÷¿øí“¦MÀ‚.tÏÂ[,éœDäå˜5¦.j›àUëå!¾wÏîZ°V2å­Ò–^¿y›¶É£çÇ7ΞF£†1Fü̾¿|µé`¡#%Ú†—^§)ƒx1^PG,ÆL4ã<û1fPO¬Èª‰™6+^�GCc®.|4N¥ô´´ÀcF`VW>²vãÇ‚ií˜Ñòüþoš3]ãí¯X^¥³¡]çÍ�A9ÙYš0HC˜!“—¼Ã×½NôýæOÏêAíºïšo`ðûà}‹¨³ÁbIJ÷—ksš1ƒJƒÓnþ®ó1ðÁz±kï~“�L±±ùþའ�ÅÇŽ¿ÓxhÈÙêÍg* ÂßëGšŸÇê^5ÏÀ |ÜWTšc0™o<§óœè¿%½Æë0!h›ÃÌ€ßX|#Á2ƒlé@~3–á.~­×^ðnÄæ”4¶ð ï‚7Ï™¡žII‰_C•^òî%î^®K#‡ Q¦�Ðìé“�Ýnº‡uy^�ßœ)±xõÞŽz°^ÊBrÄwÌË¥ÛæÍ |¿ ó#\ˆ¼÷ñ*ÓwGš8ÈÒ§W$1‘—}jªÈÅ€ßy:»nö«_ó£Î»ñêÖ{µ±L¾g�ÄD@¥wì¢\š\L«ô`½Ó—nÑÞ0v' ¦�h8¿/-«¡�ÛJÅiö:ªª®'‰/S§Ž¨K~õíÝ™¦Œë#6÷Â"uõ—èkß{Ml~·š2Ú¹÷$ýà§ïÑ~~�sã(åÙêÚ°Õ¬ÙUvÒìs2ŽØ�+V.Bsþˆy#ô¹9añ¹ïP%]njµ!ѹs–ç‚à#%—�ÔïvLc¼³`Ú�ØÄˇ±á‚keâ� ›·[˜™1e¼E(�Dû÷¥[çµ§—\ðûrØ`Mð !¸‘ „ÐÓÆx¯îóòÌ‚ih^A8�••i˜é<i²)ïüNæCx8yŽ�3µ£{- žÝ»™£Eèú‘ÄVMšEC Ú]v‚iK!.EÄbÌÄûxq Úˆ‹Áf·qÜäJ+B‹ñØOlÎοÁ¬u>ÁûCB¨ø»§ž3iÆÂdf©Ð†vµrcÌø�K¡UŒ�?„|F‚yA´�ýæÝÈ+îQ?æ¬î¾Ã$”Ƴ‰c¯³¦/H‡y�ÎŽ²„IUü¯±âpÃÓÂ9LyëtòTeà ‘×h§ñÐBxnñ–j· nšwVëìæ™l±–?xÏBú͓Ϛ´U °ÜÒ¿oïÜÉø[Bû-¸)ÐFÜ`ÿlæÔ‰Ú<+ °Tï`±ÆÅÄ|˜?x?]e6sÞr#w+ “[ÿ“{Å»¸C õGº.uýÖ9¿á`ŽNø]ÀÚ¬�¨è€°ô"Óñ� úXXY’ –±¸çv‹E'Ì�°`‚5ë¥×ÞÒAKö®Ûn&ÙI¬ûTn“Ó°×ßyNùH¤tn¼W'R{™WF€°G@¥1ÝþÚ7¬}.çO—::/Û³”²°�ÓΡ†�ðÉç6ÐËKwБ’VÓNª:¡ôœééÛ_�Cýú87/´ï`¥I(­—½ns‰~Ë×6„Àú-Çé‘ÇŸ7µø{_¿�þæQû§¦Ä"Ps¾�n¹ÿ�š‰YýÙü¹Cé÷?»[ò•° ¯–Á´! l5štDÓabnìõ#lQ 63{ Á�¬­g›Áæ4¥í¨_ŸžâðÒ1Óclþ¨N雹P ö`΂éÁ‹NØÔÑM›×Ԝע1ÏÔ×›5É:wê¤gÑ®Ð@‚Ùb#�5Ì$\4>Ã=6ÖoÚ¨qVmwÄzG±3ñ>^¼CÜYÉ8¸€¿H)ãqö´I¶ìæßšÐæÛ±{Ÿ) 6€� ¦Ý3~ã‚ùÅŽ²²Z„ÒÚsÕ p)£ß¼KÕkÁ¡ƒˆMì|Ë#lpC�i4µ «‘P�0‡‹¾6nºÃÔw͹Z±©n>Iùnæ‰öwê&/á–u©©‰¶lߥ­A*Ÿ¹h&èã�‚Í3Ð<»nøPaß|@ïŒ<ÓÉø[š9e‚mW ØÏ¢Y©z‹\lâ�¼_¹Úzx^g‡«A0íï~à®cmw�f]9t0­X»ÁTô¾G”‚i¬CG$÷DŠêßÇ”ß�\}ÈfQ/,´Èn†Œüà}ëþ»n'¸Jš6q¥¥YEñЧFž�Þ{ý�ç”�DJçÆ{u"µ—ye{„§? ¹©1íŠM• \¥êmiI„²°�}L;òýOЬ…¿¡Ÿüêã�Bi”!öÛîû=õ¼Ú/¢ªæŒtëK ÒÙÅ«Êà¸äA`üõ=Å‹­ÙÌäª Ö¥ÁZ¼vc‰I(�´S'xÿ¢Ï~уõJü?“׊øç˜9d¼C¾Âäß|2Ãr0êíPcÑ® l‚¨„zúN¬œL��­Úpò3/Â*íl˜¶�½&€Æý@Ã6nA-þ¨Íoëùùf 6rd’ÍwËÏîß·—)Úά¸)‘‹�XŒ™D/.B“¢ü�iÂtw0¿ŒAö7Œ¸Z…eÄËäÖ˜ñ¹Ñ„ã�wXÀP„Òx6DøÌÖÿpè)Rê¡Ð@„/k¦èX¿i+½üú2úõÏáÿ:Í÷­\*¬5L�8VŽŽyØÙ<3ÄÂç9q¨ÁHÉö[‚p,˜p´Sžù}X¨ÞÁâc?…sïïð×+Ó‡+ÖГϾ,,æl'ø47Î‘ÓÆ2ìï~à ·h֥ÉâÍ'ÃàgZEp³mb# ÐOøWï‹Ó¹}oÔòÖËÆš «#¡ipÀG%”FÞxèÓPmP=÷ú;OUg"ǹõ^�È0ïŒ#Њ@BhLg¦YåÛÙfATk“¢¿“7VYc:4¦Ï.ÞBÿü_ïX„{¡s’ð5ÒLÿö³÷ÄéÀ\Zpch_RCÒ€¾]èð1³Fö‚#×îpÂ'§‰O²³ÒiÂè^´f㱃…yìK—š…y%góĪõVAöìiåyuóøg§Òƒw�±ÿü«Ûè¿_a‰çˆøB@^+â‹;æ†ð�s !OA×ÐÖP: “²ÑPžBðl,/Þá`2WÖèÓÓ5çZÐày€äÃ'%HL×^¨×ÂÆò;v4EZ«`¦Â�+5¥“ò̪bc¼úA±3‰0^üÀÞË:ü�ðiŠTs‘jü©ÊqkÌø�‹ª-‘ÆÅš÷tq)˜›ø´tBÍÍWh÷¾ƒÂJG9Aëë¼ðA^[[Gð% ŸÜùù)UXú�©å€�Ëáp(fÑC|~ãý:Ön#¯Næ™NßçzÞ±®)Y~Kz›:æŸ�ú>Ž5.z{"¹úÁ;–”–Y؃åü�:tÈ–rŠ…E îßò™dKfŸ"¼âÝ܃Aíº„ïŸžÝ‹èDù©@5úž®:C°Þa$¥o¡q R ¦‹ \a%Ö}i#¼þ΋”¯xÍçÖ{u¼¶�ùb�ððZcÚ•#\ãúæÑÌÁ�hÅ�V÷O }"+<(ZSˆxû0jå4>î^g7}ÿÇoÛ2S\”G£†u>ÌrhûîrÚs Â"À†üÑãÎN£§¦¶§§þ÷SôÝ-£m»ÊÅIÁötǼôݯ;Û”°e”$,³§0 ¦.6Ñæí'hŠC­gY0ÝOø@ïÓÓz2Ùm€àc2dÊï\ÃPNÏáØ ¯±á‚keâlòˤû0•ã�áìk~N�qáÜç1WN9^¦Å{$°0š—… ÚºàÖyºvÕ6/^l¤FáãôÐ*– Â#U„Ûzüâ¥öïezùÚ,„3)³Cù‘'áXŒ™D/ž€íc¡~�Ç\s„Žø�aŒëtþ¼uÎÒŸ¯n�¿q1¶!ÚûXóž›Ór¸'Òv@ð¼qËÚ´u§0Aª6ó “Ò*³Ò‘ÖÉù"CB˜Ý=jxdx” ^¡(Mh/"Öo�ÎIMý·¤·K¿:yÏÓӻƗ`¼…zæï“Æ�Öª„Ó:xw„ëü}(" …pît`ö?–û™^ñîî:¶ªk´ëÊ9lˆI0�¸}�˜ÓØo8tÄì²®:àª(÷2¹åê"Ö}*·ËiØëï<§|$J:·Þ«¥½Ì'#ÀG@eñÅME WÓh _E\QF•µMtðÎ4©t6Á`‘… ±|‘ Æg<<;Yqžþé?ßQ²róìÁô£ïηÞj/4Òÿz9ýåÅM�|_~t =þ¹©�p¨ Ÿû݃B³GÀñG±'Kü<†ÌšÖŸ~üK|‚µÌy;LŸ(?GÇJ«[3Š»™Sú›Â`TÈk…* Ç1m�úk¦©�ím›)¡¨}”‹¸›/­¡x�æ9̼ÓÀ«Ec¹ÅLw¾Ð´B[à?ºüT…V´©�y ¡1„lF’ýKŸ…{ŸÒÞª-nNÓÇbÌ$ÊxqŠa<¦ó{<:íSh“z8B:-?T_ø�K(~ÂykÞ³�>±Ãa\¤Å»Ú’7Þ¥CGÍûaÃÉ=@‡E2ÅïRöWºjÝ&Í<»«fj[–Úˆ[‘!LV;!y¾€oX#%òoÉØý^n¯î5Ö¸„˯1½¼çûïº�ÖmÚ¦²q²~U ÍÛw>\A[wì¡»nŸGÑZ(2¶9œ{¯x÷÷`íŒf]ÒË6d½¿|•éÐܾ‡i†Áo;#àШ‘† à›u#c½¸W™âwk¿<Ö}*·5œ°—ßyáð‘iÝZ7¡­Ì##À„F@µew>¦õf|afúÞ­}=J£.YØàÖB«·#™®ÿùË�„)´‹¦&¥ˆíå?üü^‹P ss2è‡ßžGßzl––ï+œq¤ÚÎQîg›øæ@â"0t`¡Ð43kËZÐv­[­ðG l&F ª“]¡òðsF YÈÌ´jÙªL¢&kûCµ+W¦�SÞF“u�ó;i�;´¡ñ\ÖîSi%811j¬;ؽ�ß·`y"}Æc&Räâ;_¼ŽGy3Õ/ËzoÅ+.:Á®±æÚa‘Ò'«×Jcƒš®¼Q)Âá僆má�tÎŒÉôÕ/>BŸýô=óé8�µiÛ®ð ‘Zì„HÑcÌ1r=òoGGTѵL‘¬×Ñü–¢á5TÞX㊿`Ïýâ{’ð¿þÕ/>LwÞz³vxÉp¦¾__ö¾e�3X›Ü~æï~án‡…¿%4(Üø©êl5U�iUšØèˆñ±v?"Ff¼Q¹ÊŸ²ìȰÈX÷©C6•ɼüÎSVÈ‘Œ#À$ JÓ.J¦]Ó˜öoY0�Ó¼LVNŸ¹@o}°×òàß„Ðùá{ÇYâ刿ýâtš7g h´¬F|à™OËåèážÝ;ŠS×ÎN3å6ÊíQÜ‘²2[ò:ZE[v–ÑÉSç…I·&ᣧ3 P¨ñ©§Ñëtz=wþ"íÜ{’vì9Ig«ë…À>‡†*¢)ãûh¦È�–c—®Yå/¦Ð÷¬¤ý‡OkÉõïJCþ»8®£ìä9­Íªz:³Ó½z´lœãyõ¹±iPJGKÎÒé³uÔ«{'�S�R¤>P•áe´œ_z}{ Š�kšÈ˵ K‰Ä�,À†_j'šÖ(~¬9-ÌÓWŠM¯*¡é–EÃÒðÁE¯S¶±š˜Ýêx3’q¼ãõ{ômÕ™VS‡ii)Ô·—Õ´9úþ²ð×§~+ð%¶g-_s˜RÄô­7 þ“ZÍÎ~¼ê�ö;€¹uŒÍiûø ‡ÜïáÔ©J+¯ª4Ç´T²T"cÑ|¹Õp$íÈË‘ÓÂD·îGåu¾æ›Ó:Ṍa¾á¹ž®KçÖ©}㟖Ò+h&Â傺ägÑþÓM˜.?³ Cp÷Õï.¡]ûN)“@Ø÷_ÿt«ð)éL .RWw‰~ðßïÒÒwöVæ 7=-|pîÓé¾2› t F÷|îi*æÙU¿ËË_{L{ôÂ’môŸÿû‘0CÚ`Išß1“~þÃÛ醃,ϼŒ€–³Q0 áåÚM%Aû BÛÕͦý&Žéò€„¶ÿðÃ7iÅš#&Á¬±}ðý¯ÿp3-¸q¨1Ú÷û}‡*µñn¬øß¿3Ÿ¹ÏþðÈO~õ1=ÿêÖ@X&8¶ùû�0nphcö�¿5ŽýÂèño¿JGÄ¸× &ÖaªVþá_ߤ—–š7žpØãgÿz»&ÀÖóØ]Ýïvõ8�—× §ù8#�Œ¨N¹Ÿ>Ó:صóp¿qq³=‰Ê{é‰r‹+|ßrã,%<˜§³2#×ÎÖ U™ –Ý2èiÛÒU… Ú?yÂÍÜ𥦦 / ³Å[iÖ´I�¸`7òÁcZÌ~à¿÷À!cµÚ}�,˜NÐõÚÒ0—#üžcÜ|Çó›wz ñ7öú‘š s¿@´r­ØC3¿·V ÍéX ¦Ýä=Ö¸Ëm‰4<@$…Ìy:Áœ÷ôÉã©ìd…É žâï›Î“~ÕÒêa\ á9ÜóD«EžÈ}êåwžk?îÝœýà—ë`�ÄF CZ{K2…rœ[d-Ý­’=,GÞà`Á´lýdú»/ÏÔ4&åøHÃálXËýN�§… ñkß{�ÞÿD-”FYg„¦ó—¾µ˜þãÿ™}ÛÕ³bíºã‘§l…ÒÈ߯_üÖËtüDµ]1¶ñ[w–ÓüûŸ ——î°J#3üáéut›òC“5“œÚÔW„°÷��öÑ·ÿý-¥PeCÛös_‰^{w°ª\†ƒ0#o$YÚø ÷{VˆSÆ­šÁˆ›=5¸é•ëŽÒ<�ûG+Ù ¥QNÅéZzì_¡ïÿøm“v>žùIªß…*ÎÈ“üÜéïð÷bœ…Òz™zn·ÅoîУW¸ÿå§ï†´ŒàÅx0á�ŒS„Åp6F )€yNù}IߨÖÀ3Õá¯ÁÊsë™ì‹šwÐJ‰”,u6¦¼ ÑÕç`Ê»ÞT%|QËÔYÓ˜6¯»÷¤åêCqrþX…yÌÄ yoëõ{<ž©® Ù í»¬Z³}Ö˜ö—� „‘ QyW #G ´å‡�Y¿oƒfP<ÌͱZM‚66“˜0ö:ËÃ�[vX„2H¿Ô2�­>'GÂ�V®Z…Þ�.Þ`¬mܺÓRbaAgS\¢þ–L�ð à7.n¾ãùÍ{0øaõšÔRËtVvŒg —÷xÂ=\amN×~×ËÁaº³âÝæ€ÂŒ÷Èa1L+¬45^ºDËW­ÓÙ�øšÈ}êåw^Ä€F˜ÑÍù1B8#À´!î_hjí¨ž9tÓóû³)A˜�„LËZpòFk˜$er(·í*7µ­{QÝ1„).Q[v”i‚F'ü>ñÌ:Úµ7øfïš�Çè3_{Áâ[U~í…FzfñfÕ#Û¸µBË÷žÏý…`’Ü)ÁÄ÷ýó,Õœ·j9;)£©©Y«ï?}ÏIrúÕŸV)5ÏeŽ QǼ4zTSÎUë�™Âr`õú£rÍ â_ú½åèáÇŸ3™¹¶ E<»x ="ÆB[ ×–©ýÁaì|î/jT8@}ý–ãªGZ\,Æ»-3†òZaxÄ·Œ@›CJE]Mí†éҵ§]Ï÷ì³jÙ¥÷3^%þ`ùêˆY�}�ÕCcÚ°I¨k ä›|LŸ£Ú:óá)_ð�Ú··yýƒöØKKÞ¢òSÁ¤EÜ 2ò˜qÄ8,Âïñ¨›_¶ƒ›»{ö[癢»,žÄû�‹›�HdÞeŒ–0äg8̳sÏ9:ì°J0½kïqPµÕZWØ…&y†IãF bf�3„­Z¿ÉÒòNÚ-óa¬CGKLkªž ïk7Ø¿‡è颹bL½%Ì¿ËZÛ�ó;‘lÊ;™~KÑ`&çõÕ»T¤ïx~ó.c§ ×^°„é�îŽY|U}nÆ9å=q�˜ó–i¯ÐšÞȼW…ï,•)m9¯—a˜0ïÙ£ØRÅöÝûiÍúÍQù2Oä>õò;϶ÇnÎ�³ÊÅ3Œ@ 0}P'zão¯§Çæô¤ï,èK¯\yЕ´÷�}f ½ð‡‡hü螦6Â÷¢#é™_?@ø]ËŸî*òr¼«ê 'ŽÓá Åi£A¾ã }îß©ŠÓÑTvÞ)ÇXòlز�Þýh¥eC¾´ì$½üÚ2:YQiɺim#/GJJéé–БcÇ…;�ÖC^Ø<‡�ýtºJm¾\>I¡½î�2==�`zóiÐ$Á,þŒ¤Ú,Àóù7Ì$ÙL*4þòüzùõetìø “™@äÁvæ,42ŽÆL»šÇŒý˜A%*ù=/}‡¶lß%,µ þð=w´ä„ö›•F½Ä¦jÏîÝ|‡×o\Ül`"òÞµ‹õ´ý‡+ÖˆƒÃf¡ æB̃/¼ò†Ð¬mC:~§*«´C>u’ ý¹|U ¦±F,yë=±FœÑÌí"MÀºÅ‡Ÿ¬Ú¶;äbÚT¾¤!œ– >«kÌß^äæ´¬“zzüÆ_xõMí= &Áqkò_žÅö€ÖE¬ÃòÞ�^f¨+ê9$´±ÿøÌ‹tT”e¦v´à¦ÙK2H“ˆ¿%sÛ¼ ù‰‹Ûïx^óŽwiøa?pø(UŠ9¤^ø‘6~‡âóæ±×ÞzŸ6)´÷[´Q½é»`¥zɻ׸k—›Ïð>"k©BÈ«'èu [mi��ùsg(æ¶«ôÉš ôçç^Ñb­Ã·s,æä*¡ ŽCD[wì6}Géå⚨}êõwž#¯ïÝž½æ—Ëg�ÄG`\ß<úçÛûÑßÞØ‹r2Ü3ã dRãKøgÁ´µ«k̦%‘¢W÷NÖ„QÆüßÝ©,f}=ú”òY$‘º¿éO-M?øû›(;«åD)4Ãÿô×õBm6ß½ns‰fz¸C†uˆCûzÝf³ögª0Ñó?ÿ~-”4Ê¡Yý­¼!>˜ÍÜÁÚ°ôÝÝóàðýãïßBóç5e]úîÍ”ôùÚVŸ5yi=zÿxêÝÃÚ_ÿñ½[ù¡É=ræÏaÜ,û`Ÿî%ò¾üLJ©Xru‚‰s˜ 7ÒÞ4cr?c”§÷³„îŸÿöS«…Öô½wXOÜ@‹wƒ¤¥;3ˆïWÞØA‡��1•�Àwþv®æCYH#Í#_}^lò›Íeý÷ÿ-§¹Ó)×–B4¢GqG °ÿé»ÆÒ¦m­›4ø üXŒ/ü¶š›¯Ð'ÂÔ½‘`*_E^ŽwU}áÄEº‘Nœ–¸à/\êÞ­ˆ}à.S6lðþþÏÏ…µ _uø“©Gq=rkùCö'°ñ`¤þwSzz*Á¯+ÌS}©ÓÆËý¨aCÄÉÿ-&Ax+;yŠ^šÈ ¼BëWßxA´nŸnM”�•©¥—ß1‘ZUFBX6á�ç22„ÿÓƤ�{¬á�ÎjJïªØ8/Ñþ�úÐJkjº¬µMŸÇ‹¾S ê¼/à‡ÇŒý˜>‰J^�G;< LĘ„€BAi¦{�>k�y§Mo úvï7.n6Ì+Þ¿þ6ÁÔr0‚)vÙû±AÇ-7˦i«bŽÖç9$†0ø·Oþ•úõn9@‰Ã 8ÄÍZ�0·çjl¢ã„ñõÕ/>¢'U^!`è&4òOUš‡‡›þøL©¶n ãÚ ´ c¬ß+Ê ’4퇀Þx ý°B;.0÷õàýh³ô^!Îâ¥o;FçÕ7ßÕÒædgÓ×¾¼OK…6ýSÏ-Ö�]¾Ür° ÂA!rQÖ7~ôHÂ%âoIÕ·ã¼ÂEŧÛïx^ó­XÚ‘)%%E›OäÃWr:„{÷4[ÖQ¥ñ"ÎKÞ½ÂÝËuÉc|?¬Z×j!¸&µäiG#†´Ëˆ÷ú½tíB…û…u›¶êÕo*„/ó×—½-ØŽ°dmKËZ:°_=[àêUŸ*ðèÆëïyvU±`ˆ»jZ_ Ä»Z‹Fcœ~ïEŸêe{uõú;ϯ~>^Ì�^áÎå2Œ# �„L˶,˜¶vóy¡M+Sqa®•0a ¿ù¥™¶üΛcL#á±ÒjezhL ¢ÐĶ£1Â/2Ìo;!h•Ê~¥¿øÐ$¡qdôéåÁ$·¬µ,—¡§ u…ìðîÛ¯Sš8/*È%˜´6RÅiûNc:·îÑ�3¦˜5´¡1­¢U’iœæ„Éx�>sÁ¢Õž›“A�~J­q,î[hÝÛ¾Ûì—]UW"ÇAsߎºtÎ<2 ò‘Š›X�wK¦('Y¦ `ÚÐbºoÑ­”ÙÁ¼¨šÍÝi“ÆY¥ sÖñ@c®A£†[×ÿ`¼Ûh‘ͼéå¨4¦õgÆk~§éÚ9ŸÆ�E£G Wˆós̸‰ ´Æñ½jü†5ÎsòZ` cN#WüµjóÀ1ܸÉ{Ký†ÂÞÚ­?rvàòའi—0“jgÕÚ„ÐrÆAø}Ž–0–î]x‹æ†~7/^´®ƒz0%],êoV8Œôç‰xU­Æqg×&´ÖÞþàS’# :Þ%àÒB§aƒ“é]iÙûË…&óIÝ:†‘¦c^.á Òíܳ_Ïòªšì2¥§§Öì>½ºÓTÁw†‡C‰ô[Ráj®P=WÅɘ¹‰‹\¶F{Ü|ÇÓËu›w¼ófefÚjMëõ¯x_„ s„x”­òÓy}ï'ïnâîõºd‡;ÚP~ªÂòóâÐA,ñªÕÜ«Jç$.Ô:‹õÚ͘c?Y½^ð^©­±ÆwU=˜ðîŠÜìÓPu¹ñÜËï<[M/‹ü@€ªŽd�Sáîçx…o¨�®Pùù9# BZ±ßùÆ—U�\‰óº|�Ilß·h�„  ¢²Jå�¢N;’®ý»Cø•©SGµ|Üþý×¾('†¦Üw¿ùXÐ4¡Âô/þð~ßiµµušfl�Ã?iºØ`ÉÈH×êvæºQü¡†ò‰Št*GË36ÇçΜ‚â4‚ùnøV…ÉZøïÄ„yÂ, ü¡Êi=Ÿ_ãõ¹=fb5^Ð·Æ ÊJrk<Úa�Mäù7̤¹BUYu†.ˆñSÏ]:w¢¢‚.Úx·ËkŒ÷{̸… „ßþúß›bºد�íœMžï~3ü5Ç-ÞïÂ[¯ ýŠFø» ̼ëæÞS„µ$1÷àO'àõ¹‡î¾1S… ¸½6~ð-˜’š"âœ}êe�&¤ñ‡9¸J¸²€)p¬X?´uCÔ›#üV'E³vàI¨C$:^ÀáàA“ø½ßú‹鲸ï(Þ# ºtÑúiqàÀiŸb¼|ý±Ïjk%üH7ˆ¿ËÍ—…Éd˜MnÖ¾3±nâ·¿¢ÑR¢ü–u~ Õ?^¬×nõ)x/.* ¯ù3ÔØxI{�«ïpx—ƒo¼—vL±`-ÈÊì Í-ZdŒÿñ›w·p÷c]RuÍ8á›ÑP4so¤õB k ì�À2¾90gà@¾9RÅú‰usv8ó¦[}iÛÂÉçåw^,úm�v~ô{ݧ¿8-#À´ ’B0�ɔɌ€J³ôDyâ ¦ »¶nF˜[^¨ü¤Õ$MaAh_00ýì„TšÎ³ïü­“¬¦4•Uµâ¥ñªæCÛô D ¸[p3¢!²ûò¸k—l9´˜vîÅÉù‚ŸéÏ|`¯Û\">èÌ>â‚ù—.-·jªCË>‰¾7šM/-³–ªŒ¶ü<Öã=öò!¦Péù9#ÐVÀ.þdÂFò¡£ÇMÑxçê˜gMkJ£xëVX ýň…ˆ«…À<˜Ð<â‚=ÊÈcÆ#`ã¤X¯Æ#Xôìn€2NšoˆW¸ØVèâƒDᇣðŒ 0.*å;3X Ög‰‚�•óĈÁA+h�ÛQ¸}ŠÃ.ò�»²ÝŽç±¢FÔk\¼|ÇsƒwŒá`c\�Z|ÄÆŠw7p�‡ (uáP þ¼ îS/P ]¦—ócèÚ9#À0‘#��6°eakL[€Ê—lÙÉÄL‹÷'WèôÙV3Öz�i¡OÖc¡wBÕçœ$ ™Z§ðÇ.u5ø 7¯ŸégOíoªnýæãâ„{« Z6ã av0Mð³5VÜUVL•Š€œ¦æ|ƒI“[NÏa3±ïfn¬!Ö˜¶bÂ1Œ@8lܲ�êêëMY`ÒjLŒ€ 3*T8Ž`F€`F€`F€`F@G !wY0­wŸý¦§;æu0%€ß³5æ fS‚6�1A“ÍF´£¡W÷à&Ç�–žÂçµ]9‰"˜ž5Íl–»®þmÝÕêǦ½�4srÍ,›1ÎxÍg™ÎV[!XÓ˜]r‚Ö#ç�UøÂ…K±ªÚTo¬Ç»‰E@^+I8Š`àPÇʵi¥ð÷)Ó¨áƒå(3ši@3<F€`F€`F€`F€`B!àª)ïgמ¤“ç.Ѭ!ù4±ŸwfeaƒSmÖP`$Ós(ø^?¢;­X{$Ь†‹Mô›§ÖÒ?}³Å¿HàAºQùª®8}Á5ô³š•{ðî±Â÷Ph³ÒF&²2[|ãœÜggG–ÏIÙn¦;ª‡0Yš!ü¹5Š…0zÂè^tª²V˜Ž­ Äã&˜o<ï¥ðŽr‚L¥WV™û¾W�ÈM]ivóˆC0ÎIð¼mÁs»÷4Öã=TKäµ"Tz~δðÛ€™îKMMÚ_Ó%q>5«ÎÖͧ©¤´\ó=&ã‘�•ES&Œ•£9Üà1Ó:™›È0Œ#À0Œ#À0Œ#À0Œ€¸&˜þì“{èÝ]g4–ñÞqzâ3ÃèÖë¬B:7Ú$›geSÞjT'�ëcL#Õ3/m¢/>4Qøå OPª®!ñb»+|0+=ëZCöíb) Bé¯}aº%¾-GÀ ìôIýèí÷`€`ú›3“Vo8ˆÃ LšÏ˜ÒÏ'z÷´ ”·î*§ÏÈ á]ûNYüX;LgvH3”Ôr[!„Å#É¿‰F³æ–ŠD„›‡)Tå;�‹÷ñ.¯NÛÅé�dFàWø ]¨ƒûƒðÓÌš6‘à#–©m!Àc¦mõ7·–`F€`F€`F€`/pÅ”÷îòº€PZgö¯kOé·®_e-8L«!¾oáu”šjîâ‹�—é?ù5 mѶH(vî”ejúkoïšcͦ89PZ^#G)ÃÝ»u¤æóO¿´™NVœW¦oË‘²ô6!H®«»D²ïQÊ-}&ã¦(/û`/�©6›ê6æ{Fô‹L½jLZv”�Œ¬�ó;fÊlбÒjKœ±ç@•œ°®§óãïã]^+üÀ„ë`âaR%L¡4,ÓŒ=’®14þ›Çz€�@å"F€`F€`F€`F€h“˜¥–BÐpÉ*Ô»Ðh�‹°xK6YØÀ¦¼-ið—{Ë\ë&ò’e»èîÏþ%¨ðK]brÄŽfÖj=s¶Ž^ysgÐÆ½óQ«fo°„0¡~ãL³ÿM˜‹þô—ÿJUgBû<Vv²=›9µ¿©IÐ^·¥Ä¢1=KJgÊt-Ð)/“†)2=º$楟ýz9 ‹µÚ¹÷$½þÎnKü´‰Á5³õ Å Á4Æ̃‡KÝ ó,‡Þ[¾_h4¶š97–ùÛ§Öƒ1½�÷ñ.¯1‹+gâ�ŒŒð4ž»УÜM7Ï™Aü¾'�è34¥ýô^µÒ]$mrÅ”w$G“G6¨ãÑ”ŸŒy-I/=ñ0�éLûTÆæ•e�§œ&‘ÂК~áiÚ¨¡ø†Fè/þý‹à`ù aýûŸÝ­ýuïf5û,o2á¬�x6qLoÊÊ´÷wêÔŒ·^Ïè‘ÝiÙó_ QÊõ¨ WhQÿðÛóè'ÿ|kÐtª‡Ð˜¾Sü®œ’¬álÌ ~òÏ ,&à�ip!=Æb,sȼÃñ:ÞÙ”·±—øž`F€`F€`F€`F€`F€ŽÀŸV–™<»ö•žU+Zš: $…`š5¦�õöØëzÐkO†þò«û5óθ†"øþÒ#“éùß?H=‹;’§¥ÂwœsMÖ@FÅ „ƒ2¥ “ÄF‚Yä`”¡*#Dž £{ !æçi¾0£œß1SYü¤±½5¼àS8a Ì7ôê—ék_˜® K³³BûÙkjº¢äE…“2¡™ÇfYѦÉÂô´ÍÕáRß^ù´äÏ�ÒcŸ™BúvQj\£¿gNéO¯ÿå³ôè§Æ‡[E ýÿûÑBúŸÞA]:gâT78œ0uBß æàaŠü½—¿DÆ«~[ýût¡'~q/Ý0c�ð«¯ÆºQFjŠyŠ7þ®äñdü ¥Šß7„ÍFÊ�~“Ægú½›ã]/3š«|ˆ)š²8/#À0Œ#À0Œ#À0Œ#À0Œ#À0Œ#�ìT×]¶4ñB£½EZKâíÄÆ½0âm-©¥[¹ÍTȘ޹ôÖ7F›âÜ |ë[ߢŋŠûÑ�~D?üp œ¨7Ï>û,}ÿûß°ÿÐ=cé?¾wK ìÅ ´s+«.PuM5 ?Ë—„)à‚.Ùš&1×0ÜV¦²÷ì?¥á�“�!|Mw¦aü‘cg„°®eg§ÌY™éá�¬`šùHÉYͯô…ºKš&64f‹…ð²w�ü ÄNÊç4f`Þ|ÿ¡Jª¨¬Õ|0\Ñ!s©ÖúõÀá*ÍD{Óåfê˜Û�òÄ_Ÿž�B ®åÒÀó¡#Ut²ò¼fn¾�¶X²×‰qSRVMâðþ d6^U‚m¹¿Â~�wøã>zÜl¿  €6mÚäWS¹F€`F€`F€`F€`F€`F ¡˜óÓÍ´ÿT½© ÿã8Ò-Ëi ¸j¤¥zœO–¥³)ïÈïÓ3_Îò#/ ‰rö‚aüÙQ¡}ëtÉqdBÜ�º¸ ÒÿšÿèÞ¢áf¿vÈH¥‘úi*®q8b¸°'¹‰K¸í•׊pószF€`F€`F€`F€`F€`F -! RgvÉ€²£ÙÎk¤ÈJ&_#-Æi>YØÀ¦¼�"ÇéF€h;ÈkEÛi9·”`F€`F€`F€`F€`F€�+ ;Ûn*»#˜¿]®æpWãÂF€`b†À•+jí1cˆ+fF€`F€`F€`F€`F€`â�+ •i75¦]1å-ÜîZHÁ·%M¤² ¦#E’ó1Œ#�¼ÈkEò¶”[ÆG æÜy:YqšÒRS©g�nÂÅAFð qò4QùŽø˜ F fà`~¿5çj)--•²³³(;+“2ÒÓcÆWÌ0Œ#?ð;^üôsÂ0Œ#À0Œ€ •ÆtÜ ¦UŒûǦ¼ýD›ëbF 1`Átbôsé-›¶î¤>YMúï!+3“î]´€ºw+ô¶â(KOT¾£l6­Ù°…6oÛ娘OÝu+víâ(-'b¼Fàø‰rÚ(æ›3gÏRuÍyRY-IMI¡Oß»�zyÍ—Ï0Œ#§´Õw¼H»ãä©JZ¼ôSö93&ÓÈaƒMqˆ øÆúýŸŸ§¦¦ËöïC·Ü8+æF€`F Ð÷�¼»© ìŠ)o…Â4)L�ÛÕ½ Š›€DÅgfF€ˆäµ"ncFŸ8_{�>\±& ”Fµõ ôΟøÄAdÕ$*ß‘µÖœëÂ…:ºPçìïâÅFsf11@àÒ¥&z÷£ô×——Ò�CG„`ºF)”k—››ÆbC  ä*F€HzÚò;^¤�Ûpñ¢åݰ®®>Òâ8ŸË`Ï¡ºæœ©�Ο¯u¹.Ž`F€ð�јö[4mîL›ñà#À0Œ™„qŒ#Ð8UyZ) ª¬:CÍÍW(%Å•ó‰®C›¨|»3ÖnÜJõõ �ú»tîD£G „ù¦� bcüé^¥salÀæåå0|Œ#à1<‡y 01üŽ1tq•‘瘸ꎄ`&‘ÇL"󞃃™d�¸E@ícZ%ެ ®ø˜Ž¬j÷r±`Ú=,¹$F€`’Ö˜N–žô§Ð䃙Ùd¢ô´4esÐÎön:†QÖyd¢òy‹[s¶�Óí¶�»u›¶’Q#½¨°+ ¦]¿ríÆ°„Ò™™4_÷Š¢8Š`\D€ç0Áä¢\E -¿ã¹ dŒ ã9&Æ�€Õ'ò˜IdÞp¨0ËŒ#G$„Æt;… \X3ñŒda ¦=ƒš fF a�׊„m3î)¥e'iû®½´sÏ~ÊïÔ‘n½yõêQìi�~Þ£¸ååæÌ&iÈ þÏïN‰Ê·ãHïgOŸL“Ç�¶d_·i›ðß»ÃÏŒ@¬8s¶Z›;åúÓRSiêıԣ{7êÚ9ŸRE¸ñÒ%ªfê›Å &F€`�¶‹@[~Çk»½Î-gF€`�DD@µ¯îæ^¢+ÓÙV £¼LWŠvÔgnâ¨BNÄ0Œ#÷¨иgšô ¢·íÜK'ÊOê„°Õë7Ñýw݈Kä›´´TºoÑ­ôŽðÿZyºŠÚ·kOƒô¥›æL�ëf%*ßn€ möœœlKQÐ4ebâ �å«Ö[\fàpÏ=wÜB]»ä›XÍÈH×ɘ"9À0Œ#ÐæhËïxm®³¹ÁŒ#À0Œ#�Ð$„Æô€‚Lº{\!½²¹2ö£Ó¼Ó6’… ,˜ÀÎ7Œ#À0×�× †€T£·ïÞKuÂ7ªŠ’í�¢ kgzø¾Eª¦Æu\¢ò× 2sŒ€‹/+·”vÃÌ©¡´%G0Œ#À´iø¯Mw?7ž`F€`Õ¾º›{¦®©5ÿêÁ!4wXg*«¹H3u¢ë{åú±›€øÆ4WÄ0Œ#à)ªÔÓ ¹ð¸EàØñ´M˜ëÞ»ÿPH§Ož2 '`F -#ÐØxÉäƒXví¢Ydh˸pÛF€`F€`F€`�d@ !4¦u ï[ ßúzeÁ´¯pseŒ#À$,˜NˆnòŒÉ+W®hþO!�>Uq:h=0o}ýÈa4¨ß é"yxøØqº*xÑ©wÏtåê:r¬”Ž–”j¾žáó¹_ïžÔ¾}{jº|™¶lß­™ÞÆ}ga·_Ÿ^Ô§W½嵤´Œššš”ÏäÈânE”�•)G+Ãê_|èÔ­°@35�ß×ÁÃǨ¼¢’j…릦˚�nhÁt/.ÒøÖó»zÅ·¸ƒwø(G¿á]×””ö”‘Þb¶¸¸¨�ÒÓÓ‚5¿Í<ƒµ‚²“ÔÐp‘.Š?q­ã£Ì¨C‡ íš™™!~‹ýæ>�Ê>]uFüfÎÐé3ÕZ˜’.èÒ™ºŠñˆß�a¿Cãoi/^l4e�äCGŽ™â¬�vÚoc Ö„v�­>G•UUT!p9{¶F÷¨  tÕ„È0¯)Õœ;oÉj޲dˆaDsó‚ë†3ÕÕtF`ٗĆy17;[Œ›|ЯÁ´~8ä&î^Ïavs;Ú{øèqáæâ�¯­ÕævÌëÅEÔMÌi9ÙYá@¢¥uhÇ;êqc]’Í#—å6ïhs,ç0/çv»HÂ¥'Ê5÷ª¼iii¦w+¬!GÅ;ÏÙꪯo Žs©kç|êÙ½8lKnŽ�÷Kâ¯B¼Ïž§*«´w¾¼¼m>6x ö¾�wß#â½ÓH}zõT®§—››é˜XÿŒ¤¿ããŒ÷˜/«kjQ)bŽÄ;ªyõŽ‹~õ¢O�[³è¬Ó§Äû4ú¶A¼`-Âa/|D2çÚõG$ñ±œcÜ^;"i´yð µkïªïçÅ÷Rº˜wзE…â]L¼“áý7\ò—hË�嘉vMòƒw¿æ/¯¾SíÞc0ŽÝ~ ÷·ÁéFÀ}J0í~óÕ%b±`bF€`F@F†íB �4´úìB£ñ‡�G/¨öÂziÉ[¦¢ï¼m½ñ·&a/ø…ä¾E è¹—_§òS­®Q�yíÆ­4cÊš>y¼©,cà¹ÅK�Á ÷sgL¡IãGMƒ‡Øà|þ•7Léæß0‹úô ‚A@;uâXš6iœ&¬U¥Ñã¼àÛ/Ü÷ üm;÷èM±\!¬ÆØÂ¦í”‰c(+ÓÙaKA sù»ö¤}YÆt¨&=þ…‡ÅFzNÐdgÅø[ºì±‘kþ½È™€ÿ­7Ï¡\…ïnlÊ¿Q9?ÂR¼üúÛªG¦¸Gî¿‹zˆƒ±¤“bþxmÙû¤ë|alâ7Šy÷á’ªìÜÜàýn^¤Ç7ä–í»hùêõtéRðƒ<éâ€Éð!iÎŒÉÔ!##$;nâîõf7·ã�Ö‹KÞÔy¼ÿБ@pÄÐÁ´àæÙŽ…önâ&ìxws]ÒëÆ£—åﱘüžÛ�˜E{ÿâkËlíáwýͯ|NS~²†6‹¹AµÏ„wšâýkŠ˜3CÍ—n�½ý[wì¡÷>^©ñªÇ¯ËW­§ÛæÍÕèòZuïÂ4°crí¾\“ÓΜ:Q{w³$¾ñÉšõ´ÿ`ë|€è¿ûÊçÉî “ïx¨ÓÏ~õªOÑ@zý­÷mO|¼jÝ0s uÌóÏ%ø2R,æÔïöÚal“_÷øž[,ÞëêÕÚiß³Äï.ÔÜ¢óì5.n”ï÷˜qsMòƒw¿æ//¾SíÞÁ¼xÔÇ<_F ¶\QÈ`ÛG°`× gªv¹c¯ú`ˆ+\-#À0Œ@œ"ÀkEœvŒGlA›qû®}tàðÑ 5tZ_F�Ølô›–‹M&£²^ÿá£%´ríF[Þš [4¾UÂ5½ ?®˜,~}™¦mhW>ZW­Û¤iÜ=p÷ívÉ|�÷whÑ#ÌA§ÏœÕþpPbºÔ;9¬ÌDym€Wß|OhE7„Í26çB�óí»÷Ñû¯²8+…e‚?>ó"Í¿a&A«,™iýæí„±Žß`0ÂØ\½~3•ˆ~ZxË�”¦Pù¼˜dêfr~¯ÃUâ·¸ìýåšæ¾“º.]º¤<�ö!uïVh›Íܽ˜ÃŒ ÂÆ96С5Œvï; i”ÞsÇ|ÍzF°´~à‚ú½X—üšc¼à=XŸDûÌë¹=ZþÂɱ±‘ð+5›¶í´ÍŠùô“5¨Y\q˜Ç޼3¨ûý嫵5võ"kíkËÞ£[nœ,Y›xæf¿zѧz'¬ß´� xö½ KHï~´’&;8Lª—› W¿Ö/±º$4¥—¼ùn¡4j¿JkÅ÷݉²S´èÖ›BjÇ{�‹×å{�w2­IÀÇÍù˯ïT/Þ½+\&#ÀD†@Nz �«oµ ˆR:¤»·�ê^I‘µ/¢\Á^Þ"*�31Œ#À$¼V$]—Z�·�[vÐO¿¨i}Jšo÷ßu;}öÓ÷ÐØëFÄD(�ØiㄹvS0Ék‚™T˜rtB0õµïÀa'I=Oãî(8%¹>Z¹–6ÙüvZV¼§ƒvñâ7Þ‰H(�¶A(LsšcËÞûØ‘PZÇ ¦¹_ÚÕ°¨�¬´nÓVúhÅš�BicûOSôϼ¸D3%jŒuUaÓ fqã• ”~ò¯‹ ¥�í€öÌâ¥ok&è�ñú½_¸{1‡émÀæCYÐÓ#Ýs¯, *Pñ ðäöºäçã6ïzyqõzn÷‚çPeÂÅé8¡ÍÛviî'Ti½3xo€•'„wâ âpis}´ýêUŸ¢P6úÖÉ·*ÒàðD[!?×/1-ÓÜa8ûN(-+׬•ãÇk\¼.?XÛ"}–Œk°pk]òë;Õí÷ÇHÇçcoxtZ±©àÏNïN©î‰““Bc:Øæ• =0 ŒÀUaêFì¯\»jaa}WèÅèÏõ«)ž $K~CY¦2®¥EY äl'>Šj¯Õt-îÚ3LIZõýµô-ñz^9ÝUj/˜²äÕ8…11Q àäc?Šâ9k ¨¾õtsÝØÚQvVV‹¹îQèS ÍàÉü�»~$õï×›V¬Þ üÊU™ØŸFD„ÀB##A@bGƒôS êàk+˜@î<»øJáËW§ÔÔTÍTu¦ð \%„Õª`hƒÀGžÝ»š_|ƒg·q+Ö¯v?�‡øƒ~“íè£k5Ÿ–]…ÿãd¥�[wX|4ëm…?JøÖØ�©äÔÔM�ÚtØÁïtuél� |™¯\·Q/ÎtíÔ1�òÅÌM�Ǹ—ü cMÀF0ÌšêS®øÍÉ>¦Jþ¤1~ sû¡(/7;TOžÃmLþ«~Î æõ´ºŒ Òc^Á¦÷„±×©²'E4ÎìÖ ø6‡É}üv1�á�Z&˜‰„©Ü[nœezä7înÏaÆÆ”Ÿªs„Ÿí^=Š þÒ!ì:.4ë¡Åc$PÚè( ó»L~ãâæºäö#c#‡£åÝÏ9Ì˹]ÆÅ­ð°Á¨¡á¢V\ƒÃò{ÕánB·bŸòÝ…+†¢‚®„Ã,ð7m¤±Fí.<®1Ô-Þ½Ü]—ôÂëßÛ„S&XûéÙ½Át£hüëãÈéá¹L¯Â^½ãyݯ^õ)p†<„€**.*c°P¤wÄûö !Ü5ŠÃ�±"?ç¿×/15îCèk*¬Ó໬äDY`^Òy€µæ*æ,™¼ÆÅíòý3^¬I~ðîõü¥�¿¾SÝ|Ôyç+#ÀÄ_½¡õ/ȤÍ%µ4´[Ý;Á]·eI!˜ŽŸîbN+—…(·©]{Ò®×î›Äõ²ˆÃU¿ÇsíOl~âÚ#>|ÿVN+pü¥ˆ�äý^»^QÄ!�ˆ7¥Óó^i)Ï®•eL'¶Äñ¨�‰`â�}ké#ÇJƒ2‰ ;˜ë–7ƒfòé!>xoš3]ÔB`\±Ü,˜ž7w†fÊ ›bò*wv³ª*‚Ú%?תtáÆA¦‘Ñ6Ú ñ-k¨À-L‰Ù™ ö‹o/p¿gá-Jذ™Œ¾Ýµ÷€øÛoòe !6„dÞ»P™7"O‰ƒ#2åwêH7Ï™¡ å!슔ÖoÞF }{ãìi4jøc´6WÀ*LaêtèH‰&dëݳ»¿˜wß>O¸þâ·Oš„ë…]È®¿™bxƒMnVe‚OT£ïBêyñµ·,j®ÄžtIë~W!ø� Ú2­Þ°Yøµ·�PXØ•Æ�%gó<Œß!«2Ág'|²êcÏë„¶?Ü*¼'™‰O‡púôê!¡ ¼ÀÝR‘ˆðbSÕ3æºá4wæTÓXÀ¡£¿¾¼ÔrÈiíÆ-JÁ´WãQů1Î�uÉí9ÆÈ_°ûHy÷sórn†M4Ïn½yN ;B¿ùÓ³�0nö]󗜓�EÞ·ˆ:‹µJ'˜þÇ!H#©,Æx5f6‰C^òA7ÄšÕ¿oo#[šî‡Âb†q½3%ˆQÀ«w<¯ûÕ«>E7ìÂG¼›©�x§žAc„E'#ÁMѲ÷? aÚ˜Ãý{?ç˜X­î£ÖZ¢jMŻƋKÞ$¼“ .�T‡y½ÆÅíòý3^¬I~ðîõü¥�)»ï¯¾SUc=Ü÷G�w¾2Œ@|!°àº®„?/()ÓÆÓh^€ÄeÆ7—ššÅK]-�>['N ^¤ u�âb“ø�kæ‘„†¯ôîÚwJhJõ ¹ªÅ¡Exf·%¯Eã!RkWüñ&Ä-c\!@WMèܯʼn0®ˆÇµ¥„VÁ²v×ú�V¾`Ñ@"p-Ü’ úX‚®Åi7â^¦=ÒXâ["´´[=�È­RíŠhcøZ¡-Q"·xŽW-¨ej‰7-ÿKé®ÅëyéZã[¢ôüêx®Õmà«¥MR¼öÿB�×®ÚE«1zž–ºZžá_-‹&ÜN½&ä†ïTqm/F®©bààmÜki®¥C\‹ ]v ½um ]wb0`L]‹ ÜckùãL»Çø¼'TÙ[â®j§§q�SÔ0£©ß#¬ÿÁÿYëýU¡!t…ö쯯ùb#Zü6.6 !I35‰x”Ѿ};±ù˜B:¤ aXºušCÙB[ ‡zw¤ì¬ôpð_^°Ó,Cƒƒø«®‘7rÌ ˜‚@š^ñJ=„6Ž>ß⽑ í‰ÍQPAkÔbCtÑ‚›Œ¬k¦ÑgN�H¥BË[AHk'˜6âaÀOܳ2;þPçX!äyúÅ×ļ۪íròT¥6ÇëãÀÃfǤèÓUg-õÂ�qq½– Šhµa³ØLíèÞE 4í1scÊx‹PéöïK·ÎkO/ypÀTç'^®±Z;¼l¿ÝšŠw�ïYH¿yòYÓ¡Â*ánæhI©é7î5.^—ï%¾É¸&¯h×¥P˜{ñ�j7ÖÃy Å7?g�äD !ÓÉÙÜ*'ìØs’vî=%NšVС£UÂÄÖY:%„ÒN误lq’ŒÓ0Œ€�®94 o6¸ˆF í&>š{P¿ÞöfV Yù–ˆ häéé`Aë ÂhüéBÝ`écý,KlFØQ–0= ídM W73§L°ågðÀ~JÁ´JÛжÄ w˜Pž6i…cЀª9WKù�òw΢%“铯�V6ãnÄÐA�‹n V™)A#a¦ü‚¤a�¦Àj…,”66‚Öûïº�`Ò}ÚÄq”–fýdöw?æ0ù ƒ“áûZ½;vï3Fkî(Œ‚i¿qÑ™qc]ŠÕãï:^^½šÛ½äÙIÙC Oò-Iq(ÈŒ]aÆH^�™óµµ&�êÌ¿AðjG0õ¡§Ì£]úd��´_½êSà�±$¯µX¯Ç^oÖ”6öÍq´§X“d+JÆ4Ép«µÃKì‚­©Ð̽nøPáºÉìþLu�I0í5.^—ï%¾Éº&³Hç¯pñvë;5ØXwúþ.`’ëWv´Ëøq�ì2‹Q p¤ä }¼ê0­\”Öo9Nõõ­GQËYFÀ!•UÄôZ»©$�£W�N4srš;c Ý8sP >Þnx­ˆ·qÆÌ®B -kÝʹûöî© £‡‹�r&ÿ€J0c§<«°¾ËÚ2õPh Ãe² ¦á¯W6· £øm�6Xnê)|wXp:6TeÙ|·ª¬þ}{™Ó*?˪|‰wNr� ~¡aG£G ·¦UøÚåO”ø³bƒW&lòCû8!M°tɆ{š0ãÞ­¨ (,p�! ¦k…ðßH±ÀÅ­uIõðzŽq‹wcxuïÕÜî¿NË)Ö%A(�gÆQòœàÕ˜Á¡™0ƒYZ�€³{·"áûýˆœµM†#íW¯ú�€w#¸¾1ÒÀ~}Äzmhi{·Át,Öc?¸}ïlMbLË8Èaðéæ;ž×å»�«±¼d]“ÐÆHç/#>Nï£ýNu6ÖC¿?:å—Ó1Œ@r!�‚i>$× ¬8}�^{{½õ¾0ݺ»<¹Ç­a’�Ò²‚üåw̤…óGg׉S¿ñe>™×†ÄlØ€Óµ£ƒizÀ5ÃõBCÚÑ0ÅÊä?ssƒV�ÿà ÇéC˜Ûܽï Ðd)'ŒãóÂkmmÁ—4|Âæçw¤T±Y,S]½ÙO²ü<‘×”–YšPqºJӮă2„¦s±ðÛÛ�ú óÁ²™dKf¡2åß üy‡ò9/o2ÃÏ(Ö‡`üªúã5Ú.2…Âd’qÂs˜`o4˜¡×óì~Y _ÌF¼L{½“pUà7©ÓE…Á…¯Nyôw§<¸™ÖGB‘jÜœ“h±ÀÅ­u)sŒ[¼‡ê;7ž{5·»Á[¤e¤ sþÁ\;À}0òjÌœ;oL«~2o�:†þËy’1M¿zÕ§ÀYž/ç¬_ý_?Á›Ÿ‹µÃËö9YS;|Úë¼ÔˆwT#y�‹×åÛâö}2®IÀ(šùKÆØ�ïT'c]5Ï©æC™3Œ@ò#��‚éäï–¶Ù [K5A×kˬþ”"E$U˜Þ+èSH�õ�4`ì º"6�[þš©ùrë=âàCïʵ8íÞ�¶5_k9?Ò° ,Òžâ|Ñ �’šBiÒ)]ÿËÄ}¥ãš™AG¶¢Þ#úRÕñJª:q:šª,y«Ï5П_ܤýÍ™6€>ûé‰4kJK:Ž`T@x�4{Á¨K~§sÝB( aSìHsé~ ÁóÆ-;h“ðí+ûœÔë‡ß^üµ5š4n´&,V §u,.^l¤CÂL2þ>‘…Âä9LYB¸Ù¾}{=™éªÚ<[¼ômS'¼ã]ll¤ÌöÅNʉ—4*m˜| Fi©©Ú\ t¡ hrñ"4¯U¤õŸ$˜2°? è×[•ÛA¹Üd GÓ¯^õ)pViÂ;™²ÌωÞ�±X;¼Ä,˜»½^¼‹!�ñà9á¶ÆH^ãâuùƶ¸}ŸŒk0ŠfþÒ1öó;Õ­÷G�w¾2Œ@ÛB !Ó,üK®Aºns ýßÓhåÊQ5,·KõÕ�z ïK=‡ö¢â�Ý© whS}QU*e¾zåj@𭠳şJ¨ü™(ØO»Bók‚ò«ú³kuµ†Q×UÒÂ×Òøá«È�x‘OOƒç-ñˆk©W‹ÓÒ‰ßùpmݱ‡îº}©´k�æT£Å+¥½U‹=Ú2c•ß(\Öy€yàP„4Ƽ˜›�1´Ù“…TnŒ‚÷hÚiÄN/'‘qw:·c|MõË¿ïXàâ”w½Ÿì®±˜cÜâÝ®MnÆ{5·»Éc¸eeg…>�¬L¯ÆŒü»NÆ , yJ òiM¿zÕ§è—úzëaÅfñNŠ<ï×P øð<k‡—ÍÊpð†úåßuÓåË&¶¼ÆÅëòM�q9�Œk ŠfþB~¿¿Så1 TêýQ•‡ãF ùH Á4 s .;O?þÝjúà­-5 +/‹†NAƒ'¥�ã‡P·þ±7# Á&6;S„p‘‰ˆG06»ê¡ýùƒU�Ò½%t|×1*Ùy”Žl?DÕ'Ï“8¾ÿpÅAÂßcŸ™BßùÛà&ðÊ �³Õçæº.^´mOºðs a4þTfŸl3òFÀ'>Y½>¨Pè0ß�Óêmõ¿©ÇÒ¤q×ÓÁÃÇè ÐŒ>Zr‚B™0‡¹ï×—½OêN“æ.ºÖ‰©8§C MúJRiG××Ûϱz»å rŒÙdJ£�y¹9zsW•ÉòÀÃ0nÚ*î²°_¶<�ȸðúàÅܺVïRde÷íªf¯ÆŒü»Òøˆƒ·*!V(Œbñ<š~õªO�Cf¦ÕR‹ÊÝK,0‹u�‰¼vDŠÖSù7%�?¯qñºüH±qš/ÙÖ$´;šù ùãõ;5Ôû#xgb�¶‡@òìÊ´½¾KØ×¶K§_<·•þü‹7„9íæ°Úѱ°�¾q]7w4 ™<<¬¼œ˜`ìÈÈÊ �ãkzªÊ’ :´i?í_¿�ö­ÙMu5VgzZÕõ·^K«7£ÿøÞ-1Óž†PÈé)NU8.z;® ¤á“4t ¤S…E&F €&ö�{-¬ fŒÇŒNÅÝ L&¢a¶&­ß|÷#Kžh#Œæt£-Ë«ü0û;tðíuœ9[­áQRZN%'Ê>¢e*?UIûÄ|1|È@Ó£.�;™ÂÀªB÷âÿÏÞyÀYQ^ýÿlï½K[ÚÒ«ôŽˆ  ŠØ£Æ¨IÔTcbÿšjÞß$&QßÄ»€ˆ(X@Azgé½.m+°½òÎÀÜ�vï�{wæ¶ý=|–™yúó�gÊ�óœs<³ŽÃ±<}.xú¾ªë¨�FÚåÂ×¹«À¦¸yá„2¤øÁ´²};ö3ÒÓtÕ–Ÿ×ûäÖe2ѹóGÅF�6Wr²ZøÌ\üy�11åZ•Åê{˜•÷öV ¬•…͘ÛuÕ„]s&ÙÀç{euµ«®ø$Í™ëŸ4îA#­9¯v�Sî¾Ñb©Ê*ÿŸWÐ:ÍÚÚ{L0?;œBq“`´PŽ]Û(ƒÝ\ì®_9í~k猲>_?“¬ì»r¼ßšûW ýNUŽËÌû£2?öAÚKÓo®;KÇKkhlÏTš”¯ÿ`V­ö‰öتvP�µ*…@zµpoûÏ?|@»VxTù°#høÌQÔâ �Ê!3€€÷²»äÿ�¹y‚TÉ��ûĵ»�¶/ÛJe§KMU¼cϺáîW鯿™Esf0U™‚Ÿÿ(bßÑüWTìz®ô>LY;º[×ÎÁ?pŒ ä œ,<­Ón,Ò3¦N4;„Œ�k�FW)\=hû¶À‚Bþ:¨¿¤MÎ V¾\µŽ.\TûÓ,šÓ:Átšþ·û];r˜åØr oå íŸÛ”äd]7Ž‹yê*ùÿ6ú8骎`HKOMÑu“5øY[¼µ)¡ÆÝÌïéâR½%�”¤$ã`æ’áÃ{Œ š ¾¾‡µæÞnÃð}V¥]s&ÅÀÚCqIõéåzhf®cW5hž(órÝÁøÞ¡ƒ™}»Î)·­Õ†å8£û*Ç+C(Ë+»#í[}� æg‡ŽÉˆ½érfiÓvs±»~å­ž3ʺµûV?“|ÙwíX<9öÇïT3Ï£ûœöýÑ“q"/€@è·j(¿±�~6ï =÷e!Ýùï]ôΆ³VU�z‚œ@#…Ó®ˆ zn]=vÇÿšJ'¤&ÒŒï΢ß-û Ý÷—ï@(äóÝ~l6ÎÏn§ß~þgúÞ‹�ÒÈÙc…c$÷ãâ—Õ?õ±µ¯ƒ™e_÷)”Ûc?±_¬XMÏþß«ôÅW«� ¥Ù”ÝèáCè»÷ÝI7ͼBéPž!66£�²úæ»åácÇ]¦Â�…Ú@IDAT›I42µÇÚØÁXk™µ©YH­ eZ­é’Æ´ú¡³{ßA*ÖÎO_ÞÃ<½·[=v_Ögל1`îØ½ß­{3×±Ì'Nø‹×v»ã,°K+ý/;kÇßñv�SW¢XLÇV[”A^,¥ŒÓî—–Þû‡Õ÷m}Ì XžÚóeæ˜OlÚ¶S—5;+]g7»ëWFÛ–¯žKV<“üÕw%?3ûþø�jæ¹cæýÑÌø�@ ô¨ßмßɲZúp›P…UÖž¶+h Úc»ÚE½ž( ‹¥Qéõy[éù‡Ÿ¥ŠÒ‹n+IÊH¦r ýaÅ_éúïÝHiíÔ/gn+@Û ôÝ�î~ú[ô§UÏÒ ?º™RÛé5Ù´�øã?¾¤¿ýß×Úh‡�ý‡ŽÒû.¡—ßxŸ6‹Ù �†£b“»×]3‰~ôÝûhÒ¸Q”f ÍfX‘ À”šµÚn²ÐtçžÚh���Ó»ö ­¿.�+€Ff§c££u=‹Zã];çªâY»ëý…ŸÐé³çTñ­=0º7-[±¦µÕÚR>5%‰´̸!^dt/fa¾‘Æt×ÎméŸ?+esˆsÛëºP *iíV ?ÒJ ïó¸™Ï‡Ÿ|!-fÑöËHÓ•óL¿z‚ά9kÀ¾þÎBš·h ;QH5µj¿ÕÜ^iÙy©=³ÚÕéi)Ú.Ñ‘ã'é¿ï.¤#ÇNP•È�5>ø\°°—M­ú:ð‡î cFèš=!Ìy¿=‘Ä„•”–Óêõ›é£¥ËuyÛçdS¯yºøPˆ˜>e¼NC�è­\»‘^{{�$låkHöÙÎ×/ø(f«YH³mÇnÕù–™„"÷ù}J[…6=Ï90�£Ç ¥¹¯ýˆÞIý;vh'g•¶ÁÎÅW÷4츇Ùyo·�Ϫ´cÎDD„Óˆ¡ƒtcXºìkéÝ[y-ò5zHh3¿#Þ7Œ´åt•\‰`lRb¼*™ë}÷ƒ�é pyPßÐ Ý ùÙöú; œ.ãç.ûÊ•ßCTé�çTF1zÄy×±åkë³/WéžÖO扅¿gÎ9òÊŽÕ÷˜`v˜9/|MñµúÒïÑQqݨC˜´x[«Qo7»ëWŽÑê9ãËg’Õ}Wr±rß_¿S­x´’êz‡uAÐwíK¯ö8†ò],Béí‘Ùôñ¿>¤¥ÿ·Øíx‡L»ŠfýpŽäÏÖmfdHﺚÆß6‰–ˆkþS×ýŸÿõef$Ðm³õ\r`蔊À™³E´ýŠÿhWÏߤÄD!„îMì×H¨¦ª^(?Q,xÛ£‚ìÓ—ÿ´!Wh´ß#ø"k¿Y“�?â(ç> ƒ_xå-Ê»¢qÊB�³çЉµ\äÀš”ÂYvñ¾>¾÷à=rVÃ-kĶš‰g‹ÔŠŽ aéKoœ”úÄmÈÂ5®„5E‡hXŸ�‘¬UU]Ó"¸•ÛŠˆˆ�ú©ü .§i·�;ª5£åtÖX7ê*SÌüaþ¸ôÇyùƒ{¬0SÊÃ|>äóÕ«G7� M®[¹Ð'_hÔnU è8ýÔ™³ôžÐÐæÀ¼y.(™ó"œYÓ¯–Ò}ùûãf�+°*Ãiq¿fa…»0q¬^°í®L°¤³¿ÆCJÂmŸÏ _æ‹–|q%:Œ"…0HyÝÊùùíÑ­«|èØ†w^DÑeba �™]4Ô7H*Ç ;cG^¥8jÙ f.¾ºÇ´Ð²gÏŽ{˜�÷v;(Ì_´”Øü´«ÀÈZ-ä~½{Ò 3¦º*¦J³kÎ ÜŸ6ˆ µuuŠö.ÑWâý�ÝÄÆÄP||œ$Œ6Z§(ät·W÷<Òºv`!óü�–:-£MøàãϤ¨Ä„úþ·/¿ËØùŽç‹ój×9ePùâ=„…G¼øIxQÐÖ‚ÝIì{µR,àÓ.´Sæ÷÷¾÷˜`~vhÏÇIa1éÕ·çû&nll’oðBM^g®×;/ö2 vs±»~yLVÏ_>“¬ê»Ý÷/ýNµêýQž+Ø‚´–hL« ]†gü¸m;`ÛòHO_JöÒ·B鸤xaø~ºÿ¯A(Ý–' Æ2Â…¥ƒ™Âÿ/üšòGõq:®ŸýæcÚ¼]»RØiv$�=B óíùÑkBkbûÎ=A�¶k�;v „`æ{ÞMãG‡PZ ÈâcòÉB·ÖV]ïÄ{kë5*¬ýŽŠŠ4Ôbb¡óa¡Iˬ•«n±ðrè ~F¤¸¦&½å!£Ì£G 5Š–âx(¤yA|\öu`A–‘PšûÁý3#”î#„¬Úe;íúÈaƒ„Io×f§¹¬9Æô•ׇ‘‰R£†„ÈIãF%9âøœk™ó}žcS'ŽÑi“›éK~Ïn”×¥“™¬A›gܨᔓ­6ŸªÌ%Õu«L?¡ByèØ%î¼pD<¯y.³eÖð2 ]:åŠyc|;_ÜcŒ˜Zgõ=Ì÷v+ÇÏuqŸ½ Þ¼ Ù1gØ�³ç>?×XhYZVnh•Åì¸Ç�.-â2“?RÜ#† êï4«rñ��ïx¾:¯vœS/ãû¤>\’üx ¡µR(mäs\_Ö·1Vßc¸÷ÁþìPž�ñìäEªl½†ÝÍ\~76þJÞ)·ƒäâJY^¹o7»ë—Çbåœñõ3ɪ¾Û}ÿòÇïT+ß幂-€@`X{è½¾æ í8YiyÇ,L[Þ+7*?0qVí±›âH¶‘@Qxm‹Ì¢5 VÑG_ಥ~ãH«‘³Ç¸Ì‡D�à#�›ß‰¾ÿÒctÝÃ78íüã¿ûDh³µh:͈¿¨¬ª’L¿>÷Ò´hé2C¿¤rçõï#™êf“Ýý„Ö „*X:Ój0óäñ£)ËÀ¸Q^WqùÂÔ²‘Ö¦³2•Õ:Á©³¼VÅ_p"À3[¿ÞBãøÚ).³³¦ò7Ï’4“ùC‘'á¼0¹m6 Ø�ôÍ7›]ÊçL€éQ%^ffaý7︙2Ó]»Ô�«gŽ“Æ�¢93¯•£BvËê˜Í” c(Jh/yÎ_t¾à T¸ó=�?Ž› ü¼¿í¦ë]f f.¾ºÇ¸hA¢•÷0_ÜÛ-²ßª°kÎŒºj°dÞWùáßÙ YËwìÈaÎ’ ããbc…‹Œ‰’•à ŠÈë¦M¦ÔÔdELhïÚuN™kªßzãõÄüݶþ2wö wÙü’nå=F@0?;ä1˜Ýò;,[ÙùÆ­³Å»‰kc¦vs±»~™‰UsÆÏ$«ú.³°këëß©V¿?ÚÅõ‚xGà™ÏŽÓÜçwùÝ!šþ·môöú³ÞU䤔맟“B� Át`œ‘‹aÑ´%2‡ömÜOoÿê5—�ºæ[3hö£s]æA"€@ð¸îáÙÔ¾{.½öóÿP“F}èh ýêÏŸÓžÌÛÁOßû°O36m¸sÏ~—•ðG0þ@=XüÅŹÿ¸â²²Lä‰üaK©E¢Šh?B(�Ù±Ð-«ë•ee\‘"¯t·â](ZóQ„ëå~(WwõAî o�Ò�âìì·ÝÜùœòŒ]{H 7œiɲÖ/k;ñHöûÜÚÀçãñq’Í=²\öóî,°IÍö¢ý&¡Õk惶³z<�ç¹çTkÚ¨>¾�°éT^Ð’.öÍ6›Ý³{WZ³~‹äG¹L�YCÅUˆs¼^˜&ŽŽv/œdÞ3…�œ…Óë…™ìBaƛ˺ J 'WùìJËÊL§oÞ5W2ózøè C¿Ÿü‘;'+“&ŽIl¾ß›`tM+ï_ÞÔiw¾nY®O¯î´rÍ¡ÁT$ñQÞŸ�úÀc sÁU°š»Ý÷0£±ðçι^òO¿[ÜÛ´šÒ|=𢇫† �\tÕ¡�³š ×oçsIÛ«ï1¾ì»Ìʪ{˜/ïíÚóàíñå÷(ÏKkß…<©Áê9Ãmó{6›g]öõZ:WT¢{Îñ¼bW"Ó¦Œw¸³ð¤Ï|Ol',J,ùb…Ðî<#ж¼wr=¬­Ë ì8Ÿ»ßr»v¾ãùú¼ÚqN™S·®�è>ñ¼f÷ §„¥µÉöË$;´Ë¡«…5>ÿ,Äö÷;†|~å-Ï=«î1r�¼µãÙ¡¬ßê}£{»³6¢…%„Œ´TêÒ©� IØ2‚Ù`7»ëçqZ5güñL²¢ï¾¸ùúwªï�f¯ ä°—@cÓ%úÇ'U�¼ðU!Ý9ª�*®5aâC¦úÍÓ‹ÚÕЄ?nV•ì‘O_ÿܳ›ª \Ü{ï½´bÅ GŽgžy†n¹åÇq°î¼ùæ›ôÄO8ºÿ�¹Céé_‡À¦ž"hUT.•TÔÓŸoý-•ª} :%vnênwë$eöABœÀ‘í‡èÅ>G¥z­£—ÿ~+M�гU&Ýø=¡öÕÅ>|Ø+Ó¦­êLÞ±{Ÿä?š?�¸ ݺv–>”õ&`@ ­`?€²ÙÛᣖ?:ñ‡\¥éźºzb­ËȈH!(—îKl3B¸?à-ìð4°©jöQȦÀÙ�2û›Œ‰‰¦TÑv¢ðëÏÀãeßÛUÕ5’yd6 Ì?9bEÿ8Ä á5 °ãÅ‚î³U�™p»lJ½Z´ÍÇY`š,|Þ³Ÿn3ig}áþ³O⊊*oæÎþ%î¢~öËë͹tÖfkãÙÇv‘ð;]'Lš³6[¤““[[mÈ”g&ì •ç Ÿ7 ðœ‰×%_O¼h"Aøpõ4:w÷Ÿžý·jXÝ„9÷ÛæÌ”âxÏ›Jq=±YãŒôT±˜!Cb£*äáA s13;ï1fÚoM+îaþº··fÜþ.kåœásÈ÷+¶ÎÁæ²y¡Qºrñ³‡Ã¦­;„�ø5ª!ß2û:am¥‹*ÎÙ_£ü^Q-Ì„7Šý””$añ%Czoá2v¼Ë8ëK Ç[yNåqò{dqI™ô,â÷ÄÔ”ñ jÑP/)-‹]äw~ÏlÍûŒÜ¦Õ[+î1F} †g Jù½“ýH׈¿Æ¦FÉ·4û—æW|~åkÕ›w #&g7»ëç1´vÎøó™ÔÚ¾óø}¬ü�ê¯÷G_pB zåU�ÔïÉuª„´„HÚý»Ñª¸Ö¥Æ4?”A{¬Lþol滫Ã"é½ß¿âR(ýÀߦÁSíY°à›‘¢ot܃~áGôüwÿFej_�ù׊V ¦½éÊ\&ÀB¶ íhÖ�æÔΨg­ þcm;�Ë…)>þsXpiõuâå�Ñ®ÚöG�—5¶}ìd‚ËvÙYÒŸ¯Çåm{,”÷V+ÚÛ6ƒ©k•°Ð•ÿ¬ ÁÎ�;X·^fì\xvÞcdNvm­¸‡ùëÞn_Ôkåœás˜!,ðŸ�¯QWÏn;Þeì‡ÝuZyNå¾²à’ÿœ…Ì {ι³ö¼‰·âcÔn0<;XV»(Õh,VÆÙÍÅîú™EkçŒ?ŸI­í»•sÁU]¾ü�j×û£«ñ! @À>Íù+·äÎ’˜§½±D0m¤ãqIcÈÓŽy’‚iOhYŸ÷xx2� �§u¬¢-K68mà¡çHý& tšŽ�Ð&Щozàï�гßü³Ê´ñ¾CEôâ›èÁoŒ m6º£ÇOJÂè½»ìûÆ4à²@š5@@@@@@@@@@B�€�\Z²båH-L[Ù!oê‚`ÚjÖ”©ZÒ{"Ó©²¼‚>ó¾ÓJ¿ý�ïA(í”@ íè>´'Ýû§éÕŸªMH>ÿêZºçVö³díc ÏõÜbsº¬Íg…Ÿ:W�ýâ²@ºGž9ó®êB€€€€€€€€€€@`h6P:'#õdïÇa­@ÙµµmeŠåû8eˆa"AÚ�a3FЩý'éó—–8_V^M/¿¹‘þÖG\ ï¬Û¸•=N�rÛÓ¤q£º»ì—Œ…Ñl²»^øDuا¦l®ÛjÓ¦ÎÚD<€€€€€€€€€€€ÿ �Æ´pu£ >”KÓ:ü>‰¸M§ÂéhÁaZ3o¥a›×=t�¾iœa"AÚ.�~t3Ü´OÜ?Ž8 ¼öÞ¦ L/[±š6mÛ)õ½ðôYás0‘† îïK ì<|LH€8Ì~ôúû½rŸ+®¤7ço¥oÌêˆ Ä�O>ÿŠvìÞ§êÚÅŠJÕ±?êêêæºKÊÊ]v¥o~O!�îM];wt™‰          ¡M x4¦ ΃Qç ²y¥Õ�Ö{U) yD .,‚Îm郛öÓί¶ëÊ&g¥ÐíOÞ­‹G€Èz ëEãn�H«ßo±¸ðâ›Z0½ð“Ïiß�ÃòÛþ}z:öýµsö\1ìþ£wî¥& ¶âÚÑ)ÉIþê.Ú Íæ°­6°iŸ�i‚„`Ú‡°¯4Å&¼y~®|k¹aãs¿ƒ’2’ Ó  2�)÷LS ¦��(£ù‹wÐÜYå,±mll¤‹?£#ÇN¨úÂÝ9³®¥¬Ì U¼/ö8$4¤…Yôã']6˾°Ùô€¾ù.ó!@@@ t „ ?\ÑÑÑÄï6rˆŽ‰–w±RÑÑQ®ê=Ç!€€€@k àý±µQ‚‹€‘¼•ïVKÓF}2ª[ÙoU]F Tp`9�³á tæÐ)Ú¾l‹®îá3GÑÐéÃuñˆ-�ì®í¨ÿ„�´ëëޤw>ØP‚éêêI(]xúŒ£�¼“•™.„ÒÓ)=5E*ѧ‚]B;Zü�¿pÑe“ûõ–Ò;´s™‰   ¡O€?(üä‘ûC !´1¼•ÿ@@@Àjx´š(ê�À&�i“ç‚i“ ,ÊÖDaTCk¬ÒÕ&túgý`Ž.  àŒÀ°ëGªÓ› iû®Ó4¸gE|ÏBß‹?¥¢âRU›¹íÛÑÍBS:A˜Åöe(<}VFk}\kû�š’,>L±@º/%ÄÇi“q           *Fnš�”“U…<<°Dc:!F_MJœ>Îþ™ÎÞì—¦éJ�Ñ4�òðXjÂé ‹ÖèÊÌøÎ,Jïà?“¶º!@ à ½v8½ýÔkÔP×àèë¢Owû]0}®¸„>滵Éy]:IBé¨(ß™ÆÛ¹g¿$�>yJ­µíve'¯KGIS¢O¯Ú$ƒ€€€€€€€€€€€SFŠÀaBhe°Dzœ•E÷ŽiO¯¯mù`~ÿxßiº�²êR( ‹¥-K7RõÅjUB|J]sÿ U@@À�ˆÈ8eˆt_‘ó.]¾�~õØ5ò¡Ï·,f¡tuM�ªíüžÝhÎÌkUqv\¸Xá0×]Y¥¾ß*Ûd_rƒ¯˜îk—“¥LÂ>€€€€€€€€€€˜"T¦¼ÿgnß+•Ž•ÖÒÈn)4¬K’©Az“IëhÓÞPô¾ÌyaÆ{Ûg›tL¾ûŠŠ�ÖÅ#@Üè?q�J0}æÜEÚ´í$ ÒÉ]QËÓ;!„ÒŸRcc“ªî}{ÓÌk'«âì88vâ”$�Þ³ÿ Ëê3ÒÓé˜Ü{]ÂB"€€€€€€€€€€€K— Rµ2Yƒ,EY¢1-·xÝÀLy×§[hLû7×íør»ªÑðˆpšpÇU@@À,�>cûé²®X{Øç‚é½Ó‡Ÿ|®ëËUƒÐ5“Çéâ­ŠàçØö�{%�ô™sE.«íÙ­«d®»g÷®.ó!@@@@@@@@@@Ìh6p2n­%o²T0mv`VçƒÆ´ÕD�××@á´eÕÒ2sóJ¦¼@@À‰iIÔu`7:¶ãˆ£øš�Çè§�8mß)ص—–|±B×ÎèCiÒØ‘ºx+"JËÏSÁ�tm�Xõã$DGG íèÞ’@:3#ÝI.Dƒ€€€€€€€€€€€w äÒÁ´&4¦õL슩 ‹¢½kvéªyÃ]"@@À=‡ç«ÓÛvž¢Êª:JLˆñ¤¯ònÚºƒ–­\£+ËiL[=.iG8tÔeÕ9Ù™’0š}HGDD¸Ì‹Dð–@t¤^ míwihL{{vÚh¹j!˜Þ¿a¯jô¹ù�(opwU@@ÀS݆ôE–ªŠmÂé £º©â¬>X³a }½v£®Úi“ÇÓ°ÁýuñÞF444Ðv¡•ÍšÙÅ%e.«éÝ«»ä?:¯‹ï}l»ìA@@@@@@@@@B’@^fMë—AŸï.uŒïÖá9Ž}+v‚R0­u´­5+mÔaLàà‰óTZX¢J|Í0Õ1@@Àyƒôè»Ï´J0­}^hûõÕªu´~óvm4ͼv 蛯‹÷&â\q‰$Œ.ص��VwY;z@JMIvš   ÈΜ-¢ù}ªêâäñ£¨Ÿ^ª8€XC€-Èýûµw¨¡¡å=³G·.4cêDk@- `’ÀÚ�[iËv½…=£â·Í¹ž²33Œ’    àg¯Ýß—žÿª�N”ÖÒø^©týÀLK{”‚i-˜òÖ±ï¸ à„®ò“ëâ ž`?ÓÙ]s¨èØ9GÑÝûÏ:ö­Þùlù×´uÇn]µsf]Kù=ôBr]F7û‘ÒGŽéï›Ê¢¹íÛIéA‡4€€;�šÚZኣJ5ŒªªjÕ1@¬#ÀßCÊÏ_PUxñb…ê¸-0“sE-‹êcbb(¯KG·(.v¼ÀFQQQÔ=¯³|èv[V~žŠŠ[´Kä‘‘Ô£[Wù0$·••Uºû¿³�ÖÖÖ9KB<€€€€@xx²ûwgo»‚ihL{{ú=/·w×IU¡ÌNYÔ±7Lͪ à@Àk�útQ ¦÷*öº.Wºœví= Ê->:±Pº5æ³kjj溵U�‰ƒþ}ò…@º7uîØA›„c�&°nÓ6ª®®qŒ0#=•èë8ÆNë °æî¦m;ÅDGÓ£�Üï8v¶³c÷>Z½~³*ù?~Huìêàëu›hïþCYÂèûß¾‡â ÒÕ à>ÐÏ:Æ    À ˜Æ<ðˆÀ¡=jÁtþÈ>•GfpE€}ÖoYÚâïùð±Rjjj¦ˆˆpWÅœ¦iMy³FɂşÑÁÃGUeøÑœYÓ)·½wþ2NŸ9çH«*Ö$'%^6×Ý¿%&&hRq    Ь߼�”£9Ù™L‡À‰çßG�«¿™´ ë±%¥�ýB×JR¸—¿ÙZµ­=ÜÚÖùÆhA@@Z@0ÝÂ{&;pZ•«ÇUð§‚�Vhß#WWþØÉrêÞµõþÇêêê…PúS:~ò”ª�ô´I(�•‘®Š7s°{ßÚ¾s/�(Tßµe»tÊ•Òýz÷Ô&á@@@@@ œ‹U• ´C:|4´Ó“Æ�¢QÃô®ÞÖoÞ®Ò^×rÁ1€€€€@Û"”‚i­LyûfÒ9^J u ªÆòõPã@ZC }÷öºâ'N�oµ`º¢²RÒ”Vú‹ã†ÚegIæ»S’“tí:‹¨¾Ó ví•Ò\¯³ÀϪAB3šÿ:´Ëv– ñ     !@àðÑã.GÁÚÔüý*<Ü;kP.+€ÄȈC«Pqq±Ð;t@@@@ P¥`Z ‚i-{ŽÙ¤®2$¦'û˜F«dvʦˆ¨jjhrTyúìǾ§;,.-+—„Ò¼UöíÌæ»ãbc”ÑN÷Y+šÒZßÔÚéi©�´Ùºµuà@@@@@ ¸°F´2ä¶oG§ÎœuDÕÕ×Sáé³Ä¿C@@@@@ ­JÁ´Vcšýø ØOà¸0§« …/X°š@–NŸ=rÆQí¹bçZÉŽLNvΜ+¢…N¬å¬ =ºu¡›…PÚŒ¶‚¤-ÒìGÚUèž×YHç÷èæ*Ò@@@@@BŒ@¥ø½q®¸D5ªáCÒ…TYÕò[„…×L«0á@@@@ �JÁ´öAcZKÄžã“§ÕZ‹íºc•¯=¤Q+´m³T‚é’Ò–9ž�ÉíÔ…ÞûàbÍeè+ü<Ïž1U¥Û/?á²¹n!�®©©Õ¥ËQ‘‘4hÀesÝÙ™­÷ƒ-׋-€€@è8~ò�8¯Ú·Ë‘æ•|ìn{áb—”ŠsWJÅ¥—Sgf¤QVF:eŠùîÍ\wצÙt¾�ˆñ)ç"—Õú!æE”‡ŽsSm˜4'ù¾a6Øq�Ûdîr}¹å{Ž2ð=¦kç\qÎ:ÒÎ=ûIlî{òøQŽã@Úijj&þTZ^.¬N�—,OÕ‹gßç“Äõ”FÝóº›ìöWh÷ŠcâšRù¹§ŒSîóXÊÏŸwDEˆþóýÝlà¹ÎþÃùwaM­øÛjñÇÏŠñÜŽ‹�¥Xa…‹·qq1Ô³[EEµ|nõç}ÀŠëô¤°¦ýM-³‹ŠŠR='ù>|T¼—”•Ÿ§êêJI–ÓÓ¨c‡öÄ÷e`-oJAă_ð•‚i% ûöµæt³»äØ×jh³2:¨¼¥åž ¦»vïI×Μ£û=x@_š1u¢S¶GÄ¥‚]ûhßÁÃNóp€—üG ¡4 §@@@À�=ûÑö�{œfãß8üñ–?–�1„âãâœæ•øwÐ; ˇÒvúÕ©‹†¼¿ðIÀ J¼rÀBñ1#†ÒØ‘ÃLYáb,tYôɺg«\ÿW«×ÓÕFSJr’å·mEe¥4~enšy--þt¹JˆÏVQXÈrë�×ÑÛóÑé³EÊ"´nÓ6?z8�u•*^>°ãœrݾ8¯võ]fcv¾ Ø�æô©ê¼L?šF^5X®Êé¶LÐ>Z²L,’PŸ7m¾¦®Ÿ6™’Ä‚ _‡‹bQ_‹î pæ-Zê.ÝsûÊ KÌæcõ}€Û îføX�GkÆ;';STv‹_”‚éâÒ2âyÁ ’%°àtkÁ.Z±fÕ×7¸ìV´XLÕ7¿‡$\��1çÉe…&²+íµ2aÌéy欪•k7ÐþƒGTÉ�>|?ÅÄD«â”UUÕ´kßAÚwà�îÙ Ìg´ÿÈw ÁtËùõÇ}ÀÊëô½—ˆÅmÆó‚çÀ�þ–ôÜZ¾r-móˆç“6ð{Çxñ,-Þ=´ßtµyq    ú,ýš¾lO.ª¡áyÉ4´‹ï>H@0훉ª5§›‘›é›†Ñ €@›"�Ú.]5Þ �k,«2^9èÕ»M™>K—4bØ ñÁ|Œ.¾±±IÒŽæ�ÓZó{ÚÌl¦›Òl¶@@]¾Ò]6U:›Î^$´«ÙêM†°Lê�Í›ø›¹0/pw>k OŸšÆj÷A²©èáâ KÌ;67/¶(‚iJ¿òÖ|•)}¹�<ÿ£¥ôà=·I¿—Üå–t¶^0ñ§:sûfûÏBií7K³e­Èç�ë”ï7k6˜»—oÙ¾‹F «cVœlÔ  ALÀÁtEm£J(Í<^üúÓA<1Œº^~^½Z49Ës¿tFõ"@”’³ÔÂߪõGe^åþà«FÒ¨q“•QÒþ¡%=RhKËáÀ¡£’@ú�Ð�rxõ¿d®[hHûóã‚«>" @@ x¤$%Q˜øÇþ1YØÉ,,c•Η_¯“|7f _¹fC‘Bø)ÜM°yð8áû²D«�„‘¬ ;–�žu¼xýæm†M³Oìí³…öµÐôm²`†ƒ3?”†•ø8rØ þÔMX=ùzÍF�•”^Â*Jÿ>=iãÖT(|�+ `Œ‚¯Î)·måyåúìè»7óÅ�nî«2°¯ôUë7)£û©)É”&þš…õ�‹Â'lù…‹Ž4Þá…¼àã–Ùשâí<`3·<·´>¦jüIóõ×C˜•w’“Ì™#W ¥YßQ˜ÿŽîØ:k�kƒ«ûç 6îÚñÙ}Ì–”�]ýtìÐÎÅZÓJÁôq±(ˆý9{â/ÜQ™…;låBéß]Y5ûLæç?§øù!® e²´Ïæ®W¬ÞàÒU’®P€GlڶéPš}Sóâ~¦²Is^<Æ÷½a�«Vøžf ëŒtýóÚW÷»®Ó>bñûÕæP#´£µÏÈ=Âä¹|ÏaÿãÄý&'+“xáƒÖ’ûèÞ+\› ì×;Àgº   `'KÓ•µMº>ž¯rþ�E—¹•Î^¤[Y-Š+446‹—l…p(Œ(1-I‘»  ` �¤ŒdUEµbñ“»Ð£ïUÔ½÷P]6ö'Í~¥Ù¼XÁν’@º´ÜµY¿Þ=%�t—N¹ºú  Þ˜;{†aÑjñ±—µywí= þö«ü{²›wÝ2Û°¬«È>Â(›.–}€òÇsÖ Öj5�Â;6ëläït¯Ð¦ætuõŽÚ~ýTч„ mÉ+©ªºZ(ÌášÉã$<ó>·¢DÕµk§Œ§D¡ÙÈöµÝYà`|}N¹VœW®Ç޾ûb¾lز�X¦ |n§NKúæ+£¥÷¾/V¬Q™û>tä8�fz;wì Êk×û°½y–ÞšÏß^xE%üÊÎÊpzN¼í »Ùjû’W.<á{ û[WW÷ÎlÜ•c“÷yÑ k‡» lUÉÓpH˜æV†N¹í…Ð9•ץ£XôRà8fkM'O�&ö?í¯Àó€¯mHIN¢™×NQ]#UB‹˜…ïŸkÌç³>Ô~3�-R?˜OZj M›<^«7‹ |u°ë:½~ZËâï bÑÏó/¿©š6û®øïægè]·ÞHé‚—ØL<»ÌR³V]”e°   Z,L+äÈx.¬¦”ÓZ»Õ¶ÇYì%°ÿP‘ºýbYu:Ž@@ÀK—„æ•2œ9§ý ®L%Z¶©ÙP(½üÓÅôÍ;o–L=ìÚ'­fW—l9b“k²v´Ñ‡ù–œØk ÄÇÅÿå £¡ÂçèßûPeöÌÙ"IÓSûÈU/ºuíL7^w�*Kxx8M3BCÎè„,¨5zþmÜÒ"H‘+?ú*�PšÓztëJ×_Nï/üDÎP[æ+3LLPk�²–-Pç�å�vº³ÚqN¹-«Î«³~s|kún÷|a�<¼¨CÝrãu*íT9�ßí¢££èÃO¾�£¤-›¯÷•`ZÕ°�ØŸ6 ¦µ�ÍÒó"^L¢ Îî¡Äý�‹•C¶dŸu¬ª ]»tRR'±‚ÕJ¥ ö3íOÁôWÂz€6°%Œ;æÎ¢¡ ¬ ñq’†+ hßÿðikâÏ™9�<±è¡¬3P÷�&Ìž1•Ú¸/m^§Ü6ßkï¸ù•PšÏñˆ¡u‚éÊ*µ›À@� 耀€€}Âí«Ú¾šå r LË$ìÛ¦§]þP#·›+ïb  –HHU,Ž�q¾†jÉÚfÚ´W-ÈægÂö-¨w¿�ôÚÛ $xΞüQrÖô«é{Þ#ù 6ú(oéàP€€¸ À~HY»QØÔ÷ù Ê(·ûFwš§W�<] ¤´�Í+ÍGs: V†RkJ+ËuñŽBS0C¼¬8 ññŠß:Â2”•ÁªsÊ}²â¼z26Oúî‹ùÂZ½õõ ª! ÐÇP(-gbá, Û”Ao@™:ûÚ{‰rd¬Á« F÷ÎîZRêã#Â_´6hùjM{s~wn…´uZyÌî *5–¸~¶*¡J+ÛeMðÛçÌ¢Ñ#†Ò}wÎ 9¡4�µù’^ñ…tzð÷uÚ»gw1Òt˜xƒö.[iA|‹ Jèõ5g¨°ÌØ‚XkFàükkjµ¹¬ö¥Æ™ÀÁæn´©êëêÔ¦tëÕÇm   `+�†:õÇZqÿiZÔáê/Å~ÝL{�©…Ò¼Z»^˜í^ÖŽî-´ÓZ|¿¹,€Dð�\­,ÖÆKKU»ºpÖÖZr¥Ù•𬝧®Ná²çJÅl.Yû;‹}àÆ ?µ®Bg!¸ÐšÂv•¿-¤µöœ2#«Î«§¼ÍöÝóÅH ¬5ßm4¾n];‘Ò—µÖ÷´Q™`�cᢑ�HWª0׬ F÷ÎîZRêc­ï±Ð…ýkkG?yÊ]&Ü 1[ö�îëÀmk/,bËîç1“Ï]=�šÎ~µµî–½Vr·ÑOX`áY™é:a«¿Çãïë”-1þ~ËiÊEE¡<Œ @@‚‘Àý¯î¡¥;K¥®ÇF…Ó¼‡Ò°.úßÞŽÍÁ´xÏбÀÞgAiÉg�¶±†t‚iá{�WÅk ´1,.€€ š ¾Ô÷‰ 1Rk�MD¬h¦Ã§ôšHáçŒÿŒô,L:š5î>ª•÷6Ž„× Ÿvu¼m¨—~”³/ÄœìLo«D9� %ÐÔÔL»÷‚ÛÓÄk++©¢¢ŠØ—4?ÒÒR(Rá—T¦'~›S’\ÿX4ëó‚èŸ6ðÇxwÁBw}²3Ýç”ûoÕyU²°²ï¾˜/åçõî]j„�ö#ÇN*‡¥Û× lØôr¨ÿ–M6<+Á°i³Ü�“âoQJa3çtfž;O˜÷^¹fƒª2öÛW\"ýñ~llŒ°ÔÐ^øœî@ÝÄy sæþ¼N£ÅBWîØg9€€€@ðØxô¢C(ͽ®mh¦×Öœ‚é|ËaüõÞ²êí©H+ ծ䷧ն]k�� ˆµ£cÕ¾‡Ú6%Œ@À ü‘Tªk$Átµ°²@¥ ‹ôBimù˜?°†tï^Ýå(—[~¦°0¹N“%¡òa²ãØ Í8ÿe�´³Æ&ŽIc„<�Ð'À‚çM[wÐæm;…ùTc–Õ55Ä­ ²¯äÖÖÂsm0SwÂ_ÍÚ²¡vìËsÊḭ̀7ËØŽ¾ûb¾”_ЛœŸÿÑR³Ãväcab­°°ºî©]˜®w€0¹*Ü£¢¢èß¹×í¨×¬ßLë7k}™;QxšÔÖžÚçdIóK["E,0ò3íÁt©�Æ´Y«Úq…ÚñÈaƒ¥Å.FÂiy¬µµu’�vöÓ¾\D²†<»¹àßœž,ú�ë³bëÏë4)1J+VœDÔ  Bàb�ÞZò…j}\kº‚ihL·f ˜+[ß T5¡‚i ‚XAà’�_¯!˜>/ÜkÎû²™J.¸J³p¹ðÄ1ºç®;)..†X‹gÕºMWÍ—ÆZa²¤Ñ,„ÎlÜaûÎ=Lû4Ú?àgÒÂÅŸùÌŸ¨v¯·Ã¯®Ö É›ÄXÜ…p#sZî Yº¯Ï)ã±ê¼ÚÕw_Ì~w³*D„GXUU@ÖcÕ|áÁ… w¾7E á´»àÌú’Q¹Ãþ¥—­\Cüg&‚mþíáI›fêu—ÇÈl»¿ªîújIºûŸ�ŽføÚ¹}ÎLiq/(3³`¬¨¤”>]þ5mÛ±‡æÌºÖ/æÙýy�²ùz�Ð!`d ÛêŸù–¦ ,y“ï}Ÿ1í�,þq�`/�Átƒ…ìí}Kí¬å}îÈŠ‚ª¬ÎÙ¦ñYÛ’{ þ# ªŽ ½'›H¸š6øÃJç®ÝhÅšõ¦òû#S†ð_†  úØtë!a®ÕYàß6l¾›µXÙ¼p „¸8½6©‘™ñ@é¯/û¬ç”ÙÕw_ÌÖ8µ*DEYò)Īît=àîüô>zÂy¢‰J³pšý;û2$'%êšÓš¼×eâ¶�àIàß’lÙjä°Atðð1:(4£�/$wn5ØÜ÷¢%_ÐÝ·ÝäsÍi^§ñZhðäôãϾԕñu„‘ࢲªÚ×ݸö‚ùœÚÙw_Ì—ŒôTÝ|è‘×…:´ÏÑÅ»Š`¡“ö›‚«ü¾HkjÔ[(óE»fÚeîfÆï,Oùù dä«ÙY~gñìgÚׂi£²<__~ÇsæBÃÝxÙô:»ƒ’]B•–•KÏèã'O‹§ˆ}ÜkÃé³E´ïàê›ßC›äö¸5÷^§1âw.€€€@è0R ³Z¿ÓÁ´‘°ÜÆÅþÚ‘И¶Ò7üPfíc«BÙéR*>QD¥§Š©¤°„êªj(!5‘Ó’¨sß®Ôe@^«´›ëªëèÕŸþ›ª/´øô;±ç8ÍûÃ[ôà³ß³jWOѱ³txËSýb6mM0]_[/iÐkåæw¢ðˆpm´ã¸¦²†JÄ|•C|Jeäfʇ¹åÚ¹À×] ­¹¸xýŠ~;û-™ù“…ÈW¶1B˜ì,_2KÂæ¨ËÂfþ1. � òkŸYvöuƒ€�“BN«©5X¤gL�hØQîÅÇŦù:ÒHûª¸´Ìm7HéÛm_½ÉÌçÔξûb¾d¤é³Oó±#‡ys*ýZ†*²ŸZ9\¸(|Öh%îV"62ãíMý’ÖõdoJz_&=5EW˜5ƒÙ$¿Ú¯F¦Ê+*[¾Íè:ãa„+WLl‰Äª¶X ÏCõ—,œìè/W­#íõ[$4§Í¦­¼à:õpÒ ;€€€€SF¿é�ÉœV`"ÁÁ´‰†ìÌâË•–vŽ#�ënlÒkL[aÊû¤ôì´wÍ.—Ãg!õè›ÆÑ´¯§ødÏýל:pR%”–;¸ÉœÐVÎ�mhصb;½òØ¿uƒºÿ¯Ñ�iWéâåˆ ®¡ù|G>¤¼AÝè'o=á8ÆNë„Gª}þÕTWRDx355;_,Ð,ÌŸÖ×׉¿zj¼mjj k¦^# ŒË,`މ&µ YKÂæËÂç¨È�x4¶î$ 4€€€%Œ>†è›ï²îÃÇœ›ývYÐâÄD!ðãÅbÊEÀf¥åå÷$°ª æsjgß}1_Ò%�i^ß² ~÷¾ƒ4°_oêØ¡]`M7½IMI&¶� ÖfgmL#MV9�¿¶¡ÄÝJ†Z3Þ|¿dßļÈÕUØ\°‹ö8ìÈÂ&´Yó:=MoÀ‘Éâ�ËçT]i�øý´bõzºnšõRò¤D½õ+åüW÷ÄõQ\lŒ.CY¹smo6Ãm¥ÿe¹q^|ÌšÔçÅ¢’¯„pZÊLjŸ[yÀuª<Øh #cV+^å×w-åÇ’ÖGY皌ӵÞkLóä~ûW¯Óú…«MùÒ«:_IË^ý”Ö.XEwþæ^|�g«â£„ÐÉ(DÅå%`4ø0ñãÁs«ß_áR0íy�(á ��`šËÊ=Bß‹. ["ý{„QLd˜0‰MŽ¿«çü‡ŽžPkqÅÆÆÒsû³'M#/€€ØN@©%©m¬ðôYÚ¹'0Nòo®œ¬L:s®ÅJ /^·i]=qŒ¶ëÒ1ûÉÞ³ï�aZ(GË95:VõÝó…víœKÇN:†Âš’ï/ü„n¿y&uhç™IoG%~ØI«'„EeX¶b Ý&›�B‰»Uly!Á ávAr…Iù.�r•Q†ûÕµµ*Á4g:$Ìy�ð¡`š­=uÌmO…§Î¨úX°{?±°tÔð!–úH6LïÚ{€†H1bѰ'!UÒöV/Pa~lŠœ¯+eàgÒº�Û”Q–ïWT¶,0‘+�|Í+ï¸NÍG3|¡1m‰ÔÊÈñµ‘ƒl3ƒ6“G+˜†Æ´j­ËÓl`X¾¡®ÞëJ?ý÷Ç´îƒU¦„ÒÊFª/VÑË�¾@Ÿ¿ø‰2Úí~‡^)'¯½.ß`Z±ºÌA1í�ëèé/ÿW÷wÝóƒp4¾ëò� û¨èØ9ß5ˆ–T"¢ÔÓœXWßH׎ £é£Ã©cVe KŽ)ÂÂwœX0ïÂ꺪^€€€€¯ df¤ëš\þõZ•¦$gà…¶¥w,–¬~h �-*¡ÓgÏQ•�}<�1DÛÚ¸µ€>ûrÕi~ œŽy.Q ²u…C "˜Ï©Ý}÷Å|™~õÒšfMÏ×ßYHó-‘„Ö5Bð§ |}•–�—®1^ü!=M-@ã>9~’þûîB:rìU “Êr`!( ÝØÿ|q‰z!¦œÇîm¨p·ŠŸ z*C^çNÊC§û];uÔù8×j_;-laÂô)ã „Ï—håÚ�ôÚÛ hÏþCb¾• +T—ÇÉÊ|ß/.X¼mÇnÕG¯¿³@zvUÄ \X[]n[ÎÃϸ [ èÀá£T$Æ_-üH+•axŸŸÅüÌþð“/hó¶�rQÇÖH#Ý‘¨Ø±ú>€ëT»   ^0 «@^WgX0(ÕE!˜6<—¶F6ÌFo}Lî=AKž_¤ë/Ÿ×ž#zS»¼vâÅÿßu”Ní?IÍmmþá°{õ.ɬ·®'¬�ùÐó? w~ó_:¶ãEˆ3ÃfŒ ½ÅI‰ÐˆŽŠ‰¢”l½I°Ä4ßúì 6š<ÇVÏ[As~z[°u=$ú¥4Õ׫?ü˜¨öYa¦ ò€€€€•²33$„òÃ7xá•·(¯sG©)ª�=WàSÄ@IDAT¬rhMh³€ÿ8ðþï=x�´o÷ù=º 3Y¡ […)Ú­»…»ŒHJIJ¢Jñ‘^+ Tæ¥ý`>§v÷Ýó…5 Ç�ºJ29¬žW—èБãÒdzð*V˜ümhh ë§^bN‚Ùï}òi톭Rß”ã8uæ,½'4À9ð}€ßgeÁ ÇõïÓ‹fM¿šw}B…»UÐŒÉ]»\¾§»kƒ5„Ûçd«¦¼°‡çj”Áï wõy›ž%žO#†¤õ›·ëª8'|$/ZòÅ•ø0Š+�µ‚xNäçQ�n]¯äs¾a-ìvÙYt¶¨X•é¨XŒñÒ'¥yÎó]9×Y£›5ª�B¯îy´E<‡”�…Ìó?ZªŒr¹ÿÁÇŸIé‰ ôýo·€€€@�0R:6RNnÍp¬Ñ˜6�–©{·¦£Ê²üª Ê—We<ö­#Àæsµ¡ÁKSÞ_n“Ìñ*ëk×½ýráoé/?F·þ¿oÐíOÝM�¿ÿýeÝ¿hÒ]S¥rþ®»ÑCÏý@>4½Íì”Mßé1zfÃsôçµÿ Ûžü±àŒ¬~¤½]|aTâÌ0º.ë ˜6O9A@… F ¤ëk\Z‘üÇœÊþ,ˆ:¨Ÿ®ŒaäbGN³c;qìÕ»xK—$¿�ÅBh­J§$'µd Á½`>§¾è»§óÅ›)2rØ aÒÛµ�µŒÙ§vm]�C(Ím±–f „áÃ}Ò¸‘.»Â÷ í·¾ù+„w«Ø±V»2°iìí²•Q.÷ó4Bl>ÏJõ. [˜8nÔpÊÉÎtSã%Õ3J™ùü… å¡ËýÑ#†:MçÅ[Ú¹~ÁÅ\?f¸´ðÄi…Š„H!P6¨¿"F½«Ô†æû†‘PšKpÿÌ¥ûä÷0=ì¸X}�2oB½Xh�   œŒd»â3…¥A-áµ´jû*ÓjÁ)_$íkµm×l —¦z�ù>³„ö®Q¯l «o¿ýì÷¨}�º*bâchî/î Gþó¨¤ùÛ¹_Wi?61N—×l„vþ˜-‡|m‹@õ…*Úöùæ¶5èm¤Á‚hLÈÉA7@@<&À§NBóËl˜<~4e˜7[Þê|¬™vë�×S\l¬ÛªYCuîìnó{†`>§v÷ÝÓù2vä0ÝtˆŠr½x˜ªßqó,Is˜;ž„ó"˜æ>Ø�ôÍ÷¤ûÂô°yA G›È*ÜM Õe6 ¯] À¾¥=ùÎ`´°âð±ã.Ûµ#‘«|óŽ›iÊ„1B[ÛõugÔþù‹æJä÷È3¥]-·SQY­VËiü<š~õDÉ2‚çl{Ý´É”ššì,Y¡•×W¿Þ¢ÁµSTuº;°ú>€ëÔq¤ƒ€€¸#`$ HSÞ±QzùvBŒÞG¨»›M×¾ðkWVš­ų̀'ý9n¨õÎÇôÙ#gT çÓÝÙ]sTqÚƒÞ£ûÒSÿ�ÂÄ­MV—ž*!³}KÏÍt[Ÿ\yI¡0±X× Rz‡ Šf·"𘎦ò³eT_S/�§}�\êÐ3בÇQÐäNõÅj:¹ç˜0i~Œ*Ë+(%+•ró;QÏáùÂùåë«äd5 ß»rðd¹ÀÑ7Þ¹ùñÛiòÝרâÌüdÄÃTWÝò#„} ÿ~ù3­0vÕÎÏÆü€ª/V¹ÊâH»ëw÷Ñè›Æ9Ž]í<6òª­ªudyàï�PïQ}èõ_¼D»V¨LÄəӓèŽ_ÝCƒ®vn¶JÎ+o‹Ž�£Wöo!”6^1�Õ9›îøõ½ÔKøã~rêO%a¸\Öìx¾~çKzÿé·äbÒö ~- HU‘^Ô FóþçmÚ²t£SSØ,”žt÷Tšõý9)|ú+lýt#½òØ¿Í'g¦PScU�¯tÄýâƒßPn/õ½lÅ›ËhþßqäÉÔ�~òV˵ëHPìT”^¤·ž|•ö‹Z¿ér6¾æþü2í*9ÊíÖì|áÏÜù´ª¾»~ûM=g¼*ÎèÀçôÑ«¢zÅâ—Ç¿7™þÖ£îIq“n|�Žž(S¥'&&ÒîÝ—ýqªp  ~$Àþ˜YÛ®Jl#„å ha–? +M`× ËD¬‰)å‰ÂR6GÊïP¼åÙþ Üÿâ’2©<†Ô”ñq¾E˜XRZ.„·bA©\ðøAh'¯`>§¾è;Ï—sE%T/„ƉBÃY9_–|þìÞ§:=ßù攞–ªŠ3{Àæ»YpÈæñ««kˆ¯P&‹÷Bxò\äO3ìÛ·¢¢Jò=#mù:b¿ÄÜöéëïëßè\3w£ñ´õ8J—Ÿ¿(]K<ßx1’ôÏ ž—,ŒMˆ÷ÞŠóå9S"\A°{ öÏõJó\<Å<÷$°n®«º¶–Å~JJ’°<’!=?¹OŸ©œŸï#UâÂ÷V†áë3V\‡â„ðšØñq±RŸ¥H‹þ³ó>€ëÔ¢“„j@@@ Ä ,ØRDßK½°ïæaÙôÏ»ò-¹e¡?ÝÒƒ†tI¢çªix×dš1 òNj+ÒþƒÆ´–�õÇFÓJ¡‘'-fvÌ¢S E.�§ã;ŽRW!ܳ"x´ÖƒuÚz+J/Ðk?ûíúz‡ÓnW–UЋ?|Ž®¾ïZºé'·:Í''ì]»›^ùÉÿQMEµ¥Û²ò‹?ø=>ï)ª(Ó˜Îò`<ºŠ-ˆ8¶ãˆÄ„µË]þ.õ3Ú»zÝó?PÇÞ�]e÷Yû6>s-í3G›«ß[!ù#wDx±³oÝzýç/ §]¾^~ôwë$iᇑ¯eeyOæËmOÞ­,jzß_ç4JÒ•÷˜1g< Úg…§å‘@@ì À‚9þsøã|NV¦«,~Mc™+­ÖÌŒ4¿öÏ×�ó9õEß�Í&:zBuºøý-%¹e‘ƒ*ÑÄ nù/�½]v–ôlýfîÁÆÚýe-êŒôTéÏ®ö¬œ3¼ø„5¬�OŸ©œßU}ÎÚ±"ÞÎû€•Ì­+êLF"&«×Æëí3·‚Åí#rè)¡)m§Pš»Ç/ÉÊÁ´’†=û F¦¼f­=i5§[{]ö×ù�Ø}LÈG·v)”VöýË×>wª-ç;°a/½ðÝ¿»JËyk*kè³—¨ÌxËiþÚظ�þz÷ÿ�;¡´²§ž¢|ëbÎ�ÆÎ�¨ÒBØôñ:•†¿§ýÜñå6zîÛu+”VÖ»úýôüwÿ¦ŒÒí{:_V½÷•®wþ<§Z“ý��ÍK×>+t    à7›¶�Dõ‚\6áËZø      ¾' UÐäX­”¿ø´Üù¾ñý© ½Âô~}ÌúqÖÒyƒÞoññsô¿wý�–<¿ˆj…е5¡ïØþ”/|Rkÿ´æ˜[Ó—e�£Ø×t—þyÔwüÊ~«µ�/æ…ϼ¯�V/}a±Ê'²Oí¼AÝiÊ=ÓhÂS¨çUùŽ›À¶Ï7«Êûó@Ÿ‘‰jö>`Ò ê?a åtm§ë&›]ÿôßëâýÁýí5²·£y6ß¾éãõŽcOvøÞ´èo ͼÇ%ÅKþ™‰öžÆmÜ´Ÿv­,pÚœ§óåðÖƒNë2Jð÷9Õ ¦…‰7OƒWOë@~k ð;òªu›h•ð¹® úöÒFá@@@@@@ÀGš œ? 1•¥Á2SÞ–öÊMeZa4¦Ý³ ¹Q£¥ÎUz+˜î'”ùÂ7óþõ{U=cóÎKžÿˆ¾úï4^a'c*%exnÆí¾g¾£ªW>`“ÄZ»rš7[Ùß4ûèe¿À1ñ1R5—Ä•ûå?× ¢YÐØ ´Ì�Ì3-8L7«íö‡ M6s}Õu#UÝcMÙ7þß+T~FíOW•ÉÇìOZë›ýkßþÔ=4xêPUo8}C¥¾Rø¼žpçb3ïr�üZY8ƸDá*ÅœY?6¥­œŸ«ç­æµ'Ê]3½Ý°h-�;zF—ö�çÒÔoMw¤9ÏóB[¾ôT‰*ïâg? ¾^´÷<_Ì;Ωjpn¢â.ûï’³5B0-£À@@@š/pd3ÝìSšÿêÅVø�-);/|(Óñ“§%ÿ­ÚA$ÄÇÓèáêßÚ<8°�@L”^Ÿ9Ö ®5=€`º5ôÚPÙ†0ýTQúõŽ|P2o¬ô5-×Áfª?ñ! þœÆÜ<žf|÷b!g †«®Iwýö›ª®±–óÕß¼–Žl;DË·:ÒøMÉÉbjߣƒ#NÞYöÊRy×±�ó³ÛtBiNì5²ÝÿÌCôÌ]O;òú{çã,Tu!"2‚~ðòO©CÏ\U< ›1B³yk94‰W›¯£Ý GI‚aeG‚—;¬Íþð ?2Uzà”!”’•BŠ/Hù ÷ž ^ØÐu`7SååLl@X(}Íý3TÑ9yíéÑ7~AOßô”ʬ9_˶Òàk†©òûb¾ØqNUƒps Õ˜n‚`Ú 1$ƒ€€€€ÿ üó?¯Se[À2Xfï¦{ÇŽ èè(7¹�       `�ëfR÷¬8:\Ì¿ë.‡9C³å]K¶zÑ·%ÕÚ[‰Öo(4¦íå͵7iL{écšëKÎL¡ý÷çÔgL?>4 ¬]¼òí/é××ý‚–½úi@ùSæ³úº‡fö�#i4…9®øÄ9ÞèÂÑ‚#ª¸Ø„X#4±�…®ƒºIæ±�¥û2¾²¬BçWzÊ½Ó …Òr¿ø¼÷æÖ•¡¤P­-,ì_+“[¿ïA},XgMxe`¿Ïž„‹%tZí¬µ=Qh†…”ìTÑæ8]Òñ]GuqvÏÛΩn$Î#Ø<¾2@0­¤�}Tlãͳ÷x¶4lpدÅ�N Žý�P&F|o ýxZgúöÄ\ZüÃÁ4¦›…ïvOƒöYáiyäðŒ@tT´‹a”š’D™锕™N,”Îï‘�´ bH�P#”‚i˜òöí4¬ ‹PiŽÊ­·ÆÇ´\‡¼�ˆŠ”L'�ºiíþz-õ3:¸y¿œìزöè{¿}CÒ´vDúi‡}[ÊΔéª1S7›~„Pl éüÛ™¿ô¸k…?çKBøÈ&Ò9ôÖ‹þ¶åÿ<®ÇÊãoŸäLs½«ß_iZ0­Õtæò)Ùi¼qR²RU¦Ñµõøb¾ØuN]\“¨Õ˜†`Z‡    €¦LMµuuA‘‘‘âOl#.o“)JüîCh»‚òW¡V ÓöNàzŠ�´%µ­X¥1­¬—Ïmÿ‰ƒ¤¿£‡éõŸ¿DZÏ{×î–„×ý& Tõù¾vzÛ­o®'2Ú½ <«Ú÷¶ßr¹jóärš'[HËBiOÊÙ™·ïØ”Þ!ƒÊN—JÍœ;z†nÒ/˜0êCUy….:>)N§�ˆKŽWEU_¨’4êåóí‹ùçT'˜ÖXPArr 3s’Œh‹ tîØÁâQ€€€€€€@(ÐÛg‚Ñi… LÛ{Òê„ï°ýT©¯m17lGòu§Çß’úŒé§«žµªC%Äk‘<.­™ë@kFÇ,KºcI=VV‚ò±·LTU¹ê½¯TÇÎŒ4Ú+Êõ>Ƶå+ËÔíäÌd•yC_Ì—@8§lÊ]¼¹&´Ï e}Øð-� Ô˜†)oßN’ËÓ—Í++[fŸ»v‡¸¤xšýè\b-ie8{ô¬ò0¨÷Y#W.³Ö¾ÍMÍ^5™“×NWnÜ­)5']ï*"&>ðÓÜß1sÆÓ’çQSc“Ôý‚e[)³S¶«¡Hi¹z�ý…så.˱)ó ÅçUy´õøb¾Â9ÕjL{#˜Ö>+T`q           àSA)˜ÖjÁ566úZ[k¬^ø˜6ðXécÚÓŽ½;SbZ"U*´MË„¯éP iíõ‚éâãçlžV•c�h'êâq»9yíueX(=ý;3uñÁ‘”‘Lƒ¦¥­Ÿn’ºÏêµóWºJF'½`úØÎ#4‘®vZöäÞã¤] �Ñ1S•ßó%ΩvŽzaÉ[¥i®‚ˆŸÐÛgöy¦�úÕÜÜLõ5jíìÔviFYƒ2Ž5CYð® ›?Ù@M ®\”¶B8Ÿš�ªlNÚ/;S¦‹3‘Ö>�¢bÔf—W½û%�w£l¦î@É3îÖIª®(I¨™¹j�2'mû|3iMu+ŠÐ×ïêÍ„gjL¥ûb¾Â9�ŠQûY÷FcZ»ˆIÉû           ¾%”‚i­°‚i{'�¤1m(˜öÜÇ4 S—¿öy"T]·`iµ³;æw¶wÐ>®½Sß®ª+Ê.Ò†�Öªâ´Û—mÑF™>62³] êcSÒž¾L¤*ƦÈÿqÿ_¨¢ô¢*>Xz�èM9]õ&Ë]�'>%�XÛ_ëiñ?ú?±ç8mY²A™]Úï5ª�.ÎîùçT»ØAÁD„öYa¢²€€€€€€€€€€€ØDÀRÁôüÍEôû��Ò’öšYÖjL³F-ÿ!ØC Á‰Æ43ot£Õ«íÑñ]Giá3ïÓ¯®}œþrÇï%!õ¹cgþ{•ù«ÎWÒç/~Bï?ý–2ZÚïØ»“..˜#®¹†®ûïþö Z»àk]üÅ’ 4ïoѶÏ6ëÒÌFiœï_¿—æÿ骫®sTÃZÛ'v£Õﯠ�+ ñÚ�›¿ƒâãTÑEÇÎÑog>A‹þ6ß©ö4׿Ði�*¨ñáÁXá7ÛÓ0ë‡7銬™·’Þ|â*?{YC½¡®�¶ ¿Õÿ¸ïÏÄûÊ�?º/õ¼*_%íûb¾øûœFFE¨ÆÝè…tí³BU!@@@@@@@@@@@|JÀ2ÓO.%+…nüÉ-ôÎoþ«¬Žj*ªé‹——JñÉ Ä&¢ã“ã%á7Ÿ¯²Ó¥Ò¢’qBè{ûS÷¨ÊÚÁ¨Ùciñ³ …ðXmVÞU?û�H݇õ¢Ã[¨²±6<ÿ1cÖRg¿ÕFá†Î1Š&oæ k{b;ÐÎi]�þ^`G �i ì‚€€€€€€€€€€€Ÿ ˜—(ºèhCÓ%•Pš³¾¶úŒm‚i#a ¦£¢Ô>I]tI�4¦#�•ëYÃ3.É|e¥§Š 3³pŽ}»óK!„àüíaJL×7Z/†ÅÇÏÖï,²¶²†øORsô>˜µy¬>žùý›è¹oÿU'¤d iþÓôV_¬ÒF›>žöÀõ:Á4æÅÅ'Štõ”89wrÆ1s'ÐÞ5»…ö¯±‰qþµ'×([6Í=túpÚ°h�G]šóØ­ôÏž¡ÚªZ]9£…r¦17O .ýóäCÝÖÓùÒsx>ظOW�«žÓK«òuõÆÂ{Wý7zV¸Ê�4ûK=l¯¶AoF»Ò í6³ÍiF76z®Mg¶½¶ž�ÓÎ4¦5¦‡Ý±ª,¯t—ÅizJv*Ýó‡û©ë nNóskÁþà•ŸRrfŠÛa°6ëìGçêòÅÄÇèâœE°ÆôðY£�%ëâ•ÚîºDÁBÀþþ°ø{DÒŒ6Êã,®ä¤ñ‚gùý?þÖI7Ýe@=>ïWÔ¹oSeY‹úÖ'î¢;s¯ËüžÎ—{þçòÔo³_Ï©F2]o`=Á% ‘Á´;BHß°D0í»î^nÉH0ÍÓöh ¦¼�§ŠÖ'®»Üÿ¿Ñ£oþ‚¦=pµïÑÁ]vÉÄwn¯Žô�ß‹~ûÙŸhØŒNËDDFHù�fð ÁÈ<9›Wí±2�÷YÀ¨ FqÊgûlBÜŒqgå]ÅÛY·«v5-*&Ê¥ÿhoûíl¾Èõåtm'ï¶zk÷9ÕjL74xnÃèYÑê�£Aìºyæ³ÛiÏé*it/|UH‹8˜Râ,'KuZ+æöÑI06@cÚølÆ›C¸E>¦íéeÛ«uÛg›ˆ5c•�5�@ T\jnV ¥Þ Á´‘u U¥8�¼»ñœC(͇Šjèíõg-¥2‚ihL[:/•5 ³¿Ø³Qh¬o0ŠFœ�¶/ÛJï?ý–®…Â$7„ hL‡Ê™Ä8@@@@@@@@@@‚�À…j½Ìïb­çÖL]�ÕÝkKÞ’ùgW ·& Ó­¡çYY6åÍ!<Òx ûÊE°‡À%aÇŸ}p×VÕJþ™Ù×ô� ûh×Ê]ƒ¬-=鮫uñˆ�`%Ð,æ¿244ª5¨•iÎö¡1íŒ âA@@@@@@@@@@@M@ýUþrš‘ X]ʳ#KÓž5ÙúÜF‚ihL·ž«Q �i'¦¼¡1mDÍ»¸çú;=KuÕu⯖Xè¯ÕuVó�?¹…"¢‚òrv6$Ä·q—šÔ‚èÆFÏWe=+Ú8V @@@@@@@@@@ \2�L‡Y,™JI–‘°>¦ çP«#›®Ì¸p'¦¼ê[Ý*¸L üL)•{„#<"œ¦~k ¾f˜Gå��@³ÆÇt#4¦ý”¡          AL@cÈTIx ¦�¤åFRu«Î…‘`ÓVÑU×ÓtÅ”7|L«¹Øq›çQµÝ†ô ÛŸº‡:ôÌõ¨2ƒ@0hÖjLkŽÍŒ¦¼ÍPB C+¾VgJ�i#И¶ç’‘My³f®Qh„Æ´¯âb� ¦ÃÄ’ö#ÍBèö=r©Kÿ<ê?i] ^5ŽB `´‚é&/ÓF‹˜l˜è€€€€€€€€€€KÞd±Â4¥`ÚHØÁ´=s¶ùÊ”s®1 SÞV‘ŸñÐ,Û$ŠŠ‰¢Hñ-þxMiíÓ¥}«ÚB= è´‚éÆ&£G¢ëQ`á†k>H™€‘5l#«Ùr~o¶–¦Ã äå—Ès!‚Ù ¦aÊÛ,=ÏòÉ‚i§Ó L{FÔyînƒ{8OD ´1ÍMMªk}N«�=+œdE4€€€€€€€€€€´iF’]«5¦�í38öÿÏÞyHM|üqý8zï½”ŽˆHS,ˆ‚н÷Þÿv±÷н¢?QÄŽØiŠHïý€£\ùç›#KÊìÞn’ݽò}ºdf2åå“l²—7ï�ÊØ@�é蜴<ÓT•×t. ÓÑÏ^I ”°‡îΣÇt)¿"xø$@$@$@$@$@$@$@$@$@$@Ñ$�¯p™ö;2i±4L'&&:¸Ó0í@âK�á1�Î’œÜsZ½}”�� ”zy9Ö{Kžö@T<CrRMb Ù€;I€H€H€H€H€H€H€H€H€H€H ”P½ƒ7ù¯úBÅôJ)•ò¾h¬u¢260”·_t­ý䙲‰‰ÎË…L€˜$ð�€}�it¬š­j@Õ³"T}î#          �ÒJ@ÊÛçXÞNK£ Ú)Ie$=ÅÚUå²É.z ¯‰ÊØ@ixì"­•oZ? �@8rmÓh©aÚï#áèÍ:$@$@$@$@$@$@$@$@$@$@$P ¨ÞÁ—1Ù ý8&«5Ùe� šËô}ë[Z_Ú§®%ïg††i?i†îË<;"!Éy¹¨¼C÷Ƚ$@$P8µaºðvæªg…y?Ó$@$@$@$@$@$@$@$@$@$@$@TѰUQ³½ðJòÒØÜöÆÈQ�ÊËâ {äˆzå¥G³ŠæÝ¾¦UÆzLûŠ8ЙÕcZa˜Vx53A$@. Ø×˜F7ªÙZ¡ºW=+BÕç>          (­òÌÞª‡ $øÊÛ7Ã4ôëÓ²²þ‰ö Sh˜Žuó5¨âž›k^…:::°W �ÒG@å1)Õ=+Ò>XŸH€H€H€H€H€H€H€H€H€H€J�|1[�#ö×2í«aÚP1ÚÛDÕZǹ¹Ñ¶Ô÷Ÿ�”è`�Gî&¥©`æ—3dúø©aòy�^,•kU «.+‘€†i®1ÍëˆH€H€H€H€H€H€H€H€H€H€Â#  å]¤=¦Ã;,ïµT^pô˜öε°¡¼U~ý…t´vñjÉ7µ«Ó¼®$&>GbëšÍ²/k_ ÷ŒJå¤J�ª�¼*‘{0G–ÿµL6­Ø YÛvÉî»õjUëT“ê kJífu¤Z½êª¦a•m_¿M¶¬Þ,ÛÖm‘­k·Jöž}½ÊU./ Ú4’†íK¿¿µai~¥më¶Ê²9ÿé|ÀèÀþ’Q1C?0jÞ¹¥$§&+;ܼr£,›½D¹Ï^¸wç¦íP˜J Oûî:$ÂÅ,TÏ GŸ,           �ô§ƒjZ²Ó6èUáÖ@/½G©­ÊØ@Ãt”`›º-“èt×ÏÏ‹,”÷ŽÍ™òÈðûL½Š\ýúMÒ²[kK™*óÎm¯ËŠyË»šÝR®}û–@ÞœXó÷*ùîÕ¯dñŒE’½7ۼˑnÛû|ùIºÙ±3HúÿâÙÏäŸé ƒÔ((†‘ºû)½dÀÅC¤l…²!ëÆrçÁìƒòó{“å�ϧË&͸J’SS¤mïörÒµ§I�F5CU徿NúS²2³50!¢M¯v�<V¹9NôódmcÏ©žö:Ì“ � � � � � � � � � � ˆœÚ©ºŒ�²Vö(°ýÕ«œ*Ã:ºwîT1-–†i†òV�Êè—©Œ�&£î;WŽ<þ¨Ã�Å)µàç¿äãÞLGf�¿&Ï–¿Ì“Sn:]úœÕ?ЬŒÂ‹>°“ �‰O�×¼ê·ÊZtmMÃt€†3‘Ÿã\ž!!Âèª{–s$–� � � � � � � � � � � 4¯YV¾»¾£|6{³$'%Ȩ®µ¤bº¿¦d{‹Ñ9Sè1}ø ‰Nwýp�¿^µÃz³;·î°tS¾JyK~×Ö�òâ¥O˺×XÊÃÍ|p÷[R«i©Û¢^Ð&ðžñÙ´ ûƒíØ»k�¼qÃËrâ5§èÞÓÁêE»ü×ÿý"?ø¾%”z¸c",ú§�|$«W’ŽŽÖ› ¸h°ô:ýXGXwú›—&:ÊY@áÀõf—H×ŒŽ´¾}<æI€H€H€H€H€H€H€H€H€H€H 4€qúÖÁ�¢vÈÅÒ0­ò˜¦a::׈9t®Ê;6V†é›2ÆÔrU*Xú}ͰÌ(�’–¢‡ê®×²¾ìÖÖ:þçײ;³`½i£¬­üó»ßËÙ^`Y¶kÿY­4¶ÂøÕ¼K+©Õ¸–äikg¯Z¸B×#/׿¬ýº0n†é?¿ùC>ºÿ=Ë1™3•jV–mI…j´cX)öµÀQǰeÕ¦@3¬=]±F¥@ÞH”«\ÎHrK®`2ŠYð=‹p‰iQMb2÷É4 � � � � � � � � � � �@ìKÃ´ÊØ@Ãtt.š2’èXÅ=°3ʉÌ�Ö0Þ®r­*–Q‡ß:RžÕ¼¥wn>ìY]¥NUyÏhAØäĤ˶#ä÷ÿ÷¶ÌýþOKX;:˜Ìûi®Ã8ë Ÿ¼\j7«ci†u­¿ÔÖ žòá���ß�Žh"—¿x�¥^¬20ì#|·JŽè×Qθël‡�yŸÆè«çp ?šÁ ¸ÖJ0Q*Ø=¦# ã Hñ¼g•Š“Äƒ$          �8c3GÐ8^UUƆÜ\«w]¼t+iã&˜ Óe\†üâ±lÎŽ®Zh^Êf©Ñ¨¦\÷Ö-R©Fe½¸ó‰ÝåŽÏî“Ö=ÛYŒÒØ™V.]Î~à|)g ¾aÙz±{:cü3}¡‘Ô·m~ɳW9ŒÒØ™Z6U†ß>R®{ƒnð…'2Ò7òù“ŸÈ¾¬½–¡ñ=:ù†árñ³W:ŒÒ¨˜®ézúgÉ�«NÖÛ!l÷0­>…bA ÷ õžž�hŽßžªgEx-Y‹H€H€H€H€H€H€H€H€H€H€HÀoÅÒcš¡¼ý¾ ‚÷—Xˆa:k¸æká±§2Å¢dåÚU†h»To¨§ß¹E6.ß íŽí`ßmɧf¤ B{/žñw <)9I‚àѧYjj¡»U:˜ë´êÞFþ難õ>N<‚µ·ížáÐãô;GÉ1gô-T¥A—�(Žë$uš×-´®ß:|“�;¼àSÒSƒµgÇnÉÚ¶+°?Q;§ÕÔäÃIÀ[wÝëdOf–ò}ÏÎÝZz·dïËÖ öe+fHFÅr’Q)C�®ß¦¡Å;wÓÊ�’o å/z³`Ýñ�ÚDˆÂ¤f“ÚZëðŒ²yyy²eõfYÿïZY¿t�Þuí¦µµÉõôkÕ5 Ô¸Û×o“Ú±ª$Y»Ž«Ö­ØÞËç.•ÍZˆ÷]Ûvjûªë“5êµj ³ TŒ0áô˜Ž| ÓBgu          ˆ"�bi˜VÊ;:WI¢f4DeS•õýÚþ­­ C™YZuokÎZÒÕê×|‘Ì�™–jÍì˜ò5£ŸYöîÚ«‡éVߨ ïéxÊŒ ¿Š}½^ðé9üذՊ‡Qʭ׌ÃcN½Ç¢ç-ìxï3ƒÔ¿xæS™>~j  ÎÏó ^äƒ%ÀhñŒE2ç»?e¾¶Ýîa¬ÊŸšõRÀXŽ0ñ ½3Tu}Ö,pØÝ…ÖÃĆÂ&@dïÙ/ŸŒùPf;SfTö £tŸsúˉWŸ*I)¡oýO�~DTáóÑ1&ÜóõÃú¿}:U>j¼ìÕÖm·KF¥rrÎC:AÄÞÎÈçØÖ˜NÔ"D*ªgE¤}°> � � � � � � � � � � �€?B['üÃ÷^è1í;Ò &Éac¬Ê“XU´3—;¦}ô³£%ÖEö*–®—M+¬^ÐÍŽn´ÛjõªËº%kû±–õªù+¤Q‡&�²¢˜X2s±C­!W„"/òbšÐUUØ)ÚdSFKÂëº0Éܰ]^½æy�±8R)[!#`”FÛÂG‹l„üBz\9¹¼}ËXÙºvKÈŽaxÿñ­Iòϯ eô˜‹ÍÁ$3LA¬¹þá=ïëBàIýÊ•ÏÉy�^"Gé´^°öPÞII‘_¯4L£Ër          ˆ=�ÈßôÇ^GLj*c=¦˜|)H6¦á„£mÜ„±m‘æ1m–ÚÍêh^˜G˜‹"NÃô¥ËŸ¶´+W¹œ ¼d¨¥ÌœAHe»¼sÇë²zÑJ{q‘ÉÀ¸jÁ ‹>•kU‘£Gn(´tR‚2ýÂ%O¹2JB‹ÇK0éà©sÆj”6ë/ôç.xBéål®, ^ø^ŽøÃ`U,åß�ý*¬É–FZ&GÇ,I.&R¨žæ>™&          ˆ�ã1�››;j¥h¤ä|“Ç´Ò0�5ð¸|õªçtïLó /=1h¸ms=U:ç@Žü1qº|÷êW–PÅ0`�q×9úzÁªv(ëzR-Ìó,Ëî-ÚººOžõ°œpÉé7z€¤•K·ì�wD„–6K-¬YœÞšÅæv%5�PçvÏyãX“SS¤N‹ºÚzÒ‚5É“S’õ°è9Ê-|õnmÝéz­êÕõ-ê`Mn¬÷l–ESæ[ÊÒ´þšwie®¢LghÙ*�Wó„'þ'y¶µ¬Q¡¿k6ª¥w°æ4Ö¼6 Ö·ÆwàÔ[Î0ÒXŸ}ç–z>G þÏo‹û�@¨ó› ÂàcíôÆG6•ú­êü2ÏRëhÿûû?‚õÖ#{(ïdzLG‚�uI€H€H€H€H€H€H€H€H€H€H ÈðÕ0=yÑvY´~·´¯WNŽk]%jËPÞQCëèX3Ã.S¦Ý¬ûz¸Ãà©}š1^¬·mÛ:�ÐÙ\ThÆÃU —Ëò¹Ëä÷ Ó4ƒÛNK¬™{êÍgHû>,åöLÛÞGHËn­u#›yB$óÒòó»“嘑ý¤ïÙý¥|Õ æ*qKïÑŽÝ.UëU³•êÌ’BôÓ$@$@$@$@$@$@$@$@$@$@$Pìø²Æt¾vØc§¬³üةּe§ÇŒÊcš¡¼=B ÒkL'ΰ& Ët¢ cQAgêM�/O�~D–ÿµÔQáô;FIÓNÍåÁ V¡¬ƒIæ†í2gÒ,™ýÍLKˆå`õQ^¡ZE¹NómÝ£mÐjµÐÇS>üIî|»üðÖw‚ðáñËíbönµï+�yœ/»lÐÖa.ʲ{{–ãÚîwî¥QÚ8\³öpÚ[×n5vG´Eñ®Ãz <¦íR±F%I/_ÖRìÆ0�có˜Nvq¯QMb²(Æ � � � � � � � � � � �@ÌøâîªÙ(d÷~«wÛνNc�_G¥2LÓcÚ/ºÎ~à5½¯ .§e:I[O׫Ìý~–üýëY¬­c«ò^Eÿ�v–^#úD4TµúÕ%µlªdïÍV¶ƒAkåâóÛø©rÞc—Œj…Iºfð¾rì ò×säó'?‘­k6+›`mgìŸ;éO¹ì¥k¤|•؇÷VæöeíUê[Z k7­#XÙ,¯]÷’¶NtGé<´›4;º¥~™÷Ç;­špCqaÒ¦W{Y<ãï@µ`×n BˆDç!Ý‚î=zpWÉÚ¶+°¿Q‡&�t¸ ûÓô˜—ë‘ � � � � � � � � � �@Ñ$à‹a:Ö‡¦2¶Ñ0½³�¦­3½OÔ—J²¶ž®WùõS íbß®È�©e´5±/}áÉܸ]7’íÚºS7@Â0——›gó¿?ÿ•GN¿Oî˜p_Ød¬ãÛþØ#d¦ùÇ·'ÉÆå,}™U WÈ#’«ß¸I¹ž¯Q/Ûô VÏUŒ±}ý¶h Ulûlݳm`Ídã f�?¿ùCÿ$$&Hý6 5u iÑ¥•4ïÜJRÒRŒªqÙn]㌀ûâ–ÕêI†’ðt6‹ÊÀmÞ,�0çõZ7¶[θûì ûÂÝaåM�épɱ � � � � � � � � � � MÞ­Šq8.•Ç4CyGïD¤ççHf™Tm«Q #&ÇÈ@÷�æM½dæbÝ0É‘Â�hxr˜üõ ŸË�ý»Q>áñÿÉè1Ê K`�íî§#ÝNé%Cþã[“Fn»l[·U>¾ÿ=ÝÓÚ¾/šyxwÛeÛzwá›íý””|�Ózkçn�ÌûqŽò�0‰k*ãƒó›¤MÆèp\'é}f_izT e›hnQxéß?ôŽˆ‡Ýµe§äç函n»1@´×ßε…ñƸ©.&Á¨&1ÇÀ- � � � � � � � � � � �€•À–¬òÝ‚mR.-INéTݺӇœ/kLû GD]¨ Óô˜ŽaD•a˜ÖÅi—ŽŠç(¼Qk5©íÐqâÓãen ÊW­ Ç�w‚\ùêõ’–‘féë K­a�-‚dà�ÝîØríÛ·È�Ü!Õê×pÔ„qÆëXJ¹*åÃÑcÚ�D.|úr~ûȰB¹cÍðÙßΔ§Ï}TÞ¾e¬dïÙïì0Ê%{wìöe„2 e"6JcàòU�ו/ êD5Ñ(5%1â!TÏŠˆ;a          (–lÜ+}›#·Ž_*W¾¿XÎ|e¡ïGí‹aZ³É9DaÃtÔq[ 26Ð0í–fáíÒó Ö ·‡FK¿BWªYYÚõ>B†ß6RúùIݸ›^Þ†«Á¼Z ? g x»¼ìDÇŽµ‹W9Ê")hÜ¡©Üú¿»¥u�¶Žf±6LW©SUÊVȰè±á¿u²;Óæ¥ã"’Ù¿g_ÄšÀ³¶ÏYýå�É�Ë%Ï]¥yÁ÷’J5*ÚÂ}ôàû…Öó»BÕzþÌRJMG$„È“;¢)y9¹ŽîSS#ðA�iF� � � � � � � � � � �€’À»36Èö=6AT˜º$S&ÿ½]Y×maäoúÝŽäc;•±Aåaçã�¥º« )ð˜FÈ_»¤–ugØ2÷sÙ‹×èÞÆæ2¤ûŸ‚|ùÜKñ—Ï~&íû)ªkÀR1ÌŒ*Ô÷†eêµ¢ÃìR¯£ú°† ¼¤Í²qÅFs6êixr7l×È¢—O~ã9å¦Qß<€}]oó>?Ó;µðÔnëIѯ£þA[VmÒCÈ#Œü³þ¬Sn—Yš—}¯Ó�•¦�šÛw…Ìçå:�¯!˜vÖl\Ë”+Höq¬TªYÅQªÀí÷7Õi ÔnöåÚÖ€Gi4L»AÉ6$@$@$@$@$@$@$@$@$@$@$�{EP6ÕÎÚç,3íŽ8Y, Óô˜Žø<{j�Âc:µ¬5¶›�’SS”Íúœ}¼üüþ²{{V`ÿÆåä�‰¿IwmMg?DeàÎÚæ4>º«^«R®r9‹wòvm­éXKóÎ--†iŒ?uÜÏÒoô€°BW»ÑWµöøÎ-;¤¾4tÓ�¥M®Â›Ö\açæ欧tõ†5ŸžšáN™/Ÿ?ñ?Ù´Ò:Á`õ¢•…¦íF`/!Õk6v†º‡Qzà¥C=oQi¬ö˜NŽX=Õ÷;âNØ€H€H€H€H€H€H€H€H€H€H€JUäd8@ú)þ„òVh¤Ùp¢&4LG ­²ã²ÚÓ ’/ùyyŽýiåÒe~À�7à¢ÁŽî¾yq¢`�_?déì%ŽnÊUògýÜ<�×�},ýWªUxxhK2Ý4#~b’u}ÞƒÙäsmÍnè¼(Õ©@IDAT ©T£’£Ûí"÷�Q©œ£Ÿ-«7;ÊŒ‚uÿ®‘­k‚ï7ê¹ÙâæÛ¾Op±óšÜ´¢p/ûê jZ†Íܸ]öïŽ<ì8:©\»ŠØ'tLûè'Ù±)Ó2Fqͨ¼ëé1]\Ï&õ&         (T¶]ÍÒ¢Ù‹¡¨ Ó å½YF3JÃ8�§å�^>z†iÑ1gôu¬õ ƒÞÔq?y>àµÿ¬–/´Ðàv©Ý¼®½H¶ižÎ?¾=Iß:v)˜ñé4AØl³ÔkÙÀœ�IºBµŠrdÿ£c!õÓ猑P†^G£0 T!¥çý0[›ÜÙŒ•Šš�Ûn€�ÿãÜ Ýï_ÿ&L ÝWÛ¿g¿£q8‘ª7¨ai_=ÿ¹¥,ÜŒn$ïÛÁR!ÌŸ»ðqÉÚ¶ËR^3JÃtZäÓ~Ïä*Ž,©3 � � � � � � � � � � „C@eÁñÙaºä¦srüñ  çÄ”Æ:åó(=¦±–r4%95Y^v¢cˆï_ÿZiœ|ÿ®7õu©±&ðÁìà ´›;@hð¯5¯ëç.|BóhÎ6ïx€y¼Óˆ»já ™ …p¾ç„[åñ‘êFj„sV…•Þ³c·|ÿÚ×ò¿‡>°ô�L½Võe±(8ù¦ÓEu®VÌ[.cN»W?ž5¯rÒ¡ÛŽÍ™úD€ç/~RæNú3,uUžáÿþþ�Œtœdï=Ì<÷`Ž ö¯ÿûEü2ÏÑ7 ‹ÕT·”CŸ±×¼ ¯ÿlì€QöÍ›^‘ÙßÎ4Š,[xQó_8ež,øù/Ù°t½òø�ŽFþÛW¾”ÏûØ( lUk>vJToh5L£xʸeܽïˆ*¬7ôÅ$ s({sŸ§Ý:RÒm 6¯Ü$÷½S&jÞðÁ¼§ÁÇŠÉEUTç*=­X®û³öjF麮±®@EÍûºã G;ú°À`Ú²[k�1Ú,S>øQ¦2E ORJ’l^µY`(…À«¡²íÒá¸N²þ¿u–bL<¸oÈopxgm?ì% ýíF×{Ý.‰ÉIÒ¢K+¹òÕë-}Áˆ C­!Ð=M‹�–‘¦ñH�ý{öÉÞ�{´­ÓSmp?jzT £yÐmÇ:ëÒ»¶^úO?Uÿ`¼J5 B½ïÓB|gmÝ¥Óû�;@N½ù G¿¸®O¾ñtwß»–}û´ó6ù�oõOÙ zØo|O1!Ç#8 ¿½F+gþßhK[d>{üc™ÿÓ_�rãü ´ÄÔ–y?Ì5Iç!]Åü}´ìŒ0£Z6 �ÓRdu          Ÿ€Òc:üæaÕôÍ0Öh>UR…ò¦Ç´OpƒtSA3LçæZ×#NHLPzáéÂu1ÖG|å0y÷ö×-}üôî÷Ò{T?)_¥‚^¾S3 Û Âð^ÆGl†MKG‡20€žtýiª]š¡ó°QÜ\†Eºƒy§ua½èé+¤\Ö¯6ú�dÛi`ÍÃ;OÞ¿û­€1ØÞhÖÍ“ÌuþýÃjh6ï³§\4Äa˜Fœ#Uøð­Aá ùí³i²S3’ÛÅläÅ>b{ ï-_*B´ÃÀª:�ÛÖm³t £0>áJOm2CÍFµ ­ƒ÷i·ž)oÝ윀Æ0|Ã+Û.æIö}8Ö¦/’¿´0é*Ù»k�à£Õ9@½Lm-ðÂÖéÆåö:˜ôá—à{e7†i{Ì“ � � � � � � � � � � !`÷@F5Ÿcy—˜5¦i˜rùT\Q÷˜ÎµôoäXIç!ݤV“Ú–áàúÝ«žÑرm�Úxli¤È |7ŒŸç?~©îýª¨"ðÐv+X'yôÃJ£MÜvá[»ÎC»Éuoß"�Žp§ ŒÃvƒd0åà1ÝùÄîÁv;ʃ`SÒSeÔ}ç:ÂVÛ;€çòè1jk’W²ï šÇyµ‡sZÙ¶Þå½Fô‘ÓïeÛ<{Ô .rìYÇéžéÁkY÷ؽ¿Í{¡ÃEÏ\¡}®Ô=£Íû Kouù})¬_?öç)~eӹƴlÙ � � � � � � � � � � ¨8]Æü�Œí›Ç4BvÛ†mÁgCz€SRR’˜�Ñ å@•DÙüƒ’—c5L—wáý ïg„¿6{D"¤sa‚6üÆ /[ªþ6~šœrã=,4Â:wÖSVÎ_.›µõŸ�ðÓ–‡2è¯Jíªºwmï3ûêù}á“—ËŠùËd¡¶ò‚_ Ö#Võk”¡ÿ:ÍêJßÑôÇð˜.*Ò¸CS¹éÃ;åïi dæW3ä?-,6Öh%µ›Õ‘6Ç´—¶½ÚkaË«…ªjÙw¤U÷6òùŸXÂm[*i™Êµ«Hö�õsc«]Ús„ÜñùýúzÌÿü¶Èrý .<–O¹y„*]µVµ½?#¿Oó&Æuc÷¼6ö«¶µšÖø�ÜU{®ªªìôÛGIçÁÝä;-Üü²9ÿ Bo‡’¬m¡Ï ÚÙ¿“´éÕN_ÛçuóªMAC�cå ¡nä�m8ßG£®yë¶�¹�@Zi˜v†ØÔ’P]KAª²˜H€H€H€H€H€H€H€H€H€H€J5Å«yßí¼EÇZá©F8o¦#„æ±z¾Ý0­­Û© ìöóó­!¹Ãí£ã€£å……o­^½aM9ç¡ ôýżM I�µ=Kvk©±vtFårºAkùÂH®ÀÐÜäÈfúç¤ëNÓ½lÑwÖ¶]ú¹›|-ÔyÅš•¤rÍ*/i„:/ÊC3>xAÃ8�°çXFË Úù…Ѷ‚¶†¶—µÄ»žÔCð�ñk9#´4x•-_VÒµµ�«Õ¯ÇŠÖ¥¾â•ëå`öAÙ¸l½>ýToPCj7¯ðvoѹ¥Üþé½úd¬û�Or*¶I’¬¥Í‚kæá_ž’l-Œ6Œ¹Ðý#ÜxnNޤ–MÓÛ•«\^Ã^^Û¦jkA{xÏ_öâ5º�}ýÒuúºÏûwïÕuÅ1¥j^â`^µ^u} ópÆÃõ=ôêSôêãXpL8§ž’š")eSôõ½«iýÂ]%ç>r±àOQ=üÜxLÇó86 � � � � � � � � � � 'vdèît%ôvDÅÚ0m>t³‘Ú\δòl–ªVô¯sŸ{Âz¾õZ5ð¹×ÃÝÁ¨Wµ.>á{n]ôRÕê×Ð Ä5¢ª˜näÖ Ý^FæúmêU_0×mY_µ+hÚ ÏX &<ÔmQOÿø=¶_¼ýÖËmšQ=R¡Çt¤ÄXŸH€H€H€H€H€H€H€H€H€H ´PE"öû=»o.�åÓ¬6îr©ZÈf¿Íè¦+Ófa(o3�è¤í¡+T÷ndŒŽ¦ì•H 8P=èÊÒ0]œO)u'         (â”Ó>Ûz}3L_Ô»ŽçE½ëúîÞmÀn˜¦Ç´™NtÒvÃ4ÂaSH€HÀoð$·K9¦íH˜'          ߨ–Ùt¾­÷6œÕÍÙC_7žÐPÚÔ)' Öî–vu3dðÑ ql7LÓcÚÃÉ ³éÁ¹–š4L[p0C$à�…a:#C½&v¨!Už×¡ês � � � � � � � � � � ”VJôϖiß Ó8IƒÚWÕ?±8a4LÇ‚òá1rsó$Gû˜¥ríªæ,Ó$@$à �„Dg0zLû‚–�� � � � � � � � � � �@Ü8ßþÇM•ÈNJ²ÚÔÊ;2~‘ÖζyK£}Õ:4LGÊ‘õI€ '��”h©”¨ªÓÓ“-eádè1%Ö!          ‘|í?»”ñyáæbk˜¦Ç´ýÒˆn~ùªmŽÊW­à(c � x%°{{–¥ Dlp#4L»¡Æ6$@$@$@$@$@$@$@$@$@$@¥‘@Z’Ólœ–ì,óÂÆßÞ¼ha[¦#æ±zåŠé–h”¶à`†HÀG‰6�éÊ•¬÷‡bW$@$@$@$@$@$@$@$@$@$@$@�ÓŽ®iáбAy9¦E%K™×Œ5¶×ÞbØž†éÂÖ†ÚŸ�c0!¡ØÎi°3$@E�ÀÎ-;-J¹õ|vÛÎ283$@$@$@$@$@$@$@$@$@$@$P  h[Eþwy{™´p›T-—,—[×÷£.1†éüü|ÉËËL}¿FôØÖ˜Î×XSH€H vnÊ´t›¤­1íFh˜vC�mH€H€H€H€H€H€H€H€H€H€J+�^Í+ >Ñwoû£¥Mý&%9mê¹¹¹ôÀª‘ÈÓ ÿfÉÉ!k3¦I€ü#°}ãvKgééÉ–|¸NT —ë‘ � � � � � � � � � �@ô [ô=”7PÑ0½ ¦Lkß9Ù­Ì‘ �€O27l³ôT.#Õ’7C�épI± � � � � � � � � � � DŸ@‰2L#”7%:’“-Ø@ÎÛ‚„ Ÿl[»ÕÒSÅòi–|¸¦Ã%Åz$@$@$@$@$@$@$@$@$@$@$}ÎxØÑÓ—ÊÛŒaw’žæ ¥»w×^ɨT.ì>X‘H€Â!°eÍfKµªUÊZòáfŠ‚azõêÕòË/¿è= ‹cìc„7ïGYõêÕ¥gÏžázÈz¿ÿþ{@ó8hJ?ì7êW­ZUêÔ©ƒ"ϲaÃÉ?´\„Ѱ­1˜}ùòåuÝŒýÜ’ � � � �@I%§ ü~6~Cã8�´±5—™ÓÆþ´´4ß~?gee"ý›ÇDÚ�`û«T©¢ÿ-bÔó²Ý¸q£®O°±Â)‡>*Tð¢F íÿý'†#�yì@-a.W¥kÖ¬)ÕªU37q�^°`�äää:&0ëbÎC—&Mš¸ÖÁÜpÆŒ�ëÇ<†=mncßW£F iݺµ½JÄùƒʯ¿þªó17ÆÁ®‡‘‡>Gu”¹ Wé�;wêú»~Béeè‚m­Zµ¤{÷î®t07Ú¶m›L™2%èù2ê¦øôë×Ϩîz‹w †>ö1Ñ©ªLUŽï× 'œàZ£áúõëåÇ\G‘ˆ]O|¿† Iʺ«V­ÒõÁ÷=”ØÇ·×Åû¨SO=Õ^q~ñâÅ2uêTËõq'Z\ϧœrŠ›¦–6Ë—/—~øÁñ}7W*Œ êâ|�qÆæf®ÒÐgòäÉ!õ §c¿ôÁù2¾_ÁÆÅ»¹ÂįóµråÊB¿_áè>§�vZajºúüüóÏ–ë9ÔøÁöá~8tèP1Þã}ë�!C ¿$T(¶†i†òŽíåW.#Å1àÞÌ,¦TX@$à…ÀA-ÃöõÖPÞ5«—wÕ¥aäuÕØ‡F³fÍ’‘#GFüGI°¡ñÃé©§ž ¶»ÐrüÑvî¹çÊ¿ÿþ[hÝp*\~ùårÛm·…SUYgÇŽrÙe— ^Dxü°¼ýöÛeøðឺÂñ|öÙgú9Ã�FóG#ol1�‘¶×ƒÑþÚk¯Õ\zQèÑG•¯¾ú*ð‡‰1Ž1®Y#mÞéÚµkËÕW_íùeÄØ±cå›o¾ üð6ôÁ8#¯ÚšË ®Ÿ6mÚ4tùïû￯ÿᆥ\Œþ�®Ìy#mlQÇHc[¯^=¹òÊ+/$¼ÎÕwß}§Ÿ/£óXFßæ}F™¹þp»ä’K x‘I“&ø˜ûÕ§J7\Ï—^z©”+çm2"&é„ó‡¶J³ÎàrÑEy~YŒ—"ÐÇëR@Ðç /L�ñ"~éƒëçâ‹/–ôôt/êè/�¾ÿþûÀýÇÜY8/hŒ:ÐÇ�ó…—Fø{ÑgŒgÖS•6¾_•*URí» ß/|Œ}ªñíeö<ƒ>ø~yý¾ãþóí·ß*ù˜Ç5§U‹ç)®Ÿ¦M›ªv‡]öé§ŸêÏ œ¯Pcš÷™ÓypÁýÐëóâ�7Þ|ßÍãDšÆó⪫®’V­Z…ÍBUñ•W^Ñù„k2÷aèŒmݺuõç»WãÇOvíÚå‡:ròÉ'˳Ï>뺯ٳg˨Q£dÿþý®û07Ľù…^0E”Æù:óÌ3eïÞ½µ Vyâĉòæ›oÛ]hùœ9sôû;îÓ~È_|!o¿ý¶ë®ð· ÞGù%ø;åå—_vÝ®Ÿ#F~Ë»îèPCüNÀû ·‚óuúé§û¦®Ÿ?üЭ:úß‚^žÇö�ñw~ÿºðÁù ö·`¤ýâz~ç�w"m¨?sæLýûîõ]‚Ñ!þ–3ÿ­;xð`O×·ÑoQßÒ0]ÔÏPѯbE(ÝÍšWcãÚEDCªA$Pl\±ÁqukUt”…SP˜q#œ>¼Ôy÷Ýw}ûÑ=ð’? ;vìèJ-üh÷Ë( Ðfõµk×Ε>x ê‡Qƒã% ^¬üñR±¢»ëåù矗qãÆ¹:{#¼Ô€¡/Ša„u#¯¾úª¼ôÒKnš:Úà¥^$üöÛo®�y¸žzè!Gßn 0qcÚ´i’œìŒÈNŸï½÷žÜu×]áT «þ0ýúë¯Ãª«ª„?l`ü÷KÀ¼¹ü!zÍ5׸mîh}ðÜ­À`�³~ÉO?ý¤OÚpÛŸßúÀ3/#ÜŠßúàÅ&Ù¸¼èÁ‹}¿/|üþ~aþøw+h‹{¼_2}út}R‹Ûþ¢Á×PJJŠ+•&L˜ ]5V4‚çÎYÙ²î"èÀ(}ÿý÷+z޼hîܹò×_éÞk‘·.hñÖ[oɘ1cÜ6·´Ãï ãùïW7‚ßsø ä‡À˜ƒ‰gˆÎãö|½øâ‹òÚk¯ù¡ŽÀÓßU¼î¯0.qÄnÔèã—QÚІ ·“‡ð÷—_FièóÁè†S·/cR°_FiCÞ[´h�lÄ#©_Fi Ž¿-0Q¢C‡ë‚0’úe”Fø­ŠÉ”Gy$² Œ’~¥18þ¾Àó´[·n낸þü2J£?Ü[ñþÆ­ç½£-Æ· &Ì/\¸Ðõû(m� ¦ö¾Ýäñ÷2~³öêÕËMsý·†Ÿúàï ü~îÝ»·+}pýø)˜@‹ç¡ÛçôñË(�ãÂß:`ä62%ÞGùe”†>àc\ßø»Ãíû_s_E9]lטf(ïØ^V0ðT«’a4oã&Kž ðJ`ÃÒõŽ.ÔsçÕo�i?ÿh3 ìÞ½ÛHF¼ÝŒÉD> ¼°Ý BÚù)o¶lÙ2×]úù’ÆP(¹¿ŒöÆø±øçŸÙˆ·~¾äÃà¸v`(w+^Œ¶ª1q®`lp+^ŒÚª16 (¹ªüüQäåúñbÔVBPâ�·â×K}c|ü‘]”ôÁD bÜŠßçË0œ¹Õ-ü\?xÑçV¼ÙUcþóÏ?žî‡xqá§à¥<^Ö¸¿ïÏ0žyù~ùe43x¬Y³Fþøã#ñÖ [Õ`«êåþŒ—¨~ ŒÁ^ž§^تŽF |çÝ î§~ ~¯â;ïV¼K°1½ü&÷r,Áôñb¨\ºti°n]—ã;ïV¼K°1¦Ü­øit5tÀ=Ú­DãïÓÌÌL·êî§~‹—w¸Ÿú-˜âV¼¼‹6æ¾}û‚í*´ÜKÛ`�{1äfggëÖu¹Cœ—¶Áöb8õÒ6˜>Fˆû`ûC•{i¬_/}zi }ü4ºúy9Æhèã嚌Æ÷Ëàdlñ¯¤K±5L3”wì/ÍZ5Ê[ÍZ»I$ßRÆ � x!°~ÉZGó& ª8ÊÂ)ˆ·aZõœ Gï`u0óû˜cŽ ¶»Ðrxø)ƒêvö.ôp;31Ø1@·³Ñѧۙ›ÁôAy³fÍBí¹ÏKÛ`#„¤[©_¿¾Û¦AÛy ]ë¥m0…¼„ö­\¹r°n]—{ �í5¬´Ji·ÞoèËK[•.(s눶^Ã\£»”$}¢q¾¼0GxW¿ÅKŸ~­9j>&/}Fãþãå~ˆðÀ~ BŒ»,Gà·xy5hÐÀouÄË1úµF¬ù ¼cË–-Í]ù’öšÞËoË`Ê{9F·ž|Át�ÃGÛ¶mƒí.´¼K—.…Ö‰¤î…n½'1Ž[O¾`:âÙî%T~Ÿ>}‚uíª¿Ç½èãÇš¾fÅq/ôò7¼ÑýÜϼ\�ê§à»Ž n夓NrÛTÙ®S§Nž¼ïàï§à~æå}‚k›�§sçÎâåžæÇšµf}p¾¼¼ÿAXh?åè£�ö¤œû)}ûöõôÑü\;n½“¡G4Η—g�kf›ù":‹—ß0^—í3ë‚4ž¥ÐÉ­ø}¾T÷¿ß)»=Öh¶ó5”÷oKwÊüµYÒºv†ÛÒÿtf*�i/3/Ì}3­&P¯NEY¸øðìÌÕ«·Iϼ}²)Á]X5õ(,%(ÍÖ.^m9üÔÔ$iÒÈ]¨»x‡òVýˆÀQXw.ØÏ1c@ �2¬}‹5½þPÂ,CxVa&°1N¸[CÔ‡>XO΋ñ ¡ 1ë!kŒ™Š…ébè`l�ú�5ÒCy» ;Šþ <"2ÝëlL„‹¼ñÆ¥qãÆèÚ•` Bx°øåY}܆mÀ¼Ê½xA™A ¬sëÖ­ÍE¥qýÁClÕªUµ Vúà:r+{ŒPY~y¢àe//f±Æ®/^#fÐÇ˯X?^‹^¼FÌú`¼…¥¡¢Hxñ38räHO«ŒûÏÚµÎÉ}æqÂMãû®z9n{ÜOÃ/ÏE,câŃeðìòUÅ|ìXoÖËóÏSDðëyŠß ^ŒËø}€�à^¢|°<ÇwÜ¡¯íl”EºÅÒ,8WaêUð{ kòº]Vãß~ûíú³Ý�È¿Œãó2yç ³ˆŒbü~68û[”«ÒF&,¢¿*UÜMFÿXï¿5°Ä�¡�Ñ¿}|ä!ÁöcBÖtö2‘ a�ú‘6àÍl¬pÊN¡à݆]DZbMzè�HÆûJóبcˆ¹\•†QÚËz׆!D@¤7|Ї¡þ¼ì²ËÜw¼.c}09÷Cƒ�j,ó1Û�ûΗÁïo|?ñ›Þø¾ÛYýÓÃØ�ï—×ec0‘ËM�ô±�L7U¹úÀÈ5~üxýú1ó1Ž9ÔÖ®;&}àï'/£6¢ áz.LûøöqñÜòªÏÀ�å“O>ÑÿF5î‡öqÂÍãûŽwk^“XðìÂõÊûµ06Ð|ð÷“Á$ üÖÀù ¥O8c@¯|àØ‚¥¡ð7�qÿQ��wr… î‡^×Gˆtüö)ìzG?Î×qǧ_ÏÆýÇ`jü`ûpÿÁߺvQ½S¶×)îù2”¯ 0ŽòÕ_ÖÉ}_,Ô¼i`C¹a€ÿ³��ð@µÇ_GªhxôcF{‹õî¼óÎÀ0gï$Ý1(��wâ‘ç~’—ßžP£}ëÚòê¸kdNR�@$@$à…À-=®‘½»‡—êtD]™ðöy…vÙçä—eÅêí–zXÌÅKTÏ)¬ãç÷Lìx_I?‹ŒŽÑø™d”[cŸ9o”ÁÛÐ/�}„¸3þ�´ëbŒg.GÄ\†jn×V,èíð¿ÿý·Cc¯yL”™óæ4þðb6ÆÃÆü¡dôoì3ç�´±E#�-þðâ-dŒ‰ S¦LÑùý›Ç2ê™÷eæzàãÅ{Àè/f¡�ñ‡¤j\U™ÑÞØâÅ£—ÙúF?X Ærãz6ÊíÛÂtŸþýûÛ›Eœß´iSXúÖ1ôñÃãÇ/}p¾üàƒó…ªóÎ £^¬Á£Á«@LþˆôE–¡‡1>ø¸]ûÍè[L¬2¾ïÈÛÇQ•©ê€�#0Æ�à–ùû^PêÔK¥ƒQ[x&{™D`î Ëàú 5¦yŸ9�~�‡ÁÌË$&³>ÉŒû¡yœHÓ8_n×v5ë‚4–lWs[CglqÿñËc“óŒç…1Æ5ÒÆÖ\fOƒŒÓ~Œ�¡øc„Ò (ýú=†g¼ùþcŒklÍ,TeØ_^0ç…[    �ð`â´}).LäðãïÄð4ˆO-ß<¦_�b�¡ýÒOkåºãHB™è˜êG½ù…èŒZº{mÞÄ–mñÒÍR+o�$KžÔ‚zSH€HÀ � ËÖ[ŒÒèëmŒ[ñëE”ÛñÙÃ-¹ø¶ÃËDó Åøj#‚—¼EI¼xÍFã8¼„Çó[xìû2ß‹ŽÅ;lØ0/]øÚ/¿CpyQJ¿C”•$}p¾üQæ…ôñ;d£}jžòEE` ôcB‚ŸÇã%¼«Ÿz}yñº6úðs‹%ZŠ’µ þ~Üýbì%*�_:°    �’GÀ˜j>2•íÓ¼¿$¤}³&fîɱðØ{ WææYÊüÌð…¿Ÿ4Ãë«M ëËñƒsåŸ6H�¼ÝáuÀZ$@$‚ÀŠ¿–9övl_×QnA¼ Óª*ϳp�‡õH€H€H€H€H€H€H€H€H€H€J•a:Þï´cAÖ7ôæ`ä‚„;ºÕ T/üU'QÝš¥n´nQS2ʦXšÎY°NäfYʘ! 7–Í^âhÖ¥“û%!âýçsÊq:Y@$@$@$@$@$@$@$@$@$@$ PE�V½S.i°|3LÇ =¦cM¼`¼£;Ô³ üèòܨ–�¼‰å1­š]à$öq˜@o›¡h[æ^™9w�ÔÕÖ™N‘è­)~X¦H€J"�¿§-pV¿cš9Ê")ˆ÷C\e§Çt$g�uI€H€H€H€H€H€H€H€H€H d(Іé75Oi³Œÿs³,Þ°×\ä9í›a:ÖkL«<Ñò£¹¨µgÔ%£ƒ6Ú:ÓÍ›T³ÌS–H¢äkkMï²”3C$@áXøËƒ(¶†i•Ç´ê$úÌ‹ÝiNÜÎÂaͺòËoË$-?GêçeYö1C$@…˜óÝ,G•aÛ:Ê"-PM`Š´/õUã«fÁyƒmI€H€H€H€H€H€H€H€H€H€ŠÕ»bÕ;åâwd¡5.Q†i†ò}²ýÚÛ¨~eØÏºØùûŸÌÑ»ož»Cëí×Xì‡H ä˜óõ ËAvïÜPÚ¶ªe)s“‰÷첎;:ÔîСƒ£Œ$@$@$@$@$@$@$@$@$@$@¥‹€ÊÙ6Þï´UvÖ2ªµœ=œ*ß ÓeÄéÌ�ŰΪYª“è� ›† pÎéGYöNÖÖ™ž:c¹¤k^Ó�swZö1C$@Á,š:_Ö/ßhÙ=êT§A×R!ÌL¼âƒ–›nºIjÖ¬)õêÕ“ûî»Oºví¦ö¬F$@$@$@$@$@$@$@$@$@$PR ¨lš*Ûg,�?_ËÛiýõ¦‘o†iojDÞZÊ[eÉ�¼g¶‡@¯®�¥]k«Gã3c§éMá5 5…H€ #0ë?Yª4k\MN:Á{otï‡8t¸úê«eæÌ™2}út9ï¼óPD!         (åT¡¼ãíl¥<%>[¦K”aZ5»@ ‘…¾móšž=o­¼òÎ I’}z ,ñÉ'ŸÈ}÷ÝØ]¡B�Q9Y¼x±Å(m´ýý÷ß�$·$ xñÅuOz;„Ÿ7ož¤¤¤Øw•ª<¿K¥êt‰ƒÝ¸q£ 4Ȣˇ~(={¾LÆu×]'sæÌ ´íÖ­›|üñÇ�<$à•À×_-Û¶m tÓ AéÓ§O ÏDôìß¿_Þ|óMÁïD˜C &—!Í­·ÞÖ¤ÈP}•Ö}¼ÖKë™çq“ � � � DBÀ7Ãôy=kËcßöԺ𘺒”¨Y§£$*ƒ ÓQ‚F·ÿwãñòë+dͺÃ^vhöÞ'³%kw¶<ýÐɲWóØÈÔ Ô �ÒI`ãwSdìsßY>55In½ª¯¥Ì¯Œê9áWß%µ•— Ž5XyIáÀ5´#?“?ÿü³²ÑÞ½{z÷î­Ü_Z ƒ}g‚•›¹ðz4Ó8œ&—Ã,üN­_¿ÞÒ%&6QHÀO�<òˆ¬^½:Ðe�=h˜Ј^bòäÉr×]w &®„#0bûí·òÃ?È�wÞ)矾ތ÷ßpèÔáµ>+Ö$   (šTþ¿E6”÷uÇ7�f5ÊÊÜÕYÒºv† ?ºFT©Òc:ªx#î¼lz²î}îUÖ�Þèèóoʺ�;eÌ=Ãä@Óv²§Lá!#V€ H€Š4�½³æÊ#·¾ïÐñîúK½:å~Ð09ÅV­Zéá¿—-[fiïô¢š5w�œrökr÷íC¤ò°�’-‰Þck �bC oþBsý’“{xÝK(ò ¶rÎéGEí8Tω¨ VB:³·ÞzKn¿ývù믿ù“N:IÏ—�CT¼XñòÕ.Xg•â$Ì[Ú¨‰ýX—¼4‹—ï¯Gõ•C.j.^KáIi^—ýU«VÍk·lO$G'NÔ=žƒ©P»vmiß¾½T¯^]_~þ>=Ú!ÝŠ+ô.xÿ F’å$@$@$@$@$Pò+�éXã§Á!ÖÄÃïŠó{ÈÚõ;åƒO¯Sg´DHï[îüLN™¹BzßxŽ$W‰Ž—¤1·$@ñ%� Í®ÊŸ=Gî»þÉܱϢLËf5dÌ]C,e~gè1íŽhÆ ë’â…d¿ã´¸S‰­Š»aúä“O–Ï?ÿ< %^d¯\¹R5j(+� ~—JãY/~ǼnÝ:‡Ò0ZQH€Š'� 6èá»UÚ0@|ðAÇd¼¬¬,yüñÇå�wÞ 4»ì²ËtOé@$@$@$@$@$@$à�Ÿú‰y74LÇyØ>|ç Ü¿UÐú&Ε{†Þ!¿ð}Ð:ÜA$P¼ ”Ï? y?þ,7^úšlÛ¾Çr0å2Rå™O,MásÂ]¥½ñ+©­³³³eÆŒ–Ã;ýôÓ¥E‹–²_~ùÅ’/Í~—JóÙ/úÇ>kÖ,‡’Xÿ—B$P< Œ3FvíÚeQ“5 gìØ±£4*–/_^î¿ÿ~¹ñÆõvW\qE‰�–cÄ � � � � �@€@zŠ3ÚqZ²¿¦d_Cy4�A‚‡@ö0ÄË��&]ÿ‰Lž²DÙË®]ûåý1˯ŸN“c/"�‡vSÖc! �@ñ"�¨yI7ËÝ!ÓÞû^îB=ùä•ÇO•6-œa“ý>R>'BÅ:Áû÷ï]éÐÞzõêIzzzXuí•vîÜ) ,�ùóçËöíÛõ¢X·{÷îz¨pÔ_µj•8p ÐÔíxX÷xΜ9o¡½{÷J“&MtƒiË–-¥lÙ²�þc™€Nð>7/kÕªed ÝÚÏSJJŠÀ7^òûï¿Ë¾}‡# ¼gçÎ�å˜cŽ‘%K?óáU}Þyç…T×ömÛ,u"9÷«W¯Ê Á‹wœs»à,\¸P°ÜÀ±\¹r‚ñ"9ö¾‘·Ÿ#U£,’ã3ÚDsm6*ÝaýçŸôï)¾—mÚ´Ñ?EÙK777W¿OAïÿýW?¬æÍ› î-¸æŠëó!¼!Ã,uêÔ‘Æ�›‹ô{®³Ô­[7ä}533S¶nÝh’œœì:ŠB4®?Î)¼Íñ¬QIZZšÔ¯_?° <þüóO=4ò–-[ô}˜ÐÓ¶m[Ý(¨hKØïsfî8'~?óüà²|ùrA?fÙ³Ç:I¿ þûï?seºY³faGoñCw¥‡ ±îòâÅ‹õß28Ÿøà9†g"ží•*U²|Úµk§?sBõé÷>\[_ýµ£Ûûî»OÎ9çG¹½àšk®‘N8A¿·Ù÷E3�ßIñ¸Çx=§ñºÖq.ü¸Þcq‹æuþI€H€H€H€¢C`Tך2{åáÉ®ƒÚW•&Õݽ¦! ÓÁȰÜ3�ן>]®¾ýsùbÒ¢ }­\²^VÞöšüôÖwÒkd?é9¼wкÜA$Pt ”ÑT«——%õ³6ÉÃ�|-㿜ïPQ¡_z„ÓÍi8rTö¡ ¸ |8ô°º2dˆàÅp8‚ðŽ#FŒ§j Â9_uÕUºA0PhJ Ìó#�<¢¨Ï<óLY¿~}`o¤ãíÞ½[®»î:ùá‡,F`£ÃªU« <ˆð²5–½0fNNN`X÷”)SùP ¼t´Ÿ'¬ý:{öìPÍ¢ºÏÆ»K—.ã4 Óo¼ñF`lxUãe}¨ /¾ø¢¼þúë�6HÜ{ï½rþùç[ÊT¤(fcŒÍX'0³ ÌøóÏ?o.²¤a˜îر£ôéÓGàý ãu$b?G¡ÚFzm‡êË�}ÑfcÖk_~ùåº!Í\n¤ñ=…ÇÞСC�¢¸oq}a½ô/¾øÂ2 ¬ž5\p�Ü|óÍú„󾢞Fd{(o|—íc¾ofyà�dôèÑæ"KúÑG•qãÆÊ9!þ#‘h\3~žÓáÇ[ž]æcƒq|!}ô‘þ Ú±c‡ž7ÿS¹reyòÉ'å¸ãŽ3ÒöûÜ«¯¾*½zõòý™ç<÷úöíÐ?XbÑ¢EÒ¿ÿ`»å`hŸ(Øy(á—îö~‘Çó{Ú´iòÕW_É÷ßïðDVµ1Ê0‘%Ö“â>þøcËoè‚Él£F�2Ô*t‹ 7±–XÞcü:§ñ¸Öq^ü¼Þcq‹õµÄñH€H€H€H€¼Ùµ–Ô¨�"Ó–ì�UÒä‚cêxïÔÖƒ¿þ׶Σ™µ¿tŒæXìÛ=�çÇœ,�ÕµÐÖü»FÆÝûŽÜÞû:™øôxÙ°ô°�¢ÐƬ@$7e´‘ëåí–>×ʆIÓd؈—•FéÊÓ僗ϒþ½›ÇL×H�KÑTÌl˜Œæ8‘ômöâ-¬]$uÑ×Ô©S夓N j”F(.¾øbÝ ÑìU‡}‘ŽwõÕWËäÉ“ƒ¶ƒWî%—\"=ôº�™À#×þâÇýÇ„¥Ãܹs“`<�§Ø Ó†«k×®¯QUÈo»Þƒ ²É7ß|ã(SÀP€—³féׯŸÃ(�ýð %0~}ûí·rë­·ê Â�8`ôÉõI]£ÿhn£ÍÆÐÇ}å•W5J£¾§¨s÷Ýw["(}Äz‹ïŒ‚Ÿ|òIP£4tÂýáqaPÇĈâ$ï½÷žC]¬AkÕu«*3·³ï·çÍuUiÔ÷ûšñûœ†:¦µk× <Ò¿ûî;ýÞ¢2Jã¸áu‹‰ 'NTap”Á#ÖïgžŸ\B1qL…õç§îvu0i¿eÎÓ¢Œ?>"£tÅŠcn”†þö¥6Pvà 7D<á íb)ªó¬*3ëdßoÏ›ëi?Ïi8ãㆳ §?¿¯÷PcFë Ö!   ˆ?�ãZW‘{‡5‰ŠQGWl Óô„‹ÿÅ®wߨ_ºÃùòYÕ>k{–L~ã[yèä»å™s•_>øA27nWUe �@ $Iž4ÎÝ)ý®‘²‹É-·|$—Ýü©¬YçôêжŽ|òÆhéÙ¥QL5. Ï 4àQÚ´iSÝ{8¦â4Øo¿ý¦¿Äµ¯o¨R'++Kà5kã­ªª aÀúé§PUû^{íµ�Æò@E*/%x4…#v#0ÚÄÚëÛ¬'¼àaX7‹a˜†WØÑGmÞ%*ýÍŽ:ê(iР�¹Hu ÃKa2iÒ$G•aÆ9ÊPÃs¸‚вçž{®üúë¯á6)ÖõbÅ!ZÆ8y÷ÝwåÚk¯ §jÔêÀ¸O2„QWâÑ‚ Ãí'Võpl†G¯1&BKóÜ5êÄjë÷5ësŠÁ` �ûpQB‰Œ>ºÛÏg^¬¹ÇáÇ6šºãü!ô5<»ÝB®ÇZ0⯿þ² ‹Ðü0®SDŠã95Ÿ·h^ïæqŒt´îaFÿÜ’ � � � ”n å]ºÏÌŽþìá�äˆ6µåÞǾ—Ùó׆5îÒÙKŸñcÆI“ŽÍ¤í1í¥U�¶Ò°]ã°Ú³ �€ÿÊç�†Ù�›%Ë—m’ÆÍ’?›t ³Në¤OLAïXK¼#klÚ´Inºé¦Àaùå—úz¶·Ýv[ ,ž‰c�=V÷Ô²ë¯E„Ÿt+Ï>û¬cmÉ„„9òÈ#¥S§NºkÃk/áUk!F2ö¼yóÕ±Vm‡!�¡½Í‚ñ|ðA=¬ª¹<šin±®0æ~Ìió¤<°Ö9îS0䛟3hc7B‰«ÄØï絎¥i÷i³À˜n.ËÈÈ�=z˜«(Óðû:r#Ín-Ü÷Üɳõ�~§$Iƒ¶�¤~ë†R·e}©Û¢žÔnVGRÒÕ/¨‹.jFEƒ@‚äKÕ¼ýR#¯ìúo•¬üw­L]¸^fýµFþ:$.îÃW]ØC®»´·$%ÆÏC£(¬/­ÒÁl ÷lùgÏžáV�k½W^yÅ1þ]wÝå0J£<¥^zé%×Fpó@—�õªíO>±6¤!ð:ÁËq„¬�•Œ1B`4{¥Ás'”a!v¡«Yà…f—éÓ§ëáFíånóXÆ5»ìÝ»×1é†i³´oß^àÝf6ð˜Ê0ݸqc騱£`B„!0P⺇1P%XÚ.ö‰öýª< ðøÀ@2zôh9å”Sôq�º˜¨r°ÕÚæj/ÑGÎÁ\ÉÕ°åjÛ<Í-7'Oßæåæi¡çò$ÍHÃ’n,Ðð ŒùÚöðÈËx‘iv9êôOBý…_íÅQÒšÑ/‘ÊhÛD¤“µ}j�ýŒwx,ì0 ­·?ô�©’*©…i4EjÔ“Š*Ç�zæý� zyÁN£LßÚËõãG·àPÀ ½l•[ÊÌûõôáqP_ÿ?P^ÐWÁØHÊkŒõ>ÍýkeÆþ}¬}ôô] ¯¹\dŒ¡m ú9||–ãЋQµðÏ¡tAæP!Š ÊQÅ.»¬ûõ³n>ñæë@/7®™‚mæ†íR¡zEɨ˜!‰Úu†k‚ë\ÿ>ìË–2š§ONæÙ·M A¸1SÖjkE¯\½]r´ë>IKM’Ñ#Ž– Ïê"µj”§ITëÄ;Œ7Ne˜6&£ N�ÃcÚ,ðä‚WW0Á�­þýû;Bš«¯*çË/¿\µK/ƒñÔl˜FáJ-o, Ó5kÖ”~ýú ºC,B·" «JìÞÉ0©BU;ž ªÎ|(C¸eûZàvÏ>œ L80ŽqªIf•`6¦áÃÊYg�e®¦§q¼fŽ(D8êpBÂ::34lØPn¹å¹õÖ[¥ëÖ­ÓÃ}‰�¥,áüÞA„„`‚H8ßö—ïÉKÃ4&D˜½:¡/®ßP: ’¾ Ó¦M ž½�ÀŽ8&ð‡.øªÖÌ…hçÎ�ã¨�sh¿®™xžSܯN;í4=l½ýñ\À¤ D 0$Ã4¸\ýõFÇö„N°¦QAõÌ‹'‡ÒÄBw„å¶ ¢ eôùZ2t·Oˆ0ÊKã¶8žSœ§X\ïÁ®‡hÜÂ�År   (­ìÑ´0 9ԻԒ‰†é’r&‹éqѶ¶<ûà0¹Aó®üxâ_2áë…²~Óá4^kÛº­‚Ï?Ó½ô¶$@¡ Dÿ)CÚËÃ:HFÙ”PUcºOeŽ©Ú`*ÜxLÇZo·ãÁKpëÖ­–æ ‹50CI·nÝ<¦ñÒ5Ô‹b¬ckU¸K{¿óðê¶Tá5­ò …aÖlä‚.Ý»w×�~ënvCyÓ¦MEõÂ:³aa·z6”q^‘XsÛ›—áç^S«V-iܸq°.õ°áö�ªg^¼¹ØuŒ$ Ý1i “ÇÌr饗 ÿXî¤k×®!Ÿkæv±J«žƒ*/êXéSÔÆ)Žç cq½‡:W~ßÃB�Å}$@$@$@$PÚà}™=’–h¢aº_ ªu�ByáC¡j�†šõ-WõÕ?¿L_&ßÿ²D~ÖÖ±]¿Ñ#5!“ øG RÅtÒ¿µœ2¸�tîX4×®S=#ü#^O*̆·ðz)>µÖ¯_(ŠÄa�CyµT†cc_8Ûºu놬¦:!Di' W0 ƒ“!Ÿ}ö™¾6¶]G„bÅz¥fÁËp•ÀÓÔ¼^¯ªN$eÁ Èð¶4‹Ý[ÚØg/Ç„„0`€QűE¨Û>Zç~ø!°ë¢nß¾]ƒ(ÔæµFy¸a¼’á¹.ç[¬7ŽPÌ0ôT®\Ùè2°E�pŒE�Å4m6á|Ï1Ñkvš=ó×®]S¢*Œ=ð8 %öȪ~Bµ�Ö¾>ø Ð®U^–…6ŠA¿®Õ¹ˆÕ9Å=¿mÛ¶Ai=øàƒA÷ÛQûó$X?ñäL§pËc¡;žÙÆá†^x^Lœ8Qÿ€3ÖïÒ¥‹±“ìÒÓÓ�ªqÙÂß.ˆüA) PÏ)4�Åõì‰Æ=,ØX,'   �ÒHÀþî JËäÒbë1­ ÓJÃtÉøúöéÙTð�,úw“Ìœ³ZfÏ_+ þÞ +×d–ŒƒäQ�@1#Ъy éѹ¡Ûýð÷³(‚êk}nÓ.n<¦a¬ùñÇí]9òXøÑGu”ǪÀlp5ƬQ£†‘ º…WœAçâ ¸Î<óLyê©§ê"<#έÝèl÷NÆï{£xmÁ«7š¯1ûËm»Ú¿Aƒ‚�ùE*ŒÚ¡ Óh‹µÀ͆ix�ÃÃÌÌb7Là WåÍknƯ¾úªüñÇæbKzöìÙ–¼‘±�ò’²�•Ñ_Å÷³—´9­ªïw™ÊÓ“&"•Í›7ëK¿¨ž‘öíúS§N„êGT†¢$~]3ñ<§ª¨^‡ó\ gŒxr G¿Pub¡;ž=xÛŸ9†^˜hˆ =øÀÓj¨¯7cu<¤|yçR:±žÜ�ãwÌâxNql±¸Þƒ1ŒÆ=,ØX,'   �ÒH@õž¸8¼Gðã\ùj˜ž¼h»ÌYµKZÖÊ�“;E÷E±j68 Ó~\E«�¶-k >ç�,X÷.kw¶,[¹MViêuvÊÆ-Y²-s¯ìܵ_öî= ÙX+Z[’”” ©)IZÙd™=o­ Ö<<±¶s¢fHHÄk>¬û\PnÍ'h†€2Úþ¼„Dí“ ùÚ§ �¨¥%WÏkee�>”/£­�v‡Úi¬-Œ¾ òÐil Êô-m^Û:7ë\›>Xû ¶¶V–sh‹õ°õ´–/X'ûð6,hëgûå±®¶^WÛWÒ_¸ƒki”r©R±BšTÂGónž>s¥ôéÑT“¿eÛnýû’—g^¤º€BqW­’!µµ5¢ë×­$ÍW“Ö-jÂuW©:sQã¬zFÄZG•n<¦áÙÌ‹Õ|Lðœ�§˜Ã‘z„ãÉ‹û¢)N?ØFŒ!Ï>û¬ÀèjÈG}ä0:Û Ó0¼úeŒ0Æ�dk×mÒ4ØuiŸÝùË/¿:ÖÇKuólQ„!6¦a _¾|¹¥¯Â¼¥1Y㥗^²´a¦€@,Ù„{²O*2_±8oª�ÇnÆÅ}»(Þ›àщ(K—.µÖ#�<¢{�Z ãœñ뚉ç9­V­šïýº®âÉÅ+”XéŽgÇ»ï¾+/¿ü²¶þ7"=|ñÅúÏ¥1cÆ}Fz=þ`í«V­êØeŸTæ¨PÊ ŠÛ9Åé‰Õõ®º¢qS�Ã2   (­h˜öáÌ¿üóZyàË�žæ¬Î’ûOnÈû�P½ð÷úrÛoÙŸÿÊ—K•#ÛÕÑ?þ÷î½Ç|Íà|@ä f¬.Ø&hÛD-oÞjûµ²Z¶ØwPkÑ Ûš�ºÀX­±-Ækäµ}ÙîƒZ:Çœ6¸Ñî`ö¡öZØÖ‚~ÆQÏ0Ž›¶ºáyíƒ;¶ù ci<¸ÄrLL HKM–´´$}›®mÓÓ’µp€ÉRVÛ–ÕŒÅe‘NO‘r)Ú:ΩúZÎø.”×òå´m…rišA§`[¡|š¶Öjᆾ}ûjáRsõÉ ‰‰ ú˜Ð¥¤ˆêëcS½<6$c­O´Ç«X±¢cN~±"�çIß¾}-žÁS¦LÑ_xžã½xñbKÃAƒYò±ÎØÃxc|L²° ƒé…cÂú¶­[·VEG>xð`ÁºÛ† ìöŽ;ôPÛ(Sy®a­Ï`£(£4î0„"ܸ›I#ÁÆ-åE‘ îv#Ja¡úýf]¿¾?ËSÄ;¤¯™ î;øî!Ê�±^vÏž=ÅÂ^Ÿ“&MrL’1÷iz÷îÝ‘6‰¸~8×L<ÏiQ6êÄ“KÄ'ÚÖ Vºc‚ÉùçŸ/£G�Ö£›Lžyy’�2»µt휔Ñò ùHk­¬ �/ Z$5¿xýc¤±…�7©L¾ö)#I ÈžõfïûÍC_ËkÞ¤¤DIÖî{0ò&#­ñÅ'EK§¤ú$'iFí£åÑ&¢¿5ÃwIÕ3"ÖǪ2Ž«~pÄZ¯h�§Z÷²0£héÍ~½žÃQ£FY Ó˜¬ðé§ŸÊW\¡«­ò.Æ;šÇiô�¿³fÍ2²®·ðºe˜FÇçm6Lãþ�pÞð4‡Ø ÓXß³Iõ$H¬¾Û.0dà ºà¾1©×ê¼yóäâ‹/¶7)Òy7×cQeƒs���má]Ø5c©lÊ¸á‚æM›,%cêJÎ:ë,‰4œiÙ²E#ÊÈ›o¾)Çwœùpôô%—\"O<ñ„¥ü±ÇD.€áÆA8óhK8×L<Ïi°¨ÑæNÿñäý¼LÔ‹µîøN`9 cIŠ+VèáïUĘ &è÷ŽÎ� ¢~…sN¼ÖÁó¬C‡ºñÜèkß¾}ú­»îºË(ŠúÖíý7RżÜcbyN½\ë`ëëÝ|Šò=̬'Ó$@$@$@$P\ ¨~;—§o¡¼wgKi\{äEÍ0­záo¼\4Æç–Š ´S4ƒvJ„mŒÚ^®™Z ÒÚK ä ÊQV°¯`¿–Ö÷ªk©w¨m¿£O½os[[ŸŠ±µ®õR˜]Ëh}õèåÚV/Óó‡ÓšÍ· —d­L³—B‹‚6Zxôÿgï>२î6ޤ÷"E�¢‚] ŠKl`¯olhÄÄX¢Ñ×^¢±%Fc‰MˆkìA_MTŒ½+Dl¥Yè‚ í=Ï&g��=»w÷ÞÙÝÙ�ßñ³NŸ9ç;»{—ùŸbÿ³¡ÞœçI”íulÇëvOMíË ]�Yù!%G �i߉Zn•Ù£G�¬7ØÔ©S³ÖUÓ _«GB­o>|x*È¥VÄ.Ýwß}éÀt¸Ûì˜>}ú¸]Ë>}ñÅ#iM¬r¹à{®Bh¼l½�‚ã•«;o¦Õ’v„ ‡æk-=qâĬn¿�?þxsÖYgeœÃ-¨Åº¯r…Û‡iTïǸڄßû28p`�ôQ¹èBë®»nÖõ”>餓²ÖWÊ\ÝasÌ1æÖ[o5çÞ%uï­J2®"ˆ[ŸkZ×ß3}W–:òž©µ{•i¹]®†Œ}\ÍûöíkôRE3Uô7nœ¹ôÒK³þæ¼÷Þ{¦œ�iås›m¶ÉLkÝwÜ‘ªtåzfѺ¨R”ß¿á<•ó;&Ê{å{]&•~¿‡ï Ë € €Ñ ˜ŽÎ²lgR-W¢õ�A—|A·�)µ* ®Ú¶(›J?|$üE®k»ÿ(Ö"PU¾ÊKå.€¯Õ™ïG¹óUªëéádÇŽS]Fºk<òÈ#æì³ÏNu—ìÖ…§_|ñExUl–}qÃ] ›Y½/ÜXÓîXµ¾zýõ×Í[la¦J¶–V>Â�ŸÖ­[µÂ¬+)ÀlýüöÛo§ºnÛ¶mÎCõ»n¿ýö37ÜpCzŸ—^z)u\¸µ´~óí½÷ÞéýÂ3¾ûTWÀÍ×Z=|ÞJ.Gõ~Œ£ÍÒ¥K½ÝÞn¸á†u’Gå¢ ©b„‚¹Ê�KcV]`ÛjÚÇ©'ª(ò›ßü&#{øÃŒ*|¬¹æšë;tè�±¬…|�TùaÚ´iYÇD¹¢Ð÷LRîi±¶åvYg�uRC:¸|ªr–zäÐß”bS¹óž/ú»¥ž4ìÄi§�–±ëgŸ}–±\Žý�»úê«3*”é³¢1¯¯ºêªÈzDpe‰êû7Nß1 ½§Q¾×å§÷»»ïL@@¢ð='NJŒS «6…[Ä%å¦Uí #ã €@™ÂÊtÙŒËø‚ãuµþÈ8A.l²É&¹Vk<(ó¥pÀ1ß¾åÞæ D)¿¾ŽÅäí�CÉ~DA\u™½xñâŒSí¾ûîËå^¦·Ýv[£–Íu½`&½÷_xá…à*ï|ø8�ÿ¬q=5þm0éú¾ân_×™Á`£ÛÏM$¹çž{Üb,§Q½ãf£ü\vÙeYã¶êû¤�1¦£rÑMw‰à@ÝŪeä7ß|\]õóGq„éÖ­[F9Ô[Áí·ßž±N Ú/ÜúZÝìçãõ¦›nÊ:G”+ŠyÏ$éžc\n—pÏú;îN¾Ðü—;ï…ä+ü·[ÇT¢Kÿ.]º˜=öØ#+ËêZü€È[¡$ë VDõý·ï½¾÷4Ê÷ºòÇ÷»òEB@h¸€ïùL\bœÎZlny–yî£y /¨ç U˜?ô�ËMó8³ @ ŒqLûþ&54 YFÂz]J]%‡Ó¹çžkî½÷ÞðêÔ˜Œ\p�QWÍqM¾®jÁ{ÑEe<°TðT]vÞyç�ãGç*—‚m;ì°CÆæÇÜ<úè£ëÔµôFm”±®œ jõîŽwذaeaÈ�!&ÜÅg8Èí;Ñzë­—ÕUºZ¬†Ç¹°ÃçRèá¤cºWá¤ëz`ÿù矇7¥ºWK¸8¤¨Þ�q²‘¹Zø…ƒ¡ú�å•W¦ÇÏç•‹»Æ…^hÚ´iãSÓÉ“'›wÜÑ\qÅ&Ø p'½·>þøcÓ�.Šƒç+õ|³fÍÌÉ'Ÿœu™o¼1+à¬ÀˆZÓìٳͱÇkôùqIAüN8ÁŒ;֭ʘªµïÞ;Õ±PŸ÷LRîitY›Ë颮’ÃIŸ{ ¯àëÅAïU”v7<¾ÔyW÷ܪ¥Ï´ÆiΕÔþu×]g.¾øâ¬]|]0gíT‚çœsŽñõN2~üøTÐúÏþsjh _¹ô¹Ö}9ì°Ã ú}Õ÷o9¾cÊuO£~¯ë-Rê÷{ Þ†œ@@ �`oÐnwßód·­\ÓŒÿÚì|åÛæì>5ÿsósñØþÝUìЯ�¾°pÖ�ÎÅÌ[¼"ã üf¨éÐ*²a¬3έµ¤X¸paz½êVòn:#õœÑm=Àwéð™KÏÉ®ñì¶3Eø�Àð}o2S>Ÿ›æ:t¨7šÞ¡ 3z@[°k×®Y¶pV®¹æ£îT]RpRÁк’ºWVÀÔ%=”|ÿý÷Ýbzª ›ºjÍ$_´hQFwÜ:P]mvêÔ)}7£ò©kÈ`R€ïÍ7ß ®JÍ«�Ö©»o;•·\-È÷»ßµ*ö%uílŢ¿þzß®©uz‹8ßùÃ'RKIŸ¿ººU÷Šš*0ãžjá|óÍ7‡O“µ¬Ö†£G�ÎZ\ñÓŸþÔ(x_©¤.µeL .÷ë×/¸*çüQGež}öÙôv½`Öè|i̘1Þ‡üîUwîÜTOuåÍ·]yR¯ ÁÏŽo¿r¬Óg»wïÞ©HÔ;H±I•Cô}îÆ?|ž¨¾KùSî{õ{]æ¥z¿‡ï'Ë € €åðý»H¿û*�V{2u®jªÅ´–ÔRºó�̵T6Ê‚”R ®�i_M¸°C8ïáåðþn9¼_xÙí§Öäj1E�<×5Ôe¯†×8ÿüóÍk¯½æ P¨ËÙ_ÿúש®”]ÞÜT]KæJºf°ëÌ\ypÇkÕpªë˜ðþê^]�Rµâ vUÞOA’M7Ý4õÀ½�’jQ¨€�ï^(^Éž`Ô�Pøýê 4‡ ‚ËËXAfu ê’¯žÛœª Q_`Zݨªµy!I÷@­£5޹eÁÀ[ðx½ÕºúG?ú‘÷šÁ}ƒóåø,¯çæ£x?–Ú&ü9uyOÕ¶ZjÌuUf �KÞ?ßr.áó+H®¼ésª÷£ºy~ÿ„÷×r®Ö‰¾}£\ç{?ò]§ïuÛ-ÿ`º÷Þ{SC Ï¡Öìê¾úì³Ï6Ï?ÿ|Öw—]ÖgTÝ“Êõž‰êž]Š-g±ûßsu]·¾ó¢r©«l Ø*`ªJYÚ!8,—ïØàßßv­‹:滛¥îéëª\ÌÏzë­—*—ʦ®±ã’ dyä‘Ô÷×C=d^y壡fò¥õ×_ß ><Õõz¡e‰êû·Tß1•¸§¥x¯ë¾Eõ~¯ë»$×{¤¾Çå:ë@@�$ øžVòY »å˜G6ÆôÆç¿jÂM¼'üfÓ±USWžÈ§uÕÞ�ü‚œ@ *6ß|só�ü£ây9rdÆ8ϻ첋Q©IKê.ðƒ>Huƒ©–ÀkºgÏž)†+®¸ÂÜtÓM$jÙ£±¨ã˜ô úã�?Nuã«”º¬UpM¿I|ãpRß˜Ê êèA7):8Ô¢]]éê‡~‡RãkªB�K ©…˜‚;zélp‡ ¸¼jÕû1jua¬Jj¥¦–³ZÖkÙ²e©±WÕŠOßõýÌ |óQ¹ä:·Ô_VChaµôÓø¸*Wœz|ðå?ªuºŸªì¡Ï“†©Ðw *£¸ÞBŠý,Uò=£÷ ÷4û�Qj=„ùè£�Œ~#¨¾¾kõwUŸ!ýmU%5ýf¨Ï÷ny×{XCŸ(�«ï. á¡üioYÆ)_?+³v‡ìÆ?;±PÕ�iÕÈ¿ãŽ;Š(.»"€$AàÚk¯Mµ\©tYŒU«àwß}×l»í¶FAX߸̕Îg¥®¯x{î¹§Që\—ôp7¸ìÖ×êT�;µR�Ë¥8üuyaŠ € € € €Ñ ¨©<0ã¤C† 1>ø`ƺr/liÓ3B�é7l`ºg„�éªîÊ[cª5‘jê»VªÍœ÷-k�{©†? @ ~j!Ó¤I“Të+MÝK­±Ü¼›ºuún5jT,‚ÒÝh£�Ìý÷ß?ܘäè±ÇË B«ÅaR’ZˆuÔQAi•]ãn“@@@@@ 6|]fקǨjÔ©êÀ´Àõ@·!IÝW¹ µ›ª;«ð|x�–Ý:7ï¦:Ö͇§êþKÛ5uÛ‚óáñR6ŽEê+ . ƒ/ו­o�¶¹íÁix½[vSuêæ5 .kÞuZß2p\¼þùÏš .¸ +“[8 é­·ÞJ�Á­VõÁ4tèÐÔxÖÁuÌ#€ € € € €@íè9{8Åa˜ ÛÁeVjÔ(kUƒVT}`ºA¥·+ð¡q¬â2œÆRÀÚ«ƒSÍû–s­SkpwŒ›ºunªõšwËÁ©[Ÿk]p»›÷ ØÞÐ{ÄñT³€j9¹Ö¼®uoÓ¦MÓ-~}ón]pªùàKç .ëYpYón�›ºunYÓð¼[WÍæä=^ú»ðÝwߥÆbÕž ľüòËfܸqYUké£�>:k}µ¯P H�áûÙgŸ¤_zé¥Ô+\.ýøüÍo~^Í2 € € € €Ô�Àæ›on† f^|ñÅt©:è ô|¥f¤n]!SíSÈ뫯¾23gÎLÝ+]¿G�¦W¯^© ©Ž×{<×TÛÂ/í«Ànx½[n Ï»eMÃón]¾©‚¿n»›wS­×¼[vóá©òIB IêÉDèÅ‹›%K–¤†×~gå³8çœsRßùö©¶m»í¶[ÊC•Îò%}×]}õÕ©!JòíÇ6@@@@¨~�»îºËÜrË-æË/¿4;î¸c*P]ñRý†Jg%âÓ&²Àt:‡Ì$Z@Ö]«ÍDC$¸ðú"½îºëŒÆNU9r¤9ùä“M›6m¬BÑHŽ€*¦|þùçEX:~ö³Ÿ™=öØ£¨ãªagUÖ©+(Ý¢E sùå—›Ýwß½ŠD@@@@@ �ãŽ;.‚³Dw Û3ëdŠûE™LG©É¹@ ÝÂÜQ¨ÅpÔ_\îÜL@ ~­[·.*SC† 1—]v™0`@QÇUËÎò˜;w®7»únÜ~ûíÍyç�W³å÷œ• € € € € �H�ÈÓíZ41s¾]ž�تYãŒe@ ö\×箤 L«Ûk$C _ïú>Ð8Ò B¯¿þúf³Í63»ì²KMW^ êÛµkg6Ø`³å–[šC9$5ÔA2Þ”@@@@â,Q6�͈LGו÷±?êiÎyðÓtF¾ãÚ¦ySÆVMƒ0ƒ@B˜Ž'«@”^$H†À/ùKsøá‡›fÍše¼Ô]u÷îÝMóæÍ“ñßRžy晩9•]¯¶mÛ&ªü@@@@p‘µ˜µ]w3 [KóÖ´…f½®-Ínwr×`Š ðµ˜&0� 7EM¼ÀàÁƒo>|xp‘y@@@@@ –Ù#LíÓ&ºÓÚ¿]êKM2…eX¹r¥ÑË%uãM`Úi0E@@@@@’)@ÿºÉ¼ï”�’ Ðbºd´œ@@@@@(�@šL˜.Í­ã¬$R@Aiµ–vcL«¥´ZL7jug‰ä¥Ð € € € € € €@IZ¬™6nÖ${]C.íÙ’ŽE�ªðuã­À4 @@@@@@ ¾ÿ³u·ŒÌ4¤«i×¢Iƺ†.D{¶†æ†ã@ ªLWõí#ó € € € € € �P�“wéeÖîÐ̼>e¡Yo­–æ§Û÷ˆ\‚Àt䤜�ä øÓêΛ„@Ò–/_nš6mštÊ� € € € € c�ýw5z•*1*•,çE � Lkœi—Ô�7]y; ¦IX²d‰9öØcͺë®k† füñ$2Pf@@@@@ �iÞ ™€¯Å4�éÈx9Q \rÉ%æ©§žJåü‹/¾0§Ÿ~ºYºti–„,#€ € € € € Ð0Ó óãh¬X±ÂèåR“&MŒ^$’*ðÞ{ïe}ñâÅæ“O>ÉXÇ € € € € €I b”„»L(“-¦ËÍeªFÀ×c€>'$@@@@@�d <ôÐC©6÷ØcÓ¡C‡D`˜NÄm¦�”G@­¥ƒA7µ–öæÊ“®‚@å|ïÿàg¤ò9$ € € € € €å8ðÀÍo¼‘ºìµ×^kxàÓ«W¯rg£ì×£+ﲓsAjW€Óµ{o)YýL×Ï�£@@@@@�Z;vl:(­2Ξ=ÛÜ}÷ݵZÜŒr˜Îà`"@`º!z[‹k¬‘ýgvÕªUµXTÊ„ € € € € P€ÀÂ… ³öò­ËÚ©Vd?1¯�BQ¨Œ�éʸsÕø ¨;ûpR—÷$@@@@@�d ø/ù9Õ¢Nöó”ò™çš7¦.4ëvmiܵgâP¨FÜ‚A7Ƙ®Æ»Hž£ðý˜`Œé(…9 € € € € P]¦#¸_cžŸiÎä³ô™Þ˜²Ð\qàºéef@ ö|-¦}-Fk_‚"ðßÓ¾x!€ € € € €$C`õêÕYmÔ¨QÖºZ\YWÞ·½43Ãço/Ï2‹–®ÌXÇÔ¶€ZK[ƒÒbº¶ï7¥«[À˜~Fê>{ € € € € €Ô’€¯ñ’¯÷ÍZ*³+Kd�éo—e¡—|Ÿ½Î]˜)Ôž€¯Å´/0W{%§Dø|?&|?:üG³@@@@@ Ö|ψ}Ï’k­Ü*Od�éZÄ¡L Pœ�éâ¼Ø»ö|]ÙÓbºöï;%D@@@@r $¹+ï&¹PX�+@`ºX1ö¯u_-7Óµ~×) € € € € �[ Î-¦_þt�ÿù"3 [K³Ë†s¢ž[L׎Ã@ [À˜öæ²�d µ)àëÊžÀtmÞkJ… € € € €"×Óy~†¹à‘Éé"œ¼K/sÖž}ÒËQÌЕwŠœR ¸kú((ç ÌÁ…@R|ïÓI¹û”@@@@ÈÆQÜÖ84ò»å¹.;©é�ÏL7+V®ÎX×Ð…ÈÓ�egeu´y;k@ V¾Ó¾À\¬2Mf(¡€ïÇ�é‚sj@@@@@ æq L/ønE†ÜŠU«ÍwËWe¬kèBd�é†f„ã@ úôeüBUPΘ«þ’R ðUÌ~F ; {!€ € € € €ÔŠ€¯+ï¤ÄRL×Ê»˜r �p‹é&MšÐ•w î Y¨œ€/0M‹éÊÝ®Œ € € € €TZÀ×x©‘¯kê2gÔ×vÔÙ"0]æ›Êå¨e�p`Z5||�¹Z6 l6Ûl³àbj~ã�7ÎZÇ @@@@@�døZLÇ!0]ý&å¸×@�dЕw2î3¥,\àÐC5S¦L1÷Üs�iÕª•9þøãͦ›nZø Ø@@@@@ ¦|-¦ãЕ÷j�r#Ϻ†¬"0Ý=ŽE� µ˜~¡ªµ4-¦3ˆXH ÀÙgŸmô"!€ € € € € àk1‡Àt9îLd]y72Ù1sl9 Å5@ 2¾ÓIé~¢2â\@@@@@ªI yóæYÙmÑ¢EÖºZ\Y`ºq('à L'¥–OqRì� € € € € €IØgŸ}L»víÒEïÒ¥‹Ùk¯½ÒË•šYíéË»Qv»äe�®¼ÄÇÁ wå­ 4�é ó € € € € € �d�^½z™±cÇš|0C9è ƒL×®]AB`:·™B"PußìŸÀtyܹ  € € € € €T�@Ÿ>}Ìi§�« ÛOV~|C9gíTÄ ºò.‹]@ ¿€¯+oƘÎoÆV@@@@@@ ‘¦ÛµhœåÕªYöº¬�X�5# ÖÒ N»¤ 4]y; ¦ € € € € € €@r" L³}Ï Åc†õ0m[ÐSx Ô¸@¸+o¦i1]ã7�â!€ € € € € €@Õ Ø¶‡YɆy"M‘EŽÚÍôïÚ¼1e¡éߥ…±YçH3ÊÉ@ þj-cšÀtüï9D@@@@@J-Y`ZÚ¿]êUêLs~ˆ§-¦ãy_È € € € € € Pi�Ⱥò®tA¸>T^€Àtåï9@@@@@@Š(GWÞ¦‹½+ì�9Â�iíHWÞ9¹Ø€ € € € € €ÄB uóÆùhÚ¸‘i²F´ƒL˜Î f¢ (¥&çB@@@@@J#pô°'þé�zšÆ¦#c:#·, € € € € € € €Ä^àW»ö6ÖjiÞùb‘Ù°Gk³ß .‘ç™Àt䤜�d ¨;ï`¢ÕtPƒy@@@@@@ ž#6ëlô*U¢+ïRÉr^@¬Àm·Ýf dú÷ïoÎ=÷\L@@@@@H¤�éDÞv �@ùÂ-¨Ëwe®„@å&Nœh.¼ðB3gγbÅ sç�wš1cÆT>cä@@@@@�2 ˜.38—C Ö躻Öï0å+F`Ò¤IY»O˜0!k+@@@@@¨uƘ®õ;Lù(£€‚ÒÁÀ´ZKÓbºŒ7€KÅN qãÆYyZ¹reÖ:V € € € € €$G`æÌ™æÉ'Ÿ4­[·6x`b N`:1·š‚"P~‚Òå7çŠñ 0¯ûAn@@@@@�J |øá‡æ ƒ2‹-JeåÁ4÷ÜsO¥³U–ëÓ•wY˜¹É Åt2î3¥,\`�5²ÿÌ®Zµªð°' € € € € €@M Ü}÷Ýé ´ öòË/›qãÆÕTs&û‰y®=Y�Ô!  \0§A¸:ÐØ\Ó´˜®éÛKá@@@@@�¢,X�uÌ·ß~›µ®W˜®Å»J™¨�€‚ÒŒ1]!|.K�`E —A*k8 ¦ € € € € €@ò|àc+µ,éÓÿž4ϼ:y�é×¹…9d«µjÙ�²!€€G@_œÁ@-¦=H¬J”-¦u»), € € € € P§�é:‰êÞáö—f™³ü4½ãkS˜«Y?½Ì Ô¾€‚Ò¦kÿ>S‚ŸwÔÊ•+Ý,S@@@@@�„ $90YWÞ{yVÆÛæÞ×¾4ß|»{]-cR6’. /Îà—§Z†Ò:4éïŠd—ߘæ3‘ì÷¥G@@@@�d $¹Åt¤cL'ûmDé@@A¸` ŽÓ¼'’.¬¨á,L;‰êœ.Z´Èœ|òÉæ™gž1íÚµ«ÎB�k@H¼À‚ Ì 'œ`Î8ãŒÄ[P?�'žxÂ<÷Üsf­µÖªß 8 @¨°ÀìÙ³ÍqÇgú÷ï_öœ˜.;9D�ZhÒ¤IF`zÅŠ´˜®ÅM™ VÔpùºiqÛ˜Æ_àºë®K¥•S=Ð%!€ €Õ*pà 7˜]vÙÅ 4¨Z‹@¾+$pï½÷š3Ï<³BWç² € €@tO>ùdª²]Û¶m£;igò=#ö5r*àT‘ïòò§óÍ„‹Í†=Z™aëµ�üü´˜Žœ”"�\�p‹iºòNî{�’ÿG€Àtí½æÌ™S{…¢D € �X�ùóç'¶ì¼þ·ÞzkýæH@@ FsçÎM5BÙwß}Ëš«¸¶˜¾ñßÓÍ%�NI[œ=¢�9iç^éå(f"cº‘'7«=ëX…µ+ Óz¹¤Óz‘Hª€¯–]y'õÝ@¹@@¨ ��ÖFA( € `:vìXv‡¸¦o²�é`ÒrÔ±Þ"HÁ+1�ÔC ÜbšÀt=9¤¦|�i_7-5Uè„æ*;6ã�·Ü2a¥¦¸ € P­§hX’·ß®Öì“ï˜ œrÐA1ÍÙB@²Þþøcóü»ïfo(㚸¦ç-^ž¡0É ³ÚF¦ùZ'gìYø�é­Øê Åt@lNœ€¯+oZL×ÞÛ ]«VµW(J„ € €@�}»w7ûn¿}�{³ € PY�#.¹¤²°W�k`º0‘uå]ŽÌr ˆ·�éxßrW~Óå7çŠ € € € € €@œ|½júzߌsê›7Óõ•ã8ÈXsÍ5MÓ¦MÓë—/_ÎÓi f’( ±×:uê”QôÁƒg,³€ € € € € �_‹é8”Þ7žt„½x§ŠH`:wš< P# J‡Ó N“H²ÀÕW_m ÖgcĈæÂ /L2eG@@@@-à LǶÅtÄ‘iƘNô[ŸÂ#­€oêÎÛ¥ï¿ÿÞ˜vL“*0|øp£ @@@@@�$ DÖbº‘'b¾Ú׿;ÉÚ”�PWÞz¹¤À´^$@@@@@@cš7ožÅТE‹¬uµ¸"²Àt-âP&(N ˜VkiZLgÈÞ € € € € € P»tPFá†j¶ÜrËŒuµºðCŸ»µZBÊ…e¦—-[F‹é²és!@@@@@ˆ»À®»îjî»ï>3nÜ8Ó¥Ksì±ÇÆ"˾ž°=f7(¯¦ÄÇÁ hÖ¬YFWÞ LëEB@@@@@ø�ÀÖ[omôJZ¢+ï¤ÝqÊ‹@ Â-¦cº„Øœ@@@@@¨"�ÈÓm[d7¾nÕ¬qQ�Uh¨@óæÍ�ZM»´téRZL; ¦ € € € € € €@‚" L=¬Gã‘Ûv7�[7ÍXÇÔ¶Àk¬aœVËi¥Õv@‚Óµ}Ï) € € € € € Pˆ@v3çBŽòìsèVk™¾�[˜×&/HM÷Ú¼³g/V!€@­ ¨Å´^êÆ[ɦƒ-©kÝ€ò!€ € € € € €d D˜Öi·î×6õʼK �$�-Z¤ZM/Z´(Uìï¾û.œnÛ¶m’(+ € € € € € P«=¹lÔȳ²�«"ëÊ»�ùàp¨¦õrI�i½H € € € € € €É 0�Ü{OÉ(‰€k1íNîZL»e¦ € € € € € €ñPãè–k6ÎÈP«ÐrÆÆz.˜®'‡!€€_€Ó~Ö"€ € € € € €q5¬{FÖF 둱ÅB¤cLG‘!Î�Õ-вeËŒ®¼—,Ybô"!€ € € € € €ÄS༑}ÍÀn­Ì„ßšMÖnmÜ5òŒ˜Žœ”"�l�V­Z§]RPš1¦�Ó$ ,_¾Ü4mÚ4É”@@@@@ Æéjô*U¢+ïRÉr^*  t00½xñbZL'ô½@±ÿ#ðÉ'Ÿ˜8À¬»îºfäÈ‘æ�wÞ�@@@@@�Ä ˜NÜ-§À”V uëÖ�éo¿ýÖèEB ©\p�yóÍ7SÅÿý÷͹瞛T Ê� € € € € �`Ó ¾ù�R¨+o½\RPš1¦�Ó$ ¼ýöÛÅž0a‚YºtiÆ:@@@@@¨uƘ®õ;Lù(³@xŒi�/­àôªU«ÌkP¦Ì·ƒËÅ@ qãÆY¹X¹reÖ:V € € € € €$Cà£�>2¯¾úªéÙ³§Ùe—]’Qh[JÓ‰¹Õ�ò(øÜ¶mÛT«i�/½zõj³hÑ¢TpZëI$MÀW!C5H € € € € €ÉxòÉ'ÍèÑ£Ó?ì°ÃÌe—]–^®åš/ÖòÝ¥lTH M›6FcM»¤Ó R“H¢-¦“x×)3 € € € € àøë_ÿš±á®»î2Ó§OÏXW« ¦kõÎR.*(  t00½páÂT«é f‰K#P1ZLWŒž #€ € € € €±˜7o^VžÔÀ/ ‰®¼“p—)#eP—Ýj5í’Óz‘H¢�éèïúŠ+Ì’%K¢?qg\¾|yÆ^ß-[fÒ#D†  €’î�@@IDAT P·@£F�L›–-ëÞ‘=@@@ æ4j8éßIH‘¦Ç¾È¼6y�Y§S ³Ç&�’àG@À#à LkœiI +ïhïú /¼`N;í4óå—_F{âzží¼¿üÅèEB@Š8ùÀÍi‡Rìaì�@Jફ®23f̨ˆÆ[o½•qÝ;í‰Ï¿ûnÆ:@@�ºZ4kfŽÜ}w3 W¯ºv­¹í¦#¸¥½õ•9ñ®�Òg:j»îæòÖM/3ƒÉ 0�œ{MIë ÅtÝFÅìqã�7Æ&(]L¾Ù@ \÷ÀæðÿجձcxËä8묳Ì=÷Ü“wŸrn|cÒ$£ @(Và…÷Þ3Ï_}±‡UýþILG6Æô˜ff¼ni–ùzÑ÷ëX@�d´oßÞ´k×.]Øùóç› ¤—™A I¦£½ÛIk%Z5Ά €qX¾re\³F¾b,pß}÷Å8wd @(\`ÚìÙæó˜ôŒXx®¾g’Ó‘uå=ÉŠ¬;ñíÒ•¦ËÃÌfmgÔ¦€‚Òj5í�i'Á4‰tå]Ú»Þ¶U«Ò^€³#€ € ,\¼8â3rº¤ 0ÀLœ81iŦ¼ € P£M›Dª¬!ÓUs«È(TƒÀšk®i:tè`Z¶li–,Ybô%;oÞ¼T«é`Kêj( yD ¡´˜n¨`þãï:ÿ|³iÿþùwb+ € �í~ñ C`:&7£†²qõ‰'ÖPi(  €ÔºÀ•vH’YsæÔz1ó–�Àt^6"€Å (0­.½˜VR`Z-§ LoÉÕ-@`ººï¹G@@ ŽÙ°OŸ¸g“ü!€ €)¦IñxvÒ<óÁÌÅf“µ[›­ß>òŒ&¯}|䄜|.0=sæÆŸw�éuì?˜I$I€Àt’î6eE@@@@ò ĵÅô5O}n~÷Ä´tæÏÑÇœ¸s¯ôr3kDqÎ�„\`Ú­Ÿ;wnªÕ´[fŠ@Rc:)wšr"€ € € € €u Ä10mGd5ú÷ŒŒÌßü\ærÆÆz.˜®'‡!€@~�N�:™Ž;¦w"0�¦`&a´˜NØ §¸ € € € € €@• ¬²‘é…KWdäzþ’ÌåŒ�õ\ 0]O8C�ü J«Õ´KsæÌ1 N“Hš�é¤ÝqÊ‹ € € € €äˆc‹éܹ�vKd�éF�²3f[}“@ ¡íÛ·7j5ݤɆ²_²d‰ùæ›oŒ¦$’$@WÞIºÛ”@@@@È/@`:¿[@�¢ÙÚ*�;wNwç­/Z¦Õrš„@’œQ\UÚØ`ƒ 2Ö±€ € € € € � ÓɸϔÊ,Ð¥K—T«iwÙ¯¿þšÀ´Ã`š� /¼Ðì»ï¾¦yóæfàÀ�æšk®1M›6MLù)( € € € € €ÀILÿ§�Ý,˜C"PWÞz¹¤À´ZM“H’€z¸öÚk“TdÊŠ € € € € �C É�éÈÆ˜ÎaËjH°@×®]SÝy;‚¯¾úŠÀ´Ã`Š € € € € €‰hÑ¢EV™›5k–µ®W˜®Å»J™ˆ‰ÀZk­eÔ�·Kj1ýå—_š•+WºUL@@@@@@�Äzè¡eÕP��;wÎXW« tå]«w–r!�©Û­[7Ó®];³`Á£î)fÏžm Öz € € € € € �$�Ñ£G›=z˜×^{Íôë×ÏŒ5*1Å'0�˜[MA¨Œ€ZM«Ko¦•ÔbZ]z˜®Ìýઠ€ € € € € PY�#F½’–èÊ;iwœò"Pf ˜vI-¦˜&!€ € € € € €$G ²Àtëf�³Ôš7�ìôYçfT‡€Ój5íÒ¬Y³RÝy»e¦ € € € € € €•Xí¹t#Ϻ†®Š,r|øÐîy9`pWÓ£}³Œu, €@ò4NB÷îÝM£Fÿù ›7ož™>}ºY¼xqò0(1 € € € € € �P�ÈÆ˜>|h7Ó³C3óÊg LŸNÍÍO¶é–PRŠ�A�Æ�›ž={¦ºóÖøÒ«W¯63gÎL�5ݯ_¿à®Ì#€ € € € € €Ô¨@d�iùì8°CêU£V ê)àZM+0­4cƌԋÀt=A9 @@@@@¨2�Ⱥò®²r“](£€ZL+8í’ºòÖXÓ$@@@@@@�Ê øÆ“ö�;ÝÐ\˜n¨ Ç#€@�k¯½vF`Z-§§M›f–.]Zç±ì€ € € € € € P:�552-ÖÌ ·nÖ8ò f^!òÓsB@À˜–-[šuÖYÇtéÒ%Å¡q¦?ÿüóTwÞø € € € € € €TNÀÆ¥Í1Ãzfdàèa?ô„›±¡ ‘Ž1Ý€|p(Ô¸@ïÞ½�ºôþúë¯S%ýâ‹/Œ^ýû÷¯ñ’S<@@@@@@ ÞçŽìc6êÙÊL˜þ­Ù´Wk³÷æÿile® LG©É¹@ §@¯^½Œºô~ç�wRû¨Å´Æš&!��eË–™fÍš%¥¸”@@@@@ ÊöÝ¢‹Ñ«T‰®¼K%Ëy@ C@]yëµÆÿùÚ™;w®ùì³ÏŒ¦$j]àÌ3Ï4믿¾0`€ùãÿXëÅ¥| € € € € €d ˜Î"a”B I“&¦_¿~©VÓ:¿Æ™ž:ujª;ïR\�s"�Ûn»ÍÜ{ゥì,]ºÔ\y啿Í7ߌKöÈ € € € € €e +ï²0s�@Ÿ>}ŒÆšV7ÞJS¦L1Ó¦M3›m¶Yj™ÿ!P‹“&MÊ*Ö'Ÿ|b† ’µ>î+V­ZeæÌ™“îù ù]±bEÆe|û­™³`AÆ:@@�\+íï™®:äÚÌz@@@� ˜.!.§F�L�¾}û¦ºó~ñÅS”V€Ž±w3�Xª-×}}°T ðV[?~¼9á„ÌŒ3b•õÃ/¹$Vù!3 € �ÃwÝÕ\:ztü3Jc)pÍ5ט &˜æÍ›W,Ó§OϸöwÝeÚ¶j•±Ž@@ —ÀÂÅ‹Í ûíg¶ÞpÃ\»°¾ ï¼óŽy÷ÝwSC@:´ WŒÇ%LÇã>� !бcÇÔ—l§N�R­.œÓ8Ó Pkü]µ(P+�é1cÆÄ.(]‹ïÊ„ €¥¸óÉ'Ía?þ±ÙÐöèDB �+®¸ÂÜtÓMÅR–}Ÿ³5I € €@1o~ô‘yãæ›M«-Š9Œ}#¸ËV,<çœsÒg;õÔSÍ)§œ’^®åƘ®å»KÙˆ™@£F�RãL«Ko—ÔbZÁiµ*иq㬢Uc‹éŶ&% @¨�ï—/¯•¢PŽ2 <ñÄe¼—B@J'°ø»ïÌÄ©SKwΜWàf[) ˜n¸áÂ0¸½–æi1]Kw“² P[mµ•¹òÊ+S-¦•]ízôèaV¯^m¸&!Pk¾Ó+W®¬úbvj×®êË@@@ Ys,HV�)mä½{÷6Sy€¹+'D@�Ê4oÖ¬2æªfîܹ ßÿ½ùÎVhÓ¦MÆúZ\ 0]‹w•2!c�ÃÕ¯_?£ñ¦]R@š ´Ó`Zk¾÷¶*bT{úý/~av4¨Ú‹Aþ@@ !ûØnòL'äf—±˜LHw‹e$åR € PB�›ÇŽ5ïOž\Â+pêB|χ}Ï‘ =_5íi`úߓ晗>�oÖéÔÜ1´{59�W(£�è2bs©Š øºò®…Ó‡% € €TX ]ëÖæG›mVá\py@@ 0¦ITZ ²Àô]¯Î6ÿ{ß'éò¼úÙsÃáÓËÌ € �D_WÞÕ8ÆtïeF@@@@¢Hr‹é5¢Â¼ó•Y§zøí¯ÍôyË2Ö±€ €@Ò|]°˜NÚ»€ò"€ € € € €D˜^¼lU–æÒåÙë²vb €5,àëÊ›Àt ßpІ € € € € àˆ,0í=;+@H¸]y'ü @ñ@@@@@�€@’»òŽlŒé€'³ € ð_Ó¼@@@@@ªAàÅOæ›f,6÷le¶[¯}äY&09)'D�º–-[f>ýôSÓ²eK³Î:ë_à®®sTj{5ç½RfI¿®ïýMWÞIWP~@@@@Hª@\[L_ÿôæòǧ¦oËy#ûš_ì´vz9ŠºòŽB‘s €@ÁÏ?ÿ¼Ùf›mÌž{îi†nöÞ{o3kÖ¬‚�¯äŽÕœ÷Jº%ýÚ¦“þ ü € € € € €Àq L¯¶Ù»ñÙé?dÒÎÝZÎØXÏZL׎êG`âĉ&_ëÄÆ�›ž={š¶mÛVO¡ª4§‹/6'�t’™?~ºï¿ÿ¾¹ð Í-·Ü’^Ç™jÎ{=“”'ÓIºÛ”@@@@¨>�Õ62½`ÉŠŒŒÏ[¼ÿüss×]we¬c¡aÕ”–¶šó,ó € € € € € P^Å¥‚I=ü†ãRÁíµ4O‹éZº›”¥Á 8xà�æ³Ï>37ÞxcÆù>øàƒŒå3fµ¶ö¥æÍ››^½z¥7Í›7ϼùæ›fÊ”)æë¯¿Nm[ýõÍFmdÚ´i“Þ¯�™ï¿ÿÞ|òÉ'fâĉæÓO?5;v4l°A*¸Þ¹sç:O1wî\3gΜŒýÖ^{mÓ¢E‹Œu¹¤_¶lYz³¾,ûõë—^v3_|ñ…Yºt©[Ì;-æú¹N´|ùr3iÒ$£òÉ[/Í÷Ýw)ãöíÛ›àkã�7ÎùE_î¼7ôž:“ð½éÙ³§qàô^Qo³fÍJ½ouÏô0`@zwžb¦Qºs]öE@@@@@ ?üpsà 7¤³~ä‘Gš5ÖX#½\‰oäd„Àt P9eõ œzê©æþûïO‘]i>þøc7›š*€=sæÌŒun¡oß¾æÙgŸM-Þ{ï½æòË/7óçÏw›ÓÓ:¤Zhï¼óÎéu¹f¾ùæó¿ÿû¿æùçŸ7+V¬ðî¶ÖZk™_ÿú×fÏ=÷ôn×J}Ùýå/ÉØ®cŽ>úèŒu¾¤wß}w³xñâôæ5×\3$W�ž`1b„Y°`ApUÎù+¯¼Ò|ðÁ9·çÚ ‡^xÁ<öØcæÉ'Ÿ4 .̵kÖú?ü0g@¶yW†¢º§®pá{sóÍ7›aÆ¥º¢úé§Sݧ»}Ý´S§N©÷çn»íæVÕ9-•{�f@ †¾µ©>ÿòË¢J¸aŸ>Eí_ë;cXëw˜ò!€ € € Pgœq†YwÝuÍ{ï½—jtXŸøHµJ˜®Ö;G¾K* `«Z3»à².¦§oÚu§�¯«úéÓ§›U«V¥‚¥gžyfμªUï1Çc®»î:³Ï>ûäÜOÁW�s­@f¾ô¥} }üñÇÕ¶¹à‚ L³fͲvßc�=²Ó�?þxA�iå#”ÖÉwÚi'Jk}>m¦böuÇ©ÅúèÑ£M¸%»ÛžoÚ®]»œAiWL~ŠÙ7˜§(ïið¼ÁyµÎ?餓Ì3Ï<\�1¯ÖóÇw\êuî¹çfló-”ÒÝw=Ö!€IxÃV˜e+²“¦ÙJt¤0üÁ‚9@@ iÓí3�…�†*Û“\³PCФ¹P^@�ø ì¿ÿþF¯¤%ÓI»ã”·`uu L«»e†Õ*¹®¤î�Õ­ò…^X×®©í×_½Ù{ï½�oìbµVà°˜è�wÞ™ ¤ÿýïϺþàÁƒMïÞ½SùsÕ͸‚˜]ºtq«¼Óýë_YëóÔ³vŽh…|�8âˆT—ëõ9¥º¸®dŠúžæ*‹ºîΔ÷ç?ÿ9U9B]œçJÕîž«\¬G P�Ïl…˜gßyÇLš6Í̱=4̵¯EvH‡6­Z™ÎmÛšN¶ÒË`ÛEþŽƒ™.vøü ð¢­üÅW_ý°"Ç\K;Jï®]Moû{KŸ) Xµzµù‡­X¹Ôþަ}¶ßÞ´ôTà îã›ÿÆöôÔodm1t¨ikÿ(iŸqo½•ª¤ÞQ\ÞqÇðêÔ²*ÀÞ÷ï{·iåö›mfÖ®ã·z΃ـ €@~sûíæŸ¯½–QÒcGŽ4çuTÆ:@@ ²‘¦ÛµÈ>U«f�+[:®Ž@-Z”q´‚Æ­[·N¯S÷Ûj¡¬¤.®ÕÅv0)ø8{öìÔ*�ß<È*ôÓøÕêV9˜4fô‹/¾h¶·É‚I¨®¸â oPº­ ‚l¸á†æ+û�WcW‡ׯ¾úª7nœ w®rì»ï¾©VÚîZjÝ­VÓGåù±®¼<õÔSî�ÔTáó»vØa‡Ô8ÏnÙMÕBWÝh7$)à.G_ÒøÞL�'ÝÊ> T«qu=­{¤îÔ5î´ZÃçK¥Ì{)îi®²+h¬éõÖ[ϨûxµþW«þ`Òûç’K.1êz>W*µ{®ë²�J ¬°ß}wÙï¾??úhAAµ{í÷®¾g·Þ`sžýNÝÄVrJrºáá‡ÍòÐð{n³�Y¿W¯$³$²ìwØßEá…uA´²¿ŸÖ±êÍm×V'Ù!TzØá'H#0ÙV(:ÅV §µ:v4;Ù߿ŦÇíïëóCCâ4¶c€)0í’×gÙáTr¥!˜~=zdm~Çþ>ËwÜ-vHÓYl¬@ª^`âÔ©æ]û7 ˜¶°•]ÚõMïÙç%ØçDÁTß¿}ÁsTãüB[‘˜* Æç^�@ ’ÙÑäzææèa=Ì›Sßõ°mº™í³»®çé9 �² „ƒ§jA­@§K—^z©›5 b‡[š*ЫÔË>|×xÕÝ»wOï¯co¹å–ô²ft½p`úÁô`Ï:ë,óóŸÿ<ÝÂZAÚ#�<2+ب±›ÕÕv¸%¶º‡P÷áÁTW`úõ×_Ï 4«[p_wá:ïÿøÇàéÓóãÇ�OÆÓ+ê1óŽm±N F_tÑEfÈ�!¦I“†}µ•2梁§a-»n×9ä�Të}÷þUE„1cƤÑÁãT™aéÒ¥FÁ}_*µ»ïš¬C ’/½ÿ¾9×ö&0eÖ¬¢²¡Š¯Nœhö¶ßÕÇîµ—9×öð�ÔtÃC™Åö{%˜Öµ½V˜Š0ŸK`±w[kõzÈVû‚ç`�† Pi°¡‚�Ô†@â7ƒýu1ëtjn^ùl�ém§{mÖ9°•YªK@­ŸÃco¾ùæEBA¼ŽöA˜Z ƒÒ:ɱÇ›˜v­¯ƒùÃþ\LÍ+(­q¤ƒ©ÿþæ!ûð×]wMµ vÛìþç?ÿi@¦¾}ûš-¶ØÂ(HìÒ¶u‡Z3wÊÑé‰'žp»¦§•èÆ[Wк[o½Õôð´>Ѷ8¥RÝÓ\eÔ=úÝï~—±y Û²Gãs« w½?\R0M]ЯokgûR5»ûÊÃ:ò ž&5¿£ýM]LR¥§£vß=ë�íz €@òÔ’9œ¾¶½½5$ùŽ§ç™†ˆrlTTŒJ’ó €¥hä9­}¼yZ#Ê3Z§�9a§µ JG‰Ê¹Ê.0sæLsÚi§eu�½™×­˜¤ ß¶–ëÚ¶¥F8iœjuÅLáÀ´Æ|V^‚©M›69»ÛÖ9>øààî©ùwß}7k�Vì ’¨‹é`�2x�Ê¢®ÉƒIãQo»í¶ÁUe›W·Üáô±}h÷Tê{.¿Чžzjxuzy·ÝvKÏ»™©¶UZ®T­î¹ÊÃzr hŒOuýZHPZcà6_sÍ\§J­¿î�Ì÷Ë—ç݇�¨Ã6vˆß«!]9Öšì\ÛKÍ/m¯/óBC®¨œÖÚÝŽ¦<Ã=�ã�¾øÂøÚç»âû“'›…‹gí¢Àw1éÜV�;˜Ô³ÄÛUð[6˜gæ@¢èîiðÕ¼y :ù—žÀv÷Î4 j*G&àûíTéJƒ¾ÂQiЧÂ:@ �ÈZLG“΂@exU`øŽ;î0±ãÆ©;ã`Rv/Ûk±)ü ¯–¬ Tº¤ÌÁô…}`N:ŸÆ Ε;ì°¬–ؾóèx•çâ‹/N�¿ìÎ÷ÿ÷Fç§÷lׂ³B]ÙêøÆ�+3޼ÆJÖ¸ÜÁô³ŸýÌ(Ъñ³·ÞzëŒn׃ûUrÞw/¢¼§á²uëÖͨu|®¤næÃi^žW«{¸Œ,#�O@Ýv«;=ý]ð%u¿zàðᩱ£5~t[aHi¼ýNºÝö@ð°íq#˜š5mj4.èšvJB�L�CwÞÙ¨d—4ÔÄlÛ{˔ٳÍd[9O]xû’Öj,Þ›O?Ý·™ud Ûd£q ÃÁ`µ\Þwûí3öÍ·à{`ÚÑþ ØÄö\TLRp[cnaϺôºþ¡�ÊPn¦ €µ#à L[y*¬ñµçßõ´˜+±\)U ÷fã* viß¾àlE]iP¿]¢Ò “`Š”F€Àti\9kŒÎ9çÓ9PStáÂ…FÃïì8†¹ÒïÿûT·Ü¹¶ûÖ«[é�6ÚÈ·)µî’K.ɹMÔ¥r8 0 ¼*c¹OŸ>©1Ÿƒ-[}çÑAêf|¸ ®Å¶ÄüÎÓ+ƒŽÛËöñ[;”B+Ï8ì 0赑ý¾ÔVrR`[A鿜y¦ùQ‘=näË#Û¨%�ílÀpïí¶ËY$�·øk;TÇÛR5œþùÚkæ�gŸMU oc� €ÆV7ïo†ÆïT ¹¨À´§«ííí÷û�|��s`sÙÊœÁÀ³‚âÁÀ4-r2½XB’$à 780íi1ÝÍÓ2;IΔ5>TŒÏ½ ' €€O U³Æfñ²†6ÔrÔ釪@QŸ™ó!SuU¬±uÝK]@ç J�5*À-¶8áq¥‹=Þ׺V-`ëJêÒ;˜|çqÛ÷ß7›šª;ïp—ÝÚL+^ì˜Ûjࡇjv÷ŒÍçN»bÅ £1¾o¹å£û·é¦›š“N:ɼþúën—ŠL}÷"ê{,X×®]ƒ‹ ž¯V÷œ$Fà™·Þ2ï|úiVyP¸è˜cÌm×ø¾ tð€Ñ¶7‰ßÿ⦕­ 3欳Jq˜G H�-4�^qEÎàóØ—^*òŒìžT�ážî¶.4iŒó·>ú(kw_7áY;ÙƒC•KÃ�èpkì­m·þ$@�døºØþfÁ‚œ=8Õ¥¢ž9–y†êh Q×9ØŽ@)\¥Áð5¿‡ÂÛÃËáßSÚ^L¥Áàù¿ }çîÏ< PË£¶ëžQ¼ðrÆÆz.ÐbºžpVûjmªÖÕ#FŒ¨Waƒ­²ësµ\§ð¸ÔáíZï3ßÖ”U˽FžÖ»ì²‹Ñ¸Õ‹ã4ª;o ]R—Ù“C-•*ÙZÚåëÆo4ûÛßÌM7Ý”ê†Ý­÷M¿·ÇŽ›z)ï—_~yEºú.Ç= –_cLG�ªÑ=jÎW»û׿¼…;ÆþµÇÞm¾•êê[¯ú¤O§O7ϼý¶ùÄNõ@ê[Û›G;„ƒÆ²Ö8Ã; dúvÏü�X×uÔüùwßÍØM]ϺÖsìu|î93ÕvŸ<Óv£¬ýûÙk °×ÓKcûþ†Oø‘íåCLJS°… Û¦àSÛ³E!i˜­Xä«  n§5_Újƒ Ò]­k?u =ÎV>øÌN§Ù¼¶hÖ̬gÿÖ¯o‡5j{ÙpùÎÞVŠû¾FÒ—ÕUCô°h–}Ó¤iÓ‚‹�WWþÊ_û·WU¡E/½õðÍ}ž‚]›ár|–óåIå{ÎVüS7ÔúÞ˜÷í·¦�Êf?CëÛï }–ÖíÙ3Uî|ç©Ä6�eøû{ï͸´¾ïô½ !êJj½ÿ}è!¿¾ íc[û£À¶ûNԼ‡ô]¯î+]Òw¾ZS¿f»÷nHšc{…zËæ{š®è‹¯¾J�©­»^Ëm%NU Ò¸ì)4?Òö¢÷. @ <¾ß ú{1×>§éÔ¶mÑ™ðµ¶Ö߬ñŠ>1 ¡€* †{³ ‡ó].ŠJƒÁßZ DŸh¼’«Ò`pÿ|yc PíçŽìkvoeÞŸþ­ÙdíÖæ€ÁÑ6~“OaO«]’ü#P Àšk®izÛk§ø¸ãŽKu‹]à¡Y»540nù¬ ø›á ‡÷ÑøØ¹ Íìð=÷ÜÓüýïOŸæå—_6 f·ÿï¸.áÖÒÚQ>•Nßúè£�6Gy¤7nœyê©§Ìóvl×ÙžÀH0¯êî[Œ¯¹æšàê²Ì—ãž–º Õè^j“bί.ç›{º�.æì[=´ou%=¸ÿE¾óÞ¶½w\|ûíf¼�æKßv[*P|þQG™�í×…¤¿>þ¸ùÝÝwgìú›c�5Gî¶›¹æþûÍM�<’ Twx5ôU`úêO4=íß“\é;<Å—ž±ì|ûÿùÑG�^…¤_{­7htݘ??öXÞSüé´ÓÌv(“´ÿŸl%c‚I�j%ÝçËíßý}† nÎ9_Êû•ó¢ ÞÐÚÞŸ3~òsêõ×g(è=7ßGL«tú«­Øç«œÌWKû»kS[)D!~j+¼(YL*ÇgÙ—UÀ¸ü®»ÌÓ¶Ç!_zò�7Ò«{Ûž{ô}¡ÖîqJú¾ÔÃ}kƒéÛ�w!�éðÃI�c£¾}Mg[i¨�¤ïµšv4õ½¤`·Ûz«J¤.mgßõ /±Áîûÿýos�ªg’­0<¯;]Ó=í÷¦!0]Û@�È:Ûg/úÞw•—܉`¦ÕÃÝÓÿýíêöÑt×-·Ìxæó•ç7yGû7pM;ÌP!)ÊŠ—q©LZH¹U!ØÝÜV[«C‡BN‘Ú'êŠ|ÕdX0’Ý‘JƒT,æý¾ P~£K�v%!0í$˜&Fàˆ#Ž0ØVT.iœÝž¶uG_ûpIÝoGÕÊ´•mAÒ�Ô˶: §º‚®«V­2_ÙàJ0ùÎܮ�iuƒ­î¼>øàÔnáÀ´ÆnîW`0$x�RÍ+Pºë®»¦^ºÆ”)SÌ+¯¼’~}ýõ×Y—~øá‡Ía‡f¶´ÿ€+gòÝ‹RÜÓr”©šÜËáQ×5î²ô¯¼òJ3Ï>$9r¤¹á†ê:„íexÕ¶Ló=À?Ö�ÔŠ¬”éO¶ÂÌåwÞYð%”×½l7á—Œmûñ�ë<.ŒÕߨ]wÚïú?Üw_�Çëz»Ú ï�¿úUê‚ïß5|ûEµn¹}0WWRe¥kmPú�=”w÷ÅöAÔÉ6>ÃþÍøÅ~ûåÝ·Ô÷+ïżqóu×õ–^­õãÐíñlOO7á +h¨Ï“^·?ñ„ùÕ!‡˜ŸØÏp¡­¨}Ÿ³¨?Ëáò —Ðè£ò ãvöuçèëÜíï›no{€p�im×9˜Ÿ[ù ÷à;_x݇¶�Ñ¿û]ªutx[¡Ë ´ÇåžšgöCª]@£ºÙÖÌÓCÏ.`V�I.}nÛgÿMN¯Ú¿ÓÝãGe燓¯UvxŸRT¼¬teÒpó-«7&ŸïÝöwM!�éRUä«&Ã|¾ámT ‹ä^¦Ò`n¶ €@õ DßÏkõZ�ó„ìa»cUPÒ½±͆ÙR NG”Ž‚R-·ÃiüøñáUË&L0 ,“/ܾõÖ[›¡. Õ�·ÒŒ3ŒÎLqh-ÌOx^ ~b[U]o[U½a[ðŒ3ÆH϶�)w*×=-w¹t½8»WÂ#xM�-®a”Vz̶òT�š/�wì°á¤VšÇÙàJ)“Zý”vyYe[×�sË-æ¾gžq«ŠšªëXl MjAp¶½^¸+ÛB�¯Ä~ �}l? $𴝂‡¹R¥îW®ü$iý:¶%®¯©ºe¯tR«mu'XLR÷œçýå/Y­À‹9‡ö-ågYAéÓíÐ)á\uåQßO7ÛÏÒ‰è�&_Þ|�d=„^i+væKºWØŠ�ᤖ>Ťðþ®¶›ºs…÷sëóMõ}wÀyç5((­óçë#ßõÙ† Ð0�``Ù�)Ü%÷ÔY³Ü¦Œix}ø8íì;ð$ú ¼ß¹çÖÙ{“;ÆU”½Ëö\—/[™ôz[™ÔWÏ]ÃU&½Ñ66ˆ:ùþÖëôéSç¥ô›i·ÓOÏÙ»Lø®"ße¶rr]ÿ¾ª&Ãp9ó-»Jƒá}|­ÖÃûh9\±Oë|¿õ´>WR¥Á`rçtS·M•ë“TipW[¹ûû\Ró¾Šðu�—Jƒu ±ªU€ÀtµÞ9ò]ó¾€òã¶;Ö9¡ñƒwÜqGp15ï †wR7ßû…Z‡½ôÒKf¡íê0ÜZZ�û½K¤ æ­¡ó*›ÆÑ>ᄲNõ™±Ü©\÷´Üå _/nîáü•{ùS;žn8}è&9¼�åÊŒ÷¦5^ª‚Ó¥JêR¶�Ëù®…m�¿Øv_lR0Æ×Í_¾ó¨5ñ˜ÿV\Ê·_\¶©UÉ?^|1+¨ÖÜÛÑ,GW† ìØÕ¹‚ý•¼_q1­d>ýwìæpâP©°ØÏR° z�>d‡"©o*ÕgY~/³¿+ëóÍ•å_¯¿žjî–+=Uëäðð6êScfçK¯ØJšauÃ>xýõó–µM-ƒ‚=pLœ:55îôÌo¾IïÛ§[7Ó«kñãw�oxÖçoAúÂÿ�éÙ¹sxË €eèáùþ ÿ¾˜’£2^x½¯Åt¾Àt¥+^–¢2i}n™þ.‡“Z²w¬£÷¬8Tä‹‹aد®e_ ™Jƒ™jTÌô` jG€®¼kç^R’ÐÏÚq='Úî]úÞ¶Æùýïo.»ì²¬kïÛî5~r8m·ÝváUYË L»^nǽӘÍÿú׿2öUëjß8É;Åpa±}èN-‹×1||}–ËyOë“¿¨�‰‹{Ôå*ö|¾ ‰ºÝ'ÅK p9«OpÀ[ÈT5ýüñ¥#ì;l±…ékƒŸØñ]5žœz„“‚¥cl«ë“<0¼)ïòä™3ÓÛ5†´ºV ^A[u�|å=÷˜Om¯át‹§ùxϘÛ�Û^ÔJ2œ¶=þøð*£ñ­w<8k½oE·㹩 ä`kvåùèË/Ï8…Ð4 ¬ùÙ>û˜‘C‡¦Æ…Õj�×{Ùßþ–•oÓFÙÞU©’÷+œ—$.«e´¯EÉzk¯]q}WÕ2Ⱥ�µ@•ìþÝÝwgìû£"[aëàeö½ö»ïfœÇ-¨ëÑÝíï÷A6ˆ®û¥±®[7ožú®WKñïíûuÑ’%f®ý[2϶ ïšã;×��) €@i|�ãpËç)¹ZL‡Ö_{Ƙö¾U’¨*^ŽØv[ÓÊþ}©OÊW™T+õw.œ\eRßoöð¾…,küî7>ü0k׺ZKGY‘Oÿ&«oŠƒa}òî* +ºJƒù~›E]iP¿�”¨4XŸ»È1 ÐP�×í3¨íß �6ÚÈ 2¤¡§«šã LWÍ­"£I8ãŒ3̨Q£2Š~·}€µÌv3zºí&H]p/µ-åžµÁŠ_Ùîa´>˜ÔEù6Ûl\å�_Ï>ŒÓØÑÁn»ÿf$†»»·¬öž¬ +Ç�gØ[g�uŒZ!·ÈÑ¢Q-UÕÊüZ;nh8­›c¬Êð~Q/—ëžF�o�¯šÝKáQè9}ã LªW¾ýô`>œz×£ÕZø¹–gÚ`ÕßB•´¯Æš½âç?7ï¸cúÐumàmû]>Ð~ç)Èü‡»vºÙ‹�´c¤¶oÝ:}L¡3§z¨9aÿý�&.õíÞÝì4h�Ùßv ûþäÉnujª.mõ÷`«?mð=LÓz=  ·âS«ƒ†¶Êkת•ÑË%ßýÓ8} lËJgÚ!ÂcG�¶ã½¿f[Ž?õæ›î4©é—ž‡yq¹_MØÂ[}”Ub�…»aÝ+fñŠÍš™ÁÔyVu-Íý÷›ÿ{啌}ÕUþ_mo}tÆúb¢ú,ëšj¡¥ /á¤ÀæÍö÷g¿À0úŒëó¯€ê|Ð\õ÷¿g¦ °Ê;Ò>°ŽCR7ÙáÀôK¶E´‚í¹R¸;Gíçká“ëøàzuéÓZ¯®PƒIh‹Mz_ùº#ßÑ~�_ÿË_µîΗÔE¤¾÷I €•ð��˜T, æ´!]y—ºâe%*“m �ÁVRÓ¿sÂiH¿ïÊQ‘¯Z Ãv…,Si�Jƒ…¼OØ�ZøÓŸþd.4²8ÿüóͱ¶1G�é$ÜeʹÀ%¶UÌ“O>™>¯Z‡“»áÇûØÖZ§�vZxלË;ÚÀÄV[meTs&˜´þôZÓ¶lS€+<®´ÛWAÐBÓþ60 L¿óÎ;‡6µ5>w]iþüùFãPç ¼-òüØ¿øâ‹3Zl»kl¹å–檫®r‹ééW\a>þøãôräЫµ ʨuª®¡|øZìê µp‘k8•#ïå¼§áò5t¹Tî ÍWÜ�÷µ˜ã^†ZÏŸ§¾1T{Ù1mK•î°Ai_ëOµê ¥ƒ×W õ3ÛŠùž§Ÿ®6 j©ûé` âŒr,¨]®`Œ~çu”9ä ³Žžj[e„ÓY;Å`…ƧÓ&y†ƒÒ.{{Ù^E²ÓsçºÍéiîW:3 œ™cÇD÷�ž¡­¬Ñ´IõüsF�ÝëO9%õÙ¿ï>²ÁÅú¦¨?ËwØß¸á±ÖUiæÚ“OÎJó«Ê-ê¹áyÛr÷�I“‚›Ìk6ø—À´Ê×ÛßÐÁ¤J#\=F„“z^Ð+œÔ£E}R¾À³ÆPßÖÖ’/6)0íKêy£® ´ï8Ö!€”_ {!]yçh1nIî\¥ñµ˜.GÅËrW&­ï�{ø…¼‡æûýR®Š|Õbè,`%•³‘¨4˜mÂjUছnÊ(šÕI L3ÆtÆ­g�ÂfØÀÀ´iÓÒ¯™žš«jÉÜGó³rüC"ßUÏ=÷\Ó*Ð*,¸¯ºöΔ>Ô¶‚Û¬ˆV šçë’p§�v2mÛ¶ ^Þ;¯rO™2%«ìÎb®ç�ÿ·¶E›Ûœ~‘ãAÛtÛ­m0)­{ `õ$û0T÷'WPZÇýĶœëg»r §rä]×,×= —¯¡Ë¥roh¾â~¼/0�«âFÜËR«ùS`×—ôÂR¥Éžn²ø9b·Ýò^2Wwu¾n·óžÈn<å ƒòî2Èö¦lIívöiܶ8N³Á™\É×r[ï‡pP.÷+Wj}½ºó;åúëÍ768N›U¨÷“p>ŠYÖçü û;$œÔÕw}SÔŸåOC¿³”¯£l¯ …´N?ë°Ã²Š1ÍØÍÚ©L+Ô-dÛÐïjUzÝÓ}§²ô’*'œÔ}¼¯e[x?߲ƪT_R«{uµ]lÊU9CÃ�@ªCÀ×ûPp¬hý­ =ü›¬áN‚ŸÃ-­%àû»Uߊ—‡î¼sª«(›µ¡€…V& ŸêKϳ¥ð>…,«'•Gÿ;ÌFp ÿÑÇ«”+5¤"Ÿ†p 'Uä«oª´a}ó­ã|½Ð¸Jƒ¾óRiЧÂ:¨F #¡FrÁä‹›·×Ò<�éZº›”¥&6·-;Ôõ&v,»B’ZQ«òoûÛBvOïÓÙÖÐÝ~ûíÓËᵂŽCÒôO—»…äM]*~øá梋.*d÷’íS®{ejÁ=J�bÎE`º­Êì«Vj¾äÆšòmkè:�NÃm <ßC£à~{÷N�\§ù/<ç ï^®«k:µš^ËQÂi¾§ç‹ð>qYV÷´›ç ^öìÒÅìbÇð ¾±±š†Þq¸_q1-W>4fߟ}Ôl≩V¸áë¶´Ýg3bDxuU,÷ïÙÓ(ÿÁ¤Vá¾1�ƒûäš�ú³ì $«w�BÒ \ÕwG0Å©2‹*aóü¦öu×­2øÖû Ë[×ü9*ŽækM�ïœrºÿn{ÒØåÔSÍŸìøßáÖtùÎÇ6@�ò øþ  0ëoé*;Þ²K[Æ#Ö̳þ¤U/Póþ;”�ÛWM}¿éãVñ2ŠÊ¤®Ì…Nð?öVó ‰±ožçc:+òU°Pë\ûQi0— ë@�Úˆ´ï»?™o^þt�éÝ©¹9t«ÒuYÛ·„ÒE) î§ ¶NTà¶¡©¾ç¨ïq}ìø‰?üpª[ku!>uêT£Z5ÁÔ¡C‡Tðúì³Ï6þ‘ܧ®yuçý¬¯:œÔEöΞZ±áý´ì3÷íWÈ:Ÿ×ûà¶‹ $|mÇ-4i mÖõZÛ¶rÉ•J�÷àuKqOål)îó æ¡Y衸¶å:¦”îÁ<ÕâþMÛúVÝcê5ë›oŒkñ´–ý­¶Žmy£ —:Ü2†ýÎéT@ï4Áü—â³~¿«EîÆ}û/›s^¿×¶¿Ó&zÒ÷“¦ûz`Èy¢nP% Çï;]êE;¶d8©õÙËžÓõíÆÛ�_ãLÿÙ3†w}Óªè 1}Ÿ™Olëwuƒ¯WWûþÛzƒ Œ‚Ûn¼±Ñq$@�xt´ÿU±+8ÔÏ"[!ß 519Ôûž¾Ïoµ \Ò8Ó n+˜l=­í�Û·7¾ßô ­xÙÐ�¸¼kZheÒà1úÍ®LÜ^×¼~§ýìÊ+͇¶wÃpR×ç‡çéuIûGQ‘/x¿Ã¿¿Âyªk¹†uå©�í®Ò`Öo3ûÌ÷Û¨T•}C£ø®_H™òUÔçæÀáÃÍnvxAÝ3 �T�ÈÓ÷¾þ¥ùÕ½?Œùú’ R_Ø€¤ºRî˜t²?ÌÕ­tÔéÚk¯5z•3)hzÖYg¥^êrú#;Þ—öÁ­Öo`ÿQÒ-OC…æSÝyëÕ�T*s—§¾öÁè›o¾™ ÀêÞ~ev.[¶Ì¨[s�õ­nÏ\íh[ù)/zåê Ý�ÓMK�ww7�úž†Çw×É5Uu�^H*¥{!ׯæ}|-¦Ã ª¹|µ�w]4¶¨üÓôz´BŸk^-±}݇ûZ2øÎ¡®`Ãi–}°¢ Kù†dãg.¸½Væ{uíÚà¢Äá~5¸1=ÁøO>1z›v<Øüd—]Š=¬¤ûkìõ¿ØÞ÷Û ~áï’B/l Uè1Q–õ~׃ð`R÷�›sLpUÞùÅöwj0©õÖl[áÀ×,¸_¹æ}'ÚJŸªTбM›t6ܺô ;£  4$)0¬ójáæR{Ý�=Ã̸íù¦ 6\sÒIæÈK/Í F�ÓX˜�¾ürê¥õ Lï¹õÖfÏ¡C ê¦=x.æ@¢Peæîö7~8Ø©@³~Ï*ðì’†ê Z=c¨ÒQ°•µÛ?×ß__ ´œe]þ4�ª2iðœ¾yý&yßvÝýÈ‹/š‡ŸÞ,°CÆøÒÙ¶§=ýû0_ ûUº"_¹ ó™Ôw•©4Xß÷Ç!Pk¾ÆMÿÏÞ}ÀIUžýÿ¿–í»´-ÀÒ¤QPDÄn0–¨±a‰�5–˜è“Xb‹ã£MшQöc¢F±!V¥wé}Ù]–í»ÿûºgÎ03;ÛÏìNùÜy�œ:gÎyϺ̞ï]bí�ûq-˜~ñËý_’ôäÿš·]n>¥¯ôÊJuÞ‹9¸$�––Ö¤ñ£]zÛˆ:�†Í#Ì^±R¢å3�5÷Öøù Lû÷âÐ×À{4, cއIMKÇpÿñáüÏßÕ´fhLébZ½ü˜Ð!Ô˜ÉÁÇêz]c’†:6š·å6±õi¨{�„Ï+ÔuÅã6mq{žéÁEFRyð•WdêþÐÅfk]ŸÛÿ-k«íP%8luL}Û"éß=í)A[²,ß°ÁwÉNëèS�<Ò·-T‹œqÇ×êªÜ÷‚F.èƒî¥¦³›EÃö©7Ý$÷<ÿ¼Ôõ¿ßêM›äoo½e§3Ž:Jn¿äÛª:ø8Ö@ZG »i¥Lk¥" ¦ý‡dÐʬ]Ìß ZÉÉé}E+ÈiÑニž7¸DZÅK7*“ߣ³þÑœ9ò�i¥ºÓôƳÇtsÞP%q­yzC˜DbE¾p:–ášSiðKKK¥Ápý„q^ˆDׂé½e�Ý ëÍ–”×Þ‰\ €á ˜—¬»ç=°G�Zr~ô>àq÷�DRMO¡J¹i•ؘâßå›ÿñiuœ×ÿ˜x[Ö® [Zø¼Z*èÎë�0­LïúŸÿ‰¸–�/ü±üÝ ·+¥±½.4õ~3L¥ÊH*Ú·0­×¦At@0¢{ï–Ž/NƒŸ˜ÖÏÇŽ-ú3ùÎ_ÈÓACß�ëyÛ´ûdÞ°™1¾}а£…+½L«íž'80бuS¶…h ¡-&tŒQJ €&|^�¦­±¦ãŸë˜k}u,樂£"©hÀü�wÞ ¸¤/üÆ“Ö @úù—–Ž/|>·×µ"Íe§œb';ócÓJì‹E‹ì¿ouuWê;þêç@IDAT\CqI‰Ü`†*z÷Ojq«pçœÌ@/*@ÞnºòÖ²vóf߉4�ÖâL;-ªCÓ¡ïH«xéFeRP n:ï¼»ðÖÓGbE¾H1l.?•å¨4èÁ±(üL0ï±¾{"˜®O‡} €´P Ôø ñþ壅¤ayùÁýû‡<ïï¿.:æU¨Ï1ä ±Q»qÕ°*¸«=ƒµ1e« ‚K4uÝV4–w𽸹îF7Ç‘üyix–Å-åÿhÆ->á°Ã>rÃ7Ç….ØNZÏJK ßž=[´}ÿ¢ã>Þrá…rîqÇI}!ïÑ×]W«»Nÿó´Õ²Žuªü~òd;ve¨}�ÙÖÐ8��9‡›ÇŒ1csúw�ªçÖ±"u: [7™·|¹””•¼¥V”Ð)ZŠ¿˜4ÉN:~ùÒuëäKSë+TÏZ¸Ptìðà²lýzÛrú”#ŽÞÅ: €@˜BÈú÷‚þ{ä_1µ—·kn§å´^–þû¥¿ëƒÿ¾Ð}¡Îi/ݨLª÷ÚÒ2Å µñoÓ{HCûÕ÷¯%×Ð’Š|‘bØÜû§Ò`m9* Ö6a ´žÀgËóå‡M{ex�örì�ÚC ¶ôJ¦[*Èë@¨G€ÓõàDÐ.í¶MCÌàõ‹Ö¬‘¾ùF&¹ü�^ƒäàGþ­õꢩª®®Õ²[�ím‚”H,�ÌØÝÁ]Ú…«‹ôpÞ[^Ú‚8TёєßC¶  ýªïws=† W¯®u‰¶_|òɵ¶ûoÐñ ·4²"ŠÿëZcYdm­î´¼rÞs¹ ,¯=óLg5êçZ¡c܈2Ýt_í_´;ïÉæ÷i¨ßÇG›VÖÑZt|öáýúÙé—§ž*Zê¡W_•7gάuKúYL×ba v�!Æ‚ÖÐ:~´Åf_‹i¿ãµW–-æ{a¨Ó¡Îi/ݨLZפ!³VÔÒÞ|§Ö >ÏL³,ýÛÊ¿èXÔ¯ú©œüñþ›k-GbE¾pÖÃ* Ri0 ?Vœš)ðاdÊ×ù^ý»Ÿô‘ßžt€oÝ�Ï¿Ìnœ‰s € €@-‚éZ$¹A[hN2ãs†*¿öšíÛjW³·i‹¼à¢-Õæ,]¼9`}ú·ßІ‘Á%R[L÷1WpYeº1޶ÒÖŸW¨–.jX×ø€ÑæÛ×ë¶¡VbYúã�µ.ýŒ£Žªµ-xƒ¶X­k¬øàcÛb]+ê—i¦‚Φ¢N,•‰!‚æÏ½ãJk@\"y|éàkmh=/;[îºôR߃zÿã£ñw´ÿõ³ŒD«@¨ï*6˜Þ²%à–|Á´·Kog§Ž3½ÍÛõ·³Mç¡Î«ÛCýýªb–ë_¢­¢ì9Ç+«Íßs:-|öYùàá‡å¹[o•_žvšÿmù–ÿôòË’_Tä[µàTä Þ§•»8°ÙSðùâiÝ©4|ÏÎw²P?›±PiP+ þ¿[n‘Ï\~~Ì1Á·o×õ犂±'à_é̹»†zìpŽ ÷üÉ™�Ï힘¹QG'sµL»ÊÉÉ@ ˜ôˆäµ‹N:)äåéCú“n¼Ñvò€fl=hPÈW=þÖ[µjî;j�õÄÛo;«óºÎpP¬„jÍû‘ót‰éR6šJ]¾­õyuËÊ Ù¥ ¢E“e[]«Û†{öî­ÕÂÞ[pË›àû­2]¿øÑGÁ›ízBÈ­­¿ñ„C­õ¦úûçbÓµe,=Óa‚‹VÐÏöû Öðu=, ~}4­kë±P>RLï!@�ÖÕ²Y{XZL;½Í8sçJµeõs¼Ñßõ]Í÷ÈP¥­+^†º¦ÖÜöë³Ïi£¡´†Ó •x©È×�ƒ›û©4H¥A7ž84_  $pا½¥UæY‡»É4Átó?^‰ €@ƒ¡úVuÖàI8 U6LN ïÖycm¥|Á=÷ȽÏ?/ÚU^CE»Ò{~Ú4ùtþü�‡žoÆŸ õði¦éRîjS‹?ø=t|©K¦L‘ù¦{¹à2¨wo9õÈ#ƒ7Gĺv \4¸»á¯•ïV® Þ±ëmýyi(–bìß'þóŸ:ÆsWa¡lÚ±#b][óÂÜ6Ôqâ;ddÔº…—ê�õ@ýïú—=$3êø½àîŸyµ.­ÑŽ7Áô`ó{%¸èƒÚóï¾[¦š 2…ÅÅÁ»£n½�é¹"¸òÌns�ϼ÷^­ N÷’‘x“ú;µ´¼¼I—¶« @®2ÿÖ�‘®'éß³g“ÎÅÁ €îè¸Åé©©'kJ‹i†#¸+o ¥�n¬NlVÚºâeðõ´öºórëE…|[íÎ;Ôß]þÇKE>ÿ{÷2•©4îŸ1Î�‘#@uèÈù,¸h�eË–Éo¼!?uÑ8räH9÷ÜseðàÁ­p¼E< „j1ª»–x2‰ä{}ðW¿’ïLëè�æ¡}pÑÏíéÿþW^þøc`ÜìÕK4îߣ‡h ¤cvj ý¥iq·pÕ*ûò£Ìï–ãF�>•hHöÛsΑß=ñD­}Í�+ÇÞpƒŒ3Ýéjh¢-¶µß¶ Î ÿ÷üóEÇ�Ä¢÷ •3‚æWlØ ?»í6QŸ!`CúæáÐÞÒRÙmÂÔ­ÆqÓÎ�rœ È´{3ÿ¢-¿]¾Ü“l«c¼Þÿg>¯AÝ&%&Ê% Œpr³ Ÿ×A(ZqÁ¿T›ŸI­È A¢v¨4·›.µËeuÒ®¾µ²ÅÓ7ßìÿ²¸]vÛpXß¾òÍ’%ž�¼þºíþQǙ֖HFëg1ßü̾ÿõ×¾î°õÁs‰kÚ¿ÜôØc6ÔPÚM§v žÛ©“ÿ!­²¬ÿÍþ~òd¹üÁk½Ÿ·¼ô’üõ_ÿ’“ÆŒ w{š1.u¼ðŽê°ús§¿·Ö˜éÒSN‘É'œPë<‘²A[æ¬ûàƒ€Ëy2DÏ‘Ü�÷ÌモËþô'É1?+tíj»fÕŸ­8¡cij­)ô³Ñ߯ߚ N?˜ŸÉP¡´B 2ÿ¶Q@ÚF »jÁ¿…´þM²zófßÅè÷‚Tów„ �u\a^D‹~ß®ÜZW7Þz¼V¼ÔJŽÁCÃ8eûío}ï¥ÇkEÙËÿïÿB¶‘\QV¯½®ræ„ òâ‡Öº'ýÛåO=%ÿ5ß…ê ö�Š|ËÍß5þÅ©ÈwÕgØï@33ýw³\�€SiP[ÿ;%Z+ ê—Úå{c‹~_ÓÊÛ¡¾ŸQi°±Š‡Ñ$@0MŸ׊-dºÏ=Å<$]eB£Ù³gûÎ÷�y¨§¡õõ×_/Gq„o; ´T`  ‹z˜àr³ß…Ãë˸¥ïÅë[. cM?n‚éËÍCþ½æáK¨¢a’�oõ[ó{E»ÀÕ`3¸œmÆ�šúÎ;²Ú„7ÁE[¹¾9sfðæZë£ �“ÇŽ­µ=R6Œìßß>�Ñ0?TÑ®¨ëëŽZäÓzüƒ¯¼êtµ¶=e‚éࢭ#šLë9ÚúóÒ‡[ÁÁ´^—>€ÔÀS§PeÃöí¡6Çå6· µâEp0­•´Å­N¡*e8ð§™^Þ˜1ÃYµs surŠßmLëûŸ`*4\ÖYò73¼@¨¢§ÿ=kV¨]Û–Fx·ýǚ ¦ƒêë …êZ2àFÛpe£÷¿q} ©SKz£8Ðôr¡‚( €m#ÐÝTöò¦5¤r*¼êùwß­S{ääÈ�Û¶Ù‹�TqS7†ê¡Élþ/Ü/Û¢2©so��ëwµ?^v™œnÆœ®H«C½`¾#ü�y~ª´FE¾h0 eÓ’mT¬­G¥ÁÚ&lA ‚ÿÝÑ{Ò[â¥LÇË'Í}"€€ÐÖ«‡˜‡�7šà)Ñ´šûÔtѤE»Vþꫯ¤ØtMyƒi©xì±ÇÚíünüùÏ–x@V›ZìǼüñ�tã´œ#LG˜.½ß2c©^j>³àM}Kí^U�1¢ÖKµöýÃ×\#—Ü¿mÉVë€6h×κúêŽjûÝ7_x¡|`Æ•ÖÀ¤©%’BÕ¶þ¼Ž4?CÚ*º©¡S$6õówûx· ¯3Á­vׯ C•PhêqÚjÿ Ó@p0|޶þ£ô&Óƒ>´~üßÿnrWÑνø·xq¶EÒ\LJÔ{Ô Dumm6Øôì©eƒKÝõk«žû¯¼Òþ|Fê½r] €@¬ hÐ\ü[Pj�þE×�`Úÿ8ç˜úZLë1á¬xÙV•I�{oì\+Òžkžÿh÷ÝÁåá×^“ŸŽr,j=6Üù¢Å0Ø­%ëT Ô£Ò` k Ð:¡âq·‡cŒéÖù,yˆ  ¤µëî›Mצ§Ÿ~º$™î¯´Timä… åO¦¥äL—Võ<¤Œ ÛáR¢@@[HëÏ”v!¯!µó3—·—¨!Ä{¦«º+Íïˆà±ÞšŠR_ËjÛíõ»ï¶]¯6弚VøoÞ{¯ íÓ§)/k“c;™VÏÏ›n»ÇÚä÷�´Pµ­?¯MEíF¾)¥X»G7Ý/S<nj÷™�›n.ý[/5ä¬ô5ú;¦®®!:Gkí×`ü×?ÿ¹|ò裶Køæ¼o¤Óúûýð~7}ðÁ͹õV{ÍF‚iý~ÝTšÓÊ@�¶ÐÓõ•à`º¡ï õµ˜Ö÷q*^êðÍ)ÑRQ¶¡{»Ù aÊ@{кï…ê}¹Vä»é¼óšÔmsð #ýûRðõ†sÝ©4Xß{Pi°>ö!€Ñ!@0ŸW‰. hËiíÖû¦›n’ .¸@Ú›q9µhËéf콇zHž}öYÙcÆê¤ €@| 胖?\|±|þøã¢ã8k«ggL·úD´[= 0õÅ»¦¢Ë/O;­¾Ãex¿~ò©©°p›y/ œë+ƒÍ˜ÖÚÝÜ'�Z* j%ë¦ý[N+ÃO2O;òÈzÿûjÊy9"S@ÿ ½è¢‹ä¹çžó]àdÓƒG¼”ÈR/Ÿ÷‰m" �RóàôJ3¦ž†ÓO=õ”¬7¡“>L.4¡Ð ¦Û& «¯3üÜ&×È›"€@äh€¤-~ÃY4ÐÑ)–‹¶Õo =|‹ƒ¶ú¼ô!–†K:Qš'à¶¡žO[:é4fÈ�/*Ü¿K¼€ ¡¹¶Èo¨—‡¼/m‚€~ŸÕÀY'  €@ô hàöã?ÿÙè8ç˜cD'7ŠÛ/Û¢2iK*BêwíŪ¥ÅÍŠ|maØÒûwãõTtC‘s €@4üÑ §4Ä<;X¼x±l†�:§ŽÞ;Zû^ôïK‘ÀQ¥5+1ý„¸v)Ó®Qr"ˆVÛÚÑÔLÔ.½³²²lKéeË–ÙpZÇ™~ÿý÷mH­áô˜1c¢õ6¹n@@@@ ^�¶ªxYïEEáN*òEá‡æÒ%SiÐ%HNƒ@h�…1¦ãñSçž@ ¤@²i 9ÉtßtË-·Èa‡æ;¦Òt…8{öl¹ÿþûeÆŒ¾í, € € € € € €4NÀµ`ºcZíÆ×™©u�5Ò¸Ëã(@ u’ÌXˆG}´Üzë­2qâDß›W™±.\(ÚņŽýP`Æï£ € € € € € €± ªÃîÀ޽[~—®Ó—Õ=àj.8*6céXÔ@@@@@@�†j7snø5!�8ûЮÒ''M¾ZU ˜ù‡t y@�hh×®�ôïß_~÷»ßIÏž=åå—_–íÛ·Ûq§µµô‹/¾(›7owú ƒІ[â@@@@@@�6p-˜Ö;8¬oG;µÙÝðÆ €€‹NwéÒE®¹æéÓ§�<þøã²råJNWTTÈ'Ÿ|"»wï–믿Þvÿíâ[s*@@@@@@ ÕBôå]ãr_Þ®uåÝj*¼ Њ æ7qJJŠœvÚirûí·Ëa‡æ{÷ÊÊJ™7ožÜ}÷Ýò /0î´O†@@@@@@ P€`:Ѓ5@ ¤@RR’L˜0Aî¼óN9ùä“EÇ¡ÖRUU%k×®•‡~ØN:5@@@@@ˆ&�ö©žÜùæ¤v ’œ¢µs@3æÓÍ@ã% ŸFëxÒwÜq‡üò—¿”œœ Q]]m[K¿úê«òÇ?þQæÏŸŸ@Ü5 € € € € €D¥À/Æ÷¸îË&ô�DN»Y\cÚÍ ã\ €@$ è¸Ó½zõ’ßüæ72`À™:uª¬ZµÊ^ªŽ;ýÙgŸÉŽ;䪫®’“N:IRSS#ñ6¸&@@@@@@À'pÓÉÈÀné²`ý^Ö#S~~XWß>·¦Ý’ä< 7:îtzzºœyæ™Ò­[7ùÛßþ&sæÌ±÷¯]{/^¼Xî½÷^Y¶l™\xá…Ò£G`-£¸�âFk è¸äÚ-<@@@@@�H8}TÑ)\…®¼Ã%Ëy@ æ4`?~¼í¾[Cj «µh×ÞÛ·o—üãr÷ÝwË÷ßóÜ`ý?üðƒœ~úéÒ¿9묳dÅŠõ¿€½ € € € € €ĘÁtŒ} Ü´®€Ž;=dÈùÃþ`»÷Ön¾µÔÔÔHyy¹LŸ>ÝŽIýÁˆvõM‰OýùX¸p¡½ùyóæÉí·ßŸÜ5 € € € € €@Ü LÇíGÏ�#€€[:îtnn®\~ùår×]wÉÁì;µvݬ-¦uûŸÿügÙ´i“o ñ! œPÚ¹ãùóç;‹Ì@@@@@ˆ ºŒ‹�™›D�p è¸ÓÉÉÉrüñÇK×®]å‰'ž°­¥5”Ô®½·mÛf»ö^¾|¹\wÝu2jÔ¨p_ç�­¸\ôg‚‚ € € € € ŸŸ}ö™,[¶LFŽ)ãÆ�‹‚é¸ù¨¹Qh íÚ[ÿ!ѱ¥‡.¯¾úªlÞ¼Ù×µ÷§Ÿ~*[¶l‘+®¸BN>ùdÉÈÈh�Ëâ=ÚP@+-íê�‚ € € € € �<òˆüå/ñÝøwÜa3߆^¨ÝŒ+†o–[CZC@[ÈæååÉÕW_mjÿ®½«ªªdÉ’%rÏ=÷ȃ>(k×®m�Kâ=ÚP€ÓmˆÏ[#€ € € € €$ Á“O>pEO?ýtÀz,¯LÇò§Ë½!€@› h+Ù””9á„ä¾ûî“ÓN;MRSSíõh7ÎùùùòòË/Ëí·ß._ýu›]'oÜ:´šngÞ@@@@ˆd�òòr)-- ¸Ä={ö¬Çò Át,ºÜ´¹€ví=bĹóÎ;妛n’Þ½{ÛkÒ®œuü鯾úJn¹å;þôÎ�;Ûüz¹€ðÐj:<®œ@@@@@ z\ ¦ç®-”¿~²AþóÝŽèàJ@�0 h(Ù¥K¹ì²Ëlëé1cÆøÞQ»íX·n�<ôÐC¢ãH,Z´È·�…Ø Åtì|–Ü  € € € € €@ó’š÷²Ú¯ú×¼írýËË};f-Ï—GÎä[gˆg &“““e„ Ò½{wyþùçå¿ÿý¯ˆ¶žÖî;>úè#Ù¸q£\qÅrâ‰'JFFF<“ÅÔ½LÇÔÇÉÍ € € € € €4CÀµÓÏ}¾9àí_›³M6ï) ØÆ  ïÚµ÷À�åøƒíÞ{ðàÁ>’ÊÊJÛbú®»î’x@V¯^íÛÇBt Еwt~\= € € € € €@Ë\ ¦ K«j]MqYímµb gRfffÊÏ~ö3™2eŠwÜq¢�µ–êêjÙ³g�¼úê«rÛm·É¬Y³ì¶8#йÛ%˜Ž¹�”B@@@@h¢€kÁtß—Ã@�¸HJJ’Ñ£GÛpúÆo”^½zYíÚ»¢¢BæÎ�+·Ür‹<ú裲iÓ¦¸÷ŠfºòŽæO�kG@@@@pC€`Ú EÎ�4S@[ÒæååÉUW]eê±cÇúÎTUU%›7o–©S§Ê­·Þ*sæÌñíc!º¦£ëóâj@@@@@�xøfM�L�¹Q>]š–ÛO ËY9) €@£4´LNN–£Ž:Jzöì)¯¼òмýöÛ²sçNÑÖÓåååòùçŸËúõëå¼óΓ³Î:KºuëÖèós`Û „êÊ[?[  € € € € €‘ ðÜç›å¶·Vû.åúzË­§ôõ­»±@‹i79 à‚€Ž3Ý¿¹ùæ›åþûï—‘#GúΪ­§×­['�<òˆ{zþüù¾},D¾@¨Ó:ž8@@@@@�HøÇg�CŠ>>}£TV¹ÛÀŠÓ‘ðIs  €€W@ÌÔÔT9ñÄ¥wïÞòüóÏË´iÓ¤°°Ð×zzÆŒ6¤¾à‚ äŒ3Î�.]ºàá¡ZLLGø‡Æå!€ € € € €q$�¿¯2àn«M¯ŸÅåUÒ)ݽ8™ÓĬ €‘! ­§‡*wÞy§Ü}÷Ý2dÈß…iëé5kÖȃ>hÇž^°`�o ‘)ªÅ4]yGægÅU!€ € € € €„GÀ½ˆ;<×ÇY@�¸ÐV¶íÛ··­¢µ‹ïgžyF>ùäÙ·oŸhk[{úÓO?µ­§'Ož,§Ÿ~ºäææÆ­W$ß8Át$:\ € € € € €€éеV1�¦]-´˜v•““!€î $%%Ùñ¦ï¹ç¹ãŽ;dàÀ�¾7ÑÖÓ«W¯–?ýéO´žö©DÞÂØ±c.jðàÁ’��°�@@@@@bY€Ó±üéro 3Úz:++KÎ=÷\ÑPó©§žkº´´Ô¶ž.++“éÓ§ËÚµkEÇžÖÖÓŒ=9ÿ½÷Þk[¸Ïž=[FŒa»g�œ«ãJ@@@@@â] Dƒiq¹Á´¸L‡jʪÉw¼¨Ü? Ðm=}È!‡È”)Sä½÷Þ“çž{NV­ZeOé´žÖ±§¿üòK¹öÚkeôèÑ-y;^ë’@×®]åÙgŸuélœ@@@@@—Z!™sWÞ¡îÀe$N‡Ä™€ÓzúüóÏ—ÿû¿ÿ“I“&IZZšUб§µõ´¶¦¾é¦›ä‰'ž�­[·Æ™·‹ € € € € €@ä ¤¤¤HbbbÀ…eff¬ÇòJ˜ƒéX¦ãÞ@�¶H0]R$''ûZO‡{Z»õ~ä‘Gäæ›o–9sæ´ÝÅòÎ € € € € € `CéK/½4@ââ‹/Xo«•©Ýà¸Æåμ]ëÊ»­�x_@ žœÖÓç�wž >\ž~úi;ÖtII‰Ô˜1ÊËËEÇ5^½zµœy晢ÇõêÕ+žÉ¸w@@@@@ÚLàÎ;ï”#FÈÒ¥Kåàƒ–SO=µÍ®¥µß˜`ºµÅy?@Àe§õ´þvÿý÷ËÑG-Ï<óŒ,_¾Ü¾“Ž=½qãF™:uªÌ�;W®¾új9ꨣDÇ«¦ € € € € € €@ë œuÖY­û†�x7ÓQk­bÚ¿¹ZH%\åäd €@Û hëéÎ�;ËÙgŸ-C‡•çŸ^>üðC)**òµžÖ.½µõ´ÖÀºè¢‹¤ÿþmwÁ¼3 € € € € €Ä�€kcL·O ¨[Ó“];}Ü| Ü( ÐRm ­Ý€ÜsÏ=2eÊÛÅ·sNm=½cÇyñÅåÿ÷åÝwß•}ûö9»™#€ € € € € €@ „h0íòÓ"®%Ç™ð�}hW陕°�@�ÖÐÖÓ™™™rÊ)§È#�<"—\r‰äääØ7×±§+**dÁ‚rÛm·ÉþðY¼xqë\ï‚ € € € € €q)àZWÞžgƒè/WHŸœ4Ñu  €@Û hëéAƒÉí·ß.'N”Ç{L¾ûî;{QÕÕÕRXX(ï¼óŽ,Y²D&Ožl»øvì¶½rÞ@@@@@ZK êÆ˜>zP–èDAˆm=�šš*Ç{¬SúÍ7ß”·ÞzK6oÞl/²²²RV¬X!÷߿̜9S®»î:9ôÐC#ç¸@@@@@ˆz׺òŽz nˆq�ÄÄDéÛ·¯Üpà òÐCÉ‘G)ZkÑÖÓeeeòÙgŸÙý?ü°lذ!ÆE¸=@@@@@ð„”i‚i~Ö@�8H0}q¤¤¤È¸qãìØÓ:Æt¿~ý|UUU²qãFyòÉ'åÆo”�>úHJKK}ûY@@@@@@�ØhŸšXë¦Ò’ko«uP6L7‹C@�XÐÖÓyyyr饗ڀú´ÓN“ŒŒ {{555R^^.óæÍ“ßÿþ÷rç�wʲeËbåÖ¹@@@@@øÅøî[.×]:¤¹L'¼+ €q# ­§“““eÔ¨Q2eÊ™>}º<õÔS²xñbk ­§÷ìÙ#ÿú׿äûï¿—É“'ËOúSÉÉɉ#n@@@@@�x¸rbOé››&s×ÊÀnrî˜n®ß6Á´ë¤œˆ.gºcÇŽ¢­¦‡ &¯½öš¼ûcÇÑÖÓ•••²|ùr¹ï¾ûäÓO?•k¯½VÆŒ]7!W[RR"ééér5\ € € € € €ûNž#:…«Ð•w¸d9/ eIII2pà@¹å–[äᇖ±cÇúî ººZÊÊÊdÖ¬Yrà 7È< ëÖ­óíg¡~�ÂÂBÛmú�!Cäˆ#ްÁý¯`/ € € € € €±%@0[Ÿ'wƒ´H@[O§¦¦Ê„ ä/ù‹ÜvÛmÒ·o_ß9µ{ïÍ›7Ë3Ï<#¿ùÍoäí·ß–½{÷úö³Zàþûï—3fØ�[¶l‘ßýîwRZZú`¶"€ € € € € ƒÓ1ø¡rK €@K¥{÷îrÙe—É£�>*gœq†´oßÞžV»÷®¨¨�… Ê­·Þ*¿ÿýïíÔ-}ÏX~ý¢E‹nO»ô^µjUÀ6V@@@@@ˆeƘŽåO—{CZ ��� ÉÉÉ2jÔ(™2eŠmñûä“OÊ?ü`ϪÝ{ˇ~(K—.•³Î:ËN={ölÁ»6ý¥Úòøã�?–�>úHúõë'?ûÙÏäÀlú‰Âø µ .ðS@@@@@âOà믿–eË–ÉA$‡zhÜLÇÍGÍ�"€ÍÐî½µµô¤I“dذaòæ›oÊþóÛ¥·ž±²²RÖ®]+ýë_eöìÙrùå—Ëĉ%--­yoØÄWÍœ9S¦N�jÿ×kݽ{·\{íµ¶ÅwO¶Ã ¦ÃFˉ@@@@@�¨xì±Ç䡇ò]óÝwß-ÿó?ÿã[�å‚éXþt¹7@ÀE�¤¤$Ûù·¿ý­}ôÑ¢­§¿øâ )//mý«óo¿ýV–/_.Çwœ\qÅ2|øp¯ ô©4ˆÞµk— Èõˆ÷ßߎ‹}á…JzzzèµòV‚éVçí@@�ÐþrjESDÚ™¹YÞQÖN’Jj¤²Z¤ÊPi¦*ï²®{¦ï\¤Ú·Í³¬ë¾IÏïÝ_#5v»YµÛt®Çmj?\ªûv0kZdæúv²õë­æhOq:õÙRP&Ý;¥Ú�Nç?N@žõÑy;ïÆvfE×uU¿ûêv]Oônog6è1º]ç‰f`9ÏÜl3Ëz\¢Ù©Û=s³ìÝ–”èÙ—¤/6%5)A’Ì�º]·éܳÇîæÿ@@ˆhí‰ôïÿ{À5ê³v‚éV@ðUUU²gÏyç�wl—ßçœsŽŸ:///l|}ûö•Þ½{Ë–-[ì{hPýÆoˆv)®­¼#¡LG§À5 € €@< ”›p·Ô¤º¥U"ef*Ië*5�+Ljêç›`øÛ‚tÙ²±Úî+3‰­£¯ñÌ÷¯—{·ë¾rsœ®WØeÏñžeO°¬ÇhÀìÌ+LÊ«ëzŒÎ5t– | ?ý\W˶…u¥ËÉ"]ö¿ÃÓß›åïWîߥKN@�¬aµ ­íÜ„Ö)&ÄN6Éw²Î;dï¾”¤v’¢ëÞ¹]7Ǥ˜ýÎrªî3“gž`çÛRHu�}’Pm~–ª*deIºdçט]¤}² ÍM}ƒt³’fæÉ&d§ € € ,PQQa‡Èô߮ϴã¥Ðb:^>iîpQ 11Q²³³Eƒgƒúµ×^“÷Þ{OvìØaßE»÷^½zµ<üðÃvlê+¯¼RŽ:ê(ÑPÛírøá‡‹ž¿¬¬L.\h[o¯\¹R^|ñE{�º¿­ Át[¼? €D’€µÅ&¥ÝW)¾ÉY/1Áï>gŸY.1˺­Äë™û¯{‚gݧ´DëqÚ:8  ú•È ý[î\e–W™p‘•æשÔ~¤æ \¥ãq"ãÌä-w¬3 ëBW,ÐÞé& Ö�:MÃjóγž ºÍ»žnqÝžî]Ïð®g˜u]Ö¹îÏô.{¶{Ö ¿�O‚9 € €@´˜¯·î•/V푯VHŸœt9ç°®î�˜3!€Dœ€†­Ú½÷àÁƒå¶Ûn“ã�?ÞvAòõ×_ÛpX»$Ñî½çÌ™c[TŸxâ‰rÙe—ÉСC]½ É�3]‡k­²üü|Y¿~½è{ëuhxÞ¹sg{�®¾iO¦c_½F  €  $UÔÈ^ï5Áß^³\dæÅf]·ë|¯ �ußÂÝ5ÒÝŒ¦âÙfBfï~]wÂgmQLA Ö´r„çç^ïÔ¿¦„ÿrËRÌŸTkh�içf=9AÚ{—·–ˆœmö™ÖÜíÍ1º½½î·ëû—;x·t·ìóàÕ € €@c⽓ùêéNymÎ6¹ñµ¾“}±r�üù¿êȾ=, €Ä’€†®©©©2~üxëÏ/½ô’¬ZµÊÞ¦Ó½÷[o½% ,°­¬Ï8ã éÖ­›k Nk·Ý7n”gŸ}VŠŠŠl·âÓ¦M“ÜÜ\¹æšk¤k×¶«0ï_6\û 9 €4Y@CàÂò)4¡q�w®Ë(š†žvn×÷/;Á³§ËÚ™âŽ€ŽŸ¬ã-Û¹©è¹¯¬R:e¦Ømv fÝo¶ï_öŽÙìÝnvùöë²~ÏÔÉìö.{çær}ûõÒu¿þÏν;=›í>=D‹Ùm˶ü}Ò-+Ã.ĨfÅ·nº*×e—Ú.y÷Õèöàe³AƒZÏ>Ïþj»ÍoÙ ûõ¸jóÎ~»ì]/(.—ŒÔ$»¯J·™IçñZô¿oSXö˜ÿ¶÷ÿe‘/·ïßÓÐ’¶îî`‚ë&¸ÖICìŽvÙ™‹Y7ûMGXzœîÓ©SŠwÙÌ5,§ € € P—€kÁô _xÆötÞè�¹ÛäæI}¤{çTgs@�ÐpXÃß‹.ºHÆŒ#¯¼òŠh0¬-™õ“vï­aõC=äëÞ[Ãl·º÷îØ±£œuÖY²nÝ:;ƵRë{jPÞ¿9ÿüó%9Ù<5iƒB0Ýè¼% €1$ Á°O&€Ê÷Îu}eA�Óv�Ù^hdÝo'ï²ÒÚ­u< ~uü`S8ÉÌó‹Ê¤GNûýÛL×ʺÝ3±g®ËIº]ç¦òe¢³lÖõ|ÎþD³O�Ó¹c×Í~=&Ñ{¬†Ïz¼@;at<~­qÏNH­av•[¼ÒôNä„ÖUfÙ³MClϲÎ+Íqμ²Êl7·Îí²÷žõýÛ+¼û+Í áºO×+Ìòæ]Å’Õ!Õüý±[4æN—ø;J�O-0äv¶Ö7×nÇmPmÂëN&¼î¤s¿eý�4¸S‚t6!¶nÏòÎu]ƒq  € €@l ¸L™Z¾Áeýs“°ŽÄ´€°þ6Lîºë.9餓ä±Ç“¹sçÚûvº÷þæ›odéÒ¥âv÷Þ}ûö•K.¹Ävé={ölûžÛ·o—W_}UòòòìûµÅ@0Ýê¼' €‘'`².É/«1á²Èn�—y‚fg®³³_ƒgÝ®­œc-\Ö°6%9QR’ÌdúNµs]o·»wYÃe{œ™;ËÉæ5ɦR¤oÃgóz'„ö¬·³­Š#ï'€+ —€­`~®li›º¨µnMCr ­íä °+*«ìz¹Ý¾¹Ü·Ý»­¢JôÝ^î,›y™]ß¿=Ãoý}URR#Ú�¸_ûúZ>¡6h¨­µ¶qÑÀº³ש޹YÏòn×y¶ÙžåÝoê‡P@@¢DÀµ`:Jî—ËDh§{ï &øº÷ÖÔ¡º÷þî»ï|Ý{kxÜ’¢ðèÑ£m«mozÑ¢E¶µ¶†à/¼ð‚oZ[s·v!˜nmqÞ@�ÖÐVÊ»Mx¼«´Fv™¹†ÍÎ\·ëº�›pY—µ5s4mí›f‚äÔ”$ï<Ñ7OMN’T³/-Åì7s 3Ó’í²†ÏºM'gYÃdmILA Ö´;vçç?\÷ª­¶5À.ÓðZƒë ©Ô4"ÑVÝeåURêì+¯´ÇÙug»Ù¦ëÚʼ-‹joib¨­-¯5¨ÎÖÀÚؾe³-Çbë<'ͳO»§ € €´�€kÁ´©Z«˜ïà@�8Ðî½5lÖVÌcÇŽµÝ{ðÁÝ{¯^½Z~øaÛ½÷W\!fë˜ÕÍ-úž'œp‚ìÚµKüqÙ´i“yÀR-_|ñ…äääHVV– 0 ¹§oÖë¦›ÅÆ‹@huíÆv§ ™wxƒf]Þif�o3-µEó.oø¬at4t¦Aqš ”Ó͸¼é&F[mk˜½OƒjoXmç&È.)«� ;öJFZ’gŸÙ¯¿Ç4ÐÞgöéXà­Q´RÑ–}5fÒwkø=;˜ÖPkPÝÅ„ØvÙØ]Ó=ÁuW³M÷u5“ùULA@¢Và�;äê–ù®ÿêczÉ�§÷ó­»±@0í†"ç@hPÀ¿{ï!C†Èûï¿//¿ür­î½ß~ûmY°`�œuÖYræ™gJÏž=[»¹ÖP&Ó„Èf9Ó,ÛÀY·kð¬Ûì\·kàìé*;Þ>_î¢SÀé¾¼SfÓ{ÁÒP[í}¥ž¹ÿ²w[±ÎM˜]l‚l;7ëá.Eæ-ŠÌÐ k‹±µ[q ¨í”î]6v7^k�ÝÍLºO�£ € €‘&ðäÌM—4uæF¹þ„Þ’•á^œìÞ™.•@B hWÛÚjùâ‹/–Ã?Üvï=mÚ4Ûõ¶¾¢²²RÖ®]+ùË_ä³Ï>“Ë.»L&Nœ(M«¶¯a°ÐÚ�¸Ž7ýå—_Ú Ú¼y³¼ôÒK’››+Çw\è‹tyk¨\[ŠS@@ ¶@q¥'`ÞbBf š·š�YçžI̺¶|®‹´­‹04g¦'K{2{–5l¶Û�mvî �ñª­?5Þ"UÀ µ³Ú7>ÔÖ¨Xƒlmy­¡uÀT²ÛÞ’ýû4Wqº_YXˆ�nZVkHí™Dò‚–�õLžÜ†ë£â¼ € B`wqíJ…æ;Át,6!€D�€Ó½÷СCåÎ;ï´Ý{ÿýï—o¾ùÆvíítï=oÞ<Û½÷1Çc»ß9rd“nRá1cÆÈE]dÃé¥K—ÚîÃøáyñÅíxÓ‡rH“ÎÙœƒi1Ý5^ƒ ‹:>³v�ª¡óVíFÕÌu]ÃfgjËÎ:sûô3%K ›½Ëº®“†Í¾mf9™±—cñÇ”{B�(ÐÊ>NE ÓD¹¥¼²ZŠMP½·´\4°v& µ÷–”K‘ß6]¯ª®?dnÄ[Ö:D+W­Û[c§Z;ý6hËj ©�©»©¯ÝݬwÏ0“n7ë:>6@—€Ûß„¨w®OŠó"€4(àtï=~üxÑî½?øà¯X±Â¾VÇ….**²cCk˜|ÆgØ.¸8à€Ïí -´u\ë]»vÙ±­·nÝjÃom����mÃé¾}û:‡‡eN0VNŠ aùå5²Ù„Ì›MàìL:†§nÓZ·™!F[½´3as0wÌð„Ê2Rlè¬sOmÖ½û´+m  €@l ¤˜JE)R%ËL�)Ú½xÑ>Oˆ]d‚j ²u]lÏÜ,ïó,W׸ûèÖi�½¼ îó¦™Ö×T÷ðÖ=4¼6ë=u›�D:§^7æ³æ@ZC ’Ÿ›NHk—¿ßðWwmb¶ €´²€†Ç]ºt‘ .¸À¶p~ýõ×í¸ÐÛ·o·W¢Ý{ÿøã�òÄOÈìÙ³å¿ø…üñÒ¡C‡F]iRR’Lš4IÖ¬Yc»/++ ½µ ñ=zÈå—_.:&u¸ÊèÑ£eÖ¬Y¾ÓëýŽ1·Î €‘.`—É&,o4Ó¦bOȼÉÎû—kD»ànÍ’bºÑîhÂegÒ Y—=sÓâY—½-ž[óºx/@�ØÐJK:uËjø¾lh­­®Mp]¨áµ ¬uîYöÌu¹ÜÅîÄKM¥/ÿº¾1°Û›'À=2=AµÖžI¤§Ù¦Ë½Ì<1Ôƒè†o™#@@ ¦Âÿ…€`:¦~`¸@ zœî½,·Þz«œp ò�üÃŽ ]QQa»à.//— Øî½'L˜ ×\s�4¶+nSú¼óÎcúÃ?´PÅÅÅòöÛoË�(gžyfØð~ûÛßÊŽ;ä�wÞ‘¼¼<¹ñÆmkí°½!'F@ ‰fÈ(Ùà �7˜ày£™l],6ŒÖ–Ï­U’Ì“ñŽ©Ò)3E´•Ú ^YvÙÐf›îÓå´ÓDŒ‚ €@ 8C?tÏά÷ªJË+=�u±'¬.ÐðÚ,¯Ø˜/é&×å‚bS¡Ú¥nÄ÷šçW˜V×:ÕU´uµ'¤T÷òÖZë¤ãbS@@ ¶BÅÒ.7˜‚éØþâî@�¨pº÷7nœèÔü±<÷Üs6ŒÖ›Ññ§KJJdúôé²dÉ9í´ÓäüóÏ—†ºãÖà[CïK.¹ÄŽ7=gÎk³~ýzÛ}¸vë=qâİyM™2Et¢ € Ðå¦Å³ÎÌx–vn–×{è�&|Þ^Z÷ƒj7¯WÃäN™©ÒÙL�Ú§Øe  u›N8ëC}  €±,�–’d*X%I×ΦßízжÀÖ€º@lï\×÷xƒk].-wgœ g޹;C_P×4 ¨Ez›�Ú7µO�¼ëÉíB¿Ž­ € €€¿Á´¿Ë €# Ý]kXüóŸÿ\=ôPyóÍ7mëæ-[¶ØkÔ®¸7mÚ$O?ý´mU}á…Ú±¤ëë’[Co ¼u¼éüü|Y¹r¥m‰­­°_|ñE;ÞôÈ‘##Æ€ A@ )ÛÌ8Î6ÿhÂçõ:™ÀYçDkëçp—TÓµ¶ �Û›àY'8뼓]öÏ´r÷§Àù@bIÀi�Ý3·î»ÒÖ×6¨Þ«�µ™Ì\ƒl�ÛÉls£ëp­Ä¶½Tdþ®Ðß)´µuooP}€ °0Ë}¼ëÝÌø×@ðèsïÔÔTÑá&�’‘Qe5ç¸pÏC�1ú_þæ_‰kÁt¨ ã+Gó?^‰ ¢­œu|èþýûËM7Ý$Ç{¬ ¢uœéÒÒR*k7ß?üðƒÜqÇvÌèk¯½ÖŽS]—ŸþïãMk×ÚS§Nµs ¹?ýôSÉÉɱátïÞ½ëz9Û@h3�*óG—†Îë̤ó€É„ÏÚw8‹†ÎYÞÐ9«ƒ>›mÒl èŒ4×þ¼ çmpn@bJ@[^çé”U÷íâÒŠýAµ ¬ó�ÐÚ.—Ú »¥(Z N§¯Cœ(Ã|EЖÕ}5¨6“Î5´ÖI—Ûñ9„›@bU@Ÿyk¯žO=õ”ï/¾øbßr¬/¸÷ä T2ëzÜ €@«hKç””8k÷Þ3f̰ÿp/Z´È¾¿vï­5ÌfÍš%Ë–-³Á³¶ 0`@ÈëÓsýô§?•U«VÉ?ÿùO©¬¬ §§M›&J_zé¥Ò¾}û�¯e# €áÐ.·×ÕÈZo­ËD;at¸ÞÛü]læìöiRm�У“]×:ËlӖω<5?çE@ ¬™iÉ¢SÏÜÐçêXÖÚº:¿¨Ô†Ö:_³¥@ôqo~‘ÙZ1¼ù—¸ÏTž[fÆ·Ö)Tq‚ê~}ìØÕÁ¯a@7ô�º5&l^kBç5ÞZ—gm5©t‹¶vÎ6­›³:¦Ivв¶z¦ € €@| hå³óý@§ºÊn ­MHí™—š¹gY×5ÔnIq*á}¶5ôY&æµ ­5¨>PçÞ)ijóÐ'`+ €( CXêI%AjÿëºZYó¯š`ºùv¼@ �´{ï~ýúÉõ×_/G}´<÷Üs2sæLÙ»w¯¯{ï¥K—Ê=÷Ü#~ø¡\sÍ5røá‡‹¶¼vŠv™2|øpÑnRt¼éyóæÙ×®[·N^xá;¾õøñã�Ù#€ Ðd�­fÌçÕ>zh ¡5�Öu·ÿ°Ó‹KOM²”5|ÖË:w–5x¦Ås“?B^€ €^ç{EéTËD[\k@½»PëRÙå�ëº.—˜q°[R>3•÷‚CkíÈECj'¨îï]Ö9cZ·D›×"€ ^ׂéŽé‰µ®4#µö¶Z±@�fh°¬]rzè¡2lØ0Û�·Ž½`Á{6§{ï/¾øÂvïý“ŸüÄŽÝ1pà@ß»iP=aÂÙ½{· §×¬Y#ú: ©_|ñEéܹ³ ¯}/`@� � ÓÈy• šW™Ðyµ™¯.ªöÎk¤¨"è`VuœçÜN<§ÛðÙ  u®]tR@@�ÖÐÊo]:¥Û)Ô{ë×6¬6!õNZ—È®ÏrAqóZ[›,ÜóÌ|ÿ .ÍW¢þDCêþÛÙù³>À¬'í¯¯ü2Ö@ˆ{�Ð]y×þ·¶%P®Ó¿ßCn|m…ïZ.8kë¢\ÿÉÑI‰4åqÓšs!€ €@ä h¥»îÙ™v ¾ÊÊ*ZÛ–Õ%6´Þa‚k ¯u*(.>¼QëvhS±ð£ £óÒ=õ Nž¹†Õ:å¦XQ±Š áþÕs÷iˆˆkÁ´~ãúw²S &Ü €@ 8Ý{�5J†*Ÿþ¹üýï—ùóçÛ«vº÷þòË/eùòåâß½w¯^½lWßÚ­÷Œ3ìñºüúë¯ÛÓ§œrJß9—† ÐT�]e&€6Á³†ÐË5€6Ór³¬Á´[%-%ÉÓ­egO×–]Ì\Ch £S“öÈ-g΃ €±) •õò²3ì|‡e¦w4 ©÷”ع.ïØ³Ï.—–WÞàºVBÔiö¶ÀC»˜`z� ¨5°Öi°w9;5Ô£ûÀײ† €@´ „îÊÛÝ»p5˜v÷Ò8 €ÍÐî½µ…óqÇgêwß}W^{í5Y·n�=¡÷Þ:žô\ <�9R.¾øb;Þ´ŽU­-§µµŽ7���m[X7ïŠx €@[ WŠ,+¨¶!ô2 Ÿ5Œ6“›- µëí®>wÎðÎ5|Î�N™ mÔVŸ;ï‹ €±- •üzå¶·Sð�jkj ©·Û°ÚXo7öîft ¾ÃTZÔé‹í�ï¢-¬kPí7 éÔN2xÚÅ €AüSÂ* €@ë Ì™3ÇŽ�––&Gu”#://O´[n7ŠŽݳgO¹âŠ+dܸq6`Ö±£ |Ý{/]ºTî½÷^ùä“Oäºë®“cŽ9FvíÚeÃi§Z[YóÍ7òÒK/IVV– <Ø�Kã €aÐA´†ÐËÌòº½î´€n×.Aºyƒç®Y@{Bh�3îs>LN‰ €4S@+ê4 gç€3”›ñ¬m`mBêíùûd›†×ºlæÕÕMûÎè´°þlkÀ[H?3†õV{¦v2¤³§KðÀ£XC@ 22Rj÷î–’än/!î<ù�L?® @ ‚´;í¿þõ¯2wî\Ñ.¸ßxã Ñ ¹k×®¶•óðáÃmP=lØ0»M�iNÑ×%''ÛÖÐS¦L‘I“&Éã�?^«{ïÙ³gËŠ+äÔSOµÓ™gž)Ï>û¬ ±µ…µŽ7�““#×^{­½žæ\ ¯ApG`·é†[h�–˜zéžjB—U·üüɦËÈn<›IçvÙ´†Öš‚ € ½)f<랦•µNÁEÃémùûëm\›IǹnJYkƯÖiÚF}•§KñTóŒ¨ «‡vng&�'È0³žEwàM¡åX@�V8ol7ùn}‘ï�N‘#ýrÓ}ën,L»¡È9@š,°eËÙ¼y³TVš>VM©¨¨°sín{Æ ¶³Õ:uïÞ]FŒ!Rk`=dÈÛ4òÿ´{ïÔÔT9öØcm«çwÞydžáþÝ{ë5i­-¹O8á9üðÃå£�>²ï ×ùþûïË�h»þNI¡{ÖFÒs Ð"�5æÁÞb  ó«e© ¡ç×Ȧ}MkÑê$/+ÓÏyÞZÇÖ š‚ € _žÞpÌ÷À~97®áôNÓ%øVoP½m÷>³\,UMha]fòé»kÌ8öu¯LP{ƒêá&´ÖåM‹k  €m%pɑݥK‡ù|å9 ;M®:¦§ë—B0í:)'DhŒÀ€¤_¿~¢Ýeû×Ù «�í:ÎóÚµkå½÷Þ³Aµ̽zõòÕV8P:wì¦Ëy½ÿ\ƒn}í•W^i»÷~î¹çdúôé²wï^_÷Þßÿ½m=­!´NkÖ¬±§Ø±c‡«Zƒò“N:Éÿ´,#€´P@Ÿí-2¡ó"ÓúYÃç%&Œ^l–÷zê-µèìú 1/Û;™0Z—iÝ"R^Œ €Ä…€ÓƒÎð¾��µ¶°Þª!õîbZë²nkJÙX\#:}´I_å ­;$‹ ¨ÛÉð¬n‚êYfÙÌͨ2@ZE`ÒA9¢S¸ Át¸d9/ €@½t�Ü~ûí¶kmçyñâŲråJÙ³gO­×‡Õ%%%RXX(Ë–-“·ß~[´5tFF†pÀ¾î¿5¬îß¿¿tèСÖùœî½G�%<ð€Ìœ9S¦N�*HkÑq¥÷íÛ'K–,‘öíÛKff¦ÛàZßó…^°!øØ±ck�»® ·Ür‹¼þúëöu7Üpƒ\zé¥uÊv@ æJÍs·L hDkm–µUtž„4ꘑbBçLéžc& ¢½!t’éž›‚ € €€[N ë‘æúN©Ý~k@½EÃj3mñ×…ûÊ}Ç4´Pd*e~³£ÚLû�ÔPz„ §‡›�ú X�°S;I«= èþ±„ €@„ LGèÃe!€±. a²ÇÚjZÇpvÂàõë×Û�Zƒj�´µrQÑþq-=^'§uµ†Õùùù²hÑ"TkËh •õüÎxÕ:ïÓ§� ±õ3bƒÏß›ú�v›ñ M—Ü-)í¼á³Bëâˆ#|Ý€ïڵˆÈNP­ó-[¶ØVÏÎët®�°†Û:UTxj"kËêÍ›7ËçŸnßGßK[në{hwã›6m’ÒÒÚêù_~ùeéÒ¥‹üñþo°L0ÀÁ D©À†âY°ËL¦Eô@ë¼ÄtÓÝÜ¢ãòõÌÍ4“¶‚no—µu4@@�xЊš:�è›#'ÚÇ2h+êM;÷š©X6™Àz³YÞ¾§¤ÑDÚ›ÑÂÝUò¢÷&ЀZƒêQ9žyïLÂêFƒr  à‚€ýxß}÷ùÎtë­·Úž9}bx�`:†?\n ˆU zu i�’“=!††Õ=zô�£Ž:ʶ�ÖVÒÚ2zéÒ¥¾VÕÚÂZ·ù· V#ÿ°Ú1Ó°zëÖ­µZ\;û�×i7Ý/½ô’oú�Cñßí[&˜öQ°€Q" ãB±­Z–›î¿3ôw&�ÞZÒüÖÐBÛñöL­A´Ni) €%?\& € €@ håMíÜ¿+ð’òJ_X½q‡†ÖV×ò,Ôeëwý¯¶W›I÷VÙCºg˜�ÚÕ‡˜ zPÇ™ÐÍŒWMrŠ�m €€+S§N 8�®ë�‘ñPøç%>eîˆÿ°Ú¹Ý>}úØñ£µ5³†Ñ•••6löoU­Áµ¶¶.NX¼=x]ÏûÙgŸÙ±°ýë_Ë„ ‚aˆx�U…52ßtÉ=ßÐ:_”ßü:·Sºô2Á³ ¢»˜ynBèˆÿ à@@¢I =%Iôèl'çºKMX­!õFRë|ÃŽ"ÙUX»÷7çxÿù–}5¢Ó´�Õ¾Íe%ÈhT�Î1óÜvÒ¿­ª}8, €-ؽ{wÀòóóíókÿ^Cˆ¡‚éú0¹@�@�PaµvÍÝ·o_™4i’íλ¼¼ÜvÕí?^õŠ+D¿ 4¶h·àóæÍ“3fØqª³³³|)cL7HÄ &�ró¬éÛ�žzž™Ï3aôÎÒæÑ3S¤w—fÒ Ú3§;î0}pœ@@�zÒ4¬îiÂj39eoI… ª7l/ò…Õ…ûÊ�ÝõÎ0•Uȯ’çWyë’¦Au‚fBjgž¼ÔµzÏÅN@Wƒé™ËóåëÕÒ77]ÎÛÍyæ €DŒ€†ÕIIûÿùÓ.À;tè ƒ ’ÓN;ÍÖLÓn¼7lØàë\[X¯^½Z ë¼ §øáÛuøøñ㎠ՕwÀ¬ €aØfºàþv§NÕò­ ¡5ŒnNINj'tÕðÙLvÞÞŽלsñ@@¿@ûôdÒ;ËNλí.* ª7˜VÕëí¼H**þa‡©Ìúá&�ö«!õ¡¹&¬6-«3óné´ªvœ™#€õ „z^/ ™ö?™¯O¨û^ür‹Üü¦·ú”9^ê?_0¨¯ä@ÚV@»HÑÉ ¬5¬îܹ³mý¬�³v×½wï^Y·n� «µuµŽ-½víZÙ·Ï3†“~™Ð@úˆ#ލu3ñüE£@ ìÚ-÷\>Ïõ†ÑºÞœÒ-+ÃÑFë¤ãBS@@@ º²;¤Ù ¦÷ïâ»íþ[Ãj ªuږ߸ñªmå×�"OzǪ`Ƨְz¬ ©Çh÷ßf�‚ €€¿€kÁô _mñ?¯¼1w›ÜúÓ¾Ò­cJÀvV@ˆ�PauNNŽŒ5ÊÕXk ê>ø@>üðC9ôÐCåœsΑÄÄÄZ·G0]‹„  ࢀv±7gGµÌÑ0zG�l5-¤›ZÒR¥O·Ž¾ Z—3R]ûS¡©—Ãñ € € Њ½L%T�Æ ënßu_i¥ü¸½Ð†Ô?nÓ°ºPJË«¼"­»ª°J^[ã94Ï´ ÛÅLVwi'#̸Õ@�øpíiÓÞÒÚÿ0•VìïÖ#¾™¹{@�XpÂjç^´eõ…^(ç�wž ¤“““�]s‚éV@ …ówUË7&€þFÃh3íiÜqïÚ¥Sº ¢ûtë`ç=r2ö³‚ € €ñ+�‘–$Cȶ“£°yW±ü¸­ÐLE²ÎÌw”8»êœk¥ÙwÖëäÉ ²L6 ¨·S‚bº§ €ñ(êyq¼8¸LÇ ÷‰ €€# _ 4Œ®+�ö?ÎYvæñ2fˆs¿Ì@ ùó4ˆÞ^#O­¨’âÊÙ[Ñôsi è¾Bçé¼£tÌ W£¦+ò @@âW@+³êä´ª.(.÷Õ…6¨ÖÀº¡’o*ÕêÕÎ8ÕLýþ̤¹bP¢Ñ5AFT7DÈ~@ ꦣþ#ä@@bI`áîùj{µ|mZC뼨‰Atjr¢ô5t?o­ËI‰´Dˆ¥Ÿî@@�¶è”™"#̵“^KEeµ ¨×m-”µfÒVÕåµ{Yõ¿ný[§¨¢Fî[Xi7w4Aõ¸®íLHÝNÆ™.ÀGfówŒ¿Ë Û‘Ò�iíΙ½b�äuJ‘“†ç¸ŽN0í:)'D@ P T×,‘òE#ðJYC�¶XQP#_šÚ3ÕÈ��™–,v× º“ ¤èÚ¡-nƒ÷D@@�8HNj'{v¶“ð~{‘ ©×n)°óâÒúkÝšÝþ-ªsRäHÓ’ZÃêñÝÌù;2FµcˇÀ¬ùrþÔE¾SŸ7¶›ßVí›–ìiZÝÁÑýM­atÏ.í¥-¢cø§…[C@@ vœqªÇ�èaoòGÓÝ÷Æ�{MHݸ zƒ©ÔûúÚ*3yŒ†wöÔR딚»vÜÄ–@¤>/ÎßçZÁ_»°¤ö6ÿýM]&˜nªÇ#€ € ЀÀÂÝÕ2Û´ˆž½UçžÚý ¼Ä·ÛÓ5w'ÐÃFwÏÉôíc@@ˆ�>Ý:ŠNã‡{‚jmQ­-©WoÖi�ì+«? Yl*ý.ÞS%O.¯m;=!¯�L0õÓ¢šñ©cå§„û@�ÖÕEÓšW4|µ®Ó5!®,!Ô4|M� € €@T ì,­‘ÏL=kk�Ì2Aô¶’_Žë¸#‹m@�ÎÒßÑ:õîÂÑuP±@@bXÀiQ}”·EµŽQmCê-{dÕ¦©¬ª»Ò¯þ6ËþMæ9F»ù>ÚÕGç%ÈDVç¤VÄð�·†@Ô Dj‹éÖ€t-˜n�‹å=@ˆF�Q£FÉË/¿pé#FŒXg¢O`îÎjFÿcY•ì­¿"­›ë—×ÑF÷ìl[F×:€  € € €@œ 8cT;ª—•XeZQk@½jÓYgº¯¯l5•…ß0Ý~¿±ÖsT†IB~5$Q&š°ú°Üvõ½”} €@Ü „jpªarK€¦[¢Çk@h„À¹çž+kÖ¬‘W^yE222äW¿ú•|ðÁ�x%‡ €@$ ä—ÕÈ Sæ3™–ÑÚJº±¥kçt`Bè�Þ)-…¯á�µã8@@@@´§)�dL))¯”•& Ö�Zç;ö”Ô‹¤Ã¦þ‹ªìÔÅ´ž>ÆÔÇtO�c»·“Î)´¦®�� 7¡~6þéWã˜x"Ö8'ŽB@ E·Ür‹èDA�èXbÆ,ûÔÑ3ÌôÕöº»� ¾+ ž5„ÔËFçvJ>„u@@@�f ¤›¿¹Fö˵“žbGA‰ ¨WnÔ :_JË«ê<óSÉøŸëªÌä9äÈ®íl@}œ ©‡vËÔy*v €ÍˆÔ®¼i1ݬ�“!€ € Ð|m=ÝLH¯-j|½PífÎѽ²¤÷NÍ¿^‰ € € Ð$�.¦2°NGën_§cS¯Ø˜o¦=²aGQ½çúÒTBÖéþ…"vH�ãM@}\�v¶Ûïz_ÈN@ ™Ú«fYY™ïÕ‰‰‰’” m‰CUÎiü³1ß Õ³ wYÏå± @@�ð ”ר ú“Í&�6SQEãÞ/#-I›z�Ñ=:Iv‡´Æ½�£@@@Â* £é4i¬È®ÂRY³¥@–kP½!_ö•™~½ë(kLåä5EUòÔŠ*é˜,r¼ ¨O°S¢t0ë@À �É“'Ëã�?î;ÕÅ_,N·u ,-âZ0*/um�Êû#€ € °¡¸F4ˆþx“ŽÝø.º{æ¶—!½³d°™¤U4?H € € €@Ä ätL�Æ îf¯U[SkH½Ü„Ô›vî­óú M¥åÿXm'‘JÛÝ÷‰Þ ºW&éG�pì@�~ÿûßKÿþýeÁ‚2lØ0¹à‚ |M«êW[¨¸ãZ0Ý‚kॠ€ €„]`yA�|d‚è�M ýíÎÆ…ÑíÚ%˜ :[†�eiZE‡ýcâ @@@�° 8­©OÛ×¶¦Ö€zنݲl}¾T×Ô�ÀÌ0Ã=étÛ<‘1¹ rbÏD9©g;Ô1T’Ö[àä gŸ}¶èI%Ôo³º+6ïÊ ¦›çÆ«@@¢@àûÝÕò¡ £5�^¼§q_¥;e¦˜ :[†šI[G'%¶‹‚;å@@@š* -©�ÞÝN•UÕ6œ^º~·èT¸¯¼ÎÓÍÝY#swVÊ3.õð¬9ÙÔ'› ú ³LAbI qOÓÇ®Ó�Ò“dCÐûf¦¶}èA—Ä* € €@Œ |·«Z>0Aô«eeaã¾>wÏΔ¡}²e˜ £ûæuŒq!n@@@ X@+%�è—c'Ý·vk¡,ýq·,1!õÖÝÅÁ‡ûÖç×Èâü*ydQ• ê” ?1!õ¤^íäàl*9û�X@�¨HU·¦q�Ö}®Ó—Mè!7¾¶Â÷Æ—Žï!ٙɾu@@—À‚Ý52mc•™ªeU#Ãh#Z[E3�t·¬Œp]çE@@ˆB�~¦Ò²N§ÞW¶åï“%Rÿ¸ËÖuÝÎ 3„ÔŠ‚*ùë’*hºøÖ€zR¯DR‡J{ê: Û@�¶H�Ú¿«jÄÝdÚµ`úü±ÝäÀ.é2gÍÿÏÞ{ÀÉqUù¿G3š‘&iF“$�Fq”s²å(ÙÈc0`‚Á`ð’ö­Ù·ðßý?ÌîòþÀîoIƒM6àˆq6²dÉ–låœóHMÍHßù•¨žJ�«{º{~ç£VWݺu÷ªnMßsÏ9Í2I¿ožW>0ÔX+ � � � �À  °C]s?{ô‚2ñ£#¸æž5±Lf«2º¸`X$§0 � � � � � �À '€ÅÌø\³ ZšÚÎJêíª¤F|ê`^{UA %õtµ¤¾y\–ܬJê™%nÅO°2˜N$@I%à5×ÎŒ$@$@$@$@$�­PF÷ÊŸU!½UÝ¥E"sT=]Òók*$˜¯þFR=ó� � � � � � d�’Âa�¸Ôíçºdã¾:Ùs¼I¶ªÚK,¦Þ­–Ôp÷=O­§¡ ¾u|–L,ôÒ-†H€H ¡¼F¤Èfß"ogæ"gÅœ$@$@$@$@@ î\Ÿ<£ÊègŽôÊ[u½a[€x8s&–_ˆ ¦Jéa9ÙaÏa      ˆ–@þð¹|N•ñ9ßÕ#ÛÖ˶Cgd«~“-ŠjKC·üû‘K*³äVµ¤Æ§|¸—J(X)L' ÿ xŘîóY3MÅ´ÿ×�%’ � � � ÄI Gÿè}êH�<­Êè�‡WF£º9“Êd®*¤çêw.•Ñq^žN$@$@$@$@$@$ ,Š^<­Òø@I½õà™¿)ªƒ+©ß<Ý+øüó;"+ÆfÉmjEýîñÙ’Eu4è™—HÀ'yóiÃr²|*ýB1TLûŠ“…‘ � � � ÄC`Õ©^yê°~T)}¶;|I3ǗʼÉPF—Ëð\ZF‡'Æ$@$@$@$@$@$@‰&%õ’i£ŒOGg·l=pF¶¨õ®# A«~AeãóàÛ݆rúݪ¤¾b”¿ ¡ •ó � (�÷^T)/nï_L³tr±ï!œ©˜æ­F$@$@$@$0 öµôÉwõÈZuÓ}PcH‡“šªb™?¹ÂPHæå„ËÎã$@$@$@$@$@$@$0`òr‡ÊÅ3FŸÖöNUP«’ZÕûO4{¶©­KäWû{ŒÏä¢!r¹*§?5=[jt›B$@‰$pó¼rùí§çÊ˪œ®‘+÷_]í{uTLûŽ”’ � � � „#Ð¥Þ¹ÿx¨Gþ¤ÖÑ«ÕJ:œŒ-/”ù5å†BºlÄðpÙyœH€H€H€H€H€H€RŽ@Q~®\>[cRëçLs‡lVõæýur¢þ¬g[èâí­=òؾY¦ ê÷LÌ’;'dËPR{òb" �@ü–M+|%TL'Š,Ë%   �‡zH~þóŸKn®®4»ÿ~ù¾`9ÊM<Ö©Uôéçp�´‡qÕ]R8LÔT¨BºBÆUHì) � � � � � �@Æ(/Γå ÇŸ#§[ õ¦ýg¤ùìyϾ¿®‹ºñùê;ÝrçÄlýdÉEåÔP{Âb" �@Ê b:e/ F$@$�)žxâ ùÉO~bt§««K¾ó�ïÈÂ… eÙ²e™ÒEöƒBhêì“ÇöÊÔBzkchWÝYYCd¡*¢L©Ä�¦� � � � � � �@¦_Y$øÜzédÙq¸A6©õÆ}uÒ×çþ }Vy?ªÔøÌ+"ïU%õ]ª¤.Î¥«ïL¿OØ?ÈTLgÂUdH€H€RšÀŽ;\íÛµkÓ.*LÈ4kN÷ÊïU!ýûƒ=a»†¸Ñ §TÊ"UHçæd‡ÍÏ $@$@$@$@$@$@$�‰fM(|î¼rŠ*§Oˆ½ur Ö;õ–†>ÙÒÐ-ÿ²Q õû&eÉ¥•´¢ÎÄû‚}"�L!@Åt¦\IöƒH€H e â^±êµâ5e;À†‘@°rû·zäwªŒÞÆ:®ºO­”…ú=2?ŠZ˜•H€H€H€H€H€H€2›À0]´}ÉÌ1ÆçdÃYCA½AÕMmnWß½jX�ßáøÌ9DÞ?9[Þ?)[ò©Ê웄½Kk[·n•M›6É”)SäÒK/Më¾DÓxKÑÐb^    O›zå7z ¥tg¯g–@â¼Éå†BzöIJ@7H€H€H€H€H€H€H€¼ Œ.-�›–â3Q¶ª—wöœ–­ÏxfÞ¢‹Ä·hê¯oê–»UA}·*¨áò›B$�:~ûÛßʃ>hпøEùû¿ÿûÀ~&oP1�ÉW—}# H ´˜N‰ËÀF$ˆÀ“‡{å×j!½úThmteIž,™6JO«”â‚a j ‹%      �Ì&0GyãËéwöž–·wŸ’ºæW§ÏiT­Ÿïí1>WŽÊ’ÖdË»ÇÓÍ· H`üèG?²Õúýï_>÷¹ÏÉС™¯¶ÍüÚ.-wH€H€H€H€â%P¾O¾¹¥GVžì•£gÕ_XYPS¡ éJ™1¾4D."      ˆ†Âc-_8Îøì<Ò`(¨7ð¶¢^¥‹Éñù÷ÍCäªÑYòà¼l)F+êhx3/ øI ¡¡ÁV\ww·´µµIII‰-=w¨˜ÎÄ«Ê>‘ � ¤ZL§Ôå`câ °UÝ�=¶¯G~¹_—]‡�Ò¢árÑôQÆ?”)$@$@$@$@$@$@$@‰#0Sƒãs‹ZQÂzÝ®“Òè‹úˆ..LÓãwý‡§dË=jE=GcRSH€H Y|UL¯Ù×,ëµHMEžÜ2¿7H€H€H€H€Ò‹Ào5vô/ôGêæ†àfgÉRUF/�9ZªÊ Ò«ƒl- � � � � � � d(�ùZ ŸãgÚä-µ ~kçIééuÿ¾éx¯à³ tˆ|lj¶¼oRv†a·H€R�€oŠéŸ­:aëÏ#«OÈ?Þ8AŠó|«ÂV>wH€H€H€H€ü'p^½t?²·Çø„Š]Q’'—¨Bú’YcdX´ú%X" � � � � � � ÄO`ly¡ÜqŹéâ‰ò¦*§ñ9ÓÜá*x“.Jÿâ[ÝòŸÛ{ä^UPã“C#j'&�€h1íÅ–w¬Áv�Ù¤bÚ¸,‚H€H€H€L á|ŸütO�üL•ÒÍ�Á+›2¶D.Ue4ÝugÄ#$@$@$@$@$@$@$�j†ç•«çWŸMêæ{íŽZÙ¢ÙÕÌÃm}ò¯»å¿·wËǧeŸ‘¹ŒCíÅ �˜М9&l<‰H€H€"'0˜WÀEN‰9ŠÀ‰ö>yXÒPJw…!½hj¥¡�ž4zÄ@5•õ’ � � � � � � ø@`�ºøÆç@m³¡ Þ¸¯ÎUj£.Zÿö¶ùÞŽù„*¨?9=[FçQAíÅ �¨P1.f&   �Ì pälŸüx× é`=Ê2D.Ÿ3F.›]%ÅyÁ²1�H€H€H€H€H€H€H LS,ø\¿x¼¬Ù^+ol?!}Ž0Ô�ºˆý:€Ï}SP�+ ‚: /7›L8©®_Ú^/ÅùCå¶�M&*¦£¡Å¼$@$@$@$�æ �þ¡þ˜ü¹ºì&ÃsäòÙcT)]%ئ� � � � � � � d.�Ê’|¹ýò¹nÑxC9ýƶÒ~¾ÛÕax\ÃçãúþÙBµ H "©êasÛñ6¹ëÿÛ*-ç.<ÿxû´P1í/O–F$@$@.óæÍs¥Í�;וƈ—À“‡{å¿vtËžfo…tiÑp¹zAµ\¦?$)$0PÚÏuÉÞͶêÇU îÏhdË�3b½ÓK‹†É¸ŠÌVÎ"¶ÛÓkû•ÒàÕ¦<ÿðú>ùÒ] £ÁǼ)D «»Wö×6Ëá“-ÒÚÑi\S¤Á ÊF —ãKaœÒr¶Sð‰DÚÏûë~-’:™‡LuMr¢á¬¹øÎQ7¡³&”öÃmôôöÉöÃõò·µ9žÙ³ÔÌ£¤p˜>;y’¥G‰�G¤SŸ¿xdâ¨R\�O<—H€H€RšÀ¥:§€\|ÿuó1ilµÇ >­‹ä¿¶¡[~µ¿G¾0{¨¼{<ͧSú‚²qBÀ[l�å�f‰·Å´¿íòM1íõ£`ˆW†%k%  �#pà 7È—¾ô%ùÅ/~!YYYrß}÷ɲeˬ=¬8ó¬=Ý+ÿ¹½GVŸòžH-ÌË‘kŒ3Üvg^ïÙ£t#°çX“üò•]¶f#Æù»/›lK µsª±]}i§-Ë”ªùô­™½èçØ™6éU¥ŒSjÚÊšlhc(iC ¡õœ<¿þ°`‘Ew�÷ømvæ)µ’Gø…[.™•Ï<Ÿß"¯ªuùY]ÈaJ…òd( “Fâ¿_xû°lÚ_窣Ö×>¼TFäG¦È­kÒñÿEûøï*Ô’€XÜ1gb™þT-¹!˜`|ýé_¶;q:žSÄä¤� $žÇöÄ3f $Š€éâ{¥*§_ÝtÌö·ÎÛ¥‹æ?³¦K~s Kþ~v¶,­þU�‘ $�€—^×KÿO‹|SLÇÓžK$@$@™Nà _ø‚àC!? ;Û'ßQ…ôox‘ªVHת…ôµ Ç ¶)$� jªŠ�¸VõêÞc�Q5mïñ&W~”›é2,ÇÛžï,¯_�™$�û÷ò†#òÒ;GŒ‘vã´*ä~öüv¹hú(yßÕӌ爋"¥'òšN˜vX¬ÆÇ–R19¾¸r"ôÀî ã<Þ»�6÷u\•9×üX]›ñY£nGoX2Ѩ‹Cf`L&�4#À±=Í.››±®ÒY—i j,yuÓQ×߸¯Ÿì|>09[¾<'[ªò¹ 6cov,b©j1qâÈHÅtðx* � � � �ÿÖÒßÒØM=V힥1WÌ©’åª�.ŠÐÉr*7I ¡pOŽ)+”õm�zNªtk{gÄ÷ëÇŒñ#åeêÆ„QE†{Z¸ô¶Ê¼IåB%‹•HênCA÷ÇUûä­�'cnäúݧ ×÷×//7-�èi™‰‰úU[�Ç\O$? 9Õj[à,î³±à"Ñ·÷¿_¹Gvm��\?3ÑÕ±|  TrÔ+ÉŠ‹&.¾_Q5Ü|;å7º¨þ‡zäæ 5bP;�sŸ�¼ó™ÏHuuµ¬_¿^jjjä£ýhJôyˆÇLCŸÏšiß–qz™rs(I‰ûˆ�   Hs?ØÕ#ÿ¶ÙÛBº@-EW,™ ñœÆ¤y/ÙüÁD`¢*%†çfÛÌ{޹ãF{1Ùë‘o0¸ñv²ào-'‘ÔßZ[påm\Ç勯ã¸×Êô|µÞ|ÿ5Óc=”b—Î#w\9ÅcªÀZ*·I uÀ}¶5tZ¶lîXyªíœà˜)»tÒ:VÅ´Y†ù ¥1bˆã³ ¦RÖ¨+Ñ'Tm(¯ábô¶K'[“¹M$@$@$à3xÂç�m'ä…wKû¹n[ Oî•g�vÊÿ­ÊéûgdÛŽq‡2�À­·Þ*ø¤”$a²Á7ÅtJ�ccH€H€H€H ¬«ë•olî‘õg¼­¤¯œ[¥ÊŒ‰†‚/ºË. "py:E-D·¬ôºùìy©kê�Šwœ¥@&ÝØëpù�ßLÓÕb:˜ÀûàÉÉÒ:õŸ~ÔÝ«º?†b¼¤`˜ŒSoPbD#p%î´Ú6ÏÏÕØjè›)'êÏÊ^uS{Z­÷Îvt±£FæËDuÇ�ï`‚vwv{?ûÎsЇ¢¼grÄûP–ºmÅ× QX9ŒÑO•ƯpÒÝÓ+ÎÕª‚;¿`‹Æ3Í燇f‘i!®g £e£±õ¼a}yö|—œÕ‰®ösøî2ø¡ÝùÃrŒþä«{klÏžXj¸E·‘ðÍcumÚF»•*½á≲|á¸�õã¿U•g §TÊØŠÂ¤+¥âºÆsM¡ûßíáºC­Ï­‚1gÇáÐ.¦‘º†À˜‰`ÙAcë99ÙÐn§%2¥ªÿÝkí þnè²¼OÍ÷êÆ‚Xï7¶�—Nõ- ã*ФZÇZð‰Eübƒºƒ¶]¿ýP½À‹L3Ú®ý+/nŒ3Févè¿¥‚õ˯1,mȱý‰ç½Œ7ÓI`°Àûx‘zÈzáíòZ•ÔV�{ïÿ½©[ž?Þ+ÿ»Õ™tÿuë|õüê ÇC8ª“Ú¿|y—Ô·ô+‡�ùѾå ÇË»–Œ7ûÎãæ>¯?{~»¹k|ßpѹN-‚ƒÉóë â½Zåë÷^*yab´¶¶wʆ½§e³ž{ÄáÐZ–×öW?t±ä†W´{�kÚ[»NºNE<é+Õr4R�¢d $Y×Õ¯kÚ¨–¸?·-,ª6](â¼_½NúÜí Šžpå߯^Ù%GuB(ÁXóþ«§IqˆÉcBµÑÏcN7Þ°bÎ×q!¬Ši(ó›TÁVR8ÌÏêeaqÇÆ}u�øì8pR•Õ™*PâÁRü¹u‡l}öê/ð,¨©0àNü|o´èçó ×ì¿ym·M™¼NÇP(Ð?qãlùá3[\ãþk„ÖÆ*ˆ]þ#ÇûôNõ:1{b™Q/”ÛVÙz°Ê“÷iˆ…h”ö~² Öv(èþËvcÁAkû·ð÷Åuú÷ ޽؎TüÃÑö�Ûýz/Ez ˜�¼gn¿¼ÆA†±{ÿ‰f[·ßÒÅù·½Ü+ŸŸ•m¸÷¶ä �@RxÍOàoK?%ü_œÖöÑËÇÈÿzò@ ÷‡/##tE:…H€H€H€H r«OõÊ¿nì–Mî¿ú ¨¹qéD¹L]¹RH Ý xY9ÃM÷峫‚v –ÏPøZe†Z3†««X¯|ø�…>˜øÆDn¬J^”«L|Öî¨uYXëǤíóëiz�Étë±dlƒâë›�J 'Sgý`ôò†#:qÔ$Z>#aŠ#g½ÁöaMúè‹;¥M­¢£Ẋ²à޶¼HóÔ6;–Ò°°§ˆ¤ã5µ^·u»N b[°bÍ»G-@¿õø;å”�ÑJºŒ1èÆ–½ºàÈ*ÓÆ^³a1íXMÃ]}"ž1` kõ\Ъ‹à]®ò3IN6¶Ëã+÷Øúªðkò]j¡ ¥ðxõÂá%Ézo@™nµp6Û‚ûVvÁ#½²ñ¨\¢`Q$�ø ‡Âj<÷–{Õwu‘þ«{ú_•Ë*ù÷rB. %�$àÛS}ß²±òè}³åsËÇÉpš|ó®)Ø-VM$@$@$@éGq¤ß÷Z—§RzÉôQò�w/¦R:ý.+[„ÀÈ¢aRYbwe½_•wμÖÓáÛ)ÓÕò:”À]o¤‚Éùgß;‘™$PJÿç6D¬”¶öë_ÁK’õÞ¥t}I®Áï&„Á'pß ËæHùàÁ$œ•P²ØÀ}7ܱG"p¯"á$YcX"Ú®o±O÷÷R¬ýæy$0°(ùÁ»—Èâi•®ê·5öÉ]¯vɨ79 �@ò ŒŠSúÂý1ä(À5p8)) gÊhX&z ÞßøÀÉâN;ÝåcΟß:(¿a¶ëôdŒ1®J� W«ä¨‡€‰£G’ ¤FìgS TÄb™D=°ŽvJ$®«�ç¤òþ«ö]p„¾"tÞµ§U�íþ«S­‰UQÿgµX~ﲩ¶n&û½EþFøËºÃjÙlw��ç{±ºÙ~}Ëq×û7’ÅhV‹k,Vš4ºXÝu‘óºˆ äÀÇ*Po=xƈsmM7·“ÉÆú¼ày¿õVyª±C¼úþœŽ1ó&•kœn³µöïD�aöZ.ìÅÛödŽí~¿—¼x0�H ŸÀ]¬ö�k¦Ëì eÆ"áý»É*°ž~C=Ë}}ñPYPê›�¥µ n“ XxýÙàõw£å”¨7}ULG];O    �ANà»zäë›ì`&LÊÝz館bÛ™çò›Ò�Ü·abÙ*PÜz)¦ázÓ±ZÃÅO¼÷†YÖâÛPP@ÁûŽºê|gÏi[ N(¯1¹ÿ™Ûæò[7ÖLœüۯכ»Æ·3“,håÅýÊÐ߯Ük¸ ·žàeýס^‹BgDzmVn>n(ò¬ù¡ü{÷e5²Äaùƒ8ãO½qÀfY½ãpƒ[±¦ªØZ„ƒÚLHÔc–ŸÈoX¦Zeò(û'”gz¥åp^ãÀ}.bèú-˜L³*ÆP~Q^Žº}NLLk¿ÛIyxŸA±êx(¹ûêéb½ÏðNÅõybµÝ=”�S«Üü“ùÞÀ3„ø£P¦â=ýÔû¸ÇSïWxq. ;û·EcN^û—¨Ûø[/™d{—A¹ûƒg¶º€a1×¼Éå^ÅH2Ù˜ @„0VÀ}7¬É�ÞZ° } »=Qc˜ÙN¯ïX۞̱Ýï÷’¦‘ ¸ `œÅ¢ÁgÞ< k¶×Ú2¼£‹øoz±KþE]{ß?=ù¿Al�á d8¯mÎ…Öñ"èÿEoI<ŸH€H€H€H b'Úûä¾Õ]žJiXXÁ‚ê=WÔØ&p#.œI M@I�Ip«8㑚Ç`Iç\¥;=L|ió\¯oÄ�0j„`‚û³·ÏLxZ刺ïŒõÇ,t¡´ElG«Rå/›7ÖZ�±.¶ë„8`µjë W ×,¨–›.îWJ#ÃXµòþì»ç»®b;CÉ6‚XàNùÐòé‚{!•Òh+¬éœRZDÅ´É$¯)ÚŽçü¯§Ý*°.øÄ�³]JiäÁbŽ÷]m·BEº¹�Û‘J*�1f[Ï ÜJ[ÅWÚ©¨F^§•µõüx¶W«·çX;.ŒÇ�xêKö¹x?þY½}8e\E‘|ù®Å6¥4ò@)OŸ¼yN@1 ëÛ/ܱPL±Ç>Oö{^FÌ Ñùö…øJiH0¯&ÆÁ0ÿaaÛ]ªØu.°ÂB…n�çzïÁ› M8%ÙlP?Þw÷\7# ”Fé­¸h‚ë:ãØ™ïxÔ1†ùÕvô+‘’®ï¥D2aÙ$�,ømˆßgÓEº^/ÿï�Ýò©7tÁr‡ó—a²ZÈzH ó ä9æFÐãá9öy›x)ø[Z¼­áù$@$@$@$0¼|¢Wny©Kž;æð3ª}ÇÄõ—ß»XfMï^t b3œ,çjT9m•çZ=c.{)¬aqí‡Àuõu‹ÆÛŠÂ„­Ó�œ-C˜¬ø‡ËT§T–ä&ÜÍcÍIŒqZß|Îf±‰6à:\=¿ÚlŽíJ€…SÜ1ßjëûÝïÚNHðŽW òµvWê nBÔÅÃ=¬SŠòsœIƒv?¯).V½zK€…¯U–Î-uÁK0™¯VŽã* m‡ëƒ(�l™ßzÌT^[ûé6b¸÷†U¯SNx¼÷’ÍmºAÐÁdކq Úè%1†ùÕv¯þø™–®ï%?°,hϾüÞEr±#ÜÚõìÑ^¹UçS^­uϧ t»Y? d�;ÛÿO�/·.ðökéÊ;Vr<�H€H€H€b ð_Û»åÿÝjŸÈF1°Ö|�® FÌ< &3Ô]ÜS›7ÚÕ*Éi íŒÍ K)?]°z¹‡ëp(fc‘`Ï2âÚâØù®þ‰” £’§ñR¶ÏÕø“° &—¨²m�ÃBÚ«œ`çû™e¿Óµø3kîØO­¸ÆBƒx~¶ey¹›?çˆaêw�éT^:^SðmP¹NYâ1yjÍ‹êéãJå¨Z_šâŒ=m¦‡ûNÕ1Æl·SÁ ]¸Ow âLï;ÑH®kê0âqÇ:ö¢ ØPµ¨ûb”õºZJÃeºSàÙ"uêòÚ)X¼Ï áï Pï!¯ñ>�ÞÎþçÍ–jÇgÜëwŸ²%7©òß)Éf ïP *¼ÂDxyí@?’=†ùÙvçuð{?]ßK~s`y$0Ð0n¼OC(MÖ�'=a]x\=Ðݳ²K¾2o¨|~–ÝóÕ@·›õ“@ºX<¡HžÿÒByfS�Ë–�\^%˜ÇðS¨˜ö“&Ë"  �z{{u¢|ˆñ ‘�‡2”ÀYõäú÷ou«{�]„qâ¼\U9órŸ2�€S�þÁºÍšŽ“Î a(w"•�½¸Aco<Ùl”ÓÔÖiÄ\„ŲŸì1Ì϶ÇÒßhÎI×÷R4}d^H7åúNûÔÍsäùõ‡åå GlÍêH¯ìié”ÿRåôÜ‘þZuÚ*â �€oÜ¿�}+š‘ � � €À³Ï>+?þñ�0~ðƒÈÂ… eÅŠ�4nd.�'õGÒÞì‹×^£³…êÒò½Ë¦Êl�Xp™Kƒ=#op7ºfGmà`­Z¾žUe±é^zï1»…,ÜpÂ"6”@Ùó‹wŠÓ�l¨sü:—µ©*^.=á&/œ �U©„ø‹p§—¯“÷ñ,E£¸ûäMsdåæcòú–ã-*¨U—ì\µOÖî8)[13éÞ) <î‡Æ ·˜Žæº¦ã5Å={¾³¡F4÷°WÞlUÌF#©<Æ »ŽØ­™�öôšÆÛád¿*¶»º{%ÇÓE¸sC‡b즥e±Z^ešœó¸ýZˆ“jï�x¯ÄrŽSÙïµ+�Ù äËõHö9éø^J6#ÖGE`Τ2ùü{a{¬m8¯ÎZî×ù—ÿÑy �@jˆ&!µûÇÖ‘ � � �À€øçwºå‘½îDpMùžËk¤M¬”R•bcVΦÀJzAM…;Ój³ÔÅqXX‡’NUj¼µë¤+ &1.�9FÆ©KÙùú"ª¾‚ä‘T!°eËyë­·düx ó7ˆ·î`ÜÝp>;(Я÷FÜ�‹¡€ÓÜWoít{]‰Ôb:]ØdÊæ¼?ãÛ£¹¥â}/ESó’ '°lîXà ‘34ÊÓGzåý:?ÓÚü\!��$ðÈ#�تì±ÇäôéÓ¶´LÝ¡b:S¯,ûE$@$@$�Tµí}r÷_»ä�Svë@X#`2vքОIm,+#�$0ÝÃ=7—˜àµŠW>ëñ`ÛpGL HzgÏ©`‡3*nm�“¸èà“köKg·[I¼aïi›%» cêØs3ð ·¹NE”T^îdájóÕ�Gç&j£ÉC;<7ºx¾~´ 1n§V»™Ö{ï[�o¿FŒ(Èu5׊ªh%Õ®k¬×´\ïO§<½Ön]ã<j?WcÔOq<Pˆ<üÜ6�ËøÁ(£7úV?j„Áiò˜bu·üãå^ߩ䶖Ëm;Œk“F÷»37�®ßuJ^ÞpTz-¡2Ìc‘~'ò½iü̇1dßñ¦ EbAĦýu®ãc5 ˆSÒ™M¦Œa~�íÎkÉ~¬ï¥HÊf �ÈÌ™XfÌ»`³UVéüÌû_딓êÚ›B©F ¹Ùþw3Ú×ÖÖþ)ÕÚëg{¨˜ö“&Ë"  ”µõÉT)½¥Áþc°Ÿ¹užŒ-wOd JPì4 „ 0¦´@œÊ´—Þ9"ÎX�‘Ä­îœ�òŒ* œ‡ˆi½í`½üèÙ­ž »ãgÚ S«ÆA¶?ÝÎÒÓg±VW,™àj0”I?|f«a¡å%â5¿¨üóª;î6bt#^·S0Éí¼†PÔýDuÛÕm5¬£Q6\ƒ÷‰MA•wpãkÉ^w«¯o9.+73Ü`ÃÊq¤­±É¡|ÁõÂu}ìå]²zÛ g3Å˲Е) ç�£Œœ‚xÌPfþî¯{dÓ¾:©m8k»ï±`J”'ÞØ/ýj]D }¸§w <úÒN£| Å=  ÖPЮÚzÜyбŸè뚬kꜨDçv«Ûéï=¹ÙXЪ.�Íç Vð÷“z=‚É�WNq]SÜãßÓûûgÏo7ž'§E<î×ÓMíÆ=šiÖÕûõÙvŽÙÓÊû`,¡äwÆ‚ÅôžcMÆx…1Ëú9áð*�ñÌzܺ�1h ä½_Û*¸§Ÿ_HþëO›\ã Æܯ'u¼ëµ;jÏ�SùÞpÖ•¬ýŸ¿¸ÃˆIŠþ›¸þß{r“+¾ô$ý›~¢.²pJº³É„1,c{²ÞKÎû‰û$@ñ¨Öß(èüËDÇB­M:OóA�¯9rÖüK/¾zx6 �@ü| ¤öêÎyçp«L•/·/¬ˆ¿u,�H€H€H€RœÀÁÖ>ùðë]r@¿­2{B™|ä]3.,)$@‘€Òy�Zw™âŒ=Tãæ"Vo8SV p¯hÕkÂ¥ô¿ÿf½˜–¾çT©p¬®Í¦DÁ„¾Õª “ôø@ŠÕõk÷,•Gžß!°ä%ëv�Ô~ØÝ€.œR)Z>=ÔiÆ1(dÿã·ëmmwÒ³oÔXÊ]Ù&Œ*’ÏݾÀ•޶¼¶é˜¡ ´„•'”ôá䯋'Í2gb¹¼±Ý® FŸyaGÐsœ}q§‘%÷ÿRæV�•u›‡ëkܸæP„‡“šªâpYr¼R]?ßvédùãª}žå¯ß}Jð�@ÙQ¤ñŠÏwu»MìQeéµ Çy–a&ÂÕ:Eaq…Uök”o?Þh°ÊÎʲÝÿ°¶¼R]!zI"¯k²®é’i•òÊÆ#.ž°ZLjùζ.vX<µR>p­÷³ …ÈõSú¹u‡ŒóÍÿðÁŽÃ ÆiX��?l¨z%èÕú»Ï7x|ì]³ÌÓŒïD�1¶Ê|ÞñR$O«Q-y¹CÊVksxÌÀ §kΈ Œ!Æÿÿ9ühl\»å’Irõüj3[Ò¾±À îLÿª‹wœr¢¾M~ùÊ.#™fë˜é\D€ƒàåå'‘ï £QIþ é?­Þ'O©·,äÁ³Ù© /�ç<˜¤3›DŒaÁ8%*=c{²ÞK‰bÂrI`0Àï„ù…þž°þ]²«YçmVvÉ£ËrdB!çhó=’J}‚°é³Nb8ŽeÒ®{¹vŒ½ûñÊãrÏO¶Ë¾xDxl—|í‰ý1–ÄÓH€H€H€H =`ÅíGV¹•Ò‹tûÞf&¸Ó£7l% <�éãB»¼‡â Ö›áJŒ«æ¹•P6Áe2>‡Táa�”ÇoÂËf� ZtOÏ…Å'^žd9éy]ª„ñë·hg—·’}½í²É.KOKsƒnÎÓ8°¡M+–Œ—pÍtÃÊÝL�äûLKG$ÙÒ"Ü“;YMQ¡ÆtgǼâ°ït(»�çpßN ³0¶ßªVÛ±Xš7¨’6˜$꽬¾D¥c‘ÂíD"Ï-÷©r#œ¤3›LÃüÛ“ý^ woñ8 �@ìîY>ÃõwßµœÆ[Ç‘—71âÔ"Fu8+fŒ ¨XN?ôHÇ gcb=ÏYN¬û—à§´ó¸s1|Ñ+·3Ý*^cd®…Ÿ5oÔÛzÀíz$ÞB•í[{BUâÞW©%ÿ|õ�gÿÈ©VÃòߪÂët°÷²Ü±æõû½—âh¯µmÖw—uí°zM1îM³þ5îÌom»¹�úîÕ±ñ™µŒ¿œcSTŽÌ“+猕¥3G›§…ýö›�xÜ�ážs+³Á‘Þ�~�aF½In»Ÿc{²ßKæuâ7 �@bàï>Œf¸Ô²­±Oî[Ý-¿¿&GÂü\JL£X* ü�€×ß]ƒÅ•÷í¨õo¸˜oŠ9_{SÎvÙÎßöõK¤´ Ç–Æ  l¾ýíoËw¿û][·¿üå/Ëç?ÿy[Z*ïÜ{ï½òꫯšøÈ?ý“\»hQ`°m|TcJ¿tÂî"X©`‰7Ø®ûK‘À¸é„5_‹~ÕØºP�,&P’šÒ¡±_a)÷oîÏ�¨,ÿÌ2ÓéÊ Úúv( `ы؛ñÊ<ÕØ.gU¹‡mð†eºË7æ8J¿Öö.ÃÂV¾øUkº/Påua^Žà®„ÓAp�^èS§´«»gð�oô£H?#Ô 5¬–㸒>ÕxV™õ©2´[Ù 5˜•(¦£¿¯ë@]S¸L?Qö‚Ûôóº°^b€ ”?Ãu»LïWÄÜÅ„f,æuÍíó¶ŽNÉÉÎ(ÎPfÙˆá6…t,åóˆ”À—ÕR§Š~L‚â™ËÑwñ¸ïËFäcM¤å™ùñÞ0Ëöãï²üÉj[QÓuqÏ'ošc¤A)]Û€1 Óˆ1]Y’o¼ûÀ%^Iu6‘ô/]Ç0?Æö�z/Er]˜‡H z¿yu·¼³×¾yÅØ,yäÊøþ¾Ž¾%Æ->([4äÙo~SæNžØOÖÆ%Ÿþ´þö¬T÷æ(cÊÊû‰ÞøðCÉë›7ªyì±ÇdÙ²e�ýdl\wÝu²wï^[U/¿ü²L�šù/¾YL÷ÙÖ(^`é¥ñ·Qæ � � ^kÀøŽLß ÿ­ïv)¥çkÌU*¥Ó÷š²å™O:%(öð %yPÔ©uð`(ã&Œ o‰ ” KÜ` sœªÌ`u¥r:îQXDã“()Vå3>~ˆß×u ®),#«Ë ��\œeøÉÜY6÷I Xì¥+>~J"Þ~¶/\YX¤KýDHº³“tÃüÛê½”ˆ{‘e’ ˆ|@íÁ#ÂÖƒg8^8Þ+¾Ý-ß\⛊,P67H ^sÃ^sÈ‘”åwžæŽnyjc�.ê"ï¿xT`a¹_õÄ¿0TK|±ÅU�‘ � � �@òü÷öùÕ~{ø’iÕ%ac®&¯…¬‰H€H€H€H€H€H€H€HÀJà£ïš)SªJ¬Iòؾùþû�-wH �RU1}ª¥S®ÿÖùÊöÉ?ü~¯ÜøŸ›Œ�O~¢H¬bÚÏ–²,   @?ÛÛ#ßÜj�39FÝÜ~øº™Ø*VM$@$@$@$@$@$@$@$ŽÀ‡¯Ÿ!£KíÞCþmK·<ª j �À¿~ë¤k<À±íx›ünÝ©À¾TLûA‘e� � � d4�½-}òo›íJé|�ÇwÏò�˜¦ €�#       �4&P0¤ó8Ãs³m½ø†Î÷ìo¥û_î$œ@ªZL·v¸j´œ³Ï‰Æ Ç7ÅtŸÇs«áš($@$@$@$�öWºÝñ7b�i_i›öeH€H€H€H€H€H€H€H C Œ)-0bN[»×Ú%‚y �€ˆ—^×Kÿ+ßÓñ4‚ç’ � � �@ªø6u˺º^[ón½d’Ì_jKã � � � � � � � �@j˜=¡Ln^:ÉÖÈ7O÷ÊCOy¶ Ü!�ABÀËÞØÃ.9.TLÇ…�'“ � �@x .teZ°`�+� ©GॽòÃ]v6‹§UÊUó«S¯±l � � � � � � � �@X×,¨–ES+mùþgg�¼Zk7L°eà øH U]yÓbÚNj̢H€H€H` ,_¾\þéŸþIªªªdìØ±òµ¯}M®¼òÊ�jë��@§þù× vWNÅyrוS#,�ÙH€H€H€H€H€Œ€šü ŽhVÖ�ÀgxŽ=®è€µ�“ � 8�;¯œ"e#†ÛÚñ/»¥ÇoóP[ Ü!� RV1-n›é>ñ÷¡ê×MàåcÜK³îW},‡H€H€Ò‰À§?ýiÁ‡’>RÞÛìx½çŠ)’3”gÒç*²¥$@$@$@$@ƒ•@–NL>tïeƒµûì7 � �@Ãt±æy~n[ çþ–>Ã¥÷¿,ðMu(›$�¼ôº^úßxú™Õxèñ\   �Œ$€˜Òï±»ð†›§iÕ%Ù_vŠH€H€H€H€H€H€H€�ãFºÂµýHCº½}†.½Û½�ìþºª6l˜+-¨˜ÎÄ«Ê>‘ � � ÄEàÿl³+¥G�Ì—›—NŠ«LžL$@$@$@$@$@$@$@$�Zn½d’T–äÙõ-Ǽ�í wHÀøÀl¥Üxã�RSScKˆ�dXLÓÁ@\YÖI$@$@$�²þx¨GÞ8e_{ÓÅS¶½l � � � � � � � �@ìnÔyŸ_¼¸3PÀë'{åÉÃ=rû„ì@7HÀOwÜq‡TVVÊêÕ«¥ººZî¹ç?‹÷µ,{ Ãø‹öM1íÕ0wˆìøÌH€H€H€H ‘>÷f·­ø52{b™-�;$@$@$@$@$@$@$@$@™A`î¤r™7¹\¶8èÐk»©˜ÐàF"\qÅ‚O*É�$˜LÓ•w*]q¶…H€H€H`@ üj¿Ý…7sÝ¢ñÚ&VN$@$@$@$@$@$@$@$�X^ó?¿=àž'Jl+X: ,/ƒc/ÃäxZé›bº8Ïn|�¥­ÏË¥›ƒx.Ï%  H.�‡÷Øp\>»JF—æ'·¬�H€H€H€H€H€H€H€H ©ªÊ äÒYclu:ç‰l¹CH Óâ›bú“˪l—à3×TKN¶—nÝ–�;$@$@$@$�ž9Ú+»›ík¯š?6%ÚÆF� � � � � � � � $–ÀUóìó@;šúä¹c½‰­”¥“@ ðÒêÚgKão¬ÝÌ9Žòî[6VfŒ)��GZeÚ¨|Y1‡±ãÀÉSI€H€H€’Là×7ÞKgŽ–Ò¢áIn«# H�ú–sr¬®Mr†fɤÑ#$o˜o?�“ÕÖ�ÞÞ>Áµnh=g\ëù¹R˜—+Ãéá-zÌ2˜ pŒÌWŸ}' �ÁK ¼8O.š>JÖï>€€ù¢›ª}³ñ ”Ë HEÞÓþª¦}ýå}ÅÔÁ‡B$@$@$@éD–Ò+OÚWÀ^:Óî¾)�úö’ � „&°zÛ yzÍéí»ð»pxŽ|üÆÙ2¾²(ô‰;ŠÓ¾ù»·¥³»?ÄÌñ¥òÞeS#:?X¦W6•7¶ŸvØ–þÉçÈu7HqØ_Û,«¶—Ó�ª”î�UN;ehv–|æÖ¹2aÔç!î“À 'ï9è � �@Z€;o«búÕÚ^ÙßÚ'5E^¶¤iÝU6ž\†xh¦ÿö³Ù•7Ö_Ó±6‚ç‘ � � �À@xòp¿bí˜2¶Dª+ ²I¬›H€H AšÚÎËÓkû•Ò¨¦í\—üáõ}ò¥»FTkŸþ2?ÓÜaË‹rã•–³�‚O$Ò~¾;’lƒ*Ïù®ùó›eíŽZq«¢í(º{zi%oGÂ=0ø1F¥›ÀáS-Òä1¾æåH͘b× xÔs¼d–.„‚· � �@b`±êd›èbGSžÐy£˜CušÉƒß™KÀkùE¸ßVÑÒà“-1æ'  È8Ï:â-žZ™q}d‡H€H€.8v¦MàÞÙ)µ m†emv–×Oqgnî§�–öNùÞ“›¤±5ò%… Ù‘jבíx#s žxc¿>ÂY:fÄLv`‚AàTc»¬ÛuÊE®n—//õš�™/Cuÿ|g�4«‹Üî^ûûßu2H`�Hå12S/ÉÞc�RUVè^gw¯:éíÆ;�‰$@$@ %09¡VjYêº{ö„2¹ýòšä5"HM°ÚQп:ÝÌV0œ?ÝM^ßYwHz5î·UÊ‹óäÞ³ …´5=O'±0�B$àM •ÇHï§êžãMr•eQÒA�iÚãr"ý{Ê� �@zÀü�U1½òd¯üÝL*¦Óë*²µ±€"úË+ÆÇrjDçð×mD˜˜‰H€H€â#ðÕ¯~U{ì1£�x@|ðÁø äÙ¾ØXo·˜š<¦Ø·²Y � �@j]Z ˆÑ U¦3®gj¶˜­ E`¿*qœr륓\Jigî“ xàéÍ%Q©k[ E´Îa¯*ª)$@$@OÀ9?´Á14ð-d H = 0Ætz^7¶šH€H �<þøã¥4šý?ÿó?òÚk¯¥Q2·©›ú¤Ó¢—©T•%y™ÛaöŒH€HÀF€JiŽ´Üéèì–Žóݶ¶�Ñ…³Ô žB$Ž‘ññ‹ôìÎî9tªßu÷žcTLGÊŽùH€H ‘F—æK±Å›Q»þɹ­Ñî¥'‘õ³lÈT´˜ÎÔ+Ë~‘ � ¤ �={ö¸Ú²wï^¹æšk\éLH.�íM­´V]]Q”ܰ6È ÔbñœÆnõ’\�ë:elIàÜ'#žâéæ9ÛÑ%¥EÃ ËÆ‰£GDeáˆ)�ÆÖsr²¡ÝpÉ|ªñ¬QbÉŽY ˜H(Sw¾±N¬7¶ž—Ã:Q|ö|—œ=×-íçðÝ%ˆý8<7[ò‡åèg¨ä«›elÏžX*p�ê%°~êÒóL©./4\6£»Ž4õ4¶�—ή^£Ýãt<ª®Ð<ùn·ÎfÁ¾áÒøŒ²g|êš:Œv"~åýT•ív¾3=hÛµñÛÕË‘ºViFÛµåÅà öˆQ WʱˆŸÜ÷)w´+WY$Eyñ‡ÛÀuÞ°÷´ îqÓÙózOd)÷Bk°/Ðû%þ:"éO4yý,EÓ–hó6´œs�‚ñ&ÖçÞZ˜ŸÏÒ@Œ‘־IJ�ûb÷ÑF鵸®©*6ö‘¾[Çñ!˜}Þär™ªÌaí‰çmíöÆØÓÕÓ+e#òdú¸™RÕÿðj žÕƒS®õõŸ~†H¶º¶ÇX[R0Lð|Ëñ_½Ê3Ó’1~%ªífðÝ­,1ž«k“£:æ¶ëb ŒWÓçN*¼+öè5éîÁ•» áÆµD�‘ÉàŽ&ŠËßð%ôkZu‰XÐ{U]£ž›ÚôïŒÚú¶@ÝÎ|�A6Òõ~LÔ=ƒ{ÄÊØÌ¿Á‚ ”ÓMíúwTÿ»ehö™V=Ò•=™c¤«r&� $�扚ÏÖêÛ¡óHsFFÿ7I n� Ó¼ H€H€H€-�}-ýw€€É= �@l~n»*$¼Óyª¼ýúÇ.5ÜT>³ö€¬Ù^늋Z¡�x×â ²|á8Uv„n¯¿ze—NÐ÷OÞz��‰Ä÷_=ͶÒÝ+Ÿ™ÖÚÞi(78#GN·šÉ}õCKn¡{’J�=»ÕVÆ�WNQEv™<üÜ6CÙ`=¸õ`ÿÞ¢©•ò¾«¦ bG"PXüòå]R3ÏÛå ÇË»–Œ7”?fº×w°¶C õð_¶ p¯óp-¯ÓëxÝ"­Z¦0’î¨ò‡qË%“äjKŒÏPyƒÃ=óÈóÛ¥U\Øå´± ×(—.š–½ýüÄí%êYJ\‹í%×ëâ§”X,[œÇ"Ý÷ûYJöi?CåkÑ…£¬ò‘ëgÊo^Ûm[h³n×I™9¾T>¡±ÛøÌרùÚ¦£²bɹ~ñxkQ¶í�ûëä­�'miÖŒ[Xt„1ýZ}† #Xà‘¬ñ+m·ö}—.øåË;ƒ.þú󛇮ë/�5Fya‡íÚ„×1F&‹{"¹Xù'jž°°®Eÿî€ì=Þh¼ X¶þBÁ=ïT¬†jS:Þ�‰¼gŸj•Ÿé{Ù*xãï“`òüúòEÿ´Ê×ï½TòríÓèÉ#­má6 �@r `¼Æb\Sö:æ‘Ìt~“@,öïß/¯¿þºTVVÊÍ7ßKiyŽý�š–]`£I€H€H µ ô©åšS`]Bx‡Ûìצ"Fë¾�ï [@©MnvñY»£VVo;´±˜˜|~ý!U`÷JŒ`×í:%O¾±?¨"Üz¬Ç¾õø;Eð‚š ë!×6,}q§a­ä:&Ö}#¢P†AqùÈó;\Jig5°¼…Åó½+f…,£Ùë›�ÉsëÀÏ>¶9ËÄkéå Gdÿ‰&ùÐòR¢a ¢‘æ³�:É»#¨ReáZ¾øÎÃòþ[æ†,>™ÜC6$΃�]=òèK;=”Òýãʼºñ¨RËÐ{®›°rìϑܭD=KÉì¬õ�R¢^b•d>Kfý#Írñ�1ÆêýÁ¬c§z~xáíÃ.¥´yü½ï/ž1:è"¡WB Æ-(ñð�"J¥X’ø=~¡Í‰lû_u\ÿó[ÅãçD…ýiõ>e;ÌóÚ2à†ßÜ3ƒ ¬pKäí=.=Ýf,>€å´)øÉˆ<ÑH¦Ü�~ß3Ñ0Œ'o¢ÆÈxÚÄsI€â#Pá÷æœGНtž=˜ ¬^½Z>ô¡ÜvÛmò½ï}/°ŸÉ‘-»Ïdì � � �À %p¢ÓÏý2²(:åLÿ™Ü"Gà°Z’¾¤ÊÐHä U^s¿ åöïWî‰H)m֥ϯ_Ùm¸V6ÓœßmP«¥\hÆ"PJC9©À}7,2#äƒU[(ÅÄ_7“gÞ<V)m­ns¿ÿÔfê5=Ü6,àæ2�kNXŸ“dsÖ?Òa•Õä¡$õ*ÊxX¢ÚßB^9—–¨g)q-ö.ÙkܧÇ*É|–¬môkŒ´–™ˆmXØ“—t1J0�;]X‚“fµÎŽT6âYïð®ˆVü¿ÌºÕv<£èg¨±ßlò¼ôÎas7å¾ýäžI\¦�ëw�ÐX0†÷¦)ÕåER�w3?¾3å~ôóž±òIôv¢ÆÈD·›å“ '0Ò±ˆ·6øŸBÁ áð ðè£�ÚRŸ~úiÙ¾ÝîåÖ!ƒvh1�A“]! HM´˜NÍë‚VÕ;æ@‹ò¢�嚺½cËH ¹æ×”k æn£Òv�É ¥§U6î;°ä‚[ê /´Jã,#.´ÓE%bgnV·®Me-ÂPFà ×KÊF ×X¦ÃÕEø…¸ÓNWÖ˜ð…ÕÙÇo˜íuº¬Úzܰêö:ˆöŽÑxÕˆ Œ§9ºß£åÁjmEœìÊ‘ÑÅS¶º ‡R{Òèbu×=DΫÂe¿*-¡L· Á[ž1b¹ZÓ±ÝÑÙ-¯ª«\/A{á~®µ£ÓÓ½7©kTù±lîX¯Ó=Ój.ÄòÆÁUV–äëÄùP½–žâÏ)÷yÕKoŸhîp—Kf§ žw¨ÉcgþHöq�™b^SLd5¨«éý'š�øäæq|#^,\…Î×ø¼ÉXX&êYJv_ü¬/‘ÏR2ÆH?Y„*ëòÙU2cüHù˺ÃzÛÃ)Ì™T&‹5Áë[Ž»Þ¡f# aå>ÄX(¯Pdc|õ²Î6Û†Å85V`´ºøŽTü¿Ì:Ñvx�xMyÉ8�u9~T‘á™ý�H¸Ð^e%kŒô‹{²¸x±JDÚTuÓ�%mæÛJw¼3LA|öh%�ïGk_ýºg¬e&k;cd²ÚÎzH€ÜŠòíóDõçÌQÛ�—)$ �¦¦þÅhæymmö¿­ÍôLû¦b:Ó®(ûC$@$@$1�Ö.û ÄÁ¥� ÄFqœMÁ¤ê¿ýz½¹k|›±úFèûn›'å×ù¿_¹×pÍj=ÁË"wåæã‚XÄVÁsûîËjdÉ´Jk²¼¥®^Ÿzã€Í²zÇáCAXSUlË‹�cgÜ?ÑÆ÷\^#STñ‘AœdW¡$\¢1AoÕØÆP ›åÍžÙêRœBù<ÏC‰ O§"e!ë�M („�kÚÉðÕ Ge©ºØµ¶ÁlK¨o¸F‡‹tsì„“NËx,@Ÿ¼\†'š;\ { b“:cçzå‹%ÍëšÂu;êÃ5°Ê‹êú8˜ÒÞšÏïíD>K~·Õ,ñé¡8v ,Õ�òŠÞÓXÈLªÊ åŠ9U®Ã‰|–’1Fº:”€<ï·ë¸ˆ…&XÜñÔû=}ÇS õðzá\ töo‹—¼šuï ÞÏêYõb�zÞѰï¨Ëãó–…&P^?±jŸ|Fß)ÑŠã—Yg"Ú¾IgY”¨ Ì;tÌE,i«àÝö¸zqǶ·æòÞNö/÷dqñ¦å*b¥c‘žùnÀ»É*ˆ/­¤óýèÕ×xï¯2™–¨12‘mfÙ$@¡ ¸âË;æ‘BŸÍ£$œ€W˜G/ã¦à%¤ï‘Øý[yôùå ò­ËŸ·ÿèq“H€H€H £ xýQáõÇGFCHÑÎ�wÑÁ ’B$�°zƒâñ†­JiÔ¶lžÛZ×#ç#¦¤U0Iÿ‰g»”ÒÈEëû®žjÍnl› rçÄ-uʇ–O¸ÙL”RÎw©’Á©FœÐn�gX#[Ût¬®MàÚ*°|]µÕíÎöšÕrÓÅýJiœ3V'¿?ûîù®rᾊüh\#ÙTJãÜ,UÞ¯¸h‚x)þÏ´xû¼îÑô3Ú¼Á®iQ^Ž|F¯©•Ê>ÕØ.»5z2%ÑÏR¢úòÆöZ]ÀrÊõC§:ÕâÊg=Ê-§ Ô³d¶#Þ1Ò,'Ñßðvaz?‘o�¯X|‰ÆŠ9T›áÆx¨…÷goŸ/Ãsûñà¼#êÀ⬠TQ�c~�_�ƒlÄÓö•jqî”ë—Lp)¥‘gÖ„Ryÿ5Ó�ÙSnßîÉ%ˆòLÔ{ß/IÇûÑ�{Æ/~‘–“ì12Òv1 �@ìà!Ê*Îy$ë1n“@4¼æ†½æ�£)3]òÚŸª8ZýýWŽÊGÞ.ßyáˆ|òç;åÏŒ£4žJ$@$@™CÀë� ¯?>2§ÇéÓg\Js¢5}zÀ–’@z€µï(—«•%yE‡Ù£f‡et½Za[-å�oéÌÑ!'mç«E︊B³Hã»>ˆ‚Ôê†Ù<á@­Ý¹™î×7ÇÁ® a%丶J}ó9›U8ŽÁõøÕó«­ÙÛP-œb·.ÇÁZG¹�‚lÜ  è`2GÝg;íô’�àîÕ¿ÒB]S(Ô.v¸§G½uÆëö«�‰~–üjg²Ë¨gÉÚÏxÆHk9‰Ü. öÄz,S!,Áu‹ÆÛº…¾Ó²Ø–ÁcǯñË£è IÑ´Šö“–� ( ¤.›m·”¶V6C MíŸÓZ¶_ÛñrÏT.P¾z ¼µ`ÁW"$]îÇxï™D° W¦utæµKÄé¬�û$@þp>¯v¿{þÔÁRH`°ðM1ýÓUöUú?^y\zœ³½ƒ�.ûK$@$@$�Òr ño—”¾\l\@ÜQ/ÉÒ_û86Wã›L²[¥AÝA;e‰‡’ÏšÓ¹ÓÇ•Z“<ã,#ƒ—®ƒ¿ó‡�²R-µ¡ŽÖ*ÏV±c'wh¶T;”æŽ,®Û8Ž˜ÐVñRÈ€!,£‚É%ªÐwŠW9Î<æ>¬¸ã4˜”ªòÛ)^.˜‘'ÙÜ�íòs?’kêuÏ6´Ú¯©Ÿmò*+ÑÏ’W�é�æõ $úYrr‰gŒt–•©ûãÕbÛ)N%®ó¸ußÏñËZn$Û‘¶áœ“ΚP&pûJ&�q‡©•?™Çüàž‰\p &ꂼ?œ2=ˆ%µ3_¬û©~?úqÏÄÊ†ç‘ �€•@O�]íœG²æå6 DCÀËhÉ˸)š2Ó%¯o�[±‚ºô�íìÜĬîKÀl' � � �@ê(È"gõïSÎuö¸\êšÇøM$X‹ÖT•-äî0nH½\AŸÕ¸½»ñ� ¾±U |‚‚Ù¹òÀûŽ7Y³Û'êÛT)�ª 7̰H›¢ý˜¦Šóx\Õ–Ù]àº*ÖX49Å©˜vöùÇ”æ;O³í�ö(׫ÛI–�‘aÚž�åXõc9×¹™lîÎúýÜ�äš–�ÈsUé¥ueò1!ÑÏ’�MµõAu­^ßÓN�{þ�GlÉpåeO0ñZ<áõ $úY²¶/Þ1ÒZVºnC»AcI<Ùl,Âijë4âÓ#–4â;" ÄP‡;Mô5šØÊ~Ž_VÎ~¶½Ñ± õŒ3®#�×}�ôT?¸g"\XÃO[,ˆn•`–ÔÖ<Á¶3á~ô㞠Ƈé$@$ �s]ö¿?ó‡Rß ?æ N€Šéàl">âe=àœì‰¸0f$  � "àµÚÍë�� êrÚte¤†AY»ã¤|lÅL)-Š|ÙëG¨³M؇îKMÁ«x¹È†ûËp‚ñH¦Ý�~Ü3±òô2¾Šµ,žG$�þšÎÚÃî`‰Bƒ�Àß>-çºzåæùåR’~ž &þ–MÍÌK$@$@ƒ„€×j·�ü¡=H°GÔÍqöõ-óéˆJ`& �H Æ©˜…Ò7\›¼b9âX _»pœ,›7V¶«K͇ëe�º ç&®¾ùò.ùì»çGm9®­ç y§âV–Nis¬êwǾ3,³¡IEî‰âKië‚Ô3²Ð} U¿Q_ž¥D¶?Qeô³ï™(.‰.÷/ë…TJcñJvöA|G, I%ITÛ †¹raŒìB.¡ïÞ�¡ùDsÔùžŽæ\æ%È<õÍöy"ç(à”Ž ûO4˾MÆ·ÓjyŽœnĺ]0¥»¾”˜]Ýýn¼Q¨S‰éåê3˜+\³Qp}luŽtX{ÛGD3wò¾S…{"{ìµjLŒž:zzí÷F¤íNæ³i›R!ß@?KñŽ‘©À0Ú6têøöÖ®“®Ó`9zéÌ12®¢Pò4ìÆ&¨¤›Õ³Í>‹ûÚn×9ÉNHdÛKŠÜ‹UZ,ž3’Ý×T©�\‚_ Þ�ÁÙÄr„Ï[,Ôx d.ç ô2Áu®™€ãNñRÊ9ó${?Ü“Ýg}›öŸq&ɘÒð!$r<¬%›ƒÄâuUàHÈgÉÑ””ÚÍäg)¥@[s ¶ÙåAà’™£å®eS-¹.lâ/¶õ.P0<¡ÓX®zƒ%$²í# ÝÖ(§4tD8‰ä½®ŒT>N.Á¯ïÇàl¼Ž8ýYóàï¼f‡Û^ëqn“ >Îy¢%öy¤ÁG„=Ît-î0jMíî´x8ÐP<ôx. � � D@Àk’ÈkU\E1‹Ïæ–ÚP«k“N�±J!H=j1mbE6ì;-‡N]p/•¬cñ-¬©/Ÿ]åªÒËÖ•éo §›ì.á¼ò½µÓmI‰ÅôÁ“-R«Jò`²f[­ë�—RΕiüâ>€]ЉîNY½í¸« ‘XL¸-(Õz4I•g)–¶'ò¯Å™ø,%’a´e{)–L²˜]^!$‘mÇ¢+„8°Ê¶Cõ® ÖãØ®‹à½â<'�öÉ%øÕâýœM¾Çb–:‡[^ëÙÛ5ŒËyþ´"á6 jð`uÂñ»jÞHû;zPbç3’€—Á±ß³©˜ÎÈ[‡�" H%TL§ÒÕ°·¥lؙ氚ޯÖ; �Ô#�;4K¦Œ-±5 /?·Íp£m;�„�&¥àðÜìˆkÆùûŽ»-—Í `Ø´¿ÎÜ |�U×¶V©._½jO®Ù/�Ýî…6öž6\’[ËÀöT[çñTÙ�—û@õîÓ÷×Ý.×ìÅyYL�(Èu5דeÑJª=KѶ?QùÛ³”(Žñ–‹g%˜`!Ò;{N;<àé~µ“�Ueö±¾§·O^Ýt4hžaã>÷;#è ix€\¢»h¼/ð*‘çZظóHƒœñPNã9zucðç,º+ÀÜ$@™@`¿ÃÓÔ,µ–.Î¥b:®m*ôÁËhÉk9ÙmÕàg®*ûŒÀ:®ä˜|SL#Þ�SÜÍwæà> � � d>�E‹¹:9þ|W†Àe•ö?‡v§ˆ%ÎÀÐ`­$�Úî¼rŠä¨‚Ú*PÌ}ï‰Mò³ç·Ë^Uô¶Ÿë²LæŸnj—mëCZW¿¾å¸¬Ü|L`•kcđƹ¦ôêv«ÆøD9�½¼K-_O˜‡ßÅî8Ø�ƒ?q‡¬Ù^kS.b%òžcMò½'7¹”˜“ÆËÄQ#l%!föŠ%liØALì>³Õ`Fˆ“ýâ;Gä7¯ºc³"Ž+bº„ ÷döVW;7È·Ǹ®Öºñ{ù}WOsYFZó˜ÛpaìLª?úÒN©U¿¸op·bAÖO¯= «¶º­³Í2ù,™u¤Ûwº?KéÆí­!œòŒÞ»Î(‹1öþèÙ­¶ñÒ<.6�œn5Æh<É�D·ýÚ…Õ®n`¼üÓê}.pãüÓ¿l—£u­®s2-�\¼¯(ïGo.HÅb,çâ.,lü‰.lÜ®óá=�¿“æä»ú÷$Æ/Áߘõ-ç¤×o“1¯Ê˜F$�2v³{jqÎ¥LCÙ�´$�²ŠiŮ߯¿ÔΓ–· M$@$@‘¸ãŽ;äÈ‘#òë_ÿZòòòäþûï—yóæEv2s%œÀU£³äç{û-Π@¸ýòš„×Ë H “<òü�ëÃP²n×IÁÇ* §Tʇ–O·&…Ü.WëÒë5¦ôsëÙòA�g”×ùƪÅp¯N6vJ;¤Cùú±wͦK`!ÓæPj#ÓP�í +­PñÍÂjªŠÍ͈¾1 %ÃSjÝ Å#Ú‹pÁ\H¢ï^ޝm:f((­Ç1¹ EN8¹ñâ‰á²$ìx¢¸còø?~»>pí#éÀ³oÔØá]Y'Œ*’ÏݾÀ•îL8XÛ"ÿõ§�2lh¶t©ÕUƒ¶ ‚)Ê.ŸS%“FÛ8Ë4÷áj}ly¡8cÜíÑɲo?ÞhÜ£ÙYYk/SPö•sÇš»¶ïD>K¶ŠÒl'QÏR²ÆÈ4Ã-pc�ñÕ:Ñ»ÿþ›õ/çtLD¨ë½�¥n®±XÈ”µ;jH±zøÚ=KÍC ûNtÛçL*7÷'uQ‘U°˜i­~rs²Vþ-í]®YÖüÁ¶bŒ Ö–hÒÍ%š¶¤RÞt¿ÍrÎÄryc»}A!ž�G^ØqÕ�¾¸ÓÈ %÷ÿJÂqØ‘H ¡Ìß—f%ËtþˆB~HYÅ´Gûÿòö8CŸ¤ ñ  ˆ–À¿øEY·n�¬\¹R>øÁF{:ó'�Àò1Y2Ìâ}·¡õœÐ�w�³èŒ$àå2:’ŽÆrÞÕó« ‹`u@‰Œx¾êÖªð¨o>çy Â^Jid†2$¥ô‚š _YäY¾W"Þ¦À¤°È¦”†óiÕvWææùPìÜvÙd—5¹y<Ô÷<�—=­zd¨, ;–Hî]êÆÜzíãéDgW¿²7T9¸Ÿ¡@Ã;‹pOû?Y­ßoZ:1Tq®cËŽs¥ýÿìÝ|dgy÷ýKõÞ·÷æíëÝuY÷ÞÁ`¡š@ !‰IÃ!�–<„ðæ¡=!BÀ'¶)¶Á`â‚{]Ûk{ûz{“V½w½×uF3šr$ÍH3šö;ŸÞ¾g¤�ÿ¹ïÛ7î50¸³ùöïÙD]<~–&:^*,‹×ÏÒT~×™×T·Kk;Ç<}‰ãÒ á%ƒíw¢µ%mýáÓíAŸm»G¬™3î% �÷S7î&SZïs×Ë”ëô¥!»ÞÐήÐþ­8ÝÜ=¥PÚö—ˆß‘¡×1•éx»L園a›Tÿ<ÆÛðÚ­ ¥P_XŒ¤³ïg®�;îªÃ3ô;fÜ`̘€Õ”X‹K‘þ¹jîØßp3v"(m’6˜vûþã¯Ø1ûIr«ûÜ 6m?E\ €¤¤€Õ |Ãüà¯D¯îOï6úRòFqÒŒ XI¹�ß´^Þ{ù*)- oww"¨Æö×Å-“x®Ìܼ¢NÞsùÊ€9“�ZiV '#éÎ=k¶|ôúµ®jmDßñÎM2Ë¥j\· Íñ =>põj·Å32/î3raÄ>³öÙýý·opB¹ V [´^KP®YT6¼íúrFhX¸n<~–÷Ÿªã©ø³”ªÖvÞö{Èš)ˆ´»é¼%µËéþ¦³^¼Ï}ÝâjýݿΩd²ó´Ðí*—Z5¬duºu¸¸ßQ>�î.6·¨ Wn½xED/ðÝvé ©.+g,A�Œxõ@ðs¡æ§ß¿©s3“ôB‹ŠÂ›µ)(Hü¿An¹®[þ;Öà§°ÓÙÛ"€ €)*ðö…Á`¼¼¯~ÜR‹)z‰œ6q°’:S馺�kËÊ:¹óÝ[ÅJ}έ.‰(ä³ã¹•H¶’»%úÐ2šÎªB¾fËBù‹÷l•ß¾b•Såw4Û[(øÑÖÉ6-ù—ïX{·³«Šä·.Y!ö�4°„õxÇ™]U,wÜr¶\¤UDÛù¹•´³ðbùÜ ù¤£Wh \——¡ƒw¯+„¶ë=Ù}³öC;·p$žî¹z¯Ý®?ô¼"™ÎË ¿ƒ uo_y±v¼í³jŸYûìN¥³ëùÈukå–‹–OZò˪]£¡V`;éã3–?Kã#žó#ý¼Es±þYšìgf¼s›êvãí/Úù-9h¿«»ÀŸ‡Àq['ð^8?ƒ�Úr—Ÿ%Ûÿï½m½órÏDa�ÕHñák×È¥úsä¶Ÿ�Cy']~N'3 ¼ß>Ý~Ù²¸žûèÁW-¨”O½ëlY½°jÜŸ{³ù}yÉr¯lÕÚ €[×ß‘qv·ë‰—‹›U¬æ…~ö"ý»þºü;›ÊŸG·SC½BïC4?«¶íÆe5ò§·n–eú"Lðo5ïž­ÉŒ\µZìeâ‚ÈJW»Þ›€ßs¡÷8ðœ#ýzÝL#€ÀÌôhsPö\(°{ûB—¿ W`�(n½õÖ -Î9ç¹à‚ ‚æ%bÂíßɘ–,Mºc²ÏEþ´¶£¼«#_¾Hr=n—‘Nމ €S¸ýöÛå±Çóoþ½Ï|F®Ø¼Ù?�#ç=Ø/Ǻƾ˼mÛR­^Ò½mÎt¸^®�t°ê»Ï´uk)Ñéìé׿EÕÔ-v]v¶OkË:Ùºx¹Çû:-XïêpªìîÖj»­]éA�gC ‘í!w­ÞËRýÌÄ£³Ï{}K—óyïÔÏdŽóY­*) ¦§ÓM÷gi:ÇNÆmSåg)í¢9'ûfMX5ôí:ÌÑvÓí÷¶µ¥l?O¾Î~gX� öÂNxŸ%öbÏLw3uîÖìɦN±+Ê´†ê²BéÎ{žØ'/î ~ˆ~§¾òÉɉìå¨xžß=/Õ˧îÞtˆÛΙ%ÿ÷½ÑÕ´ƒ�‰˜]¥}é ífþO�Ð3`@ˆLà=K=òå7ý+?»ó$Á´_ƒRC \Ã8ë§Új°· 6òv¢§zœÐí,xY<«,tö´§­´Ñ¢Y3=Ñžx¢Ü£=ÏÐõ­äSžfš%¢›îç}¢sŽç¾':n².K•Ÿ¥dõ‹ô¼ì’…­ÖOÔÙïŒB­)#™º™:w+UîV²Ü‚¶ÝGšƒH|/ÈÍLÓ \‚o,ŸÇ`�Ð)û�>Ñ÷½düz L#€@|žÑçA�Ý{õyñ°RÒÖ'Sç–ëºå¿Ó9g꘎Û"€ €@Ú|pyvPµnVòâùÝ§Òæú¸@@�ôxòõ“Ò¡58veEùþ �ó3i—ÄÜmÜãÎQ@ 6Ïî<¥5³ôùw–£)Ýû—£ùAI{�”jcº,¤ý�¢¼ìŒÿœöŸP.@�4¨ÎÏ’�¬ ~ ö¾'¤Ñr) € €é$`íÈÿúå#òëíGÂ.këÛ³ÛQ ÎÀ%17 ÷ĸsTˆ­ÀOž~ô;úœ¨RŸÑ!€@ìbV•÷G/™+ÿô˱/»džÓ®VìN•=!€ €ñøø*�|wßPÐAí¸\¾i~Ð<&@@ˆ·ÀˆÖ›hÕt÷i›ÒÖ®´ ­·öqO4uÉ['ZåL[OØiXÛöWœ½ l~ºÌÀ%1w÷ĸsT˜9�G_=v°�és":2IÀšƒ íì;@,»˜ÓŸºz¡¬™[,¯딵óJäúõ3×Py,AØ €d®Àüâ,ùÄYùÖž±pÚJ l^Q7­vk3W”+G@˜ŠÀçîzA:ºûe*ϯ;w±äç¦çƒt\¦òišþ6¸Oß�= €@r ´tö9µ�žå'W{dn‘KJ¸ã¤™€Û'>ÖÁtL+Ç¿fmµüÙu‹¥ÓìƒÈå € �Iw¬É‘²¼±+–_¾txlc € €Ä[@Ÿ FJ[ — ×Í•sWÍŽ÷Ù%nÿ¸$Æ÷ĸsT˜1�_½xX¬I_W™/bχèÈ4×6¦£þV:±?Yû°@2L \Cé?[—#ûÊ ÿÊ_Þ[/kUÉú%5þyŒ € € /�‚Üi—þˆw?¯¦Dn½d…,¨-‰x›T\—ÄÜ5ÜãÎQ@`fvl”íû‚fÏ…Jrƒf1�@FdëËh¡]À;¡‹¦4M0=%66B@�tøèJ�úŸ»å¡×�Óþüƒ‡äî�¯“KWÅîYfL‚éà¯î^âÉ¿‚§Ú­à|@˜šÀððp؆ÙÙ|9CI£gWgËO®Ì•Oj8ýbãØýoïî—oÿâM¹ù‚erñú¹itÅ\  €@¢¬tÈüjgÐá¯;g‘\µyaмÀ‰_½tD^?è}Ðà›ÿùÛ·IaÞÄ�:ôß±Wö7ÈÝöhCp©ß~ÆþõûΕ¼’‰ƒi ]­ù‹cg:ÇÛ�3ß^ðz÷e+¥Ü¥­d߆ÿþÐN É݃éB Ì?ÿámbAïƒÏ”gwž«2´ËÖpñš-‹äʳHVrØ©=»ó¤<ôâa± z¢Î^ Ø´¬Vn±/¬zÊñŽi¥ÄŸß}Zöh ¢^½ZÖM�Ž·ŸdŸïÏc<¯&?ëv±ú¼§²y<ï'ûF˜Y{aïÞ'÷ëwÇÓa¾uq¶|íüܰùÌ@ Üž »=Cžésïê ¶­'¸&¡éžSlŠk…¿X,“Ö«5Ý3g{@RDÀíK…Û—�¹N3 {?÷Ûrå�׆—{Aõ'¯É‰Æ‰K…Eq8VE@ .VRô{ïšR(m'd¡´…Óãuߣáëx%œÝ¶³°í¿Ý«U}w»-žtÞ-ñý¿¯�t=[á ¯­Šò™î,”þ—û^‰8”<¿ÖÎ>çžYëéÖÅûóO¯D|Öíz¦ûyOeóxÞOö� 0³ǵV�¯þäU×PúÏÖy¥gövp´i ¸=v{†<ÍÃD¿¹ËŸm.•KE¿ß€-f¾^§€ƒ3Š €@¦ ¸}ùÈT‹L¸î?_Ÿ#Ë˲ä�_”þ€çÚÇÎtÈ¿üøU¹UKNŸ¿zv&Pp� €)(ðÔ'œR—n§níÏÑvŸ‹ r%?×#¹:=¤O.4ÈíÖðØÚl®«,tÛÔ™gaô¯·»ÄÕeb½5ýkmñ6µ�µ‹m[õÛ¿xá�|亵aû߸¬FÛ½ö¾Ùß­í`:ݴΫœs´™v ‹´dñ\m›ÛÚ´m³Û®cÇ[gäœU³‚ö>uÀ©nÜí8VM·µm}ƒØnå¬Úå_hõßéVCK¬?�Öúº%Õ2ðÍÌwi ¢·—+¬ ïÉºŠ’<×UâõY·ƒÅûóksW f"€ 0�€½ÜõcýnÚh9€9/Wn^›2˜¡ûg�x ¸=N†`Ú%—Ž9ÁtÌIÙ! €Á[·n•'Ÿ|Ò?37W~­[çŸf$3Þ¹È#+˳åÏ^�ÍÁ��ïÓj¨ëó[.Z&yúPŸ@�d8îR»GMy¡¼óÂe²|^…x´-æ©vOì8! vºÞ|Á2Ùº².p¶¼ U6ÞÿÌÁ ’Õ»Ž4Ë['ÛdÙÜò u­ j_׬¡ö?ü×K¾Igèkc»¬(O~ÿíÄ®Ç×ÝóDxõ� ­S+™íÛg´ÃíÚ–÷[Z}zhWYš/ï¹lUÐõvhø¿çh³üôéàªÐss²eÅÜŠÐ]¤üt¬?�Ö.÷‡¯Yæò7ÿù\Ð sª‹åöëÂ× Ûpœñú¬Ûáâýy�µù8DÌF@ LÀš)ùé3dû¾†°egWgÉ?Ÿ“+«+¦þ]4l§Ì@ ÃÜþ´³…cÙÅä5·sâWA,oûBHe�;î¸C>øÁJEE…œuÖYòÍo~S*++Sù’8÷) ¬Õ?–º&OÞ·,<|~y_½|E«ë´Ët €$“ÀéæðPö}W®’U *§J[©êßì8t©ö,áw®_JÛJç�5[n»lEÐú6á ™ÃL0ÃŽm%¼?~Óú PÚ6¹dü°-Û»‚Ãó°b8Þ±üâùCa{\P[*zë– PÚV*-ÌuJsìÆuÎ5Ù¼ºŠ"¹ã–³eÓòZ›L«.^ŸÇx"%ò³n×5ÝÏ{*šÇó~²o@�™°ÿ?}NâJ@Ÿ«üâê'G¾´5GB_ä³*Jÿý—;åç.££; k#€ ;«2;´;x*¸jìÐå‘L7iIæ¾�¡ UÏÓ¦-Ï* š8±qY­,¨- œ¥U|÷MG:±ai�Svèúu…ú<¦-¤Twè6±œnÓö¡Û]Ž÷Ž —Š•î¯[2»L~÷ÆõrÅÙ äSïÚ$³µªïtìâõyŒ§U¢?ëvmÓù¼§¢y<ï'ûFˆ¿ÀÏ”ÿøÕN±šo;�>Hqž©ès:RY iƒiÔð¿]VŠbVL~zÝÒr7Ô(΋U@@�´øÀr�l®É–¿|yP^j nÔÐJ�í?Ñ*oÛ¶D–§aœi}c¹8@ ¬-ãÐê¶Ô‡…VzeËŠZY1¿RÛ™. s'£hi3ÚÖß:I;ÎöRתUrìL§÷¡mOûL2²eEpUá¾Õ­-a[Ö70öïó¢Y¥¾Åqži ÚÏÒÒé‹&ì}'eç9“çê;îLãõyŒç5$ú³n×6�Ï{*šÇó~²o@�ø س ¥O5u…ä¼Úlù‡-9”’“aF* Ž5%ä;ÿüü|ßh†¡/èÚ‰¸¼§<­ó‹I0=­3`c@@ C¬jïû¯Ê•/î”oî.1vBÛóüÖƒoÈeçËMç/ÉP!.@ Við|@†v'›:Åz‘CbíB[‰]{¡j¥†¨‘”Ömt)éÜ¥í%ï=Öz¨ éÐ ÚJÒØÃ·‡(ALXÉãe¼üõžËW¬=³£gZÃéù!¥ÄgöŒ’ëhñú<Æó*ùY·ëšîç=Íãy?Ù7 €@ì¬DæÏŸ;$O¼ÜÌ‹ïH¸Æ#ŸÙ@œåó`˜ú7ß|³|÷»ßÕ¿c¼å‘-Z$7ÞxcÂ/,´vG;¡¤,1�p)N@Ha�¿Ú˜#ÔeËß¼2(;‚¿îYééÝÚîô�ç-‘5‹ªRø*9u@�T¸tã<Ù{¼Å5œö]SOß X;€ÖËsâ” ¾`í§]èìl·Ç¢UpWÍhûúÞû|»Œx84<"=}RT�ñ6eEùQÙï8+6´†·é]]^¢"‡JÉ]ÄëóOŒD~Öíº¦ûyOEóxÞOö� [��GšäÏ·ï@Ë˲äó›säÒÙ´Jö–h� 6ÈÏþsyà�ÄJO¿ï}ïs†‰>/·Ú°}áy¬Î-&?ÍÁ�O½§æþgg¬N›ý € €é%pùœlyüú<ùðŠð¶#ë[º�¶•î}b¿tõ¤×…s5 €3.mUlVµõÇnX'7œ»XJ" O5wÉ�Ÿ: ÿ÷'¯…µ è»à¾þAßè´‡Ot�7J #±§}rQî ×Åe¼p?Ê]'åêÉòyŒ'N"?ëv]Óý¼Çëw@<ÍÙ7 €@ò tjM9÷<±O¾÷«]®¡ôGôùÈc×åJ'ÿ­ä §(°nÝ:ùË¿üKùã?þc©«sofhŠ»žòfnµPEû}}²ƒS÷ÁdB,G@fH WŸ©[{I—é›ÀŸ}mP…”ž~aÏiyãP£\¯ÁÀ¶5sfè¬8  €@º Xéæh;�–z¾âìrɆy²Ó)Ý$û´Êí} 8QgU}ÿð‘=ò7o”Ðpµ²´`¢M£Z–—þb×D;(Iâ`º²$ÜÅ­�≮/•–%Ëç1žf‰ü¬ÛuÅâó�ßñ4gß €É-ðìÎSòЋ‡Åí…¼eZJúï6åÈUs£{ñ0¹¯˜³C 5Ü 'e0�£ æçdKßà°_¶¬�ÌÛ�Á € …À5ó²åŠ9yòym{ú;{ƒÛžîÖ0ÁJ =úê1yÿ•gÉbmÏ“@�hÚ»û£Y=hÝ-™¼qi�ÓÛ«Õã­“mràd«3t«ÙãhC‡¼~°Q6-¯ ÚW]EQдM¬^X%‹f•†ÍŸh†•æt{³¢mòó’÷™EmExµÝ�máíNOt}±X64<öŒ'ûoÉòyïü|ó‡¦î‘ÈϺ�,?ï±üà³eˆ �9‡NµËþw·¾Üèþ}ôã«<ò7J�ÓLæ@q¥$HÀµ*ï·2“¿Äì­Éß½tž|ýÑc~ªß¹x®œ@@�èô�?ùìÙ9b!õ?h@ýjSpã)­�}ò�ûwˆµßyíÖERaÕªÑ�k#€¤¢À@ÀKã¡çoo»·uõ…Ξòô¬Ê"±Þþ=²}¿®5{üüùƒÒÒ|ŒM].Átx[V”'Wm^8åóI‡ Ý‚ik{Ѫ»ŒEÉW7£\—ªÐۺܻm?ѼTù<†^C~®GÛ.«] ¹£×y$çVŠ$tÛÐé:—— Òå³>�ß¡NL#€¤¯€}�yøå#òÜ®S®¹¥:KþrcŽl«£”´+3˜!·~c]b:f?培q±üû‡WËŸ\³P¾ÿѵòGW-˜!&ƒ €¤¯À…úGÙ/®Î“¿Ò?ÐÜÞ¶ê¯þñî—äÉ×O¤/W† 0®@QAøûæg&(]kgß@pmãî<ÊöÃJS_¸6üEõ¦öð¿µZb:4ä{å@ƒ®o�òÈéµzmyxIòÞþ!­îòPÜ.´¼8?lß-ú\´]*C¯µº,¸Ju Ø´†€©t™òY�öwÀT,Ù@ õžØqÜynáJÛKù­Ï;Ôç„Ò©wo9ãôýûÌ®0¸¨Ìô¯9fÁ´�Ê jäÏ®[$W¯©šþ™±@@¿À'W{äéódUyøWD{XýÀså+÷½âT•ê߈@�´¨.+ wwm·ªŸ­*âÇ´)ˆxwV«GhW�Þtž>‰\>¯"hU ÿþý¡7ŪÿÎÔά–¸4ÕñÒžzyä•c2<ëGC"eÅyaܯìoÐv£{‰!•?�¡5ú³ÚÙ÷­©t™öY�ôwÀT,Ù@ uv¼uFþùÞWäÁç¹¾¹º"ËyÎñûú¼ƒ’C`&JL‡¿Z�×ÎY € €„,.ɒǯϓ‡O Ë—ß”]­Á¦Oi5©ÖVÓY *�jPi:�I@ ,ð²P1°Úe w¿£áîÛ·-u‚_«zíxc‡<ðìA9©ÿV¸uûO´Ê¼ê©,Í×:‚_‚²Z9Ft'Õå…R]Zà¯@Ûh¶f½¬³ ÔÚ–>Rß!¯êH{Ú¹•¶uÞuñrçŪÀêž- ýúO_“Õ‹ªäâõóô¼Š¥( ÉŠ!=ž•Ànhé‘’¢\Y<«,ôp)?}‹ºüË�_ ¡í_ý_½tØy íŠMóeVU‘Ôê=±6í[IøV­¦½E«œ¶`pÝ’)-Ì�È¢¢$¼Ä´½Ü`ß+Þ¶m‰Ì®,{¢±µGŽ�éÐÏS§Tê6v»Tÿ<^K�Ú†v{�µÈ×¶C®Þ²PæÕ”8U«ÛOA¿þ̵«½¹[u볫Ô+¤KåÏz<„01‰ �ÖŽô#¯ûwÓ­[W™%¾>G®žÓr“n‡bD)ú· m>㺼 ¦£¼)¬Ž €$ZàZmwúÚyyò¯{†ä+oJ÷Xó‡Î©íÑ?þ¬ß²²N®<{�ÔiU©t €é+°nq�<³ódÐ6µ÷Ê÷Þ4o¢‰üz·³ØBî¿}ÿyA«Z)ëN žC; D-à •C×ñM/›[î Zøwµ¶)ýЋ‡ƒæ[»ëH³ÓÛ‚\ à‹òsœ°·Ð bmþº%ÕòákÖØ¨¿ûÞ¯v‰UY>Q÷âžÓb}`wöò:yß•«g%l|Ž›—hèû­ú2´;ÙÔ)?|t�3ÛBQ�Þ+ ÚYõÜk4Ü�¤«Ò,h=¡�s`·ïx‹|åÞç>{²ƒ�c¥ºCƒiÛ6•?��×¾U¿G=ªÕCK�Ѫæ­T¿u¾—3ìe _·eE�¼÷ŠðÏQ<>ëvÌ™ø¼Çów€Ï�! €@ê Ôk“öoÆv­uÅ­+Ñ4êOÖåÈ'΢„´›óH�ÂÜðFŠócû3~„d¸rÎ@@`R�ßÓ?æž»)O>´Âý âö} òOÿ³]~öÌ[A%é&Ý1+ €¤”Àµ[J¡†¶‘t&»µíÛvxh,`³yVÔ-”¶e†FJoZV+ ëJm×î²�óeEH•Þ¡+Úq¬TxOßX(më4µõ†®ªç]õÓ¾Lu;ßö±^£÷ÕÂâ‰:»[n¡´mÓ¬%§£éìe¶ñ:+$zœñöŸêŸGŸAiQžÜpîߤëÐéÀPÚVjšÀ=ÖŸu;ÞT?·‘n7¿ì:è@RW Mk ù©>wøò=ÛÇ ¥o×çϽ-�P:uo3gž!Kj åmk‚®öýçÏšžîÁôtÙ@H @mA–üã–ùå5yZŠÚý«ÝÓož”/þèEù¥–F³út €é%`Õ\ßzñ §TñdWvÛ¥+¤º¬`²ÕüË­Zèét›µôè{._9á.²µJð�ß´^Þ{ù*±00š®Q«ôN×./Ç#w¼s“¼íü%bãÑvÍZj>šn½Výi kÛo»¾(VÛüTÿ<Ú5øºmkæÈÖ•³|“ Ç ìmãTü¬ÏÄ`Y @ éº{å¡Ë~ø¢<£ÏܺëçgËÃ׿Éõ¹Eu¾ÕõB‡É.ðoZ-_ú­åòW.�ÿùÄzù­s¢û><ÙõEöJõd{a9 €L*ÐÙÙ)%%—z™t'¬€À8«²ä{çÊ#'‡å«»†d{cp•žÖÌ£Z¥ÖSoœ”Ë6ΓK6Ì—‚¼èr�sxf#€$X`ã²-][,÷>±_žj“àrÏ"VUó�ç-[ïå}õŸ­•–,Ñà{¼RÓn;²*‹7/¯ ¥ÝÚéuÛÆæYkWË#¯•}Ç[ÅÚ8ž¬T§¶Ö¶r~îØ¿iS qíøSÝζ�WgAæ¥Z¢|£–:ÿ¥¶/}TÛñ¶¹Ö®÷D�]KVH[á­oËlõ�\·VžÝyÊ9ÖD/³Y•ï jK�ÒÂn™y:|}&öbÅÖUuòøkÇåðévçó6‘¥=¤Ÿ¬‹ÕgÝŽ3ÕÏm¤ÛÍäï€ÉÜXŽ �öáÉ7N8MŽŒW{Î95ÙòGkõ)yæ™gdîܹò·û·rýõ×G»›„®ûí·Ëc�=æ?‡ï}æ3rÅæÍþiF’OàžCCòÍÝC²¿Ýý«^A^Ž\ºÁêyAó“ïJ8#@�h,̵6þº4 ³q ¥­Íb_{¸=ÚF³•‚´j½Ãû,Év 4m Š;ºœR²VRÖž&øª/Öðº¤0WlËŸ¬úî3mÝzÌéìé—\�Gò´Ý3k?ÙJ~ÒÑ:¥êúVut“–?ÓÚã„Ïvorõ^Z;Üv?ªË ¥TïÅt;³¯oérìûõ¾æ8û¯*) ¦#íÒéóh/ú�lêÒjåû¤·oÈñ0 y Ì^Öì³éò#4)W²Öõ;`R8V@˜1�Þþ! ¤�Ë;NŒû¢ÖÊò,ùƒÕ¹uñØKƒ3v‚hR�›î¼SÞ8xпÞÏ¿ô%Y¿t©z¦FÎÿÄ'äTS“ÿpÏë[2§ºÚ?ï‘|á òäŽþÃÜu×]rÉ%—ø§‰¯%¦ãëËÞ@�/~ñ‹N(m'Ož”;õKàµ×^«Uù¹W» ±¸m‰G¬ÿ�}Pï’SÝÁu¯>Ä~øå#òÄëÇå’õóäbí}áB,ŽÏ>@'`!™•f¯+´€±:ºZ\l›‰ö9Þ±¦;¿\PëéÆìƒºŠ"§›û±XÙ§ÓçÑ^Ú˜¯í~[ë.VÞ±>/ßþõ;Àw|† €‰èÖÒO½~Â)%m5Õ¸uóвä÷5�¶¶¤é@�‰¦'Òa €1xóÍ7ƒöÒÖÖ&õíÄåË—Íg�x|d¥G¬ÿZzú_÷ JS_ðQì�ç_o?*�ï8.¯›'­Ÿ+eQ¶ï¼G¦@@@@ Õ¬6�§ß<á4 fµå¸uµYò‰³<ò{ÚÓ!€‘LG¢Ä: €LC`x8üË;¥¥§ʦS°7—?ºÊ#_~cP~ôÖ�´öïÆÚ…zìµcNѺ¹r¡öµÚF( € € €dŽ@ƒ6òÌ›'å™�'ǽèªü,yïÒlùôúÑVVè@` íííòÀ8µjÞzë­’——5ELOáÃÂ& €D#0b 0†tÓ! LΈ@žþ±øWsäO×åÈ·÷i?(Í!%¨íDžÖ?@­ßºr–\°vŽ,¬¿:Ø9q‚ € € €q8Rß!Ïj½}øǩÑÒÓZÙ>®/¾çQHz\' 0™@cc£¼ó�G�:«~ÿûß—ûï¿_ &Û4å—L§ü-ä@’]À-˜NösæüÒ[ @ÿxü£5§º­×€ú;Úu}Oø /ï«ëW/¬rjÒ!€ € € €@úì:Ò,Ïî:%{Ž6�{Q³ 5�Ö0ÚBéJH�ëÄ"¸çž{ü¡´m³gϹï¾ûäý碌HÙõ¦SöÖqâ €©"@UÞ©r§2ï<­µUñmýw5œ¶þpgx@½[ÿ8µ~Am©l[3[Î=kvæaqÅ € € €¤‘À {NËsH?Ó9îU--Í’ßÑ0úö�‰LA ££#l+«Ú;:‚éL¸Ë\# €@B¦ÊÏÁ#°?4­ÿïƒCò½ýCòFKx@}ìL‡{¢C~ùˆœ¿zŽö³¥´(3Ú¿‰�‘Õ@@@HZ�öî~y^ÃèçwŸ¯ÛP•%Ñ0ú¶%Òã1�édee…mž)µnÆ4˜~àµ3²÷t·l^T*W®¦ªÇ°O3@2RÀíK…Û—�ŒÄᢓNà=K=bý/�Ëj@ýTýpØ9¶uõ;á´Ôçž5KÎ;kŽ,šE;ÔaPÌ@@@@ ×·Ë F¿´·~³¹dv¶|XéëæQ_÷„P,D�) Ä,˜þûûÊ·Ÿ8á?‘¿}ûùÄeóýÓŒ € �©n%¦ ¦3õÓ�:×}ýül±þ…3ÃòýCò³#áµ]Í‹{ê�~ÙÜr§Šï-+êRç"9S@@@ÒX`û¾±*»žj›ð*oY”-\î‘sk ¤'„b!1p{6ìV¸)F‡KªÝÄ$˜ ¥í ÿã©“ÓIu«9@�D ¸}©ÈÎæ‹~¢îÇ�Nà<ý£Ôú?[7"wi@}×[CÒ=¾�·N¶‰õ½pHÎYeíPÏ’ªÒ‚𙃠€ € €qhjï•÷jéh}‘|¢êº‹5ú€†ÑH/. ¯V8n'ÈŽ@@¦§ù!èé ÛC{oø¼°•˜� €@¸•˜&˜Î€Ÿf—¸´4Kþîìùôúù¡†Ó÷ªK:­×lÕ|?òÊQ§_¿¤FCêY²fM¼¤ÙÇ�ËA@@H2��‡›ä¥}õò桦IÏìs›säýËîè Ãkî›7�1)1MUÞÓ¹l‹ �N÷;ÌõM$P¦ÿÎJ�Ó¿¨¥¨ï;<,?Ö�º'ü{®ØÛ/î­wúê²Ù´ÌBêZ™SU<Ñ!X† € € €@Ê œlê’×Þ:#¯i ÝÜÑ;áõi²ó. ¢o]œ-çÔP:zB,"�„nÁ´[ᦙ>õl—fšÝjBœÎyÅ$˜žÎ °- €é.àö¥";›?Òý¾s}áV•˜õ_Ðö¨|dH~¢!õ3 îo~7µ÷Ê£¯jUßÚ/šU*5¤Þ¸´VÊ‹óÂwÌ@@@RP ­«Ov¼ÕèÒG:&½‚‹feË-‹²�Pš¶£'åb’V Yƒé, O¦GÄ¥�¾iÈÆ$˜v;)·“ŸÆy²) €)+pþùçË#�<â?ÿM›6Iq1%@ý Œdœ@žGä½K=N¿·mD~¦!õO� ËÑ.÷/ºGê;Äúž=(g-¬Ò€ºF6hŸŸ«;¢C@@@ …zûµ©«ƒg´o”=ÇZ&=óE%YòN £ß¹È#+ÊÂC£IwÀ  �tIL»üŠq«5{: 1 ¦§sl‹ €@º üã?þ£s‰Ï?ÿ¼lذA>ûÙϦû%s}D,°ª\ € € �h�£ N½_ië£é¬ŠîË5Œ¾Lû•LÓ!€™.`¿ ?táù·ßœðS|ð‚9þñXŒLÇB‘} € €¤µÀªò,YUî‘�¯òH× Èo4 ¶ ú í�wM\©‘½©o½Èa)-ÌÓ�ºÜ ª—Ï­�겂´vãâ@@@�X 4µ÷zƒè“­Î°³g‚6˜B¼ 8K,Œ¶ ú2‘Ž„1‰ˆüÝۗʪÙÅòÆñNÙ0¿DÞ}²ð«7¦œì @�ñúúú$??üX‚)!P¬ß o\�íôvÂo¶Œ8µ•¤~ª~¬ª#·‹éèé—Wœqz[^WQè„ÔË4¤^>·\Š rÝ6c € € �‘<¿e!ôÉ6í[åLkOTNMXZ5÷¥F¯­ TtTx¬Œ+ð £­�GG0Uö‰ €@€ÀSO=%ÿ÷/�mÛ¶ÉW¾ò™7o^ÀŒ"€@* ¬«Ì’u•ùäj�tkij_@m!õDmSÛ57èCëŸÝyÊ!˜¯U}[H½LCêesÊ%?דÊ4œ; € € •@oÿ�<Õæ„Ñoi}¼±3ªí­­hk#úb-m¡t!REåÇÊ €@¼¦ã-Ìþ@2^à/þâ/äøñãŽÃsÏ='ŸýìgåÛßþvÆ»€@: XUp×ÍÏvz»¾C#N)ê§5¤¶ º­â«>~¦S¬b‡÷wÆâÙe²Tj ©m˜›“=ñXŠ € €¤�@ÿà°DÔÚéÃõíQ�}Ež8AôE£aôâJEGÈÊ €À LÏ08‡CÈ,�Ó§OûCiß•ïØ±Ã7ÊÒ\`Ii–,)õÈ—{_Ó©qXžÑ€ú™úy¦aâj¿�æðév§ìÕcŽTQ~Žl[3Ç ©—Ì)“¼^ÿOó�—‡ € €@Z ô É!ý;ÇBègwž+!Mg±ó…B_X§ý¬,ÙZÃË»Ñø±. �h‚éDߎ� �ÖÃÃáÁSv64¥õMçâ˜@à}hbý§ÖŠôêó ©ŸÕ€ÚúÍ#lé]ÔÝ7(�jHm½u‹fi‰j-U½DKS/Ña¡×t € € €@²Øß0‡4„¶0úÐé69Rßõ©mªÊ’ 4Œ¾ÀÂhíóy?7jC6@�ä¸ï¾û¤··Wn¸á©ªªJΓŒñYñä*Æ ì@�@‚é@ Æ@ P @¦\97Ûém~s߈ÔÖËóÚïi›<¨>¢ÕÜYÿøhÕßójJœ€Úª_¬¡uEI~à!G@@â*ÐÚÙçTÇíѧÚådStmDÛÉ­®È’ók½Aô¶º,©Ê§zî¸Þ4vŽ x×»Þ%/¿ü²sì¯~õ«òãÿX.\˜�s™ÉƒLϤ6ÇBÈ8‚錻å\0S°‡-7-°Þ[«ÂéžyÎBê3#NP½¿}ò úDc§Xÿô›'�ó¨)/t‚j+YmAõ쪢)Ÿ"€ € €¡§š»œRоfˆÛ{BW™tzEY–lÓ’Ðç×z‡³ ¢'EcHi�xÀJÛ…444ÈÝwß-wÞygJ_W$'O0‰ë € 0E‚é)±Èl}óÎEí½G:Gä•&oPý™aÙA‰êƶ±þ¥½õÎNŠ r�€zѬRY¤¥ªÕ•Jއæø¸!€ € €ÀäƒCÃÞz´æ&«–»«w`ò CÖXUž%çi‰èó4ˆÞ\�-‹J¢Cˆ˜D�4èèoÖ ­­-ͯÚ{yÓq›¹H@�D L'Jžã"�~ö°fQÉXP]¯%ª_ÐÒÔ/jHýbã°¼Ù2y‰j{h´óH“Óû„,¤^X§!µ3,•ªÒß"† € € �ÁͽN}´Ášê�£ áAJ$<ë+³ä\ ¢­?¯&Kê( ë €@ de…¿�322ùs�t ‰i0}ÿ«gä`c�œ·¤\.X^ž>\ €Ó ˜ž#€ÀV½ÝÛZï-ñÜÚo!õˆ¼¤!õK£ÃHþ¤±LÖ?õ†÷`Ö.õB-Im¥©çT—ÈÒ9e”ªžà>°@@�t°ÒÐo�j“SM]Nm!´µm—­YËÖ ¡5€>ÇÂh–ç…0Ñî—õ@ÒC fÁô�÷�»ž=åWùòm+ä}çÏöO3‚ €@& Lgâ]çšHŒ@…>ì¹fžõÞ z`Xœ�úåÆyYÃjë[û'?7{ødýëý+/Ð�zam©,¨+‘:œUI[Õ~F@@HA�ú–n9vÆ[ úXC§3>•˨ÌóÑFoµ0Z‡9´4J¶A�  Äô4ovS×@P(m»ûϧOLOÓ•Í@R_€`:õï!W€@ª äêà ê²µ·+ð8—±«uD¶k@½½ÉV숤LµÈ1--a½ìôjåç8µÕó-°Ö¾¼XŸHÑ!€ € €@Ò ´uõið¬á³}¯·¡Ò=}ƒS:Ïe¥Y²EÃç-ÕYNÉèÕ”†ž$!€@F LOóöw÷ …í¡½7|^ØJÌ@@ ͦÓüsy¤˜À}h´¦Â#Xî=ñ†ÞyEKToo–W4¬~E‡._íî²[bí=Þâô¾…V¸Ôók-¬Ö¾¦DŠ r}‹"€ € €À tõÈq Ÿ�7j¯´ÑS©’ÛNµ@ßoÝ\�­}–l £k ¢gà6rHs‚é4¿Á\ €‰ ˜N”<ÇE�Hêô¡Òuó­«kïµfoH}ï¡!鉴Tµ¯ ð7�U^S^èÔTÏÓ ÚÂêB-mM‡ € €Ó°FO8´7ˆ¶ñƶž)ïØJC—軥·-ñ8�ôÆ*Bè)c²! 0�Áô8‘,r«�²"‘c@�t ˜N÷;Ìõ!�~›ª²eS•ÈGVx«ÿnÔRÕ¯6�ÈkZšúµÑaKmU›Œ=³þµ·Îø¡j5¬¶�ÚÛ;CJVûyA@@ÀU SKBŸÔàùDc—7Œžf]•Ÿ¥ßûG{-mã5”†vµg& �i;OvÉ+GÚeYm‘\°¼<¦—Oq…˜r²3@‚¦ƒ=˜B�Ô°‡SWϵ~¬Tõ�v ªµdõ ª½ý° Gvmg4¨¶>0¬®*-pê¹ÕÅR[Q(‹g•‰U N‡ € €@& XmD‡ëÛåLk�œlòÑͽS¦ÈÓ¯òõT+m½½Œº¬Œ¢eSeC@`šÉ\búÞ—äŽÿÚë¿Â;®^(w^¿È?=Ý‘˜Ó#.E¦³øwmº÷†í@Ò@€`: n"—€aËõ!Öò2�ܺxlÑëNH=,¯·ŒÈëZ¿¡ÃH;{Èf}`5ॅy2·¦X,¬ž[]"st8»²(Ò]² € €)!pº¹[N6wjiè.9e!tS§töh›:Óè6TfÉz�-ëuš@ y’9˜þÖãǃ ¾ú¿G厫HAîX�… ¢œˆI0å1Y@�Œp ¦=oõ¸ƒÀ…"€@FlÐ’ªÆ~¿õk j_@m¡õ-ò«5ò°º£§_ö³¾Åï—ëÉvj ©çV{ÇuH»Õ~"F@@’T GÛƒ>Ùì Ÿ-€ö�EXõÐ8×µ¶ÂB;a´Ð4�ŽQv0Ι� �·RÀñ8Ð$ûlê 9ª½gPƒé¼I¶ŒlqL‚éÈ/EvR¬… €@º …]ŠÛqa+1Hq«.pkM¶öcÒ§¿- ~SKS[‰ê7GÇ#ý{b@Úmèpú±½ŠXUàVÏÑ�zvU‘3œEéê@"Æ@@fPàtK·œ¶Ú ¢½ãÓ©ŠÛN=[ =;!te¶¬³Ñ£¥¢í{7 €@j $óóa·:6"}nÉ]ˆI0É�X@�Lp{Ó-;›¿3ñ³À5#€€H¾¨ «‡ô¯ ª-¤Þ©%ªwŽ»#óU¾óp“#�>¹óÕÞ°z¶…ÖVÓvµŸˆ@@�i X[Ð@�ÑÞzhxz�ð‹õ©ýÚŠlY£á³…Ðët|­=niÁ4¯�Í@f^À-˜v{Ž<ógf/B…ÿc3ÃÒܱ ¦]NÈå¼áÇ1@H¨Àš5k$??_úúúüç±eËÿ8# €@¦ ØÃµ�Z øÆ€jÀÍd»…ÔÚ·z«·jÀë{"Àg�7v:} qQ~Ž–ª.+QmAõ,-a=«²XJ sWc@@¿€53cmA×kIhë}a´UÏ=Ýnva–¬Ñ긭·ðy�†Ð+ÊÂC�é‡í@’G ™ƒi·|×%ž2fl‚i×Ãó�§+ 3@2J ¬¬L¾þõ¯Ë?ÿó?ˉ'䦛n’OúÓeÀÅ"€S°‡qÖ¿cÑX-LïÖ€ÚBê]XïnóNG³ÿn}xxðT›ÓnW¢Áô°†Ùg/¯“ºÊB'¸žUQ$¥E±iC)ðXŒ#€ €$§@Gw¿Ô·ŽЯ½ÕèT¡ÝÙÞÞf´W`OËWkøìôåÞÒÐkʳ¤Nƒi:@�Ì ˜Î¬ûÍÕ"€ 0£×^{­XO‡ 0=�YúÐÎúËæØ~´^pí¬*p «w�Õ{4¬Þ£Ó§£(]mûñ=l|fçI›ôwŹNH]Wá «ë4¬¶ñJmך@@ 5¬)˜†Öí»¥ÁJAÛ¸»z§@›ˆ•‚¶ú, ž½ÃlgHUÜ©ùyá¬@�X $u0-á/L%]UÞnE¸ÃO;Ö·�ý!€ € €@¦ ØÃ=§Ý½JoPíóhìÕ€ZCê½£½w|X:¢|Öh'ÝJXççzœ€ºv4¨¶ÀºVkZûÖt € €‰Ô7ÏXðÜÖãú‚hö ÅääJµ5 ŸWi èU:´ñ³4�®Îçû`L€Ù  �¦á/»&ÅÕº=Òˆ¼aµÉ/!ŽUyO~pÖ@@@â!PS�%Y?+xïG»FdßhXí Û‡�éÞ(ŸMöéÃÌcg:�>ø"5å…Rk½Õ�ÃòâüÐU™F@˜¦@[WŸÏ>;´wh%¡›Ú{§¹ç±Í õÈ•:¯´Z››±q ¢@�)1† ©€Õ®¹fÍÙµk—“[n¹Å?žÈ‘”hcÚ-)w;ñDBrl@@@`¡><´þª¹Á‡:4°n÷†Öû5¬ÞoãÚ÷ ¯ÉT£>µ~÷Ñ൭”µÕ5£�µ/À¶aQ>ï k1… €Œ t÷Jc»7tnô…У߹ì…ÁXuEú•l…ÏÖ[mC  —@Çʘý €xî¹çùÁ~ ííírýõ×˦M›’‚Æ-ßu«9{ª'ËÓ�©Ê± € €¤�À’Ò,±þÚyvIcÕ‚é‘P[Pm½�è––¾è/ÝšoìtúЭ­-k'´Ö�ºº¬À)u]S®Ã²B)$´åb@ÒP »oPš,lÖ’Îö’_“Ñ�m½NIèXµýìc«Òª¶—ëw¿å<[ï £@ûˆ"€ÄY ´´T>ùÉOÆù(Ñï>Ë¥�é‘&Ó1 ¦ÝÎÇ-Q�þòÙ@@HœÀ"}8iý•!%¬z,  ª5¬~KÇßÒ¡U>•ζZ¸¾=ló’Â\'¬®Ö�ºFCkZxm}iQ^ØúÌ@@’U £»ß ž-t¶ª¶½½7€ŽuølVSŽÏË,€ ¢m¼N›|¡C@�p·6¦‡ÃW›òœ˜ÓEùÙa'PZ“]‡í— € € €@¢ê õ�¦öÔŸ‰µUmµTkX}PKWt†#ÒÖ¼n¤S�=bý‘úްM¬zp_H]Uê ¬«,´.-zÜþ¢ Û 3@@�Ø �H³ÎÞйÙhwæéüXV»í;ãr}Oo©†ÎË´÷³� ÚÆ Æ*Âñ­Î@&Èr)yìV@y‚]L¸(&éñœò|y÷¹³ä^¬÷ìCÎñ�3‚ € €d‚€=ü\[™åôÞë{ÚЫAµ†ÖµzpkÓÚkÒé¾)6�hwO6u9½›oEI¾?¤®´°º4_{ˆ-£C@¢híì“f ›}}ûØ´-‹G—¯_©–j-6¾æW,t¶q £k)ýrö‰ �¡.¹´$]UÞvoþå=+å¢r¤©W¶-+wú ½g\6 € € &`UFZ~H)k[ñ¸VþJÓˆ´ô�Èa ªk`mC ®û§Qg–=¶þ­Smaçc¥©- ¶ÀºÒëïЪ¯«("¸c €™!`ßZ{´Æ–~'|nÑé'„öÐÃZ*:�…Ï‹5|¶ÞBgVäeÉæê,™¯UrÓ!€ €@ü܃éØ7&%¦}§ó®-.OX| "€ € €¸ ØÃÖñ¸Zhí„ÕTímú¨öí®»‹h¦Uµy¦­ÇéÝ6ÈÖàºRKUWŽÖVÂÚ¦+lÚ†fçz›urÛó@@ 9‡Å‚f Ÿ[:{G‡:Ýá�nÑáp,ëë ¹ì²\‘E8[oÁ³o¸XƒèyE„Ï!\L"€ 0ãnÿOã}ù°ó�i0¶wf € € €LKÀZ_4+|7�Z=ø ®-°>Ú)Þ¡N[h}¢{z¥™¬4T“µ©ýx�•®öÖR^’'•Å\çë¸GÇÇÛ–ù € [û—¿ÍBç.oðì«9¥M§}atgÏ4Þj‹ðt-`^8:/ԗ…ÑÕùn�»#Ü1«!€ €@ÜÜÚ˜Öº¼cv\‚é˜Q²#@@@`fj´jpë·T‡w@_i>ª!õ1 ­m8XÛðX—HSßôÿ°´‡ÛÖ?£©ø8�T;!už”ÛPûòbï¸oh¥³é@@`|«éÄæö®~gØ::ôÑ£ó4”ž‰®:_d�Î 4|¶àÙúÀñ*T™‰ÛÀ1@ˆ‹@Q^ø?äy1üÇ�`:.·��"€ € €‰ÈÕ¿%—iµ˜Ö»u�Z`ÊBj«*ü˜–®¶¡õûÛG¤Q IÇ"¸¶ãúJk©w; ï¼Ò" ªÇÂê2g:_r´ªð9UER¦avQ>¾Ž/È@�TèîtÂæSM]â »-löÎ6ìÐé™êì¥7«r{M…·© ¡}5¸X]Ì?É3u+8 €3.ðîsgÉ+G:üǽfm•,­-ôOOw„¯Ód{@@@ Jô�ój}àl½[×=(NPmU‚[`mC§×ÒÖ¾q-¼“ζ[?QÉkkϺLÃk ­-¨v†E¹b¡¶w<_Çs¥¸@/Œ@�$èêpþ}k×ã|½ý{g¥ž}Ó:Ų寉/Ü*)±ª¶�¾xtÜ‚g›7:,â‰ñĈ,E@ �>°mŽÔhS]Oík‘…Õò‰ËæÇôjùšSNv† €€»ÀÐÐ�XŸ——ç¾s@H2{(½²<ËéÇ;5 ¨O:½†Õ^{ÇuØcã¢%¯c”\ë èCûÉÚ¼¶óôèw ª-°öõeÚv‰Ø…y:Ô0[‡bçåxÆ»4æ#€ à*Ð?8¤a³Î=öR•o¨ãÚ´…ïE+'|ÖÚJ?ÏtW«¥�ç‰ÌÑ yîhmC›ga´�Ó!€ € \¿¾Z¬�GG0Uö‰ €@€À—¿üeù×ýW'˜þЇ>$ŸûÜç–2Š €@ê øJ\�w}C"§œ�zDNi€}ªG§�¡wú´N×ëòXv´h›ÖOÖåçz¼áµ×N¯Áµ-¸öNÛ|ï8U‰O¦Ér@ u¬*íN š;5\öõ<Û¸oèŒkØÜ7 ÿ¸%¨›U˜%s´&ÍÙ.ÏÑq ™gë´7xÖy:íÒ,d‚ΖÃ"€ €áÓá&ÌA@ f¿ùÍoäßø†ßÿþ÷eÆ rë­·úç1‚ €@º äk�äÅ%YN?Þ5i.}ZÃi ¬-¨¶që-°>­¥®Ok©k·6±cÝY¸Ð×Ö#�ÚOÖYIl«&ÜXäI±†ØÅ9NxmËlÜ·UŠO&Êr@ ~V…¶É6ìêÔÞ2Û¸7xîw–9ãºÎpJ6^½5¯a¡ól-í<[K6϶qíCƒh«†›@He‚éT¾{œ; €@Ò ìÛ·/ì÷îÝ6� €dª€' ­Ë‰ ,˜vëÑ º^³ä °uºÁJ^;ÃéˆC€mçe%±}í�Ntž�˼aõX`]^[ l[nólÜ·,;‹Ô!Ð�qÈl Œ»´4s·†ÇVªÙ‚f·€¹»otèLûhÆé‚)ÜŠ2 œë4`®ÓÀy––l¶ñY:^g¥žGÇ-|¶`š@È‚éL¸Ë\# €@†‡‡ÃŽ���6� € 0±€=´_ž›%ËË&n»EÎŒ†×gz5¼Öq›nÐñ3d۸ͷaø?ÓŸD”K½%õ¢ H ò,¤ÖÞÂê|_hí�.�.t–åh{ÚÙRQ’'…ºMžVKN‡$«€ÕPÑÓ?(m�ý2¨#õhÈl½…Í=0Û°Û ›½�³wz@zûWmöx–VH�†Ëµù"Öž³…͵:Û¼ÑÐÙÆ­/äÉëxŒÌG@¸ÿþû¥¹¹Y.»ì2Y²dIF˜ðõ(#n3‰ �(�¡¡ð)Ó‰º@ Šô¯ÜEZ}¸õ“u­ýÞ�ºQCêF «ûlèo oÒñx•Ä=Ç^ n¬o]0É´U5n�µÓkP:,Èóøçèz6m!xáèÐÚÛ¦Cư`Ù~7õhPÜ«!r¯Í6´y.Û|ÿ¸oÞèP—[­ÉÜ•êËO6×ägIµ†Ìµ:¬Ñ¡3Oç[í¯ÈKæ+áÜ@@ U~÷wW~øaçt=�ü÷ÿ·œ{rúS>O‚é)Ó±! €“ ¸•˜¶/t € �x�м,±€aÅ$¥°íL{õ]3 ¨›4Àv†6Þ§ãd۰ٙѡwy[ÿÌ^Ÿ…>ÞvS£+¡í;K«BÜÂê| «}¡u�†Õþy¾ñ\ïr ²�e:Ì×y6�oÛëÐBr:H¼€ý^°@¹OCã¾o˜ì›¶`¹Oƒdoଡòè´•P¶q ™�ñÑáÈHrË¡Úö»½ZÃåJ š5T® œmžM{‡ÞÚÆ ø-”�i@ˆ£ÀöíÛý¡´Æ 7Ýu×]Óq4g× €d„€[‰i‚錸õ\$ €@š Xh1¯(K{»°Éƒ×!Íp,¬¶ Ú7lÑÚ6Ý¢ó[4¸¶a³Îkµy£Ó‰Š~†5trªÎÕ’�Óír<ÙÞ ZCj«bÜ ­�a¶äåŒÍËËÑi[Ç™0îŸ?º¾Nçê:¹:¤C ‡¥pH|Ã~›Öpxl¨ãÞul~Ÿo¹3nË,x¶Þ»�w|H‡âÜ^Á Ü {ÏÅBæJ}‘ÈBæJ·¡†Ë6¯ÊBgg™7|v¦u¹gò_Ó3pö@pèìì [ÐÚÚ6/gPb:ï*ׄ �4n%¦©Ê;in'‚ €@Ü,±¶F­ÝÑH‚l߉XõâR·jhÝjÃÑé6 °�ißø‹g†e–¶mjÓV¢;Y: ì·6¶cÙi¡no@­Á·…Ú¾°Ú;>:­Ë,ÀÎZHnÓVš¼¶¢Ð™oó|ó}ã9zÃÆÆ½Ës4³y]f%ÊéÒ[À^ÎÒ7JœÏ¯¶�<8¨ã6ý<ú–é´Ⱦù:}¦µGJ s�ù6í[nCï´7dv¦mž†Ð:;Ë4PNÔ )3yG õåž2 �ë{Fä¼Úl)×€Ùj­ð½ã¾Z,œ ZÃeÚ<:@@ ݲ2øï‹˜Ó?zî´ké•‹WVÈ…Ë+ÒísÂõ € µÁtÔdl€ €@F ø‚‰ �ì@¨> ¦- nÓ,؆íACÑi�§Á¶3Ôe�㺬{ú¥O'.ãV“°SŠTƒ¼®¸aü�f[H­½ÇBíÑ¡UYî×6�í °mž3î m\Cm[6:mûñÎÓ¡>Œjíê—šò]Ç;m¨|ËíY•â¶Ì†¶LGƒ‡:Ãb;ß2±ÿtÞè0pÜ.Ï–;C[gtÄ;Û™òýϪp¶ªÚ»±šœG¼AªÞ'PÕ6´å6æ Æm†5/lUAÛ2 €Ç†¶llzXWô­kãVµm7¤½M7¶õHEq¾Ú–ûÖÒylÜ»­Í³u,p¶q —Çæé´³Ì;ß΃n|�"}‚Xš›%eÚ³Ók`lí2—Ù< �mh!³oX®ËÊu[V®Ëòƒ?Nãˆ% € €i-³`úã?Ø-¾Öè`}í‘còµß^%·n­Kk<.@�ɨÊ{2!–#€ €@,,ô©ÓÒÓu…¶7oäÍ~µp¨†Õ#Òa¡µÓ븆ÕZwÌëÔy6Ýió�qï°ËæéNfºmíh®q:ëZàÙ¯½&™ÓÙ Û"0ã—ädI‰ÅÅú°DCb ”‹G界Î6ÏÖ±ð¹ÔæYðì,Ó°YÇ©{ÆoD@�4p+1m/dfB§_1§ß<Óã¥}{ûÑó§ ¦} @2VÀ-˜¦*ïŒý8pá €$­€fTþv[½'}¸í»8«V¼KCê®Ñ»{tܦýãdwkéU+©ííÇÆ{t{[Ï;·Rát¤£€½XR¤}¡þ ZÉd«öºHǽC÷NÛÐÂe[φÖÛz¾q ›‹}´ŽSJ9?-\ €¤ƒÁô4ïb·VñÚuñc( Ó €(àV•·Ç£Ošè@@�4(Я:Z¼²:ßw�S¹}{°¡X¶°ºw4ж¼Gƒm›öηñÑi�o�%zu#þðéíÒÜ|FÄ£ÅG=¹²mÃ&)(,{œÑ§… ût¶^¿3îZ)i︛.½¬ºô< s­Íò|ímÜ;ôNçëô�½»¥E?GYCúfÅð€\º~�,®­vj˜W$gégß>ÿ¾Þ;m³3O÷aãN¯¡²ýœØ¸UÑN‡ € �9Ó™s¯¹R@fTÀ­Ä4ÁôŒÞ† €i"`á�·dh`èm7yª÷ÌÿßÞ}ÈQÖÿ®¤jj�Ð! ¡# E¤w�"‚(E)PQø!Š(zQ@)Ò‘Ž" ô¡— -BzOîþÏgöž½Ùݹäîyž»ÛÛ}?vfvfžï¼foË|Ÿç™Üm¦½ÿ~^â—{ÿÎŒ¶L~~IÁ[Iê6Cí’Õ òËrË5_üOÛ-°CòéÑv7çÞx³™:{¶ Ùf#í¿¿µ¯Y¹ïäžÉzÞæÇ“uõ˜ÿg·µÿ·÷FÖ2{OeMÛç•,×?MkÔ¿äÑ>§eöÿ¹G;¡õõ¼–½÷É'ö¾ÖºC·5³‰Ø5ÖXÃôïß?yÞ.Ì—W>žaFׄî]�{ÌݧZóZ¤‹j¹ÇÜ=±µ¼ø^ØZëÙ[t'÷ËÎÝSÛNÛu¯ídyjÚÝ“;¹Ÿ·]^ow eSg/0+ôï‘Ìë9-Oþ5Mw³÷óîÖ´L‰åú¦y-ë¦d³í?%Ÿ‹µ¿%•Ã?ß<þÔãùÕ¾¿ófÛáƒòóL € € ÐÓ­QZÌ:ú�S\ôC„‚ €@µ dõ˜f(ïjUpü € ÐÕlÓ&9uK;ZZ^x¤—�ÊLÿÂöÜn*§œ¼›Ye@aØ=×�G�{‹yôå—ó»>ëˆkÍ;ŒÈÏ3� € €í/Pî‰éO¾šk}{ŠY¶o7³Çð壂Ø�ƒ( € Ð^Y‰izL·—6ûE@@@@Ê[ œÓ/Ž�n¸âu3OÃ>Ùr÷FË›¿|gÝh I{ßh{cG € P �5”7=¦ ˆ˜A@@@@ªZ 1kxêNùÛSãóIiUÿÀk_š>š-’(‰éŒ‘¼[Ü*Zäì@�. �ÕcšÄt8q„ˆ € € € €´ƒ@9÷˜ž:{aÉOŸ[º¬d¥V.ˆ’˜ne]¬† €@Õ dõ˜f(ïª{pÀ € € € € €@"�•˜.šššÒHbv排˜Î (+ðÒCa  €•-�ÕcšÄteŸsŽ@@@@hI +1].Cy×f$x²†Înéà–°3·Þz«éß¿¿9þøãͰaúøQ> € € € € €@¥ Ôf å³Çt”Ät¥¡s< €ÄøéOjô�‚ € € € € PÝåÝcº43�ÕAÙ÷ FÊ›{Lûò³ € € € € € €T‹@¯^½J5kYÉJ° 4-mìPÞñ*Ž’˜Ž{B@@@@@@*S`½õÖ3;ì°CÁÁtÐAó�5“}�éxÑÄÊ;b¦<Þ¡±'@@@@@@@ ¼®½öZ£ãÇ�7Ûm·�Ùzë­Ë"ÀìaÆã…'1/ö„ € € € € € €T´ÀQGUvÇW›1–wCı¼£ å�Õa:««wÙé € € € € € € €€ÉÈK›¬<°/U”ÄtŸu%õ÷ïIgì € € € € € € €e(P›Ñó¸ìzL¯½Bo³Çˆ|‡oµbÁ<3 € € € € € € €å)�‘—6Gò6Ѻ5_uÄ:f§u—1ã§Í7Û®µ´1¸_yŠ € € € € € € €@�@—IL+ê6T<3 € € € € € € €å/P“‘™nŒØe:Ê=¦ËŸ‘@@@@@@@Z¨Éx¢1c™ï"Ó¾rl‡ € € € € € €Tˆ@ïîu%GÒ£>^:9ÞžJÂd € € € € € € €@WØoÔ€‚0×ÔÛì>|ù‚e!3õ!³- € € € € € € €@×5¤¿yð”‘æÞÑ_˜¾=êÌ[­hêj³øö;VÓ~nl… €@›æÍ›gzôèѦmX@@@@@�޾j_£íQÊ»=TÙ' €M'N4ßþö·ÍZk­e¶ß~{óØc�aƒ € € € € €@Õ �˜®ºSÎ#€t¤ÀÙgŸmž|òɤÊ?üМ~úéY=u!€ € € € € P$¦Ëâ4 €@¥ Œ=ºàÐ&L˜`>ùä“‚eÌ € € € € € PéÜcºÒÏ0LJ Щ‹-*©¿®®®d @@@@@ªGàá‡6ü±Ù|óÍÍlPNbº*N3‰ ÐY$¦;Kžz@@�òø¿k¯5½{ö,ÿ@‰@@�vx{ìX3iÊ”vÚ{ùîöŒ3Î0×_}>ÀK/½Ôì¹çžùùJ� 1]©g–ãB( Óeq@(K�¿ðBYÆEP € €t”ÀQ¿ÿ}GUU6õLš4© )­À®µ�V«!1Í=¦ËæeH  €•(�•˜®­åã·Ï5Ç„ €t5�)UØ3¥«�#âE@¨<�™3g–ÔÔ©SK–U⮌WâYå˜@ÊF ¡¡¡$î1]BÂ@@�6l˜Ù{ï½;¸VªC@@ ¼V\qŨ¦¦¦¤ÎÆÆÆ’e•¸ êPÞ·3÷�<ÂôíQå¼DIq7fŒå]ÃXÞQN;Aèº$¦»î¹#r@@@@@ =Ê91}ósóIiû{g››Ÿ›�!Jb:Z4ì@�  1]A'“CA@@@@*\`†í1]\¦Í)]V¼Nkç£$¦3:L:L·ö° €@¥ �˜®Ô3Ëq!€ € € € €~åÜc:;6¿ãÌÚ*Jb:kÇ,C@ ÚJêêâÜ‹£dÇ,@@@@@({�ìäoV7àŽ?”¬[57D -Jbº1ã&ÓY¨ÏG� €tžÀ2Ë,cÖ[o½‚¶Øb‹‚yf@@@@@ªG +‡š•kí ‘¬Äqļ´ÉÚg'u"€ P‘矾Ù|óÍMŸ>}Ì.»ìbÎ9眊OV¦œ{LÇ�e €]]`ýõ×7·ÞzkW? âG@@@@ˆ ÐÞÉß�³†òÎ8Û» zL{Ó±! € € € € € € Ðz�ž={–¬Ü»wï’e�± 6#3Ý13%1�OFÜ�áG� € € € € € € €@Yôïßß|ðÁ±zè¡ó�5“•ßÍÊûÆe(oßÊÙ@@@@@@¨&�óÎ;ÏŒ9ÒŒ3Æl²É&æ›ßüfY~mƽš²îéìm”ÄtV<q{†Èf € € € € € € €@å÷š.‡#«1¥ÞF“• ö‹6ÊPÞ~U³ € € € € € € €@9´÷PÞQÓ½»—î¦OÏ(�±Ëá € € €ì�S8@IDAT € € € PÑ]"1½ÅХ̶k-Sp"Ý|PÁ<3 € € € € € € €å)P›‘™nhŒ7”w´nÍ7ÿ`ó·§Ç›É3˜�Ö[Öl´Jßò%*@@@@@@@��Ò;L1/m¢%¦õ‘[­X<3 € € € € € € €å/P[z÷fÓ¯Ã´ÉØ}ù£! € € € € € € €@<�SÚgºÑÄËL“˜Žw®Ø € € € € € € Ð%zu/M÷êVíXJ÷m×ì@@@@@@@ +ì9b€: W>Ôúº³ï¨�ùùЉ¨÷˜ †í@@@@@@@:^ g·Zs÷‡›žoæ,h0{�`Ö]±O´@HLG£dG €´,0gÎÓ«WsK³–×ä@@@@@:G`¹¾ÝÌIßÜ.•“˜nVvŠ €@Nફ®2^x¡Qbzß}÷M¦±A@è\�Óìw´>={vnÔŽ € €@' <1z´7aB'F@ÕÕ(@bºÏ:ÇŒ Ð!o¼ñ†ùÝï~—¯ëÎ;ï4k­µ–9î¸ãò˘@@èx�wÆ�ëøJ©@@�28鍊Ê(B©�Új9PŽ@�Ž3fLI•ï¼óNÉ2 € €T§ÀÂ… «óÀ9j@@šøN\]/zLW×ùæh@:P ëKU}=½x ¨ @([�ý÷ßß 2¤lã#0@@Ú[`•UV1«®ºj{WS¶ûöÙgÍÛo¿m6Ø`³é¦›–mœ1ãêxLMö… €@J ¡¡!5—›¬««+YÆ@@�Ž8ï¼ó̰aÃ:¶ÒTmµµµfäÈ‘©%L"€ € бÛl³�Yj©¥:¶ÒTm½{÷6GydjIuM^~ùåæøCþ ýë_›£�>:?_©$¦+õÌr\ €�.�ÕcšÄt§Ÿ@@Ì:ë¬cFŒ� € €U+pÚi§™ 7ܰj�¿3¼±±Ñ\yå•!\uÕUU‘˜æÓ§�@â ,Z´¨dg$¦KHX€ € € € €T�€®Ϙ1£àx'Ož\0_©3$¦+õÌr\ €�.�•˜æÓ�~Z@@@@@ ¢å}Ó³ÌÔ9 Í.ë/gÖØ«‡*@(�¬Ä´î'HA@@@@¨N�ššš’×ðÞÕP¢%¦¼òuóäûS³ßß?ÖÜuâFfÔ�þÕ`È1"€ �)�u�izLgR±@@@@¨ �jNLGé¶õ¿w§ä“ÒzÅ,lh47ÚÞÓ@ªY +1Í=¦«ùÁ±#€ € € € €Õ+%1=sÞ¢ÁsK—•¬Ä@*X ¡¡¡äèè1]BÂ@@@@@ jè1zª³†=/=´¶G@ K dõ˜æÓ]ê, € € € € U šÓÑî1õŒ°3@*@`Ñ¢ÒÑCè1]'–C@@@@@”œnl,ìù«ù¬¤u@5Þ›ÞòüD3yֳӺ˚µVèí½Ÿâ £$¦ ÙrUÐaº˜šy@�jÈê1MbºÚ^/ € € € € P(PΉéï~Ó<òöWIÀçÜû‘¹ý„�ÌVk,UxžsQî1íY7›!€ PÑY=¦ëêê*ú˜98@@@@@�Å dõŒ.îA½ø=´Ï³Ï}8-Ÿ”v5Üðôx7ü%1]ÔÓ< Êö@§ € PÕ$¦«úôsð € € € € €@¦@Vb:sÅ^8k~CI�Óç,,Yæ» Jb:»r2ÓÙ.,E¨ÓÕr¦9N@@@@@ L zL×f¤w²îéìy¨í˜˜öŒˆÍ@¨�¬Ä4÷˜®�“Ëa € € € € €žY=¦Ë!1�‘—6óÒ&Jbº1#¤¬À=Ï ›!€ Ð%6Þxã’¸‡^²Œ € € € € €Õ#P®‰éÚŒ{57dÝÓÙóTÕ{nÇf € °�}÷Ý×|üñÇæÿø‡éÝ»·ùþ÷¿o6ÜpÃ%lÅÓ € € € € €•,P¾‰éRõ˜CyGILg%Ê3ê¥GÂ@*\àä“O6úGA@@@@@ œÚ;ae(ïr$6@@@@@@@ \úôéSJ]]�©¯�ÒŸ¸`¿m�ÉêxœÕA¹­ûuëGILgÝôš{L;b@@@@@@@�œÀa‡V@qÄG%§;»´wbºóSï�-Lý € € € € € € €@ üøÇ?6C‡5£G�6ë®»®9è ƒ:¨æÅWS›‘™nˆØe:Jb:+žŒ¸¤<‹ € € € € € €T�À>ûìcô¯œJmÆ�Ø YCg{e(oϺ٠@@@@@@@  2òÒ&b^ÚDIL÷é^º›>=¢tÆ.ƒS@ € € € € € € €@e Ôd ‰Ý±ËtiFÙÃsç –3£Vë—ßRã�¼Ù ü< € € € € € € €å+�‘—Žl´nÍÿüápsÝSãÍŒ¹ Í®.oÖ^¡wÔ@Ù € € € € € € €@û¨óqqihŒ7˜w´Ät�½öÑÛ¬T+ó € € € € € € €e.`Ó½%%âHÞ&Zbº$J € €@Å \xë­Fÿ( € €T«À—Ó¦™;¼ZŸãF@¨@�ŒÓ&b‡iÓøšá�@hw�×>ø Ýë @@ÊYà”K/-çðˆ @@�6 ôî^W²MŸ¥ËJVjå‚ÚV®Çj € PÅS¦L©â£çÐ@@ ÒæÏŸ_i‡Äñt€À‡~صP € Ð1sæÌ阊¨¥K ÐËì9b@Ȧo¹BÁ|È ‰é=¶E@ JN:é$S_Ï@ªäts˜ €T´À®»îj6Ûl³Š>F®}öÚk¯öÙ1{E@:X ÿþfƒ 6èàZ©®«üéˆuÌù 3'c°¹ý„�̾£F ½¦Ñ–h{cG € P"0}út£/{]½L�:Õ¨— ê®~&‰@�êPOéM6Ù¤z8ò`�Ë.»Ì¼ù曦gÏžÁûb € €@gL›6Íœp fÔ¨Q�Q=uV¹‰é*pø €í'0nÜ8sê©§š_|Ñ <Øœ}öÙfûí·o¿ Ù3 € € € € €e*@bºLO a!€t}�cŽ9Æ<üðÃùYi¥•Ì3Ï<“Ÿg@@@@@�jàÓÕr¦9N@�xíµ× êüüóÏÍ„  –1ƒ € € € € €@5ÔWÃArŒ €t†À¢E‹JªåþÌ%$,@@@@@ªNàÍ7ß4/½ô’:t¨ùÚ×¾VÇObº*N3‰ ÐY‰éºººÎ…:@@@@@ÊDàÎ;ï4§œrJ>šãŽ;Μ~úéùùJ�`(ïJ=³ €@§ d%¦é1Ýé§…@@@@@�N¸úê« ê¿òÊ+ͬY³ –Uâ ‰éJ<« €@Y,\¸°$zL—�°@@@@¨*�)S¦”ïôéÓK–UÚÓ•vF9@�² ÇtÙœ A@@@@ÊF ¦¦¦$–ÆÆÆ’e•¶ Ú=¦ç-l0×?=ÞÌžß`v¾¼: W¥Yq< €´I€Ómâbe@@@@@� ˆ–˜Þó’ÑæõOg&T?ü±¹ç¤fý•úT0‡† €ÀâŠ{L«Cy/ÞŒg@@@@@�J¨ÖÓQ†ò¾çÕ/òIi½Pæ,h0·>?±Ò_3 €- Ð[ºEž@@@@@ªZ€ÄtÀéŸ5oQÉÖ33–•¬Ä@*T +1]_m ’ Uã°@@@@@*U J�é¬{q—Þ²»R 9.@JЇñÖ ã]êÄ@@@@@ ÚªµÇ4]·ªí•Îñ"€tˆ=¦;„™J@@@@@�.'Pî‰éGÞþʼ;a¶9¸ŸÙr�¥¢ùFIL7f„SC—é !€T‹‰éj9Ó' € € € € Ð6�rNLÿþ�±æ’ÿ|’? ß쳆9z›•òó!Q†ò €m@¨D†ò®Ä³Ê1!€ € € € €á嚘^¸¨Ñ\þè§øçÿ}V02%1�y�izL‡œ¶Eèâô˜îâ'�ð@@@@@�vÈJL·SUmÚíœ fQCáXÙSg/hÓ>·r”Äôâ*à9@ªQ€ÓÕxÖ9f@@@@@ÀO 1«'°ß®¼·ÊºUs̰HL{Ÿ6D@ e�¬Ät}}}Ëð  € € € € €@Udõ˜.‡ÄtmƈØE¨ƒÎO”Ät£)ìÒ­ˆjìÿ( €T«À�!CÌꫯ^pø[n¹eÁ<3 € € € € €Õ'P®‰é¬ünVØ÷ŒEILûVÎv €T²ÀE]d¶Øb ³ÜrË™ý÷ßßœsÎ9•|¸ € € € € €@+Ê51]›‘9nhhŵr•8cŠ–v˜V—i  €U-0bÄsË-·Tµ� € € € € P(P¶‰éŒ›L7D¼ÉtFÞ»†9@@@@@@@�8½zõ*ÙQ�=J–uô‚¬~ÇYý“}ã"1í+Çv € € € € € € €@<ðÀ‚-¾ùÍošÁƒ,댙ŒÓ&b‡ie(ï¬LyVF½3©@@@@@@(�Ã?Ü 8Ð<õÔSfµÕV3G}tY„V›‘™Ž9”w”ÄtYH € € € € € € ÐvÙe£åT2òÒQ{LGÊ;« wVàåK, € € € € € € €ÍµÃb7d ŸÝ¼I«§¢$¦ûô¨+©°_O:c— °@@@@@@(S�šŒÞDZ†óŽ’˜Þ“�fÕûæù–é]oÞlP~ž @@@@@@@�òÈÈK©Çt´nÍ÷ž4ÜÜôÜD3wAƒÙsÄòf¥¥{”·*Ñ!€ € € € € € €äj“Ìta&:×c:cŒïüV­›ˆ–˜î^_k¾³õŠ­«•µ@@@@@@@ÊJ ìï1]VZƒ € € € € € € Ðf�¬Ätcaê6ïÓmåÓng<"€ € € € € € €tM�¾= Ü®µ™ênuáÃxKƒÄt×|M5 € € € € € € Uà;_+¼uó1Û¬dê#%¦km‰-;C@@@@@@è’÷¿ö¥yåãfÝû˜ýF Œv $¦£Q²#@J-Zdô¯{÷î¥O²@@@@@�*`(ï*9Ñ& €@Ç œwÞyfÍ5×4Æ 3gžyfÇ@� € € € € €”‰‰é29„� PY�<òˆ¹âŠ+LCCCr`×]w�¹óÎ;+ë 9@@@@@Z)@bº•P¬† €@[Þ{ï½’Õ³–•¬Ä@@@@@*P ¾�‰CB@ Ó.\XC]]]É2 € € € € €TŸÀ¸qãÌOvKPX€ € € € €T™ÀSO=e;ì°ü­ ÷Ûo?sÁT¼CyWü)æ@:C +1M�éÎ8Ô‰ € € € €”—Àu×]—OJ+²;î¸Ã¼ûî»åd;DCbºPÙ% €YCyÓcš× € € € € €À”)SJ¦OŸ^²¬Ò�˜®´3Êñ €”…‰é²8 � € € € €”�@MMMIL���%Ë*mA´›]N›³ÐÜüÜD3QƒÙ{ä³ê²=+ÍŠãA@ ÕYCyÓcºÕ|¬ˆ € € € €T•‰éVžî9óÌ¿jÆLš“lñ§Ç>5÷Ÿ< Y¾W+÷Àj €T–@V�iî1]Y瘣A@@@@|²zLûì§«me(ï;_ž”OJ `Êì…æ¶'u5 âE@ š@VbšÓÑxÙ € € € € Ðe²ÓÕÐc:JbzÖ¼E%'>kYÉJ,@@ B²†ò¦Çt…žl @@@@hƒ‰é6`±* €,^€Ó‹÷áY@@@@@ Zª51]_­'œãF@ =²Óô˜nOqö� € € € €t �rOL¿?q¶yzÌ4³Ê²=ÌŽë. 5Jbº±±4žššÒe,A@ Z²†òæÓÕrö9N@@@@@ e�rNLÿë�Éæ¨kÞÊäÖ+šs÷[3?2%1Û"€ P‰Y=¦•˜ž6mšqIëFÛ²KÿT²¦ÓÏ9#-8p éÖ­›[ô8fÌ“Ž5+UP¼ÜUªåË-·œYuÕUÝ¢ Çgžy&ÓG;U]-=¦Ÿ[a…Ìl�¬òŸéÓ§›'žx"‰'½ÿ⊟suºå:_Ûm·�[ìý8nÜ8óøã�ø¤cq;võ¶ô¸âŠ+š=öØÃ­îýøÖ[o™Ç{Ì,X° ¿W§[àæ÷8hÐ sä‘GºM¼Ÿ}öYóè£�æ_Ï®N·Ãô¼›v�ZÇMëõó½ï}ÏtïÞÝmêõ¨sõ¯ýËÌŸ??¿½«Ã=ê‰ô´[1½L¯Ÿ£�>:ù»wÏû<ÞÿýI<îï=]Gk¦U§Ö“Ï÷¿ÿ}³Ê*«ø„‘ßæ®»î2<ð@òúI×ïêq+?ç–»õôú9öØcÍÚk¯�~ªÍÓ×_½y衇’×OV�YËÒ•¸çWZi%sâ‰'šµÖZ+ýt›§¯ºê*óïÿ;ÿzNïÀÕ•^–5­õtžN:é$³Î:ëd­Òêe—^ziOúó+kã–bsËœÄú­xôúq¯ç¬XÜ2W·›O?ÊçG?ú‘Ùxã�Ó‹Û<}Ùe—™{ï½·àýÐídqõ»uÜ£{ýl¹å–n‘×ã\ÐjŸÅU Ÿ“O>Ùl²É&‹[m‰Ïé|ÝsÏ=™>KÜ8µ‚þÞO8á³í¶Û¦–¶}ò’K.iñ|µvoº€¤ÏSųõÖ[·v³Ìõ®¸âŠÄ'ýyQ¼bÖ«âuôþ¬x¶Új«â§Ú4ÑE%ï‡ú{oM½-­ãþ¾†Þ¦ú‹WþÓŸþdô–þ{o©NmÛÒsÎ'ôïý¯ýkþóËÅšUgÖ²t|Šçøã�7n¸¡Û�×ãÕW_mþóŸÿ˜†††·ÏŠ¥x™Î×øC3dÈ�÷Óš'î¸ãsß}÷åÿÞ‹ë)ÞGÖóZæ>ß×\3ìâ§¾oèûXúõ“Ž¡¸þô|zZï?øÁ’ïAéíÛ:­ïŠ'ýý9]OzZûNϧ§��~‡…}—wß7Òûoiº¥˜Ü÷Õ~ýú…„cž|òÉäû|ñûO:žtéåéi}žêûshyíµ×ÌÃ?œý¤ëhË´|;ì°Ðp̻ロ÷ÑÎZƒ«4½®âÙwß}ÝSÞ�cÇŽ5ÿûßÿJÞÒu¥§U‘›w�Z¦xvÙeM•/¾ø"‰gq¯Ÿt½®²âezýl×¾gΜ™¿žàêÒcº¾ÖLëï}äÈ‘é]xOë7ªÞÓõ¶6&WéòË/oÖ[o=7ôøüóÏ'¯ŸâxÜNÓËÓÓzÞÍëõ£ß1Ê›o¾¹Ä¿÷tÝÅÓš—�ÎYŒòá‡fÆ£}»ãwõ´4¿ì²Ëš¥—^Ú­ôøù矷�vÜR éJõ¹Õ³gÏô"ïiýÍÿ)ŽA;_Ü2ùÔÖÖzÇ�ÞÐ]ïͪϭ—õ\Ö²¶ü†tûn�Ç«ÿ¬`·{j¼9qÇUÍÊK÷(Xî3Sc2w•×gë¦m®úïgæÿîù°`ßÿúÊæÌ=‡,c@�jPbK?òÓåë_ÿºùïÿ›^ä5­„Ù/ùKó�ï|Çk{m¤¤ë1Çcž{î9ï}¤7Ô…šŸÿüçéEmš~ÿý÷“‹J¾Æ(:¶_ýêWÞ»zýõ×Íá‡n¦L™â½�ô†p€9ÿüóÓ‹Ú4ýÔSO™oûÛ™IÌ6í¨ie%¦•Hñ-ºh¤xb•m¶ÙÆÜpà ޻Óßšþæb•M7ÝÔÜ~ûíÞ»ÓâÃ\JrꢘoQBH›c•¡C‡&�|÷w÷Ýw'É@ßí‹·ÓÅb]dñmLpã�7š_üâÅ»õž×…5jñ-J2œsÎ9¾›—l�’Bçž{nÉ~}¬¼òÊæé§ŸöÝÜÄö0`€Q£(ß_ûÛß̯ýkïã)ÞPõ�ïÅýØçK×Ôð§\|t!K¯ßd̵×^kÎ:ë¬bvïù>}ú$çk©¥–òÚGì÷Å#ß �±_?º¸¯×³ï¿Ø_Ë,³ŒÑw*9ù}WÑwðXEÓõ�ªG¿‹{þóŸÍoûÛXá˜5ÖX#Izùî0ö÷ }^(éåûþsç�wšSN9Å÷pJ¶S#/%•}‹X¨1B¬¢ÄЃ>è½;%ÉÕØ0VQ# 5Jð-jpvÜqÇùn^²�1©á¡o)·ß;zï:ôÐC}§d»�vÚɨ¡�oÑwÅã-úîÇmúûôÕW_M~Ÿ*£|ðÁæ¼óÎóÞ•œGq„ùòË/½÷‘ÞP ¹Ï>ûìô¢6M+¡¨ë꘣„ÆóÑG™ï~÷»F‰×E¿uò“ŸxïJ Ný~=z´÷>Ò†Æ3cÆŒäz�þÎb}ª±©oÑß¹®±©#@hQög?ûYÐç¡®gêóBïÓ¡Eß u-S¾eΜ9ÉñÄðq1è;§®“uvÙñ�/›·ÇÏ*㑟llÖ]Éï»tzGQšØþ^é}&Ó5†±¼KPX€ P5Y­òc$¥¨5gžy¦™8q¢·§z ÅJJ+]ÈÔ�/ßrùå—›XIiÅð—¿üÅ|ðÁ¾á$‰�XIiqÛm·ù\wÝuÑ’ÒŠG=ûÔãÙ·ÜtÓM¾›fn§$Þ /¼�ù\kÊ7fQ,j½í[”x�YÞ{ï½ DpÈE¬ãÐ%‚}KÈEݬ:Ç�”IúgÅóñÇ'‰˜¬çZ³L bÅóâ‹/zï2ælñÙgŸÅ£$EÌ¢ Q!>1.ˆ¤�G¢Bâ i‘ŽÃMë‚êË/¿ìfÛü¨ é1ËܹsÍK/½ä½ËØçkÖ¬YAŸ_åOÈ{{ÖI™0aB�Oì¿w}· ùþ«Þ¥1ˤI“‚êÄþüÒwç�÷ŸØñèó"ä5’´Í:Ïï¼óNÐ绾Ç,ú.òûûªö†œ¯�F¡Y®ŠE=ž}Ë-·Üâ»iævú|ù}û÷—¾o†üþúûßÿ-)-0ý}„$Õð,VRZñÜ|óÍæí·ßÖ¤WQCÊXIi †ZúMè[Ô�)VRÚÅò÷¥Fò±’ÒŠG£ó(Ùí[´}È미^í/äõsñÅ' C‹÷ë;á…Å£Ñtbý&TY5úоå�ücÐç_º^}—?ãŒ3Œ¾³úO,C„¾ÄnWA�Y�ÉJSÁ^uDIL{ÕÌF €T°@¬–Ë#R²Á·ÄL»B¾È…$Ù]ýÅ�S§N-^Ôêy Á»¨¥o™7ožï¦-nòm�/ɾ½©t€¾=e[Ä Ü§oO¬ÅŲOßžs‹‹Ç·w ö©˜±‹oïIÅ¡}±‹zÁùõ‹]BŽ1ÖÐxécR/wß:ÌlV½!æí�†Ôõ-Ñ v)·xBn²úê«Çæ º�I{¼~Êíõ¬!u}K¹½ÿ„ü-´d2ÜgÈ{iKñ„|F‡|öµ�†Øô-!Û¶TgÈ÷Ÿ�m[Чoß¾-=µÄå!ß-[ÚyÈð¬¾#´‹–ëVZ¾¥®®ÎwÓvÙ®x¸Ú•„ül�ß§j|æ[”\Š]BöÙ×Bö3Iîœ'Ožì&Ûürí¨¥ÊÔØË·|úé§¾›¶¸�Oû–ö¸^rŒ18“�c Ij»ú‹C6¨áZ¥–ÚŒÎÇ áp'\$¦+õUÃq!€tª€†N—؉3]4 ¹ÏPèýÓǦi%…Bî‰ãžRé˜4´Ý¨Q£Ò‹Ú4½çž{¶iý%­¬û†Ü£sÿý÷_Rmz^ÃʇÜÓð�CiS}KZyÇw :_v)$±]Ÿ†’1bDñâVÏk˜ý˜C÷Úk/³Ùf›µºþâ5Ìy̋׶/äž¡62ôž‘écÔ0y!çK÷d6lXz—AÓÚŸ†Cõ-º'tè=ªÓukX»�dІ}[ýõÓ» š>ýôÓMHâLñ„Þ#6}Š'$ù¥xBï雎Gç+äõxê©§½_¤cÑ´z„ž¯˜ÃÐéøÖ]wÝâ0[=¯¡ cÜ7ÒU¨xBîq¯×OLí/ä¾�±ÿ¾N;í´ Ä}Œ{À»s¥ÇO<1èõ£íC>ÿÒ±hZ·Â 9_ò‰ÙØBÇÒXBŸï!ŸÅ>ºmLÈïÅÒ�¥8}ß 9_5¤¡Xq<ûí·_Ð÷Ÿ£Ž:Ê{ØÿâX4¿Ûn»}þhhà˜É`ýž 9_ú¾›ußϬcoÍ2ýþ ù>ÐAµ¦šV¯³Å[ýó-ºUUÌ¢ßÊ›o¾¹÷.÷Þ{oïm³6Ô{OÈï÷Ýwß=k·ÞËô]¼øS[vûz‹~[„\SÚyç�Ûþ×]mµÕ‚^?Ûo¿ýëhË ºuHÈß{Ì»wïÞA¯ç�k}YnºEGÈï¹ØñèJ!ßïBÎu–�®‡\¯ËڧﲚŒìq¤Ó&Ê=¦¯|ìSó›{ ‡K8nûU̯öˆßÙ‘í@èhÝÃP÷ZÓ]èÓð]RεLÖ]÷c7k:ýœ‹]Ët]VC~¸i~[Cf¥‡ÏŠCë/×2-×ñé~i!_ä´/ O¥!Å\kðt�®®–µ®{NÐOè… ¬xt¾Òûwõd=f-S<Ç{¬÷ý µO g©áàÓ>ZîbÓ´Š›oéQ=—t!*´h¸> WäâqûsõjÞM/îQ=…b$ÞÇŽ›ø¤ÿ¾\LéXÒÓ.®ô2Åㇻ† •�‹']Gº^-WI/KO«JÈ�þÜÞ�Q½†Üß{ºŽ¶L+ž�‘.=ê=Qñ¤ë×òô|zZÏ¥‹žS£˜�‹Žéýi8Ãôß{ú¹ôtVLn™z‹éÂHŒ¢áä²F'pu-©­§×sHo¼tŽÎý½·Ã’–Ë'V£ GèâIÇY<ÝRLZOñÄêÁ¦÷ ôß»‹cqõ»uÜ£^ϱâQ/„Åù´&.Ý;V�ºO>ù$ÓÇ{kõÚéß¿kV]â:u&ë|-qæô·û|¹÷ç¬²Þ Š×“O¬¿/ é,ŸÖÔÛÒ:1ÿÞÕ3&}¾ZªS&-=§ó«‘Öo¼±ÄxZŠÃ-×çiH£˜ôù×ðÛé¿wWGz�âéâuôY±z¤tOÌ $U׳¤8ô¼¶ÑùŠqQV¯ éÜÒßWq|éùô´^Ï1¾ÿÌž=»àû˜;^ç’®sqÏéýùk_ûšÛÌûñ«¯¾J¾?Ë']wKÓ-Ťxb$šôÞ¬ï«Åï?éxÒ›^žžÖßú·¾õ­ôª^Óz/|ä‘Gò¯Ÿtm™Öß—îYZôÝP¿/Üë¹51¸:Óëêõ¬ûû¶æ»€Û>ëñé§Ÿ.ˆÇ­“®+=­çݼ{Ô2ýnWCÚÐ^ó²Ñïwß׳bQÑëG ‡”¬ )ºž [¸óåö•>öÖLë÷»î§«óR4|¿îu¯÷çt½Úgz¾¥iW·^Ïò mh¬ë?ºÅ“Ïß{:f]oQêІ½ºœîu_üúI×å gä®�…4lP=W\q…Ñk¨¥ÏÓt ‹‹Qñ¨átèg††'ÿç?ÿ™ÜZ0«¾¬eÅ1jÅ£óµÃ;hÖ»èžëéë«nGYu.n™°©¡iè5ÝnQ_ůg—{ÌŠEϹåjô¡ëÇ1>S]�!�»]øªyõ“»xà”fĪaï�Ú!‰éVf@@@@@@@�êøÖE¯šW>.LLßÒ3rµðÄtFgìêDæ¨@@@@@@@�j¨m�2mPV÷˜ÎW<7 f:d¦@@@@@@@ÊU #/m²rÁ>ñÓcÚG�m@@@@@@@� ¨Íè}Ü)3MbºÂ^, € € € € € € à#P“Ñeº¼†òÎÈ’gÄìsìlƒ € € € € € € ÐY=¦3rÁ>¡Dé1Ý·G]IÝYËJVb € € € € € € €@Ydu>.«Äô~› 4k è•Ç´TwsঃòóL € € € € € € €”·@mFf:ÖPÞõ1]½£ï?e„¹õù‰fQƒ1ûl<À ìß=Ʈ٠€ € € € € € €@d åÝi(ï(‰iôïYoŽÙvåà  @@@@@@@�Ø5íØc:Ê=¦c0ûC@@@@@@èX�¬Óeu�éŽå 6@@@@@@@�ؽº×•ì²G}œ¾ÎqöR @@@@@@@º’À› ,w“!ýÍVk.U°Ìw¦¦Ñß�Ù@@@@@@@ ržzªù÷›_™åûu3Çn»²éÑ-N_gÓ•óáH@@@@@@@�²ˆ“Þ.ËC#(@@@@@@@�r 1]g�@@@@@@@�  1]Á'—CC@@@@@@ÊA€Ät9œb@@@@@@@*X€ÄtŸ\ @@@@@@(Óåpˆ@@@@@@¨`Ó|r94@@@@@@@ HL—ÃY @@@@@@@ ‚êcÛ”Ù Ím/L4��Æì;j Я[¬]³@@@@@@@�.,PÓhKhüSmRz× _1ã&ÏMvµÒÒ=Ì'�0ûwÝ5Û#€ € € € € € €tq�(CyßþâÄ|RZŸO�gn{qR§!|@@@@@@@�QÓ3ç-*‰eVƲ’•X€ € € € € € € PñQÓ¯Ä"€ € € € € € €x Ô{oɆ € € € € € € €%0iú|sÏ«_šÝjÌ!›¯`êkk¢‰é(Œì@@@@@@èÚã&Ï5{^òªùbÆ‚ä@n|f‚yèÔ‘Q*ÊPÞ��¥±ÔÄIœ—î˜% € € € € € € €ÑþñÜ„|RZ;íÓ™æ¶'E©'Jb:J$ì@@@@@@@ ÓfÎ]TR÷Œ9 K–ù, 1í£Æ6 € € € € € € €@… Ôfd�2FÏö9ìŒ]ûì†m@@@@@@@º²@mÆýš³îëìq�$¦=ÐØ@@@@@@¨4�Œ¼´)«ÓY½·k*í,p< € € € € € € €@ Ô˜Ò,o£ÉÊ·�Óm7c @@@@@@@ âjKóÒåÕcºâÄ9 @@@@@@@ Êj32Ó ÜcºÊ^. € € € € € € ÐŽyiÓЧÂú8»ÉÞËÅlŒ½C¶z|ëFÙÉ?;§Ç¾=êÌþ› 4½»×eo܆¥¼ö¥™9oQS=¹þåª#W“Y?gToWÔ2  € € € € € € è.Ì ênÌ�ÉÐ× M�¶·±�´ÉÝF³ÈN«óqnÚ>¦ž›es�u6·ÞJ} wì9wÍŸ'w†^ºw}RŸbXT“�Ë&œ]Lt bŽQ¢$¦3¢¹àß6)½„ò—Ç?7÷Ÿ<Â( ë[ö¾t´yþ£é¾›l·ùÐþæŽ6Ê'¯ žlåÌáW¿iyû«V®½øÕÛró‡†-~¥%<ûÓ[ß77>;a k-ùé¾ö�°ýªæ¤o¬ºä•³Æ¥�|b®{j¼Q²¼ ž$Á›âvûÜs¹Ì¸þr‰r¥ÌsÓú#:z›•ÍÁ› ZLmK~ê~Û¨áo6žésæët øâ:su7Ç ½»uúÙ†‡n±‚Ùi½e—\ébÖxëóYæoO�7Óf»xš-ruå6NT¸úµÈW«ÿèÑþëÙ­Îì³ñ3rp¿Üžÿý䫹掗&% -’jlv÷MVM�MËô|ÒÐ! £ù\jƒþ=ëÍ�›²q…ßÖþîW¾0zsvö…ñh._sÉú6=ö±çkûuâ4Dyåãfö|5Œ±;Îý?å“,-˜/Œ/™Kê¬1°—f‚ˤéó›¢4YØ=æŽ]�͹X‹çsë¨áP·ºÜöÁ±@@@@@/�&ÍI®÷v¯¯M˜JÇ)'—$9S��6¹¨­ÏÙ“g.0½{Ôš× Ë](x%-ÿøÐ¸$‡¶|ßnI õ)ÙÚS~y* ªØÇN¶Ç³ Ñœµ×P3dùž^&n£'ÞŸjŽ¿þ�äúúн Ml½ªO ×ĪiÞŧcQŒïL˜�ìîÿ4̲ù n×^�Ê�{ÿX¯m‹7Úgã�æòÃÖ.^ܦù½/³ùÓãäOUq¬¡¼£$¦Û$‘ZyÌ¤Ùæ–ç'šc¶])µ´õ“7Ù„k¬¤´j}Ξ ¿ÚÖßÛvåÖ‘Zóš'?�–”Önoxf‚ùºíŽÛF˧jiý¤Ò1’ÒªQ‰äókF ég¾6léÖ‘ZóÁ×'Gû£ülÊk˜ÁË…}±œ0m¾9ûž“ó£}¹ºÝc.†B/-SÀníC_2ñ­!¦¿=®Ðrù£Ÿ& ‡V]®‡­Ãþ¯éØ›ªMbUÝùy;¡×—+z׈*'ì°Š[ô¨ÏÕ¯f-0+ôïž?éã×Îs9Í$óZnãR£šùö½Õ÷{FqðzÖ߬#©ä}šêÍŽ-Y3‰kþ¢†ä‡ÍÚ+ôN¶ýÏ3æ›…‹M}�mºåbÈÇ•;IíEÏIINúᢆ:zO¤ € € €´�€Í%É!ýKý„ªl’ý=8ßöXÔ( J<åê°õ4Õ¥$•¦•µ²ÿÏ?¯¤‹–«SŒ’€ëG걨ÎQÚ±:×�›ÏÅ‘žNâJbk4ꈢû½~÷k~yª+ºÆú{›8ÓãÊËô´aåzKê¸+;‘Äap±ºåZGF㾜kØßðgí½†xýçÙ¦™o|×(ɹª½^§ãvçA�¹XÒ±åΑ–«·©ÊKã¦ÛN6µæ {mõë‡%_•sú‰íì§2°_÷œOS.†\<Íq5ÇèÎaî9mÿ£�V5§ï6D“ÞåÇ·¼oþñ\xÇCðÍ —3×µžw,³ç7˜=.yÕ¼m;×Å(Ï4-É_¬æùÒu¨ƒ®|= EÉ÷q6ŸRd­¿‰m×òË=5fj´ü—ŽCײ·^s©¤C¤ÏqýÝv€Œ™”V ¹¿8Ÿh · ¿Jk÷×§‡ÿn J®ˆØ%$ 7aêüØá˜�m·¼×ÔÒÃwû¬íô¦ã›˜~Ù~@Ä.úÃòML«5Mìò¨í-þ÷›“£%¥ÝqÝýêÞ‰éÛmOé�¿ƒ{|gü,së Íá[­èµéQ 5b%¥Uñ3öKÏõöÍ9$žXIiÅs×Ë_Ø^ÜË&·8Ð|[Ëu6#)­z•È»ÈÞŠAÉÒmhJïójHyÕë˜[¥ë=UŸ;úí³á*¹‡Íõº8rKœ‰{Ì=›óÑöçÛà#VõkDéö¥ã:óîÍø©óÌF©�és¦uKbt ì3¦ÍKZíÿn¿5Mhc”/íˆßÜó‘m³Ð TÚ°¥øÜ%±5ÅâBúÜËÛ�åW{®ž\|rÇêó(g5ÎÓ…P5dJêkÒHŸWwò|jFŽã­�¾2�¶ÛjÁ·¾šo�ëü¤Gƒ1+,e25w¾ŠëOæÝJM3ú´7ŒÛçmÖGÅ—iò)ù¹ãVÌxÔ/!]DUâã{«»Ž~\<%ûT¥E%½Hñèõìû;¹h×F æU–k:_Jl¤Káœ.ø§Ÿ5F‰¥:ûâþþ×Ã)»½^õßÏÌtûHß:/]g.Õa×NÅ‘š4_ÌX`æØÆ�ú>:"œ.¼WèŸû¼Èׯš*võ7Ïç–h^‰¡Þ¶!åé>/tîsïGÉ߇*§ÏUsÝ9I÷œ–§ãS£k5Ä={Ÿ¡fu»��2ÆöüÅc’†¦kR<®ît�Íb…±äVk}¦Ì^`.8x-³©g§w jÈ­2áP@TCR‹ýO’°kZÑÅ‘7ÒzZÇ>êºØ ÛñGßwÙÀïZBS5F¿)õýYEC½º:òõ6Õ™[žK–iZEZÏ%pbô¼úŸ%ßµ�RX\¯«3 Áþ'=ïâÒ¶*ê(ñsÛ¸<¤üܾv4BfŒ¢[‘^rèÚA»:ôÏo˜ÿ¾3%hnc]?ü§½žàÛ€Z£»]ôjòztû yÔõ£O™tñÙ�~ëìk;j©è;¹æ}Ëü…‹Ì‘}ÓüËÆã~7µu_:—”Ö¶ú )º6ª¿Ñ=G ðÚÍ�öú|¬¤´ïœôqˆIDATxÈ^GÒw;½®}ÊMÏŽ�–”VýSí{ª®gŸò ¿ßGê0–¶xÚ&—}Ó 7vqïÕ>ûÕwžØ%Æ­™SÛ~Y´pú}ÍŸ%ÚRÚ aêŽî[ÔÃçü�K>Ô}÷Q¼]ȃ¯Û¡�/{´9S¼oŸù-×ðk�¡º6]½¿¹ºéG€OÝYÛø¾©k_ë¬gLüt\«Ûá|ËÊK‡õæËªw€mEå[\3ßí³¶ëíÙ;Yûj�‘œ•ðô-êÙ»„ôNýb’u,ŸÙ/`¾åCÛk6vQïWßÄtÌFî¸4lºobúÛ/vyò½©Þ‰éÿ½;%Ÿ”Ž—;¾‰éû^‹“àtÇ¢–¼÷ŽþÂ;Yu‡ý’+)­˜tAâÛ0æ_÷멬„k¬¤´âQC5&9Ñþàö)÷�þ2ZRZõ?ijý鿟zû(žXIiÅ£^z-ûŽJ FP±’ÒŠçB{{}§Ò(6>å û^á’ÒÚ>ù(,¾ú’ßñ’?'•`Òˆ1¾?lõCÛ%¥U­þ^CÊ/ïüÀ¶(îáý~¨†”G]óV>„7È©¥ýr6¹´�çùÒ�¾¯hn8¤óR³·ùù燛Íìkȧ(Á½�zk޽x­òáaŸ÷{\<Úüç'{'ƒuûÝJÉ}gÑgsHÙõÂWÌCv„ŸµŸo?Þÿ|é¸bŽXõ�çrñt¯Oµ qx­|<Ðúè{TŒrû‹Í-?k(¸ÿ¯½�Å(wÚćRúýö×ß—’y1Š~[Üm?O}1ŠaŸË^³#%ÆñÑû¡âÑwŸ¢ÏÓÝmÃ`%_c”ÇíïK�˜§F;>E‰�o¥>O}ö‘ÞF¿�²‰!ßpJNíi?¿—Š †”½l#ã�D•dîoߟջT%4¢ï¾÷ØÏSߎ6ú>ï’ÒŠ'4¢‚jH°…gCw½ªÑ¢+ú¼)wƒ•ûš=Fø� ªÏãXIi‡’xÛ­µŒÙÏ3‘§Xb%¥ÏKöïKûüî6+i¶ÍE×FÔH"V™bÿN•,?Éö ö)¾n{’G.ÿµï‰¾9ŒXß Ó‡ô†í,ãûû}lSƒðôþB§C®i«aC좕¾Å5tõÝ>k;ßÏ.íkCûÞ»ø^ W»n°¼ùãƒã’FØ1âÒ¨¦±SFIL님¾é‡�Zª'­¢ì�<]Z+m©•k¥¥ÖSj1ëû£_�º¦/‚7ÙÅÓç,*©/—kVAÛ¨-]q|šW<Ùûû{¬}ëE¢Voú�£¡,KŽ?1iª_±¤ç“éf/]|ÐÐ!ñì>|yóËÝWO.ÎêK�«O�¹ü 3Ê=×rLËÚÖì?Ø~ï/)òÙÏö^yÕ^¼Òpé1Š>€wµÃOø �¬κ‡rŒ¢{¹µ_o`Õ¿×ÈÉ—�X÷(×—Ê�Vûm¶‚}íLHZ«ÅðÑP,{4DÑëùâ‡?I†ž��~І4DÙÁžo%b–m<‡ÉW [ ]ʶF�ó·åŽ)äå¾_]ÝY�!=ª†4bÉŠEËôšö-ƒ±´TgÈ—±~ö30véa{~ø–ZÿM[¬R=�|K{|ñžðc]=¡b—�†ŸNñÝ¥¥ãÐõ¾å}Û¨&vywülïÄô¶!BìòнÈëûÃ6f# w\J¢øŽ ¡Öȱ‹.Dø&¦³=Üh±âú·½x훘V£#—”Ž�Fpí{íî>»­KJ{í h#½¿ª…¼ïðvºæ’ÒE»öšUOåÛìÅ:߆CJ¼ÅJJë”°¸Ù6ŽÒ-M|І!Œ•”Výjx¨[EùŽ’ ¡äb%¥�†}Tßž¯×=õy´¤´âÑmÆ´O_]dŽ•”Vÿb;Xo¯søÙÆJJ«~5„Òß—oÃ<Å+)­x”ÀÕh¾¯gÝÊ+VRZñ¼l¿ûè}ãÑûs¬¤´âQC±ìùòMÄèºa¬¤´âQ£ò[mã†#=GnP"/æç©ŽiøÑ“={À©á‘KJëøb}ÿñ½N¡÷—”Ž‹öñèÛS¼Ó1ß{ÜñqßT‡*ÊÕÕ\gÖ|²žÝ¦g÷:3ÊóÞÒi×ë¿·¾QOJ}‰OŽ9ãØµ¾|8‹|\š°E� 4¤”¹!CKi¨?µ(Õð@‰�vÞB½MUç{�%1åV×VÉpµo>È,pΔhW µìÓ=‹“:2âQ}‰�]AJÉ´[–<6š~öþ§‡Úx²†{Ôö­)#÷3?fýäâA.ž\]Ú¶¹Î–ÏS.þdm{™úä¤ï0ðÚ‹F�8{ï¡I<̽FåêJ{ä–7û4¯§aèô~¾Š÷°ôŠG_TÔj×ÝcZË|‹¾àoßËv ¸‡²z:ê˜Ô+4Fù¦vë{ž‰UÿÎv{ ›£–·1Š^;ÇÄ£/rw½2Éöä�ÓjV!_ä° ×®{r|´‹#º'о£ü?›õ~z¡v4´giú\ËþÍú–í°Àê 3YµCÀß—ï->wü[­6bÌâöíóÜðÁþ­^×o‡³ëÚ¡À|ËšK‡:öÝ—Û.d¨Æ•ù¸ú‹WZ*7fñòÖÌë6 ±Ë²ž½©‡oOÙŃî�î[z4:j©N aë[ôc¹œŠ»Ç]̘4ĸo‰}]qhÈ}ßÚƒ*«^5÷-êQ»Lµ¿ã|ËT;Ülì¢^L¾å«€m[ª3¤qÝWv˜áØ%dŸ!ÇÒÒqhÈaß;‰§8BF‹uÁ:íò÷®ßȱ‹®kù–·5l©Î�Ï ]„Ž]t¯Wßr-«¥:uÈ·´84¾ïívEÔ·¨ÇZì²úòþ ï×YÁÿ·IKDZQÓíkZz~qË5ÚUì²u@GÝÖç; W¬¢ï¿»nè=aoÛQëŠÇ>�Öiˆ}íb¯±ú58º÷Õ/M¬ºÖ¢kJ¾E�˜t½OÃÓÇ('Ù:¾�,T¿:Bêvº•€ŠFOSBSïÛJêõàœùy»Poëî9­«Æ¡Jx^yø:A£Ú¶÷‚¾ï¤á挻>Hâf¯/¸ä¦êq±¸óó¶nÅãbÔw0}6ÿb÷!&´q‚nM§ÆPÔB·æqÇ�Ô×To’„uõÛ�'Å¥Ñb]<úÍ% »Ztû‘X· �EÛëí¿<2Æ®¢î£Æ&/üéF …�!€Tª@>9n?r’ûŸÜ²âù 탾=ë’ûäŰ™`aÓzÚ¤žk@  7�›{N+©g—z›� ¼?•;õjÐE­$i!ÅbŸtuº�èô2=éâœn/¨j˜{ßž.÷¨!³”tP#’¤Ž¢X\\Z?7m×Êý?™×=Åt¾|[Æ»8ô¨ ©¿½ï£äËâJKç~´»úõ¨ŠÓó¹xsË’g킱“ç$뜹×P³òÒa?Þ5¼ïvÈb½~Ô(NgÁÕßl ¥¹âžsçPK5¤¸Î÷…ºÇYà�eõùñ-ï%Éò�õz´'µ'�vº)=¸×•bHÇ¥x4LÞŸ�\7i̤ç}Ëvø­ï]÷v²ÿõWÎ}‘Oê*ŠCû/Œ-W£bw½ ÿpÀšæ°-ýG ÑÿlDžÕ4œ\r¯ó&=—3È�¿æyM¹ØrçQ�ŽTÔ¨îhÏaÒ’Øÿè¾RºçlŒ¢XSH9ÛÞ�7VC�r»'¸n¨¡_HùÁßß1º‡iŒ¢¿u ]Û-à)1‡ÒÕE> -rÁ7æP¨}DCYúŽ¢¥$énv8ðwí�ð1ÊŠ¶QCÈPÞºßõ.¼’Ü;F;Úxb– xìÌÏ��FÚóî#;šÊvç½”Üo6–QÈ­Ôw§È>ÿýÙ(ï[èïK¯ç˜åQûzö½%›Þ—õznp_üS�£ÿØx|{²©Ç¢âÑûtŒ¢!à:ÕúxÞŠâ3{+D}^Äjà ÏS}^øvÐ÷^Ý^ãƒÀ[†8[%ªî=i„ñÕK¿¹uû‘W?‰3¼zˆihz�–é[tßÚ‡ßüÊwó‚í4ââ�ÇnP°¬­3'ÜðN2|r[·ËZ_#kü~ÿ5³žjõ²sïk{Q~Òêõ‹WTB˽]œb;ýÔÞÇ=¤h´Î_5%ÎÔñFi:Õ‘ü³;V2ª`:Y–{>I¨Ùy�D r™íÔ¤ß/´Ou P$:öÜ?;g§]BSÓùxìLzÞí‹G( 1]jÂ@@¢ ä’âºè’ËØ'ÿÍM&�ÒËÝ…·�.ì*¹2L~ú€Ôøc¦~KûSÍõåóZ7¿,™.ŒY!ôc\-¶c%dÔ3«_�\o…;~ÕÑOS,Ià¹ÚuQE1C†¹J‡†ôžf�ta$W[î¼5OçÖvó.8¯†×›¿°Ñlo£hHf5DÐt+WoÞ¤i¢x¹«[Ž4ªN¬‘”ÔËb²½X£¡×\IŸ3-ËÇÖ´‚›·Ï$‰õV=m×Õ‚{…kôˆsmÃùÆSX±³ÉŠM  mvÆ«�²Rt¡OC·éõ8ÄŽø“«/W{³�šKáòF3Î&”u¾ÎÞghÁ15oÑú)%_qǦ‡Q©Ð§9‚týÚsó3¹™1“f'Ù~·ßšÉÅËÖ×^ºæèOf& «Ô3B=.TÒõÔ�ºåƒ>ÿþpÀ°à÷½mgJ¾(‘«âê/Œ)©[¦ï_¹usCŠ«±™F& õEŸËçÞÿQ22ƽ?Ûzܱ7×ÝKÓ3Zž^gâôùÉ÷Ý¢#F¢J�;õþªZùlùi;‘®¿y:7¥‘(ôzŽõýG½:õÏ5¨S-I,qäbÌŧi­ëzý‡ÜV.wrÿÕíÕËOŸ‰Å±¸:“D^SœZG%‰Íþg¾µUÒ-ä6e¹=æþ«ï¾sìHzÊÕßôØtü.›‹×­—Þ Ó €Õ @bºÎ2Lj € € € € € €t¢€ÿ ?:1hªF@@@@@@è:$¦»Î¹"R@@@@@@@ K �˜î’§� @@@@@@@�®#@bºëœ+"E@@@@@@º¤‰é.yÚ@@@@@@è:$¦»Î¹"R@@@@@@@ K �˜î’§� @@@@@@@�®#@bºëœ+"E@@@@@@º¤‰é.yÚ@@@@@@è:$¦»Î¹"R@@@@@@@ K �˜î’§� @@@@@@@�®#@bºëœ+"E@@@@@@º¤‰é.yÚ@@@@@@è:ÿ xem}TIEND®B`‚././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/source/user_docs/capi-crd-arch.svg0000664000175000017500000105360515212012064022777 0ustar00zuulzuul CAPI Management ClusterOperators / Controllerscapi-controller-managercapo-controller-managercapi-kubeadm-control-plane-controller-managercapi-kubeadm-bootstrap-controller-managerCustom Resource Definitionsgroup: cluster.x-k8s.ioClusterMachineDeploymentMachinebootstrap.cluster.x-k8s.ioKubeadmConfigTemplateKubeadmConfiginfrastructure.cluster.x-k8s.ioOpenstackClusterOpenstackMachineTemplateOpenstackMachinecontrolplane.cluster.x-k8s.ioKubeadmControlPlanewatcheswatchesaddons.stackhpc.com [1]HelmReleaseManifestwatcheswatchescluster-api-addon-providercluster-api-janitorwatcheswatches././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/doc/source/user_docs/index.rst0000664000175000017500000002164415212012064021517 0ustar00zuulzuul============================================ Understanding Magnum Cluster API Helm Driver ============================================ Introduction ------------ Since moving away from using Heat in order to provision Kubernetes clusters, Magnum, like many other projects, has transitioned to Cluster API (CAPI) for managing these clusters. As an active Kubernetes project, Cluster API uses software extensions, called operators, to provide a declarative API for cluster lifecycle management. Magnum occupies a unique and important role in the OpenStack ecosystem as the project that acts as a *bridge* between OpenStack and Kubernetes' Cluster API (CAPI); acting to further expand OpenStack's capabilities. Effectively, this provides users the ability to create Kubernetes clusters using the OpenStack API, bypassing the need to directly interact with Kubernetes or its API. Therefore this document, based on the following `blog post `_ , aims to provide a comprehensive guide to the Magnum Cluster API Helm Driver to aid in understanding its implementation and architecture. .. note:: The Magnum Cluster API Helm Driver is reliant on the pre-installation of some OpenStack services on the target cloud before being able to be deployed. This list includes all the core OpenStack components - Keystone, Glance, Neutron, Cinder \& Nova - as well as Octavia (API load balancers), Barbican (cluster certificates) and Magnum itself. Core CAPI Concepts ------------------ As previously defined, Cluster API provides an API for being able to manage the various components of a Kubernetes cluster. This conceptually looks like a Kubernetes cluster managing other Kubernetes clusters; the former, named the 'CAPI management cluster', is the one providing the API for managing the latter workload clusters. The CAPI management cluster provides this API via a set of custom resource definitions (`CRDs `_). These CRDs are groupings of API objects which, in turn, together make up the Kubernetes cluster; for example, the ``pods`` resource is an endpoint in the Kubernetes API containing, specifically, pod related objects which are targeted when performing operations on pods. However, ``pods`` is a built-in resource in Kubernetes, whereas CRDs are, as the name implies, custom and contain a select set of objects depending on the CRD's purpose. One could manually target these CRDs to deploy a Kubernetes application and then manage it, but this isn't very efficient or scalable. This is where Kubernetes `operators `_ come in. Also known as controllers, these operators are a way of packaging, deploying and managing a Kubernetes application in an automated way. Furthermore, Kubernetes operators keep an eye on the CRDs and, when a change is detected, they will take the necessary actions to ensure these changes are reflected in the cluster. Therefore, the Magnum Cluster API management cluster consists of a collection of CRDs set into motion and monitored by a set of operators. The diagram below highlights these various components and their relationships: .. figure:: capi-crd-arch.svg :width: 750 :alt: Cluster API Custom Resources and Controllers for the Magnum CAPI management cluster .. note:: [1]: The ``addons.stackhpc.com`` API group will be renamed in the future, as more of the relevant source code repositories are donated by StackHPC to be under the control of an open-governance foundation. .. note:: Here are the generalised roles of the various CRDs from the diagram above: * **clusters.cluster.x-k8s.io** - The top-level resource representing a single `workload cluster `_. * **machinedeployment.cluster.x-k8s.io** - The ``Machine`` equivalent of a Kubernetes pod `Deployment `_ which manages a set of pods for the purpose of running an application workload. * **machine.cluster.x-k8s.io** - A representation of an individual machine, usually a VM, belonging to a workload cluster. * **kubeadmconfigtemplate.cluster.x-k8s.io** & **kubeadmcontrolplane.cluster.x-k8s.io** - `Kubeadm `_ is a tool for bootstrapping standard Linux hosts and converting them into Kubernetes cluster nodes. Combined with the relevant controllers, these two CRDs provide a declarative interface for node bootstrapping and configuration. * **openstack{cluster,machinetemplate,machine}.infrastructure.cluster.x-k8s.io** - A representation of the OpenStack cloud resources required by a single workload cluster. The `cluster-api-provider-openstack `_ (``capo-controller-manager`` in the above diagram) is responsible for reconciling the state of these resources using OpenStack API calls. The ``cluster-api-janitor`` also watches these CRDs to clean up any OpenStack resources left behind upon deletion of the workload cluster. * **{helmrelease, manifests}.addons.stackhpc.com** & **helmchartproxy.addons.cluster.x-k8s.io** - A representation of a `Helm `_ release or plain Kubernetes manifest to be installed on a workload cluster. Installation and subsequent reconciliation are carried out by the `cluster-api-addon-provider `_. .. note:: To view a list of the installed CRDs on a given Kubernetes cluster, run ``kubectl get crds``. Making Workload Clusters Reproducible ------------------------------------- The benefits in making cluster deployments as reproducible and consistent as possible are well known; so how is this achieved in the context of the Magnum Cluster API? The Magnum CAPI Helm driver uses images, often built and tested by CI pipelines, which are then referenced in a Magnum `cluster template `_ in order for them to then be targeted. Nevertheless, the *way* in which these images are used and managed can vary, each appropriate for different use cases. For example, the following `section `_ of the driver development environment `script `_ utilises OpenStack CLI to download, then upload the latest stable image to the target cloud, followed by generating a corresponding cluster template; which is more suited to a development environment. Whereas, a more production-ready approach would involve a level of version control for both the images and the cluster templates. An example of which can be found in the following Ansible `playbook `_. High availability CAPI Management Clusters ------------------------------------------ A typical requirement for a production environment is for the CAPI management cluster to be highly available, which by default, the Magnum CAPI Helm driver's deployment isn't. However, by utilising the following Ansible `tooling `_, alongside this specific `example configuration `_, it is possible to deploy a high availability CAPI management clusters. The resulting Magnum CAPI management cluster architecture will result in a highly-available, auto-healing Kubernetes cluster which itself is managed by a Cinder-backed lightweight k3s cluster. The illustration below provides a visual representation of this architecture: .. figure:: capi-architecture-diagram.png :width: 700 :alt: Architecture of the Cluster API Custom Resources and Controllers Some of the key features of the Ansible `tooling's `_ resulting architecture include: * Version-controlled: - `configuration management `_ - `encrypted secrets `_ * `CI/CD automation workflows `_ - Simplify routine maintenance and updates. * `Disaster recovery `_ - Using regularly upstream CI tested processes. * `Remote Terraform state storage `_ - For the purpose of maintaining the state of the Seed VM. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.6934328 magnum_capi_helm-1.4.0/etc/0000775000175000017500000000000015212012201014360 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7014327 magnum_capi_helm-1.4.0/etc/oslo-config-generator/0000775000175000017500000000000015212012201020563 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/etc/oslo-config-generator/capi_helm.conf0000664000175000017500000000017415212012064023364 0ustar00zuulzuul[DEFAULT] wrap_width = 80 output_file = etc/capi_helm.conf.sample namespace = capi_helm namespace = capi_helm_cluster_labels././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7024329 magnum_capi_helm-1.4.0/magnum_capi_helm/0000775000175000017500000000000015212012201017072 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/__init__.py0000664000175000017500000000123315212012064021211 0ustar00zuulzuul# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version __version__ = pbr.version.VersionInfo("magnum-capi-helm").version_string() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781011584.705433 magnum_capi_helm-1.4.0/magnum_capi_helm/common/0000775000175000017500000000000015212012201020362 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/common/README.rst0000664000175000017500000000070215212012064022057 0ustar00zuulzuul=============================== Magnum CAPI Common =============================== There are two magnum drivers using Cluster API. Ideally we would both build on a common shared code, ideally within the Magnum tree. Some code and some ideas for these utilities come from this alternative Apache2 licensed driver: https://github.com/vexxhost/magnum-cluster-api This is structured to make it easier for us to work out how to share some of this code ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/common/__init__.py0000664000175000017500000000000015212012064022470 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/common/app_creds.py0000664000175000017500000001256715212012064022716 0ustar00zuulzuul# Copyright (c) 2023 VEXXHOST, Inc. # Copyright (c) 2023 StackHPC # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # This code is making use of the good work done here: # https://github.com/vexxhost/magnum-cluster-api/blob/main/magnum_cluster_api/resources.py import secrets import yaml import certifi import keystoneauth1 from oslo_log import log as logging from magnum.common import clients from magnum.common import context as ctx from magnum.common import utils import magnum.conf CONF = magnum.conf.CONF LOG = logging.getLogger(__name__) class ApplicationCredentialError(Exception): message = "Failed to perform action on application credential." def __init__(self, message=None): self.message = message or self.message super(ApplicationCredentialError, self).__init__(self.message) def __str__(self): return self.message def _get_openstack_ca_certificate(): # This function returns the CA bundle to use when verifying TLS # connections to the OpenStack API in both the Cluster API provider # and OpenStack integrations on the cluster (e.g. OCCM, Cinder CSI) # # If no CA bundle is specified in config we use the CA bundle from # certifi # This is because the Cluster API provider contains NO trusted CAs # and, because it is a pod in Kubernetes, it does NOT pick up the # trusted CAs from the host ca_certificate = utils.get_openstack_ca() if not ca_certificate: with open(certifi.where(), "r") as ca_file: ca_certificate = ca_file.read() return ca_certificate def create_app_cred(context, cluster): osc = clients.OpenStackClients(context) kst = osc.keystone() name = f"magnum-{cluster.uuid}-{secrets.token_hex(4)}" description = f"Magnum cluster ({cluster.name or cluster.uuid})" # TODO(johngarbutt) be sure not to allow the admin role # roles = [role for role in context.roles if role != "admin"] if hasattr(kst.client.application_credentials, "create"): # keystoneclient return kst.client.application_credentials.create( user=context.user_id, name=name, description=description, # roles=roles, ) else: # openstacksdk return kst.client.create_application_credential( context.user_id, name=name, description=description, ) def _get_app_cred_clouds_dict(context, app_cred): osc = clients.OpenStackClients(context) return { "clouds": { "openstack": { "identity_api_version": 3, "region_name": osc.cinder_region_name(), "interface": CONF.capi_helm.app_cred_interface_type, # This config item indicates whether TLS should be # verified when connecting to the OpenStack API "verify": CONF.drivers.verify_ca, "auth": { "auth_url": osc.url_for( service_type="identity", interface="public" ), "application_credential_id": app_cred.id, "application_credential_secret": app_cred.secret, }, "auth_type": "v3applicationcredential", }, }, } def get_app_cred_string_data(context, app_cred): clouds_dict = _get_app_cred_clouds_dict(context, app_cred) return { "cacert": _get_openstack_ca_certificate(), "clouds.yaml": yaml.safe_dump(clouds_dict), } def delete_app_cred(cluster, app_cred_id): # NOTE(northcottmt): Admin privileges are needed to delete app creds # outside the requestor scope context = ctx.make_admin_context() osc = clients.OpenStackClients(context) kst = osc.keystone() LOG.debug( f"Deleting application credential with ID {app_cred_id} " f"for cluster {cluster.uuid}" ) if hasattr(kst.client.application_credentials, "get"): # keystoneclient try: app_cred = kst.client.application_credentials.get(app_cred_id) except keystoneauth1.exceptions.http.NotFound: raise ApplicationCredentialError(f"{app_cred_id} does not exist.") if not app_cred.name.startswith(f"magnum-{cluster.uuid}"): raise ApplicationCredentialError( f"{app_cred_id} is not managed by Magnum." ) app_cred.delete() else: # openstacksdk try: app_cred = kst.client.get_application_credential( cluster.user_id, app_cred_id ) except Exception: raise ApplicationCredentialError(f"{app_cred_id} does not exist.") if not app_cred.name.startswith(f"magnum-{cluster.uuid}"): raise ApplicationCredentialError( f"{app_cred_id} is not managed by Magnum." ) kst.client.delete_application_credential(cluster.user_id, app_cred_id) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/common/ca_certificates.py0000664000175000017500000000445515212012064024063 0ustar00zuulzuul# Copyright (c) 2023 VEXXHOST, Inc. # Copyright (c) 2023 StackHPC # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # This code is making use of the good work done here: # https://github.com/vexxhost/magnum-cluster-api/blob/main/magnum_cluster_api/resources.py from magnum.common.x509 import operations as x509 from magnum.conductor.handlers.common import cert_manager from oslo_utils import encodeutils def _decode_cert(cert): return encodeutils.safe_decode(cert.get_certificate()) def _decode_key(cert): key = x509.decrypt_key( cert.get_private_key(), cert.get_private_key_passphrase(), ) return encodeutils.safe_decode(key) def get_certificate_string_data(context, cluster): # Magnum creates CA certs for each of the Kubernetes components that # must be trusted by the cluster # In particular, this is required for "openstack coe cluster config" # to work, as that doesn't communicate with the driver and instead # relies on the correct CA being trusted by the cluster # Cluster API looks for specific named secrets for each of the CAs, # and generates them if they don't exist, so we create them here # with the correct certificates in certificates = { "ca": cert_manager.get_cluster_ca_certificate(cluster, context), "etcd": cert_manager.get_cluster_ca_certificate( cluster, context, "etcd" ), "proxy": cert_manager.get_cluster_ca_certificate( cluster, context, "front_proxy" ), "sa": cert_manager.get_cluster_magnum_cert(cluster, context), } certificate_string_data = {} for name, cert in certificates.items(): certificate_string_data[name] = { "tls.crt": _decode_cert(cert), "tls.key": _decode_key(cert), } return certificate_string_data ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/common/capi_monitor.py0000664000175000017500000001713715212012064023437 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from magnum.conductor import monitors from magnum.i18n import _ from magnum.objects import fields as m_fields from magnum_capi_helm import driver_utils from magnum_capi_helm import kubernetes MONITOR_STATE_READY = _("Ready") class CAPIMonitor(monitors.MonitorBase): def __init__(self, context, cluster): super(CAPIMonitor, self).__init__(context, cluster) self.__k8s_client = None self.data = {} self.data["nodes"] = [] self.data["pods"] = [] @property def _k8s_client(self): if not self.__k8s_client: self.__k8s_client = kubernetes.Client.load() return self.__k8s_client @property def metrics_spec(self): # TODO(dalees): Not yet implemented for CAPI helm driver return super().metrics_spec def pull_data(self): # TODO(dalees): Not yet implemented for CAPI helm driver return super().pull_data() def poll_health_status(self): """Poll health status of API and nodes for given cluster from CAPI Design Policy: 1. Magnum should not require direct access to workload clusters. This means all health status must come from Cluster API's management cluster resources. These already provide health info. 2. Calculating the overall health status. We depend on Cluster and OpenstackCluster resources having updated 'status'. From these, we can retrieve overall health and report back. Some errors surface only in OpenstackCluster state, under FailureMessage and FailureReason. Surface this to users, as it can be helpful (eg. Neutron 409's when deleting a network that has ports) If we find any Cluster.status.conditions field *not* Status:True, then list and set Unhealthy. If we find OpenstackCluster.status.ready *not* true, report back and include FailureReason and FailureMessage if set. Iterate all nodegroup MachineDeployment and report back on status.conditions. 3. The data structure of health_status_reason: As an attribute of the cluster, the health_status_reason has to use the field type from oslo.versionedobjects/blob/master/oslo_versionedobjects/fields.py :return: None. The class variable data is updated with current status and reason. """ # Start with a good state for everything status = m_fields.ClusterHealthStatus.HEALTHY reason = {} for monitor_type, monitor_func in [ ("cluster", self._poll_cluster), ("infrastructure", self._poll_infra), ("controlplane", self._poll_controlplane), ("nodegroup", self._poll_nodegroups), ]: reason[monitor_type] = monitor_func() if reason[monitor_type] != MONITOR_STATE_READY: status = m_fields.ClusterHealthStatus.UNHEALTHY self.data["health_status"] = status self.data["health_status_reason"] = reason def _poll_cluster(self): """Get status from Cluster. This has most status info available as it bubbles up. """ namespace = driver_utils.cluster_namespace(self.cluster) resource_name = driver_utils.get_k8s_resource_name(self.cluster, None) resource_cluster = self._k8s_client.get_capi_cluster( resource_name, namespace ) if not resource_cluster: return "Cluster resource not found." cluster_conditions = [ c.get("type") for c in resource_cluster.get("status", {}).get("conditions", {}) if c.get("status") != "True" ] if cluster_conditions: return f"Waiting on {cluster_conditions}" return MONITOR_STATE_READY def _poll_infra(self): """Analyse OpenStackCluster resource This represents the CAPO Infrastructure component. """ namespace = driver_utils.cluster_namespace(self.cluster) resource_name = driver_utils.get_k8s_resource_name(self.cluster, None) resource_capo = self._k8s_client.get_capi_openstackcluster( resource_name, namespace ) if not resource_capo: return "Infrastructure resource not found." capo_status = resource_capo.get("status", {}) capo_ready = capo_status.get("ready", False) capo_message = capo_status.get("failureMessage", "") capo_reason = capo_status.get("failureReason", None) if capo_reason: return f"{capo_reason}: {capo_message}" if not capo_ready: return "Infrastructure not ready." return MONITOR_STATE_READY def _poll_controlplane(self): """Analyse KubeAdmControlPlane resource This CAPI controller manages the control plane machines """ namespace = driver_utils.cluster_namespace(self.cluster) resource_name = driver_utils.get_k8s_resource_name( self.cluster, "control-plane" ) resource_kcp = self._k8s_client.get_k8s_control_plane( resource_name, namespace ) if not resource_kcp: return "Control plane resource not found." conditions = [ c.get("type") for c in resource_kcp.get("status", {}).get("conditions", {}) if c.get("status") != "True" ] if conditions: return f"Waiting on {conditions}" return MONITOR_STATE_READY def _poll_nodegroups(self): """Analyse MachineDeployment resources There is one machinedeployment per nodegroup (which holds information on machineset and machine resources) """ namespace = driver_utils.cluster_namespace(self.cluster) nodegroup_reasons = [] for nodegroup in self.cluster.nodegroups: if nodegroup.role == "master": continue resource_name = driver_utils.get_k8s_resource_name( self.cluster, nodegroup.name ) resource_md = self._k8s_client.get_machine_deployment( resource_name, namespace ) if not resource_md: nodegroup_reasons.append( f"{nodegroup.name} resource not found." ) continue conditions = [ c.get("type") for c in resource_md.get("status", {}).get("conditions", {}) if c.get("status") != "True" ] if conditions: nodegroup_reasons.append( f"{nodegroup.name} waiting on {conditions}" ) if nodegroup_reasons: return ",".join(nodegroup_reasons) return MONITOR_STATE_READY def _is_cluster_accessible(self): # This monitor does not directly access Kubernetes clusters # as it obtains all information from CAPI Management cluster. # So all clusters are accessible, if CAPI is functional. return True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/conf.py0000664000175000017500000002645015212012064020407 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy # of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from oslo_config import cfg capi_helm_group = cfg.OptGroup( name="capi_helm", title="Helm Cluster API Driver configuration" ) capi_helm_opts = [ cfg.StrOpt( "kubeconfig_file", default="", help=( "Path to a kubeconfig file for a management cluster," "for use in the Cluster API driver. " "Defaults to the environment variable KUBECONFIG, " "or if not defined ~/.kube/config" ), ), cfg.StrOpt( "namespace_prefix", default="magnum", help=( "Resources for each openstack cluster are created in a " "separate namespace within the CAPI Management cluster " "specified by the configuration: [capi_helm]/kubeconfig_file " "You should modify this prefix when two magnum deployments " "want to share a single CAPI management cluster." ), ), cfg.StrOpt( "helm_chart_repo", default="https://azimuth-cloud.github.io/capi-helm-charts", help=( "Reference to the helm chart repository for " "the cluster API driver. " "Note that if helm_chart_name starts with oci:// " "you will want this to set this to the empty string." ), ), cfg.StrOpt( "helm_chart_name", default="openstack-cluster", help=( "Name of the helm chart to use from the repo specified " "by the config: capi_driver.helm_chart_repo" ), ), cfg.StrOpt( "default_helm_chart_version", default="0.10.1", help=( "Version of the helm chart specified " "by the config: capi_driver.helm_chart_repo " "and capi_driver.helm_chart_name. " "A cluster label can override this." ), ), cfg.IntOpt( "minimum_flavor_ram", default=2048, help=("Minimum RAM for flavor used to " "create a Kubernetes node."), ), cfg.IntOpt( "minimum_flavor_vcpus", default=2, help=("Minimum VCPUS for flavor used to " "create a Kubernetes node."), ), cfg.StrOpt( "csi_cinder_default_volume_type", help=("Default StorageClass volume type for persistent volumes."), ), cfg.ListOpt( "csi_cinder_allowed_topologies", default=[], help=( "Select the Nodes where the application " "Pods may be scheduled based on Node labels." ), ), cfg.StrOpt( "app_cred_interface_type", default="public", help=( "The value to use in the interface field of " "generated application credentials." ), ), cfg.StrOpt( "api_resources", default={}, help=( """ Dictionary of cluster api resources to modify api_version and plural names in string format. Example:: '{ "K8sControlPlane": { "api_version": "controlplane.cluster.x-k8s.io/v1beta1", "plural_name": "kubeadmcontrolplanes" }, "OpenstackCluster": { "api_version": "infrastructure.cluster.x-k8s.io/v1beta1", }, }' """ ), ), cfg.ListOpt( "k8s_control_plane_resource_conditions", default=[ "MachinesReady", "Ready", "EtcdClusterHealthy", "ControlPlaneComponentsHealthy", ], help=( "List of conditions to check for kubernetes control plane " "resource to consider as ready." ), ), ] capi_helm_cluster_labels_group = cfg.OptGroup( name="capi_helm_cluster_labels", title="Default values for cluster labels", ) capi_helm_cluster_labels_opts = [ # etcd cfg.IntOpt( "etcd_blockdevice_size", default=0, help=( "Size of the etcd block device in GB. " "0 means use the default ephemeral disk." ), ), cfg.StrOpt( "etcd_blockdevice_type", default="volume", choices=["volume", "local"], help=( "Storage type for the etcd block device. " "'volume' uses a Cinder volume; 'local' uses an ephemeral disk." ), ), cfg.StrOpt( "etcd_blockdevice_volume_type", default="", help="Cinder volume type for the etcd block device.", ), cfg.StrOpt( "etcd_blockdevice_volume_az", default="", help="Availability zone for the etcd Cinder volume.", ), cfg.IntOpt( "etcd_volume_size", default=0, deprecated_opts=[ cfg.DeprecatedOpt("etcd_volume_size", group="capi_helm") ], help="Deprecated: use etcd_blockdevice_size instead.", ), cfg.StrOpt( "etcd_volume_type", default="", deprecated_opts=[ cfg.DeprecatedOpt("etcd_volume_type", group="capi_helm") ], help="Deprecated: use etcd_blockdevice_volume_type instead.", ), # addons cfg.BoolOpt( "monitoring_enabled", default=False, help="Enable the monitoring addon on the cluster.", ), cfg.BoolOpt( "kube_dashboard_enabled", default=True, help="Enable the Kubernetes Dashboard addon.", ), cfg.BoolOpt( "auto_healing_enabled", default=True, help="Enable auto-healing for cluster nodes.", ), cfg.BoolOpt( "auto_scaling_enabled", default=False, help="Enable the cluster autoscaler.", ), cfg.IntOpt( "min_node_count", default=None, help=( "Minimum node count for autoscaling. " "Defaults to the nodegroup node_count when unset." ), ), cfg.IntOpt( "max_node_count", default=None, help=( "Maximum node count for autoscaling. " "Defaults to the nodegroup node_count when unset." ), ), # auth cfg.BoolOpt( "keystone_auth_enabled", default=False, help="Enable the Keystone authentication webhook.", ), # CSI Cinder — moved from [capi_helm], old location still accepted cfg.StrOpt( "csi_cinder_availability_zone", default="nova", deprecated_opts=[ cfg.DeprecatedOpt( "csi_cinder_availability_zone", group="capi_helm" ) ], help="Default availability zone for Cinder volumes.", ), cfg.StrOpt( "csi_cinder_reclaim_policy", default="Retain", choices=["Retain", "Delete"], deprecated_opts=[ cfg.DeprecatedOpt("csi_cinder_reclaim_policy", group="capi_helm") ], help=( "Reclaim policy for dynamically provisioned persistent volumes. " "Can be 'Retain' or 'Delete'." ), ), cfg.StrOpt( "csi_cinder_volume_binding_mode", default="WaitForFirstConsumer", choices=["WaitForFirstConsumer", "Immediate"], deprecated_opts=[ cfg.DeprecatedOpt( "csi_cinder_volume_binding_mode", group="capi_helm" ) ], help=( "Controls when volume binding and dynamic provisioning occur. " "Can be 'WaitForFirstConsumer' or 'Immediate'." ), ), cfg.StrOpt( "csi_cinder_fstype", default="ext4", deprecated_opts=[ cfg.DeprecatedOpt("csi_cinder_fstype", group="capi_helm") ], help="Filesystem type for persistent volumes.", ), cfg.BoolOpt( "csi_cinder_allow_volume_expansion", default=True, deprecated_opts=[ cfg.DeprecatedOpt( "csi_cinder_allow_volume_expansion", group="capi_helm" ) ], help="Allow users to resize persistent volumes by editing the PVC.", ), # Octavia load balancer cfg.StrOpt( "octavia_provider", default="amphora", help="Octavia load balancer provider (e.g. 'amphora' or 'ovn').", ), cfg.StrOpt( "octavia_lb_algorithm", default="", help=( "Load balancer algorithm. " "When unset, defaults to SOURCE_IP_PORT for the ovn provider " "and ROUND_ROBIN for all others." ), ), cfg.BoolOpt( "octavia_lb_healthcheck", default=True, help="Enable health checks on the load balancer.", ), # networking cfg.BoolOpt( "master_lb_floating_ip_enabled", default=True, help="Associate a floating IP with the API load balancer.", ), cfg.StrOpt( "fixed_subnet_cidr", default="10.0.0.0/24", help="CIDR for the node network when no fixed_subnet is specified.", ), cfg.StrOpt( "api_master_lb_allowed_cidrs", default="", help=( "Comma-separated list of CIDRs allowed to reach the API load " "balancer. Empty means all CIDRs are allowed." ), ), cfg.StrOpt( "extra_network_names", default="", deprecated_opts=[ cfg.DeprecatedOpt( "extra_network_name", group="capi_helm_cluster_labels" ) ], help=( "Space separated list of names of additional Neutron networks " "to attach to each node." ), ), # boot volume cfg.StrOpt( "boot_volume_type", default=None, help=( "Root volume type. " "Falls back to the cinder config option " "default_boot_volume_type when unset." ), ), cfg.IntOpt( "boot_volume_size", default=None, help=( "Root volume size in GB. " "Falls back to the cinder config option " "default_boot_volume_size when unset." ), ), # chart version (template-only — cannot be overridden per cluster) cfg.StrOpt( "capi_helm_chart_version", default="", help=( "Helm chart version to use. Can only be set via the cluster " "template label, not overridden per cluster. Falls back to " "capi_helm.default_helm_chart_version when unset." ), ), ] CONF = cfg.CONF CONF.register_group(capi_helm_group) CONF.register_opts(capi_helm_opts, group=capi_helm_group) CONF.register_group(capi_helm_cluster_labels_group) CONF.register_opts( capi_helm_cluster_labels_opts, group=capi_helm_cluster_labels_group ) def list_capi_opts(): return [(capi_helm_group, [o]) for o in capi_helm_opts] def list_capi_cluster_label_opts(): return [ (capi_helm_cluster_labels_group, [o]) for o in capi_helm_cluster_labels_opts ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/driver.py0000664000175000017500000016012515212012064020753 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import enum import re import requests import yaml from magnum.api import utils as api_utils from magnum.common import clients from magnum.common import exception from magnum.common import neutron from magnum.common import short_id from magnum.drivers.common import driver from magnum.objects import fields from oslo_log import log as logging from oslo_utils import strutils from oslo_utils import uuidutils from magnum_capi_helm.common import app_creds from magnum_capi_helm.common import ca_certificates from magnum_capi_helm.common import capi_monitor from magnum_capi_helm import conf from magnum_capi_helm import driver_utils from magnum_capi_helm import helm from magnum_capi_helm import kubernetes LOG = logging.getLogger(__name__) CONF = conf.CONF NODE_GROUP_ROLE_CONTROLLER = "master" def _gather_label_names(): regular_label_names = set( opt.name for opt in conf.capi_helm_cluster_labels_opts ) deprecated_label_names = [] for opt in conf.capi_helm_cluster_labels_opts: for dep_opt in opt.deprecated_opts: deprecated_label_names.append(dep_opt.name) return frozenset(regular_label_names.union(set(deprecated_label_names))) _REGISTERED_LABEL_NAMES = _gather_label_names() class NodeGroupState(enum.Enum): NOT_PRESENT = 1 PENDING = 2 READY = 3 FAILED = 4 class Driver(driver.Driver): def __init__(self): self._helm_client = helm.Client() self.__k8s_client = None @property def _k8s_client(self): if not self.__k8s_client: self.__k8s_client = kubernetes.Client.load() return self.__k8s_client @property def provides(self): return [ { "server_type": "vm", # NOTE(johngarbutt) we don't depend on a specific OS, # we depend on kubeadm images with cloud-init "os": "ubuntu", "coe": "kubernetes", }, { "server_type": "vm", "os": "flatcar", "coe": "kubernetes", }, ] def validate_master_resize(self, node_count): # This driver supports resizing to the same values # as initial create, so re-use the Base class validation. return self.validate_master_size(node_count) def _update_control_plane_nodegroup_status(self, cluster, nodegroup): # The status of the master nodegroup is determined by the Cluster API # control plane object kcp = self._k8s_client.get_k8s_control_plane( driver_utils.get_k8s_resource_name(cluster, "control-plane"), driver_utils.cluster_namespace(cluster), ) ng_state = NodeGroupState.NOT_PRESENT if kcp: ng_state = NodeGroupState.PENDING kcp_spec = kcp.get("spec", {}) if kcp else {} kcp_status = kcp.get("status", {}) if kcp else {} # The control plane object is what controls the Kubernetes version # If it is known, report it kube_version = kcp_status.get("version", kcp_spec.get("version")) if cluster.coe_version != kube_version: cluster.coe_version = kube_version cluster.save() kcp_true_conditions = { cond["type"] for cond in kcp_status.get("conditions", []) if cond["status"] == "True" } kcp_ready = all( cond in kcp_true_conditions for cond in CONF.capi_helm.k8s_control_plane_resource_conditions ) target_replicas = kcp_spec.get("replicas") current_replicas = kcp_status.get("replicas") updated_replicas = kcp_status.get("updatedReplicas") ready_replicas = kcp_status.get("readyReplicas") if ( kcp_ready and target_replicas == current_replicas and current_replicas == updated_replicas and updated_replicas == ready_replicas ): ng_state = NodeGroupState.READY # TODO(mkjpryor) Work out a way to determine FAILED state return self._update_nodegroup_status(cluster, nodegroup, ng_state) def _update_worker_nodegroup_status(self, cluster, nodegroup): # The status of a worker nodegroup is determined by the corresponding # Cluster API machine deployment md = self._k8s_client.get_machine_deployment( driver_utils.get_k8s_resource_name(cluster, nodegroup.name), driver_utils.cluster_namespace(cluster), ) ng_state = NodeGroupState.NOT_PRESENT if md: ng_state = NodeGroupState.PENDING # When a machine deployment is deleted, it disappears straight # away even when there are still machines belonging to it that # are deleting # In that case, we want to keep the nodegroup as DELETE_IN_PROGRESS # until all the machines for the node group are gone if ( not md and nodegroup.status.startswith("DELETE_") and self._nodegroup_machines_exist(cluster, nodegroup) ): LOG.debug( f"Node group {nodegroup.name} " f"for cluster {cluster.uuid} " "machine deployment gone, but machines still found." ) ng_state = NodeGroupState.PENDING md_status = md.get("status", {}) if md else {} md_phase = md_status.get("phase") if md_phase: if md_phase == "Running": ng_state = NodeGroupState.READY elif md_phase in {"Failed", "Unknown"}: ng_state = NodeGroupState.FAILED return self._update_nodegroup_status(cluster, nodegroup, ng_state) def _update_nodegroup_status(self, cluster, nodegroup, ng_state): # For delete we are waiting for not present if nodegroup.status.startswith("DELETE_"): if ng_state == NodeGroupState.NOT_PRESENT: if not nodegroup.is_default: # Conductor will delete default nodegroups # when cluster is deleted, but non default # node groups should be deleted here. nodegroup.destroy() LOG.debug( f"Node group deleted: {nodegroup.name} " f"for cluster {cluster.uuid} " f"which is_default: {nodegroup.is_default}" ) # signal the node group has been deleted return None LOG.debug( f"Node group not yet delete: {nodegroup.name} " f"for cluster {cluster.uuid}" ) return nodegroup is_update_operation = nodegroup.status.startswith("UPDATE_") is_create_operation = nodegroup.status.startswith("CREATE_") if not is_update_operation and not is_create_operation: LOG.warning( f"Node group: {nodegroup.name} in unexpected " f"state: {nodegroup.status} in cluster {cluster.uuid}" ) elif ng_state == NodeGroupState.READY: nodegroup.status = ( fields.ClusterStatus.UPDATE_COMPLETE if is_update_operation else fields.ClusterStatus.CREATE_COMPLETE ) LOG.debug( f"Node group ready: {nodegroup.name} " f"in cluster {cluster.uuid}" ) nodegroup.save() elif ng_state == NodeGroupState.FAILED: nodegroup.status = ( fields.ClusterStatus.UPDATE_FAILED if is_update_operation else fields.ClusterStatus.CREATE_FAILED ) LOG.debug( f"Node group failed: {nodegroup.name} " f"in cluster {cluster.uuid}" ) nodegroup.save() elif ng_state == NodeGroupState.NOT_PRESENT: LOG.debug( f"Node group not yet found: {nodegroup.name} " f"state:{nodegroup.status} in cluster {cluster.uuid}" ) else: LOG.debug( f"Node group still pending: {nodegroup.name} " f"state:{nodegroup.status} in cluster {cluster.uuid}" ) return nodegroup def _nodegroup_machines_exist(self, cluster, nodegroup): cluster_name = driver_utils.chart_release_name(cluster) nodegroup_name = driver_utils.sanitized_name(nodegroup.name) machines = self._k8s_client.get_all_machines_by_label( { "capi.stackhpc.com/cluster": cluster_name, "capi.stackhpc.com/component": "worker", "capi.stackhpc.com/node-group": nodegroup_name, }, driver_utils.cluster_namespace(cluster), ) return bool(machines) def _update_cluster_api_address(self, cluster, capi_cluster): # As soon as we know the API address, we should set it # This means users can access the API even if the create is # not complete, which could be useful for debugging failures, # e.g. with addons if not capi_cluster: # skip update if cluster not yet created return if cluster.status not in [ fields.ClusterStatus.CREATE_IN_PROGRESS, fields.ClusterStatus.UPDATE_IN_PROGRESS, ]: # only update api-address when updating or creating return api_endpoint = capi_cluster["spec"].get("controlPlaneEndpoint") if api_endpoint: api_address = ( f"https://{api_endpoint['host']}:{api_endpoint['port']}" ) if cluster.api_address != api_address: cluster.api_address = api_address cluster.save() LOG.debug(f"Found api_address for {cluster.uuid}") def _update_status_updating(self, cluster, capi_cluster): # If the cluster is not yet ready then the create/update # is still in progress true_conditions = { cond["type"] for cond in capi_cluster.get("status", {}).get("conditions", []) if cond["status"] == "True" } for cond in ("InfrastructureReady", "ControlPlaneReady", "Ready"): if cond not in true_conditions: return is_update_operation = cluster.status.startswith("UPDATE_") # Check the status of the addons addons = self._k8s_client.get_addons_by_label( { "addons.stackhpc.com/cluster": driver_utils.chart_release_name( cluster ) }, driver_utils.cluster_namespace(cluster), ) for addon in addons: addon_phase = addon.get("status", {}).get("phase") if addon_phase and addon_phase in {"Failed", "Unknown"}: # If the addon is failed, mark the cluster as failed cluster.status = ( fields.ClusterStatus.UPDATE_FAILED if is_update_operation else fields.ClusterStatus.CREATE_FAILED ) cluster.save() return elif addon_phase and addon_phase == "Deployed": # If the addon is deployed, move on to the next one continue else: # If there are any addons that are not deployed or failed, # wait for the next invocation to check again LOG.debug( f"addon {addon['metadata']['name']} not yet deployed " f"for {cluster.uuid}" ) return # If we get this far, the cluster has completed successfully cluster.status = ( fields.ClusterStatus.UPDATE_COMPLETE if is_update_operation else fields.ClusterStatus.CREATE_COMPLETE ) cluster.save() def _update_status_deleting(self, context, cluster): # Fetch the current app cred ID before we delete all secrets app_cred_id = self._get_app_cred_id(cluster) # Once the Cluster API cluster is gone, we need to clean up # the secrets we created self._k8s_client.delete_all_secrets_by_label( "magnum.openstack.org/cluster-uuid", cluster.uuid, driver_utils.cluster_namespace(cluster), ) cluster.status_reason = None # Clean up the app cred if app_cred_id: try: app_creds.delete_app_cred(cluster, app_cred_id) except app_creds.ApplicationCredentialError as e: # NOTE(northcottmt): We don't want this to be a failure # condition as it may prevent cleanup of broken clusters, # e.g. if cluster creation fails before the appcred is created # or cluster deletion fails after the appcred is deleted error_msg = ( "Failed to delete application credential for " f"cluster {cluster.uuid}: {e}" ) cluster.status_reason = error_msg LOG.warning(error_msg) cluster.status = fields.ClusterStatus.DELETE_COMPLETE cluster.save() def _get_capi_cluster(self, cluster): release_name = driver_utils.chart_release_name(cluster) if release_name: return self._k8s_client.get_capi_cluster( release_name, driver_utils.cluster_namespace(cluster), ) def _update_all_nodegroups_status(self, cluster): """Returns True if any node group still in progress.""" nodegroups = [] for nodegroup in cluster.nodegroups: if nodegroup.role == NODE_GROUP_ROLE_CONTROLLER: updated_nodegroup = ( self._update_control_plane_nodegroup_status( cluster, nodegroup ) ) else: updated_nodegroup = self._update_worker_nodegroup_status( cluster, nodegroup ) if updated_nodegroup: nodegroups.append(updated_nodegroup) # Return True if any are still in progress for nodegroup in nodegroups: if nodegroup.status.endswith("_IN_PROGRESS"): return True return False def update_cluster_status(self, context, cluster): # NOTE(mkjpryor) # Because Kubernetes operators are built around reconciliation loops, # Cluster API clusters don't really go into an error state # Hence we only currently handle transitioning from IN_PROGRESS # states to COMPLETE # TODO(mkjpryor) Add a timeout for create/update/delete capi_cluster = self._get_capi_cluster(cluster) if capi_cluster: # Update the cluster API address if it is known # so users can get their coe credentials self._update_cluster_api_address(cluster, capi_cluster) # Update the nodegroups first # to ensure API never returns an inconsistent state nodegroups_in_progress = self._update_all_nodegroups_status( cluster ) if cluster.status in { fields.ClusterStatus.CREATE_IN_PROGRESS, fields.ClusterStatus.UPDATE_IN_PROGRESS, }: LOG.debug("Checking on an update for %s", cluster.uuid) # If the cluster does not exist yet, # create is still in progress if not capi_cluster: LOG.debug(f"capi_cluster not yet created for {cluster.uuid}") return if nodegroups_in_progress: LOG.debug(f"Node groups are not all ready for {cluster.uuid}") return self._update_status_updating(cluster, capi_cluster) elif cluster.status == fields.ClusterStatus.DELETE_IN_PROGRESS: LOG.debug("Checking on a delete for %s", cluster.uuid) # If the Cluster API cluster still exists, # the delete is still in progress if capi_cluster: LOG.debug(f"capi_cluster still found for {cluster.uuid}") return self._update_status_deleting(context, cluster) def get_monitor(self, context, cluster): return capi_monitor.CAPIMonitor(context, cluster) def _k8s_resource_labels(self, cluster): # TODO(johngarbutt) need to check these are safe labels name = driver_utils.chart_release_name(cluster) return { "magnum.openstack.org/project-id": cluster.project_id[:63], "magnum.openstack.org/user-id": cluster.user_id[:63], "magnum.openstack.org/cluster-uuid": cluster.uuid[:63], "cluster.x-k8s.io/cluster-name": name, } def _create_appcred_secret(self, context, cluster): app_cred = app_creds.create_app_cred(context, cluster) string_data = app_creds.get_app_cred_string_data(context, app_cred) name = self._get_app_cred_secret_name(cluster) self._k8s_client.apply_secret( name, { "metadata": {"labels": self._k8s_resource_labels(cluster)}, "stringData": string_data, }, driver_utils.cluster_namespace(cluster), ) return app_cred def _ensure_certificate_secrets(self, context, cluster): # Magnum creates CA certs for each of the Kubernetes components that # must be trusted by the cluster # In particular, this is required for "openstack coe cluster config" # to work, as that doesn't communicate with the driver and instead # relies on the correct CA being trusted by the cluster # Cluster API looks for specific named secrets for each of the CAs, # and generates them if they don't exist, so we create them here # with the correct certificates in for ( name, data, ) in ca_certificates.get_certificate_string_data( context, cluster ).items(): self._k8s_client.apply_secret( driver_utils.get_k8s_resource_name(cluster, name), { "metadata": {"labels": self._k8s_resource_labels(cluster)}, "type": "cluster.x-k8s.io/secret", "stringData": data, }, driver_utils.cluster_namespace(cluster), ) def _label(self, cluster, key, default): if key not in _REGISTERED_LABEL_NAMES: raise ValueError( f"Cluster label '{key}' is not registered in " "capi_helm_cluster_labels. Add it to conf.py first." ) all_labels = helm.mergeconcat( cluster.cluster_template.labels, cluster.labels ) if not all_labels: return default raw = all_labels.get(key, default) # NOTE(johngarbutt): filtering untrusted user input return re.sub(r"[^a-zA-Z0-9\.\-\/ _]+", "", raw) def _get_label_bool(self, cluster, label, default): cluster_label = self._label(cluster, label, "") return strutils.bool_from_string(cluster_label, default=default) def _get_label_int(self, cluster, label, default): cluster_label = self._label(cluster, label, "") if not cluster_label: return default try: return int(cluster_label) except ValueError: return default def _get_label_csv(self, cluster, label, default): """Return a cluster label as a filtered list split on commas.""" if label not in _REGISTERED_LABEL_NAMES: raise ValueError( f"Cluster label '{label}' is not registered in " "capi_helm_cluster_labels. Add it to conf.py first." ) all_labels = helm.mergeconcat( cluster.cluster_template.labels, cluster.labels ) raw = (all_labels or {}).get(label, "") or default if not raw: return [] return [ re.sub(r"[^a-zA-Z0-9\.\-\/\:]+", "", entry.strip()) for entry in str(raw).split(",") if entry.strip() ] def _get_chart_version(self, cluster): version = cluster.cluster_template.labels.get( "capi_helm_chart_version", CONF.capi_helm.default_helm_chart_version, ) # NOTE(johngarbutt): filtering untrusted user input return re.sub(r"[^a-z0-9\.\-\+]+", "", version) def _get_kube_version(self, image): # The image should have a property containing the Kubernetes version. # image.get() uses dict.get() which bypasses the overridden __getitem__ # in openstacksdk Resource objects (data lives in _body, not the dict). # Unknown properties are packed into image.properties by openstacksdk. kube_version = image.get("kube_version") or getattr( image, "properties", {} ).get("kube_version") if not kube_version: raise exception.MagnumException( message=f"Image {image.id} does not " "have a kube_version property." ) raw = kube_version.lstrip("v") # TODO(johngarbutt) more validation required? return re.sub(r"[^0-9\.]+", "", raw) def _get_os_distro(self, image): # image.get() uses dict.get() which bypasses the overridden __getitem__ # in openstacksdk Resource objects. os_distro is a declared Body field # in openstacksdk Image, so getattr() reaches it via the descriptor. os_distro = image.get("os_distro") or getattr(image, "os_distro", None) if not os_distro: raise exception.MagnumException( message=f"Image {image.id} does not " "have an os_distro property." ) return re.sub(r"[^a-zA-Z0-9\.\-\/ ]+", "", os_distro) def _get_image_details(self, context, image_identifier): glance = clients.OpenStackClients(context).glance() if hasattr(glance.images, "get"): # glanceclient image = api_utils.get_openstack_resource( glance.images, image_identifier, "images" ) else: # openstacksdk image = glance.find_image(image_identifier) return ( image.id, self._get_kube_version(image), self._get_os_distro(image), ) def _get_app_cred_secret_name(self, cluster): return driver_utils.get_k8s_resource_name(cluster, "cloud-credentials") def _get_app_cred_id(self, cluster): # determine the existing application credential secret secret_name = self._get_app_cred_secret_name(cluster) if not secret_name: return None secret_namespace = driver_utils.cluster_namespace(cluster) # fetch the existing secret and unpack the application credential ID try: clouds_dict_str = self._k8s_client.get_secret_value( secret_name, secret_namespace, "clouds.yaml" ) if clouds_dict_str: clouds_dict = yaml.safe_load(clouds_dict_str) auth_dict = clouds_dict["clouds"]["openstack"]["auth"] return auth_dict["application_credential_id"] else: msg = "secret containing credential does not exist" except (KeyError, yaml.YAMLError) as e: msg = str(e) LOG.error( "Failed to fetch application credential for cluster " f"{cluster.uuid}: {msg}" ) def _get_etcd_config(self, cluster): lconf = CONF.capi_helm_cluster_labels # Support new-style and legacy labels for volume size and type, with # new-style labels taking precedence etcd_size = self._get_label_int( cluster, "etcd_blockdevice_size", self._get_label_int( cluster, "etcd_volume_size", lconf.etcd_blockdevice_size, ), ) if etcd_size > 0: etcd_block_device = {"size": etcd_size} # The block device type can be either local or volume etcd_bd_type = self._label( cluster, "etcd_blockdevice_type", lconf.etcd_blockdevice_type, ) if etcd_bd_type == "local": etcd_block_device["type"] = "Local" else: etcd_block_device["type"] = "Volume" etcd_volume_type = self._label( cluster, "etcd_blockdevice_volume_type", self._label( cluster, "etcd_volume_type", lconf.etcd_blockdevice_volume_type, ), ) if etcd_volume_type: etcd_block_device["volumeType"] = etcd_volume_type etcd_volume_az = self._label( cluster, "etcd_blockdevice_volume_az", lconf.etcd_blockdevice_volume_az, ) if etcd_volume_az: etcd_block_device["availabilityZone"] = etcd_volume_az return {"blockDevice": etcd_block_device} else: return {} def _get_dns_nameservers(self, cluster): dns_nameserver = cluster.cluster_template.dns_nameserver if dns_nameserver: return strutils.split_by_commas(dns_nameserver) else: return None def _get_monitoring_enabled(self, cluster): return self._get_label_bool( cluster, "monitoring_enabled", CONF.capi_helm_cluster_labels.monitoring_enabled, ) def _get_kube_dash_enabled(self, cluster): return self._get_label_bool( cluster, "kube_dashboard_enabled", CONF.capi_helm_cluster_labels.kube_dashboard_enabled, ) def _get_autoheal_enabled(self, cluster): return self._get_label_bool( cluster, "auto_healing_enabled", CONF.capi_helm_cluster_labels.auto_healing_enabled, ) def _get_autoscale_enabled(self, cluster): return self._get_label_bool( cluster, "auto_scaling_enabled", CONF.capi_helm_cluster_labels.auto_scaling_enabled, ) def _get_autoscale_values(self, cluster, nodegroup): auto_scale = self._get_autoscale_enabled(cluster) min_nodes, max_nodes = self._validate_allowed_node_counts( cluster, nodegroup ) auto_scale_args = {} if auto_scale and min_nodes != max_nodes: auto_scale_args["autoscale"] = "true" auto_scale_args["machineCountMin"] = min_nodes auto_scale_args["machineCountMax"] = max_nodes return auto_scale_args def _get_k8s_keystone_auth_enabled(self, cluster): return self._get_label_bool( cluster, "keystone_auth_enabled", CONF.capi_helm_cluster_labels.keystone_auth_enabled, ) def _get_fixed_network_id(self, context, cluster): network = cluster.fixed_network if not network: return if network and uuidutils.is_uuid_like(network): return network else: return neutron.get_network( context, network, source="name", target="id", external=False ) def _validate_allowed_flavor(self, context, requested_flavor): # Compare requested flavor with allowed for Kubernetes node nova = clients.OpenStackClients(context).nova() if hasattr(nova.flavors, "list"): # novaclient flavors = nova.flavors.list( min_ram=CONF.capi_helm.minimum_flavor_ram ) else: # openstacksdk compute proxy flavors = nova.flavors(min_ram=CONF.capi_helm.minimum_flavor_ram) for flavor in flavors: vcpus = flavor.vcpus LOG.debug( f"Checking if {requested_flavor} matches " f"{flavor.id} or {flavor.name}" ) if requested_flavor in [flavor.id, flavor.name]: if vcpus < CONF.capi_helm.minimum_flavor_vcpus: raise exception.MagnumException( message=f"Flavor {requested_flavor} does not " f"have enough CPU to run Kubernetes. " f"Minimum {CONF.capi_helm.minimum_flavor_vcpus} " "vcpus required." ) return raise exception.MagnumException( message=f"Flavor {requested_flavor} does not " f"have enough RAM to run Kubernetes. " f"Minimum {CONF.capi_helm.minimum_flavor_ram} MB required." ) def _is_default_worker_nodegroup(self, cluster, nodegroup): return cluster.default_ng_worker.id == nodegroup.id def _get_node_counts(self, cluster, nodegroup): # NOTE(scott): In CAPI MachineDeployment resources created by the # capi-helm-charts, the `replicas` field is omitted when autoscaling # is enabled (since we're relinquishing control over node count to # the autoscaler) so if a user creates a nodegroup where only # node_count is provided and min/max are not, we need to be careful # about setting the default min/max values to equal node_count. min_nodes = nodegroup.node_count max_nodes = nodegroup.node_count if nodegroup.min_node_count is not None: min_nodes = nodegroup.min_node_count if nodegroup.max_node_count is not None: max_nodes = nodegroup.max_node_count # If min/max node counts are not defined on the default # worker group then fall back to equivalent cluster labels if self._is_default_worker_nodegroup(cluster, nodegroup): # NOTE(scott): Magnum seems to set min_node_count = 1 # on the default group so treat this as if it were None if nodegroup.min_node_count == 1: min_nodes = nodegroup.node_count # We still want to be able to override the default node # group values with labels for consistent behaviour with # Magnum Heat driver. conf_min = CONF.capi_helm_cluster_labels.min_node_count conf_max = CONF.capi_helm_cluster_labels.max_node_count min_nodes = self._get_label_int( cluster, "min_node_count", conf_min if conf_min is not None else min_nodes, ) max_nodes = self._get_label_int( cluster, "max_node_count", conf_max if conf_max is not None else max_nodes, ) return min_nodes, max_nodes def _validate_allowed_node_counts(self, cluster, nodegroup): min_nodes, max_nodes = self._get_node_counts(cluster, nodegroup) LOG.debug( f"Checking if node group {nodegroup.name} has valid " f"node count parameters (count, min, max) = " f"{(nodegroup.node_count, min_nodes, max_nodes)}" ) if min_nodes is not None: # ClusterAPI Provider OpenStack (CAPO) # doesn't support scale to zero yet. if min_nodes < 1: raise exception.NodeGroupInvalidInput( message="Min node count must be greater than " "or equal to 1 for all node groups." ) if min_nodes > nodegroup.node_count: raise exception.NodeGroupInvalidInput( message="Min node count must be less than " "or equal to current node count" ) if max_nodes is not None and max_nodes < min_nodes: raise exception.NodeGroupInvalidInput( message="Max node count must be greater than " "or equal to min node count" ) return min_nodes, max_nodes def _get_csi_cinder_availability_zone(self, cluster): return self._label( cluster, "csi_cinder_availability_zone", CONF.capi_helm_cluster_labels.csi_cinder_availability_zone, ) def _get_csi_cinder_reclaim_policy(self, cluster): return self._label( cluster, "csi_cinder_reclaim_policy", CONF.capi_helm_cluster_labels.csi_cinder_reclaim_policy, ) def _get_csi_cinder_volume_binding_mode(self, cluster): return self._label( cluster, "csi_cinder_volume_binding_mode", CONF.capi_helm_cluster_labels.csi_cinder_volume_binding_mode, ) def _get_csi_cinder_fstype(self, cluster): return self._label( cluster, "csi_cinder_fstype", CONF.capi_helm_cluster_labels.csi_cinder_fstype, ) def _get_csi_cinder_allow_volume_expansion(self, cluster): return self._get_label_bool( cluster, "csi_cinder_allow_volume_expansion", CONF.capi_helm_cluster_labels.csi_cinder_allow_volume_expansion, ) def _get_octavia_provider(self, cluster): return self._label( cluster, "octavia_provider", CONF.capi_helm_cluster_labels.octavia_provider, ) def _get_cni_type(self, cluster): network_driver = cluster.cluster_template.network_driver if not network_driver: return None # NOTE: filtering untrusted user input return re.sub(r"[^a-zA-Z0-9\.\-_]+", "", network_driver) def _get_octavia_lb_algorithm(self, cluster): provider = self._get_octavia_provider(cluster) conf_default = CONF.capi_helm_cluster_labels.octavia_lb_algorithm dynamic_default = ( "SOURCE_IP_PORT" if provider.lower() == "ovn" else "ROUND_ROBIN" ) return self._label( cluster, "octavia_lb_algorithm", conf_default or dynamic_default, ) def _get_allowed_cidrs(self, cluster): cidrs = self._get_label_csv( cluster, "api_master_lb_allowed_cidrs", CONF.capi_helm_cluster_labels.api_master_lb_allowed_cidrs, ) LOG.debug(f"CIDR list {cidrs}") return cidrs or False def _storageclass_definitions(self, context, cluster): """Query cinder API to retrieve list of available volume types. @return dict(dict,list(dict)) containing storage classes """ LOG.debug("Retrieve volume types from cinder for StorageClasses.") availability_zone = self._get_csi_cinder_availability_zone(cluster) c_client = clients.OpenStackClients(context).cinder() if hasattr(c_client, "volume_types"): # cinderclient volume_types = [i.name for i in c_client.volume_types.list()] else: # openstacksdk block storage proxy exposes types(), # not volume_types volume_types = [i.name for i in c_client.types()] # Use the default volume type if defined. Otherwise use the first # type returned by cinder. default_volume_type = CONF.capi_helm.csi_cinder_default_volume_type LOG.debug( f"Default volume type: {default_volume_type}" f" Volume types: {volume_types}" ) if not default_volume_type: default_volume_type = volume_types[0] LOG.warning( f"Default volume type not defined." f" Using {default_volume_type}." ) elif default_volume_type not in volume_types: # If default does not exist throw an error. raise exception.MagnumException( message=f"{default_volume_type} is not a" " valid Cinder volume type." ) default_storage_class = {} additional_storage_classes = [] allow_expansion = self._get_csi_cinder_allow_volume_expansion(cluster) reclaim_policy = self._get_csi_cinder_reclaim_policy(cluster) binding_mode = self._get_csi_cinder_volume_binding_mode(cluster) allowed_topologies = CONF.capi_helm.csi_cinder_allowed_topologies fstype = self._get_csi_cinder_fstype(cluster) for volume_type in volume_types: storage_class = { "name": driver_utils.sanitized_name(volume_type), "reclaimPolicy": reclaim_policy, "allowVolumeExpansion": allow_expansion, "volumeBindingMode": binding_mode, "availabilityZone": availability_zone, "volumeType": volume_type, "allowedTopologies": allowed_topologies, "fstype": fstype, "enabled": True, } if volume_type == default_volume_type: default_storage_class = storage_class else: additional_storage_classes.append(storage_class) return dict( defaultStorageClass=default_storage_class, additionalStorageClasses=additional_storage_classes, ) def _process_node_groups(self, cluster, nodegroups): nodegroup_set = [] for ng in nodegroups: if ng.role != NODE_GROUP_ROLE_CONTROLLER: nodegroup_item = dict( name=driver_utils.sanitized_name(ng.name), machineFlavor=ng.flavor_id, machineCount=ng.node_count, ) if self._get_autoscale_enabled(cluster): values = self._get_autoscale_values(cluster, ng) nodegroup_item = helm.mergeconcat(nodegroup_item, values) nodegroup_set.append(nodegroup_item) return nodegroup_set def _update_helm_release(self, context, cluster, nodegroups=None): lconf = CONF.capi_helm_cluster_labels if nodegroups is None: nodegroups = cluster.nodegroups image_id, kube_version, os_distro = self._get_image_details( context, cluster.cluster_template.image_id ) network_id = self._get_fixed_network_id(context, cluster) subnet_id = neutron.get_fixed_subnet_id(context, cluster.fixed_subnet) values = { "kubernetesVersion": kube_version, "machineImageId": image_id, "machineSSHKeyName": cluster.keypair or None, "cloudCredentialsSecretName": self._get_app_cred_secret_name( cluster ), "etcd": self._get_etcd_config(cluster), "apiServer": { "associateFloatingIP": self._get_label_bool( cluster, "master_lb_floating_ip_enabled", lconf.master_lb_floating_ip_enabled, ), "enableLoadBalancer": True, "loadBalancerProvider": self._get_octavia_provider(cluster), }, "clusterNetworking": { "dnsNameservers": self._get_dns_nameservers(cluster), "externalNetworkId": neutron.get_external_network_id( context, cluster.cluster_template.external_network_id ), "internalNetwork": { "networkFilter": ( {"id": network_id} if network_id else None ), "subnetFilter": ({"id": subnet_id} if subnet_id else None), # This is only used if a fixed network is not specified "nodeCidr": self._label( cluster, "fixed_subnet_cidr", lconf.fixed_subnet_cidr, ), }, }, "osDistro": os_distro, "controlPlane": { "machineFlavor": cluster.master_flavor_id, "machineCount": cluster.master_count, "healthCheck": { "enabled": self._get_autoheal_enabled(cluster), }, }, "nodeGroupDefaults": { "healthCheck": { "enabled": self._get_autoheal_enabled(cluster), }, }, "nodeGroups": self._process_node_groups(cluster, nodegroups), "addons": { "openstack": { "csiCinder": self._storageclass_definitions( context, cluster ), "cloudConfig": { "LoadBalancer": { "lb-provider": self._get_octavia_provider(cluster), "lb-method": self._get_octavia_lb_algorithm( cluster ), "create-monitor": self._get_label_bool( cluster, "octavia_lb_healthcheck", lconf.octavia_lb_healthcheck, ), } }, }, "monitoring": { "enabled": self._get_monitoring_enabled(cluster) }, "kubernetesDashboard": { "enabled": self._get_kube_dash_enabled(cluster) }, # TODO(mkjpryor): can't enable ingress until code exists to # remove the load balancer "ingress": {"enabled": False}, }, } # Add boot disk details, if defined in config file. # Helm chart defaults to ephemeral disks, if unset. boot_volume_type = self._label( cluster, "boot_volume_type", lconf.boot_volume_type or CONF.cinder.default_boot_volume_type, ) if boot_volume_type: disk_type_details = { "controlPlane": { "machineRootVolume": { "volumeType": boot_volume_type, } }, "nodeGroupDefaults": { "machineRootVolume": { "volumeType": boot_volume_type, } }, } values = helm.mergeconcat(values, disk_type_details) conf_boot_size = lconf.boot_volume_size boot_volume_size_gb = self._get_label_int( cluster, "boot_volume_size", ( conf_boot_size if conf_boot_size is not None else CONF.cinder.default_boot_volume_size ), ) if boot_volume_size_gb: disk_size_details = { "controlPlane": { "machineRootVolume": { "diskSize": boot_volume_size_gb, } }, "nodeGroupDefaults": { "machineRootVolume": { "diskSize": boot_volume_size_gb, } }, } values = helm.mergeconcat(values, disk_size_details) # Sometimes you need to add an extra network # for things like Cinder CSI CephFS Native # NOTE(mattcrees): extra_network_name is deprecated, so # extra_network_names takes precedence if both are set. # extra_network_name should be removed in a future release. extra_network_name = self._label( cluster, "extra_network_name", "", ) extra_network_names = self._label( cluster, "extra_network_names", lconf.extra_network_names, ) if extra_network_name and not extra_network_names: extra_network_names = extra_network_name if extra_network_names: ports = [{}] for network in extra_network_names.split(" "): ports.append( { "network": { "name": network, }, "securityGroups": [], } ) network_details = { "nodeGroupDefaults": { "machineNetworking": { "ports": ports, }, }, } values = helm.mergeconcat(values, network_details) if self._get_k8s_keystone_auth_enabled(cluster): k8s_keystone_auth_config = { "authWebhook": "k8s-keystone-auth", "addons": { "openstack": { "k8sKeystoneAuth": { # addon subchart configuration "enabled": True, "values": { "openstackAuthUrl": context.auth_url, "projectId": context.project_id, }, } } }, } values = helm.mergeconcat(values, k8s_keystone_auth_config) LOG.debug( "Enable K8s keystone auth webhook for" f" project: {context.project_id} auth url: {context.auth_url}" ) api_lb_allowed_cidrs = self._get_allowed_cidrs(cluster) if isinstance(api_lb_allowed_cidrs, list): allowed_cidrs_config = { "apiServer": {"allowedCidrs": api_lb_allowed_cidrs} } values = helm.mergeconcat(values, allowed_cidrs_config) cni_type = self._get_cni_type(cluster) if cni_type: cni_config = {"addons": {"cni": {"type": cni_type}}} values = helm.mergeconcat(values, cni_config) self._helm_client.install_or_upgrade( driver_utils.chart_release_name(cluster), CONF.capi_helm.helm_chart_name, values, repo=CONF.capi_helm.helm_chart_repo, version=self._get_chart_version(cluster), namespace=driver_utils.cluster_namespace(cluster), ) def _generate_release_name(self, cluster): if cluster.stack_id: return # Make sure no duplicate names # by generating 12 character random id random_bit = short_id.generate_id() base_name = driver_utils.sanitized_name(cluster.name) # valid release names are 53 chars long # and stack_id is 12 characters # but we also use this to derive hostnames trimmed_name = base_name[:30] # Save the full name, so users can rename in the API cluster.stack_id = f"{trimmed_name}-{random_bit}".lower() # be sure to save this before we use it cluster.save() def create_cluster(self, context, cluster, cluster_create_timeout): LOG.info("Starting to create cluster %s", cluster.uuid) self._validate_allowed_flavor(context, cluster.master_flavor_id) nodegroups = cluster.nodegroups for ng in nodegroups: self._validate_allowed_flavor(context, ng.flavor_id) # we generate this name (on the initial create call only) # so we hit no issues with duplicate cluster names # and it makes renaming clusters in the API possible self._generate_release_name(cluster) # NOTE(johngarbutt) all node groups should already # be in the CREATE_IN_PROGRESS state self._k8s_client.ensure_namespace( driver_utils.cluster_namespace(cluster) ) self._create_appcred_secret(context, cluster) self._ensure_certificate_secrets(context, cluster) self._update_helm_release(context, cluster) def update_cluster( self, context, cluster, scale_manager=None, rollback=False ): # we get here if cluster was patched with new node_count self._update_helm_release(context, cluster) def delete_cluster(self, context, cluster): LOG.info("Starting to delete cluster %s", cluster.uuid) # Copy the helm driver by marking all node groups # as delete in progress here, as note done by conductor # We do this before calling uninstall_release because # update_cluster_status can get called before we return for ng in cluster.nodegroups: ng.status = fields.ClusterStatus.DELETE_IN_PROGRESS ng.save() release_name = driver_utils.chart_release_name(cluster) # Only attempt deletion of CAPI resources if they were created in # the first place e.g. if trust creation fails during cluster create # then no CAPI resources will have been created. if release_name: # Begin the deletion of the cluster resources by uninstalling the # Helm release. # Note that this just marks the resources for deletion, # it does not wait for the resources to be deleted. self._helm_client.uninstall_release( release_name, namespace=driver_utils.cluster_namespace(cluster), ) def resize_cluster( self, context, cluster, resize_manager, node_count, nodes_to_remove, nodegroup=None, ): if nodes_to_remove: LOG.warning("Removing specific nodes is not currently supported") self._update_helm_release(context, cluster) def upgrade_cluster( self, context, cluster, cluster_template, max_batch_size, nodegroup, scale_manager=None, rollback=False, ): # TODO(mkjpryor) check that the upgrade is viable # e.g. not a downgrade, not an upgrade by more than one minor version # Updating the template will likely apply for all nodegroups # So mark them all as having an update in progress for nodegroup in cluster.nodegroups: nodegroup.status = fields.ClusterStatus.UPDATE_IN_PROGRESS self._validate_allowed_flavor(context, nodegroup.flavor_id) nodegroup.save() # Move the cluster to the new template cluster.cluster_template_id = cluster_template.uuid cluster.status = fields.ClusterStatus.UPDATE_IN_PROGRESS cluster.save() cluster.refresh() self._update_helm_release(context, cluster) def create_nodegroup(self, context, cluster, nodegroup): nodegroup.status = fields.ClusterStatus.CREATE_IN_PROGRESS self._validate_allowed_flavor(context, nodegroup.flavor_id) if self._get_autoscale_enabled(cluster): self._validate_allowed_node_counts(cluster, nodegroup) nodegroup.save() self._update_helm_release(context, cluster) def update_nodegroup(self, context, cluster, nodegroup): nodegroup.status = fields.ClusterStatus.UPDATE_IN_PROGRESS self._validate_allowed_flavor(context, nodegroup.flavor_id) if self._get_autoscale_enabled(cluster): self._validate_allowed_node_counts(cluster, nodegroup) nodegroup.save() self._update_helm_release(context, cluster) def delete_nodegroup(self, context, cluster, nodegroup): nodegroup.status = fields.ClusterStatus.DELETE_IN_PROGRESS nodegroup.save() # Remove the nodegroup being deleted from the nodegroups # for the Helm release self._update_helm_release( context, cluster, [ng for ng in cluster.nodegroups if ng.name != nodegroup.name], ) def rotate_credential(self, context, cluster): # Current cluster owner to revert to if rotation fails old_user_id = cluster.user_id # Template cluster name for log messages cluster_name = ( f"'{cluster.name}' ({cluster.uuid})" if cluster.name else cluster.uuid ) LOG.info(f"Rotating application credential for cluster {cluster_name}") # Set new owner now to ensure resource labels are applied correctly cluster.user_id = context.user_id cluster.status_reason = None # Fetch the existing application credential ID to check against later old_app_cred_id = self._get_app_cred_id(cluster) # Tuple of possible exceptions raised when creating new app cred exceptions = ( app_creds.ApplicationCredentialError, exception.AuthorizationFailure, # Raised on Keystone error requests.exceptions.RequestException, yaml.YAMLError, ) error_msg = None try: # Create and apply application credential to cluster new_app_cred = self._create_appcred_secret(context, cluster) except exceptions as e: error_msg = str(e) LOG.warning( "Failed to rotate application credential for cluster " f"{cluster_name}: {e}" ) else: if not new_app_cred: error_msg = "Application credential secret is unset." LOG.critical( "Failed to rotate application credential for cluster " f"{cluster_name}: {error_msg}" ) elif new_app_cred.id == old_app_cred_id: error_msg = "Application credential secret failed to apply." LOG.error( "Failed to rotate application credential for cluster " f"{cluster_name}: {error_msg}" ) # Update user-id label on existing secrets if it has changed elif cluster.user_id != old_user_id: try: self._ensure_certificate_secrets(context, cluster) except requests.exceptions.RequestException as e: error_msg = str(e) LOG.error( f"Failed to transfer ownership of cluster " f"{cluster_name} from {old_user_id} to " f"{cluster.user_id}: {e}" ) else: LOG.info( f"Ownership of cluster {cluster_name} transferred " f"from {cluster.user_id} to {context.user_id}" ) if error_msg: cluster.status = fields.ClusterStatus.UPDATE_FAILED cluster.status_reason = error_msg cluster.user_id = old_user_id cluster.save() raise exception.MagnumException(message=error_msg) # Remove the old credential now that the new one is working if old_app_cred_id: try: app_creds.delete_app_cred(cluster, old_app_cred_id) except app_creds.ApplicationCredentialError as e: # NOTE(northcottmt): Not a failure condition as the cluster is # functional, but report the old app cred could not be deleted cluster.status_reason = ( "A new application credential was applied successfully " "but the previous application credential could not be " f"deleted: {e}" ) LOG.warning( "Failed to delete application credential for " f"cluster {cluster_name}: {e}" ) cluster.status = fields.ClusterStatus.UPDATE_COMPLETE cluster.save() def create_federation(self, context, federation): raise NotImplementedError("Will not implement 'create_federation'") def update_federation(self, context, federation): raise NotImplementedError("Will not implement 'update_federation'") def delete_federation(self, context, federation): raise NotImplementedError("Will not implement 'delete_federation'") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/driver_utils.py0000664000175000017500000000251515212012064022171 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Collection of static functions that are shared within the driver. import re from magnum_capi_helm import conf CONF = conf.CONF def cluster_namespace(cluster): # We create clusters in a project-specific namespace # To generate the namespace, first sanitize the project id project_id = re.sub("[^a-z0-9]", "", cluster.project_id.lower()) prefix = CONF.capi_helm.namespace_prefix return f"{prefix}-{project_id}" def sanitized_name(name, suffix=None): if not name: return None return re.sub( "[^a-z0-9]+", "-", (f"{name}-{suffix}" if suffix else name).lower(), ).strip("-") def chart_release_name(cluster): return cluster.stack_id def get_k8s_resource_name(cluster, name): return sanitized_name(chart_release_name(cluster), name) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/helm.py0000664000175000017500000001017415212012064020403 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import functools import json import pathlib import typing as t from magnum.common import utils from oslo_concurrency import processutils from oslo_log import log as logging from magnum_capi_helm import conf LOG = logging.getLogger(__name__) CONF = conf.CONF # This code is loosely based on: # https://github.com/azimuth-cloud/pyhelm3 # Ideally we can share this code in the future. def mergeconcat(defaults, *overrides): """Deep-merge two or more dictionaries together. Lists are concatenated. """ def mergeconcat2(defaults, overrides): if isinstance(defaults, dict) and isinstance(overrides, dict): merged = dict(defaults) for key, value in overrides.items(): if key in defaults: merged[key] = mergeconcat2(defaults[key], value) else: merged[key] = value return merged elif isinstance(defaults, (list, tuple)) and isinstance( overrides, (list, tuple) ): merged = list(defaults) merged.extend(overrides) return merged else: return overrides if overrides is not None else defaults return functools.reduce(mergeconcat2, overrides, defaults) class Client: """Client for interacting with Helm CLI.""" def __init__(self): self._default_timeout = "5m" self._executable = "helm" self._history_max_revisions = 10 self._kubeconfig = CONF.capi_helm.kubeconfig_file def _run(self, command, **kwargs) -> bytes: command = [self._executable] + command if self._kubeconfig: command.extend(["--kubeconfig", self._kubeconfig]) stdout, stderr = utils.execute(*command, **kwargs) LOG.debug(f"Ran helm {command} got out:{stdout} err:{stderr}") return stdout def install_or_upgrade( self, release_name: str, chart_ref: t.Union[pathlib.Path, str], *values: t.Dict[str, t.Any], namespace: str, repo: t.Optional[str] = None, version: t.Optional[str] = None, ) -> t.Iterable[t.Dict[str, t.Any]]: """Install or upgrade specified release using chart and values.""" assert release_name is not None command = [ "upgrade", release_name, chart_ref, "--history-max", self._history_max_revisions, "--install", "--output", "json", "--timeout", self._default_timeout, # We send the values in on stdin "--values", "-", "--namespace", namespace, ] if repo: command += ["--repo", repo] if version: command += [ "--version", version, ] process_input = json.dumps(mergeconcat({}, *values)) return json.loads(self._run(command, process_input=process_input)) def uninstall_release( self, release_name: str, namespace: str, ): """Uninstall the named release.""" assert release_name is not None command = [ "uninstall", release_name, "--timeout", self._default_timeout, "--namespace", namespace, ] try: self._run(command) except processutils.ProcessExecutionError as exc: # Swallow release not found errors, as that is our desired state if not exc.stderr or "release: not found" not in exc.stderr: raise ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/kubernetes.py0000664000175000017500000003075715212012064021636 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import copy import json import os import pathlib import re import tempfile import yaml from oslo_log import log as logging import requests from magnum_capi_helm import conf LOG = logging.getLogger(__name__) CONF = conf.CONF class Client(requests.Session): """Object for producing Kubernetes clients.""" KUBECONFIG_ENV_NAME = "KUBECONFIG" def __init__(self, kubeconfig): super().__init__() self._tempfiles = [] cluster, user = self._get_cluster_and_user(kubeconfig) self.server = cluster["server"].rstrip("/") ca_file = self.ensure_file_cert(cluster, "certificate-authority") if ca_file: self.verify = ca_file # convert certs into files as required by requests # https://requests.readthedocs.io/en/latest/api/#requests.Session.cert client_cert = self.ensure_file_cert(user, "client-certificate") client_key = self.ensure_file_cert(user, "client-key") if client_cert and client_key: self.cert = (client_cert, client_key) elif user.get("token"): self.headers.update({"Authorization": f"Bearer {user['token']}"}) else: raise Exception( "No supported authentication method found in kubeconfig" ) def ensure_file_cert(self, obj, file_key): """Returns the path of cert. Returns a string containing the path to a file with the requesteddata. First check if there is a file path already, if the data is there, put it in a file, add path to the _tempfiles list for cleanup and return the created file. """ if file_key in obj: return obj[file_key] data_key = file_key + "-data" if data_key in obj: # NOTE(dalees): The created file may contain private key material # but is owned by the current user and mode 0600. # See also: tempfile.mkstemp # In Python 3.12, cleanup may be improved with # delete=True,delete_on_close=False with tempfile.NamedTemporaryFile(delete=False) as fd: fd.write(base64.standard_b64decode(obj[data_key])) self._tempfiles.append(fd.name) return fd.name return None def __del__(self): # Remove any temporary certificate files this class owns. for file_path in self._tempfiles: try: os.remove(file_path) except (OSError, FileNotFoundError): pass self._tempfiles = [] def _get_cluster_and_user(self, kubeconfig): # get the context current_context = kubeconfig["current-context"] context = [ c["context"] for c in kubeconfig["contexts"] if c["name"] == current_context ][0] # extract cluster and user from context cluster = [ c["cluster"] for c in kubeconfig["clusters"] if c["name"] == context["cluster"] ][0] user = [ u["user"] for u in kubeconfig["users"] if u["name"] == context["user"] ][0] return cluster, user @classmethod def _get_kubeconfig_path(cls): # use config if specified if CONF.capi_helm.kubeconfig_file: return CONF.capi_helm.kubeconfig_file if cls.KUBECONFIG_ENV_NAME in os.environ: return os.environ[cls.KUBECONFIG_ENV_NAME] # the default kubeconfig location return pathlib.Path.home() / ".kube" / "config" @classmethod def _load_kubeconfig(cls, path): with open(path) as fd: return yaml.safe_load(fd) @classmethod def load(cls): path = cls._get_kubeconfig_path() kubeconfig = cls._load_kubeconfig(path) return Client(kubeconfig) def request(self, method, url, *args, **kwargs): # Make sure to add the server to any relative URLs if re.match(r"^http(s)://", url) is None: url = "{}{}".format(self.server, url) response = super().request(method, url, *args, **kwargs) LOG.debug( 'Kubernetes API request: "%s %s" %s', method, url, response.status_code, ) return response def ensure_namespace(self, namespace): Namespace(self).apply(namespace) def apply_secret(self, secret_name, data, namespace): Secret(self).apply(secret_name, data, namespace) def delete_all_secrets_by_label(self, label, value, namespace): Secret(self).delete_all_by_label(label, value, namespace) def get_secret(self, secret_name, namespace): return Secret(self).fetch(secret_name, namespace) def get_secret_value(self, secret_name, namespace, key): secret = self.get_secret(secret_name, namespace) if secret: encoded_value = secret["data"][key].encode() decoded_value = base64.b64decode(encoded_value).decode() return decoded_value def get_capi_cluster(self, name, namespace): return Cluster(self).fetch(name, namespace) def get_capi_openstackcluster(self, name, namespace): return OpenstackCluster(self).fetch(name, namespace) def get_k8s_control_plane(self, name, namespace): return K8sControlPlane(self).fetch(name, namespace) def get_machine_deployment(self, name, namespace): return MachineDeployment(self).fetch(name, namespace) def get_manifests_by_label(self, labels, namespace): return list(Manifests(self).fetch_all_by_label(labels, namespace)) def get_helm_releases_by_label(self, labels, namespace): return list(HelmRelease(self).fetch_all_by_label(labels, namespace)) def get_helm_chart_proxies_by_label(self, labels, namespace): return list(HelmChartProxy(self).fetch_all_by_label(labels, namespace)) def get_addons_by_label(self, labels, namespace): addons = [] try: addons.extend(self.get_helm_releases_by_label(labels, namespace)) addons.extend(self.get_manifests_by_label(labels, namespace)) except requests.exceptions.HTTPError as e: if e.response.status_code != 404: raise try: addons.extend( self.get_helm_chart_proxies_by_label(labels, namespace) ) except requests.exceptions.HTTPError as e: if e.response.status_code != 404: raise return addons def get_all_machines_by_label(self, labels, namespace): return list(Machine(self).fetch_all_by_label(labels, namespace)) class Resource: def __init__(self, client): self.client = client assert hasattr(self, "api_version") self.kind = getattr(self, "kind", type(self).__name__) self.plural_name = getattr( self, "plural_name", self.kind.lower() + "s" ) self.namespaced = getattr(self, "namespaced", True) self.api_resources = json.loads(CONF.capi_helm.api_resources) def prepare_path(self, name=None, namespace=None): # Begin with either /api or /apis depending whether the api version # is the core API prefix = "/apis" if "/" in self.api_version else "/api" # Include the namespace unless the resource is namespaced path_namespace = f"/namespaces/{namespace}" if namespace else "" # Include the resource name if given path_name = f"/{name}" if name else "" return ( f"{prefix}/{self.api_version}{path_namespace}/" f"{self.plural_name}{path_name}" ) def fetch(self, name, namespace=None): """Fetches specified object from the target Kubernetes cluster. If the object is not found, None is returned. """ assert self.namespaced == bool(namespace) assert name is not None response = self.client.get(self.prepare_path(name, namespace)) if 200 <= response.status_code < 300: return response.json() elif response.status_code == 404: return None else: response.raise_for_status() def fetch_all_by_label(self, labels, namespace=None): """Fetches objects matching the labels from the target cluster.""" assert self.namespaced == bool(namespace) label_selector = ",".join(f"{k}={v}" for k, v in labels.items()) continue_token = "" while True: params = {"labelSelector": label_selector} if continue_token: params["continue"] = continue_token response = self.client.get( self.prepare_path(namespace=namespace), params=params ) response.raise_for_status() response_data = response.json() yield from response_data["items"] continue_token = response_data["metadata"]["continue"] if not continue_token: break def apply(self, name, data=None, namespace=None): """Applies the given object to the target Kubernetes cluster.""" assert self.namespaced == bool(namespace) body_data = copy.deepcopy(data) if data else {} body_data["apiVersion"] = self.api_version body_data["kind"] = self.kind body_data.setdefault("metadata", {})["name"] = name if namespace: body_data["metadata"]["namespace"] = namespace response = self.client.patch( self.prepare_path(name, namespace), json=body_data, headers={"Content-Type": "application/apply-patch+yaml"}, params={"fieldManager": "magnum", "force": "true"}, ) response.raise_for_status() return response.json() def delete_all_by_label(self, label, value, namespace=None): """Deletes all objects with the specified label from cluster.""" assert self.namespaced == bool(namespace) response = self.client.delete( self.prepare_path(namespace=namespace), params={"labelSelector": f"{label}={value}"}, ) response.raise_for_status() class Namespace(Resource): api_version = "v1" namespaced = False class Secret(Resource): api_version = "v1" class Cluster(Resource): api_version = ( json.loads(CONF.capi_helm.api_resources) .get("Cluster", {}) .get("api_version", "cluster.x-k8s.io/v1beta1") ) class OpenstackCluster(Resource): api_version = ( json.loads(CONF.capi_helm.api_resources) .get("OpenstackCluster", {}) .get("api_version", "infrastructure.cluster.x-k8s.io/v1beta1") ) class MachineDeployment(Resource): api_version = ( json.loads(CONF.capi_helm.api_resources) .get("MachineDeployment", {}) .get("api_version", "cluster.x-k8s.io/v1beta1") ) class K8sControlPlane(Resource): api_version = ( json.loads(CONF.capi_helm.api_resources) .get("K8sControlPlane", {}) .get("api_version", "controlplane.cluster.x-k8s.io/v1beta1") ) plural_name = ( json.loads(CONF.capi_helm.api_resources) .get("K8sControlPlane", {}) .get("plural_name", "kubeadmcontrolplanes") ) class Machine(Resource): api_version = ( json.loads(CONF.capi_helm.api_resources) .get("Machine", {}) .get("api_version", "cluster.x-k8s.io/v1beta1") ) class Manifests(Resource): api_version = ( json.loads(CONF.capi_helm.api_resources) .get("Manifests", {}) .get("api_version", "addons.stackhpc.com/v1alpha1") ) plural_name = "manifests" class HelmRelease(Resource): api_version = ( json.loads(CONF.capi_helm.api_resources) .get("HelmRelease", {}) .get("api_version", "addons.stackhpc.com/v1alpha1") ) class HelmChartProxy(Resource): api_version = ( json.loads(CONF.capi_helm.api_resources) .get("HelmChartProxy", {}) .get("api_version", "addons.cluster.x-k8s.io/v1alpha1") ) plural_name = "helmchartproxies" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7064328 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/0000775000175000017500000000000015212012201020234 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/__init__.py0000664000175000017500000000000015212012064022342 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/base.py0000664000175000017500000000143115212012064021526 0ustar00zuulzuul# -*- coding: utf-8 -*- # Copyright 2010-2011 OpenStack Foundation # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslotest import base class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7074327 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/common/0000775000175000017500000000000015212012201021524 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/common/__init__.py0000664000175000017500000000000015212012064023632 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/common/test_app_creds.py0000664000175000017500000001536615212012064025117 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections from unittest import mock import keystoneauth1 from magnum.common import clients from magnum.common import utils from magnum.tests.unit.db import base from magnum.tests.unit.objects import utils as obj_utils from magnum_capi_helm.common import app_creds class TestAppCreds(base.DbTestCase): def setUp(self): super().setUp() self.cluster_obj = obj_utils.create_test_cluster( self.context, name="cluster_example_$A", master_flavor_id="flavor_small", flavor_id="flavor_medium", ) @mock.patch.object(utils, "get_openstack_ca") def test_get_openstack_ca_certificate(self, mock_ca): mock_ca.return_value = "cert" cert = app_creds._get_openstack_ca_certificate() self.assertEqual("cert", cert) @mock.patch.object(utils, "get_openstack_ca") def test_get_openstack_ca_certificate_get_certify(self, mock_ca): mock_ca.return_value = None cert = app_creds._get_openstack_ca_certificate() self.assertIsNotNone(cert) @mock.patch("secrets.token_hex") @mock.patch.object(clients, "OpenStackClients") def test_create_app_cred(self, mock_client, mock_token): mock_client().cinder_region_name.return_value = "cinder" mock_client().url_for.return_value = "http://keystone" mock_app_cred = mock_client().keystone().client.application_credentials app_cred = collections.namedtuple("appcred", ["id", "secret"]) mock_app_cred.create.return_value = app_cred("id", "pass") context = mock.MagicMock() context.user_id = "fake_user" context.roles = ["member", "foo", "admin"] mock_token_hex_nonce = "abcd1234" mock_token.return_value = mock_token_hex_nonce app_cred = app_creds.create_app_cred(context, self.cluster_obj) app_cred_string_data = app_creds._get_app_cred_clouds_dict( context, app_cred ) expected = { "clouds": { "openstack": { "auth": { "application_credential_id": "id", "application_credential_secret": "pass", "auth_url": "http://keystone", }, "auth_type": "v3applicationcredential", "identity_api_version": 3, "interface": "public", "region_name": "cinder", "verify": True, } } } self.assertEqual(expected, app_cred_string_data) mock_client().url_for.assert_called_once_with( service_type="identity", interface="public" ) mock_app_cred.create.assert_called_once_with( user=context.user_id, name=f"magnum-{self.cluster_obj.uuid}-{mock_token_hex_nonce}", description="Magnum cluster " + f"({self.cluster_obj.name or self.cluster_obj.uuid})", # roles=["member", "foo"], ) @mock.patch.object(app_creds, "_get_openstack_ca_certificate") @mock.patch.object(app_creds, "_get_app_cred_clouds_dict") def test_get_app_cred_yaml(self, mock_clouds, mock_ca): app_cred = collections.namedtuple("appcred", ["id", "secret"]) mock_app_cred = app_cred("id", "secret") mock_clouds.return_value = { "clouds": { "openstack": { "auth": {"application_credential_id": mock_app_cred.id}, } } } mock_ca.return_value = "cacert" string_data = app_creds.get_app_cred_string_data( "context", mock_app_cred ) mock_clouds.assert_called_once_with("context", mock_app_cred) mock_ca.assert_called_once_with() expected = { "cacert": "cacert", "clouds.yaml": """\ clouds: openstack: auth: application_credential_id: id """, } self.assertEqual(expected, string_data) @mock.patch.object(clients, "OpenStackClients") def test_delete_app_cred(self, mock_client): mock_app_cred_client = ( mock_client().keystone().client.application_credentials ) mock_app_cred = mock.MagicMock() mock_app_cred.name.startswith.return_value = True mock_app_cred_client.get.return_value = mock_app_cred app_cred_id = "abcdef12345" app_creds.delete_app_cred(self.cluster_obj, app_cred_id) mock_app_cred.delete.assert_called_once() mock_app_cred.name.startswith.assert_called_once_with( f"magnum-{self.cluster_obj.uuid}" ) mock_app_cred_client.get.assert_called_once_with(app_cred_id) @mock.patch.object(clients, "OpenStackClients") def test_delete_app_cred_not_found(self, mock_client): mock_app_cred_client = ( mock_client().keystone().client.application_credentials ) mock_app_cred_client.get.side_effect = ( keystoneauth1.exceptions.http.NotFound ) app_cred_id = "abcdef12345" self.assertRaises( app_creds.ApplicationCredentialError, app_creds.delete_app_cred, self.cluster_obj, app_cred_id, ) mock_app_cred_client.get.assert_called_once_with(app_cred_id) @mock.patch.object(clients, "OpenStackClients") def test_delete_app_cred_invalid_name(self, mock_client): mock_app_cred_client = ( mock_client().keystone().client.application_credentials ) mock_app_cred = mock.MagicMock() mock_app_cred_name = mock.MagicMock() mock_app_cred_name.startswith.return_value = False mock_app_cred.name = mock_app_cred_name mock_app_cred_client.get.return_value = mock_app_cred app_cred_id = "abcdef12345" self.assertRaises( app_creds.ApplicationCredentialError, app_creds.delete_app_cred, self.cluster_obj, app_cred_id, ) mock_app_cred.delete.assert_not_called() mock_app_cred.name.startswith.assert_called_once_with( f"magnum-{self.cluster_obj.uuid}" ) mock_app_cred_client.get.assert_called_once_with(app_cred_id) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/common/test_ca_certificates.py0000664000175000017500000000607115212012064026260 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from magnum.common.x509 import operations as x509 from magnum.conductor.handlers.common import cert_manager from magnum_capi_helm.common import ca_certificates from magnum_capi_helm.tests import base class TestCACerts(base.TestCase): @mock.patch.object(cert_manager, "get_cluster_magnum_cert") @mock.patch.object(cert_manager, "get_cluster_ca_certificate") @mock.patch.object(ca_certificates, "_decode_key") @mock.patch.object(ca_certificates, "_decode_cert") def test_ensure_certificate_secrets( self, mock_cert, mock_key, mock_ca, mock_mag ): mock_ca.side_effect = lambda cluster, context, cert="ca": cert mock_key.side_effect = lambda cert: f"key-{cert}" mock_cert.side_effect = lambda cert: f"cert-{cert}" mock_mag.return_value = "cert_mag" context = mock.MagicMock() cluster = mock.MagicMock() data = ca_certificates.get_certificate_string_data(context, cluster) self.assertEqual( { "ca": {"tls.crt": "cert-ca", "tls.key": "key-ca"}, "etcd": { "tls.crt": "cert-etcd", "tls.key": "key-etcd", }, "proxy": { "tls.crt": "cert-front_proxy", "tls.key": "key-front_proxy", }, "sa": { "tls.crt": "cert-cert_mag", "tls.key": "key-cert_mag", }, }, data, ) mock_mag.assert_called_once_with(cluster, context) self.assertEqual( [ mock.call(cluster, context), mock.call(cluster, context, "etcd"), mock.call(cluster, context, "front_proxy"), ], mock_ca.call_args_list, ) def test_decode_cert(self): mock_cert = mock.MagicMock() mock_cert.get_certificate.return_value = "cert" result = ca_certificates._decode_cert(mock_cert) self.assertEqual("cert", result) @mock.patch.object(x509, "decrypt_key") def test_decode_key(self, mock_decrypt): mock_cert = mock.MagicMock() mock_cert.get_private_key.return_value = "private" mock_cert.get_private_key_passphrase.return_value = "pass" mock_decrypt.return_value = "foo" result = ca_certificates._decode_key(mock_cert) self.assertEqual("foo", result) mock_decrypt.assert_called_once_with("private", "pass") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/common/test_capi_monitor.py0000664000175000017500000002261315212012064025633 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock from magnum.objects import fields as m_fields from magnum.tests.unit.db import base from magnum.tests.unit.objects import utils as obj_utils from magnum_capi_helm.common import capi_monitor class TestCAPIMonitor(base.DbTestCase): def setUp(self): super(TestCAPIMonitor, self).setUp() self.cluster_obj = obj_utils.create_test_cluster( self.context, name="cluster_example_$A", master_flavor_id="flavor_small", flavor_id="flavor_medium", stack_id="cluster-example-a-111111111111", ) # add in missing node group flavor for ng in self.cluster_obj.nodegroups: if ng.role != "master": ng.flavor_id = "flavor_medium" ng.save() # Create capi monitor self.monitor = capi_monitor.CAPIMonitor(self.context, self.cluster_obj) # Patch k8s client and set Ready state for all CAPI objects. self.patcher = mock.patch.object( capi_monitor.CAPIMonitor, "_k8s_client" ) self.mock_k8s = self.patcher.start() ready_state = { "status": {"conditions": [{"type": "Ready", "status": "True"}]} } self.mock_k8s.get_capi_cluster.return_value = copy.deepcopy( ready_state ) self.mock_k8s.get_capi_openstackcluster.return_value = { "status": { "ready": True, } } self.mock_k8s.get_k8s_control_plane.return_value = copy.deepcopy( ready_state ) self.mock_k8s.get_machine_deployment.return_value = copy.deepcopy( ready_state ) def tearDown(self): super(TestCAPIMonitor, self).tearDown() self.patcher.stop() def test_healthy_cluster(self): self.monitor.poll_health_status() self.assertEqual( self.monitor.data["health_status"], m_fields.ClusterHealthStatus.HEALTHY, ) self.assertEqual( self.monitor.data["health_status_reason"], { "cluster": "Ready", "controlplane": "Ready", "infrastructure": "Ready", "nodegroup": "Ready", }, ) def test_cluster_absent(self): self.mock_k8s.get_capi_cluster.return_value = None self.monitor.poll_health_status() self.assertEqual( self.monitor.data["health_status"], m_fields.ClusterHealthStatus.UNHEALTHY, ) self.assertEqual( self.monitor.data["health_status_reason"], { "cluster": "Cluster resource not found.", "controlplane": "Ready", "infrastructure": "Ready", "nodegroup": "Ready", }, ) def test_cluster_unhealthy(self): cluster_state = { "status": { "conditions": [ {"type": "Ready", "status": "False"}, {"type": "ControlPlaneReady", "status": "False"}, ] } } self.mock_k8s.get_capi_cluster.return_value = cluster_state self.monitor.poll_health_status() self.assertEqual( self.monitor.data["health_status"], m_fields.ClusterHealthStatus.UNHEALTHY, ) self.assertEqual( self.monitor.data["health_status_reason"], { "cluster": "Waiting on ['Ready', 'ControlPlaneReady']", "controlplane": "Ready", "infrastructure": "Ready", "nodegroup": "Ready", }, ) def test_infra_unhealthy(self): infra_state = { "status": { "ready": False, } } self.mock_k8s.get_capi_openstackcluster.return_value = infra_state self.monitor.poll_health_status() self.assertEqual( self.monitor.data["health_status"], m_fields.ClusterHealthStatus.UNHEALTHY, ) self.assertEqual( self.monitor.data["health_status_reason"], { "cluster": "Ready", "controlplane": "Ready", "infrastructure": "Infrastructure not ready.", "nodegroup": "Ready", }, ) def test_infra_has_failure_message(self): infra_state = { "status": { "ready": True, "failureMessage": "abc", "failureReason": "123", }, } self.mock_k8s.get_capi_openstackcluster.return_value = infra_state self.monitor.poll_health_status() self.assertEqual( self.monitor.data["health_status"], m_fields.ClusterHealthStatus.UNHEALTHY, ) self.assertEqual( self.monitor.data["health_status_reason"], { "cluster": "Ready", "controlplane": "Ready", "infrastructure": "123: abc", "nodegroup": "Ready", }, ) def test_controlplane_has_conditions(self): cp_state = { "status": { "conditions": [ {"type": "Ready", "status": "False"}, {"type": "Available", "status": "False"}, {"type": "CertificatesAvailable", "status": "False"}, { "type": "ControlPlaneComponentsHealthy", "status": "False", }, {"type": "EtcdClusterHealthy", "status": "False"}, {"type": "MachinesCreated", "status": "False"}, {"type": "MachinesReady", "status": "False"}, {"type": "Resized", "status": "False"}, ] } } self.mock_k8s.get_k8s_control_plane.return_value = cp_state self.monitor.poll_health_status() self.assertEqual( self.monitor.data["health_status"], m_fields.ClusterHealthStatus.UNHEALTHY, ) cp_status = ( "Waiting on ['Ready', 'Available', " "'CertificatesAvailable', " "'ControlPlaneComponentsHealthy', 'EtcdClusterHealthy', " "'MachinesCreated', 'MachinesReady', 'Resized']" ) self.assertEqual( self.monitor.data["health_status_reason"], { "cluster": "Ready", "controlplane": cp_status, "infrastructure": "Ready", "nodegroup": "Ready", }, ) def test_nodegroup_has_conditions(self): ng_state = { "status": { "conditions": [ {"type": "Ready", "status": "True"}, {"type": "Available", "status": "False"}, ] } } self.mock_k8s.get_machine_deployment.return_value = ng_state self.monitor.poll_health_status() self.assertEqual( self.monitor.data["health_status"], m_fields.ClusterHealthStatus.UNHEALTHY, ) self.assertEqual( self.monitor.data["health_status_reason"], { "cluster": "Ready", "controlplane": "Ready", "infrastructure": "Ready", "nodegroup": "test-worker waiting on ['Available']", }, ) def test_machinedeployment_absent(self): self.mock_k8s.get_machine_deployment.return_value = None self.monitor.poll_health_status() self.assertEqual( self.monitor.data["health_status"], m_fields.ClusterHealthStatus.UNHEALTHY, ) self.assertEqual( self.monitor.data["health_status_reason"], { "cluster": "Ready", "controlplane": "Ready", "infrastructure": "Ready", "nodegroup": "test-worker resource not found.", }, ) def test_all_missing(self): self.mock_k8s.get_capi_cluster.return_value = None self.mock_k8s.get_capi_openstackcluster.return_value = None self.mock_k8s.get_k8s_control_plane.return_value = None self.mock_k8s.get_machine_deployment.return_value = None self.monitor.poll_health_status() self.assertEqual( self.monitor.data["health_status"], m_fields.ClusterHealthStatus.UNHEALTHY, ) self.assertEqual( self.monitor.data["health_status_reason"], { "cluster": "Cluster resource not found.", "controlplane": "Control plane resource not found.", "infrastructure": "Infrastructure resource not found.", "nodegroup": "test-worker resource not found.", }, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/test_driver.py0000664000175000017500000042327715212012064023166 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from uuid import uuid4 from magnum.common import exception from magnum.common import neutron from magnum.objects import fields from magnum.tests.unit.db import base from magnum.tests.unit.objects import utils as obj_utils from magnum_capi_helm.common import app_creds from magnum_capi_helm.common import ca_certificates from magnum_capi_helm.common import capi_monitor from magnum_capi_helm import conf from magnum_capi_helm import driver from magnum_capi_helm import driver_utils from magnum_capi_helm import helm from magnum_capi_helm import kubernetes CONF = conf.CONF class ClusterAPIDriverTest(base.DbTestCase): def setUp(self): super(ClusterAPIDriverTest, self).setUp() self.driver = driver.Driver() self.cluster_obj = obj_utils.create_test_cluster( self.context, name="cluster_example_$A", master_flavor_id="flavor_small", flavor_id="flavor_medium", stack_id="cluster-example-a-111111111111", ) # add in missing node group flavor for ng in self.cluster_obj.nodegroups: if ng.role != "master": ng.flavor_id = "flavor_medium" ng.save() def test_provides(self): self.assertEqual( [ { "server_type": "vm", "os": "ubuntu", "coe": "kubernetes", }, { "server_type": "vm", "os": "flatcar", "coe": "kubernetes", }, ], self.driver.provides, ) @mock.patch.object(driver.Driver, "_update_status_deleting") @mock.patch.object(driver.Driver, "_update_status_updating") @mock.patch.object(driver.Driver, "_update_all_nodegroups_status") @mock.patch.object(driver.Driver, "_get_capi_cluster") def test_update_cluster_status_creating( self, mock_capi, mock_ng, mock_update, mock_delete ): mock_ng.return_value = True mock_capi.return_value = {"spec": {}} self.cluster_obj.status = fields.ClusterStatus.CREATE_IN_PROGRESS self.driver.update_cluster_status(self.context, self.cluster_obj) mock_ng.assert_called_once_with(self.cluster_obj) mock_update.assert_not_called() mock_delete.assert_not_called() @mock.patch.object(driver.Driver, "_update_status_deleting") @mock.patch.object(driver.Driver, "_update_status_updating") @mock.patch.object(driver.Driver, "_update_all_nodegroups_status") @mock.patch.object(driver.Driver, "_get_capi_cluster") def test_update_cluster_status_creating_not_found( self, mock_capi, mock_ng, mock_update, mock_delete ): mock_ng.return_value = True mock_capi.return_value = None self.cluster_obj.status = fields.ClusterStatus.CREATE_IN_PROGRESS self.driver.update_cluster_status(self.context, self.cluster_obj) mock_ng.assert_not_called() mock_update.assert_not_called() mock_delete.assert_not_called() @mock.patch.object(driver.Driver, "_update_status_deleting") @mock.patch.object(driver.Driver, "_update_status_updating") @mock.patch.object(driver.Driver, "_update_all_nodegroups_status") @mock.patch.object(driver.Driver, "_get_capi_cluster") def test_update_cluster_status_created( self, mock_capi, mock_ng, mock_update, mock_delete ): mock_ng.return_value = False mock_capi.return_value = {"spec": {}} self.cluster_obj.status = fields.ClusterStatus.CREATE_IN_PROGRESS self.driver.update_cluster_status(self.context, self.cluster_obj) mock_ng.assert_called_once_with(self.cluster_obj) mock_update.assert_called_once_with(self.cluster_obj, {"spec": {}}) mock_delete.assert_not_called() @mock.patch.object(driver.Driver, "_update_status_deleting") @mock.patch.object(driver.Driver, "_update_status_updating") @mock.patch.object(driver.Driver, "_update_all_nodegroups_status") @mock.patch.object(driver.Driver, "_get_capi_cluster") def test_update_cluster_status_deleted( self, mock_capi, mock_ng, mock_update, mock_delete ): mock_capi.return_value = None self.cluster_obj.status = fields.ClusterStatus.DELETE_IN_PROGRESS self.driver.update_cluster_status(self.context, self.cluster_obj) mock_ng.assert_not_called() mock_update.assert_not_called() mock_delete.assert_called_once_with(self.context, self.cluster_obj) @mock.patch.object(driver.Driver, "_update_status_deleting") @mock.patch.object(driver.Driver, "_update_status_updating") @mock.patch.object(driver.Driver, "_update_all_nodegroups_status") @mock.patch.object(driver.Driver, "_get_capi_cluster") def test_update_cluster_status_deleting( self, mock_capi, mock_ng, mock_update, mock_delete ): mock_capi.return_value = {"spec": {}} self.cluster_obj.status = fields.ClusterStatus.DELETE_IN_PROGRESS self.driver.update_cluster_status(self.context, self.cluster_obj) mock_ng.assert_called_once_with(self.cluster_obj) mock_update.assert_not_called() mock_delete.assert_not_called() @mock.patch.object(driver.Driver, "_update_status_deleting") @mock.patch.object(driver.Driver, "_update_status_updating") @mock.patch.object(driver.Driver, "_update_all_nodegroups_status") @mock.patch.object(driver.Driver, "_get_capi_cluster") def test_update_cluster_status_create_complete( self, mock_capi, mock_ng, mock_update, mock_delete ): mock_capi.return_value = {"spec": {}} self.cluster_obj.status = fields.ClusterStatus.CREATE_COMPLETE self.driver.update_cluster_status(self.context, self.cluster_obj) mock_ng.assert_called_once_with(self.cluster_obj) mock_update.assert_not_called() mock_delete.assert_not_called() @mock.patch.object(driver.Driver, "_update_worker_nodegroup_status") @mock.patch.object(driver.Driver, "_update_control_plane_nodegroup_status") def test_update_all_nodegroups_status_not_in_progress( self, mock_cp, mock_w ): control_plane = [ ng for ng in self.cluster_obj.nodegroups if ng.role == driver.NODE_GROUP_ROLE_CONTROLLER ][0] control_plane.status = fields.ClusterStatus.CREATE_COMPLETE mock_cp.return_value = control_plane mock_w.return_value = None result = self.driver._update_all_nodegroups_status(self.cluster_obj) self.assertFalse(result) control_plane = [ ng for ng in self.cluster_obj.nodegroups if ng.role == driver.NODE_GROUP_ROLE_CONTROLLER ][0] mock_cp.assert_called_once_with(self.cluster_obj, mock.ANY) self.assertEqual( control_plane.obj_to_primitive(), mock_cp.call_args_list[0][0][1].obj_to_primitive(), ) mock_w.assert_called_once_with(self.cluster_obj, mock.ANY) worker = [ ng for ng in self.cluster_obj.nodegroups if ng.role != driver.NODE_GROUP_ROLE_CONTROLLER ][0] self.assertEqual( worker.obj_to_primitive(), mock_w.call_args_list[0][0][1].obj_to_primitive(), ) @mock.patch.object(driver.Driver, "_update_worker_nodegroup_status") @mock.patch.object(driver.Driver, "_update_control_plane_nodegroup_status") def test_update_all_nodegroups_status_in_progress(self, mock_cp, mock_w): control_plane = [ ng for ng in self.cluster_obj.nodegroups if ng.role == driver.NODE_GROUP_ROLE_CONTROLLER ][0] control_plane.status = fields.ClusterStatus.CREATE_IN_PROGRESS mock_cp.return_value = control_plane mock_w.return_value = None result = self.driver._update_all_nodegroups_status(self.cluster_obj) self.assertTrue(result) mock_cp.assert_called_once_with(self.cluster_obj, mock.ANY) mock_w.assert_called_once_with(self.cluster_obj, mock.ANY) @mock.patch.object(driver.Driver, "_update_nodegroup_status") @mock.patch.object(kubernetes.Client, "load") def test_update_worker_nodegroup_status_empty( self, mock_load, mock_update ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "workers" nodegroup.status = fields.ClusterStatus.CREATE_IN_PROGRESS md = {"status": {}} mock_client.get_machine_deployment.return_value = md self.driver._update_worker_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_machine_deployment.assert_called_once_with( "cluster-example-a-111111111111-workers", "magnum-fakeproject" ) mock_update.assert_called_once_with( self.cluster_obj, nodegroup, driver.NodeGroupState.PENDING ) @mock.patch.object(driver.Driver, "_update_nodegroup_status") @mock.patch.object(kubernetes.Client, "load") def test_update_worker_nodegroup_status_scaling_up( self, mock_load, mock_update ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "workers" md = {"status": {"phase": "ScalingUp"}} mock_client.get_machine_deployment.return_value = md self.driver._update_worker_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_machine_deployment.assert_called_once_with( "cluster-example-a-111111111111-workers", "magnum-fakeproject" ) mock_update.assert_called_once_with( self.cluster_obj, mock.ANY, driver.NodeGroupState.PENDING ) @mock.patch.object(driver.Driver, "_update_nodegroup_status") @mock.patch.object(kubernetes.Client, "load") def test_update_worker_nodegroup_status_failed( self, mock_load, mock_update ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "workers" nodegroup.status = fields.ClusterStatus.CREATE_IN_PROGRESS md = {"status": {"phase": "Failed"}} mock_client.get_machine_deployment.return_value = md self.driver._update_worker_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_machine_deployment.assert_called_once_with( "cluster-example-a-111111111111-workers", "magnum-fakeproject" ) mock_update.assert_called_once_with( self.cluster_obj, nodegroup, driver.NodeGroupState.FAILED ) @mock.patch.object(driver.Driver, "_update_nodegroup_status") @mock.patch.object(kubernetes.Client, "load") def test_update_worker_nodegroup_status_not_present_creating( self, mock_load, mock_update ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "workers" nodegroup.status = fields.ClusterStatus.CREATE_IN_PROGRESS mock_client.get_machine_deployment.return_value = None mock_client.get_all_machines_by_label.return_value = None self.driver._update_worker_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_machine_deployment.assert_called_once_with( "cluster-example-a-111111111111-workers", "magnum-fakeproject" ) mock_update.assert_called_once_with( self.cluster_obj, nodegroup, driver.NodeGroupState.NOT_PRESENT ) mock_client.get_all_machines_by_label.assert_not_called() nodegroup.destroy.assert_not_called() nodegroup.save.assert_not_called() @mock.patch.object(driver.Driver, "_update_nodegroup_status") @mock.patch.object(kubernetes.Client, "load") def test_update_worker_nodegroup_status_not_present_deleting( self, mock_load, mock_update ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "workers" nodegroup.status = fields.ClusterStatus.DELETE_IN_PROGRESS machine = {"status": {}} mock_client.get_machine_deployment.return_value = None mock_client.get_all_machines_by_label.return_value = machine self.driver._update_worker_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_machine_deployment.assert_called_once_with( "cluster-example-a-111111111111-workers", "magnum-fakeproject" ) mock_client.get_all_machines_by_label.assert_called_once_with( { "capi.stackhpc.com/cluster": "cluster-example-a-111111111111", "capi.stackhpc.com/component": "worker", "capi.stackhpc.com/node-group": "workers", }, "magnum-fakeproject", ) mock_update.assert_called_once_with( self.cluster_obj, nodegroup, driver.NodeGroupState.PENDING ) @mock.patch.object(kubernetes.Client, "load") def test_update_worker_nodegroup_status_machines_missing_non_default( self, mock_load ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "workers" nodegroup.status = fields.ClusterStatus.DELETE_IN_PROGRESS nodegroup.is_default = False mock_client.get_machine_deployment.return_value = None mock_client.get_all_machines_by_label.return_value = None self.driver._update_worker_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_machine_deployment.assert_called_once_with( "cluster-example-a-111111111111-workers", "magnum-fakeproject" ) mock_client.get_all_machines_by_label.assert_called_once_with( { "capi.stackhpc.com/cluster": "cluster-example-a-111111111111", "capi.stackhpc.com/component": "worker", "capi.stackhpc.com/node-group": "workers", }, "magnum-fakeproject", ) nodegroup.destroy.assert_called_once_with() nodegroup.save.assert_not_called() @mock.patch.object(driver.Driver, "_update_nodegroup_status") @mock.patch.object(kubernetes.Client, "load") def test_update_worker_nodegroup_status_running( self, mock_load, mock_update ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "workers" md = {"status": {"phase": "Running"}} mock_client.get_machine_deployment.return_value = md self.driver._update_worker_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_machine_deployment.assert_called_once_with( "cluster-example-a-111111111111-workers", "magnum-fakeproject" ) mock_update.assert_called_once_with( self.cluster_obj, mock.ANY, driver.NodeGroupState.READY ) @mock.patch.object(driver.Driver, "_update_nodegroup_status") @mock.patch.object(kubernetes.Client, "load") def test_update_control_plane_nodegroup_status_empty( self, mock_load, mock_update ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "masters" mock_client.get_k8s_control_plane.return_value = None self.driver._update_control_plane_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_k8s_control_plane.assert_called_once_with( "cluster-example-a-111111111111-control-plane", "magnum-fakeproject", ) mock_update.assert_called_once_with( self.cluster_obj, mock.ANY, driver.NodeGroupState.NOT_PRESENT ) @mock.patch.object(driver.Driver, "_update_nodegroup_status") @mock.patch.object(kubernetes.Client, "load") def test_update_control_plane_nodegroup_status_condition_false( self, mock_load, mock_update ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "masters" kcp = { "spec": { "replicas": 3, }, "status": { "conditions": [ {"type": "MachinesReady", "status": "True"}, {"type": "Ready", "status": "True"}, {"type": "EtcdClusterHealthy", "status": "True"}, { "type": "ControlPlaneComponentsHealthy", "status": "False", }, ], "replicas": 3, "updatedReplicas": 3, "readyReplicas": 3, }, } mock_client.get_k8s_control_plane.return_value = kcp self.driver._update_control_plane_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_k8s_control_plane.assert_called_once_with( "cluster-example-a-111111111111-control-plane", "magnum-fakeproject", ) mock_update.assert_called_once_with( self.cluster_obj, mock.ANY, driver.NodeGroupState.PENDING ) @mock.patch.object(driver.Driver, "_update_nodegroup_status") @mock.patch.object(kubernetes.Client, "load") def test_update_control_plane_nodegroup_status_mismatched_replicas( self, mock_load, mock_update ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "masters" kcp = { "spec": { "replicas": 3, }, "status": { "conditions": [ {"type": "MachinesReady", "status": "True"}, {"type": "Ready", "status": "True"}, {"type": "EtcdClusterHealthy", "status": "True"}, { "type": "ControlPlaneComponentsHealthy", "status": "True", }, ], "replicas": 3, "updatedReplicas": 2, "readyReplicas": 2, }, } mock_client.get_k8s_control_plane.return_value = kcp self.driver._update_control_plane_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_k8s_control_plane.assert_called_once_with( "cluster-example-a-111111111111-control-plane", "magnum-fakeproject", ) mock_update.assert_called_once_with( self.cluster_obj, mock.ANY, driver.NodeGroupState.PENDING ) @mock.patch.object(driver.Driver, "_update_nodegroup_status") @mock.patch.object(kubernetes.Client, "load") def test_update_control_plane_nodegroup_status_ready( self, mock_load, mock_update ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client nodegroup = mock.MagicMock() nodegroup.name = "masters" kcp = { "spec": { "replicas": 3, }, "status": { "conditions": [ {"type": "MachinesReady", "status": "True"}, {"type": "Ready", "status": "True"}, {"type": "EtcdClusterHealthy", "status": "True"}, { "type": "ControlPlaneComponentsHealthy", "status": "True", }, ], "replicas": 3, "updatedReplicas": 3, "readyReplicas": 3, }, } mock_client.get_k8s_control_plane.return_value = kcp self.driver._update_control_plane_nodegroup_status( self.cluster_obj, nodegroup ) mock_client.get_k8s_control_plane.assert_called_once_with( "cluster-example-a-111111111111-control-plane", "magnum-fakeproject", ) mock_update.assert_called_once_with( self.cluster_obj, mock.ANY, driver.NodeGroupState.READY ) @mock.patch.object(kubernetes.Client, "load") def test_nodegroup_machines_exist(self, mock_load): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client mock_client.get_all_machines_by_label.return_value = ["item1"] nodegroup = obj_utils.create_test_nodegroup(self.context) result = self.driver._nodegroup_machines_exist( self.cluster_obj, nodegroup ) self.assertTrue(result) mock_client.get_all_machines_by_label.assert_called_once_with( { "capi.stackhpc.com/cluster": "cluster-example-a-111111111111", "capi.stackhpc.com/component": "worker", "capi.stackhpc.com/node-group": "nodegroup1", }, "magnum-fakeproject", ) @mock.patch.object(capi_monitor, "CAPIMonitor") def test_get_monitor(self, mock_mon): self.driver.get_monitor(self.context, self.cluster_obj) mock_mon.assert_called_once_with(self.context, self.cluster_obj) @mock.patch.object(kubernetes.Client, "load") def test_get_capi_cluster(self, mock_load): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.driver._get_capi_cluster(self.cluster_obj) mock_client.get_capi_cluster.assert_called_once_with( "cluster-example-a-111111111111", "magnum-fakeproject" ) @mock.patch.object(app_creds, "delete_app_cred") @mock.patch.object(kubernetes.Client, "load") @mock.patch.object(driver.Driver, "_get_app_cred_id") def test_update_status_deleting(self, mock_get, mock_load, mock_delete): mock_client = mock.MagicMock(spec=kubernetes.Client) app_cred_id = "abc123" mock_get.return_value = app_cred_id mock_load.return_value = mock_client self.driver._update_status_deleting(self.context, self.cluster_obj) self.assertEqual("DELETE_COMPLETE", self.cluster_obj.status) mock_delete.assert_called_once_with(self.cluster_obj, app_cred_id) mock_client.delete_all_secrets_by_label.assert_called_once_with( "magnum.openstack.org/cluster-uuid", self.cluster_obj.uuid, "magnum-fakeproject", ) def test_update_status_updating_not_ready(self): self.cluster_obj.status = fields.ClusterStatus.CREATE_IN_PROGRESS capi_cluster = {} self.driver._update_status_updating(self.cluster_obj, capi_cluster) self.assertEqual( fields.ClusterStatus.CREATE_IN_PROGRESS, self.cluster_obj.status ) @mock.patch.object(kubernetes.Client, "load") def test_update_status_updating_condition_false(self, mock_load): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_client.get_addons_by_label.return_value = [] mock_load.return_value = mock_client self.cluster_obj.status = fields.ClusterStatus.CREATE_IN_PROGRESS capi_cluster = { "status": { "conditions": [ dict(type="InfrastructureReady", status="True"), dict(type="ControlPlaneReady", status="True"), dict(type="Ready", status="False"), ] } } self.driver._update_status_updating(self.cluster_obj, capi_cluster) self.assertEqual( fields.ClusterStatus.CREATE_IN_PROGRESS, self.cluster_obj.status ) @mock.patch.object(kubernetes.Client, "load") def test_update_status_updating_ready_created(self, mock_load): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_client.get_addons_by_label.return_value = [] mock_load.return_value = mock_client self.cluster_obj.status = fields.ClusterStatus.CREATE_IN_PROGRESS capi_cluster = { "status": { "conditions": [ dict(type="InfrastructureReady", status="True"), dict(type="ControlPlaneReady", status="True"), dict(type="Ready", status="True"), ] } } self.driver._update_status_updating(self.cluster_obj, capi_cluster) self.assertEqual( fields.ClusterStatus.CREATE_COMPLETE, self.cluster_obj.status ) @mock.patch.object(kubernetes.Client, "load") def test_update_status_updating_addons_unknown(self, mock_load): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_client.get_addons_by_label.return_value = [ { "metadata": {"name": "cni"}, "status": {}, }, { "metadata": {"name": "monitoring"}, "status": {}, }, ] mock_load.return_value = mock_client self.cluster_obj.status = fields.ClusterStatus.CREATE_IN_PROGRESS capi_cluster = { "status": { "conditions": [ dict(type="InfrastructureReady", status="True"), dict(type="ControlPlaneReady", status="True"), dict(type="Ready", status="True"), ] } } self.driver._update_status_updating(self.cluster_obj, capi_cluster) self.assertEqual( fields.ClusterStatus.CREATE_IN_PROGRESS, self.cluster_obj.status ) @mock.patch.object(kubernetes.Client, "load") def test_update_status_updating_addons_installing(self, mock_load): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_client.get_addons_by_label.return_value = [ { "metadata": {"name": "cni"}, "status": {"phase": "Deployed"}, }, { "metadata": {"name": "monitoring"}, "status": {"phase": "Installing"}, }, ] mock_load.return_value = mock_client self.cluster_obj.status = fields.ClusterStatus.CREATE_IN_PROGRESS capi_cluster = { "status": { "conditions": [ dict(type="InfrastructureReady", status="True"), dict(type="ControlPlaneReady", status="True"), dict(type="Ready", status="True"), ] } } self.driver._update_status_updating(self.cluster_obj, capi_cluster) self.assertEqual( fields.ClusterStatus.CREATE_IN_PROGRESS, self.cluster_obj.status ) @mock.patch.object(kubernetes.Client, "load") def test_update_status_updating_addons_deployed(self, mock_load): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_client.get_addons_by_label.return_value = [ { "metadata": {"name": "cni"}, "status": {"phase": "Deployed"}, }, { "metadata": {"name": "monitoring"}, "status": {"phase": "Deployed"}, }, ] mock_load.return_value = mock_client self.cluster_obj.status = fields.ClusterStatus.CREATE_IN_PROGRESS capi_cluster = { "status": { "conditions": [ dict(type="InfrastructureReady", status="True"), dict(type="ControlPlaneReady", status="True"), dict(type="Ready", status="True"), ] } } self.driver._update_status_updating(self.cluster_obj, capi_cluster) self.assertEqual( fields.ClusterStatus.CREATE_COMPLETE, self.cluster_obj.status ) @mock.patch.object(kubernetes.Client, "load") def test_update_status_updating_addons_failed(self, mock_load): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_client.get_addons_by_label.return_value = [ { "metadata": {"name": "cni"}, "status": {"phase": "Deployed"}, }, { "metadata": {"name": "monitoring"}, "status": {"phase": "Failed"}, }, ] mock_load.return_value = mock_client self.cluster_obj.status = fields.ClusterStatus.CREATE_IN_PROGRESS capi_cluster = { "status": { "conditions": [ dict(type="InfrastructureReady", status="True"), dict(type="ControlPlaneReady", status="True"), dict(type="Ready", status="True"), ] } } self.driver._update_status_updating(self.cluster_obj, capi_cluster) self.assertEqual( fields.ClusterStatus.CREATE_FAILED, self.cluster_obj.status ) @mock.patch.object(kubernetes.Client, "load") def test_update_status_updating_ready_updated(self, mock_load): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_client.get_addons_by_label.return_value = [] mock_load.return_value = mock_client self.cluster_obj.status = fields.ClusterStatus.UPDATE_IN_PROGRESS capi_cluster = { "status": { "conditions": [ dict(type="InfrastructureReady", status="True"), dict(type="ControlPlaneReady", status="True"), dict(type="Ready", status="True"), ] } } self.driver._update_status_updating(self.cluster_obj, capi_cluster) self.assertEqual( fields.ClusterStatus.UPDATE_COMPLETE, self.cluster_obj.status ) def test_update_cluster_api_address(self): capi_cluster = { "spec": {"controlPlaneEndpoint": {"host": "foo", "port": 6443}} } self.driver._update_cluster_api_address(self.cluster_obj, capi_cluster) self.assertEqual("https://foo:6443", self.cluster_obj.api_address) def test_update_cluster_api_address_skip(self): self.cluster_obj.api_address = "asdf" capi_cluster = {"spec": {"foo": "bar"}} self.driver._update_cluster_api_address(self.cluster_obj, capi_cluster) self.assertEqual("asdf", self.cluster_obj.api_address) def test_update_cluster_api_address_skip_on_delete(self): self.cluster_obj.status = fields.ClusterStatus.DELETE_IN_PROGRESS self.cluster_obj.api_address = "asdf" capi_cluster = { "spec": {"controlPlaneEndpoint": {"host": "foo", "port": 6443}} } self.driver._update_cluster_api_address(self.cluster_obj, capi_cluster) self.assertEqual("asdf", self.cluster_obj.api_address) def test_update_nodegroup_status_create_complete(self): nodegroup = obj_utils.create_test_nodegroup(self.context) nodegroup.status = fields.ClusterStatus.CREATE_IN_PROGRESS updated = self.driver._update_nodegroup_status( self.cluster_obj, nodegroup, driver.NodeGroupState.READY ) self.assertEqual(fields.ClusterStatus.CREATE_COMPLETE, updated.status) def test_update_nodegroup_status_update_complete(self): nodegroup = obj_utils.create_test_nodegroup(self.context) nodegroup.status = fields.ClusterStatus.UPDATE_IN_PROGRESS updated = self.driver._update_nodegroup_status( self.cluster_obj, nodegroup, driver.NodeGroupState.READY ) self.assertEqual(fields.ClusterStatus.UPDATE_COMPLETE, updated.status) def test_update_nodegroup_status_create_failed(self): nodegroup = obj_utils.create_test_nodegroup(self.context) nodegroup.status = fields.ClusterStatus.CREATE_IN_PROGRESS updated = self.driver._update_nodegroup_status( self.cluster_obj, nodegroup, driver.NodeGroupState.FAILED ) self.assertEqual(fields.ClusterStatus.CREATE_FAILED, updated.status) def test_update_nodegroup_status_update_failed(self): nodegroup = obj_utils.create_test_nodegroup(self.context) nodegroup.status = fields.ClusterStatus.UPDATE_IN_PROGRESS updated = self.driver._update_nodegroup_status( self.cluster_obj, nodegroup, driver.NodeGroupState.FAILED ) self.assertEqual(fields.ClusterStatus.UPDATE_FAILED, updated.status) def test_update_nodegroup_status_create_in_progress(self): nodegroup = obj_utils.create_test_nodegroup(self.context) nodegroup.status = fields.ClusterStatus.CREATE_IN_PROGRESS updated = self.driver._update_nodegroup_status( self.cluster_obj, nodegroup, driver.NodeGroupState.PENDING ) self.assertEqual( fields.ClusterStatus.CREATE_IN_PROGRESS, updated.status ) def test_update_nodegroup_status_delete_in_progress(self): nodegroup = obj_utils.create_test_nodegroup(self.context) nodegroup.status = fields.ClusterStatus.DELETE_IN_PROGRESS updated = self.driver._update_nodegroup_status( self.cluster_obj, nodegroup, driver.NodeGroupState.PENDING ) self.assertEqual( fields.ClusterStatus.DELETE_IN_PROGRESS, updated.status ) self.assertEqual(nodegroup.as_dict(), updated.as_dict()) def test_update_nodegroup_creating_but_not_found(self): nodegroup = obj_utils.create_test_nodegroup(self.context) nodegroup.status = fields.ClusterStatus.CREATE_IN_PROGRESS updated = self.driver._update_nodegroup_status( self.cluster_obj, nodegroup, driver.NodeGroupState.NOT_PRESENT ) self.assertEqual( fields.ClusterStatus.CREATE_IN_PROGRESS, updated.status ) def test_update_nodegroup_status_delete_return_none(self): nodegroup = obj_utils.create_test_nodegroup(self.context) nodegroup.status = fields.ClusterStatus.DELETE_IN_PROGRESS result = self.driver._update_nodegroup_status( self.cluster_obj, nodegroup, driver.NodeGroupState.NOT_PRESENT ) self.assertIsNone(result) def test_update_nodegroup_status_delete_non_default_destroy(self): nodegroup = mock.MagicMock() nodegroup.status = fields.ClusterStatus.DELETE_IN_PROGRESS nodegroup.is_default = False result = self.driver._update_nodegroup_status( self.cluster_obj, nodegroup, driver.NodeGroupState.NOT_PRESENT ) self.assertIsNone(result) nodegroup.destroy.assert_called_once_with() def test_update_nodegroup_status_delete_unexpected_state(self): nodegroup = obj_utils.create_test_nodegroup(self.context) nodegroup.status = fields.ClusterStatus.ROLLBACK_IN_PROGRESS updated = self.driver._update_nodegroup_status( self.cluster_obj, nodegroup, driver.NodeGroupState.NOT_PRESENT ) self.assertEqual( fields.ClusterStatus.ROLLBACK_IN_PROGRESS, updated.status ) self.assertEqual(nodegroup.as_dict(), updated.as_dict()) def test_namespace(self): self.cluster_obj.project_id = "123-456F" namespace = driver_utils.cluster_namespace(self.cluster_obj) self.assertEqual("magnum-123456f", namespace) def test_label_return_default(self): self.cluster_obj.labels = dict() self.cluster_obj.cluster_template.labels = dict() result = self.driver._label( self.cluster_obj, "monitoring_enabled", "bar" ) self.assertEqual("bar", result) def test_label_return_template(self): self.cluster_obj.cluster_template.labels = dict(monitoring_enabled=42) result = self.driver._label( self.cluster_obj, "monitoring_enabled", "bar" ) self.assertEqual("42", result) def test_label_return_cluster(self): self.cluster_obj.labels = dict(monitoring_enabled=41) self.cluster_obj.cluster_template.labels = dict(monitoring_enabled=42) result = self.driver._label( self.cluster_obj, "monitoring_enabled", "bar" ) self.assertEqual("41", result) def test_label_unregistered_key_raises(self): self.assertRaises( ValueError, self.driver._label, self.cluster_obj, "not_a_real_label", "default", ) def test_sanitized_name_no_suffix(self): self.assertEqual( "123-456fab", driver_utils.sanitized_name("123-456Fab") ) def test_sanitized_name_with_suffix(self): self.assertEqual( "123-456-fab-1-asdf", driver_utils.sanitized_name("123-456_Fab!!_1!!", "asdf"), ) self.assertEqual( "123-456-fab-1-asdf", driver_utils.sanitized_name("123-456_Fab-1", "asdf"), ) def test_get_kube_version_raises(self): mock_image = mock.Mock() mock_image.get.return_value = None mock_image.properties = {} mock_image.id = "myid" e = self.assertRaises( exception.MagnumException, self.driver._get_kube_version, mock_image, ) self.assertEqual( "Image myid does not have a kube_version property.", str(e) ) mock_image.get.assert_called_once_with("kube_version") def test_get_kube_version_works(self): mock_image = mock.Mock() mock_image.get.return_value = "v1.27.9" result = self.driver._get_kube_version(mock_image) self.assertEqual("1.27.9", result) mock_image.get.assert_called_once_with("kube_version") def test_get_kube_version_works_from_properties(self): # openstacksdk Image: dict.get() returns None but custom properties # are accessible via image.properties (unknown attrs packed there) mock_image = mock.Mock() mock_image.get.return_value = None mock_image.properties = {"kube_version": "v1.27.9"} result = self.driver._get_kube_version(mock_image) self.assertEqual("1.27.9", result) @mock.patch("magnum.common.clients.OpenStackClients", autospec=True) @mock.patch("magnum.api.utils.get_openstack_resource", autospec=True) def test_get_image_details_ubuntu(self, mock_get, mock_osc): mock_image = mock.Mock() image_metadata = { "os_distro": "ubuntu", "kube_version": "1.27.9", } def image_side_effect(arg): return image_metadata[arg] mock_image.get.side_effect = image_side_effect mock_image.id = "myid" mock_get.return_value = mock_image id, version, distro = self.driver._get_image_details( self.context, "myimagename" ) self.assertEqual("1.27.9", version) self.assertEqual("myid", id) self.assertEqual("ubuntu", distro) mock_image.get.assert_any_call("kube_version") mock_image.get.assert_any_call("os_distro") mock_get.assert_called_once_with(mock.ANY, "myimagename", "images") @mock.patch("magnum.common.clients.OpenStackClients", autospec=True) @mock.patch("magnum.api.utils.get_openstack_resource", autospec=True) def test_get_image_details_flatcar(self, mock_get, mock_osc): mock_image = mock.Mock() image_metadata = { "os_distro": "flatcar", "kube_version": "1.28.2", } def image_side_effect(arg): return image_metadata[arg] mock_image.get.side_effect = image_side_effect mock_image.id = "myid-flatcar" mock_get.return_value = mock_image id, version, distro = self.driver._get_image_details( self.context, "myimagename" ) self.assertEqual("1.28.2", version) self.assertEqual("myid-flatcar", id) self.assertEqual("flatcar", distro) mock_image.get.assert_any_call("kube_version") mock_image.get.assert_any_call("os_distro") mock_get.assert_called_once_with(mock.ANY, "myimagename", "images") @mock.patch("magnum.common.clients.OpenStackClients", autospec=True) def test_get_image_details_ubuntu_openstacksdk(self, mock_osc): # Simulate real openstacksdk Image: dict.get() returns None because # Resource(dict) stores data in _body not the dict storage. Known # fields (os_distro) are attributes; unknown fields (kube_version) # are in image.properties. mock_image = mock.Mock() mock_image.get.return_value = None mock_image.os_distro = "ubuntu" mock_image.properties = {"kube_version": "1.27.9"} mock_image.id = "myid" mock_glance = mock.MagicMock() mock_glance.images = mock.Mock(spec=[]) # no 'get' → openstacksdk path mock_glance.find_image.return_value = mock_image mock_osc.return_value.glance.return_value = mock_glance id, version, distro = self.driver._get_image_details( self.context, "myimagename" ) self.assertEqual("1.27.9", version) self.assertEqual("myid", id) self.assertEqual("ubuntu", distro) mock_glance.find_image.assert_called_once_with("myimagename") @mock.patch("magnum.common.clients.OpenStackClients", autospec=True) def test_get_image_details_flatcar_openstacksdk(self, mock_osc): # Simulate real openstacksdk Image: dict.get() returns None because # Resource(dict) stores data in _body not the dict storage. Known # fields (os_distro) are attributes; unknown fields (kube_version) # are in image.properties. mock_image = mock.Mock() mock_image.get.return_value = None mock_image.os_distro = "flatcar" mock_image.properties = {"kube_version": "1.28.2"} mock_image.id = "myid-flatcar" mock_glance = mock.MagicMock() mock_glance.images = mock.Mock(spec=[]) # no 'get' → openstacksdk path mock_glance.find_image.return_value = mock_image mock_osc.return_value.glance.return_value = mock_glance id, version, distro = self.driver._get_image_details( self.context, "myimagename" ) self.assertEqual("1.28.2", version) self.assertEqual("myid-flatcar", id) self.assertEqual("flatcar", distro) mock_glance.find_image.assert_called_once_with("myimagename") def test_get_monitoring_enabled_from_template(self): # Check default if label is not present self.assertFalse(self.driver._get_monitoring_enabled(self.cluster_obj)) for val in ["true", "True", "TRUE"]: self.cluster_obj.cluster_template.labels["monitoring_enabled"] = ( val ) result = self.driver._get_monitoring_enabled(self.cluster_obj) self.assertTrue(result) def test_get_kube_dash_enabled_from_template(self): # Check default if label is not present self.assertTrue(self.driver._get_kube_dash_enabled(self.cluster_obj)) for val in ["false", "False", "FALSE"]: self.cluster_obj.cluster_template.labels[ "kube_dashboard_enabled" ] = val result = self.driver._get_kube_dash_enabled(self.cluster_obj) self.assertFalse(result) def test_get_chart_version_from_config(self): version = self.driver._get_chart_version(self.cluster_obj) self.assertEqual(CONF.capi_helm.default_helm_chart_version, version) def test_get_chart_version_from_template(self): self.cluster_obj.cluster_template.labels["capi_helm_chart_version"] = ( "1.42.0" ) version = self.driver._get_chart_version(self.cluster_obj) self.assertEqual("1.42.0", version) def _get_cluster_helm_standard_values(self): """Return standard helm values which can be modified for tests. There is little point in multiple tests writing the same dictionary that contains all aspects of a cluster when the side effect they are testing is limited to boot volumes, or keypairs. """ app_cred_name = "cluster-example-a-111111111111-cloud-credentials" ext_net_id = self.cluster_obj.cluster_template.external_network_id return { "kubernetesVersion": "1.27.4", "machineImageId": "imageid1", "cloudCredentialsSecretName": app_cred_name, "clusterNetworking": { "externalNetworkId": ext_net_id, "internalNetwork": { "networkFilter": None, "subnetFilter": None, "nodeCidr": "10.0.0.0/24", }, "dnsNameservers": ["8.8.1.1"], }, "etcd": {}, "apiServer": { "associateFloatingIP": True, "enableLoadBalancer": True, "loadBalancerProvider": "amphora", }, "controlPlane": { "machineFlavor": "flavor_small", "machineCount": 3, "healthCheck": {"enabled": True}, }, "addons": { "monitoring": {"enabled": False}, "kubernetesDashboard": {"enabled": True}, "ingress": {"enabled": False}, "openstack": { "csiCinder": mock.ANY, "cloudConfig": { "LoadBalancer": { "lb-provider": "amphora", "lb-method": "ROUND_ROBIN", "create-monitor": True, }, }, }, }, "nodeGroups": [ { "name": "test-worker", "machineFlavor": "flavor_medium", "machineCount": 3, }, ], "osDistro": "ubuntu", "nodeGroupDefaults": { "healthCheck": {"enabled": True}, }, "machineSSHKeyName": None, } @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client mock_get_net.side_effect = ( lambda c, net, source, target, external: f"{net}-{external}" ) self.driver.create_cluster(self.context, self.cluster_obj, 10) expected_values = self._get_cluster_helm_standard_values() mock_install.assert_called_once_with( self.driver._helm_client, "cluster-example-a-111111111111", "openstack-cluster", mock.ANY, # NOTE(dalees): Compared separately for improved diff repo=CONF.capi_helm.helm_chart_repo, version=CONF.capi_helm.default_helm_chart_version, namespace="magnum-fakeproject", ) helm_install_values = mock_install.call_args[0][3] self.assertDictEqual(helm_install_values, expected_values) mock_client.ensure_namespace.assert_called_once_with( "magnum-fakeproject" ) mock_appcred.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_called_once_with( self.driver, self.context, self.cluster_obj ) self.assertEqual([], mock_get_net.call_args_list) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_no_dns( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ("imageid1", "1.27.4", "ubuntu") mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.cluster_obj.cluster_template.dns_nameserver = "" self.driver.create_cluster(self.context, self.cluster_obj, 10) expected_values = self._get_cluster_helm_standard_values() expected_values["clusterNetworking"]["dnsNameservers"] = None mock_install.assert_called_once_with( self.driver._helm_client, "cluster-example-a-111111111111", "openstack-cluster", mock.ANY, repo=CONF.capi_helm.helm_chart_repo, version=CONF.capi_helm.default_helm_chart_version, namespace="magnum-fakeproject", ) helm_install_values = mock_install.call_args[0][3] self.assertDictEqual(helm_install_values, expected_values) mock_client.ensure_namespace.assert_called_once_with( "magnum-fakeproject" ) mock_appcred.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_called_once_with( self.driver, self.context, self.cluster_obj ) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_boot_volume( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ("imageid1", "1.27.4", "ubuntu") mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client CONF.cinder.default_boot_volume_type = "nvme" CONF.cinder.default_boot_volume_size = 12 self.driver.create_cluster(self.context, self.cluster_obj, 10) # Get standard values and modify them to match this test expected_values = self._get_cluster_helm_standard_values() expected_values["controlPlane"]["machineRootVolume"] = { "volumeType": "nvme", "diskSize": 12, } expected_values["nodeGroupDefaults"]["machineRootVolume"] = { "volumeType": "nvme", "diskSize": 12, } mock_install.assert_called_once_with( self.driver._helm_client, "cluster-example-a-111111111111", "openstack-cluster", mock.ANY, repo=CONF.capi_helm.helm_chart_repo, version=CONF.capi_helm.default_helm_chart_version, namespace="magnum-fakeproject", ) helm_install_values = mock_install.call_args[0][3] self.assertDictEqual(helm_install_values, expected_values) mock_client.ensure_namespace.assert_called_once_with( "magnum-fakeproject" ) mock_appcred.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_called_once_with( self.driver, self.context, self.cluster_obj ) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_boot_volume_extra_networks( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client CONF.cinder.default_boot_volume_type = "nvme" CONF.cinder.default_boot_volume_size = 12 # Driver should combine boot volume with extra network. self.cluster_obj.cluster_template.labels["extra_network_names"] = ( "foo bar" ) self.driver.create_cluster(self.context, self.cluster_obj, 10) expected_values = self._get_cluster_helm_standard_values() expected_values["controlPlane"]["machineRootVolume"] = { "volumeType": "nvme", "diskSize": 12, } expected_values["nodeGroupDefaults"]["machineRootVolume"] = { "volumeType": "nvme", "diskSize": 12, } expected_values["nodeGroupDefaults"]["machineNetworking"] = { "ports": [ {}, { "network": { "name": "foo", }, "securityGroups": [], }, { "network": { "name": "bar", }, "securityGroups": [], }, ], } mock_install.assert_called_once_with( self.driver._helm_client, "cluster-example-a-111111111111", "openstack-cluster", mock.ANY, repo=CONF.capi_helm.helm_chart_repo, version=CONF.capi_helm.default_helm_chart_version, namespace="magnum-fakeproject", ) helm_install_values = mock_install.call_args[0][3] self.assertDictEqual(helm_install_values, expected_values) mock_client.ensure_namespace.assert_called_once_with( "magnum-fakeproject" ) mock_appcred.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_called_once_with( self.driver, self.context, self.cluster_obj ) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(driver.Driver, "_ensure_certificate_secrets") @mock.patch.object(driver.Driver, "_create_appcred_secret") @mock.patch.object(kubernetes.Client, "load") @mock.patch.object(driver.Driver, "_get_image_details") @mock.patch.object(helm.Client, "install_or_upgrade") def test_create_cluster_with_keypair( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.cluster_obj.keypair = "kp1" self.driver.create_cluster(self.context, self.cluster_obj, 10) expected_values = self._get_cluster_helm_standard_values() expected_values["machineSSHKeyName"] = "kp1" mock_install.assert_called_once_with( "cluster-example-a-111111111111", "openstack-cluster", mock.ANY, repo=CONF.capi_helm.helm_chart_repo, version=CONF.capi_helm.default_helm_chart_version, namespace="magnum-fakeproject", ) helm_install_values = mock_install.call_args[0][2] self.assertDictEqual(helm_install_values, expected_values) mock_client.ensure_namespace.assert_called_once_with( "magnum-fakeproject" ) mock_appcred.assert_called_once_with(self.context, self.cluster_obj) mock_certs.assert_called_once_with(self.context, self.cluster_obj) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(driver.Driver, "_ensure_certificate_secrets") @mock.patch.object(driver.Driver, "_create_appcred_secret") @mock.patch.object(kubernetes.Client, "load") @mock.patch.object(driver.Driver, "_get_image_details") @mock.patch.object(helm.Client, "install_or_upgrade") def test_create_cluster_flatcar( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ( "imageid1", "1.27.4", "flatcar", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.driver.create_cluster(self.context, self.cluster_obj, 10) expected_values = self._get_cluster_helm_standard_values() expected_values["osDistro"] = "flatcar" mock_install.assert_called_once_with( "cluster-example-a-111111111111", "openstack-cluster", expected_values, repo=CONF.capi_helm.helm_chart_repo, version=CONF.capi_helm.default_helm_chart_version, namespace="magnum-fakeproject", ) mock_client.ensure_namespace.assert_called_once_with( "magnum-fakeproject" ) mock_appcred.assert_called_once_with(self.context, self.cluster_obj) mock_certs.assert_called_once_with(self.context, self.cluster_obj) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_no_autoheal( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ("imageid1", "1.27.4", "ubuntu") mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.cluster_obj.cluster_template.labels["auto_healing_enabled"] = ( "false" ) self.driver.create_cluster(self.context, self.cluster_obj, 10) # Get standard values and modify them to match this test expected_values = self._get_cluster_helm_standard_values() expected_values["controlPlane"]["healthCheck"]["enabled"] = False expected_values["nodeGroupDefaults"]["healthCheck"]["enabled"] = False mock_install.assert_called_once_with( self.driver._helm_client, "cluster-example-a-111111111111", "openstack-cluster", mock.ANY, repo=CONF.capi_helm.helm_chart_repo, version=CONF.capi_helm.default_helm_chart_version, namespace="magnum-fakeproject", ) helm_install_values = mock_install.call_args[0][3] self.assertDictEqual(helm_install_values, expected_values) mock_client.ensure_namespace.assert_called_once_with( "magnum-fakeproject" ) mock_appcred.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_called_once_with( self.driver, self.context, self.cluster_obj ) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_etcd_block_device( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ("imageid1", "1.27.4", "ubuntu") mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.cluster_obj.cluster_template.labels.update( { "etcd_blockdevice_size": "10", "etcd_blockdevice_volume_type": "nvme", } ) self.driver.create_cluster(self.context, self.cluster_obj, 10) # Get standard values and modify them to match this test expected_values = self._get_cluster_helm_standard_values() expected_values["etcd"] = { "blockDevice": { "size": 10, "type": "Volume", "volumeType": "nvme", } } mock_install.assert_called_once_with( self.driver._helm_client, "cluster-example-a-111111111111", "openstack-cluster", mock.ANY, repo=CONF.capi_helm.helm_chart_repo, version=CONF.capi_helm.default_helm_chart_version, namespace="magnum-fakeproject", ) helm_install_values = mock_install.call_args[0][3] self.assertDictEqual(helm_install_values, expected_values) mock_client.ensure_namespace.assert_called_once_with( "magnum-fakeproject" ) mock_appcred.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_called_once_with( self.driver, self.context, self.cluster_obj ) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_etcd_block_device_local( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ("imageid1", "1.27.4", "ubuntu") mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.cluster_obj.cluster_template.labels.update( { "etcd_blockdevice_size": "10", "etcd_blockdevice_type": "local", } ) self.driver.create_cluster(self.context, self.cluster_obj, 10) # Get standard values and modify them to match this test expected_values = self._get_cluster_helm_standard_values() expected_values["etcd"] = { "blockDevice": { "size": 10, "type": "Local", } } mock_install.assert_called_once_with( self.driver._helm_client, "cluster-example-a-111111111111", "openstack-cluster", mock.ANY, repo=CONF.capi_helm.helm_chart_repo, version=CONF.capi_helm.default_helm_chart_version, namespace="magnum-fakeproject", ) helm_install_values = mock_install.call_args[0][3] self.assertDictEqual(helm_install_values, expected_values) mock_client.ensure_namespace.assert_called_once_with( "magnum-fakeproject" ) mock_appcred.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_called_once_with( self.driver, self.context, self.cluster_obj ) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_etcd_block_device_legacy_labels( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ("imageid1", "1.27.4", "ubuntu") mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client # Test the legacy labels for etcd volume size self.cluster_obj.cluster_template.labels.update( { "etcd_volume_size": "10", "etcd_volume_type": "nvme", } ) self.driver.create_cluster(self.context, self.cluster_obj, 10) # Get standard values and modify them to match this test expected_values = self._get_cluster_helm_standard_values() expected_values["etcd"] = { "blockDevice": { "size": 10, "type": "Volume", "volumeType": "nvme", } } mock_install.assert_called_once_with( self.driver._helm_client, "cluster-example-a-111111111111", "openstack-cluster", mock.ANY, repo=CONF.capi_helm.helm_chart_repo, version=CONF.capi_helm.default_helm_chart_version, namespace="magnum-fakeproject", ) helm_install_values = mock_install.call_args[0][3] self.assertDictEqual(helm_install_values, expected_values) mock_client.ensure_namespace.assert_called_once_with( "magnum-fakeproject" ) mock_appcred.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_called_once_with( self.driver, self.context, self.cluster_obj ) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_network_driver( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, mock_get_allowed_cidrs, ): mock_image.return_value = ("imageid1", "1.27.4", "ubuntu") mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.cluster_obj.cluster_template.network_driver = "cilium" self.driver.create_cluster(self.context, self.cluster_obj, 10) expected_values = self._get_cluster_helm_standard_values() expected_values["addons"]["cni"] = {"type": "cilium"} helm_install_values = mock_install.call_args[0][3] self.assertDictEqual(helm_install_values, expected_values) @mock.patch.object(app_creds, "create_app_cred") @mock.patch.object(app_creds, "get_app_cred_string_data") @mock.patch.object(kubernetes.Client, "load") def test_create_appcred_secret(self, mock_load, mock_sd, mock_create): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client mock_sd.return_value = {"cacert": "ca", "clouds.yaml": "appcred"} self.driver._create_appcred_secret(self.context, self.cluster_obj) uuid = self.cluster_obj.uuid name = "cluster-example-a-111111111111" mock_client.apply_secret.assert_called_once_with( "cluster-example-a-111111111111-cloud-credentials", { "metadata": { "labels": { "magnum.openstack.org/project-id": "fake_project", "magnum.openstack.org/user-id": "fake_user", "magnum.openstack.org/cluster-uuid": uuid, "cluster.x-k8s.io/cluster-name": name, } }, "stringData": {"cacert": "ca", "clouds.yaml": "appcred"}, }, "magnum-fakeproject", ) @mock.patch.object(ca_certificates, "get_certificate_string_data") @mock.patch.object(driver.Driver, "_k8s_resource_labels") @mock.patch.object(kubernetes.Client, "load") def test_ensure_certificate_secrets( self, mock_load, mock_labels, mock_string_data ): mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client mock_labels.return_value = dict(foo="bar") mock_string_data.return_value = { "ca": {"tls.crt": "cert1", "tls.key": "key1"}, "proxy": {"tls.crt": "cert2", "tls.key": "key2"}, } self.driver._ensure_certificate_secrets(self.context, self.cluster_obj) mock_client.apply_secret.assert_has_calls( [ mock.call( "cluster-example-a-111111111111-ca", { "metadata": {"labels": {"foo": "bar"}}, "type": "cluster.x-k8s.io/secret", "stringData": {"tls.crt": "cert1", "tls.key": "key1"}, }, "magnum-fakeproject", ), mock.call( "cluster-example-a-111111111111-proxy", { "metadata": {"labels": {"foo": "bar"}}, "type": "cluster.x-k8s.io/secret", "stringData": {"tls.crt": "cert2", "tls.key": "key2"}, }, "magnum-fakeproject", ), ] ) mock_string_data.assert_called_once_with( self.context, self.cluster_obj ) mock_labels.assert_called_with(self.cluster_obj) @mock.patch("magnum.common.clients.OpenStackClients.cinder") def test_get_storage_classes(self, mock_cinder): CONF.capi_helm.csi_cinder_default_volume_type = "type3" CONF.capi_helm_cluster_labels.csi_cinder_availability_zone = ( "middle_earth_east" ) mock_vol_type_1 = mock.MagicMock() mock_vol_type_1.name = "type1" mock_vol_type_2 = mock.MagicMock() mock_vol_type_2.name = "type2" mock_vol_type_3 = mock.MagicMock() mock_vol_type_3.name = "type3" mock_volume_types = mock.Mock() mock_volume_types.list.return_value = [ mock_vol_type_1, mock_vol_type_2, mock_vol_type_3, ] mock_cinder_client = mock.Mock() mock_cinder_client.volume_types = mock_volume_types mock_cinder.return_value = mock_cinder_client storage_classes = self.driver._storageclass_definitions( self.context, self.cluster_obj ) self.assertIsInstance(storage_classes, dict) self.assertIsInstance(storage_classes["defaultStorageClass"], dict) self.assertIsInstance( storage_classes["additionalStorageClasses"], list ) self.assertEqual( "type3", storage_classes["defaultStorageClass"]["volumeType"] ) self.assertEqual( "middle_earth_east", storage_classes["additionalStorageClasses"][0]["availabilityZone"], ) @mock.patch("magnum.common.clients.OpenStackClients.cinder") def test_get_storage_class_volume_type_not_available(self, mock_cinder): CONF.capi_helm.csi_cinder_default_volume_type = "type4" CONF.capi_helm_cluster_labels.csi_cinder_availability_zone = ( "middle_earth_east" ) mock_vol_type_1 = mock.MagicMock() mock_vol_type_1.name = "type1" mock_vol_type_2 = mock.MagicMock() mock_vol_type_2.name = "type2" mock_vol_type_3 = mock.MagicMock() mock_vol_type_3.name = "type3" mock_volume_types = mock.Mock() mock_volume_types.list.return_value = [ mock_vol_type_1, mock_vol_type_2, mock_vol_type_3, ] mock_cinder_client = mock.Mock() mock_cinder_client.volume_types = mock_volume_types mock_cinder.return_value = mock_cinder_client self.assertRaisesRegex( exception.MagnumException, r"not\sa\svalid\sCinder", self.driver._storageclass_definitions, self.context, self.cluster_obj, ) @mock.patch("magnum.common.clients.OpenStackClients.cinder") def test_get_storage_class_volume_type_not_defined(self, mock_cinder): CONF.capi_helm.csi_cinder_default_volume_type = None CONF.capi_helm_cluster_labels.csi_cinder_availability_zone = ( "middle_earth_east" ) mock_vol_type_1 = mock.MagicMock() mock_vol_type_1.name = "__TYPE1__" mock_vol_type_2 = mock.MagicMock() mock_vol_type_2.name = "type2" mock_vol_type_3 = mock.MagicMock() mock_vol_type_3.name = "type3" mock_volume_types = mock.Mock() mock_volume_types.list.return_value = [ mock_vol_type_1, mock_vol_type_2, mock_vol_type_3, ] mock_cinder_client = mock.Mock() mock_cinder_client.volume_types = mock_volume_types mock_cinder.return_value = mock_cinder_client storage_classes = self.driver._storageclass_definitions( self.context, self.cluster_obj ) default_storage_class = storage_classes["defaultStorageClass"] volume_type = default_storage_class["name"] self.assertEqual("type1", volume_type) @mock.patch("magnum.common.clients.OpenStackClients.cinder") def test_get_storage_classes_openstacksdk(self, mock_cinder): CONF.capi_helm.csi_cinder_default_volume_type = "type3" CONF.capi_helm_cluster_labels.csi_cinder_availability_zone = ( "middle_earth_east" ) mock_vol_type_1 = mock.MagicMock() mock_vol_type_1.name = "type1" mock_vol_type_2 = mock.MagicMock() mock_vol_type_2.name = "type2" mock_vol_type_3 = mock.MagicMock() mock_vol_type_3.name = "type3" # Simulate openstacksdk Block Storage Proxy: no volume_types attribute; # volume types are listed via c_client.types() mock_cinder_client = mock.Mock(spec=["types"]) mock_cinder_client.types.return_value = [ mock_vol_type_1, mock_vol_type_2, mock_vol_type_3, ] mock_cinder.return_value = mock_cinder_client storage_classes = self.driver._storageclass_definitions( self.context, self.cluster_obj ) self.assertIsInstance(storage_classes, dict) self.assertIsInstance(storage_classes["defaultStorageClass"], dict) self.assertIsInstance( storage_classes["additionalStorageClasses"], list ) self.assertEqual( "type3", storage_classes["defaultStorageClass"]["volumeType"] ) self.assertEqual( "middle_earth_east", storage_classes["additionalStorageClasses"][0]["availabilityZone"], ) @mock.patch("magnum.common.clients.OpenStackClients.cinder") def test_get_storage_class_volume_type_not_available_openstacksdk( self, mock_cinder ): CONF.capi_helm.csi_cinder_default_volume_type = "type4" CONF.capi_helm_cluster_labels.csi_cinder_availability_zone = ( "middle_earth_east" ) mock_vol_type_1 = mock.MagicMock() mock_vol_type_1.name = "type1" mock_vol_type_2 = mock.MagicMock() mock_vol_type_2.name = "type2" mock_vol_type_3 = mock.MagicMock() mock_vol_type_3.name = "type3" # Simulate openstacksdk Block Storage Proxy: no volume_types attribute; # volume types are listed via c_client.types() mock_cinder_client = mock.Mock(spec=["types"]) mock_cinder_client.types.return_value = [ mock_vol_type_1, mock_vol_type_2, mock_vol_type_3, ] mock_cinder.return_value = mock_cinder_client self.assertRaisesRegex( exception.MagnumException, r"not\sa\svalid\sCinder", self.driver._storageclass_definitions, self.context, self.cluster_obj, ) @mock.patch("magnum.common.clients.OpenStackClients.cinder") def test_get_storage_class_volume_type_not_defined_openstacksdk( self, mock_cinder ): CONF.capi_helm.csi_cinder_default_volume_type = None CONF.capi_helm_cluster_labels.csi_cinder_availability_zone = ( "middle_earth_east" ) mock_vol_type_1 = mock.MagicMock() mock_vol_type_1.name = "__TYPE1__" mock_vol_type_2 = mock.MagicMock() mock_vol_type_2.name = "type2" mock_vol_type_3 = mock.MagicMock() mock_vol_type_3.name = "type3" # Simulate openstacksdk Block Storage Proxy: no volume_types attribute; # volume types are listed via c_client.types() mock_cinder_client = mock.Mock(spec=["types"]) mock_cinder_client.types.return_value = [ mock_vol_type_1, mock_vol_type_2, mock_vol_type_3, ] mock_cinder.return_value = mock_cinder_client storage_classes = self.driver._storageclass_definitions( self.context, self.cluster_obj ) default_storage_class = storage_classes["defaultStorageClass"] volume_type = default_storage_class["name"] self.assertEqual("type1", volume_type) @mock.patch.object(helm.Client, "uninstall_release") def test_delete_cluster(self, mock_uninstall): self.driver.delete_cluster(self.context, self.cluster_obj) mock_uninstall.assert_called_once_with( "cluster-example-a-111111111111", namespace="magnum-fakeproject" ) @mock.patch.object(driver.Driver, "_update_helm_release") def test_update_cluster(self, mock_update): self.driver.update_cluster(self.context, self.cluster_obj) mock_update.assert_called_once_with(self.context, self.cluster_obj) @mock.patch.object(driver.Driver, "_update_helm_release") def test_resize_cluster(self, mock_update): self.driver.resize_cluster( self.context, self.cluster_obj, None, None, None, ) mock_update.assert_called_once_with(self.context, self.cluster_obj) @mock.patch.object(driver.Driver, "_update_helm_release") def test_resize_cluster_ignore_nodes_to_remove(self, mock_update): self.driver.resize_cluster( self.context, self.cluster_obj, None, ["node1"], None, ) mock_update.assert_called_once_with(self.context, self.cluster_obj) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(driver.Driver, "_update_helm_release") def test_upgrade_cluster( self, mock_update, mock_validate_allowed_flavor, ): node_group = mock.MagicMock() mock_template = mock.MagicMock() mock_template.uuid = "foo" self.driver.upgrade_cluster( self.context, self.cluster_obj, mock_template, 1, node_group, ) # TODO(johngarbutt) improve the testing mock_update.assert_called_once_with(self.context, self.cluster_obj) self.assertEqual("UPDATE_IN_PROGRESS", self.cluster_obj.status) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(driver.Driver, "_update_helm_release") def test_create_nodegroup( self, mock_update, mock_validate_allowed_flavor, ): node_group = mock.MagicMock() self.driver.create_nodegroup( self.context, self.cluster_obj, node_group ) mock_update.assert_called_once_with(self.context, self.cluster_obj) node_group.save.assert_called_once_with() self.assertEqual("CREATE_IN_PROGRESS", node_group.status) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(driver.Driver, "_update_helm_release") def test_update_nodegroup( self, mock_update, mock_validate_allowed_flavor, ): node_group = mock.MagicMock() self.driver.update_nodegroup( self.context, self.cluster_obj, node_group, ) mock_update.assert_called_once_with(self.context, self.cluster_obj) node_group.save.assert_called_once_with() self.assertEqual("UPDATE_IN_PROGRESS", node_group.status) @mock.patch.object(driver.Driver, "_get_allowed_cidrs") @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY ) @mock.patch.object( driver.Driver, "_get_image_details", return_value=3 * [mock.ANY] ) @mock.patch.object(helm.Client, "install_or_upgrade") def test_delete_nodegroup( self, mock_helm_update, mock_image_details, mock_storageclasses, mock_get_cidrs, ): ng_to_delete = next( ng for ng in self.cluster_obj.nodegroups if ng.role == "worker" ) self.assertTrue(ng_to_delete is not None) self.driver.delete_nodegroup( self.context, self.cluster_obj, ng_to_delete, ) # Check that node group has been removed from Helm values helm_values = mock_helm_update.call_args[0][2] remaining_nodegroups = [ng["name"] for ng in helm_values["nodeGroups"]] self.assertNotIn(ng_to_delete.name, remaining_nodegroups) def test_create_federation(self): self.assertRaises( NotImplementedError, self.driver.create_federation, self.context, None, ) def test_update_federation(self): self.assertRaises( NotImplementedError, self.driver.update_federation, self.context, None, ) def test_delete_federation(self): self.assertRaises( NotImplementedError, self.driver.delete_federation, self.context, None, ) @mock.patch("novaclient.v2.flavors.FlavorManager", autospec=True) @mock.patch("novaclient.v2.client.Client", autospec=2) @mock.patch("novaclient.client.Client") @mock.patch("magnum.common.clients.OpenStackClients.nova") def test_validate_allowed_flavors_ram_error( self, mock_osc_nova, mock_nova_client, mock_versioned_nova_client, mock_flavor_manager, ): mock_flavor1 = mock.MagicMock(id=1, vcpus=1) mock_flavor1.name = "flavor_tiny" mock_flavor2 = mock.MagicMock(id=2, vcpus=1) mock_flavor2.name = "flavor_small" mock_flavor3 = mock.MagicMock(id=3, vcpus=4) mock_flavor3.name = "flavor_medium" # Assumes that the list returned by novaclient.flavors.(min_ram=xxxx) # is already filtered so no need to check that. filtered_flavors = [ mock_flavor1, mock_flavor2, mock_flavor3, ] mock_flavor_manager.list.return_value = filtered_flavors mock_versioned_nova_client.flavors = mock_flavor_manager mock_osc_nova.return_value = mock_versioned_nova_client self.assertRaises( exception.MagnumException, self.driver._validate_allowed_flavor, self.context, "DS9", ) @mock.patch("novaclient.v2.flavors.FlavorManager", autospec=True) @mock.patch("novaclient.v2.client.Client", autospec=2) @mock.patch("novaclient.client.Client") @mock.patch("magnum.common.clients.OpenStackClients.nova") def test_validate_allowed_flavors_vcpu_error( self, mock_osc_nova, mock_nova_client, mock_versioned_nova_client, mock_flavor_manager, ): mock_flavor1 = mock.MagicMock(id=1, vcpus=1) mock_flavor1.name = "flavor_tiny" mock_flavor2 = mock.MagicMock(id=2, vcpus=1) mock_flavor2.name = "flavor_small" mock_flavor3 = mock.MagicMock(id=3, vcpus=4) mock_flavor3.name = "flavor_medium" # Assumes that the list returned by novaclient.flavors.(min_ram=xxxx) # is already filtered so no need to check that. filtered_flavors = [ mock_flavor1, mock_flavor2, mock_flavor3, ] mock_flavor_manager.list.return_value = filtered_flavors mock_versioned_nova_client.flavors = mock_flavor_manager mock_osc_nova.return_value = mock_versioned_nova_client self.assertRaises( exception.MagnumException, self.driver._validate_allowed_flavor, self.context, 2, ) self.assertRaises( exception.MagnumException, self.driver._validate_allowed_flavor, self.context, "flavor_small", ) @mock.patch("novaclient.v2.flavors.FlavorManager", autospec=True) @mock.patch("novaclient.v2.client.Client", autospec=2) @mock.patch("novaclient.client.Client") @mock.patch("magnum.common.clients.OpenStackClients.nova") def test_validate_allowed_flavors_ram_ok( self, mock_osc_nova, mock_nova_client, mock_versioned_nova_client, mock_flavor_manager, ): mock_flavor1 = mock.MagicMock(id=1, vcpus=1) mock_flavor1.name = "flavor_tiny" mock_flavor2 = mock.MagicMock(id=2, vcpus=1) mock_flavor2.name = "flavor_small" mock_flavor3 = mock.MagicMock(id=3, vcpus=4) mock_flavor3.name = "flavor_medium" filtered_flavors = [ mock_flavor1, mock_flavor2, mock_flavor3, ] mock_flavor_manager.list.return_value = filtered_flavors mock_versioned_nova_client.flavors = mock_flavor_manager mock_osc_nova.return_value = mock_versioned_nova_client try: self.driver._validate_allowed_flavor(self.context, 3) except Exception as e: self.fail("Raised exception %s" % e) try: self.driver._validate_allowed_flavor(self.context, "flavor_medium") except Exception as e: self.fail("Raised exception %s" % e) @mock.patch("magnum.common.clients.OpenStackClients.nova") def test_validate_allowed_flavors_ram_error_openstacksdk( self, mock_osc_nova ): mock_flavor1 = mock.MagicMock(id=1, vcpus=1) mock_flavor1.name = "flavor_tiny" mock_flavor2 = mock.MagicMock(id=2, vcpus=1) mock_flavor2.name = "flavor_small" mock_flavor3 = mock.MagicMock(id=3, vcpus=4) mock_flavor3.name = "flavor_medium" filtered_flavors = [mock_flavor1, mock_flavor2, mock_flavor3] mock_nova = mock.MagicMock() mock_flavors_sdk = mock.Mock(spec=[]) # no 'list' → openstacksdk path mock_flavors_sdk.return_value = filtered_flavors mock_nova.flavors = mock_flavors_sdk mock_osc_nova.return_value = mock_nova self.assertRaises( exception.MagnumException, self.driver._validate_allowed_flavor, self.context, "DS9", ) @mock.patch("magnum.common.clients.OpenStackClients.nova") def test_validate_allowed_flavors_vcpu_error_openstacksdk( self, mock_osc_nova ): mock_flavor1 = mock.MagicMock(id=1, vcpus=1) mock_flavor1.name = "flavor_tiny" mock_flavor2 = mock.MagicMock(id=2, vcpus=1) mock_flavor2.name = "flavor_small" mock_flavor3 = mock.MagicMock(id=3, vcpus=4) mock_flavor3.name = "flavor_medium" filtered_flavors = [mock_flavor1, mock_flavor2, mock_flavor3] mock_nova = mock.MagicMock() mock_flavors_sdk = mock.Mock(spec=[]) # no 'list' → openstacksdk path mock_flavors_sdk.return_value = filtered_flavors mock_nova.flavors = mock_flavors_sdk mock_osc_nova.return_value = mock_nova self.assertRaises( exception.MagnumException, self.driver._validate_allowed_flavor, self.context, 2, ) self.assertRaises( exception.MagnumException, self.driver._validate_allowed_flavor, self.context, "flavor_small", ) @mock.patch("magnum.common.clients.OpenStackClients.nova") def test_validate_allowed_flavors_ram_ok_openstacksdk(self, mock_osc_nova): mock_flavor1 = mock.MagicMock(id=1, vcpus=1) mock_flavor1.name = "flavor_tiny" mock_flavor2 = mock.MagicMock(id=2, vcpus=1) mock_flavor2.name = "flavor_small" mock_flavor3 = mock.MagicMock(id=3, vcpus=4) mock_flavor3.name = "flavor_medium" filtered_flavors = [mock_flavor1, mock_flavor2, mock_flavor3] mock_nova = mock.MagicMock() mock_flavors_sdk = mock.Mock(spec=[]) # no 'list' → openstacksdk path mock_flavors_sdk.return_value = filtered_flavors mock_nova.flavors = mock_flavors_sdk mock_osc_nova.return_value = mock_nova try: self.driver._validate_allowed_flavor(self.context, 3) except Exception as e: self.fail("Raised exception %s" % e) try: self.driver._validate_allowed_flavor(self.context, "flavor_medium") except Exception as e: self.fail("Raised exception %s" % e) @mock.patch("novaclient.v2.flavors.FlavorManager", autospec=True) @mock.patch("novaclient.v2.client.Client", autospec=2) @mock.patch("novaclient.client.Client") @mock.patch("magnum.common.clients.OpenStackClients.nova") def test_validate_upgrade_cluster_node_group( self, mock_osc_nova, mock_nova_client, mock_versioned_nova_client, mock_flavor_manager, ): mock_flavor1 = mock.MagicMock(id=1, vcpus=1) mock_flavor1.name = "flavor_tiny" mock_flavor2 = mock.MagicMock(id=2, vcpus=1) mock_flavor2.name = "flavor_small" mock_flavor3 = mock.MagicMock(id=3, vcpus=4) mock_flavor3.name = "flavor_medium" filtered_flavors = [ mock_flavor1, mock_flavor2, mock_flavor3, ] mock_flavor_manager.list.return_value = filtered_flavors mock_versioned_nova_client.flavors = mock_flavor_manager mock_osc_nova.return_value = mock_versioned_nova_client node_group = mock.MagicMock() node_group.flavor_id mock_template = mock.MagicMock() mock_template.uuid = "foo" for ng in self.cluster_obj.nodegroups: if ng.role != "master": ng.flavor_id = "flavor_small" ng.save() self.assertRaises( exception.MagnumException, self.driver.upgrade_cluster, self.context, self.cluster_obj, mock_template, 1, node_group, ) @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_api_lb_allowed_cidrs( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, ): cidrs = "192.168.0.0/16,10.0.0.0/8,123.123.123.123/32" self.cluster_obj.labels = dict(api_master_lb_allowed_cidrs=cidrs) mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.driver.create_cluster(self.context, self.cluster_obj, 10) cidr_list = cidrs.split(",") helm_install_values = mock_install.call_args[0][3] self.assertEqual( helm_install_values["apiServer"]["allowedCidrs"], cidr_list ) @mock.patch.object(driver.Driver, "_get_k8s_keystone_auth_enabled") @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_keystone_webhook_enabled( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, ): mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client mock_get_keystone_auth_enabled.return_value = True # Enable webhook self.cluster_obj.labels = {} self.driver.create_cluster( self.context, self.cluster_obj, "timeout-not-used" ) helm_install_values = mock_install.call_args[0][3] self.assertEqual( helm_install_values["authWebhook"], "k8s-keystone-auth" ) self.assertTrue( helm_install_values["addons"] .get( "openstack", {} ) # Default to {} so that next .get isn't called on None type .get("k8sKeystoneAuth", {}) .get("enabled") ) @mock.patch.object(driver.Driver, "_get_k8s_keystone_auth_enabled") @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_keystone_webhook_disabled( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, ): mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client mock_get_keystone_auth_enabled.return_value = False # Disable webhook self.cluster_obj.labels = {} self.driver.create_cluster( self.context, self.cluster_obj, "timeout-not-used" ) helm_install_values = mock_install.call_args[0][3] self.assertNotEqual( helm_install_values.get("authWebhook"), "k8s-keystone-auth" ) self.assertFalse( helm_install_values["addons"] .get("openstack", {}) .get("k8sKeystoneAuth", {}) .get("enabled") ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_octavia_provider_amphora( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, ): mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client # Simulate setting provider label self.cluster_obj.labels = { "octavia_provider": "amphora", "octavia_lb_algorithm": "SOURCE_IP_PORT", } self.driver.create_cluster( self.context, self.cluster_obj, "timeout-not-used" ) helm_install_values = mock_install.call_args[0][3] self.assertEqual( helm_install_values.get("apiServer", {}).get( "loadBalancerProvider" ), "amphora", ) self.assertEqual( helm_install_values["addons"] .get("openstack", {}) .get("cloudConfig", {}) .get("LoadBalancer", {}) .get("lb-provider"), "amphora", ) self.assertEqual( helm_install_values["addons"] .get("openstack", {}) .get("cloudConfig", {}) .get("LoadBalancer", {}) .get("lb-method"), "SOURCE_IP_PORT", ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_octavia_provider_ovn( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, ): mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client # Simulate setting provider label self.cluster_obj.labels = { "octavia_provider": "ovn", } self.driver.create_cluster( self.context, self.cluster_obj, "timeout-not-used" ) helm_install_values = mock_install.call_args[0][3] self.assertEqual( helm_install_values.get("apiServer", {}).get( "loadBalancerProvider" ), "ovn", ) self.assertEqual( helm_install_values["addons"] .get("openstack", {}) .get("cloudConfig", {}) .get("LoadBalancer", {}) .get("lb-provider"), "ovn", ) # Check algorithm default if no label provided self.assertEqual( helm_install_values["addons"] .get("openstack", {}) .get("cloudConfig", {}) .get("LoadBalancer", {}) .get("lb-method"), "SOURCE_IP_PORT", ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_octavia_disable_monitor( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, ): mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client # Simulate setting provider label self.cluster_obj.labels = { "octavia_lb_healthcheck": False, } self.driver.create_cluster( self.context, self.cluster_obj, "timeout-not-used" ) helm_install_values = mock_install.call_args[0][3] self.assertFalse( helm_install_values["addons"] .get("openstack", {}) .get("cloudConfig", {}) .get("LoadBalancer", {}) .get("create-monitor") ) def test_validate_auto_scale_max_lt_min(self): self.cluster_obj.labels = dict( auto_scaling_enabled="true", min_node_count=3, max_node_count=0 ) self.assertRaises( exception.MagnumException, self.driver._get_autoscale_values, self.cluster_obj, self.cluster_obj.nodegroups[0], ) @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_auto_scale_enabled( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, ): auto_scale_labels = dict( auto_scaling_enabled="true", min_node_count=2, max_node_count=6 ) self.cluster_obj.labels = auto_scale_labels mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.driver.create_cluster(self.context, self.cluster_obj, 10) helm_install_values = mock_install.call_args[0][3] self.assertEqual( helm_install_values["nodeGroups"][0]["autoscale"], auto_scale_labels["auto_scaling_enabled"], ) self.assertEqual( helm_install_values["nodeGroups"][0]["machineCountMin"], auto_scale_labels["min_node_count"], ) self.assertEqual( helm_install_values["nodeGroups"][0]["machineCountMax"], auto_scale_labels["max_node_count"], ) @mock.patch.object( driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_auto_scale_disabled( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, mock_get_keystone_auth_enabled, ): auto_scale_labels = dict( auto_scaling_enabled="false", min_node_count=2, max_node_count=6 ) self.cluster_obj.labels = auto_scale_labels mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.driver.create_cluster(self.context, self.cluster_obj, 10) helm_install_values = mock_install.call_args[0][3] self.assertNotIn( "autoscale", helm_install_values["nodeGroups"][0], ) self.assertNotIn( "machineCountMin", helm_install_values["nodeGroups"][0], ) self.assertNotIn( "machineCountMax", helm_install_values["nodeGroups"][0], ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_nodegroup_node_count_validation( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, ): auto_scale_labels = dict( auto_scaling_enabled="true", min_node_count=2, max_node_count=6 ) self.cluster_obj.labels = auto_scale_labels mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) # Create cluster with extra node groups ng = obj_utils.create_test_nodegroup( self.context, min_node_count=3, max_node_count=2 ) self.cluster_obj.nodegroups.append(ng) self.assertRaises( exception.NodeGroupInvalidInput, self.driver.create_cluster, self.context, self.cluster_obj, "not-used", ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_extra_nodegroup_auto_scale_values( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, ): auto_scale_labels = dict( auto_scaling_enabled="true", min_node_count=2, max_node_count=6 ) self.cluster_obj.labels = auto_scale_labels mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) # Create cluster with extra node groups non_auto_scale_nodegroup = obj_utils.create_test_nodegroup( self.context, uuid=uuid4(), id=123, name="non-autoscale-group", node_count=1, ) self.cluster_obj.nodegroups.append(non_auto_scale_nodegroup) auto_scale_nodegroup = obj_utils.create_test_nodegroup( self.context, uuid=uuid4(), id=456, name="autoscale-group", node_count=1, min_node_count=1, max_node_count=3, ) self.cluster_obj.nodegroups.append(auto_scale_nodegroup) self.driver.create_cluster(self.context, self.cluster_obj, 10) for ng in self.cluster_obj.nodegroups: print(ng) # Unpack some values for asserting against helm_install_values = mock_install.call_args[0][3] helm_node_groups = helm_install_values["nodeGroups"] helm_values_default_nodegroup = [ ng for ng in helm_node_groups if ng["name"] == self.cluster_obj.default_ng_worker.name ] helm_values_autoscale_nodegroup = [ ng for ng in helm_node_groups if ng["name"] == auto_scale_nodegroup.name ] helm_values_non_autoscale_nodegroup = [ ng for ng in helm_node_groups if ng["name"] == non_auto_scale_nodegroup.name ] # Check that node groups were passed into Helm values correctly self.assertEqual(len(helm_values_default_nodegroup), 1) self.assertEqual(len(helm_values_autoscale_nodegroup), 1) self.assertEqual(len(helm_values_non_autoscale_nodegroup), 1) # Check default node group values ng = helm_values_default_nodegroup[0] self.assertEqual(ng["autoscale"], "true") self.assertEqual( ng["machineCountMin"], auto_scale_labels["min_node_count"] ) self.assertEqual( ng["machineCountMax"], auto_scale_labels["max_node_count"] ) # Check extra autoscaling node group values # NOTE: CAPO doesn't support scale to zero so # min node count should be max(1, ng.min_node_count) ng = helm_values_autoscale_nodegroup[0] self.assertEqual(ng["autoscale"], "true") self.assertEqual( ng["machineCountMin"], max(1, auto_scale_nodegroup.min_node_count) ) self.assertEqual( ng["machineCountMax"], auto_scale_nodegroup.max_node_count ) # Check extra non-autoscaling node group values ng = helm_values_non_autoscale_nodegroup[0] self.assertEqual(ng.get("autoscale"), None) self.assertEqual(ng.get("machineCountMin"), None) self.assertEqual(ng.get("machineCountMax"), None) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_boot_volume_size_label_valid( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, ): disk_size_configuration_value = 15 disk_size_label_value = 32 CONF.cinder.default_boot_volume_size = disk_size_configuration_value self.cluster_obj.labels = { "boot_volume_size": str(disk_size_label_value) } mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.driver.create_cluster(self.context, self.cluster_obj, 10) helm_install_values = mock_install.call_args[0][3] self.assertEqual( helm_install_values["controlPlane"]["machineRootVolume"][ "diskSize" ], disk_size_label_value, ) self.assertEqual( helm_install_values["nodeGroupDefaults"]["machineRootVolume"][ "diskSize" ], disk_size_label_value, ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_boot_volume_size_label_default( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, ): disk_size_configuration_value = 15 CONF.cinder.default_boot_volume_size = disk_size_configuration_value self.cluster_obj.labels = {} mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.driver.create_cluster(self.context, self.cluster_obj, 10) helm_install_values = mock_install.call_args[0][3] self.assertEqual( helm_install_values["controlPlane"]["machineRootVolume"][ "diskSize" ], disk_size_configuration_value, ) self.assertEqual( helm_install_values["nodeGroupDefaults"]["machineRootVolume"][ "diskSize" ], disk_size_configuration_value, ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_create_cluster_boot_volume_size_label_invalid( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, ): disk_size_configuration_value = 15 CONF.cinder.default_boot_volume_size = disk_size_configuration_value self.cluster_obj.labels = {"boot_volume_size": "NotANumber"} mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) mock_client = mock.MagicMock(spec=kubernetes.Client) mock_load.return_value = mock_client self.driver.create_cluster(self.context, self.cluster_obj, 10) helm_install_values = mock_install.call_args[0][3] self.assertEqual( helm_install_values["controlPlane"]["machineRootVolume"][ "diskSize" ], disk_size_configuration_value, ) self.assertEqual( helm_install_values["nodeGroupDefaults"]["machineRootVolume"][ "diskSize" ], disk_size_configuration_value, ) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_nodegroup_node_count_min_max_equal( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, ): # When autoscaling is enabled but no min/max node counts are # provided for the default node group, we want autoscaling to # be disabled on the default node group. self.cluster_obj.labels = {"auto_scaling_enabled": "true"} mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) self.driver.create_cluster(self.context, self.cluster_obj, 10) helm_install_values = mock_install.call_args[0][3] helm_values_default_ng = next( ng for ng in helm_install_values["nodeGroups"] if ng["name"] == self.cluster_obj.default_ng_worker.name ) self.assertEqual(helm_values_default_ng.get("autoscale"), None) self.assertEqual(helm_values_default_ng.get("machineCountMin"), None) self.assertEqual(helm_values_default_ng.get("machineCountMax"), None) self.assertEqual(helm_values_default_ng.get("machineCount"), 3) @mock.patch.object( driver.Driver, "_storageclass_definitions", return_value=mock.ANY, ) @mock.patch.object(driver.Driver, "_validate_allowed_flavor") @mock.patch.object(neutron, "get_network", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(kubernetes.Client, "load", autospec=True) @mock.patch.object(driver.Driver, "_get_image_details", autospec=True) @mock.patch.object(helm.Client, "install_or_upgrade", autospec=True) def test_nodegroup_master_lb_fip( self, mock_install, mock_image, mock_load, mock_appcred, mock_certs, mock_get_net, mock_validate_allowed_flavor, mock_storageclasses, ): # Disabled master LB in labels. self.cluster_obj.labels = {"master_lb_floating_ip_enabled": "false"} self.cluster_obj.cluster_template.floating_ip_enabled = True mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) self.driver.create_cluster(self.context, self.cluster_obj, 10) helm_install_values = mock_install.call_args[0][3] apiserver_expected = { "associateFloatingIP": False, "enableLoadBalancer": True, "loadBalancerProvider": "amphora", } self.assertEqual(apiserver_expected, helm_install_values["apiServer"]) # Enabled master LB in labels. self.cluster_obj.labels = {"master_lb_floating_ip_enabled": "true"} self.cluster_obj.cluster_template.floating_ip_enabled = False mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) self.driver.create_cluster(self.context, self.cluster_obj, 10) helm_install_values = mock_install.call_args[0][3] apiserver_expected = { "associateFloatingIP": True, "enableLoadBalancer": True, "loadBalancerProvider": "amphora", } self.assertEqual(apiserver_expected, helm_install_values["apiServer"]) # Absent master lb label means default to True. # cluster_template floating_ip_enabled is used only for FIP on nodes. self.cluster_obj.labels = {} self.cluster_obj.cluster_template.floating_ip_enabled = False mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) self.driver.create_cluster(self.context, self.cluster_obj, 10) helm_install_values = mock_install.call_args[0][3] apiserver_expected = { "associateFloatingIP": True, "enableLoadBalancer": True, "loadBalancerProvider": "amphora", } self.assertEqual(apiserver_expected, helm_install_values["apiServer"]) # Absent master lb label. # cluster_template floating_ip_enabled is used only for FIP on nodes. self.cluster_obj.labels = {} self.cluster_obj.cluster_template.floating_ip_enabled = True mock_image.return_value = ( "imageid1", "1.27.4", "ubuntu", ) self.driver.create_cluster(self.context, self.cluster_obj, 10) helm_install_values = mock_install.call_args[0][3] apiserver_expected = { "associateFloatingIP": True, "enableLoadBalancer": True, "loadBalancerProvider": "amphora", } self.assertEqual(apiserver_expected, helm_install_values["apiServer"]) @mock.patch.object(app_creds, "delete_app_cred", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(driver.Driver, "_get_app_cred_id", autospec=True) def test_rotate_credential( self, mock_get, mock_create, mock_certs, mock_delete ): app_cred_id_old = "abcde12345" app_cred_id_new = "edcba54321" mock_app_cred = mock.MagicMock() mock_app_cred.id = app_cred_id_new mock_get.return_value = app_cred_id_old mock_create.return_value = mock_app_cred self.driver.rotate_credential(self.context, self.cluster_obj) mock_get.assert_called_once_with(self.driver, self.cluster_obj) mock_create.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_not_called() # user_id unchanged mock_delete.assert_called_once_with(self.cluster_obj, app_cred_id_old) self.assertEqual(self.context.user_id, self.cluster_obj.user_id) self.assertEqual( fields.ClusterStatus.UPDATE_COMPLETE, self.cluster_obj.status ) self.assertIsNone(self.cluster_obj.status_reason) @mock.patch.object(app_creds, "delete_app_cred", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(driver.Driver, "_get_app_cred_id", autospec=True) def test_rotate_credential_new_user( self, mock_get, mock_create, mock_certs, mock_delete ): self.cluster_obj.user_id = "user_a" self.context.user_id = "user_b" app_cred_id_old = "abcde12345" app_cred_id_new = "edcba54321" mock_app_cred = mock.MagicMock() mock_app_cred.id = app_cred_id_new mock_get.return_value = app_cred_id_old mock_create.return_value = mock_app_cred self.driver.rotate_credential(self.context, self.cluster_obj) mock_get.assert_called_once_with(self.driver, self.cluster_obj) mock_create.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_delete.assert_called_once_with(self.cluster_obj, app_cred_id_old) self.assertEqual( fields.ClusterStatus.UPDATE_COMPLETE, self.cluster_obj.status ) self.assertIsNone(self.cluster_obj.status_reason) @mock.patch.object(app_creds, "delete_app_cred", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(driver.Driver, "_get_app_cred_id", autospec=True) def test_rotate_credential_delete_failed( self, mock_get, mock_create, mock_certs, mock_delete ): app_cred_id = "abcde12345" mock_app_cred = mock.MagicMock() mock_app_cred.id = app_cred_id mock_get.return_value = None mock_create.return_value = mock_app_cred self.driver.rotate_credential(self.context, self.cluster_obj) mock_get.assert_called_once_with(self.driver, self.cluster_obj) mock_create.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_not_called() # user_id unchanged mock_delete.assert_not_called() # Failure to delete the old app cred is not a error condition self.assertEqual(self.context.user_id, self.cluster_obj.user_id) self.assertEqual( fields.ClusterStatus.UPDATE_COMPLETE, self.cluster_obj.status ) self.assertIsNone(self.cluster_obj.status_reason) @mock.patch.object(app_creds, "delete_app_cred", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(driver.Driver, "_get_app_cred_id", autospec=True) def test_rotate_credential_create_failed( self, mock_get, mock_create, mock_certs, mock_delete ): mock_create.side_effect = exception.AuthorizationFailure self.assertRaises( exception.MagnumException, self.driver.rotate_credential, self.context, self.cluster_obj, ) mock_get.assert_called_once_with(self.driver, self.cluster_obj) mock_create.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_not_called() mock_delete.assert_not_called() self.assertEqual( fields.ClusterStatus.UPDATE_FAILED, self.cluster_obj.status ) self.assertIsNotNone(self.cluster_obj.status_reason) @mock.patch.object(app_creds, "delete_app_cred", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(driver.Driver, "_get_app_cred_id", autospec=True) def test_rotate_credential_is_none( self, mock_get, mock_create, mock_certs, mock_delete ): mock_create.return_value = None self.assertRaises( exception.MagnumException, self.driver.rotate_credential, self.context, self.cluster_obj, ) mock_get.assert_called_once_with(self.driver, self.cluster_obj) mock_create.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_not_called() mock_delete.assert_not_called() self.assertEqual( fields.ClusterStatus.UPDATE_FAILED, self.cluster_obj.status ) self.assertIsNotNone(self.cluster_obj.status_reason) @mock.patch.object(app_creds, "delete_app_cred", autospec=True) @mock.patch.object( driver.Driver, "_ensure_certificate_secrets", autospec=True ) @mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True) @mock.patch.object(driver.Driver, "_get_app_cred_id", autospec=True) def test_rotate_credential_unchanged( self, mock_get, mock_create, mock_certs, mock_delete ): mock_app_cred_id = "abcde12355" mock_app_cred = mock.MagicMock() mock_app_cred.id = mock_app_cred_id mock_get.return_value = mock_app_cred_id mock_create.return_value = mock_app_cred self.assertRaises( exception.MagnumException, self.driver.rotate_credential, self.context, self.cluster_obj, ) mock_get.assert_called_once_with(self.driver, self.cluster_obj) mock_create.assert_called_once_with( self.driver, self.context, self.cluster_obj ) mock_certs.assert_not_called() mock_delete.assert_not_called() self.assertEqual( fields.ClusterStatus.UPDATE_FAILED, self.cluster_obj.status ) self.assertIsNotNone(self.cluster_obj.status_reason) class TestClusterLabelRegistry(base.DbTestCase): """Enforce that every label key used in the driver is registered.""" def test_all_label_calls_use_registered_keys(self): import ast import inspect from magnum_capi_helm import conf as capi_conf from magnum_capi_helm import driver as capi_driver regular_label_names = set( opt.name for opt in capi_conf.capi_helm_cluster_labels_opts ) deprecated_label_names = [] for opt in capi_conf.capi_helm_cluster_labels_opts: for dep_opt in opt.deprecated_opts: deprecated_label_names.append(dep_opt.name) registered = frozenset( regular_label_names.union(set(deprecated_label_names)) ) source = inspect.getsource(capi_driver) tree = ast.parse(source) label_methods = { "_label", "_get_label_bool", "_get_label_int", "_get_label_csv", } for node in ast.walk(tree): if not isinstance(node, ast.Call): continue func = node.func if not ( isinstance(func, ast.Attribute) and func.attr in label_methods ): continue # second positional arg (index 1) is the label key if len(node.args) < 2: continue key_node = node.args[1] # Variable keys (e.g. inside _get_label_bool forwarding to # _label) are validated at runtime; only check string literals. if not isinstance(key_node, ast.Constant): continue self.assertIn( key_node.value, registered, f"Label '{key_node.value}' used at line {node.lineno} " "is not registered in capi_helm_cluster_labels_opts. " "Add it to conf.py before using it in the driver.", ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/test_helm.py0000664000175000017500000001154715212012064022611 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from magnum.common import utils from oslo_concurrency import processutils from magnum_capi_helm import helm from magnum_capi_helm.tests import base class TestHelmClient(base.TestCase): def test_mergeconcat_dicts(self): defaults = dict(foo="bar", asdf=dict(a="b", c="d")) overrides = dict(asdf=dict(a="c")) result = helm.mergeconcat(defaults, overrides) expected = dict(foo="bar", asdf=dict(a="c", c="d")) self.assertEqual(expected, result) def test_mergeconcat_list(self): defaults = ["foo", "bar"] overrides = ["bar", "baz"] result = helm.mergeconcat(defaults, overrides) expected = ["foo", "bar", "bar", "baz"] self.assertEqual(expected, result) @mock.patch.object(utils, "execute") def test_install_or_upgrade(self, mock_execute): mock_execute.return_value = '[{"foo": "bar"}]', "" client = helm.Client() result = client.install_or_upgrade( "myfirstcluster", "mychart", dict(foo="bar", b=42), repo="http://myrepo", version="v1.42", namespace="mynamespace", ) self.assertEqual([{"foo": "bar"}], result) mock_execute.assert_called_once_with( "helm", "upgrade", "myfirstcluster", "mychart", "--history-max", 10, "--install", "--output", "json", "--timeout", "5m", "--values", "-", "--namespace", "mynamespace", "--repo", "http://myrepo", "--version", "v1.42", process_input='{"foo": "bar", "b": 42}', ) @mock.patch.object(utils, "execute") def test_install_or_upgrade_oci(self, mock_execute): mock_execute.return_value = '[{"foo": "bar"}]', "" client = helm.Client() result = client.install_or_upgrade( "myfirstcluster", "oci://localhost:5000/helm-charts/mychart", dict(foo="bar", b=42), version="v1.42", namespace="mynamespace", ) self.assertEqual([{"foo": "bar"}], result) mock_execute.assert_called_once_with( "helm", "upgrade", "myfirstcluster", "oci://localhost:5000/helm-charts/mychart", "--history-max", 10, "--install", "--output", "json", "--timeout", "5m", "--values", "-", "--namespace", "mynamespace", "--version", "v1.42", process_input='{"foo": "bar", "b": 42}', ) @mock.patch.object(helm.CONF, "capi_helm") @mock.patch.object(utils, "execute") def test_uninstall_release_works(self, mock_execute, mock_conf): mock_execute.return_value = "", "" mock_conf.kubeconfig_file = "/etc/magnum/kubeconfig" client = helm.Client() result = client.uninstall_release( "myfirstcluster", namespace="mynamespace" ) self.assertIsNone(result) mock_execute.assert_called_once_with( "helm", "uninstall", "myfirstcluster", "--timeout", "5m", "--namespace", "mynamespace", "--kubeconfig", "/etc/magnum/kubeconfig", ) @mock.patch.object(utils, "execute") def test_uninstall_release_ignore_not_found(self, mock_execute): mock_execute.side_effect = processutils.ProcessExecutionError( stderr="release: not found" ) client = helm.Client() result = client.uninstall_release( "myfirstcluster", namespace="mynamespace" ) self.assertIsNone(result) @mock.patch.object(utils, "execute") def test_uninstall_release_raises(self, mock_execute): mock_execute.side_effect = processutils.ProcessExecutionError( stderr="oh dear!" ) client = helm.Client() self.assertRaises( processutils.ProcessExecutionError, client.uninstall_release, "myfirstcluster", namespace="mynamespace", ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/test_kubernetes.py0000664000175000017500000004620215212012064024027 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import os import pathlib import tempfile from unittest import mock import yaml import requests from magnum_capi_helm import kubernetes from magnum_capi_helm.tests import base TEST_SERVER = "https://test:6443" TEST_KUBECONFIG_YAML = f"""\ apiVersion: v1 clusters: - cluster: certificate-authority: "cafile" server: {TEST_SERVER} name: default contexts: - context: cluster: default user: default name: default current-context: default kind: Config users: - name: default user: client-certificate: "certfile" client-key: "keyfile" """ TEST_KUBECONFIG = yaml.safe_load(TEST_KUBECONFIG_YAML) class TestKubernetesClient(base.TestCase): def assert_request_called_once_with(self, mock_request, *args, **kwargs): # requests >= 2.34 makes Session.get() pass params=None explicitly # to Session.request(); older versions omit it. Drop the implicit # params=None so the assertion works on both. mock_request.assert_called_once() actual_args, actual_kwargs = mock_request.call_args if "params" not in kwargs and actual_kwargs.get("params") is None: actual_kwargs = { k: v for k, v in actual_kwargs.items() if k != "params" } self.assertEqual(args, actual_args) self.assertEqual(kwargs, actual_kwargs) # Basic lookup, non "-data" key def test_file_or_data(self): client = kubernetes.Client(TEST_KUBECONFIG) data = client.ensure_file_cert(dict(key="mydata"), "key") self.assertEqual("mydata", data) # Lookup with a "-data" key, requiring temporary file @mock.patch.object(tempfile, "NamedTemporaryFile") def test_file_or_data_create_temp(self, mock_temp): client = kubernetes.Client(TEST_KUBECONFIG) data = client.ensure_file_cert( {"key-data": base64.b64encode(b"mydata").decode("utf-8")}, "key" ) mock_temp.assert_has_calls( [ mock.call(delete=False), mock.call().__enter__(), mock.call().__enter__().write(b"mydata"), mock.call().__exit__(None, None, None), ] ) self.assertEqual(mock_temp().__enter__().name, data) # Lookup with no key, expecting no error, and no data returned. def test_file_or_data_missing(self): client = kubernetes.Client(TEST_KUBECONFIG) data = client.ensure_file_cert(dict(), "key") self.assertIsNone(data) def test_client_constructor(self): client = kubernetes.Client(TEST_KUBECONFIG) self.assertEqual(TEST_SERVER, client.server) self.assertEqual("cafile", client.verify) self.assertEqual(("certfile", "keyfile"), client.cert) @mock.patch.object(tempfile, "NamedTemporaryFile") @mock.patch.object(os, "remove") def test_client_certificate_finalizer(self, mock_remove, mock_temp): kubeconfig = yaml.safe_load(TEST_KUBECONFIG_YAML) # Set -data in base64 to create tmp files. del kubeconfig["users"][0]["user"]["client-certificate"] kubeconfig["users"][0]["user"]["client-certificate-data"] = ( base64.b64encode(b"client cert data").decode("utf-8") ) client = kubernetes.Client(kubeconfig) # Ensure a temporary file was created mock_temp.assert_has_calls( [ mock.call(delete=False), mock.call().__enter__(), mock.call().__enter__().write(b"client cert data"), mock.call().__exit__(None, None, None), ] ) # Call finalizer method directly client.__del__() # Exactly one temp file should be cleaned up mock_remove.assert_called_once() def test_get_kubeconfig_path_default(self): self.assertEqual( pathlib.Path.home() / ".kube" / "config", kubernetes.Client._get_kubeconfig_path(), ) @mock.patch.object(kubernetes.CONF, "capi_helm") def test_get_kubeconfig_path_config(self, mock_conf): mock_conf.kubeconfig_file = "foo" os.environ["KUBECONFIG"] = "bar" path = kubernetes.Client._get_kubeconfig_path() del os.environ["KUBECONFIG"] self.assertEqual("foo", path) def test_get_kubeconfig_path_env(self): os.environ["KUBECONFIG"] = "bar" path = kubernetes.Client._get_kubeconfig_path() del os.environ["KUBECONFIG"] self.assertEqual("bar", path) @mock.patch.object(kubernetes.CONF, "capi_helm") @mock.patch( "builtins.open", new_callable=mock.mock_open, read_data=TEST_KUBECONFIG_YAML, ) def test_client_load(self, mock_open, mock_conf): mock_conf.kubeconfig_file = "mypath" client = kubernetes.Client.load() self.assertEqual(TEST_SERVER, client.server) mock_open.assert_called_once_with("mypath") @mock.patch.object(requests.Session, "request") def test_ensure_namespace(self, mock_request): client = kubernetes.Client(TEST_KUBECONFIG) client.ensure_namespace("namespace1") mock_request.assert_called_once_with( "PATCH", "https://test:6443/api/v1/namespaces/namespace1", data=None, json={ "apiVersion": "v1", "kind": "Namespace", "metadata": {"name": "namespace1"}, }, headers={"Content-Type": "application/apply-patch+yaml"}, params={"fieldManager": "magnum", "force": "true"}, ) @mock.patch.object(requests.Session, "request") def test_apply_secret(self, mock_request): client = kubernetes.Client(TEST_KUBECONFIG) test_data = dict( stringData=dict(foo="bar"), metadata=dict(labels=dict(baz="asdf")) ) client.apply_secret("secname", test_data, "ns1") mock_request.assert_called_once_with( "PATCH", "https://test:6443/api/v1/namespaces/ns1/secrets/secname", data=None, json={ "stringData": {"foo": "bar"}, "apiVersion": "v1", "kind": "Secret", "metadata": { "labels": {"baz": "asdf"}, "name": "secname", "namespace": "ns1", }, }, headers={"Content-Type": "application/apply-patch+yaml"}, params={"fieldManager": "magnum", "force": "true"}, ) @mock.patch.object(requests.Session, "request") def test_delete_all_secrets_by_label(self, mock_request): client = kubernetes.Client(TEST_KUBECONFIG) mock_response = mock.MagicMock() mock_request.return_value = mock_response client.delete_all_secrets_by_label("label", "cluster1", "ns1") mock_request.assert_called_once_with( "DELETE", "https://test:6443/api/v1/namespaces/ns1/secrets", params={"labelSelector": "label=cluster1"}, ) mock_response.raise_for_status.assert_called_once_with() @mock.patch.object(requests.Session, "request") def test_get_secret(self, mock_request): client = kubernetes.Client(TEST_KUBECONFIG) mock_response = mock.MagicMock() mock_response.status_code = 200 mock_request.return_value = mock_response secret_name = "secret1" secret_namespace = "ns1" client.get_secret(secret_name, secret_namespace) self.assert_request_called_once_with( mock_request, "GET", "https://test:6443/api/v1/namespaces" f"/{secret_namespace}/secrets/{secret_name}", allow_redirects=True, ) @mock.patch.object(requests.Session, "request") def test_get_secret_value(self, mock_request): client = kubernetes.Client(TEST_KUBECONFIG) mock_response = mock.MagicMock() mock_response.status_code = 200 mock_request.return_value = mock_response secret_name = "secret1" secret_namespace = "ns1" secret_key = "mykey" secret_value = "mysecretvalue" mock_response.json.return_value = { "data": { secret_key: base64.b64encode(secret_value.encode()).decode(), } } self.assertEqual( client.get_secret_value(secret_name, secret_namespace, secret_key), secret_value, ) self.assert_request_called_once_with( mock_request, "GET", "https://test:6443/api/v1/namespaces" f"/{secret_namespace}/secrets/{secret_name}", allow_redirects=True, ) @mock.patch.object(requests.Session, "request") def test_get_capi_cluster_found(self, mock_request): client = kubernetes.Client(TEST_KUBECONFIG) mock_response = mock.MagicMock() mock_response.status_code = 200 mock_response.json.return_value = "mock_json" mock_request.return_value = mock_response cluster = client.get_capi_cluster("name", "ns1") self.assert_request_called_once_with( mock_request, "GET", ( "https://test:6443/apis/cluster.x-k8s.io/" "v1beta1/namespaces/ns1/clusters/name" ), allow_redirects=True, ) self.assertEqual("mock_json", cluster) @mock.patch.object(requests.Session, "request") def test_get_capi_cluster_not_found(self, mock_request): client = kubernetes.Client(TEST_KUBECONFIG) mock_response = mock.MagicMock() mock_response.status_code = 404 mock_request.return_value = mock_response cluster = client.get_capi_cluster("name", "ns1") self.assertIsNone(cluster) @mock.patch.object(requests.Session, "request") def test_get_capi_cluster_error(self, mock_request): client = kubernetes.Client(TEST_KUBECONFIG) mock_response = mock.MagicMock() mock_response.status_code = 500 mock_response.raise_for_status.side_effect = requests.HTTPError mock_request.return_value = mock_response self.assertRaises( requests.HTTPError, client.get_capi_cluster, "name", "ns1" ) @mock.patch.object(requests.Session, "request") def test_get_k8s_control_plane_found(self, mock_request): client = kubernetes.Client(TEST_KUBECONFIG) mock_response = mock.MagicMock() mock_response.status_code = 200 mock_response.json.return_value = "mock_json" mock_request.return_value = mock_response cluster = client.get_k8s_control_plane("name", "ns1") self.assert_request_called_once_with( mock_request, "GET", ( "https://test:6443/apis/controlplane.cluster.x-k8s.io/" "v1beta1/namespaces/ns1/kubeadmcontrolplanes/name" ), allow_redirects=True, ) self.assertEqual("mock_json", cluster) @mock.patch.object(requests.Session, "request") def test_get_machine_deployment_found(self, mock_request): client = kubernetes.Client(TEST_KUBECONFIG) mock_response = mock.MagicMock() mock_response.status_code = 200 mock_response.json.return_value = "mock_json" mock_request.return_value = mock_response cluster = client.get_machine_deployment("name", "ns1") self.assert_request_called_once_with( mock_request, "GET", ( "https://test:6443/apis/cluster.x-k8s.io/" "v1beta1/namespaces/ns1/machinedeployments/name" ), allow_redirects=True, ) self.assertEqual("mock_json", cluster) @mock.patch.object(requests.Session, "request") def test_get_manifests_by_label(self, mock_request): items = [ { "kind": "Manifests", "metadata": {"name": f"manifests{idx}", "namespace": "ns1"}, } for idx in range(5) ] mock_response = mock.Mock() mock_response.status_code = 200 mock_response.json.return_value = { "metadata": { "continue": "", }, "items": items, } mock_request.return_value = mock_response client = kubernetes.Client(TEST_KUBECONFIG) manifests = client.get_manifests_by_label({"label": "cluster1"}, "ns1") mock_request.assert_called_once_with( "GET", ( "https://test:6443/apis/addons.stackhpc.com/" "v1alpha1/namespaces/ns1/manifests" ), params={"labelSelector": "label=cluster1"}, allow_redirects=True, ) self.assertEqual(items, manifests) @mock.patch.object(requests.Session, "request") def test_get_helm_releases_by_label(self, mock_request): items = [ { "kind": "HelmRelease", "metadata": {"name": f"helmrelease{idx}", "namespace": "ns1"}, } for idx in range(5) ] mock_response = mock.Mock() mock_response.status_code = 200 mock_response.json.return_value = { "metadata": { "continue": "", }, "items": items, } mock_request.return_value = mock_response client = kubernetes.Client(TEST_KUBECONFIG) helm_releases = client.get_helm_releases_by_label( {"label": "cluster1"}, "ns1" ) mock_request.assert_called_once_with( "GET", ( "https://test:6443/apis/addons.stackhpc.com/" "v1alpha1/namespaces/ns1/helmreleases" ), params={"labelSelector": "label=cluster1"}, allow_redirects=True, ) self.assertEqual(items, helm_releases) @mock.patch.object(requests.Session, "request") def test_get_helm_releases_by_label_multipage(self, mock_request): items = [ { "kind": "HelmRelease", "metadata": {"name": f"helmrelease{idx}", "namespace": "ns1"}, } for idx in range(10) ] mock_response_page1 = mock.Mock() mock_response_page1.raise_for_status.return_value = None mock_response_page1.json.return_value = { "metadata": { "continue": "continuetoken", }, "items": items[:5], } mock_response_page2 = mock.Mock() mock_response_page2.raise_for_status.return_value = None mock_response_page2.json.return_value = { "metadata": { "continue": "", }, "items": items[5:], } mock_request.side_effect = [ mock_response_page1, mock_response_page2, ] client = kubernetes.Client(TEST_KUBECONFIG) helm_releases = client.get_helm_releases_by_label( {"label": "cluster1"}, "ns1" ) self.assertEqual(mock_request.call_count, 2) mock_request.assert_has_calls( [ mock.call( "GET", ( "https://test:6443/apis/addons.stackhpc.com/" "v1alpha1/namespaces/ns1/helmreleases" ), params={"labelSelector": "label=cluster1"}, allow_redirects=True, ), mock.call( "GET", ( "https://test:6443/apis/addons.stackhpc.com/" "v1alpha1/namespaces/ns1/helmreleases" ), params={ "labelSelector": "label=cluster1", "continue": "continuetoken", }, allow_redirects=True, ), ] ) self.assertEqual(items, helm_releases) @mock.patch.object(kubernetes.Client, "get_helm_chart_proxies_by_label") @mock.patch.object(kubernetes.Client, "get_helm_releases_by_label") @mock.patch.object(kubernetes.Client, "get_manifests_by_label") def test_get_addons_by_label( self, mock_get_manifests, mock_get_helm_releases, mock_get_helm_chart_proxies, ): manifests = [ { "kind": "Manifests", "metadata": {"name": f"manifests{idx}", "namespace": "ns1"}, } for idx in range(5) ] helm_releases = [ { "kind": "HelmRelease", "metadata": {"name": f"helmrelease{idx}", "namespace": "ns1"}, } for idx in range(5) ] mock_get_manifests.return_value = manifests mock_get_helm_releases.return_value = helm_releases mock_response = mock.MagicMock() mock_response.status_code = 404 mock_response.raise_for_status.side_effect = requests.HTTPError mock_get_helm_chart_proxies.side_effect = mock_response client = kubernetes.Client(TEST_KUBECONFIG) addons = client.get_addons_by_label({"label": "cluster1"}, "ns1") mock_get_manifests.assert_called_once_with( {"label": "cluster1"}, "ns1" ) mock_get_helm_releases.assert_called_once_with( {"label": "cluster1"}, "ns1" ) self.assertEqual(helm_releases + manifests, addons) @mock.patch.object(requests.Session, "request") def test_get_all_machines_by_label(self, mock_request): items = [ { "kind": "Machine", "metadata": {"name": f"machine{idx}", "namespace": "ns1"}, } for idx in range(5) ] mock_response = mock.Mock() mock_response.status_code = 200 mock_response.json.return_value = { "metadata": { "continue": "", }, "items": items, } mock_request.return_value = mock_response client = kubernetes.Client(TEST_KUBECONFIG) machines = client.get_all_machines_by_label( {"capi.stackhpc.com/cluster": "cluster_name", "foo": "bar"}, "ns1" ) mock_request.assert_called_once_with( "GET", ( "https://test:6443/apis/cluster.x-k8s.io/" "v1beta1/namespaces/ns1/machines" ), params={ "labelSelector": ( "capi.stackhpc.com/cluster=cluster_name,foo=bar" ) }, allow_redirects=True, ) self.assertEqual(items, machines) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/magnum_capi_helm/tests/test_magnum_capi_helm.py0000664000175000017500000000211015212012064025133 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from magnum.drivers.common import driver as common from magnum_capi_helm import driver from magnum_capi_helm.tests import base class TestMagnumDriverLoads(base.TestCase): def test_get_driver(self): cluster_driver = common.Driver.get_driver("vm", "ubuntu", "kubernetes") self.assertIsInstance(cluster_driver, driver.Driver) def test_get_flatcar_driver(self): cluster_driver = common.Driver.get_driver( "vm", "flatcar", "kubernetes" ) self.assertIsInstance(cluster_driver, driver.Driver) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7144327 magnum_capi_helm-1.4.0/magnum_capi_helm.egg-info/0000775000175000017500000000000015212012201020564 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011584.0 magnum_capi_helm-1.4.0/magnum_capi_helm.egg-info/PKG-INFO0000644000175000017500000000424715212012200021665 0ustar00zuulzuulMetadata-Version: 2.4 Name: magnum-capi-helm Version: 1.4.0 Summary: OpenStack Magnum driver using helm to create k8s clusters with Cluster API. Home-page: https://www.openstack.org/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.10 License-File: LICENSE Requires-Dist: pbr>=2.0 Requires-Dist: oslo_log Requires-Dist: oslo_utils Requires-Dist: magnum Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: license-file Dynamic: requires-dist Dynamic: requires-python Dynamic: summary =============================== magnum-capi-helm =============================== OpenStack Magnum driver using Helm to create k8s clusters with Cluster API. The driver uses `capi-helm-charts `_ to create the k8s resources needed to provision a k8s cluster using Cluster API, including various useful add-ons like a CNI and a monitoring stack. Note, the above Helm charts are intended to be a way to share a reference method to create K8s on OpenStack. The charts are not expected or intended to be specific to Magnum. The hope is they can also be used by ArgoCD, Flux or Azimuth to create k8s clusters on OpenStack. Work on this driver started upstream around October 2021. After failing to get merged during Bobcat, we created this downstream repo as a stop-gap to help those wanting to use this driver now. https://specs.openstack.org/openstack/magnum-specs/specs/bobcat/clusterapi-driver.html ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011584.0 magnum_capi_helm-1.4.0/magnum_capi_helm.egg-info/SOURCES.txt0000664000175000017500000000622315212012200022452 0ustar00zuulzuul.coveragerc .stestr.conf AUTHORS ChangeLog LICENSE README.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini devstack/plugin.sh devstack/lib/magnum-capi-helm doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/_static/capi_helm.conf.sample doc/source/configuration/config-reference.rst doc/source/configuration/index.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/user_docs/capi-architecture-diagram.png doc/source/user_docs/capi-crd-arch.svg doc/source/user_docs/index.rst etc/oslo-config-generator/capi_helm.conf magnum_capi_helm/__init__.py magnum_capi_helm/conf.py magnum_capi_helm/driver.py magnum_capi_helm/driver_utils.py magnum_capi_helm/helm.py magnum_capi_helm/kubernetes.py magnum_capi_helm.egg-info/PKG-INFO magnum_capi_helm.egg-info/SOURCES.txt magnum_capi_helm.egg-info/dependency_links.txt magnum_capi_helm.egg-info/entry_points.txt magnum_capi_helm.egg-info/not-zip-safe magnum_capi_helm.egg-info/pbr.json magnum_capi_helm.egg-info/requires.txt magnum_capi_helm.egg-info/top_level.txt magnum_capi_helm/common/README.rst magnum_capi_helm/common/__init__.py magnum_capi_helm/common/app_creds.py magnum_capi_helm/common/ca_certificates.py magnum_capi_helm/common/capi_monitor.py magnum_capi_helm/tests/__init__.py magnum_capi_helm/tests/base.py magnum_capi_helm/tests/test_driver.py magnum_capi_helm/tests/test_helm.py magnum_capi_helm/tests/test_kubernetes.py magnum_capi_helm/tests/test_magnum_capi_helm.py magnum_capi_helm/tests/common/__init__.py magnum_capi_helm/tests/common/test_app_creds.py magnum_capi_helm/tests/common/test_ca_certificates.py magnum_capi_helm/tests/common/test_capi_monitor.py playbooks/post.yaml playbooks/pre.yaml releasenotes/notes/.placeholder releasenotes/notes/add-cni-network-driver-a677f2514db975e0.yaml releasenotes/notes/add-helmchartproxy-resource-ad5dab507b219dfa.yaml releasenotes/notes/autoscaling-all-worker-groups-8c7a07d0f56ac2ca.yaml releasenotes/notes/autoscaling-default-worker-group-751f100603fb3d0a.yaml releasenotes/notes/autoscaling-min-max-defaults-0a0385bea438bc46.yaml releasenotes/notes/change-helm-repo-55869552914bb9ed.yaml releasenotes/notes/cinder-volume-binding-mode-fc24de202303975c.yaml releasenotes/notes/cluster-label-registry-38de700e433df68a.yaml releasenotes/notes/control-plane-resize-19cc0926a0ab92f9.yaml releasenotes/notes/credential-rotation-91773703e1f8f057.yaml releasenotes/notes/fix-floating-ips-bd6ac8f8785503d1.yaml releasenotes/notes/fix-nodegroup-delete-c83210c6029041d2.yaml releasenotes/notes/generalize-api-resources-e3b9f1606a51fb6f.yaml releasenotes/notes/initial-release-f507ed074fcf7763.yaml releasenotes/notes/openstackcluster-v1beta1-2699449249aa0e27.yaml releasenotes/notes/prelude-1.4.0-1093690774fa034a.yaml releasenotes/notes/support-multiple-extra-networks-05d10102412e6388.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder zuul.d/base.yaml zuul.d/nodeset.yaml zuul.d/project-templates-tempest.yaml zuul.d/project-templates.yaml zuul.d/project.yaml zuul.d/tempest-jobs-v1-34.yaml zuul.d/tempest-jobs-v1-35.yaml././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011584.0 magnum_capi_helm-1.4.0/magnum_capi_helm.egg-info/dependency_links.txt0000664000175000017500000000000115212012200024631 0ustar00zuulzuul ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011584.0 magnum_capi_helm-1.4.0/magnum_capi_helm.egg-info/entry_points.txt0000664000175000017500000000032615212012200024062 0ustar00zuulzuul[magnum.drivers] k8s_capi_helm_v1 = magnum_capi_helm.driver:Driver [oslo.config.opts] capi_helm = magnum_capi_helm.conf:list_capi_opts capi_helm_cluster_labels = magnum_capi_helm.conf:list_capi_cluster_label_opts ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011584.0 magnum_capi_helm-1.4.0/magnum_capi_helm.egg-info/not-zip-safe0000664000175000017500000000000115212012200023011 0ustar00zuulzuul ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011584.0 magnum_capi_helm-1.4.0/magnum_capi_helm.egg-info/pbr.json0000664000175000017500000000005615212012200022242 0ustar00zuulzuul{"git_version": "a8b82c2", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011584.0 magnum_capi_helm-1.4.0/magnum_capi_helm.egg-info/requires.txt0000664000175000017500000000004415212012200023161 0ustar00zuulzuulpbr>=2.0 oslo_log oslo_utils magnum ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011584.0 magnum_capi_helm-1.4.0/magnum_capi_helm.egg-info/top_level.txt0000664000175000017500000000002115212012200023306 0ustar00zuulzuulmagnum_capi_helm ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7074327 magnum_capi_helm-1.4.0/playbooks/0000775000175000017500000000000015212012201015610 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/playbooks/post.yaml0000664000175000017500000000017315212012064017471 0ustar00zuulzuul--- - hosts: all roles: - fetch_docker_log - fetch_kubelet_log - hosts: controller roles: - fetch_pod_log ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/playbooks/pre.yaml0000664000175000017500000000061515212012064017273 0ustar00zuulzuul- hosts: all tasks: - name: Set up ipv4 iptables rules for Kubernetes become: yes iptables: state: present action: insert chain: INPUT ip_version: ipv4 source: "{{ item }}" jump: ACCEPT with_items: # Default flannel pod cidr - "10.244.0.0/16" # Default kubernetes service cidr - "10.96.0.0/12"././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781011584.694433 magnum_capi_helm-1.4.0/releasenotes/0000775000175000017500000000000015212012201016276 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7114327 magnum_capi_helm-1.4.0/releasenotes/notes/0000775000175000017500000000000015212012201017426 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/.placeholder0000664000175000017500000000000015212012064021706 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/add-cni-network-driver-a677f2514db975e0.yaml0000664000175000017500000000047115212012064026746 0ustar00zuulzuul--- features: - | The CNI deployed in clusters can now be selected via the ``network_driver`` cluster template option. The value is passed through to the helm chart as ``addons.cni.type``. Supported values are ``calico`` (default) and ``cilium``. When unset, the helm chart default is used. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/add-helmchartproxy-resource-ad5dab507b219dfa.yaml0000664000175000017500000000103115212012064030363 0ustar00zuulzuul--- features: - | Add support for new resource HelmChartProxy. Users can manage addons on workload clusters as part of magnum cluster operations by defining HelmChartProxy resources in their CAPI helm chart [1]. Sample examples on how to define HelmChartProxy resources are available in link [2]. [1] https://github.com/kubernetes-sigs/cluster-api-addon-provider-helm [2] https://github.com/kubernetes-sigs/cluster-api-addon-provider-helm/tree/9d44995329945a318abd6b2ffd7bca7b526401a9/config/samples ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/autoscaling-all-worker-groups-8c7a07d0f56ac2ca.yaml0000664000175000017500000000077415212012064030602 0ustar00zuulzuul--- features: - | Adds support for autoscaling of non-default worker node groups. The min/max node count properties on each node group are passed to the autoscaler to determine autoscaling behaviour. If the target cluster has the `auto_scaling_enabled` cluster label set to `true` then any node groups with both `min_node_count`` and `max_node_count` properties set will be allowed to autoscale between these min and max node counts depending on the cluster's current workload. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/autoscaling-default-worker-group-751f100603fb3d0a.yaml0000664000175000017500000000056115212012064031031 0ustar00zuulzuul--- features: - | Adds support for autoscaling of the default worker node group by respecting the `min_node_count` and `max_node_count` cluster labels. Unlike the Heat driver, when these labels are provided, the cluster will always start with node count equal to the minimum node count value and the alternative `node_count` flag will be ignored. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/autoscaling-min-max-defaults-0a0385bea438bc46.yaml0000664000175000017500000000047015212012064030210 0ustar00zuulzuul--- fixes: - | Fixes an issue where creating a cluster with the 2 or more nodes in the default worker group and with `auto_scaling_enabled=true` but with neither `{min,max}_node_count` labels set would lead to a situation where the default worker group would only have 1 node instead of 2. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/change-helm-repo-55869552914bb9ed.yaml0000664000175000017500000000036715212012064025540 0ustar00zuulzuul--- upgrade: - | Default helm repository is updated to 'azimuth-cloud.github.io/capi-helm-charts'. deprecations: - | Helm repository 'stackhpc.github.io/capi-helm-charts' is deprecated. The last version supported on this repo is 0.9.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/cinder-volume-binding-mode-fc24de202303975c.yaml0000664000175000017500000000112615212012064027553 0ustar00zuulzuul--- upgrade: - | csi_cinder_volume_binding_mode previously had no effect. If you use capi-helm-charts<0.8.0 then you would have set it to Immediate and since 0.8.0 it has defaulted to WaitForFirstConsumer. The configuration is now being used, and we have set the default to match the new versions of the capi-helm-charts. However, if you have older clusters using a template with a very old capi-helm-chart, then really you need to keep the older default, to avoid a breakage on upgrade. This is specifically because the storage class, once created, is immutable. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/cluster-label-registry-38de700e433df68a.yaml0000664000175000017500000000134315212012064027146 0ustar00zuulzuul--- features: - | All cluster label defaults are now configurable site-wide via a new ``[capi_helm_cluster_labels]`` section in ``magnum.conf``. Each supported label has a typed option with a default value that users can still override per cluster or template via Magnum labels. deprecations: - | The ``csi_cinder_availability_zone``, ``csi_cinder_reclaim_policy``, ``csi_cinder_volume_binding_mode``, ``csi_cinder_fstype``, and ``csi_cinder_allow_volume_expansion`` options have moved from the ``[capi_helm]`` section to the new ``[capi_helm_cluster_labels]`` section. The old locations are still accepted but will produce a deprecation warning. Please update your ``magnum.conf`` accordingly. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/control-plane-resize-19cc0926a0ab92f9.yaml0000664000175000017500000000046115212012064026612 0ustar00zuulzuul--- features: - | Adds support for resizing control plane nodes. This feature will be possible only when using Magnum 2025.1 or above, and will otherwise provide no changes. Usage with command line client: openstack coe cluster resize --nodegroup default-master ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/credential-rotation-91773703e1f8f057.yaml0000664000175000017500000000076215212012064026305 0ustar00zuulzuul--- features: - | Adds support for application credential rotation for workload clusters via the credential API. fixes: - | Fixes a bug where the application credential created for a cluster could persist after cluster deletion if the performing user did not have the correct privileges to delete it. This only applies to clusters using Keystone Ussuri or later due to a bug in earlier releases. See `LP#1901207 `__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/fix-floating-ips-bd6ac8f8785503d1.yaml0000664000175000017500000000030615212012064025724 0ustar00zuulzuul--- fixes: - | Fixes an issue where floating IP disable setting from cluster template was ignored by the driver. `LP#2085339 `__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/fix-nodegroup-delete-c83210c6029041d2.yaml0000664000175000017500000000044615212012064026330 0ustar00zuulzuul--- fixes: - | Fixed an issue where non-default node groups could not be individually deleted. The node groups would get stuck in the DELETE_IN_PROGRESS state and the underlying VMs would keep running. Node groups were only cleaned up when the entire cluster was deleted. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/generalize-api-resources-e3b9f1606a51fb6f.yaml0000664000175000017500000000065515212012064027531 0ustar00zuulzuul--- features: - | Adds new configuration parameters to update api_version and plural names of k8s resources related to Cluster API. To be specific, the resources are Cluster, OpenstackCluster, MachineDeployment, K8sControlPlane, Machine, Manifests, HelmRelease. Adds new configuration parameter to specify list of conditions to check in kubernetes control plane resource to consider resource as ready. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/initial-release-f507ed074fcf7763.yaml0000664000175000017500000000206715212012064025626 0ustar00zuulzuul--- prelude: > * This is the initial release of a new in-tree Magnum driver which leverages Helm and the Kubernetes `Cluster API `_ project to provide an alternative to the existing Heat driver for creating and managing Kubernetes clusters via OpenStack Magnum. * This project started life as a out-of-tree driver which was developed and maintained at https://github.com/stackhpc/magnum-capi-helm. This 1.0.0 release marks the deprecation of this out-of-tree source repository and the first official release of the in-tree version which is under OpenStack governance, with source code now available at https://opendev.org/openstack/magnum-capi-helm. * The driver is generally ready for production use and has been deployed on a number of OpenStack systems prior to this first official release. However, this does not preclude the possibility of unforeseen issues and omissions at this early stage of the release cycle - bug reports welcome and appreciated! ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/openstackcluster-v1beta1-2699449249aa0e27.yaml0000664000175000017500000000036215212012064027163 0ustar00zuulzuul--- upgrade: - | OpenStackCluster API usage has been updated from v1alpha6 to v1beta1. Before upgrading, cluster-api-provider-openstack should be upgraded to at least v0.10.0, as this is the first version that provides v1beta1. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/prelude-1.4.0-1093690774fa034a.yaml0000664000175000017500000000204515212012064024412 0ustar00zuulzuul--- prelude: > The 1.4.0 release introduces many new features and improvements, including: * Adds support for the ClusterAPI resource HelmChartProxy. * The CNI deployed in clusters can now be selected via the ``network_driver`` cluster template option. * Adds support for application credential rotation for workload clusters via the credential API. * Adds support for attaching multiple extra networks to a cluster. * Adds new configuration parameters to update api_version and plural names of k8s resources related to Cluster API. * Adds new configuration parameter to specify list of conditions to check in kubernetes control plane resource to consider resource as ready. Operators shoud note that all cluster label defaults are now configurable site-wide via a new ``[capi_helm_cluster_labels]`` section in ``magnum.conf``. Each supported label has a typed option with a default value that users can still override per cluster or template via Magnum labels. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/notes/support-multiple-extra-networks-05d10102412e6388.yaml0000664000175000017500000000046215212012064030562 0ustar00zuulzuul--- features: - | Adds support for attaching multiple extra networks to a cluster, via the label ``extra_network_names``. This label takes a space separated list of network names. deprecations: - | The label ``extra_network_name`` is deprecated in favor of ``extra_network_names``. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7124326 magnum_capi_helm-1.4.0/releasenotes/source/0000775000175000017500000000000015212012201017576 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7124326 magnum_capi_helm-1.4.0/releasenotes/source/_static/0000775000175000017500000000000015212012201021224 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000015212012064023504 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7124326 magnum_capi_helm-1.4.0/releasenotes/source/_templates/0000775000175000017500000000000015212012201021733 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000015212012064024213 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/source/conf.py0000664000175000017500000002175415212012064021115 0ustar00zuulzuul# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "openstackdocstheme", "reno.sphinxext", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "magnum_capi_helm Release Notes" copyright = "2022, OpenStack Developers" # openstackdocstheme options openstackdocs_repo_name = "stackhpc/magnum-capi-helm" openstackdocs_bug_project = "magnum_capi_helm" openstackdocs_bug_tag = "" openstackdocs_auto_name = False # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. # The full version, including alpha/beta/rc tags. release = "" # The short X.Y version. version = "" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "native" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "openstackdocs" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "magnum_capi_helmReleaseNotesdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( "index", "magnum_capi_helmReleaseNotes.tex", "magnum_capi_helm Release Notes Documentation", "OpenStack Foundation", "manual", ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ( "index", "magnum_capi_helmrereleasenotes", "magnum_capi_helm Release Notes Documentation", ["OpenStack Foundation"], 1, ) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "magnum_capi_helm ReleaseNotes", "magnum_capi_helm Release Notes Documentation", "OpenStack Foundation", "magnum_capi_helmReleaseNotes", "One line description of project.", "Miscellaneous", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ["locale/"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/source/index.rst0000664000175000017500000000024715212012064021451 0ustar00zuulzuul============================================ magnum_capi_helm Release Notes ============================================ .. toctree:: :maxdepth: 1 unreleased ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/releasenotes/source/unreleased.rst0000664000175000017500000000016015212012064022463 0ustar00zuulzuul============================== Current Series Release Notes ============================== .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/requirements.txt0000664000175000017500000000040415212012064017076 0ustar00zuulzuul# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=2.0 # Apache-2.0 oslo_log oslo_utils magnum ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7144327 magnum_capi_helm-1.4.0/setup.cfg0000664000175000017500000000220115212012201015421 0ustar00zuulzuul[metadata] name = magnum-capi-helm summary = OpenStack Magnum driver using helm to create k8s clusters with Cluster API. description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://www.openstack.org/ python_requires = >=3.10 classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython [files] packages = magnum_capi_helm [entry_points] magnum.drivers = k8s_capi_helm_v1 = magnum_capi_helm.driver:Driver oslo.config.opts = capi_helm = magnum_capi_helm.conf:list_capi_opts capi_helm_cluster_labels = magnum_capi_helm.conf:list_capi_cluster_label_opts [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/setup.py0000664000175000017500000000125115212012064015325 0ustar00zuulzuul# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools setuptools.setup(setup_requires=["pbr"], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/test-requirements.txt0000664000175000017500000000065315212012064020061 0ustar00zuulzuul# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking>=7.0.0,<7.1.0 # Apache-2.0 coverage>=4.0,!=4.4 # Apache-2.0 python-subunit>=0.0.18 # Apache-2.0/BSD oslotest>=1.10.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 testtools>=1.4.0 # MIT black<25.0.0 # MIT magnum ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/tox.ini0000664000175000017500000000370315212012064015132 0ustar00zuulzuul[tox] minversion = 3.18.0 envlist = py3,black,pep8 ignore_basepython_conflict = True [testenv] basepython = python3 usedevelop = True setenv = PYTHONWARNINGS=default::DeprecationWarning OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run {posargs} [testenv:pep8] commands = black {toxinidir} --check -l 79 --diff flake8 --ignore=W503 {posargs} skip_install = true [testenv:black] commands = black {toxinidir} -l 79 allowlist_externals = black skip_install = true [testenv:venv] commands = {posargs} [testenv:cover] setenv = VIRTUAL_ENV={envdir} PYTHON=coverage run --source magnum_capi_helm --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [testenv:docs] deps = -r{toxinidir}/doc/requirements.txt commands = rm -rf doc/build doc8 doc reno lint doc8 -e '.yaml' releasenotes/notes/ sphinx-build -W --keep-going -b html doc/source doc/build/html allowlist_externals = rm [testenv:releasenotes] deps = {[testenv:docs]deps} commands = reno lint doc8 -e '.yaml' releasenotes/notes/ sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:debug] commands = oslo_debug_helper {posargs} [doc8] # Skip old releasenotes that don't pass checks ignore-path = releasenotes/notes/initial-release-f507ed074fcf7763.yaml, releasenotes/notes/autoscaling-default-worker-group-751f100603fb3d0a.yaml, releasenotes/notes/autoscaling-all-worker-groups-8c7a07d0f56ac2ca.yaml, releasenotes/notes/change-helm-repo-55869552914bb9ed.yaml [flake8] # E123, E125 skipped as they are invalid PEP-8. show-source = True ignore = E123,E125 builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781011584.7134328 magnum_capi_helm-1.4.0/zuul.d/0000775000175000017500000000000015212012201015026 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/zuul.d/base.yaml0000664000175000017500000000561515212012064016642 0ustar00zuulzuul--- - job: name: magnum-capi-helm-tests-cluster parent: magnum-tempest-plugin-tests-cluster-base nodeset: magnum-ubuntu-noble-32GB pre-run: playbooks/pre.yaml post-run: playbooks/post.yaml required-projects: - openstack/devstack-plugin-container - openstack/magnum-capi-helm - openstack/octavia - openstack/ovn-octavia-provider roles: - zuul: openstack/devstack-plugin-container vars: kube_version: "v1.34.7" kube_version_dir: "2026.04-5" devstack_localrc: CONTAINER_ENGINE: "crio" CRIO_VERSION: "1.32.1" DISABLE_AMP_IMAGE_BUILD: true ETCD_VERSION: "v3.6.11" ETCD_SHA256: "8756f7a4eaf921668a83de0bf13c0f65cae9186a165696e3ae8396afe6f557ed" FIXED_RANGE: 10.1.0.0/20 K8S_CAPI_VERSION: "v1.10.4" K8S_CAPO_VERSION: "v0.11.3" # CAPI mgmt cluster kubernetes version K8S_VERSION: "1.34.7" K8S_TOKEN: "0cvq6p.k454yiandj1tjxxv" K8S_COPY_KUBECONFIG_PATH: "/etc/magnum/kubeconfig" MAGNUM_GUEST_IMAGE_URL: "https://github.com/vexxhost/capo-image-elements/releases/download/{{ kube_version_dir }}/ubuntu-24.04-{{ kube_version }}.qcow2" MAGNUM_GUEST_IMAGE_EXTRA_PROPERTIES: "--property kube_version={{ kube_version }}" OCTAVIA_NODE: api devstack_local_conf: post-config: $MAGNUM_CONF: capi_helm: default_helm_chart_version: "0.23.0" test-config: $TEMPEST_CONFIG: magnum: image_id: "ubuntu-24.04-{{ kube_version }}" labels: octavia_provider: "ovn" master_lb_floating_ip_enabled: "true" devstack_plugins: devstack-plugin-container: https://opendev.org/openstack/devstack-plugin-container magnum: https://review.opendev.org/openstack/magnum magnum-capi-helm: https://opendev.org/openstack/magnum-capi-helm octavia: https://opendev.org/openstack/octavia ovn-octavia-provider: https://github.com/openstack/ovn-octavia-provider devstack_services: etcd3: true container: true k8s-master: true k8s-capi: true octavia: true o-api: true o-cw: true o-da: true o-hk: true o-hm: true - job: name: magnum-capi-helm-tests-cluster-calico parent: magnum-capi-helm-tests-cluster vars: devstack_local_conf: test-config: $TEMPEST_CONFIG: magnum: network_driver: calico - job: name: magnum-capi-helm-tests-cluster-cilium parent: magnum-capi-helm-tests-cluster vars: devstack_local_conf: post-config: $MAGNUM_CONF: cluster_template: kubernetes_allowed_network_drivers: flannel,calico,cilium test-config: $TEMPEST_CONFIG: magnum: network_driver: cilium ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/zuul.d/nodeset.yaml0000664000175000017500000000027315212012064017364 0ustar00zuulzuul- nodeset: name: magnum-ubuntu-noble-32GB nodes: - name: controller label: ubuntu-noble-32GB groups: - name: tempest nodes: - controller ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/zuul.d/project-templates-tempest.yaml0000664000175000017500000000434315212012064023046 0ustar00zuulzuul--- - project-template: name: magnum-capi-helm-tests-cluster-v1_34 description: | Runs Magnum CAPI Helm with Magnum master check: jobs: - magnum-capi-helm-tests-cluster-calico-v1-34 - magnum-capi-helm-tests-cluster-cilium-v1-34 gate: jobs: - magnum-capi-helm-tests-cluster-calico-v1-34 - magnum-capi-helm-tests-cluster-cilium-v1-34 - project-template: name: magnum-capi-helm-tests-cluster description: | Runs Magnum CAPI Helm with Magnum master check: jobs: - magnum-capi-helm-tests-cluster-calico-v1-34 - magnum-capi-helm-tests-cluster-cilium-v1-34 - magnum-capi-helm-tests-cluster-calico-v1-35 - magnum-capi-helm-tests-cluster-cilium-v1-35 gate: jobs: - magnum-capi-helm-tests-cluster-calico-v1-34 - magnum-capi-helm-tests-cluster-cilium-v1-34 - magnum-capi-helm-tests-cluster-calico-v1-35 - magnum-capi-helm-tests-cluster-cilium-v1-35 - project-template: name: magnum-capi-helm-tests-cluster-2026-1 description: | Runs Magnum CAPI Helm with Magnum master check: jobs: - magnum-capi-helm-tests-cluster-calico-v1-34-2026-1 - magnum-capi-helm-tests-cluster-calico-v1-35-2026-1 gate: jobs: - magnum-capi-helm-tests-cluster-calico-v1-34-2026-1 - magnum-capi-helm-tests-cluster-calico-v1-35-2026-1 - project-template: name: magnum-capi-helm-tests-cluster-2025-2 description: | Runs Magnum CAPI Helm with Magnum master check: jobs: - magnum-capi-helm-tests-cluster-calico-v1-34-2025-2 - magnum-capi-helm-tests-cluster-calico-v1-35-2025-2 gate: jobs: - magnum-capi-helm-tests-cluster-calico-v1-34-2025-2 - magnum-capi-helm-tests-cluster-calico-v1-35-2025-2 - project-template: name: magnum-capi-helm-tests-cluster-2025-1 description: | Runs Magnum CAPI Helm with Magnum master check: jobs: - magnum-capi-helm-tests-cluster-calico-v1-34-2025-1 - magnum-capi-helm-tests-cluster-calico-v1-35-2025-1 gate: jobs: - magnum-capi-helm-tests-cluster-calico-v1-34-2025-1 - magnum-capi-helm-tests-cluster-calico-v1-35-2025-1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/zuul.d/project-templates.yaml0000664000175000017500000000464615212012064021375 0ustar00zuulzuul--- - project-template: name: openstack-cover-jobs-magnum description: | The standard openstack-cover-jobs but with magnum added as a required-projects. This checks out the project code and allows tox_install_siblings to use it when building the venv. check: jobs: - openstack-tox-cover: required-projects: - openstack/magnum - project-template: name: openstack-python3-jobs-magnum description: | The standard :zuul:project_template:`openstack-python3-jobs` but with magnum added as a required-projects. This checks out the project code and allows tox_install_siblings to use magnum when building the venv. py310 is in 2023.1/2023.2/2024.1/2024.2/2025.1/2025.2/2026.1 PTI py311 is in 2024.1/2024.2/2025.1/2025.2/2026.1 PTI py312 is in 2025.1/2025.2/2026.1 PTI py313 is in 2025.2/2026.1 PTI check: jobs: - openstack-tox-pep8: required-projects: - openstack/magnum - openstack-tox-py310: required-projects: - name: openstack/magnum override-checkout: stable/2026.1 - name: openstack/requirements override-checkout: stable/2026.1 - openstack-tox-py311: required-projects: - openstack/magnum - openstack-tox-py312: required-projects: - openstack/magnum - openstack-tox-py313: required-projects: - openstack/magnum - openstack-tox-py314: required-projects: - openstack/magnum gate: jobs: - openstack-tox-pep8: required-projects: - openstack/magnum - openstack-tox-py310: required-projects: - name: openstack/magnum override-checkout: stable/2026.1 - name: openstack/requirements override-checkout: stable/2026.1 - openstack-tox-py311: required-projects: - openstack/magnum - openstack-tox-py312: required-projects: - openstack/magnum - openstack-tox-py313: required-projects: - openstack/magnum - openstack-tox-py314: required-projects: - openstack/magnum post: jobs: - publish-openstack-python-branch-tarball ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/zuul.d/project.yaml0000664000175000017500000000056115212012064017371 0ustar00zuulzuul--- - project: queue: magnum templates: - magnum-capi-helm-tests-cluster - magnum-capi-helm-tests-cluster-2026-1 - magnum-capi-helm-tests-cluster-2025-2 - magnum-capi-helm-tests-cluster-2025-1 - openstack-cover-jobs-magnum - openstack-python3-jobs-magnum - release-notes-jobs-python3 - publish-openstack-docs-pti ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/zuul.d/tempest-jobs-v1-34.yaml0000664000175000017500000000230415212012064021104 0ustar00zuulzuul--- - job: name: magnum-capi-helm-tests-cluster-calico-v1-34 parent: magnum-capi-helm-tests-cluster-calico vars: kube_version: "v1.34.8" kube_version_dir: "2026.05-6" - job: name: magnum-capi-helm-tests-cluster-cilium-v1-34 parent: magnum-capi-helm-tests-cluster-cilium vars: kube_version: "v1.34.8" kube_version_dir: "2026.05-6" - job: name: magnum-capi-helm-tests-cluster-v1_34 parent: magnum-capi-helm-tests-cluster-calico vars: kube_version: "v1.34.8" kube_version_dir: "2026.05-6" - job: name: magnum-capi-helm-tests-cluster-calico-v1-34-2025-1 parent: magnum-capi-helm-tests-cluster-calico-v1-34 required-projects: - name: openstack/magnum override-checkout: stable/2025.1 - job: name: magnum-capi-helm-tests-cluster-calico-v1-34-2025-2 parent: magnum-capi-helm-tests-cluster-calico-v1-34 required-projects: - name: openstack/magnum override-checkout: stable/2025.2 - job: name: magnum-capi-helm-tests-cluster-calico-v1-34-2026-1 parent: magnum-capi-helm-tests-cluster-calico-v1-34 required-projects: - name: openstack/magnum override-checkout: stable/2026.1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781011508.0 magnum_capi_helm-1.4.0/zuul.d/tempest-jobs-v1-35.yaml0000664000175000017500000000201715212012064021106 0ustar00zuulzuul--- - job: name: magnum-capi-helm-tests-cluster-calico-v1-35 parent: magnum-capi-helm-tests-cluster-calico vars: kube_version: "v1.35.5" kube_version_dir: "2026.05-6" - job: name: magnum-capi-helm-tests-cluster-cilium-v1-35 parent: magnum-capi-helm-tests-cluster-cilium vars: kube_version: "v1.35.5" kube_version_dir: "2026.05-6" - job: name: magnum-capi-helm-tests-cluster-calico-v1-35-2025-1 parent: magnum-capi-helm-tests-cluster-calico-v1-35 required-projects: - name: openstack/magnum override-checkout: stable/2025.1 - job: name: magnum-capi-helm-tests-cluster-calico-v1-35-2025-2 parent: magnum-capi-helm-tests-cluster-calico-v1-35 required-projects: - name: openstack/magnum override-checkout: stable/2025.2 - job: name: magnum-capi-helm-tests-cluster-calico-v1-35-2026-1 parent: magnum-capi-helm-tests-cluster-calico-v1-35 required-projects: - name: openstack/magnum override-checkout: stable/2026.1