Sistema de Aprovisionamiento
OmniCRM utiliza Ansible para automatizar el aprovisionamiento, configuración y desaprovisionamiento de servicios al cliente. El sistema de aprovisionamiento está diseñado para ser flexible, permitiendo flujos de trabajo complejos mientras mantiene la consistencia y la fiabilidad.

::: note ::: title Nota :::
Para una guía completa del viaje de producto a servicio con ejemplos detallados de playbooks de Ansible, estrategias de precios y escenarios del mundo real, consulte Guía Completa del Ciclo de Vida del Producto. :::
Descripción General
Cuando se ordena un producto o se necesita configurar un servicio, OmniCRM crea un Trabajo de Aprovisionamiento que ejecuta uno o más playbooks de Ansible. Estos playbooks interactúan con varios sistemas backend (OCS/CGRateS, equipos de red, APIs, etc.) para aprovisionar completamente el servicio.
El sistema de aprovisionamiento admite dos flujos de trabajo principales:
- Aprovisionamiento Estándar - Activado por el personal o los clientes a través de la UI/API
- Aprovisionamiento Simple - Activado por sistemas externos como OCS para operaciones automatizadas
Valores de Estado de Aprovisionamiento
Los trabajos de aprovisionamiento y las tareas individuales pueden tener los siguientes estados:
- Estado 0 (Éxito) - El trabajo de aprovisionamiento se completó con éxito
- Estado 1 (En Ejecución) - El trabajo de aprovisionamiento o la tarea se están ejecutando actualmente
- Estado 2 (Fallido - Crítico) - Ocurrió una falla crítica que causó que el aprovisionamiento fallara
- Estado 3 (Fallido - Ignorado) - Una tarea falló pero tenía
ignore_errors: true, por lo que el aprovisionamiento continuó
Cuando un trabajo de aprovisionamiento falla, OmniCRM envía notificaciones por correo electrónico a la lista de notificación de fallos configurada con información detallada sobre el error.
Cómo los Productos Impulsan el Aprovisionamiento
La definición de Producto es el plano de lo que se aprovisiona y cómo. Cuando un usuario selecciona un producto para aprovisionar, el sistema lee varios campos clave de la definición del producto para determinar qué hacer.
Campos del Producto Utilizados en el Aprovisionamiento
Una definición de producto contiene:
provisioning_play- El nombre del playbook de Ansible a ejecutar (sin la extensión .yaml)provisioning_json_vars- Cadena JSON que contiene variables predeterminadas para pasar a Ansibleinventory_items_list- Lista de tipos de inventario que deben ser asignados (por ejemplo,['SIM Card', 'Mobile Number'])product_id,product_name, campos de precios - Pasados automáticamente al playbook
Ejemplo de Definición de Producto
{
"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
}
De Producto a Trabajo de Aprovisionamiento
Cuando se inicia el aprovisionamiento, el sistema:
-
Carga el playbook especificado en
provisioning_playEl sistema busca
OmniCRM-API/Provisioners/plays/play_psim_only.yaml -
Fusiona variables de múltiples fuentes en
extra_vars:a. De provisioning_json_vars:
{"iccid": "", "msisdn": ""}b. Del cuerpo de la solicitud: Cualquier variable adicional que el usuario/API proporcione c. De los campos del producto:product_id,customer_id, etc. d. De la autenticación:access_tokeno configuración pararefresh_token -
Asigna inventario basado en
inventory_items_listAntes de ejecutar el playbook, la UI/API solicita la selección de inventario:
- SIM Card - El usuario selecciona un SIM disponible del inventario
- Mobile Number - El usuario selecciona un número de teléfono disponible
Los IDs de inventario seleccionados se agregan a
extra_varscon el tipo de inventario como clave:extra_vars = {
"product_id": 1,
"customer_id": 456,
"SIM Card": 789, # inventory_id del SIM seleccionado
"Mobile Number": 101, # inventory_id del número de teléfono seleccionado
"iccid": "", # De provisioning_json_vars
"msisdn": "", # De provisioning_json_vars
"access_token": "eyJ..."
} -
Pasa todo a Ansible a través de
hostvars[inventory_hostname]Dentro del playbook, las variables son accesibles como:
- 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]"
Cómo los Playbooks Utilizan Variables de Inventario
Una vez que el playbook tiene los IDs de inventario, recupera los detalles completos del inventario desde la 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 }}"
El playbook puede entonces usar estos valores para:
- Aprovisionar la tarjeta SIM en el HSS con el IMSI
- Configurar el número de teléfono en el sistema de facturación
- Asignar los elementos de inventario al cliente
- Crear el registro de servicio con estos detalles
Ejemplo del Mundo Real: Aprovisionamiento de SIM Móvil
Desde play_psim_only.yaml, así es como utiliza los datos del producto y del inventario:
- 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
Esto demuestra el flujo completo:
- La definición del producto especifica
provisioning_play: "play_psim_only" - El producto requiere
inventory_items_list: ['SIM Card', 'Mobile Number'] - El usuario selecciona elementos de inventario durante el aprovisionamiento
- Los IDs de inventario se pasan al playbook como
extra_vars - El playbook recupera los detalles completos del inventario desde la API
- El playbook utiliza los datos del inventario para configurar el equipo de red
- El playbook marca el inventario como asignado al cliente
Reversión y Limpieza: Patrón de Mejores Prácticas
Mejor Práctica Crítica: El mismo playbook debe manejar tanto la reversión de aprovisionamiento fallido como el desaprovisionamiento intencional utilizando la estructura block y rescue de Ansible.
Estructura del Playbook
Desde play_psim_only.yaml:
- name: OmniCore Service Provisioning 2024
hosts: localhost
gather_facts: no
become: False
tasks:
- name: Main block
block:
# --- TAREAS DE APROVISIONAMIENTO ---
- name: Get Product information
uri: ...
- name: Create account in OCS
uri: ...
- name: Provision subscriber on HSS
uri: ...
- name: Create service record
uri: ...
# ... muchas más tareas de aprovisionamiento ...
rescue:
# --- TAREAS DE LIMPIEZA ---
# Esta sección se ejecuta cuando:
# 1. Cualquier tarea en el bloque falla (reversión)
# 2. action == "deprovision" (limpieza intencional)
- 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:] }}", # Número ficticio
"ue_ambr_dl": 9999999, # Inusualmente alto
"ue_ambr_ul": 9999999
}
loop: "{{ hss_subscriber_data | dict2items }}"
when:
- deprovision_subscriber | default(false) | bool == false
# La afirmación final determina el éxito o el fracaso
- name: Set status to "Success" if Manual deprovision / Fail if failed provision
assert:
that:
- action == "deprovision"
Por Qué Este Patrón es una Mejor Práctica
1. No Duplicación de Código
Las mismas tareas de limpieza manejan ambos escenarios:
- Aprovisionamiento Fallido (Reversión): Si alguna tarea en el
blockfalla, la secciónrescuese ejecuta automáticamente - Desaprovisionamiento Intencional: Cuando se llama con
action: "deprovision", el playbook salta inmediatamente arescue
2. Limpieza Completa Garantizada
Cuando un aprovisionamiento falla a mitad de camino, la sección de rescate asegura:
- Se eliminan todas las cuentas de OCS creadas
- Se eliminan todas las entradas de equipos de red configurados
- Se devuelve el inventario asignado al grupo
- Se eliminan o se establecen como inactivos los suscriptores de HSS
- No queda aprovisionamiento parcial en ningún sistema
Esto previene recursos "huérfanos" que:
- Consumen inventario sin ser rastreados
- Crean cuentas de facturación que no están vinculadas a servicios
- Causan confusión durante la solución de problemas
- Desperdician recursos de red
3. Manejo de Fallos con Graceful Failure usando ignore_errors
Observe que cada tarea de limpieza utiliza ignore_errors: True. Esto es intencional porque:
- Durante la reversión, algunos recursos pueden no haberse creado aún
- Queremos intentar todas las tareas de limpieza incluso si algunas fallan
- La afirmación final determina el éxito/fallo general
Por ejemplo, si el aprovisionamiento falla en "Crear cuenta en OCS", la limpieza intentará:
- Eliminar la cuenta de OCS (fallará, pero se ignorará)
- Eliminar perfiles de atributos (fallará, pero se ignorará)
- Devolver inventario (tendrá éxito)
- Eliminar suscriptor de HSS (puede que no exista, se ignorará)
4. Distinguir Desaprovisionamiento de Reversión
La afirmación final al final de rescue es ingeniosa:
- name: Set status to "Success" if Manual deprovision / Fail if failed provision
assert:
that:
- action == "deprovision"
Esto significa:
- Si
action == "deprovision": La afirmación pasa, el playbook tiene éxito (estado 0) - Si
actionno está configurado o != "deprovision": La afirmación falla, el playbook falla (estado 2)
Así que el mismo código de limpieza resulta en diferentes estados de trabajo de aprovisionamiento dependiendo de la intención.
5. Limpieza Condicional Basada en el Tipo de Servicio
Algunas tareas de limpieza utilizan condicionales para manejar diferentes escenarios:
- 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
Esto permite una limpieza flexible:
- Eliminación completa: Cuando las SIM están dedicadas a clientes (
deprovision_subscriber: true) - Estado inactivo: Cuando las SIM son reutilizables y deben permanecer en HSS (
deprovision_subscriber: false)
Cómo Usar Este Patrón
Para Aprovisionamiento:
{
"product_id": 1,
"customer_id": 456,
"provisioning_play": "play_psim_only"
}
Si el aprovisionamiento falla, la reversión automática ocurre a través de rescue.
Para Desaprovisionamiento:
{
"service_id": 123,
"service_uuid": "Service_abc123",
"action": "deprovision",
"provisioning_play": "play_psim_only"
}
El playbook salta directamente a la sección rescue, ejecuta toda la limpieza y tiene éxito.
Resumen de Beneficios
✅ Única fuente de verdad: Un playbook maneja aprovisionamiento y desaprovisionamiento ✅ Operaciones atómicas: O completamente aprovisionado o completamente limpiado ✅ Sin recursos huérfanos: Aprovisionamientos fallidos no dejan rastro ✅ Mantenimiento más fácil: Cambios en la lógica de aprovisionamiento se aplican automáticamente a la limpieza ✅ Errores reducidos: Sin posibilidad de que el código de aprovisionamiento y desaprovisionamiento se desincronice ✅ Testeable: Se puede probar la lógica de desaprovisionamiento ejecutando con action: "deprovision"
Este patrón debe seguirse en todos los playbooks de aprovisionamiento para asegurar fiabilidad y consistencia.
Sobrescribiendo Variables del Producto
Las provisioning_json_vars pueden ser sobrescritas en el momento del aprovisionamiento. Por ejemplo, un producto podría definir:
{
"provisioning_json_vars": "{\"monthly_cost\": 50, \"data_limit_gb\": 100}"
}
Pero al aprovisionar, puede sobrescribir estas:
{
"product_id": 1,
"customer_id": 456,
"monthly_cost": 45,
"data_limit_gb": 150
}
Las extra_vars fusionadas utilizarán los valores sobrescritos. Esto permite:
- Precios personalizados para clientes específicos
- Límites de datos diferentes basados en promociones
- Pruebas con diferentes parámetros sin modificar el producto
Productos Sin Inventario
No todos los productos requieren inventario. Por ejemplo, un complemento de datos o un interruptor de función podrían tener:
{
"product_id": 10,
"product_name": "Extra 10GB Data",
"provisioning_play": "play_local_data_addon",
"provisioning_json_vars": "{\"data_gb\": 10}",
"inventory_items_list": "[]"
}
En este caso, el playbook recibe:
extra_vars = {
"product_id": 10,
"customer_id": 456,
"service_id": 123, # Servicio al que agregar datos
"data_gb": 10,
"access_token": "eyJ..."
}
El playbook simplemente agrega los datos al servicio existente sin necesidad de elementos de inventario.
Flujo de Trabajo de Aprovisionamiento Estándar
El aprovisionamiento estándar se inicia cuando:
- Un miembro del personal agrega un servicio a un cliente desde la UI
- Un cliente ordena un servicio a través del portal de autoservicio
- La API se llama directamente con
PUT /crm/provision/
Cuando Haces Clic en "Aprovisionar"
Aquí está el flujo completo que ocurre cuando un usuario hace clic en el botón "Aprovisionar":
1. La UI Muestra la Selección de Productos
El usuario selecciona un producto del catálogo de productos. El producto contiene:
provisioning_play- Qué playbook de Ansible ejecutarinventory_items_list- Inventario requerido (por ejemplo,['SIM Card', 'Mobile Number'])provisioning_json_vars- Variables predeterminadas
2. Selector de Inventario (Si es Necesario)
Si inventory_items_list no está vacío, aparece un modal mostrando listas desplegables para cada tipo de inventario. El usuario debe seleccionar elementos de inventario disponibles antes de continuar.
3. Botón de Aprovisionamiento Clicado
JavaScript envía la solicitud 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. La API Recibe la Solicitud
El endpoint de aprovisionamiento (routes/provisioning.py):
- Valida la autenticación (token Bearer, clave API o lista blanca de IP)
- Verifica que el usuario tenga permiso
CREATE_PROVISION - Extrae
initiating_userdel token - Carga la definición del producto desde la base de datos
- Recupera la ruta del playbook:
OmniCRM-API/Provisioners/plays/play_psim_only.yaml
5. Variables Fusionadas
El sistema combina variables de múltiples fuentes:
# Del producto
product_vars = json.loads(product['provisioning_json_vars'])
# Del cuerpo de la solicitud
request_vars = request.json
# Agregadas por el sistema
system_vars = {
'product_id': 42,
'customer_id': 123,
'access_token': g.access_token, # Ver sección de autenticación a continuación
'initiating_user': 7
}
# Final fusionado
extra_vars = {**product_vars, **request_vars, **system_vars}
6. Registro de Aprovisionamiento Creado
Registro en la base de datos creado con estado 1 (En Ejecución):
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 Ejecución
'task_count': 85,
'initiating_user': 7,
'created': '2025-01-10T14:30:00Z'
}
7. Hilo en Segundo Plano Generado
run_playbook_in_background(
playbook='plays/play_psim_only.yaml',
extra_vars=extra_vars,
provision_id=456,
refresh_token=refresh_token # Para la actualización del token durante la ejecución
)
8. La API Devuelve Inmediatamente
Respuesta devuelta a la UI con provision_id:
{
"provision_id": 456,
"provisioning_status": 1,
"message": "Provisioning job created"
}
9. La UI Realiza Polling para Actualizaciones
La UI comienza a realizar polling a GET /crm/provision/provision_id/456 cada 3 segundos para verificar el estado. La respuesta incluye:
{
"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 Ejecuta en Segundo Plano
El playbook ejecuta tareas secuencialmente:
- Cada finalización de tarea crea un registro
Provision_Eventen la base de datos - El evento incluye: nombre de la tarea, estado (0=éxito, 2=falla, 3=falla pero ignorada), resultado JSON
- La UI muestra el progreso en tiempo real mostrando tareas completadas y la tarea actualmente en ejecución
- Las tareas fallidas muestran mensajes de error en los detalles del evento
Seguimiento en la UI:
Mientras el aprovisionamiento está en ejecución (estado 1), los usuarios pueden ver:
- Página de Detalles del Servicio - Muestra la insignia de estado de aprovisionamiento (En Ejecución/Éxito/Fallo)
- Registro de Actividades - Enumera todos los eventos de aprovisionamiento con marcas de tiempo
- Vista de Detalles de Aprovisionamiento - Muestra el progreso tarea por tarea con expandir/colapsar para detalles
Ejemplo de visualización:
Estado de Aprovisionamiento: En Ejecución (8 de 12 tareas completadas)
✓ Obtener información del producto de la API de CRM ✓ Obtener detalles del cliente ✓ Asignar tarjeta SIM del inventario (ICCID: 8991101200003204510) ✓ Asignar número de teléfono (555-0123) ⟳ Crear cuenta en OCS/CGRateS (en progreso...) ⏺ Configurar políticas de red ⏺ Crear registro de servicio ...
11. El Aprovisionamiento Se Completa
Estado final establecido:
provisioning_status: 0- Éxitoprovisioning_status: 2- Fallido (error crítico)
La UI deja de hacer polling y muestra el resultado:
- Éxito: Marca de verificación verde, servicio marcado como Activo, el usuario puede ver los detalles del servicio
- Fallo: X roja, mensaje de error mostrado, opción para reintentar o contactar soporte
- Notificación por correo electrónico: Si hay un fallo, se envía un correo electrónico a
provisioning.failure_listen la configuración
Autenticación y Autorización
Seguimiento de Usuarios
Cada trabajo de aprovisionamiento rastrea qué usuario lo inició:
- Iniciado por el usuario: El campo
initiating_userse establece en la ID del usuario de su token JWT - Autenticación por clave API: Utiliza la ID del primer usuario administrador
- Autenticación por lista blanca de IP: Utiliza la ID del primer usuario administrador
Verificaciones de Permisos
El sistema verifica los permisos antes de permitir el aprovisionamiento:
- El personal necesita el permiso
CREATE_PROVISION - Los clientes solo pueden aprovisionar servicios para su propia cuenta (
VIEW_OWN_PROVISIONpermiso)
Cómo Ansible se Autentica con la API de CRM
Los playbooks de Ansible necesitan hacer llamadas API autenticadas de vuelta al CRM (para obtener detalles del producto, crear servicios, actualizar inventario, etc.). La autenticación se maneja a través de tokens Bearer pasados al playbook.
La fuente del access_token depende del método de autenticación utilizado para llamar a la API de aprovisionamiento:
Método 1: Inicio de Sesión del Usuario (Token Bearer)
Cuando un usuario inicia sesión a través de la UI web:
- El usuario se autentica:
POST /crm/auth/login - Recibe un token JWT
access_token(de corta duración, 15-30 min) yrefresh_token(de larga duración) - Realiza la solicitud de aprovisionamiento con el token Bearer en el encabezado
- La API de aprovisionamiento extrae el token del encabezado
Authorization: Bearer ... - Se almacena en
g.access_token(contexto de solicitud de Flask) - Se pasa a Ansible como variable
access_token
Código (permissions.py):
# Extraer el token Bearer del encabezado
auth_header = request.headers.get('Authorization', '')
if auth_header.startswith('Bearer '):
bearer_token = auth_header[7:]
# Validar y decodificar
decoded_token = jwt.decode(bearer_token, secret_key, algorithms=['HS256'])
# Almacenar para aprovisionamiento
g.access_token = bearer_token
Código (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étodo 2: Clave API (Encabezado X-API-KEY)
Para sistemas automatizados que utilizan claves API:
- El sistema realiza la solicitud:
PUT /crm/provision/con el encabezadoX-API-KEY: your-api-key... - La API de aprovisionamiento valida la clave API contra
crm_config.yaml - Genera un nuevo token JWT sobre la marcha para el primer usuario administrador
- Se almacena en
g.access_token - Se pasa a Ansible
¿Por Qué Generar un Token?
Las claves API son cadenas, no JWT. Los playbooks llaman a los endpoints de API esperando autenticación JWT. Así que:
- Valida la clave API
- Si es válida y tiene el rol
admin, genera un JWT temporal - Usa la ID del primer usuario administrador como sujeto del JWT
- El token permite que el playbook realice llamadas API autenticadas
Código (permissions.py):
def handle_api_key_auth(f, api_key, *args, **kwargs):
if not secure_compare_api_key(api_key):
return {'message': 'Invalid API key'}, 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étodo 3: Lista Blanca de IP
Para sistemas internos de confianza en redes privadas:
- El sistema realiza la solicitud desde una IP en la lista blanca (por ejemplo, 192.168.1.100)
- La API de aprovisionamiento verifica la IP del cliente contra
ip_whitelistencrm_config.yaml - Si está en la lista blanca, genera un nuevo token JWT para el primer usuario administrador
- Se almacena en
g.access_token - Se pasa a Ansible
Código (permissions.py):
def handle_ip_auth(f, *args, **kwargs):
client_ip = get_real_client_ip()
if not is_ip_whitelisted(client_ip):
return {'message': 'Access denied'}, 403
admin_user_id = retrieve_first_admin_user_id()
access_token = create_access_token(identity=str(admin_user_id))
g.access_token = access_token
Usando el Token en los Playbooks
Cada llamada API en el playbook incluye el token:
- 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"
Expiración y Actualización del Token
Los playbooks de larga duración (5-10 minutos) pueden superar el access_token (expiración de 15-30 min). Para aprovisionamientos iniciados por el usuario, el sistema pasa tanto access_token como refresh_token:
refresh_token = request.cookies.get('refresh_token')
run_playbook(playbook_path, extra_vars, provision_id, refresh_token=refresh_token)
Si access_token expira, el ejecutor del playbook puede:
- Detectar respuesta 401 No Autorizado
- Llamar a
POST /crm/auth/refreshconrefresh_token - Recibir un nuevo
access_token - Reintentar la solicitud fallida
Para autenticación por clave API/lista blanca de IP, los tokens generados pueden tener una expiración más larga (1-2 horas) ya que estos son sistemas automatizados de confianza.
El Proceso de Aprovisionamiento
-
Creación del Trabajo
Cuando se recibe una solicitud de aprovisionamiento, el sistema:
- Valida la solicitud y verifica permisos
- Carga el playbook de Ansible especificado en la definición del producto
- Crea un registro de
Provisionen la base de datos con estado 1 (En Ejecución) - Extrae variables de la definición del producto y del cuerpo de la solicitud
- Captura tokens de autenticación para el acceso a la API
-
Manejo de Tokens
Los playbooks de Ansible necesitan autenticarse con la API de CRM para recuperar datos y hacer cambios. El sistema de aprovisionamiento maneja esto de dos maneras:
- Token Bearer (JWT): Para aprovisionamientos iniciados por el usuario, el
refresh_tokende la solicitud se utiliza para generar tokens de acceso frescos durante la ejecución del playbook - Autenticación por Clave API/IP: Para sistemas automatizados, un
access_tokense pasa directamente al playbook a través deg.access_token
- Token Bearer (JWT): Para aprovisionamientos iniciados por el usuario, el
-
Ejecución en Segundo Plano
El playbook se ejecuta en un hilo en segundo plano utilizando
playbook_runner_v2. Esto permite que la API devuelva inmediatamente mientras el aprovisionamiento continúa de manera asíncrona.Durante la ejecución:
- Cada finalización/fallo de tarea crea un registro
Provision_Event - El manejador de eventos monitorea fallos críticos vs. ignorados
- Actualizaciones de estado en tiempo real se escriben en la base de datos
- La UI puede realizar polling para actualizaciones a través de
GET /crm/provision/provision_id/<id>
- Cada finalización/fallo de tarea crea un registro
-
Ejecución del Playbook
El playbook de Ansible típicamente realiza estas operaciones:
- Recupera información del producto desde la API
- Recupera información del cliente desde la API
- Asigna elementos de inventario (tarjetas SIM, direcciones IP, números de teléfono, etc.)
- Crea cuentas en OCS/OCS
- Configura equipos de red
- Crea el registro de servicio en la API de CRM
- Agrega transacciones de costo de configuración
- Envía correos electrónicos/SMS de bienvenida a los clientes
-
Manejo de Errores
Los playbooks de Ansible utilizan secciones
blockyrescuepara la reversión:- Si una tarea crítica falla, la sección de rescate elimina el aprovisionamiento parcial
- Las tareas con
ignore_errors: truese marcan como estado 3 y no fallan el trabajo - Errores fatales (errores de sintaxis YAML, fallos de conexión) crean un evento de error especial con información de depuración
Ejemplo: Playbook de Aprovisionamiento Estándar
Aquí hay un ejemplo de 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
Este playbook demuestra el flujo típico:
- Recuperar detalles del producto desde la API de CRM
- Generar un UUID de servicio único
- Crear la cuenta de facturación en OCS
- Crear el registro de servicio a través de la API de CRM
- Agregar transacciones de costo de configuración
- Si algo falla, la sección
rescueelimina la cuenta de OCS
Flujo de Trabajo de Aprovisionamiento Simple
El aprovisionamiento simple está diseñado para sistemas automatizados que necesitan activar el aprovisionamiento sin interacción del usuario. El caso de uso más común es el aprovisionamiento de complementos activado por OCS a través de ActionPlans.
Endpoints de Aprovisionamiento Simple
OmniCRM proporciona dos endpoints de aprovisionamiento simple:
-
POST /crm/provision/simple_provision_addon/service_id/<id>/product_id/<id>Para aprovisionamiento automatizado de complementos (por ejemplo, cargos recurrentes, recargas automáticas)
-
POST /crm/provision/simple_provision_addon_recharge/service_id/<id>/product_id/<id>Para operaciones de recarga rápida que necesitan retroalimentación inmediata
Autenticación para Aprovisionamiento Simple
Los endpoints de aprovisionamiento simple utilizan listas blancas de IP o claves API para la autenticación:
- La IP de origen de la solicitud se verifica contra
ip_whitelistencrm_config.yaml - O se puede proporcionar una clave API de
api_keysencrm_config.yaml - Se genera un token de acceso y se pasa al playbook a través de
g.access_token
Ejemplo: Callback de ActionPlan de OCS
OCS puede configurarse para llamar al endpoint de aprovisionamiento simple al ejecutar acciones recurrentes:
{
"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"
}
]
}]
}
La acción realiza un POST HTTP a:
Esto activa el playbook asociado (por ejemplo, play_topup_no_charge.yaml) que agrega datos/créditos al servicio.
Ejemplo: Playbook de Recarga Simple
Desde 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": "Thanks for topping up {{ monthly_cost }}!"
}
status_code: 201
ignore_errors: True
Este playbook:
- Obtiene detalles del servicio y del producto desde la API
- Recupera el método de pago del cliente
- Carga al cliente a través de la API de Stripe
- Agrega saldo monetario a OCS
- Registra la transacción en el CRM
- Envía un SMS de confirmación (con
ignore_errors: Truepara que los fallos no fallen el trabajo)
Cadenas de Aprovisionamiento
Para productos complejos que requieren múltiples pasos de aprovisionamiento, OmniCRM admite cadenas de aprovisionamiento. Una cadena ejecuta múltiples playbooks secuencialmente, pasando contexto entre ellos.
Ejemplo de caso de uso: Un servicio agrupado que aprovisiona:
- Servicio de internet base (crea el registro de servicio principal)
- Complemento IPTV (utiliza el service_id del paso 1)
- Complemento de IP estática (utiliza el service_id del paso 1)
El servicio de aprovisionamiento automáticamente:
- Consulta la base de datos por el
service_idcreado por el primer playbook - Lo inyecta en los
extra_varspara los playbooks subsiguientes - Rastrear cada playbook como un registro
Provisionseparado
Razones de Fallo y Depuración
Cuando el aprovisionamiento falla, el sistema captura información detallada para ayudar a diagnosticar el problema.
Escenarios Comunes de Fallo
Fallos de Tareas Críticas (Estado 2)
Estos causan que todo el trabajo de aprovisionamiento falle:
- Llamadas API que devuelven códigos de estado inesperados
- Fallos de afirmaciones (por ejemplo,
assert: that: response.status == 200) - Faltan elementos de inventario requeridos
- Equipos de red inalcanzables
- Credenciales inválidas o tokens expirados
- Errores de OCS/OCS
Fallos Ignorados (Estado 3)
Estos se registran pero no fallan el trabajo:
- Fallos de notificaciones SMS/correo electrónico opcionales
- Búsquedas de datos no críticas (marcadas con
ignore_errors: True) - Operaciones de limpieza durante el desaprovisionamiento
Errores Fatales
Estos impiden que el playbook se ejecute en absoluto:
- Errores de sintaxis YAML en el playbook
- Variables indefinidas en el playbook
- Archivos de playbook faltantes
- Fallos de conexión al controlador de Ansible
Cuando ocurre un error fatal, el sistema crea un evento de error especial que contiene:
- El código de salida de Ansible
- stdout completo (contiene detalles del error de sintaxis)
- stderr completo (contiene errores de tiempo de ejecución)
- Una lista de causas comunes para ese tipo de fallo
- Todas las variables pasadas al playbook
Correos Electrónicos de Notificación de Errores
Cuando el aprovisionamiento falla (estado 2), se envía automáticamente un correo electrónico a la lista de notificación de fallos configurada (provisioning.failure_list en crm_config.yaml).
El correo electrónico incluye:
- Información del cliente
- Detalles del producto/servicio
- Resultados de tareas codificados por colores:
- Verde: Tareas exitosas
- Naranja: Tareas fallidas pero ignoradas
- Rojo: Fallos críticos
- Para fallos críticos: Salida de depuración completa incluyendo cuerpos de solicitud/respuesta
- Para errores fatales: Salida de Ansible, mensajes de error y causas comunes
Monitoreo de Trabajos de Aprovisionamiento
API de Estado de Aprovisionamiento
Para verificar el estado de un trabajo de aprovisionamiento:
GET /crm/provision/provision_id/<id>
Authorization: Bearer <token>
La respuesta incluye:
{
"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": "{...}"
}
]
}
Listando Trabajos de Aprovisionamiento
Para obtener una lista paginada de todos los trabajos de aprovisionamiento:
GET /crm/provision/?page=1&per_page=20&sort=provision_id&order=desc
Authorization: Bearer <token>
Admite filtrado:
GET /crm/provision/?filters={"provisioning_status":[2]}&search=Mobile
Authorization: Bearer <token>
Esto devuelve solo trabajos fallidos (estado 2) donde la descripción contiene "Mobile".
Mejores Prácticas
Diseño del Playbook
- Siempre usar block/rescue: Asegúrese de que el aprovisionamiento parcial pueda ser revertido
- Usar ignore_errors con juicio: Solo para operaciones verdaderamente opcionales
- Registrar variables importantes: Utilice tareas
debugpara registrar valores clave para la solución de problemas - Validar respuestas: Utilice
assertpara verificar que las respuestas de la API sean las esperadas - Idempotencia: Diseñar playbooks para ser seguros de volver a ejecutar
Autenticación
- Aprovisionamiento iniciado por el usuario: Siempre use refresh_token para playbooks de larga duración
- Aprovisionamiento automatizado: Use lista blanca de IP o claves API con tokens de acceso generados
- Expiración del token: El refresh_token asegura que los tokens de acceso se regeneren según sea necesario
Manejo de Errores
- Proporcionar contexto: Incluir customer_id, service_id y detalles de operación en mensajes de error
- Notificar adecuadamente: Fallos críticos desencadenan correos electrónicos, pero no spam para fallos esperados
- Información de depuración: Capturar cuerpos de solicitud/respuesta completos en registros de Provision_Event
Seguridad
- Validar entradas: Verificar customer_id, product_id, service_id antes de aprovisionar
- Verificaciones de permisos: Verificar que los usuarios solo puedan aprovisionar para clientes autorizados
- Datos sensibles: Utilizar el sistema de redacción para eliminar contraseñas/claves de los registros
- Lista blanca de IP: Restringir endpoints de simple_provision a sistemas de confianza únicamente
Rendimiento
- Ejecución en segundo plano: Nunca bloquear las respuestas de la API esperando el aprovisionamiento
- Intervalos de polling: La UI debe realizar polling para actualizaciones de estado cada 2-5 segundos
- Tareas paralelas: Utilizar la paralelización nativa de Ansible para operaciones independientes
- Actualizaciones de base de datos: El manejador de eventos actualiza la base de datos en tiempo real, no es necesario consultar durante la ejecución
Documentación Relacionada
concepts_ansible- Conceptos generales de aprovisionamiento de Ansibleconcepts_api- Autenticación y uso de la API de CRMconcepts_products_and_services- Definiciones de productos y serviciosadministration_inventory- Gestión de inventario para aprovisionamiento