From dfb5b270cb8d0f7e5c174f356db3f9e4bca9d582 Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 02:30:26 +0100 Subject: [PATCH 01/42] refactor(formula): align to template-formula & fix #19 --- AUTHORS.md | 2 - FORMULA | 9 + Gemfile | 6 + LICENSE | 20 +- README.rst | 269 ------- bin/kitchen | 29 + commitlint.config.js | 3 + docs/CONTRIBUTING.rst | 209 +++++ docs/CONTRIBUTING_DOCS.rst | 96 +++ docs/README.rst | 231 ++++++ docs/_static/css/custom.css | 21 + docs/conf.py | 173 ++++ docs/index.rst | 20 + iscsi/clean.sls | 7 + iscsi/defaults.yaml | 101 ++- iscsi/init.sls | 2 + iscsi/initiator/clean.sls | 9 + iscsi/initiator/config/clean.sls | 16 + .../initiator/config/files/default/iscsi.tmpl | 26 + .../config/files/default/open-iscsi.tmpl | 21 + iscsi/initiator/config/init.sls | 5 + iscsi/initiator/config/install.sls | 31 + iscsi/initiator/defaults.yaml | 88 --- iscsi/initiator/init.sls | 8 +- iscsi/initiator/install.sls | 143 ---- iscsi/initiator/kernel/clean.sls | 30 + iscsi/initiator/kernel/init.sls | 5 + iscsi/initiator/kernel/install.sls | 39 + iscsi/initiator/make/clean.sls | 21 + iscsi/initiator/make/init.sls | 5 + iscsi/initiator/make/install.sls | 55 ++ iscsi/initiator/package/clean.sls | 20 + iscsi/initiator/package/init.sls | 5 + iscsi/initiator/package/install.sls | 37 + iscsi/initiator/remove.sls | 47 -- iscsi/initiator/service/clean.sls | 25 + iscsi/initiator/service/init.sls | 5 + iscsi/initiator/service/install.sls | 58 ++ iscsi/isns/clean.sls | 8 + iscsi/isns/config/clean.sls | 28 + iscsi/isns/config/files/default/isns.tmpl | 21 + iscsi/isns/config/init.sls | 5 + iscsi/isns/config/install.sls | 83 ++ iscsi/isns/defaults.yaml | 36 - iscsi/isns/init.sls | 7 +- iscsi/isns/install.sls | 96 --- iscsi/isns/make/clean.sls | 21 + iscsi/isns/make/init.sls | 5 + iscsi/isns/make/install.sls | 55 ++ iscsi/isns/package/clean.sls | 20 + iscsi/isns/package/init.sls | 5 + iscsi/isns/package/install.sls | 37 + iscsi/isns/remove.sls | 30 - iscsi/isns/service/clean.sls | 17 + iscsi/isns/service/init.sls | 5 + iscsi/isns/service/install.sls | 48 ++ iscsi/libtofs.jinja | 112 +++ iscsi/man5.jinja | 32 - iscsi/map.jinja | 76 +- iscsi/{codenamemap.yaml => oscodename.yaml} | 52 +- iscsi/osfamilymap.yaml | 182 +++-- iscsi/remove.sls | 5 - iscsi/target/clean.sls | 9 + iscsi/target/config/clean.sls | 16 + iscsi/target/config/files/default/ctld.tmpl | 27 + iscsi/target/config/files/default/ietd.tmpl | 26 + iscsi/target/config/files/default/lio.tmpl | 38 + iscsi/target/config/files/default/tgtd.tmpl | 28 + iscsi/target/config/init.sls | 5 + iscsi/target/config/install.sls | 31 + iscsi/target/defaults.yaml | 149 ---- iscsi/target/init.sls | 8 +- iscsi/target/install.sls | 145 ---- iscsi/target/kernel/clean.sls | 30 + iscsi/target/kernel/init.sls | 5 + iscsi/target/kernel/install.sls | 42 + iscsi/target/make/clean.sls | 21 + iscsi/target/make/init.sls | 5 + iscsi/target/make/install.sls | 55 ++ iscsi/target/package/clean.sls | 20 + iscsi/target/package/init.sls | 5 + iscsi/target/package/install.sls | 37 + iscsi/target/remove.sls | 47 -- iscsi/target/service/clean.sls | 25 + iscsi/target/service/init.sls | 5 + iscsi/target/service/install.sls | 57 ++ kitchen.yml | 191 +++++ pillar.example | 739 ++++++++++++++---- pre-commit_semantic-release.sh | 30 + release-rules.js | 18 + release.config.js | 106 +++ test/integration/default/README.md | 50 ++ .../default/controls/config_spec.rb | 4 + .../default/controls/packages_spec.rb | 4 + .../default/controls/services_spec.rb | 6 + .../controls/subcomponent_config_spec.rb | 4 + test/integration/default/inspec.yml | 17 + test/integration/freebsd_disks.sh | 9 - test/salt/pillar/centos6.sls | 46 ++ test/salt/pillar/define_roles.sls | 7 + 100 files changed, 3506 insertions(+), 1444 deletions(-) delete mode 100644 AUTHORS.md create mode 100644 FORMULA create mode 100644 Gemfile delete mode 100644 README.rst create mode 100755 bin/kitchen create mode 100644 commitlint.config.js create mode 100644 docs/CONTRIBUTING.rst create mode 100644 docs/CONTRIBUTING_DOCS.rst create mode 100644 docs/README.rst create mode 100644 docs/_static/css/custom.css create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 iscsi/clean.sls create mode 100644 iscsi/initiator/clean.sls create mode 100644 iscsi/initiator/config/clean.sls create mode 100644 iscsi/initiator/config/files/default/iscsi.tmpl create mode 100644 iscsi/initiator/config/files/default/open-iscsi.tmpl create mode 100644 iscsi/initiator/config/init.sls create mode 100644 iscsi/initiator/config/install.sls delete mode 100644 iscsi/initiator/defaults.yaml delete mode 100644 iscsi/initiator/install.sls create mode 100644 iscsi/initiator/kernel/clean.sls create mode 100644 iscsi/initiator/kernel/init.sls create mode 100644 iscsi/initiator/kernel/install.sls create mode 100644 iscsi/initiator/make/clean.sls create mode 100644 iscsi/initiator/make/init.sls create mode 100644 iscsi/initiator/make/install.sls create mode 100644 iscsi/initiator/package/clean.sls create mode 100644 iscsi/initiator/package/init.sls create mode 100644 iscsi/initiator/package/install.sls delete mode 100644 iscsi/initiator/remove.sls create mode 100644 iscsi/initiator/service/clean.sls create mode 100644 iscsi/initiator/service/init.sls create mode 100644 iscsi/initiator/service/install.sls create mode 100644 iscsi/isns/clean.sls create mode 100644 iscsi/isns/config/clean.sls create mode 100644 iscsi/isns/config/files/default/isns.tmpl create mode 100644 iscsi/isns/config/init.sls create mode 100644 iscsi/isns/config/install.sls delete mode 100644 iscsi/isns/defaults.yaml delete mode 100644 iscsi/isns/install.sls create mode 100644 iscsi/isns/make/clean.sls create mode 100644 iscsi/isns/make/init.sls create mode 100644 iscsi/isns/make/install.sls create mode 100644 iscsi/isns/package/clean.sls create mode 100644 iscsi/isns/package/init.sls create mode 100644 iscsi/isns/package/install.sls delete mode 100644 iscsi/isns/remove.sls create mode 100644 iscsi/isns/service/clean.sls create mode 100644 iscsi/isns/service/init.sls create mode 100644 iscsi/isns/service/install.sls create mode 100644 iscsi/libtofs.jinja delete mode 100644 iscsi/man5.jinja rename iscsi/{codenamemap.yaml => oscodename.yaml} (60%) delete mode 100644 iscsi/remove.sls create mode 100644 iscsi/target/clean.sls create mode 100644 iscsi/target/config/clean.sls create mode 100644 iscsi/target/config/files/default/ctld.tmpl create mode 100644 iscsi/target/config/files/default/ietd.tmpl create mode 100644 iscsi/target/config/files/default/lio.tmpl create mode 100644 iscsi/target/config/files/default/tgtd.tmpl create mode 100644 iscsi/target/config/init.sls create mode 100644 iscsi/target/config/install.sls delete mode 100644 iscsi/target/defaults.yaml delete mode 100644 iscsi/target/install.sls create mode 100644 iscsi/target/kernel/clean.sls create mode 100644 iscsi/target/kernel/init.sls create mode 100644 iscsi/target/kernel/install.sls create mode 100644 iscsi/target/make/clean.sls create mode 100644 iscsi/target/make/init.sls create mode 100644 iscsi/target/make/install.sls create mode 100644 iscsi/target/package/clean.sls create mode 100644 iscsi/target/package/init.sls create mode 100644 iscsi/target/package/install.sls delete mode 100644 iscsi/target/remove.sls create mode 100644 iscsi/target/service/clean.sls create mode 100644 iscsi/target/service/init.sls create mode 100644 iscsi/target/service/install.sls create mode 100644 kitchen.yml create mode 100755 pre-commit_semantic-release.sh create mode 100644 release-rules.js create mode 100644 release.config.js create mode 100644 test/integration/default/README.md create mode 100644 test/integration/default/controls/config_spec.rb create mode 100644 test/integration/default/controls/packages_spec.rb create mode 100644 test/integration/default/controls/services_spec.rb create mode 100644 test/integration/default/controls/subcomponent_config_spec.rb create mode 100644 test/integration/default/inspec.yml delete mode 100644 test/integration/freebsd_disks.sh create mode 100644 test/salt/pillar/centos6.sls create mode 100644 test/salt/pillar/define_roles.sls diff --git a/AUTHORS.md b/AUTHORS.md deleted file mode 100644 index a2b3a8e4..00000000 --- a/AUTHORS.md +++ /dev/null @@ -1,2 +0,0 @@ -- 0xf10e -- noelmcloughlin diff --git a/FORMULA b/FORMULA new file mode 100644 index 00000000..287336da --- /dev/null +++ b/FORMULA @@ -0,0 +1,9 @@ +name: iscsi +os: Debian, Ubuntu, Raspbian, RedHat, Fedora, CentOS, Suse, openSUSE, Gentoo, Funtoo, Arch, Manjaro, Alpine, FreeBSD, OpenBSD, Solaris, SmartOS, Windows, MacOS +os_family: Debian, RedHat, Suse, Gentoo, Arch, Alpine, FreeBSD, OpenBSD, Solaris, Windows, MacOS +version: 3.3.0 +release: 1 +minimum_version: 2017.7 +summary: iscsi formula +description: Formula to use iscsi technologies +top_level_dir: iscsi diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..3b36de32 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem 'kitchen-docker', '>= 2.9' +gem 'kitchen-salt', '>= 0.6.0' +gem 'kitchen-inspec', '>= 1.1' + diff --git a/LICENSE b/LICENSE index abe9a365..3e21c72f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,13 @@ -Copyright 2015 TU Berlin and Fraunhofer FOKUS + Copyright (c) 2014 Salt Stack Formulas -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 + 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 + 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. + 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. diff --git a/README.rst b/README.rst deleted file mode 100644 index ff06bfe0..00000000 --- a/README.rst +++ /dev/null @@ -1,269 +0,0 @@ -====== -iSCSI -====== - -Configure iSCSI targets and initiator on GNU/Linux and FreeBSD. - -.. note:: - - See the full `Salt Formulas installation and usage instructions - `_. - -Available states -================ - -.. contents:: - :local: - -``iscsi`` ----------- -Meta-state to deploy iSNS and iSCSI solution. - -``iscsi.isns`` ---------------------- -Configure the iSNS discovery service for iSCSI and iFCP. - -``iscsi.target`` ---------------------- -Install and configure the iSCSI Target service. Supported vendor implementations include- - -- ``/etc/ctl.conf`` for ``ctld(8)`` on FreeBSD -- ``/etc/target/saveconfig.json` for ``LIOkernel target`` on GNU/Linux -- ``/etc/targets.conf`` for ``tgt(8)`` on GNU/Linux (largely obsolete) -- ``/etc/ietd.conf`` for `ietd(8)`` on GNU/Linux (largely obsolete) - -``iscsi.initiator`` ------------------- -Install and configure the iSCSI initiator service. Supported vendor implementations include- - -- ``/etc/iscsi.conf`` for FreeBSD -- ``/etc/iscsi/iscsid.conf`` or ``~/.iscsid.conf`` for ``open-iscsi`` on GNU/Linux - -``iscsi.remove`` ----------- -Meta-state to remove iSNS and iSCSI solution. - -``iscsi.isns.remove`` ---------------------- -Remove iSNS software (`open-isns`). - -``iscsi.target.remove`` ---------------------- -Remove iSCSI target software (``ctld`` or ``tgt`` or ``ietd``). - -``iscsi.initiator.remove`` --------------------------- -Remove iSCSI initiator software (`iscsdi` or `open-iscsi`). - -OS families -============ -Works on FreeBSD, RedHat, Debian, Arch, and Suse os families. The ``iscis.target`` state for Linux (``LIO target``) requires the ``target_core_mod`` kernel module, which some vagrant, and all container images, lack. - -Archlinux ---------- -AUR iscsi packages are supported. Run `users` state (users-formula) to create required ``iscsimake`` user (gid 0) on "Archlinux" before "iscsi-formula" states run. See example pillar data:: - - iscsi: - {% if grains.os == 'Arch' %} - user: iscsimake #already defined in initiator/defaults.yaml - - users: - iscsimake: - sudouser: True - shell: /bin/bash - empty_password: True - home: /home/iscsimake - createhome: True - optional_groups: - - wheel - - root - sudo_rules: - - 'ALL=(ALL) ALL' - {% endif %} - - -Good Pillar data -================= -As with any daemon, bad iscsi conf(5)iguration causes issues. Bad pillar data results in daemon segfault, and problem with jinja 'max recursion exceeded' errors. Sanity check your pillar data to avoid unexpected failures - -Potential useful future features -================================ -- iscsi-plugin: https://github.com/projectatomic/iscsi-iscsi-plugin -- k8s-plugin: https://github.com/rootfs/kubernetes -- fcoe -- running in docker container - - -GNU/Linux sample pillar -======================== -The state ``iscsi.target`` generates (``lio`` or ``tgt`` or ``ietd``) configuration, and the `iscsi.initiator` state generates (``open-iscsi``) configuration, for both FreeBSD and GNU/Linux. - -Pillar-data:: - - iscsi: - isns: - enabled: false - target: - lio: - myconf: - fabric-modules: - discovery_enable_auth: 'true' - discovery_mutual_password: "itsreallyme" - discovery_mutual_userid: "target" - discovery_password: "letmein" - discovery_userid: "initiator" - name: "iscsi" - storage-objects: - attributes: - block_size: 1024 - emulate_write_cache: 0 - max_sectors: 1024 - queue_depth: 128 - task_timeout: 0 - unmap_granularity: 0 - dev: "/dev/vg_storage/station4mp" - name: "mptarget4" - plugin: "block" - wwn: "6be30fb6-3bc9-43c4-a866-4d8633af5cf2" - targets: - fabric: iscsi - tpgs: - attributes: - authentication: 1 - cache_dynamic_acls: 0 - default_cmdsn_depth: 16 - demo_mode_write_protect: 1 - generate_node_acls: 0 - login_timeout: 15 - netif_timeout: 2 - prod_mode_write_protect: 0 - luns: - index: 0 - storage_object: "/backstores/block/mptarget4" - node_acls: - attributes: - dataout_timeout: 3 - dataout_timeout_retries: 5 - default_erl: 0 - nopin_response_timeout: 5 - nopin_timeout: 5 - random_datain_pdu_offsets: 0 - random_datain_seq_offsets: 0 - random_r2t_offsets: 0 - chap_mutual_password: "itsreallyme" - chap_mutual_userid: "target" - chap_password: "letmein" - chap_userid: "station4" - mapped_luns: - index: 0 - write_protect: 'false' - tpg_lun: 0 - node_wwn: "iqn.1994-05.com.redhat:station4" - tcq_depth: 16 - portals: - ip_address: "10.100.0.199" - port: 3260 - tag: 1 - wwn: "iqn.2003-01.org.linux-iscsi.storage:mptarget4" - - -FreeBSD sample pillar -====================== -The state ``iscsi.target`` generates ``/etc/ctl.conf`` for ``ctld(8)`` and enables the service. - -Pillar-data:: - - iscsi: - target: - ctld: - myconf: - isns-server: - - 'localhost' - worstdevs: - auth-type: 'none' - bestdevs: - chap-mutual: - - user = 'user' - - secret = 'secretsecret' - - mutual-user = "mutualuser" - - mutual-secret = "mutualsecret" - initiator-name: - - 'iqn.2012-06.com.example:initiatorhost1' - - 'iqn.2012-06.com.example:initiatorhost2' - initiator-portal: - - 192.168.1.1/16 - - '[2001:db8::de:ef]' - portal-group: - cloud-west-zone0: - discovery-auth-group: no-authentication - listen: - - '0.0.0.0:3260' - - '[::]:3260' - - '[fe80::be:ef]:3261' - lun: - example0: - Alias: 0 - path: /dev/zvol/tank/example_0 - blocksize: 4096 - size: 1G - example1: - Alias: nice1 - path: /dev/zvol/tank/example_1 - option: - - 'naa 0x50015178f369f093' - example2: - Alias: sillyexample2 - backend: block - path: /dev/zvol/tank/example_0block_backends - device-type: 0 - size: 5G - option: - vendor: myvendor - ha_role: primary - readcache: on - readonly: on - rpm: 0 - umap: on - writecache: on - file: /dev/sd - 3: - Alias: myfile - path: /tmp/myfile - size: 1G - target: - 'iqn.2008-04.com.example:target0': - Alias: bestdevs-cloudstore - auth-group: bestdevs - portal-group: cloud-west-zone0 - lun: - - name = example0 - 'naa.50015178f369f092': - port: - - isp0 - - isp1 - portal-group: cloud-west-zone0 - lun: - - name = example1 - 'iqn.2008-04.com.example:target1': - alias: lazydevs-cloudstore - auth-group: no-authentication - portal-group: cloud-west-zone0 - lun: - - name = example2 - - name = example3 - initiator: - iscsid: - myconf: - node.startup: automatic - 'iqn.2018-07.com.example.iscsi:example01': - targetAddress: '10.10.10.10' - 'naa.50015178f369f092': - targetAddress: data1.example.com - chapIName: user - chapSecret: secretsecret - 'iqn.2018-07.com.example.iscsi:secretdata1': - targetAddress: creditcards.example.com - authMethod: CHAP - chapIName: 'iqn.2018-07.com.example.iscsi:trustedguy' - chapSecret: secretsecret - diff --git a/bin/kitchen b/bin/kitchen new file mode 100755 index 00000000..1cd44f3a --- /dev/null +++ b/bin/kitchen @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kitchen' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("test-kitchen", "kitchen") diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 00000000..2f9d1aa0 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], +}; diff --git a/docs/CONTRIBUTING.rst b/docs/CONTRIBUTING.rst new file mode 100644 index 00000000..0e447189 --- /dev/null +++ b/docs/CONTRIBUTING.rst @@ -0,0 +1,209 @@ +.. _contributing: + +How to contribute +================= + +This document will eventually outline all aspects of guidance to make your contributing experience a fruitful and enjoyable one. +What it already contains is information about *commit message formatting* and how that directly affects the numerous automated processes that are used for this repo. +It also covers how to contribute to this *formula's documentation*. + +.. contents:: **Table of Contents** + +Overview +-------- + +Submitting a pull request is more than just code! +To achieve a quality product, the *tests* and *documentation* need to be updated as well. +An excellent pull request will include these in the changes, wherever relevant. + +Commit message formatting +------------------------- + +Since every type of change requires making Git commits, +we will start by covering the importance of ensuring that all of your commit +messages are in the correct format. + +Automation of multiple processes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This formula uses `semantic-release `_ for automating numerous processes such as bumping the version number appropriately, creating new tags/releases and updating the changelog. +The entire process relies on the structure of commit messages to determine the version bump, which is then used for the rest of the automation. + +Full details are available in the upstream docs regarding the `Angular Commit Message Conventions `_. +The key factor is that the first line of the commit message must follow this format: + +.. code-block:: + + type(scope): subject + + +* E.g. ``docs(contributing): add commit message formatting instructions``. + +Besides the version bump, the changelog and release notes are formatted accordingly. +So based on the example above: + +.. + + .. raw:: html + +

Documentation

+ + * **contributing:** add commit message formatting instructions + + +* The ``type`` translates into a ``Documentation`` sub-heading. +* The ``(scope):`` will be shown in bold text without the brackets. +* The ``subject`` follows the ``scope`` as standard text. + +Linting commit messages in Travis CI +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This formula uses `commitlint `_ for checking commit messages during CI testing. +This ensures that they are in accordance with the ``semantic-release`` settings. + +For more details about the default settings, refer back to the ``commitlint`` `reference rules `_. + +Relationship between commit type and version bump +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This formula applies some customisations to the defaults, as outlined in the table below, +based upon the `type `_ of the commit: + +.. list-table:: + :name: commit-type-vs-version-bump + :header-rows: 1 + :stub-columns: 0 + :widths: 1,2,3,1,1 + + * - Type + - Heading + - Description + - Bump (default) + - Bump (custom) + * - ``build`` + - Build System + - Changes related to the build system + - – + - + * - ``chore`` + - – + - Changes to the build process or auxiliary tools and libraries such as + documentation generation + - – + - + * - ``ci`` + - Continuous Integration + - Changes to the continuous integration configuration + - – + - + * - ``docs`` + - Documentation + - Documentation only changes + - – + - 0.0.1 + * - ``feat`` + - Features + - A new feature + - 0.1.0 + - + * - ``fix`` + - Bug Fixes + - A bug fix + - 0.0.1 + - + * - ``perf`` + - Performance Improvements + - A code change that improves performance + - 0.0.1 + - + * - ``refactor`` + - Code Refactoring + - A code change that neither fixes a bug nor adds a feature + - – + - 0.0.1 + * - ``revert`` + - Reverts + - A commit used to revert a previous commit + - – + - 0.0.1 + * - ``style`` + - Styles + - Changes that do not affect the meaning of the code (white-space, + formatting, missing semi-colons, etc.) + - – + - 0.0.1 + * - ``test`` + - Tests + - Adding missing or correcting existing tests + - – + - 0.0.1 + +Use ``BREAKING CHANGE`` to trigger a ``major`` version change +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Adding ``BREAKING CHANGE`` to the footer of the extended description of the commit message will **always** trigger a ``major`` version change, no matter which type has been used. +This will be appended to the changelog and release notes as well. +To preserve good formatting of these notes, the following format is prescribed: + +* ``BREAKING CHANGE: .`` + +An example of that: + +.. code-block:: git + + ... + + BREAKING CHANGE: With the removal of all of the `.sls` files under + `iscsi package`, this formula no longer supports the installation of + packages. + + +Semantic release formulas +------------------------- + +These formulas are already compatible with semantic-release: + +* `apt-formula `_ +* `bind-formula `_ +* `cert-formula `_ +* `chrony-formula `_ +* `collectd-formula `_ +* `cron-formula `_ +* `deepsea-formula `_ +* `dhcpd-formula `_ +* `fail2ban-formula `_ +* `golang-formula `_ +* `grafana-formula `_ +* `influxdb-formula `_ +* `iptables-formula `_ +* `iscsi-formula `_ +* `keepalived-formula `_ +* `libvirt-formula `_ +* `locale-formula `_ +* `logrotate-formula `_ +* `mysql-formula `_ +* `nginx-formula `_ +* `openvpn-formula `_ +* [`WIP `_] `packages-formula `_ +* `php-formula `_ +* `postfix-formula `_ +* `postgres-formula `_ +* `prometheus-formula `_ +* `rkhunter-formula `_ +* `salt-formula `_ +* `sudoers-formula `_ +* `sysctl-formula `_ +* `syslog-ng-formula `_ +* `sysstat-formula `_ +* `systemd-formula `_ +* `timezone-formula `_ +* `ufw-formula `_ +* `users-formula `_ +* `vault-formula `_ +* `vim-formula `_ +* `vsftpd-formula `_ + +Documentation +------------- + +`Documentation contributing guidelines `_ diff --git a/docs/CONTRIBUTING_DOCS.rst b/docs/CONTRIBUTING_DOCS.rst new file mode 100644 index 00000000..5b59b298 --- /dev/null +++ b/docs/CONTRIBUTING_DOCS.rst @@ -0,0 +1,96 @@ +.. _contributing_docs: + +Contributing documentation +========================== + +|docs| + +.. |docs| image:: https://readthedocs.org/projects/docs/badge/?version=latest + :alt: Documentation Status + :scale: 100% + :target: https://template-formula.readthedocs.io/en/latest/?badge=latest + +Toolchain +^^^^^^^^^ + +The documentation for this formula is written in +`reStructuredText `_ +(also known as RST, ReST, or reST). +It is built by +`Sphinx `_ +and hosted on +`Read the Docs `_. + +Adding a new page +^^^^^^^^^^^^^^^^^ + +Adding a new page involves two steps: + +#. Use the + :ref:`provided page template ` + to create a new page. +#. Add the page name under the ``toctree`` list in ``index.rst``. + + a. Do not just append it to the list. + #. Select the best place where it fits within the overall documentation. + +SaltStack-Formulas' RST page template +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. _saltstack_formulas_rst_page_template + +Use the following template when creating a new page. +This ensures consistency across the documentation for this formula. +The heading symbols have been selected in accordance to the output rendered by the +`Markdown to reStructuredText converter `_ +we are using for some of the pages of this documentation. + +.. code-block:: rst + + .. _template: + + [Page title] + ============ + + [Introductory paragraph] + + .. contents:: **Table of Contents** + + [Heading 2] + ----------- + + [Heading 3] + ^^^^^^^^^^^ + + [Heading 4] + ~~~~~~~~~~~ + + [Heading 5] + """"""""""" + + [Heading 6] + ########### + +#. The first line is an anchor that can be used to link back to (the top of) + this file. + + a. Change this to be the lowercase version of the file name. + #. Do not include the ``.rst`` file extension. + #. Use hyphens (``-``) instead of spaces or non-letter characters. + +#. Change the ``[Page title]`` accordingly, matching the same number of equals + signs (``=``) underneath. +#. Change the ``[Introductory paragraph]`` to be a short summary of the page + content. + Use no more than three paragraphs for this. +#. Leave the ``..contents:: **Table of Contents**`` line as it is. +#. Use the remaining headings as required to break up the page content. + + a. You will rarely need to use beyond ``[Heading 4]``. + #. Again, no single heading should have more than about three paragraphs of + content before the next heading or sub-heading is used. + +Obviously, it is not necessary to follow the steps in the order above. +For example, it is usually easier to write the ``[Introductory paragraph]`` +at the end. + diff --git a/docs/README.rst b/docs/README.rst new file mode 100644 index 00000000..56f2e5d2 --- /dev/null +++ b/docs/README.rst @@ -0,0 +1,231 @@ +.. _readme: + +iscsi-formula +================ + +|img_travis| |img_sr| + +.. |img_travis| image:: https://travis-ci.com/saltstack-formulas/iscsi-formula.svg?branch=master + :alt: Travis CI Build Status + :scale: 100% + :target: https://travis-ci.com/saltstack-formulas/iscsi-formula +.. |img_sr| image:: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg + :alt: Semantic Release + :scale: 100% + :target: https://github.com/semantic-release/semantic-release + +A SaltStack formula that is empty. It has dummy content to help with a quick +start on a new formula and it serves as a style guide. + +.. contents:: **Table of Contents** + +General notes +------------- + +See the full `SaltStack Formulas installation and usage instructions +`_. + +If you are interested in writing or contributing to formulas, please pay attention to the `Writing Formula Section +`_. + +If you want to use this formula, please pay attention to the ``FORMULA`` file and/or ``git tag``, +which contains the currently released version. This formula is versioned according to `Semantic Versioning `_. + +See `Formula Versioning Section `_ for more details. + +Contributing to this repo +------------------------- + +**Commit message formatting is significant!!** + +Please see :ref:`How to contribute ` for more details. + +Available states +---------------- + +.. contents:: + :local: + +``iscsi`` +^^^^^^^^^^^^ + +*Meta-state (This is a state that includes other states)*. + +Deploy iSNS, iSCSI initiator, and iSCSI target +packages, manage configuration files and then +starts the associated iscsi services. + +``iscsi.target`` +--------------------- +Install and configure the iSCSI Target service. Supported vendor implementations include- + +- ``/etc/ctl.conf`` for ``ctld(8)`` on FreeBSD +- ``/etc/target/saveconfig.json` for ``LIOkernel target`` on GNU/Linux +- ``/etc/targets.conf`` for ``tgt(8)`` on GNU/Linux (largely obsolete) +- ``/etc/ietd.conf`` for `ietd(8)`` on GNU/Linux (largely obsolete) + +``iscsi.initiator`` +------------------ +Install and configure the iSCSI initiator service. Supported providers include: + +- ``/etc/iscsi.conf`` for FreeBSD +- ``/etc/iscsi/iscsid.conf`` or ``~/.iscsid.conf`` for ``Open iSCSI`` on GNU/Linux + +``iscsi.isns`` +^^^^^^^^^^^^^^ +Install and configure iSCSI name service. + +``iscsi.target.package`` +^^^^^^^^^^^^^^^^^^^^^^^^ + +Install iSCSI target packages. + +``iscsi.target.config`` +^^^^^^^^^^^^^^^^^^^^^^^ + +Customises iscsi target configuration. Requires ``iscsi.target.package`` via include list. + +``iscsi.target.make`` +^^^^^^^^^^^^^^^^^^^^^ + +This state makes iscsi target services on FreeBSD. + +``iscsi.target.kernel`` +^^^^^^^^^^^^^^^^^^^^^^^ + +Configures required kernel modules. + +``iscsi.target.service`` +^^^^^^^^^^^^^^^^^^^^^^^^ + +Start iscsi target services. Requires ``iscsi.target.config`` via include list. + +``iscsi.target.clean`` +^^^^^^^^^^^^^^^^^^^^^^ + +*Meta-state (This is a state that includes other states)*. + +this state will undo everything performed in the ``iscsi.target`` meta-state + in reverse order, i.e. +stops the service, +removes the configuration files and +then uninstalls the package. + +``iscsi.initiator.package`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Install iSCSI initiator packages. + +``iscsi.initiator.config`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Customises iscsi initiator configuration. Requires ``iscsi.initiator.package`` via include list. + +``iscsi.initiator.make`` +^^^^^^^^^^^^^^^^^^^^^^^^ + +This state makes iscsi initiator services on FreeBSD. + +``iscsi.initiator.kernel`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Configures required kernel modules. + +``iscsi.initiator.service`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Start iscsi initiator services. Requires ``iscsi.initiator.config`` via include list. + +``iscsi.initiator.clean`` +^^^^^^^^^^^^^^^^^^^^^^^^^ + +*Meta-state (This is a state that includes other states)*. + +this state will undo everything performed in the ``iscsi.initiator`` meta-state + in reverse order, i.e. +stops the service, +removes the configuration files and +then uninstalls the package. + +``iscsi.isns.package`` +^^^^^^^^^^^^^^^^^^^^^^ + +Install iSCSI isns packages. + +``iscsi.isns.config`` +^^^^^^^^^^^^^^^^^^^^^ + +Customises iscsi isns configuration. Requires ``iscsi.isns.package`` via include list. + +``iscsi.isns.make`` +^^^^^^^^^^^^^^^^^^^ + +This state makes iscsi isns services on FreeBSD. + +``iscsi.isns.kernel`` +^^^^^^^^^^^^^^^^^^^^^ + +Configures required kernel modules. + +``iscsi.isns.service`` +^^^^^^^^^^^^^^^^^^^^^^ + +Start iscsi isns services. Requires ``iscsi.isns.config`` via include list. + +``iscsi.isns.clean`` +^^^^^^^^^^^^^^^^^^^^ + +*Meta-state (This is a state that includes other states)*. + +this state will undo everything performed in the ``iscsi.target`` meta-state + in reverse order, i.e. +stops the service, +removes the configuration files and +then uninstalls the package. + + +Testing +------- + +Linux testing is done with ``kitchen-salt``. + +Requirements +^^^^^^^^^^^^ + +* Ruby +* Docker + +.. code-block:: bash + + $ gem install bundler + $ bundle install + $ bin/kitchen test [platform] + +Where ``[platform]`` is the platform name defined in ``kitchen.yml``, +e.g. ``debian-9-2019-2-py3``. + +``bin/kitchen converge`` +^^^^^^^^^^^^^^^^^^^^^^^^ + +Creates the docker instance and runs the ``iscsi`` main state, ready for testing. + +``bin/kitchen verify`` +^^^^^^^^^^^^^^^^^^^^^^ + +Runs the ``inspec`` tests on the actual instance. + +``bin/kitchen destroy`` +^^^^^^^^^^^^^^^^^^^^^^^ + +Removes the docker instance. + +``bin/kitchen test`` +^^^^^^^^^^^^^^^^^^^^ + +Runs all of the stages above in one go: i.e. ``destroy`` + ``converge`` + ``verify`` + ``destroy``. + +``bin/kitchen login`` +^^^^^^^^^^^^^^^^^^^^^ + +Gives you SSH access to the instance for manual testing. + diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 00000000..4617efcd --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,21 @@ +/* + Override styles for in-use Sphinx theme +*/ + +/* The next two `.wy`-based rules are specifically needed for the dealing with */ +/* the `sphinx_rtd_theme` bug where long lines do not wrap in tables */ + +/* override table width restrictions */ +.wy-table-responsive table th +, .wy-table-responsive table td +{ + /* !important prevents the common CSS stylesheets from + overriding this as on RTD they are loaded after this stylesheet */ + white-space: normal !important; +} + +.wy-table-responsive +{ + overflow: visible !important; +} + diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..1525f9fd --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- +"""Configuration file for the Sphinx documentation builder. + +This file does only contain a selection of the most common options. For a +full list see the documentation: + +* http://www.sphinx-doc.org/en/stable/config + +""" + +from __future__ import division, print_function, unicode_literals + +# from datetime import datetime + +from recommonmark.parser import CommonMarkParser + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +__author__ = 'Imran Iqbal' # noqa: E221 +__copyright__ = 'Copyright (C) 2019, MYII' # noqa: E221 +__license__ = 'Apache-2.0' # noqa: E221 +__version__ = 'latest' # noqa: E221 +__maintainer__ = 'Imran Iqbal' # noqa: E221 + + +# -- Project information ----------------------------------------------------- + +project = 'iscsi-formula' +copyright = __copyright__.replace('Copyright (C) ', '') # noqa: A001 +author = __author__ +version = __version__ +release = __version__ + + +# -- 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 = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['templates', '_templates', '.templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = ['.rst', '.md'] + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# -- Options for the reStructuredText parser --------------------------------- + +file_insertion_enabled = 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 = 'sphinx_rtd_theme' + +# 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 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'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'iscsi-formula' + + +# -- Options for Markdown output --------------------------------------------- + +source_parsers = { + '.md': CommonMarkParser, +} + + +# -- 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': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# 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', + 'iscsi-formula.tex', + u'iscsi-formula Documentation', + u'', + 'manual', + ), +] + + +# -- Functions: `setup`, docstring preprocessing, etc. ----------------------- + +def setup(app): + """Prepare the Sphinx application object. + + Used for providing a custom CSS file for override styles. + + Parameters + ---------- + app : object + The Sphinx application object. + + Returns + ------- + app : object + The Sphinx application object. + + """ + app.add_stylesheet('css/custom.css') + return app diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..128fa941 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +.. _index: + +.. ``iscsi-formula`` documentation master file. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to iscsi-formula's documentation! +============================================ + +.. toctree:: + :maxdepth: 1 + :caption: Contents + :numbered: + :glob: + + README + CONTRIBUTING + TOFS_pattern + AUTHORS + CHANGELOG diff --git a/iscsi/clean.sls b/iscsi/clean.sls new file mode 100644 index 00000000..6b84bc00 --- /dev/null +++ b/iscsi/clean.sls @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - iscsi.isns.clean + - iscsi.initiator.clean + - iscsi.target.clean diff --git a/iscsi/defaults.yaml b/iscsi/defaults.yaml index bcaa9a43..be5b15ca 100644 --- a/iscsi/defaults.yaml +++ b/iscsi/defaults.yaml @@ -1,19 +1,95 @@ -# vim: sts=2 ts=2 sw= et ai -# +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- iscsi: - cfgsource: salt://iscsi/man5.jinja + filemode: '0640' + user: iscsimake #archlinux only + rootgroup: root + subcomponent: + config: '/etc/iscsi-subcomponent-formula.conf' + # Just here for testing + added_in_defaults: defaults_value + winner: defaults + + config: + acl: + allow: + ietd: /etc/ietd/initiators.allow + deny: + ietd: /etc/ietd/initiators.deny + name: + modprobe: /etc/modprobe.d/iscsi-modules.conf + iscsi: /etc/iscsi.conf + fcoe: /etc/fcoe/cfg-eth2 + tgtd: /etc/tgt/conf.d/targets.conf + ctld: /etc/ctl.conf + ietd: /etc/ietd.conf + lio: /etc/target/saveconfig.json + isns: /etc/isns/isnsd.conf + isnsadm: False + isnsdd: False + open-iscsi: /etc/iscsi/iscsid.conf + servicename: + iscsi: iscsid + fcoe: #Fibre-Channel over Ethernet (FCoE) Target + - fcoe + - lldpad + - fcoe-target + tgtd: tgtd #TGT Project (userspace) RedHat/Debian/Arch/Gentoo + ctld: ctld #CAM Target Layer/iSCSI target daemon, FreeBSD. + ietd: iscsitarget #iSCSI Enterprise Target (IET) project + isns: isnsd #Open-iSNS implements iSNS protocol (RFC4171) + lio: target #LIO is defacto standard on enterprise linux + open-iscsi: open-iscsi #Open-iSCSI + servicetext: + iscsi: 'iscsictl_enable="YES"' + ctld: 'iscsid_enable="YES"' + serviceload: + ctld: /etc/rc.conf + manpage: + iscsi: iscsi.conf(5) + open-iscsi: iscsid.conf(8) + tgtd: targets.conf(5) + ctld: ctl.conf(5) + ietd: ietd.conf(5) + isns: isns_config + kmodule: + iscsi: + name: iscsi_initiator + text: 'iscsi_initiator_load="YES"' + ctld: + name: ctld + text: 'cfiscsi_load="YES"' + ietd: + name: iscsi_trgt + text: iscsi_trgt + lio: + name: target_core_mod + text: target_core_mod + fcoe: + name: fcoe_target + text: fcoe_target + data: + iscsi: {} + fcoe: {} + lio: {} + tgtd: {} + ctld: {} + ietd: {} + isns: {} + isnsadm: {} + isnsdd: {} + open-iscsi: {} + kernel: mess_with_kernel: False - modloadfile: /etc/modprobe.d/iscsi-modules.conf modload: modprobe modunload: modprobe -r modquery: modinfo - group: root - filemode: '0640' isns: enabled: True - provider: isnsd + provider: isns pkghold: False pkgs: wanted: @@ -22,7 +98,7 @@ iscsi: make: wanted: [] - client: + initiator: enabled: True provider: open-iscsi pkghold: False @@ -33,18 +109,13 @@ iscsi: make: wanted: [] - server: + target: + port: 3260 enabled: True provider: lio pkghold: False pkgs: wanted: [] - # tgt #Linux iSCSI target framework user-space tools - # iscsitarget - # iscsitarget-dkms - # fcoe-target-utils - # lldpad - # fcoe-utils unwanted: [] make: wanted: [] diff --git a/iscsi/init.sls b/iscsi/init.sls index 513129a9..ad8b5af8 100644 --- a/iscsi/init.sls +++ b/iscsi/init.sls @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls include: - iscsi.target diff --git a/iscsi/initiator/clean.sls b/iscsi/initiator/clean.sls new file mode 100644 index 00000000..b4fc9a4f --- /dev/null +++ b/iscsi/initiator/clean.sls @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .service.clean + - .config.clean + - .kernel.clean + - .make.clean + - .package.clean diff --git a/iscsi/initiator/config/clean.sls b/iscsi/initiator/config/clean.sls new file mode 100644 index 00000000..2690f9ff --- /dev/null +++ b/iscsi/initiator/config/clean.sls @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_clean = tplroot ~ '.initator.service.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_service_clean }} + +iscsi-initiator-config-clean-file-absent: + file.absent: + - name: {{ iscsi.config.name['initiator'] }} + - watch_in: + - sls: {{ sls_service_clean }} diff --git a/iscsi/initiator/config/files/default/iscsi.tmpl b/iscsi/initiator/config/files/default/iscsi.tmpl new file mode 100644 index 00000000..25591207 --- /dev/null +++ b/iscsi/initiator/config/files/default/iscsi.tmpl @@ -0,0 +1,26 @@ +######################################################################## +# File managed by Salt at <{{ source }}>. +# Your changes will be overwritten. +######################################################################## + +{% if data and component and provider -%} + +{%- macro readconf(outdict, spaces=0) -%} + {%- for key, value in outdict.items() -%} +{{ iscsi_conf(key, value, spaces, loop.last) }} + {%- endfor %} +{%- endmacro -%} + +{%- macro iscsi_conf(key, value, spaces=0, last=False) -%} + {%- set shift = spaces * ' ' -%} +{%- if value is mapping %} +{{shift}}{{ key }} { # nickname +{{ readconf(value, spaces|int+4) }} +{%- elif value is string or value is number %} +{{shift}}{{ key }} = {{"'" if value is not string else ''}}{{ value }}{{"'" if value is not string else ''}} +{%- endif %} +{%- endmacro -%} + +{{ readconf(data, 0) }} + +{% endif %} diff --git a/iscsi/initiator/config/files/default/open-iscsi.tmpl b/iscsi/initiator/config/files/default/open-iscsi.tmpl new file mode 100644 index 00000000..49984c12 --- /dev/null +++ b/iscsi/initiator/config/files/default/open-iscsi.tmpl @@ -0,0 +1,21 @@ +######################################################################## +# File managed by Salt at <{{ source }}>. +# Your changes will be overwritten. +######################################################################## + +{% if data and component and provider -%} + +{%- macro readconf(outdict, spaces=0) -%} + {%- for key, value in outdict.items() -%} +{{ openiscsi(key, value, spaces) }} + {%- endfor %} +{%- endmacro -%} + +{%- macro openiscsi(key, value, spaces=0) -%} + {%- set shift = spaces * ' ' -%} +{{shift}}{{ key }} = {{"'" if value is not string else ''}}{{ value }}{{"'" if value is not string else ''}} +{%- endmacro -%} + +{{ readconf(data, 0) }} + +{% endif %} diff --git a/iscsi/initiator/config/init.sls b/iscsi/initiator/config/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/initiator/config/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/initiator/config/install.sls b/iscsi/initiator/config/install.sls new file mode 100644 index 00000000..1db12eda --- /dev/null +++ b/iscsi/initiator/config/install.sls @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_install = tplroot ~ '.initiator.service.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} +{%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} + +include: + - {{ sls_service_install }} + +iscsi-initiator-config-install-file-managed: + file.managed: + - name: {{ iscsi.config.name[iscsi.initiator.provider] }} + - source: {{ files_switch([iscsi.initiator.provider ~ '.tmpl'], + lookup='iscsi-initiator-config-install-file-managed', + use_subpath=True + ) + }} + - mode: {{ iscsi.filemode }} + - user: root + - group: {{ iscsi.rootgroup }} + - makedirs: True + - template: jinja + - require_in: + - sls: {{ sls_service_install }} + - context: + data: {{ iscsi.config.data[iscsi.initiator.provider|string]|json }} + component: initiator + provider: {{ iscsi.initiator.provider|string }} diff --git a/iscsi/initiator/defaults.yaml b/iscsi/initiator/defaults.yaml deleted file mode 100644 index cc2f8c4e..00000000 --- a/iscsi/initiator/defaults.yaml +++ /dev/null @@ -1,88 +0,0 @@ -# vim: sts=2 ts=2 sw= et ai -# -iscsi: - user: iscsimake #archlinux only - initiator: - iscsid: #FreeBSD Foundation - man5: - manpage: iscsi.conf(5) - vendor: FreeBSD Foundation - svcname: iscsid - kmodule: iscsi_initiator - kmoduletext: iscsi_initiator_load="YES" - svcloadfile: /etc/rc.conf - svcloadtext: iscsictl_enable="YES" - config: /etc/iscsi.conf - format: - div: " = " - quote: '' - end: ';' - ucl: True - json: False - html: False - myconf: {} - - open-iscsi: #Open-iSCSI for Debian, etc - man5: - manpage: iscsid.conf(5) - vendor: Open-iSCSI - svcname: open-iscsi - kmodule: None - kmoduletext: xxxxxxxxx="YES" - svcloadfile: - svcloadtext: - config: /etc/iscsi/iscsid.conf - format: - div: " = " - quote: '' - end: '' - ucl: True - json: False - html: False - myconf: - #Reference: https://github.com/open-iscsi/open-iscsi/blob/master/etc/iscsid.conf - node.startup: manual - node.leading_login: No - node.session.timeo.replacement_timeout: 120 - node.conn[0].timeo.login_timeout: 15 - node.conn[0].timeo.logout_timeout: 15 - node.conn[0].timeo.noop_out_interval: 5 - node.conn[0].timeo.noop_out_timeout: 5 - node.session.err_timeo.abort_timeout: 15 - node.session.err_timeo.lu_reset_timeout: 30 - node.session.err_timeo.tgt_reset_timeout: 30 - node.session.initial_login_retry_max: 8 - node.session.cmds_max: 128 - node.session.queue_depth: 32 - node.session.xmit_thread_priority: -20 - node.session.iscsi.InitialR2T: No - node.session.iscsi.ImmediateData: Yes - node.session.iscsi.FirstBurstLength: 262144 - node.session.iscsi.MaxBurstLength: 16776192 - node.conn[0].iscsi.MaxRecvDataSegmentLength: 262144 - node.conn[0].iscsi.MaxXmitDataSegmentLength: 0 - discovery.sendtargets.iscsi.MaxRecvDataSegmentLength: 32768 - node.session.nr_sessions: 1 - node.session.iscsi.FastAbort: Yes - node.session.scan: auto - - fcoe: #Fibre-channel over Ethernet interface - man5: - manpage: - vendor: - svcname: ['fcoe', 'lldpad',] - kmodule: None - kmoduletext: xxxxxxxxx="YES" - svcloadfile: - svcloadtext: - targetconf: /etc/fcoe/cfg-ethx - targetifce: eth2 - format: - div: ": " - quote: '' - end: '' - ucl: False - json: True #? - html: False - myconf: - globals: {} diff --git a/iscsi/initiator/init.sls b/iscsi/initiator/init.sls index 54b70169..5e9b1ac3 100644 --- a/iscsi/initiator/init.sls +++ b/iscsi/initiator/init.sls @@ -1,3 +1,9 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls include: - - .install + - .package + - .make + - .kernel + - .config + - .service diff --git a/iscsi/initiator/install.sls b/iscsi/initiator/install.sls deleted file mode 100644 index 2567877a..00000000 --- a/iscsi/initiator/install.sls +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: ft=sls -{%- from "iscsi/map.jinja" import iscsi with context %} - - {%- set provider = iscsi.client.provider %} - {%- set data = iscsi.initiator[provider|string] %} - - {%- if iscsi.client.pkgs.unwanted %} - {%- for pkg in iscsi.client.pkgs.unwanted %} -iscsi_initiator_unwanted_pkgs_{{ pkg }}: - pkg.purged: - - name: {{ pkg }} - - require_in: - - file: iscsi_initiator_service_config - {% endfor %} - {%- endif %} - - {%- if iscsi.client.pkgs.wanted %} - {%- for pkg in iscsi.client.pkgs.wanted %} -iscsi_initiator_wanted_pkgs_{{ pkg }}: - pkg.installed: - - name: {{ pkg }} - {%- if iscsi.client.pkghold %} - - hold: {{ iscsi.client.pkghold }} - {%- endif %} - - reload: True - - require_in: - - file: iscsi_initiator_service_config - {% endfor %} - {%- endif %} - -{%-if iscsi.client.make.wanted and salt['cmd.run']("id iscsi.user", output_loglevel='quiet')%} - {%- for pkg in [iscsi.client.make.wanted,] %} -iscsi_initiator_make_pkg_{{ pkg }}: - file.directory: - - name: /home/{{ iscsi.user }} - - makedirs: True - - user: {{ iscsi.user }} - - dir_mode: '0755' - {%- if iscsi.client.make.gitrepo %} - git.latest: - - name: {{ iscsi.client.make.gitrepo }}/{{ pkg }}.git - - target: /home/{{ iscsi.user }}/{{ pkg }} - - user: {{ iscsi.user }} - - force_clone: True - - force_fetch: True - - force_reset: True - - force_checkout: True - {% if grains['saltversioninfo'] >= [2017, 7, 0] %} - - retry: - attempts: 3 - until: True - interval: 60 - splay: 10 - {%- endif %} - - require: - - file: iscsi_initiator_make_pkg_{{ pkg }} - {%- endif %} - cmd.run: - - cwd: /home/{{ iscsi.user }}/{{ pkg }} - - name: {{ iscsi.client.make.cmd }} - - runas: {{ iscsi.user }} - {% endfor %} -{%- endif %} - -iscsi_initiator_service_config: - file.managed: - - name: {{ data.man5.config }} - - source: {{ iscsi.cfgsource }} - - template: jinja - - user: root - - group: {{ iscsi.group }} - - mode: {{ iscsi.filemode }} - - makedirs: True - - context: - data: {{ data|json }} - component: 'initiator' - provider: {{ provider }} - json: {{ data.man5.format.json|json }} - - {%- if iscsi.kernel.mess_with_kernel and data.man5.kmodule and data.man5.kmoduletext %} -iscsi_initiator_kernel_module: - file.line: - - name: {{ iscsi.kernel.modloadfile }} - - content: {{ data.man5.kmoduletext }} - - backup: True - {%- if not iscsi.client.enabled %} - - mode: delete - cmd.run: - - name: {{ iscsi.kernel.modunload }} {{ data.man5.kmodule }} - - onlyif: {{ iscsi.kernel.modquery }} {{ data.man5.kmodule }} - {%- else %} - - mode: ensure - - after: autoboot_delay.*$ - cmd.run: - - name: {{ iscsi.kernel.modload }} {{ data.man5.kmodule }} - - unless: {{ iscsi.kernel.modquery }} {{ data.man5.kmodule }} - - require: - - file: iscsi_initiator_kernel_module - {%- endif %} - - require_in: - - service: iscsi_initiator_service - {%- endif %} - -iscsi_initiator_service: - file.line: - - onlyif: test "`uname`" = "FreeBSD" - - name: {{ data.man5.svcloadfile }} - - content: {{ data.man5.svcloadtext }} - - backup: True - {%- if not iscsi.client.enabled %} - - mode: delete - service.disabled: - - enable: False - {%- else %} - - mode: ensure - - after: ^salt.*$ - service.running: - - enable: True - - require: - - file: iscsi_initiator_service_config - - watch: - - file: iscsi_initiator_service_config - {%- endif %} - {%- if data.man5.svcname is iterable and data.man5.svcname is not string %} - - names: {{ data.man5.svcname|json }} - {%- else %} - - name: {{ data.man5.svcname }} - {%- endif %} - {%- if data.man5.kmodule %} - - unless: {{ iscsi.kernel.modquery }} {{ data.man5.kmodule }} - {%- endif %} - -iscsi_initiator_service_running_failure_explanation: - test.show_notification: - - text: | - In certain circumstances the iscsi initiator service will not start. - One reason is your kernel version was upgraded and reboot is needed. - If that's the case then run command: - 'systemctl enable {{ data.man5.svcname }}' && reboot - - onfail: - - service: iscsi_initiator_service - - unless: {{ grains.os_family in ('MacOS', 'Windows') }} #maybe not needed but no harm diff --git a/iscsi/initiator/kernel/clean.sls b/iscsi/initiator/kernel/clean.sls new file mode 100644 index 00000000..5a064cb0 --- /dev/null +++ b/iscsi/initiator/kernel/clean.sls @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_clean = tplroot ~ '.initiator.service.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} +{%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} + + {%- set provider = iscsi.initiator.provider %} + {%- if iscsi.kernel.mess_with_kernel and provider in iscsi.config.kmodule %} +include: + - {{ sls_service_clean }} + +iscsi-initiator-kernel-clean-cmd-run: + cmd.run: + - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} + - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} + - require: + - sls: {{ sls_service_clean }} + +iscsi-initiator-kernel-clean-file-line: + file.line: + - onlyif: {{ iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} + - name: {{ iscsi.config.name.modprobe }} + - content: {{ data.config.kmodule[provider]['text'] }} + - backup: True + - mode: delete + + {%- endif %} diff --git a/iscsi/initiator/kernel/init.sls b/iscsi/initiator/kernel/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/initiator/kernel/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/initiator/kernel/install.sls b/iscsi/initiator/kernel/install.sls new file mode 100644 index 00000000..589d6c2f --- /dev/null +++ b/iscsi/initiator/kernel/install.sls @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_install = tplroot ~ '.initiator.service.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} +{%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} + + {%- set provider = iscsi.initiator.provider %} + {%- if iscsi.kernel.mess_with_kernel and provider in iscsi.config.kmodule %} +include: + - {{ sls_service_install }} + +iscsi-initiator-kernel-install-file-line: + file.line: + - onlyif: iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} + - name: {{ iscsi.config.name.modprobe }} + - content: {{ data.config.kmodule[provider]['text'] }} + - backup: True + {%- if not iscsi.initiator.enabled %} + - mode: delete + cmd.run: + - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} + - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} + {%- else %} + - create: True + - mode: ensure + - after: autoboot_delay.*$ + cmd.run: + - name: {{ iscsi.kernel.modload }} {{ iscsi.config.kmodule[provider]['name'] }} + - unless: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} + {%- endif %} + - require: + - file: iscsi-initiator-kernel-install-file-line + - require_in: + - sls: {{ sls_service_install }} + + {%- endif %} diff --git a/iscsi/initiator/make/clean.sls b/iscsi/initiator/make/clean.sls new file mode 100644 index 00000000..d7bbed95 --- /dev/null +++ b/iscsi/initiator/make/clean.sls @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_clean = tplroot ~ '.initiator.service.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + + {%- if iscsi.initiator.make.wanted %} + {%- for pkg in iscsi.initiator.make.wanted %} +include: + - {{ sls_service_clean }} + +iscsi-initiator-package-make-clean-{{ pkg }}-removed: + pkg.removed: + - name: {{ pkg }} + - require: + - sls: {{ sls_service_clean }} + + {%- endfor %} + {%- endif %} diff --git a/iscsi/initiator/make/init.sls b/iscsi/initiator/make/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/initiator/make/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/initiator/make/install.sls b/iscsi/initiator/make/install.sls new file mode 100644 index 00000000..2adf48fe --- /dev/null +++ b/iscsi/initiator/make/install.sls @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_install = tplroot ~ '.initiator.service.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + + {%- if iscsi.initiator.make.wanted %} + {%- if salt['cmd.run']("id iscsi.user", output_loglevel='quiet') %} +include: + - {{ sls_service_install }} + +iscsi-initiator-make-file-directory: + file.directory: + - name: /home/{{ iscsi.user }} + - makedirs: True + - user: {{ iscsi.user }} + - dir_mode: '0755' + + {%- for pkg in iscsi.initiator.make.wanted %} + +iscsi-initiator-make-{{ pkg }}-git-latest: + git.latest: + - onlyif: {{ iscsi.initiator.make.gitrepo }} + - name: {{ iscsi.initiator.make.gitrepo }}/{{ pkg }}.git + - initiator: /home/{{ iscsi.user }}/{{ pkg }} + - user: {{ iscsi.user }} + - force_clone: True + - force_fetch: True + - force_reset: True + - force_checkout: True + {% if grains['saltversioninfo'] >= [2017, 7, 0] %} + - retry: + attempts: 3 + until: True + interval: 60 + splay: 10 + {%- endif %} + - require: + - file: iscsi-initiator-make-file-directory + +iscsi-initiator-make-{{ pkg }}-cmd-run: + cmd.run: + - cwd: /home/{{ iscsi.user }}/{{ pkg }} + - name: {{ iscsi.initiator.make.cmd }} + - runas: {{ iscsi.user }} + - require: + - git: iscsi-initiator-make-{{ pkg }}-git-latest + - require_in: + - sls: {{ sls_service_install }} + + {% endfor %} + {%- endif %} + {%- endif %} diff --git a/iscsi/initiator/package/clean.sls b/iscsi/initiator/package/clean.sls new file mode 100644 index 00000000..05e2afe6 --- /dev/null +++ b/iscsi/initiator/package/clean.sls @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_clean = tplroot ~ '.initiator.config.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_clean }} + + {%- for pkg in [iscsi.initiator.pkgs.wanted, iscsi.initiator.pkgs.unwanted] %} + +iscsi-initiator-package-clean-{{ pkg }}-removed: + pkg.purged: + - name: {{ pkg }} + - require: + - sls: {{ sls_config_clean }} + + {% endfor %} diff --git a/iscsi/initiator/package/init.sls b/iscsi/initiator/package/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/initiator/package/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/initiator/package/install.sls b/iscsi/initiator/package/install.sls new file mode 100644 index 00000000..5c1290db --- /dev/null +++ b/iscsi/initiator/package/install.sls @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_install = tplroot ~ '.initiator.config.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_install }} + + {%- if iscsi.initiator.pkgs.unwanted %} + {%- for pkg in iscsi.initiator.pkgs.unwanted %} + +iscsi-initiator-package-install-{{ pkg }}-removed: + pkg.purged: + - name: {{ pkg }} + - require_in: + - sls: {{ sls_config_install }} + + {%- endfor %} + {%- endif %} + {%- if iscsi.initiator.pkgs.wanted %} + {%- for pkg in iscsi.initiator.pkgs.wanted %} + +iscsi-initiator-package-install-{{ pkg }}-installed: + pkg.installed: + - name: {{ pkg }} + {%- if iscsi.initiator.pkghold %} + - hold: {{ iscsi.initiator.pkghold }} + {%- endif %} + - reload: True + - require_in: + - sls: {{ sls_config_install }} + + {%- endfor %} + {%- endif %} diff --git a/iscsi/initiator/remove.sls b/iscsi/initiator/remove.sls deleted file mode 100644 index 183ca2c6..00000000 --- a/iscsi/initiator/remove.sls +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: ft=sls -{%- from "iscsi/map.jinja" import iscsi with context %} - - {%- set provider = iscsi.client.provider %} - {%- set data = iscsi.initiator[provider|string] %} - -iscsi_initiator_service_dead: - file.line: - - name: {{ data.man5.svcloadfile }} - - content: {{ data.man5.svcloadtext }} - - backup: True - - mode: delete - service.dead: - - enable: False - {%- if data.man5.kmodule %} - - onlyif: {{ iscsi.kernel.modquery }} {{ data.man5.kmodule }} - {%- endif %} - - {%- if iscsi.kernel.mess_with_kernel and data.man5.kmodule and data.man5.kmoduletext %} -iscsi_initiator_kernel_module_{{ data.man5.kmodule }}_removed: - file.line: - - name: {{ iscsi.kernel.modloadfile }} - - content: {{ data.man5.kmoduletext }} - - backup: True - - mode: delete - cmd.run: - - name: {{ iscsi.kernel.modunload }} {{ data.man5.kmodule }} - - onlyif: {{ iscsi.kernel.modquery }} {{ data.man5.kmodule }} - - require: - - iscsi_initiator_service_dead - - require_in: - - iscsi_initiator_service_config_removed - {%- endif %} - - {%- for pkg in [iscsi.client.pkgs.unwanted, iscsi.client.pkgs.unwanted,] %} -iscsi_initiator_wanted_pkgs_{{ pkg }}_removed: - pkg.purged: - - name: {{ pkg }} - - require_in: - - file: iscsi_initiator_service_config_removed - {% endfor %} - -iscsi_initiator_service_config_removed: - file.absent: - - name: {{ data.man5.config }} - - onlyif: test -f {{ data.man5.config }} diff --git a/iscsi/initiator/service/clean.sls b/iscsi/initiator/service/clean.sls new file mode 100644 index 00000000..065a557e --- /dev/null +++ b/iscsi/initiator/service/clean.sls @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_clean = tplroot ~ '.initiator.config.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_clean }} + +iscsi-initiator-service-clean-service-dead + service.dead: + - name: {{ iscsi.config.servicename[iscsi.initiator.provider] }} + - enable: False + - require_in: + - sls: {{ sls_config_clean }} + +iscsi-initiator-service-install-file-line-freebsd: + file.line: + - onlyif: {{ grains.os == 'FreeBSD' }} + - name: {{ iscsi.config.name.modprobe }} + - content: 'ctld_env="-u"' + - mode: delete + - backup: True diff --git a/iscsi/initiator/service/init.sls b/iscsi/initiator/service/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/initiator/service/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/initiator/service/install.sls b/iscsi/initiator/service/install.sls new file mode 100644 index 00000000..51523a97 --- /dev/null +++ b/iscsi/initiator/service/install.sls @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_install = tplroot ~ '.initiator.config.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_install }} + + {%- if grains.os == 'FreeBSD' %} + +iscsi-initiator-service-install-file-line-freebsd: + file.line: + - name: {{ iscsi.config.name.modprobe }} + - content: 'ctld_env="-u"' + - backup: True + {%- if not iscsi.initiator.enabled %} + - mode: delete + {%- else %} + - mode: ensure + - after: 'sshd_enable.*' + - create: True + {%- endif %} + {%- endif %} + + {%- set servicename = iscsi.config.servicename[iscsi.initiator.provider] %} +iscsi-initiator-service-install-service-running: + {%- if not iscsi.initiator.enabled %} + service.dead: + - name: {{ servicename }} + - enable: False + {%- else %} + service.running: + - name: {{ servicename }} + - enable: True + - onfail_in: + - test: iscsi-initiator-service-install-failure-explanation + - watch: + - file: iscsi-initiator-config-install-file-managed + {%- endif %} + {%- if servicename is iterable and servicename is not string %} + - names: {{ servicename|json }} + {%- else %} + - name: {{ servicename }} + {%- endif %} + - require: + - sls: {{ sls_config_install }} + +iscsi-initiator-service-install-failure-explanation: + test.show_notification: + - text: | + In certain circumstances the iscsi initiator service will not start. + * your configuration file may be incorrect. + * your kernel was upgraded but not activated by reboot + 'systemctl enable {{ servicename }}' && reboot + - unless: {{ grains.os_family in ('MacOS', 'Windows') }} #maybe not needed but no harm diff --git a/iscsi/isns/clean.sls b/iscsi/isns/clean.sls new file mode 100644 index 00000000..b1a0a502 --- /dev/null +++ b/iscsi/isns/clean.sls @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .service.clean + - .config.clean + - .make.clean + - .package.clean diff --git a/iscsi/isns/config/clean.sls b/iscsi/isns/config/clean.sls new file mode 100644 index 00000000..1ecb3121 --- /dev/null +++ b/iscsi/isns/config/clean.sls @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_clean = tplroot ~ '.isns.service.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_service_clean }} + +iscsi-isns-config-clean-file-absent: + file.absent: + - name: {{ iscsi.config.name[iscsi.isns.provider] }} + - watch_in: + - sls: {{ sls_service_clean }} + + {%- if 'isnsadm' in iscsi.config.name and iscsi.config.name['isnsadm'] %} +iscsi-isns-config-clean-file-absent-isnsadm: + file.absent: + - name: {{ iscsi.config.name['isnsadm'] }} + {%- endif %} + + {%- if 'isnsdd' in iscsi.config.name and iscsi.config.name['isnsdd'] %} +iscsi-isns-config-clean-file-absent-isnsdd: + file.absent: + - name: {{ iscsi.config.name['isnsdd'] }} + {%- endif %} diff --git a/iscsi/isns/config/files/default/isns.tmpl b/iscsi/isns/config/files/default/isns.tmpl new file mode 100644 index 00000000..4062a824 --- /dev/null +++ b/iscsi/isns/config/files/default/isns.tmpl @@ -0,0 +1,21 @@ +######################################################################## +# File managed by Salt at <{{ source }}>. +# Your changes will be overwritten. +######################################################################## + +{% if data and component and provider -%} + +{%- macro readconf(outdict, spaces=0) -%} + {%- for key, value in outdict.items() -%} +{{ isns(key, value, spaces) }} + {%- endfor %} +{%- endmacro -%} + +{%- macro isns(key, value, spaces=0, last=False) -%} + {%- set shift = spaces * ' ' -%} +{{shift}}{{ key }} = {{ value }} +{%- endmacro -%} + +{{ readconf(data['isns'], 0) }} + +{% endif -%} diff --git a/iscsi/isns/config/init.sls b/iscsi/isns/config/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/isns/config/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/isns/config/install.sls b/iscsi/isns/config/install.sls new file mode 100644 index 00000000..63fa72de --- /dev/null +++ b/iscsi/isns/config/install.sls @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_install = tplroot ~ '.isns.service.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} +{%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} + +include: + - {{ sls_service_install }} + +iscsi-isns-config-install-file-managed-isnsd: + file.managed: + - onlyif: {{ iscsi.config.name['isns'] }} + - name: {{ iscsi.config.name['isns'] }} + - source: {{ files_switch([iscsi.isns.provider ~ '.tmpl'], + lookup='iscsi-isns-config-install-file-managed', + use_subpath=True + ) + }} + - mode: {{ iscsi.filemode }} + - user: root + - group: {{ iscsi.rootgroup }} + - makedirs: True + - template: jinja + - require_in: + - sls: {{ sls_service_install }} + - watch_in: + - service: iscsi-isns-service-install-service-running + - context: + data: {{ iscsi.config.data[iscsi.isns.provider|string]|json }} + component: isns + provider: {{ iscsi.isns.provider|string }} + + {%- if 'isnsadm' in iscsi.config.name and iscsi.config.name['isnsadm'] %} +iscsi-isns-config-install-file-managed-isnsadm: + file.managed: + - name: {{ iscsi.config.name['isnsadm'] }} + - source: {{ files_switch([iscsi.isns.provider ~ '.tmpl'], + lookup='iscsi-isns-config-install-file-managed', + use_subpath=True + ) + }} + - mode: {{ iscsi.filemode }} + - user: root + - group: {{ iscsi.rootgroup }} + - makedirs: True + - template: jinja + - require_in: + - sls: {{ sls_service_install }} + - watch_in: + - service: iscsi-isns-service-install-service-running + - context: + data: {{ iscsi.config.data[iscsi.isns.provider|string]|json }} + component: isns + provider: {{ iscsi.isns.provider|string }} + {%- endif %} + + {%- if 'isnsdd' in iscsi.config.name and iscsi.config.name['isnsdd'] %} +iscsi-isns-config-install-file-managed-isnsdd: + file.managed: + - onlyif: {{ iscsi.config.name['isnsdd'] }} + - name: {{ iscsi.config.name['isnsdd'] }} + - source: {{ files_switch([iscsi.isns.provider ~ '.tmpl'], + lookup='iscsi-isns-config-install-file-managed', + use_subpath=True + ) + }} + - mode: {{ iscsi.filemode }} + - user: root + - group: {{ iscsi.rootgroup }} + - makedirs: True + - template: jinja + - require_in: + - sls: {{ sls_service_install }} + - watch_in: + - service: iscsi-isns-service-install-service-running + - context: + data: {{ iscsi.config.data[iscsi.isns.provider|string]|json }} + component: isns + provider: {{ iscsi.isns.provider|string }} + {%- endif %} diff --git a/iscsi/isns/defaults.yaml b/iscsi/isns/defaults.yaml deleted file mode 100644 index 8a7e0e99..00000000 --- a/iscsi/isns/defaults.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# vim: sts=2 ts=2 sw= et ai -# -iscsi: - isns: - isnsd: - man5: - manpage: isns_config - vendor: Open-iSNS - svcname: isnsd {#Open-iSNS implements iSNS protocol (RFC4171)#} - kmodule: None - kloadparm: - cmd: isnsadm {#iSNS client utility#} - config: /etc/isns/isnsd.conf - format: - div: " = " - quote: '' - end: '' - ucl: False - json: False - html: False - myconf: - Database: /var/lib/isns - RegistrationPeriod: 10m - DefaultDiscoveryDomain: 1 - SLPRegister: 1 - ClientKeyStore: 'DB:' - SCNTimeout: 60 - SCNRetries: 3 - ESIMinInterval: 1m - ESIMaxInterval: 2m - ESIRetries: 3 - Auth.ReplayWindow: 2m - Auth.TimeStampJitter: 1s - #AuthKeyFile: /etc/isns/auth_key - #KeyStore: DB: - #Network.ReconnectTimeout: 10 diff --git a/iscsi/isns/init.sls b/iscsi/isns/init.sls index 54b70169..f2a8955d 100644 --- a/iscsi/isns/init.sls +++ b/iscsi/isns/init.sls @@ -1,3 +1,8 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls include: - - .install + - .package + - .make + - .config + - .service diff --git a/iscsi/isns/install.sls b/iscsi/isns/install.sls deleted file mode 100644 index 72c5907f..00000000 --- a/iscsi/isns/install.sls +++ /dev/null @@ -1,96 +0,0 @@ -#-*- coding: utf-8 -*- -# vim: ft=sls -{%- from "iscsi/map.jinja" import iscsi with context %} - - {%- set provider = iscsi.isns.provider %} - {%- set data = iscsi.isns[provider|string] %} - - {%- if iscsi.isns.pkgs.unwanted %} - {%- for pkg in iscsi.isns.pkgs.unwanted %} -iscsi_isnsd_remove_{{ pkg }}_pkg: - pkg.purged: - - name: {{ pkg }} - - require_in: - - file: iscsi_isnsd_service_config - {% endfor %} - {%- endif %} - - {%- if iscsi.isns.pkgs.wanted %} - {%- for pkg in iscsi.isns.pkgs.wanted %} -iscsi_isnsd_install_{{ pkg }}_pkg: - pkg.installed: - - name: {{ pkg }} - {%- if iscsi.isns.pkghold %} - - hold: {{ iscsi.isns.pkghold }} - {%- endif %} - - reload: True - - require_in: - - file: iscsi_isnsd_service_config - {% endfor %} - {%- endif %} - -{%-if iscsi.isns.make.wanted and salt['cmd.run']("id iscsi.user", output_loglevel='quiet') %} - {%- for pkg in iscsi.isns.make.wanted %} -iscsi_isns_make_pkg_{{ pkg }}: - file.directory: - - name: /home/{{ iscsi.user }} - - makedirs: True - - user: {{ iscsi.user }} - - dir_mode: '0755' - {%- if iscsi.isns.make.gitrepo %} - git.latest: - - name: {{ iscsi.isns.make.gitrepo }}/{{ pkg }}.git - - target: /home/{{ iscsi.user }}/{{ pkg }} - - user: {{ iscsi.user }} - - force_clone: True - - force_fetch: True - - force_reset: True - - force_checkout: True - {% if grains['saltversioninfo'] >= [2017, 7, 0] %} - - retry: - attempts: 3 - until: True - interval: 60 - splay: 10 - {%- endif %} - - require: - - file: iscsi_isns_make_pkg_{{ pkg }} - {%- endif %} - cmd.run: - - cwd: /home/{{ iscsi.user }}/{{ pkg }} - - name: {{ iscsi.isns.make.cmd }} - - runas: {{ iscsi.user }} - {% endfor %} -{%- endif %} - -iscsi_isnsd_service_config: - file.managed: - - name: {{ data.man5.config }} - - source: {{ iscsi.cfgsource }} - - template: jinja - - user: root - - group: {{ iscsi.group }} - - mode: {{ iscsi.filemode }} - - makedirs: True - - require_in: - - service: iscsi_isnsd_service - - test: True - - context: - data: {{ data|json }} - component: 'isns' - provider: {{ provider }} - json: {{ data['man5']['format']['json'] }} - -iscsi_isnsd_service: - {%- if iscsi.isns.enabled %} - service.running: - - name: {{ data.man5.svcname }} - - enable: True - - watch: - - file: iscsi_isnsd_service_config - {%- else %} - service.disabled: - - name: {{ data.man5.svcname }} - - enable: False - {%- endif %} - diff --git a/iscsi/isns/make/clean.sls b/iscsi/isns/make/clean.sls new file mode 100644 index 00000000..b4115a8a --- /dev/null +++ b/iscsi/isns/make/clean.sls @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_clean = tplroot ~ '.isns.service.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + + {%- if iscsi.isns.make.wanted %} + {%- for pkg in iscsi.isns.make.wanted %} +include: + - {{ sls_service_clean }} + +iscsi-isns-package-make-clean-{{ pkg }}-removed: + pkg.removed: + - name: {{ pkg }} + - require: + - sls: {{ sls_service_clean }} + + {%- endfor %} + {%- endif %} diff --git a/iscsi/isns/make/init.sls b/iscsi/isns/make/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/isns/make/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/isns/make/install.sls b/iscsi/isns/make/install.sls new file mode 100644 index 00000000..c74fe9e9 --- /dev/null +++ b/iscsi/isns/make/install.sls @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_install = tplroot ~ '.isns.service.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + + {%- if iscsi.isns.make.wanted %} + {%- if salt['cmd.run']("id iscsi.user", output_loglevel='quiet') %} +include: + - {{ sls_service_install }} + +iscsi-isns-make-file-directory: + file.directory: + - name: /home/{{ iscsi.user }} + - makedirs: True + - user: {{ iscsi.user }} + - dir_mode: '0755' + + {%- for pkg in iscsi.isns.make.wanted %} + +iscsi-isns-make-{{ pkg }}-git-latest: + git.latest: + - onlyif: {{ iscsi.isns.make.gitrepo }} + - name: {{ iscsi.isns.make.gitrepo }}/{{ pkg }}.git + - isns: /home/{{ iscsi.user }}/{{ pkg }} + - user: {{ iscsi.user }} + - force_clone: True + - force_fetch: True + - force_reset: True + - force_checkout: True + {% if grains['saltversioninfo'] >= [2017, 7, 0] %} + - retry: + attempts: 3 + until: True + interval: 60 + splay: 10 + {%- endif %} + - require: + - file: iscsi-isns-make-file-directory + +iscsi-isns-make-{{ pkg }}-cmd-run: + cmd.run: + - cwd: /home/{{ iscsi.user }}/{{ pkg }} + - name: {{ iscsi.isns.make.cmd }} + - runas: {{ iscsi.user }} + - require: + - git: iscsi-isns-make-{{ pkg }}-git-latest + - require_in: + - sls: {{ sls_service_install }} + + {% endfor %} + {%- endif %} + {%- endif %} diff --git a/iscsi/isns/package/clean.sls b/iscsi/isns/package/clean.sls new file mode 100644 index 00000000..a10fbf67 --- /dev/null +++ b/iscsi/isns/package/clean.sls @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_clean = tplroot ~ '.isns.config.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_clean }} + + {%- for pkg in [iscsi.isns.pkgs.wanted, iscsi.isns.pkgs.unwanted] %} + +iscsi-isns-package-clean-{{ pkg }}-removed: + pkg.purged: + - name: {{ pkg }} + - require: + - sls: {{ sls_config_clean }} + + {% endfor %} diff --git a/iscsi/isns/package/init.sls b/iscsi/isns/package/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/isns/package/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/isns/package/install.sls b/iscsi/isns/package/install.sls new file mode 100644 index 00000000..193cd17b --- /dev/null +++ b/iscsi/isns/package/install.sls @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_install = tplroot ~ '.isns.config.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_install }} + + {%- if iscsi.isns.pkgs.unwanted %} + {%- for pkg in iscsi.isns.pkgs.unwanted %} + +iscsi-isns-package-install-{{ pkg }}-removed: + pkg.purged: + - name: {{ pkg }} + - require_in: + - sls: {{ sls_config_install }} + + {%- endfor %} + {%- endif %} + {%- if iscsi.isns.pkgs.wanted %} + {%- for pkg in iscsi.isns.pkgs.wanted %} + +iscsi-isns-package-install-{{ pkg }}-installed: + pkg.installed: + - name: {{ pkg }} + {%- if iscsi.isns.pkghold %} + - hold: {{ iscsi.isns.pkghold }} + {%- endif %} + - reload: True + - require_in: + - sls: {{ sls_config_install }} + + {%- endfor %} + {%- endif %} diff --git a/iscsi/isns/remove.sls b/iscsi/isns/remove.sls deleted file mode 100644 index cf030df7..00000000 --- a/iscsi/isns/remove.sls +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: ft=sls -{%- from "iscsi/map.jinja" import iscsi with context %} - -iscsi_isns_service_name_stop: - service.dead: - - name: {{ iscsi.isns.isnsd.man5.svcname }} - - enable: False - - sig: {{ iscsi.isns.isnsd.man5.svcname }} - -iscsi_isns_service_config_backup: - file.copy: - - name: {{ iscsi.isns.isnsd.man5.config }}.bak - - source: {{ iscsi.isns.isnsd.man5.config }} - - force: True - -iscsi_isns_service_config_removed: - file.absent: - - name: {{ iscsi.isns.isnsd.man5.config }} - - require: - - service: iscsi_isns_service_name_stop - - file: iscsi_isns_service_config_backup - - {%- for pkg in [iscsi.isns.pkgs.wanted, iscsi.isns.pkgs.unwanted] %} -iscsi_isns_remove_{{ pkg }}_pkg: - pkg.purged: - - pkg: {{ pkg }} - - require: - - service: iscsi_isns_service_config_removed - {% endfor %} diff --git a/iscsi/isns/service/clean.sls b/iscsi/isns/service/clean.sls new file mode 100644 index 00000000..1cb1af29 --- /dev/null +++ b/iscsi/isns/service/clean.sls @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_clean = tplroot ~ '.isns.config.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_clean }} + +iscsi-isns-service-clean-service-dead + service.dead: + - name: {{ iscsi.config.servicename[iscsi.isns.provider] }} + - enable: False + - require_in: + - sls: {{ sls_config_clean }} diff --git a/iscsi/isns/service/init.sls b/iscsi/isns/service/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/isns/service/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/isns/service/install.sls b/iscsi/isns/service/install.sls new file mode 100644 index 00000000..0fb27628 --- /dev/null +++ b/iscsi/isns/service/install.sls @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_install = tplroot ~ '.isns.config.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + + {%- set provider = iscsi.isns.provider %} + {%- set servicename = iscsi.config.servicename[provider] %} +include: + - {{ sls_config_install }} + +iscsi-isns-service-install-service-running: + {%- if not iscsi.isns.enabled %} + service.dead: + - name: {{ servicename }} + - enable: False + {%- else %} + service.running: + - name: {{ servicename }} + - enable: True + - onfail_in: + - test: iscsi-isns-service-install-failure-explanation + - require: + - sls: {{ sls_config_install }} + - watch: + - file: iscsi-isns-config-install-file-managed-isnsd + {%- endif %} + {%- if servicename is iterable and servicename is not string %} + - names: {{ servicename|json }} + {%- else %} + - name: {{ servicename }} + {%- endif %} + {%- if iscsi.kernel.mess_with_kernel and provider in iscsi.config.kmodule %} + {%- if 'name' in iscsi.config.kmodule[provider] %} + - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} + {%- endif %} + {%- endif %} + +iscsi-isns-service-install-failure-explanation: + test.show_notification: + - text: | + In certain circumstances the iscsi isns service will not start. + * your configuration file may be incorrect. + * your kernel was upgraded but not activated by reboot + 'systemctl enable {{ servicename }}' && reboot + - unless: {{ grains.os_family in ('MacOS', 'Windows') }} #maybe not needed but no harm diff --git a/iscsi/libtofs.jinja b/iscsi/libtofs.jinja new file mode 100644 index 00000000..900e62b5 --- /dev/null +++ b/iscsi/libtofs.jinja @@ -0,0 +1,112 @@ +{%- macro files_switch(source_files, + lookup=None, + default_files_switch=['id', 'os_family'], + indent_width=6, + use_subpath=False) %} + {#- + Returns a valid value for the "source" parameter of a "file.managed" + state function. This makes easier the usage of the Template Override and + Files Switch (TOFS) pattern. + + Params: + * source_files: ordered list of files to look for + * lookup: key under ':tofs:source_files' to prepend to the + list of source files + * default_files_switch: if there's no config (e.g. pillar) + ':tofs:files_switch' this is the ordered list of grains to + use as selector switch of the directories under + "/files" + * indent_witdh: indentation of the result value to conform to YAML + * use_subpath: defaults to `False` but if set, lookup the source file + recursively from the current state directory up to `tplroot` + + Example (based on a `tplroot` of `xxx`): + + If we have a state: + + Deploy configuration: + file.managed: + - name: /etc/yyy/zzz.conf + - source: {{ files_switch(['/etc/yyy/zzz.conf', '/etc/yyy/zzz.conf.jinja'], + lookup='Deploy configuration' + ) }} + - template: jinja + + In a minion with id=theminion and os_family=RedHat, it's going to be + rendered as: + + Deploy configuration: + file.managed: + - name: /etc/yyy/zzz.conf + - source: + - salt://xxx/files/theminion/etc/yyy/zzz.conf + - salt://xxx/files/theminion/etc/yyy/zzz.conf.jinja + - salt://xxx/files/RedHat/etc/yyy/zzz.conf + - salt://xxx/files/RedHat/etc/yyy/zzz.conf.jinja + - salt://xxx/files/default/etc/yyy/zzz.conf + - salt://xxx/files/default/etc/yyy/zzz.conf.jinja + - template: jinja + #} + {#- Get the `tplroot` from `tpldir` #} + {%- set tplroot = tpldir.split('/')[0] %} + {%- set path_prefix = salt['config.get'](tplroot ~ ':tofs:path_prefix', tplroot) %} + {%- set files_dir = salt['config.get'](tplroot ~ ':tofs:dirs:files', 'files') %} + {%- set files_switch_list = salt['config.get']( + tplroot ~ ':tofs:files_switch', + default_files_switch + ) %} + {#- Lookup source_files (v2), files (v1), or fallback to an empty list #} + {%- set src_files = salt['config.get']( + tplroot ~ ':tofs:source_files:' ~ lookup, + salt['config.get'](tplroot ~ ':tofs:files:' ~ lookup, []) + ) %} + {#- Append the default source_files #} + {%- set src_files = src_files + source_files %} + {#- Only add to [''] when supporting older TOFS implementations #} + {%- set path_prefix_exts = [''] %} + {%- if use_subpath and tplroot != tpldir %} + {#- Walk directory tree to find {{ files_dir }} #} + {%- set subpath_parts = tpldir.lstrip(tplroot).lstrip('/').split('/') %} + {%- for path in subpath_parts %} + {%- set subpath = subpath_parts[0:loop.index] | join('/') %} + {%- do path_prefix_exts.append('/' ~ subpath) %} + {%- endfor %} + {%- endif %} + {%- for path_prefix_ext in path_prefix_exts|reverse %} + {%- set path_prefix_inc_ext = path_prefix ~ path_prefix_ext %} + {#- For older TOFS implementation, use `files_switch` from the config #} + {#- Use the default, new method otherwise #} + {%- set fsl = salt['config.get']( + tplroot ~ path_prefix_ext|replace('/', ':') ~ ':files_switch', + files_switch_list + ) %} + {#- Append an empty value to evaluate as `default` in the loop below #} + {%- if '' not in fsl %} + {%- do fsl.append('') %} + {%- endif %} + {%- for fs in fsl %} + {%- for src_file in src_files %} + {%- if fs %} + {%- set fs_dirs = salt['config.get'](fs, fs) %} + {%- else %} + {%- set fs_dirs = salt['config.get'](tplroot ~ ':tofs:dirs:default', 'default') %} + {%- endif %} + {#- Force the `config.get` lookup result as a list where necessary #} + {#- since we need to also handle grains that are lists #} + {%- if fs_dirs is string %} + {%- set fs_dirs = [fs_dirs] %} + {%- endif %} + {%- for fs_dir in fs_dirs %} + {%- set url = [ + '- salt:/', + path_prefix_inc_ext.strip('/'), + files_dir.strip('/'), + fs_dir.strip('/'), + src_file.strip('/'), + ] | select | join('/') %} +{{ url | indent(indent_width, true) }} + {%- endfor %} + {%- endfor %} + {%- endfor %} + {%- endfor %} +{%- endmacro %} diff --git a/iscsi/man5.jinja b/iscsi/man5.jinja deleted file mode 100644 index 6cbf32d9..00000000 --- a/iscsi/man5.jinja +++ /dev/null @@ -1,32 +0,0 @@ -{%- if data and component and provider -%} - -{%- macro man5(outdict, format, spaces=0) -%} - {%- for key, value in outdict.items() -%} -{{ man5conf(key, value, format, spaces, loop.last) }} - {%- endfor %} -{%- endmacro -%} - -{%- macro man5conf(key, value, f, spaces=0, last=False) -%} - {%- set shift = spaces * ' ' -%} -{%- if value is mapping %} -{{shift}}{{ f.quote }}{{ key }}{{ f.quote }}{{ ": [" if (json and not ucl) and key|lower != 'attributes' else ':' if (json and not ucl) else '' }} -{{shift}}{{ '{' if json or ucl else '' -}} -{{ man5(value, f, spaces|int+4) }} -{{ shift ~ '},' if (json or ucl) and key|lower == 'attributes' else shift ~ '}' if json or ucl else shift ~ '' }} -{{ shift ~ ']\n' if last and (json and not ucl) else shift ~ '],\n' if (json and not ucl) and key|lower != 'attributes' else '' }} -{%- elif value is string or value is number %} -{{ shift }}{{ f.quote }}{{ key }}{{ f.quote }}{{ f.div }}{{ '' if value is number else f.quote }}{{ value }}{{ '' if value is number else f.quote }}{{ '' if last and (json or ucl) else f.end }} -{%- elif value is iterable and value is not string %} -{{ shift }}{{ f.quote }}{{ key }}{{ f.quote }}{{ ' = {' if json or ucl else '' }} -{%- for v in value %} -{{ shift }}{{ shift }}{{ f.quote if v is not number else '' }}{{ v }}{{ f.quote if v is not number else '' }}{{ '' if loop.last and (json or ucl) else f.end }} -{%- endfor %} -{{ shift }}{{ "}" if json or ucl else '' }} -{%- endif %} -{%- endmacro -%} - -{%- set ucl = data['man5']['format']['ucl'] -%} -{{ '{' if json and not ucl else '' -}} -{{ man5(data['myconf'], data['man5']['format'], 8 if json and not ucl else 0) }} -{{- '}' if json and not ucl else '' }} -{%- endif -%} diff --git a/iscsi/map.jinja b/iscsi/map.jinja index 98509e4d..b576c8f0 100644 --- a/iscsi/map.jinja +++ b/iscsi/map.jinja @@ -1,35 +1,45 @@ -#Defaults -{% import_yaml "iscsi/defaults.yaml" as defaults %} -{% import_yaml "iscsi/initiator/defaults.yaml" as initiator %} -{% import_yaml "iscsi/target/defaults.yaml" as target %} -{% import_yaml "iscsi/isns/defaults.yaml" as isns %} -{% import_yaml "iscsi/osfamilymap.yaml" as osfamilymap %} -{% import_yaml "iscsi/codenamemap.yaml" as oscodenames %} +# -*- coding: utf-8 -*- +# vim: ft=jinja -# Merge site-specific data -{% set iscsi = salt['grains.filter_by']( - defaults, - merge=salt['grains.filter_by']( - initiator, +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{#- Start imports as #} +{%- import_yaml tplroot ~ "/defaults.yaml" as default_settings %} +{%- import_yaml tplroot ~ "/osfamilymap.yaml" as osfamilymap %} +{%- import_yaml tplroot ~ "/oscodename.yaml" as oscodename %} + +{#- Retrieve the config dict only once #} +{%- set _config = salt['config.get'](tplroot, default={}) %} + +{%- set defaults = salt['grains.filter_by']( + default_settings, + default=tplroot, + merge=salt['grains.filter_by']( + osfamilymap, + grain='os_family', merge=salt['grains.filter_by']( - target, - merge=salt['grains.filter_by']( - isns, - merge=salt['grains.filter_by']( - osfamilymap, - grain='os_family', - merge=salt['grains.filter_by']( - oscodenames, - grain='oscodename', - merge=salt['pillar.get']('iscsi', {}), - ), - base='iscsi', - ), - base='iscsi', - ), - base='iscsi', - ), - base='iscsi', - ), - base='iscsi', -) %} + oscodename, + grain='oscodename', + merge=salt['grains.filter_by']( + _config, + default='lookup' + ) + ) + ) + ) +%} + +{%- set config = salt['grains.filter_by']( + {'defaults': defaults}, + default='defaults', + merge=_config + ) +%} + +{%- set iscsi = config %} + +{#- Post-processing for specific non-YAML customisations #} +{%- if grains.os == 'MacOS' %} +{%- set macos_group = salt['cmd.run']("stat -f '%Sg' /dev/console") %} +{%- do iscsi.update({'rootgroup': macos_group}) %} +{%- endif %} diff --git a/iscsi/codenamemap.yaml b/iscsi/oscodename.yaml similarity index 60% rename from iscsi/codenamemap.yaml rename to iscsi/oscodename.yaml index e30d28df..10896cac 100644 --- a/iscsi/codenamemap.yaml +++ b/iscsi/oscodename.yaml @@ -1,51 +1,45 @@ - +# -*- coding: utf-8 -*- +# vim: ft=yaml +# {% macro debian_codename(name, codename=none) %} - -{%- set isns_svcname = 'isns' %} -{%- if name in ('wheezy', 'trusty',) %} - {%- set isns_wanted = ['isns', 'isns-client',] %} - {%- set srv_wanted = ['python-configshell', 'python-rtslib', 'targetcli', 'tgt-glusterfs', 'tgt-rbd',] %} -{%- elif name in ('xenial',) %} - {%- set isns_wanted = [] %} - {%- set isns_svcname = 'open-iscsi' %} - {%- set srv_wanted = ['python-configshell', 'python-rtslib', 'targetcli',] %} -{%- elif name in ('jessie',) %} - {%- set isns_wanted = [] %} - {%- set srv_wanted = ['tgt-glusterfs', 'tgt-rbd',] %} -{%- else %} - {%- set isns_wanted = ['open-isns-server', 'open-isns-utils', 'open-isns-discoveryd',] %} - {%- set srv_wanted = ['python-configshell-fb', 'python-rtslib-fb', 'targetcli-fb', 'iscsiuio',] %} -{%- endif %} - {{ codename|default(name, true) }}: isns: - isnsd: - man5: - svcname: {{ isns_svcname }} pkgs: - wanted: {{ isns_wanted }} - server: + {%- if name in ('wheezy', 'trusty',) %} + wanted: ['isns', 'isns-client'] + {%- elif name in ('xenial', 'jessie') %} + wanted: [] + {%- else %} + wanted: ['open-isns-server', 'open-isns-utils', 'open-isns-discoveryd',] + {%- endif %} + target: pkgs: - wanted: {{ srv_wanted }} - + {%- if name in ('wheezy', 'trusty',) %} + wanted: ['python-configshell', 'python-rtslib', 'targetcli', 'tgt-glusterfs', 'tgt-rbd'] + {%- elif name in ('xenial',) %} + wanted: ['python-configshell', 'python-rtslib', 'targetcli'] + {%- elif name in ('jessie',) %} + wanted: ['tgt-glusterfs', 'tgt-rbd',] + {%- else %} + wanted: ['python-configshell-fb', 'python-rtslib-fb', 'targetcli-fb', 'iscsiuio'] + {%- endif %} {% endmacro %} - {% macro fedora_codename(name, release, codename) %} {{ codename|default(name, true) }}: -{%- if release in (26, 27,) %} + {%- if release in (26, 27,) %} isns: pkgs: wanted: - isns-utils - isns-utils-libs - target-isns - server: + target: pkgs: wanted: - targetcli - fcoe-utils - {%- endif %} + {%- endif %} {% endmacro %} ## Debian GNU/Linux diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index e54238a5..2981132d 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -1,41 +1,26 @@ -# vim: sts=2 ts=2 sw=2 et ai +# -*- coding: utf-8 -*- +# vim: ft=yaml # -FreeBSD: - group: wheel - isns: - pkgs: - wanted: - - net/open-isns - unwanted: [] - server: - provider: ctld - pkgs: - wanted: - - net/istgt - unwanted: [] - client: - provider: iscsid - pkgs: - wanted: [] - unwanted: [] - kernel: - modconfig: /boot/loader.conf - modload: kldload - modunload: kldunload - modquery: kldstat -q -m - +# Setup variables using grains['os_family'] based logic. +# You just need to add the key:values for an `os_family` that differ +# from `defaults.yaml` + `osarch.yaml`. +# Only add an `os_family` which is/will be supported by the formula. +# +# If you do not need to provide defaults via the `os_family` grain, +# you will need to provide at least an empty dict in this file, e.g. +# osfamilymap: {} +--- Debian: - isns: - isnsd: - man5: - svcname: isns - client: + config: + servicename: + isns: isnsd + initiator: enabled: False #see https://github.com/saltstack-formulas/iscsi-formula/issues/10 pkgs: wanted: - open-iscsi - libiscsi-bin - server: + target: provider: lio pkgs: wanted: @@ -46,68 +31,24 @@ Debian: - iscsitarget - iscsitarget-dkms -Gentoo: - server: - pkgs: - wanted: - # sys-block/tgt - - sys-block/targetcli-fb - target: - lio: - man5: - kmodule: iscsi_tcp - -Arch: - initiator: - open-iscsi: - man5: - svcname: ##open-iscsi on archlinux uses non-standard service names - - iscsi - - iscsid - user: iscsimake - client: - make: - gitrepo: https://aur.archlinux.org - cmd: makepkg -si --noconfirm -f - wanted: [] - server: - pkgs: - wanted: - - python-pip # makepkg states needs 'gitpython'. - - thin-provisioning-tools - - linux-lts # For kernel scsi modules - make: - gitrepo: https://aur.archlinux.org - cmd: makepkg -si --noconfirm -f - wanted: - # rdma-core - # tgt-rdma - - python-rtslib-fb - - python-configshell-fb - - targetcli-fb - target: - lio: - man5: - kmodule: target-core-mod - RedHat: + config: + servicename: + open-iscsi: iscsi + isns: isnsd isns: pkgs: wanted: - isns-utils - yum-plugin-versionlock initiator: - open-iscsi: - man5: - svcname: iscsid - client: pkgs: wanted: - iscsi-initiator-utils - iscsi-initiator-utils-iscsiuio - libiscsi - libiscsi-utils - server: + target: pkgs: wanted: - libvirt-daemon-driver-storage-iscsi @@ -120,25 +61,24 @@ RedHat: # fcoe-target-utils Suse: + config: + servicename: + open-iscsi: iscsid isns: pkgs: wanted: - open-isns initiator: - open-iscsi: - man5: - svcname: iscsid - client: pkgs: wanted: - - libopeniscsiusr0_2_0 + - libopen-iscsiusr0_2_0 - open-iscsi - libiscsi8 - librdmacm1 - qemu-block-iscsi - iscsiuio - yast2-iscsi-client - server: + target: pkgs: wanted: - python3-configshell-fb @@ -152,3 +92,73 @@ Suse: - yast2-iscsi-lio-server - qemu-block-iscsi +Gentoo: + target: + pkgs: + wanted: + - sys-block/targetcli-fb + # sys-block/tgt + config: + kmodule: + lio: iscsi_tcp + +Arch: + config: + servicename: + open-iscsi: ##open-iscsi on archlinux uses non-standard service names + - iscsi + - iscsid + initiator: + make: + gitrepo: https://aur.archlinux.org + cmd: makepkg -si --noconfirm -f + wanted: [] + target: + pkgs: + wanted: + - python-pip # makepkg states needs 'gitpython'. + - thin-provisioning-tools + - linux-lts # For kernel scsi modules + make: + gitrepo: https://aur.archlinux.org + cmd: makepkg -si --noconfirm -f + wanted: + # rdma-core + # tgt-rdma + - python-rtslib-fb + - python-configshell-fb + - targetcli-fb + +Alpine: {} + +FreeBSD: + rootgroup: wheel + isns: + pkgs: + wanted: + - net/open-isns + target: + provider: ctld + pkgs: + wanted: + - net/istgt + initiator: + provider: iscsi + pkgs: + wanted: [] + config: + name: + modprobe: /boot/loader.conf + kernel: + modload: kldload + modunload: kldunload + modquery: kldstat -q -m + +OpenBSD: + rootgroup: wheel + +Solaris: {} + +Windows: {} + +MacOS: {} diff --git a/iscsi/remove.sls b/iscsi/remove.sls deleted file mode 100644 index 35a14dc6..00000000 --- a/iscsi/remove.sls +++ /dev/null @@ -1,5 +0,0 @@ - -include: - - iscsi.isns.remove - - iscsi.initiator.remove - - iscsi.target.remove diff --git a/iscsi/target/clean.sls b/iscsi/target/clean.sls new file mode 100644 index 00000000..b4fc9a4f --- /dev/null +++ b/iscsi/target/clean.sls @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .service.clean + - .config.clean + - .kernel.clean + - .make.clean + - .package.clean diff --git a/iscsi/target/config/clean.sls b/iscsi/target/config/clean.sls new file mode 100644 index 00000000..6981246e --- /dev/null +++ b/iscsi/target/config/clean.sls @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_clean = tplroot ~ '.target.service.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_service_clean }} + +iscsi-target-config-clean-file-absent: + file.absent: + - name: {{ iscsi.config.name['target'] }} + - watch_in: + - sls: {{ sls_service_clean }} diff --git a/iscsi/target/config/files/default/ctld.tmpl b/iscsi/target/config/files/default/ctld.tmpl new file mode 100644 index 00000000..cd9ef970 --- /dev/null +++ b/iscsi/target/config/files/default/ctld.tmpl @@ -0,0 +1,27 @@ +######################################################################## +# File managed by Salt at <{{ source }}>. +# Your changes will be overwritten. +######################################################################## + +{% if data and component and provider -%} + +{%- macro readconf(outdict, spaces=0) -%} + {%- for key, value in outdict.items() -%} +{{ ctld(key, value, spaces) }} + {%- endfor %} +{%- endmacro -%} + +{%- macro ctld(key, value, spaces=0, last=False) -%} + {%- set shift = spaces * ' ' -%} + {%- if value is mapping %} +{{shift}}{{ key }} { +{{ readconf(value, spaces|int+4) }} +{{shift}}} + {%- elif value is string or value is number %} +{{shift}}{{ key }} {{ value }} + {%- endif %} +{%- endmacro -%} + +{{ readconf(data['ctld'], 0) }} + +{% endif -%} diff --git a/iscsi/target/config/files/default/ietd.tmpl b/iscsi/target/config/files/default/ietd.tmpl new file mode 100644 index 00000000..1b568a0f --- /dev/null +++ b/iscsi/target/config/files/default/ietd.tmpl @@ -0,0 +1,26 @@ +######################################################################## +# File managed by Salt at <{{ source }}>. +# Your changes will be overwritten. +######################################################################## + +{% if data and component and provider -%} + +{%- macro readconf(outdict, spaces=0) -%} + {%- for key, value in outdict.items() -%} +{{ ietd(key, value, spaces, loop.last) }} + {%- endfor %} +{%- endmacro -%} + +{%- macro ietd(key, value, spaces=0, last=False) -%} + {%- set shift = spaces * ' ' -%} +{%- if value is mapping %} +{{shift}}{{ key }} +{{ readconf(value, spaces|int+4) }} +{%- elif value is string or value is number %} +{{shift}}{{ key }} {{ "'" if value is not string else ''}}{{ value }}{{"'" if value is not string else ''}} +{%- endif %} +{%- endmacro -%} + +{{ readconf(data, 0) }} + +{% endif %} diff --git a/iscsi/target/config/files/default/lio.tmpl b/iscsi/target/config/files/default/lio.tmpl new file mode 100644 index 00000000..d0060832 --- /dev/null +++ b/iscsi/target/config/files/default/lio.tmpl @@ -0,0 +1,38 @@ +######################################################################## +# File managed by Salt at <{{ source }}>. +# Your changes will be overwritten. +######################################################################## + +{% set arrays = ('fabric_modules', 'storage_objects', 'alua_tpgs', 'targets', 'node_acls', 'luns', 'mapped_luns', 'portals')%} + +{%- if data and component and provider -%} + +{%- macro lio(outdict, spaces=0, parent=False) -%} + {%- for key, value in outdict.items() -%} +{{ readconf(key, value, spaces, parent, loop.last) }} + {%- endfor %} +{%- endmacro -%} + +{%- macro readconf(key, value, spaces=0, parent=None, last=False) -%} + {%- set shift = spaces * ' ' -%} + {%- if value is mapping and not value -%} +{{ shift ~ '{}\n' if last else shift ~ '{},\n' }} + {%- elif value is mapping and value and parent|lower not in arrays %} +{{ shift ~ '"' ~ key ~ '": ' }}{{ '{' if key|lower in ('parameters', 'attributes') else '[\n' ~ shift ~ ' {' if key|lower in ('tpgs',) else '[' }} +{{ lio(value, spaces+4, key) }} +{{ shift ~ '}' if last and key in ('tpgs', 'parameters', 'attributes') else shift ~ '},' if key in ('parameters', 'attributes') else '' -}} +{{ shift ~ ']' if last and key not in ('parameters', 'attributes') else shift ~ '],' if key not in ('parameters', 'attributes') else '' -}} + {%- elif value is mapping and value -%} +{{ shift ~ ' {' if key|lower not in ('parameters', 'attributes') else '' -}} +{{ lio(value, spaces|int+4, key) }} +{{ shift ~ ' }' if last else shift ~ ' },\n' }} + {%- elif value is string or value is number %} +{{ shift }}"{{ key }}": {{ '' if value is number else '"' }}{{ value }}{{ '' if value is number else '"' }}{{ '' if last else ',' -}} +{{ '\n' ~ shift ~ '}' if last and parent|lower in ('tpgs',) else '' -}} + {%- endif %} +{%- endmacro -%} + +{{- '{' -}} +{{ lio(data, 4) }} +{{ '}' -}} +{%- endif -%} diff --git a/iscsi/target/config/files/default/tgtd.tmpl b/iscsi/target/config/files/default/tgtd.tmpl new file mode 100644 index 00000000..cb0f3806 --- /dev/null +++ b/iscsi/target/config/files/default/tgtd.tmpl @@ -0,0 +1,28 @@ +######################################################################## +# File managed by Salt at <{{ source }}>. +# Your changes will be overwritten. +######################################################################## + +{% if data and component and provider -%} + +{%- macro readconf(outdict, spaces=0) -%} + {%- for key, value in outdict.items() -%} +{{ tgtd(key, value, spaces) }} + {%- endfor %} +{%- endmacro -%} + +{%- macro tgtd(key, value, spaces=0, last=False) -%} + {%- set shift = spaces * ' ' -%} + {%- if value is mapping %} +{{shift}}<{{ key }}> +{{ readconf(value, spaces|int+4) }} +{{shift}} + + {%- elif value is string or value is number %} +{{shift}}{{ key }} {{ value }} + {%- endif %} +{%- endmacro -%} + +{{ readconf(data['tgtd'], 0) }} + +{% endif -%} diff --git a/iscsi/target/config/init.sls b/iscsi/target/config/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/target/config/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/target/config/install.sls b/iscsi/target/config/install.sls new file mode 100644 index 00000000..706d42ee --- /dev/null +++ b/iscsi/target/config/install.sls @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_install = tplroot ~ '.target.service.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} +{%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} + +include: + - {{ sls_service_install }} + +iscsi-target-config-install-file-managed: + file.managed: + - name: {{ iscsi.config.name[iscsi.target.provider] }} + - source: {{ files_switch([iscsi.target.provider ~ '.tmpl'], + lookup='iscsi-target-config-install-file-managed', + use_subpath=True + ) + }} + - mode: {{ iscsi.filemode }} + - user: root + - group: {{ iscsi.rootgroup }} + - makedirs: True + - template: jinja + - require_in: + - sls: {{ sls_service_install }} + - context: + data: {{ iscsi.config.data[iscsi.target.provider|string]|json }} + component: target + provider: {{ iscsi.target.provider|string }} diff --git a/iscsi/target/defaults.yaml b/iscsi/target/defaults.yaml deleted file mode 100644 index 4ec5e378..00000000 --- a/iscsi/target/defaults.yaml +++ /dev/null @@ -1,149 +0,0 @@ -# vim: sts=2 ts=2 sw= et ai -# -iscsi: - target: - port: 3260 - tgtd: #TGT Project (userspace) RedHat/Debian/Arch/Gentoo - man5: - manpage: targets.conf(5) - vendor: tgt project - svcname: tgtd - kmodule: None - kmoduletext: xxxxxxxxx="YES" - svcloadfile: - svcloadtext: - config: /etc/tgt/conf.d/targets.conf - format: - div: " " - quote: '' - end: '' - ucl: False - json: False - html: True - myconf: - include: '/etc/tgt/salt/*.conf' - default-driver: iscsi - iSNSServerIP: 127.0.0.1 - iSNSServerPort: 3205 - iSNSAccessControl: Off - iSNS: Off - #ignore-errors: yes - targets: {} - - ctld: #FreeBSD Foundation - man5: - manpage: ctl.conf(5) - vendor: FreeBSD Foundation - svcname: ctld #CAM Target Layer/iSCSI target daemon - kmodule: cfiscsi - kmoduletext: cfiscsi_load="YES" - svcloadfile: /etc/rc.conf - svcloadtext: 'iscsid_enable="YES"' - allowconf: - denyconf: - config: /etc/ctl.conf - format: - div: " " - quote: '' - end: '' - ucl: True - json: True #compatible with ucl in theory (in practice?) - html: False - myconf: - debug: 0 - maxproc: 0 - pidfile: /var/run/ctld.pid - timeout: 60 - isns-period: 900 - isns-timeout: 5 - auth-group: {} - portal-group: {} - lun: {} - target: {} - - ietd: - #iSCSI Enterprise Target (IET) project - man5: - manpage: ietd.conf(5) - vendor: IET project - svcname: iscsitarget - kmodule: iscsi_trgt - kmoduletext: iscsi_trgt - svcloadfile: - svcloadtext: - config: /etc/ietd.conf - allowconf: /etc/ietd/initiators.allow - denyconf: /etc/ietd/initiators.deny - format: - div: " " - quote: '' - end: '' - ucl: False - json: False - html: False - myconf: - HeaderDigest: None - DataDigest: None - MaxConnections: 1 - MaxSessions: 0 - InitialR2T: Yes - ImmediateData: No - MaxRecvDataSegmentLength: 8192 - MaxXmitDataSegmentLength: 8192 - MaxBurstLength: 262144 - FirstBurstLength: 65536 - DefaultTime2Wait: 2 - DefaultTime2Retain: 0 - MaxOutstandingR2T: 8 - NOPInterval: 0 - NOPTimeout: 0 - DataPDUInOrder: Yes - DataSequenceInOrder: Yes - ErrorRecoveryLevel: 0 - targets: {} - - lio: - man5: - manpage: - vendor: Kernel.org - svcname: target - kmodule: target_core_mod - kmoduletext: target_core_mod - svcloadfile: - svcloadtext: - config: /etc/target/saveconfig.json - allowconf: - denyconf: - format: - div: ": " - quote: '"' - end: ',' - ucl: False - json: True - html: False - myconf: - fabric_modules: {} - storage_objects: {} - targets: {} - -### FUTURE TODO - fcoe: #Fibre-Channel over Ethernet (FCoE) Target - man5: - manpage: - vendor: - svcname: fcoe-target - kmodule: fcoe_target - kmoduletext: fcoe_target - svcloadfile: - svcloadtext: - config: .... - targetifce: eth2 - format: - div: " = " - quote: '' - end: '' - ucl: False - json: False - html: False - myconf: {} - diff --git a/iscsi/target/init.sls b/iscsi/target/init.sls index 54b70169..5e9b1ac3 100644 --- a/iscsi/target/init.sls +++ b/iscsi/target/init.sls @@ -1,3 +1,9 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls include: - - .install + - .package + - .make + - .kernel + - .config + - .service diff --git a/iscsi/target/install.sls b/iscsi/target/install.sls deleted file mode 100644 index 2ca27963..00000000 --- a/iscsi/target/install.sls +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: ft=sls -{%- from "iscsi/map.jinja" import iscsi with context %} - - {%- set provider = iscsi.server.provider %} - {%- set data = iscsi.target[provider|string] %} - - {%- if iscsi.server.pkgs.unwanted %} - {%- for pkg in iscsi.server.pkgs.unwanted %} -iscsi_target_unwanted_pkgs_{{ pkg }}: - pkg.purged: - - name: {{ pkg }} - - require_in: - - file: iscsi_target_service_config - {% endfor %} - {%- endif %} - - {%- if iscsi.server.pkgs.wanted %} - {%- for pkg in iscsi.server.pkgs.wanted %} -iscsi_target_wanted_pkgs_{{ pkg }}: - pkg.installed: - - name: {{ pkg }} - {%- if iscsi.server.pkghold %} - - hold: {{ iscsi.server.pkghold }} - {%- endif %} - - reload: True - - require_in: - - file: iscsi_target_service_config - {% endfor %} - {%- endif %} - -{%-if iscsi.server.make.wanted and salt['cmd.run']("id iscsi.user", output_loglevel='quiet')%} - {%- for pkg in iscsi.server.make.wanted %} -iscsi_target_make_pkg_{{ pkg }}: - file.directory: - - name: /home/{{ iscsi.user }} - - makedirs: True - - user: {{ iscsi.user }} - - dir_mode: '0755' - {%- if iscsi.server.make.gitrepo %} - git.latest: - - name: {{ iscsi.server.make.gitrepo }}/{{ pkg }}.git - - target: /home/{{ iscsi.user }}/{{ pkg }} - - user: {{ iscsi.user }} - - force_clone: True - - force_fetch: True - - force_reset: True - - force_checkout: True - {% if grains['saltversioninfo'] >= [2017, 7, 0] %} - - retry: - attempts: 3 - until: True - interval: 60 - splay: 10 - {%- endif %} - - require: - - file: iscsi_target_make_pkg_{{ pkg }} - {%- endif %} - cmd.run: - - cwd: /home/{{ iscsi.user }}/{{ pkg }} - - name: {{ iscsi.server.make.cmd }} - - runas: {{ iscsi.user }} - {% endfor %} -{%- endif %} - -iscsi_target_service_config: - file.managed: - - name: {{ data.man5.config }} - - source: {{ iscsi.cfgsource }} - - template: jinja - - user: root - - group: {{ iscsi.group }} - - mode: {{ iscsi.filemode }} - - makedirs: True - - context: - data: {{ data|json }} - component: 'target' - provider: {{ provider }} - json: {{ data.man5.format.json|json }} - - {%- if iscsi.kernel.mess_with_kernel and data.man5.kmodule and data.man5.kmoduletext %} -iscsi_target_kernel_module: - file.line: - - name: {{ iscsi.kernel.modloadfile }} - - content: {{ data.man5.kmoduletext }} - - backup: True - {%- if not data.enabled %} - - mode: delete - cmd.run: - - name: {{ iscsi.kernel.modunload }} {{ data.man5.kmodule }} - - onlyif: {{ iscsi.kernel.modquery }} {{ data.man5.kmodule }} - {%- else %} - - mode: ensure - - after: 'autoboot_delay.*' - cmd.run: - - name: {{ iscsi.kernel.modload }} {{ data.man5.kmodule }} - - unless: {{ iscsi.kernel.modquery }} {{ data.man5.kmodule }} - - require: - - file: iscsi_target_kernel_module - {%- endif %} - - require_in: - - service: iscsi_target_service_running - {%- endif %} - - {%- if grains.os == 'FreeBSD' %} -iscsi_target_service_freebsd_support: - file.line: - - name: {{ data.man5.svcloadfile }} - - content: 'ctld_env="-u"' - - backup: True - {%- if not iscsi.server.enabled %} - - mode: delete - {%- else %} - - mode: ensure - - after: 'sshd_enable.*' - {%- endif %} - {%- endif %} - -iscsi_target_service_running: - {%- if not iscsi.server.enabled %} - service.disabled: - - enable: False - {%- else %} - service.running: - - enable: True - - require: - - file: iscsi_target_service_config - - watch: - - file: iscsi_target_service_config - {%- endif %} - - name: {{ data.man5.svcname }} - {%- if data.man5.kmodule %} - - unless: {{ iscsi.kernel.modquery }} {{ data.man5.kmodule }} - {%- endif %} - -iscsi_target_service_running_failure_explanation: - test.show_notification: - - text: | - In certain circumstances the iscsi target service will not start. - One reason is your kernel version was upgraded but host not rebooted. - If that's the case then run command: - 'systemctl enable {{ data.man5.svcname }}' && reboot - - onfail: - - service: iscsi_target_service_running - - unless: {{ grains.os_family in ('MacOS', 'Windows') }} #maybe not needed but no harm diff --git a/iscsi/target/kernel/clean.sls b/iscsi/target/kernel/clean.sls new file mode 100644 index 00000000..06c0a0ca --- /dev/null +++ b/iscsi/target/kernel/clean.sls @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_clean = tplroot ~ '.target.service.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} +{%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} + + {%- set provider = iscsi.target.provider %} + {%- if iscsi.kernel.mess_with_kernel and provider in iscsi.config.kmodule %} +include: + - {{ sls_service_clean }} + +iscsi-target-kernel-clean-cmd-run: + cmd.run: + - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} + - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} + - require: + - sls: {{ sls_service_clean }} + +iscsi-target-kernel-clean-file-line: + file.line: + - onlyif: {{ iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} + - name: {{ iscsi.config.name.modprobe }} + - content: {{ data.config.kmodule[provider]['text'] }} + - backup: True + - mode: delete + + {%- endif %} diff --git a/iscsi/target/kernel/init.sls b/iscsi/target/kernel/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/target/kernel/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/target/kernel/install.sls b/iscsi/target/kernel/install.sls new file mode 100644 index 00000000..b2ec9c81 --- /dev/null +++ b/iscsi/target/kernel/install.sls @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_install = tplroot ~ '.target.service.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} +{%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} + + {%- set provider = iscsi.target.provider %} + {%- if iscsi.kernel.mess_with_kernel and provider in iscsi.config.kmodule %} +include: + - {{ sls_service_install }} + +iscsi-target-kernel-install-file-line: + file.line: + - onlyif: {{ iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} + - name: {{ iscsi.config.name.modprobe }} + - content: {{ data.config.kmodule[provider]['text'] }} + - require_in: + - cmd: iscsi-target-kernel-install-file-line + - backup: True + +iscsi-target-kernel-install-cmd-run: + {%- if not iscsi.target.enabled %} + cmd.run: + - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} + - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} + - mode: delete + {%- else %} + - mode: ensure + - after: autoboot_delay.*$ + cmd.run: + - name: {{ iscsi.kernel.modload }} {{ iscsi.config.kmodule[provider]['name'] }} + - unless: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} + {%- endif %} + - require: + - file: iscsi-target-kernel-install-file-line + - require_in: + - sls: {{ sls_service_install }} + + {%- endif %} diff --git a/iscsi/target/make/clean.sls b/iscsi/target/make/clean.sls new file mode 100644 index 00000000..2bbff228 --- /dev/null +++ b/iscsi/target/make/clean.sls @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_clean = tplroot ~ '.target.service.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + + {%- if iscsi.target.make.wanted %} + {%- for pkg in iscsi.target.make.wanted %} +include: + - {{ sls_service_clean }} + +iscsi-target-package-make-clean-{{ pkg }}-removed: + pkg.removed: + - name: {{ pkg }} + - require: + - sls: {{ sls_service_clean }} + + {%- endfor %} + {%- endif %} diff --git a/iscsi/target/make/init.sls b/iscsi/target/make/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/target/make/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/target/make/install.sls b/iscsi/target/make/install.sls new file mode 100644 index 00000000..87057c92 --- /dev/null +++ b/iscsi/target/make/install.sls @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_service_install = tplroot ~ '.target.service.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + + {%- if iscsi.target.make.wanted %} + {%- if salt['cmd.run']("id iscsi.user", output_loglevel='quiet') %} +include: + - {{ sls_service_install }} + +iscsi-target-make-file-directory: + file.directory: + - name: /home/{{ iscsi.user }} + - makedirs: True + - user: {{ iscsi.user }} + - dir_mode: '0755' + + {%- for pkg in iscsi.target.make.wanted %} + +iscsi-target-make-{{ pkg }}-git-latest: + git.latest: + - onlyif: {{ iscsi.target.make.gitrepo }} + - name: {{ iscsi.target.make.gitrepo }}/{{ pkg }}.git + - target: /home/{{ iscsi.user }}/{{ pkg }} + - user: {{ iscsi.user }} + - force_clone: True + - force_fetch: True + - force_reset: True + - force_checkout: True + {% if grains['saltversioninfo'] >= [2017, 7, 0] %} + - retry: + attempts: 3 + until: True + interval: 60 + splay: 10 + {%- endif %} + - require: + - file: iscsi-target-make-file-directory + +iscsi-target-make-{{ pkg }}-cmd-run: + cmd.run: + - cwd: /home/{{ iscsi.user }}/{{ pkg }} + - name: {{ iscsi.target.make.cmd }} + - runas: {{ iscsi.user }} + - require: + - git: iscsi-target-make-{{ pkg }}-git-latest + - require_in: + - sls: {{ sls_service_install }} + + {% endfor %} + {%- endif %} + {%- endif %} diff --git a/iscsi/target/package/clean.sls b/iscsi/target/package/clean.sls new file mode 100644 index 00000000..c5cdd65c --- /dev/null +++ b/iscsi/target/package/clean.sls @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_clean = tplroot ~ '.target.config.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_clean }} + + {%- for pkg in [iscsi.target.pkgs.wanted, iscsi.target.pkgs.unwanted] %} + +iscsi-target-package-clean-{{ pkg }}-removed: + pkg.purged: + - name: {{ pkg }} + - require: + - sls: {{ sls_config_clean }} + + {% endfor %} diff --git a/iscsi/target/package/init.sls b/iscsi/target/package/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/target/package/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/target/package/install.sls b/iscsi/target/package/install.sls new file mode 100644 index 00000000..e58b6939 --- /dev/null +++ b/iscsi/target/package/install.sls @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_install = tplroot ~ '.target.config.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_install }} + + {%- if iscsi.target.pkgs.unwanted %} + {%- for pkg in iscsi.target.pkgs.unwanted %} + +iscsi-target-package-install-{{ pkg }}-removed: + pkg.purged: + - name: {{ pkg }} + - require_in: + - sls: {{ sls_config_install }} + + {%- endfor %} + {%- endif %} + {%- if iscsi.target.pkgs.wanted %} + {%- for pkg in iscsi.target.pkgs.wanted %} + +iscsi-target-package-install-{{ pkg }}-installed: + pkg.installed: + - name: {{ pkg }} + {%- if iscsi.target.pkghold %} + - hold: {{ iscsi.target.pkghold }} + {%- endif %} + - reload: True + - require_in: + - sls: {{ sls_config_install }} + + {%- endfor %} + {%- endif %} diff --git a/iscsi/target/remove.sls b/iscsi/target/remove.sls deleted file mode 100644 index f8e0b40f..00000000 --- a/iscsi/target/remove.sls +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: ft=sls -{%- from "iscsi/map.jinja" import iscsi with context %} - - {%- set provider = iscsi.server.provider %} - {%- set data = iscsi.target[provider|string] %} - -iscsi_target_service_dead: - file.line: - - name: {{ data.man5.svcloadfile }} - - content: {{ data.man5.svcloadtext }} - - backup: True - - mode: delete - service.dead: - - enable: False - {%- if data.man5.kmodule %} - - onlyif: {{ iscsi.kernel.modquery }} {{ data.man5.kmodule }} - {%- endif %} - - {%- if iscsi.kernel.mess_with_kernel and data.man5.kmodule and data.man5.kmoduletext %} -iscsi_target_kernel_module_{{ data.man5.kmodule }}_removed: - file.line: - - name: {{ iscsi.kernel.modloadfile }} - - content: {{ data.man5.kmoduletext }} - - backup: True - - mode: delete - cmd.run: - - name: {{ iscsi.kernel.modunload }} {{ data.man5.kmodule }} - - onlyif: {{ iscsi.kernel.modquery }} {{ data.man5.kmodule }} - - require: - - iscsi_target_service_dead - - require_in: - - iscsi_target_service_config_removed - {%- endif %} - - {%- for pkg in [iscsi.server.pkgs.unwanted, iscsi.server.pkgs.unwanted] %} -iscsi_target_wanted_pkgs_{{ pkg }}_removed: - pkg.purged: - - name: {{ pkg }} - - require_in: - - file: iscsi_target_service_config_removed - {% endfor %} - -iscsi_target_service_config_removed: - file.absent: - - name: {{ data.man5.config }} - - onlyif: test -f {{ data.man5.config }} diff --git a/iscsi/target/service/clean.sls b/iscsi/target/service/clean.sls new file mode 100644 index 00000000..8dde7e3b --- /dev/null +++ b/iscsi/target/service/clean.sls @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_clean = tplroot ~ '.target.config.clean' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_clean }} + +iscsi-target-service-clean-service-dead + service.dead: + - name: {{ iscsi.config.servicename[iscsi.target.provider] }} + - enable: False + - require_in: + - sls: {{ sls_config_clean }} + +iscsi-target-service-install-file-line-freebsd: + file.line: + - onlyif: {{ grains.os == 'FreeBSD' }} + - name: {{ iscsi.config.name.modprobe }} + - content: 'ctld_env="-u"' + - mode: delete + - backup: True diff --git a/iscsi/target/service/init.sls b/iscsi/target/service/init.sls new file mode 100644 index 00000000..d3e55181 --- /dev/null +++ b/iscsi/target/service/init.sls @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +include: + - .install diff --git a/iscsi/target/service/install.sls b/iscsi/target/service/install.sls new file mode 100644 index 00000000..9eb64669 --- /dev/null +++ b/iscsi/target/service/install.sls @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{#- Get the `tplroot` from `tpldir` #} +{%- set tplroot = tpldir.split('/')[0] %} +{%- set sls_config_install = tplroot ~ '.target.config.install' %} +{%- from tplroot ~ "/map.jinja" import iscsi with context %} + +include: + - {{ sls_config_install }} + + {%- if grains.os == 'FreeBSD' %} +iscsi-target-service-install-file-line-freebsd: + file.line: + - name: {{ iscsi.config.name.modprobe }} + - content: 'ctld_env="-u"' + - backup: True + {%- if not iscsi.target.enabled %} + - mode: delete + {%- else %} + - mode: ensure + - after: 'sshd_enable.*' + - create: True + {%- endif %} + {%- endif %} + + {%- set servicename = iscsi.config.servicename[iscsi.target.provider] %} +iscsi-target-service-install-service-running: + {%- if not iscsi.target.enabled %} + service.dead: + - name: {{ servicename }} + - enable: False + {%- else %} + service.running: + - name: {{ servicename }} + - enable: True + - onfail_in: + - test: iscsi-target-service-install-failure-explanation + - require: + - sls: {{ sls_config_install }} + - watch: + - file: iscsi-target-config-install-file-managed + {%- endif %} + {%- if servicename is iterable and servicename is not string %} + - names: {{ servicename|json }} + {%- else %} + - name: {{ servicename }} + {%- endif %} + +iscsi-target-service-install-failure-explanation: + test.show_notification: + - text: | + In certain circumstances the iscsi target service will not start. + * your configuration file may be incorrect. + * your kernel was upgraded but not activated by reboot + 'systemctl enable {{ servicename }}' && reboot + - unless: {{ grains.os_family in ('MacOS', 'Windows') }} #maybe not needed but no harm diff --git a/kitchen.yml b/kitchen.yml new file mode 100644 index 00000000..557a86f4 --- /dev/null +++ b/kitchen.yml @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +# For help on this file's format, see https://kitchen.ci/ +driver: + name: docker + use_sudo: false + privileged: true + run_command: /lib/systemd/systemd + +# Make sure the platforms listed below match up with +# the `env.matrix` instances defined in `.travis.yml` +platforms: + ## SALT `develop` + - name: debian-10-develop-py3 + driver: + image: netmanagers/salt-develop-py3:debian-10 + provision_command: + - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com + - sh bootstrap-salt.sh -XdPbfrq -x python3 git develop + - name: ubuntu-1804-develop-py3 + driver: + image: netmanagers/salt-develop-py3:ubuntu-18.04 + provision_command: + - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com + - sh bootstrap-salt.sh -XdPbfrq -x python3 git develop + - name: centos-7-develop-py3 + driver: + image: netmanagers/salt-develop-py3:centos-7 + provision_command: + - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com + - sh bootstrap-salt.sh -XdPbfrq -x python3 git develop + - name: fedora-30-develop-py3 + driver: + image: netmanagers/salt-develop-py3:fedora-30 + provision_command: + - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com + - sh bootstrap-salt.sh -XdPbfrq -x python3 git develop + - name: opensuse-leap-15-develop-py3 + driver: + image: netmanagers/salt-develop-py3:opensuse-leap-15 + provision_command: + - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com + - sh bootstrap-salt.sh -XdPbfrq -x python3 git develop + run_command: /usr/lib/systemd/systemd + # Workaround to avoid intermittent failures on `opensuse-leap-15`: + # => SCP did not finish successfully (255): (Net::SCP::Error) + transport: + max_ssh_sessions: 1 + - name: amazonlinux-2-develop-py2 + driver: + image: netmanagers/salt-develop-py2:amazonlinux-2 + provision_command: + - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com + - sh bootstrap-salt.sh -XdPbfrq -x python2 git develop + + ## SALT `2019.2` + - name: debian-9-2019-2-py3 + driver: + image: netmanagers/salt-2019.2-py3:debian-9 + - name: ubuntu-1804-2019-2-py3 + driver: + image: netmanagers/salt-2019.2-py3:ubuntu-18.04 + - name: centos-7-2019-2-py3 + driver: + image: netmanagers/salt-2019.2-py3:centos-7 + - name: fedora-30-2019-2-py3 + driver: + image: netmanagers/salt-2019.2-py3:fedora-30 + - name: opensuse-leap-15-2019-2-py3 + driver: + image: netmanagers/salt-2019.2-py3:opensuse-leap-15 + run_command: /usr/lib/systemd/systemd + # Workaround to avoid intermittent failures on `opensuse-leap-15`: + # => SCP did not finish successfully (255): (Net::SCP::Error) + transport: + max_ssh_sessions: 1 + - name: amazonlinux-2-2019-2-py2 + driver: + image: netmanagers/salt-2019.2-py2:amazonlinux-2 + + ## SALT `2018.3` + - name: debian-9-2018-3-py2 + driver: + image: netmanagers/salt-2018.3-py2:debian-9 + - name: ubuntu-1604-2018-3-py2 + driver: + image: netmanagers/salt-2018.3-py2:ubuntu-16.04 + - name: centos-7-2018-3-py2 + driver: + image: netmanagers/salt-2018.3-py2:centos-7 + - name: fedora-29-2018-3-py2 + driver: + image: netmanagers/salt-2018.3-py2:fedora-29 + - name: opensuse-leap-15-2018-3-py2 + driver: + image: netmanagers/salt-2018.3-py2:opensuse-leap-15 + run_command: /usr/lib/systemd/systemd + # Workaround to avoid intermittent failures on `opensuse-leap-15`: + # => SCP did not finish successfully (255): (Net::SCP::Error) + transport: + max_ssh_sessions: 1 + - name: amazonlinux-2-2018-3-py2 + driver: + image: netmanagers/salt-2018.3-py2:amazonlinux-2 + + ## SALT `2017.7` + - name: debian-8-2017-7-py2 + driver: + image: netmanagers/salt-2017.7-py2:debian-8 + - name: ubuntu-1604-2017-7-py2 + driver: + image: netmanagers/salt-2017.7-py2:ubuntu-16.04 + - name: centos-6-2017-7-py2 + driver: + image: netmanagers/salt-2017.7-py2:centos-6 + run_command: /sbin/init + - name: fedora-29-2017-7-py2 + driver: + image: netmanagers/salt-2017.7-py2:fedora-29 + - name: opensuse-leap-15-2017-7-py2 + driver: + image: netmanagers/salt-2017.7-py2:opensuse-leap-15 + run_command: /usr/lib/systemd/systemd + # Workaround to avoid intermittent failures on `opensuse-leap-15`: + # => SCP did not finish successfully (255): (Net::SCP::Error) + transport: + max_ssh_sessions: 1 + - name: amazonlinux-2-2017-7-py2 + driver: + image: netmanagers/salt-2017.7-py2:amazonlinux-2 + +provisioner: + name: salt_solo + log_level: info + salt_install: none + require_chef: false + formula: template + salt_copy_filter: + - .kitchen + - .git + +verifier: + # https://www.inspec.io/ + name: inspec + sudo: true + # cli, documentation, html, progress, json, json-min, json-rspec, junit + reporter: + - cli + +suites: + - name: default + excludes: + - centos-6-2017-7-py2 + provisioner: + state_top: + base: + '*': + - template + pillars: + top.sls: + base: + '*': + - template + - define_roles + pillars_from_files: + template.sls: pillar.example + define_roles.sls: test/salt/pillar/define_roles.sls + verifier: + inspec_tests: + - path: test/integration/default + - name: centos6 + includes: + - centos-6-2017-7-py2 + provisioner: + state_top: + base: + '*': + - template + pillars: + top.sls: + base: + '*': + - template + - define_roles + pillars_from_files: + template.sls: test/salt/pillar/centos6.sls + define_roles.sls: test/salt/pillar/define_roles.sls + verifier: + inspec_tests: + - path: test/integration/default diff --git a/pillar.example b/pillar.example index cff13153..1bb269af 100644 --- a/pillar.example +++ b/pillar.example @@ -1,182 +1,581 @@ -#Sample pillars for iSNS/iSCSI on FreeBSD and GNU/Linux - - {% if grains.os == 'Arch' %} - ### this user is needed for archlinux -users: - iscsimake: - sudouser: True - shell: /bin/bash - empty_password: True - home: /home/iscsimake - createhome: True - optional_groups: - - wheel - - root - sudo_rules: - - 'ALL=(ALL) ALL' - {% endif %} - - +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- iscsi: - isns: - enabled: True - target: - - ctld: #FreeBSD - myconf: - isns-server: - - 'localhost' - auth-group: - worstdevs: - auth-type: 'none' - bestdevs: - chap-mutual: - - user = 'user' - - secret = 'secretsecret' - - mutual-user = "mutualuser" - - mutual-secret = "mutualsecret" - initiator-name: - - 'iqn.2012-06.com.example:initiatorhost1' - - 'iqn.2012-06.com.example:initiatorhost2' - initiator-portal: - - 192.168.1.1/16 - - '[2001:db8::de:ef]' - portal-group: - cloud-west-zone0: - discovery-auth-group: no-authentication - listen: - - '0.0.0.0:3260' - - '[::]:3260' - - '[fe80::be:ef]:3261' - lun: - example0: - Alias: 0 - path: /dev/zvol/tank/example_0 - blocksize: 4096 - size: 1G - example1: - Alias: nice1 - path: /dev/zvol/tank/example_1 - option: - - 'naa 0x50015178f369f093' - example2: - Alias: sillyexample2 - backend: block - path: /dev/zvol/tank/example_0block_backends - device-type: 0 - size: 5G - option: - vendor: myvendor - ha_role: primary - readcache: on - readonly: on - rpm: 0 - umap: on - writecache: on - file: /dev/sd - 3: - Alias: myfile - path: /tmp/myfile - size: 1G - target: - 'iqn.2008-04.com.example:target0': - Alias: bestdevs-cloudstore - auth-group: bestdevs - portal-group: cloud-west-zone0 - lun: - - name = example0 - 'naa.50015178f369f092': - port: - - isp0 - - isp1 - portal-group: cloud-west-zone0 - lun: - - name = example1 - 'iqn.2008-04.com.example:target1': - alias: lazydevs-cloudstore - auth-group: no-authentication - portal-group: cloud-west-zone0 - lun: - - name = example2 - - name = example3 - -#Linux targets - - lio: - myconf: - fabric_modules: - discovery_enable_auth: 'true' - discovery_mutual_password: "itsreallyme" - discovery_mutual_userid: "target" - discovery_password: "letmein" - discovery_userid: "initiator" - name: "iscsi" - storage_objects: - attributes: - block_size: 1024 - emulate_write_cache: 0 - max_sectors: 1024 - queue_depth: 128 - task_timeout: 0 - unmap_granularity: 0 - dev: "/dev/vg_storage/station4mp" - name: "mptarget4" - plugin: "block" - wwn: "6be30fb6-3bc9-43c4-a866-4d8633af5cf2" - targets: - fabric: iscsi - tpgs: - attributes: - authentication: 1 - cache_dynamic_acls: 0 - default_cmdsn_depth: 16 - demo_mode_write_protect: 1 - generate_node_acls: 0 - login_timeout: 15 - netif_timeout: 2 - prod_mode_write_protect: 0 - luns: - index: 0 - storage_object: "/backstores/block/mptarget4" - node_acls: - attributes: - dataout_timeout: 3 - dataout_timeout_retries: 5 - default_erl: 0 - nopin_response_timeout: 5 - nopin_timeout: 5 - random_datain_pdu_offsets: 0 - random_datain_seq_offsets: 0 - random_r2t_offsets: 0 - chap_mutual_password: "itsreallyme" - chap_mutual_userid: "target" - chap_password: "letmein" - chap_userid: "station4" - mapped_luns: - index: 0 - tpg_lun: 0 - write_protect: 'false' - node_wwn: "iqn.1994-05.com.redhat:station4" - tcq_depth: 16 - portals: - ip_address: "10.100.0.199" - port: 3260 - tag: 1 - wwn: "iqn.2003-01.org.linux-iscsi.storage:mptarget4" + config: + data: + open-iscsi: + ##https://github.com/open-iscsi/open-iscsi/blob/master/etc/iscsid.conf + node.startup: manual + node.leading_login: 'No' + node.session.timeo.replacement_timeout: 120 + node.conn[0].timeo.login_timeout: 15 + node.conn[0].timeo.logout_timeout: 15 + node.conn[0].timeo.noop_out_interval: 5 + node.conn[0].timeo.noop_out_timeout: 5 + node.session.err_timeo.abort_timeout: 15 + node.session.err_timeo.lu_reset_timeout: 30 + node.session.err_timeo.tgt_reset_timeout: 30 + node.session.initial_login_retry_max: 8 + node.session.cmds_max: 128 + node.session.queue_depth: 32 + node.session.xmit_thread_priority: -20 + node.session.iscsi.InitialR2T: 'No' + node.session.iscsi.ImmediateData: 'Yes' + node.session.iscsi.FirstBurstLength: 262144 + node.session.iscsi.MaxBurstLength: 16776192 + node.conn[0].iscsi.MaxRecvDataSegmentLength: 262144 + node.conn[0].iscsi.MaxXmitDataSegmentLength: 0 + discovery.sendtargets.iscsi.MaxRecvDataSegmentLength: 32768 + node.session.nr_sessions: 1 + node.session.iscsi.FastAbort: 'Yes' + node.session.scan: auto - initiator: - iscsid: - myconf: + iscsi: ##freeBSD + ##https://www.freebsd.org/cgi/man.cgi?query=iscsi.conf&sektion=5&manpath=FreeBSD+10-current node.startup: automatic - 'iqn.2018-07.com.example.iscsi:example01': + myiscsi: ##nickname + targetaddress: iscsi1 + targetname: 'iqn.1900.com.com:sn.123456' + myiscsi6: + targetaddress: '[2001:db8::de:ef]:3260' + targetname: 'iqn.1900.com.com:sn.123456' + chaptest: + targetaddress: 10.0.0.1 + targetname: 'iqn.1900.com.com:sn.123456' + initiatorname: 'iqn.2005-01.il.ac.huji.cs:nobody' + authmethod: CHAP + chapiname : 'iqn.2005-01.il.ac.huji.cs:nobody' + chapsecret: secretsecret + example01: + targetname: 'iqn.2018-07.com.example.iscsi:example01' targetAddress: '10.10.10.10' - 'naa.50015178f369f092': + data: + targetname: 'naa.50015178f369f092' targetAddress: data1.example.com chapIName: user chapSecret: secretsecret - 'iqn.2018-07.com.example.iscsi:secretdata1': + secret: + targetname: 'iqn.2018-07.com.example.iscsi:secretdata' targetAddress: creditcards.example.com authMethod: CHAP chapIName: 'iqn.2018-07.com.example.iscsi:trustedguy' chapSecret: secretsecret + ietd: + ##http://manpages.ubuntu.com/manpages/trusty/man5/ietd.conf.5.html + IncomingUser: joe secret + OutgoingUser: jack secret2 + 'Target iqn.2001-04.com.example:storage.disk2.sys1.xyz': + IncomingUser: jim othersecret + OutgoingUser: james yetanothersecret + 'Lun 0': Path=/dev/sdc,Type=fileio + 'Lun 1': Blocks=10000,BlockSize=4096,Type=nullio + Alias: Test + HeaderDigest: None + DataDigest: None + MaxConnections: 1 + MaxSessions: 0 + InitialR2T: 'Yes' + ImmediateData: 'No' + MaxRecvDataSegmentLength: 8192 + MaxXmitDataSegmentLength: 8192 + MaxBurstLength: 262144 + FirstBurstLength: 65536 + DefaultTime2Wait: 2 + DefaultTime2Retain: 0 + MaxOutstandingR2T: 8 + NOPInterval: 0 + NOPTimeout: 0 + DataPDUInOrder: 'Yes' + DataSequenceInOrder: 'Yes' + ErrorRecoveryLevel: 0 + + isnsadm: {} + isnsdd: {} + isnsd: + ##https://manpages.debian.org/testing/open-isns-server/isnsd.conf.5.en.html + Database: /var/lib/isns + RegistrationPeriod: 10m + DefaultDiscoveryDomain: 1 + SLPRegister: 1 + ClientKeyStore: 'DB:' + SCNTimeout: 60 + SCNRetries: 3 + ESIMinInterval: 1m + ESIMaxInterval: 2m + ESIRetries: 3 + Auth.ReplayWindow: 2m + Auth.TimeStampJitter: 1s + + tgtd: + ##https://linux.die.net/man/5/targets.conf + ##https://github.com/fujita/tgt/blob/master/conf/examples/targets.conf.example + include: '/etc/tgt/salt/*.conf' + default-driver: iscsi + iSNSServerIP: 127.0.0.1 + iSNSServerPort: 3205 + iSNSAccessControl: Off + iSNS: Off + 'target iqn.2008-09.com.example:server.target1': + backing-store: /dev/LVM/somedevice + 'target iqn.2008-09.com.example:server.target5': + 'direct-store /dev/sdd': + vendor_id: VENDOR1 + removable: 1 + device-type: cd + lun: 1 + 'direct-store /dev/sda': + vendor_id: VENDOR2 + lun: 2 + 'backing-store /dev/sdb1': + vendor_id: back1 + scsi_sn: SERIAL + write-cache: on + + lio: + ##https://www.systutorials.com/docs/linux/man/5-saveconfig.json + ##---------------------------------------------------------------------- + ## The sample below is complex because its used for verification. + ## You should only use the minimal pillar data for your needs. + ## The format is important so review this example carefully. + ## + ## Multiple objects are supported: + ## See: https://github.com/saltstack-formulas/iscsi-formula/issues/19 + ##---------------------------------------------------------------------- + fabric_modules: + callmewhateveryoulike0: + discovery_enable_auth: 'true' + discovery_mutual_password: itsreallyme + discovery_mutual_userid: target + discovery_password: letmein + discovery_userid: initiator + name: iscsi + callmewhateveryoulike1: + discovery_enable_auth: 'true' + discovery_mutual_password: itsreallysticky + storage_objects: + callmewhateveryoulike_sda: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd0 + name: sda + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf090' + callmewhateveryoulike_sdb: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd1 + name: sdb + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf091' + callmewhateveryoulike_sdc: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd2 + name: sdc + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf092' + callmewhateveryoulike_sdd: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd3 + name: sdd + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf093' + callmewhateveryoulike_sde: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd4 + name: sde + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf094' + targets: + canada: + fabric: iscsi + tpgs: + attributes: + authentication: 0 + cache_dynamic_acls: 1 + default_cmdsn_depth: 16 + default_erl: 0 + demo_mode_discovery: 1 + demo_mode_write_protect: 0 + fabric_prot_type: 0 + generate_node_acls: 1 + login_keys_workaround: 1 + login_timeout: 15 + netif_timeout: 2 + prod_mode_write_protect: 0 + t10_pi: 0 + tpg_enabled_sendtargets: 1 + enable: 'true' + luns: + callmewhateveryoulike0: + alias: 'd6b1e8e70a' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 0 + storage_object: /backstores/block/sda + callmewhateveryoulike1: + alias: 'd6b1e8e70b' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 1 + storage_object: /backstores/block/sdb + callmewhateveryoulike2: + alias: 'd6b1e8e70c' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 2 + storage_object: /backstores/block/sdc + callmewhateveryoulike3: + alias: 'd6b1e8e70d' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 3 + storage_object: /backstores/block/sdd + callmewhateveryoulike4: + alias: 'd6b1e8e70e' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 4 + storage_object: /backstores/block/sde + node_acls: + callmewhateveryoulike0: + attributes: + dataout_timeout: 3 + dataout_timeout_retries: 5 + default_erl: 0 + nopin_response_timeout: 5 + nopin_timeout: 5 + random_datain_pdu_offsets: 0 + random_datain_seq_offsets: 0 + random_r2t_offsets: 0 + chap_mutual_password: itsreallyme + chap_mutual_userid: target + chap_password: letmein + chap_userid: station4 + mapped_luns: + mappy0: + index: 0 + tpg_lun: 0 + write_protect: 0 + mappy1: + index: 1 + tpg_lun: 1 + write_protect: 0 + mappy2: + index: 2 + tpg_lun: 2 + write_protect: 0 + mappy3: + index: 3 + tpg_lun: 3 + write_protect: 0 + mappy4: + index: 4 + tpg_lun: 4 + write_protect: 0 + node_wwn: 'iqn.1994-05.com.redhat:station4' + tcq_depth: 16 + parameters: + AuthMethod: 'CHAP,None' + DataDigest: 'CRC32C,None' + DataPDUInOrder: 'Yes' + DataSequenceInOrder: 'Yes' + DefaultTime2Retain: 20 + DefaultTime2Wait: 2 + ErrorRecoveryLevel: 0 + FirstBurstLength: 65536 + HeaderDigest: 'CRC32C,None' + IFMarkInt: Reject + IFMarker: 'No' + ImmediateData: 'Yes' + InitialR2T: 'Yes' + luxembourg: + fabric: iscsi + tpgs: + attributes: + authentication: 0 + enable: 1 + luns: + callmewhateveryoulike0: + alias: 'd6b1e8e70a' + index: 0 + storage_object: /backstores/block/sda + callmewhateveryoulike1: + alias: 'd6b1e8e70b' + index: 1 + storage_object: /backstores/block/sdb + callmewhateveryoulike2: + alias: 'd6b1e8e70c' + index: 2 + storage_object: /backstores/block/sdc + callmewhateveryoulike3: + alias: 'd6b1e8e70d' + index: 3 + storage_object: /backstores/block/sdd + callmewhateveryoulike4: + alias: 'd6b1e8e70e' + index: 4 + storage_object: /backstores/block/sde + node_acls: + callmewhateveryoulike0: + attributes: + dataout_timeout: 3 + #mapped_luns: {} + parameters: + TargetAlias: LIO Target + portals: + callmewhateveryoulike0: + ip_address: 10.0.2.254 + iser: 0 + tag: 2 + wwn: iqn.1996-04.lx.suse:01:a66aed20e2f3 + diff --git a/pre-commit_semantic-release.sh b/pre-commit_semantic-release.sh new file mode 100755 index 00000000..9d34d74c --- /dev/null +++ b/pre-commit_semantic-release.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +############################################################################### +# (A) Update `FORMULA` with `${nextRelease.version}` +############################################################################### +sed -i -e "s_^\(version:\).*_\1 ${1}_" FORMULA + + +############################################################################### +# (B) Use `m2r` to convert automatically produced `.md` docs to `.rst` +############################################################################### + +# Install `m2r` +sudo -H pip install m2r + +# Copy and then convert the `.md` docs +cp *.md docs/ +cd docs/ +m2r --overwrite *.md + +# Change excess `H1` headings to `H2` in converted `CHANGELOG.rst` +sed -i -e '/^=.*$/s/=/-/g' CHANGELOG.rst +sed -i -e '1,4s/-/=/g' CHANGELOG.rst + +# Use for debugging output, when required +# cat AUTHORS.rst +# cat CHANGELOG.rst + +# Return back to the main directory +cd .. diff --git a/release-rules.js b/release-rules.js new file mode 100644 index 00000000..c63c850d --- /dev/null +++ b/release-rules.js @@ -0,0 +1,18 @@ +// No release is triggered for the types commented out below. +// Commits using these types will be incorporated into the next release. +// +// NOTE: Any changes here must be reflected in `CONTRIBUTING.md`. +module.exports = [ + {breaking: true, release: 'major'}, + // {type: 'build', release: 'patch'}, + // {type: 'chore', release: 'patch'}, + // {type: 'ci', release: 'patch'}, + {type: 'docs', release: 'patch'}, + {type: 'feat', release: 'minor'}, + {type: 'fix', release: 'patch'}, + {type: 'perf', release: 'patch'}, + {type: 'refactor', release: 'patch'}, + {type: 'revert', release: 'patch'}, + {type: 'style', release: 'patch'}, + {type: 'test', release: 'patch'}, +]; diff --git a/release.config.js b/release.config.js new file mode 100644 index 00000000..afa0cb11 --- /dev/null +++ b/release.config.js @@ -0,0 +1,106 @@ +module.exports = { + branch: 'master', + plugins: [ + ['@semantic-release/commit-analyzer', { + preset: 'angular', + releaseRules: './release-rules.js', + }], + '@semantic-release/release-notes-generator', + ['@semantic-release/changelog', { + changelogFile: 'CHANGELOG.md', + changelogTitle: '# Changelog', + }], + ['@semantic-release/exec', { + prepareCmd: 'sh ./pre-commit_semantic-release.sh ${nextRelease.version}', + }], + ['@semantic-release/git', { + assets: ['*.md', 'docs/*.rst', 'FORMULA'], + }], + '@semantic-release/github', + ], + generateNotes: { + preset: 'angular', + writerOpts: { + // Required due to upstream bug preventing all types being displayed. + // Bug: https://github.com/conventional-changelog/conventional-changelog/issues/317 + // Fix: https://github.com/conventional-changelog/conventional-changelog/pull/410 + transform: (commit, context) => { + const issues = [] + + commit.notes.forEach(note => { + note.title = `BREAKING CHANGES` + }) + + // NOTE: Any changes here must be reflected in `CONTRIBUTING.md`. + if (commit.type === `feat`) { + commit.type = `Features` + } else if (commit.type === `fix`) { + commit.type = `Bug Fixes` + } else if (commit.type === `perf`) { + commit.type = `Performance Improvements` + } else if (commit.type === `revert`) { + commit.type = `Reverts` + } else if (commit.type === `docs`) { + commit.type = `Documentation` + } else if (commit.type === `style`) { + commit.type = `Styles` + } else if (commit.type === `refactor`) { + commit.type = `Code Refactoring` + } else if (commit.type === `test`) { + commit.type = `Tests` + } else if (commit.type === `build`) { + commit.type = `Build System` + // } else if (commit.type === `chore`) { + // commit.type = `Maintenance` + } else if (commit.type === `ci`) { + commit.type = `Continuous Integration` + } else { + return + } + + if (commit.scope === `*`) { + commit.scope = `` + } + + if (typeof commit.hash === `string`) { + commit.hash = commit.hash.substring(0, 7) + } + + if (typeof commit.subject === `string`) { + let url = context.repository + ? `${context.host}/${context.owner}/${context.repository}` + : context.repoUrl + if (url) { + url = `${url}/issues/` + // Issue URLs. + commit.subject = commit.subject.replace(/#([0-9]+)/g, (_, issue) => { + issues.push(issue) + return `[#${issue}](${url}${issue})` + }) + } + if (context.host) { + // User URLs. + commit.subject = commit.subject.replace(/\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g, (_, username) => { + if (username.includes('/')) { + return `@${username}` + } + + return `[@${username}](${context.host}/${username})` + }) + } + } + + // remove references that already appear in the subject + commit.references = commit.references.filter(reference => { + if (issues.indexOf(reference.issue) === -1) { + return true + } + + return false + }) + + return commit + }, + }, + }, +}; diff --git a/test/integration/default/README.md b/test/integration/default/README.md new file mode 100644 index 00000000..37cf963c --- /dev/null +++ b/test/integration/default/README.md @@ -0,0 +1,50 @@ +# InSpec Profile: `default` + +This shows the implementation of the `default` InSpec [profile](https://github.com/inspec/inspec/blob/master/docs/profiles.md). + +## Verify a profile + +InSpec ships with built-in features to verify a profile structure. + +```bash +$ inspec check default +Summary +------- +Location: default +Profile: profile +Controls: 4 +Timestamp: 2019-06-24T23:09:01+00:00 +Valid: true + +Errors +------ + +Warnings +-------- +``` + +## Execute a profile + +To run all **supported** controls on a local machine use `inspec exec /path/to/profile`. + +```bash +$ inspec exec default +.. + +Finished in 0.0025 seconds (files took 0.12449 seconds to load) +8 examples, 0 failures +``` + +## Execute a specific control from a profile + +To run one control from the profile use `inspec exec /path/to/profile --controls name`. + +```bash +$ inspec exec default --controls package +. + +Finished in 0.0025 seconds (files took 0.12449 seconds to load) +1 examples, 0 failures +``` + +See an [example control here](https://github.com/inspec/inspec/blob/master/examples/profile/controls/example.rb). diff --git a/test/integration/default/controls/config_spec.rb b/test/integration/default/controls/config_spec.rb new file mode 100644 index 00000000..5701bb22 --- /dev/null +++ b/test/integration/default/controls/config_spec.rb @@ -0,0 +1,4 @@ +control 'iscsi configuration' do + title 'should match desired lines' + +end diff --git a/test/integration/default/controls/packages_spec.rb b/test/integration/default/controls/packages_spec.rb new file mode 100644 index 00000000..4884bbdf --- /dev/null +++ b/test/integration/default/controls/packages_spec.rb @@ -0,0 +1,4 @@ +# Overide by OS +control 'template package' do + title 'should be installed' +end diff --git a/test/integration/default/controls/services_spec.rb b/test/integration/default/controls/services_spec.rb new file mode 100644 index 00000000..6db06ca2 --- /dev/null +++ b/test/integration/default/controls/services_spec.rb @@ -0,0 +1,6 @@ +# Overide by OS +control 'iscsi service' do + impact 0.5 + title 'should be running and enabled' + +end diff --git a/test/integration/default/controls/subcomponent_config_spec.rb b/test/integration/default/controls/subcomponent_config_spec.rb new file mode 100644 index 00000000..fb1535d7 --- /dev/null +++ b/test/integration/default/controls/subcomponent_config_spec.rb @@ -0,0 +1,4 @@ +control 'iscsi subcomponent configuration' do + title 'should match desired lines' + +end diff --git a/test/integration/default/inspec.yml b/test/integration/default/inspec.yml new file mode 100644 index 00000000..ef693713 --- /dev/null +++ b/test/integration/default/inspec.yml @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +name: default +title: iscsi formula +maintainer: SaltStack Formulas +license: Apache-2.0 +summary: Verify that the iscsi formula is setup and configured correctly +supports: + - platform-name: debian + - platform-name: ubuntu + - platform-name: centos + - platform-name: fedora + - platform-name: opensuse + - platform-name: suse + - platform-name: freebsd + - platform-name: amazon diff --git a/test/integration/freebsd_disks.sh b/test/integration/freebsd_disks.sh deleted file mode 100644 index 17967ce3..00000000 --- a/test/integration/freebsd_disks.sh +++ /dev/null @@ -1,9 +0,0 @@ -mkdir -p /tmp/myfile/ -truncate -s 20m /tmp/myfile/disk0.img -truncate -s 20m /tmp/myfile/disk1.img -truncate -s 20m /tmp/myfile/disk2.img -truncate -s 20m /tmp/myfile/disk3.img -mdconfig /tmp/myfile/disk0.img -mdconfig /tmp/myfile/disk1.img -mdconfig /tmp/myfile/disk2.img -mdconfig /tmp/myfile/disk3.img diff --git a/test/salt/pillar/centos6.sls b/test/salt/pillar/centos6.sls new file mode 100644 index 00000000..d769ec9c --- /dev/null +++ b/test/salt/pillar/centos6.sls @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +-- +iscsi: + lookup: + master: iscsi-master + # Just for testing purposes + winner: lookup + added_in_lookup: lookup_value + + tofs: + # The files_switch key serves as a selector for alternative + # directories under the formula files directory. See TOFS pattern + # doc for more info. + # Note: Any value not evaluated by `config.get` will be used literally. + # This can be used to set custom paths, as many levels deep as required. + files_switch: + - any/path/can/be/used/here + - id + - roles + - osfinger + - os + - os_family + # All aspects of path/file resolution are customisable using the options below. + # This is unnecessary in most cases; there are sensible defaults. + # path_prefix: iscsi_alt + # dirs: + # files: files_alt + # default: default_alt + # The entries under `source_files` are prepended to the default source files + # given for the state + # source_files: + # iscsi-config-file-file-managed: + # - 'example_alt.tmpl' + # - 'example_alt.tmpl.jinja' + + # For testing purposes + source_files: + iscsi-config-file-file-managed: + - 'example.tmpl.jinja' + iscsi-subcomponent-config-file-file-managed: + - 'subcomponent-example.tmpl.jinja' + + # Just for testing purposes + winner: pillar + added_in_pillar: pillar_value diff --git a/test/salt/pillar/define_roles.sls b/test/salt/pillar/define_roles.sls new file mode 100644 index 00000000..bfd8b69d --- /dev/null +++ b/test/salt/pillar/define_roles.sls @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +# libtofs.jinja must work with tofs.files_switch looked up list +roles: + - foo + - bar From 433d64784ea7f4e0d846d2532af03b041aeaa98b Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 16:36:27 +0100 Subject: [PATCH 02/42] fix(template): comments not supported by json standard --- iscsi/target/config/files/default/lio.tmpl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/iscsi/target/config/files/default/lio.tmpl b/iscsi/target/config/files/default/lio.tmpl index d0060832..2f10856e 100644 --- a/iscsi/target/config/files/default/lio.tmpl +++ b/iscsi/target/config/files/default/lio.tmpl @@ -1,7 +1,8 @@ -######################################################################## -# File managed by Salt at <{{ source }}>. -# Your changes will be overwritten. -######################################################################## +{#######################################################} +{# File managed by Salt at: #} +{# salt://iscsi/target/config/files/default/lio.tmpl #} +{# Your changes may get overwritten. #} +{#######################################################} {% set arrays = ('fabric_modules', 'storage_objects', 'alua_tpgs', 'targets', 'node_acls', 'luns', 'mapped_luns', 'portals')%} From 8664023cda59afadb3c3c15eab3038160bdbffc0 Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 17:12:13 +0100 Subject: [PATCH 03/42] fix(config): only generate custom config if pillar data supplied --- iscsi/initiator/config/install.sls | 1 + iscsi/isns/config/install.sls | 2 +- iscsi/target/config/install.sls | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/iscsi/initiator/config/install.sls b/iscsi/initiator/config/install.sls index 1db12eda..f24ae400 100644 --- a/iscsi/initiator/config/install.sls +++ b/iscsi/initiator/config/install.sls @@ -12,6 +12,7 @@ include: iscsi-initiator-config-install-file-managed: file.managed: + - onlyif: {{ iscsi.config.data[iscsi.initiator.provider|string] }} - name: {{ iscsi.config.name[iscsi.initiator.provider] }} - source: {{ files_switch([iscsi.initiator.provider ~ '.tmpl'], lookup='iscsi-initiator-config-install-file-managed', diff --git a/iscsi/isns/config/install.sls b/iscsi/isns/config/install.sls index 63fa72de..c1894569 100644 --- a/iscsi/isns/config/install.sls +++ b/iscsi/isns/config/install.sls @@ -12,7 +12,7 @@ include: iscsi-isns-config-install-file-managed-isnsd: file.managed: - - onlyif: {{ iscsi.config.name['isns'] }} + - onlyif: {{ iscsi.config.data[iscsi.isns.provider|string] }} - name: {{ iscsi.config.name['isns'] }} - source: {{ files_switch([iscsi.isns.provider ~ '.tmpl'], lookup='iscsi-isns-config-install-file-managed', diff --git a/iscsi/target/config/install.sls b/iscsi/target/config/install.sls index 706d42ee..ce1837fb 100644 --- a/iscsi/target/config/install.sls +++ b/iscsi/target/config/install.sls @@ -12,6 +12,7 @@ include: iscsi-target-config-install-file-managed: file.managed: + - onlyif: {{ iscsi.config.data[iscsi.target.provider|string] }} - name: {{ iscsi.config.name[iscsi.target.provider] }} - source: {{ files_switch([iscsi.target.provider ~ '.tmpl'], lookup='iscsi-target-config-install-file-managed', From 2575062ccca711c8192f9c5f1b57db5940622e95 Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 19:41:17 +0100 Subject: [PATCH 04/42] fix(jinja): fixes for file management --- iscsi/initiator/config/install.sls | 1 - iscsi/isns/config/install.sls | 3 --- iscsi/osfamilymap.yaml | 2 +- iscsi/target/config/files/default/ctld.tmpl | 4 ++-- iscsi/target/config/files/default/ietd.tmpl | 2 +- iscsi/target/config/files/default/lio.tmpl | 2 +- iscsi/target/config/files/default/tgtd.tmpl | 8 ++++---- iscsi/target/config/install.sls | 1 - pillar.example | 2 ++ 9 files changed, 11 insertions(+), 14 deletions(-) diff --git a/iscsi/initiator/config/install.sls b/iscsi/initiator/config/install.sls index f24ae400..fa486d0c 100644 --- a/iscsi/initiator/config/install.sls +++ b/iscsi/initiator/config/install.sls @@ -29,4 +29,3 @@ iscsi-initiator-config-install-file-managed: - context: data: {{ iscsi.config.data[iscsi.initiator.provider|string]|json }} component: initiator - provider: {{ iscsi.initiator.provider|string }} diff --git a/iscsi/isns/config/install.sls b/iscsi/isns/config/install.sls index c1894569..9029eef2 100644 --- a/iscsi/isns/config/install.sls +++ b/iscsi/isns/config/install.sls @@ -31,7 +31,6 @@ iscsi-isns-config-install-file-managed-isnsd: - context: data: {{ iscsi.config.data[iscsi.isns.provider|string]|json }} component: isns - provider: {{ iscsi.isns.provider|string }} {%- if 'isnsadm' in iscsi.config.name and iscsi.config.name['isnsadm'] %} iscsi-isns-config-install-file-managed-isnsadm: @@ -54,7 +53,6 @@ iscsi-isns-config-install-file-managed-isnsadm: - context: data: {{ iscsi.config.data[iscsi.isns.provider|string]|json }} component: isns - provider: {{ iscsi.isns.provider|string }} {%- endif %} {%- if 'isnsdd' in iscsi.config.name and iscsi.config.name['isnsdd'] %} @@ -79,5 +77,4 @@ iscsi-isns-config-install-file-managed-isnsdd: - context: data: {{ iscsi.config.data[iscsi.isns.provider|string]|json }} component: isns - provider: {{ iscsi.isns.provider|string }} {%- endif %} diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index 2981132d..4dfda17d 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -21,7 +21,7 @@ Debian: - open-iscsi - libiscsi-bin target: - provider: lio + provider: tgtd pkgs: wanted: - tgt diff --git a/iscsi/target/config/files/default/ctld.tmpl b/iscsi/target/config/files/default/ctld.tmpl index cd9ef970..12353e4d 100644 --- a/iscsi/target/config/files/default/ctld.tmpl +++ b/iscsi/target/config/files/default/ctld.tmpl @@ -3,7 +3,7 @@ # Your changes will be overwritten. ######################################################################## -{% if data and component and provider -%} +{% if data and component -%} {%- macro readconf(outdict, spaces=0) -%} {%- for key, value in outdict.items() -%} @@ -22,6 +22,6 @@ {%- endif %} {%- endmacro -%} -{{ readconf(data['ctld'], 0) }} +{{ readconf(data, 0) }} {% endif -%} diff --git a/iscsi/target/config/files/default/ietd.tmpl b/iscsi/target/config/files/default/ietd.tmpl index 1b568a0f..55096d5b 100644 --- a/iscsi/target/config/files/default/ietd.tmpl +++ b/iscsi/target/config/files/default/ietd.tmpl @@ -3,7 +3,7 @@ # Your changes will be overwritten. ######################################################################## -{% if data and component and provider -%} +{% if data and component -%} {%- macro readconf(outdict, spaces=0) -%} {%- for key, value in outdict.items() -%} diff --git a/iscsi/target/config/files/default/lio.tmpl b/iscsi/target/config/files/default/lio.tmpl index 2f10856e..4bdbc9a8 100644 --- a/iscsi/target/config/files/default/lio.tmpl +++ b/iscsi/target/config/files/default/lio.tmpl @@ -6,7 +6,7 @@ {% set arrays = ('fabric_modules', 'storage_objects', 'alua_tpgs', 'targets', 'node_acls', 'luns', 'mapped_luns', 'portals')%} -{%- if data and component and provider -%} +{%- if data and component -%} {%- macro lio(outdict, spaces=0, parent=False) -%} {%- for key, value in outdict.items() -%} diff --git a/iscsi/target/config/files/default/tgtd.tmpl b/iscsi/target/config/files/default/tgtd.tmpl index cb0f3806..e855dd39 100644 --- a/iscsi/target/config/files/default/tgtd.tmpl +++ b/iscsi/target/config/files/default/tgtd.tmpl @@ -3,7 +3,7 @@ # Your changes will be overwritten. ######################################################################## -{% if data and component and provider -%} +{% if data and component -%} {%- macro readconf(outdict, spaces=0) -%} {%- for key, value in outdict.items() -%} @@ -14,15 +14,15 @@ {%- macro tgtd(key, value, spaces=0, last=False) -%} {%- set shift = spaces * ' ' -%} {%- if value is mapping %} -{{shift}}<{{ key }}> +{{shift}}{{ '<' ~ key ~ '>' }} {{ readconf(value, spaces|int+4) }} -{{shift}} +{{shift}}{{ '' }} {%- elif value is string or value is number %} {{shift}}{{ key }} {{ value }} {%- endif %} {%- endmacro -%} -{{ readconf(data['tgtd'], 0) }} +{{ readconf(data, 0) }} {% endif -%} diff --git a/iscsi/target/config/install.sls b/iscsi/target/config/install.sls index ce1837fb..e13827a0 100644 --- a/iscsi/target/config/install.sls +++ b/iscsi/target/config/install.sls @@ -29,4 +29,3 @@ iscsi-target-config-install-file-managed: - context: data: {{ iscsi.config.data[iscsi.target.provider|string]|json }} component: target - provider: {{ iscsi.target.provider|string }} diff --git a/pillar.example b/pillar.example index 1bb269af..59a723ea 100644 --- a/pillar.example +++ b/pillar.example @@ -135,6 +135,8 @@ iscsi: lio: ##https://www.systutorials.com/docs/linux/man/5-saveconfig.json + ##https://bugzilla.redhat.com/show_bug.cgi?id=1643673 + ##https://github.com/open-iscsi/rtslib-fb/issues/5 ##---------------------------------------------------------------------- ## The sample below is complex because its used for verification. ## You should only use the minimal pillar data for your needs. From bf993618bd8780d9e8ccc84f948daa160edef850 Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 19:57:04 +0100 Subject: [PATCH 05/42] fix(ubuntu): update list of default packages --- iscsi/initiator/config/files/default/iscsi.tmpl | 2 +- iscsi/initiator/config/files/default/open-iscsi.tmpl | 2 +- iscsi/oscodename.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iscsi/initiator/config/files/default/iscsi.tmpl b/iscsi/initiator/config/files/default/iscsi.tmpl index 25591207..778ee575 100644 --- a/iscsi/initiator/config/files/default/iscsi.tmpl +++ b/iscsi/initiator/config/files/default/iscsi.tmpl @@ -3,7 +3,7 @@ # Your changes will be overwritten. ######################################################################## -{% if data and component and provider -%} +{% if data and component -%} {%- macro readconf(outdict, spaces=0) -%} {%- for key, value in outdict.items() -%} diff --git a/iscsi/initiator/config/files/default/open-iscsi.tmpl b/iscsi/initiator/config/files/default/open-iscsi.tmpl index 49984c12..67c1829d 100644 --- a/iscsi/initiator/config/files/default/open-iscsi.tmpl +++ b/iscsi/initiator/config/files/default/open-iscsi.tmpl @@ -3,7 +3,7 @@ # Your changes will be overwritten. ######################################################################## -{% if data and component and provider -%} +{% if data and component -%} {%- macro readconf(outdict, spaces=0) -%} {%- for key, value in outdict.items() -%} diff --git a/iscsi/oscodename.yaml b/iscsi/oscodename.yaml index 10896cac..f23c22ca 100644 --- a/iscsi/oscodename.yaml +++ b/iscsi/oscodename.yaml @@ -21,7 +21,7 @@ {%- elif name in ('jessie',) %} wanted: ['tgt-glusterfs', 'tgt-rbd',] {%- else %} - wanted: ['python-configshell-fb', 'python-rtslib-fb', 'targetcli-fb', 'iscsiuio'] + wanted: ['python-configshell-fb', 'tgt', 'tgt-rbd', 'python-rtslib-fb', 'targetcli-fb', 'iscsiuio'] {%- endif %} {% endmacro %} From 36c63a71e456b3b3080575071e1f6951aaff45b7 Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 20:00:39 +0100 Subject: [PATCH 06/42] fix(ubuntu): fix target service name on ubuntu --- iscsi/defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iscsi/defaults.yaml b/iscsi/defaults.yaml index be5b15ca..1c522295 100644 --- a/iscsi/defaults.yaml +++ b/iscsi/defaults.yaml @@ -35,7 +35,7 @@ iscsi: - fcoe - lldpad - fcoe-target - tgtd: tgtd #TGT Project (userspace) RedHat/Debian/Arch/Gentoo + tgtd: tgt #TGT Project (userspace) RedHat/Debian/Arch/Gentoo ctld: ctld #CAM Target Layer/iSCSI target daemon, FreeBSD. ietd: iscsitarget #iSCSI Enterprise Target (IET) project isns: isnsd #Open-iSNS implements iSNS protocol (RFC4171) From c90f86d09b25b88052cb9fd114682fac5ed23b7c Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 21:12:31 +0100 Subject: [PATCH 07/42] docs(readme): update documentation re. targets --- docs/README.rst | 27 +++++++++++++++----- docs/link-transport-storage-protocols.png | Bin 0 -> 288267 bytes iscsi/defaults.yaml | 3 +++ iscsi/osfamilymap.yaml | 4 +-- iscsi/target/config/files/default/tgtd.tmpl | 2 +- pillar.example | 11 ++++++++ 6 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 docs/link-transport-storage-protocols.png diff --git a/docs/README.rst b/docs/README.rst index 56f2e5d2..bdc98fe9 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -14,8 +14,14 @@ iscsi-formula :scale: 100% :target: https://github.com/semantic-release/semantic-release -A SaltStack formula that is empty. It has dummy content to help with a quick -start on a new formula and it serves as a style guide. +Configure iSCSI targets and initiator on GNU/Linux and FreeBSD. + +Technology +---------- +.. image:: docs/link-transport-storage-protocols.png + :target: https://github.com/saltstack-formulas/iscsi-formula + :scale: 25 % + :alt: Link/Transport and Storage protocol infographic .. contents:: **Table of Contents** @@ -59,10 +65,19 @@ starts the associated iscsi services. --------------------- Install and configure the iSCSI Target service. Supported vendor implementations include- -- ``/etc/ctl.conf`` for ``ctld(8)`` on FreeBSD -- ``/etc/target/saveconfig.json` for ``LIOkernel target`` on GNU/Linux -- ``/etc/targets.conf`` for ``tgt(8)`` on GNU/Linux (largely obsolete) -- ``/etc/ietd.conf`` for `ietd(8)`` on GNU/Linux (largely obsolete) +- ``/etc/ctl.conf`` for ``ctld(8)`` on FreeBSD +- ``/etc/target/saveconfig.json` for ``LIO`` on GNU/Linux +- ``/etc/targets.conf`` for ``tgt(8)`` on GNU/Linux +- ``/etc/ietd.conf`` for `ietd(8)`` on GNU/Linux + +The defaults targets are- + +- ctld on FreeBSD +- LIO on CentOS, OpenSUSE, Arch +- tgt on Debian + +The default choice is modifable via the `iscsi.target.provider` pillar value. +New providers can be introduced via pull request. ``iscsi.initiator`` ------------------ diff --git a/docs/link-transport-storage-protocols.png b/docs/link-transport-storage-protocols.png new file mode 100644 index 0000000000000000000000000000000000000000..dfe52e2cc22ee2d7e36af5ac1546befb3a067c7a GIT binary patch literal 288267 zcmeFZ1yq$^*Dp#)BOoFO(y4UErldo<8;MQBCN?DvB1(vWfFLC;DIu{b6%eIUx}>{9 z@~o}z|9$`OJ>UJ#{qDGD+;PVE_82gDo@cGO)|zY1`J3~%p3MslHHDkF6u2lTC^wZ9 zWwlUHFoRK0ZX{u&fji+3ekr4%(9t~6F@S4Xc+a_X5n$n>Fw=J2);422NJw&xX93ual3)pKa~1 zUhnVd;C$_EG{fgivl}?@9%zu&Bl4s4|1nEioYA)Oc{ZfQ1HUCU{ruOGd8klO$u3vL z!NPg;spI`AA*{~!a-K5@mtQ9pez(pJA~!pd=SN}Pj&!BhOtr zgUo*hwBKfV7roPgOOtrbXFhr|@XOB0Ev`aUkcFnpu=ZhfIn$)z=7eu%GDp&zFE~#{ zpMH2=NA38uCE_SP{?I~5hq%hr$=WLJMiW9ro;6o8%?pF-x8QT*&PbAjpk{M z=IasbDyiy`=*afhGxAN;)i(}^q4JulzUFK|Onc2PjJUN5U^0bgg zP~Rl1vZll8^(k>Kr;bWMBg#E>ih|rpqi&30rAg&Uw(+9DQhrN*?{7`}2nLK~|PmhO%nG z)tRbA)l1@zPWGjYacq%Tyb1Z@;nE7hnMuv$r)wXgrq2CWE9NR~I5Z(|`OTyoS4!O2 zL_fXUQhNTVkrYofOJ0kzx7=fy@-&5~@tU(LjJB>h3^KrhaZ zm8I`8B;NUKc^_4UZ8U4v{b#8wIFGBmHA~GSd6^vOrqvlsscuzG%k66C`K!YjLl>9j z&--~o;&m_Vh%-0dTex&WN#1t)W&BD^P}g2kJWC9_V~Y`sHZm5K#d4IPh+-{rUJA$8 zMX&_C8lCp*N$r(LUR^}Q$ILJX7}kVa<(QC>ruOWsp+!DJL!{?pzrV#mqE1+pB0R}y z8#iG1WRzHbcZ1d27k_5s2J1aYOV&V|+3fv*332|eRmaSc*FU+jVv7#_x^LgTJO3TRU@tC?-)scgf(RgCTOr>>2v2DzAyoZ>P(N|Eu+sgE}v z9~3V>qLosla36GXzF~&BcF^of{PpbTN#+BAS4%e{H@t|Rm2-@|#mbYe7{1@snZtx| zbJUygohS-*$G%Un@Abq*?**@EFk3?Q`S+n6ok)$yW&kvm_llM-!rbmYG$;OzT3-9Qiao**~m6yd^6_%TS*dCKhFN%D<1VJTh5# zRE4mFtrgy#3Ft}AH%gbNAkf2$sJz!7kfHic6V>XMYRj0~;5A;I{)tO!Za$teeDU-9 zqDUNRt^5u#${VieUxMYGse(}lK1obritC~_puZYu`lLwdF^!r_e*d$_rx2*iJC0Q1 zCy|BIM(R-#DZ|wM{PyOSiSA+%xN@5NrR@D_aBM17g)hsZl=?+?ihNMC#V|A`^XF_B zz212TpfjQNJy@3*#*4HJGfxhZO=5uvA2xa4hQBN!utEQ}+zXGyictP?j=F)NRjQ#! zKNhnyitBOw>B*dYP>Gt|`Pa2=&4F3mWb~M}fKp6ZOwFPEsy1E*0p(}Abb1;)xkdVC zsm%owMj>0bx1ZllzR2YeRuh-mIVu~H$#{1x{SrsqtH+$s=>vz4m8a_Nwp3 zIir+7Fsy+yX8>|TW@QxD^F(LdH}6i2KR>*?&hH(ryjSiSiC5db$ZtRMJs_5L-bZgR z4E%`gv#hX)3GoD_@O^%&=Nj+FG}T7;4|b_<_58#fleWsCg>_!y)S|F0&$M9`z_T!L z96nW7yjLy?N4G!QO6tFD%?B5fwYK`0j$X=BC}d6bxc~EX3^mHRkZ0aqDQF%<=_c2fnLkMZ^4Jk6oNox8VMoGbEdWh=>F^5!>r z+;tSybf*U$H^;F8WZwO{yNSN0>-+Vg!M>J`Qqv*clJw-vAk~NnF6sv~7gD<$7Qt0c zxTsE%o6!-FocPt_-pYFzk^EAXwBKmgu)2jr=Q|{`1>38%MW|!ow?BowhCC0S#~P2I znaoDlf0k3VAg+e>sK5J={0;9xo|y)x9Wm?1@qNNq-;bqUQ4MP-j%KQnv4x6R!Qgm~le(bX<&Slaz*?Q^2?7;_oFS0|nZoH}G| zW|o-P_ee$B%!;sn846nBty-&|2CF3ubWyMQ^S0{@C=|LH*-2C`heb*-n>gQ)e}kK} zt5O*;7W(5sjQFQFG9%gH-rB}lOli_?9JJjw6F#Bi;?J-O-c`^0{V;BqJN&wnKK z*P0g)Fe~0E)!EEqV4#wj#;lm5ie1x_wGR`+=B%ERFC7Rax!tVZ`IG)66t8ORd5pBJ zs*kV#?Js!jVH8W}gY6$tKbY-XX4S2#nqiY-7^0!ztiL89)ziVflgU$DGxN~pOR7*3 zv5X>yU=x0(S4S*KDof6fpI9?V*t=fQt)7O96HUx4MPEL*I^pqhtiow!)^)}o{&Z$M zFy&8Q3D8(O{^UAbUP$~i;-FG5f>4Y51L{4+5QKwR8ledz8d13G%7OkLcuVz4sRnd?F>sej}_m~0agExw$beF&s zi<-D^Gces37ZlD`GrL2MeTxaj5!=`@uWgJ?kE~Myc>!L>tYG z<^diO)f@Hqo~4)=y!}LdpZdLT>dTra6v0HkVHZx9EMa;shC2j1+U1|vQ*v$Is+5!% zztnKH>nhd+cD)JCc)I{@~j4qsD<}-WC@)fVoD}S zQK;N)(tlOqJxT&upGpAA6!B7u-lsVnly^r*Axm4P2MYmP*Qfs2Uq0E;4}Vcs!VZ%w zuzI2Oj2xxFrb%1ehFUY~28`MlzpYA(k=l|)og|%7jBa8lkwqdW>{%TBEb2VwV*({9 z#67(j{?lC_N`1S6qhTg{%NG)@N0)`gIU(3u6ItG9KBGtIbc)>q&p$PW$-HK1CX87r zbVpEsjva>7xIMnVM3-EiqL!4q)kJNS#48st^+Pah>h?TSvLf>**SxSY)<8S$HxwU9-`_86WToR_4K=u(N*BBU z{Y+-msGM{guHN}Rhfj8vAzrn+yQGXP@^*Nsg?w)sfK=yUm8ZjE0I-m>-w zcn{kyztrav9)5Pf4X4b`XFWa33{$4 z)|KX)1iH`2(YNnQ`Wi2!zTlaMmQC_QB{jhyRV?YDPuKM-%fWr7|CcV4ejt?^7*0FBvGFlvc z(Q2QE+cw7(zSe|qtyAIdRB?@QMVk1?F3%-mR}r0~_fZ>N1H#UWJr*1q-u!6xQI z>YP`nHgyUKkwtviI_>#E0~~0Cjee@Gk~8E^Zw@!ndf3*vy;a98+xScHgbIrV4c@vo zpkFC;f9_M%G!Sqa9~!5uA(*#@+?X6+U?D^M6|Z}G?~!0dN(iEeLMe;9Y>GP`D_iP( z&+C3_uQ41!6DRl4;x=nMyTAWJx7U4T`qqM;*sHC5PbDP(jo$c!Q|%-)=>; zC~D1(1?}E8n9V{J9yT_-mHL7rH&UQgwtcn#IW6tit|^C>r&t8pA`!+J8Sz7uumo>7 z7n53|g^BArJ)3kN-+P53dL_I5)Kr5XMije>UcuVlnhPb&XCL(>@ix$ca>DmgQ6vWW zzmeBqNyQ8f4SSJ4WO1a4OOUCz<1XFIuC8T{&IxPt$nyRC1trjFJV&T7KFnAPC78qc zRbg1LP@d3U7Js@-<~pMjDK~TF(mlk*SxB)Dg|1wVcc=MSzgQ2UOt`;*g}j;0oMaBw zrMqlN+WUzCS@?c)3Bj`-;e94l8N00A1g<*chkOkWl$FVQukGq|1;$l4)s(3!=}N9n zHBnCAJ)qkb35<3{#guK3b~vt@#&)ds#W9Q@c0$q;vcq7bT{|xau0(=;+R#V zL>H+nTeM2TpRI+vNOKC??G6?<(`p1m8>)x!B)6i{vdMZeeB2&0BQS2`@ZigK<*Ho1 z868Uvbt;Try5)38w~qN)gvR0>-{{vf2Zz%982DY?o_Kx1K|b~OkA_AiDn1BhUli+H z^lw%y1kIWMl++BN!=NCPL#fPDO&3BnPef6fzK=z+fuHy?M7!p8==4f*T)YMkrQAm- z8v0#(?4H&~+Ex4`bw224JY9}-T21nj67K|}TI<6c0tPBsXrwrl@#Gsqug2m`b~<>) zv2TBiB(icfD~-Q{Zaw#TtL}Lwt}!#D{aF(q#pqD;+imH^gIOYyhfeGjc6P5HHd-+; zBw4IxRn$P)+h%n+I&ViQ8Vq1OtreIOACb}zwUN_SvHz$$E*ufn{x&{J3*lA_hnk-u*tF77f6Xj;!{$Ov67Jc zSb9Lz$JnmIEz>M}A?TqujubhzN)BY{U*%wuG4ghp9!(d9NLXme$ZN-`<}ReunNa{gG@2mG|Xg z6A32iukop2HjTiITFSI%u{f?HX*~oIXhgU5{DaM{z8AKBqo3wGa$Y2gyi?~)JIdpw zgnGF3Fpz}l4(=)aBm}QVUm9!d$A)=sW=RWyy(?H=ZgcK0Q99LHrLD(w+z7Uu@Xt#J z@^@C0W|+f?70`Fb4np9h2=bTpQp?!dfur$aso16RP=_b}A8y=>!-XA|4RM;Y+SdcpbOz$2&3APcbs;e%Xy3wJ!$x#p#NV~X z?w7A?c%38fUww^Y)(Jri(z1L}I|;e>@N3UaRtt>&VPifE-T60}GeeIvB%4;5@#&v9 zZpRieIJR2l<_DI@QgyZVzbcT+dc?B-h_bKi)uXh+CLV6nuD&7ngp6uqT(xe`alA1q zog)8Iiz#3J5K6PV!69iM^S6vz1FYz_Ti=;qshm9x zTJtAq3D(*}iG2oPq-lCS+f9}pqjwO8whn<+DqbEj#rH>wlaKJ@Xg|H>tXY{<7cLf0 zz*^xOQq(~mHop4idGFWjejMIlA^Mwyx@n}-SPxucX&5bi>~CH#Sh_KqYP-&vV?FYL zC=krenwBp+GCdKFkTa=tzZv-6w3+5bguy$LU(2lxo%EAjM5|ZP;Raz5h9s-9Yd+bQ zcSijS**m?KmE$x#OxLxiBkn2L$-F*te|!#|MF__!8VbN!m05}fUa-ghq<@COFqmJ< z)=CtV`Sk7e1~kav=>{WZ)(PI=E&c$pO3fGsL{skI*Pv)wxli9Mt#1^5|9BcnCHFFE z*%?x8eWBqsn6T5wGRw%{Q<+fBA+PD>zI@4PzU7V5=M_;xHqlTQ+?hu2|2FmTPRuE1 z_w&J58TcW}cRvweV)?VynQ`Zw1*?1(WunrCQ7wypqx$|~D9<7E*=@2qjBoAunGJ+> zwX#triZ~Bf-PIR$%4D&QQ7mM|7?bB{{PcD2FQ`@AdGRe#UDn@Tui(H`)e@Hxz4bBl z9Z!b3(ziaP0FtOKi%8heN6MtRN9i&dK_3Y&9(4yl#<6^->ek@{A3h%R7j^7^88EvU zlH4!Cv8_)_`KsJj$LnRD>5j^sOt{vKOVoL3ueIkPuS$e>j7IM{I*!Pd)1>DY*%ioT zm#eC36?UtY+I=Ayl~wgB`r7eO_yPhY$!0dns1TRRB2yNOKQ`xL zO&Wp*7fn^toYhPsA;4TCx=pYV-Nd0eYD(L&-AzOl z?*-J@VU)0)X+#K;J&LJ(R(?-gVghzd}b0);byhyqa>m+~L?sj0`but-N zr6-}Hq--6z`4MAc1j4V%w^5<`VEh(bwOV_1zpD$f-)nyuB}hRIyGe=9&A7_0^Oaa{ zqcX%ktNtN*tgIUGl&Mh~^9bYXIh=lz{O1z9{pU29ai)fq{&VZ&{LfHS2DkF$SLf`< zNJJtDdyLI|mk_Zz0;hZ1JS90PFHc^4`ys$-I;L5SdH9ht1U_vfb^pU7LB;amQSQj? zgz<*&((LaT$EriQ5e^1+S#j3OnJ;&V8=K5$#Xo9%wy@LqvW^`|Df|qDsY_gJ zHTO8;%#DKt?OvjP$IW&B9gQyt<*QoZ%7V<}^G{g~5x5~&_JT4|-3=MAE=x1M-eqW}1;|8{*z$}h@o1c`D z4wLdsl3ErKgTw|exx6NQ>Zu58BI^nc#vawD4f&XX+cj7Oo2oBw(INc9bQ2C*4be&c zH?-J!?-irBx9a|oi^lPS%t&HA|)Faml^zC zit_aNC`NJYIw|p%Q=VO0>yT&A!y$~D0UBbwpK4bTtg6x<$p|UXTCEV?QO;}D+GM!a z)naEK1WZ-vYJ#_3m30rd{b-l`cGlB(=WXkGf+r57d|(XaJKTckNLdU;eB?tu8&)Eo zGPB8U!vjkLaR}<(Q?%B7hRFxa>6Rv{@{jCLQ5=0B_A2KpHkvT~u%0ZTt(31f?nYzw zB8qZ`M90?CaW=j=vGl}Z`f%Ux`??=Tfu|lk75`%Q=Ry9^jZZi~;zOgf8utaH`0!0r z2HM}u-^ZKgvJ{`}3aRO?#i4Cxw3n!Y6Rbv*ReiIez^dgQ|C3lo>H}(4-ybgH8ie=Niru++{Y# zhN6Q5M)f2;qGmsdX5<|6!Uv2!suc6eh2G0rYBwusuRYI|$9i~>p7c&gz^CIl1a}|z zqgY>Am@=cD*Xl{gj!awU%ZC(i_Rz?6Ol^lhlRgt_s+xV_sBnJ!U7PgY&Y|XY=XQVk zHeny@DC3?yE zJ>5E+R4S?6v^1m#ameB*O-3shsU?5=!*M9r{gAqbY;%p26yjsemWg+4rr*%0USZ&x zS}FI~9ye@ZUSUsm@u!fQ^2&)T9qLWp%6b??B!8sRLHOd^qEY2blQ^H}dej{?_V)%` zUUT2D%L&X6+Y$T;N7%Wp9_zweBfPfhekR_MD3t&9hS{Q^PAc}{XK#4V@tRoZ`-IGl zcleHH;UUU6VdohUu2R&5YKBkrBsFe6T8P)7wUF`hn$EVUzx{SF^Tp_*e1W?tqO1x< zQ9Bt`35_Y-&U7bRYh0^0Q8`bO6z%<|qeb+dDxcf#rH{H*J@)u0%M$dS%6ei)s186~ z&r)8RWZzd>3B4gse7vJ~M-R(U;iKoFx?Fh9o)`9*r|dV3kpn49i!XEg`W{S^54jn% zc~%o8=A4iG?tYq}hKLSQ1uGPj=SV{t3O`LX&o*cbQuv>>K5^w0es@72tvQ0Q9QD;0 zEzq3Kd#)83m?>3jS%hwDYq8O2KKfkKoou`*`Pndoot+J0v@mMlfj?b*-#&mdz>|to z`MJP}4gDfD59S^{Gf@fmkBuHB;To4mQ|=!uvp#lrH)~hHU4^|>yYBp~54m--dccW- z(&gr(oq?EuX0A)){N5sgw!#oC;m5z4yWnAR8);hyUs(`+qB0%32Xa(B^+X~4tl`1( z?|SV=(@B}bVQ4?`j-;|sZ>XZ$?crlkdpsk{<%bLLhO=%3`(+wqB@w%z8!0LU!v`i*yQ#?z!metE(y-DYQUM;xY#~E^+ffuqc6QNotd$ik8Uzn~lzI5NOqK zkMfOU-&912NH(Wszqe*BpWLJ6W~{{5qZRq*%huSw&!5$E>-QTnGTy(@&w)Ly_QO=( zw26WIz;cZCIQT9`w5XbGnAm9Yl^QxU?DkJ`_elyze5w$~7;Ci?)33M*wpOx(xw`PY z;tBy;!epVtd+2*k@tu-ruadnX60?+~r9!rgpxE=6qo zkZs>uPGjd=X(6J;Y6L3U>ZYCPV)n_U#fG(yFG9=o(Yq0~LZSr0OgTh`EE(26M`M@O z?KemsC=AIxiG~|yZuDLD+|=^eIpwv!X%Y4^nuz_b`(5-Ocx=|?)YYbC?P+-8dFx3*e*t$A7b6$ z9Y#^d-f1Aj`r&a8yxBR+_63CF={>2$VEwMtOwpS)fgT12gqdrER6b#(iE>Fk{X2-& zPm!ya^07xoZL-(`)36&W`6P!Uw^EwSi%*-=dj0#q@_LdqGZQ1;+(@ij)YQ=CCjNOH z2o1X4obinGn@8K3zD60t;L23`ev29fg|OWoyeeU!ssgcsIdfWA!z`hkKF+S-RSFao zF-ad+3oAz`oYoR*YwsdXzthk{Pit>2POmSZ%B|`u1GTePeBut(exjyh^~BLi#F}1G z0$0oj0t7fi;TE($&Q2~K5Fc^+-*O?~Z{%$*dfMMj;Ev+-2C5pgGB9^2Ek7qeCpU+j zkG&T!y#y|;n7g$NL`zowA0oh&IK3Sl?h4`J^7i)T^ycG)x!ZE_h=_=Aar1KV@^XL{ z93H+da0?#}7Y_!ch(9u9p&nN5_O5Vym6VWaBD6laKR7O0~&Dga&rrFaPxBTig5kCKX|ID`p@1j9{H+g~w}Q%fL0#Yse;?G<$s+S)t2{_cTn{r8sER{w10>gn$EyN$IK7t{&r3?||MB=h`R zd*qun|LpN^>p?#GpC$r^`;S}yrLW&}`K?!oEX>LiIjEAXI6ZQ{5Nnu~y*1?bpO(D* zLN@%umK+ws)_ffNRu)zq7D7T+9Q=I3mOO%%JUl`|Jbx3V#(U{2!n$Q98d$NlYiv||5oDToTp$^z+nzZ*lXkzV-^zm&GH<@$Xo z#`T{y{+}di+rhkD{-4SDSJQt=k#dK7!`vO!-PJ7}pjPnzT+V;g_@9!rKt%L_yZb8r zFBbKGX(#r_s1-q9n7i-a#@B|r{ki%Rww&yLtBRKPcc_3^SRu72PVZsi1-1UYfk2Oc z9$DF0xY$BLO#X*M{&nB}KQMLyC=_Wp791k{+?K#X1uQs(Ek&$3toXTkgaidGZA5GY z{$08U%m(gl;SQCu1uh8o893nYeWtzt$3`Fgdvk9)DAEAAc|nNIN=Td4_H=c1vWL3=yR!Z=p8uP6 zf9wAbL;YWM|9i7Py=7pozQEn>;OgEk|LO4mLBl^1RP3#QYr+1rssFvnAGQ1qvA~@F z+yh7mfR*c?$odacL3-2w#?L=Y@PFe7K=gkP@?X;Lf2-@i)%9P}z<;Une_PjotLwj{ zf&WtD|F*9G&(wwcui6~y0+K&(PJC3WOp$7^IDHHMsRY{9+7u>{xE2+w1 zE#TZDAr*9ML@c48(4r{GO6mAaY)*TpksMu0?)I(lLC=rSC{;QjzQde?^Wratt$Vzq;XJ=Q} zMsfP}acxV$a*8c)RI;hpI8(Aos1yG~U48vb&mV@p0@vrW*J9L!T9}XT&#wH@!kI}6 z!KbE+{jQ^V}HZ#9uqD9s6Km^);GYz~Ob-#<%hSN0_?#qeoo_eUh1mVw)dY!6Yf}-8(t<>l57(lhb^4 z71medJUQdD5Hm3`k(!#iyu3U(I5^)LK}aKU0R|QR`JCZ?o?>=${Ls1B?;U!WxiN}b z$Mh)4fA=$1O1tmD@)0A-$#%mIgiOEOZ2Fqbri@{0caa2%Q(Lm_}$fBevQubu4r zus{+@3Txc^YMUA0HI4A}Yseh0vSmOK`qp^#!}$7QMRK&vbaDg}3WZkL^guRBs+1o+ ziZf{vBHB6lJu>&}n_qb5zQ)q*n24k0UCoI-c4m=&jz#VBc&5>JT6PwjZr=6u z>U?Jw;mHjPbw7a30^LJgVZ2M-K1I|+g9I@#NGM=mpwfyS&42RcFdrewIQ2j_q z3a;Nh+bAzLZ;`k>dt>#ITr0Wr+0A=3PUG>0$iaD*haaA7R`r36D_9$?FmRgnavxF^ zLQY(Kyt1TZv&;O7_wQp`ptwxcxt&aVn|pY8&=TFPLtiJmb0<&hy)1p)<&Te}X6Ha! z(K@QIckiwKXQ|MxBoVSc;RSqYMMFb!7kYFZ-W0fi{S+2gQ|t(#x;fTi>^GTbeOJNmo}uwX5;1avrbDj-ObDN&q;gcbaJw zp(KdC%VW9tEw0Ia>a_}Udem#J^3eu;zc)g#8KCS}5=kdBemf^E*O$HNqI^5gTIbi- z*DI_#Y-r*}H+t|7%}h;Ab#_LC zUq!a#p@zK-BmAYsZ?Y`zih6C1Raiyj+pocJFvG+p->&uXevOPme!4n45wVt=BT`4N zK##|&nwkTzA82FK;gPZ%e0Ut<(Ipo}I_-{o>J!*Z2i-hy8W0fWQ`}>+X@`*`FUWx1hi7O0oiZU`X+S=NVj*e1N zQig_x_V)I)M5~8rDlK{>ObMs?_AWqJSs8NAaui~>E$$bs*m}T*4vt36y)T_?1HG7a z^nqKM*k%&BU+i_v0!M3aZ%;`$0@{Lok7siHQKE$9?BSie76o7U8uL!W?&5SWJ){2+ z)2zHUJKOSfcRt;$D$QevUsUxwm8h3L@TAsfH$@j;$sGF_Dm^eZHoiwe@nzl>{9$2X z;rsiepWJfbmoCDRsO&sZ{XQ?Ry=F@(fjKW{W_otzL~zLFIvzNCQBjc&tFE4&pwrlg z`1ttkOyRXrM49a~?`mhijUT0xwJ8*)S-Jy8E`nza4D~EtJKldB&=o2DaHpx@Z&(z@N`k{-c*6EUw2aTOU;^|%zp__ArS?x z4y#{EQW3xI5t{YC`n6(e%jfYX**x%9iD6Y`PE%9g!A{Hdl~t^Gjb(e}W-TilTg&ms z@^tv1Qc68W8rj?}1! zR@Tn;xH5x^FB?ZQo};Gajr)-vEMT1;%iZ<6U+{)fR*olOeOI4HO+0$WZ8NtzyStJ& z-bw}q+Ux;C9`zJIUl__)JwGZBO2-j^=<4bs;}@7(5HP&shM@xW>(7LNa};eoM@=16 z-cy;^7q-&h!1%tvP^_Cz^jpq!&%oEn`gWZ2dkmVM9EqPyC9J*KY=kslZox9$J0FZo z@vm^z1EI(j!9Y|7Mpm|F*xA|XQaAq$nAE$m-^j?#|6bz~54>%fWx=4?pbXSy<3+E$Aq)JjYu63Dyev5&#<-&ah zv#jP`v3~hg4=ixVz8!=*cC$twU?IrRUeQ7Q)-dR3MBm#ecI)d)Dp8l_U(03rUZx6p$L8OrU@+Q z-1xX*W3=2H+yVQr0}*&3`||}$#u$L?FX4DH?=-xKa!+iliVvM!T(0H`gWgb)%wU!E z76i6j7f4>(>&K0&<-h{mbCIYU1)SiAF$`+{An58Kb8=#W6S10IT;7Zr5}|i#*s4Ls z@R~`N4V&v?z0!5~HLU6A$FG&lAh3ne_k?=u1D84zTTa`#5aaXvAjjb{4+J>o$>%x= zyu*3&7+Cgln)e%`_{_}N1nnxq6hZ3Wrm8M}q*Fj+*x%nr*`9N&oVJpoQ5fdRY+yv9vDH19k0`*Vg~-?qUPXPLFljR3GYh&r%Dh-)Dcen}O6));N#5I2YHSz1sn>V(P4)n~ z+x`Gwx;v6(B>uJS-7=|r$gu#DmOzlin6e?Y(YX7iEE`4+K;D6iuxQ1|$jHoZ>tpxN zwimZeZ$(J#0oOyOFrKCC?Cjl*UV=~7m34JTXs&i6B)ShW1C9-aLZUM+YG^LzuuRia z0pbXqj~3p{uje7MIy(s=t(q%@FEsyV9Fp-hAeu zUgLj#c>==&OfE2)>ZXF*gQxt0hk7N>H(%UlK}hURdGvwI_(V4&Xg-|G zjJ)2v+0+0ACB+LH0(4tz}E(6OxAic&VNH7H@r|WbRb88%cO4il?*|g zmc-TRLYYMFWR3I2a@E>)0`T?I@xH)i6LUR1FW@_eHp~&xH}45f0pu@BG{4$UJ~{mi z0$L1}sGjaf7y;yd({lYvhKF1On9W{7=0$FGb#+C>9zaq*061w!>manxr+M^#AHExO z(l9Gh+_pCGva-b+MCe$FvqF!a)4c^G^dLonAO<%6jGL1m=RYwRfuC1LTTGU8~)*=*bX2-|ylg442OI-~YapY`#79 z7-TGdDy`voWMBmqTAhh(`eq(I0=?ceph_816YZ(D0noV9c+d-sEnht|!#>2(@CSgx zTKA>SZksZaRb@FjIVg1LgGDRIjSwmL7MXmd7Iy)h>{P`H1-!l7Z6!1{Ro($xwHb0? z;?i0I*%A&HnXO*H$Of;UKkK z-`L=)UnJJQmvFw_z$LCb+w2Qq1mu<=LKGDjS8WvD`Td#p_upUqE;QvTb8L6^8>}lT zE8VXyey!cLR8sm5JW#{bbZfJyhOmNFTJe|!^<*|E2to=x!L~1h>V+)Q7I1d+Uu4MRagGDq*5;}m8+pEnTaCS)jQu-r|*W$I>?B$Qz=A%+r zK-b6pZw#@NLM~}lLbE?gja$w}%8!*Yykksi96$gr)=PH<|1E&%>lpCM!K%+*Fo4%< z39ARc0JQF_OI{9-Ppp>K&UD_GJG*f5%FJjQ7#mwPs?sOVfyGu=R_;$Yma7K&59TV7 znZo0W36}>HXwqM#Qi1XrS-~N_q`Lv68Pd;PC@xI_kf*FJerL%PgUyK9Dl8w8X*HDlB+i{yFoB(JIDDvgsgq6u*(W?)vQok98bHes7pQgSO@aoMSnBg3)7Mx_ z>%CrI$!!-oh161bmr@XA$GREUgUQW{XQzq! zX1|(t0$?CXd1i6Am<1rxl*J9?k!4dEIaT8ahf`SZbvo+}baY;er+*9VszuHr5NT{> zH%7~Ycq3zD(|hpDsKCPas~SL&FrdIziuk>(N;;1i5zK&IwJ>#a^#K8D`fD+3XdWsNamxr!1af<4>UG zK>Q#fBRjcRXgQzWF>%}m$y+OdD7&}hDOjfnDD4sBb6_|RK{+?Q52oqMY18+H2*eLH zMpMBSY>V>Qcz90qsKr!P^0v(GL>t-AOO*9_1?8U4X9=%>kegQJ0MhNk<2Dfdrc~J& zA1s0L9VGabAg|iIiWTWRF#px5Z^9Bqv=0Dz7CxkkN5HhbR0coTpnd^#7_Fq?G`gpV@qwCQm{ zN&?YVGY$?8B;0`l>9siHk{L4NXduj-m{i%o{e-mgGAEe_;(%I7LwEDy^ZW|D$mI48 z9D>UE+wSlMWSTHcj$h5B<{#Na)Zd$3$xTpu0s^K@U&i#Mjj3wCp|^L8))X z17l9nHs{M$t^bDd;HJs?#H1N?VUwY!`6(zvdCRX4GOyE~tiL~*@=)i>fq`gtF8HEn z#>PlLXIky83L_XHMoVD!;^ zy>*+FUCVu$*JD<(mmt4&6|h><(XYCp{NUu&knX8y&>;dfCoQv?(`_ZNgNw!Q2_UKm6-4xR{+OEb|Fx8CtMHn) z(iYt{{j%G4^PnnaXyEJ_AL_FJj#&-pv z+8SKakFs$vkL7^lP$}Ot{iWK__(xTt+Wd^}A>{wAK|ujAgz(c+w$u=a8Xz=3sfyU~ zUl9#DpPU8RXEU;LPBwA>F4Vjivq|mX$JX%H;@C$8n2%pH?`B+alge=|ku;!F(xMpT zqZM0R+vC%E9FDK0Rjig^+yhos#0P|zI@OipkcEf77c=7cm3+g`H2OJpFRrk zOV6^K00B))vw0be>*Y7D0o)SKNj06V7x%UsR@qP$dL)aJbs&MEJn*PE#jONE9#=%D znkKvlMEimX^@<&+j2e&IiYyGNOM05(jjR3|TyblG4oJM)KomEefU?abZV5e*=lBI! zJEUyWS8S-KIrnwhI1b<=8rjO~va_@4r>#qd3}#TlqC`}Fl$*mQYdm6zzIbv?EG%;W zUKk1|_NSvx`-1f22+0eJv%@uHEtj@o4oU?;i5`~o^^7U8q%WEr3~Q^4Y@|p(&wG>% z5pq>!UUh&jq%{tH7eJo_NMSnv4rH}okK$B1_($&^u<4b!s&+lZ=27(kp?i(sNt0xu z%OCD?ZK!~z&|}3RURzUh9-mw`X8>7RD43^9TMm+dSo>yzef~?)uTSI-4&5TC z`8}OqxlpD~Px}B+?roof4$oLmSiKba>RDfEQa@O~4Diuyx{W9p9jS)kCV^LIuAnjv z%Etqxpd_uNJz(nAP{Wpc(>K?TzVX!Xgx>^6&bZmO~t{# zfYJ|ZMjBl}qTESjamztG(<4FHOt(mRQIS!FrJVXiWjaVhZ!@d7mc1`3f?Qu5AlV3g z^8imk=pjiTJg!Dp5Z%&%nf^sqe~UYwxjdOgnt4R)Jb)A=TV;NImV7V`I1l(n<7V74 zvZC$YsGWJT0V2f=VT%`xZ|hwI^?E1%D5y*Uw2-`zwX7J6yu)S^*8q54hFCG;ygx@O zV{GdvXQjZZ48Q~nV7fXwl%Ft_pAwmlf~sC`lq3&H^yI`!P+;FUQ$ow|`S}7l{b!Oh z{>ZrDUo{lNqLy|F=$`3%W6UQ}qW}lBZJo zE$bbU;Jf}ufHx6^cD@#H{9*lrk@yARfmZ583(Fb-OW50kU>2|Vs&zH^dssfc1 zVth;_mo+6fJ17A_lo0#^`~(LZn<%)#!^5j}8vlu#4d^7dY5Yf-ij0AKQReWYZBVzl z2M9JG?1jkyiQIgUzGF>V-gG~5E!TAQ9!fW(WCvt<&3h*`i1 z0ahOxt5n-zpsNpDLd+J{Y?9VfmIlnMNj>4cb^!v-6^nb8nrNXT*1Kj|6j*&_RSzIT z8zpmK=dbLqcdoCv{9e_mrGuTtI(GT90CW;G*MK!pOu5AbAODQvLdZ8lJ`g}wvq06? zJ(4#7JzO1#BIC*scFT4Q09-O*j+I&Y$!9DYvF*AA#Q4{p6XZD-pzb_OHwZ;HKLFan zvHZ;McsUF5(QC_S<%1(K?GAC?{dsZ@1FW5kU>)F;{HUq7JV4YDpMQEA@kFTMk^5I1 zmNeJ)JNk=fPF~RU;rG+F1Jq~8f)P;h^%Fx>ul(OK+-6bRWX&+s)N})hIV3C$d1NHi z;x$oi4@AO(>TcWEP9VVmSsPRLdA6Lr{e!mnw3iSRa6b8j`rWt2V;`~JS`%%oy5p1- zg-W&}gfW-tXs_x0JfUfLf+KCNF|5fm*5i6>t9-r3wj-7XnTI2fq525OqiH0s0KFXm zc*KSxX>)L%1l+>oSWz8hG-V;(2L}f`h>fz@%WZFvs`SqIfD{`^bm=#(zm&W>1u?%j zL%d-q3mnApk#d=BLQ2oQG{$ZDrkQ#KZZtU7l{NkBloaoK#0fO<1@PP^LoR2RSKye+ z9uSb)1!|sZgXu>A|*Dabr>lP|*yV4TEW+ihf#(l9;=l9*&p({+kzkZNCV+DaPN zxmpy&^)y4?`W*sa+(Vu{nwf*NJVQ4tf>|~75M)r`>=6nKZ-d`U44eyWl(z%|&is5u zu}a8QAgfEwYZPNiM@y^HXYVJVM(Qd*r-^uwau_XAP-tns28U&|qPjpyK}(7HAoocJ z@%>Kq!2N_4!03*T`XO#^BDrg1{ASHp`jU-PZ@s9^BPBQ#goU@Pm(lz|zVJ zNMH~vZo1NntcgWsWiRD(5M^Yfq@;K5c+yC&_ekBu23ZpTwz;%eF#VPuJi4ca#l?;e z4$904($4|7gF}wc&``B=x!z34SrPl?ykxV~436QNEr3kS3E#o9TMY7ti;bI^Ro_L* z%gV}1OGiA#B_&&`tNF$AK1qN&eX=>A!Qw99aKppH!Ee8d@k`cO3E&Y2D}8)rpi$Kv<-jcosV7vT30BDwT9 zC|QK6sr>H|2uPK%(-?Q|_fNc_1O=(LEh*UMSpGjJ(W^|Xd9tj30tooMh>ghl=i&bU zi3j!nKfiSFDv}XsaW&($tm1>*#dvJGa7Jk&rRT3JFFB;}ja?t8Mop#}NT>b}n!Ylu z&F1+!Xwl+MDGtHiHF%Iha4lXOT3m}4mk^wy!HZLzQrz8LO0govi@$k(|Lgsh>;AC! z?7h2Z=FFMdG;Kl2Ny$RtPQYllQyv>I2=4w8$pp7TQH)ly2^W)=@4R;zje{ii=tLZ0 zX%knl>u5*ylqqRbFqy^kf?10Np5B9q6^X&*MKHn$Ek)o2$A2sM^I5hSri=yvkUKEa z$rGxqybXg8s=zs;@S(9c8CgkCY+$(Bt2qwiERzx^i)DHncVM}VDqV5EjUV@+DJH$W zQU_E!^!fRf>t* zg^foAlBsec03eLz~S7@^|ILzx5CJaGw|yoHW-X0>L`fCSs6-B=}WVu_C~7-(_Q zX|gKb_P?Vb!(!u#x&;83(?U;P2wPWQqF{Y{^bUUwVwAKc(&|%6YEidAZn7fjCK#$) zhs}%R#cT{%Rxu!5MO{S_#YY;ZUEMG<(u)eDnnEue4yDx>=fq<=zW)Vsa@7Bo!tnfs zmr5OE0A#ar8kwq;q0LOkP4G9ut+4QsiAXEQmsdCCQ!;Cudwji<#UK3t`p*S)LAG=+ zA02YvX_1snUA26&d;5RGwR6P(_f@VwK%iIDuMQ7iwgdSmz>!w}}$e4@iV z!x-Kk_WB7Xf$@MzRyLyNYuIIL2>-GkGJiUJT}4?`(X--TW$E|75y|NlY;?2%fc(uJ zPzgtJy?oEEine@;mq33JFqfy*O|omMy6Qa%x3X>Zmjne+m>FWSATzBs2!jv{^-Re3-(08OHonWCMQphl;*Vi0ILSP|J-d-i&@>*75FrVysMy<#5JnVpkOd zXH4;|dTqo=ne`E0=cjPdjb+0jiD1uEUxA}}m_&^cz9#mQ(a&BlTu~)u+d0wy%nC*_ zJXJuTG2n)uE*c+-ZNF3xv=!?AHjv?LV1i+<3JNDaEZD@z)sidX0AZeF49FHBPJ!mp zWi2#ck6I@-G^?6NvAk!B12F{R3R|ZFvQ(j0A6pc-s7@ zY^46IeYDLiaV(oqP1oE9sKQS5zNp_`(d+OPdI-E7jvJbrE(ym60asu@pIepTFc2YK zF()G#UoPP|Z>5w&Ba;Z(3@KqJxZ6~wK&LnyaU2_Y^3%c_XYvP2wFk;Z%6jaWZU47! zJb;nrvU9+(zd?vHowIR<$r5Nqs4fDrQz2M%PO{-l5C~QZOGKlS<+4P7bfW@T+2tH| zxuWWtX-GV?*W(eCUh>cT{gq~+H7Q9+*<2BidT~~XPo_27CEtiZeqUGmh#i2iSjn9~xw5Co$fFj~?UGiDZSFr}xB;~E>8WHmK9xwzRc z_YltP^av=#sLm2Hla(;x(6w@M(s8JW*t-rc5I8K&e^bZu{wb?`SRs$KkIbNU?5Dna zDyXrrkrY2;N}>yO*;d403J0pOgddFcmQ?_0BK!nO*b!KxWaH=mH{tLBh7Cfz==+OE-x)~s7u%?&V8>$55~gvg15dh> zaN-=~^)r-;D|$Qz1mceySO4?w-#VzK7g_HVc`bKmLciEG+zSTd) z4QFaqIVL7BS;6e#v@eZHh05cz*Eu@6sEU98GuxyRuE3X`bi@R+AL}_)9I;{hz;^QW(n^2(>PA=`p_8lqCZj8+h0c2X2>>F{9jwd%8q46Z;v-?Q zmCwQ)#IR5Vrqbi9C^z?P{jq|R+Cr35U%Dvs04 z+K7WhXewf)sujd&=;F;k>P!~p0;a>}hzk9p4rE6JR-Or%`SW@r5TqvT$5u=m0nUo0 z!vjvt%yh917+BnB<>lVz6lWOktSC3)qW%h>kpR-^uD(nUStc<~jXNfqr%8_3^O7)X# zQQ-QeZli4wp!p3|Hr^JVwFo0!&8|9@9MCh-LA}1w$t!oOROt5g^3Bty5M(;+D8k4I zEVIKgfT+o#_!{se5aIA}NEaDD$ll*4q0XZv9>NSP@=Nl?E*H)_pxYZQdgj|>XKu@b zfTKXHCkM-Ij^?-^Ams=W;HS(N05#4Gh$A^od3mid+WuT;B%!ZrFJqmuIg7~F-OsK0 zDzb-{Jcc%$_P%K!S&-pP*p%o%E<-rP6c_D#VXz*)PVA#mL5Yvxk#1%+FSq;SI5oP* z_0$f%{fOO2bz+8KmEz1U#5Pj2j{)soX?uWomY;#~K>N$S|H#F4zS8-jj`m*`q=R%O zvQ?Nw>T^w_x}~B`p%$Z&N`(p|rF?z7o1^}hSyM!%Q&w9TH~rDi8_)x)8%^%tjdTOOoqFtun85VtB?tpg+d$U z1*;Su_Cd_M0=8m~P71=R7u9BuhVZyjpE0|d*p)Q z_nD{X|7K#FoU7h<)_(^cuPN$&FZ#hJ3DaK+D#or08n4)kG}_l^i}|!$$FfhB2B#AI z=;Hdk8^i$GHJkKmyDlbTPgnT}CnCmwc~}EQJWd_U;fEX@ozA_2dxpe%yO5P|NQ$ZyzR)@CUYngC}X>6e@1L8|{?& z$zZATpFMvQswPs4NvxQuJ)M{VA2RpcU)o%MeQ_0UK1T~J8hnRPd&BX$Iqn8oD&La@ zEEZS!cPu_0Mn8t`c<^JbHf{PGmM^|EEVaH}|MT#C?Ja`hcGhw8SgA_3oOZZ&yz}ST zK*-X=UnlDF+Hv!7@vHFhO~;&MX`b*RMeu5E&~>l(>)W%S^yj)hV zw%%5I29asc75%vX-utpCuri&+U}AhdDISyx?#p?|^cYk5LL(|jWD=`n^x`@B*fikx z!TG!8TDXe#yET$v{&=R(FLEbM>!FqkzZqUvNiY)yH>h3S4W7LmcM9jM`aE^N-{7Ys z3MK+|T+PY-*)%#;b5OrE@f<8_xBS}l)TRG?c{HO?4gYa)v?-35QMJj<$gkUW=>20Q zX<8!nEJ5(EypETJ9ci;>V~FFuy*t-O^ev%)i)U%qu8q{W(wxhux?e0d$Zp@on>g-& zd8XNmB>suXKC6_tVWcbG>f4jBxX2>yt*k?=6!}yPvXPc1uAffdmbo;{VCE&=R)^VG zzTU7TOQkTP)F^tIS-XmFnK@#9`9tK?Wl5k5`^LwA`ezD{t?~SwM2JEpH zK7)=pt$)z(u*21QVTj;#>IK1!Q7QrK;aeu3R2hr(w8xy{R>y(jmKQKi9971MT?m#e zW1;I){CEnlMc=q0IJ2ii3=vyY^MBT4e|^+1Th&(@NnF8O!Se`wkRgJ6;vy<>ilW?( zqTu%#fdR8)1CJ~#${c8QGTyfsW4FlE%4m5@1`{iszmmI)V483isChSgWh%%9ek$nD zl;tI8rw@)PsjSF79E0=hUEi5LXn7WPkCdIBx83yW zjQ_zCSv)X%&(oFk;Pw6xo2SI%(YDYb>rXqm)w6l7`v9T(CF&)XMD<$=489VvAEvmG zSWyi0%s|ZD-=z$JZ&q1DG=Kd0@JcS?6#m+p<@;jlTu8ond1gghXLD}T@lph z;O!I|$$iZj2i753XcS{&Bcr!ash9kiAD5acIcUXXMY$3whG4+JZv(Vmxkuu!MB=No z=O5W{6OC(V%l8VdJHg$2Ma>kBL1#?prK??0JFZG9{Qj`eC=~gtPUyoW7f9J+`L7JEy!gEWvt@Ex94IxWI>>fTxqpY52J3UDH z>&sV{Gj;qx`Hlv}on05$v7>yBmdOjsszvPOmsl|%wElJ`&!>qo@BTtg$LHG-UY0Qt zkD2N9TRvU#dEGrNLxqA59(61Sx-(A@UQ?9gbx9ap&i*8YMcHN80M~VcZKWyNptDgs zycBLj_!z=Nc1@vBTA@^7R`LoKS|{*14kD7~hmBDHh0JK>$S5O@@aw-Q*hnG=%@KxN zPO5M<`@XRfTPr2idNB(@Z+T2OEfgG0q54WpF$3Pnu%n1sfOSdit3=|@cG1}@$&t^O zSaLwl$+ak`G)t;A2rn%TYXAi1)I1NF#$R!m$PS67+=<*Fc^aXgyGVD+e=)+I>C6vNCDK=@x)ub z;waB7tnJirp!0eQi-%-AFZ+WX=6%J&w*phBX3ZQla<>wl1O^>QiIi z%3(Z;7*;=!_1y5s-xd?c(fK8nOO^WqZ9gHh0VuB!A6PhI@k;Xyk|ALq(=wc*)le|* z5Ji5?oQOHxx_e+CCV(}Ki0-u%Ec|lKvP#nsig=D2+#EEZbpIMd;Ip zYgYW5f6O<7z8?5(-;Y#<|GDp!sh2RGw-RV8s5m~5VRM-yfjwruQ-d9ptkzfk^s86d zo!s1;2IP@S@@jrY;+={nJC;LMXp9&km}1o6ey!B z4vpPl1A~;KwBXYbcTr3N90?Hogp$JZ6N#;Z5XiI-&6e13Soof~-Led;F9Op1?twwv zfSO4WH*561JudvmcJB2fMd$(GTz$k1oTHbLHnQJYB|TC4e{xAC<(kq#cRZbK-N#}| zd}el=4n~<>xlCd>^M2$-HPM}00HjKzh7|()91>RN+bFNUj{!JU{HR=3ulh z4Ubrql-&YvEhsHAFcIwS6B?4pQeG+ruvj^9%qqq@R%;cuLc@Cmk#IS0;pAW>@W}LK zY{q1nsUOpc6PSh-HjL9SZOvZ9c3Q;*7KJ%jw7q9LAy<7JorXz2lzIutvzer!(!v)OQ!D)l13SuL7-S9j%0b>FycZLU9% zF0$GX74I?ub(-W}ve8%k-3+ z3Pk~|=~knk1c5Js=!m(vDi~U*j_wQ(%-sz1rIpNS(2aW-$l!-mPt~R`M;l96#On08 zxPV4OG$$O)9AG@SY;0iO~&xK}MMN zR07i+M$JaX?{$%FzI8y}=m8~V3;kHNwQ>+=^+zaxdbfU$yA{?$7}=llabqQEX$H39 z^0x(F$S(ur>P|)=AETC-7o|leOx3I;HFW!$`;rEB_U6#n3-A?jib!V2+Yz#%C~c5o@fCLu;cF8D!dExAscfZr3nwrm;BV5_1`WqQp1 zw^Ay;9LvTIU)cOCOCAF#ity`dL)QGRD;!xuL-Jvb>Oc&al3B| z8%+ozJP96Xc%0V3)|6x=`5rifNMr^FMj+-AlO$m)tTaBLSJWhGNWxDACx%NNMNUy& z`QBx&La;0a2fFN(ozClJl3jH|ysdLf1LM^bME8#nVyDZ-K5-KC_Wb)|I>$9tyxP_^mzibTxgOW^^^z6^(E3BMshSd zO%(y>s?R9ta+6%l)we&P$}!#Rkp{N4#Y}MLs1-crlUH+85bW0_{l8!x$&4a#eium~ zM3#RNKRE0MM*{}1r7=YU(Q#!we&4c8{ymE$7Y22=X9%Gk3_mENrjpQmySbFKwzr5S zXQZXfJ$Va@Xe5lVkHeXupn@}+4B5p9%YPeaSte}WK_>$hPE+Bi56d4n2)aIY{nAK= z?w08qDcKn}^&KliUuCE61RGJ$sLHjy|9~au*z#;>rL&M25;C=a-^u0sxh?;Apy)?5y`2}nh^La z7!-0rTEv{QLZdR@cd`I`lL}=B+ybP2jYtZ08{Iu$(21{hx+q6O>V(gUm-Gn_6rr*f zgwM!M2@1YJ#)mMg|L7c67WU=m2wyiJlFmkLRtsBw_~j}M0$Yrd8aED9W$88itV!Uw z&>{AfU@KyL!n;awqFenC4h?0@K8Z|J=v7R{7?p3>&0t3t8|OFq#MP=2M{d8)OewYZ zgD^`P4E&l;oR}U{8o0rt_Mgro84RGls=wfFt>sAmrW&9{^dMm0{Luny7>yw6hin(Z z_4dijtvR@Wo#u((W!`QP_!1%LprRy7o1jVCyz$7MagU;|_DpR!C*z&~l8D=+JhZWe zfMEQiqm}8!7T;N!WsQ;zmfU#9=QX|vg=3iAiqm``t5LI-IFqwBIM-epxAqIQb>Hl4 z=|!8TJRaL`rHWqm@~#XvvmRnNJ^hk%tYSdlOY#;~&mIlUp-rY6~DMm|nd~`UOlw#YEPjtA(-!{9O1>ghe4Z@AOE2>zIDs4F)74IG$o9UZ3;JNAD_4^ zFN*E#y|@nOsz9J(kR&ds#7Lo0FI@V2?;o3b-4YgybP?>ShqeAEpU7qNI&DXM2{al?VAWb*iNS~w_i%KJtd z%5!D0+40}casNvI=5Et<3gnZPt6AwBepsX>_z%7nsv^rw->URO*G_NUId~%81g-&* z*W1mbq{L>e5{8??pUMep?A25EeMdk>7BaKMBlyvy+maj1J*4M;(|Y~cO;Kr_GjYnR z^zAE$MN7zmpORHUBFXZE%voKvh4TUi1;hzf5mpAztc94uem_`YbMaJB|Iww9DQ4q_GSQ#SLDa@B+|!$i$G@pwTJdex`_c$ zTrYb`Gj9nIsx&E|Uu}^A!s@P^F0G9#m~A8f5K>)UT_hK%+};00S*6qfTVhXBHwb?r z8s`_B$CG@Z>>jk8@N$FUTwxnKi?Mz zUQI$~B3*h(Z-t#J+BaULje9&$IV=-^`={qpzAPYQG=xposZ>!(5I4k`I*zK^qYp>=#548LsnBRr_O{uLy9A*dd7C9Iookg6 z^E7ls*H~?DrO;dLK>{$r*k;NyG`bQc7OfyWI%5Q&kG^?K=p2_+V1m87p2KWJ>Y= z^sQe0myVdwknNl5IeME41$hUbYln@0b&bxz+4yiRyO3x1a%~U^nBtnOoMe#ICdD8> zyv&Rd2k=uffEnQl!tqtY@g?k=6~0B5%q5iUHo@MI^eC?32IH4#mheuqIlGA+x-M)W`av?xRgQ3R*fW#J`7*PfwZWNc5rlUPZNquWOaWc73wbx`SdX2jKHuw}6mloL=1DYJ7uX(Sp$$oaF zp#p~~!oEloUgxcFW)~;R_zRP}MPM=sfPk6#us0g9uS;Sg?=EZ*eil*jT~&q@67OXVyI9S?F|Z^ zr?oh{^9~(Bd-k@Es?L?!KfRX0qnQj>&HPWWP7XJnb)ezpgPm!UGsubIutdOeu(%NT z*3`j9KR**kcZ!a3zpNadR5Cs;5-%LJH|}t!b=1w$e5-7OgS&$eirG=OA~*QoV}%VG zkGEAhpDDx{0V8N3^!Mns&FRouZ9c@q@ri#dMWIRvcetl3$F>@qJRy=kO}uH6x3mBw z1Yt#!4g;npY83*rmx-OFTE>K{$KL*}r4y45gY{{QD93+yE?)m9S~s3xPYOU+K;Lgt zYcg2XH~j8+RmW0CkW${9#HTZbFok7p^UD}Yt}@*>_?vix>+k2|>cgBS28d)5MQ|Vk z0zL3S;Wn<$#&0x&cqMoWhQw!TNFCNSrfHeBL5kWPT$WClr8|+%?mEow2L2yx!R4z3 z-vuj%QR(__?!IZ(Njedq|6^R8-&88ExzKgC1tEqjVaEG97It^o%(|cJocAaGduF`V z7iwS(7_%5`+~7JZHsJk06$uab56}y$GnE40Bl|z31m=CtSIjD7^paKi^q4&HR%JB{ zIeS6dLRB~Z%^gXh!E;ODGjX=fDTjOD1dz(6DM*U@fs67aS6gmWk~Le?c85ZG$TWc_WT`Xzj^ zlUtj>LnK1hC<`CD#AuV9{oXP@ubf2Nu$vqqb;U$#lp};Z9Ar1Sh+(4bzO?(kl9F97 zRd^CDAkff@RMD}bWV{T)Uq>Fl;9$p1E{?Vxfh<4P6)i7qWZ)=qgp61xXmxiw>iea? z7_a_|u6IV%wk5uCr1{kHac8XWV<};cbVKp;0(-%$pm#-DAPgYqcC&QyF~(07m%H@m z`G>Wcv4Lo5&SbQHh*l%F)a&sVUT6E|6JwGAr3Ajgci^uVRlE8hj!#@)KcDEGbk2tx zFGQCz20h^w`g=(>2nP|j{Vr`RmyM$#F}xDf7KFy3y#y3pESH)dtJNh+(*=!6V_Gq0 zVszZYZo1OH$LH0$Ww4G({nK>c+3mA$sOs(FNbT06;eiZ0XG_rtUF7%d%!I7wse&!t z5j?QeEUNp)Ws^sAa>6MWcO@T@cNn2%q$6S^P-{d= zaq9XP_|ebFs@DpqsjjYDJ`4#7gpLX#BxF@>hYIaWh)#C8yO&mooqZ7(7w2C==sxQz z6`KujZBMVf7A4npJ&!i`=jceF(mP&PWUpZji`KP7B=7vT=nw%X}WM`R=oA8?I%k7(*cfBn}jXC5f^!zEImLDAsDT{sHc{8T+ zrG)9sZqH0BrhW2Hs7!oMbuPW2;e9B#yh(`qT9Gb6&v7rEAc> z5?9LwbIyaTRZN-p+&eOb_hJS{RONA*Iolm^5eyW;>7?tEvAakkx5*gSnkBx0cZA@P zWE3`O+Q^%%`2CBlRC7g7)*Qv6=`p-Ufbi#H06kg-?aWCgnYN3P)~>RuYlH8{Vwp%t zO!;jT)tZ24?RhWkZ!sh#oN8dYehhOk_-}7IbuZ4Sxzo9_!ZOdfZ*=G)5NJ*}il8xu*6M^37P2uXf%SV^{ui z=ARwq@&)nR1C-#IUv$PYXqj-liq23_YMqq(5~V53koZNdCb=FC-#HcV;|Sp5S7P}?l| zakJS_U)TU0HrUN01H?d~9?zPT)F*hloh$9a8Ki&Sdu7JES@&6qSr8=f_QFw>qTEn_ zS2A<#Rjfmfjc39z!N(s%Zt^l%FipAko}$D`kThB~O)o;fNl&ttiLhWzsPHH?^d2(! z^hTa7`J>F-HH83WysbtNTFaf7C;NNSwgz$WPpiaeP`KWvt!M~Yz@JMzsoN=+EDr6j z-gg($&!x?VgMCH2GN&~gPbW?#JV5~Drl$GNH(i_3L2+m3pU+QSUwafY=ayC%JR& z$W?V`UG}`6_ty97$2ifXmEoS!^Jx>Va5c@#Zt2T)Uzl`&nOYi|^p$>d$M(YIlnj7Y zrR!-q?JRJ6TctOp+cj|0=Iq~r;IBN1hoMu~m)@E%bEwAcKs&eZK1&VN>>=^-`odk} z+x8DT-$ks*Od58y-}|h#2u%CBZJkM9N#~NONldOF?ohgae@ZQE<;VD-fA&0K$0s~J zH?Ha^b=!$&G?B4Ia=-ol$Np;INiWfvp^fv7`{s+eCnwdlAOCYO%5~mLpSM|n3l)Hs zzF3C9kYIoI{rz^2O=Uu6){paucW292;Si!MXQRBwHDUd4o%qQ}!T8(nx~}_K&R)71 zi41SVrLUJqYueRs^NB2Py(!-)$wWF#7tWdHC8clD_VPSWB@*2RVk;@#O{3T(KGA#c z4+4^5e12lGZ06L=WW}<;U#ofDPPx?Cgdc^VFwc0M842s5NIzL5`Qu09Q?8Demw2G$ zlWFSv7jehg27C<1MeKayoEt7{MK0Vy8WfH_tHjMb(`Jif{o=7J3{>PDjiq{zZ>luw zc(MS1<@()l*OQQ%mK6m)b>-+g>6$JJP` zMLO4cv|vAb;fH>6g_TkIJykF%vcJEGt6o?tRKy8KS zK&{uqVf`Jht0@bU5MT!FFaawt6^kVx6u!F)b(43v;04hBhRMl*v;5*jJ(c)?Dp;ag z9jQ)`9YzAyYpCN#L9GT908=WlHXZ{#fUR>>7m#l@L!+a;^G`WBU|2t!e5eHc-udEt zlq%CaaI$EQk)N;|Q(f|IB_B>4>Q?Cd+u#uhgfhNrSbtjH?pdO_E4CwHE@p(J#L=a~ zK6wP-MpHpAQKLSbu<%gUAe~(j}^N;a}N+IQ)T&O1~BK*++Zq=x?XH z1pQXWtBiTm2PaT!cO-n!uCXwRK@=eSIFLr9tQ8XF;4wE^48AgfaEN;nVKHC;X75;h z_B}re7Otix;m*_9A+-W|6TKQxZ3~AYQzT^*GCU{@YU`*aL-U_V<4bnulIVdyaeGj@ zN|I3mrsp}PQT3=P77N@c;}!YIrjge==#x&P7MJrjg-s+NSl_3GGTD{Ok)`7XPS{-x z#m_GloUa3Vpi9v!`W^P_bzARk%{@|9blB?zKTO z`iE3)Wo@xO-oVg$Cv(xXzNJu1NDGZ0$IOf*+l|J_c#}{~6;zQdE&V!{Q$N2GYM7aj!LN5bEJH{HXKPlU|M!c1+=JmsuM>iL^zAptyP_Q z3Q1zp>wxkxqTLw;!wcwpiE%j<--%<$o;+BbFadtHC)RJ%ZI#$LG+h|tO9=@iu9E@d zwh{dZv9it#Z;Wx1?dwO3_|$L$$M%yoj6)cHZU-d|=BhC*Pt{Iul5jbo$Pm(vBBUhx z1do9k)2fLu{C_{y4Hr>a*x@oUxVQl2TKaFkyoN&b$RGG9Z-o#I=o{q@j{nTxDxYJa ziXej%MU-wbmI)ZKMjSd8Y&17PMDny&9;|KdL?ctKM!THGu5UI-C=M{#9oF4zGCm8w zwKccb*!|L2+B}tap1!D*&F(y!;a}jk`(b`4r1n8HRSeANnT!Y(<)6Y?uX?&A`PRFs zxOdg}&tv+|RV_qI$?X;~|yo}XV@Kkf{c z*leNpvt+rQJ}yy}uo~txoX4*Me)8NIOZoKAS>ez`XAp%`b=+0Q_G+q$CW>y8%eUPm zTo&gLUks2dQa||Qqgkb!b=^O*r@u=xS(2~A18}tzIW*`&{wWFXUv3&sU`ppR$K{+qdrqNZHp3=p?Iy4NNC9sXI-huFD$S%e zDG(oR8`sC+`f@PO!Y7#h?~n6YSHK8woO^G5b}z1>u6iCL~x$0303%m~O^* z!Q`~El#G9-H7$p?aQu|4af80-osUQfrapJ^f4DjvV#>!c#jhp@LoB> zhzRD}{rbg+w=_qD7d@)ht@T0aa{<1B0w)DCp}{Z`0Ie>Ht&)7TMx6i_Qzmt;I<{Z- zjl<5^^<)M8r;VJdN^tLXkdTuBw?Z`r2S{H~EKvg-$$S zoQ9JeY}=zFKE2mDA;|6zB}*?s{GSJ3bw?9@;L}U{u|0t~sNxQP3rA)tDa16#8R%ag zv%9(*?Dp)hPb~~WA~ki(sjVnr{CR%Lu!RPYiHnv;17jMny(cu_mOzJu*t}w2=z@Bv z^0g-h;{bF2F6NN=-q>Rx`wuFf9j#&zM3^t*0Fr){*&zon9bjN!%9tBiSYeNp6Qc8? z4*|49Ujx4I#7X^?qlmuO@5;nUA-^T7xge|Yiy%tu*+{5Ad#U_>PLV|CMkGS<^fXYt zuK0=d?tD^f6e5F)9Au4$gByCrzNz|n)iw4Z4~^(|r0@Z2DSro-zRq8photO@FIC@@ zi@zsyfB5Je38_Qdj`;d6c7seOi2TaSm2&*h>#a%wd@h-1k)8kR0`yPqr)aK;{AoJ@ zcMQ{6KbOf~5SrZS^tha~-f_pB9B9Dj>innTSY4@e3vQH= z&qKwWxYy)Oh8M|}zexSfh-3G(YrpID=`tTv$-7_G?HI=hiqIXh^dw#OxRaOls;TI# zcP{=C!%s|xt+pz^04)P4KNutGTP{Ne1zK>vGwvu{7Rim7FtxZiVSN+n$18xfVPto_ z$;=S*`Be44+n42_W7(~^k5m=3XLS!;9_X)qB=(m?K;y1{3%gcFMoS6=xT!#wf1MGMuO0MxYY_iphM zIbu|-gf!qS-)~c0UvZHD4QdcBiPmto$nNVuDmOFaNtUu@q4-)fB?ZyQ>)LN zf46FYLt(j3f(Ry)kgxyX?!Ljq#CoMrE#BO~(xt?z?{MgxV3jrS=51S#7l(69Ym^Q- z*2dSS2gNr9|H^m#m6LIlaqqtRimn`UBwmDv*p{yxR{r$H#(Oz%Ot6_W|F+CwGt4$W z*MqI_H{kef(iykbNMZFdY@M+v;U7Z;Qf`^pxzR5q`9TlXwUxLGDNr2Rt0UauPG z_Npa}5N+5j?w{S0yD^;qdWn-br?usqxKY3J{%4(~Z0$u88T=Fd&lXpd{O@}O;`mla zZUvz?+%HKVUC1A=8E=3JxrMY;Ql7M(ZK4Y%HcB`HOUD=y4D?K_L&G5_+sT{I^w|)^ zDRDf~oO!iK_VrzbTJ!RaBB`rP?{tmue=wMa;GLo(`^c^I;>S(i*3G27u#HpaAal`y z?2XnC59DrYc{XZJgRd$qzl>0QR+c^C$|OBk1#YV9*0SH%iGTe**0py7k4(8oU|@LI zZtfau&ziJcnamzeqr`iym{r*V7g&!T9(Bui}sE)!kkpL}R9^PqOF@&nonHzWMWG+5b zaiamH$SM?JfwUw5RP3~}$Tagiw)RRYWP*rSIsW^mzss%*h>IqnsRGu8a2KchpEG_J zOT+#vn2ex$Wl%RE;_jqQ+)8jV)kxLX)&i&6LSDT${7K^-xXNg3p7M>-bdk3xz$sT; z!7TonhH!4B*A)SI%YLcT9y9|-p7Hkaiba{@c1SK7Ouph{Zs?GX(KU&e94Z6O`(-L8WZL4!e_qI= zJbPvj{1QNeD4WhvNvI$>HcIP;ivN5`yII9a~2BC>KNWZp2BZ6@Nny4A006+$_4lG2nY5Z*jGM4Do)6l|)U!2mMYj?am zrOhfr$$~T5gj@1*u`wd2zWOVPFR!ibPS<0E*X?h-LA3d6Fy}wum)>J3DI)GbWq?0=)=xF#oXBm5IfW50VtH7x+-$97fAc z)+Ak0FR3`|`^^u{=p$#ZJ%R-v%h&wdpFmaJjdA+mwVLnG^A;|v)M+okA45OZqNvZ8 zHfDSz*v|!}H^e_I9Q&`pBNO4=Fh!4Vd6 zj)!9sIKEslO~0g(X+5LnU7aKifDj5Bd2MNxzu6-BE=t{4Vw6kNziv7mO-n zR3F2kR4tKCNH9hc7bZT-f!o7`aX8QH4-;N)enU(NfQSilrB?kBE`pQeCQs4^kz(kL3PZwom-+4*7fQc z4DJnqdP6{ZcRMvTxO|7tb(M~;@OZOSF01tC-tRU0ykIuU;a7(5tz#^G<-XB705r~O zLGp~c7j8bg&1{|-@EVra@q5Qtf-16RI8NHjE*m}}t^X(+iQ4$W{!5yI5J^*Ed^YNH zu|#+tcQ``<)(z&bwQMm5e$@yuqNhGaD<7)U(Z%IquJ}_>Ps)c@)PQp9)D1!cSnDF9bU+A1(fd^} z+-}S`Q0p8@Rg#-{3C-|XBd%-te2Ci^P<3|@82#n6e% z=fCssgKkG>J1}H{yjO->1;pXXO$$%iM4w$;Odbg1nK@TXq0NZ59gqteW8la+? zD}=SB{lXQwNmR?m*TN$G$MIKF#WE)nn;+3%_<#QqPBS{ru@Hm;GBf5 zYJFW8>w(E@0`due^Uzfg#QL(e0042a97yutx+)oew(gtYB$v@h+PbK`4uMaHE(Hj* zH#Z+dLfFLGd%@*nYP=+;k3l}W!fevYe1I913(9c;48tg_4pu^n@}Ty>Ja_YK-nlR^5`Z?;emM2drFRD&_;+L&JsX<*f+ik7 zbB>q2GMcKe1PN5{%^)qbq{+I1EJ6`5 zJ=gX?k3@HKs?j)PAb@s&mQa_fn&{62_Z!^qeYoMPsh=w5zvxZYb&B zTxJ$BB3OGY&2fGo=jzBSS5JHbU=m5-D@1v@vSf%}=8 z@-z>v$iz6dA9Y3!{r~`MHMB0s^tBLY8~~uA+JpNC zO|Z`=eB$8lK~xd?hCj5ZQW5JhEfSlrvQ!YtB>)iJ_@NsgSq&kF=|M`(f_U%ZBFf(recemJ`wu8iediaS`Q|jwccEI8(T>b4T~TL| zMEAw>3|haLa4NlK^1lL%Jq~-@yev(Q!cIfO@6}IetA zdbn@8_Yr>2RHF4Wq?UpTDD2j943-w55X%qPHDfU=5CSOCidn*K>8Kzr@i5dtS2?U@jwn2)Pc)WtS_yRwhUH1op z09+H!*uv%oxl&;?K!g%qog0k&r*W_#DH_lZC3|ej$&@T!JL-N3K0I|P){Qwh+{6M> zMhKHNn77uc#bs7IkN_9u$sjHxS7t#4p8f$3?qZ#U0S+#dFO8qWHqbm6va7`hEO;nmT5xQ4xYE<$jku2^Nlt&kRVut;-5Q)r81@3 z>qp>CWjfc;`Dr4oN<&}F0y{oEw9o3_)<};AmhI*vvktNbp5X~xg^g(;bXnc!!{0h- zdBJ?s<&Y+qa|>`2fTHHB+o1oo?c?rQfl=VNMIix+YpypYxX1$5Zg%t^7NrnS-3id; zDy<8fR@w+;#!NKhpdL-SdphCb2K-R*!~*{5;ztk7iHO)XX2{3B$Nw1#{*B)A@l`e! ze6O;hq0WbqY>%HyP3R6nNu`BJvHR#VTL~x9g$SSD=r(ch1?R|WjBdH!mSKn8IQj(& z2oPW1(vuNE(oV}`7)e5$*K-xw-+=*&Z~xW?XPP^Hm>EhgDB1723vu(s0Rh?#;yKP> z4(22qI(}CgB4c%xOz%5AL}{mA&{esWJh9?p>H@n56?-z%Db8T zSEd5;zdz z3qu<{IyZPHjia3Qa>tsh^QP~ci!XhrTQXtl!NZsuy*pI|A)Drif{-(ru3u3T?!^7B zj+4fAmGw^VT#ra-6AM&DWosHMG|*)t^=Qj1{=z}#wVRj!d~;#hk9rF_j)HH5PLP+A zbS=DY{p|Z}zKBrA8)gWTs}T^=?H(6!K@FGO6?(NhkAxvbFJbBZi%NhCK7k#bhP9}V z7!#bx!v|_^(@@xHDm#3T1EPn`>uhLjGqJ;{sETfKKl2Sfw>1^9 ze@^WF0l6qD^icXOx%*!M$2PuELLaCvqMi48)HdviO)smmLa&Sy!$Zu*>js{=Q~tWE zKC!!S+I4M|p8pa8=e}6iSqau*2;~j7cOK(gz2QHgT>HM#b-z+QNnqg;3ZR5TJGx*R z$F+W-tEodpm>axmQq;D86zlX4p|@Mx(G$G!nX@Gv`B{)`D6SiJZGcL;y*2_n)G8)n zU~jaW^;NtGl{qhAgjA62?$+M-x+ea^?6%*G?|6@@2p%%=Tf>ez^KWyJ&St)T+y)I3 zO7Ie?wwr$xg%(9O(W|bYM_~|-Gd@unsHZBYWqlcP12u~H$Nv1k*!$%V__|F7K?%HM zXAMLJ^@f9L!e6H9Z|w63stnL?qYfMy@ihHfe~53)>IHj>m>TLf?V3Fx<9;~A+l`TRl9A>y;yy02Y^Jy{Jpp2#%Z#WAzfvlaH zW78udez*D3cU%)Yyokf{UA&cu2Vx1zF3OA^!QCey8&~SbV|--f<&CpKCb4&Ff=b)7 zE-o}=KL0SBviOj&?Zs$mw6TN3^}uhy+UnHkB4%Z_IMFF*%be+MS$K zn&a5@sZz;;IR^iLkQbbs+^NQXozjFATi5OTqI=Pdz#xpb7E`nl5Kvh8ZIRn^X}w0h zntDLE#9;=zycQxL-w};9ETFO)9RDo9@w1=aqc9uw*N&Z3@^|oHlN-E4wq&V%NASiZ zjFd>HW(Tbo`KyFxc>q=g{{)i$ifj(t#$ySHU0o6j<)0@ zgi>8yEFwlge!4}rm*{|KnR!=Ls#N{Gc_&|# zfk5=NSSb|dvdnl61e6 zWahA3Y>vYmOBPbX3=JFYr~5rC{C%qdBcR--%;R?BBkJ~I`u*V;6+yu8c%cOo=U33; zHKC&MLh}dzS4iMvbt1q~)PcDtV}TpYVyNX-W;a2*d(pjcFm}7MN~R2Fw(T)>%i;zy zxFzCC(CIh(15Z!|={8dgYv)t-KI)q;GaE!jZO6jhPj9b`qhEp;E$6K=PRYHlvn3Wx zl%Kri#+2~|HR0;T-dhNuSxY!jV}2ZDVJeE8)XU>o(ldG_;$o-$652qnzY(+>bz5Kh zvFu2hR(sE-OXRsDU2W%+k*~-m2a-Px3R_(6W>mKVQk>3o*0GL6eJ9e@@Gz{I!dAZl zu+v?LZ4Vsgtt6tPl&;iW+0+`G-*hU;%bKaO5&Jjg3*Q}6NM`@T;nq+7sd%o`y(^DD z5=_|jMoI9AYyNk}7#G+x`-HfahWQTp^sN*)h|+T(Ro~bLXhfO!iN-AHES?kR8SE-+ z;e9WSG!D;lB)FnUC+DVg+kWKwFA(_7-GKlFw5}a?dO->qH zc32fj zmS(3DI#u`pnb<+NSjy=)SArNEVv2zq>nsC5>97c;E#2xG=MqR@mk=>}_}94RWdnC1 zY@C)_?d2h~md5!dt*LH%=cXfr-yGwPl(M5Zp|1epx`>}b)qm%v1JdCMd#EI}XX*@E zs)eyTiDml_YxV+pX1QOtYUP~|cL;)^f`Oy-7d=)S>|6A+NKcy|9@EX{1Euz5|8k|F z_K{u%YsQ%!)Om1CVy61(%ENwaUUv(nIt{a(jj<@IlVJitf}CvTVlj1JgKP@+7aw#m zgi>ONhaW9qCHZl|Hsv$F_wilP>tk@sr0z^(Ra@BaJ?yf{Z|_RdSQs13rp}S65=r&3VMDNiVuo&9k7y;Cn^!bJ$&fK z5t5!*7yaOHMki0~mLpQL8%K09pTO#s*Gm=o&pJ#Ti{)7$8i~t|c{=!?EmyCSD5d^8 zmC0HG*1ZgGZ;Wmh%&8pnq{;9Kh>zQQ8%Nvfm4@sU^>DC+6g5?}u9`fiv(em2<~LU0 z1xGJDjF&%FL@#`z=SCxaqU3XXueiB!i8WBQt4MzuBaM-s**PYX0-jbRl+6Fky_`ps z?r+PS<>6D1cw!bDih!`)s;(PFChM=h|6s6{Z}E-u{PSRNSd?9tL}TIKy6g&k^qfS# z_p^A4S*zJPmv9ZaJjy_ltvGCMZsq+uOn%{NA^)PrVCsJpJ7d53_Oo_<<;yVzxk+|U zx3&1coGewXQ16SZ_xrTmCfyS`4E~1j=NkV$XwMT@KAaO*K}vKf2`vp9zHGcn$O2VJ zpqSJ}&8es7AnU&pI$){2x@LAV37MJZPW-WPcY8n;_e+U;(IO}>ZtOf=`jV~?0{?;l zgItn#KTxf}zaU4st9kX3bSD4ZeeC2L{9>BhYEm4kPaG3Xm$Bo~K)eJ!29k9u=TI82 zK;@%quHIix98lovH{NWm~M9a5jE%JaP44 zs$Z64RT_}}tKBrx&8xIkqmbYxH+epdAQifFMNgD%Z zqRpBytsj}Y9Ufjt(LQ?4-0sjp%n|9rLP*m?+tD;vE;hJAdV-FIO6L83>G5w49lo(X z;nr@ebv*OM-RFt@0Bb=z!23Q&L%CFGd9|Gv?@}KJFEW3w^snXNnViHN4h78UOeVdZ zNi#iK`(~Ul$oQw&zZlfDNu5wy!+%k2v-Udh!l6xgTIQ`NTq?27NLV%Dzuq7|lJc@N7I@h0!gluW=o z3#fqHZ?@O;G!i7gKaIBmB^3aYQ2<#BdrVvae^xiWQh89!RxAB;If*&tNsc^cW`-nP zs%xz8yP4hSkr@|O0zB=pJBlp)*@b$N7LT**R55B#=aF^6)z*8$%&~Am*V1!Ht7tS8kDsmSCNq^*b)Y{U(L3E7Vz&P+iMJgRTLTf#^;X^DypR4yswZHd^VL_QPNK2%Xo}F>8%c>c2nF)Q$7;&%pn+XUjty^xLFZXL5Ww6!l8e1Ay3Yy3yJ? z%P}RNu5vN|&F{ILh)gImJdVsCr}rvPx%4h~2Pgb2#RBew7hB?&+Y|f0Fof0dbes-y zx@nsUDrP6GRh2OHW5s)JSCy6|HSK-A?#X=;!4o2mWZfN(gg`LxG9Qk-tr($+R7plA9T3op z*{$ZNFy5ivA|`WGcxyullnG2MVrwm0{$8a{voh5I>6b0{z)d%D$(m}!lBB!GPOIMm*k!h{gd zI|EFnJioC{lE+rfttjx;^q(XZMYucb4hEb37%!VTw9;zlFgvt&wJv-KU`noyt-hda zb|I~Z32p9QmOvswHp(HDd-f(y1k6#qLRZID@->uN|l4W3!e2;|+vxb}g- zO}A>AStL*U)k`AR{m!@<;V@`+MO#$Tf}q@8FlmwI1sg|i;=#TG%*f9t>s*CI43y79 z0ygz985JfX|5GsUGj{bQqeJWn9g#io*_Pq1o2i`!PK>=405+7xi=TWguZ2qpM7TH0 zEz9;;Zs2Kq8}64_s=BG%qeoJibf-`hvh4ZSw%{3Y#11gn*ui1{dSLG96FM@+U&<cvg&WOCw)OS|Ow zA9L~c6qGjN2`Avw5exFXxRBxUR#D+t_CuF5`{u=m>}lkEw7!eOmO0*kC+6DWXA{7# z=lCFlc%b9*{kp@;d7CR-7ia21N~GMOvULr`4hQeGK@kX_Su}yjG4p~oV&m^-2bLgH zw!8zOgjimprTqmEMjiCK-5ZSW>4OfF5k&!CmW*4@;>NppZ6tZOS?=`E-4&7Gn8WFL ztV)o)8tH_I0SRj^!#rgC=9XP@xA$D~@S>GD5qMYH)%hIk@E)e3Y%H4h+3`I4qi`+h zPn}xJn1d%_=}H_DXBV@qs*L1J!y`r8?M~q0s`zB;^OWtNX;LJm3JxUh=7f)5KDUnA zlZ3G_5DgTpt^8hF{jG)us(}@t=>>i0ct;;Epg3J$wNwpB_-u%mDNc2QKpeiznJV7j zda6)EXzu?c4&pj1$)MoJms4dSqkcsm$O{fw=?C8Wtn-`LR7y$#GRi*y$!xpY)QG?J zEskMj&dX!MA#-U2pWU#%aXH^BSP57{R*1`G#S2K>1d$@l*RpzJCiA)&{zF>uL6lN! ziAsk*f<6v+E28OqRT9uC`Nn++5P^t{6A@p?zn2d6;)G^L2ZW6kUBj}My6WFWcVC6d z&`xmWcC@{Tvw6Q@582lSy%|L4I~l%8r>e${2qE-`$7E*<1Zbi zg`dchQ~h#yR6UhdcbsSt+74P!0jqo7&vc((b5OJ#0_@6^x2^8WI{kd`|BpXI7vD z%!gFUM0^_|E_m^(GzgGkZPmOE_Gjf5f?z9dgE+wIMRE|7D+P_m>{@KmR=1}wfv$Ub zalQZ1GNqP2awMcfFe)jc5l5(J8q)oU|MgZ8{Y;srsQcWBwvy~lN#{rGIDs$Y5n2=x zjW|_bdnEYMYu^urkspW)LZ2?Tw=iL7`R!jmhJ@M#`1*cR1W`a_2z@>blP1r?e!f21 zYA$Mi&6!$jv;T)6v=Ri1gf(01^M-#*Nqm19_g84WaRosk^U2uZp`|Ky-PM(V-0$FM zEq9%h6pK@BsQaP98X!G`_J694|J_|D_mQBZZs4weMu^K%jM|k2^lJKHWU}T&SAvq) zT1>{?t}tm>sRTJMJ@2G=Pjc@TX6n$lCB@ICT8hXBYi@@3e+FCqh+KVIeS%s1-Pi6t z-~pvnoO#cW?rbL7!LYeLhOZD?8#EzS+N$pvpB^}kiY~5DAR^L`07@1HB>jF$AIh&iox5+@OYGJ**&4iN8MIto zP#0F&>wriMH942}3j6NTMXerM zujArNrx-&u5Bvh8K$l)B;7w<>8oz$J zxJBq}vn_yUpkOm9?+wTN#<0-;`hX(uO;TS=e)M0gy9HK?|Go1zRDvymvq~Zyji~b{ z*PU;j&6O<)d~JJd8`wPpn|$_9TwUZ*Ft-Jd~IW2)%7kqj~)wfzF{46tw7< zJu^0Z`uFkg!I~h+5#IW`Zr91bR1*|8H3RkGS7KW*&>tll7GIw|qf*cct91f|8oJvFkEQqrcPAu`k6O=AJCKdi>=S9OcUaL z6=mkRfpr|PX(vR~0oACpmI@>wcVs2Wj_bsa9QPHgoP5^h{8~<&F4>3_0MK&%Z9a*v zYIDmEX(WtDQAJ>-=ZVcEM8gVTA;|iZv?1V%RgGv$eyY%UNz=ngN#Q|*qm|$~Z{eAQ zjmfVyF$CXg>}A7|4Jgr66lY&j1)^5gvOkZ;8+hz~T#$UO>nJeD#reu}$bFr1jJ}z=|empd#J?J7-5sWoOID#7@Aw;EZpw1kr7EX?qfB_{D-A_Eo zNMKTGOPW4wBn_EfYLzp+vD;Buf0?@nI({#YqM9Ze^5=tQI^p008tV$<-bQX#Wnz3^}k;9ix_*j&jTSwo5)EwRo8VUB3c{o{lXy` z2owQ+POQycy-`xpRbc*|^n6Ht6enpzY1H}$LBWr&x*)Qh1!1By;rWX`#VidyFfxK! z=xCYai|~&fbNqEUsvsLj2ln=0heJYKa9FIh++V_zjBy^=r|zVg^P;p~Hj)M-hUWME zR$ScQ@p;A8*e27S%1Z;$ylpC6jdXlTmFTR>qo&_Q#8r+%CHB%fQ1+#8-oHa7|mx9AgWcAd9{#?zdP6zH5s>U+_= zSDT+6Zy7M|v?a+tyz&u_EjD`20tGqgOWT98=ftjjl7+Rcl?Z+Xpzw92<9l&1QQPs9 zVmDeSh3KgVui7|o-oU%qQw@D-{5{b|Mw!{6d54dm*y6S}(HJv|eS; zSyegEFrM#;NBTb=v+)<0(vx33gMyDyj`L_HY|0|d_lZ*K3zY?6mA0bf3|JA#QIGC1 zOKPM8H_Y9uC^tsc>$Ub^rs9L)UiHV|7Df{)#?)#Hu>;=5D4(O7&)`BXkUV2lw!_Z^ zQLWt?Elkwnhreg--;?Tn=?Am>dp63{b9JJMDSB`}`_eA91OUV+cfVc(uQ;}hSx=gW ze}apt7L!HcaLU!17h6v5AFHh@ogijo#VA7jP|w@acH}>kJR|zWp{r*UlM(FCH`y-c zkEesMtvCqOWJsRxDH@7weqRZ^-!xFe4B0*ltu1Ha`C3W<*6GvC0f}f&a}FCzS}4tH zpRk+I#K^A2mPjqxWI?VZ{8Y9k9jC>Fenw=wgcT+pgjy8A-8D8il|R_7?VC;_&R#Kx z@6>J&HJVCOhlhY6{@Zj_Dz~b<>kep6vUwpz-_G|~-dgCc^tlN;aX&8X)ejkRI7s%+ z{J+A+Inz?{5lfLL%G6+|H{tG^;=Xwe*59R!^@quX2|G=}sUhrAk_j&R?pJauER)Hm z!)1&!>|njDxXSs6&PIyE-=9y=cE@kg8qzdcM-lT`j{4!8iY2HJuklQ}pcSXT+~8E_ zt|(OGr=7B9sO0ma1I*?CVKvU`Y8i5H6SjnH05zfkraSK_%zu`E2Ayhnu)%rm*F+>M zo1AD^MzN=h5FjjouKRvS+vjh}|NQug2TMl5YN-`A8sX>!l644-`0AAxMcp~EC9V+D zCel20qO&QB7>GBB8-*GU1AvC#H0#by*&}@&EOxyO*dE{gAG-OwYj(#!Cnp(eaqc&D zO|>q&j7+}Ep-@r&3<1a}$+8-6wYmx5PCw;7_y{m`$ZeZgh0P3`WdWrVX45?L4y5UjcY9X zn7KQ^leHJu0wY^iLU91Y6ldAGJ)hY(fk}{7zeq+HE$QY;WDR7~*9uSGldWY zfT(1xHu&LGCJE_jpPMsM9{1N@yYN81S6J~J5I{`*cX4k_M({72UY0&?*Z7q$3`Vc= zmoelrhB4l2kHf5bMtOpD#>9<)wDFZcnCF_S5J1v;CL}L1Q+V{O)pg{Owosj^s99g= z?HhT3k<&&4BNHxaI61G{58aQE24!}Ji9@MKF=OOV={+bFhrmoD>(r?L9yRHdEhmN` z@>y>U`Aqd3|HUmhvIduWeP7XhyPKvTXrkHh&Qj-7{a;aMn^;p=X(@=q$?IpqE4gqo zjn24WfbhZWO3HfKDm8ctljdI($+L4SA%`*JgifPm;RqDe^jn!>q(4!95RFh|x$@I~ z_|Ph#_O%SAa>8Ki3Tl~;Ki|Gkpzp7@w4p{sgrE=8q~P*X<7J=*>5TG91`5NLh$rHv zOf!APMYP2BABvKK<;RS({dS?_DiKemO8$u|I{k+d7CqWIfk=ZZ_Y^s^8c%e(S3SwfvRcZ46;_{jnG9+Y~FRO*Yq}lZ&MQ~(9e?uthKaih=+_wy! z@&n3UxLNu~e-~N;N=~03SX<*QyG)Z}L-6FXbg4 zkw^sx5oH1kkYAX|`F*-FDIhUBxBiZSZ=^c6c>Jt)cdlTddlE&{-r_JdKDtxXkj*`Z z3qR7fev}0kkYGDI9l79bW|+D1uRh;IE=3}Sf%XxH{o{%bPn%_6j!=uCu?3#2HKe8$ z4$sSFObQO{wD`E7goFhuH#%b1=D}sZ8a%C7(!#|<2z*5iqTDS$3pt8c)I_M+X%Iy{ zQ4(?psyyZCFyM6q*c`yyLMtnm6!4OM5GWHJhlr!oLdzY4xC4rL`)`gVQ?QoHzV=`bn+3JEQ34N8-Sh>CY_knFbfmBsjbk)^V zyh}0PYegIweU{~1a-A@{D7kn8DbtY41ehWvjp5sux=xhgyj)d%TAzzl}j@ z(q;Adm0!7M#k-@FY%ra)KrOg4W$ioD$K;lFfXs97x_fzMiHO&SoQ}1lso*JaVLs|j zL88>c$+IPv0`4O1EWXCSNHO(TQbVD`)o4R!N>h}!jj&bf!RmAwOr$Pyv zPDG56(8u^J=Whj6(VATSm+P}K?3e5^T#hV+0d!Hr2WI{(n4Z_a-8VjuM|OFHGyZD5{?d{mk)evV?FFJS zZHsLK=Pp|sf~+vN1^L|4|CyU#8|ASI+4CW4%-zRW>xWvmfVy&>{H$N~XM3KlU{lcs zj)TE%Upx0YyFrAkM4whFQM{?#d@AK3O2^+M<#N#oC4ZlbT6Y`&=ZI9UqndP|qq5XA zRmkts18lYu32&a4jb2QUUoch=Ibjw*tlSy0tFyGdmMAp1rz_NROmr#lnp_~l&)EzbQ9DGs>Ddp%**Rp7uYVpdh*Is%g(R!5M=brPhR@fo|V#*3rhAztC z*B*g4;CkXEUf{%Lm=lpdVDT1?wY_t>Uy>T2KK>J}9>diG!!^b45LhNfBm z2LLenHS*RX$`WQ&FK_1e&jQ;Ksly(GkO771SV<#qB(2gK^sI=64ZQVenD^kBE1e=*#l1LDgYY#yKP+{dLGN<>d1@yuTTw)CY59{fmwe*-hM zlmLI`i_mM=R6>Xy0%U;L#m4@=>wc(qth5dsNWr4UYZrWi8s z{!fbSO+1GtlW$u)|Ga|BcHn8QLSemDJsxHm^WM&~rg!6qaTi)}&ttznOmRv+X0y1+ zI?OsY_4GIwpFM4dlW1wFUJ7|~Cq`K;3N~M@NVZzi4zOLwscdx|{)tR2c0-n_ ziTUnJ62HBS8Nag!t!V0oBohEVvr?Jxyz4FjXB#Jjh3_WtQm`&$G|@YF;uuU zlGrRG=9Bs}x#OfL!}<#Id8jDwJ73?fG9WB*!1T!z9prpeqd`gx19+dtJV^@0haNzZ zKJ;otBVSR>LLwo_)Ylzc&sR#5P5=Q3sHEWV&&&E1jYI*Fg}ZlupM@iuo~h9BA(=ex z2SF+;3Le1%n#tV{5V>Xka3@VgWgD5zh#`g)!KARP3<*bbwZ8>Dr(k$XD1W+l4^>%M zet`n=c_za$ERX`fiIM`@gV}#z_rW2*V708TL4gBGR<`4><6We(mMi%uKV<^!g2bA! znGO)t$-GApC59;vCuw=hQd`Z6q0bWrXwD~Y#RCn|JJ^Xe@guM%)R{h*iJv@+fd2p2I#DG@m#ih^vl81TyIF;1#yL(pCPm_bKZ|7Mh z`gp+b4ll&}7RkUGl%L^dAK||fo(0<9yU;xJRR#_iWE<}J+J5DsI9b=mpe$jYy?52x zbRYWt>WPJOAgTm0D_V5wt3}b^RJiwJ3!<}lhVoRCt6fh+Pc4VXb{?_rxe6t~!H71K zj<*3na-Uag9aSQqg&;F_>X`!k;X-{62dT(J6o&$3{XbDRnqVkSY6Uy)>zNJs8*8@b z)LVshilM6qdXn(3N~cMG>I&BXFBK&Od!)W(2w*kYw)7Aij?sfV&!S{s+l{DmH<=Lh zuRj%l)cs;6a38$%VE;oR#AZQxZTBRk`(%Y1Q|)EnU@O~oNObVMw)N}#p8QEjTmV1{ ziQlo}*Y=q9S$}E0^(rN8#^ODo$#EO)|7>C~0t+7qJF2WmK#;d-u%=1JBp<6h(KHVy zh^@f8fjnQQcpWhFZgbXmLDVwTxW|T6^=|pOx8=(#fI83lUD<4{0c@D(JK@SgyMk?7;UpN6@^DBkx0X8v_xT{uRu$j#Ghq* zfiXiVnK{niIG+Yp-U^z&n0-Ogy+#Z-`CWrL~5Z0AE2_G)EflD&yp4(=Sk z={Wpfy2wrlf)HD+p3jpWf{eSg?%R3yma34oxEhlce}V%Hg+ok=?TKiCr)J^pvQum8 zV+Tk;S|%wo3%uY{3OD0!BR)n&^aK+&u5vt@jwvx*qLumPx)xeb|A>BOHV_O|m?PK) zKIxQIfqdH$QXOQq)OL%`81wa1ZR9{wQrR(n;{HN-Lv+B z^-$ddExO`ZJ(}>KT+rR41x0f6!P1D(9nLn2`PjuRnr7Iy1}dcuCT$hoZ`}wY_n?W9 zr23}a{_e7D`YzidW(D^ARv3k6O?Uv_ZDa5*oEQz+A`YWRoPKQ*(c+~wIHkLDmt;TN3j##_n^wJ(#h}QJi7I{a2>e4^5wmo$fxV(h};A!LY;H zE%tboFj()b@I1e+jV2y4C9Qoe&PMW7Hg1wrU&o`!30{cQDT9rzBgfHJhH6LJxiAPp zUy2`7b^7bByB}v|3bjOgaoo7Gxf_c{&LXf+dN*q9bg+FROWY*q5vzm}P(!N2Nl zf3W+6*4ZP}{8)asO0vBbr*t)x1Z|M9NW7k_hSzB#JY9LWVkhU(9UtenNYlV+A_OfJ zE?D{VYsrAeV?)o`nm`7Y>{`m|J;}SdX^e7}r^oz3E$P0g{o~Plm!cwf@eh5^mirZs zx->eK)Pud<pYEmWzit$ZniStEoA*K6dshj#GJ8-4DUH0$)d_ zHR4(bj}N%)h32+4xYl`U@zK6JzGq0bW@e62@;Q1v_gj}`mUdldaEE!3?-UH4StD;e z|5J++o0}W{kRJ&ZULa%cas9SXj6UClVq+`V@(9^FbjD27anV=Ooj<>$noS&Dk)=h= z6RwkDTFF=Zs3?FbLOCFfD;>ntE5f=Bo)1X1DY>~y3$2J}dXxPKq7??x;S$GxZq_xL zEI+N&=^qayku(b=Lk>W{cUHxtO&wuHAp&tOVd;FVBaZ>!!}|M7=h!h>iL8$1o_T{m zZQ28kh0h@KC9I&{lX!W^JkI*`wCNn-aG}IbF9=hpOE}B2>wcoDXyl9;K!g zA5u`}g7--?9QvNL9wri0H)PH;=P3VV&J3XndA>z=_ZzRt-Cu6|#_JV8xoZfQ81Y-* zTi?Jji}wc~YT;c|pe;D6LwSFi`(+&YoODGk4j`e$n#XhRlA$W9k74 z9j(r>VeC|}&-Q2S8sGVK#?rs|t!d}ouaWTt|gm2?d~5zFS|;jw={G1+^uGauOrMv z8T=K?1&Nyur_B()0k{rTTTlPi7jCQMK|yEkfGN{QkY z+eP03QYFJwE%QrO-c?Lltdi*GLfE0dujN=qEJo&m>p03LS62;X{y61U5%S2r>uU!u z4srO~5Wo3mmoZyOU3tN=yJpa13M zux+hn{P4jRagwc}&BWrbGRNp9HiwCk)Y{OmzrV>1_241R@=iR@AFXiKL;mM8e|~UED%Mop`l#Ba?NMFI?Dg~&Hla#k4WLcEd>8i)!?Sn}37K#)H=-q$(2^SUeFf3fMo!VRUFP{JoU0mw; zrl@vcnyU^l1wz3gSim?4ItnS93}}%pQ4F9~V2v?3vcniMzA17Y-Z2~zsTNzhs-f# zmC_RyslPGDu9StG*J7w!o6IOQVN5c{MX$Q|1}NZ=U@L0jH|t%DTe$l5Ca6Ci8(}yH zzHZ_Od65g)t0WlR*p5KkFj0O3UE~f|J z&F|{EATS~Mp|FVY#)9T8xFM%G86X z!o&Y=c}(>BkK51(RsJsj{gU#{SdXFPYx4ZIeLUGOrc=3r7(0stzI%xV^-_obrrm)2 znCg#zKK_5iXfab@aB6dhDaYG++PY#Xf&y}CIWjWGaBZ_S1U=2EOeS|!aP;__+sIMS ziVDOzlEo=01|kD7jL4J{SKh2k)fcgpWpeCBn%$>)6>-noq^O%rycG#I7d#JVW(D;! zLi~1+h{sZKP)xOcjj_{}P1Tm5D`(H`p5ebg+r1q6h!|-ZF6cQE+NH3rvb2V=LIXk3 z;&KxzhT(2*g`l9>&>m`36JfY>+w; zoH6txeZ5G)7#aB+ zb+DHlN3~w6ooSA4Xd1D-$MMs&s)g)!mU)o)gYOUvbV*wNt{?_j>Zp9eZDV&}Rypl@ z>>@Tano3Yj3J1KfySd7IPjIv9?;SdAf?10@=G?d3`ip}UxE^{zQ(N}DB8-E zej)M;zF6mZaW>?(#8(PSlv66F)^7N9S8+Ri0J()|^~%a3pAQ5AGA5L78J(25Nwawl zS*1xFIgnxyePF_HD8gKsYRW3kX%?l^irkZ+*HY;ut2Kc?MK13Da{R})LohBUayT7%zlIaQwFuMNKbLJ_{IJy)dta}z0CzXoP z=j;+q!3C5=Pw9h(M5xJrE>`zRjW;}%1`9?A1LjKy^wcO`!FH}zt^g3yE651Zf;tc> zXFvl5pchpgsmV|$-Kt}H;3&|7q$UKZFeZE?{3IVV(&+Sr%0$f((GoN9mWHzprDLXZ z(hatq!G&9n2g7`+K|x*SOY+l=qSsD4;aoDEP|0B3bE;TZn&KfYZKJK=H$K4mAl@0& zjUgNsxpdhTcp=Xe*udY){o`$Gq{mA{0VImszZ@4%F%!zrgTBF-{8= zri>!3Tg~T(c+77;YguX@^cC=Jf^jzW;`Y68$);-cN(PvKu%Y6a;bLG-OFcMRY*M2J zXFCs9A_tOpId_y4WrngSq_GVprvtf)=AnCsnnc3K;m9Lce)VXgXaZ?cMe*oe@oP#k z%R+(DkGDPlwO!_~-2-V?-6s^$oBUM~`Ry*`d1_#=G|D@w4Kf^So6xivkiurY-ToQP zR`8DF`^y10zxDR-vHw6JQZ0zRcXngaqEas7P=I!I(?{Qbwl5_wv>0jdT;NW?D#_v< zz^->e7_I<@-hyi%C-kYeF0vXk!tb&KH;=^RnG$jXILIhq@E-23rYyq(P$EMIgYHb( z*kFtWuBFKGchipSB<*v?5NKND@L|Zk=QmcSF`(VKFcmduKx4URIWnm@4c%yzi*BA%9btEs{89^)v~0r7h$0-p zq5TAasFHeHHSUDnp9g!lt|tbdtO62H5(}2YhYYAb#wd$lbG5Rh-XauCfs2J@3VYGg zK!F9qWlevujo-n2&ykG#>5)mi-uI#i;B>YiA2Er^%oMA#2$57-O(U044OW*!OdkYZ zDudh2O-BE63ZQvx!dL_UvNnVzys}L2Jt0=$APB_;ZE*g_w$dL{h9`S}+HCzsC(*mZ z_%V=byD4mg%?~X&ObvK9UKf-Jj>P(i`90T;b>K>}hn}ojt~$2NOZe^i{n3^*r((H;rvHYHS;gZQDkZ#ir z^`iCI5TqB}gge5301>l^^>|h`TWG-Kz=QEGkL6%ur@6$JUVZKZ0pZT#I2ro?_P}6l zno-UDP+Z%`-F}Bz!ms!~T3Jg;o7q`v{X#vH6NhJ!sQu#N`H%w-SNg^VK|Z+9n+F$H zu9a_zJ)xQ~F2cM3yh*(9nETUF^KdZPh=xbtn)*&&X&k_ccUA))6GwfABcp6T2g-+@mZ&7W5_Rf&VfX?9n5Yc86R+=ee5-)H z?Z5qwXVqo{ne;OEAaoQFWM;fdtbzX%Hmryz;XVMvHRSh{ukh`o(hsjmvsd|amyH}h zBP75-#9Noy8kE3B^;cFAl=oJz8%e9Ti7Lx1)HzZ|G1cQ+ivp#4J^S3i-A zHgA(?t9PQs5dB6(f@_;Z2niT-5qPj2+V%}z&Hwb0PMXh7KoV9Gtu;ux8l@Z7A`!Rz zv!^U6?#xCcYhmf)t+2kl+!*@@KaowBuVpB$d1j~S@NiQV^)mlh_Nc+13Do-DC{$L~ zmzx|M^?C~tU`dlv-LvJe^5=f{LAOObHzY+)o&d*TwbNTqE0ahiv^k9fNSwW$XNMluCpAuU~kzrr$GrKn;E zGsQBgnRslC40UGD_HH zxU#ao?n_29yVJey@#fq-!8!Z-6%8Y!IFkc%iQ{w5zuT=%5h`eai*!kcd`4}sUImxV zd&HRO$2(Y`q;^0S#7Scxy3+<+!jX4mtVhhBqR2HW`&tO&H>7Y|Wl2!d_>YOgSr%wvpy^DWt-r87Zb#->?DjF-)Ik zsa4XYbJ6!H;>27*Kuvh)*XjH z^=tY!{*a(7H%(^gvoJWrwe$;r^-GGY$y z505mlgn++QOikExkYoUODqW=KBy#I*@o+HVH;?8*BymSCHrZpubiDY_G)NB`_19veLkEma|A|`Rt5;iWb(XUPbKo;GU zT~n%zecX|UFK$MXw*r3+G^LM_AZrte>cXS%ol?XCFyNSEIsFzOm5`I~>)TV-a4+b! z3=YyQN^-zM|K8dS1yD?`Z6XX{W)NYq1PI24VyDD3>vj(qXXt{sL^=iU=o_0AMWV2dhIUoijV(>!6y$i+*G;jWE~>Z~cDntTao> z)m%Y>cp)oSvPJ1RfPCU?(;+XXnmH&(vgPlRFV4iq3FEBTQ~{KFtq_w2d*ItMR7a53 zfiWO8k^8g?o+#ixDPK%r7Ddq;;r_&fw(xLz1jx|I&nZoZ7-u(Y7cnrSmv3_HD2fvL z3%e#k4NamwWXWEI?*9Mq86*qP`*F$SlU`zJ&5}Heuo+_oUFC?tG>gf%ha9kR}(AVOmq5@p~p6qHqTE#5w(4_Cu@ZqQwFeYEVpaJj+Lt;$p*X-^Y zY-3QB`;L`{%0zK*mp(v9=niWKnj68&4lnq|?c1DRB#6gmo~J&QuVvLbSzTwr z88S)efC36v(Vtm2)nDj+?{XK4Fuozqv54CbIp~{PObw=h`I?RQbgbEf#e}sLNI+(y z{$=pHE^&a*yLgs5lBVt*`P4AC2nS98CW26qDCt5clEJfReg*F)N)nJf?R%a;j%uRF zvnd}!?7&p%wDPP|OfZ53fEh~9FXbijC~82y;p7sbI8Kq4=5A)BzlFW8F-B{VO?(;4 zDM_w{;W2l3g|DCm5ngCe)DRZ{C3oBTB)X=b_hYiyj_A(KYwDQiX4aElz-pP213FE)?pD>&WhFU_hz3L-#ibBEKeMQqmbJd zKWM!!acBbE*DtfCKBZ}>Q_LA9`JYgXzLBHBA$H`!GrfS>P{T%W;-Fn>Ss9Nzrq;ZP zdu_m9Ruk$o--GG2oh^)S$JhNIIDRCTt5BH#E*12gsiaxh@2A(?B7!kWbea?-;SJMa zo6jF}_ryr(8C(=C$Q(lU9Yh174B!&uoydZ>$cvzxSFdo-k6%udiDuha%Gjkg?Wp9zO@n>Z)W z`ZEbBF@zyOvzd{cg{w*E92?&o{+2N?-adoCj(EP+vgWj8CStB^&i@@5+2%uG-+Zg5 zt5b$`reD~FN{1Z#hp<1+GObb8@h_s!m;d=pQlbQ-V@lH8AGS{2kB}w-AfzP}Hog`| zs5rJwa(ttqNT4-!e^KX-^yR})sE z5QaCk=^gb-mH9Y-)W_pQ<6ud#brxk~5ue=vfz<#@oBVG6QeszCHpG`x_oBaz3b#vV zN8t<8e0@KZy2YBVYk3>`wAf*?5S0am&)S6B!QKw|!X5z7(jBuNx=w~L}Z-ag<6 zaj47^0>EMf2W1Yk_I-?&Z%s52TrCTKp9~{(MhcDNU;IvycJFjCEt~$^wB*3?@3+=7 zg=TKRUf<~)!>Oc&hLtVi#~shnHJX&LZlTC8@?_}PAHEX=9c3(Z7ynXoH9JOEr=@os zkB9M|1)j8b?u{wM;9jICQ3#vs99p}cUg)(`HQXAU*Do#IznmT;yzL15yCh49DiI2W z9hSt*vNQf*Y4>6QlK#Pm?uQ0!BV}dBpU+9qN)4WgDZe@zeVt7G%p+VA0!rC`aX>Q3 zXg%Rp(&5jTj0Il7ib^4z6F~#+UMB_?3w#D!L&%5hI%~>~nZu)6I6kJQy`TX{S3q1s zl9KecdooEHqBF`7WH55rD^|KI#Dzwo0|>}g2iR@4GI)5}zMN?DD)5W5scbaU`8x!( z=%h)_2J&w~W=)Py5V4puijS7rHVgy7#NJtf&YxKsCb!mP+%>Y9cLy;wMWKMAIJbd! zx?57c6dBmme#;3vDdXcagy+>5RMH*L!8R)br*IxQa|IVQ`s*}~+JbsC#=0iDi_>?l zi~!(Gd1lE*L;Z)2{OE367Oe`_VErnD5pQ+sertVOvEW?PnDiPF38}G6TrrE8+Z{AK z-q?T=B*f*hZV!!2(o7Y|@3ya^X1!r+?X?o!x02uYa@?qyMeUUPq!M7yl~4Z||6lZ7 z5Nwej^1#8Rb932KU6L5+Z10 z2PvJ9zYH3h4U~T(qEK9&!(yQa2zK#&lP1}r>T>K~!%7_CG7TbNOlOv+X<3iqqC2rJ zO}X&m;!;_|+IWDoO8D|37u8ww2PH^SPUyu50FXxj!v@bhCg8~} z=)Bqh!ewaxQ%9z)Z=v%xWC(&~AThm2yfBJO`aFM@s)O=T6l0gVCMpdzw3s^J2q8Ld zIyUheYN*X};Uh+n<2K*4cD*akga&{ZlS@y7*eMCd1Jac$4a(|vPd+)`azgYfA=S@BqzpYuih z&(StYkj3rEUnv!dVFsu>M^ML#_M(ZLFE*uN;fAk(r+8mGlQ*ZVtqX}hAQKhf&WKHU z<8mpsJUm-DJZ;c_2s;cKIX?)SPGO_t8jAFTSB;9rrmCyqIoH4fTH6Vr;YKAbVf1tg z06_u$?>)8vAO)6rvLK)34AB9R9m+m1MRNPI~W z9-x@e@`I^4gR%M>nGN5(@xZE(hI%b8iIVnm>Jm_Gi6D{6i zZDTAY^t-ZpQCn?5;EM8PRK{CX8fI7t#n-|pNDA_Jj)r6f1i`?mX^rffQA|Lvpf)_D zAkBobR!YyOgCqvWS3?X zWF{`a_qCH@sr$v-pLAqYTYp)`raCNAaeU5}HCn?CMo*P+oJ_3y9uu+7AY$K* z7wiFfaU_WdBoBwV;XM=wFbh)`!-qlR8b618%oFK3Oz1jiKDu9e+%LtX#)^wbiCS#< z_pwzqu&i}{;ljHSpE{Dhq4qerOGewxC_j<07o>)QG$5wn>quh#s0Aj4-`Nbv4><-f1%O^?Rx!H_Y*=z)Nenw8O(KuV|SH#EOAm~YZm zriKL!hp8!JmeY}q5jpGmnjIL%b26i zoAEZe9U*WhzuDCEMmjx) z>YYA4C{BKuTaN$YpOUp*8B#&^klziIk2M~G<(QLzgA)Q&R8TYQW0d^8d`r^RKm|~@ zthnNX70FiE>RXxkPh`c4+C=Yrn@>5OQQ1d%zo2?dl+?`Tf0XdQq*mp9okC8gf`$?T zKqgsL3Lt+3HC!Iti3&WZ3jv<5^thP;fR3W^9Deh?-kRwkWh-qfb5;OAq_F5^hi|J0 zw7cwTf|C-X-H1ukfFv!V|0KU*|0%(}Aqw{*2zYt#=t!7KBkDf9O;bdX4oK_3=1EI7YdHDBRavSp}T_Si&!j|WYOgR?=N6I3R8y{RwAG`jSY{{Kq&XINc4?YYU(q1j zV!xoeNZdsDJ`a4n8O5H&b$Z%K(;V*nTWG4Z&yk{l1b7Yl7+Ytqo>J3)*~57*qdmlW zu7}RD4PSubQ^km=cQfKmxUj1a~4(JaRtVhQNyGO zeMhpc*=6&vA@em?qCX6)I7L*uVe!i7)KUIV&ZCWDF})a0E?)sHCv1_lcY~f98h&cL zEG!6CrNIL4?l~en$%;g1K|DHMn%NtisnC&EvJ2sgC)cHRpTSB3GUum1jrv95Nk+sS zLo@|KQ|F^Nu=}(;=``K=6s=^nU2-nSOc&lN3q8dqQ+~`116fAKndGK4qD<$92a7C4 zq&U2ma(v#<1$nTQbH7@aj%Y#!3mF- z-)`+xY>|-ooZzzpp+$`!So)S)`UujY?^)5m`04UBNe~>v0WKb__A#n7WkP0sBmBuf z@*E$b=9-k$f|=3^HyPI<|3s&8_yEqw+Y36P1UWPA+QQ46GL&O}je&Q)DdRhA*4MsJ zS;uDM{p^L$*3({EN!^0X*;r}vuyX8L9O1I#{C8Wud6uK3t)YJMx;GK+X?MTEr3%&} zH@0%lU=bph(joBy{;_90-T8NO6tgI59(g|vmM4xvK{T&uWdFdNa3Im!Sap8gLEvxh zgJHWDb!$e}-S~sV*FPenOXf_8$s+xGcg1;?hYJV2(6w0aFr+}jUPJNqZb6>4U*vmr zITcGaZLT6q!oRMUqox4uoS%dprxggdR5+i{DQ%|rmkrxY3s8XdU@XcNpXIz*}3T`810xWe5ZCed@)Y^HfBQV7-{cw(X${x z$OwX8kYn&ZI{hJyXq~3Aej^@hdkwX^LY8kLRHp#AYy2Ycv7Sm`S6l!(%3;z`QOeZU zIB|c?)*5U?_P#twskG0OGL-@}5?+kW_@tT-*UOqSmapEu39as&OMrP^hO|R8;E}T41#4dG+S?zjeEAY78dfo4^kJgNFpwp@`t+am6gP5TX;QQsZ!;#w3-go zc`_bzzT$?#A@QL&S7gg3lONoab#0Z&5EtLA8C7v2e*J+?oLL*&5$YqNxJ3gQ(}KTa z(u+bKOr7AiNjhI$OXb#QfXpel^811LeRFZ{0r!tA)_0G0!Z(GXrJ|st(ruwI-n!1bRnNFp z?>aW1;PTl_Wt#bi86mA9Yi4Q$*WwT$l<&xjtbcN zxm;?O^YJ=iyS=xpoTa3}-oDd%o~?7G{&>&(?)566z7U%D!n8nOeG$&?J|vB__6VwA zx@kX{$9dI}=FD%tAMn3=>t)*H(>A&*j~?QIOSJWM6f<*Y%B2Qo(Q5M-`powFO%7PY zdF6U_-tR7Q1n;aEGZBPSGBq=R1L=HD!}&g+k4=j`FZ4n7C*Ne90_{}Wd>ymLho7;| zS`#BjZ*-+5kg4wcKl|}E{T!rviuqgX_BGVW%hP#cP#7}RUd&VE#YZoeB>9S~44mwU z{=-Oa9D+SCg;u8q2OjE1xeVj(x`*lGDeG+S!;|t1zD;dg*Id~8Jb&6f72((3-U3&C zCmbW4WPEY4*Y)P){i-0ws!H|cFZlbu?<(Fq+b%3A;CWfE^L=~2$@24LQy<}e2trXo zTRs&TfLdCA^T0Lre&;&Y7<+c+adEh+w#H#y_TvS-JF}M2zHbGqd2;39=KJc7?>SlQ z=V_aFS1m2pY|hi=z1P8cw~+M6nJI?c~D840WPStgi<6tF;}gO6QY#zi9> z9_CFw)j5O@9~a>>RZw1R?VigS=~|Ffm8GevzGI0eX@>mjJw@Q< zCsAs7eQ-fdN{!zBxk6Jcgabj1(1NYNpw-?EhnBlC<5tTmj9_^gix?O+gT*>pqPDLD z3D|3EP2Tjs*pk#)lzZ*Y;eUFIkzd~%@-6@fPRX4)jl`*9HWT6cJb{CsRToz9X6YxX z(|>Mv`}s}4Fo7LR#nHvOA~drHr;iU0u^knXjhzCZ^rpW@MG-wSgEIo%Q8NV#*QsTt zX`GO)m;dQwJOs!jdLRlUHOPkg@KAF6aA0zCAdO8nZbrbdOb!311t?DLolMDZNWzlJ zM*^ka^zDLuy<{v|Psak?dkz&|gOU_3q5x@}s*IpuV8G1D+i~QHTjz|4rq3->DC)%# zQ0H$y&DdT&>AXC{wP~b+Qp*Eq883I02v@pjXNAbG!xXGRwVjQTm;n@GsWVNLS$~uM z^-QSJd>7Sv9vd2ab|Dy8Cz$~hrt!?&C#L?XK%eu+W~WPzA;r>O-~H|igbE0gm=5j+#4Yq1l>N&RjINq&|1>NtWdzu|<~GqJ zg$6o~MTS1u^ZVVynW_4L;&KwRb=8Wz`I}GpTB?0*lP!1=5j3Cjxm z?!hAGI!*y+g~H&Z{njq8$F-U_6Yas;SK;!UZE_Mp1SL~7?H%8%->|BNfGsD^)s-nH2O-A7hAqu-gAJU zp+0GLKTu%H>&V{z1~v;|@lDNx8V!X4E7axIH2U; z9czRDYnDDbbG6I4DWpl_4%?o0BX#+cfd!=#!wyC!%sFAE!N3X;O~=*ol^3HvYJg;g z%RNCu1J=%cBO0!JBf;@u)7Qso)YOW$4G}^I?9dht>E$`hD1ASr65c6O{`$VR!!Nya ziCmXtxb=S~IK&kGrc0wyd;>3BBWG*)o}QXIjFk0S*siM?6zj1471cbtB}ThO|5ISo zYrp%`@sjM(yn?QN26Ux}G0##`vB%Q;Ndu&QVYOBAhTgPY@I$>Mf<}_Lc$-^1Et&M2 zVsaxnySu`(IK7ORv)>)Tm?`GrVG}cur#}Rshgh4b%9r2eW-0|LI*k>q)c9Gr$<+-N zO9z`&7Sgg(VZL1^x^ySh>L{v<%Hc(G zp+3vUE7|zd$hdh(9IPme_!fhAsP6j=;!4u%s!>W=rn0lp;izuxIm}C9mX3s+R9NG@ z?^plyf0*Ryh^ZX^yyP+;JwNG`X_1uc+dR!ucizP&)o z1du}u!3z2`gQ%wg`m{)uWjT1?JR{wxLQyY8>RdYRED=R0J@42eEnX>uz| zk)w|sdrnLoBuWUXFA>z4lZj8I*K{dKyLJ=8dtqf;t|SFj{UjOt!SViwEh#2b^55w! z)jMnnt$mxLFSl7oW2J8q)4@_q0c}We?=zkC=0_|JbF%zJM@jizXKv?N*X9}i86s67 zEIRW3Yvs2{P{(mrLPW>Nib=#E=yM1Ou(e!WZA|7kXyK^(5fSZt$XMxVXI*LS7^^R! zM8YZ@R>G<;C=rt(kw3JLt}M+<$PTDrZEu6!II;9{E64`$37(f;Gi z^vj!{yZ)*qX$owBVpn&4{?QHI0(R=YX-sb{ zF9Z!qT!u)Cj1bTlpk!`KfQD0^?cOK(4ZkOUPg3@jU{>T8-<%5|0IpMBC@xn=S8T3d zi6Z^CY~pMM%h)c2uy_i?nIs<4t6d>NWSZK*Mv8?TRw;|i=^w5cV%R8-Y%d>rkA#x4 zFltU5>Yu6ymC_#|XyNBW{SS^3YVuMTPEotTm>Z8F%~YR^{ir~?quaU;K;%!0`mbwM zf~GG0eTcCY5P`|gGRgEIVZeg&Gol*3ho=oa%%l1Ff`1;R1dv;g6+i4XTUxrVrg6OB zhGv4}Orync=xa0;b@P~6Xe7%KTOB-Ir2;Rv*Rv%;(TDTPSz7!I_g|j(wHBQx(>;2Bxz5}i#WLQLA0GW$S% zovLh4G7Rq(F3Lhj!WpSf0u=K<`PVfaIvFvD1ezdv-))$lCRbg4nLx5WOfmDf#+_W~ z?f??V1LEF{J`b8fIZ>KWfT5jgrhEMLO?z^=ScRHQ^)>_3?8B!p7l?Ph?S;ek-(Cg5 z2{Rc%A(f{xbhk0NokfWY8@3YOG05e#)>_K!{;KOof;L0HUUi2im{4_Znp6{KHj>tn z>ipFYdk@t9W3seG=ULGc^#0Ge_}joSLAj*=}89{lCa z#aRyC89Xsj{T>ybZZPG=?OQP(itoJ=`eP5MHy2`dqx zFJ=GP0e{4nu+a2pHdxAbOmFsRyrzeq2RM7bafkFG`~zNc&Eq5IbTeg73%py2zV-eG zz+f(k?|ovb_*Y=!o7kw>Kn~8L&wD5!Ad2tvkN<@xufT1xk=CK_K2L87N8k4*qh3&K zHzS)vFn2a&XMJzC1_>3WIM<;q1CzrAqfVDzmm@{O z?8PwWpho?xQX_^#N@yW^fYE$gE%Qn0z!rIe&hHAj%=u^ggzz%-a1%Ec?;(Csv_EjG z1w6qHlQECU!i*hm6z*L4KUGVo>e%I4k1~=$yO03WB>4$gRkoFmsuD{>3PkcGYD803 zF?14=ebo>#3X(}KfFYvle7Z3lFTd;k^8~ouq5;HABL+ulZ)-X%p%8m9-F!j=9Un8| zVCYT{=s}QzVyZElQOsT!`!X$K2qa)iBm<=P@-UxHYg57Tkssg4V$N-^Ik85e8pnm4 z9D_U%4;EJWX0R9*JJ)E8wgP%a^rsx=+ZT}-0R~zNv?LnJ>G~Sz<9iV-;;M4_g0WLg zmAUFlvl^tFki_{vmC_0bsH&C9Y^Z6<=>@<0AqE>v{s<)1iEE0NWre068!HNkPs@Mv zgNynbeo8CrnlWZb`ZJLjJvFfi>XxFjR z^fz&flYd;5LAKcjoPjxA^}i&zIjZ9|%WAS)_*njO)A6Dtjr+Lj40XMxZgR|)`+pvH z&gg&-Emsq0_o#TBe^PzRxyUg9V~`v1u{M_MchbmSDe~5cE#G!cLox zVEZy(e?cT>JX-#!@H4xdo9Eh2=u!Uj^d||SsaNgBrTnDYC6^8QrRl9U}{9ABo&+AVPWLP!8!qC zT)dngHdrt$!gvRsQk=ifjmsdHSH(b@;%qSR5TaYco(I(6XgzS>q^30)NZU7YnhTt} zfdWYCK8iNSTuQfkQ8CKMG|a*ZXGpy?g!mzWm4gY4L5! zB7lO4*L82I+v9h;j*_OTah;Re2;Fcmn=J8PoZ%3D01$?oUY0;2bg8XLp zcjWp`A#|4k4N0!4^gb6OYOJ=)CGWE}Yo*?pfLew{76M^Oy?Fmy*UBjnVRy{=#&v(S zUiZUftbP_oY-mxq6Py?n5B>L5>}!DuoWJ@+5XvhrcKVqCO4lXCWm&XG4(&} z3-d2+*lj|ygxD6eb0rlINK(v+gj?97;1!ibktZFIQslJTWYl8G#hQYnokaupi1bDr zgYP9CjT9a8ZYqHK;o;@c`upg}1rBP&T@bU18hutoh>G|Ox{e3b3x&Pzd`sTj88_Kj zWAnys-viv^;UEmDWoFDE%jdxWhA;2W(M9&1ot5ioQjUkgT3_UL()5E;fPq@-AHCM) z8&3#9nbLB@m@lf+Ni#eJZeJ$B`JiEV$N&;i2lj-oo9?OuCV{Qt#-t*ZDdBlI01Hyp zPGf~hUQ}7bf#^RQZ@@002Gu}PE0pM!cX$?SS*H>MZSkS_6c2hxE)VU3wpUaoO z2V9Y~+K&%+?!Q$KSZ@tgMW#=X4yxNt0-c(YABCYY(^z%Gr8vV6Je&>n(nB%MQAIU} z--pGdhh=gu16mM%BS8j0(#k@2l%GUQ(*1~?upDlb_$Qb76@yP%{;i`xT?XzAAQ+-( zybGz*2+AN*muFH^I)DutRm{?`8;LK}TB-Hjz^`pOxXr%JISrx^+l#iQBl$NjnXqmY=V>-LcWJ+>5A2^1hh zeT?oVRTal7Bg)LbkV$bAA~_zNfEr^SCb8E-xcK2jOX*BKhZ~uPM`INrRg9LVt-q|l z%3NzV|8EU8i~6nvymyFwTR=>W?8}o1%rl@znNq2Y`VLC4s!JK4swcfSKv%5_EIa6t z9m7yK_zfsM(VFjTwZA$-v_LAP^7W8+2$BBz3@^Fi5cRXbb~9&qK%MO1=a8|eT~t@F zI44#ACgmVTSQ{8!xLku&j~^>Wmjdx0%dZ?S%hRc&>rdk;i-?fMOMZNelR}^h6|HMn z@8QNYbzHRu4%Mj73y`dS+=4TeNOJ zlvX#GZTK({v29^spi7Uc>UhP%T(^nP6OaBcmHR%7BrQTFK#??Jv)|z+>na}q=C!jO zsFr}ppU9aLh?$hmmEV;uWlUePHXOFBvTaK|lb9l@S2{f((OMUiWls_!2;O1To76dM zJ{(g9E=>H#$KOl$V^<>liDeh!k|oI-f|f~pNb=~i$fj+ZFj7?`oM5FPs7;Br({eo( zvL;46ExuQlni;ont4-{OPJAL3nE#9l;TbD1Xjev*_^Oc=VRCXVGV1fb|&96 z1+~(G#>Td~swSxnaZ=CosZ`v{o}U7HUQeq}%aBEip8RMIraxfA$<5J@xF@&arlkX- z;^M#;9H5B?5lWq4x94DzNoR8WBYY1z2x*4s8rEykE%_ z*j{3})+hC>R!A|XICnsrM7_5)vO;|?X&z(4<4chhO_p?W%fUW%^>(9`!x^(fMh_=H zOvh59!e5_UYOv%%W_goy8Wauy1~t&X@te|#C^6x%v~9CbDk(X{2xomC+C@#z{>==H%+V!9 zo}ejtG4%j*(|Z@+J4ccZ4*5nRqSyx9a~w9$mYoDew^+lBcPtd zoSH{-(|?Jh)A4jAPnZRey4zAYd0Yj zhZg#q?eLcnNUhFKZjT%c9K@7%?wu0a8@y^bA~!#s%fgp?-pP{w&oQCf{m(U2Uz84Z z#uogDxa2@c4%90Jt1509zq_R-9oS;s?&S_`TKy*Ix%6jEB|_hXDRXmDmdk-4Q>4j} zbW8#=MdS?K6y`mcgbotw485O9yVo$2A4;&gbSHm#%$-A=kd`Mkx}a{7N#ge|swQMx zBnV;E2W7#fD$7mL*%KcwY6Xhy*ewUcnr3*KgHPlg3IegSS%fS!-mzD7cifue0N4u5JEp>eOU_$qg z=Yjo2->X?yXY5|hk0$gr`B;L0WrE>YfT8+1TO*fVU?x1+!tGV#a1W`8BqWK-b*tgIe=wek3zO>Lu)81yJsxj%z_UvUU-OyfAqs!2}E5(BIX+5EDzI*2h zGJK(aPExi6j%+3xQ2VcYrXKdh9T!P>RH-abxM3Np z18XT`{vrdE^|o|p6kXO!N~}31uaBk0uZ4m&RzA*Z+)CuAwv_yD!jYUr0!|5}e6K&< z#1c!?)zu~$7*IV&WCP>klkSr@&k6Bmh(1*i_w#z_37rQT0aG zTAq48AucY~9k!u_UD%P^(^OD74-YgapixEsk%O-r6_aZM{I_hCLPc`mw84-5>YGN# zPv`y&8bWm?z~%p-lT+u_t8QVr9Q0qVcQ9Q)&OW3x9%Smi-uhT4xVHF)#;fN#|9pE; zQ)VjbhM3y%4LV`6b`wiECLJqH?FAegj#jX*4QiFr5+U^*fTDD-suLl8O$Pu3gDNGE zFGmfO(+QSa!Z?9azAuNXZjH!;$}zi6qQn9HO1sDJP}QB&!N(omw?5sVQSV*ysKIKB znd2{|FJQ9c^(iETWa@am+w^v!tM0Sqec6*;%=$1{i4sFpZ#D0SSKJpO11!1#NeLxz zX(4W{$j_UYwKeA9Y>T6t-wYO9t&J}7d{Y-kHv(+uFIDGuzE(DOc9+pRwB3lHT@MhP zuM+Ybkjn{&Rn#T*^ZCiSA*6P!@I;8tT9H%O=PyGZUL?A&ji5XRjnz!~^rZ>oX5tqr z!Y{g(R3|A)V~J&t<6{atEDn?pKvSE_D#e_`!MN*^l-P(+tELH4Vp9O8T?w+9Q1T;K zO!0bkC!gnR;Z-oDV;P3cA6U<8u~Z9}U(vGKFB;z!2kX_DT22vrOV2}TXp4tt-a@uO zyRxfaM$(SbWc#_?OD{vek0z;PWYC->*|zXZh|V^9dX^l3Sbgz?F~#`)l&1?X)5ulJ z>&2v6o;0}pZA9<1t58s*AoACX16QdN*FsTeXT3c^(J{2P6q5+L#y^f%Jd9XWoAbZ8EX z?0vd>z4esf)8#vt9FAJcQd7+oiw$v}Rh63_ZgHcfeo33+prSt+%Z}~SX zgHmV_?RNf)O0#J*l*+T6$@k9eazsS`poz}xenguzWfYcKslM~B?-FeOzKsvu@NjV% z%~V#Bsl2ATH6wJiB*74X0!fOUUwje`vFXa#<4m-b6`613$x&Y(-5uQ{-a1r9?MPhoAVqKb9(-@1x++#b3S?P>ylI@nQeA4CEk=Z zuYwAp>GHDQ{}iY#{P6DR8X5b}y@V4PCHp zFyp(Diwzs^66?g=GX<6y#lHEYD5fsm4B}cm*$6pDZHkLSBDRWCXDc?&)7{5Mu7E?2 z8zr#HxM~jXcudg4};WVmyqbiL)Wi)t@aJu44{8Uu-%*UeaMX<)87ASOPk*zXVS%qy8;u5L>#UF zObc4>7K|!U-%-t|2$%%o!~+K*3aa-B>A`DQHRz2 zTEPD{HL)UYm-F+rdMfJ9&BQ)YU)gWW{c;?A>+hxAyVcaT?{Y=l!S%*Xr$CbWqJmBT z=Ue9GCHW*rjwYg2LV%o|E*Nw z;^{bPT6-Skx!JArR)R+9+uLsU?>wFMu9rhe z+IUO1ujW47i4vv@UV1p<`||#X}!^4`mytSC)iM+q2`n7@mydI!?ks3nPq7OM{`z( zBeD9QWAFeMydrzacdw4Czah0;Z83tPd3nH!@}z8yJYnci=T$UFMUuHfxnoGjH;APe z2OU(wpCKo-Jp6o6Xf~m%AVJX*VD$9j5>?>$oBhlMyDa_q=Q?-#doX~m&+FpgKtBO< zVM9O|jaTfsVVO=UY_Q^lCZwKT5jSGr3QS?p!32PRjaF85xDjv0vGx2a5t@XXab<0v z5nUKF*-8__Ye*NhQRl)50+N~{Lt2$XWR_LbrL`8H1R5POg8O!WFLH=~(T#^tJt9Ns zepE;SJcB`8jJr>InA<4K5RT1TmsEh8Qyb392=HKHY+U}{&h7WPKGe4Q8|-w4t+YD|vdu3)_x-(Z^ga1d$-Hx! zDg}Q!{GS%UyqU?|o~oB6d(;0=Nq_it#J-bV-ClRrkPj^v$ns{ZM&r=f(fXYqKSoSE z5CHfd-3T1|i#fUelh^3mVBMwxTXK|nO~Vl=JzM&p^kF4D^8q!*u8$X@s4OREkYkTO3TD*8Nxi^9zy3;f*)fk zJ(rku9b+ru#Wdr_ja*U*VO?c`A{T-5q{>Qwi1UhfIDLF!7;w}ktuOvPP z8D9i?(KSJ(IX|fcz3wbn1^nT9xUrv6a_|{b#hENUtqDbS&W07SS0#L@vH#)a4Ri`= z1Q!0n8y2gurh7@hWyi}?8XJRl%{ED5jLp-(**a6I;2k%trlYOl`=YhbNW>r4p!Ysd z{=zvmwz`l`4CBOScfU1Y_UyK8CyUG zgjMqyD|E2|6;_>Tf8kVD(IFl!<0;r7080AZ3GkOZ9pC{R^wg`hwbJUPND8Qj?b>jU z_=Ftv&E%Wyj!FYTD}9SIQa_8?j#MLWhe9%Ud@kQkg8JX<+H-6~L>IC6$VL+4+&i2H zo}`5OTrPZe9Oy0eTME1^ed|gR##O&YeO0(TJ^w06ENCp|pz6H5Hu{F4#}0&t43NuC zHdqxjdF<`&QW8-e>BxACj--o(%)w>y;xy!cHkyj>Y5^+hZE5m1yNIb=U)L9pZ9C|@ zc#rk`ODnH*G4$=_LZU;0^g`tWfWjUlJ-#qlrI6)_lE#CVU0u|NrSWklS!MF2GN)bY zy6QQbpBh}oibLbTGtUu#bzf$JfF4It6Y{$AK;LtQ_pHd#mjq`O!zeeQyveB7cEz%Wdeh)J- z#xZ^^FSjWe6W((D#wzh(E07_tRZ+r5U#8)F9aKN+qdMhO4NZ&*El$nASTpjG{QH1{=((9lw(^(SPrYp0lbM?nOK?~9 z<_ROqWp<({G!19>Y`>b3agH~-=)$MH>a{4*ohJRa1W|88QBSBzb@L-ofoeCNCeS-V zOGk}Mb45XEeyqm-bLEyP*X-n_HL^iq*=ItIZb6@OeJ)Z>ZXv)JQQLnzncY=xR#tbO z`LfCnq)SF0g@*gVkvI-f2wJhXawViwpPF{O&R-{R?1u%^fPC&I#wsF%G-CleZ52&z z!;5(Gr~i+puZ)W0@7Z1`R&;?<+@ZL;yA;<_ytunVvEuIT?oe!TFHjaMRw(X^yD#$Y z|9;MU&VHCrGqaOlk}JuT1l8^|uG7FAS({Rl+r#a0Bp^b;-MK832{cH-SfWOaZrT>3t^$2P6vc z{V?CsplAFRWv8PwGGOsz>J@T^Pv%o!VMVjmd2r8oZ|@X%KYZP=wzNB=lG9emXg;|* z>j&~MPxkSX!(qyfWL;xB-u^TZSkXfYxVeOBynp(R=&i<^ zDmlM%yO%w)30(4EVnrj>&FeV6VN(-t2@S|S!T%VG?LD@OMF&q&r~1~djt0%7aYTwboIwa zot@AbDK=5J8~=i?)dEOXZlvf#m!YaydjeMzSz-mN0~`eM)_uOp+=u7b{*;|n!TOLke~<(c%@a<)K- z?+87P^e=bt)6R6h@6kHGnHd9&79}yakQZq8ukZRG;`P*aIxw#IrEeYHH8 z5b&~W`~nGqdXgy9DrsA=iOzXWLZ^C8NbQzFB|-t?Kb_uQ&4kEpqT~1P!h$yU0Y`Z~gfGE8td786^=>A#`Hm zJ5GIJX+a>4^fiB2ISw9$tU2%SRFAqLp*p8F7H5NtvlRepks$WK5EXwKCUHI>*jPmS zN7fd?OK@5aDAzQ)%vSNQkuxe!B16oV0zbxKH5;Sur&I`!)d1?(pWCI>Ts1b<^)~#& zNB$OTx!|bO2|~)yk7?SlU;cNouwjcZNZw%V>$mkIO^2P!Cms9l-46jF#jHkd>kJLh zL2Y7HAAx;lJprp3aEjAk?m(Nl=Q27z5095sm>m%)<0xXMVv*+82*|<4KU_C2oHc{k z0&~ZQjXgar;pJnb^6}h8_I{creE;n>+}SzQle93ocl5TxR4Xs^NJ#8M1gE$b8LD> zimI9_v?VPd=2D>PJAmYJ3-*A^V+!;s-+}b%2y9_z`*xlbQo@0YZN@NE!IcpMdOg4o z=qV`^Qa~~PHTg@e+mC++#wo_CB>t!cYdoMDk@D-r3C94yH@dN}3lOl4-3Q!-FCfCL zxm*T|kZ!c^bQH;F}k8_g>+OQR!$CgO)k7kFJf8$$?4@}Eviv#Bo|Sp7baFgM@xiu zX__^E@5%=M`9>d0h%xlH!?dGATpVr3INShe^|!n!bO;*wdW)RQ;l}C1C7gUzj;zZ-=SD!Z?}Bp z_I_1tczoDNU8eT@E-P=svm3bkk$`>y$H3kGm$p+9;^>v~l*=K~mAKeXZ6fnm4+Zxs zppxhaO%aEo|J{5@?BS#!Kd_i5?$-1#aOdKh$gW<}{Z5vs@iR!eRTyp(5 zoVasRY)+4yTN&ZyK`rZhtuzF! zFdX>H7PpOh*13JcR%@%{{=0Wl@l0B2X?PE|itlJ2jU+jfT$21r&Hg>^5cXa|*I@lH zv+zz9Nydv`WwAP~{H*vnMAQY-xt$(|Ju_m!+u}CC88|;88&hA5y+rMVV8hHe)wo|$ zp!zuB%JeXIWvB215e@?mcBB^r`bJwG4?Ewmzj0@Ka#cw6SJeD#P#DGAc)MP2Y>$cz z?bqk!fx6RX{}g@5tCM@$jy#x4tu_tRZWP;GXzzcWnWqbUc3*Z^4e*9<^y3d0XY-65 zQIh_BU9(X3+5}%v&rr{V=`Pdz+9fjeE4yazVnb}*_ovVxqV0c~oyAmycSaa+rVP-& zv+n`y$y3iA@dF3ag;`f`5XjqM=qk=F z1}ynw2X;>G$b~HlcW!;#$4SRqSt^b%E|#n{=CqyqHPwwzZjYlS#~x5=C{x+B)j!Ch zxG?vg4!k>sBHNE^YgwE%9i>5d`Iwn?v@D74rus;iF5BLnNsj=%IvfPjw_1cZ=0FXq z3wQ~}4R)+-HB%7Q<#e;GDHuZx2}*^5#*|*`#8~xP4q?Uh-riyDQ9ed&SGTF&WGb0a zThkyy-^L0e1?8f%qp7QSdn(QXA((1QU4pOoS=au2n7A(nrkiZMT+`LH@Epbper#j0 zGH3TZ2+NOtD&EV)?B+j>8*LQ>i|fkX1~$%{W_dNO=eEIkIs1Pn8E8WZ7>?u5#mLAN!5wpr9ifx5 zxSjR^Prh+Gp=z>+gniPnLGSsJJ6bkp*(^h<@ys#?4~3 zowzu8N_g}NYEA|;)FX6PZBrtG@!Yjj>--Snpn4W{TlI=p)wXhhGT zTPbW90Dze}EM{|PGq~zs+<0C~$koq<&>}Fe_!l{3#vBfg?)V|I8maq3A*@}HjRtjhbtm z5_mj1<#$1>6L>h%x`$|#as84v10x8MH2`{1+xxv|m*)hN1xO_et7$Sx!zRO)76rZ- z;w*JHSzd#)S!!$nW1x_Ca?l-2e7J-s&^2WJrHtwI#wlat`{9p7RX2lx7BLryW`rT8 zjL%a!ZJPEl9DpKJ3-7-dcRMRqC;;2bJ#URsZ-tH_rsF?}KBkvpeLp=~5p^5Gv$N$+ z)8*}RvVtg*noJ=AZo*Rfd>;xex?+>31KWyZ%1k*66%GY38sQ7}YUMw5**Xhc6>h32=Wv6}_SQgd}e3{D0u` zQ;FrYb*EH+Epa)^xPuRf3AT&{faehw3#2x2e7QD)(Yj^%>~(Ld&U;?2#X2tQ$a`V> z#UH=YRKX>jn|XFYi+*;Yti!tQ`f_w#uLsMa?ibtXq%MB2B#D91OEbD*r5YW!>!+5P zqKA#MC9^EvBJ+Xj>;(TE7USoCU1B$7e0*#TNYdlZXMGMpAi4hXchK!??3 z8Ql&G7Uy~ueyvesNOxZGf2SKBmiX#lRueWn4p$b{6o4~ISpetZuEmY3A+MJ?od$;b z-{b9Ra+o*Na)1Lt&iT$ZEJQ}suC0`uzK84kRo7#TV_vqxSX9x-Vc zh5;c*A4v9ca{plVAeK>wv^G7URJqNGJBh>CM9r9^t-ZD%+B^QS-j=b7{=%re#aG>* z67B~I={uKu{uibLZBdUGFzsy^hDFiz+E2lh4_Wdn40caoK8Weg_7#>vVzTci&L8t;+yFN#1Tc^QdSGM(c**y!Dl-ybzRlCbH$Z)BJe4Z}hgS z%_T35Jt77a0PEO=w6JpbIPt>NfSXChv!_8kuYlL4@@V0Rn63iR>?RV)Z#J4+i|fsf zDi-Uj)z1mWT_?qO+9;c@E~B^+hEo~ycrQ=Je8%0~L;wO>{VxmakB{R4FNc$el+$Y5 z{?NZe@l5o=cGEV`zjM-cK0fM9vb>xK=`3bEVRIyb0`CtfBd7uPV(jcBJSgJ*B>}JD z3zOQ27KrN!M$PV@%}g|9tar8*qyeGU;P3nWiKosZ2STjYPZ2C=)iKX@*@|!1{=`S) z@R7iHSnbJ$KO+Af zz&ewXmW*7iS(spXhbnSP-6I!)EisqMw9!*kNFN!R8+beGfOWdk*1RUKTj$rsbd1`3 zUt0|hAOF~MjGH3@^MhKjMRH#SX7LXl$SG4NjCJqh6O1aYqstj!kc9Av^c_WZxxxJw zY@KLymI8#ezWYdoIN{`&PxA{5qbUjr{WWf4@3LdGFdy2BaeR1?A#0E&q}63vc^}Nz z<7H-H*jW5W#_mYl5Z00n$4DH_XWRxR{!>G&@Ug!B^qd5-Uu)v`FT@WYk=go893Vw- zgTKMbZiLjYg}q0EkE$DO(;n@>#MrX&>WZ5h4UfHOFNe&&+Hj6eZwDz|WeM(FX;>z# zL=sR2jG?(dJ=&DC?sOf<$$+iW{T8dWfZuo6LwpYHBi(BabIZ_TiXJrV4YY~?N-m_b zI2mhh7))tdyk2#>u^L~E76=a*UfviR>-jr!U01oLM&;Pk6phPceh#nXGT8jGTQiJc zE?p|^np;m3D)1L|HL;w(wGObX(c5998DfCD#T+4o+oQ`E1K-e6jbCOSpllMnA@yD& zD0>@_Q^~`N!vzRZbKMEM=4GiqAXcu=GL~hKksjT9l79bIWPS8N{|eKSv@kdYlp=E# zi-3IN7#d~-0un{E;5h}*|7!elWuf%3hAqW+758nhA%I<=akxUU+-O?Zocs&=j0PJu zf}+sM|Je2mb@yjG@cNx{#)b&FhZp2)U2p5BhFRotJBKhRicF0Js6-WUvhh*>lj5Kv zDXw#n!^;h8xF?|eBbL6tAQ*~*TC(Vny>o4yg)#)+2PvOKBU@c_5I8HUx8}Er)eng! zr=ty>xwO9doBvyWl?H%X;v^JUUmY80q?7G6Ia2QQy5)l~!CmQL;9F=nvG;?s(^104 z!-c-trg!7|(W}0Zq^*e64CggZOqxE*wko9bB z=JH=#!{t#2XP?wI=DE(kJ!SrP-S;a0s}sb!4m!^tMC@OWs{g2*W4RU!F**xOM3dI~F<%zG{KY3* z+c+0AJqLkcqhydhna`p)IuDzMVJ$C8jW`^-l|O>`3fT-76K&Y_)Nr)4Pyq0V$DkUh)1FhJ86pAhHwCxA|}|JciZ;#B%HBJvrk^QYZOcb-NME7%O=&6WPTb5V|c1qpF$3J_VDAtz(1{*^=c#>**`gnKS2CquDZu_6kutOt?<)l54q8ze3bZ&2(Hj?^^U9yTIrYX z2`P;Xqi9&fBg>hC<(vO%p#(2XTA-Ey#2I!GN4UsHGOUtWVVxx5r|`i)3eWE8_oXo- zrb{?w4JZYXwzsuK5aY+mm*;p&l)JNC=V;OXX}RG@?l0`>e0=z%4QrfF@&+RTWXDEi z@KPLWs}sw?YW+2tAYx_RI+3Jk__z1@rq-u%*}TY-VASq$qL@!_mGv_*+zIz1q$v@{w1pF6L>LC#` zTq7da46DHnEyI7VFUu3PcL%RVi(9a1%pa9T&?)2roGuOkY3G?IgZmZ{r{in6^P5VZ7Fa4wp=;Ws(>TE+1Y&ByK?nIwQ-=2t#QV#;S^(v<`ogAPEk!)$q^ zuNzKEu85b8V0EjM9AwfQoXHAFZn)3vcgUef)lTISt44=2yORgwVlg6KEq|ubgq#k_ zV-=ESlzac%dGEuKZ(Nh`;HnrzjX;O5l^p(}z*NZ?n~vwLT4+VWl{(Im4FZu9c$+#+ z`*(F=#L!_tY4+^=$xbak4QWUUXS){niv(wa3|N8KPqLa!Vy>g+H@Ts>y0(ngNLof%2BXecM4Y$YkJ@{6e>1T7OaY^&R@jLeBC^93`q7W6I)@rW z))%z{f8cOdaF!~4exN{v6WLTZWuQxUZD%40EwUE8*8bdkPgC);q>N2-jCFUe++(<| z;kOPU4*%-|I}2ucdij7 zjmpg<&apFp;(4))rh_Zv>&N@tui3QIET*#Ek{)w~tUnnCQ`J`1wljZVzJbj5Fv=TC zLw@|&z5T@*yXLjF@zd4ib{6r*{`c{RgFD!o4L1+ral{p@BK={@T9JiE-@v6|KuU*>mAm`5(Y$HU{pc*O zmw^iyc;v%_$i_7#INdOVJ9>eI!$Lh?Ujp}`ERCP#C%UA*qeH%gX z=H9<&tl3WufQID2<1R!bIH-zim$&!U_rE_)`0#L3^;kX1>2U8APsKZ=bZ#I`g+K*g zR7$@)o_>J>$U@PbF#!Is3%{hW4rKYOVWd@ zf@fk)iVLg*5`(XCqIOT{@IQ&w|HP7O>#&}jQj|vtMg1ODR)kn{y#Cl2(+w8~++JQ) zTFK&TH0zjgW*qMuoj@Z&EXTEEw`+|9N*c=+zcVdIga=AWYT_TGQ1Wk8|B}_@cW_7q zm~fpI0E*m4wlTvjxKc&4R#~V#TQJw4H4d^F(hp60jW*ftS52y(9cr!4nN>m-ak)$F_s zlpg5QBsM%G%5<||{B`r?%(w(2=p-o4g!vzW&qc`;|7B&iC#u#lp~XqZ1vlPA<;Pa3{AIg}oK3b(*Nl?EP{r2U}~(IGY%5h0CN#BnYPHMs9Fj`=!9= zmOhaodJABPs~Ghq;B030T#oDgq@i1LIBTVqIOAUvy6N^+D+9nZaOzd(SyznyT|80V zdpi`XN#;KMa?JnF1u$0s7p{oEz?Y%iyNsp~BC}T%j+hqQed*01IpWVI8ClGkpehs~ z)+x7lmYOM1&M{*2x2qt3X)@3KeI_UE#|R*fu#5Fe6iHMHl*(+VQK)Bc_&etf*M6{zZ}soW{5RFo|zvhG+tvYGaaN-uUecF zc>8<5A`naSU9=MCv@Q*Ip^&vZ0{o*EtF z&6CIutM8lqtn=y^IbFALyO-aJnIH7;BvY5Y^2?89`1CYVUj@~8x98z^P*a8+H1%Xi zkGuBil>n`BnvdA?xMGNzjc_CMhLh#{)g%gKKtlTE3u?$yM1uim$+fN_$#*;tWkPE5 zPS=@onY5HMT52uL%IZIai<$3(ud2#cxp{lb0PM_OdQu@{d=4@$K{$g}zSlTEvRX6| zVJt?A z&Qo}b$NQ)0sk)e&3U z@;j>dUvYHz7|xXRMSMo9&jU;uT6m8va?e*=A1N+;^^-I-2g zP!SY@x|j1MQOg1nG!nH>@3ll(tTlJ4QKj-PkA@Um3&qP)12r@$1;I)KR(8 zg^N-@Ah=7SP~D-^H4AA`{MbPq`UohjsiogkCtyK2$`a#aPY17KL4L$2ff+evkHJ!@ zIGc%_hKqR2<2EEw<&UA2I>4*ld1vVOG{Y_ z;;yWk@LVl<7d6wao_#)kP{9hBCMEPVkO0^!yu_+&mubsLNQzUyJR@*4MUo%iOLPI` z5O`kQVF6*SvaO!b7iiP+Z(l$ni!akG=c>{}{RN3f{}-=_GDA?se1V%BB1J>KT*I4- ztS64?1#?-o`zNgN@xT3&p&iZu^Wp8o^Vg@xv{(*tYq3Am1h)^|396bN3!Eg>79E&$ zslAbw&s z#QYbQ#P8|e3F{aM#wKH2gO9=Rp+V!r32&WmWBxr!rD?otrCaDS8gKrKZsC*INgSP? zMp0MbFI~>eCYB_$sD+HNID=vA=oMc4ulKq$^^A8=_izA+Dc`$l(FGeDri)uu)NeOy zqk-2;MgDmvtm~ONQf9i9)k~@x4DAK#|xHb@2pFku^D7OxTO94+7@&&>Wiy928oQ0d(zz{A* z&bjqr-_BTxaj(NYen7`N?7u9P7e09Ke~4911%%CEr59Kpd3k+JaNAXjpvT&$Y@=j< zo0?VHHx%m>7SqC{YY(aYxcO&)-}$LB@n}Vf*+{>_Csk>Dero8gpywX5;h6vYdBbbJ zMc;V_O#=QKoF7}Vd{p(9W^+Z*o>9|S`BGzw@aO!=Pev6dqGN11$^pc5@h`vDKR_Y1 zJ7i;Dntds0HE{gsBoZlnksU=GEY~;4@$*Wh6`1eguYE3%eO%bRrg8<`)*tiRZuWUN zw_+OJE=vO6BE(+)wHXV!6+_lq?FrhgElBy=laDUyn;|Oqab^8#&im3%qlXdzaU^0V z!{}#5751`u?_7rP-rx9Uw8dsl`DrjJCbE&Pj%_wNKpNQl_{^bZ)?8EJaBz?V$$vPj zZ+MvrbZ^7rW3Fedi={N(M5-J0E2V+$_IbT2@O)l4|NiC|c)xl)O^Ws+(3wURI#atB z-q~NaPim=9`J<>%v5Vb~dyx3cSrA!-mlF;UGYr;#n_RpSX{|pp#K#9QPW{w8aQ=N~ z!AaERVK^i&p(kLUukY^a8rm7A1yIxf_&A?8&UWn22>&0jqP_a`02%save-e%1dGna}nL?K^cmBuk2(b-b9%tAnaw0M#e9-VWCn_!W)) zlP_eZMPJe-OJ+U@$#hG7DeY{g6HfZ8bNPe2!KTtsZQZk22jME}{OolJKQCE&_jf)> z6Ip1Ey5n!j+QO~H_#&IPm*>`UT@1Kp=f$vu3YhSEJ(Upf3Ib)EKOzri3}f9iKxIp?|_gx9*kuvzsJ+YOgAw8zT2g3z4(yi=Q?q@NATHrzpnGk+%5 zh=a8fg)tNkioDq#eyQ$J@zHQL@BA}!em;SwpYpGITugkUpZ_)sT`q}WPGOJ{Fb>tg zk(lQo@9_9+cDhHqBojK!L(5|^)n7?R>wi2(9m2r26Gh(pT_JX~Fy8W=4^LWqq{Hix ze+_0UX2sg(x8n=rmv86Mh@d(^Vx%tO=boDF>K5z*I~$0%PV@15O78svQh zXP+s3an7Mh5aqh|tlDsAQw8+mJ1xG;I+iD&b&_P2EoN>q@pJQ2EC8CqviPgD z#u`Kdoo)W^6e$V1UpsS?9Jb*1$iQGe-7#?fe9P5(J+7DF;nju#@!+FE?YVHx;ZeRzn%?ld z`X*<~zc?p)v)1QR-q_)5PSJdyrYR{uv}@;NL=quYw8&uqL#%?t|HBF1PKQ#olh@tE? zx(&&#gu?-_fqy^`4I)2%ZwI5hcX9@nnh^1FIT*A@#rLQC?!Qtw5>nv>a zSimSy)8Udm?S02#FNS7u6zQ^)Z&x#MQITm*rQEjum+y|fNRB4kCHw+mkLh;WX3Rl? zov0z@@nZri=jV$%+6J=2fTu}Mu~gMFQRuI;W?Tne?a{z<>f`YW9i1Ep#j=d#m(kb} z{TtVc8SCf_Qx8E3jWOqbJnAPLqxt^rll%lnM}sdz{P;zyZ?@y{beQ*f#8eb?mSPWz zE4KS-J$_w(cJKYS!^`)HhPX?$B0Tea_8<`|CwBOfdhtF(YfPZEHl&6oTwyB5xBkv1 zFaZ^#%;eEO^WYP}tS8}$76^Y$Lpghf|7aL178B$6S_R$%@Syc2=h{g*L~ z6WwS-%VmSkQ5-XNpfe|Z_4fCtIo_zsTj$02Q7m%*YZ7e2+=f1%%Oe(LZ-ILIVB&!D z^ZdY@w{E?geQcA!Wo(TM!zF5=wtw^mlKUv#D39gK^5{k0HSh9%wc-305 zT#<%ApbIqnso{0Mt|psNfoflRqQvH^Df6~B?)vSfnUA&BhcB#DAcRe%J7BCEXN8l1 z%8I}v_EwSc_3pYYJC|B~1Y`Qva1EFe-TopLU~{a@bhI6x)zJ3M`J>ka9)?BWRR{4n zE~Zi6S8(KT;A3xE;6-B`&mQ(D>`qjApBh+Wp10_D{h$Aq;X1_vT~?|)N0z;NQyHzS zoy#Kuh1_S)V--%%H+kb=({vkGX>)Ya@l0bv=tjXtz)(|X6t48INyVDCJG2#u&M0#V zH9Qpo5>s5(`Nrx+nyN$NMB|H|MbtIy@^Q&w@{t!Vh$D7sf7>VO?NyP_!9YW(%gI?o z5_C>Oc+{!_^FVCFM{snXTWIf5@FwyU!y;FC?MJ!o9StM})JMJajY$Q*th}9d z6(vVUqNnJGx1Q!}-v?gF+I8oCbm@kDocAk9g6o#YFCo_9lXIe^jT!+yKeDu5r%aKf* z%V)@M3bsOJ678JFP{}x40tjuKkQfNrV zvJ{|whT|Tqjf=w0R@!cwNtOX)QCgM4A*13#12+3qyBG!1v^Uj0h~YRF_EU~6&)@Zy zw)vhZ$T-f|=35#&_TT*O4&0!E`dulJ-a}1bEJyvTO^g_YopgaMy6}F2)M4gP^LUG| zJE9LL{Q`==nCHY-xUb5%2!}jk82cW(y!>w~{JhA?e=6M#|m%qDi9-A7mY&%)FQZFwZfQ=|PWwq{ihKCuk$Y?%x)E^AYBJ z?6A2|o?O^?I9&<6*p4DU<4>7-N-LXs$-)1=1rw#t$R~xuWhAz!Z1gcsj1c1+N)z@k zIITa_WbNmALm%EveT_2HN?l5~;s2{tlI`{-E%N(ek;iem*2`|1CL7to7ioQ+FM(%g z+HG|?1(9DhVZ+9&m&9Jy4Fhjq+U`K;=_4o5Hg2t7_5gMa11+7WjK0oOkjHi3+iaAS zRfI#zMvuvBV8FM@qY-eGaWo3Y=y&gx?WgL4wtW7UYk6{P7AJb>pGg>17ur;v{F!Bz z`yo0!y6+*a4rW+IdTTt4$G`pJJ^RQ1#wXhDc-Y+(De|v;U=)ewC=>U|@N#2I!veXV z^oMtz(HS?9a--;K3^(*Pak01Jp04N$GF67^e|g&=yu89;n)!^|=eF2MjNfzZ7A+rM z-9YyUHiIUd+MvDDT~zD6ytB|h*Rv6P^n%UboqpY{%ODq{57TSuK3jY{3BfIR7lgim z)%Q9$ux3R#s!wk4)?eY6>zqwPMN6OO3gf5z3MOTd@~;ACCo6j~_wDualsgb;QXvQ2 zH8k|RPN+-Uf7QD#6ASd-b}QSQ#8(x5Qf;7;oSV(wc$kZ59|L?dr+B%4yQOnvl@6VL zT^Ffw{Q8T49b+5rcJJC}(;NpMUETj#0k?HGHGjBASQ1(N5~>ukUf{i{~& zIYL#=)J!%FhcV~UtIzo)&MLk<#faOHW#f-Gbcnd-7-u!NmaGQh(dc4!8uM(!hmj{Y zW@O@aC1dR=__hpBed@9ap?z2FPVxTDP}kz*xbI=DIKPrw;8@&GlJOMI3xh?YtP~jQ zHWK5yg#DAZ$y=f{t0u}S`>))DAQGU#$OoH%7a7mv!i-YY9W|{yGRnhG1x%Li^`%!A zMgP0`%~{k%PqJE#zABP(S#vpeKw8Wy7dosaEm0Y&g+8-+@&hML)GE_tcDui7U<&eDuN&u}k{bvSn0^W`_1sA$GBr7tS{sT=Jm^>?CWYReG@EWdE z!&Ej09b>8-#88HMwKFoaySm%F8!qmsYbR>Lyj*}n1^iOT?cU_^VF&CGutj}a&r}1354ydh9b@mnZOjZVq7&x(UEtKs4G#!lMSoUf z;8Y@u62h%Gw+QLSmo_I5*4S1e{a7ZBFnAA7`<_rQ-Q?dVq3KdhR7o*(f66jbbmbLi zl4HYPp3|6cD%fO)jD+%X?z)PT7AB1c;MpAek26w5S!Rg^XH%_WikFZt^8r{$BhLZN*dKzR-z3Aw@6XV6gy4D8(kUV!u=f&x41@OT;F_GzJO45TWo=fH&a~;1zf`e^{amgIaavmdX zl+Tk%c}^>RbRkm&1`ov3LMNnh-_C%jyxGmIPvspaSyD!VB#jO%{WkmSyQ*G51`$l1 zL^mUM?Nzho5;fQhCTf*9B$5Ut3+93T3k#`-Gd~1{cgGv>*RtiyYFeXPvQ)K*ly*E@ z|siL#|A7pJM>EIp~hPQ;L&dctH&7*wKFgEUJ! z-RKYxzhT6c|2b1WA&F<4%^{oY`o}{z93+=dhB9f8eGBaLr{NTYuB6GpGS4F)I7B`W zO)?wsN9}I9obuPgzE|pYvx?-NMYrf|JPwg^aFXk!L4PY<#J=HK*5_EH9$yk}H*4be zG@K%O{#`^jkLeADo7~y|I^2^x=xXwIy{l zyfJ3pta>F;Tsghh%P?pg+*~E1Fj`=1xl#A~UvZj8oT6Q{2ladQ%*I=ltIka;H=*8_ zo%oQ^s`eMGu@pibi(ABni8SR>)HJ@@JpOv{5`6xY44y5VN=a5;Tl4d)S8i;pIwkZzf_K{!P zBD1692tfR^$60?8R~MF&LxfxJLHVsR@uJfAS(gLv<6~%P?ml{}^LVF>{$I{1IB{$Y zkOzs+(?Zo}aRM`*@sD)@8!Wg5=w5du0Y?_t;hHN|y0h&^XQ!(K#KkA_uG0wx*}mvh zpKKZV0uFkk#ETac zr_Xl1hudpB9TwJoMLDS1S!PYDuW7Ai^pz>}`%c)7Rs5}zNO>*e>m~%Gg6gbr>UTST z*#k+bKpL&Vdty|9N|LX2}f&&t&`ba=Fbh>s3LUuRhfm2jtu@ye8Q{<7z9 zbZK-3KKw#!P{xB2SV=}!!5esb-#jRGg+f#v=ze!Fopom=V3ib1=6U;g9r$WO4SMXi z!XwU*2yiz;$y-iVa|m<4eDCNsS$~Q+&j)VG|K{?;#K^VH;gybh=tOob0B%NVt-MmN z$*Ihw7V+BKEv&UwR-=Za02Ic~%vk7*S9u&F?d=A_wKWmj^HZ!;yB|79$ZZ{+cV!A( za^>T~{co%PnWr1!ayOG`Q2i@k+o&fKZQS~!L?Ai-NHlpO)gh(OKH)Cj*Ek0gN$W9N zbq}FOIo5GCU-rK~HYrZ@eEJx{q|b!#*kv@=2YDXM({LW9TcDh8}joQM$!)xDAp0cdy?BZl+u-91cV7N&t#M^C@@$(~&eT zEltIH=WrX9P&i7dAy9D{t>%iv5Ic#V@3)OxLs5z^k3C`SEoJ^vi{w%yW zl^0yctfb`3mWnC?%U@$%J8?Y29hx{?EN-?1r{4BQ9M;YiH09-fv;Quw*GZjD2&VF^ z-AAf)8|k@Xt620ezm{@yw608W&urSS{xFZt!uFFDn&8CnTedS|vK88qQii#UY2%&` zUuSZPicm$;ICR5|khz<`-&aZ=VjQ}(XUkIGg;|#DxXd$F$Z{P!I&W1Mef-jgNU&J) zpNB$WPS2GDw|CggXsmuqs&o(t zC%-bZVhlAm;-!-c)|{u6Y%x=}B=Wgu)%buE{JUdrDxJeo-}6`21BH7JJIc3CkhL^* zxkY?f6AlCAvl3>7dp3dVoV#fXQ7+1lKesl%^h{p%bD)3iN(^Bru4h^eB9hV}D~b8i_HyK$Xgu~^NPqZh z!;(kuw52fT*c^GXbtBcAm)D=|zVZ9g!_YlPo#C)q?QG&oeRH49J~+?j!dn;JQpKBh zm6_qO=H%%waOR@{0Or|g=B7CPZ>P$zLgU!slLom`o8e^Jjng8;kf!o}A*bJYbm|-!ED;#??`wYkX zwMthVd93#KqYtKYK>I zpCN-iv;{|S1sh{5|JGm7$Xw-;MbyMep4{aj34Mb(aTvOZ3|gg()wZ#dfV~*D%#JCA z7XP+~4HiEPl3XCVy^~4^*;kN|&ieM%DTAk(fL3@97H90nv9qosnVHKdFp66<@v!NAB`PUf9*k$X$wzJf|kDZ$@Qt(G54exNgw;`^}3 zqfyh|;wr%Wx?&ZPh@8SfFw9rTNV^cwqG}nRrFsU=S)-n<)10Vg=`qe2NTAT;#>@9? z+F72HRIryRb{Z2S{N!?!7At{)8Jn>8ce9%-6YUzM2<9R<9HHP3?Mv=|*oU*!J%J1< zPF7R;LkK|5w%-mdt)d|vh7isDHT^zX*CP>TA&`buXT{MUrifLKZe>#fxRQ@u{yQ9{ zl4Zy2*4){}#V82;pNqNNq>UNl$~Qqtp$r9f&2nx$7Zxgs^+uIhVJJ>t=~y~P5~>3X ztMyB=(tzrq1g%QB|9~l)3`IX8CnTKuNyCO4v8_fu^R`7lc%Sh!OGXd zi!khednTcj)R!@_Th|w>D;E|1VC|FI>GvsW;tp$jk(wmY7f7Qiicv7UHbExADJMgY zv;4TeRyuE;0lSpdWd7j$kf}BhS)rp1yHz6UnmAq1oPlA@vZhgJ2H->S)N#kZ^MG5` zrb8OO*cI~U-KRP)l}GKf(YKpEBtJno0GwktM2c_iF2FHX#CeF~;jr&n*$GMrztsM+ zu74gFdR=qH{6&F*7TbBdh6nMdE0SJN7g@}E^rufUK=qn+7(DOlDFffYGl-WXOTpwK z8s?Y^`Bh0b`y^B>Z7=JOER$Fx8?yCi%ynXm@y+if{gY z2-kYDDA{KegCC@PkCD)bv^Mv_#aQmKRnGyx0q)H|3CTT;)`Rf)v# z5GlK;{efChrJ*9bPyxBt%<4vACt$HiIb`VTty3SYNnZzkLsLOa3hj5j`WY~& zmuk`M04^L}O01O|=4dV?9{zX4)YbmL=TaqMgs1Up^JEX2aa-Bwz)xxiq6SUi*?xo{ zVy`|XPI95r(D)=5el_DDJ--s5hmg`~;##R1@7K>zwmh9rQwE^W(>UUvshMf(o#Lvv zJHHcA71GE1AT1q$b0gECg8n|nea|t@@{XmBk%F!2f3^e>W@+A|x@}eV$`dIUQQ}#? ztM^LEu5r{(xiT;R*0uQ4yv!u?G2l}K^vX+`0O6fTUaBfj7z)6Mb!BlaKS450BUiME z?hVrDU3Dl;Q84fc-8Dir!Uw(?;{z$p!c9UYD9ZRf%$??v@nT()ujXI* zMqay3l5F%8s@N|QPW(=H2l$rb$pHYmFHPTvKsjgMe|8sJHn~d?C@@4uy2Zz0VBPgR z?P95t`ce}q+V?=W;`lZkba^>tr6o4QsD3NDDnwCSe4pk~x@KI^TQESHMb7(s_mnhu z<^;1j+<5ZpO)#$nvpJ}!%Ot9g{?Z!8G%#<4g0L7;*dd#u+#!wH2&`CrE-wo=fwk#l z+<9QbQK4)QpB?`{n!Ylut*(hS1Pe}(BE_w^Q(THead#^Y#a#o%9g4fVJH_4I-HN*x zzr5eQ_is*~=j5F1*?X-uGi!i=(b|)waz0NdVn8}q^+_d~R}VA8q$^v=_DdFLRlA?# zx#zjE;8T&A?G>+3(KB z1mAzZnQrSdRFr@_SKd{3UQgj@UN4K*U&spn`T9KnST~aoV}^lrpGPtI?xWISyxvuL zIj^HMky@_5{5i)csG42TlhCn1hcd_hV7bm#Wipwwz1bf))8D_b|M$15IfRcLwWk*; zB}#B!CvUiL&3_!mGFeUb2OA(p>6QQNF=mqO3mUv0D!S~K+PD4I8;M4ujo(b8TYI&c zY0v;soq8_@0Ksb0%`&kUU7A#2NAwVS3dmpQ>#Wc5W%T==EtMtjzsfY($U^a8@t`N) z+ezQ`S(mtw{CK|u)7g>(-)kms-GgiX;lemQ#e}Dm+?}76d}xG4>8nzkY)0>2V#zLROB4Zib$FZOK#1Y;OXsgm z@*6Kr46)jFjrrs_4Lvsgn0rxJS}J>1;$D)Jn^VntMQwAAAo9zaHP2=H+@Zl;ThQDP z1OyZ90xu@#-F>!I)+~}>R zAbWOF|AEjbX4V$Fipkrrz3Wr<`hUt!kpC2Hndk>J%cHSFT!faR)-M&(&!I!bh!e%m zMT~{%I1v{j4ct8o0AWZ)c?$!NuJu_kNkfMh2C|BNZ#ul81xjLU|0z_Rc+lMQWRr^dIV}UzxEZel? zl3&|_0O;)^#yeD1y1jFfqyf~FR{F6-Avblt@0K-fXlgbYj8;4(s4Y;?mU3=-HDN|W zLqNUfQEY6)s}Y-bmuBj=YoFxft2Ue*cdwm8BH|tD@MvInU%b(LT~?n3`cNpC+r||jbJ`!d22R0~h3=CPaY}u>O7PU8d+c=ggHH_yoaRDv>-7o%k z*55A(U>O26(5yIE&WZJ%d{5)ONSgzPD`|&wo=RT2lso2C863#%jb!x^cSUymTLFkL zk57oY_QoKFO&UUO8*<^{T!Z-}aX;P0iW~}Kp7HyvezpS{vJjQkxLMJ)i>Mnpz^6nF`H{*-Y>VFm&~qt0?GYSjZ-#69yd!&iG7o&X^5ZY4oB;XFaKL;Rf?T6e3&lJ6TGv3{KKpAZ)WW&S!2@Q{FK#;_qpc zi)QM(_pE}$G2f2jxDBT<{fVzNOW<-~hDbWb=fo)B`Vi=0epgmQvp~TNIpm`PGYz?9 zU?5*hwfs3kWb+A9H9_%!x{$Ym39s7NT$p}Nc^ZmnA;WJj?YWkj7AqZTyxd zI-da2BV}ijSm2Y-NhG;s$9$e-w`*))Q1_Cm+>w$@2TqirlkLMGscBi5eYCHWIAyg ziKzIqJ5D$B7Fv{~4sPhXhiwqZoSLYe6)uMa6nb<^ zh11H&y+}Y%J=afIh+ODU2?fY!=C(vd|aH|rY(l-hK=FC zkh6Adqhx<#yzcI{XQnqr^#hJ-cd@+`4{mUr$@1|Txw>(CLSMe{6z7yRr?SNF{atzX zh71O5M5qBT?gS%4z-xQ#yPMO|7lD>UC5>;^!C^v>ENd-21i>JI z7RLw}<;k)yZQ4J@5aX%4p8o92uqWPsQz5Ku@|ri)=Z@t zAWbg8B2t<@@;+e|SDD2vkR)kfQq3Lx%;Hm60L5@OeOHHOBb)D4nBTbz0RQkD_w*t` ziFshO%o3$nDCh=1iFp8E;FMidu@~|&e8V7Oo|9gDARpaK8&&ZA&=u|6i}7-u85te# z01iAY#?JKxU!suwFbs_b8t%AMAQuZjqmmRu2Cyg$6vJ3UH9M@RH3*o6)iThF=J@BK zxhI=9;dRk&rl^de>LxXdiy*}ENBDvlOSpJ6TMrmP)0gO=Y>kC34L^o zE`N860mPxF>yIKz!kd*9%p7BYfy1zj5Dd+?03Dh{xs*wPrmbCKD+bv^9nQKze{#AFmM!7~;cRm76TfEa+isRsuJt|=Zm;McWkcibcu6a*xbHlpTv@e)BH3KTYA zt}r35OeJbLted4(3?~|9@x1;wrzvomc!pvh7jwema8}sjp;dg`M+4B=I$5!5`pUulw>>z)fpv(0L-HtyA@kMh*$nDqv5kwI^@FEw-z$Uy?%|&3rH7T3Yijev|Wf*Ff=r+ z`mtuNQ_HGvP=7OKFPH>H)?1yLSUyH$3B1q)Mp5I z7;RcfOZBp=Z@LYhp?P`f?P4f7nLmW%U~aLCg((?!s~M1l=D|z97|QLIq5nXX6-K2{ zgr&~{2Y_A-$sKw08c8+l!x5ShEzZ>%TK=9v6%~9BIv7uTRKtei>Qvd)kwzBDtXeU~ zooW2LYA*H70Ms25Ycdjw4-&p){wvKQWMXHsxqzy6xYE z`cZFG;%=V#JTr=vZ!BE%yGS~TyGYwj~CI7l-nVydtv1OsT?MVOHECw?#iAY_mZGPLWQzf%e2?INGAean;?M-gZm zjBjP3^KC;LOEnc|hFen`OQ~14hCe>HTP2Q`LX+~tfAAUoZt+H9>&u$RXAz7C2e@*t zb*gCH+8}TR42sakkH!l_YC2FZnpU!Cui)JBwI(__mD~H_J!i%vK?%{`wp{#^gKOW2 zDq?RgCRJ>%53gaC*J0mi(w*cTS`l2C21Qvz>){Gdd? zRfu)SLdEf*zM#nE2_ZrtwL-QeV_({`vHJZqZ4Y9Ltf^O~=&pw&MJ%B1BUsd$3}Own z47U!H^4tf1_SPS#r4CYI?DUve$(?Ufig8UjM*)y~%{AJ{E-I^^1G7Ar*}>d3eP%u< z@>U0&_BIj7cUHtrn$7veR%aXoGJ&T(fr&A_eXPPT-PsvD*y9qJWWZF~g{_405e<`` zlLlo8>9%ho2yieI8pj&tPO^w7f8t*M;`~GbCzn)i%_Jm91{7newe_k(^(r({ZirJL z3XAOj;6ve7oH78?*i%@vT}$^aH?qzuj3%O3x0eVilHEKJf+dIjrvQKnY;fC~$N1YX z=LU|Hv(;6cuO5&0Xs1lP4s9OB6#fyZI4HK88_KE85Q47>#v^?5N|{yUikjQ&zWQc; z2sb0~Kf=?Rf*2S~2s7*fT#IT{Cc)h6B|0?7K%c*MJ6$<{oF*!%ZyMLeT67yv+f74NW`)!7BwAigkw>u%j`GphI!4?qn|DBu4lGn!}KVN@Ua{>(|sH%5UJ zANA+ksGnpc3#~=)nQy?w^F=~8 z_3kHz*M#Xm+MC>|3HH?(f1`oS$wMVFFyw=$vzs*IceM!Ei^Lq_aszcg)o!XN&T4CP zr@A{!WiqzAvfo@fof>?v-zj?N_tE6C7_g6IUip_pwF89BJ?R&&oMhmByvk?LXkVwq zBOWa2kY4tu59!OIVKTs`UkjuJq3cO-A(PxF1Fz7#!yX#HxPnourOY;Qw_joCq*x6A zZqUUhao*9>Q_07Ja(>dl_^eIzEkq?^GOsk8EoGbEMrlre!>tVmHpq-9@S{a{4Y%a; zWs9|&*|k_nadvjFPjGkr{jUTKKa`4_=gRZCqI`4!dgy@V@n^hUK(^?=uVT3D{b6y7 zGnL;0!cbEBR^|f9PwhsuNLjpT#djN&r&S8d>af$|xb{ZWEM5`@69K%d6<%*iZYpN(#jh8%;(1n2jh)TpR9c)_WKrI=kDd1+=YETk6}8u$59#Ze4QAHxs> z+>gyW*3YllVnmVx7r%CYGK-4!3Ci~^@G&?_B~}PF=6k=-#^y^7S`)o)`b29E*wD7~ zb?L`;FT#5tk z{jy^jGn^cqn9}=IlZ)|Z)9e$ zvgpUMO`));_!=pD2n=Et3Q;xOMu-enauO6MI+S447r=FGlca+81mzSK&hY}%k_XFIq3XQ^oO9qeq1r!?Y*~4Az0WXSJ&>_?ciNwAm%_gf>J(T9 z4EnhClyPck}kVVrgmm$?)(joRD7&0Wd}Z08x(aj~!&$ju=-U?AaeoJiT=kgSg4? zsPNJ${6Ov}DKp;irPmX#FeDToUVL5x?B@KhPQTEHxL5$B$;6i*&__ai;G9>C9{sVK zT}$ixLmLO$bvGW2aJ5d3A?*JZ~Df=83UY55MLt6u9&*-bbGUd+~65VTU3C1wpnma5bj)Wsh3qNM_Dp+MBQU zpQ9Q5Y&_2i7fI}NMjHys@A};1v`X1xIHM65P(w^fI%`ueh>cW-j9$*wj|2A@5~os@ zV9k;sWUNrbsBs(ep-~OWz`~G?!s$gr(hWt0Yv)0~6`X%e-9j-beYy4?y}O>y+r3YO z)I|@xDvBc}my9g_LlQhqU_f(he<5t>(QNt^pfIMm2xTm!DjGh}%ZD&kfD{FG|L~b; z_j}>!(+!cpMb$SaFNJ8~%FLmLLRRvJQXdPd{j(7VDX@X38J2GtGsj=k(47v z^r2k7e*o%gilc=T5ylbf3v&lm9_u{?k8y>SY5YEyC`!k-HLvnM?`ejYnZg$6ct9W| zZ9D?`zTh``i?x>ER^`Qv6a~`thQGlOqSF39|2zyjR&g0Q&vNo)>~G~3wf{;44fIH| zGlY!CtgZOmxmY}^oo>y^7Kv29b@MaT&P?6!@m7(tf8{rQ(c$?br^W+ralh#*M1hWC zfD!`vd?>8w#M6xTQ$l#r;>^63pqQ{d~%hxU;|CNrfbXWpC#_j=MnYhAqs*_Hyyl_KbQHct7$xafXzAGAhHTQ4yTj*juAvo zL68V^Y|i#!a)0vl=B@JouLamIl21bDN1yNMA6~UN({8nyVpSE`zx_Mxz}+6g46e_J zq*UJDp&Zt3dQe^5#|41%<6OR59et>|h;o_bf78EO<1e+DklU+CVXL?IR6fZ+ zZq6Oj1Y$UhSZhQH9<`*B4JqvZ#b~y?We^g*8uHzv?LFeex$pJJ8#RVQ`x%z~;dM|u zS_-d<75PeIkXSovG5m!sRtwLVdC`JOo=s^hn)ztA-l-9WHsH_Mc~A7;vF3<5u_)g< zQ8yU`nI?@7dQK? z81s=aA1a11`=dn|R4pxjLQ#>@c$h27$JQU64<_-*`O0m82?3OSfx%JV*wETC-_3a_)$5kJ6*4s{Lq&C=jYu#Ce zU4^tmBR-k;K!&eBf@^vH?U9av)y~qpb!3~xkV-V45g_o&Ld!ghKUyy;HWKqq;4{5_ zTCG`LLD{TJWH2n|`#Bvlh~W(S;ePTd=5=SeKXypQ+wz%#Ult`VX~y4Vl-Kq|v%^}H zhL+AT%Qr_6bnf}`w(8Im0nEreZp9A9bEN)md~GLD=RrX- z=***7tsBYmFt%r9x~(2uM4gZ`v9LWI%6D<0jieIbqvtBvf`)Re-rcA((WD-!)6S!4eBhn5O4lphisv>+&#w5t>iN<^0FmX-+&~ zCct;o`|ReRWx3oi*=RBlzs@W_VTK-(2KcqR6*3_s3k~nz zNlAZ379XLXWe{;`&>4Y3UW(`?*!!5^L!fBbTwp>PQ^_%UllfbQO+F(GWM;2YmuM8p zj?7s~%`W)|v?o)wvuHpchN7>TO>UdT-2#CW$BI!>#qAFy5z#O%jamxH#?+4r?id05 znxp3fYI+ZfgSjApLgUJ=OXwv8D~ui<02^~ws{(mUt-jezVz59)py`lY*vzncbPv%gd69qMiQeX)&%Ro|PyWt?FAg;DNeQRL{xJZX$)a4VSzVB^? zx}>zITDyTF1^(o_<$Tc3yKwb%UUx~v=?#ba-CLFclvNk$GRt5O-h_BvGx>d!*3o07 z+_sU>{&pR;km|iDqS3~twYRMi6m7+D)J4Zn%7!i@#Pwg^tQKL$)Ntxi$nG+A!DQsj zAt|%9Tfh5GQx1o;Lz%5)8rB1lzKx*M=-qUXk`sLj`546A;orQFyZMP`dAaWDMo_=W zP$0 z(dlSvM~r4Q5i3yJ_H47YY5^@`E+k=M>B`M5=P{y#_v>VCjq9$2b+YBzan3a&4pb`6 z@Js+F9|onrOb{JxM0XhWQEcpaWo6t7zN?<@1babuNr?qJQ3;GZfgxV8KjJWw8py>&mF1Rcv8=Zg<8_&dcZJ{=FM%v^`k&`F-^=SAz8ei2g!W z?oR$B3}dIq%~2+Ue^oQss9UHZAQYmDw3{Tg@+C2 zU~{2GfqTZaUmqkyIeVb=>Bs`^)z0r!5+?rJJ%!Qa-%;2xV8Dl~vIYoeK8Y-U1hNA_ z9hW7h0%A}g-g1vT`iqTHEn_Rq0q#=$t9rQUjw8F@-)lUpZd z1CK%@ITLH(bY#njmZ1(S8tD`ESgi8z+SmYQ3)FT+DhHexB1u0^bL2Lq5`STL_dN7~ znd?WOv{%hC z=ab!ffBJ`c6k$d+EEb?aR$Pu-7G-Its+aiL*3p!e7fwO<3~ztQs?nU{@9x8j`3w|* zbm_7f8W1sCyVnwG)WMRL7^_*JHJ?B^gm9D~ z_*q!`G(G&g&P*{FFn^(OUmo6yh2|_$byLBJH^?{m8?t`wd-ZO1(Y|}i6UDn(dMvQL zKMZCY;)aHi^oPP?6AP7^j-#CYcK@_oYvOsgVD;?onnjS=u&|DZYe}Y{Jbsbk#^?#&br@!NGO*6%Omw1c_Z;r1?I`Mc?@=a1j z>)pnVpf=CJU>wH}@9754s%0lv+<(GaRyJOPhg;-(BPdP$Z^Q2wcQ~2Hf~yJTR?XXu z@QIb}EhkIe4NYs9Fde(1z)i<>^B>xSsc9F8z1cs{$rKY6D}UbmbEKUs8ES0uloek= z0TAprJ$(c|$)B&j)vA@6+`k0U*$832yJWnIWS&m>1QNukd2QGMnF)FZpc2ej8;)rg z6V;rYa9w+(o^v1O<3|!OR1hRqok_WQ#zG7f4j~$f+vQ^!A$ufq&RH0LE~K>1a(Lwk4Vju$J_h-ow${sT5U;U$kR z^*23_rBI{!UVCTtC;$LfGG^&`%Vr!ZC-fQJ2F&rw72g$PvFA?qrTpuw{7k|O205ly z1#7WqE1I5-(X@T=>x%3Tt5vza#0S5&o+`^r?0b{ws?hWh++R3+*dU5XlO zV=>B5$T0tVEkJ%^)dKrc?0OWo)Uyr#%n^|k$qU0}E&km#2o-d)kvrb(RsW4ybWLQ1 z`0Fa~x6KJY2@%enGSDgj3j)`UWI!&A1Py#FmVl9UFF*^57Ir)JUkCq==!{|xg#MaR zHs2^N%8BuMCit9Gi}Hh5^x%cE^Gf0!z$^#+HW*pCQtdt;G)~@0wzJkxC-_#<$z(%A z^z&|jSIxF9&+B{a>bd)EqbCvnM;~lv^Fz(xh4<;zc9sDmPIm^;uY`aSpIDw1l8O? zf@~!fC)D0Q4sJ6H^*mH>h{`_gJ2}5fUV|~rPvPs!fMJk~OVGmO z-qc?R8!oakgcf@%nWL7b*DfPn>g1S{k4z(!j#ky6zj=4v0&hC+UQ-Z-uolgiGV|h7 z`PEqBQ2ImuFkC{>?q+D#IzG*9E@})%T!Cin0**>_{wR)iq%NyTe(&A{vKHHDgc-zO zK(yMs=>-8!q1r?L z#D0rvQo0+-nhNyop{dMLbJw`-dprs%U}@{qXEGV>kK!X6#H+~h5TBCb*RC9$$in+# zuENDr!=>Ctw4m;!OJKBJ-`h)vNM7M{c1((g7Sr{UE~fttqN=llkBng36*X|P`yi3o zX8I!_X8gN7Sn~2u!+Hh7kHyYaTQ)p|Q3TK%7x1+Mg;_rjS%U#-}1VB1GV#a-6z z+_I49mFIC`C@7<04plYM5;F~k=o5Lo?Z2N)G+72et&_l*#^>X;;6lDKCNXw_A-gb` zI$d2`#Y?U^19bQyl+5v${= z+x(IyZjAC}8o|Fd6T1(P8AI{ZbC&1Fyn_QF3>op}VBUCR8@$UM_@^@Vh*9wcGwG?1 z$g@oiOtTgKrPD8NgaeWoAiL!O&E|e==>lNs&%2OPc)xeYQA18N)&Nh`!qbcVVd!L% z;$Qns84p6oiamORET}DH?r}um*}F#rehApw-h@M17>oW{k_j6b+TBtjlk53&yTI*1#65DZGg9n%n z9aMNh?baYZ|HgxZD^F@_tkhaxpTPp<9JTf1aZSr^moS>0EbLoP2*Z#%QD8JSn&wu% z4@@M6A{(^LEj0Jx)Vkf@d=55;8)O3gKAtmRmVqk$3Y5!H+(O2Ao#RQ@!X-=CzhwJT zF4@ZSszWb{Ndl5^T}%4eDrh@++s)Ju@4{`{Di~=Tasv;(H78Nr+LmE+s?WUggN8vj z^!~Cn6ZVgbCrHd0gR-|4(fJd&nP|%B9vyItp2wQEy61}{lZIEw3Yx76s2A>B#BULC zRqNoxzzSWz_j(e_zlAz$5CVssROMSSi{VysubY|Bp;&t<$}sy>nNdQOY9ZCh^k z*h|s2IjfY?9r)9)Fujn~xQ_$`=zP&>y}yn8>`4y3+fN%tfN3t1)YZ*da%ad9yzCaq zAUwJmz(W@%y+V%g7T->Om@r^g5mcq^qQMliZZHuu=>_WGGJ@PKRLN4}UMwYOv zlgb#?#If6v%Au1Q%>**vGf`@C?9qo9l9{dOFdWTpJ~{Tb3}i9u7ce@)sft~L1crhs z)zQkVGws)+It8pPT=DOY(!(Xj%vT3vjVg+eANwKNrDLPW->5+;Az=44XY3FK<=;t( zs9xR3&!}@uI?0XnxZM6qPXEkj=+N2PTgu!wUaVzH-)C$~6|#TpB2*v%cz9;hYz03@ z28@wVeOHs~TgK1<7#NW{vF}7LnTk2ahlHB$H^rexR@*S+76LwAq=oX(fE~@NX!jPW zl_;U`@{9~kBiB-KRV@y_{Mz+`T3CzR_MrxNb5bs@vLgKZrAGzGAE^MW_zd; z-%6n}49|^l>79w9Sjz$FgM6l^^vu!p@}B?)luCV|kNUk+!xF+#uOAEQ`ylcz9fjzU zP=D*f=245pZ2ZFe7}jBqI`CT1R3+M^d^JRvEukYML`Q)2unjVrQid2Do<#-_n_qHv z0o9tSSR&l)%4<@p8!OYnF`cUE-^IejOGUVyaeLI;><$RfgcEb|EovhMSQj(Ml%s=% z`S6fU2mni+f(r|AH2cvv-YdS@0x4r(FalL*v2ZlzmKCzT5B%B?_;|f&RIL+zJZ2_g z-|WWR8o$(fsuQxY{Hk|$biA*%p#>QqCV2ckjUruC6eS3XpOjY$v>|n3Jt|=PWOaIE zk2Asab25LgWOmqXEWE$%iN{wwsv3EQ&U?zY>B0f;rXaK%>+35*Q zO;N7Z-Eg*aYso(W{n2ljG3ZUuF9g8}H^-yec>rlea#LtyJU zpRRfu@xvtMmEGP*CXT*28ixtn!mPaOTJx2YghV3f&mXd?wizY*38DOh38IL>xxJ1- z^38{CQV9#uz?AX^Ti@Ql^`(@~%8qYiN68D9`)SVKaUKel{1%#Reb2(RSK_3YVsFn| z4~%6;(n9mfL;nX8{4BAz>Psko7IzW%wZErhm?%elgR9l;Il)#Kd*$PFvQ$l`bO=jW zzdV)Ci{yNAktTjtqxZGp(p=puYB&4)k3gPE(>>dZ=k=o`^Amgd!$b!<_j7HprMPT9 z`AtNPt+hcCDPlSdHGuDznELNYU+mUoYh;uh9BQd8Aou>iA;YdPea@6##r@<^32y4s+IJ-oQbE}EWQ zDMVEyR@x5B+gA0ODa2}M2Wh{8?G`h=CRVPM(w?I*r;9!ATQs46p#qi?wW>+T12Rdn z=)p`{YfZ0_a0Q2_EkpA9R(~D`Ds}WaUL3UbJ7*U!(s^-Oe%u@(@qAUP&{}RfyW4kK zSV)Z`lt(rZ6%i2vy?S3;%hz)ySV`&CNBgtCel6T{;)h3KM#I@nhT^Rp$oxP2utJ86 zJg~?b5B^kqAzS9<5RT`h5jQ_HbLNVL{!O%gHrvq*#%jnlJR_?_462*NK~iioSmf_$ zwB~Q{xfiqJVK~B`Vn-V4%lG3;U&x4X%}VdsundQY@_`02U2-Fmw@WiND)iZx2CYaJ z$zd*g%5{f5*Jgy4Po)0LeBsd&=%22+PAaera4tz4Qm^Rkn7Z-|UL<}QhDB!0<7JR8 zl^}^D(zvb5x##3ugXwXrdk6x#5#dbU;%DFNI3=lq{QzOACe2)bR9}9IX-TKPP4OLQwFTPXzTe6F-ulQbq4t$_ailM1YO~qLNAc=;F0jLMoXZsU z^Mv=Di!eMmu3kOn)q7$KQXyPlU};Ie=KD^=y-lV@GarP~ezNe9^@aX!-jDSU>z}Yy zT)wUZN7Mes;{;}-_cGh7@S(OXcWelJTs^9VT^A8SH85~k1wAqtkd2jbT>Gd6*G-AJ z&3n!Vj)oQG(U0+N|F{jIWxe(MluGm7ekPuFK=+soK5|eAtCng}hGowtGR_$ViSF54 z>7Rt|Tz!ISjMP}X7Q^;M53>mig^>O~)_-ios&_EGr!`3d)Wog&?z}Ay8Tjr0lHjZo z;aGD&#BH7xq6u&MAN_Y@k2~a$D1VgRF&b;!PDYieI{=y5yE{AMlWpkO;Hq zuJG1AthZ>FJ^rC`jcmNDLq^qC`$5yKz}y`Qht9T_E^j@XK#zAA1#rtd|pvBUoUrQs-+A@l#3B+3G1HT%E)D;>;PWrtZ=pWNS2-9JHLU+eAePT`S8 z;IX#8MlIY$)E9H3VLO4SNoGEw1E90}E1Eo=M};XQ8Sq|bzU&7&5;0c8#gPQh{%GjP zQ|L?ukggCUwvev51gm$l0suuF#sE#l^+yQKCjcykX)vH`sAj=#qW2LAwqvNd(TJy^ z9^FET{N!Cyco2H|AAQ0}Eu_0dy`&&evd*%#PN9wq#^3xS2Up?h=1d0-2ys?PH&}p% zO@Id2;g3YH?fg46=nf8ka{XhNC>sBR(B}*ht-e?r4&vjgY%jZTv-8a%r|p*|F)VI4CwDll5fSTgC3YVX60fEN=$2*zsR zb}}u?43bzFfvz*x`E|ptF=yf8D3UI6*e$2dvASbIyR)*Xp&bAy3DD>3$?X@pq976S z)>T8Mb5J@=po3rK@d6Y}eF___&;K}$i6(IZrWP^@TN>Ty+uW7q(Gg*i*Haic2>ne? z?oKA=IsZ0$sO_}e_>@9Y===uGi9IAPam7q~Y0leD1lVvmqbkqwqpG>k32tp@x= zR+26se|0b_T2UVsq%4J)(B(6*s|vKi`w~2wOgxL3pJEhHLT3n`Y5SMT)7sw_6hsqt zfntK@li}=kCYR&I^Q)5Oh_<-&Xr5wO03UgLxT;!lqPfjyy7z}?oKaHL?=p0;@6p4bdv*L3ZkJVnw~mZLRKr-=NNtTaavB~QxTr>@c$@17@r*nIx>JyQvGuY^{9_@~ zzZqmZn7+=i&|40qxpn-NBJ)G|I0Rod8Iu5~();F5*e8EZsv7sc9<_FD<4wn2c-t)x)%QbS^(NEsd{MgPCR7Q zLy?&=Z>CttBI`MD0n{%%?g`+8UW5Wv_qQR}q%BKK0r(;E(0sw@fM)2x>&IG;oG!*;C<)Jl{79qIfHRH7$S$+j zt-SPyr-&Inpv-bHzcs6&UVf|#-#-Gxy7S>}+~;I1XQP5FVR!s$?n&x6W+GNzJjKwO zRY6y&J1>Ae_<6f{1qIqXGE0sOH?Dh1Y(Q@cuw%J?E+cDd$t+__&Jf2O`2lfCvuN+k zrsfjZaZ4SCl81ee{68G3`(u@m(Shd-?#Us5&+JS=vHHsr6J(yCUZmD+=sVe7N(G5m zhpq9jBpEJJe?Ud?g{t}Z-+j(a*xtGG9^?K}Coq?AK3kSnMj^tnFqy_x_0fV)Y%$1K zz0L*%?Yr78{Y3tqrc%>9c;UaUc7y|Hl$1SSJSz ziiOQh;Yo8~?T~(O+7UrPuzTc!8bFND!0%nFxIoBm7c2})cJL@XS416Tvm1B229*og z2e2{28VUy7jBVRVbHR`Zh1-8RT!m6%YXx;TL_==cd+r(J4{DDPBE8B>Z1o!K*+Lb$ z{TfhJ%{A|b?Dag~;Ppu5DH6+DN=hmmk0)qZ>K5>!MlKdFUEpK5o}uUQ0>b>2zq1w7 zI5J}xhl=1!fg|!2{WFwG3h;_5AP12KSc(lNL2#~AZF<9`HH@FEfmm8g1Zm2W75KuM zVokE*W1P5!>;Dkd=IZ_b_pB-qw(pT2F;#EVg~9h~>(Ha`&iDhf8-cpO`UH+IkT2`Q z5WhKPTc9ul=Go5^mk$O(VYC`>ON(-bBVfUH;+e2S=Z0g1W(Nm>BHV)OdMr+&GxxjV zAZU|r`r4m0rrJ&Sx8udtcgFkrP_&YFI?lO?Uf*{#grt8gQ22bQyXjNangZ4e;^P|5 z+cg^e^Q30tYmMN)(SbxwF0pw!(~6!v_y?ykZR^ia=V7FDh1>Xy&|d&uhM}FeJvsv| zqAK&{pQ}&&$4Hy(7EtT@WJI+nRO-j0$4A9hu6M<2a{m5nE5}wAfhP-`trR2?!dzY{ zMcBQ$z6!*{Q$9#>ha~BR-}hEeA8USgx6rh7B4nm<3#CB^qovB03ZmzIcueu*%l6bRdkRlJqD(=2ekDGcsQVR&rdddYQgd z@c7G)^Lq>w?>;Fm^6YWNCtNEa+n@hPickWgU1bPnw*4(ikY(>DnW0o!^kBm{Z&UQ& z|GLRAY}hl)e>Qi`>*&3!=kPR}J%_%w`ghr8v>P1#k)1wVlA<#8-v2x8ZQBt?c-)je z)K*Zo&!}ag%XOA|f&DQaeDzVsWqahV4u_tYrc2AX`h88i$`+5yl}b;!(``OzAB(9s2!Jg1Q`^^gd%C*$%C@TKcHK|x zGyc%#KDzMI^F^@zB;nw`y+##ObHCuJ&5Of#)iO!2($2ukzBPc6fs`b88yn4}(_up) z&4}#(TjIg@VDtT{FQ*;)U;L}Sos{4^|z*S0Q4X^oj@zaAPoHAaGmJZot#mKzV=LjE9o+0w9F;baEa z`5#LWwCexbH5bDsQ|kOPHeB^y@$%;X(DV)Ld38WK=G`1Qwwr$(C zZQFKZ+uwCR-|_x}z4okGGv}P6m87g^6$TcN8osO_qkT{3`!%=xXA8!2#ZjtZIr+Zq z``gu#=GyHs@q~s&23LF2<@g9!nChnI=WA$avcir}QzP$nR2HAx2^M%P(d2cG&(c&T z_sQ!fDdBr_y(IwKx1EsXUR8A3j27ZVr}4c0*!g4H9vWzkJVOh9b?SV1d+em6=TYCf zIk}ay_PPcQkZ4d?x!eAIxpF74b@{%KgwFvEjQ^NkdtdN+yPK?-GRJ@?#wKE6{d+MU zBJFyWRi& z{-z|`2!Up7jgpd$smA{WhX(znlVi03JSoqOh|qoHw8YyIb=>QWBN<<{Xt^<}v@ALf z6As^n_!3J)$Svr`ppC1dysaWXb&~>>N-)h`2{h@5agCe4&XM1wBDeUHq zPAlrKSA9@obLR3cx+tmPl-tPnOOcYE_NqNYGt1*6NS8L$I?!S^E{00M#CN;$yB&7y z%oal6F>5{i7t1)__5WF6R&d!^i_C}~`3yA$2wylK7U^8Oh_AAxK#|hl-ED%c$H~u- zVB-l*)Q~}XN@knmRFINCKBf!ytCco(*bBB7PTGle>5gYNY}&4QME&^Cq(SJqAYG0SZXR}3?D?h^n62W+PFQc>2vG4Tak0bU*VWvy4^?n zykY6O(H&FuFfWiLSWYM|v9V5@7;oBmO^z;*fCkdVAEo zbYVpeo6>2Ijg}=?DeIkfBE`Q=Y(hYhbV;PR} zoQ2r`JrJy#(%V|gKNF}*emOWD;($0vEu{Zc32RE10+`SrpxqvnF@w(t>WQ}_T~E8Y z7##hmBU{gRhh29~UT^cNA0ZNSI&H^$pXYUCWGDqNuTrX!@=NnCJ)d`O4T$vKwvVKQ z8%-W%H2d`m9UDcdnmqN3HtlcHd|BjzW9-(Qmo-BLMpfk&M;=?>J$Jr;bU)r`dR{Gv zdtIQ#9GU7U87IzmKE4ySeR_0tr&#pQ~X?QGVlwM%_cqrn}l2|}RNj<8d?F(h~7XTC`lqc~#va?i&5BAM={FvmI=4vihdha;Rbc?0v)mLIk&E{WiXSE5cS3`J1cJyO4 zV!2ih#rEnp<9ysvb)p+)~@nd>bYosr~p`Zap!t88nD5|i(+ASx_--zyLEKpD-$7<7WC~o?QyhqoX zw1Vh<_6_aGKR*i%+!Mu;TQ1Qn+JhfJwf-^6s-_=nw5<oddLsTc|RH?R8w=vrfxv>7ZC$Igd{SJNXr}^vuAqS zYqftEmR8m{E~cObwSxe%&xZXs223I35&fmU-3oZp4qe2uu}U!1Y=}AZrJ-NqjWcw$ z>{x{hnh~<7Bj_;y)n4?YmrstN8p8bnKTn}FEcSdjapO=R!Ac39++F8zYpGU~o{o_3JZ&fEwe(|_JHbeH|rv{+5Gez_uLESK>Y zyqfLejXB4q+M-jz{wMOHQL7cd5Ki2L@ zE8d8=(RkZ^TNPP~3jozek!j!7o4-MV1I*6n{Ox<1a#1r{+)H$tE}#Em=sSjFt=x8h zVyLh>u7!%0Z9PY#L@;m6J%Y~qs|HEmL;N&?|JL?e3y_yE(S2H~f@*Ppwu_}lOyj#* zzWcU~Je|4T4jCLQu_x9INBrFbn}WDuGF7}U(#L2;%~gt+XC4WmQ}yVS4+;<(O%43_ z`|nKIL353W%;(CSM#)F!+`0WI6leG#Y6IfLp)NG|B6|b5#YreX9C2O^C%|aW9tRB+ z^j7A<2w_N?InrrOYP{;?H2a~2fO{*PY-gUQZ9RLkRO^xklZC7kIoOZ%t9Hp`5$GKZ*tJCEH>@#!&9n9fejIG6%AAgCNnJ#I}u4=UjnMf z-SVGLfA{$1N&WTpb=E)B`JP70ADJ{^R?vgf=~5tj$+f(-pxr4~TceY7EK`1;@-9A! z|D$rOaxJ7;P)})fPU8`e*TvE;K+KG1a@4sKRG4*nU5UoCsZc6(s^xT9F{~BtZ7=~* zG!@AeSzjKxAT`#6;F!g8J*cTyAhuF#30B9TQmq%LwY7bXZ)2z4oPkWTDX;iJ+0v7P zW^)Gj7V6EzflWB!G<9}xOS!p@77uj**FSwLHVFmf9A>SyaPY@d^ULnNP2Ln{X7aJ1 z?&Ql_qrq{YHjwU>yJ0B6o9UK92PGF*5OU-QT_2w^-u+Pru5O+`-(Lyqf^LUO7_m++ zp#b9y8C9w!vSNcBX5xzL$v{~$_MQWOLFB&#jIURez-fR0y)+!?UNs_0h4PJ4{s)=@PPBi;*5l4@V*dtJP?Tnz{T4LSUW$+Y zqeNOwI{Sx)%+JuOzJ#q{zW7)Um41lEIny9S!ay^?iPUOmkcN3|FjiLBLJ|t2N{rPy z3Kl$gTdZPmF?25)y+%nCA!ctCv0@}!o_L?!g5+S-*C=E#F$l#3Gxn;Rn&6xq^;?b_-y_^EIJ?230J!M#LZ>)^w$@ zp0*#B14W8;K1wMC5)dS_Y2zp_!*;% z%rVIDIOgw=X@}Gs6>I zdIeC&@b|J_ryf1-oGlEe3haYTry30|HxH}7N8XnD=MM;8JfJ8brNFIzz4_Ias8 zg0Zjw`RzfTr1CDS>*PIw1J~9Cj)~9lrH?A6AryVqSy&X4@0QQEl!F(3!J3h_jOEtk zIHy~$CZ7ScXa&T-o{cmsUn{ph?{6Oj>7%StCsM&Od#4ipto^#*bG`@Ns8az0lZBL82&4$_Nbksti(KybiwHHsXM7AAgipMz` zkn@wY7%l{k`uLutu2Eu%Q@rfQq4$CV#QnYrxR_O~J8vFzYxA*fjj&BVKfRg3*B!HG z7?4?#xpkrYSbeBghlNs|S3&xQQL5@~PbFpwkt{+TXl}AIik;*}auN8hJ~X_i?ElXv zt|+SWrSL1bZN{;K3G8+-r>tUBOJ!#Zma`ep1@Gni09RNdW>Zvu32`qLA`%MysTH4o zZ9XV=BqC0-YJcyD^3vZgs%60u|A&LCBMToeHp(Hx^vt?OfSPsy%4Rw+p&%Q`NEAvm zfuzo?WJAgz4=qQ7dl3eL*(l#R^0|EH^WKD#B(}Z=eik^6S z)>2FHy^@qO;gKZ^711b(3wj6-ww4UgVbB~l?j`yi=X$S!tJ6!;v{>KgXzH>Lt(isw zdT)0%$Px;*h|=<9ppObIN-XH4h&Ga~{UG+sfw!Z#$~8oYGt_|?q|o*@Sfsh?5c&}M z#8$jd_EA5b^}02BdQ3cLt#=}dh40&_vVwEZ0ueppNM|2opNC0V9}k}{7DFl(RjV4J zBsI1R(A4>hpy^$jv%>EKWDYufk5O44`>OnpDH&CKSyM(+7Kp}+m~yHn&GUTpBKezt z!{eg-M#b3Atr?{W{R2W&<`6)p8~#@Zahrc{!U%EKn%Wz*aD$!{8mU1t-6>9i-^@?G z5|xvu9gj}09z0n4q{#6 z-?TkN7WUfLa{@#%)qnS%pn&XwxE}$iP3TnMVfL5dJrv5leFQXweiMe@}j8LFY7Qxx)?&rtlb!w}Ob0<%#g!WB| z?Sroo$Hx7YqV6+g#e8MfhvAZ(m%@4^?8wWY^XFOB%ELl=DN0DcwbvCy(AH|=7{Yxz z_d`P0D;gGG7FvjP>nCr?%iwHgO*3woN#F~J^y>S3f-zeme0lOp>vD)F-rdJg95ky( zBhs4oF$?|KKw}haNy3R3cnnUmFe%0y*8@^LNo*fwD;jk~|H7|+IjbwzyFDgd4SCU7 z>g4CkL+mfC#bS(W~ivSeXwpyJ(s;k2JI6tT0Q71*0_XtG8u1n=g|Xi2I4pEhXK~x79*< zR9^f+Da>}eXLGUK_5Kt%5tea;FdKHd>$xO=F3opJQ!5e!d3{$af--|q^ApWOLV^P| zY9J1CLlO3un9YpL#rr zd*No}Yjy_LWN=)y`~Xpm@+!l+ME-JkFqTyQh5J)qMcpC)W5?%m(>wX^i?Fa%-uy3G2VBY@q_|76) za~c79!+OPP5C91rv46aflJr;!BSPP-GD=MSS&SQ#mYK;dGz^l*mw1y@{XfA9Pn(4V zdXSucdaXp7OKTSVp}p)f|B)ZF%gPg&K~$(J!W{6%Mg$2tt_?^NlBX#ul}WI8Bc0SS z3iNs8PN06z5}6qCqB)PB4qBlytH=JxLL%Rr4hoDg!!VQxBqN>OsYzxDE{TMO?LPC} z35OzdjwsjcEKMv+Imv4Amk6rjA!Dkoz%a)cDD%-3QXfxK+v|Ek>8l8UgR25@+VEA3 z3lz1_VbPda39!y}{}*|=a4c9c>h)TqQVD@zIf8N^@@j)p>+kg26D<0WRLpU+On#G8 zD9V9z9KCUizB_l5f?)Pjp)9k|5mT#uv9)r3HmpJb9TR zaP;h0rBhjY#St%tZz(6PG;?RxjKn9%G7~a=_3oNK7sr{n?kTKH%bTPp)cy2uI_6`b z<0H*rK!p84vD45XN{#=VX=WVAE6)$QlYkCanc@Uz6$+g-P&k@rGDZ^z{FjyzE!FlG&d;B-6jKB7dqGJ1fBWf9n6zl=LSgHr zd9VtrkbaV^8s0Vid{S~0gD*@$3^coyjf=~lx2*FA71BwM5SOo3D0T-+|6~o>L=2>< z_vXByGWf)*L~)d@13=$kv=S8I+5B1ygC0giR zeGr;V#4(-eq7isvIC2tcy6(7MTK3HsryGe{z8e9M9#-%sZB}3r7LzAF@Y}Tpx`nD~ z)u)fX>IitkGPe7Xuhb+H?La2pi1J~5zWL`wkJd6+lQKmsM5{>k3u{O z+NCfw5!h3nPF>6PVhBcH0&En0P%=H|Ux;@AI0BjAyn`5>$6HPL1TA!J+_CpnZ9LpW z^oCIPOPS}@U)~CX91Zzwk2`7JBI~~pc})(z-j8p;3!oXJm1G9kw=FRs1wf|C|J0Ms zJkogfGnxD(&yc*I%_kR;XKC#a&< zAoTKBx+u~;Qfn}Dg|&^fRPC3B0s*%`35}5!PdWa+1R4=7{qQ3A;9w)6#>E%Z2W+=& zsTCL?nC3yxxZ?Ytbi41bxkJ~CLZAhQd2YREA1VvK3CKv4MzLT(Ys_Hl+EH|9`u%*gE*3k%K^>P@4uSvW z$GLd;HdS=o{gVNb8vzU`s3%E$njyGsx|}(uj_y+}Pl>$mxqYp_OlllA3;aEgDfl~1vD zTz=RvsCCMWS7z|1YXA6)Vjlu$4EP?-q=}0nW;RFPWk^v^hf{{ZWwB%t%m&>=NvmJL%vF5@H2eNaF zk2{owv9;!BI7b1w29c@i;I{P3~Dx@}9TRAgqPP8MX_PaP0MBbv3?>wC;kVIe_ zFLQNknqRk^gIaA3ztze*I7Yao@3OIfEllEixXAK|!n70jZ(jxtspG;Kw-_2-TF?q= zG;Vb7y12T=_H|GL6u76)s^v7?mwtz zWRA{(Y&uZR{hyypGR&Z|^76v`*hah>`hq(VbhRq@ysvrUhui%P@E%{^h{F8Zg)DdJ z(;3<%=eUT0HWIU_veezbBz*klOX>CJr4W}Hu>o^k{8U7Ds~;xi{%tYSkyfr;QyqOf zn3y>px&n+ST;z5FF>Y%<-(hUhL+WD>*LtbNUGw%H-YYG!qj}Jbd50`81&A?PpogEd zZtGPp{@(TUvMt~`P9iO&WVD*Ns5N{rcTATg>cbUTA*9_IrF%6ioN}|r8Ho>xk)%3| z$(&LSue=iR8;b^D5G!P~*g_h_8>-E}RPA4U)`=c59D+6JBn)4N1w9toqPhe9EaY7I zVhKb%Xg&5gT(ssx7tipwe5!Xn)#n$sZ3U@CQ52ox7d&ODxgf`DG1e5y#>u zAg`QBfy+m@W*^;+wCY$GBd%oNQV|L3nAQ*glXa!hSa9uJ~YV2l~H(gi|H z9t8v;00*+?#ew;iZkm1J!>a+0={P@2s?L^(3ys`Wi_j0m^b?o@sUBk-BBgA}Nu+N= z&o_d@pd8XQEW!bp07rl5^Q64l%)%HT5UpOwMGmA*~3Z3TJ+Q+>DfwSK&PshvZ z15o--W8=|0JKplRT5n|+0KZpwqPcC!9v1bK!c&A^6IG|m)li$i0;c{wjfH!Ju8<83 z{RTO_$D)k&l{~KQBdT5aynkql<;?cnw0R-cFs@+-#=+705PE)2e06mePHxNf*+R-k z=h$lPbk8Y1E#a)A-IJ@H@vH?ptCR|th?I2FPGjCxRZqG!BiF>ACFDw{JygXBzn}v_9}NmpkH9}#t*Ji{RylTg_%?;F>CHpv;rih0*$&l>j)Z-3J!xlJ}jQ7j0f(wk37s1vjb^EwD zkfJ}f3tU4^^#4$JYEX-EbdWQqMZj&k4DE1)1bWp(zJMFYVLZ_|{PxZ-=YX0-U>QcQ zLp2;&NB~tD?~AR!60YNgiGDxoK;8JW_7W0B>aXV9cjL%Gm-V+v*&?xAqt1{-F2!_b zx8GnKU`^ECpgdB2^B_Ui`jbL7PaO_ zY2dqFP7Qgip?_J1D0mpz_v?DUko7Tz_VU|FO@k2{U1=iyIfwoq@uKOW*iYD(EJ1!Y z81B3nug|E2n?uNKK}|k#Qr*QSJcSGh%&H(&EmrD96m+jM&~K0MBdM#@1B?VKGY zG9IiHobJ9&W4g-bfH6XQ-aX>F7y}0y3%1vhkp*OY@LKH=52L~9oyIsGvwppjVdUb|83m|-5z~@y)-A>-H{H6|( zeaY!#5NC|?)OZLcV7i8rZM_u(HPw0a&%P*;Zu5261K8gG62artWV3}$hz04L$yFC+ z-AO&S%F6C3bGqCuKXj{!i-ceEMaOil2=n_%tBX}(&R1lgm1}p(G>diT=6TT0ECzJ& zp|6%}bRr$QD1>oN8D`LZptdt1GVc1Nv6u5H=FNIDdKR_0T-cih5{h19p60@$>ink! zX9nG->R-t|TfdtZg$p#L8tg%^9TXKz#HR=gv+04%YphG`2N(9 zmi68W{+oSK1R~YE2Yd^L8@CH89$H--oCKOUP7LdtS-u*E`QkS%=q)>TDy5J0yTHQ6tB6f>eP&!GE}Z>`Ds0VzNlFf%(#naRn^iHw#Owr-se%94`0HFqn)@|g%B2A z+K+qMhM>scp3kg~$H>I9=1U&0uF}>Ldsi;GugTsiV9=aJ{7 zW7kNxKc;6Ei&uN6qPB<4M&*gF$K`jW>7>-#C)}Z0O1X}k@B9ZV?B)&6xLcq?A0)LF zyUEFwnzoOoEq>?i2S37BBKlvKcV7_bC}x#>eAJ|LUF#Ka-&QyTJ3J_xo%!9706KIQ za^&o}2fFr7soI`v4nDgyGuuhi5t86j?gA*OyWDCsGFV@=net>9r(n8M1jf}^l(8tn zA9hEyUJrg63%V4Cc_`VL6@_PEm+b?kGE<*i_McgDDqU|jrJ#N8tGuURK)mr#(Wz&_ z&LI27ay#1{PxM=mhyoD6-PMVV!v6}yop zI$9a?tErg=IslMrP*SDIc4>%w=-So*S|%e&UufJCpWVR1Wfc*b))%MGO=$ZS&>G#J zk7r^w3zvRrbQw$P+(C${(Eu8gSDV5D{=7G3VLx3w4k%?VfXf5$Y725*4}+8Z&$Kns zQ<|qomU@p_AIc9Ar0$LWosd9$?4gv^_czm9)vf^}Dqlt?Cnag=x@Sq3^8L^duJ1{+ z=mB@KkK4feSSTpx?v%V(fs6<}!w}BbMZ(TFU~QA&cm`NC0o2i2F*X++O>f8Sq+ zpUn7XDW1%~vR-=c<*BE++1dOmqpShInofhCkV#d5Lx4jfTy7vTW421E1vU4aD!zyHVQpSML`1-wV_Y(M&pTiEht1EVOvjDMvJMUV96$BX)-IP+pCgHLS^$Xs2FNmF1zl66p-Rzb8v znOgQhTQEQmkVH-r_)^K?v{QmD4^SR1f8I|mp04#iZtiZh!9meE&djDp?c;jcTnG$7 z0tgWC@bUkuG6OVV0arQxFT%yt!SIVI=EKrHv!pJTuu*?*S*FZQm4vqIo|i{nTMn5Dk{3fpQeM1hhKk8|f$sR- z0x@aRmwQr;>SLY<^d>9Y7`N-A?YODX67@SH$KuPQpe=r1Yf_feqKRX#y z77G+vbw40i zC0F2<@eD>dDD{z3Y!1R^;_V^`K(c~y=QI{1u^2(i<-G8t3Ntlq-|~^+#R3Oo{3`3< zJCK$P95Pmzc4yn{s96tMaj)xD6-^(!OOzmXN_m3*7~ z*%>&iGritqKbEG1YM`xQS=f?!h9+gzi;CW++NG>6fF4|&bR|YrFhD}I{`YTCHV9sj z8t;3z1*IT(|G6y@tq!;H)>5%Q#4rE|hs+fW2GMNC6_8Wra(#P!rD(K_PS@?V+|9fk zpIG>OU|7&h_WNLxZ1G#~XWNtMzV?Z8UfIKN75DZ6+YQS<2Pl?89H|OleupIiImQ>i1p{$xhA*G+!Tu zb={dwYcO97;0HI{|8OJktBd^=k&; z`)eaqrSaebBH6YQfUW$$@hewKZwR15kY)fG8$*vM__UD|C!@(XF6T+n7bH(w%O>Lx zHHO-+F|*w${R58>#XaZ^S#4{dNKq#;+Q?`iA^ol#)Zw)Dc%Zc`ge=_=3(fI8h6j!u z5DzfgoOiyYE$$4oVG4jr$$0cGgR(5NrH=b~5{c|zeu`TJ4s3&xs~K!5be+~f_Af~x^}>CGhJW@0FEK(kQThn=p$Jinv|K=U$?GLe zgURWnL$Sl_ioIil>;Or^gXM<5BEUAZ2o7{pV+dLlsWBVlMmicaSthjSKvGYR9UF3G z{87A~W(A9p!sg+D2DSx6E1j^N$Q+~@1t-0AWoT-c{S5W_i0R-AG zme0g~lqhS+VQ$poQsKDxHuGgyt6f@Ml8I6XBKP0u1ymTpHZ!z1x4J5fMq3jIY9UMj zmMHCRWhWBLx^5UcJYP(Tvay6_hByKzK3_*xtN3$CDF6W~y+T+Zx}MF%afuxk121K} zDg&yd+xu!`jaW%-X%#EZib(*``|n>Z7*wF!*!J_=cz^z2OK1UWbuBKh%OW3#Pv>~M zYw`KCrh%I?4JbK5Q6w+21OY^-P3BMDCd`G@{FH|WScixbro(-d9;6A-!H*=J(~QHZ zG59%jy!@m^CpVo1*lAk9=SJ~t@|9zQwTI+J?E3(3obY4pJ9HphMcS-QOK(8(-LJ5* zQuWJDda#siCN(x8J_JDjw}P}t!n^I;nA^NW+$(NPp$XWY(;7n)ccrpz!{vJsW#*y= z|3glYv_%Cb%D3z$5lQJSSmzY^(J9ZJXP!o<_kO=L>>@&N-{yyEHO#Pm@AdB>6hy8v zDoM)l2Zi)t41VK>Z!_!ax1Do{gb1re#i1uv-5!%jAMj%8hxexC@eUgO+{NUoUTF+~ z@z^wQbh{gfpCv~^%7lW7u3xZ%U{nn>M+BnHhQ&_5>ROT^S;GRz9Pe`c$Y&=`5ZArO z^@NNj-28XFW4L^uMk@DzJGr0c?c;cwbfmrOw3-;Y!AuSs3-rIuaeG)p3M<>lOHueR z6*llPX+JL5yK$qNh#}|ylPXzUt@EVmOh%Nk_(iw0!2LSj|FHFv_r=Dep|;RCg%V6) zaL-!&0`+;`|8(W{`JZ_U!U1^;^ejYe{EY0^$$p$HC>1i*x>%b$^uDt1gtHp_H$JaR z%|H-9-8YpIx)B|!?V0`g);3gcOxR`#O&pb8j*1!>N*=ds1iVcJlCFAJG+4^sS5K)g58`{KNw29Y-A*; z*t6bfL3#7*RwRu;o|fuE&0=PIec`|H$v;h?SEx=N*ZJ)7lXV!>%OO{mkV-iBuckTs zr3liBBPB-b^MTbP)TeyT#_dcD+JCkgt>1C&q2@29#hT=W4G4(fsAPr*47ciQPA2LU zP$}uBRG)UYK4iu-T7T5pAdvgxfKc6U_z3`DHQ_dX;d$LQxn#@Y`U=RyojO7bP;Z8spCQCv2am&lI|HxnZjZ$~&1L-MaU9Y;#o(ZX<8^4ZX2Zz7q zAK$RU0t4ZITowC&+OsrP>K-VEY#2@5s5pt6<>s2kGg}eI({`!ss;Op(IOyO+i*i#b zw7c#3YLZ3s6N_8uMxlRHLT>j59a80UXsS5pOvnx}Bx-V|uP=A|nWJ=OOKPLBoG>qK0b0=xZ%c;+ba#81 z(*7AVmf8;7hTTXrbJz)TZA5_g_K94LHl-;Qyo?T* zMp|pCG`Ut{=d3?X6b;;a$7v?CK z=qiE!~H7b`*wj_2{R3?Z&BTvrLltBJc{J_ZwKy3#Nq6H~wN?V^mg2`X^?=W2t zB;lMh+npx^zyMBs*}Z|D7Y>%Wl=nz>CR~Ky=0Xu-q(YAt6d=HOX7jhZMnJy>8rWuR zX{a-EJ#k8WoOF4YbDv61t{}u)tC6Ys!rzUaX@e;V0O`tZ|3G_ot)~9jn@m=G26@#^ zu`{<#SZ%2tT|f@Xztp&QdR8Y&{v-yvOBZn%%z%Q-N@IH&V*9q$6R*R~AFdCbJX&)= z#U7es+sF3jmvjlwvFU4Zf>OiShRF1P!3vslbL)-}a{>k2APQ(Il5=RX-d+Xgh@l); zgnDAcEMCK&?%u&ntmNnBP9`?bnP3l99i<@aKmTgQA1w^&TnHMowpv`@N}c-hCb$L5 zLmj74J6@^(C%f>3)*ZWWLubp^>o@yUOGV#gO06W8R(npnZ)DqZHfk72R;F{|$F(p+ z6jx8M007b9oFOd=cqj1MLi`s2Wc&mcZ<1qJ8Y}?L?C&6r1Bm=3+2McbAq#<^^)`*? zl@_Rjr;d~Ni|5^6riZ~0*L;g<(2Nd)sCEfuNQ+7$8f5_BA5Z6Oc)aW^MG>@Mii^s? zjYub`bl!bzz<1jc)*%B2)*oF;w7lFp%cPi~CDjA;dF9f#GH>SSr=-{n(AL87^ zZf3>h9zL%QVtCuaCC%nII&pbmYDp>u2D(~@a?I!*546QvlUp1q0C9uf6qi}0JZFjC{h7D2Q z;=DMN`|fX^4(RL16s1oQ!uK9)x!3wYJ>{F+dLcd-(0=5L3l5PH0%3yFAj=)r04x1% zoewtLV0*_B8|X$xT;c-U?eR_ulpqE=-znT*CkBq;qO~~5hlaOmTUIjz!kR+{hA2)K z5adtx?P21k@U>QbeHz1OAlyurkZ5|q1fGb?EWCi{O@@hRY9krhh1>p?bFP&a_0XOD z#)*IlT!kt^6vgMBDzBow?X>mV-70)RU2qeV$II*8UqG>8>~oIK`aYiI#%D!QoyjB) zOTbY~TyaAvkAd@+j?@4c3R`wO2%MAj<+*iTiGD}%x;Kb&Lka5ix9J<7OwNF286Ktx z{`NX9d#z@p#tEmI2=^7`)KTA>LP2{~T@(!HD=<;-5-3=4_niGwZ+2I;60k1;8f=fJ zgiCr6K?n>YqxkIV!0oP1(6Ex~Z(HZjN`ss*AB4?8rmIrEg36=PJtRoXroXSYYc|eO z_WnN6DNWL%jROiO&vDK{nr%MB<`bm^&3u4>7inEus0I-h;0@+#k%c!7MiM?Q#M?Tz zJ!*|5{MQh#eN2gDk)rCsvE%xw@1APr3u;P==x6Q-9-BCZ^Wf#G*Z#HmzPlCJ+@oj- z0r{7K&aVOEuztgayfJuxtx9e=3Y+n}$o{VuK(L?hy2tsFkE6Hf%fppF8w&*d^p}Pa zlgBg8_e?hkVbEGD^&rn(VVFt8wZp>jk`}`n|D0a=(qgs_gRv2}2U-yz+ShGGiB~N_ zxqUvYDygx&sAFl(FYm|}I$B>ER{ywlApN1E;D*Y7fwuM#At#|TMe(P_oTH%bD3}aW z4nod&q70klfN%5&as&A9PGAJf@rLBN_z1UpuH%EbSmeDtGc1*efmBhL&03`$uxi*tY5hI0XckpH74 zcn_O79z(X3fVc>yC4L=|PTs>BD z_kj0y9Mw7^W>}{0uK!4%LOK-QLK5c#ePJ}GArD_y#!(9vXPoBqZGoRW`1UY}k%VLw z`f(+@-mp~R~ zhEfbGHEVH(E*lfaY)V^8(^2j2j^9_ey3EGL3|C=&5gN{~(;n~0HV{MYImWCeT@QP0 zO-P2+m{fvVWet!M!421X`xFsSASW1w(QY7O__K~w&vByl@LTRT*xkX~iSM01T+MI1 zp6?&U5nl_l!oT4`GcRnpYQ0^>+nL78VG39<`kzTWJ0p}%;CH7~THtH=81%Y1gXgX; zY9}Q+tj-`=3hMg)e%Xxh#sOy7w|y#*X(YrQa*4ZZF39-m-+OC*1=HYHAvSicH&IUQ zh3dNN<~mmJvbY^~-ui|97^0>`HQ;GXd}&3mg}O(qS5cVUY=HsX`(lLva7Go)EsnXA z1-#oec5OzZD)ilcw+mX(Dr0;u0_p_TzT?bH`=-kqv}Wxwx z4Hl1%K9~oIKDGIKtgn}=7hh)Fj%3Ch*$z#dE#u;W3oE+4?$vUgd~g#;yB@gS=7dc; zS~Tu*t;Xw(#|tR$H1@Rm#vzIoC4vsAgrcPmd_7Ahr7gOIx~b0ew;ftK-N9$wxaF4X zLHD4aVe&VpF@g3~ zGWj-(;kKU{OFYc|N3YSSuQ1>Sm(#X7R z0d-#c9zSnnys|hih7o9)8vBaHr<+JIa`+`l5=H(!yLi|C+`Ro1C#C^y?mSdx?{4|n zROS2FaZPJIOx-_QKn8qJPT;f8C4r3DWZ$e;zz!9_Qr#{kn*&`Z zZ~nK2Y1+3hz&)!^T5>6?get-PKBAxHyWWbGN0^A54>-HFMCX%+-f3aLU*%sFla$J) z$Ak@#B?7)*6ZqLxJuP}qg2A2_Q2QkzTaMQI6ods*^#1q9cSB!HYbE5~649Oy^C`e0 zi4s>{dL6}cos8MV_)3?&(jq~JbFx_*o=8w&M!*ivpAj37gWF~PQWA%fGO=@aS{;6l z2Wh1=N`wIvD>>XL5`JX~xlIdbEZ~GG(LWTcn`-w~#snOspFBE1FR&8vA=zLq#U!&y zHeFa3XKyYAQy1Uk6T*>g0w+xj6o6Ca9eylO#&7)H*Pr_4s`D34ho7QM0jAa6fyVyQ zRYL_j^D6G7xX@2I z=Bm1hP*ZVIC)V0OvdNB|eiU^_n<)K86kpI{#uF7jn!k>YJ<>I>7viq0IP_B^-3P>k zQHUD(jJG>PN6GquTm7-I{k@-e`8Z2$1izSg%UuWI^@ho*-Cc^w>k$d1*I{1D%M9%a z3oxTpH8uszJZt^QIL?uIS^s2WnEFSow5bNh3ik?GgBl^$CV?9JKvvIUf#;Z*y!xd{}VIN_%-94ck?V!n<4* zZ_!GD`DL&<{huS#_mq+W9ERQIi!dU?A%h8mdH?Iq<*3JVA-b;%8j=mi$j?>!0(mW~!X{TgZHu_%A~wOs-zNR%&XKT_*nOa~}}%a+ssj7jT`!d>9%dflAr zzUm-DavVZ7{F{4z$r5Bo49>ov^>WOY#^cgSI&m0hINK~NFVJY`S!TH(epifI2O)bf zGfkkoj7_C%ajQU|>HpDm&CzlFPy5DhlE$`eTWxIHwi-KW*tD^2HZ~fwv28cT2Je1< z=e+;!xo3Cp+_}%pJTvor!0a_??~wHZP~AnERy{wdTgl`}5$G5q`d&IM^jKV)ASb1x z48QA8>pZe+V7jfiul||@rY)xEbq>*%l})eQ@6GwriZf-=#wV&~p>%kR>eqvI2M~-$ z8Z1jktCOrFLKVvp?_u}| z2ujDrn^+%_)6(tnboI0gRJ8LMlSFe`Vm1~>-iVY=n&knT_Ebk`8jmp1KE}}%iRCNN ztUr|_WeJ`A+J4shH)h#^q_$Qb_9NQW0)3js6xA_h*ST$bzCbLMyyPaIk+(R~8qZ5- z6M?qu1-GpQT_{GZ)uJR>I~X3ZAc^YO`jha~i;t(tti7zQsydIoC*Mn~?u2mZ=2q9h zgiht1!}d_U+ZW3u`uoq7y*fW)8%WLJ39^1-LC>Pnbo*;#K>?yMQjxCSP>=s~LrK2S zVJSB`g?Rh24$Yo@dlWV{#{~z;7abu;LJ4lPH9kYWUD$8!wF7bceW+XQpv!yDuv0!m zQ=ilBSOGQeN?{lreVn4jQX^sahbEq@mvU4$-{$}<^tw4b&O~Y?vCeD+^C6#3$sWd> zMOo&;x!Khm!&njHJ-1k#DW{T$3s?YIg4>8D|F?;<(MpP5D;LoA&n0oxCZ>GisqK4W z+e&G|VuV!_ta0y8l@10mVNU4!5JpK$-or(iM7;k(+kmbymqWx1+fV;`BAT0NFm$<4 zwB6Ul>Dp1#cHgKjN1V$qDo#r#lS7@g+XsPrdqOwaB!o}$_iHT*Vmm?EBd!s3AnvD~@@@tv9+A{i&w*5GUFo=k1=q zB!gbO`kGZLRsQ8sTZ~o`_>w_e50&9sDAq*8%Gp1Q_#d4@-cG! zWVqRab4`9X8?=?b)n(58$Z<;=sb;LhNI|ygAKxG~4fhbz#aQ>cu^O5&wb1Wf!-qL! zG`u0&j=UwO3cfNl$gUxJg+>6eQl`NdHfreWcu|HnGTMaE*%c&BvsMHe%rNa59J26L zrn95V4Y;G{CY$}H2mM=gtd3b_cUaPrL)K2wc_sTW#+`Zf1V-QVs<)` zRlaB_H(6lX8l`>}ns-W4$sXi#O+GJ%#apoKWt@;xu z#Cy0TzV7^-DR~Tf1iyC@RnmfeJNdcM(O<}IjhkVwKSnU3!Um%C3tEIavM7Y;Mk)$= zBX`M>C)*D2nX{i`$bbV=5h01jFhq-4;2&wd1siKG4mJl`rPHWH zjLc%`14+iam1_t^)Ir4P!O+xL{!USWkBc1wbS%M2)k<*EemH5(U=llpgP3Eb$TR+N zrcqq**SG`QH>VzsN*3Wsu7XAav137_PUDfzvTyRTab;o35&3-oY~!Z6D)`lj+pNyu zmaHZRbI~NBBjibwl_{gJpuwv1#{p=cP{6f=U9`s2MDTw}6j3!{x~GyI(+M%g5&@Pi zOa`0u&hQg+-+d6bA27nhB*oiw8{E&n-$q#~FIcZs9MYEjGV@3%RaT&QF-cf+eL zHs{*D2a5ulm2nnm}Nmoj3KV$3KCR#CgCRTzlSn5-B?>u;IC|?7@CjacpW=6 z@}D~F$dTG_^za^bvo3#{)5&nj0orN}TBs$5jY~3)FdG)lP2@Myf2o>CT=Ks%VW+P0 z%xT|rtD8{R`%s20ktB%5dT$%`*<&N>=A<)oOLb*E=G4|^9Xh>mV0guX`Zn7GfC z>-(Cn-(afPA|5Z!u?Xq9b4Y94X9rf}6cX~#i|hXbi{H9)hU8Y3fv<~+W9ZoQFwvKs zE^oYEe!Fov)35KfZ`Bu^V`U;Yqi>+g4P=jN$-aD&oTt^JT)TQ(YA~e0$W2S?@DjfokDQ5*uD;57`NV%1e_;i{aO!Rw5q z-X&b@+jW94Qar9oMTp0X4j-#d4{H6~ZjQ@Sit3_C6W_8CvyA*RoxUjjC z$lXB}ewP5}0_I#x+209PJihzW+)mR;fnkWKp1ef!WrV@%x%1o=B%xQl2#HVZ-0*8K zRKz|}v9%AD$Agn3J(HQlXGdMxP9?k@)=ifl4H-~2YuhLG+fhGep;sQk;|X8%A#2jC z-tD^HwW(b#K>S)>q+XB@FOC-G^k(gC+bD%~ia$1@%v^<_TCoQgIZc>tT~n7jBH2~K zk))a(U}rj7tb<;tYI`jv;A_8of24FmeI!iw1Q*cW=#uAZE=Y$(UmU%s_NSJ_29JD> zaJ8r7`HPFI?fzdqdTYFb_pX+p*?UtT-Lo-!ldmH3QR+rkw~4mhoG(K&RQ)@}obKYx0-6p?!P|wZ!E7RuCcM{+qy10jZkduYnaBrz z7|N;&L?RsiH~I~WHc}K~bEOQ$meaA!ZJd9aZ4bs?PfQ*kCanV8&2=ouC41o$TGrnF z%ej_pe!A>Pg7c;60J*b&CF+t=)m7=>ff-nK^qx+*EV3J4&mlv;SVsV{ibv@#PClql zOjfhPbPxt`zzbjV(mGYb;r!=Ue%*oAB-LNip@_zSH6(fDSZF2kAF_y5sc1F;!#_|5 zoU+RZ8D4&=#|5_rtltJ_?;Rb!t`^McP&%?m7FrK{SwF#_XqFcYrD3l>q)1F#;QE2m ziS0p`{2fy7SVk{pMkoyhJmxsWww7b9sUJqw4$GdiwhNJZCePm7O1GQkaaSyPCINcL z!L%C-o5XGQC=DUCkAI3SB{XSl9Bmv<)z3JlXsk}{s(X|X4Z>ukyrO&Xa9 z=Kbtf1qjeHk!IGZe|}|Z<8;I~6_&(7?`UyaeYq6K+-f4k-pSfvNfWDt7SpFeOUFCA zX=n-KfNN?;;Nyw)!+RTIQA~?BFtoSQUggzZX%TvvB{+uFe(-Lyz*=A%Cpphv>5)l? z1VruFw5nv-wGDJun=uBW4XSxp>FRgL(72UHxWO5TC)0do`{(?{7yFZ@mcyI}i*QG7 zm+T*^XN&6&@}x2~M=)5$qTzB>#zV{E>|DTK)ks;hGe5~GTr=LSghhVj)MkhXan2I~ z#3FUMLoTx@1w7lvMf!QURZ?;ctqo~8?Ol`Ts1ZHPQdaf6lo}3k=Q!<~fj+mdOGl~< z)}PC*0gNj@#BoM7!2hAW$M{QFp9(lQOE4 zZbS~;6b8qw0W@5_5e%}712xNsog(bI{PGL9QD@Lc@O~!&qCDSuVj(*8<8k+`^;=!D9N&cr zh7KCBU{k9HBcfhi-}FrgFnhPzQx^+RxqFD*=aB_|Ubd~!(t57o|BmGd8aL3bw0vw{ zW0h>pV!)FK8^7SFEt>pna8>Z;9FU$kgq?j|V&+;phqt?5D~`5ALT#V3dD1b>da=1} z3kd$;C)S8Z=zW!!H5e_WXeV;OxatsDn6t?xVnc*cmQ7duq0lbjAMQY4n|vjD$fk?Z> zjMh$7LnfpZZ6X>klX%FQ;J@sDAY+-x1@j{Do~{Fj4mX-KWeq=!ALmv8gPCF}cp;>( z%A=kLmSYsm5;)>Mr+?pF&r08%niz;fzERDhn^R87mxp@N2w=7vLmT77|A@tYQ)F%8 z(^SNd*6Dn|ssQ!8e<_|;NI8~aA zfla~I>gv^re7|VTMI){&18;hu>p2lkkV!K5(u&T| z^%J5cz8p>F0~9EZgO};wQoERkeu4XNE)6t=&a0jc)~Hpz;%Bsl6|-sF>htck+S)YCy@wKxO&UC zv@q=OsaafRBXNU7*(tiR#d;vIDc)fzEj1Vu+SEoc;?^BEleW42nf~o@E z;rDa+0$T4FaL_?EyesTwmNXm)iCD{_$ib_uL>UDDB$yTRqaxwEV$8*sZCnd>g z;tk>M#i05oC&|)jyWVGUL?CIFYsyB*{c%cUW5xz0%Jw}=OyasOvP&MJ+|QD=8&)zj z=u}N!n%(7Pk~+Slq9Sr&+ia-`b3j$V`V7+Qz@wS%Y)Y<3N_BuKfppe=@w@Xqe=l;L z5sDmA_=0pUtpwP_`yr?F8$~58MaYFJ@){A{GuM3X08i;NYz#@>Uo+!3-5F093Dfaek`5~>z8Z_qZ-lm4_d7=E$YPbqshJHP2Jl*?$7`4P*Ta7e zXyz19NvS4R*YhIJj)NM0vC)!inwguMESw((DbPN`0h|kKn5h~{#Nk2*d4TFy|EbHMFgmx=Id0Y4^SkJEh zMn3WK(mX{ckgjPgK>^mWTIZi>NU0CM!kTTTW(6nL8MZ9b8+?xS^tHG9&KDcYMTQi; zM!_(3tq#1H#X-jMWIfuHQcFEe+^K?eb;e)1ysAto3>IA8WrJ%4$QDh)TwU_&)$iH`LP)oJv~R|a-c`jYAtrh!In3KjOOV`c9GF`s ztiMVG#TYSshoc&Ggy1@7tjMaWlYSlMc@4&V0%u|Mt|qa=9RnqgWEA&t;PQ*kuAa2f zcqnDUzcw1?w{LUuMr+@6SE|>^`GZA^iU=K;JGOh5h;p z7!YSlpE~K)>DoSyYW?b24IqM)^Jn3KlkS?~_gj|RXARk}_xm5O<0~8#leq^2W(jXg zfmRbp&UtsJ!XF2tEsET}!7hKEwgaDA+TL@dF^UaeK!L)~=i^Kr!hgJvx8CJHY8;Sc zJ_%p%&s@DJbMf_&ZTt89EB63-EeS0tpA~KeULW+lNyn_v4QwU|{sz6FGJl$ptL^!h zYNTN@j&>1nzTrz8=(O>H-b5_mbJOwh=GK0a2$KCS z?4$Xl%oVGYN?TssoWzfrT(S-0Q-gd)%0KZF_m8=(NYu$Q^A_?7gaCj!teks{p77K; zrUGIJY1A#c-Ax4>Gy9n;Xe;eK1v%!9inNBogx2WoeP(5J_UB$=8CJw+F57t^ued_< z^;N#DEe@j|vR10qMXqUAzgUOYEDNSv|DT$gtgmBT5y>34?E>~riRN{To5q%%(D4gW zw}-CT_ISU1u}<)3*xDpLLAnrK&J$Io&v=q2cjy3!O?-t819Ob-p}RwEC+7laVq^4|jxtj>0_^wzB^AxS3H`ZfU+2Y7;=#1a3Z@4k|2+QflVVwB?+TQ!lVkd`a>dWn=B5R3dzP&c( z)7e@4{_X{BBvUTVYDXb0L_loTf1ijq^B@&`kl;Pz}>wPuFq*#S+ zSW%S4>r%ogywR?3kgc~g$R0D(gfXmyGhCVymgniHLo#lG9oD&$cnxl;F+FBBoQrxj zxA^Y#t{*?mmz!9lh)p89mx9{Q=#G*WlUH7sclIXN%*=6}rac{XTbzr2vcwV#*XT(o zn_Z+&#YrG-G1Q)Z>haNTj>ucRw!VsIigm+_(4`xK>G0fihR4M!_qaB|cKfek<4A6$ zLeBB@Mx-?p12#YoA+9+rXK0x_y#v-+wYGjaSI^ zLwlUnayX8O1k=D*Oq-JsS_}=R`P*U@00-n$bqc>J{aU~nU!^OKsQjdl+ zfpE{==-}dY=v-%wnyx|0PvSIaa1VKe>wBx=FZs0f_zX%_{e!!C*rhceL=}dj#185( zFN9N(ef>D!(3i(=wg7Mq6K3{Ti7ajyYEt)Zj7>;0=iz@fhz@{wIu*Pmi9mL0xdNh zTUONk?K<0_{=$zhy$D(iyx%%C*!dnij#nI_$IS!t*I3iZyl{|E^}p|}E<2`NN^djh zojhRuMnvs3G^WE*QZo82#D^-vYbaZEy{ic z9E-SLOqjBSp>=l!ksMANC!QC%fxIOC+AtjU=AyVoIV9o6MicnVZ1L2cQ?!snu3RX) zOBuTr+f*`J$^EK>hNQ*zdo~oSAO#|Dg>CB)+N-=&slwuyqACY6LC&Z>?+i$q9l>)R z`VvUGn8mkU6w8!QKjGtpF9UY1@E^busVU{7yWqp_GR{U`Sr8|<>7;m;qv+m9uS944X>qXaY zvb}MW`i8k;0}yS9AEX*d>( z_RlT$6Wn;JQ~vD5Rn&X?6$j?ZN#!+IrNQ%XiGaN58j_Ke+wZ!9mINU@nw`oOehZHh zUCzaU@6|kBRpUaPPR98tVpRb@8mxy_ZyRvW$o~iC^xVyy(6^qr3IFzo?u2A4dyYow zpZn045R3D&YUgcDe64;`#--aJp$^58>M3U5D6-NS(kP2Tu-ctC zp9pB92c))72%yz_y)7C&JLP6BE%x57Z@oQ=a0#%@dNV;)|BpZflCHk^<%pz(qsab+ap+&7 zzkkoi?ISZULbr$GrP>Fm#Wo=3-TNy3Qsku=ggwwSDt>YO@!I6qmX9D?+$9?D`W|<+ zv8H4sZd$EvA)oh)tywVQn%O+5Hxe8&=}94KESy$|Hg+Qh!9zOYpSPpz!VnLZK3*fO z5)|G~ul%d{=EGb0?UW?ee}YH5Mo-Fce!RH3If|b47TmAyYDqE0^7XJwzD(pWkM~Q^coqypNf+dz#=H^yNhGRvbO{Z zb3YUk3v}sf51CGeIl*xpjdZI2ZB#s~@;8AzjzQ0Y$K-9Xs`gSx(qat`c-r_7Hl=U8 z2|Jf}3CSv^$+pt@a8bM%yLJKf-o5WXTC=ZC8O8nAqaqXhk!+J{H6J;)Yz+%lZ|4YF zgl)~f0+t<;f!h>62l@-_XFGTS3&(fhY8fH+oDGa5m7xzR)gU=@aLYW05jE8iHCYKt zywfddR>qLe>*-)FgRo`ubx1J>JnyUwk|Mw+~e9wJv<%c}vecR~aa_=koUNCtrq zV6fb6R%a547l?30s>HN9Z)VEwd9UvRS3_2C&0zk1rLFry3)~o@Vk-l+G`n@DIXRYb zT$2d5mp(V({+aA5Kh#eVYJ2Jdja17Co-BINAlENbV{rwZNOV*xkY{h7hkAWt_vw|c zO3eQGiavB^6To>#6JY?S|a<*Zd=;_l^8% zeC$!_t0d9i4GmN2CuIe{%d|%O0)1@HtA9Pdp37uw-`NJzAzOZu@}iDoBzIs(Fy+dJ zs#PcQRZNLAv6&3Bb8vx7qkW{7B_K?tz{`cM-}Q`(vYcPP;k{}@Y5Tedj>y;CdDZhU zbyD`ehxuVEL)L!qrs-s9mvS8!-|=VPFX&gCAG-wtZzM__45|kwe|X4nQ^q2zCW?e; zd+6Dk44E2u)mft&9<9fRlwZ`3{ZKP&^Z~Oldi4K3tga?Q&)aK3Q*KjNUM(%PT~6BV z^`D_d6O(A8e>SQ8w2({W8H~poR%|Qz=~@t*-MTv=Pdyqt?8(RM`}4P;p{nco_Y0T( z8(Mrh&Boewt)PKb?RJ7Lub#IJ-@qQQqC%uJ5ByJ3_`HtbTRLncf6#;fWA#m$%V&z=S@OtA==EL>AAlo)l%EZu3 zcrp5P`u)YyB7=l31dSB}rVfd)pZ4VNUi(F|W8;)?#36aHMNTuUOT7ZGFWu&Uq!@0a zNbOr}#D8VF(hzI>K@Hvu9q302Azi0s5AMzosc_xw4c_&IO8&{qE4Hlr=a(4z)k!T? zm$LY8#|y;Zq{kOHjE=9J4q~khElZB{LN|`G$lFY174f}q0p7eD&VQq;VN5{JkO8_0 zA{EkG-}grfdBxDec({tA5e)sCR1S=XFW1)$vQz%|^_f!pMRr5JXJ1*cQ7%&^gWI?7 zP@ebjn|RkloJ*A|irmqV1>77k}8xFI~{?tc3SdX zMfI8g`;8Nv?+Aua;Xbtf##WbNk$&~br1td}G%I)D50wSz=#*)>UZ$J=RgL7I4MQ$Ce|_5c$O!&eY9QV;EtxiRx=QUdRD z*O#fx%}xo`K~zfy7@~1xCit~UEYZ7$&3C1;ZeR4`vk+4Feg)UY0qEL}_tr6zuO#2) zLHhTj%R_)_pQN|hr`SDukrRh@@i1)GOW2rt%~!i9+p zelah+smGLDx!39Pt+3vj>E8kl8LN;Xq1Dcv=@@YR(nx3dJLP-A|pAej>iQJ-r1C-ob>{=)$d`Ifs|y;y9}?8FP~m0M!OJXW!Kci zT0x79s*dzCzD9qfT@D!apqwcZ?ZUQ${Ovr8w@@s=M+!T@+T6{)@8k}>ep3DnzPk|T zIW&mNcITuH4zIwqMN8XZlo)`fGm))i0Z!z(7wT%Ga^4a)i=1O|b!xD93YGz%$;DK{ z9ZJ`k8izC*qSx~CjB*X=Kturb+U~&nH~CyD*UI+Iv^;}C(|Ru}B(OL}DWs=ZSoo~4 zPog3;&%t0^!}dbht1=8z>P*cnT zDDYRFvams(z*+x^w<h@svor0&8J zkQ)8n@o{AwK=acc3(RRdR`8Niw+d_h)JJ^SC;85&Yn}DoftDM+W-TrlwAnU1Q?j*CX)lHhU85^(-OsG2z-rp>Fdb;F+a$7AQUkUB$hh_yWLLJ}4&e)8wL^0Us7; z;Jpkf_z(&%@uO#(`-Q!ybxfuz$e>BeEFz2B5y_|RPqanaTib>c-(IqA&y0dtyAKUj zQf~wuA;IlkMQa&QmxNNp%H4vU}s-$1_I zcgal@sh>#vUerVoQ(<^(bvv#;UX>J^7P%^p2tlJtSpq)Cr+TJ}%+9_0I{)kkOB(ZHMYrxl%Bq+G#2LMiCLYw^OsL{*MXz(G}NSVOUu(HD`IOPs)` zP!)1*Q1b6)-fxPzxgDcGh{+YjNq8T2!RHTdSLDWmT& zg;vuh#cl7<8aCqg5P+aV#O=mh%2_Q7SE>J*jkzZ^PPWJkY))3&(1=dWoRQE@zyTGa zlX6=LlAN*Oty7%Fbk@f89Y_j{>bR_>FPHZ2}< z93#QtnPfY56$TfSuPS(Rw|BR#%pR%u)%V2*%{kKw%+D0xyS|k&f^Ps`iyBTfz)|ox zkO78jU@7qDK7$*j-bSMJ*J(AeWkWr2_s>30WL{S?Y(k`m;Pj+b>I?Kb!DfkN){W%l z&fjKif?pG)`Of_hPp3n$mP=G-Q?>k7IDcF%tCP=yxyg-c;MYVi65g3dJdfish1lQny@!d1MLWgEx!=>dMi*Uusfj|{wj3$u zUy6rL84h-bt{(SbK8S1+4a$cjry5B!)?ZW?zeC(%U!5hNJ`UEsjJ2 z@TM3;>%BdP49YJUC9Fxi8e=bT0p7E~qrhkP>J~V(J6nhC?^d3H*9@Ecg!qbziub~A z{dano7mYg38B7{ZK+sq3iWx}2$pcFuIU!AL&TXaP=x^P9(!HR84KVY9y!c7ceyIDw zph*=5IHQYY!Y}dkt(5M~m)NwvF_s1+H=3bChN$ajq4FKCCFX!QJvnD46vbacNfog) zZ0TzHm>G6U;}II~*Kof!%dNRsdZ3Fk17b%051Q?rDpQ`@Rq?iFS5Z_mnNRRQ$I z(7a7G!N(l0toP%>X0@XlA{1dSLKdrhev?dm1yXC|dyms56-*CP=+V{gR7Fz`=%9t` zY*A=n@m}|2Un93g&*@I+ksKd3WZ@0Ng$xC#STW2!YxgK&H!6<6@NH=SQqYYM@(;fO zhW@}pmEvk49bI|z#eg3Y+Wdj)6!eP;Mzi@Uq|Tr@_{*na(EStmFg%GoAyF!6Fx%DI z#8I2J9l=xcm8ON~qsG-WYu_aC-zn?f-OBUaa7h|Z1L>zGGi@9Dr%yeCp_N+kWpi#D z#D(FQUcb5UOl*rWAgn;Q1{#-KX*v){dGHT+uG?(n6d{#`a6@@L;00(|)B9-nkwxC& zk}i>#=35q#QsH~hsans~D_A$?eLM5C7_aWke(;MM*YTI|n&v^|+B2%LC_-;R_0G>@ z!^l8&dJIW*f@Z=o z5?)Zj)$)oXgR>%NH}Jq{SdHzrC*2voRyXdm3l&MxYw1KYU_N^0lhW=*O~+y2LxeT+jif^4Y}e6Ew$Y1 z6F>!3tc~W=K;dZhhP-52Wq^1_6gkA?IMfmz5L=>{AHR2m&brFTnSVJ8vapZ%*FShUGB>aS1hH}}+Wa8^0;-+%JOAklKqhDvvzfo#wor8 zhh``DqnfNDqvgU^eZj{r->%VYL%DWl`2GjHKZry|s3Jwf#QBn6&&j~gLC?$FG=!%k z8UmKzO=(9+BHfqEhON8~i!DWQ!c$rh6>EY51I?N>WopVq0M?RgLxC&2m+P4l$b%8& zLz-*_39%HbHx+AUXuZkY!&gB7(#P9&R9pMqds9@rMzOn^pXrD+dGRR7^mST*`IzqM`JdL9+LugQTR z3C|F{H22(6v53Hr4$dSwkif>|Uq|``5u{Z~ z)_{>&?(KU#2Ti#TxW_7=d2-KC*Z23?kLMp;$C-mwWY^*!= zjrhZT`uOW>u;oQp&*1N*i=%v_07gKzqeyl3H84EC{T&!-Ej+NPA+=A`#gBrGj0RA?E06ClTZAo zO{$7>WscL}BnRJmE)^Cqt}M}7y5Ya(FuML2_cHuu0b0BsVhMx>g8mBsj_me=JS0&5 zp3ism{;qY=fK#BOQ4y~szpkxhU!#D=SC?OO`dZ!8^Fa6@`luTbcqcQ=j~s>1vBKxSP_-bsW^F{|+{EZem|K768Ef{nd=&uPuAy2bEIsqF4GS zfk-oE1K$>xcRe#)@%>~XIf^N#x=(x$Un?LNbpU`b>-SP9bCTaqH45@UP7Ghc_dnsg z3P-wD62=VJ1|H4ZLlP|R=VAO@5Xphui3Aa2&r}sUAj&HxtL40eMHucL*Dxb^If&di zLM~Ymk)F6CPvjZT83AbjT0Z1?HjFOnG}YOK8$3CQoV3%Tvk_+LL-{(lw#s1DAlQ!dhk2kNiZ z;ykXSOKFgX6(Ip%pNGCbRhR(hZ#w0${I5(*Ax)XuHc|qLi*%?ok1zE96&>(Ar~gFx z|3*gqg5py`4;gC%iC|m!>@^rQG{MjfkiqL=&Q>&TWj~RYz!n{YFYhn87#V=(nWmDG znu?#$BRt5h6x@hSi5=)e&7;Se)O-YbXS!$ZL}4ya1U5A#-!b1}r@!F+yk$Oo@L^iW zbPSJL^v{$VPJ6w_proHQJf=tJ-k0m`gRrP)dVE-E))Y(Z4&Qco(%*kqy=kC-%&_V= z2YwKh@XQH$`k;#=fzRMt@dCdypDfq#zq!)ckP|SncujGxPy&#mrZzBZ=(M&eF(&+@ z8;)3-;VM^iEXqv?&gfH5SCXN`P5eKcIIPtK_VXB zuhXqB6N^yr-4IkBnLYFK<#zabo-+)aJZRM7^>%n&zv9{L3Q(%I5ZtJ~8iG2w*8%`) zXej_&E7LkH{d$+3v6*=bA&8t67&1g?famP7XnEeD*a1B>R02Ty{fW&dCcwGhqE=wW z#scK#P;LCX9GqVv+c8_acWM0 zXqNoI89fP~Nt%EQekM9Y6K8jaCiuuQR-WkRacVBd>Uum#!q2KJ1gA)z-k2lK;Fu5V zbsdFgv?cJ9(Mo@vcR1+5eG6SS<$Aqk?_`)4F<7Ty2il~;iyFR+R>gH#q|663m_j%R z?6uSskn2qUl)pYwdxH4y&-=F;`z;tgmDR139Z+*hfWg6%6fSJ0gJwS0pWu|Mtm$rk zOgEke*|ObYiSeIbal_zl%XxIbfy%!-2BMDEr1sM^CaOYI686VWRm%{UdwgmKXiiY&Xeh>HUD3qzHChQGax$Vn*pDpIF(bGbGh}PI>+_fjbt^@_( z{^Ii&X5&HvxU)Omot?BNo3}~@sKQPk5oDt!=E@+W3t^xUT>rgs-eXYUn%`z^mn5SP3- zu3Zbkz-}|Gc$X3Q>bR-l`F>2`?1|3Udmim;PrbPY*Sj9wez5I{xl>WCU$`-;$V8cj z##9!=uwhi}e&-ZOxj6@S-wcA=D(U$Ki>+tODCpAH>Cl2wQzoZwBZld|!wpjw`b=Hl^-GM}NYNF;yy<)(7=)`u+Ge1d@&vRJWJe8P&;t zivoklscNy-esG@m00gJR{j-%z{wE+07?K4ueM2iXH-i+MdyNZB8-i7g{Z78nXwBf*kj}@Z+Dw}Q82(DN8oO_7)5>${U`)HK5 z(|_zyK7eV-+*Xcj536=g@w)KtQ5w$r8c;85L9VhbuLmzam#r=x_tOhII!KS+8w+TtqD%_;98%!o(|>p)VG z_5b$^wetcBj?*C?h^!ATw*Vu7pysKybXn9Rs___D+;_O710o~X?iVpdAxwKCVkHv* zK;|5aNxj-SITxP@3YVWcu)5slyIfAPLr_3L@+&@#v}!^d_d(Xd1ZmlU53|sAm>Jb! z1)v2re}6KaU|n+bsf8iKljmJ1*fdD#uY=+ZdMzTAi>JP%! zCJejT-L5!T%49FbD#Kv6u|hWnHfP5!p8q0fs2=&e&BUtd#(4R@+8Oa)zwYI}Sbb!A zQsQW!g`H^&qy5a;RC(}vgv}{A3y2O2LhNefxHcL4B!xrLz+hFx5lXrqF-Q_MGF`su zd-pFi4PUfaY?b*xBU6DfVPT4)czss#k_dq&(DDFeqW?>OzGI4Dw)LZXX7cs(kUWQh z&1gIpKU_L#r&aIti!wrO7QS_+GZ0kWcT~b$>6!s@dp%WBTF{qzJH)x3MN}8R?-0RC zRwKZ-$6K?p1fV-TUtJHWnR))!g#W4cF~L97?1iM{N=Ol9u#fCizBsIWNal5BF$(UF z^gJaJa<<1mt$#U0czRNCZtod9!HQx6@=CqErm@sm8|tUFi2vX)0|0HE%y9Ihg$zM; zTf?G}E-XO3fCwoXJBt8LnIJGA#In9Jo#W=yDl*sPnVj)9<EO0z$q5mP7+tYimad#qOS6NB-~oNYepF<2xV!7)I5lVM|EiYk87IXXhS~ zsbr$N+k@f0eA$M!{U;`mI&jpDr6e-yf`ME7pNYu>F{0FcL=%JxK&YX=&`uQ4piPj- zA=fW@RW_ARMf2EcG`knzaZrX$F?Jpt3QWguHJ|}0UQNSs$-2;Q#3a2)$qZJoISOD+ zW$gKV(?@HvzA3u{KHIk0(K zAN-e}y}{$^lYd)~CAYSIP*ccehI=+lvtT5&;_x-m^%)AT=}+j2WqeAfCJML&mVE-y zO6IgS3y<{MuauMig%)eXN&v=6OCTTcnD>Q90DYQ)=pXJ2gQsJ-!+7F#<3KwvzjwL8 zT9NR26l|w&&{^UedkINzCaC@{-7sRU|Hs~6_Eq(LU%>D|P$}t=#uW14(aah?z%VM-}S#=!S(Rk53oM&J=a`gjycv`)t>0bjK3q2y{iBn zPexi6>$>>C_LTqW7)AEEq(rJRNYoeGFZpoK)DB+2rj^j^2bMs_UpSxwJ(Cp(a@LId z@;TmTscX2cPmu3fh^mGjSB+a@9ChvYwJ7%Aa>xBzQm%Pm&%Bozv~$tw_O zz^tTDgW6?M3!bdOs4woQf6BUa#c)PnY17ipDh4GR_V@3uiMZQBdTM;lX#Oph;;5GMzh??mR z&4@U9xT9=c`2_CDm|zWsG8HJGkLq!QXM3ateH)+j)k#p#oyKv}XGmC09jo{uC+J76 z$|rb`3RrHUkFxqFm-__<#%(pP+8dD5LwV%kg8oP)6HqP$CCj9^-g_2xiVy{~vUmyr z+-Hbwe_m^oCOnpLkA-birknjO6;KI=Y)BXe)T>R7&b;e<)-5jJ=m1i5ow)&2@w}(j zo3U|>9*e&ySboW@2Ds7Pr28@+qL&BpZ9uiwk1-t4rWPF%YYRocdfYkv%QV6{+*QNK ziD~uA+R2Mxjtm5nSCV>VH_c-o7=b3T0+|?sa5M(|Km;*!a=Y`q`t;$)bUS{;r z@l$o6D{!~bTlXHet;t!`Hn;=a7Iw?bmg`3jXyHNTlU~O<&m*ny1B?k}^{tP%mwj(} zScknIkQpZ1Ud1SGi60w|2&2fEciPq3(;n|G$_1mbEQ}Xn3o`ur7rN_j|DF&H5p=R( zKogCYo_67VQv2^qc`sg@M*c5t;qruFucHr<(!dPnPRPK&@SmiECc@avK+ocdBh<||K(Xi|uw^Bw}0 zqmf;RT?uN+qOvitg`(Pt1_qpp9O-q4$o=pA(~SJH|M|(`XBNKu7kZ5aKY zJ`GAW*O2=!n^UUq>fEgoA&wsh4YR|>&lg+G)W27kT9K?`Ul`qvQk-f!+g*pNITvq4 ze`LJaObX#BQ|@;voc>dkB(SN%`5NQ}Lymg=gE^ivxOZmtSm*cephW z3s*tl@RL&&6^A0+`=_B46%eMf#`c|0s;M@qsS`C~$2(!aukisP?Jm|Ut@T~w<|E2E zKO`;}MEZ2>pXN_HB(-$Eh5t|wWkR&J;Ric-^oQ>}Wfwj(@9Bx$Y6Htv^amO=4SeRC z4ajeKD`~DzN*Cs%Wui7H>Ldl7m#R#n%}U2GS0a0_a$L0~d>N?^U7qJ6ni?m9eq^g4 zT&i5B{9$d{5)8#n)NVeN6|%TS^RW-(32?sdc|OrT71gB?iotA$?c^QjO@w^j>YY1Ee<=M4_rYjx=H&v(*CscIs5o*06Od#{<fj!B$WUceY*LpqgRMpS{ZYZ+`n^W)n|y^@%>WUx@BZ9L@Sr35 z&tvl^<|9EQ%{_eMmO}(21V?6;aV*IQ({MjrRO_=vnsey`SNUjC+8Mm>*#$h)j8!u~ z_&_xK=kLW2=X*NgA2X)ie|>X`>(q5A6J*Z+nA`=!l}q{+ycHF+^I<(j*3q9+ltc5c z_7~7fdsDLt6QcXK_uc(!-+!tMUl03{@JSUh9t=QMapC(XYf{#M(elaBcWaWo>BJ5~ zW;|z{b}BZFf4z_O)R)Yc+m`KBk6jnO;(Ro`vBnFE9a*N~s2Z>)f1@&uFflif-d&0K zerlmjS%&{y*shPw_+q88wxh0AJhi}uI~5Pw>kFv zRkiP!3W+D}Bcq$0R)GyQjQ~t39C;8{FU8S#TRt5si=fLn;Vj~0Y@{FqdaEcXh3d@v zTyMo#iAU9S&*Rc`Kx|C@SR%{mfhhf{GCsZK#M|=`|AYAA6K1I~UEvhM=PL(pji$X? zC6voSyK9x6{wZb9=P(IbM#oo;SxXFg@GUL9bQ+eUX1_ZSOJ0lab5rBnOl_O<1onmk zt2f5FXvIYL`EnCB+6)h5pzkZwv|(;2pc%)t_9n;D7Icfh!-L3TETD>QQ3}^We>W%G z;^w1k_(@$gtpQ3qrtp8ROWuwI+{0JGxVp6wJ*LZ) zz#5{3clw53%Hli+0wYhQ`;RSZ6F?Fr1=hgnrfV&zNe4&5;m9OnY15!vkue#J+9ax{r-A^E>f4GrTteX(5yP4$R6)~G5P0Ee&>SLGJBq) zok_@R!)49AL$Ws^da-WjBSlD#?T18CQrtUQ-iZ#==nvmCz=>C{g?9!iG3v~oKO2bi zkY)`9<;Z_D3TP4$!ek;2m-XsXF`1E&`9Z36*c=(JAn^9$CVR&yG6vzlL6qV*nwne5 zLP#Fhp8?e%Mfzn;(-F}cD%OP$0UQ=#f2G~>M+gaYI3U4DGs@HUC%_&$iY)Et+u)l? z-Cvl(WJS}iUzTXyS>VQrl4svzUQE^w1%HOCW8SFxK=SDm&0c0j#z8k6@&2?oqa=bT zm!U|I-%i1|(GNSP;y7j=6pzEp_kktV2KC7YCfFfAK81%Wx`~Ww z{_?yQtUF$MyO~)}is;9!b#jeHX^+&*v4tSI`MI!X@mjvIPN?N3A3-wZ`(AKA{Onq08f@H7h|=@*PTm3U$p3e!XiO@xdBgd3yn=>Sn@LJIg`ZsT{E zB7C1GxYon?@=yI8QfENomjG-(#uYUCxd_j^w6is~^FEcF_a`;IHi3a3sowges^=X< z(x>|ag=6+u5ZTu6yMJyoX>kC|v!tUFpgOq`-21k^lKjwYzJ^Hy}zW&!kciBuO9&DleYc|LoDv2x{@#FfO znCU6fj8j^jF8eeHv5u0RLCv4sXc9<0Uw|cKzu+V)qrfeFFsDHFZb_}VA8&A zke?v+k(VoeMl7ox$@z#@dFQ6Ee17)Q3wq63|M@=vtcK6a1S8etOc!W<0a|Zb_HZ`F zMSnQ^kCO^V27}YvhLyHL@T}#je?oz|nXt-`70-Hz|MREUM&5NjAIQgOV_zyeKMN3q zF>^he^O#taPPCe}9Xt>eJ|o8#1&}EGZ9cuV?m_9Pt46j>F2RkEs8bBchbXL2J7(pG zk*I7wX26qDNwxqo?n4lwji!q*b*iIVM2wJK%iV&~;a|5p(&1bH1@9R7g2ka>4j9^M zG7OVi?v)~|ID5sKM=0IVbiO|noYgV>h%U}?-fcAtcm{bWSPwuVXAVbJ4(|1p>qoRa zIv85M6^@fe*>t3?%5w`E^<1yy~o06A? z_F}Il^I&Qv0--kh0flsc8 zT#Qg>5~?_HbnTOG zG}Mj;GzJj5qS@?evU=kb^yea&7+$j#0fj&K5e|$#VP|HRC`U=Ox>`t4$)_PEDd~!! zO$uy3zV{FZj-!3q@EDI}vMj#ic61+PS}w5yL4k|U{FmSOAGvGkEXYqo{7MA4Im~1? z#V&U!Vm^qaf;K^4Ctl`bwEYlm=6jY!?O1FUp4@|v)6aBxbzKWB3t4)iqZ&4axZn6g zGxM#TnS(7>X06*d(xO3Ml1$rv67k}Lf2$Y_mPwqU$NQk zxo2XA!g%lA_=!q0c{QI(7fC-PR0`FOjxFcyu_{a;!&fG_?!6c=VQ?ghPT zrxWk7{5_-EZkx??sPt6uJ!^>+d6MR3UYw6gld$jp#W{Tg{1 z%#!^>VhIJ-`u-C}0B)^#MjEBs}6xwJa8;4V1_jIEWkFFfiy)yb` zH{=13&?$bQP96_@CbE(-=^wUCHZ+%)0;`RoLlWZw!`o~a>E`b^g(GuPgDg?uv@^cR zUJ<8NQIFlPR-bHfpzJ9&DB?L|J2!MfrEkLZ8c3Sizmv$-XuK?`#{H&T2mc^t7kixl z6r;#+XP6h)Iv$9}NMuYuYNH9q`7qS#>g8p&nJx5)f$3QD28y{$Z=gl2$$3>M`el=r z9I{cpFB&y_GYXGc;7EqvT^`10-GpJPu)hbVjzZ14KOzjGR0`K|XEbl`?w)%$E&N48 zw;qixxmU*#4b!Y-z<;em1^O$pMpGO)w7+Q~Nvn6nd&zfKwK?e0ERriQ>G zFL1GEX9zo06L;toHO-~#R&#Yt%{Yl$(+Z*z*JpZc80Mjo_Qsz&i;>$;^vo8NKfQtj zX%h9YR4#reSJ0p+ZRRjvD@vvzjh-SbYw`?V4?LtF+R3mOc)cn!ze9mZDd72yU0H@N zJeuVRN(4rDEi8P?6H74GY0XhN(uLn*O=PdfPzlF5mM`pbzRdPnlu`~^uINnAWP@xF zHS{q~54w+9MJkqt8|=IUIG^?jGc6PR-E#8Aw|_lm+2 z&qHjR^cAC+^6uWCm`CV!8gskaO?9d2pkPLWtzDW-q)&t9MsI)H>(^x#>alLVJKmEY zHX>u%I?vlm3bT3SN!t#Ux!~5h{#)#iVzQ-CZSzPp9!{9NbNMRooPsE=zOJf4ZJC+H zl!IGEYSX8hq7pxzPV!x)4woqLk}}lDw{*qmaP47lVPfxYpZ3e<=o*|g+~!psYNsYZJEKu zUNw+0aT}z_>+`g59=i@6X=l0T>A`?}oqqnCpo^86x|ZVW#2G2+G&9b^{SjL&>+jni zNA{BfOHz5989w_*5Nko-7n(}??c{JbL@hyfxNe_r&yYU%2srvAUR0M}j* zJ9Eda*R?%QXV)#5;zS5#B+uf@MGKj`$ybe{}r#bds6=xQUKQ3)1VB#E=&NFI5Tx~c&6bbDP*x4P{4 z?_Ne3q#Zw%r9URN4;aL;Jm?CYU-q<|jot0Lq@C}2KO4SXVr_8RIDfu%Uw-O1-}h%D zzw>`yUl(8cvt9Ajy?o=He!p<+ZU5*9G`@?e>Hbm3?RhS$r^W4n>8W!%J1qC9Yov9b z!u!~=Vf^Pr#~&J+=g!uLqRZ>z7`JWo#e25rwb5z{6WyfS65YFjBB{q3bmE(O?@QlL zw4l%3x4W^r_dVWoM`^yG;I{hhUijzp*5_;}(fpt4IP=NLk%xJ21x|WL$x;!yGZKb1yg*gkQ zF11T#ii4>r6fkO5vCu^mY?Q%=p>aq3#wc7?d3H;z46!iAZeDHCG)gHnWt0Ts@yzy%5!y zz=OQrZ|C{=MwqWq-n!)ZcRB8)N^(i}Bz|r8D~AUNk$7&+m<2~!iiqE(8OqeAmRMAQ z3Slhl-7Br;Dc0^{X~OOqF__~dPug*UMyzrHblh=%_RiMdQV`uGu9 zPt+t;t2owUmZ2;t24~S419Ll|PU4!ZqSEy{na28d!V)nhPyd2!m8z?&L^5P)$NE2l z_9}dZ>dP28cs1*)Nez>MH^Bw%-D)LO8v9R?fuzMBK79See!2j$Z^4`AE>u;eBYIuu z(QGz|i%npdbh)RzoiWAPVn9AnqoZ9?{HbIFa*6#FyJv6#IHvbFO1vl*cPtzgDm>#Q z$)~S7^@091yz7WOdDCJBt~^f$F@ubptyVR?lhN2G>7>nZr%mEywFY*?vio-(qay&E zdQwq}n|{B=V8{W%VEEw&xp*#%OX&Gu8tUnE-@siDgXPiHYB#eXx0|(flWZH2m2bR6 z2YkE@pI!}r^j@=QZV-+;d$qmt$fxBkowL3!E2W~EygxFUgeF){A>o=zVre1!Az7oT zimX+^G^x&+R#mFx1J7m0L&Y8k}bS`uIvIR)xQou-V1yso>Z(KKi8PU-e>{dI}gzrzyaFxT7Lp1mUaeM8FXDeUVOj>J7`K5_jzb|FRD zYrax?ctwnwJ}(j+{ZBSdmv3M(zb~ez6LFTB;=hqv*_pp4L^)jpSKeUN(h>jCqhN`! zamApcd^ZO>0OpV~2btfvUa)>9l>1r(C{mx3ZWUb2l_L*yY;N{2*wn>r|26#~Puvr# zj|~bbsVGDg<}*4G_48uMuYb&(I!Cm7O5>_a>hZyDUZaNL95@U#{*eszZUoA6d2Z-H)Gy$C zmqi6`TP9to^Q9qbY3%s(Z9vbEXoOQHtxQlcB2HvHMhQi@F6g_RK=jBS+X&#y_A|pDNsWuRIqg8+@;TX3ApqP$ zhuU*k=Tu;8R%%;;$JkSYsYwA%Azffbt|q7XSDzxpoG_Q})K8dN?Nu=yRJ{;*Q13rA zXje4Gnz?$3y911?H9c4nAQ$XLuQ;?$fO*nvc@0L3SA!N5%$B7(tRZ;^dpuU3=F4B{ z|DN@6u&B%4xloqNAa+pPF`}K6ywxr&$6jQofBUuhs*)YxL}3^B;WQwjX6qvT3ofc0 z8b1#k4+*np`DK73Tt`-$s$f-5^QVZbo__zT)v9-XKYS3%)>1x^ren%DMV^@adeuq< z6K{OjW{HUI21zyOtY-H~;o6&;Jr>33GDW+-_flOmc{87Es$S6v5{%^vz0z;cHTnhS z3J7`yg7ew2dQrDSx=z64xMV7{CT_h!UE5T8go`FVY1?2v4)2|hjrKV;T^R2w&OKJ+ zdAr;km~W%`>t%_rQTV$u&NY8C2de^gQ$j)(#;wQau5$MOA7ByGO_HQ zk4SCKM|+F9G1hH-O_S|peG5jm;$v$%poX#X6ONRe{08mtNE&skxvDH@yyJ@0%C@mz z|F25k(3)wZU0iZeJW8MKHi^Qrp=9xV?;@q*=g$Z*S^?49)P_2g2Vyq@8wCj{18{;H z=>qk&ECTn>Sq;vBA;|v1x6M8cZv5nbKHqJ8%9lcD$PWuP^!Cy+CxlgB`yof2Oy%S= zgETo&hkBd%7whUK%p~=>h3h(vaUr_nYP0D(l6Ao^_MD~sV66O7(-voGQ8ezf>Zk3| z$B8%Ftw+xJ=etdE^MJerVKs_O25WXOKYouDLv45Bbh|%@LByvUA~V7f zzi&&=I#q#a>r?JYCMvzts3gUM?TPf2yIG|Cjm046##Tj%)al-9#l(R8_T1ODA?xK7 zGhe?glxUW~;gZ9d{`*8nQY|Z-R*{{s+cSt^8qxPfAzB}(;gMQwxlz4JDc7$>M;*i) zRM=mGbqB>xIWTIm69*ajHQMK;{M!wkRz-8VpmuVnLt4DSX=={K`q8eIrf#XLX2CR( zr6JkRf!yJwjKum=5z3eSx;4dC!eHr$o1}2Q0WP7$zq_eqcxI1EGktRkxmKmFzC*>M zFj=UySK23DL`YmXiq$h_uTx2=0bIx0CU^H@6Jm1fw;&LW7WRPcw@xT`?O2kInXZLoDB1xNva z5ap+Zs><-)=B%3nMJs1Piy4h?o$})~s&(~)CCXN%PtOO7u6o2qk3uKg35f#s>6UXB zZJ)vZK61#ot0+F=FbKFKUIgtCNg>ncM+WJuv)Ev}-Y{Zv1L4mx*3~%q25|FpUOdRJ z{*KX`MsosJ_#E)CjhsI`C4A#cl^&}as!85>RES>eJ2*Nx7!*ABxN}X5fI1f_kp*CXqVs*nFeL)fd8bl3HrcIaVrI*n8s8_bFy6yZX&` z7T=*?^s>4idD&<;*_nP7(SFHMsF4FpaysO2)le$LRlRN*gge`P6@r!eCo)~O^dy4u z^p?Gw;BSH=DHJm+<#r`M8mb|ROl3*ONMIcwbV#}M3A==K6llY55%C;3g-A>x8f#e} zBAOV*e%$p1T?ho1K}}f1h=D&~Pq>4-}S%GBn*fF?tVtBmlG9#=XAXG?`sf6KTcoGMBHJ} zJC;f+ea9?0cvPlPW)YNMmtzXvmdZP9`X!+XVg+mq{%A#t6*RIO`awgMyIaE!&I&n< zIh}9^5g~MtS8tfm%nT(x)(1QUhx-1tcO93zPpcb=cfakG%1cXFyXjoI_X7Wl$bSA&ahg?oTP^0!#@ro;NK7=7{V3$ z2ASAp(~Cq4zEPMlF`_)tK^%;OtgeYANN6`{2HSoEa_?0X43Gue6!ktkcjN2R0)Q@E_#f{&C*>8vt{G%<8gBv=e9jU-w+GYAxFf!Nj_ zdGTOW`(anc+FF)FR6%oH#gFcb+Z!Rh?{o*xODq(k@`WtfU0kIM%$Z%P+ImWRgIug5 z;*jXvoxpiU`N{&e#ndYIiVYi55b7Zn&Tb-c3uSQ_<5evFjuF}7Kjsg(8}Gn@*gt+l zzJ?%cew9S}O$QV0uTG*Y1rbMGg@cI6V)hgNC1k}MA-1tV3G@k9dG%B5(z8PCQdvb< zWa`6HIDn2ho}_qX!_4mKMFqpkl}cirwXCV3;u4R(jFXz}7lRNV)G~2Hc_!a;AcqB? zwRI^xyO5yoG32wP^8V(Em^qJ^yh_P1 zfB2Ho#FobR$mmHocoVdpokk??MntJtkC*y54aNMd|ceSLZ}Ca zesc<2n0)w|FJF2n6cJZCDDG1cqm>iQmoc0grI)Kj@0ujCUigUc!t}M53g(qE-Yyx* zI;uJ_nG6DzCr`%#2yDrT3Dx|tsG?arJ7;8dEMqM@vq(Duat}6@qGzTH@tE`%Gx{m7 zlcD;3u+57gw2ErpK*fE~Y@c)9Adc}L9_YZsUo@y)U0tn+_97NwnDSD4gvFQl>&Y@P z0o~*|2%aX5F$(?$?Orp^8Mg?}A(E!2j+KK-bggH2rS%eoGX5<+@?}Q5@0;~d z){Q1O;G1napFwC>Yy}pIIa0BEP>$-{8qw&>8YyflT={6)LYy_*>;@Vs0Pu$iU17NI zm2rOt8MPyM;Z4=+n!JIQ916fBG82trtzo=h{wZ~$R4dbKI_%Ul> zPMHnBZ|ZlJDW+2Fy?JS;h{J5q67IosORGXK?H3&RF%=cc#hEmV9SM#ZR@1&JD(OLN zE?$<7_v0K}_ zM@bCiM4G~4M0W$C`oMW@w3}~GcKBD;Ga=boZTYkl^zS~dg|c;kjb;?~AQ?)bn|&c`X3vR5xFl~?SuxQUL1F2q z)emL$TRQux1ce%W#gz?F>pzkTlo&jKB1%HZMg!WI+h~mdQG)xcs-j#6=U;~x|?J)OV9*lzO<0RtA} zFl`eF?JAKFZ>R?%DL0A1l+H+!${#DR{SaNizJaBmV6wwaJ=5DFh$VN1D}XHw^JqeW zly8ey9Iv05=Tb>FLz<2pwkM%6^kJ*!6PqcrCLKU6PtSXqF)Udu8Y|O9Az^?1iv3LI zOSGabdHFo(9t9x(4hXyg9a$hFY|*mXF*(XFuxkiSkus+OR$*a^VuT4qS2NfyaOluT zHU8>bEud3M<-_DtS;74cq}F^ zGOol=0qYA9>GU*9PTr@A?)>3`(m# z1z!D(BcCflDXKaaPF+&!w-}3Ot4JvoB@yDsD812h$Rfi5FfIT0lOGYk9-5f*@n2VU zfS8bXUWcBfJ=tyv`7Md=1FVi9jD!C>E6V`ZRLb^9?!ygI@WMQEX2XLjD~?;b8}{gx~98 zU}JScSW4DAg#s6i?lFM-!>PZpo6?Mp;Yoz}Ha{qkhbqVvbesN6$A1H~1^u?FJpJvv zZh55_>I0o!Dc~)VzQ1gLrQcT-nG2I58?Rvp_#j9P>X0YuJQfVprWnT)b9fPR2 zm!D`{NeT^*bj`NC=;px!VXXb0O{97_cdor7$PXGn(|>Van@;4}icka$d!+1g`|0UH zqjIRCa{>Bf(|4vmkQjujvntlMU!v%3o8v@(O?7+{hY0FmO+ZE5gym577*-6u!YJ1M zw>y>rDUtW;G4+V^oBKzxeIB z*8OJBLnOP6=c*n5W*Nh6N**dF2EP82p^L(&Zt({F&ZQTrRvw_SViV6{MEh82n}*Jv zn+{T$KTAnq1-d*r5e7yImLjU8BPb$SEMpYjTXlTe7_%5=F%B6?h=%J=9r+cM_e%RE zhtapZFR32%{>^ml&MD}C*W;g}xpMT9aDf0WDU4Z}d^6M#7fG>On$NTV;AKf_w~vsN zIv;XBiM4N!7oz)4;c~Rj(W(6py(vX}d3i>Yr-oH|Jy4{~P`ODqgsQxq^x@7>Ip1Fn z#y74K+himtgc_esgh@~?V&3Km#C~=5+XmTn23evMjMbP$Bp$L9F&ZbwVT=*9?Bo}G zk}meHyRQ9J40Gx|c$4>Xcd*AT*KodIdynx;QetIF@>{x1JNlFFE9k;L)h^ z1}rCMXbwYEjZ%MHP2~4nu^WFxD#0)U!t-5*j7py$D#Q~|AGh8f+T9PHb3M*@FXpzD zHynKdp^?Tb9lIz^+Sk(Tm5&GIEyQZNOJwEKOO|pa5Aj%kC25u)DgU?hiT+C-Za_#H zFBqy0p^_$$4aotonI=|=_Q!mc%w+_#;wt=ede>tfzI34>Q?RTDmwM}%7l88aJ^#VD z&l^L5Sgpu||KLZlZ}2|#JqXmR2`P-BQjC*IBG>Of%Gr^bT?9ul`?HBT_7vuCL<&vg z@jdP}e+7mMC=X5l%5dMXx3<4}bG^@=ksS;y;0H9jPslff6=3Epori-sg^ngp4nn7i zF&?)FF5;tFHyusI0xLpcF$%A4ZSSuOI|^pja_U(K*kd(6!RkA2a_EV5l8p8ys*lOg5stz^$z)m%5KUY;kmigbYLb+dN*p=F5$cH zp)7vWFOCOYU62#QaRZ_6&yQp!Bdx571&&pMN{~}ULBGN*3j8Q@8G+NYEez&ZEeB#4 zW3u8(QpPZbE4gKl_H1=V%&_s707;j#ve3)Vw zrpWr6Ji%0gl=r+`+=ijwLs_9b*`Zk7DYtX^Y|#_0STWH5ESYhC@hIrPNAa*7e6J@- zg&PIM{E|u~bE!-t|H%?bl8#JbE)Eh^MU_Jd*1U}K*@i1~)W-k&J&4p9!DZXH!!pnzBNV4%gA%Pv_}^>FDm%>D4tgPe;zJnl%>c40wF!gKSIEJXPxr?rOxL#_larIm|2=>I{+*E*kpy!n$yU3bZ*eVqRMywm7ZiZ8f|Qh$LJ8Q6 z2NPI+eRSIaCgY&sze^wKyzj=-@fbBW7QFTf^7;n`fZJ}h{{Ln%EW_Izw?034UIX`` zHy$_Omufe1+Pim$66oIctG1lQmhCizN3c1+nBGeff!Z|ur{|-4A5}at@P!#|SEIkd zxUG+e*2`Y^hfgZ`#l={oZAKNqX7pDxS1U57vXA85tuo6y=y)czQ>`@DuP zt27=;LKk{C3DJGX;seD?QE!i=Gx?GFrMT@Cn4~)53GRC8ZT82~tCl{T#kYFwm(;S> zEa&1s?8jg6fFrb?v?et@eZlUG&U zeiHZTL>D5yNB9pEdNBjbAT4|Q^UG;np#+W*?se4G-MuwrhC_tWkD=45hGl8)Ohl?QTovQ>lW?ikEhy|SX>EI&KE<0BCkVD3~Lqvlkx zx(FiAW{hIXRdt#EOEC?2LhWvee!KvKJnDoYh1Ce^>gwwG-($SGP;W0#^tj37-7cPN z4uiq`A|)uJAM@Og>bL9G!;`sf*IJefRdrlnlZ35r#_Jxj-KhYGv0A8`nwknD;xfid zbKOe3XEC+)OXPPw6G0ci2@-{5V$d1?@6({ZZO{Udb<=8#rlR8cPIiPb^pj}z=;lB? zlXgRly`X*v!gZ3gwDb{A0>c25`gdYuL#esO@^M3BV`EE83jnP0^75>#tlZpg=1{@u z7=_$%(``-|`)_X=*u%xR?EKtZbl=9!={h?9$>$OH@m&XVb8}5iO-acRBXt0->pc-= z`S~+O2c7Ighx4`8^ED;e*%ZWR`1trH)@kmN3k(Bh-@ngP7)g?19`l$I{t%MH9B zY}xY~6XJ(K#A#9Y{B*BcthTDN+I+nkE3kecOd}>Q$^pr*@q1Y42}MK zVq;_Bk5}7COx=r{n;);{t*;sQkLuRF9@a_XV`Gh$E`^?NN4C?wy@rxF^3vS)A}wsZ z^8)M*o`AQ4X=(F|iuNn%@+*CokIM=QfC!XI-!8!DU>_2cZ4L+sct^ykq^jz@#g;z1 z`J@DObaJWzo~kMbN5|Nh7;b;jyZeR3vH_kEes8*1jaDyDaq(Z4^W(YF_AMDH@PZhK z+~OIzrk=xc1Yu%;Z|CRd`S|!kLPF?)hmH=C`L9j$liznMtJ1PES(pOQ7Jw@@H#gyl zQ#{k$YAmhis!Rvs7y)nry}`u9B#!&PJnqdNCk3a7IF8ojbM9`b>_KMQJmFh9OYylU-dtS`~ zy`83NYGL7!w^*W8uP}W%S)|IORmc20=^B9dZOy#ZfP?R{+kQ#w!zNQ&zQLCNr^bVd zdi(7w0zq>#Gq;D+p0{kpc4r%X06(R;_nmDHka*qbn?TrWv1Pbd_ln9s@;MzWc}u07 z!vkmoJb&Bddaj|N!8#!HcriYBIB(tRdAjy@_>jJ|Zmk1_Nh)vL89)&VQ&Xh+_U(3h zI=g8!U-&ujFZ}lhl}AeAJqNRuBZBt_B4|8al^&0Gmnit3EG#Sr8Psd77GK^!xCrpF z;7J?&(XWH{qB@fMhw~98jjGi|s07tXVl0GGA_G~y3~`P0K~+TqB8 zAaKmoV&`Lp?hfD>k0+oHz3xn9@c_CQc*BF4CeDD@2()2Q9#s26Xu2&AZ-s8!xB03#laiP1>h&U$QBOM(jrI>nvBy50`;s#xA5FwL{V^A;G zYv*b?4U_89AIWqJOw3EG`2oov;7IW@@d`;n26QI`Ky^vf52J`fkB_}q%? zoAfun?Z=+S?rC}|V(Fu`WV0(kO11~`L)w9a3H~AgL2Yeqd~Ju(|2*J7#Rm|}^V9J& z@Jl#c+H)A!($4uUz;CBv)*kCXZ>96QR&4jS0^#-6rNamaT+Nz;++-_4y|o%36VBB< z4zi6r?5Vcy9l!tBh7DM=;){NDJzZ@+4P@>s;3MuAqvC8R>!&vWUG59E@^4Q7k@5JK z$?Fv8p#!!s_VXo^!35hOj_Ggcht{U1FYR`2IZu7Qetse${PMrv)9PJTPY>r_L%d$R zKWem&A9t@8PebblFd^jq^dKmLdZg9~FAmVX82YfHR&AynF4*G2zqEaZORutEH^7*-lg#IW872lW zQPb0!0m1c>`?0mxdGg@mlY#!@Y^AC#z?RxBtNx81e-&kTjCv!ggw4#%G-|C<-TP)u z(gkjWt3~SCxfUH1og5g}3-Z!<@B%vYkwf) z4yOwV@!iFpo^+zSpN0Q7Faqi2Z&-t@0Gdss;##ojG>X^00?fa>j+~pD6Lg;zjvw5R zh6MinZN6aDblUwy4)u$KNyZyHn=iuEjmruNRO+|u*#Oc32>N7R$HHy05PYVk21oO) zQj-}|OUuI!e-vH!!)hRpkN&Q3*oDP^Qm^PDz!OJQZ}F&C{K>Ko@Ub?~i$EytxUL-5 zE~h2_F}KbcHe4AA1AldprRG^5a9NeO0@s*XJXF*==O+n@*A|7on=Es)s{s zZ+oWG`^R`4H`A@77_g%8NSc8A)xkmh6Ob;r+Spoe);PC^wN#5I;rhPJ`*A>^*d4u2eE)x=oi>dAWwQLd6ubVcFCqNr z@g?dndXWRrw{-^<1895)<$~@JLie*H3yX#(TiFNOx=k*BXv$du3xEs&h?cC9l0!fQ z+x4^pSwhF_c9U&{-#(r!z%ZQ0_~GVs*I`Z|Oosu&s+du z=zX5N>>$($#C~@eQT(n;ti<^namdqmHJegFtllHfb+w049w(CIS z7WOA5Cl{UOEq_x>Ln*NUE;>aBNb@-B^GCtQf|ND^29`CQ#j5JmuLck;j2tgXxhC{XikkvUVxEauTl{CZW zbpWqN2wbUkG6Um|L)SX@J+c1@+V>DZOP}9AzKDnsp6!%_qXz7NWB_%Os=5NbuJIMk zS99h^jX*YQ-Hg*rnQZ~aJJvwZm1x#ByzO}>*#B=^7ZCiRY{T50l_%w$XvqNGx|h!b z{hZPO2L;PYm;*zf#CgD|7{011gTKLmB}~}dC;TVD^rgKxn+yyLO?!n!3*(cA>hbP} zbJflZwm>2>x3W62C(Xkq-H|ml-L zPYo8c6>1DMn!fbcXUa<+=R*fQ##?SgLS7F*o9u^qwl^*$=3zB8oFmbTywU+qzIn3g zUM~PcPY3$v3%>RSfv+p@V&g31U&MC3fAMt>N@L}9b(vtq8HEP`T_qQMv;m|Cqgwe2 zKt)3jlVJHh8S!^-d=s@sQMIRA?r+%d;jbz|n@c1At5; zJ3p}p}X_28id;wiu86*BzsyK60XzsMKnNW;BL?vsJePE1??4lmKD-h0XH2L~6$ z%qKes#~#~!je{HmPXC18y2g1t-!BUR+6JKIvF!oZ@Y?=<=tz)#7pAyNfIB*Ko(}W- zm*K~PB>&GdQF8qx>bd_XE0g7I-d;(k@&Esrd+WEZp6?6v1t=g5g0yrB(k0!Eba!`m z3rL5Abax|2cegZ1BOoCu-EfDu-_P^hzu-QXUx9Px?Afu_nmsc&lj1}^pbi|h-yIz& zH%gG7R@nLMgY*v$M2x3Tk?{=v(0w`;!wE%%rkeWdbk+ z0D~zi>cK|<7r8NOEN4q4<0-isc(*8*3*Penv5S@X4bU#pFO^`P9Z(YFntlZ!!m6)> zO7n1F#=o?a#csEH(C!8DnAbDwM}iGJBFkxO^Komix6|!An+JT`n~2CFP(erMkPFr| zW%O$c^5v+@uAG}K z@kc^JLTp|4_4}JMJ%Ho8#Mj_S^d9!qR@{hy;L#TWj+S2!iFiYQDpPlqX}C6Rv{s-_ z$F7{EtfR96a+nuLa;WKcUkTfX{))id5oqlVT{)ONAy2KghErL7AF=2NzL&s9`Ur_W zXM4BIx1>M5tM(B>eYa4fHecbE`mg|WBvV_!HvRa3)sKKM(-zqd0_O$vCti1bz_DC! zW=7Q(o*7VGmCqrt@+jT8vDIaYk(R0|`ydM`_j2L>2Tj3$D9%R!|2;k)66`ao0(I*F zxXnevo)735#1F6s5-Fpd6QBla2Q8)}9`LYt8I%(=srOzgJIj=*KlH z*KT5tKS-1yw{6-2S6hHGo{|C+fo7;N*M7r^@?n>_U){pOLRR+2e6#`Sl6>bqXPeA&X2czK-xh~Q;9 zbc$OoaK8{zMOBdsH`-T~aaUQ+r8MFwOM-e+_NEOhml`7{r1 z<6Ic`-Il{U{k@bGZqY+y?Q;6VT-F2GdMYu1`S^NbEkjIa>EmFKkVD1zK!e_o{w?Jm4}O)0Z` z>=JwYpSleB*70qe|USZd6%}eb^0CubIY#DI~WTIZi9OS|Qr|SHV_p*M3 zk(vK}UpG!t0N@2xx_vjO2EKqydoV|WsB`|{bLyXt5b4qVdRL>xwZiN(D6gK|N-{zz7T4@i7^<*v|?sP$}&Z@8?Yt(#pAyVYeg$$>TxtdFh7KwZ2_Fd+^~ zE^IO$!Z1tU=Hs5?d8XduaW`i>UX++4=H}*u2cI75y1cBxJC9nP?mC|KHM&xq7+Zoy zYAvRh-Qf6Mhe%yQ0ipP8lj!OCcE8Gv0Z0_iW1w)EdA5K2kIjPQO*=jr@w_$zegxhQ zuOT(w-rhb_V7=>Z>^F4kf1l$T)Fbr{--l9_vbVn1>i|(swV9fj6mZjR4B2V6d*AHM zmVHJ8nYc`~Vpzuh5r{SmZHu=T`_HLDHjRCuUPn*QuLqgKJvcfONJ3tJ-}8_S;^E`7 zKy{h3KEqDaftSqBRU!Iq|K^D@?G`z;Mxx1*Yu2<25Ts`3=9-h1ou_3%`A+l-vsnVK zX$C^T&S*-!8c{RVxiI|AWkW}s>)|5M?-mzt0rT(_LmvYLn$YXw;1JYtppyVkH)m}I zfAh?-kM*{I%zMsr8G!iS{p<%v-lRDz-oy1~OcsDAK%Kb&w0YS3!`-&T8Rwt;_(GYK z4XzwO@a=f6e+By8Ev*6Z{s~MkH$%t$DTpbTXL6Fkebuy+Ia1!F$L=1(`>cH2tsDdX zKpH#voI!yhS08@Aetvy~gJe?Quo1ku9__DEx z!E*uqh_~p><)_XSsBA!dRj0cI^XNtD#>U1LLgo6UU#q-LP}x_f zJt%|HUFI}E8H5k%c}Cl?q3!5GJSK1clTI#Pnf0Imu5&id&;QzM%H@HI|7NJ6{i0&@ z1E}|4c;zJ}T)+HbfGP`guza+mANN;H@(KzLK#qQ|Sxfyrzq)$zL6@f;*rfT`^k;7? zsCX=&zUnjX%6N5fR5^|^4S>}LBS6zqKnLJPX;~%SFWJL1vLAs=_26K|tJq`fJ)l`s zj|?0oA+O>;!_eDz^dIb2d3In6ocCr*jvT3<2{%X|ZlKNrBv&`iOv7~wRwe+{X?Z&1 ze^S!W@aTksOGrqtUT&OZQGEcJ_{WT*I^e%-Keaa>_kq?mu@-C(wY>p5rwHR!Lyb1Z1au!O95Asme3e=Y1#RF>es^H8IiiO{K>7Z4kXgoVQ}| zC8edqW3#9B+YVX0UiI93%s#j0fJaV$H#IWa1dP1eacdZAuW~yO#I%_>{hjm0teRK2 zf~vCeITxLqxt1er#zWfoJcnI|O%NM^+y#qtEpHWxZ@r=^Pi}DGuTmjV0qmkXT zo!N}_-wQ0Fz7P9)Hyi)@zv8dM+evCJlM-w#{cl=S3je;4r}cM<|%_zdr!7dmLRlN}DK2lyVZ|65zo z(9pQlV`%&p0N_8Hm$!+mY?PVeS#xtUsQ;J1&4y7F0f*Lc9HIbHl(lQo%FY?`V;ccb zbAUn?v=j9rub~kTlEF;D z05h(stQ`B!gM;Lmo8omwJ3#2R3}k5gISsRmCOfn7%m*;>9<)M8v$2V|DV!2O>h}UF z`un!?DWGv&;IaZ8bq*CMEkIY}_9}C9ur#jv6;^K+lYM?5*>HM#3UpJ;%Y%i?_y>>^ zeSu`lBOJ%I^Q_T6uR0zyoB=lT1Y#zW$wO!tFR!hQ7cVL?ApyupSXfxdHA(YRQ$U)1Q=qJ%pkQH9(gIx)qDYGo2DBm|-bLZE z04+*vI@mp+H99&vI2e|sH|W)C*s&}{DIFlr9MwcV+i8dIstI_K<5)pLm`E-VT7bCc zqGkbR9`RQCfkoB{lwlyKrWRs>H6(28ke+pe$?0gZJg|5K5R#~~8 z=Elax{QUgJM)#zuc0Z1JBT$?FY4GQNrU7JfFe3xf4p{jT%8>`4w=*7JGZ7&4T5!i8 zn3|K#f&J$8n6=Ldj!X46U`Rv8ic$c5YU=9hYHRZ(kFF`x&H@y=xLCUH2|Uojfhm>g zUjex1K!Uc_C*^(OrOgYJvIyGVIT` zMFnxf&pQ8WJNROU=0741;4cbnV3J5Lgm80!k*hKqrO|Ebo}JrpgsbOG|?VFQDjv zpO_DO#eg;+P~-4Y%B8yl?reN?sh0JR^g)QLLAZkc4^Tb-|NrOzQY0;~_P<^L&<^tdr$JiGzl8L}@7=_p<<5-x z$x98@%1~2GlTvo5CO8e;Wz*J0G_Yw`l&~3%;0vu9?Btl}ZXUB_dMJ z0~IJrl<}@=iq2`I8JId2?@YrQ$%n%VX-bP1(Le1``BJ_z@Iu$dcN}#RH+}h+8Hzg! z*SBI~K!}f*U_d6n(SiE% zo=oUv!t9ET=oG2g*Mv5+lJAA1_c%H@f1)N#pq(P+yU&H%=!&sk)^2>$-S~+hvzA@30O3w+zC%dwP(wVNFOH_A@F&k%x6`4h@UNfS4%*du4F zWy_dUdZzF6^oDWNHA>mYB=sZ%%YPgr<>jKX6*SG2gD%3M^U5cvS=<|U@a=bwg%7y@ z8Fug`X#cpJihj|MPZ*_X1GYk0)(wo-Cu1o{8IQ3Bgf2Z!vIM9o%zC9+(y9LfyBAb2 z%}osQTIH+s6S$4kXuAI|C>dwK{NS`^I4&nBzADQjTUj=-*_Uf@Nm~l4WqxHFTDQd| zmi#)-DZCo?q6S(Iw3cdY5GffeTaE7Cgp+eW{a-qo_W_1N3RZGbPGMfKsCO<%<^Exl z@rRNm4oC6By+;HOXtEabDCsC1RMYv5|9TGe0lqjJ_&0Z*nPBdS(@43emW`Jqos*+7 z{(yhoPR7dlw-vCuJ~om`2(utkJ_;@(s?_jl-cZjaR-xidV7!59;f4cjFUeB%b$hB2 zA8&O-ez9`+?A!u;!Vr{BpY?Khh5FhLidx*$vV?!OE6>?Urg79igdbA>>4Yyx0N*6^ zl&okZQ&L5lpM*C21s4v&h@|Nrr$b+|QzyN=0I-hpTf_9TR6eMV0Lhe`~}ARYJfOw*5x z14MVVkw92@P(<9pJ`St}G( z-ryYnM0ho|0s8qoxHEmG+XAFqXjux&lciiroH-<2(OGDJG|ITqQb?AjNi1npUs+%t zX1I4>@MvJe(1NQj#}Q$-n~skBUhdtldpd$1{$A|d<$+qr1-4>d@-;#z!zi?<2r7#n zI&uQq@LluhTAvTF-h2|+q@dtAR0$s|CGsOtqLeSr_CSLDLyE10Q=s4ylgw8%Jl5f1 zm`y3~x`IaszBj~j2`FuZ5PxuVtC!!oZWn?y<)TWdFvXHeX?Vxa zyZKcA3Yd-m*=&;+%}(WRT8D%~xAn@xdhO$i{Kw(JWF9N@#lM?})X8%m6eWNM3zTclhD-CL#y%QA_z?7U?s6)wju%Bggs z(S$5Mx<@M-zR>s_| zOWRn;+FHt5*NX(8eKzJ8)WHr_6ooemVlKqOo!$B*xz!~p>e(0l799dPUow9u1dm=A z$u=_Yie>jTEL5gG*1fDIt}RcP#cDW1X?~*cs#fGE;vuu8vY4PCJ!lvVAIVlK!W|n&O;z>dRqcA5 zi#az`0-BhosZ4f|;s)YL0G_6NPqzxI_K)sa<+GN#Y|W)>A(l0WK1KLuA95UVc4G89iP3C@cF6QWy^7KRGRPoE-ZjvSglpVI{v= zOQc-E#ZJj}7|WKda)!Njh0897c?H^x;GN5cgN#v^qpG~9u#tuV8*+?64uTo)@8_g( z00s6aTXN>&!O13dzeyXIRRS-EbU>XO z{vH_;`DM8P4K0i-TWM8GZjH*kPM(9P&(rI0ufDQ?l7+WA#bU|Ody8$vo zrUbM}TiKyIQkl1z&N33k|DR>BZO08>5bxlpYkif>%5u^@ylny~D#!Cw2bC+~;jE%Fg2 z*Bffn&vmu#Jm8&({R%6%RZ?y<0ne!(8+>Y`&fM!7Ue0H0N3M*NgYEiJydT2#I z`N|rnAX4!SJMBOaN+rr$w3%?P7RKEnh=6Xx2*{^X3b++X8CoVgrsy`rrUBF?p`-|0 zwq&)3xd~vy0flzaI&FnHG<{|#!oYWXiX88qH%K<%QzKo$);Y%Jel^7~iP9XdlcD-+ zPm-y&8{6vvCmgN}Tr+3dg4SL=CEW5dl)0Na#^6F|0bTZyAT*lzj8daKb3F!bjzM*9 zl5pw*S1Bi97T!~Q2BGwW!_2PIUU+kgc=aJ;RZVfke#QP+kZ>XX5Q5)#7i~&vIn&m* z(NAVthHWNph@ZVVa9*yBB`|ltnjXHdGZKP}e)YCDC3>+02(PI|S9T`-CO^d{FH6F` zX$_acYQM%wIKg0x6`wIkMP7He8Z$HShko@9{!Hs;J1~(BI=apGHbBJOcotNJD(cK# z+EiaGz21*^4N)vEgXC4C3e%VbF%6Fh!$rRGF67~qH1qx43)R_f{pFQ{h5NfuwBSvMvh*iWdAvOy6E0BxId;(*>>94KBZHm` z?SjtM%Jq^72a>KZ^LdGp@<;bA7^pEO6!982g004+lfspANKtrTswPa)z|P7vkT7Ug z@eGOT`=7|XHf#}|icx-b4@&yeJB0PihPAGx-bcaco!VsC)Kpz zZxV6z@kyzp=e#I%hrtoK`m8X_#ZubivMDqTf{sy`0CJ!qynIGBOQOk-NxyGA0Bcf^ zfRQ|he5n!>q}2b)2y9nZlQH@VSDpJR!4Xs8CBz?nb{Wjh-Eo|rqzN~TolTgmP-3#KC<%)a#ZsdPYhk(u3sA~7&U-AiUq92NwPL6}Z5;*)x{xovFa?n)S&34Es zp?GwNWZv<5%MAx%mbD*8HCT+ju)GU#y`!R?6%6Vewf$oE1J49tv&PD=vy;Omt=J|(Y>Rc;s6iIQC`OZ)+7(Xut<>T+nJiR6fLas@v=#q)neiLZ3tfQ>^Z`Yv50bvYI!d6=7uP-13x(wHPt;bT8) zZg^X^V7?wGOQF%AI|mxfJFF)-l-{^6n7Ln|x1KWQ;udFseKtLXga9l#dLLHF#F~ft z$gj1{)f&OKK^;h^2qA;w^%?<%;Uvl=Atwo%@dKW|XlUFdW%Z#>wM~Ug{Z%xJXgfaA zSz;6?mqYrVud44i_DgtGPvTi55Izur%;zV&udmtE*&S-Ousz0pvB-H*c@1^Bc4vVPakGjn6_;3cQvi{*5goo@PVlVoAD^Uzn=T3nDMhcg2G1%3xmr zK>uA6I=9EkyN7MbO*%AM`n5Cye3!~NdD=gPi~^PVu?d3ztV4p`Hp@a4gtbMY}$lnX%_VaUW%)QZlvuv?^EiQw1Ol0?Eh3AqBO!+S9U^x{BFR#k#g+))?r==f?K5CUE*w~D z9azHNQYK^$oVsi=CdOdzhTR3m57ST`+dwGL5sB4gQ-_v8d8<`^c1;9>jrn*UbnG zUNks2D*1EQ=NFu$(^&9jN70?!Wb0?a6G$^dlrIEjf?;;FtJ4syoEj3BR3)`38-j@6 zJM@Z1x}Uc2GC=jI8&Y|mw$DUJSeIBa=1sE&2&Zp$JdnoC< zh-`ae3HclW#P9ih86{1_rxg{BTXO~#e+<%-gyKfl`}?2xuR6&S@dPOFDXFRK0fTbx<74+&fKUyAmCd#}-w5MJ3! zFJTKXVI1!LZNpO5Y0ggi`$ABg5+k0%w$f2sEvuuam;QG=n#i{Pvo$Ss?DmAIh~MTu zyGbW-69mhzTW)`I*)WmC!Rch91Q@>(D>=|+@9EnED{(0b`ODYWYc$FMxO z-yM{3#Sf9bfC@!(vB-FSmV896{HJZ57uA0UT0~e0#>-R_*C?^7UcQe-dVZodCU{~l zhko|tA^#*UPwBn_3|kGnAli5KVn#|9r<^)dlSf%OrB__!y>Wz=P>|ZtxyRJiCw5CF zR%K=cK|1!hNqd*V+ZDQw#5V;P;ESvc2X%Xav!mqDE!e3-rP8OzOWXr+-o>6{g2)#^Eb*X{YRdKeJi; zD}gvM{3At5@&bv9wTk7j{u?tx{1k8yeLym!`WHw8<~r&g@v@F`vSY!i%Rdk}6yzb* zgZ&x;Xpe6zoMPY(VQMh1RRwli4e)s@gbJyP=z@^)G0oebkAeY)_hA1W%0L zpPFip!43w zQkStCZuCWl({Y%&ErK^0yy0UsuMD$vbn0@HOVbj0 zt%i~ zp)~t0Sp$Z6!!-yUz%*B0*Fg>)Si7A-9QqFLy0N?b*>5ave#lfSP=i#~i-89XVrEzO zlVxwq!+>W1l?u8rtqZVsci_9_1hW$L48MqJnH>9GZ+ug9viB7aCPw+M>fjLsUF2Q` zG#a8tGA#@{y-UDq@lb%-RQ?(YN`wW^qolvqgzYCiLaUR5lRj9s|Ex$t#-tHzJ(8id zpRptvNVYvhjM!XyK{(ZNWi%uF{Ip~newhu^7Ufo54JZEZzhhyaZFo&|Pu@wd-T=7^ zj5=%=X5uxMe>Nc^5sLOkp|{pW^F6n<6yK12FB_rB#20W}<2t3RQaRQr&XJ-{( zS=?E0!~i}(J(BEoyv%Y9om-FPj$GKg-{EL#4hdMnRa#cQsxROaqNd)x2Z8w}eF)?G zo8h{Kk*^%gl7;_05WSfTd@gwhL{#mUNYRu1awsu<2noEQ0-2N3rvgixE*%slJ}|sl z93y1e zYbH;skDyiy7}n^}Xs7r&rT9=31d(k!Mv9{EPHSdywCWU#`WK3Ubi~3LZ*2NQY40eG z){QZ&yAzx*_y!dW4ryGUZgFK}0b5ZA*7Di;5+|tplEap034i|FIb@W4!wk=~Pe!*} zy6PjazYtMPjDTkhCKo%9h=WUg-STYjjt292B2IW#B>mHN%t=@Oa8Kd$6ogaLb5j8C zA$4+UFL0FYvS}*BK=@2%iY&5zJh`Zw-3cn}Hc^;-BXq$LO4F^WemWgU(bW?qP`0{W zuuQzpV-L6LwbHL0nBhPiS{Bo=dsszi8zldPt^o@JgxK(^33$@h|@EpNG~AOAITtquB;$6SSIA({h1(O>0QY+OU=%0qU_zD(_gAlJ(Xp^A-1~`xug?0a(N?FK(;Qx(gEJPj z)qZ^I^%h=F#2DDyM{osEp}opUtZj2Lw==O86D+k2O!i>*L?IH^d%U-QVj zS?jo%`t6nPsq!c3>4YR?I&~4_RJ`M=uE=vqjEdu~q@(#bxCM`ygYV({L)GKa{BKjc zRWA&cpHf z?N_2&OVh}QT91Dp$huxni@yR#C7u;MUF(~|1$*-HUoTE)#tNwFaTz@wa-&wPD8?^* z0XU1Z`PYa}(DEcp5D7YwYXEFWCzAZI=H`=kuauCo4ZtKuu@Kd0+iJS*3G90(~@ zOPtDJ9*=DrvZZaRG*6mV7Nc1YO^NwNjnua3Qenp5A4u%{3{emf#xN1xR8!~p2A#@2 z>EZ(PLC47uEW=n8YT4z~?o(;sLTP9m)%5FvD(0#o23`dA%gyN*CV0=hg(5NnqN59$ zH_iug5sZ}PdEID7>z(#hR8}AR)mhUsIv94NP_CM(q}O~un&%b`&Ls0K?@ZPmP!Foh zOxK2Opfj(T+ZT@`$EPB{G=z|D>Wh-25$(B{9S>yJeu!Cr!6vMBF$r}%pUv;3H#Ug3Gd_ZYf2nWhp-{d@-Qbk(|HV)G_83vn$n*jy+{H`QN{G*ct@_vJ^xi7V(7Zi z^*@nW_g^jgcmaN>B>?!r&;Z~Eo>OG#)VuP1Ha~B9>{@EmN_h9~&`w-4?+q_u*z}K@ zN<%nFIQo~aV;YxQ;{BXvajX`2y@l#igIt>!S>*{PheJ5b{f)g8v2A6&8gpZVUnI3t zOEt!DAR*^OoJQ0`@t4M(TmxYU1V)igd^e34dYNsNPI!p+V>NT(j(kxDCVWGcodti8 zM$F9B$JDnSC_eNQS|&QlNy}r8zJ49`W!sj(iz%U{ZLTSW1#c`AQTu&24p&8npT>(2 ziv@u65b<*&CXzjmuHuT6G<^73b|$o_L2!MBrWed0s)YJw|NEY6Tvr7VM*@n|X)ufb zyz0>Gw&T14Zq6nPjSaBaro?n_t?jYNZ+lK5lxw7v@!INfJ^AMYlLA-f@+#lQ35~v} z?S5ONGA>2%BwH7-KHW1+>q*f25?`WH!nDGRH@F9Z)5l<-8;X@x?9UioHu7aFHDeor zay+%U)Rx}vmtcJJSA_D|qGs?j#n>o{dFtP2*r4~l`J1$KLrfY8qbS!rIrh}t%P}zX zj`U5hk?|4BR!fk|l79!p5zzE}6Y@8ksp^lSLDJH1_%Qk!X+I+9K?!%xc9W zk%zmNi_a^$Db*UI3G<$Txc~J6Y?s3KICHFYzwfsH1dZ)vFh8`Niq%s%dU0R128-6h zU%O(hvfH1sW}ouzCb9J5bBndqTTsxLkDbtWRkSi+x4&23WbMXyQ9Vz<^`1rRQ|mpu z$<6;cC=^eFq<>5Ot`P6e0t=??WF)o5CN0{leq`bK15?Qy3j_xezUGHx^z{LTCkx%2 zz;dRi*ko%Ctg3gdc@v7lbHrc?pl@QxeIJpON9qFLPW4uM)A}J3{I9deCZ=%o;-GdU zkIPS`Tm8ZBJ=c%laZ$_H!ggRDnC&|H*6IF+F`yA*;D>~+Fi}v$$S99mnio~6Hix;a zVOdth*+{|H8c=CTXt&#T_n#|95Pmud<#R_e@||u*W!6If_xJ{Rw23;WW40%npVCI_ za*At}rCR-8W;hJRk`NTV{|0cIh>2XVH|ZTBW{Tza1xsH`O_1&_P}Lprl5SI7fI>*& zFF&Wn|{0W+rwHy3}p`U%9ss8A2DHCiZb> zZYn9YkBhhlrT*E{7OqpNuA|q-8$WaxGnEc``r4Bg>2$Grm^JU*e`E!aZcHueVxq^? z-1<^Z!b<>Mp@SIeXz%tjb#V6&`c)VvUV%M;AA1RM@J_n)-eX#|{QJ^X6&r9Xi@fguS<(6I9eUrzqqPbC9P#=`Hi0yZ7s?=cS_}%ZW+U9sc zA<1GstJQvche6h0enqSF@iCsm6z|eGWM7(p6+5cvtL@mro$LFj{*VR-jP*+TP|~;k z?SVMBY-~Ke2coRdpsbA&N%*&v3LEPEbq+Z`EPneU`(6Y*F$f8$^ z2|P7}hHteuq1rCEe6Cshg^i;=m*8}E@T|@7v-kZmihSAQ&?$oqwel!o)xqKQQ9n%; zv#VE!%;E6fpT81UqgvDC=A^OE>?j-pbMzPU2lMu;{u*}Az>_)OwcjE+Uqvs&)=*1L zosnO9iNk1kIrft>x#VK$_u|{`U&-q7@$W^D>VH5%CBd03+=Ik(PDj0BkrCEERxhqw zOEQ6irNdE$Vj`hvEg?foJjnJyIJ0+x}Bvmsp{RL zjoRrDj7IwS`Czi=B~6dhTehzU)%LiTBItr~?sGcBI7W0eyeiLxRM)8P|(OmI!q1=v zZr{_S;o-ad@QTRzX~3!+4h@udj%kS}ICngph-Zp%C!2VZSW-`n=OD5^|Qyv2j5wZx?$DG4A(=|fe zX=qSXw<{#F9F!a_k#!z}|d?du_MqxlkFMO`(d zh>}R`I|;-4qRLIdDHtC0+IK+&2DW}Pgpr&1@SGt(loj+xmT{u?AC4FiWDZ*oBR<-X zEq{FQixa_G-Ns7S$dEcDlv>g0+?{!ru8ej~2GjHKV#TiZDbbYZTlj`zeR4&9~*b`X;I#+VNu7HcB+)6&xjNwOGGm^+$B}!S1 z6epK_wsLf7Nd9$XU!=)0ujRde}D02939S(Zlp}^6Ky4LmTn6 zZL&q)$-Y}d4*&X0w;YfGr->%y&&fPHAVh5V!x_S`gIja_coVO)6a2opXra>k(y!I+ zB3KGy&NX|-;CCToDA|1!($Ibt&p~-V^>llYqvq?zTKyE5)pAh@$4+0!f`K3co%yYB z5BkN;dhci3cm0L5_~LXDXiUY?nT~CZ?SGFHtqA+N=&xVXI_EtR)*Zg2ZTdF&OR3^2 zeeVW|g`f*pTgYTg+T3-oIeR*qlI6H_nRP_p^dm33k(CL4V%JZiRRm>Nmv)p-q;Gpy z8w4Y0tcVQ%i*9Xh3I8-gf&aiWW($wunYia=(r!eaLuE zf(E(I)VE~lpqg6aN>u-eb}IUZ2SYmkj?WW@RI{9txzEr+fU)#WbJ?qav}sUL7H?UI zFR6QXxy<#ruQ{~dNZ|t-DZfBC)QU-c z=f0cKEgSq&k&q$77?EdSzl0_YKgoUzt{ES~AsTg59?;6e{M4(s70a*+1u;!ak*Rh^ z!CGPjO~clSv9~y-o=3QOPv3|yY$m%f7JAH?h0vUCaj;VD8wR4E z2zU?A6ElF=wkM2k|NY}KJV&#hi|7ZJ9Nj#NgIY-$9BFI;%X#&Sww)*P6EEBLs3}*p z-XKZZ*S{j7BvSP>Em&`mDFviXA*A)};z2G-DR8==$;O*nL09 zq!WV#5g7g1)G@Ry7;2aKjeptLzmL~g{X8stm^Ih$t*Ga#f>)_*#>FvN27+xP9`GWo z-bnepeHFJ_FWnuVk#qi+#H7NY!J?^mUdEY~BzwzvJk5`DVUo4DuXbwdGrpaM!`%bE zH3~~xBPF~cRBUA6*?}M{Edeqn90!ArC1zKS`*eIaKg9eCFAzfc6wsmkyV#PENOJ`m z?dh3eJ2^L%z{PY?CtKr@Ok-86=*E6*EsJxn6{_ZLNzGH0z3?}GV?DzNuJhsyLPO8L zk^3fYwPxUwN63U{^YPl__Cl}s))A^r+tVgD!=4}MhSkTnUY9#|9%9gw<^(TMoLjWy z&NI2E&YJAnIR-2=h(j_5&t%GMUl|U0MH1(xR{dyMqgx0s9~mM(Lt^#L(A?Z4WfTDr zZ*<=a>(`pzhY=j_y;T=A{=^X?1^$Z;JtAlE!lkZqiT%f}ovdp5s=FsdKf-=J%I`{PRYs;L`VRH#*P zGj)3^rhS!%efOcv??ys5$0(DCO?oCrpS%MdyW!N^TW!vDJE=5s zg=mI&Lw19fmFF-#QrxW=+S#M;XFGm}0G7)zuQ^Gz2NKKOjeGwFGjg7XWz@ynwyI15 z_jQT!t9E-WlN?@_sZs3cto;*mUP-%c#)LCJpI zKQ@jFdv6ACdsCpUvo=k$eA;pq%4f~(uebeQWFJ$0FGC8O+|!H#KT-MK7n;#g2l)rZ z_2C%#hfYeg9xAV8n#panR++aOT{U~XT~O6NY8}4p$bVq)YM>dv8*Pc59XoitSm1gQ zcguJtoW{if&}~EqcX=Q6=G_JrI^53#UzU8&A-uDm6!CT2h^(8A;Et>rupouTaahdp zVIx8}-XmL|_{*!_KfXwxz{%~99k^CKa|Xd;G$`zn515u188+C4O^ zxuGx5Guy;|>PqAng1cIYw_(HmTc)7OdfeQpXS^#cGv#={AhJf%@VqgW*}gwTpd{~J zyImBA`oyIAz4@F>Z$U>r_3pCFEbDQI&j8*aRJbbL=O@vMe)agin%R}Nd08dCYmv=+ z1aU@V|G_cjKQ1P>mv}zngvk`?wT+!KtmcpFR%kz`=f>)SpKf{8 zQ{yL@Q;E29TUS}v^UJm0-0piR>Dl;v*SNjVnwI-W=&GQSz1k{tP#*Z_?12d?G3z)9 zH3LpG%zGaykCW=`qj%@Kw$)r#WXA?nNYIMqOj79;`>)h+nx$I5JC)U`q-&j}Cg#VK zC5*fIOiq$EcSkYo%oT!%k-NyP&otcM_^SR~ylFB?j`D)x`O@+6-`K-8Z>bY3ka2{Y#l=;@Z&ahZs8-fxSL*I0a3{bx)pIeuI$m7_G(xqw4lq>w{*#%M`6n z<{xb_0c~@URG#)`gu{Po&XFe1&n34yEz1Q zJ%sbgn7?|*zCuWItR!p#-Lt{P#U}3%CQR{;VdB@0SfBRa?q2yb1VFJFz-=F}5LvJh zTE=*?{9%a+6t~1k`7>{n_@X za3z@&C6%2_v!P?(qr2?0%!auRzRp}b#oV@xAyfEvtXBrtrIiAm2jX~Ie#KYw@O6za zBG|}MoBvv9lL!0Pw)3OeZ4d6v&%5doUcr~s5HV85H?cknOX`fbNiEf#nU9#v?7v>B zbjXhks%MFRSWTg*3@Z(I_4M{sr`V4Psv5qLOz{_?QyAIf-#MDVzH4sYRv!pwXE5Gq zH6=HiYDI^8M29={ZEJS8GY2aa83_U6&?lT!@{7rgWF{>mbmZWbKYnW1ln8o%i-RkF z$yAvY$^NGL(U%4%^K_5EitL|g01x#m>JxuIkHJzX|6`OJZrMm|O+9+jXUKZkB^x+6 zL*6+{qb6Rk@)2ShnW2=Hg`VF|U#Eq7lEjcD8uW3kA)2=IwHow}^_m;ehIhBh%+&tl z&BzW)7QSTp7v3pmylH2QUxn?so9Jz3SrbOvD#y@$Qm`PeByTx{%h-G9cFX_v4!N?K zg&&v3HcchK2~EnNExU1#!TU(rwB)`o)kjBxVdwD6qc6H<&s*Q$p9(cSE5ceE5xT@d z;O0hm*Zw16ASDDA`aVkREb|RJ$&V0!lH=bo#*#LLKeamX)2@B8D`Gt60?Zwn1fqYmQHiFv{ejTtl zuZ~8#Dg7M_#pUxWok&r(zoKT>-rcFpln#(zMuH=5x~4^Ul*M)_KK29i+=s&~R0>ob z8I9gZGt03@l;fF==pUbcO=rl@B>JnfNRcX&!Sm>(LDV!;hQIE}=h6p{j*9nSS9Zt%v+@P# z3g3}MJ9TbZ(d{`M+xs5GWKiid^7!1!sn`&(lXh@nNpWZ*)DH`LUl@Sf&5^_in-U9l z>JX>fRn6U^K0=_#Az@UHFZvsEx1%IKz72tE$PYx1tt4D^MT!lTk$1wol`Y=Gp}(a_va?{`i@*zz z<7J{!|8ZJ)yP-9g)aGFd4|$PG#X5b!6h`_gBrQbOy~xRL6CQ%OoocuC?I5tBqL|uV zCP`up?OJZ+@ATc$s)g(rlAE1wz1Q8T%{ys|G1I>V)M$!W^f*T8I>q!k@*>wMF~`hqz}%D|BxJKKVAOb<;oGNMNF{yR*f&oF6y|=U zu4oq_YTS<@E;oyCZr`}o0BXbks>e&!W>34&166fDBe~fT4-wZ!Zr(rNUs{#Oa z!T8giOlMRUUAlRnO;)(sl%|w-^(k+{jc4AEbBwh-OmC0 z`TGYLM8ouUsa?e%5y6!?Z4aN|S86$dXKZNy)L5h@D>wG2by>1M%oEK)Wc4Kcz(Nvc z)>0itW5;GZpez`GxKa~Ea0L#ipzFPb!51Eic^0td*`OJ&u+>5=ifV6fi&evr03&p* zp(Rnim2lnEW%=V+hX)TRui&r2A)7)@D%iYOalhh3tZ5SZ%y<{osU2wsgH=`ODZe}H z_QHe&1&UyOx7a|>$IzTajv~n*L=j`nFvvM)RWO(Jt$9wSNz1LqYP#IlwCLibGywek zY_?Vme4deCTjz|^uz(6uJ?j#t;QhI5Qvd+r0Ic%YRw#=GSrxp@0A!$9g`#Yw6LrDD zjT2`LEO1(1v{10gK7)bi*K>v#K5Cj5yXWop>&M zo2Hz$Ot0HIBF6>e!qKJHoFki)xu>E76F#0FV4!CVll=D}9i)FWSE7QRy$RTrDk_S^ zDAj0oN%>v>((us*& zIItP9*h@(Ro}BabGMp%Z?01&+@EwfOY=v+bo_#(Rz}_cGT?G{2FYUX_(lYEcjj{GH zy-{UT!3KLd0`ikCQ}waPv$mA4HR#vMXtwhE1ln)Wo?36b)Pz9dbL=4VJNs18zOz6W zovat;tK1*(uSyE;XPZ_cU9`19HB|Ye(=TImBLws~j60^`xk7<0Ov*pjcDZ@5ExRBk z-IL)-F=x_qm_I%#o$G-2{`#JOIEbK8FZp(vxDp>uDuv9`rrDBG^hD=hht#xJ0z9`x zX|)GPq}ZVA@1K6a{-v8g0w(A!&s_Z8n}>D2PX5G@DfR89(!WhlXW~9cX9(efc!~w` z`|Mh4GlP?Yb{vPKud}J@7BmZh7JyWK{`A>_CdLTXtKZ6xIQy$%bX(+nZVB*G++jz#4^J=(y9a)#&qI?__Wa z4JqB@06l^nw~7uhMS%!3n`t=`wMLmm&c!3WKeivgS@!jv!BPbAXKbp^+iM$P$aaVLx-+CoyE; z_l;~$_x+r%yRP{jt9cz7$~{{0n_5bE?4th&ZLtrD1*x;;2?aLgCw}fF5X)1dN+?>= z)@&U94hdhSo!}BJquSz`m_{P^U3OadM zu*gNaZ70V+Ymq@|7_rUnYmPgG6obVYZbKX9_47GATe2aE$p+XDtL2Q2;UR|&g>EPW zKm$$6@kg5_xbnrQ1_Xh~G=-Jm@TP%kjca@s*YxGgU%Y z!ll&(zq2bP$uuX;-7;TGVb_L7rHKqlpAnFHl)gYI{TEOq4n%uupfL8#AW%Y+p8;Yz z11S|ScM*=*H6pu@cZZ^n2pIDw9!B@v&#FCGk8MKfT;pGazfsXS^vS>hJ24u*ZTwGI z&-p5{^!Y4OmO8(!_o&WqlYvdz5GO2xg~Tn(z%(794!hG)2cc?MpzO4J{k}{cbH+L* zqI@gd$Tri`KVbZXAj3K={0X>n;I|V6I=dYEKw5H4Frlki+K@oTuvX6_;cUx| z;4y#Sc@6EH$5?~0A>CDzW}iCO!?4NPLIH|(UPI-!8*EfoMcc#up>UzlG*bjI=U%5zo;9 zfSn$ERfVLS%xF;;%xEycz33r3Zi1tV3rL6|a>UQ0Tlo_SoJ@o_Vtkn<^F4X3R4%R% z__m>gd9b45Xn}6}TMdVK%g>{?35&W@2_$$3U`e4H7$yfm02i@GLJWGnvRojpST~qL z>Ne?Tf>|6xdR1n53a3)K{;ymlQ$mnoPAvua{Uqbp^<&y^=BjQ#sgKQb10HMpC9}_V zz6J5QqyUOHcs( zZ`Z28@aoKkxS5;#zX3;ln|Q1|vmz!_sZIlKV`rR88n1bkOX(lMh7ot5>m8|wy#sUl zI1K&~pc(VFb?Jp^EY3yaS?;$J0KzV#MpCI#S6G5%r0rTRlAb}<{lXucZq_BBe-a3) zQp>;qU{AeTXG(el*c&xP7u(;4&4Xmen&{;kaU3}t<3xO3AI_oTWc}NT{9fitzR@OK z<~m^^egeGrTJu9OHyf61&%gti4heOKj%n>>an|XImys4A#r?Scmi2h??uCjL$86`e zal0i0(;7sANBHnL%-^|DQ`nYY)#7Ut%)H$Me)$6*Q@s;D`iYD_Ar5-oZ;IzarD=JX zs1rmglcR8&FpSZ!jyy#nHfY}jwg*|7Sc-;5hUVa)XGzWRx&`)vAw24%cLM{IS0zs% zwfiw})%5q|iik3VA3B#dOd*n5(uv+`u&Wf8h%D;?nhpSfsqsC-Mh-%pARf3wr01t; z-QPARg{IDHfQ>f|LIIL|NX9qi3Ea{kH2jktVuuMlL2}JY>3^5NLSAto8uteO!mw2 zTsq2-I)u;gYO(3eRMh{;o~=CP=fwkk(o5by zDjmC$E_k>p;BviKKDPZHqiwOTyB&fDR)gJa&z^`u!PIq4tEt^&S2Ak7niqXPu|icQ z0X;ruz~X<9_K*)rjlaE*Xv+vIw09AA9RoQ7~aW*?04X$od z;#mezFw1%7qjUZV{Gb2JYCGTnH!X>CUZdv|)FR67Zk=s;T}>5+g?&)}g6ihX?}D#I z;MGsS-~D*8$W51f*}Y}nkabZ{?Y5D~+nowmB5v{7nt|6Z-eZ_z&mb%7Dgm;y>AwFK z$$HU^T^ezAZwq>0XgnkNNH+Z)rqBj_NNxPgD&;m#fXP5RT6AR+Z4IZI8W!|M0!i9w zG~$tUEoY;unrb|;+?^O!28>w1x)kM66nIbX4ceb|1;{5Fp|6_R!iPj<5lOY8oz(m{ zTvrQ2A4OM7KyiEf861QeJPwn{&_c#JZFx%Vyw7O2CFq*AHI1Xf*89PfMh-Nl zX%32EX#wu6>^DkeqSCIp{(-wTVc(4B)`AyeE-twda+;&q+cy^aZCtFpnX-^boka4Q zGe-tW*z@O;`hiGys%Hb_kLbJvAKq>r?f2k})?mY0`a{cfQ*Ka^eb43CZz3XohyL`4 zF*sRk-1g1cmTYrd8<#FpNe#U?J9Z3iTCgexQfI_q00?s1aQ!xu{~hRiV#@1+kFfdK z(;zEv-ZrPrSQcK2hV)zqGaYWa06}R*Mh;k_yX`zR8VC7t;tH~P>{2i*9AAO;0r|6I% zuvxqD5!pOE9SXYiFiHJNT8dkNMY?QktyiU|1~T_&@ZZM=v=tsCVofKV%0c4bLgEwD ztLy&URu9~(wPzNUY6^pfkN|)LVY20`V$%9RQ}GB@6!FpSirwcK!?goVu~5E)kz?ye zE7;Rg+=`hUqa|FNViStfne};`>$X8?>4?QIU}h@%oNX!Jt&?i+Ul+=1-vGsx?%$Rf zGLAnh=E%dLdQ*U(0FN_h4J#Qcq8KME#>(U6>p!y3{}$O1qrgV+odZZY(HQnTcht>F zY+SBfF#f$xBTuaY#j(WHe&?mpv!BK9IX>-L&?5f#pI>&ot}nyimsO`#?BLMDib@Eo z8a#(^oJCP)W=bvv+XQ8lHShhWJzEi=?>`@TlH;;hh-PKo+Lr0*(?i3}6;9sNsag1~ zG~baa(X%CiA<_cKA#Kh?Zhq|-?6T>U82=quuwTzAf97}N!j1l6cz>IH?RyAQF9Y*h zkUa~Gi9&~W`FL0AozB%b|6zZ~OUzSoyWeJQ^W&0+pi|KjT_rzJV|+flCg!s0xDz9^ zna46UNO6}DTsx?1ZIw4V#1es~C~A)RcFZ`@&fuBo5xvr5_aVwA+Zt5UxStO)3}2c_ zBxCe!f)c^rk+pfhd^oc94>dR$+BJ;5H{q>&|2@qvB>h-QE{>FipHlWWYE@N8Ot4kl zTXPxTIe#AQ_SEvc5?Sj2&7Fsz)3gh z+C|v-ii6^yB+nVgJE(tQ1*GgpFts-`UL?5QwXS5SsrefmxKQG#9Pi|yzY@T{3NFP4 zBoGp(E+2!rQ`XO5_X`A{^K7rXR2LusCn%Fn4b;VXiBzaM6wDpTY}J%gn!0U8OxIVF zi}4dV8_OVobdoRomiLUDutW|UEJuhTNfW<}y@u#(EF~8Jiz1PsbR0duMnF+yPM7;r zzgtD}(IG;Bm^KmLpwsWeHk}4!puTl49!T|`#SLOyp21|3KGG(fH!p8KbsH=$Cn1EakH7%{|XyVACpS%h?`r2(|F=;MsirsjR z`$?FwlEPQFK~S+pXO?G5Nx`M|9k;^!n!;%>^*Up3u&3;H^?YucK+o6<`|{8AYLPAv ze#Ip=Ls>f~mcTP}t(}>-Kpj~21VK}CHWY`_<7|{Xl-yiL!(U@}mvHi_gX%f#tTXH7 za5Re$^HO2^7-=j%7Xs~s(un%;X&BvZPNSPv=*4MT=u#S0jb;80_T96Y*e(58ws~i$ zF!pNx{bj&tmBZWeI{2e=7uYSVw`6PSntH0!AK+%u7OxRU?tM^4=o|&2f5*qMzQ5|# z=C@o_=HYo-u28v;An$lEb}8VY0K_Dw@G$mDJ4)iyZMu=?y}|P)zOBrVZ1)o2<^3Nw zSl7_6`F7o&OEaRLCUjs4l3mAfHg0@1lJUt8UJS<2m>T8Os5z|~Z3e9mFAApuQkrDA zu#)td#_f1D*50)J{`4p&A)KZ(4L{2hLH!}6=cVOxB)l|{KWBsd$F_KiS!`A9)Mb$v4Q;QTS;T}YWeXdoT7z}JrlEzGGjg!D=mdhI1f|Pe zX?ilK_)cWy5|XhPTyH;OXA$LY9^6OC5u+y?3!hn1f5)FTq-wAwaYbCIANq81)=G64 ziI@i-G*fr>V3*TPmQs&foZS%vh#MX~>@>CbHow@M)hJmu>-Z zdpGPzOTHe~ICH!%G1b7HuYi;0SUMj27!^JnU9OQh{!(0n`e1@a^}Z+B-=sypxemLD5sK zdnq#xID01gR83i`B>p`OfG6$^Vw2?5-m0#34RxYf$|u{oC38MoaR8gNWmtt4@omk~ z#^vrap)8n06O-GZC7stfzuRoKB@l_06;ar-u9oLyyXDv`19}Hnk@G0 zSE?~SJ@`;RBfMCGZm1-iWkjROA%o4MB3~9)RI93u@K05a!6D06sOowWy*e2?5x)6i zo9aAi`+>!)Eb*6&?DfTy2UG5~xP)IX*|G zVKi+n8rLvB(jLzq0#sCt7{MchAZtPK09LC>5QO19NW&`&JupR&pw%@J!5HEh!l;qQ zEtim?0a3k)R+TtG&le@02hprK-(P%w9MRCjn&O_rfsH0p9^6A=HBvSnQyr%X`qclR zK!LexX<`sME8Raj0R={Jh#-a_E*W6obKjnNEOD<%w`JkM)5S)QE=k+IdrtnK0NCG| zrK3HaL|bj2QwQx8dl4NKk_MtOf?Hr2Y};*2xQYtc@ieQBG_S#kkv<@m??!+N(6=cX zZ3+0C3;-GcIkIBT5Vwa=4hI5A;S>xs9@v;Aw5Ql^ zsY6idD;g2R2pm1=60;E;dVqReac?LB7i(qK@@0}5Vk&;RQB@N12lLMZ;63z~F&#zf zKVzd2v#CU`g{S=eSvfRnlu{%3!g`Wnvev^bM>MPxi;9|MspMBb$2Z<|0 z#?gj}it!YH23<6y!`fhdhF!(H%Q+XB+P%85t*9l{NM6gdYSdN!5)~OWsHGWCsJ!=$ z2!6R7ma~T>T3=+a(~}%PFq%n1>~Ge#SE*j4QN!eX8xn$^*}%H_vmcBoHMH9|d*AOc zDIpzgw=E9>k6o9P^jbk%?;O~+ zSBRN2qTT_InFc047G>V#P|?h>$rp&@W9Prf{r=UkRNH%NGPF_+rSdKtjx(CsXj3+= z)mqtHuHEH3QW(|eb}<_JLzlvE?57DDj|if3Ea+HO&}Rj@c>*2B^hu2%mLwE8&nT)eu00&Teb2 z7zP(ifdxwy;EXqqXEN{& zA1Yu3?w@neknc8VZ}fpOO~OSP4227aN^U9^Y_z~~4W0L&LGD~=|E=b8PzG$gX8$dU z!a4$v3+HF^-f}y6%lE=D=}8-+SQeMHBB+K*8p}qFv|qv|l0tGVBBhuhKJ{(s|LHv{ zS@dt@r(1<#Q7OO5>y0VOvcHpU{9z`1ma~$0S~5Derb;CtYob=jI)##$Fo<@=YUJCsDUo zmd0*4V$|CYyDYr5WsaSibW16xuJ1uDD=B7lYDhe_#D(M|$05Gf_FXw{M9}=QNQ$Sd z?-`r&6`tsNu{=1p_N}jox+@a>hqnYEQX&PO>E!vZ+@Iomp5`R3*(U!^`qCg=Nu;WO zO?-?iSh;_5MJRGo5K>UMcTQ_x5zGS|g+ zGeo>i%BEsAJh@t_zlV%Q4A?Zdg2j0X9NlVscu%OXL9QSlHAuGA!_rX;0BjJ*63}3- zIc!ku#&NU^UDHPzA86hhGL_tY;Jh016W!mnV0QadzQx&SFnh5`f?$0q@uzP!?IchP9m0bm!m)bAw|u^( zt~P-U*xPmL?~27$a%tu_WtB|$RnF8tIDnl%_SG{<-Szo$E9eG}f|NyIap4N2@p*EJ zHz`?ccAzN{O%^+qV*`NG*$13X5wu&(>#kTXu_E;i4!BEroH~s=$hg~t&5By_mLJ3c z&exEP3jV;zI3&uDoESk2Gj{)|@)M1}=dU*Pbgcjl zC}8G1IRjF}#kWXe7VYr~U2SS=c=o#0!mo6$Tls!y zD>0yo|LLbK_}*U*{r(8jO2$}w&c1q|LZkRVVVzyLTvkgwAWtA(PCTe8nZN^5D7}X< zpVt&Ie)qEk<5F3}YaxDbh6!9<#*KNux)(Sr4bK}gw(1L>is`e32kf=KU*XKUzX#Wk zUNh{J`#rDuvXy9(>>$SarGQ!hUb3E_3or%qe#SI&w#0>Dx}e1wlcqEwBlVI-qb5mX zTWptIkNRo$TBEc-X7+!7^&6mahA$TjSrW=l-&@|rkqFb_H&v*Gu7x%+hJyj{aQ)_W zHT-cHH7fd(uB3%e6SERELl1!FHMBLgm=zIq&A@_s|Df8!Dar=3Gzscyl{@QLjt2i< z{2tn!77$U(tNLMgd33L(KO_-Q0A;X^?RcnxB5jo50Ceuu%$gQ`-cw2*nkn)5!IWQN zhEP$#s8m`%7tqP&Dp58D708>9a;V4DO*BGyPu1(ffbJ&_KQ`ONp&>csA@ z7?mqeVRo-_>xu{9bDFf|CJfGKs_c|g|^InCiSY@2VS>3J|+8t|D&_VoX5GFIK ziY+Q9%wQ!k>V0&PV;bP~+==Q*KX2QYsPBTKrm-uksXVWv%Z8s^0fro9d_KAN>g%bi zWbI(JzG{?=q9cvKeo@<=3GtcjtF&Wg+nrbX`^bQ^5ckUgf#Dr1)%pv#VXu%NP!s@m zkJ+_ewRE?B8pwKM=^4A%TA%^~@-sWaQxs&q@EjU&g)nO4e|dT-trO~P11JDX5UMDIP46jZ-KNru!@^W`Ew91M^_j-Z^{0a>w;79<>;GQHl9tK&XgY3aljQ6D&INdc!9uTNWxsRi z@6*Cs-t%BpG}1H#Kq%4YHi(ek0Z4JTTkRQSqr6WnrI(#jKft|? zEpPO^_4xUQ{>_;x9xcaYd_Q?$r|Eo{1x3mc4iy1SmxqI1C+1p}jofeF;%Z9|tzvy> ztuzw}aQ^h<=Z+2?e||p|uhCZtteGm-Mpb@a1YZ03Wb35r(p(;IHOc-C_-*}A2By50 z&4

MjvN2T^Vmw9iFCC!ovhvTSOOhQ@qXdNU_ZkyjZWLaikNcb*`qRCk_sf&mC!% zx)#*4O7#xD>KhBKN7`g}|E{{iYfkU|K5=^HZ)tZTskY)e?($;w{n)!0kzRWwGeW*O z^?TAny2R0z1&nccIez!}eO_Ea=uKr_X5suL>gMqBU^;{y1#p;k-v(@kHo55*AL0ZB zadUZU3d3b26&toDq#Tv+MdtpN3upd@;DLPu=9+4M9e_YJO=Qb;TtvX91-6{Bf*G}R zBWVd+_a0NHFMf^VeWgrWRSE(G#CN85F~+7F1Y*Xgk0@rbpaw5=WtRx7gbZ?gH+ zB}gKoVx;K_FLY22|02oJf3ALf!F-4BVyLCMG1~N-TXRxsxg~gdL*+=;O3mjAY&L6L zT~c9G&Mb78J-=l8ncaq3r0{-M`FTgPht8mIB>jmCr93GGtq&CO z2d$s>8vJtkJ5t#4%O?K0Jr*ZECANg8Q0*OftNw;iXGMygfSah6bUKFq$`%6%yEc4oQC$B`T>sG092F2hDF0pr^Lu-2*5zOghYm*Bhu3+(&gOoI zdvAV$*HsrLGym9^yULL=>3)8rB(1n9G`nDqv3c*5yLe7n2a$gvY3L-mx!CQBOrR2I z(7Ekg4&lGOF;%^thWsTrk!7T&JJ-xV&QZw-%v_AgUk4Q^Zv_v~&rQ)N#^UZRwao}b z)^=m*wzaGTk=hEeil{_es07S`lPJA-VW}QY%4-4(rg&!g5aNs{{;gOWd~pE ziRfWK+k4Z1tOlxFwU%&U(KZv)rEc?fDLt{`RU`I`z;l#PxCK^!sLH>duzlQsJY^|G z@;8HLrpt@EIQ9(rKfi{Ge&^>7@xa_K&C!N6jm&jeD+q%Fl{7nRJUfBpglm1Wf9bR7 zOzQ{8FvXnp+HzyBK8Jp+;O6m3ZeZ{n>PmL49uYyGAUsy|8t@%;JR_J4mPjG9emzaM zGuhC0YT<+~*5#54n2Q6=lSv~6WmfS8V_rt1&OwcOI8WBf);&4QwAz16qHHTcLGs=0 z9MS5?jOP&G7{NRLC1D!EK9=Y3XZ8=$;_iAX6wl^oeA;go+iv4XaT1Xlczy`zLIS3d z@_07|{nA)uf(Ul~Uaf$iv_ErK>u~v!KmvCCeZQxsVt8s^PdYz+Y<3IRu{~I>!@pI2TD$v5tA|f&?oKiSmsvOSm6f zW<(6rQT>CjD(7~HW7~XZyqw_6U4Z&O5T%8{P18s#ma_kYQG(v*_1N89U*?I``8fqC z-A?=PI!gDN@Kf9O4dgnUWX7UNe50|K_1lC$?+sugsf(ENG{x<;4Zsh^Gv%_ z%a6W!;c5IcIZZmiNhW*n8htnG1T=s?Q+s_oCF30>K)BgxP=L4ryo6s)@3Dm6Z;g%%DT z=;f$qJ~$R3X`mH5SA)LJ5;?_|CL#Y(~hUeNB{tgcIznl+_>=O z$+_O|&G&OUrgl7mSj}_J0-!^?Hy+b2cO4Szr_qO(4dqsg&jy}rG+Qi(ry+|8vMJNj zRLMRf58qVteP}fF?GV-NZd!ZPHxBR@Q}W*Py*)<~44WH8adUHzyMLVxzi5>~fKun> z>di~%{8=m)`zE#kcGVHQtm~?|LbSD72%(yxoPvXt3Vw&ANe`cOxxE3&j!3NTd$x=V z({Ob|&#uaeD`ZM$j+wbPR6jD5TTKEiM!S$9*^WD8d7y^HhU9OkGS^ z0du-GOT{Vr0)`F+iBDL2*ulYpY33`MnWK5ny~&(xsh^HeL|d~6d)pkm5yx`191@ zRK3e!e6~H0xGt`ie4g-LCKM$Ji*zUEM)yG45%#0WCV(YchS{*x#Rh^a5iuk@Xjx{OuScGZ;(#pUu^Ob1?-QKm2z?a8eA>g(Zn$f!3uRnC==#} zTes5Dfai4%Zug}hRBCZRXel*XKb8ktBAELtPIXa;z5l(GQ_@xj9wQi1>up(FqE7wH zOF#c1J-Q6RZSycX@~zEpuWc>56HHfUT*>+BSX%@o$9}yayMaUZBF8ebIIp71SLWrj zz-YM1Eh`JImt(EZ7q%{T z*DhCJIJtQR)3FG?)-b?^JLE)bd!L&rAOcP7xvpR&(h%?)^960BXZz1g%UT`uN?4O5 z)(Ai7xdqN`2{7XLfwbCm+2EgbHgtUR0w z%l|=Y%STy#7RJGTPrZL_$r6-)DCMT zMSmu9f^aG%C(DqK9ZD$H^5*^{kT-+&n-bg@7J!053eZAcM<)ePLcq1&B{xuq5UY_* zYh9g|Z5RxVwJX>|;$zc6Jn4q1PGOGIeHiT8;er`QF&_XzrZ~-fvog4tohOT#bEdqx zmRXOOS-N{8mRG_IUs5Aoe(tDdzdjG{SePmlJA#aLz!nYH zhL^0N#u3||$W{NtJFJ>_!{Fz}S3U|dG8tgxZf`SiJ9En4i9Ac$cMn`W1d%o~ptomO zR6PZRJSZLd1Y4g}-yg{$fy3cPnOX4Fq=v4k0~UlO@`;u1{3niPuG?x3ubVH^>(OHJ za1(7gMibWOZko3s<>FKElQ~F&W=zSFTG`#egI0%!z_piu1<=qEC_3tC9mA3A+@(rc z%O(yxpP5~R2b^P5#+Ky%>}Bm{6Sr&{cdRn?+Q3Nv(Gr_3B0rJ{8ZJ#jIjE+zTIClX zg_Yq(zV`Xqn1bTcyE_E|D6L~I?sP^~(9`_mWO@=khkXLi35Jk3Bxr`b(>E=ujzvO8 znjVo_f>q+!;8|_85?F1r$V2_)oX#1^i1&CDyNav6ZzJaF!bpJ02ND^@etW6Zcc)3T zJ>+7l6F&WY;dnP0DMb_prnR;fV_Uo>sQmoiH|o(peIMexH6yhI)KQnvgCK%TQ$!bD z;H-HA{p=7z;NrI|D+PPwHe4GzijQ?nxzlM`@>-OosmN`0%(XtFN=z?iYgTa&@__xG z=~(y3L-UBUP(US&Q5*8Jsw4aGDgWjT&FGgnK{MbmcGQsfr-zv{<>!e_>*(}hdtaTf zsqiW>Q>3d{&zdYOFF~(q66gqPX$G#hRo>470I-?6D^X6rd@cVtkk2*oVyfZT<&l}q zyp13hz|PSl4?HUTLc>t=v)#JKSqlT!WhkhD;!hok7cn1`c`n=lw-77$5;I^>yZ2sb zynW-zeQRD@&h4kt$RESs0m|B?+;mSIW9d^!b}(E0mJW-%oEC0ZHnCIbxP>CAMu5z` zt8^~mAnLyXy~HJ>vE+Rl2smYe2_y(9z5?e>xFI0#V@{zcd79cgcl)=6{CEVrje4xl zr#?Bn&WF=_4~CK4>RI6rj(nj%Vd91R<0>nWY)E$kyYl>B(bw-P0cPV zODFefUVmcw5j$&U9 z17ZbteNq}yevfXVwqvEC86D@sULspk9nPw2j=^t30S@NA&L!tyULU*&i-*+zkU{)G zejrigdn1jC4USAtbmo9o#r^lJT4$pE;~u4vrD{Bf@(F0;_u9Go!r%%r68akAWZ?%)w1)Kh zkb`AWCkO`ZvRUQ}x@92X>`B1+8G<3>h_6NVN~cauCTBb-8BLhOJ^J2ljVv_{gx_uy zTDVPt0>1UF^lKVl?fu-%QmT2CbV+v1oKA|aS4&*aag6_a zcp-l!O7Zz(N86~i40VYHF%-%$JUMi%duB&5h23jaG;JrRdTm-OJPoa8{7EHkN^%T| zQI(_fwBOS}dt*ojdKR>vojGIV1>=NW)^poP zl0JN_Y;D~0aF=0oSnGG7Nr?IgZy#vq-)8ynRYFT&b0v4Tlm?wdSU^jj6`!!(la+7% z8y8()j`u#9(M4;4FfOmlfTf-A@ZFSON@%-!NmdB3{P`@Q4S9!Y>!QKsYt1pe#0rtW ztH4jo%Ss&VwHsymxOkgy5P9pl*QZF2!pfQ<`v5p29H!*ehuSi~t|ZCHQdICjCKOuJ zHg5FI*^N5Zw1!7zyuzjAL8_I(ZxW9{{O17vaG4I$K~UcKVE7pb&4c!u@UuM_K29V{ zD5iHa$xZ835Dl?%aElXD&iH>#vCYCH`e19hew!8zET%kW5gDZm0-gWz5lcUV3xzy5 zUrlvQOKM>N?r-zrJZfys!EqbuVdr;~-=9B4cs^c`VeH+!H-`Y|PGsX;ZHL&Zy5nBy zYM=R9vpVD*=8-4144UO{Y=1_Mi?+ej%14%Y>jmozljC}DCR*EK-VvgGqbEbwE4XDs z%;zxshvI~QR;~{0x*;brIXjzB4au}DsA%8{p$lJqj$>TxDR^Ru3oGD&23NLNl(0r* zf|wtHi4oEc$MPf0P1$vgQ6>kxn@ST2+LT3>jLU`cS5ZE!70owgF2h0s6do{lwU#HE zHN-=-7s^>qF2FxX`xUz(RW@VSIaN1o-V7TElo9-r4(7LlukZWy48{HnU?QCg`=Hvo z|A5O4{80D5)BleJJDB{0s^;1p@HcA_)a;v!cs5nL-Fh0ZGMtLJd^%M;;pWC$!=+T3 zeTYEt7w};;VBm?5EcSVJ=9R-DUFR*BGgWiuko_|muMF`>zAkGc!(Lh`T<3fZ0!v&H zBap^N*&lkv^WOTIfW+nodLwPmZlVj_rATQS5E)$EIpcW@9i4W>jp1>sVa2z{1Lb{u z60o8RU32Cv?;bU8x+P(+X-e)Xj`S7lI@)Oa0-$DM3#>jo=rg&i_G^E=L*+s!xq3G} zSa#&P929>&t`-PKp>qI#THeF(!==)R2(BUazcYf`Qp3M~V_@-tDx8p&8Az||q#FEi zB9l6tbe0|GGX3Oe*MSl@-hXnIv$tAgElR+7W>N!KnJG01B@6Qw6H^f%S)y%R&olfR zMVz--yu?Iv$u)Pt8(y&%`=!ValdmhEW^sKsel!&WVsQm7xPZn=-9u8gJ8|0~t%!#P zE@*vnGfsSMKAM@<06#v@s}M6~TaF85zm;_X4S!ORj@m2^$EGyeS(I-iix^K>kH*%;{hYr?G&L>%^(XA05Mz|L={ z1NoNzpM@LbY|D`lfbAsSl zVcOz`#6K;?F^8WZh&Dy@yY=hvyT$N8cRJ*A)~z)S&O2t>IWV!X|CUddd^sq-tjW}o zNiuFKkl~t&Zi24^uGJ{_sT$r*lnpd(5)1TlEqIlQEbHfIhz5HV?V59qsS68bTc1jJ zl)I9{q+~#t*IMc&oV_*crtFXH(*(Y|aNvcZ+uTA7!G)@{8>Zaan!Y( zs2Erm_DGlvh3o6hFT$(|ZyV$5JOhk;rfVNC3jMB3Q{Qu>D4Zpzu=KBo~Z&8D|uCiIlK=vHiZ)q*4!`T0#Y7K^IHu7nzYC! zVbi9jL;K&iF@7^E_}XLb4=)sm_@6hO?l!Lju~{##!Pj5i)yZESepW`1j6bF?Q{N=u{W_6$-Q?Tqtd>`1WyKnbe=PZ%K;H{39(-$ zuJLIy$UYdW1?MMl1^zCBMn*~NaB`#co9$M{|lQ!`wY zs?qzr^MBv}!1;^$M+ zyzBX~&i^&|Y|z{Ha_5!c_gS4o&uVQX^>W|+Ev@C&f9K;MCP`Z7)6dsc|j11E8(&mN-VASf#7;OIJ z1%bgX%!O(}So)H?+#O1_C>9$;9nwMt2ZDl3)ed?Mix*c-U|Mix)|>IW5s_k+Xj*=Z zZG`9O(4;lhJ#`f&{UpJum+kVFOoA5R4XTX3tW0gDbUA$(I9(2jEI+)sK^s)qG8@{X z@biU{Dr5Eb+SPBv^2@&eJt%44arg;tf|aG2&RPtoQin#yFRe3w_@NB5`B(* zG22DY7z+KuoIVY&;5RyN8Yak#yK3UQ{?+5*R=2o}wnXj5-C;E8S&HK5*N&Jy7ynxd zCaiTWwTQIsg6Y1KuUV3!0OfHo**4-raN`V)5eseJ4lj;wZAK1RWKzirRC`KF#%!9} z(eH=q?hP7u=(M!QRm6)wt`PuVUovqwic#ixa1IHu>>fwlk3G+6p04`ugaI-N1S)!b z5!6U}o|Tenl#~9~DCa&dz01%sF8wR;U1GH=b@s6R6w695>|DB~;<^fr;mHoY|e6*?A zMrG&|K5a@KVwOa6LIU3gsoDOP0T(%#e|kG!FJJK~K#982)|_#FxL9`_Hf~mw*`cNB zem`|Jw1g49NKrj8Et7OnYCN;FxYFF%d85u{tHa5T@RMK_9((=iA^pqb6dwin|C%Jc z&RM=UIip{dg8QF{7<#g(YT6iCncr7oWPiU_FngQ+cCRauoqAp3<;{ci+ufaoV-A0{ zwrI=(qum6frM}D|Y*!wt>8-I_&*rP5pKkj@O`nri=Ok;fD3M=9W1Jw&OhbK?hikyQpN2r z+9``3k}xjqc*~=r;V9l5@S^_#>ciQp`;;Q9+jnW`8sOX`cXC>DF@a= zq9l5_Kr8O#ck^i)2TLrvd^I14}cwU-=QH^b%_{!rI+A3^X!x!SbfTtxv2y zYf{O)?|VNh8jV-sdg=(@-O(1eP~eo?4bH)}ZeF8s#@`&PY`xNzjdKw2;{f|p#_iqo zBR?=)3}ju!jE5wA+Ap7Uq%+e|vF{&dwM!Q5-#%aIlq?UB)c|G})YAk*ZQsa3$S%D4 zYZi)tr+tt-fE$Nkq+ z0ZMJoxtc^<-z$Pxy2hzCTAF~v#B8_}>tp3AuG0ZDkJ zE(}#yNXsw~@Eq}+RtE4C{!DbGRSU9Yzx4I%>W?+XCnkv}J|j$uY)|nuzk0`KkOk&3 z^2tk4K&CL47AGV{(t$pj+JuTg1ZufU9qQeyL%M=%xj{*aarFUFiw=7DGt zc;h&MwlV2=Zng`_u7y%`_h_t7m$qKd;wVB=$=ows54&z#4}kA+1yS}rEL`haQc-L> zE*z>cnfBi(5XUDhgaNQ7b*3W0St)=6OoO4VGoA`7^08R2;k|%{ zk`LH)AnQoUX3wOvflxeq2NGMxY9jCaaV%(+$~Eu%FZaR1a|hf+Cf+#1Bkk$A;BZO5 zW&g!*X-BSlq^R+Y)gQ|$J~ShUkxEJapd~rAKy$5`KeoO*OoxYXIRXg``<*2D(Tlxr zE$Ry?A|GbLY~nNp%Ug4!*(l$%mNccw?gA24GNubK%B(hLZ~Uz=gl$GWCtKmF2k&3m zK@W(v#DJq&@Crp}x7U*j5jF0gr<$T_hOR{f3rOXywDXC8(i*`AE-%7H;vSQZ)<4XW zHvbXo146mYtx^2{N7FY(SKd5f-f&_~Y)mG$ZQHhO+fF97Ik9f+nRsH`ww>&K|GWDs z=iHprzwYX)F4tpY(^5WTKL;lfl;bg!9m_s>dB_pKz(I58GOuwtS^2$Bx#Qqes!(CI z2Wf5tePltwlM|BD-|A7J*QrsG${4UQQ5f*~LZ&CzAABR{|4_Hd&5=eq2Al}|c#L@D zb#pY-q7r_8Gtm9aHc5{TC4oNwER6qpocmTNsEV5>{}I#cO=IID76!rp5%8Q!#`Bs5 z4{Qv$dvf@C|K~cR=V;pdzMOVMNJP!+d46#d@btk_3b#DjbNKbC5fAA*u5u$f^m%H~ z%f+5fSDV(b^;POaw7#L|dClPO{P^Y&pK6X5aA^4D;AeR?>qpYJrg8Xn)XSxBy&ONZ zE%3EK)?HJ05Cn5m74UHAqp?>S{_%i({@S-AC$lL0_o~PJfWe^6Q7#BVRCJX^vU=R| zS3=B8`a~`f#OkChNPQ|jfJ{?dsr>sAk^e{FG@|(WYm2_ z-=mOwPpN_~_duVlp{;LaRVbln5JSTLQe&^esD7rvJfKVc^&ZtgYQY9wUs~Spt`14A z3Kwq>+v9Csrm1dnz4HbQsGs(5H2wMU9(6OD?q0 zUtTvU5MbO@@x#K=(Z>mV__tTpeGh=xu3fp7)7s}2*u-Knj8?kvd78`?I|U1ZMb=Ji-Qry{n%3=PV1(lDPN%T`X;!x*0CBpM4> z53fnecLhNpHzoz*X>6sF|Z z5x*(|P?VCf?4f_u2s&xYTaij`R!GZn*-pHg|>{oop;djl2FCwn(oyLk~2v zF{;u$l$5&EI324V&|_$x)S-?U(4_F%wvAno(rC~bzgTN3s~E?s7%PPhTYokE-&oEqHX^d??KTTnlYd2QJrko zjdP||W%&7YW#ygU|F6^e@%)_1m7H4BI*|EW=IW0zy8wu_dcWJz~4+rqEH3HtfU?4Sfe(=Ursnr~TJ3IJWD-f&p zQbp^iCZ;J*Q7lX@j%a0N2K^Qzw!`yP+LqK@ND8>-gWLMVjzNf@Kh?v7Mw4sbD(WlY z?s~lHe2TG_>Yx`}=Av%}{MG_=&_TC{wm{c+&^4bu<6ydh&GthGf9E{@sgj zY78j=tDBZh=wNagK$;bK?A#>KNp+0Nc3S0WGF6q$5PJ{{+p5>*U#Vvzg|->_fPaI~ zdv%@O+@fa+2fCK1%FWX6Apc797C*J>uUoZtA?Dfkh|7vW9KIAZa<`xltb{__h~(ke^{Q>VRoQF| zamv~OJW=FS!Ozorymp{^Jp#2x6Dob;5kWo=^fHD zTa_la5Wf<|#f7D>m2~WG&Ax@y%G*hR^k(7yJ9(^uxt+*=Zn1SZHRiUmKC6|2@^>^y zB?fp0XfBCtsn_pbbIz;^(;Pe6x{d|1UN_>!c4P{tE3p42l5VPEVp(aSs1)!#x`hmC zr>;(8oe4qeOJt7lBx-a|{0_4W#}pB?bci*^Hi{#&k3MBY)-U2nZ{nj97Ab=P=BDPI z=0y#BDTdVu9j>(kb#Ybqa|G&iSpzr9(3E+W7DA5)rL{Hudhd_mt{o7EX9~#=(PO)# z319qg$Nw#hZ!~yUJns*>X7-UjccA4g(Nw0Egv;nz4U%I?>b)_Y`S4KA8;33>b2Ftxy`?de6{vc@zYgAT>dUubM-%0-Kf)$-k6LkCb zDJXjm#}EmO6QCk8NF@l>-++jjcMPdFGti<7z-Hq+EtV{#61xp|Deo#CXHvT=T|xM* zm(sYJYAa^X3j~4@w1I!re22Jv{2n^f*3**VWH3e1N|`up0s3d_5@jIU1iC^RY*OVi z)=fXC=n?eaA;Im@9QM@v@;md%g~TGRg(GhWv7ziO0GK?d)WMI@ZMN|8=dN3`Q2w*5 zkI(hTf(sMY6wuyE9Yhu6b7xmQAeKf!x>c2Df%BIUtH2c9F!WS>j^1e#rF4Xbf|QW7wEtJQKTB@az9tG_O^H0{kD397Z@D@%ZJAZXx2C772A$v~C5ga?lybMu z%b&dqWi;aBn!&`CW+=(RviY;#NQs}~D55~nUUkPZoz3F4J_7gRF=5rKD)oJA%7?Y6Q3DqkA%u*rOp9evP@bZlqTSHBFg`ysyCd_DHx@aV`x zc*tr_m6mY6+r&kXZN7rz*p#BtzU@t2uiNdqXXZI&u3Ue>V`d2GPbryjWu4#N-}VO4 z8Jkd&_thqz1VU`XaTUwwO&g7#m)nCPZk&Na0)nsS?@yTAV)6e!vwCh%Lcq3e zD}PvVfeg&=dv_~I)+<-O_jzv~?ihscXIKpH$A2xYCW|N@pFUTceBiFmHWHVYJ}*sA zpqj>9-dU@At@{h_b1ef#)cb<`ET}|0fz0i6e)NJtrjssY`l7@eY4a^`IEf ze&Sxn0**9Qi0zo2^&V;JQ_`9sa00H5b^qV8|n>Il7c zdAa*EZz2~CJ0Q+N(b92N%w#CB{=tmYKwpIdU7cxGr^20nxw zEQ9Y5+lrUq*w@V8GKai=L*O2Xadf=;F%4;8>b0rFH7SGDHdC79+%b!>O=`AV%QzVA z;6@{iFGtI$MN03qH~errbn|!7x})YR1%f8Op@?Gqk=_bU<<;Zmf4j&$_p)XO$tP0b zv+Nj_+{Z-q$;Yga)M~f-wJHzn-4Z*X@x@*z;?9=1a34{&Yfr&qPf&Hy&JqUY2wo zo=i&Gxi!)zM>A=&I`vtXKR>8-T~%{#S){Ww>(A#v2nGF=+@zTdH|F`V-TKOwvZt*r z{oCWUuGM+}VdNEP`Hq(N(th`Yv!k*(uDa&cKRm1OPto9gnKR4Rt{u&*a=D3qI;(IA z|H_=}@?+Api7ii&&rjjLsmC_gWHxeOBwER$7sTh;J4fkh;5<*dv z#(;IqXe>uR5CG{V?XswmT`m(+OvyE9avfs(%-G2GF{XBh4ezpK<=~>a`LmC|Cu8wZ|&mmLum;`Nxcp|i+=yL;}XFd zmyC=rlu~pHYbNHk2gn2^^{h*@Avtp<5Pi@AAWb(WF3F#IKUMOf6+zmAdKONuEDdYN z=UgaFUcx5wlia>*Of2K+U-k5?FcF+NkDL(M>X0F>?~);nRyOfi9c*cp@?KX%ehU^z z+r_C$_$r2RW_jURet~L_FnGvB)5fyXi*{~28FM)#{m2$kL7<_YXDX>|P^1z+$ro3R z6!Cx%!*g*K)ddkL=|&+1Pv3;9duUau{h=%wNkQ85Hxe?Zj(OuGJ`fW`23XQ;+2>-b zfU%z^ahd^;-d>4tS^aBQznPt2Eq8?15ztn0j+#o+u6=n#PS{0eu_aLcZitNjWefx_ zpd9SgEw@xxHc9;>A&^{oZm?)T<+Oo+Ay4@ zi5fnJ=j5QK>1N%_b<{tdKe$m)~x{O`yS-*m@vOW(^u%j3em=q_?# zJW)7@7hxa#&HZuluL|JouCpP#3j2#4C%J9#Glcf^)mb_F>0kGbZ1GPJKCY7VY@>m< z1@k^f9+zko_$5q5D7KHB%_?|onLcPq95v!T66*07B^40MDZ!M+4MH?o<(r+|XJ)s} zc~W0h{AWt4mjjO!6D@_;hf)xT=^k|D$EA9BQcfOya>50IHAhwdy=rXnu593vf?ZCM z98DHjFtNz~XH|AofP={w{_}RSrVb{ij)<*E7!r0GcZH&`LBRunYm&P8fa6E6^FjrZ z*%N`;&fXkhtdZV!27AS^m!OI6XkLu46yH?K^}Ae_(<%68l}2 zqwDz?yGGu`F2puo;87BMU33~&%E%?90TJ6xHRzk}Q0^W2U^NNM?&Y2oe0}>{^hYGN;mfy2K!;DV2ebYD7U}LuY9jPu{3^=J>5v&Ok$Dx>8CYc-wMhr> zXzvc5IE#aN7OUtxaC7uy3i5VM#4GpR-yTn4q{-x_>{o)U6v~p2xrHlvrZ_kV_H#kB z6y6cWda(mM1w55FvUALKC`T6Zdz#dWd{k!(twu|gEfMnit~R+}D!Uk}2Y8t6=G}_4 z44jF>54@d}U84^TgqAcq01YitVu}w=LDHHz*LCt4&q;51pHe85De06BT2iC5O|| zo|gpW%zs4Ma_H%`0gIp;v*Hvk*!hb1@CajRI9e{7LrZz08+_a;^nMjH!Qhjj-Yo zaCC|*?UM=Ga?)FBTm;rUZ#tF@-;WTB^7R3-{v1YJ;n_OC{bIZ198t9vK3kSc15sYP z;uxb01T%a4zr)st@pBV_qQ2<&aTS`*%n*T&EHgk|4*1#HWuC13IHI=`o7#)CPn9dd zr#%H;Mb96E3O&!s8hV|cM73o%#Z$cND=X@r*H{chMBU8#+823#JXd^eRo5e_<0f?< zyP{U3X)T8o`CiMcaiwH0T_3v&`f}uBFj>d~?xu5xJp*JE@x30O-7=-*t+qtI*E=C$ z5@i$?2EIEO>bpazFi?5EhdV<4j0HQt8NE?xc3c?y@k^zNNSI!lIDKSD;VJLXdWBAA>eKFn0;y4?E z)4f1%%Od|^!Nc_7HQxrFVRv5IwMHI~EiQVmv50!i3MKU*r`#IoO+lj84s0?Lr%wOh zH4vvR4IA%dm?=(k6eT&S=@;&v|HQ=8_eZK1F8)orn@Y1+*XupzGVUpPF|n3F(B13U zo;QA|kn72iP4&rXkukLM_JjrJ7|H4Db^kZJT&Bv-)_y&}@JJaL$tjTUc^B6zW8(GX zaO!m)^#%{cId!wi<7Z^;T;}OEwOrfH1I_V(sm{aB{V;UqkPZjdw*rAoK-pDiO8v2T zc9mkjY-w-rd%PnO298gv&ne*h`R`yB{`lmok`)XIfL_lvb!6s6Si{%PYf;j##vPf# z%SGt3bFK|1frLP6Y-AxW@gqKgVhg8L{BY1#x&$9EO~zl$m&CcT`gq+^=Hnw|5b;#a zmz?`{dBPTN7b|kd2Z3kOS75N!*`CThJA}Y!Ns)OQ2nh-vR)$7nZZluz6d{28=JiG4 zV3M&}MSFYzCv6Rmv6c%hW~>=vRG)PDT^DCYjIik6*{~mtgWC*H5rRe9DpG(j7&sXj zXTZ=4E`y*6!lE{viLcz#AU5NW2B#hgF}3cRU!gKw!M~0-cLexA>C`Fzp(WGh!M6^+ zKlIhS9L$Reig|#kP#^=l;(k%;`>!&UOsI#Nd4{rBJub%~X_?!kMXhJ9qiR_SBe6kj z0OL>$IBY|hI0M&rM|IG*5HxS->YdZaY8!5&Irpkv$Nq5A#4U~&`T`aEnaY!9Inxo3 zN_r@AU{w^`@J2a^{0vp59oB)5qSuE_HaUUcytCBb=P{ZXbR$3BjqN$Z<24D}l;b72 zUTE(02X=8C+FG9;UG}+EsD3WxKqQMe>a2xl{u=1D({*rDrqnBY8wG_FRiRQrML327 zG0N~DmB3?)w&L&+VesRSxs>J(IpC(>&c_>7i*=7E28!A11*X3v? z0!}QgJ@IC4%db%N6P8^J$?x`~e<-|^OnL14)~iv`x_^V70v~I=$I)z-7D;^9%jAK^ z_<6|LGI#wKnF5Bs;7{&=+p7TG4paq>xR89$miBU&lSpCTr-)&5`q&g&al`9R9}nJU zzV>Z@ZqOk3 z+-{6B^r~q$+PQ26CGa&3)Zg8JKG2Xnj)kW_hva*^lGbx6{3q|_W~wN?_R{|_F^@op z!?v4zCV33%0wkRo`BldU-$?ebNX zJ3U^%AziHlYwlzD{0PJr>{k}Q+8KPinoTLuS>;^BG!UNo-xDdE$@w4w?1+z;dVT#O zKHlDAkmoiI4)ol!8*88KQ|ax1Io9aMeCDBe{)g>;vZoYG!sU6DQA9hc4-i7buZx5d zsgO>j36d`&zsJb3e!bY;t4WI`P3GmTe`(~}?OYKsg-OxHE-|f+Gf7{`1cd^#lG~H=tZ@W9turx-?V(arIkw?v&G-b;0u}jzKN~Yh} zdo~&Y2gM1<;PrXA9d}66j0oe_iGggJpSN9kJ6&ym*i7Kp^AzR0yw1M*F;pg9MZnuw zCm9%{-=n=)zs~9|@Zwl#RO5O3-moOiu`9-{{nYd2WJ#|(ytB-6^mCI-!@73zBK!5} zik{Wh*R`?Q=;FKwg2<=rIx-nZ-|NhqPCLn3iSrsyK}d_f4KTc2zdlIcGK}@OSRUYz z4oarvyQX0OwqZ zj5bU9AnP-qhZ{ukn3w(TYC(Sxoo8%%NH%SAjhoZ8$Ve9t!N*MJ%e z%WPnM2}+`NcMIMby5Ll<;614B?m99@5mV!>w=sy@n(z3KSJe?q*S7MEPFNL+I^UG+ zP)onhs}wvRMK{GVVLlZyx%Nl^x2Sp$3AoygKxNny%@@e;ZG3MZw?qbZIp}ywe1kJX z95x=Fr288pQn>4Y(8}8}v4qi{d*+HZ{${F^zp~)#2+#D!5)2SY(!WcUQd;o|p|p~#`(6q3CdwTIJS7NdvEHZ`oaE{Z?+FceV#>tyPwzc> z*b`80aLh4Jt27^4Zq0wqU->GfN(!4ZZaZi;)P&ot?kIE#1l;!*S#t?4)!K#GtKcsm zlD&yc#3?E>uPePX7s{Yt|x*4u$2_1Q=p-y=h1=uoU3sJOAM9lZ9J1oqkoUjjIf=(_d9 z=H4y~C$QqX%ThX4noC;SS4YOPN08&?!Mvvhov9mtbU zMct0>>8&69N<9&4Ir>>sI>XC6zu^02u55VfDk&8Q#xGj~DveT8YzNm}j8nP;6%J*~ z>c%q<8g#s9P*KLU~NYfBv&(KbJi{cO4d z%P!+PXRGd*o7_i{H&}mxOQ>R_t)%QFFV{2VTt3V!Esl{2N(6>A|!k(1Gm~rw8p9C6JD86?KF2;s5H7liyx_T`W+NF zCNsyWNYNJWbGeQoH}`nHq5jf&fkJ%xB5#JPYliqrFe&-77X6mxcWF)TXWkI-{s$<< z`FQ?!woaRHYr)mDzT*37O@Fu^-^yGvd;pLFEv#GUuv82j-wwjsf*%>9v3D^|Dnj`;f4lwWbJ+oAVnUpTkpCq| zRrx`d%nCJxAR5T+I*U3uF23tusaBu{xiOXK9urL4ic%*BBgSsRlVe}UZ^Q5YE>oL! zYEJ!;j7Kkm~W|C*lV_p3$>-3YHQ=Bl@ z=f_0C>$;^J9^ZJ<8Li*Z&{5;#SK`KBRP;zD|059Pi1&Wx-OI=^%N?zI_H<>DjDCXZNx-`Z-=t7 zf3H`I{}(gBtxyD$rxyC}G-qtclkL7gF!QD|(2LV6;^;3%@vo;5WtdYabEPluV;kt! zptZHL2NH>awtOa3A2}G%h9ZUDPa!gzlS@5b%=nCX+^qF8$PB@fT*q|cfkFzhU)#+M zua}c~353(Qe)@~*cQLS155E|umt4C!>_!C!^*e04Il8w6%fB^WCTy;GItn_#hPE_= zKwVF>DZm6xbB^J$(m)oFk1>}@DArW`*8!+^of^;mft8SO`8gp6^Qu{8P)05v1kBwS{Ey@+{mcWy%-4Y~~Ns;jt~v&f`!nv5R*U8~9s zCVTE(7nWkS!E<#9grm#=0|iqR!s1oX!bJaz8SJ(gF1cqxh`apcEToBEy_iS>KRjgRAYdmEo0t<^{Cgid@avkO;4NsiR51XTMT#Y-gR9f zYW?iItA#V}-eneL!&8&z=jR)QOY3=QKJ#Is(M7_A0_Y{5N{p_k%E!oIyQHGxjKOFL zyqg=FH*Mn&9QMQavI$mKc$Nh_a$XL3avw@{2ynIDxN-Caci#zE1W9NNdVCq~%>1{e zDo#j~&YTNy@ILoK>}eYW1*HzFwB|h18X5B1YlCSM^)l3lXP56)FA@EL7CaS9cSFk}Jk^M29;dd2+7A zK?_9@UD`~s8^*BZ`G|060#ESZmsfE~++ZaaVfb$5kvOxW!-XfzXiGKQRl$P*rijJ` z!L}n1C3OA(1^^?CxLZizY6bGE4Wtdzgv}{31$_7M6}C*ZVlZa3!2o1)XQk1rA+GEn znCl_o#A9@B-0ypU>1~IxKWXfXCo))7uAR`IE|P%-d~qLAonfp>{n=K;A3h&><);4!M6U9 zLf5NAmhA?*vdPk~s=w#{)Q*-a27EkpwX;#uSpaP>3bNk_;5G<{=%;Nuhp%+?euhgg zd85OXK3FM%$+MYuAN*NVq)bUGtzcF`rOE={=irJ(LPHgvv#}PTF;1&H{Xk%oc#Cet zqgv|+6BbD$6}i}UhZBv&oDvd4z3XK&$Gdgd@3N|bajbg8Kqd`WAS3(#wE%9YAt^;{ zGv#hUh&s+&F0>63r8$b5zd*Xy;Sq{9}#zmn;f_Y!~@zSIbAVlH+WJs@9wX^%2CvybH$O%6)8IdF)_BKhsah~P=xye-Aeg} z5YWX=;m8o|C&P)JduzwkN!YqpK#$bGIo%%3^!AmsFp1dRSH~ z@9V*na8}B{QhXZ5uC0~>4HN(=jYgzEorz}goilx;b%l;#a;UOR{ESOv zZXr5>`ez@vs2~7Rx6=F)0TZ|R7MIHE&)uzjgUmP%cT91HkIxy1VT~}t-s{daEP))&szt;&x1Lv! zpHNAhlAFOXEv&398wEYlF6hPVj-UOdr4dt;9~6Yb%*N(%SzL!igi{EU-SNNuicK3Fu|EwW}YkwAZ~_qpFG zsC37|!yQpmc1FKIk+Mc12F@`Q8?bgBY8~kGu-@(Q`AQ?FC$XWT_K|vpUzZ-WJ9GXP z;31WYr|vUy(A0hHV*;ugB|W|^|Be;F(PLf}!u|L%G>I@D!0N;BKDniXX7Eykk~bz(ap-XS&6bYK+7XHy&i$Xm-ri=S3J0T=#cH*CVpLxNC^I&}4}7r18>dFg3EJOE(c}te1HEa1~!&S;I;+PI89`6SnfxzCVby9!V%#O>GY?I}Pny z4X}A+PaTx}(~DRQi$VghIn%AXekBK6R6>+AgK885Miv0wvcg6?fJs2|8E>}a2WfB= zlMwP-ZaYo4)BWAyJp(^i33aO?(y^A>P8e+|_&ZoBb(~ea6oTSklkx^#Ev`p|=f1PV z;HFhes^Lnj1bgY5QYk-Sss@17e8pFORisLWy-r1Mr45t0OR3Q@QQ9ordUF0TPug5{ zC-zn)(NPSS9I{a_Hp039Nvf+;L!ewfr?LP_+AISkPH9+!i z5{rjqx{4+LLtRxVX1O8@>)x4u#M_z}WW3ukKw*4p2hL}5xJ{!bGAU1a zLfk~~@N7hm7DvjqxjP`!?dS`E3{n?dgT zb>uhP0t6Y+csAM^j}^A`Zg~azBJqidqfx(XD>?1Ng&Gi^@rSv`1#H9yD_|TY4RL4` z5E@@1OSqKtW4VGmDpV*_^aMeV!fJ7j$Twtmys zPQGblC5oICD$y{X-8GPB8UW>+oo64zdXtm z8x&tu?wov{w$iY<= z6-Y1Y26#|y7n@kSxCWa`Cqdy2=V-ZKY0Jam``8vBy01g2$zVO$&ft^bqb>cAT6SbG z+RG)oXs?1dBazdZta*Rewx=HR5v{4fycC0fB3-~b6K-AdE9&&MhauDHO@P6chQ|KY z4MZ2Oc*lgEy3t=W>-oWMsy3C)P`+U5eo~%}y4hC6nAG97Y4K)7 zyW1^rGe=v24rj(OU8JM_k=XNB@T_gKwsTU81N<9!v-_Hfwd@(X{@%P6`!^9CBEO9! z+Gwbg%$1b%R2Ni_Qd3s%nUY_eRgWcuU$64vWGAF9M*PP45^YtO+mxLRdkdbF2ielQ^`ie!bVfl94bun%usa{otJ?r)}4b=q4-(D=T zTtlC04um?7aoa}BL4+rA0h2<3Uya9^x$z#usrUDU6f3^S*J<G3cHh9xhV zR#Si&ZvijyDRmdD zFv7{h2|vTVtDv9-xUaOdRw|6NI*YgK84@njHzp4ihH(_)yWVzQv-SIvT)0di_7&pWyl%W-rzMRHB;;Z#sVXX1eaxzp=rhdQbsIum%E%|a)Ql36< zSfv?>Bi&!Tc*No4cIP$4$g+>nxbiQj4Ob>nsfSrD4v{_fKf=WReKEjA^IiNq*gh-U z#ftY0us8%uKzOE@z2_L=7x2ric5gA7F6XM9Uwl6){Sf{;DtQhxK&E_r_Q-?i%$eZI z{6hF+zFeHB4?Nx_I`;3Cqd^(`ZMuiLXpq+@qZ{>B{~&hU|NJlr1w~9%gy-dzogNW_ zo`Mj4Ptg13@O|1@TT?bVD_*CNO@t`#$)KBrzB*FIsQMI8z-2r5YfFH?!SVKZcDY~t zN<(2QLdBr=r2n`<hg|ouC!9q(199x9kV>?85?j1$KWG;WpVpaOoLC96W&wteb z=)tQjE;sPLHER##{L$B|DLO*F=CffTmZ@0^G_LKK_u{J*1q1g*-X+^P7ulip4m^1# z0dK)eD-OuCiG5m?yID0BWxURudq01#+VXDi2$srVxH6-j%@et-)wC8{?lnogz!@s2$Q!Q zsOhq|P@^d;6JH}XXdB|D^=I)d+-?%cfWil)gaC6-FnBhkY0EJ{(26+WK&|J%KWNSU zL&f1vk$Eg1#_1CA=7?Hg0QKf;{;JaPxUdBUddPkUBBo7Cb?bic;kOck%drE`&#(8j zagqd4sJ2``O6%tD|9e%6z;ldj1Mua7CF}oeEbf%hSD=s8{?yw)+*Q{yBlMA}ug3R1 z`t$fCfMJn-s>aBk+Enh`@|(+wYhdhl{bUc(pqZ|gU58~+OESE6qgG>UZ2z%_nXid1 zg2PwGD6l>WMIG(&#)>{Ih2T-u!-oZ4)5+bb7;Z&1Uhbge1ZvzlMGbgTp77m%LF8}N z7-OR@eYKWdWnb&+go#cRqW^Og^uJwgBUg_L*2=FH$P3twICPO7<9hiQx(E{$w((tQ zw=Po!{CGYe=|?Ywcq4f728aBF{zdrK37(h7AL%re?!S2{pEc1=%pI!u4es}sF~zM9 zbYf6sHYAtcH%CSr!h?MS#FIt0W_=FAnQrFO+S%AUx1-s4nuw(_h4WGh(q+hRs23Hu zADxB63BZHw7!C1kt3Ez~_9TCqK_i_{?^3?cWt>=LwGlM*X@2uv1iPwauaUb5`*oDr z^UbHTz9}RKU@Eg(8fB@_C+|>2u)%B13JY==R-l?K5ja0)y8co|1Ql!K3a@}L0#bA1 zrM*g_^a)XB{aw01{2wzXb5?06Iir{E@bvNO_w+tnX_bz$rtI1+=Ck3!*1dN+5sp^5 zM&)BYSt88+hkQ=1TB_e=$9&1cfDI>cq62Nsn4$Rl+r;SR*5_n5zSS{`%uk(%t2x^u zY8X1#GnCyNE$o+tPJ*j|%E&Z6M$DFf>Y|1oFpm8B7GH(^kMVu%`ZkP_IO3J|vP>to zZWqZL0;;wSES{Jsv(?5ett>b9P8$}uBYVPW3O}m0%xI_x0D_O+2x(xU>~9(bB{4uI z<#U8;y5F9;zh^!kl7~cAol$R1%^GF_8w}Flp_C|mEZj5{n>`pNKQB)Q{dTs@mI*qJV{h`QN=5s{7qgNR zxrvI#)z6o(8;721&K6IS1W?He*~Gk6cE;uFyKvFj#p^|!CMBsDJv{zjD-B!as&($r z=8fEQpDU9u3ylnAsx`v(3llIllY*kN&TUbYQ=;=n?_Bl>Hy>#8LiG1@n(7KbL6hzI zJ=37oH-IRNvQ2c!s-p|3>aq3GDz7YUv5#%mA9aDYHc|3J^zfu7FlOr88PA6pXG6cR zhO~yDTi`T|3x|bEy>@H8#A4iN^?UEvev;n4KK4aHT^v=4dHzrC4X*!75;#ZNUG##v zWBm|fRe4c(S0SkvZQ8Oy$syqa@=GHAT(_-URyJmYQL?x163E!}Jk(>m>&k0`HoBZp z0Ld8$thz0)H@9xmKB;k~pV53XPweY_km2hmN9ccA^2hJFgT_wg39?;DM^ z^WOA0;Z9DSxZ@5`^1QHRTgM-}v+!k*17 zyvPb0^8ykjJ3Xim|9Gv%UXm~0I9X>mprcRr@V_OGHw6QDG;$~svmi+Trqta0@ACY{4}~Qf9ed3P!mqANukP2MS{2K= zb$KP^1eK>S$cgH*2W2TLQ6krmE1pay{uk(6R=2}0TQa{20t30V1!vI1OwAmW$Yajh zy0Xz7Gt{RwCf<|EQY{%*NOHbU*$tca6VxPPK#4u_)~c^MfBm$h>L>G>@kCj2$ZS7% zFjokGYn9_ktianP%(F`V0E zA(@-icXZ3ude+zbLQ{suyNN3&-U^3E9%f>@D7z{NX|%i0omA>@F>GkX5dx(`XK5}+#F5BDijHWA+`)S0_qGZ7R;-6en*Ix!^hUG3xUO|JFy(RR7|GZ zndQo%%TlY)SqIH{C)uc%n^;&ShK25~H;AdAUjMhEz}2MQg~iHA-K|7<|68(9@$pa^ z$d)~5Bs9}>e|ydZL%Q`=&VU*cnIz~Faw}K@_bw|-O-1)(qoeGoO5S>>;m`Ot2>=;@ z7(D+HVI$xs_Ul$|_<{=1*Ld2=m3P8iQ+vK+xgsv>)~?zKr0pY7s|&A!7VgrpPZg_u z?^(4s(7Fgd)rHjvpa=E-FZA1+cG?}D>}_1-x@>l7e1=*Lkj(J+s$zZv6MW=E@2sId zrE=F9u%CmBIa+M9GbRN5lIO17tj5}|H}b~l*4gNL$t#0d~CKQZ#cA>}d zo^eN$ICQ4D?}6u0av*OCAgI(B?$c>P27y;o*)BZ8$90ykgCYY3po~l4Vd08xf`i(8j=!Ab0C~Kz@|>d|_Ik68@nfgM_hj(+v09HHQ<<5Jsgc14vXC zDUCu3d&)m}i7`fmxPI9GI!cOowD|UvO4e`J9MB|+rbZy=!O0qdcprCws#_JBuv9dH zgQS%DH@ply5L=$}6b+|J$e_E9`yfbDk+vnr|6itAg|Ap4uNQHAdG7z5DonjxmW<*h zb7X#8R9u`3=Dq?ZIZEWv%AL#%)n7U`J-gIC&VZ5dRor#3JO8P%Wuf5>s$j()nh|vC zFoGRTuil!qnpJP7I{X-;;eFeeqvM&7+$(?WPc4`BN*^EfG`h?z9o>d<6Fa^Rv0r+~ zhbtCwKNQKGT}ieLvUN!3P$TgLbTPL9eI011jb1DEbntNB{6Bg$De0q-%DsYesJ@YDG*(KZIl<@Vc8|aheF+txxf#L7v|*OH!HQw`tE&5nm9gl!mZlm+tuL7R~-}QH} ztVb!^g)g7CKGqG}XtrJsMOIa`tCbxqkOksl5y1v@vZ9MAl*V>q#8gCw3RZrEu%v&7 z9!Kw3Bft<*$w{>%GB72BwQ?f&v#h9hf`f6gZ(l6#SiQL%&c^TFcNbG2qDxSzoorQg z>6FZE97;ha$wJ?iTAJV)c(%2xJ+oxNwkG*xn?c~!`ehr-Pm94M7cE`LYlQ832a+Lo z2vGKe4=xBAm6Zo4p1xH0&G&+j(EEZ29ugH&)vuTr&=F*Ayt^ddHSO|_B9x$e>qbi} z6yiZ495aAKcB96ZJXi$wnu zwGA<)Z+n~N>#_daR_0VK%QoiTL8NLGt>6A5DG7V3Z5Z<{Z0nu*=^E$Fsi2#mMDEvw zV$9x%KA$uOBc>y9J0}iw!IH(m>;ExlHq)9a z*LPfafO8vq5;Jmed*1V!#9RU!jv4c_WYuHb$-Vi)EaY4Zu3$@sYE=~<4iKrrDg z!w2#!Cj@iZr9PfJg_B@m&O!i?0+Wj~%!?`YdmHfVHCnW=0__)#fyXaz4eUJ%AF|FX zO2J*)UOHL}9GOI`Eq=|CtCq{Q{S+KATd5)XX?%H5#Mkuh z%*&|85&^AzwmOeDAmGl=rTaaR=>*h58%vPbb(tN7vP`I0UvmC>7d5Tl8IE{*Rv%id zvHW^i02jXZ%KL0HU0(-1%xfNhalEqM*F3xA&S93@`!o3G(nU+ypPtAOy!YW-+n4bE zMbT;>T5~@CWc^qzqx!*mG0L8n>)1%D<*v{r@#|Mws0@cTopWkw$3rF|tq#WWQuf76 zq6;vs7I8xK^yakYdCi|6G27HhRPciu7XPm_Ev|luYx_xH@Lj=$jK|S+=ijQK*A7jR zu84@)0Y-wE&pSLB`{LuTwG><;GWu{#EI`rfRWH*P*23!0!0WB;rtI!d_;f;AxUU1h zkzs8rY~&Qe$V{BXdq(Opniv?85i$87t4dOQMd6u}PK zs^awP=v65##a2PToPCBH{(vdqMS8c7>Ia?!W`tNa?l-Ay*rC9Yf)N!;@yJ8tn9A#A z_x1pM(W$MzuJaT_kyrb8a_i-co8L{SCq2@fd92wmO2hT{k=Mgj3oyZ18|7#AwTFB- zt}JsX`8IDc6qE=MH*0ITzQY~At`t&Eo&3gj0FUKZ=H6+d8}!I8dK~XpWmSWyUVNIk z--ie=00=c5NtLX)*zNC|V_jAlM2V8m-L1hSJv86A_GJv4+*E#8**u7Qn&{tw4zXJH z4DFFZW)rh5vjCH+-@WyOj3(;Y(;@cjCue|$Ar`I4M3-*(!ioR2lhp!@4M91U-psU7 zYzU$AFBZ^KgH@|aQ7@P#?Lp5Ua_ia0H?`B&dpf+Q?Lr+6J}$>AJ27s(qWQg;vO}~a zcOpd?nKE!{DS>E@Z{&&W$mRtDP)iyG{LbiLQLGHqat&&=y>Z93{;OA#)8-8R5eCUn zhpH?{uvp^2Yl%L8<9>Xufvnh@f09Nc2t>? zN{j7p5MW9ZXrhDwtM_m{`jlT)6gtG--mjeh z5=|4EkmSa-xU67HQBlg}GApOdKTlx4RF)5L$G7D6*Z}))Drb(tI!7`!qX&pnH7sWZ zyMNO4RqEz(SpI)3fajUlf5k*`gskjUIOaGM?82*_ULc+Q$`F4^N*dskXtG$1W zBKh)4<8ty-06DtPo4M%#>4L8MM6?t$kHY`!kZTOucf3e|y%jpF$OJq~R`YK)RaBQ) zZ62N_A&&5*hB)dUkE!%ghL${Mx)CxYL{p(})|ABc3Kd#4`(h%kj*WgG6u@-3rj8Os zZyiwjHC)2~J!h2+l8(;?)UWf+mKY}>#wz}n3v{TOHt?oD?y?-V zU_0CGU!eN>^*umg?o^LoaOq>mlQRnv@OVdGSiw8fVUFXQtIwFK4q5YR--(HL0vCdy zw|urRB1+<~=G1|dlgdC2&SYx%%xC^=dpKE)A`zN>e_$()8q-Wip5wK z^7}RMHrMwC)HfGzi@yETYFTHtB0glQZEm-dremLaQkTjb{4mi{to^0TBBMXQ;N*dO!xwevZ)=7aC%Xz?3y6Y`zr_OeE!=TOvbc|(sB^ zLqkYf^Fj5Ok5s!Ru+K4Njv}U-kT4QB~hc_sKJoZ!GrLRdq0KHBT}Ayq$0^jjJUj zB)^w-5cLpe)ULrJRKM%sa8ph5f!=O&uI&_>L3$V^R_BPu+z}t51)Y>n)YQRSWqMnl z&HlYsoF9}E{w-|-f}A#@SzE%v$5k~%DA9#Cc|CP0)#!SD3koh~+=D6oVr^ZZK}9#+Zvy@0NW$JT}$2KMS0NgaHsa0qh;O+}!^ z%!-6Kbr5Y*&o0{@<37s;2r3b`ebJw9FnycQ!FruH*6L)Cs& z6kc88i(&d;{hU&7FuU%N^f5~2>!lD)=EhIu#^%udyV-8r^&qADYB&A-Q7|xy2cdD&wet<2wS2x%cYO`g!X{IFgu;I5X_nLFlGth5S$lg2Ls7nxZv}+<16vJtTJNJ(FNi%92&)XaA;o73$o=B zCJ+IF31H!xfv-&KmeZl2R;beQ)zRQL-I&buoso37C@3Kfjb%;6z=JdA+W)S2xN%h> z&!ojxPvh5Mot2egn5xqG>CAvne(yioQvQCnNDBx6S)wuBuvg@Iz6yODx9UFKuwaO3 zfg}p9YlKl?!0D#UeiiA`@`giI5J&~0zJjHF8cVw0X$2{Aq(Ftb^`0>tjRwD3FSTIe zXY&jLbr;6_E>+K+TUgM5uZQ`gHt;HCH8Q97*J8g@l^5Tb>z>^gl~M-3vpVh`8~rwe z*zd*I+|kLVvy~^+MTk?=(mM2^f20!N<_dUi_sJ9xJa(z#R_tW2zJTAJFPg9C-p75! z?fU+h76BIrw#0Jx&M%Bs&Ffar`X5)^!7RzjG)ZtC^hPJQ(qNAQ!N98;QRREN+Cv6q zbr^!CX|Ed!gb1P?V(tUYprg&}q#6K7JvOW=k$aOl&&u>+$xYr)I!jJ6Di-P`SATiv zX-FZOWM;`DivqUMBfkPogS)-WbH`eq-_-4&dpgN$2FLf1jt5MSisU%S#5{Dbbtu=xY+re{=1U-e6^ z%c%eegMiO@hUL}JWU;T>GM%>*pj^h%9s&_d>F60p5UCB+ShL^gy zCwO05f-DOYKMP6AuhknL*lPa#@eb;P=XbVB<7HN&Mg@v7fMw;z8S}u=H>5Eo@Vwrq zIOWqRS(EiK@Fbsc|7=?eIX3$z@bWJ}77j3QNbPqD1Y;cnq$2mt)g8#!C%a`%QIj^g zHC}}mRy)wmSo_1Kh*;6Z8IEQp`0g0N3?@k_1w}j0tz}m02l@Qo_t=2DXd<5?SgCgE zNfLo1B$&omUOvIMzpnQ+Jos2$@3+#An;zx=98-kQjSnbDy>$S2jyjy2A?cQLa&=Q z$08|GQF4chWuzrD&B&+Ccj!YYZhd-`$7-x?gCBF>Lr$jd%m(!}bwEin4%yAXMc zZBjC1^ojglqwhO7+}h9pKhE7&Po|GaT#g`bOtb)T%?w7G*J_xIxIod>0n$DZzW)R7 z!6c(=3|5%Pum0T0^?^}08>e$R*nhuzn_UC0O{!pi++UK`LIC_cfnA=hojTt=@!tPQ zw>EY*Arr#h2z8qYMgk#4pY*p(gxaLP88Z_xE=PB|m5dA#pDF0`=LxP$;>t)u7Gp63 zwSN34hivi1N{AAA#uBmEiie8xagI`8{Fi&vj$o%=YjU04qxGTjgQ9BALo>iQk}^Mm zKOgrFD!_Ja_214 zZ|1MEaW;wHLAMhepcioX^v*ETiyxy_8m}_)@_^Hv{HtTylD)pRBTKF?VJ6s4PCjg+ zM3w)(YN6@3ZN#Q;SY0{ApX)cXtE%aSs(iJ5{-JZ_;_AY-9p1SwxLNQokHZE#%sr|Y zum-JE6AQT$IIH7NtW|0oMl_#Qd=zf0Z&~u+LQSOHKu#_IGMjvgX(m9!7u?oX zL=!29`ai6hO2ELcfiyU=O*Wz!HcCQSwL+P_%0>$^k#+&=$o+hbOT4TEGRNepHWa$_ z5`G0dW(it8bJfPTUHp&A+oK1J`BTo8YkE1eond}1q5;pgy8{tk4C-p(_ECmndgx|l z8trbrP&8J6+qcv3KIbT7xcU;F%Z^fa?+AJb8rm%K z@|h@)O1dGIS{Qh{dF{RGkJa*zYBjxi4hDXuH4{Dy8*HSM6;Q4&*djOpbeii-_lk=% zUiE5>eJ`hTm>92s0zjoL!5~NGwyh> z4s;eTH$9wBJGFs$W60jan5CoDQs++%iQ}>u8pibUkE%b<8H?BT1&*$p3r^na8B7I# z`wXuS*l~FQSJD%r{F+C=+0x#x#n1)>1RNJX3`{= zD@6_nHEg`8eiY2&8)ukd|v|1vWJbbEK_4k`}gj z*8EtlE`_|3SYCV>1HSvpdx=;x3P?Pu$EZ>8^{Tp%=#gdy{3s2)f;aI$*^KPT?UW7E-zk>b8?f%LjU)7o7IKBU!= z?bu#*K(s?~#Pi~T@@5X*8f{22membmq|3|u*Q;qi@Bs=n6b$0TdI8Fqt|yy>19|!J zf_aPbsDK4ZmeGlKqX^y?C1Q^BPN!?V!G5+GLg3uyN;w<=E-UnZ=WG>bLDiZwN`yYR zWfcdC%w>0I=R@rL!|f0y7lj;18I`wa8-~2iC`)Hg0@3V8WAQsw#q~>NOGkx%`0KTq zv(lfAADJ6}bG!WszNK=}*#O%42RO^pEr*{$`425s0%QnevTPSfdoia3U zbkAz5wr$`jm{!BVw}rb|W_y2)sO~77=MOJozQ% znRJR5FywLZ`7{=qHzEbbH+WOCLiR64<^ESzT7pToCGS~>`f>)=T9jXd2>yvvNdq}0 zIi!MHdJ{II#w40&K{*8prDfh!4x3Y6eXygz_xurojB1(+Fa(flrav2Ds5mPd5}t;# zb&Z+_C)mkIrpr?qFsg28kspFj);XD-y_%lhn-MhcNRocc=9KT4!!6I&`ZVk z(UG|G@(&go@XLHxP=5?d7+1nA%_yY{!$h{_3V#CZ*6bJCk^-o_EQCl0F{O%f(unPTFXfZ_E5mgddMP3Hq$xd%o%-R4d76Q2ISpM1dy6Ewo+Ixt$kng z^X~6%O}KAwwEeI#M*|=Y;$nm&(gHM`cu27jMMdA1FYn&&S|TSDZ#)}(1(v%XtDV;Q zA4@8+4%*irCx4@-9mqU$pE9m+Lx=(eeCU;$f7eP`wr`j@9MJY}r~SJY>%Yt5X0gWB zcnrl3+dXo4yGz51ia~QUbG%RVsif-CrQk;e=yaNKmD=U`S{D>A5~JD4CHptYFa?MN zc$V=fDE~<>=lO}@&QEuFL7IQP=XspQu)dou2Ka`Z;Q!p=U5Vrk&T(pWpTcWVI&7P` zxY`ZhhP>S`!=0@W{ZCzH#`0AYpW6tcE_F3^w4MA@Pf_&=D?V*#;$Nd4hi@A;6*RW5 z;`E4)AJ$K`UvpHO$|`)u{@F_=IGC>~%ET^_)xAq9E$n?&mU; zeeS$>?3ftZHf`~(``4L+q|4nIr*nFU^t8{f$v)TRA3^%(<;8dY9{yQX#}KK>pL3N1 ze&HJadj8j&8)N&EYZosEh&z9!-Qn4#2Q+J=4F26s(Sjq(ug`fim<}Bm> zZ>j>qnSN9Gc-?^(bqEyon2gksrPlS3aY@CZP-IpiyhbN)7j)I7<>2zJ;C>8O%C$M3 z)KV0J7uPG`EGhf&eDD7f&@gh)FHg*x4FarfpqBzJ*zkI$@E>U0|suIrIc83C} zacKAdEjZ#-tOo)Nm~nBDyr#&%ZhfKk(gBc)@5*fc^&WM-o(@sLPL-)76x){alNYm( ztS|DV`TO`m)tVrp>i*+6zI)33-?RgRhIY+#MJ!E9kwrgO%gX(n=ScNf1#CPwd&1c< zbB`gKU~)~i=>BQ*b8&^Z(%2sB(o-rvFhT51(TF%aqt87H1Z zix;`9#LtT1z2j0}gO_rWXHiir;>wK_3jxTlq0F%ME%feZeXc<{|O}FG;=u-9`U%4NCo7vF$7Sjm5 zwrnQAf?FodCCV5b{=cP5Ng?(*C(nI|!S`y3@!zkv-d@c3m94048XcHO&QEUE70=Vf z{9Zw4{=8fuZdtNHwpr5Njf|Mj)tMT6>!b$dfxydbBFq#1PUnUj5}{Ua>t@B}0btC% zH$HT0TrI;#rT&HvUGICLdia*$hITdru-AsWs~G~Aw_yLjvqcLL`#aZ<^LYm})U*M* zsL#SjQR#?6b9&yah^2%E04J1qEZ|6@ssfm$|KEHNgEd6BW@jn?vm&?d#;=U;Y#!v0 z;cJy5c|Y_-=#rNY$mQ26`@SRV9R930N5$xTUWx_)x>Y9<%Y;C+{9G1(6aDzUwm0q` zJ9~~kjt@8Pbz!_h! z#FwRXnB^FFXNB9pZ_ZR4iqHT-^)dvjc8ZDuh0U)VXFJXtV2lM6*>vbynfkgFZD!h7 zpc+Rh$BQ8E)=q`@lpPvp`fh3=7=c(JSY7-e-0rI^i3{26F*UQ%*6DQC^Oj-&4QN#{ z+M`*YEU+~y(GtGrHJcXvs{U7H)kpD1CZrC_Vu_ky%Mga@<4nR<<0f=4Mz|!e5ci0Q zX;I=PsvylvSAD!6OU{YFWAWh)@;ACDy7;reZYN`tF{Klv$S6Z*0G8B-zD}0Mo~blB z$5S8Aa#P3tLAl+?nR96c-0u^k9mH}ZP9%WI_J-iHAu-3k^}tm#wtI^h@E+NvO~);K z;6dRYEqLr--``&2;(X4K`Byj>5eGN8&^A-f6ob8Tr%1!8L$xmAwpu!Yzq#wnnoM&y zbI=aB8OVS@(-uT1f$P>5{xT{cC>8|Ab+gNER+=3GOk##oj_er5i(0_9%rdwEfX7AQ+FwpV)E_(yIEVIRUPtzK<*M35-?PZI&P~{OR6SYgMvIMSn`l`*Q z>AHr_T}S;Qsj@m-eX31f0}PWg`4o1YFw)f$N&XI(KK)>o#o!%Ju>4I^bjTES83mpHrei$!g|1j37of-NDn3cUV~?|N`D9-z^MKxK?T@U?q&&M zB+Nf67rWW{I9UIEdJY=`BtNcyFP4Hr$nTYKOZ{K-cQbQ^84v)V=Hyc#7C=wWWYChR z3QbAgVIX|mI1J0#+^T<&XQaDytnw0?Vm^}B6MA~N_O(XL#fPuzF5G#Zn-(%%)_i^p z#fLKppLt>Ob0{ccM|dI(?pPi$4XZ}k~O+sMvxOrl8Pgh0n^&m6F%6$N~!qIQT$dFwN6|KeB85O|0)sE3(0Bz?q+* z%Jc>#f1!Jgh=BrZShBfMuoH_Mjk5W++d1$zVU#9;C@`mitMiOenVDzDT0G7Lr-zs6 z-^^h1?seW41I!PG*oLH_sinU~MIGoBn7E^*go3(@0R|c&qYgr(&><^ECCAw4JfVA5 zR_cs#e^l{w6$&$zHfnKqOJgUqd5Ui{yYR4;G?SP>%c~mlx}iIVxjA0Xnq7=}nci02 z6$_^8)_jaKs>mVXm~q~^cm>-HO#GjBqfbbTqd(@la&<#zYHF-LBL<>IBqz2gk=Xoi zEViR29y%WT0G2*-__>u#StJ+fp4Tq#fe1tpr*{c$?p>@G?W}q>kA0c7HZ#uScBlC3 zmv*QL*)bk7Y@&(b#+v0md`(YjYhfS84kz2QDdCDlYM?nAozB6Pz}n@6e}R49C7Wg{zu0f_ub4E|<{o*z ze^;h_boYL6_s*cPY^oFTu`C;d<##XJYZH?#*WIvv_}qDv$gFyJ%&}}XjzfL|e~!1k zvQW{Nf|#ei{cjiTVdK9#AJWyKwEl_m zRLN<5=ETs7;c@n3H*-}QFK3N7_}MD*bGydL?e}6aFULz~s9^1}Spd-U?o|>t|MIgj z6`rhsVzyoqA|N45eIC1E-3YY;d*`Ea##q+&^#*}Nu3Ecfc3trCKw27W9|#B$MaPnz zXzE<}WAXLA9XIUhD6n$|8SrDFSf!oc61PCSd67OSR0iL2stwENM;2k%;}TtZY%lg} zmr)=Mt+u_6Soc-B@uR*6=W_EnppQj+A}^zs!ofhg;rCQpDNS92?R%POh@qKWtY+bL z?3Pb&2}0#(h*4>*OxL|r_p}ew2721d!|6bAZq#EceuY#_>}01K=jwv)=g!~NQb}xo z#U?yxr1Eax_Bw%#TDHZ9MUw6*O?nS=u?cvaMhuK7o*<3yn7r!>)T#;iLOCtOEr+=H zFDas{Ur764feU{B4St*=IJiwWp1WcM0gwREZXRReXnGaJEF5~ceYd&Ew~0<9vPb5y zHv`SdV(Zr|#&cZf^qKXv9L3=oW39jmED`;@p zMjS8Ko8PCkwk_E;vm=;`b%9#-ugZ+&jr0@(J@sof@QHWha-=gibww7|#>#{R(MD1$G)$ z^ec{(=``5mp&XSO6&30sbrI}_T<7etIFN#?hhr#iwSAmS zS&{4M=Lp59BpTezY5IFIR@(6DnMu_qru#SINpvZv>{UiT*O)B>cK^inc&UBym`T=d zex~Hi7m*WR4vR~PUeL45POtN>idOv1k{x@ z#Hmdq{YDrC$zQIeUK*jEQq@Vd08nY#x4#(!v_(B(rc^O%G@0+7EArjalU-EP#3G{a z?1eP_xnh3rG-GwUcXqOTg|hu84A;(V{c+vTo;a;-$9TdNO>GVbEO5l2j!Sn5)Roc% zdWN)+!yfTBkj#Gdq{}!4V*A*=j^=&EZaF~EP>1L>NY9$eL1;D=3xrLlUtMRrm-o~7 z;Ndi>;DpXE|9lAtQ6$GlPGt7g-`4hw3O!;1$_g~9h8-oTozCk1HffUn2y&bw$2w~^ z0S`IyWRAV2qdt}T-w~NqeVU2xFi?&qJiN{eoSLKSdcNDd6_-v39r8{#9~Urx4oCGP zmQHLWSfbj^2Lvs1e4N~NcN2TlPL)UYF?&{WY;L3@J}nU|9W5^8m-=y$Ghwj`tJyxXU6yIMC2; zNvHTnss81?vq&nLWJV;~<+YA(U-qh~piq^Hgite%`E`#bt8BB{#_oIOPE zSNOTm-)8j`ukDGxva7B&NDsA0HD44C4l2!;{mL{$BG^io1diTT@}JKIlTBuGk#gSG z^hx}8(wj(V6Fe?gOEWHIhU~QQtvUbw{-%`@OK8z>U^AMFe4c&8kIO=6w-kzP2N6I^*=spCRzB4Q5Q$T1CGKl-NMgVU_0lDZ(HG+>>l z1bvwY95Bw=5lk7xgWKX;#zO>&z_*9ID^8*GU(1=#V&5TobjY?8{AkFr-0t{+{mOGE z_FDb~z$}(E(+A(NfXpaiG=i}V9x7(ET2R?PcyV!+x2}ca_anaXtg>Hb>6Q za0r49AuQUQverCSfMf@d{KWY^Iwm|C67cjwjs`X2tcu9}uklNi<#s?6t<*s#|8K`X zm7A4Gq(wx)W4p@P ztP6PHiE-1M$NN9bpl)M={r)ig$}nqC!R5sf2eq>BZSolcC*rXaRKVsb)d0~^KRY0= z9E+d02K7#YUu~NiKpN!mGt`5UGb@?n;~%|S6agW8w4D0hCRh>Wg8u!3rF&t(XDL>& zHTYn+q{tHRazp&+o_hcJ?Ao9#J=5?^wn4x@R>rF*CAjt%5pz7&5G@Ge+(Zoyv0)eP zCtVPXH{z*105E3aW#u$Yl2;o7YM)eJ z6Ni_6h$IX8GwkC#fhGR;-K0Y}g&y!!<&M6t*x|QHp(LaO^-m%1OCpL9*F(<_y%+jR zU3YbF`&o;CkiwRPQS^Q4DGT=|76LZ^v%k}S&q+GC+`Zk=aok^Lh6eQp&ju%_bkEK% zaAVjL%qSBue#T=G(HjEu4BH9LyXfp2=R02uD=I7K5QPhg6k;CXSs{OPdOhDf&Ue{= zOsd`m@2#|uut?$V?c65f?rj!MAxS1qTwa@SOb;~E2(xohG$*Dh)t z&Dar1!Kb%|jD+EkJctsWEd=1pWprlQl>AiFfy~Fo(LyqPfKuEBLW@@a{X@LpH}`Ab z(s(MzgH;SiEf?`7c=sy)!p2pu&P+o|HFa>tWA8R_W-timc@Xeh8s ztW7S?GE*0aI4~3nsGak9$aWlJQiNzgcTY|T8S^0&v9SDK2$H0W_*q!gH%326(X7HG zbpi^JWb`5{c6gCgxHSm{ozm;;$a;BVX3xUrux0@!0m(7n?b)f3n~jt>*;TbT%4Jc6 zpEmWDROko%`C%KRO6Sw|*tQ542W8_Zqkqri81Qq_GWB9bSDpvoy49mGv|SO)o1|;S z!oL!JV|IV%lt4Q>C2vxI_ik_*dCnx*%>5Ew_&vpI7HBt!>tl6EnFWiNq**UUxFnw=kS01$E(OQbj6(w!Gh)^=>$MgaT1+&9}+}nzm-c5Rje_3NPKa z@79bqBDjjq=%cnp(rixLN4<|@$?*TWF@6as zugd$a6$veh9LQ{noPbdV4VfS2v8d~YuAnETP2SRd%dLV?mT%6N(cyF9_4*N`4^prw z1VSNO^)?*a&d^s-@usah@57b4*^Qd#Dc*v`tbi-MbujOchWnw!2gBrm0N6!)J2sx= z<2waG0@5mmAp@6WUI8@=dWDlys0>w7bx1>bMRqGPdaE>_0t&QdROtx%uIhCT(LV;D z1Vjo@Z{N4XvF^`Gd7eOfTYHM765X9gXr%H5f-?tQA^i{NHWV>mjP`YvZkE_iQpw&uivwcJ4(j6 zrq@N;wkEgE(OzHrASI5 z=tWnwm3Yhqz-)VDfpr7LVG$dC#9SFIu|c zG(YY3pXJeB3;;+%*#09tO+N;26bBJx?6|dqRVpf4&6P+mc@*EHaS8)_pO zyoB`r8J@@+&6aNcwz2Z}ZHncS{O+tF<8s7A}XGkCD{s4t>TayHDZ6m;>|#eyqL zayqxIfQ`fJqc{-@^6&ia%WKbz3R5qL>j^I0YaE7KQ*Ao8ickKQ=0G#k6;iBGSC62_-zSB_`DNkKum!omNT#vO{kLCcK9XLC__o_?BLAk!#-mG_$Coa zk~7!+41I6J1OOEL3*>zxylmcfe1YWeEBVDr2qOr93)>YM!lW|ugNDvcprQ|u!go==jVOyqZfBZw^E z<ET84&~DC|*W?=FE_g6ow1KlfL_>4JuMC;{BT*Wvgv($%R|%dbnc;Lu za0KYk@5Z~<8P<_R&XMl4!0ljpF!RJDa##!b>HhBCUF|6rLRb*G$tY21+w0zXMh#hn z!QCGUkYXB}OL%f(S$&3?FPz-Gr;6{ckk>0c z$yQogY$#>eU^}^1u+aY4MfKo@Ee&ag-<_T$2myd8et(ycRz;L=*qX@l2cL~zu@XK+ z64&I0?E5A&JUDFH=x|^d)zYMV9(((&w7^KO(}FXG&WopB90}innUIq4@i?V^Gq-;e zESU9wZ0sre(Jt1%nPrTb*TrNXur?`4m}MURxHX!cYeb`kC?8n?f<+TmxPBVtTHvb5 zxX80=c{Y2oyCt%3Sr@IWcGZ4wZC+7YZuKJQ57xB@FmW#h`^19HzK2S!5(UynK#h*VwtaI38gun+k>urPLRw0-Z)?PTe@_3Iy@@E-cy+*LYbK`2C=T=auaOTi zzo`3KJ(lx6siM4&o(}G4EgqN;|f;_ zrzJAxI1(vB1bT}tHi#NMo>qy8mrfM-3p3wXm5TS+CT8sg*VPXHnyk1p%53CB93gub zj6AAbmi)*jSUA1ZqsLSGmu@exf#Hz$@W@&mKPGRd298k<%8(&Y$Y?EybtdG3ntlBR zqOXVKC766XUWYD`x)j{gCItfPS`cxe5z$#5753z5LUP`+YoUZ$s62fKCdR>Qd z;J4=rk!elt<4+w?9qvk3dhHiGmd$Ln9TT}*n)qCtF!;7C@#>GTjf`_-sIUvZT=VcG z&c!4j`+THLoig^A%Vzap*7_S`+XEuy-lwhz^DHZnV2G}r->`s~2VpsA8JZ=5wP z@AjOJe{>dTM=P9;eZ4RcNQy|lUAAZ1AY51n&FO6Uy*adL;ppp&W3V zy!9SmUP{EN3Hx?<7k2O3!h!UVLg40WXZSWr*<~o6*>?NCY^rv8|*p16A4R! zbc8JAw?J3ueqW9F)*{jMG}|zn&u49}kL$@Iy#O)b+~+@wP=LFq%l_8qRl>*D@kXdX z`)sSy@tlI4kh?>B{amfc(~fg{wh~8=LB2}V{}zejp(nLZx0+i(k=4Q@~BL2uRT3V*M_iQN~egDE#6HX zM(vr};*Wn+>bsV01@%*dxhMC@;D6ckfAe`eG%vlwe#-UtE)xytVC~id)lqr-ZgHz{jGJNS;(0o<_*RFT)u*m}4Z z>wX3(UvK$&yxwMlH=B3rO*pFZZ=Jav{YNgC=FxtoRsj8Ncr3+ffH8F8bH`jiY*$I# zxUiS_@)|ZvLd9%FKP%@gd}ibx`-y7{uL^CuMz$)IBvsJ>N^XElzUiX$l7V$4eopK{ z<-a#KUFS)zKCpB$0ekBs8^18L{2rW#zmH^$4SoT7%W_IG4H5f2hZ^{+KkFAtSL(kV z*;XaqOc9$e#D6$Jpcvvl|9d-2&U{H#oVF-K+s3w!^S^$yKh+<}%;8_Wo~*xNIJP*u zneop?QSaVgp#O4Y$$+!X9uRS>J|ZYsdg*pFRl5X!S(^q=7j9Ky1AccY3NY(%u%PcX^%O$ZvrS!`}0k>Jijb>&p7%`zRqlZB<&F%5UM- zhGi`}0Z;+)GmMiuoqDFal8h)h&{ zi6_MOY7HeVm+S+ec7?z5RS4jo`cbWjVo$fxA$n@nkx2~lkoTekACs|WO|*{KOO9;? z7*Sf>#a6&U{%naebk=dF--;ft8d>(rFn|_e6M!@yy$=Y ze8us9Mb#&DmM1+sp5H$Ra;2OWvVw}DXN-G0sQTz0A&A*v%3;;u3e5v6BgoiT7T=V> zmt98$3ae{B4?k-gJEz(kWY?Hi-nt9;8z~o=P`JGovhnBwX~kKxnR4o0$G=iuXS1a) zmzv#gEZw`ReV3|{n~}}xF0+n^M$vwZDy0=wu=jW2#fnvxs0CR0p1{j1i+KW1CPA6f zToU)#4$aVfa>O*t`Q#Iog(TSvE@Wj{)NVZ<;}$Jo!}@Q}c`lUFw*^`9<;0tJoQv^q zH=DPmhCM{(*82Nyxzj+2al|@FcDz#gxE=b5*G`gvg4zMbK(=%0GL_lg#)~&Ee)tRL z(8Kg}oc}}UW*uy1@#~Osr=bu+bltDZMxg<6H!k8LZg2|`1Mll2HRR4m?P!3$TlI21 zc&vG5Q!4@M-XeU4nt3oG1ZuM9BWmIExGVGX;#KnV+&%n(0Xn&v6Da@5^83H69(#+? zN-`k${_L!HKbLyVElLR;B35{XX`bh0{*OQ@zZ0>;^V#bQY7-{uch#RKwLM}=?zLEX zm)NS6SOFYWsCO%tEx5+I(zIJC6|8^&+L%gg!pfzbW$8e{M@l$NH%+z&z2_8}u7DFOQ? zwY7b=n>`?CBMWafcp*wOAcqtKyaPW&``~sK;n&;?YfP*?lFwCp6|7Hr{|DbhRSH<1 zx-Zp=(u%A-(EW_PivJ%?UmX=!_XPQn-~obraCe7b!QI^%+%0I(;O_1a+}$m>TX6T_ z?zZ#&c6a`oGl#k7&E>tmRn^_qFi^J31IRStX0iel!qD8{|B7TP!pJG-e{Wf~=_CT6 zCZZ{GTs}n2g9|FY#{xdv?eMkF9uI9!&g63T(h3?C*X z!wy!9YMhZGgO*KE`1(`5q<=s%&tSj>zT|=$=$$7($rX2C$*W;;dF90BrB!0|A4EWc zX7+z;b6~+(>_Wi|N*6^5xhKL?X~}lUqQSKTtg~1Rs~$yK8}RH3kut z(BO1PO6$t|i!+4WMTsJi6kcHer=*@KJBMAyiCE;$M3b72xXD5qUBs&L(y;U+L;soF z#!ZmSaQ*02a86_qA_|87quv7;Tat}<3lsC9od`J-vbOxcMSdnx+h*pNHr*T%k)5LO z{*312ec)1q@W0gjB2&*3l-NRn>IxBqYBHTYyj;-;uhg%SB(#J^7azMoBwN$P-Q;w> zpqy^f0sgBmqHI+}*H@w7tzmCGECCr-a`tx94-q{UMNOkmxv{(b?EFmbOyyrgWe7c{ zL}YyJBuadWk-S6L;TywyC~Cg2H88>hGKbvm+Vex$=Pn?&eWUVyj#=C}TFZGaH^=8U z0|`MIX$VzFwy%V}aDwF#ODhGx0#rTwQv<1uUKIV_%BlgqMd84JDutR=$|+kx)QHQ<5wGEYE>KIeM*Uq`{W7(ouODMNtA&f+=;x5z3;3}R zH=rEyuzJKTE7-_`Ppow9NDL7>5(|)?$u*cgM=8duaMUdjqq@FkiX=w4oE?p3ak+mn zCN82xJx7GmjOX52lOz6@rX8Nu-pSsbz4pQvhH>&Ol3V%ve|y8pkw%wQq}g4|mZUT1 z=ha4qN`$HUS#0({)v^Y0%B=rX>}^g?26s|4j8RP<&ANDQxje<*j8*GRkPz?KvGsIc zN8C=1ah&N$hZFjZP>*3>M_ogtPebCC;j?((HW~jf#WtO?vitr0j9Lrb&6e`w%Bl^W zE>kCvt5v@AY0c2dvEW~=E($D}|KuDc+PPR3BsTZoLssh;csyRVKU_@pHf?3BYg!xg zyvF{#jt*BkddrkgI6X=-$)$9in$^#yq}Ug!;Ck=+_vzU^6nPOFlO;IUt0C!VTwEM4 zn=IYUUpAIE-+@zq?vIi&)CdC%>A}_dt+?EAfuJqUYXn(Fd(JaE(h_jt}m`1ANo3f_#@A=Nz*%>2`RN2(``$WoTL z3A-~@R7?=SG#Oe;8bNS!85r~Ub zz8*66nbq$Lh;j^f`|FdkOdWTd0@KYlcGd(qr?l8xvq$6h0^H?`cc_rfi)vuJ99eDf z_3d>R@j00)$0r|rHTwG1R+=fRGc)rIT7GkrnO*Dxypk!0mbY@*TfvAip_jXXjOeC(% zKqT{Q6Ek=qd&HMxJcP3F!5Yyb9=~CPtRxwk>PKyhopIAQ`0;SB)sceyfrg_`=coIk z9ETA-LdDgMmUFY#AhRn^%wn|q#d)7=$*1HWq&$uXbrm{Zq|lYc(b}Bf48ZL^WbB=a z9@Royw)SNg>wBtb`kLvj?s~p@2`mYvln1h?4tM*9+}e`I6rz4zunrTB7vkX^E4RdtN|LcRzq4{+_tu7+^pl} z_+~Is+JEZWkj*Qdw2u4R#Pjr&LhEq;7b<%WH|WN06ovWc-NEzUjxHSy9pjqraxWPZ zN7Ltsa?ieNGvlGs0-NRh=N=J~6*}k#k^*FNt5|Nj$pNC5cI?4e!JG4o%|M8gjCBg` zFCvYDK+CN6SExTL3t*|UjAP^2;=(D>tsYpoXxEkrJWo3P6zgj zE@`V&yd&bmGe)Y_B#n~<>3TAyPvW#iX^DDm!3B;MBB<(Bo|1;{<7@)Nq<&z`mtL&? zi8~o<9pp@3f0?UPlKxB3_u+ZPOSOB4=T%k5`l5*9YBE((HcB5G{-! zDtAY6u>rneOMGOKys6;FyaRn^nHR_J>9CZa#~JHlwMfQ%UOrBz#~pT8k?C#TB2zz- z%U#Bt2a> zV%tY&Jyi?NHzN_9+w!xhR-v2bUxC{+O=&SoEh4E$m)8{k`)*8jE8laksms`}PE$C7GYzl6fIMuT@V7aQr#N(&ARIEOin;Of^~e(&b9DMEXK zVrO}nv?zdLjeOh{AO@{ZX1t$pkzQ+Um$w@FPTW0vREBKgPTp32ob~<}@RTf-{ z|IdoYJCQ}p2CM1(b+4dDZQY)x1mcgI+bkxIHRB@7*UZSNP~HgTlM{W%CBo|u*ZQ`I%Rziv9)?=S!ZLlje&a^b2_R<&?LuyBs#U_NV)C;vdudaf6*$r2hLxT%I{+(u01c_D6=GhrygykB^Z7?G);C$#T^)ry76S56u2SYl?vu_5Z}h!D;c!pQqG0-P+Mi7ur)dzkS) zGj%#yMMS~Ew1aarO|!EX4_Otys-a&rp&&Oaic{+-&LpW1Cm%e1dQH!kLEf|?Zw z@RvYigGFtFwDIbgNHE($DGGMAtM4LNm^`QF!HX9g`9?GdSzgb>Sg_L@%WE52%%@p1 zN1F6XPmu7`v~1$~lrcXcjePSgB4y+sKVb?= z%zFxf(_jISxn+o);!pYxrqvcFFT!riq0RidJM{0VxmK$514;HQ?O7t=V(w}TM;N8G z%-0OzlC!^GG#W9psyj>9o+5;YS2x&iD>d#IDj}E}pZ=gu6upBT@?=iPDB=co%W#Bt z6{~xd3mepPs|%du@NZGPZ<1(b zFo#T?cwsi(TW1?l{V?4NCX_9gc0;o6x@d#XEJ~Rx+8b-zjJ%0*IX??A2l<)85;Dtb;1}Zqf;%w)41b=DbVK8{18e%;fU1W_mC&(UU;*`j__~FUmnG#)< zhj3_BR=a*P*v+kW^f$PNu%>pc=rGn|%i!@K?w_`BO5&$^4&IxQf8qEqIX1I0R&d{k z3`N9b>^tA0d>y4PAk1sS%*Ki+Sj7MBm3#LhS8vo*^7}Xy61pp+B_(lXC2)2h?xA#b z4&*#^-TuYWS#W7ybZNFPEbKM&*qK;tTCxQnn1sbmd;U9?R+sD?HiZ;pjv@L#jUu2d z1lAV4TI}ft%;&5^oj@GtAt|Ck)@0&+N8-;sNs(%e4$061&Uwh@#C~UL%<7Dpx(O-m?M(v zp#Lw#5I?Z%j^*)dQCtm-D(uc)2M{nT-#iI(1$+^QQ={x$5Q6%A6n}T1lbk|4tRb@m zYEFkKDO;wQUKA?9n+y7q@5b!+^M{PB>sjB^9|T^+f3UIDkp9gUnt%L_&DPz`=SBa+ zqOoRkei_x9Q~aZ84xnc}#o7KxXyNRmktbQsRmBS~+xtk@y4LE}KjT<+D9!IT-Czzf z`Eit?_Z`*)f|S7U{mvlsUEyN=)ooMo*}nT*1GfE3s8MUzrWu}mY%U(8zXP+OmFF#= z?JeHQ&Md7>D+ZK!Y8h6t7Qc2PBtjY0PN}4CAgZ{|d2+sCgaWv`UDdfo{2P7`f7^U> z#WVXn3P6TbGJ1~cp)UYq7@3dT1S$w-5j)7Kk>R5(P7umC%XI7Bgo4I5wpA8x{(7j4 zv_HTIA6S%pOzN&%SdYhQOCqwXnDH%rG@MUnk`~g4iZ~#{)#b}wln^lij+Ks|Na%G& zo7t`g1&7o@ypX%*Fzx6b=ejJrnjT(;DBKgT4ozJJ#c66`+!d5BFG;Sh7h%BC8`#GF zUY>&U&7HT`D6*LH5D<~Ur>T`_K?xVHKEO%H-Duwb>TN8fuG%a{EKY{%3JkP(`cv&= zgEfa<|I#}s=6c5NQfFSrPwept0YdKFCZ^KLoFk-8^6xtm7m65nIv=ZFzr3Kjh6Y~^ zeC-bS@;vzQEaBh)Mol{vnTOk_>CLk+F?cyXd` zvFEGTNAGQVYt;>zp_|&n?Iljg33m?O12u^bl=Y}118nK!P=MH6h1wpUS+6xraNC7a zX{oLmV|v8zzmd=Y#*h=164DB8hC9;`8B!~Fyao*=Rh_o{tofjL2E4G^XPf-nwL8NBb|KJR#C)XGsbai}!vjkvU5Yoe3!vFS_K1eGe zSz9XYvc(u-&3(ZB4_!#pDQH~r?&G$f5&~JolNF=86{o$HdP2f!%n^vH4e!{r zJHB5*=16Vp46XFyqqJkdR>+`j_KrU2;H;gu6`Zy3Wi9!5!m_;9&OW_{&K%?qMJu#W z5Hz(UZU(!lJH&FsXlqkA8k;IG2sCvo06b^4BsD>5@5bDMS z3O8g_7f<)K>7trfCAi}!lg1n_i$Qb$!5T5*EhWaUucneU6Nq%y7FcIFq%U_1Bw@qU ze|@@pJo>ipNGvU@4w5O2D;&2G4KpXC$|$#T&{R)oR!j z7cv3mA8skFAQ4_1MWm+0rjj?VO6_eP)OcB>|8f7irO&@Iy=q|%zvQOpTi^~g_lTjm z#}E&W5Y))O011`I&FR{7g1IK$aB!8@Dd`A(7&2H@2@G>d$9?JZ!Z(E8n90x+fYqd! z`%b(TQ1WLfbG0wZzm~>3MAN_v?jxf~jly1pO=sKkyZU zXY)$w=TyHxSA5^OJ|z|5;Qid;tAfVJlc@hZ?{GZ2whlwkEl_P$hEoIoU8l4=vce3p z89Ut+=VmvaSRu@`_SG<);gDJ^iBeo6<$ZO2DJiYJ(kXuN086WF#uVeYVt=(>rnZtk zvd%efK1Td=rQJ$R7SivmyXZ;JZ>QgDpiFIx7+m&(MTk+6asK*qBkX~V9Xm2)sr)Y$ zC8b6Ez~SqXTs$(wK6hHghxDciE4(@Wh!_>vvXhU)Auc*C|G=NLbRz#wl8dTnmhP4j zP8Mlk0O!l6jiyCC%thC^ZcRKDv?zev#~UWE9nSRT#O-3-^?LY36fQUvj`fWTmzOPU zU#`vT@z!K#N457bx~yySZ&YE@gPqlK{7`c8MCp;u_Q1N(9LzZjPs*MB&@t8%u7Ov+GS7Q*Mf$s$YThHs(6*#{`;d!d~2hExLT>hBdC6EY2TPo zyrQf%$>e%L*p4Rc!{{;L%L8IoFsH_Lh$Ze2+moOQA4a8Ir!T#MKmnO))Vj2zku0Ss zbE*+LYb#MzKB5?5L44~~o5`BX1__{v`(b6p@BNc!v%Vu)dR+@WgWz&_@3b(IXmlM) zC>{<>_5%j}uXMf(hzzm2(J)rVN`WCxf@tyrvxf*NYEw+q4eMXBEwHDtIkI$KhGfFX zZ<{Z?SuSgMV(Bmdj#6AU@_?pT9m@m*UNXW36>c-#%o=f^aq?AZ$3mDT<&P2x_E{VAGkN!~7EZe&Iy;k?n4jz8E+TKnzJ0XM1V!PKx5BZrh7-zOr=x+9tS)FxF zXwRc3Qyd(NCm$S@%ExcQlGpFf?K@baR}B|;K5wpS%4~CaQQ}*kd1Eg5LFM&;AN$t` zeFM$sD>0sU2!MiAY*IGjyN=SNq(abw;8&Va-ps{Q&)P93@L{z-5T2eE6Qq04XrMnq z6+AW`#OAyWGMYOTmQB~Dt03(VzW|>&WZx%kp~c1+2g$xKj_PP!?_|X%tIVAQt5Xp} z<0OiT%h|SAgwb0sJHgR1zI+8BR=l;$R@;00jCKkCG2Yl(IO1t%?o$ozfmk_|TbSi+ zH=em-RNrjP1e#pi_O^%HqN=dJRfha3vSXTcFcZMKUs3mUQ41RZVs*lciiHQ@OnKr5 zrF&Nrj4Z0r__OI#AROSi+OiG8X902 ztnT~pZQH+Uw2^(ViOGK1#~Tk|7T#Xemt^O1*baROu_qJz1WZ^)HE~&E&8^>qQgs~O zZuNNMV1NOmh(?$r)xkHn3*tiZBv7!v_m1K~)!AY7vOqgnKNI+ie`ZNGPC5LR8^rWbsw z`{Qo?l0aoX^vC30jW>EGe*)0-J~L9S8m7r-P1 zU6ZaOpG}<1c5PMzmcD#BD77IzHUYQCk2|;-cff4uoOjyPjC9shIJyu5YM}nt5a`rl z^PwsdGcyg|{D3Sy-@!GZa4MvUbA47izeV%Q+dR|<%is(m+qD<_hX%Rgq&e@KJwg6w z0%B+kG<;hkYc~*x<7xoxZ>z<@(R$83pP8bSq^Z7I;w!xxi3w$_2QySV*r;%VmO^<$ zHd0ANl!X|}>hbTHD)a>(EO5jU5P(Z)My*dX!@%kz4Qn-+`1ySyulw&vX!_W5x`Oa$ z<)a-V3jd-9HS(90+LHB~P74%>Gg}zeya>i`bWVXC1K)AGoQB;;Jev$dj%X+Gz zq8ufwy-uoTpkrQYvp~C=wyB9M{KMyolcsgI zpAZ86)}+&BLRIwjFZIXk`}?73F3weM&lOlngR;yh^muigPLBTVmxAV1jWx1P_N(OJ zAHVGA!VDQ2Te;^sEXY;#imS=X#YYsQA2KWQX>=KAn^bz=)w1pOC}oc2OW|1K%m99c zjV6vL9b@6CFXo-9D$1`bpuJgHPY?qeI^<`Zc53#v$`HtV;|Y+rRNLsJSOR)^&}1i; zb4s(Y>EyVC%5D%0#u#@r7la&yUox2IC;vGtVpYkzeWV3y>lx`ugTstNxB$i|IL2XiZbK+su0My*f^*_6~k7K`l!XVvRG zh1ZhIKtQfSt^oxGJp}=GM_C1*C=O>&wDcOv7Sp23$uQJ7^6tLBFuycOd3`?QRrfdU z1xYwp0{)l(-2x?lkJKl@nX3$G-9NuW@%?LsIsI=>D-(^PQa$e zB#LNd#^U7!Zln#`e~FgU)Hhz}!^FqTNf=*224l@i<%6FG0&iaKj{Mnjrmnj5m?B>8 zM)%e70r?Oj9$l=4N0rJ`s#0bR4YC=WPl;EbIXB&+EqKy6!Z4;VlKLMnL3T~C**OPs zhXafo1uF1g>Ie{~1@myr<2d0XrAqQAEON;yDyTzgurGRM&u-o(6W~ZDL0kMq9h0Ac zS*Ntsl*9My@bvLYZzuD&g4MK+s^1U*b(}0KXGR-KhK>SE7tgS5Bp*L|_WO;3&Us zh-byGB!8D!|8cwU^b`-k@jQ;4s48>|Mn9zDKoWwm-J!OV3Hq_8D2{CVh@fa_;i#U} ziPoG_7=>z_E|Hzm7Ub7B%6|7~i8X_aXltUC(ZV99DQ^;|s;Fg&t})M1{Vx^{XpnPP z{iE=SjgT8Ml(hLjT29QPNM{bMT7UM>%9S-uhfD>WgM^Bg9@D`_9% zu{E4FBs|U${(=xjINLv;qco6k8Il*%)Rx)Ggu(MGazs|Cl?g;e)5?s65Yms7V4CF4 zwxZF45ZTY#9AXZ~YUgWztse{0My)BeqFNw+-Im)hy7e_@_7!mEgoi#Z;YjI$Z$uW<)~D7yvkUKzxCxIClfN|RLBMTX|F=VNK6hk9lWrA^)?rxSz2-xVr&p8G+Rrb zHS9#|?}lh({MO9D2l4m)+}5hlNFKw}HnNZ~bOQexlsyyaO3EIR^lR59Ac&b%hW`)a z)``vFzg%zBL+ajvMgCtQ19-X(JSE~oUqhTq;PM8GU^?{N1S!?ixJOt8)JS?}N|BPN z18>VXBTVSrdyHy{w~56}>z0q-%qez<#Y?4=rCl4Lf&d}S&BtlUncPwUpq&m&G^1lU z7zoq)T?Y7xb)i;EvprDX2`%wj^I+k#zFhw316mzMc;X+@BHA!PmK)X+i&A~mm* z92J%ac7lg&*jY`t)bKPsoyw|KwjuZywa?mqXlXsoA%LOEcSz`8W3t=Au*ic7mJmNV&7-j4FJ! zH=mW@JS{XmAhaqf3q=h8(yYa$+D}m&Z1^Ksh!QxO*1m-Kv1ntA-Ol_MFXAMqEBpuV z;IVK229DN*3D_mqvlSGA_)gOw*Yqtqa*9GbfE=nEO2J zqEoz|>ra&s$&bCC)hYn2)Rb80_2u_CPJwWkV$GT56ln${o+s{1!)h;ZyOPB>s+8a~ z2OAYy=Fj~>ro42e!Xvdxnwnzxmi10V8v5V+2+G4iR~j~Dp3t;k!{D6}gE$)f6`I%q z;UD5Ve}V{CRYTz>qYsa!j6%7%W9V!v&7!!-sja*-*Rl5kvUV_yc&kp z)mofRXfkO_Ive~sT|6(0qRND`HO==kG6!m=|K0@!DgN=Po9Fy%VdJbqpy$#(3(`$O zd>Xg#Kj?grjS3<0bfokyC&pl8Wzz{z_m$?)%xlW(W}~YHbv%*_Gvl^23Zy$-Gz1s!laMB@$#G zX9~}Qn`Ixdl4Iwxc#Xzr_&sO^_s`p7B2gU?L^nZNMX!YqqB${S_B@DAXYBW)jvGJz z#uZOS7j3>$`Q`$e*7peLB_3iG<6;~%J;+3P{p`o;oUQSK^?q-oe+CIy(sVt~G0mZq zfE^`ee1%Hp;HhzbC&i~vwb>@RGrf$}j;}}*xEF*L-vrqmIuk~QK|vQmbbm{h`aD%? ziOC+ise9YGAOcTsYN!<>CshUEhc>pw3L&p8FFv_OW8vYt@!=^5!bReCvJ{sZemWWU z`MvB|mbQb7iu)jwmFK*BlraXNw)0k+W)EwW#Ht$)q$*ZeL?*d(&R+1&54MVOkl3&L zEjPwnP4Psk2(oTBffELL{Y@k2d|s>!(6hducr?3bCD z^F7AtlTrV&wo|Q?BC?Os)!8?7kRd1ul7KMkX4|3_7-C8!eJ5}3{PDq_f;BJ8UUiW@ zDVaRR6@FE`Z5*QJ4+T(WL6*j@F<@UpB5CL|g=u?b9Sc=8h@`;>v%G0 zFo#UWSK}nIrkZ{iEehvEZrOBk!&v}VboEDJN#ksUlf^6HMi~P#6!og=4>uc_ILyDr z)!F8P+}tPauR8^LQd@WCtt?zw84Gy9b7XliQVO&-hDf1<#of%DJ_8Nz+A@d47x6__ zaS?OU^iY71e{J#P^bP=RMVitgkkj2_fyI+!$$%GrU}>E3jj3l}eaDGf4!;73)njya zE9~(p>$|Z+vEGL8bF_2e5309fRBtOqa9N=BM2C@!pa0yfwL;H~we3$SnWXB`i2uVcFnYp?h^G;bXAXQSLW}0JZM2te#qj6M<9k~ndwuJ#T6Ff^VIvbN#xm^E8TCdl&;Qq>!9BO{rS_ zzjDPqz^*q+Ppyc9T8P1D(4bxUvpZmnJGJOi?4Hgxh zx!aTXPHbbDx*cp8yRG|`q6?w`IPkrw;cOkGYvYwQH?6Erm|DdB(o~55Akq>@Sz0yIBg-vzl6h z)MTpyJocRe%CfyhaFBpsg?~kD7P3(H!OU1iTIleIv8SycLj~tH& zNdb0_=15RMD;(=x+3Z=gr&3Exj%$oUH5~mjCtj6K;b2!u4_c%r&GyKhqvU8Um1b4v zeAB4vVo~3CQ;w+}xQb1&Wfxf#40C7mr~nrcK%qD*qKpr(ycL~a{nJuL2G{PE7eYX4 zk$k#?3xpN<&0t5Oi7sL)_RZKjVX^w_qpZVf8x*K-P5H`RIV(op`8CS}*fLKJOlthW z3;;GQ7#_tdl`Zr-T9YS`Z8bPW@ibO*Z8%Hrf4DhxG#fGoM9pQCP(Wxjzd>OCW#p)$ zn&CYcAxZ;lwfV@K24xmoZqG7#jLm^j`wKN7=ePNo%+WXMXXv^gE*ad zP=JxwN=<{40Ts8S>4fX~m~fIBK=Dt;jngO_ozm=Sbq?@jt-IT4oL0=SLW2cIDUdIaZ=<2IG1@ND4ru zDhg60H@(FEk!+!rG|~uHugUF|OT4K1)!x3%-lB5xVg?Y6>y~hL(l+wnTxCW?;^x;; zR))@LEwdTW{Ipn}CESB&S3_k-FO7aOKrozEn*>fqHACm*MX7l;Y9=^RS>E8b|MMu6 zxzq+N9)L8=3t20f`MWT9`Dr-6-P}R^O|1BhXbzW8z2U;7WgsWj>d>}TPg&Qo=oG^O z5}Ug|+CUBc?23SSEqJ()0Io`o*3V8@RDN0p!}u%wLhZQ(3O!|cGm)I=B)5!oc(qf> zvEhL=56N8m_4l)&D!Md+4Qg+pD+02$&BiDW{ON#f&V)RMiQFVNfz31TN^D0I4}QtC zjs`bB6JIh^xELr{bufHENPpH~+pRrCOEZHsiiu6^bb-hAzT`9@>U4UA|UR;?adV(Fcy z-}88umvoTbI)!iaGS>cAS3gnc>B6Pj8P-&*H%qOZRvH!Gu&AztS4<)oV9D#R>v zc#43wq-(huYN;j+@XtLSaiEUq=heG$ra^uR;!eb=OR;y=j$rM{r%lpjqh9u?)?dI- zkuo4un$E1%0_=O{?9^bb-(r-vE`{9<5?+nfc8ur!{rP7qEArNjwX`8*-}WL02B3{& znV=`v4tQu;SE}1-afr&uWFp9&!0@>b8y0G&gdSLW%cioAEM_7>gJ+7(#2y5K9^47Zd_;%q9LU34FM0#* zSE-WrzlB9GD4r|bHZfUI9tUz6R{kOQvLtn|R!ZXlLIHadPvg4dfA>pSy~pKqwi0_* zqB-z%bcF-RUI$c$dd1>6XCuWrLT2;#RXet)B%P6r zzvR|&!ZQjid=XK?U&J^;wG1f_=miunnADqw=Q3po45$eA$69pve>qL|8&vdIyj3gE@xb3-Rh znCbYwHd&G(qP+#!i}{{l=9Yw~IBq_TWeNJ=1Aaw=z9;*?fRJ8C^RgR-4}`xjD9W`N z=pP3t;gFfAGVE(Oj=CLI+4{`%m84=2>7y-rBQOTHic_yb3LTTpbg=&jiH=^!x?3fn z#@T3csBkNEc<%bW3{prD_F*?JkAk+oeO2@QxaXb|aQl&KeIM3W%bVjvGi*{g+5JkA zw1)AB_0j$Ao&bc@n*YcLUjSR8TA~aWR|O3IIc<;8+T+!7aI*ltUFfaLIN|Xp)N!p^1!{a#XjPr#+$m{$YM07eFpGdr~vSe?I&&Syu6K zdVGBRa-N+H9zo}NzcULb1Z~PXN{&kWeTI5Oas(P(o$@VCk`11nkobo(Gn5dVt{=2( zEuvxVQra?)r>EI-Nmq4SOvgoJpX` zyLdJ!C)=bqvotDOEg+dFQx84$7j3)%A_Rni#=*_Kpd>5^*sZkgi{VjPHuSb;3OrEy z?`$IE9}nSEDVe_)uY0BX4Sz4-hs>pm5NN3V^1Tz#qy$j2X)^hf`B@O*EIYWI&sz@P z6w;hN)1zW(ViF1&1Wd}~iIm&R5wkteK$CIYUVRc!JwygPzrA+ewZ((F+k+J?k5 ziE6bkA$j-3@3vkKLNg%@CNTmig3zcVW8mA7w3yLYi{yXQ$4wqwYeFXsGwE$)Zly{ z%3W4$SFz2^IP~|=*>w6oYb{?J3M<-_;)g^4LsgFQ6_YFz|K-*VL5OnjpfdNra!Oid z@Dt;v{cM^IIW&Q&U$2g}KG7>KA`0dH@RrkShWf|DN*;AiF%$nq6JWMZSF6=S#A)ui zm}gTRQzLZKAio)R9avvsOwcbE%u-aMYK+Qc8-~_T!!RbAb!Bs|8*UY|Bqo*%oSu50 zk4(E{|K<=ak48;ReGk8c{d}sZO0-$BAu~@jODFgJDy^?3*7EM%$1Y9X$p{`QTrQGt z4$d@Hm=au?2fW5$Yw2+XPO9Q8J5x*=vEz@gmGK}r*{Iyo3a=vuFPIey32hsSCjpdy43xrAu0BpC8_QI<#Dh;Tg z{}X6`>}*KJz&hK-yHec#2hr{wczhMhn;2`)4*-lwt8vsM=L2fGc6I(?76*lMpYJPY zR9&2w5ZqH`;eq}}HE|_5DQ3O=?FZHj4?rl1ch020#?s@W!OVK}Zz$e1lrV^vY`u0~ z%4{j)oA;7c(U_%AGwbxBmK>B{OPxPQ9~!rbrf%t4R~wTaUmUO7pTtkVQsL8)ohJN% ziEFOd`*w4*MH}wD^punNaZsC5HP;9SZgWZ%S--&ZLKSJWBVp)$n9W_UnKuav&|gPC z0n~7rd*?fomA107(Elwb)xR@Zpsh>UI;a{FAafpYiWFiCZ-RAfgO8qa|ZqdmM{&vFE z^fN2F70KnSd}G6W*oizjNJuzIZD7Ynh(O294CgASoXu(h&D)v+1K~_K4Dc(_?VY(w?mud7D^o3=3LvGI;F5~NV%Azg`7GX@#Y_C*=u^;L~ zfY@J*5gHMw74=08Hba&07Ts;ceNXyOj?Wra4A&s`?6x0+W}5;kIMA{}r<@hHDbeJo zhwoa*N)_nqb~#t0oO7a*`>vyk>;?I#&|PUh0xMd|a;Fan@CE^XCoLAbLgGsc^>`HX z6&5x^$N6EygE`}LG4)Ffmk=YqKd7&hr1Eu=UM%a2(m3WJcfO@=kyS+#>cQ^n4x3}B z5O$wX3{-G2HH@=l7q<|sU(L^A7TCmyb?EG+>tcQny;;^{Ju%zYFKdgnf+62a|0gHR z!ixSl>woqt3oq;?2?My)pSBE*zL6}c+%rN<*Qp)G#qF#fU^pA$8m)2XtEt`Pk4h|^ zk2O3MbCtU#MCxq)s&nJ~@^cpe7V6YYRveA_39w)J@ZSw7T~pyBXK^_`eHwU&c*)Np z4wo+QK2J-U)d{7T+FQ?V?nH~84dkZ_i2 zdhCFPlEz!UGPp1nb`Tlkb4HafpncV|O$@P*`V-7pSeKOdZXPy2n}iQXCfwBn$({X$ zY=`(+*ld~j`}rI{e8Hvg=*Y;~6Ko(~V@u2wHX*_)(kIzq4pSwm+pxv208nNzD_+LV z@@Ef$p=@kjDzU^?Z7|R!gMN#a<6#{ZJUl*+&n?#*o5aAxi2@+zD}ePDLS?5AF44!H z)4J-a#GVsJqa3=z$8AseUVYYXr7hevu#TVSH^4fL1<}XAp;ehhtx<8LOL>@?#X4=z zvKmugG*EUIT`yiXnQS8(NgsgIM5gS1W#37vnAK43mScV@JYnSJ)I2k4!_cvODj?iV5auH za`(=yw#}EXDQdUpzg_dOVH%6rf0WP|gEDBuZBUIaU1`^;tGPPUz&DrhV_yFC({kP0 zKF{&liS-Z8b*pRFAT{>86bTI0?0_fZ{~AVB_w|elX%r+^Med+qASlYo+zjQwH*6m z^R+s~IlJ%H1%yu~Sjh6AbvWIwVEpq~0>*6KvKM}EyAVg$**63OY6-!vjg7L{vj*7S z;BD$HxG7Z$7(WljI;{la2t%QRoA#mG{GF02?&^Mr``=kOMRkVVk5)0Py=;Aizvwyi z7C6sjM4%&|u*3#Fnb1e5w;}-QW!f!ud+DKu{*L;5$7YsyF;&MN8U1^Z%3FV#0ph^O zHf8&z0MN9uDd2@ws6}y$4q3xofu3wB8I7z;+7K$FrYm5QFP?7xD%Jr4n${St;Rt zhq6cP9v>@q>|;s0W3*M-KUu2ZEk7^Nixabcg~Nx|+Dg$)(yP#jy``iaJ&7HsQ{PN>|)|sdfw0nEozv<1o3b%k{2M-I|A`B7e5(_-nyMqV;|ck z2ZrO+5D)pi1E0{(mw!IdlTdc6^kMYny00wrx!`v2B|Z+csumClh00dt%#~*tYGYW1ar~bIyMHvM+X5?_KYzsUMK@nW$rcaO)CuI{fE$8dtQaz7K>@ z5P~Ni?AbsMqnVvaHhgY&C0+JneRVP>{21}|&aX2C77|>km!L;+cSK2e<0%tFw_#rr z8jvLimRKcJRe)rHP1L}AK<2pVA|xV5u4Bi^kmI(g%cEDGAZ}e~2zJEFo}j;$UZoH@ zvaj>KC77_&5O4>K!<9%|T@iVvmeAhCute7Z)1(6FiQpq4j3MC0DwOU|Yx9^x#_~K1 z{)s`{Av!)Xgna&>hHd*a15@=*L05>zKj@|^rL5X?2Xv=`A?66JBA%1{_U}whcefQE zx6nOH7e!rbQYnWke1L%WKBSjm0&g#v#x*k7Q$NS2w1BKzRMU=6BGD#J41#K9a8eW2 z%U7V{K29N17N6@1F+?)21BKMawa=d4aZ)zl&PKJ64*%ar_X(MTF>1I-!e7TyWSua(sr0S$5)@J{ZRKU?(cmuhw$FLLSnKjuEZaJ zTG)fFE~Yrhu7(dgEvm@s%GCO6K^jBD=EE)6&SA;H1OUK@_#Uj~o{>>cH$hTVwSW32 zl(3NoyMUU)*FjGH8k>Ff_VjQUr|utz`bFHq8;o}`JZ;qeD}3nMUi$MzDeI>`t^NDN z;*9iMes$vY0!2U83xT=nD#=PIaBMEE(^r>jXYuPGL|_-(4$y9LWW7eZKVap0+v6u(TZmsz zyPlO^$vaG(J_g!#J|s?p%w}Z`j_b9+cmMhmJ*w#hG;4Y@sg9yXV3-4#>vU=WA2%T{ zcP9>n+693LdMV)V6ZpIV4)FhT0id9uD0d9FNkd>Z8fXul6}1aevx4>-f?nR??6>D^ zq`+9G2Pm)4y!>1$ALOWxD{t9zm$jqdo4&6vh-fj&BotA9uZ=gBUQO{_Il3LlltSi| z*L2rj;}(?Dy3p4;(wqYXs4r%YxuH5mSHGn$cdmBkEuoR&Q~&t@QC&nmA4UTA+_N34 zV}AE8y=M=mkVQt}D27#U;_KNTl{6J={7PNc9W|VFdW&7`J`pv<1WYn~3L&Y$>6VMv zY*%-Bl%YZXW6ow(Jx!Ood&;q4b7$}|-$I+G zj3XhI{VXEG1%6|Ddyz3lCBO*(MekFm1VP5aZmOX%sgoG%Ck(r+VKo74104|Z2{j7Q zUYhfO-ap^B2%awCtzr&6YhAk(aP7P{V~40U>2>MP+uvG6u2FO72y^P1u?=z5R{zc0 zw3|hlqXMiQdKb>^W|vM+#C&Dx!2x<|6eZz4N|Uvtd>uc~&6*_hB;Wv+%8Ns( ztFknHeDHv~cby}tmWz5s`q)nP%{!0~3Np)3lyLvsUvR)VhUoDy@AfC`ixQ&UdxF^k zRxRn<6vUJG0tYd)c;xbus!*0Dm+YCOjK1@*0UxXgb47bjv$j4XXKFgOiPMmy?`1rrCLJkIcl&DEUTvMU6vajF_aN(?*aijmqqhTYpR?Wjg@z zuCwL_S3W&$ZS!`e+6=M?HMkh#m=312rU;cUkc23%>$|vZE1nUL|?byHZ za9_P=i8Pj4a-1yI=FZ7V-jMPniY-q;aD-m)Yice+2r!Dh<*w|) z_#x(!;mJV#gqX6W&UEl?pv0o&>-WO!7j4n`r+|^fA?7sH{#u34>i!bDMWx%BMp^{X z>JjdgGJUalwg5XhYpC~*7}#Kf_vIxPy}qFg*QYf5hqXp4ae77wK<-q7yMjS2rKb4a z7z~v$ibHZx9+c1JEf}C!$U*jbDmn%#IfI`X zA$$bI?Aav}0Dw@=wFLTj2{jOtJ&s{^ECNZLJia~MnjU)8*L5_jtiA_V2r!lffFDy8 z|Bj$gaZws>4Un7G01H^Cx}%mmRPyp)vG5h$88o8!HD77tHRZR73SAUPy{QiS5{ncO z90;L9ddht+W$nVKlBDn80kxg0iqH?Z7Pw)Jzu@nO>K#8Ff*U9hzZ2zh z3^8~jt5r0Fitgu?=#tYTNXiBOD{+bOWl;G$0qXy))PIwdtksO+L`+Uq$^hA$QAcNo z4pw7w9%r>}F@@lL z(X@BM0ppF8)_?j0ym~snwo1#?A17-J@QI05KCuFGD(DdFdPkb(uwFQsnK5(dSw3 z8Fs&sJoD@1nP^6k_+zc$>^Dh^h=|;}tw=!<2Dgb!C%9cYZ_>;oD2d!&FjpPFXeXn3 zuJew;wypXUi+pQKSt?R(LO%ivsHm{3P9GZ0%P8d_I&R>hCYnPxU%WrCrZHyT)n)5n znd}%%FLN<0@`dwy+QSQA+w3BGy-Aw&f3uX&Ka%&!{(-OiH~RDZdQr4b%CK8H%W*bj z)k1fIOdCO(7JA1q!tEo#+ zp}?-~k7k2qGwU7!3f$fJ_?!AZoTF$hNzB4w%qHnZU;Sm2Ozia|ipmykHe2FUVkEXQ z6PW2@32*+Q5L1Pk^;=^nAp>k2?8^j-J*H`Sw;MxIs_Q?UjT?+nrnOVt5=%r)**Os$ zXO(aYg=XY+l4|}^fVHNsvZzv9h}&4ODiIZa7Q`yB$wpj>^Z3lRYkn8S9^QJ1F-vHl zn1+AmL*7qlNmCF2qmrK{)!C&Ki2Kjc=y81Rw{BI17Zy^QaK`H~+To8UA9v5YcXNAZ zp$OElWb4T(EnZmfZceT&rWs2>5aqQ2hO(})a`Me6XIHGMmNXWwi#^|>A~X(XVNo=0 zdJMNV>OPw|u7_jX?HG;XKuDmn^2p^{aG_ywYLlgwxX<5#|MQ5f&Od4k4?+IoyunL> z&gVvYTGN|2^#Qg|PVLrlg2my@;;$p7?ypod5^T>ppfHV_bwLi};n!Jg8Ak1zcnj`x z1oiuv+w)-=@;Mx!7kwP&V=e51{D>lFW;NmRo6%L7)kNZLd=q+|GpI;r!XKf6Ij|1JT{w z5q2{93zn)kd?UAG7K6oZkNyrgf9CjqvBfRg&RclbFfk6}d5Ua}0Mrm0hSRj?-N9Hh zromyph{_meC+y&&f+nLT91N(908DYF4#o-(pb$53q$FSLB91x^7@LFd z_IID#U*q=W=isoo<69JkXP`&fAof%=)I-$|by5e6fFTw#V6I_|{3n z&?PXJE-=h~QpKI&8`4~hfh$`0vN+>*X=!s7V6p_w>)h>sWJN^MN71$j9$DKTE+Wb- zypr!-N)aJgFf38*Pnh5h%zw9I+U^WTX!a@=OjGUxbs8{?=&H^Xp{F4NhVI2LFcTN3 z!|1Ug0@6m;c{NhaYAXk9n$Aa@bhiXh_jHtQ+k(jCY75CGAE(w~F*kNFdrP-=;nezv@<&A0*5 z1hlkC%NzAa4>o>E!Io%TY{+8ykBQL!8l+~_P&+-(g#?G?kJJ7ydq!q)h!HAF69seM z5<6GA4!<|})qObr7(xL;Sa4k6g8iPv>D`Pw*M0K9rYIhLFPYC1 z%!tD7(*}`GasZ!Kr_Wg`3h{HYvnFHP4ciDF>bG`FGqUiTVP^=oSQA) zx_mZ{1!i!%+51|u-9gj9v5x3hkJ0uU70>r7vFH(vtJ1}$EEN#4iE9fL@Ab4Iy?aI6 zzpBbjgceQaJ4bpQ^|uknU1^3TO<7%8J_aZo`~A>mI$8bR-z(v>C(ZoRg!?^1|3?gf z=3K#66HzqR({Uo93|A+Aul7>3=jr!kEu-M$68%1UWXsY*a%E06Dgx@Ti%2RrrjwGm z*zxdS6}1&@yBb6+b1#&jV~zeqSZP`x8*GCxDBp{~wc_~yi=O%Wq^_dipg}Stt_~k* zNx75bip1@hNfIf!oIQHpltK*A<~fVC>2-$WW2CF`=;bc-;MuB-N!H6(+VY3{yVBR#!_VSd zO>^dE;F2cxG!d7C^Ud92nX0ydB6z^t7RA@z*wKYsRDiDi3swp=472quo=+KlitTP_ zaUIEL>tW+bqM8)Ss%^CD>gXk_ULw__K_o8yIS>yIzhWI6@0uc0dEUnX!E}+~Rt5mb z7wJVq_x;+3?kgRBFDqpGtyH~kiG!@8?}l!Q-?#o`|D=VOzACdwsSO)lVt|52RdLlZ zO^jQ|;w;|FM0gnyf0D~QMyKI!x*h8mUCtBIahTwy6je@&SdEPGYj+@4e?h#_QmuVs zuRl$12oF@WnCkyngw^~JZ#=H<4)Xq`Z$4!_vm0b<$`=p*w|D4Q?DF0lqB^lSK zo_=>`VQ%-g&w!8o#s*Fam=Q+=dtCgp)&em1nRBUsxsvj8b+gtVGC*SY^c`6r46O^4P_gsD} zwA%HoX>|~sL7(*^&+$8~yh=&8$(CD#dZ6!-yBCsrK>@36W|Do{x2;GoxXb?9VPCD7 z3dhxWu4)S#i$(Ej*ERl71;#*t=sfmh6^FK7ZP0*-p{(IUeB^fI!P?rcXJjxO=LcWY zB{sY&Mhl=xvKHh$;c-Lvm)J(m~wDHTA$Ad9Wr!h z!)1%B)WCq8UZg3+EM5#5yI(h_`%Dhz<^Bq2P<$tJKtY$Z4lAWGxr z>~eff5p%MNc2Tk96!vB~~-bVDV|DXJVm^ieQC;zt!!C?0UV7Tzsa9`5 zg|SlCSld$Us7&zXZt&`8Dtgeu2WRkr!^E@MEnCHbMbl6_=&qsQ{mm$y<|P}or(a&4 zil8T_wJ*ro{0&^2eA`9M+=*lM*pbGW{$}f!G7*9LtEn`XuP1T)wV)Aay&fywxA~y~ zHa^tjl3u#st-w>Fj%?j0Id)D z>s9saY`flX6f+D(r`;7?$>MsyCE~4L9R+Uud?1psIJ5WJ+Adx&0Hn=$jJCTou3a$l zL8JHdA>Ri#gTFr0EpVGBVN=3__muOZLRS@_Sx9ppCWbb{_qG7vE!Sjj$>p??4sa3ahqt+g z`7vcZzgBQS_Zxx>ukP>A^%(rKzP#0g{P+^NRqQS(Y|_b#u82gQCIv)Ql%LoBA5CJ1 z<>#LT3<3=S<0F(Y0igi-_h z1n>T+N474R{7V+6o-7XoZyHs3Ds9m<(;kuX+Cqdq?LtH?4=-7CO0_PNc$PhH2qBXT zKt(mF(?vgM>i#J}2nncG4r>>fCaX7V!fLHVrd}vB-n~KxM6{4dY`EBaAue)UhcBo7 z@II(ih*_rKga5(&T_N_hQ!`Bz3NT3U)DK=Wp}5n2)aDhn+NkYqUt7bzC_|MIBn(NM z$3buP;ar^BCui={>SrXM)+iZ{uYMD3jl{>xz%5TI45J4l6RjS)ClL8^ z?XrjU>Sp{K;31WhXGx(wWAG&T=-+n=6R7gz%?#n5@)ZE@beN!}J=s6iI z^k#M{mCN|`UOeh(n6|!Ci2pw~#Lv*;Tjf!{^Bdbbkee59o3$By z;#Jh4t^3XPNcT(&MOZ<&DtE7$muL{&hE#D#xZG z0ASjpCb)*n(9Ei+c5j?hS1*e4Oesip&=1C>F~j#<<;8hV!iHSMVUE^#zVoJCa#j=ZDUqT zY5r5a?{s@3vbni07DRj3hCsbfAd^K45pXHnyH9B1!2fa@Yb(}zUvi}~^gvt!W13h4YaGjL+65o_pl zHEuktKQ=V5WuO9UW?F#I>8<#!3(ug9c>i3?hL!6{kBMl|)@KPHiy1qq5%#%hh#tV_ ze{ZWZuh`erxJwVEmSjrRQl$?;E{O%Yl9vBD>gRmzq2OVbE_>hzHhOT`RlUV^r})pUcO`k85Rdoxxfv z-h!RjQu(|Iqo+oqNkB{=i^PMdzj+KM41i1zVG>IE06sTtD*AURK_TCMhzN!mxJ7ZL zc>dlUh}8M!b%?+pNyhGJ_G0kQ~caW&kID?ZmMQ$#L6HF>?Y2WdsXfOXXj5W5ROqvBBB5yab@)11+77K;MiJ|Ic zt2+o2iylxh)O{*;yD`+zwEIc@uMfR-V@R#CptWX#3teShN}mm%CW|j!KyaCeL@XtL za#fP#gpM$K^$I?jTIx_u%2()RESP$tHEMK%> z8`!qu1_#(U&r~hj+maV!L;A~&>v`nSUF=@^u5E8p`_sguBZzL%NccRtCq08tIZ8@u zzpnT4?GtoH(-LYt^tA}dPfu2oq}y9rY}hg|gTy7Z46lgoIoyj~ z9abhFVHM8@SvbRL(sTWT7nq6C@6SU9E}D8=eJ@<AkRAKV$3v>yBX7=E8ADuNYt7X0_*3F#N9$?BW-H<`Lxf zDIWV%Fk;YwT~_JnJKvOEpQ?+fMy$x)z~{+($-i_8fuZ>|wH;Ji z1F!Zw?5O9#!py~u9vwKnoq%n(6*-EzzwB5=k`4tB513vR)tRSMe~;rX*sxUIgLK`09F0W@@$h8221F`?M$OV9P{#*l%*{0 zg&>iBR^bS`ZS>y@=BZFdoVL9z8K=5h*h`mx8FK}pVSVVD2d5LEFdTQ259Fu3-vV7> z73YVAx|iDF$LXe0^varaXWss zF&#A)!uT9&PIJDl8jG$4;KNsUeI`DAf*cWs%H08f-yo6`0@tMXXbbxC##ZvMZ#u4p zT#D;Q7QphZo|gA#=YS{35RSJu(ZopLfIj)%m5V8{Mr>epkKbtKNvf)~#t%S#`?4m{ zSxqbwFCY1MxqP%05*_ixLSf(W*t01(E{K1T15AvE*m$lO!79$r16=_h^1WaIz0&*L zYFG?n7%H?hkE}lYJ;g|5dpdJv&KkeN=yMgz5qIMkcf{@$2%82GPPg(J|a3AsmqFAAZo6D>*dAFg_6 zu;Z8*w>d0`{91{rx+X0fwb=!9$GRK2d-vd^1N&)zb!Kh32ojcG`^zX`0KFaEv(H?c z`<&~S_^_vM2ko_63wzqAU!R6U{?8s2xnZYHt|pv4roL%7Ch1_@-+(aBE8VYC({~QE z?3(kD;&j(ZYR!-iFPSyLi|RrTCQJ0OGC6!(YZk+M=b46r+iU1E9-Xm6u5^ZPE|$Tt zhp|%!$kQuOX-?w=b=&jskCZnr15Qvr_++Jl=$8*qg< zPCFq)0_9++P=UkM-?ULOgBVJ z@ZNUH?>o?Av?hnpH7iv7E*snQe(fyEjjDxblO@qrWSr%o9}s%$KRM{(G#m;UvjQxkjEpXo9zVscM@tQ65O0 zx`$1-m|wv)T&E@1EV(-xMf9KT1}piKe|wz$QJR!K>YQ2ox5BPXruR08Ty6^Z z$LZo@*pvx|>~Y8>w0j+&@Eq44V|- zBj|2!r@-O8pM1}(kHJ?^(d%$3U!{z(e{IjGFZv}Q9HBX(YLnNX{Oz0fNib=fltye zwaLo4|H9_+ys`zDVy&9kgBPJ;<&DS{db)J9f8D1P)S=iypXT&{hBf&dRCN8Igfa)C$b)XkA~e?%X}Y7Tc&N#w58D zU;%%|WZMeRZGI&}<7m9!H_QA*10JBrvwu!#R>&_}oYM!^ymS(M$dF{lbh+YHPyt$( zmwX5^Pj6L@4L370k?VfQ0Nrz<9Dv(xB;MDNYT`;n(Iv;ooQmNr^37&AQ);4Q06+-P zs8m`xCj9Qh8L;;~IUny-C$l1tFZ+RCuU>45ZBner1yjk29TFqoA@II z^XSn)Nx}qnaqHo=B^=Qm3Q1(p1FPibUN`56hij0SAK z6(K}L>MCBawmdG4ZPq}3g~a_QRbPA8*ZJnO>5}D3&#nUa8?63ff4K!sy!7mv55Ea( zN#3{6e9KoIDxZCB*iB?V9fjwP!C>z({7wxTwfxDC6Vw-_w#g45gCD6M6y@n`CG7E< zFX>`dU*qn(Gcu$}hXCklzEyAOJHv$;3`6W!YB*-%4fzdTHIeRS_6!Vn6)-$(bto z#s`73!O0fov=szCYCf7*E&8JoGqmz_(Xk7negM=8EO9&N>az#lB8lfIv9fgk@^J8F z|0~_YpGBGL+;{$8pC?@s0O6456pbgzUhNPc;M=wstnX#+=h$)WpXlD!w_@j%C3L87 z?dXz5vPy;!cM|0>4+A8f3a~G6{x92Njml_4Y8%&lDhM^%d3dXsZwDfz0Y(V3VYr;^ zL(#r)+_J)IphV#ogzm(NI}!IwNwZO}{V_r^ILL%+r#bZvx1{Z6_s0ZtM7#hq)s@+l zjCZA!=+ct8A}!|Jz^4cJ6guS4;qy=MTqe>1NIlB^;o&8s&4x zo-Df}7BNi_Wlq750R}4yR*1H^3AAk21XOU5TdQlxVq$< z!xoaiS!xmmqAZb6C7jQG&78#)kK5!mwcfF^UTj$Mr&e%)SlDTgJBk61V|bV9z8phn z0#ka%>(0Jp$cj(G^by6fLmP2*qoO@`x6^Lr(0aIqe9|;FK_4wLwO2UAz}hVjpf?0| zNF(c9&~x9W{=w@jJacGwF?t{g^Zwdu~nW}0h#FMJtQ61(?U!StUo8Dng_ zmJkst0AQ>EKc!`}MbF@?!p$u@GI{<4n{VBKsl?+b9)jMCiTOMJ?Z%1*J3fPu@ZS{; z-@_03aBHXk*t6}R`3&~ACAK5JV%*Tkl;T-X(it)#nBiQ^K?M_qB{Or3{!N|iZj-fW zl8>A&!f{Z82$Xr!k%SaKsxYV-0Lo-C1Zr&_vpiGFC9!KyDJeY*b;Dr?K>4t8-F&pP zthe&BBodQ2Bd6wM<0HPVxPbwDmapW!y4u*J+Vt|TnYJ;OFUAs+({GywVC=Wy!K)`r zh9JXS8i&9Aa35X$t+Gwsg8u=h-!q&#mNAV0aH$4J(tUV5dv>=B{K7-KzSCsHlGJHu zV@b8NaktXS3%i&bp$dizCRE8;!cm+e0))gH-;6H()UrK#2_vS5KDy=v280><0oRJ{ z=j7w@9nwu(=e zTLJ8Cu|eQFF-c)vIlB3QXABrS0br zWLE!&E@$XBnqLjb2c9UuKGW~w0faMFpZ1?nKJT|P-h2GEX%k8ihl%|j=#oqFiJ|ul zTHv^Yl&$kL4{1Y|6p6?xH}S}le;f2bgaE+Wl*M1GSr6f+?e+LSPMYMZ0O)(aYIp3E z@#?Ugpa;+c-3<`Z#pc666vld3O&X3M84%Y22mvUxhS02D<;202O)_m8owa}FQYQ48 z1;6&XgYt&#)Z=E1o#`AxcIq^Ym;j@jQ&#RA!LWkF)n-S=d?O5(3^L?n<6R{Y21KB( ze5zgaYWE_>AcDZA#PtgVQr{8>lVYpI{*^U82q;mDV&@=$h0Ciku3E>1h}isv2~Nq# z=3IACR8UdxV%XA~X#S&d_|q4H^ap)LItLfWroad6Cz@9!AmODDu+d7A_RqZEdCZz0CIINR2B-e-~45m`QL)0&2w>`Mi4_ z{?&)*<=-BA(O5--16o~%X0qQn>4sU;3t>E;r|jF!<(l_Tr;ZGY-VDw7edqr6Drc0v zDMUhE(~zWzq%92<{sh(bq6~_(G^nDu9Nl8ZRNPYvT_Qc*|6!#IKt|9S9JFg;4u&~H zl3HqC0S_%E=DO&?guSbBuDQDEnSAluGq2I8YE_JttFp|Xtf^SwOGivkxnM5sDu$rs z;-GxrORI8hsE1*Q!{q^5(ta4av$gq;jR~h>*6MSBQL8xzzAE&7rYlS;W7e5NEi;eH zab`{2i(U?l+?*HsvU@+P4!1JFncO6i4{(ep%G&obo=a7F@d5)gM0Or7pWt-0YKYw`QCRf zEgWgaIggszR5fej1PY~?5v=6kx7>$Ui2U~pFn3aD0M)<)eS!s9 zqFLJN@|4(oP2(EQ>PG@YioTFDw*U)v+Y@tZJEP|J$}$LH{rJdT zCl`wX)|DRpT(ySzWG5Qu4-X^PBo23k2FhF}0iKMUOVtIFtj~XRNE)KYka8(4wyyU; zmER&Ut}I$P9H4c(5<#am{&0CMu+;}he~F)7`^wIQJCC88PE}mLAp_iHJW^sG@|Yg} zN&I64LZoB495M5Odq$O$+b8ab}1D63lzKmT?Yj;OMX3qR{Y`bk2msxhC%yPmTuJLJ)9zV-dO83Rc!X z@7rGyIH)koMJ>c&=2(FJXAKiH9 zML$y=KhXP6G<)83(_Zj26@>%3^nMNs&>8(k23PWlKRZM+--jqT5^5pLGS~h(t@&&T ziTA(F{6b%Em1QxLBn>oD|IDO#ale1&;5qxDm)_r+*7nS2T zpQXkn1lA=wAaW}KMizm(KH<%$qhS(Fr1PBnb=j0y@b&aH8!En{V67jC^Xrf0Pb$AG zqBiMOuK!h-0q9QaL46yI*PRX;%i}HbCNlvb3^mmbdqW5oDj8(bBW$+uF_#unruL{N zd>4yWRbuWCPzEX_I!SAU0KcS2ZiK$vIMgb?*0u$qLgJ4!(+#{%?ANZh(3@2f3W&FT zMy^^mN>(>LjR@)ZnXqxaR(DT6yuGp7+e z<*3BSmVc(5~|!`J+Eq*DaDQ?Fu$N9go3&Mnu+C}_%Gt|psIn%lfp0O6%kLb@t1Hy2$C)^NP4tfq#@zf7jxB z%y8Qk!<*P#=qiJ|V-(iIW#)Og1&_H07~95)u;JmnSU9pop$aeke@6>H8c4(qzTxA< z>ZCIHozgvcX}mD+M28wkwSyWwKmh@3EE@G)#RnS}3568c(3#F$i)KtiPet?AoO(hg z8BN#}-L9hdynz)g_P0jJK_wIPSA|Sy@x9Xxkb>W2Z+Ay$4lK1W#pdv!I+6?$S7Ck4 ze!VJc&+2oo?gPyxtk=`y}(@UYZs$7q0t?B81 z(^EhedyZ39%EO*dO%g#ZCJ&A%zo#Cbs_Xy?K+n^D$Ja5+m75TC4{$Mf_}{?Pi9Eo8 zf9$hs`+58dSsv)!^OjxHCLVVmWvumefg*<>JH7G?9S5oqyWN3nI^(p=Y{dgZso^7HP{|BY+Ig#Fv^W2TM)a{TJ(#)Ho-$M;rm7FNuM_g&9!Ua}@Dj;Q#r(~jKFgMVJ0t)Ht**#y`3EijMM z)$|{)j{XZ>hnc0&(Jj6ce!6nO7o#rzfn$@EG$tzUTeNyTY5h1dG`xJDf%Zl zNDp_=_DlFA_a?U|^JBa`r*t|tkN{Nh z@epK7b#NnXBM(X~t(aF=`%H9zsPt0`Zs6hr1=(P|wC9>6m$Mk4a%(biDh5sqJFRFq z_2i%9rKPz@95N|j{A?o^poLvmS=}kQaB`V~3ycPc8rjDr69pO&=xSIx!UaHeWnQ9H z+k@L`XXj$!A2((8q64A=tRG-2CE6WuYU#(%W&;g;IizcP*LaGE0-L-Xtv&c&bh@UZ z_B&AGAqI74yA22{d3TmrMs0lm#3?MhIl0(#QSD#;d>Ysqq)! zV$g>g_AP(%kqHYj^6%vAp-M3gHVC6apz`Hc_BbxO=H?oZg~Sr_-MpM2tX?v_$cGD0 zmma^_{!Oj@?b)f<^ExTU(o>12anBi?q&6EHr;3&f3kr(ILf}iiLaw#EAyEg@( z;$Ohh_ul?KVd~^xC0a>Rt9w)ugcadGIa>{^RRWH^r&xUXKW?$f)*rNAccR_;c;zM^ zR*W}4&HKs6fd-gjxQV22@uW1$J7l!@Dta;%n*)U(q*W=ra@hiVS1y$Cm;!LJqyTVI zW`G4VTUvTf&Z)KGroR2yTcvF7^gq9mO8V4IwM{kss-eM9jSi9E>3vXkhE;6sf@^P^aQD1VrSSbxK_@WZfqnO zCPZ%dSC~i8z*En*K*xp@Bfc%~0TXA1LWIr3v1Sgz3$(w+zhI=&i~#{+GR?HcUV0?& zgNJ5g0)pDFcCC%_wx+(2f?vb_UtdUD1TV#gpXvJE9Tj1coOEpsftcNfqRoZpBWC%h z5i3tJkRtKn&=fmM{68rxH$7XeEF~M3xm#vQLAwIUU1^|tNg!2AW5fFOkj{|R zx0fdKLdpXbYms~((Yai$;?XA>3k>~bx&`h&p%3_9CVoqy?hM*7-ZiBbc^8Jb^6lV% z_FFEA1nh5Y`tyCJ8Z!Bg=kUkd+BiWr6FF#0v){ByG#B%IgRK)#Jq{OEpTUIyU zJD~(l=w)~Bw`a>?huJygi=c{Q69#faQ}Ichp(T7rvXK%VoNzdYBU49u;N4)hs1Arh zI|Kj*od-`bS(bp`3s-PqrbswvqR)88kH*6Df{Lr-?&(X}2J8F4@{GJU6uT;Ugk^tq zUc2%@l77>|{S33~NQsQ3pQH2e$8AK_fe=AGm=$h$*>#f3-`Y_FYl2eH1m%)kb03D5waQ*6)kJO~~Ar<)eJAKD^aRleKvd zKnhFl1oP>5D!O}SubVimc6dmP-L4oWp?nNEjAA#8lyPv%LqJt^g~(kpxz|1BNM+DqX3AF2By-xFK;bQyGzX%pCAjw}p}dykFrJJpL~G5Yn}Y|4l~Kxh^*Ta!HXS{7KxP_264o>1Ef6K} z3JsdP0mS;zAVrqUPok)P0u_JHSWFuX*EvjnNw+lo7lb{P%GC>2uql?yz~w2mf6qe4 z&tsS`0o(vj13o31-S?=tYh&_|*FojNHCh$Dc_*=iKk*nss)YXYqe^x@goMZMdmYQ% z#{i)^9#go1L<{WFTbxXSr4@_vB(@v^&rO`SruqMQBRcMS{S+ufAk&M`JBN zOM?^tPCX{S|LFbWkHae(?&ekW>8s0h9%pHkm(#6+L1_{@( zDNP7KDICN@frLS% zBVK{v8!&|737}{V&>~teo?zQJjc^VzWHx9o(8pKBNFg@0dyJ}!soC*I;*-ycy^Ol@ z3w7B@dS_Ei7LSU0K~ANi=?{S0XN`}lAG`_Ob-E{DP_Kju7p|3N7;=R+tw^VqiF`(V^;(a$sP7NUxVf;1X_*2->3veZB z8eb0l>Oh?nIM&wtv_*qOCvxBJ;;ltYtTs$FZ0J*sinJIDxr2fDK1u$7Oj)jESWBJgmN@yaePdVY!y>28AhmYVT4P*x~C&mOuI$bjSB-dS^lZr!_Ic-`F=iYI#i5- zcxRN&n-1(+LQKigPtHVo-(KG(E=o3vV8Lgo-OZj{Um+dv)=knU%8-2BY^}Sak}_tL zgz55DKQZ%8%`LmB(3bMe^e5sgIw(q0{|=>%R+kxNT@{0Jp?C9{<>&vf9sik3jxUjascYLL# zF5-nggHvULHRYCzfAVb$P*zxiPHXk(2OHD=9~S^z-k!*Cr93%5ALPF8-y)i*c#kW?=%&RW}`)Uztm* zQhUtWY1yMnu}x`eXWTMiOh3>pL6m?S8;t8H{Lwvu&FaFYbxkxgaW`oLP$()Mqoj!N z_}*s6eSAf08}I)WpkvS~++Hk?KiN$2mSX$I5lY_-79jD%bpxMXOc=1;>7$*R#fUzM zx6bQ)_?2`!Dc=0C=jR`A7uv?ywi)d-guE}CRDhz5M!yAP6!tNcSG z*Mop}TeZWk?IB*ZywU~yv3#+#iRL?$61kY^Ii*77YDm4nJ!*-3fBaBA6F*qq*RH_D z1JwQ}+A4fBd4Gjmrst-FjFQj$Z9Imk_8Gp$o2`W;3Pga!ECe?_@)@HstKN!t* zGN)4melcYv7MtEqAOMIk!VMgV!6$3|{JZMCALiqnT%pGfqH1yuob5X*rdNK%lu{Qq z^*MURMkP0AM9q|%%A*?y5|M2z9bjYK$&GW{Bw4b|jaAbuggM|qS4ow2G4x-8xL|)} z305Y;MY*#$)@}MP>0df=-L*k=mUh;;5z`#t%6c2!Cj`VM)gHK=M3xZDLZ8rUg3v9s z$`&%DAOR{>Po?QlAL5ooOl0s1voGWErnL zZ64RLZ^T16w|NXqh9Z&O!j+7XuYBm;xVEdvH$F~Y_wv|%={bq_ry=(MimGaQsO2x& zqARB*1$~8F%K(S7$0I)+q)2!aQ_m^GxuWm@rr6LP!tT8{<9Qzli3Y|8XJwTw)jRF& z_jI1kO6tQcyx0;9_1DEgPDM!5mz_;vaY@D661Om zla*Cd3KdPb$~(izq@0D;cW;1qd6p?)yZn!h1-Ft3@o=<|i@Ix3ePu;9YW&59KATw5 zN3TOaOBUT%yPH33%Mr!;ubp#KnG*L-fcz#3h+i!1%G5uhz6*>EId9<5$?`${^0GB$ zK4Zim`({>E9Kh4GGixO}(M%?lV4bE|lf*HL9CGw!;_{LdHlX?ryq@T%XR zd)=?6Hz=*sJ8$b-AN^PD>gu=;`}Ofcj9niG-MYGU&)ZcopNyYY$6t+1 zk9@A6eFwp)bU9OgqrEPcxOfH;eYjbov7Kq)XOZQ}FYzeY+PZ<<2jf zxPdClsL+@;iMQ&)lQ;F;C`77IAbYN3m}2Md*4h37AVsNyv1`ykltGKaWw%mg-Zxj;h*+6}q_C`>AT>?N3e`*-`?kOPm4QH4da=tZko_k1*uV z!KnmSD_ZmnWv7^QU#NPBotM!R)g`Pzfk&puJpw4Hsr3h6e<5YA9P&*E;7;g>At0_$ zxS>n`Y%)*rrtd2OZckAhJ>N9;W9l$;X-yLs77nRvBbq?u)@~9XfITi-+H4db5!u0s6kbyBbc6PeB8jsUJfs>28&f& zr!{mlpcD=PsCnP?BI_`?Y1vaMk2F$Rk;2QycbUrk-e$mPKQi`(Rz>dPw~Snb;X%;O zSuFn+n%>GFvqqeL|J5s-R`j!S^}1Ts)(HM)*5&bVBN^;WEa{Tk7qM>p zi|8lZd=(9jY*g}E<4?Iy*dh2|$X9{t41t8^mKWcB%o^KKH)e!VJnSJ=x~p&h^e%+Q zAKpro)x8Oj)s{=!o0l&T>XEduJ%pp6Nc;iH<7w%#%(lKkf*!A2bTO*V)_(Rh1)uX8 zJIqlcx}H?i=2X)rT94PLok4|D?PSpVP6s=NV@DA-@%rk8Fhdg@^+g@KskBVr5U8b& zrFQEM`a+$;B)?sINe1g@XF)@~V!E;Ni39mRYB){IY6~^F4@KyVTgI~1zz*@LL+yvh zZ#RPIT;TKMPw5RdF`>1#jYx4D_Q=tggHzlqGi{LphD{@f7)K$8Im`X32R>6aU4>wv zr!AZf7JUo#&b!MuaY@=*xzwE$3&{osZwhRSFh_)RGWML)#xn9>i4QQ0dkv)fV5-Nz z(G}0ag^(t1X~!SwKnN#+2L-2Aj$V33ux3?mE=|tJJi~`gY}o3#bbE7m7gIVKxq66a z5L|(mhE=pc5>a76`ld+eHr55Ukr)hVE_#74aDqUfsH^lD$e6J@m6IF%qjl^torQBc z9LQ2ne;Y#zWf%N3BE0v~7*+ZrxAo%B5&6p6N!_XJ)%&y3#>7bH2CseJmxIQ{f6ZfEOx7H8;M-1jEm^1EMj|0@sTrM9%BJeS*px{pm~wo>z7+gT5$ zqhpL4L}PLhgOD3MH+_HL;vSb69l@cO<2SReH zvM{CGgvz9pt%k4-D)$iVDx=`nlb%Ky$6460=)RN~;rr}-BvD#vd}c_hv;^0n9pO##-#8zHK>Ay$$R7h<6EmC-rqd=yf zO!4obK7asyISShlRMD&2hXrK+Z@9FIRvOIkmj=A{Y!EnHG=_uN!ZUk7N-<1qk2=A|=6&1!_tUYH{8z_&gBlia`kMN1rHZ=1(a z_oqEN9O8itL!IofBt8fnPsviD@4FCFu5)V|W@T*qUOa zP0VPF$MIJrtx@`wA1sI|2SL_*r=qD|H41K*xn;}FIaevi=S(-ZaB|$eM}UU`J+v(I zoK?uhQMrlB{^(@u!rG^8j2{GBfJh``k}H?{N=i2!{a*m|B>;T=ORFz_SAXP)aH*@q zU*Mpi=p%64 z!l2un$^5R%j$xvBj2e^5$P;K9jc-?QBOsTinK@wrG-rbU9|5>Un_$)A8wE+Gs;UG z#F)&g)3Ioc9NCl^e!h4o!QyLbeOLRThX;OavQPL!jlwjX-hS>hjlYh63mL-^iZ}xu zK0w2zi&#zV8fJuZ@CHHAoaZ=gd0Y}AknST9MnH%f6y+L?0ZHpZ#2#Tb2r0B+D*cFH z>Zn1PqhA0w5kl3ExKYj{pTxx>YnxozSe|}TAj8TiTCKhy2VvYOacQmOY2H4wT*Ty2 z9p|Pe1VKq&D+KYv0m1-ES(^iXd0N=aj-Tb@nxefYdow4Vty60`qweEo%nvOWGa(df z-P*w>^Z~1}4zCOJ3p(Ac?7K7ms@C-divv!^?kxE!M*5D^D|1OTi$YfNdGs@SK{-2H!r&FWK${@_C z3(aDvm4mJ52O-t_$hBv^u65zdLVJFa?%m%qqoPddJ!zB4VE>4m{BMl(#muUpLbod} zGI(aY*x&}|7YQ{?aSHsH%*{1vdZ67-nTMx>#AL*6YpHyuEWv8MTNK(ah7YTA^0v)E zWcU}@pggj#rMnYc zvO4lI7KWI z<>~K{&pBoH6Qi2Q8o6ol$6WU_`I^p|Jxt-}8liV_VZQ0TAor2pN6hE#)5b@5hcL`@ zv);$h;$)YWg+(gH*>0YJY^Ui_vA*S(Bxz|i)A~*VM+8)EYkk3RhaO(8^wkZ+((p_f z=qbI#3wXw-m7feoKvjyOur92yL5f4~6x(!y=1Q8`H?O~Mg;M_WLgMuoG;n|V?sNw7 z21L*`71A{$%jWhXV0F9dlg3D>Qvusu9?E2!QTXDP!W846=}vbjQmzB-*Za zVvRM^Pl3zVXrU!t#31OMf~v<_h!ZOo)!VPwc6!O=#+4pA#7RQ;n+q;z?AvN;IM@ms z+F>-UwPK+=MDzf6v-N2&`1;S!f!>b@_06Y`xtym~dGDR(l)PqX=pf+QPjvPI#UGh|CvMziMw1%dwO7$lEAc+ZC z$vx834^Qav8-#;EyWDl~G1LR6HsuxDaj_L^Q7;0nuHXiOgNmeZ9y z4EB#jy(_2VHRyf9SR>L@BPOn;{H(+yX3}F{p}_>Ou_8O(#B9`4PH%plX3-|yq-|_8 zQGt_|Zg`(8tg%%?{5tK6N6v|BWl7_fKO>Gub%OBccxqu<~&m*wZZ`ARo z3<(m{eMSBqzAZ-u^mpi3oSc}B*!m>=tn4(sysjrSJtOAPo7`D{>}Dvh%6TjPWI>Mn zUPDIM<}S*Jfu^e6_Iy~_yyQjqaY(z>FpfU6@;2J4p8a|~_0?a)l2gZ?Q$dkluG7>w zxYs8UQ!Defv{cj1t%A62rqYNY2qbW)@Am^kvzL=Pk)LWi07~EUeWVc=T|u>cnPF%t(`x`eTA_c3h@V zfVs&inkHyImp87LS&69^?DKK+uG`kr?#4OVM#ZgAY*mzymRrK$2V%317Nmf zW6o&1c+Z;MSqPicl!pCM;jGMO=#iOF|1Uew+DEoS>@q zu3@^d>}-;=a;8I+;$j^TKpm-(X6#Zbr~I{@c@c7e0IM@MRXhOAN**&k*T7LH&YGUYkz}Fx+>F3VD>d4BL zUp%x<+5d&%D+GUsJacQN>h((a<9U%&Pg9yD>i&<*AFHOD3D}p#I%Q@IIl|RbH;i~$ zAfN~WQA1YIQ!*7x6UJ7BqIeWa*WxA&LQLIKv)B=1q0K<=b>6Yqx>1L6?6lA7`3G=; zVzADiAvy(}2e@6|Kpp{s62H2Uu(hE|pv#c?!@Q&Pu|by4wFnC#@zJ@oUY>BSJtGC3 zA;}f=9$Gih*~qsk-`z2XDQIY*c21!G}VIi-L6qhSso7sv167?Hud}8 z0n7S1*l@)UmkvmX?HCl9EOU?mgB2wjWuCzE_;o#lE-VVYO3~(p~*H zU4x}tk%&BQ5Nv8;HXly0ypJnTDEG-MdIev!C7G76y-kF)a^t3x461tuCE&**2o=Pu zhjWzbvTQUQ6n^!K&Vu$i-SSSGHqJdr$Pd{1e97s)OJc;lIxf* zU)sK3`s%!lbLXVt;NrD>RCd4l+H^e@?JZ~CUgxg8r)-9^u_Zv$unTiC=C~c^Cjr_< zW_qQpFbkcI)9uT#J3;RFeD+8~S-dtj3#SztFree&u>a#|GJrh;CLXy+)CFZEf9i+R z&0NoUsPHfID`bHE{#+ST&#HPT5iyLa zK1KBH%ybuhaa2nfh+hPEYh~ZN>*rL+J{!Tf6TW~yD;RxjUr0%4J$GPDr#h8bG8g%mj#qq}C3cTo8x{Ie^Jk%4NML4>P+hapLUTvjW zCAky80CMItrz4HcCLTO3H6y6(RDLZ+1|P~sw2E@Ab}H-FbTBNdAi6?9kpP&@ZEhgD zr;DY(E7AR;mp$4X3z0T{7?*txA6t1Ybu5B_NmWf^XsI>$H_NNLys%_UzW0cf&8Ob! zN?JeMDuLLWW!Pn;d za@BQXB`5U@Sv%X=|KfCv!o{hXfL)~-_5C3^p~Rmv_d>!9Xrb&40T2r1 zM}GK%K^jIkr=#40PMkQKy%8es^+YLUfq(RNvQ~Y_Qd&09{(w*GJv=^jPX`h`tW%ykV|e(0}NB^?fGkt+Nz0dRDg#B`l) z&^p`keUlJ!R;*VMMe;QO!6uO$s!_Um`iCwmHbT<>qNjN>s-6X8hM6T?x4Mz`E%Gl^ zn>a?&{Nr_dOH393_sr}sOx0KxGcP0#q^0Ly5)}GibR7!HMQmti7#Db$FtES84{ zn|}#No-%fPmN_Ud_%4&mO6(`l(rW2iD2~P|r5`f=pY&*HAitmvdpZrYATlI%`-qwU>ZzD{gsH9T zwv2#zu8L3qk#^#5ZPR%%Akk^GNK)wf`p#-4i>quF)=|$G+)GNW3@JOO=kYnAT=XVw zlD#?|s8YGqxweII5x_UPcwdV2n$pY&j`c8wA0@Rx|$owA$h!&}4J#||b; zkSjEMLWtHoLM+9rh={Op&c9H4$)9#DJOv`>w;PlvpEt@cKQ=Gl-s=7~If!H`hij4s zI-PURF0Ve>%MJa z7UcD?7{n>{l2KuS-}wqd+~Y3%PcQ3_-8Dih*QCJl001V6_Po~!i|N!(n*++QfH7qf zn7V}_jmS0v6#*Qq%zWjg|t;+n$|=`>{g1)}IZ2-+OE_k+xcMF&zMSzeI4?#%OSf zKNRLsLldQr2i}%D{pdl;Qs%KXA8Z)k>8}Lz-XNZQ?a7Z0O-QY9P{k(n`!<5jt?MZ3Zmp3nB zcx#`MBj=e_J%9p?H%y;=&&OOOF2!McyrEJ81N*!93*4-(IWpyBW7D`l0Mp8(YMni7d&l5Q5J3!lm-5@ zhh+^D5sov1@wEhbqOLtLbbiZa#`8BeX*`m|T+qG*n=r6c8p+?}QB=~|dR>9uJ~XbR zrjsDxrBX;?4yq^(!{U9Iv=v9hF2e6i;Ra=J^M$HgKpgK-Hs9-B&e}}JH3p}MqV8cv5K9jE9Sy)KF@)XD90f($RAI`37HTk1mYN9bg>iy=MVEoN`Mq( zh!Oavf<1KBAd-qU3fguPeE9Y;T;{c8T<={ww&AJ+e8X3z*A}CpYOOhJJZci8C=v8( zWXTp6Dko{kPZW8g`OW$e@DU^By+<1ihZUt74dJn9d7lqX+FM~lKZ9BYNiQkt!U~W1 z08Zq1bo5_nI*=a*zSH$HXagDwp^ z6qx5^o3yMf5H3;a;J8s$lpI{B9WLr^WVT*cp&CPCZX;{9S{GvESW0ITUVK)ZlftndNF04Dojrjkr^9qJz2lI)_utIG%${{NV zyCjSPwRBpsMj|-p2w*nl1=YV0)<_xzOfU9>V1?jpY#BYQse982vLg-=`% z9y=|cK9*e|Y#Tj*xp0CqxaqG-O;J--&eQHC{8ksA*DdVPI!n~0&%xL^uP0K@b1YFP zk{%&vte6YHq|PVg&sf+L?-F+Xcch1JvC z)+Z+K;v>)t;?|l}b&u7$vaZJ|t zr?i~+2`z=k~b4uTve$#B8HmA8D z*DCM5az@p=MOfhDo!7N=jwVhyJCOI0Qtv~GqVq;IlL$udWns$2rI~)?sq?bqW3qLu zyxjXCh?W20x;RD6v;OlU=X2y!L$|KA@H)l!zCPVkafuc!-1l~A3y%<6&s#U=wXYZn zf3xup+jH;U)%R*+Nj>GegVpEM$9jY`XB52b#}47!%1IESx6kFjUyFb+Q@HTazvMIR zd%t*Mq4zcqC!fRH-MGc0Rd2L;YWi(TsXp`cXS ztZBtJiX!fpM-d6apYOF@*m~9OaEuO55Hn8@`LI>ep~4ehW3`HV)eF$>IrrajIm68L zsb@}BK};lVS7HiK;<4!VFysqr6jo;3|Gema+g!i`+t+NhNcC>wQ(9s_wYe5>2H#a%jYKY*j;PP5xN@oU{bpT9in5x#1Z0~;vZrc__5`pQ_dDN{b6(mDxiAm7T-=`bGQZ&F_&TXOSH4$(f-r;5Ie9+w;^I=paQx6l zE~<4Qo^ZG*CZ(Pc0V(`}o*}d&uwapqfvaYvh}K%)UYV*#9n3PAJXo3}_q&!kdn--b z`#jFf?AZC6ke83^)+Bzpba1|Ylh?JNAo40tdgtZ>YI)ewk(Q!08_s5HxubJqn21%7 zzOGGUv+a;YucD)6U0WThxw&L44Q+dz0#%KHGShyw&2sXecxL9Uy%89q?A0v0^1kRm zB*X34iDM-4B#-nz$$NBNgtzqn5(E&R%UT}>xfyL0{X39E7k^S`JiR_O4Xjj_tT*g6 zA}w#2+k;i2kus3x2Ql8>L0vHm&zM)D84`*eg5qDe<5X zyO@kbNDhP;)mW+$eoVT831egXnrDR5wzr&=!lLVm$p8D}dIwl<*l)>CIMj5x(XqbS z+1}+dE|T0Yv=?T|ht7R_XWpI)X~XLlve-1J_hFi&t-KKE^@eJh)!=zGT@?`GQdwxk zh4&WZdxL6O@vwp^$M@Pd(wKO5eE#?NTtiw?R@vvol&@_NmCOEQJ4_jRNeN{4JJQ`v zYW+TU1NER<`8)K>1L0p!yR=_sWJc0z>z@bB*}W#%D+NWNiu8X-fI;}Cr5IxWrMlRl zBXY+|(&~79m)hHyUmNLDn2`cv!G_EeM1gr*$&FxKi!#)~OQnvke{1EYqJ&gZ*F_;V znu=rH5jlO@7BIKjnt-$Y(S0@OhdNco4HJ!d)0)!tQe32;NE1o6n!d_ISQ9XX2|tKS z8FIlO_JTY++xZICzTS2tN4`3Y8QyTVtV!N5vD4pNJwa&-XxUp9Zmx0|BCgnExn`zm z1NRO_nK7-_dj4u-qz)}Zd=Cp|HW!Z4`wgwm9VvG|uM4C08OxFjfac8x3f_4jaUWl> z*imxOHc&lmxEzhiJh_c!t{**}V~B9iSN!MDs5Uc^@!|c2X}Z27Z5Y%jo~xv_GgLF7 z*skCHNkA2bAoq)K{K)El&EI?S<0j;Hab5j-1^>*>VXe{Xtf~3IXcqPP-fKbU=|5GB=c_EKN3xQCe(0`b)d@ zL<`u?MB41o8a5TB)~M!;MlK5SETuG<)TyuATOS7>`e?`|?(x*c=In2ev=Z`_K(LJj|l;J!KvSrI;MOO2*cL_aWR`?I#?4rli2kbL+XzbmA|Fr{^aKaLL^k| zBXV0-4-I}OEtzk?5MLKH8BTRQ&2D|DuQh@FPM!Jz$|!ZA}3=2^3Z) z`-{_8J2k-xyclI$7&8M3Vphu9#AdM>og~vZTlQwE2EjA&G|4`!h=Z?qo>B9k=`n28v$z zfee~@WhluXYKgbUwYg3XMR!qO4LU$A31v~7jqTok;kyqPmRma=A4}r-Dt6E9I#H$t z3XH14km|-|bA;yY;pN@LxUciy#2R7#Wh*ZY%kPFF55Hyqr7J<4ffOZkGh|;S!ZvbE z*Sy8Om2`f$-0&))n)z>FQ7QPJrrQpgNals>g*UrD3?^|%EjP8=%+9vfdtSzcJF&YI zf4-G}J}EehpwKgd;{MkM-rC^&Z_SJaEtsU#yJ5+rx$nKhATrJNZflBlIUdzHf|$@u z@aqEI*yx4fluF;;rq^+KaIS5FHkFi(>sd`ESMT>qkvl-|l9qFj(78)tC@1<#&&9N8 z0KUP|?M-%0QLfEzcmSaQ=-jPAY5RSo$O7Wl4(|@8!NrdhEV8wsJyDC)FM5gzOfWW{ zy`g_GAnY5TBy!5yvfq}cN*L5jvA~`d^*;6aQgoN!yWG5b z?SvhVvPpAZia&4W9dk6exf4}fiUMQ9KZ{y<%T5?R{pvuUFdiyQfFCGw&)95D7E-RSEV?J6T!`M7tjN!@+l$ zGI^U6xzB;1J~W{*No2H!ub@n&37k@S-cugw4~TLJ=WX_Vi9fS7lcNY87-AyGF&CC} zo?4xn;b4fi&SCCvba)fCle$`@Q2D{2pw_qvF$97ZVw0mcU&C0Sv)x`molF@xxHR6z z{!E;#xve}9I~tdMsE_3xowj-nwbq~4^>G?`WzMgNnRa*~;BLa-nqm{?53cXeeEV== zAOMDRGi|AU#GCU!c<>5U6jkQf*d{jWKe<5tw;^)8pWBkX8|_Gs_}_Q95CO_!a-ptnJq(QhQy%5l<&j~B35-b`FFjvtTP=TI-eG>=m%Z#6_SzR{or5uFWQ9{Od) zAy8z`%5G8Ao-Fb^hj@hKJi7i4LGC@9{gB-W4@fIqE-@8;t@+qxtQoFY3a`*|8xGdM zQS}q!#vDx3l5VC|L>84a%R!ZlvsEL9mrO7m3YtQczA`MZ-45z>Ct)|;vn_uS>$gUS z+%A4|w5BQ26CjYcl@QGfO6^W+%r@{t)TmRU^aq9Q-k#7P5P)8k)|2vf(#eL%c5($T zg;0zmeWby`dH&Q6FJ=*mBnka6th5r0S!#Mtph-1FE0z7oc6Eh$O#*dDC}95O3iG$( zNX~sR6D{Wr76GH!0^pXT4iuj?VLF4bHe{pP2vD&C*x$;kjfXwi!C4)1PzitZmz4-N zr2n{3|I$irkfQ|gr-(`_M(pmlR?!0R7u?~8Go}0W0&d^ANqKwHe(oaADqSOkoUfDY zk}U)bbzMH_q~RfLs0MrbAUI#;&%x$DuWXDD1#1_2j%E0ao4;bIY1n3VU?)aXr*9;? zv(fI;Z(tT)2}drIm#z*Y+Nc)QHcd3l5#rdMiqD+Dd1ergtC<<6mC-=`Jm;7A%N1I) zdop&E;Uv5+_+4lYmB@XX1f;&h77D+F0RS$${?Vwzmm$X`$3r{@_zFb8aqOxhg;H-0F8puynE@5b zrc@&`5i=4w(@v2seKops!XPapx`czZ8)msVe6xF)HSz(8MLRnBiy#5#vA>=DpXv-O z$mW3;#IQ1kUex#u2&Gg24=#)n)EWouh!4*G1tiyZ6{H6`_2)qYStu2&TOwCWMqC zOQR9QF~h`(neIXSjpix*-&%rTh^;Y0J?=_`1mikV=;GmOgM)AI!-oAk`?I0G9Hjb& zDtn16^pFQ48Wt#}p;0@b19R=@&B<2oMl0~sk zs3n4(Ah!#H2=f28lPds*=;3gl^Lo<+X+GHFQXcR z3`_I1d7Jzxn^17EQICD1Bd*P|IK+xHXvQuESLzAO)uWkGyf#LV;G{Q>t?Y|tO#-|A z{C}tBr%MM~B&5&jZf5;HOviUqA`i3#qcZlvYROWc6?_H)XaV+SWO!=O0z&tE zgUplwdyRSMmQ&1e$>Bw&S8lI9(96yt*iL^~oeh$0;7flfmC^uZu&`8qg!{~bKx|Zq zqxO^>R_U6Het3p^4MO71qJ9Bf%~%%q-$@ow7)bC|!W2fucM@Ln#J6Y{E75XB{m*}H zdLx8W3`5^w`~pU^CwDVvcIIU`y3n7N*6eb_^@ePb81*#q{J>dwJ-YHTLF@1sCcD)PXr$7;PiMFRYa z4hGTlkBl?K%mOkcO-w5r*?pTxqw4*M*1V*~)-Vo10))AurNK^CbL_REp|cbezi<*o zFC{W6&bCyz;$;wlLO)fL*Eyva+M0B%@;d|4BTUIW-*Jw0^7R6dQX?su$_?_slu%Z~ z4iTKoPks<`;s1Rz8ybqssB^oA+qDSRb;#iz+gqubHMkOD8)&+pJ95%@@J&x{D6AxA zY!A7VOgTjz9;)z5kU7?UFcJG&<7$)Qc;s?vdlTPK<@aTZNoifu#4Fy5aUb>QU67^> z>{Y|4{ zj5#aYfX547k4RC;IC|`^4>OI@S8EWM4ITpYa9Nkys)tL6R)wTR;_~sl0$>u+oXA5Z z;D`znCk^={&xjf!fkJt!YUC}rJh)1b0R63W!~?@(NLfo7=Wn*BKxA?>y?fH(0EU(OU+)vDibCVAaA~Z}7P@kV5$11Hb$b+n7Mf0U~ zGbf);;Y#WdAZh}klv{XqWkHiM3-}~R8IctY%EV!U6Cn>Z1Y%!)ITptfx+(>z+053$ z{xr-BHV&QV-j{tWbGN!|@+Qv3uGOQ zN(=zA2LJ@VHqbmLd?bd}`mH@vQ-yRqOO{B7UKVar5Wx%oXLYgT^%|2 zOV%{!CfD#B6Nn!l8M#|I%QwF zB#K+jPdn&5lZBlM%T&1DM(4#k!-FGIiXm2KevRzULyZKXgiQl1NN`HD8)zVBPvHg& z5Tykww*WPWw1;j}?bfU9Ipdc3qTr(F_Q0r<$pGn!MM(!Um#9lIKNG`4F#5=XcJp!3!|$ zgVN%JI(qujit{xA;w z`>!VMOvnD=)D?Y7;K^U=A&wpfn-I}mtQ}shhZ@Q;7(|=qFhb$bl5aDji2#HKxS1@r zX)v^uN&%1vEgJWPzz|owy(4q6Q2Dtxeb@y`xlw@XzCKL-R}OVJO?k55yg0u z;9)9_o@}Lq9$F0iCRk86`rU{5B!L7a>FO)!&JvWRat$BA2C|+QiCeP{S$BTSvIj!G z%Mcx4GDUYq=Zc^SIw{Y9vOBg}QbJt+_f7{n@keZI9uz+Yo^c!Ut zmq^ng{NiD2e5-oI#jv@O`#{$&RrVUe3gGCq4N-}TgQ6`d!efoX<>%IBR*1N2;WLEq zRO_SK#wu+Rlmw9g6SyLzsDio5*$hFji-^5tGeM*Fx&8ABgEj4J)9T4yKCjLB@+yNN zP+%&*B#MWP#^2}+MsPqP;3pch0$q}Vq-uOw+v)SWeJ&jh9awzq!K*ugq*7~RR$J@i zdi5oDNb7s*F6c$!mrZnhaIU`-t9MzocSTugvgd3h*V({BZ8$^ZDpP^s0=c>S* zmsi>66WhHV1cz18Vo6yNYFPn2J2h6y(wwX3YZWRoe=C~`6AG2LGJ{oj97mf z%Y3$p#kES4uZ*Ept1!i*NAK<^>L3t1K>&X7c{vCJpgS{(hy%0O3}j1XWz&Kqxw<9n)9`Tk0G9)nUKriV7CO zD0hGfOff|!X0Mtw+F~Ayt&PJJ(^X+YYiEL{#(gr?%>@KuzC5MHjMyl7$viN9{%o~F zi&3%j#a?8E0{r@L85DK{^NvCm>1*0uO&*+Yw&yL&s0^;eZi-HC0qbj8X_nm#Zkaj} zhhwp$kd#So8S8&1F68=46+k}YqDmN#_T(@HntB6 z_wf*Pl;~UOzRbD{4V8pdUMKTR-A(H2cNc`BE!oZnClP}$<_7@&h9LI{Zkeole4m>x zur{Bxa*pR3Tcq<7oqplgu3>D3av}TfLdgHio?=YqQ096RtQ+rZTX@HM#1tmL10in4 zCH*{#WW%V~b+w6sE?}jtCg#n3v40{#cw~pB$mdkMm^Y2KELMe~z10^64k@9J)ZLB> zOlqsF6}2fOWoK4P9U}Wk z^*t=F)pC0hl0jG1wh@}!z`|)50)j!|{CJ;^f%&#Ai_6b z`Jzp-7B5_hOvd&8uNMH3%gbw(I$hm4D+7N|Q+gv~wp=sxMmF)H!!QD8O(!8an>|sV z^?n_d#sEHaPTxCREgzd^RjC{)CU<}OF`~*@TnTPP`&`Ov`Z>Bb&h*iuQ+kI9Z#V1n zB9MQJwQHEr0Q}9}U*rKZs4I|0vma!`z@?Gsv8dGVnNk~(C2{R6e?(~$;BY17 z)KlZDovFK3!$GhVAt#@Zw75#%ii^=eB|>fz!KL#adZ{luf{Q^Z&jrhM)k;o!y@xNA z3QPUYlMK6W=ps!{7fYC(Ul4@LD26JCF%Y)}6HYb9%;Pra`$ zz*PPg1U+v1=5H{e6^^K3sLIm}CpJqpv`#27zB_ttf7NB-6|U8qlNNtY&%KguFg&D0V_Z64UhAQ67&%nP87Ti`qMw12n8fT5$|boXl0kaVO<6`7mmJYO z3mLp5i%;`In+zJV%1zyo*RjCJqO$=il0g86OX_B?`=+z-&6wh&+5l5#uc<>gCvT@o zpp0#d4o!VM@!~ZlGkX%cT(TW|>}(@4xDZekG+}Li?-uDp4Mh1=-)aEjMHb9%2tlIO zQ~gM8U85lj+c(ZeCHAy2O{8~sNKur!Gns}XnQOAY*_VGiR$PSttNmRHOz0(RFHg1n z43c{~na>m#5s&+wW*Qg45a7}lPfGY!gGcEBuOvP^)9azR4wa_ajATrBU7u1oe-LJ!sCZAFIMR~Za59BKewxQsiA1_EYD;)HsG5~nKKIE*IVWEyk$Hrm zsV#1-u+Z`zqoy+ArrqmRX*w`&ZKO z{i*}Xbbf(m521f6uTjgXP_>pNj+F5UHq1|3SKQFZ&035Us}EgZOY~I7O|>PbWAtI*6j5QB!mFyE3vKl#?2|b{>-u!`RvCVmK0YLf zCI;>zpMc%%Ghr9jJqQ}iDX>P(bmL;Nt~yzMQvoJr+&DCBwq0%0PmoV5FPDUXVH)mW zEkEu%E;P})cGS*PP&s))5RYRlA46!~K@`ik#F}3mGr)4zTshx8Z{U05&E)LInupPrI9kfUYHKlAB&EKT)M8MJYl-oVbYsZfPn}FSe1s9s z+?yQn=|q!bwta2)s?!k8W$yA;#z>y3^KL9*Vu*tg?o#Dvotc#-s}K_kr;FG7T(wd4 z)O}t;AKj44FgjD0&bDHWs)T1} zC19>%>QnF8tZgU5&Ccr%LgNTRI-?H`MsSYZuT=^WII)Br%I{)kb(E8*nAdrDyW-7l5WCtNXv9hOymNdP*C^8f}&xPq`i&86v^yNP>Q=+Ca(x7)CEa z{eIsuY6`i7pk0R3f7H@!YhEkz|)#4Q6-5L zoOU7I1UMJ1?F)X|Ko@bphjan39CXA4ROLx&IN{kNzpm!hX~p|B!mxl#t-=+iCd3mLtn5+-Cm@ z7tpZW@C_U8nim*Eh7ig$@;+09a~O44s*iPsB*~q`J}xGGLz~g3%i6sZbIH2v>Ht0* z!`PSR^JzA!X>A<`uPU&mG|Lsigi`Y$-WbtEn}(U3;) zeQdk>aXyGd*cG&EZDw&33=0Cwxdp4%GFsWUt83P%Yzc_+^L8A5K3k3P@t91LJ8_sY zPPcsOBop&pCV>{4&au~*YITv(TxWqilnE}wy1UWMV=0dk&=hBWq%k@4l5HtQLo)RA zFV3yF7GOay{Q@B@gJ+MOf7j>yFzjb-p5GUA=6?N;@AP5V(_xM%Z zSV{amjgd^d*gbT}9_=DC;$1R@a0jnhi2C=G0+gcm1VsTl3^FZ8`cz4@Nwi%?zTbKb z{r>rSiscLc8!tmGZdevD2CjnsEyar#4F($$nGP@|0ujw8IitM0 zyGzn&!kE%1K|_21F3`4$T+bzbFDD2BDz!J5>)$N$=l2!7em#%6!qHO}AOJea(XA4> zU?q|x6kuEcKSpw`s^?YY&4n4Ut$KsmLqCBNgMLFclsq;{qjqvNTLf63U!ANQ_km}e z4D}tH)(`(Amr0G9JNk39A2H{fK_*nDhJ0X+r@Asyw#nGxg(xKVMWgT!Z8whxfLmt9 zPPuFy?rGK5uBTY$yZ{2FZe^rU*(7=U)iFZwF1T<)sSD#4DfDtp6YwY>NGy8ckAkp|949Y00RGy)Mz`eEi!DZVB_qhPoR7BstYTDn|}}dss8B! z;$ufmF_;QAkpZ)soy#_Kc?U8zK4bEyqOOI0648u?NR(pzk$|%y`w(VrZ#+4_`WoTD z(YLn@3_jBmob=#HqdmC7)I!N6^AIIsQh`^$epz`pFht916DadR>s`81QpHq}%m?q{ zJ)hi-w*yx~=#!}^0N5unYk!>_zJ?hK7(WZRI_?AGL^>%$5J>H9soof;YCDiuL6DVuiSbP`0ok9m?%L# z{T&e>G;3IP`C}DfLCVAh2!XHCf#A&wV2G7KM(Gt0lt68UFTq>}O`BLZa>L$;^EJE5 z6~8dn;t>Ork0qo|>)wh zEiD?$HOGJS37G@8h8oaNdMJJRW+hfSkcTIKTuLRyFbf|GI!Eh#o#0-fFXHP?zb?6e zn#apTl$ZxuXpoHP9_Ycp8iJNT1$@$6fHF@hYJ@N;xH|OTX7}9xht&pr)O~I7-<-we zh@~ziyJ)MF5RFuR{(kFaSv!#m4G-ToF)z125xDr7((_nKDpl2s#qg(M7Ac*CPK~oj zmIPCxAWE5-Z2G_US<+rQ7Ple)U16jnPt#8>n9bip+%oOx@|b7P5o2Y?cM(%%NqbpS zz7RkhnK#8{WjDV+V#|*R6?*+x+6Uh~R!kEZ4P7{gV~bweZ~iJ82VcNCNubN{b_74L zH6P)+VytgG5J$aqjQxjxc)$Hl0XZ2fCL?REJv2z-`98u!2OMeMR1k%M&GgPe1AKrq z;WZ7=dk3D9P(^(`QOS@X2BmG?LXiH=`FL+4n`ogRxsAd2G+|J*TX^Z#1jX;4&dq}$ zBozM0!HC6Z9>GA!+qdS{i`zBI3XyWKhQ_1C7sY1*Y(#xn!hxJ4KN+GQrF^1~HBAnI z`!xK#?2|_rU#t<;yRX+%LVplJndoe7A$;b=RI2eF z((&$ZK0Scg?VBzSdWS5WFaPe(p5pK9?69@`T|C+tG4D?ayodgeCNjI&{E6-g4>L#y zW;?)k5>9f>QV3AMa)omw_Q^i@1>&P7Rt#_?rm)QYZzlxjds`>s#?w4&m-akPX6vGb zchRx_=B9b82AA)@64sLWH)=)pidDZfTviEQ4pd(_U&}PQii#==F5p-$U!wuox8XG6 zl4})WVKuhQrO%ZM=OrT|jleU>BckONaax63L&|8D#Q^4x{2O;0z7!R0OqyrtB5WqJ zCBsF|mhn_o>Ex6C4BT7}+#D(0m`tC1xcflutHypeUB=^~!nZlBjoX!6143MTR({8u zqu~TBQ{{&3=f_*2g_V^D!J?N7rir?%$DU5LUyF*8dF}Lke0+q+qa>(IO-(10ejkk} z-E1iNZF*;Pn_OS6iH?trjg5{C-FiO<-^$;TWTTM29u9V(% z!&~RNGu3*z!lv(YJlYva(yV9a;cgwvXzE|g!m6Z-lox0$5Po6wM_O@)~r`k-n zECi7#@+|~3CGM}L+O-@a1*OSce(Ac+#`{!nj1;u)cal(e|9Kx9;`4VQ*thkjv~f4= zhmqTiL)ZFH4mM(gw5;XaYFGLshvI7e!vj|PF_dvrpDuOu&jT% z_%tvu&?BnK^LSHT->0Uul$9V{RZUGXg?nSly6pJk3caz>_hP4Z53GGH!%t6NKSMST zZr*gh`6c)vQOxXWyt*CqhRqBmy)f-MTV<7>#Y zMyt`{8td_V<)ZziE+shMeh*Vw-){34^)lb{je<~%lkLeIsmPu=j@a%QkF~Utk`gX+ zq!9R;8J0Pnjg^(>Mqc9eew5!=?ONp4v0@Ut;Z(IHP=u}Rr!m!xyQHWv#h=w2QpnlCPRYnum0YkxTd&aQL>Kd@upd};6-k+9)hbeO4=(KGx;yye36t7oD>%^vzDFJXT8O;t z-_UP#-2v1RJ-eNksOJgr74mcInB4DjAFS+C*AlV}sG+jP1}LiWcuae1Yb%XqY5%V@ zh0oEDG=;8H|BG>5x3sE$fX|JY+WdU8n_DxzY^~c8bd$9R-z(3ku2_;Wq2pgF$Wy&C zUgAipoT1ZszZ)RDCIFtQ)SBw*U!$W%#pmB2uyJ#9FWWY`?=N;bD5|K|l$YCg z$IzBFpJ;VKyRA1Dtt7<60OS-V6tI~(2NE8eBw$e|lW8w|&LnU{JHGLxExku4*JyID zsB&P8*Qzq>PkCW2amEJTsSKtx~yw?j!1p)eaW>*!7}^V z1{dqGuk`@>zGuDqW)v*Mku7JdseU&X^Yh+^3boT|KA(YAiJnXtPYQQT3MVjY9u$aP zS*{OerOm6yb}t7$)k}hvZga8fw_F~N>tmzcMwTILXSLMTdEItrfJb1zm|EQbTWQ*x zz@mM!2Jn?+{Ha^SjGMUm>SVfV=cRYHv1=S9yxIZ7doF)JP;7K`)M>TPVNI2eWx)`F@(kN$1&}mx-VG4jXcdm2Z+&?^1^egV--~jYX% zQ#K%i=F_>0>G|tb8dDTpfF|HDqlzRv%2~%=7Qh?d4hKYAoi~QLkbECQ6a_YN)%849 zvcp*&5#9h?0BT(kL{&gvo12^QOzLy4(=dr6z*~USkdyh9mBVNG#>n`saJClv4^dSE zA{QtthQW3YR(dl$H;Ynj8@eYAfc_QFT_;TwS#%q!8ye2ru|=D%&$rSBm%+dKyxPEx zd0xD@x;|~yuP~ByaXALe1Xt%YN5bWwi%S3lWd$d6USF`Vu++Ob_iTk&oF8pWk^nvs zw3}-J6JHut*Don5I@{8}X&84KI?H!r)@%B^`K3$`VWSCdXI<9p3QQk&2uMui=F*7@ zm{W0S>GcYm-$ql?=Ilv^aj9N&LrKXFkXunniEe}Q#`a`Y+L%-E#Pv~uD1Ys|%eXG` zfiDog<9uUm3rcoOYG2*=fN;GJ`#Fp|JB%u97{_J48T^^Q-kG-rh?NW{;Agv72&Q(h4CNW!}g%Fv9@*^u9b;> z4scW6(9n>dzk1qw(>%TfXpE{?h>2kMm59{8?hzgWn0@z3HsH&>Ao{yH@eh(RM z*DZZ&+${bQLLV=?GgHsyrLUPl0hU6I>@xm6u?; z1A==(R9f|pdHI%;)!7XVDIvSyMmW?`kE3a15_KFyxz>+Y67^kqm{?eJD^0p@uJ>-z zU8gKBJXDlK&S-Gb96E_k=e+g?v&8w=L^AAvd+s80gIB#*$>cTejL^2N|Iz*C()R|S zVyCIUi^31Qbk@`bypLdkLh35>TLGhv`*g%|dCGRp#{@VE=|YpqEjvJyz0HX#F+7&T zxx>T5Gt&cnOA*rK9=X}s-&Y(#EYRE%uE?W127;-w)@#oK8>{7?(Y&+Hv4@)8$D``O zRl&gFlEu;~t-{eie$%WnGaV191JDJY8*S|b7?~g7N{J&tNalOsT&uGT3FjV$0v%u# zij9v*c(%bQHY_-*>gsC==`NiL;Ko0msPjc`AA1f69m<~Jx zSR2S_^*CHDJ!j6Wn&m>p%w-K`zXEl_}vjr znW6pN5Dog2>4fB`b3+tu)B4QysZIWRMi9QUHe{md+NlLBp1-ylfxvz1;o-6SQ;^Px zI(yr{2lz+6RQ}0V33!z@6Xld%a(txZ+!lkrdg9Mxi@toJ&SomDGrA$UT&5H4z8D48 zATI&pT|ng?tpZzLs~r89>FN1yE)PT30Vh49Le?Kz)Zf)iFy$YGDf;M?f&l8w|AwtL zRv~f4#7Q$Oo3CViznL_y>1_hi*t2;qR!M~a^h8BTI6kCOrPneA;kY78mBR02wrdt2 zAFmjK82`PUxx8bkiASeM3O@GR#lm3Tv}2y(v)Yun&lJ6 zXR~|0Ps4uz!tp(+${<53Ci#Q`-z+3pIqCoa5SN%}uD`tCM=x!dpV z6c-EBuEhs!G_)A~2m{K4%{B3pM{PBvlOzo7E~A3btgLULPeKaw^E)xHD#LvRDBr56 zjIh+s#CW;U>4i$=B$&!57bFBDSFunrrXKG^gUgpg1wDCj2}wywY`CG$X7{QUd1hNgWT_l` zc0r1^Je!l7+rgmM3+Ul__V!kH<1G8bwXU7%TEY3IlPPNtW6HFLrv}EaC_4ZdRv5Mi zfk7tdYLS8{lU|=dOd@)Cez?v1Nb^&tIFSLDCQ%KzX@wHwRifBtki_C(2A+5}auqw@5U^++fmF~$t^)o8?SQdL>JzD^ zni@f*l=(oq4=^}2H8l_ow1CA(I1foaHRXC?UaiX*f4-g*b#ktgZ|TbEZ*61K*OK0B z`?(~!r-Zqek%{R`S=qpsFN}$UL(9zu1hSmimPhTl^Ih&yCwtdSP$YoBC-@9I$e${Wi1`$jzSq*Wh1bp&T zU}`HkzN;JkwBb;Y`okfkFgPIyMBW|)!l#di038H9B5_R;G<+WG@O`owNwwA#xc!lI z)%;dkFscxwuKzArsHZzetebLds-$E(#iji_XA$(-DehSe&8m|e$WA}eAN<7anFAQ^ zCiC-r0|+Ti$fz@d=&7#rTS>{l*cUS((NX+U-B{J3=e`g~Vq0f;b$*OoR{N9zthx2? z54K8CfrTLu0iPdc+<2EMjh`Kprah4i{mCBj$<#Vdg&}{dp?9U{qJ*nkSC4tv0ZxrW z8!OXOR7#Z%_?AVpa(SnA9u+Gpj5tp%bctT?XVrMnrLwwR%vE`rsARQX?bvx;Z3`eS zTex4m(DmA>DSBrGSXuO9a^RETF5lXSW}W??Xl*kH!lROrc=c{!&-b)Z+^fGm7+b%} zOu@^`t4p7s+OK`Vem4>ZgH0aG&$};0K47hDV~7)6PGi?rPSI`VPUL#Wv-g7Z;QvI2 ztz!_}G|Z6morS?^b_}t@(qQ<2WYiwIbNRE)mBrEXU1)8#glc);ZRm&%#GI@+3dx)+ zD9uIsRz5M*OqS0tDe2!j)&pU_ZLQaz-`@Zcc`VUN1JLQ}l7;^`srqU{XPPJoY}I=1 zq5db3L~iqqG`%g{07vfr?qyOA|6xSd)n+?Y9eP6g$9*xBE+H-s#4Es9)*E((6z9IW zqUUibj0>8k(ni1DHYYSQ|1~z2ugp+3egV+5Mqp+pT~Gc2Fqqq%M;B2@N*6tSi zb~oVw;nE6zx3Ls^wbwt7feXcp9^cyG>$E>RSg98^<0M+!cUr^gW&KfZ(v54*{|mV3 z8+*(Vfe~l)A4P&rB8|3B?xVpUAF}zK^%{!AagA$hYHHe^n`w^ST+QE*9T!R@xIL-&T7in0UbN0q&@ z0!+HdR9-f>Y8x1=gsbmQ!&d2*#>^3@0+`XQj=Nj|s5;%lMy9-Aa^o^@+W-}t1Xf*H ziID;&QMeRfd@_7jMIqA({ud-~kBJU9TPBV&xzJ29=7Q zdfM1-p|j=Kt?@F^<#8U@Z4J7B=8N6NG^yv=&H_I3KBu6>_nO9WA@HdHFQ~8_gIx1j zwzgff%df(>zype+co*bqljx|3k4`E5vXjKl*_TcTL`SKOa)*x>G7u0!Q?swH|qc{$gy|s9I=;2HP8G}-sk$J%L zHE)35aXqgcP%&bwUycFh3gD5S|C}zO5oF_0qSx+JAZLzLp{jM>P%iMdDDwp$rD_Ed zU{Wt_0t5lVKGBC1BHrymj{tx5C2=^dA^)ucnJ#>h6-?d$qE6Jq-{wfcCM$np;{=Et*uyu7hPnQj17iXO>N14v~ z8(2LcHiR$KKGm`R7RR81FPRKNp*gA&-4#$8Nac501-i`eIhh20B(JhE#)&*qD((0Y zYtx_aievZEy$`yP)-6ZFa!%7XfJp|d6ObzSEBjp(tp`0!qE|;F;E~qDxiVqd-XKL$ zEc85`brZxR)=D@|oxgk@FALJz?jjgXHlUs@P}QnmOHT#$9AIU7IyxXVOk~y+ldx;r zm*V*>{992&OY0nX7+r@SPXGz&gAxXyrPS9n@TppWmaUT7#b-`vtc$3stPj31H{ZH| zi?e&r&_dwbP4l^x!;Ma92(jlCD@vV3{LF4Fo1R^9S?M=0K2>y58D zTvtJ%zbBpvjJ5t$H&6^%wN=yB_5w3E-*W_wBXE$YDDDp}{s>0}B-_eSAUyH_Prp9a zzZu;+2BjCE;R%@zMgEmIJVm~5lg|ay61YrUMA@#f{rx(^5D6|q6ZwHLJxwS3EFAcl1Skn);02*cM%WzovLO0ST-vn@XzeP1cD6MAn~bip;s0T%#1oV?xnadnH2YJZps>n}ObzP88 z#)Ep`4~|RU*2Ca8s;Vw;1Q#YMj6s3p2QV@d&g9KatF?MFB)lG!A%LlrUNL8?qzSwa z!+`u-lrY63=?`>eZ_` z;*=qacO%tdVPT3b-%U8{jk~bS7Xq7YGq*g&#l>Z0y7k8WP{jbVfplTt%fBFQjSk^V z?nA()F&{iQtLNQ3GT^JMT4>cNbcsgs7dYV@WnvQ z&RrApQk=g?`Q z=o`i9S#GY8#b{V64H1@ii6hY;5rbqav-#R}v{)4xp@k}r>OD~gK;+V>$;-=IA`1@> zKYj9Wr9{c|_^r$h4e3=E4 zzR=?i#P`gcs0+{|s*pF5uRtQ``n7k(%GOq|-f^YL;}8^h0c$>2gim14ZG=5x3b6oE zQaqamCdO+s!Ol(ga{dD$5zn7S`3dA1rt4ip2Z6Y1G=z0{^KB2y`Ul+F3NU8I$SzI&i^2k)iAMFq%W0Do56k* zr#bDbyzCAYVl|GTe|v~GduUWZ!GsoQ(Xen0v6}`CGP!kR-8=XrsDT7D{v6ONdt_<7 z%|4&y%>K8xd+Ro8#|T;>y_)7+?bKMEPO}uRtWtA~o6z2y=r0CBw8l-Oh0MF?xA$f$#GDdMtNhui_1?dY#6MpYAP7ac+Y+rQZn z`6$RzI<-bYVI!Ysq*Ln=bkRn{(J91Q#Jo+cx6E+F^uK33dkPQ>O;DIySZQ~j+nP5} zme!ccPd|Uf=LKyOTNS_lN>lid?dTy!BQ3JL3AGmuZRc%;$brvIu0Ny;ot`xF9*_Hk zSWmIlBuv4)Q+3!8!qrI#ni zhJIX$+iyva_OM2F8RiI$C4D_CBv@GNFLeIB9RNWB;~5=y@j&P4?X_h}8yuzNp*jR@ zx{PH4ExW*zb6xdN9CP{Auk4u*ne4F{?7i@pZBa9{|J*FALw5^oX*(im_Fbuo$Fo_q zm{vnSw#*4r6--3ZbjphL42B5sEuYYn)}sIeks4v+B}#(^7+TlS4h*nqKV$}T(y>+` zB9JN&qu6}1gPhgjWW%Cwr`n}7tfiuKLv8i@x9?gl7T*W^+i)ja%YKb%ys49bv0qk% z36BXgF`LWjBqWf_wi0@Y?}%NoF_~y@J#|EZ4ZJ~MOII2IS_Lln)(CrX1!Fv!i|ph~ zv_^OcVE)wex2ANrSLJtkXqJg_)$SB8OW6KTSDE1#8FdhlVR|%6_;mCjg6>#DjCV)Z z)Pd%{IraL9A~p_3!AzvLt>x>V!hG9|7!4-KH`}Oo7-)9-sU#@Gg6-AqYokf3vEseg zgwwcM+MP4HPlIW&8XC%~1f7@Q@n~_Bl;Z~Y;2j095s&RAivwWZhO_*xEzjB7{L9dU z6%T^Ags7cBqA=p4`0mWM>=>bO?(3#Owx{T`lD_qWW%fa+K6lWufa~)P4Rloul9(_p z4F}a!DcD)PFfQ?=GM#KhFIx1zi@rXxUFz)A+eDvf)iQ2lU5=n*ArH|weMP`Th+1q46C5>{jhb`Y!(T1a>ZvQ#y_C1y=ZU` z)1BsB!@p(k?ew6y!(W6(o3wyP&8d+{DY<{gvuRd#Yktik;B~3r(OcK6S|*33h`?s$ zi2{7jmD{aM>(RN}CUzFU!hwEQ(zO3AUOE)cMl=#un+!l0&AGHAC0pA{Q5Y6adj6)B zhE6$_2Ges{ECcuCb+o_WcD!S7JQb?t8-y6YhW;(#O6mljFx;O|J(^M(W0^y1jL2h| zo#6@>XLK{Uppf8yJ5{=OS&aYZwP6%4>JQSR*SKq5Hvf2FP+G7J)uNGkgX85(_d3^E zFq#wBQZSk))-s0$Mud17uM>5`-;$9T$stAl6x(AtQyRl7FH;)Jlg7hs24U^R4H}3* zw_qxg9Yqm@e3dFcF>rTM;$Lf)STaR(XmOcIJ>5xJv7tTK`{-Wk)Qs5^)eoO~k<3-b z=%FjL9xYyse?(7N_VqQHESLru=ho^(C_Z~APo~179a7C2LquJoy^~_mPiWb0E2m8~ zg6wpi+(G}l;WWUJ+jt$#U&IRfpx;Si7FBlj`>=Kw$QJiG@zTDlMJ=%H<(4Ql&=d4C zmL<+f-6KjS-ZCqgjkURo#4aeVB8@`t=0by-o@qn;e1m_OXXqny#{3TtS16 zv}+|gtC^YhkTz_`PDY}lWSy4Rkmw#QtrL^{s*Vds)c4uS+yP$M5 zqzx}Gf+xAm+PPu>))RaSCGh`^m%NLhzvaK-zl&<$`fp^Cpq$2kV_gb`n*T-v4oV*X l8~(Pa_N@QL|Ce96p}=>0)S2' }} - {%- elif value is string or value is number %} + {% elif value is string or value is number %} {{shift}}{{ key }} {{ value }} {%- endif %} {%- endmacro -%} diff --git a/pillar.example b/pillar.example index 59a723ea..dc27e445 100644 --- a/pillar.example +++ b/pillar.example @@ -2,6 +2,17 @@ # vim: ft=yaml --- iscsi: + + {%- if grains.os_family in ('Debian',) %} + target: + pkgs: + wanted: + - infiniband-diags + - ibutils + - ibverbs-utils + - rdmacm-utils perftest + {%- endif %} + config: data: open-iscsi: From 4f3789604f15004be1b49cc6b7fe3c3d73a8e116 Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 22:14:31 +0100 Subject: [PATCH 08/42] fix(suse): service is called targetcli on Suse15 --- iscsi/osfamilymap.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index b31ee8a6..bb75d1ef 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -64,6 +64,7 @@ Suse: config: servicename: open-iscsi: iscsid + lio: targetcli ### might need to add suse to oscodename.yaml if SLES support is needed isns: pkgs: wanted: From ff54b33f66e45f49c3657b1a61825ae73318e5fc Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 22:16:48 +0100 Subject: [PATCH 09/42] docs(suse): update pillar example packages --- pillar.example | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pillar.example b/pillar.example index dc27e445..ed565da5 100644 --- a/pillar.example +++ b/pillar.example @@ -3,14 +3,15 @@ --- iscsi: - {%- if grains.os_family in ('Debian',) %} + {%- if grains.os_family in ('Debian', 'Suse',) %} target: pkgs: wanted: - infiniband-diags - ibutils - ibverbs-utils - - rdmacm-utils perftest + - rdmacm-utils + - perftest {%- endif %} config: From 4b09216d6b497e4805c475c2b815889bd820ed44 Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 22:31:32 +0100 Subject: [PATCH 10/42] docs(archlinux): add user to pillar.example --- pillar.example | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/pillar.example b/pillar.example index ed565da5..029cbacf 100644 --- a/pillar.example +++ b/pillar.example @@ -1,16 +1,30 @@ # -*- coding: utf-8 -*- # vim: ft=yaml --- -iscsi: + {% if grains.os_family in ('Arch',) %} +users: + iscsimake: + sudouser: True + shell: /bin/bash + empty_password: True + home: /home/iscsimake + createhome: True + optional_groups: + - wheel + - root + sudo_rules: + - 'ALL=(ALL) ALL' + {% endif %} +iscsi: {%- if grains.os_family in ('Debian', 'Suse',) %} target: pkgs: wanted: - infiniband-diags - ibutils - - ibverbs-utils - - rdmacm-utils + # ibverbs-utils #not suse + # rdmacm-utils #not suse - perftest {%- endif %} From ea82c992548659e1e5bebbe15b1f4279329fb041 Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 22:59:54 +0100 Subject: [PATCH 11/42] fix(archlinux): update osfamilymap --- iscsi/osfamilymap.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index bb75d1ef..488d4000 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -117,15 +117,13 @@ Arch: target: pkgs: wanted: + - linux-lts # For kernel scsi modules - python-pip # makepkg states needs 'gitpython'. - thin-provisioning-tools - - linux-lts # For kernel scsi modules make: gitrepo: https://aur.archlinux.org cmd: makepkg -si --noconfirm -f wanted: - # rdma-core - # tgt-rdma - python-rtslib-fb - python-configshell-fb - targetcli-fb From 8d9d9591136cf9b594db0dab310c12a076a03370 Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 23:27:07 +0100 Subject: [PATCH 12/42] fix(jinja): fix error while parsing a flow --- iscsi/initiator/config/install.sls | 2 +- iscsi/isns/config/install.sls | 2 +- iscsi/target/config/install.sls | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iscsi/initiator/config/install.sls b/iscsi/initiator/config/install.sls index fa486d0c..e2bd4b01 100644 --- a/iscsi/initiator/config/install.sls +++ b/iscsi/initiator/config/install.sls @@ -12,7 +12,7 @@ include: iscsi-initiator-config-install-file-managed: file.managed: - - onlyif: {{ iscsi.config.data[iscsi.initiator.provider|string] }} + - onlyif: {{ iscsi.config.data[iscsi.initiator.provider|string]|json }} - name: {{ iscsi.config.name[iscsi.initiator.provider] }} - source: {{ files_switch([iscsi.initiator.provider ~ '.tmpl'], lookup='iscsi-initiator-config-install-file-managed', diff --git a/iscsi/isns/config/install.sls b/iscsi/isns/config/install.sls index 9029eef2..dfc3f14b 100644 --- a/iscsi/isns/config/install.sls +++ b/iscsi/isns/config/install.sls @@ -12,7 +12,7 @@ include: iscsi-isns-config-install-file-managed-isnsd: file.managed: - - onlyif: {{ iscsi.config.data[iscsi.isns.provider|string] }} + - onlyif: {{ iscsi.config.data[iscsi.isns.provider|string]|json }} - name: {{ iscsi.config.name['isns'] }} - source: {{ files_switch([iscsi.isns.provider ~ '.tmpl'], lookup='iscsi-isns-config-install-file-managed', diff --git a/iscsi/target/config/install.sls b/iscsi/target/config/install.sls index e13827a0..d830422e 100644 --- a/iscsi/target/config/install.sls +++ b/iscsi/target/config/install.sls @@ -12,7 +12,7 @@ include: iscsi-target-config-install-file-managed: file.managed: - - onlyif: {{ iscsi.config.data[iscsi.target.provider|string] }} + - onlyif: {{ iscsi.config.data[iscsi.target.provider|string]|json }} - name: {{ iscsi.config.name[iscsi.target.provider] }} - source: {{ files_switch([iscsi.target.provider ~ '.tmpl'], lookup='iscsi-target-config-install-file-managed', From 5de113d92b8ed019100f9ebee7e1de0f77533f8f Mon Sep 17 00:00:00 2001 From: N Date: Tue, 17 Sep 2019 23:41:14 +0100 Subject: [PATCH 13/42] fix(saltbug): fix for strange 'unless: ' bug --- iscsi/initiator/make/install.sls | 4 +++- iscsi/target/make/install.sls | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/iscsi/initiator/make/install.sls b/iscsi/initiator/make/install.sls index 2adf48fe..f82958bd 100644 --- a/iscsi/initiator/make/install.sls +++ b/iscsi/initiator/make/install.sls @@ -22,7 +22,7 @@ iscsi-initiator-make-file-directory: iscsi-initiator-make-{{ pkg }}-git-latest: git.latest: - - onlyif: {{ iscsi.initiator.make.gitrepo }} + - onlyif: {{ iscsi.initiator.make.gitrepo != None }} - name: {{ iscsi.initiator.make.gitrepo }}/{{ pkg }}.git - initiator: /home/{{ iscsi.user }}/{{ pkg }} - user: {{ iscsi.user }} @@ -45,6 +45,8 @@ iscsi-initiator-make-{{ pkg }}-cmd-run: - cwd: /home/{{ iscsi.user }}/{{ pkg }} - name: {{ iscsi.initiator.make.cmd }} - runas: {{ iscsi.user }} + - onchanges: + - git: iscsi-initiator-make-{{ pkg }}-git-latest - require: - git: iscsi-initiator-make-{{ pkg }}-git-latest - require_in: diff --git a/iscsi/target/make/install.sls b/iscsi/target/make/install.sls index 87057c92..d3ff87da 100644 --- a/iscsi/target/make/install.sls +++ b/iscsi/target/make/install.sls @@ -22,7 +22,7 @@ iscsi-target-make-file-directory: iscsi-target-make-{{ pkg }}-git-latest: git.latest: - - onlyif: {{ iscsi.target.make.gitrepo }} + - onlyif: {{ iscsi.target.make.gitrepo != None }} - name: {{ iscsi.target.make.gitrepo }}/{{ pkg }}.git - target: /home/{{ iscsi.user }}/{{ pkg }} - user: {{ iscsi.user }} @@ -45,6 +45,8 @@ iscsi-target-make-{{ pkg }}-cmd-run: - cwd: /home/{{ iscsi.user }}/{{ pkg }} - name: {{ iscsi.target.make.cmd }} - runas: {{ iscsi.user }} + - onchanges: + - git: iscsi-target-make-{{ pkg }}-git-latest - require: - git: iscsi-target-make-{{ pkg }}-git-latest - require_in: From 1ef79ec2c7bddc0091e0fd476dff19f2f976d03f Mon Sep 17 00:00:00 2001 From: N Date: Wed, 18 Sep 2019 00:14:51 +0100 Subject: [PATCH 14/42] fix(freebsd): required changes for freebsd 11.2 --- docs/README.rst | 14 +++++++------- iscsi/initiator/service/clean.sls | 2 +- iscsi/initiator/service/install.sls | 11 +++++------ iscsi/target/service/clean.sls | 2 +- iscsi/target/service/install.sls | 11 +++++------ 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/docs/README.rst b/docs/README.rst index bdc98fe9..afc7dc45 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -16,13 +16,6 @@ iscsi-formula Configure iSCSI targets and initiator on GNU/Linux and FreeBSD. -Technology ----------- -.. image:: docs/link-transport-storage-protocols.png - :target: https://github.com/saltstack-formulas/iscsi-formula - :scale: 25 % - :alt: Link/Transport and Storage protocol infographic - .. contents:: **Table of Contents** General notes @@ -46,6 +39,13 @@ Contributing to this repo Please see :ref:`How to contribute ` for more details. +Scope +----- +.. image:: docs/link-transport-storage-protocols.png + :target: https://github.com/saltstack-formulas/iscsi-formula + :scale: 25 % + :alt: Link/Transport and Storage protocol infographic + Available states ---------------- diff --git a/iscsi/initiator/service/clean.sls b/iscsi/initiator/service/clean.sls index 065a557e..213e8045 100644 --- a/iscsi/initiator/service/clean.sls +++ b/iscsi/initiator/service/clean.sls @@ -18,7 +18,7 @@ iscsi-initiator-service-clean-service-dead iscsi-initiator-service-install-file-line-freebsd: file.line: - - onlyif: {{ grains.os == 'FreeBSD' }} + - onlyif: {{ grains.os_family == 'FreeBSD' }} - name: {{ iscsi.config.name.modprobe }} - content: 'ctld_env="-u"' - mode: delete diff --git a/iscsi/initiator/service/install.sls b/iscsi/initiator/service/install.sls index 51523a97..9f8154f4 100644 --- a/iscsi/initiator/service/install.sls +++ b/iscsi/initiator/service/install.sls @@ -9,19 +9,18 @@ include: - {{ sls_config_install }} - {%- if grains.os == 'FreeBSD' %} + {%- if grains.os_family == 'FreeBSD' %} iscsi-initiator-service-install-file-line-freebsd: file.line: - name: {{ iscsi.config.name.modprobe }} - content: 'ctld_env="-u"' - backup: True - {%- if not iscsi.initiator.enabled %} - - mode: delete - {%- else %} + {%- if iscsi.initiator.enabled %} - mode: ensure - - after: 'sshd_enable.*' - - create: True + - after: 'autoboot_delay.*' + {%- else %} + - mode: delete {%- endif %} {%- endif %} diff --git a/iscsi/target/service/clean.sls b/iscsi/target/service/clean.sls index 8dde7e3b..50d8f598 100644 --- a/iscsi/target/service/clean.sls +++ b/iscsi/target/service/clean.sls @@ -18,7 +18,7 @@ iscsi-target-service-clean-service-dead iscsi-target-service-install-file-line-freebsd: file.line: - - onlyif: {{ grains.os == 'FreeBSD' }} + - onlyif: {{ grains.os_family == 'FreeBSD' }} - name: {{ iscsi.config.name.modprobe }} - content: 'ctld_env="-u"' - mode: delete diff --git a/iscsi/target/service/install.sls b/iscsi/target/service/install.sls index 9eb64669..2098ed24 100644 --- a/iscsi/target/service/install.sls +++ b/iscsi/target/service/install.sls @@ -9,18 +9,17 @@ include: - {{ sls_config_install }} - {%- if grains.os == 'FreeBSD' %} + {%- if grains.os_family == 'FreeBSD' %} iscsi-target-service-install-file-line-freebsd: file.line: - name: {{ iscsi.config.name.modprobe }} - content: 'ctld_env="-u"' - backup: True - {%- if not iscsi.target.enabled %} - - mode: delete - {%- else %} + {%- if iscsi.target.enabled %} - mode: ensure - - after: 'sshd_enable.*' - - create: True + - after: 'autoboot_delay.*' + {%- else %} + - mode: delete {%- endif %} {%- endif %} From 8dfc99336908c2be32aa4aeccd81334232904359 Mon Sep 17 00:00:00 2001 From: N Date: Wed, 18 Sep 2019 00:32:37 +0100 Subject: [PATCH 15/42] fix(isns): fix jinja in config file template --- iscsi/isns/config/files/default/isns.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iscsi/isns/config/files/default/isns.tmpl b/iscsi/isns/config/files/default/isns.tmpl index 4062a824..0f985f0d 100644 --- a/iscsi/isns/config/files/default/isns.tmpl +++ b/iscsi/isns/config/files/default/isns.tmpl @@ -3,7 +3,7 @@ # Your changes will be overwritten. ######################################################################## -{% if data and component and provider -%} +{% if data and component -%} {%- macro readconf(outdict, spaces=0) -%} {%- for key, value in outdict.items() -%} @@ -16,6 +16,6 @@ {{shift}}{{ key }} = {{ value }} {%- endmacro -%} -{{ readconf(data['isns'], 0) }} +{{ readconf(data, 0) }} {% endif -%} From 118a2d628f16921efe68ebf7934d71796d88f119 Mon Sep 17 00:00:00 2001 From: N Date: Wed, 18 Sep 2019 13:26:27 +0100 Subject: [PATCH 16/42] docs(unused): removing depreciated docs/ files --- docs/_static/css/custom.css | 21 ----- docs/conf.py | 173 ------------------------------------ docs/index.rst | 20 ----- 3 files changed, 214 deletions(-) delete mode 100644 docs/_static/css/custom.css delete mode 100644 docs/conf.py delete mode 100644 docs/index.rst diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css deleted file mode 100644 index 4617efcd..00000000 --- a/docs/_static/css/custom.css +++ /dev/null @@ -1,21 +0,0 @@ -/* - Override styles for in-use Sphinx theme -*/ - -/* The next two `.wy`-based rules are specifically needed for the dealing with */ -/* the `sphinx_rtd_theme` bug where long lines do not wrap in tables */ - -/* override table width restrictions */ -.wy-table-responsive table th -, .wy-table-responsive table td -{ - /* !important prevents the common CSS stylesheets from - overriding this as on RTD they are loaded after this stylesheet */ - white-space: normal !important; -} - -.wy-table-responsive -{ - overflow: visible !important; -} - diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 1525f9fd..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,173 +0,0 @@ -# -*- coding: utf-8 -*- -"""Configuration file for the Sphinx documentation builder. - -This file does only contain a selection of the most common options. For a -full list see the documentation: - -* http://www.sphinx-doc.org/en/stable/config - -""" - -from __future__ import division, print_function, unicode_literals - -# from datetime import datetime - -from recommonmark.parser import CommonMarkParser - -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -__author__ = 'Imran Iqbal' # noqa: E221 -__copyright__ = 'Copyright (C) 2019, MYII' # noqa: E221 -__license__ = 'Apache-2.0' # noqa: E221 -__version__ = 'latest' # noqa: E221 -__maintainer__ = 'Imran Iqbal' # noqa: E221 - - -# -- Project information ----------------------------------------------------- - -project = 'iscsi-formula' -copyright = __copyright__.replace('Copyright (C) ', '') # noqa: A001 -author = __author__ -version = __version__ -release = __version__ - - -# -- 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 = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['templates', '_templates', '.templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -source_suffix = ['.rst', '.md'] - -# The master toctree document. -master_doc = 'index' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - - -# -- Options for the reStructuredText parser --------------------------------- - -file_insertion_enabled = 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 = 'sphinx_rtd_theme' - -# 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 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'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'iscsi-formula' - - -# -- Options for Markdown output --------------------------------------------- - -source_parsers = { - '.md': CommonMarkParser, -} - - -# -- 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': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# 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', - 'iscsi-formula.tex', - u'iscsi-formula Documentation', - u'', - 'manual', - ), -] - - -# -- Functions: `setup`, docstring preprocessing, etc. ----------------------- - -def setup(app): - """Prepare the Sphinx application object. - - Used for providing a custom CSS file for override styles. - - Parameters - ---------- - app : object - The Sphinx application object. - - Returns - ------- - app : object - The Sphinx application object. - - """ - app.add_stylesheet('css/custom.css') - return app diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 128fa941..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. _index: - -.. ``iscsi-formula`` documentation master file. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to iscsi-formula's documentation! -============================================ - -.. toctree:: - :maxdepth: 1 - :caption: Contents - :numbered: - :glob: - - README - CONTRIBUTING - TOFS_pattern - AUTHORS - CHANGELOG From 55e4cfbab3e4d20cb7ccb63de9c219d0a13528ab Mon Sep 17 00:00:00 2001 From: N Date: Wed, 18 Sep 2019 17:56:32 +0100 Subject: [PATCH 17/42] fix(os): better os mapping --- iscsi/oscodename.yaml | 28 +++++++++++++++++++++----- iscsi/osfamilymap.yaml | 45 +++++++++++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/iscsi/oscodename.yaml b/iscsi/oscodename.yaml index f23c22ca..20244153 100644 --- a/iscsi/oscodename.yaml +++ b/iscsi/oscodename.yaml @@ -7,6 +7,8 @@ pkgs: {%- if name in ('wheezy', 'trusty',) %} wanted: ['isns', 'isns-client'] + {%- elif name in ('stretch',) %} + wanted: ['libisns0',] {%- elif name in ('xenial', 'jessie') %} wanted: [] {%- else %} @@ -16,10 +18,10 @@ pkgs: {%- if name in ('wheezy', 'trusty',) %} wanted: ['python-configshell', 'python-rtslib', 'targetcli', 'tgt-glusterfs', 'tgt-rbd'] - {%- elif name in ('xenial',) %} - wanted: ['python-configshell', 'python-rtslib', 'targetcli'] {%- elif name in ('jessie',) %} wanted: ['tgt-glusterfs', 'tgt-rbd',] + {%- elif name in ('xenial',) %} + wanted: ['python-configshell', 'tgt', 'python-rtslib', 'targetcli'] {%- else %} wanted: ['python-configshell-fb', 'tgt', 'tgt-rbd', 'python-rtslib-fb', 'targetcli-fb', 'iscsiuio'] {%- endif %} @@ -27,18 +29,32 @@ {% macro fedora_codename(name, release, codename) %} {{ codename|default(name, true) }}: - {%- if release in (26, 27,) %} + {%- if release > 26 %} isns: pkgs: wanted: - isns-utils - isns-utils-libs - target-isns + initiator: + pkgs: + wanted: + - iscsi-initiator-utils #cent6/cent7/fedora/amazon + - iscsi-initiator-utils-iscsiuio #cent7/fedora + - libiscsi #cent7/fedora + - libiscsi-utils #cent7/fedora target: pkgs: wanted: - - targetcli - - fcoe-utils + - device-mapper-persistent-data #cent6/cent7/fedora/amazon + - netbsd-iscsi #cent6/cent7/fedora + - yum-plugin-versionlock #cent6/cent7/fedora + - targetcli #cent7/fedora + - libvirt-daemon-driver-storage-iscsi #cent7/fedora + - udisks2-iscsi #cent7/fedora + - scsi-target-utils #cent6/fedora + - fcoe-utils #cent6/fedora + - libvirt-daemon-driver-storage-iscsi-direct #fedora {%- endif %} {% endmacro %} @@ -65,6 +81,8 @@ ## Fedora # `oscodename` grain has long distro name +{{ fedora_codename('Fedora-30', 30, 'Fedora 30 (Thirty)') }} +{{ fedora_codename('Fedora-29', 29, 'Fedora 29 (Twenty Nine)') }} {{ fedora_codename('Fedora-28', 28, 'Fedora 28 (Twenty Eight)') }} {{ fedora_codename('Fedora-27', 27, 'Fedora 27 (Twenty Seven)') }} {{ fedora_codename('Fedora-26', 26, 'Fedora 26 (Twenty Six)') }} diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index 488d4000..4d5ecb0b 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -33,38 +33,56 @@ Debian: RedHat: config: + name: + tgtd: /etc/tgt/targets.conf #cent6 servicename: open-iscsi: iscsi isns: isnsd + tgtd: tgtd #cent6 isns: pkgs: wanted: - isns-utils - yum-plugin-versionlock + # target-isns #fedora29 initiator: pkgs: wanted: - - iscsi-initiator-utils - - iscsi-initiator-utils-iscsiuio - - libiscsi - - libiscsi-utils + - iscsi-initiator-utils #cent6/cent7/fedora/amazon + {%- if grains.osmajorrelease >= 7 %} + - iscsi-initiator-utils-iscsiuio #cent7/fedora + - libiscsi #cent7/fedora + - libiscsi-utils #cent7/fedora + {%- endif %} target: pkgs: wanted: - - libvirt-daemon-driver-storage-iscsi - - netbsd-iscsi - - udisks2-iscsi - - yum-plugin-versionlock - - targetcli - - device-mapper-persistent-data - # scsi-target-utils - # fcoe-target-utils + - device-mapper-persistent-data #cent6/cent7/fedora/amazon + {%- if grains.osmajorrelease == 6 %} + - scsi-target-utils #cent6/fedora + - fcoe-utils #cent6/fedora + - fcoe-target-utils #cent6 + + {%- elif grains.osmajorrelease >= 7 %} + - yum-plugin-versionlock #cent6/cent7/fedora + - targetcli #cent7/fedora + - libvirt-daemon-driver-storage-iscsi #cent7/fedora + + {%- if grains.os not in ('Amazon',) %} + - netbsd-iscsi #cent6/cent7/fedora + - udisks2-iscsi #cent7/fedora + {%- endif %} + {%- endif %} Suse: config: servicename: open-iscsi: iscsid - lio: targetcli ### might need to add suse to oscodename.yaml if SLES support is needed + {%- if grains.osmajorrelease >= 15 %} + lio: targetcli + {%- else %} + lio: target + {%- endif %} isns: pkgs: wanted: @@ -72,7 +90,6 @@ Suse: initiator: pkgs: wanted: - # libopen-iscsiusr0_2_0 - open-iscsi - libiscsi8 - librdmacm1 From 828f82740d1895041e25928871d946bb49e55a2e Mon Sep 17 00:00:00 2001 From: N Date: Wed, 18 Sep 2019 18:54:56 +0100 Subject: [PATCH 18/42] test(travis): add travis yml --- .travis.yml | 86 ++++++++++++++++++++++++++++++++++++++++++ .yamllint | 30 +++++++++++++++ iscsi/osfamilymap.yaml | 46 ++++++++++------------ kitchen.yml | 32 ++-------------- 4 files changed, 140 insertions(+), 54 deletions(-) create mode 100644 .travis.yml create mode 100644 .yamllint diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..af0ae18c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +dist: bionic +stages: + - test + - lint + - name: release + if: branch = master AND type != pull_request + +sudo: required +cache: bundler +language: ruby + +services: + - docker + +# Make sure the instances listed below match up with +# the `platforms` defined in `kitchen.yml` +env: + matrix: + - INSTANCE: default-debian-10-develop-py3 + # - INSTANCE: default-ubuntu-1804-develop-py3 + # - INSTANCE: default-centos-7-develop-py3 + # - INSTANCE: default-fedora-30-develop-py3 + # - INSTANCE: default-opensuse-leap-15-develop-py3 + # - INSTANCE: default-amazonlinux-2-develop-py2 + # - INSTANCE: default-debian-9-2019-2-py3 + - INSTANCE: default-ubuntu-1804-2019-2-py3 + # - INSTANCE: default-centos-7-2019-2-py3 + # - INSTANCE: default-fedora-30-2019-2-py3 + # - INSTANCE: default-opensuse-leap-15-2019-2-py3 + - INSTANCE: default-amazonlinux-2-2019-2-py2 + # - INSTANCE: default-debian-9-2018-3-py2 + # - INSTANCE: default-ubuntu-1604-2018-3-py2 + # - INSTANCE: default-centos-7-2018-3-py2 + - INSTANCE: default-fedora-29-2018-3-py2 + - INSTANCE: default-opensuse-leap-15-2018-3-py2 + # - INSTANCE: default-amazonlinux-2-2018-3-py2 + # - INSTANCE: default-debian-8-2017-7-py2 + # - INSTANCE: default-ubuntu-1604-2017-7-py2 + - INSTANCE: default-centos-6-2017-7-py2 + # - INSTANCE: default-fedora-29-2017-7-py2 + # - INSTANCE: default-opensuse-leap-15-2017-7-py2 + # - INSTANCE: default-amazonlinux-2-2017-7-py2 + +script: + - bin/kitchen verify ${INSTANCE} + +jobs: + include: + # Define the `lint` stage (runs `yamllint` and `commitlint`) + - stage: lint + language: node_js + node_js: lts/* + before_install: skip + script: + # Install and run `yamllint` + # Need at least `v1.17.0` for the `yaml-files` setting + - pip install --user yamllint>=1.17.0 + - yamllint -s . + # Install and run `commitlint` + - npm install @commitlint/config-conventional -D + - npm install @commitlint/travis-cli -D + - commitlint-travis + # Define the release stage that runs `semantic-release` + - stage: release + language: node_js + node_js: lts/* + before_install: skip + script: + # Update `AUTHORS.md` + - export MAINTAINER_TOKEN=${GH_TOKEN} + - go get github.com/myii/maintainer + - maintainer contributor + + # Install all dependencies required for `semantic-release` + - npm install @semantic-release/changelog@3 -D + - npm install @semantic-release/exec@3 -D + - npm install @semantic-release/git@7 -D + deploy: + provider: script + skip_cleanup: true + script: + # Run `semantic-release` + - npx semantic-release@15 diff --git a/.yamllint b/.yamllint new file mode 100644 index 00000000..a8509bc8 --- /dev/null +++ b/.yamllint @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +# Extend the `default` configuration provided by `yamllint` +extends: default + +# Files to ignore completely +# 1. All YAML files under directory `node_modules/`, introduced during the Travis run +# 2. Any SLS files under directory `test/`, which are actually state files +ignore: | + node_modules/ + test/**/states/**/*.sls + +yaml-files: + # Default settings + - '*.yaml' + - '*.yml' + - .yamllint + # SaltStack Formulas additional settings + - '*.example' + - test/**/*.sls + +rules: + empty-values: + forbid-in-block-mappings: true + forbid-in-flow-mappings: true + line-length: + # Increase from default of `80` + # Based on https://github.com/PyCQA/flake8-bugbear#opinionated-warnings (`B950`) + max: 88 diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index 4d5ecb0b..0643e9c6 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -34,43 +34,41 @@ Debian: RedHat: config: name: - tgtd: /etc/tgt/targets.conf #cent6 + tgtd: /etc/tgt/targets.conf #cent6 servicename: open-iscsi: iscsi isns: isnsd - tgtd: tgtd #cent6 + tgtd: tgtd #cent6 isns: pkgs: wanted: - isns-utils - yum-plugin-versionlock - # target-isns #fedora29 + # target-isns initiator: pkgs: wanted: - - iscsi-initiator-utils #cent6/cent7/fedora/amazon - {%- if grains.osmajorrelease >= 7 %} - - iscsi-initiator-utils-iscsiuio #cent7/fedora - - libiscsi #cent7/fedora - - libiscsi-utils #cent7/fedora + - iscsi-initiator-utils + {%- if 'osmajorrrelease' in grains and grains.osmajorrelease >= 7 %} + - iscsi-initiator-utils-iscsiuio + - libiscsi + - libiscsi-utils {%- endif %} target: pkgs: wanted: - - device-mapper-persistent-data #cent6/cent7/fedora/amazon - {%- if grains.osmajorrelease == 6 %} - - scsi-target-utils #cent6/fedora - - fcoe-utils #cent6/fedora - - fcoe-target-utils #cent6 - - {%- elif grains.osmajorrelease >= 7 %} - - yum-plugin-versionlock #cent6/cent7/fedora - - targetcli #cent7/fedora - - libvirt-daemon-driver-storage-iscsi #cent7/fedora - - {%- if grains.os not in ('Amazon',) %} - - netbsd-iscsi #cent6/cent7/fedora - - udisks2-iscsi #cent7/fedora + - device-mapper-persistent-data + {%- if 'osmajorrrelease' in grains and grains.osmajorrelease == 6 %} + - scsi-target-utils + - fcoe-utils + - fcoe-target-utils + {%- elif 'osmajorrrelease' in grains and grains.osmajorrelease >= 7 %} + - yum-plugin-versionlock + - targetcli + - libvirt-daemon-driver-storage-iscsi + {%- if 'os' in grains and grains.os not in ('Amazon',) %} + - netbsd-iscsi + - udisks2-iscsi {%- endif %} {%- endif %} @@ -78,11 +76,7 @@ Suse: config: servicename: open-iscsi: iscsid - {%- if grains.osmajorrelease >= 15 %} lio: targetcli - {%- else %} - lio: target - {%- endif %} isns: pkgs: wanted: diff --git a/kitchen.yml b/kitchen.yml index 557a86f4..44a1598b 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -135,7 +135,7 @@ provisioner: log_level: info salt_install: none require_chef: false - formula: template + formula: iscsi salt_copy_filter: - .kitchen - .git @@ -150,42 +150,18 @@ verifier: suites: - name: default - excludes: - - centos-6-2017-7-py2 provisioner: state_top: base: '*': - - template + - iscsi pillars: top.sls: base: '*': - - template - - define_roles + - iscsi pillars_from_files: - template.sls: pillar.example - define_roles.sls: test/salt/pillar/define_roles.sls - verifier: - inspec_tests: - - path: test/integration/default - - name: centos6 - includes: - - centos-6-2017-7-py2 - provisioner: - state_top: - base: - '*': - - template - pillars: - top.sls: - base: - '*': - - template - - define_roles - pillars_from_files: - template.sls: test/salt/pillar/centos6.sls - define_roles.sls: test/salt/pillar/define_roles.sls + iscsi.sls: pillar.example verifier: inspec_tests: - path: test/integration/default From ffc2dddca45eb5217f7ea92b113dfdabd237a97a Mon Sep 17 00:00:00 2001 From: N Date: Wed, 18 Sep 2019 22:27:06 +0100 Subject: [PATCH 19/42] docs(example): update pillars for travis --- iscsi/osfamilymap.yaml | 9 +++++- pillar.example | 65 ++++++++++++++++++++++++++++++++---------- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index 0643e9c6..b4d3d5d3 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -30,7 +30,11 @@ Debian: unwanted: - iscsitarget - iscsitarget-dkms - + - infiniband-diags + - ibutils + - ibverbs-utils #not suse + - rdmacm-utils #not suse + - perftest RedHat: config: name: @@ -103,6 +107,9 @@ Suse: - iscsiuio - yast2-iscsi-lio-server - qemu-block-iscsi + - infiniband-diags + - ibutils + - perftest Gentoo: target: diff --git a/pillar.example b/pillar.example index 029cbacf..d6afbd7f 100644 --- a/pillar.example +++ b/pillar.example @@ -16,18 +16,53 @@ users: - 'ALL=(ALL) ALL' {% endif %} -iscsi: - {%- if grains.os_family in ('Debian', 'Suse',) %} - target: - pkgs: - wanted: - - infiniband-diags - - ibutils - # ibverbs-utils #not suse - # rdmacm-utils #not suse - - perftest - {%- endif %} +lvm: + files: + remove: + - /tmp/loopdevs/testfile0.img + - /tmp/loopdevs/testfile1.img + - /tmp/loopdevs/testfile2.img + - /tmp/loopdevs/testfile3.img + create: + dd: + /tmp/loopdevs/testfile0.img: + options: + if: /dev/urandom + bs: 1024 + count: 204800 + /tmp/loopdevs/testfile1.img: + options: + if: /dev/urandom + bs: 1024 + count: 204800 + /tmp/loopdevs/testfile2.img: + options: + if: /dev/urandom + bs: 1024 + count: 204800 + /tmp/loopdevs/testfile3.img: + options: + if: /dev/urandom + bs: 1024 + count: 204800 + losetup: + /tmp/loopdevs/testfile0.img: {} + /tmp/loopdevs/testfile1.img: {} + /tmp/loopdevs/testfile2.img: {} + /tmp/loopdevs/testfile3.img: {} + pv: + create: + /dev/loop0: {} + /dev/loop1: {} + /dev/loop2: {} + /dev/loop3: {} + delete: + /dev/loop0: {} + /dev/loop1: {} + /dev/loop2: {} + /dev/loop3: {} +iscsi: config: data: open-iscsi: @@ -144,17 +179,17 @@ iscsi: iSNSAccessControl: Off iSNS: Off 'target iqn.2008-09.com.example:server.target1': - backing-store: /dev/LVM/somedevice + backing-store: /dev/loop0 'target iqn.2008-09.com.example:server.target5': - 'direct-store /dev/sdd': + 'direct-store /dev/loop1': vendor_id: VENDOR1 removable: 1 device-type: cd lun: 1 - 'direct-store /dev/sda': + 'direct-store /dev/loop2': vendor_id: VENDOR2 lun: 2 - 'backing-store /dev/sdb1': + 'backing-store /dev/loop3': vendor_id: back1 scsi_sn: SERIAL write-cache: on From de4b9f0ccbc31b5b0c8c0c7adcef1f834a12bde4 Mon Sep 17 00:00:00 2001 From: N Date: Wed, 18 Sep 2019 23:34:22 +0100 Subject: [PATCH 20/42] fix(tgt): fix for tgtadm: invalid request --- pillar.example | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pillar.example b/pillar.example index d6afbd7f..2143198f 100644 --- a/pillar.example +++ b/pillar.example @@ -174,10 +174,10 @@ iscsi: ##https://github.com/fujita/tgt/blob/master/conf/examples/targets.conf.example include: '/etc/tgt/salt/*.conf' default-driver: iscsi - iSNSServerIP: 127.0.0.1 - iSNSServerPort: 3205 - iSNSAccessControl: Off - iSNS: Off + #iSNSServerIP: 127.0.0.1 + #iSNSServerPort: 3205 + #iSNSAccessControl: Off + #iSNS: Off 'target iqn.2008-09.com.example:server.target1': backing-store: /dev/loop0 'target iqn.2008-09.com.example:server.target5': From fb694a07ee6ae3dbdd49a9d68d1e82acf9676a32 Mon Sep 17 00:00:00 2001 From: N Date: Wed, 18 Sep 2019 23:43:27 +0100 Subject: [PATCH 21/42] test(travis): add dependency to lvm; update pillar data --- iscsi/osfamilymap.yaml | 6 +- kitchen.yml | 39 ++ pillar.example | 5 - .../integration/default/pillar.example.amazon | 644 ++++++++++++++++++ 4 files changed, 686 insertions(+), 8 deletions(-) create mode 100644 test/integration/default/pillar.example.amazon diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index b4d3d5d3..7689e30e 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -53,7 +53,7 @@ RedHat: pkgs: wanted: - iscsi-initiator-utils - {%- if 'osmajorrrelease' in grains and grains.osmajorrelease >= 7 %} + {%- if 'osmajorrrelease' in grains and grains.osmajorrelease|int >= 7 %} - iscsi-initiator-utils-iscsiuio - libiscsi - libiscsi-utils @@ -62,11 +62,11 @@ RedHat: pkgs: wanted: - device-mapper-persistent-data - {%- if 'osmajorrrelease' in grains and grains.osmajorrelease == 6 %} + {%- if 'osmajorrrelease' in grains and grains.osmajorrelease|int == 6 %} - scsi-target-utils - fcoe-utils - fcoe-target-utils - {%- elif 'osmajorrrelease' in grains and grains.osmajorrelease >= 7 %} + {%- elif 'osmajorrrelease' in grains and grains.osmajorrelease|int >= 7 %} - yum-plugin-versionlock - targetcli - libvirt-daemon-driver-storage-iscsi diff --git a/kitchen.yml b/kitchen.yml index 44a1598b..745d1abd 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -150,10 +150,19 @@ verifier: suites: - name: default + excludes: + - amazonlinux-2-2017-7-py2 provisioner: + dependencies: + - name: lvm + repo: git + source: https://github.com/saltstack-formulas/lvm-formula.git state_top: base: '*': + - lvm.install + - lvm.files.create + - lvm.pv.create - iscsi pillars: top.sls: @@ -165,3 +174,33 @@ suites: verifier: inspec_tests: - path: test/integration/default + + - name: amazon + excludes: + - debian-8-2017-7-py2 + - ubuntu-1604-2017-7-py2 + - centos-6-2017-7-py2 + - fedora-29-2017-7-py2 + - opensuse-leap-15-2017-7-py2 + provisioner: + dependencies: + - name: lvm + repo: git + source: https://github.com/saltstack-formulas/lvm-formula.git + state_top: + base: + '*': + - lvm.install + - lvm.files.create + - lvm.pv.create + - iscsi + pillars: + top.sls: + base: + '*': + - iscsi + pillars_from_files: + iscsi.sls: test/integration/default/pillar.example.amazon + verifier: + inspec_tests: + - path: test/integration/default diff --git a/pillar.example b/pillar.example index 2143198f..54de59ed 100644 --- a/pillar.example +++ b/pillar.example @@ -56,11 +56,6 @@ lvm: /dev/loop1: {} /dev/loop2: {} /dev/loop3: {} - delete: - /dev/loop0: {} - /dev/loop1: {} - /dev/loop2: {} - /dev/loop3: {} iscsi: config: diff --git a/test/integration/default/pillar.example.amazon b/test/integration/default/pillar.example.amazon new file mode 100644 index 00000000..ac12a721 --- /dev/null +++ b/test/integration/default/pillar.example.amazon @@ -0,0 +1,644 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- + {% if grains.os_family in ('Arch',) %} +users: + iscsimake: + sudouser: True + shell: /bin/bash + empty_password: True + home: /home/iscsimake + createhome: True + optional_groups: + - wheel + - root + sudo_rules: + - 'ALL=(ALL) ALL' + {% endif %} + +lvm: + pkgs: + - lvm2 + - cryptsetup + - device-mapper-persistent-data + - thin-provisioning-tools + files: + remove: + - /tmp/loopdevs/testfile0.img + - /tmp/loopdevs/testfile1.img + - /tmp/loopdevs/testfile2.img + - /tmp/loopdevs/testfile3.img + create: + dd: + /tmp/loopdevs/testfile0.img: + options: + if: /dev/urandom + bs: 1024 + count: 204800 + /tmp/loopdevs/testfile1.img: + options: + if: /dev/urandom + bs: 1024 + count: 204800 + /tmp/loopdevs/testfile2.img: + options: + if: /dev/urandom + bs: 1024 + count: 204800 + /tmp/loopdevs/testfile3.img: + options: + if: /dev/urandom + bs: 1024 + count: 204800 + losetup: + /tmp/loopdevs/testfile0.img: {} + /tmp/loopdevs/testfile1.img: {} + /tmp/loopdevs/testfile2.img: {} + /tmp/loopdevs/testfile3.img: {} + pv: + create: + /dev/loop4: {} + /dev/loop5: {} + /dev/loop6: {} + /dev/loop7: {} + +iscsi: + config: + data: + open-iscsi: + ##https://github.com/open-iscsi/open-iscsi/blob/master/etc/iscsid.conf + node.startup: manual + node.leading_login: 'No' + node.session.timeo.replacement_timeout: 120 + node.conn[0].timeo.login_timeout: 15 + node.conn[0].timeo.logout_timeout: 15 + node.conn[0].timeo.noop_out_interval: 5 + node.conn[0].timeo.noop_out_timeout: 5 + node.session.err_timeo.abort_timeout: 15 + node.session.err_timeo.lu_reset_timeout: 30 + node.session.err_timeo.tgt_reset_timeout: 30 + node.session.initial_login_retry_max: 8 + node.session.cmds_max: 128 + node.session.queue_depth: 32 + node.session.xmit_thread_priority: -20 + node.session.iscsi.InitialR2T: 'No' + node.session.iscsi.ImmediateData: 'Yes' + node.session.iscsi.FirstBurstLength: 262144 + node.session.iscsi.MaxBurstLength: 16776192 + node.conn[0].iscsi.MaxRecvDataSegmentLength: 262144 + node.conn[0].iscsi.MaxXmitDataSegmentLength: 0 + discovery.sendtargets.iscsi.MaxRecvDataSegmentLength: 32768 + node.session.nr_sessions: 1 + node.session.iscsi.FastAbort: 'Yes' + node.session.scan: auto + + iscsi: ##freeBSD + ##https://www.freebsd.org/cgi/man.cgi?query=iscsi.conf&sektion=5&manpath=FreeBSD+10-current + node.startup: automatic + myiscsi: ##nickname + targetaddress: iscsi1 + targetname: 'iqn.1900.com.com:sn.123456' + myiscsi6: + targetaddress: '[2001:db8::de:ef]:3260' + targetname: 'iqn.1900.com.com:sn.123456' + chaptest: + targetaddress: 10.0.0.1 + targetname: 'iqn.1900.com.com:sn.123456' + initiatorname: 'iqn.2005-01.il.ac.huji.cs:nobody' + authmethod: CHAP + chapiname : 'iqn.2005-01.il.ac.huji.cs:nobody' + chapsecret: secretsecret + example01: + targetname: 'iqn.2018-07.com.example.iscsi:example01' + targetAddress: '10.10.10.10' + data: + targetname: 'naa.50015178f369f092' + targetAddress: data1.example.com + chapIName: user + chapSecret: secretsecret + secret: + targetname: 'iqn.2018-07.com.example.iscsi:secretdata' + targetAddress: creditcards.example.com + authMethod: CHAP + chapIName: 'iqn.2018-07.com.example.iscsi:trustedguy' + chapSecret: secretsecret + + ietd: + ##http://manpages.ubuntu.com/manpages/trusty/man5/ietd.conf.5.html + IncomingUser: joe secret + OutgoingUser: jack secret2 + 'Target iqn.2001-04.com.example:storage.disk2.sys1.xyz': + IncomingUser: jim othersecret + OutgoingUser: james yetanothersecret + 'Lun 0': Path=/dev/sdc,Type=fileio + 'Lun 1': Blocks=10000,BlockSize=4096,Type=nullio + Alias: Test + HeaderDigest: None + DataDigest: None + MaxConnections: 1 + MaxSessions: 0 + InitialR2T: 'Yes' + ImmediateData: 'No' + MaxRecvDataSegmentLength: 8192 + MaxXmitDataSegmentLength: 8192 + MaxBurstLength: 262144 + FirstBurstLength: 65536 + DefaultTime2Wait: 2 + DefaultTime2Retain: 0 + MaxOutstandingR2T: 8 + NOPInterval: 0 + NOPTimeout: 0 + DataPDUInOrder: 'Yes' + DataSequenceInOrder: 'Yes' + ErrorRecoveryLevel: 0 + + isnsadm: {} + isnsdd: {} + isnsd: + ##https://manpages.debian.org/testing/open-isns-server/isnsd.conf.5.en.html + Database: /var/lib/isns + RegistrationPeriod: 10m + DefaultDiscoveryDomain: 1 + SLPRegister: 1 + ClientKeyStore: 'DB:' + SCNTimeout: 60 + SCNRetries: 3 + ESIMinInterval: 1m + ESIMaxInterval: 2m + ESIRetries: 3 + Auth.ReplayWindow: 2m + Auth.TimeStampJitter: 1s + + tgtd: + ##https://linux.die.net/man/5/targets.conf + ##https://github.com/fujita/tgt/blob/master/conf/examples/targets.conf.example + include: '/etc/tgt/salt/*.conf' + default-driver: iscsi + #iSNSServerIP: 127.0.0.1 + #iSNSServerPort: 3205 + #iSNSAccessControl: Off + #iSNS: Off + 'target iqn.2008-09.com.example:server.target1': + backing-store: /dev/loop4 + 'target iqn.2008-09.com.example:server.target5': + 'direct-store /dev/loop5': + vendor_id: VENDOR1 + removable: 1 + device-type: cd + lun: 1 + 'direct-store /dev/loop6': + vendor_id: VENDOR2 + lun: 2 + 'backing-store /dev/loop7': + vendor_id: back1 + scsi_sn: SERIAL + write-cache: on + + lio: + ##https://www.systutorials.com/docs/linux/man/5-saveconfig.json + ##https://bugzilla.redhat.com/show_bug.cgi?id=1643673 + ##https://github.com/open-iscsi/rtslib-fb/issues/5 + ##---------------------------------------------------------------------- + ## The sample below is complex because its used for verification. + ## You should only use the minimal pillar data for your needs. + ## The format is important so review this example carefully. + ## + ## Multiple objects are supported: + ## See: https://github.com/saltstack-formulas/iscsi-formula/issues/19 + ##---------------------------------------------------------------------- + fabric_modules: + callmewhateveryoulike0: + discovery_enable_auth: 'true' + discovery_mutual_password: itsreallyme + discovery_mutual_userid: target + discovery_password: letmein + discovery_userid: initiator + name: iscsi + callmewhateveryoulike1: + discovery_enable_auth: 'true' + discovery_mutual_password: itsreallysticky + storage_objects: + callmewhateveryoulike_sda: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd0 + name: sda + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf090' + callmewhateveryoulike_sdb: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd1 + name: sdb + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf091' + callmewhateveryoulike_sdc: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd2 + name: sdc + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf092' + callmewhateveryoulike_sdd: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd3 + name: sdd + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf093' + callmewhateveryoulike_sde: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd4 + name: sde + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf094' + targets: + canada: + fabric: iscsi + tpgs: + attributes: + authentication: 0 + cache_dynamic_acls: 1 + default_cmdsn_depth: 16 + default_erl: 0 + demo_mode_discovery: 1 + demo_mode_write_protect: 0 + fabric_prot_type: 0 + generate_node_acls: 1 + login_keys_workaround: 1 + login_timeout: 15 + netif_timeout: 2 + prod_mode_write_protect: 0 + t10_pi: 0 + tpg_enabled_sendtargets: 1 + enable: 'true' + luns: + callmewhateveryoulike0: + alias: 'd6b1e8e70a' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 0 + storage_object: /backstores/block/sda + callmewhateveryoulike1: + alias: 'd6b1e8e70b' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 1 + storage_object: /backstores/block/sdb + callmewhateveryoulike2: + alias: 'd6b1e8e70c' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 2 + storage_object: /backstores/block/sdc + callmewhateveryoulike3: + alias: 'd6b1e8e70d' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 3 + storage_object: /backstores/block/sdd + callmewhateveryoulike4: + alias: 'd6b1e8e70e' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 4 + storage_object: /backstores/block/sde + node_acls: + callmewhateveryoulike0: + attributes: + dataout_timeout: 3 + dataout_timeout_retries: 5 + default_erl: 0 + nopin_response_timeout: 5 + nopin_timeout: 5 + random_datain_pdu_offsets: 0 + random_datain_seq_offsets: 0 + random_r2t_offsets: 0 + chap_mutual_password: itsreallyme + chap_mutual_userid: target + chap_password: letmein + chap_userid: station4 + mapped_luns: + mappy0: + index: 0 + tpg_lun: 0 + write_protect: 0 + mappy1: + index: 1 + tpg_lun: 1 + write_protect: 0 + mappy2: + index: 2 + tpg_lun: 2 + write_protect: 0 + mappy3: + index: 3 + tpg_lun: 3 + write_protect: 0 + mappy4: + index: 4 + tpg_lun: 4 + write_protect: 0 + node_wwn: 'iqn.1994-05.com.redhat:station4' + tcq_depth: 16 + parameters: + AuthMethod: 'CHAP,None' + DataDigest: 'CRC32C,None' + DataPDUInOrder: 'Yes' + DataSequenceInOrder: 'Yes' + DefaultTime2Retain: 20 + DefaultTime2Wait: 2 + ErrorRecoveryLevel: 0 + FirstBurstLength: 65536 + HeaderDigest: 'CRC32C,None' + IFMarkInt: Reject + IFMarker: 'No' + ImmediateData: 'Yes' + InitialR2T: 'Yes' + luxembourg: + fabric: iscsi + tpgs: + attributes: + authentication: 0 + enable: 1 + luns: + callmewhateveryoulike0: + alias: 'd6b1e8e70a' + index: 0 + storage_object: /backstores/block/sda + callmewhateveryoulike1: + alias: 'd6b1e8e70b' + index: 1 + storage_object: /backstores/block/sdb + callmewhateveryoulike2: + alias: 'd6b1e8e70c' + index: 2 + storage_object: /backstores/block/sdc + callmewhateveryoulike3: + alias: 'd6b1e8e70d' + index: 3 + storage_object: /backstores/block/sdd + callmewhateveryoulike4: + alias: 'd6b1e8e70e' + index: 4 + storage_object: /backstores/block/sde + node_acls: + callmewhateveryoulike0: + attributes: + dataout_timeout: 3 + #mapped_luns: {} + parameters: + TargetAlias: LIO Target + portals: + callmewhateveryoulike0: + ip_address: 10.0.2.254 + iser: 0 + tag: 2 + wwn: iqn.1996-04.lx.suse:01:a66aed20e2f3 + From 17d3833eb058c4209c48913a4ca19c3ee9337f61 Mon Sep 17 00:00:00 2001 From: N Date: Thu, 19 Sep 2019 01:08:01 +0100 Subject: [PATCH 22/42] test(travis): change loop0-3 to loop4-7 --- kitchen.yml | 15 +++++++-------- ...example.amazon => pillar.example.loopdevs-4-7} | 0 2 files changed, 7 insertions(+), 8 deletions(-) rename test/integration/default/{pillar.example.amazon => pillar.example.loopdevs-4-7} (100%) diff --git a/kitchen.yml b/kitchen.yml index 745d1abd..26d7a8b2 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -152,6 +152,11 @@ suites: - name: default excludes: - amazonlinux-2-2017-7-py2 + - centos-6-2017-7-py2 + - debian-8-2017-7-py2 + - fedora-29-2017-7-py2 + - opensuse-leap-15-2017-7-py2 + - ubuntu-1604-2017-7-py2 provisioner: dependencies: - name: lvm @@ -175,13 +180,7 @@ suites: inspec_tests: - path: test/integration/default - - name: amazon - excludes: - - debian-8-2017-7-py2 - - ubuntu-1604-2017-7-py2 - - centos-6-2017-7-py2 - - fedora-29-2017-7-py2 - - opensuse-leap-15-2017-7-py2 + - name: loopdevs-4-7 provisioner: dependencies: - name: lvm @@ -200,7 +199,7 @@ suites: '*': - iscsi pillars_from_files: - iscsi.sls: test/integration/default/pillar.example.amazon + iscsi.sls: test/integration/default/pillar.example.loopdevs-4-7 verifier: inspec_tests: - path: test/integration/default diff --git a/test/integration/default/pillar.example.amazon b/test/integration/default/pillar.example.loopdevs-4-7 similarity index 100% rename from test/integration/default/pillar.example.amazon rename to test/integration/default/pillar.example.loopdevs-4-7 From 7671bec18c742f7eaecce65af1e0001b4938cc4b Mon Sep 17 00:00:00 2001 From: N Date: Thu, 19 Sep 2019 01:22:01 +0100 Subject: [PATCH 23/42] fix(travis): travis wants loop4-7 --- pillar.example | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pillar.example b/pillar.example index 54de59ed..cc10c4f5 100644 --- a/pillar.example +++ b/pillar.example @@ -52,10 +52,10 @@ lvm: /tmp/loopdevs/testfile3.img: {} pv: create: - /dev/loop0: {} - /dev/loop1: {} - /dev/loop2: {} - /dev/loop3: {} + /dev/loop4: {} + /dev/loop5: {} + /dev/loop6: {} + /dev/loop7: {} iscsi: config: @@ -174,17 +174,17 @@ iscsi: #iSNSAccessControl: Off #iSNS: Off 'target iqn.2008-09.com.example:server.target1': - backing-store: /dev/loop0 + backing-store: /dev/loop4 'target iqn.2008-09.com.example:server.target5': - 'direct-store /dev/loop1': + 'direct-store /dev/loop5': vendor_id: VENDOR1 removable: 1 device-type: cd lun: 1 - 'direct-store /dev/loop2': + 'direct-store /dev/loop6': vendor_id: VENDOR2 lun: 2 - 'backing-store /dev/loop3': + 'backing-store /dev/loop7': vendor_id: back1 scsi_sn: SERIAL write-cache: on From 930fb9e175209313f9c907917e863a33a8b47938 Mon Sep 17 00:00:00 2001 From: N Date: Thu, 19 Sep 2019 10:16:35 +0100 Subject: [PATCH 24/42] docs(readme): list all states --- docs/README.rst | 177 ++++++++++++++++++++++++++--------------- iscsi/osfamilymap.yaml | 2 +- 2 files changed, 114 insertions(+), 65 deletions(-) diff --git a/docs/README.rst b/docs/README.rst index afc7dc45..2cb75bcb 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -46,11 +46,11 @@ Scope :scale: 25 % :alt: Link/Transport and Storage protocol infographic -Available states ----------------- +Available Meta states +====================== .. contents:: - :local: + :local: ``iscsi`` ^^^^^^^^^^^^ @@ -62,7 +62,7 @@ packages, manage configuration files and then starts the associated iscsi services. ``iscsi.target`` ---------------------- +---------------- Install and configure the iSCSI Target service. Supported vendor implementations include- - ``/etc/ctl.conf`` for ``ctld(8)`` on FreeBSD @@ -79,120 +79,169 @@ The defaults targets are- The default choice is modifable via the `iscsi.target.provider` pillar value. New providers can be introduced via pull request. +``iscsi.target.clean`` +---------------------- +*Meta-state (This is a state that includes other states)*. + +this state will undo everything performed in the ``iscsi.target`` meta-state + in reverse order, i.e. +stops the service, +removes the configuration files and +then uninstalls the package. + ``iscsi.initiator`` ------------------ -Install and configure the iSCSI initiator service. Supported providers include: +Install and configure the iSCSI initiator service- - ``/etc/iscsi.conf`` for FreeBSD - ``/etc/iscsi/iscsid.conf`` or ``~/.iscsid.conf`` for ``Open iSCSI`` on GNU/Linux +``iscsi.initiator.clean`` +------------------------- +*Meta-state (This is a state that includes other states)*. + +this state will undo everything performed in the ``iscsi.initiator`` + meta-state in reverse order, i.e. +stops the service, +removes the configuration files and +then uninstalls the package. + ``iscsi.isns`` ^^^^^^^^^^^^^^ Install and configure iSCSI name service. -``iscsi.target.package`` -^^^^^^^^^^^^^^^^^^^^^^^^ +``iscsi.isns.clean`` +-------------------- +*Meta-state (This is a state that includes other states)*. -Install iSCSI target packages. +this state will undo everything performed in the ``iscsi.isns`` + meta-state in reverse order, i.e. +stops the service, +removes the configuration files and +then uninstalls the package. -``iscsi.target.config`` -^^^^^^^^^^^^^^^^^^^^^^^ -Customises iscsi target configuration. Requires ``iscsi.target.package`` via include list. +Available sub-states +--------------------- -``iscsi.target.make`` -^^^^^^^^^^^^^^^^^^^^^ +.. contents:: + :local: -This state makes iscsi target services on FreeBSD. +``iscsi.target.package`` +------------------------ +Install iSCSI target related packages. -``iscsi.target.kernel`` -^^^^^^^^^^^^^^^^^^^^^^^ +``iscsi.target.package.clean`` +------------------------------ +Remove iSCSI target related packages. -Configures required kernel modules. +``iscsi.target.config`` +----------------------- +Install iSCSI target related configuration files. -``iscsi.target.service`` -^^^^^^^^^^^^^^^^^^^^^^^^ +``iscsi.target.config.clean`` +------------------------------ +Remove iSCSI target related configuration files. + +``iscsi.target.kernel`` +----------------------- +Load iSCSI target related kernel modules. -Start iscsi target services. Requires ``iscsi.target.config`` via include list. +``iscsi.target.kernel.clean`` +----------------------------- +Unload iSCSI target related kernel modules. -``iscsi.target.clean`` -^^^^^^^^^^^^^^^^^^^^^^ +``iscsi.target.make`` +--------------------- +Make iSCSI related packages from git source on FreeBSD. -*Meta-state (This is a state that includes other states)*. +``iscsi.target.make.clean`` +--------------------------- +Remove iSCSI related package binaries on FreeBSD. -this state will undo everything performed in the ``iscsi.target`` meta-state - in reverse order, i.e. -stops the service, -removes the configuration files and -then uninstalls the package. +``iscsi.target.service`` +------------------------ +Install iSCSI target services. + +``iscsi.target.service.clean`` +----------------------------- +Stop and disable SCSI target services. ``iscsi.initiator.package`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +--------------------------- +Install iSCSI initiator related packages. -Install iSCSI initiator packages. +``iscsi.initiator.package.clean`` +--------------------------------- +Remove iSCSI initiator related packages. ``iscsi.initiator.config`` -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Customises iscsi initiator configuration. Requires ``iscsi.initiator.package`` via include list. - -``iscsi.initiator.make`` -^^^^^^^^^^^^^^^^^^^^^^^^ +-------------------------- +Install iSCSI initiator related configuration files. -This state makes iscsi initiator services on FreeBSD. +``iscsi.initiator.config.clean`` +-------------------------------- +Remove iSCSI initiator related configuration files. ``iscsi.initiator.kernel`` -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Configures required kernel modules. +-------------------------- +Load iSCSI initiator related kernel modules. -``iscsi.initiator.service`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +``iscsi.initiator.kernel.clean`` +-------------------------------- +Unload iSCSI initiator related kernel modules. -Start iscsi initiator services. Requires ``iscsi.initiator.config`` via include list. +``iscsi.initiator.make`` +------------------------ +Make iSCSI related packages from git source for FreeBSD. -``iscsi.initiator.clean`` -^^^^^^^^^^^^^^^^^^^^^^^^^ +``iscsi.initiator.make.clean`` +------------------------------ +Remove iSCSI related package binaries on FreeBSD. -*Meta-state (This is a state that includes other states)*. +``iscsi.initiator.service`` +--------------------------- +Install iSCSI initiator services. -this state will undo everything performed in the ``iscsi.initiator`` meta-state - in reverse order, i.e. -stops the service, -removes the configuration files and -then uninstalls the package. +``iscsi.initiator.service.clean`` +--------------------------------- +Stop and disable iSCSI initiator services. ``iscsi.isns.package`` ^^^^^^^^^^^^^^^^^^^^^^ - Install iSCSI isns packages. +``iscsi.isns.package.clean`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Remove iSCSI isns packages. + ``iscsi.isns.config`` ^^^^^^^^^^^^^^^^^^^^^ +Customises iscsi isns configuration. +Requires ``iscsi.isns.package`` via include list. -Customises iscsi isns configuration. Requires ``iscsi.isns.package`` via include list. +``iscsi.isns.config.clean`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Remove iSCSI isns configuration files. ``iscsi.isns.make`` ^^^^^^^^^^^^^^^^^^^ - This state makes iscsi isns services on FreeBSD. -``iscsi.isns.kernel`` -^^^^^^^^^^^^^^^^^^^^^ - -Configures required kernel modules. +``iscsi.isns.make.clean`` +^^^^^^^^^^^^^^^^^^^^^^^^^ +Removes iSCSI isns binaries on FreeBSD. ``iscsi.isns.service`` ^^^^^^^^^^^^^^^^^^^^^^ +Start iscsi isns services. +Requires ``iscsi.isns.config`` via include list. -Start iscsi isns services. Requires ``iscsi.isns.config`` via include list. - -``iscsi.isns.clean`` -^^^^^^^^^^^^^^^^^^^^ - +``iscsi.isns.service.clean`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *Meta-state (This is a state that includes other states)*. -this state will undo everything performed in the ``iscsi.target`` meta-state +this state will undo everything performed in the ``iscsi.isns`` meta-state in reverse order, i.e. stops the service, removes the configuration files and diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index 7689e30e..6f2cb155 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -66,7 +66,7 @@ RedHat: - scsi-target-utils - fcoe-utils - fcoe-target-utils - {%- elif 'osmajorrrelease' in grains and grains.osmajorrelease|int >= 7 %} + {%- else %} - yum-plugin-versionlock - targetcli - libvirt-daemon-driver-storage-iscsi From add55e0b4c387801c4a05f2526e2bde0f741f724 Mon Sep 17 00:00:00 2001 From: N Date: Thu, 19 Sep 2019 22:34:59 +0100 Subject: [PATCH 25/42] refactor(osmaps): add osfinger, osmap --- iscsi/map.jinja | 19 ++++- iscsi/oscodename.yaml | 31 ------- iscsi/osfamilymap.yaml | 22 ----- iscsi/osfingermap.yaml | 61 ++++++++++++++ iscsi/osmap.yaml | 83 +++++++++++++++++++ kitchen.yml | 31 ------- pillar.example | 16 ++-- .../default/pillar.example.loopdevs-4-7 | 2 - 8 files changed, 167 insertions(+), 98 deletions(-) create mode 100644 iscsi/osfingermap.yaml create mode 100644 iscsi/osmap.yaml diff --git a/iscsi/map.jinja b/iscsi/map.jinja index b576c8f0..d5cf5dfa 100644 --- a/iscsi/map.jinja +++ b/iscsi/map.jinja @@ -6,7 +6,10 @@ {#- Start imports as #} {%- import_yaml tplroot ~ "/defaults.yaml" as default_settings %} {%- import_yaml tplroot ~ "/osfamilymap.yaml" as osfamilymap %} +{%- import_yaml tplroot ~ "/osmap.yaml" as osmap %} {%- import_yaml tplroot ~ "/oscodename.yaml" as oscodename %} +{%- import_yaml tplroot ~ "/osfingermap.yaml" as osfingermap %} + {#- Retrieve the config dict only once #} {%- set _config = salt['config.get'](tplroot, default={}) %} @@ -18,11 +21,19 @@ osfamilymap, grain='os_family', merge=salt['grains.filter_by']( - oscodename, - grain='oscodename', + osmap, + grain='os', merge=salt['grains.filter_by']( - _config, - default='lookup' + oscodename, + grain='oscodename', + merge=salt['grains.filter_by']( + osfingermap, + grain='osfinger', + merge=salt['grains.filter_by']( + _config, + default='lookup' + ) + ) ) ) ) diff --git a/iscsi/oscodename.yaml b/iscsi/oscodename.yaml index 20244153..fe925668 100644 --- a/iscsi/oscodename.yaml +++ b/iscsi/oscodename.yaml @@ -29,33 +29,6 @@ {% macro fedora_codename(name, release, codename) %} {{ codename|default(name, true) }}: - {%- if release > 26 %} - isns: - pkgs: - wanted: - - isns-utils - - isns-utils-libs - - target-isns - initiator: - pkgs: - wanted: - - iscsi-initiator-utils #cent6/cent7/fedora/amazon - - iscsi-initiator-utils-iscsiuio #cent7/fedora - - libiscsi #cent7/fedora - - libiscsi-utils #cent7/fedora - target: - pkgs: - wanted: - - device-mapper-persistent-data #cent6/cent7/fedora/amazon - - netbsd-iscsi #cent6/cent7/fedora - - yum-plugin-versionlock #cent6/cent7/fedora - - targetcli #cent7/fedora - - libvirt-daemon-driver-storage-iscsi #cent7/fedora - - udisks2-iscsi #cent7/fedora - - scsi-target-utils #cent6/fedora - - fcoe-utils #cent6/fedora - - libvirt-daemon-driver-storage-iscsi-direct #fedora - {%- endif %} {% endmacro %} ## Debian GNU/Linux @@ -81,10 +54,6 @@ ## Fedora # `oscodename` grain has long distro name -{{ fedora_codename('Fedora-30', 30, 'Fedora 30 (Thirty)') }} {{ fedora_codename('Fedora-29', 29, 'Fedora 29 (Twenty Nine)') }} -{{ fedora_codename('Fedora-28', 28, 'Fedora 28 (Twenty Eight)') }} -{{ fedora_codename('Fedora-27', 27, 'Fedora 27 (Twenty Seven)') }} -{{ fedora_codename('Fedora-26', 26, 'Fedora 26 (Twenty Six)') }} # vim: ft=sls diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index 6f2cb155..f3501824 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -37,44 +37,22 @@ Debian: - perftest RedHat: config: - name: - tgtd: /etc/tgt/targets.conf #cent6 servicename: open-iscsi: iscsi isns: isnsd - tgtd: tgtd #cent6 isns: pkgs: wanted: - isns-utils - yum-plugin-versionlock - # target-isns initiator: pkgs: wanted: - iscsi-initiator-utils - {%- if 'osmajorrrelease' in grains and grains.osmajorrelease|int >= 7 %} - - iscsi-initiator-utils-iscsiuio - - libiscsi - - libiscsi-utils - {%- endif %} target: pkgs: wanted: - device-mapper-persistent-data - {%- if 'osmajorrrelease' in grains and grains.osmajorrelease|int == 6 %} - - scsi-target-utils - - fcoe-utils - - fcoe-target-utils - {%- else %} - - yum-plugin-versionlock - - targetcli - - libvirt-daemon-driver-storage-iscsi - {%- if 'os' in grains and grains.os not in ('Amazon',) %} - - netbsd-iscsi - - udisks2-iscsi - {%- endif %} - {%- endif %} Suse: config: diff --git a/iscsi/osfingermap.yaml b/iscsi/osfingermap.yaml new file mode 100644 index 00000000..78fd45f8 --- /dev/null +++ b/iscsi/osfingermap.yaml @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +# +# Setup variables using grains['osfinger'] based logic. +# You just need to add the key:values for an `osfinger` that differ +# from `defaults.yaml` + `osarch.yaml` + `os_family.yaml` + `osmap.yaml`. +# Only add an `osfinger` which is/will be supported by the formula. +# +# If you do not need to provide defaults via the `os_finger` grain, +# you will need to provide at least an empty dict in this file, e.g. +# osfingermap: {} +--- +# os: CentOS +CentOS-6: + target: + pkgs: + wanted: + - scsi-target-utils + - fcoe-utils + - fcoe-target-utils + config: + name: + tgtd: /etc/tgt/targets.conf + servicename: + tgtd: tgtd + +CentOS-7: + initiator: + pkgs: + wanted: + - iscsi-initiator-utils-iscsiuio + - libiscsi + - libiscsi-utils + target: + pkgs: + wanted: + - yum-plugin-versionlock + - targetcli + - libvirt-daemon-driver-storage-iscsi + - udisks2-iscsi + +CentOS-8: + isns: + pkgs: + wanted: + - target-isns + initiator: + pkgs: + wanted: + - iscsi-initiator-utils-iscsiuio + - libiscsi + - libiscsi-utils + target: + pkgs: + wanted: + - yum-plugin-versionlock + - targetcli + - libvirt-daemon-driver-storage-iscsi + - libvirt-daemon-driver-storage-iscsi-direct + - thin-provisioning-tools + - udisks2-iscsi diff --git a/iscsi/osmap.yaml b/iscsi/osmap.yaml new file mode 100644 index 00000000..f3d5020d --- /dev/null +++ b/iscsi/osmap.yaml @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +# +# Setup variables using grains['os'] based logic. +# You just need to add the key:values for an `os` that differ +# from `defaults.yaml` + `osarch.yaml` + `os_family.yaml`. +# Only add an `os` which is/will be supported by the formula. +# +# If you do not need to provide defaults via the `os` grain, +# you will need to provide at least an empty dict in this file, e.g. +# osmap: {} +--- +Amazon: + initiator: + pkgs: + wanted: + - iscsi-initiator-utils + - libiscsi + - libiscsi-utils + target: + pkgs: + wanted: + - yum-plugin-versionlock + - targetcli + - libvirt-daemon-driver-storage-iscsi + +# os_family: Debian +Ubuntu: {} + +Raspbian: {} + +# os_family: RedHat +Fedora: + isns: + pkgs: + wanted: + - isns-utils + - isns-utils-libs + - target-isns + initiator: + pkgs: + wanted: + - iscsi-initiator-utils + - iscsi-initiator-utils-iscsiuio + - libiscsi + - libiscsi-utils + target: + pkgs: + wanted: + - device-mapper-persistent-data + - netbsd-iscsi + - yum-plugin-versionlock + - targetcli + - libvirt-daemon-driver-storage-iscsi + - udisks2-iscsi + - scsi-target-utils + - fcoe-utils + - libvirt-daemon-driver-storage-iscsi-direct + +CentOS: + target: + pkgs: + wanted: + - device-mapper-persistent-data + - netbsd-iscsi + - yum-plugin-versionlock + - targetcli + - libvirt-daemon-driver-storage-iscsi + - udisks2-iscsi + - scsi-target-utils + - fcoe-utils + +# os_family: Suse +openSUSE: {} + +# os_family: Gentoo +Funtoo: {} + +# os_family: Arch +Manjaro: {} + +# os_family: Solaris +SmartOS: {} diff --git a/kitchen.yml b/kitchen.yml index 26d7a8b2..e0dd4313 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -150,37 +150,6 @@ verifier: suites: - name: default - excludes: - - amazonlinux-2-2017-7-py2 - - centos-6-2017-7-py2 - - debian-8-2017-7-py2 - - fedora-29-2017-7-py2 - - opensuse-leap-15-2017-7-py2 - - ubuntu-1604-2017-7-py2 - provisioner: - dependencies: - - name: lvm - repo: git - source: https://github.com/saltstack-formulas/lvm-formula.git - state_top: - base: - '*': - - lvm.install - - lvm.files.create - - lvm.pv.create - - iscsi - pillars: - top.sls: - base: - '*': - - iscsi - pillars_from_files: - iscsi.sls: pillar.example - verifier: - inspec_tests: - - path: test/integration/default - - - name: loopdevs-4-7 provisioner: dependencies: - name: lvm diff --git a/pillar.example b/pillar.example index cc10c4f5..54de59ed 100644 --- a/pillar.example +++ b/pillar.example @@ -52,10 +52,10 @@ lvm: /tmp/loopdevs/testfile3.img: {} pv: create: - /dev/loop4: {} - /dev/loop5: {} - /dev/loop6: {} - /dev/loop7: {} + /dev/loop0: {} + /dev/loop1: {} + /dev/loop2: {} + /dev/loop3: {} iscsi: config: @@ -174,17 +174,17 @@ iscsi: #iSNSAccessControl: Off #iSNS: Off 'target iqn.2008-09.com.example:server.target1': - backing-store: /dev/loop4 + backing-store: /dev/loop0 'target iqn.2008-09.com.example:server.target5': - 'direct-store /dev/loop5': + 'direct-store /dev/loop1': vendor_id: VENDOR1 removable: 1 device-type: cd lun: 1 - 'direct-store /dev/loop6': + 'direct-store /dev/loop2': vendor_id: VENDOR2 lun: 2 - 'backing-store /dev/loop7': + 'backing-store /dev/loop3': vendor_id: back1 scsi_sn: SERIAL write-cache: on diff --git a/test/integration/default/pillar.example.loopdevs-4-7 b/test/integration/default/pillar.example.loopdevs-4-7 index ac12a721..a8a2bdce 100644 --- a/test/integration/default/pillar.example.loopdevs-4-7 +++ b/test/integration/default/pillar.example.loopdevs-4-7 @@ -20,8 +20,6 @@ lvm: pkgs: - lvm2 - cryptsetup - - device-mapper-persistent-data - - thin-provisioning-tools files: remove: - /tmp/loopdevs/testfile0.img From d2f2b4efdb0693c8c2e490168e4ec9008e7b63ec Mon Sep 17 00:00:00 2001 From: N Date: Fri, 20 Sep 2019 01:08:50 +0100 Subject: [PATCH 26/42] test(travis): no iscsi target package for amazonlinux --- .travis.yml | 4 ++-- docs/README.rst | 1 + iscsi/init.sls | 9 ++++++++- iscsi/osfingermap.yaml | 1 + iscsi/osmap.yaml | 6 ++++++ iscsi/target/config/clean.sls | 1 + iscsi/target/config/install.sls | 1 + iscsi/target/init.sls | 2 ++ iscsi/target/package/install.sls | 1 + iscsi/target/service/clean.sls | 1 + iscsi/target/service/install.sls | 1 + kitchen.yml | 15 --------------- .../default/pillar.example.loopdevs-4-7 | 2 +- 13 files changed, 26 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index af0ae18c..6f29c7af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,10 +27,10 @@ env: # - INSTANCE: default-amazonlinux-2-develop-py2 # - INSTANCE: default-debian-9-2019-2-py3 - INSTANCE: default-ubuntu-1804-2019-2-py3 - # - INSTANCE: default-centos-7-2019-2-py3 + - INSTANCE: default-centos-7-2019-2-py3 # - INSTANCE: default-fedora-30-2019-2-py3 # - INSTANCE: default-opensuse-leap-15-2019-2-py3 - - INSTANCE: default-amazonlinux-2-2019-2-py2 + # INSTANCE: default-amazonlinux-2-2019-2-py2 # - INSTANCE: default-debian-9-2018-3-py2 # - INSTANCE: default-ubuntu-1604-2018-3-py2 # - INSTANCE: default-centos-7-2018-3-py2 diff --git a/docs/README.rst b/docs/README.rst index 2cb75bcb..1516e21a 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -75,6 +75,7 @@ The defaults targets are- - ctld on FreeBSD - LIO on CentOS, OpenSUSE, Arch - tgt on Debian +- Unsupported on Amazon (no packages available) The default choice is modifable via the `iscsi.target.provider` pillar value. New providers can be introduced via pull request. diff --git a/iscsi/init.sls b/iscsi/init.sls index ad8b5af8..121d34db 100644 --- a/iscsi/init.sls +++ b/iscsi/init.sls @@ -2,7 +2,14 @@ # vim: ft=sls include: + {%- if grains.os_family in ('Arch',) %} + {# This sequence avoids /etc/isns/isnsd.conf conflict on Arch #} - iscsi.target - iscsi.initiator - #Putting isns last avoids /etc/isns/isnsd.conf file conflict on Arch - iscsi.isns + {%- else %} + {# This is the normal sequence #} + - iscsi.isns + - iscsi.target + - iscsi.initiator + {%- endif %} diff --git a/iscsi/osfingermap.yaml b/iscsi/osfingermap.yaml index 78fd45f8..4ae9684b 100644 --- a/iscsi/osfingermap.yaml +++ b/iscsi/osfingermap.yaml @@ -13,6 +13,7 @@ # os: CentOS CentOS-6: target: + provider: tgtd pkgs: wanted: - scsi-target-utils diff --git a/iscsi/osmap.yaml b/iscsi/osmap.yaml index f3d5020d..90c854f2 100644 --- a/iscsi/osmap.yaml +++ b/iscsi/osmap.yaml @@ -31,6 +31,11 @@ Raspbian: {} # os_family: RedHat Fedora: + config: + name: + tgtd: /etc/tgt/targets.conf + servicename: + tgtd: tgtd isns: pkgs: wanted: @@ -45,6 +50,7 @@ Fedora: - libiscsi - libiscsi-utils target: + provider: tgtd pkgs: wanted: - device-mapper-persistent-data diff --git a/iscsi/target/config/clean.sls b/iscsi/target/config/clean.sls index 6981246e..5c034ab8 100644 --- a/iscsi/target/config/clean.sls +++ b/iscsi/target/config/clean.sls @@ -11,6 +11,7 @@ include: iscsi-target-config-clean-file-absent: file.absent: + - unless: {{ grains.os in ('Amazon', 'MacOS') }} - name: {{ iscsi.config.name['target'] }} - watch_in: - sls: {{ sls_service_clean }} diff --git a/iscsi/target/config/install.sls b/iscsi/target/config/install.sls index d830422e..04c081a9 100644 --- a/iscsi/target/config/install.sls +++ b/iscsi/target/config/install.sls @@ -13,6 +13,7 @@ include: iscsi-target-config-install-file-managed: file.managed: - onlyif: {{ iscsi.config.data[iscsi.target.provider|string]|json }} + - unless: {{ grains.os in ('Amazon', 'MacOS') }} - name: {{ iscsi.config.name[iscsi.target.provider] }} - source: {{ files_switch([iscsi.target.provider ~ '.tmpl'], lookup='iscsi-target-config-install-file-managed', diff --git a/iscsi/target/init.sls b/iscsi/target/init.sls index 5e9b1ac3..d23ee67c 100644 --- a/iscsi/target/init.sls +++ b/iscsi/target/init.sls @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- # vim: ft=sls + {%- if grains.os not in ('Amazon', 'MacOS') %} include: - .package - .make - .kernel - .config - .service + {%- endif %} diff --git a/iscsi/target/package/install.sls b/iscsi/target/package/install.sls index e58b6939..4876ab18 100644 --- a/iscsi/target/package/install.sls +++ b/iscsi/target/package/install.sls @@ -25,6 +25,7 @@ iscsi-target-package-install-{{ pkg }}-removed: iscsi-target-package-install-{{ pkg }}-installed: pkg.installed: + - unless: {{ grains.os in ('Amazon', 'MacOS') }} - name: {{ pkg }} {%- if iscsi.target.pkghold %} - hold: {{ iscsi.target.pkghold }} diff --git a/iscsi/target/service/clean.sls b/iscsi/target/service/clean.sls index 50d8f598..c00fc1b9 100644 --- a/iscsi/target/service/clean.sls +++ b/iscsi/target/service/clean.sls @@ -11,6 +11,7 @@ include: iscsi-target-service-clean-service-dead service.dead: + - unless: {{ grains.os in ('Amazon', 'MacOS') }} - name: {{ iscsi.config.servicename[iscsi.target.provider] }} - enable: False - require_in: diff --git a/iscsi/target/service/install.sls b/iscsi/target/service/install.sls index 2098ed24..bd68fea3 100644 --- a/iscsi/target/service/install.sls +++ b/iscsi/target/service/install.sls @@ -45,6 +45,7 @@ iscsi-target-service-install-service-running: {%- else %} - name: {{ servicename }} {%- endif %} + - unless: {{ grains.os in ('Amazon', 'MacOS') }} iscsi-target-service-install-failure-explanation: test.show_notification: diff --git a/kitchen.yml b/kitchen.yml index e0dd4313..63f63633 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -47,12 +47,6 @@ platforms: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: max_ssh_sessions: 1 - - name: amazonlinux-2-develop-py2 - driver: - image: netmanagers/salt-develop-py2:amazonlinux-2 - provision_command: - - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com - - sh bootstrap-salt.sh -XdPbfrq -x python2 git develop ## SALT `2019.2` - name: debian-9-2019-2-py3 @@ -75,9 +69,6 @@ platforms: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: max_ssh_sessions: 1 - - name: amazonlinux-2-2019-2-py2 - driver: - image: netmanagers/salt-2019.2-py2:amazonlinux-2 ## SALT `2018.3` - name: debian-9-2018-3-py2 @@ -100,9 +91,6 @@ platforms: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: max_ssh_sessions: 1 - - name: amazonlinux-2-2018-3-py2 - driver: - image: netmanagers/salt-2018.3-py2:amazonlinux-2 ## SALT `2017.7` - name: debian-8-2017-7-py2 @@ -126,9 +114,6 @@ platforms: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: max_ssh_sessions: 1 - - name: amazonlinux-2-2017-7-py2 - driver: - image: netmanagers/salt-2017.7-py2:amazonlinux-2 provisioner: name: salt_solo diff --git a/test/integration/default/pillar.example.loopdevs-4-7 b/test/integration/default/pillar.example.loopdevs-4-7 index a8a2bdce..a4fe1cdf 100644 --- a/test/integration/default/pillar.example.loopdevs-4-7 +++ b/test/integration/default/pillar.example.loopdevs-4-7 @@ -19,7 +19,7 @@ users: lvm: pkgs: - lvm2 - - cryptsetup + # cryptsetup files: remove: - /tmp/loopdevs/testfile0.img From 42d9f7ad4a408736e52ff970deab657f64cee422 Mon Sep 17 00:00:00 2001 From: N Date: Fri, 20 Sep 2019 12:53:20 +0100 Subject: [PATCH 27/42] test(matrix): choose appropriate os --- .travis.yml | 6 +++--- kitchen.yml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6f29c7af..2863dc5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,10 +33,10 @@ env: # INSTANCE: default-amazonlinux-2-2019-2-py2 # - INSTANCE: default-debian-9-2018-3-py2 # - INSTANCE: default-ubuntu-1604-2018-3-py2 - # - INSTANCE: default-centos-7-2018-3-py2 - - INSTANCE: default-fedora-29-2018-3-py2 + - INSTANCE: default-centos-7-2018-3-py2 + # INSTANCE: default-fedora-29-2018-3-py2 - INSTANCE: default-opensuse-leap-15-2018-3-py2 - # - INSTANCE: default-amazonlinux-2-2018-3-py2 + - INSTANCE: default-amazonlinux-2-2018-3-py2 # - INSTANCE: default-debian-8-2017-7-py2 # - INSTANCE: default-ubuntu-1604-2017-7-py2 - INSTANCE: default-centos-6-2017-7-py2 diff --git a/kitchen.yml b/kitchen.yml index 63f63633..80dd4122 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -47,6 +47,12 @@ platforms: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: max_ssh_sessions: 1 + - name: amazonlinux-2-develop-py2 + driver: + image: netmanagers/salt-develop-py2:amazonlinux-2 + provision_command: + - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com + - sh bootstrap-salt.sh -XdPbfrq -x python2 git develop ## SALT `2019.2` - name: debian-9-2019-2-py3 @@ -69,6 +75,9 @@ platforms: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: max_ssh_sessions: 1 + - name: amazonlinux-2-2019-2-py2 + driver: + image: netmanagers/salt-2019.2-py2:amazonlinux-2 ## SALT `2018.3` - name: debian-9-2018-3-py2 @@ -91,6 +100,9 @@ platforms: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: max_ssh_sessions: 1 + - name: amazonlinux-2-2018-3-py2 + driver: + image: netmanagers/salt-2018.3-py2:amazonlinux-2 ## SALT `2017.7` - name: debian-8-2017-7-py2 @@ -114,6 +126,9 @@ platforms: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: max_ssh_sessions: 1 + - name: amazonlinux-2-2017-7-py2 + driver: + image: netmanagers/salt-2017.7-py2:amazonlinux-2 provisioner: name: salt_solo @@ -135,6 +150,9 @@ verifier: suites: - name: default + excludes: + - default-centos-6-2017-7-py2 + - default-centos-7-2019-2-py3 provisioner: dependencies: - name: lvm @@ -146,6 +164,7 @@ suites: - lvm.install - lvm.files.create - lvm.pv.create + - iscsi.clean - iscsi pillars: top.sls: @@ -157,3 +176,36 @@ suites: verifier: inspec_tests: - path: test/integration/default + + - name: centos + excludes: + - default-debian-10-develop-py3 + - default-ubuntu-1804-2019-2-py3 + - default-opensuse-leap-15-2018-3-py2 + - default-amazonlinux-2-2018-3-py2 + provisioner: + dependencies: + - name: lvm + repo: git + source: https://github.com/saltstack-formulas/lvm-formula.git + state_top: + base: + '*': + - lvm.install + - lvm.files.create + - lvm.pv.create + - iscsi.initiator.clean + - iscsi.target.clean + # iscsi.isns #Travis fails with "Service isns is enabled, and is dead" + - iscsi.target + - iscsi.initiator + pillars: + top.sls: + base: + '*': + - iscsi + pillars_from_files: + iscsi.sls: test/integration/default/pillar.example.loopdevs-4-7 + verifier: + inspec_tests: + - path: test/integration/default From f4224fcebe32c65bdcb26198ebc6a48d9290bab8 Mon Sep 17 00:00:00 2001 From: N Date: Fri, 20 Sep 2019 12:59:43 +0100 Subject: [PATCH 28/42] fix(clean): fix clean states --- .travis.yml | 4 +-- iscsi/initiator/config/clean.sls | 8 +++--- iscsi/initiator/package/clean.sls | 5 ++++ iscsi/initiator/service/clean.sls | 9 ++++--- iscsi/initiator/service/install.sls | 11 +++++++- iscsi/isns/config/clean.sls | 7 ++--- iscsi/isns/package/clean.sls | 5 ++++ iscsi/isns/service/clean.sls | 2 +- iscsi/isns/service/install.sls | 5 ++++ iscsi/target/config/clean.sls | 8 +++--- iscsi/target/package/clean.sls | 5 ++++ iscsi/target/service/clean.sls | 9 ++++--- iscsi/target/service/install.sls | 7 +++++ kitchen.yml | 40 +++++++++++++++++++++++++++-- 14 files changed, 102 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2863dc5d..2d141888 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ env: - INSTANCE: default-debian-10-develop-py3 # - INSTANCE: default-ubuntu-1804-develop-py3 # - INSTANCE: default-centos-7-develop-py3 - # - INSTANCE: default-fedora-30-develop-py3 + - INSTANCE: default-fedora-30-develop-py3 # - INSTANCE: default-opensuse-leap-15-develop-py3 # - INSTANCE: default-amazonlinux-2-develop-py2 # - INSTANCE: default-debian-9-2019-2-py3 @@ -33,7 +33,7 @@ env: # INSTANCE: default-amazonlinux-2-2019-2-py2 # - INSTANCE: default-debian-9-2018-3-py2 # - INSTANCE: default-ubuntu-1604-2018-3-py2 - - INSTANCE: default-centos-7-2018-3-py2 + # INSTANCE: default-centos-7-2018-3-py2 # INSTANCE: default-fedora-29-2018-3-py2 - INSTANCE: default-opensuse-leap-15-2018-3-py2 - INSTANCE: default-amazonlinux-2-2018-3-py2 diff --git a/iscsi/initiator/config/clean.sls b/iscsi/initiator/config/clean.sls index 2690f9ff..d728f33f 100644 --- a/iscsi/initiator/config/clean.sls +++ b/iscsi/initiator/config/clean.sls @@ -3,14 +3,14 @@ {#- Get the `tplroot` from `tpldir` #} {%- set tplroot = tpldir.split('/')[0] %} -{%- set sls_service_clean = tplroot ~ '.initator.service.clean' %} +{%- set sls_package_clean = tplroot ~ '.initiator.package.clean' %} {%- from tplroot ~ "/map.jinja" import iscsi with context %} include: - - {{ sls_service_clean }} + - {{ sls_package_clean }} iscsi-initiator-config-clean-file-absent: file.absent: - - name: {{ iscsi.config.name['initiator'] }} + - name: {{ iscsi.config.name[iscsi.initiator.provider|string] }} - watch_in: - - sls: {{ sls_service_clean }} + - sls: {{ sls_package_clean }} diff --git a/iscsi/initiator/package/clean.sls b/iscsi/initiator/package/clean.sls index 05e2afe6..63b7f0e0 100644 --- a/iscsi/initiator/package/clean.sls +++ b/iscsi/initiator/package/clean.sls @@ -4,17 +4,22 @@ {#- Get the `tplroot` from `tpldir` #} {%- set tplroot = tpldir.split('/')[0] %} {%- set sls_config_clean = tplroot ~ '.initiator.config.clean' %} +{%- set sls_service_clean = tplroot ~ '.initiator.service.clean' %} {%- from tplroot ~ "/map.jinja" import iscsi with context %} include: - {{ sls_config_clean }} + - {{ sls_service_clean }} {%- for pkg in [iscsi.initiator.pkgs.wanted, iscsi.initiator.pkgs.unwanted] %} + {%- if pkg %} iscsi-initiator-package-clean-{{ pkg }}-removed: pkg.purged: - name: {{ pkg }} - require: - sls: {{ sls_config_clean }} + - sls: {{ sls_service_clean }} + {%- endif %} {% endfor %} diff --git a/iscsi/initiator/service/clean.sls b/iscsi/initiator/service/clean.sls index 213e8045..6f6ad7cf 100644 --- a/iscsi/initiator/service/clean.sls +++ b/iscsi/initiator/service/clean.sls @@ -9,17 +9,20 @@ include: - {{ sls_config_clean }} -iscsi-initiator-service-clean-service-dead +iscsi-initiator-service-clean-service-dead: service.dead: - name: {{ iscsi.config.servicename[iscsi.initiator.provider] }} - enable: False - require_in: - sls: {{ sls_config_clean }} -iscsi-initiator-service-install-file-line-freebsd: + {%- if grains.os_family == 'FreeBSD' %} + +iscsi-initiator-service-clean-file-line-freebsd: file.line: - - onlyif: {{ grains.os_family == 'FreeBSD' }} - name: {{ iscsi.config.name.modprobe }} - content: 'ctld_env="-u"' - mode: delete - backup: True + + {%- endif %} diff --git a/iscsi/initiator/service/install.sls b/iscsi/initiator/service/install.sls index 9f8154f4..cd01c61d 100644 --- a/iscsi/initiator/service/install.sls +++ b/iscsi/initiator/service/install.sls @@ -9,7 +9,7 @@ include: - {{ sls_config_install }} - {%- if grains.os_family == 'FreeBSD' %} + {%- if grains.os_family in ('FreeBSD',) %} iscsi-initiator-service-install-file-line-freebsd: file.line: @@ -17,6 +17,7 @@ iscsi-initiator-service-install-file-line-freebsd: - content: 'ctld_env="-u"' - backup: True {%- if iscsi.initiator.enabled %} + - create: True - mode: ensure - after: 'autoboot_delay.*' {%- else %} @@ -55,3 +56,11 @@ iscsi-initiator-service-install-failure-explanation: * your kernel was upgraded but not activated by reboot 'systemctl enable {{ servicename }}' && reboot - unless: {{ grains.os_family in ('MacOS', 'Windows') }} #maybe not needed but no harm + cmd.run: + - names: + - journalctl -xe -u {{ servicename }} || true + - systemctl status {{ servicename }} -l || true + - /sbin/lsmod 2>/dev/null || true + - ls /var/lib/iscsi/nodes 2>/dev/null || true + - ls /sys/class/iscsi_session 2>/dev/null || true + - onlyif: test -x /usr/bin/systemctl || test -x /bin/systemctl || test -x /sbin/systemctl diff --git a/iscsi/isns/config/clean.sls b/iscsi/isns/config/clean.sls index 1ecb3121..1991e036 100644 --- a/iscsi/isns/config/clean.sls +++ b/iscsi/isns/config/clean.sls @@ -3,17 +3,17 @@ {#- Get the `tplroot` from `tpldir` #} {%- set tplroot = tpldir.split('/')[0] %} -{%- set sls_service_clean = tplroot ~ '.isns.service.clean' %} +{%- set sls_package_clean = tplroot ~ '.isns.package.clean' %} {%- from tplroot ~ "/map.jinja" import iscsi with context %} include: - - {{ sls_service_clean }} + - {{ sls_package_clean }} iscsi-isns-config-clean-file-absent: file.absent: - name: {{ iscsi.config.name[iscsi.isns.provider] }} - watch_in: - - sls: {{ sls_service_clean }} + - sls: {{ sls_package_clean }} {%- if 'isnsadm' in iscsi.config.name and iscsi.config.name['isnsadm'] %} iscsi-isns-config-clean-file-absent-isnsadm: @@ -26,3 +26,4 @@ iscsi-isns-config-clean-file-absent-isnsdd: file.absent: - name: {{ iscsi.config.name['isnsdd'] }} {%- endif %} + diff --git a/iscsi/isns/package/clean.sls b/iscsi/isns/package/clean.sls index a10fbf67..bd22b64c 100644 --- a/iscsi/isns/package/clean.sls +++ b/iscsi/isns/package/clean.sls @@ -4,17 +4,22 @@ {#- Get the `tplroot` from `tpldir` #} {%- set tplroot = tpldir.split('/')[0] %} {%- set sls_config_clean = tplroot ~ '.isns.config.clean' %} +{%- set sls_service_clean = tplroot ~ '.isns.service.clean' %} {%- from tplroot ~ "/map.jinja" import iscsi with context %} include: - {{ sls_config_clean }} + - {{ sls_service_clean }} {%- for pkg in [iscsi.isns.pkgs.wanted, iscsi.isns.pkgs.unwanted] %} + {%- if pkg %} iscsi-isns-package-clean-{{ pkg }}-removed: pkg.purged: - name: {{ pkg }} - require: - sls: {{ sls_config_clean }} + - sls: {{ sls_service_clean }} + {%- endif %} {% endfor %} diff --git a/iscsi/isns/service/clean.sls b/iscsi/isns/service/clean.sls index 1cb1af29..9c968d1e 100644 --- a/iscsi/isns/service/clean.sls +++ b/iscsi/isns/service/clean.sls @@ -9,7 +9,7 @@ include: - {{ sls_config_clean }} -iscsi-isns-service-clean-service-dead +iscsi-isns-service-clean-service-dead: service.dead: - name: {{ iscsi.config.servicename[iscsi.isns.provider] }} - enable: False diff --git a/iscsi/isns/service/install.sls b/iscsi/isns/service/install.sls index 0fb27628..1c2d5ed3 100644 --- a/iscsi/isns/service/install.sls +++ b/iscsi/isns/service/install.sls @@ -46,3 +46,8 @@ iscsi-isns-service-install-failure-explanation: * your kernel was upgraded but not activated by reboot 'systemctl enable {{ servicename }}' && reboot - unless: {{ grains.os_family in ('MacOS', 'Windows') }} #maybe not needed but no harm + cmd.run: + - names: + - journalctl -xe -u {{ servicename }} || true + - systemctl status {{ servicename }} -l || true + - onlyif: test -x /usr/bin/systemctl || test -x /bin/systemctl || test -x /sbin/systemctl diff --git a/iscsi/target/config/clean.sls b/iscsi/target/config/clean.sls index 5c034ab8..252171f8 100644 --- a/iscsi/target/config/clean.sls +++ b/iscsi/target/config/clean.sls @@ -3,15 +3,15 @@ {#- Get the `tplroot` from `tpldir` #} {%- set tplroot = tpldir.split('/')[0] %} -{%- set sls_service_clean = tplroot ~ '.target.service.clean' %} +{%- set sls_package_clean = tplroot ~ '.target.package.clean' %} {%- from tplroot ~ "/map.jinja" import iscsi with context %} include: - - {{ sls_service_clean }} + - {{ sls_package_clean }} iscsi-target-config-clean-file-absent: file.absent: - unless: {{ grains.os in ('Amazon', 'MacOS') }} - - name: {{ iscsi.config.name['target'] }} + - name: {{ iscsi.config.name[iscsi.target.provider|string] }} - watch_in: - - sls: {{ sls_service_clean }} + - sls: {{ sls_package_clean }} diff --git a/iscsi/target/package/clean.sls b/iscsi/target/package/clean.sls index c5cdd65c..c660b279 100644 --- a/iscsi/target/package/clean.sls +++ b/iscsi/target/package/clean.sls @@ -4,17 +4,22 @@ {#- Get the `tplroot` from `tpldir` #} {%- set tplroot = tpldir.split('/')[0] %} {%- set sls_config_clean = tplroot ~ '.target.config.clean' %} +{%- set sls_service_clean = tplroot ~ '.target.service.clean' %} {%- from tplroot ~ "/map.jinja" import iscsi with context %} include: - {{ sls_config_clean }} + - {{ sls_service_clean }} {%- for pkg in [iscsi.target.pkgs.wanted, iscsi.target.pkgs.unwanted] %} + {%- if pkg %} iscsi-target-package-clean-{{ pkg }}-removed: pkg.purged: - name: {{ pkg }} - require: - sls: {{ sls_config_clean }} + - sls: {{ sls_service_clean }} + {%- endif %} {% endfor %} diff --git a/iscsi/target/service/clean.sls b/iscsi/target/service/clean.sls index c00fc1b9..70e92304 100644 --- a/iscsi/target/service/clean.sls +++ b/iscsi/target/service/clean.sls @@ -9,7 +9,7 @@ include: - {{ sls_config_clean }} -iscsi-target-service-clean-service-dead +iscsi-target-service-clean-service-dead: service.dead: - unless: {{ grains.os in ('Amazon', 'MacOS') }} - name: {{ iscsi.config.servicename[iscsi.target.provider] }} @@ -17,10 +17,13 @@ iscsi-target-service-clean-service-dead - require_in: - sls: {{ sls_config_clean }} -iscsi-target-service-install-file-line-freebsd: + {%- if grains.os_family == 'FreeBSD' %} + +iscsi-target-service-clean-file-line-freebsd: file.line: - - onlyif: {{ grains.os_family == 'FreeBSD' }} - name: {{ iscsi.config.name.modprobe }} - content: 'ctld_env="-u"' - mode: delete - backup: True + + {%- endif %} diff --git a/iscsi/target/service/install.sls b/iscsi/target/service/install.sls index bd68fea3..8ea57ea5 100644 --- a/iscsi/target/service/install.sls +++ b/iscsi/target/service/install.sls @@ -16,6 +16,7 @@ iscsi-target-service-install-file-line-freebsd: - content: 'ctld_env="-u"' - backup: True {%- if iscsi.target.enabled %} + - create: True - mode: ensure - after: 'autoboot_delay.*' {%- else %} @@ -55,3 +56,9 @@ iscsi-target-service-install-failure-explanation: * your kernel was upgraded but not activated by reboot 'systemctl enable {{ servicename }}' && reboot - unless: {{ grains.os_family in ('MacOS', 'Windows') }} #maybe not needed but no harm + cmd.run: + - names: + - journalctl -xe -u {{ servicename }} || true + - systemctl status {{ servicename }} -l || true + - /sbin/lsmod 2>/dev/null || true + - onlyif: test -x /usr/bin/systemctl || test -x /bin/systemctl || test -x /sbin/systemctl diff --git a/kitchen.yml b/kitchen.yml index 80dd4122..4b9c74de 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -177,12 +177,49 @@ suites: inspec_tests: - path: test/integration/default - - name: centos + - name: centos7 excludes: - default-debian-10-develop-py3 - default-ubuntu-1804-2019-2-py3 - default-opensuse-leap-15-2018-3-py2 - default-amazonlinux-2-2018-3-py2 + - default-centos-6-2017-7-py2 + - default-fedora-30-develop-py3 + provisioner: + dependencies: + - name: lvm + repo: git + source: https://github.com/saltstack-formulas/lvm-formula.git + state_top: + base: + '*': + - lvm.install + - lvm.files.create + - lvm.pv.create + - iscsi.initiator.clean + - iscsi.target.clean + - iscsi.isns + - iscsi.target + - iscsi.initiator + pillars: + top.sls: + base: + '*': + - iscsi + pillars_from_files: + iscsi.sls: test/integration/default/pillar.example.loopdevs-4-7 + verifier: + inspec_tests: + - path: test/integration/default + + - name: centos6 + excludes: + - default-debian-10-develop-py3 + - default-ubuntu-1804-2019-2-py3 + - default-opensuse-leap-15-2018-3-py2 + - default-amazonlinux-2-2018-3-py2 + - default-centos-7-2019-2-py3 + - default-fedora-30-develop-py3 provisioner: dependencies: - name: lvm @@ -196,7 +233,6 @@ suites: - lvm.pv.create - iscsi.initiator.clean - iscsi.target.clean - # iscsi.isns #Travis fails with "Service isns is enabled, and is dead" - iscsi.target - iscsi.initiator pillars: From b562cb02db28c2fbdc2c96b9c3a298be93eed7a1 Mon Sep 17 00:00:00 2001 From: N Date: Fri, 20 Sep 2019 18:29:46 +0100 Subject: [PATCH 29/42] test(travis): fix centos7 kernel --- .travis.yml | 2 +- iscsi/osfingermap.yaml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2d141888..2e1631fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ env: - INSTANCE: default-amazonlinux-2-2018-3-py2 # - INSTANCE: default-debian-8-2017-7-py2 # - INSTANCE: default-ubuntu-1604-2017-7-py2 - - INSTANCE: default-centos-6-2017-7-py2 + # INSTANCE: default-centos-6-2017-7-py2 # - INSTANCE: default-fedora-29-2017-7-py2 # - INSTANCE: default-opensuse-leap-15-2017-7-py2 # - INSTANCE: default-amazonlinux-2-2017-7-py2 diff --git a/iscsi/osfingermap.yaml b/iscsi/osfingermap.yaml index 4ae9684b..5ae1fe96 100644 --- a/iscsi/osfingermap.yaml +++ b/iscsi/osfingermap.yaml @@ -26,6 +26,8 @@ CentOS-6: tgtd: tgtd CentOS-7: + kernel: + mess_with_kernel: True initiator: pkgs: wanted: @@ -41,6 +43,8 @@ CentOS-7: - udisks2-iscsi CentOS-8: + kernel: + mess_with_kernel: True isns: pkgs: wanted: From 9c50ef9647c09a9ca4a03427301085da7821de62 Mon Sep 17 00:00:00 2001 From: N Date: Fri, 20 Sep 2019 18:44:02 +0100 Subject: [PATCH 30/42] fix(target): increase volsize and minor fixes --- iscsi/initiator/kernel/clean.sls | 2 +- iscsi/initiator/kernel/install.sls | 4 ++-- iscsi/target/kernel/clean.sls | 2 +- iscsi/target/kernel/install.sls | 23 +++++++++++-------- pillar.example | 8 +++---- .../default/pillar.example.loopdevs-4-7 | 8 +++---- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/iscsi/initiator/kernel/clean.sls b/iscsi/initiator/kernel/clean.sls index 5a064cb0..50593763 100644 --- a/iscsi/initiator/kernel/clean.sls +++ b/iscsi/initiator/kernel/clean.sls @@ -23,7 +23,7 @@ iscsi-initiator-kernel-clean-file-line: file.line: - onlyif: {{ iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} - name: {{ iscsi.config.name.modprobe }} - - content: {{ data.config.kmodule[provider]['text'] }} + - content: {{ iscsi.config.kmodule[provider]['text'] }} - backup: True - mode: delete diff --git a/iscsi/initiator/kernel/install.sls b/iscsi/initiator/kernel/install.sls index 589d6c2f..53205318 100644 --- a/iscsi/initiator/kernel/install.sls +++ b/iscsi/initiator/kernel/install.sls @@ -8,7 +8,7 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.initiator.provider %} - {%- if iscsi.kernel.mess_with_kernel and provider in iscsi.config.kmodule %} + {%- if iscsi.kernel.mess_with_kernel %} include: - {{ sls_service_install }} @@ -16,7 +16,7 @@ iscsi-initiator-kernel-install-file-line: file.line: - onlyif: iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} - name: {{ iscsi.config.name.modprobe }} - - content: {{ data.config.kmodule[provider]['text'] }} + - content: {{ iscsi.config.kmodule[provider]['text'] }} - backup: True {%- if not iscsi.initiator.enabled %} - mode: delete diff --git a/iscsi/target/kernel/clean.sls b/iscsi/target/kernel/clean.sls index 06c0a0ca..9f426574 100644 --- a/iscsi/target/kernel/clean.sls +++ b/iscsi/target/kernel/clean.sls @@ -23,7 +23,7 @@ iscsi-target-kernel-clean-file-line: file.line: - onlyif: {{ iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} - name: {{ iscsi.config.name.modprobe }} - - content: {{ data.config.kmodule[provider]['text'] }} + - content: {{ iscsi.config.kmodule[provider]['text'] }} - backup: True - mode: delete diff --git a/iscsi/target/kernel/install.sls b/iscsi/target/kernel/install.sls index b2ec9c81..f9d47a5a 100644 --- a/iscsi/target/kernel/install.sls +++ b/iscsi/target/kernel/install.sls @@ -8,7 +8,7 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.target.provider %} - {%- if iscsi.kernel.mess_with_kernel and provider in iscsi.config.kmodule %} + {%- if iscsi.kernel.mess_with_kernel %} include: - {{ sls_service_install }} @@ -16,27 +16,30 @@ iscsi-target-kernel-install-file-line: file.line: - onlyif: {{ iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} - name: {{ iscsi.config.name.modprobe }} - - content: {{ data.config.kmodule[provider]['text'] }} + - content: {{ iscsi.config.kmodule[provider]['text'] }} - require_in: - cmd: iscsi-target-kernel-install-file-line - backup: True - -iscsi-target-kernel-install-cmd-run: - {%- if not iscsi.target.enabled %} - cmd.run: - - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} - - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} - - mode: delete - {%- else %} + {%- if iscsi.target.enabled %} + - create: True - mode: ensure - after: autoboot_delay.*$ cmd.run: - name: {{ iscsi.kernel.modload }} {{ iscsi.config.kmodule[provider]['name'] }} - unless: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} + {%- else %} + cmd.run: + - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} + - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} + - mode: delete {%- endif %} - require: - file: iscsi-target-kernel-install-file-line - require_in: - sls: {{ sls_service_install }} + {%- else %} +bob: + cmd.run: + - name: echo hi {%- endif %} diff --git a/pillar.example b/pillar.example index 54de59ed..0517eb71 100644 --- a/pillar.example +++ b/pillar.example @@ -29,22 +29,22 @@ lvm: options: if: /dev/urandom bs: 1024 - count: 204800 + count: 307200 /tmp/loopdevs/testfile1.img: options: if: /dev/urandom bs: 1024 - count: 204800 + count: 307200 /tmp/loopdevs/testfile2.img: options: if: /dev/urandom bs: 1024 - count: 204800 + count: 307200 /tmp/loopdevs/testfile3.img: options: if: /dev/urandom bs: 1024 - count: 204800 + count: 307200 losetup: /tmp/loopdevs/testfile0.img: {} /tmp/loopdevs/testfile1.img: {} diff --git a/test/integration/default/pillar.example.loopdevs-4-7 b/test/integration/default/pillar.example.loopdevs-4-7 index a4fe1cdf..033e4098 100644 --- a/test/integration/default/pillar.example.loopdevs-4-7 +++ b/test/integration/default/pillar.example.loopdevs-4-7 @@ -32,22 +32,22 @@ lvm: options: if: /dev/urandom bs: 1024 - count: 204800 + count: 307200 /tmp/loopdevs/testfile1.img: options: if: /dev/urandom bs: 1024 - count: 204800 + count: 307200 /tmp/loopdevs/testfile2.img: options: if: /dev/urandom bs: 1024 - count: 204800 + count: 307200 /tmp/loopdevs/testfile3.img: options: if: /dev/urandom bs: 1024 - count: 204800 + count: 307200 losetup: /tmp/loopdevs/testfile0.img: {} /tmp/loopdevs/testfile1.img: {} From 9aac15a55dd15072dd96c7c0e45b2eb5423611bf Mon Sep 17 00:00:00 2001 From: N Date: Fri, 20 Sep 2019 22:51:58 +0100 Subject: [PATCH 31/42] fix(kernelmod): refactor kernel states --- iscsi/defaults.yaml | 2 +- iscsi/initiator/kernel/clean.sls | 2 +- iscsi/initiator/kernel/install.sls | 2 +- iscsi/isns/service/install.sls | 2 +- iscsi/osfingermap.yaml | 4 ---- iscsi/target/kernel/clean.sls | 2 +- iscsi/target/kernel/install.sls | 6 +----- 7 files changed, 6 insertions(+), 14 deletions(-) diff --git a/iscsi/defaults.yaml b/iscsi/defaults.yaml index 80f9a8d9..951fcfc7 100644 --- a/iscsi/defaults.yaml +++ b/iscsi/defaults.yaml @@ -10,6 +10,7 @@ iscsi: # Just here for testing added_in_defaults: defaults_value winner: defaults + loadkernelmodule: False config: acl: @@ -85,7 +86,6 @@ iscsi: open-iscsi: {} kernel: - mess_with_kernel: False modload: modprobe modunload: modprobe -r modquery: modinfo diff --git a/iscsi/initiator/kernel/clean.sls b/iscsi/initiator/kernel/clean.sls index 50593763..d2d55645 100644 --- a/iscsi/initiator/kernel/clean.sls +++ b/iscsi/initiator/kernel/clean.sls @@ -8,7 +8,7 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.initiator.provider %} - {%- if iscsi.kernel.mess_with_kernel and provider in iscsi.config.kmodule %} + {%- if provider in iscsi.config.kmodule %} include: - {{ sls_service_clean }} diff --git a/iscsi/initiator/kernel/install.sls b/iscsi/initiator/kernel/install.sls index 53205318..418c594f 100644 --- a/iscsi/initiator/kernel/install.sls +++ b/iscsi/initiator/kernel/install.sls @@ -8,7 +8,7 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.initiator.provider %} - {%- if iscsi.kernel.mess_with_kernel %} + {%- if provider in iscsi.config.kmodule %} include: - {{ sls_service_install }} diff --git a/iscsi/isns/service/install.sls b/iscsi/isns/service/install.sls index 1c2d5ed3..f78d2ca0 100644 --- a/iscsi/isns/service/install.sls +++ b/iscsi/isns/service/install.sls @@ -32,7 +32,7 @@ iscsi-isns-service-install-service-running: {%- else %} - name: {{ servicename }} {%- endif %} - {%- if iscsi.kernel.mess_with_kernel and provider in iscsi.config.kmodule %} + {%- if provider in iscsi.config.kmodule %} {%- if 'name' in iscsi.config.kmodule[provider] %} - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} {%- endif %} diff --git a/iscsi/osfingermap.yaml b/iscsi/osfingermap.yaml index 5ae1fe96..4ae9684b 100644 --- a/iscsi/osfingermap.yaml +++ b/iscsi/osfingermap.yaml @@ -26,8 +26,6 @@ CentOS-6: tgtd: tgtd CentOS-7: - kernel: - mess_with_kernel: True initiator: pkgs: wanted: @@ -43,8 +41,6 @@ CentOS-7: - udisks2-iscsi CentOS-8: - kernel: - mess_with_kernel: True isns: pkgs: wanted: diff --git a/iscsi/target/kernel/clean.sls b/iscsi/target/kernel/clean.sls index 9f426574..d245f10f 100644 --- a/iscsi/target/kernel/clean.sls +++ b/iscsi/target/kernel/clean.sls @@ -8,7 +8,7 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.target.provider %} - {%- if iscsi.kernel.mess_with_kernel and provider in iscsi.config.kmodule %} + {%- if provider in iscsi.config.kmodule %} include: - {{ sls_service_clean }} diff --git a/iscsi/target/kernel/install.sls b/iscsi/target/kernel/install.sls index f9d47a5a..b50382f2 100644 --- a/iscsi/target/kernel/install.sls +++ b/iscsi/target/kernel/install.sls @@ -8,7 +8,7 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.target.provider %} - {%- if iscsi.kernel.mess_with_kernel %} + {%- if provider in iscsi.config.kmodule %} include: - {{ sls_service_install }} @@ -38,8 +38,4 @@ iscsi-target-kernel-install-file-line: - require_in: - sls: {{ sls_service_install }} - {%- else %} -bob: - cmd.run: - - name: echo hi {%- endif %} From a31079c2759a40de93362f1e0af0bfce5ee81e49 Mon Sep 17 00:00:00 2001 From: N Date: Fri, 20 Sep 2019 23:17:31 +0100 Subject: [PATCH 32/42] fix(clean): fix clean kernel state --- iscsi/initiator/kernel/clean.sls | 1 + iscsi/initiator/kernel/install.sls | 2 +- iscsi/initiator/service/clean.sls | 1 + iscsi/target/kernel/clean.sls | 1 + iscsi/target/kernel/install.sls | 2 +- iscsi/target/service/clean.sls | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/iscsi/initiator/kernel/clean.sls b/iscsi/initiator/kernel/clean.sls index d2d55645..217db287 100644 --- a/iscsi/initiator/kernel/clean.sls +++ b/iscsi/initiator/kernel/clean.sls @@ -26,5 +26,6 @@ iscsi-initiator-kernel-clean-file-line: - content: {{ iscsi.config.kmodule[provider]['text'] }} - backup: True - mode: delete + - quiet: True {%- endif %} diff --git a/iscsi/initiator/kernel/install.sls b/iscsi/initiator/kernel/install.sls index 418c594f..9026828f 100644 --- a/iscsi/initiator/kernel/install.sls +++ b/iscsi/initiator/kernel/install.sls @@ -26,7 +26,7 @@ iscsi-initiator-kernel-install-file-line: {%- else %} - create: True - mode: ensure - - after: autoboot_delay.*$ + - match: None cmd.run: - name: {{ iscsi.kernel.modload }} {{ iscsi.config.kmodule[provider]['name'] }} - unless: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} diff --git a/iscsi/initiator/service/clean.sls b/iscsi/initiator/service/clean.sls index 6f6ad7cf..3da57022 100644 --- a/iscsi/initiator/service/clean.sls +++ b/iscsi/initiator/service/clean.sls @@ -24,5 +24,6 @@ iscsi-initiator-service-clean-file-line-freebsd: - content: 'ctld_env="-u"' - mode: delete - backup: True + - quiet: True {%- endif %} diff --git a/iscsi/target/kernel/clean.sls b/iscsi/target/kernel/clean.sls index d245f10f..89530756 100644 --- a/iscsi/target/kernel/clean.sls +++ b/iscsi/target/kernel/clean.sls @@ -26,5 +26,6 @@ iscsi-target-kernel-clean-file-line: - content: {{ iscsi.config.kmodule[provider]['text'] }} - backup: True - mode: delete + - quiet: True {%- endif %} diff --git a/iscsi/target/kernel/install.sls b/iscsi/target/kernel/install.sls index b50382f2..b085a466 100644 --- a/iscsi/target/kernel/install.sls +++ b/iscsi/target/kernel/install.sls @@ -23,7 +23,7 @@ iscsi-target-kernel-install-file-line: {%- if iscsi.target.enabled %} - create: True - mode: ensure - - after: autoboot_delay.*$ + - match: None cmd.run: - name: {{ iscsi.kernel.modload }} {{ iscsi.config.kmodule[provider]['name'] }} - unless: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} diff --git a/iscsi/target/service/clean.sls b/iscsi/target/service/clean.sls index 70e92304..5485bf5e 100644 --- a/iscsi/target/service/clean.sls +++ b/iscsi/target/service/clean.sls @@ -25,5 +25,6 @@ iscsi-target-service-clean-file-line-freebsd: - content: 'ctld_env="-u"' - mode: delete - backup: True + - quiet: True {%- endif %} From d4d176f85f6aa253d48207bb527583bfcb3a34a7 Mon Sep 17 00:00:00 2001 From: N Date: Fri, 20 Sep 2019 23:30:01 +0100 Subject: [PATCH 33/42] fix(freebsd): loader.conf is existing file --- iscsi/defaults.yaml | 2 +- iscsi/initiator/kernel/install.sls | 11 +++++++++-- iscsi/target/kernel/install.sls | 9 ++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/iscsi/defaults.yaml b/iscsi/defaults.yaml index 951fcfc7..e848e97f 100644 --- a/iscsi/defaults.yaml +++ b/iscsi/defaults.yaml @@ -59,7 +59,7 @@ iscsi: name: iscsi_initiator text: 'iscsi_initiator_load="YES"' ctld: - name: ctld + name: text: 'cfiscsi_load="YES"' ietd: name: iscsi_trgt diff --git a/iscsi/initiator/kernel/install.sls b/iscsi/initiator/kernel/install.sls index 9026828f..eb84081a 100644 --- a/iscsi/initiator/kernel/install.sls +++ b/iscsi/initiator/kernel/install.sls @@ -14,20 +14,27 @@ include: iscsi-initiator-kernel-install-file-line: file.line: - - onlyif: iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} + - onlyif: {{ iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} - name: {{ iscsi.config.name.modprobe }} - content: {{ iscsi.config.kmodule[provider]['text'] }} - backup: True {%- if not iscsi.initiator.enabled %} - mode: delete cmd.run: + - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['name'] }} - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} {%- else %} - - create: True + {%- if grains.os_family in ('FreeBSD',) %} + - mode: ensure + - after: 'autoboot_delay.*' + {%- else %} - mode: ensure + - create: True - match: None + {%- endif %} cmd.run: + - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['name'] }} - name: {{ iscsi.kernel.modload }} {{ iscsi.config.kmodule[provider]['name'] }} - unless: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} {%- endif %} diff --git a/iscsi/target/kernel/install.sls b/iscsi/target/kernel/install.sls index b085a466..cb7a0622 100644 --- a/iscsi/target/kernel/install.sls +++ b/iscsi/target/kernel/install.sls @@ -21,14 +21,21 @@ iscsi-target-kernel-install-file-line: - cmd: iscsi-target-kernel-install-file-line - backup: True {%- if iscsi.target.enabled %} - - create: True + {%- if grains.os_family in ('FreeBSD',) %} + - mode: replace + - after: 'autoboot_delay.*' + {%- else %} - mode: ensure + - create: True - match: None + {%- endif %} cmd.run: + - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['name'] }} - name: {{ iscsi.kernel.modload }} {{ iscsi.config.kmodule[provider]['name'] }} - unless: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} {%- else %} cmd.run: + - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['name'] }} - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} - mode: delete From f72fe9ef66a416e2b47d68a801afe4e84cf03353 Mon Sep 17 00:00:00 2001 From: N Date: Sat, 21 Sep 2019 00:52:51 +0100 Subject: [PATCH 34/42] fix(saltbug): file.line is buggy; use file.prepend --- iscsi/defaults.yaml | 8 ++++--- iscsi/initiator/kernel/clean.sls | 4 ++-- iscsi/initiator/kernel/install.sls | 37 +++++++++++------------------- iscsi/osfamilymap.yaml | 6 +++++ iscsi/osfingermap.yaml | 2 ++ iscsi/target/kernel/clean.sls | 10 ++++---- iscsi/target/kernel/install.sls | 33 ++++++++------------------ 7 files changed, 43 insertions(+), 57 deletions(-) diff --git a/iscsi/defaults.yaml b/iscsi/defaults.yaml index e848e97f..64b55a4a 100644 --- a/iscsi/defaults.yaml +++ b/iscsi/defaults.yaml @@ -63,13 +63,13 @@ iscsi: text: 'cfiscsi_load="YES"' ietd: name: iscsi_trgt - text: iscsi_trgt + text: lio: name: target_core_mod - text: target_core_mod + text: fcoe: name: fcoe_target - text: fcoe_target + text: iser: name: ('ib_iser', 'ib_isert', 'rdma_ucm', 'rdma_cm', 'ib_cm',) text: null @@ -103,6 +103,7 @@ iscsi: initiator: enabled: True + loadmodule: False provider: open-iscsi pkghold: False pkgs: @@ -114,6 +115,7 @@ iscsi: target: port: 3260 + loadmodule: False enabled: True provider: lio pkghold: False diff --git a/iscsi/initiator/kernel/clean.sls b/iscsi/initiator/kernel/clean.sls index 217db287..4f2c259c 100644 --- a/iscsi/initiator/kernel/clean.sls +++ b/iscsi/initiator/kernel/clean.sls @@ -8,13 +8,13 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.initiator.provider %} - {%- if provider in iscsi.config.kmodule %} + {%- if iscsi.target.loadmodule and iscsi.config.kmodule[provider]['name'] %} include: - {{ sls_service_clean }} iscsi-initiator-kernel-clean-cmd-run: cmd.run: - - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} + - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} || true - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} - require: - sls: {{ sls_service_clean }} diff --git a/iscsi/initiator/kernel/install.sls b/iscsi/initiator/kernel/install.sls index eb84081a..83a8ed04 100644 --- a/iscsi/initiator/kernel/install.sls +++ b/iscsi/initiator/kernel/install.sls @@ -8,38 +8,27 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.initiator.provider %} - {%- if provider in iscsi.config.kmodule %} + {%- if iscsi.initiator.loadmodule and iscsi.config.kmodule[provider]['name'] %} include: - {{ sls_service_install }} -iscsi-initiator-kernel-install-file-line: - file.line: - - onlyif: {{ iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} +iscsi-initiator-kernel-install-file-prepend: + file.prepend: + - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['text'] }} - name: {{ iscsi.config.name.modprobe }} - - content: {{ iscsi.config.kmodule[provider]['text'] }} - - backup: True - {%- if not iscsi.initiator.enabled %} - - mode: delete - cmd.run: - - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['name'] }} - - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} - - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} - {%- else %} - {%- if grains.os_family in ('FreeBSD',) %} - - mode: ensure - - after: 'autoboot_delay.*' - {%- else %} - - mode: ensure - - create: True - - match: None - {%- endif %} + - text: {{ iscsi.config.kmodule[provider]['text'] }} + - makedirs: True + +iscsi-initiator-kernel-install-cmd-run: + {%- if iscsi.initiator.enabled %} cmd.run: - - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['name'] }} - name: {{ iscsi.kernel.modload }} {{ iscsi.config.kmodule[provider]['name'] }} - unless: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} + {%- else %} + cmd.run: + - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} + - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} {%- endif %} - - require: - - file: iscsi-initiator-kernel-install-file-line - require_in: - sls: {{ sls_service_install }} diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index f3501824..e351e1c0 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -105,12 +105,17 @@ Arch: open-iscsi: - iscsi - iscsid + kmodule: + lio: + name: + text: initiator: make: gitrepo: https://aur.archlinux.org cmd: makepkg -si --noconfirm -f wanted: [] target: + loadmodule: True pkgs: wanted: - linux-lts # For kernel scsi modules @@ -133,6 +138,7 @@ FreeBSD: wanted: - net/open-isns target: + loadmodule: True provider: ctld pkgs: wanted: diff --git a/iscsi/osfingermap.yaml b/iscsi/osfingermap.yaml index 4ae9684b..9fa9e1bc 100644 --- a/iscsi/osfingermap.yaml +++ b/iscsi/osfingermap.yaml @@ -33,6 +33,7 @@ CentOS-7: - libiscsi - libiscsi-utils target: + loadmodule: True pkgs: wanted: - yum-plugin-versionlock @@ -52,6 +53,7 @@ CentOS-8: - libiscsi - libiscsi-utils target: + loadmodule: True pkgs: wanted: - yum-plugin-versionlock diff --git a/iscsi/target/kernel/clean.sls b/iscsi/target/kernel/clean.sls index 89530756..77ad3831 100644 --- a/iscsi/target/kernel/clean.sls +++ b/iscsi/target/kernel/clean.sls @@ -8,24 +8,24 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.target.provider %} - {%- if provider in iscsi.config.kmodule %} + {%- if iscsi.target.loadmodule and iscsi.config.kmodule[provider]['name'] %} include: - {{ sls_service_clean }} iscsi-target-kernel-clean-cmd-run: cmd.run: - - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} + - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} || true - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} - require: - sls: {{ sls_service_clean }} iscsi-target-kernel-clean-file-line: file.line: - - onlyif: {{ iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} + - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['text'] }} - name: {{ iscsi.config.name.modprobe }} - content: {{ iscsi.config.kmodule[provider]['text'] }} - backup: True - mode: delete - - quiet: True + - quiet: True #<-- not working? - {%- endif %} + {%- endif %} diff --git a/iscsi/target/kernel/install.sls b/iscsi/target/kernel/install.sls index cb7a0622..50fc707b 100644 --- a/iscsi/target/kernel/install.sls +++ b/iscsi/target/kernel/install.sls @@ -8,40 +8,27 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.target.provider %} - {%- if provider in iscsi.config.kmodule %} + {%- if iscsi.target.loadmodule and iscsi.config.kmodule[provider]['name'] %} include: - {{ sls_service_install }} -iscsi-target-kernel-install-file-line: - file.line: - - onlyif: {{ iscsi.config.name.modprobe and 'text' in iscsi.config.kmodule[provider] }} +iscsi-target-kernel-install-file-prepend: + file.prepend: + - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['text'] }} - name: {{ iscsi.config.name.modprobe }} - - content: {{ iscsi.config.kmodule[provider]['text'] }} - - require_in: - - cmd: iscsi-target-kernel-install-file-line - - backup: True + - text: {{ iscsi.config.kmodule[provider]['text'] }} + - makedirs: True + +iscsi-target-kernel-install-cmd-run: {%- if iscsi.target.enabled %} - {%- if grains.os_family in ('FreeBSD',) %} - - mode: replace - - after: 'autoboot_delay.*' - {%- else %} - - mode: ensure - - create: True - - match: None - {%- endif %} cmd.run: - - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['name'] }} - - name: {{ iscsi.kernel.modload }} {{ iscsi.config.kmodule[provider]['name'] }} + - name: {{ iscsi.kernel.modload }} {{ iscsi.config.kmodule[provider]['name'] }} || true - unless: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} {%- else %} cmd.run: - - onlyif: {{ iscsi.config.name.modprobe and iscsi.config.kmodule[provider]['name'] }} - - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} + - name: {{ iscsi.kernel.modunload }} {{ iscsi.config.kmodule[provider]['name'] }} || true - onlyif: {{ iscsi.kernel.modquery }} {{ iscsi.config.kmodule[provider]['name'] }} - - mode: delete {%- endif %} - - require: - - file: iscsi-target-kernel-install-file-line - require_in: - sls: {{ sls_service_install }} From 1a6161fbb3af0a1ffb810caa0b141fc8f03dc997 Mon Sep 17 00:00:00 2001 From: N Date: Sat, 21 Sep 2019 12:13:05 +0100 Subject: [PATCH 35/42] docs(lint): fix lint errors & skip service on centos/travis --- iscsi/defaults.yaml | 40 +- iscsi/osfamilymap.yaml | 10 +- iscsi/osfingermap.yaml | 6 +- iscsi/osmap.yaml | 2 +- kitchen.yml | 38 +- pillar.example | 10 +- test/salt/pillar/centos6.sls | 46 -- .../pillar/pillar.example} | 11 +- test/salt/pillar/pillar.travis | 639 ++++++++++++++++++ 9 files changed, 696 insertions(+), 106 deletions(-) delete mode 100644 test/salt/pillar/centos6.sls rename test/{integration/default/pillar.example.loopdevs-4-7 => salt/pillar/pillar.example} (99%) create mode 100644 test/salt/pillar/pillar.travis diff --git a/iscsi/defaults.yaml b/iscsi/defaults.yaml index 64b55a4a..b362ba1a 100644 --- a/iscsi/defaults.yaml +++ b/iscsi/defaults.yaml @@ -3,20 +3,20 @@ --- iscsi: filemode: '0640' - user: iscsimake #archlinux only + user: iscsimake # archlinux only rootgroup: root subcomponent: config: '/etc/iscsi-subcomponent-formula.conf' # Just here for testing added_in_defaults: defaults_value winner: defaults - loadkernelmodule: False + loadkernelmodule: false config: acl: allow: ietd: /etc/ietd/initiators.allow - deny: + deny: ietd: /etc/ietd/initiators.deny name: modprobe: /etc/modprobe.d/iscsi-modules.conf @@ -27,21 +27,21 @@ iscsi: ietd: /etc/ietd.conf lio: /etc/target/saveconfig.json isns: /etc/isns/isnsd.conf - isnsadm: False - isnsdd: False + isnsadm: false + isnsdd: false open-iscsi: /etc/iscsi/iscsid.conf servicename: iscsi: iscsid - fcoe: #Fibre-Channel over Ethernet (FCoE) Target + fcoe: # Fibre-Channel over Ethernet (FCoE) Target - fcoe - lldpad - fcoe-target - tgtd: tgt #TGT Project (userspace) RedHat/Debian/Arch/Gentoo - ctld: ctld #CAM Target Layer/iSCSI target daemon, FreeBSD. - ietd: iscsitarget #iSCSI Enterprise Target (IET) project - isns: isnsd #Open-iSNS implements iSNS protocol (RFC4171) - lio: target #LIO is defacto standard on enterprise linux - open-iscsi: open-iscsi #Open-iSCSI + tgtd: tgt # TGT Project (userspace) RedHat/Debian/Arch/Gentoo + ctld: ctld # CAM Target Layer/iSCSI target daemon, FreeBSD. + ietd: iscsitarget # iSCSI Enterprise Target (IET) project + isns: isnsd # Open-iSNS implements iSNS protocol (RFC4171) + lio: target # LIO is defacto standard on enterprise linux + open-iscsi: open-iscsi # Open-iSCSI servicetext: iscsi: 'iscsictl_enable="YES"' ctld: 'iscsid_enable="YES"' @@ -91,9 +91,9 @@ iscsi: modquery: modinfo isns: - enabled: True + enabled: true provider: isns - pkghold: False + pkghold: false pkgs: wanted: - open-isns @@ -102,10 +102,10 @@ iscsi: wanted: [] initiator: - enabled: True - loadmodule: False + enabled: true + loadmodule: false provider: open-iscsi - pkghold: False + pkghold: false pkgs: wanted: - open-iscsi @@ -115,10 +115,10 @@ iscsi: target: port: 3260 - loadmodule: False - enabled: True + enabled: true + loadmodule: false provider: lio - pkghold: False + pkghold: false pkgs: wanted: [] unwanted: [] diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index e351e1c0..dd82230e 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -15,7 +15,7 @@ Debian: servicename: isns: isnsd initiator: - enabled: False #see https://github.com/saltstack-formulas/iscsi-formula/issues/10 + enabled: false # see https://github.com/saltstack-formulas/iscsi-formula/issues/10 pkgs: wanted: - open-iscsi @@ -56,7 +56,7 @@ RedHat: Suse: config: - servicename: + servicename: open-iscsi: iscsid lio: targetcli isns: @@ -107,7 +107,7 @@ Arch: - iscsid kmodule: lio: - name: + name: text: initiator: make: @@ -115,7 +115,7 @@ Arch: cmd: makepkg -si --noconfirm -f wanted: [] target: - loadmodule: True + loadmodule: true pkgs: wanted: - linux-lts # For kernel scsi modules @@ -138,7 +138,7 @@ FreeBSD: wanted: - net/open-isns target: - loadmodule: True + loadmodule: true provider: ctld pkgs: wanted: diff --git a/iscsi/osfingermap.yaml b/iscsi/osfingermap.yaml index 9fa9e1bc..7b04e5d0 100644 --- a/iscsi/osfingermap.yaml +++ b/iscsi/osfingermap.yaml @@ -33,7 +33,7 @@ CentOS-7: - libiscsi - libiscsi-utils target: - loadmodule: True + loadmodule: true pkgs: wanted: - yum-plugin-versionlock @@ -53,12 +53,12 @@ CentOS-8: - libiscsi - libiscsi-utils target: - loadmodule: True + loadmodule: true pkgs: wanted: - yum-plugin-versionlock - targetcli - libvirt-daemon-driver-storage-iscsi - libvirt-daemon-driver-storage-iscsi-direct - - thin-provisioning-tools + # thin-provisioning-tools - udisks2-iscsi diff --git a/iscsi/osmap.yaml b/iscsi/osmap.yaml index 90c854f2..4a423be7 100644 --- a/iscsi/osmap.yaml +++ b/iscsi/osmap.yaml @@ -53,7 +53,7 @@ Fedora: provider: tgtd pkgs: wanted: - - device-mapper-persistent-data + - device-mapper-persistent-data #aka thin-provisioning-tools - netbsd-iscsi - yum-plugin-versionlock - targetcli diff --git a/kitchen.yml b/kitchen.yml index 4b9c74de..31e73d70 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -151,8 +151,7 @@ verifier: suites: - name: default excludes: - - default-centos-6-2017-7-py2 - - default-centos-7-2019-2-py3 + - default-centos-7-2019-2-py3 provisioner: dependencies: - name: lvm @@ -172,19 +171,18 @@ suites: '*': - iscsi pillars_from_files: - iscsi.sls: test/integration/default/pillar.example.loopdevs-4-7 + iscsi.sls: test/salt/pillar/pillar.example verifier: inspec_tests: - path: test/integration/default - name: centos7 excludes: - - default-debian-10-develop-py3 - - default-ubuntu-1804-2019-2-py3 - - default-opensuse-leap-15-2018-3-py2 - - default-amazonlinux-2-2018-3-py2 - - default-centos-6-2017-7-py2 - - default-fedora-30-develop-py3 + - default-debian-10-develop-py3 + - default-ubuntu-1804-2019-2-py3 + - default-opensuse-leap-15-2018-3-py2 + - default-amazonlinux-2-2018-3-py2 + - default-fedora-30-develop-py3 provisioner: dependencies: - name: lvm @@ -194,6 +192,8 @@ suites: base: '*': - lvm.install + - lvm.pv.remove + - lvm.files.remove - lvm.files.create - lvm.pv.create - iscsi.initiator.clean @@ -207,19 +207,18 @@ suites: '*': - iscsi pillars_from_files: - iscsi.sls: test/integration/default/pillar.example.loopdevs-4-7 + iscsi.sls: test/salt/pillar/pillar.example verifier: inspec_tests: - path: test/integration/default - - name: centos6 + - name: centos # skip service start state on travis excludes: - - default-debian-10-develop-py3 - - default-ubuntu-1804-2019-2-py3 - - default-opensuse-leap-15-2018-3-py2 - - default-amazonlinux-2-2018-3-py2 - - default-centos-7-2019-2-py3 - - default-fedora-30-develop-py3 + - default-debian-10-develop-py3 + - default-ubuntu-1804-2019-2-py3 + - default-opensuse-leap-15-2018-3-py2 + - default-amazonlinux-2-2018-3-py2 + - default-fedora-30-develop-py3 provisioner: dependencies: - name: lvm @@ -229,10 +228,13 @@ suites: base: '*': - lvm.install + - lvm.pv.remove + - lvm.files.remove - lvm.files.create - lvm.pv.create - iscsi.initiator.clean - iscsi.target.clean + - iscsi.isns - iscsi.target - iscsi.initiator pillars: @@ -241,7 +243,7 @@ suites: '*': - iscsi pillars_from_files: - iscsi.sls: test/integration/default/pillar.example.loopdevs-4-7 + iscsi.sls: test/salt/pillar/pillar.travis verifier: inspec_tests: - path: test/integration/default diff --git a/pillar.example b/pillar.example index 0517eb71..79aab6d5 100644 --- a/pillar.example +++ b/pillar.example @@ -4,18 +4,17 @@ {% if grains.os_family in ('Arch',) %} users: iscsimake: - sudouser: True + sudouser: true shell: /bin/bash - empty_password: True + empty_password: true home: /home/iscsimake - createhome: True + createhome: true optional_groups: - wheel - root sudo_rules: - 'ALL=(ALL) ALL' {% endif %} - lvm: files: remove: @@ -58,6 +57,8 @@ lvm: /dev/loop3: {} iscsi: + #target: + # enabled: true config: data: open-iscsi: @@ -636,4 +637,3 @@ iscsi: iser: 0 tag: 2 wwn: iqn.1996-04.lx.suse:01:a66aed20e2f3 - diff --git a/test/salt/pillar/centos6.sls b/test/salt/pillar/centos6.sls deleted file mode 100644 index d769ec9c..00000000 --- a/test/salt/pillar/centos6.sls +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: ft=yaml --- -iscsi: - lookup: - master: iscsi-master - # Just for testing purposes - winner: lookup - added_in_lookup: lookup_value - - tofs: - # The files_switch key serves as a selector for alternative - # directories under the formula files directory. See TOFS pattern - # doc for more info. - # Note: Any value not evaluated by `config.get` will be used literally. - # This can be used to set custom paths, as many levels deep as required. - files_switch: - - any/path/can/be/used/here - - id - - roles - - osfinger - - os - - os_family - # All aspects of path/file resolution are customisable using the options below. - # This is unnecessary in most cases; there are sensible defaults. - # path_prefix: iscsi_alt - # dirs: - # files: files_alt - # default: default_alt - # The entries under `source_files` are prepended to the default source files - # given for the state - # source_files: - # iscsi-config-file-file-managed: - # - 'example_alt.tmpl' - # - 'example_alt.tmpl.jinja' - - # For testing purposes - source_files: - iscsi-config-file-file-managed: - - 'example.tmpl.jinja' - iscsi-subcomponent-config-file-file-managed: - - 'subcomponent-example.tmpl.jinja' - - # Just for testing purposes - winner: pillar - added_in_pillar: pillar_value diff --git a/test/integration/default/pillar.example.loopdevs-4-7 b/test/salt/pillar/pillar.example similarity index 99% rename from test/integration/default/pillar.example.loopdevs-4-7 rename to test/salt/pillar/pillar.example index 033e4098..351859f8 100644 --- a/test/integration/default/pillar.example.loopdevs-4-7 +++ b/test/salt/pillar/pillar.example @@ -4,22 +4,18 @@ {% if grains.os_family in ('Arch',) %} users: iscsimake: - sudouser: True + sudouser: true shell: /bin/bash - empty_password: True + empty_password: true home: /home/iscsimake - createhome: True + createhome: true optional_groups: - wheel - root sudo_rules: - 'ALL=(ALL) ALL' {% endif %} - lvm: - pkgs: - - lvm2 - # cryptsetup files: remove: - /tmp/loopdevs/testfile0.img @@ -639,4 +635,3 @@ iscsi: iser: 0 tag: 2 wwn: iqn.1996-04.lx.suse:01:a66aed20e2f3 - diff --git a/test/salt/pillar/pillar.travis b/test/salt/pillar/pillar.travis new file mode 100644 index 00000000..1c67d8fa --- /dev/null +++ b/test/salt/pillar/pillar.travis @@ -0,0 +1,639 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- + {% if grains.os_family in ('Arch',) %} +users: + iscsimake: + sudouser: true + shell: /bin/bash + empty_password: true + home: /home/iscsimake + createhome: true + optional_groups: + - wheel + - root + sudo_rules: + - 'ALL=(ALL) ALL' + {% endif %} +lvm: + files: + remove: + - /tmp/loopdevs/testfile0.img + - /tmp/loopdevs/testfile1.img + - /tmp/loopdevs/testfile2.img + - /tmp/loopdevs/testfile3.img + create: + dd: + /tmp/loopdevs/testfile0.img: + options: + if: /dev/urandom + bs: 1024 + count: 307200 + /tmp/loopdevs/testfile1.img: + options: + if: /dev/urandom + bs: 1024 + count: 307200 + /tmp/loopdevs/testfile2.img: + options: + if: /dev/urandom + bs: 1024 + count: 307200 + /tmp/loopdevs/testfile3.img: + options: + if: /dev/urandom + bs: 1024 + count: 307200 + losetup: + /tmp/loopdevs/testfile0.img: {} + /tmp/loopdevs/testfile1.img: {} + /tmp/loopdevs/testfile2.img: {} + /tmp/loopdevs/testfile3.img: {} + pv: + create: + /dev/loop4: {} + /dev/loop5: {} + /dev/loop6: {} + /dev/loop7: {} + +iscsi: + target: + enabled: false # centos/travis workaround to pass service.running state + config: + data: + open-iscsi: + ##https://github.com/open-iscsi/open-iscsi/blob/master/etc/iscsid.conf + node.startup: manual + node.leading_login: 'No' + node.session.timeo.replacement_timeout: 120 + node.conn[0].timeo.login_timeout: 15 + node.conn[0].timeo.logout_timeout: 15 + node.conn[0].timeo.noop_out_interval: 5 + node.conn[0].timeo.noop_out_timeout: 5 + node.session.err_timeo.abort_timeout: 15 + node.session.err_timeo.lu_reset_timeout: 30 + node.session.err_timeo.tgt_reset_timeout: 30 + node.session.initial_login_retry_max: 8 + node.session.cmds_max: 128 + node.session.queue_depth: 32 + node.session.xmit_thread_priority: -20 + node.session.iscsi.InitialR2T: 'No' + node.session.iscsi.ImmediateData: 'Yes' + node.session.iscsi.FirstBurstLength: 262144 + node.session.iscsi.MaxBurstLength: 16776192 + node.conn[0].iscsi.MaxRecvDataSegmentLength: 262144 + node.conn[0].iscsi.MaxXmitDataSegmentLength: 0 + discovery.sendtargets.iscsi.MaxRecvDataSegmentLength: 32768 + node.session.nr_sessions: 1 + node.session.iscsi.FastAbort: 'Yes' + node.session.scan: auto + + iscsi: ##freeBSD + ##https://www.freebsd.org/cgi/man.cgi?query=iscsi.conf&sektion=5&manpath=FreeBSD+10-current + node.startup: automatic + myiscsi: ##nickname + targetaddress: iscsi1 + targetname: 'iqn.1900.com.com:sn.123456' + myiscsi6: + targetaddress: '[2001:db8::de:ef]:3260' + targetname: 'iqn.1900.com.com:sn.123456' + chaptest: + targetaddress: 10.0.0.1 + targetname: 'iqn.1900.com.com:sn.123456' + initiatorname: 'iqn.2005-01.il.ac.huji.cs:nobody' + authmethod: CHAP + chapiname : 'iqn.2005-01.il.ac.huji.cs:nobody' + chapsecret: secretsecret + example01: + targetname: 'iqn.2018-07.com.example.iscsi:example01' + targetAddress: '10.10.10.10' + data: + targetname: 'naa.50015178f369f092' + targetAddress: data1.example.com + chapIName: user + chapSecret: secretsecret + secret: + targetname: 'iqn.2018-07.com.example.iscsi:secretdata' + targetAddress: creditcards.example.com + authMethod: CHAP + chapIName: 'iqn.2018-07.com.example.iscsi:trustedguy' + chapSecret: secretsecret + + ietd: + ##http://manpages.ubuntu.com/manpages/trusty/man5/ietd.conf.5.html + IncomingUser: joe secret + OutgoingUser: jack secret2 + 'Target iqn.2001-04.com.example:storage.disk2.sys1.xyz': + IncomingUser: jim othersecret + OutgoingUser: james yetanothersecret + 'Lun 0': Path=/dev/sdc,Type=fileio + 'Lun 1': Blocks=10000,BlockSize=4096,Type=nullio + Alias: Test + HeaderDigest: None + DataDigest: None + MaxConnections: 1 + MaxSessions: 0 + InitialR2T: 'Yes' + ImmediateData: 'No' + MaxRecvDataSegmentLength: 8192 + MaxXmitDataSegmentLength: 8192 + MaxBurstLength: 262144 + FirstBurstLength: 65536 + DefaultTime2Wait: 2 + DefaultTime2Retain: 0 + MaxOutstandingR2T: 8 + NOPInterval: 0 + NOPTimeout: 0 + DataPDUInOrder: 'Yes' + DataSequenceInOrder: 'Yes' + ErrorRecoveryLevel: 0 + + isnsadm: {} + isnsdd: {} + isnsd: + ##https://manpages.debian.org/testing/open-isns-server/isnsd.conf.5.en.html + Database: /var/lib/isns + RegistrationPeriod: 10m + DefaultDiscoveryDomain: 1 + SLPRegister: 1 + ClientKeyStore: 'DB:' + SCNTimeout: 60 + SCNRetries: 3 + ESIMinInterval: 1m + ESIMaxInterval: 2m + ESIRetries: 3 + Auth.ReplayWindow: 2m + Auth.TimeStampJitter: 1s + + tgtd: + ##https://linux.die.net/man/5/targets.conf + ##https://github.com/fujita/tgt/blob/master/conf/examples/targets.conf.example + include: '/etc/tgt/salt/*.conf' + default-driver: iscsi + #iSNSServerIP: 127.0.0.1 + #iSNSServerPort: 3205 + #iSNSAccessControl: Off + #iSNS: Off + 'target iqn.2008-09.com.example:server.target1': + backing-store: /dev/loop4 + 'target iqn.2008-09.com.example:server.target5': + 'direct-store /dev/loop5': + vendor_id: VENDOR1 + removable: 1 + device-type: cd + lun: 1 + 'direct-store /dev/loop6': + vendor_id: VENDOR2 + lun: 2 + 'backing-store /dev/loop7': + vendor_id: back1 + scsi_sn: SERIAL + write-cache: on + + lio: + ##https://www.systutorials.com/docs/linux/man/5-saveconfig.json + ##https://bugzilla.redhat.com/show_bug.cgi?id=1643673 + ##https://github.com/open-iscsi/rtslib-fb/issues/5 + ##---------------------------------------------------------------------- + ## The sample below is complex because its used for verification. + ## You should only use the minimal pillar data for your needs. + ## The format is important so review this example carefully. + ## + ## Multiple objects are supported: + ## See: https://github.com/saltstack-formulas/iscsi-formula/issues/19 + ##---------------------------------------------------------------------- + fabric_modules: + callmewhateveryoulike0: + discovery_enable_auth: 'true' + discovery_mutual_password: itsreallyme + discovery_mutual_userid: target + discovery_password: letmein + discovery_userid: initiator + name: iscsi + callmewhateveryoulike1: + discovery_enable_auth: 'true' + discovery_mutual_password: itsreallysticky + storage_objects: + callmewhateveryoulike_sda: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd0 + name: sda + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf090' + callmewhateveryoulike_sdb: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd1 + name: sdb + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf091' + callmewhateveryoulike_sdc: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd2 + name: sdc + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf092' + callmewhateveryoulike_sdd: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd3 + name: sdd + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf093' + callmewhateveryoulike_sde: + alua_tpgs: + callemewhateveryoulike0: + alua_access_state: 0 + alua_access_status: 0 + alua_access_type: 3 + alua_support_active_nonoptimized: 1 + alua_support_active_optimized: 1 + alua_support_offline: 1 + alua_support_standby: 1 + alua_support_transitioning: 1 + alua_support_unavailable: 1 + alua_write_metadata: 0 + implicit_trans_secs: 0 + name: default_tg_pt_gp + nonop_delay_msecs: 100 + preferred: 0 + tg_pt_gp_id: 0 + trans_delay_msecs: 0 + callemewhateveryoulike1: + alua_access_state: 1 + alua_support_active_nonoptimized: 1 + attributes: + block_size: 512 + emulate_3pc: 1 + emulate_caw: 1 + emulate_dpo: 1 + emulate_fua_read: 1 + emulate_fua_write: 1 + emulate_model_alias: 1 + emulate_pr: 1 + emulate_rest_reord: 0 + emulate_tas: 1 + emulate_tpu: 0 + emulate_tpws: 0 + emulate_ua_intlck_ctrl: 0 + emulate_write_cache: 0 + enforce_pr_isids: 1 + force_pr_aptpl: 0 + is_nonrot: 1 + max_unmap_block_desc_count: 0 + max_unmap_lba_count: 0 + max_write_same_len: 65535 + optimal_sectors: 256 + pi_prot_format: 0 + pi_prot_type: 0 + pi_prot_verify: 0 + queue_depth: 64 + unmap_granularity: 0 + unmap_granularity_alignment: 0 + unmap_zeroes_data: 0 + dev: /dev/vxdd4 + name: sde + plugin: 'block' + readonly: 'false' + write_back: 'false' + wwn: '9e8d8049-3538-41cb-94e6-6dbd2f1cf094' + targets: + canada: + fabric: iscsi + tpgs: + attributes: + authentication: 0 + cache_dynamic_acls: 1 + default_cmdsn_depth: 16 + default_erl: 0 + demo_mode_discovery: 1 + demo_mode_write_protect: 0 + fabric_prot_type: 0 + generate_node_acls: 1 + login_keys_workaround: 1 + login_timeout: 15 + netif_timeout: 2 + prod_mode_write_protect: 0 + t10_pi: 0 + tpg_enabled_sendtargets: 1 + enable: 'true' + luns: + callmewhateveryoulike0: + alias: 'd6b1e8e70a' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 0 + storage_object: /backstores/block/sda + callmewhateveryoulike1: + alias: 'd6b1e8e70b' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 1 + storage_object: /backstores/block/sdb + callmewhateveryoulike2: + alias: 'd6b1e8e70c' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 2 + storage_object: /backstores/block/sdc + callmewhateveryoulike3: + alias: 'd6b1e8e70d' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 3 + storage_object: /backstores/block/sdd + callmewhateveryoulike4: + alias: 'd6b1e8e70e' + alua_tg_pt_gp_name: default_tg_pt_gp + index: 4 + storage_object: /backstores/block/sde + node_acls: + callmewhateveryoulike0: + attributes: + dataout_timeout: 3 + dataout_timeout_retries: 5 + default_erl: 0 + nopin_response_timeout: 5 + nopin_timeout: 5 + random_datain_pdu_offsets: 0 + random_datain_seq_offsets: 0 + random_r2t_offsets: 0 + chap_mutual_password: itsreallyme + chap_mutual_userid: target + chap_password: letmein + chap_userid: station4 + mapped_luns: + mappy0: + index: 0 + tpg_lun: 0 + write_protect: 0 + mappy1: + index: 1 + tpg_lun: 1 + write_protect: 0 + mappy2: + index: 2 + tpg_lun: 2 + write_protect: 0 + mappy3: + index: 3 + tpg_lun: 3 + write_protect: 0 + mappy4: + index: 4 + tpg_lun: 4 + write_protect: 0 + node_wwn: 'iqn.1994-05.com.redhat:station4' + tcq_depth: 16 + parameters: + AuthMethod: 'CHAP,None' + DataDigest: 'CRC32C,None' + DataPDUInOrder: 'Yes' + DataSequenceInOrder: 'Yes' + DefaultTime2Retain: 20 + DefaultTime2Wait: 2 + ErrorRecoveryLevel: 0 + FirstBurstLength: 65536 + HeaderDigest: 'CRC32C,None' + IFMarkInt: Reject + IFMarker: 'No' + ImmediateData: 'Yes' + InitialR2T: 'Yes' + luxembourg: + fabric: iscsi + tpgs: + attributes: + authentication: 0 + enable: 1 + luns: + callmewhateveryoulike0: + alias: 'd6b1e8e70a' + index: 0 + storage_object: /backstores/block/sda + callmewhateveryoulike1: + alias: 'd6b1e8e70b' + index: 1 + storage_object: /backstores/block/sdb + callmewhateveryoulike2: + alias: 'd6b1e8e70c' + index: 2 + storage_object: /backstores/block/sdc + callmewhateveryoulike3: + alias: 'd6b1e8e70d' + index: 3 + storage_object: /backstores/block/sdd + callmewhateveryoulike4: + alias: 'd6b1e8e70e' + index: 4 + storage_object: /backstores/block/sde + node_acls: + callmewhateveryoulike0: + attributes: + dataout_timeout: 3 + #mapped_luns: {} + parameters: + TargetAlias: LIO Target + portals: + callmewhateveryoulike0: + ip_address: 10.0.2.254 + iser: 0 + tag: 2 + wwn: iqn.1996-04.lx.suse:01:a66aed20e2f3 From d519be1faed11e8144d10f248abf885ab0135386 Mon Sep 17 00:00:00 2001 From: N Date: Wed, 25 Sep 2019 20:11:11 +0100 Subject: [PATCH 36/42] test(arch/cent): updated kitchen tests --- .travis.yml | 4 ++ kitchen.yml | 63 ++++++++++++----------------- test/integration/default/inspec.yml | 1 + 3 files changed, 30 insertions(+), 38 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2e1631fc..788a8c5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,24 +25,28 @@ env: - INSTANCE: default-fedora-30-develop-py3 # - INSTANCE: default-opensuse-leap-15-develop-py3 # - INSTANCE: default-amazonlinux-2-develop-py2 + # INSTANCE: default-arch-base-latest-develop-py2 # - INSTANCE: default-debian-9-2019-2-py3 - INSTANCE: default-ubuntu-1804-2019-2-py3 - INSTANCE: default-centos-7-2019-2-py3 # - INSTANCE: default-fedora-30-2019-2-py3 # - INSTANCE: default-opensuse-leap-15-2019-2-py3 # INSTANCE: default-amazonlinux-2-2019-2-py2 + - INSTANCE: default-arch-base-latest-2019-2-py2 # - INSTANCE: default-debian-9-2018-3-py2 # - INSTANCE: default-ubuntu-1604-2018-3-py2 # INSTANCE: default-centos-7-2018-3-py2 # INSTANCE: default-fedora-29-2018-3-py2 - INSTANCE: default-opensuse-leap-15-2018-3-py2 - INSTANCE: default-amazonlinux-2-2018-3-py2 + # - INSTANCE: default-arch-base-latest-2018-3-py2 # - INSTANCE: default-debian-8-2017-7-py2 # - INSTANCE: default-ubuntu-1604-2017-7-py2 # INSTANCE: default-centos-6-2017-7-py2 # - INSTANCE: default-fedora-29-2017-7-py2 # - INSTANCE: default-opensuse-leap-15-2017-7-py2 # - INSTANCE: default-amazonlinux-2-2017-7-py2 + # - INSTANCE: default-arch-base-latest-2017-7-py2 script: - bin/kitchen verify ${INSTANCE} diff --git a/kitchen.yml b/kitchen.yml index 31e73d70..fd08c548 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -53,6 +53,13 @@ platforms: provision_command: - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com - sh bootstrap-salt.sh -XdPbfrq -x python2 git develop + - name: arch-base-latest-develop-py2 + driver: + image: netmanagers/salt-develop-py2:arch-base-latest + provision_command: + - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com + - sh bootstrap-salt.sh -XdPbfrq -x python2 git develop + run_command: /usr/lib/systemd/systemd ## SALT `2019.2` - name: debian-9-2019-2-py3 @@ -78,6 +85,10 @@ platforms: - name: amazonlinux-2-2019-2-py2 driver: image: netmanagers/salt-2019.2-py2:amazonlinux-2 + - name: arch-base-latest-2019-2-py2 + driver: + image: netmanagers/salt-2019.2-py2:arch-base-latest + run_command: /usr/lib/systemd/systemd ## SALT `2018.3` - name: debian-9-2018-3-py2 @@ -103,6 +114,10 @@ platforms: - name: amazonlinux-2-2018-3-py2 driver: image: netmanagers/salt-2018.3-py2:amazonlinux-2 + - name: arch-base-latest-2018-3-py2 + driver: + image: netmanagers/salt-2018.3-py2:arch-base-latest + run_command: /usr/lib/systemd/systemd ## SALT `2017.7` - name: debian-8-2017-7-py2 @@ -129,6 +144,10 @@ platforms: - name: amazonlinux-2-2017-7-py2 driver: image: netmanagers/salt-2017.7-py2:amazonlinux-2 + - name: arch-base-latest-2017-7-py2 + driver: + image: netmanagers/salt-2017.7-py2:arch-base-latest + run_command: /usr/lib/systemd/systemd provisioner: name: salt_solo @@ -176,48 +195,13 @@ suites: inspec_tests: - path: test/integration/default - - name: centos7 - excludes: - - default-debian-10-develop-py3 - - default-ubuntu-1804-2019-2-py3 - - default-opensuse-leap-15-2018-3-py2 - - default-amazonlinux-2-2018-3-py2 - - default-fedora-30-develop-py3 - provisioner: - dependencies: - - name: lvm - repo: git - source: https://github.com/saltstack-formulas/lvm-formula.git - state_top: - base: - '*': - - lvm.install - - lvm.pv.remove - - lvm.files.remove - - lvm.files.create - - lvm.pv.create - - iscsi.initiator.clean - - iscsi.target.clean - - iscsi.isns - - iscsi.target - - iscsi.initiator - pillars: - top.sls: - base: - '*': - - iscsi - pillars_from_files: - iscsi.sls: test/salt/pillar/pillar.example - verifier: - inspec_tests: - - path: test/integration/default - - - name: centos # skip service start state on travis + - name: centos # skip service start state on travis/centos excludes: - default-debian-10-develop-py3 - default-ubuntu-1804-2019-2-py3 - default-opensuse-leap-15-2018-3-py2 - default-amazonlinux-2-2018-3-py2 + - default-arch-base-latest-2018-3-py2 - default-fedora-30-develop-py3 provisioner: dependencies: @@ -235,7 +219,10 @@ suites: - iscsi.initiator.clean - iscsi.target.clean - iscsi.isns - - iscsi.target + - iscsi.target.package + - iscsi.target.make + - iscsi.target.kernel + - iscsi.target.config - iscsi.initiator pillars: top.sls: diff --git a/test/integration/default/inspec.yml b/test/integration/default/inspec.yml index ef693713..31a3b4a5 100644 --- a/test/integration/default/inspec.yml +++ b/test/integration/default/inspec.yml @@ -15,3 +15,4 @@ supports: - platform-name: suse - platform-name: freebsd - platform-name: amazon + - platform-name: arch From 4ec440cbf89e168163e6aa382dfd7fb8a0b63e5a Mon Sep 17 00:00:00 2001 From: N Date: Wed, 25 Sep 2019 22:43:38 +0100 Subject: [PATCH 37/42] fix(arch): harden jinja for archlinux --- iscsi/initiator/kernel/clean.sls | 2 +- iscsi/initiator/kernel/install.sls | 2 +- iscsi/target/kernel/clean.sls | 2 +- iscsi/target/kernel/install.sls | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/iscsi/initiator/kernel/clean.sls b/iscsi/initiator/kernel/clean.sls index 4f2c259c..b21f39d6 100644 --- a/iscsi/initiator/kernel/clean.sls +++ b/iscsi/initiator/kernel/clean.sls @@ -8,7 +8,7 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.initiator.provider %} - {%- if iscsi.target.loadmodule and iscsi.config.kmodule[provider]['name'] %} + {%- if iscsi.target.loadmodule and provider in iscsi.config.kmodule and iscsi.config.kmodule[provider]['name'] %} include: - {{ sls_service_clean }} diff --git a/iscsi/initiator/kernel/install.sls b/iscsi/initiator/kernel/install.sls index 83a8ed04..95d1800f 100644 --- a/iscsi/initiator/kernel/install.sls +++ b/iscsi/initiator/kernel/install.sls @@ -8,7 +8,7 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.initiator.provider %} - {%- if iscsi.initiator.loadmodule and iscsi.config.kmodule[provider]['name'] %} + {%- if iscsi.initiator.loadmodule and provider in iscsi.config.kmodule and iscsi.config.kmodule[provider]['name'] %} include: - {{ sls_service_install }} diff --git a/iscsi/target/kernel/clean.sls b/iscsi/target/kernel/clean.sls index 77ad3831..00458d59 100644 --- a/iscsi/target/kernel/clean.sls +++ b/iscsi/target/kernel/clean.sls @@ -8,7 +8,7 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.target.provider %} - {%- if iscsi.target.loadmodule and iscsi.config.kmodule[provider]['name'] %} + {%- if iscsi.target.loadmodule and provider in iscsi.config.kmodule and iscsi.config.kmodule[provider]['name'] %} include: - {{ sls_service_clean }} diff --git a/iscsi/target/kernel/install.sls b/iscsi/target/kernel/install.sls index 50fc707b..3d637b85 100644 --- a/iscsi/target/kernel/install.sls +++ b/iscsi/target/kernel/install.sls @@ -8,7 +8,7 @@ {%- from tplroot ~ "/libtofs.jinja" import files_switch with context %} {%- set provider = iscsi.target.provider %} - {%- if iscsi.target.loadmodule and iscsi.config.kmodule[provider]['name'] %} + {%- if iscsi.target.loadmodule and provider in iscsi.config.kmodule and iscsi.config.kmodule[provider]['name'] %} include: - {{ sls_service_install }} From b8d8b7f8acf7c6164b427e708c098bf0a5cb1fa1 Mon Sep 17 00:00:00 2001 From: N Date: Wed, 25 Sep 2019 22:47:27 +0100 Subject: [PATCH 38/42] fix(service): only start service if enabled --- .travis.yml | 23 +++++++--- iscsi/osfamilymap.yaml | 1 + iscsi/target/service/install.sls | 2 + kitchen.yml | 74 +++++++++++++++++--------------- pillar.example | 2 - 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/.travis.yml b/.travis.yml index 788a8c5d..a8c7a894 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,32 +17,43 @@ services: # Make sure the instances listed below match up with # the `platforms` defined in `kitchen.yml` +# NOTE: Please try to select up to six instances that add some meaningful +# testing of the formula's behaviour. If possible, try to refrain from +# the classical "chosing all the instances because I want to test on +# another/all distro/s" trap: it will just add time to the testing (see +# the discussion on #121). As an example, the set chosen below covers +# the most used distros families, systemd and non-systemd and the latest +# three supported Saltstack versions with python2 and 3. +# As for `kitchen.yml`, that should still contain all of the platforms, +# to allow for comprehensive local testing +# Ref: https://github.com/saltstack-formulas/template-formula/issues/118 +# Ref: https://github.com/saltstack-formulas/template-formula/issues/121 env: matrix: - INSTANCE: default-debian-10-develop-py3 # - INSTANCE: default-ubuntu-1804-develop-py3 # - INSTANCE: default-centos-7-develop-py3 - - INSTANCE: default-fedora-30-develop-py3 + # - INSTANCE: default-fedora-30-develop-py3 # - INSTANCE: default-opensuse-leap-15-develop-py3 # - INSTANCE: default-amazonlinux-2-develop-py2 - # INSTANCE: default-arch-base-latest-develop-py2 + # - INSTANCE: default-arch-base-latest-develop-py2 # - INSTANCE: default-debian-9-2019-2-py3 - INSTANCE: default-ubuntu-1804-2019-2-py3 - - INSTANCE: default-centos-7-2019-2-py3 + # - INSTANCE: default-centos-7-2019-2-py3 # - INSTANCE: default-fedora-30-2019-2-py3 # - INSTANCE: default-opensuse-leap-15-2019-2-py3 # INSTANCE: default-amazonlinux-2-2019-2-py2 - INSTANCE: default-arch-base-latest-2019-2-py2 # - INSTANCE: default-debian-9-2018-3-py2 # - INSTANCE: default-ubuntu-1604-2018-3-py2 - # INSTANCE: default-centos-7-2018-3-py2 - # INSTANCE: default-fedora-29-2018-3-py2 + # - INSTANCE: default-centos-7-2018-3-py2 + - INSTANCE: default-fedora-29-2018-3-py2 - INSTANCE: default-opensuse-leap-15-2018-3-py2 - INSTANCE: default-amazonlinux-2-2018-3-py2 # - INSTANCE: default-arch-base-latest-2018-3-py2 # - INSTANCE: default-debian-8-2017-7-py2 # - INSTANCE: default-ubuntu-1604-2017-7-py2 - # INSTANCE: default-centos-6-2017-7-py2 + # INSTANCE: centos6-centos-6-2017-7-py2 # - INSTANCE: default-fedora-29-2017-7-py2 # - INSTANCE: default-opensuse-leap-15-2017-7-py2 # - INSTANCE: default-amazonlinux-2-2017-7-py2 diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index dd82230e..73ce0eda 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -115,6 +115,7 @@ Arch: cmd: makepkg -si --noconfirm -f wanted: [] target: + enabled: false loadmodule: true pkgs: wanted: diff --git a/iscsi/target/service/install.sls b/iscsi/target/service/install.sls index 8ea57ea5..1de86529 100644 --- a/iscsi/target/service/install.sls +++ b/iscsi/target/service/install.sls @@ -47,6 +47,7 @@ iscsi-target-service-install-service-running: - name: {{ servicename }} {%- endif %} - unless: {{ grains.os in ('Amazon', 'MacOS') }} + - onlyif: {{ iscsi.target.enabled }} iscsi-target-service-install-failure-explanation: test.show_notification: @@ -58,6 +59,7 @@ iscsi-target-service-install-failure-explanation: - unless: {{ grains.os_family in ('MacOS', 'Windows') }} #maybe not needed but no harm cmd.run: - names: + - echo "-- {{ iscsi.target.enabled }} --" - journalctl -xe -u {{ servicename }} || true - systemctl status {{ servicename }} -l || true - /sbin/lsmod 2>/dev/null || true diff --git a/kitchen.yml b/kitchen.yml index fd08c548..a98c575e 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -38,11 +38,15 @@ platforms: - sh bootstrap-salt.sh -XdPbfrq -x python3 git develop - name: opensuse-leap-15-develop-py3 driver: - image: netmanagers/salt-develop-py3:opensuse-leap-15 + image: opensuse/leap:15 provision_command: - - curl -o bootstrap-salt.sh -L https://bootstrap.saltstack.com - - sh bootstrap-salt.sh -XdPbfrq -x python3 git develop + # yamllint disable-line rule:line-length + - zypper install -y glibc-locale net-tools net-tools-deprecated python-xml python3-pip + - systemctl enable sshd.service run_command: /usr/lib/systemd/systemd + provisioner: + salt_bootstrap_options: -XdPfrq -x python3 git develop + salt_install: bootstrap # Workaround to avoid intermittent failures on `opensuse-leap-15`: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: @@ -76,8 +80,15 @@ platforms: image: netmanagers/salt-2019.2-py3:fedora-30 - name: opensuse-leap-15-2019-2-py3 driver: - image: netmanagers/salt-2019.2-py3:opensuse-leap-15 + image: opensuse/leap:15 + provision_command: + # yamllint disable-line rule:line-length + - zypper install -y glibc-locale net-tools net-tools-deprecated python-xml python3-pip + - systemctl enable sshd.service run_command: /usr/lib/systemd/systemd + provisioner: + salt_bootstrap_options: -XdPfrq -x python3 git 2019.2 + salt_install: bootstrap # Workaround to avoid intermittent failures on `opensuse-leap-15`: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: @@ -105,8 +116,15 @@ platforms: image: netmanagers/salt-2018.3-py2:fedora-29 - name: opensuse-leap-15-2018-3-py2 driver: - image: netmanagers/salt-2018.3-py2:opensuse-leap-15 + image: opensuse/leap:15 + provision_command: + # yamllint disable-line rule:line-length + - zypper install -y glibc-locale net-tools net-tools-deprecated python-xml python2-pip + - systemctl enable sshd.service run_command: /usr/lib/systemd/systemd + provisioner: + salt_bootstrap_options: -XdPfrq -x python2 git 2018.3 + salt_install: bootstrap # Workaround to avoid intermittent failures on `opensuse-leap-15`: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: @@ -135,8 +153,15 @@ platforms: image: netmanagers/salt-2017.7-py2:fedora-29 - name: opensuse-leap-15-2017-7-py2 driver: - image: netmanagers/salt-2017.7-py2:opensuse-leap-15 + image: opensuse/leap:15 + provision_command: + # yamllint disable-line rule:line-length + - zypper install -y glibc-locale net-tools net-tools-deprecated python-xml python2-pip + - systemctl enable sshd.service run_command: /usr/lib/systemd/systemd + provisioner: + salt_bootstrap_options: -XdPfrq -x python2 git 2017.7 + salt_install: bootstrap # Workaround to avoid intermittent failures on `opensuse-leap-15`: # => SCP did not finish successfully (255): (Net::SCP::Error) transport: @@ -151,7 +176,7 @@ platforms: provisioner: name: salt_solo - log_level: info + log_level: debug salt_install: none require_chef: false formula: iscsi @@ -170,12 +195,9 @@ verifier: suites: - name: default excludes: - - default-centos-7-2019-2-py3 + - centos-7-2019-2-py3 + - arch-base-latest-2019-2-py2 provisioner: - dependencies: - - name: lvm - repo: git - source: https://github.com/saltstack-formulas/lvm-formula.git state_top: base: '*': @@ -195,35 +217,19 @@ suites: inspec_tests: - path: test/integration/default - - name: centos # skip service start state on travis/centos - excludes: - - default-debian-10-develop-py3 - - default-ubuntu-1804-2019-2-py3 - - default-opensuse-leap-15-2018-3-py2 - - default-amazonlinux-2-2018-3-py2 - - default-arch-base-latest-2018-3-py2 - - default-fedora-30-develop-py3 + - name: target-service-fails-on-travis # skip 'target' service on travis + includes: + - centos-7-2019-2-py3 + - arch-base-latest-2019-2-py2 provisioner: - dependencies: - - name: lvm - repo: git - source: https://github.com/saltstack-formulas/lvm-formula.git state_top: base: '*': - lvm.install - - lvm.pv.remove - - lvm.files.remove - lvm.files.create - lvm.pv.create - - iscsi.initiator.clean - - iscsi.target.clean - - iscsi.isns - - iscsi.target.package - - iscsi.target.make - - iscsi.target.kernel - - iscsi.target.config - - iscsi.initiator + - iscsi.clean + - iscsi pillars: top.sls: base: diff --git a/pillar.example b/pillar.example index 79aab6d5..3efa4525 100644 --- a/pillar.example +++ b/pillar.example @@ -57,8 +57,6 @@ lvm: /dev/loop3: {} iscsi: - #target: - # enabled: true config: data: open-iscsi: From 9690093e00c9879f597a5771d56868bac05a86b2 Mon Sep 17 00:00:00 2001 From: N Date: Fri, 4 Oct 2019 17:56:44 +0100 Subject: [PATCH 39/42] test(travis): fixup yamllint --- .travis.yml | 20 +++++++++- .yamllint | 1 + iscsi/defaults.yaml | 8 ++-- iscsi/init.sls | 6 +-- iscsi/initiator/make/clean.sls | 2 +- iscsi/isns/make/clean.sls | 2 +- iscsi/oscodename.yaml | 2 + iscsi/osfamilymap.yaml | 19 ++++++--- iscsi/osmap.yaml | 2 +- iscsi/target/make/clean.sls | 2 +- kitchen.yml | 33 +++++++++++++++- pillar.example | 59 ++++++++++++++-------------- test/salt/pillar/pillar.example | 67 +++++++++++++------------------- test/salt/pillar/pillar.travis | 53 +++++++++++++------------ test/salt/pillar/users.archlinux | 15 +++++++ 15 files changed, 173 insertions(+), 118 deletions(-) create mode 100644 test/salt/pillar/users.archlinux diff --git a/.travis.yml b/.travis.yml index a8c7a894..de56f9a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,26 +30,42 @@ services: # Ref: https://github.com/saltstack-formulas/template-formula/issues/121 env: matrix: - - INSTANCE: default-debian-10-develop-py3 + - INSTANCE: debian-10-develop-py3 # - INSTANCE: default-ubuntu-1804-develop-py3 +<<<<<<< HEAD # - INSTANCE: default-centos-7-develop-py3 # - INSTANCE: default-fedora-30-develop-py3 +======= + - INSTANCE: centos-7-develop-py3 + # INSTANCE: default-fedora-30-develop-py3 +>>>>>>> 863e40c... fix(service): only start service if enabled # - INSTANCE: default-opensuse-leap-15-develop-py3 # - INSTANCE: default-amazonlinux-2-develop-py2 # - INSTANCE: default-arch-base-latest-develop-py2 # - INSTANCE: default-debian-9-2019-2-py3 +<<<<<<< HEAD - INSTANCE: default-ubuntu-1804-2019-2-py3 # - INSTANCE: default-centos-7-2019-2-py3 +======= + - INSTANCE: ubuntu-1804-2019-2-py3 + - INSTANCE: centos-7-2019-2-py3 +>>>>>>> 863e40c... fix(service): only start service if enabled # - INSTANCE: default-fedora-30-2019-2-py3 # - INSTANCE: default-opensuse-leap-15-2019-2-py3 # INSTANCE: default-amazonlinux-2-2019-2-py2 - - INSTANCE: default-arch-base-latest-2019-2-py2 + - INSTANCE: arch-base-latest-2019-2-py2 # - INSTANCE: default-debian-9-2018-3-py2 # - INSTANCE: default-ubuntu-1604-2018-3-py2 # - INSTANCE: default-centos-7-2018-3-py2 +<<<<<<< HEAD - INSTANCE: default-fedora-29-2018-3-py2 - INSTANCE: default-opensuse-leap-15-2018-3-py2 - INSTANCE: default-amazonlinux-2-2018-3-py2 +======= + # INSTANCE: fedora-29-2018-3-py2 + - INSTANCE: opensuse-leap-15-2018-3-py2 + - INSTANCE: amazonlinux-2-2018-3-py2 +>>>>>>> 863e40c... fix(service): only start service if enabled # - INSTANCE: default-arch-base-latest-2018-3-py2 # - INSTANCE: default-debian-8-2017-7-py2 # - INSTANCE: default-ubuntu-1604-2017-7-py2 diff --git a/.yamllint b/.yamllint index a8509bc8..6f616f2f 100644 --- a/.yamllint +++ b/.yamllint @@ -10,6 +10,7 @@ extends: default ignore: | node_modules/ test/**/states/**/*.sls + iscsi/oscodename.yaml yaml-files: # Default settings diff --git a/iscsi/defaults.yaml b/iscsi/defaults.yaml index b362ba1a..59104f4d 100644 --- a/iscsi/defaults.yaml +++ b/iscsi/defaults.yaml @@ -59,17 +59,17 @@ iscsi: name: iscsi_initiator text: 'iscsi_initiator_load="YES"' ctld: - name: + name: '' text: 'cfiscsi_load="YES"' ietd: name: iscsi_trgt - text: + text: '' lio: name: target_core_mod - text: + text: '' fcoe: name: fcoe_target - text: + text: '' iser: name: ('ib_iser', 'ib_isert', 'rdma_ucm', 'rdma_cm', 'ib_cm',) text: null diff --git a/iscsi/init.sls b/iscsi/init.sls index 121d34db..c450c851 100644 --- a/iscsi/init.sls +++ b/iscsi/init.sls @@ -2,14 +2,14 @@ # vim: ft=sls include: - {%- if grains.os_family in ('Arch',) %} + {%- if grains.os_family in ('SArch',) %} {# This sequence avoids /etc/isns/isnsd.conf conflict on Arch #} - - iscsi.target - iscsi.initiator + - iscsi.target - iscsi.isns {%- else %} {# This is the normal sequence #} - iscsi.isns - - iscsi.target - iscsi.initiator + - iscsi.target {%- endif %} diff --git a/iscsi/initiator/make/clean.sls b/iscsi/initiator/make/clean.sls index d7bbed95..e9d860de 100644 --- a/iscsi/initiator/make/clean.sls +++ b/iscsi/initiator/make/clean.sls @@ -7,9 +7,9 @@ {%- from tplroot ~ "/map.jinja" import iscsi with context %} {%- if iscsi.initiator.make.wanted %} - {%- for pkg in iscsi.initiator.make.wanted %} include: - {{ sls_service_clean }} + {%- for pkg in iscsi.initiator.make.wanted %} iscsi-initiator-package-make-clean-{{ pkg }}-removed: pkg.removed: diff --git a/iscsi/isns/make/clean.sls b/iscsi/isns/make/clean.sls index b4115a8a..d85f80e6 100644 --- a/iscsi/isns/make/clean.sls +++ b/iscsi/isns/make/clean.sls @@ -7,9 +7,9 @@ {%- from tplroot ~ "/map.jinja" import iscsi with context %} {%- if iscsi.isns.make.wanted %} - {%- for pkg in iscsi.isns.make.wanted %} include: - {{ sls_service_clean }} + {%- for pkg in iscsi.isns.make.wanted %} iscsi-isns-package-make-clean-{{ pkg }}-removed: pkg.removed: diff --git a/iscsi/oscodename.yaml b/iscsi/oscodename.yaml index fe925668..767341a3 100644 --- a/iscsi/oscodename.yaml +++ b/iscsi/oscodename.yaml @@ -55,5 +55,7 @@ ## Fedora # `oscodename` grain has long distro name {{ fedora_codename('Fedora-29', 29, 'Fedora 29 (Twenty Nine)') }} +{{ fedora_codename('Fedora-30', 30, 'Fedora 30 (Thirty)') }} +{{ fedora_codename('Fedora-31', 31, 'Fedora 31 (Thirty One)') }} # vim: ft=sls diff --git a/iscsi/osfamilymap.yaml b/iscsi/osfamilymap.yaml index 73ce0eda..4f4b2a8c 100644 --- a/iscsi/osfamilymap.yaml +++ b/iscsi/osfamilymap.yaml @@ -32,8 +32,8 @@ Debian: - iscsitarget-dkms - infiniband-diags - ibutils - - ibverbs-utils #not suse - - rdmacm-utils #not suse + - ibverbs-utils # not suse + - rdmacm-utils # not suse - perftest RedHat: config: @@ -103,13 +103,18 @@ Arch: config: servicename: open-iscsi: - - iscsi - - iscsid + - iscsi + - iscsid kmodule: lio: - name: - text: + name: '' + text: '' initiator: + pkgs: + wanted: + - binutils + - fakeroot + - open-iscsi make: gitrepo: https://aur.archlinux.org cmd: makepkg -si --noconfirm -f @@ -119,6 +124,8 @@ Arch: loadmodule: true pkgs: wanted: + - binutils + - fakeroot - linux-lts # For kernel scsi modules - python-pip # makepkg states needs 'gitpython'. - thin-provisioning-tools diff --git a/iscsi/osmap.yaml b/iscsi/osmap.yaml index 4a423be7..6bdae185 100644 --- a/iscsi/osmap.yaml +++ b/iscsi/osmap.yaml @@ -53,7 +53,7 @@ Fedora: provider: tgtd pkgs: wanted: - - device-mapper-persistent-data #aka thin-provisioning-tools + - device-mapper-persistent-data # aka thin-provisioning-tools - netbsd-iscsi - yum-plugin-versionlock - targetcli diff --git a/iscsi/target/make/clean.sls b/iscsi/target/make/clean.sls index 2bbff228..a355ae8c 100644 --- a/iscsi/target/make/clean.sls +++ b/iscsi/target/make/clean.sls @@ -7,9 +7,9 @@ {%- from tplroot ~ "/map.jinja" import iscsi with context %} {%- if iscsi.target.make.wanted %} - {%- for pkg in iscsi.target.make.wanted %} include: - {{ sls_service_clean }} + {%- for pkg in iscsi.target.make.wanted %} iscsi-target-package-make-clean-{{ pkg }}-removed: pkg.removed: diff --git a/kitchen.yml b/kitchen.yml index a98c575e..d00c23b9 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -197,15 +197,26 @@ suites: excludes: - centos-7-2019-2-py3 - arch-base-latest-2019-2-py2 +<<<<<<< HEAD +======= + - centos-7-develop-py3 +>>>>>>> 863e40c... fix(service): only start service if enabled provisioner: + dependencies: + - name: lvm + repo: git + source: https://github.com/saltstack-formulas/lvm-formula.git + - name: users # for archlinux + repo: git + source: https://github.com/saltstack-formulas/users-formula.git state_top: base: '*': - lvm.install - lvm.files.create - lvm.pv.create - - iscsi.clean - iscsi + - iscsi.clean pillars: top.sls: base: @@ -221,21 +232,41 @@ suites: includes: - centos-7-2019-2-py3 - arch-base-latest-2019-2-py2 +<<<<<<< HEAD + provisioner: +======= + - centos-7-develop-py3 provisioner: + dependencies: + - name: lvm + repo: git + source: https://github.com/saltstack-formulas/lvm-formula.git + - name: users # for archlinux + repo: git + source: https://github.com/saltstack-formulas/users-formula.git +>>>>>>> 863e40c... fix(service): only start service if enabled state_top: base: '*': + - users - lvm.install - lvm.files.create - lvm.pv.create +<<<<<<< HEAD - iscsi.clean - iscsi +======= + - iscsi + - iscsi.clean +>>>>>>> 863e40c... fix(service): only start service if enabled pillars: top.sls: base: '*': + - users - iscsi pillars_from_files: + users.sls: test/salt/pillar/users.archlinux iscsi.sls: test/salt/pillar/pillar.travis verifier: inspec_tests: diff --git a/pillar.example b/pillar.example index 3efa4525..3b2ac85e 100644 --- a/pillar.example +++ b/pillar.example @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # vim: ft=yaml --- - {% if grains.os_family in ('Arch',) %} +# iscsi on Archlinux needs a 'iscsimake' user users: iscsimake: sudouser: true @@ -13,8 +13,8 @@ users: - wheel - root sudo_rules: - - 'ALL=(ALL) ALL' - {% endif %} + - 'ALL=(ALL) ALL' + lvm: files: remove: @@ -60,7 +60,7 @@ iscsi: config: data: open-iscsi: - ##https://github.com/open-iscsi/open-iscsi/blob/master/etc/iscsid.conf + # https://github.com/open-iscsi/open-iscsi/blob/master/etc/iscsid.conf node.startup: manual node.leading_login: 'No' node.session.timeo.replacement_timeout: 120 @@ -86,10 +86,10 @@ iscsi: node.session.iscsi.FastAbort: 'Yes' node.session.scan: auto - iscsi: ##freeBSD - ##https://www.freebsd.org/cgi/man.cgi?query=iscsi.conf&sektion=5&manpath=FreeBSD+10-current + iscsi: # freeBSD + # https://www.freebsd.org/cgi/man.cgi?query=iscsi.conf&sektion=5&manpath=FreeBSD+10-current node.startup: automatic - myiscsi: ##nickname + myiscsi: # nickname targetaddress: iscsi1 targetname: 'iqn.1900.com.com:sn.123456' myiscsi6: @@ -100,7 +100,7 @@ iscsi: targetname: 'iqn.1900.com.com:sn.123456' initiatorname: 'iqn.2005-01.il.ac.huji.cs:nobody' authmethod: CHAP - chapiname : 'iqn.2005-01.il.ac.huji.cs:nobody' + chapiname: 'iqn.2005-01.il.ac.huji.cs:nobody' chapsecret: secretsecret example01: targetname: 'iqn.2018-07.com.example.iscsi:example01' @@ -118,7 +118,7 @@ iscsi: chapSecret: secretsecret ietd: - ##http://manpages.ubuntu.com/manpages/trusty/man5/ietd.conf.5.html + # http://manpages.ubuntu.com/manpages/trusty/man5/ietd.conf.5.html IncomingUser: joe secret OutgoingUser: jack secret2 'Target iqn.2001-04.com.example:storage.disk2.sys1.xyz': @@ -149,7 +149,7 @@ iscsi: isnsadm: {} isnsdd: {} isnsd: - ##https://manpages.debian.org/testing/open-isns-server/isnsd.conf.5.en.html + # https://manpages.debian.org/testing/open-isns-server/isnsd.conf.5.en.html Database: /var/lib/isns RegistrationPeriod: 10m DefaultDiscoveryDomain: 1 @@ -164,14 +164,14 @@ iscsi: Auth.TimeStampJitter: 1s tgtd: - ##https://linux.die.net/man/5/targets.conf - ##https://github.com/fujita/tgt/blob/master/conf/examples/targets.conf.example + # https://linux.die.net/man/5/targets.conf + # https://github.com/fujita/tgt/blob/master/conf/examples/targets.conf.example include: '/etc/tgt/salt/*.conf' default-driver: iscsi - #iSNSServerIP: 127.0.0.1 - #iSNSServerPort: 3205 - #iSNSAccessControl: Off - #iSNS: Off + # iSNSServerIP: 127.0.0.1 + # iSNSServerPort: 3205 + # iSNSAccessControl: 'Off' + # iSNS: 'Off' 'target iqn.2008-09.com.example:server.target1': backing-store: /dev/loop0 'target iqn.2008-09.com.example:server.target5': @@ -183,23 +183,23 @@ iscsi: 'direct-store /dev/loop2': vendor_id: VENDOR2 lun: 2 - 'backing-store /dev/loop3': + 'backing-store /dev/loop3': vendor_id: back1 scsi_sn: SERIAL - write-cache: on + write-cache: 'on' lio: - ##https://www.systutorials.com/docs/linux/man/5-saveconfig.json - ##https://bugzilla.redhat.com/show_bug.cgi?id=1643673 - ##https://github.com/open-iscsi/rtslib-fb/issues/5 - ##---------------------------------------------------------------------- - ## The sample below is complex because its used for verification. - ## You should only use the minimal pillar data for your needs. - ## The format is important so review this example carefully. - ## - ## Multiple objects are supported: - ## See: https://github.com/saltstack-formulas/iscsi-formula/issues/19 - ##---------------------------------------------------------------------- + # https://www.systutorials.com/docs/linux/man/5-saveconfig.json + # https://bugzilla.redhat.com/show_bug.cgi?id=1643673 + # https://github.com/open-iscsi/rtslib-fb/issues/5 + # ---------------------------------------------------------------------- + # The sample below is complex because its used for verification. + # You should only use the minimal pillar data for your needs. + # The format is important so review this example carefully. + # + # Multiple objects are supported: + # See: https://github.com/saltstack-formulas/iscsi-formula/issues/19 + # ---------------------------------------------------------------------- fabric_modules: callmewhateveryoulike0: discovery_enable_auth: 'true' @@ -626,7 +626,6 @@ iscsi: callmewhateveryoulike0: attributes: dataout_timeout: 3 - #mapped_luns: {} parameters: TargetAlias: LIO Target portals: diff --git a/test/salt/pillar/pillar.example b/test/salt/pillar/pillar.example index 351859f8..f0e892c7 100644 --- a/test/salt/pillar/pillar.example +++ b/test/salt/pillar/pillar.example @@ -1,20 +1,6 @@ # -*- coding: utf-8 -*- # vim: ft=yaml --- - {% if grains.os_family in ('Arch',) %} -users: - iscsimake: - sudouser: true - shell: /bin/bash - empty_password: true - home: /home/iscsimake - createhome: true - optional_groups: - - wheel - - root - sudo_rules: - - 'ALL=(ALL) ALL' - {% endif %} lvm: files: remove: @@ -60,7 +46,7 @@ iscsi: config: data: open-iscsi: - ##https://github.com/open-iscsi/open-iscsi/blob/master/etc/iscsid.conf + # https://github.com/open-iscsi/open-iscsi/blob/master/etc/iscsid.conf node.startup: manual node.leading_login: 'No' node.session.timeo.replacement_timeout: 120 @@ -86,10 +72,10 @@ iscsi: node.session.iscsi.FastAbort: 'Yes' node.session.scan: auto - iscsi: ##freeBSD - ##https://www.freebsd.org/cgi/man.cgi?query=iscsi.conf&sektion=5&manpath=FreeBSD+10-current + iscsi: # freeBSD + # https://www.freebsd.org/cgi/man.cgi?query=iscsi.conf&sektion=5&manpath=FreeBSD+10-current node.startup: automatic - myiscsi: ##nickname + myiscsi: # nickname targetaddress: iscsi1 targetname: 'iqn.1900.com.com:sn.123456' myiscsi6: @@ -100,7 +86,7 @@ iscsi: targetname: 'iqn.1900.com.com:sn.123456' initiatorname: 'iqn.2005-01.il.ac.huji.cs:nobody' authmethod: CHAP - chapiname : 'iqn.2005-01.il.ac.huji.cs:nobody' + chapiname: 'iqn.2005-01.il.ac.huji.cs:nobody' chapsecret: secretsecret example01: targetname: 'iqn.2018-07.com.example.iscsi:example01' @@ -118,7 +104,7 @@ iscsi: chapSecret: secretsecret ietd: - ##http://manpages.ubuntu.com/manpages/trusty/man5/ietd.conf.5.html + # http://manpages.ubuntu.com/manpages/trusty/man5/ietd.conf.5.html IncomingUser: joe secret OutgoingUser: jack secret2 'Target iqn.2001-04.com.example:storage.disk2.sys1.xyz': @@ -149,7 +135,7 @@ iscsi: isnsadm: {} isnsdd: {} isnsd: - ##https://manpages.debian.org/testing/open-isns-server/isnsd.conf.5.en.html + # https://manpages.debian.org/testing/open-isns-server/isnsd.conf.5.en.html Database: /var/lib/isns RegistrationPeriod: 10m DefaultDiscoveryDomain: 1 @@ -164,14 +150,14 @@ iscsi: Auth.TimeStampJitter: 1s tgtd: - ##https://linux.die.net/man/5/targets.conf - ##https://github.com/fujita/tgt/blob/master/conf/examples/targets.conf.example + # https://linux.die.net/man/5/targets.conf + # https://github.com/fujita/tgt/blob/master/conf/examples/targets.conf.example include: '/etc/tgt/salt/*.conf' default-driver: iscsi - #iSNSServerIP: 127.0.0.1 - #iSNSServerPort: 3205 - #iSNSAccessControl: Off - #iSNS: Off + # iSNSServerIP: 127.0.0.1 + # iSNSServerPort: 3205 + # iSNSAccessControl: 'Off' + # iSNS: 'Off' 'target iqn.2008-09.com.example:server.target1': backing-store: /dev/loop4 'target iqn.2008-09.com.example:server.target5': @@ -183,23 +169,23 @@ iscsi: 'direct-store /dev/loop6': vendor_id: VENDOR2 lun: 2 - 'backing-store /dev/loop7': + 'backing-store /dev/loop7': vendor_id: back1 scsi_sn: SERIAL - write-cache: on + write-cache: 'on' lio: - ##https://www.systutorials.com/docs/linux/man/5-saveconfig.json - ##https://bugzilla.redhat.com/show_bug.cgi?id=1643673 - ##https://github.com/open-iscsi/rtslib-fb/issues/5 - ##---------------------------------------------------------------------- - ## The sample below is complex because its used for verification. - ## You should only use the minimal pillar data for your needs. - ## The format is important so review this example carefully. - ## - ## Multiple objects are supported: - ## See: https://github.com/saltstack-formulas/iscsi-formula/issues/19 - ##---------------------------------------------------------------------- + # https://www.systutorials.com/docs/linux/man/5-saveconfig.json + # https://bugzilla.redhat.com/show_bug.cgi?id=1643673 + # https://github.com/open-iscsi/rtslib-fb/issues/5 + # ---------------------------------------------------------------------- + # The sample below is complex because its used for verification. + # You should only use the minimal pillar data for your needs. + # The format is important so review this example carefully. + # + # Multiple objects are supported: + # See: https://github.com/saltstack-formulas/iscsi-formula/issues/19 + # ---------------------------------------------------------------------- fabric_modules: callmewhateveryoulike0: discovery_enable_auth: 'true' @@ -626,7 +612,6 @@ iscsi: callmewhateveryoulike0: attributes: dataout_timeout: 3 - #mapped_luns: {} parameters: TargetAlias: LIO Target portals: diff --git a/test/salt/pillar/pillar.travis b/test/salt/pillar/pillar.travis index 1c67d8fa..041e0fbe 100644 --- a/test/salt/pillar/pillar.travis +++ b/test/salt/pillar/pillar.travis @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # vim: ft=yaml --- - {% if grains.os_family in ('Arch',) %} +{# #}{% if grains.os_family in ('Arch',) %} users: iscsimake: sudouser: true @@ -14,7 +14,7 @@ users: - root sudo_rules: - 'ALL=(ALL) ALL' - {% endif %} +{# #}{% endif %} lvm: files: remove: @@ -62,7 +62,7 @@ iscsi: config: data: open-iscsi: - ##https://github.com/open-iscsi/open-iscsi/blob/master/etc/iscsid.conf + # https://github.com/open-iscsi/open-iscsi/blob/master/etc/iscsid.conf node.startup: manual node.leading_login: 'No' node.session.timeo.replacement_timeout: 120 @@ -88,10 +88,10 @@ iscsi: node.session.iscsi.FastAbort: 'Yes' node.session.scan: auto - iscsi: ##freeBSD - ##https://www.freebsd.org/cgi/man.cgi?query=iscsi.conf&sektion=5&manpath=FreeBSD+10-current + iscsi: # freeBSD + # https://www.freebsd.org/cgi/man.cgi?query=iscsi.conf&sektion=5&manpath=FreeBSD+10-current node.startup: automatic - myiscsi: ##nickname + myiscsi: # nickname targetaddress: iscsi1 targetname: 'iqn.1900.com.com:sn.123456' myiscsi6: @@ -120,7 +120,7 @@ iscsi: chapSecret: secretsecret ietd: - ##http://manpages.ubuntu.com/manpages/trusty/man5/ietd.conf.5.html + # http://manpages.ubuntu.com/manpages/trusty/man5/ietd.conf.5.html IncomingUser: joe secret OutgoingUser: jack secret2 'Target iqn.2001-04.com.example:storage.disk2.sys1.xyz': @@ -151,7 +151,7 @@ iscsi: isnsadm: {} isnsdd: {} isnsd: - ##https://manpages.debian.org/testing/open-isns-server/isnsd.conf.5.en.html + # https://manpages.debian.org/testing/open-isns-server/isnsd.conf.5.en.html Database: /var/lib/isns RegistrationPeriod: 10m DefaultDiscoveryDomain: 1 @@ -166,14 +166,14 @@ iscsi: Auth.TimeStampJitter: 1s tgtd: - ##https://linux.die.net/man/5/targets.conf - ##https://github.com/fujita/tgt/blob/master/conf/examples/targets.conf.example + # https://linux.die.net/man/5/targets.conf + # https://github.com/fujita/tgt/blob/master/conf/examples/targets.conf.example include: '/etc/tgt/salt/*.conf' default-driver: iscsi - #iSNSServerIP: 127.0.0.1 - #iSNSServerPort: 3205 - #iSNSAccessControl: Off - #iSNS: Off + # iSNSServerIP: 127.0.0.1 + # iSNSServerPort: 3205 + # iSNSAccessControl: 'Off' + # iSNS: 'Off' 'target iqn.2008-09.com.example:server.target1': backing-store: /dev/loop4 'target iqn.2008-09.com.example:server.target5': @@ -188,20 +188,20 @@ iscsi: 'backing-store /dev/loop7': vendor_id: back1 scsi_sn: SERIAL - write-cache: on + write-cache: 'on' lio: - ##https://www.systutorials.com/docs/linux/man/5-saveconfig.json - ##https://bugzilla.redhat.com/show_bug.cgi?id=1643673 - ##https://github.com/open-iscsi/rtslib-fb/issues/5 - ##---------------------------------------------------------------------- - ## The sample below is complex because its used for verification. - ## You should only use the minimal pillar data for your needs. - ## The format is important so review this example carefully. - ## - ## Multiple objects are supported: - ## See: https://github.com/saltstack-formulas/iscsi-formula/issues/19 - ##---------------------------------------------------------------------- + # https://www.systutorials.com/docs/linux/man/5-saveconfig.json + # https://bugzilla.redhat.com/show_bug.cgi?id=1643673 + # https://github.com/open-iscsi/rtslib-fb/issues/5 + # ---------------------------------------------------------------------- + # The sample below is complex because its used for verification. + # You should only use the minimal pillar data for your needs. + # The format is important so review this example carefully. + # + # Multiple objects are supported: + # See: https://github.com/saltstack-formulas/iscsi-formula/issues/19 + # ---------------------------------------------------------------------- fabric_modules: callmewhateveryoulike0: discovery_enable_auth: 'true' @@ -628,7 +628,6 @@ iscsi: callmewhateveryoulike0: attributes: dataout_timeout: 3 - #mapped_luns: {} parameters: TargetAlias: LIO Target portals: diff --git a/test/salt/pillar/users.archlinux b/test/salt/pillar/users.archlinux new file mode 100644 index 00000000..a0f89ece --- /dev/null +++ b/test/salt/pillar/users.archlinux @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +users: + iscsimake: + sudouser: true + shell: /bin/bash + empty_password: true + home: /home/iscsimake + createhome: true + optional_groups: + - wheel + - root + sudo_rules: + - 'ALL=(ALL) ALL' From 02cad318fcb0f00739a821b0efaefa19623d6eb1 Mon Sep 17 00:00:00 2001 From: N Date: Fri, 4 Oct 2019 18:07:19 +0100 Subject: [PATCH 40/42] docs(readme): update description --- .travis.yml | 20 +------ .yamllint | 1 + docs/README.rst | 2 +- kitchen.yml | 21 ++----- test/salt/pillar/lvm.example | 43 +++++++++++++++ test/salt/pillar/pillar.example | 41 -------------- test/salt/pillar/pillar.travis | 55 ------------------- .../pillar/{users.archlinux => users.arch} | 0 8 files changed, 52 insertions(+), 131 deletions(-) create mode 100644 test/salt/pillar/lvm.example rename test/salt/pillar/{users.archlinux => users.arch} (100%) diff --git a/.travis.yml b/.travis.yml index de56f9a7..c4b0f18b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,40 +32,24 @@ env: matrix: - INSTANCE: debian-10-develop-py3 # - INSTANCE: default-ubuntu-1804-develop-py3 -<<<<<<< HEAD - # - INSTANCE: default-centos-7-develop-py3 - # - INSTANCE: default-fedora-30-develop-py3 -======= - INSTANCE: centos-7-develop-py3 # INSTANCE: default-fedora-30-develop-py3 ->>>>>>> 863e40c... fix(service): only start service if enabled # - INSTANCE: default-opensuse-leap-15-develop-py3 # - INSTANCE: default-amazonlinux-2-develop-py2 # - INSTANCE: default-arch-base-latest-develop-py2 # - INSTANCE: default-debian-9-2019-2-py3 -<<<<<<< HEAD - - INSTANCE: default-ubuntu-1804-2019-2-py3 - # - INSTANCE: default-centos-7-2019-2-py3 -======= - INSTANCE: ubuntu-1804-2019-2-py3 - INSTANCE: centos-7-2019-2-py3 ->>>>>>> 863e40c... fix(service): only start service if enabled # - INSTANCE: default-fedora-30-2019-2-py3 - # - INSTANCE: default-opensuse-leap-15-2019-2-py3 + - INSTANCE: opensuse-leap-15-2019-2-py3 # INSTANCE: default-amazonlinux-2-2019-2-py2 - INSTANCE: arch-base-latest-2019-2-py2 # - INSTANCE: default-debian-9-2018-3-py2 # - INSTANCE: default-ubuntu-1604-2018-3-py2 # - INSTANCE: default-centos-7-2018-3-py2 -<<<<<<< HEAD - - INSTANCE: default-fedora-29-2018-3-py2 - - INSTANCE: default-opensuse-leap-15-2018-3-py2 - - INSTANCE: default-amazonlinux-2-2018-3-py2 -======= # INSTANCE: fedora-29-2018-3-py2 - - INSTANCE: opensuse-leap-15-2018-3-py2 + # INSTANCE: default-opensuse-leap-15-2018-3-py2 - INSTANCE: amazonlinux-2-2018-3-py2 ->>>>>>> 863e40c... fix(service): only start service if enabled # - INSTANCE: default-arch-base-latest-2018-3-py2 # - INSTANCE: default-debian-8-2017-7-py2 # - INSTANCE: default-ubuntu-1604-2017-7-py2 diff --git a/.yamllint b/.yamllint index 6f616f2f..e825af60 100644 --- a/.yamllint +++ b/.yamllint @@ -19,6 +19,7 @@ yaml-files: - .yamllint # SaltStack Formulas additional settings - '*.example' + - '*.arch' - test/**/*.sls rules: diff --git a/docs/README.rst b/docs/README.rst index 1516e21a..5abe3f1f 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -14,7 +14,7 @@ iscsi-formula :scale: 100% :target: https://github.com/semantic-release/semantic-release -Configure iSCSI targets and initiator on GNU/Linux and FreeBSD. +Configure iSCSI Targets, Initiators, and Name-Services on GNU/Linux and FreeBSD. .. contents:: **Table of Contents** diff --git a/kitchen.yml b/kitchen.yml index d00c23b9..46e0cca7 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -197,18 +197,12 @@ suites: excludes: - centos-7-2019-2-py3 - arch-base-latest-2019-2-py2 -<<<<<<< HEAD -======= - centos-7-develop-py3 ->>>>>>> 863e40c... fix(service): only start service if enabled provisioner: dependencies: - name: lvm repo: git source: https://github.com/saltstack-formulas/lvm-formula.git - - name: users # for archlinux - repo: git - source: https://github.com/saltstack-formulas/users-formula.git state_top: base: '*': @@ -222,8 +216,10 @@ suites: base: '*': - iscsi + - lvm pillars_from_files: iscsi.sls: test/salt/pillar/pillar.example + lvm.sls: test/salt/pillar/lvm.example verifier: inspec_tests: - path: test/integration/default @@ -232,9 +228,6 @@ suites: includes: - centos-7-2019-2-py3 - arch-base-latest-2019-2-py2 -<<<<<<< HEAD - provisioner: -======= - centos-7-develop-py3 provisioner: dependencies: @@ -244,7 +237,6 @@ suites: - name: users # for archlinux repo: git source: https://github.com/saltstack-formulas/users-formula.git ->>>>>>> 863e40c... fix(service): only start service if enabled state_top: base: '*': @@ -252,22 +244,19 @@ suites: - lvm.install - lvm.files.create - lvm.pv.create -<<<<<<< HEAD - - iscsi.clean - - iscsi -======= - iscsi - iscsi.clean ->>>>>>> 863e40c... fix(service): only start service if enabled pillars: top.sls: base: '*': - users - iscsi + - lvm pillars_from_files: - users.sls: test/salt/pillar/users.archlinux + users.sls: test/salt/pillar/users.arch iscsi.sls: test/salt/pillar/pillar.travis + lvm.sls: test/salt/pillar/lvm.example verifier: inspec_tests: - path: test/integration/default diff --git a/test/salt/pillar/lvm.example b/test/salt/pillar/lvm.example new file mode 100644 index 00000000..d6260382 --- /dev/null +++ b/test/salt/pillar/lvm.example @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +lvm: + files: + remove: + - /tmp/loopdevs/testfile0.img + - /tmp/loopdevs/testfile1.img + - /tmp/loopdevs/testfile2.img + - /tmp/loopdevs/testfile3.img + create: + dd: + /tmp/loopdevs/testfile0.img: + options: + if: /dev/urandom + bs: 1024 + count: 307200 + /tmp/loopdevs/testfile1.img: + options: + if: /dev/urandom + bs: 1024 + count: 307200 + /tmp/loopdevs/testfile2.img: + options: + if: /dev/urandom + bs: 1024 + count: 307200 + /tmp/loopdevs/testfile3.img: + options: + if: /dev/urandom + bs: 1024 + count: 307200 + losetup: + /tmp/loopdevs/testfile0.img: {} + /tmp/loopdevs/testfile1.img: {} + /tmp/loopdevs/testfile2.img: {} + /tmp/loopdevs/testfile3.img: {} + pv: + create: + /dev/loop4: {} + /dev/loop5: {} + /dev/loop6: {} + /dev/loop7: {} diff --git a/test/salt/pillar/pillar.example b/test/salt/pillar/pillar.example index f0e892c7..1fa22088 100644 --- a/test/salt/pillar/pillar.example +++ b/test/salt/pillar/pillar.example @@ -1,47 +1,6 @@ # -*- coding: utf-8 -*- # vim: ft=yaml --- -lvm: - files: - remove: - - /tmp/loopdevs/testfile0.img - - /tmp/loopdevs/testfile1.img - - /tmp/loopdevs/testfile2.img - - /tmp/loopdevs/testfile3.img - create: - dd: - /tmp/loopdevs/testfile0.img: - options: - if: /dev/urandom - bs: 1024 - count: 307200 - /tmp/loopdevs/testfile1.img: - options: - if: /dev/urandom - bs: 1024 - count: 307200 - /tmp/loopdevs/testfile2.img: - options: - if: /dev/urandom - bs: 1024 - count: 307200 - /tmp/loopdevs/testfile3.img: - options: - if: /dev/urandom - bs: 1024 - count: 307200 - losetup: - /tmp/loopdevs/testfile0.img: {} - /tmp/loopdevs/testfile1.img: {} - /tmp/loopdevs/testfile2.img: {} - /tmp/loopdevs/testfile3.img: {} - pv: - create: - /dev/loop4: {} - /dev/loop5: {} - /dev/loop6: {} - /dev/loop7: {} - iscsi: config: data: diff --git a/test/salt/pillar/pillar.travis b/test/salt/pillar/pillar.travis index 041e0fbe..4af14a8e 100644 --- a/test/salt/pillar/pillar.travis +++ b/test/salt/pillar/pillar.travis @@ -1,61 +1,6 @@ # -*- coding: utf-8 -*- # vim: ft=yaml --- -{# #}{% if grains.os_family in ('Arch',) %} -users: - iscsimake: - sudouser: true - shell: /bin/bash - empty_password: true - home: /home/iscsimake - createhome: true - optional_groups: - - wheel - - root - sudo_rules: - - 'ALL=(ALL) ALL' -{# #}{% endif %} -lvm: - files: - remove: - - /tmp/loopdevs/testfile0.img - - /tmp/loopdevs/testfile1.img - - /tmp/loopdevs/testfile2.img - - /tmp/loopdevs/testfile3.img - create: - dd: - /tmp/loopdevs/testfile0.img: - options: - if: /dev/urandom - bs: 1024 - count: 307200 - /tmp/loopdevs/testfile1.img: - options: - if: /dev/urandom - bs: 1024 - count: 307200 - /tmp/loopdevs/testfile2.img: - options: - if: /dev/urandom - bs: 1024 - count: 307200 - /tmp/loopdevs/testfile3.img: - options: - if: /dev/urandom - bs: 1024 - count: 307200 - losetup: - /tmp/loopdevs/testfile0.img: {} - /tmp/loopdevs/testfile1.img: {} - /tmp/loopdevs/testfile2.img: {} - /tmp/loopdevs/testfile3.img: {} - pv: - create: - /dev/loop4: {} - /dev/loop5: {} - /dev/loop6: {} - /dev/loop7: {} - iscsi: target: enabled: false # centos/travis workaround to pass service.running state diff --git a/test/salt/pillar/users.archlinux b/test/salt/pillar/users.arch similarity index 100% rename from test/salt/pillar/users.archlinux rename to test/salt/pillar/users.arch From 61502c9e9c01351e31a278dd26b0147ae99aa8fe Mon Sep 17 00:00:00 2001 From: N Date: Fri, 4 Oct 2019 20:56:23 +0100 Subject: [PATCH 41/42] test(travis): workaround travis nuances --- kitchen.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kitchen.yml b/kitchen.yml index 46e0cca7..26c494d4 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -224,10 +224,10 @@ suites: inspec_tests: - path: test/integration/default - - name: target-service-fails-on-travis # skip 'target' service on travis + - name: centarch # skip 'target' service on travis includes: - - centos-7-2019-2-py3 - arch-base-latest-2019-2-py2 + - centos-7-2019-2-py3 - centos-7-develop-py3 provisioner: dependencies: From 2ab2b3e161f1b21b2ad0f4f48691b17de5ae9e95 Mon Sep 17 00:00:00 2001 From: N Date: Fri, 4 Oct 2019 21:40:09 +0100 Subject: [PATCH 42/42] test(lint): fix yamllint errors --- docs/README.rst | 2 +- test/salt/pillar/users.arch | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.rst b/docs/README.rst index 5abe3f1f..567cd573 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -14,7 +14,7 @@ iscsi-formula :scale: 100% :target: https://github.com/semantic-release/semantic-release -Configure iSCSI Targets, Initiators, and Name-Services on GNU/Linux and FreeBSD. +Manage iSCSI Targets, Initiators, Name-Services on GNU/Linux and FreeBSD. .. contents:: **Table of Contents** diff --git a/test/salt/pillar/users.arch b/test/salt/pillar/users.arch index a0f89ece..1f9b6413 100644 --- a/test/salt/pillar/users.arch +++ b/test/salt/pillar/users.arch @@ -12,4 +12,4 @@ users: - wheel - root sudo_rules: - - 'ALL=(ALL) ALL' + - 'ALL=(ALL) ALL'