Skip to content

Commit

Permalink
Creae the GCP node functionalities in the file 'ocs_ci/utility/gcp.py…
Browse files Browse the repository at this point in the history
…', Add the necessary pip packages for Google Cloud platform, create the GCPNodes class in the 'platform_nodes' file"

Signed-off-by: Itzhak Kave <[email protected]>
  • Loading branch information
Itzhak Kave committed Nov 5, 2023
1 parent 1b3ebbc commit d2180f7
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 2 deletions.
9 changes: 9 additions & 0 deletions ocs_ci/ocs/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2323,3 +2323,12 @@

# dir of template for html reports
HTML_REPORT_TEMPLATE_DIR = "ocs_ci/templates/html_reports/"


# Google Cloud platform
GCP_PROJECT_ODF_QE = "odf-qe"
# Operation names
OPERATION_STOP = "stop"
OPERATION_START = "start"
OPERATION_RESTART = "restart"
OPERATION_TERMINATE = "terminate"
4 changes: 4 additions & 0 deletions ocs_ci/ocs/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,3 +653,7 @@ class NoThreadingLockUsedError(Exception):

class VSLMNotFoundException(Exception):
pass


class OperationFailedToCompleteException(Exception):
pass
42 changes: 40 additions & 2 deletions ocs_ci/ocs/platform_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
get_module_ip,
get_terraform_ignition_provider,
)
from ocs_ci.ocs.node import wait_for_nodes_status
from ocs_ci.ocs.node import wait_for_nodes_status, get_nodes_in_statuses
from ocs_ci.utility.vsphere_nodes import VSPHERENode
from paramiko.ssh_exception import NoValidConnectionsError, AuthenticationException
from semantic_version import Version
Expand All @@ -83,7 +83,7 @@ def __init__(self):
"aws": AWSNodes,
"baremetal": BaremetalNodes,
"azure": AZURENodes,
"gcp": NodesBase,
"gcp": GCPNodes,
"vsphere_lso": VMWareLSONodes,
"powervs": IBMPowerNodes,
"rhv": RHVNodes,
Expand Down Expand Up @@ -2963,3 +2963,41 @@ def terminate_nodes(self, nodes, wait=True):
for vm_name in vm_names:
node_cls_obj.change_terraform_statefile_after_remove_vm(vm_name)
node_cls_obj.change_terraform_tfvars_after_remove_vm()


class GCPNodes(NodesBase):
"""
Google Cloud Platform Nodes class
"""

def __init__(self):
super(GCPNodes, self).__init__()
from ocs_ci.utility import gcp

self.gcp = gcp.GoogleCloud()

def stop_nodes(self, nodes, wait=True):
node_names = [n.name for n in nodes]
self.gcp.stop_instances(node_names)

def start_nodes(self, nodes, wait=True):
node_names = [n.name for n in nodes]
self.gcp.start_instances(node_names)

def restart_nodes(self, nodes, wait=True):
node_names = [n.name for n in nodes]
self.gcp.restart_instances(node_names, wait)

def terminate_nodes(self, nodes, wait=True):
node_names = [n.name for n in nodes]
self.gcp.terminate_instances(node_names, wait)

def restart_nodes_by_stop_and_start(self, nodes, force=True):
node_names = [n.name for n in nodes]
self.gcp.restart_instances_by_stop_and_start(node_names)

def restart_nodes_by_stop_and_start_teardown(self):
not_ready_nodes = get_nodes_in_statuses([constants.NODE_NOT_READY])
node_names = [n.name for n in not_ready_nodes]
self.gcp.start_instances(node_names)
224 changes: 224 additions & 0 deletions ocs_ci/utility/gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,25 @@
import json
import logging
import os
import time

from libcloud.compute.types import Provider
from libcloud.compute.providers import get_driver
from googleapiclient.discovery import build

from ocs_ci.framework import config
from ocs_ci.utility.utils import TimeoutSampler
from ocs_ci.ocs.exceptions import (
TimeoutExpiredError,
OperationFailedToCompleteException,
)
from ocs_ci.ocs.constants import (
GCP_PROJECT_ODF_QE,
OPERATION_STOP,
OPERATION_START,
OPERATION_RESTART,
OPERATION_TERMINATE,
)


logger = logging.getLogger(name=__file__)
Expand Down Expand Up @@ -109,3 +123,213 @@ def compute_driver(self):
datacenter=self._region_name,
)
return self._compute_driver


class GoogleCloud(object):
"""
This is a wrapper class for GoogleCloud
"""

def __init__(self, project_id=None):
"""
Constructor for GoogleCloud class
Args:
project_id (str): The project id in Google Cloud.
"""
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = SERVICE_ACCOUNT_KEY_FILEPATH
# The Google Compute Engine API for performing the instance operation
self.compute = build("compute", "v1", cache_discovery=False)
# The project id in Google Cloud, in which we will perform all the instance operations
self.project_id = project_id or GCP_PROJECT_ODF_QE

def base_instance_operation(self, operation_name, zone, instance_name):
"""
The base method for performing the instance operations: 'stop', 'start', 'restart', 'terminate'.
Args:
operation_name (str): The instance operation name('stop', 'start', etc.)
zone: The instance zone
instance_name: The instance name
Returns:
dict: The result of the execution made by the instance operation
"""
operation_name_func_call_dict = {
OPERATION_STOP: self.compute.instances().stop,
OPERATION_START: self.compute.instances().start,
OPERATION_RESTART: self.compute.instances().reset,
OPERATION_TERMINATE: self.compute.instances().delete,
}

request = operation_name_func_call_dict[operation_name](
project=self.project_id, zone=zone, instance=instance_name
)

return request.execute()

# def get_instance_status(self, zone, instance_name):
# request = self.compute.instances().get(
# project=self.project_id,
# zone=zone,
# instance=instance_name,
# )
# response = request.execute()
# return response["status"]
#
# def get_instances_in_statuses(self, instance_names, statuses):
# """
# Get all the instances
# Args:
# instance_names:
# statuses:
#
# Returns:
#
# """
# zones = self.get_instances_zones(instance_names)
# return [for ]

def get_instance_zone_dict(self):
"""
Get the instance name per instance zone dictionary for all the instances
Returns:
dict: The instance name per instance zone dictionary for all the instances
"""
# Send a request to list instances aggregated by zone
request = self.compute.instances().aggregatedList(project=self.project_id)
response = request.execute()

instance_zone_dict = {}
# Iterate over the response to access instance information
for zone, instances in response["items"].items():
if "instances" in instances:
for instance in instances["instances"]:
instance_zone_dict[instance["name"]] = zone.split("/")[1]

return instance_zone_dict

def get_instances_zones(self, instance_names):
"""
Get the zones of the given instance names
Args:
instance_names: The instance names to get their zones
Returns:
list: The zones of the given instance names
"""
instance_zone_dict = self.get_instance_zone_dict()
return [instance_zone_dict[instance_name] for instance_name in instance_names]

def get_operation_data(self, zone, operation_id):
"""
Get the operation data of a given operation id.
(For example after stopping an instance, get the data of the stop operation id)
Args:
zone (str): The zone of the operation id
operation_id (str): The operation id
Returns:
dict: The operation data of a given operation id
"""
request = self.compute.zoneOperations().get(
project=self.project_id, zone=zone, operation=operation_id
)
return request.execute()

def get_operations_data(self, zone_operation_id_dict):
"""
Get the operations data for the given operation ids
Args:
zone_operation_id_dict: A dictionary of the operation id zone per operation id
Returns:
list: The operations data for the given operation ids
"""
return [
self.get_operation_data(zone, operation_id)
for zone, operation_id in zone_operation_id_dict.items()
]

def wait_for_operations_to_complete(
self,
zone_operation_id_dict,
timeout=300,
sleep=10,
):
operations_data = []
logger.info("Waiting for the operations to complete...")

try:
for operations_data in TimeoutSampler(
timeout=timeout,
sleep=sleep,
func=self.get_operations_data,
zone_operation_id_dict=zone_operation_id_dict,
):
operations_statuses = [data["status"] for data in operations_data]
if all([status == "DONE" for status in operations_statuses]):
logger.info("All the operations completed successfully")
break
except TimeoutExpiredError:
failed_operations_data = [
data for data in operations_data if data["status"] == "DONE"
]
raise OperationFailedToCompleteException(
f"{len(failed_operations_data)} operations failed to complete after {timeout} seconds. "
f"Failed operations data: {failed_operations_data}"
)

def base_instances_operation(self, operation_name, instance_names, wait=True):
instance_names = [name.split(".")[0] for name in instance_names]
zone_operation_id_dict = {}
zones = self.get_instances_zones(instance_names)

for zone, instance_name in zip(zones, instance_names):
response = self.base_instance_operation(operation_name, zone, instance_name)
logger.debug(f"instance operation response = {response}")
zone_operation_id_dict[zone] = response["name"]

if wait:
logger.info(
f"Waiting for the operation '{operation_name}' to complete "
f"on the instances {instance_names}"
)
self.wait_for_operations_to_complete(zone_operation_id_dict)

def stop_instances(self, instance_names, wait=True):
logger.info(f"Stopping the instances {instance_names}")
self.base_instances_operation(OPERATION_STOP, instance_names, wait)

def start_instances(self, instance_names, wait=True):
logger.info(f"Starting the instances {instance_names}")
self.base_instances_operation(OPERATION_START, instance_names, wait)

def restart_instances(self, instance_names, wait=True):
logger.info(f"Restarting the instances {instance_names}")
self.base_instances_operation(OPERATION_RESTART, instance_names, wait)

def terminate_instances(self, instance_names, wait=True):
logger.info(f"Terminating the instances {instance_names}")
self.base_instances_operation(OPERATION_TERMINATE, instance_names, wait)

def restart_instances_by_stop_and_start(self, instance_names, wait=True):
logger.info(f"Restarting the instances {instance_names} by stop and start")
self.stop_instances(instance_names, wait=True)
# Starting the instances immediately after the 'stop' operation may not always work correctly.
# So, I added a few more seconds.
wait_time = 5
logger.info(f"Wait {wait_time} seconds before starting the instances...")
time.sleep(wait_time)
self.start_instances(instance_names, wait)
7 changes: 7 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@
"azure-identity==1.12.0",
"azure-mgmt-storage==21.0.0",
"fauxfactory==3.1.0",
"google-api-core==2.11.0",
"google-api-python-client==2.105.0",
"google-auth-httplib2==0.1.1",
"google-cloud-core==2.3.2",
"google-crc32c==1.5.0",
"google-resumable-media==2.4.1",
"googleapis-common-protos==1.59.0",
],
entry_points={
"console_scripts": [
Expand Down

0 comments on commit d2180f7

Please sign in to comment.