From 90769f6e02d5aedc958866eb498eb30c7f05af8c Mon Sep 17 00:00:00 2001 From: Sanjay Chari Date: Wed, 25 Aug 2021 13:45:46 +0530 Subject: [PATCH] Swap floating IPs between servers dynamic workload This patch introduces the following changes. 1. ping_subport_fip_from_jumphost has been changed to ping_fip_from_jumphost, and moved to dynamic_utils.py, so it can be used in VM dynamic scenarios as well. 2. Swap floating IPs between servers dynamic workload has been introduced in vm.py. 3. Docstrings have been added for all functions in dynamic_utils.py, and security group functions have been moved from NovaUtils to NeutronUtils, as they are more appropriate there. Change-Id: I9cd5afa3b2238c95d4f758abd95168f3942d3621 --- browbeat-config.yaml | 4 +- .../dynamic-workloads/README.rst | 4 +- .../dynamic-workloads/dynamic_utils.py | 98 +++++++++++++++---- .../dynamic-workloads/dynamic_workload.py | 3 + .../rally-plugins/dynamic-workloads/trunk.py | 44 ++------- rally/rally-plugins/dynamic-workloads/vm.py | 68 ++++++++++++- 6 files changed, 163 insertions(+), 58 deletions(-) diff --git a/browbeat-config.yaml b/browbeat-config.yaml index fe996e351..7cbd373c9 100644 --- a/browbeat-config.yaml +++ b/browbeat-config.yaml @@ -585,8 +585,8 @@ workloads: # 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, delete_loadbalancers, delete_members_random_lb, - # pod_fip_simulation, add_subports_to_random_trunks, + # swap_floating_ips_between_servers, create_loadbalancers, delete_loadbalancers, + # delete_members_random_lb, pod_fip_simulation, add_subports_to_random_trunks, # delete_subports_from_random_trunks, swap_floating_ips_between_random_subports, # 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, diff --git a/rally/rally-plugins/dynamic-workloads/README.rst b/rally/rally-plugins/dynamic-workloads/README.rst index dab6273f7..e716f5f91 100644 --- a/rally/rally-plugins/dynamic-workloads/README.rst +++ b/rally/rally-plugins/dynamic-workloads/README.rst @@ -9,6 +9,7 @@ Functions: - _create_router: Create neutron router. - get_servers_migration_list: Generate list of servers to migrate between computes. - migrate_servers_with_fip: Migrate servers between computes +- swap_floating_ips_between_servers: Swap floating IPs between 2 servers - create_loadbalancers: Create 'N' loadbalancers - delete_loadbalancers: Deletes 'M' loadbalancers randomly from 'N' loadbalancers - create_clients: Create 'N' clients @@ -20,10 +21,11 @@ Functions: - build_jump_host: Builds Jump host - _run_command_with_attempts: Run command over ssh connection with multiple attempts - _run_command_until_failure: Run command over ssh connection until failure +- _wait_for_ping_failure: Wait for ping failure to floating IP of server - add_route_from_vm_to_jumphost: Add route from trunk vm to jumphost via trunk subport - delete_route_from_vm_to_jumphost: Delete route from trunk vm to jumphost via trunk subport - get_jumphost_by_trunk: Get jumphost details for a given trunk -- ping_subport_fip_from_jumphost: Ping subport floating ip from jumphost +- assign_ping_fip_from_jumphost: Assign floating IP to port(optional), and ping floating ip from jumphost - create_subnets_and_subports: Create N subnets and subports - add_subports_to_trunk_and_vm: Add subports to trunk and create vlan interfaces for subport inside trunk VM - simulate_subport_connection: Simulate connection from jumphost to random subport of trunk VM diff --git a/rally/rally-plugins/dynamic-workloads/dynamic_utils.py b/rally/rally-plugins/dynamic-workloads/dynamic_utils.py index 23c1d176d..ea894b047 100644 --- a/rally/rally-plugins/dynamic-workloads/dynamic_utils.py +++ b/rally/rally-plugins/dynamic-workloads/dynamic_utils.py @@ -14,6 +14,8 @@ import logging import time from rally.common import cfg +from rally.common import sshutils + from rally_openstack.scenarios.vm import utils as vm_utils from rally_openstack.scenarios.neutron import utils as neutron_utils from rally.task import atomic @@ -30,10 +32,16 @@ logging.getLogger("paramiko").setLevel(logging.WARNING) class NovaUtils(vm_utils.VMScenario): def log_info(self, msg): + """Log information with iteration number + :param msg: str, message to log + """ log_msg = " DYNAMIC_WORKLOADS ITER: {} {} ".format(self.context["iteration"], msg) LOG.info(log_msg) def log_error(self, msg): + """Log error with iteration number + :param msg: str, message to log + """ log_msg = " DYNAMIC_WORKLOADS ITER: {} {} ".format(self.context["iteration"], msg) LOG.error(log_msg) @@ -76,26 +84,44 @@ class NovaUtils(vm_utils.VMScenario): else: break - def _create_sec_group_rule(self, security_group, protocol): - security_group_rule_args = {} - security_group_rule_args["security_group_id"] = security_group["security_group"]["id"] - security_group_rule_args["direction"] = "ingress" - security_group_rule_args["remote_ip_prefix"] = "0.0.0.0/0" - security_group_rule_args["protocol"] = protocol - if protocol == "tcp": - security_group_rule_args["port_range_min"] = 22 - security_group_rule_args["port_range_max"] = 22 - self.clients("neutron").create_security_group_rule( - {"security_group_rule": security_group_rule_args}) + def assign_ping_fip_from_jumphost(self, jumphost_fip, jumphost_user, + fip, port_id, success_on_ping_failure=False): + """Ping floating ip from jumphost + :param jumphost_fip: floating ip of jumphost + :param jumphost_user: str, ssh user for jumphost + :param fip: floating ip of port + :param port_id: id of port to ping from jumphost + :param success_on_ping_failure: bool, flag to ping till failure/success + """ + keypair = self.context["user"]["keypair"] + if not(success_on_ping_failure): + fip_update_dict = {"port_id": port_id} + self.clients("neutron").update_floatingip( + fip["id"], {"floatingip": fip_update_dict} + ) - def create_sec_group_with_icmp_ssh(self): - security_group_args = {} - security_group_args["name"] = self.generate_random_name() - security_group = self.clients("neutron").create_security_group( - {"security_group": security_group_args}) - self._create_sec_group_rule(security_group, "icmp") - self._create_sec_group_rule(security_group, "tcp") - return security_group["security_group"] + address = fip["floating_ip_address"] + jumphost_ssh = sshutils.SSH(jumphost_user, jumphost_fip, pkey=keypair["private"]) + self._wait_for_ssh(jumphost_ssh) + cmd = f"ping -c1 -w1 {address}" + if success_on_ping_failure: + self._run_command_until_failure(jumphost_ssh, cmd) + else: + self._run_command_with_attempts(jumphost_ssh, cmd) + + @atomic.action_timer("vm.wait_for_ping_failure") + def _wait_for_ping_failure(self, server_ip): + """Wait for ping failure to floating IP of server + :param server_ip: floating IP of server + """ + server = vm_utils.Host(server_ip) + utils.wait_for_status( + server, + ready_statuses=[vm_utils.Host.ICMP_DOWN_STATUS], + update_resource=vm_utils.Host.update_status, + timeout=CONF.openstack.vm_ping_timeout, + check_interval=CONF.openstack.vm_ping_poll_interval + ) def _boot_server_with_tag(self, image, flavor, tag, auto_assign_nic=False, **kwargs): @@ -201,10 +227,16 @@ class NovaUtils(vm_utils.VMScenario): class NeutronUtils(neutron_utils.NeutronScenario): def log_info(self, msg): + """Log information with iteration number + :param msg: str, message to log + """ log_msg = " DYNAMIC_WORKLOADS ITER: {} {} ".format(self.context["iteration"], msg) LOG.info(log_msg) def log_error(self, msg): + """Log error with iteration number + :param msg: str, message to log + """ log_msg = " DYNAMIC_WORKLOADS ITER: {} {} ".format(self.context["iteration"], msg) LOG.error(log_msg) @@ -241,6 +273,34 @@ class NeutronUtils(neutron_utils.NeutronScenario): ) return port_fip + def _create_sec_group_rule(self, security_group, protocol): + """Create rule for security group + :param security_group: security group object to create rule + :param protocol: str, protocol of rule to create + """ + security_group_rule_args = {} + security_group_rule_args["security_group_id"] = security_group["security_group"]["id"] + security_group_rule_args["direction"] = "ingress" + security_group_rule_args["remote_ip_prefix"] = "0.0.0.0/0" + security_group_rule_args["protocol"] = protocol + if protocol == "tcp": + security_group_rule_args["port_range_min"] = 22 + security_group_rule_args["port_range_max"] = 22 + self.clients("neutron").create_security_group_rule( + {"security_group_rule": security_group_rule_args}) + + def create_sec_group_with_icmp_ssh(self): + """Create security group with icmp and ssh rules + :returns: security group dict + """ + security_group_args = {} + security_group_args["name"] = self.generate_random_name() + security_group = self.clients("neutron").create_security_group( + {"security_group": security_group_args}) + self._create_sec_group_rule(security_group, "icmp") + self._create_sec_group_rule(security_group, "tcp") + return security_group["security_group"] + class LockUtils: def acquire_lock(self, object_id): diff --git a/rally/rally-plugins/dynamic-workloads/dynamic_workload.py b/rally/rally-plugins/dynamic-workloads/dynamic_workload.py index 4fefe4833..853cee181 100644 --- a/rally/rally-plugins/dynamic-workloads/dynamic_workload.py +++ b/rally/rally-plugins/dynamic-workloads/dynamic_workload.py @@ -74,6 +74,9 @@ class DynamicWorkload(vm.VMDynamicScenario, trunk.TrunkDynamicScenario, network_create_args, subnet_create_args, **kwargs) self.migrate_servers_with_fip(num_vms_to_migrate) + if workloads == "all" or "swap_floating_ips_between_servers" in workloads_list: + self.swap_floating_ips_between_servers() + if workloads == "all" or "pod_fip_simulation" in workloads_list: self.pod_fip_simulation(ext_net_id, trunk_image, trunk_flavor, smallest_image, smallest_flavor, num_initial_subports, num_trunk_vms) diff --git a/rally/rally-plugins/dynamic-workloads/trunk.py b/rally/rally-plugins/dynamic-workloads/trunk.py index cbd113bb5..f34ed3898 100644 --- a/rally/rally-plugins/dynamic-workloads/trunk.py +++ b/rally/rally-plugins/dynamic-workloads/trunk.py @@ -75,30 +75,6 @@ class TrunkDynamicScenario( self._wait_for_ssh(source_ssh) self._run_command_with_attempts(source_ssh, script) - def ping_subport_fip_from_jumphost(self, dest_vm, dest_vm_user, - fip, port, success_on_ping_failure=False): - """Ping subport floating ip from jumphost - :param dest_vm: floating ip of destination VM - :param dest_vm_user: str, ssh user for destination VM - :param fip: floating ip of subport - :param port: subport to ping from jumphost - :param success_on_ping_failure: bool, flag to ping till failure/success - """ - if not(success_on_ping_failure): - 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}" - if success_on_ping_failure: - self._run_command_until_failure(dest_ssh, cmd) - else: - 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 @@ -120,8 +96,8 @@ class TrunkDynamicScenario( " with fip: {}".format(subport_for_route["port"], subport_fip, trunk["trunk"], vm_fip, jump_fip) self.log_info(msg) - self.ping_subport_fip_from_jumphost(jump_fip, self.jumphost_user, subport_fip, - subport_for_route["port"]) + self.assign_ping_fip_from_jumphost(jump_fip, self.jumphost_user, subport_fip, + subport_for_route["port"]["id"]) # 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 @@ -467,16 +443,16 @@ class TrunkDynamicScenario( msg = "Ping until failure after dissociating subports' floating IPs, before swapping" self.log_info(msg) - self.ping_subport_fip_from_jumphost(jumphost1_fip, self.jumphost_user, subport1_fip, - subport1["port"], True) - self.ping_subport_fip_from_jumphost(jumphost2_fip, self.jumphost_user, subport2_fip, - subport2["port"], True) + self.assign_ping_fip_from_jumphost(jumphost1_fip, self.jumphost_user, subport1_fip, + subport1["port"]["id"], True) + self.assign_ping_fip_from_jumphost(jumphost2_fip, self.jumphost_user, subport2_fip, + subport2["port"]["id"], True) self.log_info("Ping until success by swapping subports' floating IPs") - self.ping_subport_fip_from_jumphost(jumphost1_fip, self.jumphost_user, subport2_fip, - subport1["port"]) - self.ping_subport_fip_from_jumphost(jumphost2_fip, self.jumphost_user, subport1_fip, - subport2["port"]) + self.assign_ping_fip_from_jumphost(jumphost1_fip, self.jumphost_user, subport2_fip, + subport1["port"]["id"]) + self.assign_ping_fip_from_jumphost(jumphost2_fip, self.jumphost_user, subport1_fip, + subport2["port"]["id"]) self.delete_route_from_vm_to_jumphost(trunk_vm1_fip, jumphost1_fip, self.trunk_vm_user, subport1_number_for_route, diff --git a/rally/rally-plugins/dynamic-workloads/vm.py b/rally/rally-plugins/dynamic-workloads/vm.py index 24018a6d3..1d0d23a05 100644 --- a/rally/rally-plugins/dynamic-workloads/vm.py +++ b/rally/rally-plugins/dynamic-workloads/vm.py @@ -81,11 +81,13 @@ class VMDynamicScenario(dynamic_utils.NovaUtils, network = self._create_network(network_create_args or {}) subnet = self._create_subnet(network, subnet_create_args or {}) self._add_interface_router(subnet["subnet"], router["router"]) - for i in range(num_vms): + keypair = self.context["user"]["keypair"] + + for _ in range(num_vms): kwargs["nics"] = [{"net-id": network["network"]["id"]}] guest = self._boot_server_with_fip_and_tag( image, flavor, "migrate_or_swap", - True, ext_net_name, **kwargs + True, ext_net_name, key_name=keypair["name"], **kwargs ) self._wait_for_ping(guest[1]["ip"]) @@ -133,3 +135,65 @@ class VMDynamicScenario(dynamic_utils.NovaUtils, if num_migrated == 0: self.log_info("""No servers which are not under lock, so cannot migrate any servers.""") + + def swap_floating_ips_between_servers(self): + """Swap floating IPs between servers + """ + eligible_servers = self._get_servers_by_tag("migrate_or_swap") + + servers_for_swapping = [] + for server in eligible_servers: + if not self.acquire_lock(server.id): + continue + servers_for_swapping.append(server) + if len(servers_for_swapping) == 2: + break + + if len(servers_for_swapping) < 2: + self.log_info("""Number of unlocked servers not sufficient + for swapping floating IPs between servers""") + return + + kwargs = {"floating_ip_address": list(servers_for_swapping[0].addresses.values()) + [0][1]['addr']} + server1_fip = self._list_floating_ips(**kwargs)["floatingips"][0] + + kwargs = {"floating_ip_address": list(servers_for_swapping[1].addresses.values()) + [0][1]['addr']} + server2_fip = self._list_floating_ips(**kwargs)["floatingips"][0] + + server1_port = server1_fip["port_id"] + server2_port = server2_fip["port_id"] + + fip_update_dict = {"port_id": None} + self.clients("neutron").update_floatingip( + server1_fip["id"], {"floatingip": fip_update_dict}) + self.clients("neutron").update_floatingip( + server2_fip["id"], {"floatingip": fip_update_dict}) + + self.log_info("""Ping until failure after dissociating servers' floating IPs, + before swapping""") + self.log_info("Ping server 1 {} until failure".format(server1_fip["floating_ip_address"])) + self._wait_for_ping_failure(server1_fip["floating_ip_address"]) + self.log_info("Ping server 2 {} until failure".format(server2_fip["floating_ip_address"])) + self._wait_for_ping_failure(server2_fip["floating_ip_address"]) + + # Swap floating IPs between server1 and server2 + fip_update_dict = {"port_id": server2_port} + self.clients("neutron").update_floatingip( + server1_fip["id"], {"floatingip": fip_update_dict} + ) + fip_update_dict = {"port_id": server1_port} + self.clients("neutron").update_floatingip( + server2_fip["id"], {"floatingip": fip_update_dict} + ) + + self.log_info("Ping until success by swapping servers' floating IPs") + self.log_info("Ping server 1 {} until success".format(server1_fip["floating_ip_address"])) + self._wait_for_ping(server1_fip["floating_ip_address"]) + self.log_info("Ping server 2 {} until success".format(server2_fip["floating_ip_address"])) + self._wait_for_ping(server2_fip["floating_ip_address"]) + + # Release locks from servers + self.release_lock(servers_for_swapping[0].id) + self.release_lock(servers_for_swapping[1].id)