Files
neutron-vpnaas/rally-jobs/plugins/vpn_base.py
Takashi Kajinami 6372f73e4c Bump hacking
hacking 4.0.x is quite old. Bump it to the version currently used by
neutron and neutron-lib.

Change-Id: Iba06a77570f594911f1a150b707c15eb361ef9ca
2024-09-21 16:03:36 +00:00

524 lines
21 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 concurrent.futures
import re
import threading
import time
from oslo_utils import uuidutils
from rally.common import logging
from rally.plugins.openstack import scenario as rally_base
from rally.task import atomic
import vpn_utils
LOG = logging.getLogger(__name__)
LOCK = threading.RLock()
MAX_RESOURCES = 2
class VpnBase(rally_base.OpenStackScenario):
def setup(self, **kwargs):
"""Create and initialize data structures to hold various resources"""
with LOCK:
LOG.debug('SETUP RESOURCES')
self.neutron_admin_client = self.admin_clients("neutron")
if kwargs['use_admin_client']:
self.neutron_client = self.neutron_admin_client
self.keystone_client = self.admin_clients("keystone")
self.nova_client = self.admin_clients("nova")
else:
self.neutron_client = self.clients("neutron")
self.nova_client = self.clients("nova")
self.suffixes = [uuidutils.generate_uuid(),
uuidutils.generate_uuid()]
self.remote_key_files = ['rally_keypair_' + x
for x in self.suffixes]
self.local_key_files = ['/tmp/' + x for x in self.remote_key_files]
self.private_key_file = kwargs["private_key"]
self.keypairs = []
self.tenant_ids = []
self.ns_controller_tuples = []
self.qrouterns_compute_tuples = []
self.router_ids = []
self.rally_router_gw_ips = []
self.rally_routers = []
self.rally_networks = []
self.rally_subnets = []
self.rally_cidrs = []
self.ike_policy = None
self.ipsec_policy = None
self.vpn_services = []
self.ipsec_site_connections = []
self.servers = []
self.server_private_ips = []
self.server_fips = []
def create_tenants(self):
"""Create tenants"""
for x in range(MAX_RESOURCES):
tenant_id = vpn_utils.create_tenant(
self.keystone_client, self.suffixes[x])
with LOCK:
self.tenant_ids.append(tenant_id)
def create_networks(self, **kwargs):
"""Create networks to test vpn connectivity"""
for x in range(MAX_RESOURCES):
if self.tenant_ids:
router, network, subnet, cidr = vpn_utils.create_network(
self.neutron_client, self.neutron_admin_client,
self.suffixes[x], tenant_id=self.tenant_ids[x],
DVR_flag=kwargs["DVR_flag"],
ext_net_name=kwargs["ext-net"])
else:
router, network, subnet, cidr = vpn_utils.create_network(
self.neutron_client, self.neutron_admin_client,
self.suffixes[x], DVR_flag=kwargs["DVR_flag"],
ext_net_name=kwargs["ext-net"])
with LOCK:
self.rally_cidrs.append(cidr)
self.rally_subnets.append(subnet)
self.rally_networks.append(network)
self.rally_routers.append(router)
self.router_ids.append(router["router"]['id'])
self.rally_router_gw_ips.append(
router["router"]["external_gateway_info"]
["external_fixed_ips"][0]["ip_address"])
if kwargs["DVR_flag"]:
ns, controller = vpn_utils.wait_for_namespace_creation(
"snat-", router["router"]['id'],
kwargs['controller_creds'],
self.private_key_file,
kwargs['namespace_creation_timeout'])
else:
ns, controller = vpn_utils.wait_for_namespace_creation(
"qrouter-", router["router"]['id'],
kwargs['controller_creds'],
self.private_key_file,
kwargs['namespace_creation_timeout'])
with LOCK:
self.ns_controller_tuples.append((ns, controller))
def create_servers(self, **kwargs):
"""Create servers"""
for x in range(MAX_RESOURCES):
kwargs.update({
"nics":
[{"net-id": self.rally_networks[x]["network"]["id"]}],
"sec_group_suffix": self.suffixes[x],
"server_suffix": self.suffixes[x]
})
keypair = vpn_utils.create_keypair(
self.nova_client, self.suffixes[x])
server = vpn_utils.create_server(
self.nova_client, keypair, **kwargs)
vpn_utils.assert_server_status(server, **kwargs)
with LOCK:
self.servers.append(server)
self.keypairs.append(keypair)
self.server_private_ips.append(vpn_utils.get_server_ip(
self.nova_client, server.id, self.suffixes[x]))
if kwargs["DVR_flag"]:
qrouter, compute = vpn_utils.wait_for_namespace_creation(
"qrouter-", self.router_ids[x],
kwargs['compute_creds'],
self.private_key_file,
kwargs['namespace_creation_timeout'])
vpn_utils.write_key_to_compute_node(
keypair, self.local_key_files[x],
self.remote_key_files[x], compute,
self.private_key_file)
with LOCK:
self.qrouterns_compute_tuples.append((qrouter, compute))
else:
vpn_utils.write_key_to_local_path(self.keypairs[x],
self.local_key_files[x])
fip = vpn_utils.add_floating_ip(self.nova_client, server)
with LOCK:
self.server_fips.append(fip)
def check_route(self):
"""Verify route exists between the router gateways"""
LOG.debug("VERIFY ROUTE EXISTS BETWEEN THE ROUTER GATEWAYS")
for tuple in self.ns_controller_tuples:
for ip in self.rally_router_gw_ips:
assert vpn_utils.ping_router_gateway(
tuple, ip, self.private_key_file), (
"PING TO IP " + ip + " FAILED")
@atomic.action_timer("_create_ike_policy")
def _create_ike_policy(self, **kwargs):
"""Create IKE policy
:return: IKE policy
"""
LOG.debug('CREATING IKE_POLICY')
ike_policy = self.neutron_client.create_ikepolicy({
"ikepolicy": {
"phase1_negotiation_mode":
kwargs.get("phase1_negotiation_mode", "main"),
"auth_algorithm": kwargs.get("auth_algorithm", "sha1"),
"encryption_algorithm":
kwargs.get("encryption_algorithm", "aes-128"),
"pfs": kwargs.get("pfs", "group5"),
"lifetime": {
"units": "seconds",
"value": kwargs.get("value", 7200)},
"ike_version": kwargs.get("ike_version", "v1"),
"name": "rally_ikepolicy"
}
})
return ike_policy
@atomic.action_timer("_create_ipsec_policy")
def _create_ipsec_policy(self, **kwargs):
"""Create IPSEC policy
:return: IPSEC policy
"""
LOG.debug('CREATING IPSEC_POLICY')
ipsec_policy = self.neutron_client.create_ipsecpolicy({
"ipsecpolicy": {
"name": "rally_ipsecpolicy",
"transform_protocol": kwargs.get("transform_protocol", "esp"),
"auth_algorithm": kwargs.get("auth_algorithm", "sha1"),
"encapsulation_mode":
kwargs.get("encapsulation_mode", "tunnel"),
"encryption_algorithm":
kwargs.get("encryption_algorithm", "aes-128"),
"pfs": kwargs.get("pfs", "group5"),
"lifetime": {
"units": "seconds",
"value": kwargs.get("value", 7200)
}
}
})
return ipsec_policy
@atomic.action_timer("_create_vpn_service")
def _create_vpn_service(self, rally_subnet, rally_router, vpn_suffix=None):
"""Create VPN service endpoints
:param rally_subnet: local subnet
:param rally_router: router endpoint
:param vpn_suffix: suffix name for vpn service
:return: VPN service
"""
LOG.debug('CREATING VPN_SERVICE')
vpn_service = self.neutron_client.create_vpnservice({
"vpnservice": {
"subnet_id": rally_subnet["subnet"]["id"],
"router_id": rally_router["router"]["id"],
"name": "rally_vpn_service_" + vpn_suffix,
"admin_state_up": True
}
})
return vpn_service
def create_vpn_services(self):
"""Create VPN services"""
for x in range(MAX_RESOURCES):
vpn_service = self._create_vpn_service(
self.rally_subnets[x], self.rally_routers[x], self.suffixes[x])
with LOCK:
self.vpn_services.append(vpn_service)
@atomic.action_timer("_create_ipsec_site_connection")
def _create_ipsec_site_connection(self, local_index, peer_index, **kwargs):
"""Create IPSEC site connection
:param local_index: parameter to point to the local end-point
:param peer_index: parameter to point to the peer end-point
:return: IPSEC site connection
"""
LOG.debug('CREATING IPSEC_SITE_CONNECTION')
ipsec_site_conn = self.neutron_client.create_ipsec_site_connection({
"ipsec_site_connection": {
"psk": kwargs.get("secret", "secret"),
"initiator": "bi-directional",
"ipsecpolicy_id": self.ipsec_policy["ipsecpolicy"]["id"],
"admin_state_up": True,
"peer_cidrs": self.rally_cidrs[peer_index],
"mtu": kwargs.get("mtu", "1500"),
"ikepolicy_id": self.ike_policy["ikepolicy"]["id"],
"dpd": {
"action": "disabled",
"interval": 60,
"timeout": 240
},
"vpnservice_id":
self.vpn_services[local_index]["vpnservice"]["id"],
"peer_address": self.rally_router_gw_ips[peer_index],
"peer_id": self.rally_router_gw_ips[peer_index],
"name": "rally_ipsec_site_connection_" +
self.suffixes[local_index]
}
})
return ipsec_site_conn
def create_ipsec_site_connections(self, **kwargs):
"""Create IPSEC site connections"""
a = self._create_ipsec_site_connection(0, 1, **kwargs)
b = self._create_ipsec_site_connection(1, 0, **kwargs)
with LOCK:
self.ipsec_site_connections = [a, b]
def _get_resource(self, resource_tag, resource_id):
"""Get the resource(vpn_service or ipsec_site_connection)
:param resource_tag: "vpnservice" or "ipsec_site_connection"
:param resource_id: id of the resource
:return: resource (vpn_service or ipsec_site_connection)
"""
if resource_tag == "vpnservice":
vpn_service = self.neutron_client.show_vpnservice(resource_id)
if vpn_service:
return vpn_service
elif resource_tag == 'ipsec_site_connection':
ipsec_site_conn = self.neutron_client.show_ipsec_site_connection(
resource_id)
if ipsec_site_conn:
return ipsec_site_conn
def _wait_for_status_change(self, resource, resource_tag, final_status,
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
ipsec_site_connection
:param wait_timeout: timeout value in seconds
:param check_interval: time to sleep before each check for the status
change
:return: resource
"""
LOG.debug('WAIT_FOR_%s_STATUS_CHANGE ', resource[resource_tag]['id'])
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:
raise Exception(
"Timeout waiting for resource {} to change to {} status".
format(resource[resource_tag]['name'], final_status))
@atomic.action_timer("wait_time_for_status_change")
def _assert_statuses(self, ipsec_site_conn, vpn_service,
final_status, **kwargs):
"""Assert statuses of vpn_service and ipsec_site_connection
:param ipsec_site_conn: ipsec_site_connection object
:param vpn_service: vpn_service object
:param final_status: status of vpn and ipsec_site_connection object
"""
vpn_service = self._wait_for_status_change(
vpn_service,
resource_tag="vpnservice",
final_status=final_status,
wait_timeout=kwargs.get("vpn_service_creation_timeout"),
check_interval=5)
ipsec_site_conn = self._wait_for_status_change(
ipsec_site_conn,
resource_tag="ipsec_site_connection",
final_status=final_status,
wait_timeout=kwargs.get("ipsec_site_connection_creation_timeout"),
check_interval=5)
LOG.debug("VPN SERVICE STATUS %s", vpn_service['vpnservice']['status'])
LOG.debug("IPSEC_SITE_CONNECTION STATUS %s",
ipsec_site_conn['ipsec_site_connection']['status'])
def assert_statuses(self, final_status, **kwargs):
"""Assert active statuses for VPN services and VPN connections
:param final_status: the final status you expect the resource to be in
"""
LOG.debug("ASSERTING ACTIVE STATUSES FOR VPN-SERVICES AND "
"IPSEC-SITE-CONNECTIONS")
for x in range(MAX_RESOURCES):
self._assert_statuses(
self.ipsec_site_connections[x], self.vpn_services[x],
final_status, **kwargs)
def _get_qg_interface(self, peer_index):
"""Get the qg- interface
:param peer_index: parameter to point to the local end-point
:return: qg-interface
"""
qg = vpn_utils.get_interfaces(
self.ns_controller_tuples[peer_index],
self.private_key_file)
p = re.compile(r"qg-\w+-\w+")
for line in qg:
m = p.search(line)
if m:
return m.group()
return None
@atomic.action_timer("_verify_vpn_connection")
def _verify_vpn_connectivity(self, local_index, peer_index, **kwargs):
"""Verify the vpn connectivity between the endpoints
Get the qg- interface from the snat namespace corresponding to the
peer router and start a tcp dump. Concurrently, SSH into the nova
instance on the local subnet from the qrouter namespace and try
to ping the nova instance on the peer subnet. Inspect the captured
packets to see if they are encrypted.
:param local_index: parameter to point to the local end-point
:param peer_index: parameter to point to the peer end-point
:return: True if vpn connectivity test passes
False if the test fails
"""
qg_interface = self._get_qg_interface(peer_index)
if qg_interface:
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as e:
tcpdump_future = e.submit(vpn_utils.start_tcpdump,
self.ns_controller_tuples[peer_index],
qg_interface, self.private_key_file)
if kwargs["DVR_flag"]:
ssh_future = e.submit(
vpn_utils.ssh_and_ping_server,
self.server_private_ips[local_index],
self.server_private_ips[peer_index],
self.qrouterns_compute_tuples[local_index],
self.remote_key_files[local_index],
self.private_key_file)
else:
ssh_future = e.submit(
vpn_utils.ssh_and_ping_server_with_fip,
self.server_fips[local_index],
self.server_private_ips[peer_index],
self.local_key_files[local_index],
self.private_key_file)
assert ssh_future.result(), "SSH/Ping failed"
for line in tcpdump_future.result():
if 'ESP' in line:
return True
return False
def verify_vpn_connectivity(self, **kwargs):
"""Verify VPN connectivity"""
LOG.debug("VERIFY THE VPN CONNECTIVITY")
with LOCK:
assert self._verify_vpn_connectivity(
0, 1, **kwargs), "VPN CONNECTION FAILED"
with LOCK:
assert self._verify_vpn_connectivity(
1, 0, **kwargs), "VPN CONNECTION FAILED"
def update_router(self, router_id, admin_state_up=False):
"""Update router's admin_state_up field
:param router_id: uuid of the router
:param admin_state_up: True or False
"""
LOG.debug('UPDATE ROUTER')
router_args = {'router': {'admin_state_up': admin_state_up}}
self.neutron_client.update_router(router_id, router_args)
@atomic.action_timer("_delete_ipsec_site_connection")
def _delete_ipsec_site_connections(self):
"""Delete IPSEC site connections"""
for site_conn in self.ipsec_site_connections:
LOG.debug("DELETING IPSEC_SITE_CONNECTION %s",
site_conn['ipsec_site_connection']['id'])
self.neutron_client.delete_ipsec_site_connection(
site_conn['ipsec_site_connection']['id'])
@atomic.action_timer("_delete_vpn_service")
def _delete_vpn_services(self):
"""Delete VPN service endpoints"""
for vpn_service in self.vpn_services:
LOG.debug("DELETING VPN_SERVICE %s",
vpn_service['vpnservice']['id'])
self.neutron_client.delete_vpnservice(
vpn_service['vpnservice']['id'])
@atomic.action_timer("_delete_ipsec_policy")
def _delete_ipsec_policy(self):
"""Delete IPSEC policy"""
LOG.debug("DELETING IPSEC POLICY")
if self.ipsec_policy:
self.neutron_client.delete_ipsecpolicy(
self.ipsec_policy['ipsecpolicy']['id'])
@atomic.action_timer("_delete_ike_policy")
def _delete_ike_policy(self):
"""Delete IKE policy"""
LOG.debug('DELETING IKE POLICY')
if self.ike_policy:
self.neutron_client.delete_ikepolicy(
self.ike_policy['ikepolicy']['id'])
@atomic.action_timer("cleanup")
def cleanup(self):
"""Clean the resources"""
vpn_utils.delete_servers(self.nova_client, self.servers)
if self.server_fips:
vpn_utils.delete_floating_ips(self.nova_client, self.server_fips)
vpn_utils.delete_keypairs(self.nova_client, self.keypairs)
if self.qrouterns_compute_tuples:
vpn_utils.delete_hosts_from_knownhosts_file(
self.server_private_ips, self.qrouterns_compute_tuples,
self.private_key_file)
vpn_utils.delete_keyfiles(
self.local_key_files, self.remote_key_files,
self.qrouterns_compute_tuples, self.private_key_file)
else:
vpn_utils.delete_hosts_from_knownhosts_file(
self.server_private_ips)
vpn_utils.delete_keyfiles(self.local_key_files)
self._delete_ipsec_site_connections()
self._delete_vpn_services()
self._delete_ipsec_policy()
self._delete_ike_policy()
vpn_utils.delete_networks(
self.neutron_client, self.neutron_admin_client, self.rally_routers,
self.rally_networks, self.rally_subnets)
if self.tenant_ids:
vpn_utils.delete_tenants(self.keystone_client, self.tenant_ids)