Skip to content

Commit 203e539

Browse files
committed
initial code for diagnostics
1 parent ed2d822 commit 203e539

File tree

7 files changed

+481
-18
lines changed

7 files changed

+481
-18
lines changed

({}))

Whitespace-only changes.

__tests__/api/BundlePush/BundlePusher.test.ts

Lines changed: 279 additions & 0 deletions
Large diffs are not rendered by default.

docs/pages/cdp/CLIReadme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ Push a CICS bundle from the working directory to a target CICSplex\.
321321

322322
* The name of a (cics\-deploy) profile to load for this command execution\.
323323

324+
* `--cics-profile` | `--cics-p` *(string)*
325+
326+
* The name of a (cics) profile to load for this command execution\.
327+
324328
### Examples
325329

326330
* Push a CICS bundle from the working directory by using

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"devDependencies": {
3737
"@zowe/cli": "^5.5.0",
3838
"@zowe/imperative": "^4.1.2",
39+
"@zowe/cics": "^2.0.1",
3940
"@types/fs-extra": "^5.0.5",
4041
"@types/jest": "^22.2.3",
4142
"@types/node": "^8.0.28",
@@ -67,6 +68,7 @@
6768
},
6869
"peerDependencies": {
6970
"@zowe/cli": "^5.5.0",
71+
"@zowe/cics": "^2.0.1",
7072
"@zowe/imperative": "^4.1.2"
7173
},
7274
"jest": {

src/api/BundleDeploy/BundleDeployer.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class BundleDeployer {
3333
private hlqsValidated: boolean = false;
3434
private jobId: string;
3535
private progressBar: ITaskWithStatus;
36+
private jobOutput: string = "";
3637

3738
/**
3839
* Constructor for a BundleDeployer.
@@ -124,6 +125,16 @@ export class BundleDeployer {
124125
}
125126
}
126127

128+
/**
129+
* Retrieves the output from the most recently completed DFHDPLOY JCL job.
130+
* @returns {string}
131+
* @throws ImperativeError
132+
* @memberof BundleDeployer
133+
*/
134+
public getJobOutput() {
135+
return this.jobOutput;
136+
}
137+
127138
private wrapLongLineForJCL(lineOfText: string): string {
128139
const MAX_LINE_LEN = 71;
129140

@@ -296,6 +307,7 @@ export class BundleDeployer {
296307
stageName: TaskStage.IN_PROGRESS };
297308
this.startProgressBar();
298309
this.jobId = "UNKNOWN";
310+
this.jobOutput = "";
299311

300312
// Refresh the progress bar by 1% every second or so up to a max of 67%.
301313
// SubmitJobs will initialise it to 30% and set it to 70% when it
@@ -331,6 +343,7 @@ export class BundleDeployer {
331343
const logger = Logger.getAppLogger();
332344
logger.debug(file.data);
333345
}
346+
this.jobOutput = file.data;
334347

335348
// Finish the progress bar
336349
this.progressBar.statusMessage = "Completed DFHDPLOY";

src/api/BundlePush/BundlePusher.ts

Lines changed: 182 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111

1212
"use strict";
1313

14-
import { IHandlerParameters, AbstractSession, ITaskWithStatus, TaskStage, TaskProgress, Logger, IProfile } from "@zowe/imperative";
14+
import { IHandlerParameters, AbstractSession, ITaskWithStatus, TaskStage, TaskProgress, Logger, IProfile, Session } from "@zowe/imperative";
1515
import { List, ZosmfSession, SshSession, Shell, Upload, IUploadOptions, ZosFilesAttributes, Create } from "@zowe/cli";
16+
import { getResource, IResourceParms } from "@zowe/cics";
1617
import { BundleDeployer } from "../BundleDeploy/BundleDeployer";
1718
import { Bundle } from "../BundleContent/Bundle";
1819

@@ -70,14 +71,17 @@ export class BundlePusher {
7071
}
7172

7273
// Get the profiles
73-
const zosMFProfile = this.getProfile("zosmf");
74-
const sshProfile = this.getProfile("ssh");
75-
this.validateProfiles(zosMFProfile, sshProfile);
74+
const zosMFProfile = this.getProfile("zosmf", true);
75+
const sshProfile = this.getProfile("ssh", true);
76+
const cicsProfile = this.getProfile("cics", false);
77+
this.validateProfiles(zosMFProfile, sshProfile, cicsProfile);
7678

7779
// Create a zOSMF session
7880
const zosMFSession = await this.createZosMFSession(zosMFProfile);
7981
// Create an SSH session
8082
const sshSession = await this.createSshSession(sshProfile);
83+
// If relevant, start a CICS session
84+
const cicsSession = await this.createCicsSession(cicsProfile);
8185

8286
// Start a progress bar (but only in non-verbose mode)
8387
this.progressBar = { percentComplete: 0,
@@ -125,7 +129,7 @@ export class BundlePusher {
125129
await this.runAllNpmInstalls(sshSession, packageJsonFiles);
126130

127131
// Run DFHDPLOY to install the bundle
128-
await this.deployBundle(zosMFSession, bd);
132+
await this.deployBundle(zosMFSession, bd, cicsSession);
129133

130134
// Complete the progress bar
131135
this.progressBar.percentComplete = TaskProgress.ONE_HUNDRED_PERCENT;
@@ -181,10 +185,10 @@ export class BundlePusher {
181185
}
182186
}
183187

184-
private getProfile(type: string): IProfile {
188+
private getProfile(type: string, required: boolean): IProfile {
185189
const profile = this.params.profiles.get(type);
186190

187-
if (profile === undefined) {
191+
if (required && profile === undefined) {
188192
throw new Error("No " + type + " profile found");
189193
}
190194

@@ -193,32 +197,86 @@ export class BundlePusher {
193197

194198
private issueWarning(msg: string) {
195199
const warningMsg = "WARNING: " + msg + "\n";
200+
this.issueMessage(warningMsg);
201+
}
202+
203+
private issueMessage(msg: string) {
196204
this.endProgressBar();
197-
this.params.response.console.log(Buffer.from(warningMsg));
205+
this.params.response.console.log(Buffer.from(msg));
198206
if (this.params.arguments.silent === undefined) {
199207
const logger = Logger.getAppLogger();
200-
logger.warn(warningMsg);
208+
logger.warn(msg);
201209
}
202210
this.startProgressBar();
203211
}
204212

205-
private validateProfiles(zosmfProfile: IProfile, sshProfile: IProfile) {
206-
// Do they share the same host name?
213+
private validateProfiles(zosmfProfile: IProfile, sshProfile: IProfile, cicsProfile: IProfile) {
214+
// Do the required profiles share the same host name?
207215
if (zosmfProfile.host !== sshProfile.host) {
208216
this.issueWarning("ssh profile --host value '" + sshProfile.host + "' does not match zosmf value '" + zosmfProfile.host + "'.");
209217
}
210-
// Do they share the same user name?
218+
// Do the required profiles share the same user name?
211219
if (zosmfProfile.user !== sshProfile.user) {
212220
this.issueWarning("ssh profile --user value '" + sshProfile.user + "' does not match zosmf value '" + zosmfProfile.user + "'.");
213221
}
222+
223+
// Is the optional CICS profile compatible?
224+
if (cicsProfile !== undefined) {
225+
if (zosmfProfile.host !== cicsProfile.host) {
226+
this.issueWarning("cics profile --host value '" + cicsProfile.host + "' does not match zosmf value '" + zosmfProfile.host + "'.");
227+
}
228+
if (zosmfProfile.user !== cicsProfile.user) {
229+
this.issueWarning("cics profile --user value '" + cicsProfile.user + "' does not match zosmf value '" + zosmfProfile.user + "'.");
230+
}
231+
232+
// Do the cics-plexes match?
233+
if (this.params.arguments.cicsplex !== cicsProfile.cicsPlex) {
234+
this.issueWarning("cics profile --cics-plex value '" + cicsProfile.cicsPlex +
235+
"' does not match --cicsplex value '" + this.params.arguments.cicsplex + "'.");
236+
}
237+
}
214238
}
215239

216240
private async createZosMFSession(zosmfProfile: IProfile): Promise<AbstractSession> {
217-
return ZosmfSession.createBasicZosmfSession(zosmfProfile);
241+
try {
242+
return ZosmfSession.createBasicZosmfSession(zosmfProfile);
243+
}
244+
catch (error) {
245+
throw new Error("Failure occurred creating a zosmf session: " + error.message);
246+
}
218247
}
219248

220249
private async createSshSession(sshProfile: IProfile): Promise<SshSession> {
221-
return SshSession.createBasicSshSession(sshProfile);
250+
try {
251+
return SshSession.createBasicSshSession(sshProfile);
252+
}
253+
catch (error) {
254+
throw new Error("Failure occurred creating an ssh session: " + error.message);
255+
}
256+
}
257+
258+
private async createCicsSession(cicsProfile: IProfile): Promise<AbstractSession> {
259+
if (cicsProfile === undefined) {
260+
return undefined;
261+
}
262+
263+
// At time of writing, the CicsSession object in the @zowe/cics project isn't
264+
// accessible, so the following code is copied out of CicsSession.createBasicCicsSession().
265+
try {
266+
return new Session({
267+
type: "basic",
268+
hostname: cicsProfile.host,
269+
port: cicsProfile.port,
270+
user: cicsProfile.user,
271+
password: cicsProfile.password,
272+
basePath: cicsProfile.basePath,
273+
protocol: cicsProfile.protocol || "http",
274+
rejectUnauthorized: cicsProfile.rejectUnauthorized
275+
});
276+
}
277+
catch (error) {
278+
throw new Error("Failure occurred creating a cics session: " + error.message);
279+
}
222280
}
223281

224282
private async validateBundleDirExistsAndIsEmpty(zosMFSession: AbstractSession) {
@@ -282,16 +340,40 @@ export class BundlePusher {
282340
this.startProgressBar();
283341
}
284342

285-
private async deployBundle(zosMFSession: AbstractSession, bd: BundleDeployer) {
343+
private async deployBundle(zosMFSession: AbstractSession, bd: BundleDeployer, cicsSession: AbstractSession) {
286344
// End the current progress bar so that DEPLOY can create its own
287345
this.updateStatus("Deploying bundle '" + this.params.arguments.name + "' to CICS");
288346
this.endProgressBar();
289347

290-
await bd.deployBundle(zosMFSession);
348+
let deployError: Error;
349+
let dfhdployOutput = "";
350+
try {
351+
await bd.deployBundle(zosMFSession);
352+
}
353+
catch (error) {
354+
// temporarily ignore the error as we might want to generate additional resource
355+
// specific diagnostics even if something went wrong.
356+
deployError = error;
357+
}
358+
dfhdployOutput = bd.getJobOutput();
359+
291360
// Resume the current progress bar
292361
this.endProgressBar();
293-
this.updateStatus("Deploy complete");
362+
if (deployError === undefined) {
363+
this.updateStatus("Deploy complete");
364+
}
365+
else {
366+
this.updateStatus("Deploy ended with errors");
367+
}
294368
this.startProgressBar();
369+
370+
// Generate additional output for Node.js
371+
await this.outputNodejsDiagnostics(cicsSession, dfhdployOutput);
372+
373+
// Now rethrow the original error, if there was one.
374+
if (deployError !== undefined) {
375+
throw deployError;
376+
}
295377
}
296378

297379
private sshOutput(data: string) {
@@ -527,4 +609,87 @@ export class BundlePusher {
527609
await this.runSingleNpmUninstall(sshSession, remoteDirectory);
528610
}
529611
}
612+
613+
private async outputNodejsDiagnostics(cicsSession: AbstractSession, dfhdployOutput: string) {
614+
// Did the Bundle contents include a NODEJSAPP?
615+
if (dfhdployOutput.indexOf("http://www.ibm.com/xmlns/prod/cics/bundle/NODEJSAPP") > -1) {
616+
let diagnosticsIssued = false;
617+
try {
618+
if (cicsSession !== undefined) {
619+
// Attempt to gather additional Node.js specific information from CICS
620+
this.updateStatus("Gathering Node.js diagnostics");
621+
diagnosticsIssued = await this.gatherNodejsDiagnosticsFromCics(cicsSession);
622+
}
623+
}
624+
catch (diagnosticsError) {
625+
// Something went wrong generating diagnostic info. Don't trouble the user
626+
// with what might be an exotic error message (e.g. hex dump of an entire HTML page),
627+
// just log the failure.
628+
if (this.params.arguments.silent === undefined) {
629+
const logger = Logger.getAppLogger();
630+
logger.debug(diagnosticsError.message);
631+
}
632+
}
633+
634+
// If we don't have a cics profile or if we do but the diagnostics collection failed, issue a message.
635+
if (diagnosticsIssued === false) {
636+
const msg = "For further information on the state of your NODEJSAPP resources, consider running the following command:\n\n" +
637+
"zowe cics get resource CICSNodejsapp --region-name " + this.params.arguments.scope +
638+
" --criteria \"BUNDLE=" + this.params.arguments.name + "\" --cics-plex " + this.params.arguments.cicsplex + "\n";
639+
this.issueMessage(msg);
640+
}
641+
}
642+
}
643+
644+
private async gatherNodejsDiagnosticsFromCics(cicsSession: AbstractSession): Promise<boolean> {
645+
// Issue a CMCI get to the target CICSplex
646+
try {
647+
const data: IResourceParms = { name: "CICSNodejsapp",
648+
criteria: "BUNDLE=" + this.params.arguments.name,
649+
regionName: this.params.arguments.scope,
650+
cicsPlex: this.params.arguments.cicsplex };
651+
const cmciResponse = await getResource(cicsSession, data);
652+
const outputRecord = cmciResponse.response.records.cicsnodejsapp;
653+
if (outputRecord === undefined) {
654+
throw new Error("CICSNodejsapp output record not found.");
655+
}
656+
657+
// We may have an array of records if there was more than one NODEJSAPP in the bundle
658+
if (Array.isArray(outputRecord)) {
659+
for (const record of outputRecord) {
660+
this.reportNODEJSAPPData(record);
661+
}
662+
}
663+
else {
664+
this.reportNODEJSAPPData(outputRecord);
665+
}
666+
}
667+
catch (error) {
668+
throw new Error("Failure collecting diagnostics for Bundle " + this.params.arguments.name + ": " + error.message);
669+
}
670+
671+
return true;
672+
}
673+
674+
private reportNODEJSAPPData(outputRecord: any) {
675+
const name = outputRecord.name;
676+
const enablestatus = outputRecord.enablestatus;
677+
const pid = outputRecord.pid;
678+
const region = outputRecord.eyu_cicsname;
679+
let stdout = outputRecord.stdout;
680+
let stderr = outputRecord.stderr;
681+
682+
if (stdout === "") {
683+
stdout = "<not available>";
684+
}
685+
if (stderr === "") {
686+
stderr = "<not available>";
687+
}
688+
689+
const msg = "CICS NODEJSAPP resource '" + name + "' is in '" + enablestatus + "' state in region '" +
690+
region + "' with process id '" + pid + "'.\n" +
691+
" stdout: " + stdout + "\n" +
692+
" stderr: " + stderr + "\n";
693+
this.issueMessage(msg);
694+
}
530695
}

src/cli/push/bundle/PushBundle.definition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const PushBundleDefinition: ICommandDefinition = {
3939
options: [ NameOption, TargetdirOption, CicsplexOption, ScopeOption, CsdgroupOption , ResgroupOption,
4040
CicshlqOption, CpsmhlqOption, DescriptionOption, JobcardOption, TimeoutOption, TargetStateOption,
4141
VerboseOption, OverwriteOption ],
42-
profile: { required: ["zosmf", "ssh"], optional: ["cics-deploy"] },
42+
profile: { required: ["zosmf", "ssh"], optional: ["cics-deploy", "cics"] },
4343
examples: [
4444
{
4545
description: "Push a CICS bundle from the working directory by using default cics-deploy, ssh and zosmf profiles",

0 commit comments

Comments
 (0)