
This patch introduces a dynamic workload to delete subports from random trunks. It also refactors existing code in trunk.py to improve readability. Change-Id: Ie0e0f81a1ab035f6964caff11a97477bd3089966
363 lines
17 KiB
Python
363 lines
17 KiB
Python
# 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 random
|
|
|
|
from rally.common import logging
|
|
from rally.common import sshutils
|
|
|
|
from rally_openstack.scenarios.neutron import utils as neutron_utils
|
|
import dynamic_utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
# This test simulates trunk subports using vlan interfaces inside the VM.
|
|
# It creates vlan interface for the subport and then adds it's MAC and ip.
|
|
# After packet reaching the vlan interface, reply packet has to be properly
|
|
# routed. This test adds this route like in this example,
|
|
# If vlan interface inside VM has to reply to jumphost, we add a route to jumphost
|
|
# via this vlan interface device
|
|
#
|
|
# sudo ip link add link eth0 name eth0.1 type vlan id 1
|
|
# sudo ip link set dev eth0.1 address {mac}
|
|
# sudo ip link set dev eth0.1 up
|
|
# sudo ip a a {address}/24 dev eth0.1
|
|
# sudo ip r a {vm2_address} via {gateway} dev eth0.1
|
|
#
|
|
# We can't use cirros image as it doesn't support vlans
|
|
# smallest flavor for centos is m1.tiny-centos (RAM 192, disk 8, vcpu 1)
|
|
#
|
|
# We also have a scenario to add subports to random trunks in this test.
|
|
|
|
|
|
class TrunkDynamicScenario(
|
|
dynamic_utils.NovaUtils, neutron_utils.NeutronScenario
|
|
):
|
|
def add_route_from_vm_to_jumphost(self, local_vm, dest_vm, local_vm_user,
|
|
subport_number, gateway):
|
|
"""Add route from trunk vm to jumphost via trunk subport
|
|
:param local_vm: floating ip of local VM
|
|
:param dest_vm: floating ip of destination VM
|
|
:param local_vm_user: str, ssh user for local VM
|
|
:param subport_number: int, trunk subport on which route is created
|
|
:param gateway: network gateway
|
|
"""
|
|
script = f"sudo ip r a {dest_vm} via {gateway} dev eth0.{subport_number}"
|
|
source_ssh = sshutils.SSH(
|
|
local_vm_user, local_vm, pkey=self.keypair["private"]
|
|
)
|
|
self._wait_for_ssh(source_ssh)
|
|
self._run_command_with_attempts(source_ssh, script)
|
|
|
|
def delete_route_from_vm_to_jumphost(self, local_vm, dest_vm, local_vm_user,
|
|
subport_number, gateway):
|
|
"""Delete route from trunk vm to jumphost via trunk subport
|
|
:param local_vm: floating ip of local VM
|
|
:param dest_vm: floating ip of destination VM
|
|
:param local_vm_user: str, ssh user for local VM
|
|
:param subport_number: int, trunk subport on which route is created
|
|
:param gateway: network gateway
|
|
"""
|
|
script = f"sudo ip r d {dest_vm} via {gateway} dev eth0.{subport_number}"
|
|
source_ssh = sshutils.SSH(
|
|
local_vm_user, local_vm, pkey=self.keypair["private"]
|
|
)
|
|
self._wait_for_ssh(source_ssh)
|
|
self._run_command_with_attempts(source_ssh, script)
|
|
|
|
def ping_subport_fip_from_jumphost(self, local_vm, dest_vm, local_vm_user,
|
|
dest_vm_user, fip, port):
|
|
"""Ping subport floating ip from jumphost
|
|
:param local_vm: floating ip of local VM
|
|
:param dest_vm: floating ip of destination VM
|
|
:param local_vm_user: str, ssh user for local VM
|
|
:param dest_vm_user: str, ssh user for destination VM
|
|
:param fip: floating ip of subport
|
|
:param port: subport to ping from jumphost
|
|
"""
|
|
fip_update_dict = {"port_id": port["id"]}
|
|
self.clients("neutron").update_floatingip(
|
|
fip["id"], {"floatingip": fip_update_dict}
|
|
)
|
|
|
|
address = fip["floating_ip_address"]
|
|
dest_ssh = sshutils.SSH(dest_vm_user, dest_vm, pkey=self.keypair["private"])
|
|
self._wait_for_ssh(dest_ssh)
|
|
cmd = f"ping -c1 -w1 {address}"
|
|
self._run_command_with_attempts(dest_ssh, cmd)
|
|
|
|
def simulate_subport_connection(self, trunk_id, vm_fip, jump_fip):
|
|
"""Simulate connection from jumphost to random subport of trunk VM
|
|
:param trunk_id: id of trunk on which subport is present
|
|
:param vm_fip: floating ip of trunk VM
|
|
:param jump_fip: floating ip of jumphost
|
|
"""
|
|
trunk = self.clients("neutron").show_trunk(trunk_id)
|
|
subport_count = len(trunk["trunk"]["sub_ports"])
|
|
subport_number_for_route = random.randint(1, subport_count)
|
|
subport_for_route = self.clients("neutron").show_port(
|
|
trunk["trunk"]["sub_ports"][subport_number_for_route-1]["port_id"])
|
|
subnet_for_route = self.clients("neutron").show_subnet(
|
|
subport_for_route["port"]["fixed_ips"][0]["subnet_id"])
|
|
self.add_route_from_vm_to_jumphost(vm_fip, jump_fip, self.trunk_vm_user,
|
|
subport_number_for_route,
|
|
subnet_for_route["subnet"]["gateway_ip"])
|
|
subport_fip = self._create_floatingip(self.ext_net_name)["floatingip"]
|
|
self.ping_subport_fip_from_jumphost(vm_fip, jump_fip, self.trunk_vm_user,
|
|
self.jumphost_user, subport_fip,
|
|
subport_for_route["port"])
|
|
# We delete the route from vm to jumphost through the randomly
|
|
# chosen subport after simulate subport connection is executed,
|
|
# as additional subports can be tested for connection in the
|
|
# add_subports_random_trunks function, and we would not want the
|
|
# existing route created here to be used for those subports.
|
|
self.delete_route_from_vm_to_jumphost(vm_fip, jump_fip, self.trunk_vm_user,
|
|
subport_number_for_route,
|
|
subnet_for_route["subnet"]["gateway_ip"])
|
|
# Dissociate floating IP as the same subport can be used again
|
|
# later.
|
|
fip_update_dict = {"port_id": None}
|
|
self.clients("neutron").update_floatingip(
|
|
subport_fip["id"], {"floatingip": fip_update_dict}
|
|
)
|
|
|
|
def get_server_by_trunk(self, trunk):
|
|
"""Get server details for a given trunk
|
|
:param trunk: dict, trunk details
|
|
:returns: floating ip of server
|
|
"""
|
|
trunk_server = self._get_servers_by_tag("trunk:"+str(trunk["id"]))[0]
|
|
trunk_server_fip = list(trunk_server.addresses.values())[0][1]["addr"]
|
|
return trunk_server_fip
|
|
|
|
def get_jumphost_by_trunk(self, trunk):
|
|
"""Get jumphost details for a given trunk
|
|
:param trunk: dict, trunk details
|
|
:returns: floating ip of jumphost
|
|
"""
|
|
if trunk["description"].startswith("jumphost:"):
|
|
jumphost_fip = trunk["description"][9:]
|
|
return jumphost_fip
|
|
|
|
def create_subnets_and_subports(self, subport_count):
|
|
"""Create <<subport_count>> subnets and subports
|
|
:param subport_count: int, number of subports to create
|
|
:returns: list of subnets, list of subports
|
|
"""
|
|
subnets = []
|
|
subports = []
|
|
for _ in range(subport_count):
|
|
net, subnet = self._create_network_and_subnets(network_create_args={})
|
|
subnets.append(subnet[0])
|
|
subports.append(
|
|
self._create_port(
|
|
net,
|
|
{
|
|
"fixed_ips": [{"subnet_id": subnet[0]["subnet"]["id"]}],
|
|
"security_groups": [self.security_group["id"]],
|
|
},
|
|
)
|
|
)
|
|
self._add_interface_router(subnet[0]["subnet"], self.router["router"])
|
|
return subnets, subports
|
|
|
|
def add_subports_to_trunk_and_vm(self, subports, trunk_id, vm_ssh, start_seg_id):
|
|
"""Add subports to trunk and create vlan interfaces for subports inside trunk VM
|
|
:param subports: list, list of subports
|
|
:param trunk_id: id of trunk to add subports
|
|
:param vm_ssh: ssh connection to trunk VM
|
|
:param start_seg_id: int, number of vlan interface to start subport addition
|
|
"""
|
|
# Inside VM, subports are simulated (implemented) using vlan interfaces
|
|
# Later we ping these vlan interfaces
|
|
for seg_id, subport in enumerate(subports,
|
|
start=start_seg_id):
|
|
subport_payload = [
|
|
{
|
|
"port_id": subport["port"]["id"],
|
|
"segmentation_type": "vlan",
|
|
"segmentation_id": seg_id,
|
|
}
|
|
]
|
|
self._add_subports_to_trunk(trunk_id, subport_payload)
|
|
|
|
mac = subport["port"]["mac_address"]
|
|
address = subport["port"]["fixed_ips"][0]["ip_address"]
|
|
# Note: Manually assign ip as calling dnsmasq will also add
|
|
# default route which will break floating ip for the VM
|
|
cmd = f"sudo ip link add link eth0 name eth0.{seg_id} type vlan id {seg_id}"
|
|
self._run_command_with_attempts(vm_ssh, cmd)
|
|
cmd = f"sudo ip link set dev eth0.{seg_id} address {mac}"
|
|
self._run_command_with_attempts(vm_ssh, cmd)
|
|
cmd = f"sudo ip link set dev eth0.{seg_id} up"
|
|
self._run_command_with_attempts(vm_ssh, cmd)
|
|
cmd = f"sudo ip a a {address}/24 dev eth0.{seg_id}"
|
|
self._run_command_with_attempts(vm_ssh, cmd)
|
|
|
|
def pod_fip_simulation(self, ext_net_id, trunk_image, trunk_flavor,
|
|
jumphost_image, jumphost_flavor, subport_count, num_vms=1):
|
|
"""Simulate pods with floating ips using subports on trunks and VMs
|
|
:param ext_net_id: external network ID for floating IP creation
|
|
:param trunk_image: image ID or instance for trunk server creation
|
|
:param trunk_flavor: int, flavor ID or instance for trunk server creation
|
|
:param jumphost_image: image ID or instance for jumphost creation
|
|
:param jumphost_flavor: int, flavor ID or instance for jumphost creation
|
|
:param subport_count: int, number of subports to create per trunk
|
|
:param num_vms: int, number of servers to create
|
|
"""
|
|
self.ext_net_name = None
|
|
if ext_net_id:
|
|
self.ext_net_name = self.clients("neutron").show_network(ext_net_id)["network"][
|
|
"name"
|
|
]
|
|
|
|
self.trunk_vm_user = "centos"
|
|
self.jumphost_user = "cirros"
|
|
|
|
router_create_args = {}
|
|
router_create_args["name"] = self.generate_random_name()
|
|
router_create_args["tenant_id"] = self.context["tenant"]["id"]
|
|
router_create_args.setdefault(
|
|
"external_gateway_info", {"network_id": ext_net_id, "enable_snat": True}
|
|
)
|
|
self.router = self.admin_clients("neutron").create_router(
|
|
{"router": router_create_args}
|
|
)
|
|
network = self._create_network({})
|
|
subnet = self._create_subnet(network, {})
|
|
self._add_interface_router(subnet["subnet"], self.router["router"])
|
|
|
|
kwargs = {}
|
|
kwargs["nics"] = [{"net-id": network["network"]["id"]}]
|
|
self.keypair = self.context["user"]["keypair"]
|
|
jump_host = self._boot_server_with_fip_and_tag(jumphost_image, jumphost_flavor,
|
|
"jumphost_trunk", True,
|
|
self.ext_net_name,
|
|
key_name=self.keypair["name"],
|
|
**kwargs)
|
|
jump_fip = jump_host[1]["ip"]
|
|
|
|
for _ in range(num_vms):
|
|
kwargs = {}
|
|
# create parent and trunk, boot the VM
|
|
self.security_group = self.context["tenant"]["users"][0]["secgroup"]
|
|
port_creates_args = {"security_groups": [self.security_group["id"]]}
|
|
parent = self._create_port(network, port_creates_args)
|
|
# Using tags for trunk returns an error,
|
|
# so we instead use description.
|
|
trunk_payload = {"port_id": parent["port"]["id"],
|
|
"description": "jumphost:"+str(jump_fip)}
|
|
trunk = self._create_trunk(trunk_payload)
|
|
kwargs["nics"] = [{"port-id": parent["port"]["id"]}]
|
|
vm = self._boot_server_with_fip_and_tag(trunk_image, trunk_flavor,
|
|
"trunk:"+str(trunk["trunk"]["id"]), True,
|
|
self.ext_net_name,
|
|
key_name=self.keypair["name"],
|
|
**kwargs)
|
|
vm_fip = vm[1]["ip"]
|
|
|
|
subnets, subports = self.create_subnets_and_subports(subport_count)
|
|
|
|
vm_ssh = sshutils.SSH(self.trunk_vm_user,
|
|
vm_fip, pkey=self.keypair["private"])
|
|
self._wait_for_ssh(vm_ssh)
|
|
|
|
self.add_subports_to_trunk_and_vm(subports, trunk["trunk"]["id"], vm_ssh, 1)
|
|
|
|
self.simulate_subport_connection(trunk["trunk"]["id"], vm_fip, jump_fip)
|
|
|
|
def add_subports_to_random_trunks(self, num_trunks, subport_count):
|
|
"""Add <<subport_count>> subports to <<num_trunks>> randomly chosen trunks
|
|
:param num_trunks: int, number of trunks to be randomly chosen
|
|
:param subport_count: int, number of subports to add to each trunk
|
|
"""
|
|
trunks = self._list_trunks()
|
|
|
|
random.shuffle(trunks)
|
|
num_trunks = min(num_trunks, len(trunks))
|
|
trunks_to_add_subports = [trunks[i] for i in range(num_trunks)]
|
|
|
|
for trunk in trunks_to_add_subports:
|
|
subnets, subports = self.create_subnets_and_subports(subport_count)
|
|
|
|
trunk_server_fip = self.get_server_by_trunk(trunk)
|
|
jump_fip = self.get_jumphost_by_trunk(trunk)
|
|
|
|
vm_ssh = sshutils.SSH(
|
|
self.trunk_vm_user, trunk_server_fip, pkey=self.keypair["private"]
|
|
)
|
|
self._wait_for_ssh(vm_ssh)
|
|
|
|
self.add_subports_to_trunk_and_vm(subports, trunk["id"],
|
|
vm_ssh, len(trunk["sub_ports"])+1)
|
|
|
|
self.simulate_subport_connection(trunk["id"], trunk_server_fip, jump_fip)
|
|
|
|
def delete_subports_from_random_trunks(self, num_trunks, subport_count):
|
|
"""Delete <<subport_count>> subports from <<num_trunks>> randomly chosen trunks
|
|
:param num_trunks: int, number of trunks to be randomly chosen
|
|
:param subport_count: int, number of subports to add to each trunk
|
|
"""
|
|
trunks = self._list_trunks()
|
|
|
|
eligible_trunks = [trunk for trunk in trunks if len(trunk['sub_ports']) >= subport_count]
|
|
num_trunks = min(num_trunks, len(trunks))
|
|
random.shuffle(eligible_trunks)
|
|
|
|
if len(eligible_trunks) >= num_trunks:
|
|
trunks_to_delete_subports = [eligible_trunks[i] for i in range(num_trunks)]
|
|
else:
|
|
trunks_to_delete_subports = sorted(trunks,
|
|
key=lambda k:-len(k['sub_ports']))[:num_trunks]
|
|
subport_count = len(trunks_to_delete_subports[-1]['sub_ports'])
|
|
|
|
for trunk in trunks_to_delete_subports:
|
|
trunk_server_fip = self.get_server_by_trunk(trunk)
|
|
jump_fip = self.get_jumphost_by_trunk(trunk)
|
|
|
|
vm_ssh = sshutils.SSH(self.trunk_vm_user,
|
|
trunk_server_fip, pkey=self.keypair["private"])
|
|
self._wait_for_ssh(vm_ssh)
|
|
|
|
trunk_subports = trunk['sub_ports']
|
|
num_trunk_subports = len(trunk_subports)
|
|
|
|
# We delete subports from trunks starting from the last subport,
|
|
# instead of randomly. This is because deleting random subports
|
|
# might cause a lot of conflict with the add_subports_to_random_
|
|
# trunks function.
|
|
for subport_number in range(num_trunk_subports-1,
|
|
num_trunk_subports-1-subport_count, -1):
|
|
subport_details = trunk_subports[subport_number]
|
|
subport_to_delete = self.clients("neutron").show_port(subport_details["port_id"])
|
|
subport_payload = [
|
|
{
|
|
"port_id": subport_to_delete["port"]["id"],
|
|
"segmentation_type": "vlan",
|
|
"segmentation_id": subport_number+1,
|
|
}
|
|
]
|
|
|
|
cmd = f"sudo ip link delete eth0.{subport_number+1}"
|
|
self._run_command_with_attempts(vm_ssh,cmd)
|
|
self.clients("neutron").trunk_remove_subports(trunk["id"],
|
|
{"sub_ports": subport_payload})
|
|
self.clients("neutron").delete_port(subport_to_delete["port"]["id"])
|
|
|
|
# Check the number of subports present in trunk after deletion,
|
|
# and simulate subport connection if it is > 0. We use the
|
|
# show_trunk function here to get updated information about the
|
|
# trunk, as the trunk loop variable will have whatever information
|
|
# was valid at the beginning of the loop.
|
|
if len(self.clients("neutron").show_trunk(trunk["id"])["trunk"]["sub_ports"]) > 0:
|
|
self.simulate_subport_connection(trunk["id"], trunk_server_fip, jump_fip)
|