Sistema de Provisionamento
OmniCRM usa Ansible para automatizar o provisionamento, configuração e desprovisionamento de serviços ao cliente. O sistema de provisionamento é projetado para ser flexível, permitindo fluxos de trabalho complexos enquanto mantém consistência e confiabilidade.

::: note ::: title Nota :::
Para um guia completo sobre a jornada do produto ao serviço com exemplos detalhados de playbooks do Ansible, estratégias de preços e cenários do mundo real, veja Guia Completo do Ciclo de Vida do Produto. :::
Visão Geral
Quando um produto é solicitado ou um serviço precisa ser configurado, o OmniCRM cria um Trabalho de Provisionamento que executa um ou mais playbooks do Ansible. Esses playbooks interagem com vários sistemas de backend (OCS/CGRateS, equipamentos de rede, APIs, etc.) para provisionar completamente o serviço.
O sistema de provisionamento suporta dois fluxos de trabalho principais:
- Provisionamento Padrão - Acionado por funcionários ou clientes através da UI/API
- Provisionamento Simples - Acionado por sistemas externos como OCS para operações automatizadas
Valores de Status de Provisionamento
Os trabalhos de provisionamento e tarefas individuais podem ter os seguintes status:
- Status 0 (Sucesso) - O trabalho de provisionamento foi concluído com sucesso
- Status 1 (Executando) - O trabalho de provisionamento ou tarefa está atualmente em execução
- Status 2 (Falhou - Crítico) - Ocorreu uma falha crítica que fez o provisionamento falhar
- Status 3 (Falhou - Ignorado) - Uma tarefa falhou, mas tinha
ignore_errors: true, então o provisionamento continuou
Quando um trabalho de provisionamento falha, o OmniCRM envia notificações por e-mail para a lista de notificação de falhas configurada com informações detalhadas sobre o erro.
Como os Produtos Impulsionam o Provisionamento
A definição de Produto é o plano para o que é provisionado e como. Quando um usuário seleciona um produto para provisionar, o sistema lê vários campos-chave da definição do produto para determinar o que fazer.
Campos do Produto Usados no Provisionamento
Uma definição de produto contém:
provisioning_play- O nome do playbook do Ansible a ser executado (sem a extensão .yaml)provisioning_json_vars- String JSON contendo variáveis padrão a serem passadas para o Ansibleinventory_items_list- Lista de tipos de inventário que devem ser atribuídos (por exemplo,['SIM Card', 'Mobile Number'])product_id,product_name, campos de preços - Passados automaticamente para o playbook
Exemplo de Definição de Produto
{
"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
}
Do Produto ao Trabalho de Provisionamento
Quando o provisionamento é iniciado, o sistema:
-
Carrega o playbook especificado em
provisioning_playO sistema procura por
OmniCRM-API/Provisioners/plays/play_psim_only.yaml -
Mescla variáveis de várias fontes em
extra_vars:a. De provisioning_json_vars:
{"iccid": "", "msisdn": ""}b. Do corpo da solicitação: Quaisquer variáveis adicionais que o usuário/API fornecer c. Dos campos do produto:product_id,customer_id, etc. d. Da autenticação:access_tokenou configuração pararefresh_token -
Atribui inventário com base em
inventory_items_listAntes de executar o playbook, a UI/API solicita a seleção de inventário:
- SIM Card - O usuário seleciona um SIM disponível do inventário
- Mobile Number - O usuário seleciona um número de telefone disponível
Os IDs de inventário selecionados são adicionados a
extra_varscom o tipo de inventário como chave:extra_vars = {
"product_id": 1,
"customer_id": 456,
"SIM Card": 789, # inventory_id do SIM selecionado
"Mobile Number": 101, # inventory_id do número de telefone selecionado
"iccid": "", # De provisioning_json_vars
"msisdn": "", # De provisioning_json_vars
"access_token": "eyJ..."
} -
Passa tudo para o Ansible via
hostvars[inventory_hostname]Dentro do playbook, as variáveis são acessíveis 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]"
Como os Playbooks Usam Variáveis de Inventário
Uma vez que o playbook tenha os IDs de inventário, ele recupera os detalhes completos do inventário da 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 }}"
O playbook pode então usar esses valores para:
- Provisionar o SIM card no HSS com o IMSI
- Configurar o número de telefone no sistema de cobrança
- Atribuir os itens de inventário ao cliente
- Criar o registro de serviço com esses detalhes
Exemplo do Mundo Real: Provisionamento de SIM Móvel
Do play_psim_only.yaml, veja como ele usa dados de produto e inventário:
- 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
Isso demonstra o fluxo completo:
- A definição do produto especifica
provisioning_play: "play_psim_only" - O produto requer
inventory_items_list: ['SIM Card', 'Mobile Number'] - O usuário seleciona itens de inventário durante o provisionamento
- Os IDs de inventário são passados para o playbook como
extra_vars - O playbook recupera detalhes completos do inventário da API
- O playbook usa dados de inventário para configurar equipamentos de rede
- O playbook marca o inventário como atribuído ao cliente
Rollback e Limpeza: Padrão de Melhores Práticas
Melhor Prática Crítica: O mesmo playbook deve lidar tanto com rollback de provisionamento falhado quanto com desprovisionamento intencional usando a estrutura block e rescue do Ansible.
Estrutura do Playbook
Do play_psim_only.yaml:
- name: OmniCore Service Provisioning 2024
hosts: localhost
gather_facts: no
become: False
tasks:
- name: Main block
block:
# --- TAREFAS DE PROVISIONAMENTO ---
- name: Get Product information
uri: ...
- name: Create account in OCS
uri: ...
- name: Provision subscriber on HSS
uri: ...
- name: Create service record
uri: ...
# ... muitas outras tarefas de provisionamento ...
rescue:
# --- TAREFAS DE LIMPEZA ---
# Esta seção é executada quando:
# 1. Qualquer tarefa no bloco falha (rollback)
# 2. action == "deprovision" (limpeza 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 fictício
"ue_ambr_dl": 9999999, # Inutilizavelmente alto
"ue_ambr_ul": 9999999
}
loop: "{{ hss_subscriber_data | dict2items }}"
when:
- deprovision_subscriber | default(false) | bool == false
# A afirmação final determina sucesso ou falha
- name: Set status to "Success" if Manual deprovision / Fail if failed provision
assert:
that:
- action == "deprovision"
Por que Este Padrão é uma Melhor Prática
1. Sem Duplicação de Código
As mesmas tarefas de limpeza lidam com ambos os cenários:
- Provisionamento Falhado (Rollback): Se qualquer tarefa no
blockfalhar, a seçãorescueé executada automaticamente - Desprovisionamento Intencional: Quando chamado com
action: "deprovision", o playbook imediatamente salta pararescue
2. Limpeza Completa Garantida
Quando um provisionamento falha no meio do caminho, a seção de resgate garante:
- Todas as contas OCS criadas são deletadas
- Todas as entradas de equipamentos de rede configuradas são removidas
- O inventário atribuído é retornado ao pool
- Os assinantes HSS são deletados ou definidos como inativos
- Nenhum provisionamento parcial permanece em nenhum sistema
Isso previne recursos "órfãos" que:
- Consomem inventário sem serem rastreados
- Criam contas de cobrança que não estão vinculadas a serviços
- Causam confusão durante a resolução de problemas
- Desperdiçam recursos de rede
3. Tratamento de Falhas Elegante com ignore_errors
Observe que cada tarefa de limpeza usa ignore_errors: True. Isso é intencional porque:
- Durante o rollback, alguns recursos podem não ter sido criados ainda
- Queremos tentar todas as tarefas de limpeza mesmo que algumas falhem
- A afirmação final determina o sucesso/falha geral
Por exemplo, se o provisionamento falhar em "Criar conta no OCS", a limpeza tentará:
- Deletar a conta OCS (falhará, mas será ignorada)
- Remover perfis de atributos (falhará, mas será ignorada)
- Retornar inventário (sucede)
- Deletar assinante HSS (pode não existir, ignorada)
4. Distinguindo Desprovisionamento de Rollback
A afirmação final no final de rescue é inteligente:
- name: Set status to "Success" if Manual deprovision / Fail if failed provision
assert:
that:
- action == "deprovision"
Isso significa:
- Se
action == "deprovision": A afirmação passa, o playbook tem sucesso (status 0) - Se
actionnão estiver definido ou != "deprovision": A afirmação falha, o playbook falha (status 2)
Assim, o mesmo código de limpeza resulta em diferentes status de trabalho de provisionamento dependendo da intenção.
5. Limpeza Condicional com Base no Tipo de Serviço
Algumas tarefas de limpeza usam condicionais para lidar com diferentes cenários:
- 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
Isso permite uma limpeza flexível:
- Deleção completa: Quando SIMs são dedicados a clientes (
deprovision_subscriber: true) - Estado inativo: Quando SIMs são reutilizáveis e devem permanecer no HSS (
deprovision_subscriber: false)
Como Usar Este Padrão
Para Provisionamento:
{
"product_id": 1,
"customer_id": 456,
"provisioning_play": "play_psim_only"
}
Se o provisionamento falhar, o rollback automático ocorre via rescue.
Para Desprovisionamento:
{
"service_id": 123,
"service_uuid": "Service_abc123",
"action": "deprovision",
"provisioning_play": "play_psim_only"
}
O playbook salta diretamente para a seção rescue, executa toda a limpeza e tem sucesso.
Resumo dos Benefícios
✅ Fonte ��nica de verdade: Um playbook lida com provisionamento e desprovisionamento ✅ Operações atômicas: Ou totalmente provisionado ou totalmente limpo ✅ Sem recursos órfãos: Provisionamentos falhados não deixam rastros ✅ Manutenção mais fácil: Mudanças na lógica de provisionamento se aplicam automaticamente à limpeza ✅ Redução de erros: Sem chance de o código de provisionamento e desprovisionamento ficarem fora de sincronia ✅ Testável: Pode testar a lógica de desprovisionamento executando com action: "deprovision"
Este padrão deve ser seguido em todos os playbooks de provisionamento para garantir confiabilidade e consistência.
Sobrescrevendo Variáveis de Produto
As provisioning_json_vars podem ser sobrescritas no momento do provisionamento. Por exemplo, um produto pode definir:
{
"provisioning_json_vars": "{\"monthly_cost\": 50, \"data_limit_gb\": 100}"
}
Mas ao provisionar, você pode sobrescrever isso:
{
"product_id": 1,
"customer_id": 456,
"monthly_cost": 45,
"data_limit_gb": 150
}
As extra_vars mescladas usarão os valores sobrescritos. Isso permite:
- Preços personalizados para clientes específicos
- Limites de dados diferentes com base em promoções
- Testes com diferentes parâmetros sem modificar o produto
Produtos Sem Inventário
Nem todos os produtos requerem inventário. Por exemplo, um complemento de dados ou um recurso pode ter:
{
"product_id": 10,
"product_name": "Extra 10GB Data",
"provisioning_play": "play_local_data_addon",
"provisioning_json_vars": "{\"data_gb\": 10}",
"inventory_items_list": "[]"
}
Neste caso, o playbook recebe:
extra_vars = {
"product_id": 10,
"customer_id": 456,
"service_id": 123, # Serviço para adicionar dados
"data_gb": 10,
"access_token": "eyJ..."
}
O playbook simplesmente adiciona os dados ao serviço existente sem precisar de itens de inventário.
Fluxo de Trabalho de Provisionamento Padrão
O provisionamento padrão é iniciado quando:
- Um membro da equipe adiciona um serviço a um cliente pela UI
- Um cliente solicita um serviço através do portal de autoatendimento
- A API é chamada diretamente com
PUT /crm/provision/
Quando Você Clica em "Provisionar"
Aqui está o fluxo completo que ocorre quando um usuário clica no botão "Provisionar":
1. UI Exibe Seleção de Produto
O usuário seleciona um produto do catálogo de produtos. O produto contém:
provisioning_play- Qual playbook do Ansible executarinventory_items_list- Inventário necessário (por exemplo,['SIM Card', 'Mobile Number'])provisioning_json_vars- Variáveis padrão
2. Seletor de Inventário (Se Necessário)
Se inventory_items_list não estiver vazio, um modal aparece mostrando dropdowns para cada tipo de inventário. O usuário deve selecionar itens de inventário disponíveis antes de prosseguir.
3. Botão de Provisionamento Clicado
JavaScript envia a solicitação 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. API Recebe Solicitação
O endpoint de provisionamento (routes/provisioning.py):
- Valida a autenticação (token Bearer, chave da API ou lista de IPs permitidos)
- Verifica se o usuário tem permissão
CREATE_PROVISION - Extrai
initiating_userdo token - Carrega a definição do produto do banco de dados
- Recupera o caminho do playbook:
OmniCRM-API/Provisioners/plays/play_psim_only.yaml
5. Variáveis Mescladas
O sistema combina variáveis de várias fontes:
# Do produto
product_vars = json.loads(product['provisioning_json_vars'])
# Do corpo da solicitação
request_vars = request.json
# Adicionadas pelo sistema
system_vars = {
'product_id': 42,
'customer_id': 123,
'access_token': g.access_token, # Veja a seção de autenticação abaixo
'initiating_user': 7
}
# Final mesclada
extra_vars = {**product_vars, **request_vars, **system_vars}
6. Registro de Provisionamento Criado
Registro no banco de dados criado com status 1 (Executando):
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, # Executando
'task_count': 85,
'initiating_user': 7,
'created': '2025-01-10T14:30:00Z'
}
7. Thread em Segundo Plano Iniciada
run_playbook_in_background(
playbook='plays/play_psim_only.yaml',
extra_vars=extra_vars,
provision_id=456,
refresh_token=refresh_token # Para atualização de token durante a execução
)
8. API Retorna Imediatamente
Resposta retornada à UI com provision_id:
{
"provision_id": 456,
"provisioning_status": 1,
"message": "Provisioning job created"
}
9. UI Polls for Updates
A UI começa a consultar GET /crm/provision/provision_id/456 a cada 3 segundos para verificar o status. A resposta inclui:
{
"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 Executa em Segundo Plano
O playbook executa tarefas sequencialmente:
- Cada conclusão de tarefa cria um registro
Provision_Eventno banco de dados - O evento inclui: nome da tarefa, status (0=sucesso, 2=falhou, 3=falhou mas ignorado), resultado JSON
- A UI exibe o progresso em tempo real mostrando tarefas concluídas e a tarefa atualmente em execução
- Tarefas falhadas mostram mensagens de erro nos detalhes do evento
Rastreamento na UI:
Enquanto o provisionamento está em execução (status 1), os usuários podem visualizar:
- Página de Detalhes do Serviço - Mostra o badge de status de provisionamento (Executando/Sucesso/Falhou)
- Registro de Atividades - Lista todos os eventos de provisionamento com timestamps
- Visão Detalhada do Provisionamento - Mostra o progresso tarefa por tarefa com expandir/colapsar para detalhes
Exibição de exemplo:
Status de Provisionamento: Executando (8 de 12 tarefas concluídas)
✓ Obter informações do produto da API CRM ✓ Buscar detalhes do cliente ✓ Atribuir SIM Card do inventário (ICCID: 8991101200003204510) ✓ Atribuir Número de Telefone (555-0123) ⟳ Criar conta no OCS/CGRateS (em progresso...) ⏺ Configurar políticas de rede ⏺ Criar registro de serviço ...
11. O Provisionamento Conclui
Status final definido:
provisioning_status: 0- Sucessoprovisioning_status: 2- Falhou (erro crítico)
A UI para de consultar e exibe o resultado:
- Sucesso: Marca de verificação verde, serviço marcado como Ativo, o usuário pode visualizar detalhes do serviço
- Falha: X vermelho, mensagem de erro exibida, opção de tentar novamente ou entrar em contato com o suporte
- Notificação por e-mail: Se falhar, e-mail enviado para
provisioning.failure_listna configuração
Autenticação e Autorização
Rastreamento de Usuário
Cada trabalho de provisionamento rastreia qual usuário o iniciou:
- Iniciado pelo usuário: O campo
initiating_useré definido como o ID do usuário a partir de seu token JWT - Autenticação por chave de API: Usa o ID do primeiro usuário administrador
- Autenticação por lista de IPs: Usa o ID do primeiro usuário administrador
Verificações de Permissão
O sistema verifica permissões antes de permitir o provisionamento:
- Funcionários precisam da permissão
CREATE_PROVISION - Clientes só podem provisionar serviços para sua própria conta (
VIEW_OWN_PROVISION)
Como o Ansible se Autentica com a API CRM
Os playbooks do Ansible precisam fazer chamadas de API autenticadas de volta para o CRM (para buscar detalhes do produto, criar serviços, atualizar inventário, etc.). A autenticação é tratada através de tokens Bearer passados para o playbook.
A origem do access_token depende do método de autenticação usado para chamar a API de provisionamento:
Método 1: Login do Usuário (Token Bearer)
Quando um usuário faz login via UI web:
- O usuário se autentica:
POST /crm/auth/login - Recebe o token JWT
access_token(de curta duração, 15-30 min) erefresh_token(de longa duração) - Faz a solicitação de provisionamento com o token Bearer no cabeçalho
- A API de provisionamento extrai o token do cabeçalho
Authorization: Bearer ... - Armazena em
g.access_token(contexto de solicitação Flask) - Passa para o Ansible como variável
access_token
Código (permissions.py):
# Extrair token Bearer do cabeçalho
auth_header = request.headers.get('Authorization', '')
if auth_header.startswith('Bearer '):
bearer_token = auth_header[7:]
# Validar e decodificar
decoded_token = jwt.decode(bearer_token, secret_key, algorithms=['HS256'])
# Armazenar para provisionamento
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: Chave de API (Cabeçalho X-API-KEY)
Para sistemas automatizados usando chaves de API:
- O sistema faz a solicitação:
PUT /crm/provision/com o cabeçalhoX-API-KEY: sua-chave-api... - A API de provisionamento valida a chave da API contra
crm_config.yaml - Gera um novo token JWT em tempo real para o primeiro usuário administrador
- Armazena em
g.access_token - Passa para o Ansible
Por que Gerar um Token?
As chaves de API são strings, não JWTs. Os playbooks chamam endpoints de API esperando autenticação JWT. Portanto:
- Valide a chave da API
- Se válida e tiver função
admin, gere um JWT temporário - Use o ID do primeiro usuário administrador como sujeito do JWT
- O token permite que o playbook faça chamadas de 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': 'Chave de API inválida'}, 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 de IPs Permitidos
Para sistemas internos confiáveis em redes privadas:
- O sistema faz a solicitação de um IP permitido (por exemplo, 192.168.1.100)
- A API de provisionamento verifica o IP do cliente contra
ip_whitelistemcrm_config.yaml - Se estiver na lista, gera um novo token JWT para o primeiro usuário administrador
- Armazena em
g.access_token - Passa para o 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': 'Acesso negado'}, 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 o Token nos Playbooks
Cada chamada de API no playbook inclui o 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"
Expiração e Atualização do Token
Playbooks de longa duração (5-10 minutos) podem ultrapassar o access_token (expiração de 15-30 min). Para provisionamentos iniciados pelo usuário, o sistema passa tanto access_token quanto refresh_token:
refresh_token = request.cookies.get('refresh_token')
run_playbook(playbook_path, extra_vars, provision_id, refresh_token=refresh_token)
Se o access_token expirar, o executor do playbook pode:
- Detectar resposta 401 Não Autorizado
- Chamar
POST /crm/auth/refreshcomrefresh_token - Receber novo
access_token - Tentar novamente a solicitação falhada
Para autenticação por chave de API/lista de IPs, os tokens gerados podem ter expiração mais longa (1-2 horas), uma vez que esses são sistemas automatizados confiáveis.
O Processo de Provisionamento
-
Criação do Trabalho
Quando uma solicitação de provisionamento é recebida, o sistema:
- Valida a solicitação e verifica permissões
- Carrega o playbook do Ansible especificado na definição do produto
- Cria um registro de
Provisionno banco de dados com status 1 (Executando) - Extrai variáveis da definição do produto e do corpo da solicitação
- Captura tokens de autenticação para acesso à API
-
Tratamento de Token
Os playbooks do Ansible precisam se autenticar com a API CRM para recuperar dados e fazer alterações. O sistema de provisionamento trata isso de duas maneiras:
- Token Bearer (JWT): Para provisionamento iniciado pelo usuário, o
refresh_tokenda solicitação é usado para gerar tokens de acesso frescos durante a execução do playbook - Autenticação por Chave de API/IP: Para sistemas automatizados, um
access_tokené passado diretamente para o playbook viag.access_token
- Token Bearer (JWT): Para provisionamento iniciado pelo usuário, o
-
Execução em Segundo Plano
O playbook é executado em uma thread em segundo plano usando
playbook_runner_v2. Isso permite que a API retorne imediatamente enquanto o provisionamento continua de forma assíncrona.Durante a execução:
- Cada conclusão/falha de tarefa cria um registro
Provision_Event - O manipulador de eventos monitora falhas críticas vs. ignoradas
- Atualizações de status em tempo real são escritas no banco de dados
- A UI pode consultar atualizações via
GET /crm/provision/provision_id/<id>
- Cada conclusão/falha de tarefa cria um registro
-
Execução do Playbook
O playbook do Ansible normalmente realiza estas operações:
- Recupera informações do produto da API
- Recupera informações do cliente da API
- Atribui itens de inventário (cartões SIM, endereços IP, números de telefone, etc.)
- Cria contas no OCS/OCS
- Configura equipamentos de rede
- Cria o registro de serviço na API CRM
- Adiciona transações de custo de configuração
- Envia e-mails/SMS de boas-vindas aos clientes
-
Tratamento de Erros
Os playbooks do Ansible usam seções
blockerescuepara rollback:- Se uma tarefa crítica falhar, a seção de resgate remove o provisionamento parcial
- Tarefas com
ignore_errors: truesão marcadas como status 3 e não falham o trabalho - Erros fatais (erros de sintaxe YAML, falhas de conexão) criam um evento de erro especial com informações de depuração
Exemplo: Playbook de Provisionamento Padrão
Aqui está um exemplo do 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 demonstra o fluxo típico:
- Recuperar detalhes do produto da API CRM
- Gerar um UUID de serviço exclusivo
- Criar a conta de cobrança no OCS
- Criar o registro de serviço através da API CRM
- Adicionar transações de custo de configuração
- Se algo falhar, a seção
rescueremove a conta OCS
Fluxo de Trabalho de Provisionamento Simples
O provisionamento simples é projetado para sistemas automatizados que precisam acionar o provisionamento sem interação do usuário. O caso de uso mais comum é o OCS acionando o provisionamento de complementos via ActionPlans.
Endpoints de Provisionamento Simples
OmniCRM fornece dois endpoints de provisionamento simples:
-
POST /crm/provision/simple_provision_addon/service_id/<id>/product_id/<id>Para provisionamento automatizado de complementos (por exemplo, cobranças recorrentes, recargas automáticas)
-
POST /crm/provision/simple_provision_addon_recharge/service_id/<id>/product_id/<id>Para operações de recarga rápida que precisam de feedback imediato
Autenticação para Provisionamento Simples
Os endpoints de provisionamento simples usam lista de IPs permitidos ou chaves de API para autenticação:
- O IP de origem da solicitação é verificado contra
ip_whitelistemcrm_config.yaml - Ou uma chave de API de
api_keysemcrm_config.yamlpode ser fornecida - Um token de acesso é gerado e passado para o playbook via
g.access_token
Exemplo: Callback de ActionPlan do OCS
O OCS pode ser configurado para chamar o endpoint de provisionamento simples ao executar ações recorrentes:
{
"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"
}
]
}]
}
A ação faz um POST HTTP para:
Isso aciona o playbook associado (por exemplo, play_topup_no_charge.yaml) que adiciona dados/créditos ao serviço.
Exemplo: Playbook de Recarga Simples
Do 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": "Obrigado por recarregar {{ monthly_cost }}!"
}
status_code: 201
ignore_errors: True
Este playbook:
- Obtém detalhes do serviço e do produto da API
- Recupera o método de pagamento do cliente
- Cobra o cliente via API Stripe
- Adiciona saldo monetário ao OCS
- Registra a transação no CRM
- Envia um SMS de confirmação (com
ignore_errors: Truepara que falhas não falhem o trabalho)
Cadeias de Provisionamento
Para produtos complexos que requerem várias etapas de provisionamento, o OmniCRM suporta cadeias de provisionamento. Uma cadeia executa múltiplos playbooks sequencialmente, passando contexto entre eles.
Exemplo de caso de uso: Um serviço agrupado que provisiona:
- Serviço de internet base (cria o registro de serviço primário)
- Complemento IPTV (usa o service_id da etapa 1)
- Complemento IP estático (usa o service_id da etapa 1)
O serviço de provisionamento automaticamente:
- Consulta o banco de dados para o
service_idcriado pelo primeiro playbook - Injeta isso nas
extra_varspara playbooks subsequentes - Rastreia cada playbook como um registro
Provisionseparado
Razões para Falhas e Depuração
Quando o provisionamento falha, o sistema captura informações detalhadas para ajudar a diagnosticar o problema.
Cenários Comuns de Falha
Falhas Críticas de Tarefa (Status 2)
Essas causam a falha de todo o trabalho de provisionamento:
- Chamadas de API retornando códigos de status inesperados
- Falhas de afirmações (por exemplo,
assert: that: response.status == 200) - Itens de inventário obrigatórios ausentes
- Equipamentos de rede inacessíveis
- Credenciais inválidas ou tokens expirados
- Erros OCS/OCS
Falhas Ignoradas (Status 3)
Essas são registradas, mas não falham o trabalho:
- Notificações SMS/e-mail opcionais falhando
- Consultas de dados não críticas (marcadas com
ignore_errors: True) - Operações de limpeza durante o desprovisionamento
Erros Fatais
Esses impedem que o playbook seja executado:
- Erros de sintaxe YAML no playbook
- Variáveis indefinidas no playbook
- Arquivos de playbook ausentes
- Falhas de conexão com o controlador Ansible
Quando ocorre um erro fatal, o sistema cria um evento de erro especial contendo:
- O código de saída do Ansible
- Todo o stdout (contém detalhes do erro de sintaxe)
- Todo o stderr (contém erros de tempo de execução)
- Uma lista de causas comuns para esse tipo de falha
- Todas as variáveis passadas para o playbook
E-mails de Notificação de Erro
Quando o provisionamento falha (status 2), um e-mail é enviado automaticamente para a lista de notificação de falhas configurada (provisioning.failure_list em crm_config.yaml).
O e-mail inclui:
- Informações do cliente
- Detalhes do produto/serviço
- Resultados das tarefas codificados por cores:
- Verde: Tarefas bem-sucedidas
- Laranja: Tarefas falhadas, mas ignoradas
- Vermelho: Falhas críticas
- Para falhas críticas: Saída de depuração completa, incluindo corpos de solicitação/resposta
- Para erros fatais: Saída do Ansible, mensagens de erro e causas comuns
Monitoramento de Trabalhos de Provisionamento
API de Status de Provisionamento
Para verificar o status de um trabalho de provisionamento:
GET /crm/provision/provision_id/<id>
Authorization: Bearer <token>
A resposta inclui:
{
"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 Trabalhos de Provisionamento
Para obter uma lista paginada de todos os trabalhos de provisionamento:
GET /crm/provision/?page=1&per_page=20&sort=provision_id&order=desc
Authorization: Bearer <token>
Suporta filtragem:
GET /crm/provision/?filters={"provisioning_status":[2]}&search=Mobile
Authorization: Bearer <token>
Isso retorna apenas trabalhos falhados (status 2) onde a descrição contém "Mobile".
Melhores Práticas
Design de Playbook
- Sempre use block/rescue: Garanta que o provisionamento parcial possa ser revertido
- Use ignore_errors com moderação: Apenas para operações realmente opcionais
- Registre variáveis importantes: Use tarefas
debugpara registrar valores-chave para solução de problemas - Valide respostas: Use
assertpara verificar se as respostas da API são como esperado - Idempotência: Projete playbooks para serem seguros para execução repetida
Autenticação
- Provisionamento iniciado pelo usuário: Sempre use refresh_token para playbooks de longa duração
- Provisionamento automatizado: Use lista de IPs permitidos ou chaves de API com tokens de acesso gerados
- Expiração de token: O refresh_token garante que os tokens de acesso sejam regenerados conforme necessário
Tratamento de Erros
- Forneça contexto: Inclua customer_id, service_id e detalhes da operação nas mensagens de erro
- Notifique adequadamente: Falhas críticas acionam e-mails, mas não spam para falhas esperadas
- Informações de depuração: Capture corpos de solicitação/resposta completos em registros Provision_Event
Segurança
- Valide entradas: Verifique customer_id, product_id, service_id antes do provisionamento
- Verificações de permissão: Verifique se os usuários podem apenas provisionar para clientes autorizados
- Dados sensíveis: Use o sistema de redacção para remover senhas/chaves dos logs
- Lista de IPs permitidos: Restringir endpoints de simple_provision a sistemas confiáveis apenas
Desempenho
- Execução em segundo plano: Nunca bloqueie respostas da API aguardando provisionamento
- Intervalos de polling: A UI deve consultar atualizações de status a cada 2-5 segundos
- Tarefas paralelas: Use a paralelização nativa do Ansible para operações independentes
- Atualizações de banco de dados: O manipulador de eventos atualiza o banco de dados em tempo real, sem necessidade de consultar durante a execução
Documentação Relacionada
concepts_ansible- Conceitos gerais de provisionamento do Ansibleconcepts_api- Autenticação e uso da API CRMconcepts_products_and_services- Definições de produtos e serviçosadministration_inventory- Gerenciamento de inventário para provisionamento