neutron-vpnaas/rally-jobs/plugins/vpn_utils.py
elajkat 00ef4b0867 pyupgrade changes for Python3.9+
As discussed at the Epoxy PTG meeting, run an automated
upgrade tool to make code python 3.9+ compliant.

This patch is for pylint.

Result of running:

$ pyupgrade --py39-plus $(git ls-files | grep ".py$")

Fixed PEP8 errors introduced by pyupgrade by running:

$ autopep8 --select=E127,E128,E501 --max-line-length 79 -r \
  --in-place neutron_vpnaas

Also did manual updates as necessary to fix other errors
and warnings after above commands.

Inspired by Octavia and Nova [0].

[0] https://review.opendev.org/c/openstack/nova/+/896986

Change-Id: I610efe1a9f20a2a799c40bcf62b98e65e70f0982
2024-11-14 10:13:53 +01:00

649 lines
24 KiB
Python

# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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 os
import stat
import time
import paramiko
from rally.common import logging
from rally.plugins.openstack.wrappers import network as network_wrapper
from rally.task import utils as task_utils
LOG = logging.getLogger(__name__)
SUBNET_IP_VERSION = 4
START_CIDR = "10.2.0.0/24"
EXT_NET_CIDR = "172.16.1.0/24"
def execute_cmd_over_ssh(host, cmd, private_key):
"""Run the given command over ssh
Using paramiko package, it creates a connection to the given host;
executes the required command on it and returns the output.
:param host: Dictionary of ip, username and password
:param cmd: Command to be run over ssh
:param private_key: path to private key file
:return: Output of the executed command
"""
LOG.debug('EXECUTE COMMAND <%s> OVER SSH', cmd)
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
k = paramiko.RSAKey.from_private_key_file(private_key)
try:
client.connect(host["ip"], username=host["username"], pkey=k)
except paramiko.BadHostKeyException as e:
raise Exception(
"BADHOSTKEY EXCEPTION WHEN CONNECTING TO %s", host["ip"], e)
except paramiko.AuthenticationException as e:
raise Exception(
"AUTHENTICATION EXCEPTION WHEN CONNECTING TO %s", host["ip"], e)
except paramiko.SSHException as e:
raise Exception("SSH EXCEPTION WHEN CONNECTING TO %s", host["ip"], e)
except OSError as e:
raise Exception("SOCKET ERROR WHEN CONNECTING TO %s", host["ip"], e)
LOG.debug("CONNECTED TO HOST <%s>", host["ip"])
try:
stdin, stdout, stderr = client.exec_command(cmd)
return stdout.read().splitlines()
except paramiko.SSHException as e:
raise Exception("SSHEXCEPTION WHEN CONNECTING TO %s", host["ip"], e)
finally:
client.close()
def create_tenant(keystone_client, tenant_suffix):
"""Creates keystone tenant with a random name.
:param keystone_client: keystone client
:param tenant_suffix: suffix name for the tenant
:returns: uuid of the new tenant
"""
tenant_name = "rally_tenant_" + tenant_suffix
LOG.debug("CREATING NEW TENANT %s", tenant_name)
return keystone_client.tenants.create(tenant_name).id
def create_network(neutron_client, neutron_admin_client, network_suffix,
tenant_id=None, DVR_flag=True, ext_net_name=None):
"""Create neutron network, subnet, router
:param neutron_client: neutron client
:param neutron_admin_client: neutron client with admin credentials
:param network_suffix: str, suffix name of the new network
:param tenant_id: uuid of the tenant
:param DVR_flag: True - creates a DVR router
False - creates a non DVR router
:param ext_net_name: external network that is to be used
:return: router, subnet, network, subnet_cidr
"""
subnet_cidr = network_wrapper.generate_cidr(start_cidr=START_CIDR)
def _create_network(neutron_client, network_suffix, is_external=False):
"""Creates neutron network"""
network_name = "rally_network_" + network_suffix
network_args = {"name": network_name,
"router:external": is_external
}
if tenant_id:
network_args["tenant_id"] = tenant_id
LOG.debug("ADDING NEW NETWORK %s", network_name)
return neutron_client.create_network({"network": network_args})
def _create_subnet(neutron_client, rally_network, network_suffix, cidr):
"""Create neutron subnet"""
network_id = rally_network["network"]["id"]
subnet_name = "rally_subnet_" + network_suffix
subnet_args = {"name": subnet_name,
"cidr": cidr,
"network_id": network_id,
"ip_version": SUBNET_IP_VERSION
}
if tenant_id:
subnet_args["tenant_id"] = tenant_id
LOG.debug("ADDING SUBNET %s", subnet_name)
return neutron_client.create_subnet({"subnet": subnet_args})
def _create_router(neutron_client, ext_network_id, rally_subnet, dvr_flag):
"""Create router, set the external gateway and add router interface
:param neutron_client: neutron_client
:param ext_network_id: uuid of the external network
:param rally_subnet: subnet to add router interface
:param dvr_flag: True - creates a DVR router
False - creates a non DVR router
:return: router
"""
router_name = "rally_router_" + network_suffix
gw_info = {"network_id": ext_network_id}
router_args = {"name": router_name,
"external_gateway_info": gw_info
}
if not dvr_flag:
router_args["distributed"] = dvr_flag
if tenant_id:
router_args["tenant_id"] = 'tenant_id'
LOG.debug("ADDING ROUTER %s", router_name)
rally_router = neutron_client.create_router({"router": router_args})
LOG.debug("[%s]: ADDING ROUTER INTERFACE")
neutron_client.add_interface_router(
rally_router['router']["id"],
{"subnet_id": rally_subnet["subnet"]["id"]})
return rally_router
def _get_external_network_id(ext_net_name):
"""Fetch the network id for the given external network, if it exists.
Else fetch the first external network present.
"""
ext_nets = neutron_client.list_networks(
**{'router:external': True})['networks']
ext_nets_searched = [n for n in ext_nets if n['name'] == ext_net_name]
if ext_nets_searched:
return ext_nets_searched[0]['id']
elif ext_nets:
return ext_nets[0]['id']
else:
return None
def _create_external_network():
"""Creat external network and subnet"""
ext_net = _create_network(neutron_admin_client, "public", True)
_create_subnet(neutron_admin_client, ext_net, "public", EXT_NET_CIDR)
return ext_net['network']['id']
ext_network_id = _get_external_network_id(ext_net_name)
if not ext_network_id:
ext_network_id = _create_external_network()
rally_network = _create_network(neutron_client, network_suffix)
rally_subnet = _create_subnet(neutron_client, rally_network,
network_suffix, subnet_cidr)
rally_router = _create_router(neutron_client, ext_network_id,
rally_subnet, DVR_flag)
return rally_router, rally_network, rally_subnet, subnet_cidr
def create_keypair(nova_client, keypair_suffix):
"""Create keypair
:param nova_client: nova_client
:param keypair_suffix: sufix name for the keypair
:return: keypair
"""
keypair_name = "rally_keypair_" + keypair_suffix
LOG.debug("CREATING A KEYPAIR %s", keypair_name)
keypair = nova_client.keypairs.create(keypair_name)
return keypair
def write_key_to_local_path(keypair, local_key_file):
"""Write the private key of the nova instance to a temp file
:param keypair: nova keypair
:param local_key_file: path to private key file
:return:
"""
with open(local_key_file, 'w') as f:
os.chmod(local_key_file, stat.S_IREAD | stat.S_IWRITE)
f.write(keypair.private_key)
def write_key_to_compute_node(keypair, local_path, remote_path, host,
private_key):
"""Write the private key of the nova instance to the compute node
First fetches the private key from the keypair and writes it to a
temporary file in the local machine. It then sftp's the file
to the compute host.
:param keypair: nova keypair
:param local_path: path to private key file of the nova instance in the
local machine
:param remote_path: path where the private key file has to be placed
in the remote machine
:param host: compute host credentials
:param private_key: path to your private key file
:return:
"""
LOG.debug("WRITING PRIVATE KEY TO COMPUTE NODE")
k = paramiko.RSAKey.from_private_key_file(private_key)
write_key_to_local_path(keypair, local_path)
try:
transport = paramiko.Transport(host['ip'], host['port'])
except paramiko.SSHException as e:
raise Exception(
"PARAMIKO TRANSPORT FAILED. CHECK IF THE HOST IP %s AND PORT %s "
"ARE CORRECT %s", host['ip'], host['port'], e)
try:
transport.connect(
username=host['username'], pkey=k)
except paramiko.BadHostKeyException as e:
transport.close()
raise Exception(
"BADHOSTKEY EXCEPTION WHEN CONNECTING TO %s", host["ip"], e)
except paramiko.AuthenticationException as e:
transport.close()
raise Exception("AUTHENTICATION EXCEPTION WHEN CONNECTING TO %s",
host["ip"], e)
except paramiko.SSHException as e:
transport.close()
raise Exception("SSH EXCEPTION WHEN CONNECTING TO %s", host["ip"], e)
LOG.debug("CONNECTED TO HOST <%s>", host["ip"])
try:
sftp_client = paramiko.SFTPClient.from_transport(transport)
sftp_client.put(local_path, remote_path)
except OSError as e:
raise Exception("FILE PATH DOESN'T EXIST", e)
finally:
transport.close()
def create_server(nova_client, keypair, **kwargs):
"""Create nova instance
:param nova_client: nova client
:param keypair: key-pair to allow ssh
:return: new nova instance
"""
# add sec-group
sec_group_name = "rally_secgroup_" + kwargs["sec_group_suffix"]
LOG.debug("ADDING NEW SECURITY GROUP %s", sec_group_name)
secgroup = nova_client.security_groups.create(sec_group_name,
sec_group_name)
# add security rules for SSH and ICMP
nova_client.security_group_rules.create(secgroup.id, from_port=22,
to_port=22, ip_protocol="tcp", cidr="0.0.0.0/0")
nova_client.security_group_rules.create(secgroup.id, from_port=-1,
to_port=-1, ip_protocol="icmp", cidr="0.0.0.0/0")
# boot new nova instance
server_name = "rally_server_" + (kwargs["server_suffix"])
LOG.debug("BOOTING NEW INSTANCE: %s", server_name)
LOG.debug("%s", kwargs["image"])
server = nova_client.servers.create(server_name,
image=kwargs["image"],
flavor=kwargs["flavor"],
key_name=keypair.name,
security_groups=[secgroup.id],
nics=kwargs["nics"])
return server
def assert_server_status(server, **kwargs):
"""Assert server status
:param server: nova server
"""
LOG.debug('WAITING FOR SERVER TO GO ACTIVE')
server = task_utils.wait_for(
server,
is_ready=task_utils.resource_is("ACTIVE"),
update_resource=task_utils.get_from_manager(),
timeout=kwargs["nova_server_boot_timeout"],
check_interval=5)
LOG.debug("SERVER STATUS: %s", server.status)
assert ('ACTIVE' == server.status), ("THE INSTANCE IS NOT IN ACTIVE STATE")
def get_server_ip(nova_client, server_id, network_suffix):
"""Get the ip associated with the nova instance
:param nova_client: nova client
:param server_id: uuid of the nova instance whose ip is required
:param network_suffix: suffix name of the network
:return: ip address of the instance
"""
network_name = "rally_network_" + network_suffix
server_details = nova_client.servers.get(server_id)
server_ip = server_details.addresses[network_name][0]["addr"]
return server_ip
def add_floating_ip(nova_client, server):
"""Associates floating-ip to a server
:param nova_client: nova client
:param server: nova instance
:return: associated floating ip
"""
fip_list = nova_client.floating_ips.list()
for fip in fip_list:
if fip.instance_id is None:
floating_ip = fip
break
else:
LOG.debug("CREATING NEW FLOATING IP")
floating_ip = nova_client.floating_ips.create()
LOG.debug("ASSOCIATING FLOATING IP %s", floating_ip.ip)
nova_client.servers.add_floating_ip(server.id, floating_ip.ip)
return floating_ip
def get_namespace(host, private_key):
"""SSH into the host and get the namespaces
:param host : dictionary of controller/compute node credentials
{ip:x.x.x.x, username:xxx, password:xxx}
:param private_key: path to private key file
:return: namespaces
"""
LOG.debug("GET NAMESPACES")
cmd = "sudo ip netns"
namespaces = execute_cmd_over_ssh(host, cmd, private_key)
LOG.debug("NAMESPACES %s", namespaces)
return namespaces
def wait_for_namespace_creation(namespace_tag, router_id, hosts, private_key,
timeout=60):
"""Wait for the namespace creation
Get into each of the controllers/compute nodes and check which one contains
the snat/qrouter namespace corresponding to rally_router. Sleep for a sec
and repeat until either the namespace is found or the namespace_creation_
time exceeded.
:param namespace_tag: which namespace ("snat_" or "qrouter_")
:param router_id: uuid of the rally_router
:param hosts: controllers or compute hosts
:param private_key: path to private key file
:param timeout: namespace creation time
:return:
"""
start_time = time.time()
while True:
for host in hosts:
namespaces = get_namespace(host, private_key)
for line in namespaces:
if line == (namespace_tag + router_id):
namespace_tag = line
return namespace_tag, host
time.sleep(1)
if time.time() - start_time > timeout:
raise Exception("TIMEOUT WHILE WAITING FOR"
" NAMESPACES TO BE CREATED")
def ping(host, cmd, private_key):
"""Execute ping command over ssh"""
ping_result = execute_cmd_over_ssh(host, cmd, private_key)
if ping_result:
LOG.debug("PING RESULT %s", ping_result)
return True
else:
return False
def ping_router_gateway(namespace_controller_tuple, router_gw_ip, private_key):
"""Ping the ip address from network namespace
Get into controller's snat-namespaces and ping the peer router gateway ip.
:param namespace_controller_tuple: namespace, controller tuple. (It's the
controller that contains the namespace )
:param router_gw_ip: ip address to be pinged
:param private_key: path to private key file
:return: True if ping succeeds
False if ping fails
"""
namespace, controller = namespace_controller_tuple
LOG.debug("PING %s FROM THE NAMESPACE %s", router_gw_ip, namespace)
count = 4
cmd = "sudo ip netns exec {} ping -w {} -c {} {}".format(
namespace, 2 * count, count, router_gw_ip)
return ping(controller, cmd, private_key)
def get_interfaces(namespace_controller_tuple, private_key):
"""Get the interfaces
Get into the controller's snat namespace and list the interfaces.
:param namespace_controller_tuple: namespace, controller tuple(the
controller that contains the namespace).
:param private_key: path to private key file
:return: interfaces
"""
namespace, controller = namespace_controller_tuple
LOG.debug("GET THE INTERFACES BY USING 'ip a' FROM THE NAMESPACE %s",
namespace)
cmd = f"sudo ip netns exec {namespace} ip a"
interfaces = execute_cmd_over_ssh(controller, cmd, private_key)
LOG.debug("INTERFACES %s", interfaces)
return interfaces
def start_tcpdump(namespace_controller_tuple, interface, private_key):
"""Start the tcpdump at the given interface
Get into the controller's snat namespace and start a tcp dump at the
qg-interface.
:param namespace_controller_tuple: namespace, controller tuple. (It's the
controller that contains the namespace )
:param interface: interface in which tcpdump has to be run
:param private_key: path to private key file
:return: tcpdump output
"""
namespace, controller = namespace_controller_tuple
LOG.debug("START THE TCPDUMP USING 'tcpdump -i %s FROM THE NAMESPACE"
" %s", interface, namespace)
cmd = ("sudo ip netns exec {} timeout 15 tcpdump -n -i {}"
.format(namespace, interface))
tcpdump = execute_cmd_over_ssh(controller, cmd, private_key)
LOG.debug("TCPDUMP %s", tcpdump)
return tcpdump
def ssh_and_ping_server(local_server, peer_server, ns_compute_tuple, keyfile,
private_key):
"""SSH and ping the nova instance from the namespace
Get into the compute node's qrouter namespace and then ssh into the local
nova instance & ping the peer nova instance.
:param local_server: private ip of the server to ssh into
:param peer_server: private ip of the server to ping to
:param ns_compute_tuple: namespace, compute tuple. (It's the
compute node that contains the namespace )
:param keyfile: path to private key file of the nova instance
:param private_key: path to private key file
:return: True if ping succeeds
False if ping fails
"""
namespace, compute_host = ns_compute_tuple
LOG.debug("SSH INTO SERVER %s AND PING THE PEER SERVER %s FROM THE"
" NAMESPACE %s", local_server, peer_server, namespace)
host = "cirros@" + local_server
count = 20
cmd = ("sudo ip netns exec {} ssh -v -o StrictHostKeyChecking=no -o"
"HashKnownHosts=no -i {} {} ping -w {} -c {} {}"
.format(namespace, keyfile, host, 2 * count, count, peer_server))
return ping(compute_host, cmd, private_key)
def ssh_and_ping_server_with_fip(local_server, peer_server, keyfile,
private_key):
"""SSH into the local nova instance and ping the peer instance using fips
:param local_server: fip of the server to ssh into
:param peer_server: private ip of the server to ping to
:param keyfile: path to private key file of the nova instance
:param private_key: path to private key file
:return: True if ping succeeds
False if ping fails
"""
LOG.debug("SSH INTO LOCAL SERVER %s AND PING THE PEER SERVER %s",
local_server.ip, peer_server)
count = 20
local_host = {"ip": "127.0.0.1", "username": None}
host = "cirros@" + local_server.ip
cmd = ("ssh -v -o StrictHostKeyChecking=no -o"
"HashKnownHosts=no -i {} {} ping -w {} -c {} {}"
.format(keyfile, host, 2 * count, count, peer_server))
return ping(local_host, cmd, private_key)
def delete_servers(nova_client, servers):
"""Delete nova servers
It deletes the nova servers, associated security groups.
:param nova_client: nova client
:param servers: nova instances to be deleted
:return:
"""
for server in servers:
LOG.debug("DELETING NOVA INSTANCE: %s", server.id)
sec_group_id = server.security_groups[0]['name']
nova_client.servers.delete(server.id)
LOG.debug("WAITING FOR INSTANCE TO GET DELETED")
task_utils.wait_for_delete(
server, update_resource=task_utils.get_from_manager())
for secgroup in nova_client.security_groups.list():
if secgroup.id == sec_group_id:
LOG.debug("DELETING SEC_GROUP: %s", sec_group_id)
nova_client.security_groups.delete(secgroup.id)
def delete_floating_ips(nova_client, fips):
"""Delete floating ips
:param nova_client: nova client
:param fips: list of floating ips
:return:
"""
for fip in fips:
nova_client.floating_ips.delete(fip.id)
def delete_keypairs(nova_client, keypairs):
"""Delete key pairs
:param nova_client: nova client
:param keypairs: list of keypairs
:return
"""
for key_pair in keypairs:
LOG.debug("DELETING KEY_PAIR %s", key_pair.name)
nova_client.keypairs.delete(key_pair.id)
def delete_networks(neutron_client, neutron_admin_client,
routers, networks, subnets):
"""Delete neutron network, subnets amd routers
:param neutron_client: neutron client
:param neutron_admin_client: neutron_admin_client
:param routers: list of routers to be deleted
:param networks: list of networks to be deleted
:param subnets: list of subnets to be deleted
:return
"""
LOG.debug("DELETING RALLY ROUTER INTERFACES & GATEWAYS")
for router in routers:
neutron_client.remove_gateway_router(router['router']['id'])
router_name = router['router']['name']
subnet_name = ("rally_subnet_" + router_name[13:len(router_name)])
for subnet in subnets:
if subnet_name == subnet['subnet']['name']:
neutron_client.remove_interface_router(
router['router']['id'],
{"subnet_id": subnet['subnet']['id']})
LOG.debug("DELETING RALLY ROUTERS")
for router in routers:
neutron_client.delete_router(router['router']['id'])
LOG.debug("DELETING RALLY NETWORKS")
for network in networks:
if (network['network']['router:external'] and
network['network']['name'] == "rally_network_public"):
external_network = network
neutron_admin_client.delete_network(
external_network['network']["id"])
elif network['network']['router:external']:
pass
else:
neutron_client.delete_network(network['network']['id'])
def delete_tenants(keystone_client, tenant_ids):
"""Delete keystone tenant
:param keystone_client: keystone client
:param tenant_ids: list of tenants' uuids
:returns: delete keystone tenant instance
"""
LOG.debug('DELETE TENANTS')
for id in tenant_ids:
keystone_client.tenants.delete(id)
def delete_keyfiles(local_key_files, remote_key_files=None,
ns_compute_tuples=None, private_key=None):
"""Delete the SSH keyfiles from the compute and the local nodes
:param local_key_files: paths to ssh key files in local node
:param remote_key_files: paths to ssh key files in compute nodes
:param ns_compute_tuples: namespace, compute tuple. (It's the
compute node that contains the namespace )
:param private_key: path to private key file
:return:
"""
LOG.debug("DELETING RALLY KEY FILES FROM LOCAL MACHINE")
for key in local_key_files:
if os.path.exists(key):
os.remove(key)
if ns_compute_tuples:
LOG.debug("DELETING RALLY KEY FILES FROM COMPUTE HOSTS")
for key, ns_comp in zip(remote_key_files, ns_compute_tuples):
cmd = f"sudo rm -f {key}"
host = ns_comp[1]
execute_cmd_over_ssh(host, cmd, private_key)
def delete_hosts_from_knownhosts_file(hosts, ns_compute_tuples=None,
private_key=None):
"""Remove the hosts from the knownhosts file
:param hosts: host ips to be removed from /root/.ssh/knownhosts
:param ns_compute_tuples: namespace, compute tuple. (It's the
compute node that contains the namespace )
:param private_key: path to private key file
:return:
"""
if ns_compute_tuples:
LOG.debug("DELETES HOSTS FROM THE KNOWNHOSTS FILE")
for host, ns_comp in zip(hosts, ns_compute_tuples):
compute_host = ns_comp[1]
cmd = ("sudo ssh-keygen -f /root/.ssh/known_hosts -R"
" {}".format(host))
execute_cmd_over_ssh(compute_host, cmd, private_key)
else:
for host in hosts:
os.system("sudo ssh-keygen -f /root/.ssh/known_hosts -R"
" {}".format(host))