From f0c199e34d9cba64b242a80b6ae40f93ff5dbae0 Mon Sep 17 00:00:00 2001 From: Dhvani Sheth Date: Fri, 5 Jan 2024 13:22:12 -0800 Subject: [PATCH 01/40] adding h100 troubleshooting script --- scripts/h100_script.py | 173 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 scripts/h100_script.py diff --git a/scripts/h100_script.py b/scripts/h100_script.py new file mode 100644 index 00000000..e34fe13a --- /dev/null +++ b/scripts/h100_script.py @@ -0,0 +1,173 @@ +import os +from datetime import datetime +import argparse +import subprocess +import sys +import shlex + + +def getDateTime(): + # datetime object containing current date and time + now = datetime.now() + dt_string = now.strftime("%m%d%Y%H%M%S") + return dt_string + + +# create directory to hold results +def createDir(): + # directory name + directory = str("/tmp/" + getDateTime()) + try: + os.mkdir(directory) + except OSError as error: + print(error) + sys.exit(-1) + return directory + + +# change ownership of all files to user so that the files can be copied +def changeOwner(path): + username = os.getlogin() + cmd = f'sudo chown -R {username}:{username} {path}' + run_cmd(cmd) + + +def getSshableNodes(hosts, path): + hosts_file = open(hosts, "r") + ssh_list = path + "/" + "sshable" + not_ssh_list = path + "/" + "notsshable" + sshable = open(ssh_list, "a") + notsshable = open(not_ssh_list, "a") + for line in hosts_file: + host = line.split() + host_value = host[0] + cmd = f'ssh -o ConnectTimeout=10 {host_value} "cat /etc/os-release | grep PRETTY_NAME"' + isSshable = run_cmd(cmd) + if 'PRETTY_NAME' in isSshable[0]: + sshable.write(host_value) + else: + notsshable.write(host_value) + sshable.close() + notsshable.close() + hosts_file.close() + return ssh_list + + +def run_cmd(cmd=None): + """ Run command on shell""" + try: + results = subprocess.run(cmd, shell=True, executable='/bin/bash', stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, encoding='utf8') + output = results.stdout.splitlines() + except subprocess.CalledProcessError as e: + print (f'Command {e.cmd} failed with error {e.returncode}') + return e.returncode + return output + + +def run_cmd_split(cmd=None): + """ Run command on shell""" + cmd_split = shlex.split(cmd) + try: + results = subprocess.run(cmd_split, shell=False, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, check=True, encoding='utf8') + output = results.stdout.splitlines() + except subprocess.CalledProcessError as e_process_error: + return (9000, f"Error code: {e_process_error.returncode} Output: {e_process_error.output}") + return output + + +# get interfaces that are Down +def ibdev(hosts, path): + log_file = path + "/" + "ibdev2netdev" + cmd = f'for i in $(cat {hosts}); do ssh $i "hostname; hostname -i; sudo dmidecode -s system-serial-number; ibdev2netdev | grep Down"; done > {log_file}' + run_cmd(cmd) + + +# get EAP-FAILURE +def eapFailure(hosts, path): + log_file = path + "/" + "eapfailure" + cmd = f'for i in $(cat {hosts}); do ssh $i "hostname; hostname -i; sudo dmidecode -s system-serial-number; cat /var/log/syslog | grep "EAP-FAILURE""; done > {log_file}' + run_cmd(cmd) + + +# get rdma links authentication +def rdmaAuth(hosts, path): + log_file = path + "/" + "rdmaauth" + hosts_file = open(hosts, "r") + log_file = path + "/" + "rdmaauth" + rdma_file = open(log_file, "a") + for line in hosts_file: + host = line.split() + host_value = host[0] + cmd = f'ssh {host_value} "hostname; hostname -i; sudo dmidecode -s system-serial-number"' + output = run_cmd(cmd) + for o in output: + rdma_file.write(o) + rdma_file.write("\n") + cmd = f'ssh {host_value} \'for x in $(seq 0 15) ; do sudo wpa_cli -i rdma$x status | grep EAP ; done\'' + output = run_cmd(cmd) + for o in output: + rdma_file.write(o) + rdma_file.write("\n") + rdma_file.close() + hosts_file.close() + + +# get logs for Link Flapping +def linksDown(hosts, path): + log_file = path + "/" + "linkflapping" + cmd = f'for i in $(cat {hosts}); do ssh $i "hostname; hostname -i; sudo dmidecode -s system-serial-number; cat /var/log/syslog | grep "Link " | tail -36"; done > {log_file}' + run_cmd(cmd) + + +# Check any GPU fallen off the bus +def lspci(hosts, path): + log_file = path + "/" + "lspci" + cmd = f'for i in $(cat {hosts}); do ssh $i "hostname; hostname -i; sudo dmidecode -s system-serial-number; lspci | grep "rev ff""; done > {log_file}' + run_cmd(cmd) + + +# Check for NVRM errors +def nvrm(hosts, path): + log_file = path + "/" + "nvrm" + cmd = f'for i in $(cat {hosts}); do ssh $i "hostname; hostname -i; sudo dmidecode -s system-serial-number; sudo dmesg | grep NVRM"; done > {log_file}' + run_cmd(cmd) + + +# Check for Pending remaps +def pending(hosts, path): + log_file = path + "/" + "pending_remaps" + cmd = f'for i in $(cat {hosts}); do ssh $i "hostname; hostname -i; sudo dmidecode -s system-serial-number; nvidia-smi -q | grep "Pending : Yes""; done > {log_file}' + run_cmd(cmd) + + +# Check for Remapping failures +def remapping(hosts, path): + log_file = path + "/" + "remapping_failures" + cmd = f'for i in $(cat {hosts}); do ssh $i "hostname; hostname -i; sudo dmidecode -s system-serial-number; nvidia-smi -q | grep "Remapping Failure Occurred : Yes""; done > {log_file}' + run_cmd(cmd) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description = 'Capture H100 troubleshooting data.') + parser.add_argument('--hosts', help = "Provide a filepath that contains list of either IPs / hostnames one per line on which you want to run this script.", required = True) + args = parser.parse_args() + hosts = args.hosts + if hosts is None: + print("Hostfile is required. Please provide one and run again.") + sys.exit(-1) + else: + path = createDir() + changeOwner(path) + ssh_hosts = getSshableNodes(hosts, path) + ibdev(ssh_hosts, path) + eapFailure(ssh_hosts, path) + rdmaAuth(ssh_hosts, path) + linksDown(ssh_hosts, path) + lspci(ssh_hosts, path) + nvrm(ssh_hosts, path) + pending(ssh_hosts, path) + remapping(ssh_hosts, path) + print("The results are at location: " + path) + From 1155dfbfa4a7385b95a54147b1c744a6cbeab4e2 Mon Sep 17 00:00:00 2001 From: Dhvani Sheth Date: Fri, 5 Jan 2024 15:04:19 -0800 Subject: [PATCH 02/40] removed a function not being used --- scripts/h100_script.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/scripts/h100_script.py b/scripts/h100_script.py index e34fe13a..15296472 100644 --- a/scripts/h100_script.py +++ b/scripts/h100_script.py @@ -3,7 +3,6 @@ import argparse import subprocess import sys -import shlex def getDateTime(): @@ -63,18 +62,6 @@ def run_cmd(cmd=None): print (f'Command {e.cmd} failed with error {e.returncode}') return e.returncode return output - - -def run_cmd_split(cmd=None): - """ Run command on shell""" - cmd_split = shlex.split(cmd) - try: - results = subprocess.run(cmd_split, shell=False, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, check=True, encoding='utf8') - output = results.stdout.splitlines() - except subprocess.CalledProcessError as e_process_error: - return (9000, f"Error code: {e_process_error.returncode} Output: {e_process_error.output}") - return output # get interfaces that are Down From 60e699f70fac2cf9d8c812c8a56522437a519f31 Mon Sep 17 00:00:00 2001 From: Dhvani Sheth Date: Mon, 8 Jan 2024 10:47:18 -0800 Subject: [PATCH 03/40] updated to have sshable and not sshable nodes one on each line --- scripts/h100_script.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/h100_script.py b/scripts/h100_script.py index 15296472..dca8a98e 100644 --- a/scripts/h100_script.py +++ b/scripts/h100_script.py @@ -44,8 +44,10 @@ def getSshableNodes(hosts, path): isSshable = run_cmd(cmd) if 'PRETTY_NAME' in isSshable[0]: sshable.write(host_value) + sshable.write("\n") else: notsshable.write(host_value) + notsshable.write("\n") sshable.close() notsshable.close() hosts_file.close() From eb756281a3457c2cc608d46d3b571c2a20acfcc1 Mon Sep 17 00:00:00 2001 From: Dhvani Sheth Date: Mon, 8 Jan 2024 11:06:42 -0800 Subject: [PATCH 04/40] updated logic for identifying not sshable nodes --- scripts/h100_script.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/h100_script.py b/scripts/h100_script.py index dca8a98e..afa0258d 100644 --- a/scripts/h100_script.py +++ b/scripts/h100_script.py @@ -42,7 +42,10 @@ def getSshableNodes(hosts, path): host_value = host[0] cmd = f'ssh -o ConnectTimeout=10 {host_value} "cat /etc/os-release | grep PRETTY_NAME"' isSshable = run_cmd(cmd) - if 'PRETTY_NAME' in isSshable[0]: + if not isSshable: + notsshable.write(host_value) + notsshable.write("\n") + elif 'PRETTY_NAME' in isSshable[0]: sshable.write(host_value) sshable.write("\n") else: From aa9243e5f613b53548e6ca844550a2541c3536d5 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 10 Jan 2024 14:17:41 -0800 Subject: [PATCH 05/40] Change bastion to controller --- README.md | 36 ++-- .../tf_init/cluster-network-configuration.tf | 2 +- autoscaling/tf_init/compute-nodes.tf | 2 +- .../{config.bastion => config.controller} | 0 ...bastion_update.tf => controller_update.tf} | 20 +- autoscaling/tf_init/data.tf | 2 +- .../tf_init/instance-pool-configuration.tf | 2 +- autoscaling/tf_init/inventory.tpl | 14 +- autoscaling/tf_init/locals.tf | 6 +- bin/configure.sh | 2 +- bin/configure_as.sh | 2 +- bin/{bastion.sh => controller.sh} | 2 +- bin/initial_monitoring.sh | 4 +- bin/resize.py | 16 +- bin/resize.sh | 4 +- cluster-network.tf | 2 +- compute-nodes.tf | 2 +- conf/variables.tpl | 14 +- config.bastion => config.controller | 0 bastion.tf => controller.tf | 167 ++++++++-------- data.tf | 4 +- inventory.tpl | 12 +- locals.tf | 20 +- login.tf | 6 +- marketplace.tf | 34 ++-- mysql.tf | 2 +- outputs.tf | 2 +- playbooks/destroy.yml | 2 +- playbooks/new_nodes.yml | 6 +- playbooks/resize_add.yml | 6 +- playbooks/resize_remove.yml | 2 +- playbooks/resize_remove_unreachable.yml | 2 +- playbooks/roles/cluster-cli/files/cluster | 2 +- .../tasks/slurm-rack-aware.yml | 2 +- .../roles/destroy_unreachable/tasks/slurm.yml | 2 +- playbooks/roles/etc-hosts/tasks/common.yml | 18 +- ...sts-bastion.j2 => etc-hosts-controller.j2} | 4 +- playbooks/roles/influxdb/tasks/el.yml | 4 +- playbooks/roles/influxdb/tasks/ubuntu.yml | 4 +- playbooks/roles/packages/tasks/el-7.yml | 2 +- playbooks/roles/packages/tasks/ol-7.yml | 2 +- playbooks/roles/packages/tasks/ol-8.yml | 2 +- playbooks/roles/slurm/tasks/backup_server.yml | 2 +- playbooks/roles/slurm/tasks/common_pyxis.yml | 2 +- .../roles/slurm/tasks/compute-rack-aware.yml | 16 +- playbooks/roles/slurm/tasks/compute.yml | 10 +- .../tasks/{bastion.yml => controller.yml} | 8 +- .../roles/slurm/tasks/destroy-rack-aware.yml | 4 +- playbooks/roles/slurm/tasks/destroy.yml | 2 +- playbooks/roles/slurm/tasks/main.yml | 2 +- playbooks/roles/slurm/tasks/ubuntu.yml | 2 +- playbooks/roles/slurm/templates/slurm.conf.j2 | 6 +- .../slurm/templates/systemd/slurmd.service | 2 +- .../systemd/slurmd.service.d/unit.conf.j2 | 2 +- playbooks/roles/ssh/tasks/common.yml | 4 +- playbooks/roles/ssl/defaults/main.yml | 2 +- playbooks/roles/ssl/tasks/debian.yml | 2 +- playbooks/roles/ssl/tasks/el.yml | 2 +- playbooks/roles/sssd/templates/sssd.conf.j2 | 2 +- .../roles/sssd/templates/sssd_ubuntu.conf.j2 | 2 +- playbooks/roles/telegraf/tasks/common.yml | 4 +- .../roles/telegraf/templates/influxdb.conf.j2 | 2 +- playbooks/site.yml | 28 +-- playbooks/slurm_config.yml | 2 +- samples/NCCL_readme | 2 +- samples/nfs/README.txt | 2 +- samples/open-ldap/add-ldap-users.yml | 2 +- schema.yaml | 181 +++++++++--------- scripts/ib_write_bw.sh | 2 +- scripts/max_nodes_partition.py | 2 +- scripts/validation.py | 16 +- slurm_ha.tf | 92 ++++----- user_data.tf | 4 +- variables.tf | 44 ++--- 74 files changed, 447 insertions(+), 447 deletions(-) rename autoscaling/tf_init/{config.bastion => config.controller} (100%) rename autoscaling/tf_init/{bastion_update.tf => controller_update.tf} (83%) rename bin/{bastion.sh => controller.sh} (99%) rename config.bastion => config.controller (100%) rename bastion.tf => controller.tf (75%) rename playbooks/roles/etc-hosts/templates/{etc-hosts-bastion.j2 => etc-hosts-controller.j2} (87%) rename playbooks/roles/slurm/tasks/{bastion.yml => controller.yml} (74%) diff --git a/README.md b/README.md index 15cf819a..6320d108 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ or: ## Supported OS: The stack allowa various combination of OS. Here is a list of what has been tested. We can't guarantee any of the other combination. -| Bastion | Compute | +| Controller | Compute | |---------------|--------------| | OL7 | OL7 | | OL7 | OL8 | @@ -41,7 +41,7 @@ The stack allowa various combination of OS. Here is a list of what has been test | OL8 | OL7 | | Ubuntu 20.04 | Ubuntu 20.04 | -When switching to Ubuntu, make sure the username is changed from opc to Ubuntu in the ORM for both the bastion and compute nodes. +When switching to Ubuntu, make sure the username is changed from opc to Ubuntu in the ORM for both the controller and compute nodes. ## How is resizing different from autoscaling ? Autoscaling is the idea of launching new clusters for jobs in the queue. Resizing a cluster is changing the size of a cluster. In some case growing your cluster may be a better idea, be aware that this may lead to capacity errors. Because Oracle CLoud RDMA is non virtualized, you get much better performance but it also means that we had to build HPC islands and split our capacity across different network blocks. @@ -62,7 +62,7 @@ Resizing of HPC cluster with Cluster Network consist of 2 major sub-steps: ## resize.sh usage -The resize.sh is deployed on the bastion node as part of the HPC cluster Stack deployment. Unreachable nodes have been causing issues. If nodes in the inventory are unreachable, we will not do cluster modification to the cluster unless --remove_unreachable is also specified. That will terminate the unreachable nodes before running the action that was requested (Example Adding a node) +The resize.sh is deployed on the controller node as part of the HPC cluster Stack deployment. Unreachable nodes have been causing issues. If nodes in the inventory are unreachable, we will not do cluster modification to the cluster unless --remove_unreachable is also specified. That will terminate the unreachable nodes before running the action that was requested (Example Adding a node) ``` /opt/oci-hpc/bin/resize.sh -h @@ -92,7 +92,7 @@ optional arguments: OCID of the localhost --cluster_name CLUSTER_NAME Name of the cluster to resize. Defaults to the name - included in the bastion + included in the controller --nodes NODES [NODES ...] List of nodes to delete --no_reconfigure If present. Does not rerun the playbooks @@ -284,14 +284,14 @@ When the cluster is already being destroyed, it will have a file `/opt/oci-hpc/a ## Autoscaling Monitoring If you selected the autoscaling monitoring, you can see what nodes are spinning up and down as well as running and queued jobs. Everything will run automatically except the import of the Dashboard in Grafana due to a problem in the Grafana API. -To do it manually, in your browser of choice, navigate to bastionIP:3000. Username and password are admin/admin, you can change those during your first login. Go to Configuration -> Data Sources. Select autoscaling. Enter Password as Monitor1234! and click on 'Save & test'. Now click on the + sign on the left menu bar and select import. Click on Upload JSON file and upload the file the is located at `/opt/oci-hpc/playbooks/roles/autoscaling_mon/files/dashboard.json`. Select autoscaling (MySQL) as your datasource. +To do it manually, in your browser of choice, navigate to controllerIP:3000. Username and password are admin/admin, you can change those during your first login. Go to Configuration -> Data Sources. Select autoscaling. Enter Password as Monitor1234! and click on 'Save & test'. Now click on the + sign on the left menu bar and select import. Click on Upload JSON file and upload the file the is located at `/opt/oci-hpc/playbooks/roles/autoscaling_mon/files/dashboard.json`. Select autoscaling (MySQL) as your datasource. You will now see the dashboard. # LDAP -If selected bastion host will act as an LDAP server for the cluster. It's strongly recommended to leave default, shared home directory. -User management can be performed from the bastion using ``` cluster ``` command. +If selected controller host will act as an LDAP server for the cluster. It's strongly recommended to leave default, shared home directory. +User management can be performed from the controller using ``` cluster ``` command. Example of cluster command to add a new user: ```cluster user add name``` By default, a `privilege` group is created that has access to the NFS and can have sudo access on all nodes (Defined at the stack creation. This group has ID 9876) The group name can be modified. @@ -301,21 +301,21 @@ To avoid generating a user-specific key for passwordless ssh between nodes, use # Shared home folder -By default, the home folder is NFS shared directory between all nodes from the bastion. You have the possibility to use a FSS to share it as well to keep working if the bastion goes down. You can either create the FSS from the GUI. Be aware that it will get destroyed when you destroy the stack. Or you can pass an existing FSS IP and path. If you share an existing FSS, do not use /home as mountpoint. The stack will take care of creating a $nfsshare/home directory and mounting it at /home after copying all the appropriate files. +By default, the home folder is NFS shared directory between all nodes from the controller. You have the possibility to use a FSS to share it as well to keep working if the controller goes down. You can either create the FSS from the GUI. Be aware that it will get destroyed when you destroy the stack. Or you can pass an existing FSS IP and path. If you share an existing FSS, do not use /home as mountpoint. The stack will take care of creating a $nfsshare/home directory and mounting it at /home after copying all the appropriate files. # Deploy within a private subnet -If "true", this will create a private endpoint in order for Oracle Resource Manager to configure the bastion VM and the future nodes in private subnet(s). -* If "Use Existing Subnet" is false, Terraform will create 2 private subnets, one for the bastion and one for the compute nodes. -* If "Use Existing Subnet" is also true, the user must indicate a private subnet for the bastion VM. For the compute nodes, they can reside in another private subnet or the same private subent as the bastion VM. +If "true", this will create a private endpoint in order for Oracle Resource Manager to configure the controller VM and the future nodes in private subnet(s). +* If "Use Existing Subnet" is false, Terraform will create 2 private subnets, one for the controller and one for the compute nodes. +* If "Use Existing Subnet" is also true, the user must indicate a private subnet for the controller VM. For the compute nodes, they can reside in another private subnet or the same private subent as the controller VM. -The bastion VM will reside in a private subnet. Therefore, the creation of a "bastion service" (https://docs.oracle.com/en-us/iaas/Content/Bastion/Concepts/bastionoverview.htm), a VPN or FastConnect connection is required. If a public subnet exists in the VCN, adapting the security lists and creating a jump host can also work. Finally, a Peering can also be established betwen the private subnet and another VCN reachable by the user. +The controller VM will reside in a private subnet. Therefore, the creation of a "controller service" (https://docs.oracle.com/en-us/iaas/Content/controller/Concepts/controlleroverview.htm), a VPN or FastConnect connection is required. If a public subnet exists in the VCN, adapting the security lists and creating a jump host can also work. Finally, a Peering can also be established betwen the private subnet and another VCN reachable by the user. ## max_nodes_partition.py usage -Use the alias "max_nodes" to run the python script max_nodes_partition.py. You can run this script only from bastion. +Use the alias "max_nodes" to run the python script max_nodes_partition.py. You can run this script only from controller. $ max_nodes --> Information about all the partitions and their respective clusters, and maximum number of nodes distributed evenly per partition @@ -324,13 +324,13 @@ $ max_nodes --include_cluster_names xxx yyy zzz --> where xxx, yyy, zzz are clus ## validation.py usage -Use the alias "validate" to run the python script validation.py. You can run this script only from bastion. +Use the alias "validate" to run the python script validation.py. You can run this script only from controller. The script performs these checks. -> Check the number of nodes is consistent across resize, /etc/hosts, slurm, topology.conf, OCI console, inventory files. -> PCIe bandwidth check -> GPU Throttle check --> Check whether md5 sum of /etc/hosts file on nodes matches that on bastion +-> Check whether md5 sum of /etc/hosts file on nodes matches that on controller Provide at least one argument: [-n NUM_NODES] [-p PCIE] [-g GPU_THROTTLE] [-e ETC_HOSTS] @@ -343,7 +343,7 @@ Below are some examples for running this script. validate -n y --> This will validate that the number of nodes is consistent across resize, /etc/hosts, slurm, topology.conf, OCI console, inventory files. The clusters considered will be the default cluster if any and cluster(s) found in /opt/oci-hpc/autoscaling/clusters directory. The number of nodes considered will be from the resize script using the clusters we got before. -validate -n y -cn --> This will validate that the number of nodes is consistent across resize, /etc/hosts, slurm, topology.conf, OCI console, inventory files. It will also check whether md5 sum of /etc/hosts file on all nodes matches that on bastion. The clusters considered will be from the file specified by -cn option. The number of nodes considered will be from the resize script using the clusters from the file. +validate -n y -cn --> This will validate that the number of nodes is consistent across resize, /etc/hosts, slurm, topology.conf, OCI console, inventory files. It will also check whether md5 sum of /etc/hosts file on all nodes matches that on controller. The clusters considered will be from the file specified by -cn option. The number of nodes considered will be from the resize script using the clusters from the file. validate -p y -cn --> This will run the pcie bandwidth check. The clusters considered will be from the file specified by -cn option. The number of nodes considered will be from the resize script using the clusters from the file. @@ -364,12 +364,12 @@ validate -n y -p y -g y -e y -cn ## /opt/oci-hpc/scripts/collect_logs.py This is a script to collect nvidia bug report, sosreport, console history logs. -The script needs to be run from the bastion. In the case where the host is not ssh-able, it will get only console history logs for the same. +The script needs to be run from the controller. In the case where the host is not ssh-able, it will get only console history logs for the same. It requires the below argument. --hostname -And --compartment-id is optional (i.e. assumption is the host is in the same compartment as the bastion). +And --compartment-id is optional (i.e. assumption is the host is in the same compartment as the controller). Where HOSTNAME is the node name for which you need the above logs and COMPARTMENT_ID is the OCID of the compartment where the node is. diff --git a/autoscaling/tf_init/cluster-network-configuration.tf b/autoscaling/tf_init/cluster-network-configuration.tf index 9b1d0972..912c3c92 100755 --- a/autoscaling/tf_init/cluster-network-configuration.tf +++ b/autoscaling/tf_init/cluster-network-configuration.tf @@ -14,7 +14,7 @@ resource "oci_core_instance_configuration" "cluster-network-instance_configurati display_name = local.cluster_name metadata = { # TODO: add user key to the authorized_keys - ssh_authorized_keys = file("/home/${var.bastion_username}/.ssh/id_rsa.pub") + ssh_authorized_keys = file("/home/${var.controller_username}/.ssh/id_rsa.pub") user_data = base64encode(data.template_file.config.rendered) } agent_config { diff --git a/autoscaling/tf_init/compute-nodes.tf b/autoscaling/tf_init/compute-nodes.tf index eb8a0c22..d8e65f5c 100755 --- a/autoscaling/tf_init/compute-nodes.tf +++ b/autoscaling/tf_init/compute-nodes.tf @@ -37,7 +37,7 @@ resource "oci_core_instance" "compute_cluster_instances" { } metadata = { - ssh_authorized_keys = file("/home/${var.bastion_username}/.ssh/id_rsa.pub") + ssh_authorized_keys = file("/home/${var.controller_username}/.ssh/id_rsa.pub") user_data = base64encode(data.template_file.config.rendered) } source_details { diff --git a/autoscaling/tf_init/config.bastion b/autoscaling/tf_init/config.controller similarity index 100% rename from autoscaling/tf_init/config.bastion rename to autoscaling/tf_init/config.controller diff --git a/autoscaling/tf_init/bastion_update.tf b/autoscaling/tf_init/controller_update.tf similarity index 83% rename from autoscaling/tf_init/bastion_update.tf rename to autoscaling/tf_init/controller_update.tf index d4154c2e..09a8cb06 100755 --- a/autoscaling/tf_init/bastion_update.tf +++ b/autoscaling/tf_init/controller_update.tf @@ -1,25 +1,25 @@ locals { - bastion_path = "${var.autoscaling_folder}/clusters/${var.cluster_name}" + controller_path = "${var.autoscaling_folder}/clusters/${var.cluster_name}" } resource "null_resource" "create_path" { provisioner "local-exec" { - command = "mkdir -p ${local.bastion_path}" + command = "mkdir -p ${local.controller_path}" } } resource "local_file" "hosts" { depends_on = [null_resource.create_path,oci_core_cluster_network.cluster_network] content = join("\n", local.cluster_instances_ips) - filename = "${local.bastion_path}/hosts_${var.cluster_name}" + filename = "${local.controller_path}/hosts_${var.cluster_name}" } resource "local_file" "inventory" { depends_on = [oci_core_cluster_network.cluster_network, oci_core_cluster_network.cluster_network] - content = templatefile("${local.bastion_path}/inventory.tpl", { - bastion_name = var.bastion_name, - bastion_ip = var.bastion_ip, + content = templatefile("${local.controller_path}/inventory.tpl", { + controller_name = var.controller_name, + controller_ip = var.controller_ip, backup_name = var.backup_name, backup_ip = var.backup_ip, login_name = var.login_name, @@ -53,10 +53,10 @@ resource "local_file" "inventory" { enroot = var.enroot, spack = var.spack, ldap = var.ldap, - bastion_block = var.bastion_block, + controller_block = var.controller_block, login_block = var.login_block, scratch_nfs_type = local.scratch_nfs_type, - bastion_mount_ip = var.bastion_mount_ip, + controller_mount_ip = var.controller_mount_ip, login_mount_ip = var.login_mount_ip, cluster_mount_ip = local.mount_ip, cluster_name = local.cluster_name, @@ -71,13 +71,13 @@ resource "local_file" "inventory" { privilege_sudo = var.privilege_sudo, privilege_group_name = var.privilege_group_name, latency_check = var.latency_check - bastion_username = var.bastion_username, + controller_username = var.controller_username, compute_username = var.compute_username, pam = var.pam, sacct_limits = var.sacct_limits, use_compute_agent=var.use_compute_agent }) - filename = "${local.bastion_path}/inventory" + filename = "${local.controller_path}/inventory" } diff --git a/autoscaling/tf_init/data.tf b/autoscaling/tf_init/data.tf index f9b04337..0fe792cb 100755 --- a/autoscaling/tf_init/data.tf +++ b/autoscaling/tf_init/data.tf @@ -36,7 +36,7 @@ data "oci_core_subnet" "private_subnet" { } data "oci_core_subnet" "public_subnet" { - subnet_id = local.bastion_subnet_id + subnet_id = local.controller_subnet_id } data "oci_core_images" "linux" { diff --git a/autoscaling/tf_init/instance-pool-configuration.tf b/autoscaling/tf_init/instance-pool-configuration.tf index 354276b6..fea68425 100755 --- a/autoscaling/tf_init/instance-pool-configuration.tf +++ b/autoscaling/tf_init/instance-pool-configuration.tf @@ -14,7 +14,7 @@ resource "oci_core_instance_configuration" "instance_pool_configuration" { display_name = local.cluster_name metadata = { # TODO: add user key to the authorized_keys - ssh_authorized_keys = file("/home/${var.bastion_username}/.ssh/id_rsa.pub") + ssh_authorized_keys = file("/home/${var.controller_username}/.ssh/id_rsa.pub") user_data = base64encode(data.template_file.config.rendered) } agent_config { diff --git a/autoscaling/tf_init/inventory.tpl b/autoscaling/tf_init/inventory.tpl index 146d5cce..534dfdcc 100755 --- a/autoscaling/tf_init/inventory.tpl +++ b/autoscaling/tf_init/inventory.tpl @@ -1,7 +1,7 @@ -[bastion] -${bastion_name} ansible_host=${bastion_ip} ansible_user=${bastion_username} role=bastion ansible_python_interpreter=/usr/bin/python +[controller] +${controller_name} ansible_host=${controller_ip} ansible_user=${controller_username} role=controller ansible_python_interpreter=/usr/bin/python [slurm_backup] -%{ if backup_name != "" }${backup_name} ansible_host=${backup_ip} ansible_user=${bastion_username} role=bastion%{ endif } +%{ if backup_name != "" }${backup_name} ansible_host=${backup_ip} ansible_user=${controller_username} role=controller%{ endif } [login] %{ if login_name != "" }${login_name} ansible_host=${login_ip} ansible_user=${compute_username} role=login%{ endif } [compute_to_add] @@ -16,7 +16,7 @@ compute_configured [nfs] %{ if nfs != "" }${nfs} ansible_user=${compute_username} role=nfs%{ endif } [all:children] -bastion +controller compute [all:vars] ansible_connection=ssh @@ -40,10 +40,10 @@ rack_aware = ${rack_aware} pyxis = ${pyxis} enroot = ${enroot} spack = ${spack} -bastion_block = ${bastion_block} +controller_block = ${controller_block} login_block = ${login_block} scratch_nfs_type = ${scratch_nfs_type} -bastion_mount_ip = ${bastion_mount_ip} +controller_mount_ip = ${controller_mount_ip} login_mount_ip = ${login_mount_ip} cluster_mount_ip = ${cluster_mount_ip} autoscaling = true @@ -68,7 +68,7 @@ privilege_sudo=${privilege_sudo} privilege_group_name=${privilege_group_name} latency_check=${latency_check} compute_username=${compute_username} -bastion_username=${bastion_username} +controller_username=${controller_username} pam = ${pam} sacct_limits=${sacct_limits} use_compute_agent=${use_compute_agent} \ No newline at end of file diff --git a/autoscaling/tf_init/locals.tf b/autoscaling/tf_init/locals.tf index 02fd1b0e..25d6691c 100755 --- a/autoscaling/tf_init/locals.tf +++ b/autoscaling/tf_init/locals.tf @@ -13,7 +13,7 @@ locals { subnet_id = var.private_deployment ? var.use_existing_vcn ? var.private_subnet_id : element(concat(oci_core_subnet.private-subnet.*.id, [""]), 1) : var.use_existing_vcn ? var.private_subnet_id : element(concat(oci_core_subnet.private-subnet.*.id, [""]), 0) // subnet id derived either from created subnet or existing if specified - bastion_subnet_id = var.private_deployment ? var.use_existing_vcn ? var.public_subnet_id : element(concat(oci_core_subnet.private-subnet.*.id, [""]), 0) : var.use_existing_vcn ? var.public_subnet_id : element(concat(oci_core_subnet.public-subnet.*.id, [""]), 0) + controller_subnet_id = var.private_deployment ? var.use_existing_vcn ? var.public_subnet_id : element(concat(oci_core_subnet.private-subnet.*.id, [""]), 0) : var.use_existing_vcn ? var.public_subnet_id : element(concat(oci_core_subnet.public-subnet.*.id, [""]), 0) cluster_name = var.use_custom_name ? var.cluster_name : random_pet.name.id cluster_network_image = var.use_marketplace_image ? oci_core_app_catalog_subscription.mp_image_subscription[0].listing_resource_id : local.image_ocid @@ -22,10 +22,10 @@ locals { // image = (var.cluster_network && var.use_marketplace_image == true) || (var.cluster_network == false && var.use_marketplace_image == false) ? var.image : data.oci_core_images.linux.images.0.id -// is_bastion_flex_shape = length(regexall(".*VM.*.*Flex$", var.bastion_shape)) > 0 ? [var.bastion_ocpus]:[] +// is_controller_flex_shape = length(regexall(".*VM.*.*Flex$", var.controller_shape)) > 0 ? [var.controller_ocpus]:[] is_instance_pool_flex_shape = length(regexall(".*VM.*.*Flex$", var.instance_pool_shape)) > 0 ? [local.instance_pool_ocpus]:[] -// bastion_mount_ip = var.bastion_block ? element(concat(oci_core_volume_attachment.bastion_volume_attachment.*.ipv4, [""]), 0) : "none" +// controller_mount_ip = var.controller_block ? element(concat(oci_core_volume_attachment.controller_volume_attachment.*.ipv4, [""]), 0) : "none" scratch_nfs_type = var.cluster_network ? var.scratch_nfs_type_cluster : var.scratch_nfs_type_pool diff --git a/bin/configure.sh b/bin/configure.sh index 76dd82f4..5e06a218 100644 --- a/bin/configure.sh +++ b/bin/configure.sh @@ -4,7 +4,7 @@ # # -# wait for cloud-init completion on the bastion host +# wait for cloud-init completion on the controller host # execution=1 diff --git a/bin/configure_as.sh b/bin/configure_as.sh index a2cbbedb..f530afa0 100755 --- a/bin/configure_as.sh +++ b/bin/configure_as.sh @@ -4,7 +4,7 @@ # # -# wait for cloud-init completion on the bastion host +# wait for cloud-init completion on the controller host # scripts=`realpath $0` diff --git a/bin/bastion.sh b/bin/controller.sh similarity index 99% rename from bin/bastion.sh rename to bin/controller.sh index 501cc559..70952302 100644 --- a/bin/bastion.sh +++ b/bin/controller.sh @@ -4,7 +4,7 @@ # # -# wait for cloud-init completion on the bastion host +# wait for cloud-init completion on the controller host # execution=1 diff --git a/bin/initial_monitoring.sh b/bin/initial_monitoring.sh index d30d78e0..c3b51519 100644 --- a/bin/initial_monitoring.sh +++ b/bin/initial_monitoring.sh @@ -4,8 +4,8 @@ scripts=`realpath $0` folder=`dirname $scripts` end_timestamp=`date -u +'%F %T'` -bastionName=`hostname` -cluster_name=${bastionName/-bastion/} +controllerName=`hostname` +cluster_name=${controllerName/-controller/} autoscaling_folder=$folder/../autoscaling monitoring_folder=$folder/../monitoring diff --git a/bin/resize.py b/bin/resize.py index 5faf6273..f644c14d 100644 --- a/bin/resize.py +++ b/bin/resize.py @@ -139,7 +139,7 @@ def backup_inventory(inventory): def destroy_unreachable_reconfigure(inventory,nodes_to_remove,playbook): if not os.path.isfile("/etc/ansible/hosts"): - print("There is no inventory file, are you on the bastion? The cluster has not been resized") + print("There is no inventory file, are you on the controller? The cluster has not been resized") exit() backup_inventory(inventory) inventory_dict = parse_inventory(inventory) @@ -167,7 +167,7 @@ def destroy_unreachable_reconfigure(inventory,nodes_to_remove,playbook): ips_to_remove.append(instance['ip']) if len(ips_to_remove) != len(nodes_to_remove): print("Some nodes are removed in OCI and removed from the inventory") - print("Try rerunning with the --nodes option and a list of IPs or Slurm Hostnames to cleanup the bastion") + print("Try rerunning with the --nodes option and a list of IPs or Slurm Hostnames to cleanup the controller") write_inventory(inventory_dict,tmp_inventory_destroy) if not len(ips_to_remove): print("No hostname found, trying anyway with "+" ".join(nodes_to_remove)) @@ -189,7 +189,7 @@ def destroy_unreachable_reconfigure(inventory,nodes_to_remove,playbook): def destroy_reconfigure(inventory,nodes_to_remove,playbook): if not os.path.isfile("/etc/ansible/hosts"): - print("There is no inventory file, are you on the bastion? The cluster has not been resized") + print("There is no inventory file, are you on the controller? The cluster has not been resized") exit() backup_inventory(inventory) inventory_dict = parse_inventory(inventory) @@ -261,7 +261,7 @@ def add_reconfigure(comp_ocid,cn_ocid,inventory,CN,specific_hosts=None): reachable_instances=instances unreachable_instances=[] if not os.path.isfile(inventory): - print("There is no inventory file, are you on the bastion? The cluster has been resized but not reconfigured") + print("There is no inventory file, are you on the controller? The cluster has been resized but not reconfigured") exit() host_to_wait_for=[] for node in reachable_instances: @@ -308,7 +308,7 @@ def add_reconfigure(comp_ocid,cn_ocid,inventory,CN,specific_hosts=None): def reconfigure(comp_ocid,cn_ocid,inventory,CN, crucial=False): instances = get_instances(comp_ocid,cn_ocid,CN) if not os.path.isfile(inventory): - print("There is no inventory file, are you on the bastion? Reconfigure did not happen") + print("There is no inventory file, are you on the controller? Reconfigure did not happen") exit() backup_inventory(inventory) inventory_dict = parse_inventory(inventory) @@ -567,7 +567,7 @@ def getLaunchInstanceDetails(instance,comp_ocid,cn_ocid,max_previous_index,index parser = argparse.ArgumentParser(description='Script to resize the CN') parser.add_argument('--compartment_ocid', help='OCID of the compartment, defaults to the Compartment OCID of the localhost') -parser.add_argument('--cluster_name', help='Name of the cluster to resize. Defaults to the name included in the bastion') +parser.add_argument('--cluster_name', help='Name of the cluster to resize. Defaults to the name included in the controller') parser.add_argument('mode', help='Mode type. add/remove node options, implicitly configures newly added nodes. Also implicitly reconfigure/restart services like Slurm to recognize new nodes. Similarly for remove option, terminates nodes and implicitly reconfigure/restart services like Slurm on rest of the cluster nodes to remove reference to deleted nodes.',choices=['add','remove','remove_unreachable','list','reconfigure'],default='list',nargs='?') parser.add_argument('number', type=int, help="Number of nodes to add or delete if a list of hostnames is not defined",nargs='?') parser.add_argument('--nodes', help="List of nodes to delete (Space Separated)",nargs='+') @@ -586,11 +586,11 @@ def getLaunchInstanceDetails(instance,comp_ocid,cn_ocid,max_previous_index,index comp_ocid=args.compartment_ocid if args.cluster_name is None: - cluster_name=metadata['displayName'].replace('-bastion','') + cluster_name=metadata['displayName'].replace('-controller','') else: cluster_name=args.cluster_name -if cluster_name == metadata['displayName'].replace('-bastion',''): +if cluster_name == metadata['displayName'].replace('-controller',''): inventory="/etc/ansible/hosts" host_check_file="/tmp/hosts" autoscaling=False diff --git a/bin/resize.sh b/bin/resize.sh index d2082db8..92dea986 100755 --- a/bin/resize.sh +++ b/bin/resize.sh @@ -23,8 +23,8 @@ fi resize_type=default permanent=1 -bastionName=`hostname` -cluster_name=${bastionName/-bastion/} +controllerName=`hostname` +cluster_name=${controllerName/-controller/} nodes=NULL for (( i=1; i<=$#; i++)); do if [ ${!i} == "--cluster_name" ] diff --git a/cluster-network.tf b/cluster-network.tf index 859daf41..acc39040 100755 --- a/cluster-network.tf +++ b/cluster-network.tf @@ -19,7 +19,7 @@ resource "oci_core_volume_attachment" "cluster_network_volume_attachment" { resource "oci_core_cluster_network" "cluster_network" { count = ( ! var.compute_cluster ) && var.cluster_network && var.node_count > 0 ? 1 : 0 - depends_on = [oci_core_app_catalog_subscription.mp_image_subscription, oci_core_subnet.private-subnet, oci_core_subnet.public-subnet, oci_core_instance.bastion] + depends_on = [oci_core_app_catalog_subscription.mp_image_subscription, oci_core_subnet.private-subnet, oci_core_subnet.public-subnet, oci_core_instance.controller] compartment_id = var.targetCompartment instance_pools { instance_configuration_id = oci_core_instance_configuration.cluster-network-instance_configuration[0].id diff --git a/compute-nodes.tf b/compute-nodes.tf index c7e21c99..85607ae1 100755 --- a/compute-nodes.tf +++ b/compute-nodes.tf @@ -37,7 +37,7 @@ resource "oci_core_instance" "compute_cluster_instances" { metadata = { ssh_authorized_keys = "${var.ssh_key}\n${tls_private_key.ssh.public_key_openssh}" - user_data = base64encode(data.template_file.bastion_config.rendered) + user_data = base64encode(data.template_file.controller_config.rendered) } source_details { source_id = local.cluster_network_image diff --git a/conf/variables.tpl b/conf/variables.tpl index 96dd18d3..e91c0b16 100755 --- a/conf/variables.tpl +++ b/conf/variables.tpl @@ -74,20 +74,20 @@ variable "marketplace_listing_id_GPU" { } -variable "bastion_block_volume_performance" { +variable "controller_block_volume_performance" { /* Allowed values "0. Lower performance" "10. Balanced performance" "20. High Performance" */ -default = "${bastion_block_volume_performance}" +default = "${controller_block_volume_performance}" } variable "scratch_nfs_type_cluster" { default = "${scratch_nfs_type_cluster}"} variable "scratch_nfs_type_pool" { default = "${scratch_nfs_type_pool}" } -variable "bastion_name" {default = "${bastion_name}"} -variable "bastion_ip" {default = "${bastion_ip}"} +variable "controller_name" {default = "${controller_name}"} +variable "controller_ip" {default = "${controller_ip}"} variable "backup_name" {default = "${backup_name}"} variable "backup_ip" {default = "${backup_ip}"} variable "login_name" {default = "${login_name}"} @@ -98,10 +98,10 @@ variable "cluster_block_volume_size" {default="${cluster_block_volume_size}"} variable "cluster_block_volume_performance" {default="${cluster_block_volume_performance}"} variable "ssh_cidr" {default="${ssh_cidr}"} -variable "bastion_block" {default = "${bastion_block}"} +variable "controller_block" {default = "${controller_block}"} variable "login_block" {default = "${login_block}"} -variable "bastion_mount_ip" {default = "${bastion_mount_ip}"} +variable "controller_mount_ip" {default = "${controller_mount_ip}"} variable "login_mount_ip" {default = "${login_mount_ip}"} variable "home_nfs" { default = ${home_nfs} } variable "home_fss" { default = ${home_fss} } @@ -127,7 +127,7 @@ variable "autoscaling_monitoring" { default = ${autoscaling_monitoring} } variable "tags" { default = "##TAGS##" } variable "private_deployment" { default = ${private_deployment} } variable "use_multiple_ads" { default = ${use_multiple_ads} } -variable "bastion_username" { default = "${bastion_username}" } +variable "controller_username" { default = "${controller_username}" } variable "compute_username" { default = "${compute_username}" } variable "localdisk" { default = "${localdisk}" } diff --git a/config.bastion b/config.controller similarity index 100% rename from config.bastion rename to config.controller diff --git a/bastion.tf b/controller.tf similarity index 75% rename from bastion.tf rename to controller.tf index c8df39cb..722b62ce 100644 --- a/bastion.tf +++ b/controller.tf @@ -1,40 +1,40 @@ -resource "oci_core_volume" "bastion_volume" { - count = var.bastion_block ? 1 : 0 - availability_domain = var.bastion_ad +resource "oci_core_volume" "controller_volume" { + count = var.controller_block ? 1 : 0 + availability_domain = var.controller_ad compartment_id = var.targetCompartment - display_name = "${local.cluster_name}-bastion-volume" + display_name = "${local.cluster_name}-controller-volume" - size_in_gbs = var.bastion_block_volume_size - vpus_per_gb = split(".", var.bastion_block_volume_performance)[0] + size_in_gbs = var.controller_block_volume_size + vpus_per_gb = split(".", var.controller_block_volume_performance)[0] } -resource "oci_core_volume_attachment" "bastion_volume_attachment" { - count = var.bastion_block ? 1 : 0 +resource "oci_core_volume_attachment" "controller_volume_attachment" { + count = var.controller_block ? 1 : 0 attachment_type = "iscsi" - volume_id = oci_core_volume.bastion_volume[0].id - instance_id = oci_core_instance.bastion.id - display_name = "${local.cluster_name}-bastion-volume-attachment" + volume_id = oci_core_volume.controller_volume[0].id + instance_id = oci_core_instance.controller.id + display_name = "${local.cluster_name}-controller-volume-attachment" device = "/dev/oracleoci/oraclevdb" is_shareable = true } -resource "oci_core_volume_backup_policy" "bastion_boot_volume_backup_policy" { - count = var.bastion_boot_volume_backup ? 1 : 0 +resource "oci_core_volume_backup_policy" "controller_boot_volume_backup_policy" { + count = var.controller_boot_volume_backup ? 1 : 0 compartment_id = var.targetCompartment - display_name = "${local.cluster_name}-bastion_boot_volume_daily" + display_name = "${local.cluster_name}-controller_boot_volume_daily" schedules { - backup_type = var.bastion_boot_volume_backup_type - period = var.bastion_boot_volume_backup_period - retention_seconds = var.bastion_boot_volume_backup_retention_seconds - time_zone = var.bastion_boot_volume_backup_time_zone + backup_type = var.controller_boot_volume_backup_type + period = var.controller_boot_volume_backup_period + retention_seconds = var.controller_boot_volume_backup_retention_seconds + time_zone = var.controller_boot_volume_backup_time_zone } } resource "oci_core_volume_backup_policy_assignment" "boot_volume_backup_policy" { - count = var.bastion_boot_volume_backup ? 1 : 0 - depends_on = [oci_core_volume_backup_policy.bastion_boot_volume_backup_policy] - asset_id = oci_core_instance.bastion.boot_volume_id - policy_id = oci_core_volume_backup_policy.bastion_boot_volume_backup_policy[0].id + count = var.controller_boot_volume_backup ? 1 : 0 + depends_on = [oci_core_volume_backup_policy.controller_boot_volume_backup_policy] + asset_id = oci_core_instance.controller.boot_volume_id + policy_id = oci_core_volume_backup_policy.controller_boot_volume_backup_policy[0].id } resource "oci_resourcemanager_private_endpoint" "rms_private_endpoint" { @@ -47,29 +47,29 @@ resource "oci_resourcemanager_private_endpoint" "rms_private_endpoint" { } resource "null_resource" "boot_volume_backup_policy" { - depends_on = [oci_core_instance.bastion, oci_core_volume_backup_policy.bastion_boot_volume_backup_policy, oci_core_volume_backup_policy_assignment.boot_volume_backup_policy] + depends_on = [oci_core_instance.controller, oci_core_volume_backup_policy.controller_boot_volume_backup_policy, oci_core_volume_backup_policy_assignment.boot_volume_backup_policy] triggers = { - bastion = oci_core_instance.bastion.id + controller = oci_core_instance.controller.id } } -resource "oci_core_instance" "bastion" { - depends_on = [local.bastion_subnet] - availability_domain = var.bastion_ad +resource "oci_core_instance" "controller" { + depends_on = [local.controller_subnet] + availability_domain = var.controller_ad compartment_id = var.targetCompartment - shape = var.bastion_shape + shape = var.controller_shape dynamic "shape_config" { - for_each = local.is_bastion_flex_shape + for_each = local.is_controller_flex_shape content { ocpus = shape_config.value - memory_in_gbs = var.bastion_custom_memory ? var.bastion_memory : 16 * shape_config.value + memory_in_gbs = var.controller_custom_memory ? var.controller_memory : 16 * shape_config.value } } agent_config { is_management_disabled = true } - display_name = "${local.cluster_name}-bastion" + display_name = "${local.cluster_name}-controller" freeform_tags = { "cluster_name" = local.cluster_name @@ -78,39 +78,39 @@ resource "oci_core_instance" "bastion" { metadata = { ssh_authorized_keys = "${var.ssh_key}\n${tls_private_key.ssh.public_key_openssh}" - user_data = base64encode(data.template_file.bastion_config.rendered) + user_data = base64encode(data.template_file.controller_config.rendered) } source_details { -// source_id = var.use_standard_image ? data.oci_core_images.linux.images.0.id : local.custom_bastion_image_ocid - source_id = local.bastion_image - boot_volume_size_in_gbs = var.bastion_boot_volume_size +// source_id = var.use_standard_image ? data.oci_core_images.linux.images.0.id : local.custom_controller_image_ocid + source_id = local.controller_image + boot_volume_size_in_gbs = var.controller_boot_volume_size source_type = "image" } create_vnic_details { - subnet_id = local.bastion_subnet_id - assign_public_ip = local.bastion_bool_ip + subnet_id = local.controller_subnet_id + assign_public_ip = local.controller_bool_ip } } -resource "null_resource" "bastion" { - depends_on = [oci_core_instance.bastion, oci_core_volume_attachment.bastion_volume_attachment ] +resource "null_resource" "controller" { + depends_on = [oci_core_instance.controller, oci_core_volume_attachment.controller_volume_attachment ] triggers = { - bastion = oci_core_instance.bastion.id + controller = oci_core_instance.controller.id } provisioner "remote-exec" { inline = [ "#!/bin/bash", "sudo mkdir -p /opt/oci-hpc", - "sudo chown ${var.bastion_username}:${var.bastion_username} /opt/oci-hpc/", + "sudo chown ${var.controller_username}:${var.controller_username} /opt/oci-hpc/", "mkdir -p /opt/oci-hpc/bin", "mkdir -p /opt/oci-hpc/playbooks" ] connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -120,7 +120,7 @@ resource "null_resource" "bastion" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -131,7 +131,7 @@ resource "null_resource" "bastion" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -142,7 +142,7 @@ resource "null_resource" "bastion" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -153,7 +153,7 @@ resource "null_resource" "bastion" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -163,7 +163,7 @@ resource "null_resource" "bastion" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -173,7 +173,7 @@ resource "null_resource" "bastion" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -183,7 +183,7 @@ resource "null_resource" "bastion" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -195,43 +195,43 @@ resource "null_resource" "bastion" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } provisioner "file" { content = tls_private_key.ssh.private_key_openssh - destination = "/home/${var.bastion_username}/.ssh/cluster.key" + destination = "/home/${var.controller_username}/.ssh/cluster.key" connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } provisioner "file" { content = tls_private_key.ssh.public_key_openssh - destination = "/home/${var.bastion_username}/.ssh/id_rsa.pub" + destination = "/home/${var.controller_username}/.ssh/id_rsa.pub" connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } } resource "null_resource" "cluster" { - depends_on = [null_resource.bastion, null_resource.backup, oci_core_compute_cluster.compute_cluster, oci_core_cluster_network.cluster_network, oci_core_instance.bastion, oci_core_volume_attachment.bastion_volume_attachment ] + depends_on = [null_resource.controller, null_resource.backup, oci_core_compute_cluster.compute_cluster, oci_core_cluster_network.cluster_network, oci_core_instance.controller, oci_core_volume_attachment.controller_volume_attachment ] triggers = { cluster_instances = join(", ", local.cluster_instances_names) } provisioner "file" { content = templatefile("${path.module}/inventory.tpl", { - bastion_name = oci_core_instance.bastion.display_name, - bastion_ip = oci_core_instance.bastion.private_ip, + controller_name = oci_core_instance.controller.display_name, + controller_ip = oci_core_instance.controller.private_ip, backup_name = var.slurm_ha ? oci_core_instance.backup[0].display_name : "", backup_ip = var.slurm_ha ? oci_core_instance.backup[0].private_ip: "", login_name = var.login_node ? oci_core_instance.login[0].display_name : "", @@ -264,10 +264,10 @@ resource "null_resource" "cluster" { slurm_nfs_path = var.slurm_nfs ? var.nfs_source_path : var.cluster_nfs_path spack = var.spack, ldap = var.ldap, - bastion_block = var.bastion_block, + controller_block = var.controller_block, login_block = var.login_block, scratch_nfs_type = local.scratch_nfs_type, - bastion_mount_ip = local.bastion_mount_ip, + controller_mount_ip = local.controller_mount_ip, login_mount_ip = local.login_mount_ip, cluster_mount_ip = local.mount_ip, autoscaling = var.autoscaling, @@ -277,7 +277,7 @@ resource "null_resource" "cluster" { queue=var.queue, monitoring = var.monitoring, hyperthreading = var.hyperthreading, - bastion_username = var.bastion_username, + controller_username = var.controller_username, compute_username = var.compute_username, autoscaling_monitoring = var.autoscaling_monitoring, autoscaling_mysql_service = var.autoscaling_mysql_service, @@ -302,7 +302,7 @@ resource "null_resource" "cluster" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -314,7 +314,7 @@ resource "null_resource" "cluster" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -331,7 +331,7 @@ resource "null_resource" "cluster" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -362,22 +362,22 @@ resource "null_resource" "cluster" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } provisioner "file" { content = templatefile("${path.module}/conf/variables.tpl", { - bastion_name = oci_core_instance.bastion.display_name, - bastion_ip = oci_core_instance.bastion.private_ip, + controller_name = oci_core_instance.controller.display_name, + controller_ip = oci_core_instance.controller.private_ip, backup_name = var.slurm_ha ? oci_core_instance.backup[0].display_name : "", backup_ip = var.slurm_ha ? oci_core_instance.backup[0].private_ip: "", login_name = var.login_node ? oci_core_instance.login[0].display_name : "", login_ip = var.login_node ? oci_core_instance.login[0].private_ip: "", compute = var.node_count > 0 ? zipmap(local.cluster_instances_names, local.cluster_instances_ips) : zipmap([],[]) public_subnet = data.oci_core_subnet.public_subnet.cidr_block, - public_subnet_id = local.bastion_subnet_id, + public_subnet_id = local.controller_subnet_id, private_subnet = data.oci_core_subnet.private_subnet.cidr_block, private_subnet_id = local.subnet_id, rdma_subnet = var.rdma_subnet, @@ -390,15 +390,15 @@ resource "null_resource" "cluster" { slurm_nfs_path = var.add_nfs ? var.nfs_source_path : var.cluster_nfs_path spack = var.spack, ldap = var.ldap, - bastion_block = var.bastion_block, + controller_block = var.controller_block, login_block = var.login_block, scratch_nfs_type = local.scratch_nfs_type, - bastion_mount_ip = local.bastion_mount_ip, + controller_mount_ip = local.controller_mount_ip, login_mount_ip = local.login_mount_ip, cluster_mount_ip = local.mount_ip, scratch_nfs_type_cluster = var.scratch_nfs_type_cluster, scratch_nfs_type_pool = var.scratch_nfs_type_pool, - bastion_block_volume_performance = var.bastion_block_volume_performance, + controller_block_volume_performance = var.controller_block_volume_performance, region = var.region, tenancy_ocid = var.tenancy_ocid, vcn_subnet = var.vcn_subnet, @@ -429,7 +429,7 @@ resource "null_resource" "cluster" { latency_check = var.latency_check, private_deployment = var.private_deployment, use_multiple_ads = var.use_multiple_ads, - bastion_username = var.bastion_username, + controller_username = var.controller_username, compute_username = var.compute_username, pam = var.pam, sacct_limits = var.sacct_limits, @@ -440,7 +440,7 @@ resource "null_resource" "cluster" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -460,7 +460,7 @@ provisioner "file" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -470,7 +470,7 @@ provisioner "file" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -478,10 +478,10 @@ provisioner "file" { provisioner "remote-exec" { inline = [ "#!/bin/bash", - "chmod 600 /home/${var.bastion_username}/.ssh/cluster.key", - "cp /home/${var.bastion_username}/.ssh/cluster.key /home/${var.bastion_username}/.ssh/id_rsa", + "chmod 600 /home/${var.controller_username}/.ssh/cluster.key", + "cp /home/${var.controller_username}/.ssh/cluster.key /home/${var.controller_username}/.ssh/id_rsa", "chmod a+x /opt/oci-hpc/bin/*.sh", - "timeout --foreground 60m /opt/oci-hpc/bin/bastion.sh", + "timeout --foreground 60m /opt/oci-hpc/bin/controller.sh", "chmod 755 /opt/oci-hpc/autoscaling/crontab/*.sh", "chmod 755 /opt/oci-hpc/samples/*.sh", "chmod 600 /opt/oci-hpc/autoscaling/credentials/key.pem", @@ -493,7 +493,7 @@ provisioner "file" { connection { host = local.host type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -516,7 +516,7 @@ this PAR is used by the scripts to upload NIC metrics to object storage (i.e. sc resource "oci_objectstorage_bucket" "RDMA_NIC_metrics_bucket" { - count = (var.bastion_object_storage_par) ? 1 : 0 + count = (var.controller_object_storage_par) ? 1 : 0 compartment_id = var.targetCompartment name = local.rdma_nic_metric_bucket_name namespace = data.oci_objectstorage_namespace.compartment_namespace.namespace @@ -524,7 +524,7 @@ resource "oci_objectstorage_bucket" "RDMA_NIC_metrics_bucket" { } resource "oci_objectstorage_preauthrequest" "RDMA_NIC_metrics_par" { - count = (var.bastion_object_storage_par) ? 1 : 0 + count = (var.controller_object_storage_par) ? 1 : 0 depends_on = [oci_objectstorage_bucket.RDMA_NIC_metrics_bucket] access_type = "AnyObjectWrite" bucket = local.rdma_nic_metric_bucket_name @@ -536,14 +536,13 @@ resource "oci_objectstorage_preauthrequest" "RDMA_NIC_metrics_par" { output "RDMA_NIC_metrics_url" { depends_on = [oci_objectstorage_preauthrequest.RDMA_NIC_metrics_par] - value = (var.bastion_object_storage_par) ? "https://objectstorage.${var.region}.oraclecloud.com${oci_objectstorage_preauthrequest.RDMA_NIC_metrics_par[0].access_uri}" : "" + value = (var.controller_object_storage_par) ? "https://objectstorage.${var.region}.oraclecloud.com${oci_objectstorage_preauthrequest.RDMA_NIC_metrics_par[0].access_uri}" : "" } resource "local_file" "PAR" { - count = (var.bastion_object_storage_par) ? 1 : 0 + count = (var.controller_object_storage_par) ? 1 : 0 depends_on = [oci_objectstorage_preauthrequest.RDMA_NIC_metrics_par] content = "https://objectstorage.${var.region}.oraclecloud.com${oci_objectstorage_preauthrequest.RDMA_NIC_metrics_par[0].access_uri}" filename = "${local.par_path}/PAR_file_for_metrics" - } - + } \ No newline at end of file diff --git a/data.tf b/data.tf index 3325be77..6528de7f 100755 --- a/data.tf +++ b/data.tf @@ -43,7 +43,7 @@ data "oci_core_subnet" "private_subnet" { } data "oci_core_subnet" "public_subnet" { - subnet_id = local.bastion_subnet_id + subnet_id = local.controller_subnet_id } data "oci_core_images" "linux" { @@ -61,7 +61,7 @@ data "oci_resourcemanager_private_endpoint_reachable_ip" "private_endpoint_reach #Required count = var.private_deployment ? 1 : 0 private_endpoint_id = oci_resourcemanager_private_endpoint.rms_private_endpoint[0].id - private_ip = tostring(oci_core_instance.bastion.private_ip) + private_ip = tostring(oci_core_instance.controller.private_ip) } data "oci_resourcemanager_private_endpoint_reachable_ip" "private_endpoint_reachable_ip_backup" { diff --git a/inventory.tpl b/inventory.tpl index 1d1586c2..6b1582ec 100755 --- a/inventory.tpl +++ b/inventory.tpl @@ -1,7 +1,7 @@ -[bastion] -${bastion_name} ansible_host=${bastion_ip} ansible_user=${bastion_username} role=bastion ansible_python_interpreter=/usr/bin/python +[controller] +${controller_name} ansible_host=${controller_ip} ansible_user=${controller_username} role=controller ansible_python_interpreter=/usr/bin/python [slurm_backup] -%{ if backup_name != "" }${backup_name} ansible_host=${backup_ip} ansible_user=${compute_username} role=bastion%{ endif } +%{ if backup_name != "" }${backup_name} ansible_host=${backup_ip} ansible_user=${compute_username} role=controller%{ endif } [login] %{ if login_name != "" }${login_name} ansible_host=${login_ip} ansible_user=${compute_username} role=login%{ endif } [compute_to_add] @@ -34,10 +34,10 @@ cluster_network = ${cluster_network} slurm = ${slurm} rack_aware = ${rack_aware} spack = ${spack} -bastion_block = ${bastion_block} +controller_block = ${controller_block} login_block = ${login_block} scratch_nfs_type = ${scratch_nfs_type} -bastion_mount_ip = ${bastion_mount_ip} +controller_mount_ip = ${controller_mount_ip} login_mount_ip = ${login_mount_ip} cluster_mount_ip = ${cluster_mount_ip} autoscaling = ${autoscaling} @@ -69,7 +69,7 @@ privilege_sudo=${privilege_sudo} privilege_group_name=${privilege_group_name} latency_check=${latency_check} compute_username=${compute_username} -bastion_username=${bastion_username} +controller_username=${controller_username} region= ${region} tenancy_ocid = ${tenancy_ocid} inst_prin = ${inst_prin} diff --git a/locals.tf b/locals.tf index 9c791ed3..588acde0 100755 --- a/locals.tf +++ b/locals.tf @@ -4,12 +4,12 @@ locals { cluster_instances_names = var.compute_cluster ? oci_core_instance.compute_cluster_instances.*.display_name : var.cluster_network ? data.oci_core_instance.cluster_network_instances.*.display_name : data.oci_core_instance.instance_pool_instances.*.display_name image_ocid = var.unsupported ? var.image_ocid : var.image - custom_bastion_image_ocid = var.unsupported_bastion ? var.unsupported_bastion_image : var.custom_bastion_image + custom_controller_image_ocid = var.unsupported_controller ? var.unsupported_controller_image : var.custom_controller_image custom_login_image_ocid = var.unsupported_login ? var.unsupported_login_image : var.custom_login_image shape = var.cluster_network ? var.cluster_network_shape : var.instance_pool_shape instance_pool_ocpus = local.shape == "VM.DenseIO.E4.Flex" ? var.instance_pool_ocpus_denseIO_flex : var.instance_pool_ocpus - bastion_ocpus = var.bastion_shape == "VM.DenseIO.E4.Flex" ? var.bastion_ocpus_denseIO_flex : var.bastion_ocpus + controller_ocpus = var.controller_shape == "VM.DenseIO.E4.Flex" ? var.controller_ocpus_denseIO_flex : var.controller_ocpus login_ocpus = var.login_shape == "VM.DenseIO.E4.Flex" ? var.login_ocpus_denseIO_flex : var.login_ocpus // ips of the instances cluster_instances_ips = var.compute_cluster ? oci_core_instance.compute_cluster_instances.*.private_ip : var.cluster_network ? data.oci_core_instance.cluster_network_instances.*.private_ip : data.oci_core_instance.instance_pool_instances.*.private_ip @@ -23,12 +23,12 @@ locals { nfs_source_IP = var.create_fss ? element(concat(oci_file_storage_mount_target.FSSMountTarget.*.ip_address, [""]), 0) : var.nfs_source_IP // subnet id derived either from created subnet or existing if specified -// bastion_subnet_id = var.use_existing_vcn ? var.public_subnet_id : element(concat(oci_core_subnet.public-subnet.*.id, [""]), 0) - bastion_subnet_id = var.private_deployment ? var.use_existing_vcn ? var.public_subnet_id : element(concat(oci_core_subnet.private-subnet.*.id, [""]), 0) : var.use_existing_vcn ? var.public_subnet_id : element(concat(oci_core_subnet.public-subnet.*.id, [""]), 0) +// controller_subnet_id = var.use_existing_vcn ? var.public_subnet_id : element(concat(oci_core_subnet.public-subnet.*.id, [""]), 0) + controller_subnet_id = var.private_deployment ? var.use_existing_vcn ? var.public_subnet_id : element(concat(oci_core_subnet.private-subnet.*.id, [""]), 0) : var.use_existing_vcn ? var.public_subnet_id : element(concat(oci_core_subnet.public-subnet.*.id, [""]), 0) cluster_name = var.use_custom_name ? var.cluster_name : random_pet.name.id - bastion_image = var.use_marketplace_image_bastion ? oci_core_app_catalog_subscription.bastion_mp_image_subscription[0].listing_resource_id : local.custom_bastion_image_ocid + controller_image = var.use_marketplace_image_controller ? oci_core_app_catalog_subscription.controller_mp_image_subscription[0].listing_resource_id : local.custom_controller_image_ocid login_image = var.login_node && var.use_marketplace_image_login ? oci_core_app_catalog_subscription.login_mp_image_subscription[0].listing_resource_id : local.custom_login_image_ocid @@ -38,12 +38,12 @@ locals { // image = (var.cluster_network && var.use_marketplace_image == true) || (var.cluster_network == false && var.use_marketplace_image == false) ? var.image : data.oci_core_images.linux.images.0.id - is_bastion_flex_shape = length(regexall(".*VM.*.*Flex$", var.bastion_shape)) > 0 ? [local.bastion_ocpus]:[] + is_controller_flex_shape = length(regexall(".*VM.*.*Flex$", var.controller_shape)) > 0 ? [local.controller_ocpus]:[] is_login_flex_shape = length(regexall(".*VM.*.*Flex$", var.login_shape)) > 0 ? [local.login_ocpus]:[] is_instance_pool_flex_shape = length(regexall(".*VM.*.*Flex$", var.instance_pool_shape)) > 0 ? [local.instance_pool_ocpus]:[] - bastion_mount_ip = var.bastion_block ? element(concat(oci_core_volume_attachment.bastion_volume_attachment.*.ipv4, [""]), 0) : "none" + controller_mount_ip = var.controller_block ? element(concat(oci_core_volume_attachment.controller_volume_attachment.*.ipv4, [""]), 0) : "none" login_mount_ip = var.login_block ? element(concat(oci_core_volume_attachment.login_volume_attachment.*.ipv4, [""]), 0) : "none" scratch_nfs_type = var.cluster_network ? var.scratch_nfs_type_cluster : var.scratch_nfs_type_pool @@ -56,10 +56,10 @@ locals { cluster_ocid = var.node_count > 0 ? var.compute_cluster ? oci_core_compute_cluster.compute_cluster[0].id : var.cluster_network ? oci_core_cluster_network.cluster_network[0].id : oci_core_instance_pool.instance_pool[0].id : "" - host = var.private_deployment ? data.oci_resourcemanager_private_endpoint_reachable_ip.private_endpoint_reachable_ip[0].ip_address : oci_core_instance.bastion.public_ip - bastion_bool_ip = var.private_deployment ? false : true + host = var.private_deployment ? data.oci_resourcemanager_private_endpoint_reachable_ip.private_endpoint_reachable_ip[0].ip_address : oci_core_instance.controller.public_ip + controller_bool_ip = var.private_deployment ? false : true login_bool_ip = var.private_deployment ? false : true - bastion_subnet = var.private_deployment ? oci_core_subnet.private-subnet : oci_core_subnet.public-subnet + controller_subnet = var.private_deployment ? oci_core_subnet.private-subnet : oci_core_subnet.public-subnet private_subnet_cidr = var.private_deployment ? [var.public_subnet, var.private_subnet] : [var.private_subnet] host_backup = var.slurm_ha ? var.private_deployment ? data.oci_resourcemanager_private_endpoint_reachable_ip.private_endpoint_reachable_ip_backup[0].ip_address : oci_core_instance.backup[0].public_ip : "none" host_login = var.login_node ? var.private_deployment ? data.oci_resourcemanager_private_endpoint_reachable_ip.private_endpoint_reachable_ip_login[0].ip_address : oci_core_instance.login[0].public_ip : "none" diff --git a/login.tf b/login.tf index 22200fc5..d8d1b59d 100644 --- a/login.tf +++ b/login.tf @@ -43,17 +43,17 @@ resource "oci_core_instance" "login" { metadata = { ssh_authorized_keys = "${var.ssh_key}\n${tls_private_key.ssh.public_key_openssh}" - user_data = base64encode(data.template_file.bastion_config.rendered) + user_data = base64encode(data.template_file.controller_config.rendered) } source_details { -// source_id = var.use_standard_image ? data.oci_core_images.linux.images.0.id : local.custom_bastion_image_ocid +// source_id = var.use_standard_image ? data.oci_core_images.linux.images.0.id : local.custom_controller_image_ocid source_id = local.login_image boot_volume_size_in_gbs = var.login_boot_volume_size source_type = "image" } create_vnic_details { - subnet_id = local.bastion_subnet_id + subnet_id = local.controller_subnet_id assign_public_ip = local.login_bool_ip } } diff --git a/marketplace.tf b/marketplace.tf index c434af50..a735598d 100755 --- a/marketplace.tf +++ b/marketplace.tf @@ -1,10 +1,10 @@ locals { // listing_number = split(".", var.marketplace_listing)[0] mp_listing_id = var.use_marketplace_image ? substr(var.marketplace_listing,0,3) == "HPC" ? var.marketplace_listing_id_HPC : var.marketplace_listing_id_GPU : "" - mp_bastion_listing_id = var.use_marketplace_image_bastion ? substr(var.marketplace_listing_bastion,0,3) == "HPC" ? var.marketplace_listing_id_HPC : var.marketplace_listing_id_GPU : "" + mp_controller_listing_id = var.use_marketplace_image_controller ? substr(var.marketplace_listing_controller,0,3) == "HPC" ? var.marketplace_listing_id_HPC : var.marketplace_listing_id_GPU : "" mp_login_listing_id = var.use_marketplace_image_login ? substr(var.marketplace_listing_login,0,3) == "HPC" ? var.marketplace_listing_id_HPC : var.marketplace_listing_id_GPU : "" mp_version_id = var.marketplace_version_id[var.marketplace_listing] - mp_bastion_version_id = var.marketplace_version_id[var.marketplace_listing_bastion] + mp_controller_version_id = var.marketplace_version_id[var.marketplace_listing_controller] mp_login_version_id = var.marketplace_version_id[var.marketplace_listing_login] } @@ -48,28 +48,28 @@ resource "oci_core_app_catalog_subscription" "mp_image_subscription" { } } -data "oci_core_app_catalog_listing_resource_versions" "bastion_app_catalog_listing_resource_versions" { - count = var.use_marketplace_image_bastion ? 1 : 0 - listing_id = local.mp_bastion_listing_id +data "oci_core_app_catalog_listing_resource_versions" "controller_app_catalog_listing_resource_versions" { + count = var.use_marketplace_image_controller ? 1 : 0 + listing_id = local.mp_controller_listing_id } -resource "oci_core_app_catalog_listing_resource_version_agreement" "bastion_mp_image_agreement" { - count = ( var.use_marketplace_image_bastion ) ? 1 : 0 +resource "oci_core_app_catalog_listing_resource_version_agreement" "controller_mp_image_agreement" { + count = ( var.use_marketplace_image_controller ) ? 1 : 0 - listing_id = local.mp_bastion_listing_id - listing_resource_version = local.mp_bastion_version_id + listing_id = local.mp_controller_listing_id + listing_resource_version = local.mp_controller_version_id } -resource "oci_core_app_catalog_subscription" "bastion_mp_image_subscription" { - count = ( var.use_marketplace_image_bastion ) ? 1 : 0 +resource "oci_core_app_catalog_subscription" "controller_mp_image_subscription" { + count = ( var.use_marketplace_image_controller ) ? 1 : 0 compartment_id = var.targetCompartment - eula_link = oci_core_app_catalog_listing_resource_version_agreement.bastion_mp_image_agreement[0].eula_link - listing_id = oci_core_app_catalog_listing_resource_version_agreement.bastion_mp_image_agreement[0].listing_id - listing_resource_version = oci_core_app_catalog_listing_resource_version_agreement.bastion_mp_image_agreement[0].listing_resource_version - oracle_terms_of_use_link = oci_core_app_catalog_listing_resource_version_agreement.bastion_mp_image_agreement[0].oracle_terms_of_use_link - signature = oci_core_app_catalog_listing_resource_version_agreement.bastion_mp_image_agreement[0].signature - time_retrieved = oci_core_app_catalog_listing_resource_version_agreement.bastion_mp_image_agreement[0].time_retrieved + eula_link = oci_core_app_catalog_listing_resource_version_agreement.controller_mp_image_agreement[0].eula_link + listing_id = oci_core_app_catalog_listing_resource_version_agreement.controller_mp_image_agreement[0].listing_id + listing_resource_version = oci_core_app_catalog_listing_resource_version_agreement.controller_mp_image_agreement[0].listing_resource_version + oracle_terms_of_use_link = oci_core_app_catalog_listing_resource_version_agreement.controller_mp_image_agreement[0].oracle_terms_of_use_link + signature = oci_core_app_catalog_listing_resource_version_agreement.controller_mp_image_agreement[0].signature + time_retrieved = oci_core_app_catalog_listing_resource_version_agreement.controller_mp_image_agreement[0].time_retrieved timeouts { create = "20m" diff --git a/mysql.tf b/mysql.tf index 78c33ca2..fd21313c 100644 --- a/mysql.tf +++ b/mysql.tf @@ -3,7 +3,7 @@ resource "oci_mysql_mysql_db_system" "monitoring_mysql_db_system" { count = var.autoscaling_monitoring && var.autoscaling_mysql_service ? 1 : 0 admin_password = var.admin_password admin_username = var.admin_username - availability_domain = var.bastion_ad + availability_domain = var.controller_ad compartment_id = var.targetCompartment shape_name = var.monitoring_shape_name subnet_id = local.subnet_id diff --git a/outputs.tf b/outputs.tf index b11d640f..af5b5cba 100755 --- a/outputs.tf +++ b/outputs.tf @@ -1,4 +1,4 @@ -output "bastion" { +output "controller" { value = local.host } diff --git a/playbooks/destroy.yml b/playbooks/destroy.yml index 46efb661..9f413982 100755 --- a/playbooks/destroy.yml +++ b/playbooks/destroy.yml @@ -9,7 +9,7 @@ - include_role: name: slurm when: slurm|default(false)|bool -- hosts: bastion, slurm_backup, login +- hosts: controller, slurm_backup, login become: true vars: destroy: true diff --git a/playbooks/new_nodes.yml b/playbooks/new_nodes.yml index 3be0cb57..a5e68f90 100755 --- a/playbooks/new_nodes.yml +++ b/playbooks/new_nodes.yml @@ -58,7 +58,7 @@ - include_role: name: nvidia_peermem -- hosts: bastion,slurm_backup,login,compute +- hosts: controller,slurm_backup,login,compute become: true vars: destroy: false @@ -90,7 +90,7 @@ name: nfs-client vars: local_path: "{{ cluster_nfs_path }}" - export_host: "{{ hostvars[groups['bastion'][0]]['ansible_default_ipv4']['address'] }}" + export_host: "{{ hostvars[groups['controller'][0]]['ansible_default_ipv4']['address'] }}" export_path: "/export/cluster" options: "defaults,noatime,bg,timeo=100,ac,actimeo=120,nocto,rsize=1048576,wsize=1048576,nolock,local_lock={{ lock }},mountproto=tcp,sec=sys,_netdev" lock: "all" @@ -134,7 +134,7 @@ name: nfs-client vars: local_path: "/home" - export_host: "{{ hostvars[groups['bastion'][0]]['ansible_default_ipv4']['address'] }}" + export_host: "{{ hostvars[groups['controller'][0]]['ansible_default_ipv4']['address'] }}" export_path: "/home" options: "defaults,noatime,bg,timeo=100,ac,actimeo=120,nocto,rsize=1048576,wsize=1048576,nolock,local_lock={{ lock }},mountproto=tcp,sec=sys,_netdev" lock: "all" diff --git a/playbooks/resize_add.yml b/playbooks/resize_add.yml index e3070b40..3d92190c 100755 --- a/playbooks/resize_add.yml +++ b/playbooks/resize_add.yml @@ -56,7 +56,7 @@ - include_role: name: nvidia_peermem -- hosts: bastion,slurm_backup,login,compute +- hosts: controller,slurm_backup,login,compute become: true vars: destroy: false @@ -89,7 +89,7 @@ name: nfs-client vars: local_path: "{{ cluster_nfs_path }}" - export_host: "{{ hostvars[groups['bastion'][0]]['ansible_default_ipv4']['address'] }}" + export_host: "{{ hostvars[groups['controller'][0]]['ansible_default_ipv4']['address'] }}" export_path: "/export/cluster" options: "defaults,noatime,bg,timeo=100,ac,actimeo=120,nocto,rsize=1048576,wsize=1048576,nolock,local_lock={{ lock }},mountproto=tcp,sec=sys,_netdev" lock: "all" @@ -136,7 +136,7 @@ name: nfs-client vars: local_path: "/home" - export_host: "{{ hostvars[groups['bastion'][0]]['ansible_default_ipv4']['address'] }}" + export_host: "{{ hostvars[groups['controller'][0]]['ansible_default_ipv4']['address'] }}" export_path: "/home" options: "defaults,noatime,bg,timeo=100,ac,actimeo=120,nocto,rsize=1048576,wsize=1048576,nolock,local_lock={{ lock }},mountproto=tcp,sec=sys,_netdev" lock: "all" diff --git a/playbooks/resize_remove.yml b/playbooks/resize_remove.yml index c75ea9fc..99029c50 100755 --- a/playbooks/resize_remove.yml +++ b/playbooks/resize_remove.yml @@ -1,4 +1,4 @@ -- hosts: bastion, slurm_backup, compute, login +- hosts: controller, slurm_backup, compute, login become: true gather_facts: true vars: diff --git a/playbooks/resize_remove_unreachable.yml b/playbooks/resize_remove_unreachable.yml index 4a5b95e7..5d8f274f 100644 --- a/playbooks/resize_remove_unreachable.yml +++ b/playbooks/resize_remove_unreachable.yml @@ -1,4 +1,4 @@ -- hosts: bastion, compute, slurm_backup, login +- hosts: controller, compute, slurm_backup, login become: true gather_facts: true vars: diff --git a/playbooks/roles/cluster-cli/files/cluster b/playbooks/roles/cluster-cli/files/cluster index a91c2ebf..e3bf875e 100755 --- a/playbooks/roles/cluster-cli/files/cluster +++ b/playbooks/roles/cluster-cli/files/cluster @@ -6,7 +6,7 @@ import grp import pwd import os -host = 'bastion' +host = 'controller' bind_dn = 'cn=manager,dc=local' groups_dn = 'ou=Group,dc=local' people_dn = 'ou=People,dc=local' diff --git a/playbooks/roles/destroy_unreachable/tasks/slurm-rack-aware.yml b/playbooks/roles/destroy_unreachable/tasks/slurm-rack-aware.yml index afe0cd62..bd735b4e 100644 --- a/playbooks/roles/destroy_unreachable/tasks/slurm-rack-aware.yml +++ b/playbooks/roles/destroy_unreachable/tasks/slurm-rack-aware.yml @@ -253,7 +253,7 @@ ignore_unreachable: True with_items: "{{unreachable_slurm_nodes}}" delegate_to: 127.0.0.1 - when: ('bastion' in group_names) + when: ('controller' in group_names) - name: move topology.conf on backup servers become: true diff --git a/playbooks/roles/destroy_unreachable/tasks/slurm.yml b/playbooks/roles/destroy_unreachable/tasks/slurm.yml index 6249b5ca..e97520c7 100644 --- a/playbooks/roles/destroy_unreachable/tasks/slurm.yml +++ b/playbooks/roles/destroy_unreachable/tasks/slurm.yml @@ -143,7 +143,7 @@ ignore_unreachable: True with_items: "{{unreachable_slurm_nodes}}" delegate_to: 127.0.0.1 - when: ('bastion' in group_names) + when: ('controller' in group_names) - name: move topology.conf on backup servers become: true diff --git a/playbooks/roles/etc-hosts/tasks/common.yml b/playbooks/roles/etc-hosts/tasks/common.yml index cd39a6c8..97888dcb 100644 --- a/playbooks/roles/etc-hosts/tasks/common.yml +++ b/playbooks/roles/etc-hosts/tasks/common.yml @@ -1,22 +1,22 @@ --- -- name: create bastion part of the /etc/hosts files for the compute nodes +- name: create controller part of the /etc/hosts files for the compute nodes blockinfile: dest: /tmp/hosts.etc.{{ cluster_name }} - content: "{{ lookup('template', 'templates/etc-hosts-bastion.j2') }}" + content: "{{ lookup('template', 'templates/etc-hosts-controller.j2') }}" state: present create: yes - marker: "# {mark} ANSIBLE MANAGED BLOCK BASTION" + marker: "# {mark} ANSIBLE MANAGED BLOCK controller" delegate_to: 127.0.0.1 run_once: true when: not destroy|bool -- name: create bastion part of the /etc/hosts files for the bastion +- name: create controller part of the /etc/hosts files for the controller blockinfile: dest: /etc/hosts - content: "{{ lookup('template', 'templates/etc-hosts-bastion.j2') }}" + content: "{{ lookup('template', 'templates/etc-hosts-controller.j2') }}" state: present create: yes - marker: "# {mark} ANSIBLE MANAGED BLOCK BASTION" + marker: "# {mark} ANSIBLE MANAGED BLOCK controller" delegate_to: 127.0.0.1 run_once: true when: not destroy|bool @@ -41,7 +41,7 @@ run_once: true when: not destroy|bool and groups['compute']|length > 0 -- name: add cluster nodes to the /etc/hosts file of the bastion +- name: add cluster nodes to the /etc/hosts file of the controller blockinfile: dest: /etc/hosts content: "{{ lookup('template', 'templates/etc-hosts.j2') }}" @@ -64,7 +64,7 @@ become: true lineinfile: dest: /etc/hosts - regexp: "^127.0.1.1\\s{{hostvars[groups['bastion'][0]]['inventory_hostname']}}.*" + regexp: "^127.0.1.1\\s{{hostvars[groups['controller'][0]]['inventory_hostname']}}.*" state: absent when: ( not destroy|bool ) and (('slurm_backup' in group_names) or ('login' in group_names)) @@ -74,7 +74,7 @@ dest: /etc/hosts src: /tmp/hosts.etc.{{ cluster_name }} force: yes - when: ( not destroy|bool ) and (not 'bastion' in group_names) and (not 'slurm_backup' in group_names) and (not 'login' in group_names) + when: ( not destroy|bool ) and (not 'controller' in group_names) and (not 'slurm_backup' in group_names) and (not 'login' in group_names) - name: remove cluster from etc-host become: true diff --git a/playbooks/roles/etc-hosts/templates/etc-hosts-bastion.j2 b/playbooks/roles/etc-hosts/templates/etc-hosts-controller.j2 similarity index 87% rename from playbooks/roles/etc-hosts/templates/etc-hosts-bastion.j2 rename to playbooks/roles/etc-hosts/templates/etc-hosts-controller.j2 index 0289b5c0..e604e118 100755 --- a/playbooks/roles/etc-hosts/templates/etc-hosts-bastion.j2 +++ b/playbooks/roles/etc-hosts/templates/etc-hosts-controller.j2 @@ -1,6 +1,6 @@ -{% for item in groups['bastion'] %} +{% for item in groups['controller'] %} {% set short_name = hostvars[item]['ansible_fqdn'].split('.') %} -{{ hostvars[item]['ansible_host'] }} {{ hostvars[item]['ansible_fqdn'] }} {{ short_name[0] }} bastion +{{ hostvars[item]['ansible_host'] }} {{ hostvars[item]['ansible_fqdn'] }} {{ short_name[0] }} controller {% endfor %} {% for item in groups['slurm_backup'] %} {% set short_name = hostvars[item]['ansible_fqdn'].split('.') %} diff --git a/playbooks/roles/influxdb/tasks/el.yml b/playbooks/roles/influxdb/tasks/el.yml index d8e45e5b..52f0ab95 100755 --- a/playbooks/roles/influxdb/tasks/el.yml +++ b/playbooks/roles/influxdb/tasks/el.yml @@ -2,6 +2,6 @@ - name: install influxdb include_tasks: el_install_influxdb.yml -- name: configure influxdb on bastion +- name: configure influxdb on controller include_tasks: config_influxdb.yml - when: "'bastion' in group_names" \ No newline at end of file + when: "'controller' in group_names" \ No newline at end of file diff --git a/playbooks/roles/influxdb/tasks/ubuntu.yml b/playbooks/roles/influxdb/tasks/ubuntu.yml index a4cf3be1..38896ea9 100644 --- a/playbooks/roles/influxdb/tasks/ubuntu.yml +++ b/playbooks/roles/influxdb/tasks/ubuntu.yml @@ -2,6 +2,6 @@ - name: install influxdb include_tasks: ubuntu_install_influxdb.yml -- name: configure influxdb on bastion +- name: configure influxdb on controller include_tasks: config_influxdb.yml - when: "'bastion' in group_names" \ No newline at end of file + when: "'controller' in group_names" \ No newline at end of file diff --git a/playbooks/roles/packages/tasks/el-7.yml b/playbooks/roles/packages/tasks/el-7.yml index d0c23143..3e5c2884 100755 --- a/playbooks/roles/packages/tasks/el-7.yml +++ b/playbooks/roles/packages/tasks/el-7.yml @@ -28,4 +28,4 @@ state: latest executable: pip3 ignore_errors: yes - when: ('bastion' in group_names) + when: ('controller' in group_names) diff --git a/playbooks/roles/packages/tasks/ol-7.yml b/playbooks/roles/packages/tasks/ol-7.yml index 7159eee4..57bead10 100644 --- a/playbooks/roles/packages/tasks/ol-7.yml +++ b/playbooks/roles/packages/tasks/ol-7.yml @@ -31,4 +31,4 @@ state: latest executable: pip3 ignore_errors: yes - when: ('bastion' in group_names) \ No newline at end of file + when: ('controller' in group_names) \ No newline at end of file diff --git a/playbooks/roles/packages/tasks/ol-8.yml b/playbooks/roles/packages/tasks/ol-8.yml index ad1d9877..8a6b3353 100644 --- a/playbooks/roles/packages/tasks/ol-8.yml +++ b/playbooks/roles/packages/tasks/ol-8.yml @@ -33,5 +33,5 @@ state: latest executable: pip3 ignore_errors: yes - when: ('bastion' in group_names) + when: ('controller' in group_names) diff --git a/playbooks/roles/slurm/tasks/backup_server.yml b/playbooks/roles/slurm/tasks/backup_server.yml index 5e931304..1dbea29c 100755 --- a/playbooks/roles/slurm/tasks/backup_server.yml +++ b/playbooks/roles/slurm/tasks/backup_server.yml @@ -35,7 +35,7 @@ with_items: - munge -- name: Render systemd units for slurmctld on backup bastion +- name: Render systemd units for slurmctld on backup controller become: true template: src: 'systemd/slurmctld_backup.service.d/unit.conf.j2' diff --git a/playbooks/roles/slurm/tasks/common_pyxis.yml b/playbooks/roles/slurm/tasks/common_pyxis.yml index ccd3fe8e..a200ad54 100644 --- a/playbooks/roles/slurm/tasks/common_pyxis.yml +++ b/playbooks/roles/slurm/tasks/common_pyxis.yml @@ -58,5 +58,5 @@ content: | required /usr/local/lib/slurm/spank_pyxis.so mode: '0775' - owner: "{{bastion_username}}" + owner: "{{controller_username}}" group: "{{privilege_group_name}}" \ No newline at end of file diff --git a/playbooks/roles/slurm/tasks/compute-rack-aware.yml b/playbooks/roles/slurm/tasks/compute-rack-aware.yml index bd270e32..7d9052d2 100755 --- a/playbooks/roles/slurm/tasks/compute-rack-aware.yml +++ b/playbooks/roles/slurm/tasks/compute-rack-aware.yml @@ -56,7 +56,7 @@ - name: set permissions become: true shell: - cmd: chown {{ bastion_username }}:{{ bastion_username }} /tmp/munge.key + cmd: chown {{ controller_username }}:{{ controller_username }} /tmp/munge.key delegate_to: 127.0.0.1 run_once: true @@ -100,7 +100,7 @@ - name: Get rackIDs for all compute nodes set_fact: racks_to_add_temp: "{{cluster_name}}:{{hostvars[item]['rackID']}}" - with_items: "{{ play_hosts | difference(groups['bastion']) | difference(groups['slurm_backup']) | difference(groups['login'])}}" + with_items: "{{ play_hosts | difference(groups['controller']) | difference(groups['slurm_backup']) | difference(groups['login'])}}" run_once: true register: racks_to_add_temp_results @@ -111,7 +111,7 @@ - name: Get hostnames set_fact: nodes_to_add_temp: "{{hostvars[item]['ansible_hostname']}}" - with_items: "{{ play_hosts | difference(groups['bastion']) | difference(groups['slurm_backup']) | difference(groups['login']) }}" + with_items: "{{ play_hosts | difference(groups['controller']) | difference(groups['slurm_backup']) | difference(groups['login']) }}" run_once: true register: nodes_to_add_temp_results @@ -138,7 +138,7 @@ - name: Get hostlist if switch exists vars: - new_line: "{% for node in ( play_hosts | difference(groups['bastion']) | difference(groups['slurm_backup']) | difference(groups['login']) ) %}{% if cluster_name+':'+hostvars[node]['rackID'] == item.item.item %}{{hostvars[node]['ansible_hostname']}},{% endif %}{% endfor %}" + new_line: "{% for node in ( play_hosts | difference(groups['controller']) | difference(groups['slurm_backup']) | difference(groups['login']) ) %}{% if cluster_name+':'+hostvars[node]['rackID'] == item.item.item %}{{hostvars[node]['ansible_hostname']}},{% endif %}{% endfor %}" command: "scontrol show hostlistsorted {{ item.stdout_lines | union (new_line[:-1].split(',') | list )| join(',') }}" register: rack_hostlist1 delegate_to: 127.0.0.1 @@ -148,7 +148,7 @@ - name: Get hostlist if switch does not exists vars: - new_line: "{% for node in ( play_hosts | difference(groups['bastion']) | difference(groups['slurm_backup']) | difference(groups['login']) ) %}{% if cluster_name+':'+hostvars[node]['rackID'] == item.item.item %}{{hostvars[node]['ansible_hostname']}},{% endif %}{% endfor %}" + new_line: "{% for node in ( play_hosts | difference(groups['controller']) | difference(groups['slurm_backup']) | difference(groups['login']) ) %}{% if cluster_name+':'+hostvars[node]['rackID'] == item.item.item %}{{hostvars[node]['ansible_hostname']}},{% endif %}{% endfor %}" command: "scontrol show hostlistsorted {{ new_line[:-1] }}" register: rack_hostlist2 delegate_to: 127.0.0.1 @@ -251,7 +251,7 @@ enabled: true -- name: Update node state on bastion +- name: Update node state on controller block: - name: Grab Node State shell: 'sinfo -h -o "%t" -n {{ ansible_hostname }}' @@ -259,7 +259,7 @@ delegate_to: 127.0.0.1 - set_fact: node_state2: "{{ node_state.stdout }}" - - name: Update node state on bastion + - name: Update node state on controller become: true command: scontrol update nodename={{ ansible_hostname }} state=RESUME when: node_state2 != "idle" and node_state2 != "alloc" @@ -279,7 +279,7 @@ - set_fact: node_state2: "{{ node_state.stdout }}" - - name: Update node state on bastion + - name: Update node state on controller become: true command: scontrol update nodename={{ ansible_hostname }} state=RESUME when: node_state2 != "idle" and node_state2 != "alloc" diff --git a/playbooks/roles/slurm/tasks/compute.yml b/playbooks/roles/slurm/tasks/compute.yml index 6dbf46cc..6eff34f5 100755 --- a/playbooks/roles/slurm/tasks/compute.yml +++ b/playbooks/roles/slurm/tasks/compute.yml @@ -59,7 +59,7 @@ - name: set permissions become: true shell: - cmd: chown {{ bastion_username }}:{{ bastion_username }} /tmp/munge.key + cmd: chown {{ controller_username }}:{{ controller_username }} /tmp/munge.key delegate_to: 127.0.0.1 run_once: true @@ -83,7 +83,7 @@ - name: Get hostnames set_fact: nodes_to_add_temp: "{{hostvars[item]['ansible_hostname']}}" - with_items: "{{ play_hosts | difference(groups['bastion']) | difference(groups['slurm_backup']) | difference(groups['login']) }}" + with_items: "{{ play_hosts | difference(groups['controller']) | difference(groups['slurm_backup']) | difference(groups['login']) }}" run_once: true register: nodes_to_add_temp_results @@ -169,7 +169,7 @@ state: restarted enabled: true -- name: Update node state on bastion +- name: Update node state on controller block: - name: Grab Node State shell: 'sinfo -h -o "%t" -n {{ ansible_hostname }}' @@ -177,7 +177,7 @@ delegate_to: 127.0.0.1 - set_fact: node_state2: "{{ node_state.stdout }}" - - name: Update node state on bastion + - name: Update node state on controller become: true command: scontrol update nodename={{ ansible_hostname }} state=RESUME when: node_state2 != "idle" and node_state2 != "alloc" @@ -197,7 +197,7 @@ - set_fact: node_state2: "{{ node_state.stdout }}" - - name: Update node state on bastion + - name: Update node state on controller become: true command: scontrol update nodename={{ ansible_hostname }} state=RESUME when: node_state2 != "idle" and node_state2 != "alloc" diff --git a/playbooks/roles/slurm/tasks/bastion.yml b/playbooks/roles/slurm/tasks/controller.yml similarity index 74% rename from playbooks/roles/slurm/tasks/bastion.yml rename to playbooks/roles/slurm/tasks/controller.yml index 53febd7c..47882734 100755 --- a/playbooks/roles/slurm/tasks/bastion.yml +++ b/playbooks/roles/slurm/tasks/controller.yml @@ -6,11 +6,11 @@ slurm_repos: "epel,ol7_developer_EPEL" when: (not destroy|bool) and ((initial|bool) or (not initial|bool and ('compute' in group_names))) - - name: run server directives ol7 bastion + - name: run server directives ol7 controller include_tasks: server.yml vars: slurm_repos: "epel,ol7_developer_EPEL" - when: ('bastion' in group_names) and (not destroy|bool) and (initial| bool) + when: ('controller' in group_names) and (not destroy|bool) and (initial| bool) when: ansible_distribution_major_version == '7' - block: @@ -20,9 +20,9 @@ slurm_repos: "ol8_developer_EPEL,ol8_codeready_builder" when: (not destroy|bool) and ((initial|bool) or (not initial|bool and ('compute' in group_names))) - - name: run server directives ol8 bastion + - name: run server directives ol8 controller include_tasks: server.yml vars: slurm_repos: "ol8_developer_EPEL,ol8_codeready_builder" - when: ('bastion' in group_names) and (not destroy|bool) and (initial| bool) + when: ('controller' in group_names) and (not destroy|bool) and (initial| bool) when: ansible_distribution_major_version == '8' \ No newline at end of file diff --git a/playbooks/roles/slurm/tasks/destroy-rack-aware.yml b/playbooks/roles/slurm/tasks/destroy-rack-aware.yml index 7f1e8846..1bc888a0 100755 --- a/playbooks/roles/slurm/tasks/destroy-rack-aware.yml +++ b/playbooks/roles/slurm/tasks/destroy-rack-aware.yml @@ -49,7 +49,7 @@ - name: Get hostnames set_fact: nodes_to_remove_temp: "{{hostvars[item]['ansible_hostname']}}" - with_items: "{{ play_hosts | difference(groups['bastion']) | difference(groups['slurm_backup']) | difference(groups['login'])}}" + with_items: "{{ play_hosts | difference(groups['controller']) | difference(groups['slurm_backup']) | difference(groups['login'])}}" run_once: true register: nodes_to_remove_temp_results @@ -87,7 +87,7 @@ - name: Get rackIDs set_fact: racks_to_remove_temp: "{{cluster_name}}:{{hostvars[item]['rackID']}}" - with_items: "{{ play_hosts | difference(groups['bastion']) | difference(groups['slurm_backup']) | difference(groups['login'])}}" + with_items: "{{ play_hosts | difference(groups['controller']) | difference(groups['slurm_backup']) | difference(groups['login'])}}" run_once: true register: racks_to_remove_temp_results diff --git a/playbooks/roles/slurm/tasks/destroy.yml b/playbooks/roles/slurm/tasks/destroy.yml index 5c58d085..7df264a6 100755 --- a/playbooks/roles/slurm/tasks/destroy.yml +++ b/playbooks/roles/slurm/tasks/destroy.yml @@ -55,7 +55,7 @@ - name: Get hostnames set_fact: nodes_to_add_temp: "{{hostvars[item]['ansible_hostname']}}" - with_items: "{{ play_hosts | difference(groups['bastion']) | difference(groups['slurm_backup']) | difference(groups['login']) }}" + with_items: "{{ play_hosts | difference(groups['controller']) | difference(groups['slurm_backup']) | difference(groups['login']) }}" run_once: true register: nodes_to_add_temp_results diff --git a/playbooks/roles/slurm/tasks/main.yml b/playbooks/roles/slurm/tasks/main.yml index 89944752..0bbfea4c 100755 --- a/playbooks/roles/slurm/tasks/main.yml +++ b/playbooks/roles/slurm/tasks/main.yml @@ -7,7 +7,7 @@ - include_vars: ubuntu_vars.yml when: ansible_distribution == 'Ubuntu' -- include: bastion.yml +- include: controller.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' - include: el7.yml diff --git a/playbooks/roles/slurm/tasks/ubuntu.yml b/playbooks/roles/slurm/tasks/ubuntu.yml index 96a8843e..5399d7cc 100644 --- a/playbooks/roles/slurm/tasks/ubuntu.yml +++ b/playbooks/roles/slurm/tasks/ubuntu.yml @@ -4,7 +4,7 @@ - name: run server directives include_tasks: server.yml - when: ('bastion' in group_names) and (not destroy|bool) and (initial| bool) + when: ('controller' in group_names) and (not destroy|bool) and (initial| bool) - name: run compute directives include_tasks: "compute{{rack_aware_playbook_suffix}}.yml" diff --git a/playbooks/roles/slurm/templates/slurm.conf.j2 b/playbooks/roles/slurm/templates/slurm.conf.j2 index 0d61eb25..624d9083 100755 --- a/playbooks/roles/slurm/templates/slurm.conf.j2 +++ b/playbooks/roles/slurm/templates/slurm.conf.j2 @@ -1,5 +1,5 @@ -{% set bastion = hostvars[groups['bastion'][0]]['ansible_fqdn'].split('.') %} -SlurmctldHost={{ bastion[0] }} +{% set controller = hostvars[groups['controller'][0]]['ansible_fqdn'].split('.') %} +SlurmctldHost={{ controller[0] }} {% if (groups['slurm_backup']| length ) > 0 %} SlurmctldHost={{ hostvars[groups['slurm_backup'][0]]['ansible_fqdn'].split('.')[0] }} {% endif %} @@ -31,7 +31,7 @@ GresTypes=gpu SchedulerType=sched/backfill SelectType=select/cons_tres SelectTypeParameters=CR_Core -AccountingStorageHost={{ bastion[0] }} +AccountingStorageHost={{ controller[0] }} AccountingStorageType=accounting_storage/slurmdbd AccountingStoreFlags=job_comment ClusterName=cluster diff --git a/playbooks/roles/slurm/templates/systemd/slurmd.service b/playbooks/roles/slurm/templates/systemd/slurmd.service index 7d4f3a2e..534afe2c 100644 --- a/playbooks/roles/slurm/templates/systemd/slurmd.service +++ b/playbooks/roles/slurm/templates/systemd/slurmd.service @@ -6,7 +6,7 @@ Documentation=man:slurmd(8) [Service] Type=forking EnvironmentFile=-/etc/default/slurm -ExecStart={{slurm_exec}}/sbin/slurmd --conf-server {{ hostvars[groups['bastion'][0]]['ansible_fqdn'].split('.')[0] }}{% if (groups['slurm_backup']| length ) > 0 %},{{ hostvars[groups['slurm_backup'][0]]['ansible_fqdn'].split('.')[0] }}{% endif %} $SLURMD_OPTIONS +ExecStart={{slurm_exec}}/sbin/slurmd --conf-server {{ hostvars[groups['controller'][0]]['ansible_fqdn'].split('.')[0] }}{% if (groups['slurm_backup']| length ) > 0 %},{{ hostvars[groups['slurm_backup'][0]]['ansible_fqdn'].split('.')[0] }}{% endif %} $SLURMD_OPTIONS ExecReload=/bin/kill -HUP $MAINPID PIDFile=/run/slurmd.pid KillMode=process diff --git a/playbooks/roles/slurm/templates/systemd/slurmd.service.d/unit.conf.j2 b/playbooks/roles/slurm/templates/systemd/slurmd.service.d/unit.conf.j2 index 0fc9a3ba..7f4faf67 100755 --- a/playbooks/roles/slurm/templates/systemd/slurmd.service.d/unit.conf.j2 +++ b/playbooks/roles/slurm/templates/systemd/slurmd.service.d/unit.conf.j2 @@ -5,5 +5,5 @@ Requires=munge.service Restart=always {% if ansible_os_family == 'RedHat' %} ExecStart= -ExecStart={{slurm_exec}}/sbin/slurmd --conf-server {{ hostvars[groups['bastion'][0]]['ansible_fqdn'].split('.')[0] }}{% if (groups['slurm_backup']| length ) > 0 %},{{ hostvars[groups['slurm_backup'][0]]['ansible_fqdn'].split('.')[0] }}{% endif %} -D $SLURMD_OPTIONS +ExecStart={{slurm_exec}}/sbin/slurmd --conf-server {{ hostvars[groups['controller'][0]]['ansible_fqdn'].split('.')[0] }}{% if (groups['slurm_backup']| length ) > 0 %},{{ hostvars[groups['slurm_backup'][0]]['ansible_fqdn'].split('.')[0] }}{% endif %} -D $SLURMD_OPTIONS {% endif %} \ No newline at end of file diff --git a/playbooks/roles/ssh/tasks/common.yml b/playbooks/roles/ssh/tasks/common.yml index 41872c6d..2975fafe 100644 --- a/playbooks/roles/ssh/tasks/common.yml +++ b/playbooks/roles/ssh/tasks/common.yml @@ -10,7 +10,7 @@ - name: Install private ssh key on all nodes copy: dest: "/home/{{ ansible_user }}/.ssh/id_rsa" - src: "/home/{{ bastion_username }}/.ssh/{{ item }}" + src: "/home/{{ controller_username }}/.ssh/{{ item }}" owner: "{{ ansible_user }}" group: "{{ ansible_user }}" mode: '0600' @@ -20,7 +20,7 @@ - name: Install public ssh key on all nodes copy: dest: "/home/{{ ansible_user }}/.ssh/id_rsa.pub" - src: "/home/{{ bastion_username }}/.ssh/{{ item }}" + src: "/home/{{ controller_username }}/.ssh/{{ item }}" owner: "{{ ansible_user }}" group: "{{ ansible_user }}" mode: '0644' diff --git a/playbooks/roles/ssl/defaults/main.yml b/playbooks/roles/ssl/defaults/main.yml index 7bfd86fa..1ce44e97 100644 --- a/playbooks/roles/ssl/defaults/main.yml +++ b/playbooks/roles/ssl/defaults/main.yml @@ -4,7 +4,7 @@ ssl_cert_country: 'US' ssl_cert_locality: 'Seattle' ssl_cert_organization: 'Oracle Cloud' ssl_cert_state: 'WA' -ssl_cert_altname: 'bastion.cluster' +ssl_cert_altname: 'controller.cluster' ssl_cert_days: '3650' diff --git a/playbooks/roles/ssl/tasks/debian.yml b/playbooks/roles/ssl/tasks/debian.yml index c9f349c7..6d6fac36 100644 --- a/playbooks/roles/ssl/tasks/debian.yml +++ b/playbooks/roles/ssl/tasks/debian.yml @@ -41,7 +41,7 @@ dest: '{{ ssl_cert_path }}/san.conf' mode: '0660' - - name: Create a certificate request for bastion + - name: Create a certificate request for controller command: > openssl req -new -nodes -sha512 -subj '/C={{ ssl_cert_country }}/ST={{ ssl_cert_state }}/L={{ ssl_cert_locality }}/O={{ ssl_cert_organization }}/CN={{ ansible_fqdn }}' diff --git a/playbooks/roles/ssl/tasks/el.yml b/playbooks/roles/ssl/tasks/el.yml index 66c05cbc..7b0ddc8a 100644 --- a/playbooks/roles/ssl/tasks/el.yml +++ b/playbooks/roles/ssl/tasks/el.yml @@ -41,7 +41,7 @@ dest: '{{ ssl_cert_path }}/san.conf' mode: '0660' - - name: Create a certificate request for bastion + - name: Create a certificate request for controller command: > openssl req -new -nodes -sha512 -subj '/C={{ ssl_cert_country }}/ST={{ ssl_cert_state }}/L={{ ssl_cert_locality }}/O={{ ssl_cert_organization }}/CN={{ ansible_fqdn }}' diff --git a/playbooks/roles/sssd/templates/sssd.conf.j2 b/playbooks/roles/sssd/templates/sssd.conf.j2 index 928b9986..9f3104bd 100644 --- a/playbooks/roles/sssd/templates/sssd.conf.j2 +++ b/playbooks/roles/sssd/templates/sssd.conf.j2 @@ -20,7 +20,7 @@ access_provider = ldap chpass_provider = ldap cache_credentials = true entry_cache_timeout = 600 -ldap_uri = ldaps://{{ hostvars[groups['bastion'][0]]['ansible_fqdn'] }} +ldap_uri = ldaps://{{ hostvars[groups['controller'][0]]['ansible_fqdn'] }} ldap_search_base = dc=local ldap_network_timeout = 30 ldap_access_order = expire diff --git a/playbooks/roles/sssd/templates/sssd_ubuntu.conf.j2 b/playbooks/roles/sssd/templates/sssd_ubuntu.conf.j2 index 10a81eb7..23918c02 100644 --- a/playbooks/roles/sssd/templates/sssd_ubuntu.conf.j2 +++ b/playbooks/roles/sssd/templates/sssd_ubuntu.conf.j2 @@ -10,7 +10,7 @@ access_provider = ldap chpass_provider = ldap cache_credentials = true entry_cache_timeout = 600 -ldap_uri = ldaps://{{ hostvars[groups['bastion'][0]]['ansible_fqdn'] }} +ldap_uri = ldaps://{{ hostvars[groups['controller'][0]]['ansible_fqdn'] }} ldap_search_base = dc=local ldap_network_timeout = 30 ldap_access_order = expire diff --git a/playbooks/roles/telegraf/tasks/common.yml b/playbooks/roles/telegraf/tasks/common.yml index e0904f7b..a1e079bf 100644 --- a/playbooks/roles/telegraf/tasks/common.yml +++ b/playbooks/roles/telegraf/tasks/common.yml @@ -1,10 +1,10 @@ --- - name: Create database - shell: "python3 -c \"import influxdb; influxdb.InfluxDBClient(host='{{ hostvars[groups['bastion'][0]]['ansible_fqdn'] }}', port=8086).create_database('telegraph')\"" + shell: "python3 -c \"import influxdb; influxdb.InfluxDBClient(host='{{ hostvars[groups['controller'][0]]['ansible_fqdn'] }}', port=8086).create_database('telegraph')\"" #- name: Create database # influxdb_database: -# hostname: "{{ hostvars[groups['bastion'][0]]['ansible_fqdn'] }}" +# hostname: "{{ hostvars[groups['controller'][0]]['ansible_fqdn'] }}" # database_name: "telegraf" # run_once: true diff --git a/playbooks/roles/telegraf/templates/influxdb.conf.j2 b/playbooks/roles/telegraf/templates/influxdb.conf.j2 index 7559a0ed..cb092eaf 100755 --- a/playbooks/roles/telegraf/templates/influxdb.conf.j2 +++ b/playbooks/roles/telegraf/templates/influxdb.conf.j2 @@ -1,2 +1,2 @@ [[outputs.influxdb]] - urls = ["http://{{ hostvars[groups['bastion'][0]]['ansible_fqdn'] }}:8086"] + urls = ["http://{{ hostvars[groups['controller'][0]]['ansible_fqdn'] }}:8086"] diff --git a/playbooks/site.yml b/playbooks/site.yml index 3bb6f837..056e43ef 100644 --- a/playbooks/site.yml +++ b/playbooks/site.yml @@ -62,7 +62,7 @@ - include_role: name: nvidia_peermem -- hosts: bastion +- hosts: controller become: true vars: export_path: "/home" @@ -73,7 +73,7 @@ name: nfs-server when: home_nfs|bool and (not home_fss|bool) -- hosts: bastion +- hosts: controller become: true vars: tmp_home: "/tmp/home_tmp/" @@ -92,7 +92,7 @@ name: fss-home when: add_nfs|bool and home_fss|bool -- hosts: bastion, slurm_backup, login +- hosts: controller, slurm_backup, login become: true tasks: - include_role: @@ -108,7 +108,7 @@ name: passwords -- hosts: bastion +- hosts: controller become: true vars: @@ -124,7 +124,7 @@ when: ldap|default(true)|bool # configure if instance_principal is False -- hosts: bastion +- hosts: controller become: true tasks: - include_role: @@ -143,7 +143,7 @@ name: nfs-client vars: local_path: "/home" - export_host: "{{ hostvars[groups['bastion'][0]]['ansible_default_ipv4']['address'] }}" + export_host: "{{ hostvars[groups['controller'][0]]['ansible_default_ipv4']['address'] }}" export_path: "/home" options: "defaults,noatime,bg,timeo=100,ac,actimeo=120,nocto,rsize=1048576,wsize=1048576,nolock,local_lock={{ lock }},mountproto=tcp,sec=sys,_netdev" lock: "all" @@ -158,17 +158,17 @@ lock: "none" when: home_nfs|bool and home_fss|bool -- hosts: bastion +- hosts: controller become: true vars: export_path: "{{ cluster_nfs_path }}" export_name: "cluster" local_path: "/export/cluster" - iscsi_ip: "{{ bastion_mount_ip }}" + iscsi_ip: "{{ controller_mount_ip }}" tasks: - include_role: name: iscsi - when: bastion_block|default(false)|bool + when: controller_block|default(false)|bool - include_role: name: nfs-server when: cluster_nfs|default(true)|bool @@ -178,12 +178,12 @@ - hosts: slurm_backup become: true vars: - iscsi_ip: "{{ bastion_mount_ip }}" + iscsi_ip: "{{ controller_mount_ip }}" local_path: "/mnt/nfs_backup" tasks: - include_role: name: iscsi - when: bastion_block|default(false)|bool + when: controller_block|default(false)|bool - hosts: login become: true @@ -218,7 +218,7 @@ name: nfs-client vars: local_path: "{{ cluster_nfs_path }}" - export_host: "{{ hostvars[groups['bastion'][0]]['ansible_default_ipv4']['address'] }}" + export_host: "{{ hostvars[groups['controller'][0]]['ansible_default_ipv4']['address'] }}" options: "defaults,noatime,bg,timeo=100,ac,actimeo=120,nocto,rsize=1048576,wsize=1048576,nolock,local_lock={{ lock }},mountproto=tcp,sec=sys,_netdev" export_path: "/export/cluster" lock: "all" @@ -259,7 +259,7 @@ name: hyperthreading when: not hyperthreading|default(false)|bool -- hosts: bastion, slurm_backup +- hosts: controller, slurm_backup become: true tasks: - include_role: @@ -274,7 +274,7 @@ name: telegraf when: monitoring|default(false)|bool -- hosts: bastion +- hosts: controller tasks: - include_role: name: grafana diff --git a/playbooks/slurm_config.yml b/playbooks/slurm_config.yml index bb3f6995..dce70f01 100755 --- a/playbooks/slurm_config.yml +++ b/playbooks/slurm_config.yml @@ -1,4 +1,4 @@ -- hosts: bastion,slurm_backup,compute,login +- hosts: controller,slurm_backup,compute,login gather_facts: true vars: destroy: false diff --git a/samples/NCCL_readme b/samples/NCCL_readme index 9279fd69..276df6e1 100644 --- a/samples/NCCL_readme +++ b/samples/NCCL_readme @@ -4,7 +4,7 @@ chmod 775 /opt/oci-hpc/samples/prep_sample_files.sh SSH to one of the compute nodes and run: ~/compile.sh -From the bastion, you can edit the third line of /home/opc/nccl_run_allreduce.sbatch with the number of nodes that you would like to test on: +From the controller, you can edit the third line of /home/opc/nccl_run_allreduce.sbatch with the number of nodes that you would like to test on: sbatch /home/opc/nccl_run_allreduce.sbatch Look at the last line of the log for bandwidth. diff --git a/samples/nfs/README.txt b/samples/nfs/README.txt index f08d289e..beec4742 100644 --- a/samples/nfs/README.txt +++ b/samples/nfs/README.txt @@ -2,7 +2,7 @@ Problem: When node running NFS needs to be terminated due to H/W failure, site.yml playbook fails, sudo umount /nfs/scratch hangs. Solution: -1. Manually change the ansible inventory file (/etc/ansible/hosts) on bastion. You will need to use sudo. +1. Manually change the ansible inventory file (/etc/ansible/hosts) on controller. You will need to use sudo. a. To replace the [nfs] group hostname with another node of the cluster to act as NFS server. Example: ansible_user=opc role=nfs b. If the node that was deleted is still there in [compute_configured] group, then remove it. diff --git a/samples/open-ldap/add-ldap-users.yml b/samples/open-ldap/add-ldap-users.yml index 73d15697..cb8da5eb 100644 --- a/samples/open-ldap/add-ldap-users.yml +++ b/samples/open-ldap/add-ldap-users.yml @@ -1,5 +1,5 @@ --- -- hosts: bastion +- hosts: controller become: true #vars: #ansible_remote_tmp: /tmp/ansible_remote_tmp diff --git a/schema.yaml b/schema.yaml index 037e685b..aec3b4b3 100755 --- a/schema.yaml +++ b/schema.yaml @@ -12,8 +12,8 @@ source: locale: "en" outputs: - bastion: - title: "Bastion Instance Public IP" + controller: + title: "controller Instance Public IP" type: copyableString visible: true @@ -27,15 +27,15 @@ variableGroups: - ${ldap} - title: "Headnode options" variables: - - ${bastion_ad} - - ${bastion_shape} - - ${bastion_ocpus} - - ${bastion_ocpus_denseIO_flex} - - ${bastion_custom_memory} - - ${bastion_memory} - - ${bastion_boot_volume_size} - - ${bastion_boot_volume_backup} - - ${bastion_object_storage_par} + - ${controller_ad} + - ${controller_shape} + - ${controller_ocpus} + - ${controller_ocpus_denseIO_flex} + - ${controller_custom_memory} + - ${controller_memory} + - ${controller_boot_volume_size} + - ${controller_boot_volume_backup} + - ${controller_object_storage_par} - title: "Compute node options" variables: - ${use_multiple_ads} @@ -115,15 +115,15 @@ variableGroups: - ${nfs_options} - ${fss_compartment} - ${fss_ad} - - title: "Advanced bastion options" + - title: "Advanced controller options" variables: - - ${use_marketplace_image_bastion} - - ${marketplace_listing_bastion} - - ${unsupported_bastion} - - ${bastion_image_compartment} - - ${custom_bastion_image} - - ${unsupported_bastion_image} - - ${bastion_username} + - ${use_marketplace_image_controller} + - ${marketplace_listing_controller} + - ${unsupported_controller} + - ${controller_image_compartment} + - ${custom_controller_image} + - ${unsupported_controller_image} + - ${controller_username} - title: "Advanced storage options" variables: - ${use_advanced} @@ -131,9 +131,9 @@ variableGroups: - ${home_fss} - ${use_cluster_nfs} - ${cluster_nfs_path} - - ${bastion_block} - - ${bastion_block_volume_size} - - ${bastion_block_volume_performance} + - ${controller_block} + - ${controller_block_volume_size} + - ${controller_block_volume_performance} - ${use_scratch_nfs} - ${scratch_nfs_path} - ${scratch_nfs_type_cluster} @@ -186,10 +186,10 @@ variableGroups: - ${ssh_cidr} - ${marketplace_source_images} - ${marketplace_version_id} - - ${bastion_boot_volume_backup_period} - - ${bastion_boot_volume_backup_retention_seconds} - - ${bastion_boot_volume_backup_time_zone} - - ${bastion_boot_volume_backup_type} + - ${controller_boot_volume_backup_period} + - ${controller_boot_volume_backup_retention_seconds} + - ${controller_boot_volume_backup_time_zone} + - ${controller_boot_volume_backup_type} visible: false - title: "Debug" variables: @@ -241,7 +241,7 @@ variables: default: false ldap: type: boolean - title: "Configure LDAP authentication from bastion" + title: "Configure LDAP authentication from controller" description: "When selected nodes will be configured to use LDAP authentication. User and group management can be performed using cluster commands." default: true cluster_name: @@ -253,22 +253,23 @@ variables: and: - ${use_custom_name} required: true - bastion_ad: + controller_ad: type: oci:identity:availabilitydomain:name dependsOn: compartmentId: ${targetCompartment} visible: complexExpression required: true - description: "Availability Domain for bastion host" + description: "Availability Domain for controller host" title: "Availability Domain" #default: ${ad} - bastion_shape: + controller_shape: + title: "Controller Shape" type: oci:core:instanceshape:name dependsOn: compartmentId: ${targetCompartment} required: true default: VM.Standard.E4.Flex - bastion_ocpus: + controller_ocpus: title: "Cores" type: integer description: Number of OCPU's for flex shape @@ -279,26 +280,26 @@ variables: and: - or: - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.E3.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.E4.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.E5.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Optimized3.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.A1.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard3.Flex" required: true - bastion_ocpus_denseIO_flex: + controller_ocpus_denseIO_flex: title: "Cores" type: enum description: Number of OCPU's for Dense IO flex shape @@ -311,11 +312,11 @@ variables: and: - or: - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.DenseIO.E4.Flex" required: true - bastion_custom_memory: + controller_custom_memory: title: Use custom memory size type: boolean default: false @@ -323,24 +324,24 @@ variables: and: - or: - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.E3.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Optimized3.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.E4.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.E5.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.A1.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard3.Flex" - bastion_memory: + controller_memory: title: Memory in GBS type: integer description: Number of memory for flex shape. Minimum 1GB per core. @@ -352,42 +353,42 @@ variables: - and: - or: - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.E3.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Optimized3.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.E4.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.E5.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard.A1.Flex" - eq: - - ${bastion_shape} + - ${controller_shape} - "VM.Standard3.Flex" - and: - - ${bastion_custom_memory} + - ${controller_custom_memory} required: true - bastion_object_storage_par: + controller_object_storage_par: title: Create Object Storage PAR description: "Create a PAR (i.e. Pre-Authenticated Request), so that user could use that PAR to upload monitoring metrics to Object Storage and share the URL with OCI service teams." type: boolean default: true - unsupported_bastion: + unsupported_controller: title: "Use unsupported image" - description: "Custom image ID for Bastion" + description: "Custom image ID for controller" type: boolean default: false visible: not: - - ${use_marketplace_image_bastion} + - ${use_marketplace_image_controller} - use_marketplace_image_bastion: + use_marketplace_image_controller: type: boolean title: "use marketplace image" description: "Use marketplace image, otherwise provide custom image OCID" @@ -395,7 +396,7 @@ variables: visible: true - marketplace_listing_bastion: + marketplace_listing_controller: type: enum title: "Image version" description: "Marketplace listing to use" @@ -406,71 +407,71 @@ variables: - "GPU_OL7" - "GPU_OL8" default: "HPC_OL7" - visible: ${use_marketplace_image_bastion} + visible: ${use_marketplace_image_controller} - bastion_username: - title: "Default username for bastion" - description: "Custom image ID for Bastion" + controller_username: + title: "Default username for controller" + description: "Custom image ID for controller" type: string default: "opc" required: true visible: true - unsupported_bastion_image: + unsupported_controller_image: title: "Image OCID" - description: "Custom image ID for compute nodes. Please note that only Oracle Linux 7 and Ubuntu 20.04 are supported as bastion image at this moment." + description: "Custom image ID for compute nodes. Please note that only Oracle Linux 7 and Ubuntu 20.04 are supported as controller image at this moment." type: string required: true - visible: ${unsupported_bastion} + visible: ${unsupported_controller} default: "image.ocid" - bastion_image_compartment: - title: "bastion image compartment" + controller_image_compartment: + title: "controller image compartment" type: oci:identity:compartment:id default: ${targetCompartment} visible: and: - not: - - ${unsupported_bastion} + - ${unsupported_controller} - not: - - ${use_marketplace_image_bastion} + - ${use_marketplace_image_controller} required: true - custom_bastion_image: - title: "Bastion Image ID" - description: "Custom image ID for bastion nodes. Please note that only Oracle Linux 7, 8 and Ubuntu 20.04 are supported as bastion image at this moment. " + custom_controller_image: + title: "controller Image ID" + description: "Custom image ID for controller nodes. Please note that only Oracle Linux 7, 8 and Ubuntu 20.04 are supported as controller image at this moment. " type: oci:core:image:id dependsOn: - compartmentId: ${bastion_image_compartment} + compartmentId: ${controller_image_compartment} visible: and: - not: - - ${unsupported_bastion} + - ${unsupported_controller} - not: - - ${use_marketplace_image_bastion} + - ${use_marketplace_image_controller} required: true - bastion_boot_volume_size: + controller_boot_volume_size: type: integer required: true minimum: 50 title: "Size of the boot volume in GB" default: 100 - bastion_boot_volume_backup: + controller_boot_volume_backup: type: boolean title: "Enable boot volume backup" description: "Schedule: Daily, Type: Incremental, Start Time: 00:00 Regional Time, Retention: 90 days." default: true - bastion_block: + controller_block: type: boolean title: Additional block volume for shared space visible: and: - ${use_advanced} default: false - bastion_block_volume_size: + controller_block_volume_size: required: true type: integer title: "Size of the additional volume in GB" @@ -478,9 +479,9 @@ variables: visible: and: - and: - - ${bastion_block} + - ${controller_block} - ${use_advanced} - bastion_block_volume_performance: + controller_block_volume_performance: type: enum title: "Block volume performance" required: true @@ -492,11 +493,11 @@ variables: visible: and: - and: - - ${bastion_block} + - ${controller_block} - ${use_advanced} home_nfs: type: boolean - title: "shared NFS /home from bastion. To use FSS, make sure you created one or added NFS mount information" + title: "shared NFS /home from controller. To use FSS, make sure you created one or added NFS mount information" visible: and: - ${use_advanced} @@ -515,7 +516,7 @@ variables: use_cluster_nfs: type: boolean - title: "shared NFS volume from bastion" + title: "shared NFS volume from controller" visible: and: - ${use_advanced} @@ -923,7 +924,7 @@ variables: private_deployment: type: boolean title: "Deploy Master Node without a public IP" - description: "Deploy with no Public IP for the master node. 'Master Node Subnet' must be a Private subnet. This will require the creation of a bastion service, VPN or FastConnect to connect via ssh to the master node" + description: "Deploy with no Public IP for the master node. 'Master Node Subnet' must be a Private subnet. This will require the creation of a controller service, VPN or FastConnect to connect via ssh to the master node" default: false use_existing_vcn: type: boolean @@ -1042,7 +1043,7 @@ variables: title: "Create a back-up Slurm Controller" default: false required: true - description: "Add a second master of the same shape as the bastion as a back-up controller node. We recommend using a FSS to save the state and share between masters" + description: "Add a second master of the same shape as the controller as a back-up controller node. We recommend using a FSS to save the state and share between masters" visible: ${slurm} pyxis: @@ -1120,7 +1121,7 @@ variables: inst_prin: type: boolean title: "Use Instance Principal instead of configuration file" - description: "You will need to set a dynamic group and policy to allow the bastion to authenticate. This will not be created automatically." + description: "You will need to set a dynamic group and policy to allow the controller to authenticate. This will not be created automatically." default: true api_user_key: @@ -1302,7 +1303,7 @@ variables: type: boolean title: "Create a Mysql Service" default: false - description: "false will use the bastion as mysqlDB" + description: "false will use the controller as mysqlDB" visible: and: - ${autoscaling} diff --git a/scripts/ib_write_bw.sh b/scripts/ib_write_bw.sh index 58138096..951afe84 100644 --- a/scripts/ib_write_bw.sh +++ b/scripts/ib_write_bw.sh @@ -248,7 +248,7 @@ scp /tmp/ib_client.sh $client:/tmp ssh $server "/tmp/ib_server.sh" & ssh $client "/tmp/ib_client.sh" -#Sync results to bastion +#Sync results to controller mkdir -p $logdir rsync -a opc@$client:$outdir $logdir diff --git a/scripts/max_nodes_partition.py b/scripts/max_nodes_partition.py index d903acc7..0f6a67e5 100644 --- a/scripts/max_nodes_partition.py +++ b/scripts/max_nodes_partition.py @@ -75,7 +75,7 @@ def getClusterNames(): del x[-1] cluster_list = [] for cluster in x: - if (cluster == "BASTION"): + if (cluster == "CONTROLLER"): continue else: cluster_list.append(cluster) diff --git a/scripts/validation.py b/scripts/validation.py index d3c56174..f287c9a1 100644 --- a/scripts/validation.py +++ b/scripts/validation.py @@ -180,7 +180,7 @@ def getResizeNodes(args, metadata, cluster_names, mode): cluster_node_set = set() for i in range(len(x)): if str in x[i]: - permanent_cluster = metadata['displayName'].replace('-bastion','') + permanent_cluster = metadata['displayName'].replace('-controller','') if permanent_cluster in cluster_names: return cluster_names, resize_cluster_node_dict else: @@ -334,9 +334,9 @@ def etcHostsSame(nodes, path): stdout,stderr = out.communicate() x = stdout.split("\n") del x[-1] - bastion_md5 = x[0].replace('"','') + controller_md5 = x[0].replace('"','') md5_set = set() - md5_set.add(bastion_md5) + md5_set.add(controller_md5) out = subprocess.Popen(["pdsh -w "+nodes+" 'linecount=`cat /etc/hosts | wc -l ` ; lines=$((linecount-3)) ; tail -n $lines /etc/hosts | md5sum'"],stdout=subprocess.PIPE, stderr=subprocess.STDOUT,shell=True,universal_newlines=True) stdout,stderr = out.communicate() x = stdout.split("\n") @@ -354,7 +354,7 @@ def etcHostsSame(nodes, path): continue else: md5 = split_str[1].lstrip() - if md5 != bastion_md5: + if md5 != controller_md5: if path is None: path = createDir() changeOwner(path) @@ -363,9 +363,9 @@ def etcHostsSame(nodes, path): f.close() md5_set.add(md5) if len(md5_set) > 1: - print("/etc/hosts on bastion and nodes is different") + print("/etc/hosts on controller and nodes is different") else: - print("/etc/hosts is same on bastion and all nodes that are ssh-able") + print("/etc/hosts is same on controller and all nodes that are ssh-able") return path @@ -390,7 +390,7 @@ def ociCommand(metadata, cluster_names): def inventoryNodes(metadata, cluster_names): inventory_node_cluster_dict = {} - permanent_cluster = metadata['displayName'].replace('-bastion','') + permanent_cluster = metadata['displayName'].replace('-controller','') for cluster in cluster_names: if cluster == permanent_cluster: inventory = "/etc/ansible/hosts" @@ -532,7 +532,7 @@ def runChecks(args, type, name, hostFileWritten, resize_node_cluster_dict, metad number of nodes and/or pcie check and/or gpu throttle check.") parser.add_argument('-p', '--pcie', help = "Runs PCIe bandwidth check") parser.add_argument('-g', '--gpu_throttle', help = "Performs GPU throttle check") -parser.add_argument('-e', '--etc_hosts', help = "Performs md5 sum check on all hosts and checks if it matches with the bastion") +parser.add_argument('-e', '--etc_hosts', help = "Performs md5 sum check on all hosts and checks if it matches with the controller") args = parser.parse_args() diff --git a/slurm_ha.tf b/slurm_ha.tf index bc3d04cd..48ccc2a3 100644 --- a/slurm_ha.tf +++ b/slurm_ha.tf @@ -1,7 +1,7 @@ resource "oci_core_volume_attachment" "backup_volume_attachment" { - count = var.bastion_block && var.slurm_ha ? 1 : 0 + count = var.controller_block && var.slurm_ha ? 1 : 0 attachment_type = "iscsi" - volume_id = oci_core_volume.bastion_volume[0].id + volume_id = oci_core_volume.controller_volume[0].id instance_id = oci_core_instance.backup[0].id display_name = "${local.cluster_name}-backup-volume-attachment" device = "/dev/oracleoci/oraclevdb" @@ -11,15 +11,15 @@ resource "oci_core_volume_attachment" "backup_volume_attachment" { resource "oci_core_instance" "backup" { count = var.slurm_ha ? 1 : 0 depends_on = [oci_core_subnet.public-subnet] - availability_domain = var.bastion_ad + availability_domain = var.controller_ad compartment_id = var.targetCompartment - shape = var.bastion_shape + shape = var.controller_shape dynamic "shape_config" { - for_each = local.is_bastion_flex_shape + for_each = local.is_controller_flex_shape content { ocpus = shape_config.value - memory_in_gbs = var.bastion_custom_memory ? var.bastion_memory : 16 * shape_config.value + memory_in_gbs = var.controller_custom_memory ? var.controller_memory : 16 * shape_config.value } } agent_config { @@ -34,18 +34,18 @@ resource "oci_core_instance" "backup" { metadata = { ssh_authorized_keys = "${var.ssh_key}\n${tls_private_key.ssh.public_key_openssh}" - user_data = base64encode(data.template_file.bastion_config.rendered) + user_data = base64encode(data.template_file.controller_config.rendered) } source_details { -// source_id = var.use_standard_image ? data.oci_core_images.linux.images.0.id : local.custom_bastion_image_ocid - source_id = local.bastion_image - boot_volume_size_in_gbs = var.bastion_boot_volume_size +// source_id = var.use_standard_image ? data.oci_core_images.linux.images.0.id : local.custom_controller_image_ocid + source_id = local.controller_image + boot_volume_size_in_gbs = var.controller_boot_volume_size source_type = "image" } create_vnic_details { - subnet_id = local.bastion_subnet_id - assign_public_ip = local.bastion_bool_ip + subnet_id = local.controller_subnet_id + assign_public_ip = local.controller_bool_ip } } @@ -60,14 +60,14 @@ resource "null_resource" "backup" { inline = [ "#!/bin/bash", "sudo mkdir -p /opt/oci-hpc", - "sudo chown ${var.bastion_username}:${var.bastion_username} /opt/oci-hpc/", + "sudo chown ${var.controller_username}:${var.controller_username} /opt/oci-hpc/", "mkdir -p /opt/oci-hpc/bin", "mkdir -p /opt/oci-hpc/playbooks" ] connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -77,7 +77,7 @@ resource "null_resource" "backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -88,7 +88,7 @@ resource "null_resource" "backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -99,7 +99,7 @@ resource "null_resource" "backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -110,7 +110,7 @@ resource "null_resource" "backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -120,7 +120,7 @@ resource "null_resource" "backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -130,7 +130,7 @@ resource "null_resource" "backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -142,18 +142,18 @@ resource "null_resource" "backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } provisioner "file" { content = tls_private_key.ssh.private_key_pem - destination = "/home/${var.bastion_username}/.ssh/cluster.key" + destination = "/home/${var.controller_username}/.ssh/cluster.key" connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -162,15 +162,15 @@ resource "null_resource" "backup" { provisioner "remote-exec" { inline = [ "#!/bin/bash", - "chmod 600 /home/${var.bastion_username}/.ssh/cluster.key", - "cp /home/${var.bastion_username}/.ssh/cluster.key /home/${var.bastion_username}/.ssh/id_rsa", + "chmod 600 /home/${var.controller_username}/.ssh/cluster.key", + "cp /home/${var.controller_username}/.ssh/cluster.key /home/${var.controller_username}/.ssh/id_rsa", "chmod a+x /opt/oci-hpc/bin/*.sh", - "timeout --foreground 60m /opt/oci-hpc/bin/bastion.sh" + "timeout --foreground 60m /opt/oci-hpc/bin/controller.sh" ] connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -184,8 +184,8 @@ resource "null_resource" "cluster_backup" { provisioner "file" { content = templatefile("${path.module}/inventory.tpl", { - bastion_name = oci_core_instance.bastion.display_name, - bastion_ip = oci_core_instance.bastion.private_ip, + controller_name = oci_core_instance.controller.display_name, + controller_ip = oci_core_instance.controller.private_ip, backup_name = var.slurm_ha ? oci_core_instance.backup[0].display_name : "", backup_ip = var.slurm_ha ? oci_core_instance.backup[0].private_ip: "", login_name = var.login_node ? oci_core_instance.login[0].display_name : "", @@ -218,10 +218,10 @@ resource "null_resource" "cluster_backup" { rack_aware = var.rack_aware, spack = var.spack, ldap = var.ldap, - bastion_block = var.bastion_block, + controller_block = var.controller_block, login_block = var.login_block, scratch_nfs_type = local.scratch_nfs_type, - bastion_mount_ip = local.bastion_mount_ip, + controller_mount_ip = local.controller_mount_ip, login_mount_ip = local.login_mount_ip, cluster_mount_ip = local.mount_ip, autoscaling = var.autoscaling, @@ -231,7 +231,7 @@ resource "null_resource" "cluster_backup" { queue=var.queue, monitoring = var.monitoring, hyperthreading = var.hyperthreading, - bastion_username = var.bastion_username, + controller_username = var.controller_username, compute_username = var.compute_username, autoscaling_monitoring = var.autoscaling_monitoring, autoscaling_mysql_service = var.autoscaling_mysql_service, @@ -256,7 +256,7 @@ resource "null_resource" "cluster_backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -268,7 +268,7 @@ resource "null_resource" "cluster_backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -285,7 +285,7 @@ resource "null_resource" "cluster_backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -316,22 +316,22 @@ resource "null_resource" "cluster_backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } provisioner "file" { content = templatefile("${path.module}/conf/variables.tpl", { - bastion_name = oci_core_instance.bastion.display_name, - bastion_ip = oci_core_instance.bastion.private_ip, + controller_name = oci_core_instance.controller.display_name, + controller_ip = oci_core_instance.controller.private_ip, backup_name = var.slurm_ha ? oci_core_instance.backup[0].display_name : "", backup_ip = var.slurm_ha ? oci_core_instance.backup[0].private_ip: "", login_name = var.login_node ? oci_core_instance.login[0].display_name : "", login_ip = var.login_node ? oci_core_instance.login[0].private_ip: "", compute = var.node_count > 0 ? zipmap(local.cluster_instances_names, local.cluster_instances_ips) : zipmap([],[]) public_subnet = data.oci_core_subnet.public_subnet.cidr_block, - public_subnet_id = local.bastion_subnet_id, + public_subnet_id = local.controller_subnet_id, private_subnet = data.oci_core_subnet.private_subnet.cidr_block, private_subnet_id = local.subnet_id, rdma_subnet = var.rdma_subnet, @@ -344,15 +344,15 @@ resource "null_resource" "cluster_backup" { rack_aware = var.rack_aware, spack = var.spack, ldap = var.ldap, - bastion_block = var.bastion_block, + controller_block = var.controller_block, login_block = var.login_block, scratch_nfs_type = local.scratch_nfs_type, - bastion_mount_ip = local.bastion_mount_ip, + controller_mount_ip = local.controller_mount_ip, login_mount_ip = local.login_mount_ip, cluster_mount_ip = local.mount_ip, scratch_nfs_type_cluster = var.scratch_nfs_type_cluster, scratch_nfs_type_pool = var.scratch_nfs_type_pool, - bastion_block_volume_performance = var.bastion_block_volume_performance, + controller_block_volume_performance = var.controller_block_volume_performance, region = var.region, tenancy_ocid = var.tenancy_ocid, vcn_subnet = var.vcn_subnet, @@ -384,7 +384,7 @@ resource "null_resource" "cluster_backup" { privilege_group_name = var.privilege_group_name, latency_check = var.latency_check, private_deployment = var.private_deployment, - bastion_username = var.bastion_username, + controller_username = var.controller_username, compute_username = var.compute_username, use_multiple_ads = var.use_multiple_ads, use_compute_agent = var.use_compute_agent @@ -394,7 +394,7 @@ resource "null_resource" "cluster_backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -406,7 +406,7 @@ resource "null_resource" "cluster_backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } @@ -422,7 +422,7 @@ resource "null_resource" "cluster_backup" { connection { host = local.host_backup type = "ssh" - user = var.bastion_username + user = var.controller_username private_key = tls_private_key.ssh.private_key_pem } } diff --git a/user_data.tf b/user_data.tf index 37298614..7249c8a8 100755 --- a/user_data.tf +++ b/user_data.tf @@ -1,5 +1,5 @@ -data "template_file" "bastion_config" { - template = file("config.bastion") +data "template_file" "controller_config" { + template = file("config.controller") vars = { key = tls_private_key.ssh.private_key_pem } diff --git a/variables.tf b/variables.tf index 20c4d9ca..e6c2807d 100755 --- a/variables.tf +++ b/variables.tf @@ -13,10 +13,10 @@ variable "compute_cluster_id" { default = "" } variable "compute_cluster_start_index" { default = 0 } variable "use_custom_name" { default = false } variable "cluster_name" { default = "" } -variable "bastion_ad" {} -variable "bastion_shape" { default = "VM.Standard2.4" } -variable "bastion_object_storage_par" { default = true } -variable "custom_bastion_image" { +variable "controller_ad" {} +variable "controller_shape" { default = "VM.Standard2.4" } +variable "controller_object_storage_par" { default = true } +variable "custom_controller_image" { type = string default = "image.ocid" } @@ -24,12 +24,12 @@ variable "custom_login_image" { type = string default = "image.ocid" } -variable "bastion_boot_volume_size" {} -variable "bastion_boot_volume_backup" {} -variable "bastion_boot_volume_backup_type" {default = "INCREMENTAL"} -variable "bastion_boot_volume_backup_period" {default = "ONE_DAY"} -variable "bastion_boot_volume_backup_retention_seconds" {default = "7776000"} -variable "bastion_boot_volume_backup_time_zone" {default = "REGIONAL_DATA_CENTER_TIME"} +variable "controller_boot_volume_size" {} +variable "controller_boot_volume_backup" {} +variable "controller_boot_volume_backup_type" {default = "INCREMENTAL"} +variable "controller_boot_volume_backup_period" {default = "ONE_DAY"} +variable "controller_boot_volume_backup_retention_seconds" {default = "7776000"} +variable "controller_boot_volume_backup_time_zone" {default = "REGIONAL_DATA_CENTER_TIME"} variable "cluster_network_shape" { default = "BM.HPC2.36" } variable "instance_pool_shape" { default = "VM.Standard2.4" } variable "node_count" { default = 2 } @@ -38,7 +38,7 @@ variable "use_marketplace_image" { default = true} variable "image" { default = "ocid1.image.oc1..aaaaaaaa5yxem7wzie34hi5km4qm2t754tsfxrjuefyjivebrxjad4jcj5oa" } variable "image_ocid" { default = "ocid1.image.oc1..aaaaaaaa5yxem7wzie34hi5km4qm2t754tsfxrjuefyjivebrxjad4jcj5oa" } variable "use_compute_agent" { default = false } -variable "unsupported_bastion_image" { default = "" } +variable "unsupported_controller_image" { default = "" } variable "unsupported_login_image" { default = "" } variable "use_cluster_nfs" { default = true} variable "use_scratch_nfs" { default = true } @@ -65,16 +65,16 @@ variable "slurm_nfs" { default = false } variable "rack_aware" { default = false } variable "ldap" { default = true } variable "spack" { default = false } -variable "bastion_ocpus" { default = 2} -variable "bastion_ocpus_denseIO_flex" { default = 8} +variable "controller_ocpus" { default = 2} +variable "controller_ocpus_denseIO_flex" { default = 8} variable "instance_pool_ocpus" { default = 2} variable "instance_pool_ocpus_denseIO_flex" { default = 8} variable "instance_pool_memory" { default = 16 } variable "instance_pool_custom_memory" { default = false } variable "login_ocpus" { default = 2} variable "login_ocpus_denseIO_flex" { default = 8} -variable "bastion_memory" { default = 16 } -variable "bastion_custom_memory" { default = false } +variable "controller_memory" { default = 16 } +variable "controller_custom_memory" { default = false } variable "login_memory" { default = 16 } variable "login_custom_memory" { default = false } variable "privilege_sudo" { default = true } @@ -107,7 +107,7 @@ variable "marketplace_listing_id_HPC" { variable "marketplace_listing_id_GPU" { default = "ocid1.appcataloglisting.oc1..aaaaaaaab2hkpxsglxfbzitiiqv6djxzj5q5soxotwdem2dd2kbifgk4p55q" } -variable "bastion_block_volume_performance" { +variable "controller_block_volume_performance" { /* Allowed values "0. Lower performance" @@ -119,11 +119,11 @@ default = "10. Balanced performance" } -variable "bastion_block" { +variable "controller_block" { default = false } -variable "bastion_block_volume_size" { +variable "controller_block_volume_size" { default = 1000 } @@ -183,11 +183,11 @@ variable "unsupported" { } variable "queue" {default = "compute"} -variable "unsupported_bastion" { +variable "unsupported_controller" { type=bool default = false } -variable "use_marketplace_image_bastion" { +variable "use_marketplace_image_controller" { type=bool default = true } @@ -195,7 +195,7 @@ variable "unsupported_login" { type=bool default = false } -variable "bastion_username" { +variable "controller_username" { type = string default = "opc" } @@ -250,7 +250,7 @@ variable "use_marketplace_image_login" { default = true} variable "marketplace_listing_login" { default = "HPC_OL7" } -variable "marketplace_listing_bastion" { +variable "marketplace_listing_controller" { default = "HPC_OL7" } \ No newline at end of file From 96841dc2be5d66534f2e18ae01d64df1079c0fab Mon Sep 17 00:00:00 2001 From: Dhvani Sheth Date: Thu, 11 Jan 2024 15:18:51 -0800 Subject: [PATCH 06/40] added nccl.conf for A100 shapes and H100 --- playbooks/new_nodes.yml | 3 ++ playbooks/resize_add.yml | 3 ++ playbooks/roles/nccl-conf/files/a100_b4.8 | 8 +++++ playbooks/roles/nccl-conf/files/bm.gpu4.8 | 8 +++++ playbooks/roles/nccl-conf/files/h100 | 16 ++++++++++ playbooks/roles/nccl-conf/tasks/main.yml | 36 +++++++++++++++++++++++ playbooks/site.yml | 3 ++ 7 files changed, 77 insertions(+) create mode 100644 playbooks/roles/nccl-conf/files/a100_b4.8 create mode 100644 playbooks/roles/nccl-conf/files/bm.gpu4.8 create mode 100644 playbooks/roles/nccl-conf/files/h100 create mode 100644 playbooks/roles/nccl-conf/tasks/main.yml diff --git a/playbooks/new_nodes.yml b/playbooks/new_nodes.yml index a5e68f90..c5ea7302 100755 --- a/playbooks/new_nodes.yml +++ b/playbooks/new_nodes.yml @@ -57,6 +57,9 @@ when: cluster_network|bool and not use_compute_agent|default(false)|bool - include_role: name: nvidia_peermem + - include_role: + name: nccl-conf + when: cluster_network|bool - hosts: controller,slurm_backup,login,compute become: true diff --git a/playbooks/resize_add.yml b/playbooks/resize_add.yml index 3d92190c..a513f4e9 100755 --- a/playbooks/resize_add.yml +++ b/playbooks/resize_add.yml @@ -55,6 +55,9 @@ when: cluster_network|bool and not use_compute_agent|default(false)|bool - include_role: name: nvidia_peermem + - include_role: + name: nccl-conf + when: cluster_network|bool - hosts: controller,slurm_backup,login,compute become: true diff --git a/playbooks/roles/nccl-conf/files/a100_b4.8 b/playbooks/roles/nccl-conf/files/a100_b4.8 new file mode 100644 index 00000000..f7bfd798 --- /dev/null +++ b/playbooks/roles/nccl-conf/files/a100_b4.8 @@ -0,0 +1,8 @@ +NCCL_DEBUG=WARN +NCCL_IGNORE_CPU_AFFINITY=1 +NCCL_IB_SL=0 +NCCL_IB_TC=41 +NCCL_IB_QPS_PER_CONNECTION=4 +NCCL_IB_GID_INDEX=3 +NCCL_ALGO=Ring +NCCL_IB_HCA="=mlx5_5,mlx5_6,mlx5_7,mlx5_8,mlx5_1,mlx5_2,mlx5_3,mlx5_4,mlx5_14,mlx5_15,mlx5_16,mlx5_17,mlx5_9,mlx5_10,mlx5_11,mlx5_12" \ No newline at end of file diff --git a/playbooks/roles/nccl-conf/files/bm.gpu4.8 b/playbooks/roles/nccl-conf/files/bm.gpu4.8 new file mode 100644 index 00000000..0fedee48 --- /dev/null +++ b/playbooks/roles/nccl-conf/files/bm.gpu4.8 @@ -0,0 +1,8 @@ +NCCL_DEBUG=WARN +NCCL_IGNORE_CPU_AFFINITY=1 +NCCL_IB_SL=0 +NCCL_IB_TC=41 +NCCL_IB_QPS_PER_CONNECTION=4 +NCCL_IB_GID_INDEX=3 +NCCL_ALGO=Ring +NCCL_IB_HCA="=mlx5_0,mlx5_2,mlx5_6,mlx5_8,mlx5_10,mlx5_12,mlx5_14,mlx5_16,mlx5_1,mlx5_3,mlx5_7,mlx5_9,mlx5_11,mlx5_13,mlx5_15,mlx5_17" \ No newline at end of file diff --git a/playbooks/roles/nccl-conf/files/h100 b/playbooks/roles/nccl-conf/files/h100 new file mode 100644 index 00000000..4fb8f75f --- /dev/null +++ b/playbooks/roles/nccl-conf/files/h100 @@ -0,0 +1,16 @@ +NCCL_CROSS_NIC=0 +NCCL_SOCKET_NTHREADS=16 +NCCL_DEBUG=WARN +NCCL_CUMEM_ENABLE=0 +NCCL_IB_SPLIT_DATA_ON_QPS=0 +NCCL_IB_QPS_PER_CONNECTION=16 +NCCL_IB_GID_INDEX=3 +NCCL_IB_TC=41 +NCCL_IB_SL=0 +NCCL_IB_TIMEOUT=22 +NCCL_NET_PLUGIN=none +NCCL_SOCKET_IFNAME=eth0 +NCCL_ALGO=auto +NCCL_IGNORE_CPU_AFFINITY=1 +NCCL_IB_HCA="=mlx5_0,mlx5_1,mlx5_3,mlx5_4,mlx5_5,mlx5_6,mlx5_7,mlx5_8,mlx5_9,mlx5_10,mlx5_12,mlx5_13,mlx5_14,mlx5_15,mlx5_16,mlx5_17" +NCCL_TOPO_FILE=/nfs/cluster/H100-topology.xml \ No newline at end of file diff --git a/playbooks/roles/nccl-conf/tasks/main.yml b/playbooks/roles/nccl-conf/tasks/main.yml new file mode 100644 index 00000000..4a4dba20 --- /dev/null +++ b/playbooks/roles/nccl-conf/tasks/main.yml @@ -0,0 +1,36 @@ +--- +# tasks file for nccl-conf +- name: Get the shape + shell: + cmd: "curl -sH \"Authorization: Bearer Oracle\" -L http://169.254.169.254/opc/v2/instance/ | jq '.shape'" + register: shape_nccl + +- name: copy nccl.conf for H100 + become: true + copy: + src: h100 + dest: /etc/nccl.conf + owner: root + group: root + mode: '0644' + when: shape_nccl.stdout == "BM.GPU.H100.8" + +- name: copy nccl.conf for BM.GPU.B4.8 and A100-v2.8 + become: true + copy: + src: a100_b4.8 + dest: /etc/nccl.conf + owner: root + group: root + mode: '0644' + when: shape_nccl.stdout == "BM.GPU.B4.8" or shape_nccl.stdout == "BM.GPU.A100-v2.8" + +- name: copy nccl.conf for BM.GPU4.8 + become: true + copy: + src: bm.gpu4.8 + dest: /etc/nccl.conf + owner: root + group: root + mode: '0644' + when: shape_nccl.stdout == "BM.GPU4.8" \ No newline at end of file diff --git a/playbooks/site.yml b/playbooks/site.yml index 056e43ef..c298fd4c 100644 --- a/playbooks/site.yml +++ b/playbooks/site.yml @@ -61,6 +61,9 @@ when: cluster_network|bool and not use_compute_agent|default(false)|bool - include_role: name: nvidia_peermem + - include_role: + name: nccl-conf + when: cluster_network|bool - hosts: controller become: true From 3706b48c8bb8b6dc6a18d4e1696ccf83a476296e Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Tue, 16 Jan 2024 11:59:43 -0700 Subject: [PATCH 07/40] Fix The n umber of drives for H100 --- playbooks/roles/localdisk/tasks/common.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/playbooks/roles/localdisk/tasks/common.yml b/playbooks/roles/localdisk/tasks/common.yml index 09ec1e5a..a012ba59 100755 --- a/playbooks/roles/localdisk/tasks/common.yml +++ b/playbooks/roles/localdisk/tasks/common.yml @@ -5,7 +5,7 @@ - name: Get the number of NVMe's set_fact: - nvme_count: "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list | length}}" + nvme_count: "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list | length}}" - name: Create a LVM? set_fact: @@ -18,7 +18,7 @@ state: present label: gpt with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list }}" + - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list }}" - name: create a filesystem filesystem: @@ -26,7 +26,7 @@ fstype: xfs opts: "-L locscratch{{item | replace('nvme','') | replace('n1','')}}" with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list }}" + - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list }}" when: not ( one_lv | bool ) - name: Mount local volume @@ -37,7 +37,7 @@ opts: defaults,noatime state: mounted with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list }}" + - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list }}" when: not ( one_lv | bool ) - name: "set permissions on {{ nvme_path_edited }}" @@ -50,7 +50,7 @@ group: "{{privilege_group_name}}" recurse: no with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list }}" + - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list }}" when: not ( one_lv | bool ) - name: Check for lvm devices @@ -61,7 +61,7 @@ - name: Create volume group lvg: vg: "vg_nvmes" - pvs: "{{['/dev/']|product(hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list)|map('join', '') | join(',')}}" + pvs: "{{['/dev/']|product(hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list)|map('join', '') | join(',')}}" - name: Create Logical volume lvol: From 617ce716f400da2838259393770a5924e96ad2e1 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Tue, 16 Jan 2024 13:48:35 -0700 Subject: [PATCH 08/40] Hardcode the OL version to mitigate the change on Hashicorp repo --- bin/controller.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/controller.sh b/bin/controller.sh index 70952302..f9c1c666 100644 --- a/bin/controller.sh +++ b/bin/controller.sh @@ -45,6 +45,7 @@ if [ $ID == "ol" ] || [ $ID == "centos" ] ; then sudo ln -s /usr/local/bin/ansible /bin/ansible fi sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo + sudo sed -i 's/$releasever/'"${vid}"'/g' /etc/yum.repos.d/hashicorp.repo sudo yum install -y terraform sudo python3 -m pip install -U pip sudo python3 -m pip install netaddr --upgrade From 70f1d416fcf1099865df7252bdd0dc07e95e6aa5 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Tue, 16 Jan 2024 13:48:35 -0700 Subject: [PATCH 09/40] Hardcode the OL version to mitigate the change on Hashicorp repo --- bin/bastion.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/bastion.sh b/bin/bastion.sh index 501cc559..a7bf37e6 100644 --- a/bin/bastion.sh +++ b/bin/bastion.sh @@ -45,6 +45,7 @@ if [ $ID == "ol" ] || [ $ID == "centos" ] ; then sudo ln -s /usr/local/bin/ansible /bin/ansible fi sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo + sudo sed -i 's/$releasever/'"${vid}"'/g' /etc/yum.repos.d/hashicorp.repo sudo yum install -y terraform sudo python3 -m pip install -U pip sudo python3 -m pip install netaddr --upgrade From 0b315ef7f7baf557cf40cc927fdec469eade6574 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 17 Jan 2024 16:42:21 -0700 Subject: [PATCH 10/40] Update for large number of NVMe's --- playbooks/roles/localdisk/tasks/common.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/playbooks/roles/localdisk/tasks/common.yml b/playbooks/roles/localdisk/tasks/common.yml index a012ba59..9b30f362 100755 --- a/playbooks/roles/localdisk/tasks/common.yml +++ b/playbooks/roles/localdisk/tasks/common.yml @@ -5,7 +5,7 @@ - name: Get the number of NVMe's set_fact: - nvme_count: "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list | length}}" + nvme_count: "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list | length}}" - name: Create a LVM? set_fact: @@ -18,7 +18,7 @@ state: present label: gpt with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list }}" + - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list }}" - name: create a filesystem filesystem: @@ -26,7 +26,7 @@ fstype: xfs opts: "-L locscratch{{item | replace('nvme','') | replace('n1','')}}" with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list }}" + - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list }}" when: not ( one_lv | bool ) - name: Mount local volume @@ -37,7 +37,7 @@ opts: defaults,noatime state: mounted with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list }}" + - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list }}" when: not ( one_lv | bool ) - name: "set permissions on {{ nvme_path_edited }}" @@ -50,7 +50,7 @@ group: "{{privilege_group_name}}" recurse: no with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list }}" + - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list }}" when: not ( one_lv | bool ) - name: Check for lvm devices @@ -61,7 +61,7 @@ - name: Create volume group lvg: vg: "vg_nvmes" - pvs: "{{['/dev/']|product(hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-32]n1') | list)|map('join', '') | join(',')}}" + pvs: "{{['/dev/']|product(hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list)|map('join', '') | join(',')}}" - name: Create Logical volume lvol: From 5e995abb6184cdb5eb57863c969744a37441757c Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 17 Jan 2024 17:12:33 -0700 Subject: [PATCH 11/40] Add DenseIO.E5 shapes --- playbooks/roles/slurm/templates/slurm.conf.j2 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/playbooks/roles/slurm/templates/slurm.conf.j2 b/playbooks/roles/slurm/templates/slurm.conf.j2 index 624d9083..3bb57372 100755 --- a/playbooks/roles/slurm/templates/slurm.conf.j2 +++ b/playbooks/roles/slurm/templates/slurm.conf.j2 @@ -113,6 +113,8 @@ NodeName={{partition.name}}-{{instance.instance_keyword}}-node-[1-{{size}}] Boar NodeName={{partition.name}}-{{instance.instance_keyword}}-node-[1-{{size}}] Boards=1 SocketsPerBoard=1 CoresPerSocket={{instance.instance_pool_ocpus}} ThreadsPerCore={{threadspercore}} State=FUTURE Features={% if instance.shape != instance.name%}{{ instance.shape }},{% endif %}{{ instance.name }} {% elif instance.shape == "VM.DenseIO.E4.Flex" %} NodeName={{partition.name}}-{{instance.instance_keyword}}-node-[1-{{size}}] Boards=1 SocketsPerBoard=1 CoresPerSocket={{instance.instance_pool_ocpus}} ThreadsPerCore={{threadspercore}} State=FUTURE Features={% if instance.shape != instance.name%}{{ instance.shape }},{% endif %}{{ instance.name }} +{% elif instance.shape == "VM.DenseIO.E5.Flex" %} +NodeName={{partition.name}}-{{instance.instance_keyword}}-node-[1-{{size}}] Boards=1 SocketsPerBoard=1 CoresPerSocket={{instance.instance_pool_ocpus}} ThreadsPerCore={{threadspercore}} State=FUTURE Features={% if instance.shape != instance.name%}{{ instance.shape }},{% endif %}{{ instance.name }} {% elif instance.shape == "VM.Standard.A1.Flex" %} NodeName={{partition.name}}-{{instance.instance_keyword}}-node-[1-{{size}}] Boards=1 SocketsPerBoard=1 CoresPerSocket={{instance.instance_pool_ocpus}} ThreadsPerCore=1 State=FUTURE Features={% if instance.shape != instance.name%}{{ instance.shape }},{% endif %}{{ instance.name }} {% elif instance.shape == "BM.Standard.E3.128" and threadspercore == 1%} @@ -127,6 +129,8 @@ NodeName={{partition.name}}-{{instance.instance_keyword}}-node-[1-{{size}}] Boar NodeName={{partition.name}}-{{instance.instance_keyword}}-node-[1-{{size}}] Boards=1 SocketsPerBoard=2 CoresPerSocket=64 ThreadsPerCore={{threadspercore}} State=FUTURE Features={% if instance.shape != instance.name%}{{ instance.shape }},{% endif %}{{ instance.name }} {% elif instance.shape == "BM.DenseIO.E4.128" and threadspercore == 2 %} NodeName={{partition.name}}-{{instance.instance_keyword}}-node-[1-{{size}}] Boards=1 SocketsPerBoard=1 CoresPerSocket=255 ThreadsPerCore=1 State=FUTURE Features={% if instance.shape != instance.name%}{{ instance.shape }},{% endif %}{{ instance.name }} +{% elif instance.shape == "BM.DenseIO.E5.128" %} +NodeName={{partition.name}}-{{instance.instance_keyword}}-node-[1-{{size}}] Boards=1 SocketsPerBoard=2 CoresPerSocket=64 ThreadsPerCore={{threadspercore}} State=FUTURE Features={% if instance.shape != instance.name%}{{ instance.shape }},{% endif %}{{ instance.name }} {% elif instance.shape == "BM.HPC2.36" %} NodeName={{partition.name}}-{{instance.instance_keyword}}-node-[1-{{size}}] Boards=1 SocketsPerBoard=2 CoresPerSocket=18 ThreadsPerCore={{threadspercore}} State=FUTURE Features={% if instance.shape != instance.name%}{{ instance.shape }},{% endif %}{{ instance.name }} {% elif instance.shape == "BM.HPC.E5.144" %} From f013113d10eaf070eb807f56c08da59d4b61f719 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 17 Jan 2024 17:17:25 -0700 Subject: [PATCH 12/40] Add DenseIO to options for controller, login or backup --- autoscaling/tf_init/locals.tf | 2 +- locals.tf | 6 +++--- schema.yaml | 9 +++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/autoscaling/tf_init/locals.tf b/autoscaling/tf_init/locals.tf index 25d6691c..1b01fd6f 100755 --- a/autoscaling/tf_init/locals.tf +++ b/autoscaling/tf_init/locals.tf @@ -5,7 +5,7 @@ locals { image_ocid = var.unsupported ? var.image_ocid : var.image shape = var.cluster_network ? var.cluster_network_shape : var.instance_pool_shape - instance_pool_ocpus = local.shape == "VM.DenseIO.E4.Flex" ? var.instance_pool_ocpus_denseIO_flex : var.instance_pool_ocpus + instance_pool_ocpus = ( local.shape == "VM.DenseIO.E4.Flex" || local.shape == "VM.DenseIO.E5.Flex" ) ? var.instance_pool_ocpus_denseIO_flex : var.instance_pool_ocpus // ips of the instances cluster_instances_ips = var.compute_cluster ? oci_core_instance.compute_cluster_instances.*.private_ip : var.cluster_network ? data.oci_core_instance.cluster_network_instances.*.private_ip : data.oci_core_instance.instance_pool_instances.*.private_ip diff --git a/locals.tf b/locals.tf index 588acde0..25e03d70 100755 --- a/locals.tf +++ b/locals.tf @@ -8,9 +8,9 @@ locals { custom_login_image_ocid = var.unsupported_login ? var.unsupported_login_image : var.custom_login_image shape = var.cluster_network ? var.cluster_network_shape : var.instance_pool_shape - instance_pool_ocpus = local.shape == "VM.DenseIO.E4.Flex" ? var.instance_pool_ocpus_denseIO_flex : var.instance_pool_ocpus - controller_ocpus = var.controller_shape == "VM.DenseIO.E4.Flex" ? var.controller_ocpus_denseIO_flex : var.controller_ocpus - login_ocpus = var.login_shape == "VM.DenseIO.E4.Flex" ? var.login_ocpus_denseIO_flex : var.login_ocpus + instance_pool_ocpus = ( local.shape == "VM.DenseIO.E4.Flex" || local.shape == "VM.DenseIO.E5.Flex" ) ? var.instance_pool_ocpus_denseIO_flex : var.instance_pool_ocpus + controller_ocpus = ( var.controller_shape == "VM.DenseIO.E4.Flex" || var.controller_shape == "VM.DenseIO.E5.Flex" ) ? var.controller_ocpus_denseIO_flex : var.controller_ocpus + login_ocpus = ( var.login_shape == "VM.DenseIO.E4.Flex" || var.login_shape == "VM.DenseIO.E5.Flex" ) ? var.login_ocpus_denseIO_flex : var.login_ocpus // ips of the instances cluster_instances_ips = var.compute_cluster ? oci_core_instance.compute_cluster_instances.*.private_ip : var.cluster_network ? data.oci_core_instance.cluster_network_instances.*.private_ip : data.oci_core_instance.instance_pool_instances.*.private_ip diff --git a/schema.yaml b/schema.yaml index aec3b4b3..3df97edd 100755 --- a/schema.yaml +++ b/schema.yaml @@ -314,6 +314,9 @@ variables: - eq: - ${controller_shape} - "VM.DenseIO.E4.Flex" + - eq: + - ${controller_shape} + - "VM.DenseIO.E5.Flex" required: true controller_custom_memory: @@ -643,6 +646,9 @@ variables: - eq: - ${instance_pool_shape} - "VM.DenseIO.E4.Flex" + - eq: + - ${instance_pool_shape} + - "VM.DenseIO.E5.Flex" required: true instance_pool_custom_memory: @@ -1398,6 +1404,9 @@ variables: - eq: - ${login_shape} - "VM.DenseIO.E4.Flex" + - eq: + - ${login_shape} + - "VM.DenseIO.E5.Flex" - ${login_node} required: true From 300bb32374e4a01a169a07aeede41d7ef2a43cd9 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 17 Jan 2024 18:06:16 -0700 Subject: [PATCH 13/40] Hide the compute agent if using the marketplace image --- schema.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/schema.yaml b/schema.yaml index 3df97edd..00db2f07 100755 --- a/schema.yaml +++ b/schema.yaml @@ -759,6 +759,9 @@ variables: title: "use compute agent" description: "Select if your image has the OCA agent rather than the oci-cn-auth package. The new marketplace images need the compute agent enabled." default: true + visible: + not: + - ${use_marketplace_image} compute_image_compartment: title: "compute image compartment" From 95645e7d48dbc1a0645af0033b6da468009849a4 Mon Sep 17 00:00:00 2001 From: Dhvani Sheth Date: Thu, 25 Jan 2024 17:10:48 -0800 Subject: [PATCH 14/40] updated code so that ansible stdout matches the output shape. added no nccl param scripts. --- playbooks/roles/nccl-conf/files/a100_b4.8 | 3 +- playbooks/roles/nccl-conf/files/bm.gpu4.8 | 3 +- playbooks/roles/nccl-conf/files/h100 | 3 +- playbooks/roles/nccl-conf/tasks/main.yml | 6 +- samples/gpu/nccl_run_allreduce_H100.sbatch | 3 +- samples/gpu/nccl_run_allreduce_H100.sh | 3 +- .../no_ncclparam_nccl_run_allreduce.sbatch | 63 ++++++++++++++ .../gpu/no_ncclparam_nccl_run_allreduce.sh | 85 +++++++++++++++++++ ...o_ncclparam_nccl_run_allreduce_H100.sbatch | 52 ++++++++++++ .../no_ncclparam_nccl_run_allreduce_H100.sh | 71 ++++++++++++++++ 10 files changed, 279 insertions(+), 13 deletions(-) create mode 100644 samples/gpu/no_ncclparam_nccl_run_allreduce.sbatch create mode 100644 samples/gpu/no_ncclparam_nccl_run_allreduce.sh create mode 100644 samples/gpu/no_ncclparam_nccl_run_allreduce_H100.sbatch create mode 100644 samples/gpu/no_ncclparam_nccl_run_allreduce_H100.sh diff --git a/playbooks/roles/nccl-conf/files/a100_b4.8 b/playbooks/roles/nccl-conf/files/a100_b4.8 index f7bfd798..f5f816f0 100644 --- a/playbooks/roles/nccl-conf/files/a100_b4.8 +++ b/playbooks/roles/nccl-conf/files/a100_b4.8 @@ -4,5 +4,4 @@ NCCL_IB_SL=0 NCCL_IB_TC=41 NCCL_IB_QPS_PER_CONNECTION=4 NCCL_IB_GID_INDEX=3 -NCCL_ALGO=Ring -NCCL_IB_HCA="=mlx5_5,mlx5_6,mlx5_7,mlx5_8,mlx5_1,mlx5_2,mlx5_3,mlx5_4,mlx5_14,mlx5_15,mlx5_16,mlx5_17,mlx5_9,mlx5_10,mlx5_11,mlx5_12" \ No newline at end of file +NCCL_IB_HCA==mlx5_5,mlx5_6,mlx5_7,mlx5_8,mlx5_1,mlx5_2,mlx5_3,mlx5_4,mlx5_14,mlx5_15,mlx5_16,mlx5_17,mlx5_9,mlx5_10,mlx5_11,mlx5_12 \ No newline at end of file diff --git a/playbooks/roles/nccl-conf/files/bm.gpu4.8 b/playbooks/roles/nccl-conf/files/bm.gpu4.8 index 0fedee48..a4bf9442 100644 --- a/playbooks/roles/nccl-conf/files/bm.gpu4.8 +++ b/playbooks/roles/nccl-conf/files/bm.gpu4.8 @@ -4,5 +4,4 @@ NCCL_IB_SL=0 NCCL_IB_TC=41 NCCL_IB_QPS_PER_CONNECTION=4 NCCL_IB_GID_INDEX=3 -NCCL_ALGO=Ring -NCCL_IB_HCA="=mlx5_0,mlx5_2,mlx5_6,mlx5_8,mlx5_10,mlx5_12,mlx5_14,mlx5_16,mlx5_1,mlx5_3,mlx5_7,mlx5_9,mlx5_11,mlx5_13,mlx5_15,mlx5_17" \ No newline at end of file +NCCL_IB_HCA==mlx5_0,mlx5_2,mlx5_6,mlx5_8,mlx5_10,mlx5_12,mlx5_14,mlx5_16,mlx5_1,mlx5_3,mlx5_7,mlx5_9,mlx5_11,mlx5_13,mlx5_15,mlx5_17 \ No newline at end of file diff --git a/playbooks/roles/nccl-conf/files/h100 b/playbooks/roles/nccl-conf/files/h100 index 4fb8f75f..2a5afbfd 100644 --- a/playbooks/roles/nccl-conf/files/h100 +++ b/playbooks/roles/nccl-conf/files/h100 @@ -10,7 +10,6 @@ NCCL_IB_SL=0 NCCL_IB_TIMEOUT=22 NCCL_NET_PLUGIN=none NCCL_SOCKET_IFNAME=eth0 -NCCL_ALGO=auto NCCL_IGNORE_CPU_AFFINITY=1 -NCCL_IB_HCA="=mlx5_0,mlx5_1,mlx5_3,mlx5_4,mlx5_5,mlx5_6,mlx5_7,mlx5_8,mlx5_9,mlx5_10,mlx5_12,mlx5_13,mlx5_14,mlx5_15,mlx5_16,mlx5_17" +NCCL_IB_HCA==mlx5_0,mlx5_1,mlx5_3,mlx5_4,mlx5_5,mlx5_6,mlx5_7,mlx5_8,mlx5_9,mlx5_10,mlx5_12,mlx5_13,mlx5_14,mlx5_15,mlx5_16,mlx5_17 NCCL_TOPO_FILE=/nfs/cluster/H100-topology.xml \ No newline at end of file diff --git a/playbooks/roles/nccl-conf/tasks/main.yml b/playbooks/roles/nccl-conf/tasks/main.yml index 4a4dba20..88c9dc36 100644 --- a/playbooks/roles/nccl-conf/tasks/main.yml +++ b/playbooks/roles/nccl-conf/tasks/main.yml @@ -13,7 +13,7 @@ owner: root group: root mode: '0644' - when: shape_nccl.stdout == "BM.GPU.H100.8" + when: shape_nccl.stdout == '"BM.GPU.H100.8"' - name: copy nccl.conf for BM.GPU.B4.8 and A100-v2.8 become: true @@ -23,7 +23,7 @@ owner: root group: root mode: '0644' - when: shape_nccl.stdout == "BM.GPU.B4.8" or shape_nccl.stdout == "BM.GPU.A100-v2.8" + when: shape_nccl.stdout == '"BM.GPU.B4.8"' or shape_nccl.stdout == '"BM.GPU.A100-v2.8"' - name: copy nccl.conf for BM.GPU4.8 become: true @@ -33,4 +33,4 @@ owner: root group: root mode: '0644' - when: shape_nccl.stdout == "BM.GPU4.8" \ No newline at end of file + when: shape_nccl.stdout == '"BM.GPU4.8"' \ No newline at end of file diff --git a/samples/gpu/nccl_run_allreduce_H100.sbatch b/samples/gpu/nccl_run_allreduce_H100.sbatch index 830870c4..e1ed5e99 100644 --- a/samples/gpu/nccl_run_allreduce_H100.sbatch +++ b/samples/gpu/nccl_run_allreduce_H100.sbatch @@ -61,8 +61,7 @@ fi -x RX_QUEUE_LEN=8192 \ -x IB_RX_QUEUE_LEN=8192 \ -x NCCL_SOCKET_IFNAME=eth0 \ - -x NCCL_ALGO=auto \ -x NCCL_IGNORE_CPU_AFFINITY=1 \ -x NCCL_IB_HCA="${var_NCCL_IB_HCA}" \ -x NCCL_TOPO_FILE=~/H100-topology.xml \ - --np $((SLURM_NNODES*SLURM_NTASKS_PER_NODE)) --hostfile $MACHINEFILE /opt/oci-hpc/nccl-test/build/all_reduce_perf -b1G -e10G -i$((1024*1024*1024*9)) -n 100 \ No newline at end of file + --np $((SLURM_NNODES*SLURM_NTASKS_PER_NODE)) --hostfile $MACHINEFILE /opt/oci-hpc/nccl-test/build/all_reduce_perf -b 1G -e 16G -f 2 -g 1 \ No newline at end of file diff --git a/samples/gpu/nccl_run_allreduce_H100.sh b/samples/gpu/nccl_run_allreduce_H100.sh index 520f125d..898e4d74 100644 --- a/samples/gpu/nccl_run_allreduce_H100.sh +++ b/samples/gpu/nccl_run_allreduce_H100.sh @@ -75,11 +75,10 @@ do -x RX_QUEUE_LEN=8192 \ -x IB_RX_QUEUE_LEN=8192 \ -x NCCL_SOCKET_IFNAME=eth0 \ - -x NCCL_ALGO=auto \ -x NCCL_IGNORE_CPU_AFFINITY=1 \ -x NCCL_IB_HCA="${var_NCCL_IB_HCA}" \ -x NCCL_TOPO_FILE=~/H100-topology.xml \ - --np $np --hostfile $hostfile /opt/oci-hpc/nccl-test/build/all_reduce_perf -b1G -e10G -i$((1024*1024*1024*9)) -n 100 >> $logfile + --np $np --hostfile $hostfile /opt/oci-hpc/nccl-test/build/all_reduce_perf -b 1G -e 16G -f 2 -g 1 >> $logfile tail -n 32 $logfile diff --git a/samples/gpu/no_ncclparam_nccl_run_allreduce.sbatch b/samples/gpu/no_ncclparam_nccl_run_allreduce.sbatch new file mode 100644 index 00000000..caa15c6f --- /dev/null +++ b/samples/gpu/no_ncclparam_nccl_run_allreduce.sbatch @@ -0,0 +1,63 @@ +#!/bin/bash +#SBATCH --job-name=nccl-allreduce-slurm +#SBATCH --nodes=2 +#SBATCH --gpus-per-node=8 +#SBATCH --ntasks-per-node=8 +#SBATCH --exclusive +export PMI_DEBUG=1 + + +cd /nfs/cluster +mkdir $SLURM_JOB_ID +cd $SLURM_JOB_ID + +MACHINEFILE="hostfile" +ORDEREDMACHINEFILE="ordered_hostfile_system_name" +ORDEREDRANKMACHINEFILE="rankfile_system_name" + +scontrol show hostnames $SLURM_JOB_NODELIST > $MACHINEFILE +echo MACHINEFILE +cat $MACHINEFILE + +source /etc/os-release +if [ $ID == "ol" ] || [ $ID == "centos" ] ; then + python3 /home/opc/node_ordering_by_rack.py --input_file $MACHINEFILE > /dev/null +elif [ $ID == "debian" ] || [ $ID == "ubuntu" ] ; then + python3 /home/ubuntu/node_ordering_by_rack.py --input_file $MACHINEFILE > /dev/null +fi + + +echo ORDEREDMACHINEFILE +cat $ORDEREDMACHINEFILE +echo ORDEREDRANKMACHINEFILE +cat $ORDEREDRANKMACHINEFILE + +mpivars_path=`ls /usr/mpi/gcc/openmpi-*/bin/mpivars.sh` + +if [[ "$mpivars_path" == "" ]]; then + mpivars_path=`ls /opt/openmpi-*/bin/mpivars.sh` +fi + +if [[ "$mpivars_path" == "" ]]; then + echo "Could not find MPIPATH"; exit; fi + +source $mpivars_path + +shape=`curl -sH "Authorization: Bearer Oracle" -L http://169.254.169.254/opc/v2/instance/ | jq .shape` +if [ $shape == \"BM.GPU.B4.8\" ] || [ $shape == \"BM.GPU.A100-v2.8\" ] +then + var_UCX_NET_DEVICES=mlx5_0:1 +elif [ $shape == \"BM.GPU4.8\" ] +then + var_UCX_NET_DEVICES=mlx5_4:1 +fi + + mpirun --mca pml ucx \ + --bind-to numa \ + --mca coll ^hcoll \ + -x UCX_TLS=ud,self,sm \ + -x UCX_NET_DEVICES=${var_UCX_NET_DEVICES} \ + -x HCOLL_ENABLE_MCAST_ALL=0 \ + -x coll_hcoll_enable=0 \ + -x NCCL_ALGO=Ring \ + --np $((SLURM_NNODES*SLURM_NTASKS_PER_NODE)) --rankfile $ORDEREDRANKMACHINEFILE /opt/oci-hpc/nccl-test/build/all_reduce_perf -b1G -e10G -i$((1024*1024*1024*9)) -n 100 diff --git a/samples/gpu/no_ncclparam_nccl_run_allreduce.sh b/samples/gpu/no_ncclparam_nccl_run_allreduce.sh new file mode 100644 index 00000000..8fa98a1e --- /dev/null +++ b/samples/gpu/no_ncclparam_nccl_run_allreduce.sh @@ -0,0 +1,85 @@ +#!/bin/bash +set -e + +# number of times to run the nccl test to stress the GPUs and RDMA network. This is different from -n iterations parameter of nccl allreduce which is set below using $iter +max=$1 + +# This assume, the hostfile passed is already ordered based on their rackId +if [ -n "$2" ]; then + hostfile=$2 +else + hostfile="/tmp/ordered_hostfile_system_name" +fi + +ORDEREDMACHINEFILE="ordered_hostfile_system_name" +ORDEREDRANKMACHINEFILE="rankfile_system_name" +echo INPUTFILE +cat $hostfile + +# will generate rack-aware ordered host file +source /etc/os-release +if [ $ID == "ol" ] || [ $ID == "centos" ] ; then + python3 /home/opc/node_ordering_by_rack.py --input_file $hostfile > /dev/null +elif [ $ID == "debian" ] || [ $ID == "ubuntu" ] ; then + python3 /home/ubuntu/node_ordering_by_rack.py --input_file $hostfile > /dev/null +fi + +hostfile=$ORDEREDMACHINEFILE +rankfile=$ORDEREDRANKMACHINEFILE + +echo ORDEREDMACHINEFILE +cat $ORDEREDMACHINEFILE +echo ORDEREDRANKMACHINEFILE +cat $ORDEREDRANKMACHINEFILE + +# The number of GPUs to use for the test. Has to be multiplier of 8. If not passed, all GPUs will be used. +if [ -n "$3" ]; then + np=$3 +else + np=$((`less $hostfile | wc -l` * 8 )) +fi + +logfile="nccl_run_allreduce.sh.log" + +for x in $(seq 1 1 $max) +do + + echo $x + echo $x >> $logfile + date >> $logfile + + rankfile=$rankfile; np=$np ; iter=20; + + mpivars_path=`ls /usr/mpi/gcc/openmpi-*/bin/mpivars.sh` + source $mpivars_path + + if [[ "$mpivars_path" == "" ]]; then echo "Could not find MPIPATH"; exit; fi + +first_node=`head $hostfile -n 1` +shape=`ssh $first_node 'curl -sH "Authorization: Bearer Oracle" -L http://169.254.169.254/opc/v2/instance/' | jq .shape` +if [ $shape == \"BM.GPU.B4.8\" ] || [ $shape == \"BM.GPU.A100-v2.8\" ] +then + var_UCX_NET_DEVICES=mlx5_0:1 +elif [ $shape == \"BM.GPU4.8\" ] +then + var_UCX_NET_DEVICES=mlx5_4:1 +fi + + # final version + # all NCCL parameters are at /etc/nccl.conf on each compute node. + mpirun --mca pml ucx \ + --bind-to numa \ + --mca coll ^hcoll \ + -x UCX_TLS=ud,self,sm \ + -x UCX_NET_DEVICES=${var_UCX_NET_DEVICES} \ + -x HCOLL_ENABLE_MCAST_ALL=0 \ + -x coll_hcoll_enable=0 \ + -x NCCL_ALGO=Ring \ + --np $np --rankfile $rankfile /opt/oci-hpc/nccl-test/build/all_reduce_perf -b1G -e10G -i$((1024*1024*1024*9)) -n $iter >> $logfile + + tail -n 32 $logfile + + +done + + diff --git a/samples/gpu/no_ncclparam_nccl_run_allreduce_H100.sbatch b/samples/gpu/no_ncclparam_nccl_run_allreduce_H100.sbatch new file mode 100644 index 00000000..591a75c1 --- /dev/null +++ b/samples/gpu/no_ncclparam_nccl_run_allreduce_H100.sbatch @@ -0,0 +1,52 @@ +#!/bin/bash +#SBATCH --job-name=nccl-allreduce-slurm +#SBATCH --nodes=2 +#SBATCH --gpus-per-node=8 +#SBATCH --ntasks-per-node=8 +#SBATCH --exclusive +export PMI_DEBUG=1 + + +cd /nfs/cluster +mkdir $SLURM_JOB_ID +cd $SLURM_JOB_ID + +MACHINEFILE="hostfile" + +scontrol show hostnames $SLURM_JOB_NODELIST > $MACHINEFILE +echo MACHINEFILE +cat $MACHINEFILE + +source /etc/os-release + +mpivars_path=`ls /usr/mpi/gcc/openmpi-*/bin/mpivars.sh` + +if [[ "$mpivars_path" == "" ]]; then + mpivars_path=`ls /opt/openmpi-*/bin/mpivars.sh` +fi + +if [[ "$mpivars_path" == "" ]]; then + echo "Could not find MPIPATH"; exit; fi + +source $mpivars_path + +shape=`curl -sH "Authorization: Bearer Oracle" -L http://169.254.169.254/opc/v2/instance/ | jq .shape` +if [ $shape == \"BM.GPU.H100.8\" ] +then + var_UCX_NET_DEVICES=eth0 +else + echo "Use the appropriate nccl test run script for non H100 nodes" +fi + + # all NCCL parameters are at /etc/nccl.conf on each compute node. + mpirun --mca pml ucx \ + --bind-to numa \ + -npernode 8 \ + --mca coll ^hcoll \ + -x HCOLL_ENABLE_MCAST_ALL=0 \ + -x coll_hcoll_enable=0 \ + -x UCX_TLS=tcp \ + -x UCX_NET_DEVICES=${var_UCX_NET_DEVICES} \ + -x RX_QUEUE_LEN=8192 \ + -x IB_RX_QUEUE_LEN=8192 \ + --np $((SLURM_NNODES*SLURM_NTASKS_PER_NODE)) --hostfile $MACHINEFILE /opt/oci-hpc/nccl-test/build/all_reduce_perf -b 1G -e 16G -f 2 -g 1 \ No newline at end of file diff --git a/samples/gpu/no_ncclparam_nccl_run_allreduce_H100.sh b/samples/gpu/no_ncclparam_nccl_run_allreduce_H100.sh new file mode 100644 index 00000000..b83307b2 --- /dev/null +++ b/samples/gpu/no_ncclparam_nccl_run_allreduce_H100.sh @@ -0,0 +1,71 @@ +#!/bin/bash +set -e + +# number of times to run the nccl test to stress the GPUs and RDMA network. +max=$1 + +# This assume, the hostfile passed is already ordered based on their rackId +if [ -n "$2" ]; then + hostfile=$2 +else + hostfile="/etc/opt/oci-hpc/hostfile.tcp" +fi + +echo INPUTFILE +cat $hostfile + +# The number of GPUs to use for the test. Has to be multiplier of 8. If not passed, all GPUs will be used. +if [ -n "$3" ]; then + np=$3 +else + np=$((`less $hostfile | wc -l` * 8 )) +fi + +logfile="nccl_run_allreduce.sh.log" + +for x in $(seq 1 1 $max) +do + + echo $x + echo $x >> $logfile + date >> $logfile + + hostfile=$hostfile; np=$np; + + mpivars_path=`ls /usr/mpi/gcc/openmpi-*/bin/mpivars.sh` + + if [[ "$mpivars_path" == "" ]]; then + mpivars_path=`ls /opt/openmpi-*/bin/mpivars.sh` + fi + + if [[ "$mpivars_path" == "" ]]; then + echo "Could not find MPIPATH"; exit; fi + + source $mpivars_path + + first_node=`head $hostfile -n 1` + shape=`ssh $first_node 'curl -sH "Authorization: Bearer Oracle" -L http://169.254.169.254/opc/v2/instance/' | jq .shape` + if [ $shape == \"BM.GPU.H100.8\" ] + then + var_UCX_NET_DEVICES=eth0 + else + echo "Use the appropriate nccl test run script for non H100 nodes" + fi + + # all NCCL parameters are at /etc/nccl.conf on each compute node. + mpirun --mca pml ucx \ + --bind-to numa \ + -npernode 8 \ + --mca coll ^hcoll \ + -x HCOLL_ENABLE_MCAST_ALL=0 \ + -x coll_hcoll_enable=0 \ + -x UCX_TLS=tcp \ + -x UCX_NET_DEVICES=${var_UCX_NET_DEVICES} \ + -x RX_QUEUE_LEN=8192 \ + -x IB_RX_QUEUE_LEN=8192 \ + --np $np --hostfile $hostfile /opt/oci-hpc/nccl-test/build/all_reduce_perf -b 1G -e 16G -f 2 -g 1 >> $logfile + + tail -n 32 $logfile + + +done \ No newline at end of file From 0b2949ba8b8436e040afdea99dd193cdcc5ae4d5 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Mon, 29 Jan 2024 15:26:17 -0700 Subject: [PATCH 15/40] Change compute_agent default value to true --- variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variables.tf b/variables.tf index e6c2807d..79cc9636 100755 --- a/variables.tf +++ b/variables.tf @@ -37,7 +37,7 @@ variable "boot_volume_size" { default = 50 } variable "use_marketplace_image" { default = true} variable "image" { default = "ocid1.image.oc1..aaaaaaaa5yxem7wzie34hi5km4qm2t754tsfxrjuefyjivebrxjad4jcj5oa" } variable "image_ocid" { default = "ocid1.image.oc1..aaaaaaaa5yxem7wzie34hi5km4qm2t754tsfxrjuefyjivebrxjad4jcj5oa" } -variable "use_compute_agent" { default = false } +variable "use_compute_agent" { default = true } variable "unsupported_controller_image" { default = "" } variable "unsupported_login_image" { default = "" } variable "use_cluster_nfs" { default = true} From caeb16900d961a7576cc04ff69a42c0a61ce1e3e Mon Sep 17 00:00:00 2001 From: Dhvani Sheth Date: Mon, 5 Feb 2024 14:44:06 -0800 Subject: [PATCH 16/40] added container NCCL script for H100 --- .../nccl_run_allreduce_containers_H100.sbatch | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 samples/gpu/nccl_run_allreduce_containers_H100.sbatch diff --git a/samples/gpu/nccl_run_allreduce_containers_H100.sbatch b/samples/gpu/nccl_run_allreduce_containers_H100.sbatch new file mode 100644 index 00000000..d46edd62 --- /dev/null +++ b/samples/gpu/nccl_run_allreduce_containers_H100.sbatch @@ -0,0 +1,79 @@ +#!/bin/bash +#SBATCH --job-name=nccl-allreduce-slurm-containers +#SBATCH --nodes=2 +#SBATCH --gpus-per-node=8 +#SBATCH --ntasks-per-node=8 +#SBATCH --exclusive +export PMI_DEBUG=1 + +cd /nfs/cluster +mkdir $SLURM_JOB_ID +cd $SLURM_JOB_ID + +MACHINEFILE="hostfile" + +scontrol show hostnames $SLURM_JOB_NODELIST > $MACHINEFILE +echo MACHINEFILE +cat $MACHINEFILE + +source /etc/os-release + +MPIVARS_PATH=`ls /usr/mpi/gcc/openmpi-*/bin/mpivars.sh` + +if [[ "$MPIVARS_PATH" == "" ]]; then + MPIVARS_PATH=`ls /opt/openmpi-*/bin/mpivars.sh` +fi + +if [[ "$MPIVARS_PATH" == "" ]]; then + echo "Could not find MPIPATH"; exit; fi + +source $MPIVARS_PATH +LOCAL_MPI=${MPIVARS_PATH%/*} + +shape=`curl -sH "Authorization: Bearer Oracle" -L http://169.254.169.254/opc/v2/instance/ | jq .shape` +if [ $shape == \"BM.GPU.H100.8\" ] +then + var_UCX_NET_DEVICES=eth0 +else + echo "Use the appropriate nccl test run script for non H100 nodes" +fi + +export NCCL_CROSS_NIC=0 \ + NCCL_SOCKET_NTHREADS=16 \ + NCCL_DEBUG=WARN \ + NCCL_CUMEM_ENABLE=0 \ + NCCL_IB_SPLIT_DATA_ON_QPS=0 \ + NCCL_IB_QPS_PER_CONNECTION=16 \ + NCCL_IB_GID_INDEX=3 \ + NCCL_IB_TC=41 \ + NCCL_IB_SL=0 \ + NCCL_IB_TIMEOUT=22 \ + NCCL_NET_PLUGIN=none \ + NCCL_SOCKET_IFNAME=eth0 \ + NCCL_IGNORE_CPU_AFFINITY=1 \ + NCCL_IB_HCA="=mlx5_0,mlx5_1,mlx5_3,mlx5_4,mlx5_5,mlx5_6,mlx5_7,mlx5_8,mlx5_9,mlx5_10,mlx5_12,mlx5_13,mlx5_14,mlx5_15,mlx5_16,mlx5_17" \ + NCCL_TOPO_FILE=/nfs/cluster/H100-topology.xml \ + HCOLL_ENABLE_MCAST_ALL=0 \ + coll_hcoll_enable=0 \ + UCX_TLS=tcp \ + UCX_NET_DEVICES=${var_UCX_NET_DEVICES} \ + RX_QUEUE_LEN=8192 \ + IB_RX_QUEUE_LEN=8192 \ + OMPI_MCA_coll=^hcoll + +env | grep "SLURMD_NODENAME=" +USER=`whoami` + +CONTAINER_IMAGE="/home/ubuntu/nvcr.io+nvidia+pytorch+24.01-py3.sqsh" +CONTAINER_MOUNTS="/opt/oci-hpc/nccl-test:/nccl,$LOCAL_MPI:$LOCAL_MPI,/nfs/cluster:/nfs/cluster" +echo $LOCAL_MPI +echo $MPIVARS_PATH + +srun --mpi=pmi2 --gpus-per-node=$SBATCH_GPUS_PER_NODE \ + --ntasks-per-node=$SLURM_NTASKS_PER_NODE \ + --container-image=$CONTAINER_IMAGE \ + --container-mounts=$CONTAINER_MOUNTS \ + bash -c " + source $MPIVARS_PATH && + /nccl/build/all_reduce_perf -b 1G -e 16G -f 2 -g 1 + " \ No newline at end of file From f676e9e3b20292ddd5b0cf169fba63c7730a539c Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Mon, 12 Feb 2024 15:23:29 -0700 Subject: [PATCH 17/40] Fix NVMe's for Ubuntu --- playbooks/roles/localdisk/tasks/common.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/playbooks/roles/localdisk/tasks/common.yml b/playbooks/roles/localdisk/tasks/common.yml index 9b30f362..35a5efb4 100755 --- a/playbooks/roles/localdisk/tasks/common.yml +++ b/playbooks/roles/localdisk/tasks/common.yml @@ -18,7 +18,7 @@ state: present label: gpt with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list }}" + - "{{ (hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[1-9][0-9]n1') | list ) + (hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list) }}" - name: create a filesystem filesystem: @@ -26,7 +26,7 @@ fstype: xfs opts: "-L locscratch{{item | replace('nvme','') | replace('n1','')}}" with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list }}" + - "{{ (hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[1-9][0-9]n1') | list ) + (hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list) }}" when: not ( one_lv | bool ) - name: Mount local volume @@ -37,7 +37,7 @@ opts: defaults,noatime state: mounted with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list }}" + - "{{ (hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[1-9][0-9]n1') | list ) + (hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list) }}" when: not ( one_lv | bool ) - name: "set permissions on {{ nvme_path_edited }}" @@ -50,7 +50,7 @@ group: "{{privilege_group_name}}" recurse: no with_items: - - "{{ hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list }}" + - "{{ (hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[1-9][0-9]n1') | list ) + (hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list) }}" when: not ( one_lv | bool ) - name: Check for lvm devices @@ -61,7 +61,7 @@ - name: Create volume group lvg: vg: "vg_nvmes" - pvs: "{{['/dev/']|product(hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]|[1-9][0-9]n1') | list)|map('join', '') | join(',')}}" + pvs: "{{['/dev/']|product((hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[1-9][0-9]n1') | list ) + (hostvars[inventory_hostname]['ansible_devices'] | select('match','nvme[0-9]n1') | list) )|map('join', '') | join(',')}}" - name: Create Logical volume lvol: From 4d10cf7234f892a4e790e91095e6e791cf6f5915 Mon Sep 17 00:00:00 2001 From: Dhvani Sheth Date: Tue, 13 Feb 2024 08:32:59 -0800 Subject: [PATCH 18/40] user home directory will be created even when nossh --- playbooks/roles/cluster-cli/files/cluster | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playbooks/roles/cluster-cli/files/cluster b/playbooks/roles/cluster-cli/files/cluster index e3bf875e..4bb4e623 100755 --- a/playbooks/roles/cluster-cli/files/cluster +++ b/playbooks/roles/cluster-cli/files/cluster @@ -184,8 +184,10 @@ def add(user, password, uid, gid, name, nossh): if(conn.result['result'] != 0): print(conn.result) + homedir='/home/{}/'.format(user) + os.system("sudo su - "+user+" -c "+" 'ls' 2> /dev/null") + if not nossh: - homedir='/home/{}/'.format(user) os.system("sudo su - "+user+" -c "+"' ssh-keygen -t rsa -b 2048 -q -f "+homedir+".ssh/id_rsa -P \"\"' 2> /dev/null") os.system("sudo su - "+user+" -c "+"'mv "+homedir+".ssh/id_rsa.pub "+homedir+".ssh/authorized_keys' 2> /dev/null") @user.command() From 550d4a050005e952496756626e88ebf58b0d26ae Mon Sep 17 00:00:00 2001 From: Dhvani Sheth Date: Tue, 13 Feb 2024 17:21:29 -0800 Subject: [PATCH 19/40] renamed the H100 topology file to avoid confusion with using for container --- .../{H100-topology-container.xml => H100-topology-kubernetes.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename samples/gpu/{H100-topology-container.xml => H100-topology-kubernetes.xml} (100%) diff --git a/samples/gpu/H100-topology-container.xml b/samples/gpu/H100-topology-kubernetes.xml similarity index 100% rename from samples/gpu/H100-topology-container.xml rename to samples/gpu/H100-topology-kubernetes.xml From 5d07d068cc4aec77570284e780e693e505b5666d Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 14 Feb 2024 14:42:36 -0700 Subject: [PATCH 20/40] Add compute agent for CC --- compute-nodes.tf | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/compute-nodes.tf b/compute-nodes.tf index 85607ae1..1544c5ad 100755 --- a/compute-nodes.tf +++ b/compute-nodes.tf @@ -25,8 +25,32 @@ resource "oci_core_instance" "compute_cluster_instances" { shape = var.cluster_network_shape agent_config { - is_management_disabled = true - } + + are_all_plugins_disabled = false + is_management_disabled = true + is_monitoring_disabled = false + + plugins_config { + desired_state = "DISABLED" + name = "OS Management Service Agent" + } + dynamic plugins_config { + + for_each = var.use_compute_agent ? ["ENABLED"] : ["DISABLED"] + content { + name = "Compute HPC RDMA Authentication" + desired_state = plugins_config.value + } + } + dynamic plugins_config { + for_each = var.use_compute_agent ? ["ENABLED"] : ["DISABLED"] + content { + name = "Compute HPC RDMA Auto-Configuration" + desired_state = plugins_config.value + } + + } + } display_name = "${local.cluster_name}-node-${var.compute_cluster_start_index+count.index}" From 9e3a9f4cc960c802455e1db981d5d30faf28b83c Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 14 Feb 2024 14:44:29 -0700 Subject: [PATCH 21/40] Update providers version --- autoscaling/tf_init/versions.tf | 2 +- versions.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/autoscaling/tf_init/versions.tf b/autoscaling/tf_init/versions.tf index 6dd2b529..69ac6583 100755 --- a/autoscaling/tf_init/versions.tf +++ b/autoscaling/tf_init/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { oci = { source = "oracle/oci" - version = "5.1.0" + version = "5.25.0" } } } \ No newline at end of file diff --git a/versions.tf b/versions.tf index 6dd2b529..69ac6583 100755 --- a/versions.tf +++ b/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { oci = { source = "oracle/oci" - version = "5.1.0" + version = "5.25.0" } } } \ No newline at end of file From 890d72a94002ceccdc39081df0383f4bbbb41c26 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 14 Feb 2024 14:50:45 -0700 Subject: [PATCH 22/40] Update cloud agent update to 1.38.0 --- playbooks/roles/cloud-agent_update/tasks/el.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/playbooks/roles/cloud-agent_update/tasks/el.yml b/playbooks/roles/cloud-agent_update/tasks/el.yml index 60211cd3..4102c6f7 100644 --- a/playbooks/roles/cloud-agent_update/tasks/el.yml +++ b/playbooks/roles/cloud-agent_update/tasks/el.yml @@ -4,36 +4,36 @@ register: version when: use_compute_agent | bool -- name: Install OCA v1.37 for OL8 +- name: Install OCA v1.38 for OL8 vars: - major_version: "{{version.stdout.split('.')[1] }}" - minor_version: "{{version.stdout.split('.')[0] }}" - sub_version: "{{version.stdout.split('.')[2].split('-')[0] }}" yum: - name: "https://objectstorage.us-phoenix-1.oraclecloud.com/p/aV_mSl96KIiapAeZtsyo-SUcPCSurDfWaj06f4XVVoNKIsxvqlZ65guPTnMuNawR/n/imagegen/b/agent_test/o/1.37.0/3/oracle-cloud-agent-1.37.2-10459.el8.x86_64.rpm" + name: "https://objectstorage.us-phoenix-1.oraclecloud.com/p/H1npAGRle5v4izHQkTysF_tfdsgO43iawRc4IC2xL5LwO6T36m8o34T8_kc_KaBS/n/imagegen/b/agent_test/o/1.38.0/3/oracle-cloud-agent-1.38.0-10815.el8.x86_64.rpm" state: present disable_gpg_check: yes when: - ansible_os_family == 'RedHat' - ansible_distribution_major_version == '8' - (minor_version | int <= 1) | bool - - (major_version | int <= 37) | bool - - (sub_version | int < 2) | bool + - (major_version | int <= 38) | bool + - (sub_version | int < 0) | bool - use_compute_agent | bool -- name: Install OCA v1.37 for OL7 +- name: Install OCA v1.38 for OL7 vars: - major_version: "{{version.stdout.split('.')[1] }}" - minor_version: "{{version.stdout.split('.')[0] }}" - sub_version: "{{version.stdout.split('.')[2].split('-')[0] }}" yum: - name: "https://objectstorage.us-phoenix-1.oraclecloud.com/p/YmPlysZFl4CKrLTKN9Rj0CMPt8qiJgflvF4vXsOaaqOfcm5NMnyBJl_dlC0V0lTo/n/imagegen/b/agent_test/o/1.37.0/3/oracle-cloud-agent-1.37.2-10459.el7.x86_64.rpm" + name: "https://objectstorage.us-phoenix-1.oraclecloud.com/p/v7U4X2bmcA_iY6UoRiGALU-A8xIrcsMZWjnfgk8zi4BDX5pfU1BV0XbHR9Iy6OJk/n/imagegen/b/agent_test/o/1.38.0/3/oracle-cloud-agent-1.38.0-10815.el7.x86_64.rpm" state: present disable_gpg_check: yes when: - ansible_os_family == 'RedHat' - ansible_distribution_major_version == '7' - (minor_version | int <= 1) | bool - - (major_version | int <= 37) | bool - - (sub_version | int < 2) | bool + - (major_version | int <= 38) | bool + - (sub_version | int < 0) | bool - use_compute_agent | bool \ No newline at end of file From 6661e494179e70d37e302685120184adb63ccac7 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 14 Feb 2024 14:51:51 -0700 Subject: [PATCH 23/40] Don't update the cloud agent by default --- playbooks/new_nodes.yml | 3 --- playbooks/resize_add.yml | 3 --- playbooks/site.yml | 3 --- 3 files changed, 9 deletions(-) diff --git a/playbooks/new_nodes.yml b/playbooks/new_nodes.yml index a5e68f90..d7926b35 100755 --- a/playbooks/new_nodes.yml +++ b/playbooks/new_nodes.yml @@ -46,9 +46,6 @@ become: true gather_facts: true tasks: - - include_role: - name: cloud-agent_update - when: cluster_network|bool and use_compute_agent|default(false)|bool - include_role: name: oci-cn-auth when: cluster_network|bool and not use_compute_agent|default(false)|bool diff --git a/playbooks/resize_add.yml b/playbooks/resize_add.yml index 3d92190c..25a4c87a 100755 --- a/playbooks/resize_add.yml +++ b/playbooks/resize_add.yml @@ -44,9 +44,6 @@ become: true gather_facts: true tasks: - - include_role: - name: cloud-agent_update - when: cluster_network|bool and use_compute_agent|default(false)|bool - include_role: name: oci-cn-auth when: cluster_network|bool and not use_compute_agent|default(false)|bool diff --git a/playbooks/site.yml b/playbooks/site.yml index 056e43ef..1f9c7ed3 100644 --- a/playbooks/site.yml +++ b/playbooks/site.yml @@ -50,9 +50,6 @@ become: true gather_facts: true tasks: - - include_role: - name: cloud-agent_update - when: cluster_network|bool and use_compute_agent|default(false)|bool - include_role: name: oci-cn-auth when: cluster_network|bool and not use_compute_agent|default(false)|bool From b35f349621b9361487de28529fe1e9f6b892eaea Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 14 Feb 2024 14:52:24 -0700 Subject: [PATCH 24/40] Add DNS entries for all nodes. Useful for separate cluster. --- autoscaling/tf_init/controller_update.tf | 2 + autoscaling/tf_init/data.tf | 15 ++++++ autoscaling/tf_init/inventory.tpl | 4 +- autoscaling/tf_init/locals.tf | 3 ++ autoscaling/tf_init/network.tf | 32 +++++++++++++ bin/resize.py | 58 ++++++++++++++++++++++-- conf/variables.tpl | 3 ++ controller.tf | 25 +++++++++- data.tf | 15 ++++++ inventory.tpl | 4 +- locals.tf | 3 ++ login.tf | 15 ++++++ network.tf | 57 ++++++++++++++++++++++- schema.yaml | 13 ++++++ slurm_ha.tf | 21 +++++++++ variables.tf | 6 +++ 16 files changed, 267 insertions(+), 9 deletions(-) diff --git a/autoscaling/tf_init/controller_update.tf b/autoscaling/tf_init/controller_update.tf index 09a8cb06..5d58f76e 100755 --- a/autoscaling/tf_init/controller_update.tf +++ b/autoscaling/tf_init/controller_update.tf @@ -29,6 +29,8 @@ resource "local_file" "inventory" { private_subnet = var.private_subnet, rdma_network = cidrhost(var.rdma_subnet, 0), rdma_netmask = cidrnetmask(var.rdma_subnet), + zone_name = var.zone_name, + dns_entries = var.dns_entries, nfs = var.use_scratch_nfs ? local.cluster_instances_names[0] : "", scratch_nfs = var.use_scratch_nfs, cluster_nfs = var.use_cluster_nfs, diff --git a/autoscaling/tf_init/data.tf b/autoscaling/tf_init/data.tf index 0fe792cb..8a54acf9 100755 --- a/autoscaling/tf_init/data.tf +++ b/autoscaling/tf_init/data.tf @@ -50,4 +50,19 @@ data "oci_core_images" "linux" { } } +data "oci_core_vcn" "vcn" { + vcn_id = local.vcn_id +} +data "oci_dns_views" "dns_views" { + compartment_id = var.targetCompartment + scope = "PRIVATE" + display_name = data.oci_core_vcn.vcn.display_name +} + +data "oci_dns_zones" "dns_zones" { + compartment_id = var.targetCompartment + name = "${var.zone_name}" + zone_type = "PRIMARY" + scope = "PRIVATE" +} diff --git a/autoscaling/tf_init/inventory.tpl b/autoscaling/tf_init/inventory.tpl index 534dfdcc..56c20cb9 100755 --- a/autoscaling/tf_init/inventory.tpl +++ b/autoscaling/tf_init/inventory.tpl @@ -71,4 +71,6 @@ compute_username=${compute_username} controller_username=${controller_username} pam = ${pam} sacct_limits=${sacct_limits} -use_compute_agent=${use_compute_agent} \ No newline at end of file +use_compute_agent=${use_compute_agent} +zone_name=${zone_name} +dns_entries=${dns_entries} \ No newline at end of file diff --git a/autoscaling/tf_init/locals.tf b/autoscaling/tf_init/locals.tf index 1b01fd6f..086873f6 100755 --- a/autoscaling/tf_init/locals.tf +++ b/autoscaling/tf_init/locals.tf @@ -8,8 +8,11 @@ locals { instance_pool_ocpus = ( local.shape == "VM.DenseIO.E4.Flex" || local.shape == "VM.DenseIO.E5.Flex" ) ? var.instance_pool_ocpus_denseIO_flex : var.instance_pool_ocpus // ips of the instances cluster_instances_ips = var.compute_cluster ? oci_core_instance.compute_cluster_instances.*.private_ip : var.cluster_network ? data.oci_core_instance.cluster_network_instances.*.private_ip : data.oci_core_instance.instance_pool_instances.*.private_ip + first_vcn_ip = cidrhost(data.oci_core_subnet.private_subnet.cidr_block,0) + cluster_instances_ips_index = [for ip in local.cluster_instances_ips : tostring((tonumber(split(".",ip)[3])-tonumber(split(".",local.first_vcn_ip)[3]))+256*(tonumber(split(".",ip)[2])-tonumber(split(".",local.first_vcn_ip)[2]))+1)] // subnet id derived either from created subnet or existing if specified + vcn_id = var.use_existing_vcn ? var.vcn_id : element(concat(oci_core_vcn.vcn.*.id, [""]), 0) subnet_id = var.private_deployment ? var.use_existing_vcn ? var.private_subnet_id : element(concat(oci_core_subnet.private-subnet.*.id, [""]), 1) : var.use_existing_vcn ? var.private_subnet_id : element(concat(oci_core_subnet.private-subnet.*.id, [""]), 0) // subnet id derived either from created subnet or existing if specified diff --git a/autoscaling/tf_init/network.tf b/autoscaling/tf_init/network.tf index eacc4b36..5c6404d6 100755 --- a/autoscaling/tf_init/network.tf +++ b/autoscaling/tf_init/network.tf @@ -162,3 +162,35 @@ resource "oci_core_subnet" "private-subnet" { prohibit_public_ip_on_vnic = true route_table_id = oci_core_route_table.private_route_table[0].id } + + +resource "oci_dns_rrset" "rrset-cluster-network-OCI" { + for_each = var.dns_entries ? toset([for v in range(var.node_count) : tostring(v)]) : [] + zone_name_or_id = data.oci_dns_zones.dns_zones.zones[0].id + domain = "${local.cluster_instances_names[tonumber(each.key)]}.${var.zone_name}" + rtype = "A" + items { + domain = "${local.cluster_instances_names[tonumber(each.key)]}.${var.zone_name}" + rtype = "A" + rdata = "${local.cluster_instances_ips[tonumber(each.key)]}" + ttl = 3600 + } + scope = "PRIVATE" + view_id = data.oci_dns_views.dns_views.views[0].id +} + +resource "oci_dns_rrset" "rrset-cluster-network-SLURM" { + + for_each = var.slurm && var.dns_entries ? toset([for v in range(var.node_count) : tostring(v)]) : [] + zone_name_or_id = data.oci_dns_zones.dns_zones.zones[0].id + domain = "${var.queue}-${var.instance_type}-${local.cluster_instances_ips_index[tonumber(each.key)]}.${var.zone_name}" + rtype = "A" + items { + domain = "${var.queue}-${var.instance_type}-${local.cluster_instances_ips_index[tonumber(each.key)]}.${var.zone_name}" + rtype = "A" + rdata = "${local.cluster_instances_ips[tonumber(each.key)]}" + ttl = 3600 + } + scope = "PRIVATE" + view_id = data.oci_dns_views.dns_views.views[0].id +} \ No newline at end of file diff --git a/bin/resize.py b/bin/resize.py index f644c14d..9525fee4 100644 --- a/bin/resize.py +++ b/bin/resize.py @@ -8,6 +8,7 @@ import shutil import os import copy +import ipaddress from datetime import datetime def get_metadata(): @@ -605,6 +606,31 @@ def getLaunchInstanceDetails(instance,comp_ocid,cn_ocid,max_previous_index,index if inv_vars.startswith("compute_username"): username=inv_vars.split("compute_username=")[1].strip() break +zone_name=cluster_name+".local" +for inv_vars in inventory_dict["all:vars"]: + if inv_vars.startswith("zone_name"): + zone_name=inv_vars.split("zone_name=")[1].strip() + break +dns_entries=True +for inv_vars in inventory_dict["all:vars"]: + if inv_vars.startswith("dns_entries"): + dns_entries=bool(inv_vars.split("dns_entries=")[1].strip()) + break +queue=None +for inv_vars in inventory_dict["all:vars"]: + if inv_vars.startswith("queue"): + queue=inv_vars.split("queue=")[1].strip() + break +instance_type="" +for inv_vars in inventory_dict["all:vars"]: + if inv_vars.startswith("instance_type"): + instance_type=inv_vars.split("instance_type=")[1].strip() + break +private_subnet_cidr=None +for inv_vars in inventory_dict["all:vars"]: + if inv_vars.startswith("private_subnet"): + private_subnet_cidr=ipaddress.ip_network(inv_vars.split("private_subnet=")[1].strip()) + break hostnames=args.nodes if hostnames is None: @@ -650,6 +676,7 @@ def getLaunchInstanceDetails(instance,comp_ocid,cn_ocid,max_previous_index,index computeManagementClient = oci.core.ComputeManagementClient(config_oci) ComputeManagementClientCompositeOperations = oci.core.ComputeManagementClientCompositeOperations(computeManagementClient) virtualNetworkClient = oci.core.VirtualNetworkClient(config_oci) + dns_client = oci.dns.DnsClient(config_oci) else: signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner() computeClient = oci.core.ComputeClient(config={}, signer=signer) @@ -657,6 +684,7 @@ def getLaunchInstanceDetails(instance,comp_ocid,cn_ocid,max_previous_index,index computeManagementClient = oci.core.ComputeManagementClient(config={}, signer=signer) ComputeManagementClientCompositeOperations = oci.core.ComputeManagementClientCompositeOperations(computeManagementClient) virtualNetworkClient = oci.core.VirtualNetworkClient(config={}, signer=signer) + dns_client = oci.dns.DnsClient(config={}, signer=signer) cn_summary,ip_summary,CN = get_summary(comp_ocid,cluster_name) if cn_summary is None: @@ -687,6 +715,7 @@ def getLaunchInstanceDetails(instance,comp_ocid,cn_ocid,max_previous_index,index cn_instances = get_instances(comp_ocid,cn_ocid,CN) inventory_instances =[] only_inventory_instance=[] + zone_id=dns_client.list_zones(compartment_id=comp_ocid,name=zone_name,zone_type="PRIMARY",scope="PRIVATE").data[0].id for line in inventory_dict['compute_configured']: host=line.split('ansible_host=')[0].strip() ip=line.split("ansible_host=")[1].split("ansible_user=")[0].strip() @@ -767,6 +796,16 @@ def getLaunchInstanceDetails(instance,comp_ocid,cn_ocid,max_previous_index,index else: instance_details = oci.core.models.DetachInstancePoolInstanceDetails(instance_id=instance_id,is_auto_terminate=True,is_decrement_size=True) ComputeManagementClientCompositeOperations.detach_instance_pool_instance_and_wait_for_work_request(ipa_ocid,instance_details) + if dns_entries: + get_rr_set_response = dns_client.delete_rr_set(zone_name_or_id=zone_id,domain=instanceName+"."+zone_name,rtype="A",scope="PRIVATE") + ip=None + for i in cn_instances: + if i['display_name'] == instanceName: + ip = ipaddress.ip_address(i['ip']) + if not ip is None: + index = list(private_subnet_cidr.hosts()).index(ip)+2 + slurm_name=queue+"-"+instance_type+"-"+str(index)+"."+zone_name + get_rr_set_response = dns_client.delete_rr_set(zone_name_or_id=zone_id,domain=slurm_name,rtype="A",scope="PRIVATE") terminated_instances = terminated_instances + 1 print("STDOUT: The instance "+instanceName+" is terminating") except: @@ -794,8 +833,8 @@ def getLaunchInstanceDetails(instance,comp_ocid,cn_ocid,max_previous_index,index # reconfigure(comp_ocid,cn_ocid,inventory,CN) if args.mode == 'add': + cn_instances = get_instances(comp_ocid,cn_ocid,CN) if CN == "CC": - cn_instances = get_instances(comp_ocid,cn_ocid,CN) current_size=len(cn_instances) if len(cn_instances) == 0: print("The resize script cannot work for a compute cluster if the size is there is no node in the cluster") @@ -813,14 +852,23 @@ def getLaunchInstanceDetails(instance,comp_ocid,cn_ocid,max_previous_index,index size = current_size - hostnames_to_remove_len + args.number update_size = oci.core.models.UpdateInstancePoolDetails(size=size) ComputeManagementClientCompositeOperations.update_instance_pool_and_wait_for_state(ipa_ocid,update_size,['RUNNING'],waiter_kwargs={'max_wait_seconds':3600}) - cn_summary,ip_summary,CN = get_summary(comp_ocid,cluster_name) if CN == "CC": - cn_instances = get_instances(comp_ocid,cn_ocid,CN) - newsize=len(cn_instances) + new_cn_instances = get_instances(comp_ocid,cn_ocid,CN) + newsize=len(new_cn_instances) else: + new_cn_instances = get_instances(comp_ocid,cn_ocid,CN) newsize=ip_summary.size - updateTFState(inventory,cluster_name,newsize) + if dns_entries: + for new_instance in new_cn_instances: + if not new_instance in cn_instances: + instanceName=new_instance['display_name'] + ip = ipaddress.ip_address(new_instance['ip']) + index = list(private_subnet_cidr.hosts()).index(ip)+2 + slurm_name=queue+"-"+instance_type+"-"+str(index)+"."+zone_name + get_rr_set_response = dns_client.update_rr_set(zone_name_or_id=zone_id,domain=slurm_name,rtype="A",scope="PRIVATE",update_rr_set_details=oci.dns.models.UpdateRRSetDetails(items=[oci.dns.models.RecordDetails(domain=slurm_name,rdata=new_instance['ip'],rtype="A",ttl=3600,)])) + get_rr_set_response = dns_client.update_rr_set(zone_name_or_id=zone_id,domain=instanceName+"."+zone_name,rtype="A",scope="PRIVATE",update_rr_set_details=oci.dns.models.UpdateRRSetDetails(items=[oci.dns.models.RecordDetails(domain=instanceName+"."+zone_name,rdata=new_instance['ip'],rtype="A",ttl=3600)])) + updateTFState(inventory,cluster_name,newsize) if newsize == current_size: print("No node was added, please check the work requests of the Cluster Network and Instance Pool to see why") exit(1) diff --git a/conf/variables.tpl b/conf/variables.tpl index e91c0b16..c73e875c 100755 --- a/conf/variables.tpl +++ b/conf/variables.tpl @@ -23,11 +23,14 @@ variable "image" { default = "##IMAGE##" } variable "vcn_compartment" { default = ""} variable "use_existing_vcn" {default = true} variable "vcn_subnet" {default = "${vcn_subnet}"} +variable "vcn_id" {default = "${vcn_id}"} variable "public_subnet_id" { default = "${public_subnet_id}"} variable "public_subnet" {default = "${public_subnet}"} variable "private_subnet_id" { default = "##PRIVATE_SUBNET_ID##"} variable "private_subnet" {default = "##PRIVATE_SUBNET##"} variable "rdma_subnet" { default = "${rdma_subnet}" } +variable "zone_name" {default = "${zone_name}"} +variable "dns_entries" {default = "${dns_entries}"} variable "slurm" { default = ${slurm} } variable "rack_aware" { default = ${rack_aware} } variable "pyxis" { default = ${pyxis} } diff --git a/controller.tf b/controller.tf index 722b62ce..3e1df6ef 100644 --- a/controller.tf +++ b/controller.tf @@ -241,6 +241,8 @@ resource "null_resource" "cluster" { private_subnet = data.oci_core_subnet.private_subnet.cidr_block, rdma_network = cidrhost(var.rdma_subnet, 0), rdma_netmask = cidrnetmask(var.rdma_subnet), + zone_name = local.zone_name, + dns_entries = var.dns_entries, nfs = var.node_count > 0 && var.use_scratch_nfs ? local.cluster_instances_names[0] : "", home_nfs = var.home_nfs, create_fss = var.create_fss, @@ -402,6 +404,9 @@ resource "null_resource" "cluster" { region = var.region, tenancy_ocid = var.tenancy_ocid, vcn_subnet = var.vcn_subnet, + vcn_id = local.vcn_id, + zone_name = local.zone_name, + dns_entries = var.dns_entries, cluster_block_volume_size = var.cluster_block_volume_size, cluster_block_volume_performance = var.cluster_block_volume_performance, ssh_cidr = var.ssh_cidr, @@ -545,4 +550,22 @@ resource "local_file" "PAR" { depends_on = [oci_objectstorage_preauthrequest.RDMA_NIC_metrics_par] content = "https://objectstorage.${var.region}.oraclecloud.com${oci_objectstorage_preauthrequest.RDMA_NIC_metrics_par[0].access_uri}" filename = "${local.par_path}/PAR_file_for_metrics" - } \ No newline at end of file + } + + +resource "oci_dns_rrset" "rrset-controller" { + count = var.dns_entries ? 1 : 0 + zone_name_or_id = data.oci_dns_zones.dns_zones.zones[0].id + domain = "${oci_core_instance.controller.display_name}.${local.zone_name}" + rtype = "A" + items { + domain = "${oci_core_instance.controller.display_name}.${local.zone_name}" + rtype = "A" + rdata = oci_core_instance.controller.private_ip + ttl = 3600 + } + scope = "PRIVATE" + view_id = data.oci_dns_views.dns_views.views[0].id +} + +#oci dns record rrset update --zone-name-or-id ocid1.dns-zone.oc1.ca-toronto-1.aaaaaaaadwpfuij3w7jpg3sj6gzc5ete2yeknrmjgwzvs6qytgkqad2vhbmq --domain mint-ocelot-controller.mint-ocelot.local --rtype A --auth instance_principal --scope PRIVATE --view-id ocid1.dnsview.oc1.ca-toronto-1.aaaaaaaamhhzrbwe4f3rx5i2hx2xlnubfjc37uvy3e7bjrbyaln5o7zjfvpa --items '[{ "rdata":"1.1.1.1","ttl":300,"domain":"mint-ocelot-controller.mint-ocelot.local","rtype":"A"}]' --force \ No newline at end of file diff --git a/data.tf b/data.tf index 6528de7f..e5dd4277 100755 --- a/data.tf +++ b/data.tf @@ -76,4 +76,19 @@ data "oci_resourcemanager_private_endpoint_reachable_ip" "private_endpoint_reach count = (var.private_deployment && var.login_node) ? 1 : 0 private_endpoint_id = oci_resourcemanager_private_endpoint.rms_private_endpoint[0].id private_ip = tostring(oci_core_instance.login[0].private_ip) +} + +data "oci_dns_views" "dns_views" { + depends_on = [local.controller_subnet, oci_core_vcn.vcn] + compartment_id = var.targetCompartment + scope = "PRIVATE" + display_name = data.oci_core_vcn.vcn.display_name +} + +data "oci_dns_zones" "dns_zones" { + depends_on = [local.controller_subnet, oci_core_vcn.vcn, oci_dns_zone.dns_zone ] + compartment_id = var.targetCompartment + name = local.zone_name + zone_type = "PRIMARY" + scope = "PRIVATE" } \ No newline at end of file diff --git a/inventory.tpl b/inventory.tpl index 6b1582ec..f39e534e 100755 --- a/inventory.tpl +++ b/inventory.tpl @@ -76,4 +76,6 @@ inst_prin = ${inst_prin} api_fingerprint = ${api_fingerprint} api_user_ocid = ${api_user_ocid} sacct_limits=${sacct_limits} -use_compute_agent=${use_compute_agent} \ No newline at end of file +use_compute_agent=${use_compute_agent} +zone_name=${zone_name} +dns_entries=${dns_entries} \ No newline at end of file diff --git a/locals.tf b/locals.tf index 25e03d70..a43c3a9d 100755 --- a/locals.tf +++ b/locals.tf @@ -13,6 +13,8 @@ locals { login_ocpus = ( var.login_shape == "VM.DenseIO.E4.Flex" || var.login_shape == "VM.DenseIO.E5.Flex" ) ? var.login_ocpus_denseIO_flex : var.login_ocpus // ips of the instances cluster_instances_ips = var.compute_cluster ? oci_core_instance.compute_cluster_instances.*.private_ip : var.cluster_network ? data.oci_core_instance.cluster_network_instances.*.private_ip : data.oci_core_instance.instance_pool_instances.*.private_ip + first_vcn_ip = cidrhost(data.oci_core_subnet.private_subnet.cidr_block,0) + cluster_instances_ips_index = [for ip in local.cluster_instances_ips : tostring((tonumber(split(".",ip)[3])-tonumber(split(".",local.first_vcn_ip)[3]))+256*(tonumber(split(".",ip)[2])-tonumber(split(".",local.first_vcn_ip)[2]))+1)] // vcn id derived either from created vcn or existing if specified vcn_id = var.use_existing_vcn ? var.vcn_id : element(concat(oci_core_vcn.vcn.*.id, [""]), 0) @@ -67,4 +69,5 @@ locals { timeout_per_batch= var.cluster_network ? 30 : 15 timeout_ip = join("",[ (( var.node_count - ( var.node_count % 20 ) )/20 + 1 ) * local.timeout_per_batch,"m"]) + zone_name = var.use_existing_vcn ? var.zone_name : "${local.cluster_name}.local" } diff --git a/login.tf b/login.tf index d8d1b59d..1aba036d 100644 --- a/login.tf +++ b/login.tf @@ -57,3 +57,18 @@ resource "oci_core_instance" "login" { assign_public_ip = local.login_bool_ip } } + +resource "oci_dns_rrset" "rrset-login" { + count = var.login_node && var.dns_entries ? 1 : 0 + zone_name_or_id = data.oci_dns_zones.dns_zones.zones[0].id + domain = "${var.login_node ? oci_core_instance.login[0].display_name : ""}.${local.zone_name}" + rtype = "A" + items { + domain = "${var.login_node ? oci_core_instance.login[0].display_name : ""}.${local.zone_name}" + rtype = "A" + rdata = var.login_node ? oci_core_instance.login[0].private_ip: "" + ttl = 3600 + } + scope = "PRIVATE" + view_id = data.oci_dns_views.dns_views.views[0].id +} \ No newline at end of file diff --git a/network.tf b/network.tf index 57617907..78196141 100755 --- a/network.tf +++ b/network.tf @@ -145,7 +145,20 @@ resource "oci_core_route_table" "private_route_table" { network_entity_id = oci_core_service_gateway.sg1[0].id } } - +resource "oci_core_dhcp_options" "cluster_dhcp_options" { + count = var.use_existing_vcn ? 0 : 1 + compartment_id = var.targetCompartment + options { + type = "DomainNameServer" + server_type = "VcnLocalPlusInternet" + } + options { + type = "SearchDomain" + search_domain_names = [ "${local.zone_name}" ] + } + vcn_id = oci_core_vcn.vcn[0].id + display_name = "${local.cluster_name}_DHCP" +} resource "oci_core_subnet" "public-subnet" { count = (var.use_existing_vcn || var.private_deployment) ? 0 : 1 # availability_domain = var.ad @@ -156,6 +169,7 @@ resource "oci_core_subnet" "public-subnet" { dns_label = "public" display_name = "${local.cluster_name}_public_subnet" route_table_id = oci_core_route_table.public_route_table[0].id + dhcp_options_id = oci_core_dhcp_options.cluster_dhcp_options[0].id } resource "oci_core_subnet" "private-subnet" { @@ -169,4 +183,45 @@ resource "oci_core_subnet" "private-subnet" { display_name = "${local.cluster_name}_private_subnet${count.index+1}" prohibit_public_ip_on_vnic = true route_table_id = oci_core_route_table.private_route_table[0].id + dhcp_options_id = oci_core_dhcp_options.cluster_dhcp_options[0].id } + +resource "oci_dns_zone" "dns_zone" { + count = var.use_existing_vcn ? 0 : 1 + compartment_id = var.targetCompartment + name = "${local.cluster_name}.local" #oci_core_dhcp_options.cluster_dhcp_options[0].options.search_domain_names[0] + zone_type = "PRIMARY" + scope = "PRIVATE" + view_id = data.oci_dns_views.dns_views.views[0].id +} + +resource "oci_dns_rrset" "rrset-cluster-network-OCI" { + for_each = var.dns_entries ? toset([for v in range(var.node_count) : tostring(v)]) : [] + zone_name_or_id = data.oci_dns_zones.dns_zones.zones[0].id + domain = "${local.cluster_instances_names[tonumber(each.key)]}.${local.zone_name}" + rtype = "A" + items { + domain = "${local.cluster_instances_names[tonumber(each.key)]}.${local.zone_name}" + rtype = "A" + rdata = "${local.cluster_instances_ips[tonumber(each.key)]}" + ttl = 3600 + } + scope = "PRIVATE" + view_id = data.oci_dns_views.dns_views.views[0].id +} + +resource "oci_dns_rrset" "rrset-cluster-network-SLURM" { + + for_each = var.slurm && var.dns_entries ? toset([for v in range(var.node_count) : tostring(v)]) : [] + zone_name_or_id = data.oci_dns_zones.dns_zones.zones[0].id + domain = "${var.queue}-permanent-${local.cluster_instances_ips_index[tonumber(each.key)]}.${local.zone_name}" + rtype = "A" + items { + domain = "${var.queue}-permanent-${local.cluster_instances_ips_index[tonumber(each.key)]}.${local.zone_name}" + rtype = "A" + rdata = "${local.cluster_instances_ips[tonumber(each.key)]}" + ttl = 3600 + } + scope = "PRIVATE" + view_id = data.oci_dns_views.dns_views.views[0].id +} \ No newline at end of file diff --git a/schema.yaml b/schema.yaml index 00db2f07..139f87c0 100755 --- a/schema.yaml +++ b/schema.yaml @@ -156,6 +156,8 @@ variableGroups: - ${private_subnet} - ${rdma_subnet} - ${additional_subnet} + - ${dns_entries} + - ${zone_name} - title: "Software" variables: - ${privilege_sudo} @@ -980,6 +982,17 @@ variables: hidePublicSubnet: true visible: ${use_existing_vcn} required: true + dns_entries: + title: DNS entry + type: boolean + default: true + description: "Only available for a private zone" + zone_name: + title: Private Zone Name + description: "The zone needs to be private for the stack to be able to add entries" + type: string + visible: ${use_existing_vcn} + required: true vcn_subnet: type: string title: "VCN IP range" diff --git a/slurm_ha.tf b/slurm_ha.tf index 48ccc2a3..9981d4b2 100644 --- a/slurm_ha.tf +++ b/slurm_ha.tf @@ -195,6 +195,8 @@ resource "null_resource" "cluster_backup" { private_subnet = data.oci_core_subnet.private_subnet.cidr_block, rdma_network = cidrhost(var.rdma_subnet, 0), rdma_netmask = cidrnetmask(var.rdma_subnet), + zone_name = local.zone_name, + dns_entries = var.dns_entries, nfs = var.node_count > 0 ? local.cluster_instances_names[0] : "", home_nfs = var.home_nfs, create_fss = var.create_fss, @@ -356,6 +358,9 @@ resource "null_resource" "cluster_backup" { region = var.region, tenancy_ocid = var.tenancy_ocid, vcn_subnet = var.vcn_subnet, + vcn_id = local.vcn_id, + zone_name = local.zone_name, + dns_entries = var.dns_entries, cluster_block_volume_size = var.cluster_block_volume_size, cluster_block_volume_performance = var.cluster_block_volume_performance, ssh_cidr = var.ssh_cidr, @@ -427,3 +432,19 @@ resource "null_resource" "cluster_backup" { } } } + + +resource "oci_dns_rrset" "rrset-backup" { + count = var.slurm_ha && var.dns_entries ? 1 : 0 + zone_name_or_id = data.oci_dns_zones.dns_zones.zones[0].id + domain = "${var.slurm_ha ? oci_core_instance.backup[0].display_name : ""}.${local.zone_name}" + rtype = "A" + items { + domain = "${var.slurm_ha ? oci_core_instance.backup[0].display_name : ""}.${local.zone_name}" + rtype = "A" + rdata = var.slurm_ha ? oci_core_instance.backup[0].private_ip: "" + ttl = 3600 + } + scope = "PRIVATE" + view_id = data.oci_dns_views.dns_views.views[0].id +} \ No newline at end of file diff --git a/variables.tf b/variables.tf index 79cc9636..4bea90d8 100755 --- a/variables.tf +++ b/variables.tf @@ -253,4 +253,10 @@ variable "marketplace_listing_login" { variable "marketplace_listing_controller" { default = "HPC_OL7" } +variable "zone_name" { + default = "" +} +variable "dns_entries" { + default = true +} \ No newline at end of file From bef9659ae3d58160320a2c4e154c37986a840d8d Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 14 Feb 2024 15:04:33 -0700 Subject: [PATCH 25/40] Edit the serach domain if not selecting DNS entries --- network.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network.tf b/network.tf index 78196141..6952fd64 100755 --- a/network.tf +++ b/network.tf @@ -154,7 +154,7 @@ resource "oci_core_dhcp_options" "cluster_dhcp_options" { } options { type = "SearchDomain" - search_domain_names = [ "${local.zone_name}" ] + search_domain_names = [ "${var.dns_entries? local.zone_name : "cluster.oraclevcn.com"}" ] } vcn_id = oci_core_vcn.vcn[0].id display_name = "${local.cluster_name}_DHCP" From 532a1e45ef7a3ed8411bed4176bf777e8b2a73dd Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 21 Feb 2024 11:51:37 -0700 Subject: [PATCH 26/40] Redirect soe output to null --- bin/controller.sh | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/bin/controller.sh b/bin/controller.sh index f9c1c666..f48b7f2d 100644 --- a/bin/controller.sh +++ b/bin/controller.sh @@ -39,7 +39,7 @@ if [ $ID == "ol" ] || [ $ID == "centos" ] ; then elif [ $vid == 8 ] ; then sudo yum makecache --enablerepo=$repo sudo yum install --enablerepo=$repo -y python38.x86_64 - sudo python3.8 -m pip install ansible cryptography netaddr + sudo python3.8 -m pip install ansible cryptography netaddr > /dev/null sudo mkdir /etc/ansible sudo ln -s /usr/local/bin/ansible-playbook /bin/ansible-playbook sudo ln -s /usr/local/bin/ansible /bin/ansible @@ -47,12 +47,12 @@ if [ $ID == "ol" ] || [ $ID == "centos" ] ; then sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo sudo sed -i 's/$releasever/'"${vid}"'/g' /etc/yum.repos.d/hashicorp.repo sudo yum install -y terraform - sudo python3 -m pip install -U pip - sudo python3 -m pip install netaddr --upgrade - sudo python3 -m pip install setuptools_rust --upgrade - sudo python3 -m pip install requests --upgrade - sudo python3 -m pip install urllib3 --upgrade - sudo python3 -m pip install oci-cli --upgrade + sudo python3 -m pip install -U pip > /dev/null + sudo python3 -m pip install netaddr --upgrade > /dev/null + sudo python3 -m pip install setuptools_rust --upgrade > /dev/null + sudo python3 -m pip install requests --upgrade > /dev/null + sudo python3 -m pip install urllib3 --upgrade > /dev/null + sudo python3 -m pip install oci-cli --upgrade > /dev/null elif [ $ID == "debian" ] || [ $ID == "ubuntu" ] ; then @@ -123,18 +123,18 @@ elif [ $ID == "debian" ] || [ $ID == "ubuntu" ] ; then fi fi fix_apt - sudo python3 -m pip install -U pip - sudo python3 -m pip install netaddr --upgrade - sudo python3 -m pip install requests --upgrade - sudo python3 -m pip install urllib3 --upgrade - pip install pip --upgrade - pip install pyopenssl --upgrade + sudo python3 -m pip install -U pip > /dev/null + sudo python3 -m pip install netaddr --upgrade > /dev/null + sudo python3 -m pip install requests --upgrade > /dev/null + sudo python3 -m pip install urllib3 --upgrade > /dev/null + pip install pip --upgrade > /dev/null + pip install pyopenssl --upgrade > /dev/null # install oci-cli (add --oci-cli-version 3.23.3 or version that you know works if the latest does not work ) - bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)" -s --accept-all-defaults + bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)" -s --accept-all-defaults > /dev/null # install oci module - pip install oci + pip install oci > /dev/null wget -O- https://apt.releases.hashicorp.com/gpg | \ gpg --dearmor | \ @@ -180,7 +180,7 @@ if [ ! -d /etc/ansible ] ; then fi fi -ansible-config init --disabled -t all | sudo tee /etc/ansible/ansible.cfg +ansible-config init --disabled -t all | sudo tee /etc/ansible/ansible.cfg > /dev/null sudo sed -i "s/^\(#\|;\)forks.*/forks = ${forks}/" /etc/ansible/ansible.cfg sudo sed -i "s/^\(#\|;\)fact_caching=.*/fact_caching=jsonfile/" /etc/ansible/ansible.cfg sudo sed -i "0,/^\(#\|;\)fact_caching_connection.*/s//fact_caching_connection=\/tmp\/ansible/" /etc/ansible/ansible.cfg From f55236579b53879f6f1313d6e473cdc21331a206 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 21 Feb 2024 11:51:57 -0700 Subject: [PATCH 27/40] Add check for hostname change --- playbooks/roles/hostname/tasks/el.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/playbooks/roles/hostname/tasks/el.yml b/playbooks/roles/hostname/tasks/el.yml index 682efa32..98966fbe 100755 --- a/playbooks/roles/hostname/tasks/el.yml +++ b/playbooks/roles/hostname/tasks/el.yml @@ -5,4 +5,21 @@ - keyword: "{% for partition in queues %}{% for instance in partition.instance_types %}{% if instance.name == instance_type %}{{instance.instance_keyword}}{% endif %}{% endfor %}{% endfor %}" hostname: name: "{{queue}}-{{keyword}}-node-{{index}}" - when: ('compute' in group_names ) \ No newline at end of file + when: ('compute' in group_names ) + +- name: Check Hostname + vars: + - index: "{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] | ansible.netcommon.ipsubnet(hostvars[inventory_hostname]['private_subnet']) }}" + - keyword: "{% for partition in queues %}{% for instance in partition.instance_types %}{% if instance.name == instance_type %}{{instance.instance_keyword}}{% endif %}{% endfor %}{% endfor %}" + shell: + cmd: "hostname" + register: hostname_output + when: ('compute' in group_names ) + +- name: update hostname for HPC cluster + vars: + - index: "{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] | ansible.netcommon.ipsubnet(hostvars[inventory_hostname]['private_subnet']) }}" + - keyword: "{% for partition in queues %}{% for instance in partition.instance_types %}{% if instance.name == instance_type %}{{instance.instance_keyword}}{% endif %}{% endfor %}{% endfor %}" + hostname: + name: "{{queue}}-{{keyword}}-node-{{index}}" + when: ('compute' in group_names ) and ( hostname_output.stdout != ansible_fqdn.split('.')[0] ) From 5a4c3a5617550bf29ef9a36ce5cc918e6244bdf2 Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 21 Feb 2024 11:52:13 -0700 Subject: [PATCH 28/40] NCCL_CROSS_NIC=1 --- samples/gpu/nccl_run_allreduce_H100.sbatch | 2 +- samples/gpu/nccl_run_allreduce_H100.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/gpu/nccl_run_allreduce_H100.sbatch b/samples/gpu/nccl_run_allreduce_H100.sbatch index 830870c4..f232a790 100644 --- a/samples/gpu/nccl_run_allreduce_H100.sbatch +++ b/samples/gpu/nccl_run_allreduce_H100.sbatch @@ -43,7 +43,7 @@ fi --bind-to numa \ -npernode 8 \ --mca coll ^hcoll \ - -x NCCL_CROSS_NIC=0 \ + -x NCCL_CROSS_NIC=1 \ -x NCCL_SOCKET_NTHREADS=16 \ -x NCCL_DEBUG=WARN \ -x NCCL_CUMEM_ENABLE=0 \ diff --git a/samples/gpu/nccl_run_allreduce_H100.sh b/samples/gpu/nccl_run_allreduce_H100.sh index 520f125d..5b776816 100644 --- a/samples/gpu/nccl_run_allreduce_H100.sh +++ b/samples/gpu/nccl_run_allreduce_H100.sh @@ -57,7 +57,7 @@ do --bind-to numa \ -npernode 8 \ --mca coll ^hcoll \ - -x NCCL_CROSS_NIC=0 \ + -x NCCL_CROSS_NIC=1 \ -x NCCL_SOCKET_NTHREADS=16 \ -x NCCL_DEBUG=WARN \ -x NCCL_CUMEM_ENABLE=0 \ From e5a76376cd50b9230541e39d9b056cc04f46e1ef Mon Sep 17 00:00:00 2001 From: arnaudfroidmont Date: Wed, 21 Feb 2024 12:13:24 -0700 Subject: [PATCH 29/40] Revert "NCCL_CROSS_NIC=1" This reverts commit 5a4c3a5617550bf29ef9a36ce5cc918e6244bdf2. --- samples/gpu/nccl_run_allreduce_H100.sbatch | 2 +- samples/gpu/nccl_run_allreduce_H100.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/gpu/nccl_run_allreduce_H100.sbatch b/samples/gpu/nccl_run_allreduce_H100.sbatch index f232a790..830870c4 100644 --- a/samples/gpu/nccl_run_allreduce_H100.sbatch +++ b/samples/gpu/nccl_run_allreduce_H100.sbatch @@ -43,7 +43,7 @@ fi --bind-to numa \ -npernode 8 \ --mca coll ^hcoll \ - -x NCCL_CROSS_NIC=1 \ + -x NCCL_CROSS_NIC=0 \ -x NCCL_SOCKET_NTHREADS=16 \ -x NCCL_DEBUG=WARN \ -x NCCL_CUMEM_ENABLE=0 \ diff --git a/samples/gpu/nccl_run_allreduce_H100.sh b/samples/gpu/nccl_run_allreduce_H100.sh index 5b776816..520f125d 100644 --- a/samples/gpu/nccl_run_allreduce_H100.sh +++ b/samples/gpu/nccl_run_allreduce_H100.sh @@ -57,7 +57,7 @@ do --bind-to numa \ -npernode 8 \ --mca coll ^hcoll \ - -x NCCL_CROSS_NIC=1 \ + -x NCCL_CROSS_NIC=0 \ -x NCCL_SOCKET_NTHREADS=16 \ -x NCCL_DEBUG=WARN \ -x NCCL_CUMEM_ENABLE=0 \ From e3a1fa678b17d7cee0c628c45479f5b29956fc56 Mon Sep 17 00:00:00 2001 From: Arnaud Froidmont Date: Wed, 28 Feb 2024 12:04:26 -0700 Subject: [PATCH 30/40] Update provider version --- autoscaling/tf_init/versions.tf | 2 +- versions.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/autoscaling/tf_init/versions.tf b/autoscaling/tf_init/versions.tf index 69ac6583..57e63004 100755 --- a/autoscaling/tf_init/versions.tf +++ b/autoscaling/tf_init/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { oci = { source = "oracle/oci" - version = "5.25.0" + version = "5.30.0" } } } \ No newline at end of file diff --git a/versions.tf b/versions.tf index 69ac6583..57e63004 100755 --- a/versions.tf +++ b/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { oci = { source = "oracle/oci" - version = "5.25.0" + version = "5.30.0" } } } \ No newline at end of file From f3325f3594e6e7854a42b8d79d24a8646d7382ef Mon Sep 17 00:00:00 2001 From: Arnaud Froidmont Date: Wed, 28 Feb 2024 12:04:53 -0700 Subject: [PATCH 31/40] Move yq install up --- playbooks/site.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/playbooks/site.yml b/playbooks/site.yml index 1f9c7ed3..a3d3f88f 100644 --- a/playbooks/site.yml +++ b/playbooks/site.yml @@ -19,6 +19,12 @@ name: fix_broken when: ansible_os_family == 'Debian' +- hosts: controller, slurm_backup + become: true + tasks: + - include_role: + name: yaml + - hosts: all become: true vars: @@ -256,12 +262,6 @@ name: hyperthreading when: not hyperthreading|default(false)|bool -- hosts: controller, slurm_backup - become: true - tasks: - - include_role: - name: yaml - - hosts: all tasks: - include_role: From c1ad52018986d6139b1cae479fbf344e5b149ab2 Mon Sep 17 00:00:00 2001 From: Arnaud Froidmont Date: Wed, 28 Feb 2024 12:05:23 -0700 Subject: [PATCH 32/40] Add BIOS seetings to launch --- .../tf_init/cluster-network-configuration.tf | 14 +++- .../tf_init/instance-pool-configuration.tf | 13 +++- autoscaling/tf_init/locals.tf | 2 + cluster-network-configuration.tf | 13 ++++ conf/variables.tpl | 22 ++++++ controller.tf | 9 ++- instance-pool-configuration.tf | 14 +++- locals.tf | 1 + schema.yaml | 67 +++++++++++++++++++ slurm_ha.tf | 9 ++- variables.tf | 23 ++++++- 11 files changed, 181 insertions(+), 6 deletions(-) diff --git a/autoscaling/tf_init/cluster-network-configuration.tf b/autoscaling/tf_init/cluster-network-configuration.tf index 912c3c92..6b2805f1 100755 --- a/autoscaling/tf_init/cluster-network-configuration.tf +++ b/autoscaling/tf_init/cluster-network-configuration.tf @@ -44,6 +44,18 @@ resource "oci_core_instance_configuration" "cluster-network-instance_configurati } } + dynamic "platform_config" { + for_each = var.BIOS ? range(1) : [] + content { + type = local.platform_type + are_virtual_instructions_enabled = var.virt_instr + is_access_control_service_enabled = var.access_ctrl + is_input_output_memory_management_unit_enabled = var.IOMMU + is_symmetric_multi_threading_enabled = var.SMT + numa_nodes_per_socket = var.numa_nodes_per_socket == "Default" ? (local.platform_type == "GENERIC_BM" ? "NPS1": "NPS4" ): var.numa_nodes_per_socket + percentage_of_cores_enabled = var.percentage_of_cores_enabled == "Default" ? 100 : tonumber(var.percentage_of_cores_enabled) + } + } shape = var.cluster_network_shape source_details { source_type = "image" @@ -52,7 +64,7 @@ resource "oci_core_instance_configuration" "cluster-network-instance_configurati } } } - + source = "NONE" } diff --git a/autoscaling/tf_init/instance-pool-configuration.tf b/autoscaling/tf_init/instance-pool-configuration.tf index fea68425..31c31ab7 100755 --- a/autoscaling/tf_init/instance-pool-configuration.tf +++ b/autoscaling/tf_init/instance-pool-configuration.tf @@ -29,7 +29,18 @@ resource "oci_core_instance_configuration" "instance_pool_configuration" { memory_in_gbs = var.instance_pool_custom_memory ? var.instance_pool_memory : 16 * shape_config.value } } - + dynamic "platform_config" { + for_each = var.BIOS ? range(1) : [] + content { + type = local.platform_type + are_virtual_instructions_enabled = var.virt_instr + is_access_control_service_enabled = var.access_ctrl + is_input_output_memory_management_unit_enabled = var.IOMMU + is_symmetric_multi_threading_enabled = var.SMT + numa_nodes_per_socket = var.numa_nodes_per_socket == "Default" ? (local.platform_type == "GENERIC_BM" ? "NPS1": "NPS4" ): var.numa_nodes_per_socket + percentage_of_cores_enabled = var.percentage_of_cores_enabled == "Default" ? 100 : tonumber(var.percentage_of_cores_enabled) + } + } source_details { source_type = "image" boot_volume_size_in_gbs = var.boot_volume_size diff --git a/autoscaling/tf_init/locals.tf b/autoscaling/tf_init/locals.tf index 086873f6..4effdfb6 100755 --- a/autoscaling/tf_init/locals.tf +++ b/autoscaling/tf_init/locals.tf @@ -38,4 +38,6 @@ locals { timeout_per_batch= var.cluster_network ? var.use_multiple_ads ? 15 : 30 : var.use_multiple_ads ? 6 : 15 timeout_ip = join("",[ (( var.node_count - ( var.node_count % 20 ) )/20 + 1 ) * local.timeout_per_batch,"m"]) + platform_type = local.shape == "BM.GPU4.8" ? "AMD_ROME_BM_GPU" : local.shape == "BM.GPU.B4.8" || local.shape == "BM.GPU.H100.8" || local.shape == "BM.GPU.A100-v2.8" ? "AMD_MILAN_BM_GPU" : local.shape == "BM.Standard.E3.128" ? "AMD_ROME_BM" : local.shape == "BM.Standard.E4.128" || local.shape == "BM.DenseIO.E4.128" ? "AMD_MILAN_BM" : "GENERIC_BM" + } diff --git a/cluster-network-configuration.tf b/cluster-network-configuration.tf index 82a3fb60..1c097ca5 100755 --- a/cluster-network-configuration.tf +++ b/cluster-network-configuration.tf @@ -48,6 +48,19 @@ resource "oci_core_instance_configuration" "cluster-network-instance_configurati } } + + dynamic "platform_config" { + for_each = var.BIOS ? range(1) : [] + content { + type = local.platform_type + are_virtual_instructions_enabled = var.virt_instr + is_access_control_service_enabled = var.access_ctrl + is_input_output_memory_management_unit_enabled = var.IOMMU + is_symmetric_multi_threading_enabled = var.SMT + numa_nodes_per_socket = var.numa_nodes_per_socket == "Default" ? (local.platform_type == "GENERIC_BM" ? "NPS1": "NPS4" ): var.numa_nodes_per_socket + percentage_of_cores_enabled = var.percentage_of_cores_enabled == "Default" ? 100 : tonumber(var.percentage_of_cores_enabled) + } + } shape = var.cluster_network_shape diff --git a/conf/variables.tpl b/conf/variables.tpl index c73e875c..62582a98 100755 --- a/conf/variables.tpl +++ b/conf/variables.tpl @@ -138,3 +138,25 @@ variable "log_vol" { default = "${log_vol}" } variable "redundancy" { default = "${redundancy}" } variable "instance_pool_ocpus_denseIO_flex" { default = "##OCPU##"} + +variable "BIOS" { + default = ${BIOS} +} +variable "IOMMU" { + default = ${IOMMU} +} +variable "SMT" { + default = ${SMT} +} +variable "virt_instr" { + default = ${virt_instr} +} +variable "access_ctrl" { + default = ${access_ctrl} +} +variable "numa_nodes_per_socket" { + default = "${numa_nodes_per_socket}" +} +variable "percentage_of_cores_enabled" { + default = "${percentage_of_cores_enabled}" +} \ No newline at end of file diff --git a/controller.tf b/controller.tf index 3e1df6ef..b215ab82 100644 --- a/controller.tf +++ b/controller.tf @@ -438,7 +438,14 @@ resource "null_resource" "cluster" { compute_username = var.compute_username, pam = var.pam, sacct_limits = var.sacct_limits, - use_compute_agent = var.use_compute_agent + use_compute_agent = var.use_compute_agent, + BIOS = var.BIOS, + IOMMU = var.IOMMU, + SMT = var.SMT, + virt_instr = var.virt_instr, + access_ctrl = var.access_ctrl, + numa_nodes_per_socket = var.numa_nodes_per_socket, + percentage_of_cores_enabled = var.percentage_of_cores_enabled }) destination = "/opt/oci-hpc/conf/variables.tf" diff --git a/instance-pool-configuration.tf b/instance-pool-configuration.tf index 04b2a23d..b28dbe5c 100755 --- a/instance-pool-configuration.tf +++ b/instance-pool-configuration.tf @@ -33,7 +33,19 @@ resource "oci_core_instance_configuration" "instance_pool_configuration" { memory_in_gbs = var.instance_pool_custom_memory ? var.instance_pool_memory : 16 * shape_config.value } } - + + dynamic "platform_config" { + for_each = var.BIOS ? range(1) : [] + content { + type = local.platform_type + are_virtual_instructions_enabled = var.virt_instr + is_access_control_service_enabled = var.access_ctrl + is_input_output_memory_management_unit_enabled = var.IOMMU + is_symmetric_multi_threading_enabled = var.SMT + numa_nodes_per_socket = var.numa_nodes_per_socket == "Default" ? (local.platform_type == "GENERIC_BM" ? "NPS1": "NPS4" ): var.numa_nodes_per_socket + percentage_of_cores_enabled = var.percentage_of_cores_enabled == "Default" ? 100 : tonumber(var.percentage_of_cores_enabled) + } + } source_details { source_type = "image" boot_volume_size_in_gbs = var.boot_volume_size diff --git a/locals.tf b/locals.tf index a43c3a9d..f87a3b68 100755 --- a/locals.tf +++ b/locals.tf @@ -70,4 +70,5 @@ locals { timeout_ip = join("",[ (( var.node_count - ( var.node_count % 20 ) )/20 + 1 ) * local.timeout_per_batch,"m"]) zone_name = var.use_existing_vcn ? var.zone_name : "${local.cluster_name}.local" + platform_type = local.shape == "BM.GPU4.8" ? "AMD_ROME_BM_GPU" : local.shape == "BM.GPU.B4.8" || local.shape == "BM.GPU.H100.8" || local.shape == "BM.GPU.A100-v2.8" ? "AMD_MILAN_BM_GPU" : local.shape == "BM.Standard.E3.128" ? "AMD_ROME_BM" : local.shape == "BM.Standard.E4.128" || local.shape == "BM.DenseIO.E4.128" ? "AMD_MILAN_BM" : "GENERIC_BM" } diff --git a/schema.yaml b/schema.yaml index 139f87c0..5b291702 100755 --- a/schema.yaml +++ b/schema.yaml @@ -64,6 +64,14 @@ variableGroups: - ${compute_image_compartment} - ${image} - ${image_ocid} + - ${BIOS} + - ${IOMMU} + - ${SMT} + - ${virt_instr} + - ${access_ctrl} + - ${numa_nodes_per_socket} + - ${percentage_of_cores_enabled} + - title: "Additional Login Node" variables: - ${login_node} @@ -822,6 +830,65 @@ variables: - and: - ${unsupported} + BIOS: + title: "Modify BIOS options" + description: "Make sure that the BIOS options are changeable for the specific shape selected" + type: boolean + default: false + visible: true + + IOMMU: + title: "IOMMU enabled" + type: boolean + default: false + visible: ${BIOS} + + SMT: + title: "SMT Enabled" + type: boolean + default: true + visible: ${BIOS} + + virt_instr: + title: "Virtualization instructions" + description: "Virtualization instructions include Secure Virtual Machine for AMD shapes or VT-x for Intel shapes" + type: boolean + default: false + visible: ${BIOS} + + access_ctrl: + title: "Access Control Service" + description: "Access control service lets the platform enforce PCIe device isolation" + type: boolean + default: false + visible: ${BIOS} + + numa_nodes_per_socket: + title: "Numa Node per Socket" + description: "NUMA Settings" + type: enum + enum: + - "Default" + - "NPS0" + - "NPS1" + - "NPS2" + - "NPS4" + default: "Default" + visible: ${BIOS} + + percentage_of_cores_enabled: + title: "Numa Node per Socket" + description: "NUMA Settings" + type: enum + enum: + - "Default" + - "25" + - "50" + - "75" + - "100" + default: "Default" + visible: ${BIOS} + use_advanced: type: boolean title: "Show advanced storage options" diff --git a/slurm_ha.tf b/slurm_ha.tf index 9981d4b2..36dc60db 100644 --- a/slurm_ha.tf +++ b/slurm_ha.tf @@ -392,7 +392,14 @@ resource "null_resource" "cluster_backup" { controller_username = var.controller_username, compute_username = var.compute_username, use_multiple_ads = var.use_multiple_ads, - use_compute_agent = var.use_compute_agent + use_compute_agent = var.use_compute_agent, + BIOS = var.BIOS, + IOMMU = var.IOMMU, + SMT = var.SMT, + virt_instr = var.virt_instr, + access_ctrl = var.access_ctrl, + numa_nodes_per_socket = var.numa_nodes_per_socket, + percentage_of_cores_enabled = var.percentage_of_cores_enabled }) destination = "/opt/oci-hpc/conf/variables.tf" diff --git a/variables.tf b/variables.tf index 4bea90d8..f9a60e5c 100755 --- a/variables.tf +++ b/variables.tf @@ -259,4 +259,25 @@ variable "zone_name" { variable "dns_entries" { default = true } - \ No newline at end of file + +variable "BIOS" { + default = false +} +variable "IOMMU" { + default = false +} +variable "SMT" { + default = true +} +variable "virt_instr" { + default = false +} +variable "access_ctrl" { + default = false +} +variable "numa_nodes_per_socket" { + default = "Default" +} +variable "percentage_of_cores_enabled" { + default = "Default" +} \ No newline at end of file From 031dfb4ca1c4c7ab5a84823a8e5250d2617fae44 Mon Sep 17 00:00:00 2001 From: Amr Ragab Date: Wed, 28 Feb 2024 14:12:37 -0500 Subject: [PATCH 33/40] adding collect_metadata script --- scripts/collect_metadata/collect_metadata.py | 149 +++++++++++++++++++ scripts/collect_metadata/requirements.txt | 1 + 2 files changed, 150 insertions(+) create mode 100644 scripts/collect_metadata/collect_metadata.py create mode 100644 scripts/collect_metadata/requirements.txt diff --git a/scripts/collect_metadata/collect_metadata.py b/scripts/collect_metadata/collect_metadata.py new file mode 100644 index 00000000..aa860e6d --- /dev/null +++ b/scripts/collect_metadata/collect_metadata.py @@ -0,0 +1,149 @@ +import argparse +import os +import sys +import socket +import multiprocessing +import paramiko +import csv +import json + +specific_fieldnames = ['displayName','hostname', 'privateIp','networkBlockId','rackid', 'ociAdName','id'] + +def is_valid_file(parser, arg): + if not os.path.exists(arg): + parser.error(f"The file {arg} does not exist!") + else: + return arg + +def is_valid_hostname(parser, arg): + try: + socket.gethostbyname(arg) + return arg + except socket.error: + parser.error(f"Invalid hostname or IP address: {arg}") + +def json_to_stdout(flattened_results): + # Write JSON data to STDOUT + writer = csv.DictWriter(sys.stdout, fieldnames=specific_fieldnames) + writer.writeheader() + for data in flattened_results: + writer.writerow(data) + +def json_to_csv(flattened_results, csv_file): + # Get the specific fieldnames +# print("Content of result:", entries_data) + # Write JSON data to CSV + with open(csv_file, mode='w', newline='') as file: + writer = csv.DictWriter(file, fieldnames=specific_fieldnames) + writer.writeheader() + + for data in flattened_results: + writer.writerow(data) + +def process_entry(entry, username): + # Replace this with the path to your private key + ssh_key = "/home/"+username+"/.ssh/id_rsa" + + # Replace this with your SSH connection details + ssh_host = entry + ssh_user = username + + # Create SSH client + ssh_client = paramiko.SSHClient() + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + parsed_data_list = [] + + try: + # Connect to SSH server using key pair authentication + ssh_client.connect(ssh_host, username=ssh_user, key_filename=ssh_key) + + # Perform SSH operations here + stdin, stdout, stderr = ssh_client.exec_command('curl -H "Authorization: Bearer Oracle" -L http://169.254.169.254/opc/v2/instance/') + output = stdout.read().decode() + parsed_instance = json.loads(output) + + stdin, stdout, stderr = ssh_client.exec_command('curl -H "Authorization: Bearer Oracle" -L http://169.254.169.254/opc/v2/host/') + output = stdout.read().decode() + parsed_host = json.loads(output) + + stdin, stdout, stderr = ssh_client.exec_command('curl -H "Authorization: Bearer Oracle" -L http://169.254.169.254/opc/v2/vnics/') + output = stdout.read().decode() + list_of_vnics = json.loads(output) + first_vnic = list_of_vnics[0] + + parsed_data = {**parsed_instance, **parsed_host, **first_vnic} + + # Extract required fields from parsed_data + required_fields = specific_fieldnames + extracted_data = {field: parsed_data.get(field, "") for field in required_fields} + parsed_data_list.append(extracted_data) + + except socket.error as e: + print(f"Error occurred while connecting to {ssh_host}: {e}") + return None + except paramiko.AuthenticationException as e: + print(f"Authentication error occurred while connecting to {ssh_host}: {e}") + return None + except paramiko.SSHException as e: + print(f"SSH error occurred while connecting to {ssh_host}: {e}") + return None + except Exception as e: + print(f"Error occurred while connecting to {ssh_host}: {e}") + return None + + finally: + # Close SSH connection + ssh_client.close() + + return parsed_data_list + +def process_entry_wrapper(args): + entry, private_key = args + return process_entry(entry, private_key) + +def main(): + parser = argparse.ArgumentParser(description="Process file or hostname/IP address and optionally generate a CSV file of results.") + parser.add_argument('input', metavar='input', type=str, help='Input file or hostname/IP address') + parser.add_argument('--output-dir', metavar='output_dir', type=str, default='.', help='Output directory to save files (default: current directory)') + parser.add_argument('--username', metavar='username', type=str, help='Username to pass to ssh connection, if not set will use login username') + parser.add_argument('--csv', metavar='csv', type=str, help='Generate a CSV file of results') + args = parser.parse_args() + + if not args.username: + args.username=os.getlogin() + + if os.path.isfile(args.input): + print(f"Processing file: {args.input}") + with open(args.input, 'r') as file: + entries = [line.strip() for line in file.readlines()] + + # Create a pool of worker processes + pool = multiprocessing.Pool() + + # Execute the process_entry function on each entry in parallel + results = pool.map(process_entry_wrapper, [(entry, args.username) for entry in entries]) + flattened_results = [item for sublist in results for item in sublist] + + # Close the pool to release resources + pool.close() + pool.join() + # Parse JSON data and generate CSV file + if args.csv: + json_to_csv(flattened_results, args.csv) + else: + json_to_stdout(flattened_results) + + else: + print(f"Processing hostname/IP: {args.input}") + result = process_entry(args.input, args.username) + + # Parse JSON data and generate CSV file + if args.csv: + json_to_csv(result, args.csv) + else: + json_to_stdout(result) + + +if __name__ == "__main__": + main() diff --git a/scripts/collect_metadata/requirements.txt b/scripts/collect_metadata/requirements.txt new file mode 100644 index 00000000..8608c1b0 --- /dev/null +++ b/scripts/collect_metadata/requirements.txt @@ -0,0 +1 @@ +paramiko From 22ab0e58eb2ea4b9dcce2ca30a43b779b790fd0c Mon Sep 17 00:00:00 2001 From: Arnaud Froidmont Date: Fri, 1 Mar 2024 15:02:43 -0700 Subject: [PATCH 34/40] Update Marketplace images --- conf/variables.tpl | 8 ++++---- variables.tf | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/conf/variables.tpl b/conf/variables.tpl index 62582a98..ca856e01 100755 --- a/conf/variables.tpl +++ b/conf/variables.tpl @@ -56,10 +56,10 @@ variable "marketplace_version_id" { "2" = "OL7.8-OFED5.0-1.0.0.0-UEK-20200826" "3" = "OL7.7-OFED-4.4-2.0.7.0-UEK-20200229" "4" = "OL7.9-OFED5.0-2.1.8.0-RHCK-20210709" - "HPC_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-2024.01.02-0" - "HPC_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-2024.01.02-1" - "GPU_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-2024.01.02-0" - "GPU_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-2024.01.02-1" + "HPC_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-2024.02.27-0" + "HPC_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-2024.02.27-0" + "GPU_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-CUDA-12.3-2024.02.27-0" + "GPU_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-CUDA-12.3-2024.02.27-0" } } diff --git a/variables.tf b/variables.tf index f9a60e5c..5f040029 100755 --- a/variables.tf +++ b/variables.tf @@ -91,10 +91,10 @@ variable "marketplace_version_id" { "2" = "OL7.8-OFED5.0-1.0.0.0-UEK-20200826" "3" = "OL7.7-OFED-4.4-2.0.7.0-UEK-20200229" "4" = "OL7.9-OFED5.0-2.1.8.0-RHCK-20210709" - "HPC_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-2024.01.02-0" - "HPC_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-2024.01.02-1" - "GPU_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-2024.01.02-0" - "GPU_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-2024.01.02-1" + "HPC_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-2024.02.27-0" + "HPC_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-2024.02.27-0" + "GPU_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-CUDA-12.3-2024.02.27-0" + "GPU_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-CUDA-12.3-2024.02.27-0" } } From 79c3cdb00eb0e394cc80918219c18d91236c9001 Mon Sep 17 00:00:00 2001 From: Arnaud Froidmont Date: Mon, 11 Mar 2024 11:44:45 -0600 Subject: [PATCH 35/40] Update include to include_tasks --- playbooks/roles/autoscaling_mon/tasks/main.yml | 4 ++-- playbooks/roles/boot-volume/tasks/main.yml | 2 +- playbooks/roles/cloud-agent_update/tasks/main.yml | 4 ++-- playbooks/roles/cluster-cli/tasks/main.yml | 6 +++--- playbooks/roles/cron/tasks/main.yml | 4 ++-- playbooks/roles/destroy_unreachable/tasks/main.yml | 6 +++--- playbooks/roles/docker/tasks/main.yml | 6 +++--- playbooks/roles/etc-hosts/tasks/main.yml | 2 +- playbooks/roles/firewall/tasks/main.yml | 4 ++-- playbooks/roles/fix_broken/tasks/main.yml | 2 +- playbooks/roles/fix_ldap/tasks/main.yml | 2 +- playbooks/roles/fss-home/tasks/main.yml | 4 ++-- playbooks/roles/grafana/tasks/main.yml | 4 ++-- playbooks/roles/home_nfs/tasks/main.yml | 2 +- playbooks/roles/hostname/tasks/main.yml | 4 ++-- playbooks/roles/hyperthreading/tasks/main.yml | 4 ++-- playbooks/roles/influxdb/tasks/main.yml | 4 ++-- playbooks/roles/iscsi/tasks/main.yml | 6 +++--- playbooks/roles/latency_check/tasks/main.yml | 2 +- playbooks/roles/limits/tasks/main.yml | 6 +++--- playbooks/roles/localdisk/tasks/main.yml | 2 +- playbooks/roles/mpi-hostfiles/tasks/main.yml | 6 +++--- playbooks/roles/mpivars/tasks/main.yml | 2 +- playbooks/roles/mysql/tasks/main.yml | 8 ++++---- playbooks/roles/nfs-client/tasks/main.yml | 6 +++--- playbooks/roles/nfs-server/tasks/main.yml | 6 +++--- playbooks/roles/no_instance_principal/tasks/main.yml | 2 +- playbooks/roles/nvidia-container/tasks/main.yml | 6 +++--- playbooks/roles/nvidia-enroot/tasks/main.yml | 6 +++--- playbooks/roles/nvidia_peermem/tasks/main.yml | 2 +- playbooks/roles/oci-cloud-agent/tasks/main.yml | 4 ++-- playbooks/roles/oci-cn-auth/tasks/main.yml | 4 ++-- playbooks/roles/oci-hostname/tasks/main.yml | 2 +- playbooks/roles/oci-legacy/tasks/main.yml | 4 ++-- playbooks/roles/openldap/tasks/main.yml | 6 +++--- playbooks/roles/packages/tasks/main.yml | 12 ++++++------ playbooks/roles/privilege_group/tasks/main.yml | 4 ++-- playbooks/roles/rack-aware/tasks/main.yml | 4 ++-- playbooks/roles/rdma-interface/tasks/main.yml | 6 +++--- playbooks/roles/safe_yum/tasks/main.yml | 4 ++-- playbooks/roles/slurm/tasks/common.yml | 4 ++-- playbooks/roles/slurm/tasks/compute-rack-aware.yml | 2 +- playbooks/roles/slurm/tasks/compute.yml | 4 ++-- playbooks/roles/slurm/tasks/main.yml | 10 +++++----- playbooks/roles/spack/tasks/main.yml | 6 +++--- playbooks/roles/ssh/tasks/main.yml | 2 +- playbooks/roles/ssl/tasks/main.yml | 4 ++-- playbooks/roles/sssd/tasks/main.yml | 6 +++--- playbooks/roles/telegraf/tasks/main.yml | 2 +- playbooks/roles/tuned/tasks/main.yml | 2 +- playbooks/roles/yaml/tasks/main.yml | 4 ++-- 51 files changed, 110 insertions(+), 110 deletions(-) diff --git a/playbooks/roles/autoscaling_mon/tasks/main.yml b/playbooks/roles/autoscaling_mon/tasks/main.yml index e3450c91..6b947a1b 100755 --- a/playbooks/roles/autoscaling_mon/tasks/main.yml +++ b/playbooks/roles/autoscaling_mon/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' diff --git a/playbooks/roles/boot-volume/tasks/main.yml b/playbooks/roles/boot-volume/tasks/main.yml index 8d556a44..a5275781 100644 --- a/playbooks/roles/boot-volume/tasks/main.yml +++ b/playbooks/roles/boot-volume/tasks/main.yml @@ -1,2 +1,2 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' diff --git a/playbooks/roles/cloud-agent_update/tasks/main.yml b/playbooks/roles/cloud-agent_update/tasks/main.yml index ea4d5d2a..cbceb221 100644 --- a/playbooks/roles/cloud-agent_update/tasks/main.yml +++ b/playbooks/roles/cloud-agent_update/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' diff --git a/playbooks/roles/cluster-cli/tasks/main.yml b/playbooks/roles/cluster-cli/tasks/main.yml index 0ef20964..22c0d8cc 100755 --- a/playbooks/roles/cluster-cli/tasks/main.yml +++ b/playbooks/roles/cluster-cli/tasks/main.yml @@ -1,8 +1,8 @@ -- include: el7.yml +- include_tasks: el7.yml when: ansible_os_family == 'RedHat' and ansible_distribution_major_version == '7' -- include: el8.yml +- include_tasks: el8.yml when: ansible_os_family == 'RedHat' and ansible_distribution_major_version == '8' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Ubuntu' \ No newline at end of file diff --git a/playbooks/roles/cron/tasks/main.yml b/playbooks/roles/cron/tasks/main.yml index e3450c91..6b947a1b 100755 --- a/playbooks/roles/cron/tasks/main.yml +++ b/playbooks/roles/cron/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' diff --git a/playbooks/roles/destroy_unreachable/tasks/main.yml b/playbooks/roles/destroy_unreachable/tasks/main.yml index 1c7b0d2c..f7df4002 100644 --- a/playbooks/roles/destroy_unreachable/tasks/main.yml +++ b/playbooks/roles/destroy_unreachable/tasks/main.yml @@ -1,7 +1,7 @@ -- include: common.yml +- include_tasks: common.yml -- include: slurm-rack-aware.yml +- include_tasks: slurm-rack-aware.yml when: rack_aware | bool -- include: slurm.yml +- include_tasks: slurm.yml when: not rack_aware | bool \ No newline at end of file diff --git a/playbooks/roles/docker/tasks/main.yml b/playbooks/roles/docker/tasks/main.yml index 62c22a6b..5ed5747d 100644 --- a/playbooks/roles/docker/tasks/main.yml +++ b/playbooks/roles/docker/tasks/main.yml @@ -1,10 +1,10 @@ --- -- include: oraclelinux.yml +- include_tasks: oraclelinux.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' -#- include: centos-7.yml +#- include_tasks: centos-7.yml # when: ansible_os_family == 'RedHat' and ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' diff --git a/playbooks/roles/etc-hosts/tasks/main.yml b/playbooks/roles/etc-hosts/tasks/main.yml index 7d623a50..f6f5f12b 100755 --- a/playbooks/roles/etc-hosts/tasks/main.yml +++ b/playbooks/roles/etc-hosts/tasks/main.yml @@ -1 +1 @@ -- include: common.yml +- include_tasks: common.yml diff --git a/playbooks/roles/firewall/tasks/main.yml b/playbooks/roles/firewall/tasks/main.yml index e3450c91..6b947a1b 100755 --- a/playbooks/roles/firewall/tasks/main.yml +++ b/playbooks/roles/firewall/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' diff --git a/playbooks/roles/fix_broken/tasks/main.yml b/playbooks/roles/fix_broken/tasks/main.yml index 87c93a55..c18728a3 100644 --- a/playbooks/roles/fix_broken/tasks/main.yml +++ b/playbooks/roles/fix_broken/tasks/main.yml @@ -2,5 +2,5 @@ # tasks file for fix_broken # to resolve error for not able to install nfs-kernel-server. seeing the same error for compute nodes while installing other packages. so adding this to run on all compute hosts the first time itself. -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' \ No newline at end of file diff --git a/playbooks/roles/fix_ldap/tasks/main.yml b/playbooks/roles/fix_ldap/tasks/main.yml index cd6ba1b8..a9855733 100644 --- a/playbooks/roles/fix_ldap/tasks/main.yml +++ b/playbooks/roles/fix_ldap/tasks/main.yml @@ -1,2 +1,2 @@ -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' \ No newline at end of file diff --git a/playbooks/roles/fss-home/tasks/main.yml b/playbooks/roles/fss-home/tasks/main.yml index e3450c91..6b947a1b 100644 --- a/playbooks/roles/fss-home/tasks/main.yml +++ b/playbooks/roles/fss-home/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' diff --git a/playbooks/roles/grafana/tasks/main.yml b/playbooks/roles/grafana/tasks/main.yml index e3450c91..6b947a1b 100755 --- a/playbooks/roles/grafana/tasks/main.yml +++ b/playbooks/roles/grafana/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' diff --git a/playbooks/roles/home_nfs/tasks/main.yml b/playbooks/roles/home_nfs/tasks/main.yml index e6a3fa1b..cae2d534 100644 --- a/playbooks/roles/home_nfs/tasks/main.yml +++ b/playbooks/roles/home_nfs/tasks/main.yml @@ -1,2 +1,2 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' diff --git a/playbooks/roles/hostname/tasks/main.yml b/playbooks/roles/hostname/tasks/main.yml index e3450c91..6b947a1b 100755 --- a/playbooks/roles/hostname/tasks/main.yml +++ b/playbooks/roles/hostname/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' diff --git a/playbooks/roles/hyperthreading/tasks/main.yml b/playbooks/roles/hyperthreading/tasks/main.yml index 5c0a2160..48c59696 100644 --- a/playbooks/roles/hyperthreading/tasks/main.yml +++ b/playbooks/roles/hyperthreading/tasks/main.yml @@ -1,5 +1,5 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' diff --git a/playbooks/roles/influxdb/tasks/main.yml b/playbooks/roles/influxdb/tasks/main.yml index e3450c91..6b947a1b 100755 --- a/playbooks/roles/influxdb/tasks/main.yml +++ b/playbooks/roles/influxdb/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' diff --git a/playbooks/roles/iscsi/tasks/main.yml b/playbooks/roles/iscsi/tasks/main.yml index 2c296c7f..b769d6ac 100755 --- a/playbooks/roles/iscsi/tasks/main.yml +++ b/playbooks/roles/iscsi/tasks/main.yml @@ -1,8 +1,8 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Debian' diff --git a/playbooks/roles/latency_check/tasks/main.yml b/playbooks/roles/latency_check/tasks/main.yml index a23c51fa..d0fd924e 100644 --- a/playbooks/roles/latency_check/tasks/main.yml +++ b/playbooks/roles/latency_check/tasks/main.yml @@ -1,2 +1,2 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' \ No newline at end of file diff --git a/playbooks/roles/limits/tasks/main.yml b/playbooks/roles/limits/tasks/main.yml index 76f56d44..aaf0dfe9 100755 --- a/playbooks/roles/limits/tasks/main.yml +++ b/playbooks/roles/limits/tasks/main.yml @@ -1,10 +1,10 @@ --- -- include: common.yml +- include_tasks: common.yml when: ansible_os_family == 'RedHat' -- include: common.yml +- include_tasks: common.yml when: ansible_distribution == 'Ubuntu' -- include: common.yml +- include_tasks: common.yml when: ansible_distribution == 'Debian' diff --git a/playbooks/roles/localdisk/tasks/main.yml b/playbooks/roles/localdisk/tasks/main.yml index 7d623a50..f6f5f12b 100755 --- a/playbooks/roles/localdisk/tasks/main.yml +++ b/playbooks/roles/localdisk/tasks/main.yml @@ -1 +1 @@ -- include: common.yml +- include_tasks: common.yml diff --git a/playbooks/roles/mpi-hostfiles/tasks/main.yml b/playbooks/roles/mpi-hostfiles/tasks/main.yml index 4fc735e7..21d20998 100755 --- a/playbooks/roles/mpi-hostfiles/tasks/main.yml +++ b/playbooks/roles/mpi-hostfiles/tasks/main.yml @@ -1,8 +1,8 @@ -- include: common.yml +- include_tasks: common.yml # when: ansible_os_family == 'RedHat' # -#- include: ubuntu.yml +#- include_tasks: ubuntu.yml # when: ansible_distribution == 'Ubuntu' # -#- include: debian.yml +#- include_tasks: debian.yml # when: ansible_distribution == 'Debian' diff --git a/playbooks/roles/mpivars/tasks/main.yml b/playbooks/roles/mpivars/tasks/main.yml index 77964dc2..6da05e55 100644 --- a/playbooks/roles/mpivars/tasks/main.yml +++ b/playbooks/roles/mpivars/tasks/main.yml @@ -1,4 +1,4 @@ --- # tasks file for mpivars -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' diff --git a/playbooks/roles/mysql/tasks/main.yml b/playbooks/roles/mysql/tasks/main.yml index ebd448b6..3d03980b 100644 --- a/playbooks/roles/mysql/tasks/main.yml +++ b/playbooks/roles/mysql/tasks/main.yml @@ -1,11 +1,11 @@ -- include: centos.yml +- include_tasks: centos.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'CentOS' -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Ubuntu' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Debian' diff --git a/playbooks/roles/nfs-client/tasks/main.yml b/playbooks/roles/nfs-client/tasks/main.yml index 2c296c7f..b769d6ac 100755 --- a/playbooks/roles/nfs-client/tasks/main.yml +++ b/playbooks/roles/nfs-client/tasks/main.yml @@ -1,8 +1,8 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Debian' diff --git a/playbooks/roles/nfs-server/tasks/main.yml b/playbooks/roles/nfs-server/tasks/main.yml index 2c296c7f..b769d6ac 100755 --- a/playbooks/roles/nfs-server/tasks/main.yml +++ b/playbooks/roles/nfs-server/tasks/main.yml @@ -1,8 +1,8 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Debian' diff --git a/playbooks/roles/no_instance_principal/tasks/main.yml b/playbooks/roles/no_instance_principal/tasks/main.yml index 270202fc..17a3fd4f 100755 --- a/playbooks/roles/no_instance_principal/tasks/main.yml +++ b/playbooks/roles/no_instance_principal/tasks/main.yml @@ -1,3 +1,3 @@ -- include: common.yml +- include_tasks: common.yml diff --git a/playbooks/roles/nvidia-container/tasks/main.yml b/playbooks/roles/nvidia-container/tasks/main.yml index 2766a27b..b0178751 100644 --- a/playbooks/roles/nvidia-container/tasks/main.yml +++ b/playbooks/roles/nvidia-container/tasks/main.yml @@ -1,10 +1,10 @@ --- -- include: oraclelinux-7.yml +- include_tasks: oraclelinux-7.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' and ansible_distribution_major_version == '7' -#- include: centos-7.yml +#- include_tasks: centos-7.yml # when: ansible_os_family == 'RedHat' and ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' diff --git a/playbooks/roles/nvidia-enroot/tasks/main.yml b/playbooks/roles/nvidia-enroot/tasks/main.yml index 7243e27b..e4518440 100644 --- a/playbooks/roles/nvidia-enroot/tasks/main.yml +++ b/playbooks/roles/nvidia-enroot/tasks/main.yml @@ -1,10 +1,10 @@ --- -- include: oraclelinux.yml +- include_tasks: oraclelinux.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' -#- include: centos-7.yml +#- include_tasks: centos-7.yml # when: ansible_os_family == 'RedHat' and ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' and ansible_distribution == 'Ubuntu' \ No newline at end of file diff --git a/playbooks/roles/nvidia_peermem/tasks/main.yml b/playbooks/roles/nvidia_peermem/tasks/main.yml index b5245a5e..52027e44 100644 --- a/playbooks/roles/nvidia_peermem/tasks/main.yml +++ b/playbooks/roles/nvidia_peermem/tasks/main.yml @@ -1,3 +1,3 @@ --- # tasks file for nvidia_peermem -- include: common.yml \ No newline at end of file +- include_tasks: common.yml \ No newline at end of file diff --git a/playbooks/roles/oci-cloud-agent/tasks/main.yml b/playbooks/roles/oci-cloud-agent/tasks/main.yml index ea4d5d2a..cbceb221 100644 --- a/playbooks/roles/oci-cloud-agent/tasks/main.yml +++ b/playbooks/roles/oci-cloud-agent/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' diff --git a/playbooks/roles/oci-cn-auth/tasks/main.yml b/playbooks/roles/oci-cn-auth/tasks/main.yml index 8705dde4..abc4c9f3 100644 --- a/playbooks/roles/oci-cn-auth/tasks/main.yml +++ b/playbooks/roles/oci-cn-auth/tasks/main.yml @@ -1,6 +1,6 @@ --- -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' and ansible_distribution == 'Ubuntu' \ No newline at end of file diff --git a/playbooks/roles/oci-hostname/tasks/main.yml b/playbooks/roles/oci-hostname/tasks/main.yml index e6a3fa1b..cae2d534 100755 --- a/playbooks/roles/oci-hostname/tasks/main.yml +++ b/playbooks/roles/oci-hostname/tasks/main.yml @@ -1,2 +1,2 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' diff --git a/playbooks/roles/oci-legacy/tasks/main.yml b/playbooks/roles/oci-legacy/tasks/main.yml index 4229a70b..e4dc405a 100755 --- a/playbooks/roles/oci-legacy/tasks/main.yml +++ b/playbooks/roles/oci-legacy/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' \ No newline at end of file diff --git a/playbooks/roles/openldap/tasks/main.yml b/playbooks/roles/openldap/tasks/main.yml index 860b1077..eee5413e 100644 --- a/playbooks/roles/openldap/tasks/main.yml +++ b/playbooks/roles/openldap/tasks/main.yml @@ -4,11 +4,11 @@ - include_vars: debian_vars.yml when: ansible_distribution == 'Ubuntu' -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -#- include: el-8.yml +#- include_tasks: el-8.yml # when: ansible_os_family == 'RedHat' and ansible_distribution_major_version == '8' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Ubuntu' \ No newline at end of file diff --git a/playbooks/roles/packages/tasks/main.yml b/playbooks/roles/packages/tasks/main.yml index 24cc3ed3..e3f1fa68 100755 --- a/playbooks/roles/packages/tasks/main.yml +++ b/playbooks/roles/packages/tasks/main.yml @@ -1,17 +1,17 @@ -- include: ol-7.yml +- include_tasks: ol-7.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' and ansible_distribution_major_version == '7' -- include: ol-8.yml +- include_tasks: ol-8.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' and ansible_distribution_major_version == '8' -- include: centos-7.yml +- include_tasks: centos-7.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version < '22' -- include: ubuntu-2204.yml +- include_tasks: ubuntu-2204.yml when: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version == '22' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Debian' \ No newline at end of file diff --git a/playbooks/roles/privilege_group/tasks/main.yml b/playbooks/roles/privilege_group/tasks/main.yml index 9698f56d..e0b68526 100644 --- a/playbooks/roles/privilege_group/tasks/main.yml +++ b/playbooks/roles/privilege_group/tasks/main.yml @@ -1,5 +1,5 @@ -- include: common.yml +- include_tasks: common.yml -#- include: el.yml +#- include_tasks: el.yml # when: ansible_os_family == 'RedHat' diff --git a/playbooks/roles/rack-aware/tasks/main.yml b/playbooks/roles/rack-aware/tasks/main.yml index 5570ac43..5c7e72aa 100644 --- a/playbooks/roles/rack-aware/tasks/main.yml +++ b/playbooks/roles/rack-aware/tasks/main.yml @@ -1,6 +1,6 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' diff --git a/playbooks/roles/rdma-interface/tasks/main.yml b/playbooks/roles/rdma-interface/tasks/main.yml index 076cd2a6..48a777ae 100755 --- a/playbooks/roles/rdma-interface/tasks/main.yml +++ b/playbooks/roles/rdma-interface/tasks/main.yml @@ -1,10 +1,10 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Ubuntu' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Debian' diff --git a/playbooks/roles/safe_yum/tasks/main.yml b/playbooks/roles/safe_yum/tasks/main.yml index 57c3c03c..52476fde 100644 --- a/playbooks/roles/safe_yum/tasks/main.yml +++ b/playbooks/roles/safe_yum/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' \ No newline at end of file diff --git a/playbooks/roles/slurm/tasks/common.yml b/playbooks/roles/slurm/tasks/common.yml index 804a2e63..9f0d0729 100755 --- a/playbooks/roles/slurm/tasks/common.yml +++ b/playbooks/roles/slurm/tasks/common.yml @@ -187,9 +187,9 @@ state: directory - name: Include pyxis prolog files - include: common_pyxis.yml + include_tasks: common_pyxis.yml when: pyxis|bool - name: Include pyxis prolog files - include: common_pmix.yml + include_tasks: common_pmix.yml when: ansible_os_family == 'RedHat' \ No newline at end of file diff --git a/playbooks/roles/slurm/tasks/compute-rack-aware.yml b/playbooks/roles/slurm/tasks/compute-rack-aware.yml index 7d9052d2..0621555d 100755 --- a/playbooks/roles/slurm/tasks/compute-rack-aware.yml +++ b/playbooks/roles/slurm/tasks/compute-rack-aware.yml @@ -237,7 +237,7 @@ when: racks_left_list | length > 0 - name: Run Pam settings - include: compute_pam.yml + include_tasks: compute_pam.yml when: pam|bool - name: run handlers diff --git a/playbooks/roles/slurm/tasks/compute.yml b/playbooks/roles/slurm/tasks/compute.yml index 6eff34f5..8f22bfa8 100755 --- a/playbooks/roles/slurm/tasks/compute.yml +++ b/playbooks/roles/slurm/tasks/compute.yml @@ -1,6 +1,6 @@ --- - name: Run Pam settings - include: compute_pam.yml + include_tasks: compute_pam.yml when: pam|bool - name: install SLURM compute packages @@ -159,7 +159,7 @@ notify: reconfigure slurm - name: Run Pam settings - include: compute_pam.yml + include_tasks: compute_pam.yml when: pam|bool - name: start slurmd diff --git a/playbooks/roles/slurm/tasks/main.yml b/playbooks/roles/slurm/tasks/main.yml index 0bbfea4c..bc94818f 100755 --- a/playbooks/roles/slurm/tasks/main.yml +++ b/playbooks/roles/slurm/tasks/main.yml @@ -7,17 +7,17 @@ - include_vars: ubuntu_vars.yml when: ansible_distribution == 'Ubuntu' -- include: controller.yml +- include_tasks: controller.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' -- include: el7.yml +- include_tasks: el7.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' and ansible_distribution_major_version == '7' -- include: el7.yml +- include_tasks: el7.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7' -- include: el8.yml +- include_tasks: el8.yml when: ansible_os_family == 'RedHat' and ansible_distribution == 'OracleLinux' and ansible_distribution_major_version == '8' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_distribution == 'Ubuntu' \ No newline at end of file diff --git a/playbooks/roles/spack/tasks/main.yml b/playbooks/roles/spack/tasks/main.yml index a803e792..a5171909 100755 --- a/playbooks/roles/spack/tasks/main.yml +++ b/playbooks/roles/spack/tasks/main.yml @@ -1,8 +1,8 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Ubuntu' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Debian' diff --git a/playbooks/roles/ssh/tasks/main.yml b/playbooks/roles/ssh/tasks/main.yml index 7d623a50..f6f5f12b 100755 --- a/playbooks/roles/ssh/tasks/main.yml +++ b/playbooks/roles/ssh/tasks/main.yml @@ -1 +1 @@ -- include: common.yml +- include_tasks: common.yml diff --git a/playbooks/roles/ssl/tasks/main.yml b/playbooks/roles/ssl/tasks/main.yml index 150216f8..2c4e3a98 100644 --- a/playbooks/roles/ssl/tasks/main.yml +++ b/playbooks/roles/ssl/tasks/main.yml @@ -1,5 +1,5 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Ubuntu' \ No newline at end of file diff --git a/playbooks/roles/sssd/tasks/main.yml b/playbooks/roles/sssd/tasks/main.yml index acad08c2..0456de77 100644 --- a/playbooks/roles/sssd/tasks/main.yml +++ b/playbooks/roles/sssd/tasks/main.yml @@ -1,11 +1,11 @@ - include_vars: /opt/oci-hpc/playbooks/roles/openldap/vars/debian_vars.yml when: ansible_distribution == 'Ubuntu' -- include: el-7.yml +- include_tasks: el-7.yml when: ansible_os_family == 'RedHat' and ansible_distribution_major_version == '7' -- include: el-8.yml +- include_tasks: el-8.yml when: ansible_os_family == 'RedHat' and ansible_distribution_major_version == '8' -- include: debian.yml +- include_tasks: debian.yml when: ansible_distribution == 'Ubuntu' \ No newline at end of file diff --git a/playbooks/roles/telegraf/tasks/main.yml b/playbooks/roles/telegraf/tasks/main.yml index b1d4a1f1..244bd257 100755 --- a/playbooks/roles/telegraf/tasks/main.yml +++ b/playbooks/roles/telegraf/tasks/main.yml @@ -1,2 +1,2 @@ -- include: common.yml +- include_tasks: common.yml when: ansible_os_family == 'RedHat' or ansible_os_family == 'Debian' diff --git a/playbooks/roles/tuned/tasks/main.yml b/playbooks/roles/tuned/tasks/main.yml index 15793bee..597898be 100644 --- a/playbooks/roles/tuned/tasks/main.yml +++ b/playbooks/roles/tuned/tasks/main.yml @@ -1,2 +1,2 @@ - - include: el-7.yml + - include_tasks: el-7.yml when: ansible_os_family == 'RedHat' and ansible_distribution_major_version == '7' and (shape == 'BM.GPU.B4.8' or shape == 'BM.GPU4.8' or shape == 'BM.GPU.A100-v2.8' or shape == 'BM.GPU.H100.8') diff --git a/playbooks/roles/yaml/tasks/main.yml b/playbooks/roles/yaml/tasks/main.yml index 4229a70b..e4dc405a 100755 --- a/playbooks/roles/yaml/tasks/main.yml +++ b/playbooks/roles/yaml/tasks/main.yml @@ -1,4 +1,4 @@ -- include: el.yml +- include_tasks: el.yml when: ansible_os_family == 'RedHat' -- include: ubuntu.yml +- include_tasks: ubuntu.yml when: ansible_os_family == 'Debian' \ No newline at end of file From 280fa3c23ebdb7e7f2447a93763db93d64c807a4 Mon Sep 17 00:00:00 2001 From: Arnaud Froidmont Date: Wed, 6 Mar 2024 12:48:34 -0700 Subject: [PATCH 36/40] Add OCI tuner --- playbooks/new_nodes.yml | 6 +- playbooks/resize_add.yml | 6 +- .../files/libnccl-ocituner.so.1.0.1_OL | Bin 0 -> 165344 bytes .../files/libnccl-ocituner.so.1.0.1_ubuntu | Bin 0 -> 168616 bytes playbooks/roles/nccl-conf/tasks/main.yml | 22 ++++- playbooks/site.yml | 6 +- ..._ncclparam_tuner_nccl_run_allreduce.sbatch | 63 +++++++++++++ .../no_ncclparam_tuner_nccl_run_allreduce.sh | 87 ++++++++++++++++++ 8 files changed, 180 insertions(+), 10 deletions(-) create mode 100755 playbooks/roles/nccl-conf/files/libnccl-ocituner.so.1.0.1_OL create mode 100755 playbooks/roles/nccl-conf/files/libnccl-ocituner.so.1.0.1_ubuntu create mode 100644 samples/gpu/no_ncclparam_tuner_nccl_run_allreduce.sbatch create mode 100644 samples/gpu/no_ncclparam_tuner_nccl_run_allreduce.sh diff --git a/playbooks/new_nodes.yml b/playbooks/new_nodes.yml index afbcbc70..b160873f 100755 --- a/playbooks/new_nodes.yml +++ b/playbooks/new_nodes.yml @@ -54,9 +54,6 @@ when: cluster_network|bool and not use_compute_agent|default(false)|bool - include_role: name: nvidia_peermem - - include_role: - name: nccl-conf - when: cluster_network|bool - hosts: controller,slurm_backup,login,compute become: true @@ -174,6 +171,9 @@ when: enroot|default(true)|bool - include_role: name: tuned + - include_role: + name: nccl-conf + when: cluster_network|bool - hosts: compute tasks: diff --git a/playbooks/resize_add.yml b/playbooks/resize_add.yml index 0394ed17..09be3ecd 100755 --- a/playbooks/resize_add.yml +++ b/playbooks/resize_add.yml @@ -52,9 +52,6 @@ when: cluster_network|bool and not use_compute_agent|default(false)|bool - include_role: name: nvidia_peermem - - include_role: - name: nccl-conf - when: cluster_network|bool - hosts: controller,slurm_backup,login,compute become: true @@ -176,6 +173,9 @@ when: enroot|default(true)|bool - include_role: name: tuned + - include_role: + name: nccl-conf + when: cluster_network|bool - hosts: all become: true diff --git a/playbooks/roles/nccl-conf/files/libnccl-ocituner.so.1.0.1_OL b/playbooks/roles/nccl-conf/files/libnccl-ocituner.so.1.0.1_OL new file mode 100755 index 0000000000000000000000000000000000000000..f4b811fb62f0b0d8800855f16174ae5edef430e1 GIT binary patch literal 165344 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 $MACHINEFILE +echo MACHINEFILE +cat $MACHINEFILE + +source /etc/os-release +if [ $ID == "ol" ] || [ $ID == "centos" ] ; then + python3 /home/opc/node_ordering_by_rack.py --input_file $MACHINEFILE > /dev/null +elif [ $ID == "debian" ] || [ $ID == "ubuntu" ] ; then + python3 /home/ubuntu/node_ordering_by_rack.py --input_file $MACHINEFILE > /dev/null +fi + + +echo ORDEREDMACHINEFILE +cat $ORDEREDMACHINEFILE +echo ORDEREDRANKMACHINEFILE +cat $ORDEREDRANKMACHINEFILE + +mpivars_path=`ls /usr/mpi/gcc/openmpi-*/bin/mpivars.sh` + +if [[ "$mpivars_path" == "" ]]; then + mpivars_path=`ls /opt/openmpi-*/bin/mpivars.sh` +fi + +if [[ "$mpivars_path" == "" ]]; then + echo "Could not find MPIPATH"; exit; fi + +source $mpivars_path + +shape=`curl -sH "Authorization: Bearer Oracle" -L http://169.254.169.254/opc/v2/instance/ | jq .shape` +if [ $shape == \"BM.GPU.B4.8\" ] || [ $shape == \"BM.GPU.A100-v2.8\" ] +then + var_UCX_NET_DEVICES=mlx5_0:1 +elif [ $shape == \"BM.GPU4.8\" ] +then + var_UCX_NET_DEVICES=mlx5_4:1 +fi + + mpirun --mca pml ucx \ + --bind-to numa \ + --mca coll ^hcoll \ + -x UCX_TLS=ud,self,sm \ + -x UCX_NET_DEVICES=${var_UCX_NET_DEVICES} \ + -x HCOLL_ENABLE_MCAST_ALL=0 \ + -x coll_hcoll_enable=0 \ + -x NCCL_TUNER_PLUGIN=$homedirectory/libnccl-ocituner.so.1.0.1 \ + --np $((SLURM_NNODES*SLURM_NTASKS_PER_NODE)) --rankfile $ORDEREDRANKMACHINEFILE /opt/oci-hpc/nccl-test/build/all_reduce_perf -b1G -e10G -i$((1024*1024*1024*9)) -n 100 diff --git a/samples/gpu/no_ncclparam_tuner_nccl_run_allreduce.sh b/samples/gpu/no_ncclparam_tuner_nccl_run_allreduce.sh new file mode 100644 index 00000000..25a496e3 --- /dev/null +++ b/samples/gpu/no_ncclparam_tuner_nccl_run_allreduce.sh @@ -0,0 +1,87 @@ +#!/bin/bash +set -e + +# number of times to run the nccl test to stress the GPUs and RDMA network. This is different from -n iterations parameter of nccl allreduce which is set below using $iter +max=$1 + +# This assume, the hostfile passed is already ordered based on their rackId +if [ -n "$2" ]; then + hostfile=$2 +else + hostfile="/tmp/ordered_hostfile_system_name" +fi + +ORDEREDMACHINEFILE="ordered_hostfile_system_name" +ORDEREDRANKMACHINEFILE="rankfile_system_name" +echo INPUTFILE +cat $hostfile + +# will generate rack-aware ordered host file +source /etc/os-release +if [ $ID == "ol" ] || [ $ID == "centos" ] ; then + python3 /home/opc/node_ordering_by_rack.py --input_file $hostfile > /dev/null + homedirectory=/home/opc +elif [ $ID == "debian" ] || [ $ID == "ubuntu" ] ; then + python3 /home/ubuntu/node_ordering_by_rack.py --input_file $hostfile > /dev/null + homedirectory=/home/ubuntu +fi + +hostfile=$ORDEREDMACHINEFILE +rankfile=$ORDEREDRANKMACHINEFILE + +echo ORDEREDMACHINEFILE +cat $ORDEREDMACHINEFILE +echo ORDEREDRANKMACHINEFILE +cat $ORDEREDRANKMACHINEFILE + +# The number of GPUs to use for the test. Has to be multiplier of 8. If not passed, all GPUs will be used. +if [ -n "$3" ]; then + np=$3 +else + np=$((`less $hostfile | wc -l` * 8 )) +fi + +logfile="nccl_run_allreduce.sh.log" + +for x in $(seq 1 1 $max) +do + + echo $x + echo $x >> $logfile + date >> $logfile + + rankfile=$rankfile; np=$np ; iter=20; + + mpivars_path=`ls /usr/mpi/gcc/openmpi-*/bin/mpivars.sh` + source $mpivars_path + + if [[ "$mpivars_path" == "" ]]; then echo "Could not find MPIPATH"; exit; fi + +first_node=`head $hostfile -n 1` +shape=`ssh $first_node 'curl -sH "Authorization: Bearer Oracle" -L http://169.254.169.254/opc/v2/instance/' | jq .shape` +if [ $shape == \"BM.GPU.B4.8\" ] || [ $shape == \"BM.GPU.A100-v2.8\" ] +then + var_UCX_NET_DEVICES=mlx5_0:1 +elif [ $shape == \"BM.GPU4.8\" ] +then + var_UCX_NET_DEVICES=mlx5_4:1 +fi + + # final version + # all NCCL parameters are at /etc/nccl.conf on each compute node. + mpirun --mca pml ucx \ + --bind-to numa \ + --mca coll ^hcoll \ + -x UCX_TLS=ud,self,sm \ + -x UCX_NET_DEVICES=${var_UCX_NET_DEVICES} \ + -x HCOLL_ENABLE_MCAST_ALL=0 \ + -x coll_hcoll_enable=0 \ + -x NCCL_TUNER_PLUGIN=$homedirectory/libnccl-ocituner.so.1.0.1 \ + --np $np --rankfile $rankfile /opt/oci-hpc/nccl-test/build/all_reduce_perf -b1G -e10G -i$((1024*1024*1024*9)) -n $iter >> $logfile + + tail -n 32 $logfile + + +done + + From a5e5ea0cbb747b952e5e98ae24e03af0e541f7d5 Mon Sep 17 00:00:00 2001 From: Arnaud Froidmont Date: Fri, 8 Mar 2024 12:10:28 -0700 Subject: [PATCH 37/40] Download tuner from bucket rather than in the stack --- playbooks/roles/nccl-conf/tasks/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/playbooks/roles/nccl-conf/tasks/main.yml b/playbooks/roles/nccl-conf/tasks/main.yml index f084baea..c38604d5 100644 --- a/playbooks/roles/nccl-conf/tasks/main.yml +++ b/playbooks/roles/nccl-conf/tasks/main.yml @@ -37,8 +37,8 @@ - name: copy libnccl-ocituner for OL become: true - copy: - src: libnccl-ocituner.so.1.0.1_OL + get_url: + url: wget https://objectstorage.eu-frankfurt-1.oraclecloud.com/p/m1Gdcbiguqst6n_aVwRZIFpRZxUG-wGMvqWS5QJeJbIvNZnqTTA3N1_DDRuYpvJx/n/hpc/b/source/o/tuner/libnccl-ocituner.so.1.0.1-OL dest: /home/opc/libnccl-ocituner.so.1.0.1 owner: opc group: privilege @@ -47,8 +47,8 @@ - name: copy libnccl-ocituner for Ubuntu become: true - copy: - src: libnccl-ocituner.so.1.0.1_ubuntu + get_url: + url: wget https://objectstorage.eu-frankfurt-1.oraclecloud.com/p/m1Gdcbiguqst6n_aVwRZIFpRZxUG-wGMvqWS5QJeJbIvNZnqTTA3N1_DDRuYpvJx/n/hpc/b/source/o/tuner/libnccl-ocituner.so.1.0.1-ubuntu dest: /home/ubuntu/libnccl-ocituner.so.1.0.1 owner: ubuntu group: privilege From 6c5d03b3e2f02c74c2ec8d83166ba3486aaeb7a6 Mon Sep 17 00:00:00 2001 From: Arnaud Froidmont Date: Wed, 13 Mar 2024 14:17:02 -0600 Subject: [PATCH 38/40] Chnage H100 Parameters for Single Subnet and NCCL 2.20.3 --- playbooks/roles/nccl-conf/files/h100 | 7 +++---- .../files/libnccl-ocituner.so.1.0.1_OL | Bin 165344 -> 0 bytes .../files/libnccl-ocituner.so.1.0.1_ubuntu | Bin 168616 -> 0 bytes playbooks/roles/nccl-conf/tasks/main.yml | 4 ++-- samples/gpu/nccl_run_allreduce_H100.sbatch | 10 ++++++---- samples/gpu/nccl_run_allreduce_H100.sh | 9 ++++++--- ...o_ncclparam_tuner_nccl_run_allreduce.sbatch | 6 ++++-- 7 files changed, 21 insertions(+), 15 deletions(-) delete mode 100755 playbooks/roles/nccl-conf/files/libnccl-ocituner.so.1.0.1_OL delete mode 100755 playbooks/roles/nccl-conf/files/libnccl-ocituner.so.1.0.1_ubuntu diff --git a/playbooks/roles/nccl-conf/files/h100 b/playbooks/roles/nccl-conf/files/h100 index 2a5afbfd..d199d0fb 100644 --- a/playbooks/roles/nccl-conf/files/h100 +++ b/playbooks/roles/nccl-conf/files/h100 @@ -1,5 +1,4 @@ -NCCL_CROSS_NIC=0 -NCCL_SOCKET_NTHREADS=16 +NCCL_CROSS_NIC=1 NCCL_DEBUG=WARN NCCL_CUMEM_ENABLE=0 NCCL_IB_SPLIT_DATA_ON_QPS=0 @@ -8,8 +7,8 @@ NCCL_IB_GID_INDEX=3 NCCL_IB_TC=41 NCCL_IB_SL=0 NCCL_IB_TIMEOUT=22 +NCCL_BUFFSIZE=16777216 NCCL_NET_PLUGIN=none NCCL_SOCKET_IFNAME=eth0 NCCL_IGNORE_CPU_AFFINITY=1 -NCCL_IB_HCA==mlx5_0,mlx5_1,mlx5_3,mlx5_4,mlx5_5,mlx5_6,mlx5_7,mlx5_8,mlx5_9,mlx5_10,mlx5_12,mlx5_13,mlx5_14,mlx5_15,mlx5_16,mlx5_17 -NCCL_TOPO_FILE=/nfs/cluster/H100-topology.xml \ No newline at end of file +NCCL_IB_HCA==mlx5_0,mlx5_1,mlx5_3,mlx5_4,mlx5_5,mlx5_6,mlx5_7,mlx5_8,mlx5_9,mlx5_10,mlx5_12,mlx5_13,mlx5_14,mlx5_15,mlx5_16,mlx5_17 \ No newline at end of file diff --git a/playbooks/roles/nccl-conf/files/libnccl-ocituner.so.1.0.1_OL b/playbooks/roles/nccl-conf/files/libnccl-ocituner.so.1.0.1_OL deleted file mode 100755 index f4b811fb62f0b0d8800855f16174ae5edef430e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165344 zcmd444S1gO`Tu{XO*Iq=MMKeh8X8K)JTw%&tqC_(;%PEZ4NWzMHni1jhI<;CtY2iS zY3N2ap_!*nn@@cPm?Jc8k(-9(W05B{$A(%b2e9J9RJ_{IKKbm_+O*V`8v<{ z>%OkfeI@<$l6E%~o_b=xw6xgPqkru9m{pA#fh4?9cYQzqC>Be^@?!(}ai7@UnjO&B z`R@? zOxa6VKa+ngXXjU)h^Ot^KbCE57ydQJj|$&dzj*a;eo?m9l`~3nbNBq`RmFQB*Es(P zTHkwIKxfzQ=-PF~zk}Zc`>y#veGmV|_wf4f;dXs-{{8lGE8x3c_pa~ZlfH-F_&vP( zd-%cdIRAe8I1%7?@PD*kqRNWx5vwZy&u*vqkXXrt;r}t;NVN-B*6E)|aqpfZ&V03d zT0-M>e#(js`7i%v@LgkrVxzOe+|h^i3m;6JIOWP|QzuTJaq-nNCQgh^Jo)Si6H6yw zJ$cGy(`QV+`s@kgu9!A;^4S+(a>eA(`|i%fNfj4Qy!5iE7hiGN&nL&Oo_y)qms}B> zGI_@2sn^6VomM`1YV5M9m(93L?KA1hcPRtYW7B6`J!9JCv5RL+yUaeGI_cu+lP6tS zZr}?0;nLWplkJB|S4^8eIW~3Dq$|#zId$^YMOVz6a@o|0*BlnR;<8I7jha4f)KRgC zlS?n2adB+o#7m}64_5@5T51!PJ7n6V%Vt>CrVksHH|nt1NvEFtqj3{|IO>O^j(&G<}Q5oi~4r zZ>_h!?^`_=jt}o!_qXQv-y`^YzQs2=@BJ1(*zbe(Y2iHWYlhzu5_N3DxBa~8+=dSf zFR359x8XZ&!+W;jJ8#3Y{r)$HeNJyZ;@fchnzr@G-G*=dT5P?%ZTPmoUgdAY?Q7rG zBe4zN_Um|Z8@_d|EmO1&_qR%_;%)d2Tjl&*vJD@!4KLe<@3;*w--d6yKAST4{USMg zeMWNbfL+S=izRPrp3!ehdvbP5hTV0s*p?&r18>=^61_fyL0K=*~eV^ z_FFnLhozW}E%Tq5|AXd_yvu$%@SEh)Z~7;zdy-4L$0pOBOTO~WjNK5tb7>G{Z0Wo- zb67!?xAjdOH`Bfo?7eW=wsZEbtV>V6GP&1! z$uWi9zt|W}ZOq)Iu$vzybn?SF9sF=%J3mZr+nAYE$VV3x!m_M;!35?mT1vC9*P2XR zV9}pgSzOrRuR42uUiEeBt7mpspVO7Bp3re>bzwU%*TGt4CaYV!kLEI}3%{CMxPEDG z|75M91B7~Y0mX9*ze-N(nOoSEtetyKZ*tNL$?9j5OW)rvnf7e*mh|7+RU4^pOD^r~ zmrV1Y+LLKL$y;{2N$pz(jAVT`WoD)KsJX63DX<|#F% zuqU%JWz{X$@h(j0oO{k+=gw?m@lUSqMR|%TZ@KP8T0P%t9cnFm<(rx-25wAd9@M^W zPh0p`{x!QZExG-(hMv7NGnad$m?h3Cd~s9ec^&ov)p^}ItF6(t?~ZZ@6}K_i+4maF z>vsRI*I=lb*Lk0R-{?O1>n&S0W}er>I?sHYGbbKh)K}r6W->#oW!sDL)_KWT{(L9@AhYuK+*4ZR|8Ct#%kHAgQH5{+e21+c z&6sNAZCl5;_YY>AYq;gVaKdvPeTI%12XD+gX!28B_=TL+dFQ-MIpHgQC|uvY^h=#` zGmBZL&oBE7=)0E#&EkZ2DSUnH9^8Lh@65+ISHIyJzIDSrgVNu&zLvjj_tN^0i|@Od z@!x$lNBEg@rCZ-XyFIyA`>tTi7ytKHa5i5|x-q!2?cQ@>^fB#>v-MXR4x!2Y4so3|PJ{3MoSK#>1uavp}@#}`&JDps5 z-!B|70^jI*uziUbH@$N?-%R7{&Q|JzS{FEerGJR@)=sE&fUFv z7Ml;gmY!U_^we7h?7-j?|4?Z6myhljdnU!lOKgSYKVi$OL~bE+!V`Jy5J@B~u|6bz zX9?Rkd^>Uewuz%G@li{*LO5CtjT6+z}FXiOGz>-IhSiGY^43}ceJzl6xWx%hwO9x zA@2J|b}@5jvfp`%71)(uf7oIAz1qze#@U7I(|&o~`eE#KGjBbTjgT&I?Mt+Hht|#9 z_D%ihC{|}Vhw*235I`S24yPR>22BYDeBwiAA4lEpcb%Rbdj)W&WpCZ8P(v zZSm<|9`fPM+m``$Kijt!er5OAx+~4Lb@Ie*CqLcPH)-m1<5-f88CsGa`~Aw6nSQ$u zCH2H0Y{^@8U&Y7&y)yh}CTs1fnmBUhkz3Do885)Qsq1;te*3|>ZlcSzg>$_+oWYLl zx9wcl-flB?x-5?kzqsviVc+TY(q+%7N!Q-D?c~sC(nRYLT-cWB-*I6-C-uZ$;lj52 z-V0lI4W}}I(eMs@v@yCWdzYRLbt6NaM{FHx`_@oCH9ITzIlD~w?wj`#!F%mh4TGv0K?Bz6!vSyUr=3>v)04KA$<3eG#*p8ip2z6X&(7GPb-))t+0R;*4h0Ddi4H@kWI>cX~Dt0y%37wp;By`4TIgYEg+0XX@BFklW+`-SZ* z|NYzATX=!i!cX}zw(jS12aM#B_|({L_I018&6sRkGMi?LKj8VR_KQ`wCU2QZF`<3# zIiK=-xs$7Vs-Nq=oqNf?*sRx{?$D*zjG~&b-q2T>l*3q`X1)kj^QEgWi)3|{#&sUN zRkI5}P2=_YU7xbwO3uEnH!btl%lJ~kB3;3YD#Vik90&%SAT%?3?onr)xn*X1(FxU` z@O69m;Vj&FVc~A$uN)~aV)_^Yi*q}#hhb*(BrBB8pH zD%qOIvYPTDO0HzUQM@1*lgNghqCngplu;e4D#vA`ROQmLUYrV9o{#QAX|j)g7-ad3 zeEnlPmxNDjP*vcv_f#ca_8JwkqIht(P@1gXWyQ!E6)17pA61pQ>~>XUF1wiuS$QJJ zW>K2#e3w-qn`D7hiUIeXDNqS;x|yU|K!AkRX~{m5bc_<5INQT^xK+Ju=yR&I=67!XeQ@HzP0lRB0 z%fw89T&n^o1>nvzLWq$wA?7~K-TAb@W0Xu#xceXhyNNB!#GV34s{+UX;4U%}*U0B* z1^4HNhBYasMRFCmHC004?w1AZp5`B$cv?V@1p{~pfVx- z;qI#iDgl;d;$nd+s{$wj;I1)}(#Y`;bD!w$T3RGWC|O70?mYzR0hVQAfIx#)0epI< z+>Ih>Hu65i+#in$Ytlpuyd2z`YNl}alL9RO%QEpNfi|lGs0DD|?IQhb@L(nF^UK}+1kwTS{^Sh1 zvJ8N`I{~<}L^4eBGQ`{;9un3hn-=(SCF2zCzF!~*;O^fF;DxNR~;ihM4W_mu+O!DNvO0JwVsNr#*Q zG57WE?xh7jTuJ-y*6!XbQfV&97 zFFPhV3S#bS++9oyJWRd z++PGLBh?=Ssv=MWz+D50hx5K3V(#Y-3~N$L3-=Ty>nQxZe=JZRsR{)eB5(u%cOxVb z-1|YyJ=NV!v~UknvYEo&Uz}=7&=RTE3baL_1Ax07k_ztUAm-k~-5s=WFH*9T!rgZZ zbVaIL1iB;e3jppOh`j|!ehM-7Gr3_+dTD{rQquM??*5TLI>6t$!vr!S5C`DSg2cin z$<7dSU+V5`TDUil*Zoc5?hgfWBGnrLxe;gq;Ld~izUnU!bMNf#d|J2{D4C#e_pbyB zBGu0Xk`X8a;4XsrzUs#ib3ZmBtVuB~++&q2p>X#o0sBTAUz2?V>_0;~*b#uc9AY6P zUz`%$7rDEF7I-z7{WpcXUlpi~R7(Y_BG3rHT?2{5CAk}7=e?b~YiZ%0qhuY0yRQ+b zk5rQd8X|Bu0CywAZg3<&f|&b}1Hzg#(E^WDvYEo&dkM5es_g~ZBG7xX-0hH9N|FyD z=Kis}J80ov0d7roQn>pWfi8eA@52J!5%?W|^X`Gz5RzX(%>B*&;k3pY5DQ4n+A@9u0`;2}!JDcrrCKu)CkENNGk8-XqW?mURy z;7DGDnENz$=hFf&Rx&~1?gs=4BGsJ&$p};da2G-B21jxY#N40fgf%Iq1)iv635B~) z6DW;T#|hYfR`*3Z7=XJRVksnhLCk%(yDMmc`zx8EaQDWOYzZnO)q4U}5oibCu7Sk3 z!I3-zG53k?uB8QDsAL_5yYCRFk5o4aG(=zq0CywAZg3=*K+OH|@USLLw7}z)Y^HGc zF#;`->Og_E2xJ3rw?pg(N3tEn+;_OUgBEzhiMqcj-2JXVSEPDLpgRIj0dV&~IBZ|_ z0L0wKyStYbc%G8>y>#xrQ6L@QZ{1Y_84)M};Ld_@*uLsCh`B%9FRV#6E%0b1;}q^b zKp-bl?JkfTfph@wJP3#Ft2Pz}_pR>Erv+XKZcQa9-2H+;L8N*@AQ^!M0PZ3PhwZEG zfY^B-X$90sDqXUy}<3?At^foC?5Q4&ktU)iDrrzsvJ%dk#@S3p`xO z6otEY6{w6OH@mx*7PuAMnyRC4_hSO}k?KByh6vma;Jh0l z90?>hLd-qN-A%N>mn+#!;qLPUS|ZiS0&Nk<2iP}VrjpfF?ZL7?$mDOt!=iKqytAU6 z6fXaIoLxy*V394mqdxcs43cUe8q&(mDyY&?o2&4o29sHF* zMg)EaIN6>a#n`08POVO5(PJephTALb>93F-@F~j2Df}9a7RZT&xdOQn7y>w1- z44qnCl^0Z7eiU9|@vyK;`2lx>x#cPR72Xsmh=k7xBms8Hoa^5JcFRM2OMWlJ&VG`+ zi)k_VKT4KRxcgdxQh=||PX+8dq1^owfZg&@<|K%@2f4d~7VbPHQ_j!Tb{ztdznBzz}-jba$+Zx3 zpXKgshQXyu#v}Jx0y&ZUM*_K#`!E3RJP11$lMI8H`^&w1K=))SP1tN zh`GnRyMh+DNXZn1yT=Mt0^B`HpbFsbeE_&?AhEb4gCOSKG&HP9EiK%i9H+S4RiFXj?xg_SjgVMEl0^`6FK~AgE!?$AHdDBJjz9~*-PZ`T0o*+qfV&-HH#m~B zAm$$D?habuW0mZraQ8@oE`YoD66glFdwT%x9*7Mg*?e4ZuiGoENiQvMC%82g<9jaM zy+R-z;O=JxG63#=7=SwqVhqUwh@JO5cW2WAS1M`Wt?cgU0yzM8Um}nTaQ7Ji+<6e+ zl8=R$dyKpDY2nUQGC|?)Ap!*ecW)<<1i1UNW92S_*bRD8RL71-M!%$TY_|eyWbVa0J!@l0PZXZhi%ChL(Ki!kgz7% zw7~UB#wpxAPap^2?i&Si0q(vEfIAPuVO#Qv5OZJa?tEI{q>>2=caIh*0J!@Afh54) zy900+K{#wn-XCJ_;qES`1zvx&?r#cruM{W+xcdbG`_5~3KLNm94&ksZ`9g@fm+ukQ zq=FW>M#&U~yKfMv1h{*uKo!8<7XonCKse$^#zV|K-QBgcz(*)qN8#?_0`&lQ?<&v$ zaCaI2cO!%&fn-B|a1V8N6D@EDnEeTbyITcX0PcQFpbg;e`vA5Z4}$qrcFwK4hxO

2;pe+VAP3;yM+9;K?)^Q$_S->l8f^NcD_BRRkUe;I1(emt+CN z&bz<6YiZ%GRI-l3-O~l?Bh@7W4G}m4fVkq~p=>+Wn?;N6srQ@FdoKu)CE zc$i&TZUo)~;La1#Bk&avbB}j-J}ul$N+u}Wy-=VaQr#htjKEC*+(jbVZ%>Dqd(+Ng zO^RvZzCg(m3U`keD2-Id2$V(OKmhJ?5$(5!K+L_s-4(QO$COM_xO+pMEkR|ZdRL$- z0xtn@*NDVTvKV6Saqh0A1+G`Jj>6sZ1nMKzjRFl3xC(%~Q6yoKi4b$I+bOI`6D@F3 z$z}?7j}~Z&R0jyOMPPRT?sk!sN%}*~J~%ss~4y|lnJO2+tOiSE8ZAl?6*A`?>uG9qvx0C!d(w%;BPG56{n!cM95x5V4yC@Lb zZ_k6+c^~fXVp`x|C|N?`?#l&ABh`5VWf3?TfV(^p+i#DCn0w`*uqG9>z;Pv06z6{aQ8qGVWbse?rYuMON(TqlJ?goxceS~bo)CKLhx3B41l|@ z1K`erq{7JM5OWWAxBZO^xX)HHPT}rx0yzLXnE0VUF2LPC0N~EE5fCGp5OXizKCDST zEpYEh-QN`MUL#OoK@KMVA&>;P`|kkUMK%)lRgXfA{Kv`GF=$r1{8&lM=OAO{ma z7bpX``!WFTavMq4$hi=64|R71Es_Z(Qxxt#RG`v=98ByhPz7-JP5|6BHWKz#Umg_P z%QC{6)Y2l^1?G7-g}Yx9sJ9>o6VD1X0Nnj&0PaQ`A;ic%5IgV7-Q7eBT%}|)g}Y}7 zv{;aXiAx3A0Pa2qfV{&s_HG!NLm}oKJC{SQk0H5dDl_df0 z{s4fxNF)6$^BTn5k8T&%q?i`?DJ4rNY!vXIK&gTPcL|gM++7X8U9J)Bt7bvWeZIRZ zXrU@mGDTsdfYSvk6%;sLpbFsbLjbsIG@^agP>8wvyStVas&pmmC~Oq4X@o67y@CSo z3p4=S{R#kgqeir^S^_cmg9E~vG|@uUpky7U>EXP4X@PqV(EZI{Z)2l? zRRZbu_uT~WvOosF-A@B>XK5s5nFk@}zSrH^w7|D38K-dfY=ImFHF32-F2LOv190bs z5!+Xt4l(z5cjwar=PQ|@aQ6s-f)KQcJp_^fcMkyIE(#;IuiCVKaBu1#)})vg_-!!H zyD8kgT%a@rZQ@CRGJw1P1i)P$Mr>a-A7badz}*$Jz$qnD6z-lTP#J%stNCwY0!FO4d=hd$2%#2--wHfd+uPKgp51F^t&0Y8AxX>-vQ?X`%&g z1M@tP!rhMxw1l8d+%M1uaQANkoOgQ|i8C@AV(xkF?w|#}Ldi}Fcb_lN6@oT#iav_THq`t?XNX*_m<&yA?g12u`uzmKnB3w?*Mp?nFXP}g37#W z(!zRV2i&AooWi{e1#$rXd2@$AF2KDv0eFs?7X-7Yv=;8o54c3B1ciG~7bpO@_jrLM zz`ch6POVNA1;H>X^Nw|Ialjc$l~A~M^M1Atr2zMSC{PA)?;8M~W0nU&E0uZIvG>up zoCMscREomA_Xt!1+|?CyPqMx-@Y88wQzSWEe6k4vW~*t;{@sf?*5@b z1Hj!s0N`$nGMNx_k8yVsE!@37(ES|Etr2JexceUhZ2))w9boq}B+d+yMMz?}t2g^@4g!F{;9vuTm+0`sh!!q59PfgFGxOgt-)3vl z*-AE3xO<>Liv>BD_n7cb@k$g(YP6~HFD9~j=4kqpr z=mxmE8i2dUMiLsC1u=Ike_GnUp7hcpS)!!k}}B{h`AU2JFH1IEpU#KaSA{0!2&r7YNDS&F2LQN z>?La+;qF>m40eHe{znlH$=3wx z0q%ZQpaEb9kv{|MeunsU-UG4oKFQrpv>2>XvY8?tlCuO_0Pen2pbcOLk#hicKS!By z5OZ(%G^|MnEd~!(vNM_+D$oUR_YMNx0C)d;ce#5Y2@sMs5OdFWcP}k)JDC0UkiPR? zB9IPn_ag!s06U2M9>95LK~jOtg_wJ+yR&IAI77)eMLf(+6379#`%HmcfE`3m0N~Cu z!VKh4h}@rqHOZ$1&Q>x(5f90M0tEnff4-YtS<*~l;sXHgA|u?ANM3`Od+w&-E~W*3 zO34z6ct}1dPzrGOT>@oh3KP`;+~r2XetQPW(pH~0&w>jNhmWAV(!=cKIx@}s%KZ--`RccRRZY%cfTx<0kDI}(*WFAMp7aV zLd<=wyR&IAc)OBuig-xQ7RUj(`)Yw)GlhwZ0l4!-wBJ4*V(#Hv?{8YT^Oa0c#6xm~ zKmowrdk7?DQe*%CcacaxlWf{0xR>)+31~k;3;Z^i=YJILUM^4yaQBk}Wil!9Cjjnp z5$(6nPkk zSfC!@?tTIdGAZ&&mfVdZ+HbFdn0wiVuqI8kaJPYZ{zu{N#|2sd?!I53O(sQt3*fxl zMdBuz4KerS?(U!kzCy`P3U{9`&;@Y!DFWRxDRMLbcaKQIBqJc^9_;R3THq`tV|(_w zw`AIdqyyaju|NjE4kGUWaA%36j4X$kd-2C%O|of$|Egr1A|8_W3giIX{TqQ?nH2dY z0C!#>w%?uxG4~{Q=hFfgE195h_elZ;0CyiHkPIe^><_?Q6o~D&2Sdy~$lb-Xz+dgG z`jX*x?tV+4ESN0vJOFojAhzFr9AfTAKMHG7K?_``WQxMww+U1N+o-I1ll6-HvsN-i0`ZJh1hvNxIU~&2QA$Hqhu$AyRQ}Kic~)p=#Ico z0JwV~zOOn7V(zot-AfC1o|3V>`rN|=(gA+nI}2n);Oiab&WbYYAm;vZo!{TIaK8@b zd0;fROduyx{Y4-*0)GH--g%HjOp@Cm=Dye6`Lu9XD4C$}HJKt%5UGAFkc>bf0Cy22 z72JnI%st-S#k6qmtz-#>y9WuBMyf9c*_D+=U@ZW5ImFft$(s;!Z~B+t-?YHZN~S2> zy-1)kQr#_36@gm-xN9K3ueugu?s@L6rG>jx$vO&mpCwQqseUBT5P`!0xEmq9uNnq1 z_gHr~(ZZdfWHW`kH}7Ce&=RRW6ljaU8vxwx5Z_lVgP42GTED+(;eJTTP6~G~5a^0j zzY^$V!FweUw+`U4eGEzMwP!)lP0k~@*c7r2X0I~Cab4^&2T3X;rCF>~MJzbzaQe7g@ z5P>rQxEmpMgCiLWG4~DbZlVRwRkE4F-9rRgBGq;RZ4vlvpxo^cyTOsHhM0SVyE|xs zUj*|!kiy-I1-c^D0|MOG@?wL zCXf+<;{dp`AeLfeB*fga+?`Deyql77ig-}<7s!cJ8@ID7%ZG)jb5C=31ubw)$rOdVHw>^PsEkza3RFelB>?Uk2#4*f7DLQE#ND;D z!1YSjQMh}aKz*dTQJ^6LR{?N0LO5(+H4$R&CHxI&_VuKR7C5P7GljcH3$#S40|eS4 zusZ;EJA}jbRsA96p5pEfTHy8Ry1yyhy;7hnQoSJ19f2nRxO*TRwy#!;_K zH9@e3%Dj`@TN`jYm}i0%?p-2KA88&DXo$e?0X)ZS41(LJ%$w=nrhqGyYNl}S6oHmV z^J9TFK);yYfkJ@Y&k)~l9}dx4ycgD@gBEYq-b!{-xOIS%bEx_() zi0`-GgqVAxyL)NjZdNk3Z=ZXSKsvzPcMD_y+o*$1lj=Zo(#a<4)Oi=SrBt?emAU12QA#kD%nZl z?vVmr0C(>t&<$|+_5j>H5Z`Za{wBEZc{jLwY2oe!^SpalpL>NsI>6n}2xI`<{V)J` z7Q{{n$pVO-_et)~rUkB4GEU*=JzXFN;O1AsdZ5=%%j7GmxV?}YQtr-eIL z$pnSFhX@n^+`XMZ65#I7zLvWP;`{B@5OdFWcQGy8FM@dJyD<%;O^4|ssQdj4uHD`VhqVhh`HCi9oD3l7I-%$ z>nPmaU!We~?u}pBl{Em|{T={!BgFUHD{Sp9omIX*5Sqw4v9Cv5a0@o`Ur{Et0^8|9J+Wz+Gg4u;1c>>)4cb^Qv-D82U-yRJy_b_+&(n1wiGWLT$ccwr(z}?^U+LdJh-2D*%cNT;r zj$|do+^rp9O|of$Ta=7b@LC2Q703ak1@K3KT!6cO4ZxiT;Yg6V0b=fH?#`zLE>ki= z5oZu^u0R1GEr1gRk^pxf39$V(g!vIv<{j$Z;()W1Dxq-imd$qgrGT{H{aBz3;NEut zw%>-}T|uR_cr&a;MZir;r6}CHP@oc!7QA-|Q~}(36M*j}MP=UU?yU{DM5#Io z_nt0L4@e8%;{_T3?mYxx`)vr`VN~WF=H8}&Gn8tkaPQ{NZ5>(wb`bnfpe+J#0PKE- z_|o+o0^Je#8Nlvmi0`+jK+HYO-MzF(7AeX8 zEfx=|u>$-yP6rdC1bDc!73>4B`#H)Cf|z@VyZKfSbAS4oEkQh*TP=_isa_SxjlfcX z-OrE&uZLt2#N12X2y2p03tX#Yf+8N~<_Hu3>|o*=fn)?G18^5XQeosQh`Fb@yO;qCH&f72;2$a zynBpV+<8V4%B+Bxd&{fgyz^O|)<)m29SP_h^BZNOgcfTLg9o;BFVue!D-!+$XuagBI@fd>Q8crf~O4 zfv!mPfX%*0;Q4aJb|(ZoD9HS9*FI?M?=hg zo4YG$f#XW1DBPVXP#LMd`Pi9~6{-Fu&>ew)0&w>PV$Zi*A?7~R-MzHHjY`J$?{nWHkPfheiCYCS z0BHeS2f&>L@qN|h5OcrzQdpC0S|ra_GENZ>$#DWX06UoYp+GLc-9G@}&V%^ADidPv z8{C~wi)8P5-QN`PpjsnP0I-9Je+VQ2?*2OfcTtpi6k_fXFNO0irbY61N|r=(a|KEP z?*6$z8Nl6_0dSW?63ieu7h>)eF9vr7EpS4~6h%DD9V$=>u!D(x1*!n<-U)!a29gRR zU#<)8S?;c-MY0Rb^FWGtn0rm29$*I(&k8gE-2G<&?nWB{F>(*Y&U=`I1B=3eqbaOcy)ovmboA|6x&1qv(} zz~^i2%8~$ge*nN;WFv7&UW1r>io1(x;eJZV5(>r)JSb3V!2s?OCSFn0rfWIPZK~xbu}vP`G=9K!Jjq*h3%*aQ6TJ?jnuEO|t33;9ltN zVp`z0!94G#aQAY7QUx{fq(B+K-G2h$F4suHB=aG5-lw;Q^RA!;PAQq9aQ8HUN(D7> zkw6u|-4g(~Yc!HF$ry;aH$NBLwY0!FO4d=hd$2&gf|}?j&;W4vCm+b&7)ESgwF+YH zd)(bb3)}|gc_4+m9~Wo|L7TW=pbg;e-vT)A_Ap}ms@V{8pXBZiTHq^`?4)q_`2t-b zXcMOhbOYRdGyr!`7}1}9f|z^5-@}^p(ju9qWGuJOy@kgzJnshB!NkV`831>`1HhdX zMr>cT9AfVIe-G|#THwDb8K;N`)x82aA!rl75y%C&`1-f&wHpqI>5dA3SND#9_C&X$N|{F z#Ipjq0C)cxVD~e`_uKbC?7VMtcRnqWRZ1o(;z2b_pa5V86PF4k0q#BrVE1#B83!@< z{3YSMi)oQOSjm!TZm2*hz}-6tlmXoRZ=TlL{R~MkgJccF+^e1m?h0Dqb}-KaDdJ&n zi9jX54kjKEr~+5qnU0D!yQMnFhjgP41Hb8vUi z0zai>Cq+D{9u(-ZU;uXsbOYR74Zz)FBVoTi3u5l+?(U_9yF|&@L4EGi1=0a_Fmb#< z2Eg5i0B~p72sbz*Lm}oK>h5e>;B+P96!DUl@^H$^8KXM($y7Vg`XtfPnr)og)!3kGntKm)+t7XxrNY9!4hr$fv=$=yw~!1+ox zQ^bR6gg}den%G024dCtp0Nm{w(SCc=+rd4^-5s=WzYXSjAceb^3v?-{i6;fR0q*`2 z0C$f@!k>SF*m*ztbXb#KTDVh6#zywJrwOD3>|o*|fee7VCjf9~X+-<&F%WZ~|8#I? z(;}IpWSk-%RD%U_6x2jNfn0#QKUpbvo<_o-e}b61zq|8k;cf%-JdncOj|&tisEPXp zk^p!A7QlHIX(Vov*${I-_*6LWVp`xUlq{ie_xS>)3TomMfii%*j|Si_*GR%7BOvBJ z%iR^Uz*$PBDBQi}ExV9P1vT-pKo!8f`S2i4&MIU#5h`w8R%+`S6` zcU~CLpMT;%+M0WuyYp$0>;`jxQ@Hz0fr1dUiRT280C)clfV(J+#2L94V(xWMgf%Iq z1^yo;ODNoZtw3oA+Qd%<$^h>E2>^F_7)da462#o|o(S#=THrh-QxxtVCQuoIHnFon z6~NtJzlodub`Wf!GVfUT)&|@G=2;+xds_wS0e0YhOrQba-unQy-wuNLR9cIsaK23e zr<7`@hzHFyffj%rcrOxY1Gskr!1migFqX=^^WEDKaIR9F6!D-LBG3h}1MhYM-2nG~ z_K&dN4uUmQ<{jtW-hkV|JpVf+7E1)p5`lC;S^$p-WB}~og?Z4fV=M!CIs9gc-;Zh`A4UcM~megObe@iIAKx&;oGx%>r#^3KKH{xZ92N zlVlRa+$;YU)}(_L?o*ZQq)3G17=bQ;yAKlRHdC0`6M(zNNZ4-=gqVBw--5fB7Ve(c zbbpWPbFUIe2e|uXfee5hM4krV&N34A+Yds_J<{FTv>3cy$v8zKBxeic0Nj1GK(3j> z#Ki#Ic}Bv1`*eu8U;Aq~?|fRg^Oa0cBtmk8Kmowrdk7@W6eb1$a2FYgOS0+J;J((~ z#k6q04d(eDMIt1Z3zP!f{iHydnZm@M0JzJIBqW&+vGX48?h0DCQ%a^N7&9;ugKjfl4g=s z5OYs=cM~me8<^*R6p4_0T%ZNu?)wGWWK!g}0M5HzMEmX85OWW8cLy!pS18#@;qLPV zx&ZDzMW9lahv@xtn6D@EznERW;-ERuC0NnkYKwB_b?e|PuN0_Q0i z%jeo5}EKy1Ig4r1;H7lk#+rUiZ-%=18sL`W_Z$N{+fF9Ny2 zWRX7rIPbhbY`=XQ#N21OJD(P~LdgV$yQc^g0Nnj!fn)><0l14GzOOnQV(u>=3Flo* z3-{himQc8RkU(jq`r<{qva$%Q1>i1+WQA*e6JqXr-CaQoce9cy3U@COs08?V-z`uT zfm;B$YasF9z7}Hc@$Rmrg}YSAItq86B~Tx!ek9Nkfx`f}8>7rHh`BdC9M+_X7VZos zo1?kS%WVl-BGrciZ4r0_fV&-%3g^8HV(ta*?x2PHAtgI0{Ja+kbVaIP33Nx`X8_zi z5L-7SQy}IZ>+W7!;36eshxNI~3Zw&ky+;XTL|`8P?kq^GpCp4I=3etqSd(m8xIcYC z_cw)~_iBNhNcE~fZUmMBaOXjMU$qEg?%Uj*PYZXgk_ifT&k-nyRM!Y3BQP0&y9na@ zs#-QY-uK+HYN z-4(RJF(p$J?%wd6EkR|ZdRL$-0xtn@*FfwBN3s}V?$(B|CbhJ{^-9)JxO<*JeWbck zpdkWR0dO}$EQMqu#N5-|-9!tVRI-`E-J=CsBGmx`Z4uZVfV&-HH#n015OWW4cLy!- z`oHV`rf~O4fv!mPf7@m(Q8IRTpZf-Zbb#OAQw1_2 za3KJ97KFq0RpTM%p5pFoTHqs;j8h~S1Pm9*iB!7^J{IVXz&ik*WA+5W3M#FIdwT7JiI|MQ!a1(&%m{}06Zx)q#AFU7TkR5P|QgI4D-_r$hBF*svxe+)7 z!1oR01;H>X^Go*$1lj=Zo(#a<4oQXcJ_};*h3@X4h5J|~J1P9UM+$TS+`X4TH^AN7 z19101YzE2ZCBZ$;-MzHHonW4KAKB+#A&?Gm_cH<+0CztOz?}s#gk%B4&U?cj!kT2$ z0#_;-r||QhE|3Fo_ay?k0C%4Oz?}zSJ7SWt5OdFVcRnp}u967~cMlOL0JwWQfh54) zpFJaY5ybb~t0Cqdyn?tVa^4B+lN0i1U^#P{2CAm(0O7uKYL z7VdH-Qxxu=C{PJ-_h|xE0Cyh;z+D50#U&XDG51_|*V4kho04@D?(Q#84{-O!r|rrb z0PcPdfV&Z5AtWmx=04orO|-yGN;Xrtd!axJz}e80UAV(#JYuAqgxM#&U~yKfMv1h{*uKo!8<7XonCKw`1DNybCWz5MrK zO=@X@k5ICX!rj9K>H+TFRiFXj?lb`IMu?@5Y>)4cb^Qv-2>sU{q|^xxtIMe ztVu5|a9qjQ=stI*KsvzP-#lSgmH}}0M*!Se5DwdKuY{QUa(8Fb0=Fm`r${gecvK(< z;O;*PBUfV+PH zz}*PpNRY{dnETPY!kRSE0{1?y`pms2f;)tt%ZAg15PRx%kT4!7DxxU_W*$mfO~fb*nS(r z^$nyl@8frdb;u65=P_G{IEA0@DuEn;dtVmF1-SQVfbF+~U=fvhC%HF2;98{;6z-iP zPyn!l6S_tq8G*?FyPqMx-#!bXwQzSaEt1D7SwfKrs*wVvk!ml2vIuMsu=^R}`|Ztt z3+_j2!+KQE!rckx`5%S5R|r%_s%HeMBJeQ4?q^6moX-M?o%i|fuBC;$Qpq|BcTX3n zk5rckG(_MGfZfkgW-P?q{oUO}3wN%P&C%QtftEfja@5caIV7BP4Sm=03~ay|lpPO2&@vb59gV2iU>H zX#yD$I1Ye2%SbV+<8XAetQMP z-1oXWpBC;WB@-0xUMNrysqPR+M&Kp@?jj@M`v#^%%st-S#k6o=pkxV!yT=QZMyg{3 z$|7(e0C%|&Zg3<+Am-lm+ps1Tw7@YXQxxvr@E2Qx%1HIDKve`@0^qJO5|?B##M}$q zT}umhy^?hl?w%)5AE|B>Xo$d70Njm62qBpWG50ukH_-wom29SP_h^BZNOgcfTLg9o z;BGgPlB7Sx-0N-+Ytlgr_xeBU{-$vEN`bCO^@2cm1fBrk?h#2d$wG*^=efI=7Pv;q z*fD+X8wAn;b}%tjAR_`70&r)EXumxkV(u~S&Zb532qohbiJ%%TkQ1qP708W18US~m zh@Nk6XbkSv^TL|s)56^W=J_9myITbcBGqF8$q3vBz+EJw{q{VFo%dXK7t_N13nfb^ z+18|p%==t_&h`A4UcLgonaV1j}?#>jbj8xw&vMZ~Kz()YwH6n47 ztb~|*7@m3R5CWE&wYA_Ew7>}^Qxxt#RG>0a?JH0P z@D1Ef0NgbY-&cM4P;d`*cP%a4U0|LEQn>pyfqH-)Ogt;l0C4x80k|6>zOT9mV&}c= zwy-8mv`AJd*-ViLs#yXp06UntRGVJBu= zQLzpsOEOljSfVnca)ruye?Q;v*?nK38BidPQws4d8)RAx6BWSfnhd&G2fN1{)pxtgG{$8~cvG$l~cW^|zO`DzEc6YVOF;MZF9{pKdxhXggg zXcNy0YJq6~9H3q2Blcdk9kKSbXxDQDw`eorCfe%+4Zdg-_X-+;Xx{?RZt@X(uUd#$ zd*2#=Ce0kd<=SlFCfd^lt-fdzrwQ7CXrBnsZub#;uR6BL+pD78!4W(N^Zn)~+WQ1u zzGxFa6Z8Pl{sBO{*GF=UY(ngFPmOjTNALsM?B^!hD+L3-XcIRH27zc_1<)Sykvt=F z5Nq%GoIjIcj^I*lW@e4FCkwKGT}+%HC;+1U`Zs9v|KBRY@nP<)yE3{(&V6uqlH(@t znQlQbU>A!|2}*$IJ^}DMW~m1@aHnSx-7@DyTX}Bso>?X+2kfGIouC4U?xg^~V^(@# z7I)U&d%r)2D(4byRdbW~%tS#AU>Dst32K4p{`2el9kb2@2f4HEs_51`_rm-h$W7ie zI|U7ZU39-AXaq((d=0SgGZOFHYY{z*Xg71j<{jE>;U@2!iv_KKT})giXak~s0bt)} zB;L2DA=cj0;LoFjBQ}e)*~v}bQ)2~PfL%=dtHu7Z9w6F(0qpyXBkel@+LeesCPo$`)}9#cDvscfX|tLe8wF+x zYV3{+c(0%~Khl0XK)Vi!_wBKWwRe8jpGiGOYz{rH?>9F#3LFqL*c}(}Ye8dvr2P|s zb`uis+glK8FNtkB@c-M{Jg9 zvy&Se1*QtR?2ZdKMbMKUX&(pB?nUei&d8Ckd3*bP{!IEfg8u;Xdp9@ym%(nqfZcHc zKN1Y)N7~;6Xb&N_#K;E3KKG(%4?8E?%v>zXx)Y_tbVl zxfV6?q@V(b_9FnFyHX>1-(HPadqK3TIHG;CHmkXb_Ci687Bz8+pcaVsc>wJ?jrhO+ zgjoC7D*xQ|9MLY+X2MOhk3D98NP`wN@v@*1i1u>;?Iw-reS0@z?ak3{=7{$9wb{Z= zv^NP_wWx`Q1#LjI?*nMJYsCNkC&b$GqTRs}?J8|{aue-2f-Ws;;#@%w5be_e+Pxad zS!4oY?N{&hXVS+J{MuLa{pKdxhXey!)WoxbK_J>c2WSszByW-Jh_yFHdzd4*MVpzA zjC_y5|7)zU_gj-04|Fx6HXvTX}Bso;miY z{psa^U36a-Q~=R^4zTxa5A5O2y3gO`&!Nh>LtE9{#mD#y>qp;5^fUD%o8*Kqa4l`Gy-<{+w|(f+fb4X}%q-vIV~M&f;YCt~dbpYi9>!4ZRP+U(>e@y!i_E+E>U6Z8Of zv2rJ1-)AJ}vKX=UnrQcN#NfxY+0RYlb29}4K(ya07zFHMzZ3yOhg-!3Qt zqJ2F;yVQgkBJ&VyuZVUTM{t=o^V}qi0#gO$K(tR0Q~-7{aU4Lq(uA)ha%8=?r$oDo zBlr(6zyEQQ_~vdw4G`@g32FhmSotPEyUrvl*?`#R-o3(~Nj*mlCfZE6NqlpepaF>X zb%I8~E>Ge-;-YqN!$#5X4hT7hVf7PJ9&vGU5pYPXyC@7o6u zYfp-H2S*I{!2CYKP2!u|1zkY2pA_@}cCqpZz~}BY$w^it*537Le}Wi1sCdLBK9n&I4!hCnQ#;Bb%F*U+V=_?Rnp2W0PQA;-nSPb*4}o9Ka*yTXqRiV zg_~$k7qkM=K26Z3l2%RxXtzuBzI|++w--jcgCp95FyC)(qP>QDNbN#NVsZ$v_WWoUaRmPs?oM*tB)+*zPz*%-dx8>`wDNU; z&t2+b@7wDTYahMcKX(~NaJ@G3+(dhcpd5(ym4XT{*~*0g?MfGW-=2YagofXEMwY?MiKC=8Uvw39>+Z?hgnGQg|0YyAa7`6d8wDdwsNvIHLWJ zwfcT@6QBEeL2;V;UqML<{{zr2Me^R>idcJQw97c6-K@<#H_=`rC{I(L7F4A0DS&om zx-%cK_Vdg9nN)E^`@`C-PUp@P)TF6*2x?P!GeElzvFAqQ)h~E^ZM5q-g8N~9@8%{x z_Z~q*n)+`+V+#KT&~8HFz3MTfQHBEg|(3Zj{0NU+HCacIS z#M=9A^=Hz-5$zIfc5)N#iGr>)^(H}23jh4P+Pz4;S3QqddsVdiIHLVanBN1riS`b` zK$`lNU@(O*1AOiwB;KpmAl9B5?O~2+*J(3TG16Wn$O7^G{0NSNUyjSf(ti2-IWgOA&&}N>SXm1vj zr>RE;6)D^g(5^&miO6!q+LNPQ#SwgsHmkXb_FO?tn);BSHih>9wCfOia6~2|*6zO9 zpGiGOa7LR6H_<-4#vVaKn);)lF@;|Nw3`t7f)m++SbJHtn>m78wb{Z=wATw-)6{1L zZ7JLa&~8WU3r=JaV(p31?%)Wn&}Jt$(VijbN>gVDdQvzUpxuku7o5l##M(QT`ZMX{ z2!8Q%`hIg0?frs*H1!L?U`6!y9MQG>M21*3QqvED-qiwvL3O|y)fEU9Km;MvznV|FBR0JsjCIGDO?QD zu0y!&y=n$x?J?1==LpVeGvOxM;{^?A>Wv2b%NkR7383ACaM^p+e#F{aZuDo;%n{tB z%@%H=y-m=XroJI)OW}(E?RJFA-m6w2)}9~j4vyejZFX`K?FE9aGWZj5e~bBngBxry#N zK~0*uS5TY6Edak`)_GtNcX}4lt#_`_R>DnmX9yb7%o&2l6ix>C9ka;;W4W{Lt2g*_ zXm%dD&z?gIH_<&HXiYP}7PO`C6M)|_+dZ(AJL_(WZijQTwmP|q?ixW?n)$S#2N<2v zKY>pH_I*YQT;?Ns7SZnGh<{Wc)@DC9(LPf!0K|9h9fCn1+HVHz`;5f<_N$4vk9^9X z$1q2<`(b_$oIBFqBgg{L{%=765bgg0?E8%5{Bu8s*ynyM+C?1EZqR0qoA}(z1;s$L zKPe~yqWuZLzRyVB+p`dB&yIE(N3=_{ndc_j69wf!wBIDC0HXcRRccqJJI^E5K2+n+ zq>3Zjzl8ZcFrC{Wr~#t=EkP|1?Jom-?mEO~h^#@Zy*}FY9Km(kOt^{9y-3giMEm1{ zMj+ZB0cbZNMnuj;tUWW@%^bmR(`E}d(Hw0n_w-(HSbdu_D)IHG-xHv74W_FTaL5bX~M z27ze52cSKK#QXL{#M;xNJbGTgjjoZw97c6U7^i9H_@IUCEj5#TbupdM0=@V0EqV0fdx4-Fi1tN-3Lx6&0JJL++afXrvG&qvS8)U%ugz+1qJ8u;_6TZ# zXulw+1)}|XfOZ|iW$)YFh_xp~yPhMsU7HCv(cUO%0HXbnpb?1nJpk<{gv;Kymm=2Q zah*StW{%*?wb{Z=v}X%ifoPv4Xak~sDnPp(;j;Jb@rbn-N4tX~c(`8QZ*HP}P|yWL z`x!wG5bdV{+Pw&uy>D+rtUWH;eH_7G)n-38(OxSU0HS@DU=WD*jR5T-gv;Ky7a-Q& zcC9~?VUFOuHZva|X-^YmfoQ*5Pyj^xtpM#pgeynn=n8KyjCK)6@BrMM9#zhz+N$Ozx|0PpKy*(K)B@3c{nOU%sPn*K?yS2oy7kU|FuwzG6Wwk>0}$P( z1dTv+p8)KA+XEZ8v+mgFHajQUYT+ij%LJ`JbgvV%0nxn_u=i~b%;L_v+ZOtB=x{F4 zRwp;nohax6?BapHNzjwRKkw8(yIv&Tx1UG!ETY}V5u3k+`TdWZ#8W#218M48g25EN z4A}P>iTCX_h_%N=dzd5Ib=u6#8)+{RWC6RF__&}Tg^vLCeMWLVawcN!Em!+9DdLFD zw`nuSP2zK71jT9U?|0Z=R+7S>0Q){8dEeQCSbKi7%Q&Llq0Kxu(cUa5Pg9QyDpI%~ zpk0~nEJv(;^eX?{RUFa2Mw`{?++0CTn);BSHih>9wChax9ub*{SbI~n>p6lm+Dy2K z&wY5gJ%WZb^+!Qt3cmtqH<|F2B(ejs_S|SUa|E|)vxS>zuNSnYsm}`9Qn(GE-ENXq zWD#QRBUS!PIyjY!rOfu!7tvf?>9Hm z-Y*zPQ@;=lrtm|6_K=DH?+t85tUW6}_b^Aao3xp!9BHo-WC6RFxK&V)!nFYHLX(_E z<|5WUxWGSm5l3vEt<4-aiKnIriqq6df|3+Q0klg^@`@b3&D-muUB(gZKA7MCxQTYR zpgc`IC8$W@34nH`2}?xQBYNJ^uHp#3TbtF~M0=^ACQV%}s7>KwfOef^ltpGB);@5h zKa+Zn;G8xSZlXP2(2%CysI$MUF@=`^+D#I@Z|_H}y(Zet9MSI5W(zmb-X>^GQ{ND@ zrSL_7cDqEsZ?8i1xj*5byMrUzwc70DCfW-GU1{nfK~D6=O)@mm)Rp2NK-Ee22=PwKzm4{-?zIFYp;ySQI6)8Lo(5{pu7TJbadwI00ID)^b&1!C1!fZlXO&Fp#F+A{b2J-#4p0f(F1YCe9Z$0?~dy zK)Wg3nS@yTSfzjNW{%j*YO^JsJ93jff>t2fe-^X>(f$oUyWK`0B0CXlZ;o~cM{t`q zJGn_11vUt}Y|-I!f*v5+cLKC~ZN%TJ79-Z47wtZdNPSG3{oEv;nkg8tMThqa27ze5 z9iTmABUwepBGx_}-@##yXb;_}@Av$X_5nc_u#1Ub3krZ}{{*02Xe0h!wFR;EWB>pC z=7`NNX*0)7;;GewVq0{$T~Go<`+9(Osg3XjCo&JQ_UvevaRirXGtW)psi}f;TXZ-@ zPys~yIDmGgjpP(LQtRzQA0Nek#)(N4N3{O{^LsZpiKli8YHZQrM}k@)+TR3d*V#y3 zkqwA_?)A~G=ZJQq&4io8Q_BPmw&-x3pb?1nr2y?F8%Y$IiCBAPw3|7iU98O(ZWuF| zAZWEkhtYyIAlk1iQM+9uqip8@V(sTI^=Hz-5!?gwdmuN7r?v~aw5W+E1wBBt9|8E> zy&BPb)oR4rYop!A5$&6`+0RY17YYWnsEJDigFv*;185Iv#Q*&##M;xNJ6fF3krZ}KL^k*)QH}zb|co_|1p0iMI5pDeQoBrNj$YlP^?8wJS->y zqJ1AgyHq3o?>`~dULEZ+j%ZhDGtW)5=LpKRsEKn06+pC42WVGnBxjKch_%m*b`?kP zYoF5ho117K64Yo>6VD22foT66pk1etyhXMn)?RtBKa+Zn;1+Er+(dhwph1h8xL42! zMEe$ic9TXDi!4N}y<)Dnn>m8Zwb{Z=w5JPNwWx{H1Z_aHPXuVU`-r_)9jo#7j0m-)JN>SY8_(jo$);>;|Q+TW}cg9FAWr~smUAwav*M{!c;?vk_NR9NcF}!V&;vyGIe_0W zdp)p+JL^t}Zl809w)(kAJhNFa0N6$MQNbV(-TMK4#~kv&3hu1CbB;fUVdrXXWv(3Q z&J$#T=$u)_S*sbJ|lS_8H-qZ!A0J#=ZMXrMfM01ZW5n6AZP&W zV&d0=Mj+Zh0cba+J6jNIAG^@o%^b1$C2h8(bE^fdK(uccv;on+9-!TBBYbU$%tNfb zIocf@!DZU)%2WL+I<|se}MVDo14T_y9EQb z=|)|NK>-l$O99%2HsbHw zGZAZVjCK)6Y!+)X$4%m?34&r_7zCpIbAa}cM*QD@LaaR_+QS^t zZqa6@YNWkRkOk~w;$A@k5baw4+Jzd?`}RV_+6ON1XHvuwo8{Wfag%szx}aE#nmA2R z0z~^nfOe@y{NI1N+S_ZQUB(gZL74A1H__fFDA%GUekP~@qWuGacBMvg7TJW@=bjer zDvsa>v{}tfv{wphw5W-j1hqi4uL5Y-X(VrvIf%9Q<^7q|a|D-aGvOxMlLZZ0)Wivb zMj+a+U!``FMiPr0Lae$Ta*O|+K?x_r?lt`zhD(Y_F%-RmRvzC9hW_MTb(O!_#2->J=hZlXO-FyMjYhZU3Bji^Z?Pl1+e#R4=m!& zx|5>Y=Ukz!er^)a%n%F!cF{dUFbG8VWWe6HJusF#>vn(GpTn^8&=vL^GFQ{^_pJkh zEWo{We=R7m35TBm_I*a;eR~U{XA$ipj@bN?HgmS=?bU)}AlkPJN^F9QmFoffJ|pqI zJrA+=#Augs#9)~=^S0^jse*DK+NTIAY=VoG;{f|UBRQ8NmwS8XOn)X-95MI@nBV_w z)7!fRH9)j~B&f9sE>^w?*!LO9yKF%0b1#W@Jx2^C+DvTI+sgzEK(wzDG};6gE0+Sa zn~=n1CSvXJ(Qf95!D4N;*rvB92wH(?j~2Aq1Q#o>%vZZT-8q0*d;5p{nRIZ(U=Pgi zBkA0BK^GA1Cj~t=;q6BNK6kGPUr8dX5o<4sb{|LZ&D!j@Ise=X1p`2|FA)sd1Q#pk z0knrqvXZHYwZ}$#m?H)YwV7GSoVSl%W`9T)i1y2Z0-NAs^*nzj zMI15seQoA!&fA*=#Xz(l7L?co7c2Jxv`bC=eR~;V?FG>;ia28Mw{UlovpN6Vy9C8Rw7(}PQAsOb2l(8j z61{J)L#(|a+GQNkuGePX=DfW`P!2@~WIAH)S<$ZI2!5wFt8LEP z;{-K8wEy#Q`^#!o(#qce+I5n=MfM@qK6s8llX{NePHiSO=k2Y61|Zsx3mR3@%7Xyy zCP`wEm58<1MZ1|JxJH{THs|g6f>t2f9~HEzq?Hc>wA)?meS0!u?HSST;0P|zW~a@0 z`_)VB5p)63{;QzJOSbYmfOfBoy>IVAtbO2Ye|XvpN3`ju8~6slQ)re_2Tie*$Qi+K#_h?Ln+PA=+ge(eBV@ z-sZf$Sx}y)9u-uia6dr1(sum4YB^%<9i{$EsyL#3jW(-o&f9YZHEHTYg4z_`1JJIs z9e=Nyh**1ZwCg#dozZ4ubKX8&VUM68P5n{On8L3B+D*2TQ)CBX?Qzj==7@HyHd}1Y z+v^3bY3j3rwiIpyXt&!=UXewJwYN?4XVSqD?Fwyn+MKs%2)feL8G@b^P6lZA+D@X# z7{uBOqus|5?HA|h`)zaH-Y*zPQ@;=lrtm|6_K@wcL}W8!?J?0F<_K=mW@gbydzByy z@SkN9w+aeUxE7#YXghqt8JUY%drSP^gGC&{XKOQObN;!f2#V9xNrI9TMgg=-ZHF&7 zk;4~xdw#UbID-3Neh;)cZ+8pI)6`RfiWHszXjj?}UvMJp5&PUn-#?1~&i{T1NATU+ zthPCCFBR0JsjCIGDO?QDuCpD!;6!F1*4`BDdXC_nHWQok_IN=;CQAm~a{7YTY&I0vBJYdd_wiA+JP{aCd7ID(JYX1~pO`)Ij6f`K&kf?zO(-vhLV zY=YMT;l6l4MJZQ>z8K??T(v!?X}Tv<_I2u`MsN)XzvxY0@41dpe=>(0<_z0 zhc7shjfj2j>Cx`s2yWD7CpXbvA?O04eS@GUg)0Dl$LvK2%;CaEK;CIYYgyUJypaS5f$>5)P{j;k?;(hyhM9(7HRU9$+OPJpSxrz1;K@BjJgxiS`&lD=^9z|31t9vNj;ve**0LjO2Y}4`S`r(eB`gc84}Qxrz2>K^HK}7atY$ z0MWi5pxuikKC&FK_T*^yaYXwXZT52$?YV*hV3aR@NH7RQ`#k{dp>$^=V(mR=_%j*i zh;~MsnNNjlL?v_C5- z0Y>{DavMOq)OP%Rdl8~%674dMNL6SvZ*$(BAt(o;eTJX{813zo0os+eEn z_ENzB5bdi4gTQESUkuP5vK@wq%s{L?KH9?^!8vVa7LT;Y3$j48-fVw5R?PazDQ63MEe|ocBSq3`}P#X+GC?##S!h}wOMU*-ah&vdjvH=v|kX^0@40G zK)cR%{C&F{vG&%}{F&5qM7v#^iOqR?qo4tZ_Ctb3Alml;w3}=vle5TD#M%p@-OLeu zxi(vD&fBvEtw6NT60`x)J{6$dZabO0MaCo6K6a{q?hcON;q&zUwmEMf6m$X6en!v( zMEhxgcCYRD`}Q`(+MA=@#}VzXYO~+wyuDU107UyP!5|Rr8v)uww!;*W1&FohMSGYd zIIqo2%}9HiAPYqM-GTxj+HVDD7upU;;CsgdpiK^BPaMS=n#y5|7)zK!q)Pvy?Kv!h$& zT&S%aH}QFoonwD?F%aFC1tmaqp9AcD+XH*Jv+m({`Ew|9?$B1Ao9J#9lpApIfgTl9 zq;NlA-)AJ=x0fT<9y`g~RUEN-jW(-olg|ju71X4u4+&~hcn?6k4vF{eiHNml#^g z+ETa;uCR@v+WX_5UgF&@MH}DsuQNZ=V_MGLC5X!QDyT z=Dgi4C{I&Q2`W-}0-#-K;_ut*5&PVG-!Y2+&hLL5(Y{-o)i&qtrGlC?b+w>2g^K~& zbteA4Jp-}!%4pYfL_4R=#OAy`UeJ)H-uR&XWsNDk1ki3W$tkiQvG$Z`H*-Y0OPeh= z=k0BR)-?4EL0bx61ZcOLwMfy(Wnw zQxIz}k9HqNw2#+jzs-63XsJDdfi(4kU@(Q>1GI-Eqb$;mSbI{mhdF}VwV7Em(%vY@ z8gMc3kf0!idjQ&n61{IPMXbH6$e&3OM{HiM&795o=bkMnPE%(IN>Vr#pj|4_`}TOm z+DoHd#u4q|Y5IQKoVO1O%G1;{f{GNL2547G^!xTU#M%?0UBwaYuWGZ}=DfXDP?M(a z64a(}BS5=OqTja{AlBY7(Vs~@N3`?WOl;2E(*zA^>fM6I6y6HZZj$6Ia`Xe7%3mO3iY~}p` z?IxtaWfEfTP0?=Vh{3EjTeyk#ku&WPv>I?R@n=CB5bfUpwA+z*uiA-Ndv3HlIAXI+ zo1K>O&%Hs=Wx&P6=L9`KwC@CH_aZqTS&Ufw@C1J*eH^j*F>Ur+%G)yq0|s17yjL&? zMEmUk?I9%ZBV!S3Z;19VM{EwgSKsd&8S?f4LDqnaiC+r}fN1{&pk0V0KC%U|_N-_Z zam41Aw3)M%w^s{_4Y-)NT~Go<`+9(OX}U8HvG&1}{F#(-#AcZ`^Xc4FLAimqPZ3lA z(LN5KU1=l!UUlR>-d-2&DvoIX0q#z!ZO%XUZb6L!7ZX1c)B@4|CP2H+M*O{M17e?h zMzrfWVl&ZZVsqYJCTK9=V&XbMBM|LN0oqMAQlOEUh_w%#=+C5?BQ}e**6!ZYmegxoi_u5EK zBdZZ>Pm6XRM{M4#&3>En_Cmpc0T&aO2nK;@p9jz$vXQ(-rXtqfSLn}Vm?Jg|wVAnz zId31EYJW)9fQyNj1qDE~p95$Y+DM|2-H5gK6nMLcBR0RU&7947dy}BpfQyNT1tmbV z?*nL;YGjm+EJLh4B|di>}qTQm+#OA!cPSBu5P24MJ z1fqQlK)XpJ{_j5_)}9pYW{zl=YqQ1XyggmeszptlCTIhqeIh`+T_gVQKb`LFT_^Z6 z>EMX=Ak6pM=DfX6(4|F9{7ldTMEeH-?Ou)KEV2o)&%HF-eH_6LXtUquyuDH|phZpG zBp3vueHB1^NF#ZR%t5R@A=<+n!KK>FEFEc27Gw>$m^eXD07U!sVzmo3lGw;0#M(RF z>d&NzBlx#4zX#f!f9_p^Vl8Uodx8=m+Fu9w+@(HZ?^Wv%YcGy=8AouvHuE;;?InV8 zU$luU1r&was~ZoS?=RZQ?&u>@TYYqWw33cAbyt z-+w}^z3q5^CiNV#*{RLM=DfXC(BO+U@wlK7i1vd3?Is_w_o|hMwHHRanIpJHn=Lly z?fHUMU$lvj3fh2Ze-NPE?j!p5pAc)0iFOA^Y!+y<)8@SW>S^`}x_r?l{wnAJqWwF7 zcCU}*7}i-_~Zo&3XGV!GJH?#1{mEK(s#t&>r%UJR?gGYtN7NFh_8u zHZwPmv}Xyj23$;hKu`cg`&|IPV-_MDkLS+1$KK+fxyX6=RDHKCJ%_|H-aQ~_FyNy5Ye6HxOOwG* z0Q){8ypEV`LG&!%Jc|F$|C181TF`31#l-D`HXz#91NMDJ;(dD_V(t0S z?%;^cGHrHR%G*-~T?SlCoFeD}qJ11--)AJ}BS$8C`)K^n^*)Z+`~%#b^jpf?y9EOV zTul5(FbG8Zn}B_vk-U#=Kw+`^EzmkF{4TufXiC;*~;DL}grNql4` zV(q#A|31PIo5kA9S<2fJ1jPniOpF$k0MUNs-D;PnI|mSJ9~nD}|9)ap#u1x6Fuw<; zbK3>w2Ht*BPys~y5rEHKX(JGk)rhqpi*^-9@Xgw+wmJXY3k5X>TufXds0E^Z9zeU! zM*MwyDq`)~(XQu+%|dM^Hs|eQ@3KFn!GMd2mj#VLw4VcLH`$24Z|_E|edta8Oqw}j z^ZVLtu{m#V60{m{G4ZgV4T$!A0PS`g@%Qazh_%;8yMrS(tF+l^bKagK=rZ79;#@%w z5be_e+PyZC)5rwG+B2iw#}S*ay;I+BoAdS|!GHl56VD0;foT66pgm+Gd5vsGto{6P z{!E5BVzWh?nOm9j_Buh#up1c>&D0PRwZjIxnqle|4W+GQNUgD~H3oAdTQLAe$+@iRdM5bYlTv@12D_w7xH zeeV5Peya$ z7iuK2k(G$GcaQdGQp6Elqs^Sn`RAT5DAuATJ}M{yqWwXDcBzlp`}SnS+RLL|#t~eg z&AiQd`_&?Q1m(VH6Mq#{0MY&(K)cdM?0tI|V(m%MuHp#(wl=G6&fAX(YJAZqz96Ut zqWu|wcAbyt-+w}^y=#;|lX{NWtkh;=bKagMXz)dw_<*1hi1xbx+D$%U@7v=LYcGv< zGe_`0ChGfbbKZVl(CUje@xOvLAlm-}&~En;{rgXdwI@WogCjPZwb^NN-d-c<@dzd4*ALjS&x{>xCLDqnaiT@T90MY(0z}~kJjyG~=pLu+Ai<}#^m9vb0<`sfs z11=VC5R?GXy#lcJZ4b=hPS4_4M$e+mxl~(u%XoLPpxl6q?g@elAiA%g?C;wiILw`O zmqfS9xew+yK+AZyTTo-bMfWK|EfC!&0DIr|zy|KDJ0ZID&WW}X%dkdfnVQ z`x3#RO>nVt9$?>RB=0g6vG%st{h17N#9*N|Gq*A1?PC+{56J@2epygp6I`r32hc7= z5|`bGwHHRah$9BSug#oodV7M@6f)bnHV&y)7cBu(7$TGy*W1?Ng5nQFsyv=!g zj-VWf_PK%zo8V&Qbbxkcx-$W>_LkTDnN)Ga;A^Y4M4Q-6*Sre7b~{_w3|%)eS0Bd?W0HI`^^!9<=Skq zId4xFv;xsSP0(f&T&$c3&~7&=lpH(J+nb`@!4ZRlFyC*R^Y%VL7ZB~A33_aTiac2nuY1i`dKNjsGju`wc+@0iX&fB{L#Xz*bCn&KA zE>^w{@VQGRqfFKz)}9^hGLGPSZRTyx+e-xHK(wzERH&qt3jx}d61{IvN34D5-|_wC zi1s_RS#5LP9w(>)qWz!o_LtSFq?Nw`wCf~#-`dq|SE$P&cb)1y7i5nQRw%<_@;EI}5C_6Gz7Ho?Wpy8zmSlEh>jV(tB} z`ZFow2>!=g_5HRv|J=_Dih*eVub@OFt^5x_yVS+rx3?nJULEZ+j^Ji(=55Z~YXs#$ zv_CDV@RF^33ZPx-V(;7Y5o@0r?JADo4{Nj9=DdBTpazKcI|Q{}vXwUjwCh~#ef!np zy}kEe{!HpQg8N~9541UN?-4Wr(f)5iqnB*uzW~}zF803t7-FA$Wwe_)f*Z8iVsqYJ zE@%a!{YgQamu%$|0PS`cd*7agSbIveJ2-+%wApEM-kvDv0;2sUK~D<*9H(|K67N;d zBi7#iPk$zT9MS$I%j;I8FWiE%uj{r0^$zb}5qg z_8!FAyZ+(Nq>Lll9oo!u6Yb4{av(nUqk@VQ?gwaBB8j(`Bi3H@4{uj-MEe?TR&x{W zxq=!X+8+|srtltscAf1oLu4Xi?eYJJ?>9$qMw^Mv`S;}To9z)a0MY)VpfQDC0koUa zogIj^w;%C#Ge@*rwb_!+trxVWsm}`9Qn(GE-EKSnUbP6(Gl_NwN3<)n*=ckBxn~Hv z($pD(o)k_7X!qKVzgLYxtUWf`eH_t#u|VH%oAdU5!9be&gS{r43Ks*k>uiSyM`Q+K?Rn9z=LpVeGqE{uj~6tgsW;wa ze_3M+F9EchY=?6(C)PzzTiZrAl9A}?LLm+5fxLuo>Pmi=W3bH_azaJ75q;L;FyU=#{f)iPaSbIaX zi#UQW*JjS<{BzG16sM`P1SKh)3eYaKolM>$;}L7migp=C@Nib&Z=3V>K|y(%dPY!@ z!qWilO55QJPGlQm?Sn7-GpXVT{;D>sZO+?k1vP2vE4=$#&Qxa&(NhXGFW1BX|Jj_imf>_Fh42n)<1rErss_ zwA*cmFF28nh<)w@FZna+;0SKiW~a@0dxfAYP2C{qN#P2BcCYR51t&5avG$s1_i+Tj zPn-QV=j};?fi(3N!C(si9Dnm7YG{C%teC66wU$o-y3N1z*O$6yE?kf&V|});U>Dr zGWKV;rkR%oZ7Dnl@H=L^2ljAh-D%P7aPH7nCpXdEEa*xzj|zGK{sGuOlKTPsJ|pqI zy&TcAc)_1VA4d#cqs@M9qCHnI0K|9hLxMpd+V25q43ZUI?JN~|X_;qhDi*^S`wEJLw541V| z+}(mMAlgp}dVpv@0nqNX9e>|mkJ#s)80|ieXy2{Pew*|5Qo#Ta?W+ZYK(sFgXb;&A zUvMHb5Nq!|=+9)BBRHqc%x6a0;{{nD+HbsOe^~(#?Uw-Bg|?GZWItl4Z+Y1nD9~q3vY&Kl1+fZ*Omob`eML0NkDAY|cOTUO_Ps?Vk!tfM|agpj~P^nY=|dBKEoG zMZ1h6xKW#VoAdSxK{*iZ8w3?Vw66eYSK1C=a3Zr2Yrp!uKa(ns;P+{>+UC4HNl*hs z`z?Z6Alm;PR=du2m?ClzvG&Gj*K-8-!u%d+bKc%5XaJ)99YG@y?XLlR?k3v-iL6De zJtx}D9Km;Jv&H7Ty;#r+MEf#98xZXa0NU-gv!XrB5$$SiW>${0=Lxbvw9gk50MULwK)cX(GMOBaNr<%%{>h(75l3)Vn>m~F z&wb=y_6UlBX#ZJI0z~^a0PRxS$>@E1Ct~e&(JteNcAGZyHs|dPf^s0*pA%F7(Y_O~ z_iYa>;m*1&}X9 zhjWFtI=PAN3_+Ix7yks#5cH&QGGO0lgx3)p8H4Cq9Pnq+#}WMEKkQlb+a@0g*e@7J zQ@;=lrtm|+zRyU!Z*NAdy)N3r9Fc0$X6EjZ_9{WvfQyM+1qCTw3)uG=$@$1!#M(2W zUBnTaXKOQOoBp|{2#V9xNrI9TMgjJHM)KZ1e8k%a{^-x7j3e58Fu(uVrnkEVC48+>gqFv7s zoYQ7vbKV{=Xh>6U{N4Vt#uQ!xXg8%h`w?p|+wae$nIqa=+H6VZwh3C()HeieDSQ#2 z-ENXqWEEoVmCt#*gCp9t+U&GB|J(}%U1{nfK~D?&FB|@!IUS zId30*#U8;xntDMnn8NP?+CwIM!HIMu*53UG|J=hI!R^}2+%wYND99RcG4YU~AccDX z+Jz=LjVwj1y*%1Q9I<)1Hgh)TpL@2TI8B`;C`sW|fOe@#UXk&LwI@Zpj3e5^L;8N( zoVO1O%G1;{f{GNL2548BB#LZ9)ZXLIq>3ZjU)5%{&3SvRpe9Y-C8$l|Mu2vmMDN=R z5Nj`q?_fPgwDa0bY|h)$1Py8G-Gasx-U`relIVT==wWY<|NrkdN3;iEe*d#MZ|@bf zrm3F_+EVx~K)YR{_w9{{eeUhg`ZMX^h<2kkJ8jO}D+FC>>IOki3ReKMdnNjPdp2V2 zMbYl#i1z!m*>7{+o+KDZQ*RLrrtt5V)gF@Q_w9p-wZ}$#m?PS~Fu(uZJJQ}M$Qp1l z@f|@y3SR^G+=UYUkG%gO*53MiefW*6_lr`f4yXXSw#we0cclB5{v9btbMG{KX(;J@Q<}wZFAntgTQixF#YjCLPK@W-^-Z*$(BDHupo?-dND@OFUqkc<7kJr=R{oM;bo1P>k3 z_j}by`+y*8z{SL`1qDF7!2JZEU5Lbc)fU9shoA9hQp6FPU(#mIQvSJD3yKZ6n7Ca~ z0z~_IfOaWT=p*wGYj22l8AoiEX)|vrZ%-AJ8*njkil73B_Hh91N+jnaM_%yutY}wp z#O5Dheh;*iw|5I_47iy1k)Rfc_BR3Abx7VvHX!!75B|=dNj*nwCfZCayTZ+`Z|}YQ)<6f9s#Sk0Um3)@FY?w@@%( z;O$EUgFv*;185J~h`(1&MXbF#+QS^tF4U$ToPX|PgZ77H6BfCccv(=8jI^HvXcr>! zUbP#s_J*hZa~E;M=J&OkvpH{X5)>P7G4ZgVBpGSn2hc7>;=O7aV(mS9y$Y;F&Sy!0?=+k?4J`O3lVE~|F1uj zX6JHkw%DAvrwdvQxL7<*(3Xs}PXuVUBW8>o`>VH?MZ1F|co63MZFAnfXG6bu+}F>#Y%Fd1oI1^gdh?;oFKS@&^Y z;EKzrSZOh{;wo1xF_}@hMB@@>%>2QW(v=odhRigWqB5iMN@h&WSXr^ebmo%zE0$I) zF`2PqWyKPUl@&8quI$d07Aq^B_viCH+WC4tuh;V&f8@^h_w#vkUI%B};d$S?Z`|XM z^d56DMD86wi#?fOM6=q>+^2|zdzL|&0E@)S4f22~0sixQyUQJk-+zM0y)wBg7}0zJ z%=mA`se*Xy~_qL(flLkiM*SguLT)3wjG`Z-+zee@RngLV7{YL=q7DtMb?1jj^ zEV)}5f&1MoDHrao25l}nahpLqU`n`e0^sg&q$J5Yh}_eXyOR;P$;~e1!oAd>+eIf9 z8uS3Bg!?!E?%s&#Tr~?K_txF9Cw+{-c{lr&3-^Kj+Jb>7D)9${LBN!7{~UmOC?Yyn z?S#mEZgLMZ0^jH6h;ma6HXDpaQHdK2#sE{oeKi30ctrgBPY}5e{xt4;f)UMnH*?oc zx)&If39v{UYmf&d_v-+-%Oj$5)pUs5Tavqi5%~Gv`T16Es=YD@KvJI#sJs5Zb=UXy z0!f?fyko+ADBf={2AC4yR>0}k@8%usIFQ7R0I3%x^+ez+T+FSfHPyN0 z24w;)if0?-0R#k22JrV-4#@|Z3z2*F_hLsX7!j;+vmjNtrx{cVut@x4pFUU>Ah~}J zz+DX~kH{W~+#4SYcMT(&54%~ED%|%P)C#aj++t7%Nbc(axa%Q>h^&RkeSC5^FrwM$ zW}{T$USiNBz#{Q(gJwW-zZIZo8&ZtOkr27}{V49cl@ZM{H%n55`?=q03)%!&Bz|Ym z4oL2w0dRLfN)g!sk$YQzxH}oq>~XV8s&H>I=oVm+__RR}Ai1vu;O2CHb7w-860|G1(M;iet_Tk}ktGnh zk4o+eM&KGZ3(AFiwn3!;i^Qu9ssPFTA^`4cMR>r8JooExKQF0h0S}0PcE43XW`o=)Tt^cLO7uB{v(D3-@Y+CIJ?S4;wTClKTt*?iNLg zj?9P1z4$wE->r;jR=QbIF5HJ1v zR4&}x47vqaBgLXi2zXE`}!;z9C2ZzIba3I{BjKE`Ho^R#Cz1N`IMJIk>&;v;BZvk-kMnq@t zJ0ZI7Ey>--2>cm0`;`m#I)i~ID)AA6K|peU0DyZaB0767hRD4nxrZ5ntKA$?F5I&W zMx&_2%MHc=$^D4UEA3ZZ;|x?yUw*QB>kKgJwW- z-vq$j5|JX2br88XC3h<$aFd%Q<-)zxpe>3@EHr2bB=>Ou9lS$u6j$;tO5V=EFLkR+ zn()5xbA9}70T$jr8T0^>_m=>jx;=ZNO1x z&2vv^AIbnz!uvaeyb=L^2GH{iN$2ey5bs5Q>_r75?x@Gjf|}vpWKao6?oS(3DZ!$0 zB|y(JB%QaHL*(AnAMP4P1W$LfsAjn58`J`l`)GqYC0JCB0O)y!6oO2H$i1LH+zpHf zj_=VHG^!cyeFjZ{Bavl1*S-v#J-h7^O`3z7Tj9pP?eMDPo4medUQdV@AVa$j!H zt^|w91pwR~kW!E(5V>6l-jLiAj0je`nQNyO?!yeq0LeYoAg=_A%3ptGcezO3WFJKC zxyfC@h~OZYcY(@ z(d03R+_RIrl@Y;jx>-^#+;hbx@p=0^h}^rj$DT|u!d>fT z?)pjhJcBYoa=+0auLO(AD*(95O?=)y_|tH&PVNdu1joQU-^#^(?=`3dB=-*ts%&!Q zTL9eECO&W93DJGeOzs*+xIg1&QMquhGpGe5_eTusY;xrT0NnK^e98F_BKMAmVow?v zfveqYR4&}J44MGR{c?k5n_T(NPwZ|nDN6DTMDCTz-O3331ekY$%7uHUK^q{sA24XQ z$(1_*+;@jbNs^5axo0GICnIpHn_bF6gcklg?Ju|C;gm{j>A0QXQ3owxTw`4V9aNf;=a&g}W9@7?70+Ra=231P1sQes&yE=%@+dCn0pPSq@jKKGSd0(PjxHlWr z0+Ra%gSre{4ZvLwQ3%OOh};La#eFw00@s5%pC}jZ1qMxk)|IvjDiu)!~ORk_8aCpZ!+sNd+Ttm74|S;=X4ZRA#Cd8&qZB?}K(%tCK59 zvL7P%rsS?+1pYCY=UcgOZ#SsTRQDLvW#G#I?z>(cJ{^(`5V;p5cLO7Ei<^zgg?pJn zQ>Hr8pg9940dTjd!vl_F4n*#!zZrYd$_RY8n{l+_rx^@n zsuK(bGw>Dw?jdz}z>&;^$i45u*pp#K;GCNy%7y#cA7~3kGu3Ym#xn2|0Pb;hc)*ct zhseDlxhEKbyWPxvX3~ALK^Y)D-`5)CGjJIIcey$|;7FE1F zGE=?Tpeh5e0`P92It0_WlK066Vn1pEkAL6KHW$ge&!9Hb{M4W>1K$PkZlFE{TetHed}&NXPuG_N;k&%nz7 zyc_5U!NGy>?)ygULucSIFz^4kNZ!2$-I?YG20Z}&1y6q^-va1)R)@b}$ej@HMRNBs z0)NKMe&xyx))@={(sS_mYKkP3~4k;3hXq%7uHWK^q{s7aFt!lKVIS?hbWwB}ryMos7VFH@lPz z_kl;X1>Jz;{)0geAh~}Iz}>43DI_}~a_{`5OZ@O@z3mnawR%?1O2nl@abXH%rQedyPRG zAh|CzXa^+sSpdD?4#6U>;WtBZ@|J<^~Xki0K3=m8|}KOd2|w>JdOa3$}I z&FEJPeB<}|e z#sJBCDnReILolB!c~>UyMBqxdavLYThZ&R!u=pF8YLL&sU%!i&=NXcZ$UcboBDpIV z(HsQxenidqyW3_^nW^qFsLH?>0eYSx<>6irk$c-cu^%;za9`?XQO$6lXHc7|-fK{o zfp-D)JVOfMo(++ES#mcp!d>oWqnhD9_^>`mQ>OZhL30Lv1JLsfDTezoh}_eXyOj~{ zZ@O7hGu(F?v}LN#8?YWBX8F&)_ zcdrP~5t73oa-W;reT=~W-tOmGxo|&YFp#N!WiXh59|CX>iSS4w*#?pOU{BolFe7l6 znjyXHX`J2g8dPSg9~e|+;9CIP)gtAV+zHWrFS|4Lq=pgh&$wAs zF5K%3Y6Vy%K4MUpfe!$1*NYS!SqzbT^OkToFrr!QW}|Z9o@LOKsa|f-oPqy*$LmYLP|61(H1S8x{ZstBW>0WA3 zCcq-G&>)|I;{dqJP5jPi7DVnFle>Zu&Agih<>I~%Y||E0W~x6JRAu1j0Nm9kerL23 zBKN%Hu3?1xJ~xZXg?qC>ZKk@xpe_Sf18~=y@FnLzh}=(h$DTAW0@u6Qs9d-g7&K+7 zV-1=!@HznQ7L%eR(;;%NPwrMm;OD>X=UcgO?>A`6RF509XW&r)?hccZBwHbJ&q?l1 zM&M32yOay}27~TQb(KL+237!Y_Xg2neu&&_lDmcx_{U)0|0x&l?FO}(>K=nSKsvyE8NhwlJL2zE8z6EYncNMG zaJRVGs9d<088o@*#F+-ofaE?2fV;&Jf3KPYk$dMIu_vvJa3AhwNx5(zV$kNI6MuV9 zpR65_+)n{;cR1qjRl6Z_Uy|INjBxjXd0(PjxVIQ|3$RFh$)E?2+}8nc_d4S5Rcjz} zAC}yGjA)(@=G?7ZxKA?}5MYrw!C(-O+;0Kk9&*Irt7byv-rf~^GR%l(4$SkdT)3Zo zKwwmWMdG&xV}RuT2>|!FBYesGKZxARyTU!e2;A*v?q)pUzS*EmfJNe3gFGO)F9YB% zcciGuGKk#MyTV<;2wVr|e4<>qk2k0kV3ByUK@}jmUj@Kj?MO+HLm+ZLczd{O7=i!% z4Y`ZTh5Jc^S{I%8kwG0GxxWLzT^|vBui65U`@H0CU6=zG<(z2Uz1w%C(S zM&MyE&$n{n-eu4oMJ2v%&;v;BuK{rPMnvDMZieW-mnL@~Bk;%F>{l+_mlzC0QHc*4 z3<8q-Q~>Uwi0FIO@esLR_)6UOFe7lm%@O6oJ;Pu$ic0)pLM6Q9zeOhu?FakHbSx_$S`&@&{ zC@S$DgDOCBzXPE6+aZ|EmAnUTjs2(zT<%s;n(!X{x;}ob01NM54C(;M`x}7XZ--zv zSKf={Z3x`wR--iG-D1!rz{2|_gJwYTUI)O+Zwpht&%k1U1HECz{2}( zgLXjjz7?SN+aZ|6mAo%(j(zA1oOi2Bn(!XDU;EH4z{2|ngC4-t06z!ld3MAkvUSm)PNbW-cxa%F^OU{4yhWnW>$DTAW0*`=s|EFBIcN;Xh=)@xi&4A>-AAq~X zk)kAxXsOya^YTM(B`5O7aFt!lKU(G?hZ#vk}QD8JwLfS8G){2e= zGYz_3bmGMZJ%Hr?`#pB|Mnpfu_Cw^}-x+(-#|ZpmFy|5F!oA&KAc{)dV=xFv?k@wl z@1cn3XV?aa+&3ooFe7k_nJPxu4t=ds4v&+~;ONxw!8w29;4%;!6fq zfaJaofV(;(`WdzcBKP{_u3-c|-_4?O;XcivHi}A|U{D80?zaGN*GHs4WF|!JImz9? z2%K}XQMqtG+oLULilP#~HE0GT_fG)0TOv{@E&c@14!N@0D8Y2g6Uj&FSzt? zO$L5`i=S&Ql6SwsfB*~c;|7C(-xY1x#fQ9#C z24jHay%?bP+aWlQD|y!^??m8Qw{rZ2rgwSs49WoMN8uX{@=CDygI)p9^9;!cIe1sN z=OlLpBZ6aK&ev)Z1nf1a1SIzl462l1k@yxs&oiVv$ej?~_a1(}Xg_Kg5&VpsMK!~{ z&Y%{M+#fNhQ-Ve10{}hGkV25f5V_YScLO7W)owPb8SYsIO@QQnxk0lMEGqx`n%yms zVvuJbavzo4t&9jh0p|Umn&IAQ&<05E2MpSkU{SdPzN=>t?@l;ht_V07&kCb?cK2D#4=i zM*!|25gtj%UWnXBCigHSaKD=)%7uHY!6+cPZ!;KEf<@&f0Pb;-GLv->xp(sKjoOn5 zMg*JO%<)Y;xtAK00g`*6L0$>U{QG;0C$T>(PTPA?&Znd%820eU-k2?T)6iev;mU) zaf5axSX3Sb;O-D9nQVo~Jw3TQ84>JsvrD;fZ!qWvB==PYJxZ{stN`HdHSu|SDMaoE zJ7Q1z7!f?t&3@&=J;z`Gkle2^7_`ZiLjkylOnlxxa7Vb$OYUJtxJSUeA5kvcyA4JG z$^D4Im`$$S55PTc;`8=qi0=E49g%7yzhg8@KtpI|T; zCRN@7z=k1vgxi=^GFe7lz%@O6o{p@Ypf>A(n|JGnEOsf0@fO|ZM&fD7|axY5m z2}a;xu5w=>`4V9aGje4<>J1NH>d<8 z_nQr>!lcTp0Jy7z=)8RhMDClDyM__?&tLKLtz5XDG^hn6_m2$fGVmP$?s^CnNwz@b zo}b(ejKCdkHgb{N*BCTqs#ONf8Tb$YcMF6cJCd9Rk$eA*u_vvJz;Ac6#6@x+Wzd$X zUTM&tfhhpo9gsq}pS?BQHzs!{BizGa-v4ot+`A0AGu5{ZdNS}e0PbE$G2Aypbl>xm zyN?m>kGt8=MRH$aFp#M}XfT+8QvtY#Af<3050U%H8)8p}8R0ItIl@J9&oCIxRR7(q zPd1i;{{!G2hv@Gc$&(Pd*C+P`Bk%xtS1HE}n&f`apbU_nldl@&Gw?Y8?s9deT5=6U z?m5X_!3cM=n+4_K89djZGE=?Bpeh6J0N}1xC(czzLFC?Zee6jMBit`_v#4CSU${jd zq!y6w`%eaS8TcgtcReI^c0uG`m)s4EaQA|FUy|i+H)sMR_h${7Gw=xj?iNToS6u>; z`>5n@WrX{EV9wvl#eFX_Xagko+YH(>@CE?x4oI$G$qb0xkG031bTYy{@nyNYlneLM z2Hk+<{)ItL2EGr#-K$Q~k_RDjuSxDcM!0WvvtPMz-)Jz9sXk^fn1PD{xQEm!S#mB! z?jw_Xm=W%xnSO#7Oz&)-G9}dY2o#EcOA@*c~5qK2L^L^{2dyhdG zAnnQb4DuQH1^{=tIy~S=Zinc;FG=nSM&Ncg3(CcPuQjO5R2LakW#Ih)+|}yvfFoH1 zk^8XZu3-c|#?7K~;Xcx!HdDRCpe_Ud++=sXI+Tz+4Uv2Mr(;hV7=eetJm1QNdxt?& zruw=;a|XTw;J#bb;Q>c-BSh}y$=%8be1)4O<-)z(pe<9KZP1>9lL5Fp)Zqa~G8ZEE z^yKbj1g>ziOSy1QGw9A#|M-$VSx*Lj55V244i7kzJrKDcye{^nj}iD`H~W*#w5V_Aw?qNpYMmI;43-=O((M;?ePz=9+!wV4Wq|a2|IQ$vfu8|zm#f1Aj${W!?t9x}PbwIJd)zE27x%r% zpfXc^+Mp@}R|0TXtHT41WI06crO92x2zA9c@sTfg=F8>(!x!WEw>7 z7uLspH!uQ^f5FeUa^c=*(3GiuYS5g4?*eeQsKWz}T-kj3|s)f-JuQ-IFcn0xlc>(PDbDwH@lPz_iTgiO!aDmo(#MQfV)>69&jYj-5l=c zu8lqEV+8&+nD^bvh5IpsflT#~!C(gN2JjwpCW||8P z#xiggfcKc=Ay~wf_ab>G0w3d6?zTzqkp^Xe^jyEhAfJJMeqP@8atI&z8Ls3#@Tu5` zioj2R`FBb#(*5ow-A>NDRZeRp1xY@`>a?dbm0;Ik8@8|TXngPlEe*ittkaXUD5+e6= zrPz;FMz{yuEOC+C4;r)qlKZO$?SSO|96--Aq(BYHH4wQsCwC_!aI>3TTqO6o2Hk+< zevd&9Ai3WG(DMu_hWjXp+>4UCj}h*dy4lY~a=*}_Ef@eK_n!;~0m=PK0PZ0OE=hJl z{PHWCld;`PamKS1N+ZX@g2Ya{t1h3Xt622jH$& zr_7QEA#(5kWVmY>;l9<)qH^KB(V!NP+#fTj10?sw0NnLi=Uj-~Hzs!jBiuzd8?)S8 zgC;<7zuur3klZf=;BHZ;+>#eQ8}50@-O32}D46rMa&g~#4B7z6{XK(rKyrTrfV)GT zf+e>@bl*>YBKD+{5$<+3yOay}T7zyta$jW714!=o1911MQ?z6eMDF#;-Ny*`F>dxN z7w#hs1^~(Z5`#fNa{u!tyNA>%S@JYQ?m5Xl%n0`onD>Fog?op=C?L7NZZHN&?yms2 z?{ReqA-NGE_nwc(o=h+TU*Tr1Ytp^kpbU`QXB*@J$$c^acey$+By%BhuS@O(!xzWGzJQ$JWK3G%x};y4k2)xR)3-0h0UO2F-xvek%ZXi#l=MJ`y7Ln&fU} zguBeml5*jG?nZ4v8z8xVXV4Bv?w10+Rcu24jHa{w@IbxH_be+zXNWu;iX#1pb1XxjRUOd%Zy! zAh|C$$ODr50s!uEb%02gK;+(jb?iw6BXEtI1?9p$+n^FK1xDi4233IMeh~n7wK{R$ ze(w5kFHi0oMl^p7=6$zv;eO1Z7BB@y;vs`NKyu#=z+JBnEh3vBy6@@9-M|Q3au_vvJXjZyeQZ8?Bm_Zw03XH^5gLXi2 z|Fzxj4s~)qZ|{T1eO_{RGNL&M=6#8B;ofG@4VVHWahE|4Ai2K?;J$m+$@#p!9wPT4 z$=%0@=A~}-D;Mtb3d9MfPybZy-jw^Ym zC2vLGCbtS)r2AcJPzgxhg$7lCsqq1h1L(XB!Fv=}@;-P~>_bi9m%3HtB6(l$?uqOCJgXV*rwzI@ z)h`TsGVpzXo@Yog+z&$JUUFr)`xxQA)y;l2!+oQ{fB=ic#|#EDa4`V)5Tq25b0Kmc zxFX!cjA$0!98ojea}7o_)$0w$GVn40?r{;GBP1`hh5Pp8o?rwX1@r#z&Pn$kgE9dY ziSHTYGw=-n?sAc-j@%B>eJ@V#3Pv>B-7F{<_r2DjGE-e-P?drA18`T1lv%O}BKNav zVoz!q;XcOAqH^Ir(x5g|y~Lm{1OHracYW4*8Y1_m9NOUlK4FE?n*RA(EsXW(Q2?hcWHC37KiKYe-ZNhc%R z6>fGZ7w%~W-I?ki*Xon?WZ?Gz+`S@2OZGtI-k98djBq~;=KY9r;l9^kK!8Q!7K6bI zTo1rKBvNu@Eky3)lY5vE%|Di!ad)hHd7sKP?v!t0J!T-eBPc0k$Z1T>`4P7 z+~XyE;zs4dz0aU2Q~lJSIRoDX;BGPT`|W!na$l3&t&DJg!OfC#;a+dhmZ>f`XwSd} z0NfoWe98F_BKPd%?qme6akERgaL+dA&Qz~9=*hr~0JwWiijq8cO}KZjjy>sP1pYOc z=UcgOKV~qHsU9*I%)s3M+(RZMNj5=r-)ob5m=U<-=7@6PUTrX%sXlBlmVq+>xW|L& zygeTx_pIceU<9soGxxPg_hANQ0xS|!4e}ZI>nH6l52Ew-K8W1A($C8ZM&Lm(?*f&J z``%_ynW^qFsLH?>0o-?W5WU}C50QIya@Q~dU+QL2xp1FnP@AdVYfzVgcL8wM2hn+Z zHbm~3$=$#RT<&J0a^XJs34M^JO!XIo<_!D>fV(A#-fuq!k$cBSbNrM4eOE@{Z@O7h zF5Gt-v}LN#8?*za5Tf!a0PYS56_M2txmPB4CnIo!n_W_cd$B>c0E@&s4SE2{{U!kJ zUI;&S6gdnc_l)H3V+8*9$NhXu74ByY1_W3neq}HSNbVm3a1TKW5!nWj`@u_NPlg%M z>~eENs&H>K7!_cV_@u!YAh|yZz&#ErM&vw*+~+0t1S6WYZszVH6z+KjWdbY`Z#2jQ zlKT|^+~tr`L=LVC_u7?l-xZ8#j)8f;r3&|6gGvDwi60nL0h0S$0NmAzfQZ})(S2{Z zB-}NOz@Krms9d<$8Pp1}NPNVg4v^d*0N}1yWU3>JA#%@c2zLV`n$>PLDi`iq22BDi z5-&Gs1|;`?K4y1|B4v&|1CjgbkA%CG5zQySybn|^+&c~01Xv^QRn2BQKj61N$Q0h0SB0Pb-` zijJ&<$i4A{;htbbv&qfe7INWUYEUM?BC*gQ4@mCg0JzH)DLFC=BKN#>-xZ8#=G`nP z7w!YC+JZ^}7KuL?Q~{Fv=K$Q*j`&=)6C(GM&2ir~jA-8HW>L9tZ#JlP(TN)j>Hx`o zH2`Yau6MIhxo|HqXmZhsV-1=C$^AM2?iNRUu9^;!droq1LO5;oe}-?V=M`8T0^> zdj$Y@uOspMPY}8P_r0o*5$+S+>{l+_a|{MtbmBDzgMj2d6o7lk5x(U2pRNk`td()! z!;HWqV9wvlg?qQbsEbZKVlW0s?)w3_#~mq3vKgZL-gQy9Cm4a-+|2b%y4M(#39v|9 zXpjda_gMhk<&KmTSpboHb#hlQ0#~_NP%dvU)1cBtgBKfA0h0UgR~F>1j)=}x`yq1A zOzs*+;2(o|AE;d3V7ozW6qUHgpbn7SUk2c=kBH7y8z6G;SP^^DzzE#pW}|X>gJlLy zQB>kggJwW-p9H|&5)uFY6GZNn$=%9`=HYIZl*=0&V$c>vCH{7WK3O{;xt{{y?udxa zRl6Z_&q(f0M&Ld-yOhftY%%DLq7q*+=m8}6bpYJG5%KRoLFC@n6noOgi01ij_A8e+ zIL%-nib|YdFbGKQw*YVtMWjGvCPeOK$vw;noO5$TxxB%%YqSNUQB>l$24jHa{s{p0 zctnaswnOBemfRDJz};@DbybXbGb*oXD@ZM<9B*4P^F@t76@?H$!J!VS?&f`knZ5PHqv<9wq zt0YZ$=NYsKu<*XopdG-WN#GR#JuEz0P1jfJNdX27`d){s2JFGo%oa#Spo-UJ!dS z%!p>SntR_v(?RlRN-D}P$|G7agIS1Ah}Ng;I3AL$A-u} zh}>Hm!(GD&{8~4Q%7uHnL9GCb#J^VSlhpx|`;P$J^@@Oq?1jjE;QirlU-;z+#3uk1z04mGN=M1_X+^+YDawDUJ8+W--lvPY8cTx(aoZA;htkq>!K5{F{lG1 z_n`pX^^W+wec;k?Z%FP2Mz}}7ybn|^+`A2$Ty)|QgJwW--w(ju;)u`Nn<2XIxyjwi z2zQ&CCFR1s#-PnbCoVK-2PF4d0Nfpp_`JOUBKO|&V^2C6;jVJCOSy2*H0XBGi5DC6 z0FwLfm)PCwNc{d2MDA;nyN?m>AA@-xs9dxlaP%9(SZD$sCB>yU&X~nP3Dy+|As5lkP(d$^=*> z{`L`lvOFNUp90`6cci4qZiw7#le>ZuxX;aka&g~V3@Tl8;!6fqfaJaofV(;(I&ZIm z$UQ5$YZ!sgceAKmxKA^vjiM4K7}Noh`z-+6^%2o|dnQEgUFqMk8yJCeZZ;|x?q@I7 z7BodsiQgJD1CskE0NgDR@$WxDV} z=UchF!IK69QB>kb27`d){tf{5P(%tuwm{@wncTySz#VRmC>QQ)3`V1<#43X^KyrTw zfO|Y5MIxs`b1TEUy&?9aBJi)l zybF{j?)fo;N&yzdhYYF!$$K|I=j{+|=E{4KyfuN_+$u^F-Zcib0xY~28q@)j_bh9u zq@Vu=Z2-<+@;+eDu0()40D7Jw+=?O_A>NDR?qmdRb+b#&aIZAz1|;`620co!sGI`O z^9-RPG7lp6jO6ZP1b(fX{c47Ly1@V-x&O67pKMSG7L`8&^gKffLH0uA-j>d2!;A>_ zyE&p}xVIXN0+RbSgE1voRBi(3d4?2&tb@qCEV(Bb5o~fZ_jN+yUTRPVNbZFOc_mm> zjsxH>hm?ZMg2+8BxhohE%)41oGu#K7v;~!bo)wiT|!` zg~+`nxjPvV>~ynBxo~eV=msSBRR%puu&As6;O@;jOCfSEN$x&I1W$CcKg-QA7yu;q zYYYaJ2=}1?+(RM-OAcHZ?gL9>Plg%c9s%?IPr117-3Ft8@!8SK@TgiocjX@b8xi2)xE5V|2765m-NXcXYMDE4OUBQT8m74|S!adWV z5|G?4HmFj9Mdj}o*j;Vn^Y(s-+|QmJds4%Q;E%z)|5Gm9+YM>~$$gJOof0f6U%tTZ zdJ~_wH$ddxl-v!B2)2MZZz~t>Wd==vGW|wl|-eS-Vm;xj5C4(ND z46XyIB|3TzFKDmb( zfpcz-C>QQ$muU+|0aL>LTZ1v1T=@wA_qa(>lI;+=_tnRqOfUj>yP4~qbl+@H21xE} z4f0B`s9XlXU2akmSq71NLw&d_7=i2DEGSoQaJ)e!Ai3XcP^AQm%BujltApsgeF#ME zx%J_$VFdp3hy8pjS8nj6K`kJ;e`HXn1dGad0J!Ue=)AoJBKO`U;cj3A?r^hFxpISR z44MGRy~?0j2^N(P0dThj(Ruqch}_pCcPk_C+ubZFS8i~WK^q{sUun>;1dGZP0Pc<; zI&VMwp>WSm?oLMFVKDCll`A*cWzY>s?r$6PD8ZugH3073AUbc~4AFh>J~Q^Dj}iFe zZuTozZg7dg03f+PXfT+8QvtY#An9CnJVfrb$vw;ncfrjOE|PnO!Dy!X@A>*V?2i(kkW77SgK^Y+3_g4+_8TcFkcR8dG?rR`&?>Zy)q=FId zW;Y95r29VCpfXdv$Dk?$?*QPgh7`kn6h!XT$z8(;_ehkZkG2}JH4r^lYOG6KKP z&60Bb#Q+u=v}LNd8MJ5M4FKF7>P&TK21M?a$=%5a)x-z=d@C33rwzI@)h`TsGVpx> z?p}4`T=gJC?itD5#|ZbWZuTn|?i&pTGS$Zn1~YIm0QZnOajrTSBKNk_Vo!z{;V!y4 zqFlJ=8jNPD*Bgvw;AH^Z<5}m04}^PJa!)YAJqq4c$~`dY-eXV(NYD584DuQH1^{=t zIt5E^hv>eiC3giQ-0f}_l#BabYfzb~E;6Xf!21EXtJNu5vIrvg*7wGq)G)$*jGIN} z!hNJcZKisOL0ty^*9lL5Fp)Zqa~G8ZEEmgMea1g>ziOSy1Q zGw9A#|2S8ltS1A%2jK2ihX)+V9*EpalDm%)_+dBul?(U11_PPu7K6bITo1rKqz(@_ zlC==I57fn;3^M{Zx;dg;xR)4=W~z4^jAh`h0Nms1P(pGfMDE*@dx8l2Ebje4i7kz9T2$}CwB!SaF3e><>J0K8B}JfPa9Na;7S1QYIS(P zkt~PE{p{k{lNv_g)7>m87w-86wVCQ@gSreH0l;0a4i7kzX%M+LC3gcO@c8@vd@C33 zeFjaL>ZbaxX~kRz~12xLHyz-0Kb6GS%e<{yH_0^a3s&YFWeiGyN?n0*I?dvD;Ms^ z3xX0C@g=9WN z?tQ1ko=h+TSGt+|=A`>DgEByRzNZ@GGw|1?c9*Ne1CC@LMD7jAUBL)E2OXb+4@w?8F&i7d(4&)?BU9Lk-V*eA9kz6Me^Qj(3WX# zF=)@g^#I;uc7$LZSMttF-p;^HZgp{yyh{zbGtEMSo(voZ;5}w<2#(@P-hGQ=ANm5n z)UAFllJ|wP^sxsr&7TYg0UVn2SMp1Mo@WTh5s_UG??rMCGXnRzIl@J9-)=ApNYC77 z4aNY;{Rx1cXGnRtFM-HCH@PPm;eMZ+xo=In7a5cRlKX82c|dZ%0f4(4QV919h}?S@ z#-3C#!aY&%=b4Li-%lG<0+Ra|233IM{ysp@Go%>q2O)A_liW3oaNp`?k&EQM(V!NP z+#fTj10?sw0NnMEQn=5B$UQr`8yMj(y4lD@a?dqr0wnkA4VnST{W1XV7InxVd0|Po zcb^=4(#i-t3g&&ca^(hl4B7z6{XK(rKyrTrfV)E-5R%&=y6?5g-N^{t?q-*A;a+Rd z4M^^b40-^`{eA%MUUkYWSp<=LR&w_-!hMXJ{mO;=NP_`Da=*l25RlyeJk#zWb$Gy$ zJPnb1S26Zvm=Smg%=*q1`0WI06cWy#&g2zxsNs&1SIzn0Ng|B#CdxfMDA(HJ0|^R1oJ*nxo~eY=msSB zT?RdXSn)k;Xcn`0Fd18H5ddW_qzbNht%N#M=~2C z_u}LpW&|#Gb40mtAFR^{83iQwUkt_o$^9Du?s0W!A$bfU_p>L&o=h+Tf78ue-=zCa zgEByJf8HPuNbXMoaF?r-^LcwUMD9(=UBL) zpSKT#$h{!BYZ&4F_hLWa%7yzGgIYjx|H_~akla56=)4_*?Oe(G%)4Sg8UlB_)yPHi z-fYkWNZxA=ngPjs89?Xl5G?1)dy%}Yflqg<#6|MXH)sPS@6iVBfaE;_p!0SJrgJ6l zqU7xi{QP_TOmmUE`whAQ$@{oL4jMNPPe8PP1dIihC#-OV)^6=0Egy}?)pUIx(f456aP3#Wv8 zK~1XdpC=+0j_?|&N1K$AXd4?1sayvx#{q*tSu3$v7-OYlU;a+P{ znW-)^sLH_m0eYSx#c(fz$h|SSYZ&1^#?7Ld;Xcx!HdDRCpe_UdthKuyQVRFe5V?;} z?gmD9lL5FpM0g~T%!SCkA-Ovlfh*kXQZC%n47xMbKNjhe^PG zGVoRa?(wX1Bt-6Ol6!&??lL!X4^6tCTc|B46JU|}ok2bWKLg+{7b!Th10whAu zv&YSXa&h0A3@S6#rwytya3uhDwMfyD?)e6_nd)eRx(plv zz+Ep=vSb=W?zPF?zzFyF$$q|-3->;QrcCuygXRo;7l6CP#OLjMA#%@3?p8*)zu;y` zxp1#HXv%5+=MSV z|3TzlncNkOz=L4k|0x&uz0II9Q{81ym4Pn;xbJF{q9p4fa?eQa8b;tt-7G2>?(+<4 zGu3+y>N4;y0PcE|k|eVsa&J2>_N0LkxZKS~<-&dN-TEL+nd&bF%^CO&0C!6eowpx@ z$h|DNTN#1B>1Ii}aNlXrmZ?5((4K)$0dRK&(Rq6{MDA(H-N^{t;AWR{;a+UeovGew z(362T0dV&Q(fjSgAaZZ5jy>sP1pfC)e!i6p_cI0qnd(;tgBkcC0QXQ3owv6^Gu0;z#xn3x0PgW1I&Yr`k^A7>xbF!@;956x4^O)18I%dINW9S? z5110*6#(4jkaVs(cw)G>BzFZPnqy#|Z!Xe(?=`3tV3GKNK@}jmzXiZu4dJ6IawkOh zy(GD77=b_IW>KoxlXV8Q0xS|AF{lG1_XhyD>mh}REQZK^;Mmxc21Yci-E5R9+_Mat z1Xv_qZqN)!?*GiUy9H8=$TJYRZ%^)4Ml_!Q^FC0jaPKr|6JU{ez@QzF+;;%D?+!>Q zA{!xcFHY`GMl@U9?2;dt-9fFrs;%o5d`**`QV++&3810h0S_ z0PcE43YM&d$bEcrH!#9o?`ETNao-CJngm!Rjx}foB=_q8xLXt{Ix-z1_r5u?C#{TV zK40VKTe)!WH)s=Jk$Bvo9gy6Q0&sUIQgUP~MD7jA-N}e%r<+~Mg?ocRw*ZU8RR%qP z5-Rp?YRZAgq&rR+=Ml?@!vtPMz&oLNq(TUd>3<8q-Pyp^BM|`e2aD2G;9vyo! z%n0`gnD^bvg?qQbsEbZKVlW0s?)w3_#~tyxYBNOleNA#tFv8vDX6}(m_ZovT0Tzi1 z4f25GJ_~@m+>!YGCy3m$le>Zu%_=tw%Ef)pG^ljZi5DAG0h0Ug@3gzx5udB}L*(B5 zme`XTM!0_r=6#@Y;ofdg>!K6)7}Noh`^y0CyWSDL<-&c4L7R(C{Ouk3WbJ_DehPrQ!;z9C zyCHJ#s){}7WCZSWvrD;fZ!ze0(TOh^^Z=6kIsoq8i0E9k1|s+BNdqHrhntPch5H(VrYI`0%AgsL+#dqqZiz^d$Y~I{mnC;ABk|?Uc4#xqAzfpTm906ca6b-01NMh27`d)Jqy5l%%Ko0;!57-$vYhQ7`H~G z3Ga~xqXI0vFEJPcB=0|ulecd?1kZ3K?~LS~2>b+iS1H$zBfL8e$^=+=A27)GPre_$ z1EA*_!f`~AjS%leW$Z--BXFym1ul|%r9q_ti^MqwRsEChQviCNA>|R72a$VOa@R1T z`C2!NTqO5&gIWO=iGNk=lhyT4y8j5!^9(6OWG_VSY02Hdh-SZ=ja(%6R)Z!17Kz&o zn)@f+Hv#lKLy8eu2a$X0QL!hjjA%BwS>htOmm0JQut+R4Xz!nN9|yqQ0Vzdf7DVoI zle?1<&AgjkTqO5_x!Qtm0Tzir81(c{x_=J9-3!rULu4mJ?t^cP`|e`|zR%5m<@k#M zY&IAWU;%D080?>PUk$)L1WD)Zl@PhNB=;~Qn)PmuC>QPp2BQKj62}^h^-sEA2f#fJ zN$2h95V@Bm_XH!F&mZgO`%!Y?-fvJQz#{RuK^`zQz@q@%<%;lt6WI!p`@kDwPbwIJ zJKZcO7w!!Pl>#ghR~b|Rl6wUJcXifT3X%KvH-x)}5zQ0bEM~bm2DJj=evLsLAh{0( z;I3DsV99~EhI{cF!rj0K_XwEx-O9y%?>1->V3Bylpc#5seXjZw|rChjY8gvV=NW9pf z2aw!4h8X#N%J`v&Hezq#y zYm>Wz5qKEP`#|O5zIPc^Mp21x8&m<3`)dH))e+Ho`(}vldscGSFam$v&7yMQzQmw5 zib{OYpbn7Srvh--N2EaHc!=D)j*LBNU<59>*{EE&XBad^QHlS~)+cKQB=`RTaJNLH zNaRU~+^dtjl@WNr&60BAe$b#Tib{OdpdFChp9AQ;9fI{-$$Mn-b_TxGtuAT8d!9kJ z01NMX4SE2{`!0aa+aZ|4mG|N`u@`-T4|l6yn(!WCFd)Ff`?oi1F9re0`xHRu?GWtY zO5QceI~@37w??E1@4W`20xZ0@7>ogu_j-WN+aXxTmAtc(cLLn;p`Fjoc>TZX*8e$Q zw|*cm*O`kJoxbdhCAFuVzHIUPPpv)q%tfaxd(Ww-pW;8||7TuUv*L=vg;y6=th(yr z%U7@Z=!#WW6s}lZc+;9xeE*t@SFJ3p_~?ZznwnNL6)s${YI*ZVFSz`YHH8b-6fR$J z(Td916~J0^Gbsb?a&_U-i&w4C{pfzxI{ozH z-d4kZ=9Zp%Mt$14|5va6^409l|GUWNslW8fl^3r%b=AdJ6jq&m!KzEvoKSdEQ(@Ja z+_}HhXVzM^sQH3bt5#gfMlg0#U+FFuUVX)iHHEjnJ8llx;y>(7O}Qn@FMi*Zt5#fI zxazoLtB*a-p5@Cg^|t(PS7K9gEtB`*KW_3rf-hci#iG@hUV7@Pi&it~zWJOb8yC)9 zxbw=lEj;||a~E!0c-+E2|LnMhXV<)Q;Xiubweb9nCoMeu*!L{F?4%_NzrJhv!atw2 zYT>#stRr*%!k_&3M+OJ^L12bl3daT+JP|7hQD3DHo03bjnX& zap-$;dp`Z1|EGN`jjp2D);k0Oh5$jvAYLLvga~11kU8`2+1Y`)4f+u5#NCrrP zB%s)gf`WhyLJQm|$Rr?Mya*x?h@glqsK_L07)3!Zh$09f@9T5AFbUzVx89%k$6l+l zt9I?Zt7_L*yQ(^;>4Pf5Tr$CRbj5S7X%{EB+@~g>J-a*sZH#xF8gKIr*Mz!;Yvt%U zplegOL&tr(=wTBwTh2+EyRb4iv;(O-inG;{x6&3Z1H>61TT zR2d&q9Da|}Dn(HH&4R9UaGN2ILH;pS*So69=5DarO*KQhg4Y?gx`MBPHw?CiqdpPk z9F*sy&3vRaC~v?xzd|~pDpyad=JB6F{WX1GJr`$=?MH!bhbuCW!ROqiC?3?0LfdJm%fNh&L4KU8)ye6Y>qVINE#UtL z{b;7@VFrA3T2%%P(DcE3H45&*tV_EXeHg{5wK^tcX3ay5=KSg`{UG6tNahbmmTN1H8zU+23viUsiZe8iwvVRs?c%zU()kA5t}dfMu0GHomB zwn26$WOra)?Z&)ZNBaan2LnfKkzRn0-cgl0ff^a(G`;>MjVPU&r+#Bp^gO50*E!uB zD~$A8qTtpxDhBoHG{#@{0#hknjnH89q zO~~&D|0d?Oi>9bAX-eV+#0n2n{y4(uY&<9TkDM+f2s(0FM1LIbwt_V1AO?|&1RAZO z&njXn=y47^y)mbQ(66UKlh8&6WF{a#O*Qk-_P$S5bH5Ag^9hYE57w-@$1u)lri@v| zlV5h#O zWY+^u>S1 zd)#fs3nylcNv97a2mdmQ&DR{=6XS?t=&!bE(8V_ z0z->jRIm_x+alM-s*8b_f4YvIS%rA}9`L&zaj_7Xu|NDdiCA$8^bE=&@Q0x)^M6pS z=elaNcqU@d6-{ZTF_W!iR$K^T-+WHjg9XuU#Lhz4Ed)kCa;?0)2R1&!Ubn|JXV1rI z|8u0nsJ{ey1@tQDHPD-&w?OM)E*?SZ102l4+A@qk(h7Heb`jY^K!(Ew?N-U z+5jH#dVU+`yAW~YYtXBzwH|YR{7Fq;v|KZ{m1xSvG)4{2GisT_Y3FUkrW|1mxgaPR z*p1l@d%IQ2-wnJXe|0x73p(&4jOP>8-1a5vkHF>$lDXI~YCFoYS0noT>){w?6@wKLMvd!M05=0{a)y9_XyAXy*!e zh<8DCH8bQ9&CF{J%r4TDGf3(ptP7-7uW8n;#aR0o)A1FEdmFJPze8OgMvw1hRJRAG zV+EYzqXb3o6o!a$n_W*}Z$AOtA@xN5{Bf+S6R>+6*adIfNzGhag#2l&jUOR<1@>=Z zTtPT`HfD7Fai;H#V^+rmrbp^H>AtFZMe=x@N{ugL#~ zR0;MfBh?0W>Htd(VYdNr)(F0~6ZY(oNTj}K=XK2aNmZZTTC-Zu!ur0YS(hg>vlwR* z6X&qAb%inRMPWpii0J(BZsn?hIWsViI9KQf^ihtPg1MXnIv2c!z>6J6_h1ct3fV6p zdkAeE1w94%U%)5$xi&&gn^PRt<)U0+Z2eKtSMhE= zAPR9N5-}(e*oed)9|fF%m)IR~Ox5)1ZtR!+un+dv%vOUmtMO3HOdWxFPX5!dQKHvoA`hHp^5nvQnH zAf6eBp>HFee2;i?8Q2F#hQ~3bx6Z5uDa@LMv~(oSlOvf@9jRFgQ?4d3^GYn%;RImo z0Hc1uYtb(F7avpC-X#iZb$9E_;}~6zXEYadMjSI|f*P>>UOY381A{vgvF9Z-OG{;D zUMAWa$MkTMDX+i5%=CAeQmZ;wUJJw;ugnZ|_|TFb|cm4#zSj zyBep^C7gG_U3J2EY5RVpV^pP0A0|{3($v!NQ*F*1+b0tUGy5z)v&t>YoP$^ z250mo|3Us5#(W+0PnAObu{Ht`D-c_^g#im3IGGK)3wuj#riV>u^xG{)gI~n@Y$WLH z^TOzKNf@sry3H{KxL@qTUI^K_yMUzv&CoRUuG0lo0|_eGV`ik1oRf6L+yo?{f;;#%Vmx)F#%9+fR_InxGTy5SuoN=;^@kFn`1#Kg=6q->S-( zvjEI*HRKxtU(GS^5h@jQM4ls$J4475nA>?cXWu|d(aeW%2mG)SvtoN;9B(qS$&ZYF z!CmyJH@R`WfiOcNfX&vh-v-!h13O6623UCnoM zjhTLXCNqCVoQPeDxX?-%(O54Veip{R65PsT(Tom9F}f9n*b;@f6@|I#&XljBnN_V1 zvpyS$*n^iWCxr#3UW( zN`upm)0`$n3dLH-%xMj{ezqYu^BZ#g9O#k8Tz>|%`2*aV*@&C^1Drl@0=)f$(=41R zX0+vW@<~p=Tip28g|k$FDCigMrt6t_Zvfl-fT0Jl2IKZ|N<4_W{12GJ3!MHu%PHn3 zPE9Z2E_j6-T?l7{D#8e@BaEpn1Z^22jQidd)V8f#$qqt12m*eBFn2ZK^IDjL+St`xl6U@XGG|RpZ>|Hk{>yn&M3V5Y~TF>`5&#-|c||?2iKn0|U4>ZCS1987DR6 zpP@|ud?n&C@K$9t)?GJ256u=d<*F!}nCQ0ZmN4tsZKg~sVamU6;|_hBQMVFiPAI|N zjC@DXz9=UUSMrdbPu!e@{9r#$5BhVnMgXTHL0msvAL9+he6-?pzXxeQ?uZ|A`t=h| zYX=B3vnsci1Ao&4xc*51H{Ytltw*bHeI@+ls=}2WRk`va?mm@K&X2@CHU@XDTu${d zk3-J@PnSS10uM;1kz6Q;A)P>eFj5-kx(nt$;1qO^Ahr~9`T_41{r8K41h<>2)DpA@ z=?qdMq%V*rG!XPOX!V+cHX+TdEvN;`x!@Ngw(k!S^i*TSzUT1%!Vt!cse=CT zsxYd(in|2pPp=6he3hWR+l3MFp)juD{mSCqf+GFg(NR_1h8Br?Y8d9eIWQ20wcQf2 zAsiU%fN~V@)DQWmu`eY7V|l>FMAX~&;K{(q2UzpXaE@J!cSvUtLs~Mu(l};&$A zu=|6(Z7A*#n>79DP^N5O!YHX5r#)LZO&%fWlh=iDyNa9I;!K$R9rSR9JdYIiEz)<0 zQQyJt4UFNIYF)zKc{Ld4w>F4-?Qx#%gtL1m(9USHJE$9J5K^k9^XVAZHk`e#K&B0& z0<^pSVNM3_PXF#HjMEDQd2rW{NN``NX7DR5CiC+_)A*dn41V&1$=t}F$pfn!-0Cxl z4|xT2-y|N{Ych{qVem#T8C)MfiLZQU3O~AI65p|GG9S|XCA{avd3y01oFZ3p>h~{B zDd)J68v_jV5=QerqSp24{Nzhh_>juex!HFr-_d_MSG4I|^-SSw4^HFFFTTjFr(fbj zj?U+6_iRA=k*|&VlhcY3V*Qp8B37h{^ItRNi zzI?xX;f4n8udB9nFML1Ty)Y7KYiIY{OUJk`UVYA8Z@A%Z!rbbF1#b0H?VjqX@x9e1 zdt)%SPXJRx)JtFBeMU&GI{ZIV)W8Nfn-%4&t)9ajwa)wMoTl5=OY z;3V~^|I2FNAFsf^sTK}ehCbx0h4uca&N=<2I%e4$sNbR%F1!hBSJ#df9)%9DxBf+K zc!M{zz$V+Yx=(h+zV|KLn9&30U%cP=HCwDJS}(rN4RQ}!p9dU6KISIcy9v9<&$tL)_}UX&c|27LiE*pF!T>f(}ipzp6QF6jq)U_)`qXwYT< zDlVA|I%+fYL07&9eb9G6FN4}oT^}L8@dL;&H`woipsLkuo#x=id1yU)_T|9yV8fL;F)(jfREyh`c%7FA^ZS+G5C7njs6 zqhITeer50n!gpU*F0F4>mio<6U*+B6lFw0nr~OL%@AOYY{btm6En~mq9s5b(e}=iJ zURs}f)hQIbsi=P+^Vs`j(on;kmMUqM`S;xzT@^g?cFZ% z^*_#$FLBD!ua}*2N2mUVQ?*6~g440TyIc=RKWuCr|Ng`QC2?aPCP5iuR%ry zzVt_woRKIa%U>G)REw@x$PXqNAODgjc(XRixLB5dKUEah{izOq@^$xm^~Y`gP-aE^ zdL-juS^M=V*NKm1`3)$)LcWZyrKRouhfrLf#4=19PL=6ONf{;z_C8Xn0{`3ljm?ho+~;IfXJq^kwPimge7To@w?ESzJ96KYcJjRPchAdW@GI*78;%{h)>}G; zR(tIG6DkDi*5>WRTD*DE||C zmWx4Vd-+;&!q}9U%oJ~dk4;OnRVX7SGci8R`=fr!NY2gampQJ3-Qlqri5^cxr_M^( z(xpf3@!zTI(gh_xphq9(VSP1^r}QuR@BZ<=*GG5v(D?`ezR%9|;6sC?v}8)nz{HHs zOpDJ+CXY6(Z;w7bRZ2xu2*ieVn|&Wh82Ejc?UD>KiwfKN=KqeV8#n2Nzj!gpN4Po`ZQ$T`!NxkfB|oJ;?q+T z(S=MH#-9jpYd*{HJ4GO-*q<%=I_Vu*M0`$8R%*gnd{0wR==#6#?DrA&RRH1K`^fu_ zq|F_^B=Wo@%Mr+2!<}8~%X^Wek2``0x86sdtDGWrLB`%&rM`TRA?bDO=k{Z_YiFgt zJa0vVXWN$g^1XJ;x-rFP% zb@a>k-(h?I2WZzxeR;o=G}P&^Y`^^YGoWkhyBve^9w=#PeVl&o@?S1^b`$dYDDR7s z%J)@{d2d@XZ&MqHrS;{#Qqn`Eg^_oTcX#N-a{BU}fuse|(lb6oQeT>#T25cSN03y$ zkCXDYuS!2Jp^`lDc^p6IemFQmUN9X|59Jh zzbvmq#=av;e8_wBrgYzk%FD~Lq+6g{tCYUHZ*Toy>hFQB-9_2Hyq6a^`q8K>{e+e- zAb$|a#-7x_bAQk9_5YAFe(5*aZ#fUgpj*0qdCx!OtjLzAFa1b;Nxwy9X?>FShS9$E z?e$Umk$RGzM`dY!`Q9Mc(U<*{Hl&^`UqZ&_$??m3`wU0F;G~rF(a^F*x`MLZe!Fsf zsqg)$vQ(WdYd@0yiG1l-LGpdYhfe#UQqZgAi#1NxOYCZ&r%H2)ipKc3!Ono~IO0N* otP+MS`NH5J|uy0SgRLF=~`4Ef6ZoAcg*}_1&x4r3XIG@8x`+zr2q$ zS=UHnkJK?w8tEv+2ps ze}AW|EU+zaQx&1Mk1@^O^?KX4)9S;=+wyix-*WnRO@2SyylvXsq^Z=Fw_E47hmT$v za-+*%`*zTE`9CQ>d0&Ptm%7Rau7B;@QD3YrZ~fuw9_>E+e0K|{*+btjOEOJ^G@Be?j+45=rfPcI< z`aIP5Kl8nc`o52Uw)Hpp^FF)c z%O7q1sg2sadCY6~Z*O|S|80BS1bDss`N13T#c#lGcmw|RH{gfD6aL%wIu&rh zwvc)i|LvOEBQ@q@!YTc-cG+laF;e@4d!rw&MCZYLKgTE57yj=gd}o>#zIlR^0As%hj+I z=My}6HEzXsoTRB#(^h<^t$1!LK7A|RvK41T{pb463jAjU{<8xAS%Lqoz<*ZY|Hl>h zNBO@0&MwTOnQf&vBjq4totnEOxaowuP+72`u*ZpL&wu8*ZbvIAec7WNq?u(PP9b7i9yJoVs zgUQBqAD^u40Jd@6d6TsrP&TeRYqGY3$;NdjPu6xI*|_eAEj5qkpZs>de80Ms9uM`- zdUUa#woU!YHuKx&pV_AWw{7Y_Zc{(9P5r<&^>4PRf4NP4`<6Pp{KWfsKf8R|-S+0V z(&9b%RQ`)+IqXxp!{)K_{qj8e)II)|`LAjI=r7rux4oL}n8>br_N45p(stP?_huh@ zb@2=Y54fOg^Vo%2r!9H=eCJP`Pd2sW9dokFPW-+-p_1+R+v4fj&Jz!Y7r*xE=FP=s z7W>|5CsI%O2-(SxZ~1kfq-pQWA(!pAvX~vNx8F~s)=$eWt1nH-J_K5JWihp6BHLMC z`p7-o&f0!kpUyrszhwRF@%6=}8(%xQv3%G1BEOt5#xHXU{PK}eewjbAvAm+5_vY5a zDy%#A4CX$%ie`PunoP{K=!dLqtS?-cEx&4zUlt8+bsx(%)O1|Ap<_w0W6pTCo?f4b{m>5v`G)E(eCC~i*1|}~NOslO)a(?0Gn$<;kzGFHTD6x?n{x7YY~PCXR6g?PKgxGa zt^XMIDNFum?0=oLt({MB{pf||&kfqg8nBzVFMej0ZD{Ja@~)00108c#vLbz~NKbRN zGks9C9!q`ch{4Wj4>oR>x^{5Mo``y0BU(4cw}8(2yTVfK%m(VHU&B9CM@9bdnuyzKzK(I~}^C~uo#)$%KcvYls4 zbk14XxnzLP(CHl|lt=LqXpPV+Zqur?mVM-F2U~aIChyyrEr0vS`hD#4@zB%D#-?O< zxZCdCFz-~eUm8dFG}MnkpZG`lxdkhRD4wAx>MmWS%;5UBysiq(Aj1>(aVtpw%Raz_ z?Y@ci`)-qX*m@6>c-Z=UPOAURe~;MRXr~tcxoPv}jpgUsXR3ZJ4|Mg`Yp^D)!Qr;7 z%dceL#x?NP3~T-0tbuKh$u(I0|GWm9?QxduF_!+%*Wme=;~I=@`JCKWe%PujcIL+H zo$|KdvpKhX?zU;!24`-t{IL49SM0Loz4EpmkJzKzCSqThX0ugLYX^U4@gmQUU9sbq z_ZFYadrxdEfBQ%7;+==tD|7f(``mrJbH-X5qE)|!cmBmYOZ07Mf=}>Y`R@<>_fY&~ zm-z$bGuEST1NMbF`SlfO1vj>9{rI}e`DVT4t7V+q^)G_HZN3P0N9lWGvgLcD^l$s# z9k%;xALOTA|FNIXV+fCPOWz(W->}?4VP86vkC%=6_AB*xORxWUE4c&P-2Xd|cNsq_ znCr8j5#?>?TJ`#04f`W~gRh3|{Sm*;XZ+c1KjW|6TKdTUr{72ZhrfpYpT3X$u=KyZ z)a&1x?Yy#wZzhu)=C1I4W*CQlT4X!tl$x{en4upDi>DQT@~_RCU-J{B`ZYgOZNrYS zf|EZ~?|<>X{ZPF_h##usQ?J@_z>b;g?&cT$ke%~C@KgR8(r>;L=KVko8 z)YsI%M*Sw;|N3d${$#yo!ypf7o1dlG;cJ^XP|&cW-?4QZ<@m97_&?di9oOdQWS9NY z-lWRg9%W`$=N=zEYdgLY)H}Un)vV>y4j^==H4*%sKW*b@%&+p4zqV!6O`N>>VS)z` zJo(NVp1dP8$3h!I=qDbs)7ZA5XBxH&Wi9kf2rYf1kl$N&`5q_f(L8Do=-C(afLg-? zvNx&nYro6~Tz3Y)%`=XrIvfJjeSyd-FCMTlPzBBIUI+M`;()XZDvODEe0j`OFyX5K!``dO@5`b@2vJp%h}^yb^`t1f(j zt-^5UPMG-uyH+3lbyknxX;-YfAq-um59yC#yR9FC{RWxGaKkqD^yBCC7|MsW$?P$# zyVWM`VX%Z2xp14v(h%Wc+ty)b%ft2$aojfZvtBcAQa$XQ&v?U!Jr%>9dxb}Iy=-sv zuvh37r+qGCEA0<^IvGWkhfJ7IPB0$Dz7?$)D!Rx6I4Oznr>|AQ};r7zhovW>g! zm-cIFN!6Bp$hRbIVD{S4xUU^7Z>#2HyguEveoEI?&BAkU@DuJq8n5{YSEJwUb}Sjo zcFYOCf$5m`{rA~Y)`O&?zz*ADPv0#vl)=f=}cC zZ!3A*zs(P@;rbWs_Lp6`Hods4v;Kv3Wqf45(M`LF4{bmCi^KKyb0)RU{=|E@e!|{g ze?{lC-(E7+eh<0*vMVR1ES_%%N1GmJbwTn8@m5bpbWST@I5jn_KaS>dRY`?FoD+f`M$>=sq_J9_JVjS5+fkA9xgWPd*~$Z8q+ zgaR3#$f>Gx*}1B+E}KP#tRV^R6Ddvh4VN_{dz%7HE_~Ow|Dj&#KAtv5@f*x|dJK(&MJrwS~ zRG=5&?vD!e0qnwk1^{=zmCQhnhnV{ociU$jT&3h7g}Zkb7z#O?m?|&=unX|~0=Y-6 zv>&XU)BXf8_xs&l2!kWw;rtkdyYCPf2l#z|SD*;63)7bY-1mf)X^~|RbH8|eSd&s1 zT&SdZ-2Gtz+otY*uYi3UunYHb0NfQu%0v!Z|eUiJoX_4$vvWLRmUl8a8SeA*a1p2HB;1U4tevv6Q z@?nU%pE@qA$p9^IosxqT?mkLj2w+(z4iFfzDu7)9xJN~Jw{zO&3xoR$?k>;*7s2dr z3U@yyFb=RR6ZZ-ftqS0F0PYEqw2gclV)uQlyGyjd*C}aVU+(_2KpNogj|r3k-2EW{ z?h27Ilbi-I_n&IRnpDyP*C?4#xO+c=DuBCp5vZ0)kyqx+T_aLqlJyXCf6m>!F}wym zoX=3W`yK&)3%14mQ-Q2Zirfs~z8gdmlY9YU?!(>PNDJJmWD|wEFA``5_vo~{eetD@@a^<4{`SZEpW4vgB0%mpuiBo-R}_? z2_}mi0l+;P2+#IU+Ye&y2agGBQlJGcQ*w;L-7j8XOE3;__umDI!DNv~0JtXtNki^| znER9NF3|$ts-(Sq(%t_hkOsK>^8#h|+dnQMp90{n2!!*|w2L6--p}2Yw7?BYCKT>I zNuUbg?!yGCgUKTM0C3j?VyCX1Am$!EI;=@8E$|EHYkyO?`>z7_J6?A`ERc<06#zHS zEa+9aNPY}4_oeP`qy_F)vWddopA%?~s!s^yBA5@r-4Zh&gqZuy?rx=p`vfKPaqb-g zZBezCKzjr`0C0CflJFUL@x0*v^~|s)-L!Czfrs-w6n@_i3iL+RuLSxc_#psyKO_^} z{{^x8zR2AJv~ag6IY{B|%LRs_>U@Eb2+jiF9);wC`y`0D%iUd|g}YkGF$#CTSztV> zwihTy@Xsc>Cm{CuM)FsPx$ihCtVxL$cr|!9Z)X^H|3V-Q@O|=qfwBm`2H?IcAgS<0 z@;QjP=efI*7VZ`$6AE|F6R3)+GX<(6I1zxm24eSw&)H#6@gp?*8*_2KrDph za)`Olad#^%@LVPH6z+bXKwDJ3OQ1c1w*hc>K~jk%Z-$tAGcWSx_b>TwTDVh6_E5O{ zxsTcs^hVW_0(}uY0KnZ3u?>#o7Z7vb;_d-j;9eyMDcpU7z))0uMqng@j{|UzLQ;82 z=0VK;es>pW;m#^KM&a&b1;zos-Uka5BiI9gdjeuZNVbER`^B2DCM8iJB`t85k_m;ouNJ6^szn0T5qt!Iy9Qz# z9Lf72=045cwY0!9mCR7M`#^!ZsM<{+8^II+?gof$a3s&o4esX-4{Op$3tRvX=bI?p zJuJ{1RX-ERMerQ}?iPrpklX;V`~IrCTWNupDw(Hn_r(HjQFX3BdjzKgaCbp$gCjW> zV(ydN-AxOeDA_~d?s9?NsCxCo_Q?7ocm{yGA7UFE$rBKBKXq7GlL1=bAteVX+uc92H>86aM`))K!~}I zb$5vtc)F7IS32DN^0~GIX@KwVrv=I)cpQMc0>WkIs$qz^|MbqVCY7|n{YoYj?!HN& zDyps*sE*)D0PY$Hmz}FFhM4kt!ri9`)J4^i0@(=m2jFgiaM`)49AfUn z-Q7qFT>6mqH-)>O60pCw<7@J$KrVvc0C2ZJxa?eY8^qj?9U9i8l@@q~l6eYucM7yc z)fEEm5i9`U?t*Y7NX~(n`)YT0(*kFd?4fY?;R3x;wXZ;51Umz8_d~ccNM3FX?nB%? zKnpw$9?lO^xcm14Ls50Nz(@o?0odOI%4a*;M}y^?AiMG3y(272A>hv`I!590PY8@h z*L;Cu1hWBIJ#(v1NyorMP@Mp`kI>>Dp%m~z%BOfioPUHF0%?GMO1Bdzi(un9Q&Xqg zvqyXn=h@$md<8vL;tz292)p@5s0{cX@Nhn%@Q?6QfvPCHS)e+CF2L#fn^N{;XjVsi zO;EMM?IWywdswB~fX`DtL*XBxL7*-QPZG!i?3TIL!vMDBAv_agWN(PM=eoO*7I>PH zO%(2a;cR<2%>ZAYzY634?tU0xTRvt6A?8lIyOkF1?t}+++E2q_>W+A zd33K9sEY1i2vkS+_W`(TAe>lC(hafue&LX?CbhJ{*C?5x@cV8Nr~~+Y&lAW3{Jzfw z;BJ7}<3(};#M~>~-5BseN;Xlrdxk(Wz}?#k@1U6mZ9aCb^z2;lDLK46b*1mNx`0k}sYw!x9ChM4;VcNb`Z zZ&z}R!ri?B;{bQxAW#Ij`!fLC6A&9h(gHE}_(5S!O0>WqQZmJhm))HeNCVt`tUwvS z-3J43S3ry*nE^5Pb?&aD1%Bl$?QaTqZxE;gxcdo#YJj`%2jH%O_>p`7V(yvluBCd38Vq;K1ZMo;O;v;o}x zlt4Sc-H!rrcR{!kBtsB$FLZY|E$|PO?4fY?3V~jLyE_H?0Pem5fV&^Ul|hn&n0to1 z2WWxMQgV>O-5G%)fV&SD7y-C@Ux1y)gP?-Syn{(tk3zsN&C=ecaBopy9N^x^1d0In z-V4Zb1`mP(Dy@ZkO99`k6#oLo7H^k88sOfm15;+ z-*2rz6~MiR2vh^yyC=ZT+d)u9W!|Cv!aCFj{QUcD9WoT|9TTVn*u@P!D3FcdR{-13 z5c@I4$gL2qg}WPRfxoO|6NS6m1e&Aja)Dd~=L2j%$4mpn+%w$WN(=XKO6KESwLn`` zy;-0=g6#pepCL)O?-$-1+;{C8)})&j?mvQg3Y^04d$mAsRQ*DrFM{s_Y(GOX!QBn9 z`)+pk04?0tC^<;s?iPWesG27*62X}O+@p|uaGwA%_jGp`XyHCc$uSCd&kz`os_g`d z5o|nN?g=C8BP3%GbFbVdtVxL$_&zXCf%8I7cMk}p0d_HQi$GZfUjcC66-L;SjI=?_ zJ=fipw7{1rnNYZUu0T~(y-%Pzf_DLM+Y3+qyj=}3ciP>xv~cgLWQM}sDS^7EdM;~^ zEE~a-0Nf2m!eMMR#N4;;9oD3g7Vg`XY@%>?uRwED-5`*Q;4=W+Ek+0-X@Qt~w!2$t zfj^{Vp2FQ(fwrhRR-iqCg8{g^j3koGfS7yJTf=>K)587AY1-cu?%p8K8&yvT^hIz# z0C&HUj3fgPbN9M?fEMnXl^mpScbC9WR9!7F62T$>?olIoN#;V#JM z2=8+KgP41TyDMpdmn)f2xO=HURa9LpP#wX!0NgbqdI~%XF?XH2YiZ#=Ldgt;yAy%B zs45r8M(}FA+zlc+Z*PE@d&678nl#eF{Ro(+z$x54B+wjHw+ZAT_!fZsZV}OWy9;9O z8{OSX3-_m#%u~2KC(ssE=Lob%a4GX$ff!?Tkxy~M0 zUj$DBaQBO3Oj3ZDd%QBN$p9_zDkTRg+}$rQ6je6~j6`rf0Qaa!-Xu#Q=DyC|1zOd95vYr*!v(St>buPK?QaCf^vTU32gpdH``xC;TeyC8n9nhi1cQg?UL!hNEWJrwRf zRG=4N7ZYz4=mWTWM*!~rn6a0zo4d;01GGr~8O&4Qac)##2;lBt3yc8V{UZSGQAolJ zk`)khuihi9Nr4vlS|!IQ{Js|pj05aq;sSvpz}+7J;GTeF!bk>U?#1pd(IWXaB~$!s zI_|C%Nc%sp!^E2e$^h>ES4Qp%NItm7A?DuO-IcURJ^<$VKni!?B~S&hi-{iyR0G`o zbpZEWVM{Ok0$XyU~Pj`2L7Rm1@IY!~` z8wJKK$i>7mfg-@&mjQ53*hoerO%QXh+%2q0i5AJzl{B%tYX#C)aWQd-KpDW@djfD* z*hpR@X^6S!y1SAV$!AW~{-&@|z*>PSs{;6~KsCVKKL_B}%e!G@`XJ^`yStWQ@E4WL zP?!yn7pSu;fJ+6k0C#^BfV)8>yxTc#7R21Q?i$vlkrw!9C7UR06i_A5tf0W|0y%)Y zrvh-dXhi3#i4%f*w!2$tp?Vz5^W79S3K$V+Q&8Xzfp&nqzYDk9Q z-AxPCl}h$d*eGD3K(B%V9~S5Xxcj{T-2EESxvCao?p}8f&_cDpl7kdB3aAhmQcz&? z@%G3@0PcPkfO}LUiDlM8%stE91zO;Cp40=OdewH z7s|t$lxTq$D9OL(x5eEkkoJFOjR|}0^>Drn;O=7pxGOZ0H%S%5+$-E&NejHQk_m;~ z24K^>OjV_zz|8YhF?XH2YiWUhqGX1`-8}+zA!rj{5Xb`DeH8$A zLm07h)k285H|!GDq>&bQj*?9j?yeJP4ndnZN+1Vt_W=OhEn&pYRTU6(-{|gETHu$C z)BdJ#cTu1%1a0Cmfp&nq?*-uQ3L|!|S_v`t3GVKu1^$MTJrwS~PM|jgZQ|1ceE@fV z41l{ojM%xV5n}G~ox_?8&;p;V26o7jojAR(;f!KY|ba#msxI;<%S2f+;Dv?MW7kr-gyE! zfP2pb@H}Qq5M-##JHAs`ht`1Krc|E7y_EuO0QbI0pdG-W$sYN?j?w3?3*zVPafsH! z-QBbpd;rYzffVk(OQ09v?jH#B0o?s{fbHj)X@{75rn?7d;r_UigK@4&U|)}p0%ZVqe-?nd0+J6Siy`Jd)ZLY|NPa}ggu?H8mOvH2 zE+&o_s0O(E?Eu_0Ho~@Hq!ME8H9Legsig&;qGX1`-4ioy3F<7!#l)WkvH*Ag4uHGC zMnH_*1u=Kd-Ho)s-%+xO!reCtG+U61iDd#gfV(dP;BK*zaNcf$m^*QID=m_zE19Qo zcdbC11-Y0wM4%nu?mYpxyKE$!x6=@FkCcTq>83^UnWMD7DcrqQpx1(2O#D`$58&>f z1911-2q8xLAm(my_W&*M7nK~OaCcr{$bwu6g2 z2$TWb{apa=3LD95mB==V`L*ecUfjR{>VXueg`EG!_p9SD<&4`-75u} z71YGH1#$p)e+huQMI$o?aQ86)++7;cdAkZ? z?lO0G)55*8l06je-c(~t(5s*()(i9j-2Ddt?tYEvygdRjcmK4oCIhr^|3t|_3U~Ji z3@NCIF9?hP+4 zF?Y({C0gK@4%hzPW71s|NCSKiJ|<8GaQD3c+!Y$hn`9-#+tBaMy$pJ8w5a%-!JbT3X;vRtZK7hOb{Z6_2 z!-$=?ix6{9Obu%?Knwg3nCAm2+&w5T6oNMKV}TKXyT1wGzDL7I!pL>+zeUN^o|En}fi%GP_lt+xBP#>A z`|kidZ$l6isLZ=zN?4D|fLAG%Q20If3seEzdy_ylz`fT4?7STWc`B`idusz;pj3v! zy^R8O0Qa6IkOjE+7=WF(gP@wqymjtv40un-+r~QL=|3 z3CURky#RL~FVF|Di^$snxcedgI4dFM&bWJk7K2li9HdA>a^mgw@P`2I{*%B6z%C-c z1K56!nY$q7E^eNpH7U?y@He8K{*NLF$q|7z zfV=MyXg5=s_$~lcwLr;K<)ph&APsQ$X#!;cyNDbE zz+GV^FH!|D_hNTf(qeFDB@>DyBsU#wOHc)H_j-Y9Glhvi0C3lc=)64wG56kE_BSou zKT$G6k%VNAKpnu{Ul7R3q{vkO+zleU%lQvt?xB}`f71fbQL>4`-E{)Z0CyiHkdsM~ z0|2;NM0DP+fS7xsyIX1De(50XZwhx81=;}aeoUZUCPnTA;O-L9d3z0ZTHwEddH#>W-D?EK0q(v>peU0fKLy~P5XqRN2V(a<-Q6Wx z;0`5IZ<%zr3ZwzA|I0k|tf@FU9twBgD$omX_kRiW1(QWS55V0Yh@H1v zA?DunqVI27;PaFmq;PkGz!1RQCkc#1a2NphD8$cI;dRCQ*^s*nv~W*Ta*V>=FYIqi zFdkKZ6(~mVFaY-i#LrcO5WDYL?k>^7{XHd9dri8#1=0Y&@6QR8Meqp#?uwXchM4<> zfBF8Vh5HO8lQ?&RKvh(|L!df>y#TmtAeodTWe{_(aCa>&+|MW4-xR(kV*+(i^`JmD zf?ok}H$d{ieJjM=b?$DYh5O4&Hc_~{O`tidE*Ho}a6SNc3&hq9Ndv^(8~*A0n-=&u zCG!;St`=yEsy7R?N3cBrcNZiT&Q&k$7u?smyPFp7KZ1F_o5J0z1$v|E7Xp0|d>??j zAL8e#ZiwA?t-A+k;l4)6K?--b2nub6?ko2M#Lrb@5OcS?yF?53ePEsseCwopKp+j6LY9eJ1j-`#3V{2r zfY=Q&(grbijk_ypfiF=qp!72E^Q>8^fA((*nP;xAr%MyEh2* zM%5DneG%Lbz}*kA4US|0V(wOV56}YNtmGhtySoI2qUvgakq8z6aF0T4gCm&>F?Ztb z0xfX8l4BI^o+&UMRR;i>h-3+9Nm>fV&IAW#_7y5OY_!yPFnx zA0>Mz+&x{OH>zHKi#@Ww2%ZMu?uT&MxvBs$_rP;uO$KOzS1CD2;qHEcp{Tk^U?hU; z0k}sYTz0Nn3Nd$+y9>0y^OYQ%H96anr&A7J|#;^*xKh`Afx zU805iI3@Z2vAo4yEszGd`^^Gn0C#T>u>Bk}FYFQADR)=W!u>}u&j-didmS&&mjK-T z3xR5YyT1>x{S3*3ht>_T`|evG)})pe?rW6HQ23g(2-E@GJx?GDaQB%2+zpU?aGwA% zcZ0hdY2iLd$tDVS&k$$^xO+Q+9KhWhXUN?Gu^A*|5ObHF_I*tYd>@$S11a1+AkYSI z_bmeL0C#@{zhVY!rfg0 zbpUr?EszDcdl3M41H{kU;q|%v8GySRY2mI{vWddoGX4lhk?5VIO-L$~VmF%H#_fmmg zfV(di=mWU>TmbHVh@ZE!5OcS?dw>@1Ba|GZaCagw1aNn`zzD$IuU5!C3b752WCO(9 zHSR9Z0zU%g`9KPH4+)F|+qy^qb$%KOc8JI3mMdj|7%k7a>1Kj;I0C$ZAQW?t> zAm*-icP%aODkU=%{LetYKpmC4ZxYA?++*uI|(&_Z>V zl7kfd&p<|Ch|1lE3yc8Vy)OXws0G;IWTr#R-Qw;7E$}~g*8Zm8e+KNewme@#5P8yGyiCeND;K{*&%@fi%F~pA;wqxcfo??g|K3f@C(t+=G7& zYf?!Ie4>&G1s}`6p#oKaDFM7ypc>%r9Rav&AY2(TrRl+)b9XH*@SnlM`3yzEAYfFW z4lpHvUkhXb?*0+L&f5^?d#LQ5XSla9;0~plDBRmB&S_3{psXT>y6M;6sl;ABFXa~6W)t$n58-jO&%DfBR+a2)ZV4ee{aPNpfFJMaW z-XYKjaPM~kcHV~I?WQtsrF#bgzDB7*3iq}M3<2ySI8R_Cf-?cOpCNwUJ^`Y&_;Xl` z0xgmUDLF=w1l0_Iae!S+Y$s5RVB?N*PeA;e9 zltu6rfbHj)39q5$_v`Miq=owuC6hQeSD-4Y-X~BU!MlRH29n|9BdLbieGiOjvgR%$;&~ffnw1CC4b-JyT#jstyz=Mz9+I_kimHnRsv|fTfV;*> zMwu+c+@(MI{-%ZM2qiNV?oI^iqN-dV8^NpF%iUlkFUbaoxqIB*NDKEPV4nY@aQBcv zb5z|Xkc;430Ped*MCa`;h`F=wZl#6$Q%dG3+?^9>i>h-3+9Nm>fV)eCcRBw-%ssKz z_ctx@K1%jbxO=)lZ&bZJ%^q1_1WyBS_lxNH?E=Ky-R>Tsg?p8fgB0$z*TeGqsi?Y1 zU?hU;0k}s+blzSHF?Yt@1zNc0D>+8t?%4w4QFV$yF@hrjxFX@P&JWI~Yy)e3>CsOl7`j^GLa z?i!JdNpcW#*Sfow7WgbBGZgO52-HQ@;R4wR_66W>5XqZlI>g*#e+X;RNDKVW?XjQ!-EC?skE;sQRQpdjuB(aCZe_=k3`L zbJw`Ln-=&)C3`5`eW*ZhRJ~Q8FM=HbxcdXK=eJ8!gS+r}Sd#%-;6HJsOCew^u;yzVq%b&;nnp)dd2@2tEM7JrRgKza3uZ%J0kE zU7`hko06%6Cf$_+X@Ff!yh)%8FeQM0O_93-;^(Syh~4)n|36Xe_aK$DNIn4O`9O*! zB<~Wa0@%gG4+N?K?*2M}`>uibxvCvv?pAl#(jxhBB{LLBP&EnE0qkPp{Q_BlyWb7K z-4HW}Ld>1GyO9>jJ(O&Ub7_HQfV=;}L4nu40^I!<0PYq@h8ZNI5Oa?_7S^Pd7Whsj z^At&#>l0`L*u}(G1=<1b{wx4@7bG7>7DLS4;_hx*BtN2L4@DB@W(o8H>|)}0fj)q{ z-wwdtZzCW^Dk0{sboT%)@DwEnDUzU?;I*K9|F&QNe-ao0xchei+@m%!Rg$|P<{o@B ztVw|u?(Zl$Mv(;7jRNBq3}Bf+5#a920JtY?BrQo3#N0V|muTTWUCGqJlWuzrE8o8X zb}?~?KpDW@djfD**hn~6r6K07aCapwlF#tcPxd!O5>#sisw^15Zw0CW?*2Idca4pN zb5$S2+yjqQfgpCIN=xw}9M{2L|5DBQhLU|c~> zd|RLhaQBx0xF<9c{{9oh+%Uby(WL2qo5|%3seK#{RaT<8ja{&H3Bhr>EUqSwX|^mM9B<= zyL$xc6x75Q1hN2kUj@M3ppnER;kBupzunzP3p_{3CJJ}g2{bFHiK7H^0Cyh{+$|c( zn4|(?_nmcjD=qL#ygZZrP2ujMK%0V^cub%j;O=_?+;^8o@+MgcG55qnVNJSefxn?- z4~4t06X;b?6Q3671GxKR0Nnjy#LiWX5Oa6Cdw>@BWF-eF++8Cu6oNLfpTG#f-MavA zkA@LDS8aMJxHIl9&;tJr%=6t8?p`A>9)dP;k3bRN?wDysP-193PGFLNuV0w z?tk-&PTVzNM1TJYV(waZ*U}>S5O_GBp>X%0KwSvh#E%8C0C#^AfV&}#B#c}KG56Sm zVNDuoftM)RMB(mcf#wjji4O|o0PcPd0C!6m$uLp_F?YMWTWNvcqGX=J-DLu8A!rjX z@&Zm?{|a#T-vK<2*%br@D)Y{CZ+F0}l@9hbH?#p9R={hLi=e z7^1a!Ago0tEe1cLWI~aIxmf~L0K1qtUZ5J_?zaPMKSTVyT?sLFySr;?k({Drh9U{7 z30|Mc-+u?##l)WkvH*Ag4q*E^X6}NRyX*dN-;K0Len-isICrB!Gr--;1abg(Uk0%K z49PHqqzPi~!hONrN(+3tl6i_G%-QQo`STlqT}&Jz&<=3-o&elkkbD?PL(JW}I=H)O zk$i@iWAa=#MH1%L3iJZ(V&b;~eE@g=9DuvuMy6<_4`S}b-2=2peo@ImiX^D=0z(!I z;8KARfV)2mz&&asASAOO<{tWOxbFfj@X<<+Q6xcCB`|Km0CpEB0^B_nfP2D5!g+f_ zue;>=e|MK?;eH%EoKL-d(mf)O2H3^K9Rg(lcYhavyTV4;;Edb|vHPCk?n+wVE0s(r zlAu~BP-VdYJ}giTaQAxwxNB@AoVRNs=Dy4KU@a}&`zx8DNP?%WuLT2G zFVF{Y_a6Yb`!&M5{QE}`bI*1604?xOlpLf;f~rSgNI^|}L0|;n?yCT}M>V4J_Ckoc z)9x?`_s0OZYc!(s zb|b{xn})-E*V4j$vXU7Jch?BiDX5A41hN2k?*hQxppnERoAla9{(gwN8)9p&;maM z=J`MhcMl2-g`iFRSYQO;?r#EckA@LDZ(j#7cb&Tnw7^T09HVe|v%q)=+QbJ1iU4=N z2Y`DbjOgz_LCn43H(^amv`D^1$<(2f?lOTiz%C|U-i5k~C1y#`|L z8{J(=3;atZ6N)6LZWX8sL7Vt5fog!eKM%lN6Grs+pCINw!QHjANS>!;hQi$q0(Bv1 z6DJ8|0q#BwfV&}#B#i70G57dTSd&It;Au)WQMmgBUiZlV|ECbNiN6Zu0PcPmfV(A( zWEdHQnEN_+x6%TCPsu!mySoM2LeM5YC(sUX_a^{$-VTBsm3eF3+a2&(O7&2DxU1V#Yvy%%8T?I0MS zGH;iA3jyD()EGq)G+hGY0K4#BEl>owcM-tO+dDUbI){lBP|AxRe$;Y`7fi{4R z++C172+55QyYDu4chdr2sbmjDCd@4q=mog@!vcK(yNJ9OfVjkRK6ej)vz+Gb`oVQ0H=3f2luqL&%aQ{Tf3`Hg+ zdj#qL?*4*6)=XjIDgf>VBZ(vnA?9A}?nYX;=P21kkqOBZiH`wr_lxi@&;LQpz0lnQw7@4TIY^NS$r^znfV=k-7?DYl zT>!X8MReZY^ta%i;qC%0+s`(RsTLV(w;lSJGneok}JYnULIDpbFsb zodl|7Qsm#fS`l}Rh|b$Zh`Fb`yOtL2hrm4lN8#>4fjWS@e=LxdNs(^?a5sn~CbM0McxCz-6E1PNe#r@bKTua3;Y%(^Azqb6KDgt z`^CT7BWssQk-r0QcZuXpvIb)Aw7a`$fq$uF4~4sL73c-H`@aPGWK!hw0Nnk7*m=7Z zV(wdi5!PgY7Wh0R2Pxd$ATR`Q_elaH!DNxc0Juj3vGexc5OdFVcYzjonv!D_?tX#S zCh~e$fV=-HPz)xEJPg1+5s00)2O;L(^z(4vC0gL`DVeI7baxA+0q*{sKpDU;BA)=@ zt_Z}=+szPj_qw~17WfP$6N*eoo*+;KaQ8a|s)NZQdjW9Q1Y+myGKjfnxx1DY_<3H6 z$o{5q_n1H(z}*iDWFz<$0Cxk#&sDcV%>BZhVNDuo;r_CcO%(2K6KIaA%LQ@~oDaa= z0;vd(wE<%874B}Oh5I-q^Azr`7H9+beZN_tJ%a54xVs=paKA7X+;#5mriJ^DV4e@8 zaQA9~-l+P8Kwkvk2jK3HnQn;P_lAM6CIhr^U!&w;oNEynimG`6BN3blzdhxub6{z>i$NQ!ksG6pest-DLK!1sZ9KJbW1_kche z;Ol*hKv@J|0dU_HkknL3+92j0yCbYgB`w^SD49_Bea{uBimLYsR7dbG0PY%ypR1}N z=5BX)EiK%;Dw&~hx4ov4=ewioxhD-|BX|;ky8+_os?`v4*SNcp7Vg`XY@%>?uRwED z-5`*Q;4=W+Ef7CfwLr{W_*qz!R$90}q-37L-C2RQs5(}lJ%WP)xVs>>!I8{>m^<(8 zZd%}1{;2&;;qDCry;1dqKwkv+19104EQDkLV(w~p56}YNtmGhtySoI2qUvgakq8z6 zaF0U#Ts0SB?$O)BniOc^u2*u5!re0k#-r*$fno%^0dP-1Y@;GcLCoFi?h-BVy0zNh zM^3s60%?Hn?_q(m2z~~@T>-HTj-(f2?!?`dw7|=iOeiu;0+tF?Mb*Uu)e)Qvz+D5e z4NfKtG55&IuqL&%z(**Vp>TI1P#0C@0@(;&eM0UAh;48r8zAOxad#sv@FQTJ52SGS zkU(=(-6oKW;9CIhy9Ht!97%YsB(FnqcPlOMrVF|0PazUZEz$@A?B`dcYzjozLH}U?w&0$9#y9Z6eBnifO`VMW#_5{V(x*T zhBYbC0`H(?>ZnO~>2X_vG{E=wQvziXJPN>F0pYT9)eywoP42Fw1^%Iu2}OoMzzTt? zsOl7`j^GLa?ivV}ovU&XbC6d48q?E-C4 z^+|#D2rdNR?t*Y7WM)ImopN_KE%1p-_E2OP1RN^R8&z)==!;-S0PcPWSB6aKvEc6O z4{I_&3;bs=&kIsy7zB(83`Nzi1x6zH5rF40M}we;%I z7{S>9p2wUBf-IHR!o8({k5DQ#bJCj#qyfIK%LU3Jc=b^|k68iX@l8;f_tqbWb*K#Z zaqw_Hq44`15vYoqI|QmD_%49wF>8XLo65Y6?yU{@8l^H6?rjmMi<)@?S-{lr88{PQ z`x)Zr?Gqqc3wJlt;-MU*WD|wEX9zR{d@Z&U$N}8F@e#ROAQj=GjX})a_oJ{Lt+a68 z2j=+_3U}M<9QpHifV*!IXa~6aD*)TikR-Ue2jg5yUTs1wKN_K?-*#0z&|ImkW#l-2Li9 za*sm%yuATp_r2!(VND9Oa6ba(`9KPH4+)F|+f|z@$yGyjd zpHeb)%%nRfkOsK>9Dy=`yH5q+u7DUrG81C%DtA}X0`H?_LXlw-FkPSu;O>`4?U7Xj z-2F5FcMZhP+XaZZSAQ?8Ni8i@tCY-8xVvAV4&d&a1hN2kUk||D0I>~@WGTeli{0Hw z3p`)RCJJ}Y7H9^z`xJp3z}-gzaJNAGyq!SIy|=qtY2n^M$vlO-OAp!-v;o}xlt4Sc z-H!rrcR^CA#3Vxya}V`}HR+}W{-KgR6z*Oj&DqdxpCUv~d6PciP_+?j9Ey2e|w90!4tk z?*`zWfN29 zw7@4SnNVaH1RN?*1#tIU1*!q=-VuPi2Et|M?a~9mJ>A{4w7`D`^L!vhhC#rnKpnu{ zzZS>>-2Ec}?gj{#owrv&?7mliH>^n`E%3EUHc@041S}S42DtkIfgHfy9{}KPfpFP* zI|DKITz9w90>4ekJVk~B12^Y&_p*23M5v`F5rWD`XusCosO zqv{5MTm+v1*nWoidAkK-?pwbd)}xgc?hh%Mr*L;xpe?G76=;v(U;yqeND}U62E^R6 z-Q7(K_bc~lKU27SgFtUoJt5E+!TkW+&oMIqG54l#h5H_$h5KeD2jg6qz))0OEie+n zA^`4DNIu+mc#R={4&v?tE!_1=j#2o1&lDJsssjaz5$qP+6GqraNKz2H?^*6H(E_g< zu{B8@H|Z`2qycs@F)UCP!OsBPcZHFu8tH|Y`-PjsnpDyvxm?MFA`?_g1*)RzVu9)i z&IRDEF%r((S%|q;xVx4X?jw}UP`Eo0sEev{foue?4$IwOB)o251H{~Q?rx-o`w=kD z|53PmNT4~YZWG8w@GSuM-C~3dj-(4>?hQR*O*8^~m8p%tt6k_i2o5GqDXyKl(Ogtq}7Qv$c+!Z1^Zx2DtJ=5Klv`GF?$%G;kR4W9k zqN-D%I)WK~673hm#M*!}Ak&H=7_XPJ+cMs44 z{~65le-!Q>6&Q-DUki*x@FM{3QIWh!RzU2&tK40n1-@3vF$#At78sAJ3j~T0d;oxZ zA`m-oXCUTYy&|khi5B>6N~VsVbXN+b0d_I*CV{dD{&lz96@l1!dmLi!#qO@81%3cL zoKGk+L3NivRaE^zpgMxD1Gw*+KL07@k>w)z3jlXZAa>p!g_wJxyIX02?^H5R z;qE?xwy655K>Pn9>U`j2tLOheT~keUtHGs-s|Jg~O%usPf2JB5T-xfEMX_nCDHj{J z8g=DhY08zw#o*FnF>z&avADD`YJG*prQv&NV`UH)(ZrSS^Zk0izkAN(`+Iah&-d%~ z?98Y0(5JJrj~^foaBlvs^vM`f-b-=5_byB1H%0S0PY1y(IYz{<{k@oj}gsJ zfO%iSqUyP=0!sk9NW4X$4+!^b0l1gb&M3s(tHV8DMDw|7W{z8NR|K+va6dsH2MG7W z0l4#!gbb1_#N0bR;CoVF1pd>_dcIjyJ-1t62w)e9?+KIu;rj-J1jelB;GqHy zK)6={aF1KW&sEFcbochQ?@5yp?w^Bs-_4@xs(FD4D?0d!KnoD=PXcgHTEx#)TOoGe z8^YaYg!>I@PO+%E%D&^r@8ej}!Ak`?fN(z#fP2Owey*xO%)RIRe&4ft}p_BUd<|ts;j01YAP!6PJucg z+*bo|4=bW`)kcW9YvCSYg!?QtM_GisCNQR=5>FFo0K)wk0Pb-`{P&+A=Dz2>z9&sa zxbOeEo^KZ6?h8z)sKgxtEkL+$1>l}kMCYm<5OZ%0cbgIJ534!FBHUX9rd3qpDuE6l z+?N1w&nTjE)d3RWozKin}S01=vO6 zB7qzr+-C!D=M_mTQiGVg9PR=m@DXYjSyWxMR$xd)B@PfM0mA*SugYEah@GqY5Oeq5 z<9kwJ1pX12_kk?JJtt7}q9r~jPzQwjBLMDu*dunX+5$0m67CU3;LFq;WfAT%fiW*y z;!J@CAlxSdaF2V$&Q)cIxl7@0G6El}X2K%eIe`f;TH?N0`(!OZxbFtwp7e;Ft9C)m zz3^_|lQtvpH`JVB5$^2*(_Xa1hXgu+a9;<&J>wBOS2ZE#Ziai75%@ec=U9Y$SfJ}g zOPnY$4+!^D0Js-CQY5k#V(wzNdyK$)tGUP`-1mOPwqVJNmiUc89}w;z18^^Uq)KEC zV($5O`JN0Ifp1hZbK;7-EszD+MdBKP93b4619*>_hcIriGViAF795|hR*^;3HC2Hj zfL#=i6et10dl-QCm}M8_S!plATXB5kf>~DP zy?xU6VZ`xA)f#0{b7rdU+Hxm#cwU>Aw+33LGA{u;oZXGom4 zI}mfvzti_)mJ!W&sX51@>Z(Mb3$TmCYXs&2;eHvwo@YqWBkLjN9uIeq5zVKoxyYjG zxgmiifL$aWD$oandlkT*=d`oD!`+2&4;az>Ihgl>)fKmW?~eB+0J}(hMIcvQaeoqk zI}b@bvK3eUN%}#rvBgEV@TYOJ?jKF89xyXY5Gf)#)vK1HLX#)M~ ziu*AD+{+Lv5y?Z$JreGL5P=?Y<`!M&Ogwtg@)Osw_}bQHjF^ z>VR+`1i(G4h|b%4X52j-?h!_~e+%Y)H;ZuZ5*SlaiEj%u0O9@u0Qa~e{`*f5yYInu ze&0<-xUW?+VG-`8z=VoQTqMu}g!^m&?ny;--mXE+Jr(XYBiu)*ImIH}YXzoNRN?@E z4j|nB`kdS|is-!EhnTw_?pa2-e+1@zAd7I%33OFd;&TG?fN*~VzS8W#k1~K>IHNGcpM&K8yImIH}_FXyt_uGq>c(OnTu*%(!0N|eSh@H1{5OcS}J5E^-7KoESr8Zk*hTSXff69RHv#Ot?Sd&*+Kcd39AB$el||JxO@SJ~F1!~B)B)i= z8(`;c7t~pqclTR;A4VJ>rPe5ms%we@V*tDG9xTuRg!h3@`+3_1%dE`X3UAZ#&%wO^ zW0AOKUSI;SmxHeev;cPTH~J*No@Yp$x3@yH7vXL*BKQV1r&uK3+$b;&2=_|`Ism(< zJP%;cGbGO26^OZe6TTm_j0iqj%{dl{H}e8rK)C;VqkXb@fL&Dn46x@JQgpH#V(ui| zJw^n-qvj%u#B)0YmH^@YxIiCZ7nOGd?0JS%og@%*m%=?@MDSuYGqn}>h(Hz)?o$PF z0K2Fh1Hhe6J3|n2FTBO~q`-*afoc}h+@6kY!4M$ae-tPI!u<;X?y?awNai8t9uIeg z5%}|JR#_xO0aF4sK)By2PzTsW;%WfyVIw?}5dY2_KN|}72qW-WYL2o`rMYzWV#sJ|yQ=lP}DklSQkBjKMU51!D8}23}+=r@} zun2cfU;+^C`=;%awE%Wexf_6cQbgzNT@Z6`f1~e7n-Rfps5!+V@#c1cX+XF?B+!vb zmFobwXGC<~ZbHnxA>6Z!aG$5<9E)%d3v>bDK2cy^CRLsSz`Y<+G|5_sx%XV{_uXRz z-doK@7U914o+tBHZHwT|l^B zE-;^h7XolEK;m3=9K_tmg}cWH_v6%DWD)K)0!yjtK?400{B26^Wk{T>79r+dy2|%t zzzFwu!M7!uQ&-%x0$D)Z_ooDMDR>`%`_4m(8A&D}=H3$S0wdh7QnSb+?)w~pp;YyJ zfl>;d3BX;3RNY;InEU8(R~X@bu$omC;m!!uQq^B>uuoP`!LIwQmV8R7n@ znsY3|Jt@#lRc{oSPr<7JxECOCt~v)|?q;}qjBr0&%|#aBK3ZTYRXtXqpMr+~a4$pR zT$O>CyBO{PBiz6Lu%7Swin}L}1;q1xn?NoFUk2dLL+k-ZG7T~JeAD-&zzF;fHH$3b zzU@0_oWE1mD+Nj^cnJV^84~BJI>g-L;jS>keUzG27U3=m)Kb;K0`(L;uubk^h?S7s z12K0Y+#`&@KLztXkVUw=0%NJ_ivo=ld3wKV+Y*lY&10aL+*O0Y}n< zm^&ZtSw`Sn)SP1x?iqn@s=7g7J_XkUa4$gY0Y|bKV(!_?eNTFfz!#{w$RgYu1eQ|O zDFXczJQaX@8DbAOk|M<1qv0Mf0`I40=D92G!3S*%vVeHLee+!i!2h7fNcUpsp{L&t?6nqPSdmO@L=c*Zq-S?hN ze&0<-;18&oun2ccU?NprA<#;}g#g@>5H34cZGf12I^1nW;N#VtViE3j0@JDLkpi6* z><_>_1L3lB)u8R}_2Hgn1ilN*`#=`qUJ&S}s+$GoQ*aXi_X32=&Q;qWcHj5D&iACp z2>ezx7g>aRlfY7{I$xllf-?ZPmmyqsuBt-Jy)E1WM&Ku^nR(udyC9GS#PhwcKrRLU ze81dz2v-rw62#oKa2FVXzYo4GDY8fi0(J@vrK-;elv40P0QX&na8>EFAm+a3wZ116 zM&L`;tg=W50!9UDsp>R=dJ0Ye@E&v61!Y#|ZHITn@u6ysvIuWZU@X<#_dfenjTGDs z;63KJ3wo@y7vXI>zD2EsMR;cfCQ{7}0<9EW58yrKqze*O=3Tzj_o40hVzs7Ngm*+> zI@O#i&`H5D0KPXcPu=avIzIIz!D&yx%UY40pWf#z@BGFoVUjz=Dug#_hY~a_lwocJb%Sq z7svv_{S1K|Al#1!;Lbyee&1^#=H43a0wdi2*{bK6Mcns20z-gs|F1v^5bpm0*z*jj zx_cI4?&HE;VTAjmYF1fVR;+8i0E^?VJNK_tI;8PevHwezuyUY3^u& zF+jK6h)FI~H{c7KnSw^^zQge<) zxQhZ^K)4SUmqb$O`L0}9J?o$LBfN(z*fO{NbAtXhJxo_X-d(vbC-cQYh zMYso(dcFbS{+&Pz5bmD=a8E*vA?ZTQy(!#nM&Qq?ImIH}+XSWo;eNY72N3Qn0k~%% zao%n~%)K_;vy5<`uI3zza90JofN&ovFb@d#VF26<5PQIptcIAo`%2%F9wYES-l^xC zMYxv)mH^@Yi9jC^?r#BbFGJ$IJp(cK#&8c9;r@V{nHQ|MTLM`?xUUe%0m6MD0Cyge z$rMep0b=eo;Vv)&AFpPSMM4s=PGATS?nerg0O8&rfV&LIWUAH~T<`9km-wDk7=iBs z^S+x!xEBO!fNvfbbqE&;o?_ zFo2!6U65yG-b{Ggj_fmRBh8wBQ4a6JI`f)O5YB%2}TE}!G>9wYDtYA&(}_XdHb zRCS6#KLt+(;9fSu1CFE!F?S{Id%y_1pPHHB75Ct6wgp*$T_k=dkW0bO0J!r;ii&h0 z=3ab--*a=1y=%amyJ{yg= zCL{0%)J#}}+rD4Le@~{WD+F38xDbGQQbgzN4G?oD;chd+eY~1eEW*7`U^-PjQlOK9 z{QH3T^`6UJ%iFdmF^=`}R@alO7}7 zZ&h=VMYuN!ETyXR1^OvC1Au#3MDMq&5OZ$|_ka=ZC#sn_eZ^f6$O7ylv9CZb1^;}D z++t%7O9$~1u=K`<-R8sM&L`; ztg;CAs6Z`MohDFE!3hA|!y<`E)t-F_<*m-*tV)wn{T;G!cBk*(8%$%{}t_WlS zc9D33Kn@TGxQ7F{?>r>VRauC+doOW!ff3C=C3?PDB(B;mFa)rR#P0O9^5 z0QY3t*$T1y9=z24`rxF^h~^vAoJw;W1*QSveyKnQ5boyzaL-r-grov7_f)uN8G#?I z<{XQJC?GG;wW5RnUTvRj9uV$718^@`#Lrc`A?B`!yT=ICchp>Dk+^Dyz>*amd|aRp z2=}`IxR))Gl_Y_fyC2WsfD!JC)y$l^;vNym0_-Ajsz449?qdMB^A_=Q)eywoTmS$0 zW<>KqHH$0~SM7O&ZNZQg9sE(C1PJ#p0JzH*;Q>c74>9*~;jS2s+)FREzdkq_VTAiEHAh(_uCnhy@%su^bnrBR1|Zyz0pK3D zNL7+N#N1oL-DHIO{>^&6StPFN3rtwi!5soEK)7!O;GVQdBFPSjxsMKan-T60t2xDj zn1L+<(^hnFl|Tm&?n?l;XB6R&{QeWf+`C77Pi7f`Pf~M^MdGTmKvzX24i}gQg!>=> z?gd43uG({zyC=fkV}$#+VBUAL2=^|5B^8zUwm=^c?k@mvFDv4|{{*r7UKj2GBiz@j znc1-7ZVF@pc9FP9AO{Hd*#O*mMRcyJLCn4DMZPBmMl_F5v&bTG)mnie6_q$Zpacl_ zzpj+KtccE4eTca?hr7ZE_m9B54`dPUIf0srN_!o6K!T16#3B+vnb`#J#b8IRbx%D>yhcW%Nx z%Lsg)nsY3|JuJ}mq9slgm2F#_+c<|2!5-`lh;Sn{GJ zek0Hag!{(;?t9rIcCMO(n7a|~0VD8@YG%$_akmAs0J}(BBaj1x`*Hy8yhrR@H3l(v zKHLRH;1{S_WRbY)IDsKATH?t9B|x|z0l;1Mh@Go)5OdGQb5dah{_7QbzFCBOQK05U zOZ-rv4hZ)*0l0@fQY5k+V(!s!k1ztiSItot;hqo}^P(kQC(rF#baqIt2J35&#YBLWiuyGWcW&;o?}7y$0c zv@--T_h8uFZALT?RC6lL?RlMT!89P;e-!8d!u<;X?iq{l*dUpQn0qSRvy8x>S96X< zLKHA1(6yq2cM8k{!hJOW_ku-0bT&fFT@QDU5%?@M7g;2(vhOAFzt>iD@HBxwAl#1u z;9j;!R+2o#+{>r=o(ve_zW=p)zF)NB?h9lAc9FP4AO{HdtpMD4i}-na2gKa%a2FWS z{IHru7Ky922n<=#!Bqk!K)5df;4WLl&)Xvqb63M%VTAi6HLENVSCs{7R&;Q_t^m4lNL!NsX@$L33r2Hy8*cKis-z(3u5lxdf$@*Bbwh(v&bTG)pmg)6_xmqKnW1;>j1dRis-!E zgqS-CcZCt|^VF=e2=}l+O+_V66sQBj{S*N1VMU52Sqm|DDcmEBztg!?#wX%&@tvOotA?neM{&v?Yn+c}82i{YMS1pe!*^n9}j_o6`8 zi=8R} z4@1m79_|4n@Ud!UUcBNi31k6wkvK#k2MG5*0Ni-M!~xJkiSzb6#N7FCR~QleyqZ;O zx_e5X1_<{%1?raIqH;CBo@YqW$wr8|XP@JHGQx=9S!#}2)7>?JF+jMVCeW}17nR2V zaF0W(PVx|QkA}O+h~WJVJYfa{_fsa8daPfcqXcQWV(&G56pU zzwZ%71TRx_)N<|~6Bq-8`%Hm`CAg@Z48T2Zq$*N|n0qSRO-2L{RWq@iyK@2)fNA} zVTidGtG*{qM&M)BOf2W_lE4HY+=mFXWKv}x0PabV#3c8=!riTKw;6$d3FaN3<=lO{ zz%(G-Ul-`eq{^oOxM!T$dAkj<`!0ujmJ#?(YR*~C-QxmXK)7EnFz+U-ybyqU!HJ!> zkAs-IccSk}j}iEBYA#yN-D?Dv0O5X+K;KPP`P+GNFFUdG_9DdGNw^1$z~2S)KJeTX z_pCq`5bjS20^^N^Y{sGXc2E zPVBs0f|z^Z1mBYiBk+UOtXj_98G#xg+7*A#twq@A2^OonzfS!U*@5 z)Es4DF2Jwb5$=zw zImaU0lLFmT^+tjD6ucUMdjXQT`y7b5*Mz&r2=}wqTx1dMqXm{y)nf(vDR>9~_j20F zK+L`KINy^2Biz4#nV#>Lt+;yvSpa)viQ5EnDflt~ciuXFu9}9Jdo0`qMl|1{X3=uK z-XdI}ymSMFi!__^vH zh`Dz>%lBl25$>OYc^_ywcXtKGQq>m)8Y%b~fcqY|j-RV0A?7|O+)YNfU$16jId?Y% zCQ{Xl1X?M04gmM0b&8T44Ka5%+-*j%?$3a$j;E?b8O97zLW?)BlWFan>hX4P`;t_svr z)sX`A6dVS?J!~Bwa3rfC=DxRLf93lnjKKeRiJouExqC@qELHtPppk-a0dS97hX)+V z48+{q!rf#9{(zc^<=ov8m`GJu2((ggAprNJbuvYhY=D@%7Vb79@bPL+SJ(*<$z6;FzZp*oQL7%3z^iyyK0Qa(WXd$UW%za$A2aLc^R5LSbDS?7O7QosP z`wHYz@Xrys^AIjOS1m!zy>yK4Nr4gg`{3J>B8zbE6c_@8`!fQi6nqfCeV47n1CFEx zG53~mR~UgWRkO+>+@k_DK)6p6sHflr0PbPy@PH%n@7nM^q;QWg0zXX6Q5NCO3XB25 z{r4ByCu^kO*8tq(*5Lt1vH-FB-u-mnlO`kZSJg~dgu5d!0SNc|1zIV18vysDbuyVE zl1&hEPlUV82z;)ZQ!K*0USJv!?q>;fQt%`I?iuUwfFmhD%)Kt$vy8xdsX50Y+{%))*_4Wwgr{|;k`znpMuK)yvJOI;BBzd zUW9kx_;j^0=VkuiTNTIx!h57ZE(M1Hc#oNf;LWo#Z#ldL$M>J5XPQOaZ(m?2)!ZRa zO2MrF-eZ+vh;E7vUaZ zMDW>ajSowp7M$d8Did(h`Hy=z9%D$a9^P2sO8+fL0}9J?o$LBfK~2(DggJm zb^N?tgqXV#?j|GL`>B~&&fSC4Z3`v<;r^XK3$V)FKLg;Nv`$fyF2vmVaJL!Z{;Zl) zmUH(ufoVXv-!9MrtaA620NgXysY=p-n0xj}-;-HJxKCGe&T{Uq3UmSCK2l&Fu*%(s z0dOx^Cy`_|#N4Cd?lHpsk6}IEmUH)#z!D(bKN08yR=N9I0Nl&gA%tWGV(whH2aLcU zP&0G>in}F{1%&$wfgB**7XonStph`{0b=f%r}&-}7=e#hvuHWL?{xx0fN(!jpacl_ z{s7!%>+pah8Jyp|?CsQ=Z62$I%INWVU;O~QZA80vu?-ZB@g!?lB9YDB02;jbFtdpslqy;he zV4dIhEF-c$l9mL#I;qEcQ{V+8bE$8m6z!D(b ze}92}vOXZ(zXsr5whk#I3lMYH!#!XG{;Hapu@!emAPWfh`vr1+9 zcMD7d!u>sg4j|lL1K^&qPDbbL4#eD*aL+Qr{Vp}oGT(II@5Xb_; zd$T|e5Z;>rcHV~Yfu~s6{T>(Ig5zt|Dzb?CZ3+wl!h4ZG2@u}10e0SYL7kO(`z7Cp zisPfysYjTf(uE1ET z`l3K11s?<0^9+ge_9VpIN5_3P8KHW;nu#^tZQo_#Jd&zjB+yF1a{%@{LyG>jX=d9`O8G&x9 zx&vL*B z_kL<-E?jXBPPHw_8sH-FJAqsZeg?puH^L)Hqzf_k=5QAnfj_He(QS6i;XYl>s^#2W6{w}EBL(UyI1GS$*odFES3}Hw`{V4dy#Hf_ z`ybEI^KChIFA0pLs-FlnQt&MR?r|f2-kyP&dsDcZjBtNI&BSukeW{vN%ei}0pq8pm6R4-)1OV<~5xw7D z2Ql~RaE~y;{V+90E$8m6z*wsK`ziLx8Y%cS0Qb0v-fu5J%)R3=z9&saxWB4qVmWtr z1SV3|`vqDlcpCusq)5>un;_;sC){mD;B(cSvYflu3rwe~X9;vt@FW248Ih_<3J`N= z!#&FgyqB7DmUFj#|AhB{sp?LF`4s#BfO|nCG09Gdxwk*s_oT-N{0TJ|E$8m70!yjt zEdu=%ycU3a*@>OEM85>F7wrQqQJ+<7N<-p)eIy{G8+ zU0?+M(@A>1E$8>WTVN4Yms^#3B2-H&5YXs^k zco_iquoF9PuZNg>eYi&$fuFACsO8)}Bruk$9xBjC!72dmaVPeEd%5cFdk^bFvj;_q{FLZAJv&pym_{s{%F(OdH?=yi}kA z2>0^uq$z=-C7Y8Fl9?mZ{i77Q8SBJoFo5+K~a0C3;sv@;Je z_u`{`Pb!RPeqPOLnwt`+8F2SI1?qrsUk$)LY!N?KZG@P+748v6xX)5^)N+2`HGwe$ zTqK?*&;W${F#z1-7V&dc9%AltxSNb<-haHFZ_By6FEC+%i^LrQEkL+$1>l~vNKTO* z5Oep|`ku5I(fqKQQ|zuMf_Yf0x@?I?pa1OPf~Nva_%k* zbPaHkI9y;J5blEjxECx^RAkR_?kaq5zTAW%v?gw-A#e40WK033FH9bJ{y2LZ;?ci8pPc54|R8e5zQmi zELzUpYXyc3aFIAbpacl_zn&#`SrPuo?>|Azy*BQg?-540FH>{Wa_$}z7*kP+GX)xeaGwmoJ+6q(Rb_~|H-@{( z2=}3CCYEz|PGCYsCGLBseXyA^WR?-`^VFQPoV$kwx+*GhqQE>L+)n}EUQncHlC=iaN(Q@uyBe3K}OFT%R4+!_) zj*)xWBUK`c5WDa7;T|voe;3Sq!Hp~KS%ItpE)t&-$N|FrJ^=4A^AN@@R^}Z%%=e?< z_)@isrty0o6&NzWMe#I&5+J-M0C_n5;j=&>^IaCk=?-=fy2Y1}&_FlK-Y?+pSCKzOeQ@E&vA1qmzj?pfpe&~$vU zT8U}gJ0dV)fD7-b0xbZCCIiO+?0JT895FHk(O!hR%?Ny;np387_nxEeqfZ;)BJoFo z4j|mW0NC>kiSzb6#N6xG_=S=19DS@s5E)wq)m>#zv@-%R z_dO4BcZCtnlhmxHxw1gbfV&SDr~|@%5CHeEML!#%ZyDjJUZQtYI z_k#^^k@&Vi0}$>n0C106#LwGP5WDZ=9^&`iWJL2?H51FZyD2bXfQ!UM0xdwe&j#S0 zw1}U#YY=lUJ=ooCMl_F5bINk=UMnzdfQ!Td0v$lO|82Gxp(K?JzzxhP&G5JCg<*)K-K^kiTld-$#Q^j-wnW>w@9MME{M4& z!d+lQ^BZawE$8m-0z(G4NPI}31PJ$a0NiCo_#?mn1Tpuzyx(_)5%@ectCn;3us}^k zB~BEm1H%0j0PbN$blzSIG54;6-95qx_ugubTF%|~9;xSBMJ0YC&;W${#{k^pis-yO z2Ql~NgWcU^g!@J{6U(`~Eij>?64wZ{0O7tIfO}FA|NSS3xrYvRcbgIJ7pOU9Id>l? zFs-5zPZsC^!u<#U?iodN-uCZj@H2pe+&#+(_g|l)=i74bUKHr6sKgHi<^kdUCII(> zB1MyIhuD2@I>_BUM&S3VxoA0ePY5ijsKo08`hal10)Ts2k*Z0CA?99tkh=$rz{jeY zdCiKuB#<@0MdA>F93b5L0C49ONi1^j5$^6D=O1vPbN^-GZ56-MAUsadt0yT=7;UbMu^1?qrszYu_X*dun{J`Q5;H3zzT zgc100YK~gY-D?EKyl9CB2{ZuV{@Xga$33Foe}b5M=W2I18PWVMnD>E}bN8&kgcmLG zDS;Ls-0uT$-;*A(^Y#S9++(ZV-DU)Sm6}tQbN4v{(_Xa1^94G9a6c1(d&VRB{U?aI zSFd*WEF+o^R&&mB?#>8wy=aNQJlQ_kJRsb^0^nZoNRi0x5OeQ1z}-DY;4i7UXgPOJ z3oLoj67Lb{1H%1g0PbavREdm3%ze%Q?jA4#zgW%8_=>wOkTt+X;u!)tK)4?du=6&A zaeEe6z>!$0mAzOfStEpFvm)JvA=sOj&D?}Y8v;p z1!@Mk@LnTO2ZZ->fStEp&|qcW(f!>!;`ns6Mor`1s=$~5F1$wyGyvf}3}EMN7vx!) zclG}6Z92aHiF%$*Lk&$|U;@D3$G{x|ElW7K6=2UZB+lDAAli%l+}&nG^TTRRS<~HH z1f~JuzDl5D2`(y^0PJ~&GYUNY%*{#O`~0&fNn>1g}*y zb15NrHwChQa9<>lvji8FvjMpCkiG84>)3np2i@_jZA4K)637(6Iy;mFobwXN=@Unh?A1 zZTq-;mJz}8)SR=NyN3n3fN-BEFmDMiDo+96UNBM=Sqm|DZ69~{7!llC%|*+(``*Xd z7Ayh6{TqS4CAg^k7=U}(NL6GGV(xqPcK3i0!5h`gyq281+X7iYxUUh&S%Qnoe|z@Bd!6;+_@A0>b?%ft)3{sJsuredk3IBNGsF?^@;eU0?)$m6}D%`F-2>Avlu& z!u@=Kl1!>R6M(zy#Ln9#h`Befa(9Ih_`zycE$8lxKn)P?zdYLhW_34NfC-6U(`~ zE-(QI_cH`qZnDbb0k|if*m-*m#N3oLOpY&92I zg!^cLrBwA;fqn`e0>Hft;a@u@$w17#G2`w5Bk=DJ*Ymw;#oZIg0^+`J6Ue3D%K+SY zNYUNX5Oc2ycYzV^cc@up5%;}WU?^3+QlONAmjG~=Ays$RA?Dus-;DO8!U*?KYF1f< zyC_ggRR;^yQ}DoHat}iici#gs_ei)$7~%dYnD>D!!rc`ZOI2SKXr$m{0PcI-I_w(~ z|DFT?9TM&)Bk=3hOf2WmNkd>FRlP`{m4fHEdot}D4YB*4dBFFi%?S6S)SOCls|BW0 z)xRHQpRALDKLEJz8SD7Dst3`YgnO0|?pxHHvz*`ej6gS4-5@ZZg6jde7p&vws?88{ zXTsfMg!=+D7cJ-R4FXH4>J))~3Z4qUy=)ynR}~@V?)=C1WWWgberjeeTX7H8+7@I1 z@qGVIAeVxl0dVK7Q;EXI#o#;5OWXq z#PiJv_vva@E$8m4KrK}rDNs+rVF292)=4B;4KeprxJMY_{>LNrd|S@lO9Er5>L&t? z6nqPSd)zu~9FiG`x$EI>G6H`<&BSun-d@}+c zujZ8H+`Ud5Ga-IjCrf~HBf?or0k6VYGL$UxdcPrdYM&PfinOM%<9f65d^?rd?3f>05 zJ!u_UNH#&tT@H7f5%^p+r!430^#aqW>RAGv6g&xld&W9E;7AG(bN2?mC$o&ed#O2R zId?A~Vq4HnRd))^r{D(w+zZy>0Y|bEV(ui|Jx1V9sJUo4cW)I~N>y(W=%?Vd0Nl&g z;Q>c73Nd#n+yh47=c<{xV#Qq%$O7W|eu6+Q1rG<{&Rd5E97z^p?uGk&PYR5{e|or{ zZ_D|8?-m#Wg!_8}r4)P(fV*s+Os0yY12K2=K6h6bf#0QO)pG7m1ZseAzeb>*f|mh! zk2&muVOHiHx-Xt>$H%HQ$|AfafiXaM4-sgjU>^YQF~?ot-$P(8{^{PPv3QVV(iv&6;I2*uw%o!KdS(&#K z-dV>-sWrzUyhVX-sySF-9>6~U`$zJ?8vV0dfW&$G9*DUY{^5JkV?^+$VBQC^2zOUt z2@reyMS(sb+#dtj^9+ge_9VpI&2SGG;eNfEndXYSA&>=x`$YmdK)9a+u;&?4^iOg$ z#N5Sj7Z~Awl$u2rao?*2h5+II_e1TIl>p)X1HhhVNY&jvh`HzQjpvyW?pxHXvIzH# zKn)P?8wBcra9;4*7I#SciZ<0cpnG|_fG`+fN*~cfP2|Ge%_ven0xkbz9$1lxIds~=JhM? zmOvH|?kfaxfN)<3z@4{FQIZW1bB~6*zzFy8Y8EZ$_q|SF2oUZ^3X}lh-XDOwY@Mnk zgS@+Q;jS>keHWPb-IjCrf zO#)+paGx*G0EGJt0PbdNh`EQu-D8CNVQMZ~&fQsoB|y0Uevo~#J|Nt`2H;+{ zj-R&|Am$$YC7y3axWB4q=E@a!M<5Fb_xlBMfN;MJfIDxUOwlC%eS!SmQ{gT!0-vj9 z(Qjj1Y;eM7t2@vilxw~wgOw}X>h~0NR+!aRPz0|B)&fUxW44vP92ZZ}hfjS`E zKLBvw!`AWh_D+bom;W5kHzVAiP;=CB?%pae1_<|C1R8*FzZQUd+&ZL?j6%%a4tJ9g z__=B(mUDMSU;+^CCkV6v;eI#(_oQ`zNU{)fSHsuDX_x}{nHzVBdQghC7?oI@{fN;M?U>*?emjQ4uSceB3$$E&nC&S%i z1b(`jiX@a6=j0pY!mAAa)=5D?zG0e0SYK~Fy$=eY>)tm9kMnqv{(8G)_=F8&GJ zATXbT>jCyWL*l%>8KS-TgYQL;5zPzKT(l;)1lS<3l&Ve<=%?VR0DGPx97n8Egy^{l z_ka<2KQ%L(SKI@BhR*vx16(A2Cy-0Q&j9v3Ly8{hLd;zbcYzVj&#GCprr-BAfuU6O zc7aj~t_0Zg45_-i0Wo*)_r50;Mz~K`vuaIuR|RUR>PUfl3JwF{9)=|DUJWsK67CU3 zxc|YA%z3|UO?NK|jHRld2sBdgEdcIuBRoe)W+3J+g}ccJ`~fu+%elKHFp;XR5NM^~ zLICc`w6g(X?#A7|Cv8T!k5_Xl&8-udPF0T-=%iqO0PYzhe%>DFXXE^AsPFDsM!4?+ z^Zw6re%}iM-Bfk6zN6ct(0&&K)LP`C?> zXnr5e`#;P1eeV<)N>!f`D5cb}3{cN2728m~| z$q4*aH51FZyCX1>s@^ZqO2OLzxFX&fV(;rc>3k1Ue~r z5&-v%h|b#uh`DEe8_zc*+*Tdar1b%~> zQ$gQZ&x7ZUjB7F-;BVIR&&mB?#>HzQ`LX@7Z=}mO~Ic5 zxEGw*d3!g+-0g7p7=gc|=Az}?y+dFrRefBbpMrM-a4$Qt_uC1?+|_Uo7=bTVGxNq3 z_lQ8&02hf<1#$rX#cSXg{(w6ViF4Hu#N7S6;`wGo@IW<-rt#2*Dp zfN=i;fV&KdbJaY=+>_z1FrxW+Fz-uPgnLS$W`K*tI|b^1a9<6;Jq#&&WFy4fm2i(R zqInjW^9hS^*9684aFKYLKm!o&#{h7TL#iIhL(ILn=zG#+MDzX!%$=|ZcVA$_02hfn z1X_S_-wMDz2}wM%17hxW&)scCG(W87l&O5bw+Kud;39FAKnD=+O8~fMECM1j0x|cx zMR(6K0-vPjoaNkI7U&w_B5}CDJRsZ$0dOy*ojw0?_pUqL-D5=aw_x6Pr@37MO9tHi zZGk=@++P6TUbaY9k|~JY_vSm@Jz#|US~azCe&0=jY(kNX#6<$RWW{|p0Cye|=c*dS z+>;CLE-<2bgqlUmxqGd^kO3|d2MCms75BgP$X$lSxvCE__x!KiU13D?M_`_B%ei|_ zpk{!J#ODO+$%^|U0PcGjk|`>(1!C^;aE~yed6}A{mUH)*z?cCp5@!lDk`?#K0Nmq{ zOjVIG#N35&HyP19RL#V4?#>BJ7~mpt-@omXwUQP0-2mK^kPQEi$S#Pv=YHvX(suj} zHK#1+?(G882Dm7GNT8FfxUU1?o`D!6(uA115$;(=;QwRqeB67l=l}mcr{(iTSR739 z@y5z1S&CK;-kr1RolcpQRt~M&$4aAH&p z!*FPE_zacr?S6ke>~&q=>-zo!-^=T|bb8+&_xr8ap66wI?)BP_liZw8F5DdklL8zh zUS}{>UvlpQz&#C7iOB8{xf|0x!w9^yo3qM=d&fVt1akr$Bz|QuUte?lwlaANjklZ{@;0YtZhZ6ZaX^0L#LCI{S-5Wm z;2w*J_Ekd=o%iTZV@<{xfzNYuLb-7F8caq}i4zT`0L#LC6ae>hM6|D336Z-a-7}29 zd$>8PT)0;l%tcX&oebsy%fkKV|FwG|BL4m-h}?@miSu4$MDuPi_XE#ga*rD{2yl?N z-k=Of_f-JSyD=i#SM@{W9!_@?Bk&n+R+L+Au-2eCib@<|&;m&JK>*yX5%KpwLFBHb zyNwaeUEQoIx7?s)&>lr49_E2E_X7dx{uuywM?@+_w)&Yfd(D|xlTJq9o89bEZn?pT zL3b3DxZI!zknZyVxO*c~CDIGgdH;J4(Z>jUyqk69mK)Rz`lG1C0R{trbngkMU35?F zqQOBRiDv?&J~&T%DDdMvE#~r;X}NQMG#C-!p!lG{C?M6}1=KEjymryp7?8v*0I7ff z<5-dLz}LArA=7f_E;E=E;GlS(!4x3Xy?`|reTy5g(?Aj@0;Ik?)iZ%>F3!rd+_?h` z<^(t>?rAU&NcA%TYc9Hvd$0>Y5)A;USEhO~@E>>h+P(nIa_1g2Xb|9__+5iCfPla) z0A1UV+;K$soNEPn7L9+k{i8Bpa0O@`kK-V@T_ugwDau56{+-;0# zzS7OARN-zmXcyoh@eG3+Al=IWy0#&eh%ES-F!wam-N}gNPr&TGr3&|yLAL-0iLV>< z0Mh+=fUa#wH6m9)blx-f$9eZLqWL~I>r#b#y+OYK2Z^^B3;@#odI0W0NIfEL5V^;` z8}1=SG+*H6uvFpJGjD#@g8&DKfAPSV``&=)FC?Wt=r7o;N7)$}u{Z#<&X-B;G zj%V5IGt)i82=_&9&MFt~K7%0{S^D6T zdv}8d0S*#RHYfwq{STf1bKZ@Pc<(*$XTt0=)7`{~=J&ze8&odNd%~dEMJK*&&;m&J zX8^cc9r51#Vu;SWlw0RZ=OM6~ywfXH1-_Y5QO4Q|dV7w#d0xhN`e zp}{;L-R}nAUWkbH-rW$n7rqy3vd9R0q?@G=ExA`3Gzf5z*vp^{NcV04+>H^@-n$Hu zdnnyajKGiZc$e#2xj64xgXSnIai2j8Al*vS(z_V=eQuR5#L-~QdV>Z) z?%dyEP*#G2|Iq6Jx}G7q-)@6=ExsFT(Zq=03*4-z8SX}dWYYA~t<2bK2&aF2J_kwnI3w_} z1lf-$7w6q>FbPQaeg;!Ya8P+60Qa;=T(gZ3o%hV$u_iN&2>$&ya?dIk?sFBF|E5V`B=USve@({7dqsD=Atg9bpl&oL+~!9nF60NjltWs^>b+%4&D zVnpy@H!I48yVal>knZOjv?#$r<*5MNts-3DkVk(V?&+ymlQu@+UxT^Vtz5Xb8MFh^ zeTP9!2@Wb>1>o)wshA8ybl!ba;qGKa@FF+6lnZyCK{p`XZ#U>sf`iJN0l0fbswV9a zxhqrQ?qfvoWp37$3-|5@{eW~o*2P6eK6Ozat#I( z1|xuUf7xJE2@Wcs0dU@9Cf;vf43WDx-Q$c1zRS%C7&7~y`7o3qL_7?cg>0O|hUU+FEIx5Hd^KSqTm*9|quVH1U4B4PmOV}yHWH>=8pd&e)e1nq!y|H`0dlPf;};O;P~NHPJDd;HE=lTJq9 z8{F(tF5G%{%(Hkvx-T^7vB{Np19116R3+(#$laChK1Sdp-K;AY?v)1pfOPL=Fkq7_ zy8&OlW|7i zE8Uz>F5LYFlYnI~5@#7qg-PIK0Pg7^+HZG226~L-owqRa^YTK&<;rV zP6o9C{F$e;xI5G#gk;vwZTY6J{TkE zhv>YA)7{4ie1@BK<-)zzpubQZVK7jDg8;Y()uDu>1tNDX-9wDPySh27T)0aHBZcZ= z9-MMNumC>;;2u+_*$wGCx!Bhdx2jHGohY!X` z;+Zg?m!*4#5%_pFXO#NfA$_2OwzSQ>rR54v~9)BG#md5%?N6E6T-r4;nNVst*{n6kr1YcdI(3 zx+H5Mau24vjS=_|H>=8pdw+xWLiHkpS^=I8z}=w^Zyk~nMDDhUIPXqI;NS6Bl=}zd_S1|w{qd0 zG?*+@HyKP7;93CgX?3{3kqkm~-ZQtyn#?c)pY7(Xa^dbVm@8Dr8O#^pPyp@)b-2Kh z><^KJT7cgJa5t*M1&(CK&sN!gr@M&}_*-sP zl#BBoGiWYUpEYPHz()bx8)ywdomV>NZQqRbXbXIrTUB19x6`1#&>Ut^E5NG&+#Bc! zK?|?E7U}H_ysKMXyhv}!pu5mK%mY*I0~X+C0PYR+hG5#yQ~5fi^!5e*hFf)Bq<7Sy zztDW#V4wgW0&s6&Fa&+P@^x`rtiw>?liV8SMS437MheaA3`POV3Gr954?x$mI{XDg zc87Q^(ml=yytA7V%EjN^4jznh{|}JY#jgye0O|e#K-aT6FeDQYxm(gb!w7tXo3qM= zThDX(+7dvzFEp43r2E|fUC-)JLedS9d-~Q`lSM}0Bi$@rvgBTA&;UsHUIt}Ax_1NU zdR8YshLs_5_ocgu5$;EL6v}=?xj64xgJwXw?=xrtr2BRN?pAf;W7uYh+?8~3+6B4ho5=!d0D#q7=a%GbN^4daBnr}2c-K}g8@LgZv@~TRHsyzWC)`3Zc6tM zBk*}{4l5V#UV{-px=%D11*H2Z0PZn$NFiAXk$d7Bu_oh;z2GAv*8I zbT2XjpW$Zdqf73!1`U98A7M}ir28NM?nZUuV^|AB?(y+hlO{&EcXhL(T%32wpc#Au{c21xh$;qFkU)P;eL!qpAk{W1d!ef3`POz-3ZYA?GSYH z%4?C{@xVvAHNlJYt~8hgq<1faDL{I61L*#C2zKF>yj#8&>o61euRNmUn&w4%=M3fm z>HVR>JRrT_2I&5F2)6J_-mdg62L7~LrAwE*7aKGPaPT*9jzPHq?*Qm}hLj`H3GrH_ zyNMCagWasC8Gm=J2F-=)`35Zocq%~GGbHbCKkDa@To*UTdbBaZ{cAAy|I`fkHiPy; zb%#N%0AB^*?toO{yoVt=@6L30GQxe4n_X&#yU(DzP`%xtrvPsT=z4}!!`%*%yOi!e zMz~+*W?jv2?{3gvsGe*vP=J3-+dT-WhkG6(_vn^blOaa9zYpgApPJ#GFc>LRUp5#m zz-IuQ_m~LR5t54`a(ASAoDuk4ZcZo{?rww0LiHwtsRFD5;GP!Yl0?!9k$dr`IPV!o z;ODqGt6aFt26Kh#fA7;0B|peL}wdB?%{MVGQ#~$FrPmTF6A~GGzf5z_>@7p z03Qb6ZWQ4HC(;LzyO!=IM&Og&tSA@f&|%PAs9tB#QhR4wU-$h|V%eT;A)>1JKIaIZAzFI0OO3>08D0PaDNx+P_Z+;d-v^B!V^ z`;mKfn#0P4d)8p2P~B%RT7cUDxW`Pq-`)(7`{HzuGs1nPn-j`~yWe24P@QEkRe+NL zxTj6L-|m3Oy??rA7~ww9%~|Eb-C{6TsGe;wUw|h7a4(p6zrAB?xE~ygHCbeY`ynvb z_s5ppTMZfnI7r-TP%glY0NjlxzGpN9(Ruf$yNMCa^W3Z`7w6q;&|IiaG-xTnQ2^Yn zCcbC15+e8B>270$dk;6O%7uG{L3^Rv$)Hw%KY!Qm4wH%`vkolM(oCF!x84 z3-`D|ccHr8pr-&=0XXkold2^B5V_AycON708E)2<3-?-s{z7$x!9W2H0^lArsY}uV zk-ItFLyW+?x;d;|xJw2jh3esZ^p=el;Aa5bV?ngv-U^X>>z86p#u=a}LjkxK9H~gMKSb`e>0V?6ex949%a`1{7&HiQkoYGL2D$$SNcZmn zxEmd*Dl+3|hx|^*bT=^qf6L8^a&g{c2F)%y@mYfwK)OE)z}@OdU6OMlI`7Rl#+tM- z0mP2BUy<|2F{lSVZ)`Y8QyyYtlW=2>jPcU*F1wd(L1o zic0*@U<#1#Zv${oM?~+d;+ZGEBVshxWQGy=6K>8b*I+PUFc(E7-eWKiNcX7#+zSz@ z5IF{-^S(OWi;TdpakF&Al6!B11_2Hd&ow9m()}a=?#75zi7firA-^Lc-A#L?r1#AL z-QNyDjaOca^mYc`*R3vT!do%u7U019G=m;MdLRF`yaT-1Mk-j1_0^(JV5uiLomcEd3St1)?p~{d2S6$6W(5f5djXoCmM_bmIpWrz~9}N zBmNk+65_Q;_c$Y(d$>8FT)0;lOuFdAP6kte<>CJGx9pyF#2>?EA#%5;dxjD2yTRNm zQ7+u$26HYtalOGjAl+91bUi!bk74}~xwn5V)?|?p?lashUAg35YtSITLE;F5G9cXt z0dze(;*Vi15V@~NcM~I;ySiCXF3!7T(CnfU4^QYVYXPMDX8_!-j`(BPR*2jy)7{1h z_sworl?(TXLA#4iTy9VUr2BjT?hZ#PlJr94o*RiZ>0|^x-pwxM!d)}ycF~Cg40-_R z-V=bk*O96uD8dl?(Tv!HA1ae86B7knRlt++z{Z$FQ{!xgWeP)?}O!_z*WIlneL%29r@#;zb5i zfOJ0{fO|S3`WRM%$lagr8AjmW@tBY6Te)!Sc_q(pqNv1O2J?V)e+__pAtL%1HUg1* z?{qIR0$<{0>8d67xdsga93)ORCn4BPH!dwgD&?lwl?`@!4~R4&|;2JKN);wFO{Al=sjaCbzc zLSzu4^KMReCnNCLZgwdb?jD2gC@OKBK@T9^hXQc-Mx;t)e~8>$uZcD3V+4Mln|0;F zy^BGA6qWcV5AXQicR;#-577PX5IpE-dps*k?@-`7-5QoAyju)L1UT?sXD|v#?_~hp z-wr`Pue=uN9S?klTNBcRcdfys00-V945k3-JqV!t+acJSSMp8|$2!aeex_Tq(uB9c zU`~Jo?_YRe$2~wmdVdMf{p}EJ^Yc8ei{bDt2L7g7rH|t%TeI1q0kAB*pE4*b5#YlB zUC$6I`cKDmNUn?Fa5pgmpX6pm&2V=ZGy~H8I)fG^IH>Fs?p8=+kli7^EF#4h@JKi7YKD8IK|dhfdl?KU!9is=0PaCZJxCcM_vB|{ zO@Th$3)0LHbdlIpYCx+;49snP%hm429tnv zpJgzm1P7Iq0l23{xFjJR5V=>RdxjDCKsRTV3wMjb93b7#Hkem}gUS;CxEDm?{|`I- z%#F|2KOJkb$cW%W;JfOjA#&l~YR~{k_pJtHB{-uRLFLb1 zx4T26VloSnd&||aCY_83-VNsdpK{?IH|PeW`+9>OB{-;D1>n4UMXDzK5V_Z;yN?mU zGu*5z7j8XQKnV^i2LW&oiquV7AaXaPdx#OiUELg3F5D%95kR^h{+j+{ zqe^g4`56HBn2Gn>TOo39{#2~VI3t2LyE&m;xJL{o0qMTnV9F*}&IjP0Ht~MD7b5pD z>7HSP`*=5Jl?!*xU=EP(0}SSEa%E2d?gbO?w^u;qe)N-Z-iwTIKX$X;;?gIU+}jNr z0O@|fpsWN3m3siV8%@059*4+1lI|u(1g~+kqFkK!pg}Vr-5)S$vB{MU0Nkx6-fypk z$h{`rZH#aq;$~I3aPM!>4oLTl3~DyH@^k?14wH%`@$8V#%Ru_wTAlm}{@oT|-^zu1 z#-JOJ?z;?nl;EK9H305jld8xFMCW}qNbWxV1YhE2UAb_dYtRo!_vr=$N^np)0f2kZ zq%Kl}$gQ_l?jimJ@9XBUa^bERi~!R8G=otkIH){+lig!MwBOzik$XGWhuq_g!1sf> z|EFBICk-Y6>AuNeDomamEfTAdjRgnAlh%wK;+g(6s<`U zBk;G}tSA@fJ!a4hNcU$ATEe8tM*+B7gJ{2fE=2B&)7{1hyw1(4a^XJ4pdFCz)dsZ! zyaIr`142cT3PkSx)7{Ak{6sgqc#-bKuV@Ln3)OE8dJ6C(0PbE$?yDvtazA)gtVtgu z++TFF&Wm(kZO~t+K4LIXfcFA$4?-$&-aQbxwM+N)%?S4!-5lmcx~m2wh3e%7qXqbH z0PZnJHQc*Ejy#hre$m$3WyhH{FYjz^`$$#0QLY>p37l&jFCv$#V_L z1$YtwccVJ7uUhnTGWOr;ZeoP{7hv`$%EdJ}ZO~k({>PxD0AB*&ZdE7tRaZlF-dnGT zHECmn`-5&)l?(SKgZ4soib1UaM+0zo6rCzW?oH|LWQ6-AZgv&9CWG!m^=B#qz9yORNR3A5(FTjTYxEIvnLPfF(qVvA(vRIQv zM&P%)Sz;rX?llJ5+2!^9YJ+kCUJAh7s16r6k|v1U-RW*(1YYK5MY%Zd1)h2FvoH(Q z&kb4%@I3(TR&}_*k;L;o_TTAlV+8)3n^on)eT6}Lp}N4JR)CG+?ofvkk}inOd&|dS zO*$EY4|lUmxp21`bQh`@8}t<5Spd$vR~;^JBn=R`*QUFV5%`Zh;^O*NF5C|q^cSk{ z8VnTR769%+b-2Khj6&pYNcRvU@W@Zd&*$0P<`EC zz5t&G;9gLND;3EV5S{mKrk+ebN z9!Yl-Bk&8{tSA@f-DuEUsQ$%6Fz$C3;7}AkWfZYJx$7~J3F1(WW z>h!h+{wt5TxL?AH^v)Ty7n&a$)BtRnbcWvs=z4~fgN#AE7U}L}MDP=CcJU(J0|woI zT#NS@^Z?R*DnQpWgzbnV$3W!X{*hRZK1SfzxLM~#y7xBd2c-MC1_OX}KMA1g8Bz)N zqMv2)d0D!L7~%c}nEQdeNcXhC2q4}6V=xLx_m=>=o*~t6Uk%ZDuT1wiBitW!bAlJ? z-efQdNcSlQQ-E|I4ZuAOsfW7?kz3yhscU405$>0`Im?T5HyO+U(*0is^MG{!ZN%;c zb;uxzXK;L8mhMGH;Cl&jzl58z>AuaN0g&!57?c6&{$#it)d3+HfatvUPj?d|@H^eC zD8~f_=rU*qr27p9Er4{t7QmXcs?*@k-VnJTyeQVBjS;G6x>;2&+zkfpfOP+b2UYy+ zOF+7R3BcV^bfzJ4_ous)5$5va_^n)K1R6T z>SkTJIPWzE{eX18+F$^X?w0~^52{nKqzNMTwt-laAx5~DxjC#{xEHR~5{v-S{d0p+ zK)SyNz&)l;)sovFa-W;-aYnd52j=rh<-&c1!6YEv7Z^+dmJ_0~5rBJIow`XEMDFHv z&oCl*xSO-eg}cpQ4v_8_8_WZihx=Io+zaXuLec<{d+UXIs<4h+dCMD9)LZej%fn41;l!d*9L1}uY-IMbj7knXnuaJNEozr6+` z_wMO#V?^_nZdR2Gce6n|U>S_WGYo2gbT0?s?trL7WPyK`TR$yH?@u~;4gLv#aKD>> zrhCeun}4SJ>jpjiGu@x(Uvc;HNA9<;;9uolpYA?h<9;81u;1pN>0WQp&p*@s7J~u) zneNx~ueb;Kqg1L$qBW3vMY@N04SoTC@cx^Brn}K#gny>{U;L$WzZ;P5Kj|vq`i7KB zRY@L%$bH)fV@<{xf$wy4Lb+J)Ee4Z-bYEwnrAqf@0Nm5+aDgMy|E=ZjPWKEW@M&(& zDi`ifgE>ID4>Ontr2AC>+zaZELedP8dzW-CG6Fxv&C)fb!u=@k*STK;NcSHM%7AqL zRPXt@zSRLD(FY~DxAe!FG%*5y#m$Ox;ns&vKA!|EgORw@paqcb_XBXZsuTO|cy7k$ zW$A8XMDtiTtICDD-Jl(?3`Sx+FAR5wI<$y1LUi5@>F#6%{`;qOnqA6;Tbn)Z z2LhJCNc`BK2axXX066bnbz;B01tRz63t~<5yqxBz-K;AY?u!lj0n1<{&M_DOr28EJ z+=J?ryx;DG$bC$@hZxa3*v(<(!rf{x0$2tk@qB|(K)Rm_z&)l;$@}d``8-VSN6(M* z9%n@J*I?d%D;Mr<29tngFcNndOaapURe<)}5XSnfR^HL{&IG>1tyx~A_gsTHz_Re3 zZZHo>?+F0yw;_1f@XBkE-o?PLbgOjjlDFAF@8#3`41+RYdHjc#1GL|U;N8Lf7I{nO z$2v3xehADxKwjkeZZ*(XaHRKEgBHN@@ZJc}ej9@KYF^2^`MmJzV-@&=ZdG}a-c1JW z0vsexF{l;bXaIkA9T5IX6sbbI7U}L}1b&H|`rw)FCWG!m^*6+p{zCNygMk8k5}@lDQlW-KH^1dxdv>^o7=hpE=CGRK z?lKrDRBtdCEx>C5x}G7`aPJL~yH*ePI3wK8baO(@a5orC3UHA43lB`V|5t!t>In+( zuOan_=uwW``V}AEZ!@C#4L4`i47VOI@%2)L>f;9U1^5sE_ksx55t2<1xrfrd$O!ya zH%si)(!Iu@L4bqAs}0HpcqssPqsVecnjmsl)2;m=&1G&@l#BD$<5J#V7pk8dv=rcb z0Nkx24VK&nkz2p$M{Cl?2>0jQtST37J@ex0OA6Hm2DJig1mNx{I$aRC2h-ij2>0P` zb``lcgYH80VuPLnJPUxkS0wh^4G_88(%r`h_aAvw#PzLQocDtU{e|kg1_K4S1%P`{ zq+*G_Ktyi+`Y)}?5F^|lb8}d^aO=xh*l!oAGYv)y@HPPMF_EezYanv#m#N7;&ItD_ z-JDP^+|34)h3XjwQw3NKz&$New?tp+BzJ4NXBgrB37C5%%7t5Be8u%$sJ?D6Ux3d8 za4(p6zkLNnZvFm0o%bRm-0yR=8pdy7GPp}NkXR)EU@ zxI0X|->yUC)~^HAnshS4eVUv4S@8Jw$H(5@287jKIgbIiXy*+YKfQ)qVz31$ZF<_q0h}l17N!z3HA|1pYgZ zbGW{h3%9-zf$O_a{n%i>0N(-NUI?Q7_7;fTP3c}_1pc&}rOz$7FE(fp;2?31LAe0$ z0N`#6qWyL!MDB^RV@-5F2Yj%b73Jc*TMe2E)$zx}A6+we0k(%r@g z{A)1x1Cxv#nyB6m668vyPFNHrqc zAaWm*?nOp4zv*U)TdL{Sb16Qb6yPB7DT6W~-5&kL`|>D~u`yHybok=-G355GIyZH&M>yIEB(+&g%_!~R5ogT${4YJhbA z0D!wgk=R%1#}&xka!R;68PUAK%`WA_t)HL3=aT{)BrY`Q0i^rg0NlNbG&s@?k$d)C z;qGHZ^GG-A%7uHSLB9Y8iM{#+ic}rhaY?ub)`feK5zU9dchyTgG)ebX zg9ZT(61N(Z0qMRGfV)wVx+C$-iF>DaQ5fq7&B}^Z?R*6##dyBk}!D5V@Pv-Ny*`8E)2<3-?-seixlM!e9W9 z?t=ih2OaUgss$qV!b!0vLyU0m>gKR=;Vv1Bxah>gJfz`%ARyg81K=KW#QUnPevVVT zuNr5B`(`&Mlnb|h>LAZ2U3B7dgDF6|&j;Y1cBCRnFGT0vlb5^-<*9_)d zbm9Pmc|f}N1mIq9q$6XkS%=$UWX0YtqRG{5u}OaD6LRHqg)6<@%1I5_cK& z0Mh+60Pfz1`1_wAa(AV>j}gsF+^j2CHaORyKZ;76ZZH5y_Xz;pgAvibss@p}obDk; z;CGQ21*H4&0lUW{;_rWg$USyutjRbdn)idbAE;c}VA5bxfP=(M z22+4^Ukl*8rz27!G6<2oGu<x+C|+mK0!Z&=0PbV9hM=EUUW@d$1wO;Asx;wUYtSyhf%gc58X&y~0l1IZ5rVyW zC2vD|^|d3kp6OPXG~sP9=oa80@s|&29eM!i{Uw0=n7tv`#w&Tp&WLsB3;a#D>e7UF zvq8TA2i{K^3;@_P349o!>ls2tqz~e?NcRvU@JVhCOBL=8gAoA^60b8D1*Cf)0PZme z+Yv=}hsa$@_c$Z)&TdXf7497$)SEvkz(L|y22+4^{{W!t8B&SJ1Vrx9^|2;1jA-8A z=B!lV9x|8{;2?3K!8{<{?*{04hEyXG&xH6nS?OM6MDs{DOE(e<_ez5X0S*#-8I%F( z-Ywkv;-`8<$`GCR;^}eTO^j$h(yt|`NEL4ViYne;3viIQ&!7d6?%M&JcdH^?Hbgc< zOR*AMFZvA0wI%fw>>3T)4Ly^b2s1xYb|)knS4+xCe{Q z5Jcxa_>OQ7F`{{%o5Mw}*I+~-+$S200@8gH0QZ<8T;NDnLga3HN4Uosf%kB8Lb*8a z6$X<693*x!m;$8x&*$4ctw_ZY{f0@oXHN_F3?rI%gL!|gT)6ccFS%bLz(L}AgL%O6 z09OGx?*&Dwj`Tz19!U2hBbsM`@2Z!+KrY;C4fK;obdWg0pbS_Z;2;3*Mn&q5v_Rx; zO?ML`n!CDLQ7+ti*2DEJz(L~S^8{J|%LDujfV{2e=HG^&!ojAat2axVP0l0e|iSK`c z$la3eK1R47tLrVUD;MtV2K_EN@qobqAl>%>a1T1-{q{IS?&-J3nhY_*eT|#L%7uH- zV8lfyK435kNcRQ+?lDKa-(CxmyD#12jBp>~=7e(L-rr!-MJHZlFa=2W(*d}r9jQp7 z%SG-=x@Q=Hf5&4Iu5abSt=|dH_3fe)cNxqB()~36?gdAxl8iv)o;o$wWRVg05;sem zm)z$X=&O-+kT~6-3`qA00Njm^)D@{go+8i1yp@9EZkMgC0P-4-I#3MEw0v5Uoj1y89T>e4d+i<>I_|G3bw?69433 z2jBk=NcZmnocCZvwBMfb^AJ7QQy3?`$f#A<^nK)PQ6z&#z23Xuv#?yhvtFakf(%~|Eby~wi- zuJ0%+@mqs=K)Qbfz`YQWDv?Qu+~ssHG6H|m&C(Z_+*cbk2yl@2h(Q^U?)L(;--a+= z&ntPioD%Dy?_B^N>sCdYIOleQW&sWo`x&$V()&Vy_S+#?!7HytdfNg&#v=@_YiYu} z-Jo571MdR{H9&gr0cgJ+g4=i{?=I=>4E#B_x}*v36$ae`9C$A<=mDg6BS8D@5OniO z-tm)T9r^+v=~i8u{P=Is4_Fr7y$lAF2(TMK*E57u(SN!O@mi#Nh!OY^9$j$%Pt9=8 z8jJwaeV@Uo5*$=+2k3f+P!ZV-k-MDkaYo=P-JDP}-2DcVfOMZ_Fr@?sm6HLwo*|VW z9T2(4PKq^|VMOpiH)quhcZ>$`b&(o*~sBJN%4-&rQ?4$cW%WVD=+l zA{6ee2Kw%ybl+-FR)T}djR4$@kb00Ih|ar|?j}YA&vUb)X1IF|ngQuP(V#^M4k||h zaJPz(fvkkcJ=zs((#8n9hnrR9!o9+v9gyyw3~EYnQ28@YBye|#EH|0;GYUQj=?b@g zhAhFm!Q3BFF5G$s!~GFJy016rQG$cYRRG+*A`K?}5S{npiQ(>JMDPqZ>&k_DtwBE^ z-A5PxQ};pR=GIu zn!y|(-3J)VE5Si!PXO)(k*diGh}=WzUSve@F&>L>eSeu;xVIZL0Mh+{L0Jh7D)#_z zH;U9v#vyW7)2;6}BzTRR73IP`XrP};n(hx6v?#$rWdi_rtBLp9Yaw#azctpRjS;~^ z+|)Nfrh9*bc0jscWKgrom8S!6cbIs;U4qCxnC?zSxPQl^3$AbF;=E@Jx&i6F%b>?5 zSH1?o-D~3g_6S7owsiL~!hMOGb>+f+u0cN_-KQH2*yPFy0NjHn-f!0+a?h@fH5p=r zdtWz)l?!*pU<8ovrx}de;{En^KcnFDvUHC#!hJuO`+v%Xd(vPMknWod zrfhQMS^(~8lZqsR5S@2xx@Q=H&vtWGxp4Ow%mLDUoWZ26{K{+62+26~LUgu_2xkiIy4B7$dUTsiQf`iH{!rc)>`|S!u z=RJKwtVt&$@DttaQm)Zpk>?G({|2P{w+1~*a8UUXfb;GRqW$)ypHc96S-SfefxqZx zUAabss}1@A>HdhpfD#;3-V4Az7)1N+9*EAnlI|f!;5WKCtX!i()nEjW?w1>kD#1bJ zzX7<%f@r_J3q2T{-MEC0lp2uJq^iy)fhzX-gM6} z!u<(1XL*tC0fV_h^&W%y0-OrKy#Q&9x8xXz+)e3TWQ6-QZkE2X<%2&sg7u{Yckov|iOjBx(~%>6)K%v^#csjy~&`xP@Q5>E5OkJ+#Qg5xT_Gk8`Is%2=`0e?BYebn+&=O)qff2$1LO< z^0zbX?o|hbWDX+t_;Il&eT=~Og1KL!T%*Bl2K|NV3kCxP_#}Yy9#kjxRRa*YyV5J0{?1$Zq0_n11dui6_TcRAhTjBr2G%?ahg-C!_TsQ$7+Z`o7< zehI)mtxoK#rXg~Vy*buoh7s;>xH+p_xJM1<3f0FA<_qv40PcmNvk4-1XSx>|;eM-| zrLQiz*BCSa@~`gI2IT_06o9)?or)z*5V=d~ZeoOcnVb3{A$i^lXJ`qU3)RmJS_<$z z0Pa?Gs+Qaak$d#mSd%tJxIgD+Rk>J`D-7BT)ddE%0&E1}?og+0Nf$)!j&yf2!hN`# zUCM>K&7iwbz1X0q0M7#8?p23{L(%||d-0e!?>-!&L0z%2mW zgX(aBBZ=oBjXMvgdx#PEV{Q&B7w)>jNTE8@V6*^l3-_2hT;NF7Ky==c!`;RRe5adL<-)zipuJFCXHYA^ zWdPhA>TrQ0sYB!*Om`+hSD9;G^`qx7B2ZMnE{1kwDP#rFCBvXE_!2bJ2Q0-?hRe%=)a8Ikl1&*Wd->61tRxMN36*rBk-r)ENxkGUu@6-$ZPN%gK`1h z0l?j;4i`9*PKey~bT=^qAMB=n$XA|st3h+2dcHwR0iFuL-Kq{3IFd(w@1Om5y4x6m ze+}lopmMP$+YH(Z)g1=MS9OQ=q@y; z8}t<51OWFjdqc2>S6++s_62^WTXkNfx7nb-&^*Inpa9DO+{YXY!4BW==WEo`I~4dK zF!uy`k>0HaBZcNxgV6%q2;e^ESO~7>m9LAVVjacTxO>$hgJcs#?x{7gCVh;+Z*{Y-T>SrV zjX^&k-LEzn0Hph+0NjJ>fRHpn=$qXa#;cm_< z7w$HLIY7E!Y%mW<_p<=F7m7{;MDCt+FEYaY$CG`1e{ISApg{v5-QP7R1JZp90C%H0 z6-!1TayO>Ci4pFPxmi&z&bw~V3`qBx1}%W)gs8j?fV)+ls>vFN+~cp0HECl+@ReZR zUn>{xW`lM>x}Ra7A04_p+{*#DJJhLL5??RM=Vj^cWQ6-CC&{f}Y?$sTgKj{&zi!Y2 zNcZOfxb*`~6+&_aMCV;jcON70``oN67w5g+pdYXdM&d071AuhD9)NpL9T<@|h}>g` z$C?Z=0>8k`VdcWzXfOg;7Vdv_=`9-tr29_*++*r+fg^bkB6nxH#~Fd|1arScxo~eW zm;|KzI)f=dx-SFZo>nLJ+jWTCrF73Q!hIT;{kC%9?lhPKr28;~c|f{f1;D+a4kaYb z5V=Qdu_lX*z)u0+Ro4%`P4}bR$LISM0O|gNK^c(lp8{|+^y=As*# z>3$&qcZWJ$;7A%Fau27wlM(pu+_vZXRxaH02Hk*k|Ja}hknZmQaQCW13dt6T+_iM~ zF#>JS1>l}mhYK9ZFhu8F zP4^5V@I`LUDi`iPgE>ID-)=AuNcWooxEIu+g`^!K_xvHTCX0-~FLSdrzU1EBpaGEX zCmZNj?56u4+>OWGs7}fI?Rnpy=kv03H!;HfeK7X}m5cM9FlYv(`^yF`fOLNbfV)+l zlK0yeLv-G4>270$`(18Ul?!*bK|3JbZ!)L>(!C0x{dNfU=asy3?Xe!6fuHA=epzvP zcQNP&r1ziPc<1Y00qOlcK>O_wJm~xKtVMeJ0^jLYofqldV$csr?{x+Pfb?Dl(0)4v z{k+oou1xPx;4|DB=0$qf8jJwadxXI#AiW0xwBHWF-n{a4u{zdaJn%E!n&3rx8w@4| zIQSd*3pd-j|5t!t0(3n?a=#tV5m<|K&oH9-4L4`ijK8~4gSkTWafA5+d??%t5Gs;v zg7~^f_aYdxP`w?EG`wD}00S*!u7}N@|5rA7i zF#7i^KdZluX&N~HiK>f4iYam=qbRn0JwWaxQ>W4K;)h|IM$?(5%`at za@UoMHF?mWUx0(ecMS##a0>wUpvZDZMj>+7(>=t9=EvL|RxaFigONgYrom_d-Uh%u zCemQZ8i?F2=^kf<`;~4^C>QQ#gULem41=ixEC=A87K#1#!g1lAuEv_oFv9&4F#8eZ z!aZd$SE#;jFkgVr18^@Cohu+Z@4j>|GQ#~nH%qrHxz`&s2yl>ii-CS+MS|A@a5sun z9BG5dU8%--H!-650yitl#d$XxG#9FWy;*NrO9B1_z}+f>49SBKxu;$i?lwl?JKd}* z7w#&nFAjVFo<~cohJ5uZj2D z%@Dbp(%r`h_fy=gD;MrZkCnT>Q2oJRpa4Gw;2t#betQZc_rz;sO@S{`)b$zLg92yun+A; z;v9o=0p04rW+#$z37)1N+xYy6;W$7Mb z1pYoj?gc6r?g@jDLiJ^X(E@xX++#s>fBRyH&U^ILu_oh;!0&Q%Lb-5v8%#z~i8mQc z6<`&B^PUc({dOxv?v7W7dxjDCId0A>7w)pbToje~Uv7x={qF_%4FLB-5bd|O`JOqS zmmL)DMMmInx>>qy$-UV?zg7=XJGlKZMYh|YUB-A#;Wp5$glsyOcs zgJuB^60b980i=5$0Pa=@6-9Q3$X!c!8zb<}ZdRoV_l^!NLAwA4iC-Di0O|e#0CxwZ z5|Igr+zSWBnshRvd4ro>QZ*V38FUM9khsvG2axV}19103suAgi$UT(qK1MW;bh9p1 zqrpmpegO^=dl?J>(!Co1_aLMmkupT?YPyFQ(R_qk-(25PH5$wsj0kX$xX)k|knY<7 zxW^O$5!nopd%i8!WSkNBN;fBz3wOW4qyPtrvkayH={^~Nds>m$S9L(-9!&QPBbo=g zIjdZ_TMXs|I7mF(U>=a}Cjf9SC=&as9lp=a=Vj?$WJL2JF!xKoNiN)54H^VENZe{r z2BiB&0PaRbVqY}`(Rt4v5Np!Jh~{~2R+I~OuR*f_2Z<96S^()j3V^${=&XduJ&^7; zMl|o^h0#sGp~v@>0?Cm3^(h_g?p_*zW@h`BMb%r={^X6dr***e1L~~a+hm{L=$zVi)gT%w!#O8h=Al*L$;2v|t`>L(J&&~cj-Q$dC-t6Xta^W5^ zm~_#J%MGRg={_HTd)g82tK$AV&qZDtYcj(K_wjDdDi`jW!JLau9AGdHNcW!MUU0oaJM?*ebris+*A9Xgvj#MNmLFDdDcPAt8@3=+H^{rgEXAHVsbmA_99zeRk2Eg6xNL7*%h}=!-?qdYL z#Lc>L;Xc=(-$f@*Hy8k<`vd^)K}YJ6)F5(C>=$b?#0b2vo5RY5yJ9fnq7zRu7zL#J zaqc|h9*cAn_#dpaW8R}DgR-i_&= zVFW(g%~|Eb-D5BpMJ0|imhyVxP{R~C{>3t!9`=oa80@oa;hi6!?F0J@$b)rjn95BKOku_k?tXg&ny zejqQ>z15&!fP=)X1_KjI?i&HP2O;%{3_*0>9qAroMDsj1hk23iUV{+<4iYCCj7}`M zj{@KxgXpp$68F#fyzFIh-s6nGd$>8FTwEtB3?>CQNbF=VHL>LWGk2bGPeXFQJ?neX zjXMvgdxjCsyTRNKR4&}(26F-&B(67@pICBV1;D)k$^CXeL~Bw@_aY;jXSk_f%%1MG z1`PrnB#tmB1C|Fk2!Oj$5iW2dEfBdE_Kr1aVg%mR&5Cky-X()(0S*!muhLtlUmu_D zp8;^U7M-mSxrfr-#)#(4ZdQxjh(WtRxGy)T0hR|iAAq|uspz?0NlNbR2^9Xk$ZlxSd%_RG#@+I*SB)v-fqw@z(L{xg8@Lg z?*ZT*RHW|6I7IHjbPqA2c@3D)CzT8Lpuva$2Z;|Di~`cV0f2kV5%0IxLga2s_c$Y( zhk$v%tz5YGH<%RQAn_uDDL}fP4!}L_i1*tih}^R;jWwBJMDur5xo4FN_l&`ui%#5S zFb_!g*8sQ|9Pxg81S0o9x)&MYzQoPaw@d$apKH({z(L~ls{ZR`K)O!=;BIszzW)g# zcWb(v7}4C<&5Cky-W7vp7oB*TK?`6xAu5l*&hA!6yx-mqk$dJPu_kSd2;L9oexP#U zo-}B8(TST3YJlb8z81iFcQ{g!WDp{EJ>8v*z-PPJrChjs47y!(;y8mI!18b(3c%g# zNL7;kA#%5*yN?n0d2ZH~3->Mt{VqE3&)4cL8vra1_wNC?2OX(PG6Ru&x+T_Rh!OZ( zZVoFK?lFTA7oGU5!6;yPxIYTOJr)t|x6g&h-IwlhM&NaBPAC`dV+l$2J?XB;r~&-K;1V=Up{uj-nDT zH)sK*`@aFWTO*?V_AU^)o6_CJ2>jQTzP^F>3k>l zh_x6Ge2H5V(uDV1gGm7nyr&yX0n&Q{K>O_wtl^cs>(e_E_?2$WN)z5@gE;{Xyw5P0 z2c&m7K>O_w?C||zu8Z_8f(PGv2mh`A{zvVC?d949ljW3!@<0Fl&kFo!1^%-F|5<_m ztiXR(;6E$ypB4De3jBY(0!OW0z2?Lt-_U-@niCH@_VD(Dk6L}miH9D(=8%8C$^Wl; z<*ap^Dkq;^S-0Wcr)@lQ!x`%~Y^rQJv$DroSv>Zv(>A=lvhIwN*L8QV>#m%((8-e*Mwwy5Dum zy2IXeTK7qcopx4bkM-S8!u{{uM)d#h+{Tj5yK>~2-Rss@mMn*#(R=2`O_f7WI&D3t z^s>sjjT_J0SUIzI-3FcO9%ntNw0G&~!;d7(R1RMM_A@K% zPus9g=cDsgYt5Q{U(v=tOK&>-$c|jP|Et%r@l00d|1R?ObgX~Z+fUnY_=eLqRW=-b z(uUK|I;gTocV)v-oVov1Z?ldKt9wq`uwmVL7J{*p`Zt}$spbzy9zI zr=H1Z9ow92C8#k;v=ZbU4T(s)mt@o@dm0rKPR62QeX=45A{eJlK)u(>`)$OIW zYuZme^*M)}x^T%M_wM?{LrXvW(4nPUc0TNrpEzgLkFPs-)d&A@&Z^ok&f&kO*XQux zc<-uTyjQ%w>YNqzRU^HZ^5+e!PW{botHwX`P5xZ5x>SG8>QjI7+|~E4;eT8ASiRqV zd-3{!)t~&t#?__j{jkH@KY90^?f0HrJ>=B$Z#ksYaKNFZZCeg4eV&I-``&a|>5*5| zKKYzilYjNQ6rBK-dAe4urSEj>o;0`6;~K#OurXZw~XnQC@$M zxATYm@29Kx`^zs^fAS}P9X*V7* zy}tRd`j+bstM|RA_N3#Mtt!=bTJ^!x|4)0@9$wS6?MFO<2;%*yR=iS_EiA1^n~1&E zoNH@CgP@9Td94U}kVGY@E^k_zc(aP8rHbM>r#(s)o1j%)MXO##<5{ovP^y&3{jI&% zOp}!FyWjoe-hb}?zG=)k<``o>#&3=}=gRCk{z&DWVQ*Eyd{)Hx0-OQIf}FN0ZVGP6N- zPF?2k>wUtZ&oHM7^kt`}^}u|}@RjV$|GkGZEP!Foxl@wds2_>8lTnune~w0eth4p0 z>G1U;_R%UXzzIXWjx{rW1}fV~O@^OqGHwqwTNJ-hQX+{jZ$Hi7(Nf zg;9%l5Oen(%0D7qcAAmDqfZ3?1;B1S*$jM1Ho6`_+ed*Xj8lG1q2OJLdHrc-oL<0; zO*J_6nZs#uRaMg-U|faJFNEDfjKj)5KsyiM%W}Y!l>%Y6DX?T=^f};XF~(@1y`@OY zkY9$j@?>Ms8kGNy_HWAO>^_Pl?NiK8os3#|7%jkG4yS+4X;)WOcW+fA)UGb+ljE>? z0y@VqH^-rmat7$q~8i2}5J0~GcG@5;tKKgCS0t(cKr6={Aq#kd!tXscrt?NU6( zHWK_S#auQCYbRHc`hB1nvyLccW(P*gHZaP}>4h0wpIAX1y?uf6So1}g*ZIzk>H^I7e8ix6u)7dzWdJ#BF|nYIOWTOqpxvfHt)cEK-q(LQ0GgMg#Ci7p~5#xw)=S|Bw?e?!pUF6etF;I#|#y}_S^G!1Rc zh5cOc=D{cLLS`lWvI+V9;NOQ|yC{5rp~F2rzhUEy>&R;8oo)QB%* zTxOsG9mF7#kw7C9^jSts1wFxG=Oy^GKl=3=Xgu0Th0J*5r^!Y(+TQoIZ0vVpeZH*F zjRA^T=Qze0!KBfvnRK@n)=DndmseM{OQSG`RM;MkzNdjAH5>)Iso-Z}tYgsDRA6io z(guv{AF}3O7ucz%NEvm3le!8O)KiS54HR?slZbuIF{d3AGcZn(${$jUo(eNZ?P3(# zol_`c!_F5}O8i2N81{n8j2r~qy#kyMg3cgCsy#?C1`Wl2k)TN5Yl?9qSuwhfRp`(Z zw6zR2@yA5{vyA5$MADB6Yb$kl3 z;wz4rar*dyt;M8l9$S z&)rhUmCnp$LcCqeNvomiC#I-WgiBD*U_xapObKkC8Tj2fSX|3V-J#j{E?6OE%ZT=O+k^3ZB^z76L;X?MJc)dPY$jio zHU5Vz<>Qasxit}kqHu1=SLp87iuB__#XQoVnZvg;8qtDN&RkAagMizsz~xoo^eSvy z^ct{#4ef!>zJ+#ff`@n)SVu7$KBE}fZGqWEigX@HUW9dlw0f~(KCu4Uk1?HCiMY2B zYw`^0dNF!oFQYm=I34$JiV0UKVuz}$;Vz@=N$l+>fjgw0$X_~vb#)SUPXN2%Z9SzJ zYYUJ+hqZA9vNvJ>KE@S@qi17AcVA%Ij$zE~6w5S+#%Nd~qctf^n>dbX0aKW{YA(|T ztb)uBjFyCQdboj8Y&%sSvQecy?OeysZv{TL0IQ!n0~T*{p6!Mh7vOQ4v-bis-veX6 z0;j)YzpDfvVn6d#0|tWN(>3sSW1NZ7v8Fyl{BMXeLORnPY-7~BIrbI&7o{U=V?Vo% zczFx9egnM?EdGZ4ZKQIrR{^Oeuu~gYY5=?SfwM-KYb#;TZWxBt8|}OcpP!Po>1`FW z?QE>?2a0)PGBXNsCNXdhyHH2f$Iejoup%`gZ=6fIrNd`B{D^afrb8d)$SLq;4rnfT z3xO9aj_$@9_!_c@A$tgI9RocJ`Cq{&%yUi5e>+7B84tg2QYfkl)BG{7fBwQKI)l?N ztjk4Ns=nolO5ex0H2-kKnJ~nlFkmALdwe)>0$yBq#4%aXrn|6TMqwZ9qZqCGD`w+Y z6eDRU{GI@eWGcp$S&A`wHR9Dz3bj(OPr7gpc?ogn2%`X%lVWmO*Hbm_#RG#0u#dP_ z8@m3;Qv&7&k~lCpUn7U}VTJCcUIFb3r0AXCo~ej`QSjCRIgh zp2(zIvCOy`jdeI4*gC){3V1EpiTUlWQq7&Jr$%>|wqh8g8!?P>L1zwQ#w<`Bwm*$w z#tC3>M;!LN1ZFBp%*akdTVt8l#$eLB?=vIipG>Mzl}n2Qu*R!$>K4ScV8pml%{bKw z;nbiNr%k|Ue1A@dMj-ZW;*{)A^=E+9Hytix>G!~*72}QpOUJQ4pTzm-EHL_;Qwsr> zLaHJK!G}%Y!{%78*k?1IhA&3~SKr9e`3TJMJj~T;#dx+8({AT5bH_PG6Qekt#JjE? zD&jy5;K0IoHQ=%ua99m?s=*%0kquy{G3wfYhkak_2wo@1S?5YCMh*s!@q{H}Cg=v} z-b4G=-n9UJD2H`8hDjNCeHFZvQ(<#e{}`CP|4`MJWV_67EKDwdF6bu<(1(Rci!hc2 zu#NLw#2V0#V0RPNf(PpcXY{52LH-WLd>8ajnS#q>Z3G}zAhvD|0TwuLG6!@g_LiDV z3z^R7_5(%(W?+3bQt84Qs{ZVCRevYWWsLUVez6mKA!KuR0!tpnRKP3PrBLWzVCn#H z_Pt{G9l<&{4%uVC?vI$y)7VQdDbj&kXuATVskNB&Q*Wk49bnpP2beVUFmTk5lV=;( z=QmfS{ELdY{vu>AV&4H>i9NULCA5kBC%Rx9?pQ|`K{tVn+LH;t+F8$1;+E(>APK=YDcJ4&7(#v z?e3C3I)$^*8Ah+1V%m#mnAYeN*2F0$xqo8j1<-ud5BrH}8zHl%fEgREGL7G06kd}X zPdDI1PjZTw%&GDuUvFo343f#(R9M04R){M(fC1+)oPKF{jZB!$s z1HXgHBL?}wZ-{-XE5K*|@NZ@08vtJ|;CF{io=(VfzIM)VhFUnz{Xzme;?X7h`rk%=c+)&#URj{z*8;6#0J1rFydWX zU??1AH}+DT%Z3~V{u(pw;VfqSf;bVq3~`~gsz+eGZ1_dh{}toyUd;TaA7T$avYZ;kjJ*z$-qLlBcRoGW!s+s|>D6sAh%I%Z6(&$SB;xRKX@YZpO}Hs;z0&=yZ{b5C~&7emA*(+KIE2NA*NSxae*gz8k>yK49nxtifUXIK>^r zUH)hI@G_@AFK~*y%BdM%p)S73^)7@nLPb>%uC3}*TdK5qpsH8-r%LVHyQGXj#DhTK zClJ1?jybOZAJoL2Rv-Su-6EtTk`rkF%9fAu-63Nbbl#WEwm4fa`v`ZViwd=Pj#1z& zX72fhNwK&ig+~IzI3uiRj954h_9npgn>bVD0+S16Z6EY!!=D8>Z!HVL9uuiZgV!k1 zAGMe5_`3sefc>%G z0AK+3rp+HITIwl9dM}u1->yPj2Hq-ugmu?VrKWRKnsQ4mm>B0W>l88b_(LX5D`L{W zAL0)EkWsfHW{fYw-i&-F(B3G=5|^@(pHJMFg!}+MPEVHSMm2v}a%~mn$yt$0+beNt2JSu; zP|gd(J~kS6t}IUV;Kx_a15ejMuK^E8=a8HzhajCqegIN3eBA}U_dg5WqlhhqoPNf4 z#q#@APpr#D6>F%p8|ge!Bc#JflX2$g02%;#4zy}@l{O*Gs;N>-l(WDuL~P&RP^H0* z5&I_K`-QIRGpDNb)I3$MJP&sX(5s78z0GQs_HI*k#}}%82j5qg>{2Pr&lM3~$)zh{ zxTl7|_bq^d5UlN1hz)Ikv5qK*15Z)NzlMD&9vI68HYTFpx(81NMn1=yZ;o^95`05C zj~LR5Y30T;bKSR0+8=<}IDk{r1Dvu|#4Ox5BL_ooFk;86urmr6%>c%xpq-hZGmvH> z;m(p*4g1*!$m{^_f42IAz3mm;AvP)6Yr#y~wvPW_M5*Qh%f* zMdQ;kuB|wG-God#Mjo`g{%KA+?oR*isp{tzsN}|7-x2G&Sy|^dTTbSe0;lnu=XHMS z^T}M#o5cgF>fG!#i4S}Sbl)T%_Tpq7wo>Pf-qN`?ZW3SBbP7MVeG=ckd@>){;w^mV z#Cdwj`<%j7af0DB#bJ;zG zuRS=8x41Tgo3Fja2OgWx*Y4hcbcL@C|C7_oq3Zh0L)BvvTBOXT-J=H@$Un}a#(y2i-6m#X@!hN}O*6taJ+FoM=*=X0bTN7M$hv=>* z%q5Rs;F7P`>?xle_mbRXZzO#CGB7nzzJ3_rGa6>eL;f>G4ycc_SwWuMdIIjKwLg<{ znr)M>&#j=WnmJV&9}&p374?`|=T%1Uti-wdS4PKimuZxMH4VJZSJl=J@Rm6O-)2_p za@abg5xSiJgD&5xJXs$7^XtAR=bZaM9=-g1)Nhva z7v2Z9t18Fxk3k36TR%e?QvZD=pvhLH&Z}Lq@115FQ+we2i|-r1WvJ^4)~i2c1-kmL z&jyYmA9)|`-G^P|XFmXr3t|7hJfwXQY*oV@BS`7Hrati8QknLnQ#sq_Ma4C`kJ9(@ z%gXpw{gf@g4na9y$?r7@ad;^(yi~d4xrX)HgiRYbkV*4*vc4U{`P!44dGyg&)T7nl zpHKZl9Rc(F#+v6Xe|-l$s}sYNr3ZRsN*Q84<7mtw^~ zi$4U(8k47(>$91Oa`xiFB0S3`V%3ADmy@gc$Dv^qtu_}RO+!7NT6*f#rDfr@v{W1`rAl5F^5k7lJ#BvMSUIUon28_)S#4pjbi;y z;GcM}u;}ZGCG~ez6#CJqpYma0(GRFDZok|&#r?}feV5gRMH!{+k0`dk0Q|S%yBa0+ zxmTTn!TS{T<4_;g0ZtV2SD_P1J{e2x|9}6xGcc-_uf0&4o{sS?u(+l^ei!d*A^Nzv zuM8J@e->7)pSAw2-x(jFK$}|6FRfoSAECR^KD!O=@}W7t@)LI1T0_>ak&n=EyWYaR z^%M2KKP>SRIL`dzNzqt?=}e!=B^RQx@(y4~@rJ~lP5sV#;_o<4)NSqir0 z>)3LYY+B!@qilxYN7)UFehU36w!CQXVUe%@!M1#nT^93t!!F;k2X@aci*{^t|99ja zF+-)h5Ni87!lqF+9cI%}Hq~vKYtuZNZnCMzriW}=VACwyKVqK5Jgc(YEx1+x*fFV@ zV}d($aCCBnw(Bs)TkJ5gV@F45r`9&Rbbrg1EV~uxW>Hzbh39fWxu<678VmOp|3(>p zQ=2c=lhDF{I!}H;cV3xs;P4E7onpKT`4Pxi_Qm=X{MWtmwMncW!C#2Xqx!!Be9KO@ zy}rGB7kI0Xtp0eSeJdc;Ubvsze2OW}KV8{Op zm65Lq2l!D#((U;X^44Bx@n_n6(e6{=TjToB9+!8|h3%)k@BFg5cO7kha03cf|lQH|nw z36}SlZObpU1EzNmN6lRDt@#n_!xpC%w){bRK;AP4D%W}C>k*Y%$Di2p?P~ihc+Vin z=G**9c3}3NExfptuI)!l+xq?K<-<#5_(3pR zju5@=_3x9$U(vn%>c#t!_pE`+7T^ot30aGF20yqY!_N16jC}lK_+uZ#p9g+Ts!Ij- zekI!a@G78{f7j?YX>&v3_#$)dQllu^kEnF;ZZ&__$SwWQP}cT9SEOtu?u%+j+- zOOHuOaL12HNy&yviIN+VnWaR1kC)J+$oz#rpfZDVBv1 zW_bBZLhP8t$h1UnfsaW}wp1uJGA%A9+51w9QWLT=qteE9v^qQ{HO}pJl*o4Yi)&2+ z@7tpnbFMg(Oz{XTIw>WA@S;zY(G9hCe>Zd4y>TghONyg9x|eMC(XB?l?CcTgewp)rEZQC2 z!x_ok9MC&v5wEz0|}&HZ`@($Is9ViBu-yhMzS zO~(8rWhTWW;|*e8Fubj631wP$yAxB=QmyvVGvOKxDK#!8BOxwjR4I$-^fi6hLa;cI~B%!G&9{-^Lj#hMp9a8se&7I z=?Td(mV%udm7GZqYf8z1tRpea&S0|0k%^^14r^6II?a0jNsff!_BtLO4{M_2v*BCN zJ`Iugz8XcNFyQTAOiB_gqaCX~aJ)uiGcw45_+mXx6+!BMPOWX)IMz3u9FQMK#BME(RpAz&d zKc4~Xnqp;zzBq670}r9USYO=F1Qqu&hp+$XRzI(Z0A32^wNcig61$L5HK> zx@(EDxNiw6#$UGoq61@*v1|){alaEZ*zT}szwG!Y+4@e~ptvszT2kMdv$FbcgJ(4% z?z6?`Ap1Nd?#;G&Z(l{;qSKK|>Id6;f*vX}`e-Y!_Fw;qaSK{lM!)!eqWko-{!;bkgr1=O zXuM?m;(nH8>x(!k%m_VE4urDB6XVab?+dxM{-#qx(no_!7pbP*erOrK(0`^3-|DON zD@lDL_3_2O9=rXS_QXGl3j9wk6wkjHm-XFC^j+v$)HU5_by}H9(XNoS!bBMnZ /dev/null + python3 /home/opc/node_ordering_by_rack.py --input_file $hostfile > /dev/null + homedirectory=/home/opc elif [ $ID == "debian" ] || [ $ID == "ubuntu" ] ; then - python3 /home/ubuntu/node_ordering_by_rack.py --input_file $MACHINEFILE > /dev/null + python3 /home/ubuntu/node_ordering_by_rack.py --input_file $hostfile > /dev/null + homedirectory=/home/ubuntu fi From d151da69102406647a11b0a4af2c737fe1303d84 Mon Sep 17 00:00:00 2001 From: Arnaud Froidmont Date: Fri, 15 Mar 2024 12:51:35 -0600 Subject: [PATCH 39/40] Update marketplace images --- conf/variables.tpl | 10 ++++++---- schema.yaml | 18 ++++++++++++------ variables.tf | 10 ++++++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/conf/variables.tpl b/conf/variables.tpl index ca856e01..97fc9eb2 100755 --- a/conf/variables.tpl +++ b/conf/variables.tpl @@ -56,10 +56,12 @@ variable "marketplace_version_id" { "2" = "OL7.8-OFED5.0-1.0.0.0-UEK-20200826" "3" = "OL7.7-OFED-4.4-2.0.7.0-UEK-20200229" "4" = "OL7.9-OFED5.0-2.1.8.0-RHCK-20210709" - "HPC_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-2024.02.27-0" - "HPC_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-2024.02.27-0" - "GPU_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-CUDA-12.3-2024.02.27-0" - "GPU_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-CUDA-12.3-2024.02.27-0" + "HPC_OL7" = "OracleLinux-7-OCA-RHCK-OFED-23.10-2.1.3.1-2024.03.15-0" + "HPC_OL8" = "OracleLinux-8-OCA-RHCK-OFED-23.10-2.1.3.1-2024.03.15-0" + "GPU_OL7_CUDA12.2" = "OracleLinux-7-OCA-RHCK-OFED-23.10-2.1.3.1-GPU-535-CUDA-12.2-2024.03.15-0" + "GPU_OL8_CUDA12.2" = "OracleLinux-8-OCA-RHCK-OFED-23.10-2.1.3.1-GPU-535-CUDA-12.2-2024.03.15-0" + "GPU_OL7_CUDA12.4" = "OracleLinux-7-OCA-RHCK-OFED-23.10-2.1.3.1-GPU-535-CUDA-12.4-2024.03.15-0" + "GPU_OL8_CUDA12.4" = "OracleLinux-8-OCA-RHCK-OFED-23.10-2.1.3.1-GPU-535-CUDA-12.4-2024.03.15-0" } } diff --git a/schema.yaml b/schema.yaml index 5b291702..be651072 100755 --- a/schema.yaml +++ b/schema.yaml @@ -417,8 +417,10 @@ variables: enum: - "HPC_OL7" - "HPC_OL8" - - "GPU_OL7" - - "GPU_OL8" + - "GPU_OL7_CUDA12.2" + - "GPU_OL8_CUDA12.2" + - "GPU_OL7_CUDA12.4" + - "GPU_OL8_CUDA12.4" default: "HPC_OL7" visible: ${use_marketplace_image_controller} @@ -759,8 +761,10 @@ variables: enum: - "HPC_OL7" - "HPC_OL8" - - "GPU_OL7" - - "GPU_OL8" + - "GPU_OL7_CUDA12.2" + - "GPU_OL8_CUDA12.2" + - "GPU_OL7_CUDA12.4" + - "GPU_OL8_CUDA12.4" default: "HPC_OL7" visible: ${use_marketplace_image} @@ -1665,8 +1669,10 @@ variables: enum: - "HPC_OL7" - "HPC_OL8" - - "GPU_OL7" - - "GPU_OL8" + - "GPU_OL7_CUDA12.2" + - "GPU_OL8_CUDA12.2" + - "GPU_OL7_CUDA12.4" + - "GPU_OL8_CUDA12.4" default: "HPC_OL7" visible: and: diff --git a/variables.tf b/variables.tf index 5f040029..0cc7e5df 100755 --- a/variables.tf +++ b/variables.tf @@ -91,10 +91,12 @@ variable "marketplace_version_id" { "2" = "OL7.8-OFED5.0-1.0.0.0-UEK-20200826" "3" = "OL7.7-OFED-4.4-2.0.7.0-UEK-20200229" "4" = "OL7.9-OFED5.0-2.1.8.0-RHCK-20210709" - "HPC_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-2024.02.27-0" - "HPC_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-2024.02.27-0" - "GPU_OL7" = "OracleLinux-7-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-CUDA-12.3-2024.02.27-0" - "GPU_OL8" = "OracleLinux-8-OCA-RHCK-OFED-5.8-3.0.7.0-GPU-535-CUDA-12.3-2024.02.27-0" + "HPC_OL7" = "OracleLinux-7-OCA-RHCK-OFED-23.10-2.1.3.1-2024.03.15-0" + "HPC_OL8" = "OracleLinux-8-OCA-RHCK-OFED-23.10-2.1.3.1-2024.03.15-0" + "GPU_OL7_CUDA12.2" = "OracleLinux-7-OCA-RHCK-OFED-23.10-2.1.3.1-GPU-535-CUDA-12.2-2024.03.15-0" + "GPU_OL8_CUDA12.2" = "OracleLinux-8-OCA-RHCK-OFED-23.10-2.1.3.1-GPU-535-CUDA-12.2-2024.03.15-0" + "GPU_OL7_CUDA12.4" = "OracleLinux-7-OCA-RHCK-OFED-23.10-2.1.3.1-GPU-535-CUDA-12.4-2024.03.15-0" + "GPU_OL8_CUDA12.4" = "OracleLinux-8-OCA-RHCK-OFED-23.10-2.1.3.1-GPU-535-CUDA-12.4-2024.03.15-0" } } From 0351666f99cbbeacfbb963170279fd1884c358e1 Mon Sep 17 00:00:00 2001 From: Arnaud Froidmont Date: Fri, 15 Mar 2024 15:08:31 -0600 Subject: [PATCH 40/40] Fix difference issue in Ansible during remove --- .../destroy_unreachable/tasks/slurm-rack-aware.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/playbooks/roles/destroy_unreachable/tasks/slurm-rack-aware.yml b/playbooks/roles/destroy_unreachable/tasks/slurm-rack-aware.yml index bd735b4e..d1bdc2a9 100644 --- a/playbooks/roles/destroy_unreachable/tasks/slurm-rack-aware.yml +++ b/playbooks/roles/destroy_unreachable/tasks/slurm-rack-aware.yml @@ -114,9 +114,9 @@ # - name: debug # debug: -# msg: "Replacing line: SwitchName={{upperswitchnames[item]}}\\sSwitches.* with SwitchName={{upperswitchnames[item]}} Switches={{racks_on_switch_dict[item] | difference(switchnames[item]) | join(',') }}" +# msg: "Replacing line: SwitchName={{upperswitchnames[item]}}\\sSwitches.* with SwitchName={{upperswitchnames[item]}} Switches={{racks_on_switch_dict[item] | difference([switchnames[item]]) | join(',') }}" # with_items: "{{unreachable_slurm_nodes}}" -# when: ( not upperswitchnames[item] is match("inactive-.*") ) and ( ( racks_on_switch_dict[item] | difference(switchnames[item]) | length ) > 0 ) and ( upperswitchnames[item] | length ) > 1 +# when: ( not upperswitchnames[item] is match("inactive-.*") ) and ( ( racks_on_switch_dict[item] | difference([switchnames[item]]) | length ) > 0 ) and ( upperswitchnames[item] | length ) > 1 # run_once: true # delegate_to: 127.0.0.1 @@ -124,11 +124,11 @@ lineinfile: path: "{{ slurm_conf_path }}/topology.conf" regexp: "SwitchName={{upperswitchnames[item]}}\\sSwitches.*" - line: "SwitchName={{upperswitchnames[item]}} Switches={{racks_on_switch_dict[item] | difference(switchnames[item]) | join(',') }}" + line: "SwitchName={{upperswitchnames[item]}} Switches={{racks_on_switch_dict[item] | difference([switchnames[item]]) | join(',') }}" state: present with_items: "{{unreachable_slurm_nodes}}" ignore_errors: yes - when: ( not upperswitchnames[item] is match("inactive-.*") ) and ( ( racks_on_switch_dict[item] | difference(switchnames[item]) | length ) > 0 ) and ( upperswitchnames[item] | length ) > 1 and ( nodes_on_switch[item] | length ) < 2 + when: ( not upperswitchnames[item] is match("inactive-.*") ) and ( ( racks_on_switch_dict[item] | difference([switchnames[item]]) | length ) > 0 ) and ( upperswitchnames[item] | length ) > 1 and ( nodes_on_switch[item] | length ) < 2 run_once: true delegate_to: 127.0.0.1 @@ -137,7 +137,7 @@ # msg: "removing line line: SwitchName={{upperswitchnames[item]}}\\sSwitches.*" # with_items: "{{unreachable_slurm_nodes}}" # ignore_unreachable: yes -# when: ( not upperswitchnames[item] is match("inactive-.*") ) and ( ( racks_on_switch_dict[item] | difference(switchnames[item]) | length ) == 0 ) and ( upperswitchnames[item] | length ) > 1 +# when: ( not upperswitchnames[item] is match("inactive-.*") ) and ( ( racks_on_switch_dict[item] | difference([switchnames[item]]) | length ) == 0 ) and ( upperswitchnames[item] | length ) > 1 # run_once: true # delegate_to: 127.0.0.1 @@ -148,7 +148,7 @@ state: absent with_items: "{{unreachable_slurm_nodes}}" ignore_unreachable: yes - when: ( not upperswitchnames[item] is match("inactive-.*") ) and ( ( racks_on_switch_dict[item] | difference(switchnames[item]) | length ) == 0 ) and ( upperswitchnames[item] | length ) > 1 and ( nodes_on_switch[item] | length ) < 2 + when: ( not upperswitchnames[item] is match("inactive-.*") ) and ( ( racks_on_switch_dict[item] | difference([switchnames[item]]) | length ) == 0 ) and ( upperswitchnames[item] | length ) > 1 and ( nodes_on_switch[item] | length ) < 2 run_once: true delegate_to: 127.0.0.1