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:
rajeshP524 2024-01-04 11:30:24 +05:30
parent 0024927764
commit 6219f5b0e7
7 changed files with 549 additions and 0 deletions

View File

@ -909,3 +909,31 @@ 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: rhel-9.2
flavor_name: m1.small
num_provider_networks: 20
cidr_prefix: "192.18"
provider_phys_net: "datacentre"
server_client_pairs_per_network: 2
# crucible controller info
hostname_or_ip:
user: root
password:
ext_iface: ens2f1np1
# 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

View File

@ -0,0 +1,133 @@
# 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
import concurrent.futures
import threading
import os
from jinja2 import Environment
from jinja2 import FileSystemLoader
@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 = f"[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 = 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 = 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},client:{index}-{index},userenv:${{client_userenv}} \\\n"
endpoint_str += f" --endpoint remotehost,user:root,host:${server_name},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(self, image, flavor, server_client_pairs_per_network, hostname_or_ip, user, password,
num_provider_networks, client_userenv, tags, profile, num_samples, **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_command = (
f"sshpass -p '{password}' scp ./rally/rally-plugins/workloads/crucible/browbeat* {user}@{hostname_or_ip}:/root/"
)
os.system(scp_command)

View File

@ -0,0 +1,58 @@
{% 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 }}
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}}

View File

@ -0,0 +1,24 @@
---
- hosts: instances
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 necessary repos
copy:
src: /root/rhel9.repo/
dest: /etc/yum.repos.d/rhel9.repo
mode: 0644
- name: copy .ssh dir to root
shell: cp -r /home/cloud-user/.ssh/ /root/ && chmod -R 0600 /root/.ssh/
- name: install podman and wget
shell: yum install wget -y && yum install podman -y

View File

@ -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 }}

View File

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

View File

@ -0,0 +1,236 @@
# 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} {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")