End Subsystems Controlled and Power Down System in Batch

Recently, I was working on a hosted IBM i environment and needed a way to bring down all of the LPARs for maintenance. I have used PWRDWNSYS *IMMED before but I wanted to come up with a clean way to do this without having to touch each system one at a time. Upon researching this, I realized that something like this would be super handy for people to execute on their own systems especially if a UPS is connected to the IBM i and it needs to cleanly power off before the UPS fails (if you don’t have a generator…but I hope you do).

NOTE: If using this code with a UPS (thanks to Robin Tatum mentioning) you will need to make sure that your UPS is connected to your Power system. Here is an example of this cable connected to a Power 8. If you would like to look into creating a power handling program, click here. You will need to make sure that your UPS is capable of being connected to the Power system and that it is physically cabled to enable this.

Enter this solution. I gathered bits and pieces from many sites in my quest across the many search results in Google. I built, tested, and refined it on a Power 7 test system that I use.
(Please note: no warranty on this code and you use at your own risk.).

What I came up with is a CLLE program that you can schedule on the Job Scheduler (WRKJOBSCDE) to execute a controlled end of all subsystems based on an amount of time, and while the subsystems are ending a way to check to see if the system is in restricted state; if it is then power down. However, if it does not reach restricted state for some reason (i.e. a job won’t end) I wanted the power down to occur anyway. You can tweak this if you have different requirements and if you wanted to power down the system controlled and remove the 60 seconds delay that would work. The only reason I did it this way was to make sure that TCP ended and that for sure the system would power down (I didn’t want to leave anything to chance).

Here are some of the links that I used to put this together. Thank you to all those who contributed bits and pieces to this by posting your thoughts, questions, and solutions online.
IBM discusses running a Full System Save or SAVSYS in Restricted State Batch <—- this first showed me the possibility of what I wanted to do
IBM Explains PWRDWNSYS
Rob Berendt offers some great thoughts and peaked my interest for sure in this question on Midrange
IBM Explains the Retrieve System Status (QWCRSSTS) API
Jean-Marie Sauvageot provides a nice example of retrieving data from the API
Simon Hutchinson explains the use of DO in CL
Joe Hertvik – Ending Subsystems Properly
Dawn May – *NOJOBLOG on ENDSBS to improve performance

IBM states in their article “Running a Full System Save or SAVSYS in Restricted State Batch” that starting at V5R3M0, it is now possible to run batch jobs to the controlling subsystem (i.e. QCTL) in a restricted state. The intent is to run scheduled restricted state saves without a console. To do this, the ENDSBS command has a new BCHTIMLMT parameter.

This BCHTIMLMT parameter is what makes all of the code below possible. Without this parameter you will get a CPF1052 “ENDSBS *ALL not allowed in current environment.”.

The Code

/*  ===============================================================  */
/*  = Program....... ENDPWRDWN2                                   =  */
/*  = Description...                                              =  */
/*  =           Alert Users                                       =  */
/*  =           End Subsystems                                    =  */
/*  =           Check for Restricted State for set count          =  */
/*  =           If Restricted State Power Off                     =  */
/*  =           Otherwise Power Off                               =  */
/*  ===============================================================  */
             PGM

/*  ===============================================================  */
/*  = Declarations                                                =  */
/*  ===============================================================  */
             DCL        VAR(&RCVVAR) TYPE(*CHAR) LEN(68)
             DCL        VAR(&RCVVARLEN) TYPE(*CHAR) LEN(4)
             DCL        VAR(&FORMAT) TYPE(*CHAR) LEN(8)
             DCL        VAR(&RESET) TYPE(*CHAR) LEN(10)
             DCL        VAR(&APIERROR) TYPE(*CHAR) LEN(272)
             DCL        VAR(&BYTESPROV) TYPE(*CHAR) LEN(4)
             DCL        VAR(&BYTESAVAIL) TYPE(*CHAR) LEN(4)
             DCL        VAR(&BIN1CHAR) TYPE(*CHAR) LEN(1)
             DCL        VAR(&SYSSTATUS) TYPE(*CHAR) LEN(1)
             DCL        VAR(&COUNT) TYPE(*INT) LEN(2)

/*  ===============================================================  */
/*  = Initialize variables                                        =  */
/*  ===============================================================  */
             CHGVAR     VAR(&FORMAT) VALUE('SSTS0200')
             CHGVAR     VAR(&RESET) VALUE('*YES')

             CHGVAR     VAR(%BIN(&RCVVARLEN)) VALUE(68)
             CHGVAR     VAR(%BIN(&BYTESPROV)) VALUE(272)
             CHGVAR     VAR(%BIN(&BYTESAVAIL)) VALUE(0)
             CHGVAR     VAR(%SST(&APIERROR 1 4)) VALUE(&BYTESPROV)
             CHGVAR     VAR(%SST(&APIERROR 5 4)) VALUE(&BYTESAVAIL)

             SNDMSG     MSG('Send Power Down Break Message') TOUSR(*SYSOPR)
             SNDBRKMSG  MSG('***ATTENTION*** The system will be coming down for +
                          maintenance shortly. Please finish your work and sign +
                          off.') TOMSGQ(*ALLWS)
                          
             /* Capture what jobs are active before the power down. */
             WRKACTJOB  OUTPUT(*PRINT) 

/*  =========================================================================  */
/*  = NOTE: Match the delay here with the (DLY)x(count+5) in the for loop   =  */
/*  =========================================================================  */
             ENDSBS     SBS(*ALL) OPTION(*CNTRLD) DELAY(300) ENDSBSOPT(*NOJOBLOG) +
                          BCHTIMLMT(*NOMAX)

/*  ===============================================================  */
/*  = Wait for CPF0968: System ended to restricted condition.     =  */
/*  ===============================================================  */
 LOOP1:      DOFOR      VAR(&COUNT) FROM(1) TO(35)
                DLYJOB     DLY(10)
                CALL       PGM(QWCRSSTS) PARM(&RCVVAR &RCVVARLEN &FORMAT &RESET +
                             &APIERROR)

/*  More Info on API Here:  */
/*  https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_71/apis/qwcrssts.htm   */

/*  ===============================================================  */
/*  = Extract system status information from API call             =  */
/*  ===============================================================  */

                CHGVAR     VAR(&BIN1CHAR) VALUE(%SST(&RCVVAR 31 1))
                CHGVAR     VAR(&SYSSTATUS) VALUE(&BIN1CHAR)

                IF         COND(&SYSSTATUS = '1') THEN(LEAVE CMDLBL(LOOP1))

             ENDDO

             SNDMSG     MSG('Delay 1 minute before PWRDWN') TOUSR(*SYSOPR)
             DLYJOB     DLY(60)

             PWRDWNSYS  OPTION(*IMMED) RESTART(*YES)

             ENDPGM 

Add The Job Schedule Entry

ADDJOBSCDE JOB(POWERDOWN) CMD(SBMJOB CMD(CALL PGM(CMALIB/ENDPWRDWN2)) JOB(ENDPWRDWN) JOBQ(QCTL)) FRQ(ONCE) SCDDATE(CURRENT) SCDTIME(230000)
NOTE: Make sure you pay close attention when you enter this command on your system. You could schedule a power down accidentally if you forget to change the time. I changed the time to 11pm to help you avoid accidentally taking your system down in the middle of the day. When I ran this test I scheduled it for 13:43.

Message displayed to Users

The Results in the System History Log