Limpiar filtro
Artículo
Pablo Frigolett · 22 nov, 2021
Servidor Externo de Lenguaje Python en un contenedor
La primera vez que se intenta iniciar un servidor externo de lenguaje Python (de aquí en adelante Gateway de Python), en la versión en contenedor, intenta ejecutar cierto código para detectar si está instalado el paquete python3-venv.
La imagen que está en containers.intersystems.com:
a la fecha de este artículo es la versión 2021.1.0.215.0
está basada en Ubuntu 18 LTS
viene con python 3.6.9 instalado
Para trascender al contenedor en inicio de nuestro Gateway de Python - para las actualizaciones por ejemplo - es necesario tener un punto de montaje externo para /usr/irissys/dev/python/virtual. Allí se registra los entornos virtuales de python que IRIS crea cuando inicia por primera vez un Gateway de Python. El usuario de sistema operativo que ejecuta los scripts de inicio del Gateway de Python, debe poder escribir en ese directorio. La razón es que además de crear allí el entorno virtual de python, cada vez que se intenta iniciar el Gateway, intenta crear un archivo temporal de prueba allí.
A continuación haremos:
creación de un contenedor con pasos adicionales para el inicio por primera vez del Gateway de Python. Los pasos adicionales son incluir un punto de mensaje, instalar unos paquetes e iniciar por primera vez el Gateway de Python.
creación de un contenedor con menos pasos adicionales para los subsecuentes inicios del Gateway Python. Los pasos adicionales son incluir el punto de montaje para python y darle privilegio de escritura a todos los usuarios.
Creación de un contenedor e inicio del Gateway de Python por primera vez
Este paso crea el contenedor de la misma forma que se creará en otras ocasiones agregando la instalación del paquete python3-venv y sus dependencias, el inicio del Gateway de Python. Se asume que en el directorio /data/durable (también en negrita en el comando) está el archivo iris.key. Y la versión del contenedor es la que existía al momento de escribir este artículo. En el directorio /data/python se creará el entorno virtual de python. Ese directorio se vuelve read-only al menos en versiones docker-desktop en Mac y docker-ce en linux. Por eso el comando que sigue a la creación busca dar permiso para la creación del entorno virtual de python al usuario que ejecute IRIS en el contenedor.
Entonces para crear el contenedor aqui va el método más crudo (directo con docker):
docker run -d -v /data/durable:/dur -v /data/python:/usr/irissys/dev/python/virtual --name iris --cap-add IPC_LOCK containers.intersystems.com/intersystems/iris:2021.1.0.215.0 --key /dur/iris.key
chmod 777 /data/python
Ahora es necesario instalar el paquete python3-venv con sus dependencias e iniciar el Gateway de Python. El contenedor necesita acceso a internet y el comando se ejecuta como root (-u 0).
docker exec -u 0 iris bash -c "apt-get update && apt-get install -y python3-venv"
Si todo va bien, veremos en la últimas líneas "Setting up python3-venv" entre otros mensajes.
Con esto, solamente faltaría iniciar el Gateway de Python que puede tomar desde los 20s mientras crea el entorno virtual, instala el módulo de iris nativo e inicia el proceso :
docker exec iris iris terminal IRIS <<EOFCMD
w "Iniciando:",\$system.external.startServer("%Python Server")
w "Terminando:",\$system.external.stopServer("%Python Server")
halt
EOFCMD
con esto, el entorno virtual queda creado en /data/python/'%Python Server_3252368016' que puede ser compartido con otros contenedores. Para no seguir con un contenedor con paquetes instalados inútilmente, eliminamos el contenedor para volver a crearlo en el próximo paso.
docker stop iris
docker rm iris
Creación de un contenedor con menos pasos adicionales
Ya creado nuestro entorno virtual Python, el único paso adicional necesario a dar permiso de escritura en la carpeta que es montada (en Mac y Linux al menos). De nuevo, directo desde docker:
docker run -d -v /data/durable:/dur -v /data/python:/usr/irissys/dev/python/virtual --name iris --cap-add IPC_LOCK containers.intersystems.com/intersystems/iris:2021.1.0.215.0 --key /dur/iris.key
chmod 777 /data/python
El tema de los permisos en mi caso fue devastador. Tuve la suerte de encontrarme con ese problema y tener que indagar qué estaba haciendo que fallara el inicio del Gateway de Python. Los scripts setup.sh y runpython.sh ubicados en /usr/irissys/dev/python eran los que se caian con diversos problemas (que no podían crear un archivo, que no estaba el paquete python3-venv, etc.).
Suerte!
Artículo
Ricardo Paiva · 9 feb, 2023
Hola a todos,
Aquí estamos de nuevo. Nuevo año, nuevo concurso, nuevo proyecto, viejos motivos.
¡Triple Slash ya está en casa!
1999, el año que aprendí a programar, mi primer "if," mi primer "Hello world."
Aún recuerdo a mi profesor explicándonos en aquella clase el sencillo "while" y cómo podemos saber si se cumplió una condición específica. ¿Te acuerdas, @Renato.Banzai? El profesor Barbosa, un tipo único.
Desde entonces, me ha encantado la idea de programar, transformando ideas en proyectos, en algo útil. Pero todos sabemos que para crear algo, necesitamos asegurarnos de que está funcionando; necesitamos no solo crear, sino también probar si funciona y si no se rompe si añadimos algo nuevo.
Y para ser honesto con todos vosotros, hacer pruebas es aburrido. Al menos para mí, no tengo nada contra vosotros si os gusta.
Usando una analogía, podría decir que crear métodos de prueba es como limpiar la casa o planchar la ropa. Es aburrido, pero necesario.
Con estas ideas en mente, ¿por qué no desarrollar una forma mejor y más sencilla de probar?
Así que, inspirado por el estilo de elixir y por esta idea de InterSystems Ideas (gracias, @Evgeny.Shvarov), intentamos mejorar el proceso de prueba y convertirlo en una tarea divertida otra vez.
Simplificamos el %UnitTest y para mostraros cómo usar TripleSlash para crear pruebas unitarias (unit tests), vamos a utilizar un ejemplo sencillo.
Pongamos que tienes la siguiente clase y método del que te gustaría escribir una prueba unitaria:
Class dc.sample.ObjectScript
{
ClassMethod TheAnswerForEverything() As %Integer
{
Set a = 42
Write "Hello World!",!
Write "This is InterSystems IRIS with version ",$zv,!
Write "Current time is: "_$zdt($h,2)
Return a
}
}
Como se puede ver, el método TheAnswerForEverything() solo devuelve el número 42. Así que vamos a marcar en la documentación del método cómo TripleSlash debería crear una prueba unitaria para este método:
/// A simple method for testing purpose.
///
/// <example>
/// Write ##class(dc.sample.ObjectScript).Test()
/// 42
/// </example>
ClassMethod TheAnswerForEverything() As %Integer
{
...
}
Las pruebas unitarias deben estar todas en una etiqueta <example></example>. Se puede añadir cualquier tipo de documentación, pero todas las pruebas tienen que estar dentro de ese tipo de etiqueta.
Ahora, arranca una instancia de IRIS e inicia una sesión de terminal, ve al namespace IRISAPP, crea una instancia de la clase Core pasando el nombre de clase (o su nombre de paquete para todas sus clases) y después ejecuta el método Execute():
USER>ZN "IRISAPP"
IRISAPP>Do ##class(iris.tripleSlash.Core).%New("dc.sample.ObjectScript").Execute()
TripleSlash interpretará esto como "Dado el resultado del método Test(), afirmo que es igual a 42". Así que se creará una nueva clase dentro de la prueba unitaria:
Class iris.tripleSlash.tst.ObjectScript Extends %UnitTest.TestCase
{
Method TestTheAnswerForEverything()
{
Do $$$AssertEquals(##class(dc.sample.ObjectScript).TheAnswerForEverything(), 42)
}
}
Ahora, vamos a añadir un nuevo método para probar otras formas de decir a TripleSlash cómo escribir las pruebas unitarias.
Class dc.sample.ObjectScript
{
ClassMethod GuessTheNumber(pNumber As %Integer) As %Status
{
Set st = $$$OK
Set theAnswerForEveryThing = 42
Try {
Throw:(pNumber '= theAnswerForEveryThing) ##class(%Exception.StatusException).%New("Sorry, wrong number...")
} Catch(e) {
Set st = e.AsStatus()
}
Return st
}
}
Como se puede ver, el método GuessTheNumber() espera un número, devuelve $$$OK solo cuando se pasa como argumento el número 42 o un error para cualquier otro valor. Así que vamos a marcar en la documentación del método cómo TripleSlash debería crear una prueba unitaria para este método:
/// Another simple method for testing purpose.
///
/// <example>
/// Do ##class(dc.sample.ObjectScript).GuessTheNumber(42)
/// $$$OK
/// Do ##class(dc.sample.ObjectScript).GuessTheNumber(23)
/// $$$NotOK
/// </example>
ClassMethod GuessTheNumber(pNumber As %Integer) As %Status
{
...
}
Ejecuta otra vez el método Execute() y verás un nuevo método de prueba en la clase de prueba unitaria iris.tripleSlash.tst.ObjectScript:
Class iris.tripleSlash.tst.ObjectScript Extends %UnitTest.TestCase
{
Method TestGuessTheNumber()
{
Do $$$AssertStatusOK(##class(dc.sample.ObjectScript).GuessTheNumber(42))
Do $$$AssertStatusNotOK(##class(dc.sample.ObjectScript).GuessTheNumber(23))
}
}
En este momento, las siguientes afirmaciones están disponibles: $$$AssertStatusOK, $$$AssertStatusNotOK y $$$AssertEquals.
TripleSlash nos permite generar pruebas desde ejemplos de código que se encuentran en las descripciones de métodos. Te ayuda a matar dos pájaros de un tiro, mejorando tu documentación de clase y creando automatización de pruebas.
ReconocimientoUna vez más, me gustaría agradecer todo el apoyo de la Comunidad en todas las aplicaciones que creamos! Muchas gracias @Ricardo.Paiva Muito obrigado pelo excelente artigo @Henrique.GonçalvesDias
Artículo
David Reche · 9 jun, 2019
¡Hola Comunidad!Este artículo es una guía sencilla sobre cómo preguntar y cómo conseguir respuestas en la Comunidad. Ya que el objetivo obvio cuando publicamos una pregunta en la Comunidad es obtener una respuesta, veamos cómo conseguir buenas preguntas que tengan visibilidad para encontrar fácilmente.Cuando se publica una pregunta es necesario completar tres campos: Título, Cuerpo y Grupo, además de las etiquetas.1. El TítuloUn buen título debería contener una descripción breve de tu problema - no debería ser más largo de 80 o 90 caracteres.Pero tampoco debería ser demasiado breve - un título de una única palabra tampoco es una buena idea. Ejemplos de buenas preguntas:Consultar una lista de propiedades con SQLCómo ignorar la cabecera en un fichero CSV cuando se usa Record MapperEquivalente a $CASE o $SELECT en SQL2. El CuerpoEl cuerpo debería contener una descripción de tu problema de manera textual y opcionalmente con un ejemplo de código como ObjectScript, SQL, JS u otros lenguajes. Utiliza bloques de código para resaltar el código ObjectScript.Proporcionar la versión del producto utilizado siempre es útil (puedes obtenerla con $zversion desde el Terminal).Y siempre que sea posible, pregunta solo una cosa en el cuerpo. Si tienes más de una pregunta y puedes separarlas, es mejor crear dos entradas diferentes. De esta forma, será más fácil para otros miembros encontrar respuestas a tus preguntas.3. El GrupoEl grupo es una etiqueta obligatoria que ayuda a categorizar tus preguntas, asociándolas a uno de los Productos de InterSystems (IRIS, Caché, Ensemble, HealthShare), Tecnologías (DeepSee, iKnow) or Servicios (Online Learning, WRC).4. EtiquetasUtiliza etiquetas para facilitar a otros miembros expertos (que están suscritos a diferentes etiquetas) a encontrar tu pregunta. Puedes elegir diferentes etiquetas relacionadas con desarrollo, pruebas, gestión de cambios, despliegues y entornos.Si preguntas correctamente y el texto es claro, se suelen obtener respuestas en poco tiempo. Puedes observar qué preguntas tienen respuestas en el contador de respuestas con fondo verde, a la derecha de cada pregunta. Y acuérdate de marcar una respuesta como aceptada (indicando que la pregunta ha sido resuelta) en caso de que la respuesta resuelva tu duda y se ajuste a lo que necesitabas.Por supuesto, esta no es la lista definitiva de recomendaciones sobre cómo hacer buenas preguntas. Así que puedes añadir tus comentarios e ideas sobre el post, para complementar lo que creas oportuno. ¡Gracias!
Artículo
Nancy Martínez · 20 abr, 2021
Al trabajar desde casa durante estos "días de coronavirus", me faltan recursos.
- no tengo ninguna máquina Linux disponible
- espacio en disco limitado
Además, Docker Desktop (en Windows10) bloqueó de alguna manera los scripts de [Durable %SYS](https://docs.intersystems.com/iris20192/csp/docbook/Doc.View.cls?KEY=ADOCK_iris_durable) como se describe [**aquí.**](https://community.intersystems.com/post/docker-vs-durability)
Investigando el caso, descubrí que se almacenaban muchos más datos de los que realmente necesitaba.
Así que diseñé mi durabilidad personalizada.
De forma similar a lo que hice tiempo atrás para los contenedores de Caché.
Utilizando las características de [**iris-main **](https://docs.intersystems.com/iris20192/csp/docbook/Doc.View.cls?KEY=ADOCK#ADOCK_iris_iscmain)agrego un script de pre-procesamiento para iniciar mi contenedor con
Después de que termina, guardo lo que creo necesitar para el próximo inicio.
Y eso es bastante menos.
De acuerdo: Puede que se me escapen algunas cosas y finalmente es más lento en start/stop. Muy bien. ¡Pero está en mis manos!
Los scripts para ejecutar y también los datos guardados están almacenados en el directorio externo que necesito para la licencia.
Y esos scripts son bastante sencillos:
# post.copycp -uf /usr/irissys/iris.cpf /external/iris.cpfcp -uf /usr/irissys/mgr/IRIS.DAT /external/mgr/IRIS.DATcp -uf /usr/irissys/mgr/messages.log /external/console.log
y
# pre.copycp -f /external/iris.cpf /usr/irissys/iris.cpfcp -f /external/mgr/IRIS.DAT /usr/irissys/mgr/IRIS.DATrm -f /usr/irissys/mgr/messages.log
Como no hay nada que configurar en la primera ejecución, omito pre.copy como la primera ejecución hecha.
Por lo demás, mi comando de ejecución en Docker tiene el siguiente aspecto:
docker run --name iris1 --init -it -p 52773:52773 -p 51773:51773 --volume c:/external:/external --rm intersystems/iris:2020.2.0.198.0 --key /external/key/iris.key -e /external/post.copy -b /external/pre.copy
Si tiempo después descubro que necesito guardar/recuperar algo más (por ejemplo, para CSP, ... ) es fácil añadirlo.
La clave del éxito fue dejar la base de datos IRISSYS en la ubicación que se incluye en el contenedor. Su tamaño de ~92 MB (en mi caso) no es relevante.
Anuncio
Esther Sanchez · 8 jun, 2022
¡Hola desarrolladores!
Hemos hecho algunos cambios en los sitios web de las Comunidades de Desarrolladores de InterSystems:
🆕 Mejor seguimiento de eventos en marcha
🆕 Programación de publicaciones
🆕 Formato del código mejorado
🆕 Creación de tablas mejorada
🆕 Mejor seguimiento de respuestas
🆕 Nuevo diseño en la parte inferior de las publicaciones
Vamos a explicar en detalle cada uno de ellos.
Eventos EN MARCHA AHORA
Para que la búsqueda de eventos sea aún más sencilla, hemos añadido una nueva sección "EN MARCHA AHORA" en la esquina superior derecha de la página.
Si hacéis clic ahí, iréis a la página del evento.
Programación de publicaciones
Lo habíais pedido... ¡y ya está aquí! Ahora se pueden programar las publicaciones, para que se publiquen en un momento determinado.
Para programar una publicación, solo hay que hacer clic en la flecha hacia abajo al lado del botón "Publicar" y elegir "Programar publicación".
Aparecerá un calendario en el que se puede elegir el día y hora en el que se quiere publicar.
Después, hay que hacer clic en el botón "Programar publicación" y la publicación se publicará en el día y hora elegida. ¡Así de fácil!
Formato del código
Para compartir el código con otras personas, hemos añadido un editor integrado cuando se introduce código.
En él, se puede elegir el lenguaje de programación y el tamaño de la tabulación.
Además, el resaltado de sintaxis se produce automáticamente cuando escribes el texto. Y el lenguaje de programación se muestra en la esquina superior izquierda.
Como resultado, en tu publicación verás el código bonito y ordenado, en el lenguaje de programación elegido.
Creación de tablas
Para simplificar el formato de las tablas, hemos añadido una función para crear tablas rápidamente, solo eligiendo el número de celdas que se necesiten.
Al hacer clic en el botón "More" (Más) se abre una ventana para configurar las propiedades de la tabla.
Respuestas y suscripciones
Para ver toda la información sobre las respuestas de una publicación, hemos añadido el número de respuestas y también el icono de suscribirse al debate, para recibir notificaciones de las nuevas respuestas.
Nuevo diseño en la parte inferior de las publicaciones
Hemos reorganizado y cambiado los iconos en la parte inferior de las publicaciones.
¡Esperamos que os resulten útiles estos cambios!
Podéis solicitar mejoras o reportar errores en el GitHub de la Comunidad. O en los comentarios de esta publicación, claro.
¡Muchas gracias!
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.