99# *****************************************************************************
1010
1111import logging
12+ import json
13+ from time import sleep
1214from openshift .dynamic import DynamicClient
1315from openshift .dynamic .exceptions import NotFoundError , ResourceNotFoundError , UnauthorizedError
1416
1517from ..olm import getSubscription
1618
1719logger = logging .getLogger (__name__ )
1820
21+ # IoT has a different api version
22+ APP_API_VERSIONS = dict (iot = "iot.ibm.com/v1" )
1923
20- def verifyAppInstance (dynClient : DynamicClient , instanceId : str , applicationId : str ) -> bool :
24+ APP_IDS = [
25+ "assist" ,
26+ "facilities" ,
27+ "iot" ,
28+ "manage" ,
29+ "monitor" ,
30+ "optimizer" ,
31+ "predict" ,
32+ "visualinspection"
33+ ]
34+ APP_KINDS = dict (
35+ predict = "PredictApp" ,
36+ monitor = "MonitorApp" ,
37+ iot = "IoT" ,
38+ visualinspection = "VisualInspectionApp" ,
39+ assist = "AssistApp" ,
40+ manage = "ManageApp" ,
41+ optimizer = "OptimizerApp" ,
42+ facilities = "FacilitiesApp" ,
43+ )
44+ APPWS_KINDS = dict (
45+ predict = "PredictWorkspace" ,
46+ monitor = "MonitorWorkspace" ,
47+ iot = "IoTWorkspace" ,
48+ visualinspection = "VisualInspectionWorkspace" ,
49+ assist = "AssistWorkspace" ,
50+ manage = "ManageWorkspace" ,
51+ optimizer = "OptimizerWorkspace" ,
52+ facilities = "FacilitiesWorkspace" ,
53+ )
54+
55+
56+ def getAppResource (dynClient : DynamicClient , instanceId : str , applicationId : str , workspaceId : str = None ) -> bool :
2157 """
22- Validate that the chosen app instance exists
58+ Get the application or workspace Custom Resource
59+
60+ :param dynClient: Description
61+ :type dynClient: DynamicClient
62+ :param instanceId: Description
63+ :type instanceId: str
64+ :param applicationId: Description
65+ :type applicationId: str
66+ :return: Description
67+ :rtype: bool
68+ :type workspaceId: str
69+ :return: Description
70+ :rtype: bool
2371 """
72+
73+ apiVersion = APP_API_VERSIONS [applicationId ] if applicationId in APP_API_VERSIONS else "apps.mas.ibm.com/v1"
74+ kind = APP_KINDS [applicationId ] if workspaceId is None else APPWS_KINDS [applicationId ]
75+ name = instanceId if workspaceId is None else f"{ instanceId } -{ workspaceId } "
76+ namespace = f"mas-{ instanceId } -{ applicationId } "
77+
78+ # logger.debug(f"Getting {kind}.{apiVersion} {name} from {namespace}")
79+
2480 try :
25- # IoT has a different api version
26- operatorApiVersions = dict (iot = "iot.ibm.com/v1" )
27- apiVersion = operatorApiVersions [applicationId ] if applicationId in operatorApiVersions else "apps.mas.ibm.com/v1"
28- operatorKinds = dict (
29- health = "HealthApp" ,
30- predict = "PredictApp" ,
31- monitor = "MonitorApp" ,
32- iot = "IoT" ,
33- visualinspection = "VisualInspectionApp" ,
34- assist = "AssistApp" ,
35- manage = "ManageApp" ,
36- optimizer = "OptimizerApp" ,
37- facilities = "FacilitiesApp" ,
38- )
39- appAPI = dynClient .resources .get (api_version = apiVersion , kind = operatorKinds [applicationId ])
40- appAPI .get (name = instanceId , namespace = f"mas-{ instanceId } -{ applicationId } " )
41- return True
81+ appAPI = dynClient .resources .get (api_version = apiVersion , kind = kind )
82+ resource = appAPI .get (name = name , namespace = namespace )
83+ return resource
4284 except NotFoundError :
43- return False
85+ return None
4486 except ResourceNotFoundError :
45- # The MAS App CRD has not even been installed in the cluster
46- return False
47- except UnauthorizedError :
48- logger .error ("Error: Unable to verify MAS app instance due to failed authorization: {e}" )
49- return False
87+ # The CRD has not even been installed in the cluster
88+ return None
89+ except UnauthorizedError as e :
90+ logger .error (f"Error: Unable to lookup { kind } .{ apiVersion } due to authorization failure: { e } " )
91+ return None
92+
93+
94+ def verifyAppInstance (dynClient : DynamicClient , instanceId : str , applicationId : str ) -> bool :
95+ """
96+ Validate that the chosen app instance exists
97+ """
98+ return getAppResource (dynClient , instanceId , applicationId ) is not None
99+
100+
101+ def waitForAppReady (dynClient : DynamicClient , instanceId : str , applicationId : str , workspaceId : str = None , retries : int = 100 , delay : int = 600 ) -> bool :
102+ """
103+ Docstring for waitForAppReady
104+
105+ :param dynClient: Description
106+ :type dynClient: DynamicClient
107+ :param instanceId: Description
108+ :type instanceId: str
109+ :param applicationId: Description
110+ :type applicationId: str
111+ :param workspaceId: Description
112+ :type workspaceId: str
113+ :param retries: Description
114+ :type retries: int
115+ :param delay: Description
116+ :type delay: int
117+ :return: Description
118+ :rtype: bool
119+ """
120+ resourceName = f"{ APP_KINDS [applicationId ]} /{ instanceId } "
121+ if workspaceId is not None :
122+ resourceName = f"{ APPWS_KINDS [applicationId ]} /{ instanceId } -{ workspaceId } "
123+
124+ appCR = None
125+ appStatus = None
126+
127+ attempt = 0
128+ logger .info (f"Polling for { resourceName } to report ready state" )
129+
130+ while attempt < retries :
131+ attempt += 1
132+ appCR = getAppResource (dynClient , instanceId , applicationId , workspaceId )
133+
134+ if appCR is None :
135+ logger .info (f"[{ attempt } /{ retries } ] { resourceName } does not exist" )
136+ else :
137+ appStatus = appCR .status
138+ if appStatus is None :
139+ logger .info (f"[{ attempt } /{ retries } ] { resourceName } has no status" )
140+ else :
141+ if appStatus .conditions is None :
142+ logger .info (f"[{ attempt } /{ retries } ] { resourceName } has no status conditions" )
143+ else :
144+ foundReadyCondition : bool = False
145+ for condition in appStatus .conditions :
146+ if condition .type == "Ready" :
147+ foundReadyCondition = True
148+ if condition .status == "True" :
149+ logger .info (f"[{ attempt } /{ retries } ] { resourceName } is in ready state: { condition .message } " )
150+ logger .debug (f"CR status={ json .dumps (appStatus .to_dict ())} " )
151+ return True
152+ else :
153+ logger .info (f"[{ attempt } /{ retries } ] { resourceName } is not in ready state: { condition .message } " )
154+ continue
155+ if not foundReadyCondition :
156+ logger .info (f"[{ attempt } /{ retries } ] { resourceName } has no ready status condition" )
157+ sleep (delay )
158+
159+ # If we made it this far it means that the application was not ready in time
160+ logger .warning (f"Retry limit reached polling for { resourceName } to report ready state" )
161+ if appStatus is None :
162+ logger .debug ("No CR status available" )
163+ else :
164+ logger .debug (f"CR status={ json .dumps (appStatus .to_dict ())} " )
165+ return False
50166
51167
52168def getAppsSubscriptionChannel (dynClient : DynamicClient , instanceId : str ) -> list :
@@ -55,25 +171,10 @@ def getAppsSubscriptionChannel(dynClient: DynamicClient, instanceId: str) -> lis
55171 """
56172 try :
57173 installedApps = []
58- appKinds = [
59- "assist" ,
60- "facilities" ,
61- "health" ,
62- "hputilities" ,
63- "iot" ,
64- "manage" ,
65- "monitor" ,
66- "mso" ,
67- "optimizer" ,
68- "safety" ,
69- "predict" ,
70- "visualinspection" ,
71- "aibroker"
72- ]
73- for appKind in appKinds :
74- appSubscription = getSubscription (dynClient , f"mas-{ instanceId } -{ appKind } " , f"ibm-mas-{ appKind } " )
174+ for appId in APP_IDS :
175+ appSubscription = getSubscription (dynClient , f"mas-{ instanceId } -{ appId } " , f"ibm-mas-{ appId } " )
75176 if appSubscription is not None :
76- installedApps .append ({"appId" : appKind , "channel" : appSubscription .spec .channel })
177+ installedApps .append ({"appId" : appId , "channel" : appSubscription .spec .channel })
77178 return installedApps
78179 except NotFoundError :
79180 return []
0 commit comments