Skip to content

Commit

Permalink
Create the node functionalities for IBMCloud with Bare Metal (#9202)
Browse files Browse the repository at this point in the history
* Create the node functionalities for IBMCloud BM, create a new test file for testing the node restart functionalities, Add a new marker 'provider_client_platform_required'

Signed-off-by: Itzhak Kave <[email protected]>

* Add few small fixes

- add one Polarion ID, and create two new Polarion IDs
- Add the 'region' param to the config file
- Increase timeout waiting for the pods
- Fix the init method of the class 'IBMCloudBM'

Signed-off-by: Itzhak Kave <[email protected]>

---------

Signed-off-by: Itzhak Kave <[email protected]>
Co-authored-by: Itzhak Kave <[email protected]>
  • Loading branch information
yitzhak12 and Itzhak Kave authored Feb 5, 2024
1 parent aaf3846 commit 48160dd
Show file tree
Hide file tree
Showing 5 changed files with 563 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ENV_DATA:
master_replicas: 3
mon_type: 'hostpath'
osd_type: 'nvme'
region: 'us-east'
REPORTING:
# This is to be used in internal image for must gather on HCI
ocs_must_gather_image: "quay.io/rhceph-dev/ocs-must-gather"
Expand Down
5 changes: 5 additions & 0 deletions ocs_ci/framework/pytest_customization/marks.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@
reason="Test runs ONLY on OSD or ROSA cluster",
)

provider_client_platform_required = pytest.mark.skipif(
(config.ENV_DATA["platform"].lower() not in HCI_PROVIDER_CLIENT_PLATFORMS),
reason="Test runs ONLY on cluster with HCI provider-client platform",
)

provider_client_ms_platform_required = pytest.mark.skipif(
(config.ENV_DATA["platform"].lower() not in HCI_PC_OR_MS_PLATFORM),
reason="Test runs ONLY on cluster with managed service or HCI provider-client platform",
Expand Down
131 changes: 130 additions & 1 deletion ocs_ci/ocs/platform_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@
get_module_ip,
get_terraform_ignition_provider,
)
from ocs_ci.ocs.node import wait_for_nodes_status, get_nodes_in_statuses
from ocs_ci.ocs.node import (
wait_for_nodes_status,
get_nodes_in_statuses,
get_node_internal_ip,
)
from ocs_ci.utility.vsphere_nodes import VSPHERENode
from paramiko.ssh_exception import NoValidConnectionsError, AuthenticationException
from semantic_version import Version
Expand Down Expand Up @@ -92,6 +96,7 @@ def __init__(self):
"rosa": AWSNodes,
"vsphere_upi": VMWareUPINodes,
"fusion_aas": AWSNodes,
"hci_baremetal": IBMCloudBMNodes,
}

def get_nodes_platform(self):
Expand Down Expand Up @@ -3070,3 +3075,127 @@ def restart_nodes_by_stop_and_start_teardown(self):
node_names = [n.name for n in not_ready_nodes]
if node_names:
self.gcp.start_instances(node_names)


class IBMCloudBMNodes(NodesBase):
"""
IBM Cloud for Bare metal machines class
"""

def __init__(self):
super(IBMCloudBMNodes, self).__init__()
from ocs_ci.utility import ibmcloud_bm

self.ibmcloud_bm = ibmcloud_bm.IBMCloudBM()

def get_machines(self, nodes):
"""
Get the machines associated with the given nodes
Args:
nodes (list): The OCS objects of the nodes
Returns:
list: List of dictionaries. List of the machines associated with the given nodes
"""
node_ips = [get_node_internal_ip(n) for n in nodes]
return self.ibmcloud_bm.get_machines_by_ips(node_ips)

def stop_nodes(self, nodes, wait=True):
"""
Stop nodes
Args:
nodes (list): The OCS objects of the nodes
wait (bool): If True, wait for the nodes to be in a NotReady state. False, otherwise
"""
machines = self.get_machines(nodes)
self.ibmcloud_bm.stop_machines(machines)
if wait:
node_names = [n.name for n in nodes]
wait_for_nodes_status(
node_names, constants.NODE_NOT_READY, timeout=180, sleep=5
)

def start_nodes(self, nodes, wait=True):
"""
Start nodes
Args:
nodes (list): The OCS objects of the nodes
wait (bool): If True, wait for the nodes to be ready. False, otherwise
"""
machines = self.get_machines(nodes)
self.ibmcloud_bm.start_machines(machines)
if wait:
node_names = [n.name for n in nodes]
wait_for_nodes_status(
node_names, constants.NODE_READY, timeout=720, sleep=20
)

def restart_nodes(self, nodes, wait=True, force=False):
"""
Restart nodes
Args:
nodes (list): The OCS objects of the nodes
wait (bool): If True, wait for the nodes to be ready. False, otherwise
force (bool): If True, it will force restarting the nodes. False, otherwise.
Default value is False.
"""
machines = self.get_machines(nodes)
self.ibmcloud_bm.restart_machines(machines, force=force)
if wait:
node_names = [n.name for n in nodes]
logger.info(
f"Wait for the nodes {node_names} to reach the status {constants.NODE_NOT_READY}"
)
wait_for_nodes_status(
node_names, constants.NODE_NOT_READY, timeout=180, sleep=5
)
logger.info(
f"Wait for the nodes {node_names} to be in a Ready status again"
)
wait_for_nodes_status(
node_names, constants.NODE_READY, timeout=720, sleep=20
)

def restart_nodes_by_stop_and_start(self, nodes, wait=True):
"""
Restart the nodes by stop and start
Args:
nodes (list): The OCS objects of the nodes
wait (bool): If True, wait for the nodes to be ready. False, otherwise
"""
self.stop_nodes(nodes, wait=True)
self.start_nodes(nodes, wait=wait)

def restart_nodes_by_stop_and_start_teardown(self):
"""
Start the nodes in a NotReady state
"""
nodes_not_ready = get_nodes_in_statuses([constants.NODE_NOT_READY])
machines = self.get_machines(nodes_not_ready)
self.ibmcloud_bm.start_machines(machines)

def create_nodes(self, node_conf, node_type, num_nodes):
"""
Create nodes
"""
raise NotImplementedError("Create nodes functionality not implemented")

def terminate_nodes(self, nodes, wait=True):
"""
Terminate nodes
"""
raise NotImplementedError("terminate nodes functionality is not implemented")
154 changes: 154 additions & 0 deletions ocs_ci/utility/ibmcloud_bm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# -*- coding: utf8 -*-
"""
Module for interactions with IBM Cloud Cluster.
"""

import json
import logging

from ocs_ci.framework import config
from ocs_ci.ocs.exceptions import CommandFailed
from ocs_ci.utility.utils import run_cmd


logger = logging.getLogger(name=__file__)
ibm_config = config.AUTH.get("ibmcloud", {})


class IBMCloudBM(object):
"""
Wrapper for IBM Cloud with Bare metal machines
"""

def __init__(self, region=None):
"""
Constructor for IBM Cloud Bare Metal machines
Args:
region (str): The region of the IBM Cloud Bare Metal machines
"""
self.api_key = ibm_config["api_key"]
self.account_id = ibm_config.get("account_id")
self.region = region or config.ENV_DATA.get("region")

def login(self):
"""
Login to IBM Cloud account
"""
login_cmd = f"ibmcloud login --apikey {self.api_key} -c {self.account_id} -r {self.region}"
logger.info("Logging to IBM cloud")
run_cmd(login_cmd, secrets=[self.api_key])
logger.info("Successfully logged in to IBM cloud")

def run_ibmcloud_bm_cmd(
self, cmd, secrets=None, timeout=600, ignore_error=False, **kwargs
):
"""
Wrapper function for `run_cmd` which if needed will perform IBM Cloud login
command before running the ibmcloud bare metal command. In the case run_cmd will fail
because the IBM cloud got disconnected, it will login and re-try.
Args:
cmd (str): command to run
secrets (list): A list of secrets to be masked with asterisks
This kwarg is popped in order to not interfere with
subprocess.run(``**kwargs``)
timeout (int): Timeout for the command, defaults to 600 seconds.
ignore_error (bool): True if ignore non zero return code and do not
raise the exception.
"""
basic_cmd = "ibmcloud sl hardware "
cmd = basic_cmd + cmd

try:
return run_cmd(cmd, secrets, timeout, ignore_error, **kwargs)
except CommandFailed as ex:
login_error_messages = [
"Error: Failed to get",
"Access Denied",
"Please login",
]
# Check if we need to re-login to IBM Cloud account
if any([error_msg in str(ex) for error_msg in login_error_messages]):
self.login()
return run_cmd(cmd, secrets, timeout, ignore_error, **kwargs)

def get_all_machines(self):
"""
Get all the machines in the IBMCloud Bare metal machines
Returns:
list: List of dictionaries. List of all the machines in the IBMCloud Bare metal machines
"""
cmd = "list --output json"
machine_list = json.loads(self.run_ibmcloud_bm_cmd(cmd))
return machine_list

def get_machines_by_ips(self, machine_ips):
"""
Get the machines in the IBMCloud Bare metal machines that have the given machine IPs
Args:
machine_ips (list): The list of the machine IPs to search for.
Returns:
Get the machines in the IBMCloud Bare metal machines that have the given machine IPs
"""
machine_list = self.get_all_machines()
return [m for m in machine_list if m["primaryIpAddress"] in machine_ips]

def stop_machines(self, machines):
"""
Stop the IBMCloud Bare metal machines
Args:
machines (list): List of the IBMCLoud Bare metal machines objects to stop
"""
for m in machines:
logger.info(f"Powering off the machine with ip {m['primaryIpAddress']}")
cmd = f"power-off {m['id']} -f"
self.run_ibmcloud_bm_cmd(cmd)

def start_machines(self, machines):
"""
Start the IBMCloud Bare metal machines
Args:
machines (list): List of the IBMCLoud Bare metal machines objects to start
"""
for m in machines:
logger.info(f"Powering on the machine with ip {m['primaryIpAddress']}")
cmd = f"power-on {m['id']}"
self.run_ibmcloud_bm_cmd(cmd)

def restart_machines(self, machines, force=False):
"""
Reboot the IBMCloud Bare metal machines
Args:
machines (list): List of the IBMCLoud Bare metal machines objects to restart
force (bool): If False, will perform a soft reboot. Otherwise, if True, will perform a hard reboot
"""
reboot_type = "hard" if force else "soft"
for m in machines:
logger.info(f"Reboot the machine with the ip {m['primaryIpAddress']}")
cmd = f"reboot {m['id']} -f --{reboot_type}"
self.run_ibmcloud_bm_cmd(cmd)

def restart_machines_by_stop_and_start(self, machines):
"""
Restart the IBMCloud Bare metal machines by stop and start
Args:
machines (list): List of the IBMCLoud Bare metal machines objects to restart
"""
self.stop_machines(machines)
self.start_machines(machines)
Loading

0 comments on commit 48160dd

Please sign in to comment.