Crucible network workload
Added a new workload in browbeat, where in it uses Crucible to run benchmarks. This workload uses Crucible remote endpoint against Openstack VMs. Change-Id: If374ed803fe57f04acaa4c64ae037516eb6f4fa9
This commit is contained in:
parent
0024927764
commit
1e85bfc730
|
@ -909,3 +909,33 @@ workloads:
|
|||
sample: 1
|
||||
ansible_forks: 5
|
||||
file: rally/rally-plugins/pbench-fio/pbench-fio.yml
|
||||
|
||||
- name: Network
|
||||
enabled: false
|
||||
type: rally
|
||||
rally_deployment: overcloud
|
||||
concurrency:
|
||||
- 1
|
||||
times: 1
|
||||
scenarios:
|
||||
- name: crucible-uperf
|
||||
enabled: true
|
||||
image_name: rhel9
|
||||
flavor_name: m1.small
|
||||
num_provider_networks: 10
|
||||
cidr_prefix: "192.18"
|
||||
provider_phys_net: "datacentre"
|
||||
server_client_pairs_per_network: 5
|
||||
# do not append '/' at the end for dir_path
|
||||
crucible_repo_dir_path: /home/stack/crucible_repos
|
||||
# crucible controller info
|
||||
hostname_or_ip:
|
||||
user: root
|
||||
password:
|
||||
ext_iface: eno4
|
||||
# crucible run params
|
||||
client_userenv: "stream8"
|
||||
tags: "browbeat:browbeat"
|
||||
profile_path: "mv_params.json"
|
||||
num_samples: 3
|
||||
file: rally/rally-plugins/workloads/crucible-uperf.yml
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from rally_openstack.common import consts
|
||||
from rally_openstack.task.scenarios.nova import utils as nova_utils
|
||||
from rally_openstack.task.scenarios.neutron import utils as neutron_utils
|
||||
from rally.task import scenario
|
||||
from rally.task import types
|
||||
from rally.task import validation
|
||||
from rally.common import logging
|
||||
|
||||
import concurrent.futures
|
||||
import threading
|
||||
import os
|
||||
from jinja2 import Environment
|
||||
from jinja2 import FileSystemLoader
|
||||
import paramiko
|
||||
import time
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@types.convert(image={"type": "glance_image"}, flavor={"type": "nova_flavor"})
|
||||
@validation.add("image_valid_on_flavor", flavor_param="flavor", image_param="image")
|
||||
@validation.add("required_services", services=[consts.Service.NEUTRON, consts.Service.NOVA])
|
||||
@validation.add("required_platform", platform="openstack", users=True)
|
||||
@validation.add("required_contexts", contexts=("crucible_provider_networks"))
|
||||
@scenario.configure(context={"cleanup@openstack": ["neutron", "nova"],"keypair@openstack": {}},
|
||||
name="BrowbeatPlugin.crucible_uperf",
|
||||
platform="openstack")
|
||||
class CrucibleUperf(nova_utils.NovaScenario, neutron_utils.NeutronScenario):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CrucibleUperf, self).__init__(*args, **kwargs)
|
||||
# Create a lock for the dictionary
|
||||
self.dictionary_lock = threading.Lock()
|
||||
self.server_client_info_dict = {}
|
||||
|
||||
def deploy_vms_per_network(self, image, flavor, provider_network,
|
||||
server_client_pairs_per_network):
|
||||
try:
|
||||
kwargs = {}
|
||||
kwargs["nics"] = [{"net-id": provider_network["id"]}]
|
||||
kwargs["key_name"] = self.context["user"]["keypair"]["name"]
|
||||
|
||||
for i in range(server_client_pairs_per_network):
|
||||
server = self._boot_server(image, flavor, **kwargs)
|
||||
client = self._boot_server(image, flavor, **kwargs)
|
||||
|
||||
# Update server and client names
|
||||
server_name = provider_network["name"] + '_server_' + str(i + 1)
|
||||
client_name = provider_network["name"] + '_client_' + str(i + 1)
|
||||
|
||||
# Acquire lock before updating the dictionary
|
||||
with self.dictionary_lock:
|
||||
self.server_client_info_dict[server_name] = (
|
||||
list(server.addresses.values())[0][0]['addr'])
|
||||
self.server_client_info_dict[client_name] = (
|
||||
list(client.addresses.values())[0][0]['addr'])
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in deploy_vms_per_network: {e}")
|
||||
|
||||
def generate_entry_strings(self, N, perN):
|
||||
inventory_str = "[instances]\n"
|
||||
name_ip_mappings_str = ""
|
||||
endpoint_str = ""
|
||||
for i in range(1, N + 1):
|
||||
for j in range(1, perN + 1):
|
||||
server_name = f"provider_{i}_server_{j}"
|
||||
client_name = f"provider_{i}_client_{j}"
|
||||
|
||||
inventory_str += (
|
||||
f"{self.server_client_info_dict[server_name]} "
|
||||
f"ansible_user=cloud-user ansible_private_key_file=/root/browbeat_pkey\n"
|
||||
)
|
||||
inventory_str += (
|
||||
f"{self.server_client_info_dict[client_name]} "
|
||||
f"ansible_user=cloud-user ansible_private_key_file=/root/browbeat_pkey\n"
|
||||
)
|
||||
|
||||
name_ip_mappings_str += \
|
||||
f"{server_name}=\"{self.server_client_info_dict[server_name]}\"\n"
|
||||
name_ip_mappings_str += \
|
||||
f"{client_name}=\"{self.server_client_info_dict[client_name]}\"\n"
|
||||
|
||||
index = (i - 1) * perN + j
|
||||
endpoint_str += (
|
||||
f" --endpoint remotehost,user:root,host:${client_name},"
|
||||
f"client:{index}-{index},userenv:${{client_userenv}} \\\n"
|
||||
)
|
||||
endpoint_str += (
|
||||
f" --endpoint remotehost,user:root,host:${server_name},"
|
||||
f"server:{index}-{index},userenv:${{client_userenv}} \\\n"
|
||||
)
|
||||
|
||||
endpoint_str = endpoint_str.rstrip('\n').rstrip('\\')
|
||||
return inventory_str, name_ip_mappings_str, endpoint_str
|
||||
|
||||
def render_template(self, env, template, client_userenv, tags, profile, num_samples,
|
||||
name_ip_mappings_str, endpoint_str, output_file_path):
|
||||
rendered_template = template.render(
|
||||
client_userenv=client_userenv,
|
||||
tags=tags,
|
||||
profile=profile,
|
||||
num_samples=num_samples,
|
||||
name_ip_mappings_str=name_ip_mappings_str,
|
||||
endpoint_str=endpoint_str
|
||||
)
|
||||
|
||||
with open(output_file_path, 'w') as file:
|
||||
file.write(rendered_template)
|
||||
|
||||
def run_bootstrap(self, host, username, password):
|
||||
try:
|
||||
with paramiko.SSHClient() as ssh:
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect(host, username=username, password=password)
|
||||
# set permissions on pkey before ansible uses that file
|
||||
ssh.exec_command("/usr/bin/chmod 0600 /root/browbeat_pkey")
|
||||
cmd = "/usr/bin/ansible-playbook -i browbeat_ansible_inventory.ini -f 16 " \
|
||||
"-vvv /root/browbeat_bootstrap.yaml &> /root/browbeat_bootstrap.log"
|
||||
_, stdout, stderr = ssh.exec_command(cmd)
|
||||
|
||||
# Check the exit status of the command
|
||||
if stdout.channel.recv_exit_status() == 0:
|
||||
LOG.info(f"Bootstrap playbook on {host} executed successfully.")
|
||||
return True
|
||||
else:
|
||||
raise Exception(
|
||||
f"Ansible execution failed on {host}. Error: {stderr.read().decode()}"
|
||||
)
|
||||
except Exception as e:
|
||||
raise Exception(f"Error in run_bootstrap: {e}")
|
||||
|
||||
def run_crucible_uperf(self, host, username, password):
|
||||
try:
|
||||
with paramiko.SSHClient() as ssh:
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect(host, username=username, password=password)
|
||||
ssh.exec_command("/usr/bin/chmod 777 /root/browbeat_uperf_script.sh")
|
||||
cmd = "/root/browbeat_uperf_script.sh &> /root/browbeat_uperf_script.log"
|
||||
_, stdout, stderr = ssh.exec_command(cmd)
|
||||
|
||||
# Check the exit status of the command
|
||||
if stdout.channel.recv_exit_status() == 0:
|
||||
LOG.info(f"Crucible uperf script on {host} executed successfully.")
|
||||
return True
|
||||
else:
|
||||
raise Exception(
|
||||
f"Crucible uperf execution failed on {host}."
|
||||
f" Error: {stderr.read().decode()}"
|
||||
)
|
||||
except Exception as e:
|
||||
raise Exception(f"Error in run_crucible_uperf: {e}")
|
||||
|
||||
def copy_crucible_results(self, hostname_or_ip, user, password, crucible_result_dir):
|
||||
cmd = (
|
||||
f"sshpass -p '{password}' scp -r {user}@{hostname_or_ip}:/var/lib/crucible/run/latest/"
|
||||
f" {crucible_result_dir}/"
|
||||
)
|
||||
os.system(cmd)
|
||||
|
||||
def run(self, image, flavor, server_client_pairs_per_network, hostname_or_ip, user, password,
|
||||
num_provider_networks, client_userenv, tags, profile, num_samples,
|
||||
crucible_repo_dir_path, **kwargs):
|
||||
provider_networks = self.context["provider_networks"]
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=16) as executor:
|
||||
network_futures = [
|
||||
executor.submit(
|
||||
self.deploy_vms_per_network, image, flavor, provider_network,
|
||||
server_client_pairs_per_network
|
||||
)
|
||||
for provider_network in provider_networks
|
||||
]
|
||||
concurrent.futures.wait(network_futures)
|
||||
|
||||
for instance_name, ip_address in self.server_client_info_dict.items():
|
||||
print(f"{instance_name}: {ip_address}")
|
||||
|
||||
pkey = self.context["user"]["keypair"]["private"]
|
||||
with open("./rally/rally-plugins/workloads/crucible/browbeat_pkey", 'w') as file:
|
||||
file.write(pkey)
|
||||
|
||||
# prepare ansible inventory and uperf script entry strings
|
||||
inventory_str, name_ip_mappings_str, endpoint_str = \
|
||||
self.generate_entry_strings(num_provider_networks, server_client_pairs_per_network)
|
||||
with open(
|
||||
"./rally/rally-plugins/workloads/crucible/browbeat_ansible_inventory.ini", 'w'
|
||||
) as file:
|
||||
file.write(inventory_str)
|
||||
|
||||
# prepare the script
|
||||
env = Environment(loader=FileSystemLoader(os.getcwd()))
|
||||
template_path = './rally/rally-plugins/workloads/crucible/templates/uperf_script.sh.j2'
|
||||
template = env.get_template(template_path)
|
||||
self.output_file_path = './rally/rally-plugins/workloads/crucible/browbeat_uperf_script.sh'
|
||||
self.render_template(env, template, client_userenv, tags, profile, num_samples,
|
||||
name_ip_mappings_str, endpoint_str, self.output_file_path)
|
||||
|
||||
# copy over scp
|
||||
scp_commands = [
|
||||
f"sshpass -p '{password}' scp ./rally/rally-plugins/workloads/crucible/browbeat* "
|
||||
f"{user}@{hostname_or_ip}:/root/",
|
||||
f"sshpass -p '{password}' scp ./rally/rally-plugins/workloads/crucible/mv_params.json "
|
||||
f"{user}@{hostname_or_ip}:/root/",
|
||||
f"sshpass -p '{password}' scp ./rally/rally-plugins/workloads/crucible/ansible/"
|
||||
f"browbeat* {user}@{hostname_or_ip}:/root/"
|
||||
]
|
||||
|
||||
# Execute scp commands
|
||||
for scp_command in scp_commands:
|
||||
os.system(scp_command)
|
||||
|
||||
# copy repos to crucible controller over ssh
|
||||
repo_names = os.listdir(crucible_repo_dir_path)
|
||||
for repo_name in repo_names:
|
||||
repo = crucible_repo_dir_path + "/" + repo_name
|
||||
scp_command = (
|
||||
f"sshpass -p '{password}' scp {repo} {user}@{hostname_or_ip}:/root/"
|
||||
)
|
||||
os.system(scp_command)
|
||||
|
||||
time.sleep(60)
|
||||
self.run_bootstrap(hostname_or_ip, user, password)
|
||||
self.run_crucible_uperf(hostname_or_ip, user, password)
|
||||
|
||||
# copy results
|
||||
with open('../rally_result_dir_path') as f:
|
||||
rally_result_dir_path = f.readline()
|
||||
crucible_result_dir = rally_result_dir_path + "/crucible/results"
|
||||
os.makedirs(crucible_result_dir)
|
||||
self.copy_crucible_results(hostname_or_ip, user, password, crucible_result_dir)
|
|
@ -0,0 +1,59 @@
|
|||
{% set sla_max_avg_duration = sla_max_avg_duration or 60 %}
|
||||
{% set sla_max_failure = sla_max_failure or 0 %}
|
||||
{% set sla_max_seconds = sla_max_seconds or 60 %}
|
||||
{% set nova_api_version = nova_api_version or 2.74 %}
|
||||
---
|
||||
BrowbeatPlugin.crucible_uperf:
|
||||
-
|
||||
args:
|
||||
flavor:
|
||||
name: '{{ flavor_name }}'
|
||||
image:
|
||||
name: '{{ image_name }}'
|
||||
num_provider_networks: {{ num_provider_networks }}
|
||||
server_client_pairs_per_network: {{ server_client_pairs_per_network }}
|
||||
hostname_or_ip: "{{ hostname_or_ip }}"
|
||||
user: "{{ user }}"
|
||||
password: "{{ password }}"
|
||||
ext_iface: "{{ ext_iface }}"
|
||||
client_userenv: "{{ client_userenv }}"
|
||||
tags: "{{ tags }}"
|
||||
profile: "{{ profile_path }}"
|
||||
num_samples: {{ num_samples }}
|
||||
crucible_repo_dir_path: "{{ crucible_repo_dir_path }}"
|
||||
metadata:
|
||||
test_metadata: "true"
|
||||
runner:
|
||||
concurrency: {{ concurrency }}
|
||||
times: {{ times }}
|
||||
type: 'constant'
|
||||
context:
|
||||
users:
|
||||
tenants: 1
|
||||
users_per_tenant: 8
|
||||
api_versions:
|
||||
nova:
|
||||
version: {{ nova_api_version }}
|
||||
quotas:
|
||||
neutron:
|
||||
network: -1
|
||||
port: -1
|
||||
router: -1
|
||||
subnet: -1
|
||||
nova:
|
||||
instances: -1
|
||||
cores: -1
|
||||
ram: -1
|
||||
crucible_provider_networks:
|
||||
hostname_or_ip: "{{ hostname_or_ip }}"
|
||||
user: "{{ user }}"
|
||||
password: "{{ password }}"
|
||||
ext_iface: "{{ ext_iface }}"
|
||||
num_provider_networks: {{ num_provider_networks }}
|
||||
cidr_prefix: "{{ cidr_prefix }}"
|
||||
provider_phys_net: "{{ provider_phys_net }}"
|
||||
sla:
|
||||
max_avg_duration: {{sla_max_avg_duration}}
|
||||
max_seconds_per_iteration: {{sla_max_seconds}}
|
||||
failure_rate:
|
||||
max: {{sla_max_failure}}
|
|
@ -0,0 +1,62 @@
|
|||
Browbeat-Crucible Network Workload
|
||||
===================================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This document provides instructions for running the Browbeat-Crucible Network workload, which is designed to test network performance using Crucible remotehost endpoint.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
Before running the workload, ensure the following prerequisites are met:
|
||||
|
||||
1. **Crucible Controller Setup**: Set up a Crucible controller on a bare metal host by following the instructions provided in the repository: `crucible <https://github.com/perftool-incubator/crucible>`_. This involves cloning the repository and executing the installation script which is a zero touch installation.
|
||||
|
||||
2. **Masquerade Rule Configuration**: Enable masquerade rule on the host using the following commands:
|
||||
|
||||
sudo sysctl -w net.ipv4.ip_forward=1
|
||||
|
||||
sudo iptables -t nat -A POSTROUTING -o $(sudo ip r | grep default | awk '{print $5}') -j MASQUERADE
|
||||
|
||||
3. **Paramiko Installation**: Install Paramiko version 3.2.0 within the Rally virtual environment. Incorrect Paramiko versions may lead to SSH issues with VMs. Activate the virtual environment and install Paramiko as follows:
|
||||
|
||||
cd browbeat
|
||||
|
||||
source .rally-venv/bin/activate
|
||||
|
||||
pip3 uninstall paramiko
|
||||
|
||||
pip3 install paramiko==3.2.0
|
||||
|
||||
deactivate
|
||||
|
||||
4. **Image Setup**: Download a RHEL qcow2 image and upload it to Glance. Ensure necessary rhel repos are copied to the directory ``/home/stack/crucible_repos``.
|
||||
|
||||
Running the Workload
|
||||
---------------------
|
||||
|
||||
Follow these steps to run the Browbeat-Crucible Network workload:
|
||||
|
||||
1. Edit ``browbeat-config.yaml`` to enable the Network workload and configure parameters according to your requirements. Adjust the parameters under the ``Network`` section, such as ``num_provider_networks``, ``cidr_prefix``, ``server_client_pairs_per_network``, ``hostname_or_ip``, ``user``, ``password``, ``crucible_repo_dir_path``, and ``ext_iface``.
|
||||
|
||||
2. If you have a custom traffic profile, copy it to the directory ``~/browbeat/rally/rally-plugins/workloads/crucible`` as ``mv_params.json``.
|
||||
|
||||
3. Execute the workload
|
||||
|
||||
./browbeat.py rally
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
|
||||
- **num_provider_networks**: Number of provider networks to be created for VM attachment.
|
||||
- **cidr_prefix**: CIDR prefix used for the provider networks.
|
||||
- **server_client_pairs_per_network**: Number of server and client pairs created per network.
|
||||
- **hostname_or_ip**: Crucible controller hostname or IP address.
|
||||
- **user**: Username for Crucible controller (root required).
|
||||
- **password**: Root password for Crucible controller.
|
||||
- **crucible_repo_dir_path**: Path to the directory containing ``.repo`` files specific to the image.
|
||||
- **ext_iface**: External interface of Crucible controller with connectivity to provider networks.
|
||||
|
||||
Adjust these parameters according to your specific requirements.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
- hosts: instances
|
||||
gather_facts: false
|
||||
remote_user: cloud-user
|
||||
become: true
|
||||
vars:
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
||||
tasks:
|
||||
- name: copy dns server
|
||||
copy:
|
||||
src: /etc/resolv.conf
|
||||
dest: /etc/resolv.conf
|
||||
mode: 0644
|
||||
|
||||
- name: Copy .repo files
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: /etc/yum.repos.d/
|
||||
mode: 0644
|
||||
with_fileglob:
|
||||
- /root/*.repo
|
||||
|
||||
- name: copy .ssh dir to root
|
||||
ansible.builtin.shell: cp -r /home/cloud-user/.ssh/ /root/ && chmod -R 0600 /root/.ssh/
|
||||
|
||||
- name: install podman and wget
|
||||
ansible.builtin.shell: yum install wget -y && yum install podman -y
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"global-options": [
|
||||
{
|
||||
"name": "common-params",
|
||||
"params": [
|
||||
{ "arg": "duration", "vals": ["60"] },
|
||||
{ "arg": "protocol", "vals": ["udp"] },
|
||||
{ "arg": "nthreads", "vals": ["1"] },
|
||||
{ "arg": "ifname", "vals": ["eth0"], "role": "server" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"sets": [
|
||||
{
|
||||
"include": "common-params",
|
||||
"params": [
|
||||
{ "arg": "test-type", "vals": ["stream","rr"] },
|
||||
{ "arg": "wsize", "vals": ["64"] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
{{ name_ip_mappings_str }}
|
||||
|
||||
client_userenv="{{ client_userenv }}"
|
||||
tags="{{ tags }}"
|
||||
profile="{{ profile }}"
|
||||
num_samples={{ num_samples }}
|
||||
|
||||
time crucible run uperf \
|
||||
--test-order r \
|
||||
--mv-params $profile \
|
||||
--tags "${tags}" \
|
||||
--num-samples $num_samples \
|
||||
--max-sample-failures 1 \
|
||||
{{ endpoint_str }}
|
|
@ -0,0 +1,54 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Check if script is run as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root (with sudo)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if action is provided
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: $0 [create|delete]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
action=$1 # First command-line argument
|
||||
|
||||
# Set variables
|
||||
ext_iface="{{ ext_iface }}"
|
||||
vlan_base_id=1
|
||||
num_vlans={{ num_vlans }}
|
||||
cidr_prefix="{{ cidr_prefix }}"
|
||||
|
||||
if [ "$action" == "create" ]; then
|
||||
# Create VLAN interfaces
|
||||
ip link set dev $ext_iface up
|
||||
for ((i=0; i<$num_vlans; i++)); do
|
||||
vlan_id=$((vlan_base_id + i))
|
||||
vlan_interface="${ext_iface}.${vlan_id}"
|
||||
|
||||
ip link add link $ext_iface name $vlan_interface type vlan id $vlan_id
|
||||
ip link set dev $vlan_interface up
|
||||
|
||||
external_gateway="${cidr_prefix}.$((vlan_base_id + i)).1/24"
|
||||
ip addr add $external_gateway dev $vlan_interface
|
||||
|
||||
echo "VLAN $vlan_id configuration completed successfully."
|
||||
done
|
||||
echo "All configurations completed successfully."
|
||||
elif [ "$action" == "delete" ]; then
|
||||
# Delete VLAN interfaces
|
||||
for ((i=0; i<$num_vlans; i++)); do
|
||||
vlan_id=$((vlan_base_id + i))
|
||||
vlan_interface="${ext_iface}.${vlan_id}"
|
||||
|
||||
ip link set dev $vlan_interface down
|
||||
ip link delete $vlan_interface
|
||||
|
||||
echo "VLAN $vlan_id removed successfully."
|
||||
done
|
||||
echo "Cleanup completed successfully."
|
||||
else
|
||||
echo "Invalid action. Please provide 'create' or 'delete' as the argument."
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,239 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from rally.task import context
|
||||
from rally.common import logging
|
||||
from rally.common import utils
|
||||
from rally import consts
|
||||
from rally_openstack.common import osclients
|
||||
from rally_openstack.common.wrappers import network as network_wrapper
|
||||
|
||||
import os
|
||||
from jinja2 import Environment
|
||||
from jinja2 import FileSystemLoader
|
||||
import paramiko
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@context.configure(name="crucible_provider_networks", order=1000)
|
||||
class CreateExternalNetworksContext(context.Context):
|
||||
"""This plugin creates provider networks with specified option."""
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
"type": "object",
|
||||
"$schema": consts.JSON_SCHEMA,
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"cidr_prefix": {
|
||||
"type": "string",
|
||||
},
|
||||
"num_provider_networks": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"provider_phys_net": {
|
||||
"type": "string"
|
||||
},
|
||||
"hostname_or_ip": {
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"ext_iface": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def _create_subnet(self, tenant_id, network_id, network_number):
|
||||
"""Create subnet for provider network
|
||||
|
||||
:param tenant_id: ID of tenant
|
||||
:param network_id: ID of provider network
|
||||
:param network_number: int, number for CIDR of subnet
|
||||
:returns: subnet object
|
||||
"""
|
||||
|
||||
subnet_args = {
|
||||
"subnet": {
|
||||
"tenant_id": tenant_id,
|
||||
"network_id": network_id,
|
||||
"name": self.net_wrapper.owner.generate_random_name(),
|
||||
"ip_version": 4,
|
||||
"cidr": "{}.{}.0/24".format(self.cidr_prefix, network_number),
|
||||
"enable_dhcp": True,
|
||||
"gateway_ip": "{}.{}.1".format(self.cidr_prefix, network_number),
|
||||
"allocation_pools": [{"start": "{}.{}.2".format(self.cidr_prefix, network_number),
|
||||
"end": "{}.{}.254".format(
|
||||
self.cidr_prefix, network_number)}]
|
||||
}
|
||||
}
|
||||
return self.net_wrapper.client.create_subnet(subnet_args)["subnet"]
|
||||
|
||||
def get_neutron_client(self):
|
||||
return osclients.Clients(self.context["admin"]["credential"]).neutron()
|
||||
|
||||
def run_ssh_script(self, host, username, password, script_path , action):
|
||||
try:
|
||||
with paramiko.SSHClient() as ssh:
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect(host, username=username, password=password)
|
||||
_, stdout, stderr = ssh.exec_command(f"bash {script_path} {action}")
|
||||
|
||||
# Check the exit status of the command
|
||||
if stdout.channel.recv_exit_status() == 0:
|
||||
LOG.info(f"Script on {host} executed successfully.")
|
||||
return True
|
||||
else:
|
||||
raise Exception(
|
||||
f"Script execution failed on {host}. Error: {stderr.read().decode()}"
|
||||
)
|
||||
except Exception as e:
|
||||
raise Exception(f"Error in run_ssh_script: {e}")
|
||||
|
||||
def render_template(self, env, template, ext_iface, num_vlans, cidr_prefix,
|
||||
output_file_path):
|
||||
rendered_template = template.render(
|
||||
ext_iface=ext_iface,
|
||||
num_vlans=num_vlans,
|
||||
cidr_prefix=cidr_prefix
|
||||
)
|
||||
|
||||
with open(output_file_path, 'w') as file:
|
||||
file.write(rendered_template)
|
||||
|
||||
def setup(self):
|
||||
"""This method is called before the task starts."""
|
||||
self.net_wrapper = network_wrapper.wrap(
|
||||
osclients.Clients(self.context["admin"]["credential"]),
|
||||
self,
|
||||
config=self.config,
|
||||
)
|
||||
self.context["provider_networks"] = []
|
||||
self.context["provider_subnets"] = {}
|
||||
self.num_provider_networks = self.config.get("num_provider_networks", 16)
|
||||
self.cidr_prefix = self.config.get("cidr_prefix", "192.18")
|
||||
self.hostname_or_ip = self.config.get("hostname_or_ip", "hostname.example.com")
|
||||
self.user = self.config.get("user", "root")
|
||||
self.password = self.config.get("password", "passwd")
|
||||
self.ext_iface = self.config.get("ext_iface", "ens2f1np1")
|
||||
num_provider_networks_created = 0
|
||||
|
||||
while num_provider_networks_created < self.num_provider_networks:
|
||||
has_error_occured = False
|
||||
for user, tenant_id in utils.iterate_per_tenants(
|
||||
self.context.get("users", [])
|
||||
):
|
||||
try:
|
||||
kwargs = {
|
||||
"network_create_args": {
|
||||
"provider:network_type": "vlan",
|
||||
"provider:physical_network": self.config.get("provider_phys_net",
|
||||
"datacentre"),
|
||||
"provider:segmentation_id": num_provider_networks_created + 1,
|
||||
"router:external": False,
|
||||
"port_security_enabled": False
|
||||
}
|
||||
}
|
||||
self.context["provider_networks"].append(
|
||||
self.net_wrapper.create_network(tenant_id, **kwargs)
|
||||
)
|
||||
LOG.debug(
|
||||
"Provider network with id '%s' created as part of context"
|
||||
% self.context["provider_networks"][-1]["id"]
|
||||
)
|
||||
# update network name
|
||||
updated_name = 'provider_' + str(num_provider_networks_created + 1)
|
||||
cmd = f"source /home/stack/overcloudrc && openstack network set --name " \
|
||||
f"{updated_name} {self.context['provider_networks'][-1]['id']}"
|
||||
os.system(cmd)
|
||||
self.context["provider_networks"][-1]["name"] = updated_name
|
||||
|
||||
num_provider_networks_created += 1
|
||||
except Exception as e:
|
||||
msg = "Can't create provider network {} as part of context: {}".format(
|
||||
num_provider_networks_created, e
|
||||
)
|
||||
LOG.exception(msg)
|
||||
has_error_occured = True
|
||||
break
|
||||
|
||||
try:
|
||||
subnet = self._create_subnet(tenant_id,
|
||||
self.context["provider_networks"][-1]["id"],
|
||||
num_provider_networks_created)
|
||||
self.context["provider_subnets"][
|
||||
self.context["provider_networks"][-1]["id"]] = subnet
|
||||
LOG.debug(
|
||||
"provider subnet with id '%s' created as part of context"
|
||||
% subnet["id"]
|
||||
)
|
||||
except Exception as e:
|
||||
msg = "Can't create provider subnet {} as part of context: {}".format(
|
||||
num_provider_networks_created, e
|
||||
)
|
||||
LOG.exception(msg)
|
||||
has_error_occured = True
|
||||
break
|
||||
|
||||
if has_error_occured:
|
||||
break
|
||||
|
||||
# prepare the script
|
||||
env = Environment(loader=FileSystemLoader(os.getcwd()))
|
||||
template_path = './rally/rally-plugins/workloads/crucible/templates/vlans.sh.j2'
|
||||
template = env.get_template(template_path)
|
||||
self.output_file_path = './rally/rally-plugins/workloads/crucible/browbeat_vlans.sh'
|
||||
self.render_template(env, template, self.ext_iface, self.num_provider_networks,
|
||||
self.cidr_prefix, self.output_file_path)
|
||||
|
||||
# copy over scp
|
||||
scp_command = (
|
||||
f"sshpass -p '{self.password}' scp {self.output_file_path} "
|
||||
f"{self.user}@{self.hostname_or_ip}:/root/"
|
||||
)
|
||||
os.system(scp_command)
|
||||
# create vlan interfaces
|
||||
self.run_ssh_script(self.hostname_or_ip, self.user, self.password,
|
||||
"/root/browbeat_vlans.sh", "create")
|
||||
|
||||
def cleanup(self):
|
||||
"""This method is called after the task finishes."""
|
||||
for i in range(self.num_provider_networks):
|
||||
try:
|
||||
provider_net = self.context["provider_networks"][i]
|
||||
provider_net_id = provider_net["id"]
|
||||
provider_subnet = self.context["provider_subnets"][provider_net_id]
|
||||
provider_subnet_id = provider_subnet["id"]
|
||||
self.net_wrapper._delete_subnet(provider_subnet_id)
|
||||
LOG.debug(
|
||||
"Provider subnet with id '%s' deleted from context"
|
||||
% provider_subnet_id
|
||||
)
|
||||
self.net_wrapper.delete_network(provider_net)
|
||||
LOG.debug(
|
||||
"Provider network with id '%s' deleted from context"
|
||||
% provider_net_id
|
||||
)
|
||||
except Exception as e:
|
||||
msg = "Can't delete provider network {} from context: {}".format(
|
||||
provider_net_id, e
|
||||
)
|
||||
LOG.warning(msg)
|
||||
|
||||
# delete vlan interfaces
|
||||
self.run_ssh_script(self.hostname_or_ip, self.user, self.password,
|
||||
"/root/browbeat_vlans.sh", "delete")
|
Loading…
Reference in New Issue