From 43eaba3401b1d756493bf42ede19e3d5de288f9c Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 29 Sep 2023 18:23:23 +0200 Subject: [PATCH 001/145] fixed rule setting for security groups --- bibigrid/core/actions/create.py | 14 ++++++++------ bibigrid/openstack/openstack_provider.py | 22 ++++++++++++---------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 88c97889..a31149ff 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -148,10 +148,11 @@ def generate_security_groups(self): # allow incoming traffic from all other local provider networks for tmp_configuration in self.configurations: if tmp_configuration != configuration: - rules.append( - {"direction": "ingress", "ethertype": "IPv4", "protocol": "tcp", "port_range_min": None, - "port_range_max": None, "remote_ip_prefix": tmp_configuration['subnet_cidrs'], - "remote_group_id": None}) + for cidr in tmp_configuration['subnet_cidrs']: + rules.append( + {"direction": "ingress", "ethertype": "IPv4", "protocol": "tcp", "port_range_min": None, + "port_range_max": None, "remote_ip_prefix": cidr, + "remote_group_id": None}) provider.append_rules_to_security_group(default_security_group_id, rules) configuration["security_groups"] = [self.default_security_group_name] # store in configuration # when running a multi-cloud setup create an additional wireguard group @@ -351,8 +352,9 @@ def extended_network_configuration(self): f"{configuration_a['private_v4']} --> allowed_address_pair({configuration_a['mac_addr']}," f"{configuration_b['subnet_cidrs']})") # add provider_b network as allowed network - allowed_addresses.append( - {'ip_address': configuration_b["subnet_cidrs"], 'mac_address': configuration_a["mac_addr"]}) + for cidr in configuration_b["subnet_cidrs"]: + allowed_addresses.append( + {'ip_address': cidr, 'mac_address': configuration_a["mac_addr"]}) # configure security group rules provider_a.append_rules_to_security_group(self.wireguard_security_group_name, [ {"direction": "ingress", "ethertype": "IPv4", "protocol": "udp", "port_range_min": 51820, diff --git a/bibigrid/openstack/openstack_provider.py b/bibigrid/openstack/openstack_provider.py index b65b5f62..15ec3ef9 100644 --- a/bibigrid/openstack/openstack_provider.py +++ b/bibigrid/openstack/openstack_provider.py @@ -48,8 +48,8 @@ def create_session(self, app_name="openstack_scripts", app_version="1.0"): auth = self.cloud_specification["auth"] if all(key in auth for key in ["auth_url", "application_credential_id", "application_credential_secret"]): auth_session = v3.ApplicationCredential(auth_url=auth["auth_url"], - application_credential_id=auth["application_credential_id"], - application_credential_secret=auth["application_credential_secret"]) + application_credential_id=auth["application_credential_id"], + application_credential_secret=auth["application_credential_secret"]) elif all(key in auth for key in ["auth_url", "username", "password", "project_id", "user_domain_name"]): auth_session = v3.Password(auth_url=auth["auth_url"], username=auth["username"], password=auth["password"], project_id=auth["project_id"], user_domain_name=auth["user_domain_name"]) @@ -64,14 +64,16 @@ def create_session(self, app_name="openstack_scripts", app_version="1.0"): def create_connection(self, app_name="openstack_bibigrid", app_version=version.__version__): auth = self.cloud_specification["auth"] return openstack.connect(load_yaml_config=False, load_envvars=False, auth_url=auth["auth_url"], - project_name=auth.get("project_name"), username=auth.get("username"), password=auth.get("password"), - region_name=self.cloud_specification["region_name"], user_domain_name=auth.get("user_domain_name"), - project_domain_name=auth.get("user_domain_name"), app_name=app_name, app_version=app_version, - application_credential_id=auth.get("application_credential_id"), - application_credential_secret=auth.get("application_credential_secret"), - interface=self.cloud_specification.get("interface"), - identity_api_version=self.cloud_specification.get("identity_api_version"), - auth_type=self.cloud_specification.get("auth_type")) + project_name=auth.get("project_name"), username=auth.get("username"), + password=auth.get("password"), region_name=self.cloud_specification["region_name"], + user_domain_name=auth.get("user_domain_name"), + project_domain_name=auth.get("user_domain_name"), app_name=app_name, + app_version=app_version, + application_credential_id=auth.get("application_credential_id"), + application_credential_secret=auth.get("application_credential_secret"), + interface=self.cloud_specification.get("interface"), + identity_api_version=self.cloud_specification.get("identity_api_version"), + auth_type=self.cloud_specification.get("auth_type")) def create_application_credential(self, name=None): return self.keystone_client.application_credentials.create(name=name).to_dict() From b705e9103e4fae80b078bbd334a26c25dfca0d04 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 4 Oct 2023 20:23:51 +0200 Subject: [PATCH 002/145] fixed multiple network is now list causing error bugs. --- .../roles/bibigrid/templates/slurm/worker_userdata.j2 | 4 +++- .../playbook/roles/bibigrid/templates/wireguard/device.j2 | 2 +- .../playbook/roles/bibigrid/templates/wireguard/network.j2 | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 b/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 index 3204d575..e86fb1a7 100644 --- a/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 +++ b/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 @@ -5,6 +5,8 @@ bootcmd: - /usr/bin/ip route add 10.0.0.0/24 via {{ hostvars[item].private_v4 }} dev ens3 {% for cluster_cidr in cluster_cidrs %} {% if cluster_cidr.cloud_identifier != hostvars[item].cloud_identifier %} - - /usr/bin/ip route add {{ cluster_cidr.provider_cidrs }} via {{ hostvars[item].private_v4 }} dev ens3 +{% for provider_cidr in cluster_cidr.provider_cidrs %} + - /usr/bin/ip route add {{ provider_cidr }} via {{ hostvars[item].private_v4 }} dev ens3 +{% endfor %} {% endif %} {% endfor %} \ No newline at end of file diff --git a/resources/playbook/roles/bibigrid/templates/wireguard/device.j2 b/resources/playbook/roles/bibigrid/templates/wireguard/device.j2 index 74b3ad9b..a3766a07 100644 --- a/resources/playbook/roles/bibigrid/templates/wireguard/device.j2 +++ b/resources/playbook/roles/bibigrid/templates/wireguard/device.j2 @@ -16,7 +16,7 @@ ListenPort = {{ wireguard_common.listen_port|default(51820) }} # {{ peer.name }} [WireGuardPeer] PublicKey = {{ peer.public_key }} -AllowedIPs = 10.0.0.0/{{ wireguard_common.mask_bits|default(24) }}, {{peer.subnet}} +AllowedIPs = 10.0.0.0/{{ wireguard_common.mask_bits|default(24) }}, {{peer.subnet|join(', ')}} Endpoint = {{ peer.ip }}:{{ wireguard_common.listen_port|default(51820) }} {% endif %} {% endfor %} diff --git a/resources/playbook/roles/bibigrid/templates/wireguard/network.j2 b/resources/playbook/roles/bibigrid/templates/wireguard/network.j2 index cf0113a4..300d21af 100644 --- a/resources/playbook/roles/bibigrid/templates/wireguard/network.j2 +++ b/resources/playbook/roles/bibigrid/templates/wireguard/network.j2 @@ -8,9 +8,9 @@ Address={{ wireguard.ip }}/{{ wireguard_common.mask_bits|default(24) }} [Route] {% if inventory_hostname in groups['master']%} Gateway={{ wireguard.ip }} -Destination={{ hostvars[vpngtw].network_cidr }} +Destination={{ hostvars[vpngtw].network_cidr[0] }} {% else %} Gateway={{ hostvars[vpngtw].wireguard.ip }} -Destination={{ hostvars[groups.master.0].network_cidr }} +Destination={{ hostvars[groups.master.0].network_cidr[0] }} {% endif %} {% endfor %} \ No newline at end of file From 6eb6e6a237375ec95ab1bcd540676675bd18df49 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Sat, 14 Oct 2023 15:50:52 +0200 Subject: [PATCH 003/145] trying to figure out why route applying only works once. --- .../templates/slurm/worker_userdata.j2 | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 b/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 index e86fb1a7..7d90c261 100644 --- a/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 +++ b/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 @@ -1,12 +1,23 @@ #cloud-config {% set cloud_identifier = item.cloud_identifier %} -bootcmd: - - /usr/bin/ip route add 10.0.0.0/24 via {{ hostvars[item].private_v4 }} dev ens3 -{% for cluster_cidr in cluster_cidrs %} -{% if cluster_cidr.cloud_identifier != hostvars[item].cloud_identifier %} -{% for provider_cidr in cluster_cidr.provider_cidrs %} - - /usr/bin/ip route add {{ provider_cidr }} via {{ hostvars[item].private_v4 }} dev ens3 -{% endfor %} -{% endif %} -{% endfor %} \ No newline at end of file +# Create a shell script to apply routes +write_files: + - content: | + #!/bin/bash + echo Adding IP Routes + /usr/bin/ip route add 10.0.0.0/24 via {{ hostvars[item].private_v4 }} + {% for cluster_cidr in cluster_cidrs %} + {% if cluster_cidr.cloud_identifier != hostvars[item].cloud_identifier %} + {% for provider_cidr in cluster_cidr.provider_cidrs %} + /usr/bin/ip route add {{ provider_cidr }} via {{ hostvars[item].private_v4 }} + {% endfor %} + {% endif %} + {% endfor %} + echo "IP Routes Added" + path: /usr/local/bin/apply-routes.sh + permissions: '0755' + +# Execute the shell script +runcmd: + - /usr/local/bin/apply-routes.sh \ No newline at end of file From 8e499b488af7d91bbbe1fc883e539d7cddbb50cf Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 16 Oct 2023 16:53:34 +0200 Subject: [PATCH 004/145] Added more echo's for better debugging. --- .../playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 b/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 index 7d90c261..1a7bc1d9 100644 --- a/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 +++ b/resources/playbook/roles/bibigrid/templates/slurm/worker_userdata.j2 @@ -7,13 +7,16 @@ write_files: #!/bin/bash echo Adding IP Routes /usr/bin/ip route add 10.0.0.0/24 via {{ hostvars[item].private_v4 }} + echo /usr/bin/ip route add 10.0.0.0/24 via {{ hostvars[item].private_v4 }} - $? {% for cluster_cidr in cluster_cidrs %} {% if cluster_cidr.cloud_identifier != hostvars[item].cloud_identifier %} {% for provider_cidr in cluster_cidr.provider_cidrs %} /usr/bin/ip route add {{ provider_cidr }} via {{ hostvars[item].private_v4 }} + echo /usr/bin/ip route add {{ provider_cidr }} via {{ hostvars[item].private_v4 }} - $? {% endfor %} {% endif %} {% endfor %} + echo "$(/usr/bin/ip route)" echo "IP Routes Added" path: /usr/local/bin/apply-routes.sh permissions: '0755' From d79d8047b6056f7dfc5685dcd334c857e65b1208 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 17 Oct 2023 18:29:37 +0200 Subject: [PATCH 005/145] updated most tests --- tests/test_ValidateConfiguration.py | 321 -------------------------- tests/test_validate_configuration.py | 333 +++++++++++++++++++++++++++ 2 files changed, 333 insertions(+), 321 deletions(-) delete mode 100644 tests/test_ValidateConfiguration.py create mode 100644 tests/test_validate_configuration.py diff --git a/tests/test_ValidateConfiguration.py b/tests/test_ValidateConfiguration.py deleted file mode 100644 index 96ffad26..00000000 --- a/tests/test_ValidateConfiguration.py +++ /dev/null @@ -1,321 +0,0 @@ -import os -from unittest import TestCase -from unittest.mock import Mock, patch, MagicMock, call - -from bibigrid.core.utility import validate_configuration - - -class TestValidateConfiguration(TestCase): - # pylint: disable=R0904 - def test_check_provider_data_count(self): - provider_data_1 = {"PROJECT_ID": "abcd", "PROJECT_NAME": "1234"} - provider_data_2 = {"PROJECT_ID": "9999", "PROJECT_NAME": "9999"} - vc = validate_configuration - self.assertTrue(vc.check_provider_data([provider_data_1, provider_data_2], 2)) - self.assertFalse(vc.check_provider_data([provider_data_1, provider_data_2], 3)) - self.assertTrue(vc.check_provider_data([], 0)) - - def test_check_provider_data_unique(self): - provider_data_1 = {"PROJECT_ID": "abcd", "PROJECT_NAME": "1234"} - provider_data_2 = {"PROJECT_ID": "9999", "PROJECT_NAME": "9999"} - vc = validate_configuration - self.assertTrue(vc.check_provider_data([provider_data_1, provider_data_2], 2)) - self.assertFalse(vc.check_provider_data([provider_data_1, provider_data_1], 2)) - self.assertTrue(vc.check_provider_data([], 0)) - - def test_check_master_vpn_worker_ordered(self): - master = {"masterInstance": "Value"} - vpn = {"vpnInstance": "Value"} - vpn_master = {} - vpn_master.update(master) - vpn_master.update(vpn) - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=[master]) - self.assertTrue(vc.check_master_vpn_worker()) - vc.configurations = [master, vpn] - self.assertTrue(vc.check_master_vpn_worker()) - vc.configurations = [vpn] - self.assertFalse(vc.check_master_vpn_worker()) - vc.configurations = [master, master] - self.assertFalse(vc.check_master_vpn_worker()) - - def test_check_master_vpn_worker_unique(self): - master = {"masterInstance": "Value"} - vpn = {"vpnInstance": "Value"} - vpn_master = {} - vpn_master.update(master) - vpn_master.update(vpn) - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=[vpn_master]) - self.assertFalse(vc.check_master_vpn_worker()) - vc.configurations = [master, vpn_master] - self.assertFalse(vc.check_master_vpn_worker()) - - def test_evaluate(self): - vc = validate_configuration - self.assertTrue(vc.evaluate("some", True)) - self.assertFalse(vc.evaluate("some", False)) - - def test_check_provider_connection(self): - mock = Mock() - mock.conn = False - vc = validate_configuration.ValidateConfiguration(providers=[mock], configurations=None) - self.assertFalse(vc.check_provider_connections()) - mock.conn = True - self.assertTrue(vc.check_provider_connections()) - - def test_check_instances_master(self): - vc = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"masterInstance": "42"}]) - with patch.object(vc, "check_instance") as mock: - vc.check_instances() - mock.assert_called_with("masterInstance", "42", "31") - - def test_check_instances_vpn(self): - vc = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"vpnInstance": "42"}]) - with patch.object(vc, "check_instance") as mock: - vc.check_instances() - mock.assert_called_with("vpnInstance", "42", "31") - - def test_check_instances_vpn_worker(self): - vc = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[ - {"masterInstance": "42", "workerInstances": ["42"]}]) - with patch.object(vc, "check_instance") as mock: - vc.check_instances() - mock.assert_called_with("workerInstance", "42", "31") - - def test_check_instances_vpn_master_missing(self): - vc = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{}]) - self.assertFalse(vc.check_instances()) - vc = validate_configuration.ValidateConfiguration(providers=["31"], - configurations=[{"workerInstances": ["42"]}]) - self.assertFalse(vc.check_instances()) - - def test_check_instances_vpn_master_count(self): - for i in range(3): - vc = validate_configuration.ValidateConfiguration(providers=["31"] * i, - configurations=[{"masterInstance": "42"}] * i) - # with patch.object(vc, "check_instance") as mock: - vc.check_instances() - self.assertTrue(vc.required_resources_dict["floating_ips"] == i) - - def test_check_instance_image_not_found(self): - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value=None) - self.assertFalse(vc.check_instance(None, {"count": 1, "image": 2}, provider)) - - def test_check_instance_image_not_active(self): - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value={"status": None}) - self.assertFalse(vc.check_instance(None, {"count": 1, "image": 2}, provider)) - - def test_check_instance_image_active_combination_call(self): - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value={"status": "active"}) - with patch.object(vc, "check_instance_type_image_combination") as mock: - vc.check_instance(42, {"count": 1, "image": 2, "type": 3}, provider) - mock.assert_called_with(3, {"status": "active"}, provider) - - def test_check_instance_image_not_found_count(self): - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value=None) - for i in range(1, 3): - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - vc.check_instance(None, {"count": i, "image": 2}, provider) - self.assertTrue(vc.required_resources_dict["instances"] == i) - - def test_check_instance_type_image_combination_has_enough_calls(self): - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - provider = MagicMock() - provider.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} - provider.get_image_by_id_or_name.return_value = {"minDisk": 22, "minRam": 12} - with patch.object(vc, "has_enough") as mock: - vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider) - self.assertEqual(call(42, 22, "Type de.NBI tiny", "disk space"), mock.call_args_list[0]) - self.assertEqual(call(32, 12, "Type de.NBI tiny", "ram"), mock.call_args_list[1]) - - def test_check_instance_type_image_combination_result(self): - provider = MagicMock() - provider.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} - provider.get_image_by_id_or_name.return_value = {"minDisk": 22, "minRam": 12} - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - with patch.object(vc, "has_enough") as mock: - mock.side_effect = [True, True, False, False, True, False, False, True] - # True True - self.assertTrue(vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) - # False False - self.assertFalse(vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) - # True False - self.assertFalse(vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) - # False True - self.assertFalse(vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) - - def test_check_instance_type_image_combination_count(self): - for i in range(3): - provider = MagicMock() - provider.get_flavor.return_value = {"disk": 42, "ram": i * 32, "vcpus": i * 10} - provider.get_image_by_id_or_name.return_value = {"minDisk": 22, "minRam": 12} - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - with patch.object(vc, "has_enough") as mock: - vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider) - self.assertEqual(32 * i, vc.required_resources_dict["total_ram"]) - self.assertEqual(10 * i, vc.required_resources_dict["total_cores"]) - mock.assert_called_with(32 * i, 12, 'Type de.NBI tiny', 'ram') - - def test_check_volumes_none(self): - vc = validate_configuration.ValidateConfiguration(providers=[42], configurations=[{}]) - self.assertTrue(vc.check_volumes()) - - def test_check_volumes_mismatch(self): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value=None) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"masterMounts": ["Test"]}]) - self.assertFalse(vc.check_volumes()) - - def test_check_volumes_match_snapshot(self): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value=None) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": 1}) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"masterMounts": ["Test"]}]) - self.assertTrue(vc.check_volumes()) - - def test_check_volumes_match_snapshot_count(self): - for i in range(3): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value=None) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": i}) - vc = validate_configuration.ValidateConfiguration(providers=[provider] * i, - configurations=[{"masterMounts": ["Test"] * i}]) - self.assertTrue(vc.check_volumes()) - self.assertTrue(vc.required_resources_dict["Volumes"] == i) - self.assertTrue(vc.required_resources_dict["VolumeGigabytes"] == i ** 2) - - def test_check_volumes_match_volume(self): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value={"size": 1}) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"masterMounts": ["Test"]}]) - self.assertTrue(vc.check_volumes()) - self.assertTrue(vc.required_resources_dict["Volumes"] == 0) - self.assertTrue(vc.required_resources_dict["VolumeGigabytes"] == 0) - - def test_check_network_none(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{}]) - self.assertFalse(vc.check_network()) - - def test_check_network_no_network(self): - provider = Mock() - provider.get_subnet_by_id_or_name = MagicMock(return_value="network") - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"subnet": "subnet_name"}]) - self.assertTrue(vc.check_network()) - provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") - - def test_check_network_no_network_mismatch_subnet(self): - provider = Mock() - provider.get_subnet_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"subnet": "subnet_name"}]) - self.assertFalse(vc.check_network()) - provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") - - def test_check_network_no_subnet_mismatch_network(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"network": "network_name"}]) - self.assertFalse(vc.check_network()) - provider.get_network_by_id_or_name.assert_called_with("network_name") - - def test_check_network_no_subnet(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value="network") - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"network": "network_name"}]) - self.assertTrue(vc.check_network()) - provider.get_network_by_id_or_name.assert_called_with("network_name") - - def test_check_network_subnet_network(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value="network") - provider.get_subnet_by_id_or_name = MagicMock(return_value="network") - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"network": "network_name"}]) - self.assertTrue(vc.check_network()) - provider.get_network_by_id_or_name.assert_called_with("network_name") - - def test_check_server_group_none(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{}]) - self.assertTrue(vc.check_server_group()) - - def test_check_server_group_mismatch(self): - provider = Mock() - provider.get_server_group_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"serverGroup": "GroupName"}]) - self.assertFalse(vc.check_server_group()) - provider.get_server_group_by_id_or_name.assert_called_with("GroupName") - - def test_check_server_group_match(self): - provider = Mock() - provider.get_server_group_by_id_or_name = MagicMock(return_value="Group") - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"serverGroup": "GroupName"}]) - self.assertTrue(vc.check_server_group()) - provider.get_server_group_by_id_or_name.assert_called_with("GroupName") - - def test_check_quotas_true(self): - provider = MagicMock() - provider.cloud_specification = {"auth": {"project_name": "name"}, "identifier": "identifier"} - test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, - 'Volumes': 42, 'VolumeGigabytes': 42, 'Snapshots': 42, 'Backups': 42, 'BackupGigabytes': 42} - provider.get_free_resources.return_value = test_dict - vc = validate_configuration.ValidateConfiguration(providers=[provider], configurations=None) - with patch.object(vc, "has_enough") as mock: - mock.side_effect = [True] * len(test_dict) - self.assertTrue(vc.check_quotas()) - provider.get_free_resources.assert_called() - for key in vc.required_resources_dict.keys(): - self.assertTrue(call(test_dict[key], vc.required_resources_dict[key], - "Project identifier", key) in mock.call_args_list) - - def test_check_quotas_false(self): - provider = MagicMock() - test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, - 'Volumes': 42, 'VolumeGigabytes': 42, 'Snapshots': 42, 'Backups': 42, 'BackupGigabytes': 42} - provider.get_free_resources.return_value = test_dict - os.environ['OS_PROJECT_NAME'] = "name" - vc = validate_configuration.ValidateConfiguration(providers=[provider], configurations=None) - with patch.object(vc, "has_enough") as mock: - mock.side_effect = [True] * (len(test_dict) - 1) + [False] - self.assertFalse(vc.check_quotas()) - provider.get_free_resources.assert_called() - mock.assert_called() - - def test_has_enough_lower(self): - vc = validate_configuration - self.assertTrue(vc.has_enough(2, 1, "", "")) - - def test_has_enough_equal(self): - vc = validate_configuration - self.assertTrue(vc.has_enough(2, 2, "", "")) - - def test_has_enough_higher(self): - vc = validate_configuration - self.assertFalse(vc.has_enough(1, 2, "", "")) diff --git a/tests/test_validate_configuration.py b/tests/test_validate_configuration.py new file mode 100644 index 00000000..c3611053 --- /dev/null +++ b/tests/test_validate_configuration.py @@ -0,0 +1,333 @@ +""" +Tests for validate configuration +""" + +import os +from unittest import TestCase +from unittest.mock import Mock, patch, MagicMock, call + +from bibigrid.core.utility import validate_configuration +from bibigrid.models.exceptions import ImageNotActiveException + + +class TestValidateConfiguration(TestCase): + """ + Class to test ValidateConfiguration + """ + # pylint: disable=R0904 + def test_check_provider_data_count(self): + provider_data_1 = {"PROJECT_ID": "abcd", "PROJECT_NAME": "1234"} + provider_data_2 = {"PROJECT_ID": "9999", "PROJECT_NAME": "9999"} + self.assertTrue(validate_configuration.check_provider_data([provider_data_1, provider_data_2], 2, log=Mock())) + self.assertFalse(validate_configuration.check_provider_data([provider_data_1, provider_data_2], 3, log=Mock())) + self.assertTrue(validate_configuration.check_provider_data([], 0, log=Mock())) + + def test_check_provider_data_unique(self): + provider_data_1 = {"PROJECT_ID": "abcd", "PROJECT_NAME": "1234"} + provider_data_2 = {"PROJECT_ID": "9999", "PROJECT_NAME": "9999"} + self.assertTrue(validate_configuration.check_provider_data([provider_data_1, provider_data_2], 2, log=Mock())) + self.assertFalse(validate_configuration.check_provider_data([provider_data_1, provider_data_1], 2, log=Mock())) + self.assertTrue(validate_configuration.check_provider_data([], 0, log=Mock())) + + @patch("bibigrid.core.utility.image_selection.select_image") + def test_check_master_vpn_worker_ordered(self, mock_select_image): # pylint: disable=unused-argument + master = {"masterInstance": "Value"} + vpn = {"vpnInstance": "Value"} + vpn_master = {} + vpn_master.update(master) + vpn_master.update(vpn) + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=[master], log=Mock()) + self.assertTrue(v_c.check_master_vpn_worker()) + v_c.configurations = [master, vpn] + self.assertTrue(v_c.check_master_vpn_worker()) + v_c.configurations = [vpn] + self.assertFalse(v_c.check_master_vpn_worker()) + v_c.configurations = [master, master] + self.assertFalse(v_c.check_master_vpn_worker()) + + def test_check_master_vpn_worker_unique(self): + master = {"masterInstance": "Value"} + vpn = {"vpnInstance": "Value"} + vpn_master = {} + vpn_master.update(master) + vpn_master.update(vpn) + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=[vpn_master], log=Mock()) + self.assertFalse(v_c.check_master_vpn_worker()) + v_c.configurations = [master, vpn_master] + self.assertFalse(v_c.check_master_vpn_worker()) + + def test_evaluate(self): + self.assertTrue(validate_configuration.evaluate("some", True, log=Mock())) + self.assertFalse(validate_configuration.evaluate("some", False, log=Mock())) + + def test_check_provider_connection(self): + mock = MagicMock() + mock.conn = False + v_c = validate_configuration.ValidateConfiguration(providers=[mock], configurations=None, log=Mock()) + self.assertFalse(v_c.check_provider_connections()) + mock.conn = True + self.assertTrue(v_c.check_provider_connections()) + + def test_check_instances_master(self): + v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"masterInstance": "42"}], + log=Mock()) + with patch.object(v_c, "check_instance") as mock: + v_c.check_instances() + mock.assert_called_with("masterInstance", "42", "31") + + def test_check_instances_vpn(self): + v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"vpnInstance": "42"}], + log=Mock()) + with patch.object(v_c, "check_instance") as mock: + v_c.check_instances() + mock.assert_called_with("vpnInstance", "42", "31") + + def test_check_instances_vpn_worker(self): + v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[ + {"masterInstance": "42", "workerInstances": ["42"]}], log=Mock()) + with patch.object(v_c, "check_instance") as mock: + v_c.check_instances() + mock.assert_called_with("workerInstance", "42", "31") + + def test_check_instances_vpn_master_missing(self): + v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{}], log=Mock()) + self.assertFalse(v_c.check_instances()) + v_c = validate_configuration.ValidateConfiguration(providers=["31"], + configurations=[{"workerInstances": ["42"]}], log=Mock()) + self.assertFalse(v_c.check_instances()) + + def test_check_instances_vpn_master_count(self): + for i in range(3): + v_c = validate_configuration.ValidateConfiguration(providers=["31"] * i, + configurations=[{"masterInstance": {"count": 1}}] * i, + log=Mock()) + # with patch.object(v_c, "check_instance") as mock: + v_c.check_instances() + print(v_c.required_resources_dict["floating_ips"]) + self.assertTrue(v_c.required_resources_dict["floating_ips"] == i) + + @patch("bibigrid.core.utility.image_selection.select_image") + def test_check_instance_image_not_found(self, mock_select_image): # pylint: disable=unused-argument + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) + provider = Mock() + provider.get_image_by_id_or_name = MagicMock(return_value=None) + self.assertFalse(v_c.check_instance(None, {"count": 1, "image": 2}, provider)) + + @patch("bibigrid.core.utility.image_selection.select_image") + def test_check_instance_image_not_active(self, mock_select_image): + mock_select_image.side_effect = ImageNotActiveException() + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) + provider = Mock() + provider.get_active_images.return_value = [] + provider.get_flavor = MagicMock(return_value={"disk": None, "ram": None}) + provider.get_image_by_id_or_name = MagicMock(return_value={"min_disk": None, "min_ram": None}) + self.assertFalse(v_c.check_instance(None, {"count": 1, "image": 2, "type": 3}, provider)) + + @patch("bibigrid.core.utility.image_selection.select_image") + def test_check_instance_image_active_combination_call(self, mock_select_image): + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) + provider = Mock() + provider.get_image_by_id_or_name = MagicMock(return_value={"status": "active"}) + with patch.object(v_c, "check_instance_type_image_combination") as mock: + v_c.check_instance(42, {"count": 1, "image": 2, "type": 3}, provider) + mock.assert_called_with(3, mock_select_image(2), provider) + + @patch("bibigrid.core.utility.image_selection.select_image") + def test_check_instance_image_not_found_count(self, mock_select_image): # pylint: disable=unused-argument + provider = Mock() + provider.get_image_by_id_or_name = MagicMock(return_value=None) + for i in range(1, 3): + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) + v_c.check_instance(None, {"count": i, "image": 2}, provider) + self.assertTrue(v_c.required_resources_dict["instances"] == i) + + def test_check_instance_type_image_combination_has_enough_calls(self): + log = Mock() + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=log) + provider = MagicMock() + provider.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} + provider.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + with patch.object(validate_configuration, "has_enough") as mock: + v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider) + self.assertEqual(call(42, 22, "Type de.NBI tiny", "disk space", log), mock.call_args_list[0]) + self.assertEqual(call(32, 12, "Type de.NBI tiny", "ram", log), mock.call_args_list[1]) + + def test_check_instance_type_image_combination_result(self): + provider = MagicMock() + provider.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} + provider.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) + with patch.object(validate_configuration, "has_enough") as mock: + mock.side_effect = [True, True, False, False, True, False, False, True] + # True True + self.assertTrue(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider)) + # False False + self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider)) + # True False + self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider)) + # False True + self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider)) + + def test_check_instance_type_image_combination_count(self): + for i in range(3): + provider = MagicMock() + provider.get_flavor.return_value = {"disk": 42, "ram": i * 32, "vcpus": i * 10} + provider.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + log = Mock() + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=log) + with patch.object(validate_configuration, "has_enough") as mock: + v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider) + self.assertEqual(32 * i, v_c.required_resources_dict["total_ram"]) + self.assertEqual(10 * i, v_c.required_resources_dict["total_cores"]) + mock.assert_called_with(32 * i, 12, 'Type de.NBI tiny', 'ram', log) + + def test_check_volumes_none(self): + v_c = validate_configuration.ValidateConfiguration(providers=[42], configurations=[{}], log=Mock()) + self.assertTrue(v_c.check_volumes()) + + def test_check_volumes_mismatch(self): + provider = Mock() + provider.get_volume_by_id_or_name = MagicMock(return_value=None) + provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"masterMounts": ["Test"]}], log=Mock()) + self.assertFalse(v_c.check_volumes()) + + def test_check_volumes_match_snapshot(self): + provider = Mock() + provider.get_volume_by_id_or_name = MagicMock(return_value=None) + provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": 1}) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"masterMounts": ["Test"]}], log=Mock()) + self.assertTrue(v_c.check_volumes()) + + def test_check_volumes_match_snapshot_count(self): + for i in range(3): + provider = Mock() + provider.get_volume_by_id_or_name = MagicMock(return_value=None) + provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": i}) + v_c = validate_configuration.ValidateConfiguration(providers=[provider] * i, + configurations=[{"masterMounts": ["Test"] * i}], + log=Mock()) + self.assertTrue(v_c.check_volumes()) + self.assertTrue(v_c.required_resources_dict["Volumes"] == i) + self.assertTrue(v_c.required_resources_dict["VolumeGigabytes"] == i ** 2) + + def test_check_volumes_match_volume(self): + provider = Mock() + provider.get_volume_by_id_or_name = MagicMock(return_value={"size": 1}) + provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"masterMounts": ["Test"]}], log=Mock()) + self.assertTrue(v_c.check_volumes()) + self.assertTrue(v_c.required_resources_dict["Volumes"] == 0) + self.assertTrue(v_c.required_resources_dict["VolumeGigabytes"] == 0) + + def test_check_network_none(self): + provider = Mock() + provider.get_network_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=[{}], log=Mock()) + self.assertFalse(v_c.check_network()) + + def test_check_network_no_network(self): + provider = Mock() + provider.get_subnet_by_id_or_name = MagicMock(return_value="network") + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"subnet": "subnet_name"}], log=Mock()) + self.assertTrue(v_c.check_network()) + provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") + + def test_check_network_no_network_mismatch_subnet(self): + provider = Mock() + provider.get_subnet_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"subnet": "subnet_name"}], log=Mock()) + self.assertFalse(v_c.check_network()) + provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") + + def test_check_network_no_subnet_mismatch_network(self): + provider = Mock() + provider.get_network_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"network": "network_name"}], log=Mock()) + self.assertFalse(v_c.check_network()) + provider.get_network_by_id_or_name.assert_called_with("network_name") + + def test_check_network_no_subnet(self): + provider = Mock() + provider.get_network_by_id_or_name = MagicMock(return_value="network") + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"network": "network_name"}], log=Mock()) + self.assertTrue(v_c.check_network()) + provider.get_network_by_id_or_name.assert_called_with("network_name") + + def test_check_network_subnet_network(self): + provider = Mock() + provider.get_network_by_id_or_name = MagicMock(return_value="network") + provider.get_subnet_by_id_or_name = MagicMock(return_value="network") + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"network": "network_name"}], log=Mock()) + self.assertTrue(v_c.check_network()) + provider.get_network_by_id_or_name.assert_called_with("network_name") + + def test_check_server_group_none(self): + provider = Mock() + provider.get_network_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=[{}], log=Mock()) + self.assertTrue(v_c.check_server_group()) + + def test_check_server_group_mismatch(self): + provider = Mock() + provider.get_server_group_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"serverGroup": "GroupName"}], log=Mock()) + self.assertFalse(v_c.check_server_group()) + provider.get_server_group_by_id_or_name.assert_called_with("GroupName") + + def test_check_server_group_match(self): + provider = Mock() + provider.get_server_group_by_id_or_name = MagicMock(return_value="Group") + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"serverGroup": "GroupName"}], log=Mock()) + self.assertTrue(v_c.check_server_group()) + provider.get_server_group_by_id_or_name.assert_called_with("GroupName") + + def test_check_quotas_true(self): + provider = MagicMock() + provider.cloud_specification = {"auth": {"project_name": "name"}, "identifier": "identifier"} + test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, 'Volumes': 42, + 'VolumeGigabytes': 42, 'Snapshots': 42, 'Backups': 42, 'BackupGigabytes': 42} + provider.get_free_resources.return_value = test_dict + v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=None, log=Mock()) + with patch.object(validate_configuration, "has_enough") as mock: + mock.side_effect = [True] * len(test_dict) + self.assertTrue(v_c.check_quotas()) + provider.get_free_resources.assert_called() + + def test_check_quotas_false(self): + provider = MagicMock() + test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, 'Volumes': 42, + 'VolumeGigabytes': 42, 'Snapshots': 42, 'Backups': 42, 'BackupGigabytes': 42} + provider.get_free_resources.return_value = test_dict + os.environ['OS_PROJECT_NAME'] = "name" + v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=None, log=Mock()) + with patch.object(validate_configuration, "has_enough") as mock: + mock.side_effect = [True] * (len(test_dict) - 1) + [False] + self.assertFalse(v_c.check_quotas()) + provider.get_free_resources.assert_called() + mock.assert_called() + + def test_has_enough_lower(self): + self.assertTrue(validate_configuration.has_enough(2, 1, "", "", log=Mock())) + + def test_has_enough_equal(self): + self.assertTrue(validate_configuration.has_enough(2, 2, "", "", log=Mock())) + + def test_has_enough_higher(self): + self.assertFalse(validate_configuration.has_enough(1, 2, "", "", log=Mock())) From 1ce4f2e0775ee5793fad039b3373ae11dd4167d5 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 19 Oct 2023 11:43:32 +0200 Subject: [PATCH 006/145] fixed validate_configuration.py tests. --- tests/test_validate_configuration.py | 69 +++++++++++----------------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/tests/test_validate_configuration.py b/tests/test_validate_configuration.py index c3611053..5deb65c8 100644 --- a/tests/test_validate_configuration.py +++ b/tests/test_validate_configuration.py @@ -14,6 +14,7 @@ class TestValidateConfiguration(TestCase): """ Class to test ValidateConfiguration """ + # pylint: disable=R0904 def test_check_provider_data_count(self): provider_data_1 = {"PROJECT_ID": "abcd", "PROJECT_NAME": "1234"} @@ -70,14 +71,14 @@ def test_check_provider_connection(self): def test_check_instances_master(self): v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"masterInstance": "42"}], - log=Mock()) + log=Mock()) with patch.object(v_c, "check_instance") as mock: v_c.check_instances() mock.assert_called_with("masterInstance", "42", "31") def test_check_instances_vpn(self): v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"vpnInstance": "42"}], - log=Mock()) + log=Mock()) with patch.object(v_c, "check_instance") as mock: v_c.check_instances() mock.assert_called_with("vpnInstance", "42", "31") @@ -93,26 +94,19 @@ def test_check_instances_vpn_master_missing(self): v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{}], log=Mock()) self.assertFalse(v_c.check_instances()) v_c = validate_configuration.ValidateConfiguration(providers=["31"], - configurations=[{"workerInstances": ["42"]}], log=Mock()) + configurations=[{"workerInstances": ["42"]}], log=Mock()) self.assertFalse(v_c.check_instances()) def test_check_instances_vpn_master_count(self): - for i in range(3): + for i in range(1, 4): v_c = validate_configuration.ValidateConfiguration(providers=["31"] * i, - configurations=[{"masterInstance": {"count": 1}}] * i, - log=Mock()) + configurations=[{"masterInstance": {"count": 1}}] + [ + {"vpnInstance": {"count": 1}}] * (i - 1), log=Mock()) # with patch.object(v_c, "check_instance") as mock: - v_c.check_instances() - print(v_c.required_resources_dict["floating_ips"]) + with patch.object(v_c, "check_instance", return_value=True): + v_c.check_instances() self.assertTrue(v_c.required_resources_dict["floating_ips"] == i) - @patch("bibigrid.core.utility.image_selection.select_image") - def test_check_instance_image_not_found(self, mock_select_image): # pylint: disable=unused-argument - v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value=None) - self.assertFalse(v_c.check_instance(None, {"count": 1, "image": 2}, provider)) - @patch("bibigrid.core.utility.image_selection.select_image") def test_check_instance_image_not_active(self, mock_select_image): mock_select_image.side_effect = ImageNotActiveException() @@ -132,15 +126,6 @@ def test_check_instance_image_active_combination_call(self, mock_select_image): v_c.check_instance(42, {"count": 1, "image": 2, "type": 3}, provider) mock.assert_called_with(3, mock_select_image(2), provider) - @patch("bibigrid.core.utility.image_selection.select_image") - def test_check_instance_image_not_found_count(self, mock_select_image): # pylint: disable=unused-argument - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value=None) - for i in range(1, 3): - v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) - v_c.check_instance(None, {"count": i, "image": 2}, provider) - self.assertTrue(v_c.required_resources_dict["instances"] == i) - def test_check_instance_type_image_combination_has_enough_calls(self): log = Mock() v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=log) @@ -149,7 +134,7 @@ def test_check_instance_type_image_combination_has_enough_calls(self): provider.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} with patch.object(validate_configuration, "has_enough") as mock: v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider) + provider=provider) self.assertEqual(call(42, 22, "Type de.NBI tiny", "disk space", log), mock.call_args_list[0]) self.assertEqual(call(32, 12, "Type de.NBI tiny", "ram", log), mock.call_args_list[1]) @@ -162,16 +147,16 @@ def test_check_instance_type_image_combination_result(self): mock.side_effect = [True, True, False, False, True, False, False, True] # True True self.assertTrue(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) + provider=provider)) # False False self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) + provider=provider)) # True False self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) + provider=provider)) # False True self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) + provider=provider)) def test_check_instance_type_image_combination_count(self): for i in range(3): @@ -182,7 +167,7 @@ def test_check_instance_type_image_combination_count(self): v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=log) with patch.object(validate_configuration, "has_enough") as mock: v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider) + provider=provider) self.assertEqual(32 * i, v_c.required_resources_dict["total_ram"]) self.assertEqual(10 * i, v_c.required_resources_dict["total_cores"]) mock.assert_called_with(32 * i, 12, 'Type de.NBI tiny', 'ram', log) @@ -196,7 +181,7 @@ def test_check_volumes_mismatch(self): provider.get_volume_by_id_or_name = MagicMock(return_value=None) provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) v_c = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"masterMounts": ["Test"]}], log=Mock()) + configurations=[{"masterMounts": ["Test"]}], log=Mock()) self.assertFalse(v_c.check_volumes()) def test_check_volumes_match_snapshot(self): @@ -204,7 +189,7 @@ def test_check_volumes_match_snapshot(self): provider.get_volume_by_id_or_name = MagicMock(return_value=None) provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": 1}) v_c = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"masterMounts": ["Test"]}], log=Mock()) + configurations=[{"masterMounts": ["Test"]}], log=Mock()) self.assertTrue(v_c.check_volumes()) def test_check_volumes_match_snapshot_count(self): @@ -213,8 +198,8 @@ def test_check_volumes_match_snapshot_count(self): provider.get_volume_by_id_or_name = MagicMock(return_value=None) provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": i}) v_c = validate_configuration.ValidateConfiguration(providers=[provider] * i, - configurations=[{"masterMounts": ["Test"] * i}], - log=Mock()) + configurations=[{"masterMounts": ["Test"] * i}], + log=Mock()) self.assertTrue(v_c.check_volumes()) self.assertTrue(v_c.required_resources_dict["Volumes"] == i) self.assertTrue(v_c.required_resources_dict["VolumeGigabytes"] == i ** 2) @@ -224,7 +209,7 @@ def test_check_volumes_match_volume(self): provider.get_volume_by_id_or_name = MagicMock(return_value={"size": 1}) provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) v_c = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"masterMounts": ["Test"]}], log=Mock()) + configurations=[{"masterMounts": ["Test"]}], log=Mock()) self.assertTrue(v_c.check_volumes()) self.assertTrue(v_c.required_resources_dict["Volumes"] == 0) self.assertTrue(v_c.required_resources_dict["VolumeGigabytes"] == 0) @@ -239,7 +224,7 @@ def test_check_network_no_network(self): provider = Mock() provider.get_subnet_by_id_or_name = MagicMock(return_value="network") v_c = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"subnet": "subnet_name"}], log=Mock()) + configurations=[{"subnet": "subnet_name"}], log=Mock()) self.assertTrue(v_c.check_network()) provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") @@ -247,7 +232,7 @@ def test_check_network_no_network_mismatch_subnet(self): provider = Mock() provider.get_subnet_by_id_or_name = MagicMock(return_value=None) v_c = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"subnet": "subnet_name"}], log=Mock()) + configurations=[{"subnet": "subnet_name"}], log=Mock()) self.assertFalse(v_c.check_network()) provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") @@ -255,7 +240,7 @@ def test_check_network_no_subnet_mismatch_network(self): provider = Mock() provider.get_network_by_id_or_name = MagicMock(return_value=None) v_c = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"network": "network_name"}], log=Mock()) + configurations=[{"network": "network_name"}], log=Mock()) self.assertFalse(v_c.check_network()) provider.get_network_by_id_or_name.assert_called_with("network_name") @@ -263,7 +248,7 @@ def test_check_network_no_subnet(self): provider = Mock() provider.get_network_by_id_or_name = MagicMock(return_value="network") v_c = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"network": "network_name"}], log=Mock()) + configurations=[{"network": "network_name"}], log=Mock()) self.assertTrue(v_c.check_network()) provider.get_network_by_id_or_name.assert_called_with("network_name") @@ -272,7 +257,7 @@ def test_check_network_subnet_network(self): provider.get_network_by_id_or_name = MagicMock(return_value="network") provider.get_subnet_by_id_or_name = MagicMock(return_value="network") v_c = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"network": "network_name"}], log=Mock()) + configurations=[{"network": "network_name"}], log=Mock()) self.assertTrue(v_c.check_network()) provider.get_network_by_id_or_name.assert_called_with("network_name") @@ -286,7 +271,7 @@ def test_check_server_group_mismatch(self): provider = Mock() provider.get_server_group_by_id_or_name = MagicMock(return_value=None) v_c = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"serverGroup": "GroupName"}], log=Mock()) + configurations=[{"serverGroup": "GroupName"}], log=Mock()) self.assertFalse(v_c.check_server_group()) provider.get_server_group_by_id_or_name.assert_called_with("GroupName") @@ -294,7 +279,7 @@ def test_check_server_group_match(self): provider = Mock() provider.get_server_group_by_id_or_name = MagicMock(return_value="Group") v_c = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"serverGroup": "GroupName"}], log=Mock()) + configurations=[{"serverGroup": "GroupName"}], log=Mock()) self.assertTrue(v_c.check_server_group()) provider.get_server_group_by_id_or_name.assert_called_with("GroupName") From 661846296687b670743926a65898d04da35d7840 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 19 Oct 2023 12:24:39 +0200 Subject: [PATCH 007/145] Updated tests for startup.py --- bibigrid/core/startup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bibigrid/core/startup.py b/bibigrid/core/startup.py index 3a073f27..4676f44e 100755 --- a/bibigrid/core/startup.py +++ b/bibigrid/core/startup.py @@ -70,6 +70,7 @@ def run_action(args, configurations, config_path): exit_state = 0 try: providers = provider_handler.get_providers(configurations, LOG) + print(args) if providers: if args.list: LOG.info("Action list selected") From 2ff9b9899e4d6c18187eb86e72695acbdb63b9fa Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 19 Oct 2023 16:30:14 +0200 Subject: [PATCH 008/145] fixed bug in terminate that caused assume_yes to work as assume_no --- bibigrid/core/actions/terminate.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bibigrid/core/actions/terminate.py b/bibigrid/core/actions/terminate.py index db812d7f..7a3fadb1 100644 --- a/bibigrid/core/actions/terminate.py +++ b/bibigrid/core/actions/terminate.py @@ -34,11 +34,11 @@ def terminate(cluster_id, providers, log, debug=False, assume_yes=False): cluster_security_group_state = [] tmp_keyname = create.KEY_NAME.format(cluster_id=cluster_id) local_keypairs_deleted = delete_local_keypairs(tmp_keyname, log) - if not assume_yes and ( - local_keypairs_deleted or input(f"WARNING: No local temporary keyfiles found for cluster {cluster_id}. " - f"This might not be your cluster. Are you sure you want to terminate it?\n" - f"Any non-empty input to shutdown cluster {cluster_id}. " - f"Empty input to exit with cluster still alive:")): + if assume_yes or local_keypairs_deleted or input( + f"WARNING: No local temporary keyfiles found for cluster {cluster_id}. " + f"This might not be your cluster. Are you sure you want to terminate it?\n" + f"Any non-empty input to shutdown cluster {cluster_id}. " + f"Empty input to exit with cluster still alive:"): for provider in providers: log.info("Terminating cluster %s on cloud %s", cluster_id, provider.cloud_specification['identifier']) server_list = provider.list_servers() From e487ce0fb17259544d630cae2126dfc80c07c39c Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 19 Oct 2023 16:31:23 +0200 Subject: [PATCH 009/145] updated terminate_cluster tests. --- tests/test_startup.py | 60 +++++++++++++++++++-------------- tests/test_terminateCluster.py | 37 -------------------- tests/test_terminate_cluster.py | 50 +++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 63 deletions(-) mode change 100644 => 100755 tests/test_startup.py delete mode 100644 tests/test_terminateCluster.py create mode 100644 tests/test_terminate_cluster.py diff --git a/tests/test_startup.py b/tests/test_startup.py old mode 100644 new mode 100755 index 3baee61a..64f154d9 --- a/tests/test_startup.py +++ b/tests/test_startup.py @@ -1,3 +1,7 @@ +""" +Modul to test startup +""" + from unittest import TestCase from unittest.mock import Mock, patch, MagicMock @@ -5,44 +9,48 @@ class TestStartup(TestCase): - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') - def test_provider(self, mock_get_providers): + """ + Class to test startup + """ + + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') + def test_provider_closing(self, mock_get_providers): args = Mock() - args.list_clusters = True + args.list = True args.version = False args.cluster_id = 12 provider = Mock provider.close = MagicMock() configurations = {} mock_get_providers.return_value = [provider] - with patch("bibigrid.core.actions.list_clusters.print_list_clusters") as mock_lc: + with patch("bibigrid.core.actions.list_clusters.log_list") as mock_lc: mock_lc.return_value = 42 self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_get_providers.assert_called_with(configurations) + mock_get_providers.assert_called_with(configurations, startup.LOG) provider.close.assert_called() - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') def test_list_clusters(self, get_providers): provider_mock = Mock() provider_mock.close = Mock() get_providers.return_value = [provider_mock] args = Mock() - args.list_clusters = True + args.list = True args.version = False args.cluster_id = 12 configurations = {} - with patch("bibigrid.core.actions.list_clusters.print_list_clusters") as mock_lc: + with patch("bibigrid.core.actions.list_clusters.log_list") as mock_lc: mock_lc.return_value = 42 self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_lc.assert_called_with(12, [provider_mock]) + mock_lc.assert_called_with(12, [provider_mock], startup.LOG) - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') def test_check(self, get_providers): provider_mock = Mock() provider_mock.close = Mock() get_providers.return_value = [provider_mock] args = Mock() - args.list_clusters = False + args.list = False args.version = False args.check = True args.cluster_id = 12 @@ -50,16 +58,16 @@ def test_check(self, get_providers): with patch("bibigrid.core.actions.check.check") as mock_lc: mock_lc.return_value = 42 self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_lc.assert_called_with(configurations, [provider_mock]) + mock_lc.assert_called_with(configurations, [provider_mock], startup.LOG) - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') @patch('bibigrid.core.actions.create.Create') def test_create(self, mock_create, get_providers): provider_mock = Mock() provider_mock.close = Mock() get_providers.return_value = [provider_mock] args = Mock() - args.list_clusters = False + args.list = False args.version = False args.check = False args.create = True @@ -70,17 +78,17 @@ def test_create(self, mock_create, get_providers): creator.create = MagicMock(return_value=42) mock_create.return_value = creator self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_create.assert_called_with(providers=[provider_mock], configurations=configurations, debug=True, - config_path="") + mock_create.assert_called_with(providers=[provider_mock], configurations=configurations, log=startup.LOG, + debug=True, config_path="") creator.create.assert_called() - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') def test_terminate(self, get_providers): provider_mock = Mock() provider_mock.close = Mock() get_providers.return_value = [provider_mock] args = Mock() - args.list_clusters = False + args.list = False args.version = False args.create = False args.check = False @@ -88,27 +96,27 @@ def test_terminate(self, get_providers): args.cluster_id = 12 args.debug = True configurations = {} - with patch("bibigrid.core.actions.terminateCluster.terminate_cluster") as mock_tc: + with patch("bibigrid.core.actions.terminate.terminate") as mock_tc: mock_tc.return_value = 42 self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_tc.assert_called_with(12, [provider_mock], True) + mock_tc.assert_called_with(cluster_id=12, providers=[provider_mock], log=startup.LOG, debug=True) - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') @patch("bibigrid.core.actions.ide.ide") def test_ide(self, mock_ide, get_providers): - provider_mock = Mock() + provider_mock = MagicMock() provider_mock.close = Mock() get_providers.return_value = [provider_mock] args = Mock() - args.list_clusters = False + args.list = False args.version = False args.create = False args.check = False - args.terminate_cluster = False + args.terminate = False args.ide = True args.cluster_id = 12 args.debug = True - configurations = {} + configurations = [{"test_key": "test_value"}] mock_ide.return_value = 42 self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_ide.assert_called_with(12, [provider_mock], {}) + mock_ide.assert_called_with(12, provider_mock, {"test_key": "test_value"}, startup.LOG) diff --git a/tests/test_terminateCluster.py b/tests/test_terminateCluster.py deleted file mode 100644 index 4d9348a6..00000000 --- a/tests/test_terminateCluster.py +++ /dev/null @@ -1,37 +0,0 @@ -from unittest import TestCase -from unittest.mock import MagicMock, patch - -from bibigrid.core.actions import create -from bibigrid.core.actions import terminate_cluster - - -class TestTerminate(TestCase): - - @patch("bibigrid.core.actions.terminate_cluster.terminate_output") - def test_terminate_cluster(self, _, mock_output): - provider = MagicMock() - provider.cloud_specification["auth"]["project_name"] = 32 - cluster_id = 42 - provider.list_servers.return_value = [ - {"name": create.MASTER_IDENTIFIER + create.SEPARATOR + str(cluster_id), "id": 21}] - provider.delete_server.return_value = True - provider.delete_keypair.return_value = True - terminate_cluster.terminate_cluster(str(cluster_id), [provider], False) - provider.delete_server.assert_called_with(21) - provider.delete_keypair.assert_called_with( - create.KEY_PREFIX + provider.cloud_specification["auth"]["project_name"] + - create.SEPARATOR + str(cluster_id)) - mock_output.assert_called_with([provider.delete_server.return_value], - [provider.delete_keypair.return_value], str(cluster_id)) - - @patch("logging.info") - def test_terminate_cluster_none(self, _): - provider = MagicMock() - provider[0].specification["auth"]["project_name"] = "test_project_name" - cluster_id = 42 - provider.list_servers.return_value = [ - {"name": create.MASTER_IDENTIFIER + create.SEPARATOR + str(cluster_id + 1), "id": 21}] - provider.delete_keypair.return_value = False - terminate_cluster.terminate_cluster(str(cluster_id), [provider], False) - provider.delete_server.assert_not_called() - provider.delete_keypair.assert_called_with('bibigrid42') # since keypair is not called diff --git a/tests/test_terminate_cluster.py b/tests/test_terminate_cluster.py new file mode 100644 index 00000000..949b0d1c --- /dev/null +++ b/tests/test_terminate_cluster.py @@ -0,0 +1,50 @@ +""" +Module to test terminate +""" +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from bibigrid.core import startup +from bibigrid.core.actions import create +from bibigrid.core.actions import terminate + + +class TestTerminate(TestCase): + """ + Class to test terminate. + """ + + @patch("bibigrid.core.actions.terminate.delete_local_keypairs") + @patch("bibigrid.core.actions.terminate.terminate_output") + def test_terminate(self, mock_output, mock_local): + mock_local.return_value = True + provider = MagicMock() + provider.cloud_specification["auth"]["project_name"] = 32 + cluster_id = 42 + provider.list_servers.return_value = [{"name": create.MASTER_IDENTIFIER(cluster_id=str(cluster_id)), "id": 21}] + provider.delete_server.return_value = True + provider.delete_keypair.return_value = True + provider.delete_security_group.return_value = True + provider.delete_application_credentials.return_value = True + terminate.terminate(str(cluster_id), [provider], startup.LOG, False, True) + provider.delete_server.assert_called_with(21) + provider.delete_keypair.assert_called_with(create.KEY_NAME.format(cluster_id=cluster_id)) + mock_output.assert_called_with([provider.delete_server.return_value], [provider.delete_keypair.return_value], + [provider.delete_security_group.return_value], + provider.delete_application_credentials.return_value, str(cluster_id), + startup.LOG) + + @patch("bibigrid.core.actions.terminate.delete_local_keypairs") + @patch("logging.info") + def test_terminate_none(self, _, mock_local): + mock_local.return_value = True + provider = MagicMock() + provider[0].specification["auth"]["project_name"] = "test_project_name" + cluster_id = 42 + provider.list_servers.return_value = [ + {"name": create.MASTER_IDENTIFIER(cluster_id=str(cluster_id + 1)), "id": 21}] + provider.delete_keypair.return_value = False + terminate.terminate(str(cluster_id), [provider], startup.LOG, False, True) + provider.delete_server.assert_not_called() + provider.delete_keypair.assert_called_with( + create.KEY_NAME.format(cluster_id=str(cluster_id))) # since keypair is not called From c3f79f160efc937eccebb07afc5e496dc30907f2 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 11:19:30 +0200 Subject: [PATCH 010/145] fixed formatting improved pylint --- bibigrid/core/utility/handler/ssh_handler.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index a705a405..d52cdde6 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -7,8 +7,8 @@ import time import paramiko -import yaml import sympy +import yaml from bibigrid.core.utility import ansible_commands as aC from bibigrid.models.exceptions import ConnectionException, ExecutionException @@ -107,10 +107,10 @@ def is_active(client, floating_ip_address, private_key, username, log, gateway, port = 22 if gateway: log.info(f"Using SSH Gateway {gateway.get('ip')}") - octets = {f'oct{enum+1}': int(elem) for enum, elem in enumerate(floating_ip_address.split("."))} + octets = {f'oct{enum + 1}': int(elem) for enum, elem in enumerate(floating_ip_address.split("."))} port = int(sympy.sympify(gateway["portFunction"]).subs(dict(octets))) - client.connect(hostname=gateway.get("ip") or floating_ip_address, username=username, - pkey=private_key, timeout=7, auth_timeout=5, port=port) + client.connect(hostname=gateway.get("ip") or floating_ip_address, username=username, pkey=private_key, + timeout=7, auth_timeout=5, port=port) establishing_connection = False log.info(f"Successfully connected to {floating_ip_address}") except paramiko.ssh_exception.NoValidConnectionsError as exc: @@ -158,7 +158,7 @@ def execute_ssh_cml_commands(client, commands, log): :param log: """ for command in commands: - ssh_stdin, ssh_stdout, ssh_stderr = client.exec_command(command[0]) # pylint: disable=unused-variable + _, ssh_stdout, _ = client.exec_command(command[0]) ssh_stdout.channel.set_combine_stderr(True) log.info(f"REMOTE: {command[1]}") From dfbac4fb56139c2ead826fdeab2155102f7762d1 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 11:19:51 +0200 Subject: [PATCH 011/145] adapted tests --- tests/test_sshHandler.py | 104 ------------------------------- tests/test_ssh_handler.py | 128 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 104 deletions(-) delete mode 100644 tests/test_sshHandler.py create mode 100644 tests/test_ssh_handler.py diff --git a/tests/test_sshHandler.py b/tests/test_sshHandler.py deleted file mode 100644 index 7a9f05df..00000000 --- a/tests/test_sshHandler.py +++ /dev/null @@ -1,104 +0,0 @@ -import socket -from unittest import TestCase -from unittest.mock import mock_open, Mock, MagicMock, patch, call - -from paramiko.ssh_exception import NoValidConnectionsError - -import bibigrid.core.utility.handler.ssh_handler as sshHandler - - -class TestSshHandler(TestCase): - def test_get_add_ssh_public_key_commands_none(self): - ssh_public_key_files = [] - self.assertEqual([], sshHandler.get_add_ssh_public_key_commands(ssh_public_key_files)) - - def test_get_add_ssh_public_key_commands_line(self): - ssh_public_key_files = [42] - line = "42" - expected = [f"echo {line} >> .ssh/authorized_keys"] - with patch("builtins.open", mock_open(read_data=line)) as mock_file: - self.assertEqual(expected, sshHandler.get_add_ssh_public_key_commands(ssh_public_key_files)) - mock_file.assert_called_with(42) - - def test_copy_to_server_file(self): - sftp = Mock() - sftp.put = MagicMock(return_value=True) - with patch("os.path.isfile") as mock_isfile: - mock_isfile.return_value = True - sshHandler.copy_to_server(sftp, "Jim", "Joe") - sftp.put.assert_called_with("Jim", "Joe") - - @patch("os.listdir") - def test_copy_to_server_folder(self, mock_listdir): - sftp = Mock() - sftp.mkdir = MagicMock() - mock_listdir.return_value = [] - with patch("os.path.isfile") as mock_isfile: - mock_isfile.return_value = False - sshHandler.copy_to_server(sftp, "Jim", "Joe") - mock_listdir.assert_called_with("Jim") - sftp.mkdir.assert_called_with("Joe") - - @patch("logging.info") - def test_is_active(self, mock_log): - client = Mock() - client.connect = MagicMock(return_value=True) - self.assertFalse(sshHandler.is_active(client, 42, 32, 22, timeout=5)) - mock_log.assert_not_called() - - @patch("logging.info") - def test_is_active_second(self, mock_log): - client = Mock() - client.connect = MagicMock(side_effect=[NoValidConnectionsError({('127.0.0.1', 22): socket.error}), True]) - self.assertFalse(sshHandler.is_active(client, 42, 32, 22, timeout=5)) - mock_log.assert_called() - - @patch("logging.info") - def test_is_active_exception(self, mock_log): - client = Mock() - client.connect = MagicMock(side_effect=NoValidConnectionsError({('127.0.0.1', 22): socket.error})) - with self.assertRaises(ConnectionError): - sshHandler.is_active(client, 42, 32, 22, timeout=0) - client.connect.assert_called_with(hostname=42, username=22, pkey=32) - mock_log.assert_called() - - @patch("bibigrid.core.utility.handler.sshHandler.execute_ssh_cml_commands") - @patch("paramiko.ECDSAKey.from_private_key_file") - @patch("paramiko.SSHClient") - def test_execute_ssh(self, mock_client, mock_paramiko_key, mock_exec): - mock_paramiko_key.return_value = 2 - client = Mock() - mock = Mock() - mock_client.return_value = mock - mock.__enter__ = client - mock.__exit__ = Mock(return_value=None) - with patch("bibigrid.core.utility.handler.sshHandler.is_active") as mock_active: - sshHandler.execute_ssh(42, 32, 22, [12], None) - mock_client.assert_called_with() - mock_active.assert_called_with(client=client(), floating_ip_address=42, username=22, private_key=2) - mock_exec.assert_called_with(client(), [12]) - mock_paramiko_key.assert_called_with(32) - - @patch("bibigrid.core.utility.handler.sshHandler.execute_ssh") - def test_ansible_preparation(self, mock_execute): - sshHandler.ansible_preparation(1, 2, 3, [], []) - mock_execute.assert_called_with(1, 2, 3, [] + sshHandler.ANSIBLE_SETUP, [(2, sshHandler.PRIVATE_KEY_FILE)]) - - @patch("bibigrid.core.utility.handler.sshHandler.execute_ssh") - def test_ansible_preparation_elem(self, mock_execute): - sshHandler.ansible_preparation(1, 2, 3, [42], [42]) - mock_execute.assert_called_with(1, 2, 3, sshHandler.ANSIBLE_SETUP + [42], - [42, (2, sshHandler.PRIVATE_KEY_FILE)]) - - @patch("logging.warning") - @patch("logging.info") - def test_execute_ssh_cml_commands(self, mock_log_info, mock_log_warning): - client = Mock() - stdout_mock = Mock() - stdout_mock.channel.recv_exit_status.side_effect = [0, 1] - stdout_mock.readlines.return_value = 49 - client.exec_command.return_value = (0, stdout_mock, 2) - commands = [42, 21] - sshHandler.execute_ssh_cml_commands(client, commands) - self.assertEqual([call('42:0')], mock_log_info.call_args_list) - self.assertEqual([call('21:1|49')], mock_log_warning.call_args_list) diff --git a/tests/test_ssh_handler.py b/tests/test_ssh_handler.py new file mode 100644 index 00000000..7c0cfd46 --- /dev/null +++ b/tests/test_ssh_handler.py @@ -0,0 +1,128 @@ +""" +Module to test ssh_handler +""" +import socket +from unittest import TestCase +from unittest.mock import mock_open, Mock, MagicMock, patch, call + +from paramiko.ssh_exception import NoValidConnectionsError + +from bibigrid.core import startup +from bibigrid.core.utility.handler import ssh_handler +from bibigrid.models.exceptions import ExecutionException, ConnectionException + + +class TestSshHandler(TestCase): + """ + Class to test ssh_handler + @todo: Test Gateway + """ + + def test_get_add_ssh_public_key_commands_none(self): + ssh_public_key_files = [] + self.assertEqual([], ssh_handler.get_add_ssh_public_key_commands(ssh_public_key_files)) + + def test_get_add_ssh_public_key_commands_line(self): + ssh_public_key_files = [42] + line = "42" + expected = [(f"echo {line} >> .ssh/authorized_keys", f"Add SSH Key {line}.")] + with patch("builtins.open", mock_open(read_data=line)) as mock_file: + self.assertEqual(expected, ssh_handler.get_add_ssh_public_key_commands(ssh_public_key_files)) + mock_file.assert_called_with(42, mode='r', encoding='UTF-8') + + def test_copy_to_server_file(self): + sftp = Mock() + sftp.put = MagicMock(return_value=True) + with patch("os.path.isfile") as mock_isfile: + mock_isfile.return_value = True + ssh_handler.copy_to_server(sftp, "Jim", "Joe", startup.LOG) + sftp.put.assert_called_with("Jim", "Joe") + + @patch("os.listdir") + def test_copy_to_server_folder(self, mock_listdir): + sftp = Mock() + sftp.mkdir = MagicMock() + mock_listdir.return_value = [] + with patch("os.path.isfile") as mock_isfile: + mock_isfile.return_value = False + ssh_handler.copy_to_server(sftp, "Jim", "Joe", startup.LOG) + mock_listdir.assert_called_with("Jim") + sftp.mkdir.assert_called_with("Joe") + + @patch("logging.info") + def test_is_active(self, mock_log): + client = Mock() + client.connect = MagicMock(return_value=True) + self.assertFalse(ssh_handler.is_active(client, 42, 32, 22, startup.LOG, {}, timeout=5)) + mock_log.assert_not_called() + + def test_is_active_on_second_attempt(self): + client = Mock() + client.connect = MagicMock(side_effect=[NoValidConnectionsError({('127.0.0.1', 22): socket.error}), True]) + self.assertFalse(ssh_handler.is_active(client, 42, 32, 22, startup.LOG, {}, timeout=5)) + + def test_is_active_exception(self): + client = Mock() + client.connect = MagicMock(side_effect=NoValidConnectionsError({('127.0.0.1', 22): socket.error})) + with self.assertRaises(ConnectionException): + ssh_handler.is_active(client, 42, 32, 22, startup.LOG, {}, timeout=0) + client.connect.assert_called_with(hostname=42, username=22, pkey=32, timeout=7, auth_timeout=5, port=22) + + @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh_cml_commands") + @patch("paramiko.ECDSAKey.from_private_key_file") + @patch("paramiko.SSHClient") + def test_execute_ssh(self, mock_client, mock_paramiko_key, mock_exec): + mock_paramiko_key.return_value = 2 + client = Mock() + mock = Mock() + mock_client.return_value = mock + mock.__enter__ = client + mock.__exit__ = Mock(return_value=None) + with patch("bibigrid.core.utility.handler.ssh_handler.is_active") as mock_active: + ssh_handler.execute_ssh(42, 32, 22, startup.LOG, {}, [12]) + mock_client.assert_called_with() + mock_active.assert_called_with(client=client(), floating_ip_address=42, username=22, private_key=2, + log=startup.LOG, gateway={}) + mock_exec.assert_called_with(client=client(), commands=[12], log=startup.LOG) + mock_paramiko_key.assert_called_with(32) + + @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") + def test_ansible_preparation(self, mock_execute): + ssh_handler.ansible_preparation(1, 2, 3, startup.LOG, {}, [], []) + mock_execute.assert_called_with(1, 2, 3, startup.LOG, {}, ssh_handler.ANSIBLE_SETUP, + [(2, ssh_handler.PRIVATE_KEY_FILE)]) + + @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") + def test_ansible_preparation_elem(self, mock_execute): + ssh_handler.ansible_preparation(1, 2, 3, startup.LOG, {}, [42], [42]) + mock_execute.assert_called_with(1, 2, 3, startup.LOG, {}, ssh_handler.ANSIBLE_SETUP + [42], + [42, (2, ssh_handler.PRIVATE_KEY_FILE)]) + + def test_execute_ssh_cml_commands(self): + client = Mock() + stdout_mock = Mock() + stdout_mock.channel.recv_exit_status.side_effect = [0, 0] + stdout_mock.readline.side_effect = ["First Line", "", "First Line", ""] + client.exec_command.return_value = (0, stdout_mock, 2) + commands = [(42, 0), (21, 1)] + ssh_handler.execute_ssh_cml_commands(client, commands, startup.LOG) + + stdout_mock.channel.recv_exit_status.assert_called() + stdout_mock.channel.recv_exit_status.call_count = 2 + stdout_mock.readline.assert_called() + assert stdout_mock.readline.call_count == 4 + client.exec_command.assert_has_calls([call(42), call(21)]) + assert client.exec_command.call_count == 2 + + def test_execute_ssh_cml_commands_execution_exception(self): + client = Mock() + stdout_mock = Mock() + stdout_mock.channel.recv_exit_status.side_effect = [0, 1] + stdout_mock.readline.side_effect = ["First Line", "", "First Line", ""] + client.exec_command.return_value = (0, stdout_mock, 2) + commands = [(42, 0), (21, 1)] + with self.assertRaises(ExecutionException): + ssh_handler.execute_ssh_cml_commands(client, commands, startup.LOG) + stdout_mock.channel.recv_exit_status.assert_called() + stdout_mock.readline.assert_called() + client.exec_command.assert_called_with(21) From c9b580f2520960b52af6953be300da3260b7b8cc Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 12:09:31 +0200 Subject: [PATCH 012/145] updated return threading test --- ...eturnThreading.py => test_return_threading.py} | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) rename tests/{test_returnThreading.py => test_return_threading.py} (63%) diff --git a/tests/test_returnThreading.py b/tests/test_return_threading.py similarity index 63% rename from tests/test_returnThreading.py rename to tests/test_return_threading.py index be9eeecd..8370f189 100644 --- a/tests/test_returnThreading.py +++ b/tests/test_return_threading.py @@ -1,17 +1,22 @@ +""" +Module to test return thread +""" from unittest import TestCase import bibigrid.models.return_threading as returnThreading -def test_method(x): - return (42, x) +def test_method(elem): + return 42, elem class TestReturnThread(TestCase): + """ + Class to test return thread + """ - def test_ReturnThread(self): - return_thread = returnThreading.ReturnThread(target=test_method, - args=[42]) + def test_return_thread(self): + return_thread = returnThreading.ReturnThread(target=test_method, args=[42]) return_thread.start() return_value = return_thread.join() self.assertTrue(return_value == (42, 42)) From ad3c1dd0eab99e04b7c8499c667e28b0be7cd719 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 12:15:59 +0200 Subject: [PATCH 013/145] updated provider_handler --- tests/test_providerHandler.py | 26 -------------------------- tests/test_provider_handler.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 26 deletions(-) delete mode 100644 tests/test_providerHandler.py create mode 100644 tests/test_provider_handler.py diff --git a/tests/test_providerHandler.py b/tests/test_providerHandler.py deleted file mode 100644 index 6735e77f..00000000 --- a/tests/test_providerHandler.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import TestCase -from unittest.mock import MagicMock, patch - -import bibigrid.core.utility.handler.provider_handler as providerHandler - - -class TestProviderHandler(TestCase): - - @patch("bibigrid.core.utility.handler.configurationHandler.get_cloud_specifications") - @patch("bibigrid.core.utility.handler.providerHandler.get_provider_list_by_name_list") - def test_get_providers(self, mock_provider_list, mock_get_cloud_specifications): - mock_get_cloud_specifications.return_value = True # for if not false - configurations = [{"infrastructure": "some"}] - mock_provider_list.return_value = 42 - with patch("bibigrid.core.utility.handler.configurationHandler.get_list_by_key") as mock_by_name: - self.assertEqual(42, providerHandler.get_providers(configurations)) - mock_by_name.assert_called_with(configurations, "infrastructure") - mock_get_cloud_specifications.assert_called_with(configurations) - - def test_get_provider_list_by_name_list(self): - keys = providerHandler.PROVIDER_NAME_DICT.keys() - values = [42] - with patch("bibigrid.core.utility.handler.providerHandler.get_provider_by_name") as mock_by_name: - mock_by_name.return_value = MagicMock(return_value=42) - self.assertEqual(providerHandler.get_provider_list_by_name_list(keys, "nonempty_specification"), values) - mock_by_name.assert_called_with(list(keys)[0]) diff --git a/tests/test_provider_handler.py b/tests/test_provider_handler.py new file mode 100644 index 00000000..6b37fec9 --- /dev/null +++ b/tests/test_provider_handler.py @@ -0,0 +1,33 @@ +""" +Module to test provider_handler +""" +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from bibigrid.core.utility.handler import provider_handler +from bibigrid.core import startup + + +class TestProviderHandler(TestCase): + """ + Class to test provider_handler + """ + + @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specifications") + @patch("bibigrid.core.utility.handler.provider_handler.get_provider_list_by_name_list") + def test_get_providers(self, mock_provider_list, mock_get_cloud_specifications): + mock_get_cloud_specifications.return_value = True # for if not false + configurations = [{"infrastructure": "some"}] + mock_provider_list.return_value = 42 + with patch("bibigrid.core.utility.handler.configuration_handler.get_list_by_key") as mock_by_name: + self.assertEqual(42, provider_handler.get_providers(configurations, startup.LOG)) + mock_by_name.assert_called_with(configurations, "infrastructure") + mock_get_cloud_specifications.assert_called_with(configurations, startup.LOG) + + def test_get_provider_list_by_name_list(self): + keys = provider_handler.PROVIDER_NAME_DICT.keys() + values = [42] + with patch("bibigrid.core.utility.handler.provider_handler.get_provider_by_name") as mock_by_name: + mock_by_name.return_value = MagicMock(return_value=42) + self.assertEqual(provider_handler.get_provider_list_by_name_list(keys, "nonempty_specification"), values) + mock_by_name.assert_called_with(list(keys)[0]) From 0b441bb0c545328274ab13e494dfef731bd76b66 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 14:06:05 +0200 Subject: [PATCH 014/145] tests not finished yet --- ..._listClusters.py => test_list_clusters.py} | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) rename tests/{test_listClusters.py => test_list_clusters.py} (61%) diff --git a/tests/test_listClusters.py b/tests/test_list_clusters.py similarity index 61% rename from tests/test_listClusters.py rename to tests/test_list_clusters.py index 12744785..d6777146 100644 --- a/tests/test_listClusters.py +++ b/tests/test_list_clusters.py @@ -1,23 +1,29 @@ +""" +Module test list +""" from unittest import TestCase from unittest.mock import Mock +from bibigrid.core import startup from bibigrid.core.actions import create from bibigrid.core.actions import list_clusters -class TestDictClusters(TestCase): +class TestList(TestCase): + """ + Class test list + """ def test_setup(self): for identifier in [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, create.MASTER_IDENTIFIER]: cluster_id = 42 - test_provider = Mock() - test_provider.name = "name" + provider = Mock() + provider.name = "name" + provider.cloud_specification = {"identifier": 21} cluster_dict = {} - server = {"name": identifier + create.SEPARATOR + str(cluster_id)} - self.assertEqual(str(cluster_id), - list_clusters.setup(server, - identifier, cluster_dict, test_provider)) + server = {"name": identifier(cluster_id=str(cluster_id))} + self.assertEqual(str(cluster_id), list_clusters.setup(server, identifier, cluster_dict, provider)) self.assertEqual({str(cluster_id): {'worker': [], 'vpngtw': []}}, cluster_dict) - self.assertEqual(test_provider, server["provider"]) + self.assertEqual(provider, server["provider"]) def test_setup_already(self): for identifier in [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, create.MASTER_IDENTIFIER]: @@ -25,22 +31,18 @@ def test_setup_already(self): test_provider = Mock() test_provider.name = "name" cluster_dict = {str(cluster_id): {'worker': ["some"], 'vpngtw': ["some"]}} - server = {"name": identifier + create.SEPARATOR + str(cluster_id)} - self.assertEqual(str(cluster_id), - list_clusters.setup(server, - identifier, cluster_dict, test_provider)) + server = {"name": identifier(cluster_id=str(cluster_id))} + self.assertEqual(str(cluster_id), list_clusters.setup(server, identifier, cluster_dict, test_provider)) self.assertEqual({str(cluster_id): {'worker': ["some"], 'vpngtw': ["some"]}}, cluster_dict) self.assertEqual(test_provider, server["provider"]) def test_dict_clusters(self): cluster_id = 42 expected = {str(cluster_id): {'workers': [{'name': f'bibigrid-worker-{str(cluster_id)}', 'provider': 'Mock'}], - 'vpngtws': [ - {'name': f'bibigrid-vpngtw-{str(cluster_id)}', 'provider': 'Mock'}], + 'vpngtws': [{'name': f'bibigrid-vpngtw-{str(cluster_id)}', 'provider': 'Mock'}], 'master': {'name': f'bibigrid-master-{str(cluster_id)}', 'provider': 'Mock'}}} provider = Mock() - provider.list_servers.return_value = [{'name': identifier + create.SEPARATOR + str(cluster_id)} for identifier - in + provider.list_servers.return_value = [{'name': identifier(cluster_id=str(cluster_id))} for identifier in [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, create.MASTER_IDENTIFIER]] - self.assertEqual(expected, list_clusters.dict_clusters([provider])) + self.assertEqual(expected, list_clusters.dict_clusters([provider], startup.LOG)) From 961bf0486d22b4964616461fcd35fb4ae5f0635e Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 16:03:01 +0200 Subject: [PATCH 015/145] Fixed server regex issue --- bibigrid/core/actions/list_clusters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bibigrid/core/actions/list_clusters.py b/bibigrid/core/actions/list_clusters.py index 1965dc39..99e1b6bc 100644 --- a/bibigrid/core/actions/list_clusters.py +++ b/bibigrid/core/actions/list_clusters.py @@ -8,7 +8,7 @@ from bibigrid.core.actions import create -SERVER_REGEX = re.compile(r"^bibigrid-((master)-([a-zA-Z0-9]+)|(worker|vpngtw)\d+-([a-zA-Z0-9]+)-\d+)$") +SERVER_REGEX = re.compile(r"^bibigrid-((master)-([a-zA-Z0-9]+)|(worker|vpngtw)-([a-zA-Z0-9]+)-\d+)$") def dict_clusters(providers, log): @@ -24,6 +24,8 @@ def dict_clusters(providers, log): servers = provider.list_servers() for server in servers: result = SERVER_REGEX.match(server["name"]) + print(server["name"]) + print(result) if result: identifier = result.group(4) or result.group(2) cluster_id = result.group(5) or result.group(3) From 83d4a2a4321e4d43f007b5f29b6c1d17ea9f64cf Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 16:03:30 +0200 Subject: [PATCH 016/145] test list clusters updated --- tests/test_list_clusters.py | 51 ++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/tests/test_list_clusters.py b/tests/test_list_clusters.py index d6777146..b0a1ada4 100644 --- a/tests/test_list_clusters.py +++ b/tests/test_list_clusters.py @@ -13,36 +13,45 @@ class TestList(TestCase): """ Class test list """ + def test_setup(self): for identifier in [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, create.MASTER_IDENTIFIER]: - cluster_id = 42 + cluster_id = "42" provider = Mock() provider.name = "name" - provider.cloud_specification = {"identifier": 21} + provider.cloud_specification = {"identifier": "21"} cluster_dict = {} - server = {"name": identifier(cluster_id=str(cluster_id))} - self.assertEqual(str(cluster_id), list_clusters.setup(server, identifier, cluster_dict, provider)) - self.assertEqual({str(cluster_id): {'worker': [], 'vpngtw': []}}, cluster_dict) - self.assertEqual(provider, server["provider"]) + server = {"name": identifier(cluster_id=cluster_id)} + list_clusters.setup(cluster_dict, str(cluster_id), server, provider) + + self.assertEqual({cluster_id: {'workers': [], 'vpngtws': []}}, cluster_dict) + self.assertEqual(provider.NAME, server["provider"]) + self.assertEqual("21", server["cloud_specification"]) def test_setup_already(self): for identifier in [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, create.MASTER_IDENTIFIER]: - cluster_id = 42 - test_provider = Mock() - test_provider.name = "name" - cluster_dict = {str(cluster_id): {'worker': ["some"], 'vpngtw': ["some"]}} - server = {"name": identifier(cluster_id=str(cluster_id))} - self.assertEqual(str(cluster_id), list_clusters.setup(server, identifier, cluster_dict, test_provider)) - self.assertEqual({str(cluster_id): {'worker': ["some"], 'vpngtw': ["some"]}}, cluster_dict) - self.assertEqual(test_provider, server["provider"]) + cluster_id = "42" + provider = Mock() + provider.name = "name" + provider.cloud_specification = {"identifier": "21"} + cluster_dict = {cluster_id: {'workers': ["some"], 'vpngtws': ["some"]}} + server = {"name": identifier(cluster_id=cluster_id)} + list_clusters.setup(cluster_dict, cluster_id, server, provider) + + self.assertEqual({cluster_id: {'workers': ["some"], 'vpngtws': ["some"]}}, cluster_dict) + self.assertEqual(provider.NAME, server["provider"]) + self.assertEqual("21", server["cloud_specification"]) def test_dict_clusters(self): - cluster_id = 42 - expected = {str(cluster_id): {'workers': [{'name': f'bibigrid-worker-{str(cluster_id)}', 'provider': 'Mock'}], - 'vpngtws': [{'name': f'bibigrid-vpngtw-{str(cluster_id)}', 'provider': 'Mock'}], - 'master': {'name': f'bibigrid-master-{str(cluster_id)}', 'provider': 'Mock'}}} + cluster_id = "42" + expected = {cluster_id: { + 'workers': [{'cloud_specification': '21', 'name': f'bibigrid-worker-{cluster_id}-0', 'provider': 'Mock'}], + 'vpngtws': [{'cloud_specification': '21', 'name': f'bibigrid-vpngtw-{cluster_id}-0', 'provider': 'Mock'}], + 'master': {'cloud_specification': '21', 'name': f'bibigrid-master-{cluster_id}', 'provider': 'Mock'}}} provider = Mock() - provider.list_servers.return_value = [{'name': identifier(cluster_id=str(cluster_id))} for identifier in - [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, - create.MASTER_IDENTIFIER]] + provider.NAME = "Mock" + provider.cloud_specification = {"identifier": "21"} + provider.list_servers.return_value = [{'name': identifier(cluster_id=cluster_id) + "-0"} for identifier in + [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER]] + [ + {'name': create.MASTER_IDENTIFIER(cluster_id=cluster_id)}] self.assertEqual(expected, list_clusters.dict_clusters([provider], startup.LOG)) From d54ab93ab5d41717856e9e52d4647ca0bcdee229 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 16:04:35 +0200 Subject: [PATCH 017/145] fixed too open cluster_id regex --- bibigrid/core/actions/terminate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bibigrid/core/actions/terminate.py b/bibigrid/core/actions/terminate.py index 7a3fadb1..15a5a859 100644 --- a/bibigrid/core/actions/terminate.py +++ b/bibigrid/core/actions/terminate.py @@ -61,7 +61,7 @@ def terminate_servers(server_list, cluster_id, provider, log): """ log.info("Deleting servers on provider %s...", provider.cloud_specification['identifier']) cluster_server_state = [] - server_regex = re.compile(fr"^bibigrid-(master-{cluster_id}+|(worker|vpngtw)-{cluster_id}+-\d+)$") + server_regex = re.compile(fr"^bibigrid-(master-{cluster_id}|(worker|vpngtw)-{cluster_id}-\d+)$") for server in server_list: if server_regex.match(server["name"]): log.info("Trying to terminate Server %s on cloud %s.", server['name'], From b78f63d2c05beeebd69840501081310244fc1bed Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 16:22:53 +0200 Subject: [PATCH 018/145] added missing "to" --- tests/test_list_clusters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_list_clusters.py b/tests/test_list_clusters.py index b0a1ada4..d7932d74 100644 --- a/tests/test_list_clusters.py +++ b/tests/test_list_clusters.py @@ -1,5 +1,5 @@ """ -Module test list +Module to test list """ from unittest import TestCase from unittest.mock import Mock @@ -11,7 +11,7 @@ class TestList(TestCase): """ - Class test list + Class to test list """ def test_setup(self): From 6ed924bdba3f91862a5daf993abc48630113fd41 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 16:23:29 +0200 Subject: [PATCH 019/145] fixed id_generation tests --- ...{test_idGeneration.py => test_id_generation.py} | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) rename tests/{test_idGeneration.py => test_id_generation.py} (82%) diff --git a/tests/test_idGeneration.py b/tests/test_id_generation.py similarity index 82% rename from tests/test_idGeneration.py rename to tests/test_id_generation.py index 6e694530..0e7c4009 100644 --- a/tests/test_idGeneration.py +++ b/tests/test_id_generation.py @@ -1,3 +1,6 @@ +""" +Module to test id_generation +""" from unittest import TestCase from unittest.mock import Mock, MagicMock, patch @@ -5,7 +8,10 @@ from bibigrid.core.utility import id_generation -class Test(TestCase): +class TestIDGeneration(TestCase): + """ + Class to test id_generation + """ def test_generate_cluster_id(self): """ @@ -26,10 +32,10 @@ def test_generate_safe_cluster_id(self, mock_generate_cluster_id): mock_is_unique.assert_called_with(21, [42]) def test_is_unique_cluster_id_duplicate(self): - cluster_id = 42 + cluster_id = "42" provider = Mock() provider.list_servers = MagicMock( - return_value=[{"name": create.MASTER_IDENTIFIER + create.SEPARATOR + str(cluster_id)}]) + return_value=[{"name": create.MASTER_IDENTIFIER(cluster_id=cluster_id)}]) self.assertFalse(id_generation.is_unique_cluster_id(str(cluster_id), [provider])) provider.list_servers.assert_called() @@ -37,6 +43,6 @@ def test_is_unique_cluster_id_unique(self): cluster_id = 42 provider = Mock() provider.list_servers = MagicMock( - return_value=[{"name": create.MASTER_IDENTIFIER + create.SEPARATOR + str(cluster_id + 1)}]) + return_value=[{"name": create.MASTER_IDENTIFIER(cluster_id=str(cluster_id + 1))}]) self.assertTrue(id_generation.is_unique_cluster_id(str(cluster_id), [provider])) provider.list_servers.assert_called() From f844f6862293339cf4068e4a16f63e4bfde141d7 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 17:31:24 +0200 Subject: [PATCH 020/145] renamed configuration handler to please linter --- tests/test_configurationHandler.py | 197 -------------------------- tests/test_configuration_handler.py | 208 ++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 197 deletions(-) delete mode 100644 tests/test_configurationHandler.py create mode 100644 tests/test_configuration_handler.py diff --git a/tests/test_configurationHandler.py b/tests/test_configurationHandler.py deleted file mode 100644 index f62161af..00000000 --- a/tests/test_configurationHandler.py +++ /dev/null @@ -1,197 +0,0 @@ -import os -from unittest import TestCase -from unittest.mock import patch, mock_open, MagicMock - -import bibigrid.core.utility.handler.configuration_handler as configurationHandler - - -class TestConfigurationHandler(TestCase): - # pylint: disable=R0904 - def test_get_list_by_name_none(self): - configurations = [{}, {}] - self.assertEqual([None, None], configurationHandler.get_list_by_key(configurations, "key1")) - self.assertEqual([], configurationHandler.get_list_by_key(configurations, "key1", False)) - - def test_get_list_by_name_empty(self): - configurations = [{"key1": "value1", "key2": "value1"}, {"key1": "value2"}] - self.assertEqual(["value1", "value2"], configurationHandler.get_list_by_key(configurations, "key1")) - self.assertEqual(["value1", "value2"], configurationHandler.get_list_by_key(configurations, "key1", False)) - self.assertEqual(["value1", None], configurationHandler.get_list_by_key(configurations, "key2")) - self.assertEqual(["value1"], configurationHandler.get_list_by_key(configurations, "key2", False)) - - @patch("os.path.isfile") - def test_read_configuration_no_file(self, mock_isfile): - mock_isfile.return_value = False - test = MagicMock() - configuration = "Test: 42" - expected_result = None - with patch("builtins.open", mock_open(test, read_data=configuration)): - result = configurationHandler.read_configuration("path") - mock_isfile.assert_called_with("path") - test.assert_not_called() - self.assertEqual(expected_result, result) - - @patch("os.path.isfile") - def test_read_configuration_file(self, mock_isfile): - mock_isfile.return_value = True - opener = MagicMock() - configuration = "Test: 42" - expected_result = {"Test": 42} - with patch("builtins.open", mock_open(opener, read_data=configuration)): - result = configurationHandler.read_configuration("path") - mock_isfile.assert_called_with("path") - opener.assert_called_with("path", "r") - self.assertEqual(expected_result, result) - - @patch("os.path.isfile") - def test_read_configuration_file_yaml_exception(self, mock_isfile): - mock_isfile.return_value = True - opener = MagicMock() - configuration = "]unbalanced brackets[" - expected_result = None - with patch("builtins.open", mock_open(opener, read_data=configuration)): - result = configurationHandler.read_configuration("path") - mock_isfile.assert_called_with("path") - opener.assert_called_with("path", "r") - self.assertEqual(expected_result, result) - - def test_find_file_in_folders_not_found_no_folder(self): - expected_result = None - result = configurationHandler.find_file_in_folders("true_file", []) - self.assertEqual(expected_result, result) - - def test_find_file_in_folders_not_found_no_file(self): - expected_result = None - with patch("os.path.isfile") as mock_isfile: - mock_isfile.return_value = False - result = configurationHandler.find_file_in_folders("false_file", ["or_false_folder"]) - self.assertEqual(expected_result, result) - mock_isfile.called_with(os.path.expanduser(os.path.join("or_false_folder", "false_file"))) - - @patch("os.path.isfile") - @patch("bibigrid.core.utility.handler.configurationHandler.read_configuration") - def test_find_file_in_folders(self, mock_read_configuration, mock_isfile): - expected_result = 42 - mock_isfile.return_value(True) - mock_read_configuration.return_value = 42 - result = configurationHandler.find_file_in_folders("true_file", ["true_folder"]) - self.assertEqual(expected_result, result) - mock_read_configuration.assert_called_with(os.path.expanduser(os.path.join("true_folder", "true_file"))) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files_none(self, mock_ffif): - mock_ffif.return_value = None - expected_result = None, None - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files_no_clouds_yaml(self, mock_ffif): - mock_ffif.side_effect = [None, {configurationHandler.CLOUD_PUBLIC_ROOT_KEY: 42}] - expected_result = None, 42 - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files_no_public_clouds_yaml(self, mock_ffif): - mock_ffif.side_effect = [{configurationHandler.CLOUD_ROOT_KEY: 42}, None] - expected_result = 42, None - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files_no_root_key_public(self, mock_ffif): - mock_ffif.side_effect = [{configurationHandler.CLOUD_ROOT_KEY: 42}, {"name": 42}] - expected_result = 42, None - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files_no_root_key_cloud(self, mock_ffif): - mock_ffif.side_effect = [{"name": 42}, {configurationHandler.CLOUD_PUBLIC_ROOT_KEY: 42}] - expected_result = None, 42 - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files(self, mock_ffif): - mock_ffif.side_effect = [{configurationHandler.CLOUD_ROOT_KEY: 22}, - {configurationHandler.CLOUD_PUBLIC_ROOT_KEY: 42}] - expected_result = 22, 42 - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - mock_ffif.assert_called_with(configurationHandler.CLOUDS_PUBLIC_YAML, configurationHandler.CLOUDS_YAML_PATHS) - - @patch("bibigrid.core.utility.handler.configurationHandler.get_cloud_specification") - @patch("bibigrid.core.utility.handler.configurationHandler.get_clouds_files") - def test_get_cloud_specifications_none(self, mock_get_clouds_files, mock_get_clouds_specification): - mock_get_clouds_files.return_value = None, None - expected_result = [] - result = configurationHandler.get_cloud_specifications([{"cloud": 42}]) - self.assertEqual(expected_result, result) - mock_get_clouds_specification.assert_not_called() - mock_get_clouds_files.assert_called() - - @patch("bibigrid.core.utility.handler.configurationHandler.get_cloud_specification") - @patch("bibigrid.core.utility.handler.configurationHandler.get_clouds_files") - def test_get_cloud_specifications_no_cloud_configuration_key(self, mock_get_clouds_files, - mock_get_clouds_specification): - mock_get_clouds_files.return_value = {"Some"}, {"Some"} - expected_result = [] - result = configurationHandler.get_cloud_specifications([{"no_cloud": 42}]) - self.assertEqual(expected_result, result) - mock_get_clouds_specification.assert_not_called() - mock_get_clouds_files.assert_called() - - @patch("bibigrid.core.utility.handler.configurationHandler.get_cloud_specification") - @patch("bibigrid.core.utility.handler.configurationHandler.get_clouds_files") - def test_get_cloud_specifications_cloud(self, mock_get_clouds_files, mock_get_clouds_specification): - mock_get_clouds_files.return_value = {"1"}, {"2"} - mock_get_clouds_specification.return_value = 21 - expected_result = [21] - result = configurationHandler.get_cloud_specifications([{"cloud": 42}]) - self.assertEqual(expected_result, result) - mock_get_clouds_specification.assert_called_with(42, {"1"}, {"2"}) - mock_get_clouds_files.assert_called() - - @patch("bibigrid.core.utility.handler.configurationHandler.get_cloud_specification") - @patch("bibigrid.core.utility.handler.configurationHandler.get_clouds_files") - def test_get_cloud_specifications_no_config(self, mock_get_clouds_files, mock_get_clouds_specification): - mock_get_clouds_files.return_value = {"1"}, {"2"} - mock_get_clouds_specification.return_value = 21 - expected_result = [] - result = configurationHandler.get_cloud_specifications([]) - self.assertEqual(expected_result, result) - mock_get_clouds_specification.assert_not_called() - mock_get_clouds_files.assert_called() - - def test_get_cloud_specification_no_matching_cloud(self): - expected_result = {} - result = configurationHandler.get_cloud_specification("some_name", {}, {"some_some": "public"}) - self.assertEqual(expected_result, result) - - def test_get_cloud_specification_cloud(self): - expected_result = {42: 42} - result = configurationHandler.get_cloud_specification("some_name", {"some_name": {42: 42}}, None) - self.assertEqual(expected_result, result) - - def test_get_cloud_specification_no_public_cloud(self): - expected_result = {42: 42, "profile": "name2"} - result = configurationHandler.get_cloud_specification("some_name", {"some_name": expected_result}, - {"not_name2": {21: 21}}) - self.assertEqual(expected_result, result) - - def test_get_cloud_specification(self): - cloud_private_specification = {42: 42, "profile": "name2", "test": {"recursive": "foo"}} - expected_result = {42: 42, "profile": "name2", "test": {"recursive": "foo"}, "additional": "value"} - result = configurationHandler.get_cloud_specification("some_name", {"some_name": cloud_private_specification}, - {"name2": {42: 21, "test": {"recursive": "oof"}, - "additional": "value"}}) - self.assertEqual(expected_result, result) - - def test_get_cloud_specification_type_exception(self): - cloud_private_specification = {42: 42, "profile": "name2", "test": {"recursive": "foo"}} - result = configurationHandler.get_cloud_specification("some_name", {"some_name": cloud_private_specification}, - {"name2": {42: 21, "test": ["recursive", 22], - "additional": "value"}}) - self.assertEqual({}, result) diff --git a/tests/test_configuration_handler.py b/tests/test_configuration_handler.py new file mode 100644 index 00000000..86564f84 --- /dev/null +++ b/tests/test_configuration_handler.py @@ -0,0 +1,208 @@ +""" +Module to test configuration_handler +""" + +import os +from unittest import TestCase +from unittest.mock import patch, mock_open, MagicMock + +from bibigrid.core import startup +from bibigrid.core.utility.handler import configuration_handler + + +class TestConfigurationHandler(TestCase): + """ + Class to test configuration_handler + """ + # pylint: disable=R0904 + def test_get_list_by_name_none(self): + configurations = [{}, {}] + self.assertEqual([None, None], configuration_handler.get_list_by_key(configurations, "key1")) + self.assertEqual([], configuration_handler.get_list_by_key(configurations, "key1", False)) + + def test_get_list_by_name_empty(self): + configurations = [{"key1": "value1", "key2": "value1"}, {"key1": "value2"}] + self.assertEqual(["value1", "value2"], configuration_handler.get_list_by_key(configurations, "key1")) + self.assertEqual(["value1", "value2"], configuration_handler.get_list_by_key(configurations, "key1", False)) + self.assertEqual(["value1", None], configuration_handler.get_list_by_key(configurations, "key2")) + self.assertEqual(["value1"], configuration_handler.get_list_by_key(configurations, "key2", False)) + + @patch("os.path.isfile") + def test_read_configuration_no_file(self, mock_isfile): + mock_isfile.return_value = False + test_open = MagicMock() + configuration = "Test: 42" + expected_result = [None] + with patch("builtins.open", mock_open(test_open, read_data=configuration)): + result = configuration_handler.read_configuration(startup.LOG, "path") + mock_isfile.assert_called_with("path") + test_open.assert_not_called() + self.assertEqual(expected_result, result) + + @patch("os.path.isfile") + def test_read_configuration_file(self, mock_isfile): + mock_isfile.return_value = True + opener = MagicMock() + configuration = "Test: 42" + expected_result = [{"Test": 42}] + with patch("builtins.open", mock_open(opener, read_data=configuration)): + result = configuration_handler.read_configuration(startup.LOG, "path") + mock_isfile.assert_called_with("path") + opener.assert_called_with("path", mode="r", encoding="UTF-8") + self.assertEqual(expected_result, result) + + @patch("os.path.isfile") + def test_read_configuration_file_yaml_exception(self, mock_isfile): + mock_isfile.return_value = True + opener = MagicMock() + configuration = "]unbalanced brackets[" + expected_result = [None] + with patch("builtins.open", mock_open(opener, read_data=configuration)): + result = configuration_handler.read_configuration(startup.LOG, "path") + mock_isfile.assert_called_with("path") + opener.assert_called_with("path", mode="r", encoding="UTF-8") + self.assertEqual(expected_result, result) + + def test_find_file_in_folders_not_found_no_folder(self): + expected_result = None + result = configuration_handler.find_file_in_folders("true_file", [], startup.LOG) + self.assertEqual(expected_result, result) + + def test_find_file_in_folders_not_found_no_file(self): + expected_result = None + with patch("os.path.isfile") as mock_isfile: + mock_isfile.return_value = False + result = configuration_handler.find_file_in_folders("false_file", ["or_false_folder"], startup.LOG) + self.assertEqual(expected_result, result) + mock_isfile.called_with(os.path.expanduser(os.path.join("or_false_folder", "false_file"))) + + @patch("os.path.isfile") + @patch("bibigrid.core.utility.handler.configuration_handler.read_configuration") + def test_find_file_in_folders(self, mock_read_configuration, mock_isfile): + expected_result = 42 + mock_isfile.return_value(True) + mock_read_configuration.return_value = 42 + result = configuration_handler.find_file_in_folders("true_file", ["true_folder"], startup.LOG) + self.assertEqual(expected_result, result) + mock_read_configuration.assert_called_with(startup.LOG, + os.path.expanduser(os.path.join("true_folder", "true_file")), False) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files_none(self, mock_ffif): + mock_ffif.return_value = None + expected_result = None, None + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files_no_clouds_yaml(self, mock_ffif): + mock_ffif.side_effect = [None, {configuration_handler.CLOUD_PUBLIC_ROOT_KEY: 42}] + expected_result = None, 42 + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files_no_public_clouds_yaml(self, mock_ffif): + mock_ffif.side_effect = [{configuration_handler.CLOUD_ROOT_KEY: 42}, None] + expected_result = 42, None + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files_no_root_key_public(self, mock_ffif): + mock_ffif.side_effect = [{configuration_handler.CLOUD_ROOT_KEY: 42}, {"name": 42}] + expected_result = 42, None + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files_no_root_key_cloud(self, mock_ffif): + mock_ffif.side_effect = [{"name": 42}, {configuration_handler.CLOUD_PUBLIC_ROOT_KEY: 42}] + expected_result = None, 42 + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files(self, mock_ffif): + mock_ffif.side_effect = [{configuration_handler.CLOUD_ROOT_KEY: 22}, + {configuration_handler.CLOUD_PUBLIC_ROOT_KEY: 42}] + expected_result = 22, 42 + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + mock_ffif.assert_called_with(configuration_handler.CLOUDS_PUBLIC_YAML, configuration_handler.CLOUDS_YAML_PATHS, + startup.LOG) + + @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specification") + @patch("bibigrid.core.utility.handler.configuration_handler.get_clouds_files") + def test_get_cloud_specifications_none(self, mock_get_clouds_files, mock_get_clouds_specification): + mock_get_clouds_files.return_value = None, None + expected_result = [] + result = configuration_handler.get_cloud_specifications([{"cloud": 42}], startup.LOG) + self.assertEqual(expected_result, result) + mock_get_clouds_specification.assert_not_called() + mock_get_clouds_files.assert_called() + + @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specification") + @patch("bibigrid.core.utility.handler.configuration_handler.get_clouds_files") + def test_get_cloud_specifications_no_cloud_configuration_key(self, mock_get_clouds_files, + mock_get_clouds_specification): + mock_get_clouds_files.return_value = {"Some"}, {"Some"} + expected_result = [] + result = configuration_handler.get_cloud_specifications([{"no_cloud": 42}], startup.LOG) + self.assertEqual(expected_result, result) + mock_get_clouds_specification.assert_not_called() + mock_get_clouds_files.assert_called() + + @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specification") + @patch("bibigrid.core.utility.handler.configuration_handler.get_clouds_files") + def test_get_cloud_specifications_cloud(self, mock_get_clouds_files, mock_get_clouds_specification): + mock_get_clouds_files.return_value = {"1": "1"}, {"2": "2"} + mock_get_clouds_specification.return_value = 21 + expected_result = [21] + result = configuration_handler.get_cloud_specifications([{"cloud": 42}], startup.LOG) + self.assertEqual(expected_result, result) + mock_get_clouds_specification.assert_called_with(42, {"1": "1"}, {"2": "2"}, startup.LOG) + mock_get_clouds_files.assert_called() + + @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specification") + @patch("bibigrid.core.utility.handler.configuration_handler.get_clouds_files") + def test_get_cloud_specifications_no_config(self, mock_get_clouds_files, mock_get_clouds_specification): + mock_get_clouds_files.return_value = {"1": "1"}, {"2": "2"} + mock_get_clouds_specification.return_value = 21 + expected_result = [] + result = configuration_handler.get_cloud_specifications([], startup.LOG) + self.assertEqual(expected_result, result) + mock_get_clouds_specification.assert_not_called() + mock_get_clouds_files.assert_called() + + def test_get_cloud_specification_no_matching_cloud(self): + expected_result = {} + result = configuration_handler.get_cloud_specification("some_name", {}, {"some_some": "public"}, startup.LOG) + self.assertEqual(expected_result, result) + + def test_get_cloud_specification_cloud(self): + expected_result = {42: 42, "identifier": "some_name"} + result = configuration_handler.get_cloud_specification("some_name", {"some_name": {42: 42}}, None, startup.LOG) + self.assertEqual(expected_result, result) + + def test_get_cloud_specification_no_public_cloud(self): + expected_result = {42: 42, "profile": "name2", "identifier": "some_name"} + result = configuration_handler.get_cloud_specification("some_name", {"some_name": expected_result}, + {"not_name2": {21: 21}}, startup.LOG) + self.assertEqual(expected_result, result) + + def test_get_cloud_specification(self): + cloud_private_specification = {42: 42, "profile": "name2", "test": {"recursive": "oof"}} + expected_result = {42: 21, "profile": "name2", "test": {"recursive": "foo"}, "additional": "value", + "identifier": "some_name"} + result = configuration_handler.get_cloud_specification("some_name", {"some_name": cloud_private_specification}, + {"name2": {42: 21, "test": {"recursive": "foo"}, + "additional": "value"}}, startup.LOG) + self.assertEqual(expected_result, result) + + def test_get_cloud_specification_type_exception(self): + cloud_private_specification = {42: 42, "profile": "name2", "test": {"recursive": "foo"}} + result = configuration_handler.get_cloud_specification("some_name", {"some_name": cloud_private_specification}, + {"name2": {42: 21, "test": ["recursive", 22], + "additional": "value"}}, startup.LOG) + self.assertEqual({}, result) From 0c4a97fd8af58ffd4e70a3cfad9b94feb6ae499f Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 20 Oct 2023 17:31:43 +0200 Subject: [PATCH 021/145] removed unnecessary tests and updated remaining --- tests/test_check.py | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/tests/test_check.py b/tests/test_check.py index b3f04f31..fcaa5e11 100644 --- a/tests/test_check.py +++ b/tests/test_check.py @@ -1,34 +1,20 @@ +""" +Module to test check +""" from unittest import TestCase from unittest.mock import patch +from bibigrid.core import startup from bibigrid.core.actions import check -from bibigrid.core.utility import validate_configuration class TestCheck(TestCase): - - @patch("logging.info") - def test_check_true(self, mock_log): - providers = [42] - configurations = [32] - with patch.object(validate_configuration.ValidateConfiguration, "validate", return_value=True) as mock_validate: - self.assertFalse(check.check(configurations, providers)) - mock_validate.assert_called() - mock_log.assert_called_with("Total check returned True.") - - @patch("logging.info") - def test_check_false(self, mock_log): - providers = [42] - configurations = [32] - with patch.object(validate_configuration.ValidateConfiguration, "validate", - return_value=False) as mock_validate: - self.assertFalse(check.check(configurations, providers)) - mock_validate.assert_called() - mock_log.assert_called_with("Total check returned False.") - + """ + Class to test check + """ @patch("bibigrid.core.utility.validate_configuration.ValidateConfiguration") - def test_check_init(self, mock_validator): + def test_check_true(self, mock_validator): providers = [42] configurations = [32] - self.assertFalse(check.check(configurations, providers)) - mock_validator.assert_called_with(configurations, providers) + self.assertFalse(check.check(configurations, providers, startup.LOG)) + mock_validator.assert_called_once_with(configurations, providers, startup.LOG) From ac71d4b8af973bcf01a6c1d38c259bb3d18ac3c3 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 24 Oct 2023 11:54:36 +0200 Subject: [PATCH 022/145] fixed remaining "subnet list gets handled as a single subnet" bug and finalized multiple routes handling. --- bibigrid/core/utility/ansible_configurator.py | 6 +++--- .../templates/networking/bibigrid_ens3.network.j2 | 6 ++++-- .../roles/bibigrid/templates/wireguard/device.j2 | 2 +- .../roles/bibigrid/templates/wireguard/network.j2 | 11 ++++++++--- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index cc8f5040..a603e1ce 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -112,7 +112,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py flavor_dict = {key: flavor[key] for key in flavor_keys} regexp = create.WORKER_IDENTIFIER(cluster_id=cluster_id, additional=r"\d+") vpngtw_dict = {"name": name, "regexp": regexp, "image": vpngtw["image"], - "network": configuration["network"], "network_cidr": configuration["subnet_cidrs"], + "network": configuration["network"], "network_cidrs": configuration["subnet_cidrs"], "floating_ip": configuration["floating_ip"], "private_v4": configuration["private_v4"], "flavor": flavor_dict, "wireguard_ip": wireguard_ip, "cloud_identifier": configuration["cloud_identifier"], @@ -126,7 +126,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py flavor = provider.get_flavor(master["type"]) flavor_dict = {key: flavor[key] for key in flavor_keys} master_dict = {"name": name, "image": master["image"], "network": configuration["network"], - "network_cidr": configuration["subnet_cidrs"], "floating_ip": configuration["floating_ip"], + "network_cidrs": configuration["subnet_cidrs"], "floating_ip": configuration["floating_ip"], "flavor": flavor_dict, "private_v4": configuration["private_v4"], "cloud_identifier": configuration["cloud_identifier"], "volumes": configuration["volumes"], @@ -358,7 +358,7 @@ def add_wireguard_peers(configurations): private_key, public_key = wireguard_keys.generate() configuration["wireguard_peer"] = {"name": configuration["cloud_identifier"], "private_key": private_key, "public_key": public_key, "ip": configuration["floating_ip"], - "subnet": configuration["subnet_cidrs"]} + "subnets": configuration["subnet_cidrs"]} def configure_ansible_yaml(providers, configurations, cluster_id, log): diff --git a/resources/playbook/roles/bibigrid/templates/networking/bibigrid_ens3.network.j2 b/resources/playbook/roles/bibigrid/templates/networking/bibigrid_ens3.network.j2 index f43e0e1c..2aa8e6ec 100644 --- a/resources/playbook/roles/bibigrid/templates/networking/bibigrid_ens3.network.j2 +++ b/resources/playbook/roles/bibigrid/templates/networking/bibigrid_ens3.network.j2 @@ -16,11 +16,13 @@ Metric=5 GatewayOnLink=True {% for peer in wireguard_common.peers %} -{% if peer.subnet != (ansible_default_ipv4.network + '/' + ansible_default_ipv4.netmask) | ipaddr('network/prefix') %} +{% for subnet in peer.subnets %} +{% if subnet != (ansible_default_ipv4.network + '/' + ansible_default_ipv4.netmask) | ipaddr('network/prefix') %} [Route] -Destination={{ peer.subnet }} +Destination={{ subnet }} Gateway={{ gateway_ip }} Metric=5 GatewayOnLink=True {% endif %} +{% endfor %} {% endfor %} \ No newline at end of file diff --git a/resources/playbook/roles/bibigrid/templates/wireguard/device.j2 b/resources/playbook/roles/bibigrid/templates/wireguard/device.j2 index a3766a07..66c75afa 100644 --- a/resources/playbook/roles/bibigrid/templates/wireguard/device.j2 +++ b/resources/playbook/roles/bibigrid/templates/wireguard/device.j2 @@ -16,7 +16,7 @@ ListenPort = {{ wireguard_common.listen_port|default(51820) }} # {{ peer.name }} [WireGuardPeer] PublicKey = {{ peer.public_key }} -AllowedIPs = 10.0.0.0/{{ wireguard_common.mask_bits|default(24) }}, {{peer.subnet|join(', ')}} +AllowedIPs = 10.0.0.0/{{ wireguard_common.mask_bits|default(24) }}, {{peer.subnets|join(', ')}} Endpoint = {{ peer.ip }}:{{ wireguard_common.listen_port|default(51820) }} {% endif %} {% endfor %} diff --git a/resources/playbook/roles/bibigrid/templates/wireguard/network.j2 b/resources/playbook/roles/bibigrid/templates/wireguard/network.j2 index 300d21af..f4bd2ffd 100644 --- a/resources/playbook/roles/bibigrid/templates/wireguard/network.j2 +++ b/resources/playbook/roles/bibigrid/templates/wireguard/network.j2 @@ -5,12 +5,17 @@ Name=wg0 Address={{ wireguard.ip }}/{{ wireguard_common.mask_bits|default(24) }} {% for vpngtw in groups["vpngtw"] %} -[Route] {% if inventory_hostname in groups['master']%} +{% for network_cidr in hostvars[vpngtw].network_cidrs %} +[Route] Gateway={{ wireguard.ip }} -Destination={{ hostvars[vpngtw].network_cidr[0] }} +Destination={{ network_cidr }} +{% endfor %} {% else %} +{% for network_cidr in hostvars[groups.master.0].network_cidrs %} +[Route] Gateway={{ hostvars[vpngtw].wireguard.ip }} -Destination={{ hostvars[groups.master.0].network_cidr[0] }} +Destination={{ network_cidr }} +{% endfor %} {% endif %} {% endfor %} \ No newline at end of file From fbabea243ccdd165abfde022efeb5c33768ddcfc Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 26 Oct 2023 12:48:19 +0200 Subject: [PATCH 023/145] updated tests not finished yet --- tests/test_ansible_configurator.py | 274 +++++++++++++++++------------ 1 file changed, 165 insertions(+), 109 deletions(-) diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index db22411d..c3ae1851 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -7,129 +7,191 @@ import bibigrid.core.utility.ansible_configurator as ansibleConfigurator import bibigrid.core.utility.paths.ansible_resources_path as aRP import bibigrid.core.utility.yaml_dumper as yamlDumper +from bibigrid.core import startup + class TestAnsibleConfigurator(TestCase): """ Test ansible configurator test class """ + # pylint: disable=R0904 def test_generate_site_file_yaml_empty(self): - site_yaml = [{'hosts': 'master', "become": "yes", - "vars_files": ansibleConfigurator.VARS_FILES, "roles": ["common", "master"]}, - {"hosts": "worker", "become": "yes", "vars_files": - ansibleConfigurator.VARS_FILES, "roles": ["common", "worker"]}, - {"hosts": "vpngtw", "become": "yes", "vars_files": - ansibleConfigurator.VARS_FILES, "roles": ["common", "vpngtw"]}] + site_yaml = [{'become': 'yes', 'hosts': 'master', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + {'become': 'yes', 'hosts': 'vpngtw', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + {'become': 'yes', 'hosts': 'workers', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] self.assertEqual(site_yaml, ansibleConfigurator.generate_site_file_yaml([])) def test_generate_site_file_yaml_role(self): custom_roles = [{"file": "file", "hosts": "hosts", "name": "name", "vars": "vars", "vars_file": "varsFile"}] - vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] - site_yaml = [{'hosts': 'master', "become": "yes", - "vars_files": vars_files, "roles": ["common", "master", "additional/name"]}, - {"hosts": "worker", "become": "yes", "vars_files": - vars_files, "roles": ["common", "worker", "additional/name"]}, - {"hosts": "vpngtw", "become": "yes", "vars_files": - vars_files, "roles": ["common", "vpngtw", "additional/name"]}] + # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] + site_yaml = [{'become': 'yes', 'hosts': 'master', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}, 'additional/name'], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}, + {'become': 'yes', 'hosts': 'vpngtw', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}, 'additional/name'], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}, + {'become': 'yes', 'hosts': 'workers', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}, 'additional/name'], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}] self.assertEqual(site_yaml, ansibleConfigurator.generate_site_file_yaml(custom_roles)) - def test_generate_instances(self): - cluster_dict = object() - self.assertEqual(cluster_dict, ansibleConfigurator.generate_instances_yaml(cluster_dict)) - def test_generate_common_configuration_false(self): - cidrs = 42 - configuration = {} - common_configuration_yaml = {"cluster_cidrs": cidrs, - "local_fs": False, - "local_dns_lookup": False, - "use_master_as_compute": True, - "enable_slurm": False, - "enable_zabbix": False, - "enable_nfs": False, - "enable_ide": False - } - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + configuration = [{}] + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, + 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, + 'local_dns_lookup': False, 'local_fs': False, 'slurm': True, + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, + 'use_master_as_compute': True} + generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + # munge key is randomly generated + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_true(self): - cidrs = 42 - configuration = {elem: "true" for elem in ["localFS", "localDNSlookup", "useMasterAsCompute", "slurm", - "zabbix", "ide"]} - common_configuration_yaml = {elem: "true" for elem in ["local_fs", "local_dns_lookup", "use_master_as_compute", - "enable_slurm", "enable_zabbix", "enable_ide"]} - common_configuration_yaml["cluster_cidrs"] = cidrs - common_configuration_yaml["enable_nfs"] = False - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + configuration = [ + {elem: "True" for elem in ["localFS", "localDNSlookup", "useMasterAsCompute", "slurm", "zabbix", "ide"]}] + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'True', + 'enable_nfs': False, 'enable_slurm': 'True', 'enable_zabbix': 'True', + 'ide_conf': {'build': False, 'ide': False, 'port_end': 8383, 'port_start': 8181, + 'workspace': '${HOME}'}, 'local_dns_lookup': 'True', + 'local_fs': 'True', 'slurm': 'True', + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, + 'use_master_as_compute': 'True', + 'zabbix_conf': {'admin_password': 'bibigrid', 'db': 'zabbix', + 'db_password': 'zabbix', 'db_user': 'zabbix', + 'server_name': 'bibigrid', 'timezone': 'Europe/Berlin'}} + generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_nfs_shares(self): - cidrs = 42 - configuration = {"nfs": "True", "nfsShares": ["/vil/mil"]} - common_configuration_yaml = {'cluster_cidrs': 42, - 'enable_ide': False, - 'enable_nfs': 'True', - 'enable_slurm': False, - 'enable_zabbix': False, - 'ext_nfs_mounts': [], - 'local_dns_lookup': False, - 'local_fs': False, - 'nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}, - {'dst': '/vol/spool', 'src': '/vol/spool'}], + configuration = [{"nfs": "True", "nfsShares": ["/vil/mil"]}] + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, + 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, + 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, + 'nfs_mounts': [{'dst': '//vil/mil', 'src': '//vil/mil'}, + {'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_nfs(self): - cidrs = 42 - configuration = {"nfs": "True"} - common_configuration_yaml = {'cluster_cidrs': 42, - 'enable_ide': False, - 'enable_nfs': 'True', - 'enable_slurm': False, - 'enable_zabbix': False, - 'ext_nfs_mounts': [], - 'local_dns_lookup': False, - 'local_fs': False, - 'nfs_mounts': [{'dst': '/vol/spool', 'src': '/vol/spool'}], + configuration = [{"nfs": "True"}] + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, + 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, + 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, + 'nfs_mounts': [{'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_ext_nfs_shares(self): - cidrs = 42 - configuration = {"nfs": "True", "extNfsShares": ["/vil/mil"]} - common_configuration_yaml = {'cluster_cidrs': 42, - 'enable_ide': False, - 'enable_nfs': 'True', - 'enable_slurm': False, - 'enable_zabbix': False, + configuration = [{"nfs": "True", "extNfsShares": ["/vil/mil"]}] + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, + 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}], - 'local_dns_lookup': False, - 'local_fs': False, - 'nfs_mounts': [{'dst': '/vol/spool', 'src': '/vol/spool'}], - 'use_master_as_compute': True} - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + 'local_dns_lookup': False, 'local_fs': False, + 'nfs_mounts': [{'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'YryJVnqgg24Ksf8zXQtbct3nuXrMSi9N'}, + 'ssh_user': ssh_user, 'use_master_as_compute': True} + generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_ide(self): - cidrs = 42 - configuration = {"ide": "Some1", "ideConf": "Some2"} - common_configuration_yaml = {'cluster_cidrs': 42, - 'enable_ide': "Some1", - 'enable_nfs': False, - 'enable_slurm': False, + configuration = [{"ide": "Some1", "ideConf": {"key1": "Some2"}}] + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], + 'enable_ide': 'Some1', 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, - 'ide_conf': 'Some2', - 'local_dns_lookup': False, - 'local_fs': False, - 'use_master_as_compute': True} - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + 'ide_conf': {'build': False, 'ide': False, 'key1': 'Some2', 'port_end': 8383, + 'port_start': 8181, 'workspace': '${HOME}'}, + 'local_dns_lookup': False, 'local_fs': False, 'slurm': True, + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'b7nks3Ur3kanyPAEBxfSC9ypfSHFnWJL'}, + 'ssh_user': ssh_user, 'use_master_as_compute': True} + generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) @patch("bibigrid.core.utility.ansibleConfigurator.get_ansible_roles") - def test_generate_common_configuration_ansible_roles_mock(self, mock_ansible_roles): - cidrs = 42 + def test_generate_common_configuration_ansible_roles_mock(self, mock_ansible_roles): # TODO + cidrs = "42" ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] mock_ansible_roles.return_value = 21 configuration = {"ansibleRoles": ansible_roles} @@ -139,13 +201,12 @@ def test_generate_common_configuration_ansible_roles_mock(self, mock_ansible_rol @patch("bibigrid.core.utility.ansibleConfigurator.get_ansible_galaxy_roles") def test_generate_common_configuration_ansible_galaxy_roles(self, mock_galaxy_roles): - cidrs = 42 + cidrs = "42" galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file"]}] configuration = {"ansibleGalaxyRoles": galaxy_roles} mock_galaxy_roles.return_value = 21 - self.assertEqual(21, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)[ - "ansible_galaxy_roles"]) + self.assertEqual(21, ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)[ + "ansible_galaxy_roles"]) mock_galaxy_roles.assert_called_with(galaxy_roles) @patch("bibigrid.core.utility.ansibleConfigurator.to_instance_host_dict") @@ -163,16 +224,14 @@ def test_to_instance_host_local(self): ip = 42 ssh_user = 21 local = {"ip": ip, "ansible_connection": "local", - "ansible_python_interpreter": ansibleConfigurator.PYTHON_INTERPRETER, - "ansible_user": ssh_user} + "ansible_python_interpreter": ansibleConfigurator.PYTHON_INTERPRETER, "ansible_user": ssh_user} self.assertEqual(local, ansibleConfigurator.to_instance_host_dict(21, 42, True)) def test_to_instance_host_ssh(self): ip = 42 ssh_user = 21 ssh = {"ip": ip, "ansible_connection": "ssh", - "ansible_python_interpreter": ansibleConfigurator.PYTHON_INTERPRETER, - "ansible_user": ssh_user} + "ansible_python_interpreter": ansibleConfigurator.PYTHON_INTERPRETER, "ansible_user": ssh_user} self.assertEqual(ssh, ansibleConfigurator.to_instance_host_dict(21, 42, False)) def test_get_cidrs_single(self): @@ -245,9 +304,7 @@ def test_get_ansible_galaxy_roles_mismatch(self, mock_log): mock_log.assert_called() def test_generate_login_file(self): - login_yaml = {"default_user": 99, - "ssh_user": 21, - "munge_key": 32} + login_yaml = {"default_user": 99, "ssh_user": 21, "munge_key": 32} self.assertEqual(login_yaml, ansibleConfigurator.generate_login_file_yaml(21, 32, 99)) def test_generate_worker_specification_file_yaml(self): @@ -285,14 +342,14 @@ def test_write_yaml_alias(self, mock_yaml): @patch("bibigrid.core.utility.ansibleConfigurator.generate_site_file_yaml") @patch("bibigrid.core.utility.ansibleConfigurator.write_yaml") @patch("bibigrid.core.utility.ansibleConfigurator.get_cidrs") - def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_roles, mock_hosts, - mock_instances, mock_list, mock_common, mock_login, mock_worker, mock_munge): + def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_roles, mock_hosts, mock_instances, + mock_list, mock_common, mock_login, mock_worker, mock_munge): mock_munge.return_value = 420 mock_cidrs.return_value = 421 mock_list.return_value = {2: 422} mock_roles.return_value = 423 provider = MagicMock() - provider.cloud_specification = {"auth": {"username":"Tom"}} + provider.cloud_specification = {"auth": {"username": "Tom"}} ansibleConfigurator.configure_ansible_yaml([provider], [{"sshUser": 42, "ansibleRoles": 21}], 2) mock_munge.assert_called() mock_worker.assert_called_with([{"sshUser": 42, "ansibleRoles": 21}]) @@ -308,6 +365,5 @@ def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_rol call(aRP.COMMONS_LOGIN_FILE, mock_login(), False), call(aRP.COMMONS_CONFIG_FILE, mock_common(), False), call(aRP.COMMONS_INSTANCES_FILE, mock_instances(), False), - call(aRP.HOSTS_CONFIG_FILE, mock_hosts(), False), - call(aRP.SITE_CONFIG_FILE, mock_site(), False)] + call(aRP.HOSTS_CONFIG_FILE, mock_hosts(), False), call(aRP.SITE_CONFIG_FILE, mock_site(), False)] self.assertEqual(expected, mock_yaml.call_args_list) From 774520202d8cac50dbb2a16704238fdfd2495560 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 27 Oct 2023 12:19:12 +0200 Subject: [PATCH 024/145] improved code style --- bibigrid/core/utility/ansible_configurator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index cc8f5040..0a5d5630 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -128,8 +128,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py master_dict = {"name": name, "image": master["image"], "network": configuration["network"], "network_cidr": configuration["subnet_cidrs"], "floating_ip": configuration["floating_ip"], "flavor": flavor_dict, "private_v4": configuration["private_v4"], - "cloud_identifier": configuration["cloud_identifier"], - "volumes": configuration["volumes"], + "cloud_identifier": configuration["cloud_identifier"], "volumes": configuration["volumes"], "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False)} if configuration.get("wireguard_peer"): master_dict["wireguard"] = {"ip": "10.0.0.1", "peer": configuration.get("wireguard_peer")} @@ -165,8 +164,8 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us master_configuration = configurations[0] log.info("Generating common configuration file...") # print(configuration.get("slurmConf", {})) - common_configuration_yaml = {"auto_mount": master_configuration.get("autoMount", False), - "cluster_id": cluster_id, "cluster_cidrs": cidrs, "default_user": default_user, + common_configuration_yaml = {"auto_mount": master_configuration.get("autoMount", False), "cluster_id": cluster_id, + "cluster_cidrs": cidrs, "default_user": default_user, "local_fs": master_configuration.get("localFS", False), "local_dns_lookup": master_configuration.get("localDNSlookup", False), "use_master_as_compute": master_configuration.get("useMasterAsCompute", True), @@ -266,8 +265,8 @@ def get_cidrs(configurations): """ all_cidrs = [] for configuration in configurations: - subnet = configuration["subnet_cidrs"] - provider_cidrs = {"cloud_identifier": configuration["cloud_identifier"], "provider_cidrs": subnet} + provider_cidrs = {"cloud_identifier": configuration["cloud_identifier"], + "provider_cidrs": configuration["subnet_cidrs"]} all_cidrs.append(provider_cidrs) return all_cidrs @@ -275,8 +274,9 @@ def get_cidrs(configurations): def get_ansible_roles(ansible_roles, log): """ Checks if ansible_roles have all necessary values and returns True if so. - :param ansible_roles: ansible_roles from master configuration (first configuration) - :return: list of valid ansible_roles + @param ansible_roles: ansible_roles from master configuration (first configuration) + @param log: + @return: list of valid ansible_roles """ ansible_roles_yaml = [] for ansible_role in (ansible_roles or []): From e7b6ccd66087d17e404358580353c418e8ce5ea2 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 27 Oct 2023 12:19:31 +0200 Subject: [PATCH 025/145] fixed tests further. One to fix left. --- tests/test_ansible_configurator.py | 221 ++++++++++++++--------------- 1 file changed, 107 insertions(+), 114 deletions(-) diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index c3ae1851..ff36fbec 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -1,13 +1,12 @@ """ -Tests for ansibleConfigurator +Tests for ansible_configurator """ from unittest import TestCase from unittest.mock import MagicMock, Mock, patch, call, mock_open, ANY -import bibigrid.core.utility.ansible_configurator as ansibleConfigurator import bibigrid.core.utility.paths.ansible_resources_path as aRP -import bibigrid.core.utility.yaml_dumper as yamlDumper from bibigrid.core import startup +from bibigrid.core.utility import ansible_configurator class TestAnsibleConfigurator(TestCase): @@ -26,7 +25,7 @@ def test_generate_site_file_yaml_empty(self): {'become': 'yes', 'hosts': 'workers', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}], 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] - self.assertEqual(site_yaml, ansibleConfigurator.generate_site_file_yaml([])) + self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml([])) def test_generate_site_file_yaml_role(self): custom_roles = [{"file": "file", "hosts": "hosts", "name": "name", "vars": "vars", "vars_file": "varsFile"}] @@ -40,7 +39,7 @@ def test_generate_site_file_yaml_role(self): {'become': 'yes', 'hosts': 'workers', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}, 'additional/name'], 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}] - self.assertEqual(site_yaml, ansibleConfigurator.generate_site_file_yaml(custom_roles)) + self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(custom_roles)) def test_generate_common_configuration_false(self): cidrs = "42" @@ -57,10 +56,10 @@ def test_generate_common_configuration_false(self): 'TreeWidth': 128}, 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} - generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, - cluster_id, ssh_user, - default_user, - startup.LOG) + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) # munge key is randomly generated common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] self.assertEqual(common_configuration_yaml, generated_common_configuration) @@ -86,10 +85,10 @@ def test_generate_common_configuration_true(self): 'zabbix_conf': {'admin_password': 'bibigrid', 'db': 'zabbix', 'db_password': 'zabbix', 'db_user': 'zabbix', 'server_name': 'bibigrid', 'timezone': 'Europe/Berlin'}} - generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, - cluster_id, ssh_user, - default_user, - startup.LOG) + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] self.assertEqual(common_configuration_yaml, generated_common_configuration) @@ -110,10 +109,10 @@ def test_generate_common_configuration_nfs_shares(self): 'TreeWidth': 128}, 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} - generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, - cluster_id, ssh_user, - default_user, - startup.LOG) + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] self.assertEqual(common_configuration_yaml, generated_common_configuration) @@ -133,10 +132,10 @@ def test_generate_common_configuration_nfs(self): 'TreeWidth': 128}, 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} - generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, - cluster_id, ssh_user, - default_user, - startup.LOG) + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] self.assertEqual(common_configuration_yaml, generated_common_configuration) @@ -157,10 +156,10 @@ def test_generate_common_configuration_ext_nfs_shares(self): 'TreeWidth': 128}, 'munge_key': 'YryJVnqgg24Ksf8zXQtbct3nuXrMSi9N'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} - generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, - cluster_id, ssh_user, - default_user, - startup.LOG) + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] self.assertEqual(common_configuration_yaml, generated_common_configuration) @@ -182,188 +181,182 @@ def test_generate_common_configuration_ide(self): 'TreeWidth': 128}, 'munge_key': 'b7nks3Ur3kanyPAEBxfSC9ypfSHFnWJL'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} - generated_common_configuration = ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration, - cluster_id, ssh_user, - default_user, - startup.LOG) + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] self.assertEqual(common_configuration_yaml, generated_common_configuration) - @patch("bibigrid.core.utility.ansibleConfigurator.get_ansible_roles") - def test_generate_common_configuration_ansible_roles_mock(self, mock_ansible_roles): # TODO + def test_generate_common_configuration_ansible_roles_mock(self): cidrs = "42" ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] - mock_ansible_roles.return_value = 21 - configuration = {"ansibleRoles": ansible_roles} - self.assertEqual(21, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)["ansible_roles"]) - mock_ansible_roles.assert_called_with(ansible_roles) - - @patch("bibigrid.core.utility.ansibleConfigurator.get_ansible_galaxy_roles") - def test_generate_common_configuration_ansible_galaxy_roles(self, mock_galaxy_roles): + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + configuration = [{"ansibleRoles": ansible_roles}] + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + self.assertEqual(ansible_roles, generated_common_configuration["ansible_roles"]) + + def test_generate_common_configuration_ansible_galaxy_roles(self): cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file"]}] - configuration = {"ansibleGalaxyRoles": galaxy_roles} - mock_galaxy_roles.return_value = 21 - self.assertEqual(21, ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)[ - "ansible_galaxy_roles"]) - mock_galaxy_roles.assert_called_with(galaxy_roles) - - @patch("bibigrid.core.utility.ansibleConfigurator.to_instance_host_dict") + configuration = [{"ansibleGalaxyRoles": galaxy_roles}] + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + self.assertEqual(galaxy_roles, generated_common_configuration["ansible_galaxy_roles"]) + + @patch("bibigrid.core.utility.ansible_configurator.to_instance_host_dict") def test_generate_ansible_hosts(self, mock_instance_host_dict): - mock_instance_host_dict.side_effect = [0, 1, 2] - cluster_dict = {"workers": [{"private_v4": 21}], "vpngtws": [{"private_v4": 32}]} - expected = {'master': {'hosts': 0}, 'worker': {'hosts': {21: 1, 32: 2}}} - self.assertEqual(expected, ansibleConfigurator.generate_ansible_hosts_yaml(42, cluster_dict)) + cluster_id = "21" + mock_instance_host_dict.side_effect = [0, 1, 2, 4, 5, {}] + configuration = [{'masterInstance': {'type': 'mini', 'image': 'Ubuntu'}, + 'workerInstances': [{'type': 'tiny', 'image': 'Ubuntu', 'count': 2}, + {'type': 'default', 'image': 'Ubuntu', 'count': 1}]}, + {'vpnInstance': {'type': 'mini', 'image': 'Ubuntu'}, 'workerInstances': [ + {'type': 'tiny', 'image': 'Ubuntu', 'count': 2, 'features': ['holdsinformation']}, + {'type': 'small', 'image': 'Ubuntu', 'count': 2}], 'floating_ip': "42"}] + expected = {'vpn': {'children': {'master': {'hosts': {'localhost': 0}}, + 'vpngtw': {'hosts': {'bibigrid-vpngtw-21-0': {'ansible_host': '42'}}}}, + 'hosts': {}}, 'workers': { + 'children': {'bibigrid_worker_21_0_1': {'hosts': {'bibigrid-worker-21-[0:1]': 1}}, + 'bibigrid_worker_21_2_2': {'hosts': {'bibigrid-worker-21-[2:2]': 2}}, + 'bibigrid_worker_21_3_4': {'hosts': {'bibigrid-worker-21-[3:4]': 4}}, + 'bibigrid_worker_21_5_6': {'hosts': {'bibigrid-worker-21-[5:6]': 5}}}, 'hosts': {}}} + self.assertEqual(expected, + ansible_configurator.generate_ansible_hosts_yaml(42, configuration, cluster_id, startup.LOG)) call_list = mock_instance_host_dict.call_args_list self.assertEqual(call(42), call_list[0]) - self.assertEqual(call(42, ip=21, local=False), call_list[1]) - self.assertEqual(call(42, ip=32, local=False), call_list[2]) + for call_happened in call_list[1:]: + self.assertEqual(call(42, ip=""), call_happened) def test_to_instance_host_local(self): ip = 42 ssh_user = 21 - local = {"ip": ip, "ansible_connection": "local", - "ansible_python_interpreter": ansibleConfigurator.PYTHON_INTERPRETER, "ansible_user": ssh_user} - self.assertEqual(local, ansibleConfigurator.to_instance_host_dict(21, 42, True)) + local = {"ip": ip, "ansible_connection": "ssh", + "ansible_python_interpreter": ansible_configurator.PYTHON_INTERPRETER, "ansible_user": ssh_user} + self.assertEqual(local, ansible_configurator.to_instance_host_dict(21, 42)) def test_to_instance_host_ssh(self): ip = 42 ssh_user = 21 ssh = {"ip": ip, "ansible_connection": "ssh", - "ansible_python_interpreter": ansibleConfigurator.PYTHON_INTERPRETER, "ansible_user": ssh_user} - self.assertEqual(ssh, ansibleConfigurator.to_instance_host_dict(21, 42, False)) + "ansible_python_interpreter": ansible_configurator.PYTHON_INTERPRETER, "ansible_user": ssh_user} + self.assertEqual(ssh, ansible_configurator.to_instance_host_dict(21, 42)) - def test_get_cidrs_single(self): + def test_get_cidrs(self): provider = Mock() provider.get_subnet_by_id_or_name.return_value = {"cidr": 42} - configuration = {"subnet": 21} - expected = [{'provider': 'Mock', 'provider_cidrs': [42]}] - self.assertEqual(expected, ansibleConfigurator.get_cidrs([configuration], [provider])) - provider.get_subnet_by_id_or_name.assert_called_with(21) - - def test_get_cidrs_list(self): - provider = Mock() - provider.get_subnet_by_id_or_name.return_value = {"cidr": 42} - configuration = {"subnet": [21, 22]} - expected = [{'provider': 'Mock', 'provider_cidrs': [42, 42]}] - self.assertEqual(expected, ansibleConfigurator.get_cidrs([configuration], [provider])) - call_list = provider.get_subnet_by_id_or_name.call_args_list - self.assertEqual(call(21), call_list[0]) - self.assertEqual(call(22), call_list[1]) + configuration = [{"subnet_cidrs": [21], "cloud_identifier": 13}] + expected = [{'cloud_identifier': 13, 'provider_cidrs': [21]}] + self.assertEqual(expected, ansible_configurator.get_cidrs(configuration)) def test_get_ansible_roles_empty(self): - self.assertEqual([], ansibleConfigurator.get_ansible_roles([])) + self.assertEqual([], ansible_configurator.get_ansible_roles([], startup.LOG)) def test_get_ansible_roles(self): ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] - self.assertEqual(ansible_roles, ansibleConfigurator.get_ansible_roles(ansible_roles)) + self.assertEqual(ansible_roles, ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) def test_get_ansible_roles_add(self): ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] ansible_roles_add = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file", "additional"]}] - self.assertEqual(ansible_roles, ansibleConfigurator.get_ansible_roles(ansible_roles_add)) + self.assertEqual(ansible_roles, ansible_configurator.get_ansible_roles(ansible_roles_add, startup.LOG)) def test_get_ansible_roles_minus(self): ansible_roles = [{elem: elem for elem in ["file", "hosts"]}] - self.assertEqual(ansible_roles, ansibleConfigurator.get_ansible_roles(ansible_roles)) + self.assertEqual(ansible_roles, ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) - @patch("logging.warning") - def test_get_ansible_roles_mismatch_hosts(self, mock_log): + def test_get_ansible_roles_mismatch_hosts(self): ansible_roles = [{"file": "file"}] - self.assertEqual([], ansibleConfigurator.get_ansible_roles(ansible_roles)) - mock_log.assert_called() + self.assertEqual([], ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) - @patch("logging.warning") - def test_get_ansible_roles_mismatch_file(self, mock_log): + def test_get_ansible_roles_mismatch_file(self): ansible_roles = [{"hosts": "hosts"}] - self.assertEqual([], ansibleConfigurator.get_ansible_roles(ansible_roles)) - mock_log.assert_called() + self.assertEqual([], ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) def test_get_ansible_galaxy_roles_empty(self): - self.assertEqual([], ansibleConfigurator.get_ansible_galaxy_roles([])) + self.assertEqual([], ansible_configurator.get_ansible_galaxy_roles([], startup.LOG)) def test_get_ansible_galaxy_roles(self): galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file"]}] - self.assertEqual(galaxy_roles, ansibleConfigurator.get_ansible_galaxy_roles(galaxy_roles)) + self.assertEqual(galaxy_roles, ansible_configurator.get_ansible_galaxy_roles(galaxy_roles, startup.LOG)) def test_get_ansible_galaxy_roles_add(self): galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file"]}] galaxy_roles_add = [ {elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file", "additional"]}] - self.assertEqual(galaxy_roles, ansibleConfigurator.get_ansible_galaxy_roles(galaxy_roles_add)) + self.assertEqual(galaxy_roles, ansible_configurator.get_ansible_galaxy_roles(galaxy_roles_add, startup.LOG)) def test_get_ansible_galaxy_roles_minus(self): galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "vars", "vars_file"]}] - self.assertEqual(galaxy_roles, ansibleConfigurator.get_ansible_galaxy_roles(galaxy_roles)) + self.assertEqual(galaxy_roles, ansible_configurator.get_ansible_galaxy_roles(galaxy_roles, startup.LOG)) @patch("logging.warning") def test_get_ansible_galaxy_roles_mismatch(self, mock_log): galaxy_roles = [{elem: elem for elem in ["hosts", "name", "vars", "vars_file"]}] - self.assertEqual([], ansibleConfigurator.get_ansible_galaxy_roles(galaxy_roles)) + self.assertEqual([], ansible_configurator.get_ansible_galaxy_roles(galaxy_roles, startup.LOG)) mock_log.assert_called() - def test_generate_login_file(self): - login_yaml = {"default_user": 99, "ssh_user": 21, "munge_key": 32} - self.assertEqual(login_yaml, ansibleConfigurator.generate_login_file_yaml(21, 32, 99)) - def test_generate_worker_specification_file_yaml(self): configuration = [{"workerInstances": [{elem: elem for elem in ["type", "image"]}], "network": [32]}] expected = [{'IMAGE': 'image', 'NETWORK': [32], 'TYPE': 'type'}] - self.assertEqual(expected, ansibleConfigurator.generate_worker_specification_file_yaml(configuration)) + self.assertEqual(expected, ansible_configurator.generate_worker_specification_file_yaml(configuration, startup.LOG)) def test_generate_worker_specification_file_yaml_empty(self): configuration = [{}] expected = [] - self.assertEqual(expected, ansibleConfigurator.generate_worker_specification_file_yaml(configuration)) + self.assertEqual(expected, ansible_configurator.generate_worker_specification_file_yaml(configuration, startup.LOG)) @patch("yaml.dump") def test_write_yaml_no_alias(self, mock_yaml): with patch('builtins.open', mock_open()) as output_mock: - ansibleConfigurator.write_yaml("here", {"some": "yaml"}, False) + ansible_configurator.write_yaml("here", {"some": "yaml"}, False) output_mock.assert_called_once_with("here", "w+") mock_yaml.assert_called_with(data={"some": "yaml"}, stream=ANY, Dumper=yamlDumper.NoAliasSafeDumper) @patch("yaml.safe_dump") def test_write_yaml_alias(self, mock_yaml): with patch('builtins.open', mock_open()) as output_mock: - ansibleConfigurator.write_yaml("here", {"some": "yaml"}, True) - output_mock.assert_called_once_with("here", "w+") + ansible_configurator.write_yaml("here", {"some": "yaml"}, startup.LOG, True) + output_mock.assert_called_once_with("here", mode="w+", encoding="UTF-8") mock_yaml.assert_called_with(data={"some": "yaml"}, stream=ANY) @patch("bibigrid.core.utility.id_generation.generate_munge_key") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_worker_specification_file_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_login_file_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_common_configuration_yaml") + @patch("bibigrid.core.utility.ansible_configurator.generate_worker_specification_file_yaml") + @patch("bibigrid.core.utility.ansible_configurator.generate_common_configuration_yaml") @patch("bibigrid.core.actions.list_clusters.dict_clusters") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_instances_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_ansible_hosts_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.get_ansible_roles") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_site_file_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.write_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.get_cidrs") - def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_roles, mock_hosts, mock_instances, - mock_list, mock_common, mock_login, mock_worker, mock_munge): + @patch("bibigrid.core.utility.ansible_configurator.generate_ansible_hosts_yaml") + @patch("bibigrid.core.utility.ansible_configurator.get_ansible_roles") + @patch("bibigrid.core.utility.ansible_configurator.generate_site_file_yaml") + @patch("bibigrid.core.utility.ansible_configurator.write_yaml") + @patch("bibigrid.core.utility.ansible_configurator.get_cidrs") + def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_roles, mock_hosts, + mock_list, mock_common, mock_worker, mock_munge): mock_munge.return_value = 420 mock_cidrs.return_value = 421 mock_list.return_value = {2: 422} mock_roles.return_value = 423 provider = MagicMock() provider.cloud_specification = {"auth": {"username": "Tom"}} - ansibleConfigurator.configure_ansible_yaml([provider], [{"sshUser": 42, "ansibleRoles": 21}], 2) + ansible_configurator.configure_ansible_yaml([provider], [{"sshUser": 42, "ansibleRoles": 21}], 2, startup.LOG) mock_munge.assert_called() mock_worker.assert_called_with([{"sshUser": 42, "ansibleRoles": 21}]) mock_common.assert_called_with(421, configuration={"sshUser": 42, "ansibleRoles": 21}) - mock_login.assert_called_with(ssh_user=42, munge_key=420, default_user="Tom") mock_list.assert_called_with([provider]) - mock_instances.assert_called_with(422) mock_hosts.assert_called_with(42, 422) mock_site.assert_called_with(423) mock_roles.assert_called_with(21) mock_cidrs.assert_called_with([{'sshUser': 42, 'ansibleRoles': 21}], [provider]) expected = [call(aRP.WORKER_SPECIFICATION_FILE, mock_worker(), False), - call(aRP.COMMONS_LOGIN_FILE, mock_login(), False), call(aRP.COMMONS_CONFIG_FILE, mock_common(), False), - call(aRP.COMMONS_INSTANCES_FILE, mock_instances(), False), call(aRP.HOSTS_CONFIG_FILE, mock_hosts(), False), call(aRP.SITE_CONFIG_FILE, mock_site(), False)] self.assertEqual(expected, mock_yaml.call_args_list) From f2015f9985f56a287e10d4edf48d7874f3100dd1 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 27 Oct 2023 13:14:16 +0200 Subject: [PATCH 026/145] fixed additional tests --- tests/test_ansible_configurator.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index ff36fbec..43230ff7 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -309,12 +309,14 @@ def test_get_ansible_galaxy_roles_mismatch(self, mock_log): def test_generate_worker_specification_file_yaml(self): configuration = [{"workerInstances": [{elem: elem for elem in ["type", "image"]}], "network": [32]}] expected = [{'IMAGE': 'image', 'NETWORK': [32], 'TYPE': 'type'}] - self.assertEqual(expected, ansible_configurator.generate_worker_specification_file_yaml(configuration, startup.LOG)) + self.assertEqual(expected, + ansible_configurator.generate_worker_specification_file_yaml(configuration, startup.LOG)) def test_generate_worker_specification_file_yaml_empty(self): configuration = [{}] expected = [] - self.assertEqual(expected, ansible_configurator.generate_worker_specification_file_yaml(configuration, startup.LOG)) + self.assertEqual(expected, + ansible_configurator.generate_worker_specification_file_yaml(configuration, startup.LOG)) @patch("yaml.dump") def test_write_yaml_no_alias(self, mock_yaml): @@ -330,7 +332,7 @@ def test_write_yaml_alias(self, mock_yaml): output_mock.assert_called_once_with("here", mode="w+", encoding="UTF-8") mock_yaml.assert_called_with(data={"some": "yaml"}, stream=ANY) - @patch("bibigrid.core.utility.id_generation.generate_munge_key") + @patch("bibigrid.core.utility.ansible_configurator.write_host_and_group_vars") @patch("bibigrid.core.utility.ansible_configurator.generate_worker_specification_file_yaml") @patch("bibigrid.core.utility.ansible_configurator.generate_common_configuration_yaml") @patch("bibigrid.core.actions.list_clusters.dict_clusters") @@ -339,17 +341,16 @@ def test_write_yaml_alias(self, mock_yaml): @patch("bibigrid.core.utility.ansible_configurator.generate_site_file_yaml") @patch("bibigrid.core.utility.ansible_configurator.write_yaml") @patch("bibigrid.core.utility.ansible_configurator.get_cidrs") - def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_roles, mock_hosts, - mock_list, mock_common, mock_worker, mock_munge): - mock_munge.return_value = 420 + def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_roles, mock_hosts, mock_list, + mock_common, mock_worker, mock_write): mock_cidrs.return_value = 421 mock_list.return_value = {2: 422} mock_roles.return_value = 423 provider = MagicMock() provider.cloud_specification = {"auth": {"username": "Tom"}} ansible_configurator.configure_ansible_yaml([provider], [{"sshUser": 42, "ansibleRoles": 21}], 2, startup.LOG) - mock_munge.assert_called() - mock_worker.assert_called_with([{"sshUser": 42, "ansibleRoles": 21}]) + print(mock_yaml.call_args_list) + mock_worker.assert_called_with([{"sshUser": 42, "ansibleRoles": 21}], startup.LOG) mock_common.assert_called_with(421, configuration={"sshUser": 42, "ansibleRoles": 21}) mock_list.assert_called_with([provider]) mock_hosts.assert_called_with(42, 422) From 7141a78e8b01635dbf3aed04a0c4a989a546949d Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 27 Oct 2023 15:09:35 +0200 Subject: [PATCH 027/145] fixed all tests for ansible configurator --- tests/test_ansible_configurator.py | 46 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index 43230ff7..dbaef9a9 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock, Mock, patch, call, mock_open, ANY import bibigrid.core.utility.paths.ansible_resources_path as aRP +from bibigrid.core.utility.yaml_dumper import NoAliasSafeDumper from bibigrid.core import startup from bibigrid.core.utility import ansible_configurator @@ -239,16 +240,16 @@ def test_generate_ansible_hosts(self, mock_instance_host_dict): self.assertEqual(call(42, ip=""), call_happened) def test_to_instance_host_local(self): - ip = 42 + ip_address = 42 ssh_user = 21 - local = {"ip": ip, "ansible_connection": "ssh", + local = {"ip": ip_address, "ansible_connection": "ssh", "ansible_python_interpreter": ansible_configurator.PYTHON_INTERPRETER, "ansible_user": ssh_user} self.assertEqual(local, ansible_configurator.to_instance_host_dict(21, 42)) def test_to_instance_host_ssh(self): - ip = 42 + ip_address = 42 ssh_user = 21 - ssh = {"ip": ip, "ansible_connection": "ssh", + ssh = {"ip": ip_address, "ansible_connection": "ssh", "ansible_python_interpreter": ansible_configurator.PYTHON_INTERPRETER, "ansible_user": ssh_user} self.assertEqual(ssh, ansible_configurator.to_instance_host_dict(21, 42)) @@ -300,11 +301,9 @@ def test_get_ansible_galaxy_roles_minus(self): galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "vars", "vars_file"]}] self.assertEqual(galaxy_roles, ansible_configurator.get_ansible_galaxy_roles(galaxy_roles, startup.LOG)) - @patch("logging.warning") - def test_get_ansible_galaxy_roles_mismatch(self, mock_log): + def test_get_ansible_galaxy_roles_mismatch(self): galaxy_roles = [{elem: elem for elem in ["hosts", "name", "vars", "vars_file"]}] self.assertEqual([], ansible_configurator.get_ansible_galaxy_roles(galaxy_roles, startup.LOG)) - mock_log.assert_called() def test_generate_worker_specification_file_yaml(self): configuration = [{"workerInstances": [{elem: elem for elem in ["type", "image"]}], "network": [32]}] @@ -321,9 +320,9 @@ def test_generate_worker_specification_file_yaml_empty(self): @patch("yaml.dump") def test_write_yaml_no_alias(self, mock_yaml): with patch('builtins.open', mock_open()) as output_mock: - ansible_configurator.write_yaml("here", {"some": "yaml"}, False) - output_mock.assert_called_once_with("here", "w+") - mock_yaml.assert_called_with(data={"some": "yaml"}, stream=ANY, Dumper=yamlDumper.NoAliasSafeDumper) + ansible_configurator.write_yaml("here", {"some": "yaml"}, startup.LOG, False) + output_mock.assert_called_once_with("here", mode="w+", encoding="UTF-8") + mock_yaml.assert_called_with(data={"some": "yaml"}, stream=ANY, Dumper=NoAliasSafeDumper) @patch("yaml.safe_dump") def test_write_yaml_alias(self, mock_yaml): @@ -347,17 +346,20 @@ def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_rol mock_list.return_value = {2: 422} mock_roles.return_value = 423 provider = MagicMock() - provider.cloud_specification = {"auth": {"username": "Tom"}} - ansible_configurator.configure_ansible_yaml([provider], [{"sshUser": 42, "ansibleRoles": 21}], 2, startup.LOG) - print(mock_yaml.call_args_list) - mock_worker.assert_called_with([{"sshUser": 42, "ansibleRoles": 21}], startup.LOG) - mock_common.assert_called_with(421, configuration={"sshUser": 42, "ansibleRoles": 21}) - mock_list.assert_called_with([provider]) - mock_hosts.assert_called_with(42, 422) + provider.cloud_specification = {"auth": {"username": "Default"}} + configuration = [{"sshUser": 42, "ansibleRoles": 21}] + cluster_id = 2 + ansible_configurator.configure_ansible_yaml([provider], configuration, cluster_id, startup.LOG) + mock_worker.assert_called_with(configuration, startup.LOG) + mock_common.assert_called_with(cidrs=421, configurations=configuration, cluster_id=cluster_id, ssh_user=42, + default_user="Default", log=startup.LOG) + mock_hosts.assert_called_with(42, configuration, cluster_id, startup.LOG) mock_site.assert_called_with(423) - mock_roles.assert_called_with(21) - mock_cidrs.assert_called_with([{'sshUser': 42, 'ansibleRoles': 21}], [provider]) - expected = [call(aRP.WORKER_SPECIFICATION_FILE, mock_worker(), False), - call(aRP.COMMONS_CONFIG_FILE, mock_common(), False), - call(aRP.HOSTS_CONFIG_FILE, mock_hosts(), False), call(aRP.SITE_CONFIG_FILE, mock_site(), False)] + mock_roles.assert_called_with(21, startup.LOG) + mock_cidrs.assert_called_with(configuration) + mock_write.assert_called() + expected = [call(aRP.WORKER_SPECIFICATION_FILE, mock_worker(), startup.LOG, False), + call(aRP.COMMONS_CONFIG_FILE, mock_common(), startup.LOG, False), + call(aRP.HOSTS_CONFIG_FILE, mock_hosts(), startup.LOG, False), + call(aRP.SITE_CONFIG_FILE, mock_site(), startup.LOG, False)] self.assertEqual(expected, mock_yaml.call_args_list) From dcf283652c6a03570ba432ec22a102925431ed9a Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 27 Oct 2023 16:28:48 +0200 Subject: [PATCH 028/145] fixed comment --- tests/startup_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/startup_tests.py b/tests/startup_tests.py index b3fee8a1..876b6d1f 100644 --- a/tests/startup_tests.py +++ b/tests/startup_tests.py @@ -39,7 +39,7 @@ def suppress_stdout(): unittest.TextTestRunner(verbosity=2).run(suite) # Provider-Test - ## Configuration needs to contain providers and infrastructures + # Configuration needs to contain providers and infrastructures if os.environ.get("OS_KEY_NAME"): suite = unittest.TestLoader().discover("./provider", pattern='test_*.py') with suppress_stdout(): From 400b350585b86bf7823c397a78a565665386dee3 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 31 Oct 2023 13:55:33 +0100 Subject: [PATCH 029/145] fixed multiple tests --- tests/test_create.py | 207 +++++++++++++++++-------------------------- 1 file changed, 80 insertions(+), 127 deletions(-) diff --git a/tests/test_create.py b/tests/test_create.py index e3cd9068..e6d76395 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -1,119 +1,75 @@ +""" +Module to test create +""" import os from unittest import TestCase from unittest.mock import patch, Mock, MagicMock, mock_open +from bibigrid.core import startup from bibigrid.core.actions import create class TestCreate(TestCase): + """ + Class to test create + """ + # pylint: disable=R0904 - @patch("bibigrid.core.utility.handler.sshHandler.get_add_ssh_public_key_commands") + @patch("bibigrid.core.utility.handler.ssh_handler.get_add_ssh_public_key_commands") @patch("bibigrid.core.utility.id_generation.generate_safe_cluster_id") def test_init(self, mock_id, mock_ssh): - unique_id = 21 + cluster_id = "21" provider = MagicMock() - provider.cloud_specification["auth"]["project_name"] = "name" - key_name = create.KEY_PREFIX + provider.cloud_specification["auth"]["project_name"] \ - + create.SEPARATOR + str(unique_id) - mock_id.return_value = str(unique_id) + provider_dict = {'cloud_specification': {'auth': {'project_name': 'project_name'}}} + provider.__getitem__.side_effect = provider_dict.__getitem__ + key_name = create.KEY_NAME.format(cluster_id=cluster_id) + mock_id.return_value = cluster_id mock_ssh.return_value = [32] - c = create.Create([provider], [{}], "path", False) - self.assertEqual(str(unique_id), c.cluster_id) - self.assertEqual("ubuntu", c.ssh_user) - self.assertEqual([32], c.ssh_add_public_key_commands) - self.assertEqual(c.key_name, key_name) + creator = create.Create([provider], [{}], "path", startup.LOG, False) + self.assertEqual(cluster_id, creator.cluster_id) + self.assertEqual("ubuntu", creator.ssh_user) + self.assertEqual([32], creator.ssh_add_public_key_commands) + self.assertEqual(key_name, creator.key_name) mock_id.assert_called_with([provider]) - @patch("bibigrid.core.utility.handler.sshHandler.get_add_ssh_public_key_commands") + @patch("bibigrid.core.utility.handler.ssh_handler.get_add_ssh_public_key_commands") + @patch("bibigrid.core.utility.id_generation.generate_safe_cluster_id") + def test_init_with_cluster_id(self, mock_id, mock_ssh): + cluster_id = "21" + provider = MagicMock() + provider_dict = {'cloud_specification': {'auth': {'project_name': 'project_name'}}} + provider.__getitem__.side_effect = provider_dict.__getitem__ + key_name = create.KEY_NAME.format(cluster_id=cluster_id) + mock_ssh.return_value = [32] + creator = create.Create([provider], [{}], "path", startup.LOG, False, cluster_id) + self.assertEqual(cluster_id, creator.cluster_id) + self.assertEqual("ubuntu", creator.ssh_user) + self.assertEqual([32], creator.ssh_add_public_key_commands) + self.assertEqual(key_name, creator.key_name) + mock_id.assert_not_called() + + @patch("bibigrid.core.utility.handler.ssh_handler.get_add_ssh_public_key_commands") @patch("bibigrid.core.utility.id_generation.generate_safe_cluster_id") def test_init_username(self, mock_id, mock_ssh): - unique_id = 21 - mock_id.return_value = str(unique_id) + cluster_id = "21" + mock_id.return_value = cluster_id mock_ssh.return_value = [32] - c = create.Create([MagicMock()], [{"sshUser": "ssh"}], "path", False) - self.assertEqual("ssh", c.ssh_user) + creator = create.Create([MagicMock()], [{"sshUser": "ssh"}], "path", startup.LOG, False) + self.assertEqual("ssh", creator.ssh_user) @patch("subprocess.check_output") def test_generate_keypair(self, mock_subprocess): provider = MagicMock() provider.list_servers.return_value = [] - c = create.Create([provider], [{}], "") + creator = create.Create([provider], [{}], "", startup.LOG) public_key = "data" with patch("builtins.open", mock_open(read_data=public_key)): - c.generate_keypair() - provider.create_keypair.assert_called_with(name=c.key_name, public_key=public_key) - mock_subprocess.assert_called_with(f'ssh-keygen -t ecdsa -f {create.KEY_FOLDER}{c.key_name} -P ""') - - def test_start_instance(self): - provider = MagicMock() - provider.list_servers.return_value = [] - provider.create_server.return_value = 42 - provider.add_auto_ip.return_value = {"floating_ip_address": 12} - c = create.Create([provider], [{}], "") - server_type = {"type": "testType", "image": "testImage"} - network = 21 - external_network = "testExternal" - c.start_instance(provider, create.MASTER_IDENTIFIER, server_type, network, worker=False, volumes=2, - external_network=external_network) - provider.create_server.assert_called_with(name=create.MASTER_IDENTIFIER + create.SEPARATOR + c.cluster_id, - flavor=server_type["type"], - key_name=c.key_name, - image=server_type["image"], - network=network, volumes=2) - provider.add_auto_ip.assert_called_with(network=external_network, server=42) - - def test_start_instance_worker(self): - provider = MagicMock() - provider.list_servers.return_value = [] - provider.create_server.return_value = 42 - provider.create_floating_ip.return_value = {"floating_ip_address": 12} - c = create.Create([provider], [{}], "") - server_type = {"type": "testType", "image": "testImage"} - network = 21 - c.start_instance(provider, create.WORKER_IDENTIFIER, server_type, network, worker=True, volumes=None, - external_network=None) - provider.create_server.assert_called_with( - name=create.WORKER_IDENTIFIER.format(0) + create.SEPARATOR + c.cluster_id, - flavor=server_type["type"], - key_name=c.key_name, - image=server_type["image"], - network=network, volumes=None) - provider.create_floating_ip.assert_not_called() - - @patch("bibigrid.models.returnThreading.ReturnThread") - def test_start_instances(self, return_mock): - provider = MagicMock() - provider.list_servers.return_value = [] - external_network = "externalTest" - provider.get_external_netowrk.return_value = external_network - configuration = {"network": 42} - c = create.Create([provider], [configuration], "") - provider.get_external_network.return_value = 32 - with patch.object(c, "prepare_vpn_or_master_args", return_value=(0, 1, 2)) as prepare_mock: - prepare_mock.return_value = (0, 1, 2) - c.start_instances({"network": 42}, provider) - prepare_mock.assert_called_with(configuration, provider) - provider.get_external_network.assert_called_with(configuration["network"]) - return_mock.assert_called_with(target=c.start_instance, - args=[provider, 0, 1, configuration["network"], False, 2, 32]) + creator.generate_keypair() + provider.create_keypair.assert_called_with(name=creator.key_name, public_key=public_key) + mock_subprocess.assert_called_with(f'ssh-keygen -t ecdsa -f {create.KEY_FOLDER}{creator.key_name} -P ""', + shell=True) - @patch("threading.Thread") - @patch("bibigrid.models.returnThreading.ReturnThread") - def test_start_instances_workers(self, return_mock, thread_mock): - provider = MagicMock() - provider.list_servers.return_value = [] - external_network = "externalTest" - provider.get_external_netowrk.return_value = external_network - configuration = {"network": 42, "workerInstances": [{"count": 1}]} - c = create.Create([provider], [configuration], "") - provider.get_external_network.return_value = 32 - with patch.object(c, "prepare_vpn_or_master_args", return_value=(0, 1, 2)) as prepare_mock: - prepare_mock.return_value = (0, 1, 2) - c.start_instances(configuration, provider) - thread_mock.assert_called_with(target=c.start_instance, - args=[provider, create.WORKER_IDENTIFIER, configuration["workerInstances"][0], - configuration["network"], True]) - return_mock.assert_called() + # TODO: Rewrite start instance tests def test_prepare_master_args(self): provider = MagicMock() @@ -121,11 +77,11 @@ def test_prepare_master_args(self): external_network = "externalTest" provider.get_external_netowrk.return_value = external_network configuration = {"network": 42, "masterInstance": "Some"} - c = create.Create([provider], [configuration], "") + creator = create.Create([provider], [configuration], "", startup.LOG) volume_return = [42] - with patch.object(c, "prepare_volumes", return_value=volume_return) as prepare_mock: + with patch.object(creator, "prepare_volumes", return_value=volume_return) as prepare_mock: self.assertEqual((create.MASTER_IDENTIFIER, configuration["masterInstance"], volume_return), - c.prepare_vpn_or_master_args(configuration, provider)) + creator.prepare_vpn_or_master_args(configuration, provider)) prepare_mock.assert_called_with(provider, []) def test_prepare_vpn_args(self): @@ -134,11 +90,11 @@ def test_prepare_vpn_args(self): external_network = "externalTest" provider.get_external_netowrk.return_value = external_network configuration = {"network": 42, "vpnInstance": "Some"} - c = create.Create([provider], [configuration], "") + creator = create.Create([provider], [configuration], "", startup.LOG) volume_return = [42] - with patch.object(c, "prepare_volumes", return_value=volume_return) as prepare_mock: + with patch.object(creator, "prepare_volumes", return_value=volume_return) as prepare_mock: self.assertEqual((create.VPN_WORKER_IDENTIFIER, configuration["vpnInstance"], []), - c.prepare_vpn_or_master_args(configuration, provider)) + creator.prepare_vpn_or_master_args(configuration, provider)) prepare_mock.assert_not_called() def test_prepare_args_keyerror(self): @@ -147,26 +103,24 @@ def test_prepare_args_keyerror(self): external_network = "externalTest" provider.get_external_netowrk.return_value = external_network configuration = {"network": 42} - c = create.Create([provider], [configuration], "") + creator = create.Create([provider], [configuration], "", startup.LOG) volume_return = [42] - with patch.object(c, "prepare_volumes", return_value=volume_return) as prepare_mock: + with patch.object(creator, "prepare_volumes", return_value=volume_return) as prepare_mock: with self.assertRaises(KeyError): self.assertEqual((create.VPN_WORKER_IDENTIFIER, configuration["vpnInstance"], []), - c.prepare_vpn_or_master_args(configuration, provider)) + creator.prepare_vpn_or_master_args(configuration, provider)) prepare_mock.assert_not_called() - @patch("bibigrid.core.utility.handler.sshHandler.ansible_preparation") - def test_setup_reachable_servers_master(self, mock_ansible): + @patch("bibigrid.core.utility.handler.ssh_handler.ansible_preparation") + def test_initialize_instances_master(self, mock_ansible): provider = MagicMock() provider.list_servers.return_value = [] - configuration = {"masterInstance": 42} - c = create.Create([provider], [configuration], "") floating_ip = 21 - c.setup_reachable_servers(configuration, floating_ip) - mock_ansible.assert_called_with(floating_ip=floating_ip, - private_key=create.KEY_FOLDER + c.key_name, - username=c.ssh_user, - commands=[]) + configuration = {"masterInstance": 42, "floating_ip": floating_ip} + creator = create.Create([provider], [configuration], "", startup.LOG) + creator.initialize_instances() + mock_ansible.assert_called_with(floating_ip=floating_ip, private_key=create.KEY_FOLDER + creator.key_name, + username=creator.ssh_user, commands=[], log=startup.LOG, gateway={}) def test_prepare_volumes_none(self): provider = MagicMock() @@ -174,8 +128,8 @@ def test_prepare_volumes_none(self): provider.get_volume_by_id_or_name.return_value = 42 provider.create_volume_from_snapshot = 21 configuration = {"vpnInstance": 42} - c = create.Create([provider], [configuration], "") - self.assertEqual([], c.prepare_volumes(provider, [])) + creator = create.Create([provider], [configuration], "", startup.LOG) + self.assertEqual(set(), creator.prepare_volumes(provider, [])) def test_prepare_volumes_volume(self): provider = MagicMock() @@ -225,36 +179,35 @@ def test_prepare_configurations_no_subnet(self): subnet = ["subnet"] provider.get_subnet_ids_by_network.return_value = subnet configuration = {"network": 42} - c = create.Create([provider], [configuration], "") - c.prepare_configurations() + creator = create.Create([provider], [configuration], "", startup.LOG) + creator.prepare_configurations() provider.get_subnet_ids_by_network.assert_called_with(42) self.assertEqual(subnet, configuration["subnet"]) - self.assertEqual(c.ssh_user, configuration["sshUser"]) + self.assertEqual(creator.ssh_user, configuration["sshUser"]) def test_prepare_configurations_none(self): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "") + creator = create.Create([provider], [configuration], "", startup.LOG) with self.assertRaises(KeyError): - c.prepare_configurations() + creator.prepare_configurations() - @patch("bibigrid.core.utility.ansibleConfigurator.configure_ansible_yaml") - @patch("bibigrid.core.utility.handler.sshHandler.execute_ssh") + @patch("bibigrid.core.utility.ansible_configurator.configure_ansible_yaml") + @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") def test_upload_playbooks(self, mock_ssh, mock_configure_ansible): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "") - c.master_ip = 42 - c.upload_data() - mock_configure_ansible.assert_called_with(providers=c.providers, - configurations=c.configurations, - cluster_id=c.cluster_id) - mock_ssh.assert_called_with(floating_ip=c.master_ip, private_key=create.KEY_FOLDER + c.key_name, - username=c.ssh_user, filepaths=[(os.path.expanduser("/Documents/Repos/bibigrid/" - "resources/playbook/"), - "playbook")], + creator = create.Create([provider], [configuration], "", startup.LOG) + creator.master_ip = 42 + creator.upload_data() + mock_configure_ansible.assert_called_with(providers=creator.providers, configurations=creator.configurations, + cluster_id=creator.cluster_id) + mock_ssh.assert_called_with(floating_ip=creator.master_ip, private_key=create.KEY_FOLDER + creator.key_name, + username=creator.ssh_user, + filepaths=[(os.path.expanduser("/Documents/Repos/bibigrid/" + "resources/playbook/"), "playbook")], commands=['echo ansible_start']) @patch("threading.Thread") From 65d5da7c440faf8b1315203cbe1fa3618eb0ef5e Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 10 Nov 2023 17:03:05 +0100 Subject: [PATCH 030/145] fixed a few tests --- tests/test_create.py | 73 ++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/tests/test_create.py b/tests/test_create.py index e6d76395..db96e319 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -3,7 +3,7 @@ """ import os from unittest import TestCase -from unittest.mock import patch, Mock, MagicMock, mock_open +from unittest.mock import patch, MagicMock, mock_open from bibigrid.core import startup from bibigrid.core.actions import create @@ -134,32 +134,30 @@ def test_prepare_volumes_none(self): def test_prepare_volumes_volume(self): provider = MagicMock() provider.list_servers.return_value = [] - provider.get_volume_by_id_or_name.return_value = 42 + provider.get_volume_by_id_or_name.return_value = {"id": 42} provider.create_volume_from_snapshot = 21 configuration = {"vpnInstance": 42} - c = create.Create([provider], [configuration], "") - self.assertEqual([42], c.prepare_volumes(provider, ["Test"])) + creator = create.Create([provider], [configuration], "", startup.LOG) + self.assertEqual({42}, creator.prepare_volumes(provider, ["Test"])) def test_prepare_volumes_snapshot(self): provider = MagicMock() provider.list_servers.return_value = [] - provider.get_volume_by_id_or_name.return_value = None + provider.get_volume_by_id_or_name.return_value = {"id": None} provider.create_volume_from_snapshot.return_value = 21 configuration = {"vpnInstance": 42} - c = create.Create([provider], [configuration], "") - self.assertEqual([21], c.prepare_volumes(provider, ["Test"])) + creator = create.Create([provider], [configuration], "", startup.LOG) + self.assertEqual({21}, creator.prepare_volumes(provider, ["Test"])) - @patch("logging.warning") - def test_prepare_volumes_mismatch(self, mock_log): + def test_prepare_volumes_mismatch(self): provider = MagicMock() provider.list_servers.return_value = [] - provider.get_volume_by_id_or_name.return_value = None + provider.get_volume_by_id_or_name.return_value = {"id": None} provider.create_volume_from_snapshot.return_value = None configuration = {"vpnInstance": 42} - c = create.Create([provider], [configuration], "") + creator = create.Create([provider], [configuration], "", startup.LOG) mount = "Test" - self.assertEqual([], c.prepare_volumes(provider, [mount])) - mock_log.assert_called_with(f"Mount {mount} is neither a snapshot nor a volume.") + self.assertEqual(set(), creator.prepare_volumes(provider, [mount])) def test_prepare_configurations_no_network(self): provider = MagicMock() @@ -167,11 +165,11 @@ def test_prepare_configurations_no_network(self): network = "network" provider.get_network_id_by_subnet.return_value = network configuration = {"subnet": 42} - c = create.Create([provider], [configuration], "") - c.prepare_configurations() + creator = create.Create([provider], [configuration], "", startup.LOG) + creator.prepare_configurations() provider.get_network_id_by_subnet.assert_called_with(42) self.assertEqual(network, configuration["network"]) - self.assertEqual(c.ssh_user, configuration["sshUser"]) + self.assertEqual(creator.ssh_user, configuration["sshUser"]) def test_prepare_configurations_no_subnet(self): provider = MagicMock() @@ -210,46 +208,33 @@ def test_upload_playbooks(self, mock_ssh, mock_configure_ansible): "resources/playbook/"), "playbook")], commands=['echo ansible_start']) - @patch("threading.Thread") - def test_start_start_instances_thread(self, mock_thread): - provider = MagicMock() - provider.list_servers.return_value = [] - configuration = {} - c = create.Create([provider], [configuration], "") - start_instances_mock_thread = Mock() - mock_thread.return_value = start_instances_mock_thread - c.start_start_instances_threads() - mock_thread.assert_called_with(target=c.start_instances, args=[configuration, provider]) - start_instances_mock_thread.start.assert_called() - start_instances_mock_thread.join.assert_called() - @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") - @patch.object(create.Create, "start_start_instances_threads") + @patch.object(create.Create, "start_start_instance_threads") @patch.object(create.Create, "upload_data") - @patch.object(create.Create, "print_cluster_start_info") - @patch("bibigrid.core.actions.terminateCluster.terminate_cluster") + @patch.object(create.Create, "log_cluster_start_info") + @patch("bibigrid.core.actions.terminate.terminate") def test_create_non_debug(self, mock_terminate, mock_info, mock_up, mock_start, mock_conf, mock_key): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "", False) - self.assertEqual(0, c.create()) + creator = create.Create([provider], [configuration], "", startup.LOG, False) + self.assertEqual(0, creator.create()) for mock in [mock_info, mock_up, mock_start, mock_conf, mock_key]: mock.assert_called() mock_terminate.assert_not_called() @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") - @patch.object(create.Create, "start_start_instances_threads") + @patch.object(create.Create, "start_start_instance_threads") @patch.object(create.Create, "upload_data") - @patch.object(create.Create, "print_cluster_start_info") - @patch("bibigrid.core.actions.terminateCluster.terminate_cluster") + @patch.object(create.Create, "log_cluster_start_info") + @patch("bibigrid.core.actions.terminate.terminate") def test_create_non_debug_upload_raise(self, mock_terminate, mock_info, mock_up, mock_start, mock_conf, mock_key): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "", False) + c = create.Create([provider], [configuration], "", startup.LOG,False) mock_up.side_effect = [ConnectionError()] self.assertEqual(1, c.create()) for mock in [mock_start, mock_conf, mock_key, mock_up]: @@ -260,16 +245,16 @@ def test_create_non_debug_upload_raise(self, mock_terminate, mock_info, mock_up, @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") - @patch.object(create.Create, "start_start_instances_threads") + @patch.object(create.Create, "start_start_instance_threads") @patch.object(create.Create, "upload_data") - @patch.object(create.Create, "print_cluster_start_info") - @patch("bibigrid.core.actions.terminateCluster.terminate_cluster") + @patch.object(create.Create, "log_cluster_start_info") + @patch("bibigrid.core.actions.terminate.terminate") def test_create_debug(self, mock_terminate, mock_info, mock_up, mock_start, mock_conf, mock_key): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "", True) - self.assertEqual(0, c.create()) + creator = create.Create([provider], [configuration], "", startup.LOG,True) + self.assertEqual(0, creator.create()) for mock in [mock_info, mock_up, mock_start, mock_conf, mock_key]: mock.assert_called() - mock_terminate.assert_called_with(cluster_id=c.cluster_id, providers=[provider], debug=True) + mock_terminate.assert_called_with(cluster_id=creator.cluster_id, providers=[provider], log=startup.LOG, debug=True) From c989ad3ea7aaf9c8293854a0fc75705869c030c9 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 14 Nov 2023 12:36:22 +0100 Subject: [PATCH 031/145] Fixed create --- tests/test_create.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/test_create.py b/tests/test_create.py index db96e319..339a90db 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -1,12 +1,12 @@ """ Module to test create """ -import os from unittest import TestCase from unittest.mock import patch, MagicMock, mock_open from bibigrid.core import startup from bibigrid.core.actions import create +from bibigrid.core.utility.handler import ssh_handler class TestCreate(TestCase): @@ -192,8 +192,9 @@ def test_prepare_configurations_none(self): creator.prepare_configurations() @patch("bibigrid.core.utility.ansible_configurator.configure_ansible_yaml") + @patch("bibigrid.core.utility.handler.ssh_handler.get_ac_command") @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") - def test_upload_playbooks(self, mock_ssh, mock_configure_ansible): + def test_upload_playbooks(self, mock_execute_ssh, mock_ac_ssh, mock_configure_ansible): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} @@ -201,12 +202,12 @@ def test_upload_playbooks(self, mock_ssh, mock_configure_ansible): creator.master_ip = 42 creator.upload_data() mock_configure_ansible.assert_called_with(providers=creator.providers, configurations=creator.configurations, - cluster_id=creator.cluster_id) - mock_ssh.assert_called_with(floating_ip=creator.master_ip, private_key=create.KEY_FOLDER + creator.key_name, - username=creator.ssh_user, - filepaths=[(os.path.expanduser("/Documents/Repos/bibigrid/" - "resources/playbook/"), "playbook")], - commands=['echo ansible_start']) + cluster_id=creator.cluster_id, log=startup.LOG) + mock_execute_ssh.assert_called_with(floating_ip=creator.master_ip, + private_key=create.KEY_FOLDER + creator.key_name, username=creator.ssh_user, + filepaths=create.FILEPATHS, + commands=[mock_ac_ssh()] + ssh_handler.ANSIBLE_START, log=startup.LOG, + gateway={}) @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") @@ -234,14 +235,15 @@ def test_create_non_debug_upload_raise(self, mock_terminate, mock_info, mock_up, provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "", startup.LOG,False) + creator = create.Create([provider], [configuration], "", startup.LOG, False) mock_up.side_effect = [ConnectionError()] - self.assertEqual(1, c.create()) + self.assertEqual(1, creator.create()) for mock in [mock_start, mock_conf, mock_key, mock_up]: mock.assert_called() for mock in [mock_info]: mock.assert_not_called() - mock_terminate.assert_called_with(cluster_id=c.cluster_id, providers=[provider], debug=False) + mock_terminate.assert_called_with(cluster_id=creator.cluster_id, providers=[provider], log=startup.LOG, + debug=False) @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") @@ -253,8 +255,9 @@ def test_create_debug(self, mock_terminate, mock_info, mock_up, mock_start, mock provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - creator = create.Create([provider], [configuration], "", startup.LOG,True) + creator = create.Create([provider], [configuration], "", startup.LOG, True) self.assertEqual(0, creator.create()) for mock in [mock_info, mock_up, mock_start, mock_conf, mock_key]: mock.assert_called() - mock_terminate.assert_called_with(cluster_id=creator.cluster_id, providers=[provider], log=startup.LOG, debug=True) + mock_terminate.assert_called_with(cluster_id=creator.cluster_id, providers=[provider], log=startup.LOG, + debug=True) From 4442c1bd4ffe3e466028a5331199874e4b5f4b37 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 14 Nov 2023 14:05:50 +0100 Subject: [PATCH 032/145] fixed some issues regarding --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index f6c40ad0..a84bed8b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ resources/playbook/ansible_hosts resources/playbook/vars/ resources/playbook/host_vars/ resources/playbook/group_vars/ +tests/resources/* +!test/resources/test_configuration.yml # any log files *.log From f5532e7c7fb3f8e6be38d0681ca0c9c548c462f4 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 14 Nov 2023 14:06:44 +0100 Subject: [PATCH 033/145] fixing test_provider.py --- tests/provider/test_provider.py | 8 +++++--- tests/startup_tests.py | 13 ++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/provider/test_provider.py b/tests/provider/test_provider.py index ef19b69d..32a472a6 100644 --- a/tests/provider/test_provider.py +++ b/tests/provider/test_provider.py @@ -4,6 +4,7 @@ import os import unittest +import logging import bibigrid.core.utility.handler.configuration_handler as configurationHandler import bibigrid.core.utility.handler.provider_handler as providerHandler @@ -61,9 +62,9 @@ "MFbUTTukAiDf4jAgvJkg7ayE0MPapGpI/OhSK2gyN45VAzs2m7uykun87B491JagZ57qr16vt8vxGYpFCEe8QqAcrUszUPqyPrb0auA8bz" \ "jO8S41Kx8FfG+7eTu4dQ0= user" -CONFIGURATIONS = configurationHandler.read_configuration( +CONFIGURATIONS = configurationHandler.read_configuration(logging, os.path.join(bP.ROOT_PATH, "tests/resources/infrastructure_cloud.yml")) -PROVIDERS = providerHandler.get_providers(CONFIGURATIONS) +PROVIDERS = providerHandler.get_providers(CONFIGURATIONS, logging) class ProviderServer: @@ -95,6 +96,7 @@ def test_get_free_resources(self): with self.subTest(provider.NAME): free_dict = provider.get_free_resources() self.assertEqual(FREE_RESOURCES_KEYS, set(free_dict.keys())) + print(free_dict) for value in free_dict.values(): self.assertLessEqual(0, value) @@ -209,7 +211,7 @@ def test_get_image_mismatch(self): with self.subTest(provider.NAME): self.assertIsNone(provider.get_image_by_id_or_name("NONE")) - if os.environ.get("OS_SNAPSHOT"): + if CONFIGURATIONS[0].get("snapshot_image"): def test_get_snapshot(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): diff --git a/tests/startup_tests.py b/tests/startup_tests.py index 876b6d1f..467e9b7e 100644 --- a/tests/startup_tests.py +++ b/tests/startup_tests.py @@ -34,13 +34,12 @@ def suppress_stdout(): logging.basicConfig(level=logging.ERROR) if __name__ == '__main__': # Unittests - suite = unittest.TestLoader().discover("./", pattern='test_*.py') - with suppress_stdout(): - unittest.TextTestRunner(verbosity=2).run(suite) + #suite = unittest.TestLoader().discover("./", pattern='test_*.py') + #with suppress_stdout(): + # unittest.TextTestRunner(verbosity=2).run(suite) # Provider-Test # Configuration needs to contain providers and infrastructures - if os.environ.get("OS_KEY_NAME"): - suite = unittest.TestLoader().discover("./provider", pattern='test_*.py') - with suppress_stdout(): - unittest.TextTestRunner(verbosity=2).run(suite) + suite = unittest.TestLoader().discover("./provider", pattern='test_*.py') + with suppress_stdout(): + unittest.TextTestRunner(verbosity=2).run(suite) From 6bbc3495689233d978e6b7374ba6267c157bcc37 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 14 Nov 2023 16:31:20 +0100 Subject: [PATCH 034/145] removed infrastructure_cloud.yml --- tests/resources/infrastructure_cloud.yml | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 tests/resources/infrastructure_cloud.yml diff --git a/tests/resources/infrastructure_cloud.yml b/tests/resources/infrastructure_cloud.yml deleted file mode 100644 index 028c0247..00000000 --- a/tests/resources/infrastructure_cloud.yml +++ /dev/null @@ -1,10 +0,0 @@ - # See https://cloud.denbi.de/wiki/Tutorials/BiBiGrid/ (after update) - -- infrastructure: openstack # former mode. - cloud: openstack # name of clouds.yaml entry - image: 2e61eb1b-dbd2-4ed8-b62b-5ee9fe0510e6 - flavor: de.NBI tiny - network: network0 - snapshot_image: test - -#- [next configurations] \ No newline at end of file From 9b0629af5422bc17b2b1a8f50e128009a14d5670 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 14 Nov 2023 17:08:21 +0100 Subject: [PATCH 035/145] minor fixes --- tests/provider/test_provider.py | 27 ++++++++++++++------------- tests/startup_tests.py | 4 ++-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/tests/provider/test_provider.py b/tests/provider/test_provider.py index 32a472a6..545ee7d6 100644 --- a/tests/provider/test_provider.py +++ b/tests/provider/test_provider.py @@ -2,13 +2,14 @@ Module containing integration and unit tests regarding the provider """ +import logging import os import unittest -import logging -import bibigrid.core.utility.handler.configuration_handler as configurationHandler -import bibigrid.core.utility.handler.provider_handler as providerHandler import bibigrid.core.utility.paths.basic_path as bP +from bibigrid.core.utility.handler import configuration_handler +from bibigrid.core.utility.handler import provider_handler +from bibigrid.models.exceptions import ExecutionException SERVER_KEYS = {'id', 'name', 'flavor', 'image', 'block_device_mapping', 'location', 'volumes', 'has_config_drive', 'host_id', 'progress', 'disk_config', 'power_state', 'task_state', 'vm_state', 'launched_at', @@ -62,9 +63,9 @@ "MFbUTTukAiDf4jAgvJkg7ayE0MPapGpI/OhSK2gyN45VAzs2m7uykun87B491JagZ57qr16vt8vxGYpFCEe8QqAcrUszUPqyPrb0auA8bz" \ "jO8S41Kx8FfG+7eTu4dQ0= user" -CONFIGURATIONS = configurationHandler.read_configuration(logging, - os.path.join(bP.ROOT_PATH, "tests/resources/infrastructure_cloud.yml")) -PROVIDERS = providerHandler.get_providers(CONFIGURATIONS, logging) +CONFIGURATIONS = configuration_handler.read_configuration(logging, os.path.join(bP.ROOT_PATH, + "tests/resources/infrastructure_cloud.yml")) +PROVIDERS = provider_handler.get_providers(CONFIGURATIONS, logging) class ProviderServer: @@ -96,9 +97,9 @@ def test_get_free_resources(self): with self.subTest(provider.NAME): free_dict = provider.get_free_resources() self.assertEqual(FREE_RESOURCES_KEYS, set(free_dict.keys())) - print(free_dict) - for value in free_dict.values(): - self.assertLessEqual(0, value) + for key, value in free_dict.items(): + if key != "floating_ips": + self.assertLessEqual(0, value) def test_server_start_type_error(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): @@ -117,16 +118,16 @@ def test_server_start_type_error(self): def test_server_start_attribute_error(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): - with self.assertRaises(AttributeError): + with self.assertRaises(ExecutionException): provider.create_server(name="name", image="ERROR", flavor=configuration["flavor"], network=configuration["network"]) - with self.assertRaises(AttributeError): + with self.assertRaises(ExecutionException): provider.create_server(name="name", flavor="ERROR", image=configuration["image"], network=configuration["network"]) - with self.assertRaises(AttributeError): + with self.assertRaises(ExecutionException): provider.create_server(name="name", flavor=configuration["flavor"], image=configuration["image"], network="ERROR") - with self.assertRaises(AttributeError): + with self.assertRaises(ExecutionException): provider.create_server(name="name", flavor=configuration["flavor"], image=configuration["image"], network=configuration["network"], key_name="ERROR") diff --git a/tests/startup_tests.py b/tests/startup_tests.py index 467e9b7e..2ac2823e 100644 --- a/tests/startup_tests.py +++ b/tests/startup_tests.py @@ -41,5 +41,5 @@ def suppress_stdout(): # Provider-Test # Configuration needs to contain providers and infrastructures suite = unittest.TestLoader().discover("./provider", pattern='test_*.py') - with suppress_stdout(): - unittest.TextTestRunner(verbosity=2).run(suite) + # with suppress_stdout(): + unittest.TextTestRunner(verbosity=2).run(suite) From 0f83ceeb80ae3c56ea54bc4fd245ec77feeb930a Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 16 Nov 2023 15:52:54 +0100 Subject: [PATCH 036/145] fixed all tests --- bibigrid/openstack/openstack_provider.py | 2 ++ tests/provider/test_provider.py | 38 ++++++++++++++---------- tests/startup_tests.py | 10 +++---- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/bibigrid/openstack/openstack_provider.py b/bibigrid/openstack/openstack_provider.py index 15ec3ef9..ae97a2ec 100644 --- a/bibigrid/openstack/openstack_provider.py +++ b/bibigrid/openstack/openstack_provider.py @@ -121,6 +121,8 @@ def create_server(self, name, flavor, image, network, key_name=None, wait=True, except openstack.exceptions.BadRequestException as exc: if "is not active" in str(exc): raise ImageDeactivatedException() from exc + if "Invalid key_name provided" in str(exc): + raise ExecutionException() from exc raise ConnectionError() from exc except openstack.exceptions.SDKException as exc: raise ExecutionException() from exc diff --git a/tests/provider/test_provider.py b/tests/provider/test_provider.py index 545ee7d6..45b0de7e 100644 --- a/tests/provider/test_provider.py +++ b/tests/provider/test_provider.py @@ -20,7 +20,12 @@ 'updated', 'user_id', 'tags', 'interface_ip', 'properties', 'hostId', 'config_drive', 'project_id', 'tenant_id', 'region', 'cloud', 'az', 'OS-DCF:diskConfig', 'OS-EXT-AZ:availability_zone', 'OS-SRV-USG:launched_at', 'OS-SRV-USG:terminated_at', 'OS-EXT-STS:task_state', 'OS-EXT-STS:vm_state', - 'OS-EXT-STS:power_state', 'os-extended-volumes:volumes_attached'} + 'OS-EXT-STS:power_state', 'os-extended-volumes:volumes_attached', 'OS-EXT-SRV-ATTR:ramdisk_id', + 'max_count', 'trusted_image_certificates', 'OS-SCH-HNT:scheduler_hints', + 'OS-EXT-SRV-ATTR:reservation_id', 'OS-EXT-SRV-ATTR:host', 'locked', 'host_status', + 'OS-EXT-SRV-ATTR:hypervisor_hostname', 'OS-EXT-SRV-ATTR:launch_index', + 'OS-EXT-SRV-ATTR:root_device_name', 'OS-EXT-SRV-ATTR:instance_name', 'OS-EXT-SRV-ATTR:user_data', + 'min_count', 'OS-EXT-SRV-ATTR:kernel_id', 'OS-EXT-SRV-ATTR:hostname'} FLOATING_IP_KEYS = {'attached', 'fixed_ip_address', 'floating_ip_address', 'id', 'location', 'network', 'port', 'router', 'status', 'created_at', 'updated_at', 'description', 'revision_number', 'properties', 'port_id', 'router_id', 'project_id', 'tenant_id', 'floating_network_id', 'port_details', @@ -41,7 +46,7 @@ 'is_protected', 'locations', 'properties', 'is_public', 'visibility', 'description', 'owner_specified.openstack.md5', 'owner_specified.openstack.object', 'owner_specified.openstack.sha256', 'os_hidden', 'os_hash_algo', 'os_hash_value', 'os_distro', 'os_version', 'schema', 'protected', - 'metadata', 'created', 'updated', 'minDisk', 'minRam'} + 'metadata', 'created', 'updated', 'minDisk', 'minRam', 'stores'} SNAPSHOT_KEYS = {'id', 'created_at', 'updated_at', 'name', 'description', 'volume_id', 'status', 'size', 'metadata', 'os-extended-snapshot-attributes:project_id', 'os-extended-snapshot-attributes:progress'} @@ -63,8 +68,9 @@ "MFbUTTukAiDf4jAgvJkg7ayE0MPapGpI/OhSK2gyN45VAzs2m7uykun87B491JagZ57qr16vt8vxGYpFCEe8QqAcrUszUPqyPrb0auA8bz" \ "jO8S41Kx8FfG+7eTu4dQ0= user" -CONFIGURATIONS = configuration_handler.read_configuration(logging, os.path.join(bP.ROOT_PATH, - "tests/resources/infrastructure_cloud.yml")) +CONFIGURATIONS = configuration_handler.read_configuration(logging, + os.path.join(bP.ROOT_PATH, + "tests/resources/infrastructure_cloud.yml")) PROVIDERS = provider_handler.get_providers(CONFIGURATIONS, logging) @@ -78,7 +84,7 @@ def __init__(self, provider, name, configuration, key_name=None): self.name = name self.server_dict = provider.create_server(name=self.name, flavor=configuration["flavor"], image=configuration["image"], network=configuration["network"], - key_name=key_name) + key_name=key_name, security_groups=[]) def __enter__(self): return self.server_dict @@ -106,30 +112,32 @@ def test_server_start_type_error(self): with self.subTest(provider.NAME): with self.assertRaises(TypeError): provider.create_server(name="name", flavor=configuration["flavor"], - network=configuration["network"]) + network=configuration["network"], security_groups=[]) with self.assertRaises(TypeError): - provider.create_server(name="name", image=configuration["image"], network=configuration["network"]) + provider.create_server(name="name", image=configuration["image"], network=configuration["network"], + security_groups=[]) with self.assertRaises(TypeError): provider.create_server(flavor=configuration["flavor"], image=configuration["image"], - network=configuration["network"]) + security_groups=[], network=configuration["network"]) with self.assertRaises(TypeError): - provider.create_server(name="name", flavor=configuration["flavor"], image=configuration["image"]) + provider.create_server(name="name", flavor=configuration["flavor"], image=configuration["image"], + security_groups=[]) def test_server_start_attribute_error(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): with self.assertRaises(ExecutionException): provider.create_server(name="name", image="ERROR", flavor=configuration["flavor"], - network=configuration["network"]) + network=configuration["network"], security_groups=[]) with self.assertRaises(ExecutionException): provider.create_server(name="name", flavor="ERROR", image=configuration["image"], - network=configuration["network"]) + network=configuration["network"], security_groups=[]) with self.assertRaises(ExecutionException): provider.create_server(name="name", flavor=configuration["flavor"], image=configuration["image"], - network="ERROR") + network="ERROR", security_groups=[]) with self.assertRaises(ExecutionException): provider.create_server(name="name", flavor=configuration["flavor"], image=configuration["image"], - network=configuration["network"], key_name="ERROR") + network=configuration["network"], key_name="ERROR", security_groups=[]) def test_create_keypair_create_delete_false_delete(self): for provider in PROVIDERS: @@ -144,8 +152,8 @@ def test_active_server_methods(self): with self.subTest(provider.NAME): with ProviderServer(provider, "bibigrid_test_server", configuration, "bibigrid_test_keypair") as provider_server: - floating_ip = provider.create_floating_ip(provider.get_external_network(configuration["network"]), - provider_server) + floating_ip = provider.attach_available_floating_ip( + provider.get_external_network(configuration["network"]), provider_server) server_list = provider.list_servers() self.assertEqual(SERVER_KEYS, set(provider_server.keys())) self.assertEqual("bibigrid_test_keypair", provider_server["key_name"]) diff --git a/tests/startup_tests.py b/tests/startup_tests.py index 2ac2823e..4a719ef2 100644 --- a/tests/startup_tests.py +++ b/tests/startup_tests.py @@ -34,12 +34,12 @@ def suppress_stdout(): logging.basicConfig(level=logging.ERROR) if __name__ == '__main__': # Unittests - #suite = unittest.TestLoader().discover("./", pattern='test_*.py') - #with suppress_stdout(): - # unittest.TextTestRunner(verbosity=2).run(suite) + suite = unittest.TestLoader().discover("./", pattern='test_*.py') + with suppress_stdout(): + unittest.TextTestRunner(verbosity=2).run(suite) # Provider-Test # Configuration needs to contain providers and infrastructures suite = unittest.TestLoader().discover("./provider", pattern='test_*.py') - # with suppress_stdout(): - unittest.TextTestRunner(verbosity=2).run(suite) + with suppress_stdout(): + unittest.TextTestRunner(verbosity=2).run(suite) From 422eda580c909be86bf9611bb2eee2d7f0db57c5 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 16 Nov 2023 16:52:13 +0100 Subject: [PATCH 037/145] removed print --- bibigrid/core/actions/ide.py | 3 +-- bibigrid/core/startup.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bibigrid/core/actions/ide.py b/bibigrid/core/actions/ide.py index 840d970b..1ad2c871 100644 --- a/bibigrid/core/actions/ide.py +++ b/bibigrid/core/actions/ide.py @@ -23,7 +23,6 @@ LOCALHOST = "127.0.0.1" - def sigint_handler(caught_signal, frame): # pylint: disable=unused-argument """ Is called when SIGINT is thrown and terminates the program @@ -82,7 +81,7 @@ def ide(cluster_id, master_provider, master_configuration, log): ssh_pkey=used_private_key, local_bind_address=(LOCALHOST, used_local_bind_address), remote_bind_address=(LOCALHOST, REMOTE_BIND_ADDRESS)) as server: - print("CTRL+C to close port forwarding when you are done.") + log.log(42, "CTRL+C to close port forwarding when you are done.") with server: # opens in existing window if any default program exists webbrowser.open(f"http://localhost:{used_local_bind_address}", new=2) diff --git a/bibigrid/core/startup.py b/bibigrid/core/startup.py index 4676f44e..3a073f27 100755 --- a/bibigrid/core/startup.py +++ b/bibigrid/core/startup.py @@ -70,7 +70,6 @@ def run_action(args, configurations, config_path): exit_state = 0 try: providers = provider_handler.get_providers(configurations, LOG) - print(args) if providers: if args.list: LOG.info("Action list selected") From 975f2e54ec5090ded70bafe37491a0a34b4498b6 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 23 Nov 2023 16:10:51 +0100 Subject: [PATCH 038/145] changed prints to log --- bibigrid/core/actions/list_clusters.py | 6 +++--- bibigrid/core/utility/validate_configuration.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bibigrid/core/actions/list_clusters.py b/bibigrid/core/actions/list_clusters.py index 99e1b6bc..554b7221 100644 --- a/bibigrid/core/actions/list_clusters.py +++ b/bibigrid/core/actions/list_clusters.py @@ -24,8 +24,8 @@ def dict_clusters(providers, log): servers = provider.list_servers() for server in servers: result = SERVER_REGEX.match(server["name"]) - print(server["name"]) - print(result) + log.log(42, server["name"]) + log.log(42, result) if result: identifier = result.group(4) or result.group(2) cluster_id = result.group(5) or result.group(3) @@ -71,7 +71,7 @@ def log_list(cluster_id, providers, log): master_count, worker_count, vpn_count = get_size_overview(cluster_dict[cluster_id], log) log.log(42, f"\tCluster has {master_count} master, {vpn_count} vpngtw and {worker_count} regular workers. " f"The cluster is spread over {vpn_count + master_count} reachable provider(s).") - pprint.pprint(cluster_dict[cluster_id]) + log.log(42, pprint.pformat(cluster_dict[cluster_id])) else: log.info("Cluster with cluster-id {cluster_id} not found.") log.log(42, f"Cluster with cluster-id {cluster_id} not found.") diff --git a/bibigrid/core/utility/validate_configuration.py b/bibigrid/core/utility/validate_configuration.py index 72deb806..a4232d2e 100644 --- a/bibigrid/core/utility/validate_configuration.py +++ b/bibigrid/core/utility/validate_configuration.py @@ -303,8 +303,8 @@ def check_instance_type_image_combination(self, instance_type, instance_image, p flavor = provider.get_flavor(instance_type) if not flavor: self.log.warning("Flavor %s does not exist.", instance_type) - print("Available flavors:") - print("\n".join(provider.get_active_flavors())) + self.log.log(42, "Available flavors:") + self.log.log(42, "\n".join(provider.get_active_flavors())) return False type_max_disk_space = flavor["disk"] type_max_ram = flavor["ram"] From 4b47b5103e1d7eefc64596279a0592adbd849cb0 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 23 Nov 2023 16:13:44 +0100 Subject: [PATCH 039/145] removed log --- bibigrid/core/actions/list_clusters.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bibigrid/core/actions/list_clusters.py b/bibigrid/core/actions/list_clusters.py index 554b7221..90e7aa5e 100644 --- a/bibigrid/core/actions/list_clusters.py +++ b/bibigrid/core/actions/list_clusters.py @@ -24,8 +24,6 @@ def dict_clusters(providers, log): servers = provider.list_servers() for server in servers: result = SERVER_REGEX.match(server["name"]) - log.log(42, server["name"]) - log.log(42, result) if result: identifier = result.group(4) or result.group(2) cluster_id = result.group(5) or result.group(3) From 5adfd66bf33097843650244de13b4c5e0fbb0985 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Sun, 26 Nov 2023 16:00:58 +0100 Subject: [PATCH 040/145] fixed None bug where [] is expected when no sshPublicKeyFile is given. --- bibigrid/core/utility/handler/cluster_ssh_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bibigrid/core/utility/handler/cluster_ssh_handler.py b/bibigrid/core/utility/handler/cluster_ssh_handler.py index 7ffd7ea5..6a5ffdb8 100644 --- a/bibigrid/core/utility/handler/cluster_ssh_handler.py +++ b/bibigrid/core/utility/handler/cluster_ssh_handler.py @@ -23,7 +23,7 @@ def get_ssh_connection_info(cluster_id, master_provider, master_configuration, l else: master_ip = list_clusters.get_master_access_ip(cluster_id, master_provider, log) ssh_user = master_configuration.get("sshUser") - public_keys = master_configuration.get("sshPublicKeyFiles") + public_keys = master_configuration.get("sshPublicKeyFiles") or [] used_private_key = None # first check configuration then if not found take the temporary key From 68b98b022cc2eb6f6efbb79564fc034a8dfa852d Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 28 Nov 2023 11:21:53 +0100 Subject: [PATCH 041/145] removed master from compute if use master as compute is false --- resources/playbook/roles/bibigrid/templates/slurm/slurm.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf b/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf index 95fbf82d..2bb2854e 100644 --- a/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf +++ b/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf @@ -69,7 +69,8 @@ SlurmdLogFile=/var/log/slurm/slurmd.log # COMPUTE NODES {% set sl = {} %} {% set all = {"nodes":[]} %} -{% for node_name in groups.master+groups.workers %} +{% set master_or_empty = groups.master if use_master_as_compute else [] %} +{% for node_name in master_or_empty +groups.workers %} {% set node = hostvars[node_name] %} {% set mem = node.flavor.ram // 1024 * 1000 %} {% if node.cloud_identifier not in sl %} From f29d7a207da1bc4a9f919d64a7923d669fe6a1fa Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 28 Nov 2023 16:42:49 +0100 Subject: [PATCH 042/145] reconstructured role additional in order to make it easier to include. Added quotes for consistency. --- .../roles/additional/example/meta/main.yml | 28 ------------------- .../additional/{example => }/tasks/main.yml | 1 + .../roles/bibigrid/tasks/010-bin-server.yml | 2 +- 3 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 resources/playbook/roles/additional/example/meta/main.yml rename resources/playbook/roles/additional/{example => }/tasks/main.yml (98%) diff --git a/resources/playbook/roles/additional/example/meta/main.yml b/resources/playbook/roles/additional/example/meta/main.yml deleted file mode 100644 index 8ff216df..00000000 --- a/resources/playbook/roles/additional/example/meta/main.yml +++ /dev/null @@ -1,28 +0,0 @@ -galaxy_info: - role_name: Hello-World Example - author: Tim Dilger - description: Shows working example of installing Ansible Role. - company: Bielefeld university, CeBiTec, BiBiServ - - license: BSD - - min_ansible_version: 2.7 - - platforms: - - name: EL - versions: - - 7 - - name: Debian - versions: - - stretch - - name: Ubuntu - versions: - - xenial - - bionic - - galaxy_tags: - - hello-world - -dependencies: [] - # List your role dependencies here, one per line. Be sure to remove the '[]' above, - # if you add dependencies to this list. diff --git a/resources/playbook/roles/additional/example/tasks/main.yml b/resources/playbook/roles/additional/tasks/main.yml similarity index 98% rename from resources/playbook/roles/additional/example/tasks/main.yml rename to resources/playbook/roles/additional/tasks/main.yml index 63ea8e43..e949ee7f 100644 --- a/resources/playbook/roles/additional/example/tasks/main.yml +++ b/resources/playbook/roles/additional/tasks/main.yml @@ -1,3 +1,4 @@ - debug: msg: - "Hello {{ ansible_user }}!" + diff --git a/resources/playbook/roles/bibigrid/tasks/010-bin-server.yml b/resources/playbook/roles/bibigrid/tasks/010-bin-server.yml index c6716010..49256c1a 100644 --- a/resources/playbook/roles/bibigrid/tasks/010-bin-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/010-bin-server.yml @@ -5,7 +5,7 @@ - name: Does folder exist delegate_to: localhost stat: - path: ~{{ ansible_facts.env.SUDO_USER }}/bin + path: "~{{ ansible_facts.env.SUDO_USER }}/bin" register: folder - when: folder.stat.exists From 0c048b77409d444cd2a0d3c1c0ad48ff3724ab23 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:09:52 +0100 Subject: [PATCH 043/145] Updated all tests (#448) * updated most tests * fixed validate_configuration.py tests. * Updated tests for startup.py * fixed bug in terminate that caused assume_yes to work as assume_no * updated terminate_cluster tests. * fixed formatting improved pylint * adapted tests * updated return threading test * updated provider_handler * tests not finished yet * Fixed server regex issue * test list clusters updated * fixed too open cluster_id regex * added missing "to" * fixed id_generation tests * renamed configuration handler to please linter * removed unnecessary tests and updated remaining * updated tests not finished yet * improved code style * fixed tests further. One to fix left. * fixed additional tests * fixed all tests for ansible configurator * fixed comment * fixed multiple tests * fixed a few tests * Fixed create * fixed some issues regarding * fixing test_provider.py * removed infrastructure_cloud.yml * minor fixes * fixed all tests * removed print * changed prints to log * removed log --- .gitignore | 2 + bibigrid/core/actions/ide.py | 3 +- bibigrid/core/actions/list_clusters.py | 4 +- bibigrid/core/actions/terminate.py | 12 +- bibigrid/core/utility/ansible_configurator.py | 16 +- bibigrid/core/utility/handler/ssh_handler.py | 10 +- .../core/utility/validate_configuration.py | 4 +- bibigrid/openstack/openstack_provider.py | 2 + tests/provider/test_provider.py | 61 ++- tests/resources/infrastructure_cloud.yml | 10 - tests/startup_tests.py | 9 +- tests/test_ValidateConfiguration.py | 321 ------------ tests/test_ansible_configurator.py | 474 ++++++++++-------- tests/test_check.py | 34 +- tests/test_configurationHandler.py | 197 -------- tests/test_configuration_handler.py | 208 ++++++++ tests/test_create.py | 293 +++++------ ..._idGeneration.py => test_id_generation.py} | 14 +- tests/test_listClusters.py | 46 -- tests/test_list_clusters.py | 57 +++ tests/test_providerHandler.py | 26 - tests/test_provider_handler.py | 33 ++ ...nThreading.py => test_return_threading.py} | 15 +- tests/test_sshHandler.py | 104 ---- tests/test_ssh_handler.py | 128 +++++ tests/test_startup.py | 60 ++- tests/test_terminateCluster.py | 37 -- tests/test_terminate_cluster.py | 50 ++ tests/test_validate_configuration.py | 318 ++++++++++++ 29 files changed, 1306 insertions(+), 1242 deletions(-) delete mode 100644 tests/resources/infrastructure_cloud.yml delete mode 100644 tests/test_ValidateConfiguration.py delete mode 100644 tests/test_configurationHandler.py create mode 100644 tests/test_configuration_handler.py rename tests/{test_idGeneration.py => test_id_generation.py} (82%) delete mode 100644 tests/test_listClusters.py create mode 100644 tests/test_list_clusters.py delete mode 100644 tests/test_providerHandler.py create mode 100644 tests/test_provider_handler.py rename tests/{test_returnThreading.py => test_return_threading.py} (63%) delete mode 100644 tests/test_sshHandler.py create mode 100644 tests/test_ssh_handler.py mode change 100644 => 100755 tests/test_startup.py delete mode 100644 tests/test_terminateCluster.py create mode 100644 tests/test_terminate_cluster.py create mode 100644 tests/test_validate_configuration.py diff --git a/.gitignore b/.gitignore index f6c40ad0..a84bed8b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ resources/playbook/ansible_hosts resources/playbook/vars/ resources/playbook/host_vars/ resources/playbook/group_vars/ +tests/resources/* +!test/resources/test_configuration.yml # any log files *.log diff --git a/bibigrid/core/actions/ide.py b/bibigrid/core/actions/ide.py index 840d970b..1ad2c871 100644 --- a/bibigrid/core/actions/ide.py +++ b/bibigrid/core/actions/ide.py @@ -23,7 +23,6 @@ LOCALHOST = "127.0.0.1" - def sigint_handler(caught_signal, frame): # pylint: disable=unused-argument """ Is called when SIGINT is thrown and terminates the program @@ -82,7 +81,7 @@ def ide(cluster_id, master_provider, master_configuration, log): ssh_pkey=used_private_key, local_bind_address=(LOCALHOST, used_local_bind_address), remote_bind_address=(LOCALHOST, REMOTE_BIND_ADDRESS)) as server: - print("CTRL+C to close port forwarding when you are done.") + log.log(42, "CTRL+C to close port forwarding when you are done.") with server: # opens in existing window if any default program exists webbrowser.open(f"http://localhost:{used_local_bind_address}", new=2) diff --git a/bibigrid/core/actions/list_clusters.py b/bibigrid/core/actions/list_clusters.py index 1965dc39..90e7aa5e 100644 --- a/bibigrid/core/actions/list_clusters.py +++ b/bibigrid/core/actions/list_clusters.py @@ -8,7 +8,7 @@ from bibigrid.core.actions import create -SERVER_REGEX = re.compile(r"^bibigrid-((master)-([a-zA-Z0-9]+)|(worker|vpngtw)\d+-([a-zA-Z0-9]+)-\d+)$") +SERVER_REGEX = re.compile(r"^bibigrid-((master)-([a-zA-Z0-9]+)|(worker|vpngtw)-([a-zA-Z0-9]+)-\d+)$") def dict_clusters(providers, log): @@ -69,7 +69,7 @@ def log_list(cluster_id, providers, log): master_count, worker_count, vpn_count = get_size_overview(cluster_dict[cluster_id], log) log.log(42, f"\tCluster has {master_count} master, {vpn_count} vpngtw and {worker_count} regular workers. " f"The cluster is spread over {vpn_count + master_count} reachable provider(s).") - pprint.pprint(cluster_dict[cluster_id]) + log.log(42, pprint.pformat(cluster_dict[cluster_id])) else: log.info("Cluster with cluster-id {cluster_id} not found.") log.log(42, f"Cluster with cluster-id {cluster_id} not found.") diff --git a/bibigrid/core/actions/terminate.py b/bibigrid/core/actions/terminate.py index db812d7f..15a5a859 100644 --- a/bibigrid/core/actions/terminate.py +++ b/bibigrid/core/actions/terminate.py @@ -34,11 +34,11 @@ def terminate(cluster_id, providers, log, debug=False, assume_yes=False): cluster_security_group_state = [] tmp_keyname = create.KEY_NAME.format(cluster_id=cluster_id) local_keypairs_deleted = delete_local_keypairs(tmp_keyname, log) - if not assume_yes and ( - local_keypairs_deleted or input(f"WARNING: No local temporary keyfiles found for cluster {cluster_id}. " - f"This might not be your cluster. Are you sure you want to terminate it?\n" - f"Any non-empty input to shutdown cluster {cluster_id}. " - f"Empty input to exit with cluster still alive:")): + if assume_yes or local_keypairs_deleted or input( + f"WARNING: No local temporary keyfiles found for cluster {cluster_id}. " + f"This might not be your cluster. Are you sure you want to terminate it?\n" + f"Any non-empty input to shutdown cluster {cluster_id}. " + f"Empty input to exit with cluster still alive:"): for provider in providers: log.info("Terminating cluster %s on cloud %s", cluster_id, provider.cloud_specification['identifier']) server_list = provider.list_servers() @@ -61,7 +61,7 @@ def terminate_servers(server_list, cluster_id, provider, log): """ log.info("Deleting servers on provider %s...", provider.cloud_specification['identifier']) cluster_server_state = [] - server_regex = re.compile(fr"^bibigrid-(master-{cluster_id}+|(worker|vpngtw)-{cluster_id}+-\d+)$") + server_regex = re.compile(fr"^bibigrid-(master-{cluster_id}|(worker|vpngtw)-{cluster_id}-\d+)$") for server in server_list: if server_regex.match(server["name"]): log.info("Trying to terminate Server %s on cloud %s.", server['name'], diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index a603e1ce..65e81aaa 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -128,8 +128,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py master_dict = {"name": name, "image": master["image"], "network": configuration["network"], "network_cidrs": configuration["subnet_cidrs"], "floating_ip": configuration["floating_ip"], "flavor": flavor_dict, "private_v4": configuration["private_v4"], - "cloud_identifier": configuration["cloud_identifier"], - "volumes": configuration["volumes"], + "cloud_identifier": configuration["cloud_identifier"], "volumes": configuration["volumes"], "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False)} if configuration.get("wireguard_peer"): master_dict["wireguard"] = {"ip": "10.0.0.1", "peer": configuration.get("wireguard_peer")} @@ -165,8 +164,8 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us master_configuration = configurations[0] log.info("Generating common configuration file...") # print(configuration.get("slurmConf", {})) - common_configuration_yaml = {"auto_mount": master_configuration.get("autoMount", False), - "cluster_id": cluster_id, "cluster_cidrs": cidrs, "default_user": default_user, + common_configuration_yaml = {"auto_mount": master_configuration.get("autoMount", False), "cluster_id": cluster_id, + "cluster_cidrs": cidrs, "default_user": default_user, "local_fs": master_configuration.get("localFS", False), "local_dns_lookup": master_configuration.get("localDNSlookup", False), "use_master_as_compute": master_configuration.get("useMasterAsCompute", True), @@ -266,8 +265,8 @@ def get_cidrs(configurations): """ all_cidrs = [] for configuration in configurations: - subnet = configuration["subnet_cidrs"] - provider_cidrs = {"cloud_identifier": configuration["cloud_identifier"], "provider_cidrs": subnet} + provider_cidrs = {"cloud_identifier": configuration["cloud_identifier"], + "provider_cidrs": configuration["subnet_cidrs"]} all_cidrs.append(provider_cidrs) return all_cidrs @@ -275,8 +274,9 @@ def get_cidrs(configurations): def get_ansible_roles(ansible_roles, log): """ Checks if ansible_roles have all necessary values and returns True if so. - :param ansible_roles: ansible_roles from master configuration (first configuration) - :return: list of valid ansible_roles + @param ansible_roles: ansible_roles from master configuration (first configuration) + @param log: + @return: list of valid ansible_roles """ ansible_roles_yaml = [] for ansible_role in (ansible_roles or []): diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index a705a405..d52cdde6 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -7,8 +7,8 @@ import time import paramiko -import yaml import sympy +import yaml from bibigrid.core.utility import ansible_commands as aC from bibigrid.models.exceptions import ConnectionException, ExecutionException @@ -107,10 +107,10 @@ def is_active(client, floating_ip_address, private_key, username, log, gateway, port = 22 if gateway: log.info(f"Using SSH Gateway {gateway.get('ip')}") - octets = {f'oct{enum+1}': int(elem) for enum, elem in enumerate(floating_ip_address.split("."))} + octets = {f'oct{enum + 1}': int(elem) for enum, elem in enumerate(floating_ip_address.split("."))} port = int(sympy.sympify(gateway["portFunction"]).subs(dict(octets))) - client.connect(hostname=gateway.get("ip") or floating_ip_address, username=username, - pkey=private_key, timeout=7, auth_timeout=5, port=port) + client.connect(hostname=gateway.get("ip") or floating_ip_address, username=username, pkey=private_key, + timeout=7, auth_timeout=5, port=port) establishing_connection = False log.info(f"Successfully connected to {floating_ip_address}") except paramiko.ssh_exception.NoValidConnectionsError as exc: @@ -158,7 +158,7 @@ def execute_ssh_cml_commands(client, commands, log): :param log: """ for command in commands: - ssh_stdin, ssh_stdout, ssh_stderr = client.exec_command(command[0]) # pylint: disable=unused-variable + _, ssh_stdout, _ = client.exec_command(command[0]) ssh_stdout.channel.set_combine_stderr(True) log.info(f"REMOTE: {command[1]}") diff --git a/bibigrid/core/utility/validate_configuration.py b/bibigrid/core/utility/validate_configuration.py index 72deb806..a4232d2e 100644 --- a/bibigrid/core/utility/validate_configuration.py +++ b/bibigrid/core/utility/validate_configuration.py @@ -303,8 +303,8 @@ def check_instance_type_image_combination(self, instance_type, instance_image, p flavor = provider.get_flavor(instance_type) if not flavor: self.log.warning("Flavor %s does not exist.", instance_type) - print("Available flavors:") - print("\n".join(provider.get_active_flavors())) + self.log.log(42, "Available flavors:") + self.log.log(42, "\n".join(provider.get_active_flavors())) return False type_max_disk_space = flavor["disk"] type_max_ram = flavor["ram"] diff --git a/bibigrid/openstack/openstack_provider.py b/bibigrid/openstack/openstack_provider.py index 15ec3ef9..ae97a2ec 100644 --- a/bibigrid/openstack/openstack_provider.py +++ b/bibigrid/openstack/openstack_provider.py @@ -121,6 +121,8 @@ def create_server(self, name, flavor, image, network, key_name=None, wait=True, except openstack.exceptions.BadRequestException as exc: if "is not active" in str(exc): raise ImageDeactivatedException() from exc + if "Invalid key_name provided" in str(exc): + raise ExecutionException() from exc raise ConnectionError() from exc except openstack.exceptions.SDKException as exc: raise ExecutionException() from exc diff --git a/tests/provider/test_provider.py b/tests/provider/test_provider.py index ef19b69d..45b0de7e 100644 --- a/tests/provider/test_provider.py +++ b/tests/provider/test_provider.py @@ -2,12 +2,14 @@ Module containing integration and unit tests regarding the provider """ +import logging import os import unittest -import bibigrid.core.utility.handler.configuration_handler as configurationHandler -import bibigrid.core.utility.handler.provider_handler as providerHandler import bibigrid.core.utility.paths.basic_path as bP +from bibigrid.core.utility.handler import configuration_handler +from bibigrid.core.utility.handler import provider_handler +from bibigrid.models.exceptions import ExecutionException SERVER_KEYS = {'id', 'name', 'flavor', 'image', 'block_device_mapping', 'location', 'volumes', 'has_config_drive', 'host_id', 'progress', 'disk_config', 'power_state', 'task_state', 'vm_state', 'launched_at', @@ -18,7 +20,12 @@ 'updated', 'user_id', 'tags', 'interface_ip', 'properties', 'hostId', 'config_drive', 'project_id', 'tenant_id', 'region', 'cloud', 'az', 'OS-DCF:diskConfig', 'OS-EXT-AZ:availability_zone', 'OS-SRV-USG:launched_at', 'OS-SRV-USG:terminated_at', 'OS-EXT-STS:task_state', 'OS-EXT-STS:vm_state', - 'OS-EXT-STS:power_state', 'os-extended-volumes:volumes_attached'} + 'OS-EXT-STS:power_state', 'os-extended-volumes:volumes_attached', 'OS-EXT-SRV-ATTR:ramdisk_id', + 'max_count', 'trusted_image_certificates', 'OS-SCH-HNT:scheduler_hints', + 'OS-EXT-SRV-ATTR:reservation_id', 'OS-EXT-SRV-ATTR:host', 'locked', 'host_status', + 'OS-EXT-SRV-ATTR:hypervisor_hostname', 'OS-EXT-SRV-ATTR:launch_index', + 'OS-EXT-SRV-ATTR:root_device_name', 'OS-EXT-SRV-ATTR:instance_name', 'OS-EXT-SRV-ATTR:user_data', + 'min_count', 'OS-EXT-SRV-ATTR:kernel_id', 'OS-EXT-SRV-ATTR:hostname'} FLOATING_IP_KEYS = {'attached', 'fixed_ip_address', 'floating_ip_address', 'id', 'location', 'network', 'port', 'router', 'status', 'created_at', 'updated_at', 'description', 'revision_number', 'properties', 'port_id', 'router_id', 'project_id', 'tenant_id', 'floating_network_id', 'port_details', @@ -39,7 +46,7 @@ 'is_protected', 'locations', 'properties', 'is_public', 'visibility', 'description', 'owner_specified.openstack.md5', 'owner_specified.openstack.object', 'owner_specified.openstack.sha256', 'os_hidden', 'os_hash_algo', 'os_hash_value', 'os_distro', 'os_version', 'schema', 'protected', - 'metadata', 'created', 'updated', 'minDisk', 'minRam'} + 'metadata', 'created', 'updated', 'minDisk', 'minRam', 'stores'} SNAPSHOT_KEYS = {'id', 'created_at', 'updated_at', 'name', 'description', 'volume_id', 'status', 'size', 'metadata', 'os-extended-snapshot-attributes:project_id', 'os-extended-snapshot-attributes:progress'} @@ -61,9 +68,10 @@ "MFbUTTukAiDf4jAgvJkg7ayE0MPapGpI/OhSK2gyN45VAzs2m7uykun87B491JagZ57qr16vt8vxGYpFCEe8QqAcrUszUPqyPrb0auA8bz" \ "jO8S41Kx8FfG+7eTu4dQ0= user" -CONFIGURATIONS = configurationHandler.read_configuration( - os.path.join(bP.ROOT_PATH, "tests/resources/infrastructure_cloud.yml")) -PROVIDERS = providerHandler.get_providers(CONFIGURATIONS) +CONFIGURATIONS = configuration_handler.read_configuration(logging, + os.path.join(bP.ROOT_PATH, + "tests/resources/infrastructure_cloud.yml")) +PROVIDERS = provider_handler.get_providers(CONFIGURATIONS, logging) class ProviderServer: @@ -76,7 +84,7 @@ def __init__(self, provider, name, configuration, key_name=None): self.name = name self.server_dict = provider.create_server(name=self.name, flavor=configuration["flavor"], image=configuration["image"], network=configuration["network"], - key_name=key_name) + key_name=key_name, security_groups=[]) def __enter__(self): return self.server_dict @@ -95,38 +103,41 @@ def test_get_free_resources(self): with self.subTest(provider.NAME): free_dict = provider.get_free_resources() self.assertEqual(FREE_RESOURCES_KEYS, set(free_dict.keys())) - for value in free_dict.values(): - self.assertLessEqual(0, value) + for key, value in free_dict.items(): + if key != "floating_ips": + self.assertLessEqual(0, value) def test_server_start_type_error(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): with self.assertRaises(TypeError): provider.create_server(name="name", flavor=configuration["flavor"], - network=configuration["network"]) + network=configuration["network"], security_groups=[]) with self.assertRaises(TypeError): - provider.create_server(name="name", image=configuration["image"], network=configuration["network"]) + provider.create_server(name="name", image=configuration["image"], network=configuration["network"], + security_groups=[]) with self.assertRaises(TypeError): provider.create_server(flavor=configuration["flavor"], image=configuration["image"], - network=configuration["network"]) + security_groups=[], network=configuration["network"]) with self.assertRaises(TypeError): - provider.create_server(name="name", flavor=configuration["flavor"], image=configuration["image"]) + provider.create_server(name="name", flavor=configuration["flavor"], image=configuration["image"], + security_groups=[]) def test_server_start_attribute_error(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): - with self.assertRaises(AttributeError): + with self.assertRaises(ExecutionException): provider.create_server(name="name", image="ERROR", flavor=configuration["flavor"], - network=configuration["network"]) - with self.assertRaises(AttributeError): + network=configuration["network"], security_groups=[]) + with self.assertRaises(ExecutionException): provider.create_server(name="name", flavor="ERROR", image=configuration["image"], - network=configuration["network"]) - with self.assertRaises(AttributeError): + network=configuration["network"], security_groups=[]) + with self.assertRaises(ExecutionException): provider.create_server(name="name", flavor=configuration["flavor"], image=configuration["image"], - network="ERROR") - with self.assertRaises(AttributeError): + network="ERROR", security_groups=[]) + with self.assertRaises(ExecutionException): provider.create_server(name="name", flavor=configuration["flavor"], image=configuration["image"], - network=configuration["network"], key_name="ERROR") + network=configuration["network"], key_name="ERROR", security_groups=[]) def test_create_keypair_create_delete_false_delete(self): for provider in PROVIDERS: @@ -141,8 +152,8 @@ def test_active_server_methods(self): with self.subTest(provider.NAME): with ProviderServer(provider, "bibigrid_test_server", configuration, "bibigrid_test_keypair") as provider_server: - floating_ip = provider.create_floating_ip(provider.get_external_network(configuration["network"]), - provider_server) + floating_ip = provider.attach_available_floating_ip( + provider.get_external_network(configuration["network"]), provider_server) server_list = provider.list_servers() self.assertEqual(SERVER_KEYS, set(provider_server.keys())) self.assertEqual("bibigrid_test_keypair", provider_server["key_name"]) @@ -209,7 +220,7 @@ def test_get_image_mismatch(self): with self.subTest(provider.NAME): self.assertIsNone(provider.get_image_by_id_or_name("NONE")) - if os.environ.get("OS_SNAPSHOT"): + if CONFIGURATIONS[0].get("snapshot_image"): def test_get_snapshot(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): diff --git a/tests/resources/infrastructure_cloud.yml b/tests/resources/infrastructure_cloud.yml deleted file mode 100644 index 028c0247..00000000 --- a/tests/resources/infrastructure_cloud.yml +++ /dev/null @@ -1,10 +0,0 @@ - # See https://cloud.denbi.de/wiki/Tutorials/BiBiGrid/ (after update) - -- infrastructure: openstack # former mode. - cloud: openstack # name of clouds.yaml entry - image: 2e61eb1b-dbd2-4ed8-b62b-5ee9fe0510e6 - flavor: de.NBI tiny - network: network0 - snapshot_image: test - -#- [next configurations] \ No newline at end of file diff --git a/tests/startup_tests.py b/tests/startup_tests.py index b3fee8a1..4a719ef2 100644 --- a/tests/startup_tests.py +++ b/tests/startup_tests.py @@ -39,8 +39,7 @@ def suppress_stdout(): unittest.TextTestRunner(verbosity=2).run(suite) # Provider-Test - ## Configuration needs to contain providers and infrastructures - if os.environ.get("OS_KEY_NAME"): - suite = unittest.TestLoader().discover("./provider", pattern='test_*.py') - with suppress_stdout(): - unittest.TextTestRunner(verbosity=2).run(suite) + # Configuration needs to contain providers and infrastructures + suite = unittest.TestLoader().discover("./provider", pattern='test_*.py') + with suppress_stdout(): + unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests/test_ValidateConfiguration.py b/tests/test_ValidateConfiguration.py deleted file mode 100644 index 96ffad26..00000000 --- a/tests/test_ValidateConfiguration.py +++ /dev/null @@ -1,321 +0,0 @@ -import os -from unittest import TestCase -from unittest.mock import Mock, patch, MagicMock, call - -from bibigrid.core.utility import validate_configuration - - -class TestValidateConfiguration(TestCase): - # pylint: disable=R0904 - def test_check_provider_data_count(self): - provider_data_1 = {"PROJECT_ID": "abcd", "PROJECT_NAME": "1234"} - provider_data_2 = {"PROJECT_ID": "9999", "PROJECT_NAME": "9999"} - vc = validate_configuration - self.assertTrue(vc.check_provider_data([provider_data_1, provider_data_2], 2)) - self.assertFalse(vc.check_provider_data([provider_data_1, provider_data_2], 3)) - self.assertTrue(vc.check_provider_data([], 0)) - - def test_check_provider_data_unique(self): - provider_data_1 = {"PROJECT_ID": "abcd", "PROJECT_NAME": "1234"} - provider_data_2 = {"PROJECT_ID": "9999", "PROJECT_NAME": "9999"} - vc = validate_configuration - self.assertTrue(vc.check_provider_data([provider_data_1, provider_data_2], 2)) - self.assertFalse(vc.check_provider_data([provider_data_1, provider_data_1], 2)) - self.assertTrue(vc.check_provider_data([], 0)) - - def test_check_master_vpn_worker_ordered(self): - master = {"masterInstance": "Value"} - vpn = {"vpnInstance": "Value"} - vpn_master = {} - vpn_master.update(master) - vpn_master.update(vpn) - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=[master]) - self.assertTrue(vc.check_master_vpn_worker()) - vc.configurations = [master, vpn] - self.assertTrue(vc.check_master_vpn_worker()) - vc.configurations = [vpn] - self.assertFalse(vc.check_master_vpn_worker()) - vc.configurations = [master, master] - self.assertFalse(vc.check_master_vpn_worker()) - - def test_check_master_vpn_worker_unique(self): - master = {"masterInstance": "Value"} - vpn = {"vpnInstance": "Value"} - vpn_master = {} - vpn_master.update(master) - vpn_master.update(vpn) - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=[vpn_master]) - self.assertFalse(vc.check_master_vpn_worker()) - vc.configurations = [master, vpn_master] - self.assertFalse(vc.check_master_vpn_worker()) - - def test_evaluate(self): - vc = validate_configuration - self.assertTrue(vc.evaluate("some", True)) - self.assertFalse(vc.evaluate("some", False)) - - def test_check_provider_connection(self): - mock = Mock() - mock.conn = False - vc = validate_configuration.ValidateConfiguration(providers=[mock], configurations=None) - self.assertFalse(vc.check_provider_connections()) - mock.conn = True - self.assertTrue(vc.check_provider_connections()) - - def test_check_instances_master(self): - vc = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"masterInstance": "42"}]) - with patch.object(vc, "check_instance") as mock: - vc.check_instances() - mock.assert_called_with("masterInstance", "42", "31") - - def test_check_instances_vpn(self): - vc = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"vpnInstance": "42"}]) - with patch.object(vc, "check_instance") as mock: - vc.check_instances() - mock.assert_called_with("vpnInstance", "42", "31") - - def test_check_instances_vpn_worker(self): - vc = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[ - {"masterInstance": "42", "workerInstances": ["42"]}]) - with patch.object(vc, "check_instance") as mock: - vc.check_instances() - mock.assert_called_with("workerInstance", "42", "31") - - def test_check_instances_vpn_master_missing(self): - vc = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{}]) - self.assertFalse(vc.check_instances()) - vc = validate_configuration.ValidateConfiguration(providers=["31"], - configurations=[{"workerInstances": ["42"]}]) - self.assertFalse(vc.check_instances()) - - def test_check_instances_vpn_master_count(self): - for i in range(3): - vc = validate_configuration.ValidateConfiguration(providers=["31"] * i, - configurations=[{"masterInstance": "42"}] * i) - # with patch.object(vc, "check_instance") as mock: - vc.check_instances() - self.assertTrue(vc.required_resources_dict["floating_ips"] == i) - - def test_check_instance_image_not_found(self): - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value=None) - self.assertFalse(vc.check_instance(None, {"count": 1, "image": 2}, provider)) - - def test_check_instance_image_not_active(self): - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value={"status": None}) - self.assertFalse(vc.check_instance(None, {"count": 1, "image": 2}, provider)) - - def test_check_instance_image_active_combination_call(self): - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value={"status": "active"}) - with patch.object(vc, "check_instance_type_image_combination") as mock: - vc.check_instance(42, {"count": 1, "image": 2, "type": 3}, provider) - mock.assert_called_with(3, {"status": "active"}, provider) - - def test_check_instance_image_not_found_count(self): - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value=None) - for i in range(1, 3): - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - vc.check_instance(None, {"count": i, "image": 2}, provider) - self.assertTrue(vc.required_resources_dict["instances"] == i) - - def test_check_instance_type_image_combination_has_enough_calls(self): - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - provider = MagicMock() - provider.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} - provider.get_image_by_id_or_name.return_value = {"minDisk": 22, "minRam": 12} - with patch.object(vc, "has_enough") as mock: - vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider) - self.assertEqual(call(42, 22, "Type de.NBI tiny", "disk space"), mock.call_args_list[0]) - self.assertEqual(call(32, 12, "Type de.NBI tiny", "ram"), mock.call_args_list[1]) - - def test_check_instance_type_image_combination_result(self): - provider = MagicMock() - provider.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} - provider.get_image_by_id_or_name.return_value = {"minDisk": 22, "minRam": 12} - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - with patch.object(vc, "has_enough") as mock: - mock.side_effect = [True, True, False, False, True, False, False, True] - # True True - self.assertTrue(vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) - # False False - self.assertFalse(vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) - # True False - self.assertFalse(vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) - # False True - self.assertFalse(vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) - - def test_check_instance_type_image_combination_count(self): - for i in range(3): - provider = MagicMock() - provider.get_flavor.return_value = {"disk": 42, "ram": i * 32, "vcpus": i * 10} - provider.get_image_by_id_or_name.return_value = {"minDisk": 22, "minRam": 12} - vc = validate_configuration.ValidateConfiguration(providers=None, configurations=None) - with patch.object(vc, "has_enough") as mock: - vc.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider) - self.assertEqual(32 * i, vc.required_resources_dict["total_ram"]) - self.assertEqual(10 * i, vc.required_resources_dict["total_cores"]) - mock.assert_called_with(32 * i, 12, 'Type de.NBI tiny', 'ram') - - def test_check_volumes_none(self): - vc = validate_configuration.ValidateConfiguration(providers=[42], configurations=[{}]) - self.assertTrue(vc.check_volumes()) - - def test_check_volumes_mismatch(self): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value=None) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"masterMounts": ["Test"]}]) - self.assertFalse(vc.check_volumes()) - - def test_check_volumes_match_snapshot(self): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value=None) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": 1}) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"masterMounts": ["Test"]}]) - self.assertTrue(vc.check_volumes()) - - def test_check_volumes_match_snapshot_count(self): - for i in range(3): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value=None) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": i}) - vc = validate_configuration.ValidateConfiguration(providers=[provider] * i, - configurations=[{"masterMounts": ["Test"] * i}]) - self.assertTrue(vc.check_volumes()) - self.assertTrue(vc.required_resources_dict["Volumes"] == i) - self.assertTrue(vc.required_resources_dict["VolumeGigabytes"] == i ** 2) - - def test_check_volumes_match_volume(self): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value={"size": 1}) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"masterMounts": ["Test"]}]) - self.assertTrue(vc.check_volumes()) - self.assertTrue(vc.required_resources_dict["Volumes"] == 0) - self.assertTrue(vc.required_resources_dict["VolumeGigabytes"] == 0) - - def test_check_network_none(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{}]) - self.assertFalse(vc.check_network()) - - def test_check_network_no_network(self): - provider = Mock() - provider.get_subnet_by_id_or_name = MagicMock(return_value="network") - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"subnet": "subnet_name"}]) - self.assertTrue(vc.check_network()) - provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") - - def test_check_network_no_network_mismatch_subnet(self): - provider = Mock() - provider.get_subnet_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"subnet": "subnet_name"}]) - self.assertFalse(vc.check_network()) - provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") - - def test_check_network_no_subnet_mismatch_network(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"network": "network_name"}]) - self.assertFalse(vc.check_network()) - provider.get_network_by_id_or_name.assert_called_with("network_name") - - def test_check_network_no_subnet(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value="network") - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"network": "network_name"}]) - self.assertTrue(vc.check_network()) - provider.get_network_by_id_or_name.assert_called_with("network_name") - - def test_check_network_subnet_network(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value="network") - provider.get_subnet_by_id_or_name = MagicMock(return_value="network") - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"network": "network_name"}]) - self.assertTrue(vc.check_network()) - provider.get_network_by_id_or_name.assert_called_with("network_name") - - def test_check_server_group_none(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{}]) - self.assertTrue(vc.check_server_group()) - - def test_check_server_group_mismatch(self): - provider = Mock() - provider.get_server_group_by_id_or_name = MagicMock(return_value=None) - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"serverGroup": "GroupName"}]) - self.assertFalse(vc.check_server_group()) - provider.get_server_group_by_id_or_name.assert_called_with("GroupName") - - def test_check_server_group_match(self): - provider = Mock() - provider.get_server_group_by_id_or_name = MagicMock(return_value="Group") - vc = validate_configuration.ValidateConfiguration(providers=[provider], - configurations=[{"serverGroup": "GroupName"}]) - self.assertTrue(vc.check_server_group()) - provider.get_server_group_by_id_or_name.assert_called_with("GroupName") - - def test_check_quotas_true(self): - provider = MagicMock() - provider.cloud_specification = {"auth": {"project_name": "name"}, "identifier": "identifier"} - test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, - 'Volumes': 42, 'VolumeGigabytes': 42, 'Snapshots': 42, 'Backups': 42, 'BackupGigabytes': 42} - provider.get_free_resources.return_value = test_dict - vc = validate_configuration.ValidateConfiguration(providers=[provider], configurations=None) - with patch.object(vc, "has_enough") as mock: - mock.side_effect = [True] * len(test_dict) - self.assertTrue(vc.check_quotas()) - provider.get_free_resources.assert_called() - for key in vc.required_resources_dict.keys(): - self.assertTrue(call(test_dict[key], vc.required_resources_dict[key], - "Project identifier", key) in mock.call_args_list) - - def test_check_quotas_false(self): - provider = MagicMock() - test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, - 'Volumes': 42, 'VolumeGigabytes': 42, 'Snapshots': 42, 'Backups': 42, 'BackupGigabytes': 42} - provider.get_free_resources.return_value = test_dict - os.environ['OS_PROJECT_NAME'] = "name" - vc = validate_configuration.ValidateConfiguration(providers=[provider], configurations=None) - with patch.object(vc, "has_enough") as mock: - mock.side_effect = [True] * (len(test_dict) - 1) + [False] - self.assertFalse(vc.check_quotas()) - provider.get_free_resources.assert_called() - mock.assert_called() - - def test_has_enough_lower(self): - vc = validate_configuration - self.assertTrue(vc.has_enough(2, 1, "", "")) - - def test_has_enough_equal(self): - vc = validate_configuration - self.assertTrue(vc.has_enough(2, 2, "", "")) - - def test_has_enough_higher(self): - vc = validate_configuration - self.assertFalse(vc.has_enough(1, 2, "", "")) diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index db22411d..dbaef9a9 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -1,313 +1,365 @@ """ -Tests for ansibleConfigurator +Tests for ansible_configurator """ from unittest import TestCase from unittest.mock import MagicMock, Mock, patch, call, mock_open, ANY -import bibigrid.core.utility.ansible_configurator as ansibleConfigurator import bibigrid.core.utility.paths.ansible_resources_path as aRP -import bibigrid.core.utility.yaml_dumper as yamlDumper +from bibigrid.core.utility.yaml_dumper import NoAliasSafeDumper +from bibigrid.core import startup +from bibigrid.core.utility import ansible_configurator + class TestAnsibleConfigurator(TestCase): """ Test ansible configurator test class """ + # pylint: disable=R0904 def test_generate_site_file_yaml_empty(self): - site_yaml = [{'hosts': 'master', "become": "yes", - "vars_files": ansibleConfigurator.VARS_FILES, "roles": ["common", "master"]}, - {"hosts": "worker", "become": "yes", "vars_files": - ansibleConfigurator.VARS_FILES, "roles": ["common", "worker"]}, - {"hosts": "vpngtw", "become": "yes", "vars_files": - ansibleConfigurator.VARS_FILES, "roles": ["common", "vpngtw"]}] - self.assertEqual(site_yaml, ansibleConfigurator.generate_site_file_yaml([])) + site_yaml = [{'become': 'yes', 'hosts': 'master', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + {'become': 'yes', 'hosts': 'vpngtw', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + {'become': 'yes', 'hosts': 'workers', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] + self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml([])) def test_generate_site_file_yaml_role(self): custom_roles = [{"file": "file", "hosts": "hosts", "name": "name", "vars": "vars", "vars_file": "varsFile"}] - vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] - site_yaml = [{'hosts': 'master', "become": "yes", - "vars_files": vars_files, "roles": ["common", "master", "additional/name"]}, - {"hosts": "worker", "become": "yes", "vars_files": - vars_files, "roles": ["common", "worker", "additional/name"]}, - {"hosts": "vpngtw", "become": "yes", "vars_files": - vars_files, "roles": ["common", "vpngtw", "additional/name"]}] - self.assertEqual(site_yaml, ansibleConfigurator.generate_site_file_yaml(custom_roles)) - - def test_generate_instances(self): - cluster_dict = object() - self.assertEqual(cluster_dict, ansibleConfigurator.generate_instances_yaml(cluster_dict)) + # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] + site_yaml = [{'become': 'yes', 'hosts': 'master', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}, 'additional/name'], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}, + {'become': 'yes', 'hosts': 'vpngtw', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}, 'additional/name'], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}, + {'become': 'yes', 'hosts': 'workers', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}, 'additional/name'], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}] + self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(custom_roles)) def test_generate_common_configuration_false(self): - cidrs = 42 - configuration = {} - common_configuration_yaml = {"cluster_cidrs": cidrs, - "local_fs": False, - "local_dns_lookup": False, - "use_master_as_compute": True, - "enable_slurm": False, - "enable_zabbix": False, - "enable_nfs": False, - "enable_ide": False - } - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + configuration = [{}] + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, + 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, + 'local_dns_lookup': False, 'local_fs': False, 'slurm': True, + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, + 'use_master_as_compute': True} + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + # munge key is randomly generated + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_true(self): - cidrs = 42 - configuration = {elem: "true" for elem in ["localFS", "localDNSlookup", "useMasterAsCompute", "slurm", - "zabbix", "ide"]} - common_configuration_yaml = {elem: "true" for elem in ["local_fs", "local_dns_lookup", "use_master_as_compute", - "enable_slurm", "enable_zabbix", "enable_ide"]} - common_configuration_yaml["cluster_cidrs"] = cidrs - common_configuration_yaml["enable_nfs"] = False - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + configuration = [ + {elem: "True" for elem in ["localFS", "localDNSlookup", "useMasterAsCompute", "slurm", "zabbix", "ide"]}] + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'True', + 'enable_nfs': False, 'enable_slurm': 'True', 'enable_zabbix': 'True', + 'ide_conf': {'build': False, 'ide': False, 'port_end': 8383, 'port_start': 8181, + 'workspace': '${HOME}'}, 'local_dns_lookup': 'True', + 'local_fs': 'True', 'slurm': 'True', + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, + 'use_master_as_compute': 'True', + 'zabbix_conf': {'admin_password': 'bibigrid', 'db': 'zabbix', + 'db_password': 'zabbix', 'db_user': 'zabbix', + 'server_name': 'bibigrid', 'timezone': 'Europe/Berlin'}} + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_nfs_shares(self): - cidrs = 42 - configuration = {"nfs": "True", "nfsShares": ["/vil/mil"]} - common_configuration_yaml = {'cluster_cidrs': 42, - 'enable_ide': False, - 'enable_nfs': 'True', - 'enable_slurm': False, - 'enable_zabbix': False, - 'ext_nfs_mounts': [], - 'local_dns_lookup': False, - 'local_fs': False, - 'nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}, - {'dst': '/vol/spool', 'src': '/vol/spool'}], + configuration = [{"nfs": "True", "nfsShares": ["/vil/mil"]}] + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, + 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, + 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, + 'nfs_mounts': [{'dst': '//vil/mil', 'src': '//vil/mil'}, + {'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_nfs(self): - cidrs = 42 - configuration = {"nfs": "True"} - common_configuration_yaml = {'cluster_cidrs': 42, - 'enable_ide': False, - 'enable_nfs': 'True', - 'enable_slurm': False, - 'enable_zabbix': False, - 'ext_nfs_mounts': [], - 'local_dns_lookup': False, - 'local_fs': False, - 'nfs_mounts': [{'dst': '/vol/spool', 'src': '/vol/spool'}], + configuration = [{"nfs": "True"}] + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, + 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, + 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, + 'nfs_mounts': [{'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_ext_nfs_shares(self): - cidrs = 42 - configuration = {"nfs": "True", "extNfsShares": ["/vil/mil"]} - common_configuration_yaml = {'cluster_cidrs': 42, - 'enable_ide': False, - 'enable_nfs': 'True', - 'enable_slurm': False, - 'enable_zabbix': False, + configuration = [{"nfs": "True", "extNfsShares": ["/vil/mil"]}] + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, + 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}], - 'local_dns_lookup': False, - 'local_fs': False, - 'nfs_mounts': [{'dst': '/vol/spool', 'src': '/vol/spool'}], - 'use_master_as_compute': True} - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) + 'local_dns_lookup': False, 'local_fs': False, + 'nfs_mounts': [{'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'YryJVnqgg24Ksf8zXQtbct3nuXrMSi9N'}, + 'ssh_user': ssh_user, 'use_master_as_compute': True} + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_ide(self): - cidrs = 42 - configuration = {"ide": "Some1", "ideConf": "Some2"} - common_configuration_yaml = {'cluster_cidrs': 42, - 'enable_ide': "Some1", - 'enable_nfs': False, - 'enable_slurm': False, + configuration = [{"ide": "Some1", "ideConf": {"key1": "Some2"}}] + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, + 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], + 'enable_ide': 'Some1', 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, - 'ide_conf': 'Some2', - 'local_dns_lookup': False, - 'local_fs': False, - 'use_master_as_compute': True} - self.assertEqual(common_configuration_yaml, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)) - - @patch("bibigrid.core.utility.ansibleConfigurator.get_ansible_roles") - def test_generate_common_configuration_ansible_roles_mock(self, mock_ansible_roles): - cidrs = 42 + 'ide_conf': {'build': False, 'ide': False, 'key1': 'Some2', 'port_end': 8383, + 'port_start': 8181, 'workspace': '${HOME}'}, + 'local_dns_lookup': False, 'local_fs': False, 'slurm': True, + 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', + 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, + 'TreeWidth': 128}, + 'munge_key': 'b7nks3Ur3kanyPAEBxfSC9ypfSHFnWJL'}, + 'ssh_user': ssh_user, 'use_master_as_compute': True} + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + self.assertEqual(common_configuration_yaml, generated_common_configuration) + + def test_generate_common_configuration_ansible_roles_mock(self): + cidrs = "42" ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] - mock_ansible_roles.return_value = 21 - configuration = {"ansibleRoles": ansible_roles} - self.assertEqual(21, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)["ansible_roles"]) - mock_ansible_roles.assert_called_with(ansible_roles) - - @patch("bibigrid.core.utility.ansibleConfigurator.get_ansible_galaxy_roles") - def test_generate_common_configuration_ansible_galaxy_roles(self, mock_galaxy_roles): - cidrs = 42 + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" + configuration = [{"ansibleRoles": ansible_roles}] + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + self.assertEqual(ansible_roles, generated_common_configuration["ansible_roles"]) + + def test_generate_common_configuration_ansible_galaxy_roles(self): + cidrs = "42" + cluster_id = "21" + default_user = "ubuntu" + ssh_user = "test" galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file"]}] - configuration = {"ansibleGalaxyRoles": galaxy_roles} - mock_galaxy_roles.return_value = 21 - self.assertEqual(21, - ansibleConfigurator.generate_common_configuration_yaml(cidrs, configuration)[ - "ansible_galaxy_roles"]) - mock_galaxy_roles.assert_called_with(galaxy_roles) - - @patch("bibigrid.core.utility.ansibleConfigurator.to_instance_host_dict") + configuration = [{"ansibleGalaxyRoles": galaxy_roles}] + generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, + cluster_id, ssh_user, + default_user, + startup.LOG) + self.assertEqual(galaxy_roles, generated_common_configuration["ansible_galaxy_roles"]) + + @patch("bibigrid.core.utility.ansible_configurator.to_instance_host_dict") def test_generate_ansible_hosts(self, mock_instance_host_dict): - mock_instance_host_dict.side_effect = [0, 1, 2] - cluster_dict = {"workers": [{"private_v4": 21}], "vpngtws": [{"private_v4": 32}]} - expected = {'master': {'hosts': 0}, 'worker': {'hosts': {21: 1, 32: 2}}} - self.assertEqual(expected, ansibleConfigurator.generate_ansible_hosts_yaml(42, cluster_dict)) + cluster_id = "21" + mock_instance_host_dict.side_effect = [0, 1, 2, 4, 5, {}] + configuration = [{'masterInstance': {'type': 'mini', 'image': 'Ubuntu'}, + 'workerInstances': [{'type': 'tiny', 'image': 'Ubuntu', 'count': 2}, + {'type': 'default', 'image': 'Ubuntu', 'count': 1}]}, + {'vpnInstance': {'type': 'mini', 'image': 'Ubuntu'}, 'workerInstances': [ + {'type': 'tiny', 'image': 'Ubuntu', 'count': 2, 'features': ['holdsinformation']}, + {'type': 'small', 'image': 'Ubuntu', 'count': 2}], 'floating_ip': "42"}] + expected = {'vpn': {'children': {'master': {'hosts': {'localhost': 0}}, + 'vpngtw': {'hosts': {'bibigrid-vpngtw-21-0': {'ansible_host': '42'}}}}, + 'hosts': {}}, 'workers': { + 'children': {'bibigrid_worker_21_0_1': {'hosts': {'bibigrid-worker-21-[0:1]': 1}}, + 'bibigrid_worker_21_2_2': {'hosts': {'bibigrid-worker-21-[2:2]': 2}}, + 'bibigrid_worker_21_3_4': {'hosts': {'bibigrid-worker-21-[3:4]': 4}}, + 'bibigrid_worker_21_5_6': {'hosts': {'bibigrid-worker-21-[5:6]': 5}}}, 'hosts': {}}} + self.assertEqual(expected, + ansible_configurator.generate_ansible_hosts_yaml(42, configuration, cluster_id, startup.LOG)) call_list = mock_instance_host_dict.call_args_list self.assertEqual(call(42), call_list[0]) - self.assertEqual(call(42, ip=21, local=False), call_list[1]) - self.assertEqual(call(42, ip=32, local=False), call_list[2]) + for call_happened in call_list[1:]: + self.assertEqual(call(42, ip=""), call_happened) def test_to_instance_host_local(self): - ip = 42 + ip_address = 42 ssh_user = 21 - local = {"ip": ip, "ansible_connection": "local", - "ansible_python_interpreter": ansibleConfigurator.PYTHON_INTERPRETER, - "ansible_user": ssh_user} - self.assertEqual(local, ansibleConfigurator.to_instance_host_dict(21, 42, True)) + local = {"ip": ip_address, "ansible_connection": "ssh", + "ansible_python_interpreter": ansible_configurator.PYTHON_INTERPRETER, "ansible_user": ssh_user} + self.assertEqual(local, ansible_configurator.to_instance_host_dict(21, 42)) def test_to_instance_host_ssh(self): - ip = 42 + ip_address = 42 ssh_user = 21 - ssh = {"ip": ip, "ansible_connection": "ssh", - "ansible_python_interpreter": ansibleConfigurator.PYTHON_INTERPRETER, - "ansible_user": ssh_user} - self.assertEqual(ssh, ansibleConfigurator.to_instance_host_dict(21, 42, False)) + ssh = {"ip": ip_address, "ansible_connection": "ssh", + "ansible_python_interpreter": ansible_configurator.PYTHON_INTERPRETER, "ansible_user": ssh_user} + self.assertEqual(ssh, ansible_configurator.to_instance_host_dict(21, 42)) - def test_get_cidrs_single(self): + def test_get_cidrs(self): provider = Mock() provider.get_subnet_by_id_or_name.return_value = {"cidr": 42} - configuration = {"subnet": 21} - expected = [{'provider': 'Mock', 'provider_cidrs': [42]}] - self.assertEqual(expected, ansibleConfigurator.get_cidrs([configuration], [provider])) - provider.get_subnet_by_id_or_name.assert_called_with(21) - - def test_get_cidrs_list(self): - provider = Mock() - provider.get_subnet_by_id_or_name.return_value = {"cidr": 42} - configuration = {"subnet": [21, 22]} - expected = [{'provider': 'Mock', 'provider_cidrs': [42, 42]}] - self.assertEqual(expected, ansibleConfigurator.get_cidrs([configuration], [provider])) - call_list = provider.get_subnet_by_id_or_name.call_args_list - self.assertEqual(call(21), call_list[0]) - self.assertEqual(call(22), call_list[1]) + configuration = [{"subnet_cidrs": [21], "cloud_identifier": 13}] + expected = [{'cloud_identifier': 13, 'provider_cidrs': [21]}] + self.assertEqual(expected, ansible_configurator.get_cidrs(configuration)) def test_get_ansible_roles_empty(self): - self.assertEqual([], ansibleConfigurator.get_ansible_roles([])) + self.assertEqual([], ansible_configurator.get_ansible_roles([], startup.LOG)) def test_get_ansible_roles(self): ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] - self.assertEqual(ansible_roles, ansibleConfigurator.get_ansible_roles(ansible_roles)) + self.assertEqual(ansible_roles, ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) def test_get_ansible_roles_add(self): ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] ansible_roles_add = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file", "additional"]}] - self.assertEqual(ansible_roles, ansibleConfigurator.get_ansible_roles(ansible_roles_add)) + self.assertEqual(ansible_roles, ansible_configurator.get_ansible_roles(ansible_roles_add, startup.LOG)) def test_get_ansible_roles_minus(self): ansible_roles = [{elem: elem for elem in ["file", "hosts"]}] - self.assertEqual(ansible_roles, ansibleConfigurator.get_ansible_roles(ansible_roles)) + self.assertEqual(ansible_roles, ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) - @patch("logging.warning") - def test_get_ansible_roles_mismatch_hosts(self, mock_log): + def test_get_ansible_roles_mismatch_hosts(self): ansible_roles = [{"file": "file"}] - self.assertEqual([], ansibleConfigurator.get_ansible_roles(ansible_roles)) - mock_log.assert_called() + self.assertEqual([], ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) - @patch("logging.warning") - def test_get_ansible_roles_mismatch_file(self, mock_log): + def test_get_ansible_roles_mismatch_file(self): ansible_roles = [{"hosts": "hosts"}] - self.assertEqual([], ansibleConfigurator.get_ansible_roles(ansible_roles)) - mock_log.assert_called() + self.assertEqual([], ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) def test_get_ansible_galaxy_roles_empty(self): - self.assertEqual([], ansibleConfigurator.get_ansible_galaxy_roles([])) + self.assertEqual([], ansible_configurator.get_ansible_galaxy_roles([], startup.LOG)) def test_get_ansible_galaxy_roles(self): galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file"]}] - self.assertEqual(galaxy_roles, ansibleConfigurator.get_ansible_galaxy_roles(galaxy_roles)) + self.assertEqual(galaxy_roles, ansible_configurator.get_ansible_galaxy_roles(galaxy_roles, startup.LOG)) def test_get_ansible_galaxy_roles_add(self): galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file"]}] galaxy_roles_add = [ {elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file", "additional"]}] - self.assertEqual(galaxy_roles, ansibleConfigurator.get_ansible_galaxy_roles(galaxy_roles_add)) + self.assertEqual(galaxy_roles, ansible_configurator.get_ansible_galaxy_roles(galaxy_roles_add, startup.LOG)) def test_get_ansible_galaxy_roles_minus(self): galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "vars", "vars_file"]}] - self.assertEqual(galaxy_roles, ansibleConfigurator.get_ansible_galaxy_roles(galaxy_roles)) + self.assertEqual(galaxy_roles, ansible_configurator.get_ansible_galaxy_roles(galaxy_roles, startup.LOG)) - @patch("logging.warning") - def test_get_ansible_galaxy_roles_mismatch(self, mock_log): + def test_get_ansible_galaxy_roles_mismatch(self): galaxy_roles = [{elem: elem for elem in ["hosts", "name", "vars", "vars_file"]}] - self.assertEqual([], ansibleConfigurator.get_ansible_galaxy_roles(galaxy_roles)) - mock_log.assert_called() - - def test_generate_login_file(self): - login_yaml = {"default_user": 99, - "ssh_user": 21, - "munge_key": 32} - self.assertEqual(login_yaml, ansibleConfigurator.generate_login_file_yaml(21, 32, 99)) + self.assertEqual([], ansible_configurator.get_ansible_galaxy_roles(galaxy_roles, startup.LOG)) def test_generate_worker_specification_file_yaml(self): configuration = [{"workerInstances": [{elem: elem for elem in ["type", "image"]}], "network": [32]}] expected = [{'IMAGE': 'image', 'NETWORK': [32], 'TYPE': 'type'}] - self.assertEqual(expected, ansibleConfigurator.generate_worker_specification_file_yaml(configuration)) + self.assertEqual(expected, + ansible_configurator.generate_worker_specification_file_yaml(configuration, startup.LOG)) def test_generate_worker_specification_file_yaml_empty(self): configuration = [{}] expected = [] - self.assertEqual(expected, ansibleConfigurator.generate_worker_specification_file_yaml(configuration)) + self.assertEqual(expected, + ansible_configurator.generate_worker_specification_file_yaml(configuration, startup.LOG)) @patch("yaml.dump") def test_write_yaml_no_alias(self, mock_yaml): with patch('builtins.open', mock_open()) as output_mock: - ansibleConfigurator.write_yaml("here", {"some": "yaml"}, False) - output_mock.assert_called_once_with("here", "w+") - mock_yaml.assert_called_with(data={"some": "yaml"}, stream=ANY, Dumper=yamlDumper.NoAliasSafeDumper) + ansible_configurator.write_yaml("here", {"some": "yaml"}, startup.LOG, False) + output_mock.assert_called_once_with("here", mode="w+", encoding="UTF-8") + mock_yaml.assert_called_with(data={"some": "yaml"}, stream=ANY, Dumper=NoAliasSafeDumper) @patch("yaml.safe_dump") def test_write_yaml_alias(self, mock_yaml): with patch('builtins.open', mock_open()) as output_mock: - ansibleConfigurator.write_yaml("here", {"some": "yaml"}, True) - output_mock.assert_called_once_with("here", "w+") + ansible_configurator.write_yaml("here", {"some": "yaml"}, startup.LOG, True) + output_mock.assert_called_once_with("here", mode="w+", encoding="UTF-8") mock_yaml.assert_called_with(data={"some": "yaml"}, stream=ANY) - @patch("bibigrid.core.utility.id_generation.generate_munge_key") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_worker_specification_file_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_login_file_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_common_configuration_yaml") + @patch("bibigrid.core.utility.ansible_configurator.write_host_and_group_vars") + @patch("bibigrid.core.utility.ansible_configurator.generate_worker_specification_file_yaml") + @patch("bibigrid.core.utility.ansible_configurator.generate_common_configuration_yaml") @patch("bibigrid.core.actions.list_clusters.dict_clusters") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_instances_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_ansible_hosts_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.get_ansible_roles") - @patch("bibigrid.core.utility.ansibleConfigurator.generate_site_file_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.write_yaml") - @patch("bibigrid.core.utility.ansibleConfigurator.get_cidrs") - def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_roles, mock_hosts, - mock_instances, mock_list, mock_common, mock_login, mock_worker, mock_munge): - mock_munge.return_value = 420 + @patch("bibigrid.core.utility.ansible_configurator.generate_ansible_hosts_yaml") + @patch("bibigrid.core.utility.ansible_configurator.get_ansible_roles") + @patch("bibigrid.core.utility.ansible_configurator.generate_site_file_yaml") + @patch("bibigrid.core.utility.ansible_configurator.write_yaml") + @patch("bibigrid.core.utility.ansible_configurator.get_cidrs") + def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_roles, mock_hosts, mock_list, + mock_common, mock_worker, mock_write): mock_cidrs.return_value = 421 mock_list.return_value = {2: 422} mock_roles.return_value = 423 provider = MagicMock() - provider.cloud_specification = {"auth": {"username":"Tom"}} - ansibleConfigurator.configure_ansible_yaml([provider], [{"sshUser": 42, "ansibleRoles": 21}], 2) - mock_munge.assert_called() - mock_worker.assert_called_with([{"sshUser": 42, "ansibleRoles": 21}]) - mock_common.assert_called_with(421, configuration={"sshUser": 42, "ansibleRoles": 21}) - mock_login.assert_called_with(ssh_user=42, munge_key=420, default_user="Tom") - mock_list.assert_called_with([provider]) - mock_instances.assert_called_with(422) - mock_hosts.assert_called_with(42, 422) + provider.cloud_specification = {"auth": {"username": "Default"}} + configuration = [{"sshUser": 42, "ansibleRoles": 21}] + cluster_id = 2 + ansible_configurator.configure_ansible_yaml([provider], configuration, cluster_id, startup.LOG) + mock_worker.assert_called_with(configuration, startup.LOG) + mock_common.assert_called_with(cidrs=421, configurations=configuration, cluster_id=cluster_id, ssh_user=42, + default_user="Default", log=startup.LOG) + mock_hosts.assert_called_with(42, configuration, cluster_id, startup.LOG) mock_site.assert_called_with(423) - mock_roles.assert_called_with(21) - mock_cidrs.assert_called_with([{'sshUser': 42, 'ansibleRoles': 21}], [provider]) - expected = [call(aRP.WORKER_SPECIFICATION_FILE, mock_worker(), False), - call(aRP.COMMONS_LOGIN_FILE, mock_login(), False), - call(aRP.COMMONS_CONFIG_FILE, mock_common(), False), - call(aRP.COMMONS_INSTANCES_FILE, mock_instances(), False), - call(aRP.HOSTS_CONFIG_FILE, mock_hosts(), False), - call(aRP.SITE_CONFIG_FILE, mock_site(), False)] + mock_roles.assert_called_with(21, startup.LOG) + mock_cidrs.assert_called_with(configuration) + mock_write.assert_called() + expected = [call(aRP.WORKER_SPECIFICATION_FILE, mock_worker(), startup.LOG, False), + call(aRP.COMMONS_CONFIG_FILE, mock_common(), startup.LOG, False), + call(aRP.HOSTS_CONFIG_FILE, mock_hosts(), startup.LOG, False), + call(aRP.SITE_CONFIG_FILE, mock_site(), startup.LOG, False)] self.assertEqual(expected, mock_yaml.call_args_list) diff --git a/tests/test_check.py b/tests/test_check.py index b3f04f31..fcaa5e11 100644 --- a/tests/test_check.py +++ b/tests/test_check.py @@ -1,34 +1,20 @@ +""" +Module to test check +""" from unittest import TestCase from unittest.mock import patch +from bibigrid.core import startup from bibigrid.core.actions import check -from bibigrid.core.utility import validate_configuration class TestCheck(TestCase): - - @patch("logging.info") - def test_check_true(self, mock_log): - providers = [42] - configurations = [32] - with patch.object(validate_configuration.ValidateConfiguration, "validate", return_value=True) as mock_validate: - self.assertFalse(check.check(configurations, providers)) - mock_validate.assert_called() - mock_log.assert_called_with("Total check returned True.") - - @patch("logging.info") - def test_check_false(self, mock_log): - providers = [42] - configurations = [32] - with patch.object(validate_configuration.ValidateConfiguration, "validate", - return_value=False) as mock_validate: - self.assertFalse(check.check(configurations, providers)) - mock_validate.assert_called() - mock_log.assert_called_with("Total check returned False.") - + """ + Class to test check + """ @patch("bibigrid.core.utility.validate_configuration.ValidateConfiguration") - def test_check_init(self, mock_validator): + def test_check_true(self, mock_validator): providers = [42] configurations = [32] - self.assertFalse(check.check(configurations, providers)) - mock_validator.assert_called_with(configurations, providers) + self.assertFalse(check.check(configurations, providers, startup.LOG)) + mock_validator.assert_called_once_with(configurations, providers, startup.LOG) diff --git a/tests/test_configurationHandler.py b/tests/test_configurationHandler.py deleted file mode 100644 index f62161af..00000000 --- a/tests/test_configurationHandler.py +++ /dev/null @@ -1,197 +0,0 @@ -import os -from unittest import TestCase -from unittest.mock import patch, mock_open, MagicMock - -import bibigrid.core.utility.handler.configuration_handler as configurationHandler - - -class TestConfigurationHandler(TestCase): - # pylint: disable=R0904 - def test_get_list_by_name_none(self): - configurations = [{}, {}] - self.assertEqual([None, None], configurationHandler.get_list_by_key(configurations, "key1")) - self.assertEqual([], configurationHandler.get_list_by_key(configurations, "key1", False)) - - def test_get_list_by_name_empty(self): - configurations = [{"key1": "value1", "key2": "value1"}, {"key1": "value2"}] - self.assertEqual(["value1", "value2"], configurationHandler.get_list_by_key(configurations, "key1")) - self.assertEqual(["value1", "value2"], configurationHandler.get_list_by_key(configurations, "key1", False)) - self.assertEqual(["value1", None], configurationHandler.get_list_by_key(configurations, "key2")) - self.assertEqual(["value1"], configurationHandler.get_list_by_key(configurations, "key2", False)) - - @patch("os.path.isfile") - def test_read_configuration_no_file(self, mock_isfile): - mock_isfile.return_value = False - test = MagicMock() - configuration = "Test: 42" - expected_result = None - with patch("builtins.open", mock_open(test, read_data=configuration)): - result = configurationHandler.read_configuration("path") - mock_isfile.assert_called_with("path") - test.assert_not_called() - self.assertEqual(expected_result, result) - - @patch("os.path.isfile") - def test_read_configuration_file(self, mock_isfile): - mock_isfile.return_value = True - opener = MagicMock() - configuration = "Test: 42" - expected_result = {"Test": 42} - with patch("builtins.open", mock_open(opener, read_data=configuration)): - result = configurationHandler.read_configuration("path") - mock_isfile.assert_called_with("path") - opener.assert_called_with("path", "r") - self.assertEqual(expected_result, result) - - @patch("os.path.isfile") - def test_read_configuration_file_yaml_exception(self, mock_isfile): - mock_isfile.return_value = True - opener = MagicMock() - configuration = "]unbalanced brackets[" - expected_result = None - with patch("builtins.open", mock_open(opener, read_data=configuration)): - result = configurationHandler.read_configuration("path") - mock_isfile.assert_called_with("path") - opener.assert_called_with("path", "r") - self.assertEqual(expected_result, result) - - def test_find_file_in_folders_not_found_no_folder(self): - expected_result = None - result = configurationHandler.find_file_in_folders("true_file", []) - self.assertEqual(expected_result, result) - - def test_find_file_in_folders_not_found_no_file(self): - expected_result = None - with patch("os.path.isfile") as mock_isfile: - mock_isfile.return_value = False - result = configurationHandler.find_file_in_folders("false_file", ["or_false_folder"]) - self.assertEqual(expected_result, result) - mock_isfile.called_with(os.path.expanduser(os.path.join("or_false_folder", "false_file"))) - - @patch("os.path.isfile") - @patch("bibigrid.core.utility.handler.configurationHandler.read_configuration") - def test_find_file_in_folders(self, mock_read_configuration, mock_isfile): - expected_result = 42 - mock_isfile.return_value(True) - mock_read_configuration.return_value = 42 - result = configurationHandler.find_file_in_folders("true_file", ["true_folder"]) - self.assertEqual(expected_result, result) - mock_read_configuration.assert_called_with(os.path.expanduser(os.path.join("true_folder", "true_file"))) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files_none(self, mock_ffif): - mock_ffif.return_value = None - expected_result = None, None - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files_no_clouds_yaml(self, mock_ffif): - mock_ffif.side_effect = [None, {configurationHandler.CLOUD_PUBLIC_ROOT_KEY: 42}] - expected_result = None, 42 - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files_no_public_clouds_yaml(self, mock_ffif): - mock_ffif.side_effect = [{configurationHandler.CLOUD_ROOT_KEY: 42}, None] - expected_result = 42, None - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files_no_root_key_public(self, mock_ffif): - mock_ffif.side_effect = [{configurationHandler.CLOUD_ROOT_KEY: 42}, {"name": 42}] - expected_result = 42, None - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files_no_root_key_cloud(self, mock_ffif): - mock_ffif.side_effect = [{"name": 42}, {configurationHandler.CLOUD_PUBLIC_ROOT_KEY: 42}] - expected_result = None, 42 - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - - @patch("bibigrid.core.utility.handler.configurationHandler.find_file_in_folders") - def test_get_cloud_files(self, mock_ffif): - mock_ffif.side_effect = [{configurationHandler.CLOUD_ROOT_KEY: 22}, - {configurationHandler.CLOUD_PUBLIC_ROOT_KEY: 42}] - expected_result = 22, 42 - result = configurationHandler.get_clouds_files() - self.assertEqual(expected_result, result) - mock_ffif.assert_called_with(configurationHandler.CLOUDS_PUBLIC_YAML, configurationHandler.CLOUDS_YAML_PATHS) - - @patch("bibigrid.core.utility.handler.configurationHandler.get_cloud_specification") - @patch("bibigrid.core.utility.handler.configurationHandler.get_clouds_files") - def test_get_cloud_specifications_none(self, mock_get_clouds_files, mock_get_clouds_specification): - mock_get_clouds_files.return_value = None, None - expected_result = [] - result = configurationHandler.get_cloud_specifications([{"cloud": 42}]) - self.assertEqual(expected_result, result) - mock_get_clouds_specification.assert_not_called() - mock_get_clouds_files.assert_called() - - @patch("bibigrid.core.utility.handler.configurationHandler.get_cloud_specification") - @patch("bibigrid.core.utility.handler.configurationHandler.get_clouds_files") - def test_get_cloud_specifications_no_cloud_configuration_key(self, mock_get_clouds_files, - mock_get_clouds_specification): - mock_get_clouds_files.return_value = {"Some"}, {"Some"} - expected_result = [] - result = configurationHandler.get_cloud_specifications([{"no_cloud": 42}]) - self.assertEqual(expected_result, result) - mock_get_clouds_specification.assert_not_called() - mock_get_clouds_files.assert_called() - - @patch("bibigrid.core.utility.handler.configurationHandler.get_cloud_specification") - @patch("bibigrid.core.utility.handler.configurationHandler.get_clouds_files") - def test_get_cloud_specifications_cloud(self, mock_get_clouds_files, mock_get_clouds_specification): - mock_get_clouds_files.return_value = {"1"}, {"2"} - mock_get_clouds_specification.return_value = 21 - expected_result = [21] - result = configurationHandler.get_cloud_specifications([{"cloud": 42}]) - self.assertEqual(expected_result, result) - mock_get_clouds_specification.assert_called_with(42, {"1"}, {"2"}) - mock_get_clouds_files.assert_called() - - @patch("bibigrid.core.utility.handler.configurationHandler.get_cloud_specification") - @patch("bibigrid.core.utility.handler.configurationHandler.get_clouds_files") - def test_get_cloud_specifications_no_config(self, mock_get_clouds_files, mock_get_clouds_specification): - mock_get_clouds_files.return_value = {"1"}, {"2"} - mock_get_clouds_specification.return_value = 21 - expected_result = [] - result = configurationHandler.get_cloud_specifications([]) - self.assertEqual(expected_result, result) - mock_get_clouds_specification.assert_not_called() - mock_get_clouds_files.assert_called() - - def test_get_cloud_specification_no_matching_cloud(self): - expected_result = {} - result = configurationHandler.get_cloud_specification("some_name", {}, {"some_some": "public"}) - self.assertEqual(expected_result, result) - - def test_get_cloud_specification_cloud(self): - expected_result = {42: 42} - result = configurationHandler.get_cloud_specification("some_name", {"some_name": {42: 42}}, None) - self.assertEqual(expected_result, result) - - def test_get_cloud_specification_no_public_cloud(self): - expected_result = {42: 42, "profile": "name2"} - result = configurationHandler.get_cloud_specification("some_name", {"some_name": expected_result}, - {"not_name2": {21: 21}}) - self.assertEqual(expected_result, result) - - def test_get_cloud_specification(self): - cloud_private_specification = {42: 42, "profile": "name2", "test": {"recursive": "foo"}} - expected_result = {42: 42, "profile": "name2", "test": {"recursive": "foo"}, "additional": "value"} - result = configurationHandler.get_cloud_specification("some_name", {"some_name": cloud_private_specification}, - {"name2": {42: 21, "test": {"recursive": "oof"}, - "additional": "value"}}) - self.assertEqual(expected_result, result) - - def test_get_cloud_specification_type_exception(self): - cloud_private_specification = {42: 42, "profile": "name2", "test": {"recursive": "foo"}} - result = configurationHandler.get_cloud_specification("some_name", {"some_name": cloud_private_specification}, - {"name2": {42: 21, "test": ["recursive", 22], - "additional": "value"}}) - self.assertEqual({}, result) diff --git a/tests/test_configuration_handler.py b/tests/test_configuration_handler.py new file mode 100644 index 00000000..86564f84 --- /dev/null +++ b/tests/test_configuration_handler.py @@ -0,0 +1,208 @@ +""" +Module to test configuration_handler +""" + +import os +from unittest import TestCase +from unittest.mock import patch, mock_open, MagicMock + +from bibigrid.core import startup +from bibigrid.core.utility.handler import configuration_handler + + +class TestConfigurationHandler(TestCase): + """ + Class to test configuration_handler + """ + # pylint: disable=R0904 + def test_get_list_by_name_none(self): + configurations = [{}, {}] + self.assertEqual([None, None], configuration_handler.get_list_by_key(configurations, "key1")) + self.assertEqual([], configuration_handler.get_list_by_key(configurations, "key1", False)) + + def test_get_list_by_name_empty(self): + configurations = [{"key1": "value1", "key2": "value1"}, {"key1": "value2"}] + self.assertEqual(["value1", "value2"], configuration_handler.get_list_by_key(configurations, "key1")) + self.assertEqual(["value1", "value2"], configuration_handler.get_list_by_key(configurations, "key1", False)) + self.assertEqual(["value1", None], configuration_handler.get_list_by_key(configurations, "key2")) + self.assertEqual(["value1"], configuration_handler.get_list_by_key(configurations, "key2", False)) + + @patch("os.path.isfile") + def test_read_configuration_no_file(self, mock_isfile): + mock_isfile.return_value = False + test_open = MagicMock() + configuration = "Test: 42" + expected_result = [None] + with patch("builtins.open", mock_open(test_open, read_data=configuration)): + result = configuration_handler.read_configuration(startup.LOG, "path") + mock_isfile.assert_called_with("path") + test_open.assert_not_called() + self.assertEqual(expected_result, result) + + @patch("os.path.isfile") + def test_read_configuration_file(self, mock_isfile): + mock_isfile.return_value = True + opener = MagicMock() + configuration = "Test: 42" + expected_result = [{"Test": 42}] + with patch("builtins.open", mock_open(opener, read_data=configuration)): + result = configuration_handler.read_configuration(startup.LOG, "path") + mock_isfile.assert_called_with("path") + opener.assert_called_with("path", mode="r", encoding="UTF-8") + self.assertEqual(expected_result, result) + + @patch("os.path.isfile") + def test_read_configuration_file_yaml_exception(self, mock_isfile): + mock_isfile.return_value = True + opener = MagicMock() + configuration = "]unbalanced brackets[" + expected_result = [None] + with patch("builtins.open", mock_open(opener, read_data=configuration)): + result = configuration_handler.read_configuration(startup.LOG, "path") + mock_isfile.assert_called_with("path") + opener.assert_called_with("path", mode="r", encoding="UTF-8") + self.assertEqual(expected_result, result) + + def test_find_file_in_folders_not_found_no_folder(self): + expected_result = None + result = configuration_handler.find_file_in_folders("true_file", [], startup.LOG) + self.assertEqual(expected_result, result) + + def test_find_file_in_folders_not_found_no_file(self): + expected_result = None + with patch("os.path.isfile") as mock_isfile: + mock_isfile.return_value = False + result = configuration_handler.find_file_in_folders("false_file", ["or_false_folder"], startup.LOG) + self.assertEqual(expected_result, result) + mock_isfile.called_with(os.path.expanduser(os.path.join("or_false_folder", "false_file"))) + + @patch("os.path.isfile") + @patch("bibigrid.core.utility.handler.configuration_handler.read_configuration") + def test_find_file_in_folders(self, mock_read_configuration, mock_isfile): + expected_result = 42 + mock_isfile.return_value(True) + mock_read_configuration.return_value = 42 + result = configuration_handler.find_file_in_folders("true_file", ["true_folder"], startup.LOG) + self.assertEqual(expected_result, result) + mock_read_configuration.assert_called_with(startup.LOG, + os.path.expanduser(os.path.join("true_folder", "true_file")), False) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files_none(self, mock_ffif): + mock_ffif.return_value = None + expected_result = None, None + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files_no_clouds_yaml(self, mock_ffif): + mock_ffif.side_effect = [None, {configuration_handler.CLOUD_PUBLIC_ROOT_KEY: 42}] + expected_result = None, 42 + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files_no_public_clouds_yaml(self, mock_ffif): + mock_ffif.side_effect = [{configuration_handler.CLOUD_ROOT_KEY: 42}, None] + expected_result = 42, None + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files_no_root_key_public(self, mock_ffif): + mock_ffif.side_effect = [{configuration_handler.CLOUD_ROOT_KEY: 42}, {"name": 42}] + expected_result = 42, None + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files_no_root_key_cloud(self, mock_ffif): + mock_ffif.side_effect = [{"name": 42}, {configuration_handler.CLOUD_PUBLIC_ROOT_KEY: 42}] + expected_result = None, 42 + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + + @patch("bibigrid.core.utility.handler.configuration_handler.find_file_in_folders") + def test_get_cloud_files(self, mock_ffif): + mock_ffif.side_effect = [{configuration_handler.CLOUD_ROOT_KEY: 22}, + {configuration_handler.CLOUD_PUBLIC_ROOT_KEY: 42}] + expected_result = 22, 42 + result = configuration_handler.get_clouds_files(startup.LOG) + self.assertEqual(expected_result, result) + mock_ffif.assert_called_with(configuration_handler.CLOUDS_PUBLIC_YAML, configuration_handler.CLOUDS_YAML_PATHS, + startup.LOG) + + @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specification") + @patch("bibigrid.core.utility.handler.configuration_handler.get_clouds_files") + def test_get_cloud_specifications_none(self, mock_get_clouds_files, mock_get_clouds_specification): + mock_get_clouds_files.return_value = None, None + expected_result = [] + result = configuration_handler.get_cloud_specifications([{"cloud": 42}], startup.LOG) + self.assertEqual(expected_result, result) + mock_get_clouds_specification.assert_not_called() + mock_get_clouds_files.assert_called() + + @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specification") + @patch("bibigrid.core.utility.handler.configuration_handler.get_clouds_files") + def test_get_cloud_specifications_no_cloud_configuration_key(self, mock_get_clouds_files, + mock_get_clouds_specification): + mock_get_clouds_files.return_value = {"Some"}, {"Some"} + expected_result = [] + result = configuration_handler.get_cloud_specifications([{"no_cloud": 42}], startup.LOG) + self.assertEqual(expected_result, result) + mock_get_clouds_specification.assert_not_called() + mock_get_clouds_files.assert_called() + + @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specification") + @patch("bibigrid.core.utility.handler.configuration_handler.get_clouds_files") + def test_get_cloud_specifications_cloud(self, mock_get_clouds_files, mock_get_clouds_specification): + mock_get_clouds_files.return_value = {"1": "1"}, {"2": "2"} + mock_get_clouds_specification.return_value = 21 + expected_result = [21] + result = configuration_handler.get_cloud_specifications([{"cloud": 42}], startup.LOG) + self.assertEqual(expected_result, result) + mock_get_clouds_specification.assert_called_with(42, {"1": "1"}, {"2": "2"}, startup.LOG) + mock_get_clouds_files.assert_called() + + @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specification") + @patch("bibigrid.core.utility.handler.configuration_handler.get_clouds_files") + def test_get_cloud_specifications_no_config(self, mock_get_clouds_files, mock_get_clouds_specification): + mock_get_clouds_files.return_value = {"1": "1"}, {"2": "2"} + mock_get_clouds_specification.return_value = 21 + expected_result = [] + result = configuration_handler.get_cloud_specifications([], startup.LOG) + self.assertEqual(expected_result, result) + mock_get_clouds_specification.assert_not_called() + mock_get_clouds_files.assert_called() + + def test_get_cloud_specification_no_matching_cloud(self): + expected_result = {} + result = configuration_handler.get_cloud_specification("some_name", {}, {"some_some": "public"}, startup.LOG) + self.assertEqual(expected_result, result) + + def test_get_cloud_specification_cloud(self): + expected_result = {42: 42, "identifier": "some_name"} + result = configuration_handler.get_cloud_specification("some_name", {"some_name": {42: 42}}, None, startup.LOG) + self.assertEqual(expected_result, result) + + def test_get_cloud_specification_no_public_cloud(self): + expected_result = {42: 42, "profile": "name2", "identifier": "some_name"} + result = configuration_handler.get_cloud_specification("some_name", {"some_name": expected_result}, + {"not_name2": {21: 21}}, startup.LOG) + self.assertEqual(expected_result, result) + + def test_get_cloud_specification(self): + cloud_private_specification = {42: 42, "profile": "name2", "test": {"recursive": "oof"}} + expected_result = {42: 21, "profile": "name2", "test": {"recursive": "foo"}, "additional": "value", + "identifier": "some_name"} + result = configuration_handler.get_cloud_specification("some_name", {"some_name": cloud_private_specification}, + {"name2": {42: 21, "test": {"recursive": "foo"}, + "additional": "value"}}, startup.LOG) + self.assertEqual(expected_result, result) + + def test_get_cloud_specification_type_exception(self): + cloud_private_specification = {42: 42, "profile": "name2", "test": {"recursive": "foo"}} + result = configuration_handler.get_cloud_specification("some_name", {"some_name": cloud_private_specification}, + {"name2": {42: 21, "test": ["recursive", 22], + "additional": "value"}}, startup.LOG) + self.assertEqual({}, result) diff --git a/tests/test_create.py b/tests/test_create.py index e3cd9068..339a90db 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -1,119 +1,75 @@ -import os +""" +Module to test create +""" from unittest import TestCase -from unittest.mock import patch, Mock, MagicMock, mock_open +from unittest.mock import patch, MagicMock, mock_open +from bibigrid.core import startup from bibigrid.core.actions import create +from bibigrid.core.utility.handler import ssh_handler class TestCreate(TestCase): + """ + Class to test create + """ + # pylint: disable=R0904 - @patch("bibigrid.core.utility.handler.sshHandler.get_add_ssh_public_key_commands") + @patch("bibigrid.core.utility.handler.ssh_handler.get_add_ssh_public_key_commands") @patch("bibigrid.core.utility.id_generation.generate_safe_cluster_id") def test_init(self, mock_id, mock_ssh): - unique_id = 21 + cluster_id = "21" provider = MagicMock() - provider.cloud_specification["auth"]["project_name"] = "name" - key_name = create.KEY_PREFIX + provider.cloud_specification["auth"]["project_name"] \ - + create.SEPARATOR + str(unique_id) - mock_id.return_value = str(unique_id) + provider_dict = {'cloud_specification': {'auth': {'project_name': 'project_name'}}} + provider.__getitem__.side_effect = provider_dict.__getitem__ + key_name = create.KEY_NAME.format(cluster_id=cluster_id) + mock_id.return_value = cluster_id mock_ssh.return_value = [32] - c = create.Create([provider], [{}], "path", False) - self.assertEqual(str(unique_id), c.cluster_id) - self.assertEqual("ubuntu", c.ssh_user) - self.assertEqual([32], c.ssh_add_public_key_commands) - self.assertEqual(c.key_name, key_name) + creator = create.Create([provider], [{}], "path", startup.LOG, False) + self.assertEqual(cluster_id, creator.cluster_id) + self.assertEqual("ubuntu", creator.ssh_user) + self.assertEqual([32], creator.ssh_add_public_key_commands) + self.assertEqual(key_name, creator.key_name) mock_id.assert_called_with([provider]) - @patch("bibigrid.core.utility.handler.sshHandler.get_add_ssh_public_key_commands") + @patch("bibigrid.core.utility.handler.ssh_handler.get_add_ssh_public_key_commands") + @patch("bibigrid.core.utility.id_generation.generate_safe_cluster_id") + def test_init_with_cluster_id(self, mock_id, mock_ssh): + cluster_id = "21" + provider = MagicMock() + provider_dict = {'cloud_specification': {'auth': {'project_name': 'project_name'}}} + provider.__getitem__.side_effect = provider_dict.__getitem__ + key_name = create.KEY_NAME.format(cluster_id=cluster_id) + mock_ssh.return_value = [32] + creator = create.Create([provider], [{}], "path", startup.LOG, False, cluster_id) + self.assertEqual(cluster_id, creator.cluster_id) + self.assertEqual("ubuntu", creator.ssh_user) + self.assertEqual([32], creator.ssh_add_public_key_commands) + self.assertEqual(key_name, creator.key_name) + mock_id.assert_not_called() + + @patch("bibigrid.core.utility.handler.ssh_handler.get_add_ssh_public_key_commands") @patch("bibigrid.core.utility.id_generation.generate_safe_cluster_id") def test_init_username(self, mock_id, mock_ssh): - unique_id = 21 - mock_id.return_value = str(unique_id) + cluster_id = "21" + mock_id.return_value = cluster_id mock_ssh.return_value = [32] - c = create.Create([MagicMock()], [{"sshUser": "ssh"}], "path", False) - self.assertEqual("ssh", c.ssh_user) + creator = create.Create([MagicMock()], [{"sshUser": "ssh"}], "path", startup.LOG, False) + self.assertEqual("ssh", creator.ssh_user) @patch("subprocess.check_output") def test_generate_keypair(self, mock_subprocess): provider = MagicMock() provider.list_servers.return_value = [] - c = create.Create([provider], [{}], "") + creator = create.Create([provider], [{}], "", startup.LOG) public_key = "data" with patch("builtins.open", mock_open(read_data=public_key)): - c.generate_keypair() - provider.create_keypair.assert_called_with(name=c.key_name, public_key=public_key) - mock_subprocess.assert_called_with(f'ssh-keygen -t ecdsa -f {create.KEY_FOLDER}{c.key_name} -P ""') - - def test_start_instance(self): - provider = MagicMock() - provider.list_servers.return_value = [] - provider.create_server.return_value = 42 - provider.add_auto_ip.return_value = {"floating_ip_address": 12} - c = create.Create([provider], [{}], "") - server_type = {"type": "testType", "image": "testImage"} - network = 21 - external_network = "testExternal" - c.start_instance(provider, create.MASTER_IDENTIFIER, server_type, network, worker=False, volumes=2, - external_network=external_network) - provider.create_server.assert_called_with(name=create.MASTER_IDENTIFIER + create.SEPARATOR + c.cluster_id, - flavor=server_type["type"], - key_name=c.key_name, - image=server_type["image"], - network=network, volumes=2) - provider.add_auto_ip.assert_called_with(network=external_network, server=42) - - def test_start_instance_worker(self): - provider = MagicMock() - provider.list_servers.return_value = [] - provider.create_server.return_value = 42 - provider.create_floating_ip.return_value = {"floating_ip_address": 12} - c = create.Create([provider], [{}], "") - server_type = {"type": "testType", "image": "testImage"} - network = 21 - c.start_instance(provider, create.WORKER_IDENTIFIER, server_type, network, worker=True, volumes=None, - external_network=None) - provider.create_server.assert_called_with( - name=create.WORKER_IDENTIFIER.format(0) + create.SEPARATOR + c.cluster_id, - flavor=server_type["type"], - key_name=c.key_name, - image=server_type["image"], - network=network, volumes=None) - provider.create_floating_ip.assert_not_called() - - @patch("bibigrid.models.returnThreading.ReturnThread") - def test_start_instances(self, return_mock): - provider = MagicMock() - provider.list_servers.return_value = [] - external_network = "externalTest" - provider.get_external_netowrk.return_value = external_network - configuration = {"network": 42} - c = create.Create([provider], [configuration], "") - provider.get_external_network.return_value = 32 - with patch.object(c, "prepare_vpn_or_master_args", return_value=(0, 1, 2)) as prepare_mock: - prepare_mock.return_value = (0, 1, 2) - c.start_instances({"network": 42}, provider) - prepare_mock.assert_called_with(configuration, provider) - provider.get_external_network.assert_called_with(configuration["network"]) - return_mock.assert_called_with(target=c.start_instance, - args=[provider, 0, 1, configuration["network"], False, 2, 32]) + creator.generate_keypair() + provider.create_keypair.assert_called_with(name=creator.key_name, public_key=public_key) + mock_subprocess.assert_called_with(f'ssh-keygen -t ecdsa -f {create.KEY_FOLDER}{creator.key_name} -P ""', + shell=True) - @patch("threading.Thread") - @patch("bibigrid.models.returnThreading.ReturnThread") - def test_start_instances_workers(self, return_mock, thread_mock): - provider = MagicMock() - provider.list_servers.return_value = [] - external_network = "externalTest" - provider.get_external_netowrk.return_value = external_network - configuration = {"network": 42, "workerInstances": [{"count": 1}]} - c = create.Create([provider], [configuration], "") - provider.get_external_network.return_value = 32 - with patch.object(c, "prepare_vpn_or_master_args", return_value=(0, 1, 2)) as prepare_mock: - prepare_mock.return_value = (0, 1, 2) - c.start_instances(configuration, provider) - thread_mock.assert_called_with(target=c.start_instance, - args=[provider, create.WORKER_IDENTIFIER, configuration["workerInstances"][0], - configuration["network"], True]) - return_mock.assert_called() + # TODO: Rewrite start instance tests def test_prepare_master_args(self): provider = MagicMock() @@ -121,11 +77,11 @@ def test_prepare_master_args(self): external_network = "externalTest" provider.get_external_netowrk.return_value = external_network configuration = {"network": 42, "masterInstance": "Some"} - c = create.Create([provider], [configuration], "") + creator = create.Create([provider], [configuration], "", startup.LOG) volume_return = [42] - with patch.object(c, "prepare_volumes", return_value=volume_return) as prepare_mock: + with patch.object(creator, "prepare_volumes", return_value=volume_return) as prepare_mock: self.assertEqual((create.MASTER_IDENTIFIER, configuration["masterInstance"], volume_return), - c.prepare_vpn_or_master_args(configuration, provider)) + creator.prepare_vpn_or_master_args(configuration, provider)) prepare_mock.assert_called_with(provider, []) def test_prepare_vpn_args(self): @@ -134,11 +90,11 @@ def test_prepare_vpn_args(self): external_network = "externalTest" provider.get_external_netowrk.return_value = external_network configuration = {"network": 42, "vpnInstance": "Some"} - c = create.Create([provider], [configuration], "") + creator = create.Create([provider], [configuration], "", startup.LOG) volume_return = [42] - with patch.object(c, "prepare_volumes", return_value=volume_return) as prepare_mock: + with patch.object(creator, "prepare_volumes", return_value=volume_return) as prepare_mock: self.assertEqual((create.VPN_WORKER_IDENTIFIER, configuration["vpnInstance"], []), - c.prepare_vpn_or_master_args(configuration, provider)) + creator.prepare_vpn_or_master_args(configuration, provider)) prepare_mock.assert_not_called() def test_prepare_args_keyerror(self): @@ -147,26 +103,24 @@ def test_prepare_args_keyerror(self): external_network = "externalTest" provider.get_external_netowrk.return_value = external_network configuration = {"network": 42} - c = create.Create([provider], [configuration], "") + creator = create.Create([provider], [configuration], "", startup.LOG) volume_return = [42] - with patch.object(c, "prepare_volumes", return_value=volume_return) as prepare_mock: + with patch.object(creator, "prepare_volumes", return_value=volume_return) as prepare_mock: with self.assertRaises(KeyError): self.assertEqual((create.VPN_WORKER_IDENTIFIER, configuration["vpnInstance"], []), - c.prepare_vpn_or_master_args(configuration, provider)) + creator.prepare_vpn_or_master_args(configuration, provider)) prepare_mock.assert_not_called() - @patch("bibigrid.core.utility.handler.sshHandler.ansible_preparation") - def test_setup_reachable_servers_master(self, mock_ansible): + @patch("bibigrid.core.utility.handler.ssh_handler.ansible_preparation") + def test_initialize_instances_master(self, mock_ansible): provider = MagicMock() provider.list_servers.return_value = [] - configuration = {"masterInstance": 42} - c = create.Create([provider], [configuration], "") floating_ip = 21 - c.setup_reachable_servers(configuration, floating_ip) - mock_ansible.assert_called_with(floating_ip=floating_ip, - private_key=create.KEY_FOLDER + c.key_name, - username=c.ssh_user, - commands=[]) + configuration = {"masterInstance": 42, "floating_ip": floating_ip} + creator = create.Create([provider], [configuration], "", startup.LOG) + creator.initialize_instances() + mock_ansible.assert_called_with(floating_ip=floating_ip, private_key=create.KEY_FOLDER + creator.key_name, + username=creator.ssh_user, commands=[], log=startup.LOG, gateway={}) def test_prepare_volumes_none(self): provider = MagicMock() @@ -174,38 +128,36 @@ def test_prepare_volumes_none(self): provider.get_volume_by_id_or_name.return_value = 42 provider.create_volume_from_snapshot = 21 configuration = {"vpnInstance": 42} - c = create.Create([provider], [configuration], "") - self.assertEqual([], c.prepare_volumes(provider, [])) + creator = create.Create([provider], [configuration], "", startup.LOG) + self.assertEqual(set(), creator.prepare_volumes(provider, [])) def test_prepare_volumes_volume(self): provider = MagicMock() provider.list_servers.return_value = [] - provider.get_volume_by_id_or_name.return_value = 42 + provider.get_volume_by_id_or_name.return_value = {"id": 42} provider.create_volume_from_snapshot = 21 configuration = {"vpnInstance": 42} - c = create.Create([provider], [configuration], "") - self.assertEqual([42], c.prepare_volumes(provider, ["Test"])) + creator = create.Create([provider], [configuration], "", startup.LOG) + self.assertEqual({42}, creator.prepare_volumes(provider, ["Test"])) def test_prepare_volumes_snapshot(self): provider = MagicMock() provider.list_servers.return_value = [] - provider.get_volume_by_id_or_name.return_value = None + provider.get_volume_by_id_or_name.return_value = {"id": None} provider.create_volume_from_snapshot.return_value = 21 configuration = {"vpnInstance": 42} - c = create.Create([provider], [configuration], "") - self.assertEqual([21], c.prepare_volumes(provider, ["Test"])) + creator = create.Create([provider], [configuration], "", startup.LOG) + self.assertEqual({21}, creator.prepare_volumes(provider, ["Test"])) - @patch("logging.warning") - def test_prepare_volumes_mismatch(self, mock_log): + def test_prepare_volumes_mismatch(self): provider = MagicMock() provider.list_servers.return_value = [] - provider.get_volume_by_id_or_name.return_value = None + provider.get_volume_by_id_or_name.return_value = {"id": None} provider.create_volume_from_snapshot.return_value = None configuration = {"vpnInstance": 42} - c = create.Create([provider], [configuration], "") + creator = create.Create([provider], [configuration], "", startup.LOG) mount = "Test" - self.assertEqual([], c.prepare_volumes(provider, [mount])) - mock_log.assert_called_with(f"Mount {mount} is neither a snapshot nor a volume.") + self.assertEqual(set(), creator.prepare_volumes(provider, [mount])) def test_prepare_configurations_no_network(self): provider = MagicMock() @@ -213,11 +165,11 @@ def test_prepare_configurations_no_network(self): network = "network" provider.get_network_id_by_subnet.return_value = network configuration = {"subnet": 42} - c = create.Create([provider], [configuration], "") - c.prepare_configurations() + creator = create.Create([provider], [configuration], "", startup.LOG) + creator.prepare_configurations() provider.get_network_id_by_subnet.assert_called_with(42) self.assertEqual(network, configuration["network"]) - self.assertEqual(c.ssh_user, configuration["sshUser"]) + self.assertEqual(creator.ssh_user, configuration["sshUser"]) def test_prepare_configurations_no_subnet(self): provider = MagicMock() @@ -225,98 +177,87 @@ def test_prepare_configurations_no_subnet(self): subnet = ["subnet"] provider.get_subnet_ids_by_network.return_value = subnet configuration = {"network": 42} - c = create.Create([provider], [configuration], "") - c.prepare_configurations() + creator = create.Create([provider], [configuration], "", startup.LOG) + creator.prepare_configurations() provider.get_subnet_ids_by_network.assert_called_with(42) self.assertEqual(subnet, configuration["subnet"]) - self.assertEqual(c.ssh_user, configuration["sshUser"]) + self.assertEqual(creator.ssh_user, configuration["sshUser"]) def test_prepare_configurations_none(self): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "") + creator = create.Create([provider], [configuration], "", startup.LOG) with self.assertRaises(KeyError): - c.prepare_configurations() - - @patch("bibigrid.core.utility.ansibleConfigurator.configure_ansible_yaml") - @patch("bibigrid.core.utility.handler.sshHandler.execute_ssh") - def test_upload_playbooks(self, mock_ssh, mock_configure_ansible): - provider = MagicMock() - provider.list_servers.return_value = [] - configuration = {} - c = create.Create([provider], [configuration], "") - c.master_ip = 42 - c.upload_data() - mock_configure_ansible.assert_called_with(providers=c.providers, - configurations=c.configurations, - cluster_id=c.cluster_id) - mock_ssh.assert_called_with(floating_ip=c.master_ip, private_key=create.KEY_FOLDER + c.key_name, - username=c.ssh_user, filepaths=[(os.path.expanduser("/Documents/Repos/bibigrid/" - "resources/playbook/"), - "playbook")], - commands=['echo ansible_start']) + creator.prepare_configurations() - @patch("threading.Thread") - def test_start_start_instances_thread(self, mock_thread): + @patch("bibigrid.core.utility.ansible_configurator.configure_ansible_yaml") + @patch("bibigrid.core.utility.handler.ssh_handler.get_ac_command") + @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") + def test_upload_playbooks(self, mock_execute_ssh, mock_ac_ssh, mock_configure_ansible): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "") - start_instances_mock_thread = Mock() - mock_thread.return_value = start_instances_mock_thread - c.start_start_instances_threads() - mock_thread.assert_called_with(target=c.start_instances, args=[configuration, provider]) - start_instances_mock_thread.start.assert_called() - start_instances_mock_thread.join.assert_called() + creator = create.Create([provider], [configuration], "", startup.LOG) + creator.master_ip = 42 + creator.upload_data() + mock_configure_ansible.assert_called_with(providers=creator.providers, configurations=creator.configurations, + cluster_id=creator.cluster_id, log=startup.LOG) + mock_execute_ssh.assert_called_with(floating_ip=creator.master_ip, + private_key=create.KEY_FOLDER + creator.key_name, username=creator.ssh_user, + filepaths=create.FILEPATHS, + commands=[mock_ac_ssh()] + ssh_handler.ANSIBLE_START, log=startup.LOG, + gateway={}) @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") - @patch.object(create.Create, "start_start_instances_threads") + @patch.object(create.Create, "start_start_instance_threads") @patch.object(create.Create, "upload_data") - @patch.object(create.Create, "print_cluster_start_info") - @patch("bibigrid.core.actions.terminateCluster.terminate_cluster") + @patch.object(create.Create, "log_cluster_start_info") + @patch("bibigrid.core.actions.terminate.terminate") def test_create_non_debug(self, mock_terminate, mock_info, mock_up, mock_start, mock_conf, mock_key): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "", False) - self.assertEqual(0, c.create()) + creator = create.Create([provider], [configuration], "", startup.LOG, False) + self.assertEqual(0, creator.create()) for mock in [mock_info, mock_up, mock_start, mock_conf, mock_key]: mock.assert_called() mock_terminate.assert_not_called() @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") - @patch.object(create.Create, "start_start_instances_threads") + @patch.object(create.Create, "start_start_instance_threads") @patch.object(create.Create, "upload_data") - @patch.object(create.Create, "print_cluster_start_info") - @patch("bibigrid.core.actions.terminateCluster.terminate_cluster") + @patch.object(create.Create, "log_cluster_start_info") + @patch("bibigrid.core.actions.terminate.terminate") def test_create_non_debug_upload_raise(self, mock_terminate, mock_info, mock_up, mock_start, mock_conf, mock_key): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "", False) + creator = create.Create([provider], [configuration], "", startup.LOG, False) mock_up.side_effect = [ConnectionError()] - self.assertEqual(1, c.create()) + self.assertEqual(1, creator.create()) for mock in [mock_start, mock_conf, mock_key, mock_up]: mock.assert_called() for mock in [mock_info]: mock.assert_not_called() - mock_terminate.assert_called_with(cluster_id=c.cluster_id, providers=[provider], debug=False) + mock_terminate.assert_called_with(cluster_id=creator.cluster_id, providers=[provider], log=startup.LOG, + debug=False) @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") - @patch.object(create.Create, "start_start_instances_threads") + @patch.object(create.Create, "start_start_instance_threads") @patch.object(create.Create, "upload_data") - @patch.object(create.Create, "print_cluster_start_info") - @patch("bibigrid.core.actions.terminateCluster.terminate_cluster") + @patch.object(create.Create, "log_cluster_start_info") + @patch("bibigrid.core.actions.terminate.terminate") def test_create_debug(self, mock_terminate, mock_info, mock_up, mock_start, mock_conf, mock_key): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - c = create.Create([provider], [configuration], "", True) - self.assertEqual(0, c.create()) + creator = create.Create([provider], [configuration], "", startup.LOG, True) + self.assertEqual(0, creator.create()) for mock in [mock_info, mock_up, mock_start, mock_conf, mock_key]: mock.assert_called() - mock_terminate.assert_called_with(cluster_id=c.cluster_id, providers=[provider], debug=True) + mock_terminate.assert_called_with(cluster_id=creator.cluster_id, providers=[provider], log=startup.LOG, + debug=True) diff --git a/tests/test_idGeneration.py b/tests/test_id_generation.py similarity index 82% rename from tests/test_idGeneration.py rename to tests/test_id_generation.py index 6e694530..0e7c4009 100644 --- a/tests/test_idGeneration.py +++ b/tests/test_id_generation.py @@ -1,3 +1,6 @@ +""" +Module to test id_generation +""" from unittest import TestCase from unittest.mock import Mock, MagicMock, patch @@ -5,7 +8,10 @@ from bibigrid.core.utility import id_generation -class Test(TestCase): +class TestIDGeneration(TestCase): + """ + Class to test id_generation + """ def test_generate_cluster_id(self): """ @@ -26,10 +32,10 @@ def test_generate_safe_cluster_id(self, mock_generate_cluster_id): mock_is_unique.assert_called_with(21, [42]) def test_is_unique_cluster_id_duplicate(self): - cluster_id = 42 + cluster_id = "42" provider = Mock() provider.list_servers = MagicMock( - return_value=[{"name": create.MASTER_IDENTIFIER + create.SEPARATOR + str(cluster_id)}]) + return_value=[{"name": create.MASTER_IDENTIFIER(cluster_id=cluster_id)}]) self.assertFalse(id_generation.is_unique_cluster_id(str(cluster_id), [provider])) provider.list_servers.assert_called() @@ -37,6 +43,6 @@ def test_is_unique_cluster_id_unique(self): cluster_id = 42 provider = Mock() provider.list_servers = MagicMock( - return_value=[{"name": create.MASTER_IDENTIFIER + create.SEPARATOR + str(cluster_id + 1)}]) + return_value=[{"name": create.MASTER_IDENTIFIER(cluster_id=str(cluster_id + 1))}]) self.assertTrue(id_generation.is_unique_cluster_id(str(cluster_id), [provider])) provider.list_servers.assert_called() diff --git a/tests/test_listClusters.py b/tests/test_listClusters.py deleted file mode 100644 index 12744785..00000000 --- a/tests/test_listClusters.py +++ /dev/null @@ -1,46 +0,0 @@ -from unittest import TestCase -from unittest.mock import Mock - -from bibigrid.core.actions import create -from bibigrid.core.actions import list_clusters - - -class TestDictClusters(TestCase): - def test_setup(self): - for identifier in [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, create.MASTER_IDENTIFIER]: - cluster_id = 42 - test_provider = Mock() - test_provider.name = "name" - cluster_dict = {} - server = {"name": identifier + create.SEPARATOR + str(cluster_id)} - self.assertEqual(str(cluster_id), - list_clusters.setup(server, - identifier, cluster_dict, test_provider)) - self.assertEqual({str(cluster_id): {'worker': [], 'vpngtw': []}}, cluster_dict) - self.assertEqual(test_provider, server["provider"]) - - def test_setup_already(self): - for identifier in [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, create.MASTER_IDENTIFIER]: - cluster_id = 42 - test_provider = Mock() - test_provider.name = "name" - cluster_dict = {str(cluster_id): {'worker': ["some"], 'vpngtw': ["some"]}} - server = {"name": identifier + create.SEPARATOR + str(cluster_id)} - self.assertEqual(str(cluster_id), - list_clusters.setup(server, - identifier, cluster_dict, test_provider)) - self.assertEqual({str(cluster_id): {'worker': ["some"], 'vpngtw': ["some"]}}, cluster_dict) - self.assertEqual(test_provider, server["provider"]) - - def test_dict_clusters(self): - cluster_id = 42 - expected = {str(cluster_id): {'workers': [{'name': f'bibigrid-worker-{str(cluster_id)}', 'provider': 'Mock'}], - 'vpngtws': [ - {'name': f'bibigrid-vpngtw-{str(cluster_id)}', 'provider': 'Mock'}], - 'master': {'name': f'bibigrid-master-{str(cluster_id)}', 'provider': 'Mock'}}} - provider = Mock() - provider.list_servers.return_value = [{'name': identifier + create.SEPARATOR + str(cluster_id)} for identifier - in - [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, - create.MASTER_IDENTIFIER]] - self.assertEqual(expected, list_clusters.dict_clusters([provider])) diff --git a/tests/test_list_clusters.py b/tests/test_list_clusters.py new file mode 100644 index 00000000..d7932d74 --- /dev/null +++ b/tests/test_list_clusters.py @@ -0,0 +1,57 @@ +""" +Module to test list +""" +from unittest import TestCase +from unittest.mock import Mock + +from bibigrid.core import startup +from bibigrid.core.actions import create +from bibigrid.core.actions import list_clusters + + +class TestList(TestCase): + """ + Class to test list + """ + + def test_setup(self): + for identifier in [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, create.MASTER_IDENTIFIER]: + cluster_id = "42" + provider = Mock() + provider.name = "name" + provider.cloud_specification = {"identifier": "21"} + cluster_dict = {} + server = {"name": identifier(cluster_id=cluster_id)} + list_clusters.setup(cluster_dict, str(cluster_id), server, provider) + + self.assertEqual({cluster_id: {'workers': [], 'vpngtws': []}}, cluster_dict) + self.assertEqual(provider.NAME, server["provider"]) + self.assertEqual("21", server["cloud_specification"]) + + def test_setup_already(self): + for identifier in [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER, create.MASTER_IDENTIFIER]: + cluster_id = "42" + provider = Mock() + provider.name = "name" + provider.cloud_specification = {"identifier": "21"} + cluster_dict = {cluster_id: {'workers': ["some"], 'vpngtws': ["some"]}} + server = {"name": identifier(cluster_id=cluster_id)} + list_clusters.setup(cluster_dict, cluster_id, server, provider) + + self.assertEqual({cluster_id: {'workers': ["some"], 'vpngtws': ["some"]}}, cluster_dict) + self.assertEqual(provider.NAME, server["provider"]) + self.assertEqual("21", server["cloud_specification"]) + + def test_dict_clusters(self): + cluster_id = "42" + expected = {cluster_id: { + 'workers': [{'cloud_specification': '21', 'name': f'bibigrid-worker-{cluster_id}-0', 'provider': 'Mock'}], + 'vpngtws': [{'cloud_specification': '21', 'name': f'bibigrid-vpngtw-{cluster_id}-0', 'provider': 'Mock'}], + 'master': {'cloud_specification': '21', 'name': f'bibigrid-master-{cluster_id}', 'provider': 'Mock'}}} + provider = Mock() + provider.NAME = "Mock" + provider.cloud_specification = {"identifier": "21"} + provider.list_servers.return_value = [{'name': identifier(cluster_id=cluster_id) + "-0"} for identifier in + [create.WORKER_IDENTIFIER, create.VPN_WORKER_IDENTIFIER]] + [ + {'name': create.MASTER_IDENTIFIER(cluster_id=cluster_id)}] + self.assertEqual(expected, list_clusters.dict_clusters([provider], startup.LOG)) diff --git a/tests/test_providerHandler.py b/tests/test_providerHandler.py deleted file mode 100644 index 6735e77f..00000000 --- a/tests/test_providerHandler.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import TestCase -from unittest.mock import MagicMock, patch - -import bibigrid.core.utility.handler.provider_handler as providerHandler - - -class TestProviderHandler(TestCase): - - @patch("bibigrid.core.utility.handler.configurationHandler.get_cloud_specifications") - @patch("bibigrid.core.utility.handler.providerHandler.get_provider_list_by_name_list") - def test_get_providers(self, mock_provider_list, mock_get_cloud_specifications): - mock_get_cloud_specifications.return_value = True # for if not false - configurations = [{"infrastructure": "some"}] - mock_provider_list.return_value = 42 - with patch("bibigrid.core.utility.handler.configurationHandler.get_list_by_key") as mock_by_name: - self.assertEqual(42, providerHandler.get_providers(configurations)) - mock_by_name.assert_called_with(configurations, "infrastructure") - mock_get_cloud_specifications.assert_called_with(configurations) - - def test_get_provider_list_by_name_list(self): - keys = providerHandler.PROVIDER_NAME_DICT.keys() - values = [42] - with patch("bibigrid.core.utility.handler.providerHandler.get_provider_by_name") as mock_by_name: - mock_by_name.return_value = MagicMock(return_value=42) - self.assertEqual(providerHandler.get_provider_list_by_name_list(keys, "nonempty_specification"), values) - mock_by_name.assert_called_with(list(keys)[0]) diff --git a/tests/test_provider_handler.py b/tests/test_provider_handler.py new file mode 100644 index 00000000..6b37fec9 --- /dev/null +++ b/tests/test_provider_handler.py @@ -0,0 +1,33 @@ +""" +Module to test provider_handler +""" +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from bibigrid.core.utility.handler import provider_handler +from bibigrid.core import startup + + +class TestProviderHandler(TestCase): + """ + Class to test provider_handler + """ + + @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specifications") + @patch("bibigrid.core.utility.handler.provider_handler.get_provider_list_by_name_list") + def test_get_providers(self, mock_provider_list, mock_get_cloud_specifications): + mock_get_cloud_specifications.return_value = True # for if not false + configurations = [{"infrastructure": "some"}] + mock_provider_list.return_value = 42 + with patch("bibigrid.core.utility.handler.configuration_handler.get_list_by_key") as mock_by_name: + self.assertEqual(42, provider_handler.get_providers(configurations, startup.LOG)) + mock_by_name.assert_called_with(configurations, "infrastructure") + mock_get_cloud_specifications.assert_called_with(configurations, startup.LOG) + + def test_get_provider_list_by_name_list(self): + keys = provider_handler.PROVIDER_NAME_DICT.keys() + values = [42] + with patch("bibigrid.core.utility.handler.provider_handler.get_provider_by_name") as mock_by_name: + mock_by_name.return_value = MagicMock(return_value=42) + self.assertEqual(provider_handler.get_provider_list_by_name_list(keys, "nonempty_specification"), values) + mock_by_name.assert_called_with(list(keys)[0]) diff --git a/tests/test_returnThreading.py b/tests/test_return_threading.py similarity index 63% rename from tests/test_returnThreading.py rename to tests/test_return_threading.py index be9eeecd..8370f189 100644 --- a/tests/test_returnThreading.py +++ b/tests/test_return_threading.py @@ -1,17 +1,22 @@ +""" +Module to test return thread +""" from unittest import TestCase import bibigrid.models.return_threading as returnThreading -def test_method(x): - return (42, x) +def test_method(elem): + return 42, elem class TestReturnThread(TestCase): + """ + Class to test return thread + """ - def test_ReturnThread(self): - return_thread = returnThreading.ReturnThread(target=test_method, - args=[42]) + def test_return_thread(self): + return_thread = returnThreading.ReturnThread(target=test_method, args=[42]) return_thread.start() return_value = return_thread.join() self.assertTrue(return_value == (42, 42)) diff --git a/tests/test_sshHandler.py b/tests/test_sshHandler.py deleted file mode 100644 index 7a9f05df..00000000 --- a/tests/test_sshHandler.py +++ /dev/null @@ -1,104 +0,0 @@ -import socket -from unittest import TestCase -from unittest.mock import mock_open, Mock, MagicMock, patch, call - -from paramiko.ssh_exception import NoValidConnectionsError - -import bibigrid.core.utility.handler.ssh_handler as sshHandler - - -class TestSshHandler(TestCase): - def test_get_add_ssh_public_key_commands_none(self): - ssh_public_key_files = [] - self.assertEqual([], sshHandler.get_add_ssh_public_key_commands(ssh_public_key_files)) - - def test_get_add_ssh_public_key_commands_line(self): - ssh_public_key_files = [42] - line = "42" - expected = [f"echo {line} >> .ssh/authorized_keys"] - with patch("builtins.open", mock_open(read_data=line)) as mock_file: - self.assertEqual(expected, sshHandler.get_add_ssh_public_key_commands(ssh_public_key_files)) - mock_file.assert_called_with(42) - - def test_copy_to_server_file(self): - sftp = Mock() - sftp.put = MagicMock(return_value=True) - with patch("os.path.isfile") as mock_isfile: - mock_isfile.return_value = True - sshHandler.copy_to_server(sftp, "Jim", "Joe") - sftp.put.assert_called_with("Jim", "Joe") - - @patch("os.listdir") - def test_copy_to_server_folder(self, mock_listdir): - sftp = Mock() - sftp.mkdir = MagicMock() - mock_listdir.return_value = [] - with patch("os.path.isfile") as mock_isfile: - mock_isfile.return_value = False - sshHandler.copy_to_server(sftp, "Jim", "Joe") - mock_listdir.assert_called_with("Jim") - sftp.mkdir.assert_called_with("Joe") - - @patch("logging.info") - def test_is_active(self, mock_log): - client = Mock() - client.connect = MagicMock(return_value=True) - self.assertFalse(sshHandler.is_active(client, 42, 32, 22, timeout=5)) - mock_log.assert_not_called() - - @patch("logging.info") - def test_is_active_second(self, mock_log): - client = Mock() - client.connect = MagicMock(side_effect=[NoValidConnectionsError({('127.0.0.1', 22): socket.error}), True]) - self.assertFalse(sshHandler.is_active(client, 42, 32, 22, timeout=5)) - mock_log.assert_called() - - @patch("logging.info") - def test_is_active_exception(self, mock_log): - client = Mock() - client.connect = MagicMock(side_effect=NoValidConnectionsError({('127.0.0.1', 22): socket.error})) - with self.assertRaises(ConnectionError): - sshHandler.is_active(client, 42, 32, 22, timeout=0) - client.connect.assert_called_with(hostname=42, username=22, pkey=32) - mock_log.assert_called() - - @patch("bibigrid.core.utility.handler.sshHandler.execute_ssh_cml_commands") - @patch("paramiko.ECDSAKey.from_private_key_file") - @patch("paramiko.SSHClient") - def test_execute_ssh(self, mock_client, mock_paramiko_key, mock_exec): - mock_paramiko_key.return_value = 2 - client = Mock() - mock = Mock() - mock_client.return_value = mock - mock.__enter__ = client - mock.__exit__ = Mock(return_value=None) - with patch("bibigrid.core.utility.handler.sshHandler.is_active") as mock_active: - sshHandler.execute_ssh(42, 32, 22, [12], None) - mock_client.assert_called_with() - mock_active.assert_called_with(client=client(), floating_ip_address=42, username=22, private_key=2) - mock_exec.assert_called_with(client(), [12]) - mock_paramiko_key.assert_called_with(32) - - @patch("bibigrid.core.utility.handler.sshHandler.execute_ssh") - def test_ansible_preparation(self, mock_execute): - sshHandler.ansible_preparation(1, 2, 3, [], []) - mock_execute.assert_called_with(1, 2, 3, [] + sshHandler.ANSIBLE_SETUP, [(2, sshHandler.PRIVATE_KEY_FILE)]) - - @patch("bibigrid.core.utility.handler.sshHandler.execute_ssh") - def test_ansible_preparation_elem(self, mock_execute): - sshHandler.ansible_preparation(1, 2, 3, [42], [42]) - mock_execute.assert_called_with(1, 2, 3, sshHandler.ANSIBLE_SETUP + [42], - [42, (2, sshHandler.PRIVATE_KEY_FILE)]) - - @patch("logging.warning") - @patch("logging.info") - def test_execute_ssh_cml_commands(self, mock_log_info, mock_log_warning): - client = Mock() - stdout_mock = Mock() - stdout_mock.channel.recv_exit_status.side_effect = [0, 1] - stdout_mock.readlines.return_value = 49 - client.exec_command.return_value = (0, stdout_mock, 2) - commands = [42, 21] - sshHandler.execute_ssh_cml_commands(client, commands) - self.assertEqual([call('42:0')], mock_log_info.call_args_list) - self.assertEqual([call('21:1|49')], mock_log_warning.call_args_list) diff --git a/tests/test_ssh_handler.py b/tests/test_ssh_handler.py new file mode 100644 index 00000000..7c0cfd46 --- /dev/null +++ b/tests/test_ssh_handler.py @@ -0,0 +1,128 @@ +""" +Module to test ssh_handler +""" +import socket +from unittest import TestCase +from unittest.mock import mock_open, Mock, MagicMock, patch, call + +from paramiko.ssh_exception import NoValidConnectionsError + +from bibigrid.core import startup +from bibigrid.core.utility.handler import ssh_handler +from bibigrid.models.exceptions import ExecutionException, ConnectionException + + +class TestSshHandler(TestCase): + """ + Class to test ssh_handler + @todo: Test Gateway + """ + + def test_get_add_ssh_public_key_commands_none(self): + ssh_public_key_files = [] + self.assertEqual([], ssh_handler.get_add_ssh_public_key_commands(ssh_public_key_files)) + + def test_get_add_ssh_public_key_commands_line(self): + ssh_public_key_files = [42] + line = "42" + expected = [(f"echo {line} >> .ssh/authorized_keys", f"Add SSH Key {line}.")] + with patch("builtins.open", mock_open(read_data=line)) as mock_file: + self.assertEqual(expected, ssh_handler.get_add_ssh_public_key_commands(ssh_public_key_files)) + mock_file.assert_called_with(42, mode='r', encoding='UTF-8') + + def test_copy_to_server_file(self): + sftp = Mock() + sftp.put = MagicMock(return_value=True) + with patch("os.path.isfile") as mock_isfile: + mock_isfile.return_value = True + ssh_handler.copy_to_server(sftp, "Jim", "Joe", startup.LOG) + sftp.put.assert_called_with("Jim", "Joe") + + @patch("os.listdir") + def test_copy_to_server_folder(self, mock_listdir): + sftp = Mock() + sftp.mkdir = MagicMock() + mock_listdir.return_value = [] + with patch("os.path.isfile") as mock_isfile: + mock_isfile.return_value = False + ssh_handler.copy_to_server(sftp, "Jim", "Joe", startup.LOG) + mock_listdir.assert_called_with("Jim") + sftp.mkdir.assert_called_with("Joe") + + @patch("logging.info") + def test_is_active(self, mock_log): + client = Mock() + client.connect = MagicMock(return_value=True) + self.assertFalse(ssh_handler.is_active(client, 42, 32, 22, startup.LOG, {}, timeout=5)) + mock_log.assert_not_called() + + def test_is_active_on_second_attempt(self): + client = Mock() + client.connect = MagicMock(side_effect=[NoValidConnectionsError({('127.0.0.1', 22): socket.error}), True]) + self.assertFalse(ssh_handler.is_active(client, 42, 32, 22, startup.LOG, {}, timeout=5)) + + def test_is_active_exception(self): + client = Mock() + client.connect = MagicMock(side_effect=NoValidConnectionsError({('127.0.0.1', 22): socket.error})) + with self.assertRaises(ConnectionException): + ssh_handler.is_active(client, 42, 32, 22, startup.LOG, {}, timeout=0) + client.connect.assert_called_with(hostname=42, username=22, pkey=32, timeout=7, auth_timeout=5, port=22) + + @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh_cml_commands") + @patch("paramiko.ECDSAKey.from_private_key_file") + @patch("paramiko.SSHClient") + def test_execute_ssh(self, mock_client, mock_paramiko_key, mock_exec): + mock_paramiko_key.return_value = 2 + client = Mock() + mock = Mock() + mock_client.return_value = mock + mock.__enter__ = client + mock.__exit__ = Mock(return_value=None) + with patch("bibigrid.core.utility.handler.ssh_handler.is_active") as mock_active: + ssh_handler.execute_ssh(42, 32, 22, startup.LOG, {}, [12]) + mock_client.assert_called_with() + mock_active.assert_called_with(client=client(), floating_ip_address=42, username=22, private_key=2, + log=startup.LOG, gateway={}) + mock_exec.assert_called_with(client=client(), commands=[12], log=startup.LOG) + mock_paramiko_key.assert_called_with(32) + + @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") + def test_ansible_preparation(self, mock_execute): + ssh_handler.ansible_preparation(1, 2, 3, startup.LOG, {}, [], []) + mock_execute.assert_called_with(1, 2, 3, startup.LOG, {}, ssh_handler.ANSIBLE_SETUP, + [(2, ssh_handler.PRIVATE_KEY_FILE)]) + + @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") + def test_ansible_preparation_elem(self, mock_execute): + ssh_handler.ansible_preparation(1, 2, 3, startup.LOG, {}, [42], [42]) + mock_execute.assert_called_with(1, 2, 3, startup.LOG, {}, ssh_handler.ANSIBLE_SETUP + [42], + [42, (2, ssh_handler.PRIVATE_KEY_FILE)]) + + def test_execute_ssh_cml_commands(self): + client = Mock() + stdout_mock = Mock() + stdout_mock.channel.recv_exit_status.side_effect = [0, 0] + stdout_mock.readline.side_effect = ["First Line", "", "First Line", ""] + client.exec_command.return_value = (0, stdout_mock, 2) + commands = [(42, 0), (21, 1)] + ssh_handler.execute_ssh_cml_commands(client, commands, startup.LOG) + + stdout_mock.channel.recv_exit_status.assert_called() + stdout_mock.channel.recv_exit_status.call_count = 2 + stdout_mock.readline.assert_called() + assert stdout_mock.readline.call_count == 4 + client.exec_command.assert_has_calls([call(42), call(21)]) + assert client.exec_command.call_count == 2 + + def test_execute_ssh_cml_commands_execution_exception(self): + client = Mock() + stdout_mock = Mock() + stdout_mock.channel.recv_exit_status.side_effect = [0, 1] + stdout_mock.readline.side_effect = ["First Line", "", "First Line", ""] + client.exec_command.return_value = (0, stdout_mock, 2) + commands = [(42, 0), (21, 1)] + with self.assertRaises(ExecutionException): + ssh_handler.execute_ssh_cml_commands(client, commands, startup.LOG) + stdout_mock.channel.recv_exit_status.assert_called() + stdout_mock.readline.assert_called() + client.exec_command.assert_called_with(21) diff --git a/tests/test_startup.py b/tests/test_startup.py old mode 100644 new mode 100755 index 3baee61a..64f154d9 --- a/tests/test_startup.py +++ b/tests/test_startup.py @@ -1,3 +1,7 @@ +""" +Modul to test startup +""" + from unittest import TestCase from unittest.mock import Mock, patch, MagicMock @@ -5,44 +9,48 @@ class TestStartup(TestCase): - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') - def test_provider(self, mock_get_providers): + """ + Class to test startup + """ + + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') + def test_provider_closing(self, mock_get_providers): args = Mock() - args.list_clusters = True + args.list = True args.version = False args.cluster_id = 12 provider = Mock provider.close = MagicMock() configurations = {} mock_get_providers.return_value = [provider] - with patch("bibigrid.core.actions.list_clusters.print_list_clusters") as mock_lc: + with patch("bibigrid.core.actions.list_clusters.log_list") as mock_lc: mock_lc.return_value = 42 self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_get_providers.assert_called_with(configurations) + mock_get_providers.assert_called_with(configurations, startup.LOG) provider.close.assert_called() - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') def test_list_clusters(self, get_providers): provider_mock = Mock() provider_mock.close = Mock() get_providers.return_value = [provider_mock] args = Mock() - args.list_clusters = True + args.list = True args.version = False args.cluster_id = 12 configurations = {} - with patch("bibigrid.core.actions.list_clusters.print_list_clusters") as mock_lc: + with patch("bibigrid.core.actions.list_clusters.log_list") as mock_lc: mock_lc.return_value = 42 self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_lc.assert_called_with(12, [provider_mock]) + mock_lc.assert_called_with(12, [provider_mock], startup.LOG) - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') def test_check(self, get_providers): provider_mock = Mock() provider_mock.close = Mock() get_providers.return_value = [provider_mock] args = Mock() - args.list_clusters = False + args.list = False args.version = False args.check = True args.cluster_id = 12 @@ -50,16 +58,16 @@ def test_check(self, get_providers): with patch("bibigrid.core.actions.check.check") as mock_lc: mock_lc.return_value = 42 self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_lc.assert_called_with(configurations, [provider_mock]) + mock_lc.assert_called_with(configurations, [provider_mock], startup.LOG) - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') @patch('bibigrid.core.actions.create.Create') def test_create(self, mock_create, get_providers): provider_mock = Mock() provider_mock.close = Mock() get_providers.return_value = [provider_mock] args = Mock() - args.list_clusters = False + args.list = False args.version = False args.check = False args.create = True @@ -70,17 +78,17 @@ def test_create(self, mock_create, get_providers): creator.create = MagicMock(return_value=42) mock_create.return_value = creator self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_create.assert_called_with(providers=[provider_mock], configurations=configurations, debug=True, - config_path="") + mock_create.assert_called_with(providers=[provider_mock], configurations=configurations, log=startup.LOG, + debug=True, config_path="") creator.create.assert_called() - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') def test_terminate(self, get_providers): provider_mock = Mock() provider_mock.close = Mock() get_providers.return_value = [provider_mock] args = Mock() - args.list_clusters = False + args.list = False args.version = False args.create = False args.check = False @@ -88,27 +96,27 @@ def test_terminate(self, get_providers): args.cluster_id = 12 args.debug = True configurations = {} - with patch("bibigrid.core.actions.terminateCluster.terminate_cluster") as mock_tc: + with patch("bibigrid.core.actions.terminate.terminate") as mock_tc: mock_tc.return_value = 42 self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_tc.assert_called_with(12, [provider_mock], True) + mock_tc.assert_called_with(cluster_id=12, providers=[provider_mock], log=startup.LOG, debug=True) - @patch('bibigrid.core.utility.handler.providerHandler.get_providers') + @patch('bibigrid.core.utility.handler.provider_handler.get_providers') @patch("bibigrid.core.actions.ide.ide") def test_ide(self, mock_ide, get_providers): - provider_mock = Mock() + provider_mock = MagicMock() provider_mock.close = Mock() get_providers.return_value = [provider_mock] args = Mock() - args.list_clusters = False + args.list = False args.version = False args.create = False args.check = False - args.terminate_cluster = False + args.terminate = False args.ide = True args.cluster_id = 12 args.debug = True - configurations = {} + configurations = [{"test_key": "test_value"}] mock_ide.return_value = 42 self.assertTrue(startup.run_action(args, configurations, "") == 42) - mock_ide.assert_called_with(12, [provider_mock], {}) + mock_ide.assert_called_with(12, provider_mock, {"test_key": "test_value"}, startup.LOG) diff --git a/tests/test_terminateCluster.py b/tests/test_terminateCluster.py deleted file mode 100644 index 4d9348a6..00000000 --- a/tests/test_terminateCluster.py +++ /dev/null @@ -1,37 +0,0 @@ -from unittest import TestCase -from unittest.mock import MagicMock, patch - -from bibigrid.core.actions import create -from bibigrid.core.actions import terminate_cluster - - -class TestTerminate(TestCase): - - @patch("bibigrid.core.actions.terminate_cluster.terminate_output") - def test_terminate_cluster(self, _, mock_output): - provider = MagicMock() - provider.cloud_specification["auth"]["project_name"] = 32 - cluster_id = 42 - provider.list_servers.return_value = [ - {"name": create.MASTER_IDENTIFIER + create.SEPARATOR + str(cluster_id), "id": 21}] - provider.delete_server.return_value = True - provider.delete_keypair.return_value = True - terminate_cluster.terminate_cluster(str(cluster_id), [provider], False) - provider.delete_server.assert_called_with(21) - provider.delete_keypair.assert_called_with( - create.KEY_PREFIX + provider.cloud_specification["auth"]["project_name"] + - create.SEPARATOR + str(cluster_id)) - mock_output.assert_called_with([provider.delete_server.return_value], - [provider.delete_keypair.return_value], str(cluster_id)) - - @patch("logging.info") - def test_terminate_cluster_none(self, _): - provider = MagicMock() - provider[0].specification["auth"]["project_name"] = "test_project_name" - cluster_id = 42 - provider.list_servers.return_value = [ - {"name": create.MASTER_IDENTIFIER + create.SEPARATOR + str(cluster_id + 1), "id": 21}] - provider.delete_keypair.return_value = False - terminate_cluster.terminate_cluster(str(cluster_id), [provider], False) - provider.delete_server.assert_not_called() - provider.delete_keypair.assert_called_with('bibigrid42') # since keypair is not called diff --git a/tests/test_terminate_cluster.py b/tests/test_terminate_cluster.py new file mode 100644 index 00000000..949b0d1c --- /dev/null +++ b/tests/test_terminate_cluster.py @@ -0,0 +1,50 @@ +""" +Module to test terminate +""" +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from bibigrid.core import startup +from bibigrid.core.actions import create +from bibigrid.core.actions import terminate + + +class TestTerminate(TestCase): + """ + Class to test terminate. + """ + + @patch("bibigrid.core.actions.terminate.delete_local_keypairs") + @patch("bibigrid.core.actions.terminate.terminate_output") + def test_terminate(self, mock_output, mock_local): + mock_local.return_value = True + provider = MagicMock() + provider.cloud_specification["auth"]["project_name"] = 32 + cluster_id = 42 + provider.list_servers.return_value = [{"name": create.MASTER_IDENTIFIER(cluster_id=str(cluster_id)), "id": 21}] + provider.delete_server.return_value = True + provider.delete_keypair.return_value = True + provider.delete_security_group.return_value = True + provider.delete_application_credentials.return_value = True + terminate.terminate(str(cluster_id), [provider], startup.LOG, False, True) + provider.delete_server.assert_called_with(21) + provider.delete_keypair.assert_called_with(create.KEY_NAME.format(cluster_id=cluster_id)) + mock_output.assert_called_with([provider.delete_server.return_value], [provider.delete_keypair.return_value], + [provider.delete_security_group.return_value], + provider.delete_application_credentials.return_value, str(cluster_id), + startup.LOG) + + @patch("bibigrid.core.actions.terminate.delete_local_keypairs") + @patch("logging.info") + def test_terminate_none(self, _, mock_local): + mock_local.return_value = True + provider = MagicMock() + provider[0].specification["auth"]["project_name"] = "test_project_name" + cluster_id = 42 + provider.list_servers.return_value = [ + {"name": create.MASTER_IDENTIFIER(cluster_id=str(cluster_id + 1)), "id": 21}] + provider.delete_keypair.return_value = False + terminate.terminate(str(cluster_id), [provider], startup.LOG, False, True) + provider.delete_server.assert_not_called() + provider.delete_keypair.assert_called_with( + create.KEY_NAME.format(cluster_id=str(cluster_id))) # since keypair is not called diff --git a/tests/test_validate_configuration.py b/tests/test_validate_configuration.py new file mode 100644 index 00000000..5deb65c8 --- /dev/null +++ b/tests/test_validate_configuration.py @@ -0,0 +1,318 @@ +""" +Tests for validate configuration +""" + +import os +from unittest import TestCase +from unittest.mock import Mock, patch, MagicMock, call + +from bibigrid.core.utility import validate_configuration +from bibigrid.models.exceptions import ImageNotActiveException + + +class TestValidateConfiguration(TestCase): + """ + Class to test ValidateConfiguration + """ + + # pylint: disable=R0904 + def test_check_provider_data_count(self): + provider_data_1 = {"PROJECT_ID": "abcd", "PROJECT_NAME": "1234"} + provider_data_2 = {"PROJECT_ID": "9999", "PROJECT_NAME": "9999"} + self.assertTrue(validate_configuration.check_provider_data([provider_data_1, provider_data_2], 2, log=Mock())) + self.assertFalse(validate_configuration.check_provider_data([provider_data_1, provider_data_2], 3, log=Mock())) + self.assertTrue(validate_configuration.check_provider_data([], 0, log=Mock())) + + def test_check_provider_data_unique(self): + provider_data_1 = {"PROJECT_ID": "abcd", "PROJECT_NAME": "1234"} + provider_data_2 = {"PROJECT_ID": "9999", "PROJECT_NAME": "9999"} + self.assertTrue(validate_configuration.check_provider_data([provider_data_1, provider_data_2], 2, log=Mock())) + self.assertFalse(validate_configuration.check_provider_data([provider_data_1, provider_data_1], 2, log=Mock())) + self.assertTrue(validate_configuration.check_provider_data([], 0, log=Mock())) + + @patch("bibigrid.core.utility.image_selection.select_image") + def test_check_master_vpn_worker_ordered(self, mock_select_image): # pylint: disable=unused-argument + master = {"masterInstance": "Value"} + vpn = {"vpnInstance": "Value"} + vpn_master = {} + vpn_master.update(master) + vpn_master.update(vpn) + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=[master], log=Mock()) + self.assertTrue(v_c.check_master_vpn_worker()) + v_c.configurations = [master, vpn] + self.assertTrue(v_c.check_master_vpn_worker()) + v_c.configurations = [vpn] + self.assertFalse(v_c.check_master_vpn_worker()) + v_c.configurations = [master, master] + self.assertFalse(v_c.check_master_vpn_worker()) + + def test_check_master_vpn_worker_unique(self): + master = {"masterInstance": "Value"} + vpn = {"vpnInstance": "Value"} + vpn_master = {} + vpn_master.update(master) + vpn_master.update(vpn) + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=[vpn_master], log=Mock()) + self.assertFalse(v_c.check_master_vpn_worker()) + v_c.configurations = [master, vpn_master] + self.assertFalse(v_c.check_master_vpn_worker()) + + def test_evaluate(self): + self.assertTrue(validate_configuration.evaluate("some", True, log=Mock())) + self.assertFalse(validate_configuration.evaluate("some", False, log=Mock())) + + def test_check_provider_connection(self): + mock = MagicMock() + mock.conn = False + v_c = validate_configuration.ValidateConfiguration(providers=[mock], configurations=None, log=Mock()) + self.assertFalse(v_c.check_provider_connections()) + mock.conn = True + self.assertTrue(v_c.check_provider_connections()) + + def test_check_instances_master(self): + v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"masterInstance": "42"}], + log=Mock()) + with patch.object(v_c, "check_instance") as mock: + v_c.check_instances() + mock.assert_called_with("masterInstance", "42", "31") + + def test_check_instances_vpn(self): + v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"vpnInstance": "42"}], + log=Mock()) + with patch.object(v_c, "check_instance") as mock: + v_c.check_instances() + mock.assert_called_with("vpnInstance", "42", "31") + + def test_check_instances_vpn_worker(self): + v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[ + {"masterInstance": "42", "workerInstances": ["42"]}], log=Mock()) + with patch.object(v_c, "check_instance") as mock: + v_c.check_instances() + mock.assert_called_with("workerInstance", "42", "31") + + def test_check_instances_vpn_master_missing(self): + v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{}], log=Mock()) + self.assertFalse(v_c.check_instances()) + v_c = validate_configuration.ValidateConfiguration(providers=["31"], + configurations=[{"workerInstances": ["42"]}], log=Mock()) + self.assertFalse(v_c.check_instances()) + + def test_check_instances_vpn_master_count(self): + for i in range(1, 4): + v_c = validate_configuration.ValidateConfiguration(providers=["31"] * i, + configurations=[{"masterInstance": {"count": 1}}] + [ + {"vpnInstance": {"count": 1}}] * (i - 1), log=Mock()) + # with patch.object(v_c, "check_instance") as mock: + with patch.object(v_c, "check_instance", return_value=True): + v_c.check_instances() + self.assertTrue(v_c.required_resources_dict["floating_ips"] == i) + + @patch("bibigrid.core.utility.image_selection.select_image") + def test_check_instance_image_not_active(self, mock_select_image): + mock_select_image.side_effect = ImageNotActiveException() + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) + provider = Mock() + provider.get_active_images.return_value = [] + provider.get_flavor = MagicMock(return_value={"disk": None, "ram": None}) + provider.get_image_by_id_or_name = MagicMock(return_value={"min_disk": None, "min_ram": None}) + self.assertFalse(v_c.check_instance(None, {"count": 1, "image": 2, "type": 3}, provider)) + + @patch("bibigrid.core.utility.image_selection.select_image") + def test_check_instance_image_active_combination_call(self, mock_select_image): + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) + provider = Mock() + provider.get_image_by_id_or_name = MagicMock(return_value={"status": "active"}) + with patch.object(v_c, "check_instance_type_image_combination") as mock: + v_c.check_instance(42, {"count": 1, "image": 2, "type": 3}, provider) + mock.assert_called_with(3, mock_select_image(2), provider) + + def test_check_instance_type_image_combination_has_enough_calls(self): + log = Mock() + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=log) + provider = MagicMock() + provider.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} + provider.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + with patch.object(validate_configuration, "has_enough") as mock: + v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider) + self.assertEqual(call(42, 22, "Type de.NBI tiny", "disk space", log), mock.call_args_list[0]) + self.assertEqual(call(32, 12, "Type de.NBI tiny", "ram", log), mock.call_args_list[1]) + + def test_check_instance_type_image_combination_result(self): + provider = MagicMock() + provider.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} + provider.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) + with patch.object(validate_configuration, "has_enough") as mock: + mock.side_effect = [True, True, False, False, True, False, False, True] + # True True + self.assertTrue(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider)) + # False False + self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider)) + # True False + self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider)) + # False True + self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider)) + + def test_check_instance_type_image_combination_count(self): + for i in range(3): + provider = MagicMock() + provider.get_flavor.return_value = {"disk": 42, "ram": i * 32, "vcpus": i * 10} + provider.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + log = Mock() + v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=log) + with patch.object(validate_configuration, "has_enough") as mock: + v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", + provider=provider) + self.assertEqual(32 * i, v_c.required_resources_dict["total_ram"]) + self.assertEqual(10 * i, v_c.required_resources_dict["total_cores"]) + mock.assert_called_with(32 * i, 12, 'Type de.NBI tiny', 'ram', log) + + def test_check_volumes_none(self): + v_c = validate_configuration.ValidateConfiguration(providers=[42], configurations=[{}], log=Mock()) + self.assertTrue(v_c.check_volumes()) + + def test_check_volumes_mismatch(self): + provider = Mock() + provider.get_volume_by_id_or_name = MagicMock(return_value=None) + provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"masterMounts": ["Test"]}], log=Mock()) + self.assertFalse(v_c.check_volumes()) + + def test_check_volumes_match_snapshot(self): + provider = Mock() + provider.get_volume_by_id_or_name = MagicMock(return_value=None) + provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": 1}) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"masterMounts": ["Test"]}], log=Mock()) + self.assertTrue(v_c.check_volumes()) + + def test_check_volumes_match_snapshot_count(self): + for i in range(3): + provider = Mock() + provider.get_volume_by_id_or_name = MagicMock(return_value=None) + provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": i}) + v_c = validate_configuration.ValidateConfiguration(providers=[provider] * i, + configurations=[{"masterMounts": ["Test"] * i}], + log=Mock()) + self.assertTrue(v_c.check_volumes()) + self.assertTrue(v_c.required_resources_dict["Volumes"] == i) + self.assertTrue(v_c.required_resources_dict["VolumeGigabytes"] == i ** 2) + + def test_check_volumes_match_volume(self): + provider = Mock() + provider.get_volume_by_id_or_name = MagicMock(return_value={"size": 1}) + provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"masterMounts": ["Test"]}], log=Mock()) + self.assertTrue(v_c.check_volumes()) + self.assertTrue(v_c.required_resources_dict["Volumes"] == 0) + self.assertTrue(v_c.required_resources_dict["VolumeGigabytes"] == 0) + + def test_check_network_none(self): + provider = Mock() + provider.get_network_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=[{}], log=Mock()) + self.assertFalse(v_c.check_network()) + + def test_check_network_no_network(self): + provider = Mock() + provider.get_subnet_by_id_or_name = MagicMock(return_value="network") + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"subnet": "subnet_name"}], log=Mock()) + self.assertTrue(v_c.check_network()) + provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") + + def test_check_network_no_network_mismatch_subnet(self): + provider = Mock() + provider.get_subnet_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"subnet": "subnet_name"}], log=Mock()) + self.assertFalse(v_c.check_network()) + provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") + + def test_check_network_no_subnet_mismatch_network(self): + provider = Mock() + provider.get_network_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"network": "network_name"}], log=Mock()) + self.assertFalse(v_c.check_network()) + provider.get_network_by_id_or_name.assert_called_with("network_name") + + def test_check_network_no_subnet(self): + provider = Mock() + provider.get_network_by_id_or_name = MagicMock(return_value="network") + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"network": "network_name"}], log=Mock()) + self.assertTrue(v_c.check_network()) + provider.get_network_by_id_or_name.assert_called_with("network_name") + + def test_check_network_subnet_network(self): + provider = Mock() + provider.get_network_by_id_or_name = MagicMock(return_value="network") + provider.get_subnet_by_id_or_name = MagicMock(return_value="network") + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"network": "network_name"}], log=Mock()) + self.assertTrue(v_c.check_network()) + provider.get_network_by_id_or_name.assert_called_with("network_name") + + def test_check_server_group_none(self): + provider = Mock() + provider.get_network_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=[{}], log=Mock()) + self.assertTrue(v_c.check_server_group()) + + def test_check_server_group_mismatch(self): + provider = Mock() + provider.get_server_group_by_id_or_name = MagicMock(return_value=None) + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"serverGroup": "GroupName"}], log=Mock()) + self.assertFalse(v_c.check_server_group()) + provider.get_server_group_by_id_or_name.assert_called_with("GroupName") + + def test_check_server_group_match(self): + provider = Mock() + provider.get_server_group_by_id_or_name = MagicMock(return_value="Group") + v_c = validate_configuration.ValidateConfiguration(providers=[provider], + configurations=[{"serverGroup": "GroupName"}], log=Mock()) + self.assertTrue(v_c.check_server_group()) + provider.get_server_group_by_id_or_name.assert_called_with("GroupName") + + def test_check_quotas_true(self): + provider = MagicMock() + provider.cloud_specification = {"auth": {"project_name": "name"}, "identifier": "identifier"} + test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, 'Volumes': 42, + 'VolumeGigabytes': 42, 'Snapshots': 42, 'Backups': 42, 'BackupGigabytes': 42} + provider.get_free_resources.return_value = test_dict + v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=None, log=Mock()) + with patch.object(validate_configuration, "has_enough") as mock: + mock.side_effect = [True] * len(test_dict) + self.assertTrue(v_c.check_quotas()) + provider.get_free_resources.assert_called() + + def test_check_quotas_false(self): + provider = MagicMock() + test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, 'Volumes': 42, + 'VolumeGigabytes': 42, 'Snapshots': 42, 'Backups': 42, 'BackupGigabytes': 42} + provider.get_free_resources.return_value = test_dict + os.environ['OS_PROJECT_NAME'] = "name" + v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=None, log=Mock()) + with patch.object(validate_configuration, "has_enough") as mock: + mock.side_effect = [True] * (len(test_dict) - 1) + [False] + self.assertFalse(v_c.check_quotas()) + provider.get_free_resources.assert_called() + mock.assert_called() + + def test_has_enough_lower(self): + self.assertTrue(validate_configuration.has_enough(2, 1, "", "", log=Mock())) + + def test_has_enough_equal(self): + self.assertTrue(validate_configuration.has_enough(2, 2, "", "", log=Mock())) + + def test_has_enough_higher(self): + self.assertFalse(validate_configuration.has_enough(1, 2, "", "", log=Mock())) From 26c6738e2e9c03a7f3d8d686bbbd027ce15744f4 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:25:57 +0100 Subject: [PATCH 044/145] Introduced yaml lock (#464) * removed unnecessary close * simplified update_hosts * updated logging to separate folder and file based on creation date * many small changes and introducing locks * restructured log files again. Removed outdated key warnings from bibigrid.yml * added a few logs * further improved logging hierarchy * Added specific folder places for temporary job storage. This might solve the "SlurmSpoolDir full" bug. * Improved logging * Tried to fix temps and tried update to 23.11 but has errors so commented that part out * added initial space --- bibigrid.yml | 8 ++--- bibigrid/core/actions/create.py | 3 +- bibigrid/core/actions/ide.py | 1 + bibigrid/core/utility/handler/ssh_handler.py | 5 +-- .../roles/bibigrid/files/slurm/cgroup.conf | 1 + .../roles/bibigrid/files/slurm/create.sh | 30 +++++++++++++++-- .../bibigrid/files/slurm/create_server.py | 22 +++++++------ .../bibigrid/files/slurm/delete_server.py | 6 +++- .../roles/bibigrid/files/slurm/fail.sh | 32 +++++++++++++++++-- .../roles/bibigrid/tasks/042-slurm-server.yml | 1 + .../roles/bibigrid/tasks/042-slurm.yml | 20 ++++++++++++ .../templates/slurm/job_container.conf | 5 +++ .../roles/bibigrid/templates/slurm/slurm.conf | 5 +++ 13 files changed, 117 insertions(+), 22 deletions(-) create mode 100644 resources/playbook/roles/bibigrid/templates/slurm/job_container.conf diff --git a/bibigrid.yml b/bibigrid.yml index 876f9e61..27481c9a 100644 --- a/bibigrid.yml +++ b/bibigrid.yml @@ -13,10 +13,10 @@ ## Volumes and snapshots that will be mounted to master # autoMount: False # WARNING: will overwrite unidentified filesystems - #masterMounts: # KEY NOT FULLY IMPLEMENTED YET + #masterMounts: # - [mount one] - #nfsShares: # KEY NOT FULLY IMPLEMENTED YET; /vol/spool/ is automatically created as a nfs + #nfsShares: /vol/spool/ is automatically created as a nfs # - [nfsShare one] ## Ansible (Galaxy) roles can be added for execution # KEY NOT IMPLEMENTED YET @@ -48,7 +48,7 @@ #nfs: True #ide: True # A nice way to view your cluster as if you were using Visual Studio Code - useMasterAsCompute: True # Currently ignored by slurm + useMasterAsCompute: True #waitForServices: # existing service name that runs after an instance is launched. BiBiGrid's playbook will wait until service is "stopped" to avoid issues # - de.NBI_Bielefeld_environment.service # uncomment for cloud site Bielefeld @@ -104,4 +104,4 @@ #features: # list - #- [next configurations] # KEY NOT IMPLEMENTED YET + #- [next configurations] diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index a31149ff..6f43920a 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -186,7 +186,7 @@ def start_vpn_or_master_instance(self, configuration, provider): server = provider.create_server(name=name, flavor=flavor, key_name=self.key_name, image=image, network=network, volumes=volumes, security_groups=configuration["security_groups"], wait=True) configuration["private_v4"] = server["private_v4"] - + self.log.debug(f"Created Server {name}: {server['private_v4']}.") # get mac address for given private address # Attention: The following source code works with Openstack and IPV4 only configuration["mac_addr"] = None @@ -201,6 +201,7 @@ def start_vpn_or_master_instance(self, configuration, provider): if identifier == VPN_WORKER_IDENTIFIER or (identifier == MASTER_IDENTIFIER and self.use_master_with_public_ip): configuration["floating_ip"] = \ provider.attach_available_floating_ip(network=external_network, server=server)["floating_ip_address"] + self.log.debug(f"Added floating ip {configuration['floating_ip']} to {name}.") elif identifier == MASTER_IDENTIFIER: configuration["floating_ip"] = server["private_v4"] # pylint: enable=comparison-with-callable configuration["volumes"] = provider.get_mount_info_from_server(server) diff --git a/bibigrid/core/actions/ide.py b/bibigrid/core/actions/ide.py index 1ad2c871..8f96cbd3 100644 --- a/bibigrid/core/actions/ide.py +++ b/bibigrid/core/actions/ide.py @@ -81,6 +81,7 @@ def ide(cluster_id, master_provider, master_configuration, log): ssh_pkey=used_private_key, local_bind_address=(LOCALHOST, used_local_bind_address), remote_bind_address=(LOCALHOST, REMOTE_BIND_ADDRESS)) as server: + log.debug(f"Used {used_local_bind_address} as the local binding address") log.log(42, "CTRL+C to close port forwarding when you are done.") with server: # opens in existing window if any default program exists diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index d52cdde6..f5c71ac3 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -109,8 +109,9 @@ def is_active(client, floating_ip_address, private_key, username, log, gateway, log.info(f"Using SSH Gateway {gateway.get('ip')}") octets = {f'oct{enum + 1}': int(elem) for enum, elem in enumerate(floating_ip_address.split("."))} port = int(sympy.sympify(gateway["portFunction"]).subs(dict(octets))) - client.connect(hostname=gateway.get("ip") or floating_ip_address, username=username, pkey=private_key, - timeout=7, auth_timeout=5, port=port) + log.info(f"Port {port} will be used (see {gateway['portFunction']} and octets {octets}).") + client.connect(hostname=gateway.get("ip") or floating_ip_address, username=username, + pkey=private_key, timeout=7, auth_timeout=5, port=port) establishing_connection = False log.info(f"Successfully connected to {floating_ip_address}") except paramiko.ssh_exception.NoValidConnectionsError as exc: diff --git a/resources/playbook/roles/bibigrid/files/slurm/cgroup.conf b/resources/playbook/roles/bibigrid/files/slurm/cgroup.conf index 657a243d..2705699f 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/cgroup.conf +++ b/resources/playbook/roles/bibigrid/files/slurm/cgroup.conf @@ -1,3 +1,4 @@ +# maybe this causes errors when using 23.11 https://slurm.schedmd.com/faq.html#cgroupv2 CgroupMountpoint="/sys/fs/cgroup" CgroupAutomount=yes ConstrainCores=no diff --git a/resources/playbook/roles/bibigrid/files/slurm/create.sh b/resources/playbook/roles/bibigrid/files/slurm/create.sh index 8c5e9507..e48d9954 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/create.sh +++ b/resources/playbook/roles/bibigrid/files/slurm/create.sh @@ -1,7 +1,33 @@ #!/bin/bash + +process_string() { + # Split the input string by "-" + IFS='-' read -ra elements <<< "$1" + + # Extract the second, fourth, and fifth elements + second=${elements[1]} + fourth=${elements[3]} + fifth=${elements[4]} + + # Replace undesired characters in the second element + second=$(echo "$second" | sed -E 's/worker-/worker_/; s/vpnwkr-/vpnwkr_/') + + # Check if the fifth element is not empty + if [[ ! -z $fifth ]]; then + echo "${second}_${fourth}-${fifth}" + else + echo "${second}_${fourth}" + fi +} + +mkdir -p worker_logs +mkdir -p worker_logs/create +mkdir -p worker_logs/create/out +mkdir -p worker_logs/create/err + # redirect stderr and stdout -exec >> /var/log/slurm/create.out.log -exec 2>> /var/log/slurm/create.err.log +exec >> "/var/log/slurm/worker_logs/create/out/$(process_string "$1")_$(date '+%Y-%m-%d_%H:%M:%S').log" +exec 2>> "/var/log/slurm/worker_logs/create/err/$(process_string "$1")_$(date '+%Y-%m-%d_%H:%M:%S').log" function log { echo "$(date) $*" diff --git a/resources/playbook/roles/bibigrid/files/slurm/create_server.py b/resources/playbook/roles/bibigrid/files/slurm/create_server.py index 13216619..d95d5c4f 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/create_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/create_server.py @@ -12,6 +12,7 @@ import sys import threading import time +from filelock import FileLock import ansible_runner import os_client_config @@ -142,19 +143,20 @@ def update_hosts(name, ip): # pylint: disable=invalid-name @param ip: ibibigrid-worker0-3k1eeysgetmg4vb-3p address @return: """ - hosts = {"host_entries": {}} - if os.path.isfile(HOSTS_FILE_PATH): + logging.info("Updating hosts.yml") + with FileLock("hosts.yml.lock"): + logging.info("Lock acquired") with open(HOSTS_FILE_PATH, mode="r", encoding="utf-8") as hosts_file: hosts = yaml.safe_load(hosts_file) - hosts_file.close() - if hosts is None or "host_entries" not in hosts.keys(): + logging.info(f"Existing hosts {hosts}") + if not hosts or "host_entries" not in hosts: + logging.info(f"Resetting host entries because {'first run' if hosts else 'broken'}.") hosts = {"host_entries": {}} - - hosts["host_entries"][name] = ip - - with open(HOSTS_FILE_PATH, mode="w", encoding="utf-8") as hosts_file: - yaml.dump(hosts, hosts_file) - hosts_file.close() + hosts["host_entries"][name] = ip + logging.info(f"Added host {name} with ip {hosts['host_entries'][name]}") + with open(HOSTS_FILE_PATH, mode="w", encoding="utf-8") as hosts_file: + yaml.dump(hosts, hosts_file) + logging.info("Wrote hosts file. Released hosts.yml.lock.") def configure_dns(): diff --git a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py index c7b080c5..7b7fc674 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py @@ -20,10 +20,13 @@ logging.info("delete_server.py started") start_time = time.time() +logging.info(f"Terminate parameter: {sys.argv[1]}") + if len(sys.argv) < 2: logging.warning("usage: $0 instance1_name[,instance2_name,...]") logging.info("Your input %s with length %s", sys.argv, len(sys.argv)) sys.exit(1) + terminate_workers = sys.argv[1].split("\n") logging.info("Deleting instances %s", terminate_workers) @@ -61,7 +64,8 @@ logging.warning(f"Couldn't delete worker {terminate_worker}") else: logging.info(f"Deleted {terminate_worker}") -logging.info("Successful delete_server.py execution!") + +logging.info(f"Successful delete_server.py execution ({sys.argv[1]})!") time_in_s = time.time() - start_time logging.info("--- %s minutes and %s seconds ---", math.floor(time_in_s / 60), time_in_s % 60) logging.info("Exit Code 0") diff --git a/resources/playbook/roles/bibigrid/files/slurm/fail.sh b/resources/playbook/roles/bibigrid/files/slurm/fail.sh index e282da18..436f8b59 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/fail.sh +++ b/resources/playbook/roles/bibigrid/files/slurm/fail.sh @@ -1,8 +1,32 @@ #!/bin/bash +process_string() { + # Split the input string by "-" + IFS='-' read -ra elements <<< "$1" + + # Extract the second, fourth, and fifth elements + second=${elements[1]} + fourth=${elements[3]} + fifth=${elements[4]} + + # Replace undesired characters in the second element + second=$(echo "$second" | sed -E 's/worker-/worker_/; s/vpnwkr-/vpnwkr_/') + + # Check if the fifth element is not empty + if [[ ! -z $fifth ]]; then + echo "${second}_${fourth}-${fifth}" + else + echo "${second}_${fourth}" + fi +} + +mkdir -p worker_logs +mkdir -p worker_logs/fail/out +mkdir -p worker_logs/fail/err + # redirect stderr and stdout -exec >> /var/log/slurm/fail.out.log -exec 2>> /var/log/slurm/fail.err.log +exec >> "/var/log/slurm/worker_logs/fail/out/$(process_string "$1")_$(date '+%Y-%m-%d_%H:%M:%S').log" +exec 2>> "/var/log/slurm/worker_logs/fail/err/$(process_string "$1")_$(date '+%Y-%m-%d_%H:%M:%S').log" function log { echo "$(date) $*" @@ -15,7 +39,11 @@ scontrol update NodeName="$1" state=RESUME reason=FailedStartup # no sudo needed hosts=$(scontrol show hostnames "$1") +echo "Hosts $hosts used" + # delete servers python3 /usr/local/bin/delete_server.py "${hosts}" +echo "Finished delete_server.py execution." + exit $? diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml index 96ea89bf..69f0098f 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml @@ -169,6 +169,7 @@ - python-openstackclient==6.0.0 - openstacksdk==0.62.0 - os_client_config + - filelock - paramiko - ansible-runner diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm.yml b/resources/playbook/roles/bibigrid/tasks/042-slurm.yml index 3d2cb4aa..49253ca0 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm.yml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm.yml @@ -15,6 +15,15 @@ - slurm-full - munge +# - name: Download Slurm (TEMPORARY) +# get_url: +# url: "https://docs.cebitec.uni-bielefeld.de/s/FjCP3xQPPnBwSy9/download?path=%2F&files=slurm-full_23.11.0-0_amd64.deb" # Replace with your package link +# dest: "/tmp/package.deb" # Destination where the package will be saved +# - name: Install Slurm package +# apt: +# deb: "/tmp/package.deb" +# state: present # Install the package if not already installed + - name: Create new secret (Munge) copy: content: '{{ slurm_conf.munge_key }}' @@ -84,6 +93,17 @@ - slurmctld - slurmd +- name: Create Job Container configuration + template: + src: slurm/job_container.conf + dest: /etc/slurm/job_container.conf + owner: slurm + group: root + mode: 0444 + notify: + - slurmctld + - slurmd + - name: Slurm cgroup configuration copy: src: slurm/cgroup.conf diff --git a/resources/playbook/roles/bibigrid/templates/slurm/job_container.conf b/resources/playbook/roles/bibigrid/templates/slurm/job_container.conf new file mode 100644 index 00000000..caa6a91c --- /dev/null +++ b/resources/playbook/roles/bibigrid/templates/slurm/job_container.conf @@ -0,0 +1,5 @@ +NodeName={{ name }} AutoBasePath=true BasePath={{ '/vol/scratch/storage' if flavor.ephemeral else '/var/local/storage' }} +{% for worker_group in groups | select('match', '^bibigrid_worker_*') %} +{% set first_worker = groups[worker_group] | first %} +NodeName={{ hostvars[first_worker].name }} AutoBasePath=true BasePath={{ '/vol/scratch/storage' if hostvars[first_worker].flavor.ephemeral else '/var/local/storage' }} +{% endfor %} \ No newline at end of file diff --git a/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf b/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf index 2bb2854e..767aa8ca 100644 --- a/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf +++ b/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf @@ -109,3 +109,8 @@ SlurmctldParameters=idle_on_node_suspend PrivateData=cloud # return node to idle when startup fails ResumeFailProgram=/opt/slurm/fail.sh + +# job container +# TO BE TESTED +JobContainerType=job_container/tmpfs +PrologFlags=Contain \ No newline at end of file From 8182901536a3a96b799c2283fa11ab89b1a429ac Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:26:25 +0100 Subject: [PATCH 045/145] added existing worker deletion on worker startup if worker already exists as no worker would've been started if Slurm would've known about the existing worker. This is not the best solution. (#468) --- .../roles/bibigrid/files/slurm/create_server.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/resources/playbook/roles/bibigrid/files/slurm/create_server.py b/resources/playbook/roles/bibigrid/files/slurm/create_server.py index d95d5c4f..ab9d2c12 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/create_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/create_server.py @@ -69,6 +69,16 @@ def start_server(worker, start_worker_group, start_data): try: logging.info("Create server %s.", worker) connection = connections[start_worker_group["cloud_identifier"]] + # check if running + already_running_server = connection.get_server(worker) + if already_running_server: + logging.warning( + f"Already running server {worker} on {start_worker_group['cloud_identifier']} (will be terminated): " + f"{already_running_server}") + server_deleted = connection.delete_server(worker) + logging.info( + f"Server {worker} on {start_worker_group['cloud_identifier']} has been terminated ({server_deleted}). " + f"Continuing startup.") # check for userdata userdata = "" userdata_file_path = f"/opt/slurm/userdata_{start_worker_group['cloud_identifier']}.txt" From a7b16612bd59a202a83d865fd89bd0b22ec23ce1 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:26:58 +0100 Subject: [PATCH 046/145] made waitForServices a cloud specific key (#465) --- bibigrid/core/utility/ansible_configurator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 65e81aaa..642e9ae4 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -102,6 +102,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py features = set(configuration_features + worker_features) if features: worker_dict["features"] = features + pass_through(configuration, worker_dict, "waitForServices", "wait_for_services") write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, group_name), worker_dict, log) vpngtw = configuration.get("vpnInstance") if vpngtw: @@ -119,6 +120,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False)} if configuration.get("wireguard_peer"): vpngtw_dict["wireguard"] = {"ip": wireguard_ip, "peer": configuration.get("wireguard_peer")} + pass_through(configuration, vpngtw_dict, "waitForServices", "wait_for_services") write_yaml(os.path.join(aRP.HOST_VARS_FOLDER, name), vpngtw_dict, log) else: master = configuration["masterInstance"] @@ -132,6 +134,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False)} if configuration.get("wireguard_peer"): master_dict["wireguard"] = {"ip": "10.0.0.1", "peer": configuration.get("wireguard_peer")} + pass_through(configuration, master_dict, "waitForServices", "wait_for_services") write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, "master.yml"), master_dict, log) @@ -194,7 +197,7 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us master_configuration.get("zabbixConf", {}), strategy=mergedeep.Strategy.TYPESAFE_REPLACE) - for from_key, to_key in [("waitForServices", "wait_for_services"), ("ansibleRoles", "ansible_roles"), + for from_key, to_key in [("ansibleRoles", "ansible_roles"), ("ansibleGalaxyRoles", "ansible_galaxy_roles")]: pass_through(master_configuration, common_configuration_yaml, from_key, to_key) From cec4102707a73aad219cdfa4b6241220dc426a53 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:27:49 +0100 Subject: [PATCH 047/145] Improved log messages in validate_configuration.py to make fixing your configuration easier when using a hybrid-/multi-cloud setup (#466) * removed unnecessary line in provider.py and added cloud information to every log in validate_configuration.py for easier fixing. * track resources for providers separately to make quota checking precise * switched from low level cinder to high level block_storage.get_limits() --- bibigrid/core/provider.py | 1 - .../core/utility/validate_configuration.py | 125 +++++++++++------- bibigrid/openstack/openstack_provider.py | 16 +-- 3 files changed, 81 insertions(+), 61 deletions(-) diff --git a/bibigrid/core/provider.py b/bibigrid/core/provider.py index 160a32e1..133088cb 100644 --- a/bibigrid/core/provider.py +++ b/bibigrid/core/provider.py @@ -22,7 +22,6 @@ def __init__(self, cloud_specification): Call necessary methods to create a connection and save cloud_specification data as needed. """ self.cloud_specification = cloud_specification # contains sensitive information! - self.cloud_specification["identifier"] = self.cloud_specification['identifier'] @abstractmethod def create_application_credential(self, name=None): diff --git a/bibigrid/core/utility/validate_configuration.py b/bibigrid/core/utility/validate_configuration.py index a4232d2e..0da48d84 100644 --- a/bibigrid/core/utility/validate_configuration.py +++ b/bibigrid/core/utility/validate_configuration.py @@ -147,19 +147,21 @@ def check_cloud_yaml(cloud_specification, log): "application_credential_secret" in auth_keys): log.warning("Insufficient authentication information. Needs either password and username or " "if using application credentials: " - "auth_type, application_credential_id and application_credential_secret.") + "auth_type, application_credential_id and application_credential_secret. " + f"In cloud specification {cloud_specification.get('identifier')}") success = False if "auth_url" not in auth_keys: - log.warning("Authentification URL auth_url is missing.") + log.warning(f"Authentication URL auth_url is missing in cloud specification " + f"{cloud_specification.get('identifier')}") success = False else: - log.warning("Missing all auth information!") + log.warning(f"Missing all auth information in cloud specification {cloud_specification.get('identifier')}!") success = False if "region_name" not in keys: - log.warning("region_name is missing.") + log.warning(f"region_name is missing in cloud specification {cloud_specification.get('identifier')}.") success = False else: - log.warning("Missing all cloud_specification information!") + log.warning(f"{cloud_specification.get('identifier')} missing all cloud_specification information!") return success @@ -179,9 +181,12 @@ def __init__(self, configurations, providers, log): self.log = log self.configurations = configurations self.providers = providers - self.required_resources_dict = {'total_cores': 0, 'floating_ips': 0, 'instances': 0, 'total_ram': 0, - 'Volumes': 0, 'VolumeGigabytes': 0, 'Snapshots': 0, 'Backups': 0, - 'BackupGigabytes': 0} + + self.required_resources_dict = { + provider.cloud_specification['identifier']: {'total_cores': 0, 'floating_ips': 0, 'instances': 0, + 'total_ram': 0, 'volumes': 0, 'volume_gigabytes': 0, + 'snapshots': 0, 'backups': 0, 'backup_gigabytes': 0} for + provider in providers} def validate(self): """ @@ -222,10 +227,12 @@ def check_master_vpn_worker(self): """ self.log.info("Checking master/vpn") success = True - if not self.configurations[0].get("masterInstance") or self.configurations[0].get("vpnInstance"): + if not self.configurations[0].get("masterInstance"): + self.log.warning(f"{self.configurations[0].get('cloud')} has no master instance!") success = False for configuration in self.configurations[1:]: - if not configuration.get("vpnInstance") or configuration.get("masterInstance"): + if not configuration.get("vpnInstance"): + self.log.warning(f"{configuration.get('cloud')} has no vpn instance!") success = False return success @@ -238,10 +245,12 @@ def check_provider_connections(self): providers_unconnectable = [] for provider in self.providers: if not provider.conn: + self.log.warning(f"API connection to {providers_unconnectable} not successful. " + f"Please check your configuration for cloud " + f"{provider.cloud_specification['identifier']}.") providers_unconnectable.append(provider.cloud_specification["identifier"]) if providers_unconnectable: - self.log.warning("API connection to %s not successful. Please check your configuration.", - providers_unconnectable) + self.log.warning(f"Unconnected clouds: {providers_unconnectable}") success = False return success @@ -252,10 +261,9 @@ def check_instances(self): """ self.log.info("Checking instance images and type") success = True - configuration = None - try: - for configuration, provider in zip(self.configurations, self.providers): - self.required_resources_dict["floating_ips"] += 1 + for configuration, provider in zip(self.configurations, self.providers): + try: + self.required_resources_dict[provider.cloud_specification['identifier']]["floating_ips"] += 1 if configuration.get("masterInstance"): success = self.check_instance("masterInstance", configuration["masterInstance"], provider) and success @@ -263,9 +271,10 @@ def check_instances(self): success = self.check_instance("vpnInstance", configuration["vpnInstance"], provider) and success for worker in configuration.get("workerInstances", []): success = self.check_instance("workerInstance", worker, provider) and success - except KeyError as exc: - self.log.warning("Not found %s, but required in configuration %s.", str(exc), configuration) - success = False + except KeyError as exc: + self.log.warning("Not found %s, but required on %s.", str(exc), + provider.cloud_specification['identifier']) + success = False return success def check_instance(self, instance_name, instance, provider): @@ -276,17 +285,19 @@ def check_instance(self, instance_name, instance, provider): :param provider: provider :return: true if type and image compatible and existing """ - self.required_resources_dict["instances"] += instance.get("count") or 1 + self.required_resources_dict[provider.cloud_specification['identifier']]["instances"] += instance.get( + "count") or 1 instance_image_id_or_name = instance["image"] try: instance_image = image_selection.select_image(provider, instance_image_id_or_name, self.log) - self.log.info("Instance %s image: %s found", instance_name, instance_image_id_or_name) + self.log.info(f"Instance {instance_name} image: {instance_image_id_or_name} found on " + f"{provider.cloud_specification['identifier']}") instance_type = instance["type"] except ImageNotActiveException: - self.log.warning("Instance %s image: %s not found among active images.", - instance_name, instance_image_id_or_name) - self.log.log(42, "Available active images:") - self.log.log(42, "\n".join(provider.get_active_images())) + active_images = '\n'.join(provider.get_active_images()) + self.log.warning(f"Instance {instance_name} image: {instance_image_id_or_name} not found among" + f" active images on {provider.cloud_specification['identifier']}.\n" + f"Available active images:\n{active_images}") return False return self.check_instance_type_image_combination(instance_type, instance_image, provider) @@ -302,9 +313,9 @@ def check_instance_type_image_combination(self, instance_type, instance_image, p # check flavor = provider.get_flavor(instance_type) if not flavor: - self.log.warning("Flavor %s does not exist.", instance_type) - self.log.log(42, "Available flavors:") - self.log.log(42, "\n".join(provider.get_active_flavors())) + available_flavors = '\n'.join(provider.get_active_flavors()) + self.log.warning(f"Flavor {instance_type} does not exist on {provider.cloud_specification['identifier']}.\n" + f"Available flavors:\n{available_flavors}") return False type_max_disk_space = flavor["disk"] type_max_ram = flavor["ram"] @@ -314,8 +325,8 @@ def check_instance_type_image_combination(self, instance_type, instance_image, p (type_max_ram, image_min_ram, "ram")]: success = has_enough(maximum, needed, f"Type {instance_type}", thing, self.log) and success # prepare check quotas - self.required_resources_dict["total_ram"] += type_max_ram - self.required_resources_dict["total_cores"] += flavor["vcpus"] + self.required_resources_dict[provider.cloud_specification['identifier']]["total_ram"] += type_max_ram + self.required_resources_dict[provider.cloud_specification['identifier']]["total_cores"] += flavor["vcpus"] return success def check_volumes(self): @@ -338,14 +349,18 @@ def check_volumes(self): if not volume: snapshot = provider.get_volume_snapshot_by_id_or_name(volume_name_or_id) if not snapshot: - self.log.warning("Neither Volume nor Snapshot '%s' found", volume_name_or_id) + self.log.warning(f"Neither Volume nor Snapshot '{volume_name_or_id}' found on " + f"{provider.cloud_specification['identifier']}") success = False else: - self.log.info("Snapshot '%s' found", volume_name_or_id) - self.required_resources_dict["Volumes"] += 1 - self.required_resources_dict["VolumeGigabytes"] += snapshot["size"] + self.log.info(f"Snapshot '{volume_name_or_id}' found on " + f"{provider.cloud_specification['identifier']}.") + self.required_resources_dict[provider.cloud_specification['identifier']]["Volumes"] += 1 + self.required_resources_dict[provider.cloud_specification['identifier']][ + "VolumeGigabytes"] += snapshot["size"] else: - self.log.info(f"Volume '{volume_name_or_id}' found") + self.log.info(f"Volume '{volume_name_or_id}' found on " + f"{provider.cloud_specification['identifier']}.") return success def check_network(self): @@ -357,22 +372,29 @@ def check_network(self): success = True for configuration, provider in zip(self.configurations, self.providers): network_name_or_id = configuration.get("network") + subnet_name_or_id = configuration.get("subnet") if network_name_or_id: network = provider.get_network_by_id_or_name(network_name_or_id) if not network: - self.log.warning(f"Network '{network_name_or_id}' not found", network_name_or_id) + self.log.warning( + f"Network '{network_name_or_id}' not found on {provider.cloud_specification['identifier']}") success = False else: - self.log.info(f"Network '{network_name_or_id}' found") - subnet_name_or_id = configuration.get("subnet") - if subnet_name_or_id: + self.log.info( + f"Network '{network_name_or_id}' found on {provider.cloud_specification['identifier']}") + elif subnet_name_or_id: subnet = provider.get_subnet_by_id_or_name(subnet_name_or_id) if not subnet: - self.log.warning(f"Subnet '{subnet_name_or_id}' not found") + self.log.warning( + f"Subnet '{subnet_name_or_id}' not found on {provider.cloud_specification['identifier']}") success = False else: - self.log.info(f"Subnet '{subnet_name_or_id}' found") - return bool(success and (network_name_or_id or subnet_name_or_id)) + self.log.info(f"Subnet '{subnet_name_or_id}' found on {provider.cloud_specification['identifier']}") + else: + self.log.warning(f"Neither 'network' nor 'subnet' defined in configuration on " + f"{provider.cloud_specification['identifier']}.") + success = False + return success def check_server_group(self): """ @@ -384,10 +406,12 @@ def check_server_group(self): if server_group_name_or_id: server_group = provider.get_server_group_by_id_or_name(server_group_name_or_id) if not server_group: - self.log.warning("ServerGroup '%s' not found", server_group_name_or_id) + self.log.warning(f"ServerGroup '{server_group_name_or_id}' not found on " + f"{provider.cloud_specification['identifier']}") success = False else: - self.log.info("ServerGroup '%s' found", server_group_name_or_id) + self.log.info(f"ServerGroup '{server_group_name_or_id}' found on " + f"{provider.cloud_specification['identifier']}") return success def check_quotas(self): @@ -404,10 +428,9 @@ def check_quotas(self): self.log.info("required/available") for provider in self.providers: free_resources_dict = provider.get_free_resources() - for key, value in self.required_resources_dict.items(): + for key, value in self.required_resources_dict[provider.cloud_specification['identifier']].items(): success = has_enough(free_resources_dict[key], value, - f"Project {self.providers[0].cloud_specification['identifier']}", key, - self.log) and success + f"Project {provider.cloud_specification['identifier']}", key, self.log) and success return success def check_ssh_public_key_files(self): @@ -419,10 +442,11 @@ def check_ssh_public_key_files(self): for configuration in self.configurations: for ssh_public_key_file in configuration.get("sshPublicKeyFiles") or []: if not os.path.isfile(ssh_public_key_file): - self.log.warning("sshPublicKeyFile '%s' not found", ssh_public_key_file) + self.log.warning( + f"sshPublicKeyFile '{ssh_public_key_file}' not found on {configuration.get('cloud')}") success = False else: - self.log.info("sshPublicKeyFile '%s' found", ssh_public_key_file) + self.log.info(f"sshPublicKeyFile '{ssh_public_key_file}' found on {configuration.get('cloud')}") success = evaluate_ssh_public_key_file_security(ssh_public_key_file, self.log) and success return success @@ -437,8 +461,7 @@ def check_clouds_yamls(self): for index, cloud_specification in enumerate(cloud_specifications): if not check_cloud_yaml(cloud_specification, self.log): success = False - self.log.warning("Cloud specification %s is faulty. BiBiGrid understood %s.", index, - cloud_specification) + self.log.warning(f"Cloud specification {cloud_specification.get('identifier', index)} is faulty.") success = check_clouds_yaml_security(self.log) and success return success diff --git a/bibigrid/openstack/openstack_provider.py b/bibigrid/openstack/openstack_provider.py index ae97a2ec..db0517e3 100644 --- a/bibigrid/openstack/openstack_provider.py +++ b/bibigrid/openstack/openstack_provider.py @@ -171,22 +171,20 @@ def get_subnet_ids_by_network(self, network): def get_free_resources(self): """ - Uses the cinder API to get all relevant volume resources. - https://github.com/openstack/python-cinderclient/blob/master/cinderclient/v3/limits.py - Uses the nova API to get all relevant compute resources. Floating-IP is not returned correctly by openstack. + Uses openstack.block_storage to get all relevant volume resources. + Uses the openstack.compute to get all relevant compute resources. + Floating-IP is not returned correctly by openstack. :return: Dictionary containing the free resources """ compute_limits = dict(self.conn.compute.get_limits()["absolute"]) - # maybe needs limits.get(os.environ["OS_PROJECT_NAME"]) in the future - volume_limits_generator = self.cinder.limits.get().absolute - volume_limits = {absolut_limit.name: absolut_limit.value for absolut_limit in volume_limits_generator} + volume_limits = dict(self.conn.block_storage.get_limits()["absolute"]) # ToDo TotalVolumeGigabytes needs totalVolumeGigabytesUsed, but is not given - volume_limits["totalVolumeGigabytesUsed"] = 0 + volume_limits["total_volume_gigabytes_used"] = 0 free_resources = {} for key in ["total_cores", "floating_ips", "instances", "total_ram"]: free_resources[key] = compute_limits[key] - compute_limits[key + "_used"] - for key in ["Volumes", "VolumeGigabytes", "Snapshots", "Backups", "BackupGigabytes"]: - free_resources[key] = volume_limits["maxTotal" + key] - volume_limits["total" + key + "Used"] + for key in ["volumes", "volume_gigabytes", "snapshots", "backups", "backup_gigabytes"]: + free_resources[key] = volume_limits["max_total_" + key] - volume_limits["total_" + key + "_used"] return free_resources def get_volume_by_id_or_name(self, name_or_id): From a9eea47cd198d1b783c1e6070636508eb3c9d6bc Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 26 Feb 2024 17:42:18 +0100 Subject: [PATCH 048/145] added keyword for ssh_timeout and improved argument passing for ssh. --- bibigrid/core/actions/create.py | 37 +++-- bibigrid/core/startup.py | 2 +- bibigrid/core/utility/handler/ssh_handler.py | 140 ++++++++----------- 3 files changed, 75 insertions(+), 104 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 6f43920a..c70f5e3f 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -81,6 +81,7 @@ def __init__(self, providers, configurations, config_path, log, debug=False, clu self.ssh_user = configurations[0].get("sshUser") or "ubuntu" self.ssh_add_public_key_commands = ssh_handler.get_add_ssh_public_key_commands( configurations[0].get("sshPublicKeyFiles")) + self.ssh_timeout = configurations[0].get("sshTimeout", 5) self.config_path = config_path self.master_ip = None self.log.debug("Cluster-ID: %s", self.cluster_id) @@ -129,7 +130,7 @@ def generate_security_groups(self): """ Generate a security groups: - default with basic rules for the cluster - - wireguard when more than one provider is used (= multicloud) + - wireguard when more than one provider is used (= multi-cloud) """ self.log.info("Generating Security Groups") for provider, configuration in zip(self.providers, self.configurations): @@ -151,8 +152,7 @@ def generate_security_groups(self): for cidr in tmp_configuration['subnet_cidrs']: rules.append( {"direction": "ingress", "ethertype": "IPv4", "protocol": "tcp", "port_range_min": None, - "port_range_max": None, "remote_ip_prefix": cidr, - "remote_group_id": None}) + "port_range_max": None, "remote_ip_prefix": cidr, "remote_group_id": None}) provider.append_rules_to_security_group(default_security_group_id, rules) configuration["security_groups"] = [self.default_security_group_name] # store in configuration # when running a multi-cloud setup create an additional wireguard group @@ -232,17 +232,17 @@ def initialize_instances(self): Setup all servers """ for configuration in self.configurations: + ssh_data = {"floating_ip": configuration["floating_ip"], "private_key": KEY_FOLDER + self.key_name, + "username": self.ssh_user, "commands": None, "filepaths": None, + "gateway": configuration.get("gateway", {}), "timeout": self.ssh_timeout} if configuration.get("masterInstance"): self.master_ip = configuration["floating_ip"] - ssh_handler.ansible_preparation(floating_ip=configuration["floating_ip"], - private_key=KEY_FOLDER + self.key_name, username=self.ssh_user, - commands=self.ssh_add_public_key_commands, log=self.log, - gateway=configuration.get("gateway", {})) + ssh_data["commands"] = self.ssh_add_public_key_commands + ssh_handler.ANSIBLE_SETUP + ssh_data["filepaths"] = [(ssh_data["private_key"], ssh_handler.PRIVATE_KEY_FILE)] + ssh_handler.execute_ssh(ssh_data, self.log) elif configuration.get("vpnInstance"): - ssh_handler.execute_ssh(floating_ip=configuration["floating_ip"], - private_key=KEY_FOLDER + self.key_name, username=self.ssh_user, - commands=ssh_handler.VPN_SETUP, log=self.log, - gateway=configuration.get("gateway", {})) + ssh_data["commands"] = ssh_handler.VPN_SETUP + ssh_handler.execute_ssh(ssh_data, self.log) def prepare_volumes(self, provider, mounts): """ @@ -316,9 +316,10 @@ def upload_data(self): else: commands = [ssh_handler.get_ac_command(self.providers, AC_NAME.format( cluster_id=self.cluster_id))] + ssh_handler.ANSIBLE_START - ssh_handler.execute_ssh(floating_ip=self.master_ip, private_key=KEY_FOLDER + self.key_name, - username=self.ssh_user, filepaths=FILEPATHS, commands=commands, log=self.log, - gateway=self.configurations[0].get("gateway", {})) + ssh_data = {"floating_ip": self.master_ip, "private_key": KEY_FOLDER + self.key_name, + "username": self.ssh_user, "commands": commands, "filepaths": FILEPATHS, + "gateway": self.configurations[0].get("gateway", {}), "timeout": self.ssh_timeout} + ssh_handler.execute_ssh(ssh_data=ssh_data, log=self.log) def start_start_instance_threads(self): """ @@ -354,8 +355,7 @@ def extended_network_configuration(self): f"{configuration_b['subnet_cidrs']})") # add provider_b network as allowed network for cidr in configuration_b["subnet_cidrs"]: - allowed_addresses.append( - {'ip_address': cidr, 'mac_address': configuration_a["mac_addr"]}) + allowed_addresses.append({'ip_address': cidr, 'mac_address': configuration_a["mac_addr"]}) # configure security group rules provider_a.append_rules_to_security_group(self.wireguard_security_group_name, [ {"direction": "ingress", "ethertype": "IPv4", "protocol": "udp", "port_range_min": 51820, @@ -443,9 +443,8 @@ def log_cluster_start_info(self): port = int(sympy.sympify(gateway["portFunction"]).subs(dict(octets))) ssh_ip = gateway["ip"] self.log.log(42, f"Cluster {self.cluster_id} with master {self.master_ip} up and running!") - self.log.log(42, - f"SSH: ssh -i '{KEY_FOLDER}{self.key_name}' {self.ssh_user}@{ssh_ip}" - f"{f' -p {port}' if gateway else ''}") + self.log.log(42, f"SSH: ssh -i '{KEY_FOLDER}{self.key_name}' {self.ssh_user}@{ssh_ip}" + f"{f' -p {port}' if gateway else ''}") self.log.log(42, f"Terminate cluster: ./bibigrid.sh -i '{self.config_path}' -t -cid {self.cluster_id}") self.log.log(42, f"Detailed cluster info: ./bibigrid.sh -i '{self.config_path}' -l -cid {self.cluster_id}") if self.configurations[0].get("ide"): diff --git a/bibigrid/core/startup.py b/bibigrid/core/startup.py index 3a073f27..25001ab7 100755 --- a/bibigrid/core/startup.py +++ b/bibigrid/core/startup.py @@ -85,7 +85,7 @@ def run_action(args, configurations, config_path): debug=args.debug, config_path=config_path) LOG.log(42, "Creating a new cluster takes about 10 or more minutes depending on your cloud provider " - "and your configuration. Be patient.") + "and your configuration. Please be patient.") exit_state = creator.create() else: if not args.cluster_id: diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index f5c71ac3..54b874b0 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -1,6 +1,6 @@ """ This module handles ssh and sftp connections to master and vpngtw. It also holds general execution routines used to -setup the Cluster. +set up the Cluster. """ import os import socket @@ -10,15 +10,15 @@ import sympy import yaml -from bibigrid.core.utility import ansible_commands as aC +from bibigrid.core.utility import ansible_commands as a_c from bibigrid.models.exceptions import ConnectionException, ExecutionException PRIVATE_KEY_FILE = ".ssh/id_ecdsa" # to name bibigrid-temp keys identically on remote -ANSIBLE_SETUP = [aC.NO_UPDATE, aC.UPDATE, aC.PYTHON3_PIP, aC.ANSIBLE_PASSLIB, - (f"chmod 600 {PRIVATE_KEY_FILE}", "Adjust private key permissions."), aC.PLAYBOOK_HOME, - aC.PLAYBOOK_HOME_RIGHTS, aC.ADD_PLAYBOOK_TO_LINUX_HOME] +ANSIBLE_SETUP = [a_c.NO_UPDATE, a_c.UPDATE, a_c.PYTHON3_PIP, a_c.ANSIBLE_PASSLIB, + (f"chmod 600 {PRIVATE_KEY_FILE}", "Adjust private key permissions."), a_c.PLAYBOOK_HOME, + a_c.PLAYBOOK_HOME_RIGHTS, a_c.ADD_PLAYBOOK_TO_LINUX_HOME] # ANSIBLE_START = [aC.WAIT_READY, aC.UPDATE, aC.MV_ANSIBLE_CONFIG, aC.EXECUTE] # another UPDATE seems to not necessary. -ANSIBLE_START = [aC.WAIT_READY, aC.MV_ANSIBLE_CONFIG, aC.EXECUTE] +ANSIBLE_START = [a_c.WAIT_READY, a_c.MV_ANSIBLE_CONFIG, a_c.EXECUTE] VPN_SETUP = [("echo Example", "Echos an Example")] @@ -53,7 +53,7 @@ def get_ac_command(providers, name): def get_add_ssh_public_key_commands(ssh_public_key_files): """ Builds and returns the necessary commands to add given public keys to remote for additional access. - :param ssh_public_key_files: public keys to add + @param ssh_public_key_files: public keys to add :return: list of public key add commands """ commands = [] @@ -69,10 +69,10 @@ def copy_to_server(sftp, local_path, remote_path, log): """ Recursively copies files and folders to server. If a folder is given as local_path, the structure within will be kept. - :param sftp: sftp connection - :param local_path: file or folder locally - :param remote_path: file or folder locally - :param log: + @param sftp: sftp connection + @param local_path: file or folder locally + @param remote_path: file or folder locally + @param log: :return: """ log.debug("Copy %s to %s...", local_path, remote_path) @@ -87,17 +87,14 @@ def copy_to_server(sftp, local_path, remote_path, log): copy_to_server(sftp, os.path.join(local_path, filename), os.path.join(remote_path, filename), log) -def is_active(client, floating_ip_address, private_key, username, log, gateway, timeout=5): +def is_active(client, paramiko_key, ssh_data, log): """ Checks if connection is possible and therefore if server is active. Raises paramiko.ssh_exception.NoValidConnectionsError if timeout is reached - :param client: created client - :param floating_ip_address: ip to connect to - :param private_key: SSH-private_key - :param username: SSH-username - :param log: - :param timeout: how long to wait between ping - :param gateway: if node should be reached over a gateway port is set to 30000 + subnet * 256 + host + @param client: created client + @param paramiko_key: SSH-private_key + @param log: + @param ssh_data: dict containing among other things gateway, floating_ip, username (waiting grows quadratically till 2**timeout before accepting failure) """ attempts = 0 @@ -105,33 +102,34 @@ def is_active(client, floating_ip_address, private_key, username, log, gateway, while establishing_connection: try: port = 22 - if gateway: - log.info(f"Using SSH Gateway {gateway.get('ip')}") - octets = {f'oct{enum + 1}': int(elem) for enum, elem in enumerate(floating_ip_address.split("."))} - port = int(sympy.sympify(gateway["portFunction"]).subs(dict(octets))) - log.info(f"Port {port} will be used (see {gateway['portFunction']} and octets {octets}).") - client.connect(hostname=gateway.get("ip") or floating_ip_address, username=username, - pkey=private_key, timeout=7, auth_timeout=5, port=port) + if ssh_data.get('gateway'): + log.info(f"Using SSH Gateway {ssh_data['gateway'].get('ip')}") + octets = {f'oct{enum + 1}': int(elem) for enum, elem in enumerate(ssh_data['floating_ip'].split("."))} + port = int(sympy.sympify(ssh_data['gateway']["portFunction"]).subs(dict(octets))) + log.info(f"Port {port} will be used (see {ssh_data['gateway']['portFunction']} and octets {octets}).") + client.connect(hostname=ssh_data['gateway'].get("ip") or ssh_data['floating_ip'], + username=ssh_data['username'], pkey=paramiko_key, timeout=7, + auth_timeout=ssh_data['timeout'], port=port) establishing_connection = False - log.info(f"Successfully connected to {floating_ip_address}") + log.info(f"Successfully connected to {ssh_data['floating_ip']}") except paramiko.ssh_exception.NoValidConnectionsError as exc: - log.info(f"Attempting to connect to {floating_ip_address}... This might take a while", ) - if attempts < timeout: + log.info(f"Attempting to connect to {ssh_data['floating_ip']}... This might take a while", ) + if attempts < ssh_data['timeout']: time.sleep(2 ** attempts) attempts += 1 else: - log.error(f"Attempt to connect to {floating_ip_address} failed.") + log.error(f"Attempt to connect to {ssh_data['floating_ip']} failed.") raise ConnectionException(exc) from exc except socket.timeout as exc: log.warning("Socket timeout exception occurred. Try again ...") - if attempts < timeout: + if attempts < ssh_data['timeout']: attempts += 1 else: - log.error(f"Attempt to connect to {floating_ip_address} failed, due to a socket timeout.") + log.error(f"Attempt to connect to {ssh_data['floating_ip']} failed, due to a socket timeout.") raise ConnectionException(exc) from exc except TimeoutError as exc: # pylint: disable=duplicate-except log.error("The attempt to connect to %s failed. Possible known reasons:" - "\n\t-Your network's security group doesn't allow SSH.", floating_ip_address) + "\n\t-Your network's security group doesn't allow SSH.", ssh_data['floating_ip']) raise ConnectionException(exc) from exc @@ -139,7 +137,7 @@ def line_buffered(f): """ https://stackoverflow.com/questions/25260088/paramiko-with-continuous-stdout temporary hangs? - :param f: + @param f: :return: """ line_buf = b"" @@ -154,9 +152,9 @@ def line_buffered(f): def execute_ssh_cml_commands(client, commands, log): """ Executes commands and logs exit_status accordingly. - :param client: Client with connection to remote - :param commands: Commands to execute on remote - :param log: + @param client: Client with connection to remote + @param commands: Commands to execute on remote + @param log: """ for command in commands: _, ssh_stdout, _ = client.exec_command(command[0]) @@ -183,61 +181,35 @@ def execute_ssh_cml_commands(client, commands, log): raise ExecutionException(msg) -def ansible_preparation(floating_ip, private_key, username, log, gateway, commands=None, filepaths=None): - """ - Installs python and pip. Then installs ansible over pip. - Copies private key to instance so cluster-nodes are reachable and sets permission as necessary. - Copies additional files and executes additional commands if given. - The playbook is copied later, because it needs all servers setup and is not time intensive. - See: create.update_playbooks - :param floating_ip: public ip of server to ansible-prepare - :param private_key: generated private key of all cluster-server - :param username: username of all server - :param log: - :param commands: additional commands to execute - :param filepaths: additional files to copy: (localpath, remotepath) - :param gateway - """ - if filepaths is None: - filepaths = [] - if commands is None: - commands = [] - log.info("Ansible preparation...") - commands = ANSIBLE_SETUP + commands - filepaths.append((private_key, PRIVATE_KEY_FILE)) - execute_ssh(floating_ip, private_key, username, log, gateway, commands, filepaths) - - -def execute_ssh(floating_ip, private_key, username, log, gateway, commands=None, filepaths=None): +def execute_ssh(ssh_data, log): """ Executes commands on remote and copies files given in filepaths - :param floating_ip: public ip of remote - :param private_key: key of remote - :param username: username of remote - :param commands: commands - :param log: - :param filepaths: filepaths (localpath, remotepath) - :param gateway: gateway if used + + @param ssh_data: Dict containing floating_ip, private_key, username, commands, filepaths, gateway, timeout + @param log: """ - if commands is None: - commands = [] - paramiko_key = paramiko.ECDSAKey.from_private_key_file(private_key) + log.debug(f"Running execute_sshc with ssh_data: {ssh_data}.") + if ssh_data.get("filepaths") is None: + ssh_data["filepaths"] = [] + if ssh_data.get("commands") is None: + ssh_data["commands"] = [] + paramiko_key = paramiko.ECDSAKey.from_private_key_file(ssh_data["private_key"]) with paramiko.SSHClient() as client: client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: - is_active(client=client, floating_ip_address=floating_ip, username=username, private_key=paramiko_key, - log=log, gateway=gateway) + is_active(client=client, paramiko_key=paramiko_key, ssh_data=ssh_data, log=log) except ConnectionException as exc: - log.error(f"Couldn't connect to ip {gateway or floating_ip} using private key {private_key}.") + log.error(f"Couldn't connect to ip {ssh_data['gateway'] or ssh_data['floating_ip']} using private key " + f"{ssh_data['private_key']}.") raise exc else: - log.debug(f"Setting up {floating_ip}") - if filepaths: - log.debug(f"Setting up filepaths for {floating_ip}") + log.debug(f"Setting up {ssh_data['floating_ip']}") + if ssh_data['filepaths']: + log.debug(f"Setting up filepaths for {ssh_data['floating_ip']}") sftp = client.open_sftp() - for local_path, remote_path in filepaths: + for local_path, remote_path in ssh_data['filepaths']: copy_to_server(sftp=sftp, local_path=local_path, remote_path=remote_path, log=log) - log.debug("SFTP: Files %s copied.", filepaths) - if commands: - log.debug(f"Setting up commands for {floating_ip}") - execute_ssh_cml_commands(client=client, commands=commands, log=log) + log.debug("SFTP: Files %s copied.", ssh_data['filepaths']) + if ssh_data["floating_ip"]: + log.debug(f"Setting up commands for {ssh_data['floating_ip']}") + execute_ssh_cml_commands(client=client, commands=ssh_data["commands"], log=log) From 8ebfe55fca77fe460376e2918e565b88dc0edb09 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:50:29 +0100 Subject: [PATCH 049/145] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 32 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..9b2471fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behaviour: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behaviour** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. Ubuntu 22.04] + - Cloud Location [e.g. Bielefeld] + - BiBiGrid Version + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..11fc491e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From cb11854111872212660545154212256085efde10 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 4 Mar 2024 11:56:14 +0100 Subject: [PATCH 050/145] fixed a missing LOG --- bibigrid/core/startup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bibigrid/core/startup.py b/bibigrid/core/startup.py index 3a073f27..95b0c292 100755 --- a/bibigrid/core/startup.py +++ b/bibigrid/core/startup.py @@ -104,7 +104,7 @@ def run_action(args, configurations, config_path): exit_state = ide.ide(args.cluster_id, providers[0], configurations[0], LOG) elif args.update: LOG.info("Action update selected") - exit_state = update.update(args.cluster_id, providers[0], configurations[0]) + exit_state = update.update(args.cluster_id, providers[0], configurations[0], LOG) for provider in providers: provider.close() else: From 65a3d218ee3d3095ea673d437f9ec93395fa2d43 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 4 Mar 2024 11:57:24 +0100 Subject: [PATCH 051/145] removed overwritten variable instantiation --- bibigrid/core/utility/validate_configuration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bibigrid/core/utility/validate_configuration.py b/bibigrid/core/utility/validate_configuration.py index 1bfb948c..f8711399 100644 --- a/bibigrid/core/utility/validate_configuration.py +++ b/bibigrid/core/utility/validate_configuration.py @@ -467,7 +467,6 @@ def check_nfs(self): @return: True """ self.log.info("Checking nfs...") - success = True master_configuration = self.configurations[0] nfs_shares = master_configuration.get("nfsShares") nfs = master_configuration.get("nfs") From 3b1e3fde57e286df1982843e64f2e2d99eb4c7c5 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:09:34 +0100 Subject: [PATCH 052/145] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9b2471fe..80c3d8ee 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -23,9 +23,10 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. -**Desktop (please complete the following information):** +**Setup (please complete the following information):** - OS: [e.g. Ubuntu 22.04] - Cloud Location [e.g. Bielefeld] + - Configuration - BiBiGrid Version **Additional context** From 05e84847449247ce865e53c7e3b1c3582586a97a Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 4 Mar 2024 14:14:55 +0100 Subject: [PATCH 053/145] removed trailing whitespaces --- bibigrid/core/utility/validate_configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bibigrid/core/utility/validate_configuration.py b/bibigrid/core/utility/validate_configuration.py index 16bb3217..cca9b113 100644 --- a/bibigrid/core/utility/validate_configuration.py +++ b/bibigrid/core/utility/validate_configuration.py @@ -317,7 +317,7 @@ def check_instance_type_image_combination(self, instance_type, instance_image, p self.log.warning(f"Flavor {instance_type} does not exist on {provider.cloud_specification['identifier']}.\n" f"Available flavors:\n{available_flavors}") return False - + type_max_disk_space = flavor["disk"] type_max_ram = flavor["ram"] image_min_disk_space = provider.get_image_by_id_or_name(instance_image)["min_disk"] From b571279ba12e92825c29c755b860e87351f32e51 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 22 Mar 2024 12:31:18 +0100 Subject: [PATCH 054/145] added comment about sshTimeout key --- bibigrid.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bibigrid.yml b/bibigrid.yml index 27481c9a..584c4bb7 100644 --- a/bibigrid.yml +++ b/bibigrid.yml @@ -7,6 +7,8 @@ cloud: openstack # name of clouds.yaml cloud-specification key (which is value to top level key clouds) # -- BEGIN: GENERAL CLUSTER INFORMATION -- + # sshTimeout: 5 # Number of ssh connection attempts with 2^attempt seconds in between (2^sshTimeout-1 is the max time before returning with an error) + ## sshPublicKeyFiles listed here will be added to access the cluster. A temporary key is created by bibigrid itself. #sshPublicKeyFiles: # - [public key one] From 35265872634870af6e7ed57038e154523cab87c3 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:06:45 +0100 Subject: [PATCH 055/145] Create dependabot.yml (#479) --- .github/dependabot.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..e644f86c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + versioning-strategy: "widen" + target: + versions: [">=3.0.0"] From 9b0f63281a3983ba3e3c299f439a97958ca8ea51 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:09:12 +0100 Subject: [PATCH 056/145] Code cleanup and minor improvement (#482) * fixed :param and :return to @param and @return * many spelling mistakes fixed * added bibigrid_version to common configuration --- bibigrid/core/actions/check.py | 8 +- bibigrid/core/actions/create.py | 54 ++++---- bibigrid/core/actions/ide.py | 2 +- bibigrid/core/actions/list_clusters.py | 48 +++---- bibigrid/core/actions/terminate.py | 3 +- bibigrid/core/actions/update.py | 12 +- bibigrid/core/provider.py | 124 +++++++++--------- bibigrid/core/startup.py | 25 ++-- bibigrid/core/utility/ansible_commands.py | 14 +- bibigrid/core/utility/ansible_configurator.py | 72 +++++----- .../core/utility/command_line_interpreter.py | 2 +- .../utility/handler/configuration_handler.py | 26 ++-- .../core/utility/handler/provider_handler.py | 24 ++-- bibigrid/core/utility/handler/ssh_handler.py | 78 +++++------ bibigrid/core/utility/id_generation.py | 14 +- .../utility/paths/ansible_resources_path.py | 4 +- bibigrid/core/utility/paths/bin_path.py | 4 +- .../core/utility/validate_configuration.py | 76 ++++++----- .../core/utility/wireguard/wireguard_keys.py | 6 +- bibigrid/models/return_threading.py | 6 +- bibigrid/openstack/openstack_provider.py | 52 ++++---- requirements.txt | 20 ++- tests/test_id_generation.py | 2 +- 23 files changed, 344 insertions(+), 332 deletions(-) diff --git a/bibigrid/core/actions/check.py b/bibigrid/core/actions/check.py index 0ad37c7b..214021a8 100644 --- a/bibigrid/core/actions/check.py +++ b/bibigrid/core/actions/check.py @@ -7,10 +7,10 @@ def check(configurations, providers, log): """ Uses validate_configuration to validate given configuration. - :param configurations: list of configurations (dicts) - :param providers: list of providers - :param log: - :return: + @param configurations: list of configurations (dicts) + @param providers: list of providers + @param log: + @return: """ success = validate_configuration.ValidateConfiguration(configurations, providers, log).validate() check_result = "succeeded! Cluster is ready to start." if success else "failed!" diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 6f43920a..966ec402 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -17,8 +17,8 @@ from bibigrid.core.utility import id_generation from bibigrid.core.utility import image_selection from bibigrid.core.utility.handler import ssh_handler -from bibigrid.core.utility.paths import ansible_resources_path as aRP -from bibigrid.core.utility.paths import bin_path as biRP +from bibigrid.core.utility.paths import ansible_resources_path as a_rp +from bibigrid.core.utility.paths import bin_path from bibigrid.models import exceptions from bibigrid.models import return_threading from bibigrid.models.exceptions import ExecutionException, ConfigurationException @@ -26,7 +26,7 @@ PREFIX = "bibigrid" SEPARATOR = "-" PREFIX_WITH_SEP = PREFIX + SEPARATOR -FILEPATHS = [(aRP.PLAYBOOK_PATH, aRP.PLAYBOOK_PATH_REMOTE), (biRP.BIN_PATH, biRP.BIN_PATH_REMOTE)] +FILEPATHS = [(a_rp.PLAYBOOK_PATH, a_rp.PLAYBOOK_PATH_REMOTE), (bin_path.BIN_PATH, bin_path.BIN_PATH_REMOTE)] def get_identifier(identifier, cluster_id, additional=""): @@ -67,10 +67,10 @@ def __init__(self, providers, configurations, config_path, log, debug=False, clu """ Additionally sets (unique) cluster_id, public_key_commands (to copy public keys to master) and key_name. Call create() to actually start server. - :param providers: List of providers (provider) - :param configurations: List of configurations (dict) - :param config_path: string that is the path to config-file - :param debug: Bool. If True Cluster offer shut-down after create and + @param providers: List of providers (provider) + @param configurations: List of configurations (dict) + @param config_path: string that is the path to config-file + @param debug: Bool. If True Cluster offer shut-down after create and will ask before shutting down on errors """ self.log = log @@ -104,7 +104,7 @@ def generate_keypair(self): See here for why using python module ECDSA wasn't successful https://stackoverflow.com/questions/71194770/why-does-creating-ecdsa-keypairs-via-python-differ-from-ssh -keygen-t-ecdsa-and - :return: + @return: """ self.log.info("Generating keypair") # create KEY_FOLDER if it doesn't exist @@ -129,7 +129,7 @@ def generate_security_groups(self): """ Generate a security groups: - default with basic rules for the cluster - - wireguard when more than one provider is used (= multicloud) + - wireguard when more than one provider is used (= multi-cloud) """ self.log.info("Generating Security Groups") for provider, configuration in zip(self.providers, self.configurations): @@ -163,9 +163,9 @@ def generate_security_groups(self): def start_vpn_or_master_instance(self, configuration, provider): """ Start master/vpn-worker of a provider - :param configuration: dict configuration of said provider - :param provider: provider - :return: + @param configuration: dict configuration of said provider. + @param provider: provider + @return: """ identifier, instance_type, volumes = self.prepare_vpn_or_master_args(configuration, provider) external_network = provider.get_external_network(configuration["network"]) @@ -209,9 +209,9 @@ def start_vpn_or_master_instance(self, configuration, provider): def prepare_vpn_or_master_args(self, configuration, provider): """ Prepares start_instance arguments for master/vpn - :param configuration: configuration (dict) of said master/vpn - :param provider: provider - :return: arguments needed by start_instance + @param configuration: configuration (dict) of said master/vpn + @param provider: provider + @return: arguments needed by start_instance """ if configuration.get("masterInstance"): instance_type = configuration["masterInstance"] @@ -247,9 +247,9 @@ def initialize_instances(self): def prepare_volumes(self, provider, mounts): """ Creates volumes from snapshots and returns all volumes (pre-existing and newly created) - :param provider: provider on which the volumes and snapshots exist - :param mounts: volumes or snapshots - :return: list of pre-existing and newly created volumes + @param provider: provider on which the volumes and snapshots exist + @param mounts: volumes or snapshots + @return: list of pre-existing and newly created volumes """ if mounts: self.log.info("Preparing volumes") @@ -275,7 +275,7 @@ def prepare_configurations(self): """ Makes sure that subnet and network key are set for each configuration. If none is set a keyError will be raised and caught in create. - :return: + @return: """ for configuration, provider in zip(self.configurations, self.providers): configuration["cloud_identifier"] = provider.cloud_specification["identifier"] @@ -298,15 +298,15 @@ def prepare_configurations(self): def upload_data(self): """ Configures ansible and then uploads the modified files and all necessary data to the master - :return: + @return: """ self.log.debug("Uploading ansible Data") - for folder in [aRP.VARS_FOLDER, aRP.GROUP_VARS_FOLDER, aRP.HOST_VARS_FOLDER]: + for folder in [a_rp.VARS_FOLDER, a_rp.GROUP_VARS_FOLDER, a_rp.HOST_VARS_FOLDER]: if not os.path.isdir(folder): self.log.info("%s not found. Creating folder.", folder) os.mkdir(folder) - if not os.path.isfile(aRP.HOSTS_FILE): - with open(aRP.HOSTS_FILE, 'a', encoding='utf-8') as hosts_file: + if not os.path.isfile(a_rp.HOSTS_FILE): + with open(a_rp.HOSTS_FILE, 'a', encoding='utf-8') as hosts_file: hosts_file.write("# placeholder file for worker DNS entries (see 003-dns)") ansible_configurator.configure_ansible_yaml(providers=self.providers, configurations=self.configurations, @@ -323,7 +323,7 @@ def upload_data(self): def start_start_instance_threads(self): """ Starts for each provider a start_instances thread and joins them. - :return: + @return: """ start_instance_threads = [] for configuration, provider in zip(self.configurations, self.providers): @@ -337,7 +337,7 @@ def start_start_instance_threads(self): def extended_network_configuration(self): """ Configure master/vpn-worker network for a multi/hybrid cloud - :return: + @return: """ if len(self.providers) == 1: return @@ -368,7 +368,7 @@ def create(self): # pylint: disable=too-many-branches,too-many-statements """ Creates cluster and logs helpful cluster-info afterwards. If debug is set True it offers termination after starting the cluster. - :return: exit_state + @return: exit_state """ try: self.generate_keypair() @@ -433,7 +433,7 @@ def log_cluster_start_info(self): SSH: How to connect to master via SSH Terminate: What bibigrid command is needed to terminate the created cluster Detailed cluster info: How to log detailed info about the created cluster - :return: + @return: """ gateway = self.configurations[0].get("gateway") ssh_ip = self.master_ip diff --git a/bibigrid/core/actions/ide.py b/bibigrid/core/actions/ide.py index 8f96cbd3..a9b26880 100644 --- a/bibigrid/core/actions/ide.py +++ b/bibigrid/core/actions/ide.py @@ -40,7 +40,7 @@ def sigint_handler(caught_signal, frame): # pylint: disable=unused-argument def is_used(ip_address): """ https://stackoverflow.com/questions/62000168/how-to-check-if-ssh-tunnel-is-being-used - :return: + @return: """ ports_used = [] with subprocess.Popen(["netstat", "-na"], stdout=subprocess.PIPE) as process: diff --git a/bibigrid/core/actions/list_clusters.py b/bibigrid/core/actions/list_clusters.py index 90e7aa5e..e6e3cf03 100644 --- a/bibigrid/core/actions/list_clusters.py +++ b/bibigrid/core/actions/list_clusters.py @@ -14,9 +14,9 @@ def dict_clusters(providers, log): """ Creates a dictionary containing all servers by type and provider information - :param providers: list of all providers - :param log: - :return: list of all clusters in yaml format + @param providers: list of all providers + @param log: + @return: list of all clusters in yaml format """ log.info("Creating cluster dictionary...") cluster_dict = {} @@ -39,11 +39,11 @@ def setup(cluster_dict, cluster_id, server, provider): """ Determines cluster_id. Generates empty entry for cluster_id in cluster_dict. - :param server: found server (dict) - :param cluster_id: id of said cluster - :param cluster_dict: dict containing all found servers by their cluster_id - :param provider: server's provider - :return: cluster_id + @param server: found server (dict) + @param cluster_id: id of said cluster + @param cluster_dict: dict containing all found servers by their cluster_id + @param provider: server's provider + @return: cluster_id """ if not cluster_dict.get(cluster_id): cluster_dict[cluster_id] = {} @@ -57,10 +57,10 @@ def log_list(cluster_id, providers, log): """ Calls dict_clusters and gives a visual representation of the found cluster. Detail depends on whether a cluster_id is given or not. - :param cluster_id: - :param providers: - :param log: - :return: + @param cluster_id: + @param providers: + @param log: + @return: """ cluster_dict = dict_clusters(providers=providers, log=log) if cluster_id: # pylint: disable=too-many-nested-blocks @@ -68,7 +68,7 @@ def log_list(cluster_id, providers, log): log.info("Printing specific cluster_dictionary") master_count, worker_count, vpn_count = get_size_overview(cluster_dict[cluster_id], log) log.log(42, f"\tCluster has {master_count} master, {vpn_count} vpngtw and {worker_count} regular workers. " - f"The cluster is spread over {vpn_count + master_count} reachable provider(s).") + f"The cluster is spread over {vpn_count + master_count} reachable provider(s).") log.log(42, pprint.pformat(cluster_dict[cluster_id])) else: log.info("Cluster with cluster-id {cluster_id} not found.") @@ -101,9 +101,9 @@ def log_list(cluster_id, providers, log): def get_size_overview(cluster_dict, log): """ - :param cluster_dict: dictionary of cluster to size_overview - :param log: - :return: number of masters, number of workers, number of vpns + @param cluster_dict: dictionary of cluster to size_overview + @param log: + @return: number of masters, number of workers, number of vpns """ log.info("Printing size overview") master_count = int(bool(cluster_dict.get("master"))) @@ -115,8 +115,8 @@ def get_size_overview(cluster_dict, log): def get_networks(cluster_dict): """ Gets all addresses of servers - :param cluster_dict: dictionary of clusters to find addresses - :return: dict containing addresses + @param cluster_dict: dictionary of clusters to find addresses + @return: dict containing addresses """ master = cluster_dict["master"] addresses = [{master["provider"]: list(master["addresses"].keys())}] @@ -128,8 +128,8 @@ def get_networks(cluster_dict): def get_security_groups(cluster_dict): """ Gets all security group of servers - :param cluster_dict: dictionary of clusters to find security_groups - :return: dict containing security_groups + @param cluster_dict: dictionary of clusters to find security_groups + @return: dict containing security_groups """ master = cluster_dict["master"] security_groups = [{master["provider"]: master["security_groups"]}] @@ -141,10 +141,10 @@ def get_security_groups(cluster_dict): def get_master_access_ip(cluster_id, master_provider, log): """ Returns master's ip of cluster cluster_id - :param master_provider: master's provider - :param cluster_id: Id of cluster - :param log: - :return: public ip of master + @param master_provider: master's provider + @param cluster_id: id of cluster + @param log: + @return: public ip of master """ log.info("Finding master ip for cluster %s...", cluster_id) servers = master_provider.list_servers() diff --git a/bibigrid/core/actions/terminate.py b/bibigrid/core/actions/terminate.py index 15a5a859..44c480ae 100644 --- a/bibigrid/core/actions/terminate.py +++ b/bibigrid/core/actions/terminate.py @@ -57,6 +57,7 @@ def terminate_servers(server_list, cluster_id, provider, log): @param server_list: list of server dicts. All servers are from provider @param cluster_id: id of cluster to terminate @param provider: provider that holds all servers in server_list + @param log: @return: a list of the servers' (that were to be terminated) termination states """ log.info("Deleting servers on provider %s...", provider.cloud_specification['identifier']) @@ -73,7 +74,7 @@ def terminate_servers(server_list, cluster_id, provider, log): def terminate_server(provider, server, log): """ Terminates a single server and stores the termination state - @param provider: the provider that holds the server + @param provider: the provider that holds the server. @param server: the server that is to be terminated @param log: @return: true if the server has been terminated, false else diff --git a/bibigrid/core/actions/update.py b/bibigrid/core/actions/update.py index ed866a12..efe9aaf7 100644 --- a/bibigrid/core/actions/update.py +++ b/bibigrid/core/actions/update.py @@ -2,10 +2,10 @@ Module that contains methods to update the master playbook """ -from bibigrid.core.utility import ansible_commands as aC +from bibigrid.core.utility import ansible_commands as a_c from bibigrid.core.utility.handler import ssh_handler -from bibigrid.core.utility.paths import ansible_resources_path as aRP -from bibigrid.core.utility.paths import bin_path as biRP +from bibigrid.core.utility.paths import ansible_resources_path as a_rp +from bibigrid.core.utility.paths import bin_path from bibigrid.core.utility.handler import cluster_ssh_handler @@ -18,8 +18,8 @@ def update(cluster_id, master_provider, master_configuration, log): ssh_handler.execute_ssh(floating_ip=master_ip, private_key=used_private_key, username=ssh_user, log=log, gateway=master_configuration.get("gateway", {}), - commands=[aC.EXECUTE], - filepaths=[(aRP.PLAYBOOK_PATH, aRP.PLAYBOOK_PATH_REMOTE), - (biRP.BIN_PATH, biRP.BIN_PATH_REMOTE)]) + commands=[a_c.EXECUTE], + filepaths=[(a_rp.PLAYBOOK_PATH, a_rp.PLAYBOOK_PATH_REMOTE), + (bin_path.BIN_PATH, bin_path.BIN_PATH_REMOTE)]) return 0 return 1 diff --git a/bibigrid/core/provider.py b/bibigrid/core/provider.py index 133088cb..61cc5012 100644 --- a/bibigrid/core/provider.py +++ b/bibigrid/core/provider.py @@ -27,8 +27,8 @@ def __init__(self, cloud_specification): def create_application_credential(self, name=None): """ Creates an application credential with name name - :param name: Name of new application credential - :return: the application credential dictionary + @param name: Name of new application credential + @return: the application credential dictionary """ @abstractmethod @@ -36,55 +36,55 @@ def delete_application_credential_by_id_or_name(self, ac_id_or_name): """ Deletes existing application credential by id or name and returns true. If application credential not found it returns false. - :param ac_id_or_name: application credential id or name - :return: True if deleted else false + @param ac_id_or_name: application credential id or name + @return: True if deleted else false """ @abstractmethod def get_image_by_id_or_name(self, image_id_or_name): """ Returns image that has id or name image_id_or_name - :param image_id_or_name: identifier - :return: said image (dict) or none if not found + @param image_id_or_name: identifier + @return: said image (dict) or none if not found """ @abstractmethod def get_flavor(self, instance_type): """ Returns flavor that has id or name flavor_id_or_name - :param instance_type: identifier - :return: said flavor (dict) or none if not found + @param instance_type: identifier + @return: said flavor (dict) or none if not found """ @abstractmethod def get_volume_snapshot_by_id_or_name(self, snapshot_id_or_name): """ Returns snapshot that has id or name snapshot_id_or_name - :param snapshot_id_or_name: identifier - :return: said snapshot (dict) or none if not found + @param snapshot_id_or_name: identifier + @return: said snapshot (dict) or none if not found """ @abstractmethod def get_network_by_id_or_name(self, network_id_or_name): """ Returns network that has id or name network_id_or_name - :param network_id_or_name: identifier - :return: said network (dict) or none if not found + @param network_id_or_name: identifier + @return: said network (dict) or none if not found """ @abstractmethod def get_subnet_by_id_or_name(self, subnet_id_or_name): """ Returns subnet that has id or name subnet_id_or_name - :param subnet_id_or_name: identifier - :return: said subnet (dict) or none if not found + @param subnet_id_or_name: identifier + @return: said subnet (dict) or none if not found """ @abstractmethod def list_servers(self): """ Returns a list of all servers on logged in provider - :return: said list of servers or empty list if none found + @return: said list of servers or empty list if none found """ @abstractmethod @@ -93,112 +93,112 @@ def create_server(self, name, flavor, image, network, key_name=None, wait=True, """ Creates a new server and waits for it to be accessible if wait=True. If volumes are given, they are attached. Returns said server (dict) - :param name: name (str) - :param flavor: flavor/type (str) - :param image: image/bootable-medium (str) - :param network: network (str) - :param key_name: (str) - :param wait: (bool) - :param volumes: List of volumes (list (str)) - :param security_groups: List of security_groups list (str) - :return: server (dict) + @param name: name (str) + @param flavor: flavor/type (str) + @param image: image/bootable-medium (str) + @param network: network (str) + @param key_name: (str) + @param wait: (bool) + @param volumes: List of volumes (list (str)) + @param security_groups: List of security_groups list (str) + @return: server (dict) """ @abstractmethod def delete_server(self, name_or_id, delete_ips=True): """ Deletes server and floating_ip as well if delete_ips is true. The resource is then free again - :param name_or_id: - :param delete_ips: - :return: True if delete succeeded, False otherwise + @param name_or_id: + @param delete_ips: + @return: True if delete succeeded, False otherwise """ @abstractmethod def delete_keypair(self, key_name): """ Deletes keypair with key_name - :param key_name: (str) - :return: True if delete succeeded, False otherwise + @param key_name: (str) + @return: True if delete succeeded, False otherwise """ @abstractmethod def get_server_group_by_id_or_name(self, server_group_id_or_name): """ Returns server_group that has id or name server_group_id_or_name - :param server_group_id_or_name: identifier - :return: said server_group (dict) or none if not found + @param server_group_id_or_name: identifier + @return: said server_group (dict) or none if not found """ @abstractmethod def close(self): """ Closes connection - :return: + @return: """ @abstractmethod def create_keypair(self, name, public_key): """ - Creates a new keypair with name name and public_key public_key - :param name: name of new keypair - :param public_key: public_key of new keypair - :return: + Creates a new keypair with name and public_key + @param name: name of new keypair + @param public_key: public_key of new keypair + @return: """ @abstractmethod def get_network_id_by_subnet(self, subnet): """ Gets network_id by subnet - :param subnet: id (str) - :return: (str) + @param subnet: id (str) + @return: (str) """ @abstractmethod def get_subnet_ids_by_network(self, network): """ Gets subnet_ids (list (str)) by network_id - :param network: id (str) - :return: subnet_ids (list (str)) + @param network: id (str) + @return: subnet_ids (list (str)) """ @abstractmethod def get_free_resources(self): """ Gets free resources. If a resource cannot be determined, assume maximum is free. - :return: Dictionary containing the free resources + @return: Dictionary containing the free resources """ @abstractmethod def get_volume_by_id_or_name(self, name_or_id): """ Returns volume that has id or name name_or_id - :param name_or_id: identifier - :return: said volume (dict) or none if not found + @param name_or_id: identifier + @return: said volume (dict) or none if not found """ @abstractmethod def create_volume_from_snapshot(self, snapshot_name_or_id): """ Creates a volume from snapshot. - :param snapshot_name_or_id: name or id of snapshot - :return: id of created volume or none if failed + @param snapshot_name_or_id: name or id of snapshot + @return: id of created volume or none if failed """ @abstractmethod def get_external_network(self, network_name_or_id): """ Finds router interface with network id equal to given network and by that the external network. - :param network_name_or_id: Name or id of network - :return: Corresponding external network + @param network_name_or_id: Name or id of network + @return: Corresponding external network """ @abstractmethod def attach_available_floating_ip(self, network=None, server=None): """ Get a floating IP from a network or a pool and attach it to the server - :param network: - :param server: - :return: + @param network: + @param server: + @return: """ @abstractmethod @@ -218,7 +218,7 @@ def get_flavors(self): def get_active_images(self): """ Return a list of active images. - :return: A list of active images. + @return: A list of active images. """ return [image["name"] for image in self.get_images() if image["status"].lower() == "active"] @@ -230,39 +230,39 @@ def get_active_flavors(self): def set_allowed_addresses(self, id_or_ip, allowed_address_pairs): """ Set allowed address (or CIDR) for the given network interface/port - :param id_or_ip: id or ipv4 ip-address of the port/interface - :param allowed_address_pairs: a list of allowed address pairs. For example: + @param id_or_ip: id or ipv4 ip-address of the port/interface + @param allowed_address_pairs: a list of allowed address pairs. For example: [{ "ip_address": "23.23.23.1", "mac_address": "fa:16:3e:c4:cd:3f" }] - :return: + @return: """ @abstractmethod def create_security_group(self, name, rules): """ Create a security group and add given rules - :param name: Name of the security group to be created - :param rules: List of firewall rules to be added - :return: id of created security group + @param name: Name of the security group to be created + @param rules: List of firewall rules to be added + @return: id of created security group """ @abstractmethod def delete_security_group(self, name_or_id): """ Delete a security group - :param name_or_id : Name or Id of the security group to be deleted - :return: True if delete succeeded, False otherwise. + @param name_or_id : Name or id of the security group to be deleted + @return: True if delete succeeded, False otherwise. """ @abstractmethod def append_rules_to_security_group(self, name_or_id, rules): """ Append firewall rules to given security group - :param name_or_id: - :param rules: - :return: + @param name_or_id: + @param rules: + @return: """ def get_mount_info_from_server(self, server): diff --git a/bibigrid/core/startup.py b/bibigrid/core/startup.py index 95b0c292..41aa75fb 100755 --- a/bibigrid/core/startup.py +++ b/bibigrid/core/startup.py @@ -40,8 +40,8 @@ def get_cluster_id_from_mem(): def set_logger_verbosity(verbosity): """ Sets verbosity, format and handler. - :param verbosity: level of verbosity - :return: + @param verbosity: level of verbosity + @return: """ capped_verbosity = min(verbosity, len(VERBOSITY_LIST) - 1) @@ -56,10 +56,10 @@ def set_logger_verbosity(verbosity): def run_action(args, configurations, config_path): """ Uses args to decide which action will be executed and executes said action. - :param args: command line arguments - :param configurations: list of configurations (dicts) - :param config_path: path to configurations-file - :return: + @param args: command line arguments + @param configurations: list of configurations (dicts) + @param config_path: path to configurations-file + @return: """ if args.version: LOG.info("Action version selected") @@ -79,13 +79,10 @@ def run_action(args, configurations, config_path): exit_state = check.check(configurations, providers, LOG) elif args.create: LOG.info("Action create selected") - creator = create.Create(providers=providers, - configurations=configurations, - log=LOG, - debug=args.debug, + creator = create.Create(providers=providers, configurations=configurations, log=LOG, debug=args.debug, config_path=config_path) LOG.log(42, "Creating a new cluster takes about 10 or more minutes depending on your cloud provider " - "and your configuration. Be patient.") + "and your configuration. Be patient.") exit_state = creator.create() else: if not args.cluster_id: @@ -95,9 +92,7 @@ def run_action(args, configurations, config_path): if args.cluster_id: if args.terminate: LOG.info("Action terminate selected") - exit_state = terminate.terminate(cluster_id=args.cluster_id, - providers=providers, - log=LOG, + exit_state = terminate.terminate(cluster_id=args.cluster_id, providers=providers, log=LOG, debug=args.debug) elif args.ide: LOG.info("Action ide selected") @@ -124,7 +119,7 @@ def run_action(args, configurations, config_path): def main(): """ Interprets command line, sets logger, reads configuration and runs selected action. Then exits. - :return: + @return: """ logging.basicConfig(format=LOGGER_FORMAT) # LOG.addHandler(logging.StreamHandler()) # stdout diff --git a/bibigrid/core/utility/ansible_commands.py b/bibigrid/core/utility/ansible_commands.py index fc6c2815..0a443e28 100644 --- a/bibigrid/core/utility/ansible_commands.py +++ b/bibigrid/core/utility/ansible_commands.py @@ -3,12 +3,12 @@ """ import os -import bibigrid.core.utility.paths.ansible_resources_path as aRP +import bibigrid.core.utility.paths.ansible_resources_path as a_rp -#TO_LOG = "| sudo tee -a /var/log/ansible.log" -#AIY = "apt-get -y install" -#SAU = "sudo apt-get update" -#NO_KEY_CHECK = "export ANSIBLE_HOST_KEY_CHECKING=False" +# TO_LOG = "| sudo tee -a /var/log/ansible.log" +# AIY = "apt-get -y install" +# SAU = "sudo apt-get update" +# NO_KEY_CHECK = "export ANSIBLE_HOST_KEY_CHECKING=False" NO_UPDATE = ("""sudo sed -i 's/APT::Periodic::Unattended-Upgrade "1";/APT::Periodic::Unattended-Upgrade "0";/g' """ """/etc/apt/apt.conf.d/20auto-upgrades""", "Disable apt auto update.") # Setup (Python for everyone) @@ -49,8 +49,8 @@ "Adjust playbook home permission.") MV_ANSIBLE_CONFIG = ( "sudo install -D /opt/playbook/ansible.cfg /etc/ansible/ansible.cfg", "Move ansible configuration.") -EXECUTE = (f"ansible-playbook {os.path.join(aRP.PLAYBOOK_PATH_REMOTE, aRP.SITE_YML)} -i " - f"{os.path.join(aRP.PLAYBOOK_PATH_REMOTE, aRP.ANSIBLE_HOSTS)} -l vpn", +EXECUTE = (f"ansible-playbook {os.path.join(a_rp.PLAYBOOK_PATH_REMOTE, a_rp.SITE_YML)} -i " + f"{os.path.join(a_rp.PLAYBOOK_PATH_REMOTE, a_rp.ANSIBLE_HOSTS)} -l vpn", "Execute ansible playbook. Be patient.") # ansible setup diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 642e9ae4..0e7365a5 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -6,6 +6,7 @@ import mergedeep import yaml +from bibigrid.core.actions.version import __version__ from bibigrid.core.actions import create from bibigrid.core.actions import ide @@ -50,8 +51,8 @@ def generate_site_file_yaml(custom_roles): """ Generates site_yaml (dict). Deepcopy is used in case roles might differ between servers in the future. - :param custom_roles: ansibleRoles given by the config - :return: site_yaml (dict) + @param custom_roles: ansibleRoles given by the config + @return: site_yaml (dict) """ site_yaml = [{'hosts': 'master', "become": "yes", "vars_files": VARS_FILES, "roles": MASTER_ROLES}, {'hosts': 'vpngtw', "become": "yes", "vars_files": VARS_FILES, "roles": vpngtw_ROLES}, @@ -70,10 +71,10 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py """ ToDo filter what information really is necessary. Determined by further development Filters unnecessary information - :param configurations: configurations - :param providers: providers - :param cluster_id: To get proper naming - :return: filtered information (dict) + @param configurations: configurations + @param providers: providers + @param cluster_id: To get proper naming + @return: filtered information (dict) """ log.info("Generating instances file...") flavor_keys = ["name", "ram", "vcpus", "disk", "ephemeral"] @@ -156,18 +157,19 @@ def pass_through(dict_from, dict_to, key_from, key_to=None): def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_user, default_user, log): """ Generates common_configuration yaml (dict) - :param cidrs: str subnet cidrs (provider generated) - :param configurations: master configuration (first in file) - :param cluster_id: id of cluster - :param ssh_user: user for ssh connections - :param default_user: Given default user - :param log: - :return: common_configuration_yaml (dict) + @param cidrs: str subnet cidrs (provider generated) + @param configurations: master configuration (first in file) + @param cluster_id: id of cluster + @param ssh_user: user for ssh connections + @param default_user: Given default user + @param log: + @return: common_configuration_yaml (dict) """ master_configuration = configurations[0] log.info("Generating common configuration file...") # print(configuration.get("slurmConf", {})) - common_configuration_yaml = {"auto_mount": master_configuration.get("autoMount", False), "cluster_id": cluster_id, + common_configuration_yaml = {"bibigrid_version": __version__, + "auto_mount": master_configuration.get("autoMount", False), "cluster_id": cluster_id, "cluster_cidrs": cidrs, "default_user": default_user, "local_fs": master_configuration.get("localFS", False), "local_dns_lookup": master_configuration.get("localDNSlookup", False), @@ -211,11 +213,11 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us def generate_ansible_hosts_yaml(ssh_user, configurations, cluster_id, log): # pylint: disable-msg=too-many-locals """ Generates ansible_hosts_yaml (inventory file). - :param ssh_user: str global SSH-username - :param configurations: dict - :param cluster_id: id of cluster - :param log: - :return: ansible_hosts yaml (dict) + @param ssh_user: str global SSH-username + @param configurations: dict + @param cluster_id: id of cluster + @param log: + @return: ansible_hosts yaml (dict) """ log.info("Generating ansible hosts file...") ansible_hosts_yaml = {"vpn": {"hosts": {}, @@ -249,9 +251,9 @@ def generate_ansible_hosts_yaml(ssh_user, configurations, cluster_id, log): # p def to_instance_host_dict(ssh_user, ip="localhost"): # pylint: disable=invalid-name """ Generates host entry - :param ssh_user: str global SSH-username - :param ip: str ip - :return: host entry (dict) + @param ssh_user: str global SSH-username + @param ip: str ip + @return: host entry (dict) """ host_yaml = {"ansible_connection": "ssh", "ansible_python_interpreter": PYTHON_INTERPRETER, "ansible_user": ssh_user} @@ -263,8 +265,8 @@ def to_instance_host_dict(ssh_user, ip="localhost"): # pylint: disable=invalid- def get_cidrs(configurations): """ Gets cidrs of all subnets in all providers - :param configurations: list of configurations (dict) - :return: + @param configurations: list of configurations (dict) + @return: """ all_cidrs = [] for configuration in configurations: @@ -297,9 +299,9 @@ def get_ansible_roles(ansible_roles, log): def get_ansible_galaxy_roles(ansible_galaxy_roles, log): """ Checks if ansible_galaxy_role have all necessary values and adds it to the return list if so. - :param ansible_galaxy_roles: - :param log: - :return: list of valid ansible_galaxy_roles + @param ansible_galaxy_roles: + @param log: + @return: list of valid ansible_galaxy_roles """ ansible_galaxy_roles_yaml = [] for ansible_galaxy_role in (ansible_galaxy_roles or []): @@ -317,9 +319,9 @@ def get_ansible_galaxy_roles(ansible_galaxy_roles, log): def generate_worker_specification_file_yaml(configurations, log): """ Generates worker_specification_file_yaml - :param configurations: list of configurations (dict) - :param log: - :return: worker_specification_yaml + @param configurations: list of configurations (dict) + @param log: + @return: worker_specification_yaml """ log.info("Generating worker specification file...") worker_groups_list = configuration_handler.get_list_by_key(configurations, "workerInstances", False) @@ -367,11 +369,11 @@ def add_wireguard_peers(configurations): def configure_ansible_yaml(providers, configurations, cluster_id, log): """ Generates and writes all ansible-configuration-yaml files. - :param providers: list of providers - :param configurations: list of configurations (dict) - :param cluster_id: id of cluster to create - :param log: - :return: + @param providers: list of providers + @param configurations: list of configurations (dict) + @param cluster_id: id of cluster to create + @param log: + @return: """ delete_old_vars(log) log.info("Writing ansible files...") diff --git a/bibigrid/core/utility/command_line_interpreter.py b/bibigrid/core/utility/command_line_interpreter.py index 30fed2c9..98ed1a35 100644 --- a/bibigrid/core/utility/command_line_interpreter.py +++ b/bibigrid/core/utility/command_line_interpreter.py @@ -28,7 +28,7 @@ def check_cid(cid): def interpret_command_line(): """ Interprets commandline. Used in startup.py - :return: + @return: """ parser = argparse.ArgumentParser(description='BiBiGrid easily sets up clusters within a cloud environment') parser.add_argument("-v", "--verbose", action="count", default=0, diff --git a/bibigrid/core/utility/handler/configuration_handler.py b/bibigrid/core/utility/handler/configuration_handler.py index c50a7f3d..bc124993 100644 --- a/bibigrid/core/utility/handler/configuration_handler.py +++ b/bibigrid/core/utility/handler/configuration_handler.py @@ -19,10 +19,10 @@ def read_configuration(log, path="bibigrid.yml", configuration_list=True): """ Reads yaml from file and returns configuration - :param log: - :param path: Path to yaml file - :param configuration_list: True if list is expected - :return: configurations (dict) + @param log: + @param path: Path to yaml file + @param configuration_list: True if list is expected + @return: configurations (dict) """ configuration = None if os.path.isfile(path): @@ -42,10 +42,10 @@ def read_configuration(log, path="bibigrid.yml", configuration_list=True): def get_list_by_key(configurations, key, get_empty=True): """ Returns a list of objects which are value to the key. - :param get_empty: if true empty configurations return None - :param configurations: YAML of configuration File containing the configuration-data for each provider - :param key: Key that is looked out for - :return: List of values of said key through all configs + @param get_empty: if true empty configurations return None + @param configurations: YAML of configuration File containing the configuration-data for each provider + @param key: Key that is looked out for + @return: List of values of said key through all configs """ return [configuration.get(key) for configuration in configurations if configuration.get(key) or get_empty] @@ -98,11 +98,11 @@ def get_clouds_files(log): def get_cloud_specification(cloud_name, clouds, clouds_public, log): """ As in openstack cloud_public_specification will be overwritten by cloud_private_specification - :param cloud_name: name of the cloud to look for in clouds.yaml - :param clouds: dict containing the data loaded from clouds.yaml - :param clouds_public: dict containing the data loaded from clouds-public.yaml - :param log: - :return: + @param cloud_name: name of the cloud to look for in clouds.yaml + @param clouds: dict containing the data loaded from clouds.yaml + @param clouds_public: dict containing the data loaded from clouds-public.yaml + @param log: + @return: """ cloud_full_specification = {} cloud_private_specification = clouds.get(cloud_name) diff --git a/bibigrid/core/utility/handler/provider_handler.py b/bibigrid/core/utility/handler/provider_handler.py index 3e74b1e9..338aba05 100644 --- a/bibigrid/core/utility/handler/provider_handler.py +++ b/bibigrid/core/utility/handler/provider_handler.py @@ -14,8 +14,9 @@ def get_provider_by_class_name(provider_name, """ Returns provider that is associated with the key provider_name in provider_dict. Otherwise, a KeyError is thrown. - :param provider_name: key of provider_dict - :return: provider + @param provider_name: key of provider_dict + @param provider_dict: + @return: provider """ return provider_dict[provider_name] @@ -23,9 +24,10 @@ def get_provider_by_class_name(provider_name, def get_provider_by_name(provider_name, provider_dict=PROVIDER_NAME_DICT): # pylint: disable=dangerous-default-value """ Returns provider that is associated with the key provider_name in provider_dict. - Otherwise a KeyError is thrown. - :param provider_name: key of provider_dict - :return: provider + Otherwise, a KeyError is thrown. + @param provider_name: key of provider_dict + @param provider_dict + @return: provider """ return provider_dict.get(provider_name) @@ -35,9 +37,9 @@ def get_provider_list_by_name_list(provider_name_list, cloud_specifications): Returns provider list for given provider_name_list If name is not found in PROVIDER_NAME_DICT, PROVIDER_CLASS_DICT is tried instead. If not found in both a key error is thrown. - :param provider_name_list: list of provider names - :param cloud_specifications: list of cloud specifications - :return: list of providers + @param provider_name_list: list of provider names + @param cloud_specifications: list of cloud specifications + @return: list of providers """ provider_list = [ (get_provider_by_name(provider_name) or get_provider_by_class_name(provider_name))(cloud_specification) for @@ -50,9 +52,9 @@ def get_providers(configurations, log): Reads list of provider_names from configurations. Determines list of providers by provider_names and returns it. If providers don't match a key error is thrown and the program exits with failure state 1. - :param configurations: - :param log: - :return: + @param configurations: + @param log: + @return: """ cloud_specifications = configuration_handler.get_cloud_specifications(configurations, log) if cloud_specifications: diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index f5c71ac3..ca6d51d9 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -1,6 +1,6 @@ """ This module handles ssh and sftp connections to master and vpngtw. It also holds general execution routines used to -setup the Cluster. +set up the Cluster. """ import os import socket @@ -10,15 +10,15 @@ import sympy import yaml -from bibigrid.core.utility import ansible_commands as aC +from bibigrid.core.utility import ansible_commands as a_c from bibigrid.models.exceptions import ConnectionException, ExecutionException PRIVATE_KEY_FILE = ".ssh/id_ecdsa" # to name bibigrid-temp keys identically on remote -ANSIBLE_SETUP = [aC.NO_UPDATE, aC.UPDATE, aC.PYTHON3_PIP, aC.ANSIBLE_PASSLIB, - (f"chmod 600 {PRIVATE_KEY_FILE}", "Adjust private key permissions."), aC.PLAYBOOK_HOME, - aC.PLAYBOOK_HOME_RIGHTS, aC.ADD_PLAYBOOK_TO_LINUX_HOME] +ANSIBLE_SETUP = [a_c.NO_UPDATE, a_c.UPDATE, a_c.PYTHON3_PIP, a_c.ANSIBLE_PASSLIB, + (f"chmod 600 {PRIVATE_KEY_FILE}", "Adjust private key permissions."), a_c.PLAYBOOK_HOME, + a_c.PLAYBOOK_HOME_RIGHTS, a_c.ADD_PLAYBOOK_TO_LINUX_HOME] # ANSIBLE_START = [aC.WAIT_READY, aC.UPDATE, aC.MV_ANSIBLE_CONFIG, aC.EXECUTE] # another UPDATE seems to not necessary. -ANSIBLE_START = [aC.WAIT_READY, aC.MV_ANSIBLE_CONFIG, aC.EXECUTE] +ANSIBLE_START = [a_c.WAIT_READY, a_c.MV_ANSIBLE_CONFIG, a_c.EXECUTE] VPN_SETUP = [("echo Example", "Echos an Example")] @@ -53,8 +53,8 @@ def get_ac_command(providers, name): def get_add_ssh_public_key_commands(ssh_public_key_files): """ Builds and returns the necessary commands to add given public keys to remote for additional access. - :param ssh_public_key_files: public keys to add - :return: list of public key add commands + @param ssh_public_key_files: public keys to add + @return: list of public key add commands """ commands = [] if ssh_public_key_files: @@ -69,11 +69,11 @@ def copy_to_server(sftp, local_path, remote_path, log): """ Recursively copies files and folders to server. If a folder is given as local_path, the structure within will be kept. - :param sftp: sftp connection - :param local_path: file or folder locally - :param remote_path: file or folder locally - :param log: - :return: + @param sftp: sftp connection + @param local_path: file or folder locally + @param remote_path: file or folder locally + @param log: + @return: """ log.debug("Copy %s to %s...", local_path, remote_path) if os.path.isfile(local_path): @@ -91,13 +91,13 @@ def is_active(client, floating_ip_address, private_key, username, log, gateway, """ Checks if connection is possible and therefore if server is active. Raises paramiko.ssh_exception.NoValidConnectionsError if timeout is reached - :param client: created client - :param floating_ip_address: ip to connect to - :param private_key: SSH-private_key - :param username: SSH-username - :param log: - :param timeout: how long to wait between ping - :param gateway: if node should be reached over a gateway port is set to 30000 + subnet * 256 + host + @param client: created client + @param floating_ip_address: ip to connect to + @param private_key: SSH-private_key + @param username: SSH-username + @param log: + @param timeout: how long to wait between ping + @param gateway: if node should be reached over a gateway port is set to 30000 + subnet * 256 + host (waiting grows quadratically till 2**timeout before accepting failure) """ attempts = 0 @@ -139,8 +139,8 @@ def line_buffered(f): """ https://stackoverflow.com/questions/25260088/paramiko-with-continuous-stdout temporary hangs? - :param f: - :return: + @param f: + @return: """ line_buf = b"" while not f.channel.exit_status_ready(): @@ -154,9 +154,9 @@ def line_buffered(f): def execute_ssh_cml_commands(client, commands, log): """ Executes commands and logs exit_status accordingly. - :param client: Client with connection to remote - :param commands: Commands to execute on remote - :param log: + @param client: Client with connection to remote + @param commands: Commands to execute on remote + @param log: """ for command in commands: _, ssh_stdout, _ = client.exec_command(command[0]) @@ -190,13 +190,13 @@ def ansible_preparation(floating_ip, private_key, username, log, gateway, comman Copies additional files and executes additional commands if given. The playbook is copied later, because it needs all servers setup and is not time intensive. See: create.update_playbooks - :param floating_ip: public ip of server to ansible-prepare - :param private_key: generated private key of all cluster-server - :param username: username of all server - :param log: - :param commands: additional commands to execute - :param filepaths: additional files to copy: (localpath, remotepath) - :param gateway + @param floating_ip: public ip of server to ansible-prepare + @param private_key: generated private key of all cluster-server + @param username: username of all server + @param log: + @param commands: additional commands to execute + @param filepaths: additional files to copy: (localpath, remotepath) + @param gateway """ if filepaths is None: filepaths = [] @@ -211,13 +211,13 @@ def ansible_preparation(floating_ip, private_key, username, log, gateway, comman def execute_ssh(floating_ip, private_key, username, log, gateway, commands=None, filepaths=None): """ Executes commands on remote and copies files given in filepaths - :param floating_ip: public ip of remote - :param private_key: key of remote - :param username: username of remote - :param commands: commands - :param log: - :param filepaths: filepaths (localpath, remotepath) - :param gateway: gateway if used + @param floating_ip: public ip of remote + @param private_key: key of remote + @param username: username of remote + @param commands: commands + @param log: + @param filepaths: filepaths (localpath, remotepath) + @param gateway: gateway if used """ if commands is None: commands = [] diff --git a/bibigrid/core/utility/id_generation.py b/bibigrid/core/utility/id_generation.py index 51ded9a9..f40336f1 100644 --- a/bibigrid/core/utility/id_generation.py +++ b/bibigrid/core/utility/id_generation.py @@ -13,7 +13,7 @@ def generate_cluster_id(): """ Generates an encrypted shortUUID with length MAX_ID_LENGTH - :return: + @return: """ uuid = shortuuid.ShortUUID() uuid.set_alphabet(CLUSTER_UUID_ALPHABET) @@ -23,8 +23,8 @@ def generate_cluster_id(): def generate_safe_cluster_id(providers): """ Generates a cluster_id and checks if cluster_id is not in use. When a unique id is found it is returned - :param providers: providers to check whether they use said cluster_id - :return: cluster_id + @param providers: providers to check whether they use said cluster_id + @return: cluster_id """ id_is_unique = False cluster_id = None @@ -37,9 +37,9 @@ def generate_safe_cluster_id(providers): def is_unique_cluster_id(cluster_id, providers): """ Checks if cluster_id is not in use on any provider - :param cluster_id: generated cluster_ird - :param providers: providers to check - :return: True if cluster_id is unique. False else. + @param cluster_id: generated cluster_ird + @param providers: providers to check + @return: True if cluster_id is unique. False else. """ for provider in providers: for server in provider.list_servers(): @@ -54,6 +54,6 @@ def is_unique_cluster_id(cluster_id, providers): def generate_munge_key(): """ Generates a munge key (UUID) for slurm - :return: + @return: """ return shortuuid.ShortUUID().random(32) diff --git a/bibigrid/core/utility/paths/ansible_resources_path.py b/bibigrid/core/utility/paths/ansible_resources_path.py index de4a99d5..34e81a74 100644 --- a/bibigrid/core/utility/paths/ansible_resources_path.py +++ b/bibigrid/core/utility/paths/ansible_resources_path.py @@ -4,7 +4,7 @@ import os -import bibigrid.core.utility.paths.basic_path as bP +import bibigrid.core.utility.paths.basic_path as b_p # UNIVERSAL ANSIBLE_HOSTS: str = "ansible_hosts" @@ -27,7 +27,7 @@ # LOCAL # ANSIBLE_CFG_PATH = os.path.join(bP.RESOURCES_PATH, ANSIBLE_CFG) PLAYBOOK = "playbook/" -PLAYBOOK_PATH: str = os.path.join(bP.RESOURCES_PATH, PLAYBOOK) +PLAYBOOK_PATH: str = os.path.join(b_p.RESOURCES_PATH, PLAYBOOK) HOSTS_FILE = os.path.join(PLAYBOOK_PATH, HOSTS_YML) HOSTS_CONFIG_FILE: str = os.path.join(PLAYBOOK_PATH, ANSIBLE_HOSTS) CONFIG_ROOT_PATH: str = os.path.join(PLAYBOOK_PATH, VARS_PATH) diff --git a/bibigrid/core/utility/paths/bin_path.py b/bibigrid/core/utility/paths/bin_path.py index 16b10011..344ac283 100644 --- a/bibigrid/core/utility/paths/bin_path.py +++ b/bibigrid/core/utility/paths/bin_path.py @@ -5,9 +5,9 @@ import os -import bibigrid.core.utility.paths.basic_path as bP +import bibigrid.core.utility.paths.basic_path as b_p BIN: str = "bin/" -BIN_PATH: str = os.path.join(bP.RESOURCES_PATH, BIN) +BIN_PATH: str = os.path.join(b_p.RESOURCES_PATH, BIN) BIN_PATH_REMOTE: str = BIN diff --git a/bibigrid/core/utility/validate_configuration.py b/bibigrid/core/utility/validate_configuration.py index cca9b113..1980abda 100644 --- a/bibigrid/core/utility/validate_configuration.py +++ b/bibigrid/core/utility/validate_configuration.py @@ -14,10 +14,10 @@ def evaluate(check_name, check_result, log): """ Logs check_result as warning if failed and as success if succeeded. - :param check_name: - :param check_result: - :param log: - :return: + @param check_name: + @param check_result: + @param log: + @return: """ if check_result: log.info("Checking %s: Success", check_name) @@ -30,10 +30,10 @@ def check_provider_data(provider_data_list, provider_count, log): """ Checks if all provider datas are unique and if enough providers are given #ToDo for multiple cloud locations additional provider data needs to be added - :param provider_data_list: list of all provider data - :param provider_count: number of providers - :param log: - :return: True if enough providers are given and all providers are unique + @param provider_data_list: list of all provider data + @param provider_count: number of providers + @param log: + @return: True if enough providers are given and all providers are unique """ log.info("Checking provider names") success = True @@ -89,12 +89,12 @@ def evaluate_ssh_public_key_file_security(ssh_public_key_file, log): def has_enough(maximum, needed, keeper, thing, log): """ Method logs and compares whether enough free things are available - :param maximum: maximum (available) resources of thing - :param needed: minimum needed to run - :param keeper: description of the object having the thing that is checked (for logging) - :param thing: description of what resource is checked (RAM for example) (for logging) - :param log: - :return: True if maximum is larger or equal to the needed + @param maximum: maximum (available) resources of thing + @param needed: minimum needed to run + @param keeper: description of the object having the thing that is checked (for logging) + @param thing: description of what resource is checked (RAM for example) (for logging) + @param log: + @return: True if maximum is larger or equal to the needed """ success = True if maximum >= needed: @@ -131,7 +131,7 @@ def check_clouds_yaml_security(log): def check_cloud_yaml(cloud_specification, log): """ - Check if cloud_specification is valid i.e. contains the necessary authentification data. + Check if cloud_specification is valid i.e. contains the necessary authentication data. @param cloud_specification: dict to check whether it is a valid cloud_specification @param log @return: True if cloud_specification is valid. False else. @@ -175,8 +175,8 @@ def __init__(self, configurations, providers, log): Sets configurations, providers and prepares the required_resources_dict. While executing the checks, needed resources are counted. In the end check_quotas will decide whether enough resources are available. - :param configurations: List of configurations (dicts) - :param providers: List of providers + @param configurations: List of configurations (dicts) + @param providers: List of providers """ self.log = log self.configurations = configurations @@ -194,12 +194,12 @@ def validate(self): The validation steps are as follows: Check connection can be established Check provider uniqueness - Check servergroup + Check server group Check instances are available Check images and volumes are available Check network and subnet are available Check quotas - :return: + @return: """ success = bool(self.providers) self.log.info("Validating config file...") @@ -223,7 +223,7 @@ def check_master_vpn_worker(self): Checks if first configuration has a masterInstance defined and every other configuration has a vpnInstance defined. If one is missing said provider wouldn't be reachable over the cluster, because no floating IP would be given. - :return: True if first configuration has a masterInstance and every other a vpnInstance + @return: True if first configuration has a masterInstance and every other a vpnInstance """ self.log.info("Checking master/vpn") success = True @@ -239,7 +239,7 @@ def check_master_vpn_worker(self): def check_provider_connections(self): """ Checks if all providers are reachable - :return: True if all providers are reachable + @return: True if all providers are reachable """ success = True providers_unconnectable = [] @@ -257,7 +257,7 @@ def check_provider_connections(self): def check_instances(self): """ Checks if all instances exist and image and instance-type are compatible - :return: true if image and instance-type (flavor) exist for all instances and are compatible + @return: true if image and instance-type (flavor) exist for all instances and are compatible """ self.log.info("Checking instance images and type") success = True @@ -280,10 +280,10 @@ def check_instances(self): def check_instance(self, instance_name, instance, provider): """ Checks if instance image exists and whether it is compatible with the defined instance/server type (flavor). - :param instance_name: containing name for logging purposes - :param instance: dict containing image, type and count (count is not used) - :param provider: provider - :return: true if type and image compatible and existing + @param instance_name: containing name for logging purposes + @param instance: dict containing image, type and count (count is not used) + @param provider: provider + @return: true if type and image compatible and existing """ self.required_resources_dict[provider.cloud_specification['identifier']]["instances"] += instance.get( "count") or 1 @@ -304,10 +304,10 @@ def check_instance(self, instance_name, instance, provider): def check_instance_type_image_combination(self, instance_type, instance_image, provider): """ Checks, if enough ram, disk space for instance_image are provided by instance_type on provider. - :param instance_type - :param instance_image - :param provider - :return true, if enough resources available + @param instance_type + @param instance_image + @param provider + @return true, if enough resources available """ success = True # check @@ -333,7 +333,7 @@ def check_instance_type_image_combination(self, instance_type, instance_image, p def check_volumes(self): """ Checking if volume or snapshot exists for all volumes - :return: True if all snapshot and volumes are found. Else false. + @return: True if all snapshot and volumes are found. Else false. """ self.log.info("Checking volumes...") success = True @@ -367,7 +367,7 @@ def check_volumes(self): def check_network(self): """ Check if network (or subnet) is accessible - :return True if any given network or subnet is accessible by provider + @return True if any given network or subnet is accessible by provider """ self.log.info("Checking network...") success = True @@ -391,11 +391,15 @@ def check_network(self): success = False else: self.log.info(f"Subnet '{subnet_name_or_id}' found") - return bool(success and (network_name_or_id or subnet_name_or_id)) + else: + self.log.warning( + f"Neither network nor subnet given in configuration {provider.cloud_specification['identifier']}.") + success = False + return success def check_server_group(self): """ - :return: True if server group accessible + @return: True if server group accessible """ success = True for configuration, provider in zip(self.configurations, self.providers): @@ -418,7 +422,7 @@ def check_quotas(self): Covered resources are: cores, floating_ips, instances, ram, volumes, volumeGigabytes, snapshots, backups and backupGigabytes. If a concrete provider implementation is unable to return remaining resources a maximum number is returned to make the check not fail because of the missing API implementation. - :return: True if check succeeded. Else false. + @return: True if check succeeded. Else false. """ self.log.info("Checking quotas") success = True @@ -433,7 +437,7 @@ def check_quotas(self): def check_ssh_public_key_files(self): """ Checks if keys listed in the config exist - :return: True if check succeeded. Else false. + @return: True if check succeeded. Else false. """ success = True for configuration in self.configurations: diff --git a/bibigrid/core/utility/wireguard/wireguard_keys.py b/bibigrid/core/utility/wireguard/wireguard_keys.py index c25c7316..2015f2e1 100755 --- a/bibigrid/core/utility/wireguard/wireguard_keys.py +++ b/bibigrid/core/utility/wireguard/wireguard_keys.py @@ -11,7 +11,7 @@ def generate(): """ Generates private and public key for wireguard - @return: tuple (privatekey_str, publickey_str) + @return: tuple (private_key_str, publickey_str) """ # generate private key private_key = X25519PrivateKey.generate() @@ -20,10 +20,10 @@ def generate(): format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption() ) - privatekey_str = codecs.encode(bytes_, 'base64').decode('utf8').strip() + private_key_str = codecs.encode(bytes_, 'base64').decode('utf8').strip() # derive public key publickey = private_key.public_key().public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) publickey_str = codecs.encode(publickey, 'base64').decode('utf8').strip() - return privatekey_str, publickey_str + return private_key_str, publickey_str diff --git a/bibigrid/models/return_threading.py b/bibigrid/models/return_threading.py index a7c7a1b4..52c8b054 100644 --- a/bibigrid/models/return_threading.py +++ b/bibigrid/models/return_threading.py @@ -12,7 +12,9 @@ class ReturnThread(threading.Thread): - An exception occurred within the called function is raised by join() """ - def __init__(self, group=None, target=None, name=None, args=(), kwargs={}): # pylint: disable=dangerous-default-value + # pylint: disable=dangerous-default-value + def __init__(self, group=None, target=None, name=None, args=(), + kwargs={}): threading.Thread.__init__(self, group, target, name, args, kwargs) self._return = None self._exc = None @@ -21,7 +23,7 @@ def run(self): if self._target is not None: try: self._return = self._target(*self._args, **self._kwargs) - except Exception as exc: # pylint: disable=broad-except + except Exception as exc: # pylint: disable=broad-except self._exc = exc def join(self, *args): diff --git a/bibigrid/openstack/openstack_provider.py b/bibigrid/openstack/openstack_provider.py index db0517e3..5efabf02 100644 --- a/bibigrid/openstack/openstack_provider.py +++ b/bibigrid/openstack/openstack_provider.py @@ -82,8 +82,8 @@ def delete_application_credential_by_id_or_name(self, ac_id_or_name): """ Deletes existing application credential by id or name and returns true. If application credential not found it returns false. - :param ac_id_or_name: application credential id or name - :return: True if deleted else false + @param ac_id_or_name: application credential id or name + @return: True if deleted else false """ try: self.keystone_client.application_credentials.delete(ac_id_or_name) # id @@ -137,9 +137,9 @@ def create_server(self, name, flavor, image, network, key_name=None, wait=True, def delete_server(self, name_or_id, delete_ips=True): """ Deletes server. floating_ip as well if delete_ips is true. The resources are then free again - :param name_or_id: - :param delete_ips: - :return: + @param name_or_id: + @param delete_ips: + @return: """ return self.conn.delete_server(name_or_id=name_or_id, wait=False, timeout=180, delete_ips=delete_ips, delete_ip_retry=1) @@ -154,7 +154,7 @@ def close(self): return self.conn.close() def create_keypair(self, name, public_key): - # When running a multicloud approach on the same provider and same account, + # When running a multi-cloud approach on the same provider and same account, # make sure that the keypair is only created ones. try: return self.conn.create_keypair(name=name, public_key=public_key) @@ -172,9 +172,9 @@ def get_subnet_ids_by_network(self, network): def get_free_resources(self): """ Uses openstack.block_storage to get all relevant volume resources. - Uses the openstack.compute to get all relevant compute resources. + Uses openstack compute to get all relevant compute resources. Floating-IP is not returned correctly by openstack. - :return: Dictionary containing the free resources + @return: Dictionary containing the free resources """ compute_limits = dict(self.conn.compute.get_limits()["absolute"]) volume_limits = dict(self.conn.block_storage.get_limits()["absolute"]) @@ -194,8 +194,8 @@ def create_volume_from_snapshot(self, snapshot_name_or_id): """ Uses the cinder API to create a volume from snapshot: https://github.com/openstack/python-cinderclient/blob/master/cinderclient/v3/volumes.py - :param snapshot_name_or_id: name or id of snapshot - :return: id of created volume + @param snapshot_name_or_id: name or id of snapshot + @return: id of created volume """ LOG.debug("Trying to create volume from snapshot") snapshot = self.conn.get_volume_snapshot(snapshot_name_or_id) @@ -217,8 +217,8 @@ def create_volume_from_snapshot(self, snapshot_name_or_id): def get_external_network(self, network_name_or_id): """ Finds router interface with network id equal to given network and by that the external network. - :param network_name_or_id:Name or id of network - :return:Corresponding external network + @param network_name_or_id:Name or id of network + @return:Corresponding external network """ network_id = self.conn.get_network(network_name_or_id)["id"] for router in self.conn.list_routers(): @@ -230,9 +230,9 @@ def get_external_network(self, network_name_or_id): def attach_available_floating_ip(self, network=None, server=None): """ Get a floating IP from a network or a pool and attach it to the server - :param network: - :param server: - :return: + @param network: + @param server: + @return: """ floating_ip = self.conn.available_floating_ip(network=network) if server: @@ -256,13 +256,13 @@ def get_flavors(self): def set_allowed_addresses(self, id_or_ip, allowed_address_pairs): """ Set allowed address (or CIDR) for the given network interface/port - :param id_or_ip: id or ip-address of the port/interfac - :param allowed_address: a list of allowed address pairs. For example: + @param id_or_ip: id or ip-address of the port/interface + @param allowed_address_pairs: a list of allowed address pairs. For example: [{ "ip_address": "23.23.23.1", "mac_address": "fa:16:3e:c4:cd:3f" }] - :return updated port: + @return updated port: """ # get port id if ip address is given if re.match(PATTERN_IPV4, id_or_ip): @@ -277,8 +277,8 @@ def set_allowed_addresses(self, id_or_ip, allowed_address_pairs): def create_security_group(self, name, rules=None): """ Create a security and add given rules - :param name: Name of the security group to be created - :param rules: List of firewall rules in the following format. + @param name: Name of the security group to be created + @param rules: List of firewall rules in the following format. rules = [{ "direction": "ingress" | "egress", "ethertype": "IPv4" | "IPv6", "protocol": "txp" | "udp" | "icmp" | None @@ -289,7 +289,7 @@ def create_security_group(self, name, rules=None): { ... } ] - :return: created security group + @return: created security group """ security_group = self.conn.create_security_group(name, f"Security group for {name}.") if rules is not None: @@ -299,8 +299,8 @@ def create_security_group(self, name, rules=None): def delete_security_group(self, name_or_id): """ Delete a security group - :param name_or_id : Name or Id of the security group to be deleted - :return: True if delete succeeded, False otherwise. + @param name_or_id : Name or id of the security group to be deleted + @return: True if delete succeeded, False otherwise. """ try: return self.conn.delete_security_group(name_or_id) @@ -310,9 +310,9 @@ def delete_security_group(self, name_or_id): def append_rules_to_security_group(self, name_or_id, rules): """ Append firewall rules to given security group - :param name_or_id: - :param rules: - :return: + @param name_or_id: + @param rules: + @return: """ for rule in rules: self.conn.create_security_group_rule(name_or_id, direction=rule["direction"], ethertype=rule["ethertype"], diff --git a/requirements.txt b/requirements.txt index d21a3828..c4ddcc7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,18 @@ openstacksdk==0.62 -mergedeep -paramiko +mergedeep~=1.3.4 +paramiko~=2.12.0 python-cinderclient python-keystoneclient python-novaclient python-openstackclient==6.0.0 -PyYAML -shortuuid -sshtunnel -sympy -seedir \ No newline at end of file +PyYAML~=6.0 +shortuuid~=1.0.11 +sshtunnel~=0.4.0 +sympy~=1.12 +seedir~=0.4.2 +cryptography~=38.0.4 +uvicorn~=0.23.2 +fastapi~=0.101.0 +pydantic~=2.1.1 +keystoneauth1~=5.1.0 +filelock~=3.13.1 \ No newline at end of file diff --git a/tests/test_id_generation.py b/tests/test_id_generation.py index 0e7c4009..64ae3703 100644 --- a/tests/test_id_generation.py +++ b/tests/test_id_generation.py @@ -16,7 +16,7 @@ class TestIDGeneration(TestCase): def test_generate_cluster_id(self): """ This test is not ideal, but prevents worst within a reasonable runtime - :return: + @return: """ test_list = [] for _ in range(10000): From 3c3e35b7c941c8eecb8287798a0e2e93e687e390 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 26 Mar 2024 16:13:37 +0100 Subject: [PATCH 057/145] added timeout to common_configuration --- bibigrid/core/utility/ansible_commands.py | 2 +- bibigrid/core/utility/ansible_configurator.py | 10 +++++++--- .../roles/bibigrid/files/slurm/create_server.py | 14 +++++++------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/bibigrid/core/utility/ansible_commands.py b/bibigrid/core/utility/ansible_commands.py index fc6c2815..66fcb5ba 100644 --- a/bibigrid/core/utility/ansible_commands.py +++ b/bibigrid/core/utility/ansible_commands.py @@ -50,7 +50,7 @@ MV_ANSIBLE_CONFIG = ( "sudo install -D /opt/playbook/ansible.cfg /etc/ansible/ansible.cfg", "Move ansible configuration.") EXECUTE = (f"ansible-playbook {os.path.join(aRP.PLAYBOOK_PATH_REMOTE, aRP.SITE_YML)} -i " - f"{os.path.join(aRP.PLAYBOOK_PATH_REMOTE, aRP.ANSIBLE_HOSTS)} -l vpn", + f"{os.path.join(aRP.PLAYBOOK_PATH_REMOTE, aRP.ANSIBLE_HOSTS)} -l vpn -vvvv", "Execute ansible playbook. Be patient.") # ansible setup diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 642e9ae4..6dad0ac5 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -29,6 +29,7 @@ SLURM_CONF = {"db": "slurm", "db_user": "slurm", "db_password": "changeme", "munge_key": id_generation.generate_munge_key(), "elastic_scheduling": {"SuspendTime": 3600, "ResumeTimeout": 900, "TreeWidth": 128}} +CLOUD_SCHEDULING = {"timeout": 5} def delete_old_vars(log): @@ -180,7 +181,11 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us "slurm": master_configuration.get("slurm", True), "ssh_user": ssh_user, "slurm_conf": mergedeep.merge({}, SLURM_CONF, master_configuration.get("slurmConf", {}), - strategy=mergedeep.Strategy.TYPESAFE_REPLACE)} + strategy=mergedeep.Strategy.TYPESAFE_REPLACE), + "cloud_scheduling": mergedeep.merge({}, CLOUD_SCHEDULING, + master_configuration.get( + "cloudScheduling", {}), + strategy=mergedeep.Strategy.TYPESAFE_REPLACE)} if master_configuration.get("nfs"): nfs_shares = master_configuration.get("nfsShares", []) nfs_shares = nfs_shares + DEFAULT_NFS_SHARES @@ -197,8 +202,7 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us master_configuration.get("zabbixConf", {}), strategy=mergedeep.Strategy.TYPESAFE_REPLACE) - for from_key, to_key in [("ansibleRoles", "ansible_roles"), - ("ansibleGalaxyRoles", "ansible_galaxy_roles")]: + for from_key, to_key in [("ansibleRoles", "ansible_roles"), ("ansibleGalaxyRoles", "ansible_galaxy_roles")]: pass_through(master_configuration, common_configuration_yaml, from_key, to_key) if len(configurations) > 1: diff --git a/resources/playbook/roles/bibigrid/files/slurm/create_server.py b/resources/playbook/roles/bibigrid/files/slurm/create_server.py index ab9d2c12..00452b97 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/create_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/create_server.py @@ -117,7 +117,7 @@ def start_server(worker, start_worker_group, start_data): server_start_data["other_openstack_exception"].append(worker) -def check_ssh_active(private_ip, private_key="/opt/slurm/.ssh/id_ecdsa", username="ubuntu", timeout=7): +def check_ssh_active(private_ip, private_key="/opt/slurm/.ssh/id_ecdsa", username="ubuntu"): """ Waits until SSH connects successful. This guarantees that the node can be reached via Ansible. @param private_ip: ip of node @@ -138,7 +138,7 @@ def check_ssh_active(private_ip, private_key="/opt/slurm/.ssh/id_ecdsa", usernam establishing_connection = False except paramiko.ssh_exception.NoValidConnectionsError as exc: logging.info("Attempting to connect to %s... This might take a while", private_ip) - if attempts < timeout: + if attempts < common_config["cloud_scheduling"]["timeout"]: time.sleep(2 ** attempts) attempts += 1 else: @@ -213,16 +213,16 @@ def _run_playbook(cmdline_args): worker_groups = [] for filename in os.listdir(GROUP_VARS_PATH): if filename != "master.yml": - f = os.path.join(GROUP_VARS_PATH, filename) + worker_group_yaml_file = os.path.join(GROUP_VARS_PATH, filename) # checking if it is a file - if os.path.isfile(f): - with open(f, mode="r", encoding="utf-8") as worker_group: - worker_groups.append(yaml.safe_load(worker_group)) + if os.path.isfile(worker_group_yaml_file): + with open(worker_group_yaml_file, mode="r", encoding="utf-8") as worker_group_yaml: + worker_groups.append(yaml.safe_load(worker_group_yaml)) # read common configuration with open("/opt/playbook/vars/common_configuration.yml", mode="r", encoding="utf-8") as common_configuration_file: common_config = yaml.safe_load(common_configuration_file) - +logging.warning(f"ThisGrep {common_config['cloud_scheduling']['timeout']}") # read clouds.yaml with open("/etc/openstack/clouds.yaml", mode="r", encoding="utf-8") as clouds_file: clouds = yaml.safe_load(clouds_file)["clouds"] From ae30617b0ef737f871b5ab08e3148425f51f879d Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 27 Mar 2024 13:12:32 +0100 Subject: [PATCH 058/145] removed debug verbosity and improved log message wording --- bibigrid/core/utility/ansible_commands.py | 2 +- resources/playbook/roles/bibigrid/files/slurm/create_server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bibigrid/core/utility/ansible_commands.py b/bibigrid/core/utility/ansible_commands.py index 66fcb5ba..fc6c2815 100644 --- a/bibigrid/core/utility/ansible_commands.py +++ b/bibigrid/core/utility/ansible_commands.py @@ -50,7 +50,7 @@ MV_ANSIBLE_CONFIG = ( "sudo install -D /opt/playbook/ansible.cfg /etc/ansible/ansible.cfg", "Move ansible configuration.") EXECUTE = (f"ansible-playbook {os.path.join(aRP.PLAYBOOK_PATH_REMOTE, aRP.SITE_YML)} -i " - f"{os.path.join(aRP.PLAYBOOK_PATH_REMOTE, aRP.ANSIBLE_HOSTS)} -l vpn -vvvv", + f"{os.path.join(aRP.PLAYBOOK_PATH_REMOTE, aRP.ANSIBLE_HOSTS)} -l vpn", "Execute ansible playbook. Be patient.") # ansible setup diff --git a/resources/playbook/roles/bibigrid/files/slurm/create_server.py b/resources/playbook/roles/bibigrid/files/slurm/create_server.py index 00452b97..55872254 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/create_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/create_server.py @@ -222,7 +222,7 @@ def _run_playbook(cmdline_args): # read common configuration with open("/opt/playbook/vars/common_configuration.yml", mode="r", encoding="utf-8") as common_configuration_file: common_config = yaml.safe_load(common_configuration_file) -logging.warning(f"ThisGrep {common_config['cloud_scheduling']['timeout']}") +logging.warning(f"Maximum 'is active' attempts: {common_config['cloud_scheduling']['timeout']}") # read clouds.yaml with open("/etc/openstack/clouds.yaml", mode="r", encoding="utf-8") as clouds_file: clouds = yaml.safe_load(clouds_file)["clouds"] From 4f2ca97bf72dad4e22f89a42d8d6dc73f65e6626 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 27 Mar 2024 16:05:45 +0100 Subject: [PATCH 059/145] fixed is_active structure --- bibigrid/core/utility/handler/ssh_handler.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index 54b874b0..27105a68 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -99,21 +99,22 @@ def is_active(client, paramiko_key, ssh_data, log): """ attempts = 0 establishing_connection = True + log.info(f"Attempting to connect to {ssh_data['floating_ip']}... This might take a while") + port = 22 + if ssh_data.get('gateway'): + log.info(f"Using SSH Gateway {ssh_data['gateway'].get('ip')}") + octets = {f'oct{enum + 1}': int(elem) for enum, elem in enumerate(ssh_data['floating_ip'].split("."))} + port = int(sympy.sympify(ssh_data['gateway']["portFunction"]).subs(dict(octets))) + log.info(f"Port {port} will be used (see {ssh_data['gateway']['portFunction']} and octets {octets}).") while establishing_connection: try: - port = 22 - if ssh_data.get('gateway'): - log.info(f"Using SSH Gateway {ssh_data['gateway'].get('ip')}") - octets = {f'oct{enum + 1}': int(elem) for enum, elem in enumerate(ssh_data['floating_ip'].split("."))} - port = int(sympy.sympify(ssh_data['gateway']["portFunction"]).subs(dict(octets))) - log.info(f"Port {port} will be used (see {ssh_data['gateway']['portFunction']} and octets {octets}).") + log.info(f"Attempt {attempts}/{ssh_data['timeout']}. Connecting to {ssh_data['floating_ip']}") client.connect(hostname=ssh_data['gateway'].get("ip") or ssh_data['floating_ip'], username=ssh_data['username'], pkey=paramiko_key, timeout=7, auth_timeout=ssh_data['timeout'], port=port) establishing_connection = False log.info(f"Successfully connected to {ssh_data['floating_ip']}") except paramiko.ssh_exception.NoValidConnectionsError as exc: - log.info(f"Attempting to connect to {ssh_data['floating_ip']}... This might take a while", ) if attempts < ssh_data['timeout']: time.sleep(2 ** attempts) attempts += 1 From ba7954265261b776b3bf40c2856325924aa73597 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 28 Mar 2024 14:40:32 +0100 Subject: [PATCH 060/145] fixed pip dependabot.yml --- .github/dependabot.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e644f86c..4da4d6da 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,4 @@ updates: schedule: interval: "daily" open-pull-requests-limit: 10 - versioning-strategy: "widen" - target: - versions: [">=3.0.0"] + versioning-strategy: "auto" From 9ccd5a5eab0f1fbb7c8795bb90b729c934cdcba3 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 3 Apr 2024 16:51:24 +0200 Subject: [PATCH 061/145] added documentation. Changed timeout to 2**(2+attempts) to decrease number of unlikely to work attempts --- bibigrid/core/actions/create.py | 2 +- bibigrid/core/utility/ansible_configurator.py | 9 ++++----- bibigrid/core/utility/handler/ssh_handler.py | 6 ++++-- documentation/markdown/features/configuration.md | 13 ++++++++++++- .../roles/bibigrid/files/slurm/create_server.py | 6 +++--- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index b3cd2c57..adeeb588 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -81,7 +81,7 @@ def __init__(self, providers, configurations, config_path, log, debug=False, clu self.ssh_user = configurations[0].get("sshUser") or "ubuntu" self.ssh_add_public_key_commands = ssh_handler.get_add_ssh_public_key_commands( configurations[0].get("sshPublicKeyFiles")) - self.ssh_timeout = configurations[0].get("sshTimeout", 5) + self.ssh_timeout = configurations[0].get("sshTimeout", 4) self.config_path = config_path self.master_ip = None self.log.debug("Cluster-ID: %s", self.cluster_id) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 6e24e4f2..af6facbe 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -6,10 +6,10 @@ import mergedeep import yaml -from bibigrid.core.actions.version import __version__ from bibigrid.core.actions import create from bibigrid.core.actions import ide +from bibigrid.core.actions.version import __version__ from bibigrid.core.utility import id_generation from bibigrid.core.utility import yaml_dumper from bibigrid.core.utility.handler import configuration_handler @@ -30,7 +30,7 @@ SLURM_CONF = {"db": "slurm", "db_user": "slurm", "db_password": "changeme", "munge_key": id_generation.generate_munge_key(), "elastic_scheduling": {"SuspendTime": 3600, "ResumeTimeout": 900, "TreeWidth": 128}} -CLOUD_SCHEDULING = {"timeout": 5} +CLOUD_SCHEDULING = {"sshTimeout": 4} def delete_old_vars(log): @@ -185,9 +185,8 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us master_configuration.get("slurmConf", {}), strategy=mergedeep.Strategy.TYPESAFE_REPLACE), "cloud_scheduling": mergedeep.merge({}, CLOUD_SCHEDULING, - master_configuration.get( - "cloudScheduling", {}), - strategy=mergedeep.Strategy.TYPESAFE_REPLACE)} + master_configuration.get("cloudScheduling", {}), + strategy=mergedeep.Strategy.TYPESAFE_REPLACE)} if master_configuration.get("nfs"): nfs_shares = master_configuration.get("nfsShares", []) nfs_shares = nfs_shares + DEFAULT_NFS_SHARES diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index 0baf0f20..0a742318 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -113,10 +113,12 @@ def is_active(client, paramiko_key, ssh_data, log): username=ssh_data['username'], pkey=paramiko_key, timeout=7, auth_timeout=ssh_data['timeout'], port=port) establishing_connection = False - log.info(f"Successfully connected to {ssh_data['floating_ip']}") + log.info(f"Successfully connected to {ssh_data['floating_ip']}.") except paramiko.ssh_exception.NoValidConnectionsError as exc: if attempts < ssh_data['timeout']: - time.sleep(2 ** attempts) + sleep_time = 2 ** (attempts+2) + time.sleep(sleep_time) + log.info(f"Waiting {sleep_time} before attempting to reconnect.") attempts += 1 else: log.error(f"Attempt to connect to {ssh_data['floating_ip']} failed.") diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index b327f2ba..0f86da90 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -44,6 +44,17 @@ sshPublicKeyFiles: - /home/user/.ssh/id_ecdsa_colleague.pub ``` +#### sshTimeout (optional) +Defines the number of attempts that BiBiGrid will try to connect to the master instance via ssh. +Attempts have a pause of `2^(attempts+2)` seconds in between. Default value is 4. + +#### cloudScheduling (optional) +This key allows you to influence cloud scheduling. Currently, only a single key `sshTimeout` can be set here. + +##### sshTimeout (optional) +Defines the number of attempts that the master will try to connect to on demand created worker instances via ssh. +Attempts have a pause of `2^(attempts+2)` seconds in between. Default value is 4. + #### autoMount (optional) > **Warning:** If a volume has an obscure filesystem, this might overwrite your data! @@ -149,7 +160,7 @@ This is required if your provider has any post-launch services interfering with seemingly random errors can occur when the service interrupts ansible's execution. Services are listed on [de.NBI Wiki](https://cloud.denbi.de/wiki/) at `Computer Center Specific` (not yet). -#### +#### gateway (optional) In order to save valuable floating ips, BiBiGrid can also make use of a gateway to create the cluster. For more information on how to set up a gateway, how gateways work and why they save floating ips please continue reading [here](https://cloud.denbi.de/wiki/Tutorials/SaveFloatingIPs/). diff --git a/resources/playbook/roles/bibigrid/files/slurm/create_server.py b/resources/playbook/roles/bibigrid/files/slurm/create_server.py index 55872254..19e9b828 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/create_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/create_server.py @@ -138,8 +138,8 @@ def check_ssh_active(private_ip, private_key="/opt/slurm/.ssh/id_ecdsa", usernam establishing_connection = False except paramiko.ssh_exception.NoValidConnectionsError as exc: logging.info("Attempting to connect to %s... This might take a while", private_ip) - if attempts < common_config["cloud_scheduling"]["timeout"]: - time.sleep(2 ** attempts) + if attempts < common_config["cloud_scheduling"]["sshTimeout"]: + time.sleep(2 ** (2+attempts)) attempts += 1 else: logging.warning("Attempt to connect to %s failed.", private_ip) @@ -222,7 +222,7 @@ def _run_playbook(cmdline_args): # read common configuration with open("/opt/playbook/vars/common_configuration.yml", mode="r", encoding="utf-8") as common_configuration_file: common_config = yaml.safe_load(common_configuration_file) -logging.warning(f"Maximum 'is active' attempts: {common_config['cloud_scheduling']['timeout']}") +logging.info(f"Maximum 'is active' attempts: {common_config['cloud_scheduling']['sshTimeout']}") # read clouds.yaml with open("/etc/openstack/clouds.yaml", mode="r", encoding="utf-8") as clouds_file: clouds = yaml.safe_load(clouds_file)["clouds"] From d4d3fc41efc2ee047b8a09710bfc1d5e6134ef33 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Tue, 9 Apr 2024 19:50:46 +0200 Subject: [PATCH 062/145] 474 allow non on demandpermanent workers (#487) * added worker server start without anything else * added host entry for permanent workers * added state unknown for permanent nodes * added on_demand key for groups and instances for ansible templating * fixed wording * temporary solution for custom execute list * added documentation for onDemand --- bibigrid/core/actions/create.py | 75 ++++++++++++++----- bibigrid/core/utility/ansible_commands.py | 2 +- bibigrid/core/utility/ansible_configurator.py | 10 ++- bibigrid/core/utility/handler/ssh_handler.py | 2 +- .../markdown/features/configuration.md | 4 +- .../roles/bibigrid/templates/slurm/slurm.conf | 2 +- 6 files changed, 68 insertions(+), 27 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 966ec402..e57d1199 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -90,8 +90,11 @@ def __init__(self, providers, configurations, config_path, log, debug=False, clu self.wireguard_security_group_name = WIREGUARD_SECURITY_GROUP_NAME.format(cluster_id=self.cluster_id) self.worker_counter = 0 + # permanents holds groups or single nodes that ansible playbook should be run for during startup + self.permanents = ["vpn"] self.vpn_counter = 0 - self.thread_lock = threading.Lock() + self.vpn_master_thread_lock = threading.Lock() + self.worker_thread_lock = threading.Lock() self.use_master_with_public_ip = not configurations[0].get("gateway") and configurations[0].get( "useMasterWithPublicIp", True) self.log.debug("Keyname: %s", self.key_name) @@ -151,8 +154,7 @@ def generate_security_groups(self): for cidr in tmp_configuration['subnet_cidrs']: rules.append( {"direction": "ingress", "ethertype": "IPv4", "protocol": "tcp", "port_range_min": None, - "port_range_max": None, "remote_ip_prefix": cidr, - "remote_group_id": None}) + "port_range_max": None, "remote_ip_prefix": cidr, "remote_group_id": None}) provider.append_rules_to_security_group(default_security_group_id, rules) configuration["security_groups"] = [self.default_security_group_name] # store in configuration # when running a multi-cloud setup create an additional wireguard group @@ -160,7 +162,7 @@ def generate_security_groups(self): _ = provider.create_security_group(name=self.wireguard_security_group_name)["id"] configuration["security_groups"].append(self.wireguard_security_group_name) # store in configuration - def start_vpn_or_master_instance(self, configuration, provider): + def start_vpn_or_master(self, configuration, provider): """ Start master/vpn-worker of a provider @param configuration: dict configuration of said provider. @@ -169,7 +171,7 @@ def start_vpn_or_master_instance(self, configuration, provider): """ identifier, instance_type, volumes = self.prepare_vpn_or_master_args(configuration, provider) external_network = provider.get_external_network(configuration["network"]) - with self.thread_lock: + with self.vpn_master_thread_lock: if identifier == MASTER_IDENTIFIER: # pylint: disable=comparison-with-callable name = identifier(cluster_id=self.cluster_id) else: @@ -185,6 +187,7 @@ def start_vpn_or_master_instance(self, configuration, provider): # create a server and block until it is up and running server = provider.create_server(name=name, flavor=flavor, key_name=self.key_name, image=image, network=network, volumes=volumes, security_groups=configuration["security_groups"], wait=True) + print("MASTER", server) configuration["private_v4"] = server["private_v4"] self.log.debug(f"Created Server {name}: {server['private_v4']}.") # get mac address for given private address @@ -206,6 +209,27 @@ def start_vpn_or_master_instance(self, configuration, provider): configuration["floating_ip"] = server["private_v4"] # pylint: enable=comparison-with-callable configuration["volumes"] = provider.get_mount_info_from_server(server) + def start_worker(self, worker, worker_count, configuration, provider): + name = WORKER_IDENTIFIER(cluster_id=self.cluster_id, additional=worker_count) + self.log.info(f"Starting instance/server {name} on {provider.cloud_specification['identifier']}") + flavor = worker["type"] + network = configuration["network"] + image = image_selection.select_image(provider, worker["image"], self.log, + configuration.get("fallbackOnOtherImage")) + + # create a server and block until it is up and running + server = provider.create_server(name=name, flavor=flavor, key_name=self.key_name, image=image, network=network, + volumes=None, security_groups=configuration["security_groups"], wait=True) + with self.worker_thread_lock: + self.permanents.append(name) + with open(a_rp.HOSTS_FILE, mode="r", encoding="utf-8") as hosts_file: + hosts = yaml.safe_load(hosts_file) + if not hosts or "host_entries" not in hosts: + self.log.info(f"Resetting host entries because {'first run' if hosts else 'broken'}.") + hosts = {"host_entries": {}} + hosts["host_entries"][name] = server["private_v4"] + ansible_configurator.write_yaml(a_rp.HOSTS_FILE, hosts, self.log) + def prepare_vpn_or_master_args(self, configuration, provider): """ Prepares start_instance arguments for master/vpn @@ -314,25 +338,40 @@ def upload_data(self): if self.configurations[0].get("dontUploadCredentials"): commands = ssh_handler.ANSIBLE_START else: + ansible_start = ssh_handler.ANSIBLE_START + ansible_start[-1] = (ansible_start[-1][0].format(",".join(self.permanents)), ansible_start[-1][1]) + self.log.debug(f"Starting playbook with {ansible_start}.") commands = [ssh_handler.get_ac_command(self.providers, AC_NAME.format( cluster_id=self.cluster_id))] + ssh_handler.ANSIBLE_START ssh_handler.execute_ssh(floating_ip=self.master_ip, private_key=KEY_FOLDER + self.key_name, username=self.ssh_user, filepaths=FILEPATHS, commands=commands, log=self.log, gateway=self.configurations[0].get("gateway", {})) - def start_start_instance_threads(self): + def start_start_server_threads(self): """ Starts for each provider a start_instances thread and joins them. @return: """ - start_instance_threads = [] + start_server_threads = [] + worker_count = 0 for configuration, provider in zip(self.configurations, self.providers): - start_instance_thread = return_threading.ReturnThread(target=self.start_vpn_or_master_instance, - args=[configuration, provider]) - start_instance_thread.start() - start_instance_threads.append(start_instance_thread) - for start_instance_thread in start_instance_threads: - start_instance_thread.join() + start_server_thread = return_threading.ReturnThread(target=self.start_vpn_or_master, + args=[configuration, provider]) + start_server_thread.start() + start_server_threads.append(start_server_thread) + for worker in configuration.get("workerInstances", []): + if not worker.get("onDemand", True): + for _ in range(int(worker["count"])): + start_server_thread = return_threading.ReturnThread(target=self.start_worker, + args=[worker, worker_count, configuration, + provider]) + start_server_thread.start() + start_server_threads.append(start_server_thread) + worker_count += 1 + else: + worker_count += worker["count"] + for start_server_thread in start_server_threads: + start_server_thread.join() def extended_network_configuration(self): """ @@ -354,8 +393,7 @@ def extended_network_configuration(self): f"{configuration_b['subnet_cidrs']})") # add provider_b network as allowed network for cidr in configuration_b["subnet_cidrs"]: - allowed_addresses.append( - {'ip_address': cidr, 'mac_address': configuration_a["mac_addr"]}) + allowed_addresses.append({'ip_address': cidr, 'mac_address': configuration_a["mac_addr"]}) # configure security group rules provider_a.append_rules_to_security_group(self.wireguard_security_group_name, [ {"direction": "ingress", "ethertype": "IPv4", "protocol": "udp", "port_range_min": 51820, @@ -374,7 +412,7 @@ def create(self): # pylint: disable=too-many-branches,too-many-statements self.generate_keypair() self.prepare_configurations() self.generate_security_groups() - self.start_start_instance_threads() + self.start_start_server_threads() self.extended_network_configuration() self.initialize_instances() self.upload_data() @@ -443,9 +481,8 @@ def log_cluster_start_info(self): port = int(sympy.sympify(gateway["portFunction"]).subs(dict(octets))) ssh_ip = gateway["ip"] self.log.log(42, f"Cluster {self.cluster_id} with master {self.master_ip} up and running!") - self.log.log(42, - f"SSH: ssh -i '{KEY_FOLDER}{self.key_name}' {self.ssh_user}@{ssh_ip}" - f"{f' -p {port}' if gateway else ''}") + self.log.log(42, f"SSH: ssh -i '{KEY_FOLDER}{self.key_name}' {self.ssh_user}@{ssh_ip}" + f"{f' -p {port}' if gateway else ''}") self.log.log(42, f"Terminate cluster: ./bibigrid.sh -i '{self.config_path}' -t -cid {self.cluster_id}") self.log.log(42, f"Detailed cluster info: ./bibigrid.sh -i '{self.config_path}' -l -cid {self.cluster_id}") if self.configurations[0].get("ide"): diff --git a/bibigrid/core/utility/ansible_commands.py b/bibigrid/core/utility/ansible_commands.py index 0a443e28..460e5346 100644 --- a/bibigrid/core/utility/ansible_commands.py +++ b/bibigrid/core/utility/ansible_commands.py @@ -50,7 +50,7 @@ MV_ANSIBLE_CONFIG = ( "sudo install -D /opt/playbook/ansible.cfg /etc/ansible/ansible.cfg", "Move ansible configuration.") EXECUTE = (f"ansible-playbook {os.path.join(a_rp.PLAYBOOK_PATH_REMOTE, a_rp.SITE_YML)} -i " - f"{os.path.join(a_rp.PLAYBOOK_PATH_REMOTE, a_rp.ANSIBLE_HOSTS)} -l vpn", + f"{os.path.join(a_rp.PLAYBOOK_PATH_REMOTE, a_rp.ANSIBLE_HOSTS)} -l {{}}", "Execute ansible playbook. Be patient.") # ansible setup diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 0e7365a5..9c2dcd95 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -69,7 +69,6 @@ def generate_site_file_yaml(custom_roles): def write_host_and_group_vars(configurations, providers, cluster_id, log): # pylint: disable=too-many-locals """ - ToDo filter what information really is necessary. Determined by further development Filters unnecessary information @param configurations: configurations @param providers: providers @@ -95,7 +94,8 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py worker_dict = {"name": name, "regexp": regexp, "image": worker["image"], "network": configuration["network"], "flavor": flavor_dict, "gateway_ip": configuration["private_v4"], - "cloud_identifier": configuration["cloud_identifier"]} + "cloud_identifier": configuration["cloud_identifier"], + "on_demand": worker.get("onDemand", True)} worker_features = worker.get("features", []) if isinstance(worker_features, str): @@ -118,7 +118,8 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py "floating_ip": configuration["floating_ip"], "private_v4": configuration["private_v4"], "flavor": flavor_dict, "wireguard_ip": wireguard_ip, "cloud_identifier": configuration["cloud_identifier"], - "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False)} + "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False), + "on_demand": False} if configuration.get("wireguard_peer"): vpngtw_dict["wireguard"] = {"ip": wireguard_ip, "peer": configuration.get("wireguard_peer")} pass_through(configuration, vpngtw_dict, "waitForServices", "wait_for_services") @@ -132,7 +133,8 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py "network_cidrs": configuration["subnet_cidrs"], "floating_ip": configuration["floating_ip"], "flavor": flavor_dict, "private_v4": configuration["private_v4"], "cloud_identifier": configuration["cloud_identifier"], "volumes": configuration["volumes"], - "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False)} + "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False), + "on_demand": False} if configuration.get("wireguard_peer"): master_dict["wireguard"] = {"ip": "10.0.0.1", "peer": configuration.get("wireguard_peer")} pass_through(configuration, master_dict, "waitForServices", "wait_for_services") diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index ca6d51d9..84998a7f 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -185,7 +185,7 @@ def execute_ssh_cml_commands(client, commands, log): def ansible_preparation(floating_ip, private_key, username, log, gateway, commands=None, filepaths=None): """ - Installs python and pip. Then installs ansible over pip. + Installs python and pip. Then installs ansible via pip. Copies private key to instance so cluster-nodes are reachable and sets permission as necessary. Copies additional files and executes additional commands if given. The playbook is copied later, because it needs all servers setup and is not time intensive. diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index b327f2ba..3e495e6d 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -179,7 +179,7 @@ Other infrastructures would be [AWS](https://aws.amazon.com/) and so on. `cloud` decides which entry in the `clouds.yaml` is used. When using OpenStack the entry is named `openstack`. You can read more about the `clouds.yaml` [here](cloud_specification_data.md). -#### workerInstances (optional) +#### workerInstances `workerInstances` expects a list of worker groups (instance definitions with `count` key). If `count` is omitted, `count: 1` is assumed. @@ -189,11 +189,13 @@ workerInstance: - type: de.NBI tiny image: Ubuntu 22.04 LTS (2022-10-14) count: 2 + onDemand: True # optional only on master cloud for now. Default True. ``` - `type` sets the instance's hardware configuration. - `image` sets the bootable operating system to be installed on the instance. - `count` sets how many workers of that `type` `image` combination are in this work group +- `onDemand` defines whether nodes in the worker group are scheduled on demand (True) or are started permanently (False). This option only works on the master cloud for now. ##### Find your active `images` diff --git a/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf b/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf index 767aa8ca..0f622ea9 100644 --- a/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf +++ b/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf @@ -77,7 +77,7 @@ SlurmdLogFile=/var/log/slurm/slurmd.log {{ sl.update({node.cloud_identifier: []}) }} {% endif %} {% if node.name not in sl[node.cloud_identifier] %} -NodeName={{ node.name }} SocketsPerBoard={{ node.flavor.vcpus }} CoresPerSocket=1 RealMemory={{ mem - [mem // 2, 16000] | min }} State=CLOUD {{"Features="+node.features|join(",") if node.features is defined}}# {{ node.cloud_identifier }} +NodeName={{ node.name }} SocketsPerBoard={{ node.flavor.vcpus }} CoresPerSocket=1 RealMemory={{ mem - [mem // 2, 16000] | min }} State={{'CLOUD' if node.on_demand else 'UNKNOWN'}} {{"Features="+node.features|join(",") if node.features is defined}}# {{ node.cloud_identifier }} {{ sl[node.cloud_identifier].append(node.name)}} {{ all.nodes.append(node.name)}} {% endif %} From 3984063f73671a6b899a22c8408e7f4db2078829 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 10 Apr 2024 15:36:03 +0200 Subject: [PATCH 063/145] added ansible.cfg replacement --- bibigrid/core/actions/create.py | 3 +++ bibigrid/core/utility/ansible_configurator.py | 10 ++++++---- bibigrid/core/utility/paths/ansible_resources_path.py | 7 +++++-- resources/{playbook => defaults/ansible}/ansible.cfg | 0 4 files changed, 14 insertions(+), 6 deletions(-) rename resources/{playbook => defaults/ansible}/ansible.cfg (100%) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 966ec402..671f2e9b 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -3,6 +3,7 @@ """ import os +import shutil import subprocess import threading import traceback @@ -373,6 +374,8 @@ def create(self): # pylint: disable=too-many-branches,too-many-statements try: self.generate_keypair() self.prepare_configurations() + if not os.path.isfile(a_rp.ANSIBLE_CFG_PATH): + shutil.copy(a_rp.ANSIBLE_CFG_DEFAULT_PATH, a_rp.ANSIBLE_CFG_PATH) self.generate_security_groups() self.start_start_instance_threads() self.extended_network_configuration() diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 0e7365a5..89e69ee2 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -6,10 +6,10 @@ import mergedeep import yaml -from bibigrid.core.actions.version import __version__ from bibigrid.core.actions import create from bibigrid.core.actions import ide +from bibigrid.core.actions.version import __version__ from bibigrid.core.utility import id_generation from bibigrid.core.utility import yaml_dumper from bibigrid.core.utility.handler import configuration_handler @@ -54,7 +54,10 @@ def generate_site_file_yaml(custom_roles): @param custom_roles: ansibleRoles given by the config @return: site_yaml (dict) """ - site_yaml = [{'hosts': 'master', "become": "yes", "vars_files": VARS_FILES, "roles": MASTER_ROLES}, + site_yaml = [{'hosts': 'master', "pre_tasks": [ + {"name": "Print ansible.cfg timeout", "command": "ansible-config dump | grep 'DEFAULT_TIMEOUT'", + "register": "ansible_cfg_output"}, {"debug": {"msg": "{{ ansible_cfg_output.stdout }}"}}], "become": "yes", + "vars_files": VARS_FILES, "roles": MASTER_ROLES}, {'hosts': 'vpngtw', "become": "yes", "vars_files": VARS_FILES, "roles": vpngtw_ROLES}, {"hosts": "workers", "become": "yes", "vars_files": VARS_FILES, "roles": WORKER_ROLES}] # , # {"hosts": "vpngtw", "become": "yes", "vars_files": copy.deepcopy(VARS_FILES), @@ -199,8 +202,7 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us master_configuration.get("zabbixConf", {}), strategy=mergedeep.Strategy.TYPESAFE_REPLACE) - for from_key, to_key in [("ansibleRoles", "ansible_roles"), - ("ansibleGalaxyRoles", "ansible_galaxy_roles")]: + for from_key, to_key in [("ansibleRoles", "ansible_roles"), ("ansibleGalaxyRoles", "ansible_galaxy_roles")]: pass_through(master_configuration, common_configuration_yaml, from_key, to_key) if len(configurations) > 1: diff --git a/bibigrid/core/utility/paths/ansible_resources_path.py b/bibigrid/core/utility/paths/ansible_resources_path.py index 34e81a74..c32161e7 100644 --- a/bibigrid/core/utility/paths/ansible_resources_path.py +++ b/bibigrid/core/utility/paths/ansible_resources_path.py @@ -22,12 +22,12 @@ WORKER_SPECIFICATION_YML: str = VARS_PATH + "worker_specification.yml" ADDITIONAL_ROLES_PATH: str = ROLES_PATH + "additional/" DEFAULT_IP_FILE = VARS_PATH + "{{ ansible_default_ipv4.address }}.yml" -# ANSIBLE_CFG = "ansible.cfg" +ANSIBLE_CFG = "ansible.cfg" # LOCAL -# ANSIBLE_CFG_PATH = os.path.join(bP.RESOURCES_PATH, ANSIBLE_CFG) PLAYBOOK = "playbook/" PLAYBOOK_PATH: str = os.path.join(b_p.RESOURCES_PATH, PLAYBOOK) +ANSIBLE_CFG_PATH = os.path.join(PLAYBOOK_PATH, ANSIBLE_CFG) HOSTS_FILE = os.path.join(PLAYBOOK_PATH, HOSTS_YML) HOSTS_CONFIG_FILE: str = os.path.join(PLAYBOOK_PATH, ANSIBLE_HOSTS) CONFIG_ROOT_PATH: str = os.path.join(PLAYBOOK_PATH, VARS_PATH) @@ -40,6 +40,9 @@ VARS_FOLDER = os.path.join(PLAYBOOK_PATH, VARS_PATH) GROUP_VARS_FOLDER = os.path.join(PLAYBOOK_PATH, GROUP_VARS_PATH) HOST_VARS_FOLDER = os.path.join(PLAYBOOK_PATH, HOST_VARS_PATH) +## DEFAULTS +ANSIBLE_CFG_DEFAULT_PATH = os.path.join(b_p.RESOURCES_PATH, "ansible", ANSIBLE_CFG) + # REMOTE ROOT_PATH_REMOTE = "~" diff --git a/resources/playbook/ansible.cfg b/resources/defaults/ansible/ansible.cfg similarity index 100% rename from resources/playbook/ansible.cfg rename to resources/defaults/ansible/ansible.cfg From a250d6e3e5a1610510530c10dce81c00cf40da7b Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 10 Apr 2024 15:47:31 +0200 Subject: [PATCH 064/145] fixed path. Added ansible.cfg to the gitignore --- .gitignore | 1 + bibigrid/core/utility/paths/ansible_resources_path.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a84bed8b..c2449005 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .run/ # variable resources +resources/playbook/ansible.cfg resources/playbook/site.yml resources/playbook/ansible_hosts resources/playbook/vars/ diff --git a/bibigrid/core/utility/paths/ansible_resources_path.py b/bibigrid/core/utility/paths/ansible_resources_path.py index c32161e7..1bdf7d60 100644 --- a/bibigrid/core/utility/paths/ansible_resources_path.py +++ b/bibigrid/core/utility/paths/ansible_resources_path.py @@ -41,7 +41,7 @@ GROUP_VARS_FOLDER = os.path.join(PLAYBOOK_PATH, GROUP_VARS_PATH) HOST_VARS_FOLDER = os.path.join(PLAYBOOK_PATH, HOST_VARS_PATH) ## DEFAULTS -ANSIBLE_CFG_DEFAULT_PATH = os.path.join(b_p.RESOURCES_PATH, "ansible", ANSIBLE_CFG) +ANSIBLE_CFG_DEFAULT_PATH = os.path.join(b_p.RESOURCES_PATH, "defaults", "ansible", ANSIBLE_CFG) # REMOTE From ba0d0335702202214fcd3f55032f081a7e86de1e Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 10 Apr 2024 17:19:08 +0200 Subject: [PATCH 065/145] updated default creation and gitignore. Fixed non-vital bug that didn't reset hosts for new cluster start. --- .gitignore | 1 + bibigrid/core/actions/create.py | 20 +++++++++++++------ .../utility/paths/ansible_resources_path.py | 9 +++++++-- .../templates => defaults}/slurm/slurm.conf | 0 4 files changed, 22 insertions(+), 8 deletions(-) rename resources/{playbook/roles/bibigrid/templates => defaults}/slurm/slurm.conf (100%) diff --git a/.gitignore b/.gitignore index c2449005..8375a9a6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # variable resources resources/playbook/ansible.cfg +resources/playbook/roles/bibigrid/templates/slurm/slurm.conf resources/playbook/site.yml resources/playbook/ansible_hosts resources/playbook/vars/ diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 1e6357e4..c1fbd0f8 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -59,6 +59,13 @@ def get_identifier(identifier, cluster_id, additional=""): WIREGUARD_SECURITY_GROUP_NAME = "wireguard" + SEPARATOR + "{cluster_id}" +def create_defaults(): + if not os.path.isfile(a_rp.ANSIBLE_CFG_PATH): + shutil.copy(a_rp.ANSIBLE_CFG_DEFAULT_PATH, a_rp.ANSIBLE_CFG_PATH) + if not os.path.isfile(a_rp.SLURM_CONF_TEMPLATE_PATH): + shutil.copy(a_rp.SLURM_CONF_TEMPLATE_DEFAULT_PATH, a_rp.SLURM_CONF_TEMPLATE_PATH) + + class Create: # pylint: disable=too-many-instance-attributes,too-many-arguments """ The class Create holds necessary methods to execute the Create-Action @@ -211,9 +218,9 @@ def start_vpn_or_master(self, configuration, provider): configuration["floating_ip"] = server["private_v4"] # pylint: enable=comparison-with-callable configuration["volumes"] = provider.get_mount_info_from_server(server) - def start_worker(self, worker, worker_count, configuration, provider): + def start_workers(self, worker, worker_count, configuration, provider): name = WORKER_IDENTIFIER(cluster_id=self.cluster_id, additional=worker_count) - self.log.info(f"Starting instance/server {name} on {provider.cloud_specification['identifier']}") + self.log.info(f"Starting worker {name} on {provider.cloud_specification['identifier']}.") flavor = worker["type"] network = configuration["network"] image = image_selection.select_image(provider, worker["image"], self.log, @@ -222,12 +229,13 @@ def start_worker(self, worker, worker_count, configuration, provider): # create a server and block until it is up and running server = provider.create_server(name=name, flavor=flavor, key_name=self.key_name, image=image, network=network, volumes=None, security_groups=configuration["security_groups"], wait=True) + self.log.info(f"Worker {name} started on {provider.cloud_specification['identifier']}.") with self.worker_thread_lock: self.permanents.append(name) with open(a_rp.HOSTS_FILE, mode="r", encoding="utf-8") as hosts_file: hosts = yaml.safe_load(hosts_file) if not hosts or "host_entries" not in hosts: - self.log.info(f"Resetting host entries because {'first run' if hosts else 'broken'}.") + self.log.warning("Hosts file is broken.") hosts = {"host_entries": {}} hosts["host_entries"][name] = server["private_v4"] ansible_configurator.write_yaml(a_rp.HOSTS_FILE, hosts, self.log) @@ -357,6 +365,7 @@ def start_start_server_threads(self): """ start_server_threads = [] worker_count = 0 + ansible_configurator.write_yaml(a_rp.HOSTS_FILE, {"host_entries": {}}, self.log) for configuration, provider in zip(self.configurations, self.providers): start_server_thread = return_threading.ReturnThread(target=self.start_vpn_or_master, args=[configuration, provider]) @@ -365,7 +374,7 @@ def start_start_server_threads(self): for worker in configuration.get("workerInstances", []): if not worker.get("onDemand", True): for _ in range(int(worker["count"])): - start_server_thread = return_threading.ReturnThread(target=self.start_worker, + start_server_thread = return_threading.ReturnThread(target=self.start_workers, args=[worker, worker_count, configuration, provider]) start_server_thread.start() @@ -414,8 +423,7 @@ def create(self): # pylint: disable=too-many-branches,too-many-statements try: self.generate_keypair() self.prepare_configurations() - if not os.path.isfile(a_rp.ANSIBLE_CFG_PATH): - shutil.copy(a_rp.ANSIBLE_CFG_DEFAULT_PATH, a_rp.ANSIBLE_CFG_PATH) + create_defaults() self.generate_security_groups() self.start_start_server_threads() self.extended_network_configuration() diff --git a/bibigrid/core/utility/paths/ansible_resources_path.py b/bibigrid/core/utility/paths/ansible_resources_path.py index 1bdf7d60..5c0e01b3 100644 --- a/bibigrid/core/utility/paths/ansible_resources_path.py +++ b/bibigrid/core/utility/paths/ansible_resources_path.py @@ -23,6 +23,7 @@ ADDITIONAL_ROLES_PATH: str = ROLES_PATH + "additional/" DEFAULT_IP_FILE = VARS_PATH + "{{ ansible_default_ipv4.address }}.yml" ANSIBLE_CFG = "ansible.cfg" +SLURM_CONF = "slurm.conf" # LOCAL PLAYBOOK = "playbook/" @@ -40,8 +41,12 @@ VARS_FOLDER = os.path.join(PLAYBOOK_PATH, VARS_PATH) GROUP_VARS_FOLDER = os.path.join(PLAYBOOK_PATH, GROUP_VARS_PATH) HOST_VARS_FOLDER = os.path.join(PLAYBOOK_PATH, HOST_VARS_PATH) -## DEFAULTS -ANSIBLE_CFG_DEFAULT_PATH = os.path.join(b_p.RESOURCES_PATH, "defaults", "ansible", ANSIBLE_CFG) +SLURM_CONF_TEMPLATE_PATH = os.path.join(PLAYBOOK_PATH, "roles", "bibigrid", "templates", "slurm", SLURM_CONF) + +# DEFAULTS +DEFAULTS = os.path.join(b_p.RESOURCES_PATH, "defaults") +ANSIBLE_CFG_DEFAULT_PATH = os.path.join(DEFAULTS, "ansible", ANSIBLE_CFG) +SLURM_CONF_TEMPLATE_DEFAULT_PATH = os.path.join(DEFAULTS, "slurm", SLURM_CONF) # REMOTE diff --git a/resources/playbook/roles/bibigrid/templates/slurm/slurm.conf b/resources/defaults/slurm/slurm.conf similarity index 100% rename from resources/playbook/roles/bibigrid/templates/slurm/slurm.conf rename to resources/defaults/slurm/slurm.conf From 7185ca156a50ab38c43cce4951c4e9578fca72a4 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Tue, 16 Apr 2024 08:47:17 +0200 Subject: [PATCH 066/145] Code cleanup (#490) * fixed :param and :return to @param and @return * many spelling mistakes fixed * added bibigrid_version to common configuration * attempted zabbix linting fix. Needs testing. * fixed double import --- resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yml b/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yml index e9986be3..df2b5def 100644 --- a/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yml @@ -42,8 +42,8 @@ login_unix_socket: /run/mysqld/mysqld.sock - name: Check if zabbix schema exists + failed_when: false shell: "echo describe users | mysql --user={{ zabbix_conf.db_user }} --password={{ zabbix_conf.db_password }} zabbix" - ignore_errors: true changed_when: false register: zabbix_schema_exists tags: From ca6cab5ce129eb93bc60e775b1f6425775eb1ffc Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Fri, 19 Apr 2024 07:33:30 +0200 Subject: [PATCH 067/145] Slurm upgrade fixes (#473) * removed slurm errors * added bibilog to show output log of most recent worker start. Tried fixing the slurm23.11 bug. * fixed a few vpnwkr -> vpngtw remnants. Excluded vpngtw from slurm setup * improved comments regarding changes and versions * removed cgroupautomount as it is defunct * Moved explicit slurm start to avoid errors caused by resume and suspend programs not being copied to their final location yet * added word for clarification * Fixed non-fatal bug that lead to non 0 exits on runs without any error. * changed slurm apt package to slurm-bibigrid * set version to 23.11.* * added a few more checks to make sure everything is set up before installing packages * Added configuration pinning * changed ignore_error to failed_when false * fixed or ignored lint fatals --- bibigrid/core/actions/terminate.py | 7 ++- bibigrid/core/provider.py | 8 +++ bibigrid/openstack/openstack_provider.py | 8 +++ .../markdown/bibigrid_feature_list.md | 1 + .../markdown/features/cluster_commands.md | 54 ++++++++++++++++++ ...Compute 2023 -- Multi-Cloud - BiBiGrid.pdf | Bin 2026743 -> 0 bytes resources/bin/{binfo => bibiinfo} | 0 resources/bin/bibilog | 16 ++++++ resources/defaults/slurm/slurm.conf | 13 ++--- .../roles/bibigrid/files/slurm/cgroup.conf | 2 +- .../roles/bibigrid/files/slurm/create.sh | 2 +- .../bibigrid/files/slurm/create_server.py | 7 --- .../roles/bibigrid/files/slurm/fail.sh | 2 +- .../playbook/roles/bibigrid/handlers/main.yml | 1 + .../playbook/roles/bibigrid/tasks/001-apt.yml | 9 +++ .../roles/bibigrid/tasks/020-disk-server.yml | 7 ++- .../roles/bibigrid/tasks/042-slurm-server.yml | 50 ++++++++-------- .../roles/bibigrid/tasks/042-slurm.yml | 26 +++++---- .../playbook/roles/bibigrid/tasks/main.yml | 1 + 19 files changed, 155 insertions(+), 59 deletions(-) create mode 100644 documentation/markdown/features/cluster_commands.md delete mode 100644 documentation/pdfs/ELIXIR Compute 2023 -- Multi-Cloud - BiBiGrid.pdf rename resources/bin/{binfo => bibiinfo} (100%) create mode 100644 resources/bin/bibilog diff --git a/bibigrid/core/actions/terminate.py b/bibigrid/core/actions/terminate.py index 44c480ae..1452cb99 100644 --- a/bibigrid/core/actions/terminate.py +++ b/bibigrid/core/actions/terminate.py @@ -148,10 +148,12 @@ def delete_security_groups(provider, cluster_id, security_groups, log, timeout=5 tmp_success = False while not tmp_success: try: + # TODO: Check if security group exists at all + not_found = not provider.get_security_group(security_group_name) tmp_success = provider.delete_security_group(security_group_name) except ConflictException: tmp_success = False - if tmp_success: + if tmp_success or not_found: break if attempts < timeout: attempts += 1 @@ -162,7 +164,8 @@ def delete_security_groups(provider, cluster_id, security_groups, log, timeout=5 log.error(f"Attempt to delete security group {security_group_name} on " f"{provider.cloud_specification['identifier']} failed.") break - log.info(f"Delete security_group {security_group_name} -> {tmp_success}") + log.info(f"Delete security_group {security_group_name} -> {tmp_success or not_found} on " + f"{provider.cloud_specification['identifier']}.") success = success and tmp_success return success diff --git a/bibigrid/core/provider.py b/bibigrid/core/provider.py index 61cc5012..9e06dbd0 100644 --- a/bibigrid/core/provider.py +++ b/bibigrid/core/provider.py @@ -265,6 +265,14 @@ def append_rules_to_security_group(self, name_or_id, rules): @return: """ + @abstractmethod + def get_security_group(self, name_or_id): + """ + Returns security group if found else None. + @param name_or_id: + @return: + """ + def get_mount_info_from_server(self, server): volumes = [] for server_volume in server["volumes"]: diff --git a/bibigrid/openstack/openstack_provider.py b/bibigrid/openstack/openstack_provider.py index 5efabf02..75de1c2c 100644 --- a/bibigrid/openstack/openstack_provider.py +++ b/bibigrid/openstack/openstack_provider.py @@ -320,3 +320,11 @@ def append_rules_to_security_group(self, name_or_id, rules): port_range_max=rule["port_range_max"], remote_ip_prefix=rule["remote_ip_prefix"], remote_group_id=rule["remote_group_id"]) + + def get_security_group(self, name_or_id): + """ + Returns security group if found else None. + @param name_or_id: + @return: + """ + return self.conn.get_security_group(name_or_id) diff --git a/documentation/markdown/bibigrid_feature_list.md b/documentation/markdown/bibigrid_feature_list.md index 5e4b9a82..0ddecab2 100644 --- a/documentation/markdown/bibigrid_feature_list.md +++ b/documentation/markdown/bibigrid_feature_list.md @@ -13,5 +13,6 @@ | [Configuration](features/configuration.md) | Contains all data regarding cluster setup for all providers. | | [Command Line Interface](features/CLI.md) | What command line arguments can be passed into BiBiGrid. | | [Multi Cloud](features/multi_cloud.md) | Explanation how BiBiGrid's multi-cloud approach works | +| [BiBiGrid Cluster Commands](features/cluster_commands.md) | Short useful commands to get information on the cluster | ![](../images/actions.jpg) \ No newline at end of file diff --git a/documentation/markdown/features/cluster_commands.md b/documentation/markdown/features/cluster_commands.md new file mode 100644 index 00000000..be16a42c --- /dev/null +++ b/documentation/markdown/features/cluster_commands.md @@ -0,0 +1,54 @@ +# BiBiGrid Cluster Commands + +## [bibiinfo](../../../resources/bin/bibiinfo) +Similar to `sinfo` but shows detailed information regarding node features. + +## [bibilog](../../../resources/bin/bibilog) +`bibilog` executes `tail -f` on the most recent worker creation out log. +Thereby, it helps you with understanding any worker startup issues. + +## [bibiplay](../../../resources/bin/bibiplay) +`bibiplay` is mainly a shortcut for `ansible-playbook /opt/playbook/site.yml -i /opt/playbook/ansible_hosts` +which allows you to execute the ansible playbook more easily. + +### Examples +You have changed something in the common configuration and want to propagate this change to the master. +```sh +bibiplay -l master +# executes the playbook only for the master +``` + +You have changed something in the slurm configuration and want to propagate this change to the master. +```sh +bibiplay -l master -t slurm +``` + +## [bibiname](../../../resources/playbook/roles/bibigrid/templates/bin/bibiname.j2)[m|v|default: w] [number] + +This command creates node names for the user without them needing to copy the cluster-id. +Takes two arguments. The first defines whether a master, vpngtw or worker is meant. Worker is the default. +The second parameter - if vpngtw or worker is selected - defines which vpngtw or worker is meant. + +### Examples +Assume the cluster-id `20ozebsutekrjj4`. + +```sh +bibiname m +# bibigrid-master-20ozebsutekrjj4 +``` + +```sh +bibiname v 0 +# bibigrid-vpngtw-20ozebsutekrjj4-0 +``` + +```sh +bibiname 0 # or bibiname w 0 +# bibigrid-worker-20ozebsutekrjj4-0 +``` + +A more advanced use would be to use the generated name to login into a worker: +```sh +ssh $(bibiname 0) # or bibiname w 0 +# ssh bibigrid-worker-20ozebsutekrjj4-0 +``` \ No newline at end of file diff --git a/documentation/pdfs/ELIXIR Compute 2023 -- Multi-Cloud - BiBiGrid.pdf b/documentation/pdfs/ELIXIR Compute 2023 -- Multi-Cloud - BiBiGrid.pdf deleted file mode 100644 index 672aa426422173a45dc8c725dcdf8ff01312031f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2026743 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%0DOYRzu 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

-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

~E5UKiSp1#f(C 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

HqzQ!sZ`UhHC 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

x1Xl>~1ilQ7a|H>4uN(4jHa7!`bP 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%0AA(cUk?N-pT= zKfmU}WRANfve*rJ3;Lj?0gUn>2}1j_`-IUR9-|yZi(KQQ-)mgdvRlwP-;mVF=KVb zN63}<3p~_yy8!g_58vOtgMfHpsQ2}m)rJPhyCc09XRE2<6;mI|Nt9VuN=(~&r;_Xo z#i%7ccE9uhGaLS%nGJUEz2=ow#vG0Vr?4oHav7Zqqi2#SP#*i1ioF75boDZBVxskf1qcP0AE8j-bOviw9L}M~BKvs=2Zw zKUUR^=hfMS)o!+-KaD{U%kQ3T&{rXl{-tanw^ApjoBr(>XR@i}O^dQ5w?|zixyq!y z{Rd5c=BXu*x9bT4iE3Q{-S)rfUV)<{%<;PUsnx6rdID?bW7_ zjb{twwxy8zz?wAqmA*ZD-1r(`f)aoT7C-XDQ=Aag`PI5>uCEV@=)ud6z;b!-ah4+w74(Dp}1ApguH9F`6hh;m6}ohBpDwlTrXyLLOa9wMY}A19aOL zvhqX7*-{?kzx{n36?HMBgY{Ne1R@y#qq>A-e16kbcV*84{D?A`JAdU&;grZ^)>vH^ zsYqnc#Nb+dwhpvvd=+#W?O6P_eC*r+GGcK)zt=6KgcZzd_eA|`peJz6xCV9ae-RcA z(cI%)mHQ``+p9H?{{N=`si^hpPZDc8<7KNIPd|H4@ZwBR`Y;NO_RvSs13-B&*& zPol2*gCwi8S>X~&heDoJEyec`!0rQxtt9<}l887+BblWGXRJe*!Y0JfYU2+K3LG4* zJHwmN_+T~8R*BR6WKN39-PK2wz(bsuNUi$`L(S}!iShnyAQO#%X-Ca+(|$pP8Q+mc zyZ;`DBakzZ{comdh-*~+J!`C0E%P?0^MV%EGI`7htZ8iMZ=)?w)N5ow)1L5}3^iSO zy*}EQfJ>)_AC9$6VPV6pCVDykr3RXJmM1RD4a6i&r-m4#F%Ws?$F@Qt5Wv#&1wbNS zB|!vsOv%Fk3CjXChi$3uNgfq?aT^O1e3Bo#)AiOWNdM4{&9~TmB~Hg30@+Y(ug)zC zj?R|~{mu1)M{UsH#0HobQJi(QL1A-B*!AswK93bo)-3$%>0RzKB0Kd?lTmvR<5 ze`@j?H;*KiUo`+CaH)|0Zw-&|gP2ZwpYD-l2fZ-IHNwpbXq(#_wkAVdGueNNc5$*ASTA}xVYzX4c# z)<4%CGzf+GG@Oq;d#}R_3oJqJ4(OV*%D^)M^VbKCVpGB1HK2bC`ky_Z7KUAk9bSzt z%Z#Dg9AVlVb$w0opM`3HVjYr(Cc{)&KQETO5cs!XjGVRyO0Plm#j;S8%kv|tsh;hW z(NaCF?fWaXi@b~y^_rv3;Q#bublRTpRt;m7&kuY7e5=S8HV%JLyAyu6)*oF>YEHmi-MGY$4Y$2gSvI8h}iP!kr8;yT02js?hghHS4e1 z2guv7Qdz%G9XLC^BAo@|`*^VcB|L>c{y~7tHeT=grPHtlt<@qkn5dE;`1}$tAGa`S z$?EdvnqR4=i0glRoz;`s8u;~B*3OMQawQuVi^;n{R$1r!yPT7$Pa#7LO$oJOB$31u z19ShC0wK(neyYsv^8o(~zDi*ut4amC7{4?qaOEtT!3T@jamvr6MaMJ!KN7%bh5AH! z);ZVRsys8cT6Nh*-J~RBQnHIlNefAbE&yRQ46~j5_@(iz`rJSHkG425N0K3WPc2dh zFi(AXV@xCv%Y5JiL8ZfO(+B&Gt}Y#y)|P4zn|z~xxjj5T$gzq~gH@R%6xCO!p0Bb7 zXUyt-?7JI`;3XS()T(c;q0T&>$&|3MAB6k@Y=7d9@f+1g3$xmVc?vj03W-#AtXx*| z$8V|O24gi(xA1{IAl4|5%2WRTScii33IABT2^D%q4jDF3WXzXJr^pbU(ESh{BW9V6 zMT~(2+LnYscUr^$3dH#S@<+d?1xf@wUX9)#uVPh%$KJM&;V>|QcWj!+60iWvj)Oqe zkp5q=p&Wij_cU6Otnjcn;{mzWvk`0!PTYbQhE}q22V;&-4&VXut!F3HsS-m)F zfAspSZznj%k~8J>hHF&j2gV`bgp6C-8&f(yQU8(t-A5f}*x$X+w57n^TS#m8^vOs* zr}jubnVuO}E_w147{PEsR)A~1&8PV<6NTeYn4fQ*SSd%xhQ&@hclNzLuR=i>?_Gj3 zu^Oj5804x15-l}l?6Vn1`Oiu@77{{}WjDG&6uqQ3<7Hg&eM(Hy=3`ZK8NupDVnXbU zMPRG(nDV2fe@CFQaMRVPrBLAFQ5T1lYou41u^*k)Y8Wn2Y^(~9P@<`{*s7(e+{)7a zmx)7gwSaDWDDTclACs++4cEtI6s0|tVg#qhs;CheCFz(nCuDP1Zgot*RwDZ=me_g^ zd?X0Zw7>%y2nvQRC&XFLPJt-i;gQcvd|&;p!OW75p{X?@U)PuPwA1bWnEGFFU{TGo z7Kz??Cmye+G!VWX^z(yf`C%2MPR{$^`t?s*@m4<_EnNH%7!3*om^Hy2$@oz#D?q2D zk5V1ss{KxY^YQ8oF zTD$IDI;}MoMQPuXFB2s070&zg$-)e$HSskvutIP>4iGFaDRZ%Z^?l@KK#r>SwWsB! zM&aQ>BwqQ7qHwo{BHLygS!YvkICkY0Kdtnb=G(tw-hzGUjWL%aTlt6U^Py4ecT;YE z=Xdl#sef&nHFacsqOJw7rL3P=)!zN=1T(nv)$_XS2LC`)pqzw@ifka(s)8^|Wk9UT z1^=>~4Coi=3O1!FA9x(=rQ2O0>|C9!6ijV)8a~oEDYDvk(LK&Z68wehJ>*p+_wIay zZ)QwsKkkEz{PO@Yu+?Sxy2STGawY(mBkVF*xK&t{bW&Tj6@~BUe{Lf*TZ4I*riss7 z8r`p^O7Ce~?jFo#Jn~32WOQ^lv{mLO@5HBqlg0y~y-hhUtm|fCd1@YpknZq)|BR~7 z^-k+q*2GbLYM3)FQBR_CY<7ZI{gXce{6UDp)rdv^+HH4K^PU_&^?l=>tVvgcPQ>-4 zw2gA9l9H$a^K6!pG<)s48blD&`8(UWno7sQ9f9kp4{U<3>ucU5cF?@ zB}#rxWb0rgO;kq-=g>SUKzN^jji?Y7F9g>0{#7Vip#FlrY&*-xiZ1Ggt7DOq&y<7TAL#z%7KYF5e>r+hRJY(?iFEKSXHn!TD zU%vBc*y*KaNnl{0P^{qEgbB64%3^z*VyTBMmcj+4%lsg1H@(XPflo-3ajShGq~S(1 zMTcxu=ZsZo35_B|E-ebb&I_IGm+g?%dh2VYEn2NlyJzaZbb4m&rJhkdP3fCvNQv(Xbec&ZY?Fmfl*S4q4Yq z=U&%Bs*g5_TeMPW>*~(h<}w8-H4zEomM>@d@#vRpZ)f!TyYHvYlr$BEW55AKOW=U0 zvYa?iRdxwrkNdXwq2V`N`Z_+bVcia4${;;xxAevl@l;HPF!c)?MuXm4YP~HjBi?OB zhi8`*7oVZgTwaqXu#YLr9~d!t-g2)wYBIGzRaT_Nlx^}CpKBT(9SVfIR_~mr0II8C;?u2CH#Jc~&vn+!G)OWB+_N#rt+7 z6UAaNfgCz@ScJbJ!#mrhJKD;0Er-QJm~=&s=n%94@+Z9`BRxIBTM{=ac*<>}KSM9E zV2_8Q{VAj<(6mjL;HEi+Ddmd5Mp?D>@drW?UHkz;_FGsO_AI#-7SqCoy%nx1qJCDj z1OqQUj8bw47Z-4Fi0*EYGOfIQktCK9t8ze_dx0dU;I6)yf#~A~yj<3ab@*aFwfsRU zV|3wN0;661L*MPuaO|q+a`dsXs;W|lG!MM`^5Ww3^mHgR;##o@YRPQT)r^}R=G|?5 z4zl~a%Qve;yh!d&7*Uy_lubQfiNtBpaNn&Rd#RjgOl`;Shg!+g^%)mS>a>`+^!g`e zU&D#Azya~3ou57t8qIyheQqiDQ(g)^V#n~ymYNQcMlKbYCCB6YxTDpz#dir7n_g_7 zC3Y+_FgjGd(Wwg2YF$jvZlwhdl)vtvPNKm+#(iK60&r903|TdBo<4bLTCkO_z0EAp z7=7jR^c(l07QSi13Vh5|JN|YyYGGcnXnDL^8(>cNI zXr0_d?`IO_!hyh7Nw;bNG)erVuBV%2J=x$oUbO4lQ=G45HXt!msda3Hk>)jq*TUMe zGIrHNE8v*>p|iL7-h4g8Lqu5k;ln9RBisHr(V3M3o!sHv&8x9rbw@+=NpOP$AAc?j zwL52wGgqdKS+-M13u#J1j7v?-*(p z0D1TPFtZ|T;~8(R{cc|<8o!rv;j2-dY-rv1=p?^w5Uw0fx^77)8K7HasY6`3vah%M zcGBciBgZr==SCWRUgwr%#i|78Jg0)5fCCl-bdH{l$TwvCG|RawQ?}jQ>B;kD_ll3l z?X9C8-``dvb=FF@Iv~Us#-BT?yB!4rgXHA$hSIJs&t0P&z>dzhys2nk9XUWCklVL! z=h=plgHk3_K7^^fNUZ{KaHP!ny`Qc>zR1ebHK`XsPra^lJU-U{WXXNH5#mX4XL%@Q z+eh?tFnxRdRXu+nflZ~XMi1Wxm!6}tDlkJH$Ncrl4?PEUMW5F zYQvCmQG2A~^!vJN1?h2-Q)d?J$B=d1XD7V462EovIiLNtOOMIH%Q90nbHTIMW7Sz% zi45(lvpy7D&v;pgw5S;1$eC4Hi|64!=X-Yyis>n=x$|YzbFrTTxv6^PZ1zB7`%}!o z0J)B~7m0-~TB#SO5XG&m8QbacIlDgS=bX4#jNqb&L5lV9&ar!Ukr^mOs%ca1*y3sT zz2l{-0df=mprCSUF*v~RSC{DGrFp@DSU{56{P3o_5b(XgKtkK;FU`)0lixR@eCRNX zdt3Z!bQ2VZtOfHl%IS~A&$h`L?@HlDO1fmkNk@b55#d*rLbDv-b4PXCGe(wsU!+ji z9-n{1k+Ah?oE%nog^EQjvPth;)ExEP`HG%PE>B zGQZw&(R$5CuWF?@U3EE&b9;&|Yb+aV3=I z)e|oqroZm1sBB+h`aV4o02$lqd|}HnD5{LHWP-x~EeWz0d4A!2dO0fL316N99Za-z z9w){p>d#!Rx?+}+XnH#ZJ{izXc{*ysZ(sOf_4-q#M?3v|W%rk$YwlWygQl3?woM@o zn$`?}S7rybSBppYSg%{$goJL7oJlC&nM@9cuZ}(4`9^39x2G)My{o%ivO)bW@)i2I zgWs&bbshoTokX^4>rjl3WjV9Le0?n~EoIZzIakZ+igE<$wV~Oj1g_3}g=?>E0Uf-2 zc_#+E1H)eSu(ij-hFgn$N&!=d|=vnJM|2I z)-hGyQe2=m7YNC|Bs>p7%B43`_e)~AXHl9vTqD;CF zUs{XyhG5~&O6Ukssz>E(!mw@AsEJ5Cy@>qe16(12B#3H}6!#DR-bU}!VHwxQ^MhwJ zk`8-K*5$!Rv=8@_?XG;Cbaj>n(;0VUl|pYz6esg}ZnT+Z_!c^SV>lZ$-^{V%U7O%A zCn=3%T}^Q4Fh8UYMEAl_S}5ADpzklWxRhmYXu%x~KE5sLa5`E2W+&rvft%QzoZ^~d zjgY6V(Q&oM-7-N->%FQZ3Eg{lpZy#^sTXSv4x|s=%=MykDCJj$Qh;5=Ss*QU%JvJ> z2)aihWa^2W{KApAtF@%ob}l z@<${eF6O?axA~kZvX)mn^QBj%fy0ZyrHI<0*JamzMjJ-pRrdPPAZY~dlitzM(cWI% z%Sn=QPGYA*Le*D7=SH??Wvz>l{zx!FU$qS4dlRN>b9Jdi0Oi zz&(;Uzx^K5Mr@DGz$k8&3g44t^9;MDt81}#zt1npbH@nx(5hYb*ELoN@asj{VW#&a za94%{>aZL-IUyK<&!j>*CLimeW={5g6I~|t8317P%!xBVd+jqbMRcz zv5mA3M3ISsRWnbyFk#gLTK*t#LcNEFRrV%8GSS8{0?of$U}qhrv#4%({B}l*kaa6! zWAD6Bbl5}dN2JPhMz(~{rhmi8z^jRg2h$~EwTF`}q!cGYrniL_&8xTTPd%w)@=e5G z^TS&6Tbc}f)zS2Jtd4N-9hv49{OpOnxVvcD@~%-nWyWB1mUL$vqDO&YfNAjkWg6be zw>aCNfo!Eu-phtV$0kIcRMO{oCR;{Qg z7R;>m&gRlmBZd=jc~9y@*0A=LQy9hHIQroQ9}&+wO3!FLjbhXSab@b&K7V$9Zf5lBcsFgE72BYv>qIgew|Va#!VSx8%+h_D5u?I=i|-7OH? z??5af+?801(Lm&BPB5<+6pXvBcmE6OaIxxW_bEun)9-2~Jvh#c6qq5m<#>u26d-%h+|u_z&%DmHEM@%FUiz8Ms>yS! zqX~AIu6XB`F?GOads6(28ISTlGPVk4K!&%2(d&{9>;TP8wEo@Qh&IM zlM8Rv>YMxliN*cX67cVJ+^luGie z|C*bapqcjfs3Ba_L3o&!v$hep7QjC6{z6tMc*l{^(eeU-VTBge^Ot8_^FLe#KDk`) zBo3;^zjLO3DkYzH6G-#?(3NL3&Ch)cBti;!s4#DNU&`qrXczpQR~$d`b7oDE$vm>;&4Fm1*5 zn&V4acBnCDUpLbt!Z#Q0Y(y&-~ zx8RrGD16|W?L6Q8V~Y&}zyYnRxI}AOPeYynyOylK(p=+F;hyKq2k`MR1SF3cKHN6h zahvONv-D%)1Z6Mbez(oSRjY2qDr z`oiWPD6mHE_r^u?M@W0>t27Fbbd68R!(NK6K;5DwZ9drJw?^M|`apsQZ;0)Mpf_y$ zb4sht+%%`;@~7G~Qkd)f^=tS#=gH-IC2ctuKUH4g%S4+Aic8X9@5O)Ppv6SwHbljKX*PXh{N6GlW;!`)cw<)uBUu64H1Fe{#=fe~*ECF_ zJZ@nhjEwl++v6gXn3$7wlMF4J`EWC`Z@c1&)Y=m6)m>WA5SZ+ecb|T}#|wKc#AG2P zQtL(`U9m7&+TbP+BMGEzqZU2ugTmiGV@!g4XE597f$I(gK%D(psj4;4(pVGU z-Y^s0)H|Fx4INAH&wRxhl$`(keI}FsZ>R#C>Sr9a(jtRje)$Q=?lV8BIWb7Gr3n%QVJi6Q4Y1+vOyosVlj>HCQ~5 z|A{aYNRHi*&>4|WOU=cw$~OiMTIk@q!W7r9@>)F3LMvcs4a z7U+mcjKe&E5RiW-%Y6bVRrUe(1Y!)!a8iEQOoBjC!*C{5-yDWOUxv^>{`8hQ=^Oo_ z)$`KeawiNW(51#kNIjJbex#G3{G|3*EG}~t*u+W9^xd~bp-GVW6SIyMu&=64T#xn> z*_-4r_l{+2(dEd2#d1Q{NAt6lX6lpA2zof_+6M-vn?PxpQHf5jSJVeoii6(OZf>c( z1lqCL-TS!9DMKk8Wrwmjz-F$1A+$G;vn<3vu-S-6t+c|-$n4d?i8jF&v_Kr7<_8Cb(-Ey-3=AH!O$T%4v#iwEH`{ojppZSQFOXkx4D|~% zp(-hkYT*~?(g`wXY{D;I;?Ti-bcR}e)Asb_B;bW@e<$|Ads5w9!tb?0 zUxG};E8fuN;S;o}HX%&E zYhGLL@8N(heTlfK8fAnzB=PEaZ41q5zpH^4R0SnrT%$1@s4CWX9OKJFhK^%51W zZsP2ADP8MOjB3qAE!5W3B<8x+-VD}}%wA54@Uc+FoE85zuaS)#bTEW!wMov@H(;Z; z3J%yhsVYQX-k?k7(!gn~)6qBQ5V;=cqSW2W9bRqIzGl4d81*(Xa-VNb9PdFn^h_S=TOca&wDJ}CHC)M+fw3AwwS zhzIMtmyn``xjD|4^TCK8YDZS26OgH(0ScsC2V&FRt1R$~@xC|RYfWn7Rar~GUC#DU z($fdd*#ZSYMVeXz?$wC1QVXtR4;$s0X7mHfTRmVI|P(w6s(kZ|R!;1E*`by>w} zpIlkSgn1lqV1~)vc}7P1!~)o!GJ>aLRgA6jb93T8|2(CQdQ2V~pEwBpxJ&;mj$&a3 z%Rg}32df{W&j0>QU@F4T5>vGY`avi<^~n$QoMN8U2?A~u3{E2lmnXSGrG#KRR@+EUym;V@Uo~l9VdO=(* z5bdA9v!&PgrtN)H@s*+AV37!d06V|{<{5+xsMMICf-;c(FefZU8oZCSEK;eZcgGP=%lA?u-`;S^`^PZT92T@_J1kWF59(XJMo z>+njS(z`W}WNm`s4ErWX!YA55X6;RK;r#Rw#of7jRpG@9luvlJY0a{*&k|37b2e>I zx2jX9e;^no0{_m$CHB1cbo*dp4^&4L-y$`-T$BUPx4iVhXp7{sc<4>&|lgQK6iOJZ`idlXbD*p27;q>iL!&G$JObMpRY>6Ts zh6kyBxOPJ>PTYExV>nLM)&CDuZy6O=(*}zMx8UyX3{G$l5Foe(1`Y1+?jZzsw;;ho zfSJLa;O_1a+#T*D@A=NTzh|vIy?S?7KV45%ZPiZ{84plC&rs?UrxUc)ZFn^vd*1!* zz*jVsvoT!V<)apT0A>4DLYA`NYFbjHlt1{t^Wh$jFccQ(c^LPJ{-n~;-{{$yH@+@D zEBD}}1Mh+_dGbdw_|5^;#xJy)GyCGG5^PF%AoP^^l$@!x6O~H|Lsrr>Jn<&at$c1`#9Yd=Sn6Tn{;#J zt3fA;t*1L+qta_6wdCXaIsT_ zwhbv;u$vC&Klla!q8c>^BFMqH(%EPx5H0$$uAz$8nhNfvYKSDy>8P-Ysky|}2 znHyVCpjA7{XJin;XV;Nn)>p=1K&Evr^u)tai@puv%}56(g+thsIC}^vE7$#KYxgdL zfLZE2@9_ln(}9P)+0Ef(`OBvhtVnV;T&(WvcjsG=^hk(^eU4#9Vn%yvU{x>opzw|z zBTw4A&gDg{t-XFoTrLGO81d}E*pDZtUHVmwa~N*zZA9u7iL(JBI>|i$$l6-mp5r)C zx=!;mFN_A^a254ct2P|;w`ANHF{0lS75BEPh!(#5R@C)D-#a@wSg2LYgck>at$!3% zYE>&1*_rbv8vXmTM-5!LAU@Z!4utBCLE4zaHodZk1T>mz@{GFw)JlIe`PlM+N(L`p z>V-gBRD#)c>TkXDUmGoxau|@&I6=H^*sLbK%jYQb1dJ`Q5SfpTS3~=8%|oHE$Y>pt zHmKAvr@OdbqCcZ;8JrH!K78J{CgVf{yZhA1V(p~1|3)ZSz98(_Vb^;n1m`-G=I|Bf zn|&kXt-;O02eEfwMzM*wz4sDmvoK*XA2rQjy+E`XvFt`-^F+rCbz>L5h9>J!n6 zOW&KL1+-@laZ2tr>o88%B7CDSoY|_1>XY&_|A&Am$n=`4_jBO8KD%S}m$pJrm&N)^ zKv?~Hv+m|^Pq&&(P|Dznp+ngsJX?BPybL= zp%5wOjrL0^AV8N=9vp6nu>tOl9DzjvVM#wRVg2mxRjSo+aQ+1>`|*NC{bO?vYLrdZ zLxz=HI28@5J7=c;K$~CL+db9{{e%n>(th$idjPm?l)j8LM8hvPv{Sa4#PC}hS{vEd z)4o#VMBadticafPr>K0Y4U*2zW2%F8H|Z9l_qv=Jk>jy7i}s{W6+x?eWXK~gTr9}% z<1kplrbiro^AR7U;iw2PatBf2!lAr3Ut*gvb^MTuxxsw|M;maIOQp0vo+V~L; zMtD@AaX6nwed`BX?q;VDK83Ru`5kYPay$$e;B`#kj_xb5*<0ngG!;c_tT3rzIc`hu z+AW=+XOEa~sJp~1K;#PBAP{iwEpoHAjWas}W4X6JPu0cerrR#*dj`W1*|`F4-s^0O z2d!EkM*j#`#wA;}!)`^CG95g`ihla(7@xmR?G&Nt8hUP&4)jIa7mXD}es1DccJt~Y z`7QBOW{-tH=mGv>ttLSv8JpxON}H7jeWiZnzUW7LnamutJM zxGzz;PQ4Rau+OB}82l~QFBa;}Zu_v=Dp5fCU(f!46VVO)yhx~=!mpR#)}#gG$dM7p z0_U#~0g-w5glY6xNPT1cc0KIb60Q}$bMQ87;e#*w#B zt&Ck7?AHoROS4Bm3BCb*GOujMDqw-Cy%Z4s_DuL)Y}(yC?f+eoOIL}=CYVCP`N}Wx zUWM3I-L^`1LV3DH^s^0?{R)4fX+H(gnwO$>BZCr=L0@*zGd)mOpIl82jcb3wSo3(U zvId^LakdRcGBiC}NOg)HMNEjwvwL=yTDb?{FydK;FGYb7g6T(jhF35!X4WWG?C@dd zzaFT){lF|P%8%&ET6t{IlKE%YR$4ZY(5$^YbCP zAO8+1jg@?-!xc#qPZw6b)aHdwqU~^WZyM28!loR5b5-tIE&)3GZDA);Pa_q%RU#i4 z*TZ2q61;Cfbj-UP%H8_tj_I!_7t=LP57Jv)I-MIA)XdKNSsa<`lf?ErRfZ#4y|5(d z`WXxMi;cEx1=9VoS2Rj}d71?TE{4 zbQc$M==J59Y?r{$>_`U8m)cFhkVXK)f`I*`Mro6_aW}nr1_63i{SVGi(Iar;qyE`< z>G4@#xmAOkyD;XBYq(p-JD@E$xdT ziN?AiU>@02X@AGT;q40G)sxaLsRc?Ya${^(pRZo%CY1oZ&X-uv_T0gUWxa}Eq?*$J z`-!V_TuY9HE8$hQfVHw(thd#psyer8akASUL-w&9eDJeUY2pIU?OMMWW3%49hI1&s z0rJ1Ae;)}eK&`qT4i~=fJuTk(_ex1>jf7T=z#Ag3EJCewI9z%3&VG@m!8$ZQWdhX& z*&P+T{QnkX4QWabxbE72cWtA;wrd=skwlS&ySoywojEy>t<`rA1~l1x^n_Mr?H>82 zs|q2Q2q>xMdgi8mzmn*`=Kr`Pvz@i1kk|lH4|b%Y zw5L!Hu6|51P-weul06?LO92*=Sy~LT%SlLxN=}#{k*tt$t+IQ24HYP+{9BHVbqZBe zQ2t)?U}2=YP(P=k6r%aMuc+DWbGFMIee+bYL=W;{xB9VBHzepVL7bsImhvIC^~GdW zb$wP!{cU%tX!Vh`_fl-U_r23XqXDKptTjjax}cBd7>Q|1tDv{q}_e} zE%AFbGSFw)fC}>Ew%f;$`f=5+s(pO@s=KKvD)o7v8SCTgtH9PdMp!={cGWLDs0Z*m zraP%0VbJT^B$@Jq2WHsl8@!|IudY%7z>-q9`oyH3X&Ws19JBwJ_#8GIOTPzD^>L-# z%A=_}|7(PSJ*e+7-D|rQyjT7`U;Ci=H18C*pkNsl*(w6k5N}3K1)_2tFr@Y_IU0_r zmPCMZdIX3YvjT}Ml8OAtpgSMpRlnA6=7mzY^mHy~@Od3;UaS&P5F|xT9V;{>u{4l%F?@SaPP%Zh3${LHqCQCY zPdIoZOIe}%Cs^7HzQ1aG9>$Rj*Y$Wc3e-;*c{cc(?Ln}iZ9L??rjPCJxiCXMBs`H` z$l1B0zySX6S+;09^`ml2{USSqj-B6U^(tKYlO|j3naVZZb8{g(KUO5H)CW zL3zA}1l^)mkVbDhfy>tmj+bP}Ns7D=e44vA4a9bPNo^&}qi5FZ@z7WBh*)gIyoDzlT9x zqNm)D-N=kY8$!bd0XOkG82g8~Org^srMn|!Yy83r(zXWi>V9-F@k193uW9rCpC{^4 zdWXJ7ScXmBGocg0;NjP-6L@d%qLq1`(3h{U+nH?A)<(B=9GgouUUzsCYipYcn*)W_ zwdPhrLx%eKn=aG47}Gcm1pJLHpkX>pNbKd~XS@ETfKM;Og$uNGw5my74hipZ;bZ2r zf2y$g-Yi%Ws0>C?AI4b&3@KKEJLOjxM}3>TdJ8iU>_2zY&%^H0xs&RGt-Q}KN>Sz9 z*@}q;TV?Xu-(T1*snLj9U@7v}*rKGEF6EO7p)9^QE&v6!sb( zE#;(aP>a<;_tUummHQavxX{bte0@_N!qgTlnR)&(6JQUINL+GA5 zlyP>RP}b`u zj@=^Qv;p_S{2g83Ar}Oeq>v_g)vQQ)LaewNJ?}pb&1Q)9^j`dgS+4Y}+iin|@jEi_ zwCL}aWzULggv*6fvdb1%lVy2lMuDrl3U;-%&Oh(?nkb7Ho%6nYzr*=ENc7mxOAo<@cY;aZ%iKK!`3mqO(e<{V@2xZL(!}gT~n9 znc@4hYyahCWN^I%b)JgVn@gak5CU&KG)k`JK!uXOCBLRq4Ub+7?1{$Z&}kBc~U4vM!|HTPwWoq#0uU*{N4J6zMD`%q7G= z`gfE=2(hz$1o6>ugf?tq39LVDll7Ow$mH0tYrr0A4)8Di{2 zESiSXQB{-D;841#*Tf@5KeE-|-|Iropto;#fVFZ9Bz&LnE3UUq_Sx1rQ`vv2l8B>U z1kc|kpeVI}w52MEIJZdc`W78lpKpf%1JA|WBO5G14U@*-Q2Y7Q{k{S%N8&8IYV;=x z8s>Jh_eaUJkpQz&wqZ#Ex?-#a9;&n?o__X^?Cj#w>BhJlUmbPCQ9$q2#HpD>#|{Zi z(nHgjv=WUG{vXa$~gEvxCV&iREG4cO%q{y;L?K-B*`P!4KF!}sjG(>GSNk~bY} zuvHPt|7;Z6DdtOc`^|?B2?eM*S@!s&7_G_h{Yr5BE444I+o#q33X=v*giPe=j z0OVN(qdhjxIL=j(DM6G?8<+?$K`nMSygDrpkL+Ta<*wPc9pM8zADH(yX=u`9k7)iR zHgfE0SGSAj*>}N{<3zR4())80Jr3Hm zr3nAtDRn`MAe%K9!M4?1@s0G|e^{iItaT~4^rD9GL2DW!m|#Nvd*^~-M~M^}Y|{@A z>7*a!xEj7{I&8fQ6#@!-Cp)&Ty7vT?9VcKiX{WkmN)?H-E zXgJ61=2Xi#NN5wL))ujryfB7TtgK}0vY1k#rT>BwDirEIWS?i$L3dKg<>lw1uFjo{ za~p~EazW5Oe42g$#L7M;zLeb{Y=@eRUY5)tSmsAKZGgb2X{2}Pj_S{&^Nll7U`Hx3 zm-!hj+4ECLDa{#9!jNK*N-&mHewUM;ih8&>RlKAt#$v24nX|D$^(AZEpH@O|}m#KM=g%e!SP}LuZdtrTfPX z;fD?Fv3w{hKlD@ktj`P9$70>?G-nZ)tR|UB2jWC3W_Qr1>%i#vRp(X24_0fuhL{rY zSJ6vEhxs0PEgTZIpchcv3gy7u3{EG%AVvzZlajt@zTx14xB+PfLAT#18_k|mQ*_$` zHtWUP>LYnG8R;8)v@Qt#_#m@REhAqJaM65;4ytJg7|{@zjgHVV2m`oZJ?p6l&-j|* z{Fa$OID^f|a2~fg`;YU}VoCbq{D&zIh>*heQTc0J6e=dl#hjNf*u?1|&ssGaYDb!s zpmqm)IYFEbb~a};;dK(XQDTiI>3dqp_jne(trk?BaS3R`tmV1$B&DQF)SWtekUUu= z2vykm|6C)ZZ_LIdG+&%up*SwE=x?!(KG5o7*VjchdTyVv%TUH~zTo(cIW5FYLoV^S z^ed>zZhKRt?(wlz6L(gpYM7bS>Ul!4lFb+0jYn7glBA?% zru6l|WuBXw;oGRs*%U>xp@%>(R#J5JtutYRewd!xZh`hDb*|w>;$7N_-a77wD)*Yv zLcGA(IH?uPVB3rNy||rOy{T5gyUzfjzxE8@WSS{`B7+WgDUK&h)Bgtx;oKN(syg?} z3T_(*+E6ENl=B20H2O9c-(Iu=XC@w)-7P~Ko|vL`#pPC@_yde5uJ#!rZ)A@~0K03E zm4vdsC>IxmAB7L1m8Y-U+}$|HMoh69m3CtxP2^IVqisI3?a-(DUi=9cppm-$#bgnXsw=*Xxiz?9{i4_k3-H4`RHXpu%m zi=l^pGg|hqMT-CXw`WJ6DGO^J@FzRlh+WQVHq_8Fv@$aw`HZ16LLmi+RJ1iu=4oG@ zih_++7I98e;H3{)v@toxzlFh}_@0YvR323^ulaLdQzUnmb0@$7?br1#!IPipb*q4j zpt?$YwPwn9E|=>9c$X-b7clu$LrZ4n^>zt~c$n)Km*;Mt&h>lb z>T=u=qF$wL5bth{3xT0~4+y)H&U6eHL({B1-Q-ke7)M_s;GS>019&Xhp9LJ); z_mlOwfV`QgNAEYRvv%Rff>&C7notEG5?=uhZiU+a#w+ISW>htM0eNsg&%m(%1r_`D z$=hsGHQfI)FCP=Y&>ocjXuTx?DPR(+|8BxRbah4OlFw7tqFYe`u z9zx-nO9>~a@XBz;1-e;;4tvCB_^@;28pYWS2pV`u1tSOxJBuNwbx_AECSD>Ybw`@LK+bhfrq;q_r5VfyZ3KUEl}<<98^b05Rz$2N&H?VYX7 zmcvBy$XAlbCb4$4c)`ir=#yCNzIZ8K-== z_*|NmLIX?W$(DC#bRJs_EdV|?B>tNuoj)9T6*_6TL%r)ZIk}5$6g1&&tJ52W!BWMK z+S+^p$n<<0Opq@I#fI={bBrEYRzb?J969eLOac^=F+Kt0)$?Swxe3NR&fnk`C?+o# z$mpR!^!4w*i$A%$y2Z2c;yU!HE7)2qN6pH>8);6H3R$j?bwaGA=xMVhtUq@Fs#D0J zL@9+v)%U+5$jMZtA%yaOBhtQ=SpPHru0o6CJ!*GZw|R8GWJ9;=Wn_HF(5H&p*l8x$ z9vnewDL-YqAwV(Zdd5t>#biWkJV}*%KD+@~W^)?DnN#f}yXEmmG2xteCh-G?UcTZu zPI|`SM(g6t{o%zZ%c~ybDHd~0wP(@)-^)qSve{^~MGYxUZMu>gJAoJoH8>`rBy^>J z!4m1-K@OI}S8x#%pYMRhsKsdzjsx5g;c!5EGVmvWztcxN!sya$TfVxVfjdh%suOo z!IChWNe*xss8jr?F^B`0&dfmBrm>x&%kY~O%Ajkq-C?D#Q=Ywo8QNiD1!RXXc{c0X)k9e zcbNnfLfCKvw6h1(T}>j$7*Hwpu&GI3$14V`Dxv}v*r^>8zZbgl`8lhHLFgD*;Ov6M zn{*~jk{o!?6EYDK%=;miKq))+d;FBOqGPBWy%b32aQ z>-nJ=wer($#7XcB`1UR4P-ja~1aB7IQ&brLg{A+q*U;d1Tvhn;|Gn0gH=9oGbiq<0 zoxO|}W$t7)uUM!7-+rcEoyqm39unOz!YD3zfL4M}oPexV(TBxpPr{#F5|RU1*4hx%Ez~htHUnr~D@l61CW#8z0>JGqaP1cyo%zqo1>A-e8lE z9mEJ0(RWhw;z=rDA&(UDLcZ4G-U0lmyjs{sJ<~9a0&AgwuXw{cq^Q!u1q=-4Kk6_( zw_ONVf9H3@p<%UkqW@bT{O@1TfBID$J|isuS@{K>zZ=fZaGnjm#Gbds%|K=MPC$-A z$>KZUi}3*?1wxI0RtKO}eMR4la3~LVn84tR!mVeq?_}Hl1(K!u`7=vY0 zbPV2zDi}JKbAwnM80hEXDQ{0!glDvEcQiHBKEFS9DEo?B&QSL|GWq|!!|{B`&Qjn^ zE~2iOPR)y+IFX7|=j7IKs=ib~u(M*$SHoKG%UwYe?)h>dOM!i2(y<`e%%S6+=+ZDM z6H!$#0knM8`1Q0{@33c-e1o5jMxc?$D*4arKn*5 zeG_n)D92~`28@v@ic3vS9_HG6(JVTx{P!Gmu>vw9ij?omHWS}8ncQ@|JKsug5U7v` ztW>*IITRz7MEX6g< ze{J8a*fO>Ob+F0|!pDj=CV;-9`mMgVd8TV^BGdzkZOgn{QH6ztuz{U^;G6dxcq3!n z1V3vqm^T}S)G+HT4vr07ZLz$(}N)a&@_cTuy1d15FI z@;r1!Fe&MRd|b@0z{GTz>|yg*uDA+0B{ZM6S@Rw3@(ldzl^#hH2Z{0ImmAP>m8bj# z^qR&*YLD=<>MP_4-*liWAjXYFV(ZKoW zTrR#ax(!cRC`s+2R@_jK86{aTyo8w#cIAch`4XYn%E%Fs#Sh?`Sb<* zr0QxpOEmjj_{5e@`b7bYIdfy)R1U1L#Ey5fa_>30p?^16SF?EBaOZ|S}m;(6xeEFbRcfj|5%n_(N?^07@4 zTyx*uDMfrHdnQ7gOMC{sN*D}BE>ZiGNyH01WrHAR+(IKn!vN^95joh!QFL32k|cXO zgi)7VOql5I_fmE)Se-aWA+Ph#WJ;3q_A4;+hi`-J-P(MjNx3Hwy$cY1M>r}kcA<o=Ha7{pL^*<1X)=(k0G(7Lk5*MqWp5$|$1W2|2^?m9mAsu0J~4l1e5#t((k^2%U zUchh3(70n|Fl{kz6+&Cx8b2L^a#ts<-Kj+R9X}HuY{ZVn`)}MAn*BCHkNA2P3+J)Llf2sI zb+tawj+{;5;3y4>x3dX{%(QZ}t&bTwtPL5|MiP6Trjj>)b}YYIJcmAJs~Yeeo?o|F zjcqRLvR&@RY4dMHay4H{_O)lwfZ)Uw$S#ha- zKMq+lI^3cT!1F_FYdR%+y}5_WLNe{2`Mq<1^HcjFQCIP@R?U%OokQEjYN)5Sq-#JZ zAlNX=VVXCfS4?1cg#af+49D$Xp$c89Uw}$YN5Nyu5AMg`5}qG0vL253=XC{m7WM07 zTDjWo{gN;`s?gEKQeH26pTsf_$PobxF}4~M`$PU+ypOKw{7s(g4P6CL>#^T1DOp+b zRXydl_`C>A_5)kYQtHGWeVy>lwXVgoPbJKHa1L=b4`ZjRtuLYnXxJ_o*qj=0zP^!w zzSV5rc))iJXqmfNaS!g%h73c#y!d%Nw1#gX-cah^2~{7tSQ=pY3>8>9JD-EbM^`Fz zs{K5I7>~MBZm6C1UnWLmaM2Lo=Cy2Zxdt_5DK1;}A!2+y@p-sJ!o;khT!Ls>#0@wm zqE(}>JBaO&@5ekM8Dwmx(FCs_76`dVbP?T+F681?7x)9_+~rF=cl-aft*SH216*Dqf7 znZ+Cf>b(Qr40#WbKBo3(U05F8(K`(ZoZL&_i86z}l@l~ol|buE#Nnz!IRz|pGl^vv zLl}#j#Vp9GF-uHVlZ80udHZAoN!90HEkofD_cP=Bg_(nQ={ycApb#`?#|)n55A%8w zQz-zrP(X)fvl)jteBX12v8@+q4d&{!8%~g^BvARG#jc7UCL?O61-OHSmb?J_3EGYe z>w$_dPR2)k*WyrqhQxfZ!^suHU|d=Vum66X=ETuH`~}o27-_QQ2^)E{EBz8)m6U{Q zv42T2trGig_T4*-DHzw=c4AW7IM9}hat?@6@5P#=l?QkdvKE8dL6K^{{Mn^*^a?qz zx8s=<7zy9mnstx)awqigxT}tNT>UfofP|-Q7H@6o;h)9N9NQsA-x<8U+*ak-b~_^Z zHeOl=VP5uunymjfz{QYs05LDb*T;V9#(i>rfCEB@HlOy05#4*C^Fz66J;j&WTh1MVKB%0)+WH`_)nLCbj(~a$j&U@Gn$#wSRWgt)Z&SA5^DZ}W zd9!|=y%K0S69JFeqp?wTysn`EEr6KnV(1Ma={yXv^8O{f>eKD2TO|8Qi15YnKu$&m zf~Xw3Rjlt&;hfmjTsx%&a0&<-gf>^fl)_qze9ML`ldH4sK6+4FrMZd2@spR)z^Z%q z5?!fh&dWJUP~ys?p!wsxACcj`c|7s6;8yE8ve&2fHX++jr^~t0C2eQj z)^C!0m$#w6aI)!4#D8>4*KU753t7<#fPH+A}dz~pUgsS?A> zx9JJx058&2qa0V*C>18`QT?@kSgh-t$ZoiU3Eia$Bb^eU`fImlSM}TBs*irUu8%uj zU}a@z>GXoZr5Hnq?ib+aTVAv5mNeth0h)X1E2#z7lho9^btz~906YMi&f@#1{pLI{ zn$N?d@g0$YwzjU(#!G=Pjqo^{vP&UEh7!>wu@v4gN^ zg>N^p`set8{d1~1)Dbq=wJhY{wB4VvQF39j%JBM$4jQuXYHWqhRY~TxuYC!4{vh6GI3eme5CHh)m0PJ>=R}c zIYvfbBH1x!Uk-h+(?#gSImUQD2FjLr@YHeMAE~i4xaGyBhV3VgLq-op736ySS1TN? z%F6D)JA#BAl*#GvLx#@>8BMuJ#c!bwrH^0%2D}2=FU~QjiYJ6EANMvNNO1*H}+R((u;p(?RBu;1sb0JQL8-b?wwd>onT#kilCxjb;zEu&oMpup7 zJA9YE-=!#@8e`B$QFIRwhh3bUoQ@E)d~=9)){PwbPqFQaR*K}_8Z)M(Q89^ zHzwOzDKSq;D!Dk!tKAR67cLVAF^c618o8@WcucqcDrBJL+JJLGKz+C(=oTjD)mAQ0 z$}Qqoo{0C&5Q^F?gWn2A2FhyFA^yLFD7j7loF6d;YkIEWs`=I?w7$PlJiF@m`4T$wS-$%V0R2N%$W zK>tra`#lE8IRMefsje!8DsJx_;k0rzBUN79F3EW(_O2X!0ykxqNd!x zz;_s*lU^SFp6I}!m$v?oHv5PD-`3OU4dtb4!>Oj-8HEO_PdwglXT`R@*@Rn1_~c2T z=bA1p+uy_^z_PTofyo=Gx*RUwINQ=e-xcTCjXUdpJ`~6AO3+HvoU9bn`7Sr=r0qotn=TQ{Y zvEvO}pFQC@I?_EfG_^9bun?7x1~G^_&8KBoW{6yZ5Nx4&6t#aTBHo3_d@MwcTf=&bf?sm{j{s-@PK}x;G9JFLXkvX*2%nlL=Wx-Ar zFZWRa#~aO(CG|8?5vHsRtc5H~6^dx*NUohQF#r*#&Ho-T0NC1NShkH||JJ!K>}5uDK}yYJ~LIZNUD_ z(j|F<{%&VxIS3aT!jc{L``mVHJ?8f+C1osT|DF)ko$bb}Zr!=+gj9)1zP1TXrN^D;?@}3 z+W*nsiKE!Ek&S4Yr6cN1bj#Ma@XPhQOUdUj;2)R}?QYMp8GZ)Wj;UtqsO?S7Clp4; zxRmXXzhA9cuV88vXBTZ{#Wqf_n4dN1*!C zvp5?0YkvNxeY*3SvDNUWy^jknr>jm=^YR`6d1IIKx9rWoc(N{6Tz7|wIdt)ow?pvX zl$dUH@?9p&GfTP?9Fxd?@RMKUJA`ZSS!(O<4W4oR6b58{;%pRJ|C#l6btbUM`*)7! z1H*%vA|+1l^;uY~lc&~uO@G2186>uI`s6HRZ-U5b(1#7ZKP`2jC}`m2kQKau-!)`# zaB8UAKZ7!cWAR+f?e{_WUD(OaX}-~DHL7z1&iO4dm}F*S(-0G(g31s9E->ZQ~72CZmOuP>wnCFx+7k0%Dp%e$wZ%*9FY>W@T(ETrk$mZ_ZD zVwF5~hGHrsRU_>YK*^Jj1b-Mi0Rfsu4hij;+gTy1OJi6{$0G z^Z6n_i}Pl{U?VtPm-=EO#`ypH$#4P==ia! zJk07GEZ^VPlo#7l$d_O%d}K!e>!E^1M-ytpIeCe}?v^c}9=A2_Gf<<&W1=VQMV}OH zc>wmov!%#2z0)B9Zm{yEed)$r(HI-al<;6Awqk%97z2kZ6={o+Hvg)jyu6&;l#M{q zs%*gyqu`VgZ3FMN)Z`mFqQi0d+4mC)+@a)76){&Z$qyX?BFQb6iiULpj`B6>`N0~m zt<_fzqG|DR=&kxPT3nS*@HZE{oN@?T=aT!EfTw?8*y*p0aq6ANTlNv$BHpG!fc-yon*_DM8S1~Su_ zbM)OP9Fh{0q3lyi2EMk#@TSk-5NY2Z4lzmEV_UfUZJU={(X)(7mwd?)7~|*U@|wD| z8#kizjya^`gKH3wbr`JivB;Cwfjqzte^TK)z9vVmWWUG+E~Pj;^eFh5rp~KcmFs}F ztgd(pEX#uj-KjlObnV|yR+A9EmOZ&WPT9^$eeMm$rn8f^V}>ETzcgQ#Li}PfR6~{u zI3_~wY&;XTJ(w?Tf~AmBSd|tJ6ryG-+oC13wK4o-%8dY!s(muG|75(rtS}wlKXta9 z9`%GWK2DsDX*me6>;im90@tFdsBC2LhAkQ!FNjx!?(1tATa9Jho(eGvs}L%`dcNnq zsT^N1aabXjDZsap&uTSTyxKlJZCk$GETOL*h4_eF_tC8HvR9&Lg6;ldDB7!zbkN(j zB({f&o!G@@#A^>MYQk%-nbQzI#+#fo*mG`Tzr$6j)$t?kL;}@ z&W?i4EUS6v>B?4jrz52mRmu};dh&jCG)@sP{{>0@grX9zk3#3fcOQJ~5_R2eeenCa z;8MfuD@&MD^oUlc<$(Fz%FA*@{hnP6Z^&EW0b+zYT#ka zRK#_RqBj^3j9|+*X^gPCm9FqPSYsX@N^Oc#0^jBnhgJsoxus#HN} z`foCTf;}rCLw88&h^7uHpYzQ4&Nep8?D_|ivE41$QaQ`dWY(xJU!BUm7tb>@PYGZt zE)At$ZPqwl#mfx6ykH?NU$%qRg`IxwrbaoTeM|D=x;x6_qfT^^F8mcSwsJT8^4cw& z@pP>ah}H%fH}9xY;u;sQn!Sr#f28zS;c|7-4ncUa{?IQs&Nzei;Sn(5*^Mx#P}+M7 zHf&q3)Bsb`nGJT8c+1ZnP*P4W?NKjKJr$Qa?;IRCeBI{^g1+^=9Qch9fM zy~KFD+xF|)Fu{I7#ZSR1M2VvTKVM@Q-wn6C;<*%Pr=#rVBPB(01lmx#HjRsm-E4<> z$#x!cfqoC0|E?T6ay=~m?DuqbyuWApFd%6lE}q%F(YxzmYm}zy>C71t0t6`ZZD=oE zuEY8@)g~eBV0EdAIUTNB4HKIwb|#1PhmQuY2`dHxPj^&3(G2K2@C1ll_&icg7!hd# ze!F&oxpD8XS%PS4yZWWt?wa9fwO`aVE(g2>=Z(7G&NE4dUC=ZRNLdq&1`!XKYcQqwv85W1>9Ew+6_DgYbL z&`Kw=6_~+__hf&CxWmafut5_}p6Ern;ON_Cw@vr=RQkd{sU@i$F(6BcrAW>q!t+@D zY(ptpL0?Za#MQ$AEe`!OU$d6OvVQ0O_PcI&S!o0;D3_zh!6V;%ypcysD<#DeqxIq} zLS{~xY3M7TK_D!7ZC9ZeKBvrej?<+m$C0?2JdvBj-i-5=;&9ar_V=_Jv9N#zkTvl> zL2)X(fe<}h+$i$;Y^q&jh)Skm%3AuiCvqns{HiV7Cu+qLEy)?|PA`C5>i8Bh36@Oi zL|XzTIZ;#~;**A%qV&icc5xf|92AgCMsqkj4iPWBYXzC~ACmd`t*RHiPd)3n#xe?j zF4=^3D{LnzrSZJ!F=J7Uu0&nJPCm8}dw&jG&P8^jox@0=v0M)Z@3tc8jCC(x6fS>u zK!ecj&Q=vPN~J{|HQ%saUH6vyVyUwsIoQV*Z4;p|Q;UmFaAZs1uH$9iFg;X0$bMZG zU?Ouj@h7^8`HgL{gAmyv)Hrx}fWZ7NsYbuSW~NKujJ#BOt|dW5{p3e!?*l`~GB*sN zuhJV+dD!sAqN?_PRjX@Mv&`qADJnM*a|&djtSAB+k!doCbnSnI_i)^^WLuhQ)(4-U zTYKM8m11&b6z>W19$a6@O1Ro^yv6gTdJq(uHQ?LjH(d4M!tCQBCax|50h+Uo^oT;j zZFGWg5D+5y^^M~jr4n;_?hv5f-x`a)6A-Hz7g>A!`FDdab?AGiFU|O>O=$iePHdMk zOkldmXqRXR@a6XkD#=#}A9DX>?XlRqUK&B&^iL~&!|DGYS6>|v_163?2uMgb5>f)< zk|NTubeAl#bPKq^(kz=&(4|inK(0ZW;jhu zCz*2su-nGRuIrpPns|6_=ac=&;fx=Q-MtrYZoytIFQ0J5&Dc{G9_KckbJc$x2WCLQ z%+Nn!(v!7|oRM)_jTQN!T%y7h*XbA4TcC0<80tDi@XeZTkMXSyChZg2`20r+n>Wt1 zL2OpMHgTRtLZ^|IrtGiW+TX==-A)!TgqzaE#y;~pt7!Xm=}Ey=VD_~}nrY-%awjNf zTTV}WFQw(lCmL^%FBs>;yIiaBC8-V&U)>Hb6{4086mZd-olTqqz2TJZ_~9AmeQ|kU zbfD%P-uwKP0p}*Lr`gEc#!~Lv*!QyxNrU(EDRM7A-b2(r32PpXKx1yvQSvl@ZJ59n z?I&Kh-itIZ%9lJ5zBYmho0bFIgk+TZmuf=PV0PWxC7fI5t~^7R{s46bVTxdECx$6C zdFJvNX=+K*P-t}%aYRF{HQtK}U~GcY7<)ZaFsEtus6@A3(>~CGA)2JiP*Qq00JTxM z;-$X&(M_C;=c`}+8}av9^@HPaJ04`0%8xzFD#na}nkEInS-TX(W2b8UO&!8wOI*{## zW!;Vs&)e4yOufo=T0w31wFz$QJzpHP1HQfkG|VqCUy{TSCYfe7m9nN{BeONeJJji@ zfpHOL$C{?_0mn7FvC(}vz>XwJBQ%0r>idKzU*n^$B*w36j(*UY|KRj%09HZ8QJxW) z-PXWIee7vU;Dx0wQQg?RXpV3`6an3rhJO8f#_&&qr~@#T24WJD8p^o#v*;+(0kB1A zuNw)UGbwd{M0Z#y3o7M~&slO>8hv~yR+2j|f6kU(*MJYr9tA0Ba1^q*((cRDg98=& z{{BbFdX1GN_Wj@=A$S-Z^%KVNj`#DhvIr!U6K!GfG36(DS7ok1@S56o)STH4qmHb_7^4K>9S}KMZ(th?lqZbIFKd%b z9P`HKS3v;cWklZ8@l|ZquU`U=`IA@D(kr>Jk{}-Sp0x{SS!1xLr{I(1k*F0>qhB6} z<1PMK7o-zUtMZp8h-Bi=?Y+1@Ll_Dy3l&wqY;_BIs`iyh@NWm*l(i7Yjvf`6y7NqghDgPYgHDoeF`ZMf&Q*jSJQlQu?ggq z8I@H8O)9QM{WR{|gS<44a>CBB{Ut8_M?-KW8zhIQYIYo*oYp$E@mIb4t-caoo$h*A zF_U*!HGLg_4>Dy+m|&<>&}jz={0GdHbBc!DA{1LAz5y6sH!Br07)g!mUlYF>76abH za3>|(@v|f@*f-uEqNw$DqHHHo@4wkD2UiBwN5Ln{;Bh|}l#ifzB=6EU@j=lAGyH?06$dN#H-eZ=A$vL6Au zklCov{NX}9aXkg(EyIcLY(u9H z9{|w-iTu&|WdC~V?9I4IlS=XF2JFMW4irMg1e%CkPJa92<8<++LqGNtkk9BjX z8!LEg24T}9nyvQ0Y_ZMOCzxH9IdMam_Kf<~PBTn;5zk?ywUpMG*Y}Ng(nwuVfh9{E zlZXq}Ko857PkA-lfkJ*voe+7sw`P6C=vTY{+>w52Jx!gr@bedLc;vivB@E2^-jBauq{T3``kZ4g0>iFc2Sr2+MgY%8Ih?{({T(5&mjf`qs= z8nIE~_xSQLfVR&k9V8)t_(;g$rJWura`{wqy30*i`tkQm21_GdPieq`TMi*emhUe6 z>8;$J{%P2Ds4pyVze)e*sf3=7$77&=J)t$cX}#OJ+V~!n>Va)clhiuN=9|^|I%e3Q z2}|i>-uI;mtQ05-Wv#$V$*OmsG8*>C06%3yOK?OI`mH??*Adr3{gsiCY7I{I@O=4W z81FK~>10NWziN8Lw{L6fh+VTwN>(oACma9rYFV*qK|Gu_BY#%KE;WIb)OrrLjCwBT z79A5-%S;{&J-GD^=y>lu%hl+!FnhSUy>L-pu>Qd2^?RDR3Tv(uH9JItFSw)3lb7P4 zYMJCME9}KQ=32$da5RHKI>wVQ1jQ3A7i(WI{(K$J@mgPvdfOLJ6dr z?a;hFnmkM0h@gya7=J$~*8%3Q@q?Dvg7Qmk>o-LHxsF`LO8*2}yE&dcvh}e$#5FZ( zn?1(Vn!nxkaJILdPEdonSviESwa24Em&oO#OrGjppX?+vwKAO~db@kufx&)VWnJT@ z;W3jBRBRX-W?gZ7;5O!Ra+ps@4-u7}`Q4zM-9QysY*({P;FUPSOjgf3C}QKihJm+z zT;1xVW>DA2bxcF0=kEK_riJ+`enRbvFe(Jf{(d3Oo~!Lba$A;1(d9n3%G0y6&hIc* z$u|luxNNQ-lt`pb2qIF6vv-bz(&r4M-u!OY1MCRlFWtlDOqHwkAJf|&>mBt^v$thq zUmjeDVH`&2qpUj`%C9B~w&X99Z1SZg`})W)pfvbi*=||2k5!v0ce0Qur|9nqo!9`d2=Jeo)ANb+<-o79R?yLR*WESMsCM-r)-DPwEPBSfT~G9}tW` z2;-c-Ou4#fyKX3JMC%Kly{GNPc&Pif^L5M{gQI=_O%&(>M(B$0r{Grov=RJxpbkZ9 znWPmXNx5h>DbOD415P)(V)TVn99n~TrQ zp)~RDqZPy~&1O3mbKJJWu$DcPTiwrWOR#=9+3BYR+OsmeY5y{zcXYnUATSHJwVyqF z1;;w%Vt~ueg5OIHoTEeVX%H6cKbJdvP1fg{&K$>6*^v~Q%7-_T{AA+Ky4;ST7Kv~)PKlqp@8{0;n8SaF4F;im~KlOT^}ymM=d z_tc_u>>Xu4!e4V?>mR^^1R*_4$H$gon;>c_6}wW&UeQ@AD*l_6_j!+Qw>Mgd50Y{; zLr%QC?oFv}W0dP4%nBGEcXW(Cxj73kzKYp*DSsA#Fz`!A!H1$iXW} zk^RA)kOVL#8*#lL@hp6Fk>my6L;8=*#%F>1UKRI_x^H*2p&6eliPNjDiEaoG#N?^` zYvZ@^QLT37+dQeNLVHYe{tbyz2x9iaO=)H}nSCMqE^&jN^Q#|YNuj+YsZd(kmD{7p z5^D(2*LPKsgmuqgeBbR#<3GB~>$d?F<%FyEA(=wgLs-MDACItBy}Vl1qJ%^XaDRDN zpt>r=yBOb=Ue5lwI@`J3A^Np8n{HylQeWVM(&lqKGVzAD@P0T!x#!9=;a_8gG{C<+ z7rq_>S}V!q+-F%OebWT zfZO@|q{!{b_lpM(A)r8AByuXM!V0ZcPk3|sa}9hlq69vVo68gKrht_E8A;Wa3Xh+2 zE8}g~Kn|i1SFp`p0&NgKZysV}v$ZM^layGwSr*VW*6Tw5hH@O=fmJdb3M)T|PhvL^ zW_f4V|C~&&1UK8h13eB!tIDV|sMcSbIOywnwb^j%f9EqZiNrfwP&)4I(?FzU!k`QPyQ_RtmO!GGY$7WR3rAC zD5dK1cR8SIgFSegDe8Xdk0Ps;q*tA4CCOszHFEE+rL=GA4!_RbKl~*wbJFeEqrNNX z@uk74wsraiZK@d-j%QVv_oXQBjs#<*$KIopr7!O(G8IPT-ZV&V!_oRip^qBst7b&bV)moe zTpD9dkhzrnDC0ApA)8GtuHDbX+c3Qswz^7~=V=&qSQ9eBM&TOa|y^9Ev3&QS9t=H#+4@ihU=X~3B8+`;6(hB`LH618RId7g$*Prz`?wUR- zY`xwn`;r_vq!Oijwq57xdc8AUA)-B=TD}{uK+|;Dna+vo zgBE8ktH)KtLnH8C-I+4$w;X_eKilY2*+z=IefS%S&5EhI*j?s)WYVgEr&iq({aCNj zWziAL4RNpeX;<6n#fRd^?X?Y3p$HvmLzT)`Y~o8csH2Ju8qBenuFMF@>Rz&1Ef&;N z=r^u<)637XCrATx#u=~F(hcL1{MGFHKg@{`LEYAypAnf!94&h=;d{664eOPw{Y0_)8#KCI-~YRg~8VZs=F z*bhG0v!%dqmKk|<)rsUCFuzUqHmsO0xb}mvDyZToV-x%G*x78n6O8+xDomm5k>JuZ zBgS(~1!3(2_lX`oWcUSPa6&9Ee>mjo0Uqg(Eg6$$dz|!smt(=^7LMFe>#=rDWK(>I zK@4VK3q_Y+T{h08A2$M9Ffmv_I6_{fb|l9qg;&qOAs)K;osxW^-?|4#tmOmbtYE#1 zBcDhbvWRQxr)1aGMEe#Q9S~Z)H4ag(mMfKAcxp0Q<;h0YoKSni6UxLckVT!@BSG$C zJ*KIsymxVO7yY)>5zw7T`i^xkf2S6;F!$6x2j(FHwziwEnG4seH>N1{&+lUM@_tat z;%^9IsRrUlh5DHN>-D-Eh#s`J$J&Yg&L&zhRzK|0Ect!9F$qhaDsn)@{p=GShDy9t z7h8kH?zUxcAY^ms~yXOR}>I1TM!#htrm~jcu;=djliRe{da#eIMnr88DEKrjHO-> zd&t2vnIwA!aVehwz6LeKr%s;0EfNPZw8V_;5J5D69lMLqm`oX!>#7pSoZ2Lb2xMB7 zag#`jE69V0ioT6dZJbOrPLi+FnfR)fC67#M#vbliJ+ezshkT#WX0*RI(3{vh3+P(l zAMWbId7Kc7A1_jNVLv=m%`8d%$R1&MAA7uK6LvyL?A`NI{mGlQ?s+HVT1l6QhZw4U z{e1>%hVp!t6H{@gtg*Um#En`JFTwLEQmPqWD|$(&3}_Mgxp#3p`c<>@^iLkKf{f;h zuU|t&l17+NF9BKS?A_ykmE!ULN^Ti z9hi6z5@K~$CbRrYl{G>q?$@XHJUk+M`sxHxUm_%mOzpn4TaQ0%K=o*D=u>JG+2jDJ z@+yQK#%0G_T}rdW=6bKGptuu@_l3!_^c0ZY%DnzYGQCbw@iNJ}omV48#ZF!X17@Fh z=}_A8@d_ep$dS4jmQd!OkgDkS1PBT)A=3x+pl;B`^+@Wmz1Crodw26B6tj9&0&Q)U z@W_%#cFC~2N-Ha-oF;z#r1wSbIeTy&qv)wG1H8CEj4p4mF0W&mt0|%4oKs=I?4gCW z#cJ5E2%x!j0;U*Hwy@UakM~)%7s>Qqpt{mE5!%5Qz0xpo@_G)$TU{c(tckLk)ZU+q zv!+>}>x@FlU#34u+x}7EI67{;VZ4Z`;jYW&U}`npY+I=el9N?RKE#i~HQy(F{!{)e z;@Gl;BYQE)`|hHTy-4!?GYWFx)DmX$4$HLPWJEnI?=?9?zA`c?9cflLx?MJ1GA1$5 zftF`3x9{8ppVD&;Jq+aLjAc|Rq!x@*-6?tW)1Uk!bFCJX2P=}R^_o548xaUb*5j%LYpj&G(pSOzO$*7W8LP)R zo$Aw`C@>@Pfi&|=wN6Aga-gqTNOaOQ{b}m%EOUO2z^F`iAt{&mTehbhGIH?V{l1Zh zeuDVj>zqV{Dski%z3yVqm?&ZQOiH~x*6n&5Ui->fW(y>Jr;C@LrcAVt-=Nc&j#npCeQ|csCR{~KwOd7?>o1y!_lV|;vkBKrndI?t`nG^*dVgg5ML~p80 zW31ODM|gTB{@%w(C5uPjWq#-9LbV`^#Lm7`2o6?_J2LvhfDPaM$mca}kCtH=u<6+O zdKLCN;04*qKOF}#C7BS4dA-GJOz0MulXNj&>{#vCp+i23EUO>cH9YNl`a}6$fF=iO z1SX$WLrBmRu5o)JPBLycLx>m_r^GY~MsCRkLEszpK_Si==J~I0KIAtKqdL`X5RXsc z#7)q##N_t~psPCJY^N<|C3xH#;x6L~B>i{*w)xlsYMgn_4XYBR^_V#|@^D0RUIT#->c{4y)jAo5s@4$74s~-w)`1Mul>``mK1tLQBdgv?lc^WlKU}$$ zMxXN&H|3T4KBD%J@Fwz^s(Q8{QLW0+#M~%47!H*S4tLw6FpjG3a4=)1G?zq~oXL&z zgl@-bNLl!gAFBo1`{%C_B8`-WBo}5-jBe~D{S}jw<)p;UKtd`l>iabX&lyw9&3gE} z$aF(=-?BX+*R!b(p8kPnF3tS$)e-rVc$=PLzv)#(MKmR2S5VU%f!F0-{!fC=FJf6h zm>}zjK0;+RC=rq%u?F(^xmqxG{+X-W);l%%#;gFj+k5dE1-yev@rN5k^)`@s`(+^? zW@@R&nvcD;H$>@z-W0=`>u245kTg@Zt3UCTKOA|(DA3op2}W%+EtK0-k!620k*z)G zWE8fN=fl<_w{%9sU-Ytdu`B5-2JE%WKS`EOgydwv?dxd5X)#Vo5I)crTk(xJ~)WZ(&}bNo%wObdetVc%BKx8Sq_ zC>wEIHU#3y18nOKUjsyFUSO?(x&ssB-8YW*w{v|#`53QIGDC6$Q`hu8)L+>>XE9u^ zFpc!nucl|N(Ub+jCC5>{Jl7K6U>X*W;wE^R#I0*A^WJ8AKL*Ip;piGC*6b|2St$2n zTS`i|s%&^pzQaPFyS}_-zMEglSS9biQ0cxD69gXBfJA~kpy(=sI*?#Aw5M5&7dt6B zutVfVsW1Nn*;>R{blv_1X6* zzdrf7_Sv>LI!i|p0bx&Xr{omITFED&9S_tRMGw3467Hq)P(Y%Fe9udZCD~p(?T6*@ zk!wwmqrcF*&qU=mbs2kY^dpRpn@mm9k1em6YU7A1@c4~*Hwd>?0nMu~Bt4?DAEMu= z!`1WwO;NQ~d+T5QXJu|E;G&E{cnQ*4hMW&!SAvXuGms znAK8@w`Hs2!lSB}8I$>?9iwz2LH9^$lWW4&;%<@#>pPANZ9l3BMymxQ6ol2{Qlq1n zYc*@HAsB2Y57PxsYIODV5*2AT)Xqh_yf6B94^C8NQb)|GHfyTw=g+CpKTe_Q<$EMU z(3!>7=$lgKAxf!qU}A?enisfhxB{A|xMmYX;2$LcTS2&yxzHka$p)nssA@ zj2Waa^3LF?Ixl7+1J1on$HqNsd}$@+*8}#&Ni3P8sO3fc@lg|dy9@His@`z=YS@KkmIAn!!n^>__j-UG+{WTlYOOSgW zVF_5OL}q&o9?PpXEsa6fYx3W`J8(mxyEE4D^4Je~6S?!fb2Yk-bo1}UKI1hP&G^|@ zRyQ%L*4o6)$8D)yE77eIsQiObT|$m1W+UIOEg>)dfKI)CzNM*DYo`}Z#dED-1_^Dt zvI7N|qCMR!XTv!Aj1eK30`2a*+-imcre~wF%Hs2Mxo$%46Vx~jc7m;#QmZ^7>ZY;H zEmFeqL(blM3&O;bRmI{Wdy})!#iZZ4lNYnq?+#-~f7L@&!c*gMwj>^GQfWk;*66;l zhVi{+>-F^LfhCmgvesD*btZN%6d}bnNKV>)dzpU?wABL@Gggn9#ruFvBzzU~R|?%V zHTf0xbjGS%T($@J&`dQh^Mgx3KkXChIyL;QyKy7#(I8w&FW*ewvtrv=eN;ai*FLV~ zZuQ*MVrn*D0h_?B6DQ;xnTPwG!Sf>9G&B203s-Kb3;~3l5tUkoi_9X~9@uX+Q-I^h~fu$gTsA3Y+z1Y}$r{s-BTtmL+o8+bJ zH^_5D1@iuMVa@kENR-4#^J7ODl9BZ$^z+>5zHC{ae|^`fA>Kp0xvlQkppi2Rrx4H; zgv%q9*B$2O(vCZrUhU;_0^!HwcOyL|S32ITYTN^QLTQHtwvb^&?PAg*#6jg~eZmAa zQ1a$M!oz1E5GyM?J3A{Yh)Rnq3*n<`B<8ITgDEKi1>_3%lXa^p~p143FOB3m{OLK(_Ohm8+ zMLwO2^qvulAx^|JWv!LPH|9$1jqJ7T{v?@cww504Y2*;YYb%lbA=$;;qZ>OrY?r@5i}!&MLcOBv&(+ zC<2r!Zweh0YI&!{rW)6+JiYeWkUcuFhS8=}&ykFw(!#8Ite#j8Y{1xG-ZY2NrxAKs#RW{HbO+Liq=6Jk*qs=4uY5lHYQ=;2p4~VD1DxRnHLye|J<+l!1k7xPc(Ws_fPIwh1p$>M)wj8VM#|JuufIp?L=^5?DV25HcT)Es zhIqtpPd?oJY)LhmMP0mdBAC5w=yyV*8|VFN!}s+x`DW%Q&}~GRjF9vZq5l+dXgewn z>Hcg7oJ@ZLrd_5|5($Ztbz3UfL;kzvSF?v=vmH}hH5eXsSGmbKS#QgD>dJ9cJ)Dq( z$9f7&E3BGo+)FXo!s;^*sZ74iU9PPJm$U4AYk^L(k;jWjwE1wb(F#-NvOXp7#p2UV zfUZZ=8jMkedR>hMH5h=*@JQ+{)O z=EHZqk-=3oI$psZV|^)hJRM9ZP=E*TmY@&DLJmao7S^YRfr6p0BGG9xeY1Am319?;BM(pa}>a67f#i}?l)@bV!c^_Xr)&zUnD7->J z!Gu1o*U3PscZ^>okye9ajbPmO_ls>Z%P#f!v_i+0+;+0Q2ByT=4}UrF`Hrp9<*fbe z%*+9S4=>yrrf)6>uF<``7fXgR@9BnO*jM!|`H5yPLXKhzl zq=1~vBHB{lfM)^jjQ6ZaY&toT#vOdxJLnI-t&DmY%gW2-L9#xP5HXZF!e79^NDz$m z1c6eY7WkLC>g6OBykHFcx|j}2KGcWh_k3uhi19d-c40T3i@xMHSL>^5q^Inkw+sSw z%QL1`*jE{eLK4mYwU<|N(MxJ`dzG`Y!=f*U6)K3SuX?AR;A3j-uu_j`C>Qi zmaDZ@Vj^G!Q($|dJc6f;^}`w%hjbb8aS_5D_jf}Svvfus=*xDoX1cEGy0q`|9qYYk z+Fq+Il+V>%9`_HjKYL28;ur8q@c`Rq&r*kP$3~A`dzm?XJ814shJjm8 zKto_9F}@f=5;<^eiC|d+(`CC5caLI$9+YX1)u)6*sfC=bNe>(3dnFExr|3v91)667 zb}{xZAsq`=fWC>>jcYbn}e0!N7h z+CiVYLJ+TxU+;dr6%U%Qt$hX{`3ZnzhAp#?D3N@+M=N{Keq*Db9dtP+gmD7SeLXwx z#+z)NVcufdMakcc|M~4AhF0I zoGe1$qS@%{5g@JKFZ2%r8l5vT6gu~9%(PhS3wzZ#z~Qn&=fa;$A1wT{F*W*~HpaaA z#?>IVb~IfM&vtWp6ng}}OYhFN8d5q<5r*8|wsAGU2l|Zzp;G~pWtl^g-Y&lszm(4L zDs}hCfjyyW_Yi$!4VN1M;_ zM3|GWR^2_he6I0qBQzi@F?7;KaW)$5%Ab==CqxIoa*CGWWemJu+b-PUpY>Krl$a%E zF)`6lWv32NEE_Nno}sX#BIt6ASK`&w1e}I8MtHbup!Et5K|QpTxU)07n4GMbs#qpi z4NzYsKj@(N-(zAy1QIUYDEgA=iaoVyXlO(rUSb_Qea>G_r29;bEIlS+gtJWD-5rgY zs$x=3NrCJLiDKtWT0$ZQIl(kkKa1tlu(Vw%pNyS6FA#cScmzWQ>NC_DkL+i~b;YJmT@H(iRF;ubl9k;| zp<`^3mc;R)!9JbO&X#y~^W@V)GCW*4p0?{B@EpTrsL!H|%!sL;sBNb%d;ub4c+59j^SJe2vO zP)Co`A5-F=lFVA+{Ikh!5c}c7GzG*jfk*-_o7y+ur9$M5;J*EV?UDtEEEOYH2~#2x zS(h)|+G|bU4ENcvKG`yf+0YR-O>7WL+J(FP#f51w6baJ>IA!(HTD)Mo4di4j-*5%{ z*L%4^E55+fxclvplmzb7*7(}T7<0QEm=yc6WZ)e_hsISXQgvIj8whOe5(^pOjd47`4eR5k=$!vB=Zuyiyk1u?uQ24x1Q!0ZA6C!W?yi5m;*l}&6FpsB zOy{ijdF|CDmgw65Xzb+GB*H@F*cde{JEVqLp{YGxY9gH>l~Pt3hf+L2E}HMX3i^sc zc=3PrYUEEmO#8=i8Q)0!!(;j*-+Wt>%91RRUq`BUCTeukdJy=ZtpF$|&(RKiBfIuc zf$a$?GKkr13J3|kAV-Cjw6D+qOF08#9Na}0_$C{U7K62KyBy)~e-Bh-5>k{TjtIm8 zW59TTjKd#oiWpbxO0utIbaLKAW5Ro6v!GuNdOcXSZp0Ok@{eMiwPG>ej{Y9Q-{cxh zcx~_y1nK8UhxaSmm1@iV73B6Z5_)7dFxvnd9VSQzPtLB?4RG{-%XoWP5`Jru` z&?*LaaR*QeI7GBVjEM1_?mu6hwqw->{*N8V8~*Q$p?0NUe`vWM6s#L^zm#e0@!y9f z8fQ_D1a#IRq`(^UUB3S=noszMy#A8Z6bpu7&kQ7C|3?^=j$Z>$o~Plh=OTn+8KQ;0 zMp9p3VAn6R{Amk=M}{&2$gj(f+}7#)BY6DmO9kv$pFZna{lSP}> z2Vpq$Ypy5FQ&j+)rw{$lj>LW5yIl=U6INt!c<-dBcH-Bg5v`vNZY8}-BYN~20|#%G zU|9-y(3g!C`a7qHarxugBmIXGvW=L|=bzdBa^b#Epgj>GYJI>t!2^hLk=XB-w`py^ z%I2MJPvV&r^Y$h}!f0JDAn)Jl3AiYPgrZ|({ybrrUACpHM5&Kp_?KBcE_&x}Ex2=b z;|zYF9T@hbh*2)X3y2)p5y989@MV+=+f)GO3zGc*&%C3F?)@{7Mm)e;0b>uuA0*;1 z4{QgIVjNVPbb)wD3r~SVr2Nkz4r-J72%dN4u%zgaL03LUbnH!h`i08p80QFP zE@cH+AN(KIN1PlnDYIuv6+5j39H2vUrEx0A{ASB?ns?@f8l9iKCVw*2FNgsnm-`Q! z>Ml=e%K8iC$NU;V0cH5a;t!G&kJ)>6zn2Oq*v5W**qTg*_5XjP;PU3^ho)Xx7D*?G z1V%VkY*TkffU7%Jp_!fa1?vCLC;6L$@qTLyEp_X$o~54ePibCeF^d!`LwJ{Au^hDT z=KY?#&VT;DeBvH6VAB82A+HG9vB9R1stn8x&=c@)B8CNV(5BBX!^lGZ&uIwUcwG(( zm~&gjhJv_r=Z zV*|rTHcp$foGfhobkCHER?dfszJ1K-kM`C_a1+(wKVr{_lQoeG30DSNPk`#;UeW9#4 zc-z9kSfW@uVRhik_FHplZ!9a*h%}g%VAt9aWc%qX+Bj{;y-PwC0|%6e`h*a`1<)1z zpi+RuMH|#&k~dyVEu7o_>fk*~PS>f2(aZX>Wj-ptD+K0wa06dWH5=Vg$9htSdE`)Y zb-Fbz#GR{d1vl1pG$reRysfAwVqBft0q`gbAQ(oh<@_a9Bwyr81gCreZp#|pE^iC} zt^TgJ>vM<8GGH|WTC!!oQgm8s+)e2$N=?+jHjWZ0sYc=0v~A{pPu2eE?eYoDP5I^M z?NVfFsM0P2zVoKi@6Cm~+<0H-^Kd^e;z3p~ZT^Td!uePtWjq%;>9}LlEfEME5PRYi z0K(-ZwV1DBM3^xHiW!lh{A=r`P5bu5K=5V?UH(DN>F)RbjgffWI|WLzaCwxjIWatz zGx5_%JXi%lY}vcH#=8@w9fDY*wJE2+r=hI>fAW9A^nFo z%^L^rD9bdnI2i?8(T@n;nCXH z&FZ4p9Y-*~1g>3oCgV%&j7!D|xb{BUQnFBZ4RZ6>@621Y)^NSO7Y;y6we^V*6UE?6 zw5^WYmfNq`wMmX0V}5D@0Xi(Q1hopkT3yYtu=Tjxff(FxDU#YHD10O2@{Km#uFC!U zanx7I%lL4A{O26?an#FWOO5AkH_>yZ^?B-ipeSEb8%`_69zv-1Ps@H~XYNn`EF$s7 zgh|Nl(y!BbX`zCej0Dw(;N9L<+kXGegCU@4M+#KQi~6>XrE*kM@A_boF#L@?e=!#R zh}q}455>fb()vBCXI1^TuO{{WiV{|BDCOA{@9jkIi|bY&Go_IC6@I#nh&zn=HjEdD z|43iBZ8?3gcDM~>!}WFKWea^j>4rnQYxT?Cr0fQaWjbOWeEx@+hXH6L8+Fi`@ zZ#8LJSfpGN9ZkAlrvjXkT8Df;MPfXothlNf4FK$fMKUzeks_CE6By1G6sF^gR6<&H z+GyT40o;x6BwX1AkZ@;QV6>9-jmPpg&I41fPkI%Gv=8VlEn&6WiK`!j_(?ahj`^{`$-8e1RM%Gxd zNLZ?mu<&tGtA{_EI;_fvaNE}bA->{Or&1QP}tQcF&D|5n+DzBWcdLU*|>3FTS(A0={@6#>~&!q+e9jSoI`9TIK`m zmcYwkha3Q9Re-`DmEY5@O&BiA^`2?vV4W)Y{K*WH1pfOR)17xSb!P>eZ|;kC#7WP( z>{Lj(=WG@ElseD5cnQh#rxK+~?qrsdE83_6{=uDg^;(T{>xAA~3pUvw%qErcmsmCG zvE6CcpgUAhW#|bt-zd-~^-O)RelPOleC9-IG|=`;5Fy%>7yqJmHBRZnOEhmQjM_8} z{vd`wa#!>KymSg8BiZY1PLm4W{b&K|cc`v=1*)Ts{Ay^nA1Fr|ngeMY@Lk%k-%=z*WT_ z0(Mmb==LPcVy3KU|+4Azr3q88Lj~M^jueCgr&)VMxks7N2Sy)zqOR%5BAyK zZ)xB0{4`&Cv!I{!k2VEKX5mqmR~AimC47v433-qXd!4=-6}Iy6ycj9t-U7xv%Yp_0 zvQqv>R&ioO>zc!Mkx!eqtHiU+1K8znxwe7DiqgUn*9z^~OW*86sRW$F7PmC3w&wQJ z%HMu|ztG$`|1C}_56>v48qcT(&>Sp(u;7w92DE5*8r!ESAHqF3YkD^B4Dfvg9*%}t z)w#A7P9ON~Ko&*U>xgOY$X+}=Wcu4e;XJ~tP66a3TPOx$%(SHK(2^)iQXP?1|D$~N ztBcz22;4V#Kg#p78s+XB=^esR?$+CV5Zlp1c285vJX+tYT7KQ;q5wni+GnWM;fzv) zw!^-bh)$uNMS>X>IqkJ9lj0u{Xz72v;KxJr;#+!!klxFSb3|7^{%^nzf-+Klsn|k@ z@t8aNAbr;4LwRnT$a~epc_3OgjFBnqv9qr+4tM->5?} z+Old={Jj|V0FaFDSUluK4Ms)~sE>>^Id4yw+B*LV5D>8$2;K*u>|Ons%*Qj5GY)6{ zANMA^;KMp&mUr8wSN0C)yH_Nxep4SEPiH_B554%+n63G)s0IN%-TuTXQ+dWS$rk{#JDJZt4{UvuSts_<6ml9p8OK zv*$H3vN)I)k`fc4TMh(okGDqI3vb79c`Il3hJ)WXbT7pz1?B!#nbveXdUu+9`Jzmn zr*7Tq!*5VK!j@!cPY{9}2*1H3^kul1F)a>B(!x3Te#!2y%jp`{0)xgpLXgCUjBH_& z+M?&UU-l_gR<^tJ0meNYD2=3BggZNlQ{AfqZ^0S!^QsJu=8K>% zo(65C#Uia!bvmVZOXvz^a5<0+UDueL^|wIuU70@)Adl~iM5Isi>;4}Ts%?9EGxec$ z>wmNXF+z9N@)O5~zt4|n*zL0k1+nvNX*GyrNPl_WrT*{RHLc zB&R!byaF!geJ)#ZOP6P<^?S`q8Y8c5K#ZCE-xC}U)sn1VxBp`qBTKUm1w`I_(0CX5 zL1bSM#}6zJ*DpE3O<^g07ZU-YvDNl7qGD;A(>@btOZj-QSb-zVd7I&5%S}X=k*60@ z`JoORFu|Cq^WvXPoAkAru9yKn32%$uL)-R&wK(HIY;MNi-gxiRSB<(H{rTu5aRXqj zpjmc{PdkOJaE8Y}ZO4bM$}0fB$@F`iQXe<~#1X1O`$u(6x*md>Peg^*TiDa*X{e)E zNez?b@mXzRIG64`=<=BMZe2EBTt`-KBwq$CvT*-E^5o=q?RbLAReC+i`t>yC#}ui*3f8-K0}%&{blCw0ESOcdfqCsi`zjI*5>tn$8Sv zIq#HKo)ttD-;VojKG8n@1P3_(wJQ|j<#VL1N1bLy%HqdM0SwLC&r;W{80>yuz+P4N z=i`u!nDyX2Q|NB*&!wFh~ENPYN^SL2F-H{`EuD;bHukXau8t1YmT9WI+E zPeAXtQ-m`T(lYQ!smQ4*a!@oo3+|BB4wA(Lc>Ro_KcoADTL6nv7VbpB#6rtI~B(%ea`=9)e4>9Nbe2S zAwV6Ji%*_xXO8QkX=b%mtlEtWsPx>$FQ)!AAm zt5q${lqfSFH^5H(bDE{eerR*u))%^0{DN&9>`k`p_cY;P8`Bg%=1-H41az@COzxr` z^6Jihfsk=$S)|Bjzno>H`~4gZK^l+iv5Jwic(nX=idpIc(Ia2|s~@Q`nWtSt77iPn zoz8=nJFY2F%F8#4dk;8Ys)bOM2ci4{i$IBx7MZ_);D@(p^pjcb;z19o_#MeE7h-5* zE|lm4roBAm1JMc5YhN*DMkxL@H<}g~B=eU`w*1S5^ZeD7c?fi0)>4n;A*%T0oDT8S z&hfWx%y%UBzuQ5@^3`1`L8kDkb~xf}ki&08!=?~){h1}UrV^}@lf=CFs`O7lO`odU z=R&vX@_4=4w>!{n+N-+J%u!A@Br<`D4M@_S;PIF1U|C8OV_F)*PiR!QDnEYw_~FBc zk3?dnWEQRQ%VauZT&Dj@9e;1jq}pEpkD2kp@CW~ovA2$js{7)`MMO$KKtNhRKw>~j z1PLWYN<=_r2C3J2rbOKypGl0ThpW#81Z67{s*U+3I^WUId7`IsK8^9J z=?dtTk!9`8R33R@v&%Hp;FG)wYv-)}HTp7?iM$`9&EBs(r5nYpO_S6sZ$(f3=Rp2h zfv#Q{S$CBxR_oNxZf6@G4?j342#=?v=y-1l00!YZzwvgVAdzpr8b#9%kY^(APeA!n z_#!n)S2@uY5GqOe<2fz&es*H9BzPEXIwOzRsMq!ojz5IaV9 z(TZOiBiRX-_@>Y8;;z1z>_`pJenpP|7+;+d-&`A5>Rlvpy2JW59-?2&!(;DSU+l^t zm!sd2PGKP)yH3^{MJr+uyy~1II5n2g(Xe?MFhWB@5)y0VE$rhBCcM<08OQc|isM~{ z{s5aC{im98=kAF(DYyK~i~yc#5GC^Bg?W5&SHo`>ea25siy(kYEmwwKnt5rn#}}s8 z@lU_QdOto7D2tl-y~`wJvx0|dhh%3>mcqxaaQ8SZ=pp)gehp3oY))P<-;YuR8MH+| z3xF8NMo2l=!wl;{?A6dGL~TM_cy$g^HCD35}Q|3=@W5E zZVi|Rdi318G=))cF1=1XkSljmPW=g5ycY)?Yr4XH^F-=1^H)c+%dCtQ2t~xL$O5r$ z!nY<~^1!bM3J?B_|3mtkxPd9|Ull4tg7+HaBY+h0IvYTzhjeE7{ff8RJ~{{f53Yf| z!7-OhseY9H>1LlH$?sMtPoQx9#roAxgYi#tuqNGR{;1O;FwEVI9XN<1>WfBve`o0M z?e6C}V9)i}h8O@)G`D{3`#m(5&bS->Hv0V^en`gco?is7>NDS>z^;N>hkrhZ01%@$ zx$mM;kdMRV%<8uWetZ$lxY!B6UE=zU(ODC~4_UWo=YWeK>ATE*DpvwO!uBK2fWVD8 zz4DR{<6?l2b@o$Pz)3#^ss?`LusV%#t%b(bE=~qVe_X$$E4~vTtSo1j9*EKyCN3kd zk_6_mIU9{7F(AT{sJKKlGeO|TR9>IQe+dO5o(AoVAAVyPU|)9aG|gWe@j90WGVOIYzgM%9Iz0}WQe zs?D2!H1#il4NGbJwns7=cK>0g8!Cl)Tm)+9G{8Q z0hvbZbxs%8pW)CN;oU9|Rnk#!zFPv;Ge~SPahK`EymPMAxzwd5a{cBsmVMK~Y{l`CmdW$!#xF~qll1?S*vYqdwqH!5 zwst155?|BSeDPPc7}8lGOf*Pw-_!CH&nyXj{ONw#a-~7_>KJqIr?M^GJNk)D* zt%se~y|ZzzL6?jNe()B%pp@+al_mWJaW89QcgUfkG&HSyzSMfmY=YIM14Y*<9wYhx zHI$c(+{jL2>?eZKJy*F7U4PL~>5$m(tp`KXaE? z?A`JSe7UaLFlX2aJkAI`;(-2$PI1Rp9VlV#;=5a z2^g5K&bmt%U2E8ht+nCn!8<16E_>fh5cCaK4&4XKOq7zve;z*Sw)9v~4f)LXGpod6 zh_G7wxMf6kJz#2^gt28D^Jnj>`;{O3HZhN+ql}|lftkEW*?1_;=Lo=4@;U+?1q~l5>lKX*9^L}BP z<>JB0I@$y8dnwzyE;S({G~xM=R#ide?0fWLMsw7U%k9rQeavz(f+@IN2noHc4nJkc zV<#j6#Xg?|CDNM9F^M|q1)Vn!us(R+xN~BzN|kV2KMoedEZiJl7(<9X5P#A>_i<#f z_>9`8NAg%^FMMkt`?@z6KZyH5>O(5Fu@|LH=q+jl5>slLoW3#ipolKAgpP2XV}x}sj|KTi4Ni-l!fo+rz# zrv2!v$Hn6%Z1r3^x^ zAjsqOwR+*^9pqLH@nB-l`Sy>^FY-*I{rTEKI>3b-ia1A9{Fvi8%wk-5dDhm}>+90_ zjFw3wFIG3^mxHRfO-#=F7BG^y7ar+7ce;JWMBH||12S~CGMtZu$C=npfWzj}<-pVd zmxI>$<#L_@cUI6TF%L^T4;Tw6-)p||8Rt`uMb8W1+KMXt%pm(8&jMy9BCh07;Dk37 z(S7HijCyU&*39KF@P|7dsEEW^FG)1=6dH_UKV z+wofJ+*0ymGL50KN(^>;(T@w)Hm?x1dx%NxTQ}>8YsBQ#evh=*w{xP~-z~Koi_WYd z={&(pS|j&r`;%m|h@+yKtFa6K7ruzQo@TTb>ib`B_gv){{FL|D0q zq0wj@Z69r*=YATIc$`$1#t|XEEu=KqL5RZD+MDY-5M3~E0sa2_|K)tYNtMCti45>v zlmqZ0>MDTI9-A8tMBKB5b>*yE>n)*BIE@_Jct0@OQ_2{8!=bjDmuKGRzy} zu*Ku{jI-Nz$;>vphuZkX3hN4VbR?M7RmKL^SPZ6zM~9AXB}LiGoBUn_?>w(*fBtN= z(91*3xHWhE-HxMWMJ4#)C_qa|>9$rU^8ZE0Kb_}ArpXwAMZ*CEQcSFF^N>hc+U2JE zfpq%Ml&ygnpk<`OHQ_)+lArDev0_t&jeGPmoXwBvQ&#Q}8@Sx}CSR`T8v?q|JB1X! z;!I67E*83bo5PeLar%ruq_FNgOq(ouV8Vb=RgbH2MxQVSJ+bupf0<4@=xN)XVY{DY zc=j9^RSXXHpY492iiLg<%l-fA+^+Zfkhc@V8g_rilP?qd2T>W{q^L5%C zDP{MQn?F!FO*+qr12D?3Xbl5GA(>VMHb(z5!$}Z#EnW11KIhlIdnfb6Dg5qq&V5C8 z=T0I;qR_nG7@e2P&=lzc2QvttC3Xj&erFnYe=6{l+fwJDFrSp)=kxJ=j+|_Dzxp^# z;3VqTq8!-BfkOjYa=}Hc+dtqsw{0tlI~QKwJlQ><@Zw_2@;||e@J(yBf;j=8$v7n` zemIsH&b+$V5!w2H_7of+l#sgyE8s{LvBIPK+${lx)O#V7BPVf zb(z`~9LYO{k>Z=Ul6@oG`?Xt*esMojB)A6f`08kSxLgjlv-Wwi@K0w9l$8QZZdK9Q zbI^Pxqv2iQJ05racAH+@{n623oafo6lM_=yY^<1D0K+8H&wojrG8vND7aBZ6yYf4h zWo`$m?uac9l^%INE&j$dkN4u~)7IxdP&>YLIC}5iML!wqda0(So-DN6-at*R{V0`_ z1Lbgv>+l^<{wArn#>+`u!O5NaGtwxZCkswGw6e90<6v5h0X!aE51i8?Xg_Zc=ov`6 z!r|HVbkl30SFEkt9_Z;vdvdDVMIKR9TrJ9TE=DD{FknV@M_{J#lX*A)kOp;G(QRaiIP;xxNaU3q3+k~_09~_ zN1mg?Zc2MhObqi!w!2$d++F;xmhqr#=5dm&YyN?`SMk;Fb+JYIc9ZkkVcT#B-uoEP z2bB80r@6qfuHQJ8T8=ev^kx2#f2Fz~em?yxXPyCeEDN^@E`yYbXJR5AWDy%O`1+~8 zw%%)@!A;p=JDOW*@o!2P_KEu2wU}WQ4-Zd~E#CDwxp;A@D163e7YS-tFmyuaDXVGm+I6Cen<4(QOQ)kq&D%Bzm`K}-Emdn9**7opl)JA^e%;EWEc?=+!nP}`e_2-ls zSI|JZ-MPg;A)6x)6slOguna8-W2kyRV5g&er;Rf%glRfeR8(wF1i8qoJ;`-4RCF_fLd1doExn`mEwCO^a~)T5fpC!Gq^{^gWvls8MhIe zyX1tV*JF*ZAl8>?o}~i9`gf~U@W;TrH2ag+<0vfj>l=v8{Axo9&!&%XsOB1|wyz(p zzHhrr?mQD{UJtwfV?4^OR|7j=G2rDr$2pC~2AK3UqZy}E-DHkW3$<5(V`pdpXsUvL z0S%@dhr>q&SbY0Pp0nh>Q_yMu+UlhRNtX){Wg_k|K@+3Hi)^1}RPr3M#A8m}E?~D=jEe>+HI4p?S zcZvQSFQf*lbAA$b_C|j#D&FB9R-$vWCs)2o0;LsowI?oCS5lg(o{;_EWEHO#nXg*0 zXl2)QmcYIf*W`m}Jfg5LeA<-J0x98=T!~>gJ`_><0gBpur`bIS&4@W54Q=RuUm|I$9fZU_^WKb(dwX*N60Yx(v*FBPQI9{HbL=Jdid((gx^m85ANEBS z%9h$za}xh3Fd)pc$n~R+S-ALI9Fy#aeUXPDtQh&}=n9wurx97=xzOS6_lV;59sGwG zS>j#+A|*zi7{#y967w)y@X~c+inZDtw_sG=80WY*BC;OaSX84nN*~Rh=(yk%0Y97Y zu)Qk|)24ZnLn|>hDc$mr{s6xoV(b8=L_)}+wr>I}ZtQDzYL`6N{F1X=6bmmhrw}g- zHd|SQt^~lhcksE7K3&&>425tk=$ng~%y@3Mu5-vAP?7JTs`thv%gZ}!GgCIa%=W(8 znZrJXMI{h^!rmW$*}@r}(p^2sw)}t-U3Ai9$&YM+uuowYC+AE)V}@o#b{-B=OaiAD zJ#j=an0#oZR%~!!s^8}@y`DHDLq;II#?ao18xvX;h*pF01rM1w#9nqk4~rkQC?vjV zgQCTU?M;cu`b!6C2`~fAWW@TU#a2cRJ34c^z$LqB7W4;;_YY56XAByb2Q%DEmyV+% zWIoWVEAR#P>!c68fqJ>0N+YAStJcHU_;ntgG&&P|+CoLg*w_i>zBg4x)i2{N)fgOu zpmmUEmn2g{$!1@2R~^ge00OP%ji>uWZJa1U-QKlL3aU7+gVcn~!=AH3)mADIA*Ya# z=1No1*WFXxldfk=6bMhnwET_aiWyP!Od|Sfd-BlOXltXbp0bSPP5oO%K^h>bSb2@1J6Q z`2nK;47{jAc?+g{UAH@ab6Cgz=h*sMEtUATo^^IUH|X9yP8#>#XeL?Ms)x0TuR7@A zmM`7bVl@G00K4vHhtA5}q=*%h^FE zAEqh{MbCo@d4v)MNrU+H6kht!#vp!xGTtJp0^TED;kbq(5f_x0nIYS3^+$EW z1fbniu3M_~UiJr@Z*<4+$F3c;cXsB))lL<{bUkG0PV9CJRu)iXWJ~CFsIJTD-cX7* zYsnmunS`@E+Yb=8{x)hcJS*SD4#9CD&%V2(k|JSrCLCeXcEV(G%4B_nerxrRa26E0mak(3d482$=V*#{g*&RCZ(OSwvHeVz8{*L!kJJbOkqG$CBFUJ@wH%s8;Tk#* zp|hVQY)RFXVpF;i^cF^&afD_P)P8@|zlrkk6|8Yz#2(VHGVeVjoOZaIdgn{<6R$7b zTuFBcpiSQ)+r8CI`?M{aCQO<%x8qRwjLUrwoOyP7k|36t(U~;s)L$GUq_xUrI3)DA z_D@F%xq?d-hD@kO`}%zza$#a?J{+C9x8YUGFTx|l9HnFcMyj4B*`${? zl6~D9$r#03eeG4pGd~A%mZ$50yu!0*Mm=3s*EDP3F&!T3DTxqhknDVnv?#DnAdd}dZb+qS()_muxU=0ix6N@OuS!X z51gK1dNk?>=NEi1K=h4LOHIi1wX}<-rkR|rtu3)0+JE-AG>o{(!ZOGQG0RXT{UJ0R zUrR!3l~T?r5U0VpmANqkBKs!HQKkKU%=fz>SX^YArE7`=sQX?nGnz8awQfv65eLe4 zL4=dlL;Jyrgv#Rr@-0*tkOm|vz4-v@xsZgIblnLR)bUyy)X*6%S@K;-@me1-Nk$(G zs49zX9n5?XJz8Y?Afn*>#ov7rRTzE*epck<0IFif*tXmq!sA=Ks=;|mp<*BSbQo7r zJHCv!lQ?FIrU9J!@mVW}EK$mh-OtFx=lop8snHMJkvx=E4g3sM8Mij0DMmadMIpzf zsJ3FqTypDh&5zhxo6)zfhG22U-vfF)Hl+sU^$tYEXV2mBxwOu`5IRb2VsWHI@zVAf z)iknklJ=4K)9!o;lP)vr{`<+(0oks~5Ya}IE#tU86z!6B6aDGk2xKb0ET9~yCng9p zU1pr;jWUUCV4e`-(r0H9YK`y|j*F8yb}2@eO*XQ%(vE~>5iO# zsL4A-t}Bb3uI_Xrpy$)U+_fYpb+57Y7P^uuoz_MHB$;$CHt7Z~8vHqd2DYi8G{i_H z6q$gq_D{kZMxQx#@etw}>DQ_*RbNKpIPv`B>nW z;Bm0KedRZ{;7%52XL)7-l!`1G>JN9KC%5}*#5(q}{9}^ftBG?9$#2j>JKf@5O+hzbra;{hRy^@6&Gl@-&GzPsvD=YMJof(31PEw1(Ir6%TB-9msdtci%l?Xv!4knX)pS!wv z1bwLu!S^k3rsuH+Wr7S$D*sBOzS{Aq!z?(@Ykdse;b_y@bL5vAs;H<49JXCqF}KLZ zR*NL3m^CuV%wl2PRtxz0G>wZe;TC~aq1>}b0Y0d29Rw{Sf6LA`JV5>=%Js<`4Xwnd z6nKtt^~|ErNbKy^t5YeFVBL)$+znO|;4KAkJe)DaW39BG{P*NwK!YDw3wX3QmJf+l zx4_FihxxyJE6>P)!{Hejf$RJ%#2PMgDxO^UWwB-YX>I(ZO=0u;J9HcMNO%+4rvbu? z`y)_-$_A@{M_f}Omp*geXNn87NU|nyVQ0F%3&oWNAUm080L<2(|r5o+C_N zzKRr&VzkJp4C^;&_#jLTCc1d|@;#TSW9Ee&JjM#9tbC>#zzGR{s3F2Unpf&tu`PVQ zCyq}CGZx|+sU0ODeh5ZKXi|F&N0kx7_ggkRwq}RpI``3Drer&Ql{W+_dm|%0m0)Z3 zb(g>BEgQ9C5NGr!yh~S0Z4x0&6J=u*|csuJl`f9Q{1T26(HbpMg}jUcw6j{>)BQEyrQiMAI< zQY$>_Q#M6tT)bPv&z>xILBguJx8yb|wkq~2j!XM?STQkqT%zl|xa|SyGzhPy`o0$} zD$(20IrY071SULYwiaP0LBOtkBJC?DG| zvLysebuT?8KH6&3I``=3(NDhW8yS&n6bQPm6^_`AZoUG)Oeh(OdUQjXo*Upd;5PnFpq^6;6WCU9ZPOK=CcIWD~@ zrJfY)x1R4qI|r)=xvB)xJ=@{jbi4^Gt!oj@A6TauF@`pyGyNDEAx2HUA}X=*`nNtV zNgJ`Txq=w_ql5XbRBtYLBlxfK1H$HU-20Y)M>L@|!tcm62D8gCKwY<7N2vHIXZtn=hD*ZE17&=Xwf`I==c1{j7-gW}%R~~la&iiQ5*-$p&J)$qEYHpac&QxhJ z1Mr=5R~5Aew8&ldyt(pUtIitbJz1^(+R&q>D{tBrb6qo0IwM09sia(T>8+MT(b+lB z+oQs6ryTyi<)+^q$G6l!eY5rU^a~X1zT$(>y7@Ul-d=+~c8$!iGzOE4U08vL-h#N( zM@_Rs^Yn1TT-6F#f?+^i!qF&%d0ZJ)!dTbvX7>G|@G(|e0}qb2D$w9~OUu@c@2Zn< zY_whAh?d9H*L)0hH%+6NXR=RZW(PFL`+U9>9a+@Mztx6c!j;0F&y0(x9+M&|3N?Tt zu_drFw_b~N>$i~oTJGs?Ag8)r3-@)XK%LmbbyfFOor=Cg*a4o2Tf~%Mz;yP% zX`!Z}L2BO{kYhBwxvzq6%`$(qwI^M@i+r7w+(Gb?w4>32b;xy#li4|k=xSI@%?tnP zr)^XC9)Uf?6wO72B}(sq2hB3N+Px_seIW4nIXUbcKoWb&ESw;;6V@;$EW>?=XQ(bg zrgB-iH5jXt01wo;{Hk`qbi*7UEzHsa;%+I(Rc6~W%9&|6d2b{xYkl=M9MBxq5m{vV zN50e`q~f>v@%c&A;Cq)=>5f4$b95;8gqL5i=O$a*^s7Cz&BLoUZw0&3-y%dF>+TEN z0^8V1o`#%+zafn$iCV5o`i(I$s~TbUJ=T{u?#^d_V`iY>83H^B(|m4h@5&)08nY~o zc65N!gbmAOsnO9H`M!)qS+{OvtJ#(-{Pe@#oz3WHz#7!Y*+Og&Rl=4lswdC%sG}?` zH}ZwL&XdljWxn_&+ZGrMq!=m*TcBm#iAhE3II5_)R=zX`?Xx1A^@>)a=gBqKm1~ck@t&Spb8HQLx_cewj!B4#=Nl!aglvM1zzy8XPTihY67X**>q4uT>FadBd@}mz5Em>nhHIBKi&1UrY*)s zw7eYCdV5#u6ADn@=SaZ2kj*cc1w-OX6;7+Pos#=Yzqo$0x$%#n2O#!w2;n${c@F!t z;Ozq^ebjA1->)2e90?#tGBPp>3JNl^BL1{j<9#I=vT7a=l6Sz>eE+afhA{4o6%KdF zsmUy=O1~q>AsU6CQMi-OpTFJ4acwRN0n5$4a^)Q+CbuPSa5&j{&oZdU; zTx*m3<0bO(xL7!}6tFDArcV+TsN{QDZWr3FHbvKO`)ZB|g;?st9P&W>%l#EV%g(qi zq?`QIU#;i^O}Qq1JTa1X8KL}ERg>gs$>`-RmCW7I+L~_I!-V)0IIJ*A0{8YnBt={} zm$&?XBOzElM01IaKRU=*^nO~}0sXsd4uMbx;qS$v5OS+Sr6Hkz+mK=kukHAv(==@P zU50OY9B}O3))GR_z{tqU4tDEh<_n3TJG8*D64FHGcqQrlU%cJgLVa9*$BZQu_f2lA z>iI{#uN=v=MjcAt1BBlEDGJk)+~WU?_0T29+nM9NGY8AFRTe^A0s`jcw8Q$?+u*Nt zN~0toT=@Gt)qVDxQg0D&9WHtKg2;|VydarLb7{hQU&g_icp~t0peYPRB5Up&{6((H z?lTJaDimYs5-4*Je&DK%H5NiV#$`ORd!aO{7cqCmHID=7oD(P23F zgHEJ;j=naIy%GeTVz~IFKBeyK8UH*Rp0pB(P#UUMgQ%$}R7EA;q$VTIBrE%w1%y-- z+ADZu*wLm7kB@W|uVejVdMWulE;&t`>Nob(Z;Ic~5m%+?rg-7vYszYlWyWfLm{WC)H{!8LO8l~3tI7k|Qp*w-kZJyuO(ooSzlN&uf zKfPW=qdXk}LyaHx0 z|A<9+@1$q1D_;Yo=|0aG%`)&|GMic?pmb0%V=SyK*!=NlBZFw>^-ngLsK6_KRF!1z zDN?b9$cf?7RHzn6;ey7EE=LDBQRd!2FKI@5J#Mr8tNSGI*`Dskq}QRQd|9b;{PRds zDywAuFIsN$B4jmdoOpi(=*WJ!R4aaT8r1=of&`QI*jS((YO0#raTV8iy)92Z0o?tz zJtbQ4)9$PfCze1kYDsL>%gI25XbuH%`(?NCBc(6s=`n=m@xLCY^g5glwCL8Xe-GoM z0BM!IMvwCucPEM&^&RkVDz*ae2t?J5?J;BhWsdQz)L_OD>1fdl2c?0)3Qy}{y|ny_ ztucrfq)EshYuEqM{Oq81q8n|K*9LwGvHx0{shB)0R}Vuq3rDkbC=DTTG1ne(H$WZ> z|MGfSZ#QZu9Clm?QSpx!LPn$@sw_@nw1Z-~fj$sU#jo7Zy%$QR_Y%xvYKyKkV8uEpd;r7|BZ>Ct_|fCG8%BKO2v-SrtGF2Y$L zHA(mR-^t;BFhqYo^Nyp-&I}gU2o@Qp>V1$~URV=to^$_$F)rK&iRQld7r~fLb{CZC zDqQ0%ECA2zxGhC)*-JCkBdiU%Kuwu4qZx`@f0Q=dK8f38}&!<6TtFedAavExIT|Q zlM6A?l=$}xl_wirCMfPVqsEq{sE5Wd)O@omi6sF$H;BD8Lry{wQWD#P=4Sm%y6D+P z0tHm0+ZV26f{N!gy9#Rg%vL|&>{mfWZV9+jL!)#&E3?Hh3_T&Q_AaOROBD*GD?ihYW$e!!5*QkvAt*YCjp z4^8eUNX&^^W^`EH2U)~~z1apRDKS^AEjOw~)rWI9O&-ExHF6BUq-LrZE$k7az_Zs@ zd$#ian0uo*gS1%v(OOttu0VknLfuYZ1h=%6R9;ucGY#a7-+B>;rR=F6o{CY~obG0Q zpsUIridIsUvcFNPCts}ot*}5Ei8Inp?m>1K_%DJO8t9Ic<6M`0Q_dFpW%F#Umkh0> z?v+vsJ_HAo)mpGgY*+QMqUd#GFU?eJ1$I{VKL6~;xgmQy*)NUFy(BMZBH2;Rq}ij!L4> zuR*fvAm6dyB3{-bs>$ev4vOELv#6>IPkJ5qfjC?XQ4aP@66n5f=FfeNK`KYfZ#cHDrSx0*woniH)w0G5$w^++f@2VdlsFLWxl0b49L!XZl283bPrnNkxVA2; zb<-X7LQqFuWAu=j@qiwHLJd@qN)s4Jq+$SmeNzP*M-DU`jCjFkXZl^BEYJ4H59GBS zRC;zasJa3u%2^+jbTan$aWm*rV7&vCjoQn>0l`x4@&)9Eayk6$%~0`*HryfJ%4}%> z{UAisV$=2&a`M?E==*O!b&!itxJWk|El@-ehTv-)`~%8hnF2bMVS zLXbxCthJ@UlED265HGopKtxoYUd{8FE$K+~o-TSaC>c;TYCN&IPp_2DPL_iRsO$x+yK1Le&5 zbS0?Tuo}1S4A#B?o7X_Ui+{%JM^AHTajM=Qm4w7yYl3LWxmJeAB#1@a1yWIAh4 ztIuu6z~?(7Uxpqc9PCB{p6^8~as8PMdQi$~yo~9fK>4T7-+FX>Xse|EY+H;mf_4)uk&YIGpJ@G899TUugEMr7aucz3uom*@cAQKi__D} z7;DX5S$Wnh86UEY`Dmqr=qrDPV?;(l{ef||y_1`rZ79Omiit&Md$)#kwe9IjKYBEnOJX(pY z3zXCmiW9}j$dB>cr%xTD`c+% z0voe*sEDJ#5bLE}TKw>L$V;vHj~tx!k(?dCd)ZW@#RYJf!9e{ZvBk=F%_(B;KiX@k zh$}3YB4e1q!cB2tz!t7v204$MkqonVxb{!U$)h9HUIr!mTG1vgh!*4wUD)hlv231B zbGlCqoRIW-EeWVU~LNgtX=Cei34|wWi_yA?dpmkmt0aab8^U(_!gY z=?sBRUpPCquppz9e3ro_O6$o(%NI2lhaVe!?tLuDH#A_@pVb}9W59BFzSM6vSJ`^A zln6QqxE4DL%?k!wu3k>+O!TsV=F`q71GCROxgL%8jPvBAv{kc|5*}DB53rRGrCNhZ z9PCIhjkMBrfw!Aqs<-Tnnu({8woXj8iG$%r>W@r9(Ual`-C`6a4^Qd-A zoNzKz6cHF|?KNl)TcPP*Oz%sp3~_@>1w<>cUiqWqYR9UJZ;lGfgwP1McElDMw1nyP znM0{j-ZDOPf1&{t+D?faOl^8=JKotkPtR(NmPsb$ZpiH?=>k9QX67qcO$h ziqs;7@=~a1C@63;`3VQhwD63vLaT5W_J@CI1(3gOE(%3lwS;wiMwMzV0{cGmMI&PtSbCFfR!> zcSTxqrE;-znj@YF;ET9_*d2rD#|3{2t_U6r_UK9Z54r0g zYpzVLd9L@K@g?-a$jM;s%W{`nGRfa>ni~AB+4Lv;S>X7pZ#*7|$CZkykj1 zylA~H=d}ba^quP86b#lMu7UqrWBysYVjwP~7vLjfmp<$}#aw$-{|lpYDyP3$Gv59aOAU>oF9Jng1eKSNiUEx)bjDY7 z{y_2Pi^z~E*p-qG;+}s8=Bq$-EI8I_{Xwa#Xx@fuwH5cryd8rSe`t!PY=E57yeo6P zoMXU4UtEH7vTLt54hsJB&=g1!{EkLZ`@RPEUmlHP5p>k`xn!n(3HZ}?fB7-W^mPl? zw0Mha%tdV1c1G)Q^OD4jD#s8}O$>~2h~qf;iExKj^(OlU@3of7YB>pAV(f z?re@~y1dJ*-~U*u#-Y*GaRVoSx_oMod*9Pu>2a;+IAjm=Yl+0Y)IlUywZqxKt;4z}^Zx~u2>}mXH zM+$GBkY0^Qvk*7Y6)&4P1<`wU&N|Fa*!RQx_J`pwL2D7Zq}Gr@@%uIpcThby*YjqvvU6PIOJ^K(X6K)_;iWo&64 z{cU18rA9ImEmsbL1wt?PjqGyO%nset%I>eswbeP#IAToD73)>XZ5t(SJI&bKAc2kZ z?U_>QOZuF))dwX9Bss2M+uVOAQpPCqB44G&{EmusiE;ZaCI6$(#B+q5zx*3)qA+HuF(ez&fa0g;E|?Zamo8`}FV zpx6J9w+i=vde`1Q+m!y{tP06-&)rGXw|b%}3NKlK2R@!!4%m+ESsG;517WYG9BYr_ zjmfx`r<>Mr2O!7$j%#HWTJ2X<4$xgyg9^E1WMkV2(121gT@B!_da|Yt0dvw$VKJT; z{VLDL8%iRCAjRWN&k%n}`bj zhoGzN>rCj;nz!a0t5-?fs!@~=Rz#S}o^qksT2v+p+!y%XfD#YRUOM(kS= zm9{nwn!7}ks$}wUGSzS^^8Pejby@l@1lI~i%<4j4#2>UqLc%rZ7MF#c9arCe{t=S! z!NGAtm#la9p>0z!?ZC5OaaMY<=kghmZ>Y7~Dp=mFu(Pm4tJ>4MIB&F0ehj)Dl_ z@=(nd+73Wcsw)xxJL_?pW@g_8wA1a}K=!7b2ea5`8BlHd?LK|`=$!6mr61$TFsru!};``>$?{hxQ{D{ z`1Wn|H1pLJR1Cb83&n+n&0DI}PGHK&(e^vR&O>7n18a@tL=FLtiJXFNxdZs#H19&P zw&R@}<1y9VDU6qkwu z684mX-i0k$y1p|SeG{2j31wG7{TbpJ(Ssv9>lb7e3&z`TqXfy%jg8hVC4c%<8Ct)w zI}1^__JYoP26GE*CqK#kNDissG>B-Fd03sIV{VInO%vo{1K&J%TS{*^U^{wZ10QDg zDNTdAu5qsr-7+t9YS?txc<6EMl<}Q^BEL63pPF*^7A_%v{BFS$w{D^-z=bkyTnL!3 zllv$Yzbz>6h-R@#Xl4d14}677LRDbL;%-+9qq1PZR1Ayr8B@ z2h_M>oE~(nLV);u&yz$^z3_mn>xl&mHC&~=_V_{G(m0qYUhAS*ZN6S1J z89q5uWr|q-ToWvMS3j~vmL7x2M}lm(nl7+KQpTMpI{d6a@!pprCDYl-vzF!QupwsH z6JbF&C8w7f8U!kumxnvcM_9YHs}{uLdQQE>P2~Y*+*Zz%+s>N>Ig`;6M8;7X68(mJ zlEA`j$EL^r*yjvDu709N_gb=Ha&+`$KVslxxT`CaiE8d_~*)$FZZY55{RsBK0jOttwV=sx zSh9M!$@~Ry?t=sCwRbezw8CP#i+e8?j#aI2N`X|?{b_M0w+7;v?8q*{6AX9f@xUIY z+Z6+0^JXJLer08yBf7fxacqG@6iz7#d03lPqZvoL??p+?88#-XcGSgomC>;#Jgqy~=KWl-y}fxdhwZRa<78=I z$Eq@ZzGi6#t$h@|`l*a_qZKjx>)VX5w4Bia`T=~_%NI?$b${3ipa>Y(?A|87qwY?x z{>)4Rm_m$;HuknoQJ+?MlC-&k(OrctGYhsX?K-$tzQj{O^zJ%Gtdk+a?9b`CxfQGy zA)VOrS+gE?4~8`q`zqh+i|dgaDksKEji{gL9+rbydli+2<)yOJC*4;QEK)gQu6wrK zbvtR5!U;s!{M83?hTbq!&UWgSq{%D&&glq%M}>z*N6Qat+pBBBrz6DD3r5PCNU+Y1 zyGP}JboYq#lRUI zq?cM-~7<7H?%keY%<&Ri2gF$YBqgA!_J_Y?KCQB zyDGQv!>eiMgDdgCw0s8acP5S`Zr2sdA#Y8z#N%q7lUyt(6hqfk+*KUVz^zTd)HAQk z4ZH||F}0v(n&b7P0CnQtd)XGIeO~_S#%H@3Qdo03+FDOK``NT$Xzup#85TY~^QdjK ze&@kDjU{*MF9Ggbva94q#8TiD1<%}|>6JROtmUs4y=N}uLX#(p$AY`O9x{V|73)e> z4$5R=ZlUnT7Y4#gE&tj?*?CO!E4&0$^86}vyS9CI7bS=;%AT2;EAQg0Y(JcEwfo00 zfQ+|q?=*-!CA&UsGmm>rL)A&*39o38ohxKs$x~|5?a?t-dFWZ~U%|wB_HOrB2H?kA|mO(Oq&3U=FTae`hwMTXuJry!rQp?v*?9 z*q;iyntc}*zRtX7tgDi^%De}w3!tbN*RaJZ!CvxOD5=2s| zWbsBP1i%Xam|{92FYi!GMjkfEc^yip!b<~z_aS*FyWVAu_QRh~jXk`Lrdrv^Ox;^3 zsXCTsHm0vO{%r*)NDoeu&HA@a4$36B2zMq ziiLf97B=B>|Dm?WAh5o`HChQvh@<{hbKS`ci$a1$2XGefO+eLq6G}H*=6)_avl#^S?p})SMjvg! zW@tBiUSULbK+G(B)17qc=5T;-w_R^Pxj$1E+glqeQLFATgRmJ9#4ju7;-unGt_K|{ zB-7{6C!Z6_p&H&SKr#mhdo)?JANFcLRC{k&yY2nCHa=U8ZzW!_-o0D2S;up3lD)oc zU{JSe^4e19qcm=txWxI+;ji_LH{s1dkqBBPv8KVx3;g)-u(g$d7TpWnqAH{2*O+&h zdS}x|iOiN*uXdQzwuSB8_TFKoR(WfSef?MvRtwBS7u1*|>dxL<(9mc;EVmhFC6(`9 zPN=lZ{$lt2@`PvFUY$O#wxd5U!L`{$x8d@1e_^lQYi)3A4FhK-^o4hx_sYq*A>?HW zSv_MEu$At|lCCdF6_)Aj3^b3DVI^oX#@@`pLefu_?>oAP?Nrk6c;rPaMn`Q5EBAYf z@xSY|=Y(Ml?mor)wq>@JP)mfoCHN_nS;R1}=D9%PQJ27~q5VZ7x27IDxTVn#G5YI7 ze5(%8aGY4;fZ-pWrt87X`$&+A>9rd(U#tgnyqnycJaM+3`N?}soj9aiWs-sERne4% zFS3cM6|~C8<>kiJ0!Q>NZl_}f`L1$>maq4B4eqr!v#=T@RL5Hx!4YZIz)8|9L&tNdRfHj^=5ZczV?oGEd*y(=DVFW>xEVZ zl!rah{=cxTztFm!+ZpLB14#xyzw2qM=H|Ptk$f>?s)?* zSR#=|90NR%+?*_)4n_y338tqM&~~9aIbu4x5e1Fv=`h44<(;`~{D1JK1TGdglF$Fd(6bn-d^?*e*sE8E>YRh9`ys1>!K9Hk)XNx|JD!(F zHS=EY`wUhwow2mGzS%_!Ny!VBMC@6_Ltt%klfi5W9eVjIIXsxvxA3ncrtivz-um5H z@-oTz$__03kztPf)*H(tdk3@3eE+-7L{<*gi8`ms4COn1;D9;u9AKa1FUVkFQRzTp z^@$4pif$I8NEo<7bhoRdI<&cu)_jZ#?uft*>@ta7I&Q8gVJX18h}5OB$35dR;1&e7 ztb5c2rXW-9O90CdzT^V72+Y_AcQQJYG7XpEGPkQzSUy8dWd9y264AsWmW9tI{aop` zrsn5Xr!gjvoqg8pS0=WxVfE${p>23}7kykCGQTr|t1M3G5wT5o9I^xmP^>+R#l1g# zD#w10-$srTM>!Ni-O|<;1?L`#eE}mgVpm&^wbG%&^(E=%QMKW)Esm~$^8BG{F>tUr zB^uyn5n+9%1%7_HG-8H*oS%^-aswp{7_0lspcK&r4jLGNRiUr-UlcIQ=zSvUiHKLp zdT(w@vO%2igC;z>c#tEZ!JdfA>$o}53+=JyniABGUrFO>uy?=7Pm4PqPR>Z`-F~RAP3fl`-PlwB8v-vk`DK&5M!$Jp ztUqJz_iSWVh5X{;DyNiqwggk;A?~b^=DG*2^>FjU8Le-~b*Mc?E6h6GQs@>NJj<>b zV~1d1v&+wZX+YN;*fA7npB0dV#zhNt-e6x5PuwwACvZ}aAVTp^*u%3kxEN{{=*GO5 z`BYvBEHZPulgxDEKN%HT*%2Y3NzA+xc(_FG{d2JMW`JA`UWPus9(Q9v>9Rj$jz$^&Z2L}PGro&1T61r0<09p5QvZC{ZLn5%MXDIq@6cQc ztm2T+vw}@ng_?}0uv;Q&lFfW|=9^LP_W(?={nYQ8+v=j?_oBHMn=JZz*AMcI+f)SZ z%4xjw8zfx{D{V|BXw;ikxq^Wj#(oQ+UuVV{FTqTZKkJ}Z;z;}6WD(n^Dr7%-cYW=T zRbt+Ctu0x3TW)Wc9)$Dh6F#@c@Z@WC?{gj5rMc?l&-!gO6S|329^;`$DPk(!Ap<@~k_UC*sYV*2!-lW`x99nADx#FeB`fPYQk8M%TvJW4~4;yq8gWv*!m zLw`*gE!>~wXMIP>(?2cGZQ}3?phgY|+M7L8Znm5J+}tr*ds`9VS77{qJ$3ZIX-4Y5 z$pSle{(q+g#oUT?cGPbUz;a3PwArQ@gm)-d9?V@U6c{-E*csTzl21fTuqE4D+UOgB zJ(HaP4m5Ty_%b%PQpj^+Ha$PwdGGjS?-np}T$sGMcd!)9*ldJNmw;?ULmJ?AcIz?# zqX}k=Eg2zdnb>)65jZ=0fd$_5{iba20#J5^LSFuhTlm|j7-I9chzOSJqOGtKEjZn} zu{L^d3OOu%WNaR#Fx^Y-I0LfciBgmx#bas{WsIje2^h17A|3|>*%bSrG9P74t(!UVsXb;tE4yMpWyNFH2GP?+j+tb68|+G4kT(}lg>zu~e3 z;PQh%Ht0;7oOV_qlJfmiNnq<_I1p3L3){&P5e77J<*)6fbP&qR_*Ovi-R`(y@ML2E zi9ez-o4piOhB(nTm_k0lWVzsL61BE|9`J0fSvLWT=5;D^zvgfNx12W8fWV8GseO}V>Ep!;>SJ*V1Wn` zS0Xz)h?YuGITkaj;T31=llNV^>N%RoP zMjg~}Mx9D7^4-lURV%~*~bqd;*MQNxN63-sq0S-a{8Z8L0 z_OD&Ucuik%hUkKHh9YN}xmyVXLB~%y!R&FLZClu91&9h#7Fa|?@yvl%wwphVG4eh$ zEen;!7h|U-^2a__DB(aqdCl@rT|v&@vanjDixc|#6-b__QGYs>vrM!jPTPH==wuCF z4BG_z!5v$|5Dx0gYw-j*f1r^Yf4I(JC7;>xaE8y}ZgAkW7ooMKh~!IEymN+ChfkP9 zgNEEuti?^bu-iASWq;^q+{HdC6euBX-Fp`|TD}9Oi8$AOs2IX6|H1Yfb%!Ut3h^!Q z1t!9K&R16eUCX^x%W55xzyqBsDzTRgtQzTk- zRr11XYYCBjfcQNr&li(DJdSq{qx!K2>R)& zy(SDa2E4apRt&+uDPRDBP`=rl^VUa}hTDK_*Z`cpz_V_Scqhh>jlfIfcYa03|NAB( zy^z_yN=FbSX^M68++!GGD!$1nefH-8yr;I3of9or0i|F zPzPR9k_1gk-+U|Kni^H)km|XCr5`AGAeZ*jpO0qjUVc&-UXZ2f*te#g!ew zCTeER=(<3);*#iW&&=GMf6;yV>%Z8q|1#+MH~jn-{1HIoM(`UEp1Iqb-SBPk1brFCU?soxAqRF$ZYD}7#z(@ zp$2RIgHO%(@IKD71k~4IZ!sr4&hw+g$sie1L{T*mN9Rft`>0{)^7yN;B+QG)f;XHD zzt+_ckG>!aw=1(JD5??$mFW7dWMLviMnFpmuys7WPF8mNG*KQ0$Z5_O^p*@!IuDE!hg6>U ziWp-}+!ioj+x}GCw)^ce_<+*p@oX_&P4~OQ@KUa_pm6ssT*#n~@zZ%ZLKFWyy2p3F zzG}LV;HG>28SQa)JyLjvT;b_RR#~jiHkX;^TgSu&U6NE&YnBo1yyPxWzWf6|U)xUW zMgH7uX+0BL)f^5STHqU|C|2(mzQU)CEFhawGWK=pLf%iAZ$J=Kc-eSqc{WIHvGu?y z@kp&$Gjp*~b7Z!!BL`(9`oRgFHb&{jc*x>I4W>YCl}&VS(cD@BAFHg-uZn{xV2I2o zrViTddHp^nijYS(psld$PFviTRCBoSr9o=xhlrzlwCRr7a}i=8M@ZMAPr>Y3NT+LB64zkZ@HJM{?b5e5K=C)M{0)D+T)v=e-0#nkpDyF{|QL)-Sl^V14+#}C0tSL*~#^0VZt=8eKzcS@l$g( z;l$j7_l<`d{W&5(J&{Kcg(E#wSJ8<C}FMel<$`H#`m$wHUTB_J#j2H ze<@xY%q2fZM%?BA^i&cnkiE(T3u@MVW10sd`Vnt^(aaY%-#Dz5N>_N{NaiIFjA=9B zA3e1ybdr^|iD?y}l_vuBJM_FrPw}Vag?~cAOcAO+cDA^RMj-A92+v44;T#rqZXEBF zo_3epixyZ8dl0GSIH5yV!7k8t`KnB}NG55TCg?z}6KxmePT5ha@J7!7<$P<*P~S7s z?zalQ`-dNxR=%k7?KD^34ldTf6Q6O4jNmV>dg%Fu!WvsKwyr$gH(SR)fV5Wj_wD9f~ zVff-Up66tIO*(z0@vHcFjq&qg|1r@r{($=lJlB#MVUpR7nig4%gE;|RvgQ^9pF=nu z?>DAVs-X-w`{{ch-toN5&}THKUv{mnK-gyQI^#H9Ol9<~?NRA-|31vNPdRb%?!aka zRwp>X+TYy!oNSw{OoL=ds_n8fC66m5;X4YU6rrcd%bd6JADN$1mZi6iBulr%rcs7| zo-CBez(e(EzN8?dx;B=EQ^U)@`F;dxy>(^0KobyppjqJn)-qg763`vE!xiFJ9-%&{_;{>^@{1^Ot&a#LKU#MepxgYt*>03b?7BO;jv&2Q8M!gnk=EZ^D zHmab&fcrZwoFDk@Uls_g%|1o*a9yn>D&N7yUX1J9R~a?w;b5+EA@*4kWTtX4F(clO z9!=+aX=&%Bz7>0bH7=4^q(%B45&MU?-@pPN565qG{sA<9L+l?|^ap(We;Kj){vQi# z`Tj##%lFr?mhXQlto=d$50(FCi2WbK+Bz*=@B?nVmz;={BKxRpxBXgZWz^fxk@X(t$Ubj55tD{fDG`wk2E%wl80PAKZ3(*S@0zY`n7tSU@MH=@ImBGV*Z=z`vbw$drj zPWLHv$Uu8jtW2`wVTvV#j9hlS#p0>?%cS8!hR7)Lo|Ir&l*niKwn4#@mtJRfXYDN_ z?h&37%VeZt`v4M$MH*Pj37xeye={>GqIo2<_0fL1nubK+bx(SpSc`%zkzK}p4`lk} zyBzi=5<_E*JbsyIv&dmG=vy2+iXZVZLvi8;F2V%{2EMQ9rU_NT^MZH+atxPKJcp%G z1{Ui3KrJcc=cM(01}0M5$tfi!8u?zmA((|M^|TizrvWG55v;=}C_TG;a&%%HcTR-` zjKU{~m?xZK&|^{;mfE-NSJa{RE5j*7q>f54VJv%I-$ZsE+CLN(z^Rjor7QsN_bHj$ zygo`=cy8O0V<7V+3^f;NRU=NkE?8POFN&n&HKtrT->c^+{$KUnpRiP}2T2p2?D@q% zTO^*(Nh?Tj%*cT@->UgWGd261d0YQ$=j`*j;;o){(=T&z7^3j6nTQrt2R>WpXn%}* zTXz~s5EAt@HmUd0%b)-Apcads1j_>X`^QM~4H%7NOCG5C$Ls8rs)LsJw+xf-qTsD>#o25BNQ}#N+&+|FC zAM0?mF;oFU^ia&7@<+ZP1xLESjP^!7WqD<lynQ?M(ckm*U$DVw_3Vnbq zdtu|zE#95A{Wa`Q-cc+42TFMdVqR|#trb1Jdc3?z^IUwl2v6EAy}MwxxHx>cfudc? zOJN$8#pe7piIUw(PXYoW2zI%jxfH)JY`*gR1P85ax2P(h@jA{X&Xys*Yvr%Nn16P% zdK~+FUkdqdy2&a1bLaPRt(!b~6^=|-7$v0y-$XH*C>`=Q8lkcaKJMDL2B^-;`$&q! zmnVt*X{K7d$W{bBR0&RHpYn$gqqXg?2emS}sUyAKgWw1@H()#|h$9;$Md0+Jq(ie{WbrImot^L|tuVTfK96s)~$8$Lb(kh#mBpNSQD&lCwM+vl@nk0PHjT zhpzDV;Dv(=NY(rrsr-QOzx9P2>^Fh=Z)8?;F?Rlu9Ql!|0Wv8-(!QbrPlfCvI1 z0KXvk6i5t&d6e!0AAtE3lA>Kl|ef!oeptLt| zA9M@n_B~1tQDj^dBNQqJJWk)pkEqm7i@)Kk4(`)%89Vx+p%dIEBqFAzqi0}bdc^&h zhnJ6EOk6@zN?Jx%?U}lUrk1vjiK&^ng{76Xle5c9S2uSL|A4^PL2rUXqM~DBoYo0-`JMLc+Oqo00?ho~R0nkpnIj zr!Okr)5wp--_WSJRQK_X9S6|~XdW%l9^8obL$d#xV1EBolKmmrzvP+(VIUy@jfaE- z5&>OihcJ9V3^^hcrl0A*iY%BJLI%*M;2>m7IH(Q4$-r1|IOqv87)rKkK(a9#~SEQ}l0rmn)}(rbrd z1nOtkpn`_|=W|f&@V{0kTr77HKkI)bfDZ==&rSaql%#SJV%SrEO#XeM=;9Q%G_+py z=szgsuQleY!pzP-!1`Jt{S{|q@e=Mif7Qv~8pYc<{)nv(<-Z%@Pv-o0Oa1Rg_-{}6 zufdU#NXu1+vcUS|WrAmm*VUyUk(Kz|TmWU}{se2E>k1wh^%QKrd#JEqvYcOKaGCMT zujBs$b}$0{KOu>Y5&#|VAO)&>7pXtJy5>(vw_mdNX9M=sf!v^mPS)JfFVu7WE9(83 zpHHV{a1a2i!sT)etG_VmFVrAS-G2sdl^pM$^`BLIuKi~%2^EL{-4oV-D;(%||G^ahcPm_VU=I!|f21#>dV{S3!2FnIo((~k@y*~MJvBH;6yL7i z1P7|^(a>{EeFuu_!Pdc$9?~++kRBzaHJCEN^r@5n)omezCcDXE>w=lzVj76w5Yu`YPeII=)KPA)Wf}6G+Z7zw@TX z7_n5%)_Q~HVKr;#=V_74_o^IQjMETG7&k&qok{PYq3K(xBjk+qVz&hEnD{O@D5Wpf zH|I17JX+M%0ht7gtQ{I&Er^^a3J<%(K_Vq^&>R{Z)H2MFv866pzXFr+mJCmW-DW!? zKTYbtxQ>UNE5Yc(%Vr_{+YM(;kb_5E440!yY*mnm|C0-vmONCcje0=k*K&yK5fZLbNlUNP}E1io=_^JvJ9fW6lV{8=BCR|;!TST`KxMGptz>aC_N zks>MA>gwuCK2AATH#MF1MtF|EU65-{kZ6{tH`f>Oq)xwoW7j*ktH7;Zn^|PP=#UQ2 zK9*_?O`l~l8bI^C8}mE;C-WP-8@ZTrU$g4*r8m^EKP-T$LuuHa2+FK@c{tnjeV2_+ z+DJ6cl@~Esqu_owtpyApCYB?1M^$h?cHD&?+*9$-w#uEBX&vX$8a1;q>^``ws+zBI=t*U};^SIUMO!RW!}B@Vh%wZ3vRfdOSOvF7Ep8XL~2k!Q3PO^F@c$ z?85fW=>=L@+chN|)6D&UT^H^J!$E>nfFs=||51eUBx_=Gy-;m?3=TSKV!N7GIkgm7 z3otxu%sBI&n}LIF376H7>f?9)i}t^#F#zdDZP@cwwotk<{0LB3(-=L~Fr`lsr$uIl%8;UGV99sPHOAuE5b#JmCyO2vW9A%@SKL0qBOmqXhw z&YFyWF#0dv{}UR&H6Y?Tb3L~&wk20QI;fZPy#NmSDeb=}f&$$Sg`MO5O9B6u#4q`! zcCQ+0V5r_!FZrvP8vq}QJhsclv`f@Hmm^^AvU?O}u|a4!FE;2S zDuJg#*K8VT{5Dk*J%m$cn>n?n%JTw=m)S0dA*kU>3d*D}ySC%>egF>2XqkqCzBXTC zttqSJiWpa!9QYU*)Lq$5*9wu_U99XM9~~X<%z5Vn@qz={2Ox3){qxI-jPd}p%|CxC zY0BX`h{jZ?iJD7_-gaW=TzxouQ&?-)M5l~FD%BHW=$n=c>C_l%*y>WcPE5&}voWBr zZ-`bL!IdAjHD{;Pb%|Z2&b_Ea%-e89YVi>c)fkRa%bwJEUwanzDYDN^?;6sNfB*G~ z02pQ3I?=qk--bl5?DXi-3)N@fr(FG{%memisbFb{H2nelEP~28yKxgA@ z%CP0cp3AY>>6xp+Y0-t<_FUIwvRPr8O}3X@$=BjQN?+%(rVs50WJLxs+m ztYWaYt{(LOoVsg1An*Hl zf}~!8WolsZBpf6X^rQBitmoX{#%o3A&x?@H6+YHE+lAB3qCq8;7DaF;-TWn!WCjR4 z3!ZMH_V}o=#h@)$Q6q(oB%Y?R`gis3erA!G z#$5y>AKf-!QZ7&#$;iJK#Qk;1YHGR2Wf0d(_3iUPmU7-0meIE@j*?4$$lE3xLlq}1 zRNc>PY-%QImkLmees2zQ&r^};kC_>HOH428@U9X z?P2;nv;~TfG?TKY8=HWeI;Q?xHyuJXb2g4*V_E_-gj=2(vnd{|5qYnobLI@%r#&^e z>GVm>0(N5gP{t>&h&MCTb-(x9g;zlD!a!%{*x2#oT>JeKVB# zC_+Vo(~5vFRpu@Zg0U6NREeqD*@GAcm37) zYttYakGswLcsm{oh9|{q8oB*!6j|v}B^(4HvJ41T4Efid>Om4iD-G4}tSCcC z0_3Wyc-N(~qwP9AwRV1Lf9VLpJ7(cm0J5-}HZL|Aia&SPRg#Q2_wnOSer0u8FMG=@ z9muj=U0)qcaL4k?9V1bi=m9L9j|WVbV-Q3=L9TYH8FypE9ATqbEV5c8b+%^{=W|U% zvskQL<^zoM2&Pkhe3v0xerwq=mptbz;ZJeaGVs-OoqW)&)@{OZr@zlmi56|J<UTu;DFZwUee5f?lh*mrtD2B20HlEE7Dl$!XIzwKmhPk_sxGE{h5yuJB zCEF-ld5pzfy0muCA>%&mq(h;$M?#r`N;r?Uo_~)6u|_&hLn%ZJr3tT&&ZzXOgEgHHIyC@U;lbq>{a~f;fGQH9ICIGtD_t& zGRvF&-#nj{8g$ZIYc)!{WpEUpC~}RVak5itV2L|LiIDx3!_QlGG08QvXa*)dq`H=+ zlw{LzUA8R9u~1`(vwmPhM#(udH?uNYJEnrZEASK~$&VyI%so{=-ds;zUYU|pIYeW* zvl6iqQh~EJj&g}UaSryxo5d~<`yykDB*w`B`grSvwDP1-pCwIuP!Q~Go`0Y(D;QNJ z{QOOHNS zRZ&bX|&es4-}DcX0adhf1+lfwtABST1gcjH_7 zs&<`kUxs#}GJ+oQZ(`F+)#}F;IuX3d3^h)7&J(vbnR*WoJ7-`9lH8j-_-F#<hqElNW!J${d4SN!w3zZQs;rDFD(;^FJ4Kh8|=WmZJs2XCTnsYk>mxl zDoHH}ML0t*Vca;@Xd~Vt!f9)VkL+D&a#?FGS>T}VH6K5!0Oil@AD*MT;A!5mttp+5 zk>DSHglIv8(pWyZ#3vUFCU+afW633XdK0r!>iD9$;-kh$}sF`zzM2t-S)> z4;y6}(!U7Kr^iM42I7^)pz4?oW)9XmrgxHH<^y~lQ>92CRr#9DUn6$y1K%l9{BVBx zUQZ+Wa~Y(=0tF?-(oq(L`j6lT`>8K0*S)1a<~ghLb=G{<&Epfcpm!pX+iwfcn)Xib z>Qwc5F1Ji3=gSnR&BsQemXAOBIZc{xM&rcBM8@@Z)U z4_8BOWac?oW{8!{^A7hr*ZHoVzPY4(5;lVpj53`wC1#8Yvx?hmY_+%xS6jJucH0=y z9jysrhZs?gZkWCr$A*|z{4|FO**fn{BVTeH=^qLS0|!VLhZNa7uCm+KEz*#S?VK8E z8mXoJXsv1M;+~Vy2Vh$4cOLJj5m-yQ1Y`S?Q<(&VG|C?)Kly{Oj8q3z*XZ(R1G+Mo zqoS*&gn0Y~g}4{0qUGOo_13=X!An#9up<&{WnC@tF7NrnqJoyV4}R1b${5a9nd+B} z)ln~NChW>3haElKlZ&9tdl--bE;xve7D(FUr4Q#99B(lcJX|{5StL>6!Sy{691UTJ zmFbsx+~o54nMQKbiIJR)oU%DB3+>NEXqFAo0`D1F@Q&~uI4Dx!DI8RUDgul{dQHyRVB$M> zV6_x*(4dVj{&DGf!k*XyFr;`E^DAcv_-6kfDMOL}EM?d~PRD4m!g#SJY`6fGI_zI% zroq6tds}Oi=BS*GycvB5sziT0rK~AT`(2W>{0a`5*V=7tgN}OEc*R3WV1PGjGS1?u z==zZE@2r-y?LK*T>xB%Lf4O(Vq;PYNBX8dbgJNyf^M?5cmHGM)Y4|p4e82;(;ug>G z?#VPFRc)g*0AKK6=y#$!P()*6g9BrHN3wapX#VARx}^_!@!$Z7+u5`Urobz`YXM-z zCbL0Zq89oi7@Iqc-1XwRz00_?N5aC|VDV~h4rb`yGU+8;`UG=y`LJ@6T3tfKcN(HI zV2EVNd$D8Yv}tEb-WljTqG>G4FG0H=C73&w%j4eJpfR`VWVr8t4uN^C!aM& z3n=?=5cHxK7#uo54qS!?6}Ll6gKGz%_+|p4?n1S=4Bb+6SyW$^1pO}dl;I%r^E-pD0VpyPPY5M#H0P ziz%2ffaC29u)h8KP&i2Y3=Vn^3`!5WRz82tawkj6(VLYgO|J;fdhSmXNW|Ok!!)an znpee109LPINJ%>dzbXx{&^~F01{%Ua?^GccZg*gi{3}QgFrRzAVt;uz54j>!*ZOfV zm8gAItSN|n`JKJ=&X(pntUKt49X62%{j>=wt-TGTWygRynpFlsaL4#7o<{BVsr<_& z*wZ3_JLcH*@a;+n7uHTlLl?k0&tF;Z{X6TQa<1dCR*kNUHB0!5w&xHm$n8qE!Y|Mp z;GhSYuwTW2Ve~IaKV@98lJ_)U85fxXr)btA0)5q%FpvwugTVOYu9M~;F71qYI{>*N zO<+bocCGaM^$}7AJPSXkWN1$L3sr4XvSAbVI`vw`uI6SK<_Imv zgbaRQA0Kzs~~)-Isz=z{mj(`5N|F!7$cp9p?CiGycN9zn4>7&P^Csy(z(K$41`hgZIBb72EKOz!k(+w&pSBvVNCDqiKhJgnD+Mwm9Dt24#r!tA z-;XnOFas{i*M`7 z?^BY~WtgWah^IE}@#WXd+-ZJDnqRACMMCx@(JdLn8$E4JZ`J?vw*p`Q_cmQRLXXi* z8-&3}oqSKgWAsvqA&V_|Zj?@sYg&QF?QtE6;iny`rs{%ZDz|jGEe(=58_Bs?p}VsU zaZ+_gh<6<(=C6oP8&HAyn%s0aXgo&blEu2=jTcMKmh2(F`~58Y3XU+IuM2}gQk5mB(%dJW7-5{40Lkf=K7PXw|Xed_k z@h)Hf<^i)x3Xx+kx%TUpp_b-JRae8Y1vRU~3u3=}_EF(1^>xpHMmZmO)cLH;l(E@G zZC#22=DdHT{HdsP@KWS(TC3VFi@v*h)T8KVYSnJ@XEK!SCK>ukPoDMBlzaTE!>1F+ zJ)y0JMKTg?QK9Q|>pq&TQL3#?au&BQI`VRRo_|S{;PtLl6;eVv;SLDF!Iyf%&Mc+x zm1OCy)#1LKLEal^F`OD9SKp9xk%k)_yBzWCG_GVuHu6bW&zg0hs*G@>3oOOs*`#9H z<1Dwh(~Y``eXj6a`Y)vYM>I%ViO4~=8M-@fP*PsgybmKdCG(#;;Nzp$A@nip7itjM zx;&gh^NWSJZ{YSF7f?$%&*j=&z91_u=y|bp&^O>+|J=s=q2OJ1+L3%S@E*y2YQxNP z=6Ay)%OqB57t3iK>WP3}A)MJC*CSd8LbM z02IC@0r1~7CK3!4vs&opWd&!XiYJ=+iZh{MIQcnBJy1UEkkIc<%x$eZ4`{IWC zslFJch!}XR3ps6GG|W&N!}(^q1VFDy07cVI^&knMlkFGxrvapc-D-#W0D=5`?@gyu zS_{mHiF`FY=K{m{^vdzzppqr%wooP{(z{&+a5&-Y%MOQB7b)^9)f;?H00sb4Y*6AG zlql2*5&2O#|EBPbcLMAKvuxjzZoEaL6#APo1jaU5Wz7KV4nO*G8Ud3yBUL(e^#$ff zfC(35+aLW<|2{nw_8l@0bbja6M>}^~2&zX{b8KxMY|y3gL^8%cxn1J3i?-xHj$bUx z_kyM{yKN0`d%k|DvUtBCuxNpEB7M4r#L5k9YLAsIb`{>24{5^v0O?O|IL8Oz6JK4J z1kxd(21GN%K<8*&SX~hf>QFMKra9eebpcb>O(48 zN8MFEO=*Wkl|YBRfHokq)@KMs2Sg3Yf`bC+pWJ{mWS1v&m_$Y zpBEA)))1`0*d&FT=NI&*^Utr$q{t%+4@2UuykwV^?nqkqG3@uMDbx(@dWEj*Frs#? zMKq6)1zkEMtMi}f%Qj7^mU-Gt1bnG5`Gzt=);?Ps+A@i8iMby<(QV5}NAw0sWcz^5$QUX%a42ZNabcc$xbmu4tDBT=#q#Hy^azLb{yStkq2N;Im zM$dW9InVd|zQ6b4nm;yk&EEID*1A`I*4psnzzxA8tT^CJY>DtGw{Klo8N1CIrxb@_ zY5g%L6j|xt1}&!P?CW2#r1r2-@5VO6>I5LgmMx~{;rqShU^ZNk3pVT{6S7EXBwt=> z+WGuQRJP$w!nWA0E7uW-=>BpY-PkbCr*ScL?7vdl-QM>wkG!$P?g4J29N<4Hwd@_Jmo)oOsc2tsBKbT@?buX?-P2wO2&lx)s&s~CCJ(P z0%|jLF{@SO%VthO?KUD1VXdz6?#9!e32I^OqHW_l%~abOwEl`la!Sb-GEvk%K5tPp zN{_NxGYT{|QHMySx<*Sdo)+fZ0Ek0(Q0u#(WC~hz2q79{Jna-*?nex2RYG#O~6^Y{&WjLzhnEQ1DszfGfT;qA-t`LtR z<%&H=Ny_>?nU6bban+)Z^5dd}x}s%Busv+cV2;F;s(Ogdm8qK5NeV_dLAbqG#UXT_+sNn=gWMOW~Y zS@;T}fV`@GFsvITd`l%nEy@5v>JVCVG`-r&&gV?cW#N7!xb0zqjr4r`8__0gpNtBR z6!(@TqyqAL6FsXT8;%^;*nAK1oXo0Wxb7#45aVqAGA17zT@*8XkNYmg9l<&^Kz{cUk#-1Nv*<=@q6Ky-K?Bbz7Nlrnx zt<$Nl3!+ZVK(D37WI1=Fyq3CCZs|^WqH=8FW1z;*MzA=OYYE%eEe}7(g*3lvP`JSap>lHc<;j5OEUN>qP<6< zYF6^d8^g0<6`^F>b9#CLs zSvS$uA80p#O`sukjMDdcCVr^Xq3{MWQ19(2QSaw!?HaqH%f7CDW)w&7!C2`99VBKo zw=Y4_kUl|jVadNw~7S@C%T1sZ$by5xySEp+QO zZ_l^y7O`i3Yv8C-`Ch0rA9538bGn=8_w1GrH|k>qKfdgq{Z`EG#|$y#P*q!u-<$AcC^ZUZ>9pDnbf@<~7p;W2a`1Pk$tMq+mY1pv>X9Z_V3mZjLkDTb`i zo&~8nhFJ1(2HMullakFl1CTg3xA>Mm3p&b*;g|l!4E1B|uX^yyLP9X4{tN6fEiN-; zLo1_B7kq^)l}-;kX1o0yBJgqM-!YishE;?wL@%Kn=a;*_k@d^r{?YdNR6PVEU3&?l z?Y&1wF!ClPM%vmy+B1#<>)>IE=`vN=IV|=Pq@pIzaRrY~P@8u}e-wgTI6%&*wbb;h zzVDvic7mNP!497mXV;9hl1!(j&2yfT@*17(2VR1timWuD0FY)$qaUbO(w$SL&Bnd7-2P8BWhx?0|Hs-{Q89z;*B-_K-GaZ21w0%6jX4t?}Z9li1BcSh@3< zWbv!ulak0L{1DPr_;3{$ST+c}9ajD0WucZ!W zWCKBu&?u&wX`fW4McIO1S9ro|Uy?SZ^JY_&c*0v``6bBnVM9X+KijfLs-mzXhjo~X zqC(pfos0UzZmI>4ucQTb)ajBj1J;S$nHjQhHfU7(nBDNnsbc5TA@8^e1&6hdwOt2^>{n`xYhT*hU z5HhGR%_9^(q!KRGIPT%dmJ!?&j%CNAN$Xurnuwb;w}TNoHL`#1zk zzCZoCJCmD;4+#D}c|%S$cQ+rFe(!$rToK~*E95S!XH&|EP&N+vq9%we;d<1MZPn|J zO^l^Io@y2BupfysANUNkG`|kc*ZN0H{_sz6i30_mR*M|Bymm{G+%O{aBg`QvEhbDAZy=}o|&1UDz`P=uyk9+CRul8^n4udxBw+pOeeHe9ORwd8V>H}>e*eD z{ru+Z`|n}ju%m__L^Zdne}rw%4K_VlmTMU}WFv^`M$OF+7;# zxh;8;2l$O2K{b2RNr`NU07{Nmi>f3zyXt;3BW8&{4I9C?c9ac9sT@An!q5vF1pum( zXb7=+2oNo|ptx9O9)b3&;5@s4oUyWQ7OZk1+9GEqQRez~P;d(NOQPiSQI^GCs-`)V z=EAPjNJ-HP+sDxs$D0gxtc${(0&Rw30afMR&)D_kz7ARY6_VR!A_GBBG?k_8XK|{F zoE^>5TrH+#C@FWC+pv+R16q9Z@Oy-gKj;_-Fl|e;RP*-_M7n2OmZpnmf!jEfsVJo1ht*UIA!RciuYO7E@3 z^n+q=<_J?!{PXbS21gNY5sg)MOH)S9wgv78!Aj@y{3vS`F&k& zfvadH*1;*P$jO;|gbT*GxoqquTu1z?e3VH17dWQ-HHzuq;FuuTU9q@d;F!!m!7=pz z0mmp?Z3oK^mPh460=q9*z5o6~gKxogf>U6s`;-8td)iT}m9*W3*(^I0l1F*i@BVB! ze+W45hH{7EINtA;;1ZjLEpyWp7F6<|$jQ~_mwPo1p^ZM7sYk%83I?S!N}B3SDIi`z zHJrv8e9~`}i&O7gzts?4meq3-zkCV0feCCi`&MFYG**;$ir611Q3uL`#GtD7dn%c`mhE;AUjpzy#Nr^ky*Lr3*O(_>|N6NdLfR!gwKtGbnR zWP`jHw|3>r!erBGWppy96KK9Py>1gZI6N*<2LfS|UW~79{3rz;l%I*8BrJ5;oYH2kN^xd%^%jx?|O=7 zf{H0b@U+wKDJ6mZi_32$?rv$3Q0i0_@3~^Vb6KPz8txm{%}NO&izMS}ruvZ;CI>Q3 z+(pb|mjR7baapG=KwHAvzju*ciZb6mb-CMF$_eW_0OPy)j5h09X^(Y?MDm7mn7-6w zqFlqJQ8k|dmk;ox(+~fh2KJ62~Y$I0f3dx#;pBDm%5W+%hSzV?mU2d|d>DV7M#L_9`J3+B9q z&NZS{t7?XvCOf1VqrSt~y3Y1s^2eMgiMN23q0jTNhaqdAC<(yBU1R+7iq_wg z$!0XPPs`IJq?AxNvEc$USNMb8yhoouQBTl}!G^t8e-vtfK;Msp9g++o1Q4icPW1gs z*x{Yu7wV1#A{y~aEGA>0ECB|RH^TP$h|l)pFF}$)^$)-BLI`x%PPSbMyQc;yy2M|o zfk-khhH230Rhu2HBcYacePc&^ye87|3@6v0$OtpqVQwc6PUbla!A*WU$d5lb4? zm!RS=u(hS)pgb$xx4M-{j491EG7mJTDXHIU65MR3&kswlNMpPe89mNxVo2n)e53Zo zzGX9#$^UToHU&eSPDb^+6}BXI0tcqP(Y)}#NInk;x)Wm0_m|wO{FeKj&L_Dnm3MV7tu=Q?N=>*BiT;BOC}u;S3^ zsHJ95_I7#x-?=(e=t60IO1O-QN36Iree|NvvC@t42v+no$$hv*{#4D~MkY;wkHGN8 zKziMmT9TQH>C71z9OzLuo%7mij1pMmGW_oKPciBLQ}~Y{JzzliNA2=v4~cVM7ugOS z;6sUZby0CA`eAb2(^FQ|X|KM3Ky$`|r!uPMBVG`Tf(;LYqt2c(ivh~lcfMTv)J7QL zAVAAy2>D`_5b@!t;6PTTph8nKaZFjnnzKrb`787M}fP0 zs*&S-z0huRq_bwSDN;U*9F1-YM_~J&TlbbzET(#5N(P zXvYolGaURsZ-B-4%U4%e#E8+Sps(m&!9LzxUd4x#N)_&g_|B|FEH^%Oh|FD^9Vi*K z{ntH!q})w*K*czNuT+fIMV2ID4ti`#U$yYo4T_O6pvKgtZHLd17UIZa(VQh~c#91P zshi(lQ z12@!G@|QNi8t_(arX=h#_I5i^?Nd8z|uDM zh1FmKh0eze%D zr2v2_Sl=l@vB4qrz`sY$d%rKT`3`Nnlc)ZSoo5A0imi;MhmpLlRUiR z+wjq5uo&DV?(mP&^$;htm7eZic2fR2nTIhRdl5T>+-p4t@*5H$2V$DheAsuwD{V{r zTiXu6zT;c`t$cxFN(yVfw>DyyfJ$nvfqi-$HK{7|kbjmYvT`)EUZPMAkiU2`9=%z^ zjjED|?pC71y$K8o5q1d_w7530KmvcycLMeg&Hm4u54cS|PO>eDVAaSd6DdCbKB85& z7S+k#ZcUFvSctQA0}P?L=pQ8Y{Glt_P@genQ5DWAA;!=99Eaki8^fK+fqF3Jw$*n&|EIAy-3@ukTZ<{D?gP(?WB-buh# zxiDL;f=isaO}2aH-PpHrpgWB;_S*ekLIbbd$z~2T16rjYA9GE|zAqah#S%bUO+e;v z0KwNk4Jz5-f82uHvmMgO&h`>x9fMiNPPE{B0Wq;6>`}@J>x?Xe`J~Mdy$CbtrQoFRmA#dJ92rqm+B&4Dx znJ~9rPE>iEYQ@{)n39k94q8huefHz8^};r^YwK5ielCdH>0fD-yJ**5YT|X62TD!QTeG1HZabh zR1cb}YOJ$1rfjOj9z>nmy)@nETXgrX^YGC>Oy%0MMt#}KnhhY}vGk!b*?*8yS5Y1O zQs&vzHsJKg`U7<==D7C+)Q=gaBcV1W8U=-+?@}Cu{eF7y&p4XIMiQhca!_W+&6!SJDRgutu3air! z7GB|sFzV`d?hbmfKM`K5 zgA6TOZATNOfXG6&fhhT`HH^fg*4l-U1)ENS_Vja=v8DWbe-=T8WXClwiPw(3 z*h}vl(Rs{R_-$lx>%HvscV5Vr)kio=YO5e``|#E}r`zK6X)E=|CU>PfIJ*jKWtnnY zxr`usZ}eL6C~sXuvk|D)^PWR`F%(E1b1O=snAp$+Y3=JWBQ?$s-qA<6lXer;^Bfgz z3&ee?Vo#aT^@Hncv5vqq0Gz~pmdU?i~*n*YoRA^AZLVz|Nch*_csjS z{L$_q3_JAPbKh*O9O9>dnL_siZ32s^Z-$p3`Td3Dl*nCqd6tf?a45>eWNGbS`paoC z!$c1FdjbW}Y~U?qj|o6gm{Q zYlKk#Gy3>v5F#r@XskPMsOPpa;!I5|#$+ALIJJ4(wABv(Hs0LOg!fT)mKp6#it~-> z%xV^161+Qmc6QBv?GN+Z;4i)m^LqYq#KxxgBaGik?_->JQThHgT%5X``Prc8@8Gf( z|J)?gx@s3ID?Jl0%53*!*3~b&C=DqCv5i?kHMM_Ih%E+CR{n^t<1YK;@GR4oVv7Nk zuQ!Qs$VP;C6}LobgZ9|m(=F=_Av3}E3_{nUv zr$nERTG_}|Ye!z8E(-KeHX{Rir-#}yNp4*a{GTU0Zt^i60|}?5Cx#WQqS$~+e~JQU zEz(8w^CA}IN!HY4@SCM0!A*Up_h_90LsXL1UR#j$!t18H>gvqVkd%#3?WccFn78?-k%nJ@7)<^+s_1^j_ETIz|s$JKFqsV5_ zHU;PWchHC+AUO{!-@5>+s2(>+R{>*`ya7491*qP}$}6uI_T4cTh&xyTKj}J<R=^W(KENOgQDEG~!D5O66O~2| z=El_>fv(aItEzxu&;Z&l|JO(GYQcd;=fV0|f&Kqp^Cbwc2gpK;7eLVXz%ylzZRk&7 zq?lJ1S%JGKo<8$pwPreIz$4K7T2mT8JXQV^*<-Zx)%PYVqyfUZ(KNS51e0GbkajZZ ziZ(8G81KYMDiXNHPJi$bC=DiYQd44Oh09^$6hOAa1kQvbK#fY#O>(if$#sI}30GZV0Z`?fq zxvIi`U{f;!Dg`#8rrHF8FQoo~Imn>I z+K=7-5@f^<{J`7%?_XfdfcqQ$z9Y~++877mPnqfXhlJPT5Gayaw9LqFwlj;BK@lsi zPf*@_ z;RWb{J2L1;XW7 z&7nK`G;d(3Xw$QxQ#3FmYxX2oj^A&;z)S!~`Kn5jN1*VfHNdZv0awEmEYoIUZoi3w^^ z!Qvx@*T+?_GTn;Z@AJ^V{Ja|mr3sQ}FRz_MvJ@D&Am%)2Nmt`%Sdfgp9|yl5>l(5*~(@niKF+%vrA?urFk%*MqL?R*$E&aueOAd5Kjk zu$_1a#oZjiFzvM_73s3b+CC(~W@03kHfr>R+{%+^Jz0Yqzm6gB8w~frYo`@B{+t}8 zBzQ=XOA(X77J!Q@a@+kLmM$oJEdA!M0rrdA%O$5?;Q+q>h*u*KHW0wZb2S`T760`nQXsUI;y zVhA5ya33@Jl)5_?eoAske2xp4CZHD}zvaI=AH8XzpWs@={s0DFSs7?9ympWp`FC?; zzQ>xFbB4=l+ZypnNCR!Z$}z!$b^ZBrwQJ z8mSbI1V1a=>;1qOuZ*Y%@Zw~6_jIMhC=;LkyuL`$w369?yipR+0I?j^{Ud>Qr2l1@ zUTkQ}OX2;jJ-l0X(jIR@M$7sY}t;Z&75;csXSq^$u`Gll_pV}ua9 z5Otii)Hm<*v21Y$qJ$Ey^dQBxs5NzCtE!>>lDhy!h6}E$^#EMwg>#Co7!%;#7H9$K zxl({re{8ZYzwxxtB*$ZWaI+1iB*1s1PMh0u%GrJ@B3+Sl1sF(~smjt(T$OM96Co`j90;=!ZN_J~Bec&b=^2OUS_oYUA(8M_FQ2 z*SGg5o4irT1XJRHWBf=VI1e^?-^LUo(e2C(^f`x6QoMO0i0v^NZ3yeQ7L2Lp#DtZ| z_d}M9z5qa_tF2zslg|EXP=uQ5vHPmJGdpj4Kvz?lI6~}Iau1ApjB$y$_m1(I~>z?rY;K^ z%2D~M_rT4T-9LfypsQec@nUPCu6Ld|u>3-y`Gj0It6b|_b(BDq6H#di_8i?chJ9~; z{zfsnFW-=mk6Shi(ahhVlvyF%^;ggtIrJH|B!{oM6F1Edj~C_$+Uu zjIZ0JeXYk`fC-iC?Y{VmnxiCI(8+%I2QMt-`1Uw4 z$A|)dlz_zP>vk`H+5icG6TS`gwWkZa)0j6Sa(p)JGk?KQ05^A`M7Ecr@oY2+br-AU zr5R*R6>w8+SPz4cJdj0nIE)R0+Z`a#3r^vXUT_Jan~90hO7?i5zx#vul+*^cSqMH8 zWJ@phT)5fhU+fruR0+7%>tFvHm!j@^x7qqm+=}*mh_gEdGx$>;Tdxn(*i=WpdHnwR zfn_3J0ataozw1+|^yCyQP@vmeN@t%9&Z5Ms)V2MV&znd2btRR5?Bx5O%|g|5Xz)SM zG9UG31*$e{yr^r_^q6296!F3I^pveH6<2IHA&39Hmqea+%9G;g{3d3%0Sf=}5Lk`d z#P}i2a1j)vpb2Qimw@9xzRASTp|v{LpO~9_z{$~&<4n6N2{8|)g1i82e zNxc*~e_Ul*(~a(~C*6WSR66c!Mw~H=q-JE<&Co&sU>0=SntWg02texyyD?!Jfc)^A zd=0%1Cf3sk>bqN`JmCN|06!%&G3A5mlnnb(zMM_kie{|DC+M?g5h58~oSc_Foxz)G z$A!VLnUdYoQ8NhtyrCtpmACBzrpnewWRjsOOy@zztu4~)0Y@||*{IO$XNj}Ehm)L# zvIM)gK7P7!F9w$``R?gZ{(!w|<<4wq(6P$OGC~1EE=okJrq99bjeH5wH~V;F*Y}tVt+Y4^)^)xK7Kn#hV07wXH}A; zv>sT|T3)X6>~y`2T7!}%Br{DE@!L``kKS)@%A5sPONv-DW7Ja`z$iQQ^eGJtegBxh zw95k(X`N4vSeyEo+9Dl&H&-3Z&!KITYTp6PX=?$qXSe6}9K zw&ODKDkAC|0mD5$nX7w6y%Z=(X}|AOeip~Kb@lc1gxf@J-9f2EXgf59_$42umoF{y zRvIna<=o7nJ_711dh;z^CqB0ZY&e#q+-JBA<+RKAyF8*hileLKmwm`#?S4RjFiL+3 zN~>_@8>r-p+d3k~gS8iAz^)MBpd#-amp*RCFv0FOzth&>gJojOJ2u@0>5?{1wO<&O zYk(IWhROg+ijAxHl%?@-X*_5~Oy4UTO%@INw^TzmGt7HDUWUMQzYsl9k7&fG$Bg(+ zoi0pQwU|PMGXpO{&vb`J!=DECo&lqB1~GhL$uP6`gWGq22`oj$@YxBXS;#(V+byri z!2che-r!DGo<;LO^nr|zsmgeMOWIxCma zx3VAZ(#-Dg8+8VtS)W9epL(uij6UrPP&NGgYHOCZ?NmJ%24OY7|4ss%?t@xslOR}r zTPzbPDvXgh20$aQhyv_)cnlz+Xl0AK_2X~g{SN55HkjlsA0hNRdF6yGyWONCRZ8}A z+!d80aEf=RoPbmZTPmMG9&b`<0bPBjoc*!eksdBwV1HyD-)ud3lnd!_ywfH*xKvu5 zIzEBnH4S+7A&;<$zN*5pac{#W=K-Au7w7Hph>b z&CQ0PI!Jg-<58p^Pw_|-1BjS*6K}{NVAHuOukLkl(D)lA@$EzTVjyux( zH!N;$+@nMIvo81 zL*dca@Oe``wnI|ihuIO2)b+NJ?Yc8HpTCJ*v>5&QI0qX?yQ!@Kuc+iB6k;Y-hmdc zr=x7=yZN>Dtg}|itY|N~){GB0KrYW!H8kYTKVO?1$?Ngz!DHLukafu8U-EeDz))Jy z`g5(B^{2zlBb>a-lI)`0u}S4Ja`(0fGXsEPu7V|8dvRIzndryJyG?9yU%w?%;E8+| z7N=j+PFxeYGq+7*ggaKh1GjdzcfOgZvn)P9GPO4?W7ryFSv?u)1l`3`RYve^j84hz z?Hq6H>LcH}YN_?okS{;fdGnZFzv>p=oSgvalva}0{VFR(0JOGC|MPYI_=;fXQ+$Hx zO~YsniJ2Jh8#=lf077II0Ehsoqr)q=s-XDohbbKI_bb)lfJF@BMEr#h{DPF!dHBF> zcd_e`8Ab?(pnT8gmp6@!e)8Gkp?<_qlje8d7?j6r`7K>b+7DR-LWNizOWkzT8n7Yy;`2GP5sM&;&$!MJrk3`1VDnU-8Kv`KoGAd4vl zVJU}OacmT^9m29pi^vka{E3uW1B)sbWCWdgDj$0v$<38)Rj)9%GRfe&Xdk2Z^Kc@c zhvsL*P4y7yu&$f4MtQ8Y^$VhI*Es6{ZX5~{`%zyas{su0+KDc1vm%xfLXvh!nJkhz zpZxVX^xJe$)!wK?%^u=?c__JZ6dp(60t>yk+%wm^P50?Av4fhiZ<4cYO7;<_32pNV zXKO62IM+@x(_?6`2H}?NpP49cDNSAQh||!Cw{41Jr(P39eNIjVf#pFf4G(9jq4cOHxr?+1P$uhV83g>To~!SHa<@Vs zWD9KjtLwRdya~L|xCTBZ24Z2Lw^HNKS?rPbn_mEViN68ze+D=S07Qw~0hbvYe$s(^ zrgj*9dP4^`dJk}bq;z7*WT(M9Us!zt8f-FS?1`Wm7tnqdiC%n);!>l??%`0;4<4b( zAAoljHu!`PALd8c++jt`8pSqb_@VO#aJarmPyDz|WFNGcULW>|XzQPgi|C zkhAb2d?#X4$qe+fFAXC_P6gX+di^##C*CoZE~%M9Leuq8p81#~{z-k!h1njFl{^~K+dxnQ zQP6-K(pU;lbhsg@czUf*P>#>aljOL-FF?5LTkg)YNL>sQ@;Gq5Q}vyuRM~uYIX3lR(c?6A z^mj(Jx(gw#TwjT~$ceK4)|I>lGx7@?pgRE&P|IOJGyUpM_*rw%(9Jpi;`QRI{jPVh z(_uVe_K5>X{Zh$=dTl5#7g^L(6^5V>;g7gdq4f6;fN?ig6|FwwR={|WLA2FIu9Y-f zhmXw9*Stj#tML+uFc9ZuYLfw1Ee!03RtCkazF}}}3 z16q||j1nTO-x3h5-oLhO7Tjyxzs57#KTb_Y4-wB$J5lIpaI|q*2*f z)PplAUOyy8*p_)pOnU0~kTTxbMu#6Hv7YRDl;;Gyy{WnuVWHw?s}`s(tBJ*n{804_ zhkutT*}0K!cZRd|(Qsp3U7Tz>MU_S{N8 z>dPP0UH0{Y_#u%^s}uE-&B!`koD`amn-BG*3k#n{JPRYv(mgjQL1T1bvw)hX5*r1` z0*Qm4TtHess;WQ@KY4#ok%81B>K1*7#|_5ZG_`GuOeiSeK$doYM(l%l|2Oc2T2u<3yei5m z71a&Sqf0v%qL&qD-svd7*_*G$2UE6q7`Rcwkr6^ce@m%Y@)PcrDimdhsE8>xOLr zhF4T;<<%z)!3B89ecI8GY3Qh%O}QeqSrOAe5EN-o1G-4lE2WtWf2(;3n%IUabQ)cW zg#s{X@;`ex;?~j?1~ZYH1=%U?o*Q<^P*oB75i*jP&Td zgWXI)&e~XemH8RhHEt{?QQ~_IB42B(0o}S>QJHG~xyCeM;HgQ7$rJ4`T%U^Ze;m($ zouoVt4eG63)RbZWfup=f*+f-H z*4M$iOGG9xRL0dlcWtD~=l`_!boH2+SPbX>7uHaLhPG6cU-Lxh$$Kw;4mcLf(!zq^ zJ08x6fJ(KU=bpfEo^!XSHj+5d*69M$?2(n#^@Y2eamw=!jv(H2$J!I&mD<^Vk|pCG ze6dgp=YLwG0Bg6;v$81l+gq6NgkrBN8%RFBoB4*~RVD#-?p7vhuZ#367E~f9p#;mbN*%b8EPbvi(e}eHgX8 z(fcK9Po^LA%V_~dxS77QPvwz&aHrJ(rHpSj5fegk4R=Zt8eA;aHeaNv`Sa^!d&b4W zO>iEmik8b=7cCK?Zr6(xMD;S)S;AfLIiH2qEb_qY5Vo#7-cs2k1Gp$g*e}E84PbeE zVzrkbFqzr-B`C|G+K>%1gEoc@A;0B|Q~=f>)`zfdUy6!3z3yO?%wM zF{m(~bDq83TeLPx<>|(+>)DaPjD6hrJVctCfvL1jfHY~rh}hBvPUIz;?S9u_MsK=x zpz!9KF)_!2a;w39!ijA&R_Bk&NDN`e?AqtHjtM z?^1oCvS@k6 z%9fVA+{^UH)d+C>muT}~qw9bVuA*f~bdv#V%X1XGHynG|EfpC%G?UEzdyesIdy74`Firpmtc?YNSAs#zpMo5(b{+x+dB~VdLO?$G_Ji8lJn!169sPS zf?kz=+uOiZ+mg>G04{r9QBApK*mkL|!ETWMR>6s}(N0O#L0%r8qNBp{g^K{!xAwm9 z$372>brMo#EErrkcBXYcbub~*gV;J_l2c~3PAhkIzL%Hl10KWR!!l3JIKb6o@BLQ9 zvdsw{rk-!$nf;&O7To^=w`2m~md#iHM{tWxz6E$659k9gW&z4{K~+XvA2E;XjaGaj z{V~|0*GjPj3`VQCS+o6)&E+c7+1Sz1n9Iay|C9g>i(o<={>pt_^@?lE-TEW|Ql@mBwSr?E@_1%EA5c^Q9uE*cBuEqiZ-hj;|hkpUU|I zzFVSl#0o=B98R23s2`VV2p0f@pP0Tcn0p(R1MqG(w2%)O0gV|hZpesFeTKyZCV?ZR zaJ<>wgo9s-xxIZ&ChdeYP5LTV%yTyeXziKBN5Pa8E;!}Y>!ett6?mh|Umi`oF`Y!c zE>f85zHUUy^obg(QpAk4&Bj)2_k^F>x~PrD&w!9rzvrY{B(kE=UTYS=j?yz%!QDPh z+$0v=afc!DVPws6hUQJ^kfv6P0pSt5T4UVEnVx|~pXED!8JZglVc^FlJ)rN!0;@rX z`7BIU?pQ$fFi#JdoU&wjdrz6m zFZmpRHAEg}K1m1KaU%qJi|$|5Mi2kjIi1Qx*VcP0KyTi0_2#uuE>eJjp;oxqcfv%a zE!54T?0Eb(PgSo1Jeq}fAX!}OgQoO%cp}$52M(L=(}e4S+kSzLC6l^ zMJJ$+?bCj7pmg^?KiGDHxR5N)7q=za57*_yL9IlN+tO}r;8+^t26Iq^9WhNJh%FdZ z;8lsC3!AjVSur6H20DA;UCd~!3jAcVcS_tITG4K22j_Y%{o}2!5~gX?$)Qs`m!Pd6 z7?lUZ~jJ}ZE6v+2(~30IbMPWRF>>N+~d0AyAI5s=*h~=Dwdpombg)zrF}gom$y70 z--%X`DI2#syTqHHYM-CCgv?6F^5ejg!80KlC;Z4muO0M(0k+YdUUby}g=d=`H6$^p zd7zFl-|`mCvxgsn0Sav|Axe0VIN~Cw+g>8dkO4j#AgL>0C$j*QqgW5;x3_mER}u3i zGaiRzPbaKbufUYYLYIf?^u1)7eX&=K6@e$}nprQ|;gZ#rS0H_II_In^0Rsa*= z?+mon^+~D$5VG{&7^0d%*xG|U!1}2G5Fyd4QN2q|Z(W_F{#wl?=pW00{r@jVrv73U z{Rey4&PT+&F#w+B0A}t0Y0DsBD86XU#r0i;05Ah0KORFmk_137389#?;4AFXCFs_s zBA`$LaxOvMz{6YDP7uJdqi>W>_miU^dqU4{%)(|>fYB4c>qgoD)$!TjWEBj|egFVR z-~Y7-$h?6C;_x;g0K2&W9EI)@bOPIdSt(re@W03r{!d!v8~ux@GHA9UevB^_{NjlG z3M@5)*~PpdYjb^W^qVig385PP5df_cH2k7=KJ@?X4zRt_!cU7Y5LnY0kaIqO&xJsK zkn&GP!~ZXgmO=bQSpYm{tOM}z>G@xz1@OICw!cXm{r^DPOVD4m1*0G9LC$byeeADD zo3kLACbB-dTJ;DJ7ob4&-#q!B0_6Ey_wQG#+FZE$E~DsLbFQ_KjI>nA`_M!<6RjOfWa=&Qfvhb;nW2TA6>pac@T4U z+Z`vWZv4Pe%p2WHrqxjEPn(F8{2frSjKwu?Qjc5yt%EMv3-C2XxiiW6yw6D zAsQ;BmEAjt=(zZ~?5U9Ow&Z?IRa**vp#BMcRbNPWYkj9;eW1!0-<`2&f}gYwPh?8z zGP2yHTp&bz{U%OMwb?E8%{%^|&ZFoG4sC$=a#u@Q&v3opr{KtF6z=E;X{vgwE@XZo z1nEcD(!pDWfv%liipBjB<EwHlTi9KETbWx4)GMK1UlqzHszSBejG?GT3^tRJ6FbugBd67uB2}l{0%m`R;f^i` zP+G70&X}83{qbtSF7W<9tn<$)SNR2XIJfGK?YeE&M&<39F&XU2vg`eb=qi!OzIWc1 zqhr>P)e++JW43_39UxnKbN4$nA+-HmCi3>h2;eh6ptbY%W~=^MeXa?&buP0_BI&3S zY?Zc899CC4+j^d6ExGj+ez1vqaemJ`XcXi98OVUVuBH%iT|trU{(y~-or^RWjRQ#I z`UBQgml?5>EQ3A~4f~>vTX7Sj-`BJ7&K)anf1}9{ao~xB+r+Y#8-6;ySaiy)uJ3gPQa2CI7-T$&Bdyku9iro*zt*w3j8&nK z<0USXQY)EbUgc;dyA`Evi@&^Bz#I6kdXd3QG>>)96Vhe#A#QEUr^PD7JCPWA^g6us z%7rcmpKNL3`g`eIl;zH>$4_|G*c6y!$=U4VT~nKS(&`c3<(yyT!}BdkuVzq0I9;(o z&fH~6mPuQM*bd(gx;z7~42^DBI99FI92<{2kPg=imW_N#^`SpOmGeHjou|h>tgYVs z;y)y+8yHomDru$VnwYRs`1sb4rDz4rYa1xgJ{NLZf;QA6_FleVE5`QVR)n$zwGpv# z82DbSKN+8upaM1pc3?0z#_XWvMak2v{L)Ln^NpE3<7Pcf+;hAF2BiH2@OcNXq*y50 zUrJ(~s)A$%aKjYc+tNvUQ0T~cg)c-{a<|}irs6VvzEVzRA-VTMj#?a3A%It`xmt2I z_CKdrBvP}fKC%8xkEsrld}Z!2F?q!k4C(9YOVB;HL{TD7VcDWb4MVmRAFb2wli;e# zt+8!n@sYapMupI%M1FZ|O{}L)d{IUUXIu#-t00Ql1Pw8U_sDwr{R}xsHZI+rC?#*z zs^pLRf{JfGSXa?+e;DTcO|DOHjiN+83$WB3mIIJ2z?V1|HQltV_NetC=T)y-*>8?j!p6OJ zw^?){JYAE^i!_V4qTW(Q6`D%t>!`7C(_Jff3^_U!_5}!83jwiEIi`l59{FJ|f|e?v zv=_lYgaX3JVnv6Cu0tNY@+#lz{1#%o3^D_f;g3oejnmuGXw_c0fmLQ#a+2bzCc~bP zuQG`Zi_MVL167ltWA6H*3ma#n*H`hYIb?S%V@nfpunim_($*QbolS+>DxuxL;_~gM z^T#qS2W*k4Nqye4*^Y8~sGa@q*K+0NYn#)exZF&YTjb{J8vZ}t-aD+xZD}7yQBhHf zf})hT1*L-`y~Tz!0RgE2qS8S?dM5$`N>vaLDG>qbAT?4$?+|+Lz4sDG@wd>u_tE{H z^M2*0W~T%&eJvWiLJync&e!!XyTu%0T!%y$wlZg2}1$mR9c4c(?FC!z3=gPad=_PTsYb!<^tM z7RI0`uYFQ&{=0f;Ozul98QsMj&7rra?fKJsAE^}1vv=B<=ZP_LZot(_op}&dw0hE zTg@qfOQCqkuzv|`_$tMLp~vQ;H`VTkVBKo9nV5%@9tCk7FHEX2F%SU;_T zcDtw@@=4-D&HYD)AHrUe>|8G&RZ&(z#lLzsy&M323M@{fCScGEg4vQ(2lpgWRpyGn z>@l{{JI{;eB_FwS9);5_IrRDxx2lA!P1DNtW{-xb~2J+^7Zb}3#OMfOrf{h%PWDUE(x4ld&>wP$iLhhrG3#{jujTHK z4XWgO7)PO+>qP1(Tn_w^wS#?l=21v-k0fR{gxP8pwLB6*nnfXcpjMnC+gNsCI?WKE`?(ZRK`aFt%G z{8KB((^pUm+sZ#r~eiiZboB1jc_{Fml7&n2_EpC#}uN%ipXKFN`HHp~lajB%#x z9}qVPc43Ojr2MLlz+|EcILL$!S(SWBc7f7`5m=6iQTZ0tTaffEt-by7mK1wYrR>h> zcgkV@V!XcA8QOZE-=%#f?*fJDq4u;6v<-M6kZ0lm?pkngm64LtwRdY1!fr}%K&nBo zSA+fQFO4*g1WZCHw9MZU32r4xbios3E#>vN>VBf%$4y-g#iGva#Qd_`x!HgCjo2?& z12xm5x1brv-JZ~AS50*iWo@`J4Xqjlb!zOkp~$CliL`3X+y~I5_{zY_x>Dza4JIL} zV|;lm{wnvM1qbzLGEli!elAUws{K~hv1Oq(;A1{tO*m6{vuJ2rm>B*J_VLNlIxf5C zs99NJL82ttCP6WSqbF~;l=7eHQ2)kbl(!iFzk7^M|7VX8O|bub(>UMPE8fE@9h%Ad z=I{r$wiUg$A0XF6>*}$QU5o6A#@@&j;mjq_weDrz+%bmq;52usC%L66`M1vIW=~gl zyDF=56A$QjHbLP_1hbC7{m7oh#kju35-7CQ4WqKo>i;0=vgX*g%kS^a7}fF6I6_t^ zrZ#r!;c#fGp}n1vES0Z@s#l`jm0f`a$@3c-jvl&k`Hg1>_Ep@TUONDeEA%~(v8ltu z{fss)6p!!QcOSIax)i6uMQ`g1L<2AD%mCp4+~)(W_mzKm0#g;okH&a*ldkDXzrRMq z@I+s78_hG-Sg-F-a%<}x$)%M(n)gTEf&vsj%EkQ_kZp+&h+qxA^WL7Ly8Ck11{4_2 z=)a6-4fPFOn7q{s&x!EUqH&84PG)DsXP!I&=v)TrjX8kMz2ynu#c$jMs6#+i(WKt! zZ>0{7jeEBJqB{f*O}zZ|vI)xbLu1T_n(=nIh+rbxneNxy|bXDjqoZs$$eh2WJ6%2F4)^7U-KMaGog!>OQTSNW7r^x zle?23V-xr}%vI=s;X(A48+lJt;OsoOfFaJ6=~s1R=w0Xxm88*@4n%z8M0!^|gOHA5 zt?elzCiU53?(9h#3J;YaOM(k}7YfnOx-2Zt_~2hhaF6SY-M+NDAjM(h@2GpVe0r7E zC<*02#4$iCX_Y7FeMx7{2l!m$E`&{V^5U=S2|PI=H5fQGvXro{|3 zF@%?-Q|d|e_-c*)B&l_MqPPI&7EuzK4{5=`>o;2M+MR5hHZn1{G%K9uy@ZGh65o^fXJZ4nN87C-GRhdrltXv-!ebG)tkxx-*1^(VbjWBooDYD{D6D19gc=)ytD4N7^+E zwL1a=-B;H(jO-1f)t7_TjsWa|7a|wDzJdXjStZjgj(JHH!VFLko_0Bm7NpxwV4!t{aaVBaV|hc- zh`fF0k=Wr?48-+|n7Z6~3wXl*;MlCm%yCx-f+C{YkI1w4re0~=q{Q`_&?I^4MxU2{ z!zhpL>)oa;?czP}nbM7;OD>X`g5jxjH7cStx#^yZi6NWEHLQ;27uQ9mjVx}Bzy}p( z8@W0tN7?GAZ(q5>5O=FFiB5s7YDf&9ZM{{usY>7%5m|5Mrg$kV3Up-*T?-8+)f*B+5AAvqAOLShFvBF1;MR{&~C7qzV)wS{t zaXr)O_2R{e-HdN^7smCOCNrJ-s?qCV3)KCD^yLyGE!4j2R^Ucph&F0cffZ+7z2{;a zJMI2iBF?CF$eCe^Swg}jRz$d4)(SPu*2Z30j+}Kyy}{stHAzuAOpo9@yT7ofdI^6^ z&wC2VOQaexZ>z)P%J%h{)-S2;Ku!*t>7C1OUo&*L(DAwcg||5;Tg4L*eAyBY{Me$< zE~Q~6_TwTE^Hg(^itC>6zWiGotgtYP4O{9d;wC#h8lNHDB)Mc|wN-@ptvH;P$AwPl z*L-q`TbM0kTasIAw2+O`!x;OWOt}RZoxHk>A5gfz6o#uXV2W|={iRx5Kd2U$aW~cI z3fm=?-RyVi8_hk)r}E<=pFx-TEBZUxP^{l1n)_O(-NLE&L6NuE8-rY&&50f3uEu?d zdb}GI1`(LM?@9P_M}uEFQXe39izx;cMYyk4(;G+0kbdX@g0<8gB(U`*OUN~Sbnn~Q zBf`otT~*^rYN@g&yP$6uepgpJFL8hnaklM~+_)tvR@x8`jWgS!(FhSKYK}rGD4vda ztgQO0(VlE}((r51ot}gzlHU@6=k#yadFAwzJ~eimuD}wZ14x{;BW@W{g%)9GV%$h~ zd1&?Fb>D%_2$r6o_>?sLWs zpLp(mM+rBJN>Jdy;DCqa8WwrUJ7I-0rQFZ%ur)mCCz^a^UFohk_TS3CX>)^&9NBGL zpGm@7uUz)Z2V__L%YONVe9ZxE^6UIBxutwphFOAT+Aa$*(m`ta`g?Rb%=*O5EL+9; zr@`-E4OL%IV*mEFkBj?l1jQHjLbly0;e(HD*r4?4EbPU*-I?K`B5R*5f_fC`sQ3Lx zqN_xAdh)bJo$3~07j=U!4?YOx+dJ`at~b*>?sOq~eKtBlv+emx#uwQ~@3+V;+U`u- z$e%}c77eqq^cHrx?{$yJol5T0EjxpLO~)j>E)Ut?2Y%L1YHIjQGlV=fy<+)ZlW}&UeB%3 zsBQ`J@hRELC9*aOUn@b6d!6@x&oVkT+XAP(Dxcyeq;=y_ZrHw*E^jS*pLGz`2?f=- zC6gv@x!O*omXCa$uVHZ3+WJa^aGk-G6U?P+i+S-Zl8};4;GN3bpOB8u^BBG%!FQ{x zH!Dd*__2Jt1MY3SaH^^m*hN>iG!8f6)Bdy%!rSe>lc=dlQm4TE4)n;B|B(DnBBuVG zWY-w4iXPB|8yTZfS2LnW8+)*t<$LMs=)!Gf@*M?|EvZ#n**xB&9@UT zi=Z!Hr>e`TLI>QWJ$8{MXByN8wQMe4f?kcWXS9YDyU&!zKRrH^7-v{eW6q^W6Vy~e z_2Px}i6TVc6+~6`hGN${0#fi+{rbn5RdMOm1rHFq{Op8i4AJae8TS&IQ&_SLI2Rda zMgN-dxNWVj^N0t~_{>Iy`yl$FA4s8Se*W&pwcvtGb8U+>oy5WWjmN8H{Ar^i9aPWQ zNPf%WnN!=~^%mEdSkiUW4$e}m>NCfy|RE?JQ;l-oGSciO$|NSzbyNO!?gXY$QehS*Yr$xwgxGi*r> zyrGgh+b2R_ilZhKKX4U%So^GUwd|Or=qVb+a`j;)k4BA5{M|ep+{beGOru4Bk7TsR z9#QO@hro1PPh%jsEdYcY_JX7>Us5A1%Kj{<3-FRmpk4cq;9eRDH4*|Ra**K_^B)aL zEmH@53YwYh2k2%Z1Lk!8^W6|qVDvpr|<2a?II@@gkw2I1F?nwbsy<8`b+v$Cx;8jeI@8dvq&-8527(sDS- zO9ZAGqJkeIa);k-gY=3u{%rdBzJ9G)!CR2=;;N8J;@T3@Y}5H!+=f|y zP$Dn9Ft{!K?1g!59P9bB9QUFeL2J(}ptwH-qDZwygQUoYl8Hx``;4w~zc`TK4v=a%n#b4|3N;CL}aM1F2Jz{Ze8xnaK1ACLco1TYD zB0hjW0xiq&r=hcsAJ=@=R(H~yORREBH9+B_#zDTst64Xw^Y(0G_7;}piPVg$(8Y`# za8U;X)R}{iSBEw3KH&^QI5PV?9~WtC zEXdwVAR6lfspJcOD87tXzX@WSt&sKCi|DO35V$1;0m{HW#($0kSr1=CZr@M>kxuZV znjVC?I`KWa0lDLXvF#N_x=md3-EitVgvV#Q>N270RSh*ee z0Uav{<4%DE>Zfcxzm*rkuqf^>u&d>XR2Be?`})OE%%mdEjjaI&=g4Sp)pJy;6v<1Gf3R^lpLGf;`@vpqD&?7*_^2VL;he z(qLEO)S+0q!)*(}-WP!H&w(BF{6X>He{S19c9qzhNnl63MWlD*vJY9xMaTy)=KN*p z`sO?=?IaUbPD|%Gt+#tp^}~BhgT;&M=zl$*0H|V$_-g|W4mTi6yFXPul0OWch;CR+ z2zRC1tdbj>4B3xc$X{g+3~`ZL$AIT`^l=_mKZ%Hy7qA??@{_$Y%F>pJ3y62w-K_ZL z!|nP`!s7aYh1AP=yjSB*{(^wT1y>oZ3lB_297B(AEcUaI2Cme-u#WRB^&_4efq{KL zthWXDhzpPe`sbFZoyLc%$c{M6rJMS6($$RO)^4{v3ru~_(GC8|1(pV5xv(hFwLcP}dmSmxJ$moL_7Rl= zA!j;q_E#gZS;Qr#@QZ=@BNwG|zZUAJ>cZ|YuncWb8$LQU>S{eCQ19ej;}ZPsp=0R2 zL(7Z8x5nnSb*pPb*$l=<)9xhe$OcV3Q9?wa|ER!YIt#eDCJJT+n&YyDbMPRST)^5k z%u;bwc7%ZAjYux>YfkBue`hPuE7v^3IEJ|!>l<^A3w8TwY2v-lIHc+UWp4r7=Wm8W zy!W~TX)O1w90`bO2T-o*)8{YA-$|Gnzmw%RM zcAK=&i_c=Xd{T(y8g8Bxzomy7rxQn>fkog}H*Z1UF~A}S_SDnLxZZkKXobZI546ax z+EO`XJ)T+AqwDQ6jLx3Gyo$D*?mmJQQ40}>tm^sF>d%%$hhYgx(prwQC-W8%zAE!r zOXgk5?b(_4Um1+8-dk@s)~BXYzS_ALHlsO5JWNmt?E9pw3qa~>ZHV;STWi?+1&RoJ z!xiF(Kc!m%e1GOI4l{2zNx;c{b=M{XHFvJ1Auyc0KjbLTYbPVp>sK>?Nz?^D95Y^7 zdN=&ZK|Uog-e1Z8Ltqvt{l_7v2x`HIrH!8|?~NI9s|iS^pDOQD$Nv}-{yX1EfSeJ}K(F6KV_RL$(jZLLDbsIP ztp|r$Cu)tf1(r_r z)uM6X#^V-B(uMAdh%*+!=9)RPO4LF6Hc@q&77etEkCibN8rdTRXlZscyk;{>5f&xGvUFyQGshYx1VN zv5A3AGvra}1=Sc!opF$Rvn$HZ4zpU^1;W;4jbKGIj>WTf2|!kOX|Q4HInjz?I;C2F z&z#aorEOAkn%oobkSE86*^y=77`48kViLvqjc zsP3SW<|Xib%OrnE2Nh{1p^{rLeN7{n=~cfeGJj34AS&E zzhviDO@GsV|5Sw4A#AJ|Dp9w=Bp7@>iA}(yF)7*jDcTaTzftrPR)@0%x+@6t1AeBM zCb~2I(ZerA?=Drdl@&`G!@0z?&|AU7>bVW;)`|)e;%!bFl22I;`i6CWeNK7^Np!PB z2!qp;*064wE{U25IQw9QS$35wkL!sKo?2DCx^b0Fu)+l!pAS3^O{j4Y&Lp5@5<8=KU&2ho7LH^}w#tGk5 zKjHc&F%4x^hA^^gcF0i?Yr&_CgE${l>6S57{<)f3`5@d2&>8*Jfq#M40_eUcgIEeg z9-LG6aqBhrN%pC66?N5u7`~e_n|hI!X|Y=6&z8-#0@kDrE>_Ig+DEQHn{(XEOT!g9@5x!rS6#>*Ek?X-YHA@<7wF>n%6i-xVY z`X<=DOWZrD6>;OPcc^e9D=PDO58~-li1c0n!MHvveU%jzZtNuzCT8$xO5()>Gr9C6 zL<7&C-9mfZ)HG8Rm38T%58{PHt;0qV!O=A`qfwTYJEr!RuMv81)2#LHZ*cIf$G#fN z*+Z1UF9o2-^og|l4Xu5uf~jnE&t53CuOs3Rr?)Xjm3BkOwa_mN?qOxB4>XS0p2Jvi zamVJetA<$XvQ`Cj@kBbC@}9rLW>fZ(`50nMW-Qi)PNP*eYpib(6I&&wz9VffAAG9e zKt<(U#WVvk+&IIAxn7+jJ|VZ8k&>fQ|A?P{^Xz-(wgiohN2o=vcU zPjl3|v+f#FVAjMjnUBS+u!^7r+B{`MV>E$5po_$1|POT0+Z zz;J*4u@Hg5+f{}v$FEEWQe$ZOn%6fpOv4$ZWW|{K(7!#%22}DahUhsMGV^2h>;Box zO^oPs9(j*e{;z%s&0}-G-&+!(7$TmJh8~caLEppiHX*+)a}pQ{e129nl3=`$;OD@k zfAi;(*(62s=0KwQ-xL2mTt9XegTzMu9L{^{UqhGrRgV5q(p#WJ1=E#2dL8NTA*HSjHKVM)iq~0s`x_Ugn3QvUH-JDn*)sbi8IFrX4l>B_X>!a zXUbQ{Q51D=%dmvrwB4PbiqQ2tqjL2W^OdQ$O34DT8_?Q=VRa&TboGJT!inS*Sk8GJ zdM&MBrGXKL&?BqP40Zy69auwtPz+$5H+Xh^IC>@ycFOo3$@}x)1oC52SsZpN78E!1 z%B<6!pc4*dSy#1Wd2%cSnr{wVeld2Fb=B*{THTjZdXhOHzc;~Qg|}i=07^ernKo$R zV(d4jym21J-7}KmX!dp>eronZu%Zh|>CR*Z)~XIY_$Fh>?6Se25h^h`%2uB%Z9G*) zE%Z(`d*vL1i$;vug$SjM1g$s#1Q$A>&B`{kl0-V{J2EJb9MjDx8PJkFW(cAsD$o^a9@MSL zt&^#&Tz0RQ+f?$=<2&>KdqylFQnZIKhNgV-R$QJ2`VpKytgJ7X>6UaOE6M4D#3q#d zxYwu>tA-mM?;IJ4@d)IUdnem?Ef1c(e2YqB4q-PBO!# z9%}RZK}v1LUXCNUhyw8seFc2!rj&~PY21%X7IDbRDP+EuYpTz8VkX`%BPCV>!|Z_D z+$_iKJOD-*0o(u2IE%g(`RTB^7jisO6`EUp}A!D`@}OcGsGJ)Yln za{7Ip5B^&PSO297j4eFjDRhJ#;I=)ar?o2mFUtPSzp!MWRP()Y?c*PPFC%y*>_3OrJ>fXviy76OyeY8R{~ z>~f0bDt);29@dt*<|PF2Fbna?vb?tL82AJh9ni`&a$TPO&6{hH`UXc`JV61@!uJe_ z;$6q2pxW4@ptDy!V) zZr(bB3Y{Cj%EH#RL=r~cN6sUQCt+RB3Xt1~dtFYGYqB;Gv9{cJd9ZFEDv>;>M097Z zYqVfi`TgnvZ+DZjjejoK=6mmRdIhdy1=2uwU|E00ynq!$oMv z)iq+pnyRec%61^)x|*>;C(+cyFBUIR(Cel)v7oS9mO2ExC-zyQwEyFSaeSMNL*Pr2 zy;L_gdxFuPMmw*4Ny>^{V-(XW19#1*7>>0o<1ZAQCCKEuu-5=@xuJLVc{jC{{G5Om zsIM!9VDa$T?Yrzau^nwfLt1EFY_Wvo`nr2t(if*W)%BUxcn|jdO z)FCOtdFm|5Q#?yt5Y~~QZ(d->zjyFEi3~m>vbLb#y@LAVoiP5=h3GNC<*h<*{^3~-q(l|JE}ptrCmc@zy9>>U2=9?>+{Ci{42n9sc| zJ+T|u3gpSs82IuWh6vTX9__DtZIr-N*Q&kN+?8s4r>rD$GURpUwW8RSXQ(#`K8e?; z-WL7gavIJsrVDdISFR7~DA3#*7HxUg(3DFVxpt&4+oc)R&27gUFxX|*m6Rw{nDfE# z!^(pSy*bYgG)LxSrdgN924E}SSdL*BY=qXd>qB!Wngez315)*zVNY7k?mIbDOr1qb9z^rD8~ z=#}p5qzjnc*H${RH2xZA^YuAyH_}Of_F!E8nr`REGj*w_{S-G@-m}doX&_rD+lnD{ zPSw)i)?>F_47Y_MHPqYQ-66H|cs!jO{P;+yZSeagVYF$>4$OUQmZnQ5CS4H?`hr5p z(bIQ;-(%?=aBL42XNk1hpnw0bk1H^S&XIs0<~3>+u-F}1v$O^H<{7|1KNB(w;2-}C zG3EXlVq#RFQNLa(1!?jp`3=JRF%x)K>T`{WuJ=IO#|s&KI#91N_;g6yw-UlGWC9-J z6+PiPwrq8&9vxub@%7+n4Q2f-`Sz%3Pi*Fp^RA)Ss#EZ~gtYa($0v;k=aR3k@k)nhs#fD>rC4bC@hp7UM#*CRKOW~|C4p`yf{7K z@(oGlxSKaufo`bg9@Wrm?oDZ;XZy$Ji za@D*LJHzDsIdMhtvlWc^`tg)=mqQnkoOvTIJsl?Gbt1~1V|aw8SC#pAcvIaGYy9o= zanhMO8w$P&6KE21l~~6|C8f6XZ$|HCUK;)Ms&fC%Hm!6`GVwa}EV@|BWgC~%C$(Wk zlwHmIS$9k{eI%V%X#dI=!lLyyejva)(OhHnoUSFDYEiv!$&82D>y%&>iO$RF^mAk^ zamh(y)e*&*AaN0^RJb{+*p=_Ozt{3HkDKMCKs!dXw=y}iuIVWX0lLTp0J9b6RtHgd-vv3?qH z@@%yk6Q@x8@s2VkOH4@#cZ{n>lFNpk`MQT=aksdJHH*@Dc^8JdsWU~xFQhYF6zrY? zs|#}95RMI8@%X(zHCmk7a|h&`{@O7PTRA;jZVQsm8MmRnf#w+U`HQHk-K~3_S-eL^ z=9*3E84(3S;eu{nH~FOuSC8a%*LEzZJxqK2Fq0V~vhZ7CyBhzmQT*%=HHZs(*;`PVDiD5S z3fKf5#vCE=;rY)j=$3iJ)GCfxLP@v~E__KixzX5y%U9MS-cds9?K#9{tKU-G4q_w` zis5WsFI-s4r`;5JebP0uMbkdZDU_G#wlrsw%akB7C7sKx(IQ(cs0YspGU-So@8vH; zjRDKGa&(1feUy&rf00PDT3+Ja-zo&@X7WTDgs~O^6N@4oz}GXOy_ca|E5s9!#URA~ z0s0(0%!He}bF`N(-t)u-;dYLcT~bfA%zO*J-sxfOh2BSPE{zPQGZ0j_Gyn`J13lL^ zL?rNHL8o|q;5Bs^+gm_XArES$5k}uhfH(Ltu-Gs?*!K^@z67vy)gg_zrY$feK5m5~ zZE{n`)}b6I22&by29wmwBxz|kNEk_IHj=d{B7Tk&^4pp-9wXinNw9ZIzLP|zuNs=u zbHYwO6clWS-Iuj^TSsl<5<{^K8_y~jUim(B20``%)WJybi75Nr7N>i*5B zM~ErF7(k|PaB_#IjTTG}-R8aIz(09&mUf%FkEw^27?y0tm6@Wct;^@$CC)MJ&d0I0 z>mK1X(ia_%+5P$9jh=6ORlu1P6NeRl?pwyGbY7A4Uzj;dZzv&gQ1lF2fYBnlTYky)m>b)j`Ck2UV~Xc}A|qEu5(KOK^wv}!EL6Oet^E)2zI zzNXIkSi_-r&zfK8M!h)8X6UCU2vXIvk}Y=msGlr^egf5tx$E^!8K)zy2P;rEB-wm*q+c12 zSbhlzpqGyt|2W%w>dVX3H`9T)9=Jb_izSo$2N&U>``dqR#&?ol4rs$~i_LzXX*EF& zhuyKIdQy2kQ23agu*AZiV7o4?^x}DIAS_YIdeHypnl>Tii z2YR7fa~2>4Clb%*CMC7FfUbb{Tn2%H9rqUB-a&GCJFiZr?8$J$7}HRJtqMSHE=H69kVx?kN@dGVIyw>T1%1={Z>`IUbT^ z_h?D)guTk{g||gSvMy&v391l9-|`@%?zs#QEX^+MMdp3%G?{p|P7I=%bjpvFxX!R2 z@Z1wEZj?FNKM1eO8Pl$`@vrPL3KTvgN=1oqSw_Ge6~jsG#e}y|4Og`f*D`bnje;hi$P@t;?u&B%oiNS(W|o3Tf&u5z zV;=ad8|L4LJ?pIaeJ#W|u`i~w@K=GCnw1}njfDj85+Gqvo0wl%|WJKiMg`wOxuSr!=5L)Eu+^# zSQ`|dSb-l!V8W4qtvv}c{d9Q>zqBU8dliyLKsJRm4COTs3a-@M4E|7WCNFQowbD7- zdGeE|1imVcxMg?+f4oGb;;MoP8Y|NbwX8;XEeoTSf4IO*;=VCLKGoLGW zHVu;7Iq)=}c}jY7QBf4>@^XF8YXMmsO0C$Vt?6H=>8#YrSh=( z4Set>@c{$4S0Mw|gmLDaFfHTI!NHRTv8bkwI+_M|&EVuE)jm~1CLqO+hklR(zJfRk z&Os7*+c7|?E@G$r@(sJgx8?pN%?nN7^DoA~P=dtTs7rFJX9hmtya+RCwq!pWm~v_% z=GZPIToYWi$8Lk!2cYYh;l%5uZujiXL=tzz*n5&srWEm(F1&a_K|y&bG0Td0-hz~f z0NS9!6oLJyIu&%xGkSqS7%~q8=Y~KCO9}1D0)h|M77QwbQ3!8~WIe}@-FYTk{x+=f z`XCq@v~M{a01irJ1I06GG9CG3cN-n$J|`w-R_!E(bSglZ9@1ahj;a)=G=c2-m@|QI4T;>>kN{fJsGC!}1eeK0ptl(V| zcMK(MmM!7n9fq9@(VIPS>eP@F`_3I`6ZEtW?qi;6a`Bw_$cNO|5o59>uR zdZ*2)nKyk~iIWK0i6idRr}7tY;>y&QRxSrsD_y~TT7R>g-i{FU;&(RCoYd_U65l56 z#%b^`+2Y;D8_?bA25Mj1}mXDHyS2K@#LFp;eNz zYGyVY6Y(y%`2fAsZZ{rJ4-Bbz&xs z%YOKARsLsZigf!Qeq4n}g0&Lim|F2~e%z=3iyxOnSu3Nf+1}>DtW1Q^$uxgpCdA#@ z;V;(#q8bM!Y(W5sO>czDk6N8NC5fItZY{fEzX`AWDqj6@(j`6a%t&d=*&y2384$mQ zq9A)9R;R%9>$BR3^l!-7kWuh9%>>Iz01zvm0j|Bv{OXR8P&{!MF?RD1Hs@>$7<#4t z=X}x!C2xo(gf#%0`|SzD;S*pYw|ami>U6f9f>?e7SVLe09%2nOtf8%tGT_WcWcEX+ zBZtxZ%n$tAHs=o68(_jd93rg50k6#MP7(J*4*7|oTl1ZSA3$>N5K&KnUkq*oFtkCJ zImMchM1&>u*GCYz*+V$gj^lZJ2W|{t7)QK`vhDy`63TCqF~L|TePI9B;=v+;-Sp>s z{p;WAVjXB8U)YlBWJp8ZIp42P1)gJmoB)= zo#CwNiKg`qowyeB-pA~68HN59sEDo?Em|Z{CBft{Y}y>kPO!M zlUx7en_xlu_)^GEx^sA-Exy&`at>Mb-;?xCvdoqyOm2>6gY#y+%@F|!C)I#gG4Oi} z@d7Fw=Ef=DK6lLr+~@p@k$6{D=;jZ{uu-}hVq+QPBOZE*jfyAmv>+zH;$dGqwg#p; zjX%VcFwb~>Dk<<^ypfw_-Sl-ax%+aQzjatZG3f;bo(am;#Naza9*>DEq{s$UXkIp= z#0sPx!T@)t3yzt@4YDgtH|dg6(ca*LjwJ5`?Sa|(lTY>bcCR_2S*H$|S^d1{Rf%oo z#1#|)0DG>3%gWj+xFGa`b7i&`{wq5>|2l#a?i|X*eUmc1I!7KSio+F-?d=?*IrFm7 zCBX9gxr%b&H3hr}A92hNgfALn*uTO{kS4;n8yJcs7 zd2vX8c*(uBx9)n3{)k_f+d+tR|LZ+W7Fz!hV&@8CjpXSuB4K#0+JB_`W09Ve69+A$ zh?K6&cajffK9Y48ckTE7-P&RK>N*=lDZs=ZTu-|P{-^y#nIyzP8_KEF)= z+cx&D(Ad`RUz{R7U1s0Cf&P`i-~zSx7v)Dj2Ri}(HXhL;0ahliZO|eevv7HDU<=aX z8Pd=jUQp`Lp#DeTrtEaio`3*N?1P?&*PS;49Y6;9V;aEfB(6$c2;dz-{8gmfoW2si zd3_$bGXc2zb8n&fg7L^V_h-lzwCVopMdU1@n)iGmp zcMwqr4=!SmxM&j4-+e<|}>g^?=Sj5cp= zB_Xq-RIni!;+PgBICvn!`_RMqeOf*FOK0H_TYg}eX5i>wc8+c|ccy~`{O zRz4N9cawqPy3iAsDOv+8>W(gpnepSo3sQxr_-tUf5|_F1n_VBLJx=z+LMTQ?`Q`e~ z4VM~zFg{7LFI_16#Z9=2`+VIf=es8lwTD+;T1-mR=H)xElCIMvs3aCS?1d8qaaDE` zOcsP*M^Vbx{FxpXBV|g1XY_?%98uTVZhCtXpH^5jxk7%g7-pfW?0>3_GJ-bN*TsMh zG#HNRm*cg93AxFn9@7wQ^A(Sk7z0})|M<}soxhNi{k7wZ<+pz%E68q^yVZ!n{1r1 zSN1G9a|-{0vx@`pBRSe}KSzB8mdmWPfBN3ibR$TnxCT!n4_T41?rIDJ?NH>iG{oa9-pN4YjzfW-`B#{-u{o-CM8d`8 zmKu|ug{VjJQ;Up#oy9$Eo%5^Zg^xCCb9~#agRQ;0?f8p*bM%wJ^;S=5W>_yr-mbsi znXaLMj!{}igK+}0&sok2uEr+lP~{CWqO{eqkOLP4Av9IZk~-4Ss;Nm!is_4iIlY4$JBq1(FhP{ZqjM<3d+Z0@Z2PiELH zxl2zy$hTtFO7I#LQDZQ4T)9mFkY)a1llJe0m{`_zquF4NMRo>?CpuM`8S_-qFyZnF zx@daJf%@pkCg6W+`;4g@Whm{As2WoDl8+EmyZt1?Ht+6bF-%2x&jlNIo_7CeC!P}vmH%@kWTWjO$;m(d$hNjnghC8aQF0VQ>e*NTb9@2w1 zoFM2|AGBu0rq*)T_s<>W%kcT?_htMRRzqNNUvI0b{8-nVjm4TCB@|hDG-Zx>ATWsC zlFKVvS4<+kbFTh)Ug;Gc@!SMvt7JJ&IhCOMh1wn$33p?j#qa`m>P_T7LWncv|L8E( zeb`}0my>wM0q|ZNJAkeE7pU2x_fKg5nZu@?-Fg#J8<~#B4BS9h`hCU&wUD6zKK_TV z=U30qzxVZg{Q+qKVyxZ=MF1c=_Ptxx=EuT@o8DH_4R6X+s!6A^Ch=7h#VycF9|0gh z6G_DnLsB_uFILq_$@_9FLVVnl#bo!gJ2+(PxNCJk-nrgs*^qs)hWB){?KDr$Nw+Dk z!^8-8>X6=C$y65jbfzLT#YW?O)-OaZe zAhdTi9{Zt?b3EvnDOctb-lvLaTfQvxCs{9W&v$MAzfJOu>4P6Y`bo>vwgrwW?{ z8Awiv)Hhc$TC4rB;p;6J<(hKOBiq=hk~Li4h9=B2n9pUm^+ma0f&736)1aKD)D7xy z#{yF|D?v#H{{=}lZP)-_q$eTYi;ap4!DT{&$bieSs#~juqDm0$FOn~eBL_k1?qv<4 z(E5x|)g8yQs?e`z^Em~NT%!LfsV8BrYDVO3=l4E|?%{54iEsad?_6?%+oyGRbDp!; zn|OV>Ew4O~?h4G3^U`W@ML|^Kc3=}svE~56e=Q^AjMcSvgh-U*#UuVNZTm@t7ZjS3 z#k>mp0C4O(3E4Vp1VftkntpEAnneMpa8`z~?8?j~S*q#FRblmdmx8ear5IXaP9({47B35-WJ5L0DebyvlZq*kw#`Y8#af5a)kFQHcAFVtuZEcy&#zFZ( z(_nRHNsiV~Wwlpx#sX3IJo(BZi#N8+<*YNRid=KK0CGXQnwUnv z@}}*@ktdv;o>!({m;vQb6%r4eqpusbzmx2s&^!Hz`TX<(C17R3oIwtWA2C3#2!1IF zQKr;g0j!+~P$7}+-~A&UbfX1Eo2B=65-}6gIrk)S$*fa_&_yJ(+iKex252aWSU_O8~Q<`UChX+p4s+(iUr-WWCT2h&$Z< zRqaIY$u+n-uOwvSn7MqC6oQWd$V*;8c23R8lePdjEiEx4NQUlDy%tU-UfZYI#SgsIf1#EK8GzxfTgFcQF3D}@ix zPErW+V@{#hRfS^(+G}f9G#M;1mw`|6C3;i5skzFf26Eb-n+kX|YO3VDI%;Q=TT0sJ zL@LIacIJz@&cRF~1h<0nq%2=gU77HDDIK4FR2JM}nx=?{*?#^BEdbvSuE8mX0DtS6 ziDwyU^#vtHewD}fx z4co`%pIkv8cq+b-%Vz(G_zU4w^`eJ z4)z&V6FHt0efmG_eFaq1P5bC72#O+#AR(oIbb~Z2A|N2$Eg;e?ox&=pGzbVNDczt* zhjdGq(hU;Qy{vZ@u-@|Q^ zBGWA4Zgkb)oa{LjHdRaDGd4JjxF1X9C6`~ZdQfd^>8s#+&igjvaK`;Dl4r6A(fkWK zLG0%6##ei&7)m7l$8EJwoDpjZz=tY$&(3D)q3-GlX#wuoaJXjsMYZ-aL{Z!ew<#2!kVbs=(g;cSMV$cm>dbn>Z># zG@}?k|ADgW*raxrX4RC#Hv95w(FAIB1? ze8X7O=T@`FECppGx%Jp&2b!x(Dy3Rvj~8u8o;zkB5_xC5Nf*T%wh1)io^dfS&?1J? z69#>XFGL4UPi`<=@2GPSFi#L>F%!{bKa z)7Hdu&sGYc=*Hol?3#TR)>MLZm8P{m$`9FD`%op~#;+Up5EE&I`t#1msz!Zq7smqC9A{@>APP zal^`q5WCr3IZb5rBdE&s?Yn@4_l1GN1>IaKMD1r!r{;E;YBYYQF%<(pjD`3!&VZUa0Zg4e4>@#HY< zy%0`Az>J8z^8uk~kum)Bekt~(#u%lS!U9$krV=kEWZ$>UV%ytdG9YV~8jtRJC?*>D zX!_%3;ALE#Sg!8o)<#^-`^sw460h4t zAi)^=juK6PKG$8w34dM(h0>WHD3zb>0CM>R+jPpcl8b|x(3R^A9|GbGy!$t?Wo8em zafQt#P3qjz_PhmhF1c)UZgxlx?)s#REFdrTGTQAvUQEAZE?sTwrQl3;@k#$m)UO3m z$@%xCQY&*JDVadtb<{XaxxOBl()r&!YM4S^>Ud|~HuhN{CdzAUea(M!vB9*cw(Zu+ zy;b)(gS7hTk-LO^me~F!lrQhC3>H29jp=>|QpSG&wU~}UF=SdS-0{@}f^tH$U^S;A z+3eC`^q5vl^9BPV3*(xHYhY|2xmRNVr}ix-LgQ^msk7@7m1ghK2;M(DXe9fbv)al7 zygBGPKAsTQV)$6xm~kI+PNoEy|x4~=5&yK*FN^xPz65D zUHQ1J35m9GA^t{BjzZy&*2gphKllpW&FCh~;=GZNAuh{l#JeH%2nr zFT!sHJ&t+u->isCpcHGbP>Qw3bH8iGl)9nQSAg6< ziEI35a2cWQQu*nS@;rjjg{28Rs%{wkK}WUx%b5w zjdj9uhP8pXSY*n{JGw4&d!-5bi7F$zfI&2_6)12kr^L4dA`nQ;<7PtHntSBKwTrLedwb*+SJN`$234RN z2Po~bD#8SwZ|?LRa=&m68JWA=wPiZigq$VU0$wY?MIfOMU-dw&72MZWxdN2x0px?X zbT{^n8yX*v04GNeER)mjn;HqgTif^xxcug5E{T7%Q^`gIebd|e1Vtc%M*WnOWtSPq z_YP4C=1e4Ks=|9ra`Tgdn?}((w8ZPjyb44`&Ww~Vshsytdcg6_N(PD*m2Ai(d9hpN zQD-`_U*yF_6r@8uX$U*;tb<(W@=EVMAE|k}@X6{7XOzR8P%Gdl%ja444uCeg z(htcA|24?$k4K>SBvj%BKo@+_W$K%Gm>cS+i3_u6& za67_B*I_cSph3gG0AF1&rGJ^lZ`h&z15tgMaLR+%Znij}H314Vy35zde?IfKu_i9` zRR$mayP=|*fmH0eYyU~r&;!ZFUnu2Hn6QPrxP0k`Wi05DWLA*-6 zza^O5sr>&I3(jKHcFw0hYMc^e@HTStN^!fLF{xWTq4 z+D*^|@=8AL)kRj`ufn_#>qb&=%Ji_YvHn_B&pcHLCu(=O;I2(e<{^KZhnH}3`q2}s zvs1Bra@WOM3!b9S#S;PJxx%sP9~fU{I^l~oz(uj(gW4$2X9n}%0s6XQ5$DE<{NWf$ zjl#~~IY$1xlvMM-?ifkb`AgwX@8a=$nJE(fT`MNL-*;c)Y5XG92_p(f4yt?tKN!HSP zrlk;~hma{K3)&i(a$ob7=7?<7K8V7|iMkc%e5QPjk3A;n8I=7>pIg0FrfWcBLA#;U zsKn0uE@QqdzS&@lbd9V`?3!?S+EZRswjSazjY=I@k{LHKW_6UHWZMnr32zH^VTMn| zbLeEqf-#c;Y}@3);U+wRcUk@fDUzP`#wjra`i2+aorx-WK+6vj+KLon{} zH2m^%n0yGgQ6kx70fHlYv9#}#DlBzyXdZT%fs$?Gp}E^Q$CIQvQl~~PkpJH=5AHN0 zDc`qa_Kk0lq5Uz(Ol(fAT1v(bGVSkzzGXa-pdks3Rw@N)9@~tTi7j$M z%_C>Bo--4xpDpsg$ZZ`?75xNrfHTagOfzTH!0utLyVu(;E%>u?>9kR^f&HtMCNNm~ z;M-SRAFhx9e`0|h@qrsK-JJ08eDi@WP6CV!f zcIvdKnr!kBA%x8^eVOuU0&N|wPsM1_uGjdl%Gk!e+Rlz#FOym<>+shwT~P7pUq4R} zD5^3XQ2VmU)dR;pI*lO##ewU~Xl6j4~|4o|7ph~a_etWvRX6qH9&t2jwmUCafLl$zoXt>TexQh~& z@KdH`Isb z3`;`Kds=kO>NcsYR$s<@6Oa&pO5ZK2k|hk-EMf%ls}^)v`+6Gq!ab9}Zto~I1V=rP z&@vR^zxSm9!->QmpT@gfJ=4i)*M@&l%7mH4FZq*ICi!cND*ta&)Mc~Ds(z|pL5CHu za$#{3wxtQ>2O4X-J_5Yd*|QZXUuz}6w9c!O&OD1#aH1jejJC&H23c3>dhDjMZw03Y zT$gNWe1!4rNKDuf!7QdJ)P7skuLhP%@qk$JvOBf@vNoGD#8$7IksS;+QHRP^!?MvN%u_;I0`!L)wn8d49Vw9;GTcZm`U-2T|0AB|48Yn$>cxy zfk1+MjUkVGvmUy68Dx9`@S|vj?oY<;pUqU>OF1@R9xxN^vYr?)kMz`kB+1NFM#Xgf zOmv^gH9$TNWIg3Z4l_8@flHa@Kny1xIU{N2jgqO}3YVYho&)SAZpm=pAFiSf-M6Ii^#g(l_B zWpLG`OS`=VDW{{ZAJ=pKW?c(e$%GR4m0gcT$u)AX`~#g-OrEi=HM!4SS$={M0=P9yZemEcQ2 z%2tU#$ucdw^%gpu{!>@McL;%*il_4X?)>~7awfbh_a~k4$l{+2a(p|(t-+_M&9YAL zA-rglPx^e_)yTftR0}hbh5g5)cT+?m91quvX#)8t3mMvwmxd3E6xIxz0zqnm%Ia{spVvs zI=e@8?HVR&5GJ@UL<7E>Yq$HRAufH<#lGyl0nEfnLCD4b!J0-P8Ga8VCqo5&J9pr< z0>en@FTt%Bt}@zoe3kkl6_%8h)t7sy);cKmwL<%V1b;Go7wzgP9v@k9kf8jEM2RoU zxi7jXoIGJ$XR4smY`L*|Jk55UV%@ErTdEbT}B)N%$;iXa2 zqN~Q$AuX;6opdBYFHgm~*{V!%&RC|q6VOC5QSEc@ejexSDH~VGspaA3-FIK-+v>ok z)xGf5H1i454G6sIjG_2=%q{47ZR7J?W1n-V*gwFu7aTFF_^pFpIpn-(>F4JsWHMmv z5+SZj4wQfxMB06Ap&QH7wb4^a2p<_bSh5;Je-G>{kLw^D&PP$g58yQhQ;I!@Oz@}y z#R(YB;I5gp83Peb$kpKFPjy#9{WsA9X}ijyW=WMCMql>84nAyv~` zZ9Ix*q0O4B!hF73+u$Up_!{&d(w7eZ%m$BCf?{|F_GlNpd}@n-u;=UIzlNyJDP_SK zte^DRnhCLWW;{Le@*P<^)Z?YtoJ_ z!7_``ZkQvYA(+`^AXE*lWobN*?w#(GMMPCSZ#9RxdZU7@B!;_MLnSI(W5MCYgyn;P z#JyO{~@s?0563BDw%=+Fxw zuZ;5k?x%yp<$cOccY@={;eB~gr_|2pN_|fGI?=5J@O@)c)bQBaeC4ro*Z{CHxg;a0 z_Y(a$zC&!XkwotIHD(1g6OEA+<@q1V%FVQOC>|BMI^U5=EJ5oD>hWC=3iFzTUN9M@ zV3lynYqZ7O&-8O-Yl^KJn723hlx-FlT>WKqn-^Puu`U+lYLTi&zDs+^1|xH59j;-A zRc)?FFB0EKVQxw$(8Wl`7VLcblT)8;&aXTu?Pal-*0^`=v4~tLdj`22D^#N|uC_na zDPc&*gofbdod-%61oBb^g8k=a1s7?1Vk71c?Fs_4rn1Z2Q<)|vuS{Y3C>I+Q_&lLW ztFcQ%QfP^}Q#dgXv$AI`EtO24R=RL#1*e~XEHXaAuhE;>t6?_8Buwz2BrmGfM(|v9 zn*J(t82-%j&*57+%QrU=U#UkoxY6bwp$9h#`-aJwh~4VqM{-|u8fk4Ad+u*>PI$L% z@+Rf%)!--A%cT61%+Lw8Rbg=OX9?<<4C@_qa_Jwa8z`N&k+J+XrIvI@6=Z8-Ws932 zo$r4vXp*_$qP+DfG-=T8h1xUo)9-mE@^DBT>J^fi?=^m?oU(Qm?SM1Drwe(50^sYk z)p2_Wzy1Eox~D-9(?346C&Mpwr4(*&w}&1@oh`| z+&b4hnLsR|08v&mKdt{@sef#e|EaxTMXgbah19MgF}X^ zJxL2_{zu?K4|eHPe+%?lFo<%t0<9!`w?%8nBN7f7yTJ)lvRazgx+9 z5}{@Y_Q%H&YWrPkQK%DKOC-t_^3cH#Wog#PIC6~qQOYTXpK+;dCXZe_N$w3A+}nEq zY?x?#zinKQbe}i@4y3QKZFnZ3Bf@U!K z;rp^VUbQ5d~GH0-jG?t=-YnA zI2^lHKf3IdTzk!dV1a_zg}F~o5fq3wgru0JG?JDa9-520EbC{Le62#iB8>zmt*$BT znqNb=RP5-cpGPu*6M<0Q$6%nTP4Uy<{&+R7JWeEB-BQE^o$yv3;xm?zJMTea3jKUW4_)dKMNnHJ0OJGjNNq`OqJP6#dra{F?vaCs{0g6a96tH9^-A!J9RAZmh4eymeQKQ8J5IHzlI3!{zAu$fUO@ zwwe?CFJB)C)XFM1CxCSYoGkwEDL9?@#WGR?chdONGY#4`#qwnE1YGhr4s7>;zXryc&{jQ*u*maG`+8S@k{d!C>?RrFg<^oiqn1by3-;U zI&VGBE`Fnzq>rb&BPs7->{G*aV}i?W(~u1qqji~~N3+I`erD@>0>l=EvI9l1&43ck zeuwBkfX;Q(U8CUzhWsB>H9i2UwvOeF8C0x?DoDL`RGScfT-Ock;^(&K)`P(L>FwwL z2F$Hb;s*K#{|@TMz%v1t0{#pF!MFL3(Wr}pzLxtDw*P0<_?^PfQa z9f=YO&*0H(57X0D$z2+Eu_;M)^Z5<^+LeQ9xpNN`Px#>o+7iDJbPmW)8qToy8Smq7 zEYS5w1=#~Be8dvg5!oIC(DTSfoI_&MV+mPxBcS z?Q+a5WuoLqgH-svTS>+-B%B#a1$YF-6-OPM$u2I!eY`@+4}0|3hzG>5K1rWXD4~~8 zrkdk+sG=q8po9kCDM92LdImVqm*k!{xTm7w?of-z8p%K!chjb_j{@|=Q6vr zR=g1sH{YZUnv}Y~u}(MTrq?zQyPS}=px%LoICxYTI@z2&Fx6#WK`+fB7s>9O67X1hHreO#%GGh{SvjRGJGYJV$Z70v(sQvl|*1VcgC&j z)!Jslp7KydzAU_WQ!UrxQCVa^!)vnHOBY^dJt;-&vDqTpNjD1Tx$bNfOzDI7q3oS9 zxKu$_SSu{&UBF}x_My7NP(JPz@ashVtCe)VqtQSMIRy+knGqiUVHOl)rCtM$- z%<Fz8&(OYyK>L-rs+7JMptbV^p&{#agh!v}<91N2(i+OkwP`G+b8`gGeHb>5y9& zvIK=mE-R=~68PpXGgK2l%GiE1dd<7bVksM`ukLj@7i~}3wUWp*@;=+hu4k>Hx=EI@ z&Ln?QbbV-FFGPG%>a%onQt43Y)(-wwizHq1rtIBqH-|2lm;(G(r^JR!#svF!nap?C zO!>D3q{ExgjFlTdslDV2EKrao8Z+6C@h79`r0&Uv8D6TjVh+*bohLc9j(qgWxLty? zh-B^6gJI1fH?oLt4zJ|3pmhN^cI3Oa=u&!hNa;u0Ml7!H-S39TJhWWwnISd3j!*Mw z$xOxavW;)au)NkvZSLOXeA)di*%@to-o_9aL5~1gGZ`)>vn1qdf-gPxXh-YQ6zN?u^U`{?=oR!*C=MK7I0K=!F_Q{dV_ zjO2u``;0pS+bK{c%2%{6*}pN?`BRWw+@n(1R;c)>zpY@ASdez3yfAXs%-HgJ$m9j* z_Y59gb14(OMx*5`B5UN{-;fl#L(<6>_r{+a793bHSe|Qs`MB8N#n(oef&!x~%H62C z{y`ldG*cDpIt_XHL~UH23l3{fhiB8f&D;mBCG`!YPQf;i!TlYa4y)xoO~H4c{Nw`B z@-8i5lKyq7cTMpjcOSG=ZBhg#ls8t7Qucm(Oq!*$s9{|3PS}<~U_iJC82HjZt3GcK zb8%I@_mQq=GY`1;cD7Fo5;Dg82M4lr(M`GZMX?6ztiOf05LakmK@)8@K8PN-K;lO6}^8qYDYUP1i@(4RaHY*>cj1T+#cj1RRLPBej$ox zaQ}^@G;_;fuhzX)`Zq>Yfu|l=`|mqIU*OkWR(q_qx}3MLv+p0A6hHV)8P4QO{>nn$ z(;ru`-sNK1gxfXkSh@l!>PV*-xXKOzRhz>}BuWHA=U2Dt!wQljA4;A1K;SLbMx-Jr zGeZ$QZgevz##od^EY>f3t8(5@_nb;%c!yHo(0orpp(ZRh9^CY(yQ`_gV2)jL76U#` zi8#ajOdP??ayjtSExEYd^V)7H0eU7G+NQTw&C5%bB_578TFF0QmBAdRkIkHrzM|ZB zy%)BGtNHnTp7v5|;G1d)l8ZhImNQ()>7?Pyr-AsU+aDL-zN?FrjPr*shE*ax)nYGD zASl)#q$1kWMyahjVP=V+2N#MW!^*F*Sk`29!wkx4tHz^os#G4jW36tgI!3UWq=jlH zHnORcV5l6_5>>Uk>ceyAZQ|3!3+52|tJbyjS#{dRh z3piRx@A7nEzdNz!u;*;ub2Ya4yJmbH`#itY5&y&V~d(Z;ma6ILFT)9<{P4wox) zMGA}fJ>?`SxnW&FyN*Dee+z78bTa?@^c)YRC06~o6Zl`vk(+XRfHB7W~L`9sfwRSg3VPEY$5Ew%rc+aR*Skr>C> zmYu&t!co^YXM%)D&J1uXE!AvgS~=(A%v({rn_dC2|=T3xA$L_Y6ldgy%{{C zd&_}m{?kQB2TWwwvA_qU`Vbwkyiwf8sG{lJ&=;niXvI5a^YY&G+KLR9;9`edfVAeD zDhU{VT>T2p5Obz$`n~sU6*<$Y>q7>Q-)00_H36HW&pdM&C%>&2H_hO( zSRG4VQczG7ao=0iv_4_|%ggi=wxx;e`p~sxz9rQrHhMZDLhtS`wg8te7d7WL%(Q2O zm^HJM3{O86szqMi@u{|y&O$%!YN7Kg7QYd}xI(nm-s*l>s4AZBq$=9GrB=W&M6uH~ zWu8eHKb7L}@}l6vOiGY}N&4s!Zd=3kZ zpUmZEr@&D2@Y{sY(JJic9fb_ib{2wm54)waV5yAy*U{ft=i8A?$7LHYHb%X1l-;1~ z58^P(g!{B3)Jr^AAf|W3?ieWtULSD1O4yj2JE6uGjaxk_`=WCMZBG1rrstPuQgOYi zs;N%bYF+GyRd1E*J9m*ZJPe*b>!!p;;f|l_n)rFl^p@(59IPyl@j<99cGL?T$oSkC z*QInqbrxt#T}H6KvufOAmJO8PB2zhU1AG#Oxs|h>X1XsJ_%8fwTJ;=Ld8UAYEAxPk zmj}nDfXeen)6saYx|e05a5_t zl*Jh=)qHcaVzRwSgkQV=@6q?LS8Qxr+QDiZ<%r2OvyZaeKyT2kF$ z9SNLzMDZ?(v7c>f+If50_bkP@2jCzwOxFc-3T z7C>9yp%fuL)LD~I1fRg`ubjUO9O&JK;A;!eKZ~skgh4Y1)x9XV`2!%>R^iin@|u;a zzGk|cXF*nukIR+2TjZ<0i}2ryUv`h0cx^Y}?14BRq!>Ov2uC}c0t~UY{Z%Tl`@y`eS@!bD?4;lkAQU`I)qC1B>z=N?d{V!mEepn-K-Ob&= zS@tL2K;j_$>G@Pv0E`EI!dUzdh>LH#Q|`g>k$Q*kJ(2Kgu#!A>a_1j>yZ>X2_=mV>=^7K)C|$==!600KJT;^aU}3 zzo2b@ObQ^A{ZCrZ-4*4k+(#dSk2#DXT!NMm;_FBTMB~pnd5Xf|H4NDxIn2!;bMmbD z9VWeOe}NhUYFw~i<4OkUaTI~yz+34bfIk#D9vLh0OEnw{_VG>Ce?z1AM?o{t&*Xb?!d8F7RqRDxS=VWjXqo-k5o&57s0a1Q%UTFbRTZ`hLgk&+-YwdTqC#a32BD zf_0FjB*+hQ3~JHuki#t$k*9%gR5h2A#{9STj`&!v4$d-J`3YKR1@G8}uCp3vuYh62 z{Wh$>j#Bl6WydQ~qTxZ8CW?`un`bM5XvhYlf#wfHh@a2{`F8vHW640kd4#J``1F+@ z0!a3l=m1=PC%XEdME4v;bbs(s++W1ezpxQLe#H?9fV_@lMu!JLBBHnz#k;^4G!s?q zWdP<%Fm?D456ck^QC#VK6NAgpV8Gp2u z%G3vWk*7N|M3Z#pK8f*<4qssKaJlbXH8r-8?)7X7;Cl>bmlX6ymh*aikzPVGbD@ld zW6~)k%i1ZeH-3d#snB)gH77WnAa1yT;pW0uGT}#qcj*A7lff%)@r_jC0prcPbVOEF zLg}dqrUzTyD_#gx>Z&6_8~Z+J?~E?;2%hQv%e!w;eznoNJP+<+V#cqC+L5pNn1cMP z=PMD%lk{06L}jc8n$;sR*^jXt^Tp9q+>?2H;T;-Dw2k#q6EpJPTKrZ}%pO`3UZRIH z@a{9b6}r+7oyT`;Ow`-}La?$O=+2)>fyMT%_Rbs(MT7e>uo;A$D^^^K?WW4!YV3z0 z$&WOLizm|@u%JIc?gKiX>UEPTg4JUl&k=kvaRrC~d{?k)X#)EMShjwa!Sb@UAe5?E zH0k-d7pd|<9#mNM0qY3XM+7knBnt2v8X$*(%-TPa!$&>CK@yL+mQTbCkDX+X1yW@j zYW83yK=pjZEp8|KNDQE+r#nm5kh=Z>!xwb-OcpF0DEW27Q$MTk7NGE&xL{xA&VZ=> ztON$q^>ZMA96v4}^#%qeU?ll*XYW_%!b_%b23A_s7k9U!LAs3t=*rJI0yhDQ%q63P zew55W;S#J#05j@OMFBKxXtIDT{t2#j1KB_jESN7Yz%T(Eg$S^H(ES7ZDCnq>Z(vmjzSBTK zUQU2I%dvX}T=Zpu0_~wp(K-^`P$fHqB9(u6{?QkW#M;|#gQ&*xM|4hB0|4J$PV}eu zQ`>4=e!tGOZVSs$!@8@%qBo{Vv(ioxk&ua+sFpB&S*raNsDbj*`~UR?|NaHb(DI?J zosFTM1?m$_-xQ0Dl^u%}3-w7r0E@JIUXCB%^4|JQB8tI)7(Bb_bX9uL7C8{S>QB79g)X?C#<0Dbai^YK&A?W$2$j%1(V*A-z)X7d< z(GJMVqmc-kTN&%29(LT|s0ix+sxD^(Gf*_N!_oxLy(@;rqGae~chd2llXsDmcL^+Q z)PR022B;DLSPYboRsy-Bg+NZv1|`prpTQz+_|Qb}4$KKW0W}ETTdY`IoUEWLurgTM zfr_?ZbSIEfFtml)+ZY(4zT!R^v85&KsLBcS#BE^qR>$iP7_5z+rL7g}aRy*T5mEfH z!YGK}L5=kH!Ty1#zgbp(tvx(P4+d?2E;9v6x@LH6mx3yastMMbVOFZF5pSqMr{(2^ zgFW7LVy?xfRfueB$5E2H@SFyDZ# zOq}=3l)VGgC7rJbq;(pnXs1FR~JeFR&|F=Ni@ zVpqm#T@1EvYo|#SC_wv4_H)D*KHei(h`7hlMVjuquAM%%;59yWa}^_lU^RbGFGJ_7 z`1?Ffx*!@kx29_3oo@j})lx+AQk70-&2#O|j_n-D3=g!Q?3Hef50*nj{~b>}|x zuvx1v3Dsk#uo_2^7q#J;_%e6UG(XdB?j5Zj74GO?CjCG~Lw#?^-BIpT^J|GHWD6$; zj;EHD#ekgPZEcCWm1Os&-n|wna~x?Se|kFp-H6TI`TLUUbHpFEcCQo9%Id%&5;V7u znBlK`BozIi*aAi6KY0a3y{IoW0pb7n@`x#aFwkFjQxbqTY^-3YjSrTcjs4I46&p7Y zNk2ANY;2&9zu94-=>KmwSc=kmwq}6+Pk8gk{_3uX(#h`XCmXP#wt5OE7CM>V_s`$Q zA(te@B*Y-6PD3E4z<hd7-z9AVPTy+hlO_$=fWidJVHVO zysKA{z z{cr#KUIn>)7F`9S0`1fd$mz?c&@P|)UI!ru<2-Zf=-&^qf9fl(;^ld!O zVO=F4BqFB1NkdCV&&hR*n}?TAoS@cnbWGH|PMvlH zFSN_(XRfnh;0nuQ>RDg8!T$6t-ksq1_tobpITTj$^=-Pau2OMMP_Ln;b~Lm9+QdBm zM>G35v0w8Vgj_&71(1h!83Kj$COj22)yLBZHrjeZ;E%;72Es)y$OC;h+HYUVjcSRS zm@(DOnak5TQW!z(CsVR2@!(CB?l?|r?yn|ux-s8}kMecmOK>YZfcKOGzs0-m6uagZ zqP{@3Zgr#l4QEI1T)YS;4pLw6(B-HyBoSJNuW?O%hkUs;8UW73#Lzs8qh8#kut$*O)8yJu zm;O?l6JArl+IWbLgG2+qjCBJ+-yz3OG)6WTZo&@cyS5m)9e3d`-5fCA{aLpQ1OpP( zl5bvu?&8p((nv!OLoMdVcDfP|ZVq)-t|W^0z8e{vKnibtCId?7KkADAIRz&WCo5f) z2ppZWd;WB=fHpYtDj{=x14@bCZM^6FU1|DxxW$g4$q zIYWXC5!^9Y(C|<45!g?xJuvkds*&_!xl?UQn^a(NNa(H*pP(*x7$hAO?HmprfYub$ zA#T6(fDD=8Qq>R$9F@L{=8<0 z_>Syu|6QNx%!{ICnk=bYZBk+;lBJQQbX}5S7qO|GvNgr*P=Pt8Q6}a8OD}9x%w^L^ z0(@YT99*Zl;Id~>J+{tm9Jh6d2@*r?$L3Jo1HL5*zc2ZJw+|3`axTXVc_jc7>7E4A zdEkPDv0I|fVv}n^ADtw6K!)sfHt-DMfMMD3d({l!3*erxFewHS&nhoGff@x zHQH7#0@j(2nW7%GTQCqiLkZ2P%@Fkbx9!(JO5Goe;*W(L{?F?8&+g}+-QGXD+dudI z|Lg|-pWh7_&+9OB6$~{L^f3GP&=X<3j&YZ}-F}a>Dfi;ZiEF@QHL~Rz3|D3it$6U= zV%(%;dm-*Kx)RYc;Giigf`6`Al!%-}UbDV1DQM3|7#Js;b!;ANaxGR~HIFY|qrzc! zJg-bbz8zc4A3%NfCIP)0&W-#;5gdhA&f&=dN(Rg-wv5kRdQ1_kc^p)J9(=w@iv`}f zP$JK5oJhX9#b%VC4*6&PEE}8PsG?Ko=aQ}j3wB4VREgEU{Stx!l9`3 ztLlf_Q!kVA^7r(HncHK1XE)m{U0vf1vMGEmeOOj2?84an0;gmqKz_R?D92J2UErKL zjphn++O?Ug==wsZ(VUSJ8D4cymz?=s{e1%lEQw5Q_7rHyNEJH<|I~H#Bd42=VC_G@{R5Z(=V+qaXBzW{IAVUk+|{c$ z&URGjD!hqo5m^>@c*ZPz&0A#B;`S7c%^Y$MzhqTG{pRY-RLe}z;OB?nR*A57r~W%1 z8G9Wrzif>=m@xbF%)&boSxSnX!8z5I9>A%0+mOT&zhhBll2=8!3a5I1K!mC~aWD~m zD=WbMT#>*312VI&M0YceGqf44p^t1i{8@zx@MW!d$Td958SQw@nkoANt3l?O>%dj~ zk9Z5`O&{NZ4Ly+gn& zlSAcWoA{)D#Wo*aIQKzd_9o~4Z1QUKxec}94j7I8HD6y+8Ib#+Sjz2zbK#Oz5f-1D zi7OY&(_xn3g|w80MJm&^cZPZ|#a-+l{Rlic6$(#>??^k9bdVwomO*57TIgX9BC|nO za=FI-#zW?EOA>=@QQG0BrI)KS1>4=|la>qF_6AI1mcFS(VaV__DoR&vWyQ$K8BZI> zPm~oU8w!5P`C_6Kmu=S&fV^pasG}Um-#w@LbWsd5+eQaC^Z?H{4)J3V*9U}K;}*^E z;;~eeYKN~>&KhsT9jxp{3gwbb{PWX4(D=Vh8)iAXI?&1ZsJJ}`*x@;|W`VEJxkTin ziYwyQPYQm6xeFrTYUG8{1%&9r|g%yZe_aWmMfk0{o~kJEqu9{U$wTX z#>piYxo~z@BMy7CvKxoF3hYu}gzaNsNjKDM&?bDe+C=%<F@u(heL0^)FvwxbnZ&7o;+rELyhID|>EOWVKdwhpxMpNZB_S#tgO=}*7m1n+Zs5^wVGnPE5SYeDHr z34tb#ryR)@#ikZa=i&w8b^vX1Swl`6)VS}+C=rN#rF&x6L?*5t_kyysTVXcB+V7-G zQPAl0x1-T_CQ@NNZo0e*5_kNXQn$P~XW5go<6d9MgC{c|4@`)s^#Dgv;)c1o5hoZT z_TK=At}D$5%Y_(phJO5OYP$z_Rfo(L>92=&E4_T{SluCmn(~E{T9WZ3Y4dciP9_e3 z*8}c{)2`ZqKV{UcDSEbbU>WIEY^&lfNH zuI(URMXl@rGgFt8POR2mhLGjhw9Vv=Xktpr$WH^`A$_9Aoy}|!1Xo1vcSwfc;mTK? z7@SEm74lv0IZGFuY}HHxLCpTl0l(MZA*8WOw?KB3;-j>2;KLv3-~LaPx&Cz{u%`=5 zRO`3qf2bMX+GYCSg6F3_$&pna%Q0I_O~27SHv1}YuDi5|M@-0*>(egPIcR>WK$+Gh z*&^LVjh-%Ehbv2VFB@Lgy>Ez@7lYvKqd4l!OqOc+%7q0Y!Uwad2?0;0H=`!vhlfoD zpA|pH*wrozl8h-=zbHi=)^i&=^HzsJN~z1jp5gV);tTh+y~M~~aYB_Lv@g3yw4aH^f{nzsd8WxTzQbklR1-#mwPc%l zHPGM6vGWIQ;lfMdVNRX%qOMGh`##VSXiK?S?||i%M|XGbD%hDOKAtgXeA}iApWIh@ zl!MeR55%~qu@d-|P-)lPI)Xjq(_AJ=pTZ{@2`;9_5!Aj8(U29Rqd6b=nd;>a8dk;R zEDJv&;y<|SH&*DGxV_>T8TR+-!_Ba#mP`hV8pNiywcLZ$FVvo5DHPJC`A2Ja zwo8B8g5tDPTEYv}*czjP-Ut=+YPONq(J?X-a}%LECY5^dza}G6V&|#GC|SX1S}oX) zxlXgx{kR1AHJdb*%rfm_WL~-a$(m`}uX>`SNIOzes2`*xMtmdBzBj@qFX=8PDD5&| zRkMbJa3N1a+BbF}^U8280jKo%{h&E55KOU1&sO=ZwYNK{o<(<^Xds!t1^z8E{4*qL z+J-LH+<*8=hv^#bNAItMEv-8*=pfBinb-WpRBlTw-EN}^Kb>h1%aA~58mizhB+q+; zKP51Eik(1l=*g6O8CXC^TmOJCfk*ZaI6sFC_~cSl%aHW z3|CLJG>Me%t_u%aG#nt0Ufvz4Mr1UpI7`-DlvUvC0|trAFrPRFH8`f$fT#eIgvcOu zeDp%i#qLMYSNB?8ic*L%6_NDiORwLoZjCk=jbUUL;X8LJy?RlDk8@7AKQO@3tIfnF z&s#wgVk@a{#jezQa8LizxKZe$RK%Tg=KdS%jMS>KgF|;p!j_a0vdeEb+3?(m^v+QC z9tT%DQKe_(J|gI^yFo*FU51W`$bI!j7Gf?sY#T2TYQJ7x*4J@P<F81>j8W!VdZN3VNCNWF0Q`e4~_3VvP5Fi_y)`uCMYFp zh%04MV1u>{H_EL9J_O@l<+r(=6giR@YVOz<}X8 z$c)g+hG^qy>#Eg|m+g&e>n+G<^OqEtZHRZ;j=dA^rdVagZVJl`^_QoJd4b>`k&EPb($55=mey7s7|jcq%& znk$iBk@L*B!EO*ucYdv2$@GZq3;s|(-&+GVPkn?NB!oPI6D(WCyfe2|vaPwpy)ZN1 zFVRo$-);TGzC!wRGF{&O+t+&PP9N20)U7n>Pn;HIJr7L}P3zK>3fBtn^62?WKh-{q zmaTVzKbo2oN!sS@{Y}{9HkyEuM6{aZl4-PGK}BZ7HP3A_#I0M;_JVA8aX@HF%-kWI8|fEVoJ$UGmH-hr?$ATL zf(br?HVpVii#p3r64gV zAPrJV4~VpYbc2)0`rV;ghUkfJs4w~JLt8w;q~G-V3V&8goab8duToxi#lgHd8+B~)^-_Wn0^h` zTGMjD{SqUV-TZ~id(xKEWw?sTY4e?d4=vWGL=dA(mK-vI{}zB7F|o&1*HYD*p9h%B zT!DNUqj|!}pNDT2ksocrRmaNSU~sUFQ|q1V%DLt!*tD%+Cud0yqoC&agYfAmPjpHAx3h9A2WpBL@j!LU$(>5_c zKiO-i!sQMg&pCl%JyEo+jt-%Jz|bq|@*f}ty7P=vwIIAIT|kxmZ$Y7DiM>8wsNeu0 z^xj~hec#Fcw~L8Ui$M2mYHgs>z*{$+d*UY9>I&8AWn9<_fs2l&$yy&vjQq_xF?DrA zf_ul_iY-BoWvQ9iW=Jwl3puUi1!1x+DNIx}M^vvXh-hi;{42DvW3kkJdM4SW_pq@Q zf48-ouV7Iv`P+(|FI@|4SvtS&lU<*Zpe#Q680?e!`SrMj2P5OlEU5{z`WPdJOMfNx zwRi*6BQ!1D&N+4eE0aK)0VZ7*g-H-;r$-NqNO5>))d~HP0tz;_JZLhxW?uI ze|K6RTN_G4do;-WLAPjXd5|L+zxpS0pN7ujk^z>EI5Fa^SOs4dt~jK$mZ<@_Iw*3M zSYwE}CfGa$Ka$bP_G2lL8N*Hau$yf&+*sL?E@*Cnu4SpVx1oj znrC$*mF1@cUK1%)Nt81Y_i|i+ocvjyXS>J#No1&w?^w>0!zp(4d#~J+t#~L6@kZjz zI=K>a(h3pEW|d*qUrPLCZ*I1TEtPJtL8ONf%&8@yFQn?5m^DhRLXy3pb#rWEEQ{m} zaiM)ah8}5wj#X859#Tfra4TQ(Yy$VSTv+|%Eh{nW;YLInEh)T9@kO>gJ}>EnnbU#- zTmS3G6$7~q>;s~RyQwffDKp=0xJ?l?+srj3vl+?Ga%Q}e$Ir3eT)S_6OQoZAB)=jQ zEBom*Y1&@Gd3sK0nc0I@aa&2p%yt9!AVw$668%2n#Hla+Fejvxs`f1kxj{jvBm)IH zIU+?TXPC$EYU%Clv5(>#97Lg_;+-7IA5=Md+G*ADiLR<`6LdPAsR+=j;F-R|h}Lb9 zwi0KQB*!gb|K$A9FgQ8kK8Qwlm>`K(R%NyEY%C|U=U9X*CoqLpG7Is9;}%9f(IGRL zIYqsH)|^=*<)Do4vg}xzn8qEi*kD_vWPk9KYW5Y)mr6Qfy`@Y8a>k5SrX_9k zf5;E^{STlYzPx->C=?_p< zQ~w@&9RZUWWOw1|c{mV#lC9~erU1RBoX`sGoI2~;@FMiJ8l1?(2}n7TESlJXLdx))BOT>-w7J67+m2F~317hDmAz6H&}uPHR2V_tOiqqvP( z!s8~n;>{U5MgtFX^X+mruZMGyJ+cAa$(1@&$t#%zq%(hBM>;i)f#0)`E;;dBrw(-- zf<7Y}tt}B_9>oXoeQ7KzA~o9KFpt_9AVf~obVJv>u45wnUuWkEB`!CKb$@~2(66Q- zwOk&>#u)nx`?3sAB1TFV18O(as@O(;7B{a01x>hg=dJ|my(I)04Wtb|eN2WOZG(wn zCmtn_*Ivq?Aeu+`0qky~z~lB^hs0U${{ZFWHZ?2(X{7w!sPjbnfe-Es(d@H(0m$Oq0$J-rs7zd z@8fskE^s%u;3|!{c1>Rr+N|n+T6V5*PJiXpdBnxkbA z{m*oa0co$s_bZP|8`Kt)s~$3m!xFf-b%HfU@iPTKr7=GLBuRD0I#fv$drHfv;bMMe zAI{?RYVy@9x3LSRFR_usFeZy0V+B}&cNuLzl@eh)XiDAFolmtf`2fQ@hvLTWLx-~P zM(WZf+kVRLBNxkQ=7tv1eEdH^Sirp0Tz_uEjiHSWuRmBW zc6OwXqAjZ;8G<{4| z@T($Ufnn_Tg^Cn~q3=GOQuyi|3*;;1a7(<_o-~oYMYkz{8Xm;&tRLd7If zxkPe;Li{b1&^c4SqQ|NSlHG71f5=DlPNDPs^F=)L^ayb1b+5l_PEm@Lv|XHY53L>d z&j^$Bkjn|}RlV|0fv?}JGAK&0^@=oJ#Y{Ih3z)8kmDW5@gMZ?ZdAd*MoM;T8S44(r zAHvA2Vcy@NsQ|)9nH$j5C!;s>|s+vg-0SIrvOOe3TIwrhDvp#|Cgpu-abnceL?DieL(79^-$&mB)!grcRQQwRl+{^jZ9EpcD>&rB+J`?T|7Z&m@ zvDJLnP*{sY^}so3k3-^=JMEqEjp^z}7Ada&LY9pvHgkryi^o7}ugvn!O3S(=irnzi zCv^B2^AY^+zCG@g{WUdi_skyd$=ez<_(qIZ8Vr$gIf_9UPe%0{+BTA<;N7}l+J!L` zVW`*f+6+aATckR252`sdVsI--syd8bdCC^C_!{OUOPpHG5*rlNwxGho)HHZR5DR+UDK*#t_dpYC$ZPVNQ7go@fNJuAqHKOoFIY(nB?v$nWn>@JY zC^y#?gMx1>n5>;n7(?9VO|2R^6IzjfTRI~#{@EU{Cx{R_LRuP}G3DC2FX|2u!9pcP zd5L$W0uyGi(c(p01L6hr8?VtNgXS+UQe-1|+PZHnwPmKsZE+Lq-?GWLJJQBxaBLlo z6RY^dx;)3VHr#lNTiB+*7qS@)NxOd|vD6W}FjkD7d|@k8hiUz82fjo6!}30CTywt1 z%RXV|%X~q)gF(G~CalY$ahn;eCx8%{(d#@jtEhu6gHGbY#IqIC^f>YL?Uz??&=(5f z3zK6ZcAsSqElkFqrkvg{r{9xpDJ769&qxcbVZW_D``Lx=fLl*Ocp?zA8<@~PQsHkRyjINSDJTIT;b|qra`Hx8$y1TGS?m}Ch3+i|PCk$G-cAuX z+M$>{_-lkrp8Ii`m9MaP3YB!5t4Y~9U6ZdQX(c)fLyVln6dcJqa-@mJ#Lednq|Kc) zY493YFH@duFi9mV3hCCRDep{O-?&xZVB3LWJSDLa47S!%D0^6DkR~1bt@80^iP?z*`V>&iJPgaJeYSpd~W572E$UdUx? zo842iteXWoL&R7d>aGAqb>$jBm~jNa{{eVoVf&Qf>FhS(WRoMxIr`=yqAs)8KH#}C zRoyx?jP1BC&Fr@aE_RSl&Lq%z>bQzkp%UF3?E|o8p6(Mvl!I&j7yvQO(F5OpH+<$l zc|7{Zgpq0KPgy0vH)e|2E&N(mRMEhIz20+>8<9Ob(dKH(Qk_D-K%7e3MbmDS-(J`l z?8{hx$MmqnbadYYmzi6*b&6cq0j`E=*QN_TD|5M|;isa+t*9a%;4) zaIzaZ8K2F`>Sy<^>K#v>j;yDiQl^e;PnY?<`tb0@rZCa#)J&mK%O4;?AU3P@2Acz# zCGh}{c=lO-5q-SB*JeZ$TI*+A)G}Nxfi2oTR5xgm;C-tdteM%U=pac}aBZx7w^{G1)Box(RPwY2W!?2O^Wqz}4Mt-07;0Ru6f8U}j@pH!y5TXOUhE`dSMPMBPY9t7$ zpGO!;g2?P!2u1u_q&pM%m)sHVb*x)(__w5TJ7bsn*1KbmH^i6u<2USDU8i;~WJsk@ zacDCoNB#bqt}f(cW7DUp{!^@{a!mZMAqtn~wJ%0$#wv7iZNvts$&1X3ub>akO#KOF z6E7nO812^e+Iu>rwS(^88!26oF?}U0PUMRcQ^+sT%8Dil^=_v|YGzqdJ898p}YRP*wa$}7CgfW<*PPU;cXBcA_kihv)T}(N`Cabf=NnTl_;D}_xVbB z!L>8H%#t~;cOO3N8y=+gTN2QjJ8M7Ww@?$jvW4?}oi-qB0*U&-W+|m7>a5^$rF@k3 zH*rZNvPXKPb?U2~Bbr$CTA21o2!`%@!4_$gb1qbGMd?$_*A(I@m|KBdXM4ds%_MD} z001Hne*0Z?l2!fEo2D33z!DvvG7e!rKGZ^uj2SU7n;(P4a>2WI012CAy6`AXhXT!Gk@jlgVd znj20b2!-^EZz0^M?r7otR26a3&+p&cj;292fIzkHXGroSi%c}LCuH7|`_?gUZh(BP z&mO4NWZwEXeDa!_Abkn1L|n^kQqcf))ZH@7(0A4Vbk$>O5@ES4w=dl3@iJ|cvI41u zbn#>z%UPNFzBKM~Wi6&+ZjNRoh`Wp5!ZM7m#8g&dQZKsnQVMOm!~X6@XI7i3RL(E!9iTxsv+b z0-Y#6^ zhm9ZzE@M?MJYlXCa=*LgC7T72-M+dAq3ont3uQ<3hq~%a5jn(QC*HMjtPi&s?8h zG~oAM#?lAtY_llU%2oPj?7ocSMAhH&P7fl{DCi-K)6~l5dd^hr4`7?BY1!JETV8Qy z#n0!sdPhbT_nWPwW7cyxQ#eb-SG(J5Vv;Q#;F?ux)7MKq9Tv4$Z7SpBHS-anzJ~pAlflQ{z_AGZ^}vkYZ&&8xuTFcv#G%n zDVzqG@ee_f4?baC$vz%b7q~X%{DQrWDxry(=zGjc%2Pre)h~#SA)MmvMZt1{L(ELG zjLe5Eu2xA7^wdjW5k=FRgJyR-qj9PrzR*Fq)0g3-+8HZFV-lh7hTzU*xammJwqP`V z28nW|YxL}!DC;9jHwbTNZA10EF)Iz76u9wN zd~>{`FU`Jn`a?Tai1Z5T1upX4l=~KhakE9)CWY4azVbwj+vvj~7XA31z)(*|{HmHK z=Q3E{n|s#7@&|A>ygZbQ4c;y#Bck)(HEaxo>X=LLWB)yIl@z{rh=8Gj=Jx1o_$une zG?XP(@MqK&*nogJ4`oS^)#LgR(r}V;#%$Z%AUV4P8^%B27|33x0rWL!`>Z2?Vz2?| zFLfTV#XFXO1X3Veu?1%H<^cld2ZkdGYbPUlt<06uJihMT-xp)Y%VSKbx_jnBe_c7|M_xY#}(BtnOQi+zZh6~wXe%UgFOoT817 zFJhK^`T1nkEQK*_9R0v3IJmcSrc*E?Kh+%Fj#Om|t=_$n_vYR=+jM5@6|#U)HH_#7 z5`yX&3OrI~t-imML4WeL0i2_vKMyj+m9$Is=Ic==EgB40r@rRzDL;2Mnjfg)HxE8J zfGD7lAqf}84wMgoXD@mP?kztB#_Xs%%^n-C>wx`r>lSI`+Gp8Zs;)|L!ThPO2Xjcl zK;M`1gwJ)z9jJD-%drLdP@S$Rnn7tvMTRNt?xU<&c{_BXhujEn)v{Z~Z<)$0->T{q z!&XVap}v|GMCt6F93^9!tuagfy!uZy?{5Wa<>5+If0I(u=be1ygo5FnNaU*c#xXyT zSB$1RBp$p-;hiX7sCnKyNFL_jp|9WL5R|-vXe%8s44$LW==^9kCw)d4-J?0oEVeIp zKA6Ud;zfq+WGCG_DN}qmc`WB%pyhhs!CFRKXFQzEr{F5dql;x;Tpq}r%EHd!B+owW zdpw0N&~Q6*e)=o#smBJtg^uIA+R3*#;;a!%qWq?h8;5aoPkpb~B_<mV|~%I;|%)<=89I69a8NTw|($AzxC<@F>8_`&OB%w zyBcU1ucJKD*fp0aE}8r2d=P;Ax$(seCvgRnaksS{yyEFCb82N z54}GUESo{^5dR&4c53U6K2w7Xn`YPrRZNXmZtt_{7^&h#$<2+G4z6X#b8+Jfcepwb9(%6nH(H2)n1Lk?#v=N< zxYgakCBlN#c3w0-eg}Z7qwu)*_Vd{STM{pmlT7dB?s~mX?d7^c`TXr#Q~)Mf%5QcetuYue~>H-sWv=qEmYmJ zIr@>nU22jutT3g*bN9-Y>U5GP!hLxs!=cuff7HvN&m1t`<80+Y?JN^kjhQ8PHu-aG zInQV3!M1_QF6p5ZJJ@7hQFgER&hHnk2+3(tdZj(?tzYQ5Fg>SP=;*(1I$eZ`#-HpN z+h^H5gv_aqHc1^>FHafh)NkFcr;LIt@dsmID{^vzk7r3JK$t}wCDWd6STt$MaE3_VZO3VgD9@ZUb+qXEKqork!&w*5LH7$4a zI|St)S$B9gd&bH)U+w+_MEmIc0)RXY6i07ppd?l;wdOuIp&-&-zaj1Y<|t3jcNdQu z5B1!(?2@lH9B^6nMYeWBgZmuF0A88)&q3|L55oVAulr~`pnhEj_{zLh$brG=Fh-hN z-uEA%Q!+dGGql&`kU{sCd@9X;*NI-b+)uulOXWY$Nxqx&$;F2>$GMs*O?oMixnd(% z7{&m&wtIwWzqq!sRnxL~s^OeDj;Xcm`?wssWr2)hbloS`jsw@(DqU@IJK*MU!Hn%l{&ok+mn;u#sNfXHPuji(DM^>5B~&np}Ec~ z5B0w@*utpqaP#<4=CGdwbl*kSJ-3qatLp#W#7ozXyt(Z!UL>dH6LFA#!mdL z{`bt&?+q)U|OX5*$T?J2fgVybQ1cp^t_JKzx+G}snEO; zw0E4vDztirp!&XCUF}{|RTnx*5wJrOCY_o5Sbc#eW~^W!sume}UG0Q0=`5!8!%-#A zhRA#+(p07YF@zeoWVM=@UQ8fOyXj#nPN1n7?EYb7Q;3Z&X6$Irw#R1jcii?fKBMJPwq<&-ht~>d93x7 zG8)Hy1=Ztkm?DyHRgFdSx`)MvG>yJp=#Bshic6tY`b|skto1(*p$%bP$;!fM-|&g1 z$vq(jDU0Hf2;$?&xqdC-XWP8z+a1+PJslD$XpJULN}s&VPro@LJIA?iNZLVnhTsv( z*h}r@Woa8JwoB>h2FOywd5N1Uch`G1n_kuOV(>KdyjiO>8*lG5;!)t`t%yMxGcdd_Oo~o9ISH*_7 zMDRX|O`34^Qc(S9W=@;+vbohoY&`l&JorIFRj72xT!;XgyB3xS3(dr;2JtJo?S@2i z9;6ZG8BrspiM|RE@iKHwM^#4nva}5Kyn6^j71laIytN0IX@?XYD(nnS>Yus38AYmL zU%Kf`ownUlSz2HJNQmM$Q%y@@hq=j-eEVgxhw}5i<^~ee!H=)o{=*4Ri@1*RIuC1h zv>7Q;Jw-TXN1VBh-tqO9jB>>JECN$MUM}YPmiO>oj#bEP|A1f9v$`7EZ3=E$p4J^& zFR_Zyd?RmbL|_;SHow}*GV>TO>cWV6mfKxQkrYQmYPb@WUOwmREg0H|g&wEn)kuzy ze)U#Sc<|JFN4_i2a5tURhqm0@%SwD@y3F#h$KZ(p zT|q)kb$FxV{09hkJ2ZY!OnB<6hu5mPw)_LkL=;1__sW6K?NM2Z zCr{#?we*AG0Gh zp(eWRruQ8M=&=ONH53(9O%xQh?#f&vHS}*CX09uye8S3g=cK1+c--AW8XO^5zy^BJWOc?Hb z?}h;R!{qktKPH;M4M#RZKi z6N21vAjeKFl;Th4#okGv1zrQgpDQ#x7TlR&2!Yo)&Q zYQR48fn?I~vsN#xC%D(8F2eJmKToQ|P2{TK?B396izz*71)aBmY>#E4?zk0X(hM?u zej!R=9X2(p>Y~Q~^kebqYiIJVim`&zs+cqdfOanQtfZ7|A^Ohh3t~hZvR>%0&Kz)5 z1JFuHI52~pBK-$w2)MaQdc;G<;P(dU76LH#`!$CY8XiFM`xmlEBj81^s^H=t0&|6m$hVthdjZbN?fFe+R;66cbcV7T6F{ z&|@<%Ca^{1W4soV>vBu)+b$k#)}hx^N0+4!W{M9U3m^|Io1I)5Vg)bKJxrR93 zlo&HRP{vQhh~Bz+z}mstE)_cNhYXVoUN`C6TCzS_E~;)r?LI~A+fkk8q7+?hlV zq=l%HpN86^J943YLJ4q6XJBX(P&H zdu3V++=!VjCk_QGx0kPBl8*k9pxq~~-vd>m4*4B*{@T9&2god2dp(p&A6KWwRgHgzl{3ct1;5cS-ef@9B z#+#o&(BjjC=(@zqQ4)H}xZ!($?518*BcLJjO-f5VfKe@>zz>-x5?`x8Oe`{p>4J^K z`(v51_($KnuMIiL$Va>hjnV0C2_~xmj5OedTzUXTmYM_?@9CDbyaAazoAzUIH5IHU zfjD0NVdqRbfSTeZmBpY^^3s<3v&-d7t1TVlwmqH@FYi<-c_l&up~`%FfTPXM?M3?Luxl=~Qb} zTTamVfY-LwMNM@6)}2+yG5fwR(-*MXw7DvlXPdcn9L_=St;UG8(04YUqQ60BRV-eH zDO&E_t-2=wG|EujmOqBV->SF^4f2yoZqD^Zcwq>dwHQ8ie&R0*xSL;SwROVg_x{$0lpcLLp4FU=bpt=Y_c^A!G3_EYa=JD&vomr;e5dvTt67@#`QG3 z;nhM_6?ghDUD(zgugNeKn&?HE_KoJl4w(4sJ~PXxAD|iTupW9~J3cp|-v}bvN)JSD zseyydHtrV(G{Z<-f^lB?fdy^l2p_Lszs->Z@l#i7YSl`~3Q+s_;mfK#NjAnyFUamr zU7XEB{V9C8OV!B+i|F$IUI!0xK) z#Ty)oF=ld?b<=4xPPv=c9>yoR4&QinLsoPpL4G9VHM4V$*|PEinbL6Fo#d2cRT@s) z`uVhA;b{nCmW(!05mtWq5{%pG#bg0OcXr9G(%l&>JshG~8kW8J1LR~-Ar=)<&8c`x z`8ZFNBv#NkyNG99pZcjS?EGDsY2|Kr za%rj`#Jb$~n8l%3lt&EBY^*7%mQgd4%BM57BVK(!MIJn=c)$@~L6mGv2j^D9VMzrm zmo-lC^=*=HCKR)+$Xul0p-^lqfUZhyv2z#lOz|knas?08bsBx?=YTP^w@(M-q_09o z(=J1tffY75#MBpUox>G5@kZ%U)>Xsfw4#d{(|f8PqVU*#E6CNDU+a!@d0NR+`6pQV zXxiefS6ZO=;pl!}v}@jqLKjRci`Qm_q9WJua(#d9t|>G<*XA3IN`UD(^ixJDR*E-l@Z{|CZZM>)3VE$*RJVQ{*@BV#`rES|+-8!28vQ~EqMe7|r<$1+1Pvg{p0a zff5pA(XtA_uHi0i2Y4yyWQRHh@1u2=H8;3xKE+ zz};%S?_N1j5&&*uB^f&VEQF8k=bMu2h5X;d-9sJ7pRX#cNc{39b){sJi0LBC+{_Jo53X%&@P&bd+;CB75Ten31*cW<}VF3 z-~QB4v+02Wu&6Se0TvbD?Ec82a*4i-2>SlNhQ7*w#(b1Z^Fos$4PkGLTD$zrYZKxW zFjmu@2mZc-ez)p=F2uiFb-%vnZ>z5Ef3oPne_M2aUq?iLx#;B4>GH?&dwFDGncN$M zJ>XEfZNjaDdCKsGo{1`}ILU~`cgOOBh1|dNyBItvb!Vd1At{QSHm^-+4m2*CK8;oR z+4!6RAn>!?*PYdm&yO6MUIAS|fr-g~n;H!IPyPD`aG`qs543)BYnlHr)?jx6*sKQ> zco*YG?Qz{N7h1Rtl)3zm2X2u+aN7fTQG-t{p4QMJ;8%TArW`iriRkae662}o?RvV20 z*E`Bn=aZ+={V9&;&jPNn5mj7G`t|6^h8@w+7uyz2t3x6qsI$rKvCr%n*Nk3jP3~vz z0Vm|72RnHXsk1VHdsA#|Ff$%`UO4P;nH3Lye8&03Wk@n&Q0+C{CnKSk3LhdC)!5j( zMoRN+9+mS@!FFR>4I>^EbQ^a`KVCQY)zmp8@TbosS^H1v5d8MMf|Hlkn*VtQF>1a8 zMvL+HouMr+?on9l3jF|m33CT{H)jmyh%U&d3*7&AIhUR%K|sr5_~`lWr6f~=+wUcs z+)k20zgKlz?*m9kifZNK+2#-!drscn5bm;h=6L69!)?1`*c0ENI&V!0k|yimLu5^_ z34enXIDKA+H9e8VBr5n9aPh`Xrpic(7GYvN}YN%-aPiJ|53PDC=)lyJ50Y_H5jjjfA zQ*jAd3x>DaCjekb9T$1w0{HV3%EA!&))J3Dw+ZDXWuUk5MB7_YlJMV!BEQ!V=sO`e z;HpQlA+i7wu&(&TOKHJPC>gk`dguJg7wp+{xe9YfrlHIW^VE|K$uLoBPXT~3n2Zef zixLmS>+wHcWjk`J)9c>1COonY?7?Hd*-HWzQ7B%mspC|M5!WBXGg~>N5SbB|^iw7$ zoEpi`=%!Urc1W+?Pcd)77G@MGAzQa zW13&UErH18OUR}<^mHS<#kr|}0Ctp&I-qm*;aS+pyywXW3x z*X7GDUqaakWUbUC=#erY;^~k$Dyc&qNzYf$t(xrnCZ$Zy)kO(@tU^ot#Z@HAnxKze zZ#klZNB?$i@WjzcTU3JP&ogkkV4U>tk{IVbX5`pDrEoi|W~@VExbxIIJg`-818u~^7GeAOh?Y| zJ6uNLs?3KYdc0j}A=}a9!*%|JTyYbOh~%1;2K24q@lS*AC^e$=S3WN;@FM#?C_Q0L zO%0EyI~(_I6xWb8x2G&GU_fRl1W(o(#_;fyOH2B2W>@#Xf=}z^yEtqzkgFbbaHFUw z2ROBOY9yCRf9|K%G-?V{rU%~t2X$o1&A&E~CI6%+dfhmo*~3@x|6OQCcYjw4{ajX* zvf$Es-uNAzy=G;;4D~6-cZ3a)zZ?39W_DLwe#61)th=DyM5i5vec)3YTGG($+ z`2Tbt{TH_y!t7`Pt(la$G(4w5BYm?^WTbrIaT{nktWW5x`y1>^-~32u`%LX{_xl`P z@aFyIcmwtQ2tnCrgY4Tcay3sqtrt__%jRgOv^B~vHA3d%60gYZVth`pYuC)pcw_$Z1)nPk}xKy}# zIL!o$tZ(sLR|F#WC3*FKzq_RDfDOw`vp_-}-S^hYZB3kmXGub-;mo!F#&tUn@J>Wh z_6G*=>r$SJMSq+6z$FRmq4?`APra*en_6-iOpI?^isMBs@CevoinOKi_$-@Gepb%3gVDKD5g{ zrl~3N=8pdaO%SFrF*iuU@|xg6CZCdl}CsP)kyRY!{{-3+{C^| z>q}9#yQfciMUA=DLpAp(92@Rb2Om*2(D5e=a$X39ABq{8dr%Y(e~l*WN+|YgK)IWn zsjP_2V9RQsEGP`*zN3gR389>Y8&%V|&vw>+-j(?Hs6_SNOTW!?X|IeEwMgU)0X>{0 zla+4~R|4UfC!(6;9K)<5$r+>kK`iq!4D*}ptMt)dJiVbNx$f?gb%QKmsv5*FQ`F{N#%O%I4KVYk3YC zyLeb-trv2NyIoV#J&1yw6<)&x%{pr|AVo0)=`EVv2E%P$pNmmW6DI;6m6K1yzXh=n z+sY1Ea`RSalQ}=qA2pftNZsM1EWRR_yQZnLoNe`>&^wj)$~Q2hYr$s!sB4lN#2c#? z{vcj-ki_ryY}UFfCHu)J+{v=xDWT~7Pl~PGc2|UtzhgOIbO{K4Co_>2O8M3mxJ$6| zO2A=#HiY~jPiyA~$dmO^Fho$bcUTzf{i}t3+~Mifv+F)zQ`$dq)hv3*n)OrjOAWj& zF0U`~7CpA@0=E`Zs35Z6ce;4Vh=FYQj=fm6j%71KueHS`G(sDzzqQn22Wx0pZcfY~ zNi>x0(LS}lu;WsPlvS}jcO;y`EKB4rJ!tCHs=#}Iuau_Vpc+C=^XBz>SCkfC8Sf3LeGt4V!5 z1Z>RcOf<2|EqiyqXG-AR2NaDhK$*sO*^nTB?{~h&} z|66VQmj8OKfcqxl8U#I*tIBrBgkH@{1hT*?iIvlMRsKX7bXwgscYB#MG7WGAvs*EX zT50x*CnWhg(#in%nT5RmFUOOrr2%sn66*R#l9B;vSE;>>mD!yhkkLqMz+8`v`UCi@ zNq|ieF^I~VP1k=H!|3AnfRJyk%Nzg0IJ^eVzEWAS>Q84I zV5c7-Z~iMwwfATzE`5BbxR(Qil(lKB22?<@SoOY$dbV^4p(Zmo15 z>^u+6?#l2uW8HnKaB!GgNzySeWM|AMRn;c$mSO$2eaxPIWkeCuylb((TY6mf+ z9*6Ab9G$lzwWDhX1Ut2E0TJydvwzJQD~(U|IrI1o-iuK}(Q|mz$-96&bJWMRXL}zM zc}k^08ohW(TjdiMZOqI2t3f@5ItGe-;D^))bKmw!z;P~l-qmExpJ_DRlQlJQbJc(@ z>a(!=<+%5Ivv?=hwxmiTu>ou%#Hr)xyc<{Ofz@v3%ILSt#>?>EqOBcvuyM2>G?AahNs z?oGHEJw{u#!uRpb6l*R$(~`}H!4fBGQRcM-bZll%*>r^fcXI!%{(Zd<$P00Qsbm{- zV%8#}@9uuQl@QW{xQ6`2YE?^gZ%llR{AbJJ(|6P1{ok#tD42K`n1(`i&*!07h5Qmw z3quc2F>?IFZCz79*4X@+E^Vhv=E4sUTnB|U0lq@c&@@&SkvFbh#m=M!{; z(2ET19KCh5ivb-3H#0#bQ*M;_<#g@Y=Q!`$UEEYZb1=c=jj!9*L3WF z03Og@4zSCG>0#Wxoh1#xvEhOM=uZE?z}jn4*Bo8oolVBKdE2p1PL%}5j#i;*AO$!J z+4H&)nn_~}4h*00aiRp2jz_7p<_VtEFP~wcCU+MmY7Cpu*p(H* z5ca!+Kor656(Y>9luxNL_X7l)s2`{~eOgA2PeLO6Yt}YW4f!76ml8O|IH5&HflPp!w93sk2%kQQ{4|rvt!rkIUf;+2NknTOYmm{QZnJbXs$2P?}Z6HG(S?r@0149pB#-;w2HUIxlCq4VU00Uek6Uf3gQwLapzdr)RDv>@EUADZ_KevaThNPfjYeye_ffZD&CL1`uxW z^;uSXlqCJmyaO@&^J9j$v_uNJ4>JGxXT-;4N~0X4G5twJg_I`#Jr?MllqGfI*9`OR*!>^_Q>H7 zx~z;>0&UNCt~ZZ`sYPzA{QwChKS-!k>UR(XI(ZHuw@#Qh_71+$coH6`QRnIAI0Krf ztbX4oWdsX#6N+E(Hk_yIc*0|fAVM>{2YMbetFwgG|Pe0<|NC}$+3{VQ1v5eW50L37)4j8B09;X1*Z(SZE!(|gC_??_X7S5O7hH`C{w-VaNlXIe+8qGejY`k~xPO5`Lhq(zK zMU}*N{-JzI9U$5C;h-5_+Mg|ECm^g-LL3Pg?&hiOtaw>i7>M~?D=eZAl1&+X&r~zu z#-g9@Z4>Xda57~FG*Oe=C5k@@n(G4Mj9>_d#U6xi2OjfJS0jAsq9%D+jjXPcm+OjI z)~uQ8(fiYoNc8$)&g}~u=e>ONnqXy2J52$tAI&jWJ)&;Jg4L!Vfe|8t7}4P66*K|# z(VKSZB)iAso~wkdIw4vbU;s!!F_IsYmgc5M0Ac3LOz z7n184M4&tvq9)|WHYS_aF2Og{&fcS0YJHiIdh!o#)y#is^&N^P?fXgAp z?r7j>=80D0&J0Rd4^X%LVYLJ^R3K&88e2I&%zZiWz1Qo5U=hmdZS?jDeo?(UlXJ-Dl@>+XK` zlke~EweP>?%%kYTdCqg6>)h9MU-w(QW^=#Ee2T#ErHSjCHkNo~LAsTX?)H~4IvFy$ zxqq@;(+o~=xJ|d;o(+x~cY!S(#;n5Erb}N5|71#p;~_+c*SeU1QC2p3CuPDV-VO&J z8em)aV?D~&v?I`H6$aRyv7oFuV8=2om94)?K!2RI>Sj!W8Ki8RfHcf zH7>ikSbnd~$!)rnPx{fU0D;YN1d!uGGkG6lOl!4WVZhg#4iqE`I0G0pQS(&*nj9k;>Y@6waJu|Ia0l>H2TQz?rxDS$uD`V?E_c0!|WchPp_6E%c z*?5sTX{U|#3#;azh-Pe~K8)9KU12qa3n#=Sb$mK9aYDXi2QV(D{kI46za0O^da(Zg z6FWv{mLK5vpv&(dx|VXZhUTTz;?`G^TI$-=m6(Zl!n4cza0V@8tBH-#%2=M*7%rT&_OubS0Jh90q`?O=; z0STxM$sPl?H=5)&f0XHNW|0s1KF$_1gAsKn%|{4l*BPKA;G|K)9McPcBWhs(!{;-( zNo~yH$9O{!qF(jXe;e-=*aHP|(dHqbxDKwYM~*-jOOtS6yx3D!q#9&qj|>DMBiY>7 z*G+p<`lY4?OyV=wM|yIC3d#8Ef)z z7wEXaPiC4A%@0~qu|SltZyX-_EvW<{YMy~Ndpp6e!wt1+`Ama`pfkX`TnTzx4%AYq z+W{hca=<}l1Z>*l=e)9TeCs?Z+dJ~*zxaW*kuE7!^5R)CA%V3G2QZjV4uuPBeX~7PD`9e(n6%zk3~(L8PP0o z@sXe5@-$8MxiiR01ru>FEGrxnnf*#_OGGGmV~KsUWT{fYbT+$pFp1q%6H}IgZTs*r7W*^Xv7~;FO+uJm_X%%?kyD-$9gn z;I4e!N|V5E!s<|!{GpGx+Dv3t>$fRn)tlriT4yt-D}1$E$3u54ilwb0J^&e_X`(-T zI^RXiJf})>6llvhfc?$DyJ|A%gXFl-Sv;0Ao4oW23!l2}%aO=RrJO}>n!Jpp91%ffml&`Ss9>C-#2xv<65@x#bbkiI|4U4Zos%>87`fXtHZww9(H#D4$gZ zWo8d*66$%^H#Dk^6knUi%40_H)jC?N!9}=(WaYcfsCk1caD_;`WPa;RDL`>0)I+80 zxUnWrQ97D7Wr)W$TU{fBur%WU;{IS`FOzAz&~0DZ+_PeITxfbV@m`DcdSJNRZxQuN zxclSC+T5Te*5Ou?kq`ZKv?B8vjYVn_)3x0q72a~Md*Dc44$qiThP^4^fY6xs=UG2ykDqwL0sR(DxXg!ub zn;`QE4mb=}lLc1DSC_2iZymJYiJLTw#vX@SS#ki>iR9x4&TVKP-G0aG<>mZd*s<<= zdb<-_Mgon z!&mlZN?GRo?3?V?p1Fbs*x&~Jaa=%lQ(6#XORNqPE-ft1ClWdE}6u^<4zbkc&pV zlN(!uAWrgc>$E0sc84iWoV-kSk04U|r7`96t$UFDBrN<^QeC+&AkeZioqaO9GriI# z%S&O!iLFjLzItF59T$Ao+aq&n4Ov6tC@x!5SM8((wzXyNUyp8gwj;pCfA{+b|K&Rf zw;}%asi{@LsVI;ecIRD|-h1mR`Na8*K?RIx-DAhw*v&V4W<@wOxlFvIPSY)*F@A9T z@>mJOxLPE02aOD{_uxT}uH9b<@P2Db@?#Cf-e))2gtB@{pF_9s zuf60{zx(#Nvz*ZHJIe(`{Xf!K?t2-A!vn8xThe};3(kw9-AZ1ryU&#XPJuSRIZj+q z@)>6r{wDaEj%S@d{s7-Yn@6-ewkZ|H1|+W*`2RPe!SPK+W>}L09R02`xL{D>uWK$FF zwkghN4_IMe;Di%5TGf|=Hua=pn`QU%n)=qUT3$qKgR*w{Kv*i9)i>4BiW=_QHgCqv z5!zzotQ<~hPadke^F0jyy0lXr(uo5KH9hca9zefa*_3gJqA4Tu2jV(UQ@=mqlBd~2#jLRN0NWZnf)m;hG@d` z-)m<|T2PyJ;8^i3zurvki71rYwpVVKR#9WVq%cc^qpT zk+NO=h)UXxsW}T-IlK78U@2g8PW+z<=vkYijS#mRn>Ha@!l2xPZH86SdnC{qb)VPO zf;d^zRdBgm8alyY+4rqKcq79XpMoBRjmDO~B+!|=5nKR$`IXdTkbEF}b6P@3-T>^! z1=J#BN2*Bf>$|tg;kfdd4%T;a(KAKQ%KK#~(v$qshKjvjFT7c558Gk*h+fIgu;Oz= zDpt=wih=)2*Ra304*w2MYhq8~9uc#BF)9P3d(f}ay$p;~Bd7e1F2f*z-kbiF-qVJf z_@R`pU7A-Uq%0gT?0e(j4P6)p49K8oCVSb_Sqy<2x~&OBxwEqo{s7}wD*Z*mp?bYM z+&{aD0Qt1WawJQl910gQs)2D11U6@h7f}H_cRk1V2@Tj9KME%o0fPuG4wyuN4KiTb z6?*Vwu>B5=c5u%zWW#-Wbh`a|;s%hY!&aZQsy3vvT;?HKI>oa`a|mqj2-1T^U6OxK zBeJBCFoX?ZDj$KIin#Q;c4r{TfaAq&t>Yz4594HYO=F$)0Uw@M_YP+I0P^?ip0Y-E zIpTimE3f>hyoNl-e69$qWAj4SwD!69M$~!QydlvcCUyc;;`Z3e2U;IsgKU11qL&ly zk{9Lwq;x&U=w^&*v`$Ab&f^T(y(C)f9W->@*Ke%tDL(5aq7A0+sz%vyN||uFkHeeL44kwTH2C=bf%cf`LuaL5Xr2 z_Xt}PI`mk>)#uc$9=VzKxPg3|2>1>1UPk#J)1^fHY0CFcQqYrITQ~cP$l=B-Uq(&? z;Fp2MrxN*=2SI)1YtBe8&{ZekTkPL^qWQuVwsUrc0kHouTKoP1{zcE+|Ij5jy$rvU z+2sdxuUbd=gKzFoPyf#-_CCq8x^C2vZ_SA+;olh-8J=P|5j}OgxvX%mu&sfR%8+GqLkT(H|(cy>O9hVzik7vw1m!6VD@adixE% zS5~ClL2!c!&29&(6Y7_E151lu;BVwS;x)FHq>5xuVT-6x>qQ{{4uP9)7%Q(?q2)X}@L z$Can0?A~alcBmDz3PW*pxAa}u8@M!YmZshNG7iy=IipC_mv_>VrM3)t(v9D!QiU-; zo&id8Vy3SurkX2ytG^aPM?+DtN4fa0sWN$eiS!j*klSTkN#3WT4C{W5-c?JPTaHuZ z2XI#-XnNv-pbR|2D^-3XWNa}*wvGNxE>#^{IzcmGmV#lZm@tW=Fq(Mil%O?hmUq=v zqw{%DA=yjX>I3W|i|9@@Fn@fqy^Pq*(%x=qJFI0P<{#-T|9y|Hs5d!0B(d+0_Ru-_fp%*0yuea? zTTkM97h6)I(jR83jQ>$X8{7b8fQHn~4*2xQ7hKd}yhxA|aQ0-rZ4Ed1bg5SSL^1Sz zDq3Grgqo1FDQ^55M!@4S!x4m(6ystmzf_Xpo@#cxR>xZ309fJ}KzV$`hHn8MHZw}D zrkarI>O;Fn1%ao1QA#8RFxdo2dJ;>u=JN@EG_Nm@Wk=CNX+DejmTN^|1_nsL4P*~1 z3A$Qd0JneM{Y~MMWhd1YD^Q9uk$NKLndrQ}1x=Ms_~#ogbT`IL%Dtf`#2pkLd<5JL zd-5(gUilrqkC&RlJJ~q|XqM&sa{AHYH9?7KT>5fdJ1o|(z z@ShUJMB5UIAqwci%(${Kl@Z{xNXZmMRo(oVMU69caLz9 zFF_Nvl+czOTjVx$QK#Y_eyR|R9d|{ZJ7Al`A-9sfWUQO5lY$m)j>7I(!D4EZ*CmL< zbr|YxE|e_rGm4(vEVp^nWjHAv$868k&0BP()@3nP@EAsvP_pM3v;MOP;w1B1N``b7 z9`QNmSwLRvR_hD63qgt2U<8~e9K~El3L&u>RMa0nPJ3Y4Qz$0X0P!&ox;g^B`M4}T z_=rLQhRdckSZ%cdA?El;evlgAeRE&JrD@PThqEt4gnsX;x%*yW?Q<`Zb1=pQ_pgYXtwqXG}MrE^bz=IG{K1eO!d|K zrTP3lg26^cdpz{l;k{LXf{p|l{kGz8Zb`)U)ta7NHq;SQGQ!g8ahh6+*ik_8f5%Iv zm)V17TDv!LJ!Z8SdQElGEKr9svG;t})&V?qUZu6XrCb#qA zd5mJOrMElRG!m|JW-H9uX})|LXllRF;MP$tdMsy1pw?+&(H5m#CLhz6Zi+n}tE12! zTN{QJ7%rCZ_U=X^T;jgMWbMnPh}N0DP|kB7eq9jR;jughEu1?#*TMnxTAtYud}bo! zu$Rpr+?PP3JqcE&pD(PL&&7$>i`z@J2kH&|ooALLvJy-}53UZfu3L*?91l`7I+4gX zbWUE%OEATrjnT2`wmSqW=57yeoVk`E-NQQ%2M-*o6r@CzfB^qyU`TM2zU)wSbN6Qy z3E&(UNMrSo&8iNMU2mDV9UNw=(h~xI6&I<96lP|)nX$-v=Sb0g%}aLdf$qSz35}h9 ze96VtCSpfw+WBU3)n$M4E=L{5rA(%eUfFGL$19}V%}%ouTrxjYLiB0!mP5bW8Gs!M6nO)4hyW7)Vt1{q)yARvya z;?$(GXq)SMY2DVHkR7$r?ewA|TlV4|Rl8me8p((QXW#o{mI_&It2NmAB#4d4S7*`B zLwD1<7|LQMCd2O*nrE=XBoM@n91U5K4_xWcNg2ZV^v)kdltEUVD|SCqXfy1NB_L|Z zZi4V_?WSLoR<*saJh7>CEnU3}dOVP{wW8ob1>+T~uXQVy@6>hfR3>F@gS6|E+4^JiVb}Mv1$=ioGmd~orr6)9T^%YR6>Z4Z&1QZ;AR8Z>m5#9L%sp!f z`WR_FW+?_VZDj}6T>N}l*_>)UyLX8sM!$aCbikxo`uUm@Yn(^e`P-aNR$y4#){^24 zAz-D$SPRc5tLT@>Vq73UbGo01ERm;9D83*p)_kI})}E0G%orJ;F<|kY`+3a?1qZpf zf(&E&=Srd~2uR?LWXB$ zRb#RVGrlk>CH+NHy7@ADOMx)PqV!JY`NeJ1_cxJr8E*1RZIMwOA1*l zR5(Y+1NH2ZCyVCM&l33P&);Im=NrEZxY}gJG#z-BhqKO~FAW%akZKwRDgE^MYn+l` zN<3lmZYe^vTlIk}Lykyu?ZdFP==m(LCAK{C0vm9fa#xg+8^Px<=9jmloon4O)xJF2 ze|21vqbS16aQ%r}$D#`(bUM9{Z-Wv@$2K|-HLx5JFxO`NjFRkobnt#%*tb}p*UK5G z*J*Bao@~TWCBLZIQlpr1&L^u3G%!kvnS&AOwiHLI!qJb8)R8~QM|<2t!4kJ>EpDa5-uGpQ?-r5V|XveNR6Blofi?H+$bt)ZBNs3kfREb zO2##pCilp`IE)rSu3D7JU7CZ^N=ti0)Rpn>4AXazZ04(0XxTL4H(}(4ZCr zezl%^Hzbse%-@mxv9gT}de%}5JTLW@spF;fMTgs2hk1P%HnFc0*jbw@AkDX{*9rAr z@^A4_1->8}hh7u6gh?CQRgDi}Q6P&^gED~UxC-9DoH+#|B4tkZ@uF!#|Ia8h`{W1S zmPo+r@y+1EnGG9iq9DHfpO&v=vOs70v<@VtWq^IJIjwFz24zRX8Ts(`F+_I{&i7@M zuj=!QmT3JO%B1iIB~eguEi~C1AbmSYlmBiB$M@Avj*Y()VcH<3|F<3Z$l8C9#oKEA zD4y>D_L=e2ozrd*X%E1*eJN?}`xzz6;y}`hsjo82<%%*h>^udiu=l5l?HaWymQ#;3 zV3()|d{kkMl1^nGw2sh30jZ-8V2mnegqsdLJ6ZCR)FXv(lCMa}<6AI-1E{xxt!0lf zr%Wrq4vmxkGVS;~Svq&<*|S7>*#w}bIphn+3>sA<@y6Q!8AbTh_QmJ+j3QjKp&El< z`DH{uE{BgOa9;RKFfVi!0FdlY7uGj_hBjV{2H>mDW_v)5}17_WtZB5$q6|qHJWW;KX!%NKU zY0c~P_nO@phF;iWnDa^+1r$@=`~)o@Y%0!mvQ(Qu)p1eN4RPE7?_SC?DPNXizN!{U z!Y#D{ah3j)2ss;%`c1sJ7y$k?yHEZ#yZ;g>WFZH5@M=)d7W(PSeDF$g(v^Rw_krF6 z|Nlzwb3M)e8O2cPB*O?Sw!{v_>O{G(ocNHe$~kB&&!AVl8Ixt=X_AtFVfHdN$%N#G zLVq$qy_B3Jbe$f;@XnLlOH`v(lD5He?()kOav|XOF~pu@6zODoZK;;Ik43u4ofSCf ze{N4~yYVZ9t112 zJoN$}>V&*O7`I&Pfh@BaPqf@;0p#uH7!SiXtYur<%3#hIB#l@=;o~p*$GjOi%G(Ay zC|CnSxImGNF;E9+7bWXTz zW{WnG9HzhWGs-dm`J9g-$=`id%6NR}6YPV$%{6-7Mr!#N)^^4K+-z9&nXA3_T?qLr zBIAGH&;FXL{q$W|sqcJNz9VbD)MCo^f@fM>rFU}b*JBP$pjS6@T&(uEK;MSTN8i=> z%y#a-UY8pd18S}w%ly$p%JX?gzRfWV9hE-2G1v_OjD1OUw+s@LKV6&IqI(DBMmf&; z%AhW(YPwe@yrK1!DWiPFApMiwu~N(_AArxE)a~_E#j=!rd{kCig}5y;RvpM^{3`!D zDUMx)>RCkA)zZNfLEWaA9>70BZm^nhD=SvYf}5(VWBXx3F=~gb*kW(tFRGi*JMx95 zAERYnJ;3e-4MQggnh(8@kChVldU`Io;>${0_`}CVpV#KA(t={ohO(UR_TG$UjQ>bl zHQ6~=m<)ni4iK-qdAjn5?Plj_tsWZ!S+CGynOJe_`v8axdW8&+I5RPgo|xCz7)nPs z@vg~1z;52kEQ*m@ds{twZDO!_r4G>FtoyEHXhld^tGkuH&nGrUN5Nc49qM^;wp#;z zd<_sh#PIy>1^3VMNckOT+>Y6^cu}`~DHK_Ui}VcZA(vFC2%xZCP8P|V=vVFEd*xl5 zbuG=uy*b@A$YeT~11QEgiu$;{4)ls0Nqk9_04_GZ2BKdua5%-j09sBUDlY~b0o3NL zSrj*IB-Tv*1smHH{H-@G2M13TlOopq)VoUQUc6!IQcTY+Q9QzKv9m*{c@h*j51Cc!- z^EstM^Gm68Rr}CQ6kR;Olft{!O|`V5-kvJds3BIcd^zdjSH$}tfZxpd7*2VcT%ff0 zr*QlOJpL-!^O7J}_$NFROmOXByVeRt;fO^3*|bBBH<#oHos_7#&Cu~j%qN@y;OcSH zh+4#hd)u$=zzyS|+s>~IpS3ld*k$IFCg3p{X8C8=z6h11?yMA|?njvqyhv$2hzKth zvgo)KIEWvAuSuFM_nqNT38Xyic3fQOM9g)&vCbib3nKz&>*(ywW8_gzh|)s)g$@Ps zV4mZcM(c{#xm*1*DOr04Q4TeF#{mE{-^l&w4`0k5HG}^aaDMgC`dh&Ie@wtBh!X07 zfL)EV&ER)u7MUG*XV1Q-&AQmdpvYbkmoc2H&G#z($l|OY($ZX>iZ)T2pd?zu?A_JY zjzv~2U+oc?!47u8RLAT~P!k$emwJ}nQ?S%|m+HDdQN`pXe+X+^R_g?F{er$F#=5Zt z(CBAIKG4;hnGfVJSM2CU>=z$?MS}av8OZwG{=784vN_N5j>(YFGtm8fL|HX%v+uLI zPtapDB_{RCFiM8kt5E|J&HaF$SRxY870Li2vkUY&1>FX0BISB$x6>ipKckeN;r0y3k1VTR zY}#YYNg#EE93$o;>h!apFqIeuD{qzWf~WYe`SiI6laX9DFWj|2)>v28de~=N8+93W z<_=m=Rn2%w<8wwOZcXj_$gAa;$RKiB&F}HPtLT-&Kn$vR@)>@TPJ)r~#Y(qAyz;&| zg*V#@Hr?fG>aa8K&KEFdT?S{e=B=blOFyG*o#38)0CuRW&=!*&WF^rm8z9*@^EsPR zhrR;OvO5BjS!@);UcujsO5eP>B>FDtVww)V{wuTrgzbFJ6)qrm>wp{w#SF5ii0-6y zr!Xn#%&xu|FXQIv)TDb`5U{o~QV}n$+WPQW?C6PK4+MK6mF)=Ajy5S<@uM^b!ay!( zxXc#H{+^0QzDH3^-Kpvh|pRR;%?-M+tsBkSz zXog#Ihk1(5X}_cOokZl93Mopdlpi|}fq3*jDJsUmgydXpNC`Tb&0nwqHCso+UudlV z=<}ahv-R+LfyOl*XN^#RC4f0fb?s{5G5Kek*bn+SNRSeud;M>3mI`8v^H@i;s$ z`Ps`6nFhLx(mw&NLFp!usy4CkSsv%)Mbr8GD~E9A>Iy1@l8W7K!sDS#BRAHc7*(V8 zuK}c}BE4AbTujlm5NEQh}WVsoyLt@kE-mELO0qaEQ^lK_Ip|pf&u2{OkdwV&N-)yG6=lD;Q zDI#+EvVUpd?TU>++-Xo^!jN1KvwfDn`sPYo8N!{Wv~FM*<1{P0y85hn(d}i*)}7Q# zFt+RbvBU->coSueB_~XBlx8_kvYP{%lK~z0hpjuYIPtc(%HCAVJWaG+X}y z4PT_M;os>STVk<=$p`}PrALX?ao}4ll6w)-`40inF7#OgVdwP~lXPErDicq>`~Y!&7o9oI->T=Uu z!l``GTBpLbf|YJW{CSt4!k30(qr>J`66@G1_Osz2a_u$AF;_`_Mg*RnZ#h7-J-6bP zO%za!Fd%h=-3NZ4B+yVe(itl_G!SI}{wG{~mFvxu0Q*8z@iI6F+6F5w4|DDnKbb|*|2^iatj1<5`xaR0*!HM61*5Et5zgj+5l zK_FS;XyI!~vuA+YS=OO*W7&;NFNccKUE1JX_Blj-mtS$Pn3yWnWN{rn<13~;<@)3q zH*i!IJ302~gz$d+ZAFTmB3|+gnB#tEuFA5dmj%V-IPOo7`S{GSrR|NCD`Bh<8sxE)PSeIX6E&whc;!?kz=()W*XbC3c zNqpGo--{zO35Y8}oh-6F^E+!F{dEufk9`X*r>X1uXv?Z!VYHnFqW=V>n1KVr&ZrKy zfJCnibx;&EKm}2J#<3$EDbqg&q8Mwd!JQg$=ZgQYWc_HxVD?!87Qyiwi=g;H_O}8% z)WbEwPBF$Dt6sWE)q|8}ZCN|yJxW~Jrg{sVp=fWJtPh;hLiBZxZVpxCPK%R-h~`x~ z2<=6OW$&|Sm{@g&O9#7F4qg-G;*Gf{Kw*oVp|FbouD;}DYja1y;to+vxj^7rjJ>bYwJgAr2-`auuV3-(CWnIX3cYn(A# zs|WO=u66>A<$Svw#cig;#Rf829LM*W*dKzY(KXy`LawKTDcG=u3hZg051xz%<!!>{=QFPu3z7vnX%5 zZY2MwmwbVKC51hGXdDMf!A{{tkaynlmr=SqUCNs`snqU#i>SuA?-e2_P{#l`3&-bD zu5*~E$pO%8TxmRFuGpS6}1 zX2v0`@HF+@>$&AWWxEK}asAus?q6heCzn4#uDT^xRhVTaG>End8?oeHlg=;5kYeUo zwJsH;Xd|(0RLBW9kd)8IWfzZO>;v9{A6|KZ$lb47LDtv?(wZm*|`Br+_SO{6Y zgXD<&lfs>l?9;(5#8<$0E2=l_`$lKl&02wqX2s{PSI|TT*Ig-1t+59lsX5+w!Z(0G z22B&gd0*%*2%I(XJ$zZ~&{O@GraF1;&b>MtK5VClKGW@{orRh@7(s-{>+0A1{E9fF zPRQPE8f#MdS2I#pHuoT@YOGZgLY-kZac+GWyD9a;8KKAembDe@jnhkcHTX2wLf?FG zmDb(LDZP!u0sqz?S$+0l;~!7DDBUT(^zO2X<{R3jQciBR zgit9HdVLg^=FQyEH7OoTPb+7-Y=4sCM^@re>Hb9w+szLLqn)#bZwNdqI}p^?Tn_0> zjMX`@%`J-xu{?@lkc!8h=5xkjTj1Lj3Ybr2Y?v#}NXi`wOSlOW5~aNpQoVfiVwqXl zC@@OSpW{Y|FUY+I*d#xbK7aS~pH*jnUbC7l7Ux@IXt%|w_YCj!VacP2%7Wo687Gsf z>zwqU`$hdFijkPjrU#cwX2KpY@bBky2kKnzUQpd9$_+Ndv8RiwON|V)q_j57DcrYW5~5KmIFy!qMNVu%EI{4 zn&^3wd5LyRrskwIdV2}eodbSl@-B;@(ZV#mnj-oS&G)g==pEi&R9u{#QooS_zlYux zYO*hws}pM{xN37VCikws?%B+VO`e4U;&pdCZSRc&<~>oGw#IFie5Y>e&=4!rw+VPP z2Ko#&enYBZMf&oyTIMB>xp@0L1UU1CKl}pzlywnr-q2$JyxxF*6AstGngZoq9WGp` z54C@X{YnwTmC#TXrIBjOS+oit?UC%eR>P4zJhtpK<%)zl_m@q{PLu_yh3w19#OiD6 zLJW9DO%kUF1m>9fJp@&Ye}jOJ_9l|48#dg)l?+~OY8e+&Q>xpBm&4gqsnYRhF6sgC z&+^8%LGV8_{`ZmeUvwn(Ul)*iza-)A;!a+6!l3oqrWmyC9yi+{lsJ?9rQ({Bn*4 z#gl6t+i_Y%X-R2yBjHAeS+gzsYF~p~gIQXjO0Ok?*5&O}PwCUk5O_omwS!8XL*m3b zD4>5&FA}wDh%dmj{w}XqcZLZ=ue4~2F0gF*9@p10lou;*r|qnrG&N`R_toGE4?Wv^ zLMFc&S#9<{KR!7=<#|gZQR(iPUQT|DggG~N4Dgoflj>$a2zYYv(C-g6tmr9eBKDdN ztSoX4l=_3zOmajKuk`fA@`3fa;-_!!hmh>ut}x5Wa++T!*6#?szsQZakqR&EFL@z* zoKVs^u|lXjr$2hqsl4+G>oTOs%LvOY7P12NC-{7x=xw~vv;Ev?eMdDLf53OpQdn%= zo38&*)-N>T(?t^C+@^aMJv4L?^hh`&{l3S#>qTK|cSBmV9z`s;5U0YNXV5^utl39Q zIdQ9%4iuhO%}Kc@<6?C9p7jqs3t37G-{9R)bah7XhK5K}C(0{5kn*h&-X_uJI&w3B z&%>#sFWUjpq&385k~uXdT!nI6ntH5Z|Z~$wlG;uXS1pzfKR6X z%c}CgS-n6+`)=^eqBo>P69GJaz2&oev)a>OS46Bn3l6IbKp~^$bC8 zhs=9bawW1oq{{|V|`qhyQ~-(i1!Z17e(c3S~CB14J9kZKIV5U2Z=C1x=; z=&_Kc%#q)+vH0_7 zzPI@$PhLDF?OX)m>?lO2yN}-NvCVCciksPClY%jjH#qH+7QAeYeK0T_1xLgeB=s zhlG(_HEo2{ro&2Zq2R)JEb7?YciC10OZM4juvFq!0|hFu6>Ty7}#E3V9Z(-l7I*Tj?niAFRZ!W?DUVNK^Z=)F*^7Lptywr0WG8 zA}^jo9T02>l2rOp6$d$yG&D|m?~=LJlHrayt#_Vi(e33r=3(8Vj-ai`By6{38~Fr} zDqHV^2usj4QAPU!7wGcYTU*Cty#G6`)6Tcrf1Z5gcRqYra@1+A%f7ydaN|CEuj`_* z9IA1JDEDj7bq64wrA60mbzU3~kzkNwixk~m>2wO)!L&v)6miY@s5L(>1$t@%b%8_d znlmb|^w1gkkz{+VM`Jcz>BSvRma4p(L<|SzH7^oY2df8ZV!<4Z$2%%P52Z;?VqSzu z)3Sv(tKEyM8F8Xgmh-p650O6KC9Unp{?tkN@1F3!{Jno0VgE~vuuJgTuADJQC_E2( z<7Ruuba`{6F9@us&9r+vzE&Cz$+?L(drXwCNu~=m%hwe-Z_fDq&yO~HcABU1blETF z&>gB!lZ~A=OLd=JA3lKRXc#@k5YV#hv?|^Uj-03wQ}K0zXx+!?k+WBeuUzhVdLvwV zMlw>#TOpW^V#}j7k4<_R%y6Z-ATJ264R;P+r2)D#QMv6X8?^XjWaS#Ks|-i#bSj<~ z%9&X!cH~wVoH!s5UEGyg#Eo!=5hVyLtpG1qEe{gmSC)YQRxFUYA}K3v!c z`vQm~@GCs=Bt#>X?fz<@RD5H!!^%@&=D&n5wTX@mvv$N5(qLFB6#HiJ~Jinof-bVkZcHNn&8-Q7sEJxmKA$u{(u?&(RBl^ zF5_%i;Kge&&h}vVqq8R|3B3h`(=n-x=y2d#2_cB%<~85256)MO{rzhM6(#soo!Tg3 zbd6Gc8?-+kvANkNEaT)_VlNYR8n0%wt}!L$_vqu#JsPK1<&X{S3(}R3Oz3)V!|H2e zTs^}HbNDDEEk;0CZVgM)c*g2$MFk!w;Pq+e=Nwm)tLJ(@5D~2LjF#Ekgnrro?2@r2 z@K3bt=Z0ej@-8#iR}KgIDiTLsnPJPTQX?Pq$1;U`ZoU=nj0nBHXOdrLiJoEIJ<4pk7VpZ3O1gn_%iT+LD7Hq`Oo7~ z&hcK7o;$;U5x(=lUDuycK9m;w3C4E>vSy6}o!KLLeZAmJ9Uui6B?C&WNwcLLol5;a zO1TFj4LIYVON-D3gbaA05NSKUszBmiJKQOSDaGtkqe7s|?aR#Ed^2ASR?^4#)Lou8 z?ttB1s{fiZ-R#b-`)JK%>Cw}D*sGkhXIjuKxGU(ujXY2zxs>X`tR%j{%&Rz0n}yW@ zojtCTmCLIwEDYC&%pFg&lcV`CV5KiLgbsy>Sw%#tV-}M)Pw~Qc2xj@VpM%8|RtDnF z{1s$I_bt^AEL!H~TxTu)Ax*&<@Xm~=E~R;@3(?acBQv7QQ~dq{251R9P%2gAEkUz; z_BgqYjZ$Rtfw!fk)+c8PxH2TIjF+vs19C>x-*SG6%i6WYBks)~sESL^L!TG4-Cr=;b+HkMhTLZ5m8amzdrn^kDs%P?jrNKlcVNIv*% zB0Mx_DJX+mzdm)rn1qZ^(BjbU@x!<)sg@81jNR#yRG+f7#1_0-N?%J>=DB=W|J1(ZEBF%s= z#43Me_R%{CHPDW%EVbDL(aD^MMlJE&-Z}(K?$j4qtaz3dQ!c4_!;f<` zs>5DRI4)eL;?z{dCe|GV^&WChX5bRLaT9nhYi28NvQ3V&dqL4Nl6rw=%y*)%{mtB@ zM1^8k&qUkDY^$boFWy_Wm#S!Ad4zK{%LwY3TGE$Yo0*pbGwe<&#eaf_ z3MIdLYC=>-cSO9T2&(Te%C@yxcDiaW05I>Uzr?=3*JkhEDaPO0o%|oB7;_GM5Fx%> z@g-$8-t6tTc5sv-Apbf$=#&(N+Q_1$+ET4*r7yGZq(eV zOzT6ggXY~4OX|cupV5L0LaiGMi1#MhAsS+6V&&AX-6zX$nNoL{XZ%Ubp8{`H-L?|v z0`fSITnc5m0Ik21yeom#+$zHjs7S{T7k{YJ1v5cl5$ce;IbE^I-MPzk1XW$jD?T(= zaP*}f4MCmGtPM4I=Yi9|Dru@X6nj7&0yxsR>*btKhbydGdDzcpHu1=V7qZEdxqyvE7VE0hp}5tJ;i@^RUehaq9*E&a_vaJbYb9^XJw zFM1R=+Zyeu!?e;s(OI)~G&2$u21#l0l9K-PV7$kfx45Vm*weCgXt6j#N*hZ2}9lBNf z5xEz0EnECMFh;CL`;P|~17Fkh)syCSQktdy^eM5c)Ha6&EE)fZ^u0Z9X0CC+TbE^} z{i?!7=Z)xAWwl~Nkw}``N(gSo5rNz-O?wbg1jY46Q8RET_J^m)EsirTz_n(0GY&Pz zF_!NQG_ft6bsyOkyJT5Hx5K`DFIU$%55nFw-^FSv>hO{$!hZ(zzEPT+7+b}CR`8CH zvwz>xCLc08$8$8%+Lb#d&~UZJ=fHO;HWubb{xK2@^EA3;7Of>)?n&u3fr}4mQOt^M zsi`ElC_Zb&l*n4lJewa{<``?_NcMw!O{S$K`ZsSq!f4M34rg`+yhJacu?D&i;EWE( z=~ONjV?6C7D+UN{j?+}`oD`$%Ob#6>38yI^sk&&-Tsyz2gnHK?cJ4^R(i2F?{6P2lK z5-hfC!(XS{a?%)R8eIlv=|Lb~KhNyEe)R!K)dcBXo2-Nt|LCm0XNUsNcGF?#wQ>XF5$hfc1ci{x%sdT+0Ga1OG$w)n~4s zux+|$yDoGR#X%h|(4EkfUK({!^r3ihp-|cQ{m8Yqh7t@-DU}1$dCAog;F2*$|6I2n z$mENXJ1SI#+mXg&_6JKbi@NmjZ!`6qE>lqq^s0?CjVAlzTPX5lv*eUZM}y@|#KqYx zxskC<;WKT%63nCFJ{Bg*)dDuJ2>NKL4qtJdfhx>mMRd~0nqyMgZb;lMEpT-yMB+PR z&_56znZhVb6~^Ci%u&z8>;v;>a}tah9a{|#Q!{y7@cF?=aCbgo`C9Tj;+rO}rbVa*Map=%BKsl)KW)>Mj zB;IH1E*H{vAcr-zVt;CD-deP1g|V*3fJ|u}C&i~O zXuRE96s)JSI4|R=J899pJB6NKQbyX^7(4Tn92 zfE1D5M0)Qbf*>6fq)4x!_uhMl@GkE3ab{=t&Ccw5%RjkaZtlHFzH+`(e&=_~p(0{Ig_z{y=k}{^I3gW|itdOF zgf2S)`WI`TtJ82M(|rI-hnjctrrtEQdVnD&U}@ zSVqSfQ)DY95?;nVQ&?`%nLHzB#?*WVUV6)lb06NkzG#{mHzeSTtn}FY@EE(hDva`_ z9G?@iO9?sKCrTkgS_Og{?CFzjm9ex&MJAPAb@62;VqS(@@Dxe|+Nd~R|MLsA`h3sb zKIm&S`i>`6)6S>Ong|JZ&ODMFQn9V_&_%b1gvKq|pU2&AKU^rfMb~&`5qdqa@FMW@ zxasA#{ka$QxYv>k2H)Kc7zf8FhPj4@&55}#?8~mFe_7g9n&YpFQNx?hQZZ=4FvjT+hR@j4wzMZwbt&K>%hmx3|Q7B11`mU@fP54;lHD|C%Uaj28jFLr)OocZGEciElGw=CzV!#x#i4umdCAG+Qy7_uJzJrj@0sJ=2)NYiez0&xy}YZwk0** z_IUE}+>)#Z!QhP>53Ed0zih47?J8vIdOwxS#4sQvppOw_dMrNMI$$RfGsn4-s=@x6 zxR}xyTZ8kJb7MIc-4z_GJQh+<++Ad~Y9GLReu1c5{8CrxbrL;uX=tX6gYFnj0$WVK zUDp`9=@X5+G_NR?ar&l9G_0k#Q)6z_f?|ZHcIqb;CyaH|sE0=&TTSx)6N$C=_T|$a zsFwwIqm{*sXIp$w-YMbDenBuZ?N>*@x8u!huEVNBsWiHXL0z(U$!b)+Di~G~-%&#l zeEG{c*247r^!#u+ZaO117j;{%m5XzDw;LU-QtXau_{6qy-ou6U{e+)k`7Pi3thdHd z&Mkgr%7+aq|I9FM{zGbyY%DoWx1K+bT~24U-8eQGsBwc|eG;hEyp=1zY~5llb+sfe zYMKClwJg}`5%O8E=+vQsnb4*+eKhyz=ri!qQ-xUE7g?LAbka6VJk8Y*jNL?;HnZ6#;#x&4ri@F8QZ0-nj;<$n9Sd{yt*u&en@iX>F@wDo{( zc3TF<{pL#UMN4AQ3g392w__>y4%SOKr$5vujK#=F)R%b)oy7-T87bhY2_G@)IQ++t~Y- z1zF6B7oO@5JKF3c%U(JJ_{SD_jf+`6Pi{s&o|A|@_WkJxe*SwB0SYKrA3jI{cj+Puo3pzE#uIb zdB1%nk;vGz^`vI$b-E)H+##3o{d`F1?09#A^(%sqg=Qncdj?Hrgtl8b_r%l6`EF5$ z>fV{P6j{!6zEm^jVC$aSk7?Zvqt{{ueXQT9u?m7-zcPV`2dcdh`Q|`r@|o7bbb$6g zE5e+V>20UOt9mYo$63uv2J1QX)@^vJRoqUq@V;O52j`)RkFtwkC2*yS95;g9R*`bn zn0I9xccR(qOZp&#Ve`v!9WaCEW6yB+Ql6QPYJ_pDQ+2Bv^co zV}Id<<=zr&4rV(iN+Nj|!#?qE zI-w5|&^H>#*vFw^r3IwijH>9s+{uw&+JS8JRf_YESRaG&? zpec6~%n7F@>)Nc3x#$8>F*dX547Y$Hw}=aazhyA}*7;K;&R>nh346Y}-yy3> zZ}*IY$-isg=T0#?4@QcM2YsPW@Vs?CrAw#6d{^^J{zFC8)%Rj)9Avfcdg1nIg0i-A@Kg~wd-r_Uim^9?FyY13!OibteNIfjmghns1s4Lz#F3!mj z$Kh!Rc*w6Rz4`Wy(F|r6%J%II1A&}0sp6~m{VN^pDThm*r-+0}(N4it zZTbOkjuJ~9PHz&3VijG}4vVes?ECIQkiy18zqpDK4=&-w46DZBikEpUEf&#O-g$^& z@;Gt0rBx6e)2Hiu$VZX2xXa`9#~`Ee0*|}ftlQ0VVmGR6NUv|Q$La#rMpCS4l!|YS zrh~;H1!gYFs699V<}=cTJyOod_$h%eOFn{}+DT;7#a>;@zz8ePvOpn)K!#ZBlOwtV zoJC1xK)zM719f&_+|oQW(?o|Rfp_Yq9tG@my>_w4JloVuwmF2}?nPyg@1NgXni)*z zN4N9>!aeo;A7UtqYH%GyrbM|QCadwOsPj^6uTD1yY&) zTCTkg@h;tU$OyChyxh3Z$+DeeP;3AoOXWEoBl)78UUg*dyzRZcXGSW9;M3OmY#|q3m4TZd7ah_Tb+`2o=yQ>5+>&DG&Ax5I_YKj`1vn$msh&#}m~JL~ zw2_Yo3%ds`1oU-C&@k}%RRQFcZ<=ChF`MT2YsT|(#z_`7G9ixHW*i?0k zzO*?!81P&vQGO3%qw-j_iw#RKwApSg=E87GOUTX)qGIR?(oDrA$*Ak=>TR*<3hENd zdLXRM22O?2ZP}jlmgirp(SG%&LWm=6lVEsF#VE%lIt%0C;^U&2Dbw{_nvnuUq=qw=-r}@5|W3@&Q7A%POP5CzTfM6bf0={fX@^R zXcw+)-@<#h2~l;F4Dp;8j!!I1^Mzw1zp{K(hH%4*OCZo&vfgvz?HTaZ&Fl8rWMAPb zucY6in-1;YL8e+Izt!nI6AaDzFx?7$AVCrmDZa60-{akG1J;|_r)&<(lLz7t`%eu6> zZJo6ej{yo3wgD=jO+-lNWyW3xamZqc8VzMDxuf!R4R z*9DX(y3ibh`fVF(P${q>f^KK~gMOM0*io8#w${AzQM;A^!1!$!(7M5!{p|h^RrGo! zP$`u|9QB3b^C_#W+9jdsfasS;em_A;|t@|kRqwA!Wn*7J0;1CzoA#9({q^l9)eT3sUa>)}swy&gFiOOW&sZ4?X z^o^qp#M^-oHclB+Jj-A31AVvNp`FvaUSNui{bqpX`}y~09(z_{hkhkAM=$|^Df3$S z%y{9Uccd4R`G~M!bQl*PUoQmsxDm6EP2HZKXBCxL#MOFb9yU$eqh0}1L~tG!qY1X@ zCcTQ{_38qgd1d@_2)nd3T&%)6X8B-%4hyi+R=_ilmv#b>`Ak148=doL#3*)(@ns6e zIbv~!8mX4)lr~3VI}ueM%21+xg$P7Wl57K(ETG17wBUzqW!gTm+v6`#ExX;4#vueK z@~Xfp(KP-|kHok3%|Gkh|ERYE(8_*Y&ihY3cS`m7cTjzXUJ7KW5nrxNi4duWaEGwJ zuuIcc?vY6yLnqwi8XzwM)3kLv7ERC#WZI5BvDt&tJ^M~mkoa4K_5U=mT?q6e$o)Ul zK4nt)%b66!ad3=j5m)u(X1R#)ON(~W#~>6aMQVnHbSZf^z?D_-giY`zb*AF0)_T5X zWyHlh8O@3!c^qy(s_TFGLOflB|Ivkbx(NUBMJR-S0x~UA$XSG{dYcMFJ~sx<;=W6H3o?2^0aU zr}(okZV1^CUw1V(x06VZ%e!x3H^Ig-QMkGmTjs{Cten1kQ6jw4G+(Y?+~G0W9ki`W z4&X=f@5sc2OiZ4GyVwnD1|R#bQUy#4?rtAiK30=XNDwLy#1z7p`%UlJAITOv^|1X} zmclQda|+P;`v5vMJfTjVd6qstxLbM)^2*4u$$5FXWnVI1fBBb?FolfeuQ;8tC-PQV zX7vC-=Xc}XKOI*oAwydQxu!bMnP5qqez4mX<3mg)jNW)i4gKK^LTKN$ht$5=@!88V z+;eQ{owxg)T#*i3l!)iLPXxO($GOA#T%W>%LBMudIHr#pfqo_**;S!apw65ZRR zEO)^0cE-tWWqNy%M%0Ae$zNBvH>ahnL{>bO#i%HOzn;S*>b{5U%4&ICrV?4a2H$>T zX?#<{`>~3Xl694cl*#M}zP+%Cw$k>Q+mLEDoVYPfJ%qhbd!*Xa65CNL{>0MmNBj)p zufrmWrDFG!T4v!lN@-qs2Z@qKJj-MzlAmXIbHyz~M5D)ArCChl5>Ot(_gBOqr=shp z)9x>BpNg)(M|6o-+Pj#1waxQyC}7#)stWYSY3YbZPUX#7e-LC_7ToJrEgG@u=3ad- z3S@m|{X-=Q|9zE2ZU4Fl2cz0k09n7}j~m5IMB>ukwQ4Sl9N;2 z3aPbw9k~sOuY1pM%!`c}v?@o>oQ6fu{x09W4ra~scMwXI7z>jTc^vFllH)qPBSFG6jRZn;|V$edtg`#gx;sD zKi^QO-)M+^8>sh`?fI`5?!WTt_eB4aO+(U*?y352E5Ps(^XAAAzmQMjhFZZDtgk&3g{K2`P<=<1l^K`^%~Nbu0}kz*QBziVN45J(t-E-e>>m%AyD|W^ zwlOvy&}+{uR{pwDsN%2ciMpHzAN4~ZP3yTsXSKE%QFA#$CCVDi`{BnRn;wcGhRin~ zYux7L0J%OTt@39&vj5#@oq~1#YOoG~sk+=%NHpjca^j(|IpcSUG*Iwe~Nt~~%0V-4>(%os_ScOV=k zd4>>Ot#dVfIxVMXa!}ot=bY6lQ#QIe8PZwc82f58D=H=riajaRqkT|!$WYqJp2f#T z9v+w(yVMm|>-W-9mBw`{pVFM^uS*9)o5rOX2t6`*@%) zqcZ3D3&$21A7h4{v$hMm2MK^Xyzy}cb7aO9JfJDWmB<5VBJ%z~{1|?zi;1syE1fv8r7DGgMWj z1RT1iJ~fmlp3Jf*eU5mCR$)88BP*k!GmY7L84w|@F<4~A1-Th29PfjCYaNNd*!K4> zX+CGhTyz1dZqKI4N@`#OtM}y}W+MLI%$)z@o&fxR;wkve&rez1{~lI%08s&u|C}RU zQ$%C{yzU^hIjMn_W^z7k9!j{2-@PiNArIvVn1X&HnvP~cVWFxSZ|3#93E+ugXt@2e zZ7M6$?5#salis_+;3uTwzUT{Gu^58L@~Z-?1VBbcNyv@jB7Pad`}T)waUb_(Fwl}_ z3-`+sp2k0UG?rp^#nF&v4FfF>x(UYJJ>M~u;^FMijnOz*dyVQzHcE8V*4jQQ5<*V&nIOc0@X?1LlC;U0S2 zw=_FS#RE%9q8g z59c5CbdMFJkUg)jQkxaxagI}kD=}ew+2qhH+>kWma;RpFQCpOe4O zFS(>+RrBIr3;u^YD?asKO9FGMd@Tvwb!MgF@~U%xz7YGQ+i*XnFX;NCY5X30J^D9-4-G~QLwUmXr5x%exT`Tx6`?G zbcvoevBGHicLgQwPXy^{*z7cH_U~$bKfUh^;&)gNKb3(2mEH^hXlb>tm2Gqs2n@uSmaiYD_=b+iJWvynK$LQKni z3ya1@YVt9DmSN=G+=Vx*t^zpIX4M&=1wEA$@4D;{*ewm*7m!J_cgLU)ar=x}u|JnH zTO!pXd*>v8s-_GXIch%?ah=_Vd5y?)T$}H!iz#*$p0#`=%u)V1e5Hh%vdfOcfx57C zZ0+1|Wfjz~IUku9u}rCN+3Vx;g1PlvVD!C-TvBWyA^2usPVMKQXTqqkr$X3mLq@&N z*8p~v7xc1Hw)(m`7btU6+s^#pW)-ruBq%X^TNU8=v9hNxFZ^{r%y}ywdmyXGRNkiL z`ap3GK&he`{3s=Tz2~3~t|vj+FqN#^bE4)cVZ}i>j!}Z z1y_r(qo|dO4(jIA?Epar_S?*6FbcA@o-?4}CBmioPvDikgcZ?O08Q4N z9C`o2Zs_X!TivEk#DdzuO)q|kH+_{qcYD9;Io++wf}^;5H=aV4(onCVp#7vDB+ybU z<}oM`?-TK9PHif8b9{jv@&n(X?h*Am+C)mW;w zdMqyO zjt@jD6lRi`1=WXhvyXxu7)LcaZuYBeo`1oS8hP(>Sq_1SmC1QvgP0FDIR>FYtBZ@n zr*_yH?6>bZjr9F3k-mR};0_4V{21u_nVg!*N?64>^!otb z?mPww6@Xl1S9eeXC$dU>ar{H6&k!?(@|DRK3%x!Q{A56)2!+=EoM!iE0d73~J9LQ; z0;!)3jD{KPw`7W#VOW7;ri+>TZC$M0DcWgBx55t0~?bS91IY?z?l-W?^V9JkfhiN^0koBEqGTn3Ogb;*^>k+I)vUF>J3OLh*O zg*LP67E~R-Z)q0bo_L+EGW<+R=~?asbQCQ)p&&qVO#btp1hBF5F-Q;#2x>&~Jow(i z{Ab7i`8ph5LpR^jLjQzwr%8r?d6FT)IB~f4F-VEOfP?jX_t_YCEj7>A9`|bCK`#sR zuY)aIpjZX;%Yr^xn%4|4hzn<%$u|K~ZkJ^x<3X-Hb&J4yv1`k~}ZEA(cwb$d#FL&?{J z2$w09_5*M-YT(Ba z%p8Q(KqE<)-_S(7wRf!#;&Cw^Sq&VNNR>3S;cZ?h&??$*JFnq&_`Djmnj`obyiQzp z49f8=Il$dL@;Vw*_wt5JayS5XFgD1rT?qfAu)sGO5?t^$;s40yLK@{91!tge1L^O`+tS0{S)p#W$yo*nEMqEsu#qB z>@)fXyTVc|W-wkD^Uk1l=HzM$-o&?dB=;s88Xaa0qqs?}01MBDO`ZS_cb zh}JZ0w>8MUHa2sL`=*DjtLJ7su)5DM7JNqLy&?|^s|FfIsha?`~M{5koZs(rQ0&m8h3HZZ#*h7I{PTu3BySoiC)4Y+Y*s zvOe#x5YkKYWjyeuJEU!mX=&b~%2L&Yu)DUCFP(f0I*=+>sq8`=B$gloz)lbM z0hyn_5cc{=9EiU%%(5JVmfQDGhGG7*fO{gtd`HrXv7=<}2X#!L;{5IFa$$gGNALLQ zKjiWHO`pLxzVFmK_P2P)PB?ZGfZ;gnCL)eWlaZVlGn5Sso7|bXoAUE~^pkj@wn4b$ zlZsg3*ZVCTl~Y`6iX?)~g&$7X*lkAHoAef=NTtsjus=H$4heuqDj7{=+A; z+|o&w*mE-nP~iXt#YWkHtm38Ch(ymO%PgRzBo+OZ$@Hm--pRc|K<2i*=c(DJc}G22@?@AA>N|MPOj0X({+rTCkY@d8Tpl0^8((_=kC2~`@jX3RmdN*IuHx_ zQlw63e>K?q?_?E;FC`?Z( zqGpA%~`q z*+kdx&gj++hu~W2=eQFi>O*=0CiQejG=2lPaREw_IbFZl~T zKP4Rc0T*V^=?)ZyGHr(}?BfDFU@MRxWo1q@&04~I9XAyC!pf!tw>b7_fyvt)XmVN9>PGLOA$2*E8ljTBG- zGMAGtk70=UGwX$%HX-W)UhqdGQ(C;{T)>R#dojXEZdqq zZxN2lhqM|Hgt%nALDxB9{6*gCL63;3iP+(=noKOtgal4tN`H9{S-r@)X$N6+**~wVQ9B0S2dDh0JCD~b>YGtY)ePSOP0tsB`yG(8@AKJn=8U>j?uQv zQqGMT^CKCiVB7Isf4$=4q0Kc^_WSUt>C zRG_E~%O7yOTE9=cvI$r1ln=1Ye^F?|dqv$;&04;)tLzTJ=O+`QL64<*?$nK%V6K=i zE=}h~7x~q8_;b6^Gjb}FbS4j(-@G`4OO8Y(WcSYZKdQ11=c!T85rOH6;+X$S(vo9H zR)Pl|tvEZX+2AUJ87Fv!Ec8s)tyg&AL2d!^kFS?>q*DySK1LS9B~;P;<*Rd%ja$@P496CThnKMIzLg0>4!sIh2z%RS zT#_h$Hn-ZfehVHkbyUp0KPfTHjS zOXsuSG?~EoyW7?*vf<>o@{gZIezu&_zA?~KU|o{>KG=DV&55Fi&o;f8|3S>FSq4WN z8hOo^;!x`4wCt&XsGD}E@l67ETNwKuQGcvk##Txl{F!`p74dSD0U+`$7euAd+2cMs zAt!ODBb!}&&PKHQPHb?fs=d5#W#uBt{&F%r`<*^%(GG{INvNOLEf-0>k@@)--u}}^ z3|RBqVp0ZHu)ZZ#E7~06QgU(mT^J&$&?!5;{`Cxhc^1y{*7Vc*zD=|OIR~QhjIe5D z7TFG;y{Ia;*QMP7z$p(lABN@_F$L$2XHVu%7%6@`0qt9&@B4G7BJflM{tt=3ui|h@ zH_gWse}d+^4SJ(fs-HV0fsPuI_#>~BT^vxexa-wT`BsW@aW0Re{>Et?IiK(>>$dpf z$MufpNRiGKH12OPflhPnDYk?t>l}PZF-oE*c2bOm= zM1)C=O1p1G2fh{JJBY0Im$^@GDSw{m)1Gv>)xia=$XXv$_?K3ikT1y}w^Ge^@XaP} z`6_+5eCYa!vDUhVrD&a1s#M-Q zCP3o=tt(3@v7qOsMKGm2?s^tgT%rUhPwxr;$zJ!nljRgR_a6b-odW0nCg2=^^%ay! z{O8EYGf;&HU&9RoYyvp_M&inO>X5;~-0oXmo>V;mFnB;ij_!W7?>_NZR~Q!oTl3NU zmhnV}B8u8~CJfu06z5R-hLr_6C zeO&{B>E+!M0rWW2o1|G-8*<@Kmrb)~88}jDQUESi>f%hjxz3K@(q&^}QiBZ22I-+W zS)iPWhH7W#%3ZUsdXZYQ1;16yg|BskWKQ*ELlkHQ+yl>|N z{1RO8@4i1}%==py^Zxj7y&;h~D=WlX`%yn5)0c|bL!F83YaP8%H}0<0`2HItJ;GpI z`i)6d%uI!p_u;)_f2u;^Ou<`OmEEt!LrR1+e=AV&H*pO0^h8^%dJe&0q z)E-C1nC)ZJ6uIkG6;>{FK*g>?Uc|Q7f<`ab1AMt$Kh-cUIAJv7Q0YKDT82io#rf)ZB>FHwNI=ZBW8j>2P9t>-3B?Qz=z; zrZHhFX=oYakjRzFqjh5&fP4JGdB|(TL2AjyjeXpfSoMhu#T?Hpb?IB!#9520haa|= zn9G&Eq2v$k333j7hynR*B45MsVf$tv?AgG)@xGZi(^FyxtxKbO_Z<(A1boR^&GH@7 zZKgF`W+Gz;ggG(EAvp5Mur8z@D&~Tx*kyeBly}@DN@Q`C)KTeYKl-JaAksH;s&+}50#r#;(V>yUEE3|`;>fsH(}xniHE^LJC=MDYS?CmQ<9qi0&=b!7i zO1XlTToJ0kTDVjDL)fJMoMFS#o@L#@ppA#4{7Yu zrx=ZTvDW!r6*mdH4Q6S ztf6E{6s0tNZikIQBhg4DgpnU+Bnukt3~7y{k40Qbdozi|v}|No7;Cr?tTPjNl_N=SrJ;WnP&iyN6>$M9h!gVARh z>d&O0=`_WAwX_}_gtWQhrVXnUB9%>$*JwJ2@4mE47^nO}LRcMK8}QC@j>7Te~Hgt_IlFXMPLNjPCZ;nWM; zQIvSZ`rDUS4>Jk4^P_CG4TK$?>Swn6WaiQJUOm>uxW^}!6IXrhe5-U**1;F4aa^5V z1td3BSdzI9-%4Nf3_(hjzZ6C}x6KGApNO3y_nUSSu+M$s$#@Hv%6esGs3(u;^;^%E z9Klf2BR`L(=G*TuLMIrD0v6<4J+Sy?&SSt$grgJa#J1Qa^F8DPac3SoCY{TB!&i%L zBz_>=-UANPHVJY2f^`hSr7ootUgu)qkjvIBA?)~ozoAI) z(`Vz>Ci&C$y2o}ToeCh{_KFo;j?rOt`#nGc3{Zr&)x7L^LBh|d3$30UJlvssN_E1H z`YR;0__F1%Oc^GJM9@ZkK>pqczB|QjZw3yWoaaViN(bn z4B=pBCP)dZqW;^-Tcz&}b0j$g82dCE^7BLe-86)U@Aw@A6mnoKT){)5bX};?UY^LI zs{oRQ?VsC9_tptFsNA#dn%C`O>@_gJ605}}FlA!1H=yYS%57M>ep<%n((ku|yU$}Y ziCcBEs5l6YhKLx>urAc^cfQQ(@{eH%ka!!Emv;}M8p|g>b!M*`R(40C*G{KVlSh0; zahjF{!cK(m`lfXPS-i6J3Pa`1Mw(dmX5aN5>a<=_z7*J$@Jd9yb9@{EzbgOAg|Mg*gk|=GBNz-87`XcsaC`RXBt(11^t@szc5Wi^#|Kd5P z-irTDZ^g+lN#Rr{u=mp^(FqMReS|c@R)~4)P;05O%1iUGI|nuTOcV3Qg70P}@A`Of zG0Ky^x~4?xkmfMcxs`4NpYVkf7~Hf<6l4HHji2y|rM5p7FAzJF)6oY)0Z&+f*x;Fg zfQ%HpcPnvlHlGd-L$K!~rH6@6md~VYwC=!EJ$?H0OyFD|4X++GIcm#a%$*0mronbz zdOU{ zd*461?sRYb@7){!Lqq<7Zhoo0UiH)Vc)V}QOjXn)GN|JHOrfy1mJ zTSgwquCmId8)|BC%sjl4N&~S)ty1pmnIpZC#e@)O7y@#@Uc znS8_{dcYx_l^5&!6u}nwkW!Q67!-du>=^VS7w{8+SN-g*Q7`(HQD^H=_@`H$_QdI& zI9(n89dg2V_otGH>x(mB#|24kpeoe>zVteEpejS*=NLiKNH*9?M^EA0w&U}tt3qi5 z1FD~!i;`*6G3TOj4zbo+i5|cwOqC|cJ!$a+1c===!`1hm3@o8U?ovZpp0@C1F`qy$ zt{;qapq&X*6u~{F7Wnex6xXIPcJ&{g40=Wx)4PEhA#M(JG%Ji z@=3|&h(@&Y$sHAyKGgD-$5>J(VwusW2By7_SIeBO6jHQtix`DX;zMx2u0SVE1MkR+cnzj>w%<0Qcs>T7`~56_TbP;}H&MRs-_M#jCQm05@nZ09T29eGGDr zg#6eX+)Lj}-(Gp=0!_2VJ-8;lH(R;$CF%PHyfrc)GQjKr1DW~k^a-c^a5^9Uu|WPm z?+4i=euq%KqfRv#2Q_hkIaM2ffqd};Y$n1eYlBwPkuge%z-e4>ZJ^OCDnHn!ZX02U8z5Lso)N?WT`tU3oG zsC00HDZSgwiPg~ik{g>B^8J$zouJpk;!ZeWY3v@gppU5_C?zMhzeATylufKB^EP|j zMmKCFL}dGR+(tXlP>X=_Lo3Y=E-onYM8JNK<3CDtGv_YnE~jU`C!^tY^qS^yQ|n-| z{rd)Trdgy}q|n9v^!D$!z|*cfQ3Ot>&HocirUTZhcrsinUZSsr4RmML2{JrW1C4Aa zH?Mx~3V(Br&~;A+Q;;&T(8zA0{t@I;-hHX>9G?+2YrfHB{@`! zZq%mOHQ=P)O4(PSARp;?X|uNnT7K zGut&(o~o~|NXoMLHGA7$^sYr)CY-J2;M^`~-FdT~EkKF0Sz+z;Tyb9HP9lmnf|p(E zA;5479dt~a64_2^&4i6`_|0$@PKiVVM7+w|F*sHG`8mRol69bd0Ikn++#{V8Aj__S9}=gQaR z`2W!gGUNNUT}b8s(btm8ksSy01(mZ*;>Eq{eJF()=ZS*xt-4Dz?S=) znhbYNRJuqF@5#tlPeR{RU#Y_-$b7h751N20-wFvq2K%h%622X$a(HS(dqT20^KW66 zf10Cm+WvM~`qO?n?U(Q7z?tJdECVxrYa1&A9n+JRxh@oom6a8X1?!~6%ZtSwwL zU}1p8EM}@>2pp7+^=*u>SUK4^u$V;+j17%!PHteqViq)iYHlTOp`!;}^w7Z0SkFMj zO2+|<`GK*GwTyw4pt-4qxtW2P4c0v@=0^r*h9^&9y#suc92T>%@lzWEE8zH4$Hw5H zfu6a(0Tw?$)_1?lK6)|6R-2%1<0H%-&)ey&QbEDa0{L`AhWWlv2RHz;SO zue;^FlSRKhl@W@z2T>HyU{{IP=2WOoXm~B*<2J_9W`&=g*kBTf;8r|0hA&&AOc##h zAOyF_48?m(&q*?ceV0UN$VO4z%egxPFWAS8q9PD4Q?BEo#~XTtSRMmrxZwCMalFU( z*A%l|kbihZ%fn1Wk?ySQSW1IQ@M448)a5+`!g8Pdp=U7u`o=Sg;)#bg*ZCUIG%~dl z9P3w8I-%s#PhSoh47?wEp$aq zQL9neY3*vA2O3(ye-uSfD1NHGi4@HaRH3-pU#}3P{U~glc?dUA#DN8bI_LM$Q8Yi0 zK0zEIM=OMlc-&;6qEmgdfJEr%LAq}IW&2NBT^5(?l#}0(G2WIVIOHD0`HVy&YirLl zBzxS6GOlc?@==d``CS`XxQaW3G8z51bmLNY5D3i;b@ePY+Mu@Wp(wb zc^lsDNT^gUh?2`x(WmsBo;|vne4}eu z;Ams745>oO=aXM|lMqLrOI^cNXLNu4>v#&Xl+CVEfiA5{Y&v0EIK5Ut51`uH-L-de zaB!q}=}IQ|{0gTJeIk90`U8-rm*5s znm+YW?1tdrFfi*TTyAheyWR=7fK*J~m_K<{ZQv^M{U4I9!lB9bYt!9GcOxN!bV`?i zA~vM-`?N%`~mmQbNXE8y7%zxqG)mAL9gALrHIQy z-t)=3e@ywwzJqWW?rtZdk-)p1E#CX~ID%QH($oS`Tz@%AgeQrO1Ri`fs4O#ca7-+N z-lF~zv7N71wLq)(c7J-0-7ZWl&7X5$tiTjm8HE6_b%~rwGll;?eO)3 zn9j;7@##jj5P`%ZQiCUP+uHl-fiidwYh}74sr9R5Z@f8P`k)$HkaKNLS zociQa5)N~5z}l}B3BYsq1{S?j^{Ol(e4Wce{OvkTO$w1uw4h|yQdk$2iQ&(BCKdu# zs{(m^glPh3=R1ts8toIPg;~@h_7&YGRp+`%SY83+;xygu75#J^I z1LFjN<$Wwg4VrAGAm6vb@Dnzo_xo3Yb56-bOz>aalmyD*6#uh%0QJ(}P8RK6#LfR8 zl6fofA*FUWFS)zN7qXKe2BP4!q@?$JKSh)Gp_z3RWv^Oai9zN)P)4SzczY=B+OH2y zn-=DZFfv_AspB?z@323C#8i1Oo7>+ml{fbu?eVX*dK?^Y5W~uKql;tF=@j5SBKcgV z=M>nG-%DR!^A!e{CBJSH03W?+A;UTckw)4Vr~h2IGTQI+7!XKqzIPEOwd z+SzDxK3SMabCB`I=K`7gsyM}WovyXI9Zpuct53>y8Vm)>B~s|hx>x!-Q_!@k{5x{O zr|a{Q-+ccE&ZT?d0{3=%7$}27I%^p!vHzLVoh3V2B4YXTe8a(W)F?%2wRJMFG>mq8;{VR=|4$ZM^4p4Ka6 z=+oWeSir@A9#6A^Q|&Lu{mbGZnki`qjLx|+v1O(2gw--3q7Zsx(ffne9raz!G2?oh zk<+aJDSyS08Zk*8K~YYksd9@p;h_`zk4+vLj#?rVaPM<*#HT4crA1W2e9P+VANsM+tNlGe zw2>~Nqj6k$qzq5E1j&d%Vci+-M1ov^8T9zeN@ktmHTF zki)r~>kFsKQ-wvuL5I4l=u%8BtiyKWP9K{VCXq4ah2V!JVt%Bu_m-AlCyW!`eRbpd z((fPL(eW_N1+uZR!^;O)e`K8QZmj%x6%W(x{fjJdN`9p`n=gIyne1yf;ovF+b4hpo zi;kSIJ}?w;kW3M{5X|r;_82>?;1-I#^p1RVoL=TF6_4_CGogRLzin7!vtBNRi%;8K zljB-Y*T`B?E6dk38?mUmkG--41-}*X!VBzbCFz_%_y{+2nXBI3-UhOK124HA_x-^Q z$bQ<%TszyjQXbdYOpopCw1d0b82B3Dfj+8WN;KNe(RSmazYGP`HWg*I^&I>vdu4*G zf!D|JI(!jJjkDrP;H`V58g>%JkA<;`TJ6N0eUa}=H%}Wm;xBt=B^?``_J7l}IdP8O z-A<}Mw6AOuw|AZ3?=S!H%Oux81}4-xSF=bYW&8;%NXONSGXf>oRl^wl`If$JUH2SK zHPzkU9Ht1|pFm8`qI1?|+B^>?@9ysE`>jlV0!vCb1_lRA>5exE;brmAozu-@>u?T( z-WG(2u7AGpBK2}F{(7BPJr(U%^++RFRracik^pRLsC<1mR~~R0#{iBto8Ri}SV~SI zVaQ3YdH8zJAjOZ|*4`bsChl%MLjn!`EPT^Aem6~?Z3 zt1@+X`}(haQ~+mebiQiW++(+(Qy|a*yxdR%I~D`vxKt)K6}1&p%(>uhF&h8t_&yS< zEAUkWgTk#_Wf9RZ%^B8nbiWIG{W_T3*VP2pw({VrO+un%_i*$TPq{m8o0Zl(^k#p( zMcxpPPi4szoR-ZBrj9e`F%^x`DDExzZD!WLzw@4e)Gro3Eb~rAN=WMV2RXyRuCm3? z&7^O?{iw(!lcL>;ddo?JDekm%&|_s^xXQKd(96vLKNh;_$y2`s5iFM{0=|=@!mc~D zl!}Vu^d447`yCmcYDIaa{U?>xEN8eSyf-JgnFULI_TsRB_=nJB{2TAc8ALB*&WjKP z8#2nPKRqD3s`o6^eKdR+_}$N?bEdUmU=3W%I&cm2x@OaXkK#>src}N^be~d&QP7Fs;LD*n4jK znS*6!hMjIc&n%wc+d%*I3zTgDNDN_g9D>f_^^WvY{U-*uZds`sKlx( zggUywT(5vZE4DAcU@0%X^;cJ;YcDgpyXz&r<`&8UPn+VPZ{NV%tjNGhB8ZLG7Ifq1 zAJhIQGM(20CnGa`Tz}(5OP5nudbEj1I&42oK8V8b2~E_|MGbFgf8a##ZHPjM+eM9 z0&1|Gt1w$LAbMOe&`;McDB&{Yrzi}e1Y)+b(6O>0wDz71SOmZeT!@|Xn+XrN0B38* z-{3_>X1L}-Pva#3g<{F@prry)WfV}v}Pwy)o`6fU4WHb^qHP7LmeXsICgCQkBs zK7falNQ;N?bfifR7v`mU^M^lDR zbX73x2K)N^1_qdUKd8{RcmL2Ru!P&F!4w}k8hN+44W)S3Zp-F6u0ruid%1mK(Vf{I zKR1}@3gKl@mtjE!WpJ)`b0o;A<6)-sh58da`9h(#RIA&dlKpBipX(F^>TwuI3b+WWe4pY@+&D5t) zQ&UsUPjwpz79?R0;NI|FBz6-zM+TjHkErafcsh|^1hY`YhoEfkxjLW}AU_h&%8KR{ zi0sv>A0NG8+&2H^`Y>H8UZ&E?lolU42$8pISzc#qHxj!Y2+|L^IKfF=g%Roiq!yJ- z%t&mayZC(lprfO6cq~-{HoiaK(#JA+9UZW6%kmlm*OpIz7Xnp$EYU6qD$;(cW~4h# z2QUVv@!~%t^8OdZZ<`QkFYhX~O&ai|nWOWMB7=tYZi)6se)Bmplhz!4J!N%10yoK~l@VvZenuGldmEhaXEz z)3F~SE1P}ZHUW$t3v;fXR8YY4c)-RlyR2V7m(5ltQ^(7N1J)z1m>%%7yMttQ2TMA+ zW8&7HK?-)O>5f;AO5BhT=nKBbH&|q@&8kr*;>}SYA+ti5e4i1n`01+mXn9z$w>E5O zW=(?eTTf#6u?ffCZIrtIZDTe=3NlF7!-jsz%6c~>ChreZ>2IGJAqza4}=0lnhi!kk+$oU9bF0j8-fcQnQ-nP6rf zR>+Y8g%PQxZ9jbBU(5IJVMTU0I1b1DFX7=mofiRrqax`lCr;)t!2p3nMQahXn!~p? zJK3awGfD4@vyHTlm%B>jG2X&=W0BWy`C5>;?LbmXsj0$S$!1)72`?XO>mD87nVYk} z{?U!dKmtKyym*sMRbe3z|JEnWzhH@q*JscNvM|=VJ zvM_OZyve`Yb0H(3)d`&$phrx%L3jHW`Tm~HDvRPXOcz5@2BNuLSakB%t^d3?{IQeX zZXe`Kg7L0K&(YYo13_pg+spK_DL15n!rGJth0lxp(;snc$knRKT=rlM#X09^ zCc`(pT!CrdUSmV*b@F@4@uK`ebeRdMH&fBS@V=4N78Fn%>SqUgE=ENAqr$m@j{U_Z z@-T%;Dpv1HOWv(fezcKN+ecC`>phx3oUPZl0%~It;zdQR6}Ju11gFunJPT}Qi`vW% zr7m~qhEW{XXIb~(;Mv!c`oXI*ztJ1ki4z0w9SheeCWeXzHc>7P|K&47UP-8gq}`Mw za!znQ12Fs@kRI5Srl_bfXW>4y9u`Uf7I6@%p3U$~R4|Tr{4L?X-B#D6ij^7Q5<|1} z)3};`_L?^_*whgT_=gW4%rKy&RMlEihqa#G1cxc+lzAXznie|C2{`>~^FZEv1j|6x zA)tT05&YEs$U>}p;EW%O4dH^fRe%3Z8=SVSnLP4^-iN_>=W01a|71iJ(`!kquNDue zHka>$2Amr?Xf4A53vgm|*?zD&98VLZjo*lmp>G60E`4CO=4ya1FzlI z#i`Obq5;X+&cpMG1E{b?zkGz-k^D295$lDwO0I2ebbc-w&yw&wp#S}L*AH?&mc$zi z*UwMkUNG_dcd_I3MiBp_Y+2BBv>6EhO^FxBudgR*dC|Y=cFc_aF!O#61Ov6RyUKs1 z+KkK*!-}rUxFEy1`@svHot1P~7jUjcTH?fnhrfyH;+j?gsBrII4|z_g5T@pPAAk4H zIRmL6%&w>uJA?Su;HDLcECfObq)I&I810D%jAaCpR`H0ioj zF`0?gH!S7+S6q+~WkcBYAEwVeE>Y8HQ1>@^l5^6cK9x2n9S^3K_sdT2SQ5D6ke}|a zkE~^rA_TfrXgIpcAz?!!_x6eli#kcKz77)VY$FY%P(X|Hd6Ns^+Rs0Av^7HkRIk?H z_qS=4mtiFxIFJNbgbKa1=K^}=o%g%{-T*b=?CrTgtM~J5hI@JS*h1pb~F@}KtWy+k~H#NL0$mVoxzkc^k+edoLD)VCC8=`92v=vwa+MVp)1mf z1=){g?Ib+FokR3d7bFq}+Ac`6525hJJ5td1S#_FE6usqPO1G+qA7Q#T3KWnFzSB|7 zv#g)L#3i*3j+C2U$=%dun!6GOt4$uYpdKU-Np8G?wvigmokCsf-{0CR&zX zUR9+8HV7LVYu-NC2dpuo}7Tmp^^{*)39W{y4iOsp){P`2r1C zeBr$m{T`+(>-|ZeZU<3b#io$K${eZq`%C4X8>9U`Rj>U5ym{+r`Rv8?Isk z6KbH^d1J9#u^Oky7B#t^ZU04C3tt3@O=Re>NIzT+Z6eY-CsK8;Y_6=ox2K6TvRI`| zw^xnRJ-<0sH9-N!)}h|Rv`1ad0LWs8Q3zDT+m(#})euY2zm<#?-2jz1GTc6+!+cEjb z_tksq-Q~?Iu`Wt}X7Y<8RP%zz<^!nugmsGyQvlBaL#CN-yKtsQZR-RjTldH+f?5Ae z@m2t*ktm{n&kK;#?hM6jpnvUU@!E`Hx&w_!5oG4&>%6yL8ZkutkmC05!yBAu{2+i> zTPi>5;0H~RMS1}h%~w~S`ByO(6XFE;c0p{xlV z37_MuG4T5zLl@b1H^cX>bg;jgVSlE_;rl2P@h|a0xBQ!p&HRq~)%iaiSJrRe&`f6| zd(HtxT$(3I81Y%6;!<74wN#j43q+^JWkyS@eD!Cgkz3&dB6UA$mzHRu4U1uk_XxsFzlg=T1yaYtQg z{})T#kc)UN%@4;y4kJ{R;U9PLW4>wfHPXsA>@uAB)`fNBR&imSTO-O``T7illIy(U!0+w-rN z`@;(=!^6Xe4x`tg{_+3XsGYDZq>7LkZonT2yQf>g*^B4d@VSPC;jcO?;~Mi!?9X-P z$0KQ;T0n2;g2andv&(T-2=z3J-q^C%;@#TLitBjJ2Wem5 zmx+S%`&psH-spk|_Y&32_#}^$VFIVzGDo_^lxqT76!_TjpQR*;ghb)3vWDqYKd)?^ zwpPa{8@qd@P#iSiE*bZfcjaS=-NQdH=F3_;$ogxURna{q&_U3VD21UK#^qrFG3&|4I_u3CquSSX4Zx5BE0=tKP>NvNrw-pu zF}H)dNHVInn9}pNLhs%+?cZLea040}IjX=uE}EK}F~Oo&riF9fj5UPA|KNk=w>gp> zo(GEq{2H+>;Idho`ok`8fDEE{9Gmf>P>@jf#}uhmy;OQ{5I->-)yNH4mr^z4+usEs ztlh>x8cCQ+=x1#CL6ev9s~ zylmP8%>U!P%_St)bMgBsi0b!q0oeyl0h_n2s*U})-sLFi6@c0nP?mhICEGnOo zN4*Xg(Z1#7;o<4pe*{dTD7dE1SXyn6P|J^$H_} zYU)-Ymx7!O_*J96`)K@E;o{xjK{xx8WD!nb4=N$rk}s@>K@{5mDxZkjJvi~w&$@tM z`u|xfFX&)4ob!$0UhlxL>Nv&_`fh>jM4o&&`z-RrLJ#D?Lub&ygz2&NAkR(;+Ddk$ z@uV3OnzOM?Wx-oN$8K{uXVv2wuQgg~BKs2iBSxvcx?f9~FCweFLLn3dEKzP=;KSQe zs#}DE1gxp~yi^>HfF=(^0J6`XTLsKNo2(C^2ejKV2cduzha9zld#AL3uNRgEfh2G0 zH(OWIUo+j)aKnzF?Y;yoFWc6hn-w}$F)7vlE3pBt8yf+gi9ceZuE~MBB9z?CZ7sq4 zzEfs0x3CsVeLWOAYYf-taklg0+WOjBx**0YU)y(vG&D4}N2%4_BdyDV0oI#~N4b{q zO;#LAt+f?$;$ajTDAZIUKpxu#@&HoKUEaV2h2PwshkhzwPB=w&j}bs)ZDA6V?apKO zg7;bQGYFHKaFmx)fLv*R()de7DPnYt5(fFb6X5bF+(Zgw!~!}qR0r5Cpn`*Fjp z!jwRC!cN-a(%=%;pC93f!Ca%U`v|@*w;rbpMHpf-h!P*5TX*!2&Ikr;nP_rY^1EQLJ^*j6eFq}<+EOy5)i}$qu1q-i z*SU_w^)5gGgdXlfO8u*QCe2Rxz!skXAOz&VxjlU1lVmdyP-A~&Rie^BDp^{$u)}zN z>&vZOsXU+K^rFSbaKB%GN|C6v@jzXi208lv0VAg6^-tYu16@S(r=*ArIfamtrqq7A zRVDEXw%7{M*t+T|hhA(hMr@D$ayTPUSz-i*?*9P%t)n69?NAPlzb+lB!R5f?<>ha- zv#VZ+MxGDawO833DBf#Qn-8uZ^n0eWp13Z^v0H!MIqNEWoRCNW$MnR*;>0fhHIB8# zSr8cKf|URQd?d`k=!jnKJHkRYAb|J;=?p z*WkL1U=fsTXK5iA0q zRtZtjMFfGQAyD`C(b48P-@D5nPeaBa7Z>M=o9m;-xJ*AD9BB z-nn0i*b&D3kP7F9FMg(&yL$mu477{+UQPaI#=bx}CH-O>5b2FdTV^d4`+$ zqR2r{=~U!msuQv#CirpD0mz^{(>Kh_55ETnB@z~*t-Qa7161p2_F?6@&TNCr9V=s7)nm;LBzy7L?3?Li-6f(lJcWj%7m6F44 zT6Ta9u-ATJ0eoQ3ecFeGk*d{4R$A=y=SUn>AjP*3CCfuGG%~1=#*YO-x;p$nw6b{% zcClXaM0HFr*xlT@s8@!UHX$)_W_IS8{_r1Pe{)I``;z+sG?h6#75U8Mu_XG>gAzil`*IxKk*yi0 zLQA`B4w?%;=fjN_=l*Cx&!FPU@kgv=QHnEYcw^~zA=$k-2b(GXs-R$m7aDH#&B#FsqT!$aI z>LtO&?B1kAZ7rd>gam1IcL3voNvuW6K7>_U=ee_Cw_&v+8G7hpbs5w`@F6J;X{1Lm zrGcWN`GNs~S-;!)#)_~N3mvQ1eP#?o-D8oMEYFwkt$N(bLoD`-I;E!dCSpzhPVR@Q zH&ZMQ)Bc^NMfd(M&EjE4>bb}_sjV52znH^Y8{aaUrw@F~M1cgoh5QmM-73ch>*)neQ|kF+ z=~~^RJ17e>hO0Z`rh4D_yu8lXN7`)1`93YPdm4CR$lWnG)fQ ztu2oSUXdu)$jMI1l&}~W6~hJi-D8%WZ4}7chy{y^i8Th>H;D+ocSuf2(WM5Z$y_%U zWr$f^V(nLElAgeUv`>>O&bDYqWxu|kc;*?G)1=p^-}7{x9R5(JZt8}Y5%6WTw6Im( z?73NSr$Zy|zLL7XD$BBrAMprpatq|0wUr|O-LWy~l|Gfx?6AcLwYtjYJC#srGn~YJ z7Wi)=WSfr%4p=83ILawmA$a90#40WOW1$gk#~lbcR*#PWDO&_MzN+g#3IrHM*3syW zeq-^F94q6oNE4#f(j5BA2ahHYf4@AiDoJJ-4;1D}Agu@|ZdnblgI2yEbMR`g zGY(1p-s|hA9Z_t$vCE@tN%Vxr&XPFv$wP=)GtBuC{s9|WfKbef`pNQ-&}(O3m02Y^ zAM>D9Bl;Z14-Aq*h-Fy@JxJ=Ak%?J#D?MRmCJ|>-Odi>|zQ>la>|V9;lUSpnT4wy| zdV5=Rx=nt_jgg6NreX@w(;7ZEEQjSJf+ zc;!|M=yxA{4tkbe99xW#)?+ES$thrXk(#H~DYUus%ncFbWU2$`8wL4KUwrO&Xl@Yd z_=3-h-~oAhIK;!|h@{@o9R1?{7{8GXd&Bcy&^aBAps%Fzy#F2*qVzMGE&Kh6_$x~$ z;B-#?2#SQOJu+~_Xw<0{8SrpxXEO8-iZlVae^u(?NppOT{)3vY9&=LY8ENi(!bQf_ z)q~Id%U0s8kkFX(hQ2`}_VfNzr4m^*D*-oRi}j!7a9g^~NUtpM`5}#U0d~>x3>AdH zk~(JQZ_i9Gub^Z!q*U}Iir0l z_YN%-0W8PT36nJZy(aN&?a=&H+|~Q}jH1WObMY6zgG2z(Up6 zWU+G`ZAZ-=E%l5`2CX%Anf+}cAyK{~qQzqhntDU50qCgx=He%4oZHA6O^ zE8~@s{dHl;WmLY5ga%$IfcKY8D4n!Q|G`kie(aGZZ5qK;`_&j8Gvor09I~Y&nDbvJ zYqRO%yUJ{A%Uu>czCcHTkc+)e-De8RZW$jd-N(Wb)y@u#FAK2MdTiZn;?G+sT1W4JF6fU}2AygT~iO&U2qFzsVS z`){~xH|h&y;;SuEVb&t&hIq2;&0v#?UJ-;&Xx{WV4=LB1awFNad-&uf! zoE2KFMX24!p1u>Xz6&P}DxUxR>GC+pcx9ZtePQgUE8G4H#b)&zudS;mGpvR-ySy1 z6Kc6KM|waRT9wJY6PFASVDK+a1ovw+_iZe|=nWhf{VogQUL3#Egg_^75}B z))%fOs99tlrbW|YU+Q-$u&M)uJsj1ZD5Nqe+1jiX&x&jK<`{X8=ngI14{>P z2=b3?INJ-=bS&Wpi3#awKO=fPktVhM)O;KaEVq6C8F)o3CA<{U0IPGEW<-(Tu*lW% z1TvaAEqO|&zYET>9&6aI%h{}+coiCSZq>rdp7&0#vVw=I-2fFrpDpEv(eEh5Y#*v325Sda=$;eaRmyf0{}o6NuzTA$}J{%c5V9N+Jp#tAbYGqX$W=N`|C2xEZ6>e*O!-`-Xa zpj6Yp{(V4Hq3nJIc<1E#vP29FZXlOvDV!b3e5-D&Ew6qKTug&qDV0TE?s?_BE$KMIp* zf#z_)zi7B|FN=>ON(3$l{0m()ILkv1vqq@_HN5 z9if89a}VkyV6=sQMFZnioX8Um(oeV%eH-gh|LDhGZ;RxyE#B+XGL5?~hG}#H!xEQ8 zyBwJprmw|1I+9%?+RtsW$@Ml2WVrQ{%p46V*;>Z?kr#G*)V&hDLGc^fR~DMi8V39? z$RN}ZR^Zutzv5z3M+3`Mf`8!*jHimqiu*wT?0Opdava9Fzl&cT0;!2Au=xZ48L{ar z%fdlLW0>B0Y9acB>gr8i)mN9@BA43Btt1dLK8my`Bsr-E*UR)t@kTuBZFLV8IUY?v+ zu9D);Gy4O4c2*Cb!wE6q>@GJFD|n?+L@wF+(3`3s+)FKy@YM&jmaR)+y)Qs9ud71H zTSMZ}pRrG$8)9pd=Z{S0=H})i_r+KAgRSxD&JVnE%h@MD_=T1vZEHXrFXheo?PT4G zj68E)ZVycXBG#glx0XI^rkOE0$CBkygf0Ag13Y{e|;c7!hQ$Qv~49fP)&;!Qvno<HKdC^H@4)evZF#U`>nK zR;s8(h5^D7m|S`!mT`Nk#pwQ$0z%b9bvWV6?CsNW~zm&A5 zfR}@CZ+~ZY32~c5qdH=Uc~?M>I(|cU`R~ip&u&?RNkhnv2M)nnOhXHWY zsOQSVt?3%q*NCfxUqOY)b=H`aecIqW%T;h-$7j+`m%Qcj>+o8bsaR@jflV)CU~_>` z8<`}l9Rn8d{66gJgsq#VL(#R)hJ64LTos54wwjzfN!t>@mDU-@G~#Ru@G$QwjrAIG zq5ULz9i3)h^U1F>A>dBd4iD5$2|P)L>DC^H2`%D}WDxOm9AO)7MPL~7tWYz5qzArT z8(HsA&Tj1Jo$sBmocPrGb)l|}g5O5^Mc#1w z5)U}!S6kmGo90<{x_P(@J4iwL4)dwAL0m=f@<0XIe?K_f)MmM%v46S|iwg@wg$x7| zNKDKuSN)mf2|q;w>o!1BW_0aEhESEvL0-Au7ED9StFw_c_fzx;CE}YKI&mZ@r5_$vbo~yO zrqC@sc-a}6Sp@%0owe1r&37fZPdc*@2`y{tS%C3-Ph%W-+S3Pr4Q^U*CIir zCr$7?EjH5oDqT`@Mwz)vF@ZZVM}$W1+hNtC&l`>$!pzQ|NTY~Fuo+A4`YJiM8ItBy zm?@iEW5b|07WMw&;WBBM@46!zi^k_fl4t(OxFqXp*k#safis$lWV>HvnoP~(uRy6B z1)YgHvGLhzi;+_m4GC{cdnyPIPvz7#lWhbFpBDJuvDetnIuN=uhp*$T&Mg7}q=|x# zUyPnUe-`!(i;hJC#bWw_Yml1M^H}WxNRYJh$Fkn10r>{PD&yiFvh`_ntWW?C-=)I! zfZX4)vELb{VeWx!7Btj=@C6D!P1i6(g)^#c{6r-Ly`lgyM-3kJ$|nil{`mYelbd7{ zKV)G}v4&rtdL`dM5$LF+0K(!S(hZ@It}0Pj!dIak=-w6E^Nv)N?|Fk6^9D2Abl)A1 zj{}3RRN$V*cplY%f7Y#Q7!?qre7SwRsj11+xk)AI{t)Sd^5=PdrIX$FxX_7Z8|XjQ z75jJ72bmD6XaN%Y8Q}5`34Z*>91VvC*hG#V{@QLRpA~^mhjkyFwqe&ibtVURyWo70 z^@DSnbjXq2c9}&>-=CA)&*!{5+_IvMNSTZt-we}jzNtPwHD#pd?8i*tx$F|xLlq=q z|92he`7MPZeba9LS(e}?ANnc#-#Favo3tXK%hN|-7!#GWuL_6VdXg;m-n^-HR=NjP zI|$UB&OJiy4f6K((h9bpmOeN0|?&;U*On`qE*1_ z@{BW;NT~SD`<&S2RolFgAYT>&AKar+;;-9Qr*xEJN&Ly;%VRM>ythi%VZGJxxW?77 z_qvm9G{3qyEbXaRnRn|fC)Gm%`lP%o@_tJktVU;Yd^~(eU`M6FfsP{npN(WxaVnDQ zxpcrC_2jj#rMoY0!;f-kC8XgbOFIcDL>r4kvLOIcCxKq*j+C%_dhx%A@sVH9yAq zJJ801JnX+bp!0Um2>SKxH+nW-FhvE)+U%<`f**-NCzXqH&A7GuaogIE<-U0R`b)HR zzl)pWWjk6L8_z1zNsfnhRM6g?RXB5Qkex3Jo+g1MG`5`jw#QUEu!3T7)beLoeqNfS zqn@a8{-d;%VE&nMSd2Fn9=LqOXW({Ta_L7GH=e-4JxsTMsIW{w*28b3{fA`8V?%5% zMI_0+C{}cOg&&|mivS&_`mVZDq1z$gSf(WigBkZUB{+L6*vJz=(Gw_xA>m4bxz;&} zo*gi79TfDSPpkI(`wgCN+y5CnUk) zLnHGHa4L{K17?w)8>lE*fXqS}Fi(E%mgxo|>z&Pqi;NeL>O3f7;Z+m==*IJtd&}ee zzJyHZQ~QZoNj-y44Ifq)Mwi1ZO(tvs&wpF)UrdlX0zlA5;H~G%?L7sq_*BmC;Zd1o zP9M_3B*hbm8Dtb)C;srx9tj}<*?Jr%lku~2fKKiqDd|G5mcDLSlJ+UR0@5LX(7->| zn0#mJ4i)?YM@$MJN^{_e_J%m3eF9>QR8Pt}3?9165BTFH5uWX}7B~tV#6}R2pM`o& z1>nI3shs$DN&^F!$4!z#f&bfW(VwZsM*O}jK#r701MA5{-8~<=Lhe*$Y<&G)1WI$I zLHvZni)id;`XcG*N$)%Hg8cC?*nHcHNso98X&@#$d8U%sSllqJzaKEABoojbYj>$! zh{J>Dn-fm=A`{_soO#@mv9qKqb7}WPUvpcbhdW0{M<;P}RU(`)Ur#O-{O)>OuU-!n zh-n9gO&~Yon}dR)8D@@eySR~h%khJve4ge?b!Bc|ZE;Mx5;C|6Y5CUhBY{=#CZzYX zCQM*^N_29M8Eu|pzl6TG$>pi9+amG(w(9l#)F4yjMVyD6?m1@AVDr;``W!KuHcUqA z?AhyF44`?ICPQ?p-kgf|xBO{==eHz4nDD$aK5-#H)#&__x5FD&N=i2d;D`5oJR*QT z8Z_`DY0F>i*O`KzWg8@9f1z`qBp_VLv~9?5@`_sYFX<*=eaWeUw$>ayBkSumGNMVZ zfa7SuF&K*bI`UB>&$-v1Yohi~Q;o9>*etetm^f7A#64BU!tYJMwhjWxVRf{@W_TDY$6BSp>odq?*XAMG#3zL5SdXn16hXPS&Y9oGNkbk(B0-U zz{*bWtYTqnjmGz}yP60Y=>H4u5>F%Ursrv^$6}IF@ymuV5|-oL&4M$={R5d^;1Ju! zngW#$#ECX>8-kCDd-}nBP&`6+a)g^aEHG}M`#tH#lTJ0s8<$`!Vx*&@8+4puq$%YG z?2$CSGZTvXah4s9k8*?v!xLYDL+J#q`XGUA!vsL9fPK@ul@{!;BJ$f4+9VE!Xhw2xP&8ba~R? z)*rP>e;1DZwx_jLcvc8sDy!s7DA3?Fsj(qcH#^gyxg`?FjQyq(?k10jCtbuk{(KLW zxgac{EC-0hH>OWX{WHMhlY#lgXx*<3Mv$UH2&~%$58O%(zu^4tflBDT6c_4u<1h*r z*sBX;h1*;r(^sQ_#6E%S9SCYtRs=y~C0>z;u8akd&py~8 zBZ)%`#~F4Z^%|*Bt1b7;`_^H0AGZ1uhFS0OIgF5O^=VdE`fbOoJj2`Vi zb9=s>P{39+ZEj{YXLAeZm@>Vd2@2|D2WdYAC-iY&P6P#Uj2y}ljK3pGb_(PmBlHxp zS&k&RfY*95mP1U3xRD2fUb4;xyKzgd?sH^$*XBYo#Pq}-JsHQJCTRdUUS!`_UR9iG zEaZ9^=Nw0ya4}0;y@5Mu;a5~83RTFY>3zh87(hp^r10kp>OhL)-XOO{(Cz73 zLHn90^66t{v|6mB=j~}$e}l1tB&_@FiK+BsfhSCf-FrF_w{+zl>IRlDC4CE_6Q`Oo zQ!ds4x(LZAv3H_sa!;6~@4l`IoU8fvAfa@B`*-lY5vM#fCEz?bX1lOnv6IVp5IQH1 z2{GvMJ=ro#DWBZmm?v=bzEY=_Wc$ss!mzn1H#a%;@>vC!fuMioToLwFGmYcUoUU(P z@`kZ+kln|pdBzH>Be1^n2Ey0Rg$LsUIII*N33Sc`2sP!bdYOQ zQyXIg`BS)D6pxM_mfnt58_A97dzcwqYVFhOi4V<*6DY>U=xMDp1imbN3PhlqJY3Ec zR>t3x>PDT#$s#;FAhOkemU?aj=;O4iv@abxA*COt-2CiI#%b9XHZ6Zu*wcje7^9&f6f!vWF^1dqV0@|Jdo?$4yky%J_!XAc1-E z;ZoAU9+jv2I~qk}avjErUZwLw8c4>{(hYTa%V(JLm*|0n-t>KQKR=iYtozixnKi&w zBR5K4k0FTn68z;&EK{JtyY~G}BF%g+TmZogR8o|HTxd*IVHbDE}QA8gTo45rTH1 z>s#w!dh`i5SpA#SBe|r4;ObK7S1u_@c{9xgu>8jsy+!qH^J2oR-kseqBDdmY2T$KV zzHuUKA@)wAr{~?*fYJpi@!e&biRN!n=Zo$5aA_l9jPIuR`J_>;A7Eq^ zxIo>39=#>LUMaYg!4jzNM3!*tTp|`KE3|?1F5+U-7oK2w&8}}*HnRW&eY3*TKYg(d z$idLVJQ}bI`zsi*Gs_~C&2mS;qnr96g{aSyIbrwkaPpU^J9NcM`uMYtt&$%}Z{bS} z{kDrtai|)+HAZ9?A3r)G2BFw}35Rng$zx9!Qw@5cr6AY-=R*7V_!t{m<}#)4K2yfh z4+23(FRNI)lD=O^eSSl0NKAueRSK7{_`3fCeid6~! z^v%SZ310sb?l+P!k>GRwcwC_=4RWejtDmu48r)6g5d-(AlQA#DLiZauh1l7PCt@^A zXl4Fbi*>2Cwn)Zx9gR~>IX#Bp5AvCcYWlZ%u3bH={{2hEz@5{e042*u1uC(PJ3#C6 z{(LXTpM>u%c*LD<%6l%r4}?$Oh19pT#Y=MFCLu12nxQ$Wb5BRTHmb@beKdm-Trr3 zTdTy&iABWqh*$y*-YTlC7skT)yE{4z2;!c#(Ot@2$ozp=4fVeBW?}n3*4{cS${>0e zRgmsnT96J|I+X5C35lhV6p&m>y1PSCQW^mTmhO~pkZu%~6p8z;zxuxCKKHrtT>j*p zIdl4)IWu!M<`=*DrX`zpiKQxVeH2xGd2bu`F)?^P2akuBr@xbJLG!bG{LtVam7srj zucCH66RvvT>YjY%y^e4I?=#5Gs~$qwyLai=2UA6o=hbc2&ruw555tXPvV?25X72AZ zpT_#yH7Y{)uJ_`r3)I`=I$d8&Vm$f*y58{|bd86Fe!_e7q^$4ba_iK?IDs#kz*G-= zfqm=shLUH;s4B)GRfdrN07;Q>yf`isU5q+~O75M=7qu#He?rFXe%OPk>nSZ&6r7(b z>cjDj2_83Y^{Cx0eK1IC%biUOG1EHK?$=ur0eEHPqA~GJMg~s^6h@ezIO02KRE(m>Yjm9k+;rku2n$ zU>B9^SF`d_r}df}I!&@@FibeO@_F3d+J1;Xc{0jKV(7~OHwR%{EiculUetN1ia2OZ zRGAh7v3WumsXvahW3c7D)pFAkqhAtu!6eaO!|7bDbe$vJAL-ZRz>n~~k%3F)IK^XL zkApqEJm7Vjzqsjc2N|UHH(H>ceYg5gy3)u?vcO9DXYWMUql`U+F54c?QgQhfX6QqY7Hs7Ao`NUkP^TV@eSLnDH zSn)D_ng?r{zw_qOeEFV0=C+iwRwZ<4uFwUXz8=169x46y@%~`>`S@B0Tdv95$u74y{@T&;oH=;6BB&mB z{oE~yg^q(-1a*&3_Z_gofyOtuLvNt&O=fRNS5MgnJK%lGMAH@|D{C^SHCA<0 zu-YAoFzEojH!2RB`L;)K_f?mKsGSwQRkDl&?}et^AvgQ-Uzw8Q@90}M{-QDSefx$d z8uBe92nY09(<^Z?0pj?9qzVmHb!0_6SJD2jk6{YfBh$~~daxw~#DFZfk1#BgXr z1D(h#)?9j9YP)f8HT1o=PSGhFiA>%o(CFbLM&)!)!zSN9a1sSERKfd-F78RL z(7m;_Kj){mn%n!C&~p%n{&%l4L*=yU)ypv*YNB}TU_EFD>(6`khy6b%;DN>up_iZu zC)EKe5}(J9KGXN6j;o0*uo7H$GrQ=za1_hjJpRK_XR!K3= zHyE1C^Xf+RkKbD3^Bz1OyFusDFr!y7+2S8%uwd)em-{GK{eN2j_PxBjyQ>P1fAsvb z5Eg^GCY5qTBm8coyYk7dWLNkJFDXaH^F1~WM`!yL&p++1g3m<|4=$wbhR#wP)-;HO9qIIOYx-wn-o|FiQ@;s-D27!P5 z)VTSkSCxP4H~@b9-NF25^!H$Eu@1)`Qx9vz80MwRU`E4a9Q~BNL&y^gu--~zNBIJS z=|l8XZ|E$p?iW&@Knu;qwZ>5IqXF=O>+Npvz~kNJ^qdQ|h-mjaPNNJ+dY$qDjnb;w z=a^`SmzlPw{NjfI)$h$5OccQT#+TzCj6g33j742fE`JC-^9Ei(n7vn84_#yUxj#f1 z_i#He9{=3v@qS_V_@>3{9b`U(oY&_Tsnv)Q*XX;m+0Wa}AabiCNiAQT&0YVytsS37 z(fi+Psd*kR_g7Qe;io@w>F23LJdb{ssz0{*JxsK?tlIH-B$}ad@mA<3<22-&y}t2}?vD!s@TOC=;=zm!sU zPoLG2-^5s4nb z*lJe`lu}k}O+An)aa(UU{N&ZfhbVZtN;0ulb!pT;4FrOgzeO1kUS++rY2V#uteIwH zv{?G|$F<~LiARNUEbi*J&Q|D*>C6n2mkBTK{f0CDAN%`*UGl=^&SELkLFOehkQSF7P{E9oDuS4L6&@ z37;Tr(6%7FEZCs~3{xhXlk!c%xE1v_B@8`HG%{=%iOvt%Ol0AlAjW88pe1@N{K~Vx z<25+wFQr^(?Q&9B2cr$D#}qfS;+NbT^HwxX-z6pJjb zRH_}L*kv7vh)bs9@h_E)z6HiQnzau{bZsvhQ42>Z%{UlzBS2kqH%i#CrE?;CGE}7b~478Kbr!B$!xucZ@w+cTfRq7=p7)g!!B7 z2UxL+V%9tIe&}m7DrB90(+Y7jqZUTvmE#qP*(5{$q=wL1kPG%jw>S)W1wwg7W%Uq{ zdU#VS)^t?3!e(QDhtc9C1@-Y^|F~Br1I|=TdXXdaO{yRNx0l0@@e7`p)u&5$m>s39 z!{PfYI=MLxow~zcaoBIT`n5HrG_j10%aQvUQuXvaUGOB$kTEEXCdxoF8r7jo+}pRI961r`D@#C-vu*l0YwH7osC(422)i(Nkb)VvO)O8U{Z ziX?rjn={au7=n9fmhuJm3YI1#tL821HEF!_n*D_BM^{Df0?y`zF5?By&JUTy=eTle z`-W76p~8{Ip5}sUKU-9Wi1G6;z>U4chlthA$4u>(rm~o3KOT|cbDj^ zUn;_cUIf$)y=r;M2_?G3>dg?SeKW-)WkG&JQR)7R4?ul8eP(aUCPsnN6QlLiUXHXr zlgIH5@Kk;$b;I34al4|MU1>g%h0rUNto3P)vJMeL3dDDRrx!QNvQgWjJEA8heAL@Z znU20UCf9d#_n=%tz)4lp*{AQXsh@=UyQXjNH92!n!D+z|q^r>n67#at~yA7AAemg7jc#nffQ-KnRR65m^N}O=^WDEjcNAAU$lb z4PtG}uy4LC#q?zH2@+UHxjE`iB}FQDe$~~Bmj@rlyV(3Xs_*JL9$hbNertre7TqV? zSTpPI@H$Gly>Yl^J+h|i!L}%~fcXL4VQQo6t~n!tZy7fLe~^CwXX!f zhPy5>3=m?4BRxTWyb*oaU@NLOKwB-hC7 zmugC5*pcRxD4+e{j}*NPon4vNFd7qK)<&wR-=aZ!GB+|-UaP|Fja0)W@9g>Nsg^c$ za)b4T>M5B-+P>4bk+tm?Byc)RdzUk6Hqd;KXjODT%d<|r`Eh3&mZ6@K#ULm!kWe@h z_(@fD(5h#_>WdeJRYBKLILce?QfvCmSMGLmDXhtRkNNrzA3IJ@RfpNWJKlw05HYJ| z@nJZ<7qFeeEoKa7E;XeFGI+No{M<)V=6>#9QUh2|k(di9KA;H>%fHKv1hwQ{v{OgU zhi+&JmUEt8PN3{`Z`hkIRYb9&GZ$`7MBgY=t#Q6K zj~o42+_CP*FBoda4^dAv=4jg9vj^(5u7%VPPw{PR`MH##WKWiQc{Esio;1z?gSpTy zYgTU64jI6tXiS9raDn|lWa`uh55`42iw>K4-PyL*7kwiadzz&&R#uV<;J zeS*YFIipC_*m%=e;9o*cJR#dEd_AiBUW#RsOfVkpVVOiUz+L0}ojG>;xZ$E^1;z;iF zjXS5(;By|l{)IOsUl`BwB@HNrvMd3(V7vo1RIuWUHPDJa6^)oAImDzWJnThJ7l*u8 z2RK#9F;+5-{smQu_dyWiEmJ?xxIs<=EaqbKyK^IYQrTR^i%YR8Jn77tK%0AIEOPBD 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

nK@_XIA+eS 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≻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

70=lBDISmr2O=RvWtq^A{Y)ok zy*#(ikvwka({H|tq$hC%M!M$BAI*o;p^wvoK6u_bVse(#+iou zVAYAXL#pbx;>VNWuLyOaoQFNaPrkR?sC|w)J5`ZetIW05`oYAaLIuT>Cop=**R;jzge3=FQ zJoG#gKCzd+?OR>9K4T_tmxW?DR(o>LRD)?^4q0+`Jhwh>X{8jg_L_SLz9@1F_xr0d zVDMsQXk!m}u_HZ`Lu4=TGx)X2vMp<=}`RtBZp4XLph_=}fYGb@u zd~)m$n^885Y?@-r!`p0A{6YDGxIEL+CMRPTkS;v7SfiAp>;okU!$=3^%qd)2>qy)nDY_;X*1mw68Xj1cc6X zt)}b4p6t&_?e_36Y&}dZ{0Wgdx|MF2x_`7kkjgYhcEz1xm})F8woq;{nrn9<2r2g| zynZi?Ud?FV7`G5}IDNU1FItgs!SElYfmG+bIR2kvhFV7_x13!@S-_*2hhoE@f5G?7 zHtVj>78)nD%NQ?BEfPTwY|arVHp*tb+(_y#sy*axoxMJKC`@X?B4C5=O?~TaRxYpGS;xcox@dK;)()#~rRYyovuoTfRF^aiXK%(gx+^F9jQ*#|EqdET!cc4XL!{j; zqXxbk>kW*f2aLUbuZl+)u~e~B1Dgp#P%ZORTs*hv)z;FUspR6`Hby_;ta>S4r$sY^ zcaXB;C@8K_%(4d!UP0z>bdzX)bG!Lqucz4yE33ie91^=$3io&t<6xG`sN`}B57tkj zH7=xL7_qo#p+CkWfpT=(4aOZSIw6&(yWA}$8L+*Yxf zGLCO>7u3l$^0T3&hrdu`IzplI4N3w4bR}`W1@)L`i3p$ znh)9SA|+9Oh2voe{){3jw6NRoEyV5cIhb}yzd)gGk3GP0++*mTvKIEP>6aGs@6F~v z%)-D*A`4S&XBhh)cNNzRF5A1w@A)tzKmZ40b?Rh0dl^By(|2GUq>4J zDnEfgx9VX8EOc zy>|;9-`I+Ni#qJMToJ!nvjSSgN8F}udT{dI<|+IyJsEUuSZ5|Duz?L=F_Z$jM`gk& ztc&Kf`$_?@BdKCDIeUoaf}nuz zeyo7PR(yF1|0f&kz%3=o|3nV1!-5uX0^R*Rtn>eo7hgE|$)F4{%ANtd17FIf9TdhL z?Tt(BI6~m=*UIA_ZBVXR3h~ghH|Bl{Zva#VvrEDednc(z?1`piJ>TFI;k%5)K;wk4 zcL5oErkMPk;M!Yhmk6Co!VBk>-S?3}@q7a`XL|VZZVV;5gt|;dM}D4?feMid0~-B5 zja{A~u<7tQ9+?VfZ0O}}K`nREPhg7&fYKh()LwCG#KKm$|2~0uTJrQbkWzuc#vk#K z_oV>RQ8}Oeb*J~28$17?vT!fEaXmf1ez@U-7^WbA`vYvmyvfRpEpV?S{eZ~Y!BVQ` zU{G16>HK7myep%N79_40fJwa8NoU0uFz0;IP{kJp@5}fF;6_ok3MWo&S@x$=Az-Ym z+BK!c={VC8WpqHr;p9*_DY0T71V;-a!pf=u58Do?Omu{g;aWOjorC7_i_3>~^L>4N zkHZ*-pdCdjJ)HPqfbiM%pwY*5yG$)%fGcnNhK6Mz^ z6g`*S;5OME{JdyuOR0EK*QJkeo)uLoEFL12>Dt`MQh>9RVvQ{s5(OVTyf1$-o2r&# z7n@QI=Q08g^2C31ynl4#EijON-h*Y&!vaHsjbVs2o;Y5~7Lr0^1dr`y$DSg(#VxoP zVdVF}Zhmo@sx&*=sJ5v+NfLFNI~dk4n?Qj)sE zlP7M9TW3)iw~b^|toowY zH@UIA%40%+JQC<{-63ah+ghPH+aZMsddyFC9~PN;9p!wI0Mt6tF2omjhH|eSi(Oq- zC|vn%;Yc9FZaj0d)1%)PMT8^f&@BA;{$pD#dGFbl$l1BqcwDDEKi$sE*3H#x56lC% z?s|AAI5PZ&bjb{e9g37v`V7s`@!Syo{tA= zfueAa3TFNFX&%Mb zAzj~pW*ZU%qg-`xEae$2wmP%0P9Ou3&3c+Z6+i`#Nr;KVcu%m)8%B@xL`H^&#wG?e z4P01k+y_Yj&t?SE?nDeKA+Rom18T%12y_F0bSK^C?#B z9ZNbt^2lgB{m&~hz^V9iO$_4U_=L6b@8Xlf#re_8f@ozlnV?Iq8?xyak|d>sQ~2Yb z*BL3*85I>3{tKBK*(qG!ETKezW{bc@wszFZKZg%bi1;RX0(sNnEs?xds?qf0UI@p- zlLCXO;UV0g$?@Ug>1i2vDSJo4|8tO>#d9p-bQ9QjI1 zT?E}vPYLdZr6g|VTm5g8xl9RrQeAjkEurDO`}VO#Uub41p;O5p8PC{;khmD&Wa|Dm zA8f5Hm`GACKa2a$2XvvGfnGLU&p$GB$LoJgQK!@!>tmv|@o<;I=5*D)f29}tk^XllaJ%dDw0!;a7z$L23osZbhutTK z0qKYA9L0(#WxCV#)!m7k3Mlg+O~6VEBvJccf!WX%1s_K06ap7LFPB|N4}q1UDPl-c z1_;DzYhG{+JcCD5+yK-~3?r^Eba5%=iG|tgrB`lHP+XVUq;8i}1i_jTLEvk@_7zM~ zVW@-P8U4c}3KR4)?0pT_y@^&;7MF7?qtO_6bv;oJfsrC9@@sofmnwS$COy5}uV`*r zsKVf=z|I^?s#v=u(2Hy5KEYkAuf*2X&9%urOOkTW#jwE5?mX|J`b-T9aYK=rEzXli zy&W3j$7O*w8}I>#xnR$mH#}oMkWwTccWkXa6lE}7U(G4NE-VaT7333zx!J*;y;Utt zl=O@otYnxHm2Lr*On{J<$a=?`QYTI6aL{`z%4SO`S)qJ8K$bW~ng{MCo%ZC9GZ(L= z)z&gr?QBdE;2(~9SD?$7sAN>&o_aRC$R@>D*PB_xfthwT#xnDY2o)5j)?}yW|IDf^ zLpPqGO0NSBa}kRje3{Fh+ zE6RW67g>D>xLXYP198D)iq$RWx)n@8zr+dE$x^}=&4{C=luKdwn*lC|-3JOCtT|R2 z2ZV%#XhOmRC+C`5IzTAr8qg|Rvx1wM;7ZQnfx%)|$HgUn1LH-1ytl1~gPE11AU8X! zOsyiV|3fr*Wu{ZR~*p^%wG3j^f+x$MDfRj+WRV zi=eQ!L1s4o`odqSr8PCl$;t6inNP)Be!m~Bhsb``2>9gxF9dFhdU@zrbg|Z?d#NN1 zIv1jr3%YrGn1VPDq?aKr-TJIBOV!!NTU^!do?4gu_way{u_EMJE4ETDTjCM-11x1XOme%O}Rp1uVIOf_HfMJNZ({g;lu;mI=SzI1Fs=#BdjLRrefpcU z|3Ms4qFLG$mKKy9)cL8;&$}KolxHgMAajJW0hsh{d*1E7@YzoB<1fYQqope?vV+X8 zYUa!n#lOo6Hx42H3mE~iiIw#xDaEx!+llg6)!lxhh>TY`g2H0~Bp|6mIc=jfZ8q=u zt!*FSh&g%a^J(u-sj$@IUMbK5U<{!YDJJI&mb_1%o!A&HrdZCOPVt%{k7fRQ`uNKE z@eYx76j)l33ErY3&G=Q;(+`O70mo9=lKE#3tfdd&4!^cc0O7J+N1U|KrRW%El&{C{Hdi6fStd>*KBlp!P()76W6He6>ApaO2VgEu1W%qVfjG)g9x(5&(^?Cq z{hUC1X_N#Rut0F_0j&eU-7+Y{YdMXi!UBPRoq>PBh}#a=p&o$1t|8q2eSLq6>K|Mq zIild0Iih$gu*Kb!=JgME`Uw@je}3&Fuy_9zabN2GEtKJY4Yp$%|IOK3S6vaND8cRf zplvb$BMQ%-rpo}IHFto%D!Fc0OWS#qP5lpg>ekU;=!j7uj3>F+DX`-Gn6S!N?@zWRq)g1J^pS8{Ij^wR2WcVi>3~@ zo8Ia^rxa3z(RZ)Z-mEab@_pL;lS&uC({3`xV&pjL(|LKP;mF(VnFoFSLRm>nQ0?*9 zXllXr=ngc{=gk45>sZgo@utsa$^|a6lLLC&uAk5ua9N9u=(Cz zs+*&Qs%)kcKJ0LPb!ZEaI^#rw33tH}WQ(S$+6bPU3~_VR^GnX_8Dx8j00q{v*+@E^ zEd4A#Q{B^k`J`c3|7?GxK6UK8mk!`F#D-7v2G9e%`b}| z&QwP0Zyt}$_{_s7Db^F>z$PLLz}nosLxZJ5d#8}i^k*>pzXFiS__HbJDDc}zvOaLL zv`ink`|zh1amuH^DzzMEQMZ$XjyT6NE{Srp_@Q#B{w)uKL(Us@POCxdH`zl}*L1PW zA1CjP)-xPW&}}-MNr>(ZU0O^wz!9XL%~#noc1nNE*mMJop~3tHe(^ds())v zf({M63L?}D#t>6XkuSFu&Y?ib(p#<+<0rF}h z*m)JeDC)Tqyul7PYFR@q-_f+QiaofOJlbkL%ez?`nKmBXNZ2C*LZne96F-*7oN~gUP(zHPFfg_(J<`D3*o1|jPBG5t`#3F zamgvU=hTtldXP)jvvD!+>=03I`fS>i=XrxWFMR*9>~FEmRKx|BYoh=C>pLnmvRd46(#17_+ExxWXGU@zD9k5L-b589!yW0l4Up z`LOU{d&bA}WcEW#B*x85%m>e7Y^!}17Fe~;U+hB_?Lc?F$QFe}f<%0FJPzAC67@zi z$PIye&v?1v^jSiedWW2ZqC3;mRhHFGodJ4jyaYo$w&^A$t0xZ}wwf-A$*Ue+tu^M9 z?;$}tX*=ya2BTCOhqX_j<);RZsbedsdPP?Ma;oSurBBSV4>Ki>0!NU8+=(7QmI|{A ze!$6p4bxSd3dMmxfJmTK@@t5S_Tmex&gdO^)FKA_O~Z?397A4L;j?qEQ$N*}n{Yg6 zf4M0@z&e}mCC8bDZ{>c*mX~MsI94{de>oE46lXVdHr%qBgk{dYFt}trs^(|4k zs&<&jy#8z^_A4<<-5sTrJB{x_cDKeYv-7=HbJq=s7O_{t?pb^L()<+u?V&#_4E7Xg zTUfiPbl^kxT-2?1`&=&B&!YSV91i~`DkOAX9h7Ka>i8*1*zeBzzI}A1e^@}^@VDDw z!Wn6Tflueam-^9&Gr{H@04QXS2y!rrX6;R>X88dN)9!b}Qyz4mUG0pZ$FTu3L8WQGFGQ@LcQeA*&!rg$2B^3PvQ$4+DhKI& z*R5ZB!hXdmMHheCgGR8~KcLfM(~G(a5~-H8+T<;MA08Q@A#l2F1x!9VzWlWg`<)s# zDLL+Z2$XbeN!m%XDc(JDtu+7H6U+b zL?GXF?dbB8*)bK(J7%rZG(J}< z1aws^Mmk!7QbEhbi3W0W8Jy+q%L_Qo1-a;fGg` zZgL*4DNIkgv9Mtd*eg1~T7-w$T+U2+0fjv{fog>tpfUTk10g# z@|Xl0-i33~^{!GA^Xb2z)N#7MbG3ybsJB1rSzESV4u9ln(aJ}b+#Sb<#v%6wry#|- zTh378eS5=HW<*A zS+d(!n3Zp}uIsRoLFYQ(?sHc0o3r+Ow}Mlz{=Fk~4ODc(?XRNkb+p=@{KooxP7RqP zSS0p@5JbUg-H@v-{_emU+h^jX1*#8gQ8u9MO3R>v8d+lf-{RNuNDN(dfA}(PQ+HIv zL zM<~~3qP+cn66-e01^0(k`q^iQTc)F7GoR}=^UD|gw2N~yDulwac&eKNlIm*6Q{?>K zD&Nq$8s_7vaB@B(ESTCa%CR}l&~ce`9^a^T*-96?NEGqd-1i7cgunz3GLJK=AHMNn z*(NSwwSpCYX>4Quu)yEdnHak{MwTT0PCl04U`iUy98+#l@=wX| z`RHoAvil_GU4d40@Ik=H%Pg4DKY`lth*74|K)!s7G(TYFGK>sniF zx*d%P<}T{^J<$UKOf9rKfnVDX?RNYpG4??Y6O8p0OSh4@b`gZ7WqveMm&OG(ollDO zXSn~+E(<);%uHa9y2WfTlJXCKS-FeTQ5ZD`y#PYeA0IjL0+;zdtam%hR?Oro;ylLO zzs;Qf_qQ~2*TVYYM!2Gr+daJ@Dm4pR&VELUcaFdX?wW6Udh8O2_%b6Y1Q1IvqlQki zgv?RO6R>9S^`&?wLBR&8z_wEJ!unP=Xs59M4uz8haREpm=+6*;d0)Hgbi?Cb5QR|A za4x-^eUS~09-8J7{D7LjAs;+OX%7aV;)*+BtMEkyR;hjjm6>!}guctSFN@VOn!T!_OzJpO_k6`w>iaby z(%jYjM}1U$&6o}f6y6ssWx&qvT8ulI+gP9Au5yg2XPTzpH%C54$>Jl@LF z6cQWfNGOR7UomT^uFQ~PVWols>fu zBpfQkPp7s{&E#9vACjl-^Zct-?Upy=un%Sr$(UFaP`tLJRP|!@x$qRr$dn=HeyEI#ii)jmQk*xULF(#W zlKjPYpC&FTQz;%Pj#Q}wr?Nj0#>hyOUi}`j?sh*+8QfYENx9=dt(Eb!S`b+$Ai1*zhvhoX9F(|q0+1N1$p%=8ymHkFOj$){{2S*$f?kx zXj3awKFmg%@l_^wYwssLJx3Idm^bW>w$oc_3JNU&$0ntM6`AgAY2Xy-4S1EhQ3gy< zpkq-WJ2P1%+t%FN+S=M4r&#tlRV{D-s`%OFOU8z9&D?mY3IZ1I2Op)kDni#uw3)6$`^qn9M1)pgbSuMl}ajaiv?F3 zUqzQZ?fLW}uf=m*Uo!I2dz zLMK@aFR?<|>mrBm(OnKw{>pL|T5i(PCMFSatE;P&ehWMZlC1kR~7;JtRGvd%{nrxeA;=I8$8 zvZ!GF_H;MvK&C};8c!}<6I1TjbvkE16!(wG=RFSdJTgn;#@|d6=a+fC%z{i`mL3<* zDDaj$V23On=R>J+Kh<7qs_W{$d84hpm|qjG-XV2)FRA0=yVo0S2`;SQ_oz5Yh3_qD zSQ%82iwL<3Sc&@GqH`)t03rayi2YFSFr%fH3My6?p_AS#d%$?tosuO_1p3X18TuuP*-lkL*SgdR+qD7d7RZes$5`8A?WKC`N@dO&T6^sB z%MQ3BA|lAeWDxu3t?SlIitNJ8n>2AsgWI<%-yDVrI-Eb0%@~h7;ZsocwF`riY-&|3 zC*KQC>HElMuNnNtBBZ#u*umc3UT*<*A|-y~qSy0M?5~d&|D5#r7hkz1%Edv#=e9$hDo4e;xwVZpF>6#l{l_1ZR%R)T(oLDKtDv^|HIz> zX;jChxLLMl{Ec?E9EVov+c+MN8;i6lJ}Dp8Hl}zPi6Ig9C!!H5l|>@uY{FtlZ(YfF zvhJxJibJ|Xmml8)v0*R}Soon*&~svqV?F#Q6l$X9h-{>0zc{C>4% z#~^8(+DCN#N<{0;d|pYE*%!;i5xmDsF9p+Q=g|MxhMIUtGn~6|CN6pZH=zEBmm%$egh2RA3s0RRx}@Qz|0;`$^x+`R^8mr+E;ca7dLPy&Ih_mc=j4@NW2>%SH~L=sWZL*~Bq26_r{6r)(6 zGVqY)+;O+axKN$?MtecePc7zd#PuYM;aklEgwu{(EVCZ9c15$V4>i`eure{QY3IcG zMRUVZc7a(*e-uq@>OBc2?gjtjL4qW|LLgm>n0_`Ka&G|Q@ES=i-~j;QpZQFD^^z)& zF{+Y0aDu2^s=eiBO!H=^u@65QQt696++uDdwJ`LJ9FQ7SaFSMv@~S-KoJiF}AFYLa9qks5+6P^(9lOw89Fkd&s?o|&Wk znrRYP&Lf5G_=&11m>?4qO?w6p^m-iX;%s#Ml%G=l*=w+krXZ%7r4Q4BI4EklWBmue zHy%7FO%vgPpAnpJtIN$kzvC=P5}# z^MV`CX~JkBjRKkRVwGLi9yhEkfY2My>x?K*HOx+@2jU)HY$p_4>3C?=16!P1Devx2 zX}?p|n9(ohblJ-nr%g|s`=#T=NM${5`iF?Uu(Z)2C+!`wM{Ep&(-DRfX~HUpkAxnO zv_uyWL<#)JuqI}1PB-^c3@2ncnmPsbr#z=j@$#BkpA=uY7AR7DF`K3i5xJCm^;R{9b8aeDj$a4_#`)~iIzf1oIIGv z4mj`Zc(%5kBpXmv)ZhB_+#KzSa+hdUXpllJEAT|GKlumzkOzE3r4ff^YM5Xsy+O^7M>d5!n0kRFcr<2Lp#-uF-7 z8=rvPSa`32eR_EEuTXfe?gfDGn1Q!Lc+Z~xuaLT-7vG0s@HhOqpI!h(Vde*T^3iY< z@Tv!IyUMp*`uUZ$S7odR>b{?)E9wRBO+4G2`lZBYk<_E{uvI6I0VCY9#@2>?oimwj z!W}%@z^a9i)_8%~W1@9%olnAR;lnb6T5mH!G-#Sf0*%*@GjX}SdxX=s}3~2 zx{3Gkvw6mFk9lw;RGPWx`QN)&&Q_wlv%C_)cnvy$WEPh96vEAe z76r^vhercQRbAZ3uxnt8ijSaBu#XnV@;08sXBD(-aAaCn9Dh=)`BN^|^pDUco-1W7qGi4QXa(_1Vuc1?WsCR0Mcsv3oJx=Dqnzz)%^j z8C>_v5*mbyB#b0QhltfeSa5mp^;qqvKvVj}PHQ@Ik<>I-INP?XlMR(T7D}( zxaV|$-V8rqu<;cU-p3n-S{ZPx#}>ua`xtPZypj?*_V~&S zo)31G_hYD^VM_>1yMa88_t#B`r}NTyh(X}Jx!ql|W&+6yW#BbFzH~U=Um8B0S6woS z=pN6X5TT$YW&t_&KkU3sgLm<28a!WLohQ^hrZ|MH52jcT@M8X5DL6&k|lsiby0p${eRed52z-WwedG}gsli7CDJ5- zfOIJW(n)9nh9(^eMLI|qgovor&;+E22%#e&NKud`9YlKXO;NhiTkb^nKIeSrp8u_D zefPU-ZCqgTzBA7}Q=fTfe$Tw}cSiJdv9Q#Li12U<${!vcWg;Dmp?pKjlkJD@Fp!3A zm|OvC)ylCh_U)c{BKV1?9bk&BfSMJ=Li<$qiv%U&P9h)=(N@9~6z;3iuK0O{Lj3cD zex{`*Yn!ZR3C4;qB*iE`N}Yw+Mz`6Se{}wu3QemmsjaTAE+{C={7{}1Iu!bPkmlQ9 z`=OWvUaj!ZzT@?unwXdMgIa^b!+N(3;$u6IviW`U@f_k}A4l>C9#%038+#rt;+67| zIKKlPt;F;bma}^q9P0ZPy-EjvUZ04g$ClT;; z33^sTgmNSu-^~M)-S3@cG-bECZfbC@h_YY-AqMdclDtOo zZ9+~hGRkafTA`w@{!Wf6)jBEhMT9)-uAYqkF=EZ zxG{9avGA(88LGJ?#}W(#Z{b$7?WlOakfXunHCKVP^>1q{D=TZ4#T;CnUETnC7i<_s z;8}c#kpF{$&{pmTvk#A&m!;w|PfvqR1LI&SSCXMaLwbpHOEt~pCZJtiY+WjkdDG&r zRihc1xU+lqk3Y_xJNL)-j<-)mm3S;eLTNHmN72bs*)a5;Fj%T4fShVOL1P)RPW}Gv6SttWOhPkvc)0n2cl2UadNJ-i2fy}3A z_$mi0TqcS3U}F!z{_D|2UMLFLFB6sP&Yvnd|g`6)HrOf)Osprl&v3s(=Zzh)Uy{6kzh+g7j(+$k+ELI zVf)&XRaxw*xmAL}3s;vDfy*Z%3v`BXH; zD}E6x+KV-d=fnEmrgjxLtfZPqS6BP{`Gd-+0kr{|O=tLzv?>d-OBUQ$-51f;RE!YU zw|{Iv(8|Wvt=wE+*9R8v*4YUb<>fm%*{`g+d;0iDSMTeVB~FxJ7{Z_j%p7L~I4Cca zbwbC-lh5G>c|5%&nYNuT69qSExZw7-W+r7S?@w?(HxF#lA2F@5YV(rzHudk;zik@$ zjdMag+S#zmZ>fy4DVS)rmOZLiV8P<@(d`3n&5M#@mxBhgka03e5L+$|wA)dX_S!+G zME%}?#k$yyrI}soqdnH^Zkq>knEHFGW3V2Xy<P5(XEBH4m3-^+>@{A{_E{; zL(=ab_GSc=om-vJ#<8}aX5_kV^swN*mP~sM>^%R2{wZ0CX*h?!9)WK=ooSh$)?0sSmup+erL&ckkI=^b(1n;hQ_B50D(wutoA?BDFq=m`uv% z8T3iRm*d{qCzSBmM~`0k@U*koL3AnGnc(3992<3qvxy&pQz#vXe4`W|nhBGv4vBEMR7s+W8 zy}SGNY*?=gkxk2aE8VlUCfSr#`7_w9 z;SW#Aa2!WTL6SdPVF6*K!hJ51iWqr?pHk`9VMh;xBt3Ukx}GhlMP;&Mn8GTc4#W-CC4cK(FVQKQD1 ztN0r+y?qO1R2Md%P-(Ancf68RfAtLJk^dALBvQRQZ>}Qqz|Ggn+x8Hc0dSNdWyPTC zE;sSJ<;-}&*=~6C{zOyv`?2KKMRGC13gTja`0iyw*`MQ4^T#jvL?q|MC{!@=m)}z@ zNCpIaINFy-D!6zfphg9y7}#6Hs5s%+7mFXPWmM-kGvrna1Oz^HT?T6p_c$DsHv*L- z<;#9-1h5_LLs$=IXNKdS)AnT>JBZ{{&q#b!JWG3icWcCkm1R~JU6-2=)*aa-t_BE$ zNlyeX5*G9NOGp{jxy{lOE3pKdas;C-=94pQtclvwdyCbY_0p_W$wqNW5wBpz%v@Th zcjyhhSQ3}0Czi6G4%@OVlTeS?+sQOXbhLw)31!pAQJ20eGg=tshzt)`u+`QN{SD#z6 zUSw;jcVYOxu?{vbDJzaw|ELb>&P5pJ2605N#sug+q^%#76&d}yY-fK*7t1Fu7Tz#O z&e4g!xr-#wVS0!9wkhv=+eEiE3Ch{}(;ssl7L)b;s4SWyX1;x=>p3vkDvsE+HE1cN z12vF&LsRZKUS^wI1!@eqrc_A$G{buOl~7l{QOMk_{S|M?48cZ* z%F4g(T?`hq5ZjN8jgT)#KhmuLkn8~!@Ep5ZGu)exWCiwiMDJbkz#DsFn$hzx&9ryU z{K?R>RNJ1F;;1xI3VGmgH^1v;?I0?$T&ATIDbIp;({r(?_udK5_DmZ#wP-5IwHzSl zDDzrPz$j9|4RdovY<$&mUE+;2w(8gs%wN$D9plPnyc%eF_$2Fa2Z-USM zU;|h0w=Ex56ebEXu6k^0!k#lb0R_(q_YVPw0GHWRMAWYB;REPYW2L#)GZ|5ZYvLwt zy7=5aY7KRUmZrXGNkc@SuQx4ZaE&cF5!y?P>OVadfus+JgY5;lsP~D zp)#5w$aRbmUpyZ?F4Vk0SPf3=-R3M_Yg2x@fPlpifJndH?6y3i(S5)(44;FHt63^@}Z+|!@JUKLavn4^A|Mh)>;R%I(P52&0L931Kq4ET zhXI)R6nO5JWlsd|`#55K>p4@QJ$y7OC%V=5%7TMHUo7$g{K+(!w&tR2elS`yHF zz%|p+V7=H8{2-z&CHaRf=CL*sRkwBNbzxpaq&yc5HUg)ydyjzT_<#V0X!^{M%?WH2 zWf&d_1pznlNa%OF{9NHqbqy`~NO>9>?CTI~R(iI^(-m>oaaF5wz2>|R*8v0_=0rfy znC@q?8$=-Mvu;uOi~&EJZ|R>qU61;5Mi|(vhAP!a!jZI?0PBZ6Cv(&M#li&wf`HSr z76x9J4Q+Oj4rsQcc1XIkUj!pI<8I?>{Yo>366bM5;xM^+J{_!A` zdjCZ?0yTI@Io1Y*Y1F7iwyH)N@aGCUIF{W`xLDoV4!m2mOBq^be$bDRk?jvoL|c?5 zOxu>fY_)q+Ran$#Ze*PFEH;IKm07`x7EtyjeGDYsuBYF?T#afogUaGVz@=zve`r?K z+ltc4vaE-#+)>TkzQEt8uFZ<}WJ|77R<9GDp5%8XIMtI_&w|5l; z0wtXl-0Y^;aaWFn2w3;KE{r(Ov{pv8^J1JC!|v|>Orr;$<1q@RT9oI`mK+|Mlu^+p zdAQ=hS<-mODZ8RHL}7BTgc`O9&`abR?#J!15Qo;AY4;|*63 ztGkX?2~YW9G2wWG4mJNwYD4D95k}x9Yu(l}69IAKX2EQJ?8M0NuH$q3;)8pyCUB(r zlLh>+x23qvlAeON0u%XmuJMdrGS zn_O}-RyI+Vh+MWgiEWfE1+b-sRRXDd@`@-}l~PsjZr`Xq$VJD2oeBIe|fFQ zY1{|D&6*lPdd_(ssLc^P3mzP>tqwKHN}yfb)UfwNHCN)-W{h6zVw3iprGlScDfw=wqE*2Q^n^+pn^~q=Y|$td>dU zBB=YEsHCOq^In%#*1dOjo9Nz?-CfiUyubXO-s1ievBwX^d*7MeKh=4t??%sg39|a{ zHVC2*PgG=a`CXN6srF&2;iKht-MrNn&BmY(z%xnN*t(eiGiojbTXGV#kJ2Gl*fS$B zR`-^vf&N7ADr@nsGgTHK8Lj`+OZ62etO3PW~BLY z&*)dj0=ll0)PFB?W>*wdN>vU6095z)gsX}Cj4XHf9rCg-0a)KNmhM6rGwjwwkeWY+ zeN>U3AJ18S==|(3C@myf=6TSm53ne+s=%h@Hz$XzrmitUc&mZ6)T3Zc}R3+YKz@obDoQ5+{v}i z=Wwvtb44Z^>dm@9T7ZlhbCKpwg|)d}Qk!uzy?qix5XJIf==9TWdIzToZj-^h((E+8 zp+hpGD1+Cg@L=U|<+$de{~b=X(4%y!<7gLZP-NPuBRZ;`Xt&0X$%KzR!1s&g?hzo? zbNGp|-pvn4EFSP`{Zbhv5C@Zz29Rtyij#*%jRq4ZH*p|>=@YnntNWNiMOY^41bPCC ztsm-4xT+eKD8R@9W#jq&-OkbFbzwf=HREQ9$TzA)a0W^-bF85O_VwhW`Lb{af29k_ zOpVumO<-0t)HeG1wSFl`@vB2Ic?P}jf&OD~_!K@c4NDO+`J6yCwO>e1cbb z04kQFQLa3mNnTE_XI(^H`%(M315d=jTlXIn6dkCdN85Rcq+zuA`N?*c#wUaND)?;$ zr84WZrP48FdYQOy_a?cHLSFEk=4PO?xcVjZ1=I> zitX0H#=Hq_2XFUKhdEYE9)HY?>n{uMEO0R9@CWpl%+PE)Gz{$<)V#c$;FQvq#<8i^CgXeU#57`k!1eyYJ4Y#G!b0TfXu_8M-%F z2q(g!sIQ-3jZjw9yQv}RF}%Gi2co^R*RAPM-9 z2DVZfu}O4K`u&+fi9E}TwQ;9q*6@gbQJu8IeMKUgHviR|H=*2I>c@Ka-x6UQh#uz> z_G=%EYYe^0zMWq=Z+}ZtCPu>s_v#uPcYQlr&(KgFj}tYimtPl4&fyjx7vWcokk|Zx zXw?Dl6}l1kl{o{)Y*l8Hk zE6Gdeo-xQsbL}=Re=LgQ?`%F*SCCEB?o}F-+mgFw%XQ$Gof~^T8-PAFS>*hL6=Bf# zU$~#{gn}l;BjzO>Mw76f-cFEGy30s|ZMxK6Ffd7aP26z#G(71~8NKr@izNwhH}TBQ zLJE5y`YtE$NmyihR+##HV#&1ctIvS zGz%WirJ1QuGk=VHaNHlvfe10kDKk58s=kt*+C_KQQ>)p>aP;v<)@1ah!w`0~V1GtC zIg-6hUMajG{SA~+QpAr|oz7{FLoG`!R#f9Oe93bkg6aE_6+hVW%~R>+#-VK-ZLAvg z%nByrVn|s%3Vr3r9y_{1=*P$fP7&S5bU|phFYRO4+4II6B5Ik)A12hIxxdK}0d?yU z@_}y`KYjvEO*c4`bJ`8ZHm$~7W9u)TJbMyzUQ**RZ4kPYQPO=k)P=2-V~hKMA3AV) z!hs+|%wPCqLf7A|F)Ir9d@J#Y`+cl;`^LUw!@>O*!QOnH#SMy^8w)ox zt1c!-$3lI0dY%E59{r5wJoN*C1RnZNWqhharybH7#3R}HddkTHx9hd+6YEn}35aKH#BnMfgB_^B zORYJGEX0}BGZcq>JZTK?>nxgPI#@&?LOhoWV?&YL=%5$A;{viTHyEBgoi&q#2<`m> zI8-Cq4=8mEe<2)OfZ&*gWg(dT??-;*3Zp(EWPNaO1M=y;8j4oXz#f4&rw~>lD_R=5 z&-i1CRuxX{D$&lb`>k=WAa0gQ{}I;sUl1L64TnzS;Hmj+tI`oQOR7!qP=(=N$f zzp*u;V2#^{xMhS^FBRQO#44xS#s2gkXyz@l9taRjyTmFNaGPtQ(N-7s)#oDck%i0> z_Gy)*^)c~lQrzJesYfQFb&*>P8h!iwjnt=W!w(#q9Un7+4f;2D?BeTlu0FbMk6wng zjob^^cbTOnTeO(;Q%`RuTp2rn3%(GhL${dtHIgM+3&(hL(@|;{Y!|&WN==Ua?5_OWL3r5N2@iirJ5@eRicW&29Vo)R>3Wdv zafSs(4v+KkSi!-o(*qgll+@H`&(h%T%05xcI+&4m5wW-vN65o^tZ?|07QC7*+1b_6se0vV#pCN94z!QGppZ{2(S08tPkfdTlk&Il_ww@6*SCS+w^ySs zq&CXu?9Y$gaU2BfQqo0g?7kwxlADTn^K7tdh5h^8iIVsRf-`6UJ zvJh~R_hZ%$@kw}Aiav~i-!Mt^8{i_y$c!h($Bj))QZ8NkzB-g(!)cw7n4G9SY<~km z3Dnvo^suGvuR_N$FxOPq20OSqI@>$EE-WfwqpXAh!r6nR(0G>?vJAJ~aD*?Z9 zHL}b8vSq~H3-440{*^Uw92#(sU|3#RTL)MOyD+cSHEh=7(PeNd)dDJI)q)UmoIWbb zE*#DvC1i;jl)J8cc6V>r)2m#3XdoGB?(N4LhNQ*(lB_YBLv4|pJ!PK;tv%W7e_0pQ zm@zjum(dth$3hlCJ=O48U0W?OI*g_e?2$q9+ou|0%DqHoLUA`olZ6kPZAw2*ljR zC>MTK8eip38{aGW2CCH}p{x2fUlOSe>>;!I!(PTuB|-Z`TbSIHm}p?Jy)-(N=G^Ux zs}_>Dig97I1Tvgh?Ck@-lFR>)xUz6I1MwvTlwYyAvNLnJ$1Z&Fz@+P(3A!j!ddeI z-W=QlO`SQlyS4sxXiI4EK$)y2XwK`-#g!73;0&hbxAsw=cM}o*UhlmU=vI$2*|*8% zFN@=7-KRmVsTAjD&R<1RFmBl4t8nGYJsx+pm zbX?X6V&xUrm{0R{m%qDTU!G7TB@KB#dAhMs|K8u}XQhTf?QwWv@q6#Ks1@(kce^Qt z#qW;$H7cE1Cf>O0blPYyeo&AZ1q8CQrs=V}HKcF|i?zcKxy3iK=40q%3BV~`= z-el}uD!HHA8A@bSOr!Pbr*bhTO4>8i=`yE17PTumD}syye^80Or&Zg{BixMP&+%^h z?8mhMjplTS{c^<1+K5d~;KXhGQIN<@dOQ znwxJe1-sED>vred2lVRG`Sy15X;>`m^^m+-Wt=qnC}XvW^hy&l%Kb4k=+PH1*?Wjx0wRy))#$`4ZO@qCaHu4Wqu3z% zAj;8~&}4+Z(nF3#T|G1!RwyPeZTzm+SAqh_i;oNW+$n2#C#v=&?ek(YTvWxgwDpRL zVL^~X|AwE^ZU&*pde7c)F4vVYnGDrv!6RP&ipm$Avtooco@*_&QGWZgo2{##wt*RV;7&{DkRQ8> zBzNnB^H?;bMtE*jg`|4Cy@u%kfTzP5)4lLjCE^EPDwtaiB#o@yn;mJRph1f6tgRl0 zhrWATL4f32jT9}@v$~{Wi{~cFy6YF^tXbJ+mC&R5W~NEcl@cAC7F5Af(@7{pkoD5c zk^Jr!f$Vl;eG!i!4?rvjt#x2^u8*HUx!G11O1flkG~}K>(AI_9-&1fgI7I2dzS8Sw zBG`<&sccx8X049e9C*9zFe0Cz$rFgO5ZLnSyj;7icte}{jG2XrzPVz3lXSuw>L=84 zf_yRj!=;)2ywp9GZ*yXu*<$LU3#p^13@(59M|A~(Y)8FdhvZGg`=H^_^-I}YT+N7| z&aj&p`GTF24q1_guUcykR${yY?HxBY)MzIVqfMmwk9_7691bP-Zm||3jqU}Wn`(}V z$ecx6j z>|E{NfW4KafhWcfoM^p)s!x16W%=a8ac`Yoo+}peflA6VUv{JMuHL)3-<@dU__*p=skmnkcvh7r;eVkr7Sb|CY9N}vE< zW)#|?rHmS!jOO~K2Kf7#!<;OGmDl`##p|jno(Jogj`8nDM?ZpXGjLv~Zpo@?ZJZ`% znWUmD^G4|t{$X&g?>@^fxT7dT7b^?wZAreH*)I$OAlfNX_a=-*#Gow8-<+JArOtw* zNxyw>UR!A&w6pHDNdvo1H$r2JwI=HV!3iW9-=qLh^VCASDTi9sdS&z~UO}zZ={KM8 zhcz3-(=PTNv9xH8j*1e_C;=7(wU_r^%d!0SuXA9yEXghb_~6eYnQ!x30Ne#28v@d! zKtU!{R^c-rr^yBh4A?^3J`k-AcCLNFBZ7x?pEh zt%Z?Y4F1UDeL4UoK(S{X#}@3PD!IV^fInOFeP>o-!kID>Hc&kGzjAp?Vp{WR^NZC@ zC!)jtXu}}bh(IJdtXFGAz{0oRG%Y>{{9psHW(0O_3t*aaiZ4@(`Om)OQl zCrl^_n29ZhA|vo|BkSQxek??zU z6Dvd3R9d}wc)~GKs-+1i-5!jmL;6CmAy(DE9vq%!X+f1w(_m?u$*lpNu|$JE!Pgo{ z_Ik`Y1Y<-p9HuPf_2`}ETXvOD+D zbrxiL84PfXTPLOu2}=UGE+d$WE=R={mAlcSh{JD1I$hC|qkq-#b8oVutXUX3fLbgn z26*gcT}12fQE^_N5Zlt}&NdY61s;D|9Cbr$tL)dK;)56z9FURXpXxIYJ08aSpX|R} zWH_kR7eUZbW6y?CXEC&KZw3r$lV(khVM`g#?(CK8M&1xXNP;?O6z)8k7aNKu%^IzE zwh`TyS5hN0*)Cj`Nb43UQpJXd@ngbDpF7dtc}u|K{cXh`9{I(VOGOgFkCD|*2u8&} zw;D@)ZvzkRtL;_|}ZSHD&D z7|JouXn$^Sq^!4-n zxue>-R#ujYssal}P3_1(5H;-G3hBPdPkTVs@gtI3SWnZw=MwA8_sm@Xkmzf}ahgg! zW3kXJo5yL9>pY#J|NivKQ1qFshL(-Vx0Srbk#-svZX?EBchk0>1N)lys#a?vrDAF? zL8E|`_T5tP4S#EXZ=wFma`7#z7@rvn7$~t)hBZ6%A^%V`#U#oVw;Ck2cBgrxe=)H& z{l>6^9O>BQK(2`A_OBiV+Lw37NpEAT^yT>6G|Wzxut(Mr28nd^wk=cxe&skx4HIM8 z^%8~b(d<~snmgY)PQNXzt)_zm!yY~ty=TQyT%tnSoS1WR4Q9$1q_d@gWtOH4d3hZ2 zos85aSwfpt`b`dNq<2?v?EbOwOuw5Av3dtM_?Q1m$*Z6Hj{DQgyPgXlJFrLFbRDc#+WVvi|nmNRZ|Nq z4I+J-XoTeb1GtTNNKIvNhSztc^rPWuv_E6j*tg}w)Oxv}!57kN_X7NPXzY1XqO0k zT|s{DsX;kS9}sNo;QqkVSH3)tr-2@^>u%of&i783kf;}kE{ZkcbI3LbDQsP#42QzW zo+4Vga&qq4e*Hr4Ehr!WmTCnBOXU4t8TB>OwLB7+R%**Ejm-$kvqScNExIT9Ivb^S zZKVX?K6$)F`{~8%#0$0#;h1VHz&x-Eeo=JZU2JE%?wB6N&>h^&$_COpvf ztOdh24d^+<9Y`rQwleP?hBqHkC!fdJ-#0X&5&}zxeeaw6z)fR9411=Od9J$$8D<^6 zY&bYFF+^>DhGf}vv6z-W?w0`K2cq*mZco(0<8usrmI5I`bzi);+t7yG6hiL~WS$VuG z!bZ3^=VFDw_bt+142x?<5-KfR?t7h+!6zIiq9;Ls&wF^-fp zL(riNAq~ekox5F;`rZK(jeD-&kI>hFE97y1tOX4*HW^4IB#d^zzKgl?3ykhtA@&Vq4w-Or~>=Z?F z1-{=>k$#?+Oeb1j@>zGyd)#@66Mn3PCNm}`jn{((Hq~EEzgvNB zmYd__IAtkrjXS2ItSq`F9R1Lo^G~3`XHWe~a&tPr<;`B?PdBkfL^OobI5xZP-`Bs& z%ky^Zs+o)N@b^3wf^#$%^Eq!CKOtv`W;#oi|3s?!GR3X1?{*3vHN0z;BKVxNZUDqV zh+S6^BT|*jKlZ~-PrW9k>05C@{?6dg(XnaP9V;uVP_ZCn*g@}$N0XQ9OX@!Bn&>Wh zOW7%1O=!z>qO2c?t^)6;Sx_zR^FDD?mdvy%iu2RcRLH!bQ`^&q>?dh3L_NzOKX|a? z$)0&$UpI``UI9z{tu1rSj+(*XHR5>3D$)BojU?~e@pmc$6hP(Sg)QIOX(=I*l=I#B ziBig!-S34t4U5FGuNlU-%t&Rz@7y%T(412lla^F@(!!ak;;q7Yv17^mcC3RAQEPM4 z+~xOEptQ4_1l09(Oxt?*l?jvh#urZzdZuwzI2c5hjM=9R@n{>R)+Hm2xM@ViU{#azKWwsF+g4pQH|%mA_+H+ z(8F~Q98;AxQExbisMz+wDB`$v0s{%)VHbmt9RwF?d&}P`(Xu1i-`=ElwYyFiw3Uns z!5T&>M{}#1a;WP}3(RQ=2}rW1=nMFSDsrgD$FP@^_bcc`*%=M^Sy#HnmgTFNa%u`N zXgZ1C;7+0UYDW#LD^o&sh`%Whs`kWAWBH6nD|O@Y)aX9bFuX{*&>fLeiOI=L;7-H~ zQo5nC#p2w($liB33`_j&p=}GSE9-m<*kD)i6aFR{z85MOqa0@)->~VcccYA-Hq$q( zoHhdjs2~1uP!C5yFlo)DDp7#5hWx+yVJkWz6KV{$rm%6b_V9GKv2@0Naz!~%2#bnR z2vOiaNs35bllHNMdMj?zB zphF=mOCfm6($mw%-31>M|J6_%5-0UocD1?Q6NBH$sm_k_iAHi?AyISemcv6^vbd?blf_gT-o)m(=1S|ge zztW%ot5KNY75Ya>;PuCs1U^f8_uXvpn&18P*WCu>^miJ9nl{$%T84xVX6DgNXf^0~L2yFSlPw>Vj0= zEnPg^EJ1Qs_klccMcKyN-pWQtMG>#*zbcDQ;V)$o_D-OHLCHITVp6uTauCoP##h;|iukkUeP2}QX*=o(pVzLbdT(#p=-SJlq$zT(V=*u9RVbV6vce78ikkL` z;i5ny9v0*_(_4}s|T(3 zVA`nZN`^@HLk{@8G`|}2$I1-hy&qdMkF_mXqXs*eTKC5H28hU>QJL|bY*_!W7|wNj zN7m!@>FPoqgN3PXw~kxnSf)I=-*J z`1bh4O7@(0L${t^tV*nQU!k|ra+2gwduZ*!wmbQpBS%$jW04Q8Dea#SXGOh})i~oi zsOUkspwgzoiY>FWIv`GI>Q*07ynUDgrh>JG&Rzcaq+&!AzkuNU9l zM6=G5>dG4Nv8LTE*i?EU=5#5a%i(BjH{#{%!U+sCmHvIhx)_H%AkgKvHsw^Ar?k&QQ>vqx$1KJuL$RJuMv_l$nzidI`ov zN5{s;2IIQI!^^|S$}hyvEyT&q!;Ke0Kt@JJK~8ajg5m-5;yi?qfQX2Yn23agm>7f( z0QVup)Fd>Qg%wCIAT7z*@6n0`#b%Jh6iXW~>hx`LTtm4(IzvH6&%g-f-{>wGV((Z9U;9akA}Dm;H1d2ch~wKXS*P?3mBu4|K0Q4l;WK zXwSM?`al>5Q6R!W9{xaYi7ntDW8%{o93%iLSLXanN`S`MsZ-WPRbO4NaaG>=5%AiqcXY-bkUVvMipEX?-q{ zMj@B@3Hki&!+Vd&P*)pIc@ZTG-Fn*`z&K>~*Q(5Me()Y=9*6f%J{ytySkZhe|K<^z z{=3L^=?Jd-ZJYcaz1bQQom7Ss$C#Jw!jIBDx>GKUo_bLedodJ;|5WFtG>KE$)7~Fe z4tr5}4(@0om@C$;fv{JvPO0YUQ%9UT%jpYTS3OB+k*b8i#Qt~vo{a`puze~MJ+jSu zDD3#%@4%{Y=7)q``pya2X{7Pt%VG{y?#%Gae>M64X&#{T@JzAe=>-hgX_L6gdK5>oMZ>Dr!a;4GQB6>IeW+i=I z{c|PFW1jc$8XUwNu5|HC2b__W32D+&nH7EDu7HC?cbL21*%n)=r*q19x5Ysv;6tg; z#Jcx0`%5+SE6A=n@2akE zx8KSy#AKJ1A6QNbe0~}JWxLzO-#^2uh%MYDOmMT-GeIOGYC&rbPOJA2=m9aX2*W`Z ziTrU8({2X?i*UI`qWhE_1=`q;frg9gb3}!HDs%Q%J>VW$g4d171y^V++^$;c8YKIT$U7(`>OU;Zg1wN zSk=(NWY2f)_rtS(eL*Xx&tKpmGw4r!zix^Kn9DT%eedsb`5)IN`ok8<*j0{~4?p;a z45oWb%hI9SSyoPK(oaYn6SqQ@=AGpiuDE|aJ-GOOQ^$yFb7`S-DSCX=8V7lo@Y#1* zv5s%hn>wZ|{bc5{_BHjN1haB^JHt<(85HGWB<{Y?xeSBf@K;wY+UTlDZt_U4N-KMf~)T(6)mg@-e1;UI{28Uc6iSFXBMQAh>Y z`-=$%O$tta&&z3D;c(b`V`CYk;^$>6lnE9!!7srxbUu1d)AxN3^J&G}1so*r*~!3h zVXLc&Eu&jGwyYn8$WBlc;l>jtT$TG!ijH5+zbr)l4^ zTeQoVtEtMik@-|SW^bBaA7D|6=zu{b=JLMYe=x?(bWFm&Si{+i)!2Hq_wy|w#~+8NPH*gV9jv@rXB8JV?A0D??$_t(=)BO|GCX^hqEo7b z?3?LM!z`=%iWt@Y=JS&Q)1tOXu?o-JrwNCo6q>Cq#@v~8Zrh4rVT7urg_h!~?)fa# zMKu{R(F!H=gp9rt4yC7tpXGRIYThtFAE;OiFMW8&__oS9RuC27SWHAjlx^j!$QdDD zIdQCX?=WSoh_|M5>U<0melh9OiR;Y>-M4m=D22}J-_Kgv8y|an(xQ&NBFB$Nz?&3x6Gi)GCdn<}@hDn*|D zCZ^J)MYuJLnA)^E5Gj=YIY4V#0j-0c0Be&e5Rk zgDwujcByCYtez|fl~pGW@)Y~*(zyT!?YX?AqmUKzY9ZmBjvGlF2caByQ18Yji-pbZ zhN2e+PgH&u%c6JUQMTXaHdwOzqH6_LUQM+P_PfaD#D#AjEKPo=zqcd$>zicuB*|oe z=B4QTtjs8T9|9j%UBtq7dtTLB(z9Q49l&i;5DwEeIFNlS?jkdZ$iD>Q#-^Axn6(Y| zy*o@%=l=5C?f*a%n;nFPLASW$IrimX6NH0=;WYUP|f3G&9AFG2~44*Srg@`lYj-i{+qr&`!O1E!yv&S?Wish}Jx;`7Jdb2~rmLt(ffft|Ka4HFhVb zyP`nFv;U!hc>410QuslEFyh~Lb3Z?lXz0<}Vg6^L{dzI4d>VVS@LB%j&rs?aOnJ*Z z@qcC<6yAUx_N7_F9y*B6jMof&x&|H4&O;{-q3czMfc?{}$b{WfAk8!+t(&ZU zo-b&*umbd;PJB*I9^X504H^?gogTqNt~g?fuR-~N$lVRyB&;P;Rq)wYxTPx=Os(LV z0NQxLL}2VSh$MN76P+f#N(xwg`cEw!|LO>w>j5dM1J3oo(~PP11^zg|{p5Zsl2chI zvOwhIy&PUxiwuDe*Hm(g3HosKo>4r6l;!Z(YCn;y)I(^F4DmSPZrOip|JGX4o~9rM z!n?ScQTJ90)uMMFZxRx`Tgyog{dXBzo!U^Tq&f{uY09V(A;@Z~ zF1@t!&_@UQSgNYWZ*}~{NPWwm(u^|)1Uz|cNUYi~?^>Q)*gb8T@iDh$O|z50iKJsu zjXq)wHX=IC$n=qg*sg1}(+Qce!7iR--jlEzm;d@3q8hx)OI(vTr*9PP%`@Cr4Lef%^q@-@swPD!ow|_w5}Laq8wAQ{A8cY+!+YTc z#AAxRvXUH(9dkE+-Bo*GlCIWz$Rx$LzmH)xH1^u)KL+tX&8|O9g^@*}U7zHnJj&nm z85pg1{+RRs$J6=G2QULO`|qRrW;*}rg2VaI+zRmtoho3yCBS;z#`v4XNE9XH#xWYT zebW`PJbq(U68<(3vj`lN6d8n?wSg9ud3GI3HBUcl{V_$A52dTx{uh(+&o}bFWr+UU z2TWwTKQwUHv47alpmz-t38|Z|2mcbk3O#FpX)uNJaD%%vHQYl ziiJEm_H9XnSpgtn($Z1uIv+kZ8A!vjDum8EsELRrR;lQq32*d zhW&DCmI6Ihl$Rx=Awu)Hxm^p_G^MY)7rJ5vO>R)4&S1aH&M^e5yU`L_q0Z8g5E>^~ zKC0D6(n`y~@SKbAu2W`>H(x9%p)xCX)BWn*`<;8`CrK};w}#!TQ5!Il98$|%%=pqe zg+DOUd|d4hk|OsvNuhC>oe{GGhDpq^C`ftA3&^;x|7_mJx^e~Qq8vH~P^Gn4Pp&~7 zX&0ezZTD*sf{vwer)P9Hw2evcfN&y zD!)_JMXD83PLX#ENJtb!`KLIlL(#M+MEMydMW!j!5;vPMhfN@n65ZcK$;22diO0%h z4yc-X5)J2*7+6JkA3n)zLTK``!-dB~f-U{}%xp`)Dr$l3qztTCl}9fn44(XYAAyjL zdHwK1(4htcrJCII%PaL`mcHt{S$9|$!g`)$@1EOlxXN}OZl~EEqDo6qoEhmS$dwNbXT?&pA+I&1zPVK`6uY=tu8 z=Q1Kb1awdK3lumBmkU3V9i|OCYfh;Vz)+o-Fg|#E4eDXJ>f`PkYSC1X8%^jNEiYpU zk`k9|Pjfy=#+1vl>UjTX!-CCNLnStB;qLhBJQQ{uQF7eRtbkzZ2e}>dhFMBUjV~J` zuR(L{%@4RatT2#=5-q)oM7KK1t;d^HQX}FArz8ae3QqmTv#am+01eib3cfRg?DJFz zS3UA&LF;Uq;`fndW|BL_=hvWbiDlJlJ=|SCNsnCQrX)$rjUx*xjxT}Np2MjoEL~hiE z$zAK+YpPX8Voz5k;K_7V{{Ccpq8d8$O15_W80v0>`jsxLzYNbpsfWv2^s86q}<4A4*IKo zrtG24`{ay}^Ff*;&0BMoEA}hiaO(%ic85kxqD{n4%o794>kDFtd`?KOI5|I` zhuWQCU4vdG!I4ldRA5L|F&Nq<*>DZ=gdp+Cr>}s-YMAUHsu9pKiFRnKg*L8aE zr~u6o1{C=bD{QY*9|g`bMqGn7)2~5p7piIhw5gUOa^@H2hkD5~lk9^~*=!pSKF&X0 zHi2}!uf^~Vpee!uR{yy~G4v);JoAr4vDa{e+8*>OxP}@D1?cCDrwzFzbW3l%V|Exq&hM+09<^slKPI@df|L^R(+fH$INNvvxiYp}Qsw2;0937@3k zq~j~bHjn-uXUX39ilxcmXcwcV_$x=!)_rvc?j7@qU<$Flt3@5j*}6NZ!J-85B=MLM z-s!cm#P+3OT8SnHj8Ligd^5^a~p;ftqK5v-75H9rDaH+$< z*&K^PllXjyHHE8CkXk>Jyxk z>SUXJ9!bxiWq?MVM`9XG*zMWJC|nlR-AuKEwJQD_kUl_-wF}=~mbRSnuH~j%g>hnP zFOwdR5cB3JC>2(21}&?y$+T@LKWM91ju*#nEH8Ck#f%0#Go(Sd13g{EX0_R zyNQS44b73vV{|gjbSG#O<@BuoW&cW2YR=!yZF2k#nqD`LItWCSV;Wlo{vEVFw>MRU z^=S|VfF4pba3jWZ{((;xzD?a_`4eIEi(gfU2x0-zQzbceTYQ{ zb_|`h-RBBCbJ_=T5f3`%InWCvTu~hSJ!_I7u{rIZzvc6+g9^XLQfn>5H1kNcJf*xt zkn<~j`x{9RH-*ZjctwRByo0xGGvy6!U@&4(dw1j1tt1Hn--6a(Jj++!~_p{Y5S!3 zyVg}%xC!aZqOARh3vfJuf~23!JJzB$W6h&kl!UMz#kU1=*gS;O=FZCsOoJvda0A(` z$47Y4Pc!p*CC&YMVYWZWnda{*na@jfS219fGrvK9fA_h?LzS-f@$c1P=mlJJgjokE zm)V73mF6$n#q1=Uaytz?g6W(!%k>BFQ)j~m^PLfrs@e}pWQPTu(+m`-W$_f9UGhE0 z)+-(rOnw&IV#Ny+5$k4E_EKf->!eisj(1ya7pL3#LWQ4F1mH@gPawhSJyG~&>k zL-v=sE~5bNlW0RnHRBpY$_B00E^M@Ei28xLTaUpEd2~z%kXZ-tTs$Cm=;29|3VggM zvEsXEPTnQ$g#hg;!39iR5*>F9b`ubyB*xUY>gn0M- zIZP8CU|~-`xnY`*@G$R2pM4{|?u4#ap#>cPRHM6T8X7^-Z*asqU76c#;!k})6xvr8 zy)bicB_!m*pYSN1LAe2XDG&7ieGQDo%WzC#g@I+w;m8b%Z}D$?2Cq1b8KaVB*pn^j zx+a2-i@w_zZr_VyC!(?iJt>mBP$@2-xd#31I#QWgw0z#B)aj|0I*nJSp{NVIiH34f zh6!vCZ+P>MAB0AGtQN6*L%4|P`d;W}&pou!0F#0u!PK=D(cW+f2F-c~Ld%sZG2S>p z$d7SsW^}w_C9q*jt(Iy0cXIO{kkEulb?=K`eX@ce;()NhcYxlot7AAd;>@$0c|%_M z3Zi*}9mwJ?0{q;5n~)@%!)s7sQA;aif88_Vd;$!)=pG>`PBveeu;};x~H! z?SX$MrDV4F6X%{g16i774=Fl}y7T4uLg)vYtW zmh2VLckk*;KwDKk&MPk!E@^JvK2S>&94m@G;QCVPOyuNA^y8|aD9JonDiZigzyM9& z!Dy|nn97&@xwoCIL9c|tn&HdR@q&h}J~KYDLzXXn_s(1I+5e=@w$q%bj2?WgWBmFW z)cgrfRnerjl3x3gUMxR_okJ%?V*)!@;ABAFa#`5Vc`-~OxOTJW64w5P1c7kTry*+qUo70c(*c6i7-+$>?8P!Z)v1&I+ z`fD7ymStgRDaFfo4MH!tEm;)6VYoH2^`1^4Xg0p~J-M|PlZgt!49m}>wKDfN2}b4c z(3A@m57PVfV}`SAzK7bV#{MGbG;~Q*Oy>n8eVB!qRV`8`J}}@`za@1obp2_Z5MLZ z^|w;W8X-DC0#pQRNv*jA_Xf!4bEVLdUgk6Re!hDrxt?CDCG%09T}t%~Lb+UGnndga z6xStJuDZ{<4^q%{BzMe<_fety@*6T4dVT@`dZS6C<}9gbc?a^6V`SrWa9)U@k62dl zp!Qur25j>|t-+5JJD(Wib+l{?^T4^f{EGTFS+K9{(p-nsE~&;48bw5?_HjOuP5zf( z&`f~gBP|MSX_kaNw}^I}U7(UPZ~m5#_>!>0I4Us?oh)T8^s!)P!n{9@{q7zebioC)ti zyV}>%C0m;k%sE$OX+NtpdgUS*<;#+z8j*6GSMgLL|V zL+Ld?OR%C>+eLOsJgl{;D*0)|`aWApV{>+jc6rED^xSE$UD-V{EW3C*HzEPp!OPIH zmD>6lR(=RipuBKUW+Oe;iupi*g6hG`3_`h0^CF)+(z)WF96Jt6P%2h!;Qd3a%ark| zC{)po<-+b0-mt|w?3vI@;OhdKMzIM-jviHfYEJlLnm;QwZ@(7m5fc*hE4S19*i_np zLH5EW_<&XXoFnU_@%_2FW=2W&!BWPpSSAbF4kR-W+AA}DwAQjIk0mzz`V|#5$#4RD zdgy>T>u^_%!y~tkW9E?t1-230ttLZ+Y@uRs4(Eh^Q^!`iB&=5#Ou0OHg&XE`x-m}@ z>K>zxIDl%7j2k>mldOxNQJ%FMT`|dbmch@gV@wQ=?O0Y}q;DCy>OCjh=n_a^p<=LH zJ(sj&ITsq>*--Nw%7YHjHtr5wgEZq{LgO`w31WA+;{C2JC&cxk#&!=36u@U;ArCmB zXxxTF*<_LEDb#cywbybkKeT-K{?-}_kAZZ<)}u@BSFtvCW6oM>zb}SqPH?c_GAK!a zqrK{OzB9sM(bBF_IIR`)x1lp0A5h?FGL1K?BdDdp7GI^U`?m3DG(@ocM{(*?$)J?^ z$0)HMJ40e6G+QR9@?i^kWQI&McSrC2)sEaO*&Pm?!-uvxPscl$3{Gw0FcK9xZK?|0 z8lsGMID~8m`ytyQkgVtTQ!AVvmnMqR5HIh9>CkUJ?ZS3Uepxl}7}J86dCfP%VvRRg zcR0A8*Ywp|SkiXRtFvoRw2?`%xpnMOk3lznY3c>sEIUbTb65WMeVS51Y$0Ow(f!xC zBg-?1=NaeEt7r~n+A44)t8%h}>RBGCFMM;QKH|_57n%+N?FXd{jyL+IhG=PpGJ|8c z`A^ymvJn_UR(H#oyamJoCOoSgUsleDJ$*LDE|2BTP^m|ofwM1j@rPL)C;KaUPriCV z(>MO?=2dV;Z^80j3pGhQ=eu$>1nqbi5u@X0Ku2*IC)O;n_@t%6p{&KT77ZQ)n>Et2 zEqcjxMM2%hEakn~ds`2GHraJu(VY|63WV5bDOA3!G{}-lOsHW#t`gBYk3An<&M}9- zLIYi>>Ezp4*c@?m&3d$GscD~?Y4<$XN_1nJ5xfI<19^bdMh^xEjhBn)I~EQUtr{1A z$`DaT$HrSgQeCLRGgBmCtnA zm}UOw4Oe@}R~G{45_wYfx?s6(q4p72vsjnY@XFEc`y@ayF46!i(0^)~_-nvOzxppx zC7?H^kKZqito%{kMEiKN@2DucU}3t$&5W@wgJu~&ld^}R)98C=X>*7l-Ondx$6aO< zho+be97650#G=fE1V4#7qPaA#{OwQR zKQ&}7XL>aCcH-Hq;q%}aZhn(eceX`@jI_+NXBbomt@^TP_*1eQl!-8+`#L$_EQo$C z9NgABojD)h%seN}oYJ1F^nd5|^6j<|o=Ik|;DpsRNC5E78oq!nDz8DwfR1?K_4gk% z$pJpw(Jd&g-+ocsSe^J|k+4?Dq{xy_1>5=13;C#&{bwXiO}HW)y{uG$>% z{TJ(Lw=%+)wkzl54tb7FU^NcUEqhYGbzOr-yHFb_m1UVx^b-wWOY-;DoiqVN!NXBS z>;{AfJ@}8<5$1EMTXOujpmH~7pZwv+Q_uJKH}PY_;|5*lPIg3SjbK?=D=F*G!wp?s z$l2DmZ_CfGiQdZb$^URs*fsBl8>tzqP$zZZ8(hsGbCHoCFBkg36!U5MXdF8GO});( zE-CHc=g-C~mZiSG^mdJsx^Tce7-Nu*Ibass~s48XuW~Kb!AfBHP_rvP3aMS4Zh(ZtRH|Q+zEXv5j$#n(Ku*8?VYwSRJCiaUwyk|F48MszicCYON@ z5h*fbQ~51qFJ6ajnJp$IA|(pYEoZye>`OqI#Q6O7MONzNm+WbS?m^BZUPYyErU^)e zmcAd4sg@~rB<=}Vlrxz}9z>Az>if z$0m*p#^W+WDkH&eTc}i9W9{YJ?pFL@XCY`@;l5N5*^zc{2gY_5rQ=pG1e30|AC|J% z4td^NFVAF&XMd>fS)R8Tb~p=u3)SqaTSGxFHlazzkY6n;j_vgEvTfHOYkJ@{87n=qYanoTvrhn?@@&ZyHB9Sl)y!i=O zpX1Kj@SeC8RcH4OC>PRSWZ2hOzXc7Lx1_00d6aJSBWLDUziN`55XEU>T$~|_ zZZ|H?8SnA&g9&%*PsG^YcbNB2rJ?tU&NTn&%C2$XBw;uhY=+MKum z0q#`R(~;!pk(5gli?m@Y!zwRD@n9;UTbPprRoRGVh{u8|Wt}4_JBiz1&2j13H3yw~ zDVh0c!Wx~xF6RZ)N^37n{x-7rFZ0dA7`Q4~(!boX1YMvn?2Yug1kxEcSfXtH}q!@;mmec<)VI zHjZbrMP?Q?cVP~{3)yW^{nq<1L;{Z)jo6?7+Dw@R9iRpxL*L>vHiXLkpM%}9@OfgrEgnLZz-LdSrMdBraXVA%Q>Z#v=5QYI(=V33vncD8PwK^HNF zdSMh2(%(JbEfD=Z(*o6rP^Ay6+rMA@{#k-uHiHeEC~!jUPTUJ|0rfiy+>++)et)Dv zf9tjZo}+rO7&6PAwompx@+6lM1%|1U-wp7VTfCU~9;D&F1U@^0C|n^zQ;^1vq%VNq zUgQ|uUv+*B8dr5*I5pnX0SD+du29H!E-<;)-j-y~4q|hxut%<7KmW zvzAZhw#Tmq@aS`CKHmw_D#nzoOORC3=b52$zRJfs6E9NnOJaB-rx-_lj6aOb;F+#k zu7A@%OdJu=rLW)T7@Q6t?WhBZID0M*^*QYtgAbKczIh@6I#e)dlD@c2G zR;l=L=2X_BM9b~Dqm8tf&QuhWZ^>-}D&!guCO2eJWqI#-hWn866P7|aG|JwBkLK1V z^2y~U%`BdYykb}zntZ@;~a|0>N8V+l0%xDF^7u2{@2(Gq@CrmXy{@`bL4{l%-x=gsC?hQry|l4b5eD+q(BlU;SA0fHJ-8To zyW@f9RFFi;(L?c8IU}ETJsM{tk59XX#r@q2fggzDyre3}K?^+@gZT^~~stR9Tj@ z6 z_n4?B*8UyuA zYqJJAKX)GeB#nhB@rB%ZtjNX&l9k+z@6sm=W}+VTZeQ4nMoD$*2vc*ny}fViW{-C( zIV*HfiPE~jt7(fqixR)vTTx!ScVKMxk)(e=k=JpNBA~^DY9~Gz$LCL$BPmF(;=iT# z)jigXYqaG-Qgw=|8}uji9G?k_IBq=Bc4%|HL`lh$aHzkMuntgAP6l{b35vA~k;2uS1RuCdTe$xflPs2Avbx%Uz&+0HHmj zhdi%JtN%SbdDrLZ4~obFme`2*3%|Ph5*E7D(Pv4m1~F7`6$$0j0?_shH|q~*n_M@m zOePyETx6ZyD0q&^s#_UECprabO-Ntgi;myN-kD*?q zjSVT=Yqd9y=vOxo$B9zXmD&E&t2h;$czwa5T=5z!Nbp zSPjs=cFpG!b0}pf13WJeqjSqQj-u#{5Tq=EMIeBUA?p@d z&d0R<%&#}LoqR4dM!*JzpO!uIh>vD_TxOB&(2%f;`eKw@Fy|n%pNFwyyv#nMuNM$Y zjTNVEt32K8+irQ+z;lPYsqg(pjrmk(zY*(Gf}h$3F!tZ*%(6-zJ|n7_SsfWZMo=fi zYY}D6C12vZ5BlJ%D@-B^X`QMHMouF*W4iD}brJ_@Rz%`GVN zII{JX;%(M!3dehUNfxe(@icK-#!bHZ*DJT7CJT^&YBWL%?81<@Do6HojC zBx0Q~z6s~6pRSd$CltT@SZE!(FgWDj^188}a+idIlDmD6(nqv9?7Ptyo6(?%tNf+) zZpL|LtXQNG`2vTBq#_}PhU8eyRd&^)-xq9s!RIQij{8iH`+3lO0&v8GtnK1S2&!^t3`^!V0n@{YOW$k)=cYNb82(E7Of&%oTZJ0P9TkOc--Vlp zwkMqU3*39pz+kj%5Rqrl5R)aI<2`jqW%(#9MBtu`%SVzGD%uG;7lKW!+?+t)j^5Of zy5n|7RC%c%9pL!yw7VmkC4JJlz5Mro2zQG`P0?=)aU~AVUpiLR{fcWG2``}OV-EF z*rGys7M0w4#8(`AL(4s<5;s*7|Iuc&Pmimrj2DkDUUj4n2QBDV=y)Y#xkMrs^SRaT$YGHJ zh4CpqYj2~}W>#>9hTBeWa}kN0ZjKHz1MTVxt3M+~$xEoWwsCH)8p;B#gYcTgS*Y=1 z&+GZejA_mNNb}HT?7*%jZ76?)z^K~xx0#{(9)$8a%sd=8g=%&!Tlbh~Hno%t6_tPq zgFA{}7Ix$db(nrD9(|`qMGX&YD#VR+3V zS+sW(8yd6Nd)Wr#i>-V^4xWwSo+X$d5ENWpPi9azvh;N{^^ z$0MmtglE(J0G?wj=++EX$>W|Gs@V7ZzZ9dHFfc2Lt!*}oJ`};M37*omj1c54kKf0N zdj{Exhr6$xB^S1jdK9{t#{wQ4(}Pd~2Xomm6$Am+Mk7$mPaeEba4bo4oc z_c`5u1*yay^Eq+9QVY3KW1v39TVy@$%psP2?#B1<*Yb!+A=e(UiNnupka>ajW*C`1 zrcS+^8Xx=>TfD_vKBF<-N#}yYhwnz5oQw{8b*5H6MD&|SRIK%3t_}#$maiDHB+ngm zEQ)_1?{=#MVlBK~$iI!XoKlXj{x#MbpL)L!PK&bg6~Wp=(lY^-8!FE(7lzCKX{(+W z+{zyW*~jjmy~&8GzPhQCDkfq<@d-?R05nwAkvBrB|I}h}20@9<;i4K-fh2FBo-$_C z!C$MXH`H(_@ch!UQjhY8KzTBR3N67Ir!8eMq-1B^m%*}pFB23oTcTW--pHP z^tXi&RRcyEKq1#YK#`>;&dzhbBPC}*bypggP)JWXe z@&Fdu!a23oQ_K$LX|bwqmGXDO1Q4>^4@Vff{b0HoCH3WgYn+;Cnv>y~qesL8C#v;8 zFGL`J;MoSA(mX}p1EetcBMRc>o_e;J$~LaVb~DZmMh#Us-7k3T+FjK|mL5J?cbaq< z2%kgdH)Ji=GQQp}qGojo{$xFguZ4QD&4o&UE~r?_M<`nDJ*|Dl4;0FfJ(4?x!ak^Y z2oCcRNo+3;MEl$kFmE&Da^VaR0pR8jS#6_`#T2mhN08MusiupYhdYPOHD{G&WRbED z=ejH}TChjb|6N&I{2GkI9KGPQW}^nS_b^9Kz7g08>+0%XIXa0Lv|@#YES0nW7rKy9 z5Qmi0$$Yxw=`iuRLnKNm|GjE>{14uhQO54avUsjazG z;XI!C<9$gif8b4?D6$4QHG#S)lDom2n0`G?&k0P_#1hs+*vNaW-v%$VKi=U16n=v! zZ}TAmzro3mpLbsPSnaBJ6EGrVyVi|KG^1pIE%7>^puVxxQWQ%4i?5SOwwYQ5^RGd1 zNpJoEII1yf!L~17cM#+LEOPIyXM4*Q@^d)Kot-PH>D_W|El2hkb;QmSpP2|1intYu z&aKwtu6!|*0duR^YtTGLL>~<>9p9MJYy}hSWCx*k)W9L;ThGe^Tk{EALonX?g9Yql zahW&oJz`CR_^T_mv}0llQagzT(2pp>|W0&?arR@pd1Na!ZE`HZe)fkMsz&FV=W`7bn0Bv*-vtmOBrC zzeRn5&qwqF0AR8otCH1ok?5Ml;9B=)F>2KHxZq*l&t;_JEYvMvr4^M5pH8IGcIa4C zE+H`6roYon`NMQGYZNy6xTh95Z;m#WrpQSSX<*TY-Fei zE6EuM<+ABIN8kNAM;uXAzhF(S#!EM*hH}y2$Wme1Ge$FEzW==ADR5$=7r2t9J0^!4?B`+zVESK5=2I5o_AwEvyhR$!1t8Bao0m}*D9V)@(4>6FN{?IX0Q4icfX>yW1n?>W-mT%w(_2SM{J=xN)1eEmLwT8g zAC&%6FyMXCv#TSy%XI~~_#X$UqYb@1Gz4TP{x|Yl{>eWM3ItvB>~mdx0`fo{8GpY5 ze$0}YAuq`!W@e+8+fq3I9@1v+a2YP*LT$04gc~?Eb2va*ez3 z2nPQD#=gpZL!&C9c&ka9HR@n|wQ=*#8&Z-K?yP6K4E=os{nM!XJrMuxsQbq;e;aj; z|BFEf{@bAY`#8e;w}Va&l`VHFcTh|ek;}1#+XoJ#-o@QXSt5;E?whW)PLhaT`FJWf zT*~oBy^8^7g$F&k4#AI@Ig5sr)*$1`x${Jo--XXP00Y0kanD8l^zy{9M|FN#^l-5O;l z4%)#DMLyH6L$mk%Y)e0zaYX>p>q}(yywv@YY7-_m0DDcMb$8FKW0jDBu26x>X4;qH zLRq*8MP_CTsVmcLa>Kc;R{m=1gyX6BX>^43R&(IJuByzX^f}aEhSTNiz*|gs)wk3B zvBkM%k2ex-*T!ajjAsOOF?}@ojpfc=BYCZv!`uVlglsfmXHPu!SM=ci3|m{Y94c?5 z;&T6{B~yy~KeuRdKT8YyRoiQG2rwZTsx{0Dt)clWg~d-p zIVzVJl3fZ6ckNH}IsJkgzi5gRwAh3kBkKE2`Py7@??`_WkYPhB5@mB9n8z#BU@WYSmUzGURoPH6Ji(Ktvt>af7MMz2P z1e5}!th)VwI>P%G7i1z)E#v9|0Avljs5&4v6%&`Sr2U|M1`vkiNij%Q0M9c>OGD+_ z%RT?KOsF_52epeO(%F6`0sALch>;g zg+E@nl&!XKq94gcT4bJWNkoW{d-DU5!E{8Ff2>##R$svBI@5`Bqh9Z!4ep6uP#+e{ zgMI?AutM2-eIuJnyqNwZmO1>GM0j3I!e5yfcXs@HPA{c`vSW6`VTMK9V<9@ha-vP^ z_8DE}`&E@GYdjRmYjJQa5smI5(pUXrn?dt16q*GRW(9aI;i20W(DSXRHkX#cAz&Y+ z?Y)Iwi2f_!_n8JfYb^lAj501Y7f^1LHoDeF>^HmJ>_XZMM6KlIsPRg`XB7T#FL_H&+p_Y=JVnJ#@MXnfTke!P6&a9kD5zzt6ztdgp9# zpTM}7nT~b$oW%X2j;;}L$AhQPZCe5UI?OxYHQc4pjzu7!)6p_JUH#(88UExrSgUI` zT!WRlF<3+te`Op-H@p@$9OZq}4^FKsuz*B58uW} zs&HX_%tL&nJIA+NZeU=R+;At2ROw z6wVbaz8<*Jiz^IU+Hdr7+3;JkiK7}zHMRi|#zvgCT78w9f^WUyE z#&Z1MDwY0M&+hoQ#yz>R<%QpwXVgDB+-UyO*6)5dVt?&-6AZ+!|F7tG<8lTAY%Wz= zsCpxiNcVC6GufkixUYqJBO`)%26WrrXofx&EI4F~Ta8w{AHbSJ=H;UnEo#;9Krp@J zr~=E7bav&j$^u8gve%}?bRO6x*@#~3VYA00Cc0v){&cr8rS_rYLIFi&Mnw0Q2L02} zEd4$n>Q`-{^;{2{`Nv4Y>hLzr%f#PPW#Jj2h21wxnFfjq4yWh?ZYE7Xsc*^2nbjJ* zGUXWR!8WuUxMz5&!XNXrs+imLIX^JJZgv5GkAZgn-|m6+|JW2Z=WqkaB}VyC+s(cF zaLT*yQxV!Wv4)@QB;hGif@u~MdShBvFE1w(ZA_?1Y;z6(>P8!V|<78j&D?z=*B3D?3dA{{QZyko5{py zP%FH!@)N5f=a-ad@Aw@zXW_+nNuy>F4LET}tIxBONvC{W2q!(9SHP~0ci?pNW&2oO zmJBU2SwFOY-hcZM_1$LswDuBd@y(P(y#S*S^8vp>5nc|E*j0O@tP~J7s$@>AtrOcE zK$H33?%1dsx<`%@yKKDJhaw3{T-yE;5ZjM0{ZB`~q&@q~yQV3UH(sl42{cbH6{Rxf z?tD2-Jq$(WXcy{jsv(E;ghi0k5J>Mq$=q$dW5y#$cl9#?L{SV<8d_r!W*Idm{mY=q zf%c}JHK*|3TF-w+&#LbFU9mu6_~9H+rg44vju8Drv&SdPxy5hyioIYTvxBr|%D30a z&O@rFomVh@!V-^9(|@`Q_+63bnr~8)X=&N`Qz}K@eNV?1lDNlHtgx5j&G;)$EY4(P z7C<8xK@$?UBNCf-`Vo7%J6;t!3dpNWRCcYFv^wKUY^^t8saxr>A>wcL-Y{>+C_ysKKZZ1GR#~;_LUEF-Yx%+5EtzhHA?(`-H6U!*?7d$b3zfOLMWAN;oa~w3*%(RiZG8ptxQ4G4)6c?MF218 z9}NEazfnv58~-~m8j5{EeB40z7}9c7SPw-i>+M3NM*qBVv<`i80Mz%jiu2hGdYIR% z|E_PU`_OT=b*ldz7(31KH=T_CL(8qdD5Km4+N{p8Ij{=#fNv@^)%~-R{dYJx21>%k z80{A!!B{HialQLUTok|f=-A@m7zyx~-zbn`uM^xTcFftwLa%H`$|~;n%u4l*mK4Hy z3{x~4ZBRhlig`%?kC|OC%+};mlynY1HBv#;*|+Kch>pRxlNqt%;HfJha-q_nFkSS_ z+~XxJyCqw+p{cW0VEv->OD4~)1TdXj$@bucTbeuM3wi_WMY6~+f&Zh0yiGS!ma_?% zvsDupuE_JRitWAjw}eiAp*!B`;TQNtWGW??kQx z;QRXp~efcL{F8d4|mh6H~7*QRU~`^G^GyvUjKJ|r@zn+|I$rh1J?E*klYKzEO*Qg z2?A=oZQfObYmj2&PQYdZzsEN21_U)yq{?(mk6JHI1tP{;ark+%DqpHJDywmhqq9;9 zkp=9&u=9Vh_nu)*t!uY1h=QVkD5!um0ciqCkxoE5NH3um=^(v_5-jxI5fKmw5UNP; zBE9$Cd+$Av_)T1Ut*xt_{hsq(=e+N=_YXBQAk1XWJkLGGJ;u1>7c^mxiYNCzX-g>s zL@9jzU)C@Zrly$=BdRK2CYxx@rtv*ESc8&%u;@AGow;tR!&x)wW*;VE4syaHttjPQ zufu}zqSu)wk_|XUzL&7Q%Ecz(l)OG&D?6GmzO(H0aweRw<-I}$KL$$+&w*}p4a6y5 z5x5s$rAKgej@&6(LsN^=n{l|eseSYgroI|}xyW1j*eVUhes%5ugwgtNl!J4xALYuT z+c0dauWzA$HKdO)*u$50H(U7+AdbPwpive=Tyt@r#S7#Udx)#^;tyi^? z1nB#6etdKxUD;QCjMhnl@KD9BTe%;*O(+$8L6cOckl~TBra+Ud2Gu3X+M>XE?0wza zC#-IUcD>iLb+^&mkQNm_JPycF^B^OsuAN>KR!83#<;2GxIM0NzFsnJWcVcGxG%+X- zR_f0!d~5q*B`>J8UNzbFpEJ7Zo4vTpfn_qHkk!AK7}T1Q=F+x5pzOG$oWWonBh@j< z>kZ@={+f>buaIjPlQn-wz5ZfgRRTmH`AdH^uvR}WJP8HHd;d=kLYneqgtqu+dH|n{ z-as9knV%H0KkWk<*Un8L`5j#*p({zd3xENCaQ0Y6p%*mY;vu)6UB4B7Y!1D-lkIMI z$jumfn2*{;IVJ+Uyv4J5Bp|)1f_^Y5odRhrbRPL{DUJZbq^gUO_FrK@6T61oS{t5* zRrMv!3Pm0w+)V35fFXpV)ekHiqvlMp24(?SicqCbBa%%abr5} z%lZ1CJJZal;j0Jzo45LU9PP@mf=mVyvDi1TVoso6XzgTj%Jjs~-J0Y|r}R$%q03<{ zjtlFYi-EM8Lt&xL+q~%(Ff`>vwO$5$Bhh{SWWoWkL*2iD{o;v(HO`2c9;^6L=al#Y zim+Ie!0SgJG9#@<&&y&%0e5|)%?CT877Bu7{Bxn0-IgT&DQ7bIW(0PiuT}NhJK@}J z3gwX9W8ahEq05+;A73U*9%jmS5g0miC~+xG*nf4bxs$sxXCgZFsLNsCaLxUVM@GBV zMxHN5@W`rqH<3<4!146$6P55W-ZOiWgIjbnq4|(~8RxJ28n=Y=AA|aI zHiAwwTm_l7zaIw5NT~FF8|>wfo!y97!&%M`s-%GOT66`(p3YbTY3Tiq-bf#&&}joV-d*8WQEDDZVlkZb#vS{P8~t#{;Y6+0R- zhQP@v_Q`+yy@RIU6S z`kXRpN8DNTdwD}g>}@*?Lj93JrQ+t!0Py{n*_;2cH2_=lOL^|X`R2#52HPOQ+69pT zfdYU%&r6`){kT@k#+?XCfi{OBl9s`~lN{ne=Qfn`5Y&~AS79*;KNtR*yW2jnkOvy7 zJ!?K4a`Tu!%)S2O@*c@KMq4he53GH=IDcOz()XrHK1eg~+IW_g*-CwYCXZn*?P63G z{i4)=F}mUb{R>YoutD+(?$Rbzl2X{XNWFk!=#;@UAB(l_*J|mnH8;PW^6>kGupOsS zq@ zEP5T}Fvrw4BMy)$`dxUF8>g2bX{u+M%8Y~CeauNUx?RpISz1mZ_jXQ>kYo|#beM{_CZ+M#jbV#AvacL zzT__Ul4-S#1?C*-#N*}D;ZpA`WW3_Qe1ewY1UuiB#L*s)vP!Q7v5Vh319E!4fV$|X zr3HD(XH{Fd2J_Kf z#b^QrT?WWs<4MAS6V9*m+PAvx85=D~UY8#Zu_Ja2Zt8Pt56=*i>wN2s^ z6!0Ukx7- zM02$kCCbed}F2x$M`EDRCD#hR73;VTC?H9lCa9cOFbDoM-n| z{koWckVAo%5CC>uuRDL)me+`-^`@B$X0m4EPoW=L#e>~XMZ2!N6?-$nm5O7vaWtVH z9LRM&nu_}##eRG)*t$GI?l^QmOJ&(uL8`>ph|>iq&b6)pfBj1)FtW|R?qU}I4I95^ zl+@tq%l|)DKlu}-zGDrjM*bOF`ct*8%_TQ4Jm_g`&j)!;PQj zD5BWaj4s$p+XI$KabZ*(usObdM2F`JUgn3e=%m*0?8YCGUw08BaaKEc|6o7dCn~7m zuyym`9@)SK)_YSO%<5`-=48)S^t#cZXIz`TZifr_ebu~QJ#j-GQ1xy&ONbFWErvc{ z4DGp(%b(rHE_Yd!Y}8SFpCmu38gyGby1GiW_v%crflv=!<0WqX+jUQm&I|`MyCd`J zzJ~7RmC!{HT)V|Uu|lAywDLE#0{!Qfa7wd#3?U0W7+Ks)A(vSgy>;!?N)d=W#a?If z?ZoI;wzf?57oQS{o|W!8Qn430W2wd_L%pQ~e$-sf-bRO($pW44(><1vlh*+xRcWDi-q6Bg#Io+Nnfbd_&o%z==(!bl%t&_VGhLI|P zC+q?}AjqfB2cH0)X9{NkrvpSzGK?K-VCe2+WDK1Z6*{4ys?ci&S4%%R)`zzmk&By$ zIhoKw+Ow1#%?U}Xvy>P7;s%2LrrGpN)zdhB4-Zkd6*@16m5_S+79?NI{Ihr|S`1p4 z$@JRLkyr4PVbY|=GE1>$;AbJr@Na02rWl@p^P{w+t;v0qA4^DUb1T)>NiOb0ZYv~O zHbC!38rtaO4T`V%Y;c(;=^WaAh>BjB`fV-F_=ZNlhZNt-t0Heg-pJZevaTvtT;D=* zSfX~aVs-6z^Cj=^pXheXGNaCGmCo0Kbt=#HkaGK39L@1id%}wC;>0tlse$mf; zq<>RAN%itv>A7FL?+^Ghf3p*CX)d~`=t zRjX1jXb;~H^a^e0ih}?ea5@$Bs4?hDNirUc4`;TLRGZB5nX#zqSQgLiEsNfieyaW# z_(Is?=kx4vmdWxShsiHD|_4xT>btU%ef8O|nSGLa~(x_GV+V$y^17JRbAeluz zG(U7z-Fm2)WBcSJa8*5YsQM*nr?(U2A8n#r!*3Bf44ns<&I-oQWq|Wa%L$O|lLL7x zV<3y3K)3Z}#Jvkzws-8u&(_~P{%<2{*{CHGIPyP8`i5pU%y<_O4b0R4tNgA5c`WLI zN&aKM5w)5pS^?H(LBw2);CL{}m=*h_@%5S_a62u^sr^Vn{rH%15QPsE5~P5-7zD2(>qy zIi+6biXXDX;V4j@iN7#tK6WGlHh4v?J88YDS<&rnTsq0*&)Pg}vWvHyXA~VC@QKn_ z-W21xC}9he=`^Ryc>b))JE@3!H^mK9R06`3!^CaDb2aM9G;>=AVIH$6c61!V7n(0_ zcX+=ofV#Q?O|f4LEGic5D@=FUxVn$UHI}UpTQl0~&H(k05^%STMOVl1P6Le#N60WQ zG-pCRHEx(BFwInTakX?t`x<htPf>|*_U3xj`NW2;C2MgKp?Djg>UOz%A~C;zTHk-dG=bEx|+ zp_}{%$~pB1=3;9AMLDZ_vbbgk{-L@Z@@_^91BMBp8`*7L{HZ5qw(XP zT_SBfFVKP&P_vaJA(*lg^L)Pm3D|1TaXI9j0rW%h`svfl(%;au8R2=Fv)kvd0e??k z^cn)!-vYkE(03^kOOINosKJgR%ntWz_RjQ{0_ohT|7fkBaz%-+b9w=D<=n=4|87nN z+JKv0>yqZ<7?4%!8bdMIN1f7U_lfFJ?&b2=>w#VmVo?wl;lpI?0pg zfqyoHM!2b8Xiork19BX`%&Z_wFs7Q7nl%xW< z>$hO@!5>4cppp%3L z_n^n7=fKR(wq}QGzmXdbwQHKdjKM&MyewUeJJ;b-fb2M$U+=Gw!taY?S5ZjqSI{X& zEz*-5d4)b%AQkvT9d#oY^VRe3mrj1>e>eFm$T2Tge3!rRBfbs>yKqoa6&?Pxs%rkn zF>@*Dyw*yIYAA83KGfRdX2h7_eAyPmAZ4OawrMQH;?`;nb03Rb)93Y(UwhtKO@*^+ z8NlX%FFCA#mT^w+HW;Anfy|$L+Lf??8<(jd3_v5hiEKtW))eHXB75Lrr|7kk@cWXL+U*(^U zd|%}UyTQUUc&8*UL7N$Tm1(X5uv<9pIv(7R$eV;szj2f9N_4()`vjThl_p z#OAqWS=jQ&&ZkrbMz}DwOBtCQofZOjeC8cWs6|<{YXPlCg^J!9kpu$-Us}Q>QfE|4 zUCN+5IJ_D`oUc>tY<6WqjMr~)01*=1Y#~4pL)tfT`+?(#=MDnWkKa8uC2jrnLvLc{ z#2YZ7==l@8zOecI9+ushI$S$&jkZF-fdMs6$Dz7$d2IHb%OOMa|c=-=A9F8cTn z(BzrmrmcA7)g#_-djTbFi}z94JC^J z>U@isos!%|uA(yQ@S0ivP?#gI-#d5=AC~{=MzjfR zM0H6GZO7y;NXC%i+WFm@=#lTck~la(@{p2%K}YuR&TF_ZqcpPcWT= z)7!JAnX0H+NIrD$2sQYv`nl0j0Vsd14B3<9JWIw!88`j6A@>Xkhb3INh0{@B%+4-d57;_Q~*v zQ>>wCkZQT1U;TB<=;kR>WytVTc9+MjfNs?T+;7MwYmTcHoK%lEuOv^}9b&wOOiuLy z$Lk^9%|2rny&H0{t9Gx68Ae+wJP%h@c}fAm zf;P@ST*@D7;{RnfW_l?>3A4Ki06x3gQPos{P#{!YJY!y?mgo;@nZrH(-_V?WKQ0(} zQmg(LNm7yfQ(TG3`85c!p6A^){5|GHCZ%l&wy{F7YaN33}9xcv{CBZSirG~WTO*5R1X zc3VBfKe~BB?q=5LFbiuNFFs()%nQB%N?J*g0*B4TA%%&m_9sKlzB3g*QwuuOuRcHZ zIk(p?TIZJPV!GFbJszh~c4^HWGNf%~A-_@n2n&}@Ee|Gne_+YRb7N?Ty}oTn*fb>p zKUC17JxJ?AN}koz09!Fq<$lvB8dB;UbWws`68x|6RM(_u!RS#+@6;i$>5CGywBylI zqAo#{s!CC-S;5@xv*7DNDN(O^f)tej<>;jVd054b{_kH@UZ!z~%VP~tYSxf?O9;{H3+Ij>QulUWw30OOTp=i3H%1Qp5 zCq(sPa!7~s&Q4?m$$eWb{5a6${t}6cAo?gr6oQM;WSjPV_?O4 zSWTkr z($DA82!0K)5#vWs27ZG%GHTO}7kkC>yDTe_>IIjQ41>75`6M^Y>4Uc|T}mvF+TkzC zYhb`DOMjIrSLtnPwh-_%a1oWaE-6SCA&8|cIm)gOD0;~WNiuF+y7=`%Y3co%UJ9bj z*H&<@aelh?{g+Z;)4}SRY;0v79-#v_u;5=2KtE?y{Jz)n+Fd%Lo5Mym%Zp&*%uoZxS~RD z^SpeoidfZ3Wdm`!>DQb}ulaOQE0SN`Q+YPlw*9FavqxLNd}Nb4|EZ$IsnTs!O_?Wr5C{N7|gw zn}8x@W5e2VJLQ(9L>@nHfH0$Z1xI>XMyJeYr*OzHUXCfJhLu(*E8cd#buL83*yTM+ z5S+9nkCmVXZQcNFV=wRlT};aCZp4a3J^=O5^T8 zW=Q=w|2mG2ntAM5FCxZ1TA}Mx0(`y72?|cT1=(N)BI6K2k!@SvMI3lR6-u%ru)wzuNYn;8i))l;v!LgnF zs3m00a-;WMM%_^!93O0VIHS~sPwyN@irW%*hhqD0@00Y z%*0E^TJs#evJ(+C4fQ9bdu+gm_cXHX_xsV0$A8G~{(YQU{Dk62`5&^=%+LQ&!srUi zA*aZE!-G%4H5GuH(arrmQUICa!ej#yHSY8EQ8Gr#U$F@X3^Z!qF=f}t3>~o;x3gZPp$1D)G zrdX9ytvgG8{%RUki-&q0)kFSV7zGDS5+DpgYmvxaxn!}#$lS@*oXgfV%)6yhTMof? z{fhF0RpphbWKisV9+ek4M3R>s9(eJC<@iNaAknal_+3+Md-PuKao8IP%>)grXFXPXNR3GX0n<-a!fEK#BSlABoeZ9#*%x8U^V7@m)9J+JQpw^w zNwrMP;fJJzCG*V?yE!yc%;$3V2fF7g-zC0@`$fsC+Z>NQHC%+gQ9vbG%ZxOby{Y^x zxqm!Cp)U&lRkl-$xd9u9rpT2JH5abSa9hf{88VJxAc6G;(eGZ2TFFS%3}7IolbZuy z_Ye6ROE!Ru$6n1SA;gjCLx?j%o{NsgJ$8i-#s~OFqG+}`GPfwmqJWc$hP;Tbcu}OF zRf_J7zKqs$aVEZ`rhG%5ZP%wtKH6aL!+G44d>@2|xa1lUq&hUXnS24Hy?21JaJTm0 za-Mxtfk7kUJ#OAA;G7)*i|+8EMBh&%&aIK?jMtko$e3%m=k1^?m#Wl2NACc-Gw6vf zwvO=&`tiL3QvS8vuNdJykFyJsVpmmY*J`C!LgpJA-9W-2(N4VR=hOivJt~3Cc#dr7 zUYan$3{s0;Gi~iH3t;lL!C1U9r=DjKDymmxmSo&6@+JiquOJMgFKWNfdo>`g|E+e! z=j(oAJOR(V_PxwD`w8BJ6fMg#?N!TbnF{iv%VO|n^7Vy?-pn94Refcf=EUAlyu9^B z&dOF?N-^UaFNaO#O*&mvF*83L5A(xzeC9LWmpH)`WO(Z`d&6fbiLO?3nGQhCj~4So zUTiHlc`m-bgIvnXLn-&ZpVnoxWmAF_;9Nc*$;Va=rIvX?r0ZTBD3J?$-vsmzA}D^A08Ai znWgi=EF}-fu64|QZTNx)wY@9{yiYyVPo~ldD6!fJ^(+gn9um~wLU^h$I;0UOf4o+s z&iMLe@C$`55RVI4RIU`y4W$e*eD?xX%qBvt*Ffx?zkSa@@c2-aayOo=T~UPIdl3ub zd~+wR6i_g$7Qe_m^VyeN&zgqWE!jhPR`JSDCTQ8 z4;5#K@l`Nh`+Ub=?xhEP3SqaRxxP@2K!%vkzTatFPe3(VawgwR5V76CG0ZAqXf4R~ zsVd_5{-pDU3=MX* zTwQp5Rl@b;du(RphsPJ+Gv!GEZOL{#ltbPCGxK0`!1LGbc_Pel@|pu1(?;10$Mdjs z?v(*fUryXmGGuTrn6i>%oh z8q@H5Kd$S>IUV#_jTh?HaD2>eyfKIU+)KEd=~SsfiWjdbjn4swaVbEJ_=%GO*28D| zO8ucQF@38B;?MQ>0~i>L4vaSjlfnkZ9!R|2bLAlDjA_K&sbE7{ z=ks#tAwwZasoGNv73nRZyQP;ZIzhrk1=v!jeE`4UyGPh4hr_-8`I@rCqQmB$t>YVa z+XK(G8=Z9S^)S6Wq@%W;Zn?JTi}7YG-wR8SD_cYU8=C)HYDm(8Md8{`1}0*pQN_-r z=$sGOnM7(2`zqF57fZt=uYcy+!m-oEgD&8+ARY43tLl?CimdNolrV|t9qfY<0c&$H zynb&Q+Eyl;A5;b8tyaK>K0I7^y{fYUAx7H-hT4wnSVc_Vl-UIIe7A)%O=AT=>^@#; zKbCRQ0Qoanmoz`Q*}c;I=76Js?}=7}qTz)(irs6e$eOVE=}0jem1m;fif3{59IZP0 z^%AigFfNLrJB72XoYEQhLu!yr;) z=DeDE6bN|Q1xz`IcZ#C}9TpZOI#Bo%I_w`D%9C?Z*!44yT`%UFwp;v+Z;j|0)oy;7 zd83n|RT@x~@W$1uD)i6`%Cl#GpPcpR(=;4`g=LHPDdNiu-3rv*(W3(ozAhOrBZ>nK z%@zYntgR4x)yzD;J0{?3*Pik6S0&kR*oTmOT^fWBWv2!XO`ADEhiz6KV_u=8xFjbe zt(cp6FFXWde1s z($HTrbBEBk&^;_D;v9s)21otA2#m*A_4}@2N_y^#RD@R0A6&htkQv-~TQ?Q~$kbBb zUo9kx=B&XP@Ud*1a0g$qAG(vYlP9OGH`X{#fB7!ggUnt*0zSZ)Lx#{r+JU;t&DY$2 zJTkrXr)rR26e2fjZ$g{nzM(;?mX(0A`4ku^3r+>V)emZ>3kLv{4i8_kDSqWV1daW|5bZ1wcI>HVzFmKEJ zR++Mr1rN_@BTA;5@l^S>3O~?v$riOy>aY;@koCQ+!AiG9@8jvB+m)R1;dxV0Ne=-& zPv_c`1Nh|tw*a^(k88B)%Hi$TOzc@a_mK=BUSzm@uM*$aqQsDTc~S2;=eeXMmdvau zm3r1}LK9>g&g2Cgx{2y^Ut;Oc3OE&gNr zBgSFh&x&1@gfw9Ql(-*8`~o5(3WpQ>(2`|!zkC~HWq!~Uay zT6SWQfrIA$-2r9G#~W<&3R<^2vRc*E36lNaPVCi2OI*CrtRm(9i_0K36DTH1+W5G; z`}*ksff77!C}!-^Yg+YcF@b1`a5-BQx(qqiTvDZd~7bEWgfLqf%XGGhRoY&5n6GO>mv^z&^Wi3*FF@OilMPSr1nM!%EO5h&Vz;vuS> zQXFEU!pI(1j?ep?_eS5f@|ZYT#L0%tr>~z4=i|)I2R66=viJ>G0jahQOUh^_rb&c+EIeVIDy<7aNfYX!s3}3T71TMEt2cziP7U$)Q_sr9w zDbA@i>p-QOEt|&3kXb~w@Wvi+OA-zo1FunepP}HS80)g8iNZjD{yTdhl2?@H-3N7d zdJ1{qHF`AiJ?6(-S6<1u(UsCCH8T_Va;;h-ohX+7TGoMLqLHdDt{(xunUNwl7ovZU z-#nVort@KJRUYouB147gz4X)eM4#+l^>)^rsTiS_K=ZXp9A2s+xPnGAM|AZ{U|3OK z=Ju)Lqc-5WWadnnsJE>Rkw0Lj#|ncoD0-O3Q9C3@K;kEjYc>-c5ARYMw$;=|UuQ^U zI~~+uB;S-OtqwcIcOVxQqOgN%WJK`X7NS1PO|(c4KNoFLXU+56J*Tf5thhhO!$`Lj z&9Y6Szekm_lP~bF4@>u;1$yvAXLlF>^@;3CWN17HiDR+6eU# zlPE?*mpkVn$qnMja}D^M>ciZpm8f(j--`K({d*)?%cPA`PJ6b%yrAG5A8D5bqRU>c z@OZH>v`H+DE%T09n7{IDJ5w*_N4rlAd-oAHzzmzM)MraYj^)P_SL;ZWPKVexyL!Q@ zS@nubYYzI!V@qzI^=1;RFr)4}a7ZN(`98DoA<90pJqyWrULp&Le>P(q#sGKXSL-($ z=!8aqTo^psU)4KM^uKC`!1`fa5?^eZ0$fY?^UFwCd-*#lZSWplQF+u1XCG7Q)Ex<( z!hXm1D$O(QQi%hJrqe_pg{$&zWx7DLH%YX>AS`3z9y8(oULsy!(bp`s5c6QSDPxs+ z+Up{lDcnJOkfjWzax8ADT=|kH_nM>*6&S@{Fk&cJt&Md_t0jrgAzMQgT143mM~P*! z`&Kd_8JYst9M#aM6J5`7jFe_Py~T#b%iVzm^OpiGezE3PPET9LB}46kVo+TQo#V~E zL7mo#l}aYwGek;&z6LSQw#aNEo)W68AiJ97$yoGS`{A5P@biK^m4cRhUO=W2#wXh{ z{}HoPw;^J}OleFz@7VEc3n&-O0DzLaXLx5{0qx^WXp8wis)Bff9hgRj^hM5ULH$4r z9IgP8#6dIZ6(#ucBcb!-V}|P+9w9Fe#>^p0RVvw6!$!Y&I+1ge`ofHKicIUe#6QLu z4HiCnZgb%5;l{j7Z*%kclNabU8Q(^Hu#&6iwOgGu|4 zrJ}6j2HmD)gR+DC)RresZ_SR)m{22Khq{~Fm(?``y#iv>ZuJusi0Sa7q07J zPPO^7N7L5sEVCNUx9poqz4g{3cfwxVxl)~iWv;rlHQjbj(BC&EX?hzO5Zb!DZ42z4 z_{qC{MesMN3j16i0%Y+0qMXiGp3rZeDhF_N-r(xg9i+E&XQ=CF+q{OlecG1;ngT`3 za<2;=#{3wf}h%E_0tiX8=hJYokrR)28W|N0!J?ms$f z_CSBH`MmZI9-KwQFCLsflOv*jl_LtIp^O?jFwq|tW*z=RnV9#e`y{YuEiLzA{F07Y z=8v@E-#kX&n!czu{>t@Usp|!Z^ysIc!~4*cvh{%7j+55QG05?v#kNVUErClHXN6iBDv(^9%?UmXCPd z#zQt&YviS|oefVJ^{(y0U$-Ebo_$Y=AhsLQ3>@&13C!AV71Wsa-c1ygXvEzL( zOh)@z^(FoE7gA5LJnl4uX~No>TXKOfS>?`)fQx3SHE4S%GAMWBS!JvNtW%6HvvbGN(W&Q%vLvcfMz zFDa_MVI~!|E~Av5H|0;ru_@zlrd0HXcPAAwj6qct!B+mL!%h-je7%*$O2bT!Hu-?? z65JSFOmb6~EwG_Vj`AJ#Mja+OiU>9TCUhQiNrbOFuCA?A8JrenX2~Q7j8e-NCKz%X%u|G3`dms6N26X%+p> zmMBE=-j3Vw;G%j!WB+1oQ!P2$ULxb{aMbWo5qK-@V9%qr9fcn0#u7~s8d|{^9U~Ur zUfxc2IB5Pj_O;0UJNNP=>lB67c)`62W}-#zmiNZ&YL1E3Sd>C@(d*_Z2E}MXWN2I? zI*mHFnuSY8V}+WffQe$6q`qS8cMS%EEs)vQHKPMZV*+0_2_`?*A0US*!7jfEvsKF( z0uS-vjYdiZb%(JsINp=TlV`Y>Hd(iRzNX(zxS}^N**f^9_6ciET@ccoUdiq(AhsH3 zU|0pB0LOesKN@8U1BA3i(R(=RBP#%Y4f_qb{|BZ2+^PXkdZoC_d2CSF8v4D%yk@yw z(&`Oe1(&^r{QR(cD-ia9*5>AwpCE;apeAIqx; z4Cvog1kHeL@4Jei2h)Q<;rXBOWSA}M#BAm0%7&3zxYh8Ij}A@|6vE!*L-{8e;QfR# z&C;!b^9|l4sb$8x4qNx_QDLKUL@CzJ@t|J8i1&8;tadCEe0s%^m^q4R!?3a(O`?4d zV?>qt-OYWGJ~|^ELH=vm#4$zJC=*)hPsD{Zja=?>ZW>mDI_BpE9>~kF)YtBUkfrCS zc{Uxu-a~#?9LJ+v%86&se9Ck1WmCQ&{}b%)J-WiZa^6Zm%>>+HyD8l#5-&Idk~hRL z;OUH6PiH1V4m(5Ygoz~UwtEx1gcAnao=6pO1olY-+A@xwk<2{({nREw{g#I@*F<`^ zbeq!heS)%PvZu{NkWDZ?X8Sjbo`>A|>%IAParWZX5>z*M0v3z2sqXuz=td#8&A0L? zCioi2AFuISJlLBw4qCKJ(+faiurv$bmQfDvbOK>^eGV8Eqwj_1mOwkn_mOe};ZCIm z>8g1dCJR#F2;Yx6B>;}GGk84Jrqz3*-Y#oyz9L1JQWpIEYUDPJM?nUq5~q1J`>JnL zqz3Eg1o`J)+cV2?f`ftuJ|-d65yO@Kkr^^=XH+!VjEOQd1mITEU+eMj|Nd+T_-_$L zc`WLv`$xlA4^T)?rtY72Gm`cIW|Uv^%78Y{uc-)d_Zw==Fa}^0obuu89{|fwckYj( zg6KuUwdreDiP_)oT$#Pl8~_^`k_Vr!WcRk$i55b&5fzzfTIkvnLx*=u9BFZ>Prj#- zTK4J}%BW$bG?x@LK_8_13dCv))b#33(e$l)NAl5Pmw`7;=Qf^2lXCTx!<#%JucG#D zgIWdyJ8|p5eiN&TSrV=cJmmGo`t`PO7tdt_OqFiBiEIzLOIz>f7<2hiop7-}F)m); z4p37ObCCx~!*9YY;*njV1S#7VuTn}?-?{M3kW~>*{=&L*97A1Ggij#n*rUnBCtU|? z0x4ds95@0?Uln7YL?7^n&Z}5_{qT!Wb85OlV=De|`-la8^9TRaB*-I(P z6Dm{7dTpF3R4O1Qq6&MERK2_=!(3HJlt9(s=U4c%S@_4V{l_@`-^e(;;*Jg!ik7~} zUvS;wFP7nwUoFE~0>4s#I?=P69xo}*XaJ6-dCGq&{~$UNv)>2o@L%{yq& z!Z%+c`t-r!)+?hd5i5sx3TpdB4xm}}JHv>WSD>5Gbh|gYiGT! zbg1-E@fnjMrDe8AKyz^ON8r=>E~JzW1VcyPU%D`tGDeiRs%*f+@c z2`9D#z-IRCIk59?Ae#l82S17xS~&?dCYJgh2`$;Fepsk zM49A$`0Bb$88EB8Nf7_dv8YPCFctJO5B6M$$P^{FJIOb+F9N8f`+JL4cpB@II!T>K z$A@LrF$$kZ{dgD#HMHbq8CO4Et+Q)XCx0(%2dAayEg_|Udq|s{{Ao=VD_58bz4ffx zTY>7KN0#K8#hqoP5c;PHg&1ADitC&x4gJU370_UNCJLo}G8yYDSD7IpE#7z*tEV4o zVm;G*#U?Ud^IN2d;l7GmAd6l+x@&p)W7MMD6ydm;hSzB8B+}O} zC9`BSt}?n=FQ&I(g(&bhG_3sTiII2b#jCBW>Ytg1zM&Z&We~O6b+>?EPr*1v^xfRK zpEt1U`WDxY`D&=TmJ{Ya$b2MyP*sU2q*5)sEz%wuXO~{K9z~H5BCc_8J?w_Th3Zc# zWlazBEqF!ofdCCRAFU!dr2=YOE+>N)A08T+%NJrSCs@X}8@L~{_v z1Kh=N8KvmCVN7q21blb$1N}r}Wy<#{L6C>QgyO-~cv}BQhZHwI`9QaxtRHafW%%T2 zus-9(R0-r``AbwxD7}x3h`e%OqXXp@m3yxju7kGa^n_>>0^C-z;BULuzMvD z2GrPdf^sAU7klXr3*O0B3rCSM>!NYQ@)F^+Sw(bS>eYxL`Kjy)Pr@&~1!HS9cOA%0 z`^h^)Gqv^ve3JXB#2xjfME8mcaIXSc7s8T5TL(jzIH&KrjKdt-A4bn{9kpEMR6}rn zL(}mhv4@#eEQopVh=v%nVhm4!Nt`jpwOVh)P5ELG7I>tY6F-`AxY2o!Pay}Xqj{oTCo$uUqGt%su=+rdgd9x44pGX6Y5#9vyXP8^<$HDE7upRqy}n}7Ym zu?9Bwc<2cUm?d+aqM)A0@UZ(*-p#1Vl(%D=ADE6xy+rP*pw7Td(3~xU#YjM~=~|&G z+Br~)#mWgNCWN$$cKj{ah*UPD`SxT-qy14THwmho)0ni)sB?mlqBBa;O6p!~P;CE& zFAFmeC48#Kq_T3!nN!al`5@RZN4NXZ5q09_>uQ{1mF0}rz9QoL*eCoIm-=|#J3H@+ zlpCW#AVXKSa;e|?bu}Drt1D^Uc_H~o&R(AF&YA^wT${3kZ&j!MGek#>WMP7*BMuGW zqG~=**S?9Aw_-)$ezj?B)AZ8BxRZ1_tgl-=+1Lg@MVV1DB<$p)p^$Y}6Dc0a?s=nR=84tR9;j&b z798utyBQ=uV|USq7u`xhB(9jK6_*F4?3-fCaIGXHtW;K8O9B_@ox`N_=f{vD2Y*)HaD9j8Z+jQ#AMB4-8h zb<7|kzMM%`9R}o0Ndqc@q4+eYaQ^BAL0RyE4Hqp!(M*6gar~w^wsGJ`YWMnc^RD8I z)zWzP3ZKMTb<8D|6)5fiddL4(tCF5ag|S-dx_i(zR7h(Jo*RMp?qTCb;6T4*sUZpJ zl_fY%jfz|x>UDXik|wRd%Q(_-i=@gy>PW`BEj87hCb!fUB4ix zS?+-8)PUqlaa&&X;n$pq^Y+C=8j150DfjGA0*N zKdy+f31Y)bURSwSa&S9B^wjuzgxYO2NMhDqJ27X_3L`LmMlr(9Mk-^?f_{;f!XX7F z<>KgCSyLZOFE=-in229m{4g4~Wv?Phy>OV8Qspl8&~d8LiD#aW(2$$N!nlO}{Wj%J z!>J`gl}Ymcds#fG=_>(8))CO_4`Cb}20cu9gi$789t=$jhoI-++eT1oa)&M5t@wUz zODFx*m&bManxndEm8rq-X0bd=?dOkO$C8~lwH(j0srOoH9pVc-G9~RQZHsxmAVbRC zm0orX7RqYomer#$I?48U5oI9DWp?DkzC&n?+2+BOHOOCfDp&2i*k@iG&NlL%U=(+N z2`KL)QA<`?uBtUK2Ii7wj^|iig46@9;C&m z*G7)&dSN$RZ=}~)eIB4GMZ>582@imEXQyw=m5DjVt*)$uduo!&sSkqX>) zT|%RhijdIs9dET-HBaB2^vLxunPakSJ&^d1c*f020m0LxI}Am~bZ@v<2jUxQh8|1R z?L&*lih?uiZ)e?TNyIM~&~Nve{zR%!jzs<+ofu09f&?2D#E=;}iQxI7HTNFD#c*%sV z`9SVuj~s|s+c`_m{2}%HAFW+(NJ8|_=`tUGKh;9ynEoZrg_G~En(yczGujv1NfMhA zhMm(VTW{iAkLLFXcJ06`{sbXjEt1|y_V4bf=5KU6FwXAL9$Ka`oMpE1O*4z1BE47O zqtH8WoG;Q-W!QFyU&31UwaWU*aDX_)%g5*9xu)vV($=9&rL@N~wkls~bFK&LEce|h zeLN{bQyOk4JSk(nf}*VGZxIu4AfGVkU$IyYzTUv9*+$l9KyobHGn~5O`KY97oN$RE zZZ~O`qmHhulFe|tU;_^mD8}2reg(E{ygA-s4)`i(g@Vm&5L2DQig>lLFeBBYuyU+S zBaVtGszeWF3+dwh>v?x%0aK(z$A|w)&zMkk>*&|klir%fI3bAZf-8#$kKnXdKfA8) zW7&=D)k0#CFs%NffGu?I^jG4fa) zQ0&f{f%(#Vw6vcCGV9t!XsL_VOxx*n)RqdZ9o6P8n=m1J)i^8C3Qd+qY9=k1mZ*0T zy^0Q*Q2h0g%>IO`V2f%Eb#!beJe|W>>QmYXIe6wZT6iao&0vS|L3&Q`tk~lb)r)Q} z0XSjYYGVM1taoo-8pni#J^8H@;&rR?5UeYlwaC-J@g zsu^S{)To^8XXiBxkEZFS9cp;)H0^12&rq-bGf%(dw$7pGbyLFtL%q3(sDubJ#`#3V z1>ddsTh;$>mi!B>L9(h0F6q6pthUaA+QpToNlyw?b2mY1Smy0}rTONI3?fnaI--y$ z`ztI?pHJgt17fo6dnwCQ>#&j(R284);5sFC;Mh3vybbf{j|TjFzH);GQFh(;iJ=CDkfBSYyFnbfTe|VR zJ?Giaai90>^E~T)-{-gX`fdMU-NCG3G1uJJ^}RmdPtQLib7(L5Bxl&r>{sJq~<6uitmvN0t!Q9+7H_zRz74jgYK_ktl(S73J%oCEa zdbX98=ilWMeH>QByA;tx%%IeoH)XPnTz6UPoXJh8?E<`JdQ&AP6csCry)3PC~NbV>4 zQ=eZ}N45E2={AqJR7|>-govn*Ro70Luy_~N`gy>~Ewd9VWm=Nfa|2_Malw6>coU;_ zqn$KQIs6(%V_1rrZKK{(c-k_o84SRP{jk{`9Z_gmuN7GpUZCwc`e0hf?2WY9gZn?^ zEk@n}P$29Km%MiDloHjmBrDe1M{=^Z1|F}l90(6U>0bGg*CVyf2efF93iqy4brlzT zwnsb4F2<}|Ew2qi@~*@~=|!IO0DnC@<`GUSfCzCYX-9Ja>y9jaes-}yg%Q73`DpyS z^V3kHlI)m~<>(ORew#{HD_}hVj1>O3+k;(d>#Ogn=~-$ktbaD{YT##EsBlErD$UqT z>cgZk;uxCIK32b}{B5bTJ~ZwOY5{0mIPHH1mi~In%zu=-*ZuvY#$e&b!pAErgL5eBZ7v`_y^@G0Lx!Y?fm&7y(unRB0@O!VZQMiWXN!XKUb{i zxhJ-4y2ILZHV)X{XCv<8m?)Y8`?i2qxj?KVLUZJbyDqyGS2`e2$1wH(Z6(bLlx(Rv_D59zbHq$L=o zusd|+7!dfPKM(i+;rTC&@y~`a{x*;>0AZjDHx{^ZDKaUL#l0h6JRYU(Tp*Vca=>7+ zgzK7<*X5iA?%Qiu33r)YA&amY2v;~*1ZtNmTkH}_s1ZtQD+nQW-8X$2v?d*0u$ly_X{e7#SW zHI%PEEsY)BL!d>rUD6{H@Z8URjO$pD+AN{^7#>ec>xpQ};NQw9@_={JNjzgXEc8I! zW~Ygxug)XtcHx?wElQ}|9@3IlU}&RGd=K1UDCX|$oJs;PCz|AM!(~jZa(O^=;_(Rb$U3Zov=}cJDpe_!CIVkct2%aj+k|rfd&fBt4Xx^XXMjH;; zLEpD6J31AS2+_NfnNIbRug_A4=flfG*e1OPvrU!3{XsS9`wl6y>IQN=cllNF37Nty znm3Hf)fYvpK6P-x@i`V%4?R=1Z&|(Ba1>w?$M;ev1PO&|^o7n9#)vt$0L@Ky;H&cA zioW>Uj_#+|{))o*kuLqGiNg44WH7-7@r?vsOd0%Q(~p}hbPelR6TV^`fj6wibqTMl zKu}KlMPO62<6^Zq<1}b|PqQ;Y>ez~HO*tVZO#NioY;;bOM((E2b4TlN{4@U(HKKZ~ z6$1*L&$`E)M;w!Y<6fgjA*ZSM@CYzdv24nhz9=Z%oKobeE?CymFV_q+l42imsXF=6}bxtMz#r|WJcQ+ zR)sB4x~6#tPnn5EzB&Ru1)^tuKm4W%+70Y$7r(G3oIMeKEM%$5x%Is{@}Pf^L`x@DSZnMzE(?ewCnE-rqySH#e6s-eh(3}OFJ>gKMzEJCddOl)#bMRy zWK9j-7-4j!B;>U)>@ydfW z@Y}56>+4dk*1$GhlQr)Yt1I|L}soEa$2g9|egqFG>Qx3yi$zdtm)z|9p^&?ZAr zvU{877TUf>i4r$BWvaZ&$h``|uSCD|8SJFf z7fbMjU?#0Y&N?~nEVAt0hm_~TaU2i_YLMq%m&{8w2sOqlv@?zOri~NnmeX#YeodtY zB3ik`;ZL&d_BoFR%iN|Wx+NdmGa6uT zG9p%SZXj~?r{+A$P{(WEM|{)GqJb50txyAc@qD5P`*^RKn(q zsTji=zfU)NmFG;*Xl7lqu=sYg9%sO;;RpAmb&+N^EK6xzFK&+$96!Y&yytTh{&{H|DeSz7ld057%=Eb#SBdEcz?3^++=* z^>n@}-%M$=q!bVXCMyZr?VUpX)kR!C&#=+>`&S?`20qG=q1elmk=k49Vaz(-i3>4A z*iHr#YAnIUB-e4p^*z~)qO@$b&0a0@QhK_84vDowEK@+M{Ogs{&sHer6)qViqEcKy zy>vwa724FeN>F34x&N|+(^Y5T)GcRD<@>mt8B!!NSWI3a79`3-5vn?o>pVLhF-8YM znl0%;{-qwJV^M z0HS8p(SdQvK(j)kWJLjp?wx(hhlQt!MtmeS(~Z*}r$0s6cDSqe=jHp30NLirM^Tpt zmy8=cQc`2b1Sx`p*Its5Dij8TA5Vct95wtg%%SuFy!jm~<=h!e)oFAy^o z+2?q2ke6@Cylge%@?!ZcbxjA@-{^$?Tt)tO&yVf?SbA(w+~jvkup_c@+IXPv(&~PG zC8v0Nz6*riz>3vbuBZ?AH@rCrf2TwKCsp#m|DROJ3up8f9=Jd(jl0jVhVwt#*!7d? zEkaB)r1eU2zsuzw00&oR2!3##RbEkqA zh914a@E{_!&ze|V*nl{D=}-?p*Fn#Ev5YT0Q6UNyIO{s>3@4$32LK6q7|a@I^~ zIF6G|+jlgoWRK{qd%4DYN&>D$Haw#UXXbz~r$^oqpFz6oXB%p&*sZTq=EV3B=`zT3 z^0cUw2W`t|Nh`SzwX$Mt<;EJ?t?Q!I2B(ks-!q?w@ss1@de6PaAP%~iLPa?Vy}Vy# z$cNY?tZQ~7PqpfJht@Xfn6bUbZ*jL7Z&_YFw8pLe+aAZ$Pg(Xj{IfWXMIz#f#gxR> zVtX%NHf|UMPh>4kGP>dJ;P%0x`}s5DJ!#I+Xg;ooXQ0pi_Zd*6dqt}jY1Uh|5@C-7 zH4aql!JUZBn(#u#J9#(UG`t?#Y8-4rkUpW=CHE~ehb={FnPkcO{rakH1Qn_ULZ4cD zJm7v=YR1=u*x$N;Hi9pQK;7{=8ym_j;H#)0H9fLeu9G*R84_o$Q(D2)W{QbWUV#Yl zKOfNks1RD#@#Pbcfa|9_8LnZNhg+}oQOdm!7lsq9NhXi2JhfEu7L&$}SU9uC1jRoo zGZ)A@I<*WVp?WGI$IGVTS z7(aE%l>3ggMc|zoAs)j^uLJIpnJE%ISvpA^bu7!BUZ=Qu13MjAdePE5w&<@2Tnjg( zCMv`T{ZoJrH+mnZUe;7FNBLGO5uG6#Bq5%??Z)j}3$J z%aK?mt5_3N{vx8zb)FZaZKzoAt;2)DuYspd(=|5c$#@PZ2)77|8$mb9w&%!vTeXj| z3<(6oaJl|(2$8Z~YSEgCW}ehNUzeG9u`c8}K5quAxX9VZ!+iVyAjIz&Q?08zOs=+C zbU6d$xu}7ki-Nym*cn4EQyd=w==1E^Xn|uTclqtG%)(O{{#HpW4(zowZ08k1*$at$ zI%!E|QUwauQ1zuI@f6MWYa%aJz^o{KWT{q28p@PCKP1$-AR}H5$*p$O7@4xUsg999 zJD*1TGTeP9Bx>x%K2xPPVpA>|sYv=Mz5VzNAuz_=U-2BJ3|< zc#eCDG>eJq-b}IHE!qLMWU9kUNu6*rd7e=U^H{? zp^}rg1edTtcSN8axE_-^$XTm>mJw~=bOol-*17gjeMs77B>*S&@qnxAen{nX|_N^L0Mo(P-(;FHgRs9ROnWIKTU6q=yYecSJq| zZoGb%8qaq8`HI|67R0h9Xzfq&E#hFi+>D>Z&f5AU`%fH*I{C2I3?sw~gSzfu$+LFy9^pIk#^b_Tx3!G(+0RIG1 z`Kl#Xwk>Cn9j+)ZJ+iT7i>EPj8$GGw<14|4u__tF(!0-6_LA0<*02Vhh0}VC1#Z=1 zsqEP2$9z-7BYtlS1eEy!Gn1Vp6FH~7SHg8;oEyyLTVa*1orN3k*OqA>Qu?{LBamW; zc<%334|unqWCULwDMWqEFTl4(Ck){{G|_hKuFPezgfmRuCif}uDY~insb1BgS=z(O zAZgDsqW@9*hgnVUODhT?Ph#`5={GSw7UJe7WbC;%cmf7=2VFw>GAJyp5v?9Y=%kk) z(>$yB^SRi$@hH8A0+Z5s!7N^5<@6nY|}s&p=0xrbmr z*V?)AOo{_5&7JkkjuF7siXLm%OU(rvsE#Gx;4}2;eH6R!fpAAZwkEZs>s>5pfw&bY^kRg#o& zrRbFk&dr|>n_kZJMU?ZN_BvcSr|%B}gOsJx=|=gIxs+muxT`Ez((t8K>WhP@ucs}S zb5VydoP>7Dv=hWr!ThYgPJQ!Y{(L>%G|o_JQa8d}UG^+;OE3o1<)gv|?<1_`ODlfU z&9@P{%hcm5I6@Oy%rj{IkjLCErmbZ$QOC)Zb2_Ri*0o|GS%`7gN)m+9Z5HMWp`=e$ zU`zq)H~LYX+sdAS6Q!Mn>{T_k$uO+^;ivH*Lx4b%klk>gBlml$3_Qf~!>GVVbTi=; zNG`N*l+-JeuADxH{J=Q*(PU8lI+FFlbR36F!HoN*B>gy2Lxoy!e~!>a+pl zIu;N4)|IbkMQlGA&@57$me+dYE>AEiY&0=G2nnSQl5N{ij!*1mL^9LAXnP!!$3xH_ zc=(w8Vl9b_nJzncqgu-8RI@hKWvP-O$|fr4w)v^%&RrdFouh#QOecuoe&2NVcsgO5 zCe`fM$^$?T$-u4t6s)HbK3JDPEfwq1LHO*dUKN0qcP13s%)%Oj!<7d#8McX@-2L1} zYtve?K$$c`PF>x8;njM$a$zYjP_U!9Hj$^Iv0$--8j~k64O8zTfxM3Dh13jFMdIUj zOqC9d)EL6J50S&2#QC8Puh(%;v|OX|P^g)k%hX0#eEJY?c1DKm120mi46W1^qL=0Z z1TVA*DQ80f*WKN3!kLxaRn4fZa75i0b;LoaS4iO}!xWmvMBiM4Tl?(^DWUl}YZWh! zX4?oE&>ec84bAHmgPuXr`401F@GXhonPOS4rG@5Xu?Lv&$;nGr)n~Z{_%c60l2HkI z@j?wMDX}nImJ4)xN-bk5S~-}}io+8D2De5{P@x@b%Y6xr($jFKbgO}i!I|?1GAYLM zPf5|+*as-a_Yr#gqmEf$Hks^`0}JYHRR*1r^m`!a%@3#FO;=Z!9@-?fG>~ zl`+H;vK$u+NBd4x8k>chv(aKWrU2B+;+W%*nW7A8W;0k>tlJ;6&2|Lu_dXV|*C{c5 z?9s-?SHR&jb_S6aDbMOh9jrOgT5gNk9rq7qS5Z(3A9ULtrUzP2>b$SL{R)j^(Wbx1 z>9$imDN6$l4xpxMX?)QA`v%6qc zA14YT_pOZ4rU{5_WaqjQU~b>)sj<#&dc@~bq^o}1_f!LwJ614ytX3$tj&oPlC`d$i z-$_@FL)}1GH@W5qd_Xhsub_i}zV{2+`CG{Cf5&g`e-_z+y%sZ8wi*hf*Rr~umt2vB z9eItKD4#l*13gBa>xJZGy_X8>6Y5#L?vqE{tMa!=!OGK;U93daa699=0?;e+*i+4P zOLQb{3#UZ)N)|O+#~n5Y0~!^buWN$<;gjb&KvE2kRSjj5%Qkawo#-nvQ=~&y2ZVq` z3s%Vex`(+v_3|I1KAYWW0mquA=NBnb%ROlCt^O2yK-i%>T_iK^5Xf*|>J`J(&?Phs z04{tvGBD^Xp``T{WRNLSM?=e<`}&7V@UODSYV~8iv3g#Hu?R*esf3kqPmeRhHNNF2 z)#MNP2;teYVZfF^3%S5TK>lcG%{sMiQXjE_EKM2d7%W&2(KTH-PS+i;97v7z=r2FR zQB)*UO0_Id)petS$%xVOypSZkTdzkw6xaeOEnj!Nf$>HlB&`**0a4P0{^}feuUFP} zZ#^k8NTlYL2H`5$vd5h zpvVKBhqo1=eC7@i&3k?P}pb?z58B zk-LYZsAFm)uX<0fAk#FEj16qHwDNY6#4VcNjev+U)Ko*VmnkH=&fpluU$Knps=%@Y zhbayrz4>`3L9;Gu9UQsnuxGWy8L^}}?ckfm(pXyeVFck;P!!GSLatLB^tK6xrsGXM zu(i}OR%K*1uxBaOcmbf2*6%qoK5Cu{%klIZS`L7aJI8XM?pRpg z9VND5K$vF;W9VG-+rzV@n^=oyCp{z`QRSiXvzn+H#^8yOWodM|g@$wZw2Rw@Y2q`2ZZg?5qJMpgm7N@F$u++u6V#CD{TgRtj&pOiC`Ljg& z7iyWYp`>b%j?Lfu>HoE~kSg|0vXUg%T$DtGk(IEitj~+emDH~Y?VGD7C@2`=XyYSB z8`Ae>Nul|SaL)V(akFnCB?`g!G*=fYuMG7Vw66*Zxpk+&wU!mhYr zVy%{??I11xhSjmP?Z&RK&le$qW;8B$z-#lq{C$4Yg-8LDkess#sJm9b)+*Dj-Fhic z91D&LzNUhBi0W6wn)c$+wq;&L5A(Ptb?04$UYFNtv{;dKA)R1Fc?ZTMbJz9uy}T;W zwEpy^u9j`5Tg z$GW~(z@+P*Rd$NhmmNiph&j?rtNNBT!YSrJvVsMYml7MFB-@B9RK{H|p-!n^s|uAy zO;l=m)mAc}h-o@n59C~?yzFIAw|}GHt;VH>^;8@u2=N`w3UyX|xvz7|Wj>v}N{|&rR78U+vSd=)Ca;rWl(>g;S=>w7x`@tdF7rQOzP?p%mURmvkBYL z3}5PX#9)D#jc2hMWX+*3syFmbKg{wRUTtWF3bdGCmoPh|j?xymE7-EX^t4czOwlc+ z1~kc^XXZel(4H9zn8d(ds#GRmRqz)UNvrURFkLUb$_Z9;8zErQif>Sjs^sL&?^Sb( z<3EJsRS=ww>h6EkEl?D5276k40x*$Ci{CJie|P+aSN%EgDiFAG*B6ATT}f@ZmbBG9 zx>20SIWZ7Iq?oeb)FMkn_;s9Y-G*0J`vap(eF`~AKJ|2RHY%8Ya>n6=9?YzH(9M`o z9q2~xcnj@Hs$SnfAUs!y;*{J*=Myh6Tq)?mGwo3bT0o!3l&V%82x%knLzdh<_>ew2<5Jo51aoElMIt(tWr1pf z;wj!eByl3pXBE5G_4QkCR#JOmVpKS!HXQM7P27VOCqeNq1*Yk|dJtHUnMW zI;}}br2P%lH3KgG)9vZE>A|XDGI>QxS}WV=J4&y7SJhLlqfv9gn}FTfFfmW7K>cX= z*YgS%t0TIbl!-T_Q2gZeR)-C>_g{IeUp*%yxPyPmw4KVK|0U3y9o-tem&sBKFop22 z<=+9ClVSE_Fg0${569c~ZP|3A0^J%t1`i%Kj5Z8>z8SX6e)Tkw(uZ0bkAM~(kMly# zx;Th%*1zkW<~>KWHCYxFrCM8Kori<9KFm+x=Sd=-$KcyvO&;vDuwABJ^%XC(HFeB- z4Y@yU)8d$7n+&i4(y1Qu7bQ6Ru-xl>`Qf}&WUTykI5&2!rnIO4Hk7D3UpXVCz&V6p z8<}W2($KX_H~dK9m~_l7cx#$Wv`dVs>G>t5OT^vkYL4vtL08{Iae!ia$efW1j$jib zDx#HB_gC}W4qYG70H6IaR@!LEq+~b; zB@ysBG`5wqWQO0ik7Cfo_{LxlSCbv}y8jf(->&Abfd3Sv&NNnx(ceF9Y2TVycBhf9Lt-FnrccR3J8;{?h z`3Y1#Rh47+q*Pm}i5}=4o!z(5X$<2Bb@gzvA0wt`-$X~P`avB9HTFdN5zJ-UZtrq8 zcdP|dPGtA?AGaJi+_KFKtHJN(c|K`aT=!)AvzgDx&{z3Ilr$X&!Ey~FRb_pkcdO35 zz1$%Q@^w?4u+vReRN4A1(NyD>rB%gY2%L zexre$yAkqES1wvwp`vpo&x3eVk_2%+!YRP=isP$7WrguTbBL#HpI_>WJmvT=GgB8z z8%3QgMOu?>gfbXRq3OOr*dLEf6H6!u_>yVbSLJ=LR@CdL;I*Next!qHw!FODkwwJ( z9)cz!ocQB1q0>&U?l;LiQDfX zCu9SdG#vY#nWv<*dx|TrKCR*-bixHvSXukVro8%>rkLi_lP^FRi2g{mhR7 z3)Us*x!k;8xBXT=`a)x>&XNqaLT$N+K5Ey`i)W^-4#ulht445#`>azfee02>apRUr zIfUQiSz4(CX)9R6HCtEHn%3HwC0L>{$g@X${W7o0zX*HSgB{el?pV`yJcvx&t-DdiP4Y;{z6>Ak{NPdxUm9^4+uac7Uz#Ppr_p)K-%8UC&vuigHi)LK;X`al=EgGVi z*cFREQSNg46^FSMjP|RbS=sdBkbY$s+Kw#t-9$<}A8Ub8tG>q4%%QT;iM(cZNXhCV z?$;zgVWdujtox=_|2$KP%ZjQT{xplD)`V#^2H#>*a8G*tAxRKa-p+x9zJQI6qeNsV$zG-UrWk zv5ti(2BQRH(MP?biNPo*aYtT8fkJHaYP=3)cA0pP|O zX3@@w&HhFZLP$y;ps}mpeeV(#T3?zpH5@QH-zObp@0PRaN~MarfAS)-OYpj%+|sam zwPxGw^x})uOSr>T`{lmcs_?>hG>ggA)wHLd@4mvaY94!nku6SgJu8m7UL)mS9=tsn0`PV<6^H)L!(VKpol(yB=d_c zQQE^P5|Gt6a#RKJ_{P{Fy1)KOqH^BZ$g8m^xLnO3s;lT`|GcyTW;0)p+I8@?UM1S^ zXy#FC`c|CO*R$UUhLmv2&UbRy_P-#?&zED<@BQMDP{3^j<0pF##p2^XQ!y3qyLrJJ zLyT$xyjOt#BTUPY1VZ zK`ExJ296#-eBA=}kh;X=&wW|-Qg)vUn)=^hd*Tljw^ItWHTkb~AUOM^+?csCr7h4;v0FZy4sa)cfl={K*(UlV1?MTJ}XArS=Tj@J%yH;ExN zWV<95&eO)wPo2lD+}V81p%50A>W>%nSLlj9YL;z6y>-s2p=0-;{M}GRjEvyuk7Zm) zz0sQkEspI$M}_MBLvNfET(LR)m^N*>vJh?M`Ak+F?j2_uN8@T=D~#=$4hn?r`={np z=+wulO2P&FhZBZ0whIRb^ww8J@fp?E`9Z#d!Dd9V1ebRm<*CvF)IYm{GBp7AOviq# zhZS#4XpH<*eHLcQM~fYz5(mh-9TORqCp zM}9DkTV+-Q^DK3e8_pbuX*C2)7=EVOtAeOsY`oalsyHV}NO!7)`g$)#DrgZ6u2%w z2<_y`5M590A4(&3UK`8r+wxIdP)|!rVrmJaosb|ho&c3N!(Sz0-u83j))Nix%}Xo| z5S2YkW7mzJtkFd!=u%Cz1Hsc$c#UNNxn(cA>gCb~pC;8u_0LERa=`P|`){NYo<0m8 zt)3TjVomDur{nA&7-Nm=yg>ssxjj;ui?meArVfcodzU4r=>vOgT;G27>7WQYguph9 zz5B&UXKavlEa%-lI@T$#Eiy%C-MGvODxo$#Uw14rCI*N~svP`9&n}2A#Yoxdy1U`S zuT)UsG^F8kp)v;p<^n6N+mwp1*UV`j#`*25!d}mY-*p+aD!L`>>KCQKw7+^R&e}Ao z>^(AZ1Ok`Ba>_%dl)B|d^m3hL#qFM7JWe@Wo31%$<^{|(vzfR86UUi-rnrld^zIfvj3&A!{uB@;UW5OMAa5`&lkyz8+*wZ$1XGk4ZF=FfZgZ3APvI8H0^4v*_LBMeiGbW({-BY z#}0DtI3%4fTc)Vz&M}?LFdL1LlT%uCluZ2;1injK-I8z!Cp=3EqGfeKiqXV06|%=wiq5Tq^grM_L*W? zxLi=ANh^=l=*oue3Wfu)rpE7{M$M|eMN%M(0dEnXw}P-GlZ7e{f{}VgL45+q3>; z8-S^AY7Wykyn(3(XKR3@W!CE>3PMrO)ab&KxcpU1#W)sCNL|!5zoV5DX@0Cx-_-UC zd@(sC`*N|(^AIbe=)H~BLp1q>uQO4GVK2GNA6x71tzefIZ%zk6qr#^j;UPxI*fq^d zKk^!SN(fDT?Y0G5$t;Jc#=5*bBaVcs#xxzzXADC|iG(DT45fHvqTO}*V>!q_jA;)P z$@{E{0$&EdK>g|w%7A;?>=eyNHL%g009m426XD2bTIz;-RrySVip@(WteMD&?m*Y# zbt}R5B(fbIkEWhDRgIMBw+g%oC;6I@Z;!?OI7uFw36lL(IU4c-^yn3n)J<+E-8-vI zZIAb9T~Z=Y{i^ym_r&@7k0CJ{A{D-VhvMerNwBw?59<&H3}X=cfqF|L%;+fWXk{&1 zSy)v?WwnC7WL@zTKQ#sP!SR5?jd8T}Ia{W-Yb#0VrkT~c1L2;L@m!Tec6j1w;oOt( z0SLDr@$KqJ$J%;p$FnOz=SMP@f(%=S2$zXCn#*}70}aY(?{0DUty>1Yhxav@=A@ozO2?=LdMU!$}#DJB~7Ab7n6z|J+Twgb>Y;uc1A;Wn|)Sf)a!G)k zyZ}6aqm=fq8k4`HNw(`lA{rO7XZ#u|u0nJVIr`^C#M3H2KYDxO3ym-Sj#lvga%WBV zcUOj~xbx&v6eqzeww|~}5coqd));&|Ea5vES=#20e7Y*R|186PmQDQ+opWFOJCdq9 zFVG@hv>6oHIK#apME>mVh)AK|%~)^b%unLVQM31F8I?n781NAtPzpgGyzGNa<-bW{hp>TJ+ zc?KCUvFy_S7_@O_epk2P3E;RZL43LRwC6JkvrvFDf76Eihukj*V^QNfc&A6`fRmd_ z>xaxLj-I}(5`Bm`;kzvbzMC-DdwdIo2#cq-zGK#%M3QQ;;85CSbDi}E$7Ng&wx}C2 z)FCU}3=O^c9qfUuRVD-DlGZt2zvzE#8e*rm>k+$(y_9QaUX870+87LEY|fr9-lcbz zU}_H3if?w>dx|%h6E$*OSQ3ji%-~y#HoijBk&0jX0W9KkmvXP84A~nX+`*7RZJIWm zwwd+WjI-)CN{{@9uf$*Wr+kT~Jlf7LDX2qHZC_ua8;VlBd*w zWw0Ffdg_f7=cy*VTYGye)DhU*p@@?rQ?>cJeFLOm0wDFEDJ5INi;lW+7wxxd%4>(Y zC!U;iuw_5&>&gr5hwx|LBJM;S#Q0ye14i*kR+fdbx83jQh2p@9{57Mw4>fh$kPd8% zR*#RVsz+IiR@gdg?GMP2$@9-x$!SF~LOEo%nWc8*Dym}@dMP6h(Lxd zzW%-Zhfk5}wcaR-9o%o3vGAb?8slgICSyxk-OaIC2c4uvCYzS@mh3xcAp<)ahh>;i zL@QJ^YrK63j|1BRW>QqsORIdXWlCyIxxIZoFU?i2k_q7ZxPSW(pTm#W=Y;DpQv`1QQ3((DMs3gs}19N9v2MP>3X6%IiLhZ#}Q-E4Pdz7U)>4vw^BncJHI14 zGs#Bq0}D&j;e0j(E2CFEvz}NdRmPImk@UVd502QULo9+KX133Q_qVNrUC9WCfC^}R zL#JGz^$O#hW7;>?{9-;o-ee?6v3+F7qoyIsXtO(`#1RKKk87VRVQGd!Hp`< zf`D13*D%qjXHua-gcjFi8^UU)sbM$IjebA=JSV=N{Ox|h0fhc25a>a?)(?(aVqqW9 za8}hvGRb8vCsz&qkRbeW8~6>1`B%sBKXwJ4ody=(s7hcBDP9#hFdnbF&;qXURnA&} z7zo$cr?R+n=aM|6Xia{`jXA2%#`z zk+#Gd08|Jq+<*9x|F+<23Toe}OW(6>3(>7$c>WzNF%{I1ixF^L!Da^{ z1|h=mk>>6&UsG!piXv^>ESHNlD=<^Ok>1>whbD41irKoz7BS5S(CM6ZJnT1+IgU)# z!u39DQox%j*!%VLo*=c!j!GB=@gYMJP;urPYG+DIgT$-A<}2f zn=Z-H`onZ-e6fk73}g|yD|atDF^G~u=pCpvqEU zX3=|)ymk|PU!O*%Qacas#sxswNDAobSbn(Yk(Ja8&bxb1Hg#(lP4p) zuBviW73Zz$7n?i$fNjb&*BqyphK4GsqV^Wu1Bsj$V6>b>?c?^LM5QotodTj(k?Bxt$j@De9I`^{yqvtkTe+mGJN-J(V$XP+rOi{G zqUY`I*2#QXMY_1Ikfw)E&SMRcK0CXv6gs{|4K6k9n(i(3y-YiyGhZYLzim#kq@seN zZpcvFtIQvE$*-FK-G-y+DVzU(0n$N9gZPl6DwkirjLHk=(}toERQN(8_VaZ8dhAeG zZ6cjoCbVJqYzWfMZEo@bSn&Q+A^Dfo1^6rfoVWz&e3FOF3L^R1sJxCMs6?7XNxSNR!wsS=nBopPt5L zXTaW}4ZJpi%QxVS>*f7G@93NqD>eIt6shahOtJ9jy70YkYLWp%EDgqN$GDwffy!AN zEb`GcUHUiA{a+@j!$74kQh;t~pz~m$8IM1fZuu-#ZO_a}^5vwZc5cfxQ(2x7Sl6~Z_V1N%#N2T04 z>>K;@n%F$RDO-K)zbyBEEBpTkkH3uN{%po_|76F`?DzCr4h(J+WpWNK{j_hkKpjyd zkcQ*Kw_zAF=Z8M2+fDdps@Tr`asgrQzKG)=73q0CTte>j!ChHp^ov2{SKX*|0m+S~ zw$XK`w0iKy@CD;6+{^O5^SM-Y%?@W+kH7)=VX@s#(ea*&AWEEZ z_6^5fN8;~jZ)U5fc#1hzUWg(3F`IjaS{iqzXya3SF&LB*{r@2CA{he_4$cweftIFVJk^7X>VxqgP@8 zOJ_m5My^l}Hp?$$mf1i~Y3AQ8ogHN506OL`OXokR?$Q1q-~Y9x`%`b}kOy|SR=U;d z*4OT(pW4uOrKkI*el&bPw{u;4vW94Z_gMOa%PE%OyOZO7rIAgn)YS&k5o9QLA<1)* zy~$@#!2_@|H7R=d;Tk#NH`bz*E(IGuch3jPP>=P8DdZ{9p035}gF+xDrwT=}y5O2k z3it15fo`5JpyoLectardshXIR!IE%hiElz>HZkgcj3O&Zha$S$&X)q?%E8i>*ll3Z z#Vc;Be^)ua5hW7BMr$9t0Gq)daeN539iqqDJuK$fRrX&-svqS^9L-*o<*i+T)_Ko* z>npT4mpfMlfQUq`4VSRf#*#E$_;sC^wtZ$6J@zxidwD9jnQv4$ zvM2P5>pZU2BZK$1&VeK9aRXBc)*0a$<*JhQN&hJ*{7qP>T`BSy%v7&kN%7dny7qBn zr52qRzVY8#Y4nzLSvZX;+170h$In7WpezKs+fs_|m!j z`sDq_+DaB{xqmll0lzEYorKkYc$Dn)9nHl$wQ=n6=tjcy|uHy!7*gP1C1z zSL~2_@5{t&qJS4!1rjtd-JUr)h`$_GA9>igyY}Qf=ata#GomCBn5hg0XSno+p1{u8 zN&7i>v^4jI(RZ|=gryNiBbr~V`P-=?4|W~ni2m6bNzz3ew$4HUmnq)efo~J$FrJ#c!>qXQ5>g- z%gxfb6{Pf~}XJw(l7A1G3MejuQP3tiU7DW+Wc1`7MO6|$zr5q&e}4RdwKDebi< z5V!32BR-bFK_MSm^KUInG;253)i)q@|G~=sXGnqxxpeU0ePRmw7io3ekpFWOkK zY4yEmcGK%TN6$~gZ~Hkr{+NPttGZgx^Db+Wx?ix!8q0!vY1yvJ^3&7(27iC32Ut6@ zepB3FF)yIA8vPxu#sa<9W#@4BJ6f%d-58R4=j7!5noh)6{Y#N=gS{f}p!Di*K^@kO zyGMRgpfCC^B617Jos$bYnCsxVVUX+b@ZsehCcul!V*7ssT>i#W`~YWrjK9(Awlrvq z90Ve#K?LoKc3tU7FZ~^fj~#?B`5AUhXsgu#bDJ36-@U_u-)rEbo&uOT?*ZT8j*Y&5 zj>pKUB&c}oZ4UH}`?{~{slTHQM(9ZAF6cG_`p!kCGN83A19%OZjH#}jj1LTdM{|3d zv1sB;qc!5U&}Mhc42|FB9jYmkz7hg_F&wLPd>4F}%`a|w5>igZXMaaKSE|--94S7}Z1Bgu+M-Bb_R!vb{p2hT zFpVCknqo%8U&{N{SV!4HN9AXYKe;geXhh}~{yRAD|Eu5qQo{c^l<+^s8#Hi&ekHYm zdk*NyGbgLpSG}(Rev&S4FUx^1wT&o+hBx557%99g)Bpr>bWlAzTQiKvJ@Glb z`^dH^#p7!Nf$dv@785;`tnGQGF<9CJ5^e3(KygoT^^;OZDF?^m!nk+ak@A}0z z*;}8(1@8}xXz0$!VsE~beduhrrq!Y{b$2Z6?GA~{1;ce1!HSj?)9f2TVw->XOlm+L z-q*`nOJO)JynX4D%xF<7ecZ2$HdRv_G|HR0DE##JVS(3N6o zujZP3&Ri?_z!f;n{-@>iUsOZmhQJgR+yG6uv{wGd=~%`Emy`0mnO8)eFSOlyX7^-9 zZqrtH_Y6W-?AH!Fx)l+V2h>|n(pQauXKY{E;^*Dfk8{mdLKUV|-GC*mCBqyDO)kT+ zgtD=f(T;q~P*OX#Pj#^;Ok%;eO%_D5Ls0~T2e=>-79j9Dg_`lfu4zQ@QY~4ad z6v=`hIY~y6im^+U1>WueyPe^X!qg!=B>(*?K%`R`Q_6*V6Ar$b)7oX(8RroLCwb;oOGW ztD553$y&=fuKnZq#`qteiRxCX;Yv7{4}P*b^p<*%r_^4e%LGl%IPq`GB#)OQvwa4n zfb)T3{7Z1Vq3=Uj!g-h4x(B&)Ood;@%Q)iOzO|1KMEZnooECJ0h0$AKET*P<@FX?n z67+54^M<*8saqef-ll@mHK~OV2NuQ%3mb)=-zVC|L*6`dmbqBYT=`j_drWEy2%>sz z>2~*`^uM-NJq&mM z|JRm@{Z-n_FL9~=g-4Wf*FR1XSa%14%A3=j08Z!*U{{~bV{Dq-QM9O@3i z7ZlSfNAoMz5VBS6^VzdiK_Jj(z`N-L7#P6d%-3a6;a=1dyMA{p^>Y}&;^C9{dyi2I z^R@7t+2>18al#36cKlDe8rRUzy4t=(-Q@8NnM;rsV}bw32BU3-pQc2}b@&U$fWL_| zh)2KB>#T2h4~K8YVuvR?=FX1ZOX7yhvD%TT1AN)KWpAY{D9eB}FX5acb)vxDp%1u~ z_LrdB0ME#Lco90rDwzNiKUrBn((>M**ay@yAEkb5&4a+1w-1Y(9Nro%Croqp(4!$K z-V$t}h^s1?mP{LGeuG`D<(mjxVHVCzTM*V{MMQgoML-GH+NgwSDW|zqQG)Uf zsgv#{hzz*93dZ-_C}%!!;nl>vs=5nd;A2M!#SG>#Iutf2`V8%aE-C5{C&pjZJ`-n; z!wW$mTi&)CC1!PUdmF#EfGpSeuPx_(;qSJmXK<^-;NGw6E*HW?%UXdyiZMX{^Kwhw z{aO86|CRFgU4i|Y&Va6z&B~>;+yKPRuPZc_0sJb?x|`zkUq~`cC$bfL%$D`2{eOy_ z_h+0PEie6EY~gPuv#%7uzd`RmpnLzWJp8k-D+Tb+rU3qeuzpbh&3;D#EYGD?E$bsX zWRCJ{2`nR=cK-!-Y00vYhNhSv#Js&z-WprRb^%UyFh-se=YNanKN$@1Q1-Ze!vU|y zXoBu*;V8+3f$!jvH%Kn$NJZ5_M(3H|pzaDIzs$guJ-nra55vA`86OJQ`Sl=Z%bVP#bnelHhJ(mgWiV ztjLrGgnS4cvc;3TkIKEWhMhM03@(Nzm|78u^WqV6&~wJ-blLt8j}_bEka9{ebN5|o zh1V@Ni<5f|s4YgaBnD!+q6KuqkzVhuLd)9dM>^Hx0H#&%bsHT2A||IUVuZ#1-LGj?JA&V?yuSJo!r~IO;WZqxwng7P)RdVQGE13KVl0(bLK(OXfKz^7B?`c2; ze+Bh2uCE09R`1-3yeNYxX_ovI=Vhjm1OBgT8m4}^`Zyv~R6$dN+R2XjWUtiU4vh=c z^lj%o^at(FIWE3sXD?%2;k{W3!!_c{Hs+kW-fBzaALLUS$f8o#7e_$-fIrz9-L@p2 zytv<%Kcj0S57n)167Wn$B>X)js{<^>!9%bKyrv`13sFeIes}jxH(Z5h*Zyd({;o~F z?M#WWrdV>uSPkL@O{-{A8L(g>Nq!%tbm0^v%(pM-a_$O~UMymMIB-NJ#8zDZzgdpp zeIBA<&t|OWZb-iQ1qYMPfB zwH{e(`n_)Y8zNXuPq3_{Tk(!g(~F!7lj-12V+$Ws`$U7c(MToLl0oKXxlPVB)S;mR z@1@a#nfWm>g_NgGaF?>5JRqs&b4N*E*4%DQKSeT;a=q8Y})Y81o8W{!hf0KROryNcp24 zdGFCB=;++{d|DIsR(OsRuxx0d+Y`dh-0Lp3@`UTjb_gpjL3uu}IhK%&v8G$6CaOWY|baG7ETF^5m3C>8jH1&yq4pOqy)tX#-xz>9z;wVfU z%Nmbj3MkhBYIw!1@s48X*zxu$X6#N*Iq%$;iXeH^GA>EZrRI7xZbq=hUb)QnX+KeQ zRRy-DO)cZD$j}IxFL%#oWZh`tunU=ud_rG@QQZ7SyqYj(LtJm zXj+&(Q!i>&_ZStvI$Mi-M)YnvlEPVd)6lzTq!D$QM1 z4wjXT!8lXw*GIVYxgz#CL+g7zjax9ozkoMa#px5@EWsEQ-DFJ(Kg@41qRJ|VmvDbx zR+OP@#NW+etZTQDPM&+W&*^0ba5Uk~-}`YauTqWvgP@#$(PMGth4`Q6g>aBxI#gOt z+CkpEON~b&&glJDgPt>nU!LZ&4$b&M{i5!LPgH<4YRE0X)!@GYos{0j6`RsUAKLd47aRF5;ugnC&hUvn7!JdN_xFF{Rf!kehGXCjv%itMhFCDYGff7bCiXZ8l3}H%TXsE#nLA6N{@>!3U#|F7cKTDCmbj4UPE?C)NrR2P` zB-N6AfJ3>vOjaMMS$gA^@?2=dL7uaH%Ls#U>RbVFXqVthA}=pzQxrtrH~-x}ADz{H{@OHKrnet1P>N87RIt@HY)fqQw` z3IH;{UpvxxhFZ%LSSoRq&mf)Z2fM^+s#p8Gt{!f2RK3Yu$PM_R}nbSzMQ#>O0tR<}s zg!yK!{( zin(>p$fA7-0^^@f_7?r>T_qn`y+DBQHg@OO9+^5^<>7a>BRCP;1;60XD`4-k4Eu2d z`**iZkAyG8$M0TLe*$7B-a&V_H7=gYimu`VQD$2ffV-x7Gzm7iM+Q{wocJ*|R*Oe& zRutiGU?Twgf!N3-=m-9Z!E&V|UvG`s$jp`>>vF2&|KPJ2q_HTJvyS+lsVY8gu!WFjGPV_tDm{@B4xN&mE?Djb4Cx7`) z^h>v4Rj>DW_D#2Xg07NOF3N%Q>9*{fr{eMJOnUE?&uY_p&z`pmb!9az&^{TOa}!hS zvuj(Yk)xSbn}woT^t&-yt?$9H#i2K`#`WU93&7Xp)m!UniP)vR4<5_$bJTqIXgpm1 zn?It+ps4>$SUCE=1&gqglhS@}tgLw*%f9Fo9tec5w86Xo&*{8h~a+wtrL_k> zC7P{ME)F?@q8j{i5l^MgX9uE)93YmX4_{g(hzxwTBQY!fm_r#nC=jy%xASO!D$D_M z%cfL=s*F?J6WZ^SyF+ivj2)NtAV1qFy8wrxQ*OKHWeZjF=*M?t&d(IS?3L5V)lDe_^wxvs3eP&;cdtADv1nFB|s7eJ^ZIdwfW5Ly+QrCDV z>N!NGduv!)ZOFpMQ#=6u9?up!H`i6U7pAEQg_GcYsRv89K2v!TBGm%KzqFLEGy;p@ zZ+?zn44Xe5e^g5UmmKgaJ0oGab*&#Rl3q)fJ@nXp1NrdqI@Q4(+H1UJWkPIjkf z;Rl+TUoCby0(P|q!n3uE;ZbCBU3AWb$LX~bY#tk8rcTh_Ef|uZyDuPQc;L$2XD>p` z-YFqzdRb{;x%5j2ri} z!6XjV=6ty^3}H6SvkxZjWTj(w$h#(pv*m9LILD_Z%Fc~35;ScI?{Ow)jTt*vMRK;A zP*nul1o;9Ht4ivo_JkErCYAjZMZf9wZ3$*`Xl~7_Jw4RueuD@`adE+2GE%rit)lre6mgO*} zG*?s&IcR{rWyKk_;cIDxeS3i9-n!PKt=@R}Q$q$n!o!iy1tB$d;ltQey)J>!;+HQ> zSm~Ar$}@Ys1p_qWUZe8hdS?a`@&!_pyRKEcP3uZ??!0S*r^CggWmjU9F zU#nGDaFiPGT;IqwWMi^6q7DtQk%Edd;;xo67?dYf40#6*-U4f1uZ$q)+NZ#ph#!H#?pz6Tl5H$Y!pvh*#D%79!nEu7mTuGx;T`= z01kgk5}f)<&7_0$-s}R|B#CKfwwI}@i5X$!*a_c+6Zw0o8v#Llc$^61c-kPxhAKGW zmw4`3s%^X}DCxFcLsnM#dD>`qyhQgPEY)NnCe9@{1-U$N%74~mH0I+)>U2~d6*u8i z|5m5rc;V}+axO4K3(kP~dULKYtG;Cg4VqMX4er+GPcY0| z^PdtvwsHPX2u%9C6@RrMI+Bi;6Q*?08dktw;<)z-U5#2YtS|uAIi|UCAYA0$?8Oa4 zhlzSOlS66vIf{s?19yR@+d?f6O=Fwq@95N}^#Ld2LqWzWdH!+07dJ;dUmFMJn)6wb zD$i6AnZ8^Hr9@O0b6xt<+cLfHi}{q)^806ssjW=(SV?5XcXX`239jTE8sp4IVcN*j z9NaZFX_KYl`PhEnJiEQOgE<7(m;><2x4Hn1PoR~S^v1|J6Gb3ks zd#7RWQo{C}Q2H@q`cPKkhxo!n6ByQ3wyX6%FXN9a#7}0=O@RkZKk(%c02nV^lKB7K*`&r6GJ-+p=UU|i9&yrGdI@@FsIP!Qw#26B} zubSwn=6Md*p*FJKdp);=T20Gqa>Cc#j)vwfbY z%s{JjIzXz}6E^cTTV`odzI7PSnU`Hq9EPn5PP~66qfnc!H?qfa-!k7s)OrsGzK=Je zp($pA6>|IDQ$f_2L_Kz@Lu%HH@#TwJVf2j$=+k?KA~4SG$)2`UJcF z&L@qizI~K8aB?ugm?Ofg!z)aGW{T3_T)}1v$;P0zVBNvQd!rZa5s(jZj%lkHpbBOL zO-xi~e?)M`g)T~k4&N(X%@sG^!CjLR(dbL$i`k=8QsA}|G-PrezEg6D|0#zk`#8P1 zvwsgyF886JX<0le)}X!KcX=1A{=||#<>LB83Axpyfmaw`aT{F?$)dA*$0)CdhOQW= z&{7xady=m=m1gy6rkF?pwYq>1Cy4Tmrw-i9su0K*X&c7Si!PJC4`Md5!`BoO>dsvQ z%g6P_pK9~&ud6yZz&Q-WyFQU*g*JDrKg%O5^#}@sv`^sTYE9(0G(EIVbD*<>V0Dr~H^LO^_KZ^GF&FAQ_KekE!Nd(|eZjE$CJOL%6 z^!q&JpB`$AKYDeaARBe!^8nI}P2tHwo=S=oYP=KPib6Zf#(*{lYM647M!Yw2Ybz3( zP*N^++@ipNt|ngi+51aSwmE-ho_`%!DrjuoLR4$aUnT})Y0N&4g`pvNcVrvg1CI2YaPRv%f)ySbIX9JIv%*>xV3B=3NpyYBmd`)(^PyM3k zv+o?Lt~p0My?2wjTZaf!Yei@b6HQCoiMv5|3lCJPmj-Pm-qiTQiSpQSg;mY;#dxrA=qR*~eAAcIx$8VnK9mSM- zJ>jSfqg-RiM|?-NNAU~uL7CJ7Y!9oUV&)?&AEa**8+H$307UYc5f+<}Y&jBX`1T`1 zfecf}C5IWu(a_iQr1My$@lBj{@6xFJRFN)vQd~mB?+^P9a(2&)F_tb#6vU5^h;6`$ zo#Y+3Kdq}mV!{dxZOpVq)NiUNLW2;=DI<4^Ec3D{ z_iq?dey-~YG|nioSpD?eTG2x>vJPiYm2lEO3QK2_{|Ng#~w2h zhS(e{Ur-kqnd8iG9^V3e7#fti!6t^kk^Ho4V(61WUTOvV)2IGYB-8cD1>sqhoarFk zotZ!twr(U@rA)3=g(VN%%myK_X&>Lty9QOOVR=d7!q7sSQg^ zchdsQHKS|sIpcjr0{R;|o}I?E4~g5prar-$uNIBm%8_A$h@22#W9uhIFn-mRK#0=O zuv(W2LVC-2l0EP!aV2Y3^R3RZ?vn!%vH1<;#DU+)78NsG{OMPUIlq+KkIn6i%uP1A zShTZaQzp326uulZa}DXq@zJzQ73bK`EhYD!-hn>YxdfF4Ac1cNr!T&<>pE>ZGd^W_ zZ&tJMY`HI?zL-Nm_@F$RI!2AS_U6smMw6;|?+O`)pz=AOa$6c*2(6OtH0j@KmASll0HbL7U5(hjnp zC5|`E!!eEnXJ(;GM>n(1l4L~_P=l9=Z0;?G#)0q6#mshu9Lh;Yg#ih^jc*uF&bVvje`W|IJ7S9>%Cc#X!=yZhU6pM65}44OCX$y{6Bqz>Zg%SATg zPtIX~h;rk@x@4d2x8)Vnh>L-ErT%QY}9kTBIs^@j8u#ziGnSE0a*A5gym> z3{QJyiR)g}jILsH31W_q8!Tvz;^1dyF!?C4A;qr6B%wwkD(C4*j?%}IyiRqxd;+1A zqK=q534LaW$}X9!FU!C;|7xAXG&A~}hVM^KTd8|leB9A{FuyFvD> zzoQmsJ}d_MdIj|zggtuuEUG<$@X*4(WE0g7>h@9k>E;HfW z+hHLP^Lhm;>bUa}y|do9lbIYf@eX_Dv9%Y{XLX5tDHVS1gL8F}mi8h12_{+b9n+Ci zry9b|($M_`-H@k=x33c#w-fYL#|l!YYG$7F(w327$K5=6pfHv0 z+<<+4zR!H_d5$u1XxR=OsYzwcaZ_&@Q6RZa6xqeK$yrrM#*5H4yE?e~YCB>0y}mVB z)clO|UY^SNqDjeUqRizz187x;9TeqW|nr)tg?VlnbA|n>e~~g zk)QO3)Mt6U@!Zd3p@NiQjWY6IFHG1ZwCjv^xFIe@#gC#%JQF()?Xli3qP-KgiP zpY}ER;3Gm>l$umy>VTveKrL6fYj>E9&nG#eep7fOW}h{Ww3gL;QD7T8U<~TZwOZ6n z+-tWC(GRGflPp)Y>r&6zS@a?cbaIR%rYmpNSPRN%i`9Kw9)*hSz=J1P$fRm1Lkh~= zGXVmD!3=dP3N3 z@G7QY(4Tb17Z+!(tkr)d5EfO;P&|QikD$Eu&s;l+HpbFNq4|)g!?RGdcg~r|loYOQ zzP5jV!{ko38uKW?LH>5hG%p_2TV28J)dT3m=k(FG;RecM?0o*Y(C4(63^?;HB)7+a zP@Tp9(BODwOuRBC{^w!G??9D5v-dG`z1sg%_Pr@H+DiNRmHol?=rs+om)?umzN*bP zNW(MJ$JDe$${ce;YooUF$4=6(xnwiw)8@zOT=9KJ}gktTF0?0 zt1o95@3iKpd@CmgAB*gg3h`L2j5^>7z?3K%sWZM-AbgAXUBK+AP!@Gd$c6 z)?T#Rj6wY@pYp@?!lNRz#xM{N6>q~L4X+mRYdkCHP{gAQ7Ham^-kV}0T<=k|(<>z$ zJp?(s4etZv^c2ekwYP#;>69Xk?>Wha7h?(U53HuF*=hDgN}V}vRIBrX78lm zILy9wg$r&@Zcog&P$uJYYWxZ3Hug<+QEtkZ4`Larl&$QyR`@dU#02>A>fJKnpMV^x z!92nbna^`lkzL}X<>5xk_D0Ds(ss2}0$kH%7l$y6nOcWUVauZP5b}g)hj|Il`*c5h zd#M@AP1yAN%be4U%2UKejiI3^46dr4-lp4S@jv9>Q@`!xVvL>=SjDZFf_#HGLAq%x zfay@Fr7)`2ta+ar<_!(5RhYY1VW9mu>Y}Jyb+}I7oT5Z4OVBxV#i(MDlb%tvqmxa= z?`>IXN{P0i{EXr#N?W68!eDqP*m@Q;72H}T-jhq!@7^6q;nuLFswW&ylc|rlY8Hm| z&9W{dm-aTor%bRXE{>j4WkQ^}sIp36I8Toa!>T;=2<*n@j>uztikh}_MB=Swf$^qm zBuIGd60|x{6{?FKhGO_|cx*8OZLES}wm!;#%I!nrTQr5ysmJ=rlE z%3IzQfz0)-)#UP}hqRZVTy+H#l|jgp3Bg+ERCtMn42#wC<|M(4#So}7^sySwx18n$jO zastJ5Jlb@W#0zkNX-bVLdmE9YrXkVWCSh+i#G@#SVqm#_(z6a&T!mScEBoBUWtLQ!VO`eFBv`9&;Z$t}cDeOTSis5wMerDoUJHv{8*~8>4??#yV z5~b>_SP(0ovJR30L96v*ysu)wi<}QFM^Ib;86%nz?$;uEK@uh~|2#@Pyb7iq5vJ4U z?g;~hoGh4PHXO3JJ~ZZ%cRt7u8?n~Vg-8*qppct=>yZ1B-Le)jepY#{r<${f#n?Xn z`NwNQqU&9YUHhqD-^vg*(2CY%FTVXIXj0L1nA`OVqA56zFY1Z^oK168+-kQ`FT8t^ z!Akihk>)@ikmX0#2dh-GIllzera$tx4GS81xXjK6gpggg{F~F`_cy33D#riFg!fN= ze?`Ukx9Zvduc;UnpNwjd1WJe*ODCsta(2sVRs|?~q1(#*Sq~sqI6W zBxc+Wa^zG@KFgU`Hc@m)>x`Brehs7Y*J@4AyM5S_b7KJCq6>R`O^pBah4As*B`8%i znf*N1VxEioJ$tz3Y-x_0`P3~hs!eX%A!TmD^3RmiqZ>^$U&e=945&lL2EvxD%bTMV z;}*N*MO%RW$I;ErB;S4L;;&S%=vBF__R){aXc$_V$?yJBPP`uoU!g)T~*$%8XeW=R?a|CfA`l>m* zFT{=Ep1BMgS#N&Y0KuFX0VwDWVWMDvK|VXLP~95{A!cS=CL(uxDJX5lW7@E-f#rDC_;0Sp2dLnxPecRNjGTM~egnB-#X!fVBD%+T%SyKjp zB#;9+pFFW+y*vYS=^24*HBHIj;Pq(XMW9Z>qpe*kokF>6uRe*9Ms;wXs>~vU8SarBJ}Q2&YLj*s0zcz}k& zJ}ZxJcOcF#nyns)0d^xy&}mHd(+b?BZ)*K2l$Hq{XD$?rI&1p0&lzMV6JQ4<{3B&4 zw(hVQzUWvnL6AbS@$)kkCbGMw2nz0*r_9BqEIyQ#Ve%wU>JWiG*MAEp^l~QkqS65v zc-tO*PbU9|ud58te=8f&RfgxEC&Tl*jgyBT!zUi7)s5=!KS2=l`RFB0D9zoIAbQv7 zB@3^iM-7pZ;i_bu_+x02AOUNTJ3BHQB1Vw3P#C8cKKAIOpqH$ObTrpgc(Qa4zs(g>!_fEy`U?|IezjRbD+h%YoyT9K|ww#ot2|WwCB-k2 z(NsNCgS>ZP^i%9T)kXKVvK?al_F$ZWbw7X$T4REI;ATaB%u20)(;Xq9Aadi9k z`P~Zv-+q9ayFzF=i;woq2{ZG;&e5$8x-K_Eif0i7O@Cl@QFIBK5bi7(1uAM7{YaUi z8C}&Q*s5_`3>Wg&5s)cY`7#EYVou{Nw58y3vhEa@r$Y$!iRn%jcpg_ zxe}M4TZ_I@Exm=kayhpz@N(F;1I!EvoH4>Tx+3Y|8JD2x`&*0WNQKpBH-pdUEeY{(ueXajd%%Q@Y3 za@|<_=nmNt@b=cAukaFjDr|}gHZ5^=($x@L&4R0i@ZTg0x_%{k;v&Dnj&V1ISNE&; zH0>H4x9_-0^|K8p(_Wl2ySOJ9rZ49|Qg^E?%6T^G|HS`weDt@?Z*b!6Fop>(ggN`I z7jGo@uroN9^}8a>swU@bUKk|bY@Z$ZvVRHMSAbQXGfIZW4cM2Zj3!&XMy$y9rlm)V zO21wFiDxbw|9fei!FN%YAd>hsEp?2!4X;m~|>~Qc`oB^9B1{(6zec05NFY$F1 zXjdz&q^2XfzX)8%M_FOi+7zcJ4)TaA>K@S)iA35|Ls-(AtLHc1^fsQs8uSXyk z*P1SHVLxs#kc3uk-ko{8eX=tuDDfre6IVDu!IKYEe<%CZkF0&!uFm>t_^w9cmx**W z5?3SfCy^7*!ry|Ai8cWDeFIPo_@&)y!jIAm&rq({Hjqn?(>pLcC)FO`=fM*Rl>67y zukWNKVL$|8XIW_*?NtHN*{44%DB<0%if?6S`JF_NsR~3stX8gmr0x3zcaAUA#T(kO z3R#GeNxTWTh)FqMrJ6a_R#o8$6Wa4@0QawT0Wn2xY|(Aa(d%2)imA8?+N{pHfVUI* zt6Z~C_`oQuBc=Y*<2=5bv!vmb$>SN?l%hfInwKDk6Log5kyxH@LO|L`*^;d_!{-_@ zQRO^xG*oG%n`!=v$v4b#hc%le2(Bd>wKIa#6$RR)?kgEBIeE99l8T9(zoR|d(>|RW z=)U=JS0$?=*FYzq6JH7Q^q$=DQuSd=@-L0tu9)DMVDnRyfh-5sOHlqxz^emPUUrQU z{#$eGIO90uVDqz^NxIF|6|TnNYCimBiTr;&4ylA3i_zl3W$QpBkcW6d|cNlMm;{JuCte#FaAvmGaby&OTuiv@~F zhrL{>1tbUQ8nBx)>Vh3nLSd(>ThBMVRD$8LrFRF2(%CN@8<-;UO7mu2dufn2?=rGf z!al5sseSj6)ctp&dej1Q>#uuR2qtu&rd)d+6FL>hY9c#CLMfSS4@|B1_>7;XyPh$#Ic-ul41$}aYmK$XRUdycQ0sp#BHxy)$p6BNEo|Gcbj zBdMu_hN^QjiNkVe@uS%75~(GCW?WC|5+n$@IDQ0s(E_yaFOE4&eUcZl0a063q*Utr zixBVmr7^JBA<-dG;sz81<&T05eSvj}U4p7)$^P0hckOrWcX1dJy88KF_P?v4`wjxG zrp^D1B@;+{whufn#9Q09VFoG>VHE07CdM~?p>iSuvQoxYx^KzCLAMw>L4mJ9Xv$d4 zdIXFgq?o)b!|Ze7bCyz7KJsWgu}7rUFj0?>=|Gq*-9^<$Du<%$t>TjQh0^lVU&Sf- zhot)&@AFyOj_GBh189`b;d@>^DnPc9&DM+|Xp)PE@M-A6*hAc<8yoVK{~DkpG#wPc zp#(DpK&caqp&o~IQ)ZF7WFDGg!^bk?=UFGVC*F0C+mz+53^UO@Nk89Jov?9nS9a=H zKL*2$yVqE3c9{A=OATr9_ex3Zc+;QMJ8eThRtxfspE zKmR!R=Zl#V{q#4!pC??nWRH2(mG47*H7Zwg<)`KN|IrHi9s~HhawRcq?3&Qxyy(gx zauC;><$wW}xQWT|n<`XAw9(ybWMJQy+NEU?Hqm5MeML$k3IQxMNe0aQp|W{=$6;xu z#@SRxeF^{ut*HC%S^S9;@4xGqnv(RIsfkX|k<)r)$A~BJXivbuH62B_J7(W#!JIK7 zsWfbv-l(G=k%x3#{9!pj5oR*xoIbBwW%{%Dg{ZeL*|VSI{3JPl8lNl4d9|Nh?Xp+c z>UA{+|J&yP+2t^*zWGZVTWft? zv+pg77bd7|ob0HqsNY)x0;nuXb}ww5EcH=Yq|9^;fdj<&rL7Sv8#g;ADvPAPv7wPI zDjORcD=Leq#VZSIB}-jB;H0Pe_Qrbp64ts-s4P#6ZEfWBtwk-&EG^9S&23S6P+6Yo zn;U+=3g;u>FDat3h#SAM)wc$YuXJtopX%#bywpb(5<>mCUygBWAMJGTns&dz9C6;d zT(d&*D3|H>`pSq^GmaC7C27e#^5hOKi~NWpUFrid)YMdeB_w}bM%P4K)EhgaYNey) z4s!3Y%8!%LH`hl;M`L5V+H>aTjawmE89N?dJ}V$I&1Z&PKNbc{XFgK>q{FRTm)QDF z#@}O_wc8puBdOK&J)94GV;WbX0YV>v;Ut!8nH7c;&A?4OkIqFbHf{@+_H{*M;)M8n zP*%Oi$x`fn>K(}dm-@nZJ3@5ki13s5FXxo9-%@&tt?g|t0j9sEFrC_J8nV{vF^_eu zPf+P!IPL%wYHmXUODCP#;tRH3*UHjKbZ*{C?aj88R;XK@5kXjoxt87vwpPcRuvT`( zZc|8MII=O&(b0KLKAvxTH#M8#!DI1DFVgupd5w|*0Re9bQI8at!3?Px(?!r1JlE!Y zM2mf%^3g{%kuE`V0s^=$Hu|Ift=}+lEv^qvW(<$@Xq6sEE>tT zlsL@P(iy$kkAOay`qW$Aib&4TMg~JAt)~;L}|DpoftY?t=&>pw~nHSSnT{s#*0r_jy2l- zmRL=Ylt@x$CPln6zA=pYfq{X>#+!`E-&oh=vDv_>QK(KrNCeO~9+xtO^2$%bp{a8I zgixEcr)_W8Xmq1Yc>K%XqlDcFW{(u2CnvCXt=as`-DykkQCQj9T7E7mK><_WqS7Uh zxs##To{I8qRUUH(Q~qi9lN_&Wl@kGC;Q2r);3glk5f53j)=`i@<0O9Y)ZklkoXAC^ z&vpK)p-s-_b!J680i4;H8O3Dr{guwnC|yZxCqd=Zr;^=?*9fZWVV={EO(esy`ee{6 zr4w;ED;AXMkRq;AJ+pNKw<)}MNh8o2eI#eVlf_m&h*eS|3EncQ@b&dAkByCGR1`~j z`@B9h2}H$f2KFz%q34LKq15)K|NT_kkUWWUYW?@#Q4nCfp!oy@^%Y&dE^2b?Xl5zZ z=Pv{JuEz59JjhF-PUKiWn=mMZvS3P~^UE|7%DxM_?f0uyuWOatud8#Ko=|!?IispK z;|vq%WZ^p87Ssf7eQEGe;*JP|nk<3UzfUtP1psv&E~tq4(I^dwj-3`S8S>ipF) zCe{(D-oRv=Ohsfv;N~R4!cSSXbG&g}hljHiuX%lAY!8wJ738~%{BomCZZCP>&x7jT z9C>exeeuP}nxrSZaK0cQL_& zObgfK=+LJ2?Y;_PXL&tl5w(*K522X{CJbYZC05&;qxo6plgc2Be#?xAWS%?jPY8gS z#M|~}SzkRjOV`}uL)O&c^%!o`fkG+08=sx+&L7{d6i6u*1^ZXaJMv;BN=pjRaqcmE zRGiy#PIs~~;aRy}jOlOpwKYNtDX1{04Udjc|48oup=A+vRj=WM#C|@gChqJ%dtuC^Sm)S3` zO=3@HgWshEB9lDbs^v~%(AmbnT@g2|LeG-onH9!L4*^D&3{8Ue5~Fz&*%<*}^&N*^ zV=3zv51(PSZ8y!+;fbs7zT?xjzxc`@7>WBt1l^SygPotea$`I(lDh8@)7@aPYDi>g zP<^$m?1TjEOv0Fxg2$*>uQBhR3>@6YKcx#?<6|&ICF~uykouPY#Bj_tx;;f)2rI9dwg76jjz5M)4 zl8&w5sUX$jIaw~7msxn;p;c(owlND6bQf#O=M`Xj#6ySGldV_I)x2&dUt%w7A;*os z<-E=3gs{YQ5IL&lf7Nbfi8Tf#uzEdhEikxRRJ&ReJL-J98`m~azQ`iv? zPk@(~os#JhHQgtT#h~@kLh7v@TNFI%0vZHK(!0v;KqpM!VGifpTVEcro` zUc%wrUH3oMqDj0mS(sU$oN!GAy^?wP)KOK^Qs?mD$M>V76;gF&?wpl^W0q5?fQX_{ zQF-Y?_kt%bg_p?un@Bk+Va`L$=N4_tM8OX*q?jbnUXOi*Re9jVw*$FnXqb04dMX)v zMdlvsI+m95@V}Tf)XyaCP#SBf^&s}M=4lUNNW4pSeg`8ASSZxS#lEH2y z6j9Etm2_UWyCNqqU+Q;hz30%kWl(8we^*s?+RNZ*V)6!4X$fJ z;@`Npr`n$+bfAQ%UxV2+R)I1Mo={PK!lefFhAfBSzZvu-QwdzV?UQ^sb$giOUVA60 zFzbCLiQcc6?y%#z>Ef@dpYW|+%8yDc#y!5Zji;7T=|`II`WML8iC`qhiJ|!>zxpCT zV(EwuOhaqL=efB#D3t+x*k-Lh^A0Tjd_H!3bAC^vQ+XyTu)6xymYs>Zt0tPGyd(;L zu_=PM<__{@Gwe%AYHWG!GC=FBjUxa#Vg1?t+SQ(tf|G-laQ2h+3hm8z2@}HQ4$Iv~M@NMXhT0*} z^mN+BrbZo-wHeHw z0;IrpteovL%^Au;>K$i;At znC|lXTsbpwE*-SA{#;c2($^$QF{hzE#l!&(PR^)s8|)Zv&PD(oWr-y2FHSZ3F_m8H?vR9D33SoxU?T14CcefEC7`Hci` z9}W@5E8gbDjM^vgK@gmFaxpD6C2-VPilWNd)qPz4DLi_z8n^QKB8?u&Qw(Lu3nKePZp->={@hc+GtOg zkDl5n;^2tCI$hOAlc))rqb7FtK3JM471cmvksQ{6N5)gb34>lR>F@-|rqw0K>FPEt z%|FJt>lV1xD$F4)$SHXE`X1Txf`nekOsGMe+kHHN(06uNb=h}4@sH$`kpo388L|~$ zA!qACUJurg-iuK2z!=H?05QIIeR{uIPCPX&NcD8|Zi5Bc`4=8$A@^6d+s{LVAo zjFgWuTo!|FLD5&H!q5+#{rUhb^MV=Z$nA=DW`Gk;WK8vfvu3q*<&CkMb#-4^a^pBW zjk<9k>a_nrd8-|*yCy1Q>-{%#5L|rUBzcPLSne6}hifW|sRVb7bn(&Pk8L_VlMI~! zS^n__K?NyEd|U+Ai#|@X$;Hd6ESTolkSI$5q|3K5NNP{DbqJzQ*WLUv+KqnTMmpI&GObRqK&&azcy1uuE)U(Txzk8Qu3wiE4@jaCooa3( zQvD!$D;G1rX~&OSW%+5allQVEw)kb06wF+@JqcTjp2A%h?tR@)K_2+lpFmK-TNOG( zMK=!;R7a;-Wx@X0ukWd>cuga;x<&ZYJbN-;T|L5B@L z#ft!nQbt8WO$ChFV?1ao1ntdWAi}NB7q$bBR}4Nt^Y@FkNu1gU=7)54s(m%+Qmf;V z86Pgk$AmZTpSucZ8nya-gk?$R-+RfMR-4?OdD9%uY2~sGrL8c}U5*tpNSKghxSZNr ziW;sahVt-GJbWlrHMoT5i>~&KqL%SKp1C8!4HeNs?C9yp~OV zxn%-Wojs#1W}ao#5M&-d%tBP{d86O0V|1NI3GD$&_vX$k(>*IluwZ%~R0#vbK zu@Wn^0}x7)UoHXv+T#kN2ww5b-0fW?=F(Y-#D#Vc4eD*-R6y4)d}CKG6it%C-fOZ$m?>S?{6Z5X=Iu7i#NBnRdy@$41VGZnRvTLaf+r`;~~7zlwQpk5Hx<_`1I9Sl*QheIAev^ z)FryT!L;8elQGR$;LfjAc=dCluqXjjQDLucb)@xC-4~zczHxqXgW3S4w-ftV$g`$mZ@(u0|fp?n`55nc2?xNsstMpuZOD z!7EnR)-X0%^h!tMuBkn?Sro*eMt3f^2{clAq^dYjTpJ~JCFjXqC>J1F%9nfxk4ue> zeNtbcqMEO{U;6siR`0-C{QXFByVemXa0vT~m=X7l{1sM{`S`n#O#$bf&B@5B%cESR z4(1e&4!?a?mQoPoD@VcMhzPFPFkJ@K_m}ct3eGGzUc8{WOgg3YMuR`Mtlum|g)2A7 zNYBd6Rqk%C?>bo=?q)xML{aUV9#NdQ7G+aVsG?fGus~~d8!;kn%)8Lz8!WGZwQc~AhZ5y`he9x*$-NtBXtP0{0yxypA%0eRL zcD|10^7A-uGKjsLkpbnJ9EB=oWC)Q59#_Rh$wOwd1DSRFV9IL9GSZ+SLw& zk4f zHk!Y?BT4N0h=ZPYF=z&OUy_sYSJdJ?9fsb%bHBdD;IZb;+m(pInVvItl9A3aPVj$7 zx(c>7zORWDmjcDzDVE|6#T^PQ4H~q?-GUc~;>Dpj1&X^RI0cGBaSskbf;+tY{_lQ( zJexf`_s*G_bN7R-r0O1$yjd@N_F%d}-wLFIL4f=H`${onkSZjNmg$LaBS*w`dN6gd zLpPl4up!&}&lRpi1F=7%D(fq)VZ9h3=+3EVg=~EASN{ge*}-iABlL~9a%kFB`OBIU zQa}fa;0Muyn$r{%G-fP323Nzw@T-L#g=?lWyb|S&za zu4tf{;r+HU>}o!C^e#Jf+k>$*0qXd*_!w#zc`piVfL{(Uzq=gs5M6onk1&ih_v!TX zPzH+unE|VJC(sj*dTO`xYukEn4fCv=8|gr~(CMf$?4_}q4cf0pswPfiWDKufxvq~> zp>je4k+7Ub6c7edV2OPGfUqaJHT);qi)fQrSzGJ;STdF^?zK<*^WBa=^mH_dI~Ji| zkit1<;(vR#?ad>A_d%vCcq-ZqjK@>r&6fG)C@nwwC(X8*(QiiXk3rQSot&=n%v9Uq z8A8~v%Q6n=P~Kk1TxVw`&BYmlW1gBYG2#AiqMDedRUj(Dr`J=CeO(Yk^Sy6?$H(0M zR4_)@_Y`}B_~nr1CGl)WmDmVVN#-WG1)n!sZ`HxuCQF^R=4-&9xjqCUa$bsfRkE1G z#Of=iQo$PzXsD7Q?D99m#~#=3Q)ufRJoyqcQX;;UwnrWJrj~b$&iqUX9C65xcb9N$ znWRX*E@djVu5xJj;P9P;!rc6)q&Hs%2tI8g^{0@n|I+79E=1@&{rIG#83v?yvx2z0 zPOCf*FX_OBCcq+TC0@RBg1iZYRwwcD z(vJ8HIw<|^1~T-(pPD%pFEmwDG)w~YVM`Cb!j8*Z3NWq^H8-YS8JtbUO36Y6tkzz3 z9AZ!b#qL-5U~mE%X+>z#u#LPNAGkY%A!YFQoFXZqQVvtDZDn;-6ffo;z4)1~a3>~o zFPgcNU>|4Zg-4wci5uv+B2nLmA)0Q8!8X%sRN7>{<>89gDhD56x>xdK&@;gCh~`Q5 zk4!NMt$nys^RMvDrk;kLah;ypZRM=Xu%=6pGx?YNUbx00-j$GU9^0R`? z=!%HiH58Gn!zXnsbBT9dmQ8ZD65sD}fM7&htDWo#sNI*~ukn51RMCvD373AG`#xX7 zw%(Op)js%UbVxr*=~`YjUF0b8ECBHG8m0)0iZ}(CR2{f(qx>e~7+`q=BEf_p8Ta?I zvf}?+|1gfaQWl*4jg-;_Dd|jYBk30(ST|ugYRkW1>hPm`l)~&`PHR&0KA`0c4OD#Q zvk?6rrYqy4txvQ4vb~B{PC@N>mTUDT~FQT46RO>Z-x4IU$~%} zGcht^I6#Xe)}G|Zo!E`Q?JkfY6SivIXNKo1@DEW*E`kY#7^T9?gl5rsV_R{N02{H+ zKa{3n=+&H55iBevmKDpsXHE*+t+mWznMbIxUv4!i;n@7Vuwj6yuvv zf>ckNirtG<*@ZVLN%idhoRzf#B8hCH!iI$ZAXL%DqpWkIRA$O%%Kr5BG!sV^E0^i^ zsN>?S;=p69BJ4jx2wc} zmtDQ$yC+!kj+ITYb@v;EVP{7XROjVvEKAWY=MTfY^1ll(<^mq~%V!EVwGoY5F$KtX zHM?rvx&uIM_O#a2kFS$#53LUueP?c+iDl5x1ZdYzkWqCosQ}` z1^(jDJW4{3&lVAr>@u#SzzCmveQaE2w6F|lI4O2<))O!8~OQ;ud!S$U4ej<$P~w zPI+i(=)iI068vZEKN__io{dxy`i~R%TipKf8hG;Tbux6SVPTkAZ)IF-o`vo(v#iTd7s5O7Hvi&R>;10iF(_cBN`8|&p zmfuYaCiX@bMtYQ}WW^_W9u46;=ao6pB&J;AQ==e84}UKti61%t1+He+QHUe%B+6fk%Jrsd1bzz8U6S6Rb7K>XrYyBK>#>K0HWRDw;}4j zUmrz60f{L+eJ9Az-@JEyp27)iYGSLZ_I1_N)QkxcxiBr7@u9CJ7`jCalwapcba?I0 z_w#DRwpN!-Q#BlPRR>DHaL3VqzGU*^Y5^F+bt>nI?+xO|hrTy)0#_wf3;}yPz?W)6 zcqd)_=Zt2mf{?1`W3;pg7x*>VpxCsz>9iz-l1B|Y!X<=IBGV+Y6$GDc=fzKvZKmf9 z+rZgd?&~}PQaxAyF9N9kKjx5q(d4nXAyp98!vfCbNF#7R2?VU!&hkS~lv@V`d9vmFC;SYKp zt|EPli$gdsr$TPri^#O+*UrmZuexzO+V=ZRnz{j zQu)FA(-G`EhP%D}Ln>qFgRgc9WybU6B3P%9$LD&W`<|Z$3``gv>h|;PCD+?X;2Mvb zF=4rDixd{z4Ku8^=QCD4Uh!HZr6w}ZvEO18+iU)4DFGt0%PZu=P^!ht%?o|GTT69+ zVIzTRYdU~Y66yG1Krlv0y`7G#eBac1?>ovbGATQLwNlr z&7`kkt(N+FDE8LqZck$@r-yY7b#*ks^cQ}1{DxFiRCe&xn(pDYMS(!;jd^&UWqh+0 zn_^pCg{)XOnFb0ag)oTAZjLmNn0<#kXiokor`LhM^5-LV;hn=5p|P$giNS9Bp?l8f zB;*N5~J zrypd$0SS&ELuv0J+P=ig7tTz4!yiT#=nWXo&AxnbzzYC?J4oj*jSZ8$JFX;CDlr_j zHg$E*A1)xwZi}C@8cvEzs#Xj@!2cfg+v8vllTG`9BQC@6m^L+7RnCe? zo5g0695A<_^ZB+Yn((e*yB~+Fs<$E!LdF9ci54IVYx$bO4*h`zVQ;H&$gEO|Td(}f zGvBtev(3;t(x*8A0)D>fkHUORGzv|1Or<)v}QfO@y# zxZb&SAc3ccpyHq9U6U4PJW#7|AP5R}WZxP(@=dZG53F^7Ta_p`5=)fU&u!D+UHftB zR4UEpIzMXx=3D*#yg&~Q+xrImTTeyO+o2Q|e_1+Mi^GP?&CT0lZ(lPP zjXWE?PMz z<~=LE@88>h<#N@**Ma>CM6ebgr@UBn&-E{Bcw<8p%))G)T_dwKW?K{@F7VkG8M z!B>fp#S_lOjF#kqX3gfyRE8Ue2ViGk_+r;~RLWqH>AU%}aF~T*>tUU&aNe9i^s$bz znqzYs2xQlHmE<&Po6XTw!Qg?|=QxcEFTv)sV<|YU^?rwEGI`v`s=~n4JFvTHufZh* ze*xuVAYEm7bjR;iCT<7V$zJWT`pG}Grd0A&a(+R3O<33^LH+Pzf5-mvFebilo4AO` z{7ZnOF-Z3(9KLZ1;6MNNIA{!Zb#)oPx`a2yW%*Nspy&STDxiFVE<2G7KD&({u}h71 z_R95l8Ytm`?-z9m`YXhWe`!Q`Fq+`P|FKn4h&Uj7moAo+q!xKPE|>aGEuTF1&SWNH zMH=%$D_j~s`;_iRQO_56HwT;;`+*STwDS-U5|1(@tB!)TUs1&E1(X%vNx_fl z6r`dm<1$1hcyZBz$l!ca9!AFdpZx>k33Jg_J~k0Rl?Ey~r~Ep)>0keF|D2*D{1WHQ zYbQsRKEV|$1`2YH;HR^w%_XYU=Pi;A$i`th zMZYV2bFHjKbQIwio%}89^DntIV3 zJQHyWz=;;){9r*#r|ib|TeM_el0A56ZQ*AT$(=bE=B=bVN{p-pOl@fFXXka|LmjW8 zu~GQm6dp6E!}i1J9n%c$!HL|4_*~z3-eSDL>%eNNF1sMSjxeEI`pduoTUUR#^ubS~ zP7iN?yaSI@g$&1F{&l9LI?Yd$Cg*$E&XgOv;7NToEgnAjr+2o_5*PsEdc3G*Z*dMr zk7h+mYjMp5Bxsw5V>NCyk+pD{dU%a>z6T2y1hWmv&{Gey%b*U52Tf^ABZk8i`+xnK zE$ru;_P?HOst8}Q_+<6A&x}s6do&7z>FNBvRgZglsKwr|PRS{~@mSNp6MJE5EoAdU z)PJX_U-|sE%;I82>AA`^tF9Q4KAR)jn%*&*r}ux&LV*UqgJuepZkA(J>*@8k%n;N~ z=IOKgcYO|=C*Pe|JW>b*G!d6L11qpqZ&IW`|E-FEF^=Ph=}+-$4U^iLDfaxfbgSvn z9gqPVBh;L5Qhly`pWkNeA#Jo{+e}G2&5^Q3`O02AKAc}Mdy!$Qid2@7r$oA7>B!+$ zFNqgx3=u(2yq%WI(Wr$jw zWA0UE5g#Ey)Q=NOE_P@}WtlcdUirr5ROz*9cU)aZ2jA2vo4XNZ_<-!zR+g%(U3V+a z^!3P_FT@^i%Carvhdm>j-GjKN?IcNmcB~C}r%z_IIBo*gtuAr^$KuKm!wIY>zW)p% zy8_(mz*T&Fcy7rO{u@6*W+|C(b4_U59w6wUT6`o}$s*9{P5mEu5YQ;9o=SJ*E0d?h zXc?D9njp27=HM3qA{t-p{bK*J1c_lhNQf(exFUiOvS5+lU(vX-Uipm7#;w6hKPdTg zx38maSfTmKKA)yF(F+kfP2|`o2PI_AFb5>uf*M;v>lkMZ6XhS)FJ1hUrxj^@&4ZVX zXmjb+=_CYS%(4txu;ddx1Eb1jdcwbdiP#&Wa>&LFJ$CeEcPdTVqD_XXS@FlK?U3kn z+k((5BNN>$g_PHiD|no+T&AN)BE16qgHsZn_=t1kZZlpQf|ciSKOn=oJ>Q+vg?lmZ zkH^4M@RQX1=zOG<9#i2}ZXw;XI9lREMXpzEJ`iFeU__kY-NDU8UXJJOLh` zHxsV~1xH=h^bHcRp7tIq6-lC*@i|{ytp6&9)7ouDd|`>l3vH?ow2zLbs~`ZC)H5=E zePVcivra-qOhHRDUzx-Q9m3;q@$4O>5Sn>nE98?NA8vGTR#vvQ*3;7yN&L)n^{r;s zasqkBQ4b6lnC-<|!hiQ70X3kchsjYo_;cqs9;<09JuDM1+Dg(nHsp$2rQR^zIku7o zG95}KOi=Omn#8lTuNN%iEZ^C1UvRtuJ78n&w;IMPB14OD5JPeC|1he9tI-wZD^dxH zvNOXU*>J1l7S{!Dgv{5rRC|PiG+~7UF)C+&WW0Po^fXh?GdoTLi}kR?VPR@3GFZ7z zb|dCamU_k|vfpY;P~+rS-(NqIYqXPKZu$1KjH~mPE(V~xsy&?gLX9dGXbk45Zf-m zMOG6eZ&Bdz>=g=>RP=p&`??Knd{bXAXVYGo9K5k{-?~;XW8De)VnYt_vRrB=!#rI5 zoad?ku>672kY^?gc5`Y1ppJmpNOEuWr+uNr%N|qFuJQ~bXn3*+#Ox5KKFkR}WklPt z{$j$6(S-Q#RS2P*FP}MxpfjlkW%t&KM)?g}E$u`>Dc!o@m7tmV|sU+thsJA z>GNgbsm@bi)FJ1Eda>&L!y*>Bzz-YOyy|fol;3;OxGckJ-F=A(6$8@0ARA1O|DZDQ zEE`g@(krPQrxVvm%nKDIo#(A*{0BT8!TC6dOx~;$4(x7c;unKJf?HZ{EWm;;@@>|_ zlpdpxHu%i^5yZj8vmdq3pR&!7f$KVor73WFC_NeLE|iemK$S~ z=Q>@R5~)w(g29UzoXP%@{Tj)87Yj7v!FUZVb}C6_66f)B&dH&Z2#{th=nR*X%aD9EPj3kCrZ9c6afZ6#xltV^U*dsZIOIXm|(Srf9$Ph%Z52-lZ4&!tKJ! zs}z>y%)dw;Bb-^=@H0-f>Hd7BulV~KmN4UoYTFsjL&b#k8@G?f$3lNVrTtg<1#nyT z_9E3!mI#BygmknYkv(2W6FUB?zK#Z#TYk3&-jNFl&qY+Anq1ZHnLnc%tzUeZ4}^rT zF<6W?MvjQB<5FsH@aIrF6$78i?Z$l2Gn^yDPBsR~mQGbnYj)=|z7t_H$<}iP(VICh zcuA!5hvZt1HtyBuZq$sw2@5{8YGr24=hv&O;G$?ZK!wugNV;SCsw>4&i{2gW!=I6H z9UbEkuBa|YXg1Rt8reUoX_m>PxY4&shZ5%|nnt7VnoT3*XCqo_n)1 zg7WpD%i{hm?Y+wrQguoWVr-#Rrg8U9Yy*oi-DD5=2>S&(KmlTh3%l0xm(ATfOgF&` zkL+6(TJRV0a^MGx>weB%BRB#fzdg6}{Opq(lAPx;P#~fpO|5m0u zu4S;H{fwa5teQ;g<1G4rgv8eA{q}L35aS^utK?qZ;jFL_I#{%Ug=y#Abxl7?4ei^X z`>!jMJT8Fz&R)+8grJZ{Qt{TJ>A|dbYIZtuYNw$2G}wh=S(Nz!Bz!eeG)g*mtZCba zA@m}cf=Z}_$(u`w38x(!0~EyIf7z>65X?Jy_Z9rcD1(kdR6}a}@{`bm5Rn#m2D>_w z%8aF_Qj0BguJKLkQAT1O2YUg6#Sv9~MrS!db0+}6v;;*~KGmreH1m-fswe~)D~aa-<=p3wLn zfLqO3kK&8=Dk3yIOi5m0;F{)scJ3VsJU6@hnBFm)7n-T-<>_kM^y<<3U1WEpGA`F0 zxD%h=4)F!88n@y|?)3oexclp`qdjUL{COMfkUY11_p#MMyrGzqr zPTu<$7n?d6ST5t=M$pk8D<~=K1p~1fsA$Wv>1Y1Ve{l?=d|iPB-~(pFrY|iD1^*hw z@X=EZ)hAF>Yxb_WIPVrd*I8^Mf|>!y(!L|fO5VGjr%#AA;aYF0c{0gzVSIAa8+7cO zBH9>-WDK>_AFpuyN|TSm#sb;gU$9TFQHA3vO?6tC%PuB#hoIx@6&|hAu%_)ODr`To z-Xo@`^$@x2Py_bvawE~gH_E?AB|7hWQ}si7Da8}M_<~n*bjhsu_{e5;l?k|Oi9G*g z?9t|h+S%p@Ad|SeySvJM_7hRJGd|w_hI?u`{RoUW(~_WW3yk9?zdF61s9%znW312X zp(=dAT6FT}g8D|98G}nKNj_Ql-0d4g&iVnq=9r+7k=;N=(ERyvFgmUBGNdb}Z*^Cj=LBz5K7 zYn;EhHaFhO=XJa+>b5hI(@wI8FymyWLcgx>Bbixx#JQMTty*rUj|!qcc|_kjV)=J} zbcEo}#QrGs0y3FN{OYj4{aVz4;$Rs0kJR*ua%nzlR5ojMnbhIqrUTse|gr*c(UYwC`Jr6_v;^ zV0Z$9YmfLMPEU;}&0k_*n5xJpcElnn1$D{E>uDt`0%{RwM3?v@G4YOnN?LQ^^L~U+ zfD5a*m~Em_Jt5S*E3iimud%!Q=XvQz_w0cr_}WprbWt>CP7G4n1d07IA5hJV)JqWF z`s%Q+!d7NtM$NePK2)&A=aN!zN(Om$`jyR5vo1*bX@x<_7lPngtU`b~J`#7j<}a3CM%2MfMN``fZF}j1S_%arBofT_ zbeN#iyYP!6mTsyJ1-E)z*8Uf8RlMwA%gK2Y)UAOWX`OKl!!D*kPxGGASnoksYHf+j z=ro5~ZU4@Mz#AERTyQ%%=qMScTXz^PIFC1+@tUgxj%BzRiEhlbM9KJp7W8gqc(p?* zr>UcNws*F2T)XYdTs?&Cx`LW)Unv43p+Uc!wkt4@c0r=t1VU-gMzA7m9!-efPVXK< zGKT}UC3~q2n-_@`v_qW$|6e*;DHldKOC%@0$M9+GRkNJ!NwSwe3PlmA#j~;w=@tUG%Vuw^?}Z9Zyo5*C zetxgd=Zz5vB6?a~n^jC^-+S^-NUG00hrZXRwKR)l{zf#0-g_L12O<6}XE=3^3mWvV zYiN>5^Qt=DIM{*hry$wDeCusrMiIO{QNa#2`v)63OjlG6k5{5`;o+#z{vdqu@qdd| zzbCjNj*+T$8`o3*={g7xqAHq$z4Lr57zP)Yr=u`g$m1}yfiH_4+qJkTYyt9Qm4yzQOS`}dOTkf2f%Cb(V} zYw3NJuBo{rj2xvHpzRnqfsw~nc+JSuniHE4ql*{P$cqtd#+0|ZOv-77q&XRG%HrPC zI3R{exp#1|NF46B>V(Fm@iCF;iFYC{$+`x1o;^|Mf~G9d?jMyVUHgz3B$+G!X}n%^ zY`Vr`_*hv(+{e;^0?NfzIeE!o7l{JUs=jmTHMX}7THl_*`{bg=DGUUriGUBEjUGRK z6!Hp>jzt2;V)$3rA~mb!Gdlp0plRg~WxbF6a*YI4#>G7(t5dHq!+>0XbNS1D*}tQs zKQm0jJ%U&)s3?IEb7TNbw{S!G6N)XoM8%hQMd3>yH309GOA@&L_VGs+C(#C8=-iA# zEw4W1Qh}oa$mx?j7?bO@ZYY^lRf+rpo-%cR_m1eUPn3#W4-ZBR4@QLPo(C>~4INO* zcSmJBiyE*u?cOzn3Jg^`-#XmT)a2^ippfvmkMc$N{j|E&$?A7ltu*q>$m5gaBR zvLx4CX3y4R+Nz`6VYQE;kqqXHHXKh^t9}MFcP@VyTtTR1zxk>UIux6 zO`%KQu-|)~&;)4YJJQsBD5seam30a=}q!>#SKoVabiw@?#R=f=FtwF>!Esws}oq$kf zcClGHP%bm>)LTxdg#q=6xtHYp7ucAMPGkXG0HkUs#ex2gU%Wq>NGRfzC06rjK-+4` zD_sjWpWj8mbE(Cp5JU^x2`I1E<_Bd$fyB;yz3bbOLLTYGw~^%oI&U=t@|nnvZjJ$z z-&XWb0QiJ04F}nmt7KLu#NW1``TB4=%!K(uu^=f7Pc37=i^#}P|A6RGy_01h%0BFe z_R0nUpV%bjm;}Byat;NVouc=^A+;_p|HcNTB}G}Vf76Ep*%ZLFy*`|uDn2a?=IdXS z{>gm`|JS^WG53v)9IcR#v;8lZdE*S*mP$#detsl^r z!-PViO1;|rCJJ{iLsMQDUV|cZU30J@dSf&o>2~@6N6+*UJ=uamewG79S6MHVm2l zQt|&^EP4eZbCv74KM`cUc==I5Opw>yRgBt7mCMl3thJ?Rk4Ja(PbFRRL-fD>5H94Q zfW`h#?{-PNs~}pLep813Eiw40a(<=-r*1E9O9#5x7q8!Nj<)K5c6GRD zPfca(RYg3(cHfQ)-o3GkV9X1)_hZ7<#J60JEvLNhG1UpGAe$et{1IM|pC;j?C!$pF zASEeKaH13*<3oX4T|VsFe?2R)@U4pzmv8P4ru%2Gs7ycB(|@h~n?&eCV{9H-6w#ds zW^{UmKd?{>{}oJyzot{Z+cEG^x-}S`5$8B1BxfbW$O}l;6C{l;?nZ>M(mC-eC$RrA zIQU+lTJ@*R6|SKda1N9gdL9Wx|3F`yy@RI!X1K-*hg#20Ecw3af}1uF5Mcw*NW21_ z3#Cp#*~F&?%8C|XvoJc0qs*N$-C$(Bli3L2u|ncc_X?P}HH6=~as6fAak;!NAraJW zKQb$6p!2N-V0NK*IZjh$!RBxSwq$Q(g4JFG1pODi^;D_7r_c?L!sR{UdsdmVdRn-I zSOOuPw1V6CZ|-ThAQFhB$8jPVFDDn|>=BxhF8F5Q%bF!|pW+)3&5IBk^xGN(aI)%H z!OI6{kOz~SgN}68#9rtV2y?h*LdJ36z(cM-05^%?WVfx*iElqP@-^v6nD=BLE^L6p z8NgK<6v#Mck`xB|pSMMOq8c0d^QI6vN)D}BPj21A>%J@WMn&4zFTjjrc;!^LL*Yso5i;A7 zaJ(Cph@fH5=ah(@CRUzFyLh9q6_} zoIz>h0wNh^PVc%nk$TJVg1`Gd&Xnp(U%lC6n{Xqba~IU|tK~(iR=F9M+RGj{f$b{N z$UbC1yhbwxZ6LDwYO8igfS*>qUfPY)zq|?ak<-0K4H|8KcubwXw5E*{(Vuo5KArWW zd6gzZKUMJ@i~MQ*-HIq^O@K1s`ebNxyojpN1xN3O)~pm2uMDd1-vhXWfqhhH)epq2 zf3ePM6wh{7i&{rvpe zBd>9A)_8D91|w~w2HMwUUH0As!(6G(pvYhnqZ~St41trF03T#%(*dv>;@i*6ivOf+ zVP}m7_|RSR8W|LjiExdl5_8w{veRQSNvX)RC5VFM`gAj4k8*xPrsX@pvb82dr2(^} zjbDf2q2e5?dklz0>P`%El7@D~UVHphL&W16Vnv7q|8?~WdkAS#=^kr14RB&Y z)-cAj&Gtc-;H7ZJ#-T0pC;&rR&?Qi|+R=uor<|Mq5Aj1=XNm7W99XnB3qZd^LijZt z{1PqXdm^?X@5E$yqQ@#;7FXA+HCK3UyIC_M2jQKr+-=D=^25Ov3`plkjqZQGFVo&c zV7=>Us}q_Q#FNY}IS~vpcuQ<-xUQR%WzfNNT>8%EP?|h zW_)l%UFQuAB673|V(T#ztl*{itVW05%{kA+Py_C$N1c_a2nKn$X&XJ*f8_LfH!hE* zVA|5cY|i2y!8U1n`7b!QlNGG<7?RM(c|IN-%r<-=i$BIslI$GBMnd2vY`YjmbcU$& zqA!P<4ss&*2R~<@40Pj^T-@c#aIegSp^NH?K6ufOJx)*obG=DymflnxYs}?&8s{EH zn{Y5nS>ziJt3Z14)n)~Pvo5b_F=vkPFS=!sW^d(c7v*0%Rgb*Ij&~@h7T-%EBKxG+ z)+vyo%$kf{IA85wbnyBDJkTrQRn5rnv`@7iG&nCQhC5hFprf{d&PEFpwx>1Dm5)bv zdC>x%1i{cq#R?C?Q46o4Dp9aPI!*5b7Sv#U_(BqIwy++oF!u6&%z5zj@k(L)iU{)Y zLsqnEtc2I~arU1^V|fW!_sJte>4!oum?EpsR3c945T zq7g!$M3v;8a0$OXU1bDE%k_Rj>E71QkUJxGx%HI5(~y|0q6URd4!?o*895B7L6_gr zrddk)#NOI0zLU>|8l?owPo^chjSbnEiOJ`W$~biRf0j-aU|%%T*lx^e`er4r=!*te zeSKU0Sz&gBl)pA$rWsI;T*=@~OH4h&>f)zU{uy%gl%oD4RIOKakk9o6{l&)L$a3DE zuy>XB8bl6_<6aQ1U&->EUUXd08NWQfh0sY$KZvFgjw8Mao`H40B{8Z!^`h-G+oQ_vtDGDND79}{Y2L?;S5iy+h1DWea}^*Yr9wO_ zkN37UevQg@7$;12n*oCfNouesS z8z)5VtK{zkc9xCTJz} zNu#CZ-rc0aV(_#^zv#hCEe`U;#4NFs4fCijcK4af3IcX#uUKrDn-dLG-0TgGvnhy% zZ3(1CzwOIKSf;5w&EkCjD*l_Oz#SazqhHwNGfnZy8S(R z3xHlpgrva&xbH}YVDnTwc3nnr4T(SUY{L(cV0p=^Z&~(l4hH^eg{y!3Y#o@3u7?5d zw-5g-5V$?fB$>l>gU_X#s-E(?&xQ^aF^$y@61qpzK!KT&Vda}4d4t8{Ug zDxxh$cn1$JIx^8cW^t1slb5 zMOz21yse3W9rzN8Thoh)K7`rPf9Aud)6IMPPP4ZUS&7%1U?w^ZR3H|WP!u=Pr z`a8mZIAJ`&_w?bgLQ@LtT(Q#d&vId4CzVUIx<`$KaS;}_*T^o&%33@gqhUfV{o7i! zOQo$1#$^B53g$mP|My zcRwP3d+zy&2)H^CT%;@+kM?y;wuN{60)9F~@@D80a6A97Gt$WQx{oPCq_oAjkUIHmZ2nYQ%PjDTGXC)1a5$I3e)3 zJ#liSO%Y$ukJ{Cfj8%x9jB22J81hf?=J@!D!iK>(-Sl2qO8Xfu&zG*vNO+!OQ z{#p`Ic@k#puKv}7ZvMQGfLkPb=ol9CIt*A_8FjpF79D< zFEoH6>D6#z825m?y(qp8SOXbMlbfP~htWixVKES!D}t3WewY=5tK_4ZlbRUKO$dg{ zU_gh`-a#0;hI;~;RuuuyXS>4#muhh;hy0%VyTII+>9hc8)16LoP|XQO5YVniYmK2K z>XJODLJ7@Ea{arpSMX)~(`j-}&{KBrlxyd=iDMu9n+id*2I`gFA2P6PG52_ijsx6a z)2EJiM25FcH)*TN@tQ$chl;_aHW^Vc7Ikw^uQ_ypn%G`gJSdO}!2$Mjgcoh4@0FI8 zDl=-c`$fh^$Fs3{Z@+i)7i1QqT=%e$iU0O;_JQZo)GxKUs+Bg33ruXRFpKvePDA^i z+&U*bb$-bp>S&{z;~q|ASFc9JJ4C|x*8@i#)u$%4z27nMJa4Re41acf-p&m!iI~e# zP$iE7>UZTR1^VF&-RAzg_X<4aU+smTcIR}s8^~7Wg}PHDD`|nSRtm0qdVK?S2iocU zuSQAqINvmW#;hf?No0Qbf9WN2Em*ZFev_^AW!;^kjp ziA&_w+zrzua@Mz$EJ0~nI66n0I1y4SET7eeuh77i zWm-Bh4m&1cPPw4RuO~UZX|#o!OHKPc;bhV~ERMDLao#EVldk}lmIPe1PozxwEN$0!X0QP1Vem_n&F6Fom4z z55!uBOPT{8_m}9#Rzo>*O!OyL1J>zuUi1hJs!6U0gZL0vIBfHyZ=dK5&vXCjxMu+L zxug?DcP;ESAmvsP3qCyxIXCftfaO#J9x%wn_@@YVRokJPrTH?#brGwBi4BX>YF`mO zf3ovV^wJfX^cTMO1gPnnkMgo0TK>axzaC!pV52&cmAi_?1lTI`> zsdmz0JhlHwfRAhap`P_`*R4~@%o8t)wA8z!y5DEmuFqM&kNhyX^%FkMEuW!WO-E{z4}WnHMC~?eYo5=;3swajXWE8++*b>5qej-U!?-efTPHi z2k2%`sh%|{{(%VsyT@P?Xl^Eu3~de##v>Eg1}82gfE-*&DlyPChL@#tRP6434U++$ z8U7a6gH3T@S95cIfLIR5@bAP2Ok!}Xx!jiQR^#AGn4^zw5rTsq><&W=oN#+b5B*?K zOXD$Y^1D4FQI z(Mwy0NoYJc8j9I&5VP#|F>l};f>GlBhKDsQP3vfoq@3r?DjbhfOYwDNRty3w*T)^ z0=kHq6xXOnK+V#a;T#z;A-x8`J^MAA|9PZMIw#UZvc1mDl$h@$MT|~m;o+e&BL3Oyc0N1?e^oXaPAh5!>#0E6k?D>&;wR-wr`zS=a&Wd=_PXtG z6Q+~+Z{R}CPVsJz$bDk2`SJz;SP4A42~_&GU&KHrl!ffk?xl+VV|kf^-)byV5~DN4 z&5iZqWb%FJcdv7XXfnX@+J~EFU}fH+!vNsMf&XfB&d;hlH75ISi@(Uqn7Z3ua#&$xBEX$w7IO<@_8nj zVZ0N#P+X;&tW5>e)t-|A#_%`hw`Uzk9&XPqjO`{gN?Ig^zGH*hw#sFAwGBbA>+p(m zMu^;kKrFS0klkW(?ITlW+YblBQX(H;L0mc^k1^=Nzi^DXZLjP~ZPXJCyhQ^5cyMdM zt;CAC@r>loz({+WVUHdp4fyn}aiTo{k*C7HGnyeL(WJ|r$eKrZ*{ifWl9eTySGi*# zR~!Pi02$7wr`gvslu<^Hgh?Z)I}MI}zi4KOJN4gOI9}SRRx1iGZTWKPy3;BTSC-O`XKx1g>KE zSIsNLe5#=Tc9R_SS~*z1eVZ8ktL$AqcB|FNVx( zHmHT57H3P5mX|~MNI)1mXp$X?C5m)4l>vXp&L|D-F;zcJBPhv9Zoea@AkFzL0DykA3;}sQYPI<;@g1a8pofu=_qkP!IWUbY}NwrP)TLv$3mXE@qkT;?`qny#7 z2bFP#KzKXE*eB%U0F_G08K0=fzxC1RpmnB=FaISoYM`{>Y;YKMlRO1dHPKh$chIuW zzrs-@|b`9#%L@8l9_fWY%Z|9 z$*GivFs~)kAPuysBuU%s;R!N+1HwNrOD=43wSv6uS}C8Y^79&sO7<9CU-a#?>D?*o z7-b$WEC|9*k+d#@&*9U{sQh*c%)>%utGtI(buN*e2%JI=L9SNQC1y?r`R#4@;b&Sl zezfR`?2U;S@d2wbF!jac_>JuB|9&szq@nHX`{&-&|Gkz~Q?r%gH=1Ij)|T$_XU?AJ zoFeKXIP(`xERDaXdR$Lzi+|ccau~)dmImzHZwbZLRUx_fF=v|iPB-mElJvOVs zsJf#uxIX#U#bTPF!}~0yNdn3nGfMWY5cwk7WJOYwQ;%f4HY{O=YQ117p9fbw&uH>- z_Yk}n-BlYz19DPOgc3oU6cc~OruMRbL2yubqAIbo>RINeg*eOCApwFwP?khgb<|YZ zW%kshAJGG8;e+js;?+@z9vLSOTZeV`C*HHe2R`KtDuCC)T zf#GwT!)!H}zFEfFnfF8MuYTJZM`+ifYHRFoNwNuR=9veGaKf``kl#zPAHLU5xbBa#{z?4?iZk#^#Yp!3`Ap)ba=oz; zk-W%fEg_LQW6-QajK5T?5$FAk+O5q;Z8{ryBT7$|=u{{_4v%GpRg zRBUA@P)EH8)6EG1{;i{8m1u8aYA0{s%TM63|L60YMZ1C4^*uzz9wYZ_;?4MjX?VI; zawfB|&_F`LaL`W;jlnOy^Iyuqlwb0@;SsO&KbCxD$`JFgolSn5wELW=XCK&!Kxhnc zv^YG3ViB`xW(r_AISScK;s0WZU@I}Dd13JGi?4H@P1*W+k7WmNkWttQC|xmxhm@=` zqR85EFFI(V=E7jw!oPXWFDG7Y_rUB-7t6nMV6qi#Oho@5S6>|v<+n5}C@mc;rF6q0 zU4pcnhk(G+A+Ug?gw)b0OM}$+;P2l1-TUsp`=C6xd9xcTayDjjDn@0~W=C>{R3JQnV3PLsFjJfJpH|&5o zt!pkV)I)0Zq2kZP{sa%U8bvI4YnCkjcP4X@b@sH(^3l=0y-C8#cq?I5)sNDj7E51% zB^ZMRUs=7F6DSX}8aEb&=w@0XTTw4R^ad31it?(CV;HQM$myO-u zzxVX?(g|+0nN&}wTz7T>#kQ}_UI2B@FxZ16RF5#3a>gJ!?L)$GJn(N3N50@yRe>(G zO_)-VGz*+hYnMer6Ztd}1B3bpKmkc?$sog*vf+hY>~zNJ(eFmoA7$m;Xz8R0e|2@T zm}H9!u#28J8;7T@nrekN;WCGK4CuQAd*Dm2^$a^DJtpGW68}v&rkESUR+0>aLfK{k zSa7U?0~I{~cmXu0NAp-xni6V~7Z%FU)xo6*YXc`Kzm1YkW@Ml#@Y)KzyUWxIG;UCM z4wiJb*>6Wij;ffe!g!U+!{QH|h_#;14TUe%ausP24ZFYC;`YajLkz^%lyW>i1j3P> z-f_%y%nxAl_#^0*t!83NAzSjys(5hos{0|xNnLTc-eFqiM!7%wLSOt#*$H_JQzuOv z!9$s|aoE%>rlDD$upIuuGX9~dJ~ukUr$4jHgMv{v9e#W{H>APPXr?5(qhIG!*-g`K z>UhkM(u#IQPbKH&1X5uorSp)lQ`6La5ETvz@3?mfPE9oW4ISuzE1!=$@TduvPi=!B zdY^P*0lzp6qFW7?Fp|Q;wnHH}wbUPtXRBnlQ%<%b6fxL% zJ4XX34kh=XDwVtx<{VH@%KaFyQT2noSNmF4q|=7OqZ;dSt{yzuL-LU?xbX8E?(wLo zJmmSv!*`=m8|sJ&^~8qSZ9y%B7sk;lU(%weTw{ORx;v@bY|1b*OA3;^5fxU5d)WVa z1%lgL#Wa*vTikxC4i6(DC4@sQG=(gxgi7{wVm zLji`ojtn-hJpt9O?$*iaFh*=ELJ%{BTPdHDGrN>TK~fX37xs-qK;RlL|J9T32$U_* z1BtoMfv8p!1Fx2r;w80Njz1V#w^8=Ng85UyNo*n!tqC)$flb8(BKf7U!<%l&s@!Rr zIdqo}V(>@fnldx`PP>=(b3&&v<)fOu9t$ty3AJQp7o>07D(DNX9)x|o4bep*&SQzV zQ4C^5PoN~Af!SosB+q$Qx5Q^p)S@PyhXxhN24ap?DBk9K*>LjIJH>0azgJnd{A9pr zmO@+@U@^=&#l`wq;gp1!{{hLGMUTki8N2~5a33E^NY04`O7|%bV_;I5mqjGJGfmcc z29pFpUd^q8Q;6r1?D~5@yJnoLIwxhcR;^66W9b`_iv3MmlA1W3hNnz&oNzj8*`-MX zaX1i1x#zA^VWgi8Dv5?`)nAqN8w|8$d_H(tgA)ypLtaBY6*rqa@S%LL$m4=(bO*FX zR3O9$LKOhX>Ez4|gDju-L{c#5Kewch8~hX-YlhnL;W%0!m2$a6p{PJKTv7=t1eH7y zC=Q5vyzm2%^6PQ1W(L!8k(Qhl5hvI-d*noqxcc!d(X-XczDld?1ep%s_?pNul}E6; zgvlj3nXY6~_D|=fqv#(MBbfI@eV0Q>K7x5le5#j0S zfQgC%)BE}l87=W@L=i6zd80{aj%eG(rwzP1q4B(Hm+fSD_P-6tW3sUWAaU(@PmD-t zXvik+tGs;qk~f}+Yjx#$tiS)gD?c`3n!yfEHk$w;7(J~3Hk9cmCEH(8?f`Ap*$VP_ z5XIoR?B?*0+Qh=x(Kdpdc!%Ec&t3>3T*?X>_Gx)7*UxM=Hmbbrt&dx`Rwx#!$%F#w zi?3`Zu`2C`5$LViu~8{xKMlC2Fm+2~7fchvfGSvdPc+z51pQN*jzAktXVRw-$BndSyQV{3lYvb5uj4(~nP zd};zHb^Kt^hD>U$9ItnLy|Q_s;WtGdw^q?8w^UPkvMV+_dLFYs=G+W{QR)%9<~O7| z&nd2d9jZ}if5M7N>Z81v%#$Dqx9FM|_EQl6dd1LjS8C5H8?uGAZrm31ie=a{1<0usbUP7B@Eg4kyk?>53XD=f3H>(Co&jHk?h9s;aCS$ zm~W_sXU^Q(d?iRer)5YH#16EYjEUq33(`7A7)q_oPG#eTc}iC8r7SSqoJ=U@T3WT zRO%u_PW(|S2KS~Rd2CckVszvM$#wdOS}r%nbuIC>=|rq)uM95B8iI2q>FLIlouQzsay!L7$Kv)=#m2ibDMtq=ShpyX= zEImmZ6c~9W7q)unQ}D?}0XwN1;oLNw&-%!I6(gV6N3dD~Qk|!(3oh3&;P!+J&5#Hw zEdhn7gW(;hh>v}e=jNd+8)xx>fk4=p@Zs+D1){4ocn05gJ^WsaQ*-zMFQ}TSJR*zU zjHMNxOf{9*_?kWnnn_9Z=1Hs>>25bf`wl%$df*WR zG)F@63l|h_F25)`dQ%%8ya-Pxe3;%cW?Y-m9x3&cVR@yfR}{Z&htsuFAg=cM>y0f* z0vK~ZK$RlUZ`OjbwSTM>@u(|6;m9^ko?O`lBMOGZQBa0xeK@tOJM=Y5%2A}QhuJyh{lCg7N|L=ub6TVK2643MPsl;ZubbT{As>@(_CeOV`K^OA;dYJg=3OQ z#c-gqI6zqcUBrC*EpXYgJdJB9{tQj++31US1H5n&Kz+QKF;xwF2_q(#+ubg}hdvE$ zk1PbePaA~eGT4+pr-8 zr5b83Ibw(&UTdX7SLtCznO;oX@-I#gjgb9BN1Z=W;c@~zKU^dC(K~z#MUrbvQA_XK z#+K-1!w7&9tG-z2`&h^O;-@x(jm=TaZ$+j=ww6fF4mtD*Jt-85)V!r^uJjC4 zu_;qCirGN+Xb8?40Kt=~(aKmF|J&8Iy~W$LsVV47j3=3MimR?%-e;MxqL<3`FZ7?l zH^E(mXEN*Yi?TC10F}9`B8o7KXNJ`^dnm3#C zPE%vhnuxD?su|tO=5NX;5A#;M8*ALwiSkqaLF)wN_V`is{Xr)mPvxzaDP{cAP(FYg{U`*cO+6B{NCq*YedoS0f#Hk6K%&q}5wEtP2RKHwO9X!{q7 z&{d0MpjkI9D%U_SbGQrnF1N;LHKY8J8poAPz9bxO6CTg}o3snU9EE9fFw2yg-aGk_ zot>S{GmA`_^Foy>kJJ!hSvB(-$h7_h;s6VYOD*{CIwJ+^9?am!87+q+C6#D2&g;SHyJgraN9j!fBCvGpVb_*9~er4``hcp*QN;j{Pva|!S5`gd5OKa{pZX(H@$uW~vx(nchc4w?J3pVeUL^hY#fwT|mnt_GV}RSUC3GO<+`EwKDhqN5*r00{Q;`q7 zhl16`F@cL%xIHE~6O(6A$73Hp%k3`t)arKh^Dsxlddm*@-C=!2o8HlVwgS3Ngae-> zoaU%T@JUCb@{VW9jr5+P$w|0Y%apogqqs&R!J=Ee#?m@d4J|xzymKpN<2WqCclO(@ zqTr&^L4)8q&nPfyH*nLNh7HCUpFtf3Q(5 z3y*Pue^Q@7Ycuk8yVAqc5{Lj0(Ut@eo=|LX7s3n0ArDJcy_<&!6}ZybosftihO#6G zF4d7CEzR#1Wo(bsBfzaB4o(rZ^nzv7lSG%v!cz7AS-Q!NIckOuKFDd;9%)5QaN^M* zIH~gr#>@a8WDHKZwjhIz21f-fL??&Z2B*wepux(QUaf`wX<~!py6_P}tw`=L2kt9K zPdgoS79Yd~JjSieg_j8gr-WM&!_>f^X!21di+rBSFK~sFF&a zFQ^DbSgJy@KPUw?J2g40XBdFuOTR;rc&R6!J!Twz^bZGaW#0KC{}k8AFuu^^fT`r# zVaYQ%9uNF{3{iP?;#hILhc7(uv-7rL(RstzWB#2m@kOQhKKzIv_ZLRTtq0Zx1U6(m zw?nq9uq1!HEN%f>?B{7+jwN@yW=f_45M==n?e5r_PahWBfFJpJjxwhhJ5Nqd8F^es z2`{3DuBdl7iReX(GZD7oC9e=D)^=6290?@bv)Lyz!c|#)m)=>~`?whBLN)bud|kag zZc*n~A(6>khSe^Uwk0~Hrmk)mHI^zE-2WtnIUyBBbrIg9+k$-M6ky{<^B$L(Nv8wc zZ=a&1?PP1-=f>G3^C!j=A(lcYemszY&1lfT>s7m%3oKU26169U>fKN9qt$snL2K+q z9Yu62%?aCi#TZQPX5bEb0tq1f6!lZ`h_vbQqdfA>)ydcB1ne3*502Qs;0v^?upJGm6WGl(%fO@$bpAg8kPDFvHYroX7r`;vC6#65O^{| zQEs&~w)RBOg18%$Z4d_8pma@Ou9iLX>yP#rOzMpF4-N7;hU;DpKHnm<%E&1MwUg!T zu&!U(WrRiW<=)8vF5E!O3p4cl2KW;co*3C*=B9T%a8IBaIZ{u9m{C%$+NS+oNKf6g zNT;UNyJh9Jgv^ag_}q1kJnbzgK}lL`+_ z?;3J7`>>}cMS_Uk)jG*`9UQH8Sg9Hn0Ko?~gpxnRUPf6g1Qa+m@ju1`NIz4ZH7o{6F%Egx^;fY+}R08)1Jvy0()#)a3u;4$Oj*58&Jt|+(_ zYjNth>OUCGdC!NDzP=Si1uW+D^ZSWjsz zMpdTeAuZhafg<{}9)+q}Jf(=hh>Fi`=N0Dx_8g~91?#P#sOVEkG1ckG`O&c#1*S@9_MQi+huC0#Y^0@>5;7?{$xzQgQ)^K* zrG@de#k{zQtw_Pd!|xNjp|1n@yPjq}imr|`>#P{Aw{n6<9CpXzcOuBfu%Om*q_BMJ zF7%QDv~|WW6a;Kb5~REbg5F9C53%rj;ieF$Euue>o8UOn94&u>FhpePcGmlgz_xFtZ@k*TSZ(}!_7o982RmGlrMxB<`| zO6t$0mv>Wh-$VNnRzx9*7)E0eeMOck1E3_C+VNhs@YNhePS>Q2lonQ+n2W%zME_iV zSleWXTv!in+8(IMz51-$wA~XM<(bC`H$~G?S~qny6i*FqX=(8d^b(v9ZvM)%x3{-N zzqGw;<7oHRp{%l)102HsDLMYdm(Uy=)ITc)faaapf-N0=wNwlhL3#wB$9@`ISdRep zW?+~`WsB2HXD&+v-YNlErz7(Lj~E?y-Biu@wJY$>hqX0`IF&J59b6Ptl;K~R-&kCo zIoN&}pL;b|=H`IA{HI^wZmToEr$c3>ohhALsL~7!et9BB*NhRPn8{zNb1Di-a?2hu z2vx0wltTHHgkhTdS%V#8xHW;cHnt9L-`d&Pu{xpE1sagqzEd2C6cr?O5}cTt)J>q& zd-Y;kzQ7bcJ@DImzQ{WrMg|qI($anf)z_LDnVwrV6{X4aqU8ei-Gs|Tti0nJO=+mR zm6!4>i3%?@=8PqBa=ZmUT>|Hp0gh6;?QLr6^t3c-HMyv$D14}7(KH?h@MBsU_<_K18>Q@g?X|{qA6%*6bW!Fut6uhXMufnStcO#y=rz-!m%~vOm%V@Geg22RV z%Uof?53y=#OhrDdy-=}?gbwi3uY@FbkpT+L4J|1tLuFImDrbrODLmG58N-s;(#pyr zkJKeDMpdeM$paw6orsVUR%!y|P0LJ8N=Pwzj<5LiiMm;G7jC&cF~)hChhnDo;Udiihja4 zH^*be<*9F?AMeJC!0L(HQtT)1r_fkEqWWm8S$!0IOWWQb^W?kD-R47wfBS^5e;SDF zLz)X#h2ZP5^{a$32Jq87FbX!3Xo@vnkB6cHG04(`QXiPieEyN7yvP=C&_f-8Ca>6a z#Yh^P9ocBCU=FeWk+X4o=wCvFW=L{zSxYJV7u?Fc#IMH>twg@9a^hAzxcAZHp<>yD z6ZR%w^4jwu24Is(FUhZjZc#okNfn3;v^wBzpeevfWrb=ou>~@MvJo%&d29ASx95w$ z=btRpLkKQZkMd;RJKq7$2a}#7+U()NYFm#A^H=@|eu(7=G+KVt>#|Gy2?}{MeC=q2 zDp8Vxjbf0c25nKgr6m2hT|xKaRfFd`aR}W$aHF)u3>Bm~X$LuZd+ftb;^Sb?b?Fkw zOmA)yZzup|9^Z*)F2o*)^iKovTu0~=fGMM~JDi;CptAbW&(QHsn|2qdV{9L4;Y>eT zW&C5@)Dh`*+n)#(NDr%uOg66mz?Z=S)np^b0Hyo)rUuaKgw0)U4LYbVK07=LMYYpG z(L0@Z82%dQ&CvUfKvI>7^#I3z!Ufjv^nyEV!s(HDGRA6I>v|MzvxndN#hZ|TLx22G z`v+S%l@I(O99eC{wUKW_$MlzJF|;snNJr@Sd5hfI#BVsW@s79Vsd2w)a9+0ep2KfQ z?~v(;G(6=)#C1+%gmzgc%htpmpGBczSnxD2CuJ24O`n`jM!pJxMG@6V`ve>ZLYVl) z?Fq(kBbefhbLex2enO(7KT5yk1^Zn`?f;(To*JG4XQ?_nk>ekwUNhfWq)yI=FbGG( zX1-w8s5xe~2Y1*UfI&pQn#p0Odxwp^_x51LW;R`FK7Vw^QK?%ngeC~Y7Q&sS*M_GH z>ro>GE3Io@qE6qi(Rr^;E}e*PUVc}1h}7wx#V6Z5{+C9v?+U1;>6b|mZr$xE4P_j0 z%K5h1kC%BPZ6#2Dvo;dJ3)EgX_v4MxLfIvnNtVA!2Icq=lz3QXw+v3mr?eb@!EJh; zHSh5}RRYm}`q#~LfgL0V-E8BSXGy1_ZE_-z`-U<}ye_9Ji=1zJ2QP)o25@v{zVHuz zKaDvKhDdl+i(uprrOClz8j^75S>-g5+tjyIn}O3v!!~GQxvq)1q0gPq^%%-X0y_w0 zRo|@G;|C`F)xlnW`%$q?lb=dT@)m()Qi2LbPEXUCcy)2=e+ry9OA(^_dNX&Q`C-;c z*8bN~j6cY5wP);41&BW-7tXXukN|$wn|97r#Lo|TVF@VXgUbvsh(v4M)XzM_)(Wqn5Y-QL85 zDmTB*Vr9r(J8$#d8np58`7X&E50#;pc{wT6@Phq%iJUP25?}ScW|Hzga_IZbb?MBg z{MVzs9bF2R7r*qw9+kH*dfe^A;U#iTagCpgSrfvzjw0I{SwGAL-g~VRP;c>mM$qTyc!E-)dw$OH5_sX06&&l>_F9M#W#PlR@nTtz)d| z^VX{_CEs<}_*`!G8?!qfGFSRC3M~YJ-tI4tP3meoT?Bae3h*OV6Sfdo?|&wCA`w6N ziSNA6CGv7yFm;H1ASVHQM|Mi}`(WYOW9u87NlwAGof zJM+{(lbn0zYQ~N1g-Gwh4hYoY8P-Wtpt8`ycIejh#kylMh;Om|l$rn=OiLEqeA?9D z-W1L=Qg01@^dm{fG6Jhf?=o2_1XYvuxf7w#=4sji6^8F+Zk;0!__6pyC=3qqvHbjK z7jcnm)#!Z5(2H#i*XJOcoyF)kYAWm0%fT})EO&|^7-R$eoo1&kejqXh@)OHtPqH(! z*qEm8L}=x!8ed(V>RSOV6}|S?PsrH`_(#>v?g1Mw7!`QajHvXnR4-k~*f-k$hBR%v z8^X{0@$=CtVXbPN{@3fqNETPE_+4u8J33=mJ{zMj{?qvx(()_wVogq+Z!xO7pnm`x ztU)&3yfy0hOcu0R>PPb{(_!&$ruQ|^;PD%`WBR0`ppU(aF>e_ag5LVh@VkFNr8P4c z_}wf`WqygYnZj!LZJZi!Kh7p}H%1grqA={d+qP3N3cF4AfV_<|+_8&dnLIl~PF`)jv$ zuwwT6sx1E*@CZ6;to~=e$xh(0SPmjn;eIeGEizE~wE4QNDc4T8o51ncOUv*7V89J$ zFo?PRb$iTD#s6fX(IkpC3KJW-*1Ep7@7X2pTC9|*Ce`ZHALw)0LvSYpYDn@05${`L z;gfOT$}dM%*62NY_?*u3pAn@d9&J@4g1*-yI2rYL_vrw>8Fq4hJq5&dQrxuG`>Y}6 z^IZBH;XBwHi@$D}w48tWeWWom7}nreRluxdcm;k8~!JQQksjBKCd(io^{>>R7}4l>aggxA|23W zwTd+f3A@BGR4^7wp5rO;g<89#U`e)vwS4N)A(8yi@so|vRRopE5dRv?I6>ox) zTCcyNrT+vFE!M4{ep70`CqGu~oh5!~-s{{q{tixYmZDYfJAbJ6H{C9=)H z=Hl@pHke!f?DI`g*4+CLE6K*QB`?d}5t-vV)v6l`vDq_gJCnJS#GzypH$(V$;Z7A} z;_~meM|M&gQkvq>Hg~mRE_a*!@W0UDf%i`q-)ZqbnSbX$plL!80Z?Uv%;1c_C1(EM z<*%&SiPVAb1l;R?%@k04Su~M;CsP`gMvZwd8QnMcsq@2#pwYM$5~~X_QC05oTM3bA zV~x`}n?CyHkMBrt2RwY{bRD&DNBY*wsbb%s&$jW9`PwhH-Z%JeZip6^2onwuyih@U zwV{Sn$P4~z5C-`AT1Df}OJJ?>K~_JJ979U+uedElP?sUZucJ_psO3{c-OAP6u4?{~ zWyqVqs9=G!{-TmKFDj+;_@B@DEGxck3kH>E$Qc`b%IXp~nd2KE`Ae$gAjqFwl@`y= zCf3>aI^<=Zqz^U_9Ctc9{1pglK7oVX^!00zzej5ypRzL8Z&5TR7~mckX60%zp6{&R z)U#{f_;SzGBj#WNH4SiZN_KbkQPxezkMY4`o>DeWiVU#B3IhF~r2-+#(~TNbGcFSK zd@<#oqxH1sNG;wjD_CsjO3`f?jGtbR{A-jgI_ni0i}ckzgqt^M%$U0& z4$d?42W_kD!KGYY#L#S1@0Ta%V?Rg-F^uQO^h~t$bzgk~11~Yoq1WE&Q~d|fxINaZ z*QHrQox5J<@HE^)GTA(EOvRH(TLz)xf*_*&Kk2kiC3Jsz55j2-6(Wu)#i!g zt?|@xw}QE#awP6y4S)VI-*tV;9+F(!y*=Ye8k%vlhso6Z%+I-(5M#}O(MXAPqot7v z6*j;c0deWko96dII2Bv{*Wr^zT%0_E3BmS*j>5YHclt=U#d`BB_s4)#HWvPMWdy=%)LJn--8p%7E#Lhp8)cx0w%Sz)c(!x=VVnk!%N z4X(>hPS3+m>nKCA%7S#(UIk2?mOgjP%e3OFbQnC z{!Y77_HU5nv31KOkRh^4davJX(@sP(LZ-qba#ILc?~Kk2DRgwc@Y!KhmUZG~(_1gJ zk~cuEe*H-K4=8OEy$YwK{(!v;?Rm2BG6cSG;y5Je{OS=N_hmTbgi ztqRlFmJUR`S&D6fvcyo&5<+v{DbC$CW?7HVxqYwc8P_8HB71E?AvB?#NU=Vq)K{7P z(Jf*AZ+3zaPV#-&x6|GbUmN_nU5KV>Hl5m*Lj~`s#&Zhc^mB%L2LH}3-u`}_kZ8^J zZX+T;5o$qEPdrsx_CZ~SN?72hn!DB=vwcu}?{AIOT3`%#Gsh&&nkL53S9DF#bs?Ji zK#1vRui=nZX3m5$XA0Q(tJBT7lOLgTO9>(p&&6hLWH%j1*If47qauDwPNJ?o1W%Ux zfynFTw0_6CdTo(8bmdewx1@f9_FRZAJ2aX()LhmO!qNFs(s{n?R5VYO8C1I+ zbY58COwI#suJq>$QW`e^v*ON23hzvhzNw}Bvv0eduay!s@2_h4elp!MWZvahJEvs_ zGuZy@i0SZUn_;upnu}A50;ZRk!6q(;xG`1p>DjZ)FLylhTp~B{K7XR>yct{%6~h;z zp-_#+KMC5gOr3X0zcZF|g&%!hHmLlW7u)vY{*>q36`B?iANcv<7R~XITIN5pvPAys zP3BE=nwRHz33(NgR~^!VFIYB!iL48wTp}O$=xtL?V`G}!KV_w;#}*pJfc3Y}3cv4vrP294qVDGoY8);9vdc0ROD(?~qv=tg~xB z$#s_P6aM<5B&7#QKIggY%d+^ETz;v}eG+&1X8a?5{Xv@i-N3{~3pZG5^=(A~R5amt zGHKUSv1x>|oPG%q-mNWdDeb^&Jv!%=d6+nKX`xRIt@F^Qw-}1-v|Hm#c6b4LIdQ27%TZ;O?Ly?; z3F3ghcSR5fb@KM)Y~KdEtGrNwZ%r9~r7Ty?uO7F3ufA!2jEQMe$3pB%z1sceI|01k z#pyBBqmfqg7vA*W$>7vXDM{6%MDAA>uiwHlEN%oK;SXSO7-a3G3dBtcu$)1LmCn+G z_^I-msU8)7&?>t}onSQsb@0^V(9o&=n+yT5orM!`9MrZf02x;o@InQjO5$W+_fnlX zyTArA!k&ygx3v}V2Z;1P?nh2Wpkz{YLqn#Zj+YngXMy*i0vf60Gbr+0Y`}#62u8{1 z%LD&{%QR=1fpk}EQy4odAbQ z60XJHxKRM?AP2?*Hfh}u=3WPR7m;Nh)hYwaDFfo?glpK9e8HduYlt^sJpKl4|La>} za(^IA2Z$n|&ckG2Z(2iu?*P4{b(h_sq=5e9qXg2tLk*~1TSp)#jC*(B2j&bxjrrjH zs5mVE@@#j8TLyLk$kYFR_N5bH&Ju)!%)g)jFp$U4vj+}-FLjCkYavZ+u1sp#UgS&q&sJU>qM z2Ds??SG)k62HwF07hA4FS{N?U>H7+$0(XF15Rhz3)Coo%<`{)Skokrl`?C@xY>X++ zXh04Y2q*m`G>t$;`}HDg<$}B7{rpJ@P0Su(n@jvhN*LgsQxb|MH*F^CD*eo8pEUeF z&H%jl&+s7v@VB8O%-iaOmYWPllJz0H#E9=H8h%mw|NT`XX63Bm`z5H<(Q3_y_IA4P zM{OVP{O|vd7*7Z185|sp=PoeO@nJ9euACS@EGAWXxj4i9<{70R@Puk4vqsc=P1wKA(@DVmLq$a`#x3Y*0B9%0$ zikX0i+P*p>xDbp1KaBbCfhpp{(z!ew95f2Jf{*C|!Hm;D>eLko5BF$TK27Kl0NQ<3 zTU$5)r|0kMYXDUps@-Wyt4L`aZ^?0 z(o}tj_NXHz80vM!;&rD${=m$;s*j(hzkP${ ze)~54$f>O}m0OquI5MpNmBkAovoD)Xw9Qhj8@%l)ug4`1G#*ToLe20Vr^lI~wmjD! z7~zC#cPHL&eel|WMm*?WZ0l=4F;svqUOzB-XBJW6dz5#tmK{F3f2n!%XWzm0b&MI` zZ9@0xVz&+8>Hi}`t1H?UHHKTH56*ERj=#POfJY2if|N*s_lqlQI9rq3e7-OCl6J%w z76iO17RBm!d(%t#O=V4`4m!#E?k?&_T&?h+y4KYm?r@%?&hg58v*ZNk= zSVFwk_!vJQA3r}o-`MyEX)G^ISAu4!!c&3&@XI*Q$b;~f%-3h17(Xte)i)rJ)Jh!2 zT;`{Ku-VM_S=4?7VN zho0VSqbIX1-ruRG9NA)G1%Kl$n}3kLQ-$hFlkca5F6`RcYQfChj0xGCZP`^-RoQe> zVAk}JNY@rRhYzU3R(mXPt${}h(sFD+%q8&$lHC3Ld%R-!lHF^0?MENbKOW+d`K0+} zzO7WogC0C3Pr1^#8OtD4FU+^JSX*5KUL6aHz{NXsso@)YRkTC?ThaSBpxZr}f*`5= zL|W7N%^Q7v{WnU^#5o|>miBB$xDNw$duebGh1;d1I>)98>ReU2=EhR>YFs?JR85SMFT?{)e2L!pp1b(^i3{B)$t_dCrS+h>)f`c~W8$L{~=#0wMNE zq5zRhMfT#GjU%4_`TR=yd$4}86Y<(+zZKiveUc?h>gPg=7<|mj;ZpskY441nz-70jsHCE}yu7@) zqJ)jH0-~-f*0vRbIt<|=sI2cbV26L(?zneBJ7O2;fZRBK5XsKvB~wXcO6t&glO$vG&offmgNGMhKOR35T1chV*Z|UXBb>$A z)-L8g_HGt18z( z`_w!&`{&PZ{%GYHWN9GzVq8VXzyDMbOqR5^VZjhZTd^O}ZhgMi;Tj=-nuMOe;Ew+5 z(7b>#b6+mca4z<`qdmLea^eYBU}c@<>W|*u?(WZ92-aD}R{;>s(FF~TctJ^taxQLu z*0$KH(t`Tdoa*Z0>gs@RP;$#2_(spZCnXaZ;6TOvHun38sPQ}hVSsJS%IbviXoBX0 zSCxsC_M2D1ql!;5v)ZSVl5z2+K@tqqzJ0M-GuB`B8d@1A%lB$DJ+eKwdVPq~(D-Z~ z*J*Y$fC|=f8r@s#3-t?;DkL_&2O4eXmHs4X&}!$wU+QLf6#Arko=kJ1{DQtpf3P+% zFfkOfs@^p;-7{J1w(9B0$Ung4SHXvl`&}sA2woU*?i z*E7I0gFLM&&8aSK%+CkvD1e&0J z0!igT?z!)yn?h`6S-7Y0BoX{=MtX@p6{mQroT6|sY`EC_;^~`TC;K}3l=_VrJ(lE? zb@)+3+~soILl2-j51<#KL`!DjW*xo#wm?;tV%5gUp$fRQZ{PEob64*2)$T(FRza?> zTgzd2O-0hP;^IX}hLvbZl{0weubQ>POgZ=HmtyZ){#3xuk=$-mkq+L*b2iyOBpODe ze_7DWXKp7V1^!+RO-+ESyr`tSaM#xE3#tUDeqkTH z&)K!7{-}rv3$UKj^~i#8bs0RS@Xe zpV%IJ#!fP6Ne!R=Fw8(SC^6-R&(1ON<@PA(b9B0viBWUIL#W!NRJF=YgvM~G(vLy< zT@EaF!8#&0G6yp3(}kjz%f6r~ zB5w;r?GZ3ldkHywoMt{gK7VK+J?XErN6Y*_oCT)M5(8B1cwZ>@EG-oc$`tc`S${Yr zd&c!}%zs@JB4!#Fw*HpT#yjmt(PjKSwKsXu*-@7U^s>>H8ZkVfrcd+iyW4G5(ZGL^ zC|)6tgQdB`^tHsZByg7M><><(iys%Q97fBN^e{{rYqE&#i5qPieU9xWX_!tU8?`Qha##>8)`i(&lH!uE zpO3HLR~MV$?%m|%SsyAc^>w4lugS=q4(Vs;RfTWatfHr$W{G&WOD?+fr|tM0vRxWv z&3~05tXw#Vl!_-DmZ*~yXG~|dBXSqiat;Jkmkr^wgs>zHE{0wVimaA^i!p~xGmVwL+loU?B;0NWlGC2v<@#ZLk8?u55NpqxF`Kv2nmKB;dIG!5w*~ABqHE1wDS7(YjDox`p z=8p<{W$+Co1*a~a;@K#zH;Y$7?@Ix%%dQ)Pj?de^`{@YUnW z)tSH&lvO4@Gr7R><7c)YRKXQa1Z9bT2#r0QeQBtVi2@>q^pydm{KRpZbT{Ah?nl3R z3LCOEgtk|FrjY^b1=p&9hfmBy=Wes~r$({O1Y~>#UVYOEoJ?*N)Q|+r@T-Q)2Y6S& z(m3>j_XT*`BSoQZJ)Q4Q1fC)UR6P%9eEMJZ@^F9OL4<+ldFRH*=O<@If6gA-Wl4^; zPAIlP#aotZZU=6i*Bh^KT@P_1Y!qcPjcE_F&8XC{SMJ0CY+(tD(t-*8)J-Dql6E$q z<2ACq5UK4;R|vNh7%8HG7QZp=b@}iJey&!@wxCSC%)cYTLBP4S=A(V{6B<(R;N_Uv z)H5}VnWgeCUnt=Pu9)8(<^$za-9(AT79Nk$h;oI_T zK7)1-a`~tDG}Q_!Znu_N$zjmn=ArzqqK{`9wM`Tlsxy|I(|sTV^E62g1!)g72>sM? ze*Y{4A|O6wWR{t`^;F)4)G|>s6G{W8GhsBo57lI7{&*F&E=0w=@UXv#>fFun^EWIt zj9H0phPmiZIB3w%D@h4mqmjQ(NOWIkUdAJ-qNj!lZ`{_KMgux>=)Fv7mUgIsBa7sC z^zGI90qvpRpTm-2_?3)%U`k(wyM6&Z9+ff;K(RB+cd&Q3sDapaP9w96+%NLYln(8q zFWBMni6|02ArY87ZeDjfuW)Nwq_Mu>b* zexh*{P4h%hD6(dJ@Y-}32u|_EeC~?${u%WqcHMg^fdcU?|L_z6hND&02M>-Xe5)f$z1P1Bu4nau)De02#7&?b8>1OEe9J=Az`1{|- z`}%=T9((p)Yh5ePbzUF)==~6=UsgNP{cBUH0&3E5hkhyA!kysv*DA56Cnp3>A-}w( z`~%Rhu8WV`eD=V59u|Wm%fCm;g7OoDHt7dT3mPhGWi}a8J_Uc!7z%GSpSQ@#zuf<6 z_0T-~6U#0FmJ7*u3tn{TPgu}aHY`D{C#Gjuz&V&o9`E|Q?{NBXzTuU8 zb+z;92_A0PuaJ-7!_;Ej$?K>+>)Uz+mo_#1`KXbF7g|r> zjW`B`jg5^Fxj3HVVut|RnjQoT<|adLkyq<6feG8&6r1dMOjh+ltP1O8yaQ2Tf9Qk- zEa#a&NjKCmtV zz;{MXpEpxDT7yf>fner>bi04E&^R7;udb;ht-3HYnF;*P@0eHR79s65WV|jIIU)IN za%5}BHSK>gEoqbTrF~mIkO5a=d{Pe@N;MNt#F<4y^_>V9GBk@2OsDY5dO9 zobvzVA+tXHxUTq%PGbDa*5HL>RHd$7K6j|KHHC?}<)g}~O6`jX2^voqD@REU+eh~B zW&MmW*VrIY!M$f1ZFkKt4Z89h>?Kki!dGHrf%!Th)B+Cy=e#D-^3aSM{$+nZC5FpY z&~a=}DU;%`nhEASEN94}WmI*D^-IwQ!PtFAyQX@4Ih#kpQ?wyoTWe3Ee66 zlanJ8Q#i$lzt32Yy;;_FZf?S_r50{IsZWTu^tQ9NQ4>mum{?_jA!m8KZd!7hiM>~V z&u9`&K_`ve${V+bcC(0#M$OnkipJpZ>qAz*pKV8C9VNcO%O$=+jo*!YS4vda`~^(= zuy#mfVHR)A;+pvP-=^G#aa$wzHYxAvxa zloX97rS4Qc^`N)90_y4b5y7EtH`i`N2}@~RE$NCb41$CVticB?U{SfL=;EWa zfp6VQ*L>C#{l7xff-@?9u{pV^S&FNe3)?naT|GsAdO6`w-E?@avX3U%x-;k=s-$55 zy^4PSaZqXTRUw$z9uDv}sP&=LETsj-XT&*MM7702{+S&;l7f8K&!7#nYAyN$HFzq1 zOYBI0bgiqD(v0}d>p8Ua(Bl62X7nG9;OE`cSmF;^m=CoSuh+Y^j@B2}nOSXa;wrWj zWrPX)dJ)QvLRhAg@{o}Zlk zP*=J9yyFxdyL}rRGxQ`njW)<~TDhs9yrI(X&%16a*e{+&)K^-E!XHFDw-nf@Nj&LE ztxs_O_0F5kahlD|^n7DnAHnUo((D<(4b_^q(o=MO9cDtOu;USk^MUZNh2$|mjzALt z@vi<-FVRB3lPV5D&kMC%!S(6sM`ONH_3sc-Q&* zbbOYWU56lJB|9Cc5&zr~gE$?R@{3$D@(xmItxhSl6D4cYoz2y1k4;;sJmm74`8?1F zDrDF~Zyv~teM5J$y}!NqDU}V(zDy0~G&xv&R8T}zGdx?F2pqh^Kla}nq#w>ydz*Pe z3)XL(VBKEUK*xd1>V%HitWKqHMOWQiC9?^1W_V}1{>r3~2s`^5cJ}Bn3aOr#>38>i zf5LjUvU#^{GN_&!YK@WsmA3fm3Y^}?p2`U=`0Gl|(a%5u&xYSEPC}zudH#B@#n6K7 zOw6u+JN!zP!YBsO@UyXRDB$jIZti9ljV--cyJEe$jk=Z+z1=ZZIeulT%V)XuVR3wF zz}ufhG36sQ*t7xF%Y4l}tjFPb)%m0c-X#{eSg#G5`7pH6Lw;#Aa5M8TNUd)mKjQt` z%NilP!6Wd2%^YN5oN7{OV5y1Cjd`EgYJN=CN2?z2xM8IEPtNQiQvCA z)*+N4YiK_8j>7q}rKY5&vM@9@J2E{zF@d>iRtN*IB(n<&wP7c7x7X~8hrHOPF>}e5 zJSOS@!YE3C8th3xz`Wk%>(^cOc1)6=o`(D)-n9nIduo}2+pxIS%>@NT7+h=i+$xdFrem+;bf$m))*x9E z6z(K5lnU{I(9fOdoyZ!FGU0xDftM4x&=N#(Oj*mneZnd@%d}Cg#8u3f3+AcJV@gY$ zh>Oc?^9!1iGES%&mZ1h;!Z_Z`uFV;uMBe}&=VorsN^_sI$82j@3D{mD*E-%KYi6;mUT6 zozUhBS)#Wxul(gW<>e#WgV4ID#9)rE>QjJ+QvWz$C#8wmrf~mQ*j&}?(DKi-=tvJ0i*Cvl>s?&L&)bQ9 z*f3I*K&pND{P}aw-{{!H$mDN7fOn?ZPP{$?D3-py^@Bj9%~ihP>=m_m`*&-NgRe28T&dB|u6()>{#AVn(ylT$8=fL<2b6PePrlQvmq0?3U<0QYJ%Us=`JskV9 z^drHw&|lE0@8EoqKU(cZDLWehvx(DJ`=2jG`{14fWqvBe&}25VXQ3b*iZW{O9hEwP z!cr&_Gd9c)>1L{3SPe{}xWb5$-sqHrXc`#Hs5O5K9VtZg~$nl4X4n?ULe0b^zih3OG)(n-2a&c>N$O zXheECP0urho9<&1O>)a`=Z17WUq*w9oTuhH5uIvA?J0Y}C%R8x&T3r$Ii3A+98tmZr~x$ z-g=!@rMKNo%$z4s0j;Dh3rlZy7~M~b>~0_b{HH-?H2ggX*P;-$ED%UwY{?klH26^7 z(p%U$XXtWy?UQ~&=rZ#Om_FEVld^n%f~&8$;%nL|&oeOiGxY5!AV$Q=R)2uSY|UYK zqNv)<_0w23f+dEE$biM7DDBW9oJ;W}l>Ah;8`f)&xxvXi%s(Jd z+=@uhb|iT{y>Ujj&@sz&kNw7+O#=??On43?>I3+#P3|BJ*h>WPDoh0*sG-o~s*Yw| z6ZI#1U>Z3%IA35}&&Eke#3M3=#tv+Ht)F9&iJZ1M(w|uq7ky**^D!JyiAa6a)11JZ z5SB1XzqDCL<0RY8CHdiB(7x03gN|D0GuQ*<;{gy88MegW*kIyynY+13W~Crin91NT zudwa3hu)_p@p_}nKr6u^FsD(zi*wco>%Pzq})r6L$ zC>3Aa|wke}nO||qZ_-73QD-``&sKZhLzF&x-2ilJKUB%zd<`0gX zNitII+!Bl^C*HN9`@)2|37?#xyzv-rPzsq ztV~amjUJsn&HGQs$Kv}W-)y^bwRVCc0)V(zEB-8z44W`btk94>wvY)9M%we(|djd+I8Fh z`R9yuLiZ(D_$9lBNQ;(0tJX-Bj-5?Hn{CF)sse|@2M@3sLJn2~fXDxXWFCWW$uFZyfg<-`Fv}1zsM3UP?7J4wr!bw8cC*d?NWch|YASK$Enz z_~`xYYrZefj?rZB;%%L#H$|3K704U(h>;@&=&1jPtvFa>7-3Z;yv8K#?m#axp6u(w;T~mzX9j4nhQ$qwSVdh{m6YbD0%4q5g9I830uW$~ zu{4Vv)tkvV4%EIcRloKD2osD&Nh)xtb^)YFtJI^7U8hHf`*@@@c{ZMD(M@djHH-3h z1lvXyMs?V+rQ|}erF&pEuz-&+kasE4Vr(s-0^o8vH;-U%3`jq33;Z6 zs?9r&4CO#HH?cIU;ORryZB#MHxXB0Ake|7kK(lPrnEphs7{b%qYe@_Lw*bBha@k8W z^4P$5l{-Xh9*><38O#a=Z8#UHH#&5uZ~1kJG>YHl)7+IL)j%x}tUk5ST~%ImFe3C5 z5v~H!7DG}URUpz%I_r07LhU<#hq$x4^uR2}!BqjXantr+`#%D?!p8S?5Z`_vs1xh?sCQuJyu z#|zMBT}$Z!YbV1U2C8>Osdyz#~3!QHuou*%?FEOSqy@B+XLCHD-jxNJ}H zCDBU8%eDfmDHgA}KJEZMgc>9YLy)peTul3xxgE-wM(8FfizgW$fJ~4i^+B9_3H!4p ze ztC$Eiz?98~$+khOA<%^~m0?kJN)f&B@Mx!UIX z4j}}PdP3yHP~frN&^I7uoR-5cr54?EUlGW!UOLtagZFP5RqlGMt#s6NUh)L${SZIb zhK-TrB)M(3jK5FGeRg*OHXXxXTKhVACa(B?QElod?+@fY@q0}ld%futkC z1}rQB0Av#Xh+TAH|03X}=ipKTtFAA|XgCMKn4wqQ438aokA8`__}AZPsxC;*Z}}Ov z*tz+YJK%^Ac93WoGkTscQ|C~l9OIcq#d0LjA^(H$vi9`@q`$zSSO-Ak0dAKT!2655 zk-Py&8=iX?PsGHe1;15>pPlMdl#>S3YHTsfVEk@Qn?Vu^T?8BlaO_a>O1O~g%s}fm z`g~04@K?>*bhzH|zMWl|n^1*l$Sy`F5I;p8&HSeU3;;R$#yx-YK|7S>yE|L-n`UMf z7I9w3h$F6IJ0m!dKrM^_2<#? ziE(wT1bXo?#H$9GjGaI@Q)GMqkh-0ylU8b!{wnl%B^7XmQU9!Nd&kV&9CBw^f73BT zqs}~aCL3k#Uh72Z>S9|A$^a$RQ?*Lt5J1cSVsanmoZq`0E@?@dtY3f)yZtLFX`5Lk zQNg0K25%nVW%$s>(R(Z5pX7M!E;!syykq045H+>&{d?cCmK&T%i<#gdR$h(9y|rn} zsFvW7;2>xCNe|+k3%Q2yTQ}s<%ga%0lW!s+6eAuXF;`|^GLn9i1p^0E{ux-u^jg#T zc@rL}GObg;q#9V(C~d5&D@#v@!_&7mV64R#2d)h)<4`LLcUrhe%#`tRu>wuwRopj8 zr{SxMEPP(7ed9|C07voOC*;L3h4X)i)DTaP^B-7vBJNo4qkR02YW zDdF;!+DiY!w(4x=;N5v{5$OHkI&i5JyS@l2`@Njr+M2%nyL6^0Qm5)zhf>|kmBX}c z&Jt)Lba5R6&)v>T{4|>)4jSZmtNCo=Q$;!Ls`=0Oqio)N;Cuqs@on#7vxPPE0gx^J zMTky$({Fm; za`bR;-F9wG=LJgP==S!WbOTdgN?;Sza?hY1mvS`}!NmLg)MnE_*V77D$;wmg@=igR zCypk1oJD%no>_bD3>c)ajSjTaKx?mm`50_^m)C13$R}(~fhcYDob`s#7w$bZQ&GVrd+z8oIV>FedcR{?8zLk6nmZj;`m&U6uEX+1T;MhBb zA}Hcl)F3znElGuazfMk0e)a9n-VCrdslwZcuE*P|Kt*asfC^{q*p$!Ivy%eDsU6~| zw4=0HA#9puF0_UqiyFeFrc8W_nv&eKylPq1Y`*F&snf%#B0Gs1MSrJx7S&sE5jW} zJQCIB!|zSx_$CqK1M;HVE@J5(xwV$U|3KNU@(Uu)*LhE^R^==%jw4MW;w^{ii5jM> zo{=+u5~McOO0Ebrd@j!ix2(B zo1%_ar;Tgs%PbR^nn|1rnMwo8%p2~UsucChpSJ${I$i{;V+V6ia|BaM1sME1kn3r@ z1>^qJjShmU8rEZk=ppHi;rIg;;1zt!#q+LUMWatBSO{2j8>*2UK?@sqqIU#;N9bFia{YZx8b)VHVuqz8VIo7NFl%`3h zK(ewU#YqQs33H39x@o_?qp{WYi<-$v0K@B>xjOc%aCYCkX?fmo!wYxc!rI)@)GW6s zrXE^+%R~$fH~&ioeqYQa3*H8L)mjmOM$3JO!CAYJyZp^BpN7x~#J8Osohx|x$JBy1 z9D*wk)*b(6Hk4Oefum7Ypx#qdrx0Kz#rfIH+7s!+Qclj{Va`%pjKJ>p^Pc3Zmx!-- zl{zO?)zv?C7*1lHU;TZ&ySxPCSl%1}^J3$}R?y?kdeDK$-aAJJ_KeL0svIi_1Yr4j zoYKr9%relY1o$Ex0PyJT)Ns{%OZ?f_GETe?|I_=eue^L{wPSAhSU9SWdXwXR$3$q? zAiPX`F2gt|tju4Ym|1?VfpS>E?2+l3&_#eH`0!+T*&nDF3}fPJ_A1>6g(e?qm+0xa zQm9~SHvp}+5@PQYwNPz)fuiQ0|H1aPD^~(&y6?Z;Z~H`HEk;*}MCn6b?G@1)G!Z$q z7^$kNex%xUH#CZ+Ii+xog;_&~My92nbg9{Cr>8b}c>D?u>=%J=#(?bMObjox9-OL5 zygp-D8{;@;JyYEa1$`jx0L(YCPMgP-SHU25CMSsk*j)hpUseWi&Dmw~hSTod!d!rG zyH)d^UM(;IU%yJgbQx5pi&LxA|10r@nFma;p47poQ`&t6o70w(2Oh1u|0F|C>p zv4eYxH%%~M&w#aJuC|Y>xQPz$5G;?hiHNn|Xj2#QTn6-Ide83m@pN(wgz;Acn1M22 zbD_=@VH|e1Uh|m4?e_Yq*Z$BP&J6v}kXAg%(!NPVK6(!~hx-AmHzjdc^ zT8tpllyBaK)@b`x@Ib%DqCL!s0X2`v)Hhz7TYEJwbcQ_Dxo#%M#ahhj+e=?dqsFhv zTMZ7?m}F$*A->}G<95q3GFW53?6(aWuRO)^g|f5v%O?#pf9?0s+LZ`E9%)IX1lGU( zc7fvSNOxAadt={;V>*XTr zYS3QGoo(x{rHRz550h$Mc?-CS_2?QoL3J^P3X0PR#uOo}mIQ0lTXJzF0AIEUk#VEj zQKwEW$cbH(r91SgLnR;oSW6W%Rdd@cM*>rOIqT5wE?~WY9SxrDVL2yxrdAN;Khf^3 zMMF+LBa6V*6MwOlYzz`!_MOl|vt{0kST#=3~~lr+_!7HnU?SyYMspCEfxzTF=k42XQd z($;g`L~*Q7FsZa9uw*-^|Q>XM5YsJYOR;ZuZy?1FD|6 z`{OS}^C&6BI4-}@OJlD@#%koyPg~1Lr1zZp<#DJa_Lddsz)6GFsOD*FZzvFRvuU9B zZGyj5?xQw;M+Ln9!?i0kbfh>z!ors@K8Ygr?iZBJ)F%gGsOE?x25~e*bEcJ3bU=en zKJT%QfPHq0Kh%O*TK~;XE-MNCBosqEID|GDDh>)EYt3Mvx!`G#|1Y^wsJPt|h1%qz zYVTxjF87LWg$fpW^~wcf9o1-ond{g4S0Y(knHy4jY11IKrh+XX0$m8RI!z;aa<5v? zWE|pU1dMdVZa7icIQ|(hL_WB?j5Q08FIFC-7k3g72PHcRYFLy9OJbkC{n9-6&*{Kw zce>8+=-6@B!aglK_ZhCG?WNB|_eM_+p@QF9aVvMWE z4Vnzs>BcLJu87PKULOm>Er{1}f~>940V4zGwNvi{esYChfIjEWGqlSoGV484l%paekh*aK5!I z!v@8j{7Zj376xENmaL&B6Gs*{cG6c!Xxz-`@1&B|~Z82MeHwO zEaKX(Vj10K%q=!i)kmgYD7SE&6}h_k*`LVD^#1J|=f>74@KS=5sDua=#gU3Z9P2Bm&vmww znd*_d>XU{o#Nz)tnLn0Ds3@Ry3kPox@uNgV4+eThMn-zz2BTp*Y6RZb){?Xh%y26pKyQGe)o`fqjcGb@=Y%ZE^5!*k zV;T7C1s;T`-^R+7wvs7*6Tie@Hk0Tja*vM#6x$MYx&yx(d?gH{f2N@e3b>)!4}e-A z7F22$h3c6rUw&}5_Sy#`gXBMa>g{1?1FpN!@LVE#n-INE&9|i<$wum=!L13XVb=uOh9n{fKTa22#MPdyHM|ULd9+H95*HQj%wZJwediff`!V963C$z|hdN zpit7V2!|p;bf8G$0606h&}`domXEgUvKt>;dyYOWADL@oq&EA_o=gn}GGI?G5jdgB zHtyA2*Ifwp-O1fTyOmwp-;fL(f^wc9%p(_AZv!+p$g^%%iE&ngaX(4#hh&e?hshHF zWN6dCVn&fL14=q+&Vl}k-lyT~M$pfsmB>A;aT=(*^uI)627gzVciUey4f9yhesCFY zZ#LGBB-c66eL8#x*IT7<2SefKNC`rV#hGjvKHV-Ii`np0{D zxgoTVd?9iKP4R8Ik!7tMlxct?k|qa1N1m+Zt2KG9Nn2B3TJGH#1fOO5$lB(X?7TaU zQu!_5P&@q2XH}7xS2XEyCqr3q@nV7qHmdBo{u3;fIq7-`uU$ zQ*mihhXsxb0ci;1R1mX2;e(zZx5<4_9Oz?a+7i0?gXiqpt7u{lqg8ot{0(>BJ-p^_}OE^|O{qU03NqbF-u>%$rQm(7p>mVS@h z-6EFjHNfgV>pbR^^U*{ZG(@8UF5Nl`M!;RI4z2&hIe-n=rH(Got}NX&o&v+tD=aMjj=Z~w@GNahrV(}U+X#^N*KA;%`q!*XMgF#M8`eG&v1y?A zA{6td;nJ&a3d8v7%uiUsdAeQEn?q9Pv0&Z%jmCo)i*hXd9|@kqzNYDY{7}0Alsn+q z_IfCrO?;hoBE->g^dHI zwTiho#x1AL!I>e0+7i=CPXHRNbq4S@15Ft*h0NyOlL1%*sQh#bDd8Sp_TzH05T(^Q#*3q6KtreTeRzZ&`&TDwawBc$MR1 zrltA+eiaKWGaEkc9}}KsrJ`v(xT@sbv@zG2J2QXLR%`YKo|T*`Mc=zXF$D$5tHc1e81 z7`2#;hov-50V<`maGG4T_-D8Npp?|4cSKRaFT`r&hYGwpH12O&-TQH>nr?HjwDz8i zkE{+MzNg70WGy|a^G#ghtEqAS+ZHT|laav;%p|R31ztvtu;QLIaFrip2FbBDUz(X7 z?Cw*NL7iJ(YQBkgUTCxlFbne&-gHGJ zc)A^cK?ON(7$J%z5tLOo(Zm5#tmQm6m8eoKTMXn4Ul&*z*nRwP^NuA)ai4bK!L9bE zN@k!98y~w844iQjD|r7GsybdVjZQ0abOf`)wy5KTjs~h5hGxHZdEJYJrA(&IW5`am z52Z1LRkTXgjjwebeZi*OPDNn`o*#@y&9zZbnpH%k7cknjz+Y{l4xDlQ2Uqg4E%<<0 zS_RJa#0@~}0jShsfm@gxztr62p*Z9@d%yCZtYm*;?YvJzYde~+8vNo)(t=Shh>Dj! zNp-Y$z9QUU(4OT%0_Qy%!vdrI|xM8<*?!{fW+M*W9z|m~< z)BeXmu+rMgDHgx*61Ifh(L4m)Kbp_IsVrb+q+Mj9N5!C0_3+JXxI%bTn>|QGS_(Vg`R8xycapvHU@)O zG`Bta4&P2s*k73^C1qOZZI*0(U0nkM;Xvh4*~{X*$@vKkX`_{CXlSn z{X5Cn;0WjvftD-IZUl%pItD_p5nODzel8 zh{%CQplg7`XAY68VrQd*%zLRySH|ajg8cs_4RRk2gYbLJ?T+G)JMzWdpZb&DdW2&1 zs4g@(oRdH8pT`IS%39!=$iFzxrSf1dZWsd~ZRNZ!T*#lxFmTdfeZ6Xe@v?5DJ;_7# z=7JkALlvQIF#DFveBZ`gHYWl2`fNXk6&F+GLyP9Kx!&KhsLPdCzPEN=Qn~*7<^bWx zL-tRW}m$b>}tfmo;A z@Wf~p(fV8VaoH=Hg-c-WU@=)DrmXNYK;8{lg?`Xj_ZA5=H!+WFTLF7N@D)TBA zJ^I#eMcT>$nSPRv8ND~Sg><$+1MkQ27ES6Bgis$5>* znI~_x&vdmh&OHofxrE?niiP$730FJrC0smLsHs-%B{Hi5R5@2>8PL-X! zxgJvC`QK8z;f_0M5)~UXp1qWMK;31mSuCOl_AWf7s-=qk=rXyQ^iS@1;Ev~l!6P5^ z0@x11dHLhKfUbAlNU`(AE6s`-id;wUe7oHzB+P-cWaIunk;_2P0DsG*5LOK_!NX=3 zK7G`PwGn$?clEbn0vI?$lb+QA8vXY0QEHyX2~6lfWL_(!%lokh-#`ggE0geZK zqPs9B!Co6iNzIcv9^HFh2N~7R#}_Fqcr4%bpi-WGQpA7WWR|KehVLg|HY!Rv> z=XmU*`$;PkY+4mA%pHjQan*v&ib!xgvZT8fUz)v+#=BDPfeZ}*YthS90O_Ifw^p3q+Y7!%>np% zfcRtgRb;aqt8B*n>{pjLG(2lZ14K1=)^_?4wicWGlm?ObWw*@wEVzr#(7HEyatXO9 z5C+SVizSG-|56r*MONoBNuMq!_b_JVK_>7zF_~Mv_FSw4TSaExG%yfD_)qKUi%|1RRn&u>*d)pj<~s) zpcxSSUI5b=B$*u20^@1};wubH+g+tE&vpa6<=Y6m&pmY0dVFksvfi)=Ac5@ezLbb0 zB3dcYLv_ewi|-@LJp<;1XOXpwYqI*PwvoCP3k6W`gtd5uJG+6q@RYtD^BPpj7~Q{V z=2f2*jU+%o*PpvK3J0saEZ}2r+ET=6qQdeeecidB&p;E^ecdVGL>-_eR{uP zTk8bmX?)DL+s8iQsy>fzzZpzpY|{4BB-?SnryN=p z62Q~Nn1-9TQ9ggM?2}C&QaV#xl2CNGvbV2Ne4O zzhzQ#tD^%L$hygLvQn4n>h1)8?2V~93mq8XNFVt00u=9*F#k5a*YLq&Ra(ufI&;~? zYLz}JW?y(;B^dU`u;-BQg;NcpVKZ3bTudu!ykd$>mjkXl#6*FoGdE;DwBvyS2nZX5 z-T^ov^1wE=rNk30`4F7fdoB(@LSp|wM$N?x=g|-N{lu_%P3oX|=vfD%)f9N+G6M|u z5{=gkslAxVsblnl-I4ROt^e0;hh!28M;W!nyhD~`SB0V!mgeO}d@m@j$`1M{9Q1I= z{&y9Qc}xtYUs((#u?|ro+{n!>fwhEuWq569qL~hjuS`Gp5p;Ms3Yd=^baOiW1#tF_ ze=F$sA!?5D^6X0T^1QSV@^~jIAjWmq{&QF$=srCYkq1uL`)~ao`7$p+QS|2goE7gM zD70vl1F6e2FfMw)Y;-p)0AMq>urQbDsT~Q)d;c`Yn2(;Ar@LIP`VI;^TC)?1#Xk&GK4Ma># z{QG`&W9`RibcFxr(Z73nR*@s~2H}iMj~i1Pv#VjSl%J%N?-I&9V7+62ynXYIJR`sG zv9&<#%)You8GUy8^yw4FrGgV5EvPWAZ|ZzX@gc9z4v01Lwo8gj)l&W}b~rwZdCIG; zJvTR}tzFTaEfd7SI^nMuQFgx?htsS|RS>%J$%|Jze_$ zq+(y>7j5*KPhX-Y3gK#(RiVe^<8+)UdPa#$BrXTLw# zT!XKnw?PaYaUf7}2`q~j^5k$1RvykVe84potWK2>>u@&1eA^}TG%>0OiT?8cA{sJ` zciq5yuxIn;UVthUkl#&yUeU|^B11Gjh7P|%y!u5S-IXGbw3>TlJ2Oq zUOP)1373}E6e--mtS1jin1qX|oVrR&*tV?b!`hW zI-+CS95XMqm62b#mq448wS-_%GWPbXK%1)*K-`mwg3)klN#;Di=Y6T*vXNXqa&t8$50C`i8nC7fAzudMt5Wyh2FWE~<8S66tOM`S+-(iBKz)+`W+Co0_zmC3&{L z-!hj~QxE(jmbEoG9HGln$09bjW!`_H5dQAx!5`;?S$E@o3%+PFN~eMI281EEaD05< z`~uZ=3M$KUXd_)IPu2|1^EWjrj$kaowplYAGWDL;U9s%u-kaTE-&oz{Z$+*w?IN^@p|o>|dxAsG zrIq8vrjJG2AC?SttY#aV{&aZ!s(}|-KlkAOOd)#A)9i;9d9~U#sS=x9aX(OBbB^b! zGtw8_TCkld!&`V1Dtx=XM@ISBde>ejppYL8x^Aq)1izT1HOH&W+T%5oSyTuu-gOGS zH^GFLO^ZNU>(kaI8i#fqHh$kmIFwbZ|XSaPWbNVC(05x>1M(phse30n{}0TP~7rHBcX;RCcJO3 zQPrh;T0HBHBAc@%A(_qoTX&F%Y@WwwN^it<$U>7yxySZw+fP1cr-h$Q6>e9%-(V=u z9jaS)kK%4992D>hHO~6+_?KT;nR8MJ_NjSVqr!z7wsx$~L_YfGWN5LoUCp~)Jb!U@ zc>DO&ZcUk2AO?j7wyRZpg+QR3OJ8GAz2%M4s`zk;bz96T-=ksq40Prit~(JO8>;EX3vMHLOyI*8_C}{8v-Auz#qe;%(ip}i`(Qc zcv_y{5)i-%5gQrTdXLbi&n%oylot&>&luX|=)&TDeK)Hf6_2Q0(uCYyJd`ccQW&I2>-{D4FubHCgh0a)P?WT$|}C@zU#YN&T-kn)^+ncOqRT{-x7cRTl-Hd z5?-zs8cz{hd=`DkKSgrF-HQxX;l1T%vAP*o)$BPk)OsTEcB?~CW^-O+n~##W(KYV& zVl?bZeI=uCV;R3qzmH60Pt=N0k^!SZ`+(1>3#Wi+uDV*X()*h4?Mg|uS@S+Q%S}F- z*m-MoH%+7PHb~mIVT@>{@aj(BdwWikb<#v7{FSMcsyl}mvM1!Nk_&6|IL!F9ro;2a zcO)loy;oXT95~1hbJh4fus%qQuNYvd0BSYzMjlxS+v?ea4pSaczxrgciEL7Cd&O(# zkk5N>(w=i(eg2EhKvG@V^d@tfC1Bg$%N@9}aW6%hq~bLux!PrXSBmEqv8{`Gu6wV{ z=8@*2rpNX!$8DK1r(*&YxkdGhlNn*QO4WEDi`IX7T4@@O zWOB5uwl(fC%2Q~^?9S(Rw#$4w$^f0IW#4;nvvAz?w}~R0Fy3ow_$wrhF)NB>-h5*W zVNNl`&OoNG=vtTtRPi`Me?a^bQNR$wLXt(b$(Wz@UtPwD$Fz?eH0-yRq~8cFWkRe` zVqEppYk8+nUSsIA)?AAYnlix+4=RbjeuGL&ca@3$NTV+Do0q;`(1gZySt!u!@0#%9 zCg+)bE{;V7A9EA=LKfZL+-asn)Lvi99 zZT29Jy8!+g9h_erwqyt%3J#-WLH2yeKoT18?%`_zz{4_wj0^&sq;QgCPa-82j^&#) zb}YslOk^d@=P_zTmCJ#(r#o=~Om8=yR(F$Hn&t@%=Q1fNnRWlM%l#&;wQy^4a>=kJ#}Neq>>ez_>}ztC|Usr~U{$ZY%oPsnh=L$Ac%x zrE9|Xa~+dU)TcIXf^Y3#4NM(t;SKr=_x5rk&-snlZV!>`liOJfI-%dn?jYFUiolsn zL~8J2CN3MBtVPW|-E%^nxhBym-qoF za63%#5%L!<>kh%Baxw4O$@MraG#V{#hY@Zyh?$nw^a+`t@dgU!5+02*{wYD^0{-#N zv>k3ZET4YGD!KD&ED^gH264LRIzvJh%Nm|Z#b#uTl0AV=~weHNx{De%ta zVMqCOYg-#-#8+cRuKN&1KH@{~3mhqalh*%k>s4|AZSwoC_XRGVF5Y$N)R7*Zv5M|~ zq?3?8ZI*0#1~nd>jaTX9j1>=z$3t-Zi8IRAGL2ML=`X>nE_a zy8#M|FBVWq3cQ6=Oow{bbgv7qkUycAH8nNFbEqYZFk}b)x~v6mGZdai`i<&w-*CjT zT+B!xI)we@L*$ERZtdOBq^9Dy7ga7s?Bhz{mJ%#xDZJHcBds;XuNK8w z7_)rZr!mw|I0F?;DbKq%eQ}NlkM-P7#wM3;p2LEho}ndj|Khlc=xs~1QLUZs|KsVa zqT=kDW^s3dy9XWICAb9$1eZY)AUHt>cMl#QxVuZx!Cit|aCaHp;h#6(S?7wixSB0b z@9M7V>dfHw!lCn9BT{508$JE9P??@NLTJsLj}5KrH?MwaHuHjdfDSazPjUQ15og(u z|BF*mHJ{`THfBzmn)H=4D;9eakF^=qL^enJIIT@4;u-jLK106$Pc1}8zTp;$19?#s z#fK`WW4>FBU+Raah8cc?Y32;XG%(1~o96iD@sDXJ zw|FX(ba60d{R&t+!MhZtdh07%Srjy4>%{(5gvj&23J$THAl)+(w=a7v-zi-yyH>%666FZEWGdl3Fv1^ z4KU3>3g6j&ZNUZMS}x%BmIveODGbtq=G@)yWFW$;!Db_EckBq&I0Ejm5mXy;I@s1h z+1-f&2c0ymFdeK%0giKz-Q_GxI9{gqaGg8XBT#z5 z*5qHP!2_NtCvKPsjd4VAU?a*i>u^BlV;n9n;LUEfCo}d&=nv)oii5cmsh8AJ8SiaZ z&0F#3L)2YfhE(Wqlwt1j8pn!%aHCai&eK5trAG`jaujhG(kH*hEg$WCL>K>|zP$H0 zCk(Sii)EkW;`_{_L|~%pWcAS{45e#Ra{6&!lVeLrv{E4@A!16}Hkw88#rft0(!3K1 zm8vmUFCbroC3}}KXn}kuE~Q{UXu}l@3-{;XJ&1QvJ{%^rK_~;JMi}7PbPj*tT!!$y zLdhXA?8#>{bJigY!2AO4yAEJPv*cA$#52>N6r z0xe7!QL8h=vU?KkLF(>CaWHL>TMC>03ZSVJJY-3WS^4Hw*cKTS;C?#ipN^vah-J;1 zV@_M?uv_MU4_wz`hW5!(hnF!CK~F{n57dom8+fjs_CC>Vvfq_eoe`?57G+DX(GQY6 zqw0`ceUV?_1b$!j#X5qMGmT!)H@{D$4btQZTuv4BjTHEuv|sAjavg%PIGXz8=Qc28 zV%?D*h>Lj{SC-2vDDy?X#HY`)y2I3t?bDu=MGb z*8^b*&&3#%SdSVXK&@IB*%k6l>nRcu{ijXaEi=6>TZm!B3JNgH+Ux1@@xHM$XJK7$ zsmQk5>}DWK(Z4My&?lZ9X3?0!E*!!LQ3iiKuu0F&a&_bU58OgG>McnGCF1J$x5G+D zlsqOu=uUMhG|#QcWxW5p&n$W#MqUts3NdhHY@0cOb4s7|GkAH3S$tVTar<18D959& ztql+C4dVDJ94?*Jx;i*!@U%TV2(mua0Dv2szzBD(o51PNVo7(iNG(9QsTl znyoXvzP>(z-CG_Cxa|fbdYgKpR;8o?c@TlwUNoQCc>eHi7N%yybp9MpovB;+%r+LC z@BQyKx$6BUmE8Hmxr3`BMW$_brhbLqXD1~=Pbf4rfF$pLrJa-d-sZRpds9+JvX-g9 z8Lbo^IZPV`xJr?kkuixby^OPWpz8~OW@_#SFU0&rE~$j*2I$KR;#dxH!e*B(nyGbn zOE!km)o&0M!m5S=iahY4miSiA{W$OU`-MrqcX`D011&*hiF5qAZI4^CS*S-!r$ z3NlqwGqRi)p9q4%u7ObGEs-GJsiKPG@qfPOCX$HngHEq2hhOUyGg-PF}@~ z7=`;PEG~mabET=76#rh&R*0PRW18VQbiVMC(0sN~{g?20L=j-tjpZ2CC%gp%ggIlE zOLdp$u%|G~1@x-cqwj_*rH$dL1cOjFBW{k1?U`d_3;wZVCf&l2g?#+&aW^UZWWP<- z@(G!~zD_@o{pa)PfFYa?eSrRKli+0+N(X``s<8ng5E`O;KE4Ao+)DL`R?7bV0s(hc z)?8x61TSYI*rPfL`($;M(+o-WK+3jcDtBd@*Nvt}X;3MjQauIL)3q&1G~t2WL1*72 zUbky!yT?g;F@({Ex7GyhIu&3!m;DGMJuu+Lr z@-Qi{Fe)IF_nu;$cgh;wHaPfUmtXpX4RF2uV&UUIq!JzUyN9EV4)hz}1%G9*BcT2d zJmEm^>&QoB$rFNcO$uRDpmu>we{v`r55ILuS5)fHubP{Ql8`)gX#f^5k0w?TQiD@O zkB-4<5ilFL!?>ZGN2KD(41pF?2rcp;BNuaapvk`ug&b}%BFsO@@KC?ki%~>iWH25a ze-&JSi#KHn)Zt^%&9MvGnETxIizl88XQkSAVnfe(y2jjczg7Vm+cRes!v}L>@Zm}W zS@OWw-Oe46^16Gh3`~@ef>;k7oZ8jH5QVgP7iBxm#6fw1;`Y-<1_rp98l)4MFHSLqoxs!P_u#J$l#T>x$Z<5~v$PeJ9tRG2kcljOVAZ zpIdc}-qo9>I?lmUI~#zW8&yv2iW9htxkLxL>5tAeWlh|4v;$bLu`k%Blf|GX_!PNn9pvRB&EncuoyFsvbHJ3sOuDH#J zv@SZBHFo5o> z%Z&}$>eT;GhH+IY3zWj?@9O@e{#Y5p*>*xf9!dieBik(PcQ3SoRJh~t08rS`XK=yR zHA6rHgzB}bD$TR#&GN2+uYy`??3$k6kR+rbM}o&1xe9hk9)0)_Eq|aRUo#8E`NqOVA@cAcNrkttnP-L*V5upmT-3YGnZT8M zpL6S{55+-Ob`T9sP!MIiuNc~0gs0XjdQE*rPQr$f+bh05nUUCV+T0#FJ)_k1HH_qN z7|Mf!l6Y!yXigVvRP^V>clHKIfM<;D!(o^`*^AxLsnPdsKg>IR)JJti6@qJeLQ@OD z(Dmh}vVkw{yGJ)mTYnmj_vU}7juo1%)_9frJ(#T$e5Z`QIS12QPMFAM=Z8A@{4EJl z7NJLH_nIjosC7d{j#iGOo`JEWRAuCGKaBhXESi6zp+yu@N( z-fb)P6a$k|DNxoc+YhLS5-oEvAPCS2yR>lA*uU?N>c=DinU@W_JkQ-G9LrvBFd$>V z|1fXoY{$?54wUnM6(?R+)wWe&+0~1!6Z-#4JsIdgRxmu9$TE?a>Av#fig3THSO)F1 zV2PiWvR1(G;DY9wvnT1b$c1+xWWjcH_e=ITD64g$i}T(Ac

TlbO#*@CuEL*SPU6;j0qdB9=qSV!!=UuQus5{ilvT(KZ@WB< znyg4J%zDyB=k`OVQc)3ZVg`c5rTWL1_gNIq^DP=<^d(ap7*%Ot zr7AB^nH{Tj|6<0221Q^MbDB`N z4wc?#bD-L!G{W}f@RR;(`}sx3(7=ugw;W&UdQc>$%u32i zjuytUJ?(8iusTs0EeYSa*==i|qNsO8X`MeAaw4==4&{{R8Fb~1V=`n@aktnWST}|F z`LcNo%u$7Jfr|sn;)7CSmZLQsyDEA?+o1k>H&n~(qV;m=Olg4^ZOt%@owKV%i?&CuXA#*>vd+kU99ZVaOKcIL-I!kX$Y_3e!e zV~0YfO_i^_dbhbp*Y54pht;!~J(XW>o??G_wD2yZkPZGgx0NeeM8w@D~okK=$Tb-8mza;a z;UhM78cONix<3?m8DM6rU;V&i{_56DIQ8iAl+zJlbkg%|=z*k4X>{0#agt_hGvswW zOAv+b*93CdsLscA%5JJkSzn1Q95Gr69xf~MJl`hs=ta0FYYwpO0$P~X!)uvR zoIQ}cNdIa4&Rt7zEk{@nA4UZ$9tEuNEz(j*CiTs+ilg2pSV*fU`-CS7yj$Jyq41;+ zDW!SVdHd+ijoV&cj3|#qTUc>s8T6V%?kub6wc!K5h^Z$j zlSa$JWyWEj6rM-@z;m@G@CC%573J+M-lKBF3MQLd8BT7HcNoZ4WUN-`!(S7sEHS?V zMI8VrTA2b=F>n#wzGpY$2OenW^ekxywVaaYZRk4Gmg%f-;Uu2pP^Df4` zb5;n4ZP3N8MVQr`?3_Y&^CVgmVAN2S`}G%r*xM=o=dt&Xc~hs=4~suC3rm+;6qd~!r82h2lqsJ))T(1PtZ&S*a(z&rGG4Y`%9kiR zBHMUXlGM(&Qg02oWI~-1ofm`TcnvUe{0aXeTL8)1fiGY+-O;I-IEF&4_=BMor$pj0 z;zPR?X`bxKZ{f5K_L=kt#nXlpMxA1`=+ftV97OfD0W%emwPMH~?tuM}!cEI--iz@T z&w6gl%+)8R2Xei189bCX8L212)C*sHux8{l!I-(Goly z2eeNpNl+rpg*+PmUXgm2cmE1fDLqB(ns#XQmiK3Q8gEG4XKUScrwE#@%^Omt4O{zI z{t+xMYKMx{TfU)pZ%LV0}r)qg1UeF1x>Es zC{sCfQH95QW;fx>54=Fg9FiQXEqxd;JXAU@(@DxXOWAVrP05EwiMjib%7W?IcNA5q zIk9V;iBl;I}ak=GKin(qPFb1M2(tO5m@GvXhs=r^h@^c@%K0*{o)$=;+I&!e_JF;9&w~mk zeZ|8k1gSSyR4lYGR;U07$}M}WZgCcP0>u6}PZ z45DbfHOmgiuGBy12ZJ@RdEv*HP0t9Ej(Xf^>Rojs-yuB8jP2>-V9gv+QqqWd8kL>C z1+0u5x6=QjRFxxK$X;I-J+(;ib^bhKawxuxEu0wL5Tupld@Xa$Y)tEM3`@gTgXlFk zVLKVc>qo(gYy1FkSwY(wJ&D`F<0KGiI5evFLhDEVz^e;2Y@hh7+FI!F@~f%ugv#3h(#flLWI_Utt`WPa^AinP~VbI^`Qbwgq}X1f{p@9vw7%| z<-Pc@ntp}DU`+3MalO}PQT_7IQ9W<(=QfcZQaH8Lq;noT$zqq(Fv69>PrQ|Js)+~i zEcFi_Zu|6{4y~JP;!2#HV-*)wR_ZjLUNVeU<2)4C#gk12h(LdElhsiUkmCjt6kMmGN6-hBn= zWmMvp!?L$htiw7>D_EbU`SU?gxtndv#5qJ&p@&pK(!$?M$sXrX452R-6BM5W-0T+V z8BA!YJ+fGHUmTPxd@|KHxW65gz*bXdcQkVSv!mak1Fj5OXypOZ(&SgGC{uWuA17Y` z9geUd*~w?wUprAN|9d}l6kthsUeTs~Qo@YK4?gh%z;9|1z}N8S#Yt$i0*o}yb=dwY z_YRT_a}9-P#0jA2>T}QVuG_9^VD#e*Jan^oY*FW;WLA6B9?3}QOGgP~Dl7>ET}a3N zR`kvN@Ru?pv53zO1o=dl<2{KvPT5AXj5TcRby|4mE-5mc<6xVjB|oQP#8OR1FI;Mz zo?2d0Q9XU|VD7`CCQo}No4uPk*+T+s@$Cu6!&0!yT^?|p4c&`H+R!g=>DU5#Lkq?t zqTdIY83#}uPqa4xdNHqLFe6@>l3vN6z_#;&OpRB#>rN&G{9C_d*5e9RMYD0zUARDd zO@qLaUzL3*Dc&V5yluTrhvyDAPNSDd514PnvOPykv+$g2rZpf%gXS1LB6twB`dIRN)~q~C&waS@ z`}3*F(>2N*Y>%s{UgG$NBuL9(6dv!tCJe*5TH(iIL zUGrBDjG9|w0Kar>w&XGuzYBH^8Rjy55zCs;YCKSO(xrRHCXWAuS4pLx%}&_ZetB>o z?fuL2yj~U#uAqqoi74M!Pm^4)mzO2D>07=gdy~OAV-*^sqim@i$3~@NW48nfo4)KI zu}G9dLU|oitq#Sh z9Hbnkc-}aL%v)NzF7ul3);SeMph~FUDRasn*^i@Luda_ED$Mcd(bIf7Cn6d$erRj> zgj`3Xa$^6Jfn0a!PJ_Rh(2zujsj=$F1SDrw%#-VkKu@0yX`owsn^3GB9bJ8azrFSm zDzH{%j;ZjT2g2af?l<{66tBz?63Z~^dPbF*O}r@(!+HgM^vBTQ?_e=iC}o)%_uy+S z?cvq|af7!j=9I2vJ%N{{i3$tNjm^bht{I0UzNbxgdy*U*I*s3+`xISXO-mkaj#yXk zs65K*=dRQ?m-c2rbxkx-BasCXb?PU#21s9NISMDhR|kB z&{k!SM5o6|%SDWg*2}f$@&}mC#mh2=9f|(QPQ3 zSwZ%V{K~uBQwnpn!Yz!q!VM%U1(oL>7c<&!Q56vtjD)`Q6R;KKN)~hZqOdyc-d~k} z3RM%0%jhuQX3a)=jGQV1&&CA4TV(X^!d7>4Qkck=%|Gd~{Hoyt!GRZ#;frPwb5xjJ z(6&M18VvmdPk0c} z{E=UfQspjWPFS%hz7Ys}o+uMHZGmo3k3+3*S=)zAnu)BB zZ?%;?g3^BXcnIsRw(SdQ4R_0ZK}6{~nvN^Tmdgb}X3Za9r3adqLmmlKMdB}kkhVt+ zv#!W7S!41K<)_Kq@eJKtlJh`X)H7>tgykw5xASsQ5%_|CK-XqGl@o6<@mbg^C`qeu z{P<*a1sEIJ?|x~lF;WM|$PK zK+%&M&}Smmz_E@$@JhI`(H|hkNAX9YnH%9xk=nxS-r_&-E=Sfpk-+R}XL$x}5?wYu zk*iyeKrj#)Uir+#!sq?<4!Z>5N};^kU#X=R4_$rh)@|>-QzhC&UjW& zCc|))T)z21NUwtg{QR}<4>X$22(60S5?2H=3N>fp;jGlvsL8tKgM5mlPHn=3DU)D* zg7f%ONt2~C+RLToE6zY`O@al|G$l1$OPV*O=1G`$H2mV*i*Y$H-=d2H2{OE1!ehAQ zc+I2qoz<>A`|E?FRzH0C*j_BB7^Y#asBYeHCR$KH<-ktg2`*N#lMlN9UMR;Z0sJG_ zJ49KV#(O2XBBzE+-Yh_OhAP|}_&rJF7Q2&Ol_j_8A3YRxge;nZzM8+17K)lNRn#x3 ztjCR&g!P;lcWbCN71jn2 z%1XtJ9r2hhyK)sd8GFZ%;e9kYy3%4~j~vvQng=ad>zsB_N!Cd16hh!{#|U8fc^ImcgU zG%+@5(r6h+FeIvsf7t9^?a-WZ-o#pD?a*ES&2-42n!N?9qxXvpxKTTX9$`9di{n7I z5>zfuilC=i=Y4If&ZGZyoF(O3c^u4k2;k zw))lF!Oxb$nyzbIt8RGj_WC+h@R8yi6(wf5^*9MAExA?}miowB(8Z9mv@lkA*%f=j}0uxS53z%@2 z^|CG6QBxQeG2?n=ID_EKrpvMQVeWpOA6#Op$--|u?`E~9kYch zU;DiupaT)FW`j?(4jyyXq{P0IkaJo$wXe#O+IgL8SSQ;*Y_32D)hPjz#{|r;UbRJ<%pb5ExGn&=dY}3nr$?D z6}v-|z|D0AP14B5Cig-$_ToAvFLVCWM{mN5rlB*ka!%+-Fp&yNU?$PfP)g+e*J*FFh0bmn+J=SUk+X@DxdGhfMLIUrF{G+uR6# z!ZhOWzQ<^v1>6D^N}K!8Ksh!cW*b+vYw`US!+IC5phjkYsPxra=VSb2M#3<_y4Ki& zDe)}O*j@1jIb7|7OnTOox&6SSFWa4M2(GT%dtUCq(@YI;{3}7j0Jo*L5lS!2w z7cL95eFbSg@vP_Fkzht75}P`0f)&5{*IJMI`i_l7!~ zEt=8IyrzQtN}6Hv`j}S|o`J0@VuQ{{gg=2v9~yDemeuS6FgkM(AkhC*Gumkgt@a4h z4O%r=Op>e^G~1+oCA1++rJX&hW_NJ!tkl zuj4W$zY6r3&pW&8LxX}}Fx%f{<L?eyb>27m;finBz^7(sN(O#0%WsjmQPHuX6U-KEeWEE}|EgUe8VyMi=83Ia%ILCo_9o+-t->)wD~ zjrG|4^aZ%P%69EHN16W&xDuKFp_tfL3q0sE0u>s`VmuAWfqxACd@`=ZkDw6JpG2l5 zDY@93f#(J6`@aSUD3cD^F-ehl=#pd35275IU6XJ~2O;XAjGh%<(|W+zLJh^O&j)tgP!fBuFX^xW3@eq+4N z46eGG>G$5cM1Lft)&2TR<$`tiHQ_)LB5f^8%QcehekUh4>vN&^NLRIXrcbi-#Fv3L zg+xPjNY+HubOwyBU*NqRw<7k_o4LA)vHg!L+j$`@Hhe_1x-UgmCXdvco5H?S=ltRpLfl3eCuj0bd6TW52Bv* zS)JD{MNv&Ny8@td-y>b&z2@Vm&a29iBmQ>G&hY|I))zVaG_C`Do-A3{%(IbPd9$06 zH*@OqwSOYnrugmNh`;AQqab18Ca|6j(D8MHBJA^F%TIl20~w)`k*JqjqwSjyn?SJ+ z!O@7D-NsxY(e=Se02g{rJ{u6x*u@4IB{Rf{M+)$?sDkH>jwP`1>E;sJZu3k&yJnx7 z2l90%WkWHxQMFSo65p=c;N#E*OMtM(82VL7_)1k1b|{R!qhc5#F-#q`+8Zyf3yge& z5PKkt_=jPtj-T4+bx4$?pO=0Vf>)_a@L~~w7%tGJ(AuB(V}E#SdjQM7;Sf|zf)!zM z_hcApohAjURHU@Y^=ims9P1lzK|8oU&b0|VhlM*+;a2LjqCBf?=&Kk~$RD6nX^(sA~%4 zD&ufG>xaDWimn!{lW)GsYghOm=?BIZT-J{=Hc~EDxb*6dStzlBuC}V1^3%H1RbRF; z4WFm&{NJd*hDMY7HKi3YL_Ln+h`61Omq+}NckrGJt*Z|QWcWEndA=3l1Tp5CX=6z# zh}gsZR#J9fsh5lz#2`Wn+vU)w)NJV$($1p(ksR@o_yIo^%gawL4wL{0 z9-8cqe?(E%JZ_(ShgcM(YczQ8@`V-$a_ho5{36LD{GrcdLLc4dv-yF`k6dUEZW_DG zxKZ7ye=$3kZ^P@2yqmfDP`cko1Gz#Be-m`Ew`!==P9`6Iw6fZU%}+ZUaEqolAf*F> zmqsAvp<|Q4-by+5_=afjUH?^ok$|a1gtS&Hhj)E3n!jleV3fYz6aImB*Tgs9Y;*20 zkv}XsSsO_{09z2DI(3GvYQ@FIclPJiQ>Ocxr*&a(T^XUrRU&sKI#fkdj}7$Zfx;3E zz*{Zi$9BEgj3`X@8tK!Wp`o&@X$n^G0VprIa-ZZv zX&@MQA1%=9J1VI>LvIJ>e-tPEiLpZf6>bxhvgB#F<^K;ihobmX4Oq2Zlzr@w#?0d{ zuDn1yZwFEEn-%3(s|y|AflC6Q2EegoA9KcC;`x1}Rh)R>BCrE6$T`ujJRU5{LWzYjnJnR<8&y|=7Imk-dl#X)vxQmw3K?GP@pYZtT>e7 zUfc>44NeP1Q=m8mNsGHnaVTyH?hqV`I}~@fpaDYOJvwEr&b`)r*E;*`bN2kOKXAcF zxVVOdG5*i<-1qN>BJq&0fu5uJGoZ&9wCy1fB*7O-*?vy^QOfDzFz##5qY3F^0ljU| z6RD`MpU+{(T8F2w^apv5fn^#wurj+mn=7`?{@Tq-YDKGtAG>0RItR4!JCPX2UXa@F zTWW9q*{S&T`tKSjAG&{R92y?s{~ESBE@F5$O+ZGO$M3ptuVV{^S(@hQc>;Ls;a8@( z)z%bqGnw@LdOzQ}&~Q&!h7jKiyx}(=+v5z|mXjy`IP7Gn|83|C+1||>{6&EeO;z*M$c;rz#R4m|j&UPyImloB z2`aXA*|-Fd>tK8Lv&)Y>R7>lUylOIl@6;$RaJIXHHvT9dxsjXxJV2fJK^~V-Tc&V7-xVMdo(}!^2!=(=M^##CX>^ z&`INmpw{?=Z$VDz#yiT8!p~n;9Ue06nyp=G$X&um>S$_^wg!VkE7|&C&Cq+)2`5BG z$hNt+&F_h~-Z6IRoalf+ZiyXLuxDpEv5%%Gi$$}!RQRONX0zJw-WV8Sd~=d5bdmuv z%Hdji`mxAs89(fMM7q!-iJL>mi2};CD(1e;BnXtwz2FT@5@p5O3Y?%s%{Vy zIfL>t=s{puYdpmmE*@7Sv#c0*o*&V#{knn0KaB${T~3M`W9SaJ^Y&j*!~i=J3o(kV zGpbP0tG?lTV*_jG*Pi&3n+r{N_B@!c%J_^eoPT%MqAgN(cDW)(HMIFdsQtNE_Wh^n z*jq|lvM#_i@o|)8W8>RWe8H=7F_sp}bu_CPOGT>t`E7WQ-i8$)79VA{NTiFGMGuhMOP?%9C#^G76QV8{hQPHCiCcImo4xI!Xv4oTgmxD@BC7|>%dk)Tj7CxX2bUiN=`}3 zphg@)?2%Z=Xj5ge0LR*}mSRMOJmjGV<9DKSZiXFsFkb#}eABXjU#G9i5MHYnP%v*O z8Kj;Rk0#EkgwyR7F>k;5U}!5}0(#w;2}5Tl zs#7V*60<+h=il&gvKdG&$veLsYmG*550r4dzlf3P>jw%mA?3`)&+Jq{XQ;z1ynTn+ z4RK$|ERZ}hq7c6a4XB#r0b$fbXm@`WbaGbo@)_I}^%dwI6;Fb^Uj(76uvvs^0hq;KhU{=;9 zmJ^-2PR$pik$Sz7x%A}V>g0#v*|x_>PJlrI&UqZFrDJ*ax~=-2e40_Zf2!_L{w;Yy zPe-a{->pRTL#_|Z3@J))L)vpoa-Q)oAp}b1IA!^FlyYu|%q5G-Mcj2=Lom4n%6veL z`G>`%zgJPfmS*dDvD*EX31WMa*T*{Lt*K=kDJAu`DP%6O{D>q;Qtq*V^2_fDWmCIM zu7;M;3zh0{L2?F_pbX`8J;(7BXzNcO1Ex@Anb5eVE|dv-!cw-<7R`OsV6!H#0(@%D z_h=EXmzUH(9bVk@M1PIl>fS2;3(9O4?(ZuqyyFz>kB*l<#!ExU0e#=E#o5ijec_Qe zDg%URtC}m?x~D%II*+QEHcVXx0hl*1dNe~Cw@<_JbGORPJ6DuEqFp1ILN>@KPER~s zWSS{27({?RC_Mc7+jwq<&-byY`mqBMa(KA!-U6ClIj6EZPAI$#3m1Ky?9}1x*y65< zyhO1LjF1YdxfL1m#)Cfzrm5}enatNCz2!;-8qX54^|U*iKE_2F_i3Q{v%%XS0l&oZ zSq2z*=^*$M+F~h>8%)Qs-=lyp)VY#Z6n=BwWn`s;WY&P8Du|UzbfshDRd$p0s^fBO z@Vbs}{=7ObNb@W*fh{+PkZeD)HGComrrSkVB|WobUB#Sj}@hj85EvSBdX#~xf#A88E98di|DNF8n7zgn!{8U>Je;^HQ%|%(|Z8;m@ zjL?q0^wG&KW4zz|>4klolD&b#QmA#0@-+CJKCvWNFv%&*`1yr_6j_?5d1Xi6is{_G zGvaX*J-t779#Cp1#%oPx>$lFc1>h-mmONcPtIXIY9(Flsjke&4e~52wY}AuGUk88a za%)TUapeM`zJBpo-IsQ`>TfbR;I!*3M-LuRY=ta&RS(r8NnNnx+PT?*apnD)TeVSk z)?Gz2{p9WV{n-9NQV)v+9z#f?Y7hJ;cm-^{9CQVjJx3oEFmL4XZRuk_>Ji(NW>So< z<4X&~hyuP*zkTbG<#{U^()U&K_e{^RlRq9t!k`z5{`8Ue;o}f_Fzb*MgHz#V$lE2VzY$fbwpK=)?DJc;vf; zebP2RYk3h)8W<7A6Sl!UJnyc1=pRU9McQ7cf1Q)!Bz})=iJ25)Kb_JLU|;Wf8m>`4 z$#tcF<1`=k*rFFNZT*ZnWHP1&Y1Tn9^22S(21n0e>%Q!hZWP;{&&-9$1K%eaC^g3n zl*4z)7q_QuF66`B3}(QdENyg(K14xV&g!JAUY3t>tsc<^(#$>!TMUvjS6>~KtEUJB zJ|q(&yyZ2o+URmJEN9rP=&pv-4q*cg5{nwKo#iWd1gP;3#M9O}Ggiy9uU)s_Y#A)b z%lFmU*@gb>beBV(hI_H>p*KI)kFh10RAw1MBrrtn%D;A$sSA+W;^=8xJbc61xf*k) zmI$v)K```brvVAFT$0UUj=M@vs>Z~1`^Jt&6BXL%a5BK1$}w}(3*0ZEB6iDG$W1(Y zHd;M0W_{GzsdSH6XWVz+m!cG>m0WoF*r#@pjnhC?gmq_nt-lRL)5^J>=z#lfwtGfm zWuzLwPxS8Ccp(l$6<-<3qn;P-)k8f_+gSB(KDws6T0&2m^t(rGP_N_*%|sbZRa6 z7A?Qxmb39Nh7>Jbg>*mH@fc68v#zwj)QwmpRP^^;L|Yd)JE2b748U#SJ*ug+}Vi^E-tctqt2+=RVdhkjP!S+vGzXuJ|yqAdJ zwLO1xkZ9Rzso3}_KPMuw>615#+_lC4iB2PXb1HeX9+Kp1+^@6pU zPC_w68wf%|a|Bt#(}L;HaOZDkU8J41gGzOJ#Jc`M zQENMH#E2zp7jNmrdFXSLxCf_PidS%q(G2J4XSi`iZP3vKbCMZ>1e3oE`CZ{h_GyG% zQ;s%RJ5J^4COiXeG#S`0FtLN41D8ID{;iFyIYyngU2cd3xn!M!m{{}es_lW8-Js~- zJ?q#{I?FCZo*{($++2M{r$i#MpTv-}qc8n1 z0w1-iv9!_;8h%}>y4oVj6M~?Gc7KLW5@AehqrXP$^M)f=j0Ew(#n}ewQ<$cjM znSSA(c(QgV)6(fJMwXsAJ849d}0>sm@Dk)O3?kzy|XKL-b>(h*YcI zRSvUl#M?wHE%`v@@-q5!Krxd z;xH7L`CPsK!*osA#vNJDBx#Sn{aQ!I_kVl$nr+tKfXws#I)(vVlm&0f&c_ z`uEB(GYw|0Z?*V>Zv`KY-21gtenA~8JJoLCk;Fa)ZWxE>gw8~oK{HpX#Z{oE)byOk zK<*mLd?uy5S5SxX&O93UgQe7|%Mm@9h&2uKVnij=jHGXOxGRdJrGecAm<7al0&8M| z+VJPSX(Zc7vsm-)l?iTG^zWg)t3uXSA~f5^PS~F))z-2tyrPRWRoyBvGHgswy(Lt} zqv=X_G}zW=2&2fp+}Kk3(62i2jWu<4G3vH3xGq^OO6EZ^(`<>EP#10Vk8O*j$y&nb zs>he(I=#=s9O!$A>?cC3gFTHdgFr1p&)p;4mFEgA#vT zguGdN^X|-=oaYxn3XoTjBPYqo%$s+TJG^;aBUuED`%Kw{Tk9H`nPWu5tkMEB_1RseJ6Mt9;f_`iIUZ4Vc*i)pD0-Rct9F@^TvlL8so_WzmIGo;X$~~a0 zoN=dXB4wRDuOLRVv~N4xG#WU$7XBX+#dw9_Yww1&8a$j1Y>%jiVwIr--qc~s1;e^f zOO2|YL~MILSo-`oSLXw_y_xe5fxu9=F7%KNq}zl*O*&O2XG zCzs={5E%q@@?CDtudGTnZf%PSfZt{=Y-!{bXD5t?4}2TN{mE?*RJ-o@v8c$mJ84Hcvyk1F_Ws?na`u{BzdIS%mMMfw31d2PdSv)kDWH79w$~dOz+Y7BE9hMs zzUjrvQXoT5spBaF-%-yi^t0eHE#RZa~IXJa`B^iF_Z9ng+7Jt@PL)CFm7TKuG_ zlnehLkcW|M~(gXZ&6{O_2h=>H5JTt7k@u@@jd;oJ04J?U*KV6l z{70<)<98)06vJJi6+lO?=+Nr zg@%x~8_*@|_G(;Mu9Ep`)4lyr61vb)kVoA9~8gaFN)pKp=pNaP5 zO?Q8+4ik%zy^|Q5WpVo%Qomn2nX8m3nct+2YK%?uS*k>uSkRh&Ep3|nL|C=R)z@b< zmQaDtl-g;g?Qtzjy*Z^2E>gWgr?+HJLYqgt!~F8qykBi9es1H2T8r9_ZN>cTMiC6E9=k~zRpuKmns?L+dJ??AoIA@|-h+-JkeJQ&Aw>>M5X+AOvkYhMbo zzn#8Jl6Nhk#DFdjFfU6$rVmEy$XvD41_@fleN<(huA z58GhGhq>eSnTRXs<<4Ngg~9#rSM>Q&Al0OtvqZGYsUlJMlCX-S6~VpZt7aZq;b)p1 zu8a$6bt<+_AEXG?9{-(B0`hUc0mMdluMA$I{=oVf>elGG(H4$1=c3%6N;S66{-GvZ zEgC{=DW+sUV3C{28D%Y(?A)FMY9?Akx8ejvWv2=`4 zqfN)tY15Pp{aJAK?ZhXa)+U(&el85u+H|XS*pt>JyuEB*f`ugg29*X?bm~U76Qn7{ zs&Fe_zyochtLwwj?O(&KxNUZ~lDJWswj9O1q-ex7R>2BV@m8q6-t?Qcqj~SaoOXEI zKPPwZhAX9ihWf~fnn2+H=~8RmywrDp|CNP(L)-*6e#d*_%y;7s6lRZ%}TF zp@J;}YBO5p{kEJoHMQ~av5ks?7YTX#aHlRBVDCwHKurt)30tt)md3K)-VqSzIsH^V zEyUbGseSlv2rGrn_7;P?)_HmFF!^6c>ct@LqRp(9J#@8(g$!|g>U^?iv#e@hb~m`e z7@~s?%1efYi${poP=Qwy=P}l9RzzK3PqK+qcK^cfu=2GX5uL(?Bi>g+srqC!;P%C@#it^VbQBt%IDaCX>Nk`LUhq$gqEAoY2t^{9KCM zbfP~?tt07^-C_~7sM5aEGZ-dtO5I4mqLh|B>&`Qwlu1ZM{iQ-l{brbeQ5uqYX%8L} zH?hr}&vk9kw?2Qw-q6%FT?0V2)l;b^*1af6U0OYq6#~6I61kZX6gdT3+cM0d%W)g@ z1>nT~@ZG`ex?UC=A+NcdTl_dPgHJKH52F2<6V?gii85U}?lt!iu&RX5|Q)lTGzLD+b&m7zc z&q!NzFnov^B1!YFxFb4MK9i7LtrioAOcmybLtvy!U2TPww>`e^>=7(%pBo~PuDmSs zZ9{5whhc!d=t*=tbMcA{zOMkZ|3@e1mP9$zy^c?xDR@GK6tCWFHBn5?g$3i3Br-dF zqJ@VX@AN~ShN1D7k`Q;d@niBqc|-!?&SF*VKb??$tY6i%O4&Io1BHloc-%a#3dI zJdhaehKdk$;N*Ijjn4V@@t}X&!@UOfy#69f(5U6~$)bOK5=v~FP#puDtLl8zmE1&8 zC8FFS?%Qz@#%6wHtApND{YlVN^m1BQd`qI<&FR^B%Srf}jxOG^4bX=Q^FKtWBZX&m z2HX3^6>8-cD3YGHt3a?pO-$}QlR~=7#k|_Xr`=o|CE=fRhZADUQ`*QhL1^ZMAkh;Q z#1Sdfmm6&U7PeMUCrsa&D6YInuKTvs*Er;7oeast8(*8T4`C8#R*0~JrP$&czQh5L zpX7e7@J<%WKEowgvZTSk19E;gHquSP$P-y=>%|XKRz-mdET;F?vlm4lR|T=_x}WVQ zWwLJLu<|g@Z_7QWBn+7Fsylo$iQYpm$?l~Iw6h!}#BsM;pl;XxG zrx~P?$HVoHc~}x;Z=R zKKb#owr+kcEpR$S2lU0UH-Cz$xIAyrP8#bPoP3>9aRKOMeIZ&2@(DjTur4`PE;L!v z?z_vt0FK6r=u^48tyiyX$|Zf@e%q>TMX z_aG+4zxN_<1~H|=Jt&fL1|1n&JF~*=r`~bFcEewxGV|}9e!W?}zo&L*+m(C0>0!*^ zvPG6?2Wi@v2I8qDJGCNhXaYGWYM6u#ud7>ECNh2sm5b^x*w&g@duHM#BOl=4}(@8Y+ zCez=a{=oMe5}gR-K{jySXwW-xu-xCba=!dJzJK=hUwb7DxAa{^Loc6eOab|mf3--C zSO3Klu+Z5^Yp=Wn<@6s*>#Irl(o8$y_-|N)js(D`1#k^wGXcjcbN@eq3jOZ`8;t+T z$`b9~fFG9nqrmKj|9KKG)4y&K@Bad=)?3aXL=AhC96 zYdSK3#r`AQ^aKW%7?-M?Rb%#hyHw)RvSVOZj(_fFIhVcC`ZKU8bD|Ljd6rj`i;zeV zcQ7qb9WHhcFY+V<_ZsIFDJ@Rxzc&-Yo|-I~?54n)oLDC+k38({`tr|y?nJpT5%4XZ4sukcqY5EH>O9SYh{zgbFT$tD0I#bi1 z?snbSwaw+r@joc;3Aiwzo4S}$U(M8Kk-%n)2Cr6`$!v&+1PSN6jJ743RwOE7;NaU?YvMEY?p@Bx17ECI2mFGV1Ou0= zd~<4IsRO08=e}Sg(?7<50OT4Vt0y0fz)+92*LE&&HaeU+JDpc;2`3{5AA}TkwtPiP z5ffPKDg!6@cWuSLAH&~|<-hSA{tA@_yJtr73GXVe$wF3Gck~ce->GwHo@oE+#as^u z$)(%mU@F5n=|%v*MK*tAc|Sv|Mozv={_q`=$1XuhtPMZ?i}bI{PQOt`dTLy1BECRZ z`4&z)5Z_>yb%ww0y^j9Z95(CBn$T;0%?~J}kWu7sXecVDwlFl+jlsLq?FxgW$d=1X z5KT@`iJ}#ANC6Qo$ig^thz!X=MxYpz@ZY1f}=tKhge-ax>b zr{{RfsG7h^t*w0TvwP8rP@xC26&n{EUy{J_@tkp3*P-wxw%_+E$8fwki`6=B7Rm*c z(H*U^U){U^l{<_ZUPclmqsU2(jjV!eQD=pq!ss2_ZCR(m8z< zGwYZ9@yT?7{^Jj{jz0?7Y@>rRJ(u~)H#zk=W_xVr%<$dXqo55kJL(1FGy5#EYhjkf zt`|wyrww5}&|lX1jkrJ4m;CN_{a78AL__g3E)x2X*S|dg{*%CtFoRsWf4Kdd z|IRVjOK2Jv?$N#XOI&IB`!)E>&*>3%j0`zRN}G?02`={7B3FzW!aVSb-Tb}LVzgeQ zd9?Rj@8Ns_BK97+pFUVzOl593WJN}+Rx7sibqk%6DnhgpZM!~P=S~N@ywxkwfP*`A zxGy+&!yoJnf#YsUQkkg)((L1*G{;?tee(<~%4G6%*4|j<4mXCn@%nSmSXuWeVB^H# z%}XmYL0cQ{{#c09OoF7RoHEkqR(fkXg$!>uVv6>2SD;p`Apw}tZCWn*36`S;S zEDd7jZ$F+K6Q01+MHrz!gphZxA;K=4Qwn!l@b01#`zR~Ts|_reCwp1@^_)rvK`Y9o zCu?i%PI1wCZ*9qBKN|1e2Cozg?g`|!?>%Xsl#DFch8Yc>6P1W&yLEMn68fT4%PsaF zbyk$NV5$9B2wB6w2KW+e(bM8(uizcsCr%{iCDreMv!r2Sfby3s9p9^{uSZdjSwqKWN-xqjnXLQCsEa1N zxeuP0&r?SIfK;i1dGJ=TB zGSnFWV-xlGL4GU@6)ZdSsAR%ssr*Xe;`2pT=-wu?`r|h*R!NQ#;RX}}e_*xsj^2!R zH4e^`-HF`^Z%qPq${DnriX5Ztaae)I=c?hc2d z-RkMIScfTQFb$W>3fyW@j0*bc2vEp_?yl2j$u@bVM5a|Fl_}WIIj}#r5J2CyB7Ilg z5aP%oVtYy$-$XU?sZy(=w;n}>h~s1u-?){x?HRi?&oyk-M1+<;TrXeB=9%`)=KTp{ zB~0J#Nlzz43pML=IBuBx> zX%?G8i*~|oS!~7e6S-dVuYT=mq8CmxN}drO&IZ=ENA=3$9+)g}GGBL`(iB^5UHax^=C*m52q;>-wvkgu7d2Hbi;|0wn(LT( zv7JKkv~!Z2&2IxdxmW^e7b<_q7Fsk%p~(IA$nJ+Rzl05~g2o?wQ?>l-DX}m*etW(O zhx4?@Xc{x8iAj+cC2}n-hMa&<#0plP%M){%XwW98l zyVM(`X1MY~X~X6Q3De5m<0e4{Y@@6r65j*cBy9LZnNr&+V(d2QROaZd6hv;I(#-6U zyzPhEo-{HebdhRrK{kxcHbc%5Se*4g zDhhZn?v0-uUcaSx9tf=0@le4lfC(+^AOu1YeN_lnRr+CA`$Elf_uH6lSmU7k5FoDU zcY@N$PJG(W!7R)DKtIwMbC=1osH;p(@PIyidY-mTWhWtb0#9zDokw#<|IDA zb@1tGDdPc-k^e2uaaazSwl2SaT9cOUIn zx)PFWKlbb66{a{OWX3usKi`Ub;W59~6GG3uSwDD(2!-(hZDOa56 z1FmDkrRueoVK4T#o z2uU9T<-5fEpsKQAa+f+q>Hc$0ZxrapVOwi-Zu8a)-KAXp?Se^yxoVtWoDcuwqDH2{ zB|QI%n9+mvl5zOBtx$%J9If?65R301GkwNh$qtFbrLN^G0}0lke5V9byDtK%j9jc{ zN|P0*O~d=k4HZV}3i#c$jzmif7wZ5TxVv~L zMNfepffe^l&wwMpBry7w+k6ByK>)~-`=b0Y>;{^EP8p$T7T4~*}tW#s$}mJG@HA8 z01!ijs^b+C3$bDURM}$T=yPY}7qE+YTDlkt@gKUXVa0r}y@fgOoM-@9vU`T~dYZd}BG4#Vx zpTFa5AT3uvzTfMb)!FuZ$*1r6xP4`J`?Ki0<;rRd5tBE?sXEc3uRux8jE!n)j=dQ_ zz(3IbWq2zXgRZn10s#_b*PWLjLv(Fq*rPe#b# zW{6UGYbbE7Iw1K@>&8eG`$>v~RMLP?=^K|Q(H$+~C_g_&>d(?Q9_G}g&~y;A-s0Wa zkQjBg#cA^6oi%GiaH z6za$N2Nc7XuXx}DUM>q^fVhe^zl%`72RW16UhKOe^zOFk??Qq z)Da~BKs~(5$@?ua!>v*@z(u2pACesVkGzVEe<)gjo|WV|<0rt$WgH za$9N?v)nI0Lr5I~4%Oy2zx5*TBLWsyjUIk2#QfJDS4R@@MZmU26LKHaL)4!77|; z7L~)V@7KbXFH9lx14Nm741%yZl}&Xuqe!|?10LLv*LV4Er)b(IS9h>q(5C`z7q|?J zcj;v;MeQNQqt_-6<9Uu`ytg5qHE^vsu0)$rYy%?Wiu;V7P~>4hh;K7g6T#GafsUbn6LbB<=}l(9CV2r^>Yc3 zc+(%SR2-Zn#cp$sZ5%ekEiuhNKW~!=C><7GLw$7=nAPjRF;!o!b^T(bvD5N*G#PpH zKA6sIyPqz9TRFVbbxosI9m;yruYXIGi9zEQI`?aro@RJEE2;J&Z}=dX);#H`A zR~hp0Dj8IzudSnB#ywu~B!*v&_CrnS~ z0AKEc8UJ7o_~}w-d?9LMSv^&7k|nEbbZ^omY-rUE+PAAlNz?I{vP3Qt9v|X1_FBn(1;{(@q@P$xKpVs0A8OCvn=8n{dVLwaK zLT24Ezy0ZZJ!JV&uV$31AR1qroAbFqH9wZ+7NZ!YH&<1QgvFk5A4zq_#^Qt1KZD}Ik!aC!F zS4-v_;hW11qKonE-`q);?tXGtws0m+p{b~fk8Ic1deW2ZHra^U=S^y<>$9a13bkC) zke1_v$t-HdJYBL*02gY7ZV$W#;eQPCxSNxj7)^UIUQb4j$>r@-4iKQW;Bn?~v6qY9 zBhc&fbuQswIVv6``2*`mJ4y!AINg{yJ^ernU2kseD_T$DOBs>Vm|YRi`1$Oxvh^WL z_6?Iuitk|`u0#&hQc)SeEcqO0!^35Yl)rLhA?{Vs#uAOSyL^Zon@m?AQ6!aNV(x5v zSoHetIbqzPlodgT4S^KFiR8O<&zrF)rS47Vuex{lj6Wl1#6tv#-rR-bBNO$}f~Ycg z<1;x}1*+noCyvSShBL& ziazyxspEoZp7!O5>tN*t1R;)AW*#^Sa!FZDw_OW7aZ~fMFk7S3lyO%owQmj8RzEr z?y&A1ZQ?wEyZbAa`q>DOGMleqE+sP%LTdf7`E^fUPGf%-kT)jnTA7F)_8b5yT~Rz* zg5qnrD6gc2MC-iO0$>@301YRyl%znu_bH1*$2 zeNpG-j@!o1cMaa|)n(R|cB-uMy1Bt(9KyD85LO{)Y{A;2*9*p;rJsw<#EIX81i4So zh8yo6%^n+uk?;T?w)=4kk8o&h%I|u2J)Jj?_u_-uw2D4%qTB+Y4wa)Pfw)QHrd=1g||(CY!A@IEAMJIHVs zt&Cc;^~mF>PfpFMrV;gpu)e+(wn>6(>1K{xGc-v0@;&jQ6-SRVGeNc;-ls zzk}geJ)Vx+$$?2FmlCW${SqGRuFSgHZ}0I^S$5gLd&|Jh!81WfU;0+<` zt~+Sv<_6J~d&T1fOHHFTJ89oQ9y!~Zbs^LW+7b{rFnPn4?gFAv^oKk$-$HprGVHrA zNxbEeK*qoin z=Or{`}x{s6k#)2qak*M`Tv4ZmLB(tgUTI!o3)3&D-WaT;x<2-!|)EA1T*!gHC`e^LvuUl^=Lz*4=MZ7XyImVEiOtIs;wrmPG z6dT3w7cBQzi|8M0gJnnU=oFGNxQyA%f%^d<-f3tV}yR_opEnmOC zhUfGr@vYP7JAk89=@^4Z`U6XNYBWlxJ)_9MGQ! zUmtAg{KJ+(3f?k>G$krv!y1BD3@Usod}pF?GW6RYSmdd@JV2FHBmWmF<)3f{|98I6 zjsCMahzsvOtxI6Z^vH~D1Sfy0=x?+<8A+h8{kNo7@LWDxqO};=JGKh@EflHv!S8{6 z(Y(|2wF)1A&IIz2_L{_0%QJW=N=&fjMu99{)V(Mz{U}Mn*qBA{H!Tkq&_m7NoG;kdit^3Oe$ zJKxx>!i`)hC3MF#1PlqNr@$)Q>%P=d92@4M z%uru@0i{y2{LV)W-cSc2eBDH+O#9s4rfKn}0tjVm?HUf9sg-=iW^W>HA0WFs9lSm7 zk+@I)*t<{FWAuc3QvffB>-qzs3`3A7lD`El40f?3Q4VBn`cb6YNp{HVfX1_2M5`UP zu6I|?dowFOzVLCqh~ES!jKqkLtL%(+s$dFH?eUN&xpmo^(dcDUe zzm3=wNQ3N=&h+41__B#EsekJho~NM~x1+G_TAx#`M&&0=IjQ7k}71jojaF~hi@!GCM^zMD`ME|pEX}S01B|~o&KM}WQiw+ zUn!4x=Kyt3@Lyi+g@?q-^gg0?luq>d6r2V;YF6#bfQ7p3A6O^G6DnJ%33w;;f@~MD zGa`EZ>@)`A$-S+Hxh0IpNsjze{=F!`zaRZS#lO!cBavsoR#vP%dZ{hyp9wNH6X+$0 zn57Vdi^wo}6zVFCQOw1&yfsLTJ$MHz<*1&Pq0_MvF;6+I9E;#F_9&ip;vTjj*^zqv zT6RcD(7OdjJaGClgy_ALq1_O0$WuC7yqi!~^K7{`^htXaB;sy6fYf?+dPy=@^nk%-5+i(qxLk#% z*o}{&{(U3M_c`s214XQgNfRlSRV=~8{)X}8U}n4n%E?N5ft<=Xkx#xeW$f~K9Y22F ztG9M=uIrJBttJgW50_s%Wd*Z1*T!Do5!0&H@fnTctQ5vkgY~!oHE^s!M^fQW7+&9$ zArOM26Hig$TUf@Sq8S)nKOQ_zpJhSI1IEX6ut(hfXFz0Isjs@jnyx|2y0N zm%{%qLE-;fJPiqndy`&i?XGLTGsJ2(5^JbdZE|Wdx%o=>P6jMVvZ?28GP-wk7KD(t zY{Z$fw$oH1Sl@(5CloIY2R}0*Cs&4?C_d|VEad7;<~QHlEPyXm~a3?F63jpAqL>$MEsguh8iU38Z!1? zav&UpA3;R?mwUner3Oa#!k=TF=n(`5m-7JSb}2M_(}2BNFp^+{;@f*@v1?kO2*rZM z>x56UW!SNR-&AMTPg7c($`0>c57@CmM(9#+A|AZ{RF4G!Ws-C{WC{-yJgch^xsz+{ zpQPoIt2;C+1u5o|2nBAt68fo;D;`WJroQzFHS}wMD9^yUTw4>wzoPxP>MoF3&V9{x z_f$7;uPZK?>V}M%Rvl+}>ptXF*SkWrM$c8|&x|@GVfF}y%0SF#ta!qNvK_&!Za*W(Oj>Z?@+2z@;!D)Gg#o;^hZM%xsUsnH19cDF z^4Qe58-}|@f4;YjRyJl-ixHt!1e_)>{+WFAKfIo|p2z@l7@-Ak_Xo9%2^nWcPfRkdR6`2|uVC*qznRvl15Z@F?f_H=)e6OEgz zilnXxQEzPbFDzX-hCz-n%X{I88#d=MHw#ox;5vS|v5$-@_g(ptTbtTAemqJ%ulekx}CX~g`2R@qy*3*{9J)pzN&1;TE)Tx)6`_(uC%YaRo3^Qp12QA&z* zL@Uc2YOvFp(#dBGg)jcAocImhFx{sj00N_X!{o1?`pvGQ%u!^qApnUu;{W;gl$#SY zcoT5a)VB>CaUckof6uVd*UbFArY^_aYJBuc^uZ~hHYPgS$on;=bYuSy0XF0A|5M0~ zp_o75X$c=Z7AkUn^KZbVNd-(cVvf7JT7EnGkmG$_OdddVQk(eGD#5N)t%E1XHafWO z=3eoPP8A`Hj)7GQO|XLA_EpHSkeu)m@>>pt2~U z_x2TM@4?=sW3=A;$?$VU2LaDQvdmk1HWK;`K?;eun;Y&!sIZet~e75#Jn zh7a6z1p^Bf)u)4fm8+3GcY%(oQ=wBe`vbqgpfri0$wzujaizma(z*7tXp?1T#$AW% ziU@g(N>5mU{m^8}cH%Jqan>dEq%d1CR{+r)iP@kmU0b?9`iat9ALyrAja~ivvFs!& zQK!^7OMe}``{K!RiQ8D)Kpkdb(&qBC5~^m;uYv1vmgce{h9sO#%+VTRut z9hMjW{v+3AS(@4;M4reS5d@5X|Hj=~acO-wZv8b?{jdsm%ZxuYYEcrufp zP9-VScHTrwdyF5nH7JQoQb{R3;|jPl&q z76S~wBE~na9k>&glu5W7`yS*|@?f={XXa5(lnW^@5X#Ka4gw7PM#f zHpxv6AC3=eyUWD9H_)aD*P6NA0&@pWBHFOjaRZM%srnVaP6@1YHDCKGtI=pcdT%J!w}-w(;${ z$|Bi_d-8;`-2OXXGT~XYLGGBXUJLtWekSx>izKe;qj&pfD$F^fgl+0|0l zN;an9@4q)>i%?ri97tG?GofXg$Up53qVEpx8L{V&3rhzb)1jj6(VCAjxE8w0ii3w! z5~$kD>2^c)!l{!U=H8iQ;jJwbpe-kzVG~dnxP?=L-aIR%9NRt<)(JYg=lNoLW52Nu z?;J-ZsS8qW#=Due|ApZ!aM%Bozj#`>Dl^LE`yW`kO!QZMI4 zz~KbqtAT8$F;`JwDUvAE`)-FViIu#_6V|~1uB1;FlZV)wq}9w_zf@`hG&SsT?g<#3Py9Dz`vBxUgvlA2y%XM38V^oEpQ+}p(RAQpcoPktQ z@Ks^RMKlM6_g9TlX{ndndm)5G--}$!^O?eqoz0#vcc)Z3Foj4DcN0wku|CAmI?-4` zVL{WRn)fLSv2PiVe)QHP)ycYAf_d#eGlrn9($nnU^)jeL*@1PR*l9xF0u~5WuL1pq#m({EL)kHY`DnIWrDlhTaREEj79YZUpOZKhjrTap( z#x*08rW5ou%5TN95*_pU5|JCMqy$qyAZq5{8-B?Bm{(T&44A&&Fb`vHLAw{6fmH@O zFzly+^Zie2lf+rossC1ft)YbS^*n`tWGE`~f3Ww~VNvz%zBr0V2%>}nQW64#AgMGW z-90o&GjykbfS@!3jCAKP3`loNcMXknhmw+><+IQ8?kC=NpL6#0+rRT%XFq?;wbtms zbq#BMzw7?o_a}6eftDO;)9k;+#B5biegBqp)w61P5q^YB`%1q7@%wH7|0x=X6MUH& z!iYZQBjCMDxG8)+S_FP}%zV93gjnvJ9~O%Ffws%HYV-q5rD-qZ99Q_F+{ZUz)wuT7 zKLYh?4USoWI2!R<4fSV!p%`O1z>J_L73jYb{YPYvU;ZzBVZ{Y*93R;L^lrgpRs*5+ zB#ClgO2)Ip{Fqo%8@4mb8zY=mkkCtn9~k700H4Y~3X%Y|Hdt!bsvpJG4nT&iG%5X~ z&1XH`;OLRMRGzDa$5Cj6xLy%3P4}!)M)bQFxkVz*wPhF8v_xoL3Tl&{O<1VZE1ZQYj5dIy!*pA%KoJf zqC=S94gIOI{|`iD|LU`+cK@3GqF3DPdq%V`y!~f#f3fP*YuY2;?Kd1GzVA1+Jb~2I z{HFP9SpM(cZ4O;OUNQWE_94Rc7e-9UFGPO#smO0rdu=~8XYbO1o8&(wv-wZ_9J)aZ zP+~vdNISm51oG~Fm)sKIE_^L9M|jZ1p9hL>zJ^ET1!6df)@ z6+^d=aP63)8-1IZ#9)A1}nP}RpU zi#sBT6pgAI6zoC@#v;A$r1gG%5peVNBP0*Esc8P^D+^o{MOnRVqXsD z4K6lDu5y>l0cQ6XvjPO1oQ+ERMcBc((12j*NzC5$!A5-z1uGpuHOhZcC3&s?^Q@qF zC~_d`;|cskY3Kd!mM`Z;FBJs1T&chOlE`x4DIu5*d@dY3>kSU?oJLG_erm|RC^d_g zxENtSPc#$UOpnXW^hs1B zk)h(U_}kb|l(Dw?+H&_#U*>XUtziIeYObWlf=oG`q;@?8+&zrlyAw|e`r+*lHPi(q zxn=NnM3cR)b5pP54F=*(H$QgTGT-#J;hiL{yJF_^)^_VsA-WIFZ>+t0;Pq^1wjQ-> zV}6iL@KC>+*v58zS&NpO4VS0r_LfAVtD8-q*LZwR6wTY+W^$7WAxeb`Ezy$f%|>;b zfm*K>G5QrH%m%|PzJz$|cTxORawtQ(#0VpGY~mTljxgPe`(zqEht=IV-OU20+!#a} z0~*Dkku~Tf%s+&&P~sQ$$xmYJ#t*cc!Yg)lK$jZu79EC8B>yo?UEY?-TKD@PAeBjp zyYiF-9!iv@k%}D^K7WS8#Tj|m`}m?=9hXwbe1O;!_GM3l`5!rD{nx9%a8kj8zyz7= z|K0@o&#O0EGd^s*6`)Uw=*mxuMHP?aXa(^uWd^^f#Lv0;oF(j@pj1&0u>;pqGPj&O zL(Plitfo!n^3O-v(HsH1+1T;D<~e%05=>Q?Ik3;MZe-F7PomSv>;`$#J6cKtB6T&W zO5k*PR=Mh^HpaZJq`RDnVEOzIxrMzi8-@)tmuaH*n#iUXZW9-?EIY( zh6W{6I&c5UtNg#55q9IBT|z|-)BSdikbDItx6n^y58{gNm_2V1jP#tYKn0=25}%S! zFe_@2dT)>A#OLLh9Ih&9~ z*`pfzuEHvtR_x}spGx105R^Bb)hkCPkd$1jE2kvEb0=7IJEm2@hg^YJo%-3iW`c}_ z&pp;MGi!vZv`^QbuZk`CDuaz0p+YN#Lb+ud$c%VY#Ch~)`<{(>!7PnmJ=-Y)f~>UP zJh&iRgSSjNKdFi^efX`pdog2Zn-;Y!z~+LU=UGBoafhPrBvOSrjNsG00wLQLr*@$& zrA}_JyW4PCm7X>lFAVz_F1|(nrb`l4Ey6oU8{(EKh-XnkVtx|rY(9LAwU4nwTf4sf zvOYYrM`JT_QDF7>ib-ryL4^7|q{x$M@caAHw4a$^ip^s zrKM3!MJwvYL}GA-1-`VB2I+dFsQlqzj_hZ{tdn>7icJda~%JW^;BbR9e%O`2OWHll6`pwK?q}!^jPvWgCp`a!oj=! zTwSKR$yp-@_RL2vA#_mNI>0gUW4zZy6*#VT={(_yN+zuF+xdt_7q_`_I30q9h(NCq(s}i#q6q-i`LD&p10{YobzX4`s*@w~GOA z&>}rgJ{`?d7VeafbUt`e7N!!hv&LnmUQ-R#3kj5;qEc3BCz^=6&2KNYJ&}Hj?7t&h zU0Dk&TAmJMV{2ZAYOVGBKvSqQs|ucV#?Y{*ov$HGhDhAq)SI1xJ4qKq@lIeY$&}Vb z@61VbN0Cp*cW_5~N;>NxT~BfnC)AoGJdC{IT`KHnIB9a_H6KTodfdslm}^L5{U6b! zJbIP0NI|y6svUtXy``o{u@=@1;d0F9IpV#v%noKLpI2Le9|??4(u_ZqrBjlU@U)%< zX>x7vzS=dZ2aA#1`qa{yPK>RUC(Zq=*+EtQkZWQ|bsUM8U4me<4*RBJ>FR$+7D606 z&pT*~0r~#1Lo1b(CDR;@_^}35v7)MG%z4vh--zA%&MHBj2imnu`99NFnjJ%`iKl3>v%QIa?7-u(&NuqFd^Nh5I(_V&PY>m}aHXP%((q`bji{JGm=B9QoO(ZlE6g zaMKe92O1V;Ryh6GnO`j?)gC0f4S5`_Ee66~<{m$$KlZipmO2Sdip^CE#OuHVWf($3 zN?7WOQi#?ck$&P`Z~CrYCny9#MvKjquanw&7VyZpfEcTCV$NbfB_Va*a+z2>om~4Y zz}yJT%CZuvbPpQW6NU~DY;1(SKCol)4~bu-IYYXc+uYvjly~FoqRr1n(7Yg>b?RVO zewC2~B&4JgIK&!Py>J}+uxam*m?$-FOoyZC9+-6Ntw(_-16k^T?X)g$c?}NSHh_x* zCNQ4lZfhHS?=fu+`Hk8N) z6U$5?F7{!l?rQXauIq}Y(>4c1rsC>@U>7RXr*K9-1KgShE|#$nR~sU%X3UcG44g+{ zWJ7WC9|d^^R)tiD?zx|bWGbwPQ#e8t!VGLH7-qYTShqqS!@<3c8Z>nImM}+w?(sjDIL@<^~mCOeN>>5WG*tW=oEOgr? zUT$S{@uwdyIacm@i)**F@Hxhe==kR+K?IfW-gTJw$Jz!LB6E_K5!mr^%G+HT@! z5WBx=K;;dj_>DYoB|AWCA?R`l7d?v_L*_qA$u4Q$% zO=p!%U=Gh9I>lB+`;Ky0H{)?L*D~fXChB#$e~p>e4O$9JU(YpeUX)iZb4t3+T5jW5 zqAAlo#`jH5T8_`hfgmGi7)gzkGo-tFEOjTyDXud|TvpnE6P$&3@nURWrt3coIsb;6 ze>u2$D=LGPhX;APVjxLi$Nu3B?=xa;S{OO1KI~CjX&~DDw+>&{i1%B=EEY|ckrNw^ zs3e8wdtrz>Nuf`B$!xNS6&zm~`& zLg?(U40(9PSQ{bEn}Ty4qrm=mFVLUX>0I=pY7pB@h34J58N$qWgX(bKX8Tn1C>(6o z#?;GEI6((QFiYU=dL10zlviQ)PD!>e&WL9EU?hS352GDME36=uNT&>4I;(^`D?TIz z6B-lR(I3E1ao2IM`<>6lizknc!WJrlwJ{;cQ(Hlv$i5dsP;9&>Z5J2nAGj(qcD6oXzxqgdvP4v$)=+X`|qLA z5AYtnbr?i*xq_UN_j{m?YU9Tk1N;;19f@`-)?wLdUw0oD-QpC5>qe8q>_TWudecx5 zIZ3d@!aB@ORP4~e%8?F&J)M^s;I}95W;*6#C}sMRvo>q8&M_~~r~$EKO8+)g{y~!T z)s%|V9$;JD3z;_U9y21;=_)~(hB9?vxTRS{qS6!9T^EeMpekX+%Iy`OmKn+6-z*k2D--mO?3fj+s9+FjLlF{<1ZOSxz`)h)6yJ_j`948K32(9 z1lyiH(tH>njP1$MvMS8A1wN^{nbKv>MnC7$5xJd05cA$HrS8D@d=g&70-Yep{3N)o z;=YXae1Y;UPSHz7r$O!AyuIv9C$%iH;C_n%Mdh;RMbB3c9Zps?cr~q#-LeIEMY#l2GM0^gIp1FstS$qCx=8U zydLi@8ACy}$uQj;=Fn6MUMF<$4!p)IqkM@9^Euc?zWa6^^ zC&)gk6Y~EQKZ&U)CjtY*{<~0xWiqng=H{{mLP+JVgR)4Uf33NH3U3F2?|qOobXc{M z5VO~q=*<4!IJRi&ckW-7z| zUXrmlRokdmX=jSHt_Gm{Wt{x3r;KqwK#2?bsRi#i@65OMv7)|J9zLifwjud!yotah zW{v=eEY~M|m(YBa&6hIO)6E>^3GjX&U@7c`&|Pi&k?6;!(UhYA_m^e4Wzf%S!jc)2+tt`7`;LPD%dwH_(+O8@GTI0Wz zmmOL|biL`EZeZ^I3>q`Ol$e>)9M;yu_8DgiTa7=A$Rnmooex!|dCSIk_h<=?wBNZ< z=yAI+#896(Lub%VL}oFCxa2#|OMxlgO|ZQnA`M?6_X)>(jKh>~f_#2#B#^PXoNk+$ z?j8~rVHJ5lZY--{oG3Sem?-@L5BihjZ!q0tmm;2#$)@EfjRhr-3+<(N0Zt5$_^oT=-34~05eIm=oaLy>@G;+o z&O6wP*5);2;lkbJ<01m)8YBDRB4P#< zeJ|GHN0Kko@r${`&U<$Gjcw*it@xJCJ%Ta1sOI#UkxJfTsWhH~co(4?M#2p-bgo;L z@1~nOXu_^EQRJn&o4sU~JJyC*yBs!nhz*3b-w#p( zjz(4K*@tBJrmivNATweSancPOksy^9H(HK~%Lq+fQ@`|7hiXmonDV%+fZGQup`SD9 z=9tLWGsni@@-5)dp%v#G1>`~%ADfm3rqB{>uInra3ilh^^%9CwXa8#iZU&YvAOR~`;NR~ljJxWSMo(;TO1oCadM%~w*OK~~X$ zHh<#>nu$@ZIleh1q>h&B8^H#2ys$;_&fu78ce@WYOzFejVZMnqE~~q zQU}(BmLb%UvX;COva}pS&9Op-lwOy~@&7i+Am$ z>zl%GdE|SsF~*_S^$2H2@}i@(5I(GtM3$%3K<9exAnb194H3;{?_y|Vj*8f_I4sp5 z-5#(6qw>O*u{tR_m{4IBor#Jf^m^8#eEnS_XHD7>TKtcfQ8gcq&)OoNGxnS|Q;+wC z>uIrDXRmm#?S2QVnUn1e8Rc(R^xAZ6$N2XGiPrc}KH1`nC?qb#(K&j>%xAbhb+p2@ zWlhT66xnhf%Gp_pnr6(b|E2x?JKtVVA71Qw6|r{VOCixct0op3@MsC=WD-%g!Wd89 zT2}m<@Uw}U;8Tcnn-?idPr0)An~dt;Qd<5vGSzp`kNiq|EpgFw{>}G_l!CiDr^IPl z_%g!P;CPejI?S_2P?&7|&okA=ptOs6L44Xre_b`u%`w?`B=G1~tHSa-$u10j%AKbV zH$W)39X&!sfIh^AifzYg1JQB6jc(Z`ueWb4b{ZB_X8cfk>=BM}>vwR1liag2(X z%WM|!Wx?D)?ZHf}RApBiM%iV-X;5M6JTGgmN?{ug*1DaF)nJ%02x@OLbN=dmMJyRS zgA$o9X)!N%KTC#h1s9-4>E};FG~9Nc#B+3f`7Ft>0g83QjSE^_Ca7GRbcm9-#J@kg zd@Z~%u6dX=w+b~E< zQz`2t&KlY(cWUTc-S0e?5Nrp(E><8g=xb6fdUv+mry!v=>O8~gWp9j2bfbE$c+=tO zL`ZRuU$W;I0d{$~SSfn>(V&E?a(pX$GCh6ftp0Fat1xk>b_A57A*rWmCJNo3?@;~e znr3_zu+aApuzJfkq^xcitpa<2i_II--cuhHK(pXS$;TQ@r?&WtAeOyIba7-KJm zyd{LSThO=+0`B*hlCA>hWsP zk#wFLh7aa!TxHgZVsbkw*~?3q`NKPfu0S)BIxy;1%xc8Tw`@0V=38kvPETV7ynlLI zYLJABiI43-@@9%m1(#QXCoFPGrnGWnW&&y6eM;h`zTW7S!t->Jo zR@$`k`ktqAO?3Gwb=AfCriw0p6qToG)%jp+#qQJgxxi*LQpe}(sQODT-mmhWiR1b3 zMl(*i(ZsbW69Q1!GKIVb3i%P`KCG3*oa$44#i?Q=s86aCJ7CX4Jxm~5xRnIM(&xPH z3F&cMS`kC}bH$0J( zW>>!<|3Mt|E6eKTy~HE>zzE8`eVWw=VYA~^CHm?=YxXwC zJVnurPj` z4KSkY;ZPhyoZ_~MAVBUsR!)1DMz|zEG+#wCg;czjCw3^va=n+_-_QrgY8^)keKF)~bhpJ># z$0Tvmgx#J;LN{M6F;7mr@T1=pp%7Oz^8cctX?mmC`~8_&cAbZdyp$-_+^qs!XUg(* z9vS~IWApy11cCmZIhLB70?P-Eno=LOY1z)y?xr4xL+qGejXbqhSZ~`W$Nk1@ zrkqUNVQGM;+szm~S2I}`yPke@WhJE+{aM0Ay?m&Hc}m5nSI;xS7XKBSq_MiK(ARSf zLl1NE?TgRTb4lbAEO4=*IWE$H`Rj_&c(rhuXN`e|+e~%ZeIKOi&<1T|XYA#YUGqYH zgieLx;YXhhstO6eVz*EpcP*={=N$){RF>3d$LfenJTg10HRq-)%)!EZQds&tC;tP> zxH8A9hvoe8$PvR0q90wIg>^yOBI;7w0P< z+=NusZ=`r`bExO8s&VQ&jjR9!)<_7zdfOv-^KG=IS0=E;A5?RToU$Uz34NwZMFdbr z7jXI_x!V(zLaw&v%5sy7OAH-f#h?(Khq5up2%yn^Jj%79@0G?XU`u^m6sGn}CD!Dq z=?t4I#@e|zKEgA?k}>JNaD}xl#Q%|W_D6%-cW*I0Gfc@U6W*+fm`(+3_LJ1)FA-&2 z79W_|BSe9AzoTbXn01BV|xuSJ>sr=Phqjhnc9QQdb2%qGRtUXm4KdB5Mcv zs;pMl1yAVDM0W^Ck=~$I;i4tCw{nsx@%I*b2=y^eeNWLZ>YH1RGe|l|vYnb)3sKGj z$Etm-e5DzGSUuZj3bKb(*ow_&yA|XG=u2?od_5R7bCJ*h$f8BB0Gi#ur3oWD*xmx% zjQ1d{VhvE0K$^~d_8IrnY+BmeDNuQa2CoVtmW*g7Xh4XJilcOPafH`~Gtl{wy7}wR zH&87|UWT9g9xnmUdk((CkR#vUsm?c%?JWO$TJs~IB|Epc;X(2xnE1CG@FK+T_~BCk zB~U*oo+|Jh26VB`oW_32$OPm5%mCkN`~ywAIK2%RVoI)`rGoj>{~<}+&H)JHU-lAL zbaGr&zLR{?lTiaIX!!O&`dhyR`iwlh*_q*QYxAh7v}Z>4h8GVQW}wZuJchbE_>r;W zZ>_f2jNv3M>1sx+D}i1O&0F!{RhK3F@2y-&6`ASM^74*6?!Sw#Nsgma;zmmAlZjv4WG0dyrO*Q~aAkRYH-U)%&hT-CAtoOLe7 zXKK1QyH<0`d=SxSY{KdOm4RqaN~@f!@jlwedib=oG&Lf-XKJVk4xLZzCFr%OK`^Nc z@ikNZ_vI-5EV%z8Q|b?oBSeP%Z-A4c@AB^NYuaxOvwbd5x;84OuF;^Xjbb|*u!4@n z@)6>SZEmW+={0>FL;2>ubj03U=-K!O@bo=7Ci2(E)9JpT+$VxqBuBSn6+6)*TT7is z-AZgLNL^S3TW$C~xK$Krtxt0kB)>-ZROl}`huOkfBgP%VD`{L7GMDjm7_I7jdTIuC zXTc1XCHZ;|18>&77&&^_)szsg%F_AH$XFPr{s^ME<>#cEpR(P4nxi5dv zpv?v2>0J{4R-o&zpmC6MJzjHcaIsc)xG&+|&MaCY)Ikw-W&$CvfBzJc!Z%|6Etm8j zrGmVcNlgdGcU+}r_=1tYq!Fq`d&lZfjj6@2$7KwE9J?DUzzG_?MB-3m1~Y z)$>H}JS((BynSU(wU>|3XO6p~A$fR%*bT@%6TULkLERU(%w^0{1Fv&)p}zLCtOoIt z;1YCUw>#DgBx^SoZfB_whS*P$qtXgzre47*G6saq@Or6wif7#YSXqtU=|CI&{dIT- z7qxwj%YUGyxeM?6$x`H$_Jot)gR7>rptbXi78KUN8u0WIU`3Yl#!lo8VtGu}F=?vT zM7ttB!@0pyH!6dLU~rdPgtt9t!n;T?VhPn;2udcUc+hRpiW12$PwhkpKQne@tAX>F zolb9@=DielW*WGGc0W;l0YGM(;g5_`^tU=~u4W!^dlBBcCl^3#OCG6T;rT`HL$koK zFWcRa+t}AIH-!XN>()jRzY4!K7#9K$kX2NXpU(PrR9mUQ$s&}zPI-(~2rW}g3(QzU z?|iZRVqy4B;*bQG6v{;jK?|{Q2>gOcuj#ob+HmbfR>cZF@S-_qe`I9Pt73l|x-r)X ziyOb{r)uy@4csjWbz36w8Tf%_u5n26+QeRPQ{b^NGpTL7&8z1{zM;veK0cw3SSFQ9 z-jiwD+AllF0`&^A(kU=x!e+I8Ubu@%C7OFl$LLG*R&S7eS3%W_TSc1TCJKDsmhLU$4sk%Y> z847Ips5sHUeQtwtzRQsI$&*3LNgJ9@(tEhT-{H%$_5)=}jB2S^kiR<-k2;)gtKmb2 zcJj)YYTcfrg^m=;tt(84y%WentvJSW?Jiu=yj9HXe_H~r-XyN{BM`T3Od&Q2- zNYPPf0Db9-XNfSXYz2OM8Pmj$sVGT?%wE(j`q{_C!vDcvPjvTXfZ#>HtM5h zV0NUTbTjiUTe@rENzj+w+fo_f1{W=8!YR6s8FNS)?lC#dNn4n1ef6j;{L+yevT8;e ze=%WaFCr~hVSiCaa=z#F;kju$2?9gt3i$f7uYJR%YW1>hNnevvW+#q^yxbrH{MCq^y##yt8gy&q_%^W79frmAfYLlHqKMFvGV0&LSkrmAlh!B1*+lJ3Zs;`KdF#KA4rf=Uc>B! z8O7HSQq5hToj3LDN5$-o)znnC95oy~GzcU+@w5(cc~95Mk00Q9u&YArkY;_3K=zx0 zMO>-;o~yD=jw2t?Y(xiCegCGFLOfVsIVURyzcz^3);qJgs!Rz^{al^DCj9uaKyJ)2 zETa=(Wh<2DbWbI)9D@M6rh}lx-%`SV-%|8w|HD+!8MXAWeeh3q1R8&AJD!vI?tMKH z2($YfG97v8{;McML;ZJx4eK9he$PbZ@LR8BVu50cp&PbVN!;)xb>c%j@3sa8dj_}^1S^e54 zP3Oifm3)l2_k0!T_-e{oA=KM9QkyqBn8lt(-=fSO^YP8RE}s(euorYt<&ypMwtlR( zI^-a&)KwYOZOi>+)?QpzNhd8nf=>;I6n0*)RLEh4)Wy)7(XxreK;?9z^FUZa=}QUr zKCcBaq-d4mX}7GHz{TLg4Dl&3S7}S_P^IMD2j*z5c~_0=*IwLat**TRN8CnLElU)Z zZqXV#2|cldljSaV-g1~*=FrdZo0iV6jObSNpzTZJ;3ZHKcw6y}TMjgpWR8@MPv*C> zYkb;R!JkR=6{^r`ly={-99#l@a#>fFA)J5e2%a>FzQwbW_@S>e7eo4~R}F>bFX*f# z0`cM9bVJrid;1dsndL^voAs0Y0ujRd$v!UK3Pm)YB9+hMl*uG)a}yLyK^{cY9a%|n z`!5d3szf}U;SbCxJo581Qy?R50iU=MUd!xlp2_r`IHCgDN%KC; zzFooj-hkv5&^O{O{Od5IJnTyg&*HuT^T^+Le=83QFL!w>9wOTI5>G9C?|ntQaXjW_ z{;m1mtKW$qSNcza_f#fjfQ?I3>DP_x*JEDCjE*2YR{fPNfr?JLXPijaGE{@*Sx!|e zd^T;4J35-^l$b3+Q8yCljYsYcf|k#vCA`~YV&-aQj;lJrCvpT9;qu#MFzhahh zg>bm;ygF3b5CT+@7SkIWsju4b-eOv|qMqH#7JYOx>mzN0TI#t+5Ji`PNOOS=H7?Qd zNkKW|#i*goyPBG{z0TwH3hEQCPN+sP!;tE&%Y%9~wGQQXNCx;==9DdI>chE5#Fi5k z<4Q%Ww^7IF0lME46!Xu=-lCugncCsm9wgS_lB6C+D^H)wO~D)e3c&7W4&BtX>>3DNo@mJ4H9;31q1io5ahY2IuE#J8e|$lUS9QQi)jMlHp+M;6ZWMzEId z({}D`p4Kppr!F;RmkihF^4=3Wt_q!Ttf9}2ok1ss zs)Hl`PI9g|9KN6(Tdu64j&eB2vdg{eTs$0CoKKvg{Nu`Od@Em1nQs1ezxe;foZCO2V9K?KB`QC)ig=8)* zKhGm?*q3kla#MIc3($|MGt3k@MDmu5A{j*72(q;K<+ijXn0sG);q;BUU9|B6rfP`Kz`gO3PTbkz7xu=piMgFlgetO6_Aq5a>g9Zxz|mo@l5c2# zg1o_}zv3p)5-ub9rrOr5xg*XoM`g`QEtEstbNu({j}E(2W?0#5LXBqASn)+>ZE+-(m)N=D547@aSm>3^542k54InsSZ}{xm92T(wP*{F_ zY;XFGRbAxPJlNDzYrOsRp$d#h*#P9(^4lWiU!8-q(5Tzh5hamOAO8m30`hS>u@h=> z7Dpo7=s65a$=ul*+TNM#Zv_)WNp~hq^fhn*6lgtfiTYyfM7ZasLhR*l^+wQCWGr;g z5e$FPvx&$x+yv)ttq!s2GVuy4YjZ+Wsn2k_cld@EKaiJ~p?N&Qr0`1H1P>Ged;SiR z(A=kQz0pAtQ)@Ugwic@G;+?gkA+@%rR@koj`t)|8@pSR`#Mxk)6v-}$5r;3u+zPX; z!wFAf87Wk2&Q;qKQuxMs=ZfL=mQJIp;+oLxI;?=FIyWot!t4pDGwk-(5El1ID+r&n zeZUr0?O;~9(2&)Mxg(}SvO?~X->Pw<6ni3{paEB%W>nQE<-ORLexmLI@>m$nqN4dW zT_qrCYg(qIWYXbrPS##JMg2Lt(9rYa(Fu}i?45&((=ZoArno$(PXmCynnJMBTJ;vW zXumpEE52#^+Oc7pTkUnl-dTVxw8|Lxvt)d)-5}a>jyTR1Aza;hDwQUCG>mEQwc?%D z3lo*RQ52#N^P>WDj*GlZiwSo5CwiwbvUS#n_df~TZz-xps%J2<4jzC4^}Q)KOsgMB z?J|aMvU(9F z^-@h4A9h=cZD_z_U;#D%8HxYhHTn0{;J3YEcljN)zaCXn517+;v^t#uEOG?EnIIx8(=op;eqy`l#+Avl^aINpTJc*Gl`?zYtj&UY6 zAnz&>%V;!aSSDecx#DPNn3Z?Cpiy6!N*4_4Pag6uR1mJ`B~DKJ_U(J(DBI~ckAXyM za*T#(cJM1x&k&Xwe|%Jq;XzssM+ZZqN=b%Ur-;@-vc*#RLScf&sb04@xSGL7kXgZt zbWv#_fif!@tlM^)Ti%zX_O!MXA}u|4SE{U2pp|WjK zogvzi7MP(~ew2Q>$eY_v>N-0ooZ>-jsNfh5x-+k*Twv#_$wQNsAp5Ku0iD*Bs*8RA z@-D!Xka!#$FiUn~rK)ZOC^{u9&p85#xwbD~WfXrbmL5(D;3KTX(S;5r$}n?HPa0o# zUVqE#^W7&iCC;mYUk>gQ1I~Z!Kqmr{9fOJvNl=SzWN`S|C;WX6v&7a%bjPwmJJ3Gh zOX7a7(VSJ$hlCwjWd$G69c+@uQU5^4O4$p{A1{vnCIx~ z-^Nfuur+F=W;W;+6@<+is62^CO<)BPQX$~P}y*FP18RNcz zx;10e@9t@`Q?*v82pZzPlP1NUjHoaGtLh(A!$HIHt*hTzGAkl`16_-^EcrtpNq2cX zo_pq0J@!erozFkw(M$&19+R6jQ5Kd-EZtHy9+FP{_$^2RiyQVN*m6hHBQ&)~LKvo7 z-Qa&ulxOf%BSuZQ(#Q8$)O;c_=e_#F`XW6>l!pCK1H=#`8j2IG2)32ZsjjT5k<%5g zFP`J2rGh;;9g@2}ftI#t3+lMBo|tBmS))A^;TaXjRYht?Ac_{jJ&hRB;PxfEQxoM_ z*I?y%e$DUvSPH_=xO-gWGWn72YRO4YjXL^GuP6c=Td)84z8Yvz0zq z<^MAv@Nd_?znW{`uZuM~&D*DZ`VI9%(%pWDxRYAQA>m|`-Tg$9Fc;$x*EpI<%dj{S z^jNx+)65xyFz&q(`?bX#3n8PrKk*wC^1;va6L> zeQl4J1>QOHHgC(O=S>}=_BY{Sq%2A)K&A5Mu;Wqg~f+nFynEXoa%dc z7u_?XAzj~ytAlpw#HWkumvemwm^C5KOZ1)&m*@D+`Acw~sUvzd_jbb^0XR2HmKc?y z$jWmJC=Z>0jOo z9cT3kM9rz_1YMJn+nHX;p1Obn4w)^p`}2Ukfp^;Ly^ zkhrb6g8G<@3VL};5VroVWvz(fCO(^yv_QQh&)@<4;j0SOx;L{_`}kioV-X{fbVkv9 zAR~yh_6};nK`U_uWb--gb2jdI$k4vpaVZ9rbe*Pl^Tj}s$B}ITGx=<@wIv+9Mop_O zb8x8RrM~fRbGXAh|8htOI5w#BRot-^In#i{x|w@<@MjKC;aRB{dt5*t3sv4xu)G-YHImyw@Ve_izeoY zdvcZWMUBX;@X&m>CJzYMy33>uHVks5ogKfo!^fU;NOdueDzk72y~I8-t-oR%JZo2F z?SykBMZ&WWUA$@9rs70juaHwILDnGby^TT@Vp7fa9dZ5E#vwULs#a>BH$Rq1SSGj% zYL3_n<>styXKP+1xN&{sieLOfrJYXk{uN~2${4BUWFZ{P=G-fGn#);m%&l2RSqP~J z*Bx*2^IlfkQ(S*`D*r515Pw~LqeyWV=L#TP)ZC}-(KJra-^i@7RD((Ozi`L_`VXMn zM1V=t|LN;*D&Aj(iuZ?WSK#`Iwt}8$6f$?iL5G$Ws_n`b(|S`jF3ef5-AMAW0$)gq ziHbY-k*2jN->{6L!M1T?4n4hDMA4%z`i?20m;wCTdCh{`QT_77eZ$_}x<1^aJ4V+J z9Aq&s3%6dP&m(5_Tki4C5(o!+6#2;3tde->h6D(&jD{0cSSz&vem8u~-U`@Qx8i5! zi~Nf?u+Af_q6SBDLOf`A!>24@jJ;|-sd&PHDN1O*#|3vny9}x79gfHo*XIuwC#>XF z)E&J&FGha;vUs2qosjg+@)Q5=YbDnw#o582ATX|=xFhem>Jn_3x??AAa$F|Xvq!wbPq&Z3mhGI>CT96tQbr!xAY2`YB>w z`neJ0rpo?fc=rmcNe!UgKLo)UnU|1#a$q}7()zY zNS+@H(l`dp%1xq*$H7QrRQYdz~JiXY!r&^ake<(`+-t?OYC7AWmd|j@n_XI?pfcv7wd8xHoS!B-DP*oxOVdLQCnp1q{+yPZbunFB4LCc>!Zi{Q@t)+(;!PD z%Q0|lIR+7c3=99m$275nVh<=q%da%~WT`=*2%_e&Cx&`=)ap61l)yCoo_G(1u&9x? zHi4@D&oVFI?=tV-sI$M_P5+C||HkNlRgC_}3!WS@u$7;a5P^nHm$+FjjIpKtfj}3( z9)0N4Ng zbbPjN4E|<8ym+N|>cF?StT`MuBs~xC?|rAxO4Z{$Rp5+&lo93g7`OJ*h6M^#}vV1$&_L37p{tg_i3W|+4j}+Ir7FdSZOg- zBI=6PN)n@MNdbKiso}41@}C{S)Krg8x%Gdt*fp{~iY6lBOvL5`4wd_aBrdn)>%B(q zLj&}8-DT_@yyap5zi%cIp6p`iFAfI(-dO$vWqo_#j|N4#RYaAnZ_EK&=s@pv11&&z zh}4qITh?y+bB5Z)5RhZw_C909*oO{0=DYllfoCo9cf(NgOKeZF*FF!o)PT}#bx`T& zAos-J&%@4wKaE5i1MshRsnXUwH23_Toqq&4O%pV8%til5TN#&KFlupO`Wf7kSNMMl z&i*St|2N?Kivqr1!8`ama8X_sJVVoY;$Q z;Z82|=8xiCHawr{e?fY>1}`|i>Mjt%G++{j7omCv=6fDf6iSob?P0v%xsi$~Hp7~K zF;g6yB21{4Qc?cA1Z#I$bI2KoU1j*?Z;LoQ=mRk(njqDdTP9>H+&|DTTAXTaxIUN) z#1GC1FY1L8-8dW!e)B}2o3^&<`C;19Oq;j2)qqPAp1et?CAll@URq~`_xpTJ9m{+1 zMN3*l=Mj=4^*_))>tlVScl$(T@%A{p@hcUoj?tJwR}c1L`LwG( zC6j73;j)k2$?i)pP$Q}4!{*(xBrZU0xjR0uiYA?gSmf-Rgk(nmevve&Ow{OZ#Bj`G3gATAqz~v&nKMnHc_>2UhzwT*|#tmz9H29 ztu_A7Y8i1N0<`W0rQ1SPi`y((*sxnpu23G~NTf$x$Qvr|g0zZP3{{w`FiFL)#mY@6 zo88iqwC9QGAJ` z7R@F=>h+gNXh9^}CV0U6j6YRHn^DMwVQi13Vv3>~2t~dUBkSmDV;LPB)WV%(1=iY0mHs6FtrHhzkGv?UA`GuLC<<9vU`w5&_q(+4bUE zl&~#*s;@TVV-9QFWfe*{!>gBvaE>Ub+D^z+0&qoE_h}CxlaknrK$y$yAd!zFGNs@x zF&bdBO@U|{an_+=L92Xcx3}9%HuAqv6oSG};C2>RX0w3ALVMlrI5f1dAdOX6JRR@i zN`HO=grC503a**i1uyMH>6rM=X=jMD;rO3k3j$PS=dojq{Yy{T>&|5-vrw4IqAEYmFRmP3ZKEwFzvkMu&b*#azDxwzSeMi)kosi5qJC6 zH&|WNz1w^4MR`;Z86^|dH)Y2h7JyKA^q&~t0c2S?m}RibJKU4k`poj+$VA>kzHTPb ziu|S`D(x3i5zs{EeU%imVN%6R7?UW&SPY&0X@B=Oc>EPS{1+ep29JLo@JL*|D2wGH zd{1jy?>1YFQ8T$Yjt=Q*yNQPA0{+>fLgC%K>;J*tTgO$kt!={#6;VP8>GH0=Hawe0BohSZ>*dYV`4ilkh=q>jt!x_~i9+ zLQ{K4R-^YK>&(eH|F-vLK377T<@oW13wwXn>&pTm?zd}aSDsiv{5i{;-}U&rUh;G3 zVvZ8_w)erea%%P`O_Hlmq`w!$!24bhqe?Ms{-z+t|DY)JN73mV@5Xnc_Mw%^ahZ8SQH1$3~ki7$q$vVDV+Z5GvV{)V+@W}{y`w1fzt2CzTOHPmxZFZNXl zure(EqGV_HcvB_7TKo?cC>5~l_SP8B(=##|VGI45$C4n2d;pn<(Vgj;&xYfFYmVdz z{ftM)#P*l;{v&k@d$nPslg797s}F&HFW@m^gNO?1BEOam+Al z!Y66UYNW~++!93ZdhZqwh~B~uYslP|)SB72lz#a7F%?jnf0DxUz|H4 ztb@G*hy?tb3iP8o^e5UJD*#LLjA;h^ssq2Z4l`jW1Oq2g(1ji1-()BJfNGbpR^yqy zlNs<<%2Ca|bBUER90ee>v~T>6jwviNQg`Awg&c1eS!lWvRu3&Y^C(9btLGBmAth5h zknETqTxLztt|0flYAX0-Id}8MNdAvS>HlVf`HhkMFJmOP;R69~>(a)naoB2UzGEdZ z2-Y^CeGM|@KTwlF7$W`!oASHqjmz`;zfoEeiJ{hf$bUAsbKweDW%9$ephz-jw{k!V zReE?PB8qIa>T3rZ?H*GVa>y;tA5sb77bdu_%pq4PrSaN>gcf<9bv zkcB(fX}56GWkUubV?g843%Ye&X&GVbadvN#$_c`0(wKCZ`^bw_CU`NDV@yx0vmOt! z7@N{NBA0eVUKjNK)y47M-z79(UQyMdub>Y%@wVN;0 zMEng8@ZaToR-U7sTzXy3se66kUt|S{88lFso~oh-jp=9Ue!aU86{Rf$ALUhX!{V1a6fGP9O zi!9u5IIH{EURKce%vq44-CQFwe`5cytTh`+Io!X~6I z9}V^VY*{k-#jxLf=;>b0N2cK(9 zD@^qTe9ObH5@RG|o)pQ?7_JV*NPQqiUY_qGvM1b}KDxcn!dKTsbIbWv!>7hdwOe{p zRcNv*Rnsvt>f)#AZW z2kBDiUYRP7B%qDb_Z-uE53z7hJ&|U9x_3K>%?mxEwMFsJMn)Ub4W&0hvj$tijtj>1B zV^KidM)_*SJLfLT;|Mv8PQ6gy_{PL+`!?A+B`rU;aeYUctSyGc@(}bS6Dv!oFqc_s zDEZ);sjx;6UHIz#G@37ZZfde?&oJ1xApCx8rcdqbNrm9nhHGrYuI;Q5U4ayF)lu|&4<|{<7B*;;5n-d+l z>uC*q;u;X&fo_SljN_6;0g+p2)D+R+la*Sp=fGIJ`_*#u$7rnrjz2J2L{G|%R{R}L zh0(>|1$1c&NncJAc?J2M1gZUwbaeekLejj1UCLF%_a~ZJasZG;@ID9D?d>s_Kd_DF z=lne-=@~Nrd9e#DYDu^9PTjiN<-m7LtkgVYxLV5>u2h0BzW0F#g48gT+T#mg(N4og_F=2#8{T`z7ojmb#{m0Hp9C~rSAnQQu z|8xjY>B_=tF6m`{|8XGcLa43r8iY=;^%Jv^&Pw9hkmKHG;)Tysp7p>;+!y%ONc%r< zyj$l6Jg;dV|Hc{GB~Hl>KUUr1i$T63*lKgZ-G9s=IpBW9!mG6W>RIc7^vO43`HS`4 z^YqLgn2z3-@PDv})N(q;hP9rJ09GJ?biM~Dr&N+;c~x=^ng;mZCUyXJScCHy$FwHE zn*k-w{Ifa#9UQLY{A3<0{lz>6a+duY2pWg~%{Io;^j+AIFtudOO%|ijvz)}+I52-i zzU3o}kDn3A<-*$B_nfFYfMD;gh>{4QD9H%pyx-j+Q>yBB4pMHGvb`hBY3n1Z)^2y) zGi=NR^27I=9*z5Z+2URf)k~MZy`;Gv44CH@j);#vtcXYeBizu+r48#SV59p9&;VMH z|1zS%y!Gwbz7j~%)9v~Ujzk%acP%XxTRlXC^)R=tKmqBjAYe`XC@pP#=JrYp`;qPJ zh+_tAKln+@6Y$eFJk#EjgBQHkM#zCKXN=nWXbna8NM?QU=HfU7#>K|OdEN6H!6j;@ zSm}OYsjtUhp3m;7JKI80tC2sce03G=j=B^!cMq2Ty#5LTrIJ3HI=(p2JfDSmAmu`< zy^-BQF6%YE7MDtV_}bO1;2E7a8Br2g+&)}(z!&#nk!|jR&wt}CUc1A*)ZmY zTlCgWk_viIB|BDI7tx5Gb{ofXRkoaRv=r~lRq^I>zJwq_??P?$L$cT+n)PRg1y49l z4K_Dzt&{Vx-rTjK##mkl7Qk?8xU$*hM%?%^yCNthxBfEC zVn;G^4sA(_9P_KE%$4nk2ACzybQ3(8^Bq)TJbu*k{=m@pzo2f{k9LaxaQlW>7pLSe zL9F|Io_}wpQ$PHjPMnCA%|`^V+L39Vt!YSF&HSnmr$@T}5aW-82m4vq?}%*JGPBqis;T?f@H=X6ygM5jUg^QXH*tvqBWiJPW)WqlDOQBN2aqR`rB z(WN4AHPht!O(+KDUPCqZZin{VK`a(~)uXLpX*OcOp!Nb%)3V>gnJ*y+cofEJO9r9s z=qjVTPbDq)+w{WXK1mRa7!bdhAVWOVyAnhVjXTZk%t@$*Awq1;PIHvK<(;y@E0R$R z&+0HMj#>KGQ!=sgz(pm~u40y5bO)JEONT7GU%mm`)P(f;xudiuj6 zQ8V^4DMQ}ePSf>kYQY@e(DF5F`>tTS{l;7RU1#`%xc9$^xa>bH6WtWV{TmT@HGVxH z?Xs;V99|+^`Klf#c+Y$zGa~-USt|ALr1wxWDz=CC2TIG7WLRt9S;C5v*A(ma;1%hd z;>i*~&r7_adAV@0!3$*54SW1v8vGiw^>8w+FjQ99-ZSaXrd9*(9sid=$y{evXZ-Cyy*83oD-v*QJEhdzXa_7! zx`7IT0)RhL$?E7#=|eK(?R#cL6K*H|hn+^1tkco0U6OrAK@OO3F~4*N-tX@~Ps0=7 z*ChrzRZSnThj8Ipz`v1{hS;y%qx5gI29 zv3`>A)W&>OW-Uo|@Z1{j=b(!U?x7lyMGYxj0mQp^jZ)$dH)MT$F`Lm z?y9Vmu+V5jy4VmjAa@`iuOPG&G`nSk`0R89LSxPQa>;H`zNE{?SrHc%YuEYU-ZYVvV#+M3(1p)+1*dT>v!c@+$C*GGNG&4n_~nZ7 z!|=8lVxnxr&+Ffq$XJn9rKt)=#e8~FVHg@#8}AY0r5V&Ob}qXf2t;()_@nRlyWs!7sASteGR^%b z&fl2({x3E6t@9BkysQ}T@HmmBnuo;r*Lqf0{bmbXwNpht9+#;q(dsF8!#z4kZ&Dq+ z;I!46c|9`?ilX;qCNATwLltin0<>*6Fmk8!=3uNc_# +Pr5;&DZjHs}cW{*SDsA)YJVQZK5E+qAm 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
r{hQf zNKq94gYZ|;KfT=lo;ECcZ~qX41nf@SwxI(Wj#>H?+@Ohp_M?B^t{LkINvSVeXCRap zj~qk?;0;G7<#QF^aFdXwHyIoCPIJv3+cn>Lhs!4WeQ179C`>LgzE{SaV@X?CPix$K zDI85yV$6Jyz*}vZm=)J=t~ky?FBQj8Xt&zowkdbYp_ZaBC*?R8?Li%QznXa7WXz+I zR%o%pEPB^Sg*jcfR7}!ZIFUj{V2I)|M90>wsjM3h^mQs?K@29a6GcXBEgE zh0ic&BfA-?C`W}Mj$*R_y`8$IO2$Fp$Yk?vpN7QsG4Icfvk#%+Pjp2Jq+pPnvS^Ee z8a(v}o@!D0RY%UhJ(e2eKiWv+O^jAM$)vsYJ3k*|w`_#Rw^V;pV9C(}gS`xCLRr*2rFqckbx&7_f1b(K? zkF3eX@tIzBf%iObh~%KSbZj)$~yIjj0Y#m5?4O4)3ijqGt{FrjIOH|SxoFJ?_G)2}}m+C$YFc)Er&a<+_ zj_G~5#-E{&p0T0ufv5W4A}hP%=z|M2l_s-YPmU<)t*6I3BTBh;fo zP_R-8Gd4jc_<@%fJti-WG;9k4%cLlr8LnAIfQ1I- z;0e`-ms`t$fiObor9LOzfcL8sD$Jmk=-!wXum}D-il3`BXD}L{$83xvWSB^iSElT3 z5a;;ZT;^F;G9Q%GjM_lZ3fFFll(=h0?kMa=TXN`lj^zmU?K<1UE!E`zG4<7dQGU>0&YYP!b6qpXvh7KI`mDnU{JCM<*fREgK4-D&_7Sc?v|iem8jgSYEi>nmVdVW| z#=oD+qKI}Ne*DZR z(_*D~_@6=RWT#rrsYTXG-jbrG+CpBO{HSGKUm52_&i4_Y{Z?PGZ?;#WE<=BpA5wgT z%TUbw^^z^|a}$dvAKmv`S& zx_0LDCb2&cL#2&_CRmGZ7$p%TK_{l2=E(dKp4D%;P9t$|%9sF7bn+B8a|Usz{zi}b z4R8cDS;cjETb>&yrmK4KH4a|o^Vx`vFkfmYK_qL29?z01?y$|eLz#^(CEh(&ROxI- zhhO@Za3Jr3kee=IIWaM}78HWoV8o$VC58P)W$OXDF%FIazxHYCtAbBADwtw7O*J8l zW%Sxh3v3Rf^n8K)m>i;XWEk^BMx7xQy#+5Gy790p6&5 zQPlgla&`gHt$p1%6DH;k^~!t=1^N|B zdNJd2_0pW*!X_SKu|co;i+!+%ht-=oH7?o}lnBZ(ySLME2;?e=_ePGTd6V0Zs;Z=} zd%dcok0W1{i?{P9U%p}@B?)SIxuNB(6d^)G>&GKXjRR)EuHnCdxbmzfeF82q6xd=4 z3^`lEga!QT(2(lp%jb6c#W6+@rerJ`w=hsG6FT_%^Q1T@7h$nuC*%7CS@PrM=O7&g%nt^peO- z9S-u=2NaKJMm0o&%K;WWUa7F)huZA9k}vCDGgOABk_wPW?-4g1B$Z7>@P0&;N*YDr z#Mhmd^!nnbqT^r9yM<7V%a2Qi#|Rn*(KDa6P}>mqs;CDalm_LX%_>_4)W%4-{d&;>h;|&;>u~$ zTzt>TVfKdl>>~xD8@Vo2f7AdNT^f9Ja_`#g`6)y(gQa@w-u4x;}jccfC7 zw!;QvvPJgQLcq->wib@fT><>Ii%N$)*zfRonk{T`KGgfJXHwRHN%4t~$CCEa%VorK zPGQo-RMm5{rF;e6NF6#)8!>8|{%wqvU7U+V=GN+|@c^4hb`O(TUoZJ%udB;h-0G?> zmR$2E@tOiv>k{JKE1}luxH>Sz~C~J6#|0Ms6!PuSi9>BW_iKldvpR; zo+~%U71U$K`*J>S+mos9-i9VRo9K}uV&S!r z1N>Uu(pVuvGU3UJvL4f(_@(vT*+vJbE)U_C*N_87B+9i-^IxExT!(5sPu!}ks>a8k z35m6ER%E${jjW8l1r;H`?|EbOvq8H1$n26tiuX(QTN(FYT`!i@jJ*rW6D8AFiLS@} zhs@7x!V#FwCrF>(SlI82s{Nra`GU{6LE?(~kJ`bO4RH`*}g06$S*8-FZMmjk{OQj1^1iw|i2O=oseqw3l0m6Lg!~ zT3~JM$|6pv;@1}b> z79A4}c^f}5@Rt#}=AHaSK^dr35yejU#_BpEv~-_5a2L9J z^yd3+S}&;&Gx62Gx5|*?{YAjzW%KRy-{HTrt}inm0ps|+@3=av?U}B7?p^|?n0^A9 z9{akSLuA7R`hpNVmwh9!gL?n`g`N^Q+EOgPn&TV{zO3$wVUxsp(#2S`u|g2U(n~Ad zXle<(-Yp*Hz5MtWb}KXNS(RQaWy!=hG^)EXF}9tP1H8l7YsZy9NfbNf@VSZRMEwc7 zQVQc9o$MrKtw)lF-7hT&z5n%Hd%9<&q|~IOAPXYDL~8Zpg~JUb*N)|0@%RI%pZb!J zIOR{{G!GW#)V4Ehc=;Vu6j_g@7z-vpRD-`Gn}Qz5_h`hU$)v%(w*cJ$?Iu;Ski;3} z?64q^dh|lCl4&5{B9Ap3OQRqakc;~9fE`Fg;JD-H_uQ*lc0#m}7e6MKkL!L?BY?fd9cpPXqdfS^+H`zO@!{#oQCly@7 z-;d5=Lx7%GG{FyUv*HwXc$aj@m04oMKIzYRr;sUl7$o+k($GLr+cCHnsL=**`)%v> z(tvWXO?DqHw2CHQh-V&00u@oF1j1G3q3p4V9 zOwI0LZn7~poxcy|W$ccL1d=Uu3tNJR;mD(YFRFfr&k;eB!351ZZzNlyZF^Q!48Ju%5GI&h8Y`2T3__&Y<|ZH_K^hZsS>v6wE)I* zldD&it%_$N7HvtCiV_|vHKiT1OQcw{%xC)vTogh%kk6QrX^~`2IM5nQQ^k4 zB;WPBf>Ka=e`3e&Ugt{yPaFNU#pipwqG%ANb}#X z;?F?~hTAB5NR9JcCkHWD46H29ALOW(Ca04p5Q0bveb?*KW3~u=PHTG@CK8U<*pfHQ z4dN5!W%;*=822xTBYlGwdJQ>kUl0c72Co7Hkv_9i>WFn6LPNX6ntP_UJBWiydr?#DQvp- ztYxsZxWDbcPetLyn}?lluL>oqOn}lxsDi-Hyo&ZkV~D3u-(#_q zw4_plxpXN0>dDQVlmUVL={8$1#J`g`Yz(5fpmUxr7dmtUGU==iB}hRr?&s)t zEnj8~2y!7GCn5?el6ARm(@dqY7S!}x*c)#Aq*b;PbK5rUZlOt4a)QH*c}RKlU!`Q} z4Gj185G_4zR*I4H=(D6OTZ@Lv^T~r!75}9`C5UHLdr4Ey(*l%rCDVcjP*5d*3M8GY zvdiWs#^|j(>JWRqC@lXge%MrG-IT=l<`QpWOK_285Pi!NYNCz_(!JtKEtW1!HFrUeCq2)8F;E%~z7ir^F`JYJ>f<{vZVSqdKtZ{7fn%AAaoU3s{A%cLl~$$fa;)}x z*yv)(GJD4H2&K<6DfNe_wIOrt=XQW>QdLS-fcgeuiT^qT$Bqye#Oa1BLHs$uIL!Tu zsa$m#>u&lryoz)xp+w&2b&p(^kL!WDHh}FchoJ!n zfO88F)?tJ!f7d07WUOM81;XqVWyyRoMwbcwLqhy?{w?d)I?qW0C+N74GkNWB6$2KV+$eps|(u^w=)N7eO0UOj4U%9=%DgQ7)-t|tbKtKJf#EEjN1n#*eC2~i^e zmB2iwQ^r&i_J4>gum;#V{mvFSY8u%BLzmG?#(^MKa*>z<==e--eWPMWwCMbbps zuW0vi)_tx4^x%6%m2h|jEB134z;j5u1D&o1~!!Q7GYC+1NrD12BGnNbn!X2nT6zsa?Vlb zb6q+vTuAi;OZ!jY`B^o?TWBX1k(rttPep>ozrEf42ksa{KDStphf?|d-jS3R0H5jn zhc#zdZ&VU-s=?$GHceAI3N z{#yB#)Jxfz#H2Njok)K%DG|8l{D+xGwV_yjDyuV6TQ_1zB2rEx{|Ap7`%{^$xHA%= z3XT^ZnLr-McXguZXuN)xGHLaXvUsaQXado8-ckVDL6oakaWV&yg&9zIxfq-H>04}~ zK%3V2l3eIo7|}cMk{ya7w%u;+sHnxWeF9*hB>@aHORI`@I2uiY$!97=tr5P*_P>QO zUA!fNMVydbVwk46YnJPzwEUamvL_D*ux!$cLILv`fz+D+njFIj04#hsj|rGUmBXBZ z%J@C>Uj^+JsnlL||Kx?DRAq=f!6(i-kN|1_5&pN(xUVK#`=jRi>)^~{|M82Zqy$+U za4(HqFo16Txb}I9<&uFhHObP$B7XWS$1)(daYiAJ-nHan52Hoa;Iv<~wCt3!rf0_e zxs4jXsCYs1;@aU*qAqCw*fg8iahhmAZ32voFY1br2OKf3mR7z1rkCb-O`OEw)J-Zi z=Qm4c^KM?W+EQt2YuCC37i4UhlBJjs`q-=IEr6Z>{Ei^NZthy+!7!Eutle*e@p4d) zTe5Rd8!_t_WINK01~o<5zKovcKB;^@DD)a;;=aKLWs{mS1oPR^7vkaqs8>4#`8_UbqLTpmVP@GJIh4=KHSemr2u~4(|j!ITJ#O zgtitplX9BPZ2kbV2AKSP{*}8XSABwks9lf9VBtRe1V0v4o9L=TD&&M z^_wnelNj8)DWv9oM#x$v^y36e%bMJ3hcDCUcyI8ypTt5yyr=?y??5{`Jzr ztkYL6$E*(-Fe`gS$afW7q(zR{EzyF)XwF5PfYOBYXB>4%@!`aJjLBf~OCE~qmYT`f zj}8y9_`pz)5RxhKTjpQrs?c3&uo?KKJ$AqF46W_IOsaV8E6X9M#iRQ1V__K&H-uop z8Ae9bT-Xdi(WZM7Lq>kfWF5vlW})_AAUh*p8xiU^?YIWmSdP}R5VH-Tsh?m*4ngKM zjifAC_vaQz6W@k0+(F9Z=eN9oB*arAoP{c}z!HL4fQfMLUz^|VMF1UFqheHwxB;zx zi=2XVR6|E-v%k3KEvqjcwTi*2BML9aSQJiL0(waMm;Mdd_(|G#mgK$I<2w6_UJh@{ zd2PH1NpPTh6@uVcFi&srMOE{#SAL5^t zD~2~Qi!5<~lctV;{HR9skKx`Ga7T!2a%Ahvq+mzMqw<~|oUsJBna5NE-I?-xa=fajZAQ>F<(`JDE%kZ&3QMI*{tw$fiL zw|(vA%+f)ZSH0h(ZmET#tNEo`_Yu~p)x%|SdAnpDZisC^Q6t3-82E84?m9lH$f@IJOjTJSOO8T zLSx5?0JZs6*2pwq#E8z>Rwg&^EN%}?i*ZH1Dce&!i#+z8(Ig_u&{$B=7B?X#pUkJt z7aRHHzI&w@5$ATA+()B#+_Q~UIhXeQo8JN{pZ2SccrJVa4jN4YB1I&_rM2JqPUqC& z59m8CKqD$2Z9dr~_->Z#HC35vJH2qD8I$Z91H3(WqGB(-%ubbp!sEB$4}nlu%eV?V zTN71XAG;^Ux{z2LPmmb1AeFH27z|J~R9cV?eEy1dy=e9kW0Vx6(YlDp^I*KM=e*`Y zRCED1E$AWsJ-iA<0Y>z zGk3Cc%N^x~^^t{fYwzMc?so)#^=!y*whY(2W>d3H$#_gl{zg~W=rI))s~ar<@L27C zziGxCb!`{g0g(vuusMx(tGKASYD9B!Fa!@b977|vJAF&Z^uaE}Z<#lX2&*B{v1#M& zOZiECoPx_IX1Oe#8~ve&IN`ue185*AC%nc^XIee`sA*gs-o$ABz6%)b5IMrUS>7$; zvi>=H%&97pq0;V_zZKo5Xs}%?jVZswG`DmblVLMz?8^-^uzo*BF0Ct^v8TENWWT^7 zSL^FvX1WGDxD%Smo* zHE9R_+*8+8l3Q>!_cWH#fX@4CTfm)uMRJd}Hq0edXY=!>m(KU!=rL^*Aki@}NrgSz5+`mVhhgPLI|2<^pxk1C1@h4&%TYks}o6EF`<@ z&uySrZzoQ#sjYw)6Ko2KO&g@q*p}E#0-$YNS|VekqHnMJY;`W+9f3LifJoq^^Q*QJ z#FPsqk4i!QpPJ#=(@)nxDhn_qR!F4mM-+@*W!>G?-N(nZk`NS>t5BXaRf2HQ{18ryI0OLF!}H($>0T z;>5$Lf5)S`9i|D2JQJ)#sY0(Q*-k$-eW)ra&M9q(4K?lqD&^|&&4iD=vQbLmIv7kK zRwLocWY)Fm@?jUwI(n`7gEM~vH_sx+_gQz4MEiUx)m2VTEeU~5#IxDvZ{jT(@T9ANEp zn;F@6y#+wjEP(HFt-tQm+ux0@_>XElEc)V06;&;C#QG!4>F-qFp#@5KDlC*-L%7TSSn|r4dRnsZZ0%r-tu|>*}d&hQ{i6(W*oK z>e_&hrmvQ8O1@OJ0Tu|?{wd&&v4n5tP|JAL!1{-&c5f{vk&@Z`RJ=aZyYSd;g3yET zsjII4mkm%8LnGzt*Nvxa8$W>-FCN3I->;JP;PHUVUDYrQQy#3#HkM?OX) zoTm=wcPLB|FXqN4=BDOH^mL7x8o7;J5Mf~*7|dT?=Ra)$y63x2Cp(i(C#>nfYLox4 zj*1cGfF|)33d$>DKNN$oaoDJA^)a9mg@pqg%y)KGO{r+u3?kEEheJbWfe2BO-#j~~ z?_8*k!SLC*jE&hzjlJG=x;X{UIz_e!^$9$+i~rkME+KDB`VG{?uF5mUI8>_+q#FE8 zX-N}(&04$9m3}bH?CE_Z^{7l_%G@H0>E z7(cnI4q+J^+JH!g0^kxsxV_QA-hvkDoeYDpjiV_g@=JX_sOt!G>-_a$(1k+Fccvh` z0B`4=h$={2nDL7UY0XgHXw=(R1a&NwC&BT}XhJMLv0{45ZdZ%#9P4wE^wmwS0Zq{M z=U)j#r9kFG{ks%}qWP#Pu4qni?Pb+P9VSmLQ=}oIM2*!T@^+E%L^R}bD`hCXs6w%~ zPZiF$UcbqX<0F4AaW?mj=G6Ms=akrQk~NVEE7aGh9~ok-_B~q*5y&43_<-}_QWU!i zm)J_a)Yt}~yLm8!VeNq2BwIe=UpGo_fkc&sBoCICC=|N}O!*OEbodw5y~h2UA5v@; z>nmX(mkM_M)(SXMesFb@tYqcyD~^Py;{R4$j+&M1@}BVjW{~`Yb$h1LVMm^V#kv8UZLsFNBy@o-8eM<%U z6I==m1#;|1_ zMMfzr_4V55U-ycv5r?@?TF7VTh8TxtY%kxY#&sCX6D{|(L%jw*IUuvlGE@;)`pt{q z!VGx5{)#$<6d)M{B8$Bp(;w2N##Cttpj1-6gTB90=g?}94=lp~vkycWNCFSf7CztkZ_xK>hVNrimz2E* z&|ni9VoY%>yCp^_uosP{brFg?__Z!^vo(40#BwFZb0-6@2Op_FDa$PsoR}>#$HpDE zE}8vk71!bSL{=Dtdg{Cfs(t*R90pVJ3_N$gAEwMj;gR~@m;)@3V+`vfNOWC;obG0s zPpPNC=NKftmefzy)C>AM%d3_R%gB3Mlws z{l*?x^PlFCD5givA6}CvhDKstt4H+vUq}S2dg?c$_pxNyP{E@}h^mL9+uu9ZYUf+u?ayklXdPHJc$F{cS!le7r&R(-{Zp$gbKfuX4U02APDpzHn ze5oh74ao+O{(G3tk&}eoD`}iCuD?kw3n;hs#=3a*5o%Osjam=|we;-#$PX`ZdH-Ff6J!l z0#D^pX0oY%8ZId;wOzwC3~=n_fNX-^BAoq)bb_jZ-pAXHdqG|> zpCc7fwyw$>XPEYA{MN$kPTH4FCBo~bs}7;(^tP(TRmbJ_juZFzwj)>5Z6_6}N7A8Q zut(VdpX{CwuqDv7Rm$YVgvFA|#;robdG|MRw8ZHW4tinAX&%Z|zB!y^6`xD>N-w#4^Iqy^n&Z685xdR_M*cW**JZqy2AO`xFi_hUwOPH5aXm`?1o^{@yg@~HooiD^SFe+M)IHIJ=j4vli<%g zK-KMw@_Pm4q0fsYrBzLJ*+s|gieDE*(`IV6%9`aoC-d@~l+?d7-42{FNJ8Yi7EK}N zkcpt2EXj80o1XAz@I!ti_J=KP2G9xDG@V)KaxO4}`{X8}l}(BsNJ0=ezvaU`HxF}U zKC#WdESfl7Jf^{Bb-bc^q4`u|OQ^3))P7`a6Ubgjcls4M)C%lmU=fri5Y2@#pXCBo zr)J3aGYg@Y2zqz@Qcv=iy(^6z*_t)99`qmZVSU@UhY;z_SX`4HDEWA-ct+D@Yn*o1 zvS~u@f1@*bz^1CA2Fv7Z(|}|J<(06L#+aDnABAG;NXJ*95`TOsu`13F-4Bk-^{b+? zpL&S8Ar3IIJ3?_YaKZOC?H|Z|WqC4JxlTTWy)V zCwXFZmGVG7bk}_E@=pC$ZO3(1os`E)WG=$!?AbZmr-Y(9n#N)?4O#}8^41lOk)9wF zCH+rR@+Y5{5=n?!rMsAO7|)nxxL~{ci9WAfUwLj^nkLjuu%>&OBL0gD#xQ+K4g%Lz z7%JD=HuW4CX57#i$TxX|_EDYlzOEdm*MPM&N2V#3gR<{`lPM^_C(Ex;2sxw?+ZJo5 z!;%SvRV@-&wlaFAFk^`AXwwYVAo~L`gV36T3VFos2+`?eL`8L=FN`RUnCYyzw8+l- zqoLOnfmBQqo?GvtBZH*RPuZUg-Vb?)^^QU3Bh8eb8hOUp67M`@@jmySaDx7DDy-PL zlhCVJIK5aTL*rXnB~vnM`ZIf?W$Ep%lhgBO!hioSsK;*$O{Ig8fuh*F#G?T^GT(!7 z;O%ue^>6k(Cs#-Ff1(jz-8f2kSm%~-wIJ)7t`VE@L%9!|2|52XhjIvn?#k%iNs)of zLU`PJgOIP8&f+(TZA!MKV23xM2J$z+EWH0uB)w$}FE27e(V~OWkltWnjDM$wl!Ajc za?YgXqL-KQtJ@@#Gz{-AaS>`>bgv!fZ#+{02yUU(_zhHKm>9j04{;8sh2q~8Lso1QX)+zZ4L{*<#R%PUX!a8x6Q}AXhrp9T(n)VGw8m_R0Ws|OeI*reb}2cL;eJHcXC$cYH7~xpEY4lnc;9_$9{Rp+Ui&w%*voG(;i%Hj zV1{g4NUC^VS(RVkv~EDP=TDCSrv+SeV#yw{(WfxwFZcduemn$;SI`b!?_?g&N$0cU zK_z~@s`ZuUyMD{jjz`9oXX`H=CH(&Z(~Cgt1U4q`HAA@HKxT%ZYiaqVU+RIMR?ch3 zMY${sr@nXHBH7t;E{SNj_amHV!lGuk@p1;MF^zurTYZ>^ zXoz08?kLmuG!_ugxc7TZ`K#Swb6ul=g*K_F{GsE)f1ZBWk>ssGNskC4qf)Vz*ZlD& zm6^UnC)HL0uFBsxH#I(ve<%Z)$kuOYA+4EVqDkUU1*WTq{4T{yl8+}SW00>r_UDGiDS-(?o zeQ~|>{f|kjZxJh~&xpXfc%LqIV#bPSAa8BcYLiSQD1MLsZL=)=xV&qFLYWO*fe4y4 z#s5W*PrY-}quY{WB;U5T^}Sr=>nxW|p{cntKcE2nTLo-k#oEm>(4WD4o*ZNJooSd8 zwu#G$UDhC6pEm!sv?uwJHxvvm+x=Vh_C8zag(E)Px|>tvy#MvJ`?`!WO4{fF@6%#| zwZT+@e@E+WS0^*;s+q5%(@Gx1s{^+%O!)kux31M*T*NB$rDw0lR&B5KDV^tf*hq!- zjpxjtX915Jwc!>rL=yHRK&QiPdI9BoH&`WDHOo{YZjnxVMAx>&VzvC;Z^1sn!LI#X zJ3KkVX*a1}>S)AgVfVKSv9r^V=ibfwZ=#8e)0ljnWipv^QTs1;eT{a1jQmfSnL%dd zpp-4=*SwL1N;wa)e9Y+b?tiiSHV33mpG66F0q8~x$Mi?tAVYoy%7~M=bLiy z{ItgnZ^@9@iCUi_zH1*-qccGVOwF%qvm80(TE zUej-Vn$?Qg0WVDZPG3uh)2J4k*F|aQ zt;ks}YNnsfv&c-U>*}ic--JqhvJb(5@Szg?kDD!ZZl!X*FJnD+O7iR-aiY4m}GTqL$N zi!c2}*b(1@tcvAg@n>EqNvEx6^3gpX0(LI3S2rJhELLhuk{Ot66tT%f&Jk?p)v(ND1$A0{shjkg_Dl0La5o_|k2yeCg)b#IZU88JyZ9p7zU z_5?PC9+{k<{dSw?%-HOFHg=qIAJpj4m=7T!Nu(4RV9k^YxwQFkhfa>j;|L3M(9N7J z3V_=V&tq0J2KXA*zk}yl{!II#$0@J2^78Q=^t~0yCY0=)-=J{knU{+h*SI*Dugp|* zLTg~C9#AODHpMS4C%w5A^b>QY5~6x&zl%DDM|9~dR9pvszuC(i z@}HCxCZ?UeT3O#~tvb$DereLUIq(GWjM+eOEP252vD2?i0mG+=51`Zr>;D$X1>)ji zyvng>XA5@3g=Uv8E!ZCRqa&i~N@r-YCxantDf}~*#cJ$QD-mcH()3@^pzVc z*himcCY@Hv&>wlJ)Go>JQPh#s5W>P!5$90zDu{gT`1s`HgPPU zH2!;hZggfs)wDk9*X=pT#koBPtl(DznT$}I_l{PTje%Ord~pXZj8l%yZ=)(_Mm;0kbZMzqKr&$40NR8Q>iLB;;g=6%P)YbkDC4)o2&Vpf<*Z%JDOB2tY zp7pD(v6UF39JfErrb4mbcvI`W+M%4Fhm9!>h4qn4^UU-2oS{(5yzlv}&#@KUB5D*K z(Q?M@2!wlymPJjzt)6w#TN4NR%;AQI!}A}eL<->y%0WPo{;%GGKxIr(X{T9ih#XDt zR9M3H(|_vF6RSTlgqT;h;|&_@?J&(fEE_PdsjaQ`tWd#|>zn;BB1ol4T0`gy-xDqQG%+(Nv{mad_5E` znZ7&iw7ye6?=sr7RgPLkRVQaTjb&4Gk|p-!$(Hrx@ahovwFohKp}phbaz@f$Uw{$T z+~V+XcnZ)Z8&L>9j;iS$UY<>eo3}_nqx)_pbkEbTtC3z|t{L@VFKy`poc`?;V{ z%J)*#>&;JD%_)+8Vr@4p^T zObj4AvKO`cE7CKDPbdETd2DO9wYdp2$L#Tg}Vs?!Ny*SII5$1!9@ob%LW=HW3l6W1~$(plLG9 zV`sayNy}khp$@V1W8?tVWOTcxFsw`6>r{uv-_yW^@X?n^n{YWLSL^!%j5?gv`VV^R z-ZVw4>+5UFic?!w{1MLYNa?h!@b(7rb(krc`x4;xLBHrTZa1_i^0UTu^PZ`iohvBlTp4%7p>kwL=3cmw35&)Cag&{5;uJqmTmC)tvk;?R&H@CXl@ z3z!pMVj88z%hOUV_V_8_q=EMg4zm@3_9Q+oZZ%LceqvFpHHPswk+CU`)%5`vRBTI4 zncz@CQ6xVfiuv|JSm~pE4xZgQUc%EK^l~?V_2>^z8ktgtoXfD-*T2x!pn%ulo&C4D zH9akCpx4R$_^|TF(jfNow-prcx!J|U#JIS~24u&31T;#|ygN6_K4Ei22Ixn09`$wQ zER$Iq1-rbOjQs;%G)-W+y;iz$ftwp;^aJ@9Tyk5B@}oCP7!(#dgxvy(ct@z)sKu%>-f;6QXA;u03j_%rQYmSqY*hT6H8V0dvXB@~XL+O?;gu9{TB@H@Ram`2 zo7oRPYphG8ef4N7rbiwG(daFxk0{o1;+f{=QrQ`K= zp?pUgBH@a9Y#fBby?Qufz>V?C?PPl3or=&&7Z&|W4Bogdr6HutqMhOp%Z2H85mW8P zKjaV7Fdzd}O*xQz0mlCi%ms`tg!_ue(^8ePg6X&JNbE|gXNTW41#Hk~-g_P^1Ado2TgZ+H+Jq}8LtBk-J@wk+V=r_$DcS)b~+aa!TH_cTd1 z6IZ}c@J~+jR=hRb4(@{vY}nW#Al$$MUam;%JYae3uHQg_!MJQgYr0r+a?DqjmTX;lI(Zy?_YFFqd&#U8K-R#cZUn)Fgk4(mVI_-Fo&3(Um@ z95Z%y;V&KnZW#ka>?(DLDvqdei9>BuB6|k`tSoIexVQveM#g$uFmk#*5lUbPO(+`-{=_0Ile9;y(Ai2I+>_Xo(UaFx zhWnd>q(1NXCy#+vIJaJ5A7hT~0)BnnT>7F+pp-HN03XrFl$Mw_w!S};O`{-;?G2bz zPM`pPYmy_pwK5Zk3?)ZUE0!PxBb3y6k5+_{H33u+IJX+?M#~oLPgs+l%}4d?=pi0X zNTmcbZ{dT8q@)On6#_N8qP}+M>mbQV=tZWlCy83}A`d$+jlKLdSxXNNh`{-`w3i9Z z2j!2sSle_$F9CK<&%T#}Zhb_rVfv;?zY8vwRjB~$T94wDmvfIM1Kh=n&nP;_#*U+; z%=Dj#iNwVH+_DdW%3LNNo-h^6a0CXf=6>6eq9c>!8SNrs=Zp$9l^|ZBFsI?RYIgh7 zU-*kdD0v@Q?O0rz*X)xRRQ1-%+2-JvyFky**OYp_b|6-5;Q4uZ2^A&HPydmJKp+XS zC{$uK7wG&<9U`oz)1~33d^Z61_Y!GT!sp=Mc-oZZ^3QHdQKp12(fM*rsKjK zvf2KnE@y`3ZSbQKPkrqmhVwQl4njf-ThkZnrZ#G7s@$7J-Fh5&G%H_tc=JKIi8FEy zgbNB~_&+`$F}eeuK1^H7>Y>k8l?2l<1xdrxo9_{-wqY0 zaWAK}-Q?Qd>n-=|bL*fYMKOB~&$i;tTSI>Xyy$}_YLZCQHhw&+#?XE*J@0L$Lh7FQ z(pJ|2TT4`yLr@@$Z92yT6Pe1GglIcQl%?cI6bMX*SsK1~B68Nl3^3KB|1KuXpj8dGbIuf38^dgOdDWLr2v?!bG zf^Q`bT!s3eqi?MCc-nIt>O`&?oBHHlwjSJn@8rq*G;CR8Hd6fl>9Sf)%~Ou226<|4 zrpgwok`yE z1oKRD(~~l7bigO$FM0cx$T~v$pznA!y&|e$I-+}ADE?}rd##g{9a~m{kw1yB?%~@m z98=jcFhqURp=hs`*VNY*&HXCI#*Y1v85ut7&hScu2N|33#fg|623mgw4`%0QeGd|_ zALccLn9B+gjB*@5MC71ig_xyeUE#k%Cto>=cix2ec;~ZOtJ^x;ABZr)U7C7DL63|Y zQ{!gzKU3-pke36mw7vZB6ta{ETy-v2GdaKB3H z+cxNQLTzvZHrV^FP{SjcBEXi=boRlM+;Fna`=JN(nTsEO@G+0R(UWsZE~@lY%i}kz zgxC=B1{%;AYGe#+bNT9e8I3R(exh_n8RfkKKPQJc_NQ#gA{Bg2ELc;qfh>rJ;QFaM zibuXrKDYD)^|cmsqEEJsgAmXjj#gH(K{ZB3Ixo)R#1JQ1J|CMRJ{Bq&DpRV1u3`PI z@6jK>Iz{!mJx(UgfZA@;drOX=eCuZR*?9iGBVj}^k{3S9k3(7OVz;ugGNI$N*hfK0 zDT}D8%_GwGYU#c2HPtsHJGC7J-!}N{^K+(CUf5at5N%bE z(%O5Cyoszo=m~u?pw5~CH;N9Y`my=71$Ii@L**bt^noI!@t}UVK>Mz~m?O18O>kC; z_@h}DLF4OSZKA7}6)N9VGB@!KrC*}9q9AUeRrtu#dLw4k_BL@TfijA0K)+ZnkC4TEDAA12{JsTY z;(e>J%eM&-jN_>K2WJaDOLY@CMM2~HV~Qw4Vv1)ozXm^dI~9;(mYH967k8rc%|WsT zM0C^n@h`;$RAA{Rw0sSk+9uSHG>PL^G-6!k@3vgTc6V~Y{nc8ByUsFA!k|LZC30p` zbhFY-APb&HDXGPGixiVG|0~6rDT)8tY{EW&G`(2D2@UX^T^t;ZDRvfDB&nfz$Zj{o-%IJVd5kfcXqoE68+PL&CaUGQZcwB3`;va}w$#ziGXNy0=jKn_xUq+sTS-^aL&m#f8=i6^3c|hM%J9zAv(|Y86o5& z=AGmDy5(~9*3Uo3gJErme)EFe2O`+MGYIZJZXxf>>-EE!ge;?LbN^9BNQ{90<-ad{ zxu;UTf+JI_%YHNKOTW_b!86sKPa$U8YLRJ`Cp3-khvZ)){z^LD{gC|l2mUbIUqjyY zl=LWt7|K);Wbi$By#jhThy3yj>Qv9m=;g13lnk6o>_`ZG3pazTjif$|95Q>4vTLOS z(}r>oq#7nQW-zJRJ8)x^l1h1^d{xd|4Ys;d{atEeG#05b`e793ByDMWfa<6vN(p-N zYKqP;>{;|nNwXUjQ3^^Gq@3H}cgj>4L5uL*I8VV@-B5SBFgc{o3UDe zRNVDZbIOu0LSHa#4N|e=>oxS!?2_>mHF7FlMjv_Tw)!yFu-c}YDSA&29uab|zB9^i zTQkGtA@IQ;A3{`h{9V`EbL0g)8K*NAyu>gYBYmUwv$zejzo-KO?mpQKT>9|`N+5N! z>&DNDGlRT$ke!#7I{aZ5C&r0|-z;`&qZYjG$!e zT)aWo==>=mwd%`y!tn-#esFj# zzcz7MG-a_zT05H5_{EgnX@1~gONUuh^EKw0y{Llzj@`G_9Hd&*G9+r{7#5X+k?lH| zNaQ99AmeKFeT(+1Bz?k`*vL6&QavX*rtP|ECG-5l*yEP*cyN@YQ!Q!}p^90&jN#Q*k}QKZ!7 zR@h*!g*Bot?IFT9==(;gca-k(WhebyT1b^g$52;y^Y`9(opGV&D@?t13g3fCW2Wwe zNqvh@U;o{=Zzr^B;WPG3w91Cj=W_()?u!MID>W;IJf+iY@Fw@mLP~hRek#E}^eOQh zPZ1K&u)lgyQ?*yrUa$TTnG$wzJIMNLBIY2!PY>!;duav!J9hjip5Y(&=n_9zhcAww zgsfH(A}QbSXjTjGB{)O`V8_3h^uzRtX^Ta{cC?`r6EN#?Z2|Jd?=DJDML~D{{Zw<6 zKHARDL@Z)r{JH9J7l+E1XhP0^$?QkUHDIZ-?gt3PM|yKz6aMHi-8hc9`|;>^WU2-y zGbrC>;p7p_{C4+v9&~L%!_N`z>aEJ3b=Q2onNUg(El;zop{?Rb_i3n?exb0|QWAOS zQZ2`)KaXq*?)Vy}Xg3ne>=%P35}P)26#krB|B}3qymK@YOZ_O;d$}4?Yld3oe->~b ziGdAIP@IUi|ESW)w7u58vyk+9Va4JtDpWeKxVq5Q#t!h*P@m+k#UwP+cA_DWA$k9W z{>oLc8_n!q*GK`Fz_DhJ8dnhU3Fjij+>%0t}rA%$h>d4kjLT9IxFiIpK53WN-rA}LtdXq;tsys?}xuq zx8QA$Q2p-o|9JYUur}MKTio5XNP*%(THM_!P@q`w;%>p+Degsz6^G&$+zJ$TiUfBE zE=6|we*d*|lA~P7GnsqtHEY()eDftUvD3a53fXS17;ToYszV-7+HjoU`uCqM;{hM} zwue4kCB2ZZo36G-oV~J%x<8@5$!q#@-v075(x%#vAv`17eVKXD^x^Eomlx07!+tk$ zY=8wYHBwc}^yr*FPrSR!T;a_t$@X8;5~t{s_8cYZoEL91V*YC>>yC>`r{4bap@iDf z!NlqLXN$Wus}bA@hurJDcaNRQxfk0VGSj$`jnv`+GTtb?+1tZ+oIwH3l|m1tytv6) zZ-!o*z{*4`A?9CQ2!&*o=2(|}`^uC6Q8@Z{GOnO<)lsM4Xkfqcer1SuSe_R@R&91M zrKs_~<=?G*@@e?e4S%$s9}}YY?s_Zk-Tg390MGA>=)6Zbtl@iN$Z&BJT*X7mPb+0fud z0it=a8Cl+4+UGMov$pCAT|gz}bMt1_tw|~ymA3rK4Z=4f)J4j0isjO)$45sK_zYjQ zw8I;vIkJfBWPCNUWDGBcO-I1qP9Q4&daA@D7ORMgGH7<1W`a~(d+F=rt-XQ(-BAKt zA6NQ%d#fTYeFDzdB-y&W*P-FdCRBmJGd;erjP1lo=Z22=^VVB#0?X{A5sO}0YNdIv zFIOc7P86Gm{EX6os{cdk*qDxtBY3FmK6|HF| z;RB|qW8rLba=U4oXON4{=d;!Mz_Fl#w{|2PhIPkz_?)-yEiJW8xCFx;bz!={_Q`+N zg~@iaDNhtLanbW=)~0FimK-Ffe29f1Va1`%@HHcjkg*n7?sgFo5qWyhyj)K-l@}t< zR`<7=Qtz9>$IM+cV1eP3p!#XvY}c~aJH$BBN0b6mDH3`RG4%G9GeM*_H`FJ3fnBF` zR#hHa!OWt1^-zqlX8FgZ^Is6RlmtMNG=gl7E3d%y>&#&5v^dI;#YZzo_m^8{q*@^~0qnbw&8aCK_%Ojb zG+=Dsjv!L|_rk9c0fD4glxzioVg50B@feTy_e2JWKiTCnm3sE@f5R;qDh`+K{|t80 z=i8Nk3~=kwYl!#ZV?zNQDJ=kcCWV>L#g5II-BNL%*wZc0JlCwITGfMj`ZQEysq z+;JM7r^o&rbJOS155Nz8Tt{u4o=oF=eN^+qLN^!1x4;9N*vYOG94`Ik%dsi4f050E z=6}QRwbRrT@ahPI%;P)s`@3#kaS&$_$pvtFK$do#TBnlS_udO4Op@-J8VRNaaQ8JU zsZIDhYBdWktfOXdN{(81T|YztV8f&#ANTL}?;<*WcCTMCJ*|;-)=dO}gcfp7jIS|@ zTs*4E%siYxzFp~6R!5|J z5sc0io3)mdQSO$}?pKf2b=+sX=uK@TM7z1S0<+rL#`@+9?Uv&gM6+3N1{F%YE$H20 z@Rd63zedpiLk7Zq$AQ{j(vMU$GY;B5n8g#=5^SOoi$63Cy~-Ef1kHaoG*Mv{$t^Qy zMXY|2wB}BsENjhmKrpB~hs7hlRoi&7=`dw#84fE=)Ctwsh}c}NT9F~KaKdUsy)sq) zJ-VFp@6&T@sFTATMf{%KI2$!e5w+%1G!?g$#VS{FXeT^{fh?T&bB8=sb`k4jZNK5{ zFcQ0G4!A_#Y<^+dPVu--hR7NLtlB7lwWIsP(j?enijG5#&{b4#PmGs@R-B*HL_wOm zx%*SQ_Vtizt;Ju1T+}z+NyR?-LR4E9XrB2Wd4pl-J(%2SN@Ns+GHHXFh_wA5g@Ge(ST~!z@As%?_rz)I0&b~Xn_PwjAde8cb{4{?A(fQ-{TqW9=jcC6*t@hTC@xw{Vr zoa-8l$L_|yxL&baw%{*lK%<|Dn!Cn5KP3Po6mt)VaryT)okOCb8+L8&B=;yap0y9c zhUd|KjnRr2hcqqU5z?{~#O;I3IiSD~0&wW&BGb6xiy@|ZsfQvK9qe-jT0P1-tzMd& zY1(p-U3JLrmLfzJXLy6=;i8*gCiBplN)v`v@Q5^jPP8IUenk^MM@B}OU;Yxrro5uf z`st_NH-MAG(df*t!O>VqQ)jan1A{>E8h+8mQ^>+D25~L_kp!zD86I)S31Lva-FJR^ zFn)kyDb`xE&V0@9{~ad-*m2rzkzBE-)}Li_j0~%LBcQqTDwgB0&#sz!;(_RRBZ)K9 z`suzJC1cIT2dPcN99ygz443sfLeC(_4@f6!!wmw($%Z^Q$E*)4DplBn9>DK zv_3JN*dpgTwlhYDRvIkdABy!TV*ms{{LQ_Ev-gIln?I^MP;ckH>Y}MIn-6l_X3IZ%$keW++TKd zyBNqta_#8JPK*bA+fbv_5M?OtiHem2`9c2xbWg#iK?V&<;jmYP8K{#c#GgyFm-Gjz zOK0OrPC3-)4`!CFp3O2LaJ@lmGo!xEP58PsIK1s*CKlq+%I(xNZ;LPU21oMEH0XM0 zbXC;WGpt?pAY<+f*V%Ju`kMoB$ufh{)}|}z%q7vBEYgBrUFKU+P5)-%Zj5HRj>eCAY`O?bLdRq&kOXftr_??4yR%l zC=x3Lx4m?h;%%lDx>*;!@j&FC=EOV)LK1Q-;QMT>#7?C8(xWHkUgiMXVjxU@Le|q8-H=&Wuf0U2s+(%{L|L z@8jOhLMRzxm#}3Hj=l&yZBpd80MsH>{wo}FJzTM9sftlqppz;(vA% z7d&15s%22h7l+QM<2_Lx*0>W&o%D@Zr+9k?agk6fZ-7RBzCi_vC#+V-ZU2@tScEGE zAO&#!Qvro#mxSr6o7%3t#S=5joh_7(Hy(6aC#dU@Zq(>4Q8{Y0cFtaSu8CaW3LhT3 z3x2~RrsLg@bmA@La!Xp@w7*!te?IHmb+i^XHUBx+8^O}qHrHFowaULj(OcP}R(fS~ z!_N=^lME$&C?kvs;D(SqEf+)ior`KSbWe?(8;pAzE$(4;g{!9K63M=o$q271W3dGF z3`}R(ASqc6Xj*S;MWSv?taHh`!?zyW7@-t;vNsMr%H!U8e=Mj2zs6RkH-a>V*%Eay zl9OYlR3E>76Y5gWU4lTMwuq%_hk7KlEXFsEML))K-d`69J=E!jbAatzqIpYiDx;pO zpi!FD5h{7yrY0R28c9p^^kjW~XkBDrzXw6B&62+3+!lKbKmyAMR7eKcdWuihK4+T9p%NZxTR`^L+M9N%QJOU}tzl=l`?Fg~|| z7$GgK^mn_(ceq9ZiZRTJ>VOCKF?k4)^QChEi~ZjWvqWPv zEsZJRbq*3L2{qH`r-cV5(w+zJ?;g=cA7@>;ORZFTa)^d6TgzBTNHU+}K0i+&ozMLu;dx z8Gn)$$-a|PBIGWWpHWmeIi_)XHA*l>1WZ3*EMQalt`)YUVHr`wY=O|#dOip#L8GiF zxpeZS)8b&`FzCaq-U{cJIEqXE3sEun%~`$R07dM{izU?vzOrdWH8Gb(ib&8hHNf5@ zdI$g!wW-RmNB-*M29!=4C-R8LEqGhAy&~Xktj&&Dv8D?_3*0XlHBqNOuYacw(RErS zqv#Mo^qj*n`{7h)zxMm6l~xdBhVb;{uep-rEK`3*3W3}$n)<4OR|@?{?-?@Bl~?@* zrt_4Ti2klvfBtYvk#pJ?j>oLGhD1EiK3zaa%$Eal3mRfLv<&Yq>Wj@11cUV^*b;$q z^j&D+K7kGuD%K=3{4dZ#xeXlu;_duwvJ&ojFh4%n z2ju&g9&~D_GN|?(S~u6bCGr9fAAE`reuQWc#H0P=5=cAzSOth1c!gX_!>_B6oydbvckx@3hGnLPPc$oC2q!lj+6Y4Ok*S6LvPV&( z%)v+jtu>S6i^3CE%WHZUX=304A3TtPwEEkLkA^5{8NMHGTMwWDNJ-7ut-ub)9oi^m z<||IS1j{d{Yr>+E{Zu^%*ic>+UcrE9DBooJO;2@fv<)dAhjH6gI((i}g|bdiIT}#& zOT{xc5}>MOT$F0d&-*a@L@^Szqua1?4>Tj*Z5ZxmH0`SSgvsRbGenuX>eQHyq4-8p z;nnTzcRaVLDKY69@^CY$@r;RXBy$)OHKe33#%_G)D1A4^j9b z%5VFVpBrCfW}drirG|`ov*k9k>!Z+HUyr}d$c#o}Oym1;`&bf#T}L$7!I||Mqnt4| z;W`f~tl-SL<#{6Ddq-w!+Utqy{{v$ru0(|I zA2)C7xUS5q$8PONJ@g*Ouc<0|JDW}x;>IfA4l??TXYn>OZEN2Nx{1|HlIHRtc8PoN zvpk$lJ3Xe3{`p%QHuugABeA8k1>MBh4gBcLLep08>DxTi^?N*=C23KHN30C&X$k#| zvVazOq-=IKX+~V}fVQKLv;<9fZoI*IXi0pE@8{pe%szq@E=Du1T|xW>w4`(2jjdaoLSYR z8axq9orp>_s!hberX;yx>Kqr!pK3d7W#Cen;QvKOS(mA7|05~e_V*uW70gVI-IcfW zg%N8($I@><$4ey@;t44=-~eTeW6Py%6`dLzwDZTcK`vTC9j)TiYoYuLzFH9vAH%r) zxHp-jm3j$&wj!f5fva%8bf*h6x{tF!`cqJ%OtrOzMxow7hRwEWakQP>y4DvD|mf zfh-Glx7KS^0C51X3X)SgE|tBE?oYvGTnilswT*BkN${g)8{etkH&&MwU;ro^?QsFq zSGF~zmgoap?7W`HO7R&J9pf$vpEn9kK05abX{k|ac5%v6127K=8odf%(zb~Uo^p^u zX*EW$0j-mpD(bfy6oRR#2hNraaJB4$Ut!h0%v}OjkzE3HA9|MJ9OEu2>_YO| zCyOg$Hj)P?f8)ZXWJI;BwEvj<+wcr(HUXWUSYIGh+91;j2GP-nsjt+$hbSVVjkfDg zY29nVjh)puzb^=;h%IpdT4s6*_Ieo9a?INi_-kFShfqQVpkDAR#ij)-f`u9?nH>rmxZq^zlQ#!=l=Au zwG`JEF)z{9WYim{)Wxc&Vrw>fCz=31Q zWciOpTG{-J8Kn=Y2*ki;6BzzfhT%`;s->R4;mW&zb36ZDp-TR~A*ctw|3cO`Rq}37 zH>3xpJmqe5(Q$gB8(=K_y5F+Bj*E#0vCPsU0hN4DZaWIz5{)sw6-{|Md209MzIK|< z#$LYCteVh+uU*wF2gf1-NXtI~Pp|50cDJC(2ZQgXxr`J^;Z3WOrOURstkPN*$9|_^ zFBQ;9V}HYy@6!KZf4@M=GhaVbFADxV7@uDhV5L+_L;<(pagp)w7gVz zoq|LBXst=H9FK=$YT&9~f)(>kdiar?H~+vLbAX;rPorXIg`8VgIGY>L!efFJ-MLBe z0h`i_G?-NA9L+e+ct(T*Tb>L)lS=Y;oQg$N#f*rhIdA`(H=~~%;nAN~Df>d|8 zz(tyGE`hZ#x*Eu|X7o~62F~!i@>RNGkpXf0WfhL9M99k|%Pu1xihk+VL=jZ2z+QgB zAn0qj)imeC9UQ>c_GUgd;IwVC1eyAn`k4l}gtoRbhRG)HZzP zvdTcKwfCQ>viZN$np#7eA4|mG?ta;&CRZcT=ANl*;;rP>9m$X!b2x~>*C+dK%ym1n zjm1|%)e<)~Wj$Qu{BkH9o}>}jFBtm;V1t04eWPD~IUas^$r|Qe4fR_80FrG!)iQjD zQ6IbwmBTvvJP7dO>@_r}yde{Ewa%dM5ym&VUtzqazjtMxuhpsw;~M?>{m5v02=kWteMsZfiNb{$=8J%`fe zR^0DB8gqts7Npo){s$sNH%sibnDhW*WGqt_Z>m3;kV*VWCMN#;<`MzI8Ha+EI*Ki; z%ckZ3hIc-cwnUL^)_@D$p|G?pV+CB#wR_Y3I*4UlXEqpxa3oUuYEARv)|qkdbayoC z2@J@-ydHJpwQE(51xp9R+a;{4si=aR_h*@2+K?Nzp?y!Tl>SbysPU^@-R@0RFBJKS z$<6`NlVlv&d+luxI(eA|kb?Bspw+RLbp!Mr1s(v*|gMLrV$I+IZh)*ee7}#FhUh1jVDT zhd6eNm(q;2autz%-Lnn-g~a z3ln^ENw*u5>oYAOe&KGQ#h(NgBr^U#F9cNwzc&a0CcbPbh_n7E_(Wk6&6z#4hZ&^9 zHm&U1874z~j87Px7QF?3OlSEl@MRSBiw~N;@h;xm6iv%^u;v^W8;KP;Fuv;OD}!8) zO*QNS82hpT1$>7Vjo%lgZx2DaM8njer-pK-{e4C-QO!y zzcb)cIk5LsepfCrX73#f1BS^Bc8vuI=G9vf3q9}^8{Y?9#P}!BJ>Lo4wxy*bUi??w z{?<39lJ_T<*pSN)Jw^zjEpsL)Qik*4#mkalBjkASt#>lN#hz}K!j-exL-seD!18NA z;2;{P^E-u#e6w8xmo0ODY&)>=bI)>CnwCK=@j-4##*fPg3j_c#M8htj7v$~uOy>U* zfmuT~+YL~J?(iStGn^h7^9huh%ns_pzc?OQJN5eMpY!5@F0eASLJ=dbL;G1}Wm0LJ zX{ptkr_CZ;uOr-B+rY9H2TMo&FG`VY{+$-Co2c=3x+esuON5{o#VF+@@)%v*Rh0gS zEr#Qgfeh)FhhNnxn`wXQ`UMr9JLNU}EvhFSrACvj$vY6b)_)x~r~ikcCZGhnZF&fv1^YEz&+MD%hIwANm1bSfQf@z`#9_sjJm;T|@KpT3)lbv6FvhXH?SpV~OelV<(0a9|aDM`%D=WKwDU4j>D!kL(>P8C#Wr;=i=+An zqj4_;Ch{LhHazn)5%z_CJ#eA}!6w{hw?)JGGBz@xf)?23KhB83SoGCOSdRbVM>Sn^A`oaGcoXWI@^9J z?gXlZ1ON(@<)*{Si$O3u%&SEc1%~EF%J1*sjCa#GYk~Zmn-UMUZ=Ws?JCXk!x^wSq z`w&{NW4T%^S~x#YXw&0|7G@yYSN7SyUmarXe~5SOjBw}X1$ny*XI!{3S`mWU7M}Y- z;IAD-_K>HFOY)V-ao@9#plRFWr4Mk&81)ncrCnYSA1^6cx-Dy~jDCwl?SGmIEl{0b zXs!f+Ldf09b=44i;@iA6SE=%CYtBwbZuGS^kqw|u;$y|9)Sco~1k|iDYVhiTRXHKk z`1G!W^_$fT0tK_RL8a0Av_jH{V}mZ#s9LI&afuqhvr$u7PvE%_-&IBPx>hvK+;dO?;8 zx4#mr$oh_TE==ue%8Cfs__1q4RA65Jqi`{t>$yL6-tRHxpxyZ`JhBBMj}%H#kkgya zAJSGLKFzc-$NnyB*c$;E*x%S8Pq>0Oa3UEt*L$(sZO`W`q1+lbbcz%XbcKLO87^ zx$kcIldo`ofo&(g`=A{A?zrs+OZ(rj2yf^AVA>FEu26>8P*J+9EEKL1@ZlHyusPFw zLF}H)m(bM<_Ft?k>sxR0Ks010*F&su^5X^7%e#$8iXYIn2N`(uq`$j{%M z&Ff{XsuJLA=5eP`z%p$6#1okz7rr=_fYNVAE1MBow1E)RO`+6S-b`p>XL;RJXhrK4 zpxsKMXC7p1_iG0aItUr`Ni||B?#o-rp3)bUiz8nt0E>6a#BeAF#Wqw#`urt61dA?% z>WkFQe(KJQm5eJ2d^FB>8<{VxaN;*Sd}myW(aYRO2-_vGSheWTx-NbBj8V;~YL0QK z|A63)_%CXloc14w`=FfqlYtE@Yr%uOljXz1L}qCOeFA7cwIpkB!`Vs@sUPR8N90_K zY=u{oxc5V9<27X|W(nZW8U|nX#LG{p@A#Uq&5kvHCqDaah7e;1-N@G+;$^h zJ$pxs>6;ZsQdt@(iweD`S##iT3E~sZpV7r8Erm@$=F>?P#Crr&5&Hw5%r z2!nAH)Bl6FXwH@@slwfz#9l3q5Ms_D>Bsk{9yBc1SbgZ7Pb)OsJy+Y}eoa1$ z9B)PL8_DeO%yHYT?ZUEr1bCLXxNog zMPX2?&3wX1Db677>i5C`!pgj$`uVmiMg={J{uyQN{Gv*yKxuw%{G+7OSMH*VNjXii zPizzKTn3=FN`zk~Cg=pv1YJ-A5!$A?_v;D^B#Q^GNgu_vzX(F`}y2-qa6N4)gGbL ztJ>9Vq4PIFzg%5A!0X8>_rR1RsS>}U>dD7Wg_z0GbIOve%PWV|09@x<=T;rw=jGi4 z*Zn>)<#SVb&C~y_<^b~$I|cyxoj#Ai=7vL5gx~qvjZu}YMAW)-5do_{Drg;{zBP=( ze24y9q=>VST6FFcmFbqT8tHXzN%G1it`4;vz=mAbN7u*tPgmxHnXDF1cU+IFY@yS1`iDBf2@WNjEMaJX86i1 z%Y`nOiXwtPO(}+De`isL3<-dr+GJJRkrPV8AmV$=yl?V2*uup3<~Ie3tAD-ojMkUk z{eF^te}CUZ{7L)a)t#LPcv9Tfdhlo*9v8^8$d*$oGpj!Fg?914L`%J#*GRYcJRQ}f zm5WKvwg#g3Sq^{!xV^^f!0^>=(evWkPD53|F_@`sb4U;Y94?<<#`y28;vy{!dE{!<*!o%qc< zgRydXCFIaW)awxekSBeP?JjMY@M+cbJBp7TTBT|pp^8JXyY^(A3acN(Gj`2;p%e@) zq$C4;m#*XgAO!5WFM`?{-k4nPY@&3w|ArjbFMgmd;BxcC4*%$-P`GXiY4{GGuP}yV6prZ{4DJs7~rl6+u zYb@)C;GW~6N@wwjP?AXR1Q6Ne6@vox_ZjaKv-w8b z6j;1n$V!A1m#;r(^MYJVGkC7HmhTf&-xe)=|5ozGP5`%p??!>ZVgTSoz=Ylaa$9kB z#sf{i@z!oC%0y`#m3pw`OQqMXyQGuF2qx>mQ|QPGbu*GN;$d0;)zC`>^aBi)^js{=Jnf;m@1K+vplR8<<^88l zKXK?|;}s2+o4H3TwEFJh$$25#GfGxoS8~n)5|~2*2KcL7V{ttzX^0}`anwQsnylSt zw~3K9s&_c=@-neV+kb)uB6Uk+p?(ay2R+Ya%5SG)G^l&+6p>O+qYar@9)GD8#B0~( z^^#Os1wjTcx7!cPouP<2%T*3OWST$z(t!#>Ti4Xb#0*b_gPhDP{ZUZ83n7);vIH!q z@ubV}d9;mRW{!=*GKy%AHCs2@Q*bpk&uEO+f6NgCIea)UN^aR6`fCDiu#sR1l*A>x zi1qqT=xhS|U^V9)E2lf&FcNI&Rl@Dsxj_4_(o2MalXc))2vPV=WSll*nZo-HV)D*W z`>^6e_<;*ox0J`5D^8^kj?CsvwnN-IeVee1y&G=Ypfh< zOMhn9S=7m~AVLQ+aG@A~&*m4+G!dgelr$*)A#g*Z+21qk&ftEJPq3)CS zwsbR82sKSCGqF9f#=dm1a9h3#A*Pno#7(FE-HFSG&IKLfG>-Zwc0vBC4{!+LSwKh8 znx1k#&iZXx4+DzxNcrs$BA%Ib*Ty8g)V|o$aNq7f&YcPG)c>UEC*YK)%ARIa5v=`2 z_zw*=S7P%n@;dMNpvW2;n)yy-PEUoh=(Kt#CjT@8VBxvED(=CXy$w-31mX zoZ^t)VA7WE-=V-GPY2%erK7k;PO63St}P`xM+2?hrj3g!U4*%5)Z~{-XkO89-fO6A zCD9ES0#E>aWM$7b3S@_aj0?69>Cx?e+44hF%SEMQXGfmNYJCLWMihD5_Ma-$3tcJKS}l?%ggFyrqPl+%bUgA zhx>foc`M&-8#sGY05OU2Q!BzVSdo#b*p~|#G@b*A?Vah0%4H2x1g!gDiYN348}{82 zkJ<6}eJ?0yXN-6mi$t3=i??ma{61MX(OtmGuUu0pYH8E;{v}qhzPnv%Iw%FZwJE)v zLi?ygLhsdx&(0g!8*w`oaiNOFyo9yAZDrzMv1>~+P*Y+~iD|;7g)lYEvDN`w(8dvR z`e*E)4V*MP)Knsjk6HRO4YcAHL|p%R`_%LFS&3=9dIQW}nD+cwQ$p3C8M zIpuNN+=>2e0TiAB_3Ngi&K)1@A9n1$%2V~ao0dMzJYckrKDN0Ey^q6tF(~l?X!{-J zsO1X)@`$&c=7KIuM2TLlVnFCkbTPk$4>eEMo; z6SjSXSv%oCw$#xVNkt{7)5B&fBY{L-cww8s@c8`Q*Y7C@jDxQNV1#>_1%eF(KZE4S zL-Me^Zz%T<41Cu%q!&xV&MA=5K6Z`kkfmq7AD0mPj- zkIMZL-t`%U4UtFWQ(}M}iDvQ0)`98WGckJj33n*8EI4OWwm?(M=35HXud$;_eb8@Y zu`q)i-1<2Wqn4B!Ba+nMP!JK-Wz9&H)1HsZc|wn!B%E}a)l{5?z)bSjhlvg@;@kI^`-B7j z7xE*h4X#5iH~;LV=NY^hdxKRx9S?-pwjvM>RY;iuS}HRi&NuL&Yzl#=d8UgyBCG|-z z-S47&(MUVIeHNanMKO;{x^i@eX<_x8NSK82G+TlgSnJM=1r)K(3=GF_p8sB>sjz{W z$i-U=q%m}?hUe=fRf=m<2s2K4xEhZ5dm%v6#efDbX4f8AwGfy~#+t{$Vh2N5R;lqR zsCZz&u9`ieB|MRMejkf$Ar%+)ie=F+c77x3)w%M5I63UANxHta7CLU}@5I(KDuvO_ z#zn857X_ybHFKHb+=q-bDukH7!X%FldZso)cwUsdt)>$S|B+9=W6(!kAC%;r;1+ca z*l~2RuWGT8MY!=()D0GiwnY-jV`B_V)Di1Q zrlnT>>V6ipao&3+E_5{F=2+ljHH)nq@Zh?6>9B}(SSm-Fx~CiSwYWYHzS{Z3h4>8H zJ!Jom@0Cpjf3!vFx3HkH-i@75y^b17Z1q*RS%M;*<8J(= z;vACwd%0!Zmh`D;j@Q}anfc8bu4jgiRHSIgm?*gO_%%2^1+TohZQSKoX5p1iYhbOx zG-v;qR8s*)?Qa~}2vVa%JIBc&H(p92j?X1`3#HEHepO-u0u}>C3f({L7SGl?ytoY( zF}#fn+@^!sy%pAF%Ffi_i3uYgBs77jV z&~R{mKu50-Ko4_|EaaM;PnYA5&v$T+lWdeB=Ha`0>j(-0&E7W=Ym2+R8mucH2BtAc+3qp{%QNi;R9$9?VZ_YMLo0Qb9&xge9er{>yVeN|108i!U5v>!1IyZuE4io zj@SAMm*K0Wo^T^2p%%nKm?r`6_; z3M#t{YchZ5nV2jm2&1k&2$ypMg(IOxYJg1TS$ILne6kZ_O^po=Y=oZSk-AxQ;|aX$ zf9w2T6wranR}INu^DYv3WZ(72-g=wj26%9DR&8?hDE~tF)(gWKPnB|9Kdin}?zG_G zSM{rCIEFX{SixqlR9G7Lc0eE1eNg0QVd*(+QxRv(haU$!*L|2bSbmv#!GX-M`>+>* zF*tZ|{058S6!mlZu{JmT7nI?-B_oh&K#OUW; zSQ4>K6J2%8uRo5v;t855XHU-_wS|&MKn;g|+HYgnujYo;iHVRzk_M}BTWZ$6<4%{9 zzr=(P*cuD-qj+U?&Ezwxhjv|bLtxYr@f(f$zn9{*;>yYa&OTSe9+z_Xu`hTIZAPhL z%y>q|?RUef)OUG0A*5Q7tfI~S!6pNVq_VO@G zH2*D!HBoPN5l1iEa4KcRzh+D(=wpiyifSPuvLs?)SE;M~m@>>tnxdghl5^KSFuL9S z%2N!pJjLUzSPXCNxV72LMnxhw7tT)k*qdLi%?ZAF5uL;xk71({;jqH=f%gZ6b~jYK zyW6eJmVF^v+u_al+COd*M|evUPBj`Nh#G$VLTg;ZLjkx;YYM)B;^$$?!3Y3=ckpuQd^<;?h!;z6e-H+*ot^SI7;IXlS?ZJ}%!pFV{H#v%F|1 zp0W?hOMua&x#>TpvU6ppjwZWThZ4$hHN)Kw7QY!m8`9OBz zQG$Y>;YIVlJCgMLs<4K1MU(bVB4i+|k8Oqsn74Z4YMx7)m-TI_6VR_LL{F0h)I?jW zGLatlE>HL8_|tRz6_!Me`JfcQ$epQ`Sqt%=#qeJgL2A)X`D}?iyoSaBhymsx0Rvlo zYPkDm1cRm!Jy|k`#NJsRQY59B4gZ?{(}I`A69(rcqt+Qxac_QMI2KIvth>ccGKTRO zGkL~gT4OB}wTO(j^Gpjr$q33nz$QzK)IsSlqA8M#5v?(8J`i1_Xr*L5=3eM(&-z`DP`ug(sz03qm}a!6_(~> zoY2QwAC&*c+FOQ2`E~8X(kLY$Akr-oqaq+kcL+*L4GJO%0@BRTEg&H^(j_enO2g0~ zF?54~#0;H7EAd{#&-;Gv=ehqM-wzyz2VPv)+H0NrT(S3FJKk*J!KnoY-epR<><6IW z!c<@l%rBIDf{8_tulOJ6kZW;!`0EclITZc-s#G&+MxO;9Dq771wi`a6q+IxP8bQl@ zD>%im28gRWCW?>U<$j2Ab%k%5*=P{HgNIFoFPi1H&2f*OBQI5PQ@5D;xd&fIrW=^a zaC&G>APqUx$D8t&;ZqYQQ7!5p7{U)QaceX^s~sOD{}8LyF@hiI^GSA>#W2ljW(I%* zjynVb9)m|qM1x@|^V4Hp;a@6U6T-}A%w>84_^$$;5R{rY|#aZ!>k%K=0= zF@QG~{#^cUDwBPDNazEw?)F)Zt<6DpJy9IzHAc|NRm*5YD^^zsaP_K1VaV|u7y4(h12-tMrsThqV(KE!bmPbUNjgVY~3fU8I;CtDQ-Vg zj=>xsjF0u`J3QI9z9I41m`al(`EX=z$Vimk`_6J|P%hs`lLgFuLqh1%oOSHl;V&M|Ip#txEf`|}x|9Y$l?-RbSte2+;jrd@=Q0~-$t=GgnS3S73%N$$<+@sqY zm#6(G<-7WjNJ~yg&sry95+uX_^W}HMD@Nm6Vb(e393fAv=-TK+_hA*>n~Q2G+(Zib z+4p@^OL&Tk5H@1X02IL*1FRmz{xH`=(k^q#x%JRLXAayfDK7}AAMs244y2iQI9W_@ zkSN^Cz{JEvqZip45&v%RaQqfnia;s#DUk(Kd^*g+k)UvihB0*~r(pnRcqH=W#|*PE ziCqP13Se6D$2K1 z8gtp#;IPjUvp>738YXeV&a@w$11%PGk=xkN_70S=rSiu>@>oNR-XUo=ujIklkMom( zb5upX!7;zR8S95uKK7#ZU9!$?&Rnipu{yz0wRG!uS{jRCi&@a%>1;?7;|S?8Uy*Yj+OVou5GZ54}}))1C^qJTw~4`PQoqm*28>M z^^8oXeoyxC%+D~Nl>EB`gvIWmfqiC#TXwb#jp^zB!L6u?aiRIrXV$^wj?~i3BP)Rc zk2)#u&{ERV)96$p3)af>qt{KixG4e+ZteVF(|hwy`SXWpT{BwU?5#4@xW(ysV)FO} z8Y+E+-VEyP5>$U6zxT9>-lI?q-WwHS5>M5@Kab3i<@xYTo7PD5DI-XmlVbs?q0hys z8J)3~hkrXf8}xk%ZDTJO&<(e~wdsUv@aX}>9qz{aY#G^~8jk^vUj`mkEhGTz;py-^ ztLAFe;ElI>LdS@~KiA7J&xQP9Cg0fj2KPSsyX>+!6kcDmLa()|ec59bvjizrZ zK@1Uud8h+jL&r<^4~Hhv8S||tnDMQkS>1WnC3|c#43?ES>cN`fW8fR7-{!NPTWEHO z5SUs*8mnjCSmKg{(0ONyB_!16LT}Zg#A;7qJ&oY*R2qBOEwR@(`dV>RujyE`8$rNt z6R&FJn>cWT5uy24^53Qhzo*MKH%$n6Tu1Ew$>FjfX8^WykuYFt*q*&VQRRzAK~=?| z%kJH~-^~jVc)sgzij(7t%JyR0vP$W4=va6-WII#5bIzWib;zIcOuu*grsm4kIFrP& z($_JFvO7ioJVy3*|D>_eZ~h1v8oP7)8V_HPKZA#I?9 z5@`Z%L5<<*{$WeG?;GUW;+a!o{Ddeokf$!(=VP3@}p61Tx0 z!OPp>sr*#Kim^5Itb7Fzc#oGD9y)pjm8Ct7MMiY);k&%OHRD9Z+c9F4l%?Jb%V7^+ zYprj45eoFvcfL~(997J;B=Imb9*`-TIvIcx=51;ZMrebczS8!tzpl7f+I4`5k&}-% zlW@Cz_t8mL<&c`zsewb4R>e<@35l^@$(>tJz!wc8!QdT@6U)-g?G}8;N-w`}1f42c z2(}K2zA8?K7>DQ_WcR}w{?83u)Hb3|-4wPdbfTEL=#f{;^PAV;6T?Eyf9_R`)gbcH zaU$rc5*Y8aQT_mhjV_HC%7J4&sX63L2AjXw&sFHCxFFt-?&|UImOCTs>`W(@guN@O zYw|4b@cUe?kHLqZLhrS0gd#=DVCTXMJ8pc%L?j<@D5q>BrQtj zU0WKHz;rS4sg)sc{#m?PNFaa`lA4ub>Fsx%V~dcg{M~bDf}mG!*kZ?asF1`YL5U*H;KvpsluGq9<5ERvq&VHKl}|P)F#T;J0N}S0!f}n zn(a}p#hqDbI~PsL7g-m4I?audPcZgW39BeX%oNk=-wl6tYghZ-=>3Mptkr#eB~WtA((!uC<*GaIhq$t@8SP~Zr&!_#IuV~oZ*jir^| zC;}fJB*vHfwv2~~dB=FKBN{fmaD;mA?2!Q8CykAQpPFxlz4oZ8`#9hEfbsW{lQBRbXl9=bB-o&t_?rr)8x2pRCM{6Ix`usNUqy4C0=eNJ4YLK`}e|MLwV)?Uu zPe8gq9SGt^_o_|K8?(!C8aHbEef64J_xr4`Pi;0o{f2%aHz+I*tqN}hn+D&2Pn4V7 z@Zy!Y|EhLHwt07f?wUwq?1=U^q|U+*px+Q17j#E8P-sgyuX59enpvjRz2rs^?b&W9 zevNd$nJy3Kgl43v(o*4GLMz9;XOHb3ZGW}74t8^^8~;O^)WRp^X84$M&$;$r6hiIM zP?U}XV;Q`$8Jiv^P`Uhi`EbxqO%rCajTVI3^*sBKu}{V6#V@NX&Q22GL~GVd{Ks7|l_&G(GM z`t6UCUjX}U_tb^SqFD5QCF0coqI>ujxG*p-qdD>c^lWb4QFLKX!A!^F-`mJ{wLQ?W z0?s^7UB;bguQ;#4+oK^W-9MkRqs@q~<6~9rpMFTgRqs;{26>BtrOb1_@)}f%{*EH~Z=YY0kGu$d_}L%^ zuOfuz;e-cP=bH+T$9jTHr?sBV+pvgY%9Sr|cRhkqSkph2U_|cw7zZ`huM%>cnWTfY zA2vw*ESYtEaQ)KK8MtL`x5sjbpY`eMS>wmEaQmwh7-R^r{D2G}hkUKX=UOvNo`uY zc6&EG4iN;^e|AGZXER)D49lIk7W!=VJVq*T;^j%NW@v*e>I+??zRQTt0{r*c4I2+) z?L3d~=A1m^k+~ZU1XIg8Yl%f%;R81#om9g z^SX+Q8Xo#dy}*^ui%STk+8o8XVA03{x{3PsXv8$R_7FGB zvw8Xg?Gzk6k1Xpo)dJ-sYjRLeI#gkgJJ*DJ`8-G^SvFQe?IDp0}QhBu?9jVB*BOebv7t+~fM-NKg0u z5>JH^d-={thT+n$O)5-Q<14&9M|ZDtSJfm(*~7$5EE56^9B9Vyl@yfLE47KzA}U}3 z_u+bt(m(y^zCE>YXe1%<5L=VRJ1IoyI*GGWMpf%3Qjz--URK02?o&K#i_5yRr~STk zSExB#h8#XWfFqJXDcS;}^8AxEFmFxmldB4D+JrX5D~mChOkVXsAn|C3O~*0Y8Ujgn z&iwqCHKQmKQjSvZI56_WymDHs{AD$P5!(0MRca_6m~<6> zaM}`9*?>t_U+I4y2IGIA<8>++ zvjA*rB|d%i3k55knG;=rJ|jq$u)6)y!LDC$1^S20q9{MYgI?r>npCIajA?u1V;Q_u zX(mVkL=IBqNb({;bLM_D5WeaGiU)=RyN{oS-mSxl5~lewms8cJFik7d~O^(ZfN( zE5f;2W2!w4KUT;1nn}{Oos6$F633OMM6E3BJ@4R%z0mP1p~(#izxU)CExsB=m2t+- zl`2-LTY%0&d;SFp9fbNULIHLo7-BVR9qr#=Q9l))HQ}w%`I>xpx};XQ%*oPEvX!w7P?yh++8v|Hj=C?b zc5^PTrJ?y^h4Lio>*-lBljz5oGaXmD2Q&+Ja~b2`Fe^S1?SEN6;cK0^fbMF8_dam} z7M=9s7CI);CYDb^KB3C0rLGSszwM~7X{Yjw%*|EPC9Usx(b^w_(N z#LXTkL0J=GWLOjCJVBK2CTVDH<(kMm-!!Gv(&e9o&)GH{l}?`O;=j9Fpz@5Bb6?|$ zojWyE`Bb^szEsn%hLFuE%VYfIg*3>DbmONH)#hdoOCLe=sSNAOMBb@AF`Wzvzl)lC z!{0p0d;icfdG2p6wjWTsZz+*6e6KJCpR{g9b`FBvNjH+7KOLc5CM<##b}6>a*(*uU zJrLv(EWyN9e`9ypnujn8iCjrLA|8%iw z@HpQno|X+F9h||EIAKLSdBAb`z}dU@uyb?F57FM{i5jbtz}t%1p-VEBwAu>gd?~{P zmvzXdzAPkdjwaHLLj~N4MC|%HvAk0-whE2iIhcNb+b)BpV9IQ1$3ZGBpX1~-IMyZT zPr2TH0t(rcKs1!TA+#D1@~oBpk4+J@MJ-vxC1GIce)KA?WwkZY!~hpWH5cZ!PQZtl zp=BV)Xd@$BJ4wAsvNkw!8^<$MGqUsalF8`BDA+4As*H(Yc&#V4;WOgQP^3ULUH<+4W|;vVhKj-=)_`H3RJQ<^@D_vs ztQkwfKWZ<#avpR6eyufdXA$(~f<@d#;HUJR`^)k!i%|;QPtU?RW25fJ(m)?Ey{cme zK~<;ri6zZ`@Cb1T{)}8V2(7)_VAPxFoce6E6}0|-2c`t>`BEs|h1^oX+&i-PLxmfP z@aF^7BXZuaTdlVD+#H*O+8*H%OB!;roXsB72W=0S|6DmuKOS+$9UgYEUdlK(v@qVt2q#2wG0$$dPx_BzE6jFtb>nhGEk1fie?@~{sbB5*qrU|l-j2kg|;}aYusPoXU%xhSL_2Avx z%nHi}#!F{w?>i)p3tAx8if;Ki-lkR7-%aA_7^-a5oTH|XT@iHF$x%^JsRbjI*C1h` z6WtLF#rn7MxDWWL>}2o=QFeljZ6=&_1$zVD_*tD$$9??N<0rpQpK6fgG+hSFu2z?y z*B~%eWqDSITy$q)ZhiZ4sDyI*to(4i_!V`5-ekW(5g+a`YH=uoESOoI1GFi);YW9~ zExvGX3#&@v52Zx1tp>E`t8LxS_*jN0)b-L&8MeM&-$9-@_ z%*(-V-TSBDZCaX@l+csNKYs7dV@p5ORM9s`N9D_vQ+vGj)PnGKPVka3*-J_^7vliR zrb9+FG>yL^H!hgVQC)uCn0ycJ*q7N&P$YQl=eK5-6A0@r+&RAt94vWIlpEmZ_c%7{ z`MpCS!ZH{9J{tK_u(MkgKOE<>k_w^v(9&+3VrY8&Ns#or+z`o^d3L<4p29QOjH5uc^Mh zGL@(gFk}50(H&8*zTRojt$xkZAJLv;i;N%|2BFp-j|zt!#|R3R$J-4?f4HkkE$3?~ zX*fqdU#UWxIO|K;Ger2BzrgEyR*IcdMaw8iMLdA}OvVzPi6OdB`ZK|;*0N7~=xFjg zZ@++A&@Ivfci{L z!J}ZHU$0VYypJ?9b$G5^xAt#Rxns+&(9bu^&o=OXdzkn=`et2!J`_*{)pTPwIJQRn{*Pf%Hs5GuAN7kQ%7B2f*C`A(ZU@BSU zYM<6q5lOh1UsYc@_2TgNhn_!Tn_fOZ9_8!|5a*MHeIoIZ`IGo#KEiH@*Dz^qZxkG8 zCd(so_@*B3h;e|T5KQZQ0Qj$4rEBLZPKq^QoqczGdfns?%m#(Wgg7mda{eu|9 z$wWr_mw8@iAiWM~N`DH_oGxCrcE1iVQCV+UDd|>+bVTH%lx~ zk5=>mHsiLo@FAalLr_A5OqIf2rC#k zx+Cl;`n;}X<*T8r1APYA4`iY+qIWJW&8C8+m>op+=L1$#99z#02b8gSICE_hJIS?_+DRd<4xW9)ydewU_lt&ZsFtrZpGXy7YcurGzuR)LpW8Uda zG?HdnaX?jMfd{}ZeUlb1_L-`_vepg_<&~i_97-+I{_Sv zm<*@83}jT;X6h;~zJj42!iy6!ROzIS`$@~SEZ*+^iC)n5Q+;Iyd^4Y$b}0hN3^AY6 zSBMJYNCJq=7iQBql^I68L&^EcKM4|}oo?I%pPoL@ZE~HxSzj?O0}i5KoEuzMtfssV z;awvkua&ij*s_8QFgKok^;{yBi;V0mfOH<77uK1ZbNkAmEOQd{Mn803Dy%A4P|JCW zQj|XsPOxLcnA0h|Hak+}u(5xquzE5cQT}=MGor{6Lhu$vgNwn1To2Gf9Zuo82%Oyaem%b-J=ioBfs7D@@OI0Ng17s~a3PiCFX z`wGuDD#)?AsrrvfTch1GQ{DDi8Xf3Doh@k1!6G4{#2n4{ghGVzWFeOZZ_`A>Fu%0k z4gKCn*28Evuf%g>p5h8JmS- z;uC_VbS7@oo0{lb;wNkJtMj%iZ;S=P>s#r2!CsXnou2~x@3>j5WPKhjbbliXg8CyS z8{P;Dp5A^6rmRZcqZ2fPoFXs^^_=j}tJd%C8s94Z+8RfcQTe(fR>(GvgG*SkoWx^| z#vYkb^?nKyth2v)aHHNOI8rcNH3E#0u0Vy5#Td{rbosei@Ixrw2|y>5=RRx{(_sLq zgQi`p5an_9Ta1UaHYQeaz`ck*AEQj|MmXsdO2ne1Zu2NIw9k3qHi&6E%UY}aBBID? zpdbzVZaqWOLeLd*>#!@U^E?i%2qo+D?@y^MT@X@6KpJcRLCxpbq;w_ zqe?PdcC7X+h$UlLdd(p(sN{mUYgs>f?Y!fy$v<%nVip)yVwj{mkGeqQx38}Xa@8s) ziH??7$kRVx`5zsfLx z-N9;x5z<{Nafpm?Z-veZmJV4m9FCM6CA|-%Zs!@_?~u}$xUXF zWv0>h^0-arJ^8C$$&befPUhmJre;oh{a8f~@76~k)V@gMsmO=gGcjmlVi$h^tf*Y* zl%NQj0^4}2x+dfxIQta&_QM5H9i?T}oS?>u=VD)}s6^^|uw-q~9YKC)2ykgzB%3by zAI02>rmOkWKQcZjG)$uBuYEaJy}F~f_m#yFWxRUwn^D|}6S%6GJ62Xm!d;#NYH>S4 zM8(AUnzxQ}CvAGvO-g3Raq@$G0!j(ogadlDIED3j<`|d?MjHe*T$Q$d?Mdl9_1b%i z3!?Z~lfg;!E$De3nVueFZF4Z#W?Ld9(1%O+o)-1*llIX20x(Gwo@Xaark6#te;2rst@U1^I24PN?p%qsxla+ zE;qG8*zZHUWHGD6Kr`_&U zrljR@^!~_ETB9c3^1ejF%%E$zFkU#q3AmQk7?rZ3fpHqLBgQy^l6`p#HWhi|IC6B+ zu)ZPFJEBt+PD}M5#?9Qb2Nsd^v~MS}-+}m^`42&`mox#PKP8$*GmZpYd%FG;{412) zuJk1zSu8@ua&)6ka&`BQ`&Xmm15jTiJi2-$;XOC}5!By~T~&GVyW5x~3yW-1Nw;Mn zVf0bhM@G$ybZf}{j7Z6|jP^Np5KFDz96u7tU)|Ft1**=P)9plcxLpLR=Vz;2EWU}I zD6g;&*Dj3_eEElH%qPyk0c|MnGoI@5-G7WJ<~YQyd91JteXZ5s$)^ z;E$g@un@Abm+&9VGO*&lFWfQ5+6}m``v$7YFD76+WuKhaxV=|7;h~rugdVs|b&A?r z|27_EnqUMrZKgc|q}A3ohPRtkYB{yjg-|VTq72@dXi29HS?NUl4M8hp`)eIr2`gbmuS5hqX_ zJX9|jc$)AGjRSG@$I?_t3~Xup`5965%3wL%$aQQ=emLGb@OIzwK+Q$IppoMk#NuI& zu4Kpfw1&N0{NatM?Uo^1rkjO?vdiLHedG z(%JOpOsy{pGgEE8I#E;K9(b#jq?!XK=Bci&VV9fcW9g07*3Y`&3HQ8=T)e6R-Nvk! zxu~$y4v72`PjSgx02J%l?e7!_fYX3zNg@*=Rj{w-mR1wi8M3;53eVc z-dcxdB1!zQX>b8PEjN>9GezhUL-}Zka~Rielu=!~o#Z9bn|f>4vd_xdyXJFcHH9$xZ`U#`nZ(RDR&?g;2h3noRF%`egfpuM}im?y7GZwo%S0g8e7-IfPSX$}#-63iw` zok70_rML~31@GeJ-gl0nn_UiOA#+}x*u4QMywoDctdrKM=F(Nu@wp{jxE9CHzSe_; z9TV9w!{GCDkDG9d>%M%CDD0s$OQ=VjkGnF)z&u>MA%!bsT%O(ewr_LD-X))Bl_)Sg z*1Ta;I=IbZiCNTO5*f=ZUYpoegVfr+z=J#(J!-8sIfCiG)Q?cy;@Tn<5Mz;Vl-Xt* zHED5AQQs0R?G5&A?=@wGmp+KW*=oOw0pi}G*UWEi3vSu5g4_$m2~3q3f~s%Z&Cd(g z>VOaL|3q63U9!r=z(Tb)b63^(U=;-FM~OwE&mktsyzzoDDa3jP_-YJCuU${YhjT)o zZv*k?2U%1uZ$td+^!v3r&!yB1%g=Q<@>PgjgCCn_1+nacEf1wYK1*Nlv`~ifF|g+t z^P|Pz>=%oN27XI|{Gqh6=vRvGoHi8wMo2#AHfoK-S}b#O>t(Lq%!V4%NDH`StrZLT z5-CKfCg@*`3K-8Ot1wV_Y?h^Jrq}=CyN7JH=vN5jjy4!%5>GbGMDc$`UCSJ*a7hhe2q-BSR%%{jt(SMqL$Lv}m`$1%y^=Xs{ z@dS8K?L_v&SsA}d<9P&2&So&?p+7>Lt{doAI%;O*|1Be6n4&;#WNmvLcZKgvSJA+o z$4kM9u1j6!BRiKhG5W@ffaN#&=6Yql-7S z6OZq-gHVOr&niDmY%fmJQ^3KU&EQ=v1gz zkFVzj zPT(vj-S{`OWoQzUQ?=kZ_f}`@%Wh({f2yx*Ja-_gRO`lHC(A5)hj;2YWm2NVP*9a* zG}`SN`)3q*>pjq)!MeJ-1xJ^Th#l(1qwM_Zckh6riL$hG1|XfU?;$(DZ#Q9PEp&^E_gMUwajLA>P$Rl=cK zt*Sj~K}H+`Pq%9Ec4-NK)b~dW%mnHRI}W+`#HwP5FdQ~bWp@*xzI)6q?}ody;-)RM z392_lW3Bs-5)yGB@x{htH!wDz#!2(W`nnrNe=k9OwbQVrNk>$NXsDX{^mJNQ^2>n4 z?_{`4g4S%)GTIsIY|D?hDm%AAqt4n=7IVUxKizvSXyEP^=Z7%L4>pu^&pPhhzk$W^ zO^6tvka7qh!+eZgW~9h_V(FOCd>;k7s%xQm8h2vrdDCuQ{AVU$4FR(SPSJ#uI#2(48L;1D zEDWAwVht+C&fCdvtb0`Ww?)*@=8SfK-RXOtHoRsteRCXfE$14R_`8eAh6n{?Ni^@+ z&1-v16O9__xGk)D@o_?GS4Hq4I7+zKqD9;UZyLSb{O~)YFx6js0h({#X23GEp@#9T z$VjV5f5f#0tWqe^if{W~PB%OhG;#Uf`7Ff!U8}!FkKcW)JY^X3nz=tyKGOn%4!7T? zEoFJs(6r0rC}C?NgfO~}Da8`$qPj)S-!@Zj;$Ba!o(^^M@_wVOTJ5m-8h>)HS{Nu0 zU7N*zg&4DbdlsrdckrlE9t>@I75qO@15OAL@H$p_+@PduzuhwjqZjYQlNt_uas>V2 z;&HTq*p{fPSg(%jY18o^rJxIs<&Wvw9IxCBj({VIbqb$g87__V(PjsD5S$R~uvy{y ztF%wnqU<`m%RVN%%?R}`J3V<}5CemI)m1pJHZb_G#L#7m0^!CrNaaWG%|0^`*0A5t z^kGC|dGES_mp0TX#KLr;1S&j@Wvxg%9!p>hSWN*^aIq_!9It78S>B-=Kwf8kfN%<7 zJBC$Ht8;)(pTe&B0%m>$m>CD-q%k=!%)%^7{^;_cxdI>k`c}7@6qIMTs$CU@t~w^e z;2vPh-avf>-=GA#l9^LS+}T*;^Bmtye9=ntp*O^Gj{t+KfOGYX)z@>lS6gl!F_D>> zE+hYn?tQWvN5cLRKg6|vw1cE{?p}ml+Nhca{bGJ1oW>kM)8&--VkKR4odm$lKM$!# z6uw^|@vX=c?A^1gjtZwBzwsS2qBweQx9AUwA4J_na-Z``DTR9*-WNiin%RP_S?-C6 z%E3YS)V^Zt>N^9Oa+L$fXXOJ(iX#Zlk1J(^2!3srSDj7I5W2ICB39N28oC8AHr|Cm zQMk4qrBAIgd5shw2)$&$RFcCh4i|-m8td$-lZ`%3_vpcadcQH>s~}w>_X^AhY#Fq>OTW^N+ZVLlHy!_p3~PIlP2L?ot8QYx%}GOMO=4;_I3_EAl7h; zeV7PKV#$5FnJe{?N>iJV^xD;56G6AWU!*MzcGw5S-f(&tix=m-m zUPB}(g4_7OAczIwWi7U8`|dwgN1jn+BvW@JHtO2#{~ll(Hf-D8l(-d#5{$61s;s94 zh|F&en2++-5CQ-$$Ipqz6s=D%%UY`M*3^@q92^GJ${T{eObkAqvE=%ue<1LX4Zubq zmMZ+wmI4l3z_1xT&ciXc>kbsBm+=recHp&%%<_Nj7RdGwdW~2nUdQz6jWx-c$A+G{C zT}iv_O|J^RT@@|3eogkTr>U2sBkHr2rphM_JJ(aAqAk`?zxqI(054NOqR36Eeg>f zkGsv*SZ%UGTqFMEpVn`o+9{4T)OkKJKsyhUv*UEM%>~{KMeyunbRq5{|E93VnDyrX z;>3OrCAaK-q6D6OBRE~-xU|Ulc!1=-1XekUV5WWmFuv!ZMedAB=ud|3OP2Zu;738CFDnVu(e28Rqk=MQ+7?{tL)g zE)asmI$_|UN9PC#SWtQ$xp_z07IlzkEi_!F$kEs#iEmu}ySQ4P}|3$s~qZbNv@ zTwE8@j+_931I4Bq97v?}&Y9H{HY}37oV%~3Mg7Kw;ACU9ywp>?j{!iuzb#-@_jnzN z=Nt-r{1(>T=|T>>R(pX#J9+&~qt-V5{A9B?<(e(n?=IH6e8#M&i@WQrECZ%)+Yy8x zoN1Z$W5dsD^0?S<_8@s2e}wb{_inKCHA%a7T#u?VXP+|OWWZQ90Z0)5kow32 zibeykSZ;`pu6Vmb{^TckJ&4SATQHrR;5vg}7PdIDfTEGyPJ~CTD=m}f zE~?*f+xeP8G8OWVJTEcfjIM#o0Af1%I2O$}o%Xn%F}Wo-{9pFDtp->VetJ@n?k0YH zM?)h#Iw2f1Hu}nH3i;1+1*7Sn`SuOek+wi>?ZPq6UmfCg^L>Gid5Qzb4y1VVg`4Nc z{-S!i_+~{?n?35T<^P+8NIBq{ILJAP%>1P3=FoDXVeMEd>%HJkOD}gCPuErfHlVzp z9c=J2v%{S2=piqOz5Qt#FdwmO0Ys=b`0E$Pnhp=NQ%(v5d4!ng%-*4kN47f9iZBGH zyQAmc)#Sg4H5D-=DO~Ur-gsJXx0G`VsWqgR)QMfR7&}`3Y6%jzolHv$HIJQ9+(Iv7 z|K`en0wIF+MQOUGjSx$Q*5_Y*JVF8qPnjNizKV7+TH``S9Nb9`tN*Ic_YIbGh@C}k z09v92K_I|mG;sJK&ru5MT9euq=iRv4(QdW0IBq^%(bOt>gPd zM<4;-o*k~AB)^tqAd7>u)kG))VmtsfKzk-HDl7ZRZOha8lKm+~Jv=1xmgf{l8+y2y zdYMem7F?PS6bec1W9RSccg#AiD7y4=zx8Z({KV^(ovR|Gsj(frR;ar7pz6c@+=F$6 z|BSs%^}s_*4u|7svx%pR-?IXX_=s+LqTl4^bN*0N4h|h2F4o!p_TQ8iUCIkD&=U1p zZaA$Mz;rB9$`ehz^h~&gM!(4$3X?a2y7nX3-f0sCVFLqGtt%L=en_YKo$m$dD4=XB zYv@7#zID{!t)dYQQ-f>Lr(MNtZ2yYcOrL@0GMav}o0*Ea9eluw)uSFk0=3M|zng22 z|FIu9yB#0@g2eb>u^S6 zz8T>BzD`5MIlxeN`o{AAFyg8afI-uBZ{gI(rg4j5wJK<2&;(Qjc5gQeJoEzEj2Aoq8>!>{xsc~61yp2Q@F`)~z+F_e);@W*SOFLBsJ?dT4;f`bxGFP!vylf0^C=Wwqd;AFLL=yna4Act3 zLJg3`5dbT9RTWzeBulwsf&e3o>n9!ytf!@=$KIr->Bq|k9NxcT#$9s=SAWggl3)^m zt)Jt)Nx~M>sOuKWbnZb<{V{pb5#;nO@Y5m%YJPzwC{MJW%f7pk;YMr> zTG(bXvj{V6KFzmt9df$ob=Mx`iwzzLfw_v%2lG}ljSf6a!;hPY53^7vV|nU{c79lf zc*h(=_nHCs2R4`n^TEZQ9K=1fl0^x}RWO)C^eFCfVAM1|Zb?8A?B{BoS2)hu^q8+a z^{x>_;K7e^L9!R%a;xuP2seDDKLO@#ZE8BZ`?zkgV}_>dpVj7n^fA$6TNo9)#$SNQ zbB2(`qiKdzG3!cE+HZJxEg(s!T0QOH^|reJh?}ngFKYOdkq4)>5a+_4_#acxk*8Ao9rrLm-rBhb`9qh7vtg@Z>4SUcnEry z;BHk8gfoLozym$8CWK|09;qfmH%2Ks=8Bp4?b*8cd1Jb+7;- z`M;!ubt3^Eahu>Dp^3YSJ7BAmdn<*BLHmOeL6ZP>EcV$A)0QF#X@P{cnpb zzWp!UVqgCZ`G3L;Y!L|5j7`|F$dmAibrN0UwB!c~NvkDIs(at&O8r85``X_xcmDqk zN(B()6Ifoz5jJMSb@Z^@M;>s*w*h9tnhM$#0fHn6V;=HhGo)tXzakza!p79L0Em(j z0hR-hhL0LsRCn2&^4tB_6UO^al6J%ZAN|d616R30bhFrTs{G4x*u7HmVA|YWOrST| z&+)2GfDCNrM*nXuW_$i;(0`?&w0jQWnwqL%m1T+`pi{OEdUom^fFqZ`GXc>1|Mg>7 zy+2m1{U0b`&A!S5(uThBOe(7@tA7E<#^vOH5ipDDPy?BRmIWc>Hvuf+{nb4f6RQTl zwk%k<>og(T^VhXv^;FjrF&(i;0YDi`ci5SZME%E!6fk8 zi_8RyW_C>x3ja@@9nMAs0IOwuT&|y5BkmQ425rJcen$M=EAijl`$Z3OdPod6;w}ax zzNOxd=4&}0WA^OEus4LfSrjZDQ`@wBbN-(b(I~;-b)S=>DY3UmBaz0B=97`jeB6z> z1}4WkwI;$eVcl-zSAJkS#x)LX76lN%TUBtDtpAZLAK0LfgbrNMHk+}s_B-x5skB+T z%H$8nng5lE1BQ*EMbk*Tpc#B^1<&7Qr>?$sBX+fM4t&x@a+B~cmyz zzRv7B_A8(JB*qGvBm?f-5nsi@Sg-{npxl;><)g@{Uu-f(KW01jaMxV`2se2g}OtpIx%G$ja)gXTFA4C4h|}E!>vP&3_Td zh|-h1tfC;VsVlvxs(2|89vZyV6cF_O;YT7J&&vFRSQNqDb3ZI8V@0w?37>7&OFI<` z1~q;bj9w5)(YSED=~-#9SY|A$YIV z-b4CoF$*9geL#`LbRq^O7X_+u`7gPjr9X094BEq^V(;qB(uFF*^!4=ay-pDx|DF)z zpl@16^8i@bql+`}dwAvj{;6*<2ld!qV3eCAW~TMb#yZ-60O#_3F_QiDpWVl46%>Pp zzRle7^W_W_Ie*p{i{DphCQLE7W6x!rVF^hZ4aSytZv7=Mez=f<{b`+^YgUNojYaC% zSRKL1tkWZ%bGuKk7h{g!^`7bPueK#63-RR_GowdBVi8Z(v_tF3wF!ZQ?XSyj!PKpW zHSpA0Uw&^##gA4TL;wc0=gBp=zcuw<0E8Y?i$vI*r=rICW7?}OYJmC{_+Qkc3mNJS=x|@=QCLQ4XSzKHCkXnDf1CaOL_oR7Lz-o=uWLp4rQ^Pft~Hlyv%wgp1At zGhkC0&JS!EA24cb9a(;VgOjuEYzP^J?!kJR*>j;k2P4m_sVWwciK4g7^5lHqufOJ|)K9Vqqxx3LQ!$Az0*>Ca3#+;NABHOOxDx4Z;pLQ~+Bj zX`0mRjU-Zu++n_>JzQN;QSr5+!r}V30T`azdoYR$s<~ab5c3mhU@Pbw&JJvei>w#7 zNC6Cc6<+xvB#09xNbkX41uCVvI{-H@`~n+|!=M z%$f4c%roarC*C+4$!?%yWRQ5qZ2Ab^Sl-}6v}3&-7fViG%u|0d^fqIbKQTD5;w*Y1 zTOwM2bP@A|>0(otNazkyQx=A?7|NYKiURxixXW2k<&>K}gE#g^&%Q-X6osaGPesPV z=)Cw`6ZNE6Kr}lbaGMHg5WGeV0Ro^g>AwNcA8pGYKfLu#P!Gn|g10z%mcH}tUfUOy zqHo$z9W$@Key~_(?gNK*WysUljk2%%341*GS$(#Hx(c4hPWpxO13O~+?J{*?ad@~r zZl1196ZhoL4GkuuFSb5Ct`yXv!owlrZ($C8beNw83XO61C_#AgUjH?e5no=y#!`p2 zsig3#Ie&Oj9ufO}b?iuMZJ10R3U`ZYes3da55o)hSnJ4_3B19mZ5XDEEN^JRuOAA5 zWf@BL27HQ5e7yHg+qTQN8ORwdb=n93B_Q}6dtcfvaAFHuu$f%gUZVnl}sQIgxuK1cprWfhO$gdDsQa_rN3Q8@?cW)G(1@E#UAp-u3 z{BJ9nm4>whsKDSyRJqf|QwMuZA7+|>Js698@*~DZuUTjzZTP&lOJpcFA6+z~e#5H% zaugOzL=;lkV{o2-o6dDDpSF6JPCGLmdM6HA9fRuPgzIAhJ)Xc7$&|2f?Pb1Rw8So; zia{}oC`4K5TH0sR!Ym|{u1f_rTG$U?RLJfT5NUXSJAmRMI0gf{wYO~e({JR6zNR5u zLa_L`EsR_IMZB303nS4bI*X*PBk|f>QC%QWRyvL&yA7W?<#O~rc5-*27nM?=6k;AH zzx0KX)cf$g&hBoD{qNbsuRj#@fi!&>yqrFW?Zfk#Zp?3J;RFHK#qt|^R;!GB-RS%_ z52)MwroXA;hMwm&d@&g+^Ctn3b*5uCj4nLQrH6EkdZ`yV4CW9cjtL7vdP_R%Y3fMe zz8^~++oJtRt?04luo!H@aZd&y!9TxIisKN}v3Jw8l$W=(q~>V|u23{uu>9em=LF;ce<5J?H)Y`w|R^p%Pr8(lY%gZHO51}E=U>>R>79#FfnR+1|5^r=^lsgK1X1@Ix4`>sujoekj; z{7ZPz7ro2JzXhNHSIx!JiRRfnYiy7^sxKE9@XE1JD=6BO)FJ}lk-QV9|-rwKA z)*@FedN%-+CKyW$;bb8aj!iS?urFa8dm^&?_d)GN%6nh=>qBD^5}@<{Ev!B%GN3M`FM| zPMQ0&D^td&_q>cezt#gRjX#&6IWKqxU5KiveqHgEgQK;rrS+NF+&|%RpkHy(6gdUI zT29Obri5C##6Yi}rYB?v-=IoTQr-#PpV=@n-f;O}*9??36z85e!!bL9xd|`CSm=(y zEPNuB9M%$DKRR0G=gqAw$=&;mWOO|`Hn35ew_6&qnwdWnBC>dd&t!ksdoN5y8%w8U zIC@c+v$r%LN+he8F${M=bu+5&#l1x%Z*Y|s6{!@JRA_JTz+05<-P|$v+8o3Ny8r*d z(1kf~+rb=}R$AUgDWQWjSP&_~#AQG70aH$PZgzGQtU1fy!y*EqC>a*wl_w}y<1sS1 z0!Rk;JNP?BLKRIn*LDYGus%nTySryYst1heslyL=4_yh=GT@-hF)NeHNzFU9K z&BsribY_O&JT^cR`Jb-e1^zHZn8HB@NC|(o(lT<_m${w-xs-8q>h1f>;Lu$ritin~ zYLgcG^6mhrxM-yVc-MJt+&2tUryR$Mtx` zM@se67BnOz?z!oTmOGv2?zMi={{(ck&CeGTPrR5S3Qa+e0~+eUiOky~w`HD$PrO^C z?8G2MG~VWi5dBE8Vy?UPM?S8SVRh&3ZQqY})QU4d2d=yhE2@}3e8CP8!M?|Zs524t z63AlxRE)J4k?vGLjo~*QT(MJIgRgEVt0zlEwA%{lW3^uHblyq9R+u!+BR`B;U=R>7 z`ow+aa>{6(AZD-KHb;KIk6m<4@aBhV9-MA(1h(Y%SZ$bn4n&P=j4j9|k#H~k#fI&8 z!rAD#H4t^0jTfE$jRQ7X`Rrb~%J~_2^y$mMJJ7=z)s2&PtdoELS!d!$S;~3M znRjvRQtZrNN7e?Nh}|ny3%EX$k%vVVduxYbtYJgP6gSV6|9;Z7od&Btl2$afD+@sw z$8dx*g{+paRO;a2{%x7C4ZvjhZI>$MhqC|rQ+GAAKG~}rhott@&E?mQgtBl0yQ38} z8~HU>@aFV5TCvFm=7C@Ktcgcl6zGrsD>Zc3$HVooIv$a3c$w|k;QbhdQ9-A}-fx0& zc*h88(C&{PONp{j)Y~}yrV#DI|oa&U4W``#^==IWW3Tq0n@gG3n3# zhxo#M4<^Q(jq+^ibcU<>G8NJY68X3`p$1yA=#q-TEdS~k8$8zL8~ri@f393qx+#1S zDb^ELo1>QcAgVBg+ief%@bPr5wse*p>Un8jt#7o9`FjFL@&B#nA^m=oF`1y2ugDp@ zeyGjSa1GNP)cILe9oa=wUJeed4hcuE?YkX($73J=Pv_6>hc%)Aj%E_M%zCPMQHg1J zE$*3XAFYg*Ti^YrNam2)5*|t3Kliqp)>nPJCk9H*A>tcswGL&FaPn6&mc!RGPPA*~ z9h2`YdA7#$^flj~T>+g6Wzyy4oS0W;J%$HHKb8N5Kcu0_Vz!r}g+*>6-!4-#`9urV`sZ z`YSsdy3;_$q}OjoG5C-%_pz{>;!39$b)7;Ci}CuMyXBAncR#N`sCwCZ7*fFXBbi;rjXo86u(N~)(->PV5g7S?kUP;O$z0V!ks&xSiFH%ttB&zlpho8XxI6Z2A zqc0Z)%goSdk>d7OUp%ZPvM zE|xeISJ|<%Vl#I9qdvrl<8h5HBKj&@i*>I=hl(-5>OkZrIl&?t31nLE= zn-NT?@)mNLmlUy2gIPvLS_Un;Hati!wPpxw76J^NC0sS~+~ZslFrRrUm9`aIOX~dv zp6DE$zRG$JxWC9FmgV}Iz5exAviVYaMVlnVz;=ff;ur*b&Pc$59y2$xY8Dj z!7j}mFfZ+$z^q**71f@NI1{5WzhA)t>L(*Lj`e%fQtsU%>xyr+3egr6vFU%7n&N^8 zIRq;suN$g(YxRV#P2u~L))(X$osOlYfNJ_m=K@y8lCHK$-}pSl5=fx_)`{@amJ3mb zC>HK|>F>+UrafJ^Es>qyb~YzGvz!NAPuf5ZwHlfTAwnCJ48<)nK9#l=3O2${woCEV zW6gE~D|U&(m??wqTQ|d6e2z)-@)%vW=fp%=N~*RIP=`7_O0$Q#apX;Ra>-@xt2IrR zDr)fjx#M`y%h6BSpu~r<>pEJxlBETfosF=uxrLf0%Eo%%MYi00=^N@4xqSw~>FK1& z;ZtEEQS9$ z>SjDB`QZ&-XD>i?ShJ(^WwgBG;>e1MXvL2M-S^$~jE|*!Jo23>V$tL>eKDVmfXUK- z2$?W0NDGYJ3>jG(e9y~HJW;f(v!w-^*6)kQSzr^G=3{bg&Xde{d*p)<$KyA(Mt}YO z*%UWK)Un+b$!vp6sP;G4^^W=?BkKNe18XHpKJ0b4%Aw2s5Por`cFGj7FaG384)Gvx zeL}s;8*1^=sC*fiH)gjJu|RsQyLut4@l1c*`@-WnQkgvrE3mx{1#CTQr4uCHT#El2 zX*CsMvdch6GWnbh{N*~%K{7qni#wU?P-qib4^pfuj`dzHdD8@!E5)t6kV|qIu+`%F zrIFSU@Blzcv-+#P{0l3Tmuzdgm z!TtT&r$H5m-*HL`THOr$OnUmpVy=6IeqUsa0d#*9AM!v^Rmw{gDq60a_Q) z2AP;+lm*?}aikron1ZAzkYss1w_`nruiwsw@_5YExYdtWvq|}GmA7c%Uz%AX%G^t_ z1;KW`u`l?ZShAQ(u5(a}_r@zM_WaDZyge*OYO3BnYLzvQTKa}Fb91TAZ;56Sw}*@< z8+*@4rF2f1QY4FYLGHkV@Ct1Rdg_nrf8G_|_bJ`}@KgGRG5h0e)@{~2Ucfp2CtTyl zFwz$2>rvAUUTy&~;fO^B-J#HjR&!sd=RA|o&vw(nJ)agttuMTzY3vqDAoryL8Oy*5 zW|hU<+Yh*6YMR6W-T72r>vj_DcRtJSlzH^kDjAU?;xHw?{>IqpW9tirx40+2A^j6j z6e8ykxDMdCY{mQ5wE7}08_Wuofl1ph1-4OMmy zGBS}EilQH?oTHZ7%e2`jojJ~hro9i-fwpzG3<2yLKs@6+T6CS@Na1jy>4$)nn zbZ9=ii=opi-dNs#t9q*C{A_zGRoGtCwm&xiX|8G-ALTJ|NC>rY$;FP(|GHev2^+mg zijhD&%?CD3u!Kz{l&zG||DY4F|K1F=X;e~#UW)R3wNzGAR8+f5=+)!SNTC(@R zyId9@_yF@nx{MJdWP;{_`ABe7W}B9eYU*2J-mY6_C4@YKal>elX7cDyY5+;+pLEMb z5nj_S<`O!wlr3wofTNQ#odwonS8Y8{ei-$jZHKUFrSpwbuqpLZ1Uqm-*UO|eGo-Vy zrA8(L$*MXpFXy`ctN_V3VlvmJp1^B*a_Gp~q@fF9ee6npd9(tosN%eQ0lU+?72Csh z&CIq?S^>AU5x(c8HS)a+9@M!rO2&~_huki$Vzsj{%&|V}W$Kb`Z)AE?U<8dK?c*-F zFRWF!&8{nItpD}f#^`Du@9F3~6??H2nJyDVLN64XR9IVJ_Lo0RE&k>D{a7b} zsVi@vRrUp*gZxmik~B8WtsI8sENsFxys(+3J zYO8TN37wrpb|tpnEtx%cL>;=AS<>k-- z?^~2X#KbdP=kkzPzjP;P-djIO%Y5dYQ--cP8(mphz3 zrhWnb{#w$()d@9^jO{OP>c)LY+l7ul3TgR3(pqZ39sejb=kJx3KQh+<#tBIs_b^P) z_!G;%mVUc_NG!lzdwXzbNHVMHQrhxy2%C+_rI15n;v4j#uN$vFTgX^3o7~1I$E#MZ zp61j6UAKRVdZtG3{JoY_xf}A#CVb9(u6^aUni8Iz>YG&)f|rO2U>o%?nECMenZ8jx zEa+;nyzv-ze*5w?6+q0p>2uTX<{QoS;pp1xx?6D9fs6yIr7M^BS%ztmoxgh&LZm_g zf+FU5IFTpzJblMcJ%Ikvzw-odQ3>_cjpm2K#_i}QB-hvGFMkRQ*b{e+s;R&vd0rWZ zzv(x>#00(A=vy*W^T`i)E0lQ}4+oPH80p|gvLqs#C(#?)Fnl$tqzHAr=|m7u!z+8$ zig(S21CPvs#lg8C{ z9^^FxTl^z++q8fEI7#ix#7t%2;YS)VISWHXQnSFt-aDq@BV3_DzG;YEqIqZA|SyCA+Hv ze4cwwlaO78aQP@`m4JKZW=@QwORwRJW4FQ5{{DKeh%B$0W6{HzZLvs=xxfdENKKr$ zG*Txf@}3<_#he+va_D!gXHG<8O0BI?!h4OATQ;5zet)S-?<&`4GM-M~z*xJ*^Jgor zE%D@B&bc(*!-{4vaJ!hlm)dVVN$b0NFr2BnJi?)aOb--HZ-~!+(i=XYB0kK!R?ygo zw2IRI=EvgV{XAZ3j))6MaeSh#_5dX+DIUpxAXc$h&P$2Lk|flSFu*A8NpL4iseVw} zwK${bzo=bZ%KDxC4WPzJAm^NFgv zsqzr&LlQFLdhAjqZTb_>IfyRHt{MJ$4yMy{_jrSngd}e}A4{(E?e29S$8n(fOe5?a z+UrN4r2bs(xxw67G-*{9FgEArahWObhNG~l(j?$3K5CNzLfMA#402?P2?ULA&T1dr z<9*dUzI535wus{X0rRdl;hCABbkAN)gO`p(1DT)1_JnE0fk{Vtx@mg)Y3k!}lu-M& zwYFK8iJr+I?0ejwJG%|XH*voGQb_=UbkL(xp{|=^FqQNH;XJNTceT5hqTaq^)!JYm zcykz1xb25-cYn*LT!uQU_4`%rp48X&^llR$RnOfDzhO1O2%S0b^1$2shcznCv`xBT zv;Vt@tJpeF-HnV5TUr!2IW07i1K!$csAZHecugfR@0WMfxF9Q%Sw;txt>szE*StXMQqWd1#F}8J2z4Y@%(F^H&YPFb4WZWEXQa zz#Pg(>cX6D&(Yr!A1R*^!8~asJg3HA99zo@*N`#qO6&MH$Es`0?KyH1-@Kzcn7Ihv z&W#Y+Qy$!H)O4$pSl?&gbrGqd_YY1e`u<(}WVhXx+|&LUE1b=ScLm8@s8bG1PcHk& zh+n&e1@f@T+9TsyCW)Q)zB$|PDwgGqho{_9=^)!}rrGup!MzuYHEyD;^78B@hs5jp z3f{8ZvOLV>aGe=XbgEa=^wQU2cpo31@_zcuopqrrzBWUZ5k+Z_V0cpN({U#)!Cy-o zgX^_{_tk*PJ78c44;lrSMbIo#uer>+^J$# z^oRGVqQ~bN1H-oRAoIOMr$wH@lc{-{*;Lme|MI6dP2s1xLjRz+5|_D~6$dLH5B*2e z9tiunqX!#Aj+&?&y>9sU!L+y*M7fY-BYGJRRv_??Emg{}T?*^GKNDdcXsfA+>w+-) z_veQM0bP@U8aoTKGzFQ58I_Hn7U~`B3>uG`PVHHz#5fSPM zXQl!;4_z(k4a|6Q-!zk?=O!NHe8S(iK8HM+Tk_1)A4cQf_<`+86YkD_Mm8dVtb!Rh z_b>emcYP18gZj|BxfuN&{4fhA=TBO!#=7u9T|t57)p}JMT&cA$f(IQD7N?X)$FHft zjHT;B{?OkO6U9A)o0+AtrTyH=74&vig^T;db`tw3o>EbED?|7lXkG}tK>-M5@uHHP zK5vm$Zq|Y?XLmy-#kso@9XKmZG|tY)cRzoAB1rP|bZNUus?q(R$Y_?KD{HkxEW{)9 z%xpq%7;^8Ko03YAfp-%L=s;absWZ9mtUirQcy0Qq56_C4BRYtJGI!!t(`djU{|dws z;tB94fH=hQ@4;L*XI2*-zCVBVNG=0_SVUHN0(T$Ms(xs(3sMzSBTwP+yLQ6X@B;Drwv3X%8Ll~3AMyQ7!h19 z*Va7yJ*1sIpvkCtJzH1rEgipLLf!~iABUvtcpHs+Fc8?w36Ph?iGjs#+($Ai!hnWi z4OlR49NZCCf4fmd`vj9!Y+iZQ8e;+N2>E@PiWg^o1WcfIDzhPff+0}K;IVATHF!QT z~Ru+7@G|y96e%njf&5tp8UvxhbeK`W_IwtVH%PaV{g=Vv!WOMCQ_KKA= z{6owEChM=oBT!2%1{g{Tiy3m~ZAmzXMN4b>SB?gYL`9WU85dp}9|kOEnbHiEolZC1 z2f=<|`skud2qb5&L{Xso^;ecMpx4*qBEqxZ)4r#$E6^w`q6v6G*kOj{$}li37-rel za7*Sik!c6s2lS z$mC}B%SRbHxsvJJ!udbz$jVl$T$L9}(Dt{6wX>(lLK{L=2rt~z$$podRa=QLOM0_CInI;h02K!ZaG|#iS}c!EM(16B1UuGqRe>Y+i`=M2WrZXQoE}I0@qd7`Ey-Z>%|-g@N5E5pSFZ# zAbWWw3G-A4ZHT!zxfhC`T{8~|P!xE7Gx~f9?XTt9cVA}I_zLxfy67-+HxV4ZcZYf5b!au)}5d#oY0dHk}u?QDDe_d`*?o{q%fUf9A zMrTF>?XGR{x9V$b)B1_ET{(Y=Hz>sfHX9rNvag~1jSVlx4S#k&rkJcoDST%s12Q!g z%LNGgr}#)4ZcFYMB1O{=-yXDneg)*DA=QO)aMzgad2TGtZ#+ALk5x*9tEKOY-Nbx` z7h7J>vHi|vYJCHkHUD=$m3Ut8 zv@i{n=f|M^c_EJB8a?TT=la+fGyK6pcg?S;;zkBZx5L~jdwPeRs8_LwBW(R)btB%{sLP_B5LMavlIeYbMq;FcP-ocXpINprMPkJ_#B<&35xAt}a zoQIp3Pw{hbS*E?`q7{~Xs=-nAftS`_t_W?9#AArpezH_J<9Op~%TOHk(M!4T#?#a3 zaK_X1Q?b@3otu+w{97x-ZVUzIJ3B#Y%1+ddi<6G-mw6Em4PHyaHTEE;)vD|@a~piG z^N}|4fkPv9eDOAqg6+W0v?hy9&NEL_0G% zavUOZ5JRxv9jN}}ebkg|Z!?IjbW`YMqbPdl+Db+82JE+}W!&L=G^c-?Va?`Tw@{Fz z-f1TLhSjKu{mdLPj^xo7Q;U6fmzvcf6RwNT(-`DmEM^P!C(g{tZo$QBPU36O)dW{u10T2kwpNcL_4|DD^mM=p-2{at^(Ruu_mDnIOXoizVIdtUuZAP8`-pz4s(UN` zD@fHR_kCxf7n!ctQG_otf4fNz@2OTNj|cLmkI1Bo-GG(yU7TxE*2!Sk3R?1oY~2q+ zw@7GjjZ{XfJLGn+0G|W)v@yN)331+TtFM3Wm%^aMocVCnzM=mV6XqWBqbkV~vdwb; zOB>Zsi6fAWcTRHz>z)xEGsN5kb*@U)z%C$A{`4VD0D65%@$7A*3m=Jn; z8PAo<2LSE=1ea87c~-0{>ZIlL`l-*GLHij>=Q+*Y)s~!2=z6Vo?!1rrzTg)?=MchSe4E%; zK4=r2DexxB%3em5Y~f)$cyY#Z7c=p!pz-8dcm|U?hkA&C*Da94dzSz%%nIO?Vown`u=ci770<&1Nmexr3S1AK^Vgu^Ppa@~9s0g-O9?K?6 z2g))&v_9uyy@v0OAl^r>n8bIYhQBbe$>bTp1{xn9>$V(zrVbsC$b@p1+1>dmnDO#+ zw;e6m=dSc-n061n8D$vmFTrUBb(1N`vkmv9!z2wZJ+|>&H*HyO#p^u{yKH9KFYPPnX=EBgH}+QVUKW zP1gMgwEFF}d!ND(TWUm{5T-A3t0<{nhs9(Md^3YBN}OdaGR@@sWcSjZfmFCFTw+W_ z9Y@8qndyL_W7EcdsNSdXbdF?-fqtF0Iy?CKY%OV!b{x6nH8*HYY9h`9?5n`u82NR#EO+XD0qaISS5x9S1DCo+Q2j1HQ6; zTsN}T@1DggpUaA=v2A*7B`+Ovmz~I{98oa97>m4_O%{+m7a7GrB?;gFb5-I&M~BSu zAVE-T`NCD4d8oGb5eb}_p@F}5x^YX)?I%l=6){c>MH;dT$vfhY@98VH75Xekj%0E7;b1E%WqfzpRWYJ#eTxoLH}v z{?twFFF%JPLvqi1%15%%4Yh%P{D3-ql;{I5!rls-f!A;VVwd?ed8F#2 zo28uWsj*xHEo|;NNsSmes{~EG#C{0pIc6D3*I6_tocbqnp6Hcsf@(L&z!0 zKW-Ksq>2eBvftQOTR2ObMp9i_T$t)WAAdaf=-gw9-#tY?+k6bW-Jl`qWy&z>(%|h3 z-kkBo&b(L`v~c^T$rOcau`elETH29hb3L%^mo668St|6w1l~S1xjTOZD^IZ#Ic_UAjiLtv?8q;(lSLCx2;p-s`u72}tDl3JoShYg@i|OwvF-&r zb{1c2AEpe2hFE+7wREaV#gHu05Mz*|stwf|AP^H+rC~*^+)tBs`oZ0oYe8AQ`tFr7 zw;Ijt?1nA&=Ib>bjndf;HaVtYQ3^T;flcyJzLutgSnSj;_Yqtz+>RqTh2Ql@sIOW7 zhbh$!rWbGAJ?+-4k(5{k7^-N5h|z*TMWH>{4_7rXc$aO$DcD=|hr!u$GTWJ1+L~O<_i;#_Ds9NS->phDfuKq1O#bjD-`PD}H%-2&|+DysDpuB9(%e^4pPdlM*slb>g zK`09)+jq;)ibP-dAx|Ur!?J)wQtxo_7f$izZ9Mbwlh4?J@>&yIYgAwldu>HMd~{EX z-TSA;UR8*ig5p+c%`f`$^N7jAGmW^y@SlDzbNN%dAAJWTOOWu){t*Vv2O+tQ!C~<- zseSLJ+4t0ATUsJK(gu4Jb==b=k#A5wZf3KJgJ^VZLH%pWlC#v4Ri$YT{`Ea&A6y$y zG#Me8$B2KIgE`4?0FC3i4nmu!6UssZhV*Nnk@jc14jt*oM8&l;?}7$tpC+RGfd{qu zsc!;xrcYkO>*Ct==c~h(sY!R-?y1mj4Mv#vtUOuZ_TDed6l6MWxHtNcN)86uEQqkR zk@WLCTML42ved(Mo!Hx+!`KyN%R)D3%ZiJCaY@n6C`($2=car<>1^ zvd_h|LP4Ooc|Q0U7Z6B*YmD%NzDV7(xZN8kV4ZnFJfGX9re?X4aqx?_z98Nw&bMY^ z?4sB2WYohp@athAjvN=gC$bJVUn%Dt2Xt*IXMLb zT)C&RUyM`Zg?726sTXN_1nuvNcp^mab$Q=${c7RCVgGyYcJa{IScIc@*6Nsp;^gAz zKv(dp;6g*UYen~Y=4kSR9vfdFl)(K)HS!bz8d(vqX>Q}%ikVhuJS5c6GyRd6tF zs(E9PeRs>!H4k+>P+RNXX3^i1#Klf5o~`ehnU~<^Xwgf>B+o_CM8AtY+i%m!vOkV?CgL~m zcz#Q{6INyfL#<_iRu8QV;KhvhCN($+1!T4-S}*hC|IFKJ9+l>^K{3VVu%f z4w-5r97gR4or|(_k6d&5x;!jO@BL20?j5*DNuUhRR>moUEM1Q)5!u=hyj+6b5C) zt-?KnBHPf>hvhh6Y8G7;mAQk`_t7QCF1$hxCpXbn#z$g&mK?g>b|pP~#&y-*>R-t% zvSLbhou!yviBS`=yYveX8|KXj*Sh1rWcbFdDznam`D6NtJJumjY~-?mfyp%jtP`W@ z9SDX;=(|-r``t`$HWna5cJi$n{~`p?_6L@?H7;fY7P?FSZ!F>helHG@+wnTW@szJBeN&-zhJSn5@}gX z<1cVxec|JUb8AfPGjVe}@b1>MK;MiaZ|`)!FD@8b2xa-2IkM`t=uRf_JgDA+Y{)Ua z;IYDhXrb#52c?mMlJ72 z%XKQ(g7<`zI}V14T{ZhswmDglAD7qF81^?tjC3ByO_?Y+BsG6*7HnSI8%K8Z)#%U0 z@!}Fc?l7`MxbKr5Ut z?)6;43AO!}OmXi!1rD9;%Y2@i!MHTMYX-(Cao2@4t4jH?Az%HN{EZv^!%*fkuGqJJ zMX6fM8{a376Fsq>_^LSk0Y7FjhgDXRm}J{S<>Z$X*@#$ZM&2v|N-dm=XKp1!u-jiX z>#U4NgiV`WtON~Z({5b64nG#Rxtqgcigcc0nO`nB!I?r)&zoqjFoD$RBC4*X2emVx!U2PX|6wGq5nF_CS?9#3vaaD)@d(O-pt^bhL%xAt!X2R!{s{~tj!dg+ZSXd1Y0m_vu1jLk zuySh7tuGY_@`I;riaioi@2ph&l<(NIm3A3Q?yAhU7R6n zJYU((sviSO_OA*EfMA&kji@_xf!`5eP0+zY)k!i4;i_i=G&= zSV3f&4rv(Jli+M6M){6!*_3?$PT5)_u5>1mDRVQfH(MFORr68*-sZxlVz;eK`Dp)5 zD5B0SvKB!wzkoTnq_jSqKbXv~QBqQ?b(6sx#+%oQ{;v7XmvQWooBbIuiPsv3?Kk_2t1V!4>w zC{APjD6v(4=b$JOQ9>!m`M8ahio?H-mWK+#o~fQ2s2Q#1Cd z8ok6^H(r*F9Da<(GT!dH!>3l3=>yQ)EXPn7w6EB?k(hBpOZ^MXP!=;}yRDvui>U$K zcv$IuazZj>zL_zlZQf$l@TmQ4+_^qA<9N0Gn`)toE)ur8%H{sWYIh7*dw|?D));hO zd9vr~5I)8IdS+Ywx?FA zL*N$5B%?bDCb}M&o?;eBZ!ZS$(`wWY8)+DAoS%erK~=*Uf@*V(eLm<|z5Ds|03VQ# zdi$F&q~y*?Qi|(*d(yUrinK)z%BjcXsj5v?<2FL7v0~;g7O}P$sCkXrl?rDSZt)V@JmP4z) zL-$oWw+7UUZ~Y`X=EFYqN5Odp0@7)UDxTJ0A>SybZ`%Mn>mrMb;EjTY(g|g?^bdv& z-Psv_sqYbppn_$S2_2$CNXjSMF>!ID)hGbBo<-O>x$M;EI=fR*r}Aw&XEy~-LR6QG z?Alk$e$=Qi2F$@GG8-17!rb>v3ApcZG1TnVi5?BrGDJiyX&4w7Xz@SlSgiojMIjEd z5-Zkh^NO>D%a#{nuxtz*#ZoE?FJIzWk2D;r)Y$jMg3@;RwcH<`e}4A|s=3yz{4i9b zEF?*E=r?BI@x|_>*%Y|>V0YsrP(Y_<4x~kWf5gEaY5u+e8;(!R?mC2V{ki_RL_{$L z8dznvU*ND0g{}i{tb)DDFah{>mCrU6m8Zs!cKYsmHG1!DurMe+%m(#?O^i6#q6B{J zBP6Gt=a;@Y3`>a4$RZ=5njMM4QjLz#p}qoR`gnDT%%WJ$erMn%NN4#4j7Qy>{TT{) z?@Yt8$MRgtYGb0%Q_CHW5}9P@9@}k3p-GD{rs>&iyuJBx0x35Y`2ghHdA(YBp@~US*9x z+`*n;be_>ZADE}=sT%$@tQ6PNMBV93ywKUH%;g4IKoh8X&$9`gb>G58#eq(bjr1(8 z6r!9$RWtgV?;D>mMaF_mDAF8w_J#3VK@v*t?+;SqP+AuYUcGjatSi9G`q8BzKcpV( znK%qWMF4^9r6Ra|C;H#2ybHYMgVC{zgT}`~9|4q>bPYghGb}(;>$!u>0L-jGAr^)o5ZK@yM6x#&r~;E;j#~h} z9mDuwkFB#bgL%H?%s2lH*NC3j%k%)*TQGSH8|;a$m;VxOwobL9799(U@05Wtzqml; z)Y$%35?3#a#Qjdhesn?r0*RWP%dQ_x-6%M@3F4P{p=ftz+KKo zy4Q`xG6+9d$IQ~BllME(!u!s`xj{vnvWZv#QvfoVLX(@m>3Nj($}ClV#^QcGmbi#2 zS?KRv?wv?1ztx5~#H1;L=aU4pp~cwLTcrj)p_Mu7?%_FDJDb7l8*? zKIvcF7%8@23F$8jr-;`vF>CG-eQB50d7OuZtqmV{WCAUn<6j4?Uo_KQkA4`p4mdkAGY$?x`p41Dp zY<3|POlf(BW=m*3$eFyK@bBcX&Rl9aoeS6pbs_SS*o~4|tZaypUWys%FVO2+x!!0P--bmryv`ChJGgsK>)?qLq zCvw>r>Bpj=C;9$n>pRke%HsqA^$~BLgNj;=*WV^7U75)}1FO7ck2foeS=$ZA#otJI zD4amChb{4XJ}!BlK>gu*u+4B{4kD(D-*}Ogz-~E|Owy7*cKn%RWCv5`!?@V5Q`@%Y z_ZQ)~l4_Z>S*8*Kd9iVQdf9n(Gw9#LdBd!_44^6t^<#-b=jc?7!e#99G*rvA-zHAa zAn;kx3Vk`K30F1|x0Q3GM@BVVIEKu=R~s3|uy-Dh0@a-X`%;DK0FyZia~j_`KKj_z z;Q2W1#gj0N??^Xdj~YaK)Swtdg9WW~H%dBIj33Njf1*ibZnFI524B?fU5RsPjQh}) z)|C+jEl8&^Cu51}I*{Xp;2nhuEK8{J&lhup(YwIm4l3_S&nGwF}i0Z*S)&;|4 zo9>-iB-3c1bAOAD^O7l5wp-6@^O>e-ds2p0EeZVreWQCe)ZbJTn_|UQ1g^u~*@li2 zfk0_anAvQy{~??5O!<| z2I-$s0=u?(qS7u*hDuq>KY5ejUK-yovcLS6F=?()e{5UK;ud-QAUKRgVp~$~+JJll zUH6N(l_F`NYAB+Spc?8|RG(TN#nuXKvufN}Yf4bec856M1J?_6Ip9a?*OH(7>+zn zwdT#^mMb&WZY+(hXcbbA(v$t-Gk59p45k10amjoxVZNbWUisV#kG1)Fn`QKM;}hVf($sr-zk;1P0d3@!j+}> z_?50GjbYScJaE6}Ss?`?_kQz>0|{9^2=Ru_+O*teaLTrlK|r16a3oe z$=T33e|5$nOT{27^$qzT5a{3htnXJ@pgiBi{$IvF!;OETDtu4GDI+}66E!(~H^~uw zDLl;@CREjiWCML5qr@h>IgWYJ+0AD&@A;E7^Ahol(YcY3+iPaDHj>N~Ok*{> zR@m6?EZ%jk2idV%Vy5%Y$$8SK-dPHhXTNE~?Pro#(Co6|2&IR;f6n1vt8p?h=-$T^ zZ}kF{Mv$yn(?{&K#EdAK_k5FmqmVAzBtf%CX_3Pub`Kd~UOJ4>rYia5E(#12~HBB`RqLR>%-BmGndxN2Epc8yrgKFC;nSLPn|# z4_qlJgw;l2oeQd-2OfJBkO%Bl=+-a3y|+72|L0p6-2Qc&Uem6NG%nQjOIte!| zXuhy52+OY;dea3F6QS3Ql`VgE@8yXc8VCe zzIw;+;>q))a``cqfJrs`s_WV|jNgmJycJ-HO-h57-SaX_*1aG9g?vymWqb)Q9m{H1 zXDYLw?jZ4AEdMj*Bl)R=wCag_sl&Be~~v=@S`BiWI0E!^@S((An{h{5J$L0 zw2}Gd2f^oJKvgUAXuF=!r%J@%ar?^kZ&JTBz_zNsC?rku{(`H<(BCf`7WO341K@4w z7?+xwn3zfVVD#qf8osSAm3#5@-=7-pvsB<01 z9p&W2t3~P}PAv6#C%XSL>SF-+yyRL;Zwv+4qmSu?eI3VsV33L{M2`5eRo{{8$;zqj zs``-Oic<7P7<53Epe97a9H_Rx5bb10v5w5Q( zoc(FpxkA3P5{R(GYH}yweaq=Y5t{_58wepmAtFYJR|=B1P!T8MBlV(v+eY)Y-0Er zF@m>41_0P#1abow>IJibLFRi3aA-h(dBx$|z~j!XG%7VWZz#mbK^nmpkr}IY(@(LQ z0ie4*j=sRyD`bCvcGv!-N)SPS`2$1AD%nTgy!G&>JS&FLUqiE7LHf@TN`#lL@Z|S&#-HK3@(AjbNVCpL;J|B#?}L5-ybYvGyc6 zXuzpeZw@&O2DVFv*jtI7#laMuqU6~v#V5Fg&JMrPNTKnXzzESFhm_b3Cm?xx;`+^z z$1qbh#o>+!;1xT9YXuWp#iZ(53hHDlUcPKDKWSSLXM?f~6^LGMZF znfq5WJfZhkmacs`MB`Q76`E!9(-Wb9G)R?Spf84Yyq}YG0)493PZOkrf%S|)V z;ktKh@dJ?oQSFRgoubn~x$wI&1wCY*0PYJIl#eE&r4AD#GDRJOhPFU$&9YPd&;sIu z$J*cxBS_O0a1*6Q^m~6y$>*YoSu2VJmU+(9vWgX@2FDo(d?x=PomRp;y36o&i!Vp& zcU5@;I5)O(*p(O>J-q>a>mFoCJ48-I40|!W{^U&w zEp@_I0EBhAd{^i7br8itagvE3uP2X#1a~ci|B}N&?1xt^J~o7Jpt*Q~{iOv4*Yas< zdpxa@6X!xu2NUMF92hu#sDSg0o66DOkb_5}qH|=-D2!?w1SWuVDNpPZcG#SaP>IYT z#OEtGxg`_wdp{^0{Y|83P#1Te*3H^n3tRx{4<&CK?$w=+|YpnQHM$;y~VOk z)76_O3<=-@-6cc0cN(q;cF{a-0d<0e5GAo`*#n`*RJGQR0P7ft?vkA8En01SxM48F zT%3;Vt^`?&wU05`gHSZA&UZV9c{IfpJdh*QTiGnB&!>-#Ugc%wThmQbHvFIw;7bT!58I zL}}`1Uh~ywn}FWnG+;qvz%cOyD7ej(9D0n?W4{FON)|R?MmDjOEe;sV|GCOgmY8LT zIJngVzw8-oj#=n72Q*~H<&wutVnB`R@X9hQew&eYk($;`<1@jrWGYa|X{P9%1upZ{^O^K)_j z?K;*2cnYq|22TLP%B#vLc zXZ!h)1Brv<_vFe>_9kj(E=anNa-T~fv8kJRxcr*2_^N=|2ked+oU)nLO71 zu@dT@4rY&%Tl_pbnL+CIOBrkmW~Np~;`Sbp5*~%%hlt9<%?`E8?cSi*0@N?E;vC>YWNuOMoAZf0U{3X%GSnVp4;C8XfTM*7(hzqYnV zX07I+9To?+D`9&Fa#uKsXC&6E3Z9n{qZ9awV8YMstLv^VkKr zQ^?oXE5Q#CRg_F_Qo%G-HLp&H{eln8`#(`{ zb(?flsH9D+Y=2G)2kB6d1Rd+V_MDqz)y+baieT1O5=wkuI5vq~1xzulVYFm0c7byj zklBz-G}`wex4u7~n;2;7BYRbVG8!licbF9$E?>b#mzX=0FX!G+*Ft_Tn#O5FY*~*foc$FY{Yn3leY=EB z#Hi?;*2B7gu*2Dt=31dL7etbyS|p76#S_7OT0!%s{m=t$+%K+YsO;aX&DVHrs%>7; z-2nx=Y6S;%53f3BV6mV<9r#3gJXK;JkjaIz)lHy_(haFx+ zx>FtBLO1A>Qm=RMz6D`e*vix2lCTqDTJ7=OC-29ZnASv-TV1~}p#X>y^hbOt6%hod zpXz0#6d$5sH%CE-z!`tQe5Hc|FDeVqw5VKJzTsHh_BOPa>H=XFM3{0^exe`K5}Kcq zJP<9%Rh3#Q}x4GO$Cei*N-Sc1=LY98g?UAnrXGk4rYC9q_ zsl}WirlKM&TXNO%H^pyLk`wf3aqQXNd^ymL-pwk!+lalB(OsOSaC#jm!2$W{(!3&D zGI8M{`EaiDxX87%6)KBJqlpLSo1|nM?1%9BcT@B#GQvg%L8j0MrY6U{RB)h-_cH?% z$k;7jd5FDgJ#PIn_E|7%M;GzOy9j0s`5{=DiISm}elj93G&wmtCS|qKNX3#r}^Q&-uDrXPXbj=O?-=4Sw5R-U$qaN7&sNP z*}|iF2&(zx^Pqs6B79k7~_wbMl!JyaGgC9^QVEo06Z_D6VHrYT@Q z{%hA8?VfrNov!%Qc$G-S8aS2Hs?d#3tFCMYKB6rZCOYA@dy~Lgn(E6?^%R+aQ>fzd~`iRj({b=z? zX~UF&vp7NZycL3fd54N*Nxo{KgGdu)28_gD$5gx{Ud!|PugJBFNafx~dPEpWRTCrh zr7c{fuLs^bIy5D1UkwHOCmG&qN?78zuI??hdLL)N5O$0kEO!nIOp3XD=3|x-x=jOD z#e8ks56!CTT%@|7@S^h|tMNT#*sDSNVsyQ=k3D+qkLI_sgraGQeuUZcWjQ@~7*`j1 zWm=Jk*0AIWuPSm%fGbGjc|LO71n+6kE8Z%~-DO3!{W*uc(pp0cI(J4GGuPz;EE)Ni z@|9sc8p~cWo4)B#Dw8D|5j`JQf~y&6RPD15Fto=qJ}{W}UfLnaX15sezk6*Nqk4#9 za;=$RjF-CVuQ|0?_XYC!mR_srC-3Rs_or0U7!Hmwydj>besO3;*BR&i)~e>_3G-o% z5`vx-imrSpW;m9czw+x55tv~9fcK+z#Uuykysy!Y-P}JjWE2tBTw8R|kgZ*DGexh8dC;t}ZCGu=nJwr+W&41NOfPd4%fv!mf z6#lZ@KGZ|*D-I^O^r@L#4Kt8=5_kMCvZa$t=`dv;RBXjJAxS)uF8-PmECC7OR7jJ_;)o-^&^+YD0|`z5;ps1gJ=#9w&+}QMM^KL&Kf!rQ=n5N`=iAU{* z{F{IctQLt3(X|QO!K?~cXQLDN1Ipa_PPVe2=-BMP)qf>%1D7;|)j-TIj?y zIe@=gr=KAEN0apviGPRA|J5do@2{I<@cns?!S{zb2H$^ej`6dE-}V0Qnyh~?$9So4 zgC&NY_3?6<9EtK0??wBLx1SIrBfSj9%WHBG>{^!ix}s~efTx5XOFx!|i_UP>d^Me1 zpmr;5GTj;({{G2*R9MixQJr0J%~O;waua`Pf1EX_UGl@CL)P~OV6TGXm;5=wJx{M@ zt}L12q)4d{P-@p6>q5Z|;zZ#dngE$wJ#gVF)rrA3L@;X1edmJE=S__+G#-YKpTcMB z{wSmuo{VM=9qdLX_{E7>HH(U0ZL(&=kJqy(6PpXi9@x4zb@GN~z_7^eo3&~nncje- z+R@Nv)om`7pOHtf8ufh@JE1%GCkjE3- z*jm%%;v@hChF=MB<0J=?<9X*wZM=8$AQ|~6*&XG-(QpjfP5se{g8r34-_Pm8L;pmg zWu0)j{hLHdb~$CU)Hv=zn#`8MA38*~=`F=EH4Io>p}~yWfvJlP&{CBNPz3R$SJB64 zJ8Z#q=VKj~vJX4*9R3nnE1k`b8V9C~9>|S>fkv zDOb|QJ(-_Q?qS$u)kxh}e{bobFceis9v%EeEH5Mr|2~UD|bZ`MT{5X7N);K227mVI5^VVH)ThUkk0~(#( z0F^F-WU*0Ctiz{kBeO02=vVi&`7gU~hU2N?-JLFxEL zX zGgxM^dJ^$1!8!SfY>?_aXCmg)0mg`X12;E0MVta+K{}-r@2BbNdQY<4)va);-*X0l6u2Z&~M5XWl2pfO%=7EsF#&3@M&%yjBX#D?= zH_!LidGo+Od-K3Qc=N#jnm7Nmg#QpU0{?+G|8f`-enEHBar{gkyZo6cR<42c;RfQ$ z1C=S@&i#Sv&vbJ?(tIX*%gc!IQSxr(GmOJB#nt|;ZGwWF=1yfbsnX2S&+2r2_p?^@1rZ6hI4`HoE*5??!$*1!eDBGw+VN8M6&$j@Cn>rRC|IrM>^FR+<54K;<|jKJ;)!OgF>2WyO%eGFtebJ~Ri|D!3aN3S zUJxEPX>OqgL6ruEW>LPJMbAE4+s7YmnJX921nW#NV0K$&D@*uLcUoNO6(F1c*U_G&>DQ(kiDX5RrdVN=-H!|vpq*$w}> zU|1wlVFh&(5kFwGm;W+r&K7+v$UF6<#0`1M#mU?|aA$2E_JWC^{$;pfK0}oOgJEgb zr6=HAr3M?Fk!U#hZO*bQ1j<0iYa zzeDXAOeQxwpxH1Yqh-PC5SBqvN1jeu(BubFtEVRi6z9S20baNrkKhgwa|I zmE0cNTH)D}#Z3~q3A8P_%$MQo^ZgZEReoKqnzq16g?NsDylA(HGh$151afKUUgq-J z>YdL?(PcA6OZ1ExaApF9sEM5$a2p$8RN1TsHFha27e{sCbA9AuBf5eSJS{|1;phxi`>^Cu4c zK5+k6`;x%F&X)xK*_QEx}!!w_Gz_|pA?S%ooJo31*yLp}AgVgLhdOyULvFe-Q!Xr>rnd4} zK8(ypY@g*wkbS~1R*nndRXU{{dWS<3^K?8m$%c;h_GJ!zZ;#rQ#9kvX)!dvS$&AIt z=5>xPnigBM^bNO8XYa9`;~9L@g7&hr^)8E^#&XTO@454Seh9de5Y{Z-)pU&%DYg_{ zoH5w_YI`@nud#*qOuuLA_O`C>eE;>u5hktyzHUV98^Hc&%LIiYr&m5D*w>i^6Jo?n3I=_W4LtOO9xE{Dqz-HFhgeGUo}4jm|4N7%ch%8+}yx z9j7{6laRm>G0qp-yyo$&jgc-_h{N*UxP52Q(L!AqA;B-~--JKM#RW06Fim6_mp)i{ zNqpvQRc#~B&895$dpSBK>fu}ZR$sc2^<5_NGGJ6C=8*lpl6oqxoU8Rjew}A?&ixZP zhf2xyx=INoTag^&-nXX?IZBpv%49&4poZNnW@hO^IA-|0XUh)7D^b?0PPdy?1s$nF z9HQ@F>XqU8xtyGJZ)gc+3QP(nCP7+nE%8g*=(2^=TMzg{Ni55wDN#e^N31DLzrL?4 zI?K`pYsuqJNvVqD#ygd0Mq8A8_&|(NBv_I?YU5__?@#$DC$&u1N^SwemIVTHRj(+=(%+hauJO>B=-yHd$NcxLi z`Ww^uGs=v_rsitw@{@UdVP$871mO=MJgF`cA1@y(2R|o-=j7sHWoPH$;Y9)hfvlW7 zoDiOshnJQ2=OqXODQ@p%3Lyf2VJ3fal45_yW{R1(SlQb_3PEDiP?i1VL~rTh;@~X6 z#%A`)%EQWu#mv>o-ocF3-pPW^$k^W1g~ipG&E3+T#oderLQAsQL8wJ%HVZQ+TO&J9 zhF>|!zmW^TzxUyPuggzX7s9zh*ik13`$vuv$;HVPLQ!g3nLaXU(1VCdz!C*i=bOFd9a>7CVeEqg; z016rgA}<2s6C`9vf?9L{G!zUBG%O4p94ss(wGZTb04xR^CIyEWJeGDl?kkISp;$9h2lV1B6<2 zQ8KFHCqeI6%L-B30>0aNvmYUR@vbn@kGSIssjqXqFCPEm=Qrku25NJkqR(r^!wD}9 zFWFW1J3<>fN|p1Fv(Dmq0Kn4cg>MN2{1LC3Ggc8gwQkeS*`D@v==aDnr+klF8X$>L zt8ew_0f^K{x~w?5ve#@CJph(7Q=3VcP5BwY($i#A_72qp(%t>g z{H_BiXPL?GJhqB+9mDQQ4l8&8&qh@po_r>tz5vWlrMpJeWtcq8ie!5Fk*OBL3+hhE zNs0Ot#C9YquM0+Bu;zqgGQ-7UmO$|Ke@2Y)5x6zwOS)n}lWk1A$H-)PZ+{PUktrCg zoVVAqam98?A&IT|)(8FH(LU6PFt&<8Az9+TeCXm#-?B%%$nN2dvq7wuq^$0WhvWG8 z))3+8lxq+FLR|}3{YW#UmPjfYQQ)KMTow?@38l5#W*SVUQgar|8@(vsJ+)g?u;umjr@Jdd_`2V{ZmEeHcGf zzzNvxymPhY%H>ncs}YmLei;loDMS4aKM<;K;cDWz4|L9wPKuI&>M*C!{t4Oki)b?~ zip_1y!r$+x)T>U8*GFpXNA>q2O}-7@`IJvFtvKu6*?BL`gx;aiZd@mc7>Eup9V5}_ zHJ?sG&i?=G5RuLcXFAJ2iOmbA2mC7!u;r!E+5@28Q%9tU?b_mrz0=m6dmhLA10eUF zHOu!$#JFC=5Q&h!i0o~)Nex_7X+Y@#!|kl|o_1ce2Z$lgG+dH#dyVn)kr+Y=81wSY z(W(L^!MQL_x(%H1gl=5Q8)320q5NzIj?5Fu2SA7)CXMdf$!2-SR=kc*k4t#2NGOW$ zXg^}e%HjKGPiT^l^_FZ`TEx_gl`#~K%Wb|7Zauk=J1XTJ^8m!wySrS#J2tI0c_ zyYoz@pjfn3NfM&FxY>Hq*S+T9GPE8=Qqfvp%JU;R zP4_sx;SYdqoCiPxhTpM9q191-7uUCf_?DoWI%n+a_`y-1GU+z>Pi8TllKN8yo?nTZ z@aW7G4V(?qw$3#59{@;e-F|0C4}kvAw#%~5t>fpyA{Z?9S)Mi+wz->c;$-(C<7NY| zUVi9ODLHsjygeTEg2h_yG!Rsp2SFoU{J-}kNF$CURB!mgDw#q0Rg2HuisldFP^E!u z$eXWyfnaBFBKNm`?mMZO3-k(>=2A`s0#)?@uIPEqDFM<_+X^e*xu`G`dw~u2N_=z~ z@)%_?(oDznE1?^dHI0|L!Xh^J2bJbX&o159 zZlW+(m}|q#vgN$#&1urV!2rNMsW436O1`6Ik87>6pB@wwh^U@X(=9S8ReE{fY z>vS?Zcf@z>QLECMy70e5h!rD|bQDFug~JQu@GSoLa;|Jedx?S;l+6pp50l$lbr2(b zX}eY`B+U86`o)A5CLcn%Zo=&|cLR-EX?}!7ie$)Enntei4A`6k#N{x39WV|_RG`o$(8`oSL!csk33a#lQ*SNu(xO3YJV)ywCQ~~^D(`WWE z%_O#~86<;sB#Jm)SaI!S^L`q4V(ab*?LuPtplyN7I)O-CT15%emy-JfuLJMNmjjq~ zJ`hB5IMz+3qi^)z`MFUP(L+M@Sore|wXc4B zgKTB?_hJGdiPqYObAr$Ol}#%l{vrz-HiY{I+up)0H8JOl(VaK%0xuiSZWjAdRN9dw zk(eE&a#hfc)KLKd7o6oyf`PmRMuEXL$$N|OjZavWNjlfk^_urGYeK?Dv*HulW(`rb zME?1-Tkz}-ViW>~Tlsc9@MvBKC3&sJwO&aZ!>XDVGs>M4oxRD4s*k2oY6W5z5vVF} zg^n)3AMO(NIa%Rvm?h&z;T$yE^eo)yp$(!nV6%3Tdy1`%wM5o zLXAGNnY|T0ik_p<=Gp6cr~LVfxA&C?>^d%$Bdkf$1poC+%a2HJrWF#P&i1`zr|o>K zR_uTxJHa}!#Rec=-UZ0Bp~M?m*dH8`aVrYm-KtWeO{6;Da8_TtgjkH}Xu)Tf3Z;6@ zsy3P!+Rr>K^F>UX`4mM|;u1159eZ^Sxf53WpgZp}8}quEtIPUo=IwnB`k3?I;`a^* z;UgzZG`Dq$V%9Eb>o9rZDb_PPuvAl1k^37|N(ofXudlmRQr377E3)|2Dh^iPw+)S( zcK)G+*={*{GfI#?FvpG@`rgzP^{O>+55y<3;P_6Aj+Imr@SFiaaq z<=|EONwf>W_1vJ#Io4;Dz~mivGM+YNoCH-VftBP7dx5)1l)PO6eH_<9h(b5g0K%tlVK1!=k2?s%wG*wf^ zOUNhq4&(LaYF}cG5y;~V1TNb;`C9R(hAWnrkT-G~RDzQHa*0jde!ya#rP)~!>#V;^ zxV;nYyV9FvNPcqKsU_MudppEPpVwm2^Ur+Df9dtTR>Di&drW=%I~wPrxs~q@H}V#O z>h9~&UJhvFtDgw3@m5$P8#UkBZhF)$82A;)^jgs<%@dH+Eia!jd%1x5c)EpnNgCQb zw_`Nv+TXvVVPJE$egF&}-lC2`EJui_*q_8d5ykRmX=py?k}nXhs7w(ZSq=~ijKydC zF(ME3c^CM3v{J)I_Pfbs<+~1^#f^+ckWl?{q~{jCD=Nf|BEP+S@Zr z_9s=Ol$7);Qc^e{oFh>TZQGY;V{Jg>)3rr(>TT~<rYu+n z0#C65CviqpB)z6~#1lOY-^buqXwRewOS^AE@WIj?XZ%v4`#L3<72zxODRWxqFhp;z zm+kGl#U#BsBhR{>6V=rVk*XZh8_g5TMk1$nnz(&%R_w{PAvPfeE#pFP^Ye{xJB%|i z3YCkUB48z2KvUm2ni8g6JWZ^_emnBS-Im^fuF06+g7y8O@2` zrVlUV!8dvhsY}Ctac31cYjXxY$U$?Bl_}V1e(Ca7NVbXsOcr$DW0d2`3YR)j93g&Z z4%w)Uyr7Y*+<}Ch6I}jVj&4sL&Snka6+Zm=&dKh#R}! z+;*!#c2srC@Bk1qxJ`kC{=(ji6ox(kvYl?y#?NorZtr3i+lKZ^zygrFKIJ?BMvF=x z0KSmP&gx#)lnA>)TgqK_;aq#<;pw`S^CW)Hy!-hrQ{FqZh>Uug7Del{5!CcH;XBq* zi%PC8GEDBL3^cm0V-AoDm!)k7;x)-rZW^{xpHK+{c~-BNB3u_B%vPy2=OAE@Fepkl z*4HoxejUXEc%Y@RXH*Sa3ud}vI~CH?#FX6$j%r@I^6SiKr9XaIx~dy>9eMfI#P;L! z_j1puC}f3BJo$yzir|b?Bz@on9E)-AnW#3)G_th$hZi8&%Gmd(e*f=GBma`LXK2Bs zyeZF{BIKvYe3Jx&UYf(~5A_~GdwfmkJD74u7~OvFi0@&{(CCUAx%4!wG`OT}(hI!X zJByR`PXP_2cGTJX05CfF=;LN^CW5)RF?yZ-+~jUl;#ODyvTY?eq-&*Y+4}2k+BGT` zo-}*D>-MCG>9Jq1y*Ec`hHiT+-k_L6J~HD2Kpfb2@Ews3skQhRKFSPi75`D_mX?i?j<%Op?oZ-w54 z23l72al3@B75fy)Ori0nsO+EFVJ%3mz?rK=CzA)@Z69frlT-D>;=Km|pbFwM43X*m z8;9Xn2g17otle9@mmZZ!lg{)yjY52jS-c7cc-XC6Jj|RhbTV{6UCm+P4%x~oURGFv zvrQ)4nn8GUU~@9T#t_m0!~T*^^{?o(7Y0EffoKogk1YDh{A(&W1q-;(fEd|Q`)Iz9 zNJf;}O0o>CtdSceLI9ZxamGQWLcyOLQ)P^IPL$SfxN2acN(0NvBsQD!_jE@7X=kc5 zSjmp^TcNJEVLGnYs8`q@XwZ&D=R5w)Pervv)j%@r&7-5?Q0r+{kaF&PM#y7jLVqf) z)wVlymC$`p&~nsMWNQz9X}|*45y@PBhqy*n-$0(pc%9uv$dlYsByUpE-HNx)zLIY@ zR9r>2iYi9g>iX|=`#sK~VdE6)v-De~$l>ZV!;J%mJ%me7(eYGuh<(@!|3~fr*Id)l z0fm%Ve^bmTUeL=IP1%$tpeKq(b|H~g>wZy)&(!}F2Vc%^T+`eZXV$Ny;n?5~y6wc6 z?Vd5_Qj?SkLe^;BzkmdUx?hs%cS4}K?{0hNI+*F+NB+Ctx)Zm?%>kL(O1gV-14`U(nk5w zGw!}VqV{PbQXFq1qx z8(Pw#l^K^N<&Z-AMjXqLLmUmTKN^(tTSIt;`OAN4H1IEQ*P%zUe4LAxBs@*HB%G1$ zq(KnNtMVnVomg{%6zQ0yKJ26AcVkyP$Fhnca>Jg>Ef~5Dq2;aO26FFVT@c3c)6rnp z#I{v zn&3h9(S1?ZR%1J+NA9|g$u&ic$%!gP63S!O%EGUjH4{bVF~!(1s@2v_#&Ve=`RVAs z#?i>_J<6*Wc7tyiO%ELVm+w6INyOeaUOPjEZ}qWADrETfCp-?{j|z8UYgBDjfS4@r ze;WTd;GFLPHk=H~Ry+WP$q>@boZ6Mr1={y-JVp1^9%CRcPQ?G}m`7taq6eM&F;hs8 z$gnZH!qkBHhlK#Fn=Zo}#{elw-AjuVv`bl6-t=pJsU-WM$mS;ok<#y4P-RIFip0P< zM&UpTnOwbJ2j5w;G}94PqJkT6kCe0qaNh@&6W%5h{GNx1$X2e$MsWM3ja72kE#W^=9xx{Pa@(6Fqg?9)WXPg%jz15(g|kx}z!2=+iSrGSy0k*CHa>9>B%*)%bI zjN*A7W*%*w_fYH83mz6LMYZJz$ya_gEK0|U1$|y;F@aNP&6~dzn{vz1@@f{rKc=R$U@3Wt?*V}7b>DO>FF9E`flQsE+!>TC z6RNJ!|MX*avlKkXN#@<|zKgz2uTB-7m;S)KZZgv+F<6W#bO*8P3-TY}BMq=m?5!%y#OU*RFA{6Y|QP(wnXkb#ThG_k-qw%*fstu zRH7iitRzeZ5B3G2gL^r?mkgNtoY;!M1C zJ7u^cy#rb1NwfHOG!W(N#xzY83#=+1<5!?E>-41L1j|{Sq@kB%tiD!61VB1~D;?m7 zpJxk1ds$;;-$;h0dsnG+b(x71=_162vd}B&AngcJtJw2O{^USAZBO&b^(HqO2M9P& z`8)#ezz;Tg5n6R=nSg36vh)3)3dX-mZNVZ`ma651}O+-YQnEYT(r^m^|u zI~4i5c}IV=ku7aM&g*WTvZdR5eWl_)XIANR9k6sg*gjiHRBgIE@%TqzMYEd8C%4Yh zj)njyAk|IGZd-{^^zylj=J0rh0G9>>WU8xS%??IT%xkn5AdHF{$?)OHI%9b{#8Nxn z7%nx7Dev0yLC{s#j9io&~$qhCAJd~k;8Cohty;HC?IWIwx9lZp@< zV_`N9r;fvTaX=~)ulIB~p}+t9;LT_J3NL!_NHHHjzr34owjxF`4&r`5IDIMe-*>N* z2LP70_Q(1Kw2+GXS-RU>MWh}9QoLZhwX6oJ1~A7lS=hNf`jD<#lSL@+`Wq3ggi7}O zhIuJPMZswWuMuHUy{T8Z%;UQA{YZHH#yFt_y}pb=K8si|A-On=ji&5J>AnR)`&Q}g?$Ryw+>bl> zbERA4gj*ZR@DMpncSotX3RTAO1o^2?CD!iOF)4SsKCbQ?TNc?&vh~0bPLFL-#$rhv zIY!2Q3ABmFyLt`w|Io<4GAR^nqkBDPWVC{Klf!A zk7}0;KEXZtPWiGAfdB>G3Hvni2ds1Cth@pHrF0jc2*Hf%{q#$*kG;$a6VItJxxO4B zisU!+iOO5e&deo6@+EUI&rGteyKYOL35N;=c6%$@_+M8jT7q2 zyYW%tu}c)AaIJpF>RVB^)QO^e9wfy76sqa z1%4_!F_K=K6s3Q#U@om&j+M<^pC2eICkUB1GNH9&?y6pz_ukkoYO%#Zi>5n`0Isx7 z6lHqc4Zn{<*E5}##ja;W9h#h7t6jvZ)mB2LV`4eK39qi1!l6B<x;jeFvL3LwjJKTleHL7b) zjq(_Ap7PbsTQPZ)7tRp>%d}eco9?hHee@!4+^89e@MzAD9K@2!@NXUUY=Ms4j?kYv z0=mc~#(Zn7ylLO*1~(c1f9$lohJhnsbiPM<0ER zwL&iS6m2ak1#^J5aq7dH zMRX6CE?3SiGGw?B&n>rLD=65TmfSMvJ4k|YmQk(;0y9}hJDqNPX4z*}kO8^$UqoPVF0{(n5AF|L$?p7;u(;NaG`RMHFe0E|zS#OHgyvc7$iLZYJW~g^r zGxVFxaN;O+#$Yk7IG5_%q0q#8D}nO~L{gcGMd9oP_yDR4IbN8jf6gq(m0MpbS@*cw z!Fo|UGGV3HYH<q-^`L7r%1sc4~QBe>n*N+ zzQCI+KFzVW+N!)bcgFve@It$yEb%#Cc(q0iLn;hQB zp7Ej?rn=6^1?e`P?tPli5Vh({oRYM%U4*|;aA%_*!7+qt>D--(_0WN~QQa4J)m&+# zgvx$B%q2>@v}M)&*)AhXes(duw{Cb?&`FiYQIGsyp{ZwcU)JT&1a^+7sv-$IpV;Xq z1dq$zb9*@54@xR>p2THpLdT{*jv$^zyGP$s$c&S~c@e+ zd#B}RD@#=~D9QZi-E819c*^|V%;t9xN4Fr;c9r6YSIt2@3Rb~u%R-)I(6Tz@fW6jD z8(XZgy%NNkivNK4X}R+2qVZ&uxn7n-SLp;ECZ?zAD0uHpL1!aGx{kTm5YFnZOO0Is zo^fqLg3_yP3A~;RIfV_WR~;q~qDV-tbjf~j;3qhG=)h{PD_MkqX^(MX)WscY8Bz&W zZ|G!54P5Ca9gs3vOxl%{g?KS5=v}W#T+)Ru8iWpPBTRdV`;bk2C#oSk5x7W=x~fV1 z7uKk8Y9vzhQa68!TA)o^ST%Z0(TX(UPLHZ73j8fsN<@L> z(wjtl^=Vhlw+4Gw%(s{|z8rdptPQT^6ht7T%c)PUtwIsR)aRxLn>zqVO*F842VDYk z4rGgmTq3b&U8YsGhVAus0z zOO|Vz(iAMaE(x5GQPrgqDKVljmG9N8;6Qpywi(Y1#>7g78uOqO>&t zjs4^g8hc65+QD^l!ru-naSE3TU?WYCYx%{(JA~gsW@F4!Ocmt4Xmy4G5mt*R&RgfY zmESDQ$l!hg*ZmI;ao>DgQ2{_kM;Jcwm0bO~%T9rrz`eCiZ@C7s%r8uV&*gD#(3vG& zgrKl@V2Af{k>|c%kv~5Jl4ES(f2I3&TnK1UP%`aUplCh*NXIA?idB7x(vwY^_DFUg zD6^>aNN&Gz)?rm#Ld%Qf2r-KbQC1LV11J(_r`qq#uF!{)pk1Ws~igdmDq^eZG=qVYXlYqU~L(&*9!tSA7a7L;PlU6(G}ZppN`)ZAUM!u&mGUsos6FouF~bKb>8Q10wMS(TgUFa zwVFukeYt|}D^i{E>LjLT9ZO9dyd9e_TR#0bd?PhoAp_^S@@hUEut-cSz=W!mJK8dW z@{eL*RsB)&m@#I}Qyxip3AtwJ8`znxA-y|Pb%t{rykFrC?2ojbVLa&D@_!KO3TS5W zJ$Cc}6F64keQpAcfj0ak3HY)f>^uUF9&Fn=>L3JbD_PVb%GA^hYPE+P?tYjwU-Bawa^j^6>mQAv z9W+AswS1l4n1LND-=?WrU)MBTx`UWnxbdpqwwJ2+z#Ce4=?7g2xY}QopZve{Z$L^L zqpfmm*?Pvi)sT{v<2$I#C$?KH{hP~%W#;6g<0)c$41IrD`v|M~7%~=MWrqBmUbpMs zt{;}kRJq25A_h4868Fe91$FF7&F|9s53|a1bb}U?T)$y0XbW+kTaq{aNpBw(Z9iU# z#KWf6v?fG@JE@um7muCFJ&jpzza+feLr?9w1Gs_VKSOEA%bn1WlM)ra`#1AFDPoW> ziaySpZOplw*hRhTG1Rq(I*rFiPl;Tf$y5z&B>As&&VM`2$bibqa5YnZcc`Mx+wm=cjt z&sCiHf#UPjGf3>6lKj*rzma002n@e*u6=JWVe^2KLR;54kK)~9>UT>dTSfBPu&?0} z`%sZS095VCDQ{DU_}m0)ZlFrUm0qjkMIFj_G4MyS(Mc9J4l!JM<}Bfbj!!Db%HJmQ zBiO2v?Iy`_uh978=g?5g^7VTS&+8Daw4L;K1WuH&m9o__W8}tEbkPSK%q}`(Hg?_X zI(tU-(e|reh%mW8uTP=U#IWvS6V{oP@sK!pJ>_4?=PwjC2$N){7=;j;--+^1r|D%A z1cBgY*E-b(^|0tFq3BvnB?^oV-Bsjj7>(dwNd5yg_> zQn-^^idX^sy3H5YTQHp!=-vRsu3P%Cg4h!m8vRnGn5~xdwwj4i&)!~7-lOTqr2)f2 za)Zu7mLb(Um>FzdPia+2CPFZI*F8`b35*e(flDr~LK@-HNV5{{v2m^*t{Y2_vLJTu z+PlbYIRsP~LiR`4_!|-eZ$dEzAD(5T3&%R%wJCs(ZrW%P4CnStxP1d%zU*df?&hiz z|8Q*m0AgRWF=MZbc_7HX7!c|&Vc%)_inWzdg&41{SgDDrQ;^bGDGN4?!dw{{QSs(K znN%zd+U+z>oG_jWZ@7XAF2#y;%Li{;bFRaNVieW1F5A!C^rEGH$r%?n2Ig<5Ma>;q zf8~gNene@X5?qb3Qkq_#q3LutPYfQKKNxiT?Ry0?Sk$*-Swz>IYFQ)yQmB=65&0P% z1E^F4#6$VEhIY&K*teLixS@64@MUIRTk+hYkNvAw7`Do%RJjgUU_fi}04>44C~c_S zy6T3mIgYHUFE0IDi9f-NM*ezsi8$ZR0UyVHtl?EF74|12r{A1h`3wbtGrRvsm)Ym2 zZ+ybY*%&`v6Be$tpjx>^op?A*>vGI9HdSkfuDUZ~rH@`5+ryfphrM1+U*fV6*@{L& z9+E9^tgn!k?tV}DU_qcx&*OTnaeC%^m-?-}$7dXILg9mUtQ-@SImPiE%PKpX(qCNk z#oJ8UC1}#0h&{_!LlA?Br znyL5W$ZRU}I$Dd6B_&ZV^$N)YvkSM9!jq_c?WFjM^rV4TP2RTU zc-oLM`U=7t)~yB^w)@r=)6%^-UL!}7f6q}?T6pqo3q^5!QZFsR#b-c#?`U#UZ@X)h zQw=sILV%R9W^*snuT!l4!ihsjtruesTL`xxZKOkFNhc30sK>sEd}&KHN)^B$i&b;C z4As*9bzILANI1(KL|nC;&~pHSx^L3I$X(w@`0X%=-1X*Zb6|_N4}UUx`p$JX!g0iA zBb*~rG9EtBn33PVY~flq&1zzNlaC>=c7u}8j+cZ2Crvj6pEmIR{V^|Cl^lXTHhdl@ zHhpzSJNMvRt^#0vfVnEekotIbH&J9Ytnw9*Uz87bCbUkz zgIa`#gLfLoV%mD$Hh_v8bYvJAP{Ks}E#xFDtn$4fkV1iQmG=TElpAOGc9JxEdK_-W z530A9tAvvsCJzlVUf)-^O%=*p0cOWeSyI!QQ_XAxVq>`u8jmDc(FC**BM6#bnvf!M z59`fBm&#h}@+yw1?arF!Zc_Spq_i>3#d_PRt@)Ou>9yn8T0K!!SKYcc9XO6&lG-21 zvSMLSge-I{ZUNTrGfOY|fh=!W z^Q_TAg(pjp51Co5#24H6Q`v7&LO6zDvD7i;mD7$IQj($xk_>;s5JL6&um|ZhtvM7R z(6{>syNJH|O4{k*I%+1=-dc&q9bBFU5f5EYFCaDqFQyDCtGTKkx@-w8?ucD7bc(K4 z(N4mR=~9G5?LgawSlR}x=cp%$+xS9=(F|{uJggFs(`A*TSfCjXaRbay)e95A43)K$ z17?U(8ZbjsHnjBglxw3c#^6%n{f5s#kqK>Q*NU=T)pMa$pR1$E-$Ab$AsqRWZYJEJ zo7Ah(IX$4HjR*Kx;aW)qy8->~#?zx6OKmWB!Lk9wG+mL#TXb}~y+NFykdu0Dyl^7! z%tmjyu#ScNXG{I6YP+jPKy0cb`Om9${u_M!Pg?wcv{jq?#D$%dr}^{>>G8Nea)4oq zifn{#y1_7*lBdz}*u=N=mALJ9P%8u|wHC5&+5*aLaYW|8jo(2jl`}`f&_H6!P#g#p zbpvQUzh?a%l4VOqAQt4*!IyT1QF zcD}L_!csoMrX$?y;4`9YC4`xX%`88j%y^>)$~~rk-S`c&GOlUPeD2_1R%ZSQW$m=Z z*pM)H+>{t823nHg3`rh%*zN9gSVu=ow~8L4eGA><-iF`tAg6P=g1Gb_**9&TS6O>p zK{D05QA&JkNWD`oSGBA_46mM7(NH?0dP~ zKq{yN-1%%ggW@|c9q$e;+d z8TA!_n*9sI9dY66;;o&R30Kf`eD#|tFiBgEmfwp0;Q)Td&kZcIT`%M1MySQ>c>NEK zS{HM$nOVhP^maH{LZToC%l==5dD@5qL3_^R`_BA!ZV3jK5(OSUI&y_(1$tPPQHbES zu|$9H%?}$1#0WYH)=pV0`xHCLLTj~^gIf#^c5&a%7?Pz%{F7V^#0iz-+)O+1uR@LC z(1}CKSCDaG8@)TGe1f4Q7cRNo#bO)e>P(m+mPv4UUJxlx` zy#Q2`Vlp7A?U_3#uYUWesm)Pef`6q`u70UHDaR;Q6Q9-s+R!a<7Y+avL>i-oIBY99 z>tSYtxw_h~W54wNBKPef`COQ28h(Y@OLX^hvz;midcU0-AKfB7ot>EAbyvqeG42tvfFL>%xBP9s1~)F-`uwc!|ipA z(;~Bgsf^`p;O>&Ra&J5gU#}RIlrez zj8rTio|<&?zTKE)#JMT=`Z+f%sKp0u@j4#{L3d#ZLbE!-;gcF``~0eb0Zdi(%o}e- zYixfSZ%QWbtGHFz(L;)|U9^den9$H!dtJza-ixCNc#nBDM18fWAiM@s+E1k2em4|t zH$oAcW}Gt5Lanx9QpZA#eQMy#ai-O*Q4-o9ZcOv63jawxp4=0B^O2+X2-<}*+})zP z+u564J3dX@B7ley@>!5Iy^@jhYX0P->JX_*!gi$LZoK=4fJFYHIs4C>KxLjr>YB2O zX!oqD-9e+-Qu;wBAcIB*EQ&u?y*RkacCM~xV?Ked>gw%aQ0c%G*n7~kf3@7Y3@?QM z3aSh16Z#js&P>F54^ZG;SfL72ZZ^AD-A&w?K6bwE#&z`NSOpgC9YUq~u;oZPsezUB zoF0+DOQhl!9q%j@twc;e7lv#2HVbF)oiU6fZ&%IijZLCXY`5RJ{Nt4+FYE$eXhek~ z(DI@n4%MoT$8+N~eHmWr%3$IiKz|ni495@p`?6~aIG}rIfsWN&z5AihL#of9@oR+E zoVgT+%@|+1Jf6}$K@aNIj z)oR9jd7Dkv?JPusme#L=fuWabvL!p%muOA354$X3t28XPSG=tx~=c~WPi5X z$UcvdE4ym{>JnVV7{F6Ker?ZATHwh7-# zZ0C@+QGSBn$iq&p?TT?@l6L^pGPSrVTaimY`&z4zg@_R`)cM(&zG`$Mh5vkS8l;xG4Yz z654X?z0%~{Z82WHc3KLEK)p~4FSaAvCqy4BRudZxu8= zD>-{TXR_+X^jlDsP&EY}JBM}{q1wn$1`gX|RzXB~q3(P4y-`;H2dHM9+g&X0Ip{H( z7PPr(pH{OX_1(iov%{BlE=oT35e<7#&B0uzKjx<+ioJ$SUHnosZhuU3i^|4v)UleF zwp=Lvp0mPrrWf+}v9jd&sQsvci5)0TsB#@xu0QHl!K?goNPj>Ow-y5ZcDsHKoII-V zgo6TvDb2MyBvH~H)yeuIP1yrY`gQP-IAPHFY2bu;RinYpkW!^UQY;E0&{(p{h_%n; z%?B$-cUoSXUVd$~u-5@{Aj5kx5oDZH?$YKfsV4mOD^MNg5;wLz(N0?xW+?VWQqK|Y z4S%HaNJShcj}=4$QXIrSuN?S`WD*j&V-mk4Wt(;y-YoUbdw6YMr0;ZG?`a%YB7L~n zYiEYG5i+!l-f#Jd6$vFxao1X2Q{+xzPk5Mq%^m&Y7JuIAzqi#Sql}n5AelFKGrkgY zHiQzy@ATz|;;gog^=UVqtd39xEa`qe*@m(_o=OjiZvAQ7YrG$J+|sjtF6Jv(U(>QS zh~Jj_sR&WK`tB35T%XM3vxNKP$ihC>8jzWI{skx^z{UgwGa@3#*PyKt2q`X+<6}TD zV~GOCinH4Qm4U5dfXj!$P!ILCGf+3L$)S9pyJSGcoUwwa_9);{1Ax-Q)Kp|p)0`xd zjx6$Y==4j4^J3zF5 zs~DlGLU_4f@8XZ;Dt>7@51k=QmL|o0cU@pMVlA;9_yMaf#82E!!L4?bju8*1eBATF zk`Jpab?#2k56srRpvAc980u*X_Q(V*_-|ZeG1thbl46~*eBOs@5H5(_@>71_ma)c` z&0Wz4J+!=7McDnE_s^3IJ?D0u%ZtPIFzgvc(P9KU4{s+wH4HqX#y0zg4cy;?vMv3?tyi-hnp+lM>2iNrx< z69_GFr6~-rgn%XA5uW@xC8}8nwO=Lc%4dRKT=4^iCqor2YpP@QtKv*C3M{n!#z7|a z0~%!Jy3%+$pUbEf&C}RkD~Pi{_&K94ZX1YayIw%cDylve8FdsMoiy~f_VGLOk_S^_ z)6o?3XSXp%K8S?Q?8E^MKk-ISs=YcjZ6fh7zL9o z+R)?hwuY|IsWrqdN3|)&^4Bq`9s4x7?d^`VP^4uv=&B!JZ zTlwqI8_SO+c*Qr}soDf*9d$_;QYrP^qRk8EOe#O|T#G~6csZHU=5AH-k^r>=E6#^5{5@4|ezGbLC zlUg8oB%db)${rGz27JnoW=@v`7-%_40Og!?^QHQao3`aTGThURhGh$UowAm7J9oM( zKhg7%XHqHpZ34k@1aO?NeG2?^vPbEsNu2|=MHGoV~gr1tB?-Yv_DYbBS zd3Ow9f!v>Tw`@WW^#*Ed!VhJmv%|a+~AK?l3u7{+M+RQ8Hz9p z@mkN1Zt_>M*Og>bwT0x$wk`|FhU|X4SK>&OE$(hisLmImlwSWKgpr~vcSzS9yfMP| zJY?BPY)JJ12+5#cp@qoAt8$DMsb30SbW3mP^Wb{s7Obg-!KxKG{!}=f4eou0bu2i! zHW8C?zm}I4>&iQnzN%%JK$!rM2_Icjv{?x^Uf*Wly2hKuXb0)6p?|UjFD}Pl{kx7D zbK=VSVy*GNv}59((8ftrb`yeaQ|h3Yrl2c`nDY_Syh!(hdnAI|qzP7G}Oxaw3j z!xmcyKEQ*!z`!SxW%1rg_iBLm{%~Zb#9Ijue>nvN`tjhEb+ZdT3uhv>yR0O|K|(=V z+h5G4ZLh<<(8cHPFoIO(Ca`3(fJqqliMm|pA>Kx#VV)^zr|doShV8T3$yK6{)py4G z=x~jyL}o>BhAw=?rBW{)p7@Lp*04l)0!(iHJLrq0MfDb$T;mA|nj)=C7o`||8WM#3 zCOY5(D0pbj{Xayljr&~VbL-=#)E=#4s9_p%M?+=I?Jlmkquqn#K3b(I!x)5v2m{H` zFaXi-{DeRxUm2^-ID=FMj18#7IbyY}_#e^y8!u0Nbnd&uly*ygBo`R)8 zYlU9%dXa*PKjb9l!HD7Lz9rKGrZk(|8aVk|SEK-79}wx?;g#YUc<2l^+h797j8xOM z&oFc8!ML5tVH7%`ZUE1wO8habzi1y)uyuWH!ZmnZb496Iz4MbIt7O7`k~!jqWx-%8<{spF)6Z#-sEjMA~=+)O)F*(NqIbD2nwcb;XOCt(;hC!{L+8#zHeh2Zu zy|^KyYQ(}G0I-kV2bT3eBnpc%{+uLkNDC~iJFxRw&+U_a0L5I}X^F>+oUVevL)4xhd>5M<$ zn7DRJqbxza$Dhq_5Xy++Y8Stt=k_qJe{#8AKJAX7yl+Z1I^mn4JAYazN#jQ*E;xDY z(!3yftQ+%dLGh|=6oG*c{8F%K=@zQwMR3}4neUiy-brY_MoUcN)WeqpfAQnVOmBNp z+2k4DD#anfaOzpW+NR$vU|2_zTRkg@bHN~eiyq~lKeI=_o7oxee_>XD)RHWYZtp2p zCrdH(2h!Y#r9g+3fWN}4sEvnFIh^c}27Z9^$wX$;; zbp^!hf>t7W%QLl?YgIYn`~g4wZRqkm8hXlxZRiyrGR#SV<|06Qo8ln5#D?zrks^9U zoH7F`3}Z>q-uh*w^pM}-;I9-_kkV})Kihg~P96e?mN|L&X*iECe;)eC;dCf2Ri^%u zX8Ap_fs7B_X%Dh)TO{9cF>vz~8}SnJs+=&clX3;^UiYf@gE3DBV@)lDra-<$V7dY6 zjlvsF-$9j6T!T%&E|_T_s`N|kZoCA& zLbo`g&ynxhS$F=!9pMGWj|Toni(5QLUcl2WzIS-(+}zbrQ@Ty`wy8!-UhQzqtNIu> zS5`7h`N=SnRjf;CvWC*xYjTHJBIePK@j7GQj%?>LvCC9nxb$MQG|eU{Jw54`Zi-6a z`{B(i0r^Ca7A)O&TP=y2t z2_PYVpG-v)G4}!EFZBc3s2pIUdJP4ShTL@8ZiLuscm|(Hn9GR#HSIm<`r6j<9$<)?fJ6$^R1XSEhMfUi zYOz#$a>qyarbFi=VC+*YKJ7b;PSel(Py4--D$&Fz7pL2uo(X4 zXaIjsr-D$C>`$un;|^EXmwoq=N!_Os^`?Aq5yr7XhZOD_fsG9@Ui5w&OnifLwW1S^ zDa%h91)xw=n!Y%#xjkMt7OOVpHS9Ta)TVlcHQ}tKUXn?S`_PHTL2?l?l*M; zuTm2G&(3&rF5bV9HI(u$X2b`Ca{fHV^LGnhrr0a?Q=Fi^5@tinMC0dxQ2?MI7Asdc zaNm@6DGaHwF*;DL`fCXsl&SDBo&57Yk9+Akk?FtE02Qo`@$kp)bGxvXk8^O3h{NUf zDSHIb`aM_tye=Eh6*F(ToT}z2SxWgVK3Wl0@ARNB+O?mgQ^Vt-mct_NRXvnUi(jg+ z|5WZ2G7V&K%Hp=M+T;+2BS?v+jrRd8!{#vu0&J@(Yr18BZ{kOI0$E^FudUAAQ-(6X z0D?1|I6R8i@%ix9kb6aj1wYp%WT26ImD*~1kw3bTTFyr~g_MpVyGr}{CNHeQgGt=o z8oZQwSarVoecU(T7yQ=p;*T7ff+%Hn zfHks#Upi#&y$_y8&a6*ezEA$@ws&0T71np@mjyfs!sS*}8J_$(OuwM(Tt=5&tFAe_ za&9Dbca+H53E-YOWj*iy%lCQcreb}u(htDFp;4+z<6T5Kw<3I|i?}?EwSB{uZ(`aJ zr*2b+d4Mly_M*NhkS6=*g+|K)1|nhpNtAV_tOJjf;Cl=z$MJoD9f^1JPKTRUZtPK} zk`AytD*P*e@pGE}ceYxM_yPT+Pcyl1J!lS35Y$Iupi}~N{`SOj*)n`CVgm(rkER#I za`oJ6SV<~khD|ou8^goY$%+6aR)tDx+6C%548{Fdl*Aoow&T8ohT~B{JCk*@zwZ79 zH!A|Vpv{~Bf~xMOvoPw5hnDD2%7A;Jtw0u8R_~bjJE#;O;Qlz2=xRo8dW|jO33Rm= zsAl^PI^?`~XnKp2Gp8C8K=0X}@j(~$P(U@@g+tYu!?Qw`n&yB5vRRO?zJorWSET$r z^ws5|ks<9WJtY&lc%*(UeGpQ<|g2~3ER&N*e@c?aYOaS8^OIpqerIw zE?$P&K%chtii!)CNF4lj{yqwpF%_xiSR9A$3d5p8^dRq{2h_dYAXbv8vVF6T7_g5? z3<+WQ4Mch=lz+;!53MUvp~@VFXtf@;uBg2zx%>=M5t}w!ZX^V>X+5vbG#`i!A$yK- z&`gqhFZ~Z*5iPpAqpp>?K> zz$Wh$0-l4fH0i?1>Jb&Nm8kpHICFxj=&d!l{3?>Ih}{lT8ZHZ3+_miNb2OW4a($xV za}|XjwYlWZ3G^LPlk#C1?uBh~)ZYR+AL5$(kM-FmY!6SdwmqI1#IqCdjFtvi=VP+i zJBqT7u2!d-PIHfOuB)Cy+5pk?khUG@%Yu5gEb{!eM$?ZiHrdx8P*L8VjZG=c)!|s_ zdty3e{IRzl<2kxWRn1>B=@lEk(>P-M1;>ui;S-Os!c^)GJ%~W1p1N3aUIvp=f_IF_ z#n??%W|^HNl)$ftM2PF@C5O~C9PZL?^lMS0IgB2KpISaAU559c| z$tA46RuFf?`UGG)Q7WDtn7CPS!NHr@M@sjq-dgy&hQ`vZ(8YGIHjBY-*B3RW?bt+} zHO%#%aSC@O3rh@uoNvVoQn6#YttK9OGxUKH4qV#Cz9FZ)g?Wf}92gd-L zCISR_fM}iM_bBv-KwUK=cw=Hgtx^S;{020pXzt%ZXoxXqXDaMUflcNYyyzcwCGY|4 z@&V<Dn0&*)HuAvMZWBNM&oQc?>5^U&o0c6iUhhJIO}p(e+NZa8ry1++9S|Y zPvJG1e|qurTcniqw+=sjm5vA6jgJZdaMgNKva@r-T%Cb+rYI|5i^phE9EbNi%tt;9 z{`{$GObeC~h{Hw@$ftdTaIYZ-iM+z49dRWJ3cLI~*!EUpubgPvtxB&TYit)Gg zw4g-2AqiYWaPWCw-cMLmRM)}{XWC@OyUO8i7%qxVpXwFyv4(8lnh3wlT(#7|?+Qr} z9D6aM?~|eU#J%*WpUxfY&VpqS57FVmEfude7)Fd=S0(<6cE8S8_Ni>XGqbY)l9=vu zJTzpWV%~XX``-17PeAQGQUKw2R&0|PS9>R}X$aBn8WXGeg2TD|cAU>SKkOd_b; z1@RW$=wEQXo%U3UJ5`AHf;B6&b~eOH>Wj1|Ma!s;*cLfMR*PA(%!^E{2R24hzX^bN z(L)OLEPZ>^o`)U9tD$ryo0m0`pIU8{^RtW|2kH%0kaC8Z^vb`ow{%y1kav33*IoM% z!-(2Y`Cjn@1w~mF6ZfH*=dUwrg)4s1)Ysc?2FuxYgZj$*@`zebV%FqOHqU~WABMlv zkEPzU|0a-#+`Az@n9pFFAtKYJrXIqUR|`- z0j8TE{TdjmOjcm#fYL*P4(F_`?6d5zZU76L7|B&H*p2wU=1RC!Tjz0FIIvU(8cN6GHig2YDVpjU`1ivxMO z3=o>{WhOwbb`Av2KMy_aDl21TKetz?vTKF>D|WL}K4x@zd^`lM=Gy>qd$q z*Is131mbn2zfA9oclW(m+aN5*62=p z)QL4G9YzKW{2%(4E1P%JnU)e-AY}10&dsF7>@KD*rqBAi=whN}T&aG+aQ{Kh-@EiR z))uI@`FgW%G|7gvQ`rIfbUagydN$wLvu4GL;AS8wd}Bta_6B0y;KFRlt1avTg4dS; zR>lv@7kCYu&+Jw6lYyiJqk%6GrsS0Upf5jld&EayQ_7zK?{8zS>a<3l{P=`ifJDOLlVf=>G1DR5Au?6-c`w%@yg2(K(fA zv#;9i_iz=uC!`0Yu66RAF7EWZz4u<$d3iEG#o@Vrrgq@Cmjt_dp#Ah+<+O(gyagzy zvtJ4PeL;}_f>W#0Z23E<=G<^0EKnC_t+`>I52AJI!@MHmjk9b@lqGT=hyxQQ!+bW0dw|`HHOF1 zsYYdz9vSqpJt@=)pli6tE~}5w1BGvUOD|j*d7RM&@jvE|UT-~TO#g!)eCbcnMPk$M zAZuhEyo3|1^GT@x8P%ke*w;bwq}68IM56BNU0hduZJ5 zTkrPhx2`O6!-Tgu?O&^@07~fiGhraL!-D!4fq2xkdITsnpohwv5RTaN+Tsg;fwtZO zAa>L7RRF`L&zU%s|MQcz83$BD;7xNkfhctb83@3lbAkOI-}5Rk;p32w8uGT@{wbgv zX*y*Pxp=5u8J{KuNTff1f=Y$%0`apkAf5cp{pS?e_B9O9D@+#L?-x5ja@7Z_X@3Yt2%YC8{ENpI3F)Zwr{x?LB3B#sH+Q&ka1m z7p;oXDOP4j+RNRf&71ZcV+~AylB?hz1bbikr|A-Md3W{0{kdu0Sf)201MO5Es%V`^pQ}V+eq_FA@48&A8E*fp+EixL@21pAuCX zdclHH=7PvnQNE6TlT*>s|8$S+l9!8VLcP*rP^8|=BR*Lr3JUM{GA2Mvh&v|$4u;Wx?R{CnX)DXvgv|;ksC!zlALM`BCne9~Z6w^4%GfiouX9HX8L_nvE=%mDb z28ai5VmZtDd&{JQ?br51houb>{D6Q;@*SY7`U;4vptL~C@q2&!3uMR)P$ZX^>h0|$ z0W)76=w$SZ^B^!U^I1yCHrBHosj5Vu*(|e>{9C-IYXfL}Ab-iHvys62JwkRTAidHX zHcDOu8Irdb!#=iQ7(A-ooDJT@QD?#<&X)woMmPWIg0cqn7><8P%?#Wsjr1%(4I z?K69sP0JnW%v>U-c?4Z~t-sJ`EiKne)HVg4WSHx6mhjm!tTy)2s9==%5EqLz8PGZU zA=EHUgAWnrl6j+E5l`;7pXq@~ojgPm_N{^UmtFCBaqhO2NB2*G-m5hw^*V#vwHSrCWerU6>U3tWbLQxCwc?94S1+EC^{d!*{@Ih8(8sF`7ZR_(qNpH`Yl_J~wtgMEjyZ1P z;JFCGLOnMV9gz5`TKK<>tuLOxE=Zfxd6?eQtsx&BaN0Y*w}$ZH8XAwMW#|9Erm7Sv z)`D3Z#?wLVA|xhMKSC{=u_x%GoOqujH$86x&h4u8i1R#D#4dCou6^M%DY zpU?6YZdRX7H&UEd0rX$QoG7J9gc)P`H-pKF!%d%rDUbcNRfNjKh1~po%GetTpBZv` z=w{KLs5sjmU;YXm2Rz5149LChPoa)^`R@dQn#OG1gVKJNwXm7GhcH>x=K&E~?T!L` zXGe+~xOJ678=KzwpRDztXqnBvFu36o0d?Gx4!b#Yp^e0SOGQRanMuB8Hg*)9zE7rF z(zy40#4(!ocTh8Q|CaW(pIjg$cUvUD)=S0yRv4<^koczd2OILz)|~Ds57QJZ-FFo5 z(LfTu5f`bkFw*hFY#E&osm}#DJmSI%jn*q(l1G2;o zv&BCUE|xB3+N^igh|FSwE`^~>d>C}dQtpTinlG)`k@o0`Ko&)4Wi&uP=67 zjS3A|4JgDRC{|Dk0%UA2g2>+HD|`!|2s!pd-L&>Z1$bnmF881S7F(tmI%um5Jb)UJ z&W5G~pX|Mi=IH?rT{j;T`*>{i{9LkS>5Es8I z(tHy1udf|(erdo8|9)jlU&PCWQ}3J8>*emxQdPWijc>(GVqsKSA7fpe%w5M1nXx2G zNxK3xy-}#IeZf5&BRlYJe*k-}2MG^!Huyj?935-!sFl|9YX1I6Kp@HPOyL0lwHtkp z&;15ET<$9TRiT?lo5JNMa@X0d6bX6fnz52FF?=Z36zj`oP0Bxf{%ZEAjMTu;9&ee> ziWe8u3Az?A9=#HN%_G?d8kTn8v77zr#KTGCaqo_ZMq6vPmgSGmmrDYdN(9~0E1Tvl zC-22-u9!t%`-uHDo92>Ki(rvsv!T|$%5W}&l2Z7Z5`X!EDqd2hMOXK}n$iwLLWiJZ zJhFv2-jbX7mR>x$Ru0GsEdV40IvHouOB>3|Yf%pG*G7J)M^9XogZ~V`JZc@kqJm-BvI0&6C(b^Uv(FUvM zVP`fB-dwcP+ua!7eTVmL8^%^{W%P=;>%9IS|T(@|T-7$hVG>=h^H+ zV0N1U<)2HivoqVByx0H#zJKevj5pOg8GF_RsglriOVRG-jr{pCP^C6k`%7T|d9#tE z$Z%2EoVMSF($gl=|KRy^v7%d4fpeM}nxr@G)NIbo711KcjqwJ^u+?%SFBh5JHx{tBC+1 zG6hO_60!7)7P}(w^`~7ufLv zt$aK^)cSTVUo+6~5b5{H9xw$-hV zF>Q2#%7<%_$z7y}#kj=qyyjirsqLX_DAKMgDuoOO^pA{cEFYGuJJ7Y7euS4l|gK zQ++c6!#o+no1riH=n|^{IX8hWB2|e;E!gOOtpBvE>K#<(PRz__5GL3=OCz3<&OLjl zsjU|Y^Y1T{3`?aGzKj48PLaT}ho|uVV%h&l#i|t`uZ)t&(*sbK%(*QsRewm zA%1#EN)WC5-NGPBcuqUJ;~JB*-sPE4mbVs77NKR=3A34obHCJs+(Kcy^FIlb66OktCfiQ98UoXEZsf4@7vUEB z)xhXu4rcY8E>%G0%NRS=YaF_yn40=iJ!6;bTOKBA_HUlLv#iMjj<8S2?sp~$RM6ll zI_!*i_Da<+JLE27&Rr+%{T>_%x$!Ky+``8UX?$XP;$Nb8B{JPjis}a>fww#diu`!V zGeC*%C<8A}+Rpy*;v_v3L+$@z?=9e>TDSi3QDP7gM7mW_Vg#gX01+t>5DG{3;Vft!n!fG-6 z=!mt#r|*8%&WbRQi9{O&fKiq}A3Y5Bx%#S8B^p#V6aa=FAWuJqe*^hum--5o0d{q4 z@X3(t|NMn`O+}~8y*J9MZbLEzOS*#C1a<<;4s7AP#+f#ym@~#GU6*hCG zFF6x1j`SQ<8d~V@n+3E0S>M%MEx8Qk1%B;H`C4+ZACd&RBZIFa0l{+^HqigHU7c z%@Lc;dA>ok968#$z41Wq`QoSbXSq7XM$Q7zhjP7a_@mw3CBWFQr38YWKikj$x%>Zs z-}*Dp-QG+yTgVcizAmEsseTvH{a^>Q$YW7JaTcOF0|3RDydid>I3s8%r>0yIqH$~v z=%TB<-L3=S0pllz$b@$*MU>5LE$?~j9hdubgZhrA$k$e9cTCz=Z|pxiCprXh3m}10 zX!dUFc@~YSlJh3;06Zr*0PCV$apET%0}{Rk&??i1k5qMgkglNRf zeVPq>B4ebi)swQ0^P-6Am6&R3qxsAS4o>n#05w+bvh){aeMw)YJVdU~)cgf@&uDAN zP0P?blC-?*4X#T1) zHtu1Px`J>B@x)Fci#x}ZeX~k7b|+m))8!J(s`9Q%=bNi20V4pyd3JLB0>b%t7?+ft zF;%wZwPOlfmk)b+`Qv=4tpLw!JTl<`mw9cq4nphNttWnT3XCubGG(v_mY|qRUiNa2 zA$i9}EX&NmcK(Y9ZW0h`Xbr1|y0J3BXI1M);<1s7&yBD7)2ht7?kRh77Ybqt{^_TQ z^8b0G^f!#^eeJi@xatnix2~OU=1XRpzO2#owJPgM10JL&!iufc=r~;3uD4&(h zwY`_bPVW}SN7{sD{{^j5q6*fjuj<2eT)>0W-WE!@)y5aupri(rst9ibH?Q*f@zhUB zcJsP1w;FE5h2_|+_&OmhsEjFa4rmC=3ySPUB7neqsoV{t4x-F<8$Y|VTAjVEP-C)~7o9@SC{PH&=rd$Af7h{re87 z*~jkM9L-$}CmNKo+rvN;b=n31_dU^>y8H(QrvFc#E5BSINjlru&dIc7dco zHAk`&L{GLz6s5I@(y5yq9-&<+Vw+h3I9TTAUr_P;2Q(oPuU{V|e&MI0#^P$HmExxE z>Cn0oD>IpLN)@;&*}J|Q!#+~mVOL}JhUE2?CIt&n3m@G9-34is=f~(MbL(A|1~G;# zz(x6yb2_u?#;WoJO?Niy#_)px$pEq(Jr}A}UTACz8vSRkr*Bj4kY*G~sr6o{gAveChgQmp@<8pmCtK7F?zDZ$ofU8+ThqH@D z7k%k9_&J6mvb=hl_HBGx*70#{%q!9s9Y`tM4Dkk#zb|Sp@QUB_pNQ)pQc9*8B=#7! z8|H7ovl-GO1vmEtgLGWykK7I4+#QZyANxEjc-&ulIw94N-_J=RbxAA*@J_7NLUe?cU13V~z0e}w=>5TN*HGP2|0fANrL{=5iqyQT~9yecV5w*hMT z%>6~7NA;VX{F~#wqg2N2|JAI5K`j#Qdimgma87689o{WR7V>f$x@UUbS;E)gA%Ckc zm^!2M@5ZH$3_n!TD-H!3O=DlZ4)n5)lu68P3<=27h)f4iGX^Du3Gga)Ht&e!FHIn&c#F!78` zy7>eEp5H;r;_p)2zel#(QMts6FV`gfs25(3_$~LGl0u;5Qn(WD!jdOVl%tJ~qDc3T(ba=4p00ix{?*Vg_V1MHEg}ub2VGW}C0ls=5dsO$wR( zq?&$`lJ;bGLQ9+HK(Ye3AhfUceDuv7Tj^dTGQv)3E~SVG zsn=n*OEd0lG-<Te9-TXV{jC6$}H$571 zBurrGE{a>`r?kh_YGsTLO96?7?d*V+jn>V+M< zYv~W7Cdj{r+Lv_$O@l9B{hg*>uO9(yM{tv?y#I(S%v$2CXQZ0_V6kv0Qo&eV-EZ<74e`(g3R zlji`5FEMH$W;%+vuvh+lP>~epTQWMq+y|7n)i`c+Aq8SYTOnyo!PZe_`s2MR+FQFmolc<_FN0^TJN0Z3rn^coKQ0JI=VRlQ&7Xl;zv-d?Np{u zIIo-_tu+6kRLI&=E=@)G`g6Xy^?U9(yF@##PTxSlQh3A^NL3M9J{n)>^DdH@8_yioEKF-9>d)hsX>Z&|}|%sRY54uABaf{wmjLtdqUMYWTQ zm$)#_j*K!TvBwHOEXk{HvB6x;BhX5IJ@lmn(^C|0l@^G69nh<$s)aJ}dqMCh0F zVN~xkON?X+RsgAP6Ak#c|Jk^>vdt(T*ER8h)*Bhj^=a57yiZkLa|Dj{8!eqv4ZBt~ zjzERcgZu%+(kMKo))uBYvFs3x_UmGcTm>bPtpo*ZwLY!=-JH2?cJ_5QO} z0C9n6o#Os89o!b0Pc!K!M!jj{Il$SiqyKg?{rUUPiw;tH$f387q!8nYC|dg<#myFT zM8URxuyY8zs^y1bJr^IXI^J?WBcrl>#co;KXZ&R{v_YCgMri4PAHYPh&M1GpnM}!F zoX=tswY44H^oKt?7V1+Jrtx-}g_`9;0a}sUVMX6SS{&a%ONALpM2rnW3$}@~vI^>n zT^F@HXOyS;Kc45$?avSAm-hnF?S=sqj*Qv^_Yr_15@?b9*}c;k?Kut`t-P^D8gyTp z@8iy6sZ)=oBLpGv`+i}>@tP6iVNwz#GL@@aUypA9(Cr&x$)60u<}II7U$x0I2OxG6 zKT@zt+eov`Xzac>D}3%})Z?>7yW(#Q#CEmd+d^8vGTPPIM*U)u{ATWDvE5~z?sJkSujpQ*;gBqGa#mu9oD+#zEY)Ad_;<-&KoQs*bU z@?s(|LD~$E%=85%GAP*qO21$p2o8Qwm#9MbLsNk`&d59=iTJ{D!n*wqy(B8>HhY2K~vWfEl0i>?>bp4`bMs}sVm?{k5MlV0VmD9{GSfB|C{5(`^}=23Z_ca zL;LKe=}p4X@v-KXS1Vt~h-8{xP+AUN@(2mE5ZD!#f;BAiy6e2NQkT|_)n08sY4;;q zN2Om|r4rHDRoPIM4NI44g#RWg*Lq|fd4Xk6hrMT{)eB zj9rNZKaoSMA{hQ++kMYS`g5P~@9qPv%!O(TVpPgu7;culQKO_Y#b{vZH46=Gywdcr zLW*me%U!ge#YR(YRQ?MS%T1H)z19yR==e(_w1WsX4OMUamrKRA%PaQt#e*K%={Cd1 zK=Dvod|p1lN}|aw_upMlKW*Prpeb=j^ z)>ln!%z(ND`7!(&%6_Ah#9o-hVgPTMVC{dn-}FalFEsmF>bn)F#$=Yv&O`01f@PHV zGS09T!9~S%G>N;btEP_|DcZ=wj19xB_;+cKsq3jlW-8^f;kDGPedTNxLM`+Wx}DU< zuoGk>E}jzah9IUDCZwr@y(^2=P)-WjbiCvoA#S?Iv$nDdK6J0%aD|BJI+)k1nZH{n zcV%%sxlb41J?I}4egE``3=Bq}DOjCdyrLN5-ybP70W{z7ji%}abpChdAhaN$)A&{Q z8_V!}^6UMkU!xpJK$U#kqr9Z_ar7S6D|lT5Vsr#hMOvWG>vwQ`$^%bSTWyeWb`z}b zklbXo?xW~si9Tl`aX{1W{?w7GW`Dc9cjX(XGy`?8e&L#*PBI*KZKj^OmRk? z|Iba3%jbR>+6G0no6;r5&aac~8PKmgdEkoBXiZV~Z%|)53`ky$)z7aK*6F9Y@&%}E zv5qYU*as*mKVYt)upvHV;XMsVcZtd4vEBQ8F(w2DRI|gtbGsC)A+Ig$AU7kF6(>rR zxl*)`pWWvxTJRNT3i75msE-yE(_L#zfPH8?5{eq;c4- zu8hx19ZcPN9rw2Djf$V#I?}jkx z3LFFgH)$d%rN>RRIyu2L12%~pfS9$V{Ezq2{{R#MZ1~wla&R~0y%~#eJ~Pads_UMJ zNifZ(ekvId=AVKkIyld`y-#CG{;V!ja*xEM#G!@mjSt$J-a8&|5QiNOR749i?3rAy zjwUBr!c)}ABzz4ob`=&h5mhL;%ypKmP39hFz8M6#bNIGJ>kuz5ZQ$p=f^DyWy@^=1 zu-X-8)yM0sI9{ZI#m(#0rx~B~flRme>!v-V1%|35yzGs^JoM1K%W^ZuO=)#|1ZQKt zd%dS!R^c~#j1O{*dx2aYcqZQU!@5Ub$rAclVe7Y~PM{CUBS6=&Xl$bW&8qp32$sEZ zt?lut@_yFEH(RBAW>ld$R_s@ro#_e zaC-bzCy+mX|MPzQIS2ln1Aoqe|J^z8Z5Uh6OxN1RO7Df~#h&>~BWyOl``GufFZQ5N zY*t0vmo|)X`Irc!tR9eH|P7$`x!vTs)lXB!r}-B!tAojE4C>GsFEm42%pHC%FU$gR!x&39zvV7|4mq8UE#;Z`B}t zEHnhVHQJ@ypv(A|(C{yPs|V2l-xK4~_aD$-elA@`1HLa9>k9T&;DCbbpv#xg&@Q8+ zVPK%614p|8?}O0sF>c&q6Tu{qe*wO2Nyz>tC=QGE;g=dBg>Dob$4e`ZE7-&&q-5lG z?$R?bGI4To^YHTVi#`$)myncttoTex8K$DDrlYI(O5ecH$lAu%&fdY%$@8t3x6ixx zzQG|ML&L&9MMTCYBqk-Nq^70k=H(X@78RG2*4EWGG&VK2wD$D&^$!dV4UbIE%+Ad( zEG{jtY;EuC?(H8O9vxqd>koPhz8anu5T$e7}1OL(R(J^kZ zVcrmt2fwf+xXu0si|}Dk+?Sdwv>Xa3qL)_P*u-?4(|5KmhW34Azi(h3|D}=rG_aq? zH3Gsxy97KuG<=XS=t$w;MA!aj(MLv91Ko}U4t_b1FMxmjMtl7f2(0)3C%&xf+9~rE zZcZ9kyHxThF&U5Q4gBCM_;T=p^F|wvQrOc z!)TF({Ap7e&-x*(9KqTA0fH1|+d}R?u)|SH={`?=$KEan0`W(#*u?rE82a|}ot}La z*Tv5p$H$$B4mDvz6MP`J6{0P*Tq+xrbQC*WNF3(&is$Hhrt_C*eY9xZb6dNzxG;(J zwhdmUSGn%!Zaa0Hn;wz=2>t!AEJJgDlM|>Z_eM#lolX&QT2nB*ChIcIt?cZ*t3EQx zViT#s_Mb(sa<*OFzIkd<3hBUCUHB<(eRX3YLm~KJT;Tk&g21jT+TygO4nDd>AFmjl$&fws3C*LoamUyz4}RZT286kZwHMweMsR zeP?^0K80(ygQx>q>RevFhJr z)G>u2lT=oTO%2o4>TJ!1k90Mrru;qoC<&Tt$TIR347px$j@V0tIHXL?Cp9jY^H}do z2`^4eg}>XS?>HXpjJ?flT^HH-cHY*Me`Z(|V~5O|j7}Fc%Oop^r?afBu1N0< zc}M5%(4(TOkR2Eo`UXJu!pr`706D6c~G`=%a1mOylZ3q0h7^K(Y^~FZ zsHIK!Qc$pi+kHqEM6{kfIH! zi_8qbpmdFXjQ{-gRx`GC^NM?|B64P#BWdKtM$U<}zQ9xaxj-iiyqh!G(Nt0lO?G0B zFF5Q>0@;0+D{nd|n`0aIs0BfWT6RV{r`)z2nwxm~qi)ogvD-seq*yi<%?!f-X-OAZ>0<7*lfI$Fc85AQ=tDR1m8WI^RdZ zX=Lrs1101>ZFN!Ph&dN|opK}{zUs)@jdH(NiIe+fZ2JRSm~cdOHG1@=$dUMpR#FxD zQP>#DlC(5*qLifuNYIpT{jZFdNE+6YY3p}4zn}SKX_1*-b4g7N890@fKFEUu_EWtI z#ul#B2}WpD2}oO1$|Y8j`)tuepzOC@j^>9E4pv#s4|m zl|w6H+T61!T;SE#ovxu-pkUhp%TZ3g%_`%RK42L9B`fV|n$pR;#_ZcMM4Nf7FTS@{ zmiT3B<+4fa(|dzlF}ah-eWkc_$lJS$eU>!fR2l~)n3ik(oD78sm`;7TY-bQ+3RFhz zJU+#lhd;3MeDelTTV&jm5SElB;ZEC@6)Ck0ti=Tv$dv2VI(6Ngf*qEo(+Z92+XqXQ zUeoW69yG5;E~myZf*s%4>XVu42xy56btlA-O0UzPR#5f@Uq0Rzar*t*rq7* zlEg^A%vu5OF^~o0a(qAjS3(rXv`WfD`*ZwQY_;L$;ZS68YPG%5O?eV zNp{FOy}2Gslr`5DCBtK~-lfWM@}&lx$~@-sNzizyb)S8NTQ=i}nnho0T|t|JzCn!$ z`EFMrhJQ=(1|wTD>@MSdyW%-3jum!xegbV42e@b@YCnS|Hmza4J=&Wx({K3W0r*s; z1|2rY)p{z1`sl{ejZs=E>WW6jJg?UbCS__O!*=Ktc4*03}tV|C^Cy z!88Z>Q)wBN;ki4n9VLY{9xR5N)p>4Mz6oj2MCrffVkI)VLKH1W_1Q$muGyuyx>4M@ zi1z9rqK?j$dAb9)sjl>ynp*^MvtpP|o8Ci7?)uc*> zz(#3>PfNep=RF-=^rss%KS*4pM~nC0r}pHd01?)l{f;W`2l{7|K6kxogF&d_T6QD4=|L@qxw2h4SlzldUym!hC*tK(vNwj9Ba%4k(fz*3gP#PguFvl z`r%M+AZVBOLmAvZbgwkM15mvU-El-h_r)7$?79#AXl(9I(ePQ zie7=Re_;)v{ViFd!Myik_-;w6w9TvzscBPK1dXFKWf(YFy!U8p$%)2$*Ypag$7a>} z6}Do#QoEN_3r44k|7_ZtAfynXsh~~lb4{c3qm4(&&Z5sbg+sXN#N^8pbm^eV6B|`p zN{kw_Gs{3cKI9XihQ4NGyJbGRHc zP49)LsZg1d`XPE}3(t#2hV+_`qMv(k2fnp=S+K)pTXmyc;hb@cFhtz)h3O~LXT)_Z z?gP(fF^UDjAhhULB}_+SdhVu*GC{kan5Y{OWfv*T*`hiB*qkgC_8GapMdD|2?~a7A zW}~`4BW;^}@D=pu)FgaUIq7dT=HiQVJ0Ig)Inz$K5gOcZaIYEY*1%NHNaj(bwam!@ zv0$NJkF&ya83Y<4>=91I5fObodK_X)H+KxA11pQvo<2iqpWfkD>v};c3M*XTHp5Bl z)akU~<3rySinv>8fndBDDXAYY8^+DBH07-f2BMPq+PZ@=rwWJeH1DQOB_4~xRRbKU zFk0Un$R*%*98%whbu><$rEK1+T5xipG|xL5m#Ly%bYk53ApqU_$)1|Sy2DG2UCvkO@9VR)l07Zx;zlJAH-qKh%QYYR!vq-zbloAN-ggr3e!R(^dvRosCj9Di+8E?I;3<96KVVzNQ(_UdSQNf z?yW}NjRzn+1xf1Yoj2NytH%WcWto~ru=#5pb!Mi1UH~G9_7wz1QZAIAy3r2T1(|h; zver4P?zHJ0MeG92f+W6T{95scv)tN2B7@Jf0gYQPYkO{!Zaf9qH!pjBLOG$F?6-`L ztExvt4czd1c8}09zdR&YdWEAbekWSj@^$-z{k>JqBU>S(@^QETdq#_Ns4b5{yzc@0 zr-0>o&P)>_+2r#uHvrAaY*T|JB{S&z(em$8{42nFnd9gq-r7xW{B2y3y=* zYAy7Gk2hf=XF&eYsis87L3ySb_BT*@%8mr`O_N&Rim7Dxw+77L|5<`4!$bQ= zm0P$O#Iea@m5zM(98?C&*w(<`Kq|T)AVQ>x2!m-49>6}mefzTNr)Usp#dulRYXS#e zd(7WqkA3acw0jSYg{fckyS{xY4*$7>n>aSp78~X3-DXxZTTE|R``64IgzE+-NWYry zjwm`0nRfHfiEst=EK{(OMz}oE=XJQmHap_B(8$+xC(mU=~pXwieb6|lk`djU8 zPY~4I8Re`4;nR7RTo=yG&1Qk{S72DzhU~+ zJ$uiFW^#|Fq0eQ~xel_{x&wdjj%&I%YG2iT%Xd=wN#5<(e%8TK<3mDHb@ofn`17;( zp@V61ZcQ1UP1_?enq{$q@^X=Xugl2w9GWgTH+~mU!M}f;Ce3RX?g^$XI&w$*z%jDyIKejCX zJ0fz$o9`-l3Q?i2CHLjhzxPp+?{z+AU6HKbcqD6?Lc(YQ5b~$pAL zVrUX<%+5-mKFUbrSfLW=cZj%8u^90~uadc6^eRc280&C$6zuQaL^zVG(tylf@zJ9N zCdVOT>R;Z`f;rm=gay&*~{0#ntTfhSuQO198D`Acx^&>^-B|b9^y6VccEYNS*-S<|+0M zkMpxdj<1VQHk`u7xoWg zcOG5g->sV>_`uIC@O+XWL-)^o7O3h~HR@f^*HfCn#akoF50%@#ooaV2xk zSk%q_7aAfK66m%=hbvX7^t8o7cC6^>S{9Q#g7>KTdh?lFgoAddej8h;zrYsOeiK{x z6)*YMyyxHgJ2FLx#6h*3b-EyMnaCCrwqquFtMD7hmL2>+DB(_yg>m!w)kFIe;|6I* z{)JwdQ330LC+BOnKRecY+au)8ux+SGXJvZu5uls2zRdj(3g-wH@xv(d1lJ7dZoHUu zWOMHn>1XKT*&_9)YWnD@EIs8+Ly1U}o=~!Z@O$`KTi_WCAFt`if$`u))e&y>ZDnX6 zS(mwHNlWt*uJNMaSlF>eWjt zpq~$CD~gRY0_L~`oNAd48?xOmQP8k+6e3PVSf@VYe z4$Z5zZVe7!%krx+YbDhfN2An}q{9yi1Jl&v$s-DO!QQ7tpAT(=HTi3V`qIH8ckW8D zUqbhATjt{%0r6!AR$!~@tHC#*$0aF`jW?lrb+KU1vS+@Ub_?puO{_Q)Z9bvPK|7R` z(eu_#RK|G&NEk$sOz;#ba` ze3&7pt|>!GI%=c%=s_^YB)Vr!BNi={z-x(4YED$vfftvep%YK-kpT7|HR<8&1nz0?OGBkg5cJWCS0f;E($&Dl`d8(jSZ{%f_fcSMF)31&wi<%jQ4 z8>_V$`=e6SJC9%0e8{H0EZ0gyB_UG$oS+d?+<_luq!`L))nVj{!T%+L+Cbnl&@b}Z z<^MM43{rB)7gva!6)>D&-VdW$X8$q~( zqdasz$4CAe)|MR!l*CVCV5MdSZHGif1Evgc$1^|tmX!IU-bit!w(tT1>(>yZWnQAd&_7b z4sDD(QPOF@Sd=oW=@yGRsVyWRd)}Qj?z=AJXc$iEe)W5BIp14TS$&wSi#>tHd+jJz zE~mWj)&1rDa1v{bF$Js;h3 ziz1Adv2OWJkWPDW`jYlB-DXgF$`1mI$YmGnebA;TX@d5cPbov)B@Y?R%G6c*lS0@6 zxU%al-n~X!gP`i+j-tBVt*Lt>Gnh4UrH=GgES(No8~}~4rFbXF zO}}qYBvMXkxOq`S3P9u2l}v}d=lG_K&vSxlGsd>ai|*0^7BVT+ALJCPnW${bBp$7i zl*Ud zbiq!MdM@)$Jy|?=T?Mi6<1nxQb)*vYlQuGNaB3Bl*6uaY*H_6ld+F7BofN_gCfn@L zAL#M`T4Y&%bzeQlJ0mT43JZP`Q6`q}st*J&`eSeZmMsbn^3>wkqL|h`bv3Fa(-Jya zZmrU66#lx_lr6yQ%taJnbUlk&mEtLq|Hi_bt^95&^e)2Ytu_iG8%u8m-kY9Zdm+o_ zG^awnr}8+1PnpzK1;pC|?OFnTr);T;bZF61Nri7SniQ%wt@{&?G#MzpnqX@n_qMF^;@G}!v@lY+l!)#lj@BoDU(3d>#mM1KTma>4lx zRB(hakJ;Cqtl}FlBkOeR$};R0qYEA~letK6XZvXqefKYwn2i++yYn-$9_|&6jy7uM zJYWv=Wq;7Z3$mwEFuh~P8D%H#?tdz6JgtepceTKfv?v@$dO!+`4rib8hTP{33%s_{ z&LqoBqaro0)s5of56xr2m`;~~UhCCpIUwcO0H~ttOoht$L@W#8MVDp>_OWxw*ug&N zQrBSDESuJZvNpDmAJ}Ujzr6A;@bxTX;(iHesS8~N+JiTV0QE|T!n-dAxPY0zFUX_a zYn}o#$jW{hQ43N@FQzBkY!Q!N^2nQ6A*c`?S(br*~p~(5rRNWtuK=$zwIa!IrT#YHY3*It2m9|fl#1E^bysP4UL+S$-ng_QJGT8A8j#@lom0^6_)}26hT8r1K{$u|C`JPEenAY7gih~`G2zF z7%$vt%SvXcFAuBj>I}5Hzbu%v#wAEC%u2?&|Aw0Vy zO(8MTZMRPE&psEr=)cWJIX6=o5np0SDhcafI*D;`(R)nJ4AYtAhfJPYqLQ1GC+z{e z1R_@{AQ2K!xCTa_!bxl7w`_V6Y+TRPLGkn}vLh_ZXn*jQsOd{ACa3G-B2MYqsOQ%lS@uwsBsCz z-b9Ii9kX9nBHiI4>Wh#PXOWBBBYv-Q7LlN$;)pOGHl6i!8!szNC2QjCWLgI_-W@$% zmk@jV7k_XY-rq5RAInJD9TSSYrBZ@!dvmdD%wZ^@0m>cw0YMv8IlBz>JtbS3z5Pul zpll}OE38YHQ0S)Aj8v3t&+u$_f|=Z{Y!zxUAH7}e`kmJ)4S4TvI0V1y%`bGwHy91+ z{>6Khj#?}nWL+nv&Tjf89yQLw6VE0uC~C`l`+(P<5VBwH3&kGF+j33Tty4?;OsrBo z!UY}44-;B0rGuw6O&#^uj%=?U8Y|?(I_hC=#@Tq-OL4MJR`OL$d;G^HoF!>uM>Nd7 z@H>XA7Z}8aG4%VCbh=t)s7~fES8|#$QGb~`j$bq-{s$*d4@Xj3E<0bDyP1tqvP&p) zau>St-lyXZmh*kgjndVJJ0CDQMM&|Qp&-1QAV;qGijlv`WXqd z80fL_rIAIpxgc$FWZ8~jVuc`)I465CsPxjA)Dmbew_4XQt0`wigSX&Z z48=~`>^uC<-qs}unB~7Z3DNq#f%MO$*Uo9HddK6Rl@;B%)`ZhVHpL1(&76aq>uBw0 zGajNToVRQtf*jQ7#EMns9A$~*&&6L_v3b3;Ue{KJm4$UhhO_Eaw@|-h3^2%qx?XTdok$v2Z}Nr>Z#k^l(69D)FyEXLB7B10V ztS6V;dMvgfY@H|OUg7FO<#@@5Ysg0M7UptO3emQnbL`qTP^ROEa$Mr$BWFYOEzEFw z9g7-R4i3BJgk0Cxwsfohc~?0Bj+74R2phpyfd68Hy8K^x^cSBg?jkXuX zMe^h7%FBX8{q{&t7WaN#D#iPn<}RZhW4*Lh znbGFmrG)@PCGGa3hrWBUgr}6{h2uQl8*alSrzf2cAM@iY*Q=>I91&SblLiL? zhG`=m#ug<ngZ(g2>_x(jx8bCS~m(q$#J>2HFi1TloZlmo0k zx6LX5njwMx$8&{|5;LNXhNiA2`v--nG4)CwF($BbO>;BU z7Sz|LU^Ep2=hy-R=|l%Q20Lv@#npNXHZQt(Uo}Gm?R5fUR@=)(P=ocQ+>IwRqcbMg z_63q^_@=Va>{k7)_&QuU#4P)(71Gj`-6Tx!)fNa+l_h-ba3WjQ0~$=#FCHody&p-_KBB4Oh|_Z$<=>NQ6n8`RafD=qw4 z;FrGLv{yDMiM!B;zALt|Acv^OQM#ov4YkD2bJdX1nPB(uJ0e{auZ4rM)r$45h>SdL za-k>7Xt+=W!5;ri8dSbMiv^%3nR38aKLFq@%fYU_T`(YbfZ1cadFEbq?iW~=`wcWp zNfwS0(!UV(o3YS;CfokV_lcy{znu4Wo3BB;AnVH8B2{FgC65=Wwj#?!mZdT>*i!Uu zTrP03mRPNS<+4t4)#Gl0to6CegBZkAQc6Ufn0GWjlF`St;aEvQv_%FRzqZj4NO0bO zS~b>mfBn!}J$GF~R9o4BRvTVlNuJ;L>~lo8?ow3^!?ZWMg@Awbl<&UP$e>^1G4);j zhdoL#&5jYk+z@U3T_(n_*!zF=*_Ml`WSm~7+?%yQnyP(2n+`RCh zfITq|z&@6TwqtarDx)m9&maoLxeD!Sd{3WzCaBhW_L^@+1tCm*6?UjP6Ci9YVMw$q zO%{Y0cxvf?8mEpUM+M?*ZsOXFA!<>)B<>7xqRQXF{yIL{cet|MGt)Z1zWF?CVw$a# z@o55!xVXrig-9x7&A3h4cvZisqR4w@d<%GszYtekWHuGEgjs(&)@x6gtFmUFFR^Kp znX;*fbSe6CGv#ST%W8+E+`Zt#JJy8=WAIjEQ+ppQ&zJ@SZ&O>mzXDopHhZkk&N}0u zEwZWc>dQsqp?>s5;-RrO)NQ-FJ}M7h8tXEJaMP;aT_~Y{n{{o*PO(uwEx$_CKR(Nd zm?YAsmg-^8%)rYs4><~2{8bNr3wNdr6)sb=UEffb&A6My`bIu( zs$A=pol7^JViLBgLudL|X!3S*S^Ze2Z}7ffix|EnAbsWfWr@_Wq2pM!l^Oxq`<6gB z*A?m`U=DW2a{eoG@ZUa``r}ilr{G2#c`=uu{eMa8XuUyc*J}?|GO6>85=Vv&Q3M9- zALz#kN@>+Cwk&J8i)k_iM)wVh^5^%f@Cv02e}r8{!5d^|J`CGQfj%I2zCs6rQN<2rXa<@_9LT@<9xi*Ho))<{vcrCN5A}sZOe4v4B0INXBZ=4 zLPDyZ9`w4%3M9JE1!LM(v~6cs{n7T$FH6&sRFraK}2k z8bS)6@~P_h>nD$4(o*6Sx>{>=#-U(JfqLY2X*y7KD>Yk(Puy~(mx`A=-LVghAP5z5 z_7nPNY{O>bS(DSM^PSwtP#eus6Ww_o~lPUc8pgsUL| zclA#7R+fpd?=g^c)nl_R*LL2MlV7&Iled07 zGs@b3mezmDP01(KMF^F3TC2KiZ1cR#@{$3J5+L-u&e5guDkn@;IFHVpasY3}@4Rw$ zz7?_*BRY-eZI+A&s?2>_yge1gBnSPW4o{UIULzwU$9lp8D=TWWKa1yzvVWpO!8SRF z%1jC$P=`cGuzxKNrtYNfmzJ?E$rapcWHXh#VyWw<73Ek zBEuow``DqWow(3drEwa`3tz*h{ogVIevd);%VQJ8viI%{I`+A_tvCowB z8qkNY_Wr4m|39UFe(65SdIBJO(zehuU4Sw*tdeKnJUFbK%5$@yn?gx_P!zKXLo>+2 zRpdGNH3PO}F{#ttImLBh$K>S>psYvl^dEu-Mf%6p(LoQ);yAgsf>4$Ew~L%xiUv3J zi_0{SVa#l7B7!S`pQJ~wNDnOVKrtD|muJO8Ob;sDmls2=i~g%PlxSg^3Be^AZ9Vrf z_Ru_kk}9(Se7*-V&oo}ojgls7R7Awuus@cOhymB_#xMw}^c6FaK^YYD%ZjbKvX>Gu z7o~9q`h2BDL?0Bc_ZX$Wb)w5;_`%DeAC89Frq0wmYxCCIVws5(WPk_l?(K-8XZ1<( z8ovhwmrkwcMglio)ZZu`31&5v$4^q;Sgqz)K17>6qWuO7w&^^}Wxl|gxH*lLp>8D> z2W)fYV|a3L9N0H*Q;s6iy1CLFa&2u-^h%h5pUlSfEr&h1*(y7eFR+A5l9eZpghbE6 zEu_PjeH%|GQUPQZfIk28b@l(!?~a_CSgwmJ>v>$Sai*=7zU3%MOZU#MmBx6B z?;(!lTdfql0RMohLNaD8S$Qtc5SqA@B;O@x(x4YBhtv3;CKuT_ax|fF;tp};w za3b6nXQF3FxF-t{GQI@e;N!I~&r#c!fih<0SULJt#3+V& ztB@bhRL;ax_3AEd0BF)?8SHH-gQCXIvSKgvBvGK`kS{IaudTQhu0nIi{yFzm4f@PT zB~6>IzkN3gI9T|Wty&R1Yd;GoM4D{6Z>-iMvttyNas8g!ilor)6!q95taqQysGc!L z9nui+H;YmBRp5?i+dWw>?sU3_Xx~tXaW}4}q4xVB7rIb~aFr8HvMujoP3oS=# ziTBXh)RUkNHoz~MA+^XJ+B;F_6=3@l%1vP*eS34)cTvR3cpB<8cAW&iO7->ik0qfeM7qarhu!4AHAcl31x4Svon-od>P*FbHBVP$qzEkRr3Gg?pKNX|N5RcXO` zct)1jVwhexU!UO_8DM?01z!H#uj{JXISO>PI=&Ml5@*Q^w+Wk|E`0MwFEqT4DEx2x z^A#}++;ZY+C^!HrET|9CjP@{Fu;|oZ{QMMoLSf$-mmpbWoz{t?WgX(bU zor`o=kPt_4P9dQ4%9tV6Q%bZ);YgZNLvkA5qDbYh1uUe>Vb1?(V2m-8fSZ?CwIkyD zuyR|tDCmKOXxGPlUx9!WWkD^|9pdvm7b=dN@Z{pu0eTVEa_ToEdVnrfE$yToOdOWp z*c1Orf59d10eOTz)zt>8?(P3AGwxr!PgFldr(1~@s20LKAGw(9$zOim?~SSzFQIa` z19i~hzLayqUpI~;6Vp{}RoE@cxn^yr*d^pe=2yjW8nj>k(Zq<;)ecFfYeJLqsdJG; zl-;~w*<3W7kAALrU_m@5Y zkMzv7zt@gT6Z@I57>@HxP}I_Sb{k%6rooY$`qQ+ z)grxw5$`SQ3lEBjQ{5Bn%ugk87SA{~_+A8foB4;pBKeQkqN-6+4KDN*S5k;jEN-T$ zKFj{ZjIwCObBzr*Kj)M#ZJ>fcLZa)8tYz))I)ihi@{Bp338SeeAadXZ@FEQkEheCF zG*|TI*%+mE9a)h+5Grz8E$7;@IP;@TJ6!$-;8dJA6jkW*(mO^{RuwEi>L~ zR>>TB50+}J@~Ayc?hMS+(?AB6ZORAiiHvNvY+GxML0FIqgSACS$E>zox}ci^T_5kC z)u@cuS=TX=TyU?Mc(EohG1&71E9ekxbYUgQe@Eu)ugzI-x)8zI=V171#*a0c0t;+3 zkh_#5y418~J*1!`SoXab-Vf9vUazcJ<)rA{cJ@b%SNF`)eyHh7*D&O~bvB{V zW8SJVifr^43v%S3DcQU5qksID5$ca14jLH1buL*jl zkBRobJ4AolmK7~sb1v89mABqL1tMlGqt##i$Ta(k83L&kiw7CMm^N2=3NN@@4oTnO zqP`b28>*0z1^~7I+*VJX3ww?bY~`6+T-kUO-WFY$-IKW}8tgd_0E+kBXry}vqe_bp zoWtRb%!<)mztuw>@=Xk-(Fx>$wmTZ`4NEdv*(n}!fWYLJEFJ7$q_aq0Ib7mh|ET%? z-*?>AX8Y{bX8Zh~ZMNSNfFicDi?=`O4h+^tq&E;|KSu4cApO8EHnAAw>~$z$sz!E^ z{-`i_#+V)bmNmN=QG(-h8LaqOY?KWKff}7+86(-A@& z2v#`PA=5K{EZOKguZuM!>+)uv5ut*d3fx_}K>I!%v$iHuwG{&_v(?|CXDhr*X+91D zRfM-4sJ+zZ)+-2>)X5Psm{r_b=eXZ8@^pcqcy3qgeH6DJOY#Zx*B5Rg%NQ83Pe>`8 z1Hx|E93EK2#402g3cR(j2oER~6ed&*``_1}zg!DU)P&R-nvmGx7;n+kuhurq$-043 zpw7J^tDeyzZste%Fi16NBxaT8^h~B6XS@_r7O3!Ws&t&4$nxZJM-Dwn8rz4ZQI^Eg%kM=T{XVFKX&PzTb z%E!qm!Fh&xy7gK>k(=^6fwl|9JrPP1(X;k$HUQ4W-0^a}o5d$8 z#)!}VGE(Pn*jB(C{`~@x-{pNJ1Ha-AHn-4^<;(7dP4m|~-W}b^4#8n-!iEPdg&Wgv zSV~K`?U)C~^c*DwER^^itFP{UKp*3zgH|n5ptNH`Cy^Kl1Iv-(OMvZgd>Hz%vixDR zA3#l*GQhC7HfZ~3=`=xEA}0O%^((r`uB#vaP!#p|jw?=^nKFvnnI4ti(R?ux_L9%* zTvP`a&BA1|3T0QiN6zOi6`ojRjAthLYMYpTs}!K0W2|l9 zx*{t*G<@Vge`Cq>Wh5yf8_yH{H;!m{gUT&Km2(M1CX{5c0ugqrc&0t5$UZPNxBTQL zDm4bh4VZ+F?&DQ?`K~7Y{K%pr*Wm0G7~{@z3qRZHgO6GzkuBDZQurq!JJl8f+To!A z&$yQJ_@8xeO`nc2Gv?hdD|Ok7q{Yeg^77U)tFaqp9f!0bU!9?#|v0`FZJ!eOy3N=L=&>P)z)^|D6_Vb0Yr09pX~oZq8G3__yA zw!mH_r$kLzkyS_611qAA*uchQoT}TRj|x|Q2r}px*a6tuZp;?3Se1}o6y9~L^Ccl{_b%aPWuPk}9R)kP_3UQ@tz{dOh2?&z;<> zTRTIC%UL6;BWO*heYG%yi?d(_gR{VRIvH36#@5$szr}ZYLBd99h=!xA)S_5{qdfD1 z3;Cw@B;|VrLfLQpGHIgjNQ#_R;#A^ryr8wG5!f;`^a>gEH&ReqcyZlKFrl8O1VZnUt)Wo}~Kq!?e9GN&tL)zQ_FAP=ou`>Hof`_BJ> z=k&2$vKppKw*1nXQtk^g@*bfU=$sgIupZbwg`5}U*Ur5{_+CJtCY+toUV`wnEOFsO;fn83gxhk!C8!|;NOJ?I1De`1eG|bQ$cx)8w6%lN zufOtAw`8f4hhUQ9zDoeelaq>6=&?4Ut8DF$f+Tw8dfsnr$vG4`L=SKgi0#3s$+KB8 zV3wMX64J+vz7_G(bCUnWQy8Z z(0>U!ClluVzz7`*!EhZVZ|+W=|9J`ZSgsFG2V${w^HX z&*(+1a zFrs;C;KXi6q`z&H);Ob`i#-1Nqm7#y`7tMLBU;>j7vWWf@x3|PxUc`9!R+7Qdt6}1G-*uI{b+XTlgEV=lWZoxk z&}AI10khVc;{NZDSHLjcNoZ!8tHl>3-^xy#VQaQh!HvpmZKnni^t>ae=YpkX!+C(3 z3eR|^i{bv-d~*orFl#W)`#J}7LMyJ0Fbp%+!G)3RC>^G=OqXD4wnoTt-%0!zV5#}$ zQ2#sJFUr5;oLWq~GtCb~gBv5w;GNn`ODLe&YkskNPwXjgbWdB|a*_$E636wf;^jxi zc0+C7eUZfYk(*oIlR4`_`CImbxe`k;1t654#Kf>Rp1ndFIH2`e;8k(^oIP2x5XqXmP`~uK( zaMcHZLbufs34*1Axyqe05n&JN1EhH}TbFqBZdMZkC zN6WJ!!JhrLOu8Wk=Z2*L*8n)3v8DuUeg^1WvG^RJ#j_P0+oG*`b9JY>wyh9ou4>C~ zX@veg=hBkjt!!S`f8zp9@9J{}6DITGk15AbJb z6d-gFzUbMrk#aLXV8$O`?Kw?&X?@-!gamJ$&sf8f616zs-wBrTNgQ8CF#?7=x9c}? zr2iRv`>Q`g7v4#JyZygJHr%hWqcVbG*uf+Yo->VPw^~wdYTkA+TB2j>Tla}T*+)PG zUj8Q#`PvFVn6rA2)^RJ%w1?W3_7tZM2Q;fJwaebz?*P+y#Gfo(WDqp?m|LGlzxz4& zJJg^?)I4$cdR@!BYtbe}=z$73XTJC1?iIM^!`5$Z%l{g%`!Kh8F#0N0GenK zIU)@mJf%+9a-y1BJ$Bm;msRV_rDt36i!f#dSeB^X;)nnQt38b}A4i8e+FZ?a&8arbEOc?2 z1EPJICK&0YL$;E`ylaJ2WvDsX0>a*mcN-Y@Q~9r1koY@lVgQu?Gy zK5xn)SiOAET(dp&LlD~-o@jV62C7Gbw6u(z$s@q^*Qcemb7j;?$|(VsjgW?4z2g1r zX%3v_dMLgv^w!ss&rrivIEjzZ4S=)+6x`i65P;W8($29pO|ttocwR-w$lh?^`sC5} zUPl~z#KB$nwzeLplNWy#x?E|fn=UDu#brr?XnM30UL|vl_TF9KewLdEWkZpL#f^~TBZBZJ$2;=-@ zN=LH3Wcq>Ee&1cRcNcitc^(BnIwMKB#ov(Mw829U_jKP6A9NQ55i9~u1-DvGR%g8E zfNV0^lh+@B-K)@yKQerhpNkZG6l6%_uo&o-oOnONqwQXhPqZ)yP|mnuG#OWaVEm9n z@^zwJ=lc-Q4H64(el`_WuFcqybcFT1udl6*AF~;>&}_nsXaw_yMy&X;<={0jS_*C= zKv-x@l0qpys$!ojYxeQr@UWZc*b^y=Cf4t0evE97ZEW6jbX=wJ<<}SZsniCGjH;&S zPv`qSGCc8XQ>M4#WDy~czd;?Y<&lWg9X2JBS1KwURG>Sl-_xVzMhn55#d$(#w>(l4 zuP+TD%U>Sqq!*pFN8-99RV|AlYUd`Zvt3G^oKLi)Q-ZssCiMkhh0lZ~r<+BL8pS{C z;}G?Ddal;u_*SzWzpF-@q-U~w)^ZRpG{~fYXtif9jLYCfD%hWK_zGKlFX^8^Nc^^+ zfnPjum>y|RLXp=y&FTEi)IN*zWu^}*CxT)1z&entTzutE1lSS~A{R3kCS8iWg#2Rl z)Q2P?FR=z3wH5$|ei1etr#Hyurv)-k!)2)383Qz@+8Mi~#c2*X1k$0lS&$xQC~uD~ zyO|?ZCb-uQWPx!lYotfV5~m6_ka+M}gRQY)$_dzj*J%Au%Lt$(rmw5zeZdk+H?|-C zK@Vd42R#UWKo7#E*F|>soxI3)W=>Yl&A?6g_^ch*YYW#80*6g~Y9cvvJMGEbvgu%d zy7(`bAgnJ<;Qi&4OVCUv7*PS#fqTzfg5)ki7t)uYql(>_ZN8g3Gjh9kn{AyaTO}q_ zQ!JFk6#|x?h*GC_IV=Z`KBzF#VP$vuX3uS+{q0-u)1lB6%DafTABBck8ou9WMC~xg zjk?XfIMF$4;oAL#%>nj0=NwC9;Nb4{f_~Gm=}A`CuVJdea^edKus0ZjYDAl|0dT5m ziH)6giP+S$vN0efqfP>J!@qfJS{V$NAyPreOK{q!cg5|M9IaUla2}url$u*TXQzA2 zZ1he+XK7HU7?%u@)!N`BTTvyTw;fShD(@3f`;}bp=$Hm|pYsENR=aInutpYJqqkj$ z>K*7zA5;fD>=u6%jJF;&eSb)Qkb~Q$J;6QsPjC5g)5LVa5}I}5?DcmNgx@#NV{Xp_ zX0;rr?zf#_f0G#MSCy+|tC@Nar$cW=yvOAq{9a5g*beTsr`Z9u=cU&H5=q4}gd1g4 zD^9AD0wWK$CPhgU+n9RX0H9NVaxDVVNCxp?9G?*{9fhE)YHT@*k#th6JVBQReYcz2 zkq5|n@f!VFUj$cD=vla-A!JPS@~07>9}e{?>K9&uA}va+_iwvd_v&sPQgNbJtNAlb zq>2y%MBsBuxjg+#Ux0^J%=T}IV}26bGOBuKbdXIMDs41ZVt|+nXh1wnoTdb zzmXtpL2EE{Wxq6JGXDI{WxrT>SP;Q zsv|_-fSY2&q$sIJSgM4wIViO@|R%T0?iA*C1AEro?CMAJB3Q1V4@U9!RGTll31ghs%%{)ewDiWL^+A$Mm|D zRE94eI}0z^u^Mfq=teooZgN^Y{$Q(WdrKO^W-$ABPFJfV`5V=|wD_ZJWBX&ZROE5R z`)yzzmoK;h7C~?nr4ALah#r2;9hD_*r{v7|T)^W1+_yG6U`_rvFUNo9dVnr&>R;8x z)k^*QT8u`o8$u4E8Qt;ZasNY@DpLX9p|5TKcqvv4W zSYhDHs5By3B6jf_Mh9xABVC**UpT|w0nHb$07{I_!m5-SDGOUy)fRu1_vrd}-Jwy?*ui>Kkdd)f2%LHbQb&DLc73#rU=9bRvz0Kz@SCJwDvVc-#@b7rQ`N zm5Rh0H@AU%W4BRC!mD;HoU!9^$(1u4%xAynNQ5E$50^hZ5fILtphV%m{$a8%zy?#4 z?--_$R86Mp`q+d?b!n)}xJPWoU+u>wE-%XDRt{EhBu9%hsGVQDjyyKpzfzcJ5>Q{5 zH7&i8Ui=yR_1~FizxA9~96ZF*WNt8-dST-^Oaw@BJO^iPfEMDfYa<3)Vj$j3!4ow5 z=J9Mh>>48guQEPs6i zp7_QQ={oMe;iD-5CQc`M)tysAfkP{V(4LE;u}=Q zY>m|FvNwqzX!7|%s5A**S8lG*JqxQO0|2ePFh+#OuH1~&J|o|=zAN$OX$EwEe9ORU zp;&`aa@z$QtX3#K2MbP(QQyrQJu@;(pdpyYGAjc_p6E7Gw!Dq-+>T!I_ z9p=(%bzlRJ&ABuC<3vCbw_bPTeX;i?Wq5esmHPhTyB(>lp2Pymp-;a_Is7}~% zFF|)8ulf`UI$YHsE=BOrxNN2)AMElEGVXt5cy!Bye1Gvn-SPr9D`MGyVBi9Ct>BeN za+XRx@uZK3mJ(Ad$$f|QA33>%qh!_B-tN@geagt__9FR-hptGLvARYsv-YXflRzQYLRwNM*??qMy2?(WEekC{gM(do? zlTV8*ephZn!HYj2zkKblUbB6dqjta+cwpD0gO6%==?Q|q_cgh!>vG<-YRnfLw zf(UG-pqtO^FgAEzMN9dEo$1M4k2Yf|@1SA}P7Rl5&nF7Go>8c{Je1g$s*DeZ*ZQ5h zx%ZD58AqMQuv6!W1>s`=z8E+4B1S%%CqsSlcZ1g>=HIt+ZH_q~zSw#L!sG#=gD4zO zGij?0`m`uqUai+Uo3&Q@HVk~No;m|^CK}8CuGAIEr~~^vdj6cPq=Tke(d^i>LL1V1 zae+fJ5ZN&4n=rc_SkO~2foV|7I>5@jw{H19yjC!l6H}%`8bd?5^C?0<>}HsXCZLXs z9psChVs(Q$F|UJU{FBWTxk|t-p7C7LBPk*P!ScR_X)VSe$~-syDB^3wN%RB{4u)2+ z`_1Jr878fXuj{8;weJl2S#-*U&+4T~x$l|Dw_Snco1LFpDy+M(XtjQXV9EJ56>R|m zAVj^^|50(wPy8$81O#k8%{ST1SH^r(Js=$3o@;@qhYknh^3#C}bpDCU=ySVA4l^--FD=Cq{Z%jn$5GY3}N>_Axkf;EGsCN{7AQ0TJ{RS$3N~2_44E< z$7iSs(v~B;+9kAgsfd%(PDbM98^W-!Q2m{z-w?*N<9U9~?2HK&lAm!o$!Pxfo)}^|0rwjk#iMYwo~w+j6}#Md#J*SAHC#z%2d` z3AL3;=Vv~QQ~A2Ixonq0@q+^_^&3xTQ6(@3hk)yTN^sj9g zpppB+L`%|E87C=QJxYXdDXV%_U~0d$wN;X1CiX}z;ne3(+)-4+) zN%oc&D|Yu{u>-WNTlr0ftGMA5SWR?yj%<+-!|>oji5IVMrkYIoVsYTqUOhy6aYR#+ zR7&w-L*zsSYl<8}0avv|fCwM~QaHICe^TQTWH`=(&H#)4z!df}!I}Sg`#pp7Yt`zF zP(ma)yFkd6dNt~yi109)-fS3}R{*xHX?a`SrdAxIDSeDqPuE`d8{_`r5iLsL4Y_Ur59|l3ROg2=hSha;Exbs?Un%^)Olp zT`iu%WKs5ZD2mU4DAHZ|`h0?a3}s*i1Ny~KB~>G;$L-Z&Y0(#MD`p}%42^t(^R6aM zyy*EEQWk!J`8ywO^zbTN@1c!-o1gtXJ6e2IO66c>(Nb;5{(z?k4=K1yTocx4?Yy8S zK0H0Y zKK-_LKk8$vcPe%1g?dVmXL21<_@+83`wFmsC$tor$HE1Zb9~RoY`Q-Ci4R{!4l~Gs z+!}tMG?-6B=fc>KM-~AIq*8LQZsED}L5^i(9Z}OqOZE2C`_B}WAI2TIC|X1@;u*dI z`0TV^KHGUFTpiUlx$24yO5xURdE#g5Z~sA*RzVTUE( z-7Gc_j!mW^&oKRhH$u-G?veyx+fO=DaKrnGz;_IJ;P3knB^^`W(&w|Xwl!hLQfq%` zwP3A3;FysikbpJx@Z=HaviLvR1U8&L|NfrAf6FvoiEVyUR3yI=+YA^HphXx+Sd&dv zBeUuyRP%OFNW1r5g!Wp9x1Ykf2faBQdGBD#XKY@#E&&7+?RKt^VzMHsQ&XN<84CbX zOzT#ji|#OUZ(6vKc{-i!e*S*St=O=eC>A2pdjg zYnAz4q2pCOO$o#to3e3F=VLUp=`{1T7w);^XawC@zWyRK9`W6Ak!X3SRWUPlqbgV= z@>(Fjsr{0aqb-pAKfV**`2%%Vx!pT#?C|zkOdo%SaO-@A-~YM)cIK3VG8hiUH{3aS zMpoU>Tr4Z6A(4??he=M6yc#s^S&b@$7jKSTosK19L>;YfQi!}) zEUfu~uc!$26-9`sEaa|YF8FoTz*}~s5qsjWf(;h5z`I1V@OuXUvGD|Uw2H?c0nWD=CZ?`8u(Mz--g%%{o84of$Su!R>XejjJ(wl)8F zK~XeW!eH-qp{j2P-|H>ql7nFLS`i*@_m_Q#zy)BIH- z%GPwVWpb-=#vL%9tU@7c%~PU^gB_SB~rwYr>K(Rmbh3CxFa(@xDG$A9+i`bk}mEx`?_a zRLfwv*Wt^Hc*8j_wE>z)ksBYPWFz0wf>7e6Oq5$}`QwI~BRVuxWtgfP4Bq8)i)aLp z9wA+^_&R%b+eANlU@bbJprV>gATp^6_2Lfa2k3GSkk6`>gYMeq zVzjKjWc52BbbR5y5v8wCYK0=?u^E2Ik=6BBpw=V2GdnkG-dT!hK@Hl{$@w`@NTke@ zsp=`gO?83KKW2#f0K7=g-1f=dQ0?|06sc)K4roNC=`u2@RC1h9Mhja^-$i^$cJNXw z8DQKjMk`CO8;*R6`7Bd6j6B}`C=bq&^EM4eKq^>2{-RIB@Vo4NVbbKMAXyP3_UBl_ zk4$=OSw1=uCuB^iN*QGqmGoCJ%K@bwDP94!()9XVjq}X6KV1;?uE5xTb{lWCTue(P zqtu!3RZUx-Rh2$?#%CRYS!W%;R1iu3iD&}(UH5%9ry;EE@RXrB9Wh1cBZ%(lM<<6D zzfs_`A$Eg}zd#_V06J&w;Ctb6kZ^VgR43k1Q5%Oq_%zG)7?R&=5q-IC~6 z0e5KNGzSY8wn_z&M#y}mlx_s{X)WsNS!^S;NLAR}&Z#?j7bY9tdGWp9 zlxa5(2pdAvrUn3=9{WQi!2N&m*dgm=dBQC+RHD))+aclw_M1%|EUy{f0^C>11j5#x zL~82oajXrpK7EjAd)XmdiYv1H+RCacvVDT5S7iG%a4xC{xg`JklEb&F50e1@_74Gy z{`ExOUjk5ZFUK(T&Gf8otn{@_uYNPvF~(%&X2pDhdG#9~A0~^ut&WYOg+3;WxT&_m z)qiF-`c@X^@3d|7F>S1Dfmf7`^lS_<+1NNRS;X{>3=D04{9VZWow=2~g|;qm$V+`Y zBVBzt(HEF3FN|!gU+Y^5nVVXeo9Ua`U~*xyh#0*CP6`|*r0`N-*IW1K_Nz z+|Ph}k;4=a!2HuqUXEVQf^JKQh>L(wu7N-(z(3IC1V{*kj&>dWIvP6qb#x32^c$GB zF)?r6#3aPQ#lC%qkc8w8Au%x-#X~BxyVT^w#P=BQQ9q)kqo*UKVtT^#_{qb^bdRr2 zf`Wm8fq4Uy027noF&Q!0<6r%CSqH+q0h$BNp`tthUBg2`#Y4Gl0#O0?c^&14FVLU9 zP_Cf@_lt4kCMFheK=EzRH563TYiOv~ucM&>N4o;=gV6A<!$mS86 zc!T;yem#*~-wqACj+N(4OyWBvq+|~t(LSc5f6DQUlZ%^2=%uiTsF=8fyn>>VvWlvj zuAcr|14AQYYa3fTdk04+ulL?Qz90Pj!@@sBL`Hp%j!8;RNli=7$jmAzEGjN3Ei12R zXl!b3X>Duo=pPsy8Xg%P8=sqpFDx!CudJ@^?(H8O9vvf2POsX90z&<Wb-I0zdR1?W6fJP;UkJoCZrS;3B3Ou?%f*Ml+Q zqq^A=o_zjKP9r@|+f6crChUdZvwBQP`GnpAWmT(tu?bBv2F(;&nkKDkMg6R-_3Ua* zH|ONol!G(TE*_PuoyOvz$I%6ai6D`+6q zPRKg+fE0LrhgoJfr-Gq&tTBJ-O!X4Pe)e4kA!b{9ioFI!+@VDPw&^BLDF9=MlL1(j z06ZvJXHu{MPW`3N>*cxKbyadf^B)~UoxioJco|EvW1itM=72ZG0hgb=7ri9}XZNtVyc z?@3gTvG)v8Mz5JHp7!osWIyxGQ(3cX@?+A+e?x9Y)l~a}4NL}ao;4crDk!R;7-ek9 zqcn-0=9fJ(C6YOl9=LD<#3&pgx*I1^)(f}<(6-KOfXiuN=9D%2Sy7b~P#Vb$y97bP zp(`yp<7dWDOt3)qZs!%zR5DO8WkoGEN3CRU=ATS+K?oYBpK6JQn%?-A;yw%&c4R>#7P+g9_-`9>31(oT}5}`kPpA~ zcHWTn${H8-46N38`*EJJt$~C_ubqLS+LK&L3I7#DqgwygfudQgW|^ZA6Pv-53d z&nJS%SsdF`MOxnEF}6PC_&SDqXHT1Os{+GP&Tmj(T-U;dsK0M=)d@?GpJyF4&l3%_NgfFSf<}yAO-;SEy|P#IP7vF11f6I-GEL`CMz_N{OMDM zWu)&U!;O(vhnRgkgP9l~4x&@uH=Saj4cnr}Mhv`c(I}AHaIF-!oWbH#zT@x0 zidyBq_AD!3^O55u2JXQd_QSSK%|I6^39EG4n`XhiMv8$dbd7I;*jF%dIe}7N?1Ex4 zIkZA`?qyHj=1jYkOQ%-u?s~JO@pu&v(|eE2cO*q!>G#w4bT6*43FL?x@5P+v6ojV> zTH6jHx~7Ya=OHXofM)}H{`ZASLTgq}<@!0Nz!IQSW8m;#Ii~!R*(DptmlEeT<#^8@ z%_WvePBsa12G z<34M+zGMD09aljmGwGtS<04BO{5s}s>!hSA3i!C+<8&m}VFpgLm2Lho%J*xyOo?T2 z9!0HNW3TS}Q%{*hqJ;ycrv)8^2^_FB8>Mbm4&6>0V1xM=8-BHW`>!{?A4$6tbKK-c zSvZt9#55bUz=~J(N@C9cu%{T1gKY30gsyNHU4q=yktTf?x1jADebA}!OOQylUBtfP zKI2Pz3A&XpE5UhXR|7$E57H54IX7 zaZpZt#)sb5s?;?y*ES-`{e)LQ&c12RTKq=&8X1aSj6Htp^9zP3R z7>Cv0BDU2{Grm(2M(HV4@D=$4cU{Y1e%nXu-p+!#MQ`q*zh3mF)r4p^i|pY7uG(p& z|7vpdNZO&jmcc`pQOSuyZdsaG@~esFBe((5bl<7iQgyL$I7W7;)xxQNglLbkyu3V{i$g zvVe{VU+C0~!dQ5wsldHQTu|c#mMbH>)06a|(!l>{^IN|9NMm+kt z-T^wiCWcLxuy2Ql{Ow!DQ-oWa+NxUTFZ|yX(6APTRV1-I{5*@>l0VZNPcHsUpf)A@ zVBDM<<76PA6)!+(PnX)VDEvL|H&Io1OnFHAGl5{h2C5VKOd z)ee|4tx5?*m&|TD;4rXn??> zo_Ygp%A^N^8a+)^#1jaxpvjnmr1ky1&9B{jt;YwbnP6WzhBR1A4gcD9hQnNr7Xf-4 zepB{@GiU>gKzqYv4dUy@ z;M;sb5p?oe*!HfYGAoYXJ<#rkAeyV?=tw&U@PGmmpIT-GjF< zPWDETR9aak*>yccxm->r=N3RY?E|+>b3T4pe^ z=RyGB;mG@lYY|6?LLyGwn04)}wp#?w0w0q3iVeZ{lp`R>7{H9XKwlpAao zjnSu(aSnpP*5Sc-faQK74?1$*3iz%^1^V0VCzR-JmmvG=OL_nl=K!PA+n(w=4J%Tz zAG9*B4?bS)Imn}|lmSM#+yUe`6&T?xDmt|uV9^r_F7UCxTlT&{CeRmf+#1a8U6QG} zO<7k{eJaFUE?MF{?g?lOa=6KI;clF``@mqQ5RTF-*N@{FSu=}Vp}TUdnXbCvD{fi_ zK(!{n863~|GZ(-AXI|kN9r7_^yq5fm@h^*AAoJ%d;+-9n3-P><7aIwu9P34}0Kf{8 zxxw}YfFKlk!-?gJII(U;!0P7{?GX3pbMYHcH&xl#i_mC2Uvo<-f$kn-oZCQ_7&iS8 z@qgYIT3WVM0?@<_i;E!v$kmXTOL$4N+X~leU^E5hL#3?X-@y%3qQzNp;- zALuFZQ2#Z5DLtq_3U2OR2GuF_wO8`bz>PqdlsvDv-7R)OM z^-oHWY1RdTP)&Q}Lo$qWjHfUpRxr{8p8s@dT}B$10{b`RFy9jmckVrVoC8HjJjFf5 zF@hlOQ2mgh5%B;rG~XG33=J?~$y&#H3OSFyUFbO||Ap(2jC8Ci8ssxDRK9;0!G(28??{~qwGW{`Idoy0NGuGIt{O(G9JrmXZ_AC1 z9q5T`snsn*Dq>Z!;-I<#r?)6v(+AeSZ7MdRahEHr}=dlLfra#LK8ow zbNRd{nvuOY!+XW^fgthO|Btn|0E?q(+D0J+Nbn@MCxPGz4vRYpgvB+uyA#|)aCe6w z%d)t;gy0t3-NND!+|H2adEfW>{_i{g`LA=%b-_$m)m_!q)xF)jQ#~^dYOBh>hDYus zC7#ValWEyG5h(fJJH6JNcFnebTO^oF@?Sr>HvjC92{Pv&4Y(<9vVx1v!uD(^(cZ36 zr9&dB|E#>yYC(Q9iEGHV^uxspTBPkl;BAmf*9KdW|MjB8 zf4g>a72=I1z**Y;P7c`T681d|*pE(_^RF##guvrvStNiPE-?O?{L}xhp#NdUMXm6qtp#2_gZEB$4N?JAvEZneh+8vms ziJQ?bGiyg8F<-}g>ebT6s*HVu8|G|F_cTZH%@)54>BE^O_BEBs z8iArd74EqGPqx5KLi*f2%NV|CQ*Yi8VDORHXb0`L>lW(g$7mm@I4@04w(i|Is>I_R zYaZ`=)>6463m15I$1UxTh*Q_g8bkprZP~+p$Ugt4|2*L@?u55bIe_bGG47QW6lj2A zZqEOlR$=|$q*eY(qOh>C0qKyxvnbrmTxjq9dlp5~Ue6Wne@vtNJ7>c6Cuj2Sw2H(- zvgYr^%6}3mE@^uUdoh}|#mI84_tMvs;gU7%aH3O0VIK;M+ZvJ|n=5Z;K2Uq#A|nVD zwziIXGW$SU7}+OKEVsfX#w^?3kh7;4BdHJ+uy>*`GR&KWV5u;}GsiS#&l~?u{L@ec0_Di+{Gl*rAyi+VoAD8tB7s!i&04PYR9XHqX4#~ z*;|fc6e>Vj9T3Is17?Iu{&6ha2mj&xpB@YMrLI505TOkWdp5-=x$u&z)@tIY!p70N zNplN(kR_~o_-=#@v9yl7W@6e{&{47teLv8%+G)nS;q@{y*N?80!huxvGzNVg8RgAh z#v!uu(u_AJ`UzVx(<-vDuFN|QGP{L&J$#8|bu^q43WnPK_?oz>=4PysM(EZd{MGEq zwH2t&oe4Z41JW}A{rZw*;+xr)9aiNz5pPbwH7gC%a+0yGZ?0{1EDLh_nqoZbSkA_N z8ZP#zXc9GU7qH42GVF&fI65X1RNy6c8S^v@AkBn+dOf%P4%*Pu6nR_6sW5)pHothK zeaAVlyh^D2359JIE(3LEj)oZsiN4ImpLiWC&79o$x&ZQGR0Ez}g-pAA7W4}m5a$>2 z9>i}nPa%7%)S=6Q`5`!*=WUdvTc4gTysm1uU=_B(Zh0vlIIdNUQh7 z6J*K!O!swuOzNZSr4+j$*DWJF>lm)_sUJJ7@3u*|Ax2F5F@n%{A@A<^5JWP@!&@HRmeF10q(&9$X@05*BCpRg2v+I8~IW^%;ig2t}# zCO;pfpQ+W^JDrZc8T+C0Xr~tMeseP;^mU!S7fTL@fVYMy#e9{b9ainY@Tm0K354UW zd?Pb>tj^>vc8;*sTLYw{vFghm3)>8AYDk)=-9y#2+fT)&Gm=-N1IktVPMpeXHCm$BYp>_fk8-EDpMRAxNV&;Z&95L6S zo;E-r>1@ix5vv|rEsDYHkSVsrs{q1jQ4DH_ytE}AN9=qUpbbb+Gk^UdqO}1DYUVam zzEjF@z{CYuHQtTx1@B<@cXJOYgH%ZrozbSEo_fnfXYSN{>)=bdX`fOj>KWyjN$n0A zUy+t6Bg5JcQg-1KNPU<3iBa0LIq~s!D8E)xwS?^<9+&5QnJ;(|s*Xo$q^;u*?!Nvk zkQ~!PY?(8zA3TP%a`WtkkCb8PSVQKmUDrxU2`^4H)xsicWsRGg=0rryppwPrg@6d{@`|zWK&Y6uZ>cg}i7;a{kpUQ|p4<^4)5OE_TZrO0>SKljumlyTH@W ztBNJ(Wy){j^G{ZTll+A}gVmP2CGxPjr#d&6cR*Z>~M8se#$&Dxe5J zw=O^Cjc28yvd63VyJ`RYsDVlBBJ#qFlU6gGMX3wzc}NWjIH z^P_tO3*m#MJ?%`S-!&RTY==Juu1}7Kt!vf|d>C!K%6cj6;r@q{UdNMn!v{`_5QOc; z3(Z);jT_aK>f5!!C*Nx64LFasWN$9FC^EQAf`64-Zw}&gy**uYnd!+CJpP6BL?*RD z{7|y?cvEXbhmLV;IMa3UMbCXFmpAAvhs5=y!nhM#+I5FNm(P!=^>Q;Z(98a{L86Pi z+vi0A0l6`r*A7NUj+-pJWW_HIh1REFrtCqTRr z5W_Ek_g3|Yyia@XOQxnK<801r-w*=%^W|f2JF9lXVa&&O^k&uHX}DHs67j>&2H2Oq z>9xf-V5x_(=;$==+PH_MekSr)`^Gn$4XY#M_`q8{mbJzOnuuEKi)Kyru#cR}O<`Kf z7M&yCu~vumue=`-d%7!nIm%b}e#ZwGGb)V*lPEF#G>@j0+Gjz2UZk;_vErj|=%iM0fECm@rx zx$=?7=95(;z02CytKw_+d&d9DETfF;Mt-C5my0X4D4aj?_)GB$QyzBC+IBn!9ey3` zHu0%Ok*k5uZ3Yze z(D2DGdY;(1XDfSmi_v{9`{%^f_mY8Zu1Ay$52uxd4_+K}pAL>YL_RK=7z;w4Sy zLBuRwHFDuw#y0bIta^vrR1W4bpIvvSe! znU|vUJ7?iLX4Rr$PH*KMYiIErX61sx8gFG-iL>MtvueT6nYXINzO&>RvwG2Rjkh|( zTbB>NnLp(Yf?YKFnOvj~nLiZ(vFE7dbGu; z-uN9Ezt&2D$qNdaluG6Orfll3XWUb!`FWFS?wueOBh=)oi?#O-34O%e_BUxVSaG=K z)%z1uq_~QXG3xGu5$qgWD7)qJ&$1Q}vsdY!UFKsy33nwn>k=6$ zr-R(B+33&PNRB&meLruG! zTX}|!8n(~I{`scI=H@E=4fQPKbx`4lk%f#D=Zs5q!8B%0OWWOF!An<{*B{ZVMWE!yh1**mN?2SzfU}KNQ#KqvieY+ zVxUe+I4fLgt$_H{1^O%)LcegbT{cv%!K5ynQA~dVupJ>R>JWPElcT!L^0Dx}>iGQv z2*W@Q<26t<6&cK}7jhDV64*V&mGX8BT$D8s)m5Q=k(mRwqmqNg&;b8$d2az#0jQco|#yE^M-7eIZ*1A0Km+1$#eQL%@QNjb; z`z|@>g-EZ`;0Mka`4vunR1Kzp{@dyJg}`s9r6M8hvB?ZugzayCT_Rlo0t zPKK=#k6bw4p#Bv9RBS%<_%)02-iUpncx7GZKa2nu{|eP}w$wtu+J%y>z2kOp6r)P6 z5PpHNu2dyHT#t{rhR>haa`m_8$@)~#7-|iy8f#M3psv=nRuu-qXCLy-he%(4VA3?# zurlH8(y`tA74)86*VC(wFN#0p4yFU5)lz3rpRFu(}}$lYdx$au=@|I2B(#P zRn|C)fBMyVY=WJ?(>hgNZhCX)PG$!5RI;9bY;7(HX~U0na^3C=^dVBRXTd;oACr(a z(JW*@J*HXi{2to%Fd}UN9sZlq*y6X@UlnUG_FBi-{^3BcN}^mhopV}W;J;#?QV8m$ ze{Iff+&`Wczg_=(WB5;1rXMS!zeLznW7>n%*}d+YBdUO=<`O?GI6GuQ)f7y~`A4jJ z*<)n)R@|}bJQz*p{^6G?RmSSrd790U9C*rMyFU{cK2gA^;zL>)r)b@9fd$&v{lg10 ziSKlRUc*QD*c*=jhYI zE6U_etcZKJUR*MezP7bc20Yh5}S9$`EFP+VjDz1jw z4XehI)EO9*Ip5SmN|v{ir`+;mfgZ2dYbDLo?%d&+{y7RvE{+C_3}y*%WK!)Z%6G5 zIdbYG*zcGo*8c(%zRz)w&JblH>g4}+bWlK>P{`oILt{-9zAxi8RHgr%AoxGd)kQPU zs&0eE{Dd{B+_pZ^_vQUUBaa9s>HX#%pYXbv@Q3&x6N!IJ(0jGueGbm_YK40I!{wxC zLX~Nw<%>C)4bi_`c0|JNevFw{_MP+f){@1a&zFMyTX9t&o&&m1Zs)?%y-WX#iJ%dj zt0^K)UD&_4e*))o^yKQCNITo!e5h|Kbn!AF#GbCfFVNr->vPj36OVv~Yz-#KhZ;Bj zw3{y>M(A9GhVnp-NOpz&|&eTy-aw;w9zX)VKP_-_(!MSf^~X=xoAIA;-cu@5Bv)#VS|v za;=vd6j#YlAe%)V%^AEpI_!x;oDAB+$H(E)dHdxTN&-Rm)uwNKE%BTDb{9x$`?L)A zpdL8)(j#iGMi`xuFJWBP@y&gGKs~pK-*k*9Q@^pCVEJYcFIoxdeW*vR_=D6qgZiq>CPy{R#*CHlhkEi%8KX@n8~oJWotZdIQa>my_O>pzj(Le*3g489J3KY49Kst|tT(8KqBC(mM40KJ7NZi~ zt3I;+!3ERef48#89{EIdf7<$3N?K-vV)QSda!Ho{+SO@(Y1PmFgs9#xCSd10wRMc= zKVe@*D?DRbDm8?O=N}7Sy%TTk6XFy%$lv3&k=rr8#@zTT7Eyy}s%4us>8X3%|MmsB zB+Gi{*!wZloQ3bdnmqDbM|h=T!&?pZsmPoDKI-mI902A*i7e!FuV(Gv&dio(Sx;=8 z%qGm)J&YBNhkhMYm8&z;)t__Y1^>Gtn+&w`?Cg~mt+!u~{|aGG{y7>NK{%$>tWEd7 z>Yb_57GB9{hsvPu|LtJ^39!Z+mV($@t%oD__+ZAP-gH;WC>3M6U13>Y zm3(em{Bk>g#lWw}F9;4repg~pDYCz@XdG935EW*d<@^zG9X6W2@zo9K z%C$;#>-&)@waCG4>G*2|*xQP%dU(;a@-bsef#UwBeQ)>28|b{2Xzv+#gJy+6?f&`q zK(d~7J^IrX?7alffghQDTAa9hzs9q(Zx}s}QlTNaw6`V(F`uOcO;%+$-jbj@+roR~ zyq>jQ7RQMz#NAc0(4`J?n{3)tL*{tmGXoBE-x&qt2Hfw)C~~IS4GkwQ+BTw_^c;j_CZS-C{`Hp{d``k(7F2iFAkF4(E<#RHm&HQoT zMd9R%D}HbKu}ixWE9Kfk9UXO*jDnXnP8Q{@Hwhq<0kqaEWOz-Ehc_&5_!jn|9{8<+ zTDbZ0?C=@P$Wo4jq?y*K80)_2Xs8FL?QJUU#~jn~N)qk5@y%O3tK<5_l)eKLYkl;1 zy$4(E=!vscDyd$Fzsr#nUG>({r-h7gIdW?1mgoW*W-j%bJ{X|pANTdplr{BHg}kw{ zZHxu0mjAAFtjssBc6?o1-q1%HBEjTU`y#Sdv%%~E91vZqYn8`>D^vJsTi%UD5o^5C zVOXh!-TtLC17^0V(u|bs_s|r(Z~1xd`N!ee?|^4PJp;hAzvNxy*@ng|od7VGBSUE6 zLaV9Lh=^NblP z$iz=v7E~++WN^GIRG~7LrA3G|GezS!dYBiBpYt}cE!v#V7=X-rR1G7C zw;EDQg$dM*V;Md5A)BN?FJdlC9(5oBAHw$9I#$4ykd$1(f9hahlbhiY`1JY2fL-auXY07{W zcM%}fJSYQ_OSX+G3ZF^{I0XfmH=7>lY9#5wdluUH-5!OLK#sbpWWfHi##$by%!WQl z@6qk%Z~2Uh!Q)rRJThkA3^`zVU1*NUqaxc<3DSdjrmT=J$MKelBuqc`q>yCf%XO@4 zg#1Cs?2_$%z1xpAHphpl@JQZlq2s112%O;VCXq7BS%NP=DwY}8w+R)47T9DvV|t$< z#pkmRf5fxcl)z9*8RKJ9-c3$rkuwD!eN?V6weAy2rmJRC?=6JVAsKCs4{35V8s}rg zB`fmA;xVNrv&k7sZ4anaTWb#pC4;KjRA(B@{ssnI8+T$TC6Dqr#9OC5XO%Mn9}Flp z*^6`uWmB8yPtVM?Z&)(|2|d()+G@Y#m|<+}rY(H_kz zWi^HB-JM{YoGLv?F}W1pG9tA-OhDjf7HCx6-07FM`+-$4$ zJP}sz_?^?nsBK!90`G>ePCa{?1oU=&sCb1@YR{y0ACqRpytU5BhC5K+A+YOgHO^JKqFFz>7pmKeBaR=uRv%(n}M zHrt+`m4Wik`Zjhth5%TbiRa~28C%}j=-zclOWdr*6e>a^;M;48(^KH-1#z zZS|A>SPpN{|RSVPJguBygV-LXL5D z?<@MT5B1o(bA?pKZ|oUg<`B9D z;jwtQkO%jep>p_g>EJFt-RUB!?a3(c(0kpVtjNGF*S62krS_J$7*VDp6|TP$>Iy#1 z`@HXv@W}E#%v1D9*~`2$4!X9l0A~E0@$qzw!gUCauB^r$I;#&qUG=*j1kSt(?dOQQ z^x5NJkI`2?R&><_VhAK-=B@Pt2-6xO(Dbtcn`QO$ao)I&=FdTrYIOIuNU7(Wl;G(fl+oop0bG6@q%Mj8^M;YwSsL?PH++ zG+%14(gCnY5vWmTA`>CXskkj%7v-OkkM)7s$9zB_Q~3kO$qI zFks9hsr+r_kM7?_|Dyqz|6c@ulM&zkviv_w|G4%qhyG*qe-CDU@c#c6%nVF?|BE#2 zZ*W)*FxO5#v;(N}UshOWfvHjr(7?(81Q#8|wuHd^DGoz4A+iD{2}TK^G-BSS_mIK| zrt$}mI|}}4ZNDiD*aW}=xD%%Fm*Al(cV#G~fKs5mMVmUxCtyzi3{Y1GmI!XZ53HO? zpGu4eKU9F~{uuIq*gY`u;1S?CVCVtzAovIQ$KSuDY-$#6m}=n~NZ^0q26T&ojS!%J z7{HhWc0(lOO#NjlryAHHG4YFInf(DGSb_){v1rp9C$=T7MoE(>21xTZGoXdUK+^%= z|B!LSwxoFg2}SH4F>5n>AmdR;B_RN5+UEP4=wCOM{(nC20z02ENm{D@ryGOW{@%gD z&cy=a`p^Bs?ElXjTUgjY|K8XFV&(jQxv_=&|8!%EneQ53<<}Ko-6vmhp7;TOx(`R7 z{P3rX!vDeTz*3uvUt@n~3$!%1CSLeHFXrZeLJroI)a+o*&D*@drgf)-y2WFlaoYK) zN6FzSdk6DEIIAp%=mBFnYMW+P!&({p=sY*(mY!@*3jxB@Id!hERR2NQG235ZS2YM! zCMMW(lAMuYk|yI^Pbi-aE(U~hE`ggDpf-3uDi-doY|yQI1IcMb`ib3~uO#2GcsZb&&TP zmR!c05rD@RHHC;)Az0|&Cv7lQnE4!mVpjCgj-mr;vMyr1g=OE1!q$9In3pcXWWTY` ziB*AK!PqIB0pZbf_jK|0(l4~qkDL%LSPM#9ZN8#q)^V=QMC7})vzWejdNm5UF1|xf|F`W9}kai@{hPxeA!vzx?T!0)fZ;JoZOxt_$+yfhnTvdeZkfJ zvoQ0R*m=|<{VK&L{EW!PN^Lc2VRd4`!wc3zuN$jh*;Sk0gz{u>flE{#t3t)&`BrVL zyx`#s`Nj!dpYV_+@`tpgD?VdYYyup~(|E)kWp>C_LdUN8d7XYcv=mcq=a%#fTKe;M zmaZC)GrB1;>x}5tAq_Me;i|*V>A&IqNRv=TtD7m)@psFE-;+o@uaB~=!-?@Itq^iIJE{I`dEc|0^4qpn&YS0rX>&2gz8g9Rag8i2upGv zEcyB(9eyl!|5mCD=8_FhP;8BW)l25ceAAopl6u4Wnzwp#mCxRM!Y0=>neEOVM{D(h zGoKw*Tq1?IKRh$F$~^(!)31I)b8W-K{O8SB52^Ip`4eX?rfVc0_gB?}zS zl`*Wg27824v?Ls;*L;6oq{L6)uwh#h#QhFh+c15Va5DZkZ;)cJqF6R}e_Zw3$KVJP9koG<&&v zVL&0c@nmIFq?+}28O9-va>~;W0N3z9VD7ME21*dKE7Cc6%67q&{39mj7^}%3{FyHc?Fht%=_#R+kF7Q@we7+ z#kq<#u83aggY&qN${4>_XXPc^>x1KixQ(8vobBW5FtR{IJo>X4D_*r#b{zK`g_<}8 zaRTN0dT9U;7enCM-XM-Fz7GjN%!D(Z7PFxTyM9{8U&EZ4X`OneIxp z!*6ZZksxzDB4h=c1%`SZ&za`qif1ENoPJbhvMsaBhMz~#B^eo2@HTU;WIv_EPH6Ld z(=9J#UVSHKMZ)%JyZSZS5R5kL;wLOz`k9LR>66Ijv;ezRlb0s(Z*n(;#y=UQA}26y+hnM0?IdVZ@vA=oNt0QO`%gw1GBhg1SX$>$2CEfg+4u3Xf4octS(7Yf}@G22tWo*sSs4M#a0y8cMP0V-j zRtq(#mx_^##HjLrlg;~s9v3lzD({YPmt2vysLKr(5uZx#hq{xYdmR(|2g}Vijoo7U z9^6-}>NXIa`;y^DU*Nnlym2fmk;C^#t}lx+H+2s_27qDrnni=(agmL`ARoW_I4Nl`eSYrcGZ{~&(@7%Y zUhgf2W40bz<}twdIS>^|yYbFO;zl?}h~@?9fU@7-<=EMwEuNmy%#rfIRjAhvn-InW zUvG`xZjOKEY4!Gt4*3@Wj0syl^lguRr>3-xxt!D-`DaFFv;`_QZhxSBgaSe?BOz{s zxl|s#UM`;_2;Jy4h>C6QE%LfF89aMiBQ5bB!`{AjlcsrZ^M150oay6nYS(w= z2Ez4qE6ohXh2a#iCvi{72WR%x$ z-2ER^@d`g&OapUw%|BIlRX+VD>>f%lPh)WK=QKyy;$?`l8AHePm022PN3 zf1tM4;KpG7wBMP^VLg3kUmPisFR*sj87ra$pY(~(AP+ZaSd^vvc#s#}i*2y#&h~Vr z63f*-QoEoW)ms^R2b=d!T<2?hEk#UwEv&+^7L{YMi!+mx^z%)qz`8A#JCNeG!!_f@SiHHkz-?^HU45 z2Sa&Ohj~3lufhBlXeC;sXN3SGD+bBzWusU~kNuFf&EB+aLe z@Y)D=^(7{z&Zv%cDW7ko^K0Hi(S|*G_IP9`^z&oaHs--!N#CeO(+Ru?U%%8M_f9l_ z5UM^mNup#CK0~4+u}{+M{9%kbe|&_T{OM_6TBnX`m-zt9w&VKvqA^Ys%e~~Ao_0X% zBk{Bf?Ku>kx`=fAllP8agFcf zA{ifgR{S#%_a+YTKRmse@wXHinuB6qQPN9nvv@tJ99;P%@G@}YSWfL#cdeXww+<^B z$h+kNOZI_YenwL{zEXvK#|Q6t4r&n#;&u%ng+D@Cj&;4jeZ9JlZ%_EkrTeYDM~Fh` zx0c=wfD)%i&9scePh7D_Mk336zp?BeKDLT->RHk*)J>rPaxr;bUB@UoG>5x$6c?$4 zRbp9$pJ=g7$HC(j)z=;$KDlns-MsoXB)XY0-XEQ$wAYBleh7)ICebEwhn7HlCP?Nj zj11emJ_x%|j;KWKJ13Z}U7#cg=cQ_wIXIAi5ITGPkQ) zc|qJDg3X)pSP5<4UAqOUXON`FuO@mJ7@_{mcc8fzM_qcxY4FgqK!rN8c8gSq2#G~*lD`5%O0WC9si^&pW2Aasg05_vX z2E+Ipl6YT-_zI2SJU6pO2HR{$_EDOY@KrA>=E%CN+@2AR>yk}cD{#1LQNs$9LWqzt zsa0jN4{-0x3^#qFX9DDj`C4f>Ewe%YbM8l^)%ICNLUH&~3@m@Fb67MAhtyhWkUS-$#7!~-5w)&KE zU5RpL^g1L4MDc{@l#y7;@*{KjNlBsMP$1kN>zXVi(U^EkORF~xJ06ux1iYQr5X`Um ziFK<(?F0^3vs9c-O@{FoLszLCK0Dzv1;3EYq`gimffQo$z)>TkMwjLl`#E9mZ>9<< zTK%rdU#A&Pb$N3aP{gQ&Hx5h;leXv9b*@O@nQZcnZ5<{wDG1tqiMh*#OWh{@Hs=M& z-8Mawz=|z|NhS7rgVawy1%0@!dM1VsaLfS?Q6pbhF1eCDzM&OsvXYik)5kk$j(%0r zM8&Kh{r7iSth={AXK!6ZaB+Y z37w_#%7~{5&h;GkC4|qvL7wTy0K7CuzBSh8321|8Jly=Hu}UpkhR(CUv=s)5_LHO* zyu< z_~dy-9Lg@_s}x+0pI+v_I!l3NzPjlzvz>rus=w_jQEr757v6|itb_sHOPURNNW#26 z&B;BNC(p4~Vz7OD{7BJe{J;VUhzNiv7JB5G9Hv?Zw2lRE80G1D9ymGQ=|3bMO1}c) zb6ue2nDkLoAY+1m$e48v>-+T9d0<;V42*!;08s$m!E@eRwwF}@p! zGSAJGRlKBj;9p>da&U_lihN*z-!Y+6jz+Lv1VQiAP5}i@6C2QOg^sU+vf#kD+O(6D zP=j$RbEX8@TcR-;no6)7X^yp`1dGRF|_+RA`0610=`EkT(Puq;kuj}q!x%!bXTz5k*YSa~XKAjZ^-oglXc zGPCDxS{)@6W;hPi18W*kcv6P~;G`a=9yTweoKzH(S3?zQ)bGNBr(FD&=ShPVn^q#O zR92FM$8AMc=1J36%4|AX^}LkO48GSUmx19lYPBcq47T%ny@kwgOdW!K>RYsb)`>Qh zFuzff4*sgsE{1_>@u*2Bp$P3oR=8MKO<-JFL<=fk)6#brD>CA+rdYP$Lscz{rB}ww ziKU-kj+f;}=Q}LVxT{H-Br2=d)IQ-izNRBVow6c6E_1DAY-wo5ZS~*g)p|8t$by}x zYIL-4P2Iz6Y``;Ivs>}AV~$C>fEI^|u)tRK@X}!iVGWi4D}gU<6%u+wM_YWS4H8*kPo_gtxj-}> z?jrP__AD&CMY%L2LtQyZH1Fxfr@1zAsA%3v8i?BtaG{lFiVT#lU@{tyRsVJ&cpA^g ziysO0lulr>H%;Ftao%e}BA$E6<2=b7Eb#wg;&FiZK1II!$?)f*I3zkYIN=J(76=h2 z;H;1_YzTdSaCN{6Q4h4k0-vsnbFDRi63w-5GEt@qfAGlGMAGKWY!o3 z3B{l|**^I)s3H{O=4AOK;6;NkF38FB$yZ#~C`w2K#!8e3%BbS>44$B!HbE454i*NY zd;kVjcfp#-d(u{{+^&KS5xHFtgy%U}Xujo^$hqQVFGOB%BG4s|zkJD^(C7OF10IA! z%FFjxft&^PeYZRr&Z{gw)c0Q%yZs4+Ktl9sJfvd61BlPdRVDqo0S0jx522XI0Afs; zVz-~#^c6y`5ZTjB)9}bvfcbnmhb_=}F`_t7P;>xs9ZcR}Z<`o9cK!K!X(o0U@yFLa zGEmcbBuCvIg)B`VMv#%b$IX#vl9~I@mzxM0L!x3&_IF~H=KQf(P^0VxL_|ylTl{K4 zzi=E7sMo09JU-j$NFD1Ev+r-Q7wUEie(HOBJ89Dj^@^@!L?F|Ve_1d$9Exq+k!gX_ z?vG--!a)DniNtM*=_I#~JwMGUsTjdfzF!zJ;-Deu$4R(m-K96akA| z)ICAUvOOds@^dA$A39-}D!EBzOh7sUWk$bDI@{(a-76GqAbHJifS|()dU4pP^%15r zsLqJ`jjhFBl(5vsP%~EAcm*xU@_KWZ)_9>GYLR%S71JZ6iTGXgiGz)a-MC&i9*!cp z5$OJoiI#vIJM@@`%F!q2tI*5uwMM+S`CcE4Se1Q+*j?i`7%@QvG$xyOb`dnl`gSYd zzNo?y67mhWY$B=+?2&)yeV0J9nV%)$3nV+a8s_>~;zMhHX}t)Ka^N66+Y9yf>BYkl z4YOV8@H$Bc`@0i6YYhEF^dosK9yXYofH=NzVXX=jGN~P2|vG zFrQ(xZ=1dP>r5T5rXOa%v`ju;pGHQBqkOloz^PE0-eSh;u(dVWJ$Wp`3V+W^;y|8- zP+t^XN+*aCMe0PE9Lb0t)hbW+e$xP%q-QA$i|WLx6!~%1=D4q+Kx+U90k?=JYGA4$ z6VGQbj3!9W97r|5+(8DzAqE3FH9BsYtpCMx53y zji3yb_r@?RQkIPByePwnDjlfd)l@rWLn@oi!lLZ2hMS=1RzL9MF`gLq7p`@qS-wH7 zs8;Nb#CyMqFryF{$pM_}iN{wA@M1th`St_ku>*(|MiV3dh0enwXDA;;>lw|8Ap28L z$e1x08bD+?4+;shniQ#6=2T)JsSN=?*5` z1XY(M2a9ya>7i6ELB>6qgFw_XR1iV(NzMmjJzq62oGA$i#`ZYS$;K)|*2nV_cfrYc zQHdVth^2tUm1tbpfCP|er}3Ct{t#fGWRM_Z6z_7uKpGZ;B znzD~?x@LTYfLDP8m6K)uL^>WRMC$qszQsTs;3!-RK#c<;@N44eJF1j}kEWVa-JxEo zR|tZ!`NYb1^yfW7ZN}h*XtvRqpNKW)6Sv$nz|s-^p8TCbvL@mu zLMMtL%x-lAk}x=V8pRvcHaF5w<&R<+beSTG)eO4(@XDl0_nYc@3YJKtsE7Ep`++GP ze{EpxG$rS14{@d(?v=`cHG-J1;_o`g{1Xx14la<&kHXXTsJNkfvs7;zEeWQ2KJ}{1 z5qZFHytx_&eEZ6mu}oxNnzvwyItmfC4@I6~YkZ`w8!!*)D*uekw=v#a#mpqm)5ccZ z5Q*O%HHt_MpCGFeONhfdL~!r%r_4i|=P0-~hW5NhIzJ2^(j}Hc@@pE~SGg!h$0C_z1SQlt3Y{7w%qXpVz`lbp;b=qP9KAK#`B}f;1aER zXQH``Ri^s}O_>cYpq9x$Hy~dsyl`JQ&N?n!9M*6?(I-a{L~OTS4mL>E&E`>AIDk(& z(m0_b=o3$PVlm01;+4a(0=*LpIxx_k2q)4pDTt*xiSWwc8DA*|eChD}y1ovLr=jxV z?q+F%7dmVurKg3n3D>D$)gLS5$RYb`E`6juv^i85_A$n!fje1@;05TRP$kWL79YT> z+HtGkrj1c_F7ZhABv~K+jEcfVi|?Oy`gv=M3;4Rcc(=SrMl_dYIi%_g9Z_8vuyy<_ zz0T4_hEixNhlm3DM0&?Jctq+pznGMK?`janA4qI0Ul4>o{j$H-{YE#C&3Pm_bNBRq zGV?w&%uMbz3TO_8%SSw|Vc2;8U;wR+4a1((jkWTTZ3nc`@=R?MWz!(4QXS$IC?t{A z(6L#n+$u^*fnm?L?Bl5W7P4PLT?VgxvaW9!wT9Gff_S3lWWt1$2Wn%?X(+|_8zt4n zhwa1ive;3R^pwuW^`nGPjG5sX8d%SJ+NAv(GV#AWW<89NKOggx^TlXlEod~8FAze} zHl1j$qLsMi@%?cM2Wt>wp+3$?)^MU=!q{1FV*@XUhj#FQ4haKQT(-s!aNnyz_Omw3 z2saTT!{B%7ulf*uCC}jde+sB5P6v#NrCy~%-@+Ok5`y)xY*kD5Q}gtbkmkn<8;zlW zmNU20Fe5<+=`xD4&kFMZqO}?@x`+TeGPXe#KU@#%Fi`%yuR`7r|={RWlG<*=VLMe{}k2H5+|+kFJ)`1J^bSN>sefM*8rTJRyPU#puo59m{O zqA2<(ipg5YSmhS5$1P+vzh z9v#W}Aktx2EJAD-mMritY~|S?5H0#ru@qTjpZxQdPw@g$kK}O#P}30gu|ihHo1Z%1 zILHOXHoVg#2N*&#cANm-SJ|0rKw?1nj+S@mDGO@7fhXU)AEbsu1=lzUFE=SIzzhCm zxGbnd0ACc~hn^lW?s5B(CF|e2WK63@@SVa-{NsZ_JtM*Y7iVuBRmqZN4;SuMNa5}U zg}XZxP`JChySqCS?(XjH?(R~!yF6<-X6%SKnR!?4lV{&u znKOwvoB$=HEb(M8Ob7^DmbJ`k$NgCM_7++?jOOUD3P`J(Ch$Tz#3urfU{f{L0qU2( zuL!HG10-&bJAkDgooQ`(g?_M6LE#Vsc(B;29q8fOV@=>p%ns$qy1Qd;;K{TWF(9ev zA&Mx1Y(IS);U6JIQ+ze7M1>L(715-H=a`WG!>F>1MI7M1*(exCIt)hB?BML8FihS7 zt1&{tvSlse_>46}qCVpOETL zzXhQe?~I&c9P*k-KYZ$2uokAUXRXQ+<}g%3Bydot7$wM$-s%cfWh&F)^NvzfC_9@U zN0>jFFUOS){0X1KUYyD~2fH^E9OMCy9~JJbT-Jli2VqmC9Do3dBd9%ph7Cnsa@JLTkvzhZsFoW*!7b+0}0L zm{f*1k&gCrNYc?kC4^TrAt<%1A5!Z@fe}}B2onNYITz)|sFenWlydaPXeyO{q zeeWezJKX!GUh9FpQg(AY@3qJnDXw7F=v4+lc48z5TL=dlUP0mYHR;yC-&su6$M=CN z7^Dh+Zm>Dv@k%JJZ%DI|DmYXhg9nY!6_oQ__!EDBRw%oUICLW4b#x z4qvYuEJspm=~RR&kVdKE&?=C>)T=^PYUx~rvNzyRa~v4NUjGwoakxYc_jr!3uXv_T zsgYCOWU%0h{R&l^oG~p-j)D*#<-ROjDKr=aXl7uZz5aM02%2qXpq`xxu=Ol$p-w{x zpQHCfp*kbjZ%DbYM5WTwvnbL4V9;EDmBuoCFEN}yLx?w7rq^UDwe&6$@9lA(C(X6= zDS{D8Ymu=HFB%hD-z5+lrqkVK*s0I`goqTwRDlcT9UL^n0~lzqYn22T+8`3bLAY+s zv0|`k)i-!R6%Gi3Dv}5j7=8jgp#%{O2;xh73qcM%D7LzagSa3nBx}?5jdQS%x1SS{ zxB_H_4JdY-o)W5W?hLw?tAm-~)k=o9pHzAaTaG=4W7rN%h#sgRD+0vk58_B9lrZ_n zBhhVip!(hLt5LpnVi|e?Xoya0c`)FbwCu>uqIUt>u_9}~h#km&?D=x6BZ3G&+LTBp z%C#&^2%Tv90WSF+d{A)KrYI=|0&gx1?6tuHO6Nsf^sOy!?wwDYn5Nd3h-N#N;ZUd< z*2niDHK(kQ5K
-;jYI8fM}{f$>!QmZjt-F7J0yD-4=T3kjb$1avO3t5NfLnp;MDUjO~6Vl()O` zpuaM?)$}U|>tdJbW8*FN>6&8=V)|p132dN;Az4jf=m=vGpFEJ&gyG;5Exe!ILK5%0pjkMF z2&QQ;5{N+B9Mk#WNlKvjt&N*rgN1}iI2(fTH=95LI9=V~IKTDYvJtjVa3W3yo-J=i zXOr(??Qc==iSOR{AQ&SN4AU}*e!x|^lz7w!2}Ev@1V4V(kzuDwMDD2ld_GL|6Uk67kKiAwxC#Bg%a z@2}_-IC%wD^X}($@tl zu0tSVv~p$J(I4L<0HIZ8@hvo|Qimx8N9N9z4v<3j_0ucdjrCqYfoxPufGLh4!KGqd zMrQdzLb5&zPw{D=7W7zw;(!WvbyNGo0}}w;3{c>2ZPEF{a}sTBu>lIgtt}=%L9nHb z3n=inw7Ks!b#ga~u;{M&r@F!AG4+~p4vw^OfZ-Q@A<|F{UZE^`GsISpiFS1}!;LB> zMGy)9@*{~0XH!NEq5<5D#zi$do3O{SH(Ax~h9mW4?><~$!F?%$faLc_!)ku#&bg>z`V%2xLD*gbfy2!d8Z$C6jEk(P`TR~*VjEJDH z^^K(H+2s1Lc0-He@ERELtpwB4B)DMYhwx;`5O%tYQlYn?WXK$Lt)x>tCNULDe)M6X zgv8&Vz|C!DX>*vy$k|R6Jfwb-}v;N!WDM#OHyRDWRo#EJb_J7bgaNzt@T`V>B`Dl__v{tK8A?t zAW;w8C8o|GOb;9_UR)V<0Z}uScD05`#I>hPaFqh`C`4QE&Q} zKoCEyu3X0p@EQ)M^yi5pEd=Sjbc2*Tm}zjx$R@z6!eC}z#zYsz@~3ngYqy0ipGVBu zI|Vpr(m-bKHJ2_ss<*mIvnm}RY|#PBixKRrG;kv*X5JLlS=6{4j37UT^5+m>XA7e= zteSLlIKPu%CSFffy~)~gb8xN$>78&H9>z~<^@bPl0wu{Azc_tr0u`*XI9u&bZ3a;a z)MrVi(gqpJpJP4+XEPPF3T7tFR@k6NPB;W2!AaK-sP9aYd~%84Exg&pn(%tyOd=2h z*~-5EOjIFrsGGF&c*WBZ_(cE*&h!eeB#MRq--QV?{S^<)z{bY%w=m%kDW4BnA!Y_T zx_<=^eu(Gl+F#pT?5$XGf7!e&E9qT{Mtp7O0hcc0{6+k`c9~+F&bRSX3vVWSH zpy@s^{8eLyru(Og5RJ5sJwR*7^5Z~2E<74#DP2PR2Z_{^=1bpS{Fb-*80K>H7Yr9YcX z+gj<#8Q4RsLDNVJ3PaP#8#vql)ippY=?@43f0bg;8Xu7Vq0;pCplkU*6q^2+kbem_ zeHdv0nF4j}4F23BsUWPNs`6F9%2wCFoLbCY$J|5@nnuVHptxjWX$0s+AE3u%V(&sN z`k~llrw8as$I|}eSUUhdI<`NjK?4BNO2N|P51l4@+CNA3H(dW%{Ey-PUvTkTnd|@W zQPIIx;#l$mSWybYESP`{%!f7DE?tRLMbf?(m*v1T zp8l6@Zz%mAhPTGrwh`c)OGJVOpA&TV^pq)q%`CT}4C--o5MM2y*z$O=q5?vt$;DdRlnJ1C8Y2*DEF( zeQp$qr5LfLr!4l!_`S&=OCuI|{aPaAI_p~qZUBt(c|DF+6eAgR{kyJ+Qw(Y1*&>-5 z*$F4Y^R5Rv7&cFu&)uCH%mI``u3GKo3pmg8r0&OK<-f`R($juC2mg1=^yd>3a2xsO zGM%ZqIm^%2YrUM>O-PVd5?s)V8OFw80LSUaCP%aA4~0elii!45hQenc{-w}`LI`{f z$%hLbtXj5`gSKa2HjgX=t6YkDH`^dz+VK4(SGMw*VyV>lrC5JL<7G?&a=OH8?ak)m z;Ph#H!|ZI)^{k%d=@alLq$wDbTbvVD2{!Su{GnzLZ++?`&6W}SrEB5_B&6s3uPju} zOHJl<-seKzMI8njZxdlooAmJb&8P<`Gy=lmO7Dm>ZZQ*@4JAguC#}-nz-Q=r>pb@6 zao!{g8&7*HoIdpxDm^Va!MLQsbh*+*vN%*89ecL1eEocMU_JQ~;6?!W=tbU@5c1Pr zJ1sD?;`<;^qwbNPKuN@>dm_sYK+5ZF+&=ieJ1qvDHLo3|XD=M!7uIZwDz+(ty33be zp4>5$L7r4jJp_@_QIDI~m0>6qJ`uE^qIx}UT|U)~&{*TD4y$w?mIT1;H@b0IlvCTK zrtXhrFwM@h=IXe5Xjn+zF&fR!QN*L}8RV1n$5}kNG_#@EHT*P?5JNAFiin&OGG@x@ zl8fZEOqai47)Hw+Q_BksqwrIvR=jf!(Y>CTe7zXQxj* zol2QaPqQdbmb}YQA8Qg^$KqDiUa5GUvI}GCmawuN?=`ObToq_jPKFmSo?xO(qblE$sa$GU1 zF6k*kBandrluYNC1nFo70U4Dr|GC9Is0lI^GbiYe)cLP=+zU7QO=Q&(cejCV!UZpR z68k0WT8FKcDxqtwOFt9C7L+;-Uv)dh3eYyUBMr*#o$kcz>-k#iUG%=L@g%({L;-cmP9|sxc`hxE0qj6Zz4nAT5QPOTIGm-Lqk%3@|ZjZNeGx zf{q5v%byqL37b<^xk?%ZWM*8!P)})aj$rEbE5JdjHcTO3}F@e5Qr`-b%l)KuyHHKjm4M<8Sor$v%TmR>N-^{ozW z5GVJLT?ywsA3{Erz>980IfDcJrup@^lLwdOVPE1~BF_+sykdO2nK{0m5Sf}E$0Z3w z`KAMvy00bpTKevA!eC;A#@Gqb&mNyiDZ)t+&_!wCDb!{d^T^9H#ux%&HYCDp!Gay5 zuyy+TXp*pO*6hLYY6?f&aXd*O>Rgu8BV2@ozUVQx;ln2rh-^V4WusaPwzCKI6aF~n z-OK`+e`*&UYS&ygGe~t!aGO_N7@fk8&t3I1gGOtJ8DGUCTb}m|Ip8(g=r4i%38%`* z7YQV`HipF*OA1YL5nWZUp$4upWZc>vp-~vPh{mz#7yFP}&$P{QDyyLib&6d-b(2>BmK zNh@nl%;|pyHE=*_WZIcYB@V%2hR3rw7%+nEZ1w7t?%Il=ZZqM2=#`!B;3Y*z1g73|lF_a%j1u-g=qK zEcmzJRzzGQ6IJG1RTXiyWnlYn9Lt%@%m7=BJbUB2w05%; zNC$yser&m^@Q36O^kd)@&s)nNVY}UyV3H*ktBU)xZYQ4$=F9}nrtw_1ydp_?I=MdT z$dY>v+X<*|UXrPgRe3dq)zWgU{(Lx;y9E>V{XS$n@TGZ_@TOnCYKE~pJs0EPM;L5N zfQ7dw!IS(*=r3esde$JE+@Jyc^hq7!D|klV+&;ekK7Zn%c0r==DhZ#@!;C39saks$ zm8l}cCn&yuJ-wbAR5Bd5Oo`)~S4xjpq<0e9EKBAR=JpzuVhk)}4AY6t&5zmHh^$55 z30qOrsvS^dT130Jyf8yZt6EgqxlvtYV#I6lc-fvQsS)Zm6nFm~yM#sOM(~_;M33N* z47rmoGbOOToYIt`>;zpx+xE1Ma_@(!OqIw){%ty35#LX|02R#aj7h0cK55WE(xg^u zGdVLsT9u2dHM=s`-CgX2sgY)=qLMPCP)y7O%r2o!HAc-#L9B+&0iT|LnQ*YRFn7Pe zr@Vt+!`xkPsYOjX$K1M0&?sjs1GIpWfi4x<+%ish6Zz-|But^Or@9IT13e5eov}zA z9kWOnIlh&x40n*KwSCyZ5*Y;K(aC7aw6vb6-KmLYjhBq3aDdx*M>8rKs?y3H5|`K*QjqILUarR@2hK2cDVx&Cq}MSi^bhgMU9qkiix{ujOHHqI*!7~Qu$P{pOFRO;AS%C_{J32rZ!zy9>oTuq}u^ASd6 zT8RzcyZKJ)d>xdzk0wDLT3${XaN}wk#G_CgNWtfuv9z%C)6|L_@`&FntOflFRCw^n znF!-zK!W6Rx~USZ6_dC|0(F@XYZentu`ITVYD3XLkN|d`Qvs%{mYlJwQULsYD*Qn(N~q!RhZ(hi}TCv6?w)RKgZOSL>^jZhArgid6LNcakxW}QRDR9pJ(UmsCW zLE=Js_I3sfFZSFxeyKk&fkA@&DuEQVWm6c9aub6ble33x9n9j4(Pz2DjycE(=nzCB+ z%1#Rzvy#C)Lw<~mnmrUOKW=28qNyHZqpV9Z7aLxy9SDCuZ;s2I-x8y+GOisoI4VwW zWU5Tb83-)4mxa!!Vphxzdix>e4fQ*Y^0?2LrRYsZMC4A$1`3xw;hw))AfNUrM((D8 zQ3|o|1l}C657bQK*WA-YS`nZYmSRlFnqr#XZ2mo#I^7JHJ2@rMxOhY|#uV+Rjbv|Q zLgiSgWl#FzFDRI;3B|Tyy;=tqT~{h;IA%)Ip5K&m6y&@uRKhj*!rp^`0Zv)5? z4<_8{5&^+iq!i}?0wU)rXh_Hy-7rYSJA)$WtzX;(eISb`>|@Ch-gmUQE4xco^olaq z+3Bz;Y1=QXgcL75iv@?XCk_t}&vUh-OUCIm zC}d%`Zs|5@gan4`>9+5Z^&BtJ{H!sarC!#POr(5+t7FS7Z0T3sGwBX9{gcX%e)r=M z7s7*E6YT1YSKrbtN^8MBfj-IZK#=V#YR9i^u}Ddi3TxrAR?($$CPK}|eW`Qa41~BBBK$l(4lSx<`x1TrH8Yi~a34b}8 zpb;f9DL$vFg5znOa?BpF>9({}M|UCNz&`YpnP;(o<9{OLc;p>ws8Z*v#c|}Oh-|-6 zz9*NtU>+?hm1NkzM=t2oM@e*OOq^&vxl4C@|Mt+ZJJx&<_5zQ1reM-guVz)`MtiwZ zdLpib`$|O6xGoZ#@zgz*9!2%lbVwZEUD0l}4QlcH$5%^S7ksWS?&8#@R1=tq=HIZj z+T1-oI77quoch+)NB`SZ?g=O&0M~bIfhD*h)5zAB}|QI+4E|K)j^3SP%9=oNj~* zRmW%Yl_>sZxX=sG%By;g&)LDy^873sDmu3o;2F#d%EKzv#OfaN%Gnd}J02-S6@O6V zeL5*&CP+ZQ3lsD`@*2|%THPJ*7D`C@i*Hy5>^B{xQ&h@sArxzy3yOoM)R=d+rar~f z6aLen_E&E}{(ljh3R3^*3rW`|T61 z1TIs;8rD|xVg+`aKWTKCb77W@i})?`fGTJj3gDgN!CBmL9p;Ts4MYvz%NXxeAf)93 z?HO?66~u^S`KLj(@m~zw2Tgrb6oATvBe^BQE+w~`lCpWl&4a=BbAwHo6|YL+hClJ& zsfxsvdE?ELWu8Js*K3+|@=1cagO$iYx&<>M)lwd?0vqAMlt|3t9$}sf8GIJ&qv%82 zP7D@7I!W$m+6sWXFo>{tdm73^%}EKg`lQgx})J48#9KW7t}~r3KwK zSx+=oK%igT@Ecyc0S|5-BG=~)QXu|iCz|F)=S%$#Ci@b}8?CjX8hz$q$kF;D5l^m}u&UoXT`B}nBh$=XpDy>vJ|&{57tpjr(Zc2)3IuP++@1w)A zgV4v0Jaq`~7ruv9GdV82?|FcTb-AB9b)9wn2-h0;PYb`AC_4el1I6=!+83o~a?VI) z-o&q7TL}EUq8$@LZ6#@PH#i)N`Q4m-*G!{ouXafy0?jEGN^iyc*Rq;D z{Hzpd{cE8vs)v3D{&LZ|74MYF0W94kI6z-3zK{qLa6#;R9t?x8y7uG*0S}KCm@3|n zmXr>uo}D1$=d$u~wZ(OL@42~w-k*Zs!{&mQw5J4~1ls&G`uKSdZX?`LjCZ%&G_6sw z$lK)>ghWd@%=Qfot(t48rA<>q3jg4$EdzpVl+D~7-Q0XA^CIXN&Un@-`C>@t5sa;@ zu85s0(lzLml(Tqf%yRqt9(gAUeUD4pvSdB7_0(9)9;*cRa^6SMf zhX^?J6ibH)wSM#6bl#!p?BsyES#2c`CXAQN-X}MkZ=*NaZlO;wNW&psIdygR>pt z;cykG=e&EHvV#u0gPRwm+C&H@MV_f*Z$=dHq9ff$hs*K5x(S@go|B*M?(= zr`NYA4S6Da{X%-9D^0dbs}y_Crrn-xZCp3t>wtP#7M+fEMyQNtlSJtfZpogPQm!Og z=*|^fLX?pm^}tFdZKlUy<2hu<9(CALcOP7FxBR9;>>V}r{ z+omA-<}I$Q1rzu$mT=QvI`rRxe4R!&;#og0e1e>TD)yCnDLhGEh3wE*-a!I96vHVR z{YU8PIc=H|&x*Dmy%art_psRpC3^|=p4wHp^UrRXT_b4*Y9y#ds@%UR`n4?z|)X%@O42{OGK?IH;mH+4xj*-9%j@ z-B5jH!Q_2TYN5uHj=F*gjId4lxZ9_TQQHlw;9@F-ULtOZYNemr( z$q+fpm4yuZ+62-p+R;?K3AQel4R0!S0i~hYSOncGUrb-S2H}XdS=jtB=1h-zVwpt>P274-bHH!*)KvqUMp%op8!kV8SZ4?*rCCa{ z+V|u98IJoizhnEvw2kc;k0Z^*wJrQhV4luFSmt?v*rOz z;ssTkkrkm+BWg!jTXo4=qv%4Ajh{k~_)mB*Ig&O1lehSjr~U_T@o$+z0WrZ3R{LWg z{r^8f4rndsVrOq)A!cc4^#{oZAnQy3Oot0JIiJ3jt^ox!jg+mvf$ay0POcyz2dK+A zSX-MLSOEBR0EYtLMg9m@_~6uSO|0#$Y@z9C|75mk04JBX5)l)W)Uo~-0S}?1+ecA&W@Lv(E)gHHl`1hG<+X% z5cbe4j12z>im(SX0*s61&j1QOOCxgwXlgq8Z-7_}0UhhV#2cBJ0PW=fPNN9T3fS?{ z4S?wcxZ9t!+8@Cme=*ziv>%i5j{*IE^PzuozJDj$Jme&;0DNfs%~#t~cvP`R@_6aI z;#p$&m#HPsPs9ZRm_T5zTbndXW{bAQ#IzU7p4HUIxS@qT4ahK?8kOQn%Co9F+NV-K zKuUitt?-H(s&f4%a6hTQ^qnG0ykX^2S}H)eP7A=>VEbWdm?|>6c!}t5HQ{Y`7Gu!? zpFarrO29JQ z3UPN04@^J#@|uOf>J?fm;-v)dKv>z^yc^mRy}7>)gn$w$<5k= z%Z7{zhSE_XEO5;u)rSPi4vcQb4Od1GCSEEOb=(mBR@cH}5n~G-b{gbltpOZwvW9B4hLcRzeEBN6JCvGgnXX!o z0}4YF-;O_63>b;nK7F$ zSHEuvU}blJWY3-6VL?E6wmwFgK)L|&Jd6eU;G`~5mLO&CBZfCxwAeb&RwuR}WW(=V zF`R;srF;WacX4?^txs!epy#SQI`(CVo zSuf*DAEia?rR<~6^v?&ArWr^M6>dS2IK*^bA@-T}6S3hin<3hRGK3I(h!I8i__p3r z$sKAEg)<|C4Wp-}=!GP5D_#2YJx1N!da|{Z4r+J@XGp@n)Hl{QP?=j^voKR#x*SZF zR+g3*SCrf~eNXEL1CXii4fA=kMGD2x%@UNqlmiQBDJ{2CUo2wU-XP-!8|zrb(($|l zeewd|g5gq9jG{u8o&$6mmWe!QZRgWsI+jGWbcq#Lk7e3xmlpa~2@hV|&S1}>{IlG1 zGZ9t^k@;P!r46tBzf;PeIwt9u(U)JX5;kovpqOkl7}gbUB0LVDUJG~FD4$aa@7RK& zph}0LT&51#bh}fb6!{jc*g%WP4c8#CH=1aZ&mciEJ&ilMDH{t!09QaRqQ^isnI|Hp zOv5!?Z6AmrWXj~m|Bn1g;AyPwLe7uSl_%3;Vt=bGxD$+W{U-XW0zsnd+Bz@kfkgfLLHU!&k~)(WR=OZBC(o5utr~bA+BYSx5Yd>bBt+!f!BI$K5NIEs*7PacUJN`{ zsQ4jKe3a7FsqZ(rLkG70MDjdGF5DZr2(cM4&QiLrJXT~SrpI62E!6Zv!^`T}!uXzh zI@KQ`em6j-FLv5m!?_^9MqPV)Nmy!R2Tvk^ZF&!x%{mvu3?STsASWWxf$}4L>7>Zn z!O{iE>K3-QRxuJyBK#3{h)GoBC7p(9!}J8D(NHk$=@riF8;0ONLHKa%wu8!iqh~4a z)8ii*oQ?D|c99ViQx&!PJ~gO|TWOi!(5>$a_*KBuly#DE2;lqIO~1X^Xa%n39Y zlUJ5>8-mWNCpYnjsb>22h4aLk=BA>vC>kb|V$rZ9At~#a;9lY&(*k~?LZMW9u%)BK z1T@i(MSpf^=xaSxzf?EQ{S+}JSZ7=#BppQEhj62VYd*AyMA(L;0`qKfvAYVwMZmY3 z6K_9W(*InF?4e7+&*Tfl%rRz@4t*EE&n07#JiCs0(Gc3%}SH~9+^AyyND_1IoL z{F-#SB9P}YY<7ed?v{nA+Rb5C@=;8D?hg0nVL`zgQ#~leCOhMZQ{eG9u*oRD+gR(6 ztVt10qZ1&ZHo+xh_|2oV_F52v!@d7Ri4G6v9IETj9(g@40cTE#XMQ7#DkaEr6cdt& zJY6DGUlR=ZydLKp6+C_-ei|Z1$h&R^bSR4%XDneqqax*0_6=b^EM&7AD2rVXO!{)5 zC;l>v8aSkNbyQ7;6+F_60N5;%#~`Al%qT(>9xrS;_3wfc2rPmq?jR-)v5r<{=|pFr ztDw!h@UcHf3%|HP%4$mz=nv3wiQO#e>FAGaLi`EO2GA72F z_!K`Z@EZ(FLs?`NS_j%3Ztw7ZOn~~YX~qt9Yl%w`mvg@BSRjic1{5n%!XC8rQqg!3eG+fPx0sq7|3{+K zuTVjHeK7TY+nmNEnrF3D`g>M4s1Rzpt$MQN6 zGc`JVBJ?uv09JiGe$=OcLWD*d6t7l<`o#&h`{TedJ-H)u(?sf-HO!@k0kKzbzm^yZ zI%>iq_?A7bf2O&-nE&Rmi1BWetZ$b@QnD=@Det>&?jV6(P7El8GzR6RiLCROXb~rw z2p0Zcwz9uEKn(Zoc0F^zEIS2#Tmi$P2C!8AeL$;r_|DdGH=+)TNZ2XYI0 z%9h6F63~aA@=QD0w*-VD&$TR;YaJe^gITL`kaU}%PzHo#B?65h^fg(Ajb&_|6Wfc^ z6KD!JQ}RlMr54k23uu{BMtlq<3X8J9l*n`PilyR|uYqMGHz)!I3Kb(@`)HG;w^FTE-wYTt7WSA7Qoq3%SldF4FQ~%8v_j-;9P)+8 z$DEW@D>xk)%-FH(b)6l}iIGgdXltRTyhR`BbHw13qZ$+$rot_h7mZ64TE;g%G1B|A z7jtzoxcq`S^$$LgLFWV2Bk|=krCnZf=3}%i#Rg=$PYf1LlF}5UTK@jMvQ}CU)-`gc z)-E_PUtwQ99xMF0jdO+WwmkjqN$p&#^-y2#hNTrRTo~{C+znLx9A<;NQtv0*l=a>6 z*(&ZT-K4&|oDnwq`zZAw-NWnNQqj9iOXMShGK3k}$y7FWo%*hL{7hQRj>s}~(oEx{ zbPsx!F_4jrqx$UfvH5p&vZ~z1W1sXqHXBJ0GRvu=0Qyi$aK#ec5vpw670v*q7VreE z*xK{kfs~o}#xm#*584|ejzx`(9lRtZyCyd-wb6WI*_y^Q_p6b+$&F^NNka~$1DCT3 z0|>>&WZ0Il-&q8@M^9a0z#25Rx<3hdsI6qgLPo_*#9#!n(eSty+d>>*+=q7psab|7 zTt#ZfCLi%bc|t}&zl%KMWP@8iv*4=%aj)) z!@#?CI_%Hw%bPVeF*(H~<|Mf;AXe~38H-YT-`KTsGYw~>vd_YBZ)otmFviSfnoXXZ zF=28ute-;#rCD!$PuCk1!r1u7SR488u?pJR#3 z5zap-32V$~jgDq3BoQ69Pwem^hbQ|qp<}%D7%wWri|X`v-Rb?I65F%Jad_PaZgPFf z=lwM`C&Qf6!?Hw5XQlvFZ~{;5=9KFtA<@7`M4xKuC0zp$8xT^2yg%v7WPwDYEiBs^ zex;{4vn@wJOW~gfy`+s!AqfSvhE+?3kK4M2Y(p*PeITJL|Jl!9_VssjBJ^>$V}u$A zC9t*Mud>XvwC90c;`01!8uuvEjAwXh(y1X$e_6B@vY8?!3YFZ=zGsiLg@hc-m>3$w z7q<>Z*=Q3JId+5Cl00=?otg_)Z1_{(dJS!D#K>{(eje6TX?2x+l3hjlD{QiE&WX{{ z131?n{{`(lj?gde!2)p<^paw(w>xpuQM2NVH9y7NhlA=Z+n#rk=OAevH>qhB!B${EJA3&cAn2sAgsi!*CGNEo+Xc9>`%xs$%f3g&0-AL)|dT} zh4^U0+NK%9(UbCe5>EDt9&R}TqFS{BN@V{iijYW;kskSNr40)jU5__|4fuEdyE!Yk?f^H=+!i+Bh31Mm5a*mm>IhIuEiu+Lwjj$p!uBkGUIMfd4g|v#o z7LJ%*EaBHc^bxXqsG%Z37yf zNVOeE{Lq)sppqpt(LgeO)HEcY=H*$V0LrxVM5#Q44NKbS-$aS5FYc5X(9$N3TKmKs zEY^18cbkVzJ4(|x&tR`x4x_e|SWok3xhNvja&hM6!gBV8=39NwD=a1}ipb>Ut$jap z5ZPX`yOQD7iERx9#z|AhXpDYiQ(MU7tIQTLvQtZ1np{zkvn_RM|EA;K!H`W`wNd@< zJd2c>h$*VSk?JUiNg_dpquNs^WC2vr>HTBZ%~2E71ut_8PvEEcgl`4+l_V#c2ibE3 z1nHGU?`B4)h5oVV;(~7jrV&Y}<1o=JNRXC2o!o`=+8kKJg0X$1$b~{NJw#i4I%z#+ z3pA%0R^RjIDtRZu(7ZyH(I-Rsw)y!$XBYNd%4m~D%oTS^SJHllbS-v$b^UWb5Nelwb5eU1%bbs6(q{$MB)B$iFf($D6%#RU^-krE zUzRt?$KV3i;38p#Qc6#@3{IpfzB%o_PsLPHt3cDU0nsOer&i_k+iWsD-9dNG@eY$a zq$2ctEMh%qWNIFM7R+iW@tMxk@okOG>M?|YLV)_pu4g`4f_lPG3atst5_P-yq%dfZD*bq#?3}cT1 zwHJ%tpu~#)8Uj%Xtt#!ZDUTI2gd9Q)f)hR#tysUo0FQdynVfkRDcae6 zWU7A_-3&zM76aR2$d%1pA)c2j{wcf=wvfFZoRFUEsR@}XDqL&r&R1QuTxjD46=f}) z9Z(-`sk}(tq2qx$LH)4XYW16OC1Tl4HC^52?67}&MYm$a4Z$#XJ#*uDqHxpY`9%)l z6Aqr&+cQUJK7AqgW0aQXwcK}vX{Os}IpPZNLY};fyiE}y>y&1$4u&t__6`Q=p@c5` z3AdJNVvVZf%ft@x>)bz(EJ{1&4U6H_Xb(`PoRiVOVg02SYr zPa0ZYr|7F2clo(LVe-42gy48H=dkQH-IDe#B%LZNvJ#)@f|)%y(GZd4D*sYJhK~I_ zHG$J?r1*%bVuZ%FhMKlLO{p2%%)D!h(b@V5C+yv9*=cmG+ftHNO#Du%eUaix*o|al z*M5L_MN40?;;xa~XpMP8ZG*Y`w3~w2t@67uNr!mKO5K#Ep`2N*EhBzsul92DDG1`p zT1Z?tyu%8d5RikWQ-PqL>|TW>UTH8^6AN49fe#H#&9K~oOvo>&PNga_IXz*#&HO$M zip<2zc$+w<$--fH5~Tdx>M6m`66ZPVPE^H!x1@3yJK`u@K&@rfGmDDotR3wxHO{}b z+d}X9NBEsQ94(yE!KBW;EHW3>rOQ;#mehLNu-CVY=ZmRH4b~@&5bn>d=uxkxWtlSb z9W-jvcad$&6}WgtK;(F-kVO&1G$T?h%(V4e zDjz7Y$~2kw$qCUTXmT*bL$bTXq}VB13OIq-srKXCLV?~p}fGP!zg-yWqXOI5;A`!$yb*%EGXZC!A^?Ju~oV;Gh_}eB|cbR_1bJX4? z&!-=?uxnsu<`=KLrT>1>;+|U4Z2hHcX`NVVs*jLCUkHW(_Q zG%77{vhNLYx=YJ{YlO&jTBP(-7t;btc@|ieqh_f78g|QpRK8DEjj(P^xsfcFf+z{- zcv9MEiJtej*pAShTmPCu_$9FwnTP;^Nx*Ejb;eZpmolE>V#;NiiYbkl8XMhw zT^jP)@~OciY`0&v-di4|S7Yx{`eEqvyUWbJ>2W?T@+M{RMdSVA<`JHkv-f_zgXZ@Z z<2(aKit5Mmhhc5pzXW~h3P4;2<8xh;z09M$ts7EsG{_o>MWQ_@1eGn_Rd1Bb5v8f3 zHPmRN5s4j6-YdRd3dzKF4cC6$P;NpYDqxb>p$+Io4?==h`h|;dUfg#J4W=363#7HZ z)mzL)3B_TuIQSqB_F_;9Wki^}WlG$B3IY=5u~o$jOPT3E)8x?q)iPJx75o>N2~8R< zBWG)@4r+(0S+6hqEGSC5L-n>exbOuGto%J5jW&I13wX)GS`wMkND`E3NWH9*ddTc) zMOpE$Wv1tOzl0TCyDG9xrC1+73ss}~PUmA!nU-5T-j)QxpY()w_;RSy4<*)>pN(wD zL7e4BBSO~aia=7Ob>B@n@Z%9onYVkM< zVdXr?AR_UcO|Hf;eY8?7`NGDkMA|9acfScjoU%6MNqnWYK);n~%6+dDFgX zi1hW1v32hDMSn z;~Iupd<0948Vj-R{zTJI{D4qP?TaPpRn5i|4k8Gx4nxl|*Wxd{z?3MFg9;c}Tl%m; ztscdnA+@fPSEDemn4jrp(yeXF2D;Y11P)rVfbMTQe$nd}ZO>Y@u8d>Vovxl|!>~X0 z9VkBA(y`zJCnq%OuqRL@u_Uiy-+M|gvJY#;+8eD!|9`Z-b980T!>1eDw(X8>+h&Iy zcha$KTOHfBt&Z(~sYS*r+T{~-?bDrl@YPG`cgY)Mq zQXfO`;E@=jmrZS@TDK?+CdwJU7i7E!e}nS^qjmN%tEs3k4c7EEXSPmC@YlC+y5_9x zZ0zbW7Y<6EpS^XS6+=8ehETo6W0DxS6jeG(#)9W6!OgOC({#4hke0j17{{OVg4C&1 zGTbUVsYFQSMt^fmcUfx?35;J|YQ4 zl2;ZcS;`Zr+7B7Ng$mc@T>`skAQh9%{=f83niqdZq(;-EW zIJbvL>gJH1Q^wYW?GYIZg! z?+S|(8COihb1P7iNJ~E5s0!H#|GG6pR8tRi(Nzh&5QqC9)aEC9@T2 z1FZmC-Zwm(U9J79eipZNQP4*M>2_Y#vI!hlyn zZyJ6;NcIEn-SXGjA09;hK$igvBLQ&jiIKlD-$w6sX+i>ZK3L}L1R(6-JG`h1h(uUH zN7L{Is~-~>oYx%zTi+l8TT)u{y+A&q!Sdf4mb7PFM(b@GS?8JGurnc_j#fet_kEKg zc^iV0tGI}f>ydU*Rh^gEViI8REtb-&XuHnLVY0bo+?4hrXeSw4jF~LFJrd ztu<=j$Q?6?3iNv9Hyi)8>lw~BekwZ}=0x%W`vm?ZdEtE!yUoK90!5md19F6!eKwH| zSItdyh<$^0F(%sFgQcnDb-%5%rr@)u9a53-{-n)${H^h_W-zRwt0^W0v;;DJK+r=j zqQ+kt$wIy0id?K-(C4B^V=md^li47&lTa+l0;60rAK0(N;0X-t9HST0`^2ZJ#G0Kf zW`pK+P0qQGm(g}Tcq$rUgp|2c7dvM@0cTPu!k+=&5`a=KS}%qP_DW2GOp@u5w~i~& zu*(%f>$STK-df4-4)sSvM*QMjzoom(^R}aI^MJ(C)&C$6Ro!(?PlQ~1p9Q9|t73f3 zrB=Ei02hLr*TRpnon(8|f4vRP;(EkXFjDQ}0HzrYGo#IsO%nv!4+?E$C7;4XD3_Ou z56$9L`lT!KyECLB9g-fGynvGR4KYHq;o z--TQ`%I-u)p{}%&5p!8YlbRb}5|LmBsXYfRMG#1s9Sh!Xq`H0#&A+oJLFaEu z)}%c$@O|D$W83a}0{8+1tg}jti-N!nGUKW??2T}5B^nK5`k$6Gfz#T5Ws-DP1LHQ# zy;*5~KA@?~-Xq~$231Q0eH$Al)3fvkId@~^Z_#^Qg7+6Rp3oLxe)%)TG)V^PdnU~%DdV2)k+Tot+vN6GHw#e%+D06EkX5boeV|;OVNkP#U2TGVPkcTm70W)Mla{c?`C@)O= zSMr+^7f$Z#vWyb9GGAPHL$hQwF-isWZuqlol9CkcT4_Nb{#dEbeC;x`a%){@ON*E3 zeyHis;k-6HX@|i_!Y(&QjiIKte1*5&9zO%#pAEJ;;!6h^gx*1tg+N!dVsBJx9EqZia`);$2xH3mGNjR zmf0la-lx2den&FT;ojE{s(IDF5?YY*XO;3Nd*xqX%T96(vL>oM;%jsb=*x8OZ zzgqjrEG>Wyt1l=%Ht0I-2jhspoJ`VloNXwLr)VdU|IuJ`t}mjh!EX@!NUk>FALAJ-)D1K2or)&&^c~S? z>@H>k?L$)cSZePYqB3>uAOZ%n{+UMMtl6X%jpMqXsO(@?O?|4b&mSYkX`!%i{G|T; zetc{!d10;oG$l#@;mTG(pt`=oPH!&S!`HO-#@xV6D`MBeYv4obQkQuMj|x+=B>$@9Mw8Z*o+A zvC)_(O?P2s?Op_SaR6B=H6zLblV7a>7L52cfvAM2 zb$IG)LMcrtc$5Yk$p7G^$~gVxBA6q9?TZN{>Q+<@(E??UwvBP^WnC1FW-Z4&HHQhc zMM5>Ivn_xvh+zO(@cvC}P$+gq7YKVuD0Y7dh~yLy5UT(|^2rHxhl1^Yfdvc0tTSWu zLUdx-1ZQk*#?H=O4ssV60{m{_h?0#yi@?59xd=n~LH z2T}bX&|=_Oup+i#oF)RZPf+8F5F^nXr>`|8>hlL5QJn%rUVsVFOK5=L>rE=d56p(B zTw-@V?Vfk=QPXiF{)j!45i*l3In+#5cO@*{TA-_Vn(yFO>yNgrIn`WdedjWD5$QXYyr$KR7ABKV?4G zm>PIem3qVW08?cHuXe#-F73R3v*{!qxj$JsXiu??u z1G>NaG2$bi?@ndj&y%4YwhHV|bnEO(v=CA7)SNRHL$t+*5BkD%l(yZD0uPL)xFqz> zt*W30%a;XZaM{`zqH9cg*u z=Pz0n2+qX-pROqNX$9f6{1RU-7}lp=iI<)xUx))atPnwVbK+oB!YA}AC;WyD_}eCs zb>MrWJrlctuD0WpWrKWlT?B2RMIu*d`7wcah#m&tH88yXS}w2=&1w8IUrb8T&L(K) zNpS~H_ZL)OO0YCNpsuiWZNsTqAcWYAJ>t+Pj@)t7pP3`{RAX|9l2UnbcKZ|*%tl#5pbv!O3wm3^L@_~DbvTsu>mu_ zqCT+mJm@JqDE#TRjTj{{$BvMX4$_NfWT|)(kkBrR>gAn5=+?Hx^!_j4HeKy#fKne8`vYM zP&*}G#NKSHoDYxKPs^Il7)5;0kMRm%oyy=&0Lriclmf&WagX3j?5m$^Lofn&^)09* z;R6-$U3RYcOAJYui}{WYL=8j^q#>ywX^*WhgRc;>0qwoaUcql+wt3Q$NN$iT1e&}H z;e~mHTrXPX8MHEgX2ic`yS{{AdVHb33{qcTKHqfTEbp){#8gR3CD_*0Rfn=fOzHc#yCR8hYCZ7w@$0{t;cLRI0qi&3C!adaB;}dZn z`vkN=GzqH-)0IbFN`Mu00+F-aYi+-laj5ypJLvh|a*uTGZ97^Gq&_3|VBRhqcatp< zQ=kE5zUj0U2=?QJ9tH$9wKCBXW+u7F3Ea9k)-&#D&M}fL^&V>NKGr*C9lIo)`d1tX zl9+3e_I$sl@2Ti8&4|rcWzp$jGy&-X2{i|gpR*VK$o3gAFMP@zZdO8T40cabpXHdj z940Kva0_IsgIos1>yPTa6WBL;PW&FUTe7QhC8|OozprAa zSk!~6>Ybxq<*s_4)~3~|<)!=nBJi27^uw9*ck!nH|6$-E$CYRL8-NN7@4lLl;l-W3 zcv-qtc<|)DwTSsVeO_X@jl~scL)42Pvw-}*qgx=(Fpt>kh`30MvqSmgGs@KhE@4aE z083_$gLY_jnCcVW_0GNde%onZeW`KNidx#K@?QI-=fmMN{C&CEAbGaqGTr?&rmB}T ztADZ6&**evqkoaF*XUHQePgj4=mLytBaDE77i%hmI@LVwld3)xSVzyUeOOCt+CQ{WECW8Qsg6N{PJGK+rnBm zr`C2$-9%1JRdWS(e-W*9ZLCLUUmXJ7)=hEuz<)W)pP^8rRl_YIekbR49=d|tFD6GN z^h9DgxCX{3O$Vo7L-h(yJO-O7Vru*Nx=KDI|2-0o-YQ0t$DQ;^k*qnv^A&^-`9cv4 z+{0buFIEh6xAIXq*63%vV2Odp->4Gea=!Ql?C?ADpfyrbMM15<5c5dRob0fK!)6k@ z0lEJ1`gpJz5ewJZSmnGJv+v$1gj*lA7xn*4<+GXtPsPQb>-_H_iH zW&Z+{0gtSJcCv5)%CoZm13zr!Lyr-xwF+_<1{5=D^|?^#~0 zmtZ<)#jIqxBEi!uIXjafO5Ez+z%ak_?q0s zs{q`){8+6ys0aoK_nH3k&76@@o4SUX&8SzYyJut13rMtZCVS?h<`(>h-kf)w`qs#N zGwk-j)4tv*LfCt=&9z7Lu+y?Pj{xZn`9s$rutU3mznB=WP=uC)zvV%g;6AR*}#{%U7;{*xr!YN9F7B;{q+F=Fz{qRf1kj_Y! zFUHlp$$Y&_tX0 z66UnAuztdL^WAkC@qq<=;;Y_bX1%R8xFsQTgUp>sS%Q0jf2%UVziSrq+0z6?ucN9( z8M71O@|vVa&G2D)Q3X%@42qQI)Mem8{7aLUYdET9Ge=c6kNkGgBYJUlTUJpbG`~V{B zHTop^y3=j(NrAX`C_b5cp6k6hHIY+{MV}B-wBrtymB;kKcFT8=4bGY_sxw1;2jcGU z4Nts1hK=>8ho#LkJ-4yYYH2Fraq8d`QSGLsVl3jdp=@U(*H~Ou*LO~1pscRFOUK}_ zOb=kI(2Xn73a!fQ{6!$N4M_};_Gq|Xq2rLyu$cz0a)oQ->X=AA7OP8}MD)->?z06cqHZ8$P+&jo|M4>h*jDUZ z*-c}}IClg0^^d)#R2~pj#vjk>1!8N`+RkFyRq&T=M`0yp9?#u!R=XhW>ZcBlzOU=q z$R9a+m^pHt<*!Sz4R-dc+t-25O&xsO4}wD*!_|#1x?SKekN15f;!wDr=fDw0bVxQO za4x;EHwv0yKuD4@hml7UnvCGx>p~^e%ZruVCagcJrEIO-P zN}AU>e|trgl-~RCJqmb+M3<(dMO?-8w@*rgy`8XPa+UjZeP|DEY#py3Zxds2 z0!ha7i^jxCE`4=|i7fAb$YE5xWhR#6jk!pT4u!GAo|iAFah zKb(i@jH=?O#+;^T931%Wl=7PQKzbgoKl%EYzLKNkc|s;P3&)#YwzG??`> zJ+!1&mZ6anl7InIfkhM5{Tz_74Rrol38&GU^m@p&dmK6&?-WdPg#am| z_o{w~`$$732eZrywEg833csW=D-sa6UY@` zpw{d59Hiw1t}aeQz;CIwLm5*%OQ(;UKA_Tct{p0ommh zgz_O{oFsjXV}s%0?i+9{eD{P*)j^L15i1g_*nM{Z7mJ(n;J1$$lOi0I%gL-M3Ky6-eC^PpE~(A{+BG+{?+9BAuX7jS-O2^w zz>G8~8kQ2;JonAZQOq!ji$mgUL3A;r~rs-*wfKA6B+9Z!>c0jptY3*136hS&nDCo0!LJzTD`_;x@A1!&y+mZk5kh zE%{#A_;7ag6pvR-E8oaV!@i;qiMt2{`--WVfJ8l$P%~Ply|%Fc@+StBW{){piJ7EO zGK_jX=S;OqW42YAPUX94K#59&zUX0ijcnk4L+Je&n0~1aMUqsl;y@@<(5@~+%FuCx zCW>m5hMETbwz@MgWm{X#VBdmH)1}X|>EJlyU(OMsv931h^C&YGzdLO+B+o-7jp4&-oti(z0dL->zd@%Bd%@70yWUk^ZSBtZIOJ zY$OvcUq;a;!YGl^S)`-ZTv5b0M~V2mK}9d$ys2tBl?VflWSct) zOUUBA7!x^iq>igvA@GMO>?HojenA0)bfj+Yxj+mlN9eisI@K+L#%5)O;WF z+-#V;WgiW8Ia}IMpN;|d{H*;Ftg4du&ddY%E#_J<3O z%gm1t{bt3g)})a(s!Y#9Vj$2 zv{M3YF2zEK1q)npQ0F-m#B3C-4J=Dvl(9M>~+WVW1q% zxHc8V9UI1@RICh$ObhlV%AFnw6=Ge|%lLyX3$-Jv462Khr9XMXm&mnZ3%ugHiMpl(xNk3Pt5ysfh;UhpyM!+s*!bzSl zr!<~e#>v2UVbbmPvhdJ&dwIL1a`Zbk=pqR+ixvuF3FLhEPQR_$m&JAU8~6{gN_AR@+q)pN#lIe4TX=L4~d$yjQ$R~^2?W35f^W} zG4h09jY;0YYvv{V5K)3}W_*#;<$)Ar$%@~RY5f=4TjVAgf9nV=mfKiv1N*{jzVwx< z^p>dV3+FS#)a2FYB zg~VUVJLuuaX{qLj6Ba4~-~Em!Yla7qOq>5k6j$iAdoA$&9AdY!4Il-VJ;{D=eyy)n zcaKrV&)U)*tG=S!PR_lNk!-nIaid?L%G7z9$SMfS1KeRCh%)26P|4C#1;Xh@Zp_A6 zc%_Rq))QvJ-)c>lec=VU#o)U!IzqUw)$RSi%lMy`y!wmu>Yv!#i-<~W1!-xaonsgy z_A4i*9H1rW@EtTjV!1rNA^M#}r&+yy_W@oJHF2aQAuk%7A)t@N!bGPJ&%*+zM5Y7b zW&bl4kK24YUn`%NfK^d0K0ZoDv3Nk$Cn_GK1mPFk)yV&=sYtwl^Ik^xq{OfjL-%D| z1x87aJnQACS)}`ktOXo{c*E=ZHGlq6DDc%MJfX@nsW|L8FhQ{>q+6X^5OwKtEFEAA=8mrBC&CKs@esud1p@Nip!SkYZe5<$%_SVz=`4sb$ zZ5if?y3T4@f0<2*DnJt}On`hfW|>j=DC0v9Q_ZFhE@y5!l*<%(udy+-1CKap+)Afuo*N2&o7v8Ii z{J@eji;Zz9{a6zZqZ8)IO8Ic;9~5!oP~42amsl{$DJ_-!WSG^i(3g!@gl&_#bzZjJ zEg=!DwA?alKb16k{B8DjmNeZ%i?~!hX?s3Ei!%#}Q+l?&W*w$$)aE+fT;49*3uaJM zS9+d{6~=31d>9_5SsG|DS``#?JSFHXZvxDtoIqYG)hNGus!XmGs&p{3IvU7rx_31@ zz>SI9d1PhI9A^~F7lgn9f8+K`v*6nRl$q~9E%jD&Rl)IxoD43ax1-1o{rhC?F|CJ2 zhL!UJ24+JAtxcO<-J-{<@y00w+shO@d*njl++`xWwo`Zs@76^w+vB6w-Z!c6S5|6m zE#046vvm|1^0}ino3-v1MI4MWKHbESrzs7+?#zCNGF*1FL-fF~?cL3S?RS-G-ct+<{EqCyDV0u;$(5j@ZNIJ1u76PejasY$h2|mGCL$Bh zzU66Qj>B|vJ`Ua=lny&e6vbJnxevRZ8Md{Wrr9svgXu4w{IPH2+&LwhGUYSOoT$>A zU(eL-RJYnTBa|}X!vlck>>>^N-TpM0w;F4b#(C> z8xd})8oC{vNFY8jWQ7L9zK9A6;hcT&4Zbie40BLX8s?Z)8*ncT4#3FFYcrw*c$tXe zeUo=gNvo&QGwMHR;LK8d%}q!(v?o4hvSEiLW|bDHz#~p&H=XuSD-`ggW4#(sz@wm8 z&3o=0C7hQ@ZHUk2MaFSG3z3JPTX?>G&aNfD?RMMws1jtuXY*mZi~C!3IeRkIEkgO> z3Z#w(kLLlTP9~u>CD}VqBAg3Rf?hHbl}rRtKQQiuOwJCPo74Rk2ZXs-BHwYnq6qDM z`Q3sAb=4EPn~+8zhVqDvhTK)ACV&Q}OPmilWIzG%lV@t>?_16WTj&= z&NbCQD}iPh;lfm{=$)*|s!H`s>)f{pOf9-esBA=oh?B@HFCT6khnHWmn{PUPQ=#6% zx7$<&M5(GOn$}g7)JaP0(ha3nnBsOe8q2M4%B!R)VPsrNN3PM&(TItRJ4LzmdOaCw z!Rc=%tNd7`FgKGnDx@?l%&vRrO79O6^*FEx`; zi}yV`8=mRpl}5|fwZN?{`^UP(y0=(n8V=H#8J`-D%VwA4;&jJ7tCit^Def^=u1%** ze4_YK@KLO|&C0}Qm3+#18)#)S;bjQ%eF#Q^P&l2ILEGOC>AYAp)$S5iRyInbLrG#< zU6qsM8Q}|JNlrtXl+FvLuIlhHvvy>Z>&TeqmOLFzKV4$@_#Y6Y8kBYG+-xNtXQC#X zH1!p_{y0uKAY++Nzs8e0!frW~o3tqPHJM7yk(El$BolS~5yB-D78|HUNT32s4;4oW z8Hw$sMHm`N&mHqO)2QP08J^AL@BV8N>>*~Z;Jn6w%g0k{%=(O8IVdMV+4(e?+^M%1 zII&Lkw&t(=9_}&xO1Qq9T1`(~bRuA9fNS+_aFvqrnso~tFEQ}lg7h>-?h!V!lCE6H zMoM@vutAP=UkjTnq@S5m^hD@NDKL(VtnC8VB0oRKT@#7Cw))1T{wZK@H+g<2i(zdB zUF7hQ&7FhL%BIV)1Pzih+}(M`X=Sr3Z3_Dp8G`FP#3RY9nE&`7OZndh(HFPS6hjTi&qz8_#`S6N~T_t!3xhOgkH_#=1#jt8ZhOK0#)z__mu>w z$ClTOFA=l+O<1Tbs$fwbZyaZ|l;K*1@?TNs4E;1B#RjpU!tr{#MX4s}k!1|zx_rqj z3Dg%N>*z-GAiWD#l_y2#<#UMWG&o{x;}9RGXcvhtUOOq&W^*Q;O|3LylV1s&8<=+I z{P9@_5A*2?0&;5)&8E)I3@+w9MSY)4&K6n@Vd$Ug2`O=!4KGCeQ7R6Od#8tN`NfNC z_q85qN)ElXs5}u_IF9FS)pr-29phrIo*=v@ltKZ26(7J0?gvLa9qLrU>C^}!dhp{) z*1$S$^tayGzjp|p3?Lp9+ePg}8fVElt$QWxZWTlc4Yr?u0WLiEoHX_nGt|ho>t%1@kCOeOD zV(A-;0$)%`5NIyeI*4ZC_Mq1KwGw}l^sx$2wr=NrAWV4^6<8m%6mI3-0#_uGVi^!Q zF8#KNV29K$lL?Uf`mei0H!W4^w5nooGk2@0NL@~H6prV#NdbRoH}6gu+SpvxW@VbY zumMS`IOWthpc&6QUpI;%W1K%k%DtCCH`lm~LE)tbfwR zt)YJ;@LoWvCUY`!n6}^XmBmvqzwYX+rnO zQFY+FkX2A1H0lel22K4=+7(w6xi^;~Pg6D?U-2Ri9^(&ZN_Omih-0>L$yHw0_^ z(T~-v6w^K}@yx8~s2`jnF>>72(tSgs*3vyIKRIbJ%m)nL}PMh`{66h<#YKR9H)sglt&X%#di-b_+cYLxORFR46#qhxpk^I z*1Y5s!@dKX!#XNe@#v?Y&4~$JEhm%9CQHM~`s?SY^z2|bBXUlXSl;{HyE9R%hUBvm z#S*Vg+Us@ujyZ@-Dyn@jyZp{GZ09h)mn+Wroouf5$dz%qrWqx0qYyVouYjtoEpu0C zup`eAv8aXjCgJ?~Vv(SAxGXB)oFsTn$~*XcA=;^sDF23S=&V}DiLQ(DBb@|Y)>(H? z5P=rH#flJZPh!KOGAJ;uV-TXPU)=q}>~-RqbwA5CA5He|%&GhhlOnCYm2)t&Q0wy< zgC?SRV8=@kA2L0ECsPGE!+&*NxD-GrN(Jul;hNDIS)~?7JWx65sy8&1^Zz~@UM|}Q z6+@{EWr+SmGY(T#$hdCz>yYyMgoph6Fp~_jwmUI5se%6+c8&|C;3sqZJ4&Aax7d_D zg=Osoa%?1n1E@ySpOwTEP4mx4a20#s&m8Cp!P9x%;2EKvR3N8fqWdy`1tcYD( z(7D{8=^?69n{E_EJ46Yymq+Ow+8JP$|4teM2!8a}Z72rbFEF@jxNq&I4|OLHE`BLx z(0}${;lV$c`7UW^Bn-W&aasZkV6*x1l~v#efPP|!LVxZx#bw5V#Ye)}CWa=~hOa9< z{R#CH9+H3s$83>Q7lySBHEA{c_NIe3z3Vb3{aXN=8^(w7H>13d#bke?Wmj|;(%acG z{f=vL7Q`4z+*G9)Om?c;_`Y?d0_|0;WMT9}yzf14fh7YS8TPll#-Avaf((XIzm6PQ zpGKxV>D?FXYfpDalEA9IMSy6WoX-y!4~tauqcK@vC0##pM4vXu>N;%aPM0GR%1Gz_ zeY^uR0Hpy1YRJp zREz}hrXjzc9;VSgpgy~&H8`OIOfx{;MMt*PBd7*mu+BshB})v!LMQedZWQRVdeW$F zWYMvZlN|8Op=zF0ug)~O5zC_ry|14!-jKJ;{qEhkqDuF-JW93H$&!?nmC7~ylq+qK zd@^Zsugj7=xQ**ID===nrC$p1VQzS8vUuBj2OyhOI(r2a?Z5=`t#66BQ&!r!EzBPn zBw%58vig!0DSinEONKg5vry&rn)j?Jh=X4QiW(^MK0ud{NjX^g3$oR0fr&gT{SqxakSeKNE=3yAa%o2SLWVMvT5S7_o-AlDWhbmzr*Jw>;5DxyJ;LgFy z4O|@xBD`OdZ#bZ{D+|wFXjQq)7z{%e9*Vv{3^$>fIXqpqj)SrwzC3}z;5Yjid5OcP z7b{n6U5h<7h<$IPKL@8jwxELWssV+O|hCqFsv5%?S$-LZv124uP~Xs#4?|pSUL%e?Tv4tGNug zo(X}talnPdOKW^Udp!aD2#8^Y2c}lKr>?Ec2ajlt4o!g!-PJE0U zNF8UnU89ksj(%CcxSwC|?AA`2Q#CV5vy{+NSvfgJm&;IJC`!fAR_8hy%BYt!JUr#7 zs8}d$bbiEC)muj4WmFiGqSxeEw+VMqXV-DmUZ;4;tew?ML%y`|G}{o@ zz)mJai#TT{O#06xk>fPnb%iFNSt+cWX6d z_{5F#mE?lLC#99}(HT8E7p6qHhr%1Dd3MABJr&x_kfjT)tWf%r{P!;w*>PWnIi$|i zb)2E>0#%`k1pZvR@dCB=@m$(ut7^k)QK+FM0mSXQ`*Z?veni(#FDV zw1uw70nhwJ&4vAn?xkbbn%ct;8&p2A5(B$BM$C%=9-f zl|EGm_bQ)EO|drM%l$jCH`Y*C3zDWp_KsWCZ6+O*S$o=C+>-1Y?m}1f5hS+1PhxH2 z*ifv5{15X2>mCnu*#187{Z#&GZEdb-jTUHJgMA6x0$H67myJebXak2w3MC!5t}>qJ zFA2|_G%=Z~`MHNBXxrAj%De$@Hx@#gY|kk`eZ0xM7c}8&)0&w}Oh*=SWmXlmQVNWI zWTgw=mSZKLAHD7-A!RcDdg3AoTyNCESM&dL4PdNlz3a8}7Nxoh{@w4hdp-Vcf$Y6| zh+bG@$c1Gj&K1@E0r6ICi}iZ({cXFxKkUkee~84cnop7n7jlaepv*{=8;Z>H{&^-jmUtmfatJYI-U%in4(1#C3ZktNvvcu3 zehq)eb6>Bcy)=>L@r#`8TFkfJ1n~LcIL(16udh|Ybh0Sxg z&E!zU>L$t5FST*6KV6XG;eW$dbz5R5ZC96<-F{WFkh{^aeQU0o>hkck6L(7KKXxHI zLMfg_foXSPTXol2SE@<=GwuA6EyPXp9edE4FrUmiakL<#Wl8dF4l}S@o+Q>|6XokU2ypQP^0tFr%O z@YP!r^m=^dRQ?LzKWJVF~fA-#&M8Csdn^M+im?-Fe+-9OdxE z_?JBg~@UPkM{P0&_7KG~OzP?Q042j>}9V1@1JEBn~^I2wR2I5{&s*H9)z_YGeYw3{uQzGNpzhBwTuVSQFbED^!N&d!J;*K9I%9cufk6FXVmxu5+=@L@w63aBv4}01py!_0;+LI60+StAjedf1Cv&w!uY5HW zic;q^f%^Tcwwt`Tl@A?)EzNY**~ZiBf#Cd18dXkpHJmxxc9HkeFb`Da@McfPY;G;u z`g_9TQ{Hb{`#w(^hzQ+JPg(C`^I|eD(dLxlvQDSvr@^$!hHQT|<+6*8M@pB+W-GC@ z{Ot_(lRykJ88SbC^(4b z9$T^$`-}@m+mWc@fQm~F*@Le9&_JJWBcJY-FC6pdr|w?sZ;sKF*rUq6TWW)e!0)1j zFoe4wK`?`#9w=DD9}G`CZ%#j%8+;V*h(mtN#4r?O@3ecf!Bt>jr((`v19IahoJF`n zh{Vm8ovE60S)O8!;2M3$Qhfqntbd6!f!Y`VyF%KSUHILpNM?sLHCkZS1Dk?GOQbz2 z7Ag8Mr%@`g-l7zk&j6(z+5sEENfQemwPHhgot+DgLn$2k0O7qy055{X`hT|6;4T&f z-68u91Ib@sX=25uyX^+EQN}vmA}J`_0!q1FH~U&-9;$&sP1!Vn zVS-)>fZ%lohRhwoz01t^(}__YZRoCw`1rY(@c2D4P-*0$KJop6`*hy{dS_wq5x!#L z9zz3l&Dj9|!LKdl1&UudZn$19Ia-k4epIXk^U69_KqxeGw?-Aj4@R)h$aVB=MMQ-F zntNzQbSGx3dJ~wkgcl(+-3Pv_93ZrP2390V|+6ZbQ<_f6@S`#_-$V8ZfiZ&8-#R}51M%=g6 z4H5Vl;g`_+o#l!bAho|EmDnBl-YJpKo;!^u z!o5MezL`cY@QEAK+Z1XB%a({QV}MioCr~a?f65NWCsogdm}!mZt%}8Y-)+Q+Jm^__ zDQ{zmCye1~99P%EiNWYx+-h1X9V_e2|e#Psh?p;2QvpJcW4Z~V=Y|kf=L7Uk?5qiWD2((3 zRt&p5fC@sGm@N`|+6^;%p2K3*_L+=lk^TQE~u~%x4;SFOO zsb`53PjBZNn52RJ@9djn_UK|F9ys2i7&_1FzbmITwtXD%+`S(#m1Lf%Fn(Q@4b0z% zxw=|~vdvrSNvxMn-G0ZhgkD)AxZZI4^e4Yedly4nv2ad@zzS$mX<1ketu>f=3b96u{^}cL{tV zD;OWL5Aa=>Hu>JsO<~`sS^V>T|04Rn;RRVENYK13iM` zfiHtiLo)#ua+n6M!7&88GYb8ntT}{&pF31SpJ%&y8uEpd0Wu)m3(4_D@-1)yT89DL zhMA7H3=~pNx@%)j|5GXCh+$Q$XQLMTT%mVF|s?(3&Zr*!7a>h+(`Y*awR55xRFKOoK-$QDL5@IEx{N;5 zPj=ENNGH(mfgQhjmDBm$ZNDbnPcOXKa047L=QLTV$ z{#GN>OL^Cd!HZ_zpy~4Et;yEb-f6+Q;_p|K&?oxtZNpRrF3q!g`>rh{8Z!z*#=2O- zASYJe>Q^Z3=PmoVE$xpN>Wd-sE4vb~F}V$iPT5XSVqNeJDFN~BVXRc67KU2)rp%50 z-M)2fJ~T8KQ4##;83Qtd?Sl$o2Jt4+P0}B9HgWvaUIvRKJY*hv zzp1+AKJwm-gLZtTO`ktIC@}R>&{7g$beFAH+`BZ*%pLRcz%y4W3A&>yC z1_vT}l*fX@`(M5hMT^7rU6?jp_;MmF9XtPg%v$$?bRqnEk-y%V-TwH~MZo3p=OfO= z1+mN2hU+#GMfQ%jvt)i{96_0YsN2QB9HdR@>!X$my@#F$wR9_o6C+#Wa9rCff`sK1 z!PzEh^1`~~a2OAW%i9%#S&9#nQ?VVBlf;_g$-`pr{95yha*|H1)oESj{GZB@X5}$@ zSL^Rf1AM4=LuD27wKIP6Gh+qwJBxnvLcZQun~?o$L|ZWD-dloYh^wa^pr!g$OK`5* ze2P_NlCqUxEjB97CuVI-Y7Dfprm$)1X<$Y5)Y^sYQsvVW7K7~bmP##XJm?|l6VSlw zFjss>U?2uKr%37~bWLH}KX4bgLuQ__CU&_x>}^}OtgEt7bCX|uOWf3e2%)gv_bwhP z;h+$}6i>(0&JUUR=Y{wUvVrqyJkn`IlPppM3TI zd;R49FZ1NfolyR#@_#o&|6k_Gmny;rFdx3m6E1*l@@0hpOb$Q}J3GMS_}Z9R0pC( z|Bw0ltbmiT0Zz=y0#HxBo&o9w3m`q8?W)<~~4AVFjom|DmD)Y68e40Br=IcK}KP zgcE>1@?QiMKmz}lh5|V2|0t&b_5!-|Pkui$pho6@rThc@8i1k#sPn5^-~NlZ!p#0v zFQCnU-2ShP10a|DEA=0+n!rD64glKmjSGf_3oxjF4N$|sDX@H<`yYV?Ah>)DD%U@K z0Z1?aVTTO>|FeE6Isk2k^-IhFtbNYH`Et^?DEgj0{@Hk!t$Ti3lkT>diff~e{*Glf75#T z$AtJV)(fDs|Gg9cXY1wbF8}A)|K>*MDbAGrhxIb^N|ePP<44s;aHpT?Ex2Tszdal8 zg!uZ3%cJyddb)p9N*+zS2pI%;w`Tho*oq>4=uJ z;Xn~NJw;RK7m~eH*WiUeK^Q)XAEqH^r6CJ8TTr$|SeKkc^@4t@FxtE)vs#w!wXm} zEHQAKNCJ=+nJAI9u(`-LQZT4gUzxsLUtG7kl1PxeNF>2t8$~eE=n)|4;(o*bhqkv2 zjwD*Hb;UT6M$FR8XvECS%*@Qp%*@QpBW7l1W@<4rGhfZIeQiIV>*shc?w`!w9Ti=@ zcT`2~TJdErl?0X(5;mx)ogC@Rh&B*uNQXP%k$116i}PuQRhHY0$Dv2ZVC`&x)Iv$A zykb$(cO(H6epnHL$351%cH$tedtg31VmcxX<>#?<4ej$I=tpURsIIr0!PP4BOec5t zJf+vzgF#by@iWQqoF z6mc@*es+L2KoYe{(P?<@@cTL!_;E*KlSIYa>wfl7pErVRIybwMjB%iGab1>JX6X17 zpGlgP*PyM$KC?hxIQT2_pNTv48F`lyC7hzDyZEEuFVj%7$HMatu#B^P^I2j?h%-s8 z@*I)*hGtDQ$4G8Ii>XuLfd@kUo6_EYvVZgpwdUYph2HI&ka<>kN*NyL z@8!9dfWsBi{>mad;(U7p!o>R%d?aWa{ERGSDGeLD`F*rNtX>a1Xg7bf8xTz!aAs1yKy+bzXQTr#hqU2V+y zOh}Z#gkxQzNe>10^)8t#?Ga+jU?tOs7+_CwLw8qcuMfEiP2?Ud0mPJdl z+8-rL#3xfML`-n>ODWzt@OSMYsBQh?Q5HjTB`Ec3Z%v;f37!`$L1;im)NuCZ%` z7>0c~D7@!2U)EosJ7!`5^V1H!IHn&w&rSrrjD$XUtFSjgzLCLlSN1|r!ot%=TH zN~NYpS>eB7Er{Rli{=T7#610Y6eti&;l^{}tJh3|q#Qy%pr}`>LUMtM5TdBpphBF* z4<`d@%JblmNYJJaG5x^uk>~n`uNy9>X{y^68>X`L23FBqTN-wWRx-&MS)bzV7u^g15WC~*aew14lcIw4ABV!(Yc=50W1J(?XvMqLH zd(ov38lc50WX#o8v*$24{lR{Go-%j51iFMB7TA5TCWHnySu-O6*@Hl@q+W>h0Xcf>N~w@Jj*YLP;!g|hy(iKzh;R3CQ9}B4# zcJMs^iY22pa@WI~sxzlUZlMr3<&3(10~s82P9d($LqE=m$s;ghz}#<(kwyeOtNqc6 z#%ouf|78GUbk4vQ;MRl&x<`?4C@dANvsfA|fj^6GVktk8=N}ohBqhn8W^P}@7NJBqD(M`f8Z+ps0s%(V1>Ogy_6y3+BHE%<9%(Nsx+B& zz!ZGLhvX3rB4{dANl``1=gds4|KRneXwb+=M$!V7MZFvK=%}n55jPlSKL!^TazVtd z!B56lK$}-LC%(#u5Xyu!5LF&zAYU97HZCrHjE)}Umv^nToR5dAZ?i*Uz>Oj;u(4OS z%z#Ugy`@P>6H$JrnQCS_QDRY3#!kANt)eMU8Obgk;7|UVAJ0%rHetriuiM7wg2$jC zB2&dc=(&!wiqh64>|%UPEWGsbO+&+B&~`dQ7uN+Fl#XwaDh!L^vwL~9vpy;TzdAIK zD-#vFN;>)ANxL?%L$3Fq3l%lBqbfzqVWv}7K@1bZHP zB0FOox4nK+I(`e90z=d6xr2d}RODmh*brEClJ-6~1%8*sh?k@=wPOuZazo6b-WjS{ zF8y<$Wp@=-3LEA7^&sE!AIqFhz0$_<)a z1lc8}6ch-KCYdezrgAz@wR8%4X&7LJi3!TP$l=0EPXjwO$YM=xHSww%a##s`2g!$t z3l72>$4)6^UZ`a0{40ls_^8k%ZZfS33bRh!Ga#sCWkCDc8pI-jEquCuL`$ON;}yME zBLSFwnU~!NIROrj+(8WR zn8zPp!>Ez`CKziM@lPl5iEBhHk}#cM-y-6*Dn9;f@)8f1Eyd5$3%_b>zJ7#zw=!7` zBUDOJHRx5nI)ng1J5)JbRNjkaQ7AdCA z!N-AGTKr)l$yTrmgE7q+1vom6jZ1ipS*skBW`#nd$-4ttVga@N%J><$I;S*@=TUywS z@d^C|flblROT>^abO#)hP)ch*JMU4B(n^gX!ZV@QasDSUop04BDIpKKA74gITiWo> zu*0aJ03?$NWJmtApViKwIjJPvo0ZHZZuxipRvTvT+-e3QCS74(e5@v2a(mu{G^nnf<#)O;V>);c_ea_C zzi!AND}`6w`Of<-K|c+TmgN3$ju3#HyCKlvT2{j}2k!GfceG5*Tylq9OLp-CAQI&J zc~bGf=hsh^)8IZnd9?Tv_3!-!f%~OzrYrzt!V@**t(N z)pi2!ck@X3-{{A3DIJqL1Ubgv`#bhsr_CJXYCrsuuMg@khB~P0$tF<*NT2-GzVNm_%_WPdcVO z_bGiPTWtEi@1(1_dp)e*TR5QW_*E(-0`b&FrWs5XWKv9-cFg7W%{9uoG9U{tFZKnJ z0cr6~WavBxA;)VKG3e!J8P)WdVN_ANV>&s%`_%+YbASf^%jIM7{e`H009Y*_aPM>tmY7g>aQZ10PD! zjhzRD00Qg7pxmmm-^x3WxjP(bdHUG>ZC%73|f;N%-X_iddTK8-Nw7^Np;G{fm3o*T|xXN;H7k*QjM7gnqrrl_ms_OoUM zB}P$ailk6>EoH^GTMcT+#kN_SvY9_ziDu`FRHGslBR zjC^g@OKiJVQIOSe<; zqoybobYnA=)5#T@P9vFE%zA;;%N3ku~)y4bg(h zH%~M0vXG02^Hl;X$iX?g(sPuPlJ(=N2p#M`4O>7j=ig84K;eAc3pz(QeCq>;{hKWU zqLm(?tK_}xxuenZvyi@Vj|yWY@veB2D8J&|aEXM|ChxVv&&W$r8|u0mVkjhW3HEZD0FX#oo*vg zp=W4~e|LkppO2D>eTY}{zHsc9ixNYRc6=izTO8}wmX^Q!Avq(_jO-Jf8@**eKLa*o zu&lcD10TDV+eNQnWgv)u% z{POJ&EX%S?6wVpZopgYyW3Ch zn@qf@7`y--gZS)R?}$5|KlrD<+1m!VV=Qx<8W$}xe4aB>efLPiHzTQDMd@zz283^A z-#@|cbr@d7i_!h(=%yRFAOLQV{9b$`-z>lF3>M$e-&{yp!za!7y4u1es zA6Mycy?kS`hb9!7y`sftwIb-_vmaTR6^Mu|x8HyMM51xp0MY{ecrd-Fyq)aA+>dsk$TjnTmrp*1`eYV=z&TL*C7jUm#$mgA#&<#QkEX~=U+a59 zBLWLC{*{?(!^DY+hkVJz4o{wy_+Bn~g#=6EM(q_RqgT{B+lHxCNLQm-YM1P)J(vmt zUHh`}oD!aPxN-M{Xa?^fj!oT*(7u-!P>V6tZ?8GGSg;4_Ds4M7hZksVr(eV`K$nh5>&j*!$})`2a>e%N`=3s z0y@#s-q|80*LbQr8ua;iBI%oEKTcN>bedT=G z-HPth$k;2-t@NnOeb~HT?(zzAy=F}FI*FLFa9Av5P%f$4M=<$)@O)ZLoR#ZsW~JoJ z0=;auhSVx7Yiu43a3&S?U^4I+bLKv%dw5=RApkzzqQyEKFQ`1h>tzei_N$M7wh;S> z-2C;W(14{I-8`dIDNkydMVjI|MR7op%)QEE(M)$1r4{1}Z=9lB$~h9-`+>aFF=So# z>M_sq(^k#!Q_T|_aa7Q<&0)QxxzRX%udutuv@=#4IaM^#6kKJ_+&_z+qH7DzG%aNl z27rlmexxWvrk(bV!Ckl9%rNPfxBYz0cyDinyBPtd^S$(?p7h!5>|iA|T8M68)~&SpR`#k1*qh#y;^j+LlCI*E3}0c6NxuxW%giylrS zZZsW(n}!EDj*i_Njh#d?10!v%K%UlS{?M9~QA+{qmth{DzYrx1$UJ%-kTWTr<|leN z)jHCPeujqK!HF3I0M354FJeBcl}K!W%|VGyclk&6u+m1h0H>-v+0rNvrY2~&@Q0bj z(%YWU-1xuiZsf%T>q3iQHD zM|^!a5CqwFHICiYIG!XNYU!0H@V%P<1ZJH~tfEGOddw8=%oF+1eq=msTj_Lx2OZ$d z9gRcqbxdrmosJg(@FtoAv&mDKkf?GMJ;Qt1*?zs z))lbc<7@yiVOvpPILO;?&VX~O=taU&-{eR|Cn*fk042M`T|DlBy!{w66)VC;HFYHH zQ5cU**7khiMSp3p*j0By(PTr1`xF?5dE?gzED6(rEn`^=>24QEH=j929D+VSx`McS z^821aPe$Q=;K5bJ>0KoW@y>z9sW(q28topPsVQI4Zg&fPs zTE!TK(SdL?@|rWNmi?EB2vO(IZX3g%ePnG{&@AJiMtQ~5tj!^L!sT#qrWfme8`6k5 zhh*k57k#C_Rc*(hF^kryLpf1|Ej|8wJ>;Fz51r`C>xYzA*y@!m_Cb|xGmBs4*Rp=t*BGB2OeYVPZfz>VQqb4o5S{~n?0gj`86{pnXARLugGnEfM-O*xolB>=SPGU zr_<+SZr7ke{gh3G%>5|txSRK@tr+~>+bOi=B>GL5m6_Q2)=On3ip^L2+GAlR&v?g3 z%MKB&XRm#YOVvrsA;2z$fVB4`lIU34=Dvo{iSDJrq|oO|9lN7;v@4mwl!v!Y_6Vc= zDpj;cuzeU85c|3QYLumACi$%0J<}nFiC3TFIL8SGniQ=t$Xnr|g^iP7Yo)-T{ekRm zi@ZVyEek@%qyME>IW^vG@ffmH2`4CBRke;4s1`661UzU1PY2O>{t2_9Qkt-CS+Hz5 zF8RX8p2g+qAj?wD8Y77@&S)Qn)tPU53iy0_&@B)XJElsIX1oG!^tGCcR(qhG4rQ}f zq@5RB)-K6mbKVd}t`kCoezB%Z6SbmupaFcM_2KK~1Hp}k6IVu?_PBn;XNg<3w9}CJ z!D_eg*`UhH^hUCWNyj=rUeTCGcHArMJR(hHea^*b|mx zsf-h8Rm4Ff)03`1K7+x3MZ z(x%975}XJm{rpN>wlSac#;$v!TT5-zNOodFxvb-!3pNei@P|gHufwB^wkwKmGH^{d z6n2IYx&=qnnEY|={u0S9I!YPg%7sV2Cs1?wdmEcFi`h`%>|UnWE(>3uzH(2Y86*X* zGmG5>?vD9MY4X+U&pK3}EQ_7@>Dxx!z!|eBkj0zlQ_M>czQ;W#$95@DTm+d$`Ybdy zHd(|6kd{f7^1gz3lo=EzOtTXns2AIA&J>}c{Pdu~k>&{v#mgDzGj zg|Elw$U94eQ)$zkUqzE-l3?M6w0?QunMQs_61H@2l1Cg7z+U!iTf7joZN}sEh zVBC>Q$D5u7zJE=FX3u}SLN4z$H`r>p>X+eFhc;zhIgZm=Hchqts227}rvpYaWz{zp zdkLt1EwYRlX2$+(x@`J&Sv2XW?U37qiSAZr8}w5Y?wXkKxlPvkQW}0F`t!?|x7y%aK-W$G{RXIxXLsq zUl|x72gq1lv$k19JcMh2t~v5235|QQEhfazlI&@)F8tdtt_jLr!w!F(t6gb?SMM%n z6dXuot-9dob}=B4TtFBiD9>hi-a* zC24=SZo7y1Gu9E)VX&I~6Z2eW zyUMxpe7k?A1Pr_PrFk~%qvCr*06<4dluchdXXe|*>U0*~`JM2qrD%wjId8?>T9kHa zi`?`=>9=v)#tqg)&AwOm4R(wAi(h9Q+9uTK)~8C+^XG`={^8G=T^$>jJFjTiU53TX zzuk{)?~_~+K0mP};Y5-R#~gu*As;{?rG;l0G(hGz2dpp^3 z8lnLCt+2i(Y!x62;0Iv%G~Y{NAmr&SwcuvQ-wlp{c;2R=Ozg6Xf@hz3ynL3Q!~Sc^ z3$2Q)9BSB0RIAyii+F{4JaRm3ekZ`87Rr8>grdKxVT>MRB~=u#SJ_r0@5F!J$wAek z2rBKWlXT&4-HDs84%k09L+%1dI<>*;Ueu!0ocWw+)wY?C01D?uH?hL$kN{K%IurD1 zQ2>fr!QS1{U`ri(PP?uMm&7Sg$M?*#0eL19-y{)HvXwI(^RkSUo-EUqV%yH7oVb3| ziel!*)v&xwsXwQKBh{4O%J`X;u1(2HK)Rc7U+S68(03bDbx3abao-6G!tMJ9Y0fGy zHN-ligx9(}@u?Z_kJl5OGMCD757v~8RVNoY3*n*Pt20bo?9756yqyA>u>;yXZT_9V zPfM_mtfs-H-B`o5Am2OAg#CG&0C%V@;ffku)J-!$(*PZFw4dJ@QY@g^QaR9nk14OM z)`@cwGt2&pTPOcePG|mB0`@b;rSKq+U=nOY=eIMlm+_+ z(+t`yA2<$T1lpv>R01YU$U@d5TkmVkpB(-`8YWH zVgCD+kisoHBoM;1EMYye<$uGsC_Nh#^1s19U&Z#RXB#pYAIEFpUy{ue>fg~<5=LXG z?T!VHq8RuaYz%w}#BIL$liJ4_B ztgqt5n5i2FZp#En7?cGI%=U za*=t?({6&^g0QHW;;dmCVkKd}xCM=4hY0<$GMd+1B!KgM%zZJtk-O1!m~}{&xVEZ% zNME1Zj%^1?cA@pdYgTP6XtBP1HO|=g=xrHjSv_9fv9J-dwu-YTIFOZ~bm)4?ioUc4 zrug-QSE$ivpq;Raerv(~c9-b+ZW-m$C5ikx+ke4OLnyhHO=PFPSHP!CpP6j;8w|bR zBdLU+6Vf+h9*>`ve!c13Mp&Arp{q#htX72Do9#$92SNpb0?f|G;s>$_Fb5e0+_Cuk z`Wb~YFsF|}_n4rJ2M5J)qou~jd2OPK_Ny6r*tVV99oXiiPBcYKt4_4+k10rZiJQH_ z5X*W0ieK06cWXq`l)O{MAzq8W(*3hCe_;D%b^cI>bk_i*qg~doB5q2=5XQ??6}MnR zyEkGZI>L-L$oJ3E`f=QYSCYX72VU@_F`1-bibvj}@E2Q42{(x~N7tWI+2D@85K5=q#fOfPs zdz$(rT1y>JSpknWicRsn78cCcL`9F4AX~nf4muCQ=;y^6e`E3xV$Ls{88EptJGE<5 zYg230)1*qCD`@)@u5o@k4&lxADY^C_bzfwg$>x1JdY<%F@ga==q25I$nk=7_sq{O1 z@E|mg=iPuTZ4d5Ezr0GSKPv0H&C`uQjwK>$;vP7}AM^#xgA^VqH`#8IEEP3^eosRv zxZwduKQXb>fDzvCxTnjAB`CFe4pVmM8Pl1YS~{_iYK%z zs>I^?9ApYuQ}}dEb7;3Hi$x_BOFXn>M@s}srpmxh3+QfKVt&u%$3ZPSk`YA91cJL| zi^b^?A1`J6$tDv=E}C^IXE=8cnytSKWjOvkFgDX_Ek37r-xzxfULW%el=r>qo@Jdw z0)ZuR?O?n$TJ^r>Z19-fGnF7OgfMz7L<2i`hG}Q?)Z!Aq8^c*2QkLPTMo&suY(L9Z z`+l~2DSuQ(LQ;my^oQZGzBY*=S&?mzA{B0XT&$&Bx5YSga{owILy0Omam*0z7D3(+ zRm!-7nySK9R!j9m5v?MTQZKh}Uv$LK_4;Bow|sgPl9+g09FQBo%tJXl214oS>Z=4K zYz@A9B73FIcxta|<(NJ+48&AF9OPY?AE}Q=b|2gj zs*MDt(3Gs4h^Vl7vRv4JE#P}yGmBF}xI=voH(DDNzY=>H+=zOhYD1`It#P{#st1>k zXGS~YvbtsVenrE&oXaO&S)(&|xL(^RLrO|UB$AnZ_b9_a-Um~vO}U6_Y|F1AwgR>~ zwxQ5z%TvT7)hTh)>PNRoqk^e#j9?jcqtnaZ!^hm|mRrKfn$vKPj}4FM1I?=FvDE{x zI$Cs%0WFgXD^oCzA*ft)lVF^>iCpacOsht6v302NiJ|-EA8hx&(MO*^;$D-cirN)yD827`>3SmBU}mpALqaPuJe??+}JsQc*}zO_iM2&Q#Ze@^5Q@ zNrnGQK>wGO!v71<&-mqUK&|{mZU5!DnHm3jJ^Uwf`yapXUuXH>lH31zV*ixi{_8VA zN&n^DKu7b%!PCGr0QVQBPyP1{hF_~iN%uu!Q;Rz2TA1qdS{Yjye*IHweq9?8 zLsMfDhc8s0@oU8$3@w#lzL0e4zYF|@W`C_W-50g~uZZ!#6<_?r4&vYD^4|m8|56C? zA8iXXWhBk}e|+zFB|m89q)5x~muWon4Mp%I*M{K1`~L4R)J z=3v_o`Ezsl=QB;;@hKe*USmS1ZBGm@bVbj0zYsN1M;bV<(n^c-_-lK%>Miqi3voc> z&HR!pXuQe#~LnJErB?KkrUMpVz=8+4&c zExef8@ET{YE&h%Vx1!qx9{51rw>UVmK2cHgYbBzXLWaE`I& z_1NfJp`Q0}(8gqWcDbr^FEpNYQ7M(5-(l&=G7p%=nNJ6a{BVT3ckBdURS6@bpjy^a z#!68QKL{wjRM3hu7kGa*gaV6B2=&9H`GclXRwkC$5H|cVI5lV$xsHt}Z&*q@OU~kY zuYiJ(#xRm|d%ZF~ADbAFoI4IF_?%Z+G^P=9`aK&P^0R^K?K}|1`_=9sW1jeDG)9y` z)t?cDd<8E{Fg~nM0~x6zL+66@k@i(0YCkk_^Trl!l)JnxPFkXp29b}Y-+{|#04UM0 z5lJSdy!-;Ad|*4fPEZg)u+2OUUXkeMkgL47jbdc{Y8 zV{)ep_F?(&=9sF^B#eq5mMRA5CMt!`*)jbzN1i>j=;a{WWfe7^&~qrUsr)k+TYjEh zcVLGvB`ysQA$W5~cuQYP&*%#Eivrx>mT2=ah3x5J++c$uXR1*(VG(2Vh3r|wi))Mn zQPuog>x;mPgTYA5L~%Tw$(T?IBbV2}6-u0?u;c|AOp)Ys(Xv${Q!k;)bWjC)9GX&} z!APKNXT0yeyS&31dyy5f7nAYm00Augv)v&X@ORRq+0Qa4E67LxShJkd(Uvn1YuBGc zLlRUefpxbh#fS4VJSy}pG=p2dIo!DYNBqYI8)B6dA^!Q>Un&R_QtUYs0tHH3`@kBG z)1?JwSmL|A3`S$O7ZM#;K3STqvk&TSOHBm19US~M&7#2&U22H^B~4;y=NzLtWnDM@ z%Zou-h42SNYXRlVyaOAnQSqEOWvmwTZcB!RlI|M;zt!oM$Or4Mnw&1+vH885A2UIi zFrd~LQIPVGG+jwxxn;R^OjmqNkC;YD3~FI|O@|ntFGeHjT70 zBVh~ba2!swQJtWmPYTcAGd+HLfzn0vzSMcK3}FA1xYyU_$ZZ)K%*I~fq~b z-tIZqozfXbQntq0p|;BUUSqOcT(X(R$Yix<;Ow}DwBL7V**v}V0Cl>*s(C$$(l*fUdS1GV zbH;m#cJVYTERAl|)^JS!+~$#(PB5ASw(amx)hMDWcbKgUzChNsvGHpJf!HGePyxVR zBaeh-Hwk<9mwsAJS_pZM!xHc@!P0x~aIQo-`DR3VSkt zmpNCR<%FAu4Cp5{3#@=Vw>nbeKw_dM@7M>Z9yy!H&P(fmBrj;NP0s8zrvJF?e}O)G zHZGPd&+g9fcwn19$G}FRE^W;;|02(w?;M|a(UJEr^KcKCJo58#?S>kFIxyw}J#U(o z7!TM2t1sN}4lKwdAHY~Ix(r)#s7 z32eg4Pw=dKPah3GceO5{s^nGHwZT22&3hizB3Bm7-`QvoN^_rN;B4|iI_KDd2L&<0 z$j!TP~AMxO_m_Qy1F;2NIz0aF7+NWaS{R zl5Cbk=u!HA{a$v*!eBL;wfVOyO;LU4`7LCfE}n($4R8L(VYy`m7$f z$!xc>q5HGMheyW@uqy1&oHA_b6)mcE2I15Qzk=@o`A{Az^S4W4o&206C$rY1rVce} zp?KK#WP;fc_!+)qdedN+mfHcX)g|in}VSZEm3lrb778?GEBuUYBgBi%d0a!}TxdEEmm+r8#&R)i7#S!J^(+FDSvh zw^s-n=K@G#&uNc=&#Sghk+Kj7IjqHo^9lPELMd|aX*4mHG*!x`_P^bhfwJ;kN3i-e zLRWXO_r&{*BFLI4wa}KntXFC>cWYcI(bAXc`PnOK21a{WJCABcz|zcnE&4q&mo36K zUtgVWXXIi5KT4#rCGQwN2aRoGQ(3cHv)(gI0Hy)ht$#IcbR8{tfde{+kO2d8GL9`8*Mc4F9)&A&t`MUAgarxQ1^nBiwJt;$( z*>9}}hiUG^_3gR|J8K}_HzfJ5HZVeF0g)cef#N?&AlCJpQj^u~4N?qe%mY{`Ox z1;cm=#RwPlw#t9+9BkGU$N~BjfOWEov0PtcT8A3~knB*>@n4u7AKKBx*6X09U&N)?v#gOR zXI(@oi(VMypbe9RpM+Y;$eSN8wm>XKh#2K%SG-A?vun$YyN?f^6a1Mq%tO{dPsmF@ z>t6UL*GvkBErTl5NhzC-YKK>VE|K#-7<2U^&i>82cc3z2Z8DO$jV>W>wdjv-B%tXP zh54R%|G+>>D1N2JEKr89L=HlJ?szBgNySKzdUen0>@(^4EUu^(b z|50@RZ)(He()E8=8~(Nr_&=)+Okaw{|3Pi|-v!P8rZ#*vIrw*d=)YAP{?S+9KNmxL z%8LJ08#@10ZAfTD@vF8V-JAXwy+JNlmi8dVdvnI2DViVBBVyA(H5PxhZE*$cjn!>k z*RO{S+4ezv9*Q$+v8EJ4-|=O1w2~MVgV?rd61r#+E{hTF3LWGSMR^%F;_}+>y2yk^ zjk)*Yd|7sb@~6jE0F|$foL`S`1fGJ60x}47t*1Fulj(Z=LHczoL_q7_f>Oi-#9
TIVOUyPEK9wXABs`SA81lhu~g)O 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 diff --git a/resources/bin/binfo b/resources/bin/bibiinfo similarity index 100% rename from resources/bin/binfo rename to resources/bin/bibiinfo diff --git a/resources/bin/bibilog b/resources/bin/bibilog new file mode 100644 index 00000000..22456e4e --- /dev/null +++ b/resources/bin/bibilog @@ -0,0 +1,16 @@ +#!/bin/bash +if [ "$1" == "err" ]; then + err_out="err" +else + err_out="out" +fi + +if [ "$2" == "fail" ]; then + fail_create="fail" +else + fail_create="create" +fi + +LOG="/var/log/slurm/worker_logs/$fail_create/$err_out" +RECENT=$(ls -1rt $LOG | tail -n1) +tail -f "$LOG/$RECENT" \ No newline at end of file diff --git a/resources/defaults/slurm/slurm.conf b/resources/defaults/slurm/slurm.conf index 0f622ea9..19645fd3 100644 --- a/resources/defaults/slurm/slurm.conf +++ b/resources/defaults/slurm/slurm.conf @@ -9,17 +9,15 @@ AuthAltParameters=jwt_key=/etc/slurm/jwt-secret.key ClusterName=bibigrid MpiDefault=none -ProctrackType=proctrack/linuxproc +ProctrackType=proctrack/cgroup # linuxproc # changed for 23.11.0 ReturnToService=2 SwitchType=switch/none TaskPlugin=task/none #TaskPlugin=task/cgroup JobAcctGatherType=jobacct_gather/linux -# see https://slurm.schedmd.com/slurm.conf.html#OPT_cloud_dns:~:text=for%20additional%20details.-,cloud_dns,-By%20default%2C%20Slurm # SlurmctldParameters=cloud_dns -# Funktioniert nicht wie vermutet. slurmctld versucht mit diesem Parameter schon beim Start alle Clients aufzulösen, -# was natürlich nicht funktioniert. +# didn't work as expected. slurmctld tries to resolve all clients on startup which doesn't work obviously # PRIORITY PriorityType=priority/multifactor @@ -37,7 +35,6 @@ SlurmctldPort=6817 SlurmdPort=6818 # DIRECTORIES -#JobCheckpointDir=/var/lib/slurm/job_checkpoint SlurmdSpoolDir=/var/lib/slurm/slurmd StateSaveLocation=/var/lib/slurm/state_checkpoint @@ -61,7 +58,7 @@ AccountingStorageHost={{ hostvars[groups.master.0].name | lower }} AccountingStorageUser={{ slurm_conf.db_user }} # LOGGING -SlurmctldDebug=info +SlurmctldDebug=debug # info SlurmctldLogFile=/var/log/slurm/slurmctld.log SlurmdDebug=info SlurmdLogFile=/var/log/slurm/slurmd.log @@ -102,7 +99,7 @@ SuspendExcNodes={{ hostvars[groups.master.0].name }} # Maximum number of nodes TreeWidth= {{ slurm_conf.elastic_scheduling.TreeWidth }} # Do not cache dns names -CommunicationParameters=NoAddrCache +# CommunicationParameters=NoAddrCache # REMOVED for 23.11.0 # Mark node status idle on suspend so DOWN is removed SlurmctldParameters=idle_on_node_suspend # Show slurm nodes all the time @@ -113,4 +110,4 @@ ResumeFailProgram=/opt/slurm/fail.sh # job container # TO BE TESTED JobContainerType=job_container/tmpfs -PrologFlags=Contain \ No newline at end of file +PrologFlags=Contain diff --git a/resources/playbook/roles/bibigrid/files/slurm/cgroup.conf b/resources/playbook/roles/bibigrid/files/slurm/cgroup.conf index 2705699f..5ab6361d 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/cgroup.conf +++ b/resources/playbook/roles/bibigrid/files/slurm/cgroup.conf @@ -1,6 +1,6 @@ # maybe this causes errors when using 23.11 https://slurm.schedmd.com/faq.html#cgroupv2 CgroupMountpoint="/sys/fs/cgroup" -CgroupAutomount=yes +# CgroupAutomount=yes # REMOVED 23.11.0 ConstrainCores=no ConstrainRAMSpace=yes ConstrainSwapSpace=no diff --git a/resources/playbook/roles/bibigrid/files/slurm/create.sh b/resources/playbook/roles/bibigrid/files/slurm/create.sh index e48d9954..d5bbc4c0 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/create.sh +++ b/resources/playbook/roles/bibigrid/files/slurm/create.sh @@ -10,7 +10,7 @@ process_string() { fifth=${elements[4]} # Replace undesired characters in the second element - second=$(echo "$second" | sed -E 's/worker-/worker_/; s/vpnwkr-/vpnwkr_/') + second=$(echo "$second" | sed -E 's/worker-/worker_/; s/vpngtw-/vpngtw_/') # Check if the fifth element is not empty if [[ ! -z $fifth ]]; then diff --git a/resources/playbook/roles/bibigrid/files/slurm/create_server.py b/resources/playbook/roles/bibigrid/files/slurm/create_server.py index 19e9b828..0e8cd0da 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/create_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/create_server.py @@ -273,13 +273,6 @@ def _run_playbook(cmdline_args): sys.exit(1) else: logging.info(ansible_execution_data) -server_start_data = {"started_servers": [], "other_openstack_exceptions": [], "connection_exceptions": [], - "available_servers": [], "openstack_wait_exceptions": []} -if [key for key in server_start_data if "exception" in key]: - logging.warning(server_start_data) - sys.exit(1) -else: - logging.info(server_start_data) logging.info("Successful create_server.py execution!") time_in_s = time.time() - start_time diff --git a/resources/playbook/roles/bibigrid/files/slurm/fail.sh b/resources/playbook/roles/bibigrid/files/slurm/fail.sh index 436f8b59..38d723b4 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/fail.sh +++ b/resources/playbook/roles/bibigrid/files/slurm/fail.sh @@ -10,7 +10,7 @@ process_string() { fifth=${elements[4]} # Replace undesired characters in the second element - second=$(echo "$second" | sed -E 's/worker-/worker_/; s/vpnwkr-/vpnwkr_/') + second=$(echo "$second" | sed -E 's/worker-/worker_/; s/vpngtw-/vpngtw_/') # Check if the fifth element is not empty if [[ ! -z $fifth ]]; then diff --git a/resources/playbook/roles/bibigrid/handlers/main.yml b/resources/playbook/roles/bibigrid/handlers/main.yml index b6b62321..d4888f5a 100644 --- a/resources/playbook/roles/bibigrid/handlers/main.yml +++ b/resources/playbook/roles/bibigrid/handlers/main.yml @@ -28,6 +28,7 @@ systemd: name: slurmctld state: restarted + when: "'master' in group_names" - name: slurmd systemd: diff --git a/resources/playbook/roles/bibigrid/tasks/001-apt.yml b/resources/playbook/roles/bibigrid/tasks/001-apt.yml index 407175db..0ca1c17b 100644 --- a/resources/playbook/roles/bibigrid/tasks/001-apt.yml +++ b/resources/playbook/roles/bibigrid/tasks/001-apt.yml @@ -10,6 +10,15 @@ group: root mode: 0644 +- name: Wait for cloud-init / user-data to finish + command: cloud-init status --wait + changed_when: false + +- name: Wait for /var/lib/dpkg/lock-frontend to be released + shell: while lsof /var/lib/dpkg/lock-frontend ; do sleep 10; done; + tags: + - skip_ansible_lint + - name: Wait for post-launch services to stop service_facts: register: result diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml index 3bc06ebf..6691225e 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml @@ -17,7 +17,9 @@ - "{{ master.disks }}" when: master.disks is defined -- block: +- when: volumes is defined and auto_mount + failed_when: false + block: - name: Make sure disks are available filesystem: fstype: ext4 @@ -36,10 +38,9 @@ with_items: "{{ volumes }}" - name: Mount disks + mount: path: "{{ item.name }}" src: "{{ item.device }}" state: mounted with_items: "{{ volumes }}" - when: volumes is defined and auto_mount - ignore_errors: true diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml index 69f0098f..580aabc4 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml @@ -70,22 +70,6 @@ - slurmdbd - slurmrestd -- name: Enable slurmdbd and slurmrestd services - systemd: - name: "{{ item }}" - enabled: true - masked: false - state: started - daemon_reload: true - with_items: - - slurmdbd - - slurmrestd - -- name: Start slurm explicit after all dependencies are configured - systemd: - name: slurmctld - state: started - - name: Register Slurm users home dir shell: "set -o pipefail && grep slurm /etc/passwd | cut -d ':' -f 6" register: slurm_home @@ -180,6 +164,31 @@ groups: - ansible +- name: Generate location specific worker userdata + template: + src: slurm/worker_userdata.j2 + dest: "/opt/slurm/userdata_{{ hostvars[item].cloud_identifier }}.txt" + owner: slurm + group: ansible + mode: "0640" + with_items: "{{ groups.vpngtw + groups.master }}" + +- name: Enable slurmdbd and slurmrestd services + systemd: + name: "{{ item }}" + enabled: true + masked: false + state: started + daemon_reload: true + with_items: + - slurmdbd + - slurmrestd + +- name: Start slurm explicit after all dependencies are configured + systemd: + name: slurmctld + state: started + - when: slurm_home.stdout != '/opt/slurm' block: @@ -210,12 +219,3 @@ - slurmd - slurmdbd - slurmrestd - -- name: Generate location specific worker userdata - template: - src: slurm/worker_userdata.j2 - dest: "/opt/slurm/userdata_{{ hostvars[item].cloud_identifier }}.txt" - owner: slurm - group: ansible - mode: "0640" - with_items: "{{ groups.vpngtw + groups.master }}" diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm.yml b/resources/playbook/roles/bibigrid/tasks/042-slurm.yml index 49253ca0..e134dbe2 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm.yml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm.yml @@ -9,21 +9,25 @@ uid: 64030 group: slurm -- name: Install Slurm package (and dependencies) +- name: Create pinning configuration for slurm-bibigrid version 23.11.* + copy: + content: | + Package: slurm-bibigrid + Pin: version 23.11.* + Pin-Priority: 1001 + dest: /etc/apt/preferences.d/slurm-bibigrid + mode: '0311' + +- name: Install slurm-bibigrid package + apt: + name: slurm-bibigrid + state: present + +- name: Install Slurm package dependencies apt: name: - - slurm-full - munge -# - name: Download Slurm (TEMPORARY) -# get_url: -# url: "https://docs.cebitec.uni-bielefeld.de/s/FjCP3xQPPnBwSy9/download?path=%2F&files=slurm-full_23.11.0-0_amd64.deb" # Replace with your package link -# dest: "/tmp/package.deb" # Destination where the package will be saved -# - name: Install Slurm package -# apt: -# deb: "/tmp/package.deb" -# state: present # Install the package if not already installed - - name: Create new secret (Munge) copy: content: '{{ slurm_conf.munge_key }}' diff --git a/resources/playbook/roles/bibigrid/tasks/main.yml b/resources/playbook/roles/bibigrid/tasks/main.yml index b81dfdbc..1a6713f7 100644 --- a/resources/playbook/roles/bibigrid/tasks/main.yml +++ b/resources/playbook/roles/bibigrid/tasks/main.yml @@ -136,6 +136,7 @@ - debug: msg: "[BIBIGRID] Setup Slurm" - import_tasks: 042-slurm.yml + when: "'vpngtw' not in group_names" - import_tasks: 042-slurm-server.yml when: "'master' in group_names" From 7eaae2010b13226a0f35f688a783237703e93f97 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Wed, 24 Apr 2024 08:03:08 +0200 Subject: [PATCH 068/145] Update tests (#493) * updated tests * removed print * updated tests * updated tests * fixed too loose condition * updated tests --- .gitignore | 3 +- bibigrid/core/actions/create.py | 1 - bibigrid/core/utility/ansible_configurator.py | 5 +- .../utility/handler/configuration_handler.py | 1 + .../core/utility/validate_configuration.py | 9 +- resources/tests/bibigrid_test_example.yml | 9 + tests/provider/test_provider.py | 19 +- tests/test_ansible_configurator.py | 59 ++-- tests/test_configuration_handler.py | 1 + tests/test_create.py | 39 +-- tests/test_ssh_handler.py | 48 +-- tests/test_validate_configuration.py | 273 ++++++++++-------- 12 files changed, 253 insertions(+), 214 deletions(-) create mode 100644 resources/tests/bibigrid_test_example.yml diff --git a/.gitignore b/.gitignore index 8375a9a6..0f7fbc2f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,7 @@ resources/playbook/ansible_hosts resources/playbook/vars/ resources/playbook/host_vars/ resources/playbook/group_vars/ -tests/resources/* -!test/resources/test_configuration.yml +resources/tests/bibigrid_test.yml # any log files *.log diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index c1fbd0f8..b6a8d66f 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -196,7 +196,6 @@ def start_vpn_or_master(self, configuration, provider): # create a server and block until it is up and running server = provider.create_server(name=name, flavor=flavor, key_name=self.key_name, image=image, network=network, volumes=volumes, security_groups=configuration["security_groups"], wait=True) - print("MASTER", server) configuration["private_v4"] = server["private_v4"] self.log.debug(f"Created Server {name}: {server['private_v4']}.") # get mac address for given private address diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 7d99253e..0ef6a39f 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -55,9 +55,7 @@ def generate_site_file_yaml(custom_roles): @param custom_roles: ansibleRoles given by the config @return: site_yaml (dict) """ - site_yaml = [{'hosts': 'master', "pre_tasks": [ - {"name": "Print ansible.cfg timeout", "command": "ansible-config dump | grep 'DEFAULT_TIMEOUT'", - "register": "ansible_cfg_output"}, {"debug": {"msg": "{{ ansible_cfg_output.stdout }}"}}], "become": "yes", + site_yaml = [{'hosts': 'master', "become": "yes", "vars_files": VARS_FILES, "roles": MASTER_ROLES}, {'hosts': 'vpngtw', "become": "yes", "vars_files": VARS_FILES, "roles": vpngtw_ROLES}, {"hosts": "workers", "become": "yes", "vars_files": VARS_FILES, "roles": WORKER_ROLES}] # , @@ -173,7 +171,6 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us """ master_configuration = configurations[0] log.info("Generating common configuration file...") - # print(configuration.get("slurmConf", {})) common_configuration_yaml = {"bibigrid_version": __version__, "auto_mount": master_configuration.get("autoMount", False), "cluster_id": cluster_id, "cluster_cidrs": cidrs, "default_user": default_user, diff --git a/bibigrid/core/utility/handler/configuration_handler.py b/bibigrid/core/utility/handler/configuration_handler.py index bc124993..8746a76f 100644 --- a/bibigrid/core/utility/handler/configuration_handler.py +++ b/bibigrid/core/utility/handler/configuration_handler.py @@ -92,6 +92,7 @@ def get_clouds_files(log): clouds_public = clouds_public_yaml.get(CLOUD_PUBLIC_ROOT_KEY) if not clouds_public: log.warning("%s is not valid. Must contain key '%s'", CLOUDS_PUBLIC_YAML, CLOUD_PUBLIC_ROOT_KEY) + return clouds, clouds_public diff --git a/bibigrid/core/utility/validate_configuration.py b/bibigrid/core/utility/validate_configuration.py index 1980abda..256858b5 100644 --- a/bibigrid/core/utility/validate_configuration.py +++ b/bibigrid/core/utility/validate_configuration.py @@ -181,7 +181,6 @@ def __init__(self, configurations, providers, log): self.log = log self.configurations = configurations self.providers = providers - self.required_resources_dict = { provider.cloud_specification['identifier']: {'total_cores': 0, 'floating_ips': 0, 'instances': 0, 'total_ram': 0, 'volumes': 0, 'volume_gigabytes': 0, @@ -227,11 +226,11 @@ def check_master_vpn_worker(self): """ self.log.info("Checking master/vpn") success = True - if not self.configurations[0].get("masterInstance"): + if not self.configurations[0].get("masterInstance") or self.configurations[0].get("vpnInstance"): self.log.warning(f"{self.configurations[0].get('cloud')} has no master instance!") success = False for configuration in self.configurations[1:]: - if not configuration.get("vpnInstance"): + if not configuration.get("vpnInstance") or configuration.get("masterInstance"): self.log.warning(f"{configuration.get('cloud')} has no vpn instance!") success = False return success @@ -356,9 +355,9 @@ def check_volumes(self): else: self.log.info(f"Snapshot '{volume_name_or_id}' found on " f"{provider.cloud_specification['identifier']}.") - self.required_resources_dict[provider.cloud_specification['identifier']]["Volumes"] += 1 + self.required_resources_dict[provider.cloud_specification['identifier']]["volumes"] += 1 self.required_resources_dict[provider.cloud_specification['identifier']][ - "VolumeGigabytes"] += snapshot["size"] + "volume_gigabytes"] += snapshot["size"] else: self.log.info(f"Volume '{volume_name_or_id}' found on " f"{provider.cloud_specification['identifier']}.") diff --git a/resources/tests/bibigrid_test_example.yml b/resources/tests/bibigrid_test_example.yml new file mode 100644 index 00000000..9e3a9b1d --- /dev/null +++ b/resources/tests/bibigrid_test_example.yml @@ -0,0 +1,9 @@ +- infrastructure: # former mode. + cloud: #credentials # name of clouds.yaml entry + + flavor: de.NBI small + image: ^Ubuntu 22\.04 LTS \(.*\)$ + sshUser: ubuntu + network: # network + + snapshotImage: # name of a snapshot diff --git a/tests/provider/test_provider.py b/tests/provider/test_provider.py index 45b0de7e..56108d79 100644 --- a/tests/provider/test_provider.py +++ b/tests/provider/test_provider.py @@ -6,6 +6,8 @@ import os import unittest +from bibigrid.core import startup +from bibigrid.core.utility import image_selection import bibigrid.core.utility.paths.basic_path as bP from bibigrid.core.utility.handler import configuration_handler from bibigrid.core.utility.handler import provider_handler @@ -58,8 +60,8 @@ 'encrypted', 'multiattach', 'availability_zone', 'source_volid', 'user_id', 'os-vol-tenant-attr:tenant_id'} -FREE_RESOURCES_KEYS = {'total_cores', 'floating_ips', 'instances', 'total_ram', 'Volumes', 'VolumeGigabytes', - 'Snapshots', 'Backups', 'BackupGigabytes'} +FREE_RESOURCES_KEYS = {'total_cores', 'floating_ips', 'instances', 'total_ram', 'volumes', 'volume_gigabytes', + 'snapshots', 'backups', 'backup_gigabytes'} KEYPAIR = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDORPauyW3O7M4Uk8/Qo557h2zxd9fwByljG9S1/zHKIEzOMcOBb7WUSmyNa5XHh5IB0/" \ "BTsQvSag/O9IAhax2wlp9A2za6EkALYiRdEXeGOMNORw8yylRBqzLluKTErZ5sKYxENf1WGHsE3ifzct0G/moEPmIkixTHR9fZrZgOzQwj" \ @@ -70,7 +72,7 @@ CONFIGURATIONS = configuration_handler.read_configuration(logging, os.path.join(bP.ROOT_PATH, - "tests/resources/infrastructure_cloud.yml")) + "resources/tests/bibigrid_test.yml")) PROVIDERS = provider_handler.get_providers(CONFIGURATIONS, logging) @@ -82,6 +84,9 @@ class ProviderServer: def __init__(self, provider, name, configuration, key_name=None): self.provider = provider self.name = name + self.log = startup.LOG + configuration["image"] = image_selection.select_image(provider=provider, image=configuration["image"], + log=self.log) self.server_dict = provider.create_server(name=self.name, flavor=configuration["flavor"], image=configuration["image"], network=configuration["network"], key_name=key_name, security_groups=[]) @@ -166,6 +171,7 @@ def test_active_server_methods(self): def test_get_external_network(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): + print("FIRE", provider.get_external_network(configuration["network"])) self.assertTrue(provider.get_external_network(configuration["network"])) with self.assertRaises(TypeError): provider.get_external_network("ERROR") @@ -220,16 +226,17 @@ def test_get_image_mismatch(self): with self.subTest(provider.NAME): self.assertIsNone(provider.get_image_by_id_or_name("NONE")) - if CONFIGURATIONS[0].get("snapshot_image"): + if CONFIGURATIONS[0].get("snapshotImage"): def test_get_snapshot(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): self.assertEqual(SNAPSHOT_KEYS, set(provider.get_volume_snapshot_by_id_or_name( - configuration["snapshot_image"]).keys())) + configuration["snapshotImage"]).keys())) def test_create_volume_from_snapshot(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): - volume_id = provider.create_volume_from_snapshot(configuration["snapshot_image"]) + volume_id = provider.create_volume_from_snapshot(configuration["snapshotImage"]) + print(volume_id) volume = provider.get_volume_by_id_or_name(volume_id) self.assertEqual(VOLUME_KEYS, set(volume.keys())) diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index dbaef9a9..9bd92df0 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -5,9 +5,10 @@ from unittest.mock import MagicMock, Mock, patch, call, mock_open, ANY import bibigrid.core.utility.paths.ansible_resources_path as aRP -from bibigrid.core.utility.yaml_dumper import NoAliasSafeDumper from bibigrid.core import startup +from bibigrid.core.actions import version from bibigrid.core.utility import ansible_configurator +from bibigrid.core.utility.yaml_dumper import NoAliasSafeDumper class TestAnsibleConfigurator(TestCase): @@ -48,10 +49,12 @@ def test_generate_common_configuration_false(self): default_user = "ubuntu" ssh_user = "test" configuration = [{}] - common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, - 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, - 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, - 'local_dns_lookup': False, 'local_fs': False, 'slurm': True, + common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, + 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, + 'cluster_id': cluster_id, 'default_user': default_user, + 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': False, + 'enable_slurm': False, 'enable_zabbix': False, 'local_dns_lookup': False, + 'local_fs': False, 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, 'TreeWidth': 128}, @@ -72,9 +75,11 @@ def test_generate_common_configuration_true(self): ssh_user = "test" configuration = [ {elem: "True" for elem in ["localFS", "localDNSlookup", "useMasterAsCompute", "slurm", "zabbix", "ide"]}] - common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, - 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'True', - 'enable_nfs': False, 'enable_slurm': 'True', 'enable_zabbix': 'True', + common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, + 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, + 'cluster_id': cluster_id, 'default_user': default_user, + 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'True', 'enable_nfs': False, + 'enable_slurm': 'True', 'enable_zabbix': 'True', 'ide_conf': {'build': False, 'ide': False, 'port_end': 8383, 'port_start': 8181, 'workspace': '${HOME}'}, 'local_dns_lookup': 'True', 'local_fs': 'True', 'slurm': 'True', @@ -99,10 +104,12 @@ def test_generate_common_configuration_nfs_shares(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, - 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, - 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, - 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, + common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, + 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, + 'cluster_id': cluster_id, 'default_user': default_user, + 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', + 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [], + 'local_dns_lookup': False, 'local_fs': False, 'nfs_mounts': [{'dst': '//vil/mil', 'src': '//vil/mil'}, {'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', @@ -123,10 +130,12 @@ def test_generate_common_configuration_nfs(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, - 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, - 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, - 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, + common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, + 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, + 'cluster_id': cluster_id, 'default_user': default_user, + 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', + 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [], + 'local_dns_lookup': False, 'local_fs': False, 'nfs_mounts': [{'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, @@ -146,9 +155,11 @@ def test_generate_common_configuration_ext_nfs_shares(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, - 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, - 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, + common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, + 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, + 'cluster_id': cluster_id, 'default_user': default_user, + 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', + 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}], 'local_dns_lookup': False, 'local_fs': False, 'nfs_mounts': [{'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, @@ -162,6 +173,7 @@ def test_generate_common_configuration_ext_nfs_shares(self): default_user, startup.LOG) common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] + print(generated_common_configuration) self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_ide(self): @@ -170,10 +182,11 @@ def test_generate_common_configuration_ide(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, - 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], - 'enable_ide': 'Some1', 'enable_nfs': False, 'enable_slurm': False, - 'enable_zabbix': False, + common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, + 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, + 'cluster_id': cluster_id, 'default_user': default_user, + 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'Some1', 'enable_nfs': False, + 'enable_slurm': False, 'enable_zabbix': False, 'ide_conf': {'build': False, 'ide': False, 'key1': 'Some2', 'port_end': 8383, 'port_start': 8181, 'workspace': '${HOME}'}, 'local_dns_lookup': False, 'local_fs': False, 'slurm': True, diff --git a/tests/test_configuration_handler.py b/tests/test_configuration_handler.py index 86564f84..d442a650 100644 --- a/tests/test_configuration_handler.py +++ b/tests/test_configuration_handler.py @@ -14,6 +14,7 @@ class TestConfigurationHandler(TestCase): """ Class to test configuration_handler """ + # pylint: disable=R0904 def test_get_list_by_name_none(self): configurations = [{}, {}] diff --git a/tests/test_create.py b/tests/test_create.py index 339a90db..612681d2 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -111,16 +111,20 @@ def test_prepare_args_keyerror(self): creator.prepare_vpn_or_master_args(configuration, provider)) prepare_mock.assert_not_called() - @patch("bibigrid.core.utility.handler.ssh_handler.ansible_preparation") - def test_initialize_instances_master(self, mock_ansible): + @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") + def test_initialize_master(self, mock_execute_ssh): provider = MagicMock() provider.list_servers.return_value = [] floating_ip = 21 configuration = {"masterInstance": 42, "floating_ip": floating_ip} creator = create.Create([provider], [configuration], "", startup.LOG) creator.initialize_instances() - mock_ansible.assert_called_with(floating_ip=floating_ip, private_key=create.KEY_FOLDER + creator.key_name, - username=creator.ssh_user, commands=[], log=startup.LOG, gateway={}) + ssh_data = {'floating_ip': floating_ip, 'private_key': create.KEY_FOLDER + creator.key_name, + 'username': creator.ssh_user, + 'commands': creator.ssh_add_public_key_commands + ssh_handler.ANSIBLE_SETUP, + 'filepaths': [(create.KEY_FOLDER + creator.key_name, '.ssh/id_ecdsa')], + 'gateway': {}, 'timeout': 4} + mock_execute_ssh.assert_called_with(ssh_data, startup.LOG) def test_prepare_volumes_none(self): provider = MagicMock() @@ -203,22 +207,23 @@ def test_upload_playbooks(self, mock_execute_ssh, mock_ac_ssh, mock_configure_an creator.upload_data() mock_configure_ansible.assert_called_with(providers=creator.providers, configurations=creator.configurations, cluster_id=creator.cluster_id, log=startup.LOG) - mock_execute_ssh.assert_called_with(floating_ip=creator.master_ip, - private_key=create.KEY_FOLDER + creator.key_name, username=creator.ssh_user, - filepaths=create.FILEPATHS, - commands=[mock_ac_ssh()] + ssh_handler.ANSIBLE_START, log=startup.LOG, - gateway={}) + ssh_data = {'floating_ip': creator.master_ip, 'private_key': create.KEY_FOLDER + creator.key_name, + 'username': creator.ssh_user, + 'commands': [mock_ac_ssh()] + ssh_handler.ANSIBLE_START, + 'filepaths': create.FILEPATHS, + 'gateway': {}, 'timeout': 4} + mock_execute_ssh.assert_called_with(ssh_data=ssh_data, log=startup.LOG) @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") - @patch.object(create.Create, "start_start_instance_threads") + @patch.object(create.Create, "start_start_server_threads") @patch.object(create.Create, "upload_data") @patch.object(create.Create, "log_cluster_start_info") @patch("bibigrid.core.actions.terminate.terminate") def test_create_non_debug(self, mock_terminate, mock_info, mock_up, mock_start, mock_conf, mock_key): provider = MagicMock() provider.list_servers.return_value = [] - configuration = {} + configuration = {"floating_ip": 42} creator = create.Create([provider], [configuration], "", startup.LOG, False) self.assertEqual(0, creator.create()) for mock in [mock_info, mock_up, mock_start, mock_conf, mock_key]: @@ -227,18 +232,16 @@ def test_create_non_debug(self, mock_terminate, mock_info, mock_up, mock_start, @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") - @patch.object(create.Create, "start_start_instance_threads") - @patch.object(create.Create, "upload_data") + @patch.object(create.Create, "start_start_server_threads") @patch.object(create.Create, "log_cluster_start_info") @patch("bibigrid.core.actions.terminate.terminate") - def test_create_non_debug_upload_raise(self, mock_terminate, mock_info, mock_up, mock_start, mock_conf, mock_key): + def test_create_non_debug_upload_raise(self, mock_terminate, mock_info, mock_start, mock_conf, mock_key): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} creator = create.Create([provider], [configuration], "", startup.LOG, False) - mock_up.side_effect = [ConnectionError()] self.assertEqual(1, creator.create()) - for mock in [mock_start, mock_conf, mock_key, mock_up]: + for mock in [mock_start, mock_conf, mock_key]: mock.assert_called() for mock in [mock_info]: mock.assert_not_called() @@ -247,14 +250,14 @@ def test_create_non_debug_upload_raise(self, mock_terminate, mock_info, mock_up, @patch.object(create.Create, "generate_keypair") @patch.object(create.Create, "prepare_configurations") - @patch.object(create.Create, "start_start_instance_threads") + @patch.object(create.Create, "start_start_server_threads") @patch.object(create.Create, "upload_data") @patch.object(create.Create, "log_cluster_start_info") @patch("bibigrid.core.actions.terminate.terminate") def test_create_debug(self, mock_terminate, mock_info, mock_up, mock_start, mock_conf, mock_key): provider = MagicMock() provider.list_servers.return_value = [] - configuration = {} + configuration = {"floating_ip": 42} creator = create.Create([provider], [configuration], "", startup.LOG, True) self.assertEqual(0, creator.create()) for mock in [mock_info, mock_up, mock_start, mock_conf, mock_key]: diff --git a/tests/test_ssh_handler.py b/tests/test_ssh_handler.py index 7c0cfd46..73aa4687 100644 --- a/tests/test_ssh_handler.py +++ b/tests/test_ssh_handler.py @@ -1,15 +1,12 @@ """ Module to test ssh_handler """ -import socket from unittest import TestCase from unittest.mock import mock_open, Mock, MagicMock, patch, call -from paramiko.ssh_exception import NoValidConnectionsError - from bibigrid.core import startup from bibigrid.core.utility.handler import ssh_handler -from bibigrid.models.exceptions import ExecutionException, ConnectionException +from bibigrid.models.exceptions import ExecutionException class TestSshHandler(TestCase): @@ -49,25 +46,6 @@ def test_copy_to_server_folder(self, mock_listdir): mock_listdir.assert_called_with("Jim") sftp.mkdir.assert_called_with("Joe") - @patch("logging.info") - def test_is_active(self, mock_log): - client = Mock() - client.connect = MagicMock(return_value=True) - self.assertFalse(ssh_handler.is_active(client, 42, 32, 22, startup.LOG, {}, timeout=5)) - mock_log.assert_not_called() - - def test_is_active_on_second_attempt(self): - client = Mock() - client.connect = MagicMock(side_effect=[NoValidConnectionsError({('127.0.0.1', 22): socket.error}), True]) - self.assertFalse(ssh_handler.is_active(client, 42, 32, 22, startup.LOG, {}, timeout=5)) - - def test_is_active_exception(self): - client = Mock() - client.connect = MagicMock(side_effect=NoValidConnectionsError({('127.0.0.1', 22): socket.error})) - with self.assertRaises(ConnectionException): - ssh_handler.is_active(client, 42, 32, 22, startup.LOG, {}, timeout=0) - client.connect.assert_called_with(hostname=42, username=22, pkey=32, timeout=7, auth_timeout=5, port=22) - @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh_cml_commands") @patch("paramiko.ECDSAKey.from_private_key_file") @patch("paramiko.SSHClient") @@ -79,24 +57,14 @@ def test_execute_ssh(self, mock_client, mock_paramiko_key, mock_exec): mock.__enter__ = client mock.__exit__ = Mock(return_value=None) with patch("bibigrid.core.utility.handler.ssh_handler.is_active") as mock_active: - ssh_handler.execute_ssh(42, 32, 22, startup.LOG, {}, [12]) + ssh_data = {'floating_ip': 42, 'private_key': "key", 'username': "ubuntu", 'commands': ["ho"], + 'filepaths': [], 'gateway': {}, 'timeout': 4} + ssh_handler.execute_ssh(ssh_data, startup.LOG) mock_client.assert_called_with() - mock_active.assert_called_with(client=client(), floating_ip_address=42, username=22, private_key=2, - log=startup.LOG, gateway={}) - mock_exec.assert_called_with(client=client(), commands=[12], log=startup.LOG) - mock_paramiko_key.assert_called_with(32) - - @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") - def test_ansible_preparation(self, mock_execute): - ssh_handler.ansible_preparation(1, 2, 3, startup.LOG, {}, [], []) - mock_execute.assert_called_with(1, 2, 3, startup.LOG, {}, ssh_handler.ANSIBLE_SETUP, - [(2, ssh_handler.PRIVATE_KEY_FILE)]) - - @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") - def test_ansible_preparation_elem(self, mock_execute): - ssh_handler.ansible_preparation(1, 2, 3, startup.LOG, {}, [42], [42]) - mock_execute.assert_called_with(1, 2, 3, startup.LOG, {}, ssh_handler.ANSIBLE_SETUP + [42], - [42, (2, ssh_handler.PRIVATE_KEY_FILE)]) + mock_active.assert_called_with(client=client(), paramiko_key=mock_paramiko_key.return_value, + ssh_data=ssh_data, log=startup.LOG) + mock_exec.assert_called_with(client=client(), commands=["ho"], log=startup.LOG) + mock_paramiko_key.assert_called_with("key") def test_execute_ssh_cml_commands(self): client = Mock() diff --git a/tests/test_validate_configuration.py b/tests/test_validate_configuration.py index 5deb65c8..322cbb36 100644 --- a/tests/test_validate_configuration.py +++ b/tests/test_validate_configuration.py @@ -6,6 +6,7 @@ from unittest import TestCase from unittest.mock import Mock, patch, MagicMock, call +from bibigrid.core import startup from bibigrid.core.utility import validate_configuration from bibigrid.models.exceptions import ImageNotActiveException @@ -32,12 +33,14 @@ def test_check_provider_data_unique(self): @patch("bibigrid.core.utility.image_selection.select_image") def test_check_master_vpn_worker_ordered(self, mock_select_image): # pylint: disable=unused-argument + provider1 = Mock() + provider1.cloud_specification = {"identifier": "1"} master = {"masterInstance": "Value"} vpn = {"vpnInstance": "Value"} vpn_master = {} vpn_master.update(master) vpn_master.update(vpn) - v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=[master], log=Mock()) + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[master], log=Mock()) self.assertTrue(v_c.check_master_vpn_worker()) v_c.configurations = [master, vpn] self.assertTrue(v_c.check_master_vpn_worker()) @@ -47,12 +50,15 @@ def test_check_master_vpn_worker_ordered(self, mock_select_image): # pylint: di self.assertFalse(v_c.check_master_vpn_worker()) def test_check_master_vpn_worker_unique(self): + provider1 = Mock() + provider1.cloud_specification = {"identifier": "1"} master = {"masterInstance": "Value"} vpn = {"vpnInstance": "Value"} vpn_master = {} vpn_master.update(master) vpn_master.update(vpn) - v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=[vpn_master], log=Mock()) + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[vpn_master], + log=Mock()) self.assertFalse(v_c.check_master_vpn_worker()) v_c.configurations = [master, vpn_master] self.assertFalse(v_c.check_master_vpn_worker()) @@ -70,242 +76,279 @@ def test_check_provider_connection(self): self.assertTrue(v_c.check_provider_connections()) def test_check_instances_master(self): - v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"masterInstance": "42"}], - log=Mock()) + provider1 = Mock() + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], + configurations=[{"masterInstance": "42"}], log=Mock()) with patch.object(v_c, "check_instance") as mock: v_c.check_instances() - mock.assert_called_with("masterInstance", "42", "31") + mock.assert_called_with("masterInstance", "42", provider1) def test_check_instances_vpn(self): - v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{"vpnInstance": "42"}], - log=Mock()) + provider1 = Mock() + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], + configurations=[{"vpnInstance": "42"}], log=Mock()) with patch.object(v_c, "check_instance") as mock: v_c.check_instances() - mock.assert_called_with("vpnInstance", "42", "31") + mock.assert_called_with("vpnInstance", "42", provider1) def test_check_instances_vpn_worker(self): - v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[ + provider1 = Mock() + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[ {"masterInstance": "42", "workerInstances": ["42"]}], log=Mock()) with patch.object(v_c, "check_instance") as mock: v_c.check_instances() - mock.assert_called_with("workerInstance", "42", "31") + mock.assert_called_with("workerInstance", "42", provider1) def test_check_instances_vpn_master_missing(self): - v_c = validate_configuration.ValidateConfiguration(providers=["31"], configurations=[{}], log=Mock()) + provider1 = Mock() + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{}], log=Mock()) self.assertFalse(v_c.check_instances()) - v_c = validate_configuration.ValidateConfiguration(providers=["31"], + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"workerInstances": ["42"]}], log=Mock()) self.assertFalse(v_c.check_instances()) def test_check_instances_vpn_master_count(self): for i in range(1, 4): - v_c = validate_configuration.ValidateConfiguration(providers=["31"] * i, + provider1 = Mock() + provider1.cloud_specification = {"identifier": i} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1] * i, configurations=[{"masterInstance": {"count": 1}}] + [ {"vpnInstance": {"count": 1}}] * (i - 1), log=Mock()) # with patch.object(v_c, "check_instance") as mock: with patch.object(v_c, "check_instance", return_value=True): v_c.check_instances() - self.assertTrue(v_c.required_resources_dict["floating_ips"] == i) + self.assertTrue(v_c.required_resources_dict[i]["floating_ips"] == i) @patch("bibigrid.core.utility.image_selection.select_image") def test_check_instance_image_not_active(self, mock_select_image): mock_select_image.side_effect = ImageNotActiveException() - v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) - provider = Mock() - provider.get_active_images.return_value = [] - provider.get_flavor = MagicMock(return_value={"disk": None, "ram": None}) - provider.get_image_by_id_or_name = MagicMock(return_value={"min_disk": None, "min_ram": None}) - self.assertFalse(v_c.check_instance(None, {"count": 1, "image": 2, "type": 3}, provider)) + + provider1 = Mock() + provider1.cloud_specification = {"identifier": "1"} + provider1.get_active_images.return_value = [] + provider1.get_flavor = MagicMock(return_value={"disk": None, "ram": None}) + provider1.get_image_by_id_or_name = MagicMock(return_value={"min_disk": None, "min_ram": None}) + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=None, log=Mock()) + + self.assertFalse(v_c.check_instance(None, {"count": 1, "image": 2, "type": 3}, provider1)) @patch("bibigrid.core.utility.image_selection.select_image") def test_check_instance_image_active_combination_call(self, mock_select_image): - v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) - provider = Mock() - provider.get_image_by_id_or_name = MagicMock(return_value={"status": "active"}) + provider1 = MagicMock() + provider1.get_image_by_id_or_name = MagicMock(return_value={"status": "active"}) + provider1.cloud_specification = {"identifier": "1"} + image = 2 + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=None, log=startup.LOG) + type3 = 3 + with patch.object(v_c, "check_instance_type_image_combination") as mock: - v_c.check_instance(42, {"count": 1, "image": 2, "type": 3}, provider) - mock.assert_called_with(3, mock_select_image(2), provider) + v_c.check_instance(42, {"count": 1, "image": image, "type": type3}, provider1) + mock.assert_called_with(type3, mock_select_image(), provider1) + mock_select_image.assert_called() def test_check_instance_type_image_combination_has_enough_calls(self): log = Mock() - v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=log) - provider = MagicMock() - provider.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} - provider.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + provider1 = MagicMock() + provider1.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} + provider1.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=None, log=log) with patch.object(validate_configuration, "has_enough") as mock: v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider) + provider=provider1) self.assertEqual(call(42, 22, "Type de.NBI tiny", "disk space", log), mock.call_args_list[0]) self.assertEqual(call(32, 12, "Type de.NBI tiny", "ram", log), mock.call_args_list[1]) def test_check_instance_type_image_combination_result(self): - provider = MagicMock() - provider.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} - provider.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} - v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=Mock()) + provider1 = MagicMock() + provider1.get_flavor.return_value = {"disk": 42, "ram": 32, "vcpus": 10} + provider1.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=None, log=Mock()) with patch.object(validate_configuration, "has_enough") as mock: mock.side_effect = [True, True, False, False, True, False, False, True] # True True self.assertTrue(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) + provider=provider1)) # False False self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) + provider=provider1)) # True False self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) + provider=provider1)) # False True self.assertFalse(v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider)) + provider=provider1)) def test_check_instance_type_image_combination_count(self): for i in range(3): - provider = MagicMock() - provider.get_flavor.return_value = {"disk": 42, "ram": i * 32, "vcpus": i * 10} - provider.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + provider1 = MagicMock() + provider1.get_flavor.return_value = {"disk": 42, "ram": i * 32, "vcpus": i * 10} + provider1.get_image_by_id_or_name.return_value = {"min_disk": 22, "min_ram": 12} + provider1.cloud_specification = {"identifier": "1"} log = Mock() - v_c = validate_configuration.ValidateConfiguration(providers=None, configurations=None, log=log) + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=None, log=log) with patch.object(validate_configuration, "has_enough") as mock: v_c.check_instance_type_image_combination(instance_image=None, instance_type="de.NBI tiny", - provider=provider) - self.assertEqual(32 * i, v_c.required_resources_dict["total_ram"]) - self.assertEqual(10 * i, v_c.required_resources_dict["total_cores"]) + provider=provider1) + self.assertEqual(32 * i, v_c.required_resources_dict["1"]["total_ram"]) + self.assertEqual(10 * i, v_c.required_resources_dict["1"]["total_cores"]) mock.assert_called_with(32 * i, 12, 'Type de.NBI tiny', 'ram', log) def test_check_volumes_none(self): - v_c = validate_configuration.ValidateConfiguration(providers=[42], configurations=[{}], log=Mock()) + provider1 = MagicMock() + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{}], log=Mock()) self.assertTrue(v_c.check_volumes()) def test_check_volumes_mismatch(self): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value=None) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) - v_c = validate_configuration.ValidateConfiguration(providers=[provider], + provider1 = Mock() + provider1.get_volume_by_id_or_name = MagicMock(return_value=None) + provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"masterMounts": ["Test"]}], log=Mock()) self.assertFalse(v_c.check_volumes()) def test_check_volumes_match_snapshot(self): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value=None) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": 1}) - v_c = validate_configuration.ValidateConfiguration(providers=[provider], + provider1 = Mock() + provider1.get_volume_by_id_or_name = MagicMock(return_value=None) + provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": 1}) + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"masterMounts": ["Test"]}], log=Mock()) self.assertTrue(v_c.check_volumes()) def test_check_volumes_match_snapshot_count(self): - for i in range(3): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value=None) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": i}) - v_c = validate_configuration.ValidateConfiguration(providers=[provider] * i, + for i in range(1, 3): + provider1 = Mock() + provider1.get_volume_by_id_or_name = MagicMock(return_value=None) + provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": i}) + provider1.cloud_specification = {"identifier": i} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1] * i, configurations=[{"masterMounts": ["Test"] * i}], log=Mock()) self.assertTrue(v_c.check_volumes()) - self.assertTrue(v_c.required_resources_dict["Volumes"] == i) - self.assertTrue(v_c.required_resources_dict["VolumeGigabytes"] == i ** 2) + self.assertTrue(v_c.required_resources_dict[i]["volumes"] == i) + self.assertTrue(v_c.required_resources_dict[i]["volume_gigabytes"] == i ** 2) def test_check_volumes_match_volume(self): - provider = Mock() - provider.get_volume_by_id_or_name = MagicMock(return_value={"size": 1}) - provider.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) - v_c = validate_configuration.ValidateConfiguration(providers=[provider], + provider1 = Mock() + provider1.get_volume_by_id_or_name = MagicMock(return_value={"size": 1}) + provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"masterMounts": ["Test"]}], log=Mock()) self.assertTrue(v_c.check_volumes()) - self.assertTrue(v_c.required_resources_dict["Volumes"] == 0) - self.assertTrue(v_c.required_resources_dict["VolumeGigabytes"] == 0) + self.assertTrue(v_c.required_resources_dict["1"]["volumes"] == 0) + self.assertTrue(v_c.required_resources_dict["1"]["volume_gigabytes"] == 0) def test_check_network_none(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value=None) - v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=[{}], log=Mock()) + provider1 = Mock() + provider1.get_network_by_id_or_name = MagicMock(return_value=None) + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{}], log=Mock()) self.assertFalse(v_c.check_network()) def test_check_network_no_network(self): - provider = Mock() - provider.get_subnet_by_id_or_name = MagicMock(return_value="network") - v_c = validate_configuration.ValidateConfiguration(providers=[provider], + provider1 = Mock() + provider1.get_subnet_by_id_or_name = MagicMock(return_value="network") + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"subnet": "subnet_name"}], log=Mock()) self.assertTrue(v_c.check_network()) - provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") + provider1.get_subnet_by_id_or_name.assert_called_with("subnet_name") def test_check_network_no_network_mismatch_subnet(self): - provider = Mock() - provider.get_subnet_by_id_or_name = MagicMock(return_value=None) - v_c = validate_configuration.ValidateConfiguration(providers=[provider], + provider1 = Mock() + provider1.get_subnet_by_id_or_name = MagicMock(return_value=None) + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"subnet": "subnet_name"}], log=Mock()) self.assertFalse(v_c.check_network()) - provider.get_subnet_by_id_or_name.assert_called_with("subnet_name") + provider1.get_subnet_by_id_or_name.assert_called_with("subnet_name") def test_check_network_no_subnet_mismatch_network(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value=None) - v_c = validate_configuration.ValidateConfiguration(providers=[provider], + provider1 = Mock() + provider1.get_network_by_id_or_name = MagicMock(return_value=None) + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"network": "network_name"}], log=Mock()) self.assertFalse(v_c.check_network()) - provider.get_network_by_id_or_name.assert_called_with("network_name") + provider1.get_network_by_id_or_name.assert_called_with("network_name") def test_check_network_no_subnet(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value="network") - v_c = validate_configuration.ValidateConfiguration(providers=[provider], + provider1 = Mock() + provider1.get_network_by_id_or_name = MagicMock(return_value="network") + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"network": "network_name"}], log=Mock()) self.assertTrue(v_c.check_network()) - provider.get_network_by_id_or_name.assert_called_with("network_name") + provider1.get_network_by_id_or_name.assert_called_with("network_name") def test_check_network_subnet_network(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value="network") - provider.get_subnet_by_id_or_name = MagicMock(return_value="network") - v_c = validate_configuration.ValidateConfiguration(providers=[provider], + provider1 = Mock() + provider1.get_network_by_id_or_name = MagicMock(return_value="network") + provider1.get_subnet_by_id_or_name = MagicMock(return_value="network") + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"network": "network_name"}], log=Mock()) self.assertTrue(v_c.check_network()) - provider.get_network_by_id_or_name.assert_called_with("network_name") + provider1.get_network_by_id_or_name.assert_called_with("network_name") def test_check_server_group_none(self): - provider = Mock() - provider.get_network_by_id_or_name = MagicMock(return_value=None) - v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=[{}], log=Mock()) + provider1 = Mock() + provider1.get_network_by_id_or_name = MagicMock(return_value=None) + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{}], log=Mock()) self.assertTrue(v_c.check_server_group()) def test_check_server_group_mismatch(self): - provider = Mock() - provider.get_server_group_by_id_or_name = MagicMock(return_value=None) - v_c = validate_configuration.ValidateConfiguration(providers=[provider], + provider1 = Mock() + provider1.get_server_group_by_id_or_name = MagicMock(return_value=None) + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"serverGroup": "GroupName"}], log=Mock()) self.assertFalse(v_c.check_server_group()) - provider.get_server_group_by_id_or_name.assert_called_with("GroupName") + provider1.get_server_group_by_id_or_name.assert_called_with("GroupName") def test_check_server_group_match(self): - provider = Mock() - provider.get_server_group_by_id_or_name = MagicMock(return_value="Group") - v_c = validate_configuration.ValidateConfiguration(providers=[provider], + provider1 = Mock() + provider1.get_server_group_by_id_or_name = MagicMock(return_value="Group") + provider1.cloud_specification = {"identifier": "1"} + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"serverGroup": "GroupName"}], log=Mock()) self.assertTrue(v_c.check_server_group()) - provider.get_server_group_by_id_or_name.assert_called_with("GroupName") + provider1.get_server_group_by_id_or_name.assert_called_with("GroupName") def test_check_quotas_true(self): - provider = MagicMock() - provider.cloud_specification = {"auth": {"project_name": "name"}, "identifier": "identifier"} - test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, 'Volumes': 42, - 'VolumeGigabytes': 42, 'Snapshots': 42, 'Backups': 42, 'BackupGigabytes': 42} - provider.get_free_resources.return_value = test_dict - v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=None, log=Mock()) + provider1 = MagicMock() + provider1.cloud_specification = {"auth": {"project_name": "name"}, "identifier": "1"} + test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, 'volumes': 42, + 'volume_gigabytes': 42, 'snapshots': 42, 'backups': 42, 'backup_gigabytes': 42} + provider1.get_free_resources.return_value = test_dict + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=None, log=Mock()) with patch.object(validate_configuration, "has_enough") as mock: mock.side_effect = [True] * len(test_dict) self.assertTrue(v_c.check_quotas()) - provider.get_free_resources.assert_called() + provider1.get_free_resources.assert_called() def test_check_quotas_false(self): - provider = MagicMock() - test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, 'Volumes': 42, - 'VolumeGigabytes': 42, 'Snapshots': 42, 'Backups': 42, 'BackupGigabytes': 42} - provider.get_free_resources.return_value = test_dict + provider1 = MagicMock() + provider1.cloud_specification = {"identifier": "1"} + test_dict = {'total_cores': 42, 'floating_ips': 42, 'instances': 42, 'total_ram': 42, 'volumes': 42, + 'volume_gigabytes': 42, 'snapshots': 42, 'backups': 42, 'backup_gigabytes': 42} + provider1.get_free_resources.return_value = test_dict os.environ['OS_PROJECT_NAME'] = "name" - v_c = validate_configuration.ValidateConfiguration(providers=[provider], configurations=None, log=Mock()) + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=None, log=Mock()) with patch.object(validate_configuration, "has_enough") as mock: mock.side_effect = [True] * (len(test_dict) - 1) + [False] self.assertFalse(v_c.check_quotas()) - provider.get_free_resources.assert_called() + provider1.get_free_resources.assert_called() mock.assert_called() def test_has_enough_lower(self): From 32a3d13e8226e2cf9ae8c55e7bdc8d3183660072 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 24 Apr 2024 15:38:35 +0200 Subject: [PATCH 069/145] added cloudScheduling and userRoles in bibigrid.yml --- bibigrid.yml | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/bibigrid.yml b/bibigrid.yml index 584c4bb7..2fcfcbc3 100644 --- a/bibigrid.yml +++ b/bibigrid.yml @@ -8,6 +8,8 @@ # -- BEGIN: GENERAL CLUSTER INFORMATION -- # sshTimeout: 5 # Number of ssh connection attempts with 2^attempt seconds in between (2^sshTimeout-1 is the max time before returning with an error) + # cloudScheduling: + # sshTimeout: 42 # like the sshTimeout during startup but during the on demand scheduling ## sshPublicKeyFiles listed here will be added to access the cluster. A temporary key is created by bibigrid itself. #sshPublicKeyFiles: @@ -21,22 +23,11 @@ #nfsShares: /vol/spool/ is automatically created as a nfs # - [nfsShare one] - ## Ansible (Galaxy) roles can be added for execution # KEY NOT IMPLEMENTED YET - #ansibleRoles: - # - file: SomeFile - # hosts: SomeHosts - # name: SomeName - # vars: SomeVars - # vars_file: SomeVarsFile - - #ansibleGalaxyRoles: # KEY NOT IMPLEMENTED YET - # - hosts: SomeHost - # name: SomeName - # galaxy: SomeGalaxy - # git: SomeGit - # url: SomeURL - # vars: SomeVars - # vars_file: SomeVarsFile + # userRoles: + # - hosts: + # - "master" + # roles: + # - name: "resistance_nextflow" # roles in ## Uncomment if you don't want assign a public ip to the master; for internal cluster (Tuebingen). #useMasterWithPublicIp: False # defaults True if False no public-ip (floating-ip) will be allocated From 927cbd77a79bce30e6f8fefb99e00ca683a8cc7a Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 24 Apr 2024 17:13:44 +0200 Subject: [PATCH 070/145] added userRoles in documentation --- .../markdown/features/configuration.md | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 5c4f77aa..1b59ce44 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -92,30 +92,16 @@ What is NFS? NFS (Network File System) is a stable and well-functioning network protocol for exchanging files over the local network. -#### ansibleRoles (optional) +#### userRoles (optional) -Yet to be explained and implemented. +`userRoles` takes a list of elements containing the keys `hosts`, `roles` and ```yaml - - file: SomeFile - hosts: SomeHosts - name: SomeName - vars: SomeVars - vars_file: SomeVarsFile -``` - -#### ansibleGalaxyRoles (optional) - -Yet to be explained and implemented. - -```yaml - - hosts: SomeHost - name: SomeName - galaxy: SomeGalaxy - git: SomeGit - url: SomeURL - vars: SomeVars - vars_file: SomeVarsFile +userRoles: + - hosts: + - "master" + roles: + - name: "resistance_nextflow" ``` #### localFS (optional) From fb17204ce774d6e8233ac7c10c27f69dccf7cb6f Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 24 Apr 2024 17:43:40 +0200 Subject: [PATCH 071/145] added varsFiles and comments --- documentation/markdown/features/configuration.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 1b59ce44..c31227cc 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -97,11 +97,13 @@ NFS (Network File System) is a stable and well-functioning network protocol for `userRoles` takes a list of elements containing the keys `hosts`, `roles` and ```yaml -userRoles: +userRoles: # see ansible_hosts for all options - hosts: - - "master" + - "master" roles: - - name: "resistance_nextflow" + - name: "resistance_nextflow" # role placed in + # varsFiles: # vars placed in + # - file1 ``` #### localFS (optional) From 0812164a44051c73157c5707a6a1cd2793ec4a04 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 24 Apr 2024 17:54:38 +0200 Subject: [PATCH 072/145] added folder path in documentation --- documentation/markdown/features/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index c31227cc..bf6e42d0 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -101,8 +101,8 @@ userRoles: # see ansible_hosts for all options - hosts: - "master" roles: - - name: "resistance_nextflow" # role placed in - # varsFiles: # vars placed in + - name: "resistance_nextflow" # role placed in resources/playbook/user_roles + # varsFiles: # - file1 ``` From 323698dd2354fbd1ee045474475bdc914901a1b7 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 24 Apr 2024 17:55:31 +0200 Subject: [PATCH 073/145] fixed naming --- documentation/markdown/features/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index bf6e42d0..ecc619cd 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -101,7 +101,7 @@ userRoles: # see ansible_hosts for all options - hosts: - "master" roles: - - name: "resistance_nextflow" # role placed in resources/playbook/user_roles + - name: "resistance_nextflow" # role placed in resources/playbook/roles_user # varsFiles: # - file1 ``` From 8b7d185b6a63234cb632f06e84172c7d34c3c22b Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 24 Apr 2024 17:58:30 +0200 Subject: [PATCH 074/145] added that vars are optional --- documentation/markdown/features/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index ecc619cd..cd7be47d 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -102,7 +102,7 @@ userRoles: # see ansible_hosts for all options - "master" roles: - name: "resistance_nextflow" # role placed in resources/playbook/roles_user - # varsFiles: + # varsFiles: # (optional) # - file1 ``` From 53a574d9cea158f56e1b64ab6f85bfd6194a23dd Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 24 Apr 2024 18:00:46 +0200 Subject: [PATCH 075/145] polished userRoles documentation --- bibigrid.yml | 8 +++++--- documentation/markdown/features/configuration.md | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bibigrid.yml b/bibigrid.yml index 2fcfcbc3..bf073108 100644 --- a/bibigrid.yml +++ b/bibigrid.yml @@ -23,11 +23,13 @@ #nfsShares: /vol/spool/ is automatically created as a nfs # - [nfsShare one] - # userRoles: + # userRoles: # see ansible_hosts for all options # - hosts: # - "master" - # roles: - # - name: "resistance_nextflow" # roles in + # roles: # roles placed in resources/playbook/roles_user + # - name: "resistance_nextflow" + # varsFiles: # (optional) + # - [...] ## Uncomment if you don't want assign a public ip to the master; for internal cluster (Tuebingen). #useMasterWithPublicIp: False # defaults True if False no public-ip (floating-ip) will be allocated diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index cd7be47d..f154efb1 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -100,8 +100,8 @@ NFS (Network File System) is a stable and well-functioning network protocol for userRoles: # see ansible_hosts for all options - hosts: - "master" - roles: - - name: "resistance_nextflow" # role placed in resources/playbook/roles_user + roles: # roles placed in resources/playbook/roles_user + - name: "resistance_nextflow" # varsFiles: # (optional) # - file1 ``` From 35335400d78e78eaf4abe71cdbad073b5f5d9662 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Thu, 25 Apr 2024 07:37:06 +0200 Subject: [PATCH 076/145] 439 additional ansible roles (#495) * added roles structure * updated roles_path * fixed upper lower case * improved customRole implementation * minor fixes regarding role_paths * improved variable naming of user_roles --- .gitignore | 6 ++++ bibigrid/core/utility/ansible_configurator.py | 32 ++++++++----------- resources/defaults/ansible/ansible.cfg | 1 + .../playbook/roles/additional/tasks/main.yml | 4 --- resources/playbook/roles_galaxy/README | 3 ++ resources/playbook/roles_user/README | 2 ++ .../resistance_nextflow/tasks/main.yml | 25 +++++++++++++++ 7 files changed, 51 insertions(+), 22 deletions(-) delete mode 100644 resources/playbook/roles/additional/tasks/main.yml create mode 100644 resources/playbook/roles_galaxy/README create mode 100644 resources/playbook/roles_user/README create mode 100644 resources/playbook/roles_user/resistance_nextflow/tasks/main.yml diff --git a/.gitignore b/.gitignore index 0f7fbc2f..e2dacc6a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,12 @@ resources/playbook/host_vars/ resources/playbook/group_vars/ resources/tests/bibigrid_test.yml +# Roles +resources/playbook/roles_galaxy/* +!resources/playbook/roles_galaxy/README +resources/playbook/roles_user/* +!resources/playbook/roles_user/README +!resources/playbook/roles_user/resistance_nextflow # any log files *.log log/ diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 0ef6a39f..526b80de 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -17,9 +17,8 @@ from bibigrid.core.utility.wireguard import wireguard_keys DEFAULT_NFS_SHARES = ["/vol/spool"] -ADDITIONAL_PATH = "additional/" PYTHON_INTERPRETER = "/usr/bin/python3" -vpngtw_ROLES = [{"role": "bibigrid", "tags": ["bibigrid", "bibigrid-vpngtw"]}] +VPNGTW_ROLES = [{"role": "bibigrid", "tags": ["bibigrid", "bibigrid-vpngtw"]}] MASTER_ROLES = [{"role": "bibigrid", "tags": ["bibigrid", "bibigrid-master"]}] WORKER_ROLES = [{"role": "bibigrid", "tags": ["bibigrid", "bibigrid-worker"]}] VARS_FILES = [aRP.CONFIG_YML, aRP.HOSTS_YML] @@ -48,30 +47,30 @@ def delete_old_vars(log): os.remove(file) -def generate_site_file_yaml(custom_roles): +def generate_site_file_yaml(user_roles): """ Generates site_yaml (dict). Deepcopy is used in case roles might differ between servers in the future. - @param custom_roles: ansibleRoles given by the config + @param user_roles: userRoles given by the config @return: site_yaml (dict) """ site_yaml = [{'hosts': 'master', "become": "yes", "vars_files": VARS_FILES, "roles": MASTER_ROLES}, - {'hosts': 'vpngtw', "become": "yes", "vars_files": VARS_FILES, "roles": vpngtw_ROLES}, - {"hosts": "workers", "become": "yes", "vars_files": VARS_FILES, "roles": WORKER_ROLES}] # , - # {"hosts": "vpngtw", "become": "yes", "vars_files": copy.deepcopy(VARS_FILES), - # "roles": ["common", "vpngtw"]}] - # add custom roles and vars - for custom_role in custom_roles: - VARS_FILES.append(custom_role["vars_file"]) - for role_group in [MASTER_ROLES, vpngtw_ROLES, WORKER_ROLES]: - role_group.append(ADDITIONAL_PATH + custom_role["name"]) + {'hosts': 'vpngtw', "become": "yes", "vars_files": VARS_FILES, "roles": VPNGTW_ROLES}, + {"hosts": "workers", "become": "yes", "vars_files": VARS_FILES, "roles": WORKER_ROLES}] + for user_role in user_roles: + for host_dict in site_yaml: + if host_dict["hosts"] in user_role["hosts"]: + host_dict["vars_files"] = host_dict["vars_files"] + user_role.get("varsFiles", []) + host_dict["roles"] = host_dict["roles"] + [{"role": role["name"], "tags": role.get("tags", [])} for role + in user_role["roles"]] return site_yaml def write_host_and_group_vars(configurations, providers, cluster_id, log): # pylint: disable=too-many-locals """ Filters unnecessary information + @param log: @param configurations: configurations @param providers: providers @param cluster_id: To get proper naming @@ -205,9 +204,6 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us master_configuration.get("zabbixConf", {}), strategy=mergedeep.Strategy.TYPESAFE_REPLACE) - for from_key, to_key in [("ansibleRoles", "ansible_roles"), ("ansibleGalaxyRoles", "ansible_galaxy_roles")]: - pass_through(master_configuration, common_configuration_yaml, from_key, to_key) - if len(configurations) > 1: peers = configuration_handler.get_list_by_key(configurations, "wireguard_peer") common_configuration_yaml["wireguard_common"] = {"mask_bits": 24, "listen_port": 51820, "peers": peers} @@ -383,7 +379,7 @@ def configure_ansible_yaml(providers, configurations, cluster_id, log): delete_old_vars(log) log.info("Writing ansible files...") alias = configurations[0].get("aliasDumper", False) - ansible_roles = get_ansible_roles(configurations[0].get("ansibleRoles"), log) + user_roles = configurations[0].get("userRoles") default_user = providers[0].cloud_specification["auth"].get("username", configurations[0].get("sshUser", "Ubuntu")) add_wireguard_peers(configurations) for path, generated_yaml in [ @@ -397,6 +393,6 @@ def configure_ansible_yaml(providers, configurations, cluster_id, log): "sshUser"], configurations, cluster_id, log)), - (aRP.SITE_CONFIG_FILE, generate_site_file_yaml(ansible_roles))]: + (aRP.SITE_CONFIG_FILE, generate_site_file_yaml(user_roles))]: write_yaml(path, generated_yaml, log, alias) write_host_and_group_vars(configurations, providers, cluster_id, log) # writing included in method diff --git a/resources/defaults/ansible/ansible.cfg b/resources/defaults/ansible/ansible.cfg index ee536310..28bf3659 100644 --- a/resources/defaults/ansible/ansible.cfg +++ b/resources/defaults/ansible/ansible.cfg @@ -1,5 +1,6 @@ # This file is moved programmatically to /etc/ansible/ansible.cfg on the master so it shouldn't be moved manually [defaults] +roles_path = "/opt/playbook/roles:/opt/playbook/roles_galaxy:/opt/playbook/roles_user" inventory = ./ansible_hosts host_key_checking = False forks=50 diff --git a/resources/playbook/roles/additional/tasks/main.yml b/resources/playbook/roles/additional/tasks/main.yml deleted file mode 100644 index e949ee7f..00000000 --- a/resources/playbook/roles/additional/tasks/main.yml +++ /dev/null @@ -1,4 +0,0 @@ -- debug: - msg: - - "Hello {{ ansible_user }}!" - diff --git a/resources/playbook/roles_galaxy/README b/resources/playbook/roles_galaxy/README new file mode 100644 index 00000000..efd4f886 --- /dev/null +++ b/resources/playbook/roles_galaxy/README @@ -0,0 +1,3 @@ +# User Roles +This folder can be filled with [galaxy roles](https://docs.ansible.com/ansible/latest/galaxy/user_guide.html). +They will not be overwritten when pulling a new BiBiGrid version. \ No newline at end of file diff --git a/resources/playbook/roles_user/README b/resources/playbook/roles_user/README new file mode 100644 index 00000000..bfb421c8 --- /dev/null +++ b/resources/playbook/roles_user/README @@ -0,0 +1,2 @@ +# User Roles +This folder can be filled with your own custom roles. They will not be overwritten when pulling a new BiBiGrid version. \ No newline at end of file diff --git a/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml b/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml new file mode 100644 index 00000000..85b79975 --- /dev/null +++ b/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml @@ -0,0 +1,25 @@ +- debug: + msg: + - "Hello {{ ansible_user }}!" + +- name: Unarchive ZIP file from GitHub repository + unarchive: + src: "https://github.com/deNBI/bibigrid_clum/raw/main/resources/Resistance_Nextflow.tar.xz" + dest: "/vol/spool/" + remote_src: yes + +- name: Install Java JRE on Debian/Ubuntu + become: True + apt: + name: default-jre # Package name for Java JRE on Debian-based systems + state: present # Ensure that the package is present, you can use "latest" as well + +- name: Get Nextflow + shell: wget -qO- https://get.nextflow.io | bash + args: + chdir: /vol/spool/ + +- name: Execute Nextflow workflow + shell: ./nextflow run resFinder.nf -profile slurm + args: + chdir: "/vol/spool" # Change to the directory where your workflow resides From c77ef62962fc65c6f5e3bd9ceaffed44e3667e5a Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 25 Apr 2024 15:09:34 +0200 Subject: [PATCH 077/145] added documentation for other configurations --- .../markdown/bibigrid_feature_list.md | 5 +++-- .../markdown/features/other_configurations.md | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 documentation/markdown/features/other_configurations.md diff --git a/documentation/markdown/bibigrid_feature_list.md b/documentation/markdown/bibigrid_feature_list.md index 0ddecab2..96d828d1 100644 --- a/documentation/markdown/bibigrid_feature_list.md +++ b/documentation/markdown/bibigrid_feature_list.md @@ -5,14 +5,15 @@ | [Version](features/version.md) | Returns BiBiGrid's version for opening issues and the like | | [Terminate Cluster](features/terminate_cluster.md) | Terminates the cluster specified by cluster-id i.e. removes key, application credentials, servers and floating-ips. | | [Create](features/create.md) | Creates the cluster specified by the configuration. | - | [List Clusters](features/list_clusters.md) | Shows info of all clusters if no cluster-id is specified. Otherwise the cluster-id's cluster will be shown in great detail. | +| [List Clusters](features/list_clusters.md) | Shows info of all clusters if no cluster-id is specified. Otherwise the cluster-id's cluster will be shown in great detail. | | [Check](features/check.md) | Checks if given configuration is valid and necessary security measures are taken. | | [Web IDE](features/ide.md) | Connects to running IDE of cluster-id's cluster. Requires that given cluster was setup with an ide. | | [Update](features/update.md) | Updates the master's playbook and runs that playbook for the master. Requires that no job is running and no workers up. | | [Cloud Specification Data](features/cloud_specification_data.md) | Contains necessary data to establish a general connection to the provider. | - | [Configuration](features/configuration.md) | Contains all data regarding cluster setup for all providers. | +| [Configuration](features/configuration.md) | Contains all data regarding cluster setup for all providers. | | [Command Line Interface](features/CLI.md) | What command line arguments can be passed into BiBiGrid. | | [Multi Cloud](features/multi_cloud.md) | Explanation how BiBiGrid's multi-cloud approach works | | [BiBiGrid Cluster Commands](features/cluster_commands.md) | Short useful commands to get information on the cluster | +| [Other Configurations](features/other_configurations.md) | Info about custom `ansible.cfg` and `slurm.conf` | ![](../images/actions.jpg) \ No newline at end of file diff --git a/documentation/markdown/features/other_configurations.md b/documentation/markdown/features/other_configurations.md new file mode 100644 index 00000000..0a92faa9 --- /dev/null +++ b/documentation/markdown/features/other_configurations.md @@ -0,0 +1,22 @@ +# Other Configurations +Besides the general BiBiGrid configuration there is also an `ansible.cfg` and a `slurm.conf`. +For 99% of all users those never need to be touched. However, some use cases require changes to those configurations. +For that purpose we store defaults in `resources/defaults` and on the first run copy copies to the actual locations +`resources/playbook/ansible.cfg` and `resources/playbook/bibigrid/templates/slurm/slurm.j2`. +That way you can make changes and if something doesn't work, you can just delete the configuration to go back to our +default one. + +## slurm.cfg +The `slurm.j2` is not a static configuration file, but instead a [jinja](https://jinja.palletsprojects.com/en/3.1.x/) template for the actual configuration that is +generated during runtime. That is necessary because it contains the actual instance names that are only known at runtime. +The jinja template is converted to the actual configuration by ansible in the `042-slurm.yml` task. + +The `slurm.j2` also takes certain information from your BiBiGrid configuration (see [slurmConf](configuration.md#slurmconf-optional)). + +Read more about the `slurm.conf` [here](https://slurm.schedmd.com/slurm.conf.html). + +## ansible.cfg +The `ansible.cfg` defines how ansible behaves during runtime. A key that sometimes need to be adapted is `timeout` which +is the timeout for the connection plugin. If your host answers very slowly, a low timeout might cause issues. + +Read more about the `ansible.cfg` [here](https://docs.ansible.com/ansible/latest/reference_appendices/config.html). \ No newline at end of file From 2a64d33d99ae6c2170812a4dc804b9748ba414c7 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 25 Apr 2024 15:09:46 +0200 Subject: [PATCH 078/145] added new feature keys --- .../markdown/features/configuration.md | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index f154efb1..006fded0 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -120,6 +120,31 @@ If `True`, master will store DNS information for his workers. Default is `False` If `False`, the cluster will start without the job scheduling system slurm. This is relevant to the fewest. Default is `True`. +##### SlurmConf (optional) +`SlurmConf` contains variable fields in the `slurm.conf`. The most common use is to increase the `SuspendTime` +and the `ResumeTimeout` like: + +```yaml +elastic_scheduling: + SuspendTime: 1800 + ResumeTimeout: 1800 +``` + +Please only use if necessary. On Demand Scheduling improves resource availability for all users. + +###### Defaults +```yaml +slurmConf: + db: slurm # see task 042-slurm-server.yml + db_user: slurm + db_password: changeme + munge_key: # automatically generated via id_generation.generate_munge_key + elastic_scheduling: + SuspendTime: 900 # if a node doesn't start in SuspendTime seconds, the start is considered failed. See https://slurm.schedmd.com/slurm.conf.html#OPT_ResumeProgram + ResumeTimeout: 900 # if a node is not used for ResumeTimeout seconds, it will shut down + TreeWidth: 128 # https://slurm.schedmd.com/slurm.conf.html#OPT_TreeWidth +``` + #### zabbix (optional) If `True`, the monitoring solution [zabbix](https://www.zabbix.com/) will be installed on the master. Default is `False`. @@ -194,7 +219,7 @@ workerInstance: - `type` sets the instance's hardware configuration. - `image` sets the bootable operating system to be installed on the instance. - `count` sets how many workers of that `type` `image` combination are in this work group -- `onDemand` defines whether nodes in the worker group are scheduled on demand (True) or are started permanently (False). This option only works on the master cloud for now. +- `onDemand` defines whether nodes in the worker group are scheduled on demand (True) or are started permanently (False). Please only use if necessary. On Demand Scheduling improves resource availability for all users. This option only works on the master cloud for now. ##### Find your active `images` From 0e5a35093f7de77a4f37e385a86348707b2b04ab Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 25 Apr 2024 17:15:57 +0200 Subject: [PATCH 079/145] fixed template files not being j2 --- .gitignore | 2 +- bibigrid/core/utility/paths/ansible_resources_path.py | 5 +++-- resources/defaults/slurm/{slurm.conf => slurm.j2} | 0 resources/playbook/roles/bibigrid/tasks/020-disk-server.yml | 5 +++-- resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml | 2 +- resources/playbook/roles/bibigrid/tasks/042-slurm.yml | 4 ++-- .../templates/slurm/{job_container.conf => job_container.j2} | 0 .../bibigrid/templates/slurm/{slurmdbd.conf => slurmdbd.j2} | 0 8 files changed, 10 insertions(+), 8 deletions(-) rename resources/defaults/slurm/{slurm.conf => slurm.j2} (100%) rename resources/playbook/roles/bibigrid/templates/slurm/{job_container.conf => job_container.j2} (100%) rename resources/playbook/roles/bibigrid/templates/slurm/{slurmdbd.conf => slurmdbd.j2} (100%) diff --git a/.gitignore b/.gitignore index 0f7fbc2f..79833f52 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ # variable resources resources/playbook/ansible.cfg -resources/playbook/roles/bibigrid/templates/slurm/slurm.conf +resources/playbook/roles/bibigrid/templates/slurm/slurm.j2 resources/playbook/site.yml resources/playbook/ansible_hosts resources/playbook/vars/ diff --git a/bibigrid/core/utility/paths/ansible_resources_path.py b/bibigrid/core/utility/paths/ansible_resources_path.py index 5c0e01b3..1f29dc25 100644 --- a/bibigrid/core/utility/paths/ansible_resources_path.py +++ b/bibigrid/core/utility/paths/ansible_resources_path.py @@ -24,6 +24,7 @@ DEFAULT_IP_FILE = VARS_PATH + "{{ ansible_default_ipv4.address }}.yml" ANSIBLE_CFG = "ansible.cfg" SLURM_CONF = "slurm.conf" +SLURM_J2 = "slurm.j2" # LOCAL PLAYBOOK = "playbook/" @@ -41,12 +42,12 @@ VARS_FOLDER = os.path.join(PLAYBOOK_PATH, VARS_PATH) GROUP_VARS_FOLDER = os.path.join(PLAYBOOK_PATH, GROUP_VARS_PATH) HOST_VARS_FOLDER = os.path.join(PLAYBOOK_PATH, HOST_VARS_PATH) -SLURM_CONF_TEMPLATE_PATH = os.path.join(PLAYBOOK_PATH, "roles", "bibigrid", "templates", "slurm", SLURM_CONF) +SLURM_CONF_TEMPLATE_PATH = os.path.join(PLAYBOOK_PATH, "roles", "bibigrid", "templates", "slurm", SLURM_J2) # DEFAULTS DEFAULTS = os.path.join(b_p.RESOURCES_PATH, "defaults") ANSIBLE_CFG_DEFAULT_PATH = os.path.join(DEFAULTS, "ansible", ANSIBLE_CFG) -SLURM_CONF_TEMPLATE_DEFAULT_PATH = os.path.join(DEFAULTS, "slurm", SLURM_CONF) +SLURM_CONF_TEMPLATE_DEFAULT_PATH = os.path.join(DEFAULTS, "slurm", SLURM_J2) # REMOTE diff --git a/resources/defaults/slurm/slurm.conf b/resources/defaults/slurm/slurm.j2 similarity index 100% rename from resources/defaults/slurm/slurm.conf rename to resources/defaults/slurm/slurm.j2 diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml index 6691225e..de4f0049 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml @@ -18,9 +18,9 @@ when: master.disks is defined - when: volumes is defined and auto_mount - failed_when: false block: - name: Make sure disks are available + failed_when: false filesystem: fstype: ext4 dev: "{{ item.device }}" @@ -29,6 +29,7 @@ with_items: "{{ volumes }}" - name: Create mount folders if they don't exist + failed_when: false file: path: "/{{ item.name }}" state: directory @@ -38,7 +39,7 @@ with_items: "{{ volumes }}" - name: Mount disks - + failed_when: false mount: path: "{{ item.name }}" src: "{{ item.device }}" diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml index 580aabc4..8abd5d61 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml @@ -14,7 +14,7 @@ - name: Create slurmdb configuration file template: - src: slurm/slurmdbd.conf + src: slurm/slurmdbd.j2 dest: /etc/slurm/slurmdbd.conf owner: slurm group: root diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm.yml b/resources/playbook/roles/bibigrid/tasks/042-slurm.yml index e134dbe2..a4d47c59 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm.yml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm.yml @@ -88,7 +88,7 @@ - name: Create Slurm configuration template: - src: slurm/slurm.conf + src: slurm/slurm.j2 dest: /etc/slurm/slurm.conf owner: slurm group: root @@ -99,7 +99,7 @@ - name: Create Job Container configuration template: - src: slurm/job_container.conf + src: slurm/job_container.j2 dest: /etc/slurm/job_container.conf owner: slurm group: root diff --git a/resources/playbook/roles/bibigrid/templates/slurm/job_container.conf b/resources/playbook/roles/bibigrid/templates/slurm/job_container.j2 similarity index 100% rename from resources/playbook/roles/bibigrid/templates/slurm/job_container.conf rename to resources/playbook/roles/bibigrid/templates/slurm/job_container.j2 diff --git a/resources/playbook/roles/bibigrid/templates/slurm/slurmdbd.conf b/resources/playbook/roles/bibigrid/templates/slurm/slurmdbd.j2 similarity index 100% rename from resources/playbook/roles/bibigrid/templates/slurm/slurmdbd.conf rename to resources/playbook/roles/bibigrid/templates/slurm/slurmdbd.j2 From 66b4996de46407024fff96ebe3638c850f3e7dcc Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 26 Apr 2024 12:22:25 +0200 Subject: [PATCH 080/145] added helpful comments and removed no longer used roles/additional/ --- resources/playbook/roles/additional/tasks/main.yml | 4 ---- resources/tests/bibigrid_test_example.yml | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 resources/playbook/roles/additional/tasks/main.yml diff --git a/resources/playbook/roles/additional/tasks/main.yml b/resources/playbook/roles/additional/tasks/main.yml deleted file mode 100644 index e949ee7f..00000000 --- a/resources/playbook/roles/additional/tasks/main.yml +++ /dev/null @@ -1,4 +0,0 @@ -- debug: - msg: - - "Hello {{ ansible_user }}!" - diff --git a/resources/tests/bibigrid_test_example.yml b/resources/tests/bibigrid_test_example.yml index 9e3a9b1d..b8b19bfa 100644 --- a/resources/tests/bibigrid_test_example.yml +++ b/resources/tests/bibigrid_test_example.yml @@ -1,3 +1,4 @@ +# remove _example in order to actually use this file - infrastructure: # former mode. cloud: #credentials # name of clouds.yaml entry @@ -6,4 +7,4 @@ sshUser: ubuntu network: # network - snapshotImage: # name of a snapshot + snapshotImage: # name of a snapshot to create volume from. Volume needs to be deleted manually afterwards (optional) From 3367d55b32426417b57019cf5a0f8b614a931e48 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 30 Apr 2024 13:11:47 +0200 Subject: [PATCH 081/145] userRoles crashes if no role set --- bibigrid/core/utility/ansible_configurator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 526b80de..2d44778d 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -379,7 +379,7 @@ def configure_ansible_yaml(providers, configurations, cluster_id, log): delete_old_vars(log) log.info("Writing ansible files...") alias = configurations[0].get("aliasDumper", False) - user_roles = configurations[0].get("userRoles") + user_roles = configurations[0].get("userRoles", []) default_user = providers[0].cloud_specification["auth"].get("username", configurations[0].get("sshUser", "Ubuntu")) add_wireguard_peers(configurations) for path, generated_yaml in [ From 678ad834b3f38815aca72849ff2c4b6dd320ec61 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 30 Apr 2024 16:08:10 +0200 Subject: [PATCH 082/145] fixed ansible.cfg path '"' --- resources/defaults/ansible/ansible.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/defaults/ansible/ansible.cfg b/resources/defaults/ansible/ansible.cfg index 28bf3659..3ed0efac 100644 --- a/resources/defaults/ansible/ansible.cfg +++ b/resources/defaults/ansible/ansible.cfg @@ -1,6 +1,6 @@ # This file is moved programmatically to /etc/ansible/ansible.cfg on the master so it shouldn't be moved manually [defaults] -roles_path = "/opt/playbook/roles:/opt/playbook/roles_galaxy:/opt/playbook/roles_user" +roles_path = /opt/playbook/roles:/opt/playbook/roles_galaxy:/opt/playbook/roles_user inventory = ./ansible_hosts host_key_checking = False forks=50 From 66ed330d74a036f37421772966dbc7e525993901 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 3 May 2024 19:46:04 +0200 Subject: [PATCH 083/145] implemented partition system --- bibigrid/core/utility/ansible_configurator.py | 7 ++-- resources/defaults/slurm/slurm.j2 | 34 +++++++++++-------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 2d44778d..c2c95219 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -96,7 +96,8 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py "network": configuration["network"], "flavor": flavor_dict, "gateway_ip": configuration["private_v4"], "cloud_identifier": configuration["cloud_identifier"], - "on_demand": worker.get("onDemand", True)} + "on_demand": worker.get("onDemand", True), + "partitions": worker.get("partitions", []) + ["all", configuration["cloud_identifier"]]} worker_features = worker.get("features", []) if isinstance(worker_features, str): @@ -104,6 +105,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py features = set(configuration_features + worker_features) if features: worker_dict["features"] = features + pass_through(configuration, worker_dict, "waitForServices", "wait_for_services") write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, group_name), worker_dict, log) vpngtw = configuration.get("vpnInstance") @@ -135,7 +137,8 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py "flavor": flavor_dict, "private_v4": configuration["private_v4"], "cloud_identifier": configuration["cloud_identifier"], "volumes": configuration["volumes"], "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False), - "on_demand": False} + "on_demand": False, + "partitions": master.get("partitions", []) + ["all", configuration["cloud_identifier"]]} if configuration.get("wireguard_peer"): master_dict["wireguard"] = {"ip": "10.0.0.1", "peer": configuration.get("wireguard_peer")} pass_through(configuration, master_dict, "waitForServices", "wait_for_services") diff --git a/resources/defaults/slurm/slurm.j2 b/resources/defaults/slurm/slurm.j2 index 19645fd3..8d7af05e 100644 --- a/resources/defaults/slurm/slurm.j2 +++ b/resources/defaults/slurm/slurm.j2 @@ -64,25 +64,31 @@ SlurmdDebug=info SlurmdLogFile=/var/log/slurm/slurmd.log # COMPUTE NODES -{% set sl = {} %} -{% set all = {"nodes":[]} %} +{% set partitions = {} %} +{% set exclude_groups = [] %} {% set master_or_empty = groups.master if use_master_as_compute else [] %} -{% for node_name in master_or_empty +groups.workers %} +{% set node_groups = [] %} +{% for node_name in master_or_empty + groups.workers %} {% set node = hostvars[node_name] %} -{% set mem = node.flavor.ram // 1024 * 1000 %} -{% if node.cloud_identifier not in sl %} -{{ sl.update({node.cloud_identifier: []}) }} +{% if node.name not in node_groups %} +{% if not node.on_demand %} +{% set _ = exclude_groups.append(node.name) %} {% endif %} -{% if node.name not in sl[node.cloud_identifier] %} -NodeName={{ node.name }} SocketsPerBoard={{ node.flavor.vcpus }} CoresPerSocket=1 RealMemory={{ mem - [mem // 2, 16000] | min }} State={{'CLOUD' if node.on_demand else 'UNKNOWN'}} {{"Features="+node.features|join(",") if node.features is defined}}# {{ node.cloud_identifier }} -{{ sl[node.cloud_identifier].append(node.name)}} -{{ all.nodes.append(node.name)}} +{% set _ = node_groups.append(node.name) %} +{% set mem = (node.flavor.ram // 1024) * 1000 %} +NodeName={{ node.name }} SocketsPerBoard={{ node.flavor.vcpus }} CoresPerSocket=1 RealMemory={{ mem - [mem // 2, 16000] | min }} State={{ 'CLOUD' if node.on_demand else 'UNKNOWN' }} {{"Features=" + (node.features | join(",")) if node.features is defined }}# {{ node.cloud_identifier }} +{% for partition in node.partitions %} +{% if partition not in partitions %} +{% set _ = partitions.update({partition: []}) %} {% endif %} +{% set _ = partitions[partition].append(node.name) %} {% endfor %} -{% for key,value in sl.items() %} -PartitionName={{ key }} Nodes={{ value|join(",") }} +{% endif %} +{% endfor %} + +{% for key, value in partitions.items() %} +PartitionName={{ key }} Nodes={{ value | join(",") }} {% endfor %} -PartitionName=All Nodes = {{ all.nodes|join(",") }} default=yes # JobSubmitPlugin JobSubmitPlugins=all_partitions @@ -95,7 +101,7 @@ SuspendProgram=/opt/slurm/terminate.sh # Suspend time is 10 minutes (600 seconds) SuspendTime= {{ slurm_conf.elastic_scheduling.SuspendTime }} # Excludes {{ hostvars[groups.master.0].name }} from suspend -SuspendExcNodes={{ hostvars[groups.master.0].name }} +SuspendExcNodes={{ exclude_groups | join(',') }} # Maximum number of nodes TreeWidth= {{ slurm_conf.elastic_scheduling.TreeWidth }} # Do not cache dns names From edf463902b4ffd1493951b083c046660751fc141 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 6 May 2024 10:10:18 +0200 Subject: [PATCH 084/145] added keys customAnsibleCfg and customSlurmConf as keys that stop the automatic copying --- bibigrid/core/actions/create.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index b6a8d66f..046704c5 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -59,13 +59,6 @@ def get_identifier(identifier, cluster_id, additional=""): WIREGUARD_SECURITY_GROUP_NAME = "wireguard" + SEPARATOR + "{cluster_id}" -def create_defaults(): - if not os.path.isfile(a_rp.ANSIBLE_CFG_PATH): - shutil.copy(a_rp.ANSIBLE_CFG_DEFAULT_PATH, a_rp.ANSIBLE_CFG_PATH) - if not os.path.isfile(a_rp.SLURM_CONF_TEMPLATE_PATH): - shutil.copy(a_rp.SLURM_CONF_TEMPLATE_DEFAULT_PATH, a_rp.SLURM_CONF_TEMPLATE_PATH) - - class Create: # pylint: disable=too-many-instance-attributes,too-many-arguments """ The class Create holds necessary methods to execute the Create-Action @@ -108,6 +101,15 @@ def __init__(self, providers, configurations, config_path, log, debug=False, clu "useMasterWithPublicIp", True) self.log.debug("Keyname: %s", self.key_name) + + def create_defaults(self): + if not self.configurations[0].get("customAnsibleCfg", False) or not os.path.isfile(a_rp.ANSIBLE_CFG_PATH): + shutil.copy(a_rp.ANSIBLE_CFG_DEFAULT_PATH, a_rp.ANSIBLE_CFG_PATH) + if not self.configurations[0].get("customSlurmConf", False) or not os.path.isfile( + a_rp.SLURM_CONF_TEMPLATE_PATH): + shutil.copy(a_rp.SLURM_CONF_TEMPLATE_DEFAULT_PATH, a_rp.SLURM_CONF_TEMPLATE_PATH) + + def generate_keypair(self): """ Generates ECDSA Keypair using system-function ssh-keygen and uploads the generated public key to providers. @@ -422,7 +424,7 @@ def create(self): # pylint: disable=too-many-branches,too-many-statements try: self.generate_keypair() self.prepare_configurations() - create_defaults() + self.create_defaults() self.generate_security_groups() self.start_start_server_threads() self.extended_network_configuration() From 8a265da881b3a783ca23fb9bcd28690171e5b96b Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 6 May 2024 10:15:16 +0200 Subject: [PATCH 085/145] improved spacing --- bibigrid/core/actions/create.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 046704c5..8d33d09f 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -101,7 +101,6 @@ def __init__(self, providers, configurations, config_path, log, debug=False, clu "useMasterWithPublicIp", True) self.log.debug("Keyname: %s", self.key_name) - def create_defaults(self): if not self.configurations[0].get("customAnsibleCfg", False) or not os.path.isfile(a_rp.ANSIBLE_CFG_PATH): shutil.copy(a_rp.ANSIBLE_CFG_DEFAULT_PATH, a_rp.ANSIBLE_CFG_PATH) @@ -109,7 +108,6 @@ def create_defaults(self): a_rp.SLURM_CONF_TEMPLATE_PATH): shutil.copy(a_rp.SLURM_CONF_TEMPLATE_DEFAULT_PATH, a_rp.SLURM_CONF_TEMPLATE_PATH) - def generate_keypair(self): """ Generates ECDSA Keypair using system-function ssh-keygen and uploads the generated public key to providers. From 913241d6ecc6fab6d6143d3c43e937d4ce46cae6 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 6 May 2024 10:19:44 +0200 Subject: [PATCH 086/145] added logging --- bibigrid/core/actions/create.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 8d33d09f..fd54b60d 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -102,10 +102,13 @@ def __init__(self, providers, configurations, config_path, log, debug=False, clu self.log.debug("Keyname: %s", self.key_name) def create_defaults(self): + self.log.debug("Creating default files") if not self.configurations[0].get("customAnsibleCfg", False) or not os.path.isfile(a_rp.ANSIBLE_CFG_PATH): + self.log.debug("Copying ansible.cfg") shutil.copy(a_rp.ANSIBLE_CFG_DEFAULT_PATH, a_rp.ANSIBLE_CFG_PATH) if not self.configurations[0].get("customSlurmConf", False) or not os.path.isfile( a_rp.SLURM_CONF_TEMPLATE_PATH): + self.log.debug("Copying slurm.conf") shutil.copy(a_rp.SLURM_CONF_TEMPLATE_DEFAULT_PATH, a_rp.SLURM_CONF_TEMPLATE_PATH) def generate_keypair(self): From 8e1c0a6bc3dfd82cdf1a97d203d2130b7aa990e7 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 6 May 2024 11:29:15 +0200 Subject: [PATCH 087/145] updated documentation --- .../markdown/features/configuration.md | 105 ++++++++---------- 1 file changed, 44 insertions(+), 61 deletions(-) diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 006fded0..a2256139 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -48,21 +48,36 @@ sshPublicKeyFiles: Defines the number of attempts that BiBiGrid will try to connect to the master instance via ssh. Attempts have a pause of `2^(attempts+2)` seconds in between. Default value is 4. +#### customAnsibleCfg (optional:False) +When False, changes in the resources/playbook/ansible.cfg are overwritten by the create action. +When True, changes are kept - even when you perform a git pull as the file is not tracked. The default can be found at +resources/default/ansible/ansible.cfg. + +#### customSlurmTemplate (optional:False) +When False, changes in the resources/playbook/roles/bibigrid/templates/slurm.j2 are overwritten by the create action. +When True, changes are kept - even when you perform a git pull as the file is not tracked. The default can be found at +resources/default/slurm/slurm.j2. + #### cloudScheduling (optional) This key allows you to influence cloud scheduling. Currently, only a single key `sshTimeout` can be set here. -##### sshTimeout (optional) +##### sshTimeout (optional:4) Defines the number of attempts that the master will try to connect to on demand created worker instances via ssh. Attempts have a pause of `2^(attempts+2)` seconds in between. Default value is 4. -#### autoMount (optional) +```yaml +cloudScheduling: + sshTimeout: 4 +``` + +#### autoMount (optional:False) > **Warning:** If a volume has an obscure filesystem, this might overwrite your data! If `True` all [masterMounts](#mastermounts-optional) will be automatically mounted by BiBiGrid if possible. If a volume is not formatted or has an unknown filesystem, it will be formatted to `ext4`. Default `False`. -#### masterMounts (optional) +#### masterMounts (optional:False) `masterMounts` expects a list of volumes and snapshots. Those will be attached to the master. If any snapshots are given, volumes are first created from them. Volumes are not deleted after Cluster termination. @@ -106,19 +121,19 @@ userRoles: # see ansible_hosts for all options # - file1 ``` -#### localFS (optional) +#### localFS (optional:False) In general, this key is ignored. It expects `True` or `False` and helps some specific users to create a filesystem to their liking. Default is `False`. -#### localDNSlookup (optional) +#### localDNSlookup (optional:False) If `True`, master will store DNS information for his workers. Default is `False`. [More information](https://helpdeskgeek.com/networking/edit-hosts-file/). -#### slurm +#### slurm (optional:True) If `False`, the cluster will start without the job scheduling system slurm. -This is relevant to the fewest. Default is `True`. +For nearly all cases the default value is what you need. Default is `True`. ##### SlurmConf (optional) `SlurmConf` contains variable fields in the `slurm.conf`. The most common use is to increase the `SuspendTime` @@ -145,24 +160,24 @@ slurmConf: TreeWidth: 128 # https://slurm.schedmd.com/slurm.conf.html#OPT_TreeWidth ``` -#### zabbix (optional) +#### zabbix (optional:False) If `True`, the monitoring solution [zabbix](https://www.zabbix.com/) will be installed on the master. Default is `False`. -#### nfs (optional) +#### nfs (optional:False) If `True`, [nfs](../software/nfs.md) is set up. Default is `False`. -#### ide (optional) +#### ide (optional:False) If `True`, [Theia Web IDE](../software/theia_ide.md) is installed. -After creation connection information is [printed](../features/create.md#prints-cluster-information). +After creation connection information is [printed](../features/create.md#prints-cluster-information). Default is `False`. -#### useMasterAsCompute (optional) +#### useMasterAsCompute (optional:True) If `False`, master will no longer help workers to process jobs. Default is `True`. -#### useMasterWithPublicIP (optional) +#### useMasterWithPublicIP (optional:True) If `False`, master will not be created with an attached floating ip. Default is `True`. @@ -198,7 +213,7 @@ Using gateway also automatically sets [useMasterWithPublicIp](#usemasterwithpubl `infrastructure` sets the used provider implementation for this configuration. Currently only `openstack` is available. Other infrastructures would be [AWS](https://aws.amazon.com/) and so on. -#### cloud +#### cloud (required) `cloud` decides which entry in the `clouds.yaml` is used. When using OpenStack the entry is named `openstack`. You can read more about the `clouds.yaml` [here](cloud_specification_data.md). @@ -214,12 +229,20 @@ workerInstance: image: Ubuntu 22.04 LTS (2022-10-14) count: 2 onDemand: True # optional only on master cloud for now. Default True. + partitions: # optional. Always adds "all" and the cloud identifier as partitions + - small + - onDemand + features: # optional + - hasdatabase + - holdsinformation ``` - `type` sets the instance's hardware configuration. - `image` sets the bootable operating system to be installed on the instance. - `count` sets how many workers of that `type` `image` combination are in this work group - `onDemand` defines whether nodes in the worker group are scheduled on demand (True) or are started permanently (False). Please only use if necessary. On Demand Scheduling improves resource availability for all users. This option only works on the master cloud for now. +- `partitions` allow you to force Slurm to schedule to a group of nodes (partitions) ([more](https://slurm.schedmd.com/slurm.conf.html#SECTION_PARTITION-CONFIGURATION)) +- `features` allow you to force Slurm to schedule a job only on nodes that meet certain `bool` constraints. This can be helpful when only certain nodes can access a specific resource - like a database ([more](https://slurm.schedmd.com/slurm.conf.html#OPT_Features)). ##### Find your active `images` @@ -246,25 +269,6 @@ There's also a [Fallback Option](#fallbackonotherimage-optional). openstack flavor list --os-cloud=openstack ``` -##### features (optional) -You can declare a list of features for a worker group. Those are then attached to each node in the worker group. -For example: -```yaml -workerInstance: - - type: de.NBI tiny - image: Ubuntu 22.04 LTS (2022-10-14) - count: 2 - features: - - hasdatabase - - holdsinformation -``` - -###### What's a feature? -Features allow you to force Slurm to schedule a job only on nodes that meet a certain `bool` constraint. -This can be helpful when only certain nodes can access a specific resource - like a database. - -If you would like to know more about how features exactly work, -take a look at [slurm's documentation](https://slurm.schedmd.com/slurm.conf.html#OPT_Features). #### Master or vpngtw? @@ -299,8 +303,8 @@ Exactly one in every configuration but the first: image: Ubuntu 22.04 LTS (2022-10-14) # regex allowed ``` -### fallbackOnOtherImage (optional) -If set to `true` and an image is not among the active images, +### fallbackOnOtherImage (optional:False) +If set to `True` and an image is not among the active images, BiBiGrid will try to pick a fallback image for you by finding the closest active image by name that has at least 60% name overlap. This will not find a good fallback every time. @@ -316,28 +320,6 @@ and can be helpful to when image updates occur while running a cluster. `sshUser` is the standard user of the installed images. For `Ubuntu 22.04` this would be `ubuntu`. -#### region (required) - -Every [region](https://docs.openstack.org/python-openstackclient/rocky/cli/command-objects/region.html) has its own -openstack deployment. Every [avilability zone](#availabilityzone-required) belongs to a region. - -Find your `regions`: - -```commandline -openstack region list --os-cloud=openstack -``` - -#### availabilityZone (required) - -[availability zones](https://docs.openstack.org/nova/latest/admin/availability-zones.html) allow to logically group -nodes. - -Find your `availabilityZones`: - -```commandline -openstack region list --os-cloud=openstack -``` - #### subnet (required) `subnet` is a block of ip addresses. @@ -348,13 +330,14 @@ Find available `subnets`: openstack subnet list --os-cloud=openstack ``` -#### localDNSLookup (optional) +#### localDNSLookup (optional:False) If no full DNS service for started instances is available, set `localDNSLookup: True`. Currently, the case in Berlin, DKFZ, Heidelberg and Tuebingen. #### features (optional) -You can declare a list of [features](#whats-a-feature) that are then attached to every node in the configuration. -If both [worker group](#features-optional) or [master features](#masterInstance) and configuration features are defined, -they are merged. \ No newline at end of file +You can declare a list of cloud-wide [features](#whats-a-feature) that are then attached to every node in the cloud described by the configuration. +If both [worker group](#workerinstances) or [master features](#masterInstance) and configuration features are defined, +they are merged. If you only have a single cloud and therefore a single configuration, this key is not helpful as a feature +that is present at all nodes can be omitted as it can't influence the scheduling. \ No newline at end of file From 1e26cc7df41a4b739b129ea8163889eab7e8aaae Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 6 May 2024 13:53:25 +0200 Subject: [PATCH 088/145] updated tests. Improved formatting --- bibigrid/core/utility/ansible_configurator.py | 21 +-- tests/test_ansible_configurator.py | 128 +++++++++--------- 2 files changed, 63 insertions(+), 86 deletions(-) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index c2c95219..1c84f456 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -64,6 +64,7 @@ def generate_site_file_yaml(user_roles): host_dict["vars_files"] = host_dict["vars_files"] + user_role.get("varsFiles", []) host_dict["roles"] = host_dict["roles"] + [{"role": role["name"], "tags": role.get("tags", [])} for role in user_role["roles"]] + return site_yaml @@ -280,26 +281,6 @@ def get_cidrs(configurations): return all_cidrs -def get_ansible_roles(ansible_roles, log): - """ - Checks if ansible_roles have all necessary values and returns True if so. - @param ansible_roles: ansible_roles from master configuration (first configuration) - @param log: - @return: list of valid ansible_roles - """ - ansible_roles_yaml = [] - for ansible_role in (ansible_roles or []): - if ansible_role.get("file") and ansible_role.get("hosts"): - ansible_role_dict = {"file": ansible_role["file"], "hosts": ansible_role["hosts"]} - for key in ["name", "vars", "vars_file"]: - if ansible_role.get(key): - ansible_role_dict[key] = ansible_role[key] - ansible_roles_yaml.append(ansible_role_dict) - else: - log.warning("Ansible role %s had neither galaxy,git nor url. Not added.", ansible_role) - return ansible_roles_yaml - - def get_ansible_galaxy_roles(ansible_galaxy_roles, log): """ Checks if ansible_galaxy_role have all necessary values and adds it to the return list if so. diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index 9bd92df0..ade2e245 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -29,19 +29,69 @@ def test_generate_site_file_yaml_empty(self): 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml([])) - def test_generate_site_file_yaml_role(self): - custom_roles = [{"file": "file", "hosts": "hosts", "name": "name", "vars": "vars", "vars_file": "varsFile"}] + def test_generate_site_file_yaml_master_role(self): + user_roles = [{'hosts': ['master'], 'roles': [{'name': 'resistance_nextflow', 'tags': ['rn']}]}] # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] site_yaml = [{'become': 'yes', 'hosts': 'master', - 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}, 'additional/name'], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}, + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}, + {'role': 'resistance_nextflow', 'tags': ['rn']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + {'become': 'yes', 'hosts': 'vpngtw', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + {'become': 'yes', 'hosts': 'workers', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] + self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(user_roles)) + + def test_generate_site_file_yaml_vpngtw_role(self): + user_roles = [{'hosts': ['vpngtw'], 'roles': [{'name': 'resistance_nextflow'}], 'varsFiles': ['vars/rn']}] + # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] + site_yaml = [{'become': 'yes', 'hosts': 'master', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + {'become': 'yes', 'hosts': 'vpngtw', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}, + {'role': 'resistance_nextflow', 'tags': []}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'vars/rn']}, + {'become': 'yes', 'hosts': 'workers', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] + self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(user_roles)) + + def test_generate_site_file_yaml_workers_role(self): + user_roles = [{'hosts': ['workers'], 'roles': [{'name': 'resistance_nextflow'}]}] + # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] + site_yaml = [{'become': 'yes', 'hosts': 'master', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + {'become': 'yes', 'hosts': 'vpngtw', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + {'become': 'yes', 'hosts': 'workers', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}, + {'role': 'resistance_nextflow', 'tags': []}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] + self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(user_roles)) + + def test_generate_site_file_yaml_all_role(self): + user_roles = [ + {'hosts': ['master', 'vpngtw', 'workers'], 'roles': [{'name': 'resistance_nextflow', 'tags': ['rn']}], + 'varsFiles': ['vars/rn']}] + # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] + site_yaml = [{'become': 'yes', 'hosts': 'master', + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}, + {'role': 'resistance_nextflow', 'tags': ['rn']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'vars/rn']}, {'become': 'yes', 'hosts': 'vpngtw', - 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}, 'additional/name'], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}, + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}, + {'role': 'resistance_nextflow', 'tags': ['rn']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'vars/rn']}, {'become': 'yes', 'hosts': 'workers', - 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}, 'additional/name'], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}] - self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(custom_roles)) + 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}, + {'role': 'resistance_nextflow', 'tags': ['rn']}], + 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'vars/rn']}] + self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(user_roles)) def test_generate_common_configuration_false(self): cidrs = "42" @@ -173,7 +223,6 @@ def test_generate_common_configuration_ext_nfs_shares(self): default_user, startup.LOG) common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] - print(generated_common_configuration) self.assertEqual(common_configuration_yaml, generated_common_configuration) def test_generate_common_configuration_ide(self): @@ -202,32 +251,6 @@ def test_generate_common_configuration_ide(self): common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] self.assertEqual(common_configuration_yaml, generated_common_configuration) - def test_generate_common_configuration_ansible_roles_mock(self): - cidrs = "42" - ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] - cluster_id = "21" - default_user = "ubuntu" - ssh_user = "test" - configuration = [{"ansibleRoles": ansible_roles}] - generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, - cluster_id, ssh_user, - default_user, - startup.LOG) - self.assertEqual(ansible_roles, generated_common_configuration["ansible_roles"]) - - def test_generate_common_configuration_ansible_galaxy_roles(self): - cidrs = "42" - cluster_id = "21" - default_user = "ubuntu" - ssh_user = "test" - galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file"]}] - configuration = [{"ansibleGalaxyRoles": galaxy_roles}] - generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, - cluster_id, ssh_user, - default_user, - startup.LOG) - self.assertEqual(galaxy_roles, generated_common_configuration["ansible_galaxy_roles"]) - @patch("bibigrid.core.utility.ansible_configurator.to_instance_host_dict") def test_generate_ansible_hosts(self, mock_instance_host_dict): cluster_id = "21" @@ -273,30 +296,6 @@ def test_get_cidrs(self): expected = [{'cloud_identifier': 13, 'provider_cidrs': [21]}] self.assertEqual(expected, ansible_configurator.get_cidrs(configuration)) - def test_get_ansible_roles_empty(self): - self.assertEqual([], ansible_configurator.get_ansible_roles([], startup.LOG)) - - def test_get_ansible_roles(self): - ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] - self.assertEqual(ansible_roles, ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) - - def test_get_ansible_roles_add(self): - ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] - ansible_roles_add = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file", "additional"]}] - self.assertEqual(ansible_roles, ansible_configurator.get_ansible_roles(ansible_roles_add, startup.LOG)) - - def test_get_ansible_roles_minus(self): - ansible_roles = [{elem: elem for elem in ["file", "hosts"]}] - self.assertEqual(ansible_roles, ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) - - def test_get_ansible_roles_mismatch_hosts(self): - ansible_roles = [{"file": "file"}] - self.assertEqual([], ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) - - def test_get_ansible_roles_mismatch_file(self): - ansible_roles = [{"hosts": "hosts"}] - self.assertEqual([], ansible_configurator.get_ansible_roles(ansible_roles, startup.LOG)) - def test_get_ansible_galaxy_roles_empty(self): self.assertEqual([], ansible_configurator.get_ansible_galaxy_roles([], startup.LOG)) @@ -349,26 +348,23 @@ def test_write_yaml_alias(self, mock_yaml): @patch("bibigrid.core.utility.ansible_configurator.generate_common_configuration_yaml") @patch("bibigrid.core.actions.list_clusters.dict_clusters") @patch("bibigrid.core.utility.ansible_configurator.generate_ansible_hosts_yaml") - @patch("bibigrid.core.utility.ansible_configurator.get_ansible_roles") @patch("bibigrid.core.utility.ansible_configurator.generate_site_file_yaml") @patch("bibigrid.core.utility.ansible_configurator.write_yaml") @patch("bibigrid.core.utility.ansible_configurator.get_cidrs") - def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_roles, mock_hosts, mock_list, + def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_hosts, mock_list, mock_common, mock_worker, mock_write): mock_cidrs.return_value = 421 mock_list.return_value = {2: 422} - mock_roles.return_value = 423 provider = MagicMock() provider.cloud_specification = {"auth": {"username": "Default"}} - configuration = [{"sshUser": 42, "ansibleRoles": 21}] + configuration = [{"sshUser": 42, "userRoles": 21}] cluster_id = 2 ansible_configurator.configure_ansible_yaml([provider], configuration, cluster_id, startup.LOG) mock_worker.assert_called_with(configuration, startup.LOG) mock_common.assert_called_with(cidrs=421, configurations=configuration, cluster_id=cluster_id, ssh_user=42, default_user="Default", log=startup.LOG) mock_hosts.assert_called_with(42, configuration, cluster_id, startup.LOG) - mock_site.assert_called_with(423) - mock_roles.assert_called_with(21, startup.LOG) + mock_site.assert_called_with(21) mock_cidrs.assert_called_with(configuration) mock_write.assert_called() expected = [call(aRP.WORKER_SPECIFICATION_FILE, mock_worker(), startup.LOG, False), From 17e89d57d10f79e5ab0d2a34c3942b5b6c9f13a8 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 16 May 2024 16:13:23 +0200 Subject: [PATCH 089/145] fix for service being too fast for startup --- bibigrid/core/actions/create.py | 14 ++++++++++---- bibigrid/core/utility/ansible_commands.py | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index b6a8d66f..9b756bf6 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -270,7 +270,13 @@ def initialize_instances(self): "gateway": configuration.get("gateway", {}), "timeout": self.ssh_timeout} if configuration.get("masterInstance"): self.master_ip = configuration["floating_ip"] - ssh_data["commands"] = self.ssh_add_public_key_commands + ssh_handler.ANSIBLE_SETUP + wait_for_service_command, wait_for_service_message = ssh_handler.a_c.WAIT_FOR_SERVICES + wait_for_services_commands = [ + (wait_for_service_command.format(service=service), wait_for_service_message.format(service=service)) + for service in configuration.get("waitForServices", [])] + print(wait_for_services_commands) + ssh_data["commands"] = ( + wait_for_services_commands + self.ssh_add_public_key_commands + ssh_handler.ANSIBLE_SETUP) ssh_data["filepaths"] = [(ssh_data["private_key"], ssh_handler.PRIVATE_KEY_FILE)] ssh_handler.execute_ssh(ssh_data, self.log) elif configuration.get("vpnInstance"): @@ -352,9 +358,9 @@ def upload_data(self): self.log.debug(f"Starting playbook with {ansible_start}.") commands = [ssh_handler.get_ac_command(self.providers, AC_NAME.format( cluster_id=self.cluster_id))] + ssh_handler.ANSIBLE_START - ssh_data = {"floating_ip": self.master_ip, "private_key": KEY_FOLDER + self.key_name, - "username": self.ssh_user, "commands": commands, "filepaths": FILEPATHS, - "gateway": self.configurations[0].get("gateway", {}), "timeout": self.ssh_timeout} + ssh_data = {"floating_ip": self.master_ip, "private_key": KEY_FOLDER + self.key_name, "username": self.ssh_user, + "commands": commands, "filepaths": FILEPATHS, "gateway": self.configurations[0].get("gateway", {}), + "timeout": self.ssh_timeout} ssh_handler.execute_ssh(ssh_data=ssh_data, log=self.log) def start_start_server_threads(self): diff --git a/bibigrid/core/utility/ansible_commands.py b/bibigrid/core/utility/ansible_commands.py index 460e5346..84b68f72 100644 --- a/bibigrid/core/utility/ansible_commands.py +++ b/bibigrid/core/utility/ansible_commands.py @@ -54,6 +54,9 @@ "Execute ansible playbook. Be patient.") # ansible setup +WAIT_FOR_SERVICES = ( + "while [[ $(systemctl is-active {service}) == 'active' ]]; do echo 'Waiting for service {service}'; sleep 2; done", + "Waiting for service {service}.") UPDATE = ("sudo apt-get update", "Update apt repository lists.") PYTHON3_PIP = "sudo apt-get install -y python3-pip", "Install python3 pip using apt." ANSIBLE_PASSLIB = ("sudo pip install ansible==6.6 passlib", "Install Ansible and Passlib using pip.") From 3fd1011d8e5b4573f53864f59f5c4cccb283798f Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 24 May 2024 11:38:00 +0200 Subject: [PATCH 090/145] fixed remote src --- resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yml b/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yml index 3a1ef202..e193ccc6 100644 --- a/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yml +++ b/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yml @@ -16,6 +16,7 @@ owner: root group: root mode: 0644 + remote_src: true with_items: "{{ collected_files.files }}" - name: Remove collected files file: From 70ceb6586c8f62b0da4b44ea3e03482e8787b665 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:14:26 +0200 Subject: [PATCH 091/145] changed RESUME to POWER_DOWN and removed delete call which is now handled via Slurm that calls terminate.sh (#503) --- .../playbook/roles/bibigrid/files/slurm/fail.sh | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/resources/playbook/roles/bibigrid/files/slurm/fail.sh b/resources/playbook/roles/bibigrid/files/slurm/fail.sh index 38d723b4..af7904e9 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/fail.sh +++ b/resources/playbook/roles/bibigrid/files/slurm/fail.sh @@ -21,6 +21,7 @@ process_string() { } mkdir -p worker_logs +mkdir -p worker_logs/fail mkdir -p worker_logs/fail/out mkdir -p worker_logs/fail/err @@ -34,16 +35,13 @@ function log { log "Fail-Script started" -# $1 is in slurm node format for example: bibigrid-worker0-cid-[0-1],bibigrid-worker1-cid-0 and needs no converting -scontrol update NodeName="$1" state=RESUME reason=FailedStartup # no sudo needed cause executed by slurm user - hosts=$(scontrol show hostnames "$1") -echo "Hosts $hosts used" +log "Hosts $hosts used" -# delete servers -python3 /usr/local/bin/delete_server.py "${hosts}" +# $1 is in slurm node format for example: bibigrid-worker0-cid-[0-1],bibigrid-worker1-cid-0 and needs no converting +scontrol update NodeName="$1" state=POWER_DOWN reason=FailedStartup # no sudo needed cause executed by slurm user -echo "Finished delete_server.py execution." +log "Nodes $1 set to POWER_DOWN." exit $? From 9ad7cab1b545f11e9f5795a1bc2bd1c281881bad Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:24:55 +0200 Subject: [PATCH 092/145] Update check (#499) * updated validate_configuration.py in order to provide schema validation. Moved cloud_identifier setting even closer to program start in order to be able to log better when performing other actions than create. * small log change and fix of schema key vpnInstance * updated tests * removed no longer relevant test * added schema validation tests --- bibigrid/core/actions/create.py | 1 - .../utility/handler/configuration_handler.py | 4 +- .../core/utility/validate_configuration.py | 10 +- bibigrid/core/utility/validate_schema.py | 60 ++++++++++ .../markdown/features/configuration.md | 22 ++-- requirements.txt | 3 +- .../tests/schema/error_master_missing.yml | 82 ++++++++++++++ resources/tests/schema/hybrid.yml | 103 ++++++++++++++++++ resources/tests/schema/single.yml | 85 +++++++++++++++ tests/test_ansible_configurator.py | 51 +-------- tests/test_configuration_handler.py | 8 +- tests/test_validate_schema.py | 29 +++++ 12 files changed, 387 insertions(+), 71 deletions(-) create mode 100644 bibigrid/core/utility/validate_schema.py create mode 100644 resources/tests/schema/error_master_missing.yml create mode 100644 resources/tests/schema/hybrid.yml create mode 100644 resources/tests/schema/single.yml create mode 100644 tests/test_validate_schema.py diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 9b756bf6..42cb316e 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -317,7 +317,6 @@ def prepare_configurations(self): @return: """ for configuration, provider in zip(self.configurations, self.providers): - configuration["cloud_identifier"] = provider.cloud_specification["identifier"] if not configuration.get("network"): self.log.debug("No network found. Getting network by subnet.") configuration["network"] = provider.get_network_id_by_subnet(configuration["subnet"]) diff --git a/bibigrid/core/utility/handler/configuration_handler.py b/bibigrid/core/utility/handler/configuration_handler.py index 8746a76f..20781458 100644 --- a/bibigrid/core/utility/handler/configuration_handler.py +++ b/bibigrid/core/utility/handler/configuration_handler.py @@ -149,5 +149,7 @@ def get_cloud_specifications(configurations, log): for configuration in configurations: cloud = configuration.get(CLOUD_CONFIGURATION_KEY) if cloud: - cloud_specifications.append(get_cloud_specification(cloud, clouds, clouds_public, log)) # might be None + cloud_specification = get_cloud_specification(cloud, clouds, clouds_public, log) + cloud_specifications.append(cloud_specification) # might be None if not found + configuration["cloud_identifier"] = cloud_specification["identifier"] return cloud_specifications diff --git a/bibigrid/core/utility/validate_configuration.py b/bibigrid/core/utility/validate_configuration.py index 256858b5..dd3c81c4 100644 --- a/bibigrid/core/utility/validate_configuration.py +++ b/bibigrid/core/utility/validate_configuration.py @@ -4,6 +4,7 @@ import os +from bibigrid.core.utility import validate_schema from bibigrid.core.utility import image_selection from bibigrid.core.utility.handler import configuration_handler from bibigrid.models.exceptions import ImageNotActiveException @@ -201,13 +202,8 @@ def validate(self): @return: """ success = bool(self.providers) - self.log.info("Validating config file...") - # success = check_provider_data( - # configuration_handler.get_list_by_key(self.configurations, "cloud"), - # len(self.configurations)) and success - # if not success: - # LOG.warning("Providers not set correctly in configuration file. Check log for more detail.") - # return success + success = validate_schema.validate_configurations(self.configurations, self.log) and success + checks = [("master/vpn", self.check_master_vpn_worker), ("servergroup", self.check_server_group), ("instances", self.check_instances), ("volumes", self.check_volumes), ("network", self.check_network), ("quotas", self.check_quotas), ("sshPublicKeyFiles", self.check_ssh_public_key_files), diff --git a/bibigrid/core/utility/validate_schema.py b/bibigrid/core/utility/validate_schema.py new file mode 100644 index 00000000..771c7c5c --- /dev/null +++ b/bibigrid/core/utility/validate_schema.py @@ -0,0 +1,60 @@ +""" +Handles the schema validation for BiBiGrid's configuration yml. +""" + +from schema import Schema, Optional, Or, SchemaError + +# Define the schema for the configuration file +master_schema = Schema( + {'infrastructure': str, 'cloud': str, 'sshUser': str, Or('subnet', 'network'): str, 'cloud_identifier': str, + Optional('sshPublicKeyFiles'): [str], Optional('sshTimeout'): int, + Optional('cloudScheduling'): {Optional('sshTimeout'): int}, Optional('autoMount'): bool, + Optional('masterMounts'): [str], Optional('nfsShares'): [str], + Optional('userRoles'): [{'hosts': [str], 'roles': [{'name': str, Optional('tags'): [str]}]}], + Optional('localFS'): bool, Optional('localDNSlookup'): bool, Optional('slurm'): bool, + Optional('slurmConf'): {Optional('db'): str, Optional('db_user'): str, Optional('db_password'): str, + Optional('munge_key'): str, Optional('elastic_scheduling'): {Optional('SuspendTime'): int, + Optional( + 'ResumeTimeout'): int, + Optional('TreeWidth'): int}}, + Optional('zabbix'): bool, Optional('nfs'): bool, Optional('ide'): bool, Optional('useMasterAsCompute'): bool, + Optional('useMasterWithPublicIp'): bool, Optional('waitForServices'): [str], + Optional('gateway'): {'ip': str, 'portFunction': str}, Optional('fallbackOnOtherImage'): bool, + Optional('localDNSLookup'): bool, Optional('features'): [str], 'workerInstances': [ + {'type': str, 'image': str, Optional('count'): int, Optional('onDemand'): bool, Optional('partitions'): [str], + Optional('features'): [str]}], + 'masterInstance': {'type': str, 'image': str, Optional('partitions'): [str], Optional('features'): [str]}, + Optional('vpngtw'): {'type': str, 'image': str}}) + +other_schema = Schema( + {'infrastructure': str, 'cloud': str, 'sshUser': str, Or('subnet', 'network'): str, 'cloud_identifier': str, + Optional('waitForServices'): [str], Optional('features'): [str], 'workerInstances': [ + {'type': str, 'image': str, Optional('count'): int, Optional('onDemand'): bool, Optional('partitions'): [str], + Optional('features'): [str]}], 'vpnInstance': {'type': str, 'image': str}}) + + +def validate_configurations(configurations, log): + log.info("Validating config file schema...") + configuration = None + try: + configuration = configurations[0] + if configuration.get("region") or configuration.get("availabilityZone"): + log.warning( + "Keys 'region' and 'availabilityZone' are deprecated! Check will return False if you use one of them." + "Just remove them. They are no longer required.") + master_schema.validate(configuration) + log.debug(f"Master configuration '{configuration['cloud_identifier']}' valid.") + for configuration in configurations[1:]: + if configuration.get("region") or configuration.get("availabilityZone"): + log.warning( + "Keys region and availabilityZone are deprecated! Check will return False if you use one of them." + "Just remove them. They are no longer required.") + other_schema.validate(configuration) + log.debug(f"Configuration '{configuration['cloud_identifier']}' schema valid.") + log.debug("Entire configuration schema valid.") + return True + except SchemaError as err: + log.warning( + f"Configuration '{configuration.get('cloud_identifier', 'No identifier found')}' invalid. See error: " + f"{err}.") + return False diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 006fded0..3983b4fa 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -101,7 +101,9 @@ userRoles: # see ansible_hosts for all options - hosts: - "master" roles: # roles placed in resources/playbook/roles_user - - name: "resistance_nextflow" + - name: "resistance_nextflow" + tags: + - resistance_nextflow # varsFiles: # (optional) # - file1 ``` @@ -120,8 +122,8 @@ If `True`, master will store DNS information for his workers. Default is `False` If `False`, the cluster will start without the job scheduling system slurm. This is relevant to the fewest. Default is `True`. -##### SlurmConf (optional) -`SlurmConf` contains variable fields in the `slurm.conf`. The most common use is to increase the `SuspendTime` +##### slurmConf (optional) +`slurmConf` contains variable fields in the `slurm.conf`. The most common use is to increase the `SuspendTime` and the `ResumeTimeout` like: ```yaml @@ -166,13 +168,6 @@ If `False`, master will no longer help workers to process jobs. Default is `True If `False`, master will not be created with an attached floating ip. Default is `True`. -#### waitForServices (optional): - -Expects a list of services to wait for. -This is required if your provider has any post-launch services interfering with the package manager. If not set, -seemingly random errors can occur when the service interrupts ansible's execution. Services are -listed on [de.NBI Wiki](https://cloud.denbi.de/wiki/) at `Computer Center Specific` (not yet). - #### gateway (optional) In order to save valuable floating ips, BiBiGrid can also make use of a gateway to create the cluster. For more information on how to set up a gateway, how gateways work and why they save floating ips please continue reading [here](https://cloud.denbi.de/wiki/Tutorials/SaveFloatingIPs/). @@ -193,6 +188,13 @@ Using gateway also automatically sets [useMasterWithPublicIp](#usemasterwithpubl ### Local +#### waitForServices (optional): + +Expects a list of services to wait for. +This is required if your provider has any post-launch services interfering with the package manager. If not set, +seemingly random errors can occur when the service interrupts ansible's execution. Services are +listed on [de.NBI Wiki](https://cloud.denbi.de/wiki/) at `Computer Center Specific` (not yet). + #### infrastructure (required) `infrastructure` sets the used provider implementation for this configuration. Currently only `openstack` is available. diff --git a/requirements.txt b/requirements.txt index c4ddcc7f..f5c73d10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,5 @@ uvicorn~=0.23.2 fastapi~=0.101.0 pydantic~=2.1.1 keystoneauth1~=5.1.0 -filelock~=3.13.1 \ No newline at end of file +filelock~=3.13.1 +schema~=0.7.7 \ No newline at end of file diff --git a/resources/tests/schema/error_master_missing.yml b/resources/tests/schema/error_master_missing.yml new file mode 100644 index 00000000..4c1495bd --- /dev/null +++ b/resources/tests/schema/error_master_missing.yml @@ -0,0 +1,82 @@ + # See https://cloud.denbi.de/wiki/Tutorials/BiBiGrid/ (after update) + # First configuration will be used for general cluster information and must include the master. + # All other configurations mustn't include another master, but exactly one vpnWorker instead (keys like master). + +- infrastructure: openstack # former mode. + cloud: some_cloud #credentials # name of clouds.yaml entry + + sshTimeout: 6 + + # customAnsibleCfg: True # If True, changes in ansible.cfg are kept. Default False. + # customSlurmTemplate: True # If True, changes in slurm.j2 are kept. Default False. + + cloudScheduling: + sshTimeout: 42 + # -- BEGIN: GENERAL CLUSTER INFORMATION -- + # deleteTmpKeypairAfter: True + # dontUploadCredentials: True + ## sshPublicKeyFiles listed here will be added to access the cluster. A temporary key is created by bibigrid itself. + # - [key one] + ## Volumes and snapshots that will be mounted to master + #autoMount: True + #masterMounts: + # - test + + #nfsShares: + # - test2 + + ## Ansible (Galaxy) roles can be added for execution + #userRoles: + # - hosts: + # - "master" + # roles: + # - name: "resistance_nextflow" + + ## Uncomment if you don't want assign a public ip to the master; for internal cluster (Tuebingen). + # useMasterWithPublicIp: False + + # Other keys + #localFS: False + #localDNSlookup: False + zabbix: True + nfs: True + ide: True + + useMasterAsCompute: True + waitForServices: + - some.service + + fallbackOnOtherImage: True + + + # master configuration + # -- END: GENERAL CLUSTER INFORMATION -- + + workerInstances: + - type: de.NBI small + ephemeral + image: ^Ubuntu 22\.04 LTS \(.*\)$ + count: 2 + #partitions: + # - ephemeral + - type: de.NBI small + image: ^Ubuntu 22\.04 LTS \(.*\)$ + count: 1 + #onDemand: False + # worker configuration + + # Depends on cloud image + sshUser: ubuntu + + # Depends on cloud site and project + # network: bibiserv-external + # network: bibiserv_test2_network + subnet: subnet + #gateway: + # ip: 129.70.51.103 + # portFunction: "30000 + oct4" + + # Uncomment if no full DNS service for started instances is available. + # Currently the case in Berlin, DKFZ, Heidelberg and Tuebingen. + #localDNSLookup: yes + + #- [next configurations] diff --git a/resources/tests/schema/hybrid.yml b/resources/tests/schema/hybrid.yml new file mode 100644 index 00000000..fa13cc4a --- /dev/null +++ b/resources/tests/schema/hybrid.yml @@ -0,0 +1,103 @@ + # See https://cloud.denbi.de/wiki/Tutorials/BiBiGrid/ (after update) + # First configuration will be used for general cluster information and must include the master. + # All other configurations mustn't include another master, but exactly one vpnWorker instead (keys like master). + +- infrastructure: openstack # former mode. + cloud: somecloud # name of clouds.yaml entry + + # -- BEGIN: GENERAL CLUSTER INFORMATION -- + ## sshPublicKeyFiles listed here will be added to access the cluster. A temporary key is created by bibigrid itself. + #sshPublicKeyFiles: + # - [key one] + sshPublicKeyFiles: + - some_key + ## Volumes and snapshots that will be mounted to master + #masterMounts: + # - [mount one] + #masterMounts: + # - test + + #nfsShares: + # - test + + ## Uncomment if you don't want assign a public ip to the master; for internal cluster (Tuebingen). + useMasterWithPublicIp: no + + # Other keys + #localFS: False + #localDNSlookup: False + slurm: True + zabbix: True + nfs: True + ide: True + + waitForServices: + - some.service + + useMasterAsCompute: False + + + # master configuration + masterInstance: + type: de.NBI mini + image: ^Ubuntu 22\.04 LTS \(.*\)$ + + # -- END: GENERAL CLUSTER INFORMATION -- + + # worker configuration + workerInstances: + - type: de.NBI tiny + image: ^Ubuntu 22\.04 LTS \(.*\)$ + count: 2 + onDemand: False + - type: de.NBI default + image: ^Ubuntu 22\.04 LTS \(.*\)$ + + # Depends on cloud image + sshUser: ubuntu + + # Depends on cloud site: + # Berlin : regionOne + # Bielefeld : bielefeld + # DKFZ : regionOne + # Giessen : RegionOne + # Heidelberg : RegionOne + # Tuebingen : RegionOne + + # Depends on cloud site and project + subnet: subnet + + # Uncomment if no full DNS service for started instances is available. + # Currently the case in Berlin, DKFZ, Heidelberg and Tuebingen. + #localDNSLookup: yes + +- infrastructure: openstack # former mode. + cloud: another_cloud # name of clouds.yaml entry + + # master configuration + vpnInstance: + type: de.NBI mini + image: ^Ubuntu 22\.04 LTS \(.*\)$ + + # -- END: GENERAL CLUSTER INFORMATION -- + + # worker configuration + workerInstances: + - type: de.NBI tiny + image: ^Ubuntu 22\.04 LTS \(.*\)$ + count: 2 + features: + - holdsinformation + # Depends on cloud image + sshUser: ubuntu + + # Depends on cloud site and project + subnet: another_subnet + + # Uncomment if no full DNS service for started instances is available. + # Currently the case in Berlin, DKFZ, Heidelberg and Tuebingen. + #localDNSLookup: yes + + features: + - hasdatabase + #- [next configurations] diff --git a/resources/tests/schema/single.yml b/resources/tests/schema/single.yml new file mode 100644 index 00000000..34e42b7e --- /dev/null +++ b/resources/tests/schema/single.yml @@ -0,0 +1,85 @@ + # See https://cloud.denbi.de/wiki/Tutorials/BiBiGrid/ (after update) + # First configuration will be used for general cluster information and must include the master. + # All other configurations mustn't include another master, but exactly one vpnWorker instead (keys like master). + +- infrastructure: openstack # former mode. + cloud: some_cloud #credentials # name of clouds.yaml entry + + sshTimeout: 6 + + # customAnsibleCfg: True # If True, changes in ansible.cfg are kept. Default False. + # customSlurmTemplate: True # If True, changes in slurm.j2 are kept. Default False. + + cloudScheduling: + sshTimeout: 42 + # -- BEGIN: GENERAL CLUSTER INFORMATION -- + # deleteTmpKeypairAfter: True + # dontUploadCredentials: True + ## sshPublicKeyFiles listed here will be added to access the cluster. A temporary key is created by bibigrid itself. + # - [key one] + ## Volumes and snapshots that will be mounted to master + #autoMount: True + #masterMounts: + # - test + + #nfsShares: + # - test2 + + ## Ansible (Galaxy) roles can be added for execution + #userRoles: + # - hosts: + # - "master" + # roles: + # - name: "resistance_nextflow" + + ## Uncomment if you don't want assign a public ip to the master; for internal cluster (Tuebingen). + # useMasterWithPublicIp: False + + # Other keys + #localFS: False + #localDNSlookup: False + zabbix: True + nfs: True + ide: True + + useMasterAsCompute: True + waitForServices: + - some.service + + fallbackOnOtherImage: True + + + # master configuration + masterInstance: + type: de.NBI small + image: ^Ubuntu 22\.04 LTS \(.*\)$ + # -- END: GENERAL CLUSTER INFORMATION -- + + workerInstances: + - type: de.NBI small + ephemeral + image: ^Ubuntu 22\.04 LTS \(.*\)$ + count: 2 + #partitions: + # - ephemeral + - type: de.NBI small + image: ^Ubuntu 22\.04 LTS \(.*\)$ + count: 1 + #onDemand: False + # worker configuration + + # Depends on cloud image + sshUser: ubuntu + + # Depends on cloud site and project + # network: bibiserv-external + # network: bibiserv_test2_network + subnet: subnet + #gateway: + # ip: 129.70.51.103 + # portFunction: "30000 + oct4" + + # Uncomment if no full DNS service for started instances is available. + # Currently the case in Berlin, DKFZ, Heidelberg and Tuebingen. + #localDNSLookup: yes + + #- [next configurations] diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index 9bd92df0..80ad7d92 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -29,20 +29,6 @@ def test_generate_site_file_yaml_empty(self): 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml([])) - def test_generate_site_file_yaml_role(self): - custom_roles = [{"file": "file", "hosts": "hosts", "name": "name", "vars": "vars", "vars_file": "varsFile"}] - # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] - site_yaml = [{'become': 'yes', 'hosts': 'master', - 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}, 'additional/name'], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}, - {'become': 'yes', 'hosts': 'vpngtw', - 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}, 'additional/name'], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}, - {'become': 'yes', 'hosts': 'workers', - 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}, 'additional/name'], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'varsFile']}] - self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(custom_roles)) - def test_generate_common_configuration_false(self): cidrs = "42" cluster_id = "21" @@ -202,32 +188,6 @@ def test_generate_common_configuration_ide(self): common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] self.assertEqual(common_configuration_yaml, generated_common_configuration) - def test_generate_common_configuration_ansible_roles_mock(self): - cidrs = "42" - ansible_roles = [{elem: elem for elem in ["file", "hosts", "name", "vars", "vars_file"]}] - cluster_id = "21" - default_user = "ubuntu" - ssh_user = "test" - configuration = [{"ansibleRoles": ansible_roles}] - generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, - cluster_id, ssh_user, - default_user, - startup.LOG) - self.assertEqual(ansible_roles, generated_common_configuration["ansible_roles"]) - - def test_generate_common_configuration_ansible_galaxy_roles(self): - cidrs = "42" - cluster_id = "21" - default_user = "ubuntu" - ssh_user = "test" - galaxy_roles = [{elem: elem for elem in ["hosts", "name", "galaxy", "git", "url", "vars", "vars_file"]}] - configuration = [{"ansibleGalaxyRoles": galaxy_roles}] - generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, - cluster_id, ssh_user, - default_user, - startup.LOG) - self.assertEqual(galaxy_roles, generated_common_configuration["ansible_galaxy_roles"]) - @patch("bibigrid.core.utility.ansible_configurator.to_instance_host_dict") def test_generate_ansible_hosts(self, mock_instance_host_dict): cluster_id = "21" @@ -349,26 +309,23 @@ def test_write_yaml_alias(self, mock_yaml): @patch("bibigrid.core.utility.ansible_configurator.generate_common_configuration_yaml") @patch("bibigrid.core.actions.list_clusters.dict_clusters") @patch("bibigrid.core.utility.ansible_configurator.generate_ansible_hosts_yaml") - @patch("bibigrid.core.utility.ansible_configurator.get_ansible_roles") @patch("bibigrid.core.utility.ansible_configurator.generate_site_file_yaml") @patch("bibigrid.core.utility.ansible_configurator.write_yaml") @patch("bibigrid.core.utility.ansible_configurator.get_cidrs") - def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_roles, mock_hosts, mock_list, - mock_common, mock_worker, mock_write): + def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_hosts, mock_list, mock_common, + mock_worker, mock_write): mock_cidrs.return_value = 421 mock_list.return_value = {2: 422} - mock_roles.return_value = 423 provider = MagicMock() provider.cloud_specification = {"auth": {"username": "Default"}} - configuration = [{"sshUser": 42, "ansibleRoles": 21}] + configuration = [{"sshUser": 42, "userRoles": 21}] cluster_id = 2 ansible_configurator.configure_ansible_yaml([provider], configuration, cluster_id, startup.LOG) mock_worker.assert_called_with(configuration, startup.LOG) mock_common.assert_called_with(cidrs=421, configurations=configuration, cluster_id=cluster_id, ssh_user=42, default_user="Default", log=startup.LOG) mock_hosts.assert_called_with(42, configuration, cluster_id, startup.LOG) - mock_site.assert_called_with(423) - mock_roles.assert_called_with(21, startup.LOG) + mock_site.assert_called_with(21) mock_cidrs.assert_called_with(configuration) mock_write.assert_called() expected = [call(aRP.WORKER_SPECIFICATION_FILE, mock_worker(), startup.LOG, False), diff --git a/tests/test_configuration_handler.py b/tests/test_configuration_handler.py index d442a650..ceed2840 100644 --- a/tests/test_configuration_handler.py +++ b/tests/test_configuration_handler.py @@ -158,11 +158,11 @@ def test_get_cloud_specifications_no_cloud_configuration_key(self, mock_get_clou @patch("bibigrid.core.utility.handler.configuration_handler.get_clouds_files") def test_get_cloud_specifications_cloud(self, mock_get_clouds_files, mock_get_clouds_specification): mock_get_clouds_files.return_value = {"1": "1"}, {"2": "2"} - mock_get_clouds_specification.return_value = 21 - expected_result = [21] - result = configuration_handler.get_cloud_specifications([{"cloud": 42}], startup.LOG) + mock_get_clouds_specification.return_value = {"identifier": 21} + expected_result = [{'identifier': 21}] + result = configuration_handler.get_cloud_specifications([{"cloud": 1}], startup.LOG) self.assertEqual(expected_result, result) - mock_get_clouds_specification.assert_called_with(42, {"1": "1"}, {"2": "2"}, startup.LOG) + mock_get_clouds_specification.assert_called_with(1, {"1": "1"}, {"2": "2"}, startup.LOG) mock_get_clouds_files.assert_called() @patch("bibigrid.core.utility.handler.configuration_handler.get_cloud_specification") diff --git a/tests/test_validate_schema.py b/tests/test_validate_schema.py new file mode 100644 index 00000000..df8e94a1 --- /dev/null +++ b/tests/test_validate_schema.py @@ -0,0 +1,29 @@ +""" +Tests for validate schema +""" + +import glob +import os +from unittest import TestCase + +import yaml + +from bibigrid.core import startup +from bibigrid.core.utility.validate_schema import validate_configurations + +TEST_CONFIGURATION_DIRECTORY = "../resources/tests/schema" + + +class TestValidateSchema(TestCase): + """ + Validate Schema + """ + + def test_validate_configurations(self): + for bibigrid_configuration_name in glob.iglob(f'{TEST_CONFIGURATION_DIRECTORY}/*.yml'): + with open(bibigrid_configuration_name, mode="r", encoding="utf-8") as config_file: + config_yaml = yaml.safe_load(config_file) + for cloud_config in config_yaml: + cloud_config["cloud_identifier"] = "some" + result = validate_configurations(config_yaml, startup.LOG) + self.assertEqual(not os.path.basename(bibigrid_configuration_name).startswith("error"), result) From 23fe109e2023afb72716e1b0cb329b95759dc383 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 4 Jun 2024 14:21:13 +0200 Subject: [PATCH 093/145] fixed ftype. Errors with multiple volumes. --- .../tasks/020-disk-server-automount.yml | 32 +++++++++++++++++++ .../roles/bibigrid/tasks/020-disk-server.yml | 32 +++---------------- 2 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml new file mode 100644 index 00000000..b803d003 --- /dev/null +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml @@ -0,0 +1,32 @@ +- block: + - name: Make sure disks are available + failed_when: false + filesystem: + fstype: ext4 + dev: "{{ item.device }}" + force: false + state: present + + - name: Get the filesystem type of the device using lsblk + command: "lsblk -no FSTYPE {{ item.device }}" + register: filesystem_type + changed_when: false + + - name: Log the filesystem type + debug: + msg: "Filesystem type is {{ filesystem_type.stdout }}" + + - name: Create mount folders if they don't exist + file: + path: "/vol/{{ item.name }}" + state: directory + mode: '0755' + owner: root + group: '{{ ansible_distribution | lower }}' + + - name: Mount disks + mount: + path: "/vol/{{ item.name }}" + src: "{{ item.device }}" + state: mounted + fstype: "{{ filesystem_type.stdout }}" diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml index de4f0049..f6dfbf85 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml @@ -17,31 +17,7 @@ - "{{ master.disks }}" when: master.disks is defined -- when: volumes is defined and auto_mount - block: - - name: Make sure disks are available - failed_when: false - filesystem: - fstype: ext4 - dev: "{{ item.device }}" - force: false - state: present - with_items: "{{ volumes }}" - - - name: Create mount folders if they don't exist - failed_when: false - file: - path: "/{{ item.name }}" - state: directory - mode: '0755' - owner: root - group: '{{ ansible_distribution | lower }}' - with_items: "{{ volumes }}" - - - name: Mount disks - failed_when: false - mount: - path: "{{ item.name }}" - src: "{{ item.device }}" - state: mounted - with_items: "{{ volumes }}" +- name: Automount + when: volumes is defined and auto_mount + include_tasks: 020-disk-server-automount.yml + with_items: "{{ volumes }}" \ No newline at end of file From 2366b01ee3674e6bd775bb3a8bcac3fd8f859e98 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 4 Jun 2024 15:16:22 +0200 Subject: [PATCH 094/145] made automount bound to defined mountPoints and therefore customizable --- bibigrid/core/actions/create.py | 8 ++++++-- bibigrid/core/utility/ansible_configurator.py | 2 +- .../roles/bibigrid/tasks/020-disk-server-automount.yml | 3 ++- .../playbook/roles/bibigrid/tasks/020-disk-server.yml | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 0a21abe9..e4dfe9e8 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -219,6 +219,10 @@ def start_vpn_or_master(self, configuration, provider): elif identifier == MASTER_IDENTIFIER: configuration["floating_ip"] = server["private_v4"] # pylint: enable=comparison-with-callable configuration["volumes"] = provider.get_mount_info_from_server(server) + for volume in configuration["volumes"]: + mount = next((mount for mount in configuration["masterMounts"] if mount["name"] == volume["name"]), None) + if mount.get("mountPoint"): + volume["mount_point"] = mount["mountPoint"] def start_workers(self, worker, worker_count, configuration, provider): name = WORKER_IDENTIFIER(cluster_id=self.cluster_id, additional=worker_count) @@ -252,8 +256,8 @@ def prepare_vpn_or_master_args(self, configuration, provider): if configuration.get("masterInstance"): instance_type = configuration["masterInstance"] identifier = MASTER_IDENTIFIER - master_mounts = configuration.get("masterMounts", []) - volumes = self.prepare_volumes(provider, master_mounts) + master_mounts_src = [master_mount["name"] for master_mount in configuration.get("masterMounts", [])] + volumes = self.prepare_volumes(provider, master_mounts_src) elif configuration.get("vpnInstance"): instance_type = configuration["vpnInstance"] identifier = VPN_WORKER_IDENTIFIER diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 1c84f456..0b470322 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -175,7 +175,7 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us master_configuration = configurations[0] log.info("Generating common configuration file...") common_configuration_yaml = {"bibigrid_version": __version__, - "auto_mount": master_configuration.get("autoMount", False), "cluster_id": cluster_id, + "cluster_id": cluster_id, "cluster_cidrs": cidrs, "default_user": default_user, "local_fs": master_configuration.get("localFS", False), "local_dns_lookup": master_configuration.get("localDNSlookup", False), diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml index b803d003..7dc49721 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml @@ -1,4 +1,5 @@ -- block: +- when: item.mount_point is defined + block: - name: Make sure disks are available failed_when: false filesystem: diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml index f6dfbf85..74dddfdc 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml @@ -18,6 +18,6 @@ when: master.disks is defined - name: Automount - when: volumes is defined and auto_mount + when: volumes is defined include_tasks: 020-disk-server-automount.yml with_items: "{{ volumes }}" \ No newline at end of file From 8b3f56546b9444815619a3c2a75336383659dc11 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 4 Jun 2024 15:48:05 +0200 Subject: [PATCH 095/145] added empty line and updated bibigrid.yml --- bibigrid.yml | 6 +++--- resources/playbook/roles/bibigrid/tasks/020-disk-server.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bibigrid.yml b/bibigrid.yml index bf073108..c2c59cbb 100644 --- a/bibigrid.yml +++ b/bibigrid.yml @@ -16,9 +16,9 @@ # - [public key one] ## Volumes and snapshots that will be mounted to master - # autoMount: False # WARNING: will overwrite unidentified filesystems - #masterMounts: - # - [mount one] + #masterMounts: (optional) # WARNING: will overwrite unidentified filesystems + # - name: [volume name] + # mountPoint: [where to mount to] # (optional) #nfsShares: /vol/spool/ is automatically created as a nfs # - [nfsShare one] diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml index 74dddfdc..9659583c 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml @@ -20,4 +20,4 @@ - name: Automount when: volumes is defined include_tasks: 020-disk-server-automount.yml - with_items: "{{ volumes }}" \ No newline at end of file + with_items: "{{ volumes }}" From 2b0b5b9495d017005ad4d58adae2eaa3d04f7881 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 6 Jun 2024 15:22:02 +0200 Subject: [PATCH 096/145] fixed nfsshare regex error and updated check to fit to the new name mountpoint pattern --- .../core/utility/validate_configuration.py | 2 +- bibigrid/core/utility/validate_schema.py | 2 +- .../markdown/features/configuration.md | 26 ++++++++++++++----- .../tasks/020-disk-server-automount.yml | 4 +-- .../roles/bibigrid/tasks/025-nfs-server.yml | 3 +-- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/bibigrid/core/utility/validate_configuration.py b/bibigrid/core/utility/validate_configuration.py index dd3c81c4..4451b632 100644 --- a/bibigrid/core/utility/validate_configuration.py +++ b/bibigrid/core/utility/validate_configuration.py @@ -333,7 +333,7 @@ def check_volumes(self): self.log.info("Checking volumes...") success = True for configuration, provider in zip(self.configurations, self.providers): - volume_identifiers = configuration.get("masterMounts") + volume_identifiers = [masterMount["name"] for masterMount in configuration.get("masterMounts", [])] if volume_identifiers: # check individually if volumes exist for volume_identifier in volume_identifiers: diff --git a/bibigrid/core/utility/validate_schema.py b/bibigrid/core/utility/validate_schema.py index 771c7c5c..d0ba629f 100644 --- a/bibigrid/core/utility/validate_schema.py +++ b/bibigrid/core/utility/validate_schema.py @@ -9,7 +9,7 @@ {'infrastructure': str, 'cloud': str, 'sshUser': str, Or('subnet', 'network'): str, 'cloud_identifier': str, Optional('sshPublicKeyFiles'): [str], Optional('sshTimeout'): int, Optional('cloudScheduling'): {Optional('sshTimeout'): int}, Optional('autoMount'): bool, - Optional('masterMounts'): [str], Optional('nfsShares'): [str], + Optional('masterMounts'): [{'name': str, Optional('mountPoint'): str}], Optional('nfsShares'): [str], Optional('userRoles'): [{'hosts': [str], 'roles': [{'name': str, Optional('tags'): [str]}]}], Optional('localFS'): bool, Optional('localDNSlookup'): bool, Optional('slurm'): bool, Optional('slurmConf'): {Optional('db'): str, Optional('db_user'): str, Optional('db_password'): str, diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 39f5e5cf..fdf12254 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -70,18 +70,30 @@ cloudScheduling: sshTimeout: 4 ``` -#### autoMount (optional:False) -> **Warning:** If a volume has an obscure filesystem, this might overwrite your data! - -If `True` all [masterMounts](#mastermounts-optional) will be automatically mounted by BiBiGrid if possible. -If a volume is not formatted or has an unknown filesystem, it will be formatted to `ext4`. -Default `False`. - #### masterMounts (optional:False) `masterMounts` expects a list of volumes and snapshots. Those will be attached to the master. If any snapshots are given, volumes are first created from them. Volumes are not deleted after Cluster termination. +```yaml +masterMounts: + - name: test # name of the volume to be attached + mountPoint: /vol/spool2 # where attached volume is to be mount to (optional) +``` + +`masterMounts` can be combined with [nfsshares](#nfsshares-optional). +The following example attaches volume test to our master instance and mounts it to `/vol/spool2`. +Then it creates an nfsshare on `/vol/spool2` allowing workers to access the volume test. + +```yaml +masterMounts: + - name: test # name of the volume to be attached + mountPoint: /vol/spool2 # where attached volume is to be mount to (optional) + +nfsshares: + - /vol/spool2 +``` +
What is mounting? diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml index 7dc49721..8e4b5f49 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml @@ -19,7 +19,7 @@ - name: Create mount folders if they don't exist file: - path: "/vol/{{ item.name }}" + path: "/vol/{{ item.mount_point }}" state: directory mode: '0755' owner: root @@ -27,7 +27,7 @@ - name: Mount disks mount: - path: "/vol/{{ item.name }}" + path: "{{ item.mount_point }}" src: "{{ item.device }}" state: mounted fstype: "{{ filesystem_type.stdout }}" diff --git a/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yml b/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yml index 8f42e022..c7030971 100644 --- a/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yml @@ -17,8 +17,7 @@ lineinfile: path: /etc/exports state: present - regexp: '^{{ item.src }}' - + regexp: '^{{ item.src }} ' line: "{{ item.src }} {{cluster_cidrs|map(attribute='provider_cidrs')|flatten|join('(rw,nohide,insecure,no_subtree_check,async) ')}}\ (rw,nohide,insecure,no_subtree_check,async) From 4480f0130302cb934906c2e3355ba08c250227a0 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 6 Jun 2024 16:45:33 +0200 Subject: [PATCH 097/145] hotfix: folder creation now before accessing hosts.yml --- bibigrid/core/actions/create.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 0a21abe9..0dd08eef 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -342,10 +342,6 @@ def upload_data(self): @return: """ self.log.debug("Uploading ansible Data") - for folder in [a_rp.VARS_FOLDER, a_rp.GROUP_VARS_FOLDER, a_rp.HOST_VARS_FOLDER]: - if not os.path.isdir(folder): - self.log.info("%s not found. Creating folder.", folder) - os.mkdir(folder) if not os.path.isfile(a_rp.HOSTS_FILE): with open(a_rp.HOSTS_FILE, 'a', encoding='utf-8') as hosts_file: hosts_file.write("# placeholder file for worker DNS entries (see 003-dns)") @@ -428,6 +424,10 @@ def create(self): # pylint: disable=too-many-branches,too-many-statements @return: exit_state """ try: + for folder in [a_rp.VARS_FOLDER, a_rp.GROUP_VARS_FOLDER, a_rp.HOST_VARS_FOLDER]: + if not os.path.isdir(folder): + self.log.info("%s not found. Creating folder.", folder) + os.mkdir(folder) self.generate_keypair() self.prepare_configurations() self.create_defaults() From 434b42e6e726b0dc351dc2991837327c21a7be0c Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 7 Jun 2024 15:40:28 +0200 Subject: [PATCH 098/145] fixed tests --- tests/test_ansible_configurator.py | 36 ++++++++++++---------------- tests/test_validate_configuration.py | 14 ++++++----- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index a23818b4..8d34ba69 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -92,16 +92,15 @@ def test_generate_site_file_yaml_all_role(self): {'role': 'resistance_nextflow', 'tags': ['rn']}], 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'vars/rn']}] self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(user_roles)) - + def test_generate_common_configuration_false(self): cidrs = "42" cluster_id = "21" default_user = "ubuntu" ssh_user = "test" configuration = [{}] - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, 'local_dns_lookup': False, 'local_fs': False, 'slurm': True, @@ -125,9 +124,8 @@ def test_generate_common_configuration_true(self): ssh_user = "test" configuration = [ {elem: "True" for elem in ["localFS", "localDNSlookup", "useMasterAsCompute", "slurm", "zabbix", "ide"]}] - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'True', 'enable_nfs': False, 'enable_slurm': 'True', 'enable_zabbix': 'True', 'ide_conf': {'build': False, 'ide': False, 'port_end': 8383, 'port_start': 8181, @@ -154,9 +152,8 @@ def test_generate_common_configuration_nfs_shares(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, @@ -180,9 +177,8 @@ def test_generate_common_configuration_nfs(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, @@ -205,9 +201,8 @@ def test_generate_common_configuration_ext_nfs_shares(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}], @@ -231,9 +226,8 @@ def test_generate_common_configuration_ide(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'Some1', 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, 'ide_conf': {'build': False, 'ide': False, 'key1': 'Some2', 'port_end': 8383, @@ -351,8 +345,8 @@ def test_write_yaml_alias(self, mock_yaml): @patch("bibigrid.core.utility.ansible_configurator.generate_site_file_yaml") @patch("bibigrid.core.utility.ansible_configurator.write_yaml") @patch("bibigrid.core.utility.ansible_configurator.get_cidrs") - def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_hosts, mock_list, - mock_common, mock_worker, mock_write): + def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_hosts, mock_list, mock_common, + mock_worker, mock_write): mock_cidrs.return_value = 421 mock_list.return_value = {2: 422} provider = MagicMock() diff --git a/tests/test_validate_configuration.py b/tests/test_validate_configuration.py index 322cbb36..27994fe2 100644 --- a/tests/test_validate_configuration.py +++ b/tests/test_validate_configuration.py @@ -211,7 +211,8 @@ def test_check_volumes_mismatch(self): provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) provider1.cloud_specification = {"identifier": "1"} v_c = validate_configuration.ValidateConfiguration(providers=[provider1], - configurations=[{"masterMounts": ["Test"]}], log=Mock()) + configurations=[{"masterMounts": [{"name": "Test"}]}], + log=Mock()) self.assertFalse(v_c.check_volumes()) def test_check_volumes_match_snapshot(self): @@ -220,7 +221,8 @@ def test_check_volumes_match_snapshot(self): provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": 1}) provider1.cloud_specification = {"identifier": "1"} v_c = validate_configuration.ValidateConfiguration(providers=[provider1], - configurations=[{"masterMounts": ["Test"]}], log=Mock()) + configurations=[{"masterMounts": [{"name": "Test"}]}], + log=Mock()) self.assertTrue(v_c.check_volumes()) def test_check_volumes_match_snapshot_count(self): @@ -229,9 +231,8 @@ def test_check_volumes_match_snapshot_count(self): provider1.get_volume_by_id_or_name = MagicMock(return_value=None) provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": i}) provider1.cloud_specification = {"identifier": i} - v_c = validate_configuration.ValidateConfiguration(providers=[provider1] * i, - configurations=[{"masterMounts": ["Test"] * i}], - log=Mock()) + v_c = validate_configuration.ValidateConfiguration(providers=[provider1] * i, configurations=[ + {"masterMounts": [{"name": "Test"}] * i}], log=Mock()) self.assertTrue(v_c.check_volumes()) self.assertTrue(v_c.required_resources_dict[i]["volumes"] == i) self.assertTrue(v_c.required_resources_dict[i]["volume_gigabytes"] == i ** 2) @@ -242,7 +243,8 @@ def test_check_volumes_match_volume(self): provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) provider1.cloud_specification = {"identifier": "1"} v_c = validate_configuration.ValidateConfiguration(providers=[provider1], - configurations=[{"masterMounts": ["Test"]}], log=Mock()) + configurations=[{"masterMounts": [{"name": "Test"}]}], + log=Mock()) self.assertTrue(v_c.check_volumes()) self.assertTrue(v_c.required_resources_dict["1"]["volumes"] == 0) self.assertTrue(v_c.required_resources_dict["1"]["volume_gigabytes"] == 0) From 868462f2a1d15da6b4f63acd632f8ae2caf6b83f Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 7 Jun 2024 16:01:52 +0200 Subject: [PATCH 099/145] moved dnsmasq installation infront of /etc/resolv removal --- resources/playbook/roles/bibigrid/tasks/003-dns.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/playbook/roles/bibigrid/tasks/003-dns.yml b/resources/playbook/roles/bibigrid/tasks/003-dns.yml index 6bb075c2..3515f86e 100644 --- a/resources/playbook/roles/bibigrid/tasks/003-dns.yml +++ b/resources/playbook/roles/bibigrid/tasks/003-dns.yml @@ -1,3 +1,8 @@ +- name: Install dnsmasq + apt: + name: dnsmasq + state: present + - name: Disable and Stop systemd-resolve systemd: name: systemd-resolved @@ -9,11 +14,6 @@ path: /etc/resolv.conf state: absent -- name: Install dnsmasq - apt: - name: dnsmasq - state: present - - name: Create /etc/hosts template: src: dns/hosts.j2 From 49ce93f4db5bfd91270a40b4994dc04a2979def6 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 20 Jun 2024 13:10:22 +0200 Subject: [PATCH 100/145] fixed tests --- tests/test_ansible_configurator.py | 36 ++++++++++++---------------- tests/test_validate_configuration.py | 14 ++++++----- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index a23818b4..8d34ba69 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -92,16 +92,15 @@ def test_generate_site_file_yaml_all_role(self): {'role': 'resistance_nextflow', 'tags': ['rn']}], 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'vars/rn']}] self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(user_roles)) - + def test_generate_common_configuration_false(self): cidrs = "42" cluster_id = "21" default_user = "ubuntu" ssh_user = "test" configuration = [{}] - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, 'local_dns_lookup': False, 'local_fs': False, 'slurm': True, @@ -125,9 +124,8 @@ def test_generate_common_configuration_true(self): ssh_user = "test" configuration = [ {elem: "True" for elem in ["localFS", "localDNSlookup", "useMasterAsCompute", "slurm", "zabbix", "ide"]}] - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'True', 'enable_nfs': False, 'enable_slurm': 'True', 'enable_zabbix': 'True', 'ide_conf': {'build': False, 'ide': False, 'port_end': 8383, 'port_start': 8181, @@ -154,9 +152,8 @@ def test_generate_common_configuration_nfs_shares(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, @@ -180,9 +177,8 @@ def test_generate_common_configuration_nfs(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, @@ -205,9 +201,8 @@ def test_generate_common_configuration_ext_nfs_shares(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}], @@ -231,9 +226,8 @@ def test_generate_common_configuration_ide(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'auto_mount': False, 'bibigrid_version': version.__version__, - 'cloud_scheduling': {'sshTimeout': 4}, 'cluster_cidrs': cidrs, - 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'Some1', 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, 'ide_conf': {'build': False, 'ide': False, 'key1': 'Some2', 'port_end': 8383, @@ -351,8 +345,8 @@ def test_write_yaml_alias(self, mock_yaml): @patch("bibigrid.core.utility.ansible_configurator.generate_site_file_yaml") @patch("bibigrid.core.utility.ansible_configurator.write_yaml") @patch("bibigrid.core.utility.ansible_configurator.get_cidrs") - def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_hosts, mock_list, - mock_common, mock_worker, mock_write): + def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_hosts, mock_list, mock_common, + mock_worker, mock_write): mock_cidrs.return_value = 421 mock_list.return_value = {2: 422} provider = MagicMock() diff --git a/tests/test_validate_configuration.py b/tests/test_validate_configuration.py index 322cbb36..27994fe2 100644 --- a/tests/test_validate_configuration.py +++ b/tests/test_validate_configuration.py @@ -211,7 +211,8 @@ def test_check_volumes_mismatch(self): provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) provider1.cloud_specification = {"identifier": "1"} v_c = validate_configuration.ValidateConfiguration(providers=[provider1], - configurations=[{"masterMounts": ["Test"]}], log=Mock()) + configurations=[{"masterMounts": [{"name": "Test"}]}], + log=Mock()) self.assertFalse(v_c.check_volumes()) def test_check_volumes_match_snapshot(self): @@ -220,7 +221,8 @@ def test_check_volumes_match_snapshot(self): provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": 1}) provider1.cloud_specification = {"identifier": "1"} v_c = validate_configuration.ValidateConfiguration(providers=[provider1], - configurations=[{"masterMounts": ["Test"]}], log=Mock()) + configurations=[{"masterMounts": [{"name": "Test"}]}], + log=Mock()) self.assertTrue(v_c.check_volumes()) def test_check_volumes_match_snapshot_count(self): @@ -229,9 +231,8 @@ def test_check_volumes_match_snapshot_count(self): provider1.get_volume_by_id_or_name = MagicMock(return_value=None) provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": i}) provider1.cloud_specification = {"identifier": i} - v_c = validate_configuration.ValidateConfiguration(providers=[provider1] * i, - configurations=[{"masterMounts": ["Test"] * i}], - log=Mock()) + v_c = validate_configuration.ValidateConfiguration(providers=[provider1] * i, configurations=[ + {"masterMounts": [{"name": "Test"}] * i}], log=Mock()) self.assertTrue(v_c.check_volumes()) self.assertTrue(v_c.required_resources_dict[i]["volumes"] == i) self.assertTrue(v_c.required_resources_dict[i]["volume_gigabytes"] == i ** 2) @@ -242,7 +243,8 @@ def test_check_volumes_match_volume(self): provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) provider1.cloud_specification = {"identifier": "1"} v_c = validate_configuration.ValidateConfiguration(providers=[provider1], - configurations=[{"masterMounts": ["Test"]}], log=Mock()) + configurations=[{"masterMounts": [{"name": "Test"}]}], + log=Mock()) self.assertTrue(v_c.check_volumes()) self.assertTrue(v_c.required_resources_dict["1"]["volumes"] == 0) self.assertTrue(v_c.required_resources_dict["1"]["volume_gigabytes"] == 0) From de145dcd6fc01fe60e06ba995b8e1a239b1e7f71 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 24 Jun 2024 15:10:14 +0200 Subject: [PATCH 101/145] fixed nfs exports by removing unnecessary "/" at the beginning --- bibigrid/core/utility/ansible_configurator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 0b470322..48e04460 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -195,7 +195,7 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us if master_configuration.get("nfs"): nfs_shares = master_configuration.get("nfsShares", []) nfs_shares = nfs_shares + DEFAULT_NFS_SHARES - common_configuration_yaml["nfs_mounts"] = [{"src": "/" + nfs_share, "dst": "/" + nfs_share} for nfs_share in + common_configuration_yaml["nfs_mounts"] = [{"src": nfs_share, "dst": nfs_share} for nfs_share in nfs_shares] common_configuration_yaml["ext_nfs_mounts"] = [{"src": ext_nfs_share, "dst": ext_nfs_share} for ext_nfs_share in (master_configuration.get("extNfsShares", []))] From 02aab94743d78756f2ea6df3c20bca3994507a48 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 27 Jun 2024 13:38:00 +0200 Subject: [PATCH 102/145] fixed master running slurmd but not being listed in slurm.conf. Now set to drained. --- bibigrid/core/utility/ansible_configurator.py | 2 ++ resources/defaults/slurm/slurm.j2 | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 48e04460..57793333 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -98,6 +98,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py "gateway_ip": configuration["private_v4"], "cloud_identifier": configuration["cloud_identifier"], "on_demand": worker.get("onDemand", True), + "state": "CLOUD", "partitions": worker.get("partitions", []) + ["all", configuration["cloud_identifier"]]} worker_features = worker.get("features", []) @@ -138,6 +139,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py "flavor": flavor_dict, "private_v4": configuration["private_v4"], "cloud_identifier": configuration["cloud_identifier"], "volumes": configuration["volumes"], "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False), + "state": "UNKNOWN" if configuration.get("useMasterAsCompute", True) else "DRAINED", "on_demand": False, "partitions": master.get("partitions", []) + ["all", configuration["cloud_identifier"]]} if configuration.get("wireguard_peer"): diff --git a/resources/defaults/slurm/slurm.j2 b/resources/defaults/slurm/slurm.j2 index 8d7af05e..ef824a1c 100644 --- a/resources/defaults/slurm/slurm.j2 +++ b/resources/defaults/slurm/slurm.j2 @@ -64,11 +64,11 @@ SlurmdDebug=info SlurmdLogFile=/var/log/slurm/slurmd.log # COMPUTE NODES +# use_master_as_compute {% set partitions = {} %} {% set exclude_groups = [] %} -{% set master_or_empty = groups.master if use_master_as_compute else [] %} {% set node_groups = [] %} -{% for node_name in master_or_empty + groups.workers %} +{% for node_name in groups.master + groups.workers %} {% set node = hostvars[node_name] %} {% if node.name not in node_groups %} {% if not node.on_demand %} @@ -76,7 +76,8 @@ SlurmdLogFile=/var/log/slurm/slurmd.log {% endif %} {% set _ = node_groups.append(node.name) %} {% set mem = (node.flavor.ram // 1024) * 1000 %} -NodeName={{ node.name }} SocketsPerBoard={{ node.flavor.vcpus }} CoresPerSocket=1 RealMemory={{ mem - [mem // 2, 16000] | min }} State={{ 'CLOUD' if node.on_demand else 'UNKNOWN' }} {{"Features=" + (node.features | join(",")) if node.features is defined }}# {{ node.cloud_identifier }} +# {{ node }} +NodeName={{ node.name }} SocketsPerBoard={{ node.flavor.vcpus }} CoresPerSocket=1 RealMemory={{ mem - [mem // 2, 16000] | min }} State={{node.state }} {{"Features=" + (node.features | join(",")) if node.features is defined }}# {{ node.cloud_identifier }} {% for partition in node.partitions %} {% if partition not in partitions %} {% set _ = partitions.update({partition: []}) %} From 622fcd2a907a0b04f4611641ab4821f2331fc2ff Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 27 Jun 2024 13:38:32 +0200 Subject: [PATCH 103/145] improved logging --- resources/playbook/roles/bibigrid/files/slurm/delete_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py index 7b7fc674..53741a99 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py @@ -61,7 +61,7 @@ if terminate_worker in possible_workers: result = connections[worker_group["cloud_identifier"]].delete_server(terminate_worker) if not result: - logging.warning(f"Couldn't delete worker {terminate_worker}") + logging.warning(f"Couldn't delete worker {terminate_worker}: Server doesn't exist") else: logging.info(f"Deleted {terminate_worker}") From 305dfabc00772a5315266efb743cb63eec9c51c9 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 27 Jun 2024 17:49:37 +0200 Subject: [PATCH 104/145] increased timeout. Corrected comment in slurm.j2 --- bibigrid/core/actions/create.py | 2 +- bibigrid/core/utility/ansible_configurator.py | 4 ++-- resources/defaults/slurm/slurm.j2 | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index c406bbd5..bffb9835 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -82,7 +82,7 @@ def __init__(self, providers, configurations, config_path, log, debug=False, clu self.ssh_user = configurations[0].get("sshUser") or "ubuntu" self.ssh_add_public_key_commands = ssh_handler.get_add_ssh_public_key_commands( configurations[0].get("sshPublicKeyFiles")) - self.ssh_timeout = configurations[0].get("sshTimeout", 4) + self.ssh_timeout = configurations[0].get("sshTimeout", 5) self.config_path = config_path self.master_ip = None self.log.debug("Cluster-ID: %s", self.cluster_id) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 57793333..ca352775 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -28,8 +28,8 @@ "server_name": "bibigrid", "admin_password": "bibigrid"} SLURM_CONF = {"db": "slurm", "db_user": "slurm", "db_password": "changeme", "munge_key": id_generation.generate_munge_key(), - "elastic_scheduling": {"SuspendTime": 3600, "ResumeTimeout": 900, "TreeWidth": 128}} -CLOUD_SCHEDULING = {"sshTimeout": 4} + "elastic_scheduling": {"SuspendTime": 3600, "ResumeTimeout": 900, "SuspendTimeout": 30, "TreeWidth": 128}} +CLOUD_SCHEDULING = {"sshTimeout": 5} def delete_old_vars(log): diff --git a/resources/defaults/slurm/slurm.j2 b/resources/defaults/slurm/slurm.j2 index ef824a1c..7def984f 100644 --- a/resources/defaults/slurm/slurm.j2 +++ b/resources/defaults/slurm/slurm.j2 @@ -99,8 +99,10 @@ ResumeProgram=/opt/slurm/create.sh # Resume time is 15 minutes (900 seconds) ResumeTimeout= {{ slurm_conf.elastic_scheduling.ResumeTimeout }} SuspendProgram=/opt/slurm/terminate.sh -# Suspend time is 10 minutes (600 seconds) +# Suspend time is 15 minutes (900 seconds) SuspendTime= {{ slurm_conf.elastic_scheduling.SuspendTime }} +# SuspendTimeout is 30 seconds +SuspendTimeout={{ slurm_conf.elastic_scheduling.SuspendTimeout }} # Excludes {{ hostvars[groups.master.0].name }} from suspend SuspendExcNodes={{ exclude_groups | join(',') }} # Maximum number of nodes From 45387c19afaf724dfd792bf6c1e957232e702a97 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 28 Jun 2024 14:13:13 +0200 Subject: [PATCH 105/145] updated info regarding timeouts (changed from 4 to 5). --- documentation/markdown/features/configuration.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index fdf12254..0bfc4f18 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -44,9 +44,9 @@ sshPublicKeyFiles: - /home/user/.ssh/id_ecdsa_colleague.pub ``` -#### sshTimeout (optional) +#### sshTimeout (optional:5) Defines the number of attempts that BiBiGrid will try to connect to the master instance via ssh. -Attempts have a pause of `2^(attempts+2)` seconds in between. Default value is 4. +Attempts have a pause of `2^(attempts+2)` seconds in between. Default value is 5. #### customAnsibleCfg (optional:False) When False, changes in the resources/playbook/ansible.cfg are overwritten by the create action. @@ -58,12 +58,12 @@ When False, changes in the resources/playbook/roles/bibigrid/templates/slurm.j2 When True, changes are kept - even when you perform a git pull as the file is not tracked. The default can be found at resources/default/slurm/slurm.j2. -#### cloudScheduling (optional) -This key allows you to influence cloud scheduling. Currently, only a single key `sshTimeout` can be set here. +#### cloudScheduling (optional:5) +This key allows you to influence cloud scheduling. Currently, only a single key `sshTimeout` can be set here. Default is 5. -##### sshTimeout (optional:4) +##### sshTimeout (optional:5) Defines the number of attempts that the master will try to connect to on demand created worker instances via ssh. -Attempts have a pause of `2^(attempts+2)` seconds in between. Default value is 4. +Attempts have a pause of `2^(attempts+2)` seconds in between. Default value is 5. ```yaml cloudScheduling: From adaeae732d10faf74c531132e959e0b00953f539 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 1 Jul 2024 11:39:19 +0200 Subject: [PATCH 106/145] added SuspendTimeout as optional to elastic_scheduling --- bibigrid/core/utility/validate_schema.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bibigrid/core/utility/validate_schema.py b/bibigrid/core/utility/validate_schema.py index d0ba629f..e563d1bc 100644 --- a/bibigrid/core/utility/validate_schema.py +++ b/bibigrid/core/utility/validate_schema.py @@ -14,6 +14,8 @@ Optional('localFS'): bool, Optional('localDNSlookup'): bool, Optional('slurm'): bool, Optional('slurmConf'): {Optional('db'): str, Optional('db_user'): str, Optional('db_password'): str, Optional('munge_key'): str, Optional('elastic_scheduling'): {Optional('SuspendTime'): int, + Optional( + 'SuspendTimeout'): int, Optional( 'ResumeTimeout'): int, Optional('TreeWidth'): int}}, From dfaff0931b4f39d8f9dff5b75a40d836aff05a8a Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 1 Jul 2024 11:39:32 +0200 Subject: [PATCH 107/145] updated documentation --- bibigrid.yml | 21 ++++++++++++------- .../markdown/features/configuration.md | 18 +++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/bibigrid.yml b/bibigrid.yml index c2c59cbb..765cab6d 100644 --- a/bibigrid.yml +++ b/bibigrid.yml @@ -7,9 +7,9 @@ cloud: openstack # name of clouds.yaml cloud-specification key (which is value to top level key clouds) # -- BEGIN: GENERAL CLUSTER INFORMATION -- - # sshTimeout: 5 # Number of ssh connection attempts with 2^attempt seconds in between (2^sshTimeout-1 is the max time before returning with an error) + # sshTimeout: 5 # number of attempts to connect to instances during startup with delay in between # cloudScheduling: - # sshTimeout: 42 # like the sshTimeout during startup but during the on demand scheduling + # sshTimeout: 42 # like sshTimeout but during the on demand scheduling on the running cluster ## sshPublicKeyFiles listed here will be added to access the cluster. A temporary key is created by bibigrid itself. #sshPublicKeyFiles: @@ -32,7 +32,11 @@ # - [...] ## Uncomment if you don't want assign a public ip to the master; for internal cluster (Tuebingen). - #useMasterWithPublicIp: False # defaults True if False no public-ip (floating-ip) will be allocated + # useMasterWithPublicIp: False # defaults True if False no public-ip (floating-ip) will be allocated + # gateway: # if you want to use a gateway for create. + # ip: # IP of gateway to use + # portFunction: 30000 + oct4 # variables are called: oct1.oct2.oct3.oct4 + # deleteTmpKeypairAfter: False # dontUploadCredentials: False @@ -45,7 +49,7 @@ useMasterAsCompute: True - #waitForServices: # existing service name that runs after an instance is launched. BiBiGrid's playbook will wait until service is "stopped" to avoid issues + # waitForServices: # existing service name that runs after an instance is launched. BiBiGrid's playbook will wait until service is "stopped" to avoid issues # - de.NBI_Bielefeld_environment.service # uncomment for cloud site Bielefeld # master configuration @@ -59,7 +63,7 @@ # fallbackOnOtherImage: False # if True, most similar image by name will be picked. A regex can also be given instead. # worker configuration - #workerInstances: + # workerInstances: # - type: # existing type/flavor on your cloud. See launch instance>flavor for options # image: # same as master. Consider using regex to prevent image updates from breaking your running cluster # count: # any number of workers you would like to create with set type, image combination @@ -89,9 +93,6 @@ # Depends on cloud site and project subnet: # existing subnet on your cloud. See https://openstack.cebitec.uni-bielefeld.de/project/networks/ # or network: - # gateway: # if you want to use a gateway for create. - # ip: # IP of gateway to use - # portFunction: 30000 + oct4 # variables are called: oct1.oct2.oct3.oct4 # Uncomment if no full DNS service for started instances is available. # Currently, the case in Berlin, DKFZ, Heidelberg and Tuebingen. @@ -99,4 +100,8 @@ #features: # list + # elastic_scheduling: # for large or slow clusters increasing these timeouts might be necessary to avoid failures + # SuspendTimeout: 30 # after SuspendTimeout seconds, slurm allows to power up the node again + # ResumeTimeout: 900 # if a node doesn't start in ResumeTimeout seconds, the start is considered failed. + #- [next configurations] diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 0bfc4f18..0b86fcd2 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -67,7 +67,7 @@ Attempts have a pause of `2^(attempts+2)` seconds in between. Default value is 5 ```yaml cloudScheduling: - sshTimeout: 4 + sshTimeout: 5 ``` #### masterMounts (optional:False) @@ -150,16 +150,19 @@ If `False`, the cluster will start without the job scheduling system slurm. For nearly all cases the default value is what you need. Default is `True`. ##### slurmConf (optional) -`slurmConf` contains variable fields in the `slurm.conf`. The most common use is to increase the `SuspendTime` -and the `ResumeTimeout` like: +`slurmConf` contains variable fields in the `slurm.conf`. The most common use is to increase the `SuspendTime`, +`SuspendTimeout` and the `ResumeTimeout` like: ```yaml elastic_scheduling: SuspendTime: 1800 + SuspendTimeout: 60 ResumeTimeout: 1800 ``` -Please only use if necessary. On Demand Scheduling improves resource availability for all users. +Increasing the `SuspendTime` should only be done with consideration for other users. +On Demand Scheduling improves resource availability for all users. +If some nodes need to be active during the entire cluster lifetime, [onDemand](#workerinstances) might be the better approach. ###### Defaults ```yaml @@ -169,8 +172,9 @@ slurmConf: db_password: changeme munge_key: # automatically generated via id_generation.generate_munge_key elastic_scheduling: - SuspendTime: 900 # if a node doesn't start in SuspendTime seconds, the start is considered failed. See https://slurm.schedmd.com/slurm.conf.html#OPT_ResumeProgram - ResumeTimeout: 900 # if a node is not used for ResumeTimeout seconds, it will shut down + SuspendTime: 900 # if a node is not used for SuspendTime seconds, it will shut down + SuspendTimeout: 30 # after SuspendTimeout seconds, slurm allows to power up the powered down node again + ResumeTimeout: 900 # if a node doesn't start in ResumeTimeout seconds, the start is considered failed. See https://slurm.schedmd.com/slurm.conf.html#OPT_ResumeProgram TreeWidth: 128 # https://slurm.schedmd.com/slurm.conf.html#OPT_TreeWidth ``` @@ -254,7 +258,7 @@ workerInstance: - `type` sets the instance's hardware configuration. - `image` sets the bootable operating system to be installed on the instance. - `count` sets how many workers of that `type` `image` combination are in this work group -- `onDemand` defines whether nodes in the worker group are scheduled on demand (True) or are started permanently (False). Please only use if necessary. On Demand Scheduling improves resource availability for all users. This option only works on the master cloud for now. +- `onDemand` defines whether nodes in the worker group are scheduled on demand (True) or are started permanently (False). Please only use if necessary. On Demand Scheduling improves resource availability for all users. This option only works for single cloud setups for now. - `partitions` allow you to force Slurm to schedule to a group of nodes (partitions) ([more](https://slurm.schedmd.com/slurm.conf.html#SECTION_PARTITION-CONFIGURATION)) - `features` allow you to force Slurm to schedule a job only on nodes that meet certain `bool` constraints. This can be helpful when only certain nodes can access a specific resource - like a database ([more](https://slurm.schedmd.com/slurm.conf.html#OPT_Features)). From 5dae8054d514d0b0df52c279c4a7850bb14bb0aa Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 2 Jul 2024 15:27:59 +0200 Subject: [PATCH 108/145] permission fix --- .../roles_user/resistance_nextflow/tasks/main.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml b/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml index 85b79975..0b497694 100644 --- a/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml +++ b/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml @@ -19,7 +19,15 @@ args: chdir: /vol/spool/ +- name: Change file ownership, group and permissions + file: + path: /vol/spool/nextflow + owner: ubuntu + group: ubuntu + mode: '0775' + - name: Execute Nextflow workflow + become_user: ubuntu shell: ./nextflow run resFinder.nf -profile slurm args: chdir: "/vol/spool" # Change to the directory where your workflow resides From 874ad82be9b3be4f2ce16f5621f9ed3f72a8663a Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Wed, 3 Jul 2024 16:48:07 +0200 Subject: [PATCH 109/145] fixes #394 --- .../roles/bibigrid/templates/zabbix/zabbix_agentd.conf.j2 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/playbook/roles/bibigrid/templates/zabbix/zabbix_agentd.conf.j2 b/resources/playbook/roles/bibigrid/templates/zabbix/zabbix_agentd.conf.j2 index dd365f91..5e6e2102 100644 --- a/resources/playbook/roles/bibigrid/templates/zabbix/zabbix_agentd.conf.j2 +++ b/resources/playbook/roles/bibigrid/templates/zabbix/zabbix_agentd.conf.j2 @@ -98,7 +98,7 @@ LogFileSize=0 {% if 'master' in group_names %} Server=127.0.0.1 {% else %} -Server={{ hostvars[groups.master.0].floating_ip }} +Server={{ hostvars[groups.master.0].private_v4 }} {% endif %} ### Option: ListenPort @@ -146,7 +146,7 @@ ListenIP={{ ansible_default_ipv4.address }} # Default: # ServerActive= -ServerActive={{ hostvars[groups.master.0].floating_ip }} +ServerActive={{ hostvars[groups.master.0].private_v4 }} ### Option: Hostname # Unique, case sensitive hostname. From 5b3bdd259898870da9b95cd665339d6ef26e5989 Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Wed, 3 Jul 2024 19:18:01 +0200 Subject: [PATCH 110/145] fixes #394 (also for hybrid cluster) --- .../roles/bibigrid/templates/zabbix/zabbix_agentd.conf.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/playbook/roles/bibigrid/templates/zabbix/zabbix_agentd.conf.j2 b/resources/playbook/roles/bibigrid/templates/zabbix/zabbix_agentd.conf.j2 index 5e6e2102..e4d62218 100644 --- a/resources/playbook/roles/bibigrid/templates/zabbix/zabbix_agentd.conf.j2 +++ b/resources/playbook/roles/bibigrid/templates/zabbix/zabbix_agentd.conf.j2 @@ -98,7 +98,7 @@ LogFileSize=0 {% if 'master' in group_names %} Server=127.0.0.1 {% else %} -Server={{ hostvars[groups.master.0].private_v4 }} +Server={{ hostvars[groups.master.0].private_v4 }}{%if 'wireguard' in hostvars[groups.master.0] %},{{ hostvars[groups.master.0].wireguard.ip }}{% endif %} {% endif %} ### Option: ListenPort From 79b4bb8ec5bb014b33023cfa48e8786fa74280bf Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 4 Jul 2024 10:52:01 +0200 Subject: [PATCH 111/145] increased ResumeTimeout by 5 minutes. yml to yaml --- bibigrid/core/utility/ansible_configurator.py | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index ca352775..aeba9ec9 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -28,7 +28,8 @@ "server_name": "bibigrid", "admin_password": "bibigrid"} SLURM_CONF = {"db": "slurm", "db_user": "slurm", "db_password": "changeme", "munge_key": id_generation.generate_munge_key(), - "elastic_scheduling": {"SuspendTime": 3600, "ResumeTimeout": 900, "SuspendTimeout": 30, "TreeWidth": 128}} + "elastic_scheduling": {"SuspendTime": 3600, "ResumeTimeout": 1200, "SuspendTimeout": 30, + "TreeWidth": 128}} CLOUD_SCHEDULING = {"sshTimeout": 5} @@ -54,8 +55,7 @@ def generate_site_file_yaml(user_roles): @param user_roles: userRoles given by the config @return: site_yaml (dict) """ - site_yaml = [{'hosts': 'master', "become": "yes", - "vars_files": VARS_FILES, "roles": MASTER_ROLES}, + site_yaml = [{'hosts': 'master', "become": "yes", "vars_files": VARS_FILES, "roles": MASTER_ROLES}, {'hosts': 'vpngtw', "become": "yes", "vars_files": VARS_FILES, "roles": VPNGTW_ROLES}, {"hosts": "workers", "become": "yes", "vars_files": VARS_FILES, "roles": WORKER_ROLES}] for user_role in user_roles: @@ -97,8 +97,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py "network": configuration["network"], "flavor": flavor_dict, "gateway_ip": configuration["private_v4"], "cloud_identifier": configuration["cloud_identifier"], - "on_demand": worker.get("onDemand", True), - "state": "CLOUD", + "on_demand": worker.get("onDemand", True), "state": "CLOUD", "partitions": worker.get("partitions", []) + ["all", configuration["cloud_identifier"]]} worker_features = worker.get("features", []) @@ -109,7 +108,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py worker_dict["features"] = features pass_through(configuration, worker_dict, "waitForServices", "wait_for_services") - write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, group_name), worker_dict, log) + write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, f"{group_name}.yaml"), worker_dict, log) vpngtw = configuration.get("vpnInstance") if vpngtw: name = create.VPN_WORKER_IDENTIFIER(cluster_id=cluster_id, additional=f"{vpn_count}") @@ -128,7 +127,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py if configuration.get("wireguard_peer"): vpngtw_dict["wireguard"] = {"ip": wireguard_ip, "peer": configuration.get("wireguard_peer")} pass_through(configuration, vpngtw_dict, "waitForServices", "wait_for_services") - write_yaml(os.path.join(aRP.HOST_VARS_FOLDER, name), vpngtw_dict, log) + write_yaml(os.path.join(aRP.HOST_VARS_FOLDER, f"{name}.yaml"), vpngtw_dict, log) else: master = configuration["masterInstance"] name = create.MASTER_IDENTIFIER(cluster_id=cluster_id) @@ -145,7 +144,7 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py if configuration.get("wireguard_peer"): master_dict["wireguard"] = {"ip": "10.0.0.1", "peer": configuration.get("wireguard_peer")} pass_through(configuration, master_dict, "waitForServices", "wait_for_services") - write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, "master.yml"), master_dict, log) + write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, "master.yaml"), master_dict, log) def pass_through(dict_from, dict_to, key_from, key_to=None): @@ -176,10 +175,8 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us """ master_configuration = configurations[0] log.info("Generating common configuration file...") - common_configuration_yaml = {"bibigrid_version": __version__, - "cluster_id": cluster_id, - "cluster_cidrs": cidrs, "default_user": default_user, - "local_fs": master_configuration.get("localFS", False), + common_configuration_yaml = {"bibigrid_version": __version__, "cluster_id": cluster_id, "cluster_cidrs": cidrs, + "default_user": default_user, "local_fs": master_configuration.get("localFS", False), "local_dns_lookup": master_configuration.get("localDNSlookup", False), "use_master_as_compute": master_configuration.get("useMasterAsCompute", True), "dns_server_list": master_configuration.get("dns_server_list", ["8.8.8.8"]), @@ -197,8 +194,7 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us if master_configuration.get("nfs"): nfs_shares = master_configuration.get("nfsShares", []) nfs_shares = nfs_shares + DEFAULT_NFS_SHARES - common_configuration_yaml["nfs_mounts"] = [{"src": nfs_share, "dst": nfs_share} for nfs_share in - nfs_shares] + common_configuration_yaml["nfs_mounts"] = [{"src": nfs_share, "dst": nfs_share} for nfs_share in nfs_shares] common_configuration_yaml["ext_nfs_mounts"] = [{"src": ext_nfs_share, "dst": ext_nfs_share} for ext_nfs_share in (master_configuration.get("extNfsShares", []))] From beb584bae6cfbaf0f2d77af95f2076ed5a9b6344 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 4 Jul 2024 14:55:31 +0200 Subject: [PATCH 112/145] changed all yml to yaml (as preferred by yaml) --- .github/workflows/linting.yml | 2 +- .gitignore | 4 +- README.md | 66 ++++++++++--------- bibigrid.yml => bibigrid.yaml | 2 +- bibigrid/core/startup_rest.py | 6 +- bibigrid/core/utility/ansible_commands.py | 4 +- bibigrid/core/utility/ansible_configurator.py | 2 +- .../utility/handler/configuration_handler.py | 4 +- .../utility/paths/ansible_resources_path.py | 36 +++++----- bibigrid/core/utility/validate_schema.py | 2 +- documentation/markdown/features/CLI.md | 2 +- documentation/markdown/features/check.md | 4 +- .../markdown/features/cluster_commands.md | 2 +- .../markdown/features/configuration.md | 6 +- documentation/markdown/features/create.md | 10 +-- .../markdown/features/multi_cloud.md | 12 ++-- .../markdown/features/other_configurations.md | 4 +- documentation/markdown/features/version.md | 2 +- documentation/markdown/software/ansible.md | 4 +- documentation/markdown/software/nfs.md | 2 +- resources/bin/bibiplay | 2 +- resources/defaults/slurm/slurm.j2 | 6 +- .../bibigrid/files/slurm/create_server.py | 20 +++--- .../bibigrid/files/slurm/delete_server.py | 4 +- ...d-ip-routes.yml => 000-add-ip-routes.yaml} | 0 ...er.yml => 000-playbook-rights-server.yaml} | 0 .../tasks/{001-apt.yml => 001-apt.yaml} | 0 .../tasks/{001-yum.yml => 001-yum.yaml} | 0 ...reguard-vpn.yml => 002-wireguard-vpn.yaml} | 0 .../tasks/{003-dns.yml => 003-dns.yaml} | 0 ...update-hosts.yml => 004-update-hosts.yaml} | 0 .../{006-database.yml => 006-database.yaml} | 0 ...010-bin-server.yml => 010-bin-server.yaml} | 0 ...zabbix-agent.yml => 011-zabbix-agent.yaml} | 0 ...bbix-server.yml => 011-zabbix-server.yaml} | 0 ...unt.yml => 020-disk-server-automount.yaml} | 0 ...0-disk-server.yml => 020-disk-server.yaml} | 2 +- ...0-disk-worker.yml => 020-disk-worker.yaml} | 0 .../tasks/{020-disk.yml => 020-disk.yaml} | 0 ...025-nfs-server.yml => 025-nfs-server.yaml} | 0 ...025-nfs-worker.yml => 025-nfs-worker.yaml} | 0 .../tasks/{030-docker.yml => 030-docker.yaml} | 0 ...slurm-server.yml => 042-slurm-server.yaml} | 0 .../tasks/{042-slurm.yml => 042-slurm.yaml} | 0 .../tasks/{999-theia.yml => 999-theia.yaml} | 0 .../bibigrid/tasks/{main.yml => main.yaml} | 46 ++++++------- resources/playbook/tools/tee.py | 2 +- ...example.yml => bibigrid_test_example.yaml} | 0 tests/provider/test_provider.py | 2 +- tests/test_ansible_configurator.py | 44 ++++++------- tests/test_validate_schema.py | 2 +- 51 files changed, 155 insertions(+), 151 deletions(-) rename bibigrid.yml => bibigrid.yaml (97%) rename resources/playbook/roles/bibigrid/tasks/{000-add-ip-routes.yml => 000-add-ip-routes.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{000-playbook-rights-server.yml => 000-playbook-rights-server.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{001-apt.yml => 001-apt.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{001-yum.yml => 001-yum.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{002-wireguard-vpn.yml => 002-wireguard-vpn.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{003-dns.yml => 003-dns.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{004-update-hosts.yml => 004-update-hosts.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{006-database.yml => 006-database.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{010-bin-server.yml => 010-bin-server.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{011-zabbix-agent.yml => 011-zabbix-agent.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{011-zabbix-server.yml => 011-zabbix-server.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{020-disk-server-automount.yml => 020-disk-server-automount.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{020-disk-server.yml => 020-disk-server.yaml} (90%) rename resources/playbook/roles/bibigrid/tasks/{020-disk-worker.yml => 020-disk-worker.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{020-disk.yml => 020-disk.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{025-nfs-server.yml => 025-nfs-server.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{025-nfs-worker.yml => 025-nfs-worker.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{030-docker.yml => 030-docker.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{042-slurm-server.yml => 042-slurm-server.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{042-slurm.yml => 042-slurm.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{999-theia.yml => 999-theia.yaml} (100%) rename resources/playbook/roles/bibigrid/tasks/{main.yml => main.yaml} (77%) rename resources/tests/{bibigrid_test_example.yml => bibigrid_test_example.yaml} (100%) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 0b983ee1..5035d0d0 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -15,6 +15,6 @@ jobs: pip install -r requirements.txt pip install -r requirements-dev.txt - name: ansible_lint - run: ansible-lint resources/playbook/roles/bibigrid/tasks/main.yml + run: ansible-lint resources/playbook/roles/bibigrid/tasks/main.yaml - name: pylint_lint run: pylint bibigrid \ No newline at end of file diff --git a/.gitignore b/.gitignore index 742d8be5..d9f42342 100644 --- a/.gitignore +++ b/.gitignore @@ -5,12 +5,12 @@ # variable resources resources/playbook/ansible.cfg resources/playbook/roles/bibigrid/templates/slurm/slurm.j2 -resources/playbook/site.yml +resources/playbook/site.yaml resources/playbook/ansible_hosts resources/playbook/vars/ resources/playbook/host_vars/ resources/playbook/group_vars/ -resources/tests/bibigrid_test.yml +resources/tests/bibigrid_test.yaml # Roles resources/playbook/roles_galaxy/* diff --git a/README.md b/README.md index d3eba843..b31cb193 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # BiBiGrid -BiBiGrid is a cloud cluster creation and management framework for OpenStack -(and more providers in the future). +BiBiGrid is a framework for creating and managing cloud clusters, currently supporting OpenStack. +Future versions will support additional cloud providers. BiBiGrid uses Ansible to configure standard Ubuntu 20.04/22.04 LTS as well as Debian 11 cloud images. Depending on your configuration BiBiGrid @@ -26,22 +26,23 @@ might be just what you need. Brief, technical BiBiGrid overview ### How to configure a cluster? -#### Configuration File: bibigrid.yml -A [template](bibigrid.yml) file is included in the repository ([bibigrid.yml](bibigrid.yml)). +#### Configuration File: bibigrid.yaml +A [template](bibigrid.yaml) file is included in the repository ([bibigrid.yaml](bibigrid.yaml)). -The cluster configuration file consists of a list of configurations. Every configuration describes the provider specific configuration. -The first configuration additionally contains all the keys that apply to the entire cluster (roles for example). -Currently only clusters with one provider are possible, so focus only on the first configuration in the list. -The configuration template [bibigrid.yml](bibigrid.yml) contains many helpful comments, making completing it easier for you. +The cluster configuration file, `bibigrid.yaml`, consists of a list of configurations. +Each configuration describes provider-specific settings. +The first configuration in the list also contains keys that apply to the entire cluster (e.g., roles). + +The configuration template [bibigrid.yaml](bibigrid.yaml) contains many helpful comments, making completing it easier for you. [You need more details?](documentation/markdown/features/configuration.md) -#### Cloud Specification Data: clouds.yml +#### Cloud Specification Data: clouds.yaml To access the cloud, authentication information is required. You can download your `clouds.yaml` from OpenStack. -Your `clouds.yaml` is to be placed in `~/.config/bibigrid/` and will be loaded by BiBiGrid on execution. +Place the `clouds.yaml` file in the `~/.config/bibigrid/` directory. BiBiGrid will load this file during execution. [You need more details?](documentation/markdown/features/cloud_specification_data.md) @@ -50,11 +51,11 @@ If you haven't used BiBiGrid1 in the past or are unfamiliar with OpenStack, we h [tutorial](https://github.com/deNBI/bibigrid_clum2022) instead. #### Preparation -1. Download (or create) the `clouds.yaml` (and optionally `clouds-public.yaml`) file as described [above](#cloud-specification-data-cloudsyml). +1. Download (or create) your `clouds.yaml` file (and optionally `clouds-public.yaml`) as described [above](#cloud-specification-data-cloudsyaml). 2. Place the `clouds.yaml` into `~/.config/bibigrid` -3. Fill the configuration, `bibigrid.yml`, with your specifics. At least you need: A master instance with valid type and image, -a region, an availability zone, an sshUser (most likely ubuntu) and a subnet. -You probably also want at least one worker with a valid type, image and count. +3. Fill in the `bibigrid.yaml` configuration file with your specifics. At a minimum you need to specify: a master instance with valid type and image, +an sshUser (most likely ubuntu) and a subnet. +You will likely also want to specify at least one worker instance with a valid type, image, and count. 4. If your cloud provider runs post-launch services, you need to set the `waitForServices` key appropriately which expects a list of services to wait for. 5. Create a virtual environment from `bibigrid/requirements.txt`. @@ -62,16 +63,15 @@ See [here](https://www.akamai.com/blog/developers/how-building-virtual-python-en 6. Take a look at [First execution](#first-execution) #### First execution -Before follow the steps described at [Preparation](#preparation). +Before proceeding, ensure you have completed the steps described in the [Preparation section](#preparation). -After cloning the repository navigate to `bibigrid`. -In order to execute BiBiGrid source the virtual environment created during [preparation](#preparation). -Take a look at BiBiGrid's [Command Line Interface](documentation/markdown/features/CLI.md) -if you want to explore for yourself. +After cloning the repository, navigate to the bibigrid directory. +Source the virtual environment created during [preparation](#preparation) to execute BiBiGrid. +Refer to BiBiGrid's [Command Line Interface documentation](documentation/markdown/features/CLI.md) if you want to explore additional options. A first execution run through could be: -1. `./bibigrid.sh -i [path-to-bibigrid.yml] -ch`: checks the configuration -2. `./bibigrid.sh -i 'bibigrid.yml -i [path-to-bibigrid.yml] -c'`: creates the cluster (execute only if check was successful) +1. `./bibigrid.sh -i [path-to-bibigrid.yaml] -ch`: checks the configuration +2. `./bibigrid.sh -i 'bibigrid.yaml -i [path-to-bibigrid.yaml] -c'`: creates the cluster (execute only if check was successful) 3. Use **BiBiGrid's create output** to investigate the created cluster further. Especially connecting to the ide might be helpful. Otherwise, connect using ssh. 4. While in ssh try `sinfo` to printing node info @@ -85,17 +85,17 @@ Great! You've just started and terminated your first cluster using BiBiGrid!
### Troubleshooting -If your cluster doesn't start up, please first make sure your configurations file is valid (`-ch`). -If it is not, try to modify the configurations file to make it valid. Use `-v` or `-vv` to get a more verbose output, -so you can find the issue faster. Also double check if you have sufficient permissions to access the project. -If you can't make your configurations file valid, please contact a developer. -If that's the case, please contact a developer and/or manually check if your quotas are exceeded. -Some quotas can currently not be checked by bibigrid. +If your cluster doesn't start up, first ensure your configuration file is valid using the `-ch` option. +If the configuration is invalid, modify the file as needed. +Use the `-v` or `-vv` options for more verbose output to help identify the issue faster. +Also, double-check that you have sufficient permissions to access the project. +If you cannot make your configuration file valid, please contact a developer. +Additionally, manually check if your quotas are exceeded, as some quotas cannot currently be checked by BiBiGrid. **Whenever you contact a developer, please send your logfile along.** # Documentation -If you would like to learn more about BiBiGrid please follow a fitting link: +For more information about BiBiGrid, please visit the following links: - [BiBiGrid Features](documentation/markdown/bibigrid_feature_list.md) - [Software used by BiBiGrid](documentation/markdown/bibigrid_software_list.md) @@ -118,7 +118,9 @@ Workers are powered down once they are not used for a longer period. [https://github.com/BiBiServ/Development-Guidelines](https://github.com/BiBiServ/Development-Guidelines) ## On implementing concrete providers -New concrete providers can be implemented very easily. Just copy the `provider.py` file and implement all methods for -your cloud-provider. Also inherit from the `provider` class. After that add your provider to the providerHandler lists; giving it a associated name for the -configuration files. By that, your provider is automatically added to BiBiGrid's tests and regular execution. By testing -your provider first, you will see whether all provider methods are implemented as expected. \ No newline at end of file +Implementing new cloud providers is straightforward. +Copy the `provider.py` file and implement all necessary methods for your cloud provider. +Inherit from the `provider` class. +Add your provider to the `providerHandler` lists and assign it an associated name for the configuration files. +This will automatically include your provider in BiBiGrid's tests and regular execution. +Test your provider to ensure all methods are implemented correctly. diff --git a/bibigrid.yml b/bibigrid.yaml similarity index 97% rename from bibigrid.yml rename to bibigrid.yaml index 765cab6d..88c3f0e3 100644 --- a/bibigrid.yml +++ b/bibigrid.yaml @@ -102,6 +102,6 @@ # elastic_scheduling: # for large or slow clusters increasing these timeouts might be necessary to avoid failures # SuspendTimeout: 30 # after SuspendTimeout seconds, slurm allows to power up the node again - # ResumeTimeout: 900 # if a node doesn't start in ResumeTimeout seconds, the start is considered failed. + # ResumeTimeout: 1200 # if a node doesn't start in ResumeTimeout seconds, the start is considered failed. #- [next configurations] diff --git a/bibigrid/core/startup_rest.py b/bibigrid/core/startup_rest.py index ad737bc4..5d24957d 100644 --- a/bibigrid/core/startup_rest.py +++ b/bibigrid/core/startup_rest.py @@ -332,7 +332,7 @@ async def ready(cluster_id: str): def test_validate(): - with open('test.yml', 'rb') as file: + with open('test.yaml', 'rb') as file: response = client.post("/bibigrid/validate", files={"config_file": file}) assert response.status_code == 200 response_data = response.json() @@ -341,7 +341,7 @@ def test_validate(): def test_create(): - with open('test.yml', 'rb') as file: + with open('test.yaml', 'rb') as file: response = client.post("/bibigrid/create", files={"config_file": file}) assert response.status_code == 200 response_data = response.json() @@ -349,7 +349,7 @@ def test_create(): def test_terminate_cluster(): - with open('test.yml', 'rb') as file: + with open('test.yaml', 'rb') as file: response = client.post("/bibigrid/terminate", params={"cluster_id": "2uiy5ka2c5y1k8o"}, files={"config_file": file}) assert response.status_code == 200 diff --git a/bibigrid/core/utility/ansible_commands.py b/bibigrid/core/utility/ansible_commands.py index 84b68f72..1a6ad31d 100644 --- a/bibigrid/core/utility/ansible_commands.py +++ b/bibigrid/core/utility/ansible_commands.py @@ -30,7 +30,7 @@ # 'else echo"Ansible hosts not reachable. There seems to be a misconfiguration."; fi',"Check for ") # Run ansible-galaxy to install ansible-galaxy roles from galaxy, git or url (.tar.gz) -# GALAXY = f"ansible-galaxy install --roles-path {aRP.ADDITIONAL_ROLES_ROOT_PATH_REMOTE} -r {aRP.REQUIREMENTS_YML}" +# GALAXY = f"ansible-galaxy install --roles-path {aRP.ADDITIONAL_ROLES_ROOT_PATH_REMOTE} -r {aRP.REQUIREMENTS_YAML}" # Extract ansible roles from files (.tar.gz, .tgz) # EXTRACT = f"for f in $(find /tmp/roles -type f -regex '.*\\.t\\(ar\\.\\)?gz'); " \ @@ -49,7 +49,7 @@ "Adjust playbook home permission.") MV_ANSIBLE_CONFIG = ( "sudo install -D /opt/playbook/ansible.cfg /etc/ansible/ansible.cfg", "Move ansible configuration.") -EXECUTE = (f"ansible-playbook {os.path.join(a_rp.PLAYBOOK_PATH_REMOTE, a_rp.SITE_YML)} -i " +EXECUTE = (f"ansible-playbook {os.path.join(a_rp.PLAYBOOK_PATH_REMOTE, a_rp.SITE_YAML)} -i " f"{os.path.join(a_rp.PLAYBOOK_PATH_REMOTE, a_rp.ANSIBLE_HOSTS)} -l {{}}", "Execute ansible playbook. Be patient.") diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index aeba9ec9..d88592f8 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -21,7 +21,7 @@ VPNGTW_ROLES = [{"role": "bibigrid", "tags": ["bibigrid", "bibigrid-vpngtw"]}] MASTER_ROLES = [{"role": "bibigrid", "tags": ["bibigrid", "bibigrid-master"]}] WORKER_ROLES = [{"role": "bibigrid", "tags": ["bibigrid", "bibigrid-worker"]}] -VARS_FILES = [aRP.CONFIG_YML, aRP.HOSTS_YML] +VARS_FILES = [aRP.CONFIG_YAML, aRP.HOSTS_YAML] IDE_CONF = {"ide": False, "workspace": ide.DEFAULT_IDE_WORKSPACE, "port_start": ide.REMOTE_BIND_ADDRESS, "port_end": ide.DEFAULT_IDE_PORT_END, "build": False} ZABBIX_CONF = {"db": "zabbix", "db_user": "zabbix", "db_password": "zabbix", "timezone": "Europe/Berlin", diff --git a/bibigrid/core/utility/handler/configuration_handler.py b/bibigrid/core/utility/handler/configuration_handler.py index 20781458..608c8e42 100644 --- a/bibigrid/core/utility/handler/configuration_handler.py +++ b/bibigrid/core/utility/handler/configuration_handler.py @@ -16,7 +16,7 @@ CLOUD_CONFIGURATION_KEY = "cloud" -def read_configuration(log, path="bibigrid.yml", configuration_list=True): +def read_configuration(log, path, configuration_list=True): """ Reads yaml from file and returns configuration @param log: @@ -143,7 +143,7 @@ def get_cloud_specifications(configurations, log): @return: list of dicts: cloud_specifications of every configuration """ clouds, clouds_public = get_clouds_files(log) - log.debug("Loaded clouds.yml and clouds_public.yml") + log.debug("Loaded clouds.yaml and clouds_public.yaml") cloud_specifications = [] if isinstance(clouds, dict): for configuration in configurations: diff --git a/bibigrid/core/utility/paths/ansible_resources_path.py b/bibigrid/core/utility/paths/ansible_resources_path.py index 1f29dc25..68dba256 100644 --- a/bibigrid/core/utility/paths/ansible_resources_path.py +++ b/bibigrid/core/utility/paths/ansible_resources_path.py @@ -8,20 +8,20 @@ # UNIVERSAL ANSIBLE_HOSTS: str = "ansible_hosts" -COMMON_YML: str = "common.yml" -SITE_YML: str = "site.yml" -REQUIREMENTS_YML: str = "requirements.yml" +COMMON_YAML: str = "common.yaml" +SITE_YAML: str = "site.yaml" +REQUIREMENTS_YAML: str = "requirements.yaml" UPLOAD_PATH: str = "/tmp/roles/" VARS_PATH: str = "vars/" GROUP_VARS_PATH: str = "group_vars/" HOST_VARS_PATH: str = "host_vars/" ROLES_PATH: str = "roles/" -LOGIN_YML: str = VARS_PATH + "login.yml" -CONFIG_YML: str = VARS_PATH + "common_configuration.yml" -HOSTS_YML: str = VARS_PATH + "hosts.yml" -WORKER_SPECIFICATION_YML: str = VARS_PATH + "worker_specification.yml" +LOGIN_YAML: str = VARS_PATH + "login.yaml" +CONFIG_YAML: str = VARS_PATH + "common_configuration.yaml" +HOSTS_YAML: str = VARS_PATH + "hosts.yaml" +WORKER_SPECIFICATION_YAML: str = VARS_PATH + "worker_specification.yaml" ADDITIONAL_ROLES_PATH: str = ROLES_PATH + "additional/" -DEFAULT_IP_FILE = VARS_PATH + "{{ ansible_default_ipv4.address }}.yml" +DEFAULT_IP_FILE = VARS_PATH + "{{ ansible_default_ipv4.address }}.yaml" ANSIBLE_CFG = "ansible.cfg" SLURM_CONF = "slurm.conf" SLURM_J2 = "slurm.j2" @@ -30,14 +30,14 @@ PLAYBOOK = "playbook/" PLAYBOOK_PATH: str = os.path.join(b_p.RESOURCES_PATH, PLAYBOOK) ANSIBLE_CFG_PATH = os.path.join(PLAYBOOK_PATH, ANSIBLE_CFG) -HOSTS_FILE = os.path.join(PLAYBOOK_PATH, HOSTS_YML) +HOSTS_FILE = os.path.join(PLAYBOOK_PATH, HOSTS_YAML) HOSTS_CONFIG_FILE: str = os.path.join(PLAYBOOK_PATH, ANSIBLE_HOSTS) CONFIG_ROOT_PATH: str = os.path.join(PLAYBOOK_PATH, VARS_PATH) ROLES_ROOT_PATH: str = os.path.join(PLAYBOOK_PATH, ROLES_PATH) -COMMONS_LOGIN_FILE: str = os.path.join(PLAYBOOK_PATH, LOGIN_YML) -COMMONS_CONFIG_FILE: str = os.path.join(PLAYBOOK_PATH, CONFIG_YML) -SITE_CONFIG_FILE: str = os.path.join(PLAYBOOK_PATH, SITE_YML) -WORKER_SPECIFICATION_FILE: str = os.path.join(PLAYBOOK_PATH, WORKER_SPECIFICATION_YML) +COMMONS_LOGIN_FILE: str = os.path.join(PLAYBOOK_PATH, LOGIN_YAML) +COMMONS_CONFIG_FILE: str = os.path.join(PLAYBOOK_PATH, CONFIG_YAML) +SITE_CONFIG_FILE: str = os.path.join(PLAYBOOK_PATH, SITE_YAML) +WORKER_SPECIFICATION_FILE: str = os.path.join(PLAYBOOK_PATH, WORKER_SPECIFICATION_YAML) ADDITIONAL_ROLES_ROOT_PATH: str = ROLES_ROOT_PATH + ADDITIONAL_ROLES_PATH VARS_FOLDER = os.path.join(PLAYBOOK_PATH, VARS_PATH) GROUP_VARS_FOLDER = os.path.join(PLAYBOOK_PATH, GROUP_VARS_PATH) @@ -58,9 +58,9 @@ HOSTS_CONFIG_FILE_REMOTE: str = PLAYBOOK_PATH_REMOTE + ANSIBLE_HOSTS CONFIG_ROOT_PATH_REMOTE: str = PLAYBOOK_PATH_REMOTE + VARS_PATH ROLES_ROOT_PATH_REMOTE: str = PLAYBOOK_PATH_REMOTE + ROLES_PATH -COMMONS_LOGIN_FILE_REMOTE: str = PLAYBOOK_PATH_REMOTE + LOGIN_YML -COMMONS_CONFIG_FILE_REMOTE: str = PLAYBOOK_PATH_REMOTE + CONFIG_YML -SITE_CONFIG_FILE_REMOTE: str = PLAYBOOK_PATH_REMOTE + SITE_YML -WORKER_SPECIFICATION_FILE_REMOTE: str = PLAYBOOK_PATH_REMOTE + WORKER_SPECIFICATION_YML +COMMONS_LOGIN_FILE_REMOTE: str = PLAYBOOK_PATH_REMOTE + LOGIN_YAML +COMMONS_CONFIG_FILE_REMOTE: str = PLAYBOOK_PATH_REMOTE + CONFIG_YAML +SITE_CONFIG_FILE_REMOTE: str = PLAYBOOK_PATH_REMOTE + SITE_YAML +WORKER_SPECIFICATION_FILE_REMOTE: str = PLAYBOOK_PATH_REMOTE + WORKER_SPECIFICATION_YAML ADDITIONAL_ROLES_ROOT_PATH_REMOTE: str = ROLES_ROOT_PATH + ADDITIONAL_ROLES_PATH -REQUIREMENTS_CONFIG_FILE_REMOTE: str = ADDITIONAL_ROLES_ROOT_PATH_REMOTE + REQUIREMENTS_YML +REQUIREMENTS_CONFIG_FILE_REMOTE: str = ADDITIONAL_ROLES_ROOT_PATH_REMOTE + REQUIREMENTS_YAML diff --git a/bibigrid/core/utility/validate_schema.py b/bibigrid/core/utility/validate_schema.py index e563d1bc..dd22fe28 100644 --- a/bibigrid/core/utility/validate_schema.py +++ b/bibigrid/core/utility/validate_schema.py @@ -1,5 +1,5 @@ """ -Handles the schema validation for BiBiGrid's configuration yml. +Handles the schema validation for BiBiGrid's configuration yaml. """ from schema import Schema, Optional, Or, SchemaError diff --git a/documentation/markdown/features/CLI.md b/documentation/markdown/features/CLI.md index e21089d7..e19c67a5 100644 --- a/documentation/markdown/features/CLI.md +++ b/documentation/markdown/features/CLI.md @@ -5,7 +5,7 @@ Available command line parameters: - `-h, --help` show help message and exit - `-v, --verbose` Increases output verbosity (can be of great use when cluster fails to start). `-v` adds more detailed info to the logfile, `-vv` adds debug information to the logfile. -- `-d, --debug` Keeps cluster active in case of an error. Offers termination after successful create. +- `-d, --debug` Keeps cluster active in case of an error. Offers termination after successful create. Prints full stack trace on errors. - `-i , --config_input (required)` Path to YAML configurations file. Relative paths can be used and start at `~/.config/bibigrid` - `-cid , --cluster_id ` Cluster id is needed for ide and termination. If no cluster id is set, diff --git a/documentation/markdown/features/check.md b/documentation/markdown/features/check.md index 2f411d5d..b7b84239 100644 --- a/documentation/markdown/features/check.md +++ b/documentation/markdown/features/check.md @@ -1,5 +1,7 @@ # Check +Besides the in depth check which is explained below there is a schema check in [validate_schema.py](../../../bibigrid/core/utility/validate_schema.py). + ## Exactly one master or vpn instance per configuration There can only be a single master or a single vpn-gateway (vpngtw) per configuration. @@ -49,7 +51,7 @@ are whitelisted. This check will only print a warning, but will not fail the che If this check doesn't succeed, downloading the `clouds.yaml` again might be the fastest solution. You can read more about these files here [Cloud-specification](cloud_specification_data.md) -### Valid +### Clouds.yaml validity A `clouds.yaml` entry is considered valid if - combined with any `clouds-public.yaml` entries it refers to using the profile diff --git a/documentation/markdown/features/cluster_commands.md b/documentation/markdown/features/cluster_commands.md index be16a42c..ec2c3243 100644 --- a/documentation/markdown/features/cluster_commands.md +++ b/documentation/markdown/features/cluster_commands.md @@ -8,7 +8,7 @@ Similar to `sinfo` but shows detailed information regarding node features. Thereby, it helps you with understanding any worker startup issues. ## [bibiplay](../../../resources/bin/bibiplay) -`bibiplay` is mainly a shortcut for `ansible-playbook /opt/playbook/site.yml -i /opt/playbook/ansible_hosts` +`bibiplay` is mainly a shortcut for `ansible-playbook /opt/playbook/site.yaml -i /opt/playbook/ansible_hosts` which allows you to execute the ansible playbook more easily. ### Examples diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 0b86fcd2..c4987907 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -3,10 +3,10 @@ > **Note** > > First take a look at our [Hands-On BiBiGrid Tutorial](https://github.com/deNBI/bibigrid_clum2022) and our -> example [bibigrid.yml](../../../bibigrid.yml). +> example [bibigrid.yaml](../../../bibigrid.yaml). > This documentation is no replacement for those, but provides greater detail - too much detail for first time users. -The configuration file (often called `bibigrid.yml`) contains important information about cluster creation. +The configuration file (often called `bibigrid.yaml`) contains important information about cluster creation. The cluster configuration holds a list of configurations where each configuration has a specific cloud (location) and infrastructure (e.g. OpenStack). For single-cloud use cases you just need a single configuration. However, you can use additional configurations to set up a multi-cloud. @@ -167,7 +167,7 @@ If some nodes need to be active during the entire cluster lifetime, [onDemand](# ###### Defaults ```yaml slurmConf: - db: slurm # see task 042-slurm-server.yml + db: slurm # see task 042-slurm-server.yaml db_user: slurm db_password: changeme munge_key: # automatically generated via id_generation.generate_munge_key diff --git a/documentation/markdown/features/create.md b/documentation/markdown/features/create.md index 7eba57a4..2df8eddd 100644 --- a/documentation/markdown/features/create.md +++ b/documentation/markdown/features/create.md @@ -65,7 +65,7 @@ The [playbook](../../../resources/playbook) and [bin](../../../resources/bin) is The playbook is executed. Read more about the exact steps of execution [here](bibigrid_ansible_playbook.md). -## Prints Cluster Information +## Logs Cluster Information At the end the cluster information is printed: - cluster id @@ -76,12 +76,12 @@ At the end the cluster information is printed: - How to connect via IDE Port Forwarding (only if [ide](configuration.md#ide-optional)) - Duration -### Print Example +### Log Example ``` Cluster myclusterid with master 123.45.67.89 up and running! SSH: ssh -i '/home/user/.config/bibigrid/keys/tempKey_bibi-myclusterid' ubuntu@123.45.67.89 -Terminate cluster: ./bibigrid.sh -i '/home/xaver/.config/bibigrid/hybrid.yml' -t -cid myclusterid -Detailed cluster info: ./bibigrid.sh -i '/home/xaver/.config/bibigrid/hybrid.yml' -l -cid myclusterid -IDE Port Forwarding: ./bibigrid.sh -i '/home/xaver/.config/bibigrid/hybrid.yml' -ide -cid myclusterid +Terminate cluster: ./bibigrid.sh -i '/home/xaver/.config/bibigrid/hybrid.yaml' -t -cid myclusterid +Detailed cluster info: ./bibigrid.sh -i '/home/xaver/.config/bibigrid/hybrid.yaml' -l -cid myclusterid +IDE Port Forwarding: ./bibigrid.sh -i '/home/xaver/.config/bibigrid/hybrid.yaml' -ide -cid myclusterid --- 12 minutes and 0.9236352443695068 seconds --- ``` diff --git a/documentation/markdown/features/multi_cloud.md b/documentation/markdown/features/multi_cloud.md index 2d573e54..0449243e 100644 --- a/documentation/markdown/features/multi_cloud.md +++ b/documentation/markdown/features/multi_cloud.md @@ -15,7 +15,7 @@ What follows are implementation details that are not relevant for most users. DNS is provided by [dnsmasq](../software/dnsmasq.md). All instances are added whether they are started once (master, vpngtw) or on demand (workers). Explicitly, BiBiGrid manages adding workers to [dnsmasq](../software/dnsmasq.md) on creation triggered by [create_server](../../../resources/playbook/roles/bibigrid/files/slurm/create_server.py) and executed by ansible -by task [003-dns.yml](../../../resources/playbook/roles/bibigrid/tasks/003-dns.yml). +by task [003-dns.yaml](../../../resources/playbook/roles/bibigrid/tasks/003-dns.yaml). ## VPN - Wireguard [Wireguard](../software/wireguard.md) creates a VPN between all vpngtw and the master node. @@ -25,7 +25,7 @@ A single keypair (X25519 encrypted and base64 encoded) is [generated](../../../b creation and distributed via SSH to the master and every vpngtw. ### Interface -Using systemd-network a persistent wg0 interface is [created by ansible](../../../resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yml) +Using systemd-network a persistent wg0 interface is [created by ansible](../../../resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml) in order to enable Wireguard. ## Port Security @@ -36,18 +36,18 @@ While forwarding the MAC Address changes, but the IP remains the IP of the worke By adding `Allowed Address Pairs` OpenStack knows that it should allow those mismatch packages. These `Allowed Address Pairs` are added to the master and to every vpngtw. -For that Ansible creates [userdata](../../../resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml) +For that Ansible creates [userdata](../../../resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml) files that are later [injected](../../../resources/playbook/roles/bibigrid/files/slurm/create_server.py) into started worker instances by `create_server.py` triggered by slurm. ## MTU Probing -MTU Probing is necessary, because MTU might be different across networks. [Ansible handles that]([created by ansible](../../../resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yml). +MTU Probing is necessary, because MTU might be different across networks. [Ansible handles that]([created by ansible](../../../resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml). ## IP Routes In order to allow workers to communicate over the vpn, they need to know how to use it. -Therefore, IP routes are [set by ansible]([deactivated by ansible](../../../resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yml) +Therefore, IP routes are [set by ansible]([deactivated by ansible](../../../resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml) for the workers, telling them how to contact the master. ## Deactivating Netplan -Netplan is [deactivated by ansible](../../../resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yml) +Netplan is [deactivated by ansible](../../../resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml) in order to avoid set ip routes being overwritten. \ No newline at end of file diff --git a/documentation/markdown/features/other_configurations.md b/documentation/markdown/features/other_configurations.md index 0a92faa9..c6cc8c06 100644 --- a/documentation/markdown/features/other_configurations.md +++ b/documentation/markdown/features/other_configurations.md @@ -6,10 +6,10 @@ For that purpose we store defaults in `resources/defaults` and on the first run That way you can make changes and if something doesn't work, you can just delete the configuration to go back to our default one. -## slurm.cfg +## slurm.j2 The `slurm.j2` is not a static configuration file, but instead a [jinja](https://jinja.palletsprojects.com/en/3.1.x/) template for the actual configuration that is generated during runtime. That is necessary because it contains the actual instance names that are only known at runtime. -The jinja template is converted to the actual configuration by ansible in the `042-slurm.yml` task. +The jinja template is converted to the actual configuration by ansible in the `042-slurm.yaml` task. The `slurm.j2` also takes certain information from your BiBiGrid configuration (see [slurmConf](configuration.md#slurmconf-optional)). diff --git a/documentation/markdown/features/version.md b/documentation/markdown/features/version.md index c5ac0cd3..272658ed 100644 --- a/documentation/markdown/features/version.md +++ b/documentation/markdown/features/version.md @@ -1,3 +1,3 @@ # Version -Prints BiBiGrid's current version. \ No newline at end of file +Logs BiBiGrid's current version. \ No newline at end of file diff --git a/documentation/markdown/software/ansible.md b/documentation/markdown/software/ansible.md index dc73d8fb..133ffa4f 100644 --- a/documentation/markdown/software/ansible.md +++ b/documentation/markdown/software/ansible.md @@ -25,7 +25,7 @@ bibiplay is the same as: ```sh -ansible-playbook /opt/playbook/site.yml /opt/playbook/ansible_hosts/ +ansible-playbook /opt/playbook/site.yaml /opt/playbook/ansible_hosts/ ``` any additional arguments are passed to `ansible-playbook`: @@ -37,7 +37,7 @@ bibiplay -l master is the same as: ```sh -ansible-playbook /opt/playbook/site.yml /opt/playbook/ansible_hosts/ -l master +ansible-playbook /opt/playbook/site.yaml /opt/playbook/ansible_hosts/ -l master ``` ### Useful commands diff --git a/documentation/markdown/software/nfs.md b/documentation/markdown/software/nfs.md index ffdc6b40..5ceb85f1 100644 --- a/documentation/markdown/software/nfs.md +++ b/documentation/markdown/software/nfs.md @@ -35,7 +35,7 @@ By mounting a volume into a shared directory, volumes can be shared. Let's assume our configuration holds (among others) the keys: -```yml +```yaml nfs: True masterMounts: - testMount diff --git a/resources/bin/bibiplay b/resources/bin/bibiplay index 6b4d6148..19e7bccb 100644 --- a/resources/bin/bibiplay +++ b/resources/bin/bibiplay @@ -1,3 +1,3 @@ #!/bin/bash # allows for an easier execution of the ansible playbook no matter where you are -ansible-playbook /opt/playbook/site.yml -i /opt/playbook/ansible_hosts "$@" \ No newline at end of file +ansible-playbook /opt/playbook/site.yaml -i /opt/playbook/ansible_hosts "$@" \ No newline at end of file diff --git a/resources/defaults/slurm/slurm.j2 b/resources/defaults/slurm/slurm.j2 index 7def984f..c90a2fd6 100644 --- a/resources/defaults/slurm/slurm.j2 +++ b/resources/defaults/slurm/slurm.j2 @@ -96,12 +96,12 @@ JobSubmitPlugins=all_partitions # POWER /ELASTIC SCHEDULING ResumeProgram=/opt/slurm/create.sh -# Resume time is 15 minutes (900 seconds) +# Resume time's default is 20 minutes (1200 seconds) ResumeTimeout= {{ slurm_conf.elastic_scheduling.ResumeTimeout }} SuspendProgram=/opt/slurm/terminate.sh -# Suspend time is 15 minutes (900 seconds) +# Suspend time's default is 1 hour (3600 seconds) SuspendTime= {{ slurm_conf.elastic_scheduling.SuspendTime }} -# SuspendTimeout is 30 seconds +# SuspendTimeout's default is 30 seconds SuspendTimeout={{ slurm_conf.elastic_scheduling.SuspendTimeout }} # Excludes {{ hostvars[groups.master.0].name }} from suspend SuspendExcNodes={{ exclude_groups | join(',') }} diff --git a/resources/playbook/roles/bibigrid/files/slurm/create_server.py b/resources/playbook/roles/bibigrid/files/slurm/create_server.py index 0e8cd0da..a7d1ca7f 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/create_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/create_server.py @@ -27,7 +27,7 @@ class ImageNotFoundException(Exception): LOGGER_FORMAT = "%(asctime)s [%(levelname)s] %(message)s" logging.basicConfig(format=LOGGER_FORMAT, filename="/var/log/slurm/create_server.log", level=logging.INFO) -HOSTS_FILE_PATH = "/opt/playbook/vars/hosts.yml" +HOSTS_FILE_PATH = "/opt/playbook/vars/hosts.yaml" logging.info("create_server.py started") start_time = time.time() @@ -109,7 +109,7 @@ def start_server(worker, start_worker_group, start_data): except ConnectionError as exc: logging.warning(f"{exc}: Couldn't connect to {server.name}.") server_start_data["connection_exceptions"].append(server.name) - logging.info("Update hosts.yml") + logging.info("Update hosts.yaml") update_hosts(server.name, server.private_v4) except OpenStackCloudException as exc: @@ -148,13 +148,13 @@ def check_ssh_active(private_ip, private_key="/opt/slurm/.ssh/id_ecdsa", usernam def update_hosts(name, ip): # pylint: disable=invalid-name """ - Update hosts.yml + Update hosts.yaml @param name: hostname @param ip: ibibigrid-worker0-3k1eeysgetmg4vb-3p address @return: """ - logging.info("Updating hosts.yml") - with FileLock("hosts.yml.lock"): + logging.info("Updating hosts.yaml") + with FileLock("hosts.yaml.lock"): logging.info("Lock acquired") with open(HOSTS_FILE_PATH, mode="r", encoding="utf-8") as hosts_file: hosts = yaml.safe_load(hosts_file) @@ -166,7 +166,7 @@ def update_hosts(name, ip): # pylint: disable=invalid-name logging.info(f"Added host {name} with ip {hosts['host_entries'][name]}") with open(HOSTS_FILE_PATH, mode="w", encoding="utf-8") as hosts_file: yaml.dump(hosts, hosts_file) - logging.info("Wrote hosts file. Released hosts.yml.lock.") + logging.info("Wrote hosts file. Released hosts.yaml.lock.") def configure_dns(): @@ -175,7 +175,7 @@ def configure_dns(): @return: """ logging.info("configure_dns") - cmdline_args = ["/opt/playbook/site.yml", '-i', '/opt/playbook/ansible_hosts', '-l', "master", "-t", "dns"] + cmdline_args = ["/opt/playbook/site.yaml", '-i', '/opt/playbook/ansible_hosts', '-l', "master", "-t", "dns"] return _run_playbook(cmdline_args) @@ -186,7 +186,7 @@ def configure_worker(instances): @return: """ logging.info("configure_worker \nworker: %s", instances) - cmdline_args = ["/opt/playbook/site.yml", '-i', '/opt/playbook/ansible_hosts', '-l', instances] + cmdline_args = ["/opt/playbook/site.yaml", '-i', '/opt/playbook/ansible_hosts', '-l', instances] return _run_playbook(cmdline_args) @@ -212,7 +212,7 @@ def _run_playbook(cmdline_args): GROUP_VARS_PATH = "/opt/playbook/group_vars" worker_groups = [] for filename in os.listdir(GROUP_VARS_PATH): - if filename != "master.yml": + if filename != "master.yaml": worker_group_yaml_file = os.path.join(GROUP_VARS_PATH, filename) # checking if it is a file if os.path.isfile(worker_group_yaml_file): @@ -220,7 +220,7 @@ def _run_playbook(cmdline_args): worker_groups.append(yaml.safe_load(worker_group_yaml)) # read common configuration -with open("/opt/playbook/vars/common_configuration.yml", mode="r", encoding="utf-8") as common_configuration_file: +with open("/opt/playbook/vars/common_configuration.yaml", mode="r", encoding="utf-8") as common_configuration_file: common_config = yaml.safe_load(common_configuration_file) logging.info(f"Maximum 'is active' attempts: {common_config['cloud_scheduling']['sshTimeout']}") # read clouds.yaml diff --git a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py index 53741a99..60966e3b 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py @@ -33,7 +33,7 @@ GROUP_VARS_PATH = "/opt/playbook/group_vars" worker_groups = [] for filename in os.listdir(GROUP_VARS_PATH): - if filename != "master.yml": + if filename != "master.yaml": f = os.path.join(GROUP_VARS_PATH, filename) # checking if it is a file if os.path.isfile(f): @@ -41,7 +41,7 @@ worker_groups.append(yaml.safe_load(worker_group)) # read common configuration -with open("/opt/playbook/vars/common_configuration.yml", mode="r", encoding="utf-8") as common_configuration_file: +with open("/opt/playbook/vars/common_configuration.yaml", mode="r", encoding="utf-8") as common_configuration_file: common_config = yaml.safe_load(common_configuration_file) # read clouds.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yml b/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yml rename to resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/000-playbook-rights-server.yml b/resources/playbook/roles/bibigrid/tasks/000-playbook-rights-server.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/000-playbook-rights-server.yml rename to resources/playbook/roles/bibigrid/tasks/000-playbook-rights-server.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/001-apt.yml b/resources/playbook/roles/bibigrid/tasks/001-apt.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/001-apt.yml rename to resources/playbook/roles/bibigrid/tasks/001-apt.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/001-yum.yml b/resources/playbook/roles/bibigrid/tasks/001-yum.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/001-yum.yml rename to resources/playbook/roles/bibigrid/tasks/001-yum.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yml b/resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yml rename to resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/003-dns.yml b/resources/playbook/roles/bibigrid/tasks/003-dns.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/003-dns.yml rename to resources/playbook/roles/bibigrid/tasks/003-dns.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/004-update-hosts.yml b/resources/playbook/roles/bibigrid/tasks/004-update-hosts.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/004-update-hosts.yml rename to resources/playbook/roles/bibigrid/tasks/004-update-hosts.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/006-database.yml b/resources/playbook/roles/bibigrid/tasks/006-database.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/006-database.yml rename to resources/playbook/roles/bibigrid/tasks/006-database.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/010-bin-server.yml b/resources/playbook/roles/bibigrid/tasks/010-bin-server.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/010-bin-server.yml rename to resources/playbook/roles/bibigrid/tasks/010-bin-server.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yml b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yml rename to resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yml b/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yml rename to resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yml rename to resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yaml similarity index 90% rename from resources/playbook/roles/bibigrid/tasks/020-disk-server.yml rename to resources/playbook/roles/bibigrid/tasks/020-disk-server.yaml index 9659583c..efaa9569 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yaml @@ -19,5 +19,5 @@ - name: Automount when: volumes is defined - include_tasks: 020-disk-server-automount.yml + include_tasks: 020-disk-server-automount.yaml with_items: "{{ volumes }}" diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-worker.yml b/resources/playbook/roles/bibigrid/tasks/020-disk-worker.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/020-disk-worker.yml rename to resources/playbook/roles/bibigrid/tasks/020-disk-worker.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk.yml b/resources/playbook/roles/bibigrid/tasks/020-disk.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/020-disk.yml rename to resources/playbook/roles/bibigrid/tasks/020-disk.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yml b/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/025-nfs-server.yml rename to resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yml b/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yml rename to resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/030-docker.yml b/resources/playbook/roles/bibigrid/tasks/030-docker.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/030-docker.yml rename to resources/playbook/roles/bibigrid/tasks/030-docker.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/042-slurm-server.yml rename to resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm.yml b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/042-slurm.yml rename to resources/playbook/roles/bibigrid/tasks/042-slurm.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/999-theia.yml b/resources/playbook/roles/bibigrid/tasks/999-theia.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/999-theia.yml rename to resources/playbook/roles/bibigrid/tasks/999-theia.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/main.yml b/resources/playbook/roles/bibigrid/tasks/main.yaml similarity index 77% rename from resources/playbook/roles/bibigrid/tasks/main.yml rename to resources/playbook/roles/bibigrid/tasks/main.yaml index 1a6713f7..2616a980 100644 --- a/resources/playbook/roles/bibigrid/tasks/main.yml +++ b/resources/playbook/roles/bibigrid/tasks/main.yaml @@ -16,27 +16,27 @@ - name: Setup persistent ip routes when: "'workers' in group_names and wireguard_common is defined" block: - - name: Running 000-add-ip-routes.yml + - name: Running 000-add-ip-routes.yaml debug: msg: "[BIBIGRID] Adding ip routes" - - import_tasks: 000-add-ip-routes.yml + - import_tasks: 000-add-ip-routes.yaml - name: Setup common software and dependencies - server rights when: "'master' in group_names" block: - - name: Running 000-playbook-rights-server.yml + - name: Running 000-playbook-rights-server.yaml debug: msg: "[BIBIGRID] Update permissions" - - import_tasks: 000-playbook-rights-server.yml + - import_tasks: 000-playbook-rights-server.yaml - name: Setup common software and dependencies for Debian when: "ansible_distribution_file_variety == 'Debian'" tags: ["pkg"] block: - - name: Running 001-apt.yml + - name: Running 001-apt.yaml debug: msg: "[BIBIGRID] Setup common software and dependencies" - - import_tasks: 001-apt.yml + - import_tasks: 001-apt.yaml - name: Setup common software and dependencies for RedHat when: "ansible_distribution_file_variety == 'RedHat'" @@ -44,7 +44,7 @@ block: - debug: msg: "[BIBIGRID] Setup common software and dependencies" - - import_tasks: 001-yum.yml + - import_tasks: 001-yum.yaml - name: Configure Wireguard for VPNs when: "'vpn' in group_names and wireguard is defined" @@ -52,7 +52,7 @@ block: - debug: msg: "[BIBIGRID] Configure Wireguard for VPNs" - - import_tasks: 002-wireguard-vpn.yml + - import_tasks: 002-wireguard-vpn.yaml - name: Setup DNS when: "'master' in group_names" @@ -60,7 +60,7 @@ block: - debug: msg: "[BIBIGRID] Setup DNS" - - import_tasks: 003-dns.yml + - import_tasks: 003-dns.yaml - name: Update host file on worker when: "'workers' in group_names" @@ -68,7 +68,7 @@ block: - debug: msg: "[BIBIGRID] Update hosts" - - import_tasks: 004-update-hosts.yml + - import_tasks: 004-update-hosts.yaml - name: Configure database when: "'master' in group_names" @@ -76,7 +76,7 @@ block: - debug: msg: "[BIBIGRID] Configure database" - - import_tasks: 006-database.yml + - import_tasks: 006-database.yaml - name: Setup additional binary executables /usr/local/bin/ when: @@ -85,7 +85,7 @@ block: - debug: msg: "[BIBIGRID] Setup additional binary executables /usr/local/bin/" - - import_tasks: 010-bin-server.yml + - import_tasks: 010-bin-server.yaml - name: Setup Zabbix Agent when: @@ -94,21 +94,21 @@ block: - debug: msg: "[BIBIGRID] Setup Zabbix Agent" - - import_tasks: 011-zabbix-server.yml + - import_tasks: 011-zabbix-server.yaml when: "'master' in group_names" - - import_tasks: 011-zabbix-agent.yml + - import_tasks: 011-zabbix-agent.yaml - debug: msg: "[BIBIGRID] Generate directory structure available on all hosts" - name: Generate general directory structure available on all hosts - import_tasks: 020-disk.yml + import_tasks: 020-disk.yaml tags: ["disk"] - name: Generate server directory structure available on all hosts - import_tasks: 020-disk-server.yml + import_tasks: 020-disk-server.yaml when: "'master' in group_names" tags: ["disk"] - name: Generate worker directory structure available on all hosts - import_tasks: 020-disk-worker.yml + import_tasks: 020-disk-worker.yaml when: "'master' not in group_names" tags: ["disk"] @@ -119,15 +119,15 @@ block: - debug: msg: "[BIBIGRID] Setup NFS" - - import_tasks: 025-nfs-server.yml + - import_tasks: 025-nfs-server.yaml when: "'master' in group_names" - - import_tasks: 025-nfs-worker.yml + - import_tasks: 025-nfs-worker.yaml when: "'master' not in group_names" - name: Setup Docker debug: msg: "[BIBIGRID] Setup Docker" -- import_tasks: 030-docker.yml +- import_tasks: 030-docker.yaml tags: ["docker"] - name: Setup Slurm @@ -135,9 +135,9 @@ block: - debug: msg: "[BIBIGRID] Setup Slurm" - - import_tasks: 042-slurm.yml + - import_tasks: 042-slurm.yaml when: "'vpngtw' not in group_names" - - import_tasks: 042-slurm-server.yml + - import_tasks: 042-slurm-server.yaml when: "'master' in group_names" - name: Setup Theia @@ -148,4 +148,4 @@ block: - debug: msg: "[BIBIGRID] Setup Theia" - - import_tasks: 999-theia.yml + - import_tasks: 999-theia.yaml diff --git a/resources/playbook/tools/tee.py b/resources/playbook/tools/tee.py index 665cbafa..9a6ec569 100644 --- a/resources/playbook/tools/tee.py +++ b/resources/playbook/tools/tee.py @@ -11,7 +11,7 @@ parser = ArgumentParser(description='Tee like program, that executes given cmd and branch stdout and stderr\n.' 'Exits with the exit code of called program.') -parser.add_argument("--cmd", help="A cmd executed by tee.py, e.g. --cmd \"ansible-playbook -i hosts site.yml\"", +parser.add_argument("--cmd", help="A cmd executed by tee.py, e.g. --cmd \"ansible-playbook -i hosts site.yaml\"", required=True) parser.add_argument("--outfile", help="Path to stdoutfile", required=True) args = parser.parse_args() diff --git a/resources/tests/bibigrid_test_example.yml b/resources/tests/bibigrid_test_example.yaml similarity index 100% rename from resources/tests/bibigrid_test_example.yml rename to resources/tests/bibigrid_test_example.yaml diff --git a/tests/provider/test_provider.py b/tests/provider/test_provider.py index 56108d79..ffeec438 100644 --- a/tests/provider/test_provider.py +++ b/tests/provider/test_provider.py @@ -72,7 +72,7 @@ CONFIGURATIONS = configuration_handler.read_configuration(logging, os.path.join(bP.ROOT_PATH, - "resources/tests/bibigrid_test.yml")) + "resources/tests/bibigrid_test.yaml")) PROVIDERS = provider_handler.get_providers(CONFIGURATIONS, logging) diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index a23818b4..0d7abb96 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -20,79 +20,79 @@ class TestAnsibleConfigurator(TestCase): def test_generate_site_file_yaml_empty(self): site_yaml = [{'become': 'yes', 'hosts': 'master', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}, {'become': 'yes', 'hosts': 'vpngtw', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}, {'become': 'yes', 'hosts': 'workers', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}] self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml([])) def test_generate_site_file_yaml_master_role(self): user_roles = [{'hosts': ['master'], 'roles': [{'name': 'resistance_nextflow', 'tags': ['rn']}]}] - # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] + # vars_files = ['vars/login.yaml', 'vars/common_configuration.yaml', 'varsFile'] site_yaml = [{'become': 'yes', 'hosts': 'master', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}, {'role': 'resistance_nextflow', 'tags': ['rn']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}, {'become': 'yes', 'hosts': 'vpngtw', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}, {'become': 'yes', 'hosts': 'workers', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}] self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(user_roles)) def test_generate_site_file_yaml_vpngtw_role(self): user_roles = [{'hosts': ['vpngtw'], 'roles': [{'name': 'resistance_nextflow'}], 'varsFiles': ['vars/rn']}] - # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] + # vars_files = ['vars/login.yaml', 'vars/common_configuration.yaml', 'varsFile'] site_yaml = [{'become': 'yes', 'hosts': 'master', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}, {'become': 'yes', 'hosts': 'vpngtw', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}, {'role': 'resistance_nextflow', 'tags': []}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'vars/rn']}, + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml', 'vars/rn']}, {'become': 'yes', 'hosts': 'workers', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}] self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(user_roles)) def test_generate_site_file_yaml_workers_role(self): user_roles = [{'hosts': ['workers'], 'roles': [{'name': 'resistance_nextflow'}]}] - # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] + # vars_files = ['vars/login.yaml', 'vars/common_configuration.yaml', 'varsFile'] site_yaml = [{'become': 'yes', 'hosts': 'master', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}, {'become': 'yes', 'hosts': 'vpngtw', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}, + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}, {'become': 'yes', 'hosts': 'workers', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}, {'role': 'resistance_nextflow', 'tags': []}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml']}] + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml']}] self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(user_roles)) def test_generate_site_file_yaml_all_role(self): user_roles = [ {'hosts': ['master', 'vpngtw', 'workers'], 'roles': [{'name': 'resistance_nextflow', 'tags': ['rn']}], 'varsFiles': ['vars/rn']}] - # vars_files = ['vars/login.yml', 'vars/common_configuration.yml', 'varsFile'] + # vars_files = ['vars/login.yaml', 'vars/common_configuration.yaml', 'varsFile'] site_yaml = [{'become': 'yes', 'hosts': 'master', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-master']}, {'role': 'resistance_nextflow', 'tags': ['rn']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'vars/rn']}, + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml', 'vars/rn']}, {'become': 'yes', 'hosts': 'vpngtw', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-vpngtw']}, {'role': 'resistance_nextflow', 'tags': ['rn']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'vars/rn']}, + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml', 'vars/rn']}, {'become': 'yes', 'hosts': 'workers', 'roles': [{'role': 'bibigrid', 'tags': ['bibigrid', 'bibigrid-worker']}, {'role': 'resistance_nextflow', 'tags': ['rn']}], - 'vars_files': ['vars/common_configuration.yml', 'vars/hosts.yml', 'vars/rn']}] + 'vars_files': ['vars/common_configuration.yaml', 'vars/hosts.yaml', 'vars/rn']}] self.assertEqual(site_yaml, ansible_configurator.generate_site_file_yaml(user_roles)) - + def test_generate_common_configuration_false(self): cidrs = "42" cluster_id = "21" @@ -351,8 +351,8 @@ def test_write_yaml_alias(self, mock_yaml): @patch("bibigrid.core.utility.ansible_configurator.generate_site_file_yaml") @patch("bibigrid.core.utility.ansible_configurator.write_yaml") @patch("bibigrid.core.utility.ansible_configurator.get_cidrs") - def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_hosts, mock_list, - mock_common, mock_worker, mock_write): + def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_hosts, mock_list, mock_common, + mock_worker, mock_write): mock_cidrs.return_value = 421 mock_list.return_value = {2: 422} provider = MagicMock() diff --git a/tests/test_validate_schema.py b/tests/test_validate_schema.py index df8e94a1..ef4d6c73 100644 --- a/tests/test_validate_schema.py +++ b/tests/test_validate_schema.py @@ -20,7 +20,7 @@ class TestValidateSchema(TestCase): """ def test_validate_configurations(self): - for bibigrid_configuration_name in glob.iglob(f'{TEST_CONFIGURATION_DIRECTORY}/*.yml'): + for bibigrid_configuration_name in glob.iglob(f'{TEST_CONFIGURATION_DIRECTORY}/*.yaml'): with open(bibigrid_configuration_name, mode="r", encoding="utf-8") as config_file: config_yaml = yaml.safe_load(config_file) for cloud_config in config_yaml: From c1862239b24ab3af9c35db09337af78ee6f88040 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 4 Jul 2024 15:44:16 +0200 Subject: [PATCH 113/145] updated timeouts. updated tests --- bibigrid.yaml | 2 +- bibigrid/core/utility/ansible_configurator.py | 2 +- .../markdown/features/configuration.md | 4 +- resources/defaults/slurm/slurm.j2 | 2 +- tests/test_ansible_configurator.py | 44 +++++++++---------- tests/test_create.py | 4 +- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/bibigrid.yaml b/bibigrid.yaml index 88c3f0e3..8982a810 100644 --- a/bibigrid.yaml +++ b/bibigrid.yaml @@ -101,7 +101,7 @@ #features: # list # elastic_scheduling: # for large or slow clusters increasing these timeouts might be necessary to avoid failures - # SuspendTimeout: 30 # after SuspendTimeout seconds, slurm allows to power up the node again + # SuspendTimeout: 60 # after SuspendTimeout seconds, slurm allows to power up the node again # ResumeTimeout: 1200 # if a node doesn't start in ResumeTimeout seconds, the start is considered failed. #- [next configurations] diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index d88592f8..875ea00c 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -28,7 +28,7 @@ "server_name": "bibigrid", "admin_password": "bibigrid"} SLURM_CONF = {"db": "slurm", "db_user": "slurm", "db_password": "changeme", "munge_key": id_generation.generate_munge_key(), - "elastic_scheduling": {"SuspendTime": 3600, "ResumeTimeout": 1200, "SuspendTimeout": 30, + "elastic_scheduling": {"SuspendTime": 3600, "ResumeTimeout": 1200, "SuspendTimeout": 60, "TreeWidth": 128}} CLOUD_SCHEDULING = {"sshTimeout": 5} diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index c4987907..60345c0c 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -156,7 +156,7 @@ For nearly all cases the default value is what you need. Default is `True`. ```yaml elastic_scheduling: SuspendTime: 1800 - SuspendTimeout: 60 + SuspendTimeout: 90 ResumeTimeout: 1800 ``` @@ -173,7 +173,7 @@ slurmConf: munge_key: # automatically generated via id_generation.generate_munge_key elastic_scheduling: SuspendTime: 900 # if a node is not used for SuspendTime seconds, it will shut down - SuspendTimeout: 30 # after SuspendTimeout seconds, slurm allows to power up the powered down node again + SuspendTimeout: 60 # after SuspendTimeout seconds, slurm allows to power up the powered down node again ResumeTimeout: 900 # if a node doesn't start in ResumeTimeout seconds, the start is considered failed. See https://slurm.schedmd.com/slurm.conf.html#OPT_ResumeProgram TreeWidth: 128 # https://slurm.schedmd.com/slurm.conf.html#OPT_TreeWidth ``` diff --git a/resources/defaults/slurm/slurm.j2 b/resources/defaults/slurm/slurm.j2 index c90a2fd6..abd8078b 100644 --- a/resources/defaults/slurm/slurm.j2 +++ b/resources/defaults/slurm/slurm.j2 @@ -101,7 +101,7 @@ ResumeTimeout= {{ slurm_conf.elastic_scheduling.ResumeTimeout }} SuspendProgram=/opt/slurm/terminate.sh # Suspend time's default is 1 hour (3600 seconds) SuspendTime= {{ slurm_conf.elastic_scheduling.SuspendTime }} -# SuspendTimeout's default is 30 seconds +# SuspendTimeout's default is 90 seconds SuspendTimeout={{ slurm_conf.elastic_scheduling.SuspendTimeout }} # Excludes {{ hostvars[groups.master.0].name }} from suspend SuspendExcNodes={{ exclude_groups | join(',') }} diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index 53bcd47e..4b465e65 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -99,14 +99,14 @@ def test_generate_common_configuration_false(self): default_user = "ubuntu" ssh_user = "test" configuration = [{}] - common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 5}, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, 'local_dns_lookup': False, 'local_fs': False, 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', - 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, - 'TreeWidth': 128}, + 'elastic_scheduling': {'ResumeTimeout': 1200, 'SuspendTime': 3600, + 'SuspendTimeout': 60, 'TreeWidth': 128}, 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, @@ -124,7 +124,7 @@ def test_generate_common_configuration_true(self): ssh_user = "test" configuration = [ {elem: "True" for elem in ["localFS", "localDNSlookup", "useMasterAsCompute", "slurm", "zabbix", "ide"]}] - common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 5}, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'True', 'enable_nfs': False, 'enable_slurm': 'True', 'enable_zabbix': 'True', @@ -132,8 +132,8 @@ def test_generate_common_configuration_true(self): 'workspace': '${HOME}'}, 'local_dns_lookup': 'True', 'local_fs': 'True', 'slurm': 'True', 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', - 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, - 'TreeWidth': 128}, + 'elastic_scheduling': {'ResumeTimeout': 1200, 'SuspendTime': 3600, + 'SuspendTimeout': 60, 'TreeWidth': 128}, 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': 'True', 'zabbix_conf': {'admin_password': 'bibigrid', 'db': 'zabbix', @@ -152,16 +152,16 @@ def test_generate_common_configuration_nfs_shares(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 5}, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, - 'nfs_mounts': [{'dst': '//vil/mil', 'src': '//vil/mil'}, - {'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, + 'nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}, + {'dst': '/vol/spool', 'src': '/vol/spool'}], 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', - 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, - 'TreeWidth': 128}, + 'elastic_scheduling': {'ResumeTimeout': 1200, 'SuspendTime': 3600, + 'SuspendTimeout': 60, 'TreeWidth': 128}, 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, @@ -177,15 +177,15 @@ def test_generate_common_configuration_nfs(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 5}, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, - 'nfs_mounts': [{'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, + 'nfs_mounts': [{'dst': '/vol/spool', 'src': '/vol/spool'}], 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', - 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, - 'TreeWidth': 128}, + 'elastic_scheduling': {'ResumeTimeout': 1200, 'SuspendTime': 3600, + 'SuspendTimeout': 60, 'TreeWidth': 128}, 'munge_key': 'TO_BE_FILLED'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, @@ -201,16 +201,16 @@ def test_generate_common_configuration_ext_nfs_shares(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 5}, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}], 'local_dns_lookup': False, 'local_fs': False, - 'nfs_mounts': [{'dst': '//vol/spool', 'src': '//vol/spool'}], 'slurm': True, + 'nfs_mounts': [{'dst': '/vol/spool', 'src': '/vol/spool'}], 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', - 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, - 'TreeWidth': 128}, + 'elastic_scheduling': {'ResumeTimeout': 1200, 'SuspendTime': 3600, + 'SuspendTimeout': 60, 'TreeWidth': 128}, 'munge_key': 'YryJVnqgg24Ksf8zXQtbct3nuXrMSi9N'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, @@ -226,7 +226,7 @@ def test_generate_common_configuration_ide(self): cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 4}, + common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 5}, 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'Some1', 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, @@ -234,8 +234,8 @@ def test_generate_common_configuration_ide(self): 'port_start': 8181, 'workspace': '${HOME}'}, 'local_dns_lookup': False, 'local_fs': False, 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', - 'elastic_scheduling': {'ResumeTimeout': 900, 'SuspendTime': 3600, - 'TreeWidth': 128}, + 'elastic_scheduling': {'ResumeTimeout': 1200, 'SuspendTime': 3600, + 'SuspendTimeout': 60, 'TreeWidth': 128}, 'munge_key': 'b7nks3Ur3kanyPAEBxfSC9ypfSHFnWJL'}, 'ssh_user': ssh_user, 'use_master_as_compute': True} generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, diff --git a/tests/test_create.py b/tests/test_create.py index 612681d2..63dd119d 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -123,7 +123,7 @@ def test_initialize_master(self, mock_execute_ssh): 'username': creator.ssh_user, 'commands': creator.ssh_add_public_key_commands + ssh_handler.ANSIBLE_SETUP, 'filepaths': [(create.KEY_FOLDER + creator.key_name, '.ssh/id_ecdsa')], - 'gateway': {}, 'timeout': 4} + 'gateway': {}, 'timeout': 5} mock_execute_ssh.assert_called_with(ssh_data, startup.LOG) def test_prepare_volumes_none(self): @@ -211,7 +211,7 @@ def test_upload_playbooks(self, mock_execute_ssh, mock_ac_ssh, mock_configure_an 'username': creator.ssh_user, 'commands': [mock_ac_ssh()] + ssh_handler.ANSIBLE_START, 'filepaths': create.FILEPATHS, - 'gateway': {}, 'timeout': 4} + 'gateway': {}, 'timeout': 5} mock_execute_ssh.assert_called_with(ssh_data=ssh_data, log=startup.LOG) @patch.object(create.Create, "generate_keypair") From 1861480a4dbfb5ffebb381fee2556e05a1581f67 Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Fri, 5 Jul 2024 12:29:53 +0200 Subject: [PATCH 114/145] fixes #394 - remove host from zabbix when terminated --- .../bibigrid/files/slurm/delete_server.py | 29 +++++++++++++++++-- .../bibigrid/tasks/042-slurm-server.yaml | 1 + 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py index 60966e3b..b74754c3 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py @@ -14,6 +14,8 @@ import os_client_config import yaml +from pyzabbix import ZabbixAPI + LOGGER_FORMAT = "%(asctime)s [%(levelname)s] %(message)s" logging.basicConfig(format=LOGGER_FORMAT, filename="/var/log/slurm/delete_server.log", level=logging.INFO) @@ -23,11 +25,15 @@ logging.info(f"Terminate parameter: {sys.argv[1]}") if len(sys.argv) < 2: - logging.warning("usage: $0 instance1_name[,instance2_name,...]") + logging.warning("usage: $0 instance1_name[(,\\n)instance2_name,...]") logging.info("Your input %s with length %s", sys.argv, len(sys.argv)) sys.exit(1) -terminate_workers = sys.argv[1].split("\n") +separator = ',' +if '\n' in sys.argv[1]: + separator = '\n' + +terminate_workers = sys.argv[1].split(separator) logging.info("Deleting instances %s", terminate_workers) GROUP_VARS_PATH = "/opt/playbook/group_vars" @@ -65,6 +71,25 @@ else: logging.info(f"Deleted {terminate_worker}") +# ------------------------------- +# Remove hosts from Zabbix +# ------------------------------- + +# connect to Zabbix API +zapi = ZabbixAPI(server='http://localhost/zabbix') +# authenticate +zapi.login("Admin",common_config["zabbix_conf"]["admin_password"]) +# iterate over terminate_workers list +for terminate_worker in terminate_workers: + # get list of hosts that matches the hostname + hosts = zapi.host.get(output=["hostid","name"],filter={"name": terminate_worker}) + if not hosts: + logging.warning(f"Can't remove host '{terminate_worker}' from Zabbix: Host doesn't exist.") + else: + # remove host from Zabbix + zapi.host.delete(hosts[0]["hostid"]) + logging.info(f"Remove host '{terminate_worker}' from Zabbix.") + logging.info(f"Successful delete_server.py execution ({sys.argv[1]})!") time_in_s = time.time() - start_time logging.info("--- %s minutes and %s seconds ---", math.floor(time_in_s / 60), time_in_s % 60) diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml index 8abd5d61..7ee24efc 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml @@ -156,6 +156,7 @@ - filelock - paramiko - ansible-runner + - pyzabbix - name: 'Add default user to ansible group' user: From 2d3d9a9ae999060478910a34744e8e6fd359d070 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 5 Jul 2024 21:18:58 +0200 Subject: [PATCH 115/145] zabbix api no longer used when not set in configuration --- .../bibigrid/files/slurm/delete_server.py | 50 ++++++++++------- .../bibigrid/tasks/011-zabbix-agent.yaml | 55 ++++++++++--------- 2 files changed, 60 insertions(+), 45 deletions(-) diff --git a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py index b74754c3..c9677393 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py @@ -12,9 +12,9 @@ import time import os_client_config +import requests import yaml - -from pyzabbix import ZabbixAPI +from pyzabbix import ZabbixAPI, ZabbixAPIException LOGGER_FORMAT = "%(asctime)s [%(levelname)s] %(message)s" logging.basicConfig(format=LOGGER_FORMAT, filename="/var/log/slurm/delete_server.log", level=logging.INFO) @@ -29,11 +29,11 @@ logging.info("Your input %s with length %s", sys.argv, len(sys.argv)) sys.exit(1) -separator = ',' +SEPERATOR = ',' if '\n' in sys.argv[1]: - separator = '\n' + SEPERATOR = '\n' -terminate_workers = sys.argv[1].split(separator) +terminate_workers = sys.argv[1].split(SEPERATOR) logging.info("Deleting instances %s", terminate_workers) GROUP_VARS_PATH = "/opt/playbook/group_vars" @@ -61,8 +61,8 @@ for worker_group in worker_groups: for terminate_worker in terminate_workers: # terminate all servers that are part of the current worker group - result = subprocess.run(["scontrol", "show", "hostname", worker_group["name"]], - stdout=subprocess.PIPE, check=True) # get all workers in worker_type + result = subprocess.run(["scontrol", "show", "hostname", worker_group["name"]], stdout=subprocess.PIPE, + check=True) # get all workers in worker_type possible_workers = result.stdout.decode("utf-8").strip().split("\n") if terminate_worker in possible_workers: result = connections[worker_group["cloud_identifier"]].delete_server(terminate_worker) @@ -76,19 +76,29 @@ # ------------------------------- # connect to Zabbix API -zapi = ZabbixAPI(server='http://localhost/zabbix') -# authenticate -zapi.login("Admin",common_config["zabbix_conf"]["admin_password"]) -# iterate over terminate_workers list -for terminate_worker in terminate_workers: - # get list of hosts that matches the hostname - hosts = zapi.host.get(output=["hostid","name"],filter={"name": terminate_worker}) - if not hosts: - logging.warning(f"Can't remove host '{terminate_worker}' from Zabbix: Host doesn't exist.") - else: - # remove host from Zabbix - zapi.host.delete(hosts[0]["hostid"]) - logging.info(f"Remove host '{terminate_worker}' from Zabbix.") +if common_config["enable_zabbix"]: + try: + # Connect to Zabbix API + zapi = ZabbixAPI(server='http://localhost/zabbix') + + # Authenticate + zapi.login("Admin", common_config["zabbix_conf"]["admin_password"]) + + # Iterate over terminate_workers list + for terminate_worker in terminate_workers: + try: + # Get list of hosts that matches the hostname + hosts = zapi.host.get(output=["hostid", "name"], filter={"name": terminate_worker}) + if not hosts: + logging.warning(f"Can't remove host '{terminate_worker}' from Zabbix: Host doesn't exist.") + else: + # Remove host from Zabbix + zapi.host.delete(hosts[0]["hostid"]) + logging.info(f"Removed host '{terminate_worker}' from Zabbix.") + except ZabbixAPIException as e: + logging.error(f"Error while handling host '{terminate_worker}': {e}") + except requests.exceptions.RequestException as e: + logging.error(f"Cannot connect to Zabbix server: {e}") logging.info(f"Successful delete_server.py execution ({sys.argv[1]})!") time_in_s = time.time() - start_time diff --git a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml index 890aaf33..a7e2e1af 100644 --- a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml +++ b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml @@ -1,18 +1,11 @@ -- name: Install zabbix python-api - pip: - name: zabbix-api - -- name: Install zabbix agent - apt: - name: zabbix-agent - state: present - when: "ansible_distribution_file_variety == 'Debian'" - -- name: Install zabbix agent - dnf: - name: zabbix-agent - state: present - when: "ansible_distribution_file_variety == 'RedHat'" +- name: Ensure zabbix user exists + when: "'master' not in group_names" + user: + name: zabbix + comment: "Zabbix Monitoring User" + home: /var/lib/zabbix + shell: /usr/sbin/nologin + createhome: no - name: Create zabbix_agent dropin directory file: @@ -35,22 +28,34 @@ mode: 0644 notify: zabbix-agent -- name: Start and Enable zabbix-agent - systemd: - name: zabbix-agent - state: started - enabled: true - -- name: Install zabbix python-api - pip: - name: zabbix-api - - name: Copy Zabbix Host delete script copy: src: zabbix/zabbix_host_delete.py dest: /usr/local/bin/zabbix_host_delete.py mode: 0755 +- name: Install zabbix python-api + pip: + name: zabbix-api + +- name: Install zabbix agent + apt: + name: zabbix-agent + state: present + when: "ansible_distribution_file_variety == 'Debian'" + +- name: Install zabbix agent + dnf: + name: zabbix-agent + state: present + when: "ansible_distribution_file_variety == 'RedHat'" + +- name: Start and Enable zabbix-agent + systemd: + name: zabbix-agent + state: started + enabled: true + # -------------------------------------- # -- Add worker node as zabbix hosts -- # -------------------------------------- From 44cb5801e06f28d18ccdf5a31dd8fbf0395387dd Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 5 Jul 2024 21:24:26 +0200 Subject: [PATCH 116/145] pleased linting by using false instead of no --- resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml index a7e2e1af..9a658689 100644 --- a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml +++ b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml @@ -5,7 +5,7 @@ comment: "Zabbix Monitoring User" home: /var/lib/zabbix shell: /usr/sbin/nologin - createhome: no + create_home: false - name: Create zabbix_agent dropin directory file: From 90fbb7c9561b438480a00d578b06fb02b568fbc3 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 14 Aug 2024 12:41:30 +0200 Subject: [PATCH 117/145] added logging of traceroute even if debug flag is not set when error is not known. Added a few other logs --- bibigrid/core/actions/create.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index bffb9835..84e2ba36 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -190,7 +190,7 @@ def start_vpn_or_master(self, configuration, provider): name = identifier(cluster_id=self.cluster_id, # pylint: disable=redundant-keyword-arg additional=self.vpn_counter) # pylint: disable=redundant-keyword-arg self.vpn_counter += 1 - self.log.info(f"Starting instance/server {name} on {provider.cloud_specification['identifier']}") + self.log.info(f"Starting server {name} on {provider.cloud_specification['identifier']}") flavor = instance_type["type"] network = configuration["network"] image = image_selection.select_image(provider, instance_type["image"], self.log, @@ -223,10 +223,11 @@ def start_vpn_or_master(self, configuration, provider): mount = next((mount for mount in configuration["masterMounts"] if mount["name"] == volume["name"]), None) if mount.get("mountPoint"): volume["mount_point"] = mount["mountPoint"] + self.log.debug(f"Added mount point {mount['mountPoint']} as a mount point in configuration.") def start_workers(self, worker, worker_count, configuration, provider): name = WORKER_IDENTIFIER(cluster_id=self.cluster_id, additional=worker_count) - self.log.info(f"Starting worker {name} on {provider.cloud_specification['identifier']}.") + self.log.info(f"Starting server {name} on {provider.cloud_specification['identifier']}.") flavor = worker["type"] network = configuration["network"] image = image_selection.select_image(provider, worker["image"], self.log, @@ -245,6 +246,7 @@ def start_workers(self, worker, worker_count, configuration, provider): hosts = {"host_entries": {}} hosts["host_entries"][name] = server["private_v4"] ansible_configurator.write_yaml(a_rp.HOSTS_FILE, hosts, self.log) + self.log.debug(f"Added worker {name} to hosts file {a_rp.HOSTS_FILE}.") def prepare_vpn_or_master_args(self, configuration, provider): """ @@ -481,8 +483,7 @@ def create(self): # pylint: disable=too-many-branches,too-many-statements self.log.error(traceback.format_exc()) self.log.error(f"Configuration invalid: {str(exc)}") except Exception as exc: # pylint: disable=broad-except - if self.debug: - self.log.error(traceback.format_exc()) + self.log.error(traceback.format_exc()) self.log.error(f"Unexpected error: '{str(exc)}' ({type(exc)}) Contact a developer!)") else: return 0 # will be called if no exception occurred From 8dbab38e82e6917e3fc8b009f17fbd3f860b0cb7 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:11:25 +0200 Subject: [PATCH 118/145] Update action 515 (#516) * configuration update possible 515 * added experimental * fixed indentation * fixed missing newline at EOF. Summarized restarts. * added check for running workers * fixed multiple workers due to faulty update * updated tests and removed done todos * updated documentation * removed print --- bibigrid.yaml | 20 +--------- bibigrid/core/actions/create.py | 18 ++++++--- bibigrid/core/actions/list_clusters.py | 10 ++--- bibigrid/core/actions/terminate.py | 1 - bibigrid/core/actions/update.py | 40 ++++++++++++------- bibigrid/core/provider.py | 15 +++++-- bibigrid/core/startup.py | 7 +++- .../core/utility/command_line_interpreter.py | 2 +- bibigrid/openstack/openstack_provider.py | 8 ++++ documentation/markdown/features/update.md | 26 ++++++++++-- resources/defaults/slurm/slurm.j2 | 1 - .../bibigrid/tasks/042-slurm-server.yaml | 18 +++++---- .../roles/bibigrid/tasks/042-slurm.yaml | 17 +++----- tests/provider/test_provider.py | 25 ++++++++---- tests/test_create.py | 12 +++--- 15 files changed, 130 insertions(+), 90 deletions(-) diff --git a/bibigrid.yaml b/bibigrid.yaml index 8982a810..ea5d2d73 100644 --- a/bibigrid.yaml +++ b/bibigrid.yaml @@ -9,7 +9,7 @@ # -- BEGIN: GENERAL CLUSTER INFORMATION -- # sshTimeout: 5 # number of attempts to connect to instances during startup with delay in between # cloudScheduling: - # sshTimeout: 42 # like sshTimeout but during the on demand scheduling on the running cluster + # sshTimeout: 5 # like sshTimeout but during the on demand scheduling on the running cluster ## sshPublicKeyFiles listed here will be added to access the cluster. A temporary key is created by bibigrid itself. #sshPublicKeyFiles: @@ -72,24 +72,6 @@ # Depends on cloud image sshUser: # for example ubuntu - # Depends on cloud site: - # Berlin : regionOne - # Bielefeld : bielefeld - # DKFZ : regionOne - # Giessen : RegionOne - # Heidelberg : RegionOne - # Tuebingen : RegionOne - region: Bielefeld - - # Depends on cloud site: - # Berlin : nova - # Bielefeld : default - # DKFZ : nova - # Giessen : nova - # Heidelberg : nova - # Tuebingen : nova - availabilityZone: default - # Depends on cloud site and project subnet: # existing subnet on your cloud. See https://openstack.cebitec.uni-bielefeld.de/project/networks/ # or network: diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 84e2ba36..0c8d904e 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -283,7 +283,6 @@ def initialize_instances(self): wait_for_services_commands = [ (wait_for_service_command.format(service=service), wait_for_service_message.format(service=service)) for service in configuration.get("waitForServices", [])] - print(wait_for_services_commands) ssh_data["commands"] = ( wait_for_services_commands + self.ssh_add_public_key_commands + ssh_handler.ANSIBLE_SETUP) ssh_data["filepaths"] = [(ssh_data["private_key"], ssh_handler.PRIVATE_KEY_FILE)] @@ -342,12 +341,12 @@ def prepare_configurations(self): configuration["subnet"]] configuration["sshUser"] = self.ssh_user # is used in ansibleConfigurator - def upload_data(self): + def upload_data(self, private_key, clean_playbook=False): """ Configures ansible and then uploads the modified files and all necessary data to the master @return: """ - self.log.debug("Uploading ansible Data") + self.log.debug("Running upload_data") if not os.path.isfile(a_rp.HOSTS_FILE): with open(a_rp.HOSTS_FILE, 'a', encoding='utf-8') as hosts_file: hosts_file.write("# placeholder file for worker DNS entries (see 003-dns)") @@ -362,7 +361,14 @@ def upload_data(self): self.log.debug(f"Starting playbook with {ansible_start}.") commands = [ssh_handler.get_ac_command(self.providers, AC_NAME.format( cluster_id=self.cluster_id))] + ssh_handler.ANSIBLE_START - ssh_data = {"floating_ip": self.master_ip, "private_key": KEY_FOLDER + self.key_name, "username": self.ssh_user, + if clean_playbook: + self.log.info("Cleaning Playbook") + ssh_data = {"floating_ip": self.master_ip, "private_key": private_key, "username": self.ssh_user, + "commands": [("rm -rf ~/playbook/*", "Remove Playbook")], "filepaths": [], + "gateway": self.configurations[0].get("gateway", {}), "timeout": self.ssh_timeout} + ssh_handler.execute_ssh(ssh_data=ssh_data, log=self.log) + self.log.info("Uploading Data") + ssh_data = {"floating_ip": self.master_ip, "private_key": private_key, "username": self.ssh_user, "commands": commands, "filepaths": FILEPATHS, "gateway": self.configurations[0].get("gateway", {}), "timeout": self.ssh_timeout} ssh_handler.execute_ssh(ssh_data=ssh_data, log=self.log) @@ -372,6 +378,7 @@ def start_start_server_threads(self): Starts for each provider a start_instances thread and joins them. @return: """ + self.log.debug("Running start_start_server_threads") start_server_threads = [] worker_count = 0 ansible_configurator.write_yaml(a_rp.HOSTS_FILE, {"host_entries": {}}, self.log) @@ -399,6 +406,7 @@ def extended_network_configuration(self): Configure master/vpn-worker network for a multi/hybrid cloud @return: """ + self.log.debug("Running extended_network_configuration") if len(self.providers) == 1: return @@ -441,7 +449,7 @@ def create(self): # pylint: disable=too-many-branches,too-many-statements self.start_start_server_threads() self.extended_network_configuration() self.initialize_instances() - self.upload_data() + self.upload_data(os.path.join(KEY_FOLDER, self.key_name)) self.log_cluster_start_info() if self.configurations[0].get("deleteTmpKeypairAfter"): for provider in self.providers: diff --git a/bibigrid/core/actions/list_clusters.py b/bibigrid/core/actions/list_clusters.py index e6e3cf03..1f07f95d 100644 --- a/bibigrid/core/actions/list_clusters.py +++ b/bibigrid/core/actions/list_clusters.py @@ -146,12 +146,12 @@ def get_master_access_ip(cluster_id, master_provider, log): @param log: @return: public ip of master """ + # TODO: maybe move the method from list_clusters as it is now independent of list_clusters log.info("Finding master ip for cluster %s...", cluster_id) - servers = master_provider.list_servers() - for server in servers: - master = create.MASTER_IDENTIFIER(cluster_id=cluster_id) - if server["name"].startswith(master): - return server.get("public_v4") or server.get("public_v6") or server.get("private_v4") + master = create.MASTER_IDENTIFIER(cluster_id=cluster_id) + server = master_provider.get_server(master) + if server: + return server.get("public_v4") or server.get("public_v6") or server.get("private_v4") log.warning("Cluster %s not found on master_provider %s.", cluster_id, master_provider.cloud_specification["identifier"]) return None diff --git a/bibigrid/core/actions/terminate.py b/bibigrid/core/actions/terminate.py index 1452cb99..9a56bd3c 100644 --- a/bibigrid/core/actions/terminate.py +++ b/bibigrid/core/actions/terminate.py @@ -148,7 +148,6 @@ def delete_security_groups(provider, cluster_id, security_groups, log, timeout=5 tmp_success = False while not tmp_success: try: - # TODO: Check if security group exists at all not_found = not provider.get_security_group(security_group_name) tmp_success = provider.delete_security_group(security_group_name) except ConflictException: diff --git a/bibigrid/core/actions/update.py b/bibigrid/core/actions/update.py index efe9aaf7..dc9f5e42 100644 --- a/bibigrid/core/actions/update.py +++ b/bibigrid/core/actions/update.py @@ -2,24 +2,34 @@ Module that contains methods to update the master playbook """ -from bibigrid.core.utility import ansible_commands as a_c -from bibigrid.core.utility.handler import ssh_handler -from bibigrid.core.utility.paths import ansible_resources_path as a_rp -from bibigrid.core.utility.paths import bin_path +from bibigrid.core.actions import create +from bibigrid.core.actions.list_clusters import dict_clusters from bibigrid.core.utility.handler import cluster_ssh_handler -def update(cluster_id, master_provider, master_configuration, log): - log.info("Starting update...") - master_ip, ssh_user, used_private_key = cluster_ssh_handler.get_ssh_connection_info(cluster_id, master_provider, - master_configuration, log) +def update(creator, log): + log.info(f"Starting update for cluster {creator.cluster_id}...") + master_ip, ssh_user, used_private_key = cluster_ssh_handler.get_ssh_connection_info(creator.cluster_id, + creator.providers[0], + creator.configurations[0], log) + log.info(f"Trying to update {master_ip}@{ssh_user} with key {used_private_key}") + cluster_dict = dict_clusters(creator.providers, log) + if cluster_dict[creator.cluster_id]["workers"]: + workers = [worker['name'] for worker in cluster_dict[creator.cluster_id]["workers"]] + log.warning(f"There are still workers up! {workers}") + return 1 if master_ip and ssh_user and used_private_key: - log.info("Trying to update %s@%s", master_ip, ssh_user) - ssh_handler.execute_ssh(floating_ip=master_ip, private_key=used_private_key, username=ssh_user, - log=log, - gateway=master_configuration.get("gateway", {}), - commands=[a_c.EXECUTE], - filepaths=[(a_rp.PLAYBOOK_PATH, a_rp.PLAYBOOK_PATH_REMOTE), - (bin_path.BIN_PATH, bin_path.BIN_PATH_REMOTE)]) + master = create.MASTER_IDENTIFIER(cluster_id=creator.cluster_id) + server = creator.providers[0].get_server(master) + creator.master_ip = master_ip + creator.configurations[0]["private_v4"] = server["private_v4"] + creator.configurations[0]["floating_ip"] = master_ip + # TODO Test Volumes + creator.configurations[0]["volumes"] = server["volumes"] + creator.prepare_configurations() + log.log(42, f"Uploading data and executing BiBiGrid's Ansible playbook to {creator.cluster_id}") + creator.upload_data(used_private_key, clean_playbook=True) + log.log(42, f"Successfully updated cluster {creator.cluster_id}") return 0 + log.warning("One or more among master_ip, ssh_user and used_private_key are none. Aborting...") return 1 diff --git a/bibigrid/core/provider.py b/bibigrid/core/provider.py index 9e06dbd0..fb359e98 100644 --- a/bibigrid/core/provider.py +++ b/bibigrid/core/provider.py @@ -88,8 +88,8 @@ def list_servers(self): """ @abstractmethod - def create_server(self, name, flavor, image, network, key_name=None, wait=True, - volumes=None, security_groups=None): # pylint: disable=too-many-arguments + def create_server(self, name, flavor, image, network, key_name=None, wait=True, volumes=None, + security_groups=None): # pylint: disable=too-many-arguments """ Creates a new server and waits for it to be accessible if wait=True. If volumes are given, they are attached. Returns said server (dict) @@ -223,8 +223,8 @@ def get_active_images(self): return [image["name"] for image in self.get_images() if image["status"].lower() == "active"] def get_active_flavors(self): - return [flavor["name"] for flavor in self.get_flavors() - if "legacy" not in flavor["name"].lower() and "deprecated" not in flavor["name"].lower()] + return [flavor["name"] for flavor in self.get_flavors() if + "legacy" not in flavor["name"].lower() and "deprecated" not in flavor["name"].lower()] @abstractmethod def set_allowed_addresses(self, id_or_ip, allowed_address_pairs): @@ -273,6 +273,13 @@ def get_security_group(self, name_or_id): @return: """ + def get_server(self, name_or_id): + """ + Returns server if found else None. + @param name_or_id: + @return: + """ # TODO Test + def get_mount_info_from_server(self, server): volumes = [] for server_volume in server["volumes"]: diff --git a/bibigrid/core/startup.py b/bibigrid/core/startup.py index 9caac7cb..8c726cb6 100755 --- a/bibigrid/core/startup.py +++ b/bibigrid/core/startup.py @@ -82,7 +82,7 @@ def run_action(args, configurations, config_path): creator = create.Create(providers=providers, configurations=configurations, log=LOG, debug=args.debug, config_path=config_path) LOG.log(42, "Creating a new cluster takes about 10 or more minutes depending on your cloud provider " - "and your configuration. Please be patient.") + "and your configuration. Please be patient.") exit_state = creator.create() else: if not args.cluster_id: @@ -99,7 +99,10 @@ def run_action(args, configurations, config_path): exit_state = ide.ide(args.cluster_id, providers[0], configurations[0], LOG) elif args.update: LOG.info("Action update selected") - exit_state = update.update(args.cluster_id, providers[0], configurations[0], LOG) + creator = create.Create(providers=providers, configurations=configurations, log=LOG, + debug=args.debug, + config_path=config_path, cluster_id=args.cluster_id) + exit_state = update.update(creator, LOG) for provider in providers: provider.close() else: diff --git a/bibigrid/core/utility/command_line_interpreter.py b/bibigrid/core/utility/command_line_interpreter.py index 98ed1a35..44275eb5 100644 --- a/bibigrid/core/utility/command_line_interpreter.py +++ b/bibigrid/core/utility/command_line_interpreter.py @@ -57,7 +57,7 @@ def interpret_command_line(): help="Establishes a secure connection to ide. Needs cluster-id set") actions.add_argument("-u", "--update", action='store_true', help="Updates master's playbook. " "Needs cluster-id set, no jobs running " - "and no workers up") + "and all workers down (experimental)") args = parser.parse_args() needs_config = args.terminate or args.create or args.list or args.check or args.ide if needs_config and not args.config_input: diff --git a/bibigrid/openstack/openstack_provider.py b/bibigrid/openstack/openstack_provider.py index 75de1c2c..5fb3bacd 100644 --- a/bibigrid/openstack/openstack_provider.py +++ b/bibigrid/openstack/openstack_provider.py @@ -328,3 +328,11 @@ def get_security_group(self, name_or_id): @return: """ return self.conn.get_security_group(name_or_id) + + def get_server(self, name_or_id): + """ + Returns server if found else None. + @param name_or_id: + @return: + """ + return self.conn.get_server(name_or_id) diff --git a/documentation/markdown/features/update.md b/documentation/markdown/features/update.md index 40ea97dd..c1033854 100644 --- a/documentation/markdown/features/update.md +++ b/documentation/markdown/features/update.md @@ -1,5 +1,25 @@ # Update +This feature is experimental -Updates ansible-playbook and nothing else. You cannot declare new instances or anything. -Only relevant if a fix or a new feature is added to the ansible-playbook. -In the future we will try to further enhance this feature. \ No newline at end of file +Update re-uploads the playbook, updates the configuration data and executes the playbook again. + +Updating the configuration data does not allow for all kinds of updates, because some changes - +like attaching volumes, would need an undo process which is not implemented. That might come in a future version. +Therefore, some keys mentioned below in [updatable](#updatable) have "(activate)" behind them. +Those keys should not be deactivated, but only activated in updates. + +**Configuration keys not listed below are considered not updatable.** + +## Updatable +- Ansible playbook + + +- workerInstances +- useMasterAsCompute +- userRoles +- cloudScheduling +- waitForServices +- features +- ide (activate) +- nfsShares (activate) +- zabbix (activate) \ No newline at end of file diff --git a/resources/defaults/slurm/slurm.j2 b/resources/defaults/slurm/slurm.j2 index abd8078b..037daf80 100644 --- a/resources/defaults/slurm/slurm.j2 +++ b/resources/defaults/slurm/slurm.j2 @@ -76,7 +76,6 @@ SlurmdLogFile=/var/log/slurm/slurmd.log {% endif %} {% set _ = node_groups.append(node.name) %} {% set mem = (node.flavor.ram // 1024) * 1000 %} -# {{ node }} NodeName={{ node.name }} SocketsPerBoard={{ node.flavor.vcpus }} CoresPerSocket=1 RealMemory={{ mem - [mem // 2, 16000] | min }} State={{node.state }} {{"Features=" + (node.features | join(",")) if node.features is defined }}# {{ node.cloud_identifier }} {% for partition in node.partitions %} {% if partition not in partitions %} diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml index 7ee24efc..e3a28ac6 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml @@ -19,9 +19,6 @@ owner: slurm group: root mode: "0600" - notify: - - slurmdbd - - slurmctld - name: Generate random JWT Secret command: @@ -42,8 +39,6 @@ owner: root group: root mode: "0644" - notify: - - slurmrestd - name: Create system overrides directories (slurmdbdm slurmrestd) file: @@ -66,9 +61,6 @@ with_items: - slurmdbd - slurmrestd - notify: - - slurmdbd - - slurmrestd - name: Register Slurm users home dir shell: "set -o pipefail && grep slurm /etc/passwd | cut -d ':' -f 6" @@ -220,3 +212,13 @@ - slurmd - slurmdbd - slurmrestd + +- name: Restart Slurm services + systemd: + name: "{{ item }}" + state: restarted + loop: + - slurmdbd + - slurmrestd + - slurmctld + - slurmd diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml index a4d47c59..d80fb67c 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml @@ -71,9 +71,6 @@ with_items: - slurmd - slurmctld - notify: - - slurmd - - slurmctld - name: Enable slurmctld and slurmd services systemd: @@ -93,9 +90,6 @@ owner: slurm group: root mode: 0444 - notify: - - slurmctld - - slurmd - name: Create Job Container configuration template: @@ -104,9 +98,6 @@ owner: slurm group: root mode: 0444 - notify: - - slurmctld - - slurmd - name: Slurm cgroup configuration copy: @@ -115,6 +106,8 @@ owner: slurm group: root mode: 0444 - notify: - - slurmctld - - slurmd + +- name: Restart slurmd + systemd: + name: slurmd + state: restarted diff --git a/tests/provider/test_provider.py b/tests/provider/test_provider.py index ffeec438..d6ba89c2 100644 --- a/tests/provider/test_provider.py +++ b/tests/provider/test_provider.py @@ -6,9 +6,9 @@ import os import unittest +import bibigrid.core.utility.paths.basic_path as bP from bibigrid.core import startup from bibigrid.core.utility import image_selection -import bibigrid.core.utility.paths.basic_path as bP from bibigrid.core.utility.handler import configuration_handler from bibigrid.core.utility.handler import provider_handler from bibigrid.models.exceptions import ExecutionException @@ -70,9 +70,8 @@ "MFbUTTukAiDf4jAgvJkg7ayE0MPapGpI/OhSK2gyN45VAzs2m7uykun87B491JagZ57qr16vt8vxGYpFCEe8QqAcrUszUPqyPrb0auA8bz" \ "jO8S41Kx8FfG+7eTu4dQ0= user" -CONFIGURATIONS = configuration_handler.read_configuration(logging, - os.path.join(bP.ROOT_PATH, - "resources/tests/bibigrid_test.yaml")) +CONFIGURATIONS = configuration_handler.read_configuration(logging, os.path.join(bP.ROOT_PATH, + "resources/tests/bibigrid_test.yaml")) PROVIDERS = provider_handler.get_providers(CONFIGURATIONS, logging) @@ -160,12 +159,15 @@ def test_active_server_methods(self): floating_ip = provider.attach_available_floating_ip( provider.get_external_network(configuration["network"]), provider_server) server_list = provider.list_servers() + get_server = provider.get_server("bibigrid_test_server") self.assertEqual(SERVER_KEYS, set(provider_server.keys())) self.assertEqual("bibigrid_test_keypair", provider_server["key_name"]) self.assertEqual(FLOATING_IP_KEYS, set(floating_ip.keys())) - self.assertTrue([server for server in server_list if - server["name"] == "bibigrid_test_server" and server[ - "public_v4"] == floating_ip.floating_ip_address]) + list_server = next(server for server in server_list if + server["name"] == "bibigrid_test_server" and server[ + "public_v4"] == floating_ip.floating_ip_address) + self.assertEqual("bibigrid_test_server", get_server["name"]) + self.assertEqual(get_server, list_server) provider.delete_keypair("bibigrid_test_keypair") def test_get_external_network(self): @@ -226,6 +228,15 @@ def test_get_image_mismatch(self): with self.subTest(provider.NAME): self.assertIsNone(provider.get_image_by_id_or_name("NONE")) + # TODO test_get_images + # TODO test_get_flavors + # TODO test_set_allowed_addresses + # TODO test_get_server + # TODO test_get_security_group + # TODO test_create_security_group + # TODO append_rules_to_security_group + # TODO test_delete_security_group + if CONFIGURATIONS[0].get("snapshotImage"): def test_get_snapshot(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): diff --git a/tests/test_create.py b/tests/test_create.py index 63dd119d..5eb60901 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -1,6 +1,7 @@ """ Module to test create """ +import os from unittest import TestCase from unittest.mock import patch, MagicMock, mock_open @@ -122,8 +123,7 @@ def test_initialize_master(self, mock_execute_ssh): ssh_data = {'floating_ip': floating_ip, 'private_key': create.KEY_FOLDER + creator.key_name, 'username': creator.ssh_user, 'commands': creator.ssh_add_public_key_commands + ssh_handler.ANSIBLE_SETUP, - 'filepaths': [(create.KEY_FOLDER + creator.key_name, '.ssh/id_ecdsa')], - 'gateway': {}, 'timeout': 5} + 'filepaths': [(create.KEY_FOLDER + creator.key_name, '.ssh/id_ecdsa')], 'gateway': {}, 'timeout': 5} mock_execute_ssh.assert_called_with(ssh_data, startup.LOG) def test_prepare_volumes_none(self): @@ -204,14 +204,12 @@ def test_upload_playbooks(self, mock_execute_ssh, mock_ac_ssh, mock_configure_an configuration = {} creator = create.Create([provider], [configuration], "", startup.LOG) creator.master_ip = 42 - creator.upload_data() + creator.upload_data(os.path.join(create.KEY_FOLDER, creator.key_name)) mock_configure_ansible.assert_called_with(providers=creator.providers, configurations=creator.configurations, cluster_id=creator.cluster_id, log=startup.LOG) ssh_data = {'floating_ip': creator.master_ip, 'private_key': create.KEY_FOLDER + creator.key_name, - 'username': creator.ssh_user, - 'commands': [mock_ac_ssh()] + ssh_handler.ANSIBLE_START, - 'filepaths': create.FILEPATHS, - 'gateway': {}, 'timeout': 5} + 'username': creator.ssh_user, 'commands': [mock_ac_ssh()] + ssh_handler.ANSIBLE_START, + 'filepaths': create.FILEPATHS, 'gateway': {}, 'timeout': 5} mock_execute_ssh.assert_called_with(ssh_data=ssh_data, log=startup.LOG) @patch.object(create.Create, "generate_keypair") From 51e9e2ed21dc1fa75eba7032489fed1bf670f4b9 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:12:28 +0200 Subject: [PATCH 119/145] Added apt-reactivate-auto-update to reactivate updates at the end of the playbook run (#518) * changed theia to 900. Added apt-reactivate-auto-update as new 999. * added new line at end of file * changed list representation --- .../bibigrid/files/enable-auto-upgrades.conf | 4 ++ .../tasks/{999-theia.yaml => 900-theia.yaml} | 0 .../tasks/999-apt-reactivate-auto-update.yaml | 7 +++ .../playbook/roles/bibigrid/tasks/main.yaml | 57 +++++++++++++------ 4 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 resources/playbook/roles/bibigrid/files/enable-auto-upgrades.conf rename resources/playbook/roles/bibigrid/tasks/{999-theia.yaml => 900-theia.yaml} (100%) create mode 100644 resources/playbook/roles/bibigrid/tasks/999-apt-reactivate-auto-update.yaml diff --git a/resources/playbook/roles/bibigrid/files/enable-auto-upgrades.conf b/resources/playbook/roles/bibigrid/files/enable-auto-upgrades.conf new file mode 100644 index 00000000..cc9e0b02 --- /dev/null +++ b/resources/playbook/roles/bibigrid/files/enable-auto-upgrades.conf @@ -0,0 +1,4 @@ +APT::Periodic::Update-Package-Lists "1"; +APT::Periodic::Download-Upgradeable-Packages "1"; +APT::Periodic::AutocleanInterval "1"; +APT::Periodic::Unattended-Upgrade "1"; \ No newline at end of file diff --git a/resources/playbook/roles/bibigrid/tasks/999-theia.yaml b/resources/playbook/roles/bibigrid/tasks/900-theia.yaml similarity index 100% rename from resources/playbook/roles/bibigrid/tasks/999-theia.yaml rename to resources/playbook/roles/bibigrid/tasks/900-theia.yaml diff --git a/resources/playbook/roles/bibigrid/tasks/999-apt-reactivate-auto-update.yaml b/resources/playbook/roles/bibigrid/tasks/999-apt-reactivate-auto-update.yaml new file mode 100644 index 00000000..8270a8fa --- /dev/null +++ b/resources/playbook/roles/bibigrid/tasks/999-apt-reactivate-auto-update.yaml @@ -0,0 +1,7 @@ +- name: Disable auto-update/upgrade during ansible-run + copy: + src: enable-auto-upgrades.conf + dest: /etc/apt/apt.conf.d/20auto-upgrades + owner: root + group: root + mode: 0644 diff --git a/resources/playbook/roles/bibigrid/tasks/main.yaml b/resources/playbook/roles/bibigrid/tasks/main.yaml index 2616a980..446a1d0b 100644 --- a/resources/playbook/roles/bibigrid/tasks/main.yaml +++ b/resources/playbook/roles/bibigrid/tasks/main.yaml @@ -31,7 +31,8 @@ - name: Setup common software and dependencies for Debian when: "ansible_distribution_file_variety == 'Debian'" - tags: ["pkg"] + tags: + - "pkg" block: - name: Running 001-apt.yaml debug: @@ -40,7 +41,8 @@ - name: Setup common software and dependencies for RedHat when: "ansible_distribution_file_variety == 'RedHat'" - tags: ["pkg"] + tags: + - "pkg" block: - debug: msg: "[BIBIGRID] Setup common software and dependencies" @@ -48,7 +50,7 @@ - name: Configure Wireguard for VPNs when: "'vpn' in group_names and wireguard is defined" - tags: ["vpn"] + tags: "vpn" block: - debug: msg: "[BIBIGRID] Configure Wireguard for VPNs" @@ -56,7 +58,8 @@ - name: Setup DNS when: "'master' in group_names" - tags: ["dns"] + tags: + - "dns" block: - debug: msg: "[BIBIGRID] Setup DNS" @@ -64,7 +67,8 @@ - name: Update host file on worker when: "'workers' in group_names" - tags: ["host"] + tags: + - "host" block: - debug: msg: "[BIBIGRID] Update hosts" @@ -72,7 +76,9 @@ - name: Configure database when: "'master' in group_names" - tags: ["database", "slurm"] + tags: + - "database" + - "slurm" block: - debug: msg: "[BIBIGRID] Configure database" @@ -81,7 +87,8 @@ - name: Setup additional binary executables /usr/local/bin/ when: - "'master' in group_names" - tags: ["bin"] + tags: + - "bin" block: - debug: msg: "[BIBIGRID] Setup additional binary executables /usr/local/bin/" @@ -90,7 +97,8 @@ - name: Setup Zabbix Agent when: - enable_zabbix|default(false)|bool - tags: ["zabbix"] + tags: + - "zabbix" block: - debug: msg: "[BIBIGRID] Setup Zabbix Agent" @@ -102,20 +110,24 @@ msg: "[BIBIGRID] Generate directory structure available on all hosts" - name: Generate general directory structure available on all hosts import_tasks: 020-disk.yaml - tags: ["disk"] + tags: + - "disk" - name: Generate server directory structure available on all hosts import_tasks: 020-disk-server.yaml when: "'master' in group_names" - tags: ["disk"] + tags: + - "disk" - name: Generate worker directory structure available on all hosts import_tasks: 020-disk-worker.yaml when: "'master' not in group_names" - tags: ["disk"] + tags: + - "disk" - name: Setup NFS when: - enable_nfs|default(false)|bool - tags: ["nfs"] + tags: + - "nfs" block: - debug: msg: "[BIBIGRID] Setup NFS" @@ -128,10 +140,12 @@ debug: msg: "[BIBIGRID] Setup Docker" - import_tasks: 030-docker.yaml - tags: ["docker"] + tags: + - "docker" - name: Setup Slurm - tags: ["slurm"] + tags: + - "slurm" block: - debug: msg: "[BIBIGRID] Setup Slurm" @@ -144,8 +158,19 @@ when: - enable_ide|default(false)|bool - "'master' in group_names" - tags: ["theia"] + tags: + - "theia" block: - debug: msg: "[BIBIGRID] Setup Theia" - - import_tasks: 999-theia.yaml + - import_tasks: 900-theia.yaml + +- name: APT Reactivate Auto Update + when: "ansible_distribution_file_variety == 'Debian'" + tags: + - "pkg" + block: + - name: Running 999-apt-reactivate-auto-update.yaml + debug: + msg: "[BIBIGRID] APT Reactivate Auto Update" + - import_tasks: 999-apt-reactivate-auto-update.yaml From 00eb5f2620ea1abd299738c0c0ae9598c4437546 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 19 Aug 2024 15:35:41 +0200 Subject: [PATCH 120/145] added multiple configuration keys for boot volume handling --- bibigrid/core/actions/create.py | 37 +++++++++++++------ bibigrid/core/provider.py | 9 ++++- bibigrid/core/utility/validate_schema.py | 27 ++++++++++---- bibigrid/openstack/openstack_provider.py | 7 +++- .../markdown/features/configuration.md | 12 +++--- 5 files changed, 64 insertions(+), 28 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 0c8d904e..374a375d 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -174,14 +174,14 @@ def generate_security_groups(self): _ = provider.create_security_group(name=self.wireguard_security_group_name)["id"] configuration["security_groups"].append(self.wireguard_security_group_name) # store in configuration - def start_vpn_or_master(self, configuration, provider): + def start_vpn_or_master(self, configuration, provider): # pylint: disable=too-many-locals """ Start master/vpn-worker of a provider @param configuration: dict configuration of said provider. @param provider: provider @return: """ - identifier, instance_type, volumes = self.prepare_vpn_or_master_args(configuration, provider) + identifier, instance, volumes = self.prepare_vpn_or_master_args(configuration, provider) external_network = provider.get_external_network(configuration["network"]) with self.vpn_master_thread_lock: if identifier == MASTER_IDENTIFIER: # pylint: disable=comparison-with-callable @@ -191,14 +191,21 @@ def start_vpn_or_master(self, configuration, provider): additional=self.vpn_counter) # pylint: disable=redundant-keyword-arg self.vpn_counter += 1 self.log.info(f"Starting server {name} on {provider.cloud_specification['identifier']}") - flavor = instance_type["type"] + flavor = instance["type"] network = configuration["network"] - image = image_selection.select_image(provider, instance_type["image"], self.log, + image = image_selection.select_image(provider, instance["image"], self.log, configuration.get("fallbackOnOtherImage")) # create a server and block until it is up and running server = provider.create_server(name=name, flavor=flavor, key_name=self.key_name, image=image, network=network, - volumes=volumes, security_groups=configuration["security_groups"], wait=True) + volumes=volumes, security_groups=configuration["security_groups"], wait=True, + boot_from_volume=instance.get("bootFromVolume", + configuration.get("bootFromVolume", False)), + boot_volume=instance.get("bootVolume", configuration.get("bootVolume")), + terminate_boot_volume=instance.get("terminateBootVolume", + configuration.get("terminateBootVolume", + True)), + volume_size=instance.get("volumeSize", configuration.get("volumeSize", 50))) configuration["private_v4"] = server["private_v4"] self.log.debug(f"Created Server {name}: {server['private_v4']}.") # get mac address for given private address @@ -219,11 +226,13 @@ def start_vpn_or_master(self, configuration, provider): elif identifier == MASTER_IDENTIFIER: configuration["floating_ip"] = server["private_v4"] # pylint: enable=comparison-with-callable configuration["volumes"] = provider.get_mount_info_from_server(server) - for volume in configuration["volumes"]: - mount = next((mount for mount in configuration["masterMounts"] if mount["name"] == volume["name"]), None) - if mount.get("mountPoint"): - volume["mount_point"] = mount["mountPoint"] - self.log.debug(f"Added mount point {mount['mountPoint']} as a mount point in configuration.") + master_mounts = configuration.get("masterMounts", []) + if master_mounts: + for volume in configuration["volumes"]: + mount = next((mount for mount in master_mounts if mount["name"] == volume["name"]), None) + if mount and mount.get("mountPoint"): + volume["mount_point"] = mount["mountPoint"] + self.log.debug(f"Added mount point {mount['mountPoint']} of attached volume to configuration.") def start_workers(self, worker, worker_count, configuration, provider): name = WORKER_IDENTIFIER(cluster_id=self.cluster_id, additional=worker_count) @@ -235,7 +244,13 @@ def start_workers(self, worker, worker_count, configuration, provider): # create a server and block until it is up and running server = provider.create_server(name=name, flavor=flavor, key_name=self.key_name, image=image, network=network, - volumes=None, security_groups=configuration["security_groups"], wait=True) + volumes=None, security_groups=configuration["security_groups"], wait=True, + boot_from_volume=worker.get("bootFromVolume", + configuration.get("bootFromVolume", False)), + boot_volume=worker.get("bootVolume", configuration.get("bootVolume")), + terminate_boot_volume=worker.get("terminateBootVolume", + configuration.get("terminateBootVolume", + True))) self.log.info(f"Worker {name} started on {provider.cloud_specification['identifier']}.") with self.worker_thread_lock: self.permanents.append(name) diff --git a/bibigrid/core/provider.py b/bibigrid/core/provider.py index fb359e98..360093ba 100644 --- a/bibigrid/core/provider.py +++ b/bibigrid/core/provider.py @@ -88,11 +88,16 @@ def list_servers(self): """ @abstractmethod - def create_server(self, name, flavor, image, network, key_name=None, wait=True, volumes=None, - security_groups=None): # pylint: disable=too-many-arguments + def create_server(self, name, flavor, image, network, key_name=None, wait=True, volumes=None, security_groups=None, + boot_volume=None, boot_from_volume=False, + terminate_boot_volume=False, volume_size=50): # pylint: disable=too-many-arguments """ Creates a new server and waits for it to be accessible if wait=True. If volumes are given, they are attached. Returns said server (dict) + @param volume_size: Size of boot volume if set. Defaults to 50. + @param terminate_boot_volume: if True, boot volume gets terminated on server termination + @param boot_from_volume: if True, a boot volume is created from the image + @param boot_volume: if a volume is given, that volume is used as the boot volume @param name: name (str) @param flavor: flavor/type (str) @param image: image/bootable-medium (str) diff --git a/bibigrid/core/utility/validate_schema.py b/bibigrid/core/utility/validate_schema.py index dd22fe28..eb4c84b3 100644 --- a/bibigrid/core/utility/validate_schema.py +++ b/bibigrid/core/utility/validate_schema.py @@ -4,6 +4,17 @@ from schema import Schema, Optional, Or, SchemaError +WORKER = {'type': str, 'image': str, Optional('count'): int, Optional('onDemand'): bool, Optional('partitions'): [str], + Optional('features'): [str], + Optional('bootVolume'): str, + Optional('bootFromVolume'): bool, Optional('terminateBootVolume'): bool, Optional('volumeSize'): int, + } +MASTER = VPN = {'type': str, 'image': str, Optional('onDemand'): bool, Optional('partitions'): [str], + Optional('features'): [str], + Optional('bootVolume'): str, + Optional('bootFromVolume'): bool, Optional('terminateBootVolume'): bool, Optional('volumeSize'): int, + } + # Define the schema for the configuration file master_schema = Schema( {'infrastructure': str, 'cloud': str, 'sshUser': str, Or('subnet', 'network'): str, 'cloud_identifier': str, @@ -20,19 +31,21 @@ 'ResumeTimeout'): int, Optional('TreeWidth'): int}}, Optional('zabbix'): bool, Optional('nfs'): bool, Optional('ide'): bool, Optional('useMasterAsCompute'): bool, - Optional('useMasterWithPublicIp'): bool, Optional('waitForServices'): [str], + Optional('useMasterWithPublicIp'): bool, Optional('waitForServices'): [str], Optional('bootVolume'): str, + Optional('bootFromVolume'): bool, Optional('terminateBootVolume'): bool, Optional('volumeSize'): int, Optional('gateway'): {'ip': str, 'portFunction': str}, Optional('fallbackOnOtherImage'): bool, Optional('localDNSLookup'): bool, Optional('features'): [str], 'workerInstances': [ - {'type': str, 'image': str, Optional('count'): int, Optional('onDemand'): bool, Optional('partitions'): [str], - Optional('features'): [str]}], - 'masterInstance': {'type': str, 'image': str, Optional('partitions'): [str], Optional('features'): [str]}, - Optional('vpngtw'): {'type': str, 'image': str}}) + WORKER], + 'masterInstance': MASTER, + Optional('vpngtw'): {'type': str, 'image': str}, + Optional('bootVolume'): str, + Optional('bootFromVolume'): bool, Optional('terminateBootVolume'): bool, Optional('volumeSize'): int + }) other_schema = Schema( {'infrastructure': str, 'cloud': str, 'sshUser': str, Or('subnet', 'network'): str, 'cloud_identifier': str, Optional('waitForServices'): [str], Optional('features'): [str], 'workerInstances': [ - {'type': str, 'image': str, Optional('count'): int, Optional('onDemand'): bool, Optional('partitions'): [str], - Optional('features'): [str]}], 'vpnInstance': {'type': str, 'image': str}}) + WORKER], 'vpnInstance': VPN}) def validate_configurations(configurations, log): diff --git a/bibigrid/openstack/openstack_provider.py b/bibigrid/openstack/openstack_provider.py index 5fb3bacd..dd1ccbfa 100644 --- a/bibigrid/openstack/openstack_provider.py +++ b/bibigrid/openstack/openstack_provider.py @@ -114,10 +114,13 @@ def get_subnet_by_id_or_name(self, subnet_id_or_name): def list_servers(self): return [elem.toDict() for elem in self.conn.list_servers()] - def create_server(self, name, flavor, image, network, key_name=None, wait=True, volumes=None, security_groups=None): + def create_server(self, name, flavor, image, network, key_name=None, wait=True, volumes=None, security_groups=None, + boot_volume=None, boot_from_volume=False, terminate_boot_volume=False, volume_size=50): try: server = self.conn.create_server(name=name, flavor=flavor, image=image, network=network, key_name=key_name, - volumes=volumes, security_groups=security_groups) + volumes=volumes, security_groups=security_groups, boot_volume=boot_volume, + boot_from_volume=boot_from_volume, + terminate_volume=terminate_boot_volume, volume_size=volume_size) except openstack.exceptions.BadRequestException as exc: if "is not active" in str(exc): raise ImageDeactivatedException() from exc diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 60345c0c..80bb7de1 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -16,10 +16,10 @@ The configuration file is best stored in `~/.config/bibigrid/`. BiBiGrid starts ## Configuration List If you have a single-cloud use case, you can [skip ahead](). -Only the first configuration holds a `master` key (also called `master configuration`). -Every following configuration must hold a `vpngtw` key. +Only the first configuration holds a `masterInstance` key (also called `master configuration`). +Every following configuration must hold a `vpnInstance` key. -Later, this vpngtw allows BiBiGrid to connect multiple clouds. +Later, this vpnInstance allows BiBiGrid to connect multiple clouds. [Here](multi_cloud.md) you can get a technical overview regarding BiBiGrid's multi-cloud setup. @@ -288,7 +288,7 @@ openstack flavor list --os-cloud=openstack ``` -#### Master or vpngtw? +#### masterInstance or vpnInstance? ##### masterInstance @@ -311,12 +311,12 @@ You can create features for the master [in the same way](#features-optional) as - holdsinformation ``` -##### vpngtw: +##### vpnInstance: Exactly one in every configuration but the first: ```yaml - vpngtw: + vpnInstance: type: de.NBI tiny image: Ubuntu 22.04 LTS (2022-10-14) # regex allowed ``` From 22f4993dfaf466327abb8b2c9ae6a5d8bc73b71f Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 19 Aug 2024 15:53:23 +0200 Subject: [PATCH 121/145] updated documentation --- bibigrid.yaml | 14 ++++++++ .../markdown/features/configuration.md | 34 ++++++++++++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/bibigrid.yaml b/bibigrid.yaml index ea5d2d73..cdc75662 100644 --- a/bibigrid.yaml +++ b/bibigrid.yaml @@ -48,6 +48,10 @@ #ide: True # A nice way to view your cluster as if you were using Visual Studio Code useMasterAsCompute: True + + # bootFromVolume: False + # terminateBootVolume: True + # volumeSize: 50 # waitForServices: # existing service name that runs after an instance is launched. BiBiGrid's playbook will wait until service is "stopped" to avoid issues # - de.NBI_Bielefeld_environment.service # uncomment for cloud site Bielefeld @@ -57,6 +61,11 @@ type: # existing type/flavor on your cloud. See launch instance>flavor for options image: # existing active image on your cloud. Consider using regex to prevent image updates from breaking your running cluster # features: # list + # partitions: # list + # bootVolume: None + # bootFromVolume: True + # terminateBootVolume: True + # volumeSize: 50 # -- END: GENERAL CLUSTER INFORMATION -- @@ -68,6 +77,11 @@ # image: # same as master. Consider using regex to prevent image updates from breaking your running cluster # count: # any number of workers you would like to create with set type, image combination # # features: # list + # # partitions: # list + # # bootVolume: None + # # bootFromVolume: True + # # terminateBootVolume: True + # # volumeSize: 50 # Depends on cloud image sshUser: # for example ubuntu diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 80bb7de1..c568d88a 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -253,14 +253,22 @@ workerInstance: features: # optional - hasdatabase - holdsinformation + bootVolume: False + bootFromVolume: True + terminateBootVolume: True + volumeSize: 50 ``` - `type` sets the instance's hardware configuration. - `image` sets the bootable operating system to be installed on the instance. - `count` sets how many workers of that `type` `image` combination are in this work group -- `onDemand` defines whether nodes in the worker group are scheduled on demand (True) or are started permanently (False). Please only use if necessary. On Demand Scheduling improves resource availability for all users. This option only works for single cloud setups for now. -- `partitions` allow you to force Slurm to schedule to a group of nodes (partitions) ([more](https://slurm.schedmd.com/slurm.conf.html#SECTION_PARTITION-CONFIGURATION)) -- `features` allow you to force Slurm to schedule a job only on nodes that meet certain `bool` constraints. This can be helpful when only certain nodes can access a specific resource - like a database ([more](https://slurm.schedmd.com/slurm.conf.html#OPT_Features)). +- `onDemand` (optional:False) defines whether nodes in the worker group are scheduled on demand (True) or are started permanently (False). Please only use if necessary. On Demand Scheduling improves resource availability for all users. This option only works for single cloud setups for now. +- `partitions` (optional:[]) allow you to force Slurm to schedule to a group of nodes (partitions) ([more](https://slurm.schedmd.com/slurm.conf.html#SECTION_PARTITION-CONFIGURATION)) +- `features` (optional:[]) allow you to force Slurm to schedule a job only on nodes that meet certain `bool` constraints. This can be helpful when only certain nodes can access a specific resource - like a database ([more](https://slurm.schedmd.com/slurm.conf.html#OPT_Features)). +- `bootVolume` (optional:None) takes name or id of a boot volume and boots from that volume if given. +- `bootFromVolume` (optional:False) if True, the instance will boot from a volume created for this purpose. +- `terminateBootVolume` (optional:True) if True, the boot volume will be terminated when the server is terminated. +- `volumeSize` (optional:50) if a boot volume is created, this sets its size. ##### Find your active `images` @@ -298,6 +306,10 @@ Only in the first configuration and only one: masterInstance: type: de.NBI tiny image: Ubuntu 22.04 LTS (2022-10-14) + bootVolume: False + bootFromVolume: True + terminateBootVolume: False + volumeSize: 50 ``` You can create features for the master [in the same way](#features-optional) as for the workers: @@ -355,7 +367,19 @@ Currently, the case in Berlin, DKFZ, Heidelberg and Tuebingen. #### features (optional) -You can declare a list of cloud-wide [features](#whats-a-feature) that are then attached to every node in the cloud described by the configuration. +Cloud-wide slurm [features](#whats-a-feature) that are attached to every node in the cloud described by the configuration. If both [worker group](#workerinstances) or [master features](#masterInstance) and configuration features are defined, they are merged. If you only have a single cloud and therefore a single configuration, this key is not helpful as a feature -that is present at all nodes can be omitted as it can't influence the scheduling. \ No newline at end of file +that is present at all nodes can be omitted as it can't influence the scheduling. + +#### bootFromVolume (optional:False) +If True, the instance will boot from a volume created for this purpose. Keep in mind that on demand scheduling can lead +to multiple boots of the same configurated node. If you don't make use of [terminateBootVolume](#terminatebootvolume-optionalfalse) +this will lead to many created volumes. + +#### terminateBootVolume (optional:True) +If True, once the instance is shut down, boot volume is destroyed. This does not affect other attached volumes. +Only the boot volume is affected. +#### volumeSize (optional:50) +The created volume's size if you use [bootFromVolume](#bootfromvolume-optionalfalse). + From 1841d5ea01952b8eb24c7e0b00cfc400b3e6ff62 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Tue, 20 Aug 2024 11:44:31 +0200 Subject: [PATCH 122/145] updated documentation for new volumes and for usually ignored keys --- bibigrid.yaml | 8 +++++--- .../markdown/features/configuration.md | 20 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bibigrid.yaml b/bibigrid.yaml index cdc75662..fa6d8f75 100644 --- a/bibigrid.yaml +++ b/bibigrid.yaml @@ -40,9 +40,11 @@ # deleteTmpKeypairAfter: False # dontUploadCredentials: False - # Other keys - default False - #localFS: True - #localDNSlookup: True + # Other keys - these are default False + # Usually Ignored + ##localFS: True + ##localDNSlookup: True + #zabbix: True #nfs: True #ide: True # A nice way to view your cluster as if you were using Visual Studio Code diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index c568d88a..37a644a5 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -135,15 +135,17 @@ userRoles: # see ansible_hosts for all options # - file1 ``` -#### localFS (optional:False) +#### Usually Ignored Keys +##### localFS (optional:False) In general, this key is ignored. It expects `True` or `False` and helps some specific users to create a filesystem to their liking. Default is `False`. -#### localDNSlookup (optional:False) +##### localDNSlookup (optional:False) -If `True`, master will store DNS information for his workers. Default is `False`. -[More information](https://helpdeskgeek.com/networking/edit-hosts-file/). +If no full DNS service for started instances is available, set `localDNSLookup: True`. +Currently, the case in Berlin, DKFZ, Heidelberg and Tuebingen. +Given that we use dnsmasq, this might be obsolete. #### slurm (optional:True) If `False`, the cluster will start without the job scheduling system slurm. @@ -360,11 +362,6 @@ Find available `subnets`: openstack subnet list --os-cloud=openstack ``` -#### localDNSLookup (optional:False) - -If no full DNS service for started instances is available, set `localDNSLookup: True`. -Currently, the case in Berlin, DKFZ, Heidelberg and Tuebingen. - #### features (optional) Cloud-wide slurm [features](#whats-a-feature) that are attached to every node in the cloud described by the configuration. @@ -377,9 +374,10 @@ If True, the instance will boot from a volume created for this purpose. Keep in to multiple boots of the same configurated node. If you don't make use of [terminateBootVolume](#terminatebootvolume-optionalfalse) this will lead to many created volumes. +#### volumeSize (optional:50) +The created volume's size if you use [bootFromVolume](#bootfromvolume-optionalfalse). + #### terminateBootVolume (optional:True) If True, once the instance is shut down, boot volume is destroyed. This does not affect other attached volumes. Only the boot volume is affected. -#### volumeSize (optional:50) -The created volume's size if you use [bootFromVolume](#bootfromvolume-optionalfalse). From 9f83f4d0b739456daee508b8b258db32a7ca3c94 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 28 Aug 2024 14:03:39 +0200 Subject: [PATCH 123/145] updated and added tests --- tests/test_ansible_configurator.py | 169 ++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 6 deletions(-) diff --git a/tests/test_ansible_configurator.py b/tests/test_ansible_configurator.py index 4b465e65..717e974f 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/test_ansible_configurator.py @@ -1,6 +1,7 @@ """ Tests for ansible_configurator """ +import os from unittest import TestCase from unittest.mock import MagicMock, Mock, patch, call, mock_open, ANY @@ -220,14 +221,14 @@ def test_generate_common_configuration_ext_nfs_shares(self): common_configuration_yaml["slurm_conf"]["munge_key"] = generated_common_configuration["slurm_conf"]["munge_key"] self.assertEqual(common_configuration_yaml, generated_common_configuration) - def test_generate_common_configuration_ide(self): - configuration = [{"ide": "Some1", "ideConf": {"key1": "Some2"}}] + def test_generate_common_configuration_ide_and_wireguard(self): + configuration = [{"ide": "Some1", "ideConf": {"key1": "Some2"}, "wireguard_peer": 21}, {"wireguard_peer": 42}] cidrs = "42" cluster_id = "21" default_user = "ubuntu" ssh_user = "test" - common_configuration_yaml = {'bibigrid_version': version.__version__, 'cloud_scheduling': {'sshTimeout': 5}, - 'cluster_cidrs': cidrs, 'cluster_id': cluster_id, 'default_user': default_user, + common_configuration_yaml = {'bibigrid_version': '0.4.0', 'cloud_scheduling': {'sshTimeout': 5}, + 'cluster_cidrs': '42', 'cluster_id': '21', 'default_user': 'ubuntu', 'dns_server_list': ['8.8.8.8'], 'enable_ide': 'Some1', 'enable_nfs': False, 'enable_slurm': False, 'enable_zabbix': False, 'ide_conf': {'build': False, 'ide': False, 'key1': 'Some2', 'port_end': 8383, @@ -236,8 +237,9 @@ def test_generate_common_configuration_ide(self): 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', 'elastic_scheduling': {'ResumeTimeout': 1200, 'SuspendTime': 3600, 'SuspendTimeout': 60, 'TreeWidth': 128}, - 'munge_key': 'b7nks3Ur3kanyPAEBxfSC9ypfSHFnWJL'}, - 'ssh_user': ssh_user, 'use_master_as_compute': True} + 'munge_key': 'b4pPb7bQTHzTDtGsqo2pYkGdpa87TtPD'}, + 'ssh_user': 'test', 'use_master_as_compute': True, + 'wireguard_common': {'listen_port': 51820, 'mask_bits': 24, 'peers': [21, 42]}} generated_common_configuration = ansible_configurator.generate_common_configuration_yaml(cidrs, configuration, cluster_id, ssh_user, default_user, @@ -366,3 +368,158 @@ def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_hos call(aRP.HOSTS_CONFIG_FILE, mock_hosts(), startup.LOG, False), call(aRP.SITE_CONFIG_FILE, mock_site(), startup.LOG, False)] self.assertEqual(expected, mock_yaml.call_args_list) + + @patch('bibigrid.core.utility.ansible_configurator.write_yaml') + @patch('bibigrid.core.utility.ansible_configurator.aRP.GROUP_VARS_FOLDER', '/mocked/path/group_vars') + @patch('bibigrid.core.utility.ansible_configurator.aRP.HOST_VARS_FOLDER', '/mocked/path/host_vars') + def test_write_host_and_group_vars(self, mock_write_yaml): + mock_log = MagicMock() + + mock_provider = MagicMock() + mock_provider.get_flavor.return_value = {"name": "flavor-name", "ram": 4096, "vcpus": 2, "disk": 40, + "ephemeral": 0} + + # Define configurations and providers for the test + configurations = [{"features": ["feature1", "feature2"], "workerInstances": [ + {"type": "m1.small", "count": 2, "image": "worker-image", "onDemand": True, "partitions": ["partition1"], + "features": "worker-feature"}], "masterInstance": {"type": "m1.large", "image": "master-image"}, + "network": "private-network", "subnet_cidrs": ["10.0.0.0/24"], "floating_ip": "1.2.3.4", + "private_v4": "10.0.0.1", "cloud_identifier": "cloud-1", "volumes": ["volume1"], + "fallbackOnOtherImage": False, "wireguard_peer": "peer1"}, + {"vpnInstance": {"type": "vpn-type", "image": "vpn-image"}, "network": "private-network", + "subnet_cidrs": ["10.0.0.0/24"], "floating_ip": "1.2.3.4", "private_v4": "10.0.0.1", + "cloud_identifier": "cloud-1", "fallbackOnOtherImage": False, "wireguard_peer": "peer1"}] + + providers = [mock_provider, mock_provider] + cluster_id = "test-cluster" + + # Call the function under test + ansible_configurator.write_host_and_group_vars(configurations, providers, cluster_id, mock_log) + + expected_worker_dict = {"name": "bibigrid-worker-test-cluster-[0-1]", + "regexp": "bibigrid-worker-test-cluster-\\d+", "image": "worker-image", + "network": "private-network", + "flavor": {"name": "flavor-name", "ram": 4096, "vcpus": 2, "disk": 40, "ephemeral": 0}, + "gateway_ip": "10.0.0.1", "cloud_identifier": "cloud-1", "on_demand": True, + "state": "CLOUD", "partitions": ["partition1", "all", "cloud-1"], + "features": {"feature1", "feature2", "worker-feature"}} + mock_write_yaml.assert_any_call( + os.path.join('/mocked/path/group_vars', 'bibigrid_worker_test_cluster_0_1.yaml'), expected_worker_dict, + mock_log) + + # Assertions for masterInstance + expected_master_dict = {"name": "bibigrid-master-test-cluster", "image": "master-image", + "network": "private-network", "network_cidrs": ["10.0.0.0/24"], + "floating_ip": "1.2.3.4", + "flavor": {"name": "flavor-name", "ram": 4096, "vcpus": 2, "disk": 40, "ephemeral": 0}, + "private_v4": "10.0.0.1", "cloud_identifier": "cloud-1", "volumes": ["volume1"], + "fallback_on_other_image": False, "state": "UNKNOWN", "on_demand": False, + "partitions": ["all", "cloud-1"], "wireguard": {"ip": "10.0.0.1", "peer": "peer1"}} + mock_write_yaml.assert_any_call(os.path.join('/mocked/path/group_vars', 'master.yaml'), expected_master_dict, + mock_log) + + expected_vpn_dict = {"name": "bibigrid-vpngtw-test-cluster-0", "regexp": "bibigrid-worker-test-cluster-\\d+", + "image": "vpn-image", "network": "private-network", "network_cidrs": ["10.0.0.0/24"], + "floating_ip": "1.2.3.4", "private_v4": "10.0.0.1", + "flavor": {"name": "flavor-name", "ram": 4096, "vcpus": 2, "disk": 40, "ephemeral": 0}, + "wireguard_ip": "10.0.0.2", "cloud_identifier": "cloud-1", + "fallback_on_other_image": False, "on_demand": False, + "wireguard": {"ip": "10.0.0.2", "peer": "peer1"}} + mock_write_yaml.assert_any_call(os.path.join('/mocked/path/host_vars', 'bibigrid-vpngtw-test-cluster-0.yaml'), + expected_vpn_dict, mock_log) + + @patch('os.remove') + @patch('os.listdir') + @patch('os.path.isfile') + @patch('bibigrid.core.utility.ansible_configurator.aRP.GROUP_VARS_FOLDER', '/mocked/path/group_vars') + @patch('bibigrid.core.utility.ansible_configurator.aRP.HOST_VARS_FOLDER', '/mocked/path/host_vars') + @patch('logging.getLogger') + def test_delete_old_vars(self, mock_get_logger, mock_isfile, mock_listdir, mock_remove): + mock_log = MagicMock() + mock_get_logger.return_value = mock_log + mock_isfile.return_value = True + + mock_listdir.side_effect = [['file1.yaml', 'file2.yaml'], # Files in GROUP_VARS_FOLDER + ['file3.yaml', 'file4.yaml'] # Files in HOST_VARS_FOLDER + ] + + # Call the function under test + ansible_configurator.delete_old_vars(mock_log) + + # Assertions for file removal + mock_remove.assert_any_call('/mocked/path/group_vars/file1.yaml') + mock_remove.assert_any_call('/mocked/path/group_vars/file2.yaml') + mock_remove.assert_any_call('/mocked/path/host_vars/file3.yaml') + mock_remove.assert_any_call('/mocked/path/host_vars/file4.yaml') + + self.assertEqual(mock_remove.call_count, 4) + + def test_key_present_with_key_to(self): + dict_from = {'source_key': 'value1'} + dict_to = {} + ansible_configurator.pass_through(dict_from, dict_to, 'source_key', 'destination_key') + self.assertEqual(dict_to, {'destination_key': 'value1'}) + + def test_key_present_without_key_to(self): + dict_from = {'source_key': 'value2'} + dict_to = {} + ansible_configurator.pass_through(dict_from, dict_to, 'source_key') + self.assertEqual(dict_to, {'source_key': 'value2'}) + + def test_key_not_present(self): + dict_from = {} + dict_to = {} + ansible_configurator.pass_through(dict_from, dict_to, 'source_key', 'destination_key') + self.assertEqual(dict_to, {}) + + def test_key_to_not_specified(self): + dict_from = {'source_key': 'value3'} + dict_to = {'existing_key': 'existing_value'} + ansible_configurator.pass_through(dict_from, dict_to, 'source_key') + self.assertEqual(dict_to, {'existing_key': 'existing_value', 'source_key': 'value3'}) + + def test_key_from_not_in_dict_from(self): + dict_from = {'another_key': 'value4'} + dict_to = {'existing_key': 'existing_value'} + ansible_configurator.pass_through(dict_from, dict_to, 'source_key', 'destination_key') + self.assertEqual(dict_to, {'existing_key': 'existing_value'}) + + @patch('bibigrid.core.utility.wireguard.wireguard_keys.generate') + def test_add_wireguard_peers_multiple_configurations(self, mock_generate): + # Set up the mock to return specific keys + mock_generate.return_value = ('private_key_example', 'public_key_example') + + configurations = [{"cloud_identifier": "cloud-1", "floating_ip": "10.0.0.1", "subnet_cidrs": ["10.0.0.0/24"]}, + {"cloud_identifier": "cloud-2", "floating_ip": "10.0.0.2", "subnet_cidrs": ["10.0.1.0/24"]}] + + # Call the function + ansible_configurator.add_wireguard_peers(configurations) + + # Assert that the wireguard_peer field is added correctly to each configuration + for config in configurations: + self.assertIn("wireguard_peer", config) + self.assertEqual(config["wireguard_peer"]["name"], config["cloud_identifier"]) + self.assertEqual(config["wireguard_peer"]["private_key"], 'private_key_example') + self.assertEqual(config["wireguard_peer"]["public_key"], 'public_key_example') + self.assertEqual(config["wireguard_peer"]["ip"], config["floating_ip"]) + self.assertEqual(config["wireguard_peer"]["subnets"], config["subnet_cidrs"]) + + def test_add_wireguard_peers_single_configuration(self): + # Test with only one configuration + configurations = [{"cloud_identifier": "cloud-1", "floating_ip": "10.0.0.1", "subnet_cidrs": ["10.0.0.0/24"]}] + + # Call the function + ansible_configurator.add_wireguard_peers(configurations) + + # Assert that no wireguard_peer field is added + self.assertNotIn("wireguard_peer", configurations[0]) + + def test_add_wireguard_peers_empty_list(self): + # Test with an empty list + configurations = [] + + # Call the function + ansible_configurator.add_wireguard_peers(configurations) + + # Assert that the configurations list remains empty + self.assertEqual(configurations, []) From 81c4919cd8c002c3810756c65185f7b344d54d83 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 6 Sep 2024 11:24:53 +0200 Subject: [PATCH 124/145] Pleasing Dependabot --- requirements-dev.txt | 2 +- requirements-rest.txt | 14 +++++++------- requirements.txt | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ef9bc405..6e4fda76 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,2 @@ -ansible_lint==6.8.0 +ansible_lint==24.7.0 pylint==2.14.5 diff --git a/requirements-rest.txt b/requirements-rest.txt index 57bc62e8..5c48187c 100644 --- a/requirements-rest.txt +++ b/requirements-rest.txt @@ -1,14 +1,14 @@ openstacksdk==0.62 -mergedeep -paramiko +mergedeep~=1.3.4 +paramiko~=3.4.0 python-cinderclient python-keystoneclient python-novaclient python-openstackclient==6.0.0 -PyYAML -shortuuid -sshtunnel -fastapi +PyYAML~=6.0 +shortuuid~=1.0.13 +sshtunnel~=0.4.0 +fastapi~=0.113.0 python-multipart -uvicorn +uvicorn~=0.23.2 httpx \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f5c73d10..48cf885c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,19 @@ openstacksdk==0.62 mergedeep~=1.3.4 -paramiko~=2.12.0 +paramiko~=3.4.0 python-cinderclient python-keystoneclient python-novaclient python-openstackclient==6.0.0 PyYAML~=6.0 -shortuuid~=1.0.11 +shortuuid~=1.0.13 sshtunnel~=0.4.0 sympy~=1.12 -seedir~=0.4.2 -cryptography~=38.0.4 -uvicorn~=0.23.2 +seedir~=0.5.0 +cryptography~=43.0.1 +uvicorn~=0.30.6 fastapi~=0.101.0 -pydantic~=2.1.1 +pydantic~=2.9.0 keystoneauth1~=5.1.0 -filelock~=3.13.1 +filelock~=3.15.4 schema~=0.7.7 \ No newline at end of file From f855dc916b6cc7be51bff7fe8d48ff380c0a9350 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 6 Sep 2024 12:22:14 +0200 Subject: [PATCH 125/145] Linting now uses python 3.10 --- .github/workflows/linting.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 5035d0d0..cca81f8d 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -5,10 +5,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Set up Python 3.8 + - name: Set up Python 3.10 uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip From cef8040ac41348741e413618bddf0fe6e11943a1 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Wed, 11 Sep 2024 15:34:12 +0200 Subject: [PATCH 126/145] added early termination when configuration file not found --- bibigrid/core/utility/handler/configuration_handler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bibigrid/core/utility/handler/configuration_handler.py b/bibigrid/core/utility/handler/configuration_handler.py index 608c8e42..1a73cc9a 100644 --- a/bibigrid/core/utility/handler/configuration_handler.py +++ b/bibigrid/core/utility/handler/configuration_handler.py @@ -3,6 +3,7 @@ """ import os +import sys import mergedeep import yaml @@ -31,8 +32,10 @@ def read_configuration(log, path, configuration_list=True): configuration = yaml.safe_load(stream) except yaml.YAMLError as exc: log.warning("Couldn't read configuration %s: %s", path, exc) + sys.exit(1) else: log.warning("No such configuration file %s.", path) + sys.exit(1) if configuration_list and not isinstance(configuration, list): log.warning("Configuration should be list. Attempting to rescue by assuming a single configuration.") return [configuration] From 5907903145d547f697fdc9776067b9de107478e9 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 12 Sep 2024 11:30:59 +0200 Subject: [PATCH 127/145] added dontUploadCredentials documentation --- documentation/markdown/features/configuration.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 37a644a5..dbce821c 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -219,6 +219,14 @@ gateway: Using gateway also automatically sets [useMasterWithPublicIp](#usemasterwithpublicip-optional) to `False`. +#### dontUploadCredentials (optional:True) +Usually, BiBiGrid will upload your credentials to the cluster. This is necessary for on demand scheduling. +However, if all your nodes are permanent (i.e. not on demand), you do not need to upload your credentials. +In such cases you can set `dontUploadCredentials: True`. + +This also allows for external node schedulers by using the Slurm REST API to decide whether a new node should be started or not. +[SimpleVM](https://cloud.denbi.de/about/project-types/simplevm/) is scheduling that way. + ### Local #### waitForServices (optional): From 628e6f99cf606ce5c1a475e3d14b2e6905660d94 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 12 Sep 2024 12:02:36 +0200 Subject: [PATCH 128/145] fixed broken links --- documentation/markdown/features/configuration.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index dbce821c..34ba43da 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -107,9 +107,9 @@ nfsshares: `nfsShares` expects a list of folder paths to share over the network using nfs. In every case, `/vol/spool/` is always an nfsShare. -This key is only relevant if the [nfs key](#nfs-optional) is set `True`. +This key is only relevant if the [nfs key](#nfs-optionalfalse) is set `True`. -If you would like to share a [masterMount](#mastermounts-optional), take a look [here](../software/nfs.md#mount-volume-into-share). +If you would like to share a [masterMount](#mastermounts-optionalfalse), take a look [here](../software/nfs.md#mount-volume-into-share).
@@ -191,7 +191,7 @@ If `True`, [nfs](../software/nfs.md) is set up. Default is `False`. #### ide (optional:False) If `True`, [Theia Web IDE](../software/theia_ide.md) is installed. -After creation connection information is [printed](../features/create.md#prints-cluster-information). Default is `False`. +After creation connection information is [printed](../features/create.md). Default is `False`. #### useMasterAsCompute (optional:True) @@ -217,7 +217,7 @@ gateway: portFunction: 30000 + oct4 # variables are called: oct1.oct2.oct3.oct4 ``` -Using gateway also automatically sets [useMasterWithPublicIp](#usemasterwithpublicip-optional) to `False`. +Using gateway also automatically sets [useMasterWithPublicIp](#usemasterwithpublicip-optionaltrue) to `False`. #### dontUploadCredentials (optional:True) Usually, BiBiGrid will upload your credentials to the cluster. This is necessary for on demand scheduling. @@ -296,7 +296,7 @@ most recent release is active, you can use `Ubuntu 22.04 LTS \(.*\)` so it alway This regex will also be used when starting worker instances on demand and is therefore mandatory to automatically resolve image updates of the described kind while running a cluster. -There's also a [Fallback Option](#fallbackonotherimage-optional). +There's also a [Fallback Option](#fallbackonotherimage-optionalfalse). ##### Find your active `type`s `flavor` is just the OpenStack terminology for `type`. @@ -372,14 +372,14 @@ openstack subnet list --os-cloud=openstack #### features (optional) -Cloud-wide slurm [features](#whats-a-feature) that are attached to every node in the cloud described by the configuration. +Cloud-wide slurm features that are attached to every node in the cloud described by the configuration. If both [worker group](#workerinstances) or [master features](#masterInstance) and configuration features are defined, they are merged. If you only have a single cloud and therefore a single configuration, this key is not helpful as a feature that is present at all nodes can be omitted as it can't influence the scheduling. #### bootFromVolume (optional:False) If True, the instance will boot from a volume created for this purpose. Keep in mind that on demand scheduling can lead -to multiple boots of the same configurated node. If you don't make use of [terminateBootVolume](#terminatebootvolume-optionalfalse) +to multiple boots of the same configurated node. If you don't make use of [terminateBootVolume](#terminatebootvolume-optionaltrue) this will lead to many created volumes. #### volumeSize (optional:50) @@ -388,4 +388,3 @@ The created volume's size if you use [bootFromVolume](#bootfromvolume-optionalfa #### terminateBootVolume (optional:True) If True, once the instance is shut down, boot volume is destroyed. This does not affect other attached volumes. Only the boot volume is affected. - From 05f8df6e2c1c1e4e635f3759361381bad8e1f526 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 12 Sep 2024 13:54:15 +0200 Subject: [PATCH 129/145] added dontUploadCredentials to schema valiation --- bibigrid/core/utility/validate_schema.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bibigrid/core/utility/validate_schema.py b/bibigrid/core/utility/validate_schema.py index eb4c84b3..4b2e3295 100644 --- a/bibigrid/core/utility/validate_schema.py +++ b/bibigrid/core/utility/validate_schema.py @@ -33,7 +33,8 @@ Optional('zabbix'): bool, Optional('nfs'): bool, Optional('ide'): bool, Optional('useMasterAsCompute'): bool, Optional('useMasterWithPublicIp'): bool, Optional('waitForServices'): [str], Optional('bootVolume'): str, Optional('bootFromVolume'): bool, Optional('terminateBootVolume'): bool, Optional('volumeSize'): int, - Optional('gateway'): {'ip': str, 'portFunction': str}, Optional('fallbackOnOtherImage'): bool, + Optional('gateway'): {'ip': str, 'portFunction': str}, Optional('dontUploadCredentials'): bool, + Optional('fallbackOnOtherImage'): bool, Optional('localDNSLookup'): bool, Optional('features'): [str], 'workerInstances': [ WORKER], 'masterInstance': MASTER, From 6acfb06b3cbcfd5f5f57a534f36e04bae5bd62d7 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 12 Sep 2024 13:54:36 +0200 Subject: [PATCH 130/145] fixed dontUploadCredential ansible start bug --- bibigrid/core/actions/create.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index 374a375d..dea3d45b 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -368,12 +368,12 @@ def upload_data(self, private_key, clean_playbook=False): ansible_configurator.configure_ansible_yaml(providers=self.providers, configurations=self.configurations, cluster_id=self.cluster_id, log=self.log) + ansible_start = ssh_handler.ANSIBLE_START + ansible_start[-1] = (ansible_start[-1][0].format(",".join(self.permanents)), ansible_start[-1][1]) + self.log.debug(f"Starting playbook with {ansible_start}.") if self.configurations[0].get("dontUploadCredentials"): - commands = ssh_handler.ANSIBLE_START + commands = ansible_start else: - ansible_start = ssh_handler.ANSIBLE_START - ansible_start[-1] = (ansible_start[-1][0].format(",".join(self.permanents)), ansible_start[-1][1]) - self.log.debug(f"Starting playbook with {ansible_start}.") commands = [ssh_handler.get_ac_command(self.providers, AC_NAME.format( cluster_id=self.cluster_id))] + ssh_handler.ANSIBLE_START if clean_playbook: From b4f54648e22e23a0f702ab4129d375ac7d03b220 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 12 Sep 2024 14:17:50 +0200 Subject: [PATCH 131/145] prevented BiBiGrid from looking for other keys if created key doesn't work to spot key issues earlier --- bibigrid/core/utility/handler/ssh_handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index 0a742318..822381c5 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -111,7 +111,7 @@ def is_active(client, paramiko_key, ssh_data, log): log.info(f"Attempt {attempts}/{ssh_data['timeout']}. Connecting to {ssh_data['floating_ip']}") client.connect(hostname=ssh_data['gateway'].get("ip") or ssh_data['floating_ip'], username=ssh_data['username'], pkey=paramiko_key, timeout=7, - auth_timeout=ssh_data['timeout'], port=port) + auth_timeout=ssh_data['timeout'], port=port, look_for_keys=False) establishing_connection = False log.info(f"Successfully connected to {ssh_data['floating_ip']}.") except paramiko.ssh_exception.NoValidConnectionsError as exc: @@ -196,6 +196,8 @@ def execute_ssh(ssh_data, log): ssh_data["filepaths"] = [] if ssh_data.get("commands") is None: ssh_data["commands"] = [] + print(ssh_data["private_key"]) + input("waiting") paramiko_key = paramiko.ECDSAKey.from_private_key_file(ssh_data["private_key"]) with paramiko.SSHClient() as client: client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) From 0c4a86a8d78d2ed6ebba9b5484a2a8a41139b440 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Thu, 12 Sep 2024 14:18:20 +0200 Subject: [PATCH 132/145] prevented BiBiGrid from looking for other keys if created key doesn't work to spot key issues earlier --- bibigrid/core/utility/handler/ssh_handler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index 822381c5..3c42cd5e 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -196,8 +196,6 @@ def execute_ssh(ssh_data, log): ssh_data["filepaths"] = [] if ssh_data.get("commands") is None: ssh_data["commands"] = [] - print(ssh_data["private_key"]) - input("waiting") paramiko_key = paramiko.ECDSAKey.from_private_key_file(ssh_data["private_key"]) with paramiko.SSHClient() as client: client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) From 3a37bccdf11a726c02671ebb2117e884e6a6636a Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 13 Sep 2024 12:20:24 +0200 Subject: [PATCH 133/145] updated requirements.txt --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 48cf885c..f6920512 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ openstacksdk==0.62 mergedeep~=1.3.4 -paramiko~=3.4.0 +paramiko~=3.4.1 python-cinderclient python-keystoneclient python-novaclient @@ -13,7 +13,7 @@ seedir~=0.5.0 cryptography~=43.0.1 uvicorn~=0.30.6 fastapi~=0.101.0 -pydantic~=2.9.0 +pydantic~=2.9.1 keystoneauth1~=5.1.0 -filelock~=3.15.4 +filelock~=3.16.0 schema~=0.7.7 \ No newline at end of file From 87d1d51c5e08efb35d2948e9d624d93910608742 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 13 Sep 2024 14:50:21 +0200 Subject: [PATCH 134/145] restricted clouds.yaml access --- resources/playbook/roles/bibigrid/tasks/042-slurm.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml index d80fb67c..7901997a 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml @@ -9,6 +9,12 @@ uid: 64030 group: slurm +- name: Change group ownership of OpenStack credentials file to slurm + file: + path: /etc/openstack/clouds.yaml + group: slurm + mode: '0640' # (owner can read/write, group can read, others have no access) + - name: Create pinning configuration for slurm-bibigrid version 23.11.* copy: content: | From f747b9f3722af134dd48192743a210c7a58cb014 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 13 Sep 2024 14:59:40 +0200 Subject: [PATCH 135/145] moved openstack credentials permission change to server only --- .../playbook/roles/bibigrid/tasks/042-slurm-server.yaml | 6 ++++++ resources/playbook/roles/bibigrid/tasks/042-slurm.yaml | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml index e3a28ac6..5f6bb2d3 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml @@ -1,3 +1,9 @@ +- name: Change group ownership of OpenStack credentials file to slurm + file: + path: /etc/openstack/clouds.yaml + group: slurm + mode: '0640' # (owner can read/write, group can read, others have no access) + - name: Create slurm db mysql_db: name: "{{ slurm_conf.db }}" diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml index 7901997a..d80fb67c 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml @@ -9,12 +9,6 @@ uid: 64030 group: slurm -- name: Change group ownership of OpenStack credentials file to slurm - file: - path: /etc/openstack/clouds.yaml - group: slurm - mode: '0640' # (owner can read/write, group can read, others have no access) - - name: Create pinning configuration for slurm-bibigrid version 23.11.* copy: content: | From 882f8d694f5ba91ed1ffe91a7ce5bc1f40dd59c3 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 13 Sep 2024 15:29:23 +0200 Subject: [PATCH 136/145] added '' to 3.10 --- .github/workflows/linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index cca81f8d..ab0c8837 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -8,7 +8,7 @@ jobs: - name: Set up Python 3.10 uses: actions/setup-python@v4 with: - python-version: 3.10 + python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip From 2dc952952fa25d9394f416f072d0a0012c6c2de6 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 13 Sep 2024 16:02:37 +0200 Subject: [PATCH 137/145] converted implicit to explicit octet notation --- .../bibigrid/tasks/000-add-ip-routes.yaml | 8 ++--- .../roles/bibigrid/tasks/001-apt.yaml | 2 +- .../bibigrid/tasks/002-wireguard-vpn.yaml | 4 +-- .../bibigrid/tasks/011-zabbix-agent.yaml | 4 +-- .../bibigrid/tasks/011-zabbix-server.yaml | 2 +- .../tasks/020-disk-server-automount.yaml | 2 +- .../roles/bibigrid/tasks/020-disk-worker.yaml | 2 +- .../roles/bibigrid/tasks/020-disk.yaml | 8 ++--- .../roles/bibigrid/tasks/025-nfs-server.yaml | 2 +- .../roles/bibigrid/tasks/025-nfs-worker.yaml | 2 +- .../roles/bibigrid/tasks/030-docker.yaml | 2 +- .../bibigrid/tasks/042-slurm-server.yaml | 32 +++++++++---------- .../roles/bibigrid/tasks/042-slurm.yaml | 16 +++++----- 13 files changed, 43 insertions(+), 43 deletions(-) diff --git a/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml b/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml index e193ccc6..732ed9ce 100644 --- a/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml +++ b/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml @@ -15,7 +15,7 @@ dest: "{{ item.path }}.disabled" owner: root group: root - mode: 0644 + mode: 0o644 remote_src: true with_items: "{{ collected_files.files }}" - name: Remove collected files @@ -30,7 +30,7 @@ line: "network: {config: disabled}" owner: root group: root - mode: 0644 + mode: 0o644 create: true - name: Generate location specific worker userdata @@ -39,7 +39,7 @@ dest: "/etc/systemd/network/bibigrid_ens3.network" owner: root group: systemd-network - mode: 0640 + mode: 0o640 become: true notify: - systemd-networkd restart @@ -50,7 +50,7 @@ dest: "/etc/systemd/network/bibigrid_ens3.link" owner: root group: systemd-network - mode: 0640 + mode: 0o640 become: true notify: - systemd-networkd restart diff --git a/resources/playbook/roles/bibigrid/tasks/001-apt.yaml b/resources/playbook/roles/bibigrid/tasks/001-apt.yaml index 0ca1c17b..7fdf90dc 100644 --- a/resources/playbook/roles/bibigrid/tasks/001-apt.yaml +++ b/resources/playbook/roles/bibigrid/tasks/001-apt.yaml @@ -8,7 +8,7 @@ dest: /etc/apt/apt.conf.d/20auto-upgrades owner: root group: root - mode: 0644 + mode: 0o644 - name: Wait for cloud-init / user-data to finish command: cloud-init status --wait diff --git a/resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml b/resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml index d70f10a5..cb293246 100644 --- a/resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml +++ b/resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml @@ -9,7 +9,7 @@ dest: /etc/systemd/network/wg0.netdev owner: root group: systemd-network - mode: 0640 + mode: 0o640 become: true notify: - systemd-networkd restart @@ -20,7 +20,7 @@ dest: /etc/systemd/network/wg0.network owner: root group: systemd-network - mode: 0640 + mode: 0o640 become: true notify: systemd-networkd restart diff --git a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml index 9a658689..8249f68f 100644 --- a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml +++ b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml @@ -11,7 +11,7 @@ file: path: /etc/zabbix/zabbix_agentd.d/ state: directory - mode: 0755 + mode: 0o755 - name: Create zabbix_agent log directory file: @@ -19,7 +19,7 @@ state: directory owner: zabbix group: zabbix - mode: 0755 + mode: 0o755 - name: Adjust zabbix agent configuration template: diff --git a/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yaml b/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yaml index df2b5def..461b0519 100644 --- a/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yaml @@ -69,7 +69,7 @@ template: src: zabbix/zabbix_server.conf.j2 dest: /etc/zabbix/zabbix_server.conf - mode: 0644 + mode: 0o644 notify: zabbix-server - name: Start and Enable zabbix-server diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml index 8e4b5f49..a33ffd91 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml @@ -21,7 +21,7 @@ file: path: "/vol/{{ item.mount_point }}" state: directory - mode: '0755' + mode: 0o755 owner: root group: '{{ ansible_distribution | lower }}' diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-worker.yaml b/resources/playbook/roles/bibigrid/tasks/020-disk-worker.yaml index 4acf3b56..f558063b 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-worker.yaml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-worker.yaml @@ -11,4 +11,4 @@ file: path: /vol/scratch state: directory - mode: 0777 + mode: 0o777 diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk.yaml b/resources/playbook/roles/bibigrid/tasks/020-disk.yaml index 08b4b980..92338663 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk.yaml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk.yaml @@ -4,7 +4,7 @@ state: directory owner: root group: '{{ ansible_distribution | lower }}' - mode: 0775 + mode: 0o775 - name: Create /vol/ directory with rights 0775 owned by root file: @@ -12,13 +12,13 @@ state: directory owner: root group: '{{ ansible_distribution | lower }}' - mode: 0775 + mode: 0o775 - name: Create /vol/spool/ directory with rights 0777 file: path: /vol/spool/ state: directory - mode: 0777 + mode: 0o777 - name: Change rights of /opt directory to 0775 and set group to ansible_distribution file: @@ -26,7 +26,7 @@ state: directory owner: root group: '{{ ansible_distribution | lower }}' - mode: 0775 + mode: 0o775 - name: Create link in '{{ ansible_distribution | lower }}' home file: diff --git a/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml b/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml index c7030971..cb91d8f3 100644 --- a/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml @@ -9,7 +9,7 @@ state: directory owner: root group: root - mode: 0777 + mode: 0o777 with_items: - "{{ nfs_mounts }}" diff --git a/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml b/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml index 71217302..21d2c264 100644 --- a/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml +++ b/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml @@ -16,7 +16,7 @@ state: directory owner: root group: root - mode: 0777 + mode: 0o777 with_items: - "{{ nfs_mounts }}" diff --git a/resources/playbook/roles/bibigrid/tasks/030-docker.yaml b/resources/playbook/roles/bibigrid/tasks/030-docker.yaml index 07830b2b..8a681a81 100644 --- a/resources/playbook/roles/bibigrid/tasks/030-docker.yaml +++ b/resources/playbook/roles/bibigrid/tasks/030-docker.yaml @@ -13,7 +13,7 @@ dest: /etc/docker/daemon.json owner: root group: root - mode: 0644 + mode: 0o644 notify: docker diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml index 5f6bb2d3..0a01914b 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml @@ -2,7 +2,7 @@ file: path: /etc/openstack/clouds.yaml group: slurm - mode: '0640' # (owner can read/write, group can read, others have no access) + mode: 0o640 # (owner can read/write, group can read, others have no access) - name: Create slurm db mysql_db: @@ -24,7 +24,7 @@ dest: /etc/slurm/slurmdbd.conf owner: slurm group: root - mode: "0600" + mode: 0o600 - name: Generate random JWT Secret command: @@ -36,7 +36,7 @@ path: /etc/slurm/jwt-secret.key owner: slurm group: slurm - mode: "0600" + mode: 0o600 - name: Copy env file for configuration of slurmrestd copy: @@ -44,14 +44,14 @@ dest: /etc/default/slurmrestd owner: root group: root - mode: "0644" + mode: 0o644 - name: Create system overrides directories (slurmdbdm slurmrestd) file: path: "/etc/systemd/system/{{ item }}.service.d" group: root owner: root - mode: "0755" + mode: 0o755 state: directory with_items: - slurmdbd @@ -61,7 +61,7 @@ copy: src: "slurm/systemd/{{ item }}.override" dest: "/etc/systemd/system/{{ item }}.service.d/override.conf" - mode: "0644" + mode: 0o644 owner: root group: root with_items: @@ -81,13 +81,13 @@ group: ansible path: /opt/slurm/ state: directory - mode: "0770" + mode: 0o770 - name: Ensures /etc/slurm dir exists file: path: /etc/slurm/ state: directory - mode: 0755 + mode: 0o755 - name: Ensures /opt/slurm/.ssh/ dir exists file: @@ -95,7 +95,7 @@ group: slurm owner: slurm state: directory - mode: 0700 + mode: 0o700 - name: Copy private key (openstack keypair) copy: @@ -103,7 +103,7 @@ dest: /opt/slurm/.ssh/id_ecdsa owner: slurm group: slurm - mode: "0600" + mode: 0o600 - name: Copy create program script (power) copy: @@ -111,7 +111,7 @@ dest: /opt/slurm/create.sh owner: slurm group: ansible - mode: "0550" + mode: 0o550 - name: Copy terminate program script (power) copy: @@ -119,7 +119,7 @@ dest: /opt/slurm/terminate.sh owner: slurm group: ansible - mode: "0550" + mode: 0o550 - name: Copy fail program script (power) copy: @@ -127,7 +127,7 @@ dest: /opt/slurm/fail.sh owner: slurm group: ansible - mode: "0550" + mode: 0o550 - name: Copy "create_server.py" script copy: @@ -135,7 +135,7 @@ dest: /usr/local/bin/create_server.py owner: slurm group: ansible - mode: "0750" + mode: 0o750 - name: Copy "delete_server.py" script copy: @@ -143,7 +143,7 @@ dest: /usr/local/bin/delete_server.py owner: slurm group: ansible - mode: "0750" + mode: 0o750 - name: Install python dependencies pip: @@ -169,7 +169,7 @@ dest: "/opt/slurm/userdata_{{ hostvars[item].cloud_identifier }}.txt" owner: slurm group: ansible - mode: "0640" + mode: 0o640 with_items: "{{ groups.vpngtw + groups.master }}" - name: Enable slurmdbd and slurmrestd services diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml index d80fb67c..3008d3cb 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml @@ -16,7 +16,7 @@ Pin: version 23.11.* Pin-Priority: 1001 dest: /etc/apt/preferences.d/slurm-bibigrid - mode: '0311' + mode: 0o311 - name: Install slurm-bibigrid package apt: @@ -34,7 +34,7 @@ dest: /etc/munge/munge.key owner: munge group: munge - mode: 0600 + mode: 0o600 notify: - munge @@ -42,7 +42,7 @@ file: path: "{{ item }}" state: directory - mode: 0775 + mode: 0o775 owner: root group: slurm with_items: @@ -55,7 +55,7 @@ path: "/etc/systemd/system/{{ item }}.service.d" group: root owner: root - mode: "0755" + mode: 0o755 state: directory with_items: - slurmd @@ -65,7 +65,7 @@ copy: src: "slurm/systemd/{{ item }}.override" dest: "/etc/systemd/system/{{ item }}.service.d/override.conf" - mode: "0644" + mode: 0o644 owner: root group: root with_items: @@ -89,7 +89,7 @@ dest: /etc/slurm/slurm.conf owner: slurm group: root - mode: 0444 + mode: 0o444 - name: Create Job Container configuration template: @@ -97,7 +97,7 @@ dest: /etc/slurm/job_container.conf owner: slurm group: root - mode: 0444 + mode: 0o444 - name: Slurm cgroup configuration copy: @@ -105,7 +105,7 @@ dest: /etc/slurm/cgroup.conf owner: slurm group: root - mode: 0444 + mode: 0o444 - name: Restart slurmd systemd: From 2c34c0359be4425a918000b63e8b4252d036c679 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 13 Sep 2024 16:27:15 +0200 Subject: [PATCH 138/145] added "" and fixed a few more implicit octets --- .../bibigrid/tasks/000-add-ip-routes.yaml | 8 ++--- .../tasks/000-playbook-rights-server.yaml | 2 +- .../roles/bibigrid/tasks/001-apt.yaml | 2 +- .../bibigrid/tasks/002-wireguard-vpn.yaml | 4 +-- .../roles/bibigrid/tasks/003-dns.yaml | 6 ++-- .../roles/bibigrid/tasks/010-bin-server.yaml | 4 +-- .../bibigrid/tasks/011-zabbix-agent.yaml | 8 ++--- .../bibigrid/tasks/011-zabbix-server.yaml | 10 +++---- .../tasks/020-disk-server-automount.yaml | 2 +- .../roles/bibigrid/tasks/020-disk-worker.yaml | 2 +- .../roles/bibigrid/tasks/020-disk.yaml | 8 ++--- .../roles/bibigrid/tasks/025-nfs-server.yaml | 2 +- .../roles/bibigrid/tasks/025-nfs-worker.yaml | 2 +- .../roles/bibigrid/tasks/030-docker.yaml | 2 +- .../bibigrid/tasks/042-slurm-server.yaml | 30 +++++++++---------- .../roles/bibigrid/tasks/042-slurm.yaml | 16 +++++----- .../roles/bibigrid/tasks/900-theia.yaml | 10 +++---- .../tasks/999-apt-reactivate-auto-update.yaml | 2 +- 18 files changed, 60 insertions(+), 60 deletions(-) diff --git a/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml b/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml index 732ed9ce..7c10866c 100644 --- a/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml +++ b/resources/playbook/roles/bibigrid/tasks/000-add-ip-routes.yaml @@ -15,7 +15,7 @@ dest: "{{ item.path }}.disabled" owner: root group: root - mode: 0o644 + mode: "0o644" remote_src: true with_items: "{{ collected_files.files }}" - name: Remove collected files @@ -30,7 +30,7 @@ line: "network: {config: disabled}" owner: root group: root - mode: 0o644 + mode: "0o644" create: true - name: Generate location specific worker userdata @@ -39,7 +39,7 @@ dest: "/etc/systemd/network/bibigrid_ens3.network" owner: root group: systemd-network - mode: 0o640 + mode: "0o640" become: true notify: - systemd-networkd restart @@ -50,7 +50,7 @@ dest: "/etc/systemd/network/bibigrid_ens3.link" owner: root group: systemd-network - mode: 0o640 + mode: "0o640" become: true notify: - systemd-networkd restart diff --git a/resources/playbook/roles/bibigrid/tasks/000-playbook-rights-server.yaml b/resources/playbook/roles/bibigrid/tasks/000-playbook-rights-server.yaml index 438e17ac..57ae7161 100644 --- a/resources/playbook/roles/bibigrid/tasks/000-playbook-rights-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/000-playbook-rights-server.yaml @@ -9,4 +9,4 @@ path: /opt/playbook/ state: directory recurse: true - mode: "0770" + mode: "0o770" diff --git a/resources/playbook/roles/bibigrid/tasks/001-apt.yaml b/resources/playbook/roles/bibigrid/tasks/001-apt.yaml index 7fdf90dc..2fda459f 100644 --- a/resources/playbook/roles/bibigrid/tasks/001-apt.yaml +++ b/resources/playbook/roles/bibigrid/tasks/001-apt.yaml @@ -8,7 +8,7 @@ dest: /etc/apt/apt.conf.d/20auto-upgrades owner: root group: root - mode: 0o644 + mode: "0o644" - name: Wait for cloud-init / user-data to finish command: cloud-init status --wait diff --git a/resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml b/resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml index cb293246..3807c137 100644 --- a/resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml +++ b/resources/playbook/roles/bibigrid/tasks/002-wireguard-vpn.yaml @@ -9,7 +9,7 @@ dest: /etc/systemd/network/wg0.netdev owner: root group: systemd-network - mode: 0o640 + mode: "0o640" become: true notify: - systemd-networkd restart @@ -20,7 +20,7 @@ dest: /etc/systemd/network/wg0.network owner: root group: systemd-network - mode: 0o640 + mode: "0o640" become: true notify: systemd-networkd restart diff --git a/resources/playbook/roles/bibigrid/tasks/003-dns.yaml b/resources/playbook/roles/bibigrid/tasks/003-dns.yaml index 3515f86e..f739a0f0 100644 --- a/resources/playbook/roles/bibigrid/tasks/003-dns.yaml +++ b/resources/playbook/roles/bibigrid/tasks/003-dns.yaml @@ -18,21 +18,21 @@ template: src: dns/hosts.j2 dest: /etc/dnsmasq.hosts - mode: '0644' + mode: "0o644" notify: dnsmasq - name: Adjust dnsmasq.resolv.conf template: src: dns/resolv.conf.j2 dest: /etc/dnsmasq.resolv.conf - mode: '0644' + mode: "0o644" notify: dnsmasq - name: Adjust dnsmasq conf template: src: dns/dnsmasq.conf.j2 dest: /etc/dnsmasq.conf - mode: '0644' + mode: "0o644" notify: dnsmasq - name: Flush handlers diff --git a/resources/playbook/roles/bibigrid/tasks/010-bin-server.yaml b/resources/playbook/roles/bibigrid/tasks/010-bin-server.yaml index 49256c1a..9712adeb 100644 --- a/resources/playbook/roles/bibigrid/tasks/010-bin-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/010-bin-server.yaml @@ -14,7 +14,7 @@ copy: src: ~/bin dest: /usr/local - mode: '0775' + mode: "0o775" - name: Delete origin folder file: path: ~{{ ansible_facts.env.SUDO_USER }}/bin @@ -24,4 +24,4 @@ template: src: "bin/bibiname.j2" dest: "/usr/local/bin/bibiname" - mode: '0775' + mode: "0o775" diff --git a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml index 8249f68f..57d1260a 100644 --- a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml +++ b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml @@ -11,7 +11,7 @@ file: path: /etc/zabbix/zabbix_agentd.d/ state: directory - mode: 0o755 + mode: "0o755" - name: Create zabbix_agent log directory file: @@ -19,20 +19,20 @@ state: directory owner: zabbix group: zabbix - mode: 0o755 + mode: "0o755" - name: Adjust zabbix agent configuration template: src: zabbix/zabbix_agentd.conf.j2 dest: /etc/zabbix/zabbix_agentd.conf - mode: 0644 + mode: "0o644" notify: zabbix-agent - name: Copy Zabbix Host delete script copy: src: zabbix/zabbix_host_delete.py dest: /usr/local/bin/zabbix_host_delete.py - mode: 0755 + mode: 0o755" - name: Install zabbix python-api pip: diff --git a/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yaml b/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yaml index 461b0519..1a39ffd8 100644 --- a/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/011-zabbix-server.yaml @@ -69,7 +69,7 @@ template: src: zabbix/zabbix_server.conf.j2 dest: /etc/zabbix/zabbix_server.conf - mode: 0o644 + mode: "0o644" notify: zabbix-server - name: Start and Enable zabbix-server @@ -121,7 +121,7 @@ state: directory owner: root group: root - mode: '0755' + mode: "0o755" - name: Adjust zabbix web frontend configuration notify: apache2 @@ -130,12 +130,12 @@ template: src: zabbix/apache.conf.j2 dest: /etc/zabbix/apache.conf - mode: 0644 + mode: "0o644" - name: Adjust zabbix.conf template: src: zabbix/zabbix.conf.php.j2 dest: /etc/zabbix/web/zabbix.conf.php - mode: 0644 + mode: "0o644" - name: Start and enable apache web server systemd: @@ -147,7 +147,7 @@ copy: src: zabbix/index.html dest: /var/www/html/index.html - mode: 0644 + mode: "0o644" - name: Force all notified handlers to run at this point meta: flush_handlers diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml index a33ffd91..4e7327d7 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml @@ -21,7 +21,7 @@ file: path: "/vol/{{ item.mount_point }}" state: directory - mode: 0o755 + mode: "0o755" owner: root group: '{{ ansible_distribution | lower }}' diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-worker.yaml b/resources/playbook/roles/bibigrid/tasks/020-disk-worker.yaml index f558063b..f33fcb6b 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-worker.yaml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-worker.yaml @@ -11,4 +11,4 @@ file: path: /vol/scratch state: directory - mode: 0o777 + mode: "0o777" diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk.yaml b/resources/playbook/roles/bibigrid/tasks/020-disk.yaml index 92338663..9948ff3c 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk.yaml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk.yaml @@ -4,7 +4,7 @@ state: directory owner: root group: '{{ ansible_distribution | lower }}' - mode: 0o775 + mode: "0o775" - name: Create /vol/ directory with rights 0775 owned by root file: @@ -12,13 +12,13 @@ state: directory owner: root group: '{{ ansible_distribution | lower }}' - mode: 0o775 + mode: "0o775" - name: Create /vol/spool/ directory with rights 0777 file: path: /vol/spool/ state: directory - mode: 0o777 + mode: "0o777" - name: Change rights of /opt directory to 0775 and set group to ansible_distribution file: @@ -26,7 +26,7 @@ state: directory owner: root group: '{{ ansible_distribution | lower }}' - mode: 0o775 + mode: "0o775" - name: Create link in '{{ ansible_distribution | lower }}' home file: diff --git a/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml b/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml index cb91d8f3..03bce9ca 100644 --- a/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml @@ -9,7 +9,7 @@ state: directory owner: root group: root - mode: 0o777 + mode: "0o777" with_items: - "{{ nfs_mounts }}" diff --git a/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml b/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml index 21d2c264..c45c3e9a 100644 --- a/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml +++ b/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml @@ -16,7 +16,7 @@ state: directory owner: root group: root - mode: 0o777 + mode: "0o777" with_items: - "{{ nfs_mounts }}" diff --git a/resources/playbook/roles/bibigrid/tasks/030-docker.yaml b/resources/playbook/roles/bibigrid/tasks/030-docker.yaml index 8a681a81..b4aad03f 100644 --- a/resources/playbook/roles/bibigrid/tasks/030-docker.yaml +++ b/resources/playbook/roles/bibigrid/tasks/030-docker.yaml @@ -13,7 +13,7 @@ dest: /etc/docker/daemon.json owner: root group: root - mode: 0o644 + mode: "0o644" notify: docker diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml index 0a01914b..54b7b30d 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml @@ -2,7 +2,7 @@ file: path: /etc/openstack/clouds.yaml group: slurm - mode: 0o640 # (owner can read/write, group can read, others have no access) + mode: "0o640" # (owner can read/write, group can read, others have no access) - name: Create slurm db mysql_db: @@ -24,7 +24,7 @@ dest: /etc/slurm/slurmdbd.conf owner: slurm group: root - mode: 0o600 + mode: "0o600" - name: Generate random JWT Secret command: @@ -36,7 +36,7 @@ path: /etc/slurm/jwt-secret.key owner: slurm group: slurm - mode: 0o600 + mode: "0o600" - name: Copy env file for configuration of slurmrestd copy: @@ -44,7 +44,7 @@ dest: /etc/default/slurmrestd owner: root group: root - mode: 0o644 + mode: "0o644" - name: Create system overrides directories (slurmdbdm slurmrestd) file: @@ -61,7 +61,7 @@ copy: src: "slurm/systemd/{{ item }}.override" dest: "/etc/systemd/system/{{ item }}.service.d/override.conf" - mode: 0o644 + mode: "0o644" owner: root group: root with_items: @@ -81,13 +81,13 @@ group: ansible path: /opt/slurm/ state: directory - mode: 0o770 + mode: "0o770" - name: Ensures /etc/slurm dir exists file: path: /etc/slurm/ state: directory - mode: 0o755 + mode: "0o755" - name: Ensures /opt/slurm/.ssh/ dir exists file: @@ -95,7 +95,7 @@ group: slurm owner: slurm state: directory - mode: 0o700 + mode: "0o700" - name: Copy private key (openstack keypair) copy: @@ -103,7 +103,7 @@ dest: /opt/slurm/.ssh/id_ecdsa owner: slurm group: slurm - mode: 0o600 + mode: "0o600" - name: Copy create program script (power) copy: @@ -111,7 +111,7 @@ dest: /opt/slurm/create.sh owner: slurm group: ansible - mode: 0o550 + mode: "0o550" - name: Copy terminate program script (power) copy: @@ -119,7 +119,7 @@ dest: /opt/slurm/terminate.sh owner: slurm group: ansible - mode: 0o550 + mode: "0o550" - name: Copy fail program script (power) copy: @@ -127,7 +127,7 @@ dest: /opt/slurm/fail.sh owner: slurm group: ansible - mode: 0o550 + mode: "0o550" - name: Copy "create_server.py" script copy: @@ -135,7 +135,7 @@ dest: /usr/local/bin/create_server.py owner: slurm group: ansible - mode: 0o750 + mode: "0o750" - name: Copy "delete_server.py" script copy: @@ -143,7 +143,7 @@ dest: /usr/local/bin/delete_server.py owner: slurm group: ansible - mode: 0o750 + mode: "0o750" - name: Install python dependencies pip: @@ -169,7 +169,7 @@ dest: "/opt/slurm/userdata_{{ hostvars[item].cloud_identifier }}.txt" owner: slurm group: ansible - mode: 0o640 + mode: "0o640" with_items: "{{ groups.vpngtw + groups.master }}" - name: Enable slurmdbd and slurmrestd services diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml index 3008d3cb..c141009a 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm.yaml @@ -16,7 +16,7 @@ Pin: version 23.11.* Pin-Priority: 1001 dest: /etc/apt/preferences.d/slurm-bibigrid - mode: 0o311 + mode: "0o311" - name: Install slurm-bibigrid package apt: @@ -34,7 +34,7 @@ dest: /etc/munge/munge.key owner: munge group: munge - mode: 0o600 + mode: "0o600" notify: - munge @@ -42,7 +42,7 @@ file: path: "{{ item }}" state: directory - mode: 0o775 + mode: "0o775" owner: root group: slurm with_items: @@ -55,7 +55,7 @@ path: "/etc/systemd/system/{{ item }}.service.d" group: root owner: root - mode: 0o755 + mode: "0o755" state: directory with_items: - slurmd @@ -65,7 +65,7 @@ copy: src: "slurm/systemd/{{ item }}.override" dest: "/etc/systemd/system/{{ item }}.service.d/override.conf" - mode: 0o644 + mode: "0o644" owner: root group: root with_items: @@ -89,7 +89,7 @@ dest: /etc/slurm/slurm.conf owner: slurm group: root - mode: 0o444 + mode: "0o444" - name: Create Job Container configuration template: @@ -97,7 +97,7 @@ dest: /etc/slurm/job_container.conf owner: slurm group: root - mode: 0o444 + mode: "0o444" - name: Slurm cgroup configuration copy: @@ -105,7 +105,7 @@ dest: /etc/slurm/cgroup.conf owner: slurm group: root - mode: 0o444 + mode: "0o444" - name: Restart slurmd systemd: diff --git a/resources/playbook/roles/bibigrid/tasks/900-theia.yaml b/resources/playbook/roles/bibigrid/tasks/900-theia.yaml index af8b19a8..57c8de26 100644 --- a/resources/playbook/roles/bibigrid/tasks/900-theia.yaml +++ b/resources/playbook/roles/bibigrid/tasks/900-theia.yaml @@ -12,7 +12,7 @@ file: path: "{{ nvm_install_dir }}" state: directory - mode: 0755 + mode: "0o755" - name: Set fact 'theia_ide_user' when not defined set_fact: @@ -75,13 +75,13 @@ file: path: "{{ theia_ide_install_dir }}" state: directory - mode: 0755 + mode: "0o755" - name: Copy IDE configuration to IDE build dir template: src: theia/package.json.j2 dest: "{{ theia_ide_install_dir }}/package.json" - mode: 0644 + mode: "0o644" - name: Build ide shell: | @@ -98,13 +98,13 @@ template: src: theia/theia-ide.sh.j2 dest: "{{ theia_ide_install_dir }}/theia-ide.sh" - mode: 0755 + mode: "0o755" - name: Generate systemd service template: src: theia/theia-ide.service.j2 dest: /etc/systemd/system/theia-ide.service - mode: 0644 + mode: "0o644" - name: Enable and Start service systemd: diff --git a/resources/playbook/roles/bibigrid/tasks/999-apt-reactivate-auto-update.yaml b/resources/playbook/roles/bibigrid/tasks/999-apt-reactivate-auto-update.yaml index 8270a8fa..ee7fb676 100644 --- a/resources/playbook/roles/bibigrid/tasks/999-apt-reactivate-auto-update.yaml +++ b/resources/playbook/roles/bibigrid/tasks/999-apt-reactivate-auto-update.yaml @@ -4,4 +4,4 @@ dest: /etc/apt/apt.conf.d/20auto-upgrades owner: root group: root - mode: 0644 + mode: "0o644" From 644bb8474f7b7fa15d378a00db4c6796e79396a7 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 13 Sep 2024 16:29:28 +0200 Subject: [PATCH 139/145] added "" --- resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml index 54b7b30d..059e11ef 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml @@ -51,7 +51,7 @@ path: "/etc/systemd/system/{{ item }}.service.d" group: root owner: root - mode: 0o755 + mode: "0o755" state: directory with_items: - slurmdbd From efe66582027a624fbfd411032914647c153e0d39 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Fri, 13 Sep 2024 16:33:17 +0200 Subject: [PATCH 140/145] added missing " --- resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml index 57d1260a..c8aebe50 100644 --- a/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml +++ b/resources/playbook/roles/bibigrid/tasks/011-zabbix-agent.yaml @@ -32,7 +32,7 @@ copy: src: zabbix/zabbix_host_delete.py dest: /usr/local/bin/zabbix_host_delete.py - mode: 0o755" + mode: "0o755" - name: Install zabbix python-api pip: From e30748ee3ff4922b03c39ea63a2f707244b9aab6 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 16 Sep 2024 12:54:39 +0200 Subject: [PATCH 141/145] added allow_agent=False to further prevent BiBiGrid from looking for keys --- bibigrid/core/utility/handler/ssh_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index 3c42cd5e..62fa312f 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -111,7 +111,7 @@ def is_active(client, paramiko_key, ssh_data, log): log.info(f"Attempt {attempts}/{ssh_data['timeout']}. Connecting to {ssh_data['floating_ip']}") client.connect(hostname=ssh_data['gateway'].get("ip") or ssh_data['floating_ip'], username=ssh_data['username'], pkey=paramiko_key, timeout=7, - auth_timeout=ssh_data['timeout'], port=port, look_for_keys=False) + auth_timeout=ssh_data['timeout'], port=port, look_for_keys=False, allow_agent=False) establishing_connection = False log.info(f"Successfully connected to {ssh_data['floating_ip']}.") except paramiko.ssh_exception.NoValidConnectionsError as exc: From 7c5477371dd85b5f6631a334d607ba631547faee Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 23 Sep 2024 15:22:33 +0200 Subject: [PATCH 142/145] removed hardcoded /vol/ --- .../roles/bibigrid/tasks/020-disk-server-automount.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml index 4e7327d7..fd8c4619 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml @@ -19,7 +19,7 @@ - name: Create mount folders if they don't exist file: - path: "/vol/{{ item.mount_point }}" + path: "{{ item.mount_point }}" state: directory mode: "0o755" owner: root From fc0aa01c4a21c93ece2da26ba2a2385a72d23edf Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 23 Sep 2024 15:22:59 +0200 Subject: [PATCH 143/145] updated versions --- requirements-dev.txt | 2 +- requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 6e4fda76..0aee6fe8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,2 @@ -ansible_lint==24.7.0 +ansible_lint==24.9.0 pylint==2.14.5 diff --git a/requirements.txt b/requirements.txt index f6920512..acb744f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ openstacksdk==0.62 mergedeep~=1.3.4 -paramiko~=3.4.1 +paramiko~=3.5.0 python-cinderclient python-keystoneclient python-novaclient @@ -13,7 +13,7 @@ seedir~=0.5.0 cryptography~=43.0.1 uvicorn~=0.30.6 fastapi~=0.101.0 -pydantic~=2.9.1 +pydantic~=2.9.2 keystoneauth1~=5.1.0 -filelock~=3.16.0 +filelock~=3.16.1 schema~=0.7.7 \ No newline at end of file From 6d400e72dfd8306e44dd2e306b92ea0fbb549b3f Mon Sep 17 00:00:00 2001 From: XaverStiensmeier Date: Mon, 23 Sep 2024 15:23:21 +0200 Subject: [PATCH 144/145] removed unnecessary comments and commented out Workflow execution --- .../roles_user/resistance_nextflow/tasks/main.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml b/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml index 0b497694..9501559c 100644 --- a/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml +++ b/resources/playbook/roles_user/resistance_nextflow/tasks/main.yml @@ -11,8 +11,8 @@ - name: Install Java JRE on Debian/Ubuntu become: True apt: - name: default-jre # Package name for Java JRE on Debian-based systems - state: present # Ensure that the package is present, you can use "latest" as well + name: default-jre + state: present - name: Get Nextflow shell: wget -qO- https://get.nextflow.io | bash @@ -26,8 +26,8 @@ group: ubuntu mode: '0775' -- name: Execute Nextflow workflow - become_user: ubuntu - shell: ./nextflow run resFinder.nf -profile slurm - args: - chdir: "/vol/spool" # Change to the directory where your workflow resides +#- name: Execute Nextflow workflow +# become_user: ubuntu +# shell: ./nextflow run resFinder.nf -profile slurm # run your workflow +# args: +# chdir: "/vol/spool" # Change to the directory where your workflow resides From 756916345191d3a2ec49574f4787611c22adc890 Mon Sep 17 00:00:00 2001 From: XaverStiensmeier <36056823+XaverStiensmeier@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:17:27 +0100 Subject: [PATCH 145/145] 545 allow attached volumes (#562) * renamed volumeSize to bootVolumeSize to avoid name issues * added implementation for adding volumes to permanent workers (they are not deleted) * implemented creating and terminating volumes without filesystem for permanent workers * fully working for permanent workers. masterMount is broken now, but also replaced. Will be fixed. * Added volume creation to create_server. Not yet working. * hostvar for each host * Fixed information handling and naming issues * fixed host yaml creation * removed unnecessary prints * improved readability fixed minor bugs * added volume deletion and set volume_ap_version explicitly * removed prints from test_provider.py * improved readability greatly. Fixed overwriting host vars bug * snapshot and existing volumes can now be attached to master and workers on startup * snapshot and existing volumes can now be attached to master and workers on startup * removed mountPoint from a log message in case no mount point is specified * fixed lsblk not finding item.device due to race condition * improved comments and naming * removed server automount. This is now handled by a single automount task for both master and workers * allows nor to start new permanent volumes if a name is given. One could consider adding tmp to not named volumes for additional clarity * fixed wrong function call * renamed nfs_mount to nfs_shares * added semipermanent as an option * fixed wrong method of default values for Ansible * started reworking * added volumes and changed bootVolumes * updated bibigrid.yaml and aligned naming of bootVolume and volume * added newline at end of file * removed superfluous provider paramter * pleased linting * removed argument from function call * moved host vars creation, vars deletion, added comments * largely reworked how volumes are attached to servers to be more explicit * small naming fixes * updated priority order of permanent and semiPermanent. Updated documentation to new explicit bool setup. Added type as a key. * fixed bug regarding dontUploadCredentials * updated schema validation * Update linting.yml * Update linting.yml * Update linting.yml * Update linting.yml * added __init__.py where appropriate * update bibigrid.yaml for more explicit volumes documentation * volumes are now validated and fixed old state of masterInstance in validate_schema.py * Update linting.yml * Update linting.yml * fixed longtime naming bug for unknown openstack exceptions * saves more info in .mem file * moved structure of tests and added a basic integration_test file that needs to be expanded and improved * moved tests * added "not ready yet" * updated bootVolume documentation * moved tests added __init__.py files for better discovery. minor fixes * updated tests and comments * updated tests and comments * updated tests, code and comments for ansible_configuration * updated tests for ansible_configurator * fixed test_ansible_configurator.py * fixed test_configuration_handler.py * improved exception messages * pleased ansible linter * fixed terminate return values test * improved naming * added tests to make sure that server regex only deletes bibigrid servers with fitting cluster id and same for volumes * pleased pylint * fixed validation issue when using exists in master * removed forgotten print * fixed description bug * final bugfixes * pleased linter * fixed too many positional arguments --- .github/workflows/linting.yml | 6 +- .pylintrc | 4 +- bibigrid.yaml | 159 ++++---- bibigrid/__init__.py | 0 bibigrid/core/__init__.py | 0 bibigrid/core/actions/__init__.py | 0 bibigrid/core/actions/create.py | 228 +++++++---- bibigrid/core/actions/terminate.py | 56 ++- bibigrid/core/provider.py | 52 ++- bibigrid/core/startup.py | 1 + bibigrid/core/utility/__init__.py | 0 bibigrid/core/utility/ansible_configurator.py | 189 ++++++---- bibigrid/core/utility/handler/__init__.py | 0 bibigrid/core/utility/handler/ssh_handler.py | 32 +- bibigrid/core/utility/paths/__init__.py | 0 .../core/utility/validate_configuration.py | 71 ++-- bibigrid/core/utility/validate_schema.py | 64 +++- bibigrid/core/utility/wireguard/__init__.py | 0 bibigrid/models/__init__.py | 0 bibigrid/models/return_threading.py | 2 +- bibigrid/openstack/__init__.py | 0 bibigrid/openstack/openstack_provider.py | 46 ++- .../markdown/features/configuration.md | 98 +++-- requirements-dev.txt | 4 +- .../bibigrid/files/slurm/create_server.py | 216 ++++++++--- .../bibigrid/files/slurm/delete_server.py | 12 +- ...automount.yaml => 020-disk-automount.yaml} | 13 +- .../roles/bibigrid/tasks/020-disk-server.yaml | 5 - .../roles/bibigrid/tasks/020-disk.yaml | 5 + .../roles/bibigrid/tasks/025-nfs-server.yaml | 6 +- .../roles/bibigrid/tasks/025-nfs-worker.yaml | 6 +- .../bibigrid/tasks/042-slurm-server.yaml | 18 +- tests/__init__.py | 0 tests/integration/__init__.py | 0 tests/integration/integration_test.py | 101 +++++ tests/test_terminate_cluster.py | 50 --- tests/unit_tests/__init__.py | 0 tests/unit_tests/provider/__init__.py | 0 .../provider/test_provider.py | 41 +- tests/{ => unit_tests}/startup_tests.py | 11 +- .../test_ansible_configurator.py | 356 +++++++++++++----- tests/{ => unit_tests}/test_check.py | 0 .../test_configuration_handler.py | 67 +++- tests/{ => unit_tests}/test_create.py | 106 ++---- tests/{ => unit_tests}/test_id_generation.py | 0 tests/{ => unit_tests}/test_list_clusters.py | 0 .../{ => unit_tests}/test_provider_handler.py | 0 .../{ => unit_tests}/test_return_threading.py | 0 tests/{ => unit_tests}/test_ssh_handler.py | 0 tests/{ => unit_tests}/test_startup.py | 0 tests/unit_tests/test_terminate_cluster.py | 151 ++++++++ .../test_validate_configuration.py | 43 +-- .../{ => unit_tests}/test_validate_schema.py | 0 53 files changed, 1507 insertions(+), 712 deletions(-) create mode 100644 bibigrid/__init__.py create mode 100644 bibigrid/core/__init__.py create mode 100644 bibigrid/core/actions/__init__.py create mode 100644 bibigrid/core/utility/__init__.py create mode 100644 bibigrid/core/utility/handler/__init__.py create mode 100644 bibigrid/core/utility/paths/__init__.py create mode 100644 bibigrid/core/utility/wireguard/__init__.py create mode 100644 bibigrid/models/__init__.py create mode 100644 bibigrid/openstack/__init__.py rename resources/playbook/roles/bibigrid/tasks/{020-disk-server-automount.yaml => 020-disk-automount.yaml} (71%) create mode 100644 tests/__init__.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/integration_test.py delete mode 100644 tests/test_terminate_cluster.py create mode 100644 tests/unit_tests/__init__.py create mode 100644 tests/unit_tests/provider/__init__.py rename tests/{ => unit_tests}/provider/test_provider.py (89%) rename tests/{ => unit_tests}/startup_tests.py (75%) rename tests/{ => unit_tests}/test_ansible_configurator.py (76%) rename tests/{ => unit_tests}/test_check.py (100%) rename tests/{ => unit_tests}/test_configuration_handler.py (84%) rename tests/{ => unit_tests}/test_create.py (67%) rename tests/{ => unit_tests}/test_id_generation.py (100%) rename tests/{ => unit_tests}/test_list_clusters.py (100%) rename tests/{ => unit_tests}/test_provider_handler.py (100%) rename tests/{ => unit_tests}/test_return_threading.py (100%) rename tests/{ => unit_tests}/test_ssh_handler.py (100%) rename tests/{ => unit_tests}/test_startup.py (100%) create mode 100644 tests/unit_tests/test_terminate_cluster.py rename tests/{ => unit_tests}/test_validate_configuration.py (90%) rename tests/{ => unit_tests}/test_validate_schema.py (100%) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index ab0c8837..cd1a8a7a 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -5,10 +5,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Set up Python 3.10 + - name: Set up Python 3.12.3 uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.12.3' - name: Install dependencies run: | python -m pip install --upgrade pip @@ -17,4 +17,4 @@ jobs: - name: ansible_lint run: ansible-lint resources/playbook/roles/bibigrid/tasks/main.yaml - name: pylint_lint - run: pylint bibigrid \ No newline at end of file + run: pylint bibigrid diff --git a/.pylintrc b/.pylintrc index 42d86e71..e1995c0d 100644 --- a/.pylintrc +++ b/.pylintrc @@ -562,8 +562,8 @@ min-public-methods=2 [EXCEPTIONS] # Exceptions that will emit a warning when caught. -overgeneral-exceptions=BaseException, - Exception +overgeneral-exceptions=builtins.BaseException, + builtins.Exception [STRING] diff --git a/bibigrid.yaml b/bibigrid.yaml index fa6d8f75..6d0d7299 100644 --- a/bibigrid.yaml +++ b/bibigrid.yaml @@ -1,29 +1,23 @@ - # See https://cloud.denbi.de/wiki/Tutorials/BiBiGrid/ (after update) - # See https://github.com/BiBiServ/bibigrid/blob/master/documentation/markdown/features/configuration.md - # First configuration also holds general cluster information and must include the master. - # All other configurations mustn't include another master, but exactly one vpngtw instead (keys like master). + # For an easy introduction see https://github.com/deNBI/bibigrid_clum + # For more detailed information see https://github.com/BiBiServ/bibigrid/blob/master/documentation/markdown/features/configuration.md -- infrastructure: openstack # former mode. Describes what cloud provider is used (others are not implemented yet) - cloud: openstack # name of clouds.yaml cloud-specification key (which is value to top level key clouds) +- # -- BEGIN: GENERAL CLUSTER INFORMATION -- + # The following options configure cluster wide keys + # Modify these according to your requirements - # -- BEGIN: GENERAL CLUSTER INFORMATION -- # sshTimeout: 5 # number of attempts to connect to instances during startup with delay in between - # cloudScheduling: - # sshTimeout: 5 # like sshTimeout but during the on demand scheduling on the running cluster - ## sshPublicKeyFiles listed here will be added to access the cluster. A temporary key is created by bibigrid itself. - #sshPublicKeyFiles: - # - [public key one] + ## sshPublicKeyFiles listed here will be added to the master's authorized_keys. A temporary key is stored at ~/.config/bibigrid/keys + # sshPublicKeyFiles: + # - [public key one] - ## Volumes and snapshots that will be mounted to master - #masterMounts: (optional) # WARNING: will overwrite unidentified filesystems - # - name: [volume name] - # mountPoint: [where to mount to] # (optional) + # masterMounts: DEPRECATED -- see `volumes` key for each instance instead - #nfsShares: /vol/spool/ is automatically created as a nfs - # - [nfsShare one] + # nfsShares: # list of nfs shares. /vol/spool/ is automatically created as an nfs if nfs is true + # - [nfsShare one] - # userRoles: # see ansible_hosts for all options + ## Ansible Related + # userRoles: # see ansible_hosts for all 'hosts' options # - hosts: # - "master" # roles: # roles placed in resources/playbook/roles_user @@ -31,75 +25,102 @@ # varsFiles: # (optional) # - [...] - ## Uncomment if you don't want assign a public ip to the master; for internal cluster (Tuebingen). + ## If you use a gateway or start a cluster from the cloud, your master does not need a public ip. # useMasterWithPublicIp: False # defaults True if False no public-ip (floating-ip) will be allocated # gateway: # if you want to use a gateway for create. # ip: # IP of gateway to use # portFunction: 30000 + oct4 # variables are called: oct1.oct2.oct3.oct4 - # deleteTmpKeypairAfter: False - # dontUploadCredentials: False + ## Only relevant for specific projects (e.g. SimpleVM) + # deleteTmpKeypairAfter: False # warning: if you don't pass a key via sshPublicKeyFiles you lose access! + # dontUploadCredentials: False # warning: enabling this prevents you from scheduling on demand! + + ## Additional Software + # zabbix: False + # nfs: False + # ide: False # installs a web ide on the master node. A nice way to view your cluster (like Visual Studio Code) + + ### Slurm Related + # elastic_scheduling: # for large or slow clusters increasing these timeouts might be necessary to avoid failures + # SuspendTimeout: 60 # after SuspendTimeout seconds, slurm allows to power up the node again + # ResumeTimeout: 1200 # if a node doesn't start in ResumeTimeout seconds, the start is considered failed. - # Other keys - these are default False - # Usually Ignored - ##localFS: True - ##localDNSlookup: True + # cloudScheduling: + # sshTimeout: 5 # like sshTimeout but during the on demand scheduling on the running cluster - #zabbix: True - #nfs: True - #ide: True # A nice way to view your cluster as if you were using Visual Studio Code + # useMasterAsCompute: True - useMasterAsCompute: True + # -- END: GENERAL CLUSTER INFORMATION -- - # bootFromVolume: False - # terminateBootVolume: True - # volumeSize: 50 - - # waitForServices: # existing service name that runs after an instance is launched. BiBiGrid's playbook will wait until service is "stopped" to avoid issues + # -- BEGIN: MASTER CLOUD INFORMATION -- + infrastructure: openstack # former mode. Describes what cloud provider is used (others are not implemented yet) + cloud: openstack # name of clouds.yaml cloud-specification key (which is value to top level key clouds) + + # waitForServices: # list of existing service names that affect apt. BiBiGrid's playbook will wait until service is "stopped" to avoid issues # - de.NBI_Bielefeld_environment.service # uncomment for cloud site Bielefeld - # master configuration + ## master configuration masterInstance: - type: # existing type/flavor on your cloud. See launch instance>flavor for options - image: # existing active image on your cloud. Consider using regex to prevent image updates from breaking your running cluster + type: # existing type/flavor from your cloud. See launch instance>flavor for options + image: # existing active image from your cloud. Consider using regex to prevent image updates from breaking your running cluster # features: # list + # - feature1 # partitions: # list - # bootVolume: None - # bootFromVolume: True - # terminateBootVolume: True - # volumeSize: 50 - - # -- END: GENERAL CLUSTER INFORMATION -- + # - partition1 + # bootVolume: # optional + # name: # optional; if you want to boot from a specific volume + # terminate: True # whether the volume is terminated on server termination + # size: 50 + # volumes: # optional + # - name: volumeName # empty for temporary volumes + # snapshot: snapshotName # optional; to create volume from a snapshot + # mountPoint: /vol/mountPath + # size: 50 + # fstype: ext4 # must support chown + # type: # storage type; available values depend on your location; for Bielefeld CEPH_HDD, CEPH_NVME + ## Select up to one of the following options; otherwise temporary is picked + # exists: False # if True looks for existing volume with exact name. count must be 1. Volume is never deleted. + # permanent: False # if True volume is never deleted; overwrites semiPermanent if both are given + # semiPermanent: False # if True volume is only deleted during cluster termination # fallbackOnOtherImage: False # if True, most similar image by name will be picked. A regex can also be given instead. - # worker configuration + ## worker configuration # workerInstances: - # - type: # existing type/flavor on your cloud. See launch instance>flavor for options + # - type: # existing type/flavor from your cloud. See launch instance>flavor for options # image: # same as master. Consider using regex to prevent image updates from breaking your running cluster - # count: # any number of workers you would like to create with set type, image combination + # count: 1 # number of workers you would like to create with set type, image combination # # features: # list - # # partitions: # list - # # bootVolume: None - # # bootFromVolume: True - # # terminateBootVolume: True - # # volumeSize: 50 - - # Depends on cloud image - sshUser: # for example ubuntu - - # Depends on cloud site and project - subnet: # existing subnet on your cloud. See https://openstack.cebitec.uni-bielefeld.de/project/networks/ - # or network: - - # Uncomment if no full DNS service for started instances is available. - # Currently, the case in Berlin, DKFZ, Heidelberg and Tuebingen. - #localDNSLookup: True - - #features: # list - - # elastic_scheduling: # for large or slow clusters increasing these timeouts might be necessary to avoid failures - # SuspendTimeout: 60 # after SuspendTimeout seconds, slurm allows to power up the node again - # ResumeTimeout: 1200 # if a node doesn't start in ResumeTimeout seconds, the start is considered failed. + # # partitions: # list of slurm features that all nodes of this group have + # # bootVolume: # optional + # # name: # optional; if you want to boot from a specific volume + # # terminate: True # whether the volume is terminated on server termination + # # size: 50 + # # volumes: # optional + # # - name: volumeName # optional + # # snapshot: snapshotName # optional; to create volume from a snapshot + # # mountPoint: /vol/mountPath # optional; not mounted if no path is given + # # size: 50 + # # fstype: ext4 # must support chown + # # type: # storage type; available values depend on your location; for Bielefeld CEPH_HDD, CEPH_NVME + # ## Select up to one of the following options; otherwise temporary is picked + # # exists: False # if True looks for existing volume with exact name. count must be 1. Volume is never deleted. + # # permanent: False # if True volume is never deleted; overwrites semiPermanent if both are given + # # semiPermanent: False # if True volume is only deleted during cluster termination + + # Depends on image + sshUser: # for example 'ubuntu' + + # Depends on project + subnet: # existing subnet from your cloud. See https://openstack.cebitec.uni-bielefeld.de/project/networks/ + # network: # only if no subnet is given + + # features: # list of slurm features that all nodes of this cloud have + # - feature1 + + # bootVolume: # optional (cloud wide) + # name: # optional; if you want to boot from a specific volume + # terminate: True # whether the volume is terminated on server termination + # size: 50 #- [next configurations] diff --git a/bibigrid/__init__.py b/bibigrid/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bibigrid/core/__init__.py b/bibigrid/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bibigrid/core/actions/__init__.py b/bibigrid/core/actions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bibigrid/core/actions/create.py b/bibigrid/core/actions/create.py index dea3d45b..07139b95 100644 --- a/bibigrid/core/actions/create.py +++ b/bibigrid/core/actions/create.py @@ -49,12 +49,13 @@ def get_identifier(identifier, cluster_id, additional=""): VPN_WORKER_IDENTIFIER = partial(get_identifier, identifier="vpngtw") KEY_PREFIX = "tempKey_bibi" -KEY_FOLDER = os.path.expanduser("~/.config/bibigrid/keys/") +CONFIG_FOLDER = os.path.expanduser("~/.config/bibigrid/") +KEY_FOLDER = os.path.join(CONFIG_FOLDER, "keys/") AC_NAME = "ac" + SEPARATOR + "{cluster_id}" KEY_NAME = KEY_PREFIX + SEPARATOR + "{cluster_id}" CLUSTER_MEMORY_FOLDER = KEY_FOLDER CLUSTER_MEMORY_FILE = ".bibigrid.mem" -CLUSTER_MEMORY_PATH = os.path.join(CLUSTER_MEMORY_FOLDER, CLUSTER_MEMORY_FILE) +CLUSTER_MEMORY_PATH = os.path.join(CONFIG_FOLDER, CLUSTER_MEMORY_FILE) DEFAULT_SECURITY_GROUP_NAME = "default" + SEPARATOR + "{cluster_id}" WIREGUARD_SECURITY_GROUP_NAME = "wireguard" + SEPARATOR + "{cluster_id}" @@ -64,7 +65,8 @@ class Create: # pylint: disable=too-many-instance-attributes,too-many-arguments The class Create holds necessary methods to execute the Create-Action """ - def __init__(self, providers, configurations, config_path, log, debug=False, cluster_id=None): + def __init__(self, *, providers, configurations, config_path, log, debug=False, + cluster_id=None): """ Additionally sets (unique) cluster_id, public_key_commands (to copy public keys to master) and key_name. Call create() to actually start server. @@ -95,7 +97,7 @@ def __init__(self, providers, configurations, config_path, log, debug=False, clu # permanents holds groups or single nodes that ansible playbook should be run for during startup self.permanents = ["vpn"] self.vpn_counter = 0 - self.vpn_master_thread_lock = threading.Lock() + self.vpn_counter_thread_lock = threading.Lock() self.worker_thread_lock = threading.Lock() self.use_master_with_public_ip = not configurations[0].get("gateway") and configurations[0].get( "useMasterWithPublicIp", True) @@ -138,7 +140,20 @@ def generate_keypair(self): # write cluster_id to automatically read it on following calls if no cid is given with open(CLUSTER_MEMORY_PATH, mode="w+", encoding="UTF-8") as cluster_memory_file: - yaml.safe_dump(data={"cluster_id": self.cluster_id}, stream=cluster_memory_file) + yaml.safe_dump(data={"cluster_id": self.cluster_id, "ssh_user": self.ssh_user}, stream=cluster_memory_file) + + def delete_old_vars(self): + """ + Deletes host_vars and group_vars + @return: + """ + for folder in [a_rp.GROUP_VARS_FOLDER, a_rp.HOST_VARS_FOLDER]: + for file_name in os.listdir(folder): + # construct full file path + file = os.path.join(folder, file_name) + if os.path.isfile(file): + self.log.debug('Deleting file: %s', file) + os.remove(file) def generate_security_groups(self): """ @@ -174,21 +189,21 @@ def generate_security_groups(self): _ = provider.create_security_group(name=self.wireguard_security_group_name)["id"] configuration["security_groups"].append(self.wireguard_security_group_name) # store in configuration - def start_vpn_or_master(self, configuration, provider): # pylint: disable=too-many-locals + def start_vpn_or_master(self, configuration, provider): # pylint: disable=too-many-locals """ Start master/vpn-worker of a provider @param configuration: dict configuration of said provider. @param provider: provider @return: """ - identifier, instance, volumes = self.prepare_vpn_or_master_args(configuration, provider) + identifier, instance = self.prepare_vpn_or_master_args(configuration) external_network = provider.get_external_network(configuration["network"]) - with self.vpn_master_thread_lock: - if identifier == MASTER_IDENTIFIER: # pylint: disable=comparison-with-callable - name = identifier(cluster_id=self.cluster_id) - else: - name = identifier(cluster_id=self.cluster_id, # pylint: disable=redundant-keyword-arg - additional=self.vpn_counter) # pylint: disable=redundant-keyword-arg + if identifier == MASTER_IDENTIFIER: # pylint: disable=comparison-with-callable + name = identifier(cluster_id=self.cluster_id) + else: + name = identifier(cluster_id=self.cluster_id, # pylint: disable=redundant-keyword-arg + additional=self.vpn_counter) # pylint: disable=redundant-keyword-arg + with self.vpn_counter_thread_lock: self.vpn_counter += 1 self.log.info(f"Starting server {name} on {provider.cloud_specification['identifier']}") flavor = instance["type"] @@ -196,16 +211,19 @@ def start_vpn_or_master(self, configuration, provider): # pylint: disable=too-ma image = image_selection.select_image(provider, instance["image"], self.log, configuration.get("fallbackOnOtherImage")) + volumes = self.create_server_volumes(provider=provider, instance=instance, name=name) + # create a server and block until it is up and running + boot_volume = instance.get("bootVolume", configuration.get("bootVolume", {})) server = provider.create_server(name=name, flavor=flavor, key_name=self.key_name, image=image, network=network, volumes=volumes, security_groups=configuration["security_groups"], wait=True, - boot_from_volume=instance.get("bootFromVolume", - configuration.get("bootFromVolume", False)), - boot_volume=instance.get("bootVolume", configuration.get("bootVolume")), - terminate_boot_volume=instance.get("terminateBootVolume", - configuration.get("terminateBootVolume", - True)), - volume_size=instance.get("volumeSize", configuration.get("volumeSize", 50))) + boot_from_volume=boot_volume.get("name", False), + boot_volume=bool(boot_volume), + terminate_boot_volume=boot_volume.get("terminate", True), + volume_size=boot_volume.get("size", 50)) + # description=instance.get("description", configuration.get("description"))) + self.add_volume_device_info_to_instance(provider, server, instance) + configuration["private_v4"] = server["private_v4"] self.log.debug(f"Created Server {name}: {server['private_v4']}.") # get mac address for given private address @@ -222,19 +240,25 @@ def start_vpn_or_master(self, configuration, provider): # pylint: disable=too-ma if identifier == VPN_WORKER_IDENTIFIER or (identifier == MASTER_IDENTIFIER and self.use_master_with_public_ip): configuration["floating_ip"] = \ provider.attach_available_floating_ip(network=external_network, server=server)["floating_ip_address"] + if identifier == MASTER_IDENTIFIER: + with open(CLUSTER_MEMORY_PATH, mode="w+", encoding="UTF-8") as cluster_memory_file: + yaml.safe_dump( + data={"cluster_id": self.cluster_id, "floating_ip": configuration["floating_ip"]}, + stream=cluster_memory_file) self.log.debug(f"Added floating ip {configuration['floating_ip']} to {name}.") elif identifier == MASTER_IDENTIFIER: configuration["floating_ip"] = server["private_v4"] # pylint: enable=comparison-with-callable - configuration["volumes"] = provider.get_mount_info_from_server(server) - master_mounts = configuration.get("masterMounts", []) - if master_mounts: - for volume in configuration["volumes"]: - mount = next((mount for mount in master_mounts if mount["name"] == volume["name"]), None) - if mount and mount.get("mountPoint"): - volume["mount_point"] = mount["mountPoint"] - self.log.debug(f"Added mount point {mount['mountPoint']} of attached volume to configuration.") - - def start_workers(self, worker, worker_count, configuration, provider): + + def start_worker(self, worker, worker_count, configuration, provider): # pylint: disable=too-many-locals + """ + Starts a single worker (with onDemand: False) and adds all relevant information to the configuration dictionary. + Additionally, a hosts.yaml entry is created for the DNS resolution. + @param worker: + @param worker_count: + @param configuration: + @param provider: + @return: + """ name = WORKER_IDENTIFIER(cluster_id=self.cluster_id, additional=worker_count) self.log.info(f"Starting server {name} on {provider.cloud_specification['identifier']}.") flavor = worker["type"] @@ -242,16 +266,22 @@ def start_workers(self, worker, worker_count, configuration, provider): image = image_selection.select_image(provider, worker["image"], self.log, configuration.get("fallbackOnOtherImage")) - # create a server and block until it is up and running + volumes = self.create_server_volumes(provider=provider, instance=worker, name=name) + + # create a server and attaches volumes if given; blocks until it is up and running + boot_volume = worker.get("bootVolume", configuration.get("bootVolume", {})) server = provider.create_server(name=name, flavor=flavor, key_name=self.key_name, image=image, network=network, - volumes=None, security_groups=configuration["security_groups"], wait=True, - boot_from_volume=worker.get("bootFromVolume", - configuration.get("bootFromVolume", False)), - boot_volume=worker.get("bootVolume", configuration.get("bootVolume")), - terminate_boot_volume=worker.get("terminateBootVolume", - configuration.get("terminateBootVolume", - True))) + volumes=volumes, security_groups=configuration["security_groups"], wait=True, + boot_from_volume=boot_volume.get("name", False), + boot_volume=bool(boot_volume), + terminate_boot_volume=boot_volume.get("terminateBoot", True), + volume_size=boot_volume.get("size", 50), + description=worker.get("description", configuration.get("description"))) + self.add_volume_device_info_to_instance(provider, server, worker) + self.log.info(f"Worker {name} started on {provider.cloud_specification['identifier']}.") + + # for DNS resolution an entry in the hosts file is created with self.worker_thread_lock: self.permanents.append(name) with open(a_rp.HOSTS_FILE, mode="r", encoding="utf-8") as hosts_file: @@ -263,26 +293,102 @@ def start_workers(self, worker, worker_count, configuration, provider): ansible_configurator.write_yaml(a_rp.HOSTS_FILE, hosts, self.log) self.log.debug(f"Added worker {name} to hosts file {a_rp.HOSTS_FILE}.") - def prepare_vpn_or_master_args(self, configuration, provider): + # pylint: disable=duplicate-code + def create_server_volumes(self, provider, instance, name): + """ + Creates all volumes of a single instance + @param provider: + @param instance: flavor, image, ... description + @param name: sever name + @return: + """ + self.log.info("Creating volumes ...") + return_volumes = [] + + for i, volume in enumerate(instance.get("volumes", [])): + group_instance = {"volumes": []} + instance["group_instances"] = {name: group_instance} + if not volume.get("exists"): + if volume.get("permanent"): + infix = "perm" + elif volume.get("semiPermanent"): + infix = "semiperm" + else: + infix = "tmp" + postfix = f"-{volume.get('name')}" if volume.get('name') else '' + volume_name = f"{name}-{infix}-{i}{postfix}" + else: + volume_name = volume["name"] + group_instance["volumes"].append({**volume, "name": volume_name}) + + self.log.debug(f"Trying to find volume {volume_name}") + return_volume = provider.get_volume_by_id_or_name(volume_name) + if not return_volume: + self.log.debug(f"Volume {volume_name} not found.") + if volume.get('snapshot'): + self.log.debug("Creating volume from snapshot...") + return_volume = provider.create_volume_from_snapshot(volume['snapshot'], volume_name) + if not return_volume: + raise ConfigurationException(f"Snapshot {volume['snapshot']} not found!") + else: + self.log.debug("Creating volume...") + return_volume = provider.create_volume(name=volume_name, size=volume.get("size", 50), + volume_type=volume.get("type"), + description=f"Created for {name}") + return_volumes.append(return_volume) + return return_volumes + + def add_volume_device_info_to_instance(self, provider, server, instance): + """ + Only after attaching the volume to the server it is decided where the device is attached. + This method reads that value and stores it in the instance configuration. + This method assumes that devices are attached the same on instances with identical images. + @param provider: + @param server: + @param instance: + @return: + """ + self.log.info("Adding device info") + server_volumes = provider.get_mount_info_from_server(server) # list of volumes attachments + group_instance_volumes = instance["group_instances"][server["name"]].get("volumes") + final_volumes = [] + if group_instance_volumes: + for volume in group_instance_volumes: + server_volume = next((server_volume for server_volume in server_volumes if + server_volume["name"] == volume["name"]), None) + if not server_volume: + raise RuntimeError( + f"Created server {server['name']} doesn't have attached volume {volume['name']}.") + device = server_volume.get("device") + final_volumes.append({**volume, "device": device}) + + self.log.debug(f"Added Configuration: Instance {server['name']} has volume {volume['name']} " + f"as device {device} that is going to be mounted to " + f"{volume.get('mountPoint')}") + + ansible_configurator.write_yaml(os.path.join(a_rp.HOST_VARS_FOLDER, f"{server['name']}.yaml"), + {"volumes": final_volumes}, + self.log) + + def prepare_vpn_or_master_args(self, configuration): """ Prepares start_instance arguments for master/vpn @param configuration: configuration (dict) of said master/vpn - @param provider: provider @return: arguments needed by start_instance """ if configuration.get("masterInstance"): instance_type = configuration["masterInstance"] identifier = MASTER_IDENTIFIER - master_mounts_src = [master_mount["name"] for master_mount in configuration.get("masterMounts", [])] - volumes = self.prepare_volumes(provider, master_mounts_src) elif configuration.get("vpnInstance"): instance_type = configuration["vpnInstance"] identifier = VPN_WORKER_IDENTIFIER - volumes = [] # only master has volumes else: - self.log.warning("Configuration %s has no vpngtw or master and is therefore unreachable.", configuration) - raise KeyError - return identifier, instance_type, volumes + self.log.warning( + f"Configuration {configuration['cloud_identifier']} " + f"has no vpngtw or master and is therefore unreachable.") + raise ConfigurationException( + f"Configuration {configuration['cloud_identifier']} has neither vpngtw nor masterInstance") + return identifier, instance_type def initialize_instances(self): """ @@ -306,33 +412,6 @@ def initialize_instances(self): ssh_data["commands"] = ssh_handler.VPN_SETUP ssh_handler.execute_ssh(ssh_data, self.log) - def prepare_volumes(self, provider, mounts): - """ - Creates volumes from snapshots and returns all volumes (pre-existing and newly created) - @param provider: provider on which the volumes and snapshots exist - @param mounts: volumes or snapshots - @return: list of pre-existing and newly created volumes - """ - if mounts: - self.log.info("Preparing volumes") - volumes = [] - for mount in mounts: - volume_id = provider.get_volume_by_id_or_name(mount)["id"] - if volume_id: - volumes.append(volume_id) - else: - self.log.debug("Volume %s does not exist. Checking for snapshot.", mount) - volume_id = provider.create_volume_from_snapshot(mount) - if volume_id: - volumes.append(volume_id) - else: - self.log.warning("Mount %s is neither a snapshot nor a volume.", mount) - ret_volumes = set(volumes) - if len(ret_volumes) < len(volumes): - self.log.warning("Identical mounts found in masterMounts list. " - "Trying to set() to save the run. Check configurations!") - return ret_volumes - def prepare_configurations(self): """ Makes sure that subnet and network key are set for each configuration. @@ -405,7 +484,7 @@ def start_start_server_threads(self): for worker in configuration.get("workerInstances", []): if not worker.get("onDemand", True): for _ in range(int(worker["count"])): - start_server_thread = return_threading.ReturnThread(target=self.start_workers, + start_server_thread = return_threading.ReturnThread(target=self.start_worker, args=[worker, worker_count, configuration, provider]) start_server_thread.start() @@ -458,6 +537,7 @@ def create(self): # pylint: disable=too-many-branches,too-many-statements self.log.info("%s not found. Creating folder.", folder) os.mkdir(folder) self.generate_keypair() + self.delete_old_vars() self.prepare_configurations() self.create_defaults() self.generate_security_groups() diff --git a/bibigrid/core/actions/terminate.py b/bibigrid/core/actions/terminate.py index 9a56bd3c..ad5dbace 100644 --- a/bibigrid/core/actions/terminate.py +++ b/bibigrid/core/actions/terminate.py @@ -32,6 +32,7 @@ def terminate(cluster_id, providers, log, debug=False, assume_yes=False): cluster_server_state = [] cluster_keypair_state = [] cluster_security_group_state = [] + cluster_volume_state = [] tmp_keyname = create.KEY_NAME.format(cluster_id=cluster_id) local_keypairs_deleted = delete_local_keypairs(tmp_keyname, log) if assume_yes or local_keypairs_deleted or input( @@ -41,26 +42,28 @@ def terminate(cluster_id, providers, log, debug=False, assume_yes=False): f"Empty input to exit with cluster still alive:"): for provider in providers: log.info("Terminating cluster %s on cloud %s", cluster_id, provider.cloud_specification['identifier']) - server_list = provider.list_servers() - cluster_server_state += terminate_servers(server_list, cluster_id, provider, log) + cluster_server_state += terminate_servers(cluster_id, provider, log) cluster_keypair_state.append(delete_keypairs(provider, tmp_keyname, log)) cluster_security_group_state.append(delete_security_groups(provider, cluster_id, security_groups, log)) + cluster_volume_state.append(delete_non_permanent_volumes(provider, cluster_id, log)) ac_state = delete_application_credentials(providers[0], cluster_id, log) - terminate_output(cluster_server_state, cluster_keypair_state, cluster_security_group_state, ac_state, - cluster_id, log) + terminate_output(cluster_server_state=cluster_server_state, cluster_keypair_state=cluster_keypair_state, + cluster_security_group_state=cluster_security_group_state, + cluster_volume_state=cluster_volume_state, ac_state=ac_state, cluster_id=cluster_id, + log=log) return 0 -def terminate_servers(server_list, cluster_id, provider, log): +def terminate_servers(cluster_id, provider, log): """ - Terminates all servers in server_list that match the bibigrid regex. - @param server_list: list of server dicts. All servers are from provider + Terminates all servers that match the bibigrid regex. @param cluster_id: id of cluster to terminate @param provider: provider that holds all servers in server_list @param log: @return: a list of the servers' (that were to be terminated) termination states """ log.info("Deleting servers on provider %s...", provider.cloud_specification['identifier']) + server_list = provider.list_servers() cluster_server_state = [] server_regex = re.compile(fr"^bibigrid-(master-{cluster_id}|(worker|vpngtw)-{cluster_id}-\d+)$") for server in server_list: @@ -186,13 +189,36 @@ def delete_application_credentials(master_provider, cluster_id, log): return True -def terminate_output(cluster_server_state, cluster_keypair_state, cluster_security_group_state, ac_state, cluster_id, - log): +def delete_non_permanent_volumes(provider, cluster_id, log): + """ + Terminates all temporary and semiperm volumes that match the regex. + @param cluster_id: id of cluster to terminate + @param provider: provider that holds all servers in server_list + @param log: + @return: a list of the servers' (that were to be terminated) termination states + """ + log.info("Deleting tmp volumes on provider %s...", provider.cloud_specification['identifier']) + volume_list = provider.list_volumes() + cluster_volume_state = [] + volume_regex = re.compile( + fr"^bibigrid-(master-{cluster_id}|(worker|vpngtw)-{cluster_id}-(\d+))-(semiperm|tmp)-\d+(-.+)?$") + for volume in volume_list: + if volume_regex.match(volume["name"]): + log.info("Trying to delete volume %s on cloud %s.", volume['name'], provider.cloud_specification[ + 'identifier']) + cluster_volume_state.append(provider.delete_volume(volume)) + return cluster_volume_state + + +# pylint: disable=too-many-branches +def terminate_output(*, cluster_server_state, cluster_keypair_state, cluster_security_group_state, cluster_volume_state, + ac_state, cluster_id, log): """ Logs the termination result in detail @param cluster_server_state: list of bools. Each bool stands for a server termination @param cluster_keypair_state: list of bools. Each bool stands for a keypair deletion @param cluster_security_group_state: list of bools. Each bool stands for a security group deletion + @param cluster_volume_state: list of bools. Each bool stands for a volume deletion @param ac_state: bool that stands for the deletion of the credentials on the master @param cluster_id: @param log: @@ -202,6 +228,7 @@ def terminate_output(cluster_server_state, cluster_keypair_state, cluster_securi cluster_server_terminated = all(cluster_server_state) cluster_keypair_deleted = all(cluster_keypair_state) cluster_security_group_deleted = all(cluster_security_group_state) + cluster_volume_deleted = all(cluster_volume_state) if cluster_existed: if cluster_server_terminated: log.info("Terminated all servers of cluster %s.", cluster_id) @@ -215,15 +242,20 @@ def terminate_output(cluster_server_state, cluster_keypair_state, cluster_securi log.info("Deleted all security groups of cluster %s.", cluster_id) else: log.warning("Unable to delete all security groups of cluster %s.", cluster_id) - - if cluster_server_terminated and cluster_keypair_deleted and cluster_security_group_deleted: + if cluster_volume_deleted: + log.info("Deleted all volumes of cluster %s", cluster_id) + else: + log.warning("Unable to delete all volumes of cluster %s.", cluster_id) + if (cluster_server_terminated and cluster_keypair_deleted and cluster_security_group_deleted and + cluster_volume_deleted): log.log(42, f"Successfully terminated cluster {cluster_id}.") else: log.warning("Unable to terminate cluster %s properly." "\nAll servers terminated: %s" "\nAll keys deleted: %s" + "\nAll security groups deleted: %s" "\nAll security groups deleted: %s", cluster_id, cluster_server_terminated, - cluster_keypair_deleted, cluster_security_group_deleted) + cluster_keypair_deleted, cluster_security_group_deleted, cluster_volume_deleted) if ac_state: log.info("Successfully handled application credential of cluster %s.", cluster_id) else: diff --git a/bibigrid/core/provider.py b/bibigrid/core/provider.py index 360093ba..ad810fc4 100644 --- a/bibigrid/core/provider.py +++ b/bibigrid/core/provider.py @@ -3,6 +3,8 @@ """ from abc import ABC, abstractmethod +FLAVOR_KEYS = ["name", "ram", "vcpus", "disk", "ephemeral"] + class Provider(ABC): # pylint: disable=too-many-public-methods """ @@ -88,12 +90,15 @@ def list_servers(self): """ @abstractmethod - def create_server(self, name, flavor, image, network, key_name=None, wait=True, volumes=None, security_groups=None, + def create_server(self, *, name, flavor, image, network, key_name=None, wait=True, volumes=None, + security_groups=None, boot_volume=None, boot_from_volume=False, - terminate_boot_volume=False, volume_size=50): # pylint: disable=too-many-arguments + terminate_boot_volume=False, volume_size=50, + description=""): # pylint: disable=too-many-arguments """ Creates a new server and waits for it to be accessible if wait=True. If volumes are given, they are attached. Returns said server (dict) + @param description: server description; ignored by some providers @param volume_size: Size of boot volume if set. Defaults to 50. @param terminate_boot_volume: if True, boot volume gets terminated on server termination @param boot_from_volume: if True, a boot volume is created from the image @@ -278,14 +283,46 @@ def get_security_group(self, name_or_id): @return: """ + @abstractmethod + def create_volume(self, *, name, size, wait=True, volume_type=None, description=None): + """ + Creates a volume + @param name: name of the created volume + @param size: size of the created volume in GB + @param wait: if true waits for volume to be created + @param volume_type: depends on the location, but for example NVME or HDD + @param description: a non-functional description to help dashboard users + @return: the created volume + """ + + @abstractmethod def get_server(self, name_or_id): """ Returns server if found else None. @param name_or_id: @return: - """ # TODO Test + """ + + @abstractmethod + def delete_volume(self, name_or_id): + """ + Deletes the volume that has name_or_id. + @param name_or_id: + @return: True if deletion was successful, else False + """ + + @abstractmethod + def list_volumes(self): + """ + Returns a list of all volumes on the provider. + @return: list of volumes + """ def get_mount_info_from_server(self, server): + """ + @param server: server to get the attachment list from + @return: list of dicts containing name and device node of all attached volumes + """ volumes = [] for server_volume in server["volumes"]: volume = self.get_volume_by_id_or_name(server_volume["id"]) @@ -294,3 +331,12 @@ def get_mount_info_from_server(self, server): volumes.append({"name": volume["name"], "device": attachment["device"]}) break return volumes + + def create_flavor_dict(self, flavor): + """ + + @param flavor: an existing flavor on the provider's cloud + @return: a dictionary containing only the FLAVOR_KEYS + """ + flavor = self.get_flavor(flavor) + return {key: flavor[key] for key in FLAVOR_KEYS} diff --git a/bibigrid/core/startup.py b/bibigrid/core/startup.py index 8c726cb6..71b659ec 100755 --- a/bibigrid/core/startup.py +++ b/bibigrid/core/startup.py @@ -34,6 +34,7 @@ def get_cluster_id_from_mem(): return mem_dict.get("cluster_id") except yaml.YAMLError as exc: LOG.warning("Couldn't read configuration %s: %s", create.CLUSTER_MEMORY_PATH, exc) + LOG.warning(f"Couldn't find cluster memory path {create.CLUSTER_MEMORY_PATH}") return None diff --git a/bibigrid/core/utility/__init__.py b/bibigrid/core/utility/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bibigrid/core/utility/ansible_configurator.py b/bibigrid/core/utility/ansible_configurator.py index 875ea00c..8777cb2a 100644 --- a/bibigrid/core/utility/ansible_configurator.py +++ b/bibigrid/core/utility/ansible_configurator.py @@ -16,8 +16,10 @@ from bibigrid.core.utility.paths import ansible_resources_path as aRP from bibigrid.core.utility.wireguard import wireguard_keys -DEFAULT_NFS_SHARES = ["/vol/spool"] PYTHON_INTERPRETER = "/usr/bin/python3" + +DEFAULT_NFS_SHARES = ["/vol/spool"] + VPNGTW_ROLES = [{"role": "bibigrid", "tags": ["bibigrid", "bibigrid-vpngtw"]}] MASTER_ROLES = [{"role": "bibigrid", "tags": ["bibigrid", "bibigrid-master"]}] WORKER_ROLES = [{"role": "bibigrid", "tags": ["bibigrid", "bibigrid-worker"]}] @@ -33,21 +35,6 @@ CLOUD_SCHEDULING = {"sshTimeout": 5} -def delete_old_vars(log): - """ - Deletes host_vars and group_vars - @param log: - @return: - """ - for folder in [aRP.GROUP_VARS_FOLDER, aRP.HOST_VARS_FOLDER]: - for file_name in os.listdir(folder): - # construct full file path - file = os.path.join(folder, file_name) - if os.path.isfile(file): - log.debug('Deleting file: %s', file) - os.remove(file) - - def generate_site_file_yaml(user_roles): """ Generates site_yaml (dict). @@ -68,7 +55,98 @@ def generate_site_file_yaml(user_roles): return site_yaml -def write_host_and_group_vars(configurations, providers, cluster_id, log): # pylint: disable=too-many-locals +def write_worker_host_vars(*, cluster_id, worker, worker_count, log): + for worker_number in range(worker.get('count', 1)): + name = create.WORKER_IDENTIFIER(cluster_id=cluster_id, additional=worker_count + worker_number) + write_volumes = [] + for i, volume in enumerate(worker.get("volumes", [])): + if not volume.get("exists"): + if volume.get("permanent"): + infix = "perm" + elif volume.get("semiPermanent"): + infix = "semiperm" + else: + infix = "tmp" + postfix = f"-{volume.get('name')}" if volume.get('name') else '' + volume_name = f"{name}-{infix}-{i}{postfix}" + else: + volume_name = volume["name"] + write_volumes.append({**volume, "name": volume_name}) + write_yaml(os.path.join(aRP.HOST_VARS_FOLDER, f"{name}.yaml"), + {"volumes": write_volumes}, + log) + + +def write_worker_vars(*, provider, configuration, cluster_id, worker, worker_count, log): + flavor_dict = provider.create_flavor_dict(flavor=worker["type"]) + name = create.WORKER_IDENTIFIER(cluster_id=cluster_id, + additional=f"[{worker_count}-{worker_count + worker.get('count', 1) - 1}]") + group_name = name.replace("[", "").replace("]", "").replace(":", "_").replace("-", "_") + regexp = create.WORKER_IDENTIFIER(cluster_id=cluster_id, additional=r"\d+") + worker_dict = {"name": name, "regexp": regexp, "image": worker["image"], + "network": configuration["network"], "flavor": flavor_dict, + "gateway_ip": configuration["private_v4"], + "cloud_identifier": configuration["cloud_identifier"], + "on_demand": worker.get("onDemand", True), "state": "CLOUD", + "partitions": worker.get("partitions", []) + ["all", configuration["cloud_identifier"]], + "boot_volume": worker.get("bootVolume", configuration.get("bootVolume", {})) + } + + worker_features = worker.get("features", []) + configuration_features = configuration.get("features", []) + if isinstance(worker_features, str): + worker_features = [worker_features] + features = set(configuration_features + worker_features) + if features: + worker_dict["features"] = features + + pass_through(configuration, worker_dict, "waitForServices", "wait_for_services") + write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, f"{group_name}.yaml"), worker_dict, log) + if worker_dict["on_demand"]: # not on demand instances host_vars are created in create + write_worker_host_vars(cluster_id=cluster_id, worker=worker, worker_count=worker_count, + log=log) + worker_count += worker.get('count', 1) + return worker_count + + +def write_vpn_var(*, provider, configuration, cluster_id, vpngtw, vpn_count, log): + name = create.VPN_WORKER_IDENTIFIER(cluster_id=cluster_id, additional=f"{vpn_count}") + wireguard_ip = f"10.0.0.{vpn_count + 2}" # skipping 0 and 1 (master) + vpn_count += 1 + flavor_dict = provider.create_flavor_dict(flavor=vpngtw["type"]) + regexp = create.WORKER_IDENTIFIER(cluster_id=cluster_id, additional=r"\d+") + vpngtw_dict = {"name": name, "regexp": regexp, "image": vpngtw["image"], + "network": configuration["network"], "network_cidrs": configuration["subnet_cidrs"], + "floating_ip": configuration["floating_ip"], "private_v4": configuration["private_v4"], + "flavor": flavor_dict, "wireguard_ip": wireguard_ip, + "cloud_identifier": configuration["cloud_identifier"], + "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False), + "on_demand": False} + if configuration.get("wireguard_peer"): + vpngtw_dict["wireguard"] = {"ip": wireguard_ip, "peer": configuration.get("wireguard_peer")} + pass_through(configuration, vpngtw_dict, "waitForServices", "wait_for_services") + write_yaml(os.path.join(aRP.HOST_VARS_FOLDER, f"{name}.yaml"), vpngtw_dict, log) + + +def write_master_var(provider, configuration, cluster_id, log): + master = configuration["masterInstance"] + name = create.MASTER_IDENTIFIER(cluster_id=cluster_id) + flavor_dict = provider.create_flavor_dict(flavor=master["type"]) + master_dict = {"name": name, "image": master["image"], "network": configuration["network"], + "network_cidrs": configuration["subnet_cidrs"], "floating_ip": configuration["floating_ip"], + "flavor": flavor_dict, "private_v4": configuration["private_v4"], + "cloud_identifier": configuration["cloud_identifier"], + "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False), + "state": "UNKNOWN" if configuration.get("useMasterAsCompute", True) else "DRAINED", + "on_demand": False, + "partitions": master.get("partitions", []) + ["all", configuration["cloud_identifier"]]} + if configuration.get("wireguard_peer"): + master_dict["wireguard"] = {"ip": "10.0.0.1", "peer": configuration.get("wireguard_peer")} + pass_through(configuration, master_dict, "waitForServices", "wait_for_services") + write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, "master.yaml"), master_dict, log) + + +def write_host_and_group_vars(configurations, providers, cluster_id, log): """ Filters unnecessary information @param log: @@ -78,73 +156,19 @@ def write_host_and_group_vars(configurations, providers, cluster_id, log): # py @return: filtered information (dict) """ log.info("Generating instances file...") - flavor_keys = ["name", "ram", "vcpus", "disk", "ephemeral"] worker_count = 0 vpn_count = 0 - for configuration, provider in zip(configurations, providers): - configuration_features = configuration.get("features", []) - if isinstance(configuration_features, str): - configuration_features = [configuration_features] + for configuration, provider in zip(configurations, providers): # pylint: disable=too-many-nested-blocks for worker in configuration.get("workerInstances", []): - flavor = provider.get_flavor(worker["type"]) - flavor_dict = {key: flavor[key] for key in flavor_keys} - name = create.WORKER_IDENTIFIER(cluster_id=cluster_id, - additional=f"[{worker_count}-{worker_count + worker.get('count', 1) - 1}]") - group_name = name.replace("[", "").replace("]", "").replace(":", "_").replace("-", "_") - worker_count += worker.get('count', 1) - regexp = create.WORKER_IDENTIFIER(cluster_id=cluster_id, additional=r"\d+") - worker_dict = {"name": name, "regexp": regexp, "image": worker["image"], - "network": configuration["network"], "flavor": flavor_dict, - "gateway_ip": configuration["private_v4"], - "cloud_identifier": configuration["cloud_identifier"], - "on_demand": worker.get("onDemand", True), "state": "CLOUD", - "partitions": worker.get("partitions", []) + ["all", configuration["cloud_identifier"]]} - - worker_features = worker.get("features", []) - if isinstance(worker_features, str): - worker_features = [worker_features] - features = set(configuration_features + worker_features) - if features: - worker_dict["features"] = features - - pass_through(configuration, worker_dict, "waitForServices", "wait_for_services") - write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, f"{group_name}.yaml"), worker_dict, log) + worker_count = write_worker_vars(provider=provider, configuration=configuration, cluster_id=cluster_id, + worker=worker, worker_count=worker_count, log=log) + vpngtw = configuration.get("vpnInstance") if vpngtw: - name = create.VPN_WORKER_IDENTIFIER(cluster_id=cluster_id, additional=f"{vpn_count}") - wireguard_ip = f"10.0.0.{vpn_count + 2}" # skipping 0 and 1 (master) - vpn_count += 1 - flavor = provider.get_flavor(vpngtw["type"]) - flavor_dict = {key: flavor[key] for key in flavor_keys} - regexp = create.WORKER_IDENTIFIER(cluster_id=cluster_id, additional=r"\d+") - vpngtw_dict = {"name": name, "regexp": regexp, "image": vpngtw["image"], - "network": configuration["network"], "network_cidrs": configuration["subnet_cidrs"], - "floating_ip": configuration["floating_ip"], "private_v4": configuration["private_v4"], - "flavor": flavor_dict, "wireguard_ip": wireguard_ip, - "cloud_identifier": configuration["cloud_identifier"], - "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False), - "on_demand": False} - if configuration.get("wireguard_peer"): - vpngtw_dict["wireguard"] = {"ip": wireguard_ip, "peer": configuration.get("wireguard_peer")} - pass_through(configuration, vpngtw_dict, "waitForServices", "wait_for_services") - write_yaml(os.path.join(aRP.HOST_VARS_FOLDER, f"{name}.yaml"), vpngtw_dict, log) + write_vpn_var(provider=provider, configuration=configuration, cluster_id=cluster_id, vpngtw=vpngtw, + vpn_count=vpn_count, log=log) else: - master = configuration["masterInstance"] - name = create.MASTER_IDENTIFIER(cluster_id=cluster_id) - flavor = provider.get_flavor(master["type"]) - flavor_dict = {key: flavor[key] for key in flavor_keys} - master_dict = {"name": name, "image": master["image"], "network": configuration["network"], - "network_cidrs": configuration["subnet_cidrs"], "floating_ip": configuration["floating_ip"], - "flavor": flavor_dict, "private_v4": configuration["private_v4"], - "cloud_identifier": configuration["cloud_identifier"], "volumes": configuration["volumes"], - "fallback_on_other_image": configuration.get("fallbackOnOtherImage", False), - "state": "UNKNOWN" if configuration.get("useMasterAsCompute", True) else "DRAINED", - "on_demand": False, - "partitions": master.get("partitions", []) + ["all", configuration["cloud_identifier"]]} - if configuration.get("wireguard_peer"): - master_dict["wireguard"] = {"ip": "10.0.0.1", "peer": configuration.get("wireguard_peer")} - pass_through(configuration, master_dict, "waitForServices", "wait_for_services") - write_yaml(os.path.join(aRP.GROUP_VARS_FOLDER, "master.yaml"), master_dict, log) + write_master_var(provider, configuration, cluster_id, log) def pass_through(dict_from, dict_to, key_from, key_to=None): @@ -162,7 +186,8 @@ def pass_through(dict_from, dict_to, key_from, key_to=None): dict_to[key_to] = dict_from[key_from] -def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_user, default_user, log): +def generate_common_configuration_yaml(*, cidrs, configurations, cluster_id, ssh_user, default_user, + log): """ Generates common_configuration yaml (dict) @param cidrs: str subnet cidrs (provider generated) @@ -194,7 +219,7 @@ def generate_common_configuration_yaml(cidrs, configurations, cluster_id, ssh_us if master_configuration.get("nfs"): nfs_shares = master_configuration.get("nfsShares", []) nfs_shares = nfs_shares + DEFAULT_NFS_SHARES - common_configuration_yaml["nfs_mounts"] = [{"src": nfs_share, "dst": nfs_share} for nfs_share in nfs_shares] + common_configuration_yaml["nfs_shares"] = [{"src": nfs_share, "dst": nfs_share} for nfs_share in nfs_shares] common_configuration_yaml["ext_nfs_mounts"] = [{"src": ext_nfs_share, "dst": ext_nfs_share} for ext_nfs_share in (master_configuration.get("extNfsShares", []))] @@ -223,8 +248,9 @@ def generate_ansible_hosts_yaml(ssh_user, configurations, cluster_id, log): # p @return: ansible_hosts yaml (dict) """ log.info("Generating ansible hosts file...") + master_name = create.MASTER_IDENTIFIER(cluster_id=cluster_id) ansible_hosts_yaml = {"vpn": {"hosts": {}, - "children": {"master": {"hosts": {"localhost": to_instance_host_dict(ssh_user)}}, + "children": {"master": {"hosts": {master_name: to_instance_host_dict(ssh_user)}}, "vpngtw": {"hosts": {}}}}, "workers": {"hosts": {}, "children": {}}} # vpngtw are handled like workers on this level workers = ansible_hosts_yaml["workers"] @@ -358,7 +384,6 @@ def configure_ansible_yaml(providers, configurations, cluster_id, log): @param log: @return: """ - delete_old_vars(log) log.info("Writing ansible files...") alias = configurations[0].get("aliasDumper", False) user_roles = configurations[0].get("userRoles", []) diff --git a/bibigrid/core/utility/handler/__init__.py b/bibigrid/core/utility/handler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bibigrid/core/utility/handler/ssh_handler.py b/bibigrid/core/utility/handler/ssh_handler.py index 62fa312f..954aff53 100644 --- a/bibigrid/core/utility/handler/ssh_handler.py +++ b/bibigrid/core/utility/handler/ssh_handler.py @@ -116,7 +116,7 @@ def is_active(client, paramiko_key, ssh_data, log): log.info(f"Successfully connected to {ssh_data['floating_ip']}.") except paramiko.ssh_exception.NoValidConnectionsError as exc: if attempts < ssh_data['timeout']: - sleep_time = 2 ** (attempts+2) + sleep_time = 2 ** (attempts + 2) time.sleep(sleep_time) log.info(f"Waiting {sleep_time} before attempting to reconnect.") attempts += 1 @@ -155,6 +155,7 @@ def line_buffered(f): def execute_ssh_cml_commands(client, commands, log): """ Executes commands and logs exit_status accordingly. + Do not log commands as they contain cloud credentials. @param client: Client with connection to remote @param commands: Commands to execute on remote @param log: @@ -187,11 +188,14 @@ def execute_ssh_cml_commands(client, commands, log): def execute_ssh(ssh_data, log): """ Executes commands on remote and copies files given in filepaths - + Do not log commands as they contain cloud credentials. @param ssh_data: Dict containing floating_ip, private_key, username, commands, filepaths, gateway, timeout @param log: """ - log.debug(f"Running execute_sshc with ssh_data: {ssh_data}.") + log.debug("Running execute_ssh") + for key in ssh_data: + if key not in ["commands", "filepaths"]: + log.debug(f"{key}: {ssh_data[key]}") if ssh_data.get("filepaths") is None: ssh_data["filepaths"] = [] if ssh_data.get("commands") is None: @@ -205,14 +209,14 @@ def execute_ssh(ssh_data, log): log.error(f"Couldn't connect to ip {ssh_data['gateway'] or ssh_data['floating_ip']} using private key " f"{ssh_data['private_key']}.") raise exc - else: - log.debug(f"Setting up {ssh_data['floating_ip']}") - if ssh_data['filepaths']: - log.debug(f"Setting up filepaths for {ssh_data['floating_ip']}") - sftp = client.open_sftp() - for local_path, remote_path in ssh_data['filepaths']: - copy_to_server(sftp=sftp, local_path=local_path, remote_path=remote_path, log=log) - log.debug("SFTP: Files %s copied.", ssh_data['filepaths']) - if ssh_data["floating_ip"]: - log.debug(f"Setting up commands for {ssh_data['floating_ip']}") - execute_ssh_cml_commands(client=client, commands=ssh_data["commands"], log=log) + + log.debug(f"Setting up {ssh_data['floating_ip']}") + if ssh_data['filepaths']: + log.debug(f"Setting up filepaths for {ssh_data['floating_ip']}") + sftp = client.open_sftp() + for local_path, remote_path in ssh_data['filepaths']: + copy_to_server(sftp=sftp, local_path=local_path, remote_path=remote_path, log=log) + log.debug("SFTP: Files %s copied.", ssh_data['filepaths']) + if ssh_data["floating_ip"]: + log.debug(f"Setting up commands for {ssh_data['floating_ip']}") + execute_ssh_cml_commands(client=client, commands=ssh_data["commands"], log=log) diff --git a/bibigrid/core/utility/paths/__init__.py b/bibigrid/core/utility/paths/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bibigrid/core/utility/validate_configuration.py b/bibigrid/core/utility/validate_configuration.py index 4451b632..458cefd9 100644 --- a/bibigrid/core/utility/validate_configuration.py +++ b/bibigrid/core/utility/validate_configuration.py @@ -325,38 +325,63 @@ def check_instance_type_image_combination(self, instance_type, instance_image, p self.required_resources_dict[provider.cloud_specification['identifier']]["total_cores"] += flavor["vcpus"] return success - def check_volumes(self): + def check_volumes(self): # pylint: disable=too-many-branches """ Checking if volume or snapshot exists for all volumes @return: True if all snapshot and volumes are found. Else false. """ self.log.info("Checking volumes...") success = True - for configuration, provider in zip(self.configurations, self.providers): - volume_identifiers = [masterMount["name"] for masterMount in configuration.get("masterMounts", [])] - if volume_identifiers: - # check individually if volumes exist - for volume_identifier in volume_identifiers: - if ":" in volume_identifier: - volume_name_or_id = volume_identifier[:volume_identifier.index(":")] - else: - volume_name_or_id = volume_identifier - volume = provider.get_volume_by_id_or_name(volume_name_or_id) - if not volume: - snapshot = provider.get_volume_snapshot_by_id_or_name(volume_name_or_id) - if not snapshot: - self.log.warning(f"Neither Volume nor Snapshot '{volume_name_or_id}' found on " - f"{provider.cloud_specification['identifier']}") + for configuration, provider in zip(self.configurations, self.providers): # pylint: disable=too-many-nested-blocks,too-many-branches + master_volumes = ( + 1, configuration.get("masterInstance", []) and configuration["masterInstance"].get("volumes", + [])) + worker_volumes = configuration.get("workerInstances", (1, [])) and [ + (worker_instance.get("count", 1), worker_instance.get("volumes", [])) for + worker_instance in configuration.get("workerInstances", [])] + volume_groups = [master_volumes] + worker_volumes + + for count, volume_group in volume_groups: + for volume in volume_group: + if volume.get("exists"): + if volume.get("name"): + volume_object = provider.get_volume_by_id_or_name(volume["name"]) + if volume_object: + self.log.debug( + f"Found volume {volume['name']} on cloud " + f"{provider.cloud_specification['identifier']}.") + else: + self.log.warning( + f"Couldn't find volume {volume['name']} on cloud " + f"{provider.cloud_specification['identifier']}. " + "No size added to resource requirements dict." + ) + success = False + else: + self.log.warning( + f"Key exists is set, but no name is given for {volume}. " + "No size added to resource requirements dict.") success = False + else: + self.required_resources_dict[provider.cloud_specification['identifier']]["volumes"] += count + + if volume.get("snapshot"): + snapshot_object = provider.get_volume_snapshot_by_id_or_name(volume["snapshot"]) + if snapshot_object: + self.log.debug( + f"Found snapshot {volume['snapshot']} on cloud " + f"{provider.cloud_specification['identifier']}.") + self.required_resources_dict[provider.cloud_specification['identifier']][ + "volume_gigabytes"] += snapshot_object["size"] * count + else: + self.log.warning( + f"Couldn't find snapshot {volume['snapshot']} on cloud " + f"{provider.cloud_specification['identifier']}. " + "No size added to resource requirements dict.") + success = False else: - self.log.info(f"Snapshot '{volume_name_or_id}' found on " - f"{provider.cloud_specification['identifier']}.") - self.required_resources_dict[provider.cloud_specification['identifier']]["volumes"] += 1 self.required_resources_dict[provider.cloud_specification['identifier']][ - "volume_gigabytes"] += snapshot["size"] - else: - self.log.info(f"Volume '{volume_name_or_id}' found on " - f"{provider.cloud_specification['identifier']}.") + "volume_gigabytes"] += volume.get("size", 50) * count return success def check_network(self): diff --git a/bibigrid/core/utility/validate_schema.py b/bibigrid/core/utility/validate_schema.py index 4b2e3295..1ed2f035 100644 --- a/bibigrid/core/utility/validate_schema.py +++ b/bibigrid/core/utility/validate_schema.py @@ -5,22 +5,50 @@ from schema import Schema, Optional, Or, SchemaError WORKER = {'type': str, 'image': str, Optional('count'): int, Optional('onDemand'): bool, Optional('partitions'): [str], - Optional('features'): [str], - Optional('bootVolume'): str, - Optional('bootFromVolume'): bool, Optional('terminateBootVolume'): bool, Optional('volumeSize'): int, - } + Optional('features'): [str], + Optional('bootVolume'): { + Optional('name'): str, + Optional('terminate'): bool, + Optional('size'): int + }, + Optional('volumes'): [{ + Optional('name'): str, + Optional('snapshot'): str, # optional; to create volume from + # one or none of these + Optional('permanent'): bool, + Optional('semiPermanent'): bool, + Optional('exists'): bool, + Optional('mountPoint'): str, + Optional('size'): int, + Optional('fstype'): str, + Optional('type'): str}] + } MASTER = VPN = {'type': str, 'image': str, Optional('onDemand'): bool, Optional('partitions'): [str], - Optional('features'): [str], - Optional('bootVolume'): str, - Optional('bootFromVolume'): bool, Optional('terminateBootVolume'): bool, Optional('volumeSize'): int, - } + Optional('features'): [str], + Optional('bootVolume'): { + Optional('name'): str, + Optional('terminate'): bool, + Optional('size'): int + }, + Optional('volumes'): [{ + Optional('name'): str, + Optional('snapshot'): str, # optional; to create volume from + # one or none of these + Optional('permanent'): bool, + Optional('semiPermanent'): bool, + Optional('exists'): bool, + Optional('mountPoint'): str, + Optional('size'): int, + Optional('fstype'): str, + Optional('type'): str}] + } # Define the schema for the configuration file master_schema = Schema( {'infrastructure': str, 'cloud': str, 'sshUser': str, Or('subnet', 'network'): str, 'cloud_identifier': str, Optional('sshPublicKeyFiles'): [str], Optional('sshTimeout'): int, Optional('cloudScheduling'): {Optional('sshTimeout'): int}, Optional('autoMount'): bool, - Optional('masterMounts'): [{'name': str, Optional('mountPoint'): str}], Optional('nfsShares'): [str], + Optional('nfsShares'): [str], Optional('userRoles'): [{'hosts': [str], 'roles': [{'name': str, Optional('tags'): [str]}]}], Optional('localFS'): bool, Optional('localDNSlookup'): bool, Optional('slurm'): bool, Optional('slurmConf'): {Optional('db'): str, Optional('db_user'): str, Optional('db_password'): str, @@ -31,22 +59,30 @@ 'ResumeTimeout'): int, Optional('TreeWidth'): int}}, Optional('zabbix'): bool, Optional('nfs'): bool, Optional('ide'): bool, Optional('useMasterAsCompute'): bool, - Optional('useMasterWithPublicIp'): bool, Optional('waitForServices'): [str], Optional('bootVolume'): str, - Optional('bootFromVolume'): bool, Optional('terminateBootVolume'): bool, Optional('volumeSize'): int, + Optional('useMasterWithPublicIp'): bool, Optional('waitForServices'): [str], Optional('gateway'): {'ip': str, 'portFunction': str}, Optional('dontUploadCredentials'): bool, Optional('fallbackOnOtherImage'): bool, Optional('localDNSLookup'): bool, Optional('features'): [str], 'workerInstances': [ WORKER], 'masterInstance': MASTER, Optional('vpngtw'): {'type': str, 'image': str}, - Optional('bootVolume'): str, - Optional('bootFromVolume'): bool, Optional('terminateBootVolume'): bool, Optional('volumeSize'): int + Optional('bootVolume'): { + Optional('name'): str, + Optional('terminate'): bool, + Optional('size'): int + }, }) other_schema = Schema( {'infrastructure': str, 'cloud': str, 'sshUser': str, Or('subnet', 'network'): str, 'cloud_identifier': str, Optional('waitForServices'): [str], Optional('features'): [str], 'workerInstances': [ - WORKER], 'vpnInstance': VPN}) + WORKER], 'vpnInstance': VPN, + Optional('bootVolume'): { + Optional('name'): str, + Optional('terminate'): bool, + Optional('size'): int + }, + }) def validate_configurations(configurations, log): diff --git a/bibigrid/core/utility/wireguard/__init__.py b/bibigrid/core/utility/wireguard/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bibigrid/models/__init__.py b/bibigrid/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bibigrid/models/return_threading.py b/bibigrid/models/return_threading.py index 52c8b054..028da822 100644 --- a/bibigrid/models/return_threading.py +++ b/bibigrid/models/return_threading.py @@ -13,7 +13,7 @@ class ReturnThread(threading.Thread): """ # pylint: disable=dangerous-default-value - def __init__(self, group=None, target=None, name=None, args=(), + def __init__(self, *, group=None, target=None, name=None, args=(), kwargs={}): threading.Thread.__init__(self, group, target, name, args, kwargs) self._return = None diff --git a/bibigrid/openstack/__init__.py b/bibigrid/openstack/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bibigrid/openstack/openstack_provider.py b/bibigrid/openstack/openstack_provider.py index dd1ccbfa..3b15ac06 100644 --- a/bibigrid/openstack/openstack_provider.py +++ b/bibigrid/openstack/openstack_provider.py @@ -114,8 +114,11 @@ def get_subnet_by_id_or_name(self, subnet_id_or_name): def list_servers(self): return [elem.toDict() for elem in self.conn.list_servers()] - def create_server(self, name, flavor, image, network, key_name=None, wait=True, volumes=None, security_groups=None, - boot_volume=None, boot_from_volume=False, terminate_boot_volume=False, volume_size=50): + def create_server(self, *, name, flavor, image, network, key_name=None, wait=True, volumes=None, + security_groups=None, + # pylint: disable=too-many-positional-arguments,too-many-locals + boot_volume=None, boot_from_volume=False, terminate_boot_volume=False, volume_size=50, + description=""): try: server = self.conn.create_server(name=name, flavor=flavor, image=image, network=network, key_name=key_name, volumes=volumes, security_groups=security_groups, boot_volume=boot_volume, @@ -193,11 +196,14 @@ def get_free_resources(self): def get_volume_by_id_or_name(self, name_or_id): return self.conn.get_volume(name_or_id) - def create_volume_from_snapshot(self, snapshot_name_or_id): + def create_volume_from_snapshot(self, snapshot_name_or_id, volume_name_or_id=None, + description=None): """ Uses the cinder API to create a volume from snapshot: https://github.com/openstack/python-cinderclient/blob/master/cinderclient/v3/volumes.py @param snapshot_name_or_id: name or id of snapshot + @param volume_name_or_id: + @param description: @return: id of created volume """ LOG.debug("Trying to create volume from snapshot") @@ -207,11 +213,11 @@ def create_volume_from_snapshot(self, snapshot_name_or_id): if snapshot["status"] == "available": LOG.debug("Snapshot %s is available.", {snapshot_name_or_id}) size = snapshot["size"] - name = create.PREFIX_WITH_SEP + snapshot["name"] - description = f"Created from snapshot {snapshot_name_or_id} by BiBiGrid" + name = volume_name_or_id or (create.PREFIX_WITH_SEP + snapshot["name"]) + description = description or f"Created from snapshot {snapshot_name_or_id} by BiBiGrid" volume = self.cinder.volumes.create(size=size, snapshot_id=snapshot["id"], name=name, description=description) - return volume.to_dict()["id"] + return volume.to_dict() LOG.warning("Snapshot %s is %s; must be available.", snapshot_name_or_id, snapshot['status']) else: LOG.warning("Snapshot %s not found.", snapshot_name_or_id) @@ -339,3 +345,31 @@ def get_server(self, name_or_id): @return: """ return self.conn.get_server(name_or_id) + + def create_volume(self, *, name, size, wait=True, volume_type=None, description=None): + """ + Creates a volume + @param name: name of the created volume + @param size: size of the created volume in GB + @param wait: if true waits for volume to be created + @param volume_type: depends on the location, but for example NVME or HDD + @param description: a non-functional description to help dashboard users + @return: the created volume + """ + return self.conn.create_volume(size=size, name=name, wait=wait, volume_type=volume_type, + description=description) + + def delete_volume(self, name_or_id): + """ + Deletes the volume that has name_or_id. + @param name_or_id: + @return: True if deletion was successful, else False + """ + return self.conn.delete_volume(name_or_id=name_or_id) + + def list_volumes(self): + """ + Returns a list of all volumes on the provider. + @return: list of volumes + """ + return self.conn.list_volumes() diff --git a/documentation/markdown/features/configuration.md b/documentation/markdown/features/configuration.md index 34ba43da..219ac3ee 100644 --- a/documentation/markdown/features/configuration.md +++ b/documentation/markdown/features/configuration.md @@ -70,38 +70,6 @@ cloudScheduling: sshTimeout: 5 ``` -#### masterMounts (optional:False) - -`masterMounts` expects a list of volumes and snapshots. Those will be attached to the master. If any snapshots are -given, volumes are first created from them. Volumes are not deleted after Cluster termination. - -```yaml -masterMounts: - - name: test # name of the volume to be attached - mountPoint: /vol/spool2 # where attached volume is to be mount to (optional) -``` - -`masterMounts` can be combined with [nfsshares](#nfsshares-optional). -The following example attaches volume test to our master instance and mounts it to `/vol/spool2`. -Then it creates an nfsshare on `/vol/spool2` allowing workers to access the volume test. - -```yaml -masterMounts: - - name: test # name of the volume to be attached - mountPoint: /vol/spool2 # where attached volume is to be mount to (optional) - -nfsshares: - - /vol/spool2 -``` - -
- - What is mounting? - - -[Mounting](https://man7.org/linux/man-pages/man8/mount.8.html) adds a new filesystem to the file tree allowing access. -
- #### nfsShares (optional) `nfsShares` expects a list of folder paths to share over the network using nfs. @@ -263,10 +231,21 @@ workerInstance: features: # optional - hasdatabase - holdsinformation - bootVolume: False - bootFromVolume: True - terminateBootVolume: True - volumeSize: 50 + volumes: # optional + - name: volumeName + snapshot: snapshotName # optional; to create volume from + # one or none of these + # permanent: False + # semiPermanent: False + # exists: False + mountPoint: /vol/test + size: 50 + fstype: ext4 + type: None + bootVolume: # optional + name: False + terminate: True + size: 50 ``` - `type` sets the instance's hardware configuration. @@ -275,10 +254,24 @@ workerInstance: - `onDemand` (optional:False) defines whether nodes in the worker group are scheduled on demand (True) or are started permanently (False). Please only use if necessary. On Demand Scheduling improves resource availability for all users. This option only works for single cloud setups for now. - `partitions` (optional:[]) allow you to force Slurm to schedule to a group of nodes (partitions) ([more](https://slurm.schedmd.com/slurm.conf.html#SECTION_PARTITION-CONFIGURATION)) - `features` (optional:[]) allow you to force Slurm to schedule a job only on nodes that meet certain `bool` constraints. This can be helpful when only certain nodes can access a specific resource - like a database ([more](https://slurm.schedmd.com/slurm.conf.html#OPT_Features)). -- `bootVolume` (optional:None) takes name or id of a boot volume and boots from that volume if given. -- `bootFromVolume` (optional:False) if True, the instance will boot from a volume created for this purpose. -- `terminateBootVolume` (optional:True) if True, the boot volume will be terminated when the server is terminated. -- `volumeSize` (optional:50) if a boot volume is created, this sets its size. +- `bootVolume` (optional) + - `name` (optional:None) takes name or id of a boot volume and boots from that volume if given. + - `terminate` (optional:True) if True, the boot volume will be terminated when the server is terminated. + - `size` (optional:50) if a boot volume is created, this sets its size. + +##### volumes (optional) + +You can create a temporary volume (default), a semipermanent volume, a permanent volume and you can do all of those from a snapshot, too. +You can even attach a volume that already exists. However, don't try to add a single existing volume to a group with count >1 as most volumes can't be attached to more than one instance. + +- **Semi-permanent** volumes are deleted once their cluster is destroyed not when their server is powered down during the cluster's runtime. By setting `semiPermanent: True`, you create a semi-permanent volume. +- **Permanent** volumes are deleted once you delete them manually. By setting `permanent: True`, you create a permanent volume. +- **Temporary** volumes are deleted once their server is destroyed. By setting `permanent: False` and `semiPermanent: False` (their default value), you create a temporary volume. +- **Existing** volumes can be attached by setting the exact name of that volume as `name` and setting `exists: True`. If you use this to attach the volume to a worker, make sure that the worker group's count is 1. Otherwise, BiBiGrid will try to attach that volume to each instance. +- You can create volumes from **snapshots** by setting `snapshot` to your snapshot's name. You can create all kinds of volumes of them. +- `type` allows you to set the storage option. For Bielefeld there are `CEPH_HDD` (HDD) and `CEPH_NVME` (SSD). + +Termination of these volumes is done by regex looking for the cluster id. For cluster termination: `^bibigrid-(master-{cluster_id}|(worker|vpngtw)-{cluster_id}-(\d+))-(semiperm|tmp)-\d+(-.+)?$` ##### Find your active `images` @@ -305,7 +298,6 @@ There's also a [Fallback Option](#fallbackonotherimage-optionalfalse). openstack flavor list --os-cloud=openstack ``` - #### masterInstance or vpnInstance? ##### masterInstance @@ -319,7 +311,7 @@ Only in the first configuration and only one: bootVolume: False bootFromVolume: True terminateBootVolume: False - volumeSize: 50 + bootVolumeSize: 50 ``` You can create features for the master [in the same way](#features-optional) as for the workers: @@ -377,14 +369,18 @@ If both [worker group](#workerinstances) or [master features](#masterInstance) a they are merged. If you only have a single cloud and therefore a single configuration, this key is not helpful as a feature that is present at all nodes can be omitted as it can't influence the scheduling. -#### bootFromVolume (optional:False) -If True, the instance will boot from a volume created for this purpose. Keep in mind that on demand scheduling can lead -to multiple boots of the same configurated node. If you don't make use of [terminateBootVolume](#terminatebootvolume-optionaltrue) -this will lead to many created volumes. +#### bootVolume (optional) + +Instead of setting the `bootVolume` for every instance you can also set it cloud wide: -#### volumeSize (optional:50) -The created volume's size if you use [bootFromVolume](#bootfromvolume-optionalfalse). +- `bootVolume` (optional) + - `name` (optional:None) takes name or id of a boot volume and boots from that volume if given. + - `terminate` (optional:True) if True, the boot volume will be terminated when the server is terminated. + - `size` (optional:50) if a boot volume is created, this sets its size. -#### terminateBootVolume (optional:True) -If True, once the instance is shut down, boot volume is destroyed. This does not affect other attached volumes. -Only the boot volume is affected. +```yaml +bootVolume: + name: False + terminate: True + size: 50 +``` \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 0aee6fe8..2225a96e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,2 @@ -ansible_lint==24.9.0 -pylint==2.14.5 +ansible-lint==24.10 +pylint==3.3.1 \ No newline at end of file diff --git a/resources/playbook/roles/bibigrid/files/slurm/create_server.py b/resources/playbook/roles/bibigrid/files/slurm/create_server.py index a7d1ca7f..afe1f22f 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/create_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/create_server.py @@ -25,20 +25,156 @@ class ImageNotFoundException(Exception): """ Image not found exception""" +class ConfigurationException(Exception): + """ Configuration exception """ + + LOGGER_FORMAT = "%(asctime)s [%(levelname)s] %(message)s" logging.basicConfig(format=LOGGER_FORMAT, filename="/var/log/slurm/create_server.log", level=logging.INFO) +# For Debugging +# console_handler = logging.StreamHandler(sys.stdout) +# console_handler.setFormatter(logging.Formatter(LOGGER_FORMAT)) +# logging.basicConfig(level=logging.INFO, handlers=[console_handler]) + HOSTS_FILE_PATH = "/opt/playbook/vars/hosts.yaml" logging.info("create_server.py started") start_time = time.time() if len(sys.argv) < 2: - logging.warning("usage: $0 instance1_name[,instance2_name,...]") + logging.warning("Not enough arguments!") logging.info("Your input %s with length %s", sys.argv, len(sys.argv)) sys.exit(1) start_workers = sys.argv[1].split("\n") logging.info("Starting instances %s", start_workers) +server_start_data = {"started_servers": [], "other_openstack_exceptions": [], "connection_exceptions": [], + "available_servers": [], "openstack_wait_exceptions": []} + +GROUP_VARS_PATH = "/opt/playbook/group_vars" +worker_groups = [] +for filename in os.listdir(GROUP_VARS_PATH): + if filename != "master.yaml": + worker_group_yaml_file = os.path.join(GROUP_VARS_PATH, filename) + # checking if it is a file + if os.path.isfile(worker_group_yaml_file): + with open(worker_group_yaml_file, mode="r", encoding="utf-8") as worker_group_yaml: + worker_groups.append(yaml.safe_load(worker_group_yaml)) + +# read common configuration +with open("/opt/playbook/vars/common_configuration.yaml", mode="r", encoding="utf-8") as common_configuration_file: + common_config = yaml.safe_load(common_configuration_file) +logging.info(f"Maximum 'is active' attempts: {common_config['cloud_scheduling']['sshTimeout']}") +# read clouds.yaml +with open("/etc/openstack/clouds.yaml", mode="r", encoding="utf-8") as clouds_file: + clouds = yaml.safe_load(clouds_file)["clouds"] + +connections = {} # connections to cloud providers +for cloud in clouds: + connections[cloud] = os_client_config.make_sdk(cloud=cloud, volume_api_version="3") + + +# pylint: disable=duplicate-code +def create_volume_from_snapshot(connection, snapshot_name_or_id, volume_name_or_id=None): + """ + Uses the cinder API to create a volume from snapshot: + https://github.com/openstack/python-cinderclient/blob/master/cinderclient/v3/volumes.py + @param connection: + @param snapshot_name_or_id: name or id of snapshot + @param volume_name_or_id: + @return: id of created volume + """ + logging.debug("Trying to create volume from snapshot") + snapshot = connection.get_volume_snapshot(snapshot_name_or_id) + if snapshot: + logging.debug(f"Snapshot {snapshot_name_or_id} found.") + if snapshot["status"] == "available": + logging.debug("Snapshot %s is available.", {snapshot_name_or_id}) + size = snapshot["size"] + name = volume_name_or_id or f"bibigrid-{snapshot['name']}" + description = f"Created from snapshot {snapshot_name_or_id} by BiBiGrid" + volume = connection.create_volume(name=name, size=size, description=description) + return volume.toDict() + logging.warning("Snapshot %s is %s; must be available.", snapshot_name_or_id, snapshot['status']) + else: + logging.warning("Snapshot %s not found.", snapshot_name_or_id) + return None + + +def get_server_vars(name): + # loading server_vars + host_vars_path = f"/opt/playbook/host_vars/{name}.yaml" + server_vars = {"volumes": []} + if os.path.isfile(host_vars_path): + logging.info(f"Found host_vars file {host_vars_path}.") + with open(host_vars_path, mode="r", encoding="utf-8") as host_vars_file: + server_vars = yaml.safe_load(host_vars_file) + logging.info(f"Loaded Vars: {server_vars}") + else: + logging.info(f"No host vars exist (group vars still apply). Using {server_vars}.") + return server_vars + + +# pylint: disable=duplicate-code +def create_server_volumes(connection, host_vars, name): + logging.info("Creating volumes ...") + volumes = host_vars.get('volumes', []) + return_volumes = [] + + logging.info(f"Instance Volumes {volumes}") + for volume in volumes: + logging.debug(f"Trying to find volume {volume['name']}") + return_volume = connection.get_volume(volume['name']) + if not return_volume: + logging.debug(f"Volume {volume['name']} not found.") + + if volume.get('snapshot'): + logging.debug("Creating volume from snapshot...") + return_volume = create_volume_from_snapshot(connection, volume['snapshot'], volume['name']) + if not return_volume: + raise ConfigurationException(f"Snapshot {volume['snapshot']} not found!") + else: + logging.debug("Creating volume...") + return_volume = connection.create_volume(name=volume['name'], size=volume.get("size", 50), + volume_type=volume.get("type"), + description=f"Created for {name}") + return_volumes.append(return_volume) + return return_volumes + + +def volumes_host_vars_update(connection, server, host_vars): + logging.info("Updating host vars volume info") + host_vars_path = f"/opt/playbook/host_vars/{server['name']}.yaml" + + with FileLock(f"{host_vars_path}.lock"): + logging.info(f"{host_vars_path}.lock acquired") + # get name and device info + server_attachment = [] + for server_volume in server["volumes"]: + volume = connection.get_volume(server_volume["id"]) + for attachment in volume["attachments"]: + if attachment["server_id"] == server["id"]: + server_attachment.append({"name": volume["name"], "device": attachment["device"]}) + break + # add device info + volumes = host_vars.get("volumes", []) + if volumes: + for volume in volumes: + logging.info(f"Finding device for {volume['name']}.") + server_volume = next((server_volume for server_volume in server_attachment if + server_volume["name"] == volume["name"]), None) + if not server_volume: + raise RuntimeError( + f"Created server {server['name']} doesn't have attached volume {volume['name']}.") + volume["device"] = server_volume.get("device") + + logging.debug(f"Added Configuration: Instance {server['name']} has volume {volume['name']} " + f"as device {volume['device']} that is going to be mounted to " + f"{volume.get('mountPoint')}") + with open(host_vars_path, mode="w+", encoding="utf-8") as host_vars_file: + yaml.dump(host_vars, host_vars_file) + logging.info(f"{host_vars_path}.lock released") + def select_image(start_worker_group, connection): image = start_worker_group["image"] @@ -65,19 +201,19 @@ def select_image(start_worker_group, connection): return image -def start_server(worker, start_worker_group, start_data): +def start_server(name, start_worker_group, start_data): try: - logging.info("Create server %s.", worker) + logging.info("Create server %s.", name) connection = connections[start_worker_group["cloud_identifier"]] # check if running - already_running_server = connection.get_server(worker) + already_running_server = connection.get_server(name) if already_running_server: logging.warning( - f"Already running server {worker} on {start_worker_group['cloud_identifier']} (will be terminated): " - f"{already_running_server}") - server_deleted = connection.delete_server(worker) + f"Already running server {name} on {start_worker_group['cloud_identifier']} (will be terminated): " + f"{already_running_server['name']}") + server_deleted = connection.delete_server(name) logging.info( - f"Server {worker} on {start_worker_group['cloud_identifier']} has been terminated ({server_deleted}). " + f"Server {name} on {start_worker_group['cloud_identifier']} has been terminated ({server_deleted}). " f"Continuing startup.") # check for userdata userdata = "" @@ -87,18 +223,26 @@ def start_server(worker, start_worker_group, start_data): userdata = userdata_file.read() # create server and ... image = select_image(start_worker_group, connection) - server = connection.create_server(name=worker, flavor=start_worker_group["flavor"]["name"], image=image, + host_vars = get_server_vars(name) + volumes = create_server_volumes(connection, host_vars, name) + boot_volume = start_worker_group.get("bootVolume", {}) + server = connection.create_server(name=name, flavor=start_worker_group["flavor"]["name"], image=image, network=start_worker_group["network"], key_name=f"tempKey_bibi-{common_config['cluster_id']}", security_groups=[f"default-{common_config['cluster_id']}"], userdata=userdata, - wait=False) + volumes=volumes, wait=False, + boot_from_volume=boot_volume.get("name", False), + boot_volume=bool(boot_volume), + terminate_volume=boot_volume.get("terminate", True), + volume_size=boot_volume.get("size", 50) + ) # ... add it to server start_data["started_servers"].append(server) try: connection.wait_for_server(server, auto_ip=False, timeout=600) server = connection.get_server(server["id"]) except OpenStackCloudException as exc: - logging.warning("While creating %s the OpenStackCloudException %s occurred.", worker, exc) + logging.warning("While creating %s the OpenStackCloudException %s occurred.", name, exc) server_start_data["openstack_wait_exceptions"].append(server.name) return logging.info("%s is active. Checking ssh", server.name) @@ -110,11 +254,12 @@ def start_server(worker, start_worker_group, start_data): logging.warning(f"{exc}: Couldn't connect to {server.name}.") server_start_data["connection_exceptions"].append(server.name) logging.info("Update hosts.yaml") + volumes_host_vars_update(connection, server, host_vars) update_hosts(server.name, server.private_v4) except OpenStackCloudException as exc: - logging.warning("While creating %s the OpenStackCloudException %s occurred. Worker ignored.", worker, exc) - server_start_data["other_openstack_exception"].append(worker) + logging.warning("While creating %s the OpenStackCloudException %s occurred. Worker ignored.", name, exc) + server_start_data["other_openstack_exceptions"].append(name) def check_ssh_active(private_ip, private_key="/opt/slurm/.ssh/id_ecdsa", username="ubuntu"): @@ -139,7 +284,7 @@ def check_ssh_active(private_ip, private_key="/opt/slurm/.ssh/id_ecdsa", usernam except paramiko.ssh_exception.NoValidConnectionsError as exc: logging.info("Attempting to connect to %s... This might take a while", private_ip) if attempts < common_config["cloud_scheduling"]["sshTimeout"]: - time.sleep(2 ** (2+attempts)) + time.sleep(2 ** (2 + attempts)) attempts += 1 else: logging.warning("Attempt to connect to %s failed.", private_ip) @@ -206,41 +351,16 @@ def _run_playbook(cmdline_args): return runner, runner_response, runner_error, runner.rc -server_start_data = {"started_servers": [], "other_openstack_exceptions": [], "connection_exceptions": [], - "available_servers": [], "openstack_wait_exceptions": []} - -GROUP_VARS_PATH = "/opt/playbook/group_vars" -worker_groups = [] -for filename in os.listdir(GROUP_VARS_PATH): - if filename != "master.yaml": - worker_group_yaml_file = os.path.join(GROUP_VARS_PATH, filename) - # checking if it is a file - if os.path.isfile(worker_group_yaml_file): - with open(worker_group_yaml_file, mode="r", encoding="utf-8") as worker_group_yaml: - worker_groups.append(yaml.safe_load(worker_group_yaml)) - -# read common configuration -with open("/opt/playbook/vars/common_configuration.yaml", mode="r", encoding="utf-8") as common_configuration_file: - common_config = yaml.safe_load(common_configuration_file) -logging.info(f"Maximum 'is active' attempts: {common_config['cloud_scheduling']['sshTimeout']}") -# read clouds.yaml -with open("/etc/openstack/clouds.yaml", mode="r", encoding="utf-8") as clouds_file: - clouds = yaml.safe_load(clouds_file)["clouds"] - -connections = {} # connections to cloud providers -for cloud in clouds: - connections[cloud] = os_client_config.make_sdk(cloud=cloud) - start_server_threads = [] for worker_group in worker_groups: - for start_worker in start_workers: + for worker_name in start_workers: # start all servers that are part of the current worker group result = subprocess.run(["scontrol", "show", "hostname", worker_group["name"]], stdout=subprocess.PIPE, check=True) # get all workers in worker_type possible_workers = result.stdout.decode("utf-8").strip().split("\n") - if start_worker in possible_workers: + if worker_name in possible_workers: start_worker_thread = threading.Thread(target=start_server, - kwargs={"worker": start_worker, "start_worker_group": worker_group, + kwargs={"name": worker_name, "start_worker_group": worker_group, "start_data": server_start_data}) start_worker_thread.start() start_server_threads.append(start_worker_thread) @@ -256,13 +376,21 @@ def _run_playbook(cmdline_args): # run ansible on master node to configure dns logging.info("Call Ansible to configure dns.") r, response, error, rc = configure_dns() -logging.info("DNS was configure by Ansible!") +logging.info(f"This is error {error}") +logging.info(f"This is response {response}") +if error: + logging.error(response) +else: + logging.info("DNS was configure by Ansible!") # run ansible on started worker nodes logging.info("Call Ansible to configure worker.") RUNNABLE_INSTANCES = ",".join(server_start_data["available_servers"]) r, response, error, rc = configure_worker(RUNNABLE_INSTANCES) -logging.info("Worker were configured by Ansible!") +if error: + logging.error(response) +else: + logging.info("Worker were configured by Ansible!") # the rest of this code is only concerned with logging errors unreachable_list = list(r.stats["dark"].keys()) diff --git a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py index c9677393..d81a3d84 100644 --- a/resources/playbook/roles/bibigrid/files/slurm/delete_server.py +++ b/resources/playbook/roles/bibigrid/files/slurm/delete_server.py @@ -10,6 +10,7 @@ import subprocess import sys import time +import re import os_client_config import requests @@ -56,7 +57,7 @@ connections = {} # connections to cloud providers for cloud in clouds: - connections[cloud] = os_client_config.make_sdk(cloud=cloud) + connections[cloud] = os_client_config.make_sdk(cloud=cloud, volume_api_version="3") for worker_group in worker_groups: for terminate_worker in terminate_workers: @@ -65,7 +66,14 @@ check=True) # get all workers in worker_type possible_workers = result.stdout.decode("utf-8").strip().split("\n") if terminate_worker in possible_workers: - result = connections[worker_group["cloud_identifier"]].delete_server(terminate_worker) + connection = connections[worker_group["cloud_identifier"]] + result = connection.delete_server(terminate_worker, wait=True) + logging.info("Deleting Volumes") + volume_list = connection.list_volumes() + volume_regex = re.compile(fr"^{terminate_worker}-(tmp)-\d+(-.+)?$") + for volume in volume_list: + if volume_regex.match(volume["name"]): + logging.info(f"Trying to delete volume {volume['name']}: {connection.delete_volume(volume)}") if not result: logging.warning(f"Couldn't delete worker {terminate_worker}: Server doesn't exist") else: diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml b/resources/playbook/roles/bibigrid/tasks/020-disk-automount.yaml similarity index 71% rename from resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml rename to resources/playbook/roles/bibigrid/tasks/020-disk-automount.yaml index fd8c4619..16cfa3bf 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server-automount.yaml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-automount.yaml @@ -1,16 +1,19 @@ -- when: item.mount_point is defined +- when: item.mountPoint is defined block: - name: Make sure disks are available failed_when: false filesystem: - fstype: ext4 + fstype: "{{ item.fstype | default('ext4') }}" dev: "{{ item.device }}" force: false state: present - - name: Get the filesystem type of the device using lsblk + - name: Get volume filesystem using lsblk command: "lsblk -no FSTYPE {{ item.device }}" register: filesystem_type + until: filesystem_type.stdout != "" + retries: 5 + delay: 2 changed_when: false - name: Log the filesystem type @@ -19,7 +22,7 @@ - name: Create mount folders if they don't exist file: - path: "{{ item.mount_point }}" + path: "{{ item.mountPoint }}" state: directory mode: "0o755" owner: root @@ -27,7 +30,7 @@ - name: Mount disks mount: - path: "{{ item.mount_point }}" + path: "{{ item.mountPoint }}" src: "{{ item.device }}" state: mounted fstype: "{{ filesystem_type.stdout }}" diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yaml b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yaml index efaa9569..17bc4b51 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk-server.yaml @@ -16,8 +16,3 @@ with_items: - "{{ master.disks }}" when: master.disks is defined - -- name: Automount - when: volumes is defined - include_tasks: 020-disk-server-automount.yaml - with_items: "{{ volumes }}" diff --git a/resources/playbook/roles/bibigrid/tasks/020-disk.yaml b/resources/playbook/roles/bibigrid/tasks/020-disk.yaml index 9948ff3c..1a1bb485 100644 --- a/resources/playbook/roles/bibigrid/tasks/020-disk.yaml +++ b/resources/playbook/roles/bibigrid/tasks/020-disk.yaml @@ -33,3 +33,8 @@ src: /vol/ dest: '/home/{{ ansible_distribution | lower }}/vol' state: link + +- name: Automount + when: volumes is defined + include_tasks: 020-disk-automount.yaml + with_items: "{{ volumes }}" diff --git a/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml b/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml index 03bce9ca..b6d96bf1 100644 --- a/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/025-nfs-server.yaml @@ -3,7 +3,7 @@ name: "nfs-kernel-server" state: present -- name: Create export directories +- name: Create shared directories file: path: "{{ item.src }}" state: directory @@ -11,7 +11,7 @@ group: root mode: "0o777" with_items: - - "{{ nfs_mounts }}" + - "{{ nfs_shares }}" - name: Configure nfs exports lineinfile: @@ -24,6 +24,6 @@ {{ '10.0.0.0/'+wireguard.mask_bits|default(24)|string + '(rw,nohide,insecure,no_subtree_check,async)' if wireguard is defined }}" with_items: - - "{{ nfs_mounts }}" + - "{{ nfs_shares }}" notify: - nfs-server diff --git a/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml b/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml index c45c3e9a..e18f89d1 100644 --- a/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml +++ b/resources/playbook/roles/bibigrid/tasks/025-nfs-worker.yaml @@ -10,7 +10,7 @@ delay: 2 state: started -- name: Create mount points +- name: Create shared directories file: path: "{{ item.dst }}" state: directory @@ -18,7 +18,7 @@ group: root mode: "0o777" with_items: - - "{{ nfs_mounts }}" + - "{{ nfs_shares }}" - name: Mount shares mount: @@ -27,4 +27,4 @@ fstype: nfs4 state: mounted with_items: - - "{{ nfs_mounts }}" + - "{{ nfs_shares }}" diff --git a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml index 059e11ef..43aa1876 100644 --- a/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml +++ b/resources/playbook/roles/bibigrid/tasks/042-slurm-server.yaml @@ -1,8 +1,16 @@ -- name: Change group ownership of OpenStack credentials file to slurm - file: - path: /etc/openstack/clouds.yaml - group: slurm - mode: "0o640" # (owner can read/write, group can read, others have no access) +- name: Change group ownership of OpenStack credentials file to slurm if it exists + block: + - name: Check if the OpenStack credentials file exists + stat: + path: /etc/openstack/clouds.yaml + register: file_stat + + - name: Change group ownership of OpenStack credentials file to slurm + file: + path: /etc/openstack/clouds.yaml + group: slurm + mode: "0o640" # (owner can read/write, group can read, others have no access) + when: file_stat.stat.exists - name: Create slurm db mysql_db: diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/integration_test.py b/tests/integration/integration_test.py new file mode 100644 index 00000000..50f9b11d --- /dev/null +++ b/tests/integration/integration_test.py @@ -0,0 +1,101 @@ +""" +Integration test module that assumes that one cluster is started at a time. +For multiple clusters the .bibigrid.mem structure needs to be updated TODO +This is not ready yet. +""" + +import os +import subprocess + +import yaml + +# Define common configuration paths +CONFIG_DIR = os.path.expanduser("~/.config/bibigrid") +MEM_FILE = os.path.join(CONFIG_DIR, ".bibigrid.mem") +KEYFILE_PATH_TEMPLATE = os.path.join(CONFIG_DIR, "keys", "tempKey_bibi-{cluster_id}") +BIBIGRID_SCRIPT = os.path.abspath("../../bibigrid.sh") + + +def start_cluster(): + """Start the cluster by calling bibigrid.sh.""" + print("Starting the cluster...") + result = subprocess.run([BIBIGRID_SCRIPT, "-c", "-vv", "-i", "bibigrid.yaml"], capture_output=True, text=True, + check=False) + if result.returncode == 0: + print("Cluster started successfully.") + # print(result.stdout) + else: + print("Failed to start the cluster.") + # print(result.stderr) + raise Exception("Cluster start failed") + + +def read_cluster_info(): + """Read last cluster information from bibigrid.mem file.""" + with open(MEM_FILE, "r", encoding="utf8") as f: + cluster_data = yaml.safe_load(f) + return cluster_data["cluster_id"], cluster_data["floating_ip"], cluster_data["ssh_user"] + + +def build_keyfile_path(cluster_id): + """Construct the keyfile path using cluster ID.""" + return KEYFILE_PATH_TEMPLATE.format(cluster_id=cluster_id) + + +def ssh_command(master_ip, keyfile, command, ssh_user): + """Execute a command on the master node via SSH.""" + ssh_cmd = [ + "ssh", + "-i", keyfile, + "-o", "StrictHostKeyChecking=no", + f"{ssh_user}@{master_ip}", + command + ] + return subprocess.run(ssh_cmd, capture_output=True, text=True, check=False) + + +def terminate_cluster(): + """Terminate the cluster by calling bibigrid.sh.""" + print("Terminating the cluster...") + result = subprocess.run([BIBIGRID_SCRIPT, "-i", "bibigrid.yaml", "-t", "-vv"], capture_output=True, text=True, + check=False) + if result.returncode == 0: + print("Cluster terminated successfully.") + print(result.stdout) + else: + print("Failed to terminate the cluster.") + print(result.stderr) + raise Exception("Cluster termination failed") + + +def main(): + # Step 0: Create the cluster + start_cluster() + # Step 1: Read cluster info + cluster_id, master_ip, ssh_user = read_cluster_info() + print(f"Cluster ID: {cluster_id}, Master IP: {master_ip}") + + # Step 2: Build keyfile path + keyfile = build_keyfile_path(cluster_id) + print(f"Using keyfile: {keyfile}") + + # Step 3: Check worker nodes by running srun from master node + check_command = "srun -N2 hostname>" + print(f"Running on master: {check_command}") + + # Run the command on the master instance + result = ssh_command(master_ip, keyfile, check_command, ssh_user) + + if result.returncode == 0: + print("Worker nodes are up and responding:") + print(result.stdout) + else: + print("Failed to run command on worker nodes.") + print(result.stderr) + + # Step N: Terminate the cluster + terminate_cluster() + + +if __name__ == "__main__": + main() diff --git a/tests/test_terminate_cluster.py b/tests/test_terminate_cluster.py deleted file mode 100644 index 949b0d1c..00000000 --- a/tests/test_terminate_cluster.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Module to test terminate -""" -from unittest import TestCase -from unittest.mock import MagicMock, patch - -from bibigrid.core import startup -from bibigrid.core.actions import create -from bibigrid.core.actions import terminate - - -class TestTerminate(TestCase): - """ - Class to test terminate. - """ - - @patch("bibigrid.core.actions.terminate.delete_local_keypairs") - @patch("bibigrid.core.actions.terminate.terminate_output") - def test_terminate(self, mock_output, mock_local): - mock_local.return_value = True - provider = MagicMock() - provider.cloud_specification["auth"]["project_name"] = 32 - cluster_id = 42 - provider.list_servers.return_value = [{"name": create.MASTER_IDENTIFIER(cluster_id=str(cluster_id)), "id": 21}] - provider.delete_server.return_value = True - provider.delete_keypair.return_value = True - provider.delete_security_group.return_value = True - provider.delete_application_credentials.return_value = True - terminate.terminate(str(cluster_id), [provider], startup.LOG, False, True) - provider.delete_server.assert_called_with(21) - provider.delete_keypair.assert_called_with(create.KEY_NAME.format(cluster_id=cluster_id)) - mock_output.assert_called_with([provider.delete_server.return_value], [provider.delete_keypair.return_value], - [provider.delete_security_group.return_value], - provider.delete_application_credentials.return_value, str(cluster_id), - startup.LOG) - - @patch("bibigrid.core.actions.terminate.delete_local_keypairs") - @patch("logging.info") - def test_terminate_none(self, _, mock_local): - mock_local.return_value = True - provider = MagicMock() - provider[0].specification["auth"]["project_name"] = "test_project_name" - cluster_id = 42 - provider.list_servers.return_value = [ - {"name": create.MASTER_IDENTIFIER(cluster_id=str(cluster_id + 1)), "id": 21}] - provider.delete_keypair.return_value = False - terminate.terminate(str(cluster_id), [provider], startup.LOG, False, True) - provider.delete_server.assert_not_called() - provider.delete_keypair.assert_called_with( - create.KEY_NAME.format(cluster_id=str(cluster_id))) # since keypair is not called diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit_tests/provider/__init__.py b/tests/unit_tests/provider/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/provider/test_provider.py b/tests/unit_tests/provider/test_provider.py similarity index 89% rename from tests/provider/test_provider.py rename to tests/unit_tests/provider/test_provider.py index d6ba89c2..c8ba647b 100644 --- a/tests/provider/test_provider.py +++ b/tests/unit_tests/provider/test_provider.py @@ -4,6 +4,7 @@ import logging import os +import time # TODO remove dirty test import unittest import bibigrid.core.utility.paths.basic_path as bP @@ -53,12 +54,8 @@ SNAPSHOT_KEYS = {'id', 'created_at', 'updated_at', 'name', 'description', 'volume_id', 'status', 'size', 'metadata', 'os-extended-snapshot-attributes:project_id', 'os-extended-snapshot-attributes:progress'} -VOLUME_KEYS = {'location', 'id', 'name', 'description', 'size', 'attachments', 'status', 'migration_status', 'host', - 'replication_driver', 'replication_status', 'replication_extended_status', 'snapshot_id', 'created_at', - 'updated_at', 'source_volume_id', 'consistencygroup_id', 'volume_type', 'metadata', 'is_bootable', - 'is_encrypted', 'can_multiattach', 'properties', 'display_name', 'display_description', 'bootable', - 'encrypted', 'multiattach', 'availability_zone', 'source_volid', 'user_id', - 'os-vol-tenant-attr:tenant_id'} +VOLUME_KEYS = {'description', 'id', 'metadata', 'status', 'size', + 'snapshot_id', 'attachments', 'name', 'volume_type'} FREE_RESOURCES_KEYS = {'total_cores', 'floating_ips', 'instances', 'total_ram', 'volumes', 'volume_gigabytes', 'snapshots', 'backups', 'backup_gigabytes'} @@ -164,8 +161,8 @@ def test_active_server_methods(self): self.assertEqual("bibigrid_test_keypair", provider_server["key_name"]) self.assertEqual(FLOATING_IP_KEYS, set(floating_ip.keys())) list_server = next(server for server in server_list if - server["name"] == "bibigrid_test_server" and server[ - "public_v4"] == floating_ip.floating_ip_address) + server["name"] == "bibigrid_test_server" and server[ + "public_v4"] == floating_ip.floating_ip_address) self.assertEqual("bibigrid_test_server", get_server["name"]) self.assertEqual(get_server, list_server) provider.delete_keypair("bibigrid_test_keypair") @@ -173,7 +170,6 @@ def test_active_server_methods(self): def test_get_external_network(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): - print("FIRE", provider.get_external_network(configuration["network"])) self.assertTrue(provider.get_external_network(configuration["network"])) with self.assertRaises(TypeError): provider.get_external_network("ERROR") @@ -228,6 +224,20 @@ def test_get_image_mismatch(self): with self.subTest(provider.NAME): self.assertIsNone(provider.get_image_by_id_or_name("NONE")) + def test_create_delete_volume(self): + """ + Checks whether creation and deletion of volumes works + @return: + """ + for provider in PROVIDERS: + volume_id = provider.create_volume(name="test_create_delete_volume", size=1, description="Test run") + self.assertTrue(volume_id) + volume = provider.get_volume_by_id_or_name(volume_id) + self.assertTrue(VOLUME_KEYS <= set(volume.keys())) + self.assertEqual("test_create_delete_volume", volume["name"]) + self.assertTrue(provider.delete_volume(volume_id)) + # maybe explicitly look up that the volume has been deleted + # TODO test_get_images # TODO test_get_flavors # TODO test_set_allowed_addresses @@ -244,10 +254,15 @@ def test_get_snapshot(self): self.assertEqual(SNAPSHOT_KEYS, set(provider.get_volume_snapshot_by_id_or_name( configuration["snapshotImage"]).keys())) - def test_create_volume_from_snapshot(self): + def test_create_volume_from_snapshot_with_delete(self): for provider, configuration in zip(PROVIDERS, CONFIGURATIONS): with self.subTest(provider.NAME): - volume_id = provider.create_volume_from_snapshot(configuration["snapshotImage"]) - print(volume_id) + volume_name = "test_create_volume_from_snapshot_with_delete" + volume_id = provider.create_volume_from_snapshot(snapshot_name_or_id=configuration["snapshotImage"], + volume_name_or_id=volume_name, + description="Test run") volume = provider.get_volume_by_id_or_name(volume_id) - self.assertEqual(VOLUME_KEYS, set(volume.keys())) + self.assertTrue(VOLUME_KEYS <= set(volume.keys())) + self.assertEqual(volume_name, volume["name"]) + time.sleep(1) # TODO remove dirty test + self.assertTrue(provider.delete_volume(volume_id)) diff --git a/tests/startup_tests.py b/tests/unit_tests/startup_tests.py similarity index 75% rename from tests/startup_tests.py rename to tests/unit_tests/startup_tests.py index 4a719ef2..a36464a2 100644 --- a/tests/startup_tests.py +++ b/tests/unit_tests/startup_tests.py @@ -34,12 +34,9 @@ def suppress_stdout(): logging.basicConfig(level=logging.ERROR) if __name__ == '__main__': # Unittests - suite = unittest.TestLoader().discover("./", pattern='test_*.py') - with suppress_stdout(): - unittest.TextTestRunner(verbosity=2).run(suite) - - # Provider-Test - # Configuration needs to contain providers and infrastructures - suite = unittest.TestLoader().discover("./provider", pattern='test_*.py') + # Configuration at resources/tests/bibigrid_test.yaml + # Needs to contain providers and infrastructures for the provider tests + # You can use bibigrid_test_exmaple.yaml as the basis + suite = unittest.TestLoader().discover(start_dir=".", pattern='test_*.py') with suppress_stdout(): unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests/test_ansible_configurator.py b/tests/unit_tests/test_ansible_configurator.py similarity index 76% rename from tests/test_ansible_configurator.py rename to tests/unit_tests/test_ansible_configurator.py index 717e974f..89ea1c79 100644 --- a/tests/test_ansible_configurator.py +++ b/tests/unit_tests/test_ansible_configurator.py @@ -1,7 +1,6 @@ """ Tests for ansible_configurator """ -import os from unittest import TestCase from unittest.mock import MagicMock, Mock, patch, call, mock_open, ANY @@ -12,6 +11,7 @@ from bibigrid.core.utility.yaml_dumper import NoAliasSafeDumper +# pylint: disable=too-many-positional-arguments class TestAnsibleConfigurator(TestCase): """ Test ansible configurator test class @@ -158,7 +158,7 @@ def test_generate_common_configuration_nfs_shares(self): 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, - 'nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}, + 'nfs_shares': [{'dst': '/vil/mil', 'src': '/vil/mil'}, {'dst': '/vol/spool', 'src': '/vol/spool'}], 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', 'elastic_scheduling': {'ResumeTimeout': 1200, 'SuspendTime': 3600, @@ -183,7 +183,7 @@ def test_generate_common_configuration_nfs(self): 'dns_server_list': ['8.8.8.8'], 'enable_ide': False, 'enable_nfs': 'True', 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [], 'local_dns_lookup': False, 'local_fs': False, - 'nfs_mounts': [{'dst': '/vol/spool', 'src': '/vol/spool'}], 'slurm': True, + 'nfs_shares': [{'dst': '/vol/spool', 'src': '/vol/spool'}], 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', 'elastic_scheduling': {'ResumeTimeout': 1200, 'SuspendTime': 3600, 'SuspendTimeout': 60, 'TreeWidth': 128}, @@ -208,7 +208,7 @@ def test_generate_common_configuration_ext_nfs_shares(self): 'enable_slurm': False, 'enable_zabbix': False, 'ext_nfs_mounts': [{'dst': '/vil/mil', 'src': '/vil/mil'}], 'local_dns_lookup': False, 'local_fs': False, - 'nfs_mounts': [{'dst': '/vol/spool', 'src': '/vol/spool'}], 'slurm': True, + 'nfs_shares': [{'dst': '/vol/spool', 'src': '/vol/spool'}], 'slurm': True, 'slurm_conf': {'db': 'slurm', 'db_password': 'changeme', 'db_user': 'slurm', 'elastic_scheduling': {'ResumeTimeout': 1200, 'SuspendTime': 3600, 'SuspendTimeout': 60, 'TreeWidth': 128}, @@ -257,7 +257,7 @@ def test_generate_ansible_hosts(self, mock_instance_host_dict): {'vpnInstance': {'type': 'mini', 'image': 'Ubuntu'}, 'workerInstances': [ {'type': 'tiny', 'image': 'Ubuntu', 'count': 2, 'features': ['holdsinformation']}, {'type': 'small', 'image': 'Ubuntu', 'count': 2}], 'floating_ip': "42"}] - expected = {'vpn': {'children': {'master': {'hosts': {'localhost': 0}}, + expected = {'vpn': {'children': {'master': {'hosts': {'bibigrid-master-21': 0}}, 'vpngtw': {'hosts': {'bibigrid-vpngtw-21-0': {'ansible_host': '42'}}}}, 'hosts': {}}, 'workers': { 'children': {'bibigrid_worker_21_0_1': {'hosts': {'bibigrid-worker-21-[0:1]': 1}}, @@ -369,90 +369,268 @@ def test_configure_ansible_yaml(self, mock_cidrs, mock_yaml, mock_site, mock_hos call(aRP.SITE_CONFIG_FILE, mock_site(), startup.LOG, False)] self.assertEqual(expected, mock_yaml.call_args_list) - @patch('bibigrid.core.utility.ansible_configurator.write_yaml') - @patch('bibigrid.core.utility.ansible_configurator.aRP.GROUP_VARS_FOLDER', '/mocked/path/group_vars') - @patch('bibigrid.core.utility.ansible_configurator.aRP.HOST_VARS_FOLDER', '/mocked/path/host_vars') - def test_write_host_and_group_vars(self, mock_write_yaml): - mock_log = MagicMock() - - mock_provider = MagicMock() - mock_provider.get_flavor.return_value = {"name": "flavor-name", "ram": 4096, "vcpus": 2, "disk": 40, - "ephemeral": 0} - - # Define configurations and providers for the test - configurations = [{"features": ["feature1", "feature2"], "workerInstances": [ - {"type": "m1.small", "count": 2, "image": "worker-image", "onDemand": True, "partitions": ["partition1"], - "features": "worker-feature"}], "masterInstance": {"type": "m1.large", "image": "master-image"}, - "network": "private-network", "subnet_cidrs": ["10.0.0.0/24"], "floating_ip": "1.2.3.4", - "private_v4": "10.0.0.1", "cloud_identifier": "cloud-1", "volumes": ["volume1"], - "fallbackOnOtherImage": False, "wireguard_peer": "peer1"}, - {"vpnInstance": {"type": "vpn-type", "image": "vpn-image"}, "network": "private-network", - "subnet_cidrs": ["10.0.0.0/24"], "floating_ip": "1.2.3.4", "private_v4": "10.0.0.1", - "cloud_identifier": "cloud-1", "fallbackOnOtherImage": False, "wireguard_peer": "peer1"}] - - providers = [mock_provider, mock_provider] - cluster_id = "test-cluster" - - # Call the function under test - ansible_configurator.write_host_and_group_vars(configurations, providers, cluster_id, mock_log) - - expected_worker_dict = {"name": "bibigrid-worker-test-cluster-[0-1]", - "regexp": "bibigrid-worker-test-cluster-\\d+", "image": "worker-image", - "network": "private-network", - "flavor": {"name": "flavor-name", "ram": 4096, "vcpus": 2, "disk": 40, "ephemeral": 0}, - "gateway_ip": "10.0.0.1", "cloud_identifier": "cloud-1", "on_demand": True, - "state": "CLOUD", "partitions": ["partition1", "all", "cloud-1"], - "features": {"feature1", "feature2", "worker-feature"}} + @patch("bibigrid.core.utility.ansible_configurator.write_yaml") + @patch("bibigrid.core.utility.ansible_configurator.create.WORKER_IDENTIFIER") + def test_write_worker_host_vars(self, mock_worker_identifier, mock_write_yaml): + mock_worker_identifier.side_effect = lambda cluster_id, additional: f"worker-{cluster_id}-{additional}" + + cluster_id = "foo" + worker_count = 0 + log = MagicMock() + + worker = { + "count": 2, + "volumes": [ + {"name": "volume1", "exists": True}, + {"permanent": True, "name": "volume2"}, + {"tmp": True}, + ], + } + worker_dict = { + "on_demand": True, + } + + expected_calls = [ + call( + "/home/xaver/Documents/Repos/bibigrid/resources/playbook/host_vars/worker-foo-0.yaml", + { + "volumes": [ + {"name": "volume1", "exists": True}, + {"permanent": True, "name": "worker-foo-0-perm-1-volume2"}, + {"tmp": True, "name": "worker-foo-0-tmp-2"}, + ] + }, + log, + ), + call( + "/home/xaver/Documents/Repos/bibigrid/resources/playbook/host_vars/worker-foo-1.yaml", + { + "volumes": [ + {"name": "volume1", "exists": True}, + {"permanent": True, "name": "worker-foo-1-perm-1-volume2"}, + {"tmp": True, "name": "worker-foo-1-tmp-2"}, + ] + }, + log, + ), + ] + + # Call the function + ansible_configurator.write_worker_host_vars( + cluster_id=cluster_id, + worker=worker, + worker_dict=worker_dict, + worker_count=worker_count, + log=log, + ) + + # Validate WORKER_IDENTIFIER calls + mock_worker_identifier.assert_has_calls( + [call(cluster_id="foo", additional=0), call(cluster_id="foo", additional=1)], + any_order=False, + ) + + # Validate write_yaml calls + mock_write_yaml.assert_has_calls(expected_calls, any_order=False) + + @patch("bibigrid.core.utility.ansible_configurator.write_yaml") + def test_write_worker_host_vars_on_demand_false(self, mock_write_yaml): + """ + This tests that no host_vars is written if on_demand is false. + Currently, that would result in an empty host vars that is not needed. + @param mock_write_yaml: + @return: + """ + cluster_id = "foo" + worker_count = 0 + log = MagicMock() + + worker = {"count": 2, "volumes": [{"name": "volume1", "exists": True}]} + worker_dict = {"on_demand": False} + + # Call the function + ansible_configurator.write_worker_host_vars( + cluster_id=cluster_id, + worker=worker, + worker_dict=worker_dict, + worker_count=worker_count, + log=log, + ) + + # Assert no write_yaml calls were made + mock_write_yaml.assert_not_called() + + @patch("bibigrid.core.utility.ansible_configurator.write_worker_host_vars") + @patch("bibigrid.core.utility.ansible_configurator.write_yaml") + def test_write_worker_vars(self, mock_write_yaml, mock_write_worker_host_vars): + provider = MagicMock() + provider.create_flavor_dict.return_value = {"flavor_key": "flavor_value"} + + configuration = { + "network": "net1", + "private_v4": "10.1.1.1", + "cloud_identifier": "cloud1", + "features": ["feature1"], + } + + worker = { + "type": "worker-type", + "image": "worker-image", + "onDemand": True, + "bootVolume": {"size": 10}, + "features": ["feature1"], + "count": 2 + } + + cluster_id = "foo" + worker_count = 0 + log = MagicMock() + + expected_group_vars = { + "name": "bibigrid-worker-foo-[0-1]", + "regexp": "bibigrid-worker-foo-\\d+", + "image": "worker-image", + "network": "net1", + "flavor": {"flavor_key": "flavor_value"}, + "gateway_ip": "10.1.1.1", + "cloud_identifier": "cloud1", + "on_demand": True, + "state": "CLOUD", + "partitions": ["all", "cloud1"], + "boot_volume": {"size": 10}, + "features": {"feature1"}, + } + + ansible_configurator.write_worker_vars( + provider=provider, + configuration=configuration, + cluster_id=cluster_id, + worker=worker, + worker_count=worker_count, + log=log + ) + # Assert group_vars were written correctly mock_write_yaml.assert_any_call( - os.path.join('/mocked/path/group_vars', 'bibigrid_worker_test_cluster_0_1.yaml'), expected_worker_dict, - mock_log) - - # Assertions for masterInstance - expected_master_dict = {"name": "bibigrid-master-test-cluster", "image": "master-image", - "network": "private-network", "network_cidrs": ["10.0.0.0/24"], - "floating_ip": "1.2.3.4", - "flavor": {"name": "flavor-name", "ram": 4096, "vcpus": 2, "disk": 40, "ephemeral": 0}, - "private_v4": "10.0.0.1", "cloud_identifier": "cloud-1", "volumes": ["volume1"], - "fallback_on_other_image": False, "state": "UNKNOWN", "on_demand": False, - "partitions": ["all", "cloud-1"], "wireguard": {"ip": "10.0.0.1", "peer": "peer1"}} - mock_write_yaml.assert_any_call(os.path.join('/mocked/path/group_vars', 'master.yaml'), expected_master_dict, - mock_log) - - expected_vpn_dict = {"name": "bibigrid-vpngtw-test-cluster-0", "regexp": "bibigrid-worker-test-cluster-\\d+", - "image": "vpn-image", "network": "private-network", "network_cidrs": ["10.0.0.0/24"], - "floating_ip": "1.2.3.4", "private_v4": "10.0.0.1", - "flavor": {"name": "flavor-name", "ram": 4096, "vcpus": 2, "disk": 40, "ephemeral": 0}, - "wireguard_ip": "10.0.0.2", "cloud_identifier": "cloud-1", - "fallback_on_other_image": False, "on_demand": False, - "wireguard": {"ip": "10.0.0.2", "peer": "peer1"}} - mock_write_yaml.assert_any_call(os.path.join('/mocked/path/host_vars', 'bibigrid-vpngtw-test-cluster-0.yaml'), - expected_vpn_dict, mock_log) - - @patch('os.remove') - @patch('os.listdir') - @patch('os.path.isfile') - @patch('bibigrid.core.utility.ansible_configurator.aRP.GROUP_VARS_FOLDER', '/mocked/path/group_vars') - @patch('bibigrid.core.utility.ansible_configurator.aRP.HOST_VARS_FOLDER', '/mocked/path/host_vars') - @patch('logging.getLogger') - def test_delete_old_vars(self, mock_get_logger, mock_isfile, mock_listdir, mock_remove): - mock_log = MagicMock() - mock_get_logger.return_value = mock_log - mock_isfile.return_value = True - - mock_listdir.side_effect = [['file1.yaml', 'file2.yaml'], # Files in GROUP_VARS_FOLDER - ['file3.yaml', 'file4.yaml'] # Files in HOST_VARS_FOLDER - ] - - # Call the function under test - ansible_configurator.delete_old_vars(mock_log) - - # Assertions for file removal - mock_remove.assert_any_call('/mocked/path/group_vars/file1.yaml') - mock_remove.assert_any_call('/mocked/path/group_vars/file2.yaml') - mock_remove.assert_any_call('/mocked/path/host_vars/file3.yaml') - mock_remove.assert_any_call('/mocked/path/host_vars/file4.yaml') - - self.assertEqual(mock_remove.call_count, 4) + "/home/xaver/Documents/Repos/bibigrid/resources/playbook/group_vars/bibigrid_worker_foo_0_1.yaml", + expected_group_vars, + log + ) + + # Ensure write_worker_host_vars was called + mock_write_worker_host_vars.assert_called_once_with( + cluster_id=cluster_id, + worker=worker, + worker_dict=expected_group_vars, + worker_count=worker_count, + log=log + ) + + @patch("bibigrid.core.utility.ansible_configurator.write_yaml") + def test_write_vpn_var(self, mock_write_yaml): + provider = MagicMock() + provider.create_flavor_dict.return_value = {"flavor_key": "flavor_value"} + + configuration = { + "network": "net1", + "subnet_cidrs": ["10.0.0.0/16"], + "floating_ip": "10.1.1.2", + "private_v4": "10.1.1.1", + "cloud_identifier": "cloud1", + "wireguard_peer": "peer-ip", + } + + vpngtw = { + "type": "vpn-type", + "image": "vpn-image", + } + + cluster_id = "foo" + vpn_count = 0 + log = MagicMock() + + expected_host_vars = { + "name": "bibigrid-vpngtw-foo-0", + "regexp": "bibigrid-worker-foo-\\d+", # this is known bug behavior that needs to be fixed + "image": "vpn-image", + "network": "net1", + "network_cidrs": ["10.0.0.0/16"], + "floating_ip": "10.1.1.2", + "private_v4": "10.1.1.1", + "flavor": {"flavor_key": "flavor_value"}, + "wireguard_ip": "10.0.0.2", + "cloud_identifier": "cloud1", + "fallback_on_other_image": False, + "on_demand": False, + "wireguard": {"ip": "10.0.0.2", "peer": "peer-ip"}, + } + + ansible_configurator.write_vpn_var( + provider=provider, + configuration=configuration, + cluster_id=cluster_id, + vpngtw=vpngtw, + vpn_count=vpn_count, + log=log, + ) + + mock_write_yaml.assert_called_once_with( + "/home/xaver/Documents/Repos/bibigrid/resources/playbook/host_vars/bibigrid-vpngtw-foo-0.yaml", + expected_host_vars, + log + ) + + @patch("bibigrid.core.utility.ansible_configurator.write_yaml") + def test_write_master_var(self, mock_write_yaml): + provider = MagicMock() + provider.create_flavor_dict.return_value = {"flavor_key": "flavor_value"} + + configuration = { + "network": "net1", + "subnet_cidrs": ["10.0.0.0/24"], + "floating_ip": True, + "private_v4": "10.1.1.1", + "cloud_identifier": "cloud1", + "fallbackOnOtherImage": False, + "useMasterAsCompute": True, + "masterInstance": { + "type": "master-type", + "image": "master-image", + "partitions": ["control"], + }, + } + + cluster_id = "foo" + log = MagicMock() + + expected_master_vars = { + "name": "bibigrid-master-foo", + "image": "master-image", + "network": "net1", + "network_cidrs": ["10.0.0.0/24"], + "floating_ip": True, + "flavor": {"flavor_key": "flavor_value"}, + "private_v4": "10.1.1.1", + "cloud_identifier": "cloud1", + "fallback_on_other_image": False, + "state": "UNKNOWN", # Based on useMasterAsCompute = True + "on_demand": False, + "partitions": ["control", "all", "cloud1"], + } + + # Call the function + ansible_configurator.write_master_var( + provider=provider, + configuration=configuration, + cluster_id=cluster_id, + log=log, + ) + + # Validate the output + mock_write_yaml.assert_called_once_with( + "/home/xaver/Documents/Repos/bibigrid/resources/playbook/group_vars/master.yaml", + expected_master_vars, + log, + ) def test_key_present_with_key_to(self): dict_from = {'source_key': 'value1'} @@ -490,7 +668,7 @@ def test_add_wireguard_peers_multiple_configurations(self, mock_generate): mock_generate.return_value = ('private_key_example', 'public_key_example') configurations = [{"cloud_identifier": "cloud-1", "floating_ip": "10.0.0.1", "subnet_cidrs": ["10.0.0.0/24"]}, - {"cloud_identifier": "cloud-2", "floating_ip": "10.0.0.2", "subnet_cidrs": ["10.0.1.0/24"]}] + {"cloud_identifier": "cloud-2", "floating_ip": "10.0.0.2", "subnet_cidrs": ["10.0.1.0/24"]}] # Call the function ansible_configurator.add_wireguard_peers(configurations) diff --git a/tests/test_check.py b/tests/unit_tests/test_check.py similarity index 100% rename from tests/test_check.py rename to tests/unit_tests/test_check.py diff --git a/tests/test_configuration_handler.py b/tests/unit_tests/test_configuration_handler.py similarity index 84% rename from tests/test_configuration_handler.py rename to tests/unit_tests/test_configuration_handler.py index ceed2840..f8727ebb 100644 --- a/tests/test_configuration_handler.py +++ b/tests/unit_tests/test_configuration_handler.py @@ -4,7 +4,7 @@ import os from unittest import TestCase -from unittest.mock import patch, mock_open, MagicMock +from unittest.mock import patch, mock_open, MagicMock, ANY from bibigrid.core import startup from bibigrid.core.utility.handler import configuration_handler @@ -28,17 +28,25 @@ def test_get_list_by_name_empty(self): self.assertEqual(["value1", None], configuration_handler.get_list_by_key(configurations, "key2")) self.assertEqual(["value1"], configuration_handler.get_list_by_key(configurations, "key2", False)) - @patch("os.path.isfile") - def test_read_configuration_no_file(self, mock_isfile): - mock_isfile.return_value = False - test_open = MagicMock() - configuration = "Test: 42" - expected_result = [None] - with patch("builtins.open", mock_open(test_open, read_data=configuration)): - result = configuration_handler.read_configuration(startup.LOG, "path") - mock_isfile.assert_called_with("path") - test_open.assert_not_called() - self.assertEqual(expected_result, result) + def test_read_configuration_file_not_found(self): + """ + Assures that BiBiGrid exits without other errors when the configuration file is not found + @return: + """ + log_mock = MagicMock() + + with patch("os.path.isfile") as mock_isfile, self.assertRaises(SystemExit) as cm: + # Mock `os.path.isfile` to return False, simulating a missing file + mock_isfile.return_value = False + + # Call the function, expecting a SystemExit + configuration_handler.read_configuration(log_mock, "nonexistent_file.yaml") + + # Assert sys.exit(1) was called (exit code 1) + self.assertEqual(cm.exception.code, 1) + + # Verify the log message for the missing file + log_mock.warning.assert_called_with("No such configuration file %s.", "nonexistent_file.yaml") @patch("os.path.isfile") def test_read_configuration_file(self, mock_isfile): @@ -54,15 +62,33 @@ def test_read_configuration_file(self, mock_isfile): @patch("os.path.isfile") def test_read_configuration_file_yaml_exception(self, mock_isfile): + """ + Tests that BiBiGrid handles exceptions nicely and gives the user info + @param mock_isfile: + @return: + """ + # Mock `os.path.isfile` to return True, simulating the file exists mock_isfile.return_value = True - opener = MagicMock() - configuration = "]unbalanced brackets[" - expected_result = [None] - with patch("builtins.open", mock_open(opener, read_data=configuration)): - result = configuration_handler.read_configuration(startup.LOG, "path") + + # Create a mock for the file opener and provide invalid YAML data + mock_file = mock_open(read_data="]unbalanced brackets[") + log_mock = MagicMock() + + # Test for SystemExit when the YAML is invalid + with patch("builtins.open", mock_file), self.assertRaises(SystemExit) as cm: + configuration_handler.read_configuration(log_mock, "path") + + # Assert sys.exit(1) was called + self.assertEqual(cm.exception.code, 1) + + # Verify the log warning for YAML error + log_mock.warning.assert_called_with( + "Couldn't read configuration %s: %s", "path", ANY + ) + + # Check that `os.path.isfile` and `open` were called as expected mock_isfile.assert_called_with("path") - opener.assert_called_with("path", mode="r", encoding="UTF-8") - self.assertEqual(expected_result, result) + mock_file.assert_called_with("path", mode="r", encoding="UTF-8") def test_find_file_in_folders_not_found_no_folder(self): expected_result = None @@ -74,8 +100,9 @@ def test_find_file_in_folders_not_found_no_file(self): with patch("os.path.isfile") as mock_isfile: mock_isfile.return_value = False result = configuration_handler.find_file_in_folders("false_file", ["or_false_folder"], startup.LOG) + mock_isfile.assert_called_with(os.path.expanduser(os.path.join("or_false_folder", "false_file"))) self.assertEqual(expected_result, result) - mock_isfile.called_with(os.path.expanduser(os.path.join("or_false_folder", "false_file"))) + @patch("os.path.isfile") @patch("bibigrid.core.utility.handler.configuration_handler.read_configuration") diff --git a/tests/test_create.py b/tests/unit_tests/test_create.py similarity index 67% rename from tests/test_create.py rename to tests/unit_tests/test_create.py index 5eb60901..c65e7a0d 100644 --- a/tests/test_create.py +++ b/tests/unit_tests/test_create.py @@ -4,12 +4,11 @@ import os from unittest import TestCase from unittest.mock import patch, MagicMock, mock_open - from bibigrid.core import startup from bibigrid.core.actions import create from bibigrid.core.utility.handler import ssh_handler - +# pylint: disable=too-many-positional-arguments class TestCreate(TestCase): """ Class to test create @@ -26,7 +25,8 @@ def test_init(self, mock_id, mock_ssh): key_name = create.KEY_NAME.format(cluster_id=cluster_id) mock_id.return_value = cluster_id mock_ssh.return_value = [32] - creator = create.Create([provider], [{}], "path", startup.LOG, False) + creator = create.Create(providers=[provider], configurations=[{}], config_path="path", + log=startup.LOG, debug=False) self.assertEqual(cluster_id, creator.cluster_id) self.assertEqual("ubuntu", creator.ssh_user) self.assertEqual([32], creator.ssh_add_public_key_commands) @@ -42,7 +42,8 @@ def test_init_with_cluster_id(self, mock_id, mock_ssh): provider.__getitem__.side_effect = provider_dict.__getitem__ key_name = create.KEY_NAME.format(cluster_id=cluster_id) mock_ssh.return_value = [32] - creator = create.Create([provider], [{}], "path", startup.LOG, False, cluster_id) + creator = create.Create(providers=[provider], configurations=[{}], config_path="path", log=startup.LOG, + debug=False, cluster_id=cluster_id) self.assertEqual(cluster_id, creator.cluster_id) self.assertEqual("ubuntu", creator.ssh_user) self.assertEqual([32], creator.ssh_add_public_key_commands) @@ -55,14 +56,15 @@ def test_init_username(self, mock_id, mock_ssh): cluster_id = "21" mock_id.return_value = cluster_id mock_ssh.return_value = [32] - creator = create.Create([MagicMock()], [{"sshUser": "ssh"}], "path", startup.LOG, False) + creator = create.Create(providers=[MagicMock()], configurations=[{"sshUser": "ssh"}], config_path="path", + log=startup.LOG, debug=False) self.assertEqual("ssh", creator.ssh_user) @patch("subprocess.check_output") def test_generate_keypair(self, mock_subprocess): provider = MagicMock() provider.list_servers.return_value = [] - creator = create.Create([provider], [{}], "", startup.LOG) + creator = create.Create(providers=[provider], configurations=[{}], config_path="", log=startup.LOG) public_key = "data" with patch("builtins.open", mock_open(read_data=public_key)): creator.generate_keypair() @@ -78,12 +80,13 @@ def test_prepare_master_args(self): external_network = "externalTest" provider.get_external_netowrk.return_value = external_network configuration = {"network": 42, "masterInstance": "Some"} - creator = create.Create([provider], [configuration], "", startup.LOG) - volume_return = [42] - with patch.object(creator, "prepare_volumes", return_value=volume_return) as prepare_mock: - self.assertEqual((create.MASTER_IDENTIFIER, configuration["masterInstance"], volume_return), - creator.prepare_vpn_or_master_args(configuration, provider)) - prepare_mock.assert_called_with(provider, []) + creator = create.Create(providers=[provider], configurations=[configuration], config_path="", log=startup.LOG) + + # You would normally test the return value of `prepare_vpn_or_master_args` here + identifier, instance_type = creator.prepare_vpn_or_master_args(configuration) + + # Assuming expected values for master instance + self.assertEqual((create.MASTER_IDENTIFIER, "Some"), (identifier, instance_type)) def test_prepare_vpn_args(self): provider = MagicMock() @@ -91,26 +94,11 @@ def test_prepare_vpn_args(self): external_network = "externalTest" provider.get_external_netowrk.return_value = external_network configuration = {"network": 42, "vpnInstance": "Some"} - creator = create.Create([provider], [configuration], "", startup.LOG) - volume_return = [42] - with patch.object(creator, "prepare_volumes", return_value=volume_return) as prepare_mock: - self.assertEqual((create.VPN_WORKER_IDENTIFIER, configuration["vpnInstance"], []), - creator.prepare_vpn_or_master_args(configuration, provider)) - prepare_mock.assert_not_called() + creator = create.Create(providers=[provider], configurations=[configuration], config_path="", log=startup.LOG) - def test_prepare_args_keyerror(self): - provider = MagicMock() - provider.list_servers.return_value = [] - external_network = "externalTest" - provider.get_external_netowrk.return_value = external_network - configuration = {"network": 42} - creator = create.Create([provider], [configuration], "", startup.LOG) - volume_return = [42] - with patch.object(creator, "prepare_volumes", return_value=volume_return) as prepare_mock: - with self.assertRaises(KeyError): - self.assertEqual((create.VPN_WORKER_IDENTIFIER, configuration["vpnInstance"], []), - creator.prepare_vpn_or_master_args(configuration, provider)) - prepare_mock.assert_not_called() + # Test for VPN args preparation + identifier, instance_type = creator.prepare_vpn_or_master_args(configuration) + self.assertEqual((create.VPN_WORKER_IDENTIFIER, "Some"), (identifier, instance_type)) @patch("bibigrid.core.utility.handler.ssh_handler.execute_ssh") def test_initialize_master(self, mock_execute_ssh): @@ -118,7 +106,7 @@ def test_initialize_master(self, mock_execute_ssh): provider.list_servers.return_value = [] floating_ip = 21 configuration = {"masterInstance": 42, "floating_ip": floating_ip} - creator = create.Create([provider], [configuration], "", startup.LOG) + creator = create.Create(providers=[provider], configurations=[configuration], config_path="", log=startup.LOG) creator.initialize_instances() ssh_data = {'floating_ip': floating_ip, 'private_key': create.KEY_FOLDER + creator.key_name, 'username': creator.ssh_user, @@ -126,50 +114,13 @@ def test_initialize_master(self, mock_execute_ssh): 'filepaths': [(create.KEY_FOLDER + creator.key_name, '.ssh/id_ecdsa')], 'gateway': {}, 'timeout': 5} mock_execute_ssh.assert_called_with(ssh_data, startup.LOG) - def test_prepare_volumes_none(self): - provider = MagicMock() - provider.list_servers.return_value = [] - provider.get_volume_by_id_or_name.return_value = 42 - provider.create_volume_from_snapshot = 21 - configuration = {"vpnInstance": 42} - creator = create.Create([provider], [configuration], "", startup.LOG) - self.assertEqual(set(), creator.prepare_volumes(provider, [])) - - def test_prepare_volumes_volume(self): - provider = MagicMock() - provider.list_servers.return_value = [] - provider.get_volume_by_id_or_name.return_value = {"id": 42} - provider.create_volume_from_snapshot = 21 - configuration = {"vpnInstance": 42} - creator = create.Create([provider], [configuration], "", startup.LOG) - self.assertEqual({42}, creator.prepare_volumes(provider, ["Test"])) - - def test_prepare_volumes_snapshot(self): - provider = MagicMock() - provider.list_servers.return_value = [] - provider.get_volume_by_id_or_name.return_value = {"id": None} - provider.create_volume_from_snapshot.return_value = 21 - configuration = {"vpnInstance": 42} - creator = create.Create([provider], [configuration], "", startup.LOG) - self.assertEqual({21}, creator.prepare_volumes(provider, ["Test"])) - - def test_prepare_volumes_mismatch(self): - provider = MagicMock() - provider.list_servers.return_value = [] - provider.get_volume_by_id_or_name.return_value = {"id": None} - provider.create_volume_from_snapshot.return_value = None - configuration = {"vpnInstance": 42} - creator = create.Create([provider], [configuration], "", startup.LOG) - mount = "Test" - self.assertEqual(set(), creator.prepare_volumes(provider, [mount])) - def test_prepare_configurations_no_network(self): provider = MagicMock() provider.list_servers.return_value = [] network = "network" provider.get_network_id_by_subnet.return_value = network configuration = {"subnet": 42} - creator = create.Create([provider], [configuration], "", startup.LOG) + creator = create.Create(providers=[provider], configurations=[configuration], config_path="", log=startup.LOG) creator.prepare_configurations() provider.get_network_id_by_subnet.assert_called_with(42) self.assertEqual(network, configuration["network"]) @@ -181,7 +132,7 @@ def test_prepare_configurations_no_subnet(self): subnet = ["subnet"] provider.get_subnet_ids_by_network.return_value = subnet configuration = {"network": 42} - creator = create.Create([provider], [configuration], "", startup.LOG) + creator = create.Create(providers=[provider], configurations=[configuration], config_path="", log=startup.LOG) creator.prepare_configurations() provider.get_subnet_ids_by_network.assert_called_with(42) self.assertEqual(subnet, configuration["subnet"]) @@ -191,7 +142,7 @@ def test_prepare_configurations_none(self): provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - creator = create.Create([provider], [configuration], "", startup.LOG) + creator = create.Create(providers=[provider], configurations=[configuration], config_path="", log=startup.LOG) with self.assertRaises(KeyError): creator.prepare_configurations() @@ -202,7 +153,7 @@ def test_upload_playbooks(self, mock_execute_ssh, mock_ac_ssh, mock_configure_an provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - creator = create.Create([provider], [configuration], "", startup.LOG) + creator = create.Create(providers=[provider], configurations=[configuration], config_path="", log=startup.LOG) creator.master_ip = 42 creator.upload_data(os.path.join(create.KEY_FOLDER, creator.key_name)) mock_configure_ansible.assert_called_with(providers=creator.providers, configurations=creator.configurations, @@ -222,7 +173,8 @@ def test_create_non_debug(self, mock_terminate, mock_info, mock_up, mock_start, provider = MagicMock() provider.list_servers.return_value = [] configuration = {"floating_ip": 42} - creator = create.Create([provider], [configuration], "", startup.LOG, False) + creator = create.Create(providers=[provider], configurations=[configuration], config_path="", log=startup.LOG, + debug=False) self.assertEqual(0, creator.create()) for mock in [mock_info, mock_up, mock_start, mock_conf, mock_key]: mock.assert_called() @@ -237,7 +189,8 @@ def test_create_non_debug_upload_raise(self, mock_terminate, mock_info, mock_sta provider = MagicMock() provider.list_servers.return_value = [] configuration = {} - creator = create.Create([provider], [configuration], "", startup.LOG, False) + creator = create.Create(providers=[provider], configurations=[configuration], config_path="", log=startup.LOG, + debug=False) self.assertEqual(1, creator.create()) for mock in [mock_start, mock_conf, mock_key]: mock.assert_called() @@ -256,7 +209,8 @@ def test_create_debug(self, mock_terminate, mock_info, mock_up, mock_start, mock provider = MagicMock() provider.list_servers.return_value = [] configuration = {"floating_ip": 42} - creator = create.Create([provider], [configuration], "", startup.LOG, True) + creator = create.Create(providers=[provider], configurations=[configuration], config_path="", log=startup.LOG, + debug=True) self.assertEqual(0, creator.create()) for mock in [mock_info, mock_up, mock_start, mock_conf, mock_key]: mock.assert_called() diff --git a/tests/test_id_generation.py b/tests/unit_tests/test_id_generation.py similarity index 100% rename from tests/test_id_generation.py rename to tests/unit_tests/test_id_generation.py diff --git a/tests/test_list_clusters.py b/tests/unit_tests/test_list_clusters.py similarity index 100% rename from tests/test_list_clusters.py rename to tests/unit_tests/test_list_clusters.py diff --git a/tests/test_provider_handler.py b/tests/unit_tests/test_provider_handler.py similarity index 100% rename from tests/test_provider_handler.py rename to tests/unit_tests/test_provider_handler.py diff --git a/tests/test_return_threading.py b/tests/unit_tests/test_return_threading.py similarity index 100% rename from tests/test_return_threading.py rename to tests/unit_tests/test_return_threading.py diff --git a/tests/test_ssh_handler.py b/tests/unit_tests/test_ssh_handler.py similarity index 100% rename from tests/test_ssh_handler.py rename to tests/unit_tests/test_ssh_handler.py diff --git a/tests/test_startup.py b/tests/unit_tests/test_startup.py similarity index 100% rename from tests/test_startup.py rename to tests/unit_tests/test_startup.py diff --git a/tests/unit_tests/test_terminate_cluster.py b/tests/unit_tests/test_terminate_cluster.py new file mode 100644 index 00000000..581e6367 --- /dev/null +++ b/tests/unit_tests/test_terminate_cluster.py @@ -0,0 +1,151 @@ +""" +Module to test terminate +""" +from unittest import TestCase +from unittest.mock import MagicMock, patch, call + +from bibigrid.core import startup +from bibigrid.core.actions import create +from bibigrid.core.actions import terminate + + +class TestTerminate(TestCase): + """ + Class to test terminate. + """ + + @patch("bibigrid.core.actions.terminate.delete_local_keypairs") + @patch("bibigrid.core.actions.terminate.terminate_output") + def test_terminate(self, mock_output, mock_local): + mock_local.return_value = True + provider = MagicMock() + provider.cloud_specification["auth"]["project_name"] = 32 + cluster_id = 42 + provider.list_servers.return_value = [{"name": create.MASTER_IDENTIFIER(cluster_id=str(cluster_id)), "id": 21}] + provider.delete_server.return_value = True + provider.delete_keypair.return_value = True + provider.delete_volume.return_value = True + provider.list_volumes.return_value = [ + {"name": f"{create.MASTER_IDENTIFIER(cluster_id=str(cluster_id))}-tmp-0", "id": 42}] + provider.list_volumes([{"name": "bibigrid-master-i950vaoqzfbwpnq-tmp-0"}]) + provider.delete_security_group.return_value = True + provider.delete_application_credentials.return_value = True + terminate.terminate(str(cluster_id), [provider], startup.LOG, False, True) + provider.delete_server.assert_called_with(21) + provider.delete_keypair.assert_called_with(create.KEY_NAME.format(cluster_id=cluster_id)) + mock_output.assert_called_with(cluster_server_state=[provider.delete_server.return_value], + cluster_keypair_state=[provider.delete_keypair.return_value], + cluster_security_group_state=[provider.delete_security_group.return_value], + cluster_volume_state=[[True]], + ac_state=provider.delete_application_credentials.return_value, + cluster_id=str(cluster_id), + log=startup.LOG) + + @patch("bibigrid.core.actions.terminate.delete_local_keypairs") + @patch("logging.info") + def test_terminate_none(self, _, mock_local): + mock_local.return_value = True + provider = MagicMock() + provider[0].specification["auth"]["project_name"] = "test_project_name" + cluster_id = 42 + provider.list_servers.return_value = [ + {"name": create.MASTER_IDENTIFIER(cluster_id=str(cluster_id + 1)), "id": 21}] + provider.delete_keypair.return_value = False + terminate.terminate(str(cluster_id), [provider], startup.LOG, False, True) + provider.delete_server.assert_not_called() + provider.delete_keypair.assert_called_with( + create.KEY_NAME.format(cluster_id=str(cluster_id))) # since keypair is not called + + def test_delete_non_pemanent_volumes(self): + cluster_id = "1234" + provider = MagicMock() + log = MagicMock() + cluster_id=21 + + # List of test volumes + volumes = [ + # Should be captured by the regex + {"name": f"bibigrid-master-{cluster_id}-tmp-0"}, + {"name": f"bibigrid-master-{cluster_id}-semiperm-0"}, + {"name": f"bibigrid-master-{cluster_id}-tmp-0-na<-0med"}, + {"name": f"bibigrid-master-{cluster_id}-semiperm-0-na<-0med"}, + {"name": f"bibigrid-worker-{cluster_id}-0-tmp-0"}, + {"name": f"bibigrid-worker-{cluster_id}-11-semiperm-0"}, + {"name": f"bibigrid-worker-{cluster_id}-0-tmp-0-na<-0med"}, + {"name": f"bibigrid-worker-{cluster_id}-11-semiperm-0-na<-0med"}, + + # Should NOT be captured by the regex + {"name": f"bibigrid-master-{cluster_id}-perm-0"}, + {"name": f"bibigrid-master-{cluster_id}-perm-11-na<-0med"}, + {"name": f"bibigrid-worker-{cluster_id}-112-perm-0"}, + {"name": f"bibigrid-worker-{cluster_id}-112-perm-11-na<-0med"}, + {"name": "somevolume"}, + {"name": "bibigrid-master-4242-0-tmp-0"}, + {"name": "bibigrid-master-4242-0-semiperm-0"}, + {"name": "bibigrid-master-4242-0-perm-0"}, + {"name": "bibigrid-worker-4242-0-tmp-0"}, + {"name": "bibigrid-worker-4242-0-semiperm-0"}, + {"name": "bibigrid-worker-4242-0-perm-0"}, + {"name": f"master-{cluster_id}-0-tmp-0"}, + {"name": f"master-{cluster_id}-0-semiperm-0"}, + {"name": f"master-{cluster_id}-0-perm-0"}, + ] + + provider.list_volumes.return_value = volumes + + # Call the method under test + _ = terminate.delete_non_permanent_volumes(provider, cluster_id, log) + + # Expected captured volumes + expected_calls = [call({'name': 'bibigrid-master-21-tmp-0'}), + call({'name': 'bibigrid-master-21-semiperm-0'}), + call({'name': 'bibigrid-master-21-tmp-0-na<-0med'}), + call({'name': 'bibigrid-master-21-semiperm-0-na<-0med'}), + call({'name': 'bibigrid-worker-21-0-tmp-0'}), + call({'name': 'bibigrid-worker-21-11-semiperm-0'}), + call({'name': 'bibigrid-worker-21-0-tmp-0-na<-0med'}), + call({'name': 'bibigrid-worker-21-11-semiperm-0-na<-0med'})] + + # Assert that the regex only captured the expected volumes + self.assertEqual(expected_calls, provider.delete_volume.call_args_list) + + def test_terminate_servers(self): + cluster_id = "21" + provider = MagicMock() + log = MagicMock() + + # List of test servers + servers = [ + # Should be captured by the regex + {"name": f"bibigrid-master-{cluster_id}", "id": 42}, + {"name": f"bibigrid-worker-{cluster_id}-0", "id": 42}, + {"name": f"bibigrid-worker-{cluster_id}-11", "id": 42}, + {"name": f"bibigrid-vpngtw-{cluster_id}-222", "id": 42}, + + # Should NOT be captured by the regex + {"name": "some-other-server", "id": 42}, + {"name": "bibigrid-master-4242", "id": 42}, + {"name": "bibigrid-worker-4242-0", "id": 42}, + {"name": "bibigrid-vpngtw-4242-0", "id": 42}, + ] + + provider.list_servers.return_value = servers + + # Patch terminate_server from bibigrid.core.actions.terminate + with patch("bibigrid.core.actions.terminate.terminate_server") as mock_terminate_server: + # Call the method under test + _ = terminate.terminate_servers(cluster_id, provider, log) + + # Expected captured servers + expected_calls = [ + call(provider, {"name": f"bibigrid-master-{cluster_id}", "id": 42}, log), + call(provider, {"name": f"bibigrid-worker-{cluster_id}-0", "id": 42}, log), + call(provider, {"name": f"bibigrid-worker-{cluster_id}-11", "id": 42}, log), + call(provider, {"name": f"bibigrid-vpngtw-{cluster_id}-222", "id": 42}, log), + ] + + # Assert that terminate_server was called only for the expected servers + mock_terminate_server.assert_has_calls(expected_calls, any_order=False) + + # Assert that the total number of calls matches the expected calls + self.assertEqual(mock_terminate_server.call_count, len(expected_calls)) diff --git a/tests/test_validate_configuration.py b/tests/unit_tests/test_validate_configuration.py similarity index 90% rename from tests/test_validate_configuration.py rename to tests/unit_tests/test_validate_configuration.py index 27994fe2..4c3d8488 100644 --- a/tests/test_validate_configuration.py +++ b/tests/unit_tests/test_validate_configuration.py @@ -199,29 +199,16 @@ def test_check_instance_type_image_combination_count(self): self.assertEqual(10 * i, v_c.required_resources_dict["1"]["total_cores"]) mock.assert_called_with(32 * i, 12, 'Type de.NBI tiny', 'ram', log) - def test_check_volumes_none(self): + def test_check_no_volumes(self): + """ + Check to see that no error occurs when no volume is given + @return + """ provider1 = MagicMock() provider1.cloud_specification = {"identifier": "1"} - v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{}], log=Mock()) - self.assertTrue(v_c.check_volumes()) - - def test_check_volumes_mismatch(self): - provider1 = Mock() - provider1.get_volume_by_id_or_name = MagicMock(return_value=None) - provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) - provider1.cloud_specification = {"identifier": "1"} - v_c = validate_configuration.ValidateConfiguration(providers=[provider1], - configurations=[{"masterMounts": [{"name": "Test"}]}], - log=Mock()) - self.assertFalse(v_c.check_volumes()) - - def test_check_volumes_match_snapshot(self): - provider1 = Mock() - provider1.get_volume_by_id_or_name = MagicMock(return_value=None) - provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": 1}) - provider1.cloud_specification = {"identifier": "1"} - v_c = validate_configuration.ValidateConfiguration(providers=[provider1], - configurations=[{"masterMounts": [{"name": "Test"}]}], + v_c = validate_configuration.ValidateConfiguration(providers=[provider1], configurations=[{"masterInstance": {}, + "workerInstances": [ + {}]}], log=Mock()) self.assertTrue(v_c.check_volumes()) @@ -232,23 +219,11 @@ def test_check_volumes_match_snapshot_count(self): provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value={"size": i}) provider1.cloud_specification = {"identifier": i} v_c = validate_configuration.ValidateConfiguration(providers=[provider1] * i, configurations=[ - {"masterMounts": [{"name": "Test"}] * i}], log=Mock()) + {"masterInstance": {"volumes": [{"snapshot": "test"}] * i}}], log=Mock()) self.assertTrue(v_c.check_volumes()) self.assertTrue(v_c.required_resources_dict[i]["volumes"] == i) self.assertTrue(v_c.required_resources_dict[i]["volume_gigabytes"] == i ** 2) - def test_check_volumes_match_volume(self): - provider1 = Mock() - provider1.get_volume_by_id_or_name = MagicMock(return_value={"size": 1}) - provider1.get_volume_snapshot_by_id_or_name = MagicMock(return_value=None) - provider1.cloud_specification = {"identifier": "1"} - v_c = validate_configuration.ValidateConfiguration(providers=[provider1], - configurations=[{"masterMounts": [{"name": "Test"}]}], - log=Mock()) - self.assertTrue(v_c.check_volumes()) - self.assertTrue(v_c.required_resources_dict["1"]["volumes"] == 0) - self.assertTrue(v_c.required_resources_dict["1"]["volume_gigabytes"] == 0) - def test_check_network_none(self): provider1 = Mock() provider1.get_network_by_id_or_name = MagicMock(return_value=None) diff --git a/tests/test_validate_schema.py b/tests/unit_tests/test_validate_schema.py similarity index 100% rename from tests/test_validate_schema.py rename to tests/unit_tests/test_validate_schema.py

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�<< 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