Limpiar filtro
Pregunta
LUIS VENDITTELLI · 1 sep, 2022
Hola!!!! Tengo un tablepane con una propiedad "where Clause = CAMPO > ?"
Cuando desde un ClassMethod quiero actualizar el query de ese tablePane usando "zen(tablePane).parameters[0].value = valor"me devuelve el siguiente error:
Cannot set properties of undefined (setting 'value')
Alguna idea de qué estoy haciendo mal?
Muchas gracias!!!!! Es difícil saber qué está pasando sin ver el código y probarlo. Antes de verlo, diría que el tableare no tiene definido parámetros en la descripción de la tabla. Ejemplo:
<tablePane id="table"
sql="SELECT ID,Name FROM MyApp.Employees
WHERE Name %STARTSWITH ? ORDER BY Name"
>
<parameter value="Z"/>
</tablePane>
Podrías intentar simplificar el código al máximo en una clase copiada de la original, y, cuando no puedas reducir más el código, nos lo mandes y podamos echarle un vistazo.
Hola Luis,
Parece que estás en una versión muy antigua (2012) y utilizando una tecnología (ZEN) también antigua. ZEN se soporta aún por compatibilidad, pero échale un vistazo al InterSystems IRIS Migration Guide en el [WRC > Software Distribution > Docs](https://wrc.intersystems.com/wrc/coDistDocs.csp).
Sobre tu cuestión, el error probablemente viene dado de que intentas establecer el `value` de algo nulo. No consigues referencia el parámetro. Prueba con el ejemplo que te ha pasado Mario, o incluso mejor añade un `id` al parámetro para que puedas referenciarlo directamente a través del identificador.
```
```
Mira por ejemplo en [Query Parameters](https://docs.intersystems.com/ens201815/csp/docbook/DocBook.UI.Page.cls?KEY=GZCP_tables#GZCP_table_parameters)
Este es el tablepane:
<tablePane id="tpDIGI" showQuery="true" valign="top" maxRows="300" tableName="NombreTabla" showRowSelector="false" width="490px" showFilters="true" showValueInTooltip="true" autoExecute="true" fixedHeaders="true" whereClause="CAMPO = ?" onselectrow="" useSnapshot="true" initialExecute="true" rowSelect="false"> <column ... /> </tablePane>
Más abajo, tengo un ClassMethod que ejecuta:
zen('tpDIGI').parameters[0].value='1111'
Ahi me da el error antes mencionado. Alguna idea??? Ya encontré el problema!!! Faltaba el tag <parameter />
Lo agregué y se solucionó!!!
Saludos a toda la comunidad.
Artículo
Ricardo Paiva · 12 nov, 2021
Este curso de formación está dirigido a todas las personas interesadas en conocer el *framework* de Interoperabilidad de IRIS. Utilizaremos Docker y VSCode.
GitHub: https://github.com/grongierisc/formation-template
# 1. **Formación en Ensemble/Interoperabilidad**
El objetivo de esta formación es aprender el *framework* de interoperabilidad de InterSystems, y en particular el uso de:
* Producciones
* Mensajes
* *Business Operations*
* Adaptadores
* *Business Processes*
* *Business Services*
* Operaciones y servicios REST
**ÍNDICE:**
- [1. **Formación de Ensemble/Interoperabilidad**](#1-ensemble--interoperability-formation)
- [2. *Framework*](#2-framework)
- [3. Adaptación del *framework*](#3-adapting-the-framework)
- [4. Requisitos previos](#4-prerequisites)
- [5. Configuración](#5-setting-up)
- [5.1. Contenedores de Docker](#51-docker-containers)
- [5.2. Portal de Administración](#52-management-portal)
- [5.3. Guardar el progreso](#53-saving-progress)
- [6. Producciones](#6-productions)
- [7. Operaciones](#7-operations)
- [7.1. Creación de nuestra clase de almacenamiento](#71-creating-our-storage-class)
- [7.2. Creación de nuestra clase para mensajes](#72-creating-our-message-class)
- [7.3. Creación de nuestra operación](#73-creating-our-operation)
- [7.4. Cómo añadir la operación a la producción](#74-adding-the-operation-to-the-production)
- [7.5. Pruebas](#75-testing)
- [8. *Business processes*](#8-business-processes)
- [8.1. *Business processes* simples](#81-simple-bp)
- [8.1.1. Creación del proceso](#811-creating-the-process)
- [8.1.2. Modificación del contexto de un *business process*](#812-modifying-the-context-of-a-bp)
- [8.2. Cómo hacer que el *business process* lea líneas CSV](#82-bp-reading-csv-lines)
- [8.2.1. Creación de un mapa de registro](#821-creating-a-record-map)
- [8.2.2. Creación de una transformación de datos](#822-creating-a-data-transformation)
- [8.2.3. Añadir la transformación de datos al *business process*](#823-adding-the-data-transformation-to-the-business-process)
- [8.2.4. Configuración de la producción](#824-configuring-production)
- [8.2.5. Pruebas](#825-testing)
- [9. Obtener acceso a una base de datos externa usando JDBC](#9-getting-access-to-an-extern-database-using-jdbc)
- [9.1. Creación de nuestra nueva operación](#91-creating-our-new-operation)
- [9.2. Configuración de la producción](#92-configuring-the-production)
- [9.3. Pruebas](#93-testing)
- [9.4. Ejercicio](#94-exercise)
- [9.5. Solución](#95-solution)
- [10. Servicio REST](#10-rest-service)
- [10.1. Creación del servicio](#101-creating-the-service)
- [10.2. Añadir nuestro *business service* (BS)](#102-adding-our-bs)
- [10.3. Pruebas](#103-testing)
- [Conclusión](#conclusion)
# 2. *Framework*
Este es el *framework* de IRIS.

Los componentes que están en el interior de IRIS representan una producción. Los adaptadores de entrada y de salida nos permiten utilizar diferentes tipos de formato como entrada y salida para nuestra base de datos. Las aplicaciones compuestas nos darán acceso a la producción a través de aplicaciones externas como los servicios REST.
Las flechas que están entre todos estos componentes son **mensajes**. Estos pueden ser solicitudes o respuestas.
# 3. Adaptación del *framework*
En nuestro caso, leeremos las líneas desde un archivo CSV y las guardaremos en la base de datos IRIS.
Entonces, añadiremos una operación que nos permitirá guardar los objetos en una base de datos externa, utilizando JDBC. Esta base de datos se ubicará en un contenedor de Docker, utilizando postgre.
Por último, veremos cómo utilizar aplicaciones compuestas para insertar nuevos objetos en nuestra base de datos, o para consultar esta base de datos (en nuestro caso, a través de un servicio REST).
El *framework* adaptado a nuestro propósito nos ofrece:

# 4. Requisitos previos
Para esta formación, necesitarás:
* VSCode: https://code.visualstudio.com/
* El conjunto de *addons* de InterSystems para VSCode: https://intersystems-community.github.io/vscode-objectscript/installation/
* Docker: https://docs.docker.com/get-docker/
* El *addon* de Docker para VSCode
# 5. Configuración
## 5.1. Contenedores de Docker
Para tener acceso a las imágenes de InterSystems, hay que ir a esta URL: http://container.intersystems.com. Después de iniciar sesión con nuestras credenciales de InterSystems, obtendremos nuestra contraseña para conectarnos al registro. En el *addon* de Docker para VSCode, que se encuentra en la pestaña de imágenes, hacemos clic en Conectar Registro, introducimos la misma URL que antes (http://container.intersystems.com) como registro genérico y se nos pedirá que demos nuestras credenciales. El inicio de sesión es el habitual pero la contraseña es la que obtuvimos del sitio web.
A partir de ahí, deberíamos ser capaces de crear y componer nuestros contenedores (con los archivos `docker-compose.yml` y `Dockerfile` que se nos dieron).
## 5.2. Portal de Administración
Abriremos un Portal de Administración. Esto nos dará acceso a una página web desde la que podremos crear nuestra producción. El portal debe estar ubicado en la URL: http://localhost:52775/csp/sys/UtilHome.csp?$NAMESPACE=IRISAPP. Necesitarás las siguientes credenciales:
> LOGIN: SuperUser
>
> PASSWORD: SYS
## 5.3. Guardar el progreso
Una parte de las cosas que haremos se guardarán localmente, pero todos los procesos y producciones se guardan en el contenedor de Docker. Con el fin de conservar todo nuestro progreso, necesitamos exportar todas las clases que se crean desde el Portal de Administración con ayuda del *addon* `ObjectScript` de InterSystems:

Tendremos que guardar de esta forma nuestra producción, mapa de registros, *business processes* y transformaciones de datos. Después de hacerlo, cuando cerremos nuestro contenedor Docker y hagamos la compilación nuevamente, aún tendremos todo nuestro progreso guardado de forma local (por supuesto, hay que hacer esto después de cada cambio que hagamos a través del portal). Para que sea accesible a IRIS de nuevo, tenemos que compilar los archivos exportados (cuando los guardemos, los *addons* de InterSystems se encargarán del resto).
# 6. Producciones
Ahora podemos crear nuestra primera producción. Para hacerlo, nos moveremos por los menús [Interoperability] y [Configure]:

Ahora hacemos clic en [New], seleccionamos el paquete [Formation] y elegimos un nombre para nuestra producción:

Inmediatamente después de crear nuestra producción, hay que hacer clic en la opción \[Production Settings], situada encima de la sección [Operations]. En el menú de la barra lateral derecha, tendremos que activar la opción [Testing Enabled] en la sección [Development and Debugging] de la pestaña [Settings\] (no te olvides de hacer clic en [Apply]).

En esta primera producción añadiremos ahora las *business operations*.
# 7. Operaciones
Una *business operation* (BO) es un tipo de operación específica que nos permitirá enviar solicitudes desde IRIS hacia una aplicación/sistema externo. También se puede utilizar para guardar lo que queramos directamente en IRIS.
Crearemos esas operaciones de forma local, es decir, en el archivo `Formation/BO/`. Cuando guardemos los archivos los compilaremos en IRIS.
En nuestra primera operación, guardaremos el contenido de un mensaje en la base de datos local.
Para hacerlo, primero necesitamos tener una forma de almacenar este mensaje.
## 7.1. Creación de nuestra clase de almacenamiento
En IRIS, las clases de almacenamiento extienden el tipo `%Persistent`. Se guardarán en la base de datos interna.
En nuestro archivo `Formation/Table/Formation.cls` tenemos lo siguiente:
```objectscript
Class Formation.Table.Formation Extends %Persistent
{
Property Name As %String;
Property Salle As %String;
}
```
Ten en cuenta que al guardar, de forma automática se añaden líneas adicionales al archivo. Son obligatorias y las añaden los *addons* de InterSystems.
## 7.2. Creación de nuestra clase para mensajes
Este mensaje contendrá un objeto `Formation`, situado en el archivo `Formation/Obj/Formation.cls`:
```objectscript
Class Formation.Obj.Formation Extends (%SerialObject, %XML.Adaptor)
{
Property Nom As %String;
Property Salle As %String;
}
```
La clase `Message` utilizará el objeto `Formation`, `src/Formation/Msg/FormationInsertRequest.cls`:
```objectscript
Class Formation.Msg.FormationInsertRequest Extends Ens.Request
{
Property Formation As Formation.Obj.Formation;
}
```
## 7.3. Creación de nuestra operación
Ahora que ya tenemos todos los elementos que necesitamos, podemos crear nuestra operación, en el archivo `Formation/BO/LocalBDD.cls`:
```objectscript
Class Formation.BO.LocalBDD Extends Ens.BusinessOperation
{
Parameter INVOCATION = "Queue";
Method InsertLocalBDD(pRequest As Formation.Msg.FormationInsertRequest, Output pResponse As Ens.StringResponse) As %Status
{
set tStatus = $$$OK
try{
set pResponse = ##class(Ens.Response).%New()
set tFormation = ##class(Formation.Table.Formation).%New()
set tFormation.Name = pRequest.Formation.Nom
set tFormation.Salle = pRequest.Formation.Salle
$$$ThrowOnError(tFormation.%Save())
}
catch exp
{
Set tStatus = exp.AsStatus()
}
Quit tStatus
}
XData MessageMap
{
InsertLocalBDD
}
}
```
El MessageMap nos proporciona el método que debemos lanzar, dependiendo del tipo de solicitud (el mensaje que se envió a la operación).
Como podemos ver, si la operación recibió un mensaje del tipo `Formation.Msg.FormationInsertRequest`, se llamará al método `InsertLocalBDD`. Este método guardará el mensaje en la base de datos local de IRIS.
## 7.4. Cómo añadir la operación a la producción
Ahora necesitamos añadir esta operación a la producción. Para hacerlo, utilizaremos el Portal de Administración. Al hacer clic en el signo [+] junto a [Operations], tendremos acceso al [Business Operation Wizard]. Allí, elegiremos la clase de la operación que acabamos de crear en el menú desplegable.

## 7.5. Pruebas
Si hacemos doble clic en la operación podremos activarla. Después de hacerlo, al seleccionar la operación e ir a las pestañas [Actions] que están en el menú de la barra lateral derecha, deberíamos poder probar la operación (si no ves la sección para crear la producción, puede que tengas que iniciar la producción si se encuentra detenida).
De este modo, enviaremos a la operación un mensaje del tipo que declaramos anteriormente. Si todo sale bien, los resultados deberían ser similares a los que se muestran a continuación:

Mostrar el registro visual nos permitirá ver lo que ocurrió entre los procesos, servicios y operaciones. Aquí, podemos ver el mensaje que se envía a la operación por parte del proceso, y a la operación cuando envía de vuelta una respuesta (que en este caso solo es una cadena vacía).
# 8. *Business Processes*
Los *business processes* (BP) son la lógica empresarial de nuestra producción. Se utilizan para procesar las solicitudes o retransmitirlas a otros componentes de la producción.
Los *business processes* se crean dentro del Portal de Administración:

## 8.1. *Business processes* simples
### 8.1.1. Creación del proceso
Ahora estamos en el Diseñador de *Business Process*. Vamos a crear un *business process* simple que llamará nuestra operación:

### 8.1.2. Modificación del contexto de un *business process*
Todos los *business processes* tiene un **contexto**. Se compone de una clase para la solicitud, la clase de la entrada, una clase para la respuesta y la clase de la salida. **Los *business processes* solo tienen una entrada y una salida**. También es posible agregar propiedades.
Como nuestro *business process* solo se utilizará para llamar a nuestra *business operation*, podemos poner la clase del mensaje que hemos creado como clase para la solicitud (no necesitamos una salida ya que solo queremos insertarlo en la base de datos).

Ahora, elegiremos el objetivo de la función Call: nuestra *business operation*. Esa operación, al ser **llamada** tiene una propiedad **callrequest**. Necesitamos vincular esa propiedad callrequest con la solicitud del *business process* (ambos pertenecen a la clase Formation.Msg.FormationInsertRequest) y para ello, hacemos clic en la función Call y utilizaremos el creador de peticiones:

Ahora podemos guardar este *business process* (en el paquete 'Formation.BP' y, por ejemplo, con el nombre 'InsertLocalBDD' o 'Main'). Al igual que las operaciones, pueden crearse instancias de los procesos y se pueden probar mediante la Configuración de la producción, aunque para esto necesitan compilarse previamente (en la pantalla del *Business Process Designer*).
Por ahora, nuestro proceso solo pasa el mensaje a nuestra operación. Vamos a aumentar la complejidad para que el business process tome como entrada una línea de un archivo CSV.
## 8.2. Cómo hacer que el *business process* lea líneas CSV
### 8.2.1. Creación de un mapa de registro
Para leer un archivo y poner su contenido en otro archivo, necesitamos un Mapa de Registros (RM). En el menú [Interoperability > Build] del Portal de Administración hay un servicio para mapear los registros, especializado en archivos CSV:

Crearemos el servicio para elaborar mapas de esta manera:

Ahora deberías tener el siguiente Mapa de Registros:

Ahora que el mapa está creado, debemos generarlo (con el botón *Generate*). También debemos efectuar una Transformación de datos desde el formato del Mapa de registros y un mensaje de inserción.
### 8.2.2. Creación de una transformación de datos
Encontraremos el Generador para la Transformación de datos (DT) en el menú [Interoperability > Builder]. A continuación, crearemos nuestra DT de esta forma (si no encuentras la clase Formation.RM.Csv.Record, tal vez no se generó el Mapa de Registros):

Ahora, podemos mapear los diferentes campos juntos:

### 8.2.3. Añadir la transformación de datos al *business process*
Lo primero que debemos modificar es la clase de la solicitud del *business process*, ya que necesitamos tener una entrada en el Mapa de Registros que creamos.

Entonces, podemos añadir nuestra transformación (el nombre del proceso no cambia nada, a partir de aquí elegimos llamarlo `Main`):

La actividad de transformación tomará la solicitud del *business process* (un registro del archivo CSV, gracias a nuestro servicio para mapear los registros) y la transformará en un mensaje `FormationInsertRequest`. Si deseamos almacenar ese mensaje para enviarlo al *business operation*, necesitamos añadir una propiedad al contexto del *business process*.

Ahora podemos configurar nuestra función de transformación para que tome su entrada como entrada del *business process* y guarde su salida en la propiedad recién creada. El origen y el objetivo de la transformación `RmToMsg` son `request` y `context.Msg`, respectivamente:

Tenemos que hacer lo mismo para `Call BO`. Su entrada, o `callrequest`, es el valor almacenado en `context.msg`:

Al final, el flujo en el *business process* puede representarse de esta manera:

### 8.2.4. Configuración de la producción
Con el signo [+], podemos añadir nuestro nuevo proceso a la producción (si aún no lo hemos hecho). También necesitamos un servicio genérico para utilizar el Mapa de Registros, para ello utilizamos `EnsLib.RecordMap.Service.FileService` (lo añadimos con el botón [+] que está junto a los servicios). A continuación, parametrizamos este servicio:

Ahora deberíamos poder probar nuestro *business process*.
### 8.2.5. Pruebas
Probamos toda la producción de esta manera:

En el menú `System Explorer > SQL`, puedes ejecutar el comando
````sql
SELECT
ID, Name, Salle
FROM Formation_Table.Formation
````
para ver los objetos que acabamos de guardar.
# 9. Obtener acceso a una base de datos externa usando JDBC
En esta sección, crearemos una operación para guardar nuestros objetos en una base de datos externa. Utilizaremos la API de JDBC, así como el otro contenedor Docker que configuramos, con postgre en él.
## 9.1. Creación de nuestra nueva operación
Nuestra nueva operación, en el archivo `Formation/BO/RemoteBDD.cls` es así:
````objectscript
Include EnsSQLTypes
Class Formation.BO.RemoteBDD Extends Ens.BusinessOperation
{
Parameter ADAPTER = "EnsLib.SQL.OutboundAdapter";
Property Adapter As EnsLib.SQL.OutboundAdapter;
Parameter INVOCATION = "Queue";
Method InsertRemoteBDD(pRequest As Formation.Msg.FormationInsertRequest, Output pResponse As Ens.StringResponse) As %Status
{
set tStatus = $$$OK
try{
set pResponse = ##class(Ens.Response).%New()
set ^inc = $I(^inc)
set tInsertSql = "INSERT INTO public.formation (id, nom, salle) VALUES(?, ?, ?)"
$$$ThrowOnError(..Adapter.ExecuteUpdate(.nrows,tInsertSql,^inc,pRequest.Formation.Nom, pRequest.Formation.Salle ))
}
catch exp
{
Set tStatus = exp.AsStatus()
}
Quit tStatus
}
XData MessageMap
{
InsertRemoteBDD
}
}
````
Esta operación es similar a la primera que creamos. Cuando reciba un mensaje del tipo `Formation.Msg.FormationInsertRequest`, utilizará un adaptador para ejecutar solicitudes SQL. Esas solicitudes se enviarán a nuestra base de datos de postgre.
## 9.2. Configuración de la producción
Ahora, desde el Portal de Administración, crearemos una instancia de esa operación (añadiéndola con el signo [+] en la producción).
También necesitaremos añadir el JavaGateway para el controlador JDBC en los servicios. El nombre completo de este servicio es `EnsLib.JavaGateway.Service`.

Ahora necesitamos configurar nuestra operación. Como hemos configurado un contenedor postgre, y conectado su puerto `5432`, los valores que necesitamos en los siguientes parámetros son:
> DSN: `jdbc:postgresql://db:5432/DemoData`
>
> Controlador de JDBC: `org.postgresql.Driver`
>
> JDBC Classpath: `/tmp/iris/postgresql-42.2.14.jar`

Finalmente, necesitamos configurar las credenciales para tener acceso a la base de datos remota. Para ello, necesitamos abrir el Visualizador de credenciales:

Tanto el nombre de usuario como la contraseña son `DemoData`, como lo configuramos en el archivo `docker-compose.yml`.

De vuelta a la producción, podemos añadir `"Postgre"` en el campo [Credential] en la configuración de nuestra operación (debería estar en el menú desplegable). Antes de que podamos probarlo, debemos añadir el JGService a la operación. En la pestaña [Settings], en [Additional Settings]:

## 9.3. Pruebas
Cuando se están haciendo pruebas el registro visual debe mostrar que tuvimos éxito:

Conectamos correctamente una base de datos externa.
## 9.4. Ejercicio
Como ejercicio, podría ser interesante modificar BO.LocalBDD para que devuelva un booleano, que le dirá al *business process* que llame a BO.RemoteBDD dependiendo del valor de ese booleano.
**Sugerencia**: Esto se puede hacer cambiando el tipo de respuesta que devuelve LocalBDD, añadiendo una nueva propiedad al contexto y utilizando la actividad `if` en nuestro *business process*.
## 9.5. Solución
Primero, necesitamos tener una respuesta de nuestra operación LocalBDD. Vamos a crear un nuevo mensaje, en `Formation/Msg/FormationInsertResponse.cls`:
````objectscript
Class Formation.Msg.FormationInsertResponse Extends Ens.Response
{
Property Double As %Boolean;
}
````
Después, cambiamos la respuesta de LocalBDD por esa respuesta y establecemos el valor de su booleano de forma aleatoria (o no):
````objectscript
Method InsertLocalBDD(pRequest As Formation.Msg.FormationInsertRequest, Output pResponse As Formation.Msg.FormationInsertResponse) As %Status
{
set tStatus = $$$OK
try{
set pResponse = ##class(Formation.Msg.FormationInsertResponse).%New()
if $RANDOM(10) < 5 {
set pResponse.Double = 1
}
else {
set pResponse.Double = 0
}
...
````
Ahora crearemos un nuevo proceso (copiado del que hicimos), donde añadiremos una nueva propiedad de contexto, de tipo `%Boolean`:

Esta propiedad se completará con el valor del callresponse.Double de nuestra llamada de operación (necesitaremos establecer [Response Message Class] en nuestra nueva clase de mensaje):

A continuación, añadimos una actividad `if`, con la propiedad `context.Double` como condición:

MUY IMPORTANTE: necesitamos desmarcar **Asynchronous** en la configuración de nuestra LocallBDD Call, o la actividad if se activará antes de que reciba la respuesta booleana.
Finalmente configuramos nuestra actividad de llamada como un objetivo RemoteBDD BO:

Para completar la actividad if, necesitamos arrastrar otro conector desde la salida del `if` al triángulo `join` que se encuentra debajo. Como no haremos nada si el valor booleano es falso, dejaremos este conector vacío.
Después de compilar y crear instancias, deberíamos poder probar nuestro nuevo proceso. Para ello, necesitamos cambiar `Target Config Name` de nuestro servicio de archivos.
En el seguimiento, deberíamos tener aproximadamente la mitad de los objetos leídos en el csv guardados también en la base de datos remota.
# 10. Servicio REST
En esta parte, crearemos y utilizaremos un Servicio REST.
## 10.1. Creación del servicio
Para crear un servicio REST, necesitamos una clase que extienda %CSP.REST, en `Formation/REST/Dispatch.cls` tenemos:
````objectscript
Class Formation.REST.Dispatch Extends %CSP.REST
{
/// Ignore any writes done directly by the REST method.
Parameter IgnoreWrites = 0;
/// By default convert the input stream to Unicode
Parameter CONVERTINPUTSTREAM = 1;
/// The default response charset is utf-8
Parameter CHARSET = "utf-8";
Parameter HandleCorsRequest = 1;
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
}
/// Get this spec
ClassMethod import() As %Status
{
set tSc = $$$OK
Try {
set tBsName = "Formation.BS.RestInput"
set tMsg = ##class(Formation.Msg.FormationInsertRequest).%New()
set body = $zcvt(%request.Content.Read(),"I","UTF8")
set dyna = {}.%FromJSON(body)
set tFormation = ##class(Formation.Obj.Formation).%New()
set tFormation.Nom = dyna.nom
set tFormation.Salle = dyna.salle
set tMsg.Formation = tFormation
$$$ThrowOnError(##class(Ens.Director).CreateBusinessService(tBsName,.tService))
$$$ThrowOnError(tService.ProcessInput(tMsg,.output))
} Catch ex {
set tSc = ex.AsStatus()
}
Quit tSc
}
}
````
Esta clase contiene una ruta para importar un objeto, vinculado al verbo POST:
````xml
````
El método de importación creará un mensaje que se enviará a un *business service*.
## 10.2. Añadir nuestro *Business Service* (BS)
Vamos a crear una clase genérica que dirigirá todas sus solicitudes hacia `TargetConfigNames`. Este objetivo se configurará cuando creemos una instancia de este servicio. En el archivo `Formation/BS/RestInput.cls` tenemos:
```objectscript
Class Formation.BS.RestInput Extends Ens.BusinessService
{
Property TargetConfigNames As %String(MAXLEN = 1000) [ InitialExpression = "BuisnessProcess" ];
Parameter SETTINGS = "TargetConfigNames:Basic:selector?multiSelect=1&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}";
Method OnProcessInput(pDocIn As %RegisteredObject, Output pDocOut As %RegisteredObject) As %Status
{
set status = $$$OK
try {
for iTarget=1:1:$L(..TargetConfigNames, ",") {
set tOneTarget=$ZStrip($P(..TargetConfigNames,",",iTarget),"W") Continue:""=tOneTarget
$$$ThrowOnError(..SendRequestSync(tOneTarget,pDocIn,.pDocOut))
}
} catch ex {
set status = ex.AsStatus()
}
Quit status
}
}
```
Volviendo a la configuración de la producción, añadimos el servicio de la manera habitual. En [Target Config Names], ponemos nuestro BO LocalBDD:

Para utilizar este servicio, necesitamos publicarlo. Para ello, usamos el menú [Edit Web Application]:

## 10.3. Pruebas
Por último, podemos probar nuestro servicio con cualquier tipo de cliente REST:

# Conclusión
A través de esta formación, hemos creado una producción que es capaz de leer líneas desde un archivo csv y guardar los datos leídos tanto en la base de datos de IRIS como en una base de datos externa, utilizando JDBC. También añadimos un servicio REST para utilizar el verbo POST para guardar nuevos objetos.
Hemos descubierto los principales elementos del *framework* de interoperabilidad de InterSystems.
Lo hemos hecho utilizando Docker, VSCode y el Portal de Administración de InterSystems IRIS.
Artículo
Jose-Tomas Salvador · 7 jul, 2021
**Palabras clave** PyODBC, unixODBC, IRIS, IntegratedML, Jupyter Notebook, Python 3
## **Propósito**
Hace unos meses traté el tema de la "[conexión con JDBC desde Python a la base de datos de IRIS](https://es.community.intersystems.com/post/conexi%C3%B3n-con-jdbc-desde-python-la-base-de-datos-de-iris-una-nota-r%C3%A1pida)", y desde entonces utilicé ese artículo con más frecuencia que mi propia nota oculta en mi PC. Por eso, traigo aquí otra nota de 5 minutos sobre cómo hacer una "conexión con JDBC desde Python a la base de datos de IRIS". ODBC y PyODBC parecen bastante fáciles de configurar en un cliente de Windows, sin embargo, siempre me atasco un poco en la configuración de un cliente unixODBC y PyODBC en un servidor de estilo Linux/Unix. ¿Existe un enfoque tan sencillo y consistente como se supone que debe ser para hacer que el trabajo de instalación de PyODBC/unixODBC funcione en un cliente linux estándar sin ninguna instalación de IRIS, contra un servidor IRIS remoto?
## **Alcance**
Hace poco tuve que dedicar un tiempo a hacer funcionar desde cero una demo de PyODBC dentro de Jupyter Notebook en un entorno Docker en Linux. De ahí me surgió la idea de realizar esta nota detallada, para futuras consultas.
#### **En el alcance:**
En esta nota vamos a tratar los siguientes componentes:
PyODBC sobre unixODBC
El servidor de Jupyter Notebook con Tensorflow 2.2 y Python 3
Servidor IRIS2020.3 CE con IntegratedML, incluyendo datos de prueba de ejemplo.
dentro de este entorno
El motor Docker con Docker-compose sobre AWS Ubuntu 16.04
Docker Desktop para MacOS, y la Docker Toolbos for Windows 10 también están probadas
#### **Fuera del alcance**:
De nuevo, los aspectos no funcionales no se evalúan en este entorno de demo. Son importantes y pueden tratarse por separado, como:
Seguridad y auditoría de extremo a extremo.
Rendimiento y escalabilidad
Licencias y soporte, etc.
## **Entorno**
Se puede usar cualquier imagen en Docker para Linux en las configuraciones y los pasos de prueba que se indican a continuación, pero una forma sencilla de configurar un entorno de este tipo en 5 minutos es:
1. **Clonar** en Git esta [plantilla de demostración](https://github.com/tom-dyar/integratedml-demo-template)
2. Ejecutar "**docker-compose up -d**" en el directorio clonado que contiene el archivo docker-compose.yml. Simplemente creará un entorno de demostración, como se muestra en la siguiente imagen, de 2 contenedores. Uno para el servidor de Jupyter Notebook como cliente PyODBC, y otro para un servidor IRIS2020.3 CE.
En el entorno anterior, el tf2jupyter solo contiene una configuración de cliente "Python sobre JDBC", todavía no contiene ninguna configuración de cliente ODBC o PyODBC. Así que ejecutaremos los siguientes pasos para configurarlo, directamente desde Jupyter Notebook para que sea autoexplicativo.
## **Pasos**
Ejecuté las siguientes configuraciones y pruebas en un servidor AWS Ubuntu 16.04. Mi colega @Thomas.Dyar los ejecutó en MacOS. También lo probé brevemente en Docker Toolbox for Windows. Pero haznos saber si encuentras algún problema. Los siguientes pasos se pueden automatizar de forma sencilla en tu Dockerfile. Lo grabé manualmente aquí en caso de que se me olvide cómo se hacía después de unos meses.
### **1. Documentación oficial:**
Soporte de ODBC para IRIS
Cómo definir la fuente de datos ODBC en Unix
Soporte PyODBC para IRIS
### **2. Conectarse al servidor Jupyter**
Utilicé el SSH tunneling de Putty en forma local en el puerto 22 de AWS Ubuntu remoto, después mapeé al puerto 8896 como en la imagen anterior. (Por ejemplo, en el entorno local de Docker también se puede utilizar http directamente en la IP:8896 del equipo Docker).
### **3. Ejecutar la instalación de ODBC desde Jupyter Notebook**
Ejecuta la siguiente línea de comando directamente desde una celda en Jupyter:
!apt-get update
!apt-get install gcc
!apt-get install -y tdsodbc unixodbc-dev
!apt install unixodbc-bin -y
!apt-get clean -y
Instalará el compilador gcc (incluyendo g++), FreeTDS, unixODBC y unixodbc-dev , que son necesarios para volver a compilar el *driver* PyODBC en el siguiente paso. Este paso no es necesario en un servidor nativo de Windows o PC para la instalación de PyODBC.
### **4. Ejecutar la instalación de PyODBC desde Jupyter**
!pip install pyodbc
Collecting pyodbc
Downloading pyodbc-4.0.30.tar.gz (266 kB)
|████████████████████████████████| 266 kB 11.3 MB/s eta 0:00:01
Building wheels for collected packages: pyodbc
Building wheel for pyodbc (setup.py) ... done
Created wheel for pyodbc: filename=pyodbc-4.0.30-cp36-cp36m-linux_x86_64.whl size=273453 sha256=b794c35f41e440441f2e79a95fead36d3aebfa74c0832a92647bb90c934688b3
Stored in directory: /root/.cache/pip/wheels/e3/3f/16/e11367542166d4f8a252c031ac3a4163d3b901b251ec71e905
Successfully built pyodbc
Installing collected packages: pyodbc
Successfully installed pyodbc-4.0.30
Lo anterior es la instalación mínima de PIP para esta demostración de Docker. En la [documentación oficial](https://irisdocs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=BNETODBC_support#BNETODBC_support_pyodbc), para la "Instalación de MacOS X", se proporcionan instrucciones para una instalación de PIP más detallada.
### **5. Volver a configurar los archivos y enlaces ODBC INI en Linux:**
Ejecuta las siguientes líneas de comando para volver a crear los enlaces **odbcinst.ini** y **odbc.ini**
!rm /etc/odbcinst.ini !rm /etc/odbc.ini
!ln -s /tf/odbcinst.ini /etc/odbcinst.ini !ln -s /tf/odbc.ini /etc/odbc.ini
Nota: La razón de realizar lo anterior es que **los pasos 3 y 4 normalmente crearían 2 archivos ODBC en blanco (por lo tanto, no válidos) en el directorio \etc\.** A diferencia de la instalación de Windows, estos archivos ini en blanco causan problemas, por lo tanto, debemos eliminarlos y luego simplemente volver a crear un enlace a los archivos ini reales proporcionados en un volumen Docker mapeado: /tf/odbcinst.ini, y /tf/odbc.ini Revisemos estos 2 archivos ini - en este caso, son la forma más simple para las configuraciones ODBC de Linux:
!cat /tf/odbcinst.ini
[InterSystems ODBC35]
UsageCount=1
Driver=/tf/libirisodbcu35.so
Setup=/tf/libirisodbcu35.so
SQLLevel=1
FileUsage=0
DriverODBCVer=02.10
ConnectFunctions=YYN
APILevel=1
DEBUG=1
CPTimeout=<not Pooled>
!cat /tf/odbc.ini
[IRIS PyODBC Demo]
Driver=InterSystems ODBC35
Protocol=TCP
Host=irisimlsvr
Port=51773
Namespace=USER
UID=SUPERUSER
Password=SYS
Description=Sample namespace
Query Timeout=0
Static Cursors=0
Los archivos anteriores están pre-configurados y se proporcionan en la unidad mapeada. Esto se refiere al driver **libirisodbcu35.so,** que también se puede conseguir en la instancia del contenedor del servidor IRIS (en su directorio {iris-installation}/bin). Por lo tanto, para que la instalación ODBC anterior funcione, **deben existir estos 3 archivos** en la unidad mapeada (o en cualquier unidad de Linux) **con los permisos de los archivos adecuados**:
libirisodbcu35.so
odbcinst.ini
odbc.ini
### **6. Verificar la instalación de PyODBC **
!odbcinst -j
unixODBC 2.3.4
DRIVERS............: /etc/odbcinst.ini
SYSTEM DATA SOURCES: /etc/odbc.ini
FILE DATA SOURCES..: /etc/ODBCDataSources
USER DATA SOURCES..: /root/.odbc.ini
SQLULEN Size.......: 8
SQLLEN Size........: 8
SQLSETPOSIROW Size.: 8
import pyodbc print(pyodbc.drivers())
['InterSystems ODBC35']
Por ahora, las salidas anteriores indicarán que el *driver* ODBC tiene los enlaces válidos. Deberíamos poder ejecutar alguna prueba de ODBC de Python en Jupyter Notebook.
### **7. Ejecutar la conexión ODBC de Python en las muestras de IRIS:**
import pyodbc import time
### 1. Get an ODBC connection
#input("Hit any key to start")
dsn = 'IRIS PyODBC Demo'
server = 'irisimlsvr' #IRIS server container or the docker machine's IP
port = '51773' # or 8091 if docker machine IP is used
database = 'USER'
username = 'SUPERUSER'
password = 'SYS'
#cnxn = pyodbc.connect('DSN='+dsn+';') # use the user DSN defined in odbc.ini, or use the connection string below
cnxn = pyodbc.connect('DRIVER={InterSystems ODBC35};SERVER='+server+';PORT='+port+';DATABASE='+database+';UID='+username+';PWD='+ password)
###ensure it reads strings correctly.
cnxn.setdecoding(pyodbc.SQL_CHAR, encoding='utf8')
cnxn.setdecoding(pyodbc.SQL_WCHAR, encoding='utf8')
cnxn.setencoding(encoding='utf8')
### 2. Get a cursor; start the timer
cursor = cnxn.cursor()
start= time.clock()
### 3. specify the training data, and give a model name
dataTable = 'DataMining.IrisDataset'
dataTablePredict = 'Result12'
dataColumn = 'Species'
dataColumnPredict = "PredictedSpecies"
modelName = "Flower12" #chose a name - must be unique in server end
### 4. Train and predict
#cursor.execute("CREATE MODEL %s PREDICTING (%s) FROM %s" % (modelName, dataColumn, dataTable))
#cursor.execute("TRAIN MODEL %s FROM %s" % (modelName, dataTable))
#cursor.execute("Create Table %s (%s VARCHAR(100), %s VARCHAR(100))" % (dataTablePredict, dataColumnPredict, dataColumn))
#cursor.execute("INSERT INTO %s SELECT TOP 20 PREDICT(%s) AS %s, %s FROM %s" % (dataTablePredict, modelName, dataColumnPredict, dataColumn, dataTable))
#cnxn.commit()
### 5. show the predict result
cursor.execute("SELECT * from %s ORDER BY ID" % dataTable) #or use dataTablePredict result by IntegratedML if you run step 4 above
row = cursor.fetchone()
while row:
print(row)
row = cursor.fetchone()
### 6. CLose and clean
cnxn.close()
end= time.clock()
print ("Total elapsed time: ")
print (end-start)
(1, 1.4, 0.2, 5.1, 3.5, 'Iris-setosa')
(2, 1.4, 0.2, 4.9, 3.0, 'Iris-setosa')
(3, 1.3, 0.2, 4.7, 3.2, 'Iris-setosa')
(4, 1.5, 0.2, 4.6, 3.1, 'Iris-setosa')
(5, 1.4, 0.2, 5.0, 3.6, 'Iris-setosa')
... ...
... ...
... ...
(146, 5.2, 2.3, 6.7, 3.0, 'Iris-virginica')
(147, 5.0, 1.9, 6.3, 2.5, 'Iris-virginica')
(148, 5.2, 2.0, 6.5, 3.0, 'Iris-virginica')
(149, 5.4, 2.3, 6.2, 3.4, 'Iris-virginica')
(150, 5.1, 1.8, 5.9, 3.0, 'Iris-virginica')
Total elapsed time:
0.023873000000000033
Un par de consejos:
1. **cnxn = pyodbc.connect()** - en el entorno Linux, la cadena de conexión expresada en esta llamada debe ser literalmente correcta y sin espacios.
2. Establece la codificación de la conexión correctamente con, por ejemplo, utf8. En este caso, el valor predeterminado no funcionaría para las cadenas.
3. **libirisodbcu35.so** - lo ideal es que este driver esté estrechamente alineado con la versión del servidor IRIS remoto.
## **Siguiente**
Ahora tenemos un entorno Docker con un Jupyter notebook que incluye Python3 y Tensorflow2.2 (sin GPU) a través de una conexión PyODBC (así como JDBC) en un servidor IRIS remoto. Deben funcionar para todas las sintaxis SQL personalizadas, como las que son propiedad de IntegratedML en IRIS, así que ¿por qué no explorar un poco más en IntegratedML y ser creativo con su forma SQL de conducir ciclos de vida ML?
Además, me gustaría que pudiéramos volver a tratar o recapitular el enfoque más sencillo sobre IRIS nativo o incluso Magic SQL en el entorno Python, que se conectará con el servidor IRIS. Y, el extraordinario [Python Gateway](https://github.com/intersystems-community/PythonGateway) está disponible ahora, así que incluso podemos intentar invocar las aplicaciones y servicios externos de Python ML, directamente desde el servidor IRIS. Me gustaría que pudiéramos probar más sobre eso también.
##**Anexo**
El archivo del bloc de notas anterior se revisará en este repositorio de Github, y también en Open Exchange.
Artículo
Ricardo Paiva · 28 ene, 2022
En este artículo, crearemos una configuración de IRIS con alta disponibilidad utilizando implementaciones en Kubernetes con almacenamiento persistente distribuido en vez del "tradicional" par de mirror de IRIS. Esta implementación sería capaz de tolerar fallos relacionados con la infraestructura, por ejemplo, fallos en los nodos, en el almacenamiento y en la Zona de Disponibilidad. El enfoque descrito reduce en gran medida la complejidad de la implementación, a costa de un Tiempo Objetivo de Recuperación (RTO, Recovery Time Objective) ligeramente mayor.
Figura 1: Mirroring tradicional vs Kubernetes con Almacenamiento Distribuido
Todos los códigos fuente utilizados en este artículo están disponibles en https://github.com/antonum/ha-iris-k8s TL;DR
Suponiendo que tienes un clúster de 3 nodos funcionando y que estás un poco familiarizado con Kubernetes, sigue este script:
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr.yaml
Si no estás seguro de lo que significan las dos líneas anteriores o no tienes el sistema adecuado para ejecutarlas, ve al apartado "Requisitos de alta disponibilidad". Explicaremos las cosas con más detalle conforme avancemos.
La primera línea instala Longhorn, un almacenamiento distribuido de código abierto de Kubernetes. La segunda instala una implementación de InterSystems IRIS, usando un volumen basado en Longhorn para SYS duradero.
Espera hasta que todos los pods estén en funcionamiento. kubectl get pods -A
Ahora deberías tener acceso al Portal de Administración de IRIS en http://<IRIS Service Public IP>:52773/csp/sys/%25CSP.Portal.Home.zen (la contraseña predeterminada es "SYS") y a la línea de comandos de IRIS mediante:
kubectl exec -it iris-podName-xxxx -- iris session iris
Simulación del error
Ahora comenzaremos a jugar un poco. Pero, antes de hacerlo, intenta añadir algunos datos en la base de datos y asegúrate de que siguen allí cuando IRIS esté de nuevo online.
kubectl exec -it iris-6d8896d584-8lzn5 -- iris session iris
USER>set ^k8stest($i(^k8stest))=$zdt($h)_" running on "_$system.INetInfo.LocalHostName()
USER>zw ^k8stest
^k8stest=1
^k8stest(1)="01/14/2021 14:13:19 running on iris-6d8896d584-8lzn5"
Nuestra "ingeniería del caos" empieza aquí:
# Stop IRIS - Container will be restarted automatically
kubectl exec -it iris-6d8896d584-8lzn5 -- iris stop iris quietly
# Delete the pod - Pod will be recreated
kubectl delete pod iris-6d8896d584-8lzn5
# "Force drain" the node, serving the iris pod - Pod would be recreated on another node
kubectl drain aks-agentpool-29845772-vmss000001 --delete-local-data --ignore-daemonsets --force
# Delete the node - Pod would be recreated on another node
# well... you can't really do it with kubectl. Find that instance or VM and KILL it.
# if you have access to the machine - turn off the power or disconnect the network cable. Seriously!
Requisitos de alta disponibilidad
Estamos creando un sistema que pueda tolerar un error en las siguientes estructuras:
Instancia de IRIS dentro del contenedor/máquina virtual IRIS - Nivel del error.
Error en el Pod/Contenedor.
Falta de disponibilidad temporal en el nodo del clúster individual. Un buen ejemplo sería la Zona de Disponibilidad temporalmente off-line.
Error permanente en el nodo o en el disco del clúster individual.
Básicamente, los escenarios que hemos probado en la sección "Simulación del error".
Si se produce alguno de estos errores, el sistema debe ponerse online sin intervención humana y sin pérdida de datos. Técnicamente hay límites sobre qué persistencia de datos garantiza. El propio IRIS puede proporcionarlos basándose en el Ciclo del journal y en el uso de transacciones dentro de una aplicación: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=GCDI_journal#GCDI_journal_writecycle. De cualquier forma, hablamos de menos de dos segundos para el RPO (Recovery Point Objective, "Punto objetivo de recuperación ").
Otros componentes del sistema (el servicio API de Kubernetes, la base de datos del ETCD, el servicio de LoadBalancer, DNS y otros) están fuera de nuestros objetivos y suelen ser gestionados por el Servicio Administrado de Kubernetes, como Azure AKS o AWS EKS, por lo que suponemos que ya tienen una alta disponibilidad.
Otra forma de verlo es que nosotros somos responsables de solucionar los fallos individuales de cálculo y de los componentes de almacenamiento, y asumimos que el resto está a cargo del proveedor de la infraestructura o de la nube.
Arquitectura
Para la alta disponibilidad en InterSystems IRIS, la recomendación habitual es utilizar mirroring. Con mirroring, se tienen dos instancias de IRIS siempre activas, replicando datos de forma sincronizada. Cada nodo mantiene una copia completa de la base de datos y si el nodo primario se cae, los usuarios pueden reconectarse al nodo de backup. Esencialmente, con el enfoque mirroring, IRIS es responsable de la redundancia tanto de cálculo como de almacenamiento.
Con los mirrors en diferentes zonas de disponibilidad, mirroring ofrece la redundancia necesaria tanto para los errores de cálculo como de almacenamiento, y permite un excelente RTO (Tiempo Objetivo de Recuperación o el tiempo que tarda un sistema en volver a estar online después de un fallo) de solo unos cuantos segundos. Aquí podéis acceder a la plantilla de implementación para Mirrored IRIS en la nube de AWS: https://community.intersystems.com/post/intersystems-iris-deployment%C2%A0guide-aws%C2%A0using-cloudformation-template
El lado menos bonito de mirroring es la complejidad de su configuración, la realización de procesos de backup o restauración, y la falta de replicación para las configuraciones de seguridad y los archivos locales que no tienen relación con la base de datos.
Los orquestadores de contenedores como Kubernetes (espera, es 2021... ¡¿queda algún otro?!) proporcionan redundancia de cálculo a través de la implementación de objetos, reiniciando automáticamente el Pod/Contenedor de IRIS en caso de fallo. Por eso solo se ve un nodo de IRIS ejecutándose en el diagrama de arquitectura de Kubernetes. En vez de mantener un segundo nodo de IRIS siempre funcionando, subcontratamos la disponibilidad de cálculo a Kubernetes. Kubernetes se asegurará de que el pod de IRIS sea recreado en caso de que el pod original falle por cualquier razón.
Figura 2. Escenario de la tolerancia a fallos
Por ahora todo va bien… Si el nodo de IRIS falla, Kubernetes simplemente crea uno nuevo. Dependiendo del clúster, se necesitan entre 10 y 90 segundos para que IRIS se vuelva a conectar después del fallo de cálculo. Esto es un paso atrás comparado con el par de segundos del mirroring, pero es algo que se puede tolerar en el improbable caso de que haya una interrupción; y la recompensa es la gran reducción de la complejidad. No hay mirroring para configurar. No hay que preocuparse por la configuración de seguridad ni por la replicación de los archivos.
Francamente, si inicias sesión dentro del contenedor, ejecutando IRIS en Kubernetes, ni siquiera notarás que se está ejecutando dentro del entorno de alta disponibilidad. Todo se ve y parece como una sencilla instancia de implementación en IRIS.
Espera... ¿qué pasa con el almacenamiento? Al final, estamos lidiando con una base de datos… Sea cual sea el escenario de respuesta ante fallos que podamos imaginar, nuestro sistema debería encargarse también de la persistencia de los datos. Mirroring depende del cálculo, a nivel local en el nodo de IRIS. Si el nodo se vuelve no operativo o simplemente deja de estar disponible temporalmente, también lo hace el almacenamiento de ese nodo. Por eso en la configuración de mirroring, IRIS se encarga de replicar bases de datos a nivel de IRIS.
Necesitamos un almacenamiento que no solo pueda preservar el estado de la base de datos al reiniciar el contenedor, sino que también pueda proporcionar redundancia en caso de que un nodo o un segmento entero de la red (Zona de Disponibilidad) se caiga. Hasta hace unos años, no había una respuesta fácil para esto. Como puedes adivinar por la imagen anterior, ya tenemos esa respuesta. Se llama almacenamiento distribuido en contenedores.
El almacenamiento distribuido extrae los volúmenes del host y los presenta como un almacenamiento conjunto disponible para cada nodo del clúster K8s. En este artículo utilizamos Longhorn https://longhorn.io - es gratuito, de código abierto y bastante fácil de instalar. Pero también puedes echar un vistazo a otros programas, como OpenEBS, Portworx y StorageOS, que ofrecen la misma funcionalidad. Rook Ceph es otro proyecto de incubación del CNCF a tener en cuenta. La gama alta de las opciones serían las soluciones de almacenamiento empresarial, como NetApp, PureStorage y otras.
Guía paso a paso
En la sección TL;DR acabamos de instalar todo de una sola vez. El Anexo B te dirigirá paso a paso en los procesos de instalación y validación.
Almacenamiento de Kubernetes
Vamos a ir hacia atrás un momento y vamos a hablar de los contenedores y el almacenamiento en general y de cómo IRIS se integra en todo esto.
De forma predeterminada, todos los datos dentro del contenedor son efímeros. Cuando el contenedor se borra, los datos desaparecen. Para tener almacenamiento persistente en Docker y que no se elimine la información al borrar el contenedor, es necesario utilizar "volúmenes de datos". Básicamente, permite mostrar al contenedor el directorio que se encuentra en el sistema operativo del host.
docker run --detach
--publish 52773:52773
--volume /data/dur:/dur
--env ISC_DATA_DIRECTORY=/dur/iconfig
--name iris21 --init intersystems/iris:2020.3.0.221.0
En el ejemplo anterior, iniciamos el contenedor IRIS y hacemos que el directorio local del host "/data/dur" sea accesible al contenedor en el punto de montaje "/dur". Por lo tanto, si el contenedor está almacenando algo dentro de este directorio, se conservará y estará disponible para su uso cuando inicie de nuevo el contenedor.
Desde el punto de vista de IRIS, podemos dar indicaciones a IRIS para que almacene en el directorio específico todos los datos que necesita conservar cuando reinicie el contenedor, especificando ISC_DATA_DIRECTORY. Durable SYS es el nombre de la función de IRIS que podrías necesitar buscar en la documentación: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=ADOCK#ADOCK_iris_durable_running
En Kubernetes la sintaxis es diferente, pero los conceptos son los mismos.
Esta es la implementación básica de Kubernetes para IRIS.
apiVersion: apps/v1
kind: Deployment
metadata:
name: iris
spec:
selector:
matchLabels:
app: iris
strategy:
type: Recreate
replicas: 1
template:
metadata:
labels:
app: iris
spec:
containers:
- image: store/intersystems/iris-community:2020.4.0.524.0
name: iris
env:
- name: ISC_DATA_DIRECTORY
value: /external/iris
ports:
- containerPort: 52773
name: smp-http
volumeMounts:
- name: iris-external-sys
mountPath: /external
volumes:
- name: iris-external-sys
persistentVolumeClaim:
claimName: iris-pvc
En la implementación de la especificación anterior, la parte "volúmenes" lista los volúmenes de almacenamiento. Pueden estar disponibles fuera del contenedor, por medio de persistentVolumeClaim como "iris-pvc". volumeMounts muestra este volumen dentro del contenedor. "iris-external-sys" es el identificador que vincula el montaje del volumen con el volumen específico. En realidad, podemos tener varios volúmenes y este nombre se utiliza solo para distinguir uno de otro. Si quieres, puedes llamarlo "Steve".
La ya conocida variable de entorno ISC_DATA_DIRECTORY dirige a IRIS para que utilice un punto de montaje específico para almacenar todos los datos que necesiten conservarse tras el reinicio del contenedor.
Ahora, echemos un vistazo a la reclamación de volúmenes persistente iris-pvc.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: iris-pvc
spec:
storageClassName: longhorn
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Es bastante sencillo. Se solicitan 10 gigabytes, montables como lectura/escritura en un solo nodo, utilizando la clase de almacenamiento de "longhorn".
La clase storageClassName: longhorn es realmente crítica aquí.
Veamos qué clases de almacenamiento están disponibles en mi clúster AKS:
kubectl get StorageClass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
azurefile kubernetes.io/azure-file Delete Immediate true 10d
azurefile-premium kubernetes.io/azure-file Delete Immediate true 10d
default (default) kubernetes.io/azure-disk Delete Immediate true 10d
longhorn driver.longhorn.io Delete Immediate true 10d
managed-premium kubernetes.io/azure-disk Delete Immediate true 10d
Hay algunas clases de almacenamiento en Azure, instaladas de forma predeterminada, y una en Longhorn que instalamos como parte del primer comando:
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
Si omites #storageClassName: longhorn en la definición de la reclamación de volúmenes persistente, utilizará la clase de almacenamiento, actualmente marcada como "predeterminada", la cual es un disco normal de Azure.
Para ilustrar por qué necesitamos el almacenamiento distribuido, vamos a repetir los experimentos de "ingeniería del caos" que describimos al principio del artículo sin el almacenamiento de Longhorn. Los dos primeros escenarios (parar IRIS y eliminar el Pod) se completarían correctamente y los sistemas volverían al estado operativo. Intentar vaciar o eliminar el nodo llevaría al sistema a un estado de error.
#forcefully drain the node
kubectl drain aks-agentpool-71521505-vmss000001 --delete-local-data --ignore-daemonsets
kubectl describe pods
...
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 57s (x9 over 2m41s) default-scheduler 0/3 nodes are available: 1 node(s) were unschedulable, 2 node(s) had volume node affinity conflict.
Esencialmente, Kubernetes intentaría reiniciar el pod de IRIS en el clúster, pero el nodo donde se inició originalmente no está disponible y los otros dos nodos tienen un "conflicto de afinidad con el nodo del volumen". Con este tipo de almacenamiento, el volumen está disponible solo en el nodo que se creó originalmente, ya que básicamente está vinculado al disco disponible en el host del nodo.
Con Longhorn como clase de almacenamiento, tanto los experimentos "forzar el vaciado" como "eliminar el nodo" tuvieron éxito, y el pod de IRIS vuelve a funcionar en poco tiempo. Para lograrlo, Longhorn toma el control sobre el almacenamiento disponible en los 3 nodos del clúster y replica los datos a través de los tres nodos. Longhorn repara rápidamente el almacenamiento del clúster si uno de los nodos deja de estar disponible de forma permanente. En nuestro escenario “eliminar el nodo”, el pod de IRIS se reinicia inmediatamente en otro nodo usando dos réplicas restantes del volumen. A continuación, AKS proporciona un nuevo nodo para sustituir el que perdió y tan pronto como esté listo Longhorn entra en acción y reconstruye los datos necesarios en el nuevo nodo. Todo es automático, no necesita tu participación.
Figura 3. Longhorn reconstruyendo el volumen de la replicación en el nodo reemplazado
Más información sobre la implementación de K8s
Echemos un vistazo a algunos otros aspectos de nuestra implementación:
apiVersion: apps/v1
kind: Deployment
metadata:
name: iris
spec:
selector:
matchLabels:
app: iris
strategy:
type: Recreate
replicas: 1
template:
metadata:
labels:
app: iris
spec:
containers:
- image: store/intersystems/iris-community:2020.4.0.524.0
name: iris
env:
- name: ISC_DATA_DIRECTORY
value: /external/iris
- name: ISC_CPF_MERGE_FILE
value: /external/merge/merge.cpf
ports:
- containerPort: 52773
name: smp-http
volumeMounts:
- name: iris-external-sys
mountPath: /external
- name: cpf-merge
mountPath: /external/merge
livenessProbe:
initialDelaySeconds: 25
periodSeconds: 10
exec:
command:
- /bin/sh
- -c
- "iris qlist iris | grep running"
volumes:
- name: iris-external-sys
persistentVolumeClaim:
claimName: iris-pvc
- name: cpf-merge
configMap:
name: iris-cpf-merge
Estrategia: Recreate, replicas: 1 le dice a Kubernetes que en cualquier momento debe mantener una y exactamente una instancia del pod de IRIS en ejecución. Esto es de lo que se encarga nuestro escenario "eliminar el pod".
livenessProbe. Esta sección se asegura de que IRIS siempre esté dentro del contenedor y gestione el escenario "IRIS se ha caído". initialDelaySeconds permite un cierto periodo de gracia para que IRIS comience. Es posible que quieras aumentarlo si IRIS está tardando mucho tiempo en iniciar tu implementación.
CPF MERGE es una función de IRIS que te permite modificar el contenido del archivo de configuración iris.cpf al iniciar el contenedor. Consulta https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=RACS_cpf#RACS_cpf_edit_merge para más información. En este ejemplo estoy usando Kubernetes Config Map para administrar el contenido del archivo merge: https://github.com/antonum/ha-iris-k8s/blob/main/iris-cpf-merge.yaml Aquí ajustamos los buffers globales y los valores de gmheap, utilizados por la instancia de IRIS, pero todo lo que puedes encontrar en el archivo iris.cpf es posible. Incluso puedes cambiar la contraseña predeterminada de IRIS usando el campo "PasswordHash" en el archivo CPF Merge. Más información en: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=ADOCK#ADOCK_iris_images_password_auth
Además de la reclamación de volúmenes persistente https://github.com/antonum/ha-iris-k8s/blob/main/iris-pvc.yaml, implementación https://github.com/antonum/ha-iris-k8s/blob/main/iris-deployment.yaml y ConfigMap con contenido de CPF Merge https://github.com/antonum/ha-iris-k8s/blob/main/iris-cpf-merge.yaml, nuestra puesta en marcha necesita un servicio que exponga la implementación de IRIS en el internet público: https://github.com/antonum/ha-iris-k8s/blob/main/iris-svc.yaml
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
iris-svc LoadBalancer 10.0.18.169 40.88.123.45 52773:31589/TCP 3d1h
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 10d
La IP externa del iris-svc se puede utilizar para acceder al Portal de administración de IRIS a través de: http://40.88.123.45:52773/csp/sys/%25CSP.Portal.Home.zen. La contraseña predeterminada es "SYS".
Copia de seguridad/restauración y escalado del almacenamiento
Longhorn ofrece una interfaz de usuario basada en la web para configurar y administrar los volúmenes.
Identifica el pod, ejecutando el componente longhorn-ui y establece el reenvío de puertos con kubectl:
kubectl -n longhorn-system get pods
# note the longhorn-ui pod id.
kubectl port-forward longhorn-ui-df95bdf85-gpnjv 9000:8000 -n longhorn-system
La interfaz de usuario de Longhorn estará disponible en: http://localhost:9000
Figura 4. Interfaz de usuario de Longhorn
Además de la alta disponibilidad, la mayoría de las soluciones de almacenamiento para contenedores y Kubernetes ofrecen opciones útiles para copias de seguridad, snapshots y recuperación. Los detalles son específicos de la implementación, pero lo habitual es que la copia de seguridad esté asociada con VolumeSnapshot. Así es para Longhorn. Dependiendo de tu versión de Kubernetes y de tu proveedor, es posible que también necesites instalar el volumen de snapshotter https://github.com/kubernetes-csi/external-snapshotter
"iris-volume-snapshot.yaml" es un ejemplo de ese snapshot de volumen. Antes de utilizarlo, es necesario configurar las copias de seguridad en el bucket S3 o en el volumen del sistema de archivos de red (NFS) en Longhorn. https://longhorn.io/docs/1.0.1/snapshots-and-backups/backup-and-restore/set-backup-target/
# Take crash-consistent backup of the iris volume
kubectl apply -f iris-volume-snapshot.yaml
En el caso de IRIS, se recomienda ejecutar Freeze externo antes de tomar la copia de seguridad/snapshot y después Thaw. Consulta los detalles aquí: https://docs.intersystems.com/irisforhealthlatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=Backup.General#ExternalFreeze
Para aumentar el tamaño del volumen de IRIS - Ajusta la solicitud de almacenamiento en la reclamación de volúmenes persistente (archivo "iris-pvc.yaml"), utilizada por IRIS.
...
resources:
requests:
storage: 10Gi #change this value to required
A continuación, vuelve a aplicar la especificación de la reclamación de volúmenes persistente (PVC). Longhorn no puede aplicar este cambio mientras el volumen esté conectado al pod que está en ejecución. Cambia temporalmente el recuento de réplicas hasta cero en la implementación, para poder aumentar el tamaño del volumen.
Alta disponibilidad: Resumen
Al principio del artículo, establecimos algunos criterios para la alta disponibilidad. Esto es lo que logramos con esta arquitectura:
Dominio de fallos
Mitigados automáticamente por
Instancia de IRIS dentro del contenedor/máquina virtual. IRIS: Nivel del fallo
La implementación Liveness probe reinicia el contenedor en caso de que IRIS no funcione
Fallo en el Pod/Contenedor.
La implementación recrea el Pod
Falta de disponibilidad temporal en el nodo del clúster individual. Un buen ejemplo sería la Zona de disponibilidad que está off-line.
La implementación recrea el pod en otro nodo. Longhorn hace que los datos estén disponibles en otro nodo.
Fallo permanente en el nodo o en el disco del clúster individual.
Igual que lo expuesto anteriormente + el autoescalador del clúster K8s sustituye un nodo dañado por uno nuevo. Longhorn reconstruye los datos en el nuevo nodo.
Zombis y otras cosas a tener en cuenta
Si estás familiarizado con la ejecución de IRIS en los contenedores Docker, es posible que hayas utilizado la marca "--init".
docker run --rm -p 52773:52773 --init store/intersystems/iris-community:2020.4.0.524.0
El objetivo de esta marca es evitar la formación de los "procesos zombis". En Kubernetes, puedes utilizar o bien "shareProcessNamespace: true" (se aplican consideraciones de seguridad) o utilizar "tini" en tus propios contenedores. Ejemplo de Dockerfile con tini:
FROM iris-community:2020.4.0.524.0
...
# Add Tini
USER root
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
USER irisowner
ENTRYPOINT ["/tini", "--", "/iris-main"]
A partir de 2021, todas las imágenes de contenedores proporcionadas por InterSystems incluirán tini de forma predeterminada.
Puedes reducir aún más el tiempo de tolerancia a fallos para los escenarios de "forzar el vaciado del nodo/eliminar el nodo" ajustando algunos parámetros:
Política para eliminar Pods de Longhorn https://longhorn.io/docs/1.1.0/references/settings/#pod-deletion-policy-when-node-is-down y desalojo basado en imperfecciones de Kubernetes: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/#taint-based-evictions
Descargo de responsabilidad
Como empleado de InterSystems, tengo que poner esto aquí: Longhorn se utiliza en este artículo como un ejemplo de almacenamiento en bloque distribuido para Kubernetes. InterSystems no valida ni emite ninguna afirmación de soporte oficial para soluciones o productos de almacenamiento individual. Necesitas probar y validar si alguna solución de almacenamiento específica se ajusta a tus necesidades.
El almacenamiento distribuido también puede tener características de rendimiento sustancialmente diferentes, en comparación con el almacenamiento local de los nodos. Especialmente para las operaciones de escritura, en las que los datos deben escribirse en varias ubicaciones antes de que se considere que están en el estado persistente. Asegúrate de probar tus cargas de trabajo y entender el comportamiento específico y las opciones que ofrece tu controlador CSI.
Básicamente, InterSystems no valida ni respalda soluciones de almacenamiento específicas como Longhorn, de la misma manera que no validamos marcas individuales de discos duros o fabricantes de hardware para servidores. Personalmente, Longhorn me pareció fácil de utilizar y su equipo de desarrollo, extremadamente atentos y amables en la página de proyectos de GitHub. https://github.com/longhorn/longhorn
Conclusiones
El ecosistema de Kubernetes ha evolucionado significativamente en los últimos años y con el uso de soluciones de almacenamiento en bloque distribuido, ahora se puede construir una configuración de alta disponibilidad que puede sostener la instancia de IRIS, el nodo del clúster e incluso los errores en la zona de disponibilidad.
Puedes subcontratar el cálculo y la alta disponibilidad del almacenamiento para los componentes de Kubernetes, lo que resulta en un sistema significativamente más sencillo de configurar y mantener, comparado con el mirroring tradicional de IRIS. Al mismo tiempo, esta configuración podría no proporcionar el mismo RTO y rendimiento de almacenamiento que la configuración mirrored.
En este artículo, creamos una configuración IRIS de alta disponibilidad utilizando Azure AKS como sistema de almacenamiento distribuido administrado por Kubernetes y Longhorn. Puedes explorar varias alternativas como AWS EKS, Google Kubernetes Engine, StorageOS, Portworx y OpenEBS como almacenamiento distribuido en contenedores o incluso soluciones de almacenamiento a nivel empresarial como NetApp, PureStorage, Dell EMC y otros.
Anexo A. Cómo crear un clúster de Kubernetes en la nube
El servicio de Kubernetes administrado de uno de los proveedores de nube pública es una forma sencilla de crear el clúster K8s necesario para esta configuración. La configuración predeterminada en AKS de Azure está lista para ser utilizada en la implementación descrita en este artículo.
Crea un nuevo clúster AKS con 3 nodos. Deja todo lo demás de forma predeterminada.
Figura 5. Crear un clúster AKS
Instala kubectl en tu equipo de forma local: https://kubernetes.io/docs/tasks/tools/install-kubectl/
Registra tu clúster de AKS con kubectl local
Figura 6. Registrar el clúster de AKS con kubectl
Después, puedes volver al principio del artículo e instalar Longhorn y la implementación de IRIS.
La instalación en AWS EKS es un poco más complicada. Debes asegurarte de que todas las instancias de tu grupo de nodos tengan instalado open-iscsi.
sudo yum install iscsi-initiator-utils -y
Instalar Longhorn en GKE requiere de un paso adicional, descrito aquí: https://longhorn.io/docs/1.0.1/advanced-resources/os-distro-specific/csi-on-gke/
Anexo B. Instalación paso a paso
Paso 1: Clúster de Kubernetes y kubectl
Necesitas 3 nodos del clúster K8s. En el Anexo A se describe cómo obtener uno en Azure.
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
aks-agentpool-29845772-vmss000000 Ready agent 10d v1.18.10
aks-agentpool-29845772-vmss000001 Ready agent 10d v1.18.10
aks-agentpool-29845772-vmss000002 Ready agent 10d v1.18.10
Paso 2: Instalar Longhorn
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
Asegúrate de que todos los pods del namespace ‘longhorn-system’ estén funcionando. Podría tardar unos minutos.
$ kubectl get pods -n longhorn-system
NAME READY STATUS RESTARTS AGE
csi-attacher-74db7cf6d9-jgdxq 1/1 Running 0 10d
csi-attacher-74db7cf6d9-l99fs 1/1 Running 1 11d
...
longhorn-manager-flljf 1/1 Running 2 11d
longhorn-manager-x76n2 1/1 Running 1 11d
longhorn-ui-df95bdf85-gpnjv 1/1 Running 0 11d
Consulta la guía de instalación de Longhorn para más detalles y resolución de problemas https://longhorn.io/docs/1.1.0/deploy/install/install-with-kubectl
Paso 3: Clonar el repositorio de GitHub
$ git clone https://github.com/antonum/ha-iris-k8s.git
$ cd ha-iris-k8s
$ ls
LICENSE iris-deployment.yaml iris-volume-snapshot.yaml
README.md iris-pvc.yaml longhorn-aws-secret.yaml
iris-cpf-merge.yaml iris-svc.yaml tldr.yaml
Paso 4: Implementar y validar componentes uno por uno
El archivo tldr.yaml contiene todos los componentes necesarios para la implementación en un solo paquete. Aquí los instalaremos uno por uno y validaremos la configuración de cada uno de ellos de forma individual.
# If you have previously applied tldr.yaml - delete it.
$ kubectl delete -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr.yaml
# Create Persistent Volume Claim
$ kubectl apply -f iris-pvc.yaml
persistentvolumeclaim/iris-pvc created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
iris-pvc Bound pvc-fbfaf5cf-7a75-4073-862e-09f8fd190e49 10Gi RWO longhorn 10s
# Create Config Map
$ kubectl apply -f iris-cpf-merge.yaml
$ kubectl describe cm iris-cpf-merge
Name: iris-cpf-merge
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
merge.cpf:
----
[config]
globals=0,0,800,0,0,0
gmheap=256000
Events: <none>
# create iris deployment
$ kubectl apply -f iris-deployment.yaml
deployment.apps/iris created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
iris-65dcfd9f97-v2rwn 0/1 ContainerCreating 0 11s
# note the pod name. You’ll use it to connect to the pod in the next command
$ kubectl exec -it iris-65dcfd9f97-v2rwn -- bash
irisowner@iris-65dcfd9f97-v2rwn:~$ iris session iris
Node: iris-65dcfd9f97-v2rwn, Instance: IRIS
USER>w $zv
IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2020.4 (Build 524U) Thu Oct 22 2020 13:04:25 EDT
# h<enter> to exit IRIS shell
# exit<enter> to exit pod
# access the logs of the IRIS container
$ kubectl logs iris-65dcfd9f97-v2rwn
...
[INFO] ...started InterSystems IRIS instance IRIS
01/18/21-23:09:11:312 (1173) 0 [Utility.Event] Private webserver started on 52773
01/18/21-23:09:11:312 (1173) 0 [Utility.Event] Processing Shadows section (this system as shadow)
01/18/21-23:09:11:321 (1173) 0 [Utility.Event] Processing Monitor section
01/18/21-23:09:11:381 (1323) 0 [Utility.Event] Starting TASKMGR
01/18/21-23:09:11:392 (1324) 0 [Utility.Event] [SYSTEM MONITOR] System Monitor started in %SYS
01/18/21-23:09:11:399 (1173) 0 [Utility.Event] Shard license: 0
01/18/21-23:09:11:778 (1162) 0 [Database.SparseDBExpansion] Expanding capacity of sparse database /external/iris/mgr/iristemp/ by 10 MB.
# create iris service
$ kubectl apply -f iris-svc.yaml
service/iris-svc created
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
iris-svc LoadBalancer 10.0.214.236 20.62.241.89 52773:30128/TCP 15s
Paso 5: Acceso al Portal de administración
Por último: Conéctate al Portal de administración de IRIS, utilizando la IP externa del servicio http://20.62.241.89:52773/csp/sys/%25CSP.Portal.Home.zen username _SYSTEM, Password SYS. Se te pedirá que la cambies cuando inicies sesión por primera vez.
Este artículo está etiquetado como "Mejores prácticas" ("Best practices").
Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems
Pregunta
Daniel Aguilar · 1 sep, 2020
Buenas tardes, estamos probando la migración a la versión de IRIS porque queríamos aprovechar el poder trabajar con el plugin Insertystems ObjectScript de Visual Studio Code para control de versiones pero haciendo pruebas veo lo siguiente:
Nosotros tenemos muchísimas clases y rutinas en nuestros namespaces y cuando hago un cambio de rama en VSCODE para que los cambios se graben en el lado servidor hay que pulsar en Importar y Compilar el namespace completo a fin de que los cambios de la nueva rama queden grabados en el servidor (Este proceso le puede costar fácilmente 20 minutos y no aporta ningún feedback del porcentaje de la operación realizado) ya que de lo contrario tengo los cambios en las vistas que ofrece el plugin de VSCODE tanto en el lado cliente como en el lado servidor pero si entro con el Studio y abro la rutina veo que efectivamente los cambios de la rama a la que acabo de cambiar no están.
¿Hay alguna forma de "aligerar" este proceso de "Importar y compilar" para que lo realice de un modo mas eficiente?
¿Hay alguien que esté trabajando actualmente con el plugin para VSCODE que me pueda echar una mano?
Resumiendo, problemas que tenemos:
-> Hay que ejecutar manualmente "Importar y compilar" los cambios para todos los namespaces cuando cambias de rama.
-> La operación "Importar y compilar" tarda como 20 minutos y no reporta feedback del estado actual de la operación (En el OUTPUT del terminal ObjectScript se queda como colgado y va apareciendo el avance por bloques derrepente)
Muchas gracias de antemano por vuestra ayuda ;-)
Daniel, let me answer you in English, as I don't speak Spanish, hope I got your point (with the help from google translate) and you'll get my point.
A common use-case for most of the editors with any languages but ObjectScript is just to edit code. With some extra possibilities related to a particular language.
But InterSystems, not a common language, and as you said you have to compile your code and would like to have more control over it.
If you would need to do it in Java, you rather use Gradle or Ant, if it would be C,C++ I think something like cmake. JavaScript or TypeScript even also use some kind of compilers. But most of those tools work outside of any editor. So, I'm sure that InterSystems ObjectScript not an exception here anyway, I would not say that's a good way if VSCode would have so much control over it. My point is, that you some own ways how to build the entire project from sources. And the most common way to do it now with %Installer manifest.
Good morning Dimitry, I'm glad to be back to talk to you.
Thanks for your reply ( I will try to answer you in English )
I have never use %Installer, I have been investigating and I seem to understand that it is a class with a manifest in which the server configuration is defined, the class must be have a method called setup and that it can be executed by terminal, I understand that a condition can also be included to force the compilation of all the classes and routines (It is right?)
Does using %Intaller offer any performance improvement?
Would the steps to follow be the following ?:
1 -> Change branch from VsCode
2 -> Launch the "setup" method of the %Intaller class from terminal (for import and compile routines in server side, Would this be equivalent to clicking "Import and Compile", right? ), Should this method be launched from within VsCode terminal?
3 -> Could the execution of the% Installer be automated after the branch change?
Thank you very much Dimitry
Artículo
Eduardo Anglada · 24 mayo, 2021
Qué te parece si te digo que muy pronto te podrás conectar a IRIS desde la aplicación escrita en Rust...
¿Qué es Rust?
Rust es un lenguaje de programación multiparadigma diseñado teniendo en cuenta el rendimiento, la seguridad y especialmente que la concurrencia sea segura. Rust es sintácticamente similar a C++, pero puede garantizar la seguridad de la memoria mediante el uso de un verificador de préstamos para validar las referencias. Rust logra la seguridad de la memoria sin emplear un recolector de basura, y el conteo de referencias es opcional. (c) Wikipedia.
Es el lenguaje más valorado durante los últimos cinco años en la encuesta de Stack Overflow 2020.
¿Qué es posible ahora mismo?
Puede trabajar con globals y hacer consultas SQL sencillas. Observa el ejemplo de trabajo.
use irisnative;
use irisnative::{connection::*, global, global::Sub, Global};
fn main() {
let host = "127.0.0.1";
let port = 1972;
let namespace = "USER";
let username = "_SYSTEM";
let password = "SYS";
match irisnative::connect(host, port, namespace, username, password) {
Ok(mut connection) => {
println!("Connection established");
println!("Server: {}", connection.server_version());
connection.kill(&global!(A));
connection.set(&global!(A(1)), "1");
connection.set(&global!(A(1, 2)), "test");
connection.set(&global!(A(1, "2", 3)), "123");
connection.set(&global!(A(2, 1)), "21test");
connection.set(&global!(A(3, 1)), "test31");
let mut global = global!(A(""));
while let Some(key) = connection.next(&mut global) {
println!("^A({:?}) = {:?}", key, {
if connection.is_defined(&global).0 {
let value: String = connection.get(&global).unwrap();
value
} else {
String::from("<UNDEFINED>")
}
});
let mut global1 = global!(A(key, ""));
while let Some(key1) = connection.next(&mut global1) {
let value: String;
if connection.is_defined(&global1).0 {
value = connection.get(&global1).unwrap();
} else {
value = String::from("<UNDEFINED>");
}
println!("^A({:?}, {:?}) = {:?}", key, key1, value);
}
}
let mut rs = connection.query(String::from(
"SELECT Name from %Dictionary.ClassDefinition WHERE Super = 'Ens.Production' and Abstract<>1"));
while rs.next() {
let name: String = rs.get(0).unwrap();
println!("{}", name);
}
}
Err(err) => {
println!("Error: {}", err.message);
}
}
}
Por lo tanto, será posible conectarse a IRIS por medio de la red, tan pronto como tenga acceso al puerto del súper servidor (1972).
Caso de uso real
Para utilizarlo en producción, tiene que ser compilado en un archivo ejecutable o librería. Yo tengo un proyecto donde lo uso ahora. Es la extensión VSCode para el control avanzado de InterSystems IRIS. El proyecto participó en el Gran Premio para Desarrolladores de InterSystems. Rust ayuda a obtener acceso directo a IRIS, y será posible comprobar el estado o detener/iniciar la producción, observar los globals y muchas otras cosas en el futuro.
Rust tiene que ser compilado en un formato binario para la plataforma deseada, y por el momento esta extensión se desarrolló para macOS x64 y Windows x64. Pero Rust se puede compilar para una amplia gama de plataformas, incluyendo Arm64.
Veamos qué más se puede hacer
Vamos a intentar ejecutar la aplicación Rust (del ejemplo anterior) con el conector IRIS en el Docker. En Dockerfile simple se puede construir una imagen con la aplicación.
FROM ekidd/rust-musl-builder
ADD --chown=rust:rust . ./
RUN cargo build --release --example main
FROM scratch
COPY --from=0 /home/rust/src/target/x86_64-unknown-linux-musl/release/examples/main /
CMD [ "/main" ]
Vamos a crearla
$ docker build -t rust-irisnative .
Y la ejecutamos
$ docker run -it rust-irisnative
Connection established
Server: IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2020.4 (Build 524U) Thu Oct 22 2020 13:04:25 EDT
^A("1") = "1"
^A("1", "2") = "test"
^A("2") = "<UNDEFINED>"
^A("2", "1") = "21test"
^A("3") = "<UNDEFINED>"
^A("3", "1") = "test31"
Test.NewProduction
dc.Demo.Production
Si te preguntas qué tamaño tiene esa imagen:
$ docker images rust-irisnative
REPOSITORY TAG IMAGE ID CREATED SIZE
rust-irisnative latest 0b1e54e7aa6f 2 minutes ago 3.92MB
Solo unos pocos megabytes de la imagen pueden conectarse a IRIS, que se ejecuta en otro lugar. Parece que es muy bueno para los microservicios y las aplicaciones sin servidor. Y como ventaja para el IoT, las aplicaciones de Rust pueden ejecutarse en pequeños PCs, por ejemplo, RaspberyPi Pico. ¿Qué te parece y cómo utilizarías Rust?
Artículo
Dani Fibla · 14 sep, 2021
Spring Boot es el *framework* de Java más utilizado para crear APIs REST y microservicios. Se puede utilizar para implementar sitios webs o webs ejecutables o aplicaciones de escritorio independientes, donde la aplicación y otras dependencias se empaquetan juntas. Springboot permite realizar muchas funciones, como:
Nota: para saber más sobre SpringBoot, consulta el sitio oficial: https://spring.io/quickstart
Para crear una aplicación web API , con uno o más microservicios, puedes utilizar Spring IDE para Eclipse o VSCode, y utilizar un asistente para configurar las tecnologías anteriores que se utilizarán en tu aplicación, mira:
.png)
Seleccionas las tecnologías y creas el proyecto. Todas las tecnologías se importarán utilizando Maven. Esto es como un ZPM visual.
El proyecto creado tiene una clase para subir la aplicación con todo lo que necesitas dentro de ella (servidor web y de aplicaciones, y todas las dependencias, concepto de microservicio).
El código fuente completo de este proyecto se encuentra en el proyecto de Open Exchange: https://openexchange.intersystems.com/package/springboot-iris-crud.
Lo primero que hay que hacer es configurar el controlador de JDBC en IRIS y el soporte IRIS Hibernate. Para ello, copia los archivos jar en la carpeta de recursos:
.png)
Abre pom.xml para configurar las dependencias de estos archivos jar.:
com.intersystems
intersystems-jdbc
3.2.0
system
${project.basedir}/src/main/resources/intersystems-jdbc-3.2.0.jar
org.hibernate
hibernate-iris
1.0.0
system
${project.basedir}/src/main/resources/hibernate-iris-1.0.0.jar
Ahora puedes configurar tu conexión con la base de datos de IRIS en application.properties, de la siguiente forma:
spring.datasource.username=_SYSTEM
spring.datasource.url=jdbc:IRIS://iris:1972/USER
spring.datasource.password=SYS
spring.jpa.properties.hibernate.default_schema=dc_Sample
#spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=com.intersystems.jdbc.IRISDriver
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults = false
spring.jpa.database-platform=org.hibernate.dialect.InterSystemsIRISDialect
spring.datasource.sql-script-encoding=utf-8
server.tomcat.relaxed-query-chars=|,{,},[,]
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
El siguiente paso consiste en crear un mapeo de la clase Persistent de Java en una tabla de IRIS:
package community.intersystems.springboot.app.model;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import com.fasterxml.jackson.annotation.JsonFormat;
@Entity
@Table(name = "Product")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private Double height;
private Double width;
private Double weight;
@Column(name="releasedate")
@Temporal(TemporalType.DATE)
@JsonFormat(pattern = "dd/MM/yyyy")
private Date releaseDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
public Double getWidth() {
return width;
}
public void setWidth(Double width) {
this.width = width;
}
public Double getWeight() {
return weight;
}
public void setWeight(Double weight) {
this.weight = weight;
}
public Date getReleaseDate() {
return releaseDate;
}
public void setReleaseDate(Date releaseDate) {
this.releaseDate = releaseDate;
}
}
El último paso es crear una interfaz en el Repositorio para exponer tu clase persistente como un servicio REST (en el patrón HATEOAS). Con operaciones CRUD, no necesitas escribir esto, solo:
package community.intersystems.springboot.app.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import community.intersystems.springboot.app.model.Product;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
Ahora, ejecutas tu aplicación como una aplicación Spring Boot:
.png)
Espera al servidor interno y abre localhost:8080. Spring boot abrirá un navegador API REST HAL. Mira este gif animado:

Consulta más detalles en mi aplicación de muestra. Lo empaqueto todo junto en un proyecto Docker con 2 servicios: IRIS y SpringBoot.
El patrón HATEOAS es muy agradable para las API URL , la ruta y la navegación. Puedes conocer más detalles en: https://en.wikipedia.org/wiki/HATEOAS
¡Espero que os resulte útil!
Artículo
Ricardo Paiva · 16 jul, 2021
Surgió una pregunta en la Comunidad de Desarrolladores de InterSystems sobre la posibilidad de crear una interfaz TWAIN para una aplicación Caché. Hubo varias sugerencias excelentes sobre cómo obtener datos de un dispositivo de imágenes en un cliente web a un servidor y después almacenar estos datos en una base de datos.
Sin embargo, para implementar cualquiera de estas sugerencias, se debe poder transferir datos desde un cliente web a un servidor de base de datos y almacenar los datos recibidos en una propiedad de clase (o una celda de tabla, como fue el caso en la pregunta). Esta técnica puede ser útil no solo para transferir datos de imágenes recibidos desde un dispositivo TWAIN, sino también para otras tareas como organizar un archivo, compartir una imagen, etc.
Por lo tanto, el objetivo de este artículo es mostrar cómo escribir un servicio RESTful para obtener datos del cuerpo de un comando HTTP POST, ya sea en un estado sin procesar o envuelto en una estructura JSON.
Conceptos básicos de REST
Antes de ir a los detalles, empezaremos hablando sobre REST en general y sobre cómo se crean los servicios RESTful en IRIS.
Unaa Transferencia de EStado Representacional (REST) es un estilo arquitectónico para sistemas hiper-media distribuidos. La abstracción clave de información en REST es un recurso que tiene su identificador adecuado y se puede representar en JSON, XML u otro formato conocido tanto por el servidor como por el cliente.
Por lo general, HTTP se usa para transferir datos entre el cliente y el servidor. Cada operación CRUD (crear, leer, actualizar, eliminar) tiene su propio método HTTP (POST, GET, PUT, DELETE), mientras que cada recurso o colección de recursos tiene su propio URI.
En este artículo, usaré solo el método POST para insertar un nuevo valor en la base de datos, por lo que necesito conocer sus restricciones.
POST no tiene ningún límite en el tamaño de los datos almacenados en su cuerpo de acuerdo con la especificación IETF RFC7231 4.3.3 Post. Pero los distintos navegadores y servidores web imponen sus propios límites, normalmente de 1 MB a 2 GB. Por ejemplo, Apache permite un máximo de 2 GB. En cualquier caso, si necesitas enviar un archivo de 2 GB, quizá deberías reconsiderar tu enfoque.
Requisitos para un servicio RESTful en IRIS
En IRIS, para implementar un servicio RESTful, debes:
Crear un intermediario de clases que amplíe la clase abstracta %CSP.REST. Esto, a su vez, extiende %CSP.Page, y hace posible acceder a diferentes métodos, parámetros y objetos útiles, en particular %request).
Especificar el UrlMap para definir rutas.
Opcionalmente, configurar el parámetro UseSession para especificar si cada llamada REST se ejecuta en su propia sesión web o comparte una sola sesión con otras llamadas REST.
Proporcionar métodos de clase para realizar las operaciones definidas en rutas.
Definir la aplicación web CSP y especificar su seguridad en la página de la aplicación web (Administración del sistema> Seguridad> Aplicaciones> Aplicaciones web), donde la clase Dispatch debe contener el nombre de la clase de usuario y el Nombre, la primera parte de la URL para la llamada REST.
En general, hay varias formas de enviar grandes cantidades de datos (archivos) y sus metadatos de cliente a servidor, como:
Codificación en Base64 del archivo y los metadatos y añadir una sobrecarga de procesamiento tanto al servidor como al cliente para la codificación / descodificación.
Primero enviar el archivo y devolver una identificación (ID) al cliente, que luego enviará los metadatos con la identificación. El servidor vuelve a asociar el archivo y los metadatos.
Enviar primero los metadatos y devolver una identificación (ID) al cliente, que luego envía el archivo con la identificación, y el servidor vuelve a asociar el archivo y los metadatos.
En este primer artículo, básicamente tomaré el segundo enfoque, pero sin devolver la identificación (ID) al cliente y sin añadir los metadatos (el nombre del archivo para almacenar como otra propiedad) porque no hay nada excepcional en esta tarea. En un segundo artículo, usaré el primer enfoque, pero empaquetaré mi archivo y metadatos (el nombre del archivo) en una estructura JSON antes de enviarlo al servidor.
Implementación del servicio RESTful
Ahora vayamos a los detalles. Primero, vamos a definir la clase, cuyas propiedades configuraremos:
Class RestTransfer.FileDesc Extends %Persistent
{
Property File As %Stream.GlobalBinary;
Property Name As %String;
}
Por supuesto, normalmente tendrás más metadatos para el archivo, pero esto debería ser suficiente para nuestros propósitos.
A continuación, necesitamos crear un intermediario de clase, que luego expandiremos con rutas y métodos:
Class RestTransfer.Broker Extends %CSP.REST
{
XData UrlMap
{
<Routes>
</Routes>
}
}
Y, por último, para la configuración preliminar, necesitamos especificar esta aplicación en una lista de aplicaciones web:
Ahora que la configuración preliminar está hecha, podemos escribir métodos para guardar un archivo recibido de un cliente REST (usaré el Advanced REST Client) en una base de datos como una propiedad de archivo de una instancia de la clase RestTransfer.FileDesc.
Comenzaremos trabajando con información almacenada como datos sin procesar en el cuerpo del método POST. Primero, añadiremos una nueva ruta a UrlMap:
<Route Url="/file" Method="POST" Call="InsertFileContents"/>
Esta ruta especifica que cuando el servicio recibe un comando POST con URL / RestTransfer / file, debe llamar al método de clase InsertFileContents. Para facilitar las cosas, dentro de este método crearé una nueva instancia de una clase RestTransfer.FileDesc y estableceré su propiedad File en los datos recibidos. Esto devuelve el estado y un mensaje con formato JSON indicando éxito o error. Aquí está el método de la clase:
ClassMethod InsertFileContents() As %Status
{
Set result={}
Set st=0
set f = ##class(RestTransfer.FileDesc).%New()
if (f = $$$NULLOREF) {
do result.%Set("Message","Couldn't create an instance of the class")
} else {
set st = f.File.CopyFrom(%request.Content)
If $$$ISOK(st) {
set st = f.%Save()
If $$$ISOK(st) {
do result.%Set("Status","OK")
} else {
do result.%Set("Message",$system.Status.GetOneErrorText(st))
}
} else {
do result.%Set("Message",$system.Status.GetOneErrorText(st))
}
}
write result.%ToJSON()
Quit st
}
Primero, crea una nueva instancia de la clase RestTransfer.FileDesc y comprueba que se creó correctamente y tenemos un OREF. Si el objeto no se pudo crear, formamos una estructura JSON:
{"Message", "Couldn't create an instance of class"}
Si se creó el objeto, el contenido de la solicitud se copia en la propiedad Archivo. Si la copia no fue exitosa, formamos una estructura JSON con una descripción del error:
{"Message",$system.Status.GetOneErrorText(st)}
Si el contenido fue copiado, guardamos el objeto, y si se guarda de forma correcta, formamos un JSON {"Status", "OK"}. Si noo, el JSON devuelve una descripción del error.
Finalmente, escribimos el JSON en una respuesta y devolvemos el estado st.
Este es un ejemplo de cómo transferir una imagen:
Cómo se guarda en la base de datos:
Podemos guardar esta secuencia en un archivo y ver que se transfirió sin cambios:
set f = ##class(RestTransfer.FileDesc).%OpenId(4)
set s = ##class(%Stream.FileBinary).%New()
set s.Filename = "D:\Downloads\test1.jpg"
do s.CopyFromAndSave(f.File)
Lo mismo se puede hacer con un archivo de texto:
Esto también será visible en los globals:
Y podemos almacenar ejecutables o cualquier otro tipo de datos:
Podemos comprobar el tamaño en los globals, que es correcto:
Ahora que esta parte funciona como debería, echemos un vistazo al segundo enfoque: obtener el contenido del archivo y sus metadatos (el nombre del archivo) almacenados en formato JSON y transferidos en el cuerpo de un método POST.
Hay más información sobre la creación de servicios REST en la documentación de InterSystems.
El código de ejemplo para ambos enfoques está en GitHub y en InterSystems Open Exchange. Si tienes alguna pregunta o sugerencia relacionada con cualquiera de los dos, no dudes en escribirla en los comentarios.
Pregunta
Laura Blázquez García · 19 feb, 2020
Hola.
Necesitamos llamar a un servicio y tenemos que encriptar la petición utilizando una clave pública. Tenemos un ejemplo de cómo realizar la llamada en PHP. También tenemos la clave pública y todos los parámetros que necesitamos. El ejemplo en PHP es éste y funciona (utiliza openssl):
$url = "https://XXXXX/";$json = '{"api_key":"XXXXX", "id":"1"}';$jsonEncrypt, = '';$publicKey = file_get_contents("public.key");openssl_get_publickey($publicKey);openssl_public_encrypt($json, $jsonEncrypt, $publicKey);$jsonEncrypt = base64_encode($jsonEncrypt);
Necesitamos hacer lo mismo en Ensemble. Escribí un post en la comunidad de InterSystems en inglés, y me dijeron que utilizase RSAEncrypt().
He probado esto:
set json = "{""api_key"":""XXXXX"", ""id"":""1""}"// Abrir el fichero de la clave pública:set file = ##class(%FileCharacterStream).%New()set file.Filename = "public.key"set key = file.Read(file.Size)// Encriptar el JSONset jsonEncrypt = $System.Encryption.RSAEncrypt(json, key)
Pero no funciona, me devuelve un string vacío. He buscado por todas partes y no sé por qué no funciona. La clave pública tiene este formato:
-----BEGIN PUBLIC KEY-----........................-----END PUBLIC KEY-----
Los saltos de línea son LF y está en UTF-8. En qué formato debe estar la clave pública para que funcione en Ensemble? Estoy haciendo algo mal?
Muchas gracias de antemano. Perdón, he conseguido obtener el error con RSAGetLastError(), y me devuelve esto:
error:0906D06C:PEM routines:PEM_read_bio:no start line; Hola Laura, tendría que mirar como usar esa función, pero una cosa que siempre puedes hacer ya que te funciona en openSSL es llamar directamente a OpenSSL. Échale un ojo a este ejemplo https://github.com/drechema/ensemble-smime También le echaría un ojo a este articulo para saber como llamar correctamente al método:
https://community.intersystems.com/post/format-public-key-when-using-rsaencrypt-method-systemencryption-or-systemencryptionrsaencrypt Gracias David.
Al final, dado que el comando de terminal funcionaba, esto es exactamente lo que hice, invocar el comando utilizando $ZF(-100, "openssl", ...). Con esto hemos conseguido que funcione. Entiendo que es una solución igualmente válida, no? Me hubiera gustado poder realizarlo con los métodos de clase de $System.Encryption, pero no lo he conseguido. He mirado este artículo y estoy invocando correctamente el método. Lo que sí he tenido que hacer es reconvertir la clave pública de pkcs#8 a pkcs#1 como indica en el artículo. Con eso consigo que encripte, aunque luego el sistema del proveedor me dice que no es válido, pero quizá sea que me falte/sobre algo. Seguiré probando a ver si consigo hacerlo funcionar.
Muchas gracias!
Artículo
Mathew Lambert · 28 feb, 2020
¡Hola Comunidad!
ObjectScript tiene al menos tres formas de manejar errores (códigos de estado, excepciones, SQLCODE, etc...). La mayor parte del código del sistema usa estados, pero las excepciones son más fáciles de manejar por varias razones. Al trabajar con código heredado, se invierte un tiempo en traducir las distintas técnicas. Yo uso mucho estos fragmentos de código como referencia. Espero que también os sean útiles.
///Status from SQLCODE:
set st = $$$ERROR($$$SQLError, SQLCODE, $g(%msg)) //embedded SQL
set st = $$$ERROR($$$SQLError, rs.%SQLCODE, $g(rs.%Message)) //dynamic SQL
///Exception from SQLCODE:
throw ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE,%msg) //embedded SQL
throw ##class(%Exception.SQL).CreateFromSQLCODE(rs.%SQLCODE,rs.%Message) //dynamic SQL
throw:(SQLCODE'=0)&&(SQLCODE'=100) ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE,%msg) //don't throw if query succeeds or finds no data
///Exception from status:
$$$ThrowOnError(st)
///Status from exception:
set st = err.AsStatus()
///Creating a custom error status:
set st = $$$ERROR($$$GeneralError,"Custom error message")
///Throwing a custom exception:
$$$ThrowStatus($$$ERROR($$$GeneralError,"Custom error message"))
///Handling a SOAP error with a status:
try {
//SOAP request code
} Catch err {
If err.Name["ZSOAP" {
Set st = %objlasterror
} Else {
Set st = err.AsStatus()
}
}
return st
///Defining a custom exception class
Class App.Exceptions.SomeException Extends %Exception.AbstractException
{
Method OnAsStatus() As %Status
{
return $$$ERROR($$$GeneralError,"Custom error message")
}
}
///Throwing and catching a custom exception
try {
throw ##class(App.Exceptions.SomeException).%New()
} catch err {
if err.%ClassName(1) = ##class(App.Exceptions.SomeException).%ClassName(1) {
//some handling unique to this type of exception
}
} Genial, muy útil! Información realmente útil. La gestión de errores reduce en gran medida la complejidad de un código. Esos números mágicos como el retorno de error son una pesadilla. muy bueno
Este artículo está etiquetado como "Mejores prácticas" ("Best practices")
Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems.
Artículo
Alberto Fuentes · 23 feb, 2021
Hola a todos! Os comparto hoy un artículo sobre la utilización del **procesamiento de lenguaje natural** y su combinación con **FHIR** donde se muestra un chatbot que interactúa con FHIR desarrollado por [Renato Banzai](https://community.intersystems.com/user/renato-banzai).
## ¿Qué significa PLN?
PLN significa Procesamiento del Lenguaje Natural (NLP en inglés) y es un campo de la Inteligencia Artificial muy complejo que utiliza técnicas para, en pocas palabras, “entender de qué se está hablando”.
## ¿Y qué es FHIR?
FHIR significa *Fast Healthcare Interoperability Resources* y es un estándar que describe estructuras de datos y operaciones que puedes realizar sobre ellas en aplicaciones de salud. En la Comunidad de Desarrolladores hay varios artículos que explican mejor cómo interactúa FHIR con InterSystems IRIS. Aquí incluso publicamos un [Webinar: Comienza a Trabajar con FHIR](https://comunidadintersystems.com/webinar-comienza-a-trabajar-con-fhir)

## Un chatbot para consultar FHIR
El método más común en los chatbots es utilizar *machine learning* para enseñar al modelo a través de conversaciones de entrenamiento. Pero cuando el chatbot necesita utilizar información en tiempo real, el desafío es mayor. Si conoces cómo funciona un modelo de *machine learning*, ya sabes que en la mayoría de los casos el modelo entrenado es una especie de aplicación con todas las decisiones integradas en su interior. Si necesitas más información sin tener que enseñar nuevamente al modelo, tendrás que crear tú mismo las integraciones y dedicar una parte de tu esfuerzo al "machine learning" y otra parte a la ingeniería del software.
## Primera aproximación
Para mostrar mejor cómo funciona FHIR en esta aplicación se decidió trabajar primero en las preguntas que un profesional sanitario podría hacerle a un chatbot. Para ello, se hizo que la ingeniería del software fuera sencilla utilizando solamente javascript y expresiones regulares con las que se buscan objetivos y estructuras en el chat.
Recordad que en la Comunidad también publicamos otro contenido con un ejemplo de chatbot donde se utilizaban otras alternativas: [Webinar: Desarrolla un chatbot con Google Dialogflow, Telegram e IRIS](https://comunidadintersystems.com/webinar-desarrolla-un-chatbot).
## Expresiones regulares

Las expresiones regulares son una forma muy potente de trabajar con textos y de buscar coincidencias en los patrones. Con las expresiones regulares puedo buscar cuál es el paciente sobre el que quiere tener información la persona que está chateando y qué pregunta o historial médico desea consultar.
Artículo
Mario Sanchez Macias · 26 sep, 2019
¡Hola a todos!
En esta publicación me gustaría hablar sobre la tabla syslog: qué es, cómo analizarla, cuáles son realmente las entradas y por qué puede ser importante para usted. La tabla syslog puede contener información de diagnóstico importante. Si su sistema tiene algún problema, es importante entender cómo analizar esta tabla y qué información contiene.
¿Qué es una tabla syslog?
Caché reserva una pequeña porción de su memoria compartida para registrar elementos importantes. Esta tabla tiene varios nombres diferentes:
errlog
SYSLOG
tabla syslog
Para cumplir con el propósito de esta publicación, simplemente la llamaré 'tabla syslog'.
El tamaño de la tabla syslog puede configurarse. El valor predeterminado es de 500 entradas. El intervalo es de 10 a 10,000 entradas. Para cambiar el tamaño de la tabla syslog ve al Portal de administración y entra en Administración -> Configuración -> Configuración adicional-> Memoria avanzada y en la fila ‘errlog’ selecciona ‘editar’ e introduce el número de entradas deseado para la tabla syslog.
¿Por qué se quiere cambiar el tamaño de la tabla syslog?
Si la tabla syslog se configura para 500 entradas, en la entrada 501 se sobrescribirá la primera entrada, y esa información se perderá. Esta tabla se encuentra en la memoria y por lo tanto no persiste en ningún lugar, a menos que la salida se guarde de manera específica. Además, cuando se paré Caché, todas las entradas se perderán a menos que se configure para guardar las entradas en el archivo cconsole.log, como se describe a continuación.
Si Caché introduce muchas entradas en la tabla syslog, y necesitas analizarlas para ayudar a diagnosticar cualquier problema, se perderán las entradas si la tabla no es lo suficientemente grande. Puedes analizar la columna Fecha/Hora que se encuentra en la tabla de syslog para determinar el periodo de tiempo que lleva llenar la tabla. Esto te ayudará para decidir cuántas entradas necesitas. A mí me gusta tener controlada la tabla y aumentarla para no perder ninguna entrada.
¿Cómo puedo analizar la tabla syslog?
Existen varias maneras de analizar la tabla syslog:
Desde una línea de comandos en el terminal de Caché en el namespace %SYS, ‘Do ^SYSLOG’
Desde una línea de comandos en el terminal de Caché en el namespace %SYS, ‘do ^Buttons’
Desde el Portal web de administración : Operaciones -> Informes de diagnóstico
Ejecutando cstat con la opción -e1
Ejecutando Cachehung
Configurando Caché para volcar la tabla syslog en el archivo cconsole.log al apagar el sistema. Para hacer esto entra en el Portal de administración web y ve a Administración -> Configuración -> Configuración adicional -> Compatibilidad, en la fila 'ShutDownLogErrors', selecciona 'editar' y activa el check ('true') para guardar el contenido de syslog en cconsole.log cuando se paré Caché.
¿Qué significan las entradas de syslog?
A continuación, se muestra un ejemplo de la tabla syslog. Este ejemplo se origina de la ejecución de ^SYSLOG en la línea de comandos del terminal de Caché:
%SYS>d ^SYSLOG
Device:
Right margin: 80 =>
Show detail? No => No
Cache System Error Log printed on Nov 02 2016 at 4:29 PM
--------------------------------------------------------
Printing the last 8 entries out of 8 total occurrences.
Err Process Date/Time Mod Line Routine Namespace
9 41681038 11/02/2016 04:44:51PM 93 5690 systest+3^systest %SYS
9 41681038 11/02/2016 04:43:34PM 93 5690 systest+3^systest %SYS
9 41681038 11/02/2016 04:42:06PM 93 5690 systest+3^systest %SYS
9 41681038 11/02/2016 04:41:21PM 93 5690 systest+3^systest %SYS
9 41681038 11/02/2016 04:39:29PM 93 5690 systest+3^systest %SYS
9 41681036 11/02/2016 04:38:26PM 93 5690 systest+3^systest %SYS
9 41681036 11/02/2016 04:36:57PM 93 5690 systest+3^systest %SYS
9 41681036 11/02/2016 04:29:45PM 93 5690 systest+3^systest %SYS
Puede parecer evidente, según los títulos de las columnas, el significado de cada uno de los elementos de una entrada, pero los describiré todos.
Printing the last 8 entries out of 8 total occurrences
Aunque esto no es parte de una entrada en la tabla syslog, es algo importante que se debe considerar, así que lo mencionaré. Aquí es donde se mira para encontrar cuántas entradas contiene la tabla syslog. En este ejemplo, solo se generaron 8 entradas desde que se inició Caché. Este es mi sistema de pruebas, así que tiene muchas entradas. Si aparece ‘Printing the last 500 entries out of 51234 total occurrences’ sabrás que se han perdido muchas entradas. En estos casos, aumenta el tamaño de la tabla hasta un máximo de 10,000 (si se está interesado en ver estas entradas) o ejecuta SYSLOG con mayor frecuencia.
Err
Esta es la información que registramos sobre el evento de interés. Normalmente suele ser un error a nivel del sistema operativo (SO), para encontrar qué error de SO se refiere tienes dos opciones:
En Linux/Unix: los números de error se suelen encuentran en /usr/include/errno.h.
En Windows pueden consultarse con >net helpmsg <numero>.
Pero no siempre es de SO y también podría ser cualquier otra cosa que necesitamos registrar. Por ejemplo, podría contener información de depuración para para un valor determinado de una variable interna o nuestros propios código de de error (cualquier cosa mayor a 10,000).
Pero ¿Cómo se puede saber qué es? Para saber qué es, debemos analizar la línea de código fuente que se indica mediante "mod" y "line". En la práctica, esto significa que no es posible encontrar ese detalle sin contactar a InterSystems. ¿Entonces, por qué molestarse en analizarlo? Bueno, existen muchas cosas que puede averiguar sin saber exactamente lo que es err, echando un vistazo a otro tipo de información. Además, puedas ponerte en contacto con InterSystems si ves que existen muchas entradas, o entradas diferentes a las entradas que normalmente ves en un sistema saludable.
Por otro lado, hay que tener también en cuenta que una entrada en la tabla syslog no siempre implica un error. (Pueden aparecer mensajes de desconexión, fichero no encontrado, etc... que no son realmente errores si están controlados).
Process
Este es el ID del proceso que introdujo la entrada en la tabla syslog. Por ejemplo, si tienes un proceso bloqueado, en loop o muerto, puedes analizar la tabla syslog para consultar si existe algún registro. Si lo hizo, probablemente será una pista importante de por qué el proceso tuvo problemas.
Date/Time
Esta es la fecha y la hora en que se realizó la entrada. Es muy importante correlacionar la fecha y la hora de la entrada con la de cualquier evento del sistema, porque con frecuencia es una pista de lo que salió mal.
Mod y Line
Mod corresponde a un archivo específico en C, y line es el número de línea en ese archivo que introdujo la entrada en la tabla syslog. Solo el personal de InterSystems que cuenta con acceso al código del kernel puede buscarlo. Solo buscando este código se puede saber exactamente lo que se registra en la entrada.
Routine
Las etiquetas, el offset y la rutina que el proceso estaba ejecutando cuando se llevo a cabo la entrada en la tabla syslog. Esto puede ayudar a averiguar lo que ocurre.
Namespace
Este es el namespace en el que se ejecutó el proceso.
Por tanto ¿cómo puedo determinar por qué err 9 está en mi tabla syslog?
Primero, analizamos la rutina indicada. Esta es la rutina ^systest del ejemplo anterior:
systest ;test for syslog post
s file="/home/testfile"
o file:10
u file w "hello world"
c file
q
La entrada de syslog indica que systest+3 es lo que se ejecutó cuando se llevo a cabo la entrada. Esta línea es:
u file w "hello world"
Dado que el proceso intentaba escribir en un archivo, esto podría ser un error a nivel de sistema operativo (OS), así que buscamos el código de error 9 en /usr/include/errno.h y encontramos:
#define EBADF 9 /* Bad file descriptor */
Dado que 9 está relacionado con los archivos y la línea de código intenta escribir en un archivo, es razonable suponer que realmente se trata de un error en el código devuelto por el sistema operativo.
¿Podemos determinar qué está mal?
Para resolver esto, primero analizamos los permisos que se encuentran en el directorio /home y en el archivo de prueba. Ambos eran 777, así que realmente debió ser capaz de abrir y escribir en ese archivo. Al analizar más de cerca el código nos damos cuenta de un error. Antes de los dos puntos del tiempo de espera de diez segundos se necesita utilizar algunos parámetros en el comando open. A continuación, se muestra la rutina actualizada que realmente termina sin errores y se escribe en el archivo:
systest ;test for syslog post
s file="/scratch1/yrockstr/systest/testfile"
o file:"WNSE":10
u file w "hello world"
c file
q
Resumen
La tabla syslog es una herramienta valiosa para depurar si se utiliza correctamente. Hay que tener en cuenta lo siguiente cuando se utilice:
err no siempre es un error del sistema operativo. Pónte en contacto con InterSystems para conocer lo en detalle lo que se ha registrado.
Utiliza otros datos relacionados para determinar lo que sucede. La línea del código COS combinada con el error puede ofrecer una suposición razonable de si es un error del sistema operativo o nuestro código.
Realiza búsquedas en la tabla syslog cada vez que tengas un problema que no puedas resolver, es posible que la pista esté allí.
Utilice la Date/Time, el número de entradas y el total de sucesos para determinar si necesita aumentar el tamaño de su tabla de syslog
Conoce lo que tu sistema registra en la tabla syslog para controlar si se producen cambios o entradas nuevas o diferentes
Las entradas en la tabla syslog no necesariamente son un problema. El tenerlas controladas es la clave
Artículo
Eduardo Anglada · 27 mayo, 2021
En este artículo voy a mostrar los resultados de una comparación entre IRIS y Postgress manejando datos Astronómicos.
Introducción
Desde siempre el cielo nocturno nos ha fascinado. Todos hemos soñado con las estrellas y la posibilidad de que haya vida en otros planetas.
Los astrónomos llevan siglos identificando y clasificando estrellas. Existen catálogos compilados en Mesopotamia y Egipto desde el siglo 2 AC [1]. Durante siglos se han ido compilando catálogos nuevos y recientemente hay dos que sobresalen del resto: Hipparcos [2] y Gaia [3]. Ambos han sido elaborados por la Agencia Europea del Espacio (ESA) que construyó naves espaciales dedicadas al mapeo y caratacterización de estrellas y meteoritos.
Hipparcos fue un proyecto pionero, lanzado en 1989, y permitió determinar la posición de cien mil estrellas con mucha precisión y un millón con una menor precisión. En el año 2000 se publicó el catálogo definitivo que aumentó el número de estrellas a 2.5 millones e incluye el 99% de las estrellas cuya magnitud (medida de brillo sin unidades, ver [4]) es menor que 11. Este catálogo se sigue usando mucho hoy en día.
En 2013 ESA lanzó Gaia [4], una nueva nave espacial dedicada al mapeo de la Vía Láctea y, por supuesto, cualquier otra estrella que detecte. No sólo calcula sus posiciones, también sus velocidades y muchos otros parámetros físicos: brillo en las bandas del rojo y azul, temperaturas superficiales, luminosidad, radio e incluye miles de meteoritos del Sistema Solar. En total el catálogo cuenta con unas 100 columnas que describen las diferentes propiedades y sus errores correspondientes. Todas las estrellas se han clasificado usando el mismo criterio y precisión, pero solo un subconjunto tiene los datos completos. Para el resto, por desgracia, son desconocidos. Durante años los científicos han trabajado duro para que los datos sean lo más fiables posibles.
A lo largo de los años ESA ha publicado varias versiones del catálogo de Gaia y en esta comparativa vamos a usar la versión 2, que es la primera que incluye los resultados para 1.6 billones de estrellas y varios cientos de miles de meteoritos. Los catálogos de Gaia se han usado en miles de artículos científicos.
Los catálogos de Gaia están redifiniendo la astronomía y son un salto cualitativo, tanto en calidad como en calidad, en comparación con los de Hipparcos. Ambos son abiertos y se pueden consultar y descargar desde el servicio de archivos [5]. El Centro Europeo de Astronomía Espacial (ESAC son sus siglas en inglés) es el encargado de custodiar y publicar los catálogos, que están disponibles tanto para la comunidad científica como para el público en general. Para reproducir los resultados se pueden descargar los datos desde el archivo oficial: Gaia Archive (esa.int)
Representación del área de la Vía Láctea que está siendo estudiada por Gaia. (Reproducido gracias a la Licencia Creative Commons By Attribution 4.0 license. Credit: "galaxymap.org, Twitter: @galaxy_map").
Gaia pretende cubrir en torno al 1-2 por ciento de las estrellas de la Galaxia.
Cache e IRIS en el procesado de datos diarios de Gaia
La nave Gaia se encuentra en el punto de Lagrange L2 definido por la Tierra y el Sol, a unos 1.5 millones de kilómetros de la Tierra. Siempre está girando y tomando datos, que se reciben en la Tierra usando las antenas localizadas en Cebreros (Ávila, España), New Norcia (Australia) y Malargüe (Argentina). Cada antena envía los datos a Alemania, donde el Centro de Operaciones de Misiones (MOC, en inglés) recibe la telemetría consistente en los datos científicos y los del estado de la nave. El MOC comprueba el estado de la nave y envía los datos científicos al Centro de Operaciones Científicas (SOC en inglés) en ESAC (Madrid). El promedio diario de datos que recibe el SOC son 40GB, pero fluctúa bastante y puede llegar a los 110GB cuando Gaia está observando el plano de la Galaxia.
InterSystems Caché juega un papel fundamental en el análisis diario de los datos. Gracias a sus capacidades Caché permite acceder en línea a bases de datos de 40TB. Después de ser recibida la telemetría se descomprime e ingesta en InterSystems Caché. El análisis de los datos y las imágenes es llevado a cabo por diferentes programas, pero todos ellos emplean la instancia de Caché para almacenar y leer los resultados. El análisis preliminar de los resultados se tiene que llevar a cabo en menos de 24h para poder crear una alerta científica si, por ejemplo, explotase una Supernova. Caché es capaz de almacenar todos los datos del análisis y gracias a su velocidad y resilencia el SOC es capaz de llevar a cabo su trabajo muy satisfactoriamente. En la actualidad se está llevando a cabo la migración a InterSystems IRIS, la nueva evolución de Caché.
Además los científicos determinan el estado de los diferentes instrumentos y la calidad científica de los resultados. Estos son enviados al resto de centros encargados de su procesamiento (DPAC in inglés), formado por universidades, centros de investigación y observatorios. Los resultados de los estudios realizados por estos centros se guardan en ESAC y se publican como los diferentes catálogos de Gaia.
Rendimiento de IRIS
Vamos a comparar el rendimiento de InterSystems IRIS 2020.1 contra Postgress 12 usando los datos del catálogo Gaia DR2 que han sido descargados en formato CSV del archivo oficial Gaia Archive (esa.int).
Como el catálogo ocupa 1.1 TB y no disponemos de espacio suficiente solo usamos 99GB correspondiente a los archivos CSV cuyo nombre sigue este patrón: Gaia_Source_1*.csv.
En este repositorio de github se pueden encontrar las instrucciones completas para reproducir estos resultados.
Preparación
Servidor: Ubuntu 20.04 con los últimos parches. 32GB of RAM. Procesador: i7-4790 @ 4.00 GHz 4 cores físicos.
Disco: Sabrent 1TB NVME
Herramienta empleada para llevar a cabo las consultas: DBeaver 21.0.1, usa un driver jdbc apropiado para cada instancia.
IRIS: versión 2020.1, empleando10GB of RAM y archivos de base de datos de 8K.
Postgres: versión 12, instalación por defecto de Ubuntu.
Ingestión de los datos
Los archivos descargados del archivo han sido concatenados en un único archivo.
Ingestión de 115453122 filas con información de las estrellas (94 columnas)
IRIS
Hemos utilizado la utilidad de IRIS SimpleDataTransfer (incluida en la distribución de IRIS). Esta herramienta usa el driver jdbc de IRIS para llevar a cabo la ingestión. Ésta se lleva a cabo en paralelo usando 10 trabajos que ingestan 200000 líneas cada uno.
Postgres
Hemos empleado pg_bulkload, que también corre en paralelo y escribe directamente en los archivos de la base de datos sin tener el equivalente a los archivos "journal" activos.
IRIS: 1525 s
Postgres: 2562 s
Consultas simples
Las siguientes consultas son simples y devuelven un conjunto de estrellas que cumplen un requisito dado:
Posiciones: estrellas con un error pequeño en la posición
La consulta es:
SELECT * FROM gdr2 WHERE parallax_over_error > 1000
Los resultados son:
IRIS: 645 filas .5 s
Postgres: 645 filas 108 s
Estrellas azules
Seleccionar aquellas estrellas con una emisión importante en el azul. La consulta es:
select count(*) from gdr2 where bp_rp < -2
Los resultados son:
IRIS: 515 filas 2ms
Postgres: 515 filas 150s
Estrellas con movimiento propio importante
Esta consulta devuelve aquellas estrellas con una velocidad transversal importante con respecto a la Tierra:
select * from public.gdr2 where (
pmra < -707.1
or pmra > 707.1
or pmdec < -707.1
or pmdec > 707.1
)
and sqrt(pmra * pmra + pmdec * pmdec) > 1e3
IRIS 94 filas 34 ms
PG 94 filas 153 s
Resultados de alta calidad (errores pequeños y un número significativo de observaciones)
Anthony Brown el líder de DPAC (el consorcio dedicado al tratamiento de los datos) tiene en su github varios ejemplos. Entre ellos destaca una consulta "simple" que recopila aquellas estrellas con errores pequeños:
select source_id, ra, ra_error, dec, dec_error, parallax, parallax_error, parallax_over_error, pmra,
pmra_error, pmdec, pmdec_error, ra_dec_corr, ra_parallax_corr, ra_pmra_corr, ra_pmdec_corr,
dec_parallax_corr, dec_pmra_corr, dec_pmdec_corr, parallax_pmra_corr, parallax_pmdec_corr,
pmra_pmdec_corr, radial_velocity, radial_velocity_error,
phot_g_mean_mag, phot_bp_mean_mag, phot_rp_mean_mag, bp_rp, g_rp, bp_g,
2.5/log(10)*phot_g_mean_flux_over_error as phot_g_mean_mag_error,
2.5/log(10)*phot_bp_mean_flux_over_error as phot_bp_mean_mag_error,
2.5/log(10)*phot_rp_mean_flux_over_error as phot_rp_mean_mag_error,
sqrt(astrometric_chi2_al/(astrometric_n_good_obs_al-5)) as uwe
from gaiadr2.gaia_source
where parallax_over_error>5
and radial_velocity is not null
and astrometric_params_solved=31
and rv_nb_transits > 3
Resultados
IRIS: En 14m 22s devuelve una lista con 736496 estrellas.
PG: Después de 20m sólo ha encontrado 495895 estrellas.
Mapa de la Galaxia con densidad de estrellas
Desde el año 2009 se publican en https://www.galaxymap.org una serie de mapas de la Vía Láctea. Incluyen mucha información, como un mapa de densidad de estrellas. Mediante isosuperficies (superficies 3D en las que la densidad de estrellas es constante) podemos explorar la Vía Láctea. La última versión emplea los datos de Gaia y el código fuente está disponible en github. Hemos llevado a cabo las mismas consultas en IRIS y Postgress:
SELECT source_id,designation , l , b , parallax , parallax_over_error , phot_g_mean_mag , bp_rp , priam_flags , teff_val , a_g_val FROM gdr2 WHERE ( parallax_over_error > 10 ) and (parallax >= 1.0) and (parallax < 1.1)
Resultados:
IRIS: 11m 49s
PG: timeout
Imagen ejemplo, los colores indican las zonas con una densidad de estrellas constante:
Tal y como indica el autor en su blog el archivo oficial de Gaia tiene un tiempo máximo por consulta de 30 minutos, por lo que el autor se vió obligado a realizar muchas consultas y tardó 24 h en obtener todos los datos. IRIS es mucho más rápido y permite obtener los datos en menos tiempo.
Aquí se puede ver una animación de las isosuperficies:
y una descripción completa de los resultados:
Todos los materiales empleados se distribuyen bajo la licencia: Creative Commons By Attribution 4.0. Crédito: "galaxymap.org, Twitter: @galaxy_map".
Conclusión
IRIS es una plataforma de datos robusta que puede gestionar sin problemas las consultas más complejas y a máxima velocidad.
References
[1] Ancient Star Catalogs. Star chart - Wikipedia
[2] ESA Hipparcos mission. Hipparcos - Wikipedia
[3] ESA Gaia mission. ESA Science & Technology - Gaia
[4] Magnitude: Star brightness. Magnitude (astronomy) - Wikipedia
[5] Parsec: Astronomy unit of length corresponding to 3.26 light years Parsec - Wikipedia
Pregunta
Yone Moreno · 21 sep, 2022
Buenos días,
💭🧱🧑💻 Hemos estado indagando y construyendo gracias al enorme apoyo, soporte, y asistencia ofrecida por el siguiente ejemplo:
https://es.community.intersystems.com/post/ejemplo-de-integraci%C3%B3n-dicom-con-un-simulador
Y del código de Github de los circuitos de ejemplo para el FIND y el MOVE:
https://github.com/intersystems-ib/iris-dicom-sample
Sería de agradecer si ustedes nos leen y responden a las siguiente cuestiones:
Desarrollando el MOVE, nos hemos encontrado con que en los primeros envíos sí se realiza y obtenemos respuesta:
MOVE
STORE
Sin embargo, ocurre algo único cuando se ejecutan varias veces el MOVE. El proceso genera una Excepción:
ERROR <Ens>ErrBPTerminated: Finalizando BP DICOM Move Process # debido a un error: ERROR <Ens>ErrException: <READ>zAcceptPDU+4^EnsLib.DICOM.Adapter.TCP.1 -- - registrado como '-' número - @''> ERROR <Ens>ErrException: <READ>zAcceptPDU+4^EnsLib.DICOM.Adapter.TCP.1 -- - registrado como '-' número - @''
¿De qué manera se podría entender y comprender la Excepción? Para depurarla posteriormente
En concreto para dar más información actualmente estamos simulando ser ORIGEN mediante:
./movescu -b VNAPRE -c ESBPRE@AA.BBB.C.DDD:2021 -m StudyInstanceUID="1.2.156.14702.1.1000.16.0.20200311113603875" --dest ESBPRE
Y además simulamos un DESTINO empleando para ello:
./dcmqrscp --ae-config ./shared/ae.properties -b VNAPRE:11118 --dicomdir ./shared/DICOMDIR
Siendo el circuito del MOVE:
Estamos empleando el código original de la clase DICOM.BP.MoveProcess publicado en el ejemplo:
https://raw.githubusercontent.com/intersystems-ib/iris-dicom-sample/master/iris/src/DICOM/BP/MoveProcess.cls
Además hemos tratado de emplear distintos puertos para DESTINO: 11114, 11116 11118 etc
Y ocurre de igual forma:
ERROR <Ens>ErrBPTerminated: Finalizando BP DICOM Move Process # debido a un error: ERROR <Ens>ErrException: <READ>zAcceptPDU+4^EnsLib.DICOM.Adapter.TCP.1 -- - registrado como '-' número - @''> ERROR <Ens>ErrException: <READ>zAcceptPDU+4^EnsLib.DICOM.Adapter.TCP.1 -- - registrado como '-' número - @''
🎓🔎🐞🩹🧑💻 ¿De qué manera se podría entender y comprender la Excepción? Para depurarla posteriormente 🔍
Muchas gracias de antemano por su tiempo
Un saludo
Hola Yone,
Podrían ser errores de red, o de timeouts durante la transferencia.
Echa un vistazo a [Tasks for DICOM Productions](https://docs.intersystems.com/healthconnect20221/csp/docbook/DocBook.UI.Page.cls?KEY=EDICOM_tasks), en particular a [Configuring a DICOM Duplex Business Host](https://docs.intersystems.com/healthconnect20221/csp/docbook/DocBook.UI.Page.cls?KEY=EDICOM_tasks#EDICOM_task_duplex_host_configure). Tienes varios configuraciones que quizá te ayuden como `TraceVerbosity`, `ARTIM`, `TXTIM ` para mostrar más o menos información y modificar ciertos *timeouts*.
También te puede servir lo que te dice el otro lado de la comunicación (lo que parece que es el simulador).
De todas formas, ten en cuenta que parece que estás trabajando con un simulador y a veces pueden tener ciertas limitaciones. Hola Alberto,
Gracias por indicarme los recursos y sobre todo agradezco que me especificaras en concreto qué propiedades aportan en este caso.
He configurado:
Seguimiento del nivel de detalle = 2
ARTIM = 20
TXTIM = 20
Y ahora sí se recibe la respuesta del Establecer conexión desde el Simulador ( Destino ) hacia la Operacion TCP Dicom ( Receptora )
Lo que sí es cierto, resulta que ocurre la excepción de forma similar, casi igual en el paso [6]; justo antes de recibir la Notificación de Conexión establecida por parte del SIMULADOR dcm4che hacia la Operacion del ESB:
ERROR <Ens>ErrBPTerminated: Finalizando BP DICOM Move Process # debido a un error: ERROR <Ens>ErrException: <READ>zAcceptPDU+4^EnsLib.DICOM.Adapter.TCP.1 -- - registrado como '-' número - @''> ERROR <Ens>ErrException: <READ>zAcceptPDU+4^EnsLib.DICOM.Adapter.TCP.1 -- - registrado como '-' número - @''
Lo más acuciante e intrigante es el hecho de que el Simulador dcm4che que replica ser un Sistema ORIGEN (envía al servicio) se queda a la escucha, tras haber enviado el C-MOVE-RQ:
Y el simulador que actúa como DESTINO se queda en el estado de Asociación Establecida, sin embargo no le llega efecitamente el C-MOVE-RQ, lo cual es muy llamativo :
¿ De qué forma nos recomiendan depurar ?
Muchas gracias de antemano por su tiempo, por leerme y responder. Un saludo Hola de nuevo, Yone:
Puedes probar a subir los timeous por ejemplo a 60s.
Además, puedes activar las trazas detalladas de interoperabilidad. Quizá te ponga así más información. Mira en:
https://docs.intersystems.com/irisforhealth20221/csp/docbook/Doc.View.cls?KEY=EMONITOR_tracing
```
set ^Ens.Debug("TraceCat","queue")=1
```
En otro caso, quizá habría que ya verlo a nivel de red (e.g. captura con WireShark y demás), o si lo quieres ver muy en detalle abrir un caso WRC.
Artículo
Heloisa Paiva · 23 sep, 2022
En este artículo vas a encontrar un sencillo programa con Python en un entorno IRIS y otro sencillo programa con ObjectScript en un entorno Python. Además, me gustaría compartir algunos de los errores que tuve cuando empecé la implementación de estos códigos.
Python en entorno IRIS
Supongamos, por ejemplo, que estás en un entorno IRIS y quieres resolver un problema que crees más fácil o más eficiente de resolver en Python.
Puedes simplemente cambiar el entorno: crea tu método como cualquier otro, y al final del nombre y sus especificaciones, añade [ Language = python ]:
Puedes usar todo tipo de argumento en el método y, para acceder a ellos, haces lo mismo que harías en COS;
Supongamos que tenemos este argumento %String, llamado Arg, y un argumento OtherArg que viene de una clase custom. Esta clase puede tener propiedades como Title y Author. Puedes accederlas así:
Este método trae un output así:
Para acceder a los métodos de clase, es básicamente lo mismo. Si hay un método en Demo.Books.PD.Books (de OtherArg), llamado “CreateString”, que concatena el título y el autor en algo como "Title: <Title>; Author: <Author>", entonces, anãdir esto al final de nuestro método Python:
proporcionará este output:
Para acceder al método, solo es necesario OtherArg.CreateString(), pero elegí usar los mismos valores en OtherArg (CreateString(OtherArg)) para que los outputs sean similares y el código, más simple.
ObjectScript en entorno Python
También hay situaciones en las que estás desarrollando en Python, pero quieres utilizar ObjectScript para ayudarte.
Para empezar, puede que quieras revisar esta lista para poder acceder a tus archivos COS desde un entorno Python de muchas maneras (nos las voy a usar todas aquí):
¿Tienes los requisitos previos? Compruébalo aquí
¿Puedes usar un servidor externo Python? Compruébalo aquí
¿Tienes todos los drivers necesarios? Descárgalos aquí o Aprende más aquí
Siempre puedes volver a esos enlaces o leer la lista de errores que encontré mientras creaba mis primeros programas Python con COS, por si te ayuda.
¡Entonces, empecemos a programar!
Antes de todo, hay que adaptar lo que viene de COS para Python. Por suerte, InterSystems ya lo hizo y solo hay que escribir “import iris” al comienzo del código para acceder a todo!
En el próximo paso, creamos una conexión al namespace deseado, usando una ruta con el host, port y namespace (host:port/namespace) y dándole usuario y contraseña:
Fíjate cómo creamos un OBJETO IRIS, para poder usar esta conexión para acceder a todo que necesitamos del namespace.
Finalmente, puedes “programar” todo lo que quieras!
Puedes acceder a métodos con irispy.classMethodValue(), dando el nombre de la clase y el nombre y los argumentos del método, puedes manipular objetos con .set() (para propiedades), y muchas otras posibilidades, mientras usas Python como quieras.
Para más informaciones sobre funciones de iris y cómo usarlas, echa un vistazo a Introduction to the Native SDK for Python
En este ejemplo, en la línea 16, he instanciado una clase %Persistent para, en las próximas líneas darles los valores “Lord of The Rings” y “Tolkien” para las propiedades Title y Author.
En la línea 20, llamé un método de otra clase que guarda el objeto en una tabla y vuelve un status si funciona. Finalmente, en la línea 23, vuelve el status.
Mezcla!
En un entorno ObjectScript, puedes necesitar de las conocidas librerías de Python, o tus propios archivos con tus funciones y rutinas.
Puedes usar el comando “import” con NumPy, SciPy y cualquier otro que quieras (considerando que las tienes correctamente instaladas: mira aquí cómo hacerlo)
Pero también, si quieres acceder a tus archivos locales hay muchas maneras de hacerlo y es fácil encontrar tutoriales para eso, ya que Python es muy popular.
A mí personalmente esta es la que más me gusta:
Aquí importé todo del archivo testesql.py, situado en C:/python y dejé impreso todos los resultados de la función select()
Extras - problemas que encontré
SHELLS: Cuando usas Shells, acuerdate de que Windows PowerShell funciona cómo un sistema UNIX-based (porque está basado en .NET) y el Command Prompt es el que funcionará con los ejemplos de Windows en la documentación oficial de InterSystems. Eso puede parecer simple para programadores con más experiencia, pero si no estás prestando mucha atención puedes perder un tiempo con este detalle.
USUARIOS Y PRIVILEGIOS: El usuario actual para programar con ObjectScript en un entorno Python necesita tener privilegios para los recursos del namespace. Recuerda que si no se selecciona ningún usuario, el actual es UnkownUser, y si no se selecciona ningún namespace, el actual es USER. Entonces, en el acceso más simple: Portal de Administración > Administración del Sistema > Seguridad > Usuarios > Unknown User > Roles, y selecciona %DB_USER, y guarda.
NO SÉ QUÉ PASA: Para tener más informacion sobre los errores que estás teniendo, ve a: Portal de Administración > System Explorer > SQL y escribe “SELECT * FROM %SYS.Audit ORDER BY UTCTimeStamp Desc”, y ahí vas a tener las más recientes audits. Acordate de seleccionar el namespace correcto para la consulta de la query. Así, puedes leer las causas de errores como IRIS_ACCESSDENIED() y mucho más, que puedes tener hacia fuera del ambiente IRIS.
ERROR COMPILANDO PYTHON: Evita llamar a Métodos con nombres como try() o con los de otras funciones de Python. El compilador no sabrá la diferencia entre el nombre del método y el nombre de la función.
¡Gracias por leer y, por favor, comparte tus comentarios, sugerencias o dudas!