• principal_3

    Desde 2015, enseñando sobre el sistema operativo z/OS de IBM en esta web. z/OS se utiliza en máquinas llamadas Mainframe.

  • principal_1

    Para realizar el contenido, utilizo el producto de IBM llamado z/Development and Test Environtment Personal Edition. Este software permite emular un Mainframe y así poder utilizar z/OS para aprender.

  • principal_2

    Es utilizado por grandes empresas (bancos, aseguradoras...). Aquí aprenderás a instalar y configurar productos relacionados con z/OS.

  • principal_4

    ADCD es una distribución de z/OS que contiene productos de IBM como IMS, DB2, CICS, ZOWE, TWS, NetView, System Automation, etc.

STC para mover salidas del Spool a dataset mediante REXX

Esta vez vamos a crear una tarea (STC) que se encargue de pasar salidas de spool a un dataset y, si se han copiado correctamente, las purgue. Esto lo haremos mediante un REXX que se estará ejecutando en bucle infinito y revisará todos los jobs del spool. Los copiará en diferentes miembros y purgará las salidas elegidas. Después, hará una pausa de 30 segundos antes de empezar de nuevo. Es importante decir que la tarea no consume máquina durante la pausa. Este REXX también crea un nuevo dataset, si detecta que el anterior se ha llenado. Cuando hay dos salidas con el mismo nombre, se sustituye las últimas letras del nombre por números. Pararemos la ejecución del bucle infinito borrando un fichero de control mediante otra tarea.

Lo primero es copiar el siguiente REXX en una librería donde podamos ejecutarlo. Yo voy a usar la librería ADCD.Z113.CLIST. Lo llamaremos LIMARRAN.

NOTA: Es posible que haya una manera más eficiente de hacerlo, pero estoy aprendiendo y es la forma que se me ha ocurrido. Creo que el código está bastante explicado con los comentarios. Este REXX procesa las salidas que están en clase A y que tienen estado “PRINT”.

 

/* REXX */

/* PARA PARAR LA EJECUCION HAY QUE BORRAR EL FICHERO         */

/* LIM.INICIADO                                              */

ADDRESS TSO

"PROFILE NOPREFIX"

/*                                                           */

/* CREAMOS EL DATASET CONTROL CUANDO LA TAREA ESTA ARRANCADA */

/* CUANDO LO BORREMOS LA TAREA SE PARARA                     */

/*                                                           */

"ALLOCATE DATASET(LIM.INICIADO) NEW DIR(0) REUSE

DSORG(PS) BLOCK(3200) SPACE(1,1) RECFM(V,B) LRECL(240) BLKSIZE(3120)"

/*                                                           */

/* LIBERAMOS EL DATASET                                      */

/*                                                           */

"FREE DA('LIM.INICIADO')"

/*                                                           */

/*  CREAMOS LA LIBRERIA DONDE SE GUARDARAN LAS SALIDAS       */

/*  FORMATO: LIM.SALIDAS.DYYMMDD.HHHMMSS                     */

CALL CREAFICH

/*                                                           */

/* HACEMOS UN BUCLE INFINITO QUE COMPROBARA SI HAY SALIDAS   */

/* QUE MOVER. HAY UNA PARADA DE 60 SEGUNDOS EN CADA EJECUCION*/

/*                                                           */

DO COUNT = 1 TO 5

  CALL COMPROBAR

  ADDRESS TSO

  /* EJECUTAMOS LA PARADA DE 30 SEGUNDOS                     */

  "BPXBATCH PGM /bin/sleep 30"

  /* DESPUES DE LA PARADA COMPROBAMOS SI EXISTE EL FICHERO   */

  /* DE CONTROL. SI NO EXISTE, PARAMOS LA EJECUCION          */

  PARAR=SYSDSN('LIM.INICIADO')

  IF PARAR /= "OK" THEN

    EXIT

  /* RESTAMOS 1 A COUNT PARA MANTENER EL BUCLE INFINITO      */

  COUNT = COUNT - 1

END

EXIT

/*                                                           */

/* SUBRUTINA PARA MOSTRAR LOS MENSAJES DE ERROR              */

/*                                                           */

MSGRTN: PROCEDURE EXPOSE ISFMSG ISFMSG2.

     /* THE ISFMSG VARIABLE CONTAINS A SHORT MESSAGE */

IF ISFMSG<>"" THEN

  SAY "ISFMSG IS:" ISFMSG

     /* THE ISFMSG2 STEM CONTAINS ADDITIONAL DESCRIPTIVE */

     /* ERROR MESSAGES                                   */

DO IX=1 TO ISFMSG2.0

  SAY "ISFMSG2."IX "IS:" ISFMSG2.IX

END

RETURN

/*                                                           */

/* SUBRUTINA QUE CREA LOS DATASET DONDE SE GUARDARAN LAS     */

/* SALIDAS                                                   */

/*                                                           */

CREAFICH:

/*                                                           */

/* COGEMOS LA FECHA EN FORMATO YYMMDD                        */

/*                                                           */

ADDRESS TSO

"PROFILE NOPREFIX"

FECHA =  TRANSLATE('345678', DATE('S'), '12345678')

/* COGEMOS LA HORA EN FORMATO HHMMSS                         */

HORA = TRANSLATE('124578', TIME('NORMAL'), '12345678')

/* CREAMOS EL FICHERO CON FORMATO: LIM.SALIDAS.DYYMMDD.HHHMMSS */

FICHERO = LIM.SALIDAS.D||FECHA||.H||HORA

"ALLOCATE DATASET("FICHERO") NEW

 DSORG(PS) BLOCK(3200) SPACE(20,20) RECFM(V,B) LRECL(240) BLKSIZE(3120)

 DIR(50)"

"FREE DA('"FICHERO"')"

SAY "SE HA CREADO EL FICHERO "||FICHERO

RETURN

/*                                                           */

/* SUBRUTINA QUE MUEVE Y LIMPIA DEL SPOOL LAS SALIDAS QUE    */

/* QUERAMOS. EN ESTE CASO, QUE ESTEN EN PRINT Y CLASE A.     */

/*                                                           */

COMPROBAR:

NUMERROR=0

/* CLASE DE LAS SALIDAS */

CLASE="A"

ESTADO="PRINT"

RC=ISFCALLS('ON')

ADDRESS SDSF

/*                                                           */

/* PONEMOS OWNER Y PREFIX CON * PARA VER TODAS LAS SALIDAS   */

/*                                                           */

ISFPREFIX='*'

ISFOWNER='*'

/* ACCEDEMOS AL PANEL ST                                     */

"ISFEXEC ST"

LRC=RC

IF LRC<>0 THEN

  CALL MSGRTN

/* COGEMOS EL DATASET EN SHARE PARA QUE NO FALLE SI ESTAMOS DENTRO */

ISFPRTDISP=SHR

DO IX=1 TO JNAME.0

 /* COMPRUEBA SI EL JOB TIENE CLASE A Y ESTADO PRINT               */

 IF JCLASS.IX = CLASE & QUEUE.IX = ESTADO THEN

   DO

      /* COGEMOS EL NOMBRE DEL JOB                                 */

      NAMEJOB=JNAME.IX

      NUM=1

      /* COMPROBAMOS SI YA HAY UN JOB CON ESE NOMBRE               */

      ISOK=SYSDSN(""FICHERO"("NAMEJOB")")

      IF ISOK = "OK" THEN

        DO WHILE ISOK="OK"

          /* MODIFICAMOS EL NOMBRE DEL JOB CAMBIANDO LA ULTIMA LETRA */

          /* POR UN NUMERO PARA PODER GUARDARLO                      */

          NAMEJOB=OVERLAY(NUM,NAMEJOB,LENGTH(NAMEJOB)-LENGTH(NUM)+1)

          /* VOLVEMOS A COMPROBAR SI EL NOMBRE NUEVO EXISTE          */

          ISOK=SYSDSN(""FICHERO"("NAMEJOB")")

          NUM=NUM+1

        END

      /* ESTAS DOS VARIABLES ES DONDE SE COPIARA LA SALIDA    */

      ISFPRTDSNAME=FICHERO

      ISFPRTMEMBER=NAMEJOB

      /* COPIAMOS LA SALIDA AL DATASET                        */

      SAY "COPIAMOS LA SALIDA "||NAMEJOB||" "||JOBID.IX||" A "||FICHERO

      ADDRESS SDSF "ISFACT ST TOKEN('"TOKEN.IX"') PARM(NP XDC)"

      LRC=RC

      /* SI SE PRODUCE UN ERROR AL COPIAR SALIDA ES POSIBLE   */

      /* QUE LA LIBRERIA ESTE LLENA. CREAMOS UNA NUEVA PARA   */

      /* INTENTAR RESOLVERLO.                                 */

      IF LRC<>0 THEN

        DO

          /* SI SE PRODUCE MAS DE UN ERROR, CANCELAMOS LA TAREA  */

          /* PARA EVITAR QUE SE EMBUCLE EN EL ERROR              */

          IF NUMERROR=1 THEN

            DO

              SAY "SE HAN PRODUCIDO VARIOS ERRORES. PARAMOS LA TAREA"

              ADDRESS TSO

              "SEND 'SE PARA LA TAREA LIMARRAN POR VARIOS ERRORES'"

              CALL MSGRTN

              EXIT 20

            END

          /* COMO NO HEMOS PODIDO COPIAR LA SALIDA, CREAMOS UN   */

          /* FICHERO NUEVO                                       */

          SAY "ERROR AL COPIAR LA SALIDA "||NAMEJOB||" "||JOBID.IX||,

              " RC="||LRC

          /* NUMERROR=1 PARA CONTROLAR QUE HA HABIDO UN ERROR   */

          NUMERROR=1

          /* CREAMOS EL NUEVO FICHERO                           */

          CALL CREAFICH

        END

      ELSE

        DO

          SAY "PURGAMOS LA SALIDA "||NAMEJOB||" "||JOBID.IX

          /* BORRAMOS LA SALIDA DEL SPOOL                         */

          ADDRESS SDSF "ISFACT ST TOKEN('"TOKEN.IX"') PARM(NP P)"

          /* RESETEAMOS EL NUMERO DE ERRORES                      */

          NUMERROR=0

        END

   END

END

RETURN

 

 

Ahora tenemos que copiar la tarea de arranque (LIMARRAN, se llama igual que el REXX) y de parada (LIMPARAR). Hay que copiarlas en la librería “PROCLIB” del sistema, en mi caso, ADCD.Z113.PROCLIB. La tarea LIMARRAN se encarga de ejecutar el REXX que hace el proceso. Se arrancara desde la consola con “S LIMARRAN”.

 

//* PASAMOS COMO PARAMETRO EL NOMBRE DEL REXX CON % DELANTE   

//LIMARRAN EXEC PGM=IKJEFT01,PARM='%LIMARRAN'                 

//* PONEMOS LA LIBRERIA DONDE ESTA EL REXX                    

//SYSEXEC  DD DSN=ADCD.Z113.CLIST,DISP=SHR                    

//SYSTSPRT DD  SYSOUT=*                                       

//SYSTSIN  DD  DUMMY                                          

 

 

La tarea LIMPARAR se encarga de borrar el fichero de control que usa el REXX para que se pare de ejecutar. Simplemente se arranca la tarea LIMPARAR con el comando “S LIMPARAR” para borrar el fichero y la tarea LIMARRAN se parará.

 

//LIMPARAR EXEC PGM=IDCAMS   

//SYSPRINT DD SYSOUT=*       

//SYSIN DD *                 

  DELETE LIM.INICIADO        

  SET MAXCC=0                

/*                           

 

 

 

Ahora intentamos arrancar la tarea y debería pararse inmediatamente porque tenemos que hacer un último cambio, pero antes quiero mostrar el error que se produce.

 

Vamos al spool y buscamos la salida. Nos fijamos que el usuario “OWNER” es el START2. Ese es el usuario que ejecuta la tarea.

 

Esto ocurre porque el usuario que estamos utilizando (START2) no tiene permisos para ejecutar comandos en SDSF.

 

Para solucionarlo, editamos el miembro ISFPRM00 de la “PARMLIB”, en mi caso, ADCD.Z113.PARMLIB.

 

Como vimos en la entrada Activando todas las opciones de SDSF, hay distintos grupos de permisos para asignar. Para evitar dar demasiados permisos a un grupo inferior, como el grupo ISFUSER, vamos a crear un nuevo grupo para asignárselo al usuario que estamos utilizando. Tenemos que ir bajando hasta encontrar el texto “GROUP ISFOPER”. Vamos a copiar todas las líneas de ese grupo, ya que lo usaremos como plantilla porque tiene los permisos que necesitamos.

 

Ahora tenemos dos veces definido el mismo grupo, vamos a modificar el primer grupo ISFOPER que tenemos. ES IMPORTANTE QUE EDITEMOS EL PRIMERO Y DEJEMOS EL SEGUNDO COMO ESTÁ. Haremos los siguientes cambios:

                En “GROUP NAME” ponemos ISFREXX.

                Borramos la línea del parámetro TSOAUTH.

                Añadimos una línea con el texto:

IUID(GRUPOREX),             /* Grupo para ejecutar REXX en batch   */

 

GRUPOREX será el ID del grupo que asignaremos al usuario START2.

 

Ahora seguimos bajando hasta que encontremos el texto “NTBL list” y añadiremos lo siguiente para asignar el grupo GRUPOREX al usuario START2:

NTBL NAME(GRUPOREX)                 

  NTBLENT STRING(START2),OFFSET(1)  

 

Guardamos y salimos del miembro ISFPRM00. Ahora vamos a la consola y vamos a refrescar los parámetros del SDSF con el comando “F SDSF,REFRESH

 

Si hemos escrito todo bien, se cargará la nueva configuración.

 

Antes de arrancar la tarea, confirmamos que tenemos salidas en el spool para que las procese el REXX.

NOTA: Recuerdo que este REXX se lleva las salidas en estado PRINT y que sean de la clase A.

 

Ahora sí, arrancamos la tarea con “S LIMARRAN”.

 

Vemos el mensaje “STARTED” y esta vez no se para la tarea.

 

Comprobamos que se ha creado el fichero de control “LIM.INICIADO” y la primera librería que guardará las salidas.

 

Vemos que ya hay algunas salidas guardadas. Como se llamaban igual cuando estaban en el spool, el nombre va cambiando.

 

Vamos a la tarea en SDSF, vemos que está en ejecución y entramos con una “s”.

 

Vamos al final de la tarea y vemos los mensajes que va sacando el REXX para hacer el seguimiento de la ejecución.

Observamos que cuando da RC=8 es porque la librería se ha llenado. Automáticamente crea un nueva y sigue guardando las siguientes salidas. La salida que no se ha podido copiar, en este caso el JobId JOB00199, se copiará la siguiente vez que pase el proceso, después de la pausa.

 

Al principio de la tarea, vemos cómo se van purgando los jobs.

 

Vemos como, al final del proceso, se han copiado las salidas que anteriormente habían dado error, como, por ejemplo, el JobId JOB00199 que se había quedado pendiente.

 

Vamos a buscarlo a su librería para confirmar que se ha copiado.

 

Entramos en el miembro correspondiente.

 

Confirmamos que es el JobId JOB00199.

 

Si ahora vamos a la opción “DA” de SDSF, veremos que, cuando está haciendo la pausa, aparece la tarea “LIMARRAN *OMVSEX”. Eso ocurre porque ejecuta proceso “sleep” del OMVS.

 

Confirmamos que, en el momento de pausa, no consume CPU.

 

Aquí vemos que, cuando está revisando el spool, consume CPU (como es lógico).

 

Ahora vamos a parar la tarea LIMARRAN, para ello tenemos que arrancar la tarea LIMPARAR para que borre el fichero de control “LIM.INICIADO”. Desde la Consola, arrancamos la tarea con el comando “s LIMPARAR”.

 

Vemos como, tras ejecutar la tarea LIMPARAR, se para la tarea LIMARRAN.

NOTA: es posible que la tarea LIMARRAN tarde un poco en parar si está en medio del proceso de revisión del spool o de la pausa.

 

Por último, confirmamos que el fichero de control “LIM.INICIADO” ya no existe.

 

Ya tenemos terminada y probada la tarea que limpia el spool. Más adelante intentaré configurar el HSM (DFSMShsm) para poder migrar librerías de salidas antiguas. ¡A ver si lo consigo!

 

 

Publish modules to the "offcanvs" position.