fuel-plugin-contrail/plugin_test/helpers/baremetal.py
Denis Deryabin 1635a5cc8e add SRIOV add/delete controller tests
Add SRIOV test contrail_sriov_add_controller
Add SRIOV test contrail_sriov_delete_controller
some other minor fixes

Change-Id: Icee1b8d3a0251a4e9399c5e5680172c5ba5c7073
2016-06-15 16:27:11 +03:00

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}