diff --git a/ansible_roles/roles/ibm_create/README.md b/ansible_roles/roles/ibm_create/README.md new file mode 100644 index 00000000..2a9976ca --- /dev/null +++ b/ansible_roles/roles/ibm_create/README.md @@ -0,0 +1,300 @@ +# IBM Cloud Support for Zathras + +This role provides IBM Cloud Virtual Server Instance (VSI) provisioning support for the Zathras test automation framework. + +## Overview + +The `ibm_create` role integrates IBM Cloud into Zathras, allowing users to provision VSIs for testing on IBM Cloud infrastructure. This implementation follows the same patterns as the existing AWS, Azure, and GCP cloud providers. + +## IBM Cloud Authentication + +IBM Cloud requires an API key for Terraform. Set the environment variable before running Zathras: +```bash +export IC_API_KEY="your-api-key" +``` + +This is validated by burden before provisioning begins. + +## Prerequisites + +### Required Tools + +1. **IBM Cloud CLI** (`ibmcloud`) + - Install from: https://cloud.ibm.com/docs/cli + - Version: Latest stable release + +2. **Terraform** + - Version: >= 1.0 + - IBM Cloud provider version: ~> 1.70 + +3. **jq** + - Required for JSON parsing + +### IBM Cloud Setup + +Before using Zathras with IBM Cloud, you must: + +1. **Install and configure the IBM Cloud CLI:** + ```bash + # Install IBM Cloud CLI + curl -fsSL https://clis.cloud.ibm.com/install/linux | sh + + # Login to IBM Cloud + ibmcloud login + + # Set target region (optional, defaults to us-south) + ibmcloud target -r us-south + + # Install VPC infrastructure plugin + ibmcloud plugin install vpc-infrastructure + ``` + +2. **Set up SSH keys in IBM Cloud:** + ```bash + # List existing SSH keys + ibmcloud is keys + + # Create a new SSH key (if needed) + ibmcloud is key-create zathras-key @~/.ssh/id_rsa.pub + ``` + +3. **Create API key for Terraform:** + ```bash + # Create an API key + ibmcloud iam api-key-create zathras-terraform-key -d "API key for Zathras Terraform" + + # Set as environment variable + export IBMCLOUD_API_KEY="" + ``` + +4. **Set up resource group (optional):** + ```bash + # List resource groups + ibmcloud resource groups + + # The role will automatically use the first available resource group + ``` + +## Usage + +### Basic IBM Cloud Test + +```bash +./burden --system_type ibm \ + --host_config bx2-2x8 \ + --cloud_os_id r006-xxx-xxx-xxx \ + --os_vendor rhel \ + --tests linpack +``` + +### With Specific Region/Zone + +```bash +./burden --system_type ibm \ + --host_config "bx2-2x8[region=us-east&zone=1]" \ + --cloud_os_id r006-xxx-xxx-xxx \ + --os_vendor rhel \ + --tests linpack +``` + +### Show Available OS Images + +```bash +# Show RHEL images +./burden --system_type ibm --os_vendor rhel --show_os_versions + +# Show Ubuntu images +./burden --system_type ibm --os_vendor ubuntu --show_os_versions + +# Show SUSE images +./burden --system_type ibm --os_vendor suse --show_os_versions +``` + +### Multi-Network Test + +```bash +./burden --system_type ibm \ + --host_config "bx2-4x16:Networks;number=1" \ + --cloud_os_id r006-xxx-xxx-xxx \ + --os_vendor rhel \ + --tests uperf +``` + +## IBM Cloud Specific Configuration + +### Instance Types (Profiles) + +IBM Cloud uses "profiles" for instance types. Common profiles include: + +- **Balanced (bx2)**: Balanced CPU-to-memory ratio + - `bx2-2x8`: 2 vCPUs, 8 GB RAM + - `bx2-4x16`: 4 vCPUs, 16 GB RAM + - `bx2-8x32`: 8 vCPUs, 32 GB RAM + - `bx2-16x64`: 16 vCPUs, 64 GB RAM + +- **Compute Optimized (cx2)**: Higher CPU-to-memory ratio + - `cx2-2x4`: 2 vCPUs, 4 GB RAM + - `cx2-4x8`: 4 vCPUs, 8 GB RAM + +- **Memory Optimized (mx2)**: Higher memory-to-CPU ratio + - `mx2-2x16`: 2 vCPUs, 16 GB RAM + - `mx2-4x32`: 4 vCPUs, 32 GB RAM + +View all available profiles: +```bash +ibmcloud is instance-profiles +``` + +### Regions and Zones + +IBM Cloud regions follow the format: `-` (e.g., `us-south-1`) + +Available regions: +- `us-south` (Dallas) +- `us-east` (Washington DC) +- `eu-gb` (London) +- `eu-de` (Frankfurt) +- `jp-tok` (Tokyo) +- `au-syd` (Sydney) + +Each region typically has 3 zones (1, 2, 3). + +### OS Images + +IBM Cloud uses image IDs for OS selection. Images are identified by IDs like: +- `r006-xxx-xxx-xxx` format + +Use `--show_os_versions` to find available image IDs for your OS vendor. + +### Network Configuration + +- **Default**: Single public network interface +- **Additional Networks**: Use `Networks;number=N` in host_config +- **Network Types**: Private networks are automatically created as needed + +### Resource Management + +- **VPCs**: Automatically created or reused if specified +- **Security Groups**: Created with open ingress/egress for testing +- **Floating IPs**: Automatically assigned to VSIs for public access +- **SSH Keys**: Must exist in IBM Cloud before running tests + +## Terraform Files + +The role includes the following Terraform files: + +- **main.tf**: Primary resource definitions (VPC, VSI, networking, security) +- **vars.tf**: Variable declarations +- **output.tf**: Output values (IPs, instance IDs) +- **network.tf**: Additional network configurations + +## Environment Variables + +The following environment variables are used: + +- `IBMCLOUD_API_KEY`: IBM Cloud API key for Terraform (required) +- `IC_API_KEY`: Alternative to IBMCLOUD_API_KEY + +## Configuration Variables + +The role uses the following Ansible variables: + +- `config_info.host_or_cloud_inst`: IBM Cloud profile (instance type) +- `config_info.cloud_os_version`: OS image ID +- `config_info.cloud_region`: IBM Cloud region +- `config_info.cloud_zone`: Zone within region +- `config_info.test_user`: SSH user (default: root) +- `config_info.ssh_key`: Path to SSH private key +- `config_info.cloud_numb_networks`: Number of additional networks + +## Limitations + +1. **No Spot Instance Support**: IBM Cloud doesn't have spot instances like AWS/Azure +2. **Root User Default**: Most IBM Cloud images use root as the default user +3. **SSH Key Requirement**: SSH keys must be pre-created in IBM Cloud +4. **VPC Networking**: All instances use VPC networking (no classic infrastructure) + +## Troubleshooting + +### API Key Issues + +```bash +# Verify API key is set +echo $IBMCLOUD_API_KEY + +# Test API key +ibmcloud login --apikey $IBMCLOUD_API_KEY +``` + +### SSH Key Not Found + +```bash +# List available SSH keys +ibmcloud is keys + +# Create new key if needed +ibmcloud is key-create my-key @~/.ssh/id_rsa.pub +``` + +### Region/Zone Issues + +```bash +# Check current region +ibmcloud target + +# Set specific region +ibmcloud target -r us-south + +# List available zones +ibmcloud is zones us-south +``` + +### Image Not Found + +```bash +# List available images +ibmcloud is images --visibility public + +# Filter by OS +ibmcloud is images --visibility public | grep -i rhel +``` + +## Examples + +### Scenario File Example + +```yaml +global: + system_type: ibm + os_vendor: rhel + results_prefix: ibm_tests + +systems: + test1: + tests: linpack + host_config: "bx2-4x16[region=us-south&zone=1]" + cloud_os_id: r006-xxx-xxx-xxx +``` + +### Direct Command Example + +```bash +# Run stream test on IBM Cloud +./burden --system_type ibm \ + --host_config bx2-8x32 \ + --cloud_os_id r006-xxx-xxx-xxx \ + --os_vendor rhel \ + --tests stream +``` + +## Additional Resources + +- IBM Cloud Documentation: https://cloud.ibm.com/docs +- IBM Cloud VPC: https://cloud.ibm.com/docs/vpc +- IBM Cloud CLI Reference: https://cloud.ibm.com/docs/cli +- Terraform IBM Provider: https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs + +## Support + +For issues specific to IBM Cloud integration in Zathras, please report at: +https://github.com/redhat-performance/zathras/issues diff --git a/ansible_roles/roles/ibm_create/files/tf/main.tf b/ansible_roles/roles/ibm_create/files/tf/main.tf new file mode 100644 index 00000000..5f7a490d --- /dev/null +++ b/ansible_roles/roles/ibm_create/files/tf/main.tf @@ -0,0 +1,125 @@ +terraform { + required_providers { + ibm = { + source = "IBM-Cloud/ibm" + version = "~> 1.70" + } + } + required_version = ">= 1.0" +} + +# Configure the IBM Cloud Provider +provider "ibm" { + region = var.region +} + +# Get the VPC +data "ibm_is_vpc" "zathras_vpc" { + count = var.vpc_name != "" ? 1 : 0 + name = var.vpc_name +} + +# Get or create VPC +resource "ibm_is_vpc" "zathras_vpc" { + count = var.vpc_name == "" ? 1 : 0 + name = "${var.run_label}-vpc-${formatdate("YYYYMMDDHHmmss", timestamp())}" + resource_group = var.resource_group_id + tags = [var.User, var.Project] + + lifecycle { + ignore_changes = [name] + } +} + +locals { + vpc_id = var.vpc_name != "" ? data.ibm_is_vpc.zathras_vpc[0].id : ibm_is_vpc.zathras_vpc[0].id +} + +# Create subnet +resource "ibm_is_subnet" "zathras_subnet" { + name = "${var.run_label}-${var.machine_type}-subnet" + vpc = local.vpc_id + zone = var.zone + total_ipv4_address_count = 256 + resource_group = var.resource_group_id +} + +# Create security group +resource "ibm_is_security_group" "zathras_sg" { + name = "${var.run_label}-${var.machine_type}-sg" + vpc = local.vpc_id + resource_group = var.resource_group_id +} + +# Security group rules - Allow all inbound +resource "ibm_is_security_group_rule" "zathras_sg_rule_inbound_all" { + group = ibm_is_security_group.zathras_sg.id + direction = "inbound" + remote = "0.0.0.0/0" +} + +# Security group rules - Allow all outbound +resource "ibm_is_security_group_rule" "zathras_sg_rule_outbound_all" { + group = ibm_is_security_group.zathras_sg.id + direction = "outbound" + remote = "0.0.0.0/0" +} + +# Get SSH key +data "ibm_is_ssh_key" "zathras_ssh_key" { + name = var.ssh_key_name +} + +# Create VSI (Virtual Server Instance) +resource "ibm_is_instance" "test" { + count = var.vm_count + name = "${var.run_label}-${var.machine_type}-${count.index}" + vpc = local.vpc_id + zone = var.zone + profile = var.machine_type + image = var.vm_image + resource_group = var.resource_group_id + + keys = [data.ibm_is_ssh_key.zathras_ssh_key.id] + + primary_network_interface { + subnet = ibm_is_subnet.zathras_subnet.id + security_groups = [ibm_is_security_group.zathras_sg.id] + } + + # Dynamic network interfaces for additional networks + dynamic "network_interfaces" { + for_each = range(var.network_count) + + content { + name = "eth${network_interfaces.value + 1}" + subnet = ibm_is_subnet.zathras_private_subnet[network_interfaces.value].id + security_groups = [ibm_is_security_group.zathras_sg.id] + } + } + + tags = [var.User, var.Project, var.run_label] + + lifecycle { + ignore_changes = [image] + } +} + +# Create floating IP for public access +resource "ibm_is_floating_ip" "zathras_floating_ip" { + count = var.vm_count + name = "${var.run_label}-${var.machine_type}-fip-${count.index}" + target = ibm_is_instance.test[count.index].primary_network_interface[0].id + resource_group = var.resource_group_id + tags = [var.User, var.Project] +} + +# Create private subnets for additional networks +resource "ibm_is_subnet" "zathras_private_subnet" { + count = var.network_count + name = "${var.run_label}-${var.machine_type}-private-subnet-${count.index}" + vpc = local.vpc_id + zone = var.zone + total_ipv4_address_count = 256 + resource_group = var.resource_group_id +} diff --git a/ansible_roles/roles/ibm_create/files/tf/network.tf b/ansible_roles/roles/ibm_create/files/tf/network.tf new file mode 100644 index 00000000..9556c63e --- /dev/null +++ b/ansible_roles/roles/ibm_create/files/tf/network.tf @@ -0,0 +1,16 @@ +# This file handles additional network configurations for IBM Cloud + +# Firewall rules for private network traffic (if additional networks are configured) +resource "ibm_is_security_group_rule" "private_network_inbound" { + count = var.network_count > 0 ? 1 : 0 + group = ibm_is_security_group.zathras_sg.id + direction = "inbound" + remote = "10.0.0.0/8" +} + +resource "ibm_is_security_group_rule" "private_network_outbound" { + count = var.network_count > 0 ? 1 : 0 + group = ibm_is_security_group.zathras_sg.id + direction = "outbound" + remote = "10.0.0.0/8" +} diff --git a/ansible_roles/roles/ibm_create/files/tf/output.tf b/ansible_roles/roles/ibm_create/files/tf/output.tf new file mode 100644 index 00000000..f05fc702 --- /dev/null +++ b/ansible_roles/roles/ibm_create/files/tf/output.tf @@ -0,0 +1,34 @@ +output "instance_id" { + description = "IDs of the created instances" + value = ibm_is_instance.test[*].id +} + +output "instance_name" { + description = "Names of the created instances" + value = ibm_is_instance.test[*].name +} + +output "public_ip" { + description = "Public IP addresses (floating IPs)" + value = ibm_is_floating_ip.zathras_floating_ip[*].address +} + +output "private_ip" { + description = "Private IP addresses" + value = ibm_is_instance.test[*].primary_network_interface[0].primary_ipv4_address +} + +output "vpc_id" { + description = "VPC ID" + value = local.vpc_id +} + +output "subnet_id" { + description = "Subnet ID" + value = ibm_is_subnet.zathras_subnet.id +} + +output "zone" { + description = "Zone where instances are created" + value = var.zone +} diff --git a/ansible_roles/roles/ibm_create/files/tf/vars.tf b/ansible_roles/roles/ibm_create/files/tf/vars.tf new file mode 100644 index 00000000..8e19671d --- /dev/null +++ b/ansible_roles/roles/ibm_create/files/tf/vars.tf @@ -0,0 +1,65 @@ +variable "machine_type" { + description = "IBM Cloud VSI profile (instance type)" + type = string +} + +variable "cloud_os_version" { + description = "OS version identifier" + type = string +} + +variable "run_label" { + description = "Label for this test run" + type = string +} + +variable "region" { + description = "IBM Cloud region" + type = string + default = "us-south" +} + +variable "zone" { + description = "IBM Cloud zone within region" + type = string + default = "us-south-1" +} + +variable "test_user" { + description = "User for SSH access" + type = string +} + +variable "ssh_key_name" { + description = "Name of SSH key in IBM Cloud" + type = string +} + +variable "vm_image" { + description = "IBM Cloud image ID" + type = string +} + +variable "vm_count" { + description = "Number of VSIs to create" + type = number + default = 1 +} + +variable "network_count" { + description = "Number of additional networks" + type = number + default = 0 +} + +variable "vpc_name" { + description = "Existing VPC name (if empty, creates new VPC)" + type = string + default = "" +} + +variable "resource_group_id" { + description = "IBM Cloud resource group ID" + type = string + default = "" +} diff --git a/ansible_roles/roles/ibm_create/tasks/add_host_to_groups.yml b/ansible_roles/roles/ibm_create/tasks/add_host_to_groups.yml new file mode 100644 index 00000000..cbfa64e6 --- /dev/null +++ b/ansible_roles/roles/ibm_create/tasks/add_host_to_groups.yml @@ -0,0 +1,53 @@ +--- +# Add created hosts to Ansible groups +- name: reload dynamic vars + include_vars: + file: "{{ working_dir }}/ansible_run_vars.yml" + name: dyn_data + +- name: Add primary host to test_group + add_host: + name: "{{ public_ip_list[0] }}" + groups: test_group + ansible_user: "{{ config_info.test_user }}" + ansible_ssh_private_key_file: "{{ config_info.ssh_key }}" + +- name: Add primary host to install_group + add_host: + name: "{{ public_ip_list[0] }}" + groups: install_group + ansible_user: "{{ config_info.test_user }}" + ansible_ssh_private_key_file: "{{ config_info.ssh_key }}" + +- name: Add host to test_group file + include_role: + name: add_to_host_group + vars: + working_group_file: "{{ working_dir }}/ansible_test_group" + group_name: "test_group_list" + host_to_add: "{{ public_ip_list[0] }}" + +- name: Add secondary host to groups (if exists) + block: + - name: Add secondary host to test_group + add_host: + name: "{{ public_ip_list[1] }}" + groups: test_group + ansible_user: "{{ config_info.test_user }}" + ansible_ssh_private_key_file: "{{ config_info.ssh_key }}" + + - name: Add secondary host to install_group + add_host: + name: "{{ public_ip_list[1] }}" + groups: install_group + ansible_user: "{{ config_info.test_user }}" + ansible_ssh_private_key_file: "{{ config_info.ssh_key }}" + + - name: Add secondary host to test_group file + include_role: + name: add_to_host_group + vars: + working_group_file: "{{ working_dir }}/ansible_test_group" + group_name: "test_group_list" + host_to_add: "{{ public_ip_list[1] }}" + when: public_ip_list | length > 1 diff --git a/ansible_roles/roles/ibm_create/tasks/main.yml b/ansible_roles/roles/ibm_create/tasks/main.yml new file mode 100644 index 00000000..07ad39d2 --- /dev/null +++ b/ansible_roles/roles/ibm_create/tasks/main.yml @@ -0,0 +1,149 @@ +--- +# tasks file for ibm_create + +- name: include info + include_vars: + file: "{{ working_dir }}/ansible_vars.yml" + +- name: move terraform files + copy: + src: "tf/{{ item }}" + dest: "{{ working_dir }}/tf/" + loop: + - main.tf + - vars.tf + - output.tf + - network.tf + +- name: append custom vars to vars.tf + shell: "cat {{ working_dir }}/add_vars_tf >> {{ working_dir }}/tf/vars.tf" + +- name: Get IBM Cloud resource group + shell: ibmcloud resource groups --output json | jq -r '.[0].id' + register: resource_group_id + ignore_errors: yes + +- name: Get IBM Cloud SSH key name + shell: | + # Try to find a key with "zathras" in the name first (most specific) + zathras_key=$(ibmcloud is keys --output json | jq -r '.[] | select(.name | contains("zathras")) | .name' | head -1) + if [ -n "$zathras_key" ]; then + echo "$zathras_key" + exit 0 + fi + # Try to find a key matching the username pattern + user_key=$(ibmcloud is keys --output json | jq -r '.[] | select(.name | contains("'{{ config_info.user_running }}'")) | .name' | head -1) + if [ -n "$user_key" ]; then + echo "$user_key" + exit 0 + fi + # Fall back to first available key + first_key=$(ibmcloud is keys --output json | jq -r '.[0].name') + if [ -n "$first_key" ] && [ "$first_key" != "null" ]; then + echo "$first_key" + exit 0 + fi + exit 1 + register: ibm_ssh_key_name + failed_when: false + +- name: Fail if no SSH key found in IBM Cloud + fail: + msg: | + No SSH key found in IBM Cloud. + Please create an SSH key first: + ibmcloud is key-create zathras-key @~/.ssh/id_rsa.pub + Or list existing keys: + ibmcloud is keys + when: ibm_ssh_key_name.rc != 0 or ibm_ssh_key_name.stdout == '' + +- name: Set SSH key name + set_fact: + ibm_ssh_key_name_value: "{{ ibm_ssh_key_name.stdout }}" + +- name: create private copy of tfvars template + copy: + src: "../templates/tfvars.j2" + dest: "{{ working_dir }}/tfvars.j2" + +- name: Add tag vars info + shell: "cat {{ working_dir }}/add_main_tf_vars >> {{ working_dir }}/tfvars.j2" + +- name: Substitute terraform vars + template: + src: "{{ working_dir }}/tfvars.j2" + dest: "{{ working_dir }}/tf/env.tfvars" + +- name: grab create start time + command: "date -u +%s" + register: create_time_start + +- name: IBM Cloud create instance + include_role: + name: tf_create + vars: + tf_var_file: "env.tfvars" + +- name: Record ip addresses in ansible_run_vars.yml + include_tasks: record_ip_info.yml + +- name: Record public IP retrieval failure + include_role: + name: record_status + vars: + results: + failed: true + status_file: "{{ working_dir }}/public_ip_status" + when: public_ip_list is not defined or public_ip_list | length == 0 + +- name: Terminate on public IP retrieval failure + include_role: + name: terminate_on_error + vars: + status_file: "public_ip_status" + exit_msg: | + Failed to retrieve public IP addresses from Terraform. + This usually means the Terraform outputs are not available. + Check the Terraform state and ensure the infrastructure was created successfully. + when: public_ip_list is not defined or public_ip_list | length == 0 + +- name: wait for ssh to come up + include_role: + name: wait_for_ssh + vars: + hostname: "{{ public_ip_list[0] }}" + +- name: Wait for user account readiness + include_role: + name: wait_for_user_ready + vars: + ssh_key: "{{ config_info.ssh_key }}" + test_user: "{{ config_info.test_user }}" + ip_list: "{{ public_ip_list }}" + +- name: grab create end time + command: "date -u +%s" + register: create_time_end + +- name: Report creation time + lineinfile: + path: "{{ working_dir }}/cloud_timings" + line: "instance start_time: {{ create_time_end.stdout | int - (create_time_start.stdout) | int }}" + create: yes + +- name: Create metadata file for later use + copy: + content: | + --- + ibm_cloud_metadata: + instance_id: "{{ instance_id_list[0] }}" + public_ip: "{{ public_ip_list[0] }}" + private_ip: "{{ private_ip_list[0] }}" + zone: "{{ config_info.cloud_zone }}" + region: "{{ config_info.cloud_region }}" + profile: "{{ config_info.host_or_cloud_inst }}" + image: "{{ config_info.cloud_os_version }}" + dest: "{{ working_dir }}/meta_data.yml" + +- name: Add host to test and install groups + include_tasks: add_host_to_groups.yml diff --git a/ansible_roles/roles/ibm_create/tasks/record_ip_info.yml b/ansible_roles/roles/ibm_create/tasks/record_ip_info.yml new file mode 100644 index 00000000..9b639551 --- /dev/null +++ b/ansible_roles/roles/ibm_create/tasks/record_ip_info.yml @@ -0,0 +1,77 @@ +--- +# Record IP information from Terraform outputs +- name: Get public IPs from terraform + shell: | + terraform workspace select {{ config_info.system_type }}-{{ config_info.run_label }}-{{ config_info.host_or_cloud_inst }} > /dev/null 2>&1 + terraform output -json public_ip + args: + chdir: "{{ working_dir }}/tf" + register: public_ips_raw + ignore_errors: yes + +- name: Get private IPs from terraform + shell: | + terraform workspace select {{ config_info.system_type }}-{{ config_info.run_label }}-{{ config_info.host_or_cloud_inst }} > /dev/null 2>&1 + terraform output -json private_ip + args: + chdir: "{{ working_dir }}/tf" + register: private_ips_raw + ignore_errors: yes + +- name: Get instance IDs from terraform + shell: | + terraform workspace select {{ config_info.system_type }}-{{ config_info.run_label }}-{{ config_info.host_or_cloud_inst }} > /dev/null 2>&1 + terraform output -json instance_id + args: + chdir: "{{ working_dir }}/tf" + register: instance_ids_raw + ignore_errors: yes + +- name: Debug terraform output results + debug: + msg: | + Public IP command result: rc={{ public_ips_raw.rc }}, stdout={{ public_ips_raw.stdout }}, stderr={{ public_ips_raw.stderr }} + Private IP command result: rc={{ private_ips_raw.rc }}, stdout={{ private_ips_raw.stdout }}, stderr={{ private_ips_raw.stderr }} + Instance ID command result: rc={{ instance_ids_raw.rc }}, stdout={{ instance_ids_raw.stdout }}, stderr={{ instance_ids_raw.stderr }} + when: public_ips_raw.rc != 0 or private_ips_raw.rc != 0 or instance_ids_raw.rc != 0 + +- name: set IP lists and instance IDs + set_fact: + public_ip_list: "{{ public_ips_raw.stdout | from_json }}" + private_ip_list: "{{ private_ips_raw.stdout | from_json }}" + instance_id_list: "{{ instance_ids_raw.stdout | from_json }}" + when: public_ips_raw.rc == 0 and private_ips_raw.rc == 0 and instance_ids_raw.rc == 0 + +- name: Record primary public IP as test hostname + lineinfile: + path: "{{ working_dir }}/ansible_run_vars.yml" + regexp: "^test_hostname:" + line: "test_hostname: {{ public_ip_list[0] }}" + when: public_ip_list is defined and public_ip_list | length > 0 + +- name: Record IBM instance ID + lineinfile: + path: "{{ working_dir }}/ansible_run_vars.yml" + regexp: "^ibm_instance_id:" + line: "ibm_instance_id: {{ instance_id_list[0] }}" + when: instance_id_list is defined and instance_id_list | length > 0 + +- name: Record public IPs + lineinfile: + path: "{{ working_dir }}/ansible_run_vars.yml" + regexp: "^public_ips:" + line: "public_ips: {{ public_ip_list }}" + when: public_ip_list is defined + +- name: Record private IPs + lineinfile: + path: "{{ working_dir }}/ansible_run_vars.yml" + regexp: "^private_ips:" + line: "private_ips: {{ private_ip_list }}" + when: private_ip_list is defined and private_ip_list | length > 0 + +- name: Record ssh connection option + lineinfile: + path: "{{ working_dir }}/ansible_run_vars.yml" + regexp: "^ssh_i_option:" + line: "ssh_i_option: -i {{ config_info.ssh_key }}" diff --git a/ansible_roles/roles/ibm_create/templates/tfvars.j2 b/ansible_roles/roles/ibm_create/templates/tfvars.j2 new file mode 100644 index 00000000..3a742990 --- /dev/null +++ b/ansible_roles/roles/ibm_create/templates/tfvars.j2 @@ -0,0 +1,32 @@ +machine_type = "{{ config_info.host_or_cloud_inst }}" + +cloud_os_version = "{{ config_info.cloud_os_version }}" + +run_label = "{{ config_info.user_running }}-{{ config_info.run_label | lower() | replace ('.','-',63) | replace('_','-',63) | replace('/','-',63) }}" + +region = "{{ config_info.cloud_region }}" + +zone = "{{ config_info.cloud_zone }}" + +test_user = "{{ config_info.test_user }}" + +ssh_key_name = "{{ ibm_ssh_key_name_value }}" + +vm_image = "{{ config_info.cloud_os_version }}" + +{% if resource_group_id is defined and resource_group_id.stdout is defined and resource_group_id.stdout != "" %} +resource_group_id = "{{ resource_group_id.stdout }}" +{% endif %} + +{% if config_info.vpc_name is defined and config_info.vpc_name != "" %} +vpc_name = "{{ config_info.vpc_name }}" +{% endif %} + +# Check "cloud_numb_networks", default value is "1" for both vars +{% if config_info.cloud_numb_networks is defined and config_info.cloud_numb_networks >= 1 %} +vm_count = 2 +network_count = {{ config_info.cloud_numb_networks }} +{% else %} +vm_count = 1 +network_count = 0 +{% endif %} diff --git a/ansible_roles/roles/ibm_vpc_create/files/tf/disks.tf b/ansible_roles/roles/ibm_vpc_create/files/tf/disks.tf new file mode 100644 index 00000000..1f97038c --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/files/tf/disks.tf @@ -0,0 +1,25 @@ +# Create regular data volumes +resource "ibm_is_volume" "data_volume" { + count = var.vm_count * var.disk_count + name = "${var.run_label}-volume-${format("%02d", count.index)}" + profile = var.disk_profile + zone = var.zone + capacity = var.disk_size + resource_group = data.ibm_resource_group.resource_group.id + tags = local.tags +} + +# Attach volumes to instances +resource "ibm_is_instance_volume_attachment" "volume_attachment" { + count = var.vm_count * var.disk_count + instance = element( + ibm_is_instance.instance.*.id, + floor(count.index / var.disk_count) + ) + volume = element( + ibm_is_volume.data_volume.*.id, + count.index + ) + name = "${var.run_label}-attachment-${format("%02d", count.index)}" + delete_volume_on_instance_delete = true +} \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/files/tf/main_net.tf b/ansible_roles/roles/ibm_vpc_create/files/tf/main_net.tf new file mode 100644 index 00000000..49776d27 --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/files/tf/main_net.tf @@ -0,0 +1,103 @@ +terraform { + required_providers { + ibm = { + source = "IBM-Cloud/ibm" + version = "~> 1.49.0" + } + } + required_version = ">= 1.0" +} + +provider "ibm" { + region = var.region + # API key authentication via environment variables (IC_API_KEY or IBMCLOUD_API_KEY) +} + +locals { + tags = var.vpc_tags +} + +# Define resource group +data "ibm_resource_group" "resource_group" { + name = var.resource_group +} + +# Create virtual private cloud +resource "ibm_is_vpc" "vpc" { + name = "${var.run_label}-vpc" + resource_group = data.ibm_resource_group.resource_group.id + tags = local.tags +} + +# Create public subnet +resource "ibm_is_subnet" "subnet" { + name = "${var.run_label}-subnet" + vpc = ibm_is_vpc.vpc.id + zone = var.zone + ipv4_cidr_block = "10.240.0.0/24" + resource_group = data.ibm_resource_group.resource_group.id +} + +# Create private subnet +resource "ibm_is_subnet" "private_subnet" { + name = "${var.run_label}-private-subnet" + vpc = ibm_is_vpc.vpc.id + zone = var.zone + ipv4_cidr_block = "10.240.1.0/24" + resource_group = data.ibm_resource_group.resource_group.id +} + +# Create security group +resource "ibm_is_security_group" "security_group" { + name = "${var.run_label}-sg" + vpc = ibm_is_vpc.vpc.id + resource_group = data.ibm_resource_group.resource_group.id +} + +# Create security group rule for SSH +resource "ibm_is_security_group_rule" "security_group_rule_ssh" { + group = ibm_is_security_group.security_group.id + direction = "inbound" + remote = "0.0.0.0/0" + + tcp { + port_min = 22 + port_max = 22 + } +} + +# Create security group rule for outbound traffic +resource "ibm_is_security_group_rule" "security_group_rule_outbound" { + group = ibm_is_security_group.security_group.id + direction = "outbound" + remote = "0.0.0.0/0" +} + +# Get SSH key data +data "ibm_is_ssh_key" "ssh_key" { + name = var.ssh_key_name +} + +# Create instances +resource "ibm_is_instance" "instance" { + count = var.vm_count + name = "${var.run_label}-instance-${format("%02d", count.index)}" + vpc = ibm_is_vpc.vpc.id + zone = var.zone + profile = var.machine_type + image = var.image_name + keys = [data.ibm_is_ssh_key.ssh_key.id] + resource_group = data.ibm_resource_group.resource_group.id + + primary_network_interface { + name = "eth0" + subnet = ibm_is_subnet.subnet.id + security_groups = [ibm_is_security_group.security_group.id] + } + + # Placement group can be configured here if needed + # PRIORITYSPOT - will be replaced by Ansible + # EVICTIONPOLICY - will be replaced by Ansible + + tags = local.tags +} \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/files/tf/main_no_net.tf b/ansible_roles/roles/ibm_vpc_create/files/tf/main_no_net.tf new file mode 100644 index 00000000..f774bcc3 --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/files/tf/main_no_net.tf @@ -0,0 +1,94 @@ +terraform { + required_providers { + ibm = { + source = "IBM-Cloud/ibm" + version = "~> 1.49.0" + } + } + required_version = ">= 1.0" +} + +provider "ibm" { + region = var.region + # API key authentication via environment variables (IC_API_KEY or IBMCLOUD_API_KEY) +} + +locals { + tags = var.vpc_tags +} + +# Define resource group +data "ibm_resource_group" "resource_group" { + name = var.resource_group +} + +# Create virtual private cloud +resource "ibm_is_vpc" "vpc" { + name = "${var.run_label}-vpc" + resource_group = data.ibm_resource_group.resource_group.id + tags = local.tags +} + +# Create public subnet +resource "ibm_is_subnet" "subnet" { + name = "${var.run_label}-subnet" + vpc = ibm_is_vpc.vpc.id + zone = var.zone + ipv4_cidr_block = "10.240.0.0/24" + resource_group = data.ibm_resource_group.resource_group.id +} + +# Create security group +resource "ibm_is_security_group" "security_group" { + name = "${var.run_label}-sg" + vpc = ibm_is_vpc.vpc.id + resource_group = data.ibm_resource_group.resource_group.id +} + +# Create security group rule for SSH +resource "ibm_is_security_group_rule" "security_group_rule_ssh" { + group = ibm_is_security_group.security_group.id + direction = "inbound" + remote = "0.0.0.0/0" + + tcp { + port_min = 22 + port_max = 22 + } +} + +# Create security group rule for outbound traffic +resource "ibm_is_security_group_rule" "security_group_rule_outbound" { + group = ibm_is_security_group.security_group.id + direction = "outbound" + remote = "0.0.0.0/0" +} + +# Get SSH key data +data "ibm_is_ssh_key" "ssh_key" { + name = var.ssh_key_name +} + +# Create instances +resource "ibm_is_instance" "instance" { + count = var.vm_count + name = "${var.run_label}-instance-${format("%02d", count.index)}" + vpc = ibm_is_vpc.vpc.id + zone = var.zone + profile = var.machine_type + image = var.image_name + keys = [data.ibm_is_ssh_key.ssh_key.id] + resource_group = data.ibm_resource_group.resource_group.id + + primary_network_interface { + name = "eth0" + subnet = ibm_is_subnet.subnet.id + security_groups = [ibm_is_security_group.security_group.id] + } + + # Placement group can be configured here if needed + # PRIORITYSPOT - will be replaced by Ansible + # EVICTIONPOLICY - will be replaced by Ansible + + tags = local.tags +} \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/files/tf/network_interface.tf b/ansible_roles/roles/ibm_vpc_create/files/tf/network_interface.tf new file mode 100644 index 00000000..92609767 --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/files/tf/network_interface.tf @@ -0,0 +1,8 @@ +# Create secondary network interface +resource "ibm_is_instance_network_interface" "secondary_interface" { + count = var.vm_count + instance = element(ibm_is_instance.instance.*.id, count.index) + name = "${var.run_label}-nic-private-${format("%02d", count.index)}" + subnet = ibm_is_subnet.private_subnet.id + security_groups = [ibm_is_security_group.security_group.id] +} \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/files/tf/output_net.tf b/ansible_roles/roles/ibm_vpc_create/files/tf/output_net.tf new file mode 100644 index 00000000..ce438d48 --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/files/tf/output_net.tf @@ -0,0 +1,63 @@ +# Output for the instance ID +output "instance_id0" { + value = ibm_is_instance.instance[0].id + description = "The ID of the first instance" +} + +# Output for the instance name +output "instance_name0" { + value = ibm_is_instance.instance[0].name + description = "The name of the first instance" +} + +# Output for the second instance ID (if exists) +output "instance_id1" { + value = var.vm_count > 1 ? ibm_is_instance.instance[1].id : "none" + description = "The ID of the second instance" +} + +# Output for the second instance name (if exists) +output "instance_name1" { + value = var.vm_count > 1 ? ibm_is_instance.instance[1].name : "none" + description = "The name of the second instance" +} + +# Output for the public IP - using floating IP +resource "ibm_is_floating_ip" "floating_ip" { + count = var.vm_count + name = "${var.run_label}-fip-${format("%02d", count.index)}" + target = ibm_is_instance.instance[count.index].primary_network_interface[0].id + resource_group = data.ibm_resource_group.resource_group.id +} + +output "public_ip0" { + value = ibm_is_floating_ip.floating_ip[0].address + description = "The public IP of the first instance" +} + +output "public_ip1" { + value = var.vm_count > 1 ? ibm_is_floating_ip.floating_ip[1].address : "none" + description = "The public IP of the second instance" +} + +# Output for the private IP +output "private_ip0" { + value = ibm_is_instance.instance[0].primary_network_interface[0].primary_ipv4_address + description = "The private IP of the first instance" +} + +output "private_ip1" { + value = var.vm_count > 1 ? ibm_is_instance.instance[1].primary_network_interface[0].primary_ipv4_address : "none" + description = "The private IP of the second instance" +} + +# Output for the secondary private IP (if using network interface) +output "secondary_ip0" { + value = var.network_count > 0 ? ibm_is_instance_network_interface.secondary_interface[0].primary_ipv4_address : "none" + description = "The secondary private IP of the first instance" +} + +output "secondary_ip1" { + value = var.vm_count > 1 && var.network_count > 0 ? ibm_is_instance_network_interface.secondary_interface[1].primary_ipv4_address : "none" + description = "The secondary private IP of the second instance" +} \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/files/tf/output_no_net.tf b/ansible_roles/roles/ibm_vpc_create/files/tf/output_no_net.tf new file mode 100644 index 00000000..faefccc8 --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/files/tf/output_no_net.tf @@ -0,0 +1,30 @@ +# Output for the instance ID +output "instance_id0" { + value = ibm_is_instance.instance[0].id + description = "The ID of the first instance" +} + +# Output for the instance name +output "instance_name0" { + value = ibm_is_instance.instance[0].name + description = "The name of the first instance" +} + +# Output for the public IP - using floating IP +resource "ibm_is_floating_ip" "floating_ip" { + count = var.vm_count + name = "${var.run_label}-fip-${format("%02d", count.index)}" + target = ibm_is_instance.instance[count.index].primary_network_interface[0].id + resource_group = data.ibm_resource_group.resource_group.id +} + +output "public_ip0" { + value = ibm_is_floating_ip.floating_ip[0].address + description = "The public IP of the first instance" +} + +# Output for the private IP +output "private_ip0" { + value = ibm_is_instance.instance[0].primary_network_interface[0].primary_ipv4_address + description = "The private IP of the first instance" +} \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/files/tf/placement b/ansible_roles/roles/ibm_vpc_create/files/tf/placement new file mode 100644 index 00000000..7fff6290 --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/files/tf/placement @@ -0,0 +1,37 @@ +# Create placement group for improved performance and availability +resource "ibm_is_instance_group_manager" "placement_group" { + name = "${var.run_label}-placement-group" + instance_group = ibm_is_instance_group.instance_group.id + manager_type = "autoscale" + enable_manager = true + aggregation_window = 120 + cooldown = 300 + max_membership_count = var.vm_count + 1 + min_membership_count = var.vm_count +} + +resource "ibm_is_instance_group" "instance_group" { + name = "${var.run_label}-instance-group" + instance_template = ibm_is_instance_template.instance_template.id + instance_count = var.vm_count + resource_group = data.ibm_resource_group.resource_group.id + subnets = [ibm_is_subnet.subnet.id] + application_port = 22 + load_balancer = null +} + +resource "ibm_is_instance_template" "instance_template" { + name = "${var.run_label}-instance-template" + image = var.image_name + profile = var.machine_type + resource_group = data.ibm_resource_group.resource_group.id + vpc = ibm_is_vpc.vpc.id + zone = var.zone + keys = [data.ibm_is_ssh_key.ssh_key.id] + + primary_network_interface { + name = "eth0" + subnet = ibm_is_subnet.subnet.id + security_groups = [ibm_is_security_group.security_group.id] + } +} \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/files/tf/vars.tf b/ansible_roles/roles/ibm_vpc_create/files/tf/vars.tf new file mode 100644 index 00000000..2cd1eb3a --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/files/tf/vars.tf @@ -0,0 +1,92 @@ +variable "machine_type" { + type = string + default = "bx2-2x8" +} + +variable "cloud_os_version" { + type = string + default = "none" +} + +variable "cloud_placement" { + type = string +} + +variable "region" { + type = string + default = "us-south" +} + +variable "zone" { + type = string + default = "us-south-1" +} + +variable "resource_group" { + type = string + default = "none" +} + +variable "run_label" { + type = string + default = "none" +} + +variable "test_user" { + type = string + default = "none" +} + +variable "ssh_key_name" { + type = string + default = "none" +} + +# API key authentication via environment variables (IC_API_KEY or IBMCLOUD_API_KEY) +# No need to pass as Terraform variable + +variable "image_name" { + type = string + default = "ibm-redhat-8-4-minimal-amd64-1" +} + +variable "vm_count" { + type = number + default = 1 +} + +variable "network_count" { + type = number + default = 1 +} + + +variable "pb_vol_size" { + type = number + default = 500 +} + +variable "pb_disk_profile" { + type = string + default = "general-purpose" +} + +variable "disk_profile" { + type = string + default = "general-purpose" +} + +variable "disk_count" { + type = number + default = 0 +} + +variable "disk_size" { + type = number + default = 100 +} + +variable "vpc_tags" { + type = list(string) + default = [] +} \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/files/tf/vm_spot_set.tf b/ansible_roles/roles/ibm_vpc_create/files/tf/vm_spot_set.tf new file mode 100644 index 00000000..c5d8551b --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/files/tf/vm_spot_set.tf @@ -0,0 +1,5 @@ +# Note: This is an appendable file to add spot instance configuration +# These properties are meant to be added to the ibm_is_instance resource in main.tf + + PRIORITYSPOT + EVICTIONPOLICY \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/tasks/add_host_to_groups.yml b/ansible_roles/roles/ibm_vpc_create/tasks/add_host_to_groups.yml new file mode 100644 index 00000000..474ad568 --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/tasks/add_host_to_groups.yml @@ -0,0 +1,35 @@ +# +# Add the instance to a group to use for installing software, etc. +# +- name: Add host to install_group + add_host: + name: "{{ public_ip_address[0] }}" + groups: install_group + ansible_user: "{{ config_info.test_user }}" + ansible_ssh_private_key_file: "{{ config_info.ssh_key }}" + +- name: Add host to test_group + add_host: + name: "{{ public_ip_address[0] }}" + groups: test_group + ansible_user: "{{ config_info.test_user }}" + ansible_ssh_private_key_file: "{{ config_info.ssh_key }}" + +# +# Add the host to the group files +# +- name: Update install_group file + include_role: + name: add_to_host_group + vars: + working_group_file: "{{ working_dir }}/ansible_install_group" + group_name: "install_group_list" + host_to_add: "{{ public_ip_address[0] }}" + +- name: Update test_group file + include_role: + name: add_to_host_group + vars: + working_group_file: "{{ working_dir }}/ansible_test_group" + group_name: "test_group_list" + host_to_add: "{{ public_ip_address[0] }}" \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/tasks/disks.yml b/ansible_roles/roles/ibm_vpc_create/tasks/disks.yml new file mode 100644 index 00000000..73e258a0 --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/tasks/disks.yml @@ -0,0 +1,33 @@ +# +# Set up disks in IBM VPC +# +- name: Get the disk information + set_fact: + disk_info: "{{ config_info.cloud_disks | replace('[','') | replace(']','') }}" + +- name: Parse disk info + set_fact: + disk_list: "{{ disk_info.split(',') }}" + +- name: Get each disk's information + set_fact: + disk_values: "{{ item.split(':') }}" + loop: "{{ disk_list }}" + register: disk_info_parsed + +- name: Set up disks + block: + - name: template over disk count and size + template: + src: "tfvars_disks.j2" + dest: "{{ working_dir }}/tf/env_disks.tfvars" + vars: + disk_count: "{{ disk_values_in.0 }}" + disk_size: "{{ disk_values_in.2 }}" + disk_type: "{{ disk_values_in.1 }}" + - name: Add to current tfvars + shell: "cat {{ working_dir }}/tf/env_disks.tfvars >> {{ working_dir }}/tf/env.tfvars" + vars: + disk_values_in: "{{ item.ansible_facts.disk_values }}" + with_items: "{{ disk_info_parsed.results }}" + when: item.ansible_facts.disk_values[0] | int > 0 \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/tasks/main.yml b/ansible_roles/roles/ibm_vpc_create/tasks/main.yml new file mode 100644 index 00000000..de43c7f5 --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/tasks/main.yml @@ -0,0 +1,145 @@ +# +# Create the IBM VPC instance +# + +# +# Set up the various terraform files. +# +- name: copy general tf files, no network + block: + - name: copy main.tf to tf/main.tf + copy: + src: "tf/main_no_net.tf" + dest: "{{ working_dir }}/tf/main.tf" + - name: cat non spot machine terraform + shell: "cat {{ config_info.local_run_dir }}/ansible_roles/roles/ibm_vpc_create/files/tf/vm_spot_set.tf | sed \"s/PRIORITYSPOT//g\" | sed \"s/EVICTIONPOLICY//g\" >> {{ working_dir }}/tf/main.tf" + when: config_info.spot_start_price == 0 + - name: cat spot machine terraform + shell: "cat {{ config_info.local_run_dir }}/ansible_roles/roles/ibm_vpc_create/files/tf/vm_spot_set.tf | sed \"s/PRIORITYSPOT/auto_delete_volume = true/g\" | sed \"s/EVICTIONPOLICY/auto_delete_volume = true/g\" >> {{ working_dir }}/tf/main.tf" + when: config_info.spot_start_price == 1 + - name: copy output.tf + copy: + src: "tf/output_no_net.tf" + dest: "{{ working_dir }}/tf/output.tf" + when: config_info.cloud_numb_networks|int == 0 + +- name: copy general tf files, network + block: + - name: copy main_net.tf to tf/main.tf + copy: + src: "tf/main_net.tf" + dest: "{{ working_dir }}/tf/main.tf" + - name: copy network_interface.tf to tf/network_interface.tf + copy: + src: "tf/network_interface.tf" + dest: "{{ working_dir }}/tf/network_interface.tf" + - name: cat non spot machine terraform + shell: "cat {{ config_info.local_run_dir }}/ansible_roles/roles/ibm_vpc_create/files/tf/vm_spot_set.tf | sed \"s/PRIORITYSPOT//g\" | sed \"s/EVICTIONPOLICY//g\" >> {{ working_dir }}/tf/main.tf" + when: config_info.spot_start_price == 0 + - name: cat spot machine terraform + shell: "cat {{ config_info.local_run_dir }}/ansible_roles/roles/ibm_vpc_create/files/tf/vm_spot_set.tf | sed \"s/PRIORITYSPOT/auto_delete_volume = true/g\" | sed \"s/EVICTIONPOLICY/auto_delete_volume = true/g\" >> {{ working_dir }}/tf/main.tf" + when: config_info.spot_start_price == 1 + - name: copy output.tf + copy: + src: "tf/output_net.tf" + dest: "{{ working_dir }}/tf/output.tf" + when: config_info.cloud_numb_networks|int >= 1 + +- name: copy general tf vars file + copy: + src: "tf/vars.tf" + dest: "{{ working_dir }}/tf/vars.tf" + +- name: add in vars for networks + include_role: + name: add_networks + +- name: Activate proximity group + copy: + src: "tf/main.tf" + dest: "{{ working_dir }}/tf/main.tf" + when: config_info.cloud_placement == "none" + +- name: Activate placement group + block: + - name: add placement group info + shell: "cat {{ config_info.local_run_dir }}/ansible_roles/roles/ibm_vpc_create/files/tf/placement >> {{ working_dir }}/tf/main.tf" + when: config_info.cloud_placement != "none" + +- name: Handle disks + block: + - name: copy to work location + copy: + src: "tf/disks.tf" + dest: "{{ working_dir }}/tf/disks.tf" + when: config_info.cloud_disks != "[0:na:na:0]" + +- name: include variables etc. + include_vars: + file: "{{ working_dir }}/ansible_vars.yml" + +- name: include net dynamic info + include_vars: + file: "{{ working_dir }}/ansible_run_vars.yml" + name: dyn_data + +- name: Set up terraform vars + include_role: + name: set_up_tf_vars + vars: + cloud_change_to: ibm_vpc + +# +# Set up the terraform variables, includes setting the disk parameters. +# +- name: copy general tf files + shell: "cat {{ working_dir }}/add_vars_tf >> {{ working_dir }}/tf//vars.tf" + +- name: Move main.tf off to the side + shell: "mv {{ working_dir }}/tf/main.tf {{ working_dir }}/main_back.tf" + +- name: Add tag info in to main info, must be in the front. + shell: "cat {{ working_dir }}/add_main_vars.tf {{ working_dir }}/main_back.tf > {{ working_dir }}/tf/main.tf" + +- name: grab ibm vpc create start time + command: "date -u +%s" + register: ibm_vpc_create_time_start + +- name: IBM VPC create server + include_role: + name: tf_create + vars: + tf_var_file: "env.tfvars" + +- name: Record IP addresses + include_tasks: record_ip_info.yml + +# Wait for the server to come up +- name: wait for ssh to come up + include_role: + name: wait_for_ssh + vars: + hostname: "{{ public_ip_address[0] }}" + +- name: Add host(s) to test and install groups + include_tasks: add_host_to_groups.yml + +- name: ssh key exchange + include_role: + name: ssh_key_exchange + vars: + ip_list: "{{ public_ip_address }}" + when: config_info.cloud_numb_networks|int >= 1 + +- name: create end time + command: "date -u +%s" + register: ibm_vpc_create_time_end + +# +# Report how long it took to create the instance. +# +- name: Report creation time + lineinfile: + path: "{{ working_dir }}/cloud_timings" + line: "instance start_time: {{ (ibm_vpc_create_time_end.stdout) | int - (ibm_vpc_create_time_start.stdout) | int }}" + create: yes \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/tasks/record_ip_info.yml b/ansible_roles/roles/ibm_vpc_create/tasks/record_ip_info.yml new file mode 100644 index 00000000..d321c63d --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/tasks/record_ip_info.yml @@ -0,0 +1,60 @@ +# +# Record the ip information for the instance. +# +- name: read in tf values + include_vars: + file: "{{ working_dir }}/tf_results" + name: tf_info + +- name: set host + set_fact: + public_ip_address: "{{ [ tf_info.public_ip0 ] }}" + test_hostname: "{{ tf_info.public_ip0 }}" + vm_host: "{{ config_info.test_user }}@{{ tf_info.public_ip0 }}" + private_ip_address: "{{ [ tf_info.private_ip0 ] }}" + hostname: "{{ tf_info.instance_name0 }}" + vpc_instance_id: "{{ tf_info.instance_id0 }}" + when: config_info.cloud_numb_networks|int == 0 or config_info.cloud_network_type == "public" + +- name: set host + set_fact: + public_ip_address: "{{ [ tf_info.public_ip0, tf_info.public_ip1 ] }}" + test_hostname: "{{ tf_info.public_ip0 }}" + vm_host: "{{ config_info.test_user }}@{{ tf_info.public_ip0 }}" + private_ip_address: "{{ [ tf_info.private_ip0, tf_info.private_ip1 ] }}" + hostname: "{{ tf_info.instance_name0 }}" + vpc_instance_id: "{{ tf_info.instance_id0 }}" + when: config_info.cloud_numb_networks|int > 0 and config_info.cloud_network_type != "public" + +- name: set ssh options up + set_fact: + ssh_i_option: "-i {{ config_info.ssh_key }}" + +- name: recording test hostname + lineinfile: + path: "{{ working_dir }}/ansible_run_vars.yml" + line: "test_hostname: {{ test_hostname }}" + create: yes + +- name: recording ssh i option + lineinfile: + path: "{{ working_dir }}/ansible_run_vars.yml" + line: "ssh_i_option: {{ ssh_i_option }}" + +- name: recording ibm vpc instance id + lineinfile: + path: "{{ working_dir }}/ansible_run_vars.yml" + line: "ibm_vpc_instance_id: {{ vpc_instance_id }}" + +# +# Save off the info in the inventory file +# +- name: ansible config for hosts + copy: + dest: "{{ working_dir }}/inventory" + content: | + [test_group] + {{ test_hostname }} ansible_user={{ config_info.test_user }} ansible_ssh_private_key_file={{ config_info.ssh_key }} + + [install_group] + {{ test_hostname }} ansible_user={{ config_info.test_user }} ansible_ssh_private_key_file={{ config_info.ssh_key }} \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/templates/tfvars.j2 b/ansible_roles/roles/ibm_vpc_create/templates/tfvars.j2 new file mode 100644 index 00000000..2167f0b1 --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/templates/tfvars.j2 @@ -0,0 +1,34 @@ +resource_group = "{{ config_info.cloud_resource_group }}" + +machine_type = "{{ config_info.host_or_cloud_inst }}" +region = "{{ config_info.cloud_region }}" +zone = "{{ config_info.cloud_zone | default(config_info.cloud_region + '-1') }}" + +cloud_os_version = "{{ config_info.cloud_os_version }}" +cloud_placement = "{{ config_info.cloud_placement }}" + +run_label = "{{ config_info.user_running }}-{{ config_info.run_label | lower() | replace ('.','-',63) | replace('_','-',63) | replace('/','-')}}" + +ssh_key_name = "{{ config_info.ssh_key_name }}" + +test_user = "{{ config_info.test_user }}" + +image_name = "{{ config_info.cloud_os_version }}" + +pb_disk_profile = "general-purpose" +pb_disk_count = 1 +pb_vol_size = 500 + +# Check "cloud_numb_networks", default value is "1" for both vars +{% if config_info.cloud_numb_networks >= 1 %} +vm_count = 2 +network_count = "{{ config_info.cloud_numb_networks }}" +{% endif %} + +{% if config_info.vpc_tags is defined and config_info.vpc_tags|length > 0 %} +vpc_tags = [ +{% for tag in config_info.vpc_tags %} + "{{ tag }}", +{% endfor %} +] +{% endif %} \ No newline at end of file diff --git a/ansible_roles/roles/ibm_vpc_create/templates/tfvars_disks.j2 b/ansible_roles/roles/ibm_vpc_create/templates/tfvars_disks.j2 new file mode 100644 index 00000000..59f887ac --- /dev/null +++ b/ansible_roles/roles/ibm_vpc_create/templates/tfvars_disks.j2 @@ -0,0 +1,7 @@ +disk_profile = "general-purpose" +disk_count = {{ disk_count }} +disk_size = {{ disk_size }} + +{% if disk_type == "gp3" %} +disk_profile = "custom" +{% endif %} \ No newline at end of file diff --git a/ansible_roles/roles/wait_for_user_ready/tasks/main.yml b/ansible_roles/roles/wait_for_user_ready/tasks/main.yml new file mode 100644 index 00000000..d2cc0d1a --- /dev/null +++ b/ansible_roles/roles/wait_for_user_ready/tasks/main.yml @@ -0,0 +1,40 @@ +--- +# +# Wait for user account readiness on a remote system via SSH. +# Retries with backoff to handle cloud instances where user +# accounts may not be immediately available after SSH is up. +# +# Required variables: +# ssh_key - path to SSH private key +# test_user - username to check +# ip_list - list of IPs to verify +# +- name: Wait for user account readiness with backoff + block: + - name: Check user account via SSH + shell: | + ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -i {{ ssh_key }} \ + {{ test_user }}@{{ item.1 }} \ + "id {{ test_user }} && echo 'User ready'" + register: user_ssh_ready + until: user_ssh_ready.rc == 0 + retries: "{{ ansible_ssh_retries | default(5) }}" + delay: "{{ item.0 * 2 + 3 }}" + with_indexed_items: + - "{{ ip_list }}" + rescue: + - name: Record user readiness failure + include_role: + name: record_status + vars: + results: "{{ user_ssh_ready }}" + status_file: "{{ working_dir }}/user_ready_status" + + - name: Terminate system on user readiness failure + include_role: + name: terminate_on_error + vars: + status_file: "user_ready_status" + exit_msg: "User account '{{ test_user }}' never became ready. Retries exhausted." diff --git a/bin/burden b/bin/burden index f7e55d06..4478041b 100755 --- a/bin/burden +++ b/bin/burden @@ -132,7 +132,7 @@ gl_git_timeout_set=0 gl_archive_location=$value_not_set gl_error_repo_errors=1 gl_valid_os_vendors="rhel ubuntu amazon suse private none" -gl_valid_system_types="aws azure gcp local" +gl_valid_system_types="aws azure gcp ibm local" gl_package_name=$value_not_set gl_show_tests=0 gl_create_attempts=5 @@ -186,6 +186,7 @@ gl_scenario_to_restore="" gl_selinux_level="enforcing" gl_selinux_state=$value_not_set gl_selinux_state_set=0 +gl_ibm_api_key="" gl_ssh_key_file="" gl_show_os_versions=0; gl_test_def_file="" @@ -469,6 +470,25 @@ gcp_image_lookup() cleanup_and_exit "" 0 } +# +# Show the OS versions available for IBM Cloud for a specific OS vendor. +# +ibm_image_lookup() +{ + if [[ $gl_os_vendor == "ubuntu" ]]; then + os_filter="ubuntu" + elif [[ $gl_os_vendor == "rhel" ]]; then + os_filter="red" + elif [[ $gl_os_vendor == "suse" ]]; then + os_filter="suse" + else + cleanup_and_exit "Unknown os vendor $gl_os_vendor, valid types are ubuntu, rhel, suse" 1 ${gl_system_type} + fi + echo "Pulling requested IBM Cloud OS image information, may take a bit." + ibmcloud is images --visibility public --output json | jq -r '.[] | select(.operating_system.name | contains("'$os_filter'")) | [.name, .id, .operating_system.name, .status] | @tsv' | column -t + cleanup_and_exit "" 0 +} + get_cloud_acct_id() { if [[ $gl_cloud_acct_id == "" ]]; then @@ -574,6 +594,10 @@ cloud_image_lookup() if [ "$gl_system_type" == "gcp" ]; then gcp_image_lookup $gl_os_vendor fi + + if [ "$gl_system_type" == "ibm" ]; then + ibm_image_lookup $gl_os_vendor + fi cleanup_and_exit "Unknown cloud type $gl_system_type" 1 ${gl_system_type} } @@ -932,6 +956,19 @@ obtain_zone() # Note that Azure doesn't always use zones unlike AWS # but we'll set it here anyway. gl_cloud_region_zone=`echo ${azure_zones[${index_in}]}` + elif [[ $gl_system_type == "ibm" ]]; then + # + # IBM Cloud zones within region + # Format: region-zone (e.g., us-south-1, us-south-2, us-south-3) + # + if [[ "$gl_cloud_region" == "" ]]; then + gl_cloud_region="us-south" + fi + # IBM Cloud typically has 3 zones per region + declare -a ibm_zones=("1" "2" "3") + ibm_num_zones=3 + index_in=`echo $RANDOM % $ibm_num_zones | bc` + gl_cloud_region_zone=`echo ${ibm_zones[${index_in}]}` fi } @@ -961,6 +998,13 @@ set_region_zone_defaults() cleanup_and_exit "Unable to retrieve the gcp region" 1 fi gl_cloud_region_zone=`gcloud config list compute/zone --quiet | grep zone | awk '{ print $3 }'` + elif [[ $gl_system_type == "ibm" ]]; then + gl_cloud_region=`ibmcloud target --output json | jq -r '.region.name' 2>/dev/null` + if [ $? -eq 1 ] || [ -z "$gl_cloud_region" ]; then + echo "Warning: Unable to retrieve IBM Cloud region, using default us-south" + gl_cloud_region="us-south" + fi + obtain_zone fi } @@ -1352,6 +1396,9 @@ set_user_name() gl_test_user=${user_name} elif [[ $gl_system_type == "gcp" ]]; then gl_test_user=${user_name} + elif [[ $gl_system_type == "ibm" ]]; then + # IBM Cloud defaults to root + gl_test_user=root else # # Default to root @@ -1453,6 +1500,31 @@ aws_specific_os_version() fi } +# +# IBM Cloud specific OS version handling +# IBM Cloud uses image IDs for OS versions +# +ibm_specific_os_version() +{ + # + # IBM Cloud API key for Terraform. Can come from: + # 1. Scenario file / CLI (--ibm_api_key) + # 2. Environment variable (IC_API_KEY or IBMCLOUD_API_KEY) + # Export IC_API_KEY so Terraform inherits it from the environment. + # + if [[ -n "$gl_ibm_api_key" ]]; then + export IC_API_KEY="${gl_ibm_api_key}" + elif [[ -z "$IC_API_KEY" ]] && [[ -n "$IBMCLOUD_API_KEY" ]]; then + export IC_API_KEY="${IBMCLOUD_API_KEY}" + fi + if [[ -z "$IC_API_KEY" ]]; then + cleanup_and_exit "Error: IBM Cloud API key not set. Provide via --ibm_api_key, scenario file, or export IC_API_KEY/IBMCLOUD_API_KEY" 1 + fi + # For IBM Cloud, we just pass through the image ID + # The image ID should be validated by the user via --show_os_versions + echo " cloud_os_version: ${gl_cloud_os_version}" >> ansible_vars_main.yml +} + # # If spot variables have not been set, then set from the config/spot_price.cfg file. # @@ -1901,6 +1973,8 @@ create_ansible_options() azure_specific_os_version elif [[ $gl_system_type = "aws" ]]; then aws_specific_os_version $system_name + elif [[ $gl_system_type = "ibm" ]]; then + ibm_specific_os_version else echo " cloud_os_version: ${gl_cloud_os_version}" >> ansible_vars_main.yml fi @@ -1918,13 +1992,9 @@ create_ansible_options() fi # # local system type, user is expected to be root. cloud systems are expected - # to be non-root. + # to be non-root. Note: user_parent_home_dir is set after set_user_name + # below, since we need to know the test_user first. # - if [[ $gl_system_type == "local" ]]; then - echo " user_parent_home_dir: /" >> ansible_vars_main.yml - else - echo " user_parent_home_dir: /home" >> ansible_vars_main.yml - fi echo " java_version: ${gl_java_version}" >> ansible_vars_main.yml if [[ ${gl_rhel_tuned_setting} == "none" ]]; then @@ -1961,9 +2031,13 @@ create_ansible_options() elif [[ $gl_system_type == "azure" ]]; then # Azure does not put the zone in the region name echo " cloud_region: ${gl_cloud_region}" >> ansible_vars_main.yml - elif [ $gl_system_type == "gcp" ]; then + elif [[ $gl_system_type == "gcp" ]]; then echo " cloud_region: ${gl_cloud_region}" >> ansible_vars_main.yml echo " cloud_zone: ${gl_cloud_region_zone}" >> ansible_vars_main.yml + elif [[ $gl_system_type == "ibm" ]]; then + # IBM Cloud uses region-zone format (e.g., us-south-1) + echo " cloud_region: ${gl_cloud_region}" >> ansible_vars_main.yml + echo " cloud_zone: ${gl_cloud_region}-${gl_cloud_region_zone}" >> ansible_vars_main.yml fi # # Record items that we need to do. @@ -1999,6 +2073,15 @@ create_ansible_options() echo " test_iterations: ${gl_test_iterations}" >> ansible_vars_main.yml echo " user_running: ${user_name}" >> ansible_vars_main.yml set_user_name $user_name + # + # Set user_parent_home_dir based on test_user (must be AFTER set_user_name) + # Root's home is /root, not /home/root + # + if [[ $gl_test_user == "root" ]] || [[ $gl_system_type == "local" ]]; then + echo " user_parent_home_dir: /" >> ansible_vars_main.yml + else + echo " user_parent_home_dir: /home" >> ansible_vars_main.yml + fi generate_tags $user_name # @@ -3499,6 +3582,12 @@ set_general_value() fi shift_by=2 ;; + --ibm_api_key) + if [[ $gl_ibm_api_key == "" ]]; then + gl_ibm_api_key=$2 + fi + shift_by=2 + ;; --ignore_repo_errors) if [[ $gl_error_repo_errors -eq 1 ]]; then echo "$1" >> $gl_cli_supplied_options @@ -3818,6 +3907,7 @@ grab_cli_data() "dir_index" "git_timeout" "host_config" + "ibm_api_key" "individ_vars" "java_version" "kit_upload_directory" diff --git a/bin/ten_of_us.yml b/bin/ten_of_us.yml index 8edce2b3..019d328d 100644 --- a/bin/ten_of_us.yml +++ b/bin/ten_of_us.yml @@ -108,9 +108,15 @@ - name: create gcp instances block: - include_role: - name: gcp_create_instance + name: gcp_create_instance when: config_info.system_type == "gcp" + - name: create ibm instances + block: + - include_role: + name: ibm_create + when: config_info.system_type == "ibm" + - name: local operations block: - name: local_operations @@ -281,6 +287,27 @@ when: - config_info.system_type == "gcp" + - name: Enable CodeReady Builder for IBM Cloud + block: + - name: Install yum utils + become: yes + shell: "yum install yum-utils -y" + + - name: Enable CodeReady Builder for IBM Cloud + become: yes + shell: "yum-config-manager --enable codeready-builder-for-rhel-9-$(arch)-rpms" + register: yum_config + ignore_errors: yes + + - name: Record status of yum config success + include_role: + name: record_status + vars: + results: "{{ yum_config }}" + status_file: "/tmp/cr_status" + when: + - config_info.system_type == "ibm" + - name: retrieve repo config status include_role: name: retrieve_status @@ -941,7 +968,7 @@ name: tf_delete vars: tf_dir: "tf" - when: config_info.system_type == "aws" or config_info.system_type == "gcp" or config_info.system_type == "azure" + when: config_info.system_type in ['aws', 'gcp', 'azure', 'ibm'] - name: terminate end time command: "date -u +%s" diff --git a/config/coremark_pro_template.yml b/config/coremark_pro_template.yml index 6b37860e..c092aa65 100644 --- a/config/coremark_pro_template.yml +++ b/config/coremark_pro_template.yml @@ -1,5 +1,5 @@ location: https://github.com/redhat-performance/coremark_pro-wrapper/archive/refs/tags -exec_dir: "coremark_pro-wrapper-2.1/coremark_pro" -repo_file: "v2.1.tar.gz" +exec_dir: "coremark_pro-wrapper-2.4/coremark_pro" +repo_file: "v2.4.tar.gz" test_script_to_run: coremark_pro_run test_specific: "--iterations 5" diff --git a/config/pig_template.yml b/config/pig_template.yml index c0d9f8a4..ca88008f 100644 --- a/config/pig_template.yml +++ b/config/pig_template.yml @@ -1,4 +1,4 @@ location: https://github.com/redhat-performance/pig-wrapper/archive/refs/tags -exec_dir: "pig-wrapper-2.1/pig" -repo_file: "v2.1.tar.gz" +exec_dir: "pig-wrapper-2.2/pig" +repo_file: "v2.2.tar.gz" test_script_to_run: run_pig.sh diff --git a/config/pyperformance_template.yml b/config/pyperformance_template.yml index 790ae22f..017d0f10 100644 --- a/config/pyperformance_template.yml +++ b/config/pyperformance_template.yml @@ -1,4 +1,4 @@ location: https://github.com/redhat-performance/pyperf-wrapper/archive/refs/tags -exec_dir: "pyperf-wrapper-2.1/pyperf" -repo_file: "v2.1.tar.gz" +exec_dir: "pyperf-wrapper-2.2/pyperf" +repo_file: "v2.2.tar.gz" test_script_to_run: pyperf_run