Système de Provisionnement
OmniCRM utilise Ansible pour automatiser le provisionnement, la configuration et le déprovisionnement des services clients. Le système de provisionnement est conçu pour être flexible, permettant des flux de travail complexes tout en maintenant la cohérence et la fiabilité.

::: note ::: title Remarque :::
Pour un guide complet du parcours produit-service avec des exemples détaillés de playbooks Ansible, des stratégies de tarification et des scénarios du monde réel, voir Guide Complet du Cycle de Vie du Produit. :::
Aperçu
Lorsqu'un produit est commandé ou qu'un service doit être configuré, OmniCRM crée un Travail de Provisionnement qui exécute un ou plusieurs playbooks Ansible. Ces playbooks interagissent avec divers systèmes backend (OCS/CGRateS, équipements réseau, APIs, etc.) pour provisionner complètement le service.
Le système de provisionnement prend en charge deux flux de travail principaux :
- Provisionnement Standard - Déclenché par le personnel ou les clients via l'UI/API
- Provisionnement Simple - Déclenché par des systèmes externes comme OCS pour des opérations automatisées
Valeurs de Statut de Provisionnement
Les travaux de provisionnement et les tâches individuelles peuvent avoir les statuts suivants :
- Statut 0 (Succès) - Le travail de provisionnement s'est terminé avec succès
- Statut 1 (En cours) - Le travail de provisionnement ou la tâche est actuellement en cours d'exécution
- Statut 2 (Échoué - Critique) - Un échec critique s'est produit, ce qui a entraîné l'échec du provisionnement
- Statut 3 (Échoué - Ignoré) - Une tâche a échoué mais avait
ignore_errors: true, donc le provisionnement a continué
Lorsqu'un travail de provisionnement échoue, OmniCRM envoie des notifications par e-mail à la liste de notification d'échec configurée avec des informations d'erreur détaillées.
Comment les Produits Pilotent le Provisionnement
La définition du Produit est le plan pour ce qui est provisionné et comment. Lorsqu'un utilisateur sélectionne un produit à provisionner, le système lit plusieurs champs clés de la définition du produit pour déterminer quoi faire.
Champs de Produit Utilisés dans le Provisionnement
Une définition de produit contient :
provisioning_play- Le nom du playbook Ansible à exécuter (sans l'extension .yaml)provisioning_json_vars- Chaîne JSON contenant des variables par défaut à passer à Ansibleinventory_items_list- Liste des types d'inventaire qui doivent être assignés (par exemple,['SIM Card', 'Mobile Number'])product_id,product_name, champs de tarification - Passés automatiquement au playbook
Exemple de Définition de Produit
{
"product_id": 1,
"product_slug": "Mobile-SIM",
"product_name": "Mobile SIM Only",
"provisioning_play": "play_psim_only",
"provisioning_json_vars": "{\"iccid\": \"\", \"msisdn\": \"\"}",
"inventory_items_list": "['SIM Card', 'Mobile Number']",
"retail_cost": 0,
"retail_setup_cost": 0,
"wholesale_cost": 3,
"wholesale_setup_cost": 1
}
Du Produit au Travail de Provisionnement
Lorsque le provisionnement est initié, le système :
-
Charge le playbook spécifié dans
provisioning_playLe système recherche
OmniCRM-API/Provisioners/plays/play_psim_only.yaml -
Fusionne les variables de plusieurs sources dans
extra_vars:a. Depuis provisioning_json_vars :
{"iccid": "", "msisdn": ""}b. Depuis le corps de la requête : Toute variable supplémentaire fournie par l'utilisateur/API c. Depuis les champs de produit :product_id,customer_id, etc. d. Depuis l'authentification :access_tokenou configuration pourrefresh_token -
Assigne l'inventaire basé sur
inventory_items_listAvant d'exécuter le playbook, l'UI/API demande la sélection de l'inventaire :
- SIM Card - L'utilisateur sélectionne une SIM disponible dans l'inventaire
- Mobile Number - L'utilisateur sélectionne un numéro de téléphone disponible
Les IDs d'inventaire sélectionnés sont ajoutés à
extra_varsavec le type d'inventaire comme clé :extra_vars = {
"product_id": 1,
"customer_id": 456,
"SIM Card": 789, # inventory_id de la SIM sélectionnée
"Mobile Number": 101, # inventory_id du numéro de téléphone sélectionné
"iccid": "", # Depuis provisioning_json_vars
"msisdn": "", # Depuis provisioning_json_vars
"access_token": "eyJ..."
} -
Passe tout à Ansible via
hostvars[inventory_hostname]À l'intérieur du playbook, les variables sont accessibles comme :
- name: Get inventory_id for SIM Card
set_fact:
inventory_id_sim_card: "{{ hostvars[inventory_hostname]['SIM Card'] | int }}"
when: "'SIM Card' in hostvars[inventory_hostname]"
Comment les Playbooks Utilisent les Variables d'Inventaire
Une fois que le playbook a les IDs d'inventaire, il récupère les détails complets de l'inventaire depuis l'API :
- name: Get SIM Card Details from Inventory
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ inventory_id_sim_card }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: sim_card_response
- name: Extract ICCID and IMSI from inventory
set_fact:
iccid: "{{ sim_card_response.json.iccid }}"
imsi: "{{ sim_card_response.json.imsi }}"
- name: Get Phone Number Details from Inventory
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ inventory_id_phone_number }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: phone_number_response
- name: Extract MSISDN
set_fact:
msisdn: "{{ phone_number_response.json.msisdn }}"
Le playbook peut ensuite utiliser ces valeurs pour :
- Provisionner la carte SIM sur le HSS avec l'IMSI
- Configurer le numéro de téléphone dans le système de facturation
- Assigner les éléments d'inventaire au client
- Créer l'enregistrement de service avec ces détails
Exemple du Monde Réel : Provisionnement de SIM Mobile
Depuis play_psim_only.yaml, voici comment il utilise les données de produit et d'inventaire :
- name: Get Product information from CRM API
uri:
url: "{{ crm_config.crm.base_url }}/crm/product/product_id/{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_product
- name: Set package facts from product
set_fact:
package_name: "{{ api_response_product.json.product_name }}"
package_comment: "{{ api_response_product.json.comment }}"
setup_cost: "{{ api_response_product.json.retail_setup_cost }}"
monthly_cost: "{{ api_response_product.json.retail_cost }}"
- name: Set inventory_id_sim_card if SIM Card was selected
set_fact:
inventory_id_sim_card: "{{ hostvars[inventory_hostname]['SIM Card'] | int }}"
when: "'SIM Card' in hostvars[inventory_hostname]"
- name: Set inventory_id_phone_number if Mobile Number was selected
set_fact:
inventory_id_phone_number: "{{ hostvars[inventory_hostname]['Mobile Number'] | int }}"
when: "'Mobile Number' in hostvars[inventory_hostname]"
- name: Get SIM Card details from inventory
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ inventory_id_sim_card }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: sim_inventory_response
- name: Get Phone Number details from inventory
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ inventory_id_phone_number }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: phone_inventory_response
- name: Extract values from inventory
set_fact:
iccid: "{{ sim_inventory_response.json.iccid }}"
imsi: "{{ sim_inventory_response.json.imsi }}"
msisdn: "{{ phone_inventory_response.json.msisdn }}"
ki: "{{ sim_inventory_response.json.ki }}"
opc: "{{ sim_inventory_response.json.opc }}"
- name: Provision subscriber on HSS
uri:
url: "http://{{ hss_server }}/subscriber/{{ imsi }}"
method: PUT
body_format: json
body:
{
"imsi": "{{ imsi }}",
"msisdn": "{{ msisdn }}",
"ki": "{{ ki }}",
"opc": "{{ opc }}",
"enabled": true
}
status_code: 200
- name: Assign inventory to customer
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ inventory_id_sim_card }}"
method: PATCH
headers:
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": {{ customer_id }},
"item_state": "Assigned"
}
status_code: 200
Cela démontre le flux complet :
- La définition du produit spécifie
provisioning_play: "play_psim_only" - Le produit nécessite
inventory_items_list: ['SIM Card', 'Mobile Number'] - L'utilisateur sélectionne des éléments d'inventaire lors du provisionnement
- Les IDs d'inventaire sont passés au playbook comme
extra_vars - Le playbook récupère les détails complets de l'inventaire depuis l'API
- Le playbook utilise les données d'inventaire pour configurer l'équipement réseau
- Le playbook marque l'inventaire comme assigné au client
Rollback et Nettoyage : Modèle de Meilleure Pratique
Meilleure Pratique Critique : Le même playbook doit gérer à la fois le rollback de provisionnement échoué et le déprovisionnement intentionnel en utilisant la structure block et rescue d'Ansible.
Structure du Playbook
Depuis play_psim_only.yaml :
- name: OmniCore Service Provisioning 2024
hosts: localhost
gather_facts: no
become: False
tasks:
- name: Main block
block:
# --- TÂCHES DE PROVISIONNEMENT ---
- name: Get Product information
uri: ...
- name: Create account in OCS
uri: ...
- name: Provision subscriber on HSS
uri: ...
- name: Create service record
uri: ...
# ... beaucoup d'autres tâches de provisionnement ...
rescue:
# --- TÂCHES DE NETTOYAGE ---
# Cette section s'exécute lorsque :
# 1. Toute tâche dans le bloc échoue (rollback)
# 2. action == "deprovision" (nettoyage intentionnel)
- name: Get Inventory items linked to this service
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/customer_id/{{ customer_id }}"
method: GET
register: inventory_api_response
ignore_errors: True
- name: Return inventory to pool
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ item.inventory_id }}"
method: PATCH
body_format: json
body:
service_id: null
customer_id: null
item_state: "Used"
with_items: "{{ inventory_api_response.json.data }}"
ignore_errors: True
- name: Delete Account from Charging
uri:
url: "http://{{ crm_config.ocs.OCS }}/jsonrpc"
method: POST
body:
{
"method": "ApierV1.RemoveAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}"
}]
}
ignore_errors: True
- name: Delete Attribute Profile
uri:
url: "http://{{ crm_config.ocs.OCS }}/jsonrpc"
method: POST
body:
{
"method": "APIerSv1.RemoveAttributeProfile",
"params": [{
"ID": "ATTR_ACCOUNT_{{ service_uuid }}"
}]
}
ignore_errors: True
- name: Remove Resource Profile
uri: ...
ignore_errors: True
- name: Remove Filters
uri: ...
ignore_errors: True
- name: Deprovision Subscriber from HSS
uri:
url: "{{ item.key }}/subscriber/{{ item.value.subscriber_id }}"
method: DELETE
loop: "{{ hss_subscriber_data | dict2items }}"
ignore_errors: True
when:
- deprovision_subscriber | bool == true
- name: Patch Subscriber to Dormant State
uri:
url: "{{ item.key }}/subscriber/{{ item.value.subscriber_id }}"
method: PATCH
body:
{
"enabled": true,
"msisdn": "9999{{ imsi[-10:] }}", # Numéro fictif
"ue_ambr_dl": 9999999, # Inutilisable
"ue_ambr_ul": 9999999
}
loop: "{{ hss_subscriber_data | dict2items }}"
when:
- deprovision_subscriber | default(false) | bool == false
# L'assertion finale détermine le succès ou l'échec
- name: Set status to "Success" if Manual deprovision / Fail if failed provision
assert:
that:
- action == "deprovision"
Pourquoi Ce Modèle est une Meilleure Pratique
1. Pas de Duplication de Code
Les mêmes tâches de nettoyage gèrent les deux scénarios :
- Échec de Provision (Rollback) : Si une tâche dans le
blockéchoue, la sectionrescues'exécute automatiquement - Déprovisionnement Intentionnel : Lorsqu'il est appelé avec
action: "deprovision", le playbook passe immédiatement àrescue
2. Nettoyage Complet Garanti
Lorsqu'un provisionnement échoue en cours de route, la section de secours garantit :
- Tous les comptes OCS créés sont supprimés
- Toutes les entrées d'équipement réseau configurées sont supprimées
- L'inventaire assigné est retourné au pool
- Les abonnés HSS sont supprimés ou mis en état dormant
- Aucun provisionnement partiel ne reste dans aucun système
Cela empêche les ressources "orphelines" qui :
- Consomment de l'inventaire sans être suivies
- Créent des comptes de facturation qui ne sont pas liés aux services
- Provoquent de la confusion lors du dépannage
- Gaspillent des ressources réseau
3. Gestion des Échecs Graceful avec ignore_errors
Remarquez que chaque tâche de nettoyage utilise ignore_errors: True. C'est intentionnel car :
- Lors du rollback, certaines ressources peuvent ne pas avoir encore été créées
- Nous voulons tenter toutes les tâches de nettoyage même si certaines échouent
- L'assertion finale détermine le succès/échec global
Par exemple, si le provisionnement échoue à "Créer un compte dans OCS", le nettoyage essaiera de :
- Supprimer le compte OCS (échouera, mais ignoré)
- Supprimer les profils d'attributs (échouera, mais ignoré)
- Retourner l'inventaire (réussit)
- Supprimer l'abonné HSS (peut ne pas exister, ignoré)
4. Distinction entre Déprovisionnement et Rollback
L'assertion finale à la fin de rescue est astucieuse :
- name: Set status to "Success" if Manual deprovision / Fail if failed provision
assert:
that:
- action == "deprovision"
Cela signifie :
- Si
action == "deprovision": L'assertion passe, le playbook réussit (statut 0) - Si
actionn'est pas défini ou != "deprovision" : L'assertion échoue, le playbook échoue (statut 2)
Ainsi, le même code de nettoyage entraîne différents statuts de travail de provisionnement en fonction de l'intention.
5. Nettoyage Conditionnel Basé sur le Type de Service
Certaines tâches de nettoyage utilisent des conditionnels pour gérer différents scénarios :
- name: Deprovision Subscriber from HSS
uri: ...
when:
- deprovision_subscriber | bool == true
- name: Patch Subscriber to Dormant State
uri: ...
when:
- deprovision_subscriber | default(false) | bool == false
Cela permet un nettoyage flexible :
- Suppression complète : Lorsque les SIM sont dédiées aux clients
(
deprovision_subscriber: true) - État dormant : Lorsque les SIM sont réutilisables et doivent rester dans le HSS
(
deprovision_subscriber: false)
Comment Utiliser Ce Modèle
Pour le Provisionnement :
{
"product_id": 1,
"customer_id": 456,
"provisioning_play": "play_psim_only"
}
Si le provisionnement échoue, un rollback automatique se produit via rescue.
Pour le Déprovisionnement :
{
"service_id": 123,
"service_uuid": "Service_abc123",
"action": "deprovision",
"provisioning_play": "play_psim_only"
}
Le playbook passe directement à la section rescue, exécute tout le nettoyage et réussit.
Résumé des Avantages
✅ Source unique de vérité : Un playbook gère le provisionnement et le déprovisionnement ✅ Opérations atomiques : Soit entièrement provisionné, soit entièrement nettoyé ✅ Pas de ressources orphelines : Les provisions échouées ne laissent aucune trace ✅ Maintenance facilitée : Les modifications de la logique de provisionnement s'appliquent automatiquement au nettoyage ✅ Réduction des erreurs : Aucune chance que le code de provisionnement et de déprovisionnement soit désynchronisé ✅ Testable : Peut tester la logique de déprovisionnement en exécutant avec action: "deprovision"
Ce modèle doit être suivi dans tous les playbooks de provisionnement pour garantir la fiabilité et la cohérence.
Surcharge des Variables de Produit
Les provisioning_json_vars peuvent être surchargées au moment du provisionnement. Par exemple, un produit pourrait définir :
{
"provisioning_json_vars": "{\"monthly_cost\": 50, \"data_limit_gb\": 100}"
}
Mais lors du provisionnement, vous pouvez surcharger celles-ci :
{
"product_id": 1,
"customer_id": 456,
"monthly_cost": 45,
"data_limit_gb": 150
}
Les extra_vars fusionnés utiliseront les valeurs surchargées. Cela permet :
- Une tarification personnalisée pour des clients spécifiques
- Différents limites de données en fonction des promotions
- Des tests avec différents paramètres sans modifier le produit
Produits Sans Inventaire
Tous les produits ne nécessitent pas d'inventaire. Par exemple, un addon de données ou un basculement de fonctionnalité pourrait avoir :
{
"product_id": 10,
"product_name": "Extra 10GB Data",
"provisioning_play": "play_local_data_addon",
"provisioning_json_vars": "{\"data_gb\": 10}",
"inventory_items_list": "[]"
}
Dans ce cas, le playbook reçoit :
extra_vars = {
"product_id": 10,
"customer_id": 456,
"service_id": 123, # Service auquel ajouter des données
"data_gb": 10,
"access_token": "eyJ..."
}
Le playbook ajoute simplement les données au service existant sans avoir besoin d'éléments d'inventaire.
Flux de Travail de Provisionnement Standard
Le provisionnement standard est initié lorsque :
- Un membre du personnel ajoute un service à un client depuis l'UI
- Un client commande un service via le portail d'auto-assistance
- L'API est appelée directement avec
PUT /crm/provision/
Lorsque Vous Cliquez sur "Provisionner"
Voici le flux complet qui se produit lorsque l'utilisateur clique sur le bouton "Provisionner" :
1. L'UI Affiche la Sélection de Produit
L'utilisateur sélectionne un produit dans le catalogue de produits. Le produit contient :
provisioning_play- Quel playbook Ansible exécuterinventory_items_list- Inventaire requis (par exemple,['SIM Card', 'Mobile Number'])provisioning_json_vars- Variables par défaut
2. Sélecteur d'Inventaire (Si Nécessaire)
Si inventory_items_list n'est pas vide, une fenêtre modale apparaît montrant des listes déroulantes pour chaque type d'inventaire. L'utilisateur doit sélectionner des éléments d'inventaire disponibles avant de continuer.
3. Bouton de Provisionnement Cliqué
JavaScript envoie la requête PUT /crm/provision/ :
PUT /crm/provision/
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"product_id": 42,
"customer_id": 123,
"SIM Card": 5001,
"Mobile Number": 5002
}
4. L'API Reçoit la Requête
Le point de terminaison de provisionnement (routes/provisioning.py) :
- Valide l'authentification (jeton Bearer, clé API ou liste blanche IP)
- Vérifie que l'utilisateur a la permission
CREATE_PROVISION - Extrait
initiating_userdu jeton - Charge la définition du produit depuis la base de données
- Récupère le chemin du playbook :
OmniCRM-API/Provisioners/plays/play_psim_only.yaml
5. Variables Fusionnées
Le système combine les variables de plusieurs sources :
# Depuis le produit
product_vars = json.loads(product['provisioning_json_vars'])
# Depuis le corps de la requête
request_vars = request.json
# Ajouté par le système
system_vars = {
'product_id': 42,
'customer_id': 123,
'access_token': g.access_token, # Voir la section d'authentification ci-dessous
'initiating_user': 7
}
# Final fusionné
extra_vars = {**product_vars, **request_vars, **system_vars}
6. Enregistrement de Provision Créé
Enregistrement de base de données créé avec statut 1 (En cours) :
provision = {
'provision_id': 456,
'customer_id': 123,
'product_id': 42,
'provisioning_play': 'play_psim_only',
'provisioning_json_vars': json.dumps(extra_vars),
'provisioning_status': 1, # En cours
'task_count': 85,
'initiating_user': 7,
'created': '2025-01-10T14:30:00Z'
}
7. Thread en Arrière-plan Lancé
run_playbook_in_background(
playbook='plays/play_psim_only.yaml',
extra_vars=extra_vars,
provision_id=456,
refresh_token=refresh_token # Pour le rafraîchissement du jeton pendant l'exécution
)
8. L'API Retourne Immédiatement
Réponse retournée à l'UI avec provision_id :
{
"provision_id": 456,
"provisioning_status": 1,
"message": "Travail de provisionnement créé"
}
9. L'UI Interroge pour des Mises à Jour
L'UI commence à interroger GET /crm/provision/provision_id/456 toutes les 3 secondes pour vérifier le statut. La réponse comprend :
{
"provision_id": 456,
"provisioning_status": 1,
"task_count": 12,
"provisioning_result_json": [
{
"event_number": 1,
"event_name": "Get Product information from CRM API",
"provisioning_status": 0,
"timestamp": "2024-01-15T10:30:05"
},
{
"event_number": 2,
"event_name": "Assign SIM Card from inventory",
"provisioning_status": 1,
"timestamp": "2024-01-15T10:30:07"
}
]
}
10. Ansible S'exécute en Arrière-plan
Le playbook exécute les tâches séquentiellement :
- Chaque tâche terminée crée un enregistrement
Provision_Eventdans la base de données - L'événement comprend : nom de la tâche, statut (0=succès, 2=échec, 3=échec mais ignoré), résultat JSON
- L'UI affiche la progression en temps réel montrant les tâches terminées et la tâche actuellement en cours d'exécution
- Les tâches échouées affichent des messages d'erreur dans les détails de l'événement
Suivi dans l'UI :
Pendant que le provisionnement est en cours (statut 1), les utilisateurs peuvent voir :
- Page de Détails du Service - Affiche le badge de statut de provisionnement (En cours/Succès/Échec)
- Journal d'Activité - Liste tous les événements de provisionnement avec des horodatages
- Vue des Détails de la Provision - Montre la progression tâche par tâche avec déploiement/repli pour les détails
Exemple d'affichage :
Statut de Provisionnement : En cours (8 sur 12 tâches complétées)
✓ Get Product information from CRM API ✓ Fetch Customer details ✓ Assign SIM Card from inventory (ICCID: 8991101200003204510) ✓ Assign Mobile Number (555-0123) ⟳ Create account in OCS/CGRateS (en cours...) ⏺ Configure network policies ⏺ Create service record ...
11. Le Provisionnement se Termine
Statut final défini :
provisioning_status: 0- Succèsprovisioning_status: 2- Échec (erreur critique)
L'UI arrête l'interrogation et affiche le résultat :
- Succès : Coche verte, service marqué Actif, l'utilisateur peut voir les détails du service
- Échec : X rouge, message d'erreur affiché, option de réessayer ou de contacter le support
- Notification par e-mail : En cas d'échec, un e-mail est envoyé à
provisioning.failure_listdans la configuration
Authentification et Autorisation
Suivi des Utilisateurs
Chaque travail de provisionnement suit quel utilisateur l'a initié :
- Initié par l'utilisateur : Le champ
initiating_userest défini sur l'ID de l'utilisateur de son jeton JWT - Auth par clé API : Utilise le premier ID d'utilisateur admin
- Auth par liste blanche IP : Utilise le premier ID d'utilisateur admin
Vérifications de Permission
Le système vérifie les permissions avant de permettre le provisionnement :
- Le personnel a besoin de la permission
CREATE_PROVISION - Les clients ne peuvent provisionner que des services pour leur propre compte (
VIEW_OWN_PROVISIONpermission)
Comment Ansible S'authentifie avec l'API CRM
Les playbooks Ansible doivent effectuer des appels API authentifiés vers le CRM (pour récupérer les détails du produit, créer des services, mettre à jour l'inventaire, etc.). L'authentification est gérée via des jetons Bearer passés au playbook.
La source du access_token dépend de la méthode d'authentification utilisée pour appeler l'API de provisionnement :
Méthode 1 : Connexion Utilisateur (Jeton Bearer)
Lorsqu'un utilisateur se connecte via l'UI web :
- L'utilisateur s'authentifie :
POST /crm/auth/login - Reçoit un
access_tokenJWT (à court terme, 15-30 min) et unrefresh_token(à long terme) - Fait une demande de provisionnement avec le jeton Bearer dans l'en-tête
- L'API de provisionnement extrait le jeton de l'en-tête
Authorization: Bearer ... - Stocke dans
g.access_token(contexte de requête Flask) - Passe à Ansible comme variable
access_token
Code (permissions.py) :
# Extraire le jeton Bearer de l'en-tête
auth_header = request.headers.get('Authorization', '')
if auth_header.startswith('Bearer '):
bearer_token = auth_header[7:]
# Valider et décoder
decoded_token = jwt.decode(bearer_token, secret_key, algorithms=['HS256'])
# Stocker pour le provisionnement
g.access_token = bearer_token
Code (provisioning.py) :
if "access_token" in g:
json_data['access_token'] = g.access_token
run_playbook(playbook_path, extra_vars=json_data, provision_id=provision_id)
Méthode 2 : Clé API (En-tête X-API-KEY)
Pour les systèmes automatisés utilisant des clés API :
- Le système fait une demande :
PUT /crm/provision/avecX-API-KEY: votre-clé-api...en-tête - L'API de provisionnement valide la clé API contre
crm_config.yaml - Génère un nouveau jeton JWT à la volée pour le premier utilisateur admin
- Stocke dans
g.access_token - Passe à Ansible
Pourquoi Générer un Jeton ?
Les clés API sont des chaînes, pas des JWT. Les playbooks appellent des points de terminaison API s'attendant à une authentification JWT. Donc :
- Valider la clé API
- Si valide et a le rôle
admin, générer un JWT temporaire - Utiliser l'ID du premier utilisateur admin comme sujet du JWT
- Le jeton permet au playbook de faire des appels API authentifiés
Code (permissions.py) :
def handle_api_key_auth(f, api_key, *args, **kwargs):
if not secure_compare_api_key(api_key):
return {'message': 'Clé API invalide'}, 401
API_KEYS = yaml_config.get('api_keys', {})
if api_key in API_KEYS:
if 'admin' in API_KEYS[api_key].get('roles', []):
admin_user_id = retrieve_first_admin_user_id()
access_token = create_access_token(identity=str(admin_user_id))
g.access_token = access_token
Méthode 3 : Liste Blanche IP
Pour les systèmes internes de confiance sur des réseaux privés :
- Le système fait une demande depuis une IP sur liste blanche (par exemple, 192.168.1.100)
- L'API de provisionnement vérifie l'IP du client contre
ip_whitelistdanscrm_config.yaml - Si sur liste blanche, génère un nouveau jeton JWT pour le premier utilisateur admin
- Stocke dans
g.access_token - Passe à Ansible
Code (permissions.py) :
def handle_ip_auth(f, *args, **kwargs):
client_ip = get_real_client_ip()
if not is_ip_whitelisted(client_ip):
return {'message': 'Accès refusé'}, 403
admin_user_id = retrieve_first_admin_user_id()
access_token = create_access_token(identity=str(admin_user_id))
g.access_token = access_token
Utilisation du Jeton dans les Playbooks
Chaque appel API dans le playbook inclut le jeton :
- name: Get Product Details
uri:
url: "http://localhost:5000/crm/product/product_id/{{ product_id }}"
headers:
Authorization: "Bearer {{ access_token }}"
- name: Create Service Record
uri:
url: "http://localhost:5000/crm/service/"
method: PUT
headers:
Authorization: "Bearer {{ access_token }}"
body:
customer_id: "{{ customer_id }}"
service_name: "Mobile Service"
Expiration et Rafraîchissement du Jeton
Les playbooks de longue durée (5-10 minutes) peuvent dépasser la durée de vie du access_token (expiration de 15-30 min). Pour les provisions initiées par l'utilisateur, le système passe à la fois access_token et refresh_token :
refresh_token = request.cookies.get('refresh_token')
run_playbook(playbook_path, extra_vars, provision_id, refresh_token=refresh_token)
Si le access_token expire, le coureur de playbook peut :
- Détecter la réponse 401 Non autorisée
- Appeler
POST /crm/auth/refreshavecrefresh_token - Recevoir un nouveau
access_token - Réessayer la requête échouée
Pour l'authentification par clé API/liste blanche IP, les jetons g��nérés peuvent avoir une expiration plus longue (1-2 heures) car ce sont des systèmes automatisés de confiance.
Le Processus de Provisionnement
-
Création de Travail
Lorsqu'une demande de provisionnement est reçue, le système :
- Valide la demande et vérifie les permissions
- Charge le playbook Ansible spécifié dans la définition du produit
- Crée un enregistrement
Provisiondans la base de données avec statut 1 (En cours) - Extrait les variables de la définition du produit et du corps de la requête
- Capture les jetons d'authentification pour l'accès API
-
Gestion des Jetons
Les playbooks Ansible doivent s'authentifier avec l'API CRM pour récupérer des données et apporter des modifications. Le système de provisionnement gère cela de deux manières :
- Jeton Bearer (JWT) : Pour le provisionnement initié par l'utilisateur, le
refresh_tokende la demande est utilisé pour générer des jetons d'accès frais pendant l'exécution du playbook - Auth par Clé API/IP : Pour les systèmes automatisés, un
access_tokenest passé directement au playbook viag.access_token
- Jeton Bearer (JWT) : Pour le provisionnement initié par l'utilisateur, le
-
Exécution en Arrière-plan
Le playbook s'exécute dans un thread en arrière-plan en utilisant
playbook_runner_v2. Cela permet à l'API de retourner immédiatement pendant que le provisionnement continue de manière asynchrone.Pendant l'exécution :
- Chaque tâche terminée/échouée crée un enregistrement
Provision_Event - Le gestionnaire d'événements surveille les échecs critiques par rapport aux échecs ignorés
- Des mises à jour de statut en temps réel sont écrites dans la base de données
- L'UI peut interroger pour des mises à jour via
GET /crm/provision/provision_id/<id>
- Chaque tâche terminée/échouée crée un enregistrement
-
Exécution du Playbook
Le playbook Ansible effectue généralement ces opérations :
- Récupère les informations sur le produit depuis l'API
- Récupère les informations sur le client depuis l'API
- Assigne des éléments d'inventaire (cartes SIM, adresses IP, numéros de téléphone, etc.)
- Crée des comptes dans OCS/OCS
- Configure l'équipement réseau
- Crée l'enregistrement de service dans l'API CRM
- Ajoute des transactions de coût de configuration
- Envoie des e-mails/SMS de bienvenue aux clients
-
Gestion des Erreurs
Les playbooks Ansible utilisent des sections
blocketrescuepour le rollback :- Si une tâche critique échoue, la section de secours supprime le provisionnement partiel
- Les tâches avec
ignore_errors: truesont marquées comme statut 3 et ne font pas échouer le travail - Les erreurs fatales (erreurs de syntaxe YAML, échecs de connexion) créent un événement d'erreur spécial avec des informations de débogage
Exemple : Playbook de Provisionnement Standard
Voici un exemple depuis play_simple_service.yaml :
- name: Simple Provisioning Play
hosts: localhost
gather_facts: no
become: False
tasks:
- name: Main block
block:
- name: Get Product information from CRM API
uri:
url: "http://localhost:5000/crm/product/product_id/{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
validate_certs: no
register: api_response_product
- name: Set package facts
set_fact:
package_name: "{{ api_response_product.json.product_name }}"
setup_cost: "{{ api_response_product.json.retail_setup_cost }}"
monthly_cost: "{{ api_response_product.json.retail_cost }}"
- name: Generate Service UUID
set_fact:
service_uuid: "Service_{{ 99999999 | random | to_uuid }}"
- name: Create account in OCS
uri:
url: "http://{{ crm_config.ocs.OCS }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionPlanIds": [],
"ExtraOptions": { "AllowNegative": false, "Disabled": false }
}]
}
status_code: 200
register: response
- name: Add Service via API
uri:
url: "http://localhost:5000/crm/service/"
method: PUT
body_format: json
headers:
Authorization: "Bearer {{ access_token }}"
body:
{
"customer_id": "{{ customer_id }}",
"product_id": "{{ product_id }}",
"service_name": "Service: {{ service_uuid }}",
"service_uuid": "{{ service_uuid }}",
"service_status": "Active",
"retail_cost": "{{ monthly_cost | float }}"
}
status_code: 200
register: service_creation_response
- name: Add Setup Cost Transaction
uri:
url: "http://localhost:5000/crm/transaction/"
method: PUT
headers:
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": {{ customer_id | int }},
"service_id": {{ service_creation_response.json.service_id | int }},
"title": "{{ package_name }} - Setup Costs",
"retail_cost": "{{ setup_cost | float }}"
}
register: api_response_transaction
rescue:
- name: Remove account in OCS
uri:
url: "http://{{ crm_config.ocs.OCS }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.RemoveAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}"
}]
}
status_code: 200
- name: Fail the provision
assert:
that:
- false
Ce playbook démontre le flux typique :
- Récupérer les détails du produit depuis l'API CRM
- Générer un UUID de service unique
- Créer le compte de facturation dans OCS
- Créer l'enregistrement de service via l'API CRM
- Ajouter des transactions de coût de configuration
- Si quelque chose échoue, la section
rescuesupprime le compte OCS
Flux de Travail de Provisionnement Simple
Le provisionnement simple est conçu pour les systèmes automatisés qui ont besoin de déclencher le provisionnement sans interaction utilisateur. Le cas d'utilisation le plus courant est le déclenchement de provisionnement d'addon par OCS via des ActionPlans.
Points de Terminaison de Provisionnement Simple
OmniCRM fournit deux points de terminaison de provisionnement simple :
-
POST /crm/provision/simple_provision_addon/service_id/<id>/product_id/<id>Pour le provisionnement automatisé d'addon (par exemple, frais récurrents, recharges automatiques)
-
POST /crm/provision/simple_provision_addon_recharge/service_id/<id>/product_id/<id>Pour des opérations de recharge rapides qui nécessitent un retour immédiat
Authentification pour le Provisionnement Simple
Les points de terminaison de provisionnement simple utilisent la liste blanche IP ou les clés API pour l'authentification :
- L'IP source de la demande est vérifiée contre
ip_whitelistdanscrm_config.yaml - Ou une clé API de
api_keysdanscrm_config.yamlpeut être fournie - Un jeton d'accès est généré et passé au playbook via
g.access_token
Exemple : Callback d'ActionPlan OCS
OCS peut être configuré pour appeler le point de terminaison de provisionnement simple lors de l'exécution d'actions récurrentes :
{
"method": "ApierV1.SetActionPlan",
"params": [{
"Id": "ActionPlan_Service123_Monthly_Charge",
"ActionsId": "Action_Service123_Add_Monthly_Data",
"Timing": {
"Years": [],
"Months": [],
"MonthDays": [1],
"Time": "00:00:00Z"
},
"Weight": 10,
"ActionTriggers": [
{
"ThresholdType": "*min_event_counter",
"ThresholdValue": 1,
"ActionsID": "Action_Service123_HTTP_Callback"
}
]
}]
}
L'action effectue un POST HTTP à :
Cela déclenche le playbook associé (par exemple, play_topup_no_charge.yaml) qui ajoute des données/crédits au service.
Exemple : Playbook de Recharge Simple
Depuis play_topup_monetary.yaml :
- name: Mobile Topup Monetary - 2024
hosts: localhost
gather_facts: no
become: False
tasks:
- name: Get Product information from CRM API
uri:
url: "http://localhost:5000/crm/product/product_id/{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_product
- name: Get Service information from CRM API
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_service
- name: Set service facts
set_fact:
service_uuid: "{{ api_response_service.json.service_uuid }}"
customer_id: "{{ api_response_service.json.customer_id }}"
package_name: "{{ api_response_product.json.product_name }}"
monthly_cost: "{{ api_response_product.json.retail_cost }}"
- name: Get Customer Payment Method
uri:
url: "http://localhost:5000/crm/stripe/customer_id/{{ customer_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_stripe
- name: Charge customer
uri:
url: "http://localhost:5000/crm/stripe/charge_card/{{ customer_stripe_id }}"
method: POST
headers:
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"retail_cost": "{{ monthly_cost }}",
"description": "{{ package_name }} topup",
"customer_id": "{{ customer_id | int }}",
"service_id": "{{ service_id | int }}"
}
register: api_response_stripe
- name: Add monetary balance to OCS
uri:
url: "http://{{ crm_config.ocs.OCS }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"BalanceType": "*monetary",
"Balance": {
"Value": "{{ monthly_cost | float * 100 }}",
"ExpiryTime": "+4320h"
}
}]
}
status_code: 200
- name: Add Transaction to CRM
uri:
url: "http://localhost:5000/crm/transaction/"
method: PUT
headers:
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": {{ customer_id | int }},
"service_id": {{ service_id | int }},
"title": "{{ package_name }}",
"retail_cost": "{{ monthly_cost | float }}"
}
- name: Send Notification SMS
uri:
url: "http://sms-gateway/SMS/plaintext/{{ api_key }}"
method: POST
body_format: json
body:
{
"source_msisdn": "YourCompany",
"destinatination_msisdn": "{{ customer_phone }}",
"message_body": "Merci pour la recharge de {{ monthly_cost }}!"
}
status_code: 201
ignore_errors: True
Ce playbook :
- Récupère les détails du service et du produit depuis l'API
- Récupère la méthode de paiement du client
- Charge le client via l'API Stripe
- Ajoute un solde monétaire à OCS
- Enregistre la transaction dans le CRM
- Envoie un SMS de confirmation (avec
ignore_errors: Truepour que les échecs ne fassent pas échouer le travail)
Chaînes de Provisionnement
Pour des produits complexes nécessitant plusieurs étapes de provisionnement, OmniCRM prend en charge les chaînes de provisionnement. Une chaîne exécute plusieurs playbooks séquentiellement, passant le contexte entre eux.
Exemple d'utilisation : Un service groupé qui provisionne :
- Service Internet de base (crée l'enregistrement de service principal)
- Addon IPTV (utilise le service_id de l'étape 1)
- Addon IP statique (utilise le service_id de l'étape 1)
Le service de provisionnement :
- Interroge automatiquement la base de données pour le
service_idcréé par le premier playbook - L'injecte dans les
extra_varspour les playbooks suivants - Suit chaque playbook comme un enregistrement
Provisionséparé
Raisons d'Échec et Débogage
Lorsque le provisionnement échoue, le système capture des informations détaillées pour aider à diagnostiquer le problème.
Scénarios d'Échec Courants
Échecs de Tâches Critiques (Statut 2)
Ceux-ci provoquent l'échec de l'ensemble du travail de provisionnement :
- Appels API retournant des codes de statut inattendus
- Échecs d'assertions (par exemple,
assert: that: response.status == 200) - Éléments d'inventaire requis manquants
- Équipements réseau inaccessibles
- Identifiants invalides ou jetons expirés
- Erreurs OCS/OCS
Échecs Ignorés (Statut 3)
Ceux-ci sont enregistrés mais ne font pas échouer le travail :
- Notifications SMS/e-mail optionnelles échouant
- Recherches de données non critiques (marquées avec
ignore_errors: True) - Opérations de nettoyage lors du déprovisionnement
Erreurs Fatales
Celles-ci empêchent le playbook de s'exécuter :
- Erreurs de syntaxe YAML dans le playbook
- Variables non définies dans le playbook
- Fichiers de playbook manquants
- Échecs de connexion au contrôleur Ansible
Lorsqu'une erreur fatale se produit, le système crée un événement d'erreur spécial contenant :
- Le code de sortie Ansible
- L'intégralité de stdout (contient des détails sur les erreurs de syntaxe)
- L'intégralité de stderr (contient des erreurs d'exécution)
- Une liste des causes courantes pour ce type d'échec
- Toutes les variables passées au playbook
E-mails de Notification d'Erreur
Lorsque le provisionnement échoue (statut 2), un e-mail est automatiquement envoyé à la liste de notification d'échec configurée (provisioning.failure_list dans crm_config.yaml).
L'e-mail comprend :
- Informations sur le client
- Détails sur le produit/service
- Résultats des tâches codés par couleur :
- Vert : Tâches réussies
- Orange : Tâches échouées mais ignorées
- Rouge : Échecs critiques
- Pour les échecs critiques : Sortie de débogage complète incluant les corps de requête/réponse
- Pour les erreurs fatales : Sortie Ansible, messages d'erreur et causes courantes
Suivi des Travaux de Provisionnement
API de Statut de Provisionnement
Pour vérifier le statut d'un travail de provisionnement :
GET /crm/provision/provision_id/<id>
Authorization: Bearer <token>
La réponse comprend :
{
"provision_id": 123,
"customer_id": 456,
"customer_name": "John Smith",
"product_id": 10,
"provisioning_status": 0,
"provisioning_play": "play_psim_only",
"playbook_description": "OmniCore Service Provisioning 2024",
"task_count": 85,
"provisioning_result_json": [
{
"event_number": 1,
"event_name": "Get Product information from CRM API",
"provisioning_status": 1,
"provisioning_result_json": "{...}"
},
{
"event_number": 2,
"event_name": "Create account in OCS",
"provisioning_status": 1,
"provisioning_result_json": "{...}"
}
]
}
Liste des Travaux de Provisionnement
Pour obtenir une liste paginée de tous les travaux de provisionnement :
GET /crm/provision/?page=1&per_page=20&sort=provision_id&order=desc
Authorization: Bearer <token>
Prend en charge le filtrage :
GET /crm/provision/?filters={"provisioning_status":[2]}&search=Mobile
Authorization: Bearer <token>
Cela ne retourne que les travaux échoués (statut 2) où la description contient "Mobile".
Meilleures Pratiques
Conception de Playbook
- Utilisez toujours block/rescue : Assurez-vous que le provisionnement partiel peut être annulé
- Utilisez ignore_errors avec parcimonie : Seulement pour des opérations vraiment optionnelles
- Journalisez les variables importantes : Utilisez des tâches
debugpour enregistrer des valeurs clés pour le dépannage - Validez les réponses : Utilisez
assertpour vérifier que les réponses API sont conformes aux attentes - Idempotence : Concevez des playbooks pour être sûrs d'être réexécutables
Authentification
- Provisionnement initié par l'utilisateur : Utilisez toujours refresh_token pour les playbooks de longue durée
- Provisionnement automatisé : Utilisez la liste blanche IP ou les clés API avec des jetons d'accès générés
- Expiration du jeton : Le refresh_token garantit que les jetons d'accès sont régénérés au besoin
Gestion des Erreurs
- Fournir un contexte : Inclure customer_id, service_id et détails de l'opération dans les messages d'erreur
- Notifier de manière appropriée : Les échecs critiques déclenchent des e-mails, mais ne spammez pas pour des échecs attendus
- Infos de débogage : Capturez les corps de requête/réponse complets dans les enregistrements Provision_Event
Sécurité
- Validez les entrées : Vérifiez customer_id, product_id, service_id avant le provisionnement
- Vérifications de permission : Vérifiez que les utilisateurs ne peuvent provisionner que pour des clients autorisés
- Données sensibles : Utilisez le système de redaction pour supprimer les mots de passe/clés des journaux
- Liste blanche IP : Restreindre les points de terminaison simple_provision aux systèmes de confiance uniquement
Performance
- Exécution en arrière-plan : Ne bloquez jamais les réponses API en attendant le provisionnement
- Intervalles de sondage : L'UI doit interroger pour des mises à jour de statut toutes les 2-5 secondes
- Tâches parallèles : Utilisez le parallélisme natif d'Ansible pour des opérations indépendantes
- Mises à jour de la base de données : Le gestionnaire d'événements met à jour la base de données en temps réel, pas besoin de requêter pendant l'exécution
Documentation Connexe
concepts_ansible- Concepts généraux de provisionnement Ansibleconcepts_api- Authentification et utilisation de l'API CRMconcepts_products_and_services- Définitions de produits et de servicesadministration_inventory- Gestion de l'inventaire pour le provisionnement