VPNaaS Scenario tests using Rally

Introducing VPNaaS scenario tests using Rally. This patch contains
rally-jobs/plugins/vpn_utils.py file that holds a few utility
functions that will be used by the actual test files.

Change-Id: I01aa18be580211f1013a2203d69dc8bd2200c514
This commit is contained in:
Aishwarya Thangappa 2015-08-15 22:24:47 -07:00
parent c574a40c59
commit 163332fd8c
3 changed files with 773 additions and 0 deletions

0
rally-jobs/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,773 @@
# 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 concurrent.futures
import exceptions
import os
from oslo_config import cfg
import re
import stat
import time
def noop(*args, **kwargs):
pass
cfg.CONF.register_cli_opts = noop
from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils as linux_utils
from rally.common import log as logging
from rally.plugins.openstack import scenario
from rally.task import utils as task_utils
LOG = logging.getLogger(__name__)
class VpnUtils(scenario.OpenStackScenario):
"""Utility class for VPNaaS scenarios with basic atomic actions."""
SUBNET_IP_VERSION = 4
def _create_network(self, neutron_client, network_suffix, cidr):
"""Create neutron network
:param neutron_client: neutron client
:param network_suffix: str, suffix name of the new network
:param cidr: subnet cidr
:return: router, subnet , network
"""
def create_network(neutron_client, network_suffix, isExternal=False):
network_name = "rally_network_" + network_suffix
network_args = {"name": network_name,
"router:external": isExternal}
LOG.debug("ADDING NEW NETWORK: %s", network_name)
rally_network = neutron_client.create_network(
{"network": network_args})
return rally_network
def create_subnet(neutron_client, rally_network, network_suffix, cidr):
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": self.SUBNET_IP_VERSION}
LOG.debug("ADDING SUBNET: %s", subnet_name)
rally_subnet = neutron_client.create_subnet(
{"subnet": subnet_args})
return rally_subnet
def create_router(private_subnet, public_network_id):
router_name = "rally_router_" + network_suffix
gw_info = {"network_id": public_network_id}
router_args = {"name": router_name,
"external_gateway_info": gw_info}
LOG.debug("ADDING ROUTER: %s", router_name)
rally_router = neutron_client.create_router(
{"router": router_args})
# create router interface - connect subnet to it
LOG.debug("ADDING ROUTER INTERFACE")
neutron_client.add_interface_router(
rally_router['router']["id"],
{"subnet_id": private_subnet["subnet"]["id"]})
return rally_router
# check for external network and create one if not found
def get_external_network():
for network in neutron_client.list_networks()['networks']:
if network['router:external']:
public_network_id = network['id']
LOG.debug("PUBLIC NETWORK ALREADY EXISTS")
break
else:
public_network = create_network(self.admin_clients("neutron"),
"public", True)
create_subnet(self.admin_clients("neutron"), public_network,
"public", "172.16.1.0/24")
public_network_id = public_network['network']['id']
return public_network_id
# create public network_id
public_network_id = get_external_network()
# create private network
private_network = create_network(neutron_client, network_suffix)
# create subnet
private_subnet = create_subnet(neutron_client,
private_network,
network_suffix,
cidr)
# create router
rally_router = create_router(private_subnet, public_network_id)
return rally_router, private_network, private_subnet
def _create_keypair(self, nova_client, key_name, key_file):
"""Create keypair
:param nova_client: nova_client
:param key_name: key_name
:param key_file: key_file_name
:return: keypair
"""
LOG.debug("ADDING NEW KEYPAIR")
keypair = nova_client.keypairs.create(key_name)
f = open(key_file, 'w')
os.chmod(key_file, stat.S_IREAD | stat.S_IWRITE)
f.write(keypair.private_key)
f.close()
return keypair
def _create_nova_vm(self, nova_client, keypair, **kwargs):
"""Create nova instance
:param nova_client: nova client
:param keypair: str, key-pair to allow ssh
:return: new nova instance
"""
# add sec-group
sec_group_suffix = "rally_secgroup_" + kwargs["sec_group_suffix"]
LOG.debug("ADDING NEW SECURITY GROUP %s", sec_group_suffix)
secgroup = nova_client.security_groups.create(sec_group_suffix,
sec_group_suffix)
# 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)
server = nova_client.servers.create(server_name,
image=kwargs["image"],
flavor=kwargs["flavor"],
key_name=keypair.name,
security_groups=[secgroup.id],
nics=kwargs["nics"])
# wait for instance to become active
LOG.debug("WAITING FOR INSTANCE TO BECOME 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 if instance is 'active'
assert('ACTIVE' == server.status), (
"THE INSTANCE IS NOT IN ACTIVE STATE")
return server
def _get_server_ip(self, nova_client, server_id, network_suffix):
"""
:param nova_client: nova client
:param nova_id: uuid of the nova instance whose ip is wanted
:param network_suffix: network name suffix
: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 _create_ike_policy(self,
neutron_client,
**kwargs):
"""Creates IKE policy
:param neutron_client:neutron client
:return:created ike_policy
"""
LOG.debug("CREATING IKE_POLICY")
ike_policy = neutron_client.create_ikepolicy({
"ikepolicy": {
"phase1_negotiation_mode":
kwargs["phase1_negotiation_mode"] or "main",
"auth_algorithm": kwargs["auth_algorithm"] or "sha1",
"encryption_algorithm":
kwargs["encryption_algorithm"] or "aes-128",
"pfs": kwargs["pfs"] or "group5",
"lifetime": {
"units": "seconds",
"value": kwargs["value"] or 7200},
"ike_version": kwargs["ike_version"] or "v1",
"name": "rally_ikepolicy"
}
})
return ike_policy
def _create_ipsec_policy(self,
neutron_client,
**kwargs):
"""Creates IPSEC policy
:param neutron_client: neutron client
:return: created IPSEC policy
"""
LOG.debug("CREATING IPSEC_POLICY")
ipsec_policy = neutron_client.create_ipsecpolicy({
"ipsecpolicy": {
"name": "rally_ipsecpolicy",
"transform_protocol": kwargs["transform_protocol"] or "esp",
"auth_algorithm": kwargs["auth_algorithm"] or "sha1",
"encapsulation_mode": kwargs["encapsulation_mode"] or "tunnel",
"encryption_algorithm":
kwargs["encryption_algorithm"] or "aes-128",
"pfs": kwargs["pfs"] or "group5",
"lifetime": {
"units": "seconds",
"value": kwargs["value"] or 7200
}
}
})
return ipsec_policy
def _create_vpn_service(self, neutron_client, rally_subnet, rally_router,
name=None):
"""Creates VPN service endpoints
:param neutron_client: neutron client
:param name: name of vpn service
:param rally_subnet: local subnet
:param rally_router: router endpoint
:param admin_state_up: admin state of the vpn_service
:return: vpn_service
"""
LOG.debug("CREATING VPN_SERVICE")
vpn_service = neutron_client.create_vpnservice({
"vpnservice": {
"subnet_id": rally_subnet["subnet"]["id"],
"router_id": rally_router["router"]["id"],
"name": "rally_vpn_service_" + name,
"admin_state_up": True
}
})
return vpn_service
def _create_ipsec_site_connection(
self, neutron_client, ike_policy,
ipsec_policy, peer_cidrs,
peer_id, peer_address,
vpn_service, name=None,
mtu=None, secret=None):
"""Creates IPSEC site connections
:param neutron_client: neutron client
:param ike_policy: ikepolicy
:param ipsec_policy: ipsecpolicy
:param peer_cidrs: list of peer cidrs
:param peer_id: peer_id
:param peer_address: peer_address
:param vpn_service: vpn_service
:param secret: pre shared secret
:param admin_state_up: admin state of the ipsec site connections
:param mtu: max transmission unit
:param name: name of the ipsec site connections
:return:ipsec_site_connection
"""
LOG.debug("CREATING IPSEC_SITE_CONNECTION")
ipsec_site_connection = neutron_client.create_ipsec_site_connection({
"ipsec_site_connection": {
"psk": secret or "secret",
"initiator": "bi-directional",
"ipsecpolicy_id": ipsec_policy["ipsecpolicy"]["id"],
"admin_state_up": True,
"peer_cidrs": peer_cidrs,
"mtu": mtu or "1500",
"ikepolicy_id": ike_policy["ikepolicy"]["id"],
"dpd": {
"action": "disabled",
"interval": 60,
"timeout": 240
},
"vpnservice_id": vpn_service["vpnservice"]["id"],
"peer_address": peer_address,
"peer_id": peer_id,
"name": "rally_ipsec_site_connection_" + name
}
})
return ipsec_site_connection
def _get_resource(self, resource_tag, resource_id):
"""Gets the resource(vpn_service or ipsec_site_connection)
:param resource_tag: "vpnservice" or "ipsec_site_connection"
:param resource_id: id of the resource
:return:
"""
neutron_client = self.clients("neutron")
if resource_tag == "vpnservice":
vpn_service = neutron_client.show_vpnservice(resource_id)
if vpn_service:
return vpn_service
elif resource_tag == 'ipsec_site_connection':
ipsec_site_connection = neutron_client.show_ipsec_site_connection(
resource_id)
if ipsec_site_connection:
return ipsec_site_connection
def _wait_for_status_change(self, resource, final_status,
resource_tag, wait_timeout=60,
check_interval=1):
"""Wait for resource's status change
Wait till the status of the resource changes to final state or till
the time exceeds the wait_timeout value.
:param resource: resource whose status has to be checked
:param final_status: desired final status of the resource
:param resource_tag: to identify the resource as vpnservice or
ipser_site_connection
:param wait_timeout: timeout value in seconds
:param check_interval: time to sleep before each check for the status
change
:return: resource
"""
start_time = time.time()
while True:
resource = self._get_resource(
resource_tag,
resource[resource_tag]['id'])
current_status = resource[resource_tag]['status']
if current_status == final_status:
return resource
time.sleep(check_interval)
if time.time() - start_time > wait_timeout:
self._cleanup(called_from="VpnUtils._wait_for_status_change")
raise exceptions.Exception("Timeout while waiting for status "
"change to %s.", final_status)
def _assert_statuses(self, vpn_service, ipsec_site_connection,
ipsec_site_connection_creation_timeout=60,
vpn_service_creation_timeout=60):
"""Assert statuses of vpn_service and ipsec_site_connection
:param vpn_service: vpn_service
:param ipsec_site_connection: ipsec_site_connection
:param ipsec_site_connection_creation_timeout: timeout in seconds
:param vpn_service_creation_timeout: timeout in seconds
:return:
"""
vpn_service = self._wait_for_status_change(
vpn_service,
resource_tag="vpnservice",
final_status="ACTIVE",
wait_timeout=vpn_service_creation_timeout,
check_interval=5)
LOG.debug("VPN-SERVICE STATUS: %s",
vpn_service['vpnservice']['status'])
assert('ACTIVE' == vpn_service['vpnservice']['status']), (
"VPN_SERVICE IS NOT IN ACTIVE STATE")
ipsec_site_connection = self._wait_for_status_change(
ipsec_site_connection,
resource_tag="ipsec_site_connection",
final_status="ACTIVE",
wait_timeout=ipsec_site_connection_creation_timeout,
check_interval=5)
LOG.debug("IPSEC_SITE_CONNECTION STATUS: %s",
ipsec_site_connection['ipsec_site_connection']['status'])
assert('ACTIVE' ==
ipsec_site_connection['ipsec_site_connection']['status']), (
"THE INSTANCE IS NOT IN ACTIVE STATE")
def _verify_vpn_connectivity(self, server_ips, snat_namespaces,
qrouter_namespaces, key_file_names,
first, second):
"""Verifies the vpn connectivity between the endpoints
:param server_ips: list of private ips of the servers between
which the vpn connectivity has to verified.
:param snat_namespaces: snat_namespaces of the 2 routers
:param qrouter_namespaces: qrouter_namespaces of the 2 routers
:param key_file_names: path to private key files
:param first: parameter to point to the self
:param second: parameter to point to the peer
:return: True or False
"""
LOG.debug("VERIFY THE VPN CONNECTIVITY")
qg = self._get_interfaces(snat_namespaces[second])
if qg:
p = re.compile(r"qg-\w+-\w+")
m = p.search(qg)
if m:
qg_interface = m.group()
else:
qg_interface = None
if qg_interface:
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as e:
tcpdump_future = e.submit(self._start_tcpdump,
snat_namespaces[second],
qg_interface)
ssh_future = e.submit(self._ssh_and_ping_server,
server_ips[first],
server_ips[second],
qrouter_namespaces[first],
key_file_names[first])
assert(True == ssh_future.result()), "SSH/Ping failed"
lines = tcpdump_future.result().split('\n')
for line in lines:
if 'ESP' in line:
return True
return False
def _get_namespace(self):
"""Get namespaces
:return: namespaces
"""
LOG.debug("GET NAMESPACES USING 'ip netns'")
try:
cmd = ['ip', 'netns']
cmd = ip_lib.add_namespace_to_cmd(cmd)
namespaces = linux_utils.execute(cmd)
LOG.debug("%s", namespaces)
return namespaces
except RuntimeError:
return None
def _wait_for_namespace_creation(self, namespace, rally_router):
"""Wait for namespace creation
:param namespace: snat/qrouter namespace
:param rally_router: rally_router
:return:
"""
start_time = time.time()
while True:
namespaces = self._get_namespace().split()
for line in namespaces:
if line == (namespace + rally_router["router"]["id"]):
namespace = line
return namespace
time.sleep(1)
if time.time() - start_time > 20:
self._cleanup(called_from="_wait_for_namespace_creation")
raise exceptions.Exception("Timeout while waiting for"
" namespaces to be created")
def _ping(self, namespace, ip):
"""Pings ip address from network namespace.
In order to ping it uses following cli command:
ip netns exec <namespace> ping -c 4 -q <ip>
:param namespace: namespace
:param ip: ip to ping to
"""
LOG.debug("PING %s FROM THE NAMESPACE %s", ip, namespace)
try:
count = 4
cmd = ['ping', '-w', 2 * count, '-c', count, ip]
cmd = ip_lib.add_namespace_to_cmd(cmd, namespace)
ping_result = linux_utils.execute(cmd, run_as_root=True)
LOG.debug("%s", ping_result)
return True
except RuntimeError:
return False
def _get_interfaces(self, namespace):
"""Do an "ip a".
In order to do "ip a" it uses following cli command:
ip netns exec <namespace> ip a | grep qg
:param namespace: namespace
"""
LOG.debug("GET THE INTERFACES BY USING 'ip a' FROM THE NAMESPACE %s",
namespace)
try:
cmd = ['ip', 'a']
cmd = ip_lib.add_namespace_to_cmd(cmd, namespace)
interfaces = linux_utils.execute(cmd, run_as_root=True)
LOG.debug("%s", interfaces)
return interfaces
except RuntimeError:
return None
def _start_tcpdump(self, namespace, interface):
"""Starts tcpdump at the given interface
In order to start a "tcpdump" it uses the following command:
ip netns exec <namespace> sudo tcpdump -i <interface>
:param namespace: namespace
:param interface: interface
:return:
"""
LOG.debug("START THE TCPDUMP USING 'tcpdump -i <%s> FROM THE NAMESPACE"
" %s", interface, namespace)
try:
cmd = ['timeout', '10', 'tcpdump', '-n',
'-i', interface]
cmd = ip_lib.add_namespace_to_cmd(cmd, namespace)
tcpdump = linux_utils.execute(cmd, run_as_root=True,
extra_ok_codes=[124])
LOG.debug("%s", tcpdump)
return tcpdump
except RuntimeError:
return None
def _ssh_and_ping_server(self, ssh_server, ping_server,
namespace, key_file_name):
"""Ssh into the server from the namespace.
In order to ssh it uses the following command:
ip netns exec <namespace> ssh -i <path to keyfile> cirros@<server_ip>
:param ssh_server: ip of the server to ssh into
:param ping_server: ip of the server to ping to
:param namespace: qrouter namespace
:param key_file_name: path to private key file
:return:
"""
LOG.debug("SSH INTO SERVER %s AND PING THE PEER SERVER %s FROM THE"
" NAMESPACE %s", ssh_server, ping_server, namespace)
try:
# ssh instance
host = "cirros@" + ssh_server
count = 20
cmd = ['ssh', '-o', 'StrictHostKeyChecking=no',
'-i', key_file_name, host,
'ping', '-w',
2 * count, '-c', count, ping_server]
cmd = ip_lib.add_namespace_to_cmd(cmd, namespace)
ping_result = linux_utils.execute(cmd, run_as_root=True)
LOG.debug("%s", ping_result)
return True
except RuntimeError:
return False
def _delete_server(self, nova_client, server):
"""Delete nova instance
:param server: instance to delete
:return:
"""
# delete server
sec_group_name = server.security_groups[0]['name']
server_key_name = server.key_name
LOG.debug("DELETING NOVA INSTANCE: %s", server.id)
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())
# delete sec-group
for secgroup in nova_client.security_groups.list():
if secgroup.name == sec_group_name:
LOG.debug("DELETING SEC_GROUP: %s", sec_group_name)
nova_client.security_groups.delete(secgroup.id)
# delete key-pair
for key_pair in nova_client.keypairs.list():
if key_pair.name == server_key_name:
LOG.debug("DELETING KEY_PAIR: %s", server_key_name)
nova_client.keypairs.delete(key_pair.id)
def _delete_ipsec_site_connection(self, neutron_client,
ipsec_site_connection):
"""Deletes ipsec site connection
:param neutron_client: neutron client
:param ipsec_site_connection: ipsec_site_connection
:return:
"""
LOG.debug("DELETING IPSEC_SITE_CONNECTION %s",
ipsec_site_connection['id'])
neutron_client.delete_ipsec_site_connection(
ipsec_site_connection['id'])
def _delete_vpn_service(self, neutron_client, vpn_service):
"""Deletes VPN service endpoints
:param neutron_client: neutron client
:param vpn_services: vpn_service
:return:
"""
LOG.debug("DELETING VPN_SERVICE %s", vpn_service['id'])
neutron_client.delete_vpnservice(vpn_service['id'])
def _delete_ipsec_policy(self, neutron_client, ipsec_policy):
"""Deletes IPSEC policy
:param neutron_client: neutron client
:param ipsec_policy: ipsec_policy
:return:
"""
LOG.debug("DELETING IPSEC POLICY")
neutron_client.delete_ipsecpolicy(ipsec_policy['id'])
def _delete_ike_policy(self, neutron_client, ike_policy):
"""Deletes IKE policy
:param neutron_client: neutron client
:param ike_policy: ike_policy
:return:
"""
LOG.debug("DELETING IKE POLICY")
neutron_client.delete_ikepolicy(ike_policy['id'])
def _delete_network(self, neutron_client):
"""Delete neutron network.
:param network_tuple: tuple, router, network and subnet to delete
:return
"""
try:
# delete interface subnet-router
LOG.debug("DELETING RALLY ROUTER INTERFACES & GATEWAYS")
routers = neutron_client.list_routers()
subnets = neutron_client.list_subnets()
subnet_id = None
p = re.compile(r"\d")
if routers:
for router in routers['routers']:
if "rally" in router['name']:
neutron_client.remove_gateway_router(router['id'])
m = p.search(router['name'])
if m:
subnet_name = "rally_subnet_" + m.group()
if subnets:
for subnet in subnets['subnets']:
if subnet_name == subnet['name']:
subnet_id = subnet['id']
neutron_client.remove_interface_router(
router['id'],
{"subnet_id": subnet_id})
# delete ports associated with interface
LOG.debug("DELETING RALLY PORTS")
ports = neutron_client.list_ports()
if ports:
for port in ports['ports']:
neutron_client.delete_port(port['id'])
# delete router
LOG.debug("DELETING RALLY ROUTERS")
if routers:
for router in routers['routers']:
if "rally" in router['name']:
neutron_client.delete_router(router['id'])
# Delete external network & subnet:
LOG.debug("DELETING RALLY PUBLIC NETWORK")
networks = neutron_client.list_networks()
if networks:
for network in networks['networks']:
if network['router:external'] and (network['name']
== "rally_network_public"):
external_network = network
self.admin_clients("neutron").delete_network(
external_network["id"])
# delete network
LOG.debug("DELETING RALLY NETWORKS")
networks = neutron_client.list_networks()
if networks:
for network in networks['networks']:
if "rally_network" in network['name']:
neutron_client.delete_network(network['id'])
except Exception as err:
LOG.exception(err)
def _delete_key_file(self, key_files):
"""Delete ssh key file
:param key_files: list of paths to ssh key files
:return:
"""
LOG.debug("DELETING RALLY KEY FILES")
for key_file in key_files:
if os.path.exists(key_file):
os.remove(key_file)
def _delete_knownhosts_file(self):
"""Removes the knownhosts file
:param server_ips: ips to be removed from /root/.ssh/knownhosts
:return:
"""
LOG.debug("DELETE THE KNOWNHOST FILE")
try:
cmd = ['rm', '-rf', "~/.ssh/known_hosts"]
cmd = ip_lib.add_namespace_to_cmd(cmd)
linux_utils.execute(cmd)
return True
except RuntimeError:
return False
def _cleanup(self,
key_file_names=None,
called_from=None):
LOG.debug("CLEAN UP CALLED FROM %s", called_from)
nova_client = self.clients("nova")
neutron_client = self.clients("neutron")
servers = nova_client.servers.list()
if servers:
for server in servers:
if "rally" in server.name:
self._delete_server(nova_client, server)
if key_file_names:
self._delete_key_file(key_file_names)
self._delete_knownhosts_file()
vpn_connections = neutron_client.list_ipsec_site_connections()
if vpn_connections:
for vpn_connection in vpn_connections['ipsec_site_connections']:
if "rally" in vpn_connection['name']:
self._delete_ipsec_site_connection(neutron_client,
vpn_connection)
vpn_services = neutron_client.list_vpnservices()
if vpn_services:
for vpn_service in vpn_services['vpnservices']:
if "rally" in vpn_service['name']:
self._delete_vpn_service(neutron_client, vpn_service)
ipsec_policies = neutron_client.list_ipsecpolicies()
if ipsec_policies:
for ipsec_policy in ipsec_policies['ipsecpolicies']:
if "rally" in ipsec_policy['name']:
self._delete_ipsec_policy(neutron_client, ipsec_policy)
ike_policies = neutron_client.list_ikepolicies()
if ike_policies:
for ike_policy in ike_policies['ikepolicies']:
if "rally" in ike_policy['name']:
self._delete_ike_policy(neutron_client, ike_policy)
# Deletes entire network
self._delete_network(neutron_client)