Are you tired of doing repetitive LPAR control updates from the HMC/SEs? If your sysplex is like ours, having 15 or more LPARs on 3 different CPCs on a PLEX, you would be as excited as me hearing about how we use APIs in zPET to dynamically update LPAR control values without logging on to the HMC/SEs.
Today I would like to share with you my experiences using the simple programming language, REXX to invoke a few APIs to update the Workload Manager Enabled Capping values for 15 LPARs on a PLEX all at once. In zPET, we perform test runs very frequently for different types of tests. In order to perform our test runs in a more consistent environment and have the result data not to be impacted by processing related weight changes, we like to run tests with the WLM disabled in most cases. Having a job to dynamically disable the WLM for a test run and later to restore the old value back is extremely convenient and efficient. The REXX EXEC is made flexible to be executed either in TSO foreground or in background (Batch) via a JCL which may be more preferrable for automation or repetitive tasks. More good news is that these APIs are free for all z/OS users. They are provided by the z/OS Base Control Program Internal Interface (BCPii) component which comes with z/OS. Its address space comes up at IPL as long as you have the proper installation. For details, please see the “BCPii setup and installation” from https://www.ibm.com/docs/en/zos/2.1.0?topic=services-base-control-program-internal-interface-bcpii or the "BCPii setup and installation" in the BCPii chapter from the MVS Programming: Callable Services for High-Level Languages publication.
If you have not been convinced yet, check out the following few simple steps! Even if you are not interested in the WLM change, the infrastructure is there, you can easily implement to make other LPAR control changes. Here is how the EXEC works:
- Obtain the CPC name(s) and their associated LPAR names which are the target systems for the LPAR control change. In our environment, we have 3 CPCs, each CPC has 6 to 8 images across 2 system PLEXes. We have 3 configuration files which define our current system configuration by using stem variables in REXX. The followings are some sample stem variable settings for one of the configuration files.
CPCNAME = IBM390PS.CPC1
IMGCOUNT = n
IMG.1 = IMGA
IMG.2 = IMGB
IMG.n = IMGn
- Using the target system names, the REXX EXEC will invoke the HWICONN API to connect to each CPC and its associated LPARs. Each connection will generate an output connection token.
- For each target LPAR:
- Using its connection token, invoke HWIQUERY API to retrieve the current WLM value
- If the current WLM value is not the set value, invoke HWISET2 API to set the desired value
- Besides checking the return code, for sanity check, invoke HWIQUERY API again to retrieve the newly set WLM value to validate the new value has been set
A few highlights:
- A test programming mode is used as an input parameter to only retrieve the current WLM value without performing the changes. It’s useful to learn the current configuration before the actual changes.
- Instead of making the WLM change dynamically to LPARs, you can also make changes in the LPAR activation profiles by specifying the target profile type and the set value will be saved in the profiles and be effective in the next activation.
- WLM change request is by each target, you can enable the WLM in one LPAR and disable it for others.
- The complete application including the REXX exec “CHGWLM.rexx” is available in GitHub repository, https://github.com/lowenho/Dynamically-Enable-or-Disable-WLM.
Sample output:
WLM Change Report 18 Mar 2022 08:57:08
******************
SYSID TYPE WLM Time Stage
----- -------- --- ---------- --------
IMGA LPAR 0 08:57:08 Orig
IMGA LPAR 1 08:57:26 Changed
IMGB LPAR 0 08:57:31 Orig
IMGB LPAR 1 08:57:49 Changed
IMGC LPAR 0 08:57:53 Orig
IMGC LPAR 1 08:58:09 Changed
IMGD PROFILE 0 08:58:12 Orig
IMGD PROFILE 1 08:58:26 Changed
CHGWLM: Change WLM request completed! 18 Mar 2022 09:00:05
Pseudo code to read in our 3 configuration files. One per CPC:
-
- Sample Content in the configuration file for CPC1
CPCNAME = IBM390PS.CPC1
IMGCOUNT = 5
IMG.1 = IMGA
IMG.2 = IMGB
IMG.3 = IMGC
IMG.4 = IMGD
IMG.5 = IMGE
/*-----------------------------------------------------------------*/
/* Procedure: ReadinputDD */
/* - Using EXECIO to read the system configuration file for */
/* the desired CPC(s), */
/* - Using the CPC.0 count to determine how many CPC(s) is */
/* provided for processing */
/* */
/* Output: where i represents the n-th config. file */
/* 1. InFile.i. - stem for the input config. file */
/* 2. InFile.i.0 - total # of lines for config file */
/*-----------------------------------------------------------------*/
ReadinputDD:
Do i = 1 to CPC.0
If Batch = 0 Then
"ALLOC F(ConfgDD) DA('"ConfigFile.i"') LRECL(137) OLD"
Select
When i = 1 Then
Do
If Batch = 0 Then
"EXECIO * DISKR ConfgDD (FINIS STEM InCFile1."
Else
"EXECIO * DISKR ConfgDD1 (FINIS STEM InCFile1."
Do j = 1 to InCFile1.0
InFile.i.j = InCFile1.j
End
InFile.i.0 = InCFile1.0
End
When i = 2 Then
Do
If Batch = 0 Then
"EXECIO * DISKR ConfgDD (FINIS STEM InCFile2."
Else
"EXECIO * DISKR ConfgDD2 (FINIS STEM InCFile2."
Do j = 1 to InCFile2.0
InFile.i.j = InCFile2.j
End
InFile.i.0 = InCFile2.0
End
When i = 3 Then
Do
If Batch = 0 Then
"EXECIO * DISKR ConfgDD (FINIS STEM InCFile3."
Else
"EXECIO * DISKR ConfgDD3 (FINIS STEM InCFile3."
Do j = 1 to InCFile3.0
InFile.i.j = InCFile3.j
End
InFile.i.0 = InCFile3.0
End
Otherwise
Do
say
say '*** Internal Error. Check CPC_Count variable ** '
say
Return 8
End
End /* End Select */
If Batch = 0 Then
"FREE F(ConfgDD)"
End /* End Do */
Return /* End ReadinputDD */
Mainline Pseudo code:
/* Copy the BCPii constants to this exec
*/
Call GetBCPiiConstants
/* Process one request(target) at a time, using the previously set
Parameters which are in stem variables of InTarget_Name and
InWLMValue.
*/
Do rqi = 1 to rqi.0 /* request index: For each target LPAR */
CPCConnectToken = 0
TargetConnectToken = 0
/* Call BCPii API to connect to CPCs and Targets
*/
Call ConnectToTarget InTarget_Name.rqi,InTarget_Type.rqi
TargetToken.rqi = TargetConnectToken
CPCToken.rqi = CPCConnectToken
If Global_RC = 0 Then
Do
EachSetRequstRC.rqi = 0 /* each set request has its own RC */
fp_GetOrig = 1 /* Setting footprint */
/* Call to obtain the original weight values
*/
Call Get_WLM TargetToken.rqi,InTarget_TypeText.rqi
OrigTargetName.rqi = RtnTargetName
OrigWLMValue.rqi = RtnWLMValue
fp_GetOrig = 0 /* Reset footprint */
/* If not running Test mode, proceed to perform SET request
*/
If PgmTestMode = 0 Then
Do
If InWLMValue.rqi = OrigWLMValue.rqi Then
Do
lineIndex = lineIndex + 1
outLine.lineIndex = ,
LEFT(OrigTargetName.rqi,5),
RIGHT(InTarget_TypeText.rqi,8),
RIGHT(OrigWLMValue.rqi,3),
RIGHT(Time(),10),
RIGHT('NoChange',8)
End
Else
Do
Call SetWLMValue ,
CPCToken.rqi,TargetToken.rqi,InWLMValue.rqi,OrigWLMValue.rqi,
,OrigTargetName.rqi
/* If the SET request succeeds, call to obtain the current
weight values and verify they were changed to the set
values
*/
If Global_RC = 0 & SET_RequestRC = 0 Then
Do
fp_Changed = 1
Call Get_WLM TargetToken.rqi,InTarget_TypeText.rqi
/* Check to make sure they were changed to the SET values
*/
If RtnWLMValue = InWLMValue.rqi Then
Do
say 'CHGWLM: WLM value has been set '||,
'successfully for '|| OrigTargetName.rqi
End
Else
Do
/* The returned WLM values does not match to the value
to be set, report the error
*/
say 'CHGWLM: ERROR: SET request failed for '|| ,
OrigTargetName.rqi
say 'CHGWLM: WLM value Expected: '||InWLMValue.rqi||,
' Actual: '||RtnWLMValue
say
Global_RC = 8
End
End
Else
Do
If Global_RC ^= 0 Then
say 'CHGWLM: Program RC = ' Global_RC
/* Record the failing RC for the current Target and then
set the failing Target name for error reporting later
*/
If SET_RequestRC ^= 0 Then
Do
EachSetRequstRC.rqi = SET_RequestRC
fi = fi + 1
FailedTarget.fi = OrigTargetName.rqi
FailedRC.fi = EachSetRequstRC.rqi
say 'CHGWLM: Set request RC = ' EachSetRequstRC.rqi||,
'('||d2x(EachSetRequstRC.rqi)||'x)'
End
End
End /* End Else if PgmTestMode = 0 */
End /* End if PgmTestMode = 0 */
End /* End if Global_RC = 0 */
If CPCConnectRC = 0 Then
Call Disconnect CPCConnectToken
End /* End do */
Exit Global_RC
Pseudo code to connect to the target CPC and its associated LPARs:
/*------------------------------------------------------------*/
/* */
/* Procedure: ConnectToTarget */
/* Using the input LPAR name to call Get_CPCName to find */
/* the associated CPC name which is required for BCPii APIs */
/* */
/* Input: InTarget : LPAR name */
/* Output: CPCConnectToken : CPC Token for BCPii API */
/* TargetConnectToken : TARGET Token for BCPii API */
/* */
/*------------------------------------------------------------*/
ConnectToTarget:
InTarget = arg(1)
InTargetType = arg(2)
/* Call to obtain the CPC name for the specified LPAR
*/
Call Get_CPCName InTarget
CPCName = RESULT
If Global_RC = 0 & CPCName ^= '' Then
Do
/* Connect to the CPC to obtain the CPC Connection which is required
to connect to the LPAR
*/
Call Connect InConnectToken,HWI_CPC,LEFT(CPCName,17)
If Global_RC = 0 Then
Do
CPCConnectRC = 0
CPCConnectToken = OutConnectToken
/* Call to connect to the input LPAR using the returned CPC token
*/
If InTargetType = 1 Then
Call Connect CPCConnectToken,HWI_IMAGE,LEFT(InTarget,17)
Else
Call Connect CPCConnectToken,HWI_IMAGE_ACTPROF,LEFT(InTarget,17)
If Global_RC = 0 Then
TargetConnectToken = OutConnectToken
End
End
Else
Do
If Global_RC ^= 0 Then
CPCConnectRC = Global_RC
If CPCName = '' Then
Do
Say 'CHGWLM: Program aborted! Unable to obtain the CPC name '||,
'for '||InTarget
Say ' Enter a valid LPAR name or check the configuration files!'
End
End
Return /* ConnectToTargets */
Pseudo code to retrieve the current WLM:
/*-----------------------------------------------------------------*/
/* Procedure: Get_WLM */
/* Call BCPii Query API to retrieve the WLM value for the input */
/* target */
/*-----------------------------------------------------------------*/
Get_WLM:
InConnectToken = arg(1)
InTargetTypeText = arg(2)
/* Set up the query parm for the HWIQuery API call
*/
QueryParm.0 = 2
QueryParm.1.ATTRIBUTEIDENTIFIER = HWI_NAME
QueryParm.2.ATTRIBUTEIDENTIFIER = HWI_WLM /* Initial weight */
Call Query InConnectToken
If Global_RC = 0 Then
Do
RtnTargetName = QueryParm.1.ATTRIBUTEVALUE
RtnWLMValue = QueryParm.2.ATTRIBUTEVALUE
End
If RtnWLMValue = 0 Then
RtnWLMValue = 0
Else
RtnWLMValue = STRIP(RtnWLMValue,'L',0)
Select
When fp_GetOrig = 1 Then OutText = 'Orig'
When fp_Changed = 1 Then OutText = 'Changed'
Otherwise OutText = ' '
End
lineIndex = lineIndex + 1
outLine.lineIndex = ,
LEFT(RtnTargetName,5),
RIGHT(InTargetTypeText,8),
RIGHT(RtnWLMValue,3),
RIGHT(Time(),10),
RIGHT(OutText,8)
Return /* Get_WLM */
Pseudo code to set the desired WLM value:
/*-----------------------------------------------------------------*/
/* Procedure: SetWLMValue */
/* Call BCPii SET2 API to change the WLM value */
/* for the input Target represented by the InTargetToken */
/*---------------------------------------------------------------- */
SetWLMValue:
InCPCToken = arg(1)
InTargetToken = arg(2)
InWLMValue = arg(3)
InOrigWLM = arg(4)
InName = arg(5)
SET_RequestRC = 0
SetParm.0 = 1
SetParm.1.SET2_CTOKEN = InTargetToken
SetParm.1.SET2_SETTYPE = HWI_WLM
SetParm.1.SET2_SETVALUE = InWLMValue
Call Set2 InCPCToken
If Global_RC = HWI_OK Then
Call WaitTime 10
Else
Do
SET_RequestRC = Global_RC
say
say 'CHGWLM: ERROR: SET request failed for '||InName
End
Return /* End SetWLMValue */
Pseudo code for some BCPii API helper procedures:
/*-------------------------------------------------------------*/
/* BCPii HWICONN request */
/*-------------------------------------------------------------*/
Connect:
InConnectToken = arg(1)
ConnectType = arg(2)
ConnectTypeValue = arg(3)
address bcpii "hwiconn ",
"ReturnCode ",
"InConnectToken ",
"OutConnectToken ",
"ConnectType ",
"ConnectTypeValue ",
"DiagArea."
REXXHostRc = RC
If REXXHostRc <> 0 | ReturnCode <> 0 Then
Do
say 'REXX RC (decimal) = ' RC
say 'HWICONN rc (hex) = ' d2x(ReturnCode)
say 'DiagArea.Diag_Index = ' DiagArea.Diag_Index
say 'DiagArea.Diag_Key = ' DiagArea.Diag_Key
say 'DiagArea.Diag_Actual = ' DiagArea.Diag_Actual
say 'DiagArea.Diag_Expected = ' DiagArea.Diag_Expected
say 'DiagArea.Diag_CommErr = ' DiagArea.Diag_CommErr
say 'DiagArea.Diag_Text = ' DiagArea.Diag_Text
say ' '
say 'Error connecting to ' ConnectTypeValue
say ' '
End
If REXXHostRc <> 0 Then
Global_RC = REXXHostRc
Else
Global_RC = ReturnCode
If Global_RC <> 0 Then
signal finish
Return Global_RC
/*-------------------------------------------------------------*/
/* BCPii HWIDISC request */
/*-------------------------------------------------------------*/
Disconnect:
InConnectToken = arg(1)
address bcpii "hwidisc ",
"ReturnCode ",
"InConnectToken ",
"DiagArea."
REXXHostRc = RC
If REXXHostRc <> 0 | ReturnCode <> 0 Then
Do
say 'REXX RC (decimal) = ' RC
say 'HWIDISC rc (hex) = ' d2x(ReturnCode)
say 'DiagArea.Diag_Index = ' DiagArea.Diag_Index
say 'DiagArea.Diag_Key = ' DiagArea.Diag_Key
say 'DiagArea.Diag_Actual = ' DiagArea.Diag_Actual
say 'DiagArea.Diag_Expected = ' DiagArea.Diag_Expected
say 'DiagArea.Diag_CommErr = ' DiagArea.Diag_CommErr
say 'DiagArea.Diag_Text = ' DiagArea.Diag_Text
End
/* No need to worry about disconnect failure as BCPii performs
implicit disconnect when the task ends
*/
If REXXHostRc <> 0 Then
DISC_RC = REXXHostRc
Else
DISC_RC = ReturnCode
Return DISC_RC
/*-------------------------------------------------------------*/
/* BCPii HWIQUERY request */
/*-------------------------------------------------------------*/
Query:
InConnectToken = arg(1)
address bcpii "hwiquery ",
"ReturnCode ",
"InConnectToken ",
"QueryParm. ",
"DiagArea."
REXXHostRc = RC
If REXXHostRc <> 0 | ReturnCode <> 0 Then
Do
say 'REXX RC (decimal) = ' RC
say 'HWIQUERY rc (hex) = ' d2x(ReturnCode)
say 'DiagArea.Diag_Index = ' DiagArea.Diag_Index
say 'DiagArea.Diag_Key = ' DiagArea.Diag_Key
say 'DiagArea.Diag_Actual = ' DiagArea.Diag_Actual
say 'DiagArea.Diag_Expected = ' DiagArea.Diag_Expected
say 'DiagArea.Diag_CommErr = ' DiagArea.Diag_CommErr
say 'DiagArea.Diag_Text = ' DiagArea.Diag_Text
End
If REXXHostRc <> 0 Then
Global_RC = REXXHostRc
Else
Global_RC = ReturnCode
Return Global_RC
/*-------------------------------------------------------------*/
/* BCPii HWISET2 request */
/*-------------------------------------------------------------*/
Set2:
InConnectToken = arg(1)
address bcpii "hwiset2 ",
"ReturnCode ",
"InConnectToken ",
"SetParm. ",
"DiagArea."
REXXHostRc = RC
If REXXHostRc <> 0 | ReturnCode <> 0 Then
Do
say 'REXX RC (decimal) = ' RC
say 'HWISET2 rc (hex) = ' d2x(ReturnCode)
say 'DiagArea.Diag_Index = ' DiagArea.Diag_Index
say 'DiagArea.Diag_Key = ' DiagArea.Diag_Key
say 'DiagArea.Diag_Actual = ' DiagArea.Diag_Actual
say 'DiagArea.Diag_Expected = ' DiagArea.Diag_Expected
say 'DiagArea.Diag_CommErr = ' DiagArea.Diag_CommErr
say 'DiagArea.Diag_Text = ' DiagArea.Diag_Text
End
If REXXHostRc <> 0 Then
Global_RC = REXXHostRc
Else
Global_RC = ReturnCode
Return Global_RC
/*-------------------------------------------------------------*/
/* Read in BCPii External constants */
/*-------------------------------------------------------------*/
GetBCPiiConstants:
"ALLOC F(HWICIREX) DA('SYS1.MACLIB(HWICIREX)') SHR REUS"
"execio * diskr "HWICIREX" (stem linelist. finis "
"FREE F(HWICIREX)"
do x = 1 to linelist.0
interpret linelist.x
end
drop linelist.
"ALLOC F(HWIC2REX) DA('SYS1.MACLIB(HWIC2REX)') SHR REUS"
"execio * diskr "HWIC2REX" (stem linelist. finis "
"FREE F(HWIC2REX)"
do x = 1 to linelist.0
interpret linelist.x
end
drop linelist.
Return 0 /* End GetBCPiiConstants */