Add support for podified environment
In order to support podified environment the following new configs were added: - proxy_host_address - proxy_host_user - proxy_host_key_data - proxy_host_inventory_path base.py contains the following changes: - Enhanced node discovery to get missing info from the inventory file. - Implemented ovn dbs access through openshift client on a proxy host - check_service_setting function is now able to check a list of config files inside a pod. Access to the operator pod through a proxy host. Change-Id: Idb3bc9d1a304b6e7a77393580c110ed08e863a90
This commit is contained in:
parent
46de9d814c
commit
2a5b9be923
@ -90,5 +90,22 @@ WhiteboxNeutronPluginOptions = [
|
||||
cfg.IntOpt('ovn_max_controller_gw_ports_per_router',
|
||||
default=1,
|
||||
help='The number of network nodes used '
|
||||
'for the OVN router HA.')
|
||||
'for the OVN router HA.'),
|
||||
cfg.StrOpt('proxy_host_address',
|
||||
default='',
|
||||
help='Intermediate host to run commands on podified '
|
||||
'environment'),
|
||||
cfg.StrOpt('proxy_host_user',
|
||||
default='',
|
||||
help='User of intermediate host to run commands on podified '
|
||||
'environment'),
|
||||
cfg.StrOpt('proxy_host_key_data',
|
||||
default='{}',
|
||||
help='Key data for accessing intermediate host on podified '
|
||||
'environment, in dict format, i.e. {"key":"key_data"}.'
|
||||
'The key_data should be key without first and last line '
|
||||
'and all new lines replaced by \n'),
|
||||
cfg.StrOpt('proxy_host_inventory_path',
|
||||
default='',
|
||||
help='Nodes inventory on proxy host on podified environment')
|
||||
]
|
||||
|
@ -13,9 +13,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
import yaml
|
||||
|
||||
import netaddr
|
||||
from netifaces import AF_INET
|
||||
@ -66,8 +69,13 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
cls.neutron_api_prefix = ''
|
||||
cls.neutron_conf = WB_CONF.neutron_config
|
||||
elif WB_CONF.openstack_type == 'podified':
|
||||
# NOTE(mblue): add podified option
|
||||
pass
|
||||
cls.proxy_host_key = cls._get_podified_proxy_host_key()
|
||||
cls.proxy_host_client = cls.get_node_client(
|
||||
host=WB_CONF.proxy_host_address,
|
||||
username=WB_CONF.proxy_host_user,
|
||||
pkey=f"{cls.proxy_host_key}")
|
||||
cls.master_node_client = cls.proxy_host_client
|
||||
cls.master_cont_cmd_executor = cls.proxy_host_client
|
||||
else:
|
||||
LOG.warning(("Unrecognized deployer tool '{}', plugin supports "
|
||||
"openstack_type as devstack/podified."
|
||||
@ -75,11 +83,13 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
|
||||
@classmethod
|
||||
def run_on_master_controller(cls, cmd):
|
||||
if WB_CONF.openstack_type == 'podified':
|
||||
output = cls.proxy_host_client.exec_command(cmd)
|
||||
if WB_CONF.openstack_type == 'devstack':
|
||||
output, errors = local_utils.run_local_cmd(cmd)
|
||||
LOG.debug("Stderr: {}".format(errors.decode()))
|
||||
output = output.decode()
|
||||
LOG.debug("Output: {}".format(output))
|
||||
LOG.debug("Output: {}".format(output))
|
||||
return output.strip()
|
||||
|
||||
def get_host_for_server(self, server_id):
|
||||
@ -98,10 +108,14 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
return subnet['gateway_ip']
|
||||
|
||||
@staticmethod
|
||||
def get_node_client(host):
|
||||
return ssh.Client(
|
||||
host=host, username=WB_CONF.overcloud_ssh_user,
|
||||
key_filename=WB_CONF.overcloud_key_file)
|
||||
def get_node_client(
|
||||
host, username=WB_CONF.overcloud_ssh_user, pkey=None,
|
||||
key_filename=WB_CONF.overcloud_key_file):
|
||||
if pkey:
|
||||
return ssh.Client(host=host, username=username, pkey=pkey)
|
||||
else:
|
||||
return ssh.Client(host=host, username=username,
|
||||
key_filename=key_filename)
|
||||
|
||||
def get_local_ssh_client(self, network):
|
||||
return ssh.Client(
|
||||
@ -128,9 +142,61 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
return ip_address
|
||||
return None
|
||||
|
||||
def get_fip_port_details(self, fip):
|
||||
fip_ports = self.os_admin.network_client.list_ports(
|
||||
network_id=CONF.network.public_network_id,
|
||||
device_owner=constants.DEVICE_OWNER_FLOATINGIP)['ports']
|
||||
for fp in fip_ports:
|
||||
if (fp.get('fixed_ips') and len(fp['fixed_ips']) != 0 and
|
||||
fp['fixed_ips'][0]['ip_address'] ==
|
||||
fip['floating_ip_address']):
|
||||
return fp
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_podified_nodes_data(cls):
|
||||
|
||||
def append_node_data(node, node_group_data):
|
||||
if 'ocp' in node:
|
||||
node_name = node.replace("ocp", "master")
|
||||
key = 'ansible_ssh_private_key_file' # meaning dict key here
|
||||
# save path of ocp nodes key (if not yet), we'll need it later
|
||||
if not hasattr(cls, 'ocp_nodes_key_path'):
|
||||
cls.ocp_nodes_key_path = (
|
||||
node_group_data[node][key].replace(
|
||||
'~', '/home/{}'.format(WB_CONF.proxy_host_user)))
|
||||
node_key = node_group_data[node][key].split('/')[-1]
|
||||
else:
|
||||
node_name = node
|
||||
node_key = 'id_cifw_key'
|
||||
node_data = {
|
||||
'name': node_name,
|
||||
'ip': node_group_data[node]['ansible_host'],
|
||||
'user': node_group_data[node]['ansible_user'],
|
||||
'key': node_key}
|
||||
nodes.append(node_data)
|
||||
|
||||
nodes = []
|
||||
inventory_data = yaml.safe_load(
|
||||
cls.proxy_host_client.exec_command(
|
||||
'cat ' + WB_CONF.proxy_host_inventory_path))
|
||||
computes_data = inventory_data['computes']['hosts']
|
||||
for node in computes_data:
|
||||
append_node_data(node, computes_data)
|
||||
ocps_data = inventory_data['ocps']['hosts']
|
||||
for node in ocps_data:
|
||||
append_node_data(node, ocps_data)
|
||||
return nodes
|
||||
|
||||
@classmethod
|
||||
def _get_podified_proxy_host_key(cls):
|
||||
start = '-----BEGIN OPENSSH PRIVATE KEY-----\n'
|
||||
end = '-----END OPENSSH PRIVATE KEY-----\n'
|
||||
key = json.loads(WB_CONF.proxy_host_key_data)['key']
|
||||
return '{}{}{}'.format(start, key, end)
|
||||
|
||||
@classmethod
|
||||
def append_node(cls, host, is_compute=False, is_networker=False):
|
||||
|
||||
hostname = host.split('.')[0]
|
||||
for node in cls.nodes:
|
||||
if node['name'] == hostname:
|
||||
@ -139,12 +205,23 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
if not node['is_compute']:
|
||||
node['is_compute'] = is_compute
|
||||
return
|
||||
|
||||
node = {'name': hostname,
|
||||
'client': cls.get_node_client(host),
|
||||
'is_networker': is_networker,
|
||||
'is_controller': False,
|
||||
'is_compute': is_compute}
|
||||
if WB_CONF.openstack_type == 'podified':
|
||||
for node in cls.nodes_data:
|
||||
LOG.debug(
|
||||
"hostname='{}', name='{}'".format(hostname, node['name']))
|
||||
if node['name'] == hostname:
|
||||
extra_params = {
|
||||
'client': cls.get_node_client(
|
||||
host=node['ip'], username=node['user'],
|
||||
pkey=f"{cls.keys_data[node['key']]}")}
|
||||
break
|
||||
else:
|
||||
extra_params = {'client': cls.get_node_client(host)}
|
||||
params = {'name': hostname,
|
||||
'is_networker': is_networker,
|
||||
'is_controller': False,
|
||||
'is_compute': is_compute}
|
||||
node = {**params, **extra_params}
|
||||
# Here we are checking if there are controller-specific
|
||||
# processes running on the node
|
||||
output = node['client'].exec_command(
|
||||
@ -155,6 +232,12 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
|
||||
@classmethod
|
||||
def discover_nodes(cls):
|
||||
if WB_CONF.openstack_type == 'podified':
|
||||
cls.nodes_data = cls.get_podified_nodes_data()
|
||||
cls.keys_data = {
|
||||
'id_cifw_key': cls.proxy_host_key,
|
||||
'devscripts_key': cls.proxy_host_client.exec_command(
|
||||
'cat ' + cls.ocp_nodes_key_path)}
|
||||
agents = cls.os_admin.network.AgentsClient().list_agents()['agents']
|
||||
if cls.has_ovn_support:
|
||||
l3_agent_hosts = [
|
||||
@ -173,6 +256,69 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
for host in l3_agent_hosts:
|
||||
cls.append_node(host, is_networker=True)
|
||||
|
||||
@classmethod
|
||||
def get_pod_of_service(cls, service='neutron'):
|
||||
# (rsafrono) at this moment only neutron service pod handled
|
||||
# since it's the only that existing tests are using
|
||||
if service == 'neutron':
|
||||
return cls.proxy_host_client.exec_command(
|
||||
"oc get pods | grep neutron | grep -v meta | "
|
||||
"cut -d' ' -f1").strip()
|
||||
|
||||
@classmethod
|
||||
def get_configs_of_service(cls, service='neutron'):
|
||||
# (rsafrono) at this moment only neutron configs were handled
|
||||
# since it's the only service that existing tests are using
|
||||
if service == 'neutron':
|
||||
pod = cls.get_pod_of_service(service)
|
||||
return cls.proxy_host_client.exec_command(
|
||||
'oc rsh {} find {} -type f'.format(pod, os.path.split(
|
||||
WB_CONF.neutron_config)[0])).strip().split('\n')
|
||||
|
||||
@classmethod
|
||||
def check_service_setting(
|
||||
cls, host, service='neutron', config_files=None,
|
||||
section='DEFAULT', param='', value='True',
|
||||
msg='Required config value is missing', skip_if_fails=True):
|
||||
"""Check if a service on a node has a setting with a value in config
|
||||
|
||||
:param node(dict): Dictionary with host-related parameters,
|
||||
host['client'] is a required parameter
|
||||
:param service(str): Name of the containerized service.
|
||||
:param config_files(list): List with paths to config files. List makes
|
||||
sense on podified where e.g. neutron has
|
||||
2 config files with same sections.
|
||||
:param section(str): Section in the config file.
|
||||
:param value(str): Expected value.
|
||||
:param msg(str): Message to print in case of expected value not found
|
||||
:param skip_if_fails(bool): skip if the check fails - if it fails and
|
||||
skip_if_fails is False, return False.
|
||||
|
||||
"""
|
||||
|
||||
if WB_CONF.openstack_type == 'podified':
|
||||
service_prefix = "oc rsh {}".format(
|
||||
cls.get_pod_of_service(service))
|
||||
else:
|
||||
service_prefix = ""
|
||||
cmd_prefix = "crudini --get"
|
||||
for config_file in config_files:
|
||||
setting = "{} {} {}".format(config_file, section, param)
|
||||
cmd = "{} {} {} || true".format(
|
||||
service_prefix, cmd_prefix, setting)
|
||||
LOG.debug("Command = '{}'".format(cmd))
|
||||
result = host['client'].exec_command(cmd)
|
||||
LOG.debug("Result = '{}'".format(result))
|
||||
if value in result:
|
||||
return True
|
||||
else:
|
||||
continue
|
||||
|
||||
if skip_if_fails:
|
||||
raise cls.skipException(msg)
|
||||
else:
|
||||
return False
|
||||
|
||||
def _create_server_for_topology(
|
||||
self, network_id=None, port_type=None,
|
||||
different_host=None, port_qos_policy_id=None):
|
||||
@ -458,22 +604,40 @@ class BaseTempestTestCaseOvn(BaseTempestWhiteboxTestCase):
|
||||
|
||||
@classmethod
|
||||
def _get_ovn_db_monitor_cmds(cls):
|
||||
regex = r'--db=(.*)$'
|
||||
# this regex search will return the connection string (tcp:IP:port or
|
||||
# ssl:IP:port) and in case of TLS, will also include the TLS options
|
||||
nb_monitor_connection_opts = re.search(regex, cls.nbctl).group(1)
|
||||
sb_monitor_connection_opts = re.search(regex, cls.sbctl).group(1)
|
||||
monitorcmdprefix = 'sudo timeout 300 ovsdb-client monitor -f json '
|
||||
return (monitorcmdprefix + nb_monitor_connection_opts,
|
||||
monitorcmdprefix + sb_monitor_connection_opts)
|
||||
if WB_CONF.openstack_type == 'podified':
|
||||
# (rsafrono) still need to re-check if works properly
|
||||
nb_monitor_connection_opts = cls.nbctl.replace(
|
||||
'ovn-nbctl', '{} punix:/tmp/ovnnb_db.sock'.format(
|
||||
monitorcmdprefix.replace('sudo', '')))
|
||||
sb_monitor_connection_opts = cls.sbctl.replace(
|
||||
'ovn-sbctl', '{} punix:/tmp/ovsnb_db.sock'.format(
|
||||
monitorcmdprefix.replace('sudo', '')))
|
||||
return (nb_monitor_connection_opts, sb_monitor_connection_opts)
|
||||
if WB_CONF.openstack_type == 'devstack':
|
||||
regex = r'--db=(.*)$'
|
||||
# this regex search will return the connection string
|
||||
# (tcp:IP:port or ssl:IP:port) and in case of TLS,
|
||||
# will also include the TLS options
|
||||
nb_monitor_connection_opts = re.search(regex, cls.nbctl).group(1)
|
||||
sb_monitor_connection_opts = re.search(regex, cls.sbctl).group(1)
|
||||
return (monitorcmdprefix + nb_monitor_connection_opts,
|
||||
monitorcmdprefix + sb_monitor_connection_opts)
|
||||
|
||||
@classmethod
|
||||
def _get_ovn_dbs(cls):
|
||||
if WB_CONF.openstack_type == 'podified':
|
||||
sb_pod = cls.proxy_host_client.exec_command(
|
||||
"oc get pods | grep ovsdbserver-sb | cut -f1 -d' '").strip()
|
||||
sb_prefix = 'oc rsh {}'.format(sb_pod)
|
||||
nb_prefix = sb_prefix.replace('sb', 'nb')
|
||||
cmd = "{} ovn-{}ctl"
|
||||
return [cmd.format(nb_prefix, 'nb'), cmd.format(sb_prefix, 'sb')]
|
||||
if WB_CONF.openstack_type == 'devstack':
|
||||
sbdb = "unix:/usr/local/var/run/ovn/ovnsb_db.sock"
|
||||
nbdb = sbdb.replace('sb', 'nb')
|
||||
cmd = ("sudo ovn-{}ctl --db={}")
|
||||
return [cmd.format('nb', nbdb), cmd.format('sb', sbdb)]
|
||||
cmd = "sudo ovn-{}ctl --db={}"
|
||||
return [cmd.format('nb', nbdb), cmd.format('sb', sbdb)]
|
||||
|
||||
def get_router_gateway_chassis(self, router_port_id):
|
||||
cmd = "{} get port_binding cr-lrp-{} chassis".format(
|
||||
|
Loading…
Reference in New Issue
Block a user