provider network ping simulation

This workload creates a provider vlan network, boots a VM on this
network and then pings this VM.

Asumming this workload runs on undercloud and both undercloud
and compute node use same interface for vlan provider network,
for example, ens7f0 in both undercloud and compute nodes (through
bridge-mappings) is used for vlan provider network.

Ideally for each provider network rally is creating, one vlan
interface on top of ens7f0 should be created on undercloud, so
that undercloud can ping the VM which is on same provider vlan

ip link add link ens7f0 name ens7f0.1 type vlan id 1
ip a a <ipadress_on_provider_net> dev ens7f0.1

However when we want to scale test vlan provider network, we
can't create those many vlan interfaces on undercloud.

This workload uses scapy to build the vlan packet with all
the required content and sends (and receives) icmp packet on the
undercloud's interface (i.e ens7f0 in this example) used for
provider network. It also sends GARP reply so that VM should
be able to resolve the ARP for provider network gateway.

As scapy has to be run as a root user, we need to move scapy
code to a separate python program.
scapy_icmp.py will send the vlan ICMP packet using scapy library
as a root user. Main workload will call this python program
with required arguments.

Change-Id: I2290b06e899b96a2a3f060ea6fedd3323978ebf3
This commit is contained in:
venkata anil 2021-08-02 18:09:31 +05:30
parent f55cd6c8eb
commit e01e264ff4
4 changed files with 204 additions and 0 deletions

View File

@ -458,6 +458,21 @@ workloads:
num_subports: 1
ext_net_id:
file: ./rally/rally-plugins/netcreate-boot/trunk_network_simulation.yml
# provider_phys_net should be provider physical network name.
# Please don't create any vlan interfaces on the physical interface used with this provider type.
# So use a dedicated interface with provider bridge mappings.
# Provide the MAC address of this interface you find in the undercloud.
# Workload will prepare a scapy packet using the interface name and mac.
- name: provider-netcreate-boot-ping
enabled: true
enable_dhcp: true
num_vms: 1
image_name: custom-cirros
flavor_name: m1.tiny-cirros
provider_phys_net: "provider"
iface_name: "ens7f0"
iface_mac: "3c:fd:fe:c1:8c:70"
file: rally/rally-plugins/netcreate-boot/provider_netcreate_nova_boot_ping.yml
- name: plugin-workloads
enabled: false

View File

@ -0,0 +1,79 @@
# 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 os
import subprocess
from rally_openstack import consts
from rally_openstack.scenarios.neutron import utils as neutron_utils
from rally_openstack.scenarios.vm import utils as vm_utils
from rally.task import atomic
from rally.task import scenario
from rally.task import types
from rally.task import validation
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", admin=True)
@scenario.configure(context={"cleanup@openstack": ["neutron", "nova"], "keypair@openstack": {},
"allow_ssh@openstack": None},
name="BrowbeatPlugin.create_provider_net_nova_boot_ping", platform="openstack")
class CreateProviderNetNovaBootPing(vm_utils.VMScenario,
neutron_utils.NeutronScenario):
def run(self, image, flavor, provider_phys_net, iface_name, iface_mac,
num_vms=1, router_create_args=None,
network_create_args=None, subnet_create_args=None, **kwargs):
network = self._create_network(provider_phys_net)
subnet = self._create_subnet(network, subnet_create_args or {})
kwargs["nics"] = [{'net-id': network['network']['id']}]
server = self._boot_server(image, flavor, **kwargs)
# ping server
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 succesful".format(server_ip))
else:
LOG.info("Ping to {} is failed".format(server_ip))
@atomic.action_timer("neutron.create_network")
def _create_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})

View File

@ -0,0 +1,46 @@
{% set image_name = image_name or 'cirros' %}
{% set flavor_name = flavor_name or 'm1.xtiny' %}
{% set num_vms = num_vms or 1 %}
{% 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 %}
---
BrowbeatPlugin.create_provider_net_nova_boot_ping:
-
args:
floating: True
flavor:
name: '{{flavor_name}}'
image:
name: '{{image_name}}'
num_vms: {{num_vms}}
provider_phys_net: '{{provider_phys_net}}'
iface_name: '{{iface_name}}'
iface_mac: '{{iface_mac}}'
network_create_args: {}
router_create_args: {}
subnet_create_args: {}
runner:
concurrency: {{concurrency}}
times: {{times}}
type: "constant"
context:
users:
tenants: 1
users_per_tenant: 1
quotas:
neutron:
network: -1
port: -1
router: -1
subnet: -1
floatingip: -1
nova:
instances: -1
cores: -1
ram: -1
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,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)