Showing posts with label ZOAU. Show all posts
Showing posts with label ZOAU. Show all posts

Friday, February 26, 2021

How to sort on the bits of a byte using IBM DFSORT?

Recently, I came across a DFSORT coding challenge titled as "Odds & Evens".

The problem statement goes like this - Given a file with valid sequence numbers in columns 1 thru 6, sort the file so the corresponding output has all the even numbered records first, followed by all the odd numbered records.

I put on my thinking cap 🎩 for a while and came up with the following answer:

 ----+----1----+----2----+----3----+----4----+----5----+----6----+----7--  
 ***************************** Top of Data ******************************  
 //Z01071A JOB 1,NOTIFY=&SYSUID                       
 //STEP01  EXEC PGM=SORT                           
 //SORTIN  DD *                               
 000001                                   
 000002                                   
 000003                                   
 000004                                   
 000005                                   
 000006                                   
 000007                                   
 000008                                   
 000009                                   
 000010                                   
 000011                                   
 000012                                   
 000013                                   
 000014                                   
 000015                                   
 000016                                   
 000017                                   
 000018                                   
 000019                                   
 000020                                   
 //SORTOUT DD SYSOUT=*                            
 //SYSOUT  DD SYSOUT=*                            
 //SYSIN   DD *                               
   INREC IFTHEN=(WHEN=GROUP,RECORDS=2,PUSH(10:SEQ=1))            
   SORT FIELDS=(10,1,CH,D,1,6,CH,A)                     
   OUTREC FIELDS=(1,6)                            
 /*                                     
I just formed a group of 2 records and PUSH'ed sequence numbers (of 1 byte) for each record of the group. As there are only 2 records in a group, the sequence number will be 1 for the first record and 2 for the second record. The sequence number will be restarted from 1 when a new group is started. 

Then, I used the sequence number field (at col 10) in the SORT statement to sort it in the descending order so that all the records with sequence number as 2 will be at the top.  A secondary sort was applied on the first 6 bytes. 

Submitting this job, I got the following output,
  COMMAND INPUT ===>                                            SCROLL ===> CSR   
 ********************************* TOP OF DATA **********************************  
 000002                                       
 000004                                       
 000006                                       
 000008                                       
 000010                                       
 000012                                       
 000014                                       
 000016                                       
 000018                                       
 000020                                       
 000001                                       
 000003                                       
 000005                                       
 000007                                       
 000009                                       
 000011                                       
 000013                                       
 000015                                       
 000017                                       
 000019                                       
 ******************************** BOTTOM OF DATA ********************************  
WHEN=GROUP is one amazing feature in DFSORT, thanks to Frank Yaeger from IBM DFSORT Development team, as he is one of the brains behind the invention of WHEN=GROUP.

We got the answer. Are we done here?

Nope, I'm just done with the Intro. 

The main reason behind the idea of writing this blog post was that when I was looking at other answers, I stumbled upon a solution which had a syntax that I've never seen before. It goes like this: 
SORT FIELDS=(6.7,0.1,BI,A),EQUALS
Most of us would use the SORT control statement to specify the control field based on which the sorting should take place. We provide,
  1. the position of the field within the record
  2. the length of the field (in bytes)
  3. the format of the data in control field
  4. the order in which field must be sorted (ascending or descending)
Let's take the first 2 items. The position of the field within the record is the byte positon relative to the beginning of the record. The length of the field is usually expressed in integer numbers of bytes. We deal with Bytes (and a pet lover has to deal with bites 🐢 sometimes).

Let's take a look under the hood πŸ”§


A byte consists of 2 nibbles and each nibble is 4 bits long. A bit is either 0 or 1.

IBM Mainframe uses the EBCDIC character encoding. Each character is represented by its 8 bit EBCDIC Code. When we turn on the Hex mode, we will be able to see an hex value for each byte. When the hex value of each byte is converted to binary, we'll get the corresponding bits. 

For example, 

SRINI becomes,
E2        D9        C9        D5        C9                  Hexadecimal
11100010  11011001  11001001  11010101  11001001    Binary

πŸ“£IBM DFSORT allows us to sort on the bits of a byte with "bytes.bits" notation. 

How to sort on the bits of a byte?

Now, we know that each character has got an 8 bit binary value, we can use the bytes.bits notation to sort using bits.
  • First, specify the byte location relative to the beginning of the record and follow it with a period.
  • Then, specify the bit location relative to the beginning of that byte. Remember that the first (high-order) bit of a byte is bit 0 (not bit 1); the remaining bits are numbered 1 through 7.
In SORT FIELDS=(6.7,0.1,BI,A),EQUALS statement,
6.7 - says that the starting postion is the last bit in byte 6. 
0.1 - says that the length is 1 bit. 
BI  - for Binary format as we want to sort on bits
A  - for Ascending order. 

But why 6.7 as the start position of the control field? 

That's because by looking at the 6th byte of every sequence number, we can say whether that's an even number or odd number.

Example:
000001 - πŸ‘€ -> that's an odd number
000002 - πŸ‘€ -> that's an even
000003 - πŸ‘€ -> that's an odd
000004 - πŸ‘€ -> that's an even
000005 - πŸ‘€ -> that's an odd
000006 - πŸ‘€ -> that's an even. I'm tiredπŸ˜‘
....
....
.... and so on.

Another significance is that for each even number, the Least significant bit (the last bit) is 0 and for each odd number, it's 1. 

Example:
1         EBCDIC character
F1        Hexadecimal  
11110001  Binary

2         EBCDIC character
F2        Hexadecimal
11110010  Binary

3         EBCDIC character   
F3        Hexadecimal
11110011  Binary

4         EBCDIC character
F4        Hexadecimal
11110100  Binary

Hence, if we sort the last bit of 6th byte in ascending order, we would get all the even numbered records first, followed by the odd numbered records. 

The EQUALS parameter is coded in the SORT statement to preserve the original sequene in the output. If EQUALS is not coded, then the output will have all the even numbered records first, followed by the odd numbered record but the even/odd numbered records will not be in sorted order.

Let's try running this SORT operation using Python 🐍


We can make use of the Python API's provided by ZOAU to run the SORT operation. Z Open Automation Utilities (abbreviated to ZOAU) lets you perform many tasks on z/OS without needing to get into JCL. IBM has developed a bridge between Python and z/OS by creating API's for Python which allow Pythonistas to access z/OS resources. 

Before we start, we need the following stuff to run the SORT operation from Python:
  1. VS Code with Zowe explorer and IBM Z Open Editor extensions.
  2. Access to Zowe explorer.
  3. Access to USS (Unix System Services). 
  4. Little bit of Python Skills.
Note: Access to Zowe explorer and USS can be obtained when you sign up for MTM2020.

First, we need to create a new file under your home directory (/z/zxxxxx) in Unix Sytem Services. Use the touch command to create a new file.

I created one using this command, touch run_sort.py. Then, I used the IBM Z Open Editor to write the following code inside this file.

 
I've used Trinket to embed the Python code in this blog post. Note that you may not be able to RUN πŸƒ this script as the ZOAU utilities for Python aren't available in Trinket.

Let's walk through the code.

Lines 1 thru 3: The required ZOAU libraries for Python are imported so that you can use them in your code. 

Lines 5 thru 9: Line #5 uses the os.getenv() method in Python with 'USER' as argument. As the operating system that Python is running under is z/OS, USERID variable is assigned with your TSO user ID. 
Lines 6 thru 9 has got 3 variables of string type to store the dataset names. 

Lines 11 thru 28: Lines 11 thru 28 mimics the functionality of IEFBR14 utility. These lines delete the datasets before creation. We make use of the zoautil_py.Datasets module which has got several dataset related functions like create, delete, exists and so on. 

Lines 30 thru 41: Writes data into the SORTIN and SYSIN datasets. 
Line #31 defines an empty list called num.This list is created to store the sequence numbers from 1 to 20. Read more about lists and how to access the elements in a list πŸ‘‰ here
Lines 34 and 35 creates sequence numbers from 1 to 20 with the help of for loop and range() function in Python. zfill() method is used to populate leading zeros. As zfill() method can be applied only on string data, the numbers are type converted to string using str() function (in line #35). All the sequence numbers are appended to the list, num. The list is then written to the SORTIN dataset.
Lines 40 and 41 writes the SORT statements to the SYSIN dataset. The write functionality is achieved via zoautil_py.Datasets module.

Lines 43 thru 53: Line 44 creates an empty list called dd_names to store the DD names that are needed for the SORT program to run. 
In Line 53, MVSCmd.execute API is called to run the program SORT with arguments MSGPRT=CRITICAL,LIST (which goes to the PARM parameter in EXEC statement) and the list of DDStatements created in lines 47 thru 50. When this instruction is executed, a job might be submitted on z/OS in the background. 

Line #55 checks the return code from MVSCmd.execute API call. If it's zero, a message is displayed in the terminal and the output dataset (SORTOUT) created from the previous execution is read. 

When the program is run with python3 run_sort.py command in terminal, we get the following output.

Note: Click on the picture to get an enlarged view. 

The records in the output dataset are displayed in the terminal after running the Python code. The even numbered records are at the top, followed by the odd numbered records.

The datasets that were created from Python can also be accessed from the terminal. The datasets are shown below:

The input dataset to the SORT program. The sequence numbers were generated in Python.


The SYSIN input to the SORT program. The SORT statements were written from Python.


The output dataset. 

We have reached the bottom of this post and we discussed about two things:
  1. How to sort on the bits of a byte using IBM DFSORT?
  2. How to perform DFSORT operation using Python? 
I hope the content in this post was helpful to you. Please post your questions/suggestions in the Comments section of this post. 

Thx and Happy Weekend!


Wednesday, October 7, 2020

Master the Mainframe 2020 | Level 2.9: ZOAU2: Power Through Python

Hiya! Welcome to my blogπŸ˜ƒ

Master the Mainframe has been a lot interesting this year (2020) as we are using Visual Studio Code (for the first time) to establish a connection with z/OS and work with data sets and jobs, thanks to Zowe™ and IBM Z Open Editor extensions. We're now able to use the mouse to scroll through the contents of a data set😎.

Picture 9.1: Viewing the contents of a data set using IBM Z Open Editor in VS Code.

In this blog post, I want to share my impressions on using Python🐍 and Z Open Automation Utilities (abbreviated to ZOAU) to build an app to validate credit card data (This is the challenge in Level 2.9 of MTM2020). 

πŸ’‘: ZOAU lets you perform many tasks on z/OS without needing to get into JCL. 

Before getting started with Zowe and Python, we will be building the logic using COBOL; we will then compile and run the COBOL program with the help of  JCL.  Then, we will be looking at Python and ZOAU way of building the same logic. In this way, I believe you will be able to understand the differences better. You'll also get answers to  some questions like 'Why Python?', 'Why ZOAU?', 'Why not JCL and COBOL?' and so on..

Fasten your seat belts, let's take-off now! 

The requirement is to read an input file (MTM2020.PUBLIC.CUST16 is the input data set name in MTM2020 system) and print a report with list of records that has got an invalid Credit card number (aka Payment Card Number). 

The input data set conforms to Track 1 Format B of ISO/IEC 7813 format πŸ‘‰(https://en.wikipedia.org/wiki/ISO/IEC_7813). 

Picture 9.2: A snap from Wikipedia that shows the structure of Track 1 Format B. I've used this structure to come up with the record layout of the input file for COBOL.

Using COBOL program and JCL:

The COBOL program prepared by this AuthorπŸ˜‰ is as follows: 
Picture 9.3: The COBOL program reads the input file; implements Luhn Algorithm to find if a credit card number is vaild or not; if invalid, the record is written to the output file as a report. 

What the COBOL program does? 

  • An input file is being read (the record layout of the input file, INFILE-REC is based on Track 1 Format B structure of ISO/IEC 7813). 
  • For each record that is being read, control is passed to 100-IS-INVALID-PARA. This para implements the Luhn Algorithm. 
  • The first PERFORM loop within 100-IS-INVALID-PARA computes the sum of digits present in the odd number positions in PYMT-CARD-NUMBER. For example, if the value in the PYMT-CARD-NUMBER data item is 1234567890223457846, then 1 + 3 + 5 + 7 + 9 + 2 + 3 + 5 + 8 + 6 = 49 will be stored in WS-CHECKSUM at the end of the first perform loop. 
  • The second PERFORM loop within 100-IS-INVALID-PARA multiply the digits present in the even number positions, by 2; if the resulting value is greater than 9, then 9 is subtracted from the resulting value (Please don't ask ME why we have to do this. IBM Scientist Hans Peter Luhn, created this algorithm😊); accumulate the resulting value by adding it to WS-CHECKSUM data-item. 
  • After the 2 PERFORM loops, divide the final value in WS-CHECKSUM data item by 10. You should assign a data item for holding the remainder. If the remainder is 0, the value in PYMT-CARD-NUMBER is valid. Else, it is invalid. 
  • When an Invalid credit card number is found, control is passed to 200-WRITE-OUTPUT-PARA to write the whole record with the invalid credit card number, to the output file. When the control is passed to 200-WRITE-OUTPUT-PARA for the first time, the header record and an empty line are written to the output file. 

I was not aware of Luhn algorithm until I started this challenge. A pretty interesting one. The Luhn Algorithm is an efficient method of checking if a credit card number is valid, locally, without needing to have the bank or financial institute process it. This way, cards can be checked directly on a web page for mistakes in typing or copying digits. More information about Luhn algortihm can be found πŸ‘‰ here.

After finishing the COBOL program, we have to prepare a JCL to compile the program and run it by providing input and output files. The following JCL does that.


After the compilation and execution of the program, we get the following output.

To read an input data set with magnetic stripe data that you might find on a credit card (if it doesn’t have a chip) and to print a report out of it with the list of invalid credit card numbers, what all we did? We wrote a COBOL program, prepared a JCL to compile the program and execute it. I call this the  usual Mainframe Way (not The Milky WayπŸ˜‰). 

Let's do the same task using Zowe™ and Python. This is what Level 2.9 in MTM2020 is all about. 

Using Zowe™ and Python🐍:

ZOAU supports shell scripts, Python and Node.js. The commands available through Z Open Automation Utilities in Python can be found πŸ‘‰ here. In Layman's terms, IBM has developed a bridge between z/OS and Python by making these commands available for us to use within the Python scripts. We use πŸ‘‰ zoautil_py.Datasets module extensively in this challenge. Modules in Python will have a set of functions, classes and variables defined in it. zoautil_py.Datasets module has got several functions like, 
  • create(name, type, size, format, class_name, length, offset) - Used to create a data set in z/OS
  • delete(dataset) - Used to delete a data set in z/OS
  • copy(source, target) - Used to copy the source data set into destination data set
You just have to refer to the corresponding function and pass necessary arguments in your Python script to perform the task it is intended to do. 

Building the logic using Python:

Note: In order to access python scripts from your home directory, /z/zxxxxxyou should be signed up to πŸ‘‰ MTM2020 and should've finished the challenges till Level 2.4. 
There's already a python script (cc_check.py) available under your home directory (/z/zxxxxx) in Unix Sytem Services (abbreviated to USS). There are LOTS of comments in the script to help you understand what the code is doing. You just have to build, upon this foundation, a logic which would write the invalid credit card number to the output file.  
πŸ’‘: A comment in Python starts with the hash character, #
I've used Trinket to embed Interactive Python in this blog post and I've copy pasted the contents of cc_check.py file in Trinket (And nope! The script shown below isn't the solution for Level 2.9😐). I'll do my best here to make you understand what needs to be done to complete Level 2.9. 

Note that you may not be able to RUN πŸƒ this script in Trinket as the ZOAU utilities for Python aren't available in Trinket.
 
 
Lines 1 thru 7: The necessary ZOAU libraries for Python are imported so that you can use them in the script. 

Lines 9 thru 16: The input data set, MTM2020.PUBLIC.CUST16 is being read into a variable called cc_contents. Line #12 uses the os.getenv() method in Python with 'USER' as argument. As the operating system that Python is running under, is z/OS, USERID variable is assigned with your TSO user ID. In Line #13, 2 strings are concatenated with '+' operator and the result is stored in output_dataset variable. Some functions (like exists, delete) in the Datasets module are being used to check if the output data set is already existing in the Mainframe. If yes, then it is deleted.  

Lines 17 and 18: ReadπŸ‘€ those comments in the 2 lines as they help you figure out what needs to be done.

Lines 21 thru 28: def keyword in Python is used to define functions. You all know that function is a group of statements which are intended to perform a specific task. This function's name is is_even and this function receives one argument from the caller. The purpose of this function is to check if the number passed as an argument by the caller, is even or odd. If it is even, the function returns 'True' to the caller. Else, it returns 'False'. Your task is to edit this script so that instead of finding the even numbers, it implements the Luhn algorithm to find the six invalid entries in the input file. You may use the sample code in luhn.py file to perform the checking logic. 

Lines 30 thru 35: Each record in the data set is stored as an element in a List named as cc_list. Like how we read records one by one in COBOL, a for loop in Python is being used to read the elements of cc_list, one by one. cc_line variable is used to hold the element in any given iteration of the for loop. Like how we use Reference Modification in COBOL to extract a portion of the string, slicing technique (in Line 33) is being used on the List element to extract the Payment card number. The extracted card number is then passed as an argument to is_even function.
πŸ’‘: List is one of the most frequently used datatype in Python. The elements are stored in a list within a square brackets ([ ]). Lists can have any number of items in it and the elements may be of different data types. Read more about lists and how to access the elements in a list πŸ‘‰ here.

Lines 38 thru 57: Pretty much self explanatory as there are a lot of comments. Once you're ready to write the 6 invalid credit card numbers to the output file, uncomment line 57. 

And, there you go! refer luhn.py file and implement the Luhn algorithm in the place of is_even function. Bonus: Pay attention whenever you find the word hint in a comment line.

With Python and ZOAU utilities, it required only ~30 lines of code to build the logic that will look for the invalid credit card numbers from a Mainframe Input data set and write a report consisting of the invalid entries to an output data set. Power Through PythonπŸ’ͺ
You just witnessed one of the newest innovations in z/OS, the introduction of IBM Z Open Automation Utilities (ZOA Utilities) in ACTION πŸ’₯. ZOAU is an alternative way of interacting with tasks on z/OS through scripting instead of writing JCL's and submitting jobs. 

Hope you liked this post. Feel free to add your comments below. ThxπŸ‘

Screenshot Courtesy: Mainframe accessπŸ’» obtained via MTM2020 contest run by IBM.