Skip to content
24 changes: 24 additions & 0 deletions src/mas/devops/ocp.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,30 @@ def getStorageClasses(dynClient: DynamicClient) -> list:
return storageClasses


def getStorageClassVolumeBindingMode(dynClient: DynamicClient, storageClassName: str) -> str:
"""
Get the volumeBindingMode for a storage class.

Args:
dynClient: OpenShift dynamic client
storageClassName: Name of the storage class

Returns:
str: "Immediate" or "WaitForFirstConsumer" (defaults to "Immediate" if not found)
"""
try:
storageClass = getStorageClass(dynClient, storageClassName)
if storageClass and hasattr(storageClass, 'volumeBindingMode'):
return storageClass.volumeBindingMode
# Default to Immediate if not specified (Kubernetes default)
logger.debug(f"Storage class {storageClassName} does not have volumeBindingMode set, defaulting to 'Immediate'")
return "Immediate"
except Exception as e:
logger.warning(f"Unable to determine volumeBindingMode for storage class {storageClassName}: {e}")
# Default to Immediate to maintain backward compatibility
return "Immediate"


def isSNO(dynClient: DynamicClient) -> bool:
return len(getNodes(dynClient)) == 1

Expand Down
42 changes: 26 additions & 16 deletions src/mas/devops/tekton.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from jinja2 import Environment, FileSystemLoader

from .ocp import getConsoleURL, waitForCRD, waitForDeployment, crdExists, waitForPVC
from .ocp import getConsoleURL, waitForCRD, waitForDeployment, crdExists, waitForPVC, getStorageClassVolumeBindingMode

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -175,7 +175,7 @@ def updateTektonDefinitions(namespace: str, yamlFile: str) -> None:
logger.debug(line)


def preparePipelinesNamespace(dynClient: DynamicClient, instanceId: str = None, storageClass: str = None, accessMode: str = None, waitForBind: bool = True, configureRBAC: bool = True):
def preparePipelinesNamespace(dynClient: DynamicClient, instanceId: str = None, storageClass: str = None, accessMode: str = None, configureRBAC: bool = True):
templateDir = path.join(path.abspath(path.dirname(__file__)), "templates")
env = Environment(
loader=FileSystemLoader(searchpath=templateDir)
Expand Down Expand Up @@ -207,21 +207,25 @@ def preparePipelinesNamespace(dynClient: DynamicClient, instanceId: str = None,
pvc = yaml.safe_load(renderedTemplate)
pvcAPI = dynClient.resources.get(api_version="v1", kind="PersistentVolumeClaim")
pvcAPI.apply(body=pvc, namespace=namespace)

if instanceId is not None and waitForBind:
logger.debug("Waiting for PVC to be bound")
pvcIsBound = False
while not pvcIsBound:
configPVC = pvcAPI.get(name="config-pvc", namespace=namespace)
if configPVC.status.phase == "Bound":
pvcIsBound = True
else:
logger.debug("Waiting 15s before checking status of PVC again")
logger.debug(configPVC)
sleep(15)
# Automatically determine if we should wait for PVC binding based on storage class
volumeBindingMode = getStorageClassVolumeBindingMode(dynClient, storageClass)
waitForBind = (volumeBindingMode == "Immediate")
if waitForBind:
logger.info(f"Storage class {storageClass} uses volumeBindingMode={volumeBindingMode}, waiting for PVC to bind")
pvcIsBound = False
while not pvcIsBound:
configPVC = pvcAPI.get(name="config-pvc", namespace=namespace)
if configPVC.status.phase == "Bound":
pvcIsBound = True
else:
logger.debug("Waiting 15s before checking status of PVC again")
logger.debug(configPVC)
sleep(15)
else:
logger.info(f"Storage class {storageClass} uses volumeBindingMode={volumeBindingMode}, skipping PVC bind wait")


def prepareAiServicePipelinesNamespace(dynClient: DynamicClient, instanceId: str = None, storageClass: str = None, accessMode: str = None, waitForBind: bool = True, configureRBAC: bool = True):
def prepareAiServicePipelinesNamespace(dynClient: DynamicClient, instanceId: str = None, storageClass: str = None, accessMode: str = None, configureRBAC: bool = True):
templateDir = path.join(path.abspath(path.dirname(__file__)), "templates")
env = Environment(
loader=FileSystemLoader(searchpath=templateDir)
Expand All @@ -248,8 +252,12 @@ def prepareAiServicePipelinesNamespace(dynClient: DynamicClient, instanceId: str
pvcAPI = dynClient.resources.get(api_version="v1", kind="PersistentVolumeClaim")
pvcAPI.apply(body=pvc, namespace=namespace)

# Automatically determine if we should wait for PVC binding based on storage class
volumeBindingMode = getStorageClassVolumeBindingMode(dynClient, storageClass)
waitForBind = (volumeBindingMode == "Immediate")

if waitForBind:
logger.debug("Waiting for PVC to be bound")
logger.info(f"Storage class {storageClass} uses volumeBindingMode={volumeBindingMode}, waiting for PVC to bind")
pvcIsBound = False
while not pvcIsBound:
configPVC = pvcAPI.get(name="config-pvc", namespace=namespace)
Expand All @@ -259,6 +267,8 @@ def prepareAiServicePipelinesNamespace(dynClient: DynamicClient, instanceId: str
logger.debug("Waiting 15s before checking status of PVC again")
logger.debug(configPVC)
sleep(15)
else:
logger.info(f"Storage class {storageClass} uses volumeBindingMode={volumeBindingMode}, skipping PVC bind wait")


def prepareInstallSecrets(dynClient: DynamicClient, namespace: str, slsLicenseFile: str = None, additionalConfigs: dict = None, certs: str = None, podTemplates: str = None) -> None:
Expand Down