Dynamic workloads: for Provider Network

This patch add support for dynamic workloads for
provider network.
1. provider_netcreate_nova_boot_ping: Creates a
   provider network and boot VM and ping
2. provider_net_nova_boot_ping:  Boot a VM on a
   existing provider network and ping
3. provider_net_nova_delete: Deletes All VM's on
   a provider network and then the network

It also install scapy on the undercloud

Change-Id: Id700c3bcde400398909c8ca9cfd2dd1451b8eb26
This commit is contained in:
Asma Syed Hameed 2021-08-16 16:04:45 +05:30
parent cd38bcb85b
commit 76bff93a95
7 changed files with 257 additions and 7 deletions

View File

@ -82,6 +82,13 @@
- debug: msg="Browbeat directory exists already."
when: browbeat_exists.stat.isdir is defined and browbeat_exists.stat.isdir
- name: install scapy
pip:
name: "scapy"
state: latest
become: true
when: ansible_distribution_major_version >= '8'
- name: Clone browbeat on undercloud
git:
repo: https://github.com/openstack/browbeat.git

View File

@ -577,12 +577,18 @@ workloads:
num_add_subports: 1
num_delete_subports_trunks: 1
num_delete_subports: 1
provider_phys_net: "provider1"
iface_name: "ens7f0"
iface_mac: "3c:fd:fe:c1:73:40"
num_vms_provider_net: 2
# workloads can be 'all', a single workload(Eg. : create_delete_servers),
# or a comma separated string(Eg. : create_delete_servers,migrate_servers).
# Currently supported workloads : create_delete_servers, migrate_servers
# create_loadbalancers, pod_fip_simulation, add_subports_to_random_trunks,
# delete_subports_from_random_trunks, delete_loadbalancers, delete_members_random_lb
# Note: Octavia scenarios are not included in 'all' by default, and have
# to be included separately.
# create_loadbalancers, delete_loadbalancers, delete_members_random_lb,
# pod_fip_simulation, add_subports_to_random_trunks,
# delete_subports_from_random_trunks, provider_netcreate_nova_boot_ping,
# provider_net_nova_boot_ping, provider_net_nova_delete
# Note: Octavia and Provider scenarios are not included in 'all' by default,
# and have to be included separately.
workloads: all
file: rally/rally-plugins/dynamic-workloads/dynamic_workload.yml

View File

@ -32,3 +32,6 @@ Functions:
- _boot_server_with_tag: Boot a server with a tag
- _boot_server_with_fip_and_tag: Boot server prepared for SSH actions, with tag
- _get_servers_by_tag: Retrieve list of servers based on tag
- provider_netcreate_nova_boot_ping: Creates a provider Network and Boots VM and ping
- provider_net_nova_boot_ping: Boots a VM and ping on random existing provider network
- provider_net_nova_delete: Delete all VM's and provider network

View File

@ -17,6 +17,7 @@ from rally.task import validation
import vm
import trunk
import octavia
import provider_network
@types.convert(octavia_image={"type": "glance_image"}, octavia_flavor={"type": "nova_flavor"})
@ -43,13 +44,14 @@ import octavia
platform="openstack",
)
class DynamicWorkload(vm.VMDynamicScenario, trunk.TrunkDynamicScenario,
octavia.DynamicOctaviaBase):
octavia.DynamicOctaviaBase, provider_network.DynamicProviderNetworkBase):
def run(
self, smallest_image, smallest_flavor, ext_net_id, num_vms_to_create_for_migration,
num_vms_to_migrate, trunk_image, trunk_flavor, num_initial_subports, num_trunk_vms,
num_add_subports, num_add_subports_trunks, num_delete_subports, num_delete_subports_trunks,
octavia_image, octavia_flavor, user, user_data_file, num_lbs, num_pools,
num_clients, delete_num_lbs, delete_num_members, num_create_delete_vms, workloads="all",
octavia_image, octavia_flavor, user, user_data_file, num_lbs, num_pools, num_clients,
delete_num_lbs, delete_num_members, num_create_delete_vms, provider_phys_net,
iface_name, iface_mac, num_vms_provider_net, workloads="all",
router_create_args=None, network_create_args=None,
subnet_create_args=None, **kwargs):
@ -85,3 +87,15 @@ class DynamicWorkload(vm.VMDynamicScenario, trunk.TrunkDynamicScenario,
if "delete_members_random_lb" in workloads_list:
self.delete_members_random_lb(delete_num_members)
if "provider_netcreate_nova_boot_ping" in workloads_list:
self.provider_netcreate_nova_boot_ping(smallest_image, smallest_flavor,
provider_phys_net, iface_name,
iface_mac, num_vms_provider_net)
if "provider_net_nova_boot_ping" in workloads_list:
self.provider_net_nova_boot_ping(provider_phys_net, iface_name, iface_mac,
smallest_image, smallest_flavor)
if "provider_net_nova_delete" in workloads_list:
self.provider_net_nova_delete(provider_phys_net)

View File

@ -60,6 +60,10 @@ BrowbeatPlugin.dynamic_workload:
num_create_delete_vms: {{num_create_delete_vms}}
num_vms_to_create_for_migration: {{num_vms_to_create_for_migration}}
num_vms_to_migrate: {{num_vms_to_migrate}}
provider_phys_net: '{{ provider_phys_net }}'
iface_name: '{{ iface_name }}'
iface_mac: '{{ iface_mac }}'
num_vms_provider_net: {{ num_vms_provider_net }}
ext_net_id: '{{ext_net_id}}'
workloads: '{{workloads}}'
runner:

View File

@ -0,0 +1,152 @@
# 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.
import logging
import random
import os
import subprocess
from rally_openstack.scenarios.neutron import utils as neutron_utils
import dynamic_utils
from rally.task import atomic
LOG = logging.getLogger(__name__)
class DynamicProviderNetworkBase(dynamic_utils.NovaUtils, neutron_utils.NeutronScenario):
@atomic.action_timer("neutron.create_network")
def _create_provider_network(self, provider_phys_net):
"""Create neutron provider network.
:param provider_phys_net: provider physical network
:returns: neutron network dict
"""
project_id = self.context["tenant"]["id"]
body = {
"name": self.generate_random_name(),
"tenant_id": project_id,
"provider:network_type": "vlan",
"provider:physical_network": provider_phys_net
}
# provider network can be created by admin client only
return self.admin_clients("neutron").create_network({"network": body})
@atomic.action_timer("neutron.show_network")
def _show_provider_network(self, provider_network):
"""Fetches information of a certain provider network.
:param provider_network: provider network object
"""
return self.admin_clients("neutron").show_network(provider_network['network']['id'])
@atomic.action_timer("neutron.delete_network")
def _delete_provider_network(self, provider_network):
"""Delete neutron provider network.
:param provider_network: provider network object
"""
return self.admin_clients("neutron").delete_network(provider_network['network']['id'])
def ping_server(self, server, iface_name, iface_mac, network, subnet):
"""Ping server created on provider network
:param server: server object
:param iface_name: interface name
:param iface_mac: interface MAC
:param network: provider network object
:param subnet: subnet object
"""
internal_network = list(server.networks)[0]
server_ip = server.addresses[internal_network][0]["addr"]
server_mac = server.addresses[internal_network][0]["OS-EXT-IPS-MAC:mac_addr"]
gateway = subnet['subnet']['gateway_ip']
vlan = network['network']['provider:segmentation_id']
dir_path = os.path.dirname(os.path.realpath(__file__))
file_path = os.path.join(dir_path, "scapy_icmp.py")
cmd = ["sudo", file_path, server_ip, server_mac, gateway, iface_mac, iface_name, str(vlan)]
proc = subprocess.Popen(cmd)
proc.wait()
if proc.returncode == 0:
LOG.info("Ping to {} is successful".format(server_ip))
else:
LOG.info("Ping to {} is failed".format(server_ip))
def provider_netcreate_nova_boot_ping(self, image, flavor, provider_phys_net, iface_name,
iface_mac, num_vms_provider_net, router_create_args=None,
network_create_args=None,
subnet_create_args=None,
**kwargs):
"""Create provider network, Boot and Ping VM
:param image: image ID or image name
:param flavor: flavor ID or flavor name
:param provider_phys_net: provider physical network
:param iface_name: interface name
:param iface_mac: interface MAC
:param num_vms_provider_net: int, number of vm's to create
:param router_create_args: dict, arguments for router creation
:param network_create_args: dict, arguments for network creation
:param subnet_create_args: dict, arguments for subnet creation
:param kwargs: dict, Keyword arguments to function
"""
for _ in range(num_vms_provider_net):
provider_network = self._create_provider_network(provider_phys_net)
subnet = self._create_subnet(provider_network, subnet_create_args or {})
kwargs["nics"] = [{'net-id': provider_network['network']['id']}]
tag = "provider_network:"+str(provider_network['network']['id'])
server = self._boot_server_with_tag(image, flavor, tag, **kwargs)
LOG.info(" Server {} created on provider network {}".format(server, provider_network))
self.ping_server(server, iface_name, iface_mac, provider_network, subnet)
def pick_random_provider_net(self, provider_phys_net, **kwargs):
"""Picks random provider network that exists
:param provider_phys_net: provider physical network
:param kwargs: dict, Keyword arguments to function
"""
kwargs["provider:physical_network"] = provider_phys_net
nets = self._list_networks(**kwargs)
return self._show_provider_network({'network': random.choice(nets)})
def provider_net_nova_boot_ping(self, provider_phys_net, iface_name,
iface_mac, image, flavor, **kwargs):
"""Boot a VM on a random provider network that exists and Ping
:param provider_phys_net: provider physical network
:param iface_name: interface name
:param iface_mac: interface MAC
:param image: image ID or image name
:param flavor: flavor ID or flavor name
:param kwargs: dict, Keyword arguments to function
"""
random_network = self.pick_random_provider_net(provider_phys_net)
kwargs["nics"] = [{'net-id': random_network['network']['id']}]
tag = "provider_network:"+str(random_network['network']['id'])
server = self._boot_server_with_tag(image, flavor, tag, **kwargs)
subnet_id = random_network['network']['subnets'][0]
subnet = self.admin_clients("neutron").show_subnet(subnet_id)
self.ping_server(server, iface_name, iface_mac, random_network, subnet)
def provider_net_nova_delete(self, provider_phys_net, **kwargs):
"""Delete all the VM's on the provider network and then
the network.
:param provider_phys_net: provider physical network
:param kwargs: dict, Keyword arguments to function
"""
random_network = self.pick_random_provider_net(provider_phys_net)
kwargs["nics"] = [{'net-id': random_network['network']['id']}]
tag = "provider_network:"+str(random_network['network']['id'])
servers = self._get_servers_by_tag(tag)
for server in servers:
self._delete_server(server)
self._delete_provider_network(random_network)

View File

@ -0,0 +1,64 @@
#!/usr/bin/python3
# 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.
import logging
import sys
import time
from scapy.all import srp
from scapy.all import Ether
from scapy.all import ARP
from scapy.all import sendp
from scapy.all import Dot1Q
from scapy.all import IP
from scapy.all import ICMP
LOG = logging.getLogger(__name__)
def _send_icmp(dst_ip, dst_mac, src_ip, src_mac, iface, vlan):
# send 1 icmp packet so that dest VM can send ARP packet to resolve gateway
sendp(Ether(dst=dst_mac, src=src_mac)/Dot1Q(vlan=int(vlan))/IP(dst=dst_ip)/ICMP(),
iface=iface, verbose=0)
bcast = "ff:ff:ff:ff:ff:ff"
# Send GARP using ARP reply method
sendp(Ether(dst=bcast,src=src_mac)/Dot1Q(vlan=int(vlan))/ARP(
op=2,psrc=src_ip, hwsrc=src_mac, hwdst=src_mac, pdst=src_ip), iface=iface, verbose=0)
# send ICMP and validate reply
ans, unans = srp(Ether(dst=dst_mac, src=src_mac)/Dot1Q(vlan=int(vlan))/IP(dst=dst_ip)/ICMP(),
iface=iface, timeout=5, verbose=0)
if (str(ans).find('ICMP:0') == -1):
for snd, rcv in ans:
if (rcv.summary().find(dst_ip) != -1):
LOG.info("Ping to {} is succesful".format(dst_ip))
return True
return False
def main(args):
dst_ip, dst_mac, src_ip, src_mac, iface, vlan = args[1:]
attempts = 0
max_attempts = 120
while attempts < max_attempts:
if _send_icmp(dst_ip, dst_mac, src_ip, src_mac, iface, vlan):
LOG.info("Ping to {} is succesful".format(dst_ip))
return 0
LOG.info("Ping to {} is failed, attempt {}".format(dst_ip, attempts))
attempts += 1
time.sleep(5)
return 1
if __name__ == "__main__":
main(sys.argv)