#Intégration continue

0 Abonnés · 16 Publications

En ingénierie logicielle, l'intégration continue (CI) est la pratique consistant à fusionner toutes les copies de travail des développeurs sur une version principale partagée plusieurs fois par jour.

Article Sylvain Guilbaud · Oct 29, 2025 14m read

Aperçu

Cette interface Web est conçue pour faciliter la gestion des tables de recherche de données (Data Lookup Tables) via une page Web conviviale. Elle est particulièrement utile lorsque les valeurs de votre table de recherche sont volumineuses, dynamiques et changeantes. Les utilisateurs finaux peuvent gérer efficacement les données de la table de recherche en fonction de leurs besoins grâce à un accès contrôlé à cette interface Web (autorisations de lecture, d'écriture et de suppression limitées à cette page).

Les données gérées via cette interface peuvent être utilisées de manière transparente pour les règles HealthConnect ou les transformations de données, ce qui élimine le besoin d'une surveillance et d'une gestion manuelles constantes des tables de recherche et permet ainsi un gain de temps considérable.

Remarque:
Si la table de recherche de données classique ne répond pas à vos besoins en matière de mappage, vous pouvez créer une table personnalisée et adapter cette interface Web ainsi que sa classe de support avec un minimum de modifications. Un exemple de code de classe est disponible sur demande.

Conditions préalables

Avant d'utiliser l'interface Web, veillez à avoir créé une table de recherche de données vide dans HealthConnect:

  1. Accédez à Interoperability → Configure → Data Lookup Tables.
  2. Cliquez sur New pour ouvrir la fenêtre contextuelle "Create New Lookup Table" (Créer une nouvelle table de recherche).
  3. Saisissez le nom souhaité pour votre table de recherche et cliquez sur OK.
  4. Cliquez sur Save pour finaliser la création de la table.
  5. Vérifiez la création de la table de recherche en cliquant sur Open dans la même fenêtre. Votre nouvelle table devrait apparaître dans la liste.

Dans cette documentation, la table de recherche de données utilisée à titre d'exemple est intitulée:
"Exemple de table de recherche de données"

Mode d'emploi

Le processus de configuration est simple:

  1. Obtention du code:Téléchargez le code fourni à partir du référentiel GitHub ou copiez-le à partir du lien communautaire.
  2. Création d'une page CSP:Créez une nouvelle page CSP dans votre environnement HealthConnect et insérez tout le code du fichier DataLookupWebApp.
  3. Création d'une classe:Copiez et insérez le code de classe fourni dans la documentation ou dans le référentiel.

Configuration

Avant la compilation et l'exécution:

  1. Indiquez le nom de votre table de recherche de données sur la page Web en remplaçant l'espace réservé indiqués par le cadre rouge dans la capture d'écran ci-dessous par le nom réel de votre table de recherche.

  1. Vérifiez si le nom de la classe dans le code correspond au nom de votre classe personnalisée.

  1. Attribuez un nom à votre table de recherche dans la classe, comme indiqué par myDtName = 'votre table de recherche'. Consultez la capture d'écran ci-dessus.
  1. Compilez la classe et la page CSP pour finaliser la configuration.

Mise à l'essai de l'application

Ajout d'enregistrements

  • Saisissez la clé et la paire clé-valeur correspondante dans les champs du formulaire.
  • Cliquez sur Add Record pour insérer les données dans la table de recherche.

Capture d'écran 1: Ajout d'enregistrements (122 – Radiographie de la colonne vertébrale)

Mise à jour de l'enregistrement

  • Sélectionnez un enregistrement existant et modifiez la paire clé-valeur si nécessaire.

  • Cliquez sur Update pour enregistrer les modifications. Exemple : Modification de  122 "Radiographie de la colonne vertébrale" en "Radiographie abdominale".

Suppression de l'enregistrement

  • Sélectionnez un enregistrement à supprimer (144 – IRM)

  • Cliquez sur le bouton Delete pour supprimer l'enregistrement de la table de recherche. Exemple : Suppression de "144 - IRM".

Vérification de la table de recherche de données dans HealthConnect

Accédez à Interoperability → Configure → Data Lookup Tables à Open

Recherche de l'enregistrement

Code / Ressources

  • La version complète du code source est également disponible pour téléchargement et consultation sur GitHub.

https://github.com/pandeySR/Reusable-Web-Interface-for-Data-Lookup-Table

<!DOCTYPE html>
<!--
This web application is designed and developed to insert, update, and delete records (key–value pairs) in the Data Lookup Table through a web interface.
Author: Sanjib Pandey
Date: 16/10/2025
-->
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="author" content="Sanjib Pandey" />
  <title>YOUR TITLE : Data Lookup Table - Data Record </title>
  <!-- Bootstrap CSS CDN, jQuery, Datatable -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> 
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> 
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
  <link rel="stylesheet" href="https://cdn.datatables.net/1.13.5/css/dataTables.bootstrap5.min.css" />
  <script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
  <script src="https://cdn.datatables.net/1.13.5/js/jquery.dataTables.min.js"></script>
  <script src="https://cdn.datatables.net/1.13.5/js/dataTables.bootstrap5.min.js"></script>
  <style>
    h2 {
      text-align: center;
      color: #fff;
      background-color: #00b300;
      padding: 15px 10px;
      border-radius: 5px;
      margin-bottom: 5px;
      margin-top: 0px;
    }
    html, body {
      height: 100%;
      margin: 0;
    }
    body {
      display: flex;
      min-height: 100vh;
      background-color: #f8f9fa;
      padding-top: 10px;
      flex-direction: column;
      align-items: center;
    }
    .container {
      max-width: 1100px;
      background: white;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0015px rgba(0,0,0,0.2);
      margin-top: 5px;
      width: 100%;
    }
 
    .table-scroll-vertical
    {
 	 max-height: 450px;
 	 overflow-y: auto;
  	 border: 1px solid #dee2e6;
  	 border-radius: 5px;
  	 margin-top: 0; 
	}
	
	.small-italic 
	{
  	 font-size: 0.85em; 
  	 font-style: italic; 
  	 color: #C0C0C0; 
	}
	
	.dataTables_filter label 
	{
  	 font-weight: bold;
  	 color: #333;
  	 color: red;
  	 float: left;
	}

	.dataTables_filter input
	{
  	 border: 2px solid #993399;
  	 border-radius: 5px;
  	 padding: 6px 10px;
  	 outline: none;
   	 width: 250px;
	}
	
	.page-footer
	{
  	 text-align: center;
  	 padding: 10px 5px;
  	 font-size: 0.95rem;
 	 color: #809fff;
 	 background-color: #f8f9fa;
  	 border-top: 1px solid #993399;
  	 margin-top: 5px;
	}
  </style>
  <style>
  @keyframes blink
  {
    50%
    {
      opacity: 0;
    }
  }
</style>
</head>
<body>
<div class="container">
<div style="overflow: hidden;">
  <div style="float: left; font-size: smaller; font-weight: bold;color:#b3ffff;animation: blink 1s infinite;">
    Environment : DEV
  </div>
  <h2 style="text-align: center; margin: 0;">
    Your Organization Name or Company Name<br>
    <span style="font-size: smaller; color: yellow;text-align: center;">Data Lookup Table</span>
  </h2>
</div> 
 <form id="recordForm"class="form-horizontal" action="" method="POST">
 	<div class="form-group">
        <!--for any other information if you want> -->
       </div>
      <div class="row mb-3">
        <label for="key"class="col-sm-3 col-form-label fw-bold">Key :</label>
        <div class="col-sm-9">
          <input type="text"class="form-control" id="key" name="key" placeholder="Enter the Key data !" required />
        </div>
      </div>
      <div class="row mb-3">
        <label for="kvalue"class="col-sm-3 col-form-label fw-bold">Key Pair Value</label>
        <div class="col-sm-9">
          <input type="text"class="form-control" id="kvalue" name="kvalue" placeholder="Enter the key paris value !" required />
        </div>
      </div>
	<div class="d-flex justify-content-between align-items-center mt-4 flex-wrap">
		<p class="mb-2 mb-md-0 text-primary">
    		<i>Click the edit icon to modify a record, then press "Update" to save. To delete, press the "Delete" button.</i> 
  		</p> 
  		
	<div class="d-flex flex-wrap justify-content-end">
		<button class="btn btn-success me-2" id="Add" type="submit" name="Add">
			<i class="fa fa-plus me-1" style="font-size:18px; color:white;"></i> Add Record
		</button>
	
		<button class="btn btn-primary me-2" type="submit" name="Update">
			<i class="fa fa-edit me-1" style="font-size:18px;"></i> Update
    	</button>

		<button class="btn btn-danger me-2" id="Delete" type="submit" name="Delete">
			<i class="fa fa-remove me-1" style="font-size:18px;"></i> Delete
		</button>
		
		<button class="btn btn-info" id="Cancel" type="submit" name="Cancel">
  			<i class="glyphicon glyphicon-refresh" style="font-size:18px;"></i> Cancel
		</button>
  	   </div> 
    </div>
    <div></div>
	</form>
<script language=SQL name="query">
  SELECT KeyName, DataValue FROM Ens_Util.LookupTable WHERE TableName='Data Lookup Table Example'
</script>

<script language=cache runat=server>
 S myKeyName=$Get(%request.Data("key",1))
 S myKeyValue=$Get(%request.Data("kvalue",1))
 S myDtName="Data Lookup Table Example"
 I ($Data(%request.Data("Add",1)))
 {
	I ((myKeyName '="") && (myKeyValue '="") && (myDtName '=""))
	{	
		I ##Class(SANJIB.DataLookup.Methods).ChkLookupKeyValue(myDtName,myKeyName)="F"
		{
			D##Class(SANJIB.DataLookup.Methods).InsertLookUpValues(myDtName,myKeyName,myKeyValue)
		}
		else
		{	W"The key has already been inserted.."&html<<divstyle=color:red>#(myKeyName)#</div>> }
		
	}
 }
 I ($Data(%request.Data("Update",1)))
 {
	I ((myKeyName '="") && (myKeyValue '="") && (myDtName '=""))
	{
		D##Class(SANJIB.DataLookup.Methods).UpdateLookUpValues(myDtName,myKeyName,myKeyValue)
	}
 }
 I ($Data(%request.Data("Delete",1)))
 {
	 I ((myKeyName '="") && (myKeyValue '="") && (myDtName '=""))
	 {
		D##Class(SANJIB.DataLookup.Methods).DeleteLookUpValues(myDtName,myKeyName)		
	 }
 }	
</script>
 <div class="table-responsive table-scroll-vertical"> 
    <table class="table table-bordered border-primary table table-hover mt-2" id="dataTable" style="min-width: 1000px;">
      <thead class="table-warning">
        <tr>
          <th>Key Data</th>
          <th>Key Pair Value</th>
          <th style="text-align:center;">Actions</th> 
        </tr>
      </thead>
      <tbody id="tableRecordset">
 		</tr>
 		<csp:while counter=myQueryRow condition=query.Next()>
 			<tr class='#($S(myQueryRow#2:"LightRow",1:"LightRow"))#'>
  				<csp:while counter=myQueryColumn condition="(myQueryColumn<query.GetColumnCount())">
      				<td style="background-color:#e6ffff">#(query.GetData(myQueryColumn))#</td>
  						</csp:while>
 							<td style="text-align:center;">
          						<button class="btn btn-sm btn-danger me-2 edit-delete-btn" data-id="#(query.GetData(0))#">
    								<i class="fa fa-edit"></i>
    								<i class="fa fa-trash"></i>
  								</button>
        					</td> 
 						</tr>
 				</csp:while>
      		</tbody>
       </table>
    </div>
  </div>
  
<script language=javascript>
  document.addEventListener('DOMContentLoaded', () => 
  {
    const editButtons = document.querySelectorAll('.edit-delete-btn');
    editButtons.forEach(button =>
    {
      button.addEventListener('click', () => 
      {
        const row = button.closest('tr');
        document.getElementById("Add").disabled=true
        const kID=row.cells[0].textContent.trim();
        const pairVal = row.cells[1].textContent.trim();
        document.getElementById('key').value = kID;
        document.getElementById('kvalue').value = pairVal;
        document.getElementById('key').focus();
      });
    });
  });
</script>
 <script language=javascript>
    document.getElementById('Cancel').addEventListener('click', () => 
    {
    	event.preventDefault();
    	document.getElementById('recordForm').reset();
    });
</script>

<script language=javascript>
  $(document).ready(function()
  {
    $('#dataTable').DataTable(
    {
      "order": [[0, "asc"]],
      "paging": true,
      "lengthChange": false,
      "searching": true,
      "info": true,
      "autoWidth": false
    });
  		$('#dataTable_filter input').css('font-size', '12px').attr('placeholder', 'Type to search..?');
  });
</script>

</body> 

<script language="javascript">
  document.addEventListener('DOMContentLoaded', () =>
  {
  	const updateBtn = document.querySelector('button[name="Update"]');
  	const deleteBtn = document.querySelector('button[name="Delete"]');
  	const addBtn = document.getElementById('Add');
  	const cancelBtn = document.getElementById('Cancel');
  	updateBtn.style.display = 'none';
  	deleteBtn.style.display = 'none';
  	addBtn.style.visibility = 'visible';  
  	addBtn.disabled = false;
  document.querySelectorAll('.edit-delete-btn').forEach(editBtn =>
  {
    editBtn.addEventListener('click', () =>
    {
      updateBtn.style.display = 'inline-block';
      deleteBtn.style.display = 'inline-block';
      addBtn.style.display = 'none';
      addBtn.style.visibility = 'visible'; 
      addBtn.disabled = true;
    });
  });
  updateBtn.addEventListener('click', (e) =>
  {
    updateBtn.style.display = 'none';
    deleteBtn.style.display = 'none';
    addBtn.style.display = 'inline-block';
    addBtn.style.visibility = 'visible';
    addBtn.disabled = false;
  });
  deleteBtn.addEventListener('click', (e) =>
  {
    updateBtn.style.display = 'none';
    deleteBtn.style.display = 'none';
    addBtn.style.display = 'inline-block';
    addBtn.style.visibility = 'visible';
    addBtn.disabled = false;
  });
  cancelBtn.addEventListener('click', (e) =>
  {
    e.preventDefault();  
    document.getElementById('recordForm').reset();
    updateBtn.style.display = 'none';
    deleteBtn.style.display = 'none';
    addBtn.style.display = 'inline-block';
    addBtn.style.visibility = 'visible';  
    addBtn.disabled = false;              
  });
});
</script>

<footer class="page-footer">
  <a href="https://sanjibpandey.wixsite.com/pandey/" target="_blank" rel="noopener noreferrer" style="color: #6c757d; text-decoration: none;"> https://sanjibpandey.wixsite.com/pandey/</a>
</footer>
</html>

 

/// Cette classe est conçue et créée pour insérer, mettre à jour et supprimer des enregistrements (clé & valeur)/// dans la table de recherche de données à partir d'une application Web. /// Ensuite, cela peut être utilisé dans des règles ou des transformations de données à l'aide de la fonction de recherche./// Auteur : Sanjib Pandey/// Date : 16/10/2025Class SANJIB.DataLookup.Methods Extends%Persistent
{

/// Avec cette méthode, on ajoute une valeur à la table de recherche. Les paramètres obligatoires sont:/// dtTableName : Le nom de votre table de recherche de données/// keyData     : La clé/// keyValue    : La valeur ( la valeur de paire de clés)	ClassMethod InsertLookUpValues(dtTable As%String = "", keyData As%String = "", keyValue As%String = "") As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="") && (keyValue '="")
		{
			S mySQLStatement = "INSERT into Ens_Util.LookupTable (KeyName,DataValue,TableName) values ('"_keyData_"','"_keyValue_"','"_dtTable_"')"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			S tSC={"InserRecorded":(myRecordSet)}
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

/// Cette méthode vérifie si la valeur existe déjà dans la table de recherche de données.ClassMethod ChkLookupKeyValue(dtTable As%String, keyData As%String) As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="")
		{
			S mySQLStatement="SELECT KeyName FROM Ens_Util.LookupTable WHERE TableName='"_dtTable_"' and KeyName='"_keyData_"'"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			D myRecordSet.%Next()
			I (myRecordSet.%SQLCODE=0)
			{ 
				S tSC= "T"
			}
			else
			{
				S tSC="F"
			}
		}
		else
		{
			S tSC= "Invalid Parameters - missing table name or key !"
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

/// Cette méthode met à jour uniquement la valeur clé dans la table de recherche de données.ClassMethod UpdateLookUpValues(dtTable As%String = "", keyData As%String = "", keyValue As%String = "") As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="") && (keyValue '="")
		{
			S mySQLStatement = "UPDATE Ens_Util.LookupTable SET DataValue='"_keyValue_"' WHERE TableName='"_dtTable_"' AND KeyName='"_keyData_"'"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			S tSC={"Updated Record":(myRecordSet)}
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

/// Cette méthode est utilisée pour supprimer un enregistrement de la table de recherche de données.ClassMethod DeleteLookUpValues(dtTable As%String, keyData As%String) As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="") 
		{
			S mySQLStatement = "DELETE FROM Ens_Util.LookupTable WHERE  TableName='"_dtTable_"' And KeyName='"_keyData_"'"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			S tSC={"Deleted Record":(myRecordSet)}
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

Storage Default
{
<Data name="MethodsDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
</Data>
<DataLocation>^SANJIB.DataLookup.MethodsD</DataLocation>
<DefaultData>MethodsDefaultData</DefaultData>
<IdLocation>^SANJIB.DataLookup.MethodsD</IdLocation>
<IndexLocation>^SANJIB.DataLookup.MethodsI</IndexLocation>
<StreamLocation>^SANJIB.DataLookup.MethodsS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}

Conclusion

Cette interface Web réutilisable et cette classe offrent un moyen simple mais efficace pour la gestion des tables de recherche de données dans HealthConnect, en améliorant l'efficacité et la flexibilité. Grâce à l'accès contrôlé dont bénéficient les utilisateurs finaux, cette interface réduit les frais administratifs traditionnellement associés à la maintenance des tables de recherche.

0
0 17
Article Iryna Mykhailova · Jan 15, 2025 1m read

Lorsque vous déployez du code à partir d'un dépôt, la suppression de classe (fichier) peut ne pas être reflétée par votre système CI/CD.
Voici une simple ligne de commande pour supprimer automatiquement toutes les classes d'un package spécifié qui n'ont pas été importées. Elle peut être facilement ajustée pour une variété de tâches annexes :

set packages = "USER.*,MyCustomPackage.*"set dir = "C:\InterSystems\src\"set sc = $SYSTEM.OBJ.LoadDir(dir,"ck", .err, 1, .loaded)
set sc = $SYSTEM.OBJ.Delete(packages _ ",'" _ $LTS($LI($LFS(loaded_",",".cls,"), 1, *-1), ",'"),, .err2)

La première commande compile les classes et renvoie également une liste des classes chargées. La deuxième commande supprime toutes les classes des packages spécifiés, à l'exception des classes chargées juste avant.

0
0 43
Article Guillaume Rongier · Jan 17, 2024 7m read

Bienvenue dans le prochain chapitre de ma série CI/CD, où nous discutons des approches possibles vers le développement de logiciels avec les technologies InterSystems et GitLab. Aujourd'hui, nous reprenons la discussion sur l'interopérabilité, et plus particulièrement sur le suivi de vos déploiements d'interopérabilité. Si vous ne l'avez pas encore fait, configurez Alerte pour toutes vos productions d'interopérabilité afin de recevoir des alertes sur les erreurs et l'état de la production dans son ensemble.

Le paramètre de délais d'inactivité Inactivity Timeout est commun pour tous les hôtes métier d'interopérabilité (Interoperability Business Hosts). Un hôte métier possède le statut Inactif lorsqu'il n'a reçu aucun message pendant le nombre de secondes spécifié dans le champ de délai d'inactivité " Inactivity Timeout ". La fonction de surveillance de la production " Monitor Service " examine périodiquement l'état des services et des opérations métier au sein de la production et marque les éléments comme étant inactifs s'ils n'ont rien fait pendant le délai d'inactivité. La valeur par défaut est 0 (zéro). Si ce paramètre est 0, l'hôte métier ne sera jamais marqué comme Inactif, quelle que soit la durée de son inactivité.

Ce paramètre est extrêmement utile, car il génère des alertes qui, associées aux alertes configurées, permettent de signaler les problèmes de production en temps réel. Le fait que l'élément "Business Host" soit inactif signifie qu'il peut y avoir des problèmes de production, d'intégration ou de connectivité réseau qui nécessitent d'être examinés. Cependant, le Business Host ne peut avoir qu'un seul paramètre constant pour le délai d'inactivité, ce qui peut générer des alertes inutiles pendant les périodes connues de faible trafic : nuits, week-ends, vacances, etc. Dans cet article, je décrirai plusieurs approches pour la mise en œuvre dynamique des délais d'inactivité (Inactivity Timeout). Bien que je fournisse un exemple fonctionnel (qui fonctionne actuellement en production pour l'un de nos clients), cet article est plutôt un guide pour votre propre mise en œuvre dynamique des délais d'inactivité, donc ne considérez pas la solution proposée comme la seule alternative.

Idée

Le moteur d'interopérabilité maintient une globale spéciale HostMonitor, qui contient chaque Business Host sous forme d'indice, et l'horodatage de la dernière activité sous forme de valeur. Au lieu d'utiliser le délai d'inactivité, nous allons surveiller nous-mêmes cette globale et générer des alertes en fonction de l'état de HostMonitor. HostMonitor est maintenue que soit la valeur du délai d'inactivité définie - elle est toujours activée.

Mise en œuvre

Pour commencer, voyons comment nous pouvons itérer la globale HostMonitor :

Set tHost=""
For {
Set tHost=$$$OrderHostMonitor(tHost)
Quit:""=tHost
Set lastActivity = $$$GetHostMonitor(tHost,$$$eMonitorLastActivity)
}

Pour créer notre Monitor Service, il nous faut effectuer les vérifications suivantes pour chaque Business Host :

  1. Décidez si le Business Host est concerné par notre configuration dynamique des délais d'inactivité (par exemple, les interfaces hl7 à fort trafic peuvent fonctionner avec le délai d'inactivité habituel).
  2. Si le Business Host se trouve dans le champ d'action, nous devons calculer le temps écoulé depuis la dernière activité.
  3. En fonction du temps d'inactivité et d'un certain nombre de conditions (heure du jour/de la nuit, jour de la semaine), nous devons décider si nous voulons envoyer une alerte.
  4. Si nous voulons envoyer un enregistrement d'alerte, nous devons enregistrer l'heure de la Dernière activité (Last Activity) afin de ne pas envoyer la même alerte deux fois.

Notre code se présente comme suit :

Set tHost=""
For { 
  Set tHost=$$$OrderHostMonitor(tHost) 
  Quit:""=tHost
  Continue:'..InScope(tHost)
  Set lastActivity = $$$GetHostMonitor(tHost,$$$eMonitorLastActivity)
  Set tDiff = $$$timeDiff($$$timeUTC, lastActivity)
  Set tTimeout = ..GetTimeout(tDayTimeout)
  If (tDiff > tTimeout) && ((lastActivityReported="") || ($system.SQL.DATEDIFF("s",lastActivityReported,lastActivity)>0)) {
    Set tText = $$$FormatText("InactivityTimeoutAlert: Inactivity timeout of '%1' seconds exceeded for host '%2'", +$fn(tDiff,,0), tHost)
    Do ..SendAlert(##class(Ens.AlertRequest).%New($LB(tHost, tText)))
    Set $$$EnsJobLocal("LastActivity", tHost) = lastActivity
  } 
}

Vous devez implémenter les méthodes InScope et GetTimeout contenant votre logique personnalisée, et vous êtes prêt à démarrer. Dans mon exemple, il y a des délais de jour (Day Timeouts, qui peuvent être différents pour chaque Business Host, mais avec une valeur par défaut) et des délais de nuit (Night Timeouts, qui sont les mêmes pour tous les Business Hosts enregistrés), de sorte que l'utilisateur doit fournir les paramètres suivants :

  • Champs d'application : Liste des noms (ou parties de noms) de Business Host associés à leur valeur DayTimeout personnalisée, une par ligne. Seuls les Business Hosts qui se trouve dans le champ d'action (qui remplissent la condition $find(host, scope) pour au moins un champ d'action) seront suivis. Laisser ce champ vide pour surveiller tous les Business Hosts. Exemple: OperationA=120
  • DayStart : Nombre de secondes depuis 00:00:00, point de départ d'une journée. Il doit être inférieur à DayEnd. Par exemple, 06:00:00 AM est 6*3600 = 21600
  • DayEnd: Nombre de secondes depuis 00:00:00, point de fin d'une journée. Il doit être supérieur à DayStart. Par exemple 08:00:00 du soir est (12+8)*3600 = 72000
  • DayTimeout : Valeur du délai d'attente par défaut en secondes pour le déclenchement d'alertes pendant la journée.
  • NightTimeout : Valeur du délai d'attente par défaut en secondes pour le déclenchement d'alertes pendant la nuit.
  • WeekendDays: Jours de la semaine considérés comme week-end. Séparés par des virgules. Pour le week-end, NightTimeout s'applique 24 heures par jour. Exemple : 1,7 Vérifiez la valeur DayOfWeek de la date en exécutant : $SYSTEM.SQL.Functions.DAYOFWEEK(date-expression). Par défaut, les valeurs renvoyées représentent les jours suivants : 1 - dimanche, 2 - lundi, 3 - mardi, 4 - mercredi, 5 - jeudi, 6 - vendredi, 7 - samedi.

Voici le code complet, mais je ne pense pas qu'il y ait quelque chose d'intéressant là-dedans. Il implémente simplement les méthodes InScope et GetTimeout. Vous pouvez utiliser d'autres critères et adapter les méthodes InScope et GetTimeout si nécessaire.

Problèmes

Il y a deux problèmes à aborder:

  • Pas d'icône jaune pour les Business Hosts inactifs (puisque la valeur du paramètre InactivityTimeout de l'hôte est nulle).
  • Paramètres hors hôte - les développeurs doivent se rappeler de mettre à jour ce service de surveillance personnalisé chaque fois qu'ils ajoutent un nouvel Business Host et qu'ils souhaitent utiliser une implémentation dynamique de délais d'inactivité.

Alternatives

J'ai exploré toutes ces approches avant de mettre en œuvre la solution ci-dessus :

  1. Créer le Business Service qui modifie les paramètres InactivityTimeout lorsque le jour ou la nuit commence. Au départ, j'ai essayé de suivre cette voie mais j'ai rencontré un certain nombre de problèmes, principalement l'obligation de redémarrer tous les Business Hosts concernés à chaque fois que nous modifions le paramètre InactivityTimeout.
  2. Ajouter de règles dans le processeur d'alertes personnalisé (Custom Alert Processor) qui, au lieu d'envoyer l'alerte, la suppriment si c'est le délai nocturne InactivityTimeout. Mais une alerte d'inactivité provenant de Ens.MonitorServoce met à jour la valeur LastActivity, donc je ne vois pas de moyen, à partir d'un Custom Alert Processor, d'obtenir un "vrai" horodatage de dernière activité (à part interroger Ens.MessageHeader, je suppose ?). Et si c'est " nuit " - retourner l'état de l'hôte à OK, si ce n'est pas encore " InactivityTimeout " nocturne, et supprimer l'alerte.
  3. L'extension de Ens.MonitorService ne semble pas possible sauf pour le callback OnMonitor, mais cela sert un autre but.

Conclusion

Toujours configurer [alerte] https://docs.intersystems.com/iris20233/csp/docbook/Doc.View.cls?KEY=ECONFIG_alerts) pour toutes vos productions d'interopérabilité afin de recevoir des alertes sur les erreurs et l'état de la production dans son ensemble. Si une configuration statique de délai d'inactivité n'est pas suffisante, vous pouvez facilement créer une implémentation dynamique.

Liens

0
0 67
Article Guillaume Rongier · Jan 8, 2024 14m read

Bienvenue dans le prochain chapitre de ma série CI/CD, où nous discutons des approches possibles vers le développement de logiciels avec les technologies InterSystems et GitLab.

Aujourd'hui, parlons d'interopérabilité.

Problème

Lorsque vous avez une production d'interopérabilité active, vous avez deux flux de processus distincts : une production active qui traite les messages et un flux de processus CI/CD qui met à jour le code, la configuration de la production et les paramètres par défaut du système.

De toute évidence, le processus CI/CD affecte l'interopérabilité. Mais il y a des questions :

  • Que se passe-t-il exactement lors d'une mise à jour ?
  • Que devons-nous faire pour minimiser ou éliminer les temps d'arrêt de la production lors d'une mise à jour ?

Terminologie

  • Business Host (BH) - un élément configurable de la production d'interopérabilité : Service métier (BS), Processus métier (BP, BPL) ou Opération métier (BO).
  • Business Host Job (Job) - Tâche InterSystems IRIS qui exécute le code du Business Host et qui est gérée par la production d'interopérabilité.
  • Production : ensemble interconnecté de serveurs d'entreprise (Hôte métier).
  • System Default Settings (Paramètres par défaut du système) (SDS) - valeurs spécifiques à l'environnement dans lequel InterSystems IRIS est installé.
  • Message actif - demande en cours de traitement par une tâche Business Host Job. Une tâche Business Host Job peut avoir un maximum d'un Message actif. Une tâche Business Host Job qui n'a pas de message actif est inactif.

Qu'est-ce qui se passe?

Commençons par le cycle de vie de la production.

Démarrage de la production

Tout d'abord, la production peut être démarrée. Une seule production par espace de noms peut être lancée simultanément, et en général (à moins que vous ne sachiez vraiment ce que vous faites et pourquoi vous le faites), vous ne devrez lancer qu'une seule production par espace de noms, jamais. Il n'est pas recommandé de passer d'un espace de noms à l'autre entre deux ou plusieurs productions différentes. Le démarrage de la production démarre tous les Business Hosts activés définis dans la production. Le fait que certains Business Hosts ne démarrent pas n'affecte pas le démarrage de la production.

Conseils:

  • Démarrez la production à partir du portail de gestion du système ou en appelant : ##class(Ens.Director).StartProduction("ProductionName")
  • Exécutez un code arbitraire au démarrage de la production (avant qu'un Job Business Host ne soit démarré) en implémentant une méthode OnStart.
  • Le démarrage de la production est un événement contrôlable. Vous pouvez toujours voir qui et quand a fait cela dans le journal d'audit.

Mise à Jour de la production

Après le démarrage de la production, l'outil Ens.Director surveille en permanence la production en cours. Il existe deux états de production : état cible, défini dans la classe de production et les paramètres par défaut du système ; et état en cours d'exécution - les tâches en cours d'exécution avec les paramètres appliqués lors de la création des tâches. Si l'état souhaité et l'état actuel sont identiques, tout va bien, mais la production peut (et doit) être mise à jour s'il y a une différence. Habituellement, vous voyez cela sous la forme d'un bouton rouge "Mettre à jour" sur la page Configuration de la production dans le Portail de gestion du système.

La mise à jour de la production signifie une tentative pour que l'état de production actuel corresponde à l'état de production cible.

Lorsque vous exécutez ##class(Ens.Director).UpdateProduction(timeout=10, force=0) pour mettre à jour la production, il effectue les opérations suivantes pour chaque Business Host :

  1. Comparez les réglages actifs aux réglages de production/SDS/classe.
  2. Uniquement si (1) révèle une incohérence, le Business Host est marqué comme étant obsolète et nécessitant une mise à jour.

Après avoir exécuté cette opération pour chaque Business Host, UpdateProduction construit l'ensemble des changements :

  • Business Hosts à arrêter
  • Business Hosts à démarrer
  • Paramètres de production à mettre à jour

Ensuite, il les applique.

De cette manière, la "mise à jour" des paramètres sans rien changer n'entraîne aucune interruption de la production.

Conseils:

  • Mettre à jour la production à partir du portail de gestion du système ou en appelant : ##class(Ens.Director).UpdateProduction(timeout=10, force=0)
  • Le délai de mise à jour par défaut du portail de gestion du système est de 10 secondes. Si vous savez que le traitement de vos messages prend plus de temps, appelez Ens.Director:UpdateProduction avec un délai plus long.
  • Le délai de mise à jour est un paramètre de production, et vous pouvez le modifier pour une valeur plus importante. Ce paramètre s'applique au portail de gestion du système.

Mise à jour du code

UpdateProduction NE MET PAS A JOUR les BHs dont le code est obsolète. C'est un comportement orienté vers la sécurité, mais si vous voulez mettre à jour automatiquement tous les BHs en cours d'exécution lorsque le code sous-jacent change, suivez les étapes suivantes :

First, load and compile like this:

do $system.OBJ.LoadDir(dir, "", .err, 1, .load)
do $system.OBJ.CompileList(load, "curk", .errCompile, .listCompiled)

Maintenant, listCompiled contiendrait tous les éléments qui ont été compilés (utilisez git diffs pour minimiser l'ensemble des éléments importés) à cause du marqueur u. Utilisez cette listCompiled pour obtenir un $lb de toutes les classes qui ont été compilées :

set classList = ""
set class = $o(listCompiled(""))
while class'="" { 
  set classList = classList _ $lb($p(class, ".", 1, *-1))
  set class=$o(listCompiled(class))
}

Et ensuite, calculer une liste de BHs qui nécessitent un redémarrage :

SELECT %DLIST(Name) bhList
FROM Ens_Config.Item 
WHERE 1=1
  AND Enabled = 1
  AND Production = :production
  AND ClassName %INLIST :classList

Enfin, après avoir obtenu bhList, arrêtez et démarrez les hôtes affectés :

for stop = 1, 0 {
  for i=1:1:$ll(bhList) {
    set host = $lg(bhList, i)
    set sc = ##class(Ens.Director).TempStopConfigItem(host, stop, 0)
  }
  set sc = ##class(Ens.Director).UpdateProduction()
}

Arrêt de production

Les productions peuvent être arrêtées, ce qui implique d'envoyer une demande à tous les Jobs Business Host pour qu'ils s'arrêtent (en toute sécurité, une fois qu'ils ont terminé leurs messages actifs, le cas échéant).

Conseils:

  • Arrêtez la production à partir du portail de gestion du système ou en appelant : ##class(Ens.Director).StopProduction(timeout=10, force=0)
  • Le délai d'arrêt par défaut du portail de gestion du système est de 120 secondes. Si vous savez que le traitement de vos messages prend plus de temps que cela, appelez Ens.Director:StopProduction avec un délai plus long.
  • Le délai d'arrêt est un paramètre de production. Vous pouvez le modifier pour une valeur plus importante. Ce paramètre s'applique au portail de gestion du système.
  • Exécuter un code arbitraire lors d'un arrêt de production en implémentant une méthode OnStop.
  • L'arrêt de la production est un événement contrôlable, vous pouvez toujours voir qui et quand a fait cela dans le journal d'audit.

Ce qui est important ici, c'est que la production est la somme totale des Business Hosts :

  • Le démarrage de la production signifie le démarrage de tous les Business Hosts activés.
  • L'arrêt de la production signifie l'arrêt de tous les Business Hosts en cours d'exécution.
  • La mise à jour de la production consiste à calculer un sous-ensemble de Business Hosts qui sont obsolètes, de sorte qu'ils sont d'abord arrêtés, puis redémarrés immédiatement après. En outre, un Business Host nouvellement ajouté est seulement démarré, et un Business Host supprimé de la production est seulement arrêté.

Cela nous amène au cycle de vie des Business Hosts.

Démarrage du Business Host

Les Business Hosts sont composés de Business Hosts Jobs identiques (en fonction de la valeur du paramètre Pool Size). Le démarrage d'un Business Host implique le démarrage de tous les Business Hosts Jobs. Ils sont démarrés en parallèle.

Business Host Job individuel commence comme ceci :

  1. Interopérabilité de JOBs est un nouveau processus qui deviendrait un Business Host Job.
  2. Le nouveau processus s'enregistre en tant que job d'interopérabilité.
  3. Le code du Business Host et le code de l'adaptateur (Adapter) sont chargés dans la mémoire du processus.
  4. Les paramètres relatifs à un Business Host et à un adaptateur sont chargés dans la mémoire. L'ordre de priorité est le suivant a. Paramètres de production (remplacent les paramètres par défaut du système et les paramètres de classe). b. Paramètres par défaut du système (prévalent sur les paramètres de classe). c. Paramètres de classe.
  5. Le travail est prêt et commence à accepter des messages.

Une fois le point (4) effectué, la tâche ne peut plus modifier les paramètres ou le code. Ainsi, lorsque vous importez un nouveau code et de nouveaux paramètres par défaut, cela n'affecte pas les tâches d'interopérabilité en cours d'exécution.

Arrêt de Business Host

L'arrêt du Business Host Job signifie le suivant :

  1. L'interopérabilité ordonne à Job d'arrêter toute acceptation de messages/entrées.
  2. S'il y a un message actif, le Business Host Job dispose d'un délai de quelques secondes pour le traiter (en le complétant - en complétant la méthode OnMessage pour BO, OnProcessInput pour BS, la méthode d'état S<int> pour les BP BPL, et la méthode On* pour les BP).
  3. Si un message actif n'a pas été traité avant le timeout et force=0, la mise à jour de la production échoue pour ce Business Host (et vous verrez un bouton rouge Update dans le SMP).
  4. L'arrêt réussit si l'un des éléments de cette liste est vrai :
    • Pas de message actif
    • Le message actif a été traité avant le timeout.
    • Le message actif n'a pas été traité avant le timeout MAIS force=1.
  5. Le travail est désenregistré auprès de l'interopérabilité et s'arrête.

Mise à jour de Business Host

La mise à jour du Business Host signifie l'arrêt des tâches en cours d'exécution pour le Business Host et le démarrage de nouvelles tâches.

Les régles métier "Business Rules", les régles de routage "Routing Rules" et les DTL

Tous les Business Hosts commencent immédiatement à utiliser les nouvelles versions des régles Business Rules, Routing Rules et des DTL dès qu'elles sont disponibles. Le redémarrage d'un Business Host n'est pas nécessaire pour cela.

Mises à jour hors ligne

Il arrive cependant que les mises à jour de la production nécessitent l'arrêt de certains Business Hosts.

Les régles dépendent du nouveau code

Considérons la situation. Vous disposez d'une règle de routage Routing Rule X qui achemine les messages vers le processus Business Process A ou B en fonction de critères arbitraires. Dans un nouveau commit, vous ajoutez simultanément:

  • le processus Business Process C
  • Une nouvelle version de Routing Rule X, qui achemine les messages vers A, B ou C.

Dans ce scénario, vous ne pouvez pas charger la règle d'abord et mettre à jour la production ensuite. En effet, la règle nouvellement compilée commencerait immédiatement à router les messages vers le Business Process C, qu'InterSystems IRIS n'a peut-être pas encore compilé, ou que l'interopérabilité n'a pas encore mis à jour pour l'utiliser. Dans ce cas, vous devez désactiver le Business Host avec une règle de routage, mettre à jour le code, mettre à jour la production et réactiver le Business Host.

Remarques:

  • Si vous mettez à jour une production à l'aide du fichier de déploiement de la production, tous les BH concernés seront automatiquement désactivés/activés.
  • Pour les hôtes invoqués par InProc, la compilation invalide le cache de l'hôte particulier détenu par l'appelant.

Dépendances entre Business Hosts

Les dépendances entre les Business Hosts sont essentielles. Imaginons que vous ayez des processus Business Hosts A et B, dans lesquels A envoie des messages à B. Dans un nouveau commit, vous ajoutez simultanément :

  • Une nouvelle version du Processus A, qui définit une nouvelle propriété X dans une demande adressée à B
  • Une nouvelle version du Processus B qui peut traiter une nouvelle propriété X

Dans ce scénario, nous DEVONS mettre à jour le Processus B d'abord et le Processus A ensuite. Vous pouvez procéder de deux manières :

  • Désactiver les Business Hosts pendant la durée de la mise à jour.
  • Diviser la mise à jour en deux parties : d'abord, mettre à jour le Processus B uniquement, et ensuite, dans une mise à jour séparée, commencer à lui envoyer des messages à partir du Processus A.

Une variante plus difficile de ce thème, où les nouvelles versions des Processus A et B sont incompatibles avec les anciennes versions, nécessite un arrêt des Business Hosts.

Files d'attente

Si vous savez qu'après la mise à jour, un Business Host ne pourra plus traiter les anciens messages, vous devez vous assurer que la file d'attente du Business Host est vide avant la mise à jour. Pour ce faire, désactivez tous les Business Hosts qui envoient des messages au Business Host et attendez que sa file d'attente soit vide.

Changement d'état dans les BPL Business Processes

Tout d'abord, une petite introduction sur le fonctionnement des BPL BP. Après avoir compilé un BPL BP, deux classes sont créées dans le paquet, avec le même nom qu'une classe BPL complète :

  • La classe Thread1 contient les méthodes S1, S2, .... SN, qui correspondent à des activités au sein de BPL
  • La classe Context contient toutes les variables de contexte ainsi que le prochain état que BPL exécutera (c'est-à-dire S5).

La classe BPL est également persistante et stocke les demandes en cours de traitement.

BPL fonctionne en exécutant des méthodes S dans une classe Thread et en mettant à jour la table de la classe BPL, la table Context et la table Thread1, où un message "en cours de traitement" est une ligne dans un tableau BPL. Une fois la requête traitée, BPL supprime les entrées BPL, Context et Thread. Puisque les BPL BP sont asynchrones, une tâche BPL peut traiter simultanément de nombreuses requêtes en sauvegardant des informations entre les appels S et en passant d'une requête à l'autre. Par exemple, BPL traite une requête jusqu'à ce qu'il arrive à une activité sync - en attendant une réponse de BO. Il sauvegarde le contexte actuel sur le disque, avec la propriété %NextState (dans la classe Thread1) fixée à la méthode S de l'activité de réponse, et travaille sur d'autres requêtes jusqu'à ce que BO réponde. Après la réponse de BO, BPL charge le contexte en mémoire et exécute la méthode correspondant à un état sauvegardé dans la propriété %NextState.

Maintenant, que se passe-t-il lorsque nous mettons à jour le BPL ? Tout d'abord, nous devons vérifier qu'au moins une des deux conditions est remplie :

  • Pendant la mise à jour, le tableau Contexte est vide, ce qui signifie qu'aucun message actif n'est en cours de traitement.
  • Les nouveaux états (New States) sont les mêmes que les anciens (Old States), ou de nouveaux états sont ajoutés à la suite des anciens .

Si au moins une condition est remplie, nous sommes prêts à démarrer. Soit il n'y a pas de demandes de pré-mise à jour à traiter par BPL après la mise à jour, soit les états sont ajoutés à la fin, ce qui signifie que les anciennes demandes peuvent également être traitées (en supposant que les demandes de pré-mise à jour sont compatibles avec les activités et le traitement de BPL après la mise à jour).

Mais que se passe-t-il si vous avez des demandes actives en cours de traitement et que BPL change l'ordre des états ? Idéalement, si vous pouvez attendre, désactivez les appelants BPL et attendez que la file d'attente soit vide. Vérifiez que le tableau Contexte est également vide. N'oubliez pas que la file d'attente n'affiche que les demandes non traitées, et que le tableau Contexte stocke les demandes en cours de traitement, de sorte qu'il peut arriver qu'un BPL très occupé affiche une file d'attente nulle, et c'est normal. Ensuite, désactivez le BPL, effectuez la mise à jour et activez tous les Business Hosts précédemment désactivés.

Si ce n'est pas possible (généralement dans le cas où le BPL est très long, par exemple, je me souviens avoir mis à jour un BPL qui prenait environ une semaine pour traiter une demande, ou si la fenêtre de mise à jour est trop courte), utilisez l'outil BPL versioning.

Vous pouvez également écrire un script de mise à jour. Dans ce script de mise à jour, associez les anciens next states aux nouveaux next states et exécutez-le sur le tableau Thread1 afin que le BPL mis à jour puisse traiter les anciennes requêtes. BPL, bien sûr, doit être désactivé pendant la durée de la mise à jour. Ceci dit, il s'agit d'une situation extrêmement rare, et généralement, vous n'avez pas besoin de faire cela, mais si jamais vous devez le faire, voici comment procéder.

Conclusion

L'interopérabilité met en œuvre un algorithme sophistiqué pour minimiser le nombre d'actions nécessaires pour actualiser la production après la modification du code sous-jacent. Appelez UpdateProduction avec un délai d'attente sûr à chaque mise à jour de SDS. Pour chaque mise à jour de code, vous devez décider d'une stratégie de mise à jour.

La réduction de la quantité de code compilé à l'aide de git diffs permet de réduire le temps de compilation, mais la "mise à jour" du code en lui-même et sa recompilation ou la "mise à jour" des paramètres avec les mêmes valeurs ne déclenchent pas ou ne requièrent pas de mise à jour de la production.

La mise à jour et la compilation des règles de gestion "Business Rule", des règles de routage "Routing Rules" et des DTL les rendent immédiatement accessibles sans mise à jour de la production.

Enfin, la mise à jour de la production est une opération sûre qui ne nécessite généralement pas de temps d'arrêt.

Liens

L'auteur tient à remercier @James MacKeith, @Dmitry Zasypkin et @Regilo Regilio Guedes de Souza pour leur aide précieuse dans la rédaction de cet article.

0
0 89
Article Guillaume Rongier · Déc 18, 2023 7m read

Dans cette série d'articles, j'aimerais présenter et discuter de plusieurs approches possibles pour le développement de logiciels avec les technologies d'InterSystems et GitLab. J'aborderai des sujets tels que:

  • Git 101
  • Flux Git (processus de développement)
  • Installation de GitLab
  • Flux de travail GitLab
  • Diffusion continue
  • Installation et configuration de GitLab
  • GitLab CI/CD
  • Pourquoi des conteneurs?
  • Infrastructure de conteneurs
  • CD utilisant des conteneurs
  • CD utilisant ICM
  • Architecture des conteneurs

Dans cet article, nous aborderons la construction de votre propre conteneur et son déploiement.

0
0 72
Article Guillaume Rongier · Déc 14, 2023 10m read

Dans cette série d'articles, j'aimerais présenter et discuter de plusieurs approches possibles pour le développement de logiciels avec les technologies d'InterSystems et GitLab. J'aborderai des sujets tels que:

  • Git 101
  • Flux Git (processus de développement)
  • Installation de GitLab
  • Flux de travail GitLab
  • Diffusion continue
  • Installation et configuration de GitLab
  • GitLab CI/CD
  • Pourquoi des conteneurs?
  • Infrastructure de conteneurs
  • CD utilisant des conteneurs
  • CD utilisant ICM

Dans cet article, nous allons créer une diffusion continue avec InterSystems Cloud Manager. ICM est une solution de déploiement et de provisionnement en nuage pour les applications basées sur InterSystems IRIS. Il vous permet de définir la configuration de déploiement souhaitée et ICM la provisionne automatiquement. Pour plus d'informations, consultez : First Look : ICM.

0
0 74
Article Guillaume Rongier · Déc 11, 2023 10m read

Dans cette série d'articles, j'aimerais présenter et discuter de plusieurs approches possibles pour le développement de logiciels avec les technologies d'InterSystems et GitLab. J'aborderai des sujets tels que:

  • Git 101
  • Flux Git (processus de développement)
  • Installation de GitLab
  • Flux de travail GitLab
  • Diffusion continue
  • Installation et configuration de GitLab
  • GitLab CI/CD
  • Pourquoi des conteneurs?
  • Infrastructure de conteneurs
  • CD utilisant des conteneurs

Dans le premier article, nous avons évoqué les notions de base de Git, les raisons pour lesquelles une compréhension approfondie des concepts de Git est importante pour le développement de logiciels modernes et la manière dont Git peut être utilisé pour développer des logiciels.

Dans le deuxième article, nous avons évoqué le flux de travail GitLab - un processus complet du cycle de vie du logiciel ainsi que Diffusion continue.

Dans le troisième article, nous avons évoqué l'installation et la configuration de GitLab et la connexion de vos environnements à GitLab

Dans le quatrième article, nous avons écrit une configuration de CD.

Dans le cinquième article, nous avons parlé des conteneurs et de la manière dont ils peuvent être utilisés (et pour quelles raisons).

Dans le sixème article nous abordons des principaux composants dont vous aurez besoin pour exécuter un pipeline de diffusion continue avec des conteneurs et de la façon dont ils fonctionnent tous ensemble.

Dans cet article, nous allons créer une configuration de diffusion continue décrite dans les articles précédents.

0
0 88
Article Guillaume Rongier · Déc 7, 2023 7m read

Dans cette série d'articles, j'aimerais présenter et discuter de plusieurs approches possibles pour le développement de logiciels avec les technologies d'InterSystems et GitLab. J'aborderai des sujets tels que:

  • Git 101
  • Flux Git (processus de développement)
  • Installation de GitLab
  • Flux de travail GitLab
  • Diffusion continue
  • Installation et configuration de GitLab
  • GitLab CI/CD
  • Pourquoi des conteneurs?
  • Infrastructure de conteneurs
  • GitLab CI/CD utilisant des conteneurs

Dans le premier article, nous avons évoqué les notions de base de Git, les raisons pour lesquelles une compréhension approfondie des concepts de Git est importante pour le développement de logiciels modernes et la manière dont Git peut être utilisé pour développer des logiciels.

Dans le deuxième article, nous avons évoqué le flux de travail GitLab - un processus complet du cycle de vie du logiciel ainsi que Diffusion continue.

Dans le troisième article, nous avons évoqué l'installation et la configuration de GitLab et la connexion de vos environnements à GitLab

Dans le quatrième article, nous avons écrit une configuration de CD.

Dans le cinquième article, nous avons parlé des conteneurs et de la manière dont ils peuvent être utilisés (et pour quelles raisons).

Dans cet article, nous allons discuter des principaux composants dont vous aurez besoin pour exécuter un pipeline de diffusion continue avec des conteneurs et de la façon dont ils fonctionnent tous ensemble.

0
0 385
Article Guillaume Rongier · Déc 4, 2023 6m read

Dans cette série d'articles, j'aimerais présenter et discuter de plusieurs approches possibles pour le développement de logiciels avec les technologies d'InterSystems et GitLab. J'aborderai des sujets tels que :

  • Git 101
  • Flux Git (processus de développement)
  • Installation de GitLab
  • Flux de travail GitLab
  • Diffusion continue
  • Installation et configuration de GitLab
  • GitLab CI/CD
  • Pourquoi les conteneurs?
  • GitLab CI/CD utilisant des conteneurs

Dans le premier article, nous avons évoqué les notions de base de Git, les raisons pour lesquelles une compréhension approfondie des concepts de Git est importante pour le développement de logiciels modernes et la manière dont Git peut être utilisé pour développer des logiciels.

Dans le deuxième article, nous avons évoqué le flux de travail GitLab - un processus complet du cycle de vie du logiciel ainsi que Diffusion continue.

Dans le troisième article,  nous avons évoqué l'installation et la configuration de GitLab et la connexion de vos environnements à GitLab

Dans le quatrième article, nous avons écrit une configuration de CD.

Dans cet article, nous parlerons des conteneurs et de la manière dont ils peuvent être utilisés (et pour quelles raisons).

0
0 68
Article Guillaume Rongier · Nov 30, 2023 10m read

Dans cette série d'articles, j'aimerais présenter et discuter de plusieurs approches possibles pour le développement de logiciels avec les technologies d'InterSystems et GitLab. J'aborderai des sujets tels que:

  • Git 101
  • Flux Git (processus de développement)
  • Installation de GitLab
  • Flux de travail GitLab
  • Diffusion continue
  • Installation et configuration de GitLab
  • GitLab CI/CD
0
0 77
Article Guillaume Rongier · Nov 27, 2023 5m read

Dans cette série d'articles, j'aimerais présenter et discuter de plusieurs approches possibles pour le développement de logiciels avec les technologies d'InterSystems et GitLab. J'aborderai des sujets tels que:

  • Git 101
  • Flux Git (processus de développement)
  • Installation de GitLab
  • Flux de travail GitLab
  • Diffusion continue
  • Installation et configuration de GitLab
  • GitLab CI/CD

Dans le premier article, nous avons évoqué les notions de base de Git, les raisons pour lesquelles une compréhension approfondie des concepts de Git est importante pour le développement de logiciels modernes et la manière dont Git peut être utilisé pour développer des logiciels.

Dans le deuxième article, nous avons évoqué le flux de travail GitLab - un processus complet du cycle de vie du logiciel ainsi que Diffusion continue.

Dans cet article, nous allons examiner:

  • Installation et configuration de GitLab
  • Connexion de vos environnements à GitLab
0
0 83
Article Guillaume Rongier · Nov 23, 2023 9m read

Dans cette série d'articles, j'aimerais présenter et discuter de plusieurs approches possibles pour le développement de logiciels avec les technologies d'InterSystems et GitLab. J'aborderai des sujets tels que:

  • Git 101
  • Le flux Git (processus de développement)
  • Installation de GitLab
  • Flux de travail GitLab
  • Diffusion continue
  • Installation et configuration de GitLab
  • GitLab CI/CD

Dans l'article précédent, nous avons évoqué les notions de base de Git, les raisons pour lesquelles une compréhension approfondie des concepts de Git est importante pour le développement de logiciels modernes et la manière dont Git peut être utilisé pour développer des logiciels. Et bien que nous nous soyons concentrés sur la partie mise en œuvre du développement de logiciels, cette partie présente :

  • Le flux de travail GitLab est un processus complet du cycle de vie d'un logiciel, allant de l'idée au retour utilisateur
  • Diffusion continue est une approche d'ingénierie logicielle dans laquelle les équipes produisent des logiciels en cycles courts, garantissant que le logiciel peut être diffusé de manière fiable à tout moment. Elle vise à créer, tester et publier des logiciels plus rapidement et plus fréquemment.
0
0 157
Article Guillaume Rongier · Nov 20, 2023 7m read

Tout le monde dispose d'un environnement de test.

Certains ont la chance de disposer d'un environnement totalement séparé pour la production.

-- Inconnu

.

Dans cette série d'articles, j'aimerais présenter et discuter de plusieurs approches possibles pour le développement de logiciels avec les technologies d'InterSystems et GitLab. J'aborderai des sujets tels que:

  • Git 101
  • Git flow (processus de développement)
  • Installation de GitLab
  • Flux de travail GitLab WorkFlow
  • GitLab CI/CD
  • CI/CD avec des conteneurs

Cette première partie traite de la pierre angulaire du développement logiciel moderne - le système de contrôle de version Git et divers flux Git.

0
0 175
Article Sylvain Guilbaud · Août 24, 2023 9m read

À titre d'exemple d'application en Java fonctionnant avec le dialecte Hibernate pour IRIS, je souhaitais utiliser l'application RealWorld et j'ai trouvé une réalisation pour Quarkus. L'application RealWorld est un exemple d'application proche d'une application réelle, avec des tests déjà préparés pour le backend. La plupart des exemples de réalisations sont à retrouver ici

RealWorld Example App

L'exemple d'application RealWorld est souvent appelé « Wikipédia pour la création d'applications full-stack ». Il sert de prototype standardisé que les développeurs peuvent utiliser pour créer des applications à l'aide de divers langages et frameworks de programmation. L'application fournit un cas d'utilisation réel en imitant une plate-forme de blogs, avec des fonctionnalités telles que l'authentification des utilisateurs, la gestion des profils, la publication d'articles et les commentaires. Avec un ensemble complet de spécifications, y compris une documentation d'API backend prête à l'emploi et des conceptions frontend, il permet aux développeurs de voir comment les mêmes exigences fonctionnelles sont mises en œuvre dans différentes piles technologiques. L'exemple RealWorld est largement utilisé comme outil d'apprentissage et comme référence pour comparer diverses technologies.

Quarkus

Quarkus est un framework Java open source natif de Kubernetes, conçu pour GraalVM et HotSpot. Créé dans le but d'améliorer l'environnement cloud natif moderne, il réduit considérablement l'empreinte et le temps de démarrage des applications Java. Quarkus est connu pour sa philosophie « privilégiant le conteneur », permettant aux développeurs de créer des applications légères et performantes en mettant l'accent sur l'architecture des microservices. Cette flexibilité en a fait un choix populaire pour les organisations cherchant à passer à des plates-formes sans serveur ou basées sur le cloud, combinant des modèles de programmation impératifs et réactifs. Qu'il s'agisse d'une application Web traditionnelle ou d'un système complexe de microservices, Quarkus fournit une plate-forme robuste pour créer des logiciels évolutifs et maintenables.

0
0 70
Article Guillaume Rongier · Fév 17, 2023 18m read

Keywords:  IRIS, IntegratedML, Flask, FastAPI, Tensorflow servant, HAProxy, Docker, Covid-19

Objective:

Nous avons abordé quelques démonstrations rapides d'apprentissage profond et d'apprentissage automatique au cours des derniers mois, notamment un simple classificateur d'images radiographiques Covid-19 et un classificateur de résultats de laboratoire Covid-19 pour les admissions possibles en soins intensifs. Nous avons également évoqué une implémentation de démonstration IntegratedML du classificateur ICU. Alors que la randonnée de la "science des données" se poursuit, le moment est peut-être venu d'essayer de déployer des services d'IA du point de vue de "l'ingénierie des données" - pourrions-nous regrouper tout ce que nous avons abordé jusqu'à présent dans un ensemble d'API de services ? Quels sont les outils, les composants et l'infrastructure communs que nous pourrions exploiter pour réaliser une telle pile de services dans son approche la plus simple possible ?

 

Cadre

Dans le cadre de ce qui suit:

Pour commencer, nous pouvons simplement utiliser docker-compose pour déployer les composants dockerisés suivants dans un serveur AWS Ubuntu

  • HAProxy - équilibreur de charge
  • Gunicorn vs. Univorn ** - passerelle web **serveurs
  • Flask vs. FastAPI - serveurs d'application pour l'interface utilisateur des applications Web, les définitions des API de service, la génération des cartes thermiques, etc.
  • Tensorflow Model Serving vs. Tensorflow-GPU Model Serving - serveurs d'applications pour les classifications d'images, etc.
  • IRIS IntegratedML - AutoML consolidé App+DB avec interface SQL.
  • Python3 dans Jupyter Notebook pour émuler un client pour le benchmarking.
  • Docker et docker-compose.
  • AWS Ubuntu 16.04 avec un GPU Tesla T4  

Remarque:   Tensorflow Serving avec GPU n'est utilisé qu'à des fins de démonstration - vous pouvez simplement désactiver l'image liée au GPU (dans un fichier docker) et la configuration (dans le fichier docker-compose.yml).

Out of scope (Hors de portée) ou sur la prochaine liste de souhaits :

  • **Les serveurs web Nginx ou Apache etc. sont omis dans la démo pour le moment.
  • RabbitMQ et Redis - courtier de file d'attente pour une messagerie fiable qui peut être remplacé par IRIS ou Ensemble.
    IAM (Intersystems API Manger) ou Kong est sur la liste des souhaits.
  • SAM(Intersystems System Alert & Monitoring)
  • ICM (Intersystems Cloud Manager) avec l'opérateur Kubernetes - toujours l'un de mes préférés depuis sa naissance
  • FHIR (serveur FHIR R4 basé sur Intesystems IRIS et FHIR Sandbox pour les applications SMART sur FHIR)
  • Outils de développement CI/CD ou Github Actions.

De toute façon, un "ingénieur en apprentissage automatique" ("Machine Learning Engineer") mettra inévitablement la main sur ces composants pour fournir des environnements de production tout au long des cycles de vie des services. Nous pourrons en savoir plus au fil du temps.

Dépôt Github

Le code source complet se trouve à l'adresse suivante : https://github.com/zhongli1990/covid-ai-demo-deployment

Le référentiel integratedML-demo-template est également réutilisé avec le nouveau référentiel. 
 

Modèle de déploiement

Le schéma de déploiement logique de ce cadre de test "Démonstration de l'IA dans les Dockers" est présenté ci-dessous.

Pour la démonstration, j'ai délibérément créé deux piles distinctes pour la classification de l'apprentissage profond et le rendu web, puis j'ai utilisé un HAProxy comme équilibreur de charge pour distribuer les requêtes API entrantes entre ces deux piles de manière indépendante.

  • Guniorn + Flask + Tensorflow Serving
  • Univcorn + FaskAPI + Tensorflow Serving GPU

IRIS avec IntegratedML est utilisé pour les échantillons de démonstration d'apprentissage automatique, comme dans l'article précédent de prédiction de l'ICU.

J'ai omis certains composants communs dans la démo actuelle qui seraient nécessaires ou envisagés pour les services de production :

  • Serveurs Web : Nginx ou Apache, etc. Ils seront nécessaires entre HAProxy et Gunicorn/Uvicorn, pour une gestion correcte des sessions HTTP, c'est-à-dire pour éviter les attaques DoS, etc.
  • Gestionnaire de file d'attente et bases de données : RabbitMQ et/ou Redis, etc., entre Flask/FastAPI et le serveur backend, pour un service asynchrone fiable et la persistance des données/configurations, etc.
  • Passerelle API : IAM ou Kong clusters, entre l'équilibreur de charge HAProxy et le serveur web pour la gestion des API sans créer de point singulier de défaillance.
  • Surveillance et alerte : SAM serait bien.
  • Provisionnement pour CI/CD devops : ICM avec K8s serait nécessaire pour le déploiement et la gestion neutre en nuage, et pour CI/CD avec d'autres outils devops communs.

En fait,  IRIS lui-même peut certainement être utilisé comme gestionnaire de file d'attente de niveau entreprise ainsi que comme base de données performante pour une messagerie fiable. Dans l'analyse des modèles, il apparaît qu'IRIS peut remplacer les courtiers de file d'attente et les bases de données RabbitMQ/Redis/MongoDB, etc., et qu'il serait donc mieux consolidé avec une latence bien moindre et de meilleures performances globales. Et plus encore, IRIS Web Gateway (anciennement CSP Gateway) peut certainement être positionné à la place de Gunicorn ou Unicorn, etc, n'est-ce pas ?  

Topologie de l'environnement

Il existe quelques options courantes pour mettre en œuvre le modèle logique ci-dessus dans tous les composants Docker. Les plus courantes sont les suivantes :  

  • docker-compose
  • docker swarm etc
  • Kubernetes etc 
  • ICM avec K8s Operation

Cette démonstration commence avec "docker-compose" pour un PoC fonctionnel et un certain benchmarking. Nous aimerions certainement utiliser K8s et peut-être aussi ICM au fil du temps. 

Comme décrit dans son fichier docker-compose.yml, une implémentation physique de sa topologie d'environnement sur un serveur AWS Ubuntu ressemblerait à ceci :  

Le diagramme ci-dessus montre comment ces ports de service de toutes les instances Docker sont mappés et exposés directement sur le serveur Ubuntu à des fins de démonstration. En production, la sécurité devrait être renforcée. Et pour les besoins de la démonstration, tous les conteneurs sont connectés au même réseau Docker, alors qu'en production, il serait séparé en routable externe et non-routable interne.

Composants "Dockerisés" 

Le tableau ci-dessous montre comment les volumes de stockage de la machine hôte sont montés sur chaque instance de conteneur comme spécifié dans ce fichier docker-compose.yml

ubuntu@ip-172-31-35-104:/zhong/flask-xray$ tree ./ -L 2

./├── covid19                             (Les conteneurs Flask+Gunicorn et Tensorflow Serving seront montés ici)├── app.py                                (Flask main app:  Les interfaces de l'application web et du service API sont définies et mises en œuvre ici)├── covid19_models               (Les modèles Tensorflow sont publiés et versionnés ici pour la classification des images Le conteneur Tensorflow Serving avec CPU)├── Dockerfile                          (Le serveur Flask avec Gunicorn:      CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:5000", "--workers", "4", "--threads", "2"])├── models                               (Modèles au format .h5 pour l'application Flask et démonstration API de la génération de heatmaps par grad-cam sur des radiographies.)├── __pycache__├── README.md├── requirements.txt             (Paquets Python nécessaires pour les applications complètes de Flask+Gunicorn) ├── scripts├── static                                  (Fichiers statiques Web)├── templates                         (Modèles de rendu Web)├── tensorflow_serving        (Fichier de configuration pour le service de tensorflow)└── test_images├── covid-fastapi                   (Les conteneurs FastAPI+Uvicorn et Tensorflow Serving avec GPU seront définis ici)├── covid19_models            (Les modèles Tensorflow au service des GPU sont publiés et versionnés ici pour la classification des images)├── Dockerfile                       (Le serveur Uvicorn+FastAPI sera lancé ici: CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4" ])├── main.py                           (FastAPI app: les interfaces de l'application web et du service API sont définies et mises en œuvre ici)├── models                            (Modèles au format .h5 pour l'application FastAPI et démonstration API de la génération de heatmaps par grad-cam sur des radiographies)├── __pycache__├── README.md├── requirements.txt├── scripts├── static├── templates├── tensorflow_serving└── test_images├── docker-compose.yml      (Full stack Docker definition file.  La version 2.3 est utilisée pour tenir compte du GPU Docker "nvidia runtime", sinon la version 3.x peut être utilisée)├── haproxy                             (Le service docker HAProxy est défini ici.  Note : une session collante peut être définie pour le backend LB. )                             ├── Dockerfile└── haproxy.cfg└── notebooks                       (Le service conteneur Jupyter Notebook avec Tensorflow 2.2 et Tensorboard etc)├── Dockerfile├── notebooks                  (Exemples de fichiers notebook pour émuler des applications API Client externes pour les tests fonctionnels et les tests de référence API en Python sur l'équilibreur de charge, etc)└── requirements.txt

Remarque: Le docker-compose.yml ci-dessus est destiné à la démonstration d'apprentissage profond de Convid X-Rays. Il est utilisé avec le docker-compose.yml d'un autre integratedML-demo-template pour former la pile de services complète, comme indiqué dans la topologie de l'environnement.  

Démarrage des services 

Un simple docker-compose up -d permettrait de démarrer tous les services de conteneurs :

ubuntu@ip-172-31-35-104:~$ docker ps
ID DE CONTENEUR        IMAGE                                 COMMANDE                  STATUT CRÉÉ                PORTS                                                                              NOMS
31b682b6961d        iris-aa-server:2020.3AA               "/iris-main"             Il y a 7 semaines         Jusqu'à 2 jours (en bonne santé)   2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:8091->51773/tcp, 0.0.0.0:8092->52773/tcp   iml-template-master_irisimlsvr_1
6a0f22ad3ffc        haproxy:0.0.1                         "/docker-entrypoint.…"   Il y a 8 semaines         Jusqu'à 2 jours             0.0.0.0:8088->8088/tcp                                                             flask-xray_lb_1
71b5163d8960        ai-service-fastapi:0.2.0              "uvicorn main:app --…"   Il y a 8 semaines         Jusqu'à 2 jours             0.0.0.0:8056->8000/tcp                                                             flask-xray_fastapi_1
400e1d6c0f69        tensorflow/serving:latest-gpu         "/usr/bin/tf_serving…"   Il y a 8 semaines         Jusqu'à 2 jours             0.0.0.0:8520->8500/tcp, 0.0.0.0:8521->8501/tcp                                     flask-xray_tf2svg2_1
eaac88e9b1a7        ai-service-flask:0.1.0                "gunicorn app:app --…"   Шl y a 8 semaines         Jusqu'à 2 jours             0.0.0.0:8051->5000/tcp                                                             flask-xray_flask_1
e07ccd30a32b        tensorflow/serving                    "/usr/bin/tf_serving…"   Il y a 8 semaines         Jusqu'à 2 jours             0.0.0.0:8510->8500/tcp, 0.0.0.0:8511->8501/tcp                                     flask-xray_tf2svg1_1
390dc13023f2        tf2-jupyter:0.1.0                     "/bin/sh -c '/bin/ba…"   Il y a 8 semaines         Jusqu'à 2 jours             0.0.0.0:8506->6006/tcp, 0.0.0.0:8586->8888/tcp                                     flask-xray_tf2jpt_1
88e8709404ac        tf2-jupyter-jdbc:1.0.0-iml-template   "/bin/sh -c '/bin/ba…"   Il y a 2 $ois         Jusqu'à 2 jours             0.0.0.0:6026->6006/tcp, 0.0.0.0:8896->8888/tcp                                     iml-template-master_tf2jupyter_1

docker-compose up --scale fastapi=2 --scale flask=2 -d   par exemple, sera mis à l'échelle horizontalement jusqu'à 2 conteneurs Gunicorn+Flask et 2 conteneurs Univcorn+FastAPI :

ubuntu@ip-172-31-35-104:/zhong/flask-xray$ docker ps
ID DE CONTENEUR        IMAGE                                 COMMANDE                  STATUT CRÉÉ                PORTS                                                                              NOMS
dbee3c20ea95        ai-service-fastapi:0.2.0              "uvicorn main:app --…"   Il y a 4 minutes Jusqu'à 4 minutes          0.0.0.0:8057->8000/tcp                                                             flask-xray_fastapi_2
95bcd8535aa6        ai-service-flask:0.1.0                "gunicorn app:app --…"   Il y a 4 minutes Jusqu'à 4 minutes          0.0.0.0:8052->5000/tcp                                                             flask-xray_flask_2

... ...

L'exécution d'un autre "docker-compose up -d" dans le répertoire de travail de "integrtedML-demo-template" a fait apparaître le conteneur irisimlsvr et tf2jupyter dans la liste ci-dessus.

Tests

1. Application web de démonstration de l'IA avec une interface utilisateur simple

Après avoir démarré les services docker ci-dessus, nous pouvons visiter une application web de démonstration pour X-Ray Covid-19 lung detection hébergée dans une instance AWS EC2 à une adresse temporaire à http://ec2-18-134-16-118.eu-west-2.compute.amazonaws.com:8056/

Voici ci-dessous quelques écrans capturés depuis mon mobile. L'interface utilisateur de démonstration est très simple : en gros, je clique sur "Choose File" puis sur le bouton "Submit" pour télécharger une image radiographique, puis l'application affiche un rapport de classification. S'il s'agit d'une radiographie Covid-19, une [carte thermique sera affichée] (https://community.intersystems.com/post/explainability-and-visibility-covid-19-x-ray-classifiers-deep-learning) pour reproduire la zone de lésion "détectée" par DL ; sinon, le rapport de classification ne montrera que l'image radiographique téléchargée.

        

L'application web est une page serveur Python dont la logique est principalement codée dans le fichier main.py de FastAPI, ainsi que dans le fichier app.py de Flask.

Quand j'aurai un peu plus de temps libre, je pourrais documenter en détail les différences de codage et de convention entre Flask et FastAPI. En fait, j'espère pouvoir faire un hébergement de démonstration Flask vs FastAPI vs IRIS pour l'IA. 

2. Test des API de démonstration      

FastAPI (exposé au port 8056) a intégré des documents Swagger API, comme indiqué ci-dessous. C'est très pratique. Tout ce que j'ai à faire est d'utiliser "/docs" dans son URL, par exemple : 

J'ai intégré quelques paramètres (tels que /hello et /items) et quelques interfaces API de démonstration (telles que /healthcheck, /predict, et predict/heatmap).

Testons rapidement ces API, en exécutant quelques lignes Python (en tant qu'émulateur d'application client API) dans l'un des [fichiers d'échantillons de Jupyter Notebook que j'ai créés] (https://github.com/zhongli1990/covid-ai-demo-deployment/tree/master/notebooks/notebooks) pour ce service de démonstration de l'IA.  

Ci-dessous, j'exécute ce fichier à titre d'exemple : https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/notebooks/notebooks/Covid19-3class-Heatmap-Flask-FastAPI-TF-serving-all-in-one-HAProxy2.ipynb

Tout d'abord pour tester que le backend TF-Serving (port 8511) et TF-Serving-GPU (port 8521) sont en place et fonctionnent : 

!curl http://172.17.0.1:8511/v1/models/covid19  # servant tensorflow
!curl http://172.17.0.1:8521/v1/models/covid19  # servant tensorflow-gpu
{
 "model_version_status": [
  {
   "version": "2",
   "state": "AVAILABLE",
   "status": {
    "error_code": "OK",
    "error_message": ""
   }
  }
 ]
}
{
 "model_version_status": [
  {
   "version": "2",
   "state": "AVAILABLE",
   "status": {
    "error_code": "OK",
    "error_message": ""
   }
  }
 ]
}

 

Ensuite, vérifiez que les services API suivants sont en place et fonctionnent :

  • Gunicorn+Flask+TF-Serving
  • Unicorn+FastAPI+TF-Serving-GPU
  • Equilibreur de charge HAProxy en face des services gênants ci-dessus
  • r = requests.get('http://172.17.0.1:8051/covid19/api/v1/healthcheck')  # tf servant le docker avec le cpu
    print(r.status_code, r.text)
    r = requests.get('http://172.17.0.1:8056/covid19/api/v1/healthcheck')  # tf-servant le docker avec le gpu
    print(r.status_code, r.text)
    r = requests.get('http://172.17.0.1:8088/covid19/api/v1/healthcheck')  # tf-servant le docker avec le HAproxy
    print(r.status_code, r.text)

    Et les résultats attendus seraient :

    200 L'API du détecteur Covid19 est en ligne !
    200 "L'API du détecteur Covid19 est en ligne !\n\n"
    200 "L'API du détecteur Covid19 est en ligne !\n\n"

     

    Tester une interface API fonctionnelle, telle que **/predict/heatmap ** pour renvoyer le résultat de la classification et de la heatmap d'une image radiographique d'entrée. L'image entrante est codée en based64 avant d'être envoyée via HTTP POST conformément aux définitions de l'API :

    %%time# Importation de la bibliothèque des requêtes
    import argparse
    import base64import requests# définition d'un point d'entrée ap pour api
    API_ENDPOINT = "http://172.17.0.1:8051/covid19/api/v1/predict/heatmap"image_path = './Covid_M/all/test/covid/nejmoa2001191_f3-PA.jpeg'
    #image_path = './Covid_M/all/test/normal/NORMAL2-IM-1400-0001.jpeg'
    #image_path = './Covid_M/all/test/pneumonia_bac/person1940_bacteria_4859.jpeg'
    b64_image = ""
    # Encoding the JPG,PNG,etc. image to base64 format
    with open(image_path, "rb") as imageFile:
        b64_image = base64.b64encode(imageFile.read())# données à envoyer à l'api
    data = {'b64': b64_image}# envoi d'une requête post et enregistrement de la réponse en tant qu'objet réponse
    r = requests.post(url=API_ENDPOINT, data=data)print(r.status_code, r.text)# extraction de la réponse
    print("{}".format(r.text))

    Toutes ces [images de test ont également été téléchargées sur GitHub] (https://github.com/zhongli1990/Covid19-X-Rays/tree/master/all/test). Le résultat du code ci-dessus sera comme ça:

    200 {"Input_Image":"http://localhost:8051/static/source/0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png","Output_Heatmap":"http://localhost:8051/static/result/Covid19_98_0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png.png","X-Ray_Classfication_Raw_Result":[[0.805902302,0.15601939,0.038078323]],"X-Ray_Classification_Covid19_Probability":0.98,"X-Ray_Classification_Result":"Covid-19 POSITIVE","model_name":"Customised Incpetion V3"}
    
    {"Input_Image":"http://localhost:8051/static/source/0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png","Output_Heatmap":"http://localhost:8051/static/result/Covid19_98_0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png.png","X-Ray_Classfication_Raw_Result":[[0.805902302,0.15601939,0.038078323]],"X-Ray_Classification_Covid19_Probability":0.98,"X-Ray_Classification_Result":"Covid-19 POSITIVE","model_name":"Customised Incpetion V3"}
    
    CPU times: user 16 ms, sys: 0 ns, total: 16 ms
    Wall time: 946 ms

     

    3. Les applications de démonstration de services pour les tests benchmarkés

    Nous avons mis en place une instance d'équilibreur de charge HAProxy. Nous avons également démarré un service Flask avec 4 travailleurs, et un service FastAPI avec 4 travailleurs également.

    Pourquoi ne pas créer 8x processus Pyhon directement dans le fichier Notebook, pour émuler 8x clients API simultanés envoyant des requêtes dans les API du service de démonstration, pour voir ce qui se passe ? 

    #from concurrent.futures import ThreadPoolExecutor as PoolExecutor
    from concurrent.futures import ProcessPoolExecutor as PoolExecutor
    import http.client
    import socket
    import timestart = time.time()#laodbalancer:
    API_ENDPOINT_LB = "http://172.17.0.1:8088/covid19/api/v1/predict/heatmap"
    API_ENDPOINT_FLASK = "http://172.17.0.1:8052/covid19/api/v1/predict/heatmap"
    API_ENDPOINT_FastAPI = "http://172.17.0.1:8057/covid19/api/v1/predict/heatmap"
    def get_it(url):
        try:
            # boucle sur les images
            for imagePathTest in imagePathsTest:
                b64_image = ""
                with open(imagePathTest, "rb") as imageFile:
                    b64_image = base64.b64encode(imageFile.read())
    
                data = {'b64': b64_image}
                r = requests.post(url, data=data)
                #print(imagePathTest, r.status_code, r.text)
            return r
        except socket.timeout:
            # dans un scénario du monde réel, vous feriez probablement quelque chose si le
            # socket passe en timeout
            passurls = [API_ENDPOINT_LB, API_ENDPOINT_LB,
            API_ENDPOINT_LB, API_ENDPOINT_LB,
            API_ENDPOINT_LB, API_ENDPOINT_LB,
            API_ENDPOINT_LB, API_ENDPOINT_LB]with PoolExecutor(max_workers=16) as executor:
        for _ in executor.map(get_it, urls):
            pass
    
    print("--- %s seconds ---" % (time.time() - start))

    Il a donc fallu 74 secondes pour traiter 8x27 = 216 images de test. Cette pile de démonstration à charge équilibrée était capable de traiter 3 images par seconde (en renvoyant les résultats de la classification et de la heatmap aux clients) :

    --- 74.37691688537598 seconds ---

    À partir de la commande Top de la session Putty, nous pouvons voir que 8x processus de serveur (4x gunicorn + 4 unicorn/python) ont commencé à monter en puissance dès que les scripts de référence ci-dessus ont commencé à être exécutés.

    Suivant

    Cet article n'est qu'un point de départ pour mettre en place une pile de déploiement "All-in-Docker AI demo" comme cadre de test. J'espère ensuite ajouter d'autres interfaces de démonstration API, telles que l'interface de prédiction des soins intensifs Covid-19, idéalement conforme à la norme FHIR R4, etc. Cela pourrait également être un banc d'essai pour explorer une intégration plus étroite avec les capacités ML hébergées par IRIS. Au fil du temps, il peut être utilisé comme un cadre de test (et un cadre assez simple) pour intercepter de plus en plus de modèles ML ou DL spécialisés au fur et à mesure que nous avançons sur divers aspects de l'IA, notamment l'imagerie médicale, la santé de la population ou la prédiction personnalisée, le traitement automatique des langues, etc. J'ai également dressé une liste de souhaits à la toute fin de l'article précédent (dans sa section "Next" (Suivant))

    0
    0 314
    Article Sylvain Guilbaud · Fév 15, 2023 8m read

    Introduction

    Dans un article précédent, j'ai abordé les modèles d'exécution des tests unitaires via le gestionnaire de paquets ObjectScript. Cet article va un peu plus loin, en utilisant les actions GitHub pour piloter l'exécution des tests et la création de rapports. Le cas d'utilisation qui nous motive est l'exécution du CI pour l'un de mes projets Open Exchange, AppS.REST (voir l'article d'introduction à ce projet ici). Vous pouvez voir l'implémentation complète dont les extraits de cet article ont été tirés sur GitHub ; elle pourrait facilement servir de modèle pour l'exécution de l'IC pour d'autres projets utilisant le gestionnaire de paquets ObjectScript.

    Les fonctionnalités dont la mise en œuvre a été démontrée comprennent :

    • Compilation et test d'un paquet ObjectScript
    • Rapport sur la mesure de la couverture des tests (en utilisant le paquet TestCoverage) via codecov.io
    • Téléchargement d'un rapport sur les résultats des tests en tant qu'artefact de comppilation.

    L'environnement de compilation

    Il existe une documentation complète sur les actions GitHub ici. Dans le cadre de cet article, nous nous contenterons d'explorer les aspects présentés dans cet exemple.

    Un flux de travail dans les actions GitHub est déclenché par un ensemble configurable d'événements et consiste en un certain nombre de tâches qui peuvent être exécutées séquentiellement ou en parallèle. Chaque tâche comporte un ensemble d'étapes - nous allons entrer dans le détail des étapes de notre exemple d'action plus tard. Ces étapes consistent en références à des actions disponibles sur GitHub, ou peuvent simplement être des commandes shell. Un extrait du modèle initial de notre exemple ressemble à ceci :

    # Flux de travail d'intégration continue
    name: CI
    
    # Contrôle le moment où l'action sera exécutée. Déclenche le flux de travail sur les événements push ou pull request
    # événements dans toutes les branches
    on: [push, pull_request]
    
    # Un flux de travail est composé d'une ou plusieurs tâches qui peuvent être exécutées séquentiellement ou en parallèle.
    jobs:
      # Ce flux de travail contient une seule tâche appelé "build".
      build :
        # Le type d'exécuteur sur lequel le travail sera exécuté
        runs-on : ubuntu-latest
    
        env:
        # Variables d'environnement utilisables tout au long de la tâche de "compilation", par exemple dans les commandes au niveau du système d'exploitation.
    package: apps.rest
        container_image : intersystemsdc/iris-community:2019.4.0.383.0-zpm
        # D'autres éléments seront abordés plus tard...
    
        # Les étapes représentent une séquence de tâches qui seront exécutées dans le cadre du travail.
         steps:
              # Ceux-ci seront montrés plus tard...

    Dans cet exemple, un certain nombre de variables d'environnement sont utilisées. Pour appliquer cet exemple à d'autres paquets utilisant le gestionnaire de paquets ObjectScript, la plupart d'entre elles n'auront pas besoin d'être modifiées, alors que certaines le seront.

        env:
          # ** POUR UN USAGE GÉNÉRAL, IL FAUDRA PROBABLEMENT CHANGER : **
          package: apps.rest
          container_image: intersystemsdc/iris-community:2019.4.0.383.0-zpm
    
          # ** POUR UN USAGE GÉNÉRAL, IL FAUDRA PEUT-ÊTRE CHANGER : **
          build_flags: -dev -verbose # Télécharger en mode -dev pour obtenir le code de test unitaire préchargé
          test_package: UnitTest
    
          # ** POUR UN USAGE GÉNÉRAL, IL NE FAUDRA PAS CHANGER : **
          instance: iris
          # Remarque : la valeur test_reports est dupliquée dans la variable d'environnement test_flags.
          test_reports: test-reports
          test_flags: >-
           -verbose -DUnitTest.ManagerClass=TestCoverage.Manager -DUnitTest.JUnitOutput=/test-reports/junit.xml
           -DUnitTest.FailuresAreFatal=1 -DUnitTest.Manager=TestCoverage.Manager
           -DUnitTest.UserParam.CoverageReportClass=TestCoverage.Report.Cobertura.ReportGenerator
           -DUnitTest.UserParam.CoverageReportFile=/source/coverage.xml

    Si vous voulez adapter cela à votre propre paquet, il suffit de déposer votre propre nom de paquet et votre image de conteneur préférée (doit inclure zpm - voir https://hub.docker.com/r/intersystemsdc/iris-community). Vous pourriez également vouloir changer le paquet de tests unitaires pour qu'il corresponde à la convention de votre propre paquet (si vous devez charger et compiler les tests unitaires avant de les exécuter pour gérer toutes les dépendances de chargement/compilation ; j'ai eu quelques problèmes bizarres spécifiques aux tests unitaires pour ce paquet, donc cela pourrait même ne pas être pertinent dans d'autres cas).

    Le nom de l'instance et le répertoire test_reports ne devraient pas être modifiés pour d'autres utilisations, et les test_flags fournissent un bon ensemble de valeurs par défaut - ils permettent de faire en sorte que les échecs des tests unitaires signalent l'échec de la compilation, et gèrent également l'exportation des résultats des tests au format jUnit et un rapport de couverture de code.

    Étapes de compilation

    Vérification des référentiels GitHub

    Dans notre exemple de motivation, deux dépôts doivent être vérifiés - celui qui est testé, et aussi mon fork de Forgery (parce que les tests unitaires en ont besoin).

        # Vérifie ce référentiel sous $GITHUB_WORKSPACE, afin que votre tâche puisse y accéder.
        - uses: actions/checkout@v2
    
        # Il faut aussi vérifier le timleavitt/forgery jusqu'à la version officielle installable via ZPM
        - uses: actions/checkout@v2
          with:
            repository: timleavitt/forgery
            path: forgery

    $GITHUB_WORKSPACE est une variable d'environnement très importante, représentant le répertoire racine où tout cela fonctionne. Du point de vue des permissions, vous pouvez faire à peu près tout ce que vous voulez dans ce répertoire ; ailleurs, vous pouvez rencontrer des problèmes.

    Exécution du conteneur IRIS d'InterSystems

    Après avoir configuré un répertoire où nous finirons par placer nos rapports de résultats de tests, nous allons exécuter le conteneur InterSystems IRIS Community Edition (+ZPM) pour notre compilation.

        - name: Run Container
          run: |
            # Créer le répertoire test_reports pour partager les résultats des tests avant l'exécution du conteneur.
            mkdir $test_reports
            chmod 777 $test_reports
            # Lancer l'instance InterSystems IRIS
            docker pull $container_image
            docker run -d -h $instance --name $instance -v $GITHUB_WORKSPACE:/source -v $GITHUB_WORKSPACE/$test_reports:/$test_reports --init $container_image
            echo halt > wait
            # Attendez que l'instance soit prête
            until docker exec --interactive $instance iris session $instance &lt; wait; do sleep 1; done

    Il y a deux volumes partagés avec le conteneur - l'espace de travail GitHub (pour que le code puisse être chargé ; nous y rapporterons également des informations sur la couverture des tests), et un répertoire séparé où nous placerons les résultats des tests jUnit.

    Après la fin de "docker run", cela ne signifie pas que l'instance est complètement démarrée et prête à être commandée. Pour attendre que l'instance soit prête, nous continuerons à essayer d'exécuter une commande "halt" via la session iris ; cela échouera et continuera à essayer une fois par seconde jusqu'à ce que cela réussisse (éventuellement), indiquant que l'instance est prête.

    Installation des bibliothèques liées aux tests

    Pour notre cas d'utilisation motivant, nous utiliserons deux autres bibliothèques pour les tests - TestCoverage et Forgery. TestCoverage peut être installé directement via le Community Package Manager ; Forgery (actuellement) doit être chargé via zpm "load" ; mais les deux approches sont valables.

        - name: Install TestCoverage
          run: |
            echo "zpm \"install testcoverage\":1:1" > install-testcoverage
            docker exec --interactive $instance iris session $instance -B &lt; install-testcoverage
            # Solution aux problèmes de permissions dans TestCoverage (création d'un répertoire pour l'exportation des sources)
            chmod 777 $GITHUB_WORKSPACE
    
        - name: Install Forgery
          run: |
            echo "zpm \"load /source/forgery\":1:1" > load-forgery
            docker exec --interactive $instance iris session $instance -B &lt; load-forgery

    L'approche générale consiste à écrire les commandes dans un fichier, puis à les exécuter en session IRIS. Le ":1:1" supplémentaire dans les commandes ZPM indique que la commande doit quitter le processus avec un code d'erreur si une erreur se produit, et s'arrêter à la fin si aucune erreur ne se produit ; cela signifie que si une erreur se produit, elle sera signalée comme une étape de compilation ayant échoué, et nous n'avons pas besoin d'ajouter une commande "halt" à la fin de chaque fichier.

    Compilation et test du paquet

    Enfin, nous pouvons effectivement compiler et exécuter des tests pour notre paquet. C'est assez simple - remarquez l'utilisation des variables d'environnement $build_flags/$test_flags que nous avons définies plus tôt.

        # Exécute un ensemble de commandes en utilisant l'exécuteur runners
        - name: Build and Test
          run: |
            # Exécution de compilation
            echo "zpm \"load /source $build_flags\":1:1" > build
            # Le paquet de test est compilé en premier comme solution de contournement pour certains problèmes de dépendance.
            echo "do \$System.OBJ.CompilePackage(\"$test_package\",\"ckd\") " > test
            # Exécution des tests
            echo "zpm \"$package test -only $test_flags\":1:1" >> test
            docker exec --interactive $instance iris session $instance -B < build && docker exec --interactive $instance iris session $instance -B < test && bash <(curl -s https://codecov.io/bash)

    Cela suit le même schéma que nous avons vu, écrire des commandes dans un fichier puis utiliser ce fichier comme entrée de la session iris.

    La dernière partie de la dernière ligne télécharge les résultats de la couverture du code sur codecov.io. Super facile !

    Téléchargement des résultats des tests unitaires

    Supposons qu'un test unitaire échoue. Il serait vraiment ennuyeux de devoir revenir en arrière dans le journal de compilation pour trouver ce qui n'a pas fonctionné, bien que cela puisse toujours fournir un contexte utile. Pour nous faciliter la vie, nous pouvons télécharger nos résultats formatés par jUnit et même exécuter un programme tiers pour les transformer en un joli rapport HTML.

        # Générer et télécharger le rapport HTML xUnit
        - name: XUnit Viewer
          id: xunit-viewer
          uses: AutoModality/action-xunit-viewer@v1
          if: always()
          with:
            # Avec -DUnitTest.FailuresAreFatal=1, un test unitaire qui échoue fera échouer la compilation avant ce point.
            # Cette action pourrait autrement mal interpréter notre sortie de style xUnit et faire échouer la compilation même si
            # tous les tests sont passés.
            fail: false
        - name: Atacher le rapport
          uses: actions/upload-artifact@v1
          if: always()
          with:
            name: ${{ steps.xunit-viewer.outputs.report-name }}
            path: ${{ steps.xunit-viewer.outputs.report-dir }}

    Ces informations sont principalement tirées du fichier readme à l'adresse https://github.com/AutoModality/action-xunit-viewer.

    Le résultat final

    Si vous voulez voir les résultats de ce flux de travail, regardez :

    Les journaux pour le job CI sur intersystems/apps-rest (y compris les artefacts de compilation) : https://github.com/intersystems/apps-rest/actions?query=workflow%3ACI
    Rapports de couverture de test : https://codecov.io/gh/intersystems/apps-rest

    N'hésitez pas à me faire savoir si vous avez des questions !

    0
    0 96