Files
krkn/kraken/node_actions/bm_node_scenarios.py
Jared O'Connell 9b83dbcf04 Baremetal Node Support (#74)
* Support for baremtal node scenarious

* Finished baremetal support

* Added documentation for baremetal

* Clarify limitations of implementation in documentation

* Add baremetal support to new run.py file

* Allow use on newer machines

Some older machines require lanplus instead of lan

* Setup to allow per-device user, pass, and bmc address

Also set min version for a dependency

* Fix linting issues

* More linting issue fixes

* More linter issues

* Account for linter standard non-conformity

* Added baremetal warning

Co-authored-by: jaredoconnell <jocnnel@redhat.com>
2021-07-02 17:31:40 -04:00

179 lines
7.9 KiB
Python

import kraken.node_actions.common_node_functions as nodeaction
from kraken.node_actions.abstract_node_scenarios import abstract_node_scenarios
import logging
import openshift as oc
import pyipmi
import pyipmi.interfaces
import sys
import time
import traceback
class BM:
def __init__(self, bm_info, user, passwd):
self.user = user
self.passwd = passwd
self.bm_info = bm_info
def get_node_object(self, node_name):
with oc.project("openshift-machine-api"):
return oc.selector("node/" + node_name).object()
# Get the ipmi or other BMC address of the baremetal node
def get_bmc_addr(self, node_name):
# Addresses in the config get higher priority.
if self.bm_info is not None and node_name in self.bm_info and "bmc_addr" in self.bm_info[node_name]:
return self.bm_info[node_name]["bmc_addr"]
# Get the bmc addr from the BareMetalHost object.
with oc.project("openshift-machine-api"):
logging.info("Getting node with name: %s" % (node_name))
node = self.get_node_object(node_name)
provider_id = node.model.spec.providerID
startOfUid = provider_id.rfind("/") # The / before the uid
startOfName = provider_id.rfind("/", 0, startOfUid) + 1
bmh_name = provider_id[startOfName:startOfUid]
bmh_resource_name = "baremetalhost.metal3.io/" + bmh_name
bmh_object = oc.selector(bmh_resource_name).object()
if len(bmh_object.model.spec.bmc.addr) == 0:
logging.error(
'BMC addr empty for node "%s". Either fix the BMH object,'
" or specify the address in the scenario config" % node_name
)
sys.exit(1)
return bmh_object.model.spec.bmc.address
def get_ipmi_connection(self, bmc_addr, node_name):
type_position = bmc_addr.find("://")
if type_position == -1:
host = bmc_addr
else:
host = bmc_addr[type_position + 3 :]
port_position = host.find(":")
if port_position == -1:
port = 623
else:
port = int(host[port_position + 1 :])
host = host[0:port_position]
# Determine correct username and password
# If specified, uses device-specific user/pass. Else uses the global one.
if self.bm_info is not None and node_name in self.bm_info:
user = self.bm_info[node_name].get("bmc_user", self.user)
passwd = self.bm_info[node_name].get("bmc_password", self.passwd)
else:
user = self.user
passwd = self.passwd
if user is None or passwd is None:
logging.error(
"Missing IPMI BMI user and/or password for baremetal cloud. "
"Please specify either a global or per-machine user and pass"
)
sys.exit(1)
# Establish connection
interface = pyipmi.interfaces.create_interface("ipmitool", interface_type="lanplus")
connection = pyipmi.create_connection(interface)
connection.target = pyipmi.Target(ipmb_address=0x20)
connection.session.set_session_type_rmcp(host, port)
connection.session.set_auth_type_user(user, passwd)
connection.session.establish()
return connection
# Start the node instance
def start_instances(self, bmc_addr, node_name):
self.get_ipmi_connection(bmc_addr, node_name).chassis_control_power_up()
# Stop the node instance
def stop_instances(self, bmc_addr, node_name):
self.get_ipmi_connection(bmc_addr, node_name).chassis_control_power_down()
# Reboot the node instance
def reboot_instances(self, bmc_addr, node_name):
self.get_ipmi_connection(bmc_addr, node_name).chassis_control_power_cycle()
# Wait until the node instance is running
def wait_until_running(self, bmc_addr, node_name):
while not self.get_ipmi_connection(bmc_addr, node_name).get_chassis_status().power_on:
time.sleep(1)
# Wait until the node instance is stopped
def wait_until_stopped(self, bmc_addr, node_name):
while self.get_ipmi_connection(bmc_addr, node_name).get_chassis_status().power_on:
time.sleep(1)
class bm_node_scenarios(abstract_node_scenarios):
def __init__(self, bm_info, user, passwd):
self.bm = BM(bm_info, user, passwd)
# Node scenario to start the node
def node_start_scenario(self, instance_kill_count, node, timeout):
for _ in range(instance_kill_count):
try:
logging.info("Starting node_start_scenario injection")
bmc_addr = self.bm.get_bmc_addr(node)
logging.info("Starting the node %s with bmc address: %s " % (node, bmc_addr))
self.bm.start_instances(bmc_addr, node)
self.bm.wait_until_running(bmc_addr, node)
nodeaction.wait_for_ready_status(node, timeout)
logging.info("Node with bmc address: %s is in running state" % (bmc_addr))
logging.info("node_start_scenario has been successfully injected!")
except Exception as e:
logging.error(
"Failed to start node instance. Encountered following "
"exception: %s. Test Failed. Most errors are caused by "
"an incorrect ipmi address or login" % (e)
)
logging.error("node_start_scenario injection failed!")
sys.exit(1)
# Node scenario to stop the node
def node_stop_scenario(self, instance_kill_count, node, timeout):
for _ in range(instance_kill_count):
try:
logging.info("Starting node_stop_scenario injection")
bmc_addr = self.bm.get_bmc_addr(node)
logging.info("Stopping the node %s with bmc address: %s " % (node, bmc_addr))
self.bm.stop_instances(bmc_addr, node)
self.bm.wait_until_stopped(bmc_addr, node)
logging.info("Node with bmc address: %s is in stopped state" % (bmc_addr))
nodeaction.wait_for_unknown_status(node, timeout)
except Exception as e:
logging.error(
"Failed to stop node instance. Encountered following exception: %s. "
"Test Failed. Most errors are caused by "
"an incorrect ipmi address or login" % (e)
)
logging.error("node_stop_scenario injection failed!")
sys.exit(1)
# Node scenario to terminate the node
def node_termination_scenario(self, instance_kill_count, node, timeout):
logging.info("Node termination scenario is not supported on baremetal")
# Node scenario to reboot the node
def node_reboot_scenario(self, instance_kill_count, node, timeout):
for _ in range(instance_kill_count):
try:
logging.info("Starting node_reboot_scenario injection")
bmc_addr = self.bm.get_bmc_addr(node)
logging.info("BMC Addr: %s" % (bmc_addr))
logging.info("Rebooting the node %s with bmc address: %s " % (node, bmc_addr))
self.bm.reboot_instances(bmc_addr, node)
nodeaction.wait_for_unknown_status(node, timeout)
nodeaction.wait_for_ready_status(node, timeout)
logging.info("Node with bmc address: %s has been rebooted" % (bmc_addr))
logging.info("node_reboot_scenario has been successfuly injected!")
except Exception as e:
logging.error(
"Failed to reboot node instance. Encountered following exception:"
" %s. Test Failed. Most errors are caused by "
"an incorrect ipmi address or login" % (e)
)
traceback.print_exc()
logging.error("node_reboot_scenario injection failed!")
sys.exit(1)