Skip to content

Commit 49c41ae

Browse files
authored
Add Verrazzano application status button and dialog (#168)
1 parent e34fb6c commit 49c41ae

14 files changed

+572
-136
lines changed

electron/app/js/ipcRendererPreload.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ contextBridge.exposeInMainWorld(
213213
'openssl-generate-certs',
214214
'validate-k8s-namespaces-exist',
215215
'validate-wko-domain-exist',
216+
'validate-vz-application-exist',
217+
'vz-get-application-status',
216218
'get-wrc-home-directory',
217219
'get-wrc-app-image',
218220
'wrc-get-home-default-value',

electron/app/js/kubectlUtils.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ async function getWkoDomainStatus(kubectlExe, domainUID, domainNamespace, option
156156
});
157157
}
158158

159+
// Currently, this call returns the status of the domain component with domainUID, similar to getWkoDomainStatus.
160+
// In future releases, there may be additional information about the application and other components.
161+
async function getApplicationStatus(kubectlExe, application, domainUID, namespace, options) {
162+
const results = await getWkoDomainStatus(kubectlExe, domainUID, namespace, options);
163+
results['application'] = application;
164+
return results;
165+
}
166+
159167
async function getOperatorStatus(kubectlExe, operatorNamespace, options) {
160168
const args = [ 'get', 'pods', '-n', operatorNamespace, '-o', 'json'];
161169
const httpsProxyUrl = getHttpsProxyUrl();
@@ -312,6 +320,32 @@ async function validateDomainExist(kubectlExe, options, domain, namespace) {
312320
return Promise.resolve(result);
313321
}
314322

323+
async function validateApplicationExist(kubectlExe, options, application, namespace) {
324+
const httpsProxyUrl = getHttpsProxyUrl();
325+
const bypassProxyHosts = getBypassProxyHosts();
326+
const env = getKubectlEnvironment(options, httpsProxyUrl, bypassProxyHosts);
327+
328+
const result = {
329+
isSuccess: true,
330+
isValid: true,
331+
};
332+
333+
const args = [ 'get', 'appconfig', application, '-n', namespace ];
334+
335+
try {
336+
await executeFileCommand(kubectlExe, args, env);
337+
} catch (err) {
338+
if (isNotFoundError(err)) {
339+
result.isValid = false;
340+
} else {
341+
result.isSuccess = false;
342+
result.reason = getErrorMessage(err);
343+
return Promise.resolve(result);
344+
}
345+
}
346+
return Promise.resolve(result);
347+
}
348+
315349
async function isOperatorAlreadyInstalled(kubectlExe, operatorName, operatorNamespace, options) {
316350
// We are currently using kubectl to see if the operator deployment exists. The operator deployment
317351
// name is always weblogic-operator...
@@ -958,6 +992,7 @@ module.exports = {
958992
getK8sConfigView,
959993
getK8sClusterInfo,
960994
getWkoDomainStatus,
995+
getApplicationStatus,
961996
getOperatorStatus,
962997
getOperatorVersionFromDomainConfigMap,
963998
getOperatorLogs,
@@ -966,6 +1001,7 @@ module.exports = {
9661001
getKubernetesObjectsFromAllNamespaces,
9671002
validateNamespacesExist,
9681003
validateDomainExist,
1004+
validateApplicationExist,
9691005
verifyClusterConnectivity,
9701006
verifyVerrazzanoPlatformOperatorRollout,
9711007
};

electron/app/locales/en/electron.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@
322322
"kubectl-tlsfile-failed-error-message": "Unable to verify TLS file {{filePath}} exists: {{error}}",
323323
"kubectl-get-vz-install-failed-error-message": "Unable to get Verrazzano installation: {{error}}",
324324
"kubectl-get-named-vz-install-error-message": "Unable to get Verrazzano installation with name {{name}}: {{error}}",
325+
"kubectl-get-vz-application-status-error-message": "Unable to get Verrazzano application status: {{error}}",
325326

326327
"helm-not-specified-error-message": "Helm executable path was not provided",
327328
"helm-not-exists-error-message": "Helm executable {{filePath}} does not exist",

electron/app/locales/en/webui.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,7 @@
13011301
"flow-validate-k8s-domain-in-progress": "Validating domain {{domain}} exists",
13021302
"flow-get-domain-status-name": "Get Domain Status",
13031303
"flow-getting-k8s-domain-status-in-progress": "Getting domain {{domain}} status",
1304+
"flow-getting-vz-application-status-in-progress": "Getting Application Status",
13041305
"flow-checking-operator-version-in-progress": "Checking operator version",
13051306
"flow-checking-vz-already-installed-in-progress": "Checking if Verrazzano is already installed.",
13061307
"flow-install-verrazzano-name": "Install Verrazzano",
@@ -1310,6 +1311,10 @@
13101311
"flow-verrazzano-undeploy-component-name": "Undeploy Verrazzano Component",
13111312
"flow-verrazzano-deploy-application-name": "Deploy Verrazzano Application",
13121313
"flow-verrazzano-undeploy-application-name": "Undeploy Verrazzano Application",
1314+
"flow-verrazzano-get-application-status-name": "Get Application Status",
1315+
"flow-validate-vz-application-namespace-in-progress": "Validating application namespace {{namespace}} exists",
1316+
"flow-validate-vz-application-in-progress": "Validating application {{application}} exists",
1317+
"flow-validate-vz-domain-in-progress": "Validating domain UID {{domain}} exists",
13131318

13141319
"add-archive-file-title": "Add Archive File",
13151320
"add-archive-file-replace-message": "This will replace the existing archive, continue?",
@@ -1517,6 +1522,8 @@
15171522

15181523
"vz-application-page-button-deployApplication": "Deploy Application",
15191524
"vz-application-page-hints-deployApplication": "Deploy Application to Verrazzano",
1525+
"vz-application-page-button-applicationStatus": "Get Application Status",
1526+
"vz-application-page-hints-applicationStatus": "Check Application Deployment Status",
15201527
"vz-application-page-button-undeployApplication": "Undeploy Application",
15211528
"vz-application-page-hints-undeployApplication": "Undeploy Application from Verrazzano",
15221529

@@ -1692,6 +1699,15 @@
16921699
"vz-application-undeployer-undeploy-failed-error-message":"Unable to undeploy Verrazzano application {{name}} from Kubernetes namespace {{namespace}}: {{error}}.",
16931700
"vz-application-undeployer-undeploy-catch-all-error-message": "Verrazzano application undeployment from Kubernetes failed with an unexpected error: {{error}}",
16941701

1702+
"vz-application-status-checker-get-status-failed-title-message": "Get Application Status Failed",
1703+
"vz-application-status-checker-get-status-failed-error-message": "Unable to get the application status due to an error: {{error}}.",
1704+
"vz-application-status-checker-application-not-exist-error-message": "Unable to get the application status because the application {{application}} does not exist.",
1705+
"vz-application-status-checker-ns-not-exist-error-message": "Unable to get the application status because the application namespace {{namespace}} does not exist.",
1706+
"vz-application-status-checker-domain-not-exist-error-message": "Unable to get the application status because the domain UID {{domain}} does not exist.",
1707+
1708+
"vz-application-status-title": "Application Status for \"{{application}}\"",
1709+
"vz-application-status-domain-title": "Domain \"{{domain}}\"",
1710+
16951711
"app-update-title": "Application Update",
16961712
"app-update-new-release": "A new release is available: {{releaseName}}",
16971713
"app-update-release-notes": "Release Notes",

electron/app/main.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,14 @@ class Main {
757757
return kubectlUtils.validateDomainExist(kubectlExe, kubectlOptions, domain, namespace);
758758
});
759759

760+
ipcMain.handle('validate-vz-application-exist', async (event, kubectlExe, kubectlOptions, application, namespace) => {
761+
return kubectlUtils.validateApplicationExist(kubectlExe, kubectlOptions, application, namespace);
762+
});
763+
764+
ipcMain.handle('vz-get-application-status', async (event, kubectlExe, application, namespace, options) => {
765+
return kubectlUtils.getApplicationStatus(kubectlExe, application, namespace, options);
766+
});
767+
760768
ipcMain.handle('is-wko-installed', async (event, kubectlExe, operatorName, operatorNamespace, kubectlOptions) => {
761769
return kubectlUtils.isOperatorAlreadyInstalled(kubectlExe, operatorName, operatorNamespace, kubectlOptions);
762770
});

webui/src/css/app.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,18 @@ h6.wkt-panel-heading {
705705
color: red !important;
706706
}
707707

708+
.wkt-status-paragraph {
709+
margin: 1rem 0 0 0;
710+
}
711+
712+
.wkt-status-paragraph.domainError {
713+
color:red;
714+
}
715+
716+
.wkt-status-paragraph.preformat {
717+
white-space: pre-wrap;
718+
}
719+
708720
.wkt-dialog-content {
709721
box-sizing: border-box;
710722
display: flex;

webui/src/js/utils/k8s-domain-actions-base.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,137 @@ function (WktActionsBase, project, wktConsole, i18n, projectIo, dialogHelper) {
3838
}
3939
return Promise.resolve(true);
4040
}
41+
42+
// create a data structure with readable details about domain status.
43+
// wkoDomainStatus is the domainStatus value from the k8s-get-wko-domain-status IPC result.
44+
buildDomainStatus(wkoDomainStatus, operatorMajorVersion) {
45+
const status = wkoDomainStatus.status;
46+
let result = {isSuccess: true, domainOverallStatus: 'Unknown'};
47+
48+
if (typeof status !== 'undefined' && 'conditions' in status && status['conditions'].length > 0) {
49+
const conditions = status['conditions'];
50+
// default status
51+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-unknown');
52+
conditions.sort((a, b) => {
53+
if (a.lastTransitionTime < b.lastTransitionTime) {
54+
return 1;
55+
}
56+
if (a.lastTransitionTime > b.lastTransitionTime) {
57+
return -1;
58+
}
59+
return 0;
60+
});
61+
62+
if (operatorMajorVersion < 4) {
63+
const hasErrors = this.hasErrorConditions(conditions);
64+
const latestCondition = conditions[0];
65+
66+
if (hasErrors.error) {
67+
// There seems to be a problem in the operator where the latest condition is progressing but
68+
// there is an error previously but
69+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-failed',
70+
{reason: hasErrors.reason});
71+
} else if (latestCondition.type === 'Failed') {
72+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-failed',
73+
{reason: latestCondition.reason});
74+
} else if (latestCondition.type === 'Progressing') {
75+
// Progressing maybe the domain is coming up, maybe the introspector is running
76+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-progressing',
77+
{reason: latestCondition.reason});
78+
} else if (latestCondition.type === 'Available') {
79+
result['domainOverallStatus'] = 'Progressing';
80+
if (status['clusters'].length > 0) {
81+
const clusters = status['clusters'];
82+
let ready = true;
83+
clusters.forEach((cluster) => {
84+
if (Number(cluster['replicasGoal']) !== Number(cluster['readyReplicas'])) {
85+
ready = false;
86+
}
87+
});
88+
89+
if (ready) {
90+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-complete');
91+
} else {
92+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-available',
93+
{reason: latestCondition.reason});
94+
}
95+
} else {
96+
// remain in progressing
97+
}
98+
}
99+
} else {
100+
///
101+
const hasErrors = this.hasErrorConditions(conditions);
102+
const completeCondition = this.getCompletedCondition(conditions);
103+
const availableCondition = this.getAvailableCondition(conditions);
104+
const latestCondition = conditions[0];
105+
106+
if (hasErrors.error) {
107+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-failed',
108+
{reason: hasErrors.reason});
109+
} else if (completeCondition.status === 'True' && availableCondition.status === 'True') {
110+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-complete');
111+
} else {
112+
// Assume this is introspection progressing
113+
114+
if (completeCondition.status === 'False' && !this.hasAvailableCondition(conditions)) {
115+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-progressing',
116+
{reason: latestCondition.reason});
117+
} else if (completeCondition.status === 'False' && availableCondition.status === 'False') {
118+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-available',
119+
{reason: latestCondition.reason});
120+
} else {
121+
// should never happened?
122+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-unknown',
123+
{reason: latestCondition.reason});
124+
}
125+
}
126+
}
127+
128+
} else {
129+
// status not defined or no conditions - error in operator or namespace is not monitored
130+
result['domainOverallStatus'] = i18n.t('k8s-domain-status-checker-domain-status-unknown');
131+
}
132+
return result;
133+
}
134+
135+
hasErrorConditions(conditions) {
136+
for (const condition of conditions) {
137+
if (condition.type === 'Failed') {
138+
return {error: true, reason: condition.reason};
139+
}
140+
}
141+
return {error: false, reason: ''};
142+
}
143+
144+
getCompletedCondition(conditions) {
145+
const defaultCondition = {type: 'Completed', status: 'False'};
146+
for (const condition of conditions) {
147+
if (condition.type === 'Completed') {
148+
return condition;
149+
}
150+
}
151+
return defaultCondition;
152+
}
153+
154+
getAvailableCondition(conditions) {
155+
const defaultCondition = {type: 'Available', status: 'False'};
156+
for (const condition of conditions) {
157+
if (condition.type === 'Available') {
158+
return condition;
159+
}
160+
}
161+
return defaultCondition;
162+
}
163+
164+
hasAvailableCondition(conditions) {
165+
for (const condition of conditions) {
166+
if (condition.type === 'Available') {
167+
return true;
168+
}
169+
}
170+
return false;
171+
}
41172
}
42173

43174
return K8sDomainActionsBase;

0 commit comments

Comments
 (0)