1635a5cc8e
Add SRIOV test contrail_sriov_add_controller Add SRIOV test contrail_sriov_delete_controller some other minor fixes Change-Id: Icee1b8d3a0251a4e9399c5e5680172c5ba5c7073
308 lines
11 KiB
Python
308 lines
11 KiB
Python
"""Copyright 2016 Mirantis, Inc.
|
|
|
|
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 time
|
|
import subprocess
|
|
from devops.helpers.helpers import wait
|
|
from fuelweb_test import logger
|
|
from fuelweb_test.settings import iface_alias
|
|
from proboscis.asserts import assert_true
|
|
|
|
import settings
|
|
|
|
|
|
class BridgeTool(object):
|
|
"""Interface for bridge management."""
|
|
|
|
@staticmethod
|
|
def call_cmd(cmd):
|
|
"""Call wrapper for command."""
|
|
try:
|
|
res = subprocess.check_output(
|
|
cmd,
|
|
shell=True,
|
|
stderr=subprocess.STDOUT).replace('\n', '')
|
|
except subprocess.CalledProcessError as e:
|
|
logger.error("Cmd ret code: {0}. Output: {1}".format(
|
|
e.returncode, e.output))
|
|
return e.output
|
|
return res
|
|
|
|
@staticmethod
|
|
def get_bridge_name(netmask):
|
|
"""Return bridge name by it's netmask."""
|
|
cmd = "ip route | grep '{0}' | awk '{{print $3}}'".format(netmask)
|
|
res = BridgeTool.call_cmd(cmd)
|
|
return res
|
|
|
|
@staticmethod
|
|
def check_bridge(bridge, interface):
|
|
"""Check interface in bridge."""
|
|
cmd = "brctl show '{0}' | grep '{1}' | wc -l".format(
|
|
bridge, interface)
|
|
res = BridgeTool.call_cmd(cmd)
|
|
if int(res) > 0:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
@staticmethod
|
|
def del_interface(bridge, interface):
|
|
"""Delete interface from bridge."""
|
|
cmd = "sudo brctl delif '{0}' '{1}'".format(bridge, interface)
|
|
res = BridgeTool.call_cmd(cmd)
|
|
return res
|
|
|
|
@staticmethod
|
|
def add_interface(bridge, interface):
|
|
"""Add interface from bridge."""
|
|
cmd = "sudo brctl addif '{0}' '{1}'".format(bridge, interface)
|
|
res = BridgeTool.call_cmd(cmd)
|
|
return res
|
|
|
|
|
|
class BMDriver(object):
|
|
"""Manage Baremetal environment."""
|
|
|
|
def __init__(self):
|
|
"""Init for this class."""
|
|
self.conf = settings.BAREMETAL
|
|
|
|
def bm_reboot(self):
|
|
"""Reboot slave host via IPMI."""
|
|
ipmi_host = self.conf['ipmi_host']
|
|
ipmi_user = self.conf['ipmi_user']
|
|
ipmi_password = self.conf['ipmi_password']
|
|
|
|
logger.info('Reboot node "{0}" via IPMI'.format(ipmi_host))
|
|
ipmi_cmd = "/usr/bin/ipmitool" \
|
|
" -H {0}" \
|
|
" -U {1}" \
|
|
" -P {2}" \
|
|
" power cycle".format(ipmi_host, ipmi_user, ipmi_password)
|
|
output = subprocess.check_output(ipmi_cmd, shell=True)
|
|
logger.debug('Reboot server output: {0}'.format(output))
|
|
time.sleep(10)
|
|
return True
|
|
|
|
def setup_host_nets(self):
|
|
"""Setup master host - add necessary bridge interfaces."""
|
|
bm_cfg = self.conf['host_bridge_interfaces']
|
|
|
|
logger.info('Setup BM node interfaces "{0}"'.format(bm_cfg))
|
|
ports = []
|
|
for cfg in bm_cfg.replace(' ', '').split(';'):
|
|
if cfg:
|
|
ports.append(cfg.split(':'))
|
|
for br_interface in ports:
|
|
bridge = BridgeTool.get_bridge_name(br_interface[1])
|
|
if not bridge:
|
|
raise Exception("Bridge not found for network: {0}".format(
|
|
br_interface[1]))
|
|
if BridgeTool.check_bridge(bridge, br_interface[0]):
|
|
logger.info('iface "{0}" already in bridge "{1}"'.format(
|
|
br_interface[0], bridge))
|
|
continue
|
|
logger.info('Add iface "{0}" as a port of the bridge "{1}"'.format(
|
|
br_interface[0], bridge))
|
|
ret = BridgeTool.add_interface(bridge, br_interface[0])
|
|
logger.debug('Add iface result: {0}'.format(ret))
|
|
|
|
def wait4node_status(self, obj, node_mac, status=['discover'],
|
|
timeout=settings.DEPLOY_CLUSTER_TIMEOUT):
|
|
"""Wait until slave host is ready."""
|
|
logger.info('Wait for node "{0}" status "{1}"'.format(node_mac,
|
|
status))
|
|
|
|
def check_func():
|
|
node = self.get_bm_node(obj, node_mac)
|
|
if not node:
|
|
return False
|
|
node_info = ('Info:'
|
|
' id:{id},'
|
|
' status:{status},'
|
|
' name:{name},'
|
|
' cluster:{cluster}.'.format(**node))
|
|
if node['status'] in status:
|
|
if not node['online']:
|
|
logger.info('Node found but "offline". ' +
|
|
node_info)
|
|
return False
|
|
else:
|
|
logger.info('Node found. ' + node_info)
|
|
return True
|
|
else:
|
|
logger.warning("Node found, but has unexpected status. " +
|
|
node_info)
|
|
return False
|
|
wtime = wait(check_func, interval=30, timeout=timeout)
|
|
logger.info('Node up in {0} sec.'.format(wtime))
|
|
|
|
def get_bm_node(self, obj, node_mac):
|
|
"""Search for nailgun node by it's MAC address."""
|
|
for node in obj.fuel_web.client.list_nodes():
|
|
if node['mac'] in node_mac:
|
|
return node
|
|
return None
|
|
|
|
def setup_fuel_node(self, obj, cluster_id, roles,
|
|
pending_addition=True, pending_deletion=False,
|
|
update_nodegroups=False, update_interfaces=True):
|
|
"""Setup roles for nailgun node."""
|
|
node_mac = self.conf['target_macs']
|
|
self.wait4node_status(obj, node_mac, status=['discover', 'ready'])
|
|
|
|
node = self.get_bm_node(obj, node_mac)
|
|
logger.info('Setup node "{0}" roles: "{1}"'.format(node['name'],
|
|
roles))
|
|
# update nodes in cluster
|
|
nodes_data = []
|
|
nodes_groups = {}
|
|
updated_nodes = []
|
|
|
|
node_group = 'default'
|
|
|
|
assert_true(node['online'],
|
|
'Node {0} is offline'.format(node['mac']))
|
|
|
|
name = 'Node_{0}_{1}'.format(self.conf['target_macs'],
|
|
"_".join(roles))
|
|
|
|
logger.info('node at name:{0}'.format(node['name']))
|
|
logger.info('roles:{0}'.format(roles))
|
|
logger.info('name:{0}'.format(name))
|
|
|
|
node_data = {
|
|
'cluster_id': cluster_id,
|
|
'id': node['id'],
|
|
'pending_addition': pending_addition,
|
|
'pending_deletion': pending_deletion,
|
|
'pending_roles': roles,
|
|
'name': name
|
|
}
|
|
nodes_data.append(node_data)
|
|
if node_group not in nodes_groups.keys():
|
|
nodes_groups[node_group] = []
|
|
nodes_groups[node_group].append(node)
|
|
updated_nodes.append(node)
|
|
|
|
# assume nodes are going to be updated for one cluster only
|
|
cluster_id = nodes_data[-1]['cluster_id']
|
|
node_ids = [str(node_info['id']) for node_info in nodes_data]
|
|
|
|
obj.fuel_web.client.update_nodes(nodes_data)
|
|
|
|
nailgun_nodes = obj.fuel_web.client.list_cluster_nodes(cluster_id)
|
|
cluster_node_ids = map(lambda _node: str(_node['id']), nailgun_nodes)
|
|
assert_true(
|
|
all([node_id in cluster_node_ids for node_id in node_ids]))
|
|
|
|
if update_interfaces and not pending_deletion:
|
|
self.update_bm_node_interfaces(obj, node)
|
|
if update_nodegroups:
|
|
obj.fuel_web.update_nodegroups(cluster_id=cluster_id,
|
|
node_groups=nodes_groups)
|
|
|
|
return nailgun_nodes
|
|
|
|
def update_bm_node_interfaces(self, obj, node):
|
|
"""Update network interfacess for nailgun node."""
|
|
interfaces = self.get_node_interfaces(
|
|
obj,
|
|
node['id'],
|
|
self.conf['target_macs'].split(';'))
|
|
|
|
def get_free_interface(allocated=[]):
|
|
ifaces = obj.fuel_web.client.get_node_interfaces(node['id'])
|
|
for iface in ifaces:
|
|
if iface['name'] not in allocated:
|
|
return iface['name']
|
|
return None
|
|
|
|
pxe_interface = interfaces['pxe']
|
|
hw = interfaces['common']
|
|
pub = get_free_interface([hw, pxe_interface])
|
|
if pub is None:
|
|
logger.warning("Interface for 'public' network not found.")
|
|
else:
|
|
logger.info("Assign 'public' net on '{0}'".format(pub))
|
|
|
|
logger.info('Found PXE net: {0}; Slave net: {1}.'.format(
|
|
pxe_interface, hw))
|
|
|
|
networks = {
|
|
pxe_interface: ['fuelweb_admin', 'storage', 'management'],
|
|
hw: ['private'],
|
|
pub: ['public']
|
|
}
|
|
|
|
logger.info('Assigned BM networks are: {}'.format(
|
|
str(networks)))
|
|
obj.fuel_web.update_node_networks(node['id'], networks)
|
|
|
|
def update_vm_node_interfaces(self, obj, cluster_id):
|
|
"""Update network interfaces for node."""
|
|
assigned_networks = {
|
|
iface_alias('eth0'): ['fuelweb_admin', 'storage', 'management'],
|
|
iface_alias('eth1'): ['public'],
|
|
iface_alias('eth3'): ['private'],
|
|
}
|
|
logger.info('Assigned networks are: {}'.format(str(assigned_networks)))
|
|
|
|
nailgun_nodes = obj.fuel_web.client.list_cluster_nodes(cluster_id)
|
|
baremetal_macs = self.conf['target_macs']
|
|
for node in nailgun_nodes:
|
|
if not node['status'] == 'discover':
|
|
# Skip not discovered nodes
|
|
continue
|
|
if node['mac'] in baremetal_macs:
|
|
# Skip Barematal node
|
|
continue
|
|
obj.fuel_web.update_node_networks(node['id'], assigned_networks)
|
|
|
|
def host_prepare(self):
|
|
"""Prepare slave host for deploy."""
|
|
self.setup_host_nets()
|
|
self.bm_reboot()
|
|
|
|
def get_node_interfaces(self, obj, node_id, macs):
|
|
"""Return PXE and Common interfaces with given MACs for node.
|
|
|
|
return:
|
|
|
|
{'pxe': interface_name1, 'common': interface_name2}
|
|
|
|
"""
|
|
interfaces = obj.fuel_web.client.get_node_interfaces(node_id)
|
|
logger.info('Check node for PXE interfaces: {0}'.format(
|
|
interfaces))
|
|
iface_pxe = None
|
|
iface_slave = None
|
|
for iface in interfaces:
|
|
logger.info("Check interface: {0}".format(iface))
|
|
if iface['mac'] in macs:
|
|
if iface['pxe']:
|
|
logger.info("Found PXE interface: {0}".format(iface))
|
|
iface_pxe = iface['name']
|
|
else:
|
|
logger.info("Found slave interface: {0}".format(iface))
|
|
iface_slave = iface['name']
|
|
if iface_pxe and iface_slave:
|
|
logger.info('Found PXE net: {0}; Slave net: {1}.'.format(
|
|
iface_pxe, iface_slave
|
|
))
|
|
return {'pxe': iface_pxe, 'common': iface_slave}
|
|
return {'pxe': None, 'common': None}
|