Initial commit

This commit is contained in:
HalbeBruno
2026-02-18 10:18:46 -03:00
commit b264b583b8
24 changed files with 2338 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# Python
__pycache__/
*.py[cod]
*$py.class
venv/
.env
# Logs
*.log
flask.log
# IDE
.vscode/
.idea/
# Project Specific
hosts.json
implementation_plan.md
task.md
walkthrough.md
.DS_Store
# Build & Dist
dist/
verification/
*.key
*.rkey

6
.pyarmor/config Normal file
View File

@@ -0,0 +1,6 @@
[project]
src = /home/halbebruno/Projetos/ZabbixAPI
[builder]
outer_keyname = license.key

61
README.md Normal file
View File

@@ -0,0 +1,61 @@
# IPv0 OLT API (Middleware Zabbix)
API RESTful intermediária desenvolvida em Python (Flask) para facilitar o monitoramento de OLTs (Nokia, Fiberhome, etc.) no Zabbix via Low Level Discovery (LLD).
## 🎯 Objetivo
Transformar comandos complexos de CLI (Telnet/SSH) e estruturas SNMP proprietárias em JSON limpo e otimizado para o Zabbix, com cache inteligente para proteger a OLT de sobrecarga.
## 🚀 Recursos Principais
- **Multi-Vendor**: Arquitetura baseada em drivers. Suporta Nokia (ISAM/FW específicos) com suporte futuro para Fiberhome, Huawei e ZTE.
- **Smart Cache**: Cache em memória evita múltiplas conexões para a mesma coleta de dados.
## 📂 Estrutura do Projeto
```
/
├── app.py # Aplicação Principal (Flask)
├── drivers/ # Camada de abstração (Factory Pattern)
│ ├── __init__.py # Contrato (Interface OltDriver)
│ ├── nokia.py # Driver concreto Nokia (Implementa connect/get_pon_stats)
│ └── fiberhome.py # Placeholder para expansão futura
├── utils/ # Cache, SNMP, Helpers
├── tools/ # Scripts de Manutenção (Build, Clean, License)
└── doc/ # Documentação detalhada
```
## 📦 Instalação (Usuário Final)
Para um guia detalhado, consulte: [doc/install_dist.md](doc/install_dist.md).
1. Transfira o arquivo `ipv0-olt-api.zip` para o servidor (ex: `/opt`).
2. Descompacte o arquivo e entre na pasta:
```bash
unzip ipv0-olt-api.zip -d ipv0-olt-api
cd ipv0-olt-api
```
3. Execute o script de instalação como root:
```bash
sudo ./install.sh
```
*Este script instalará as dependências e configurará o serviço.*
> ⚠️ **Atenção:** Ao final, ele exibirá o **Machine ID**. Copie este código para solicitar sua licença.
## 🔌 Expansão (Novos Drivers/Recursos)
A API foi desenhada para ser estendida e suportar outros fabricantes.
Consulte o guia de desenvolvimento: [doc/expansion.md](doc/expansion.md).
## 📊 Integrando no Zabbix
Para um guia passo-a-passo detalhado de como criar Master Items, regras LLD e protótipos de itens, consulte: [doc/zabbix_integration.md](doc/zabbix_integration.md).
## 📚 Guia do Desenvolvedor
Documentação técnica para manutenção e evolução do projeto:
* **Instalação (Ambiente Dev)**: [doc/install.md](doc/install.md) - *Como rodar o código fonte sem compilar.*
* **Expansão**: [doc/expansion.md](doc/expansion.md) - *Como criar novos drivers.*
* **Integração Zabbix (Guia Técnico)**: [doc/zabbix_integration.md](doc/zabbix_integration.md) - *Como criar templates e regras LLD.*
* **Build e Distribuição**: [doc/create_dist.md](doc/create_dist.md) - *Como gerar novas versões e licenças.*
* **Templates Oficiais**:
* **Nokia**: [doc/templates/template-nokia-api.xml](doc/templates/template-nokia-api.xml)
---
Desenvolvido com ❤️ e Python.

71
app.py Normal file
View File

@@ -0,0 +1,71 @@
from flask import Flask, jsonify, request
from drivers.nokia import NokiaDriver
from drivers.fiberhome import FiberhomeDriver
from config import Config
from utils.cache import cache_response
import time
import json
import os
app = Flask(__name__)
# Carregar inventário de hosts
HOSTS_CONFIG = {}
if os.path.exists('hosts.json'):
with open('hosts.json', 'r') as f:
HOSTS_CONFIG = json.load(f)
# Mapeamento de drivers
DRIVERS = {
'nokia': NokiaDriver,
'fiberhome': FiberhomeDriver
}
@app.route('/api/v1/olt_stats', methods=['GET'])
@cache_response # Cacheia a resposta baseado nos argumentos da request
def get_olt_stats():
host_ip = request.args.get('host')
driver_name_param = request.args.get('driver')
if not host_ip:
return jsonify({'error': 'Missing host parameter'}), 400
# Determinar configurações (hosts.json ou defaults)
host_config = HOSTS_CONFIG.get(host_ip, {})
# Parâmetros: Preference para query param > hosts.json > default config
driver_name = driver_name_param or host_config.get('driver')
username = host_config.get('username') or Config.OLT_USERNAME
password = host_config.get('password') or Config.OLT_PASSWORD
# Opções extras para o driver (ex: ssh_options, snmp_community)
# Remove chaves padrão para não duplicar e deixa o resto como opção
driver_options = {k: v for k, v in host_config.items() if k not in ['username', 'password', 'driver']}
if not driver_name:
return jsonify({'error': 'Driver not specified (param or hosts.json)'}), 400
if driver_name not in DRIVERS:
return jsonify({'error': f'Driver {driver_name} not supported'}), 400
try:
# Instancia o driver com opções extras
driver_class = DRIVERS[driver_name]
driver = driver_class(host_ip, username, password, **driver_options)
# Coleta estatísticas completas (Cards > PONs > ONTs)
stats = driver.get_olt_stats()
# Estrutura flexível, o driver define o retorno
return jsonify(stats)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({'status': 'ok', 'service': 'IPv0 OLT API', 'version': '3.1'}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5050)

16
config.py Normal file
View File

@@ -0,0 +1,16 @@
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
# Credenciais padrão (podem ser sobrescritas por variáveis de ambiente)
OLT_USERNAME = os.getenv('OLT_USERNAME', 'admin')
OLT_PASSWORD = os.getenv('OLT_PASSWORD', 'admin')
# Timeout do Netmiko em segundos
NETMIKO_TIMEOUT = int(os.getenv('NETMIKO_TIMEOUT', 10))
# TTL do Cache em segundos (padrão 5 minutos)
CACHE_TTL = int(os.getenv('CACHE_TTL', 300))
CACHE_MAX_SIZE = int(os.getenv('CACHE_MAX_SIZE', 100))

51
debug_snmp.py Normal file
View File

@@ -0,0 +1,51 @@
import asyncio
from utils.snmp import _snmp_walk_async, SnmpEngine, CommunityData, UdpTransportTarget, ContextData, ObjectType, ObjectIdentity, get_cmd
async def _snmp_get_async(host, community, oid):
snmp_engine = SnmpEngine()
community_data = CommunityData(community, mpModel=1)
# Using larger timeout/retries just in case
transport = await UdpTransportTarget.create((host, 161), timeout=2, retries=1)
context = ContextData()
iterator = get_cmd(
snmp_engine,
community_data,
transport,
context,
ObjectType(ObjectIdentity(oid))
)
errorIndication, errorStatus, errorIndex, varBinds = await iterator
snmp_engine.closeDispatcher()
if errorIndication or errorStatus:
return None
return str(varBinds[0][1])
async def main():
host = "10.186.203.14"
community = "public"
# Hypothesis: Slot 6, Port 1
# 0x06A00000 = 111149056
targets = [
(5, 1, 94371840), # Validated
(6, 1, 111149056), # To Evaluate
(6, 2, 111149056 + 65536)
]
base_oid_prefix = "1.3.6.1.2.1.2.2.1.2" # ifDescr
for slot, port, if_index in targets:
oid = f"{base_oid_prefix}.{if_index}"
print(f"Checking Slot {slot} Port {port} (ID={if_index})...")
val = await _snmp_get_async(host, community, oid)
if val and "No Such" not in val:
print(f" MATCH: {val}")
else:
print(f" FAIL: {val}")
if __name__ == "__main__":
asyncio.run(main())

35
doc/create_dist.md Normal file
View File

@@ -0,0 +1,35 @@
# Manual de Criação de Distribuição (Developer Guide)
Este documento destina-se a geração de novas versões de distribuição da **API** e emissão delicenças.
## 1. Gerando o Pacote de Distribuição
Para criar um pacote pronto para o cliente (binário protegido), execute:
```bash
# Executar da raiz
./tools/build.sh
```
**Saída:**
* **Staging:** `dist/ipv0-olt-api/` (Arquivos soltos para conferência)
* **Release:** `dist/release/ipv0-olt-api.zip` (Arquivo final para o cliente)
## 2. Gerenciamento de Licenças
A aplicação não rodará sem uma licença válida. Utilize o script automatizado para gerar e organizar licenças:
```bash
./tools/gen_license.sh
```
O script solicitará:
1. **Nome do Cliente** (Cria pasta em `dist/licenses/CLIENTE`).
2. **Tipo de Licença** (Data ou Hardware).
3. **Dados** (Vencimento ou Machine ID).
A licença gerada será salva em: `dist/licenses/<CLIENTE>/license.key`.
## 3. Atualizando a Versão
Ao modificar o código fonte:
1. Edite `config.py` ou features.
2. Rode `./build.sh`.
3. Envie o novo `release_production.zip` para o cliente.

112
doc/expansion.md Normal file
View File

@@ -0,0 +1,112 @@
# Guia de Expansão e Desenvolvimento
Este documento explica como adicionar novos recursos (métodos) e suportar novos fabricantes (drivers) na API Zabbix OLT.
## Arquitetura
A API segue um padrão de **Factory** simplificado:
1. **Driver Base (`drivers/__init__.py`)**: Define a interface comum (`OltDriver`).
2. **Implementações (`drivers/nokia.py`, etc)**: Classes concretas que herdam de `OltDriver`.
3. **API (`app.py`)**: Roteia as requisições para o método correto do driver instanciado.
---
## 1. Adicionar Novo Tipo de Monitoramento (Ex: Interfaces)
Para adicionar uma nova coleta de dados (ex: estatísticas de interface, temperatura, CPU):
### Passo 1: Atualizar o Contrato
Edite `drivers/__init__.py` e adicione o método abstrato na classe `OltDriver`.
```python
@abstractmethod
def get_interface_stats(self):
"""Retorna estatísticas de interfaces Ethernet/Uplink/PON."""
pass
```
### Passo 2: Implementar nos Drivers
Atualize **todas** as classes que herdam de `OltDriver` (ex: `drivers/nokia.py`).
```python
def get_interface_stats(self):
connection = self.connect()
output = connection.send_command_timing("show interface ...")
# Implementar lógica de parsing (regex)
stats = self._parse_interfaces(output)
connection.disconnect()
return stats
```
### Passo 3: Criar Rota na API
Edite `app.py` para expor o novo método.
```python
@app.route('/api/v1/interface_stats', methods=['GET'])
@cache_response
def get_interface_stats():
# ... (Copiar lógica de instanciação do driver de get_pon_stats) ...
try:
stats = driver.get_interface_stats()
return jsonify({"data": stats, "meta": ...})
except Exception as e:
return jsonify({'error': str(e)}), 500
```
---
## 2. Adicionar Novo Fabricante (Vendor)
Para suportar uma nova OLT (ex: Huawei, ZTE):
### Passo 1: Criar o Driver
Crie um arquivo em `drivers/` (ex: `drivers/huawei.py`).
```python
from drivers import OltDriver
import re
class HuaweiDriver(OltDriver):
def connect(self):
# Configurar conexão Netmiko (device_type='huawei')
return ConnectHandler(...)
def get_pon_stats(self):
connection = self.connect()
output = connection.send_command("display ont info ...")
connection.disconnect()
return self._parse_output(output)
```
### Passo 2: Registrar o Driver
Edite `app.py` e importe a nova classe.
```python
from drivers.huawei import HuaweiDriver
DRIVERS = {
'nokia': NokiaDriver,
'fiberhome': FiberhomeDriver,
'huawei': HuaweiDriver # <--- Novo registro
}
```
### Passo 3: Configurar Host
No `hosts.json`, defina o driver para o IP correspondente.
```json
"10.10.10.1": {
"driver": "huawei",
...
}
```
---
## Dicas de Desenvolvimento
- **Netmiko Timing**: Use `send_command_timing` se o equipamento tiver prompts complexos ou lentidão.
- **Regex**: Teste suas expressões regulares com outputs reais variados para garantir robustez.
- **Cache**: O decorador `@cache_response` em `utils/cache.py` já trata cache por URL (host distinto = cache distinto).

96
doc/install.md Normal file
View File

@@ -0,0 +1,96 @@
# Guia de Instalação: API Zabbix OLT
Este documento descreve os passos para instalação e configuração da API em ambiente Linux.
## Pré-requisitos
- Sistema Operacional Linux (Debian/Ubuntu/CentOS)
- Python 3.9 ou superior
- Git
## Passos de Instalação
### 1. Clonar o Repositório
O diretório padrão de instalação será `/opt/zbx-ipv0`.
```bash
sudo mkdir -p /opt/zbx-ipv0
sudo chown $USER:$USER /opt/zbx-ipv0
git clone https://git.ipv0.com.br/halbebruno/zabbix-api /opt/zbx-ipv0
cd /opt/zbx-ipv0
```
### 2. Configurar Ambiente Virtual (venv)
É recomendado usar um ambiente virtual para isolar as dependências.
```bash
python3 -m venv venv
source venv/bin/activate
```
### 3. Instalar Dependências
```bash
pip install -r requirements.txt
# Para produção, instale também o Gunicorn
pip install gunicorn
```
### 4. Configuração
#### 4.1 Inventário de Hosts
Crie ou edite o arquivo `hosts.json` na raiz do projeto com as credenciais das OLTs.
```json
{
"10.186.203.14": {
"username": "admin",
"password": "senha_segura",
"driver": "nokia",
"port": 22,
"ssh_options": {
"disabled_algorithms": {
"pubkeys": ["rsa-sha2-256", "rsa-sha2-512"]
}
}
}
}
```
#### 4.2 Variáveis de Ambiente (Opcional)
Você pode sobrescrever configurações padrão via variáveis de ambiente ou editando `config.py`.
### 5. Execução como Serviço (Systemd)
Para garantir que a API inicie automaticamente e rode em background, crie um serviço systemd.
Crie o arquivo `/etc/systemd/system/zabbix-api.service`:
```ini
[Unit]
Description=Zabbix OLT API
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=/opt/zbx-ipv0
Environment="PATH=/opt/zbx-ipv0/venv/bin"
ExecStart=/opt/zbx-ipv0/venv/bin/gunicorn --workers 4 --bind 0.0.0.0:5000 app:app
Restart=always
[Install]
WantedBy=multi-user.target
```
Ative e inicie o serviço:
```bash
sudo systemctl daemon-reload
sudo systemctl enable zabbix-api
sudo systemctl start zabbix-api
sudo systemctl status zabbix-api
```
## Teste
Verifique se a API está rodando:
```bash
curl http://127.0.0.1:5000/api/v1/pon_stats?host=SEU_IP_DA_OLT
```

86
doc/install_dist.md Normal file
View File

@@ -0,0 +1,86 @@
# Manual de Instalação (Cliente)
Este guia descreve como instalar a **IPv0 OLT API** em seu servidor Linux.
## Pré-requisitos
* Linux (Ubuntu 20.04+, Debian 11+, CentOS 8+)
* Python 3.8 ou superior
* Acesso root/sudo
## 1. Instalação Automática
1. Transfira o arquivo `ipv0-olt-api.zip` para o servidor (ex: `/opt`).
2. Descompacte o arquivo:
```bash
unzip ipv0-olt-api.zip -d ipv0-olt-api
cd ipv0-olt-api
```
3. Execute o script de instalação como root:
```bash
sudo ./install.sh
```
* Este script instalará as dependências e configurará o serviço.
* **Atenção:** Ao final, ele exibirá o **Machine ID**. Copie este código para solicitar sua licença.
## 2. Ativação da Licença e Início do Serviço
A aplicação não iniciará sem uma licença válida.
1. Envie o **Machine ID** para o suporte.
2. Você receberá um arquivo de licença chamado `license.key`.
3. Copie este arquivo para a pasta da aplicação e INICIE o serviço:
```bash
# Copiar a licença
sudo cp license.key /opt/ipv0-olt-api/
sudo chown root:root /opt/ipv0-olt-api/license.key
# Habilitar e Iniciar o serviço agora:
sudo systemctl enable --now ipv0-olt-api
```
## 3. Configuração (hosts.json)
Edite o arquivo `hosts.json` para adicionar suas OLTs.
Caminho: `/opt/ipv0-olt-api/hosts.json`
**Exemplo Completo:**
```json
{
"10.155.156.2": {
"username": "zabbix",
"password": "p4ssw0rd",
"driver": "nokia",
"snmp_community": "public",
"port": 22,
"ssh_options": {
"disabled_algorithms": {
"pubkeys": [
"rsa-sha2-256",
"rsa-sha2-512"
]
}
}
},
"192.168.1.10": {
"username": "admin",
"password": "simple_password",
"driver": "fiberhome",
"port": 23
}
}
```
Após editar, reinicie o serviço.
## 4. Diagnóstico e Debug
A API inclui uma ferramenta de diagnóstico. Para verificar status, licença e conexão:
```bash
cd /opt/ipv0-olt-api
sudo ./venv/bin/python3 debug.py
```
### Comandos Úteis
* **Ver logs:** `sudo journalctl -u ipv0-olt-api -f`
* ** Status do Serviço:** `sudo systemctl status ipv0-olt-api`
* **Testar API:** `curl http://localhost:5050/health`

945
doc/templates/template-nokia-api.xml vendored Normal file
View File

@@ -0,0 +1,945 @@
<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
<version>7.2</version>
<template_groups>
<template_group>
<uuid>b01bb96a1ed547e1a8770677a646aa3c</uuid>
<name>Templates IPv0</name>
</template_group>
<template_group>
<uuid>1a62bf6492d64062a2b5f58e30234244</uuid>
<name>Templates Nokia</name>
</template_group>
</template_groups>
<templates>
<template>
<uuid>41049df2762c47218e8282386e938ff7</uuid>
<template>IPv0 - Template Olt Nokia API</template>
<name>IPv0 - Template Olt Nokia API</name>
<groups>
<group>
<name>Templates IPv0</name>
</group>
<group>
<name>Templates Nokia</name>
</group>
</groups>
<items>
<item>
<uuid>2e17d6504d804ef098c6d35614ac0626</uuid>
<name>Get OLT Stats</name>
<type>HTTP_AGENT</type>
<key>olt.api.stats</key>
<delay>3m</delay>
<history>1h</history>
<value_type>TEXT</value_type>
<timeout>90s</timeout>
<url>http://127.0.0.1:5050/api/v1/olt_stats</url>
<query_fields>
<query_field>
<name>host</name>
<value>{HOST.IP}</value>
</query_field>
<query_field>
<name>driver</name>
<value>nokia</value>
</query_field>
</query_fields>
</item>
</items>
<discovery_rules>
<discovery_rule>
<uuid>7a978a056189440f8bc3252788ce5034</uuid>
<name>Discovery Card de Gerencia</name>
<type>DEPENDENT</type>
<key>olt.mgmt.card.discovery</key>
<item_prototypes>
<item_prototype>
<uuid>69b6137ee1f1498eb495ca9c14e0c9f1</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Utilização de Memória %</name>
<type>CALCULATED</type>
<key>mng.avgUsage[{#CARD_INDEX}]</key>
<delay>2m</delay>
<value_type>FLOAT</value_type>
<units>%</units>
<params>(last(//mng.memAbsoluteUsage[{#CARD_INDEX}])*100)/last(//mng.totalMemSize[{#CARD_INDEX}])</params>
<tags>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>b460b66d4d8c47959388655797c47abb</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Utilização de CPU %</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.9.29.1.1.4.{#CARD_INDEX}</snmp_oid>
<key>mng.cpuLoadAverage[{#CARD_INDEX}]</key>
<delay>2m</delay>
<value_type>FLOAT</value_type>
<units>%</units>
<tags>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>8f8f575a9ca24fd88519fe42a6e436b8</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Status Administrativo</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.3.1.5.{#CARD_INDEX}</snmp_oid>
<key>mng.eqptBoardAdminStatus[{#CARD_INDEX}]</key>
<delay>2m</delay>
<valuemap>
<name>CARD: Status Administrativo</name>
</valuemap>
<tags>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>6effd55c130a47a2bf9ff51e1aaf7f37</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Código PBA</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.3.1.15.{#CARD_INDEX}</snmp_oid>
<key>mng.eqptBoardInventoryPBACode[{#CARD_INDEX}]</key>
<delay>1d</delay>
<value_type>CHAR</value_type>
<tags>
<tag>
<tag>application</tag>
<value>inventory</value>
</tag>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>538be380bc604f44b4e25f8a2eb2e7ba</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: SN do Card</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.3.1.19.{#CARD_INDEX}</snmp_oid>
<key>mng.eqptBoardInventorySerialNumber[{#CARD_INDEX}]</key>
<delay>1d</delay>
<value_type>CHAR</value_type>
<tags>
<tag>
<tag>application</tag>
<value>inventory</value>
</tag>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>8a2b29a22fce4f27817e3b39b741c973</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Status Operacional do Card</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.3.1.6.{#CARD_INDEX}</snmp_oid>
<key>mng.eqptBoardOperStatus[{#CARD_INDEX}]</key>
<delay>2m</delay>
<valuemap>
<name>CARD: Status Operacional</name>
</valuemap>
<tags>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>3bb2deae12174e56ab4a91f9620028bb</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME} / Sensor {#SENSOR_INDEX}: Temperatura Atual</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.10.1.2.{#CARD_INDEX}.{#SENSOR_INDEX}</snmp_oid>
<key>mng.eqptBoardThermalSensorActualTemperature[{#CARD_INDEX}.{#SENSOR_INDEX}]</key>
<delay>2m</delay>
<units>°C</units>
<tags>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>4f7a5fecd1554ea0baa33ee04ba9ef39</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Status Energético do Card</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.3.1.4.{#CARD_INDEX}</snmp_oid>
<key>mng.eqptSlotPowerStatus[{#CARD_INDEX}]</key>
<delay>2m</delay>
<valuemap>
<name>CARD: Power Status</name>
</valuemap>
<tags>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>279d9f528e6e4c22a2947530ec4df837</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Utilização de Memória</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.9.29.2.1.2.{#CARD_INDEX}</snmp_oid>
<key>mng.memAbsoluteUsage[{#CARD_INDEX}]</key>
<delay>2m</delay>
<units>!MB</units>
<tags>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>79604b14350941cf9cc896cc22648ef1</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Status de Operação da CPU</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.9.29.1.1.5.{#CARD_INDEX}</snmp_oid>
<key>mng.operateStatus[{#CARD_INDEX}]</key>
<delay>10m</delay>
<valuemap>
<name>CARD: Status CPU</name>
</valuemap>
<tags>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>0bc920aa3f95405a86f6bfd5fb2b2ee8</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Total de Memória</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.9.29.2.1.1.{#CARD_INDEX}</snmp_oid>
<key>mng.totalMemSize[{#CARD_INDEX}]</key>
<delay>2m</delay>
<units>!MB</units>
<tags>
<tag>
<tag>Card</tag>
<value>MGMT</value>
</tag>
</tags>
</item_prototype>
</item_prototypes>
<master_item>
<key>olt.api.stats</key>
</master_item>
<preprocessing>
<step>
<type>JAVASCRIPT</type>
<parameters>
<parameter>var data = JSON.parse(value);
var output = [];
if (data.FANT) {
data.FANT.forEach(function (card) {
output.push({
&quot;{#CARD_INDEX}&quot;: card.cardIndex,
&quot;{#CARD_NAME}&quot;: card.cardName,
&quot;{#CARD_NUMBER}&quot;: card.cardNumber,
&quot;{#CARD_TYPE}&quot;: card.cardType,
&quot;{#CARD_CLASS}&quot;: card.cardClass
});
});
}
return JSON.stringify(output);</parameter>
</parameters>
</step>
</preprocessing>
</discovery_rule>
<discovery_rule>
<uuid>846ecdc97b7e45a9a62142aed73703d9</uuid>
<name>Discovery PONs</name>
<type>DEPENDENT</type>
<key>olt.pon.discovery</key>
<item_prototypes>
<item_prototype>
<uuid>a2702a688be54c88b8a3c3dc057b312f</uuid>
<name>{#PON_NAME} Status Operacional</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.2.1.2.2.1.8.{#PON_CODE}</snmp_oid>
<key>ifOperStatus[{#PON_CODE}]</key>
<value_type>CHAR</value_type>
<valuemap>
<name>PON: Status Operacional</name>
</valuemap>
<tags>
<tag>
<tag>GPON Port</tag>
<value>{#PON_NAME}</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>0b8b225f12c142a3b4001e5e48522b45</uuid>
<name>{#PON_NAME} ONTs Offline</name>
<type>DEPENDENT</type>
<key>olt.pon.onts.offline[{#PON_CODE}]</key>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.FGLT[*].pons[?(@.ponCode=='{#PON_CODE}')].onuStats.down.first()</parameter>
</parameters>
</step>
</preprocessing>
<master_item>
<key>olt.api.stats</key>
</master_item>
<tags>
<tag>
<tag>GPON Port</tag>
<value>{#PON_NAME}</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>d665247611d44c0d99320118b0a3788f</uuid>
<name>{#PON_NAME} ONTs Online</name>
<type>DEPENDENT</type>
<key>olt.pon.onts.online[{#PON_CODE}]</key>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.FGLT[*].pons[?(@.ponCode=='{#PON_CODE}')].onuStats.up.first()</parameter>
</parameters>
</step>
</preprocessing>
<master_item>
<key>olt.api.stats</key>
</master_item>
<tags>
<tag>
<tag>GPON Port</tag>
<value>{#PON_NAME}</value>
</tag>
</tags>
<trigger_prototypes>
<trigger_prototype>
<uuid>ddc136525d274ef4b88d7e45f5c1cac7</uuid>
<expression>((last(/IPv0 - Template Olt Nokia API/olt.pon.onts.online[{#PON_CODE}],#2)+0.1)
- last(/IPv0 - Template Olt Nokia API/olt.pon.onts.online[{#PON_CODE}]))
/
(last(/IPv0 - Template Olt Nokia API/olt.pon.onts.online[{#PON_CODE}],#2)+0.1)*100&gt;={$LIMITONTOFF}
and
last(/IPv0 - Template Olt Nokia API/olt.pon.onts.online[{#PON_CODE}])&lt;&gt;0</expression>
<recovery_mode>RECOVERY_EXPRESSION</recovery_mode>
<recovery_expression>((last(/IPv0 - Template Olt Nokia API/olt.pon.onts.online[{#PON_CODE}],#2)+0.1)
- last(/IPv0 - Template Olt Nokia API/olt.pon.onts.online[{#PON_CODE}]))
/
(last(/IPv0 - Template Olt Nokia API/olt.pon.onts.online[{#PON_CODE}],#2)+0.1)*100&lt;={$LIMITONTOFF}
and
last(/IPv0 - Template Olt Nokia API/olt.pon.onts.online[{#PON_CODE}])&gt;=
avg(/IPv0 - Template Olt Nokia API/olt.pon.onts.online[{#PON_CODE}],12h)</recovery_expression>
<name>Queda Massiva de ONUs {#PON_INDEX}</name>
<priority>HIGH</priority>
<manual_close>YES</manual_close>
</trigger_prototype>
</trigger_prototypes>
</item_prototype>
<item_prototype>
<uuid>7362cd3b3c814d6992928cd2e971fb6b</uuid>
<name>{#PON_NAME} ONTs Total</name>
<type>DEPENDENT</type>
<key>olt.pon.onts.total[{#PON_CODE}]</key>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.FGLT..[?(@.ponCode=='{#PON_CODE}')].onuStats.total.first()</parameter>
</parameters>
</step>
</preprocessing>
<master_item>
<key>olt.api.stats</key>
</master_item>
<tags>
<tag>
<tag>GPON Port</tag>
<value>{#PON_NAME}</value>
</tag>
</tags>
<trigger_prototypes>
<trigger_prototype>
<uuid>d0c4dee94181467685df65cfcc3a77a5</uuid>
<expression>last(/IPv0 - Template Olt Nokia API/olt.pon.onts.total[{#PON_CODE}])&gt;{$ALERTLIMMITONTS}</expression>
<recovery_mode>RECOVERY_EXPRESSION</recovery_mode>
<recovery_expression>last(/IPv0 - Template Olt Nokia API/olt.pon.onts.total[{#PON_CODE}])&lt;{$ALERTLIMMITONTS}</recovery_expression>
<name>Limite de ONTs</name>
<priority>AVERAGE</priority>
<manual_close>YES</manual_close>
</trigger_prototype>
</trigger_prototypes>
</item_prototype>
<item_prototype>
<uuid>5148cc790e694fc183aec93c3e2ab2bd</uuid>
<name>Interface GPON {#PON_INDEX} Operational status</name>
<type>DEPENDENT</type>
<key>olt.pon.status.oper[{#PON_INDEX}]</key>
<value_type>TEXT</value_type>
<status>DISABLED</status>
<discover>NO_DISCOVER</discover>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.data[?(@.pon_index=='{#PON_INDEX}')].oper_status.first()</parameter>
</parameters>
</step>
</preprocessing>
<master_item>
<key>olt.api.stats</key>
</master_item>
<tags>
<tag>
<tag>GPON Port</tag>
<value>{#PON_NAME}</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>929d97d69e7d42618bfb063368f2937b</uuid>
<name>{#PON_NAME}: Voltagem</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.56.5.1.9.{#CARD_INDEX}.{#PON_INDEX}</snmp_oid>
<key>sfpDiagSupplyVoltage[{#CARD_INDEX}.{#PON_INDEX}]</key>
<delay>2m</delay>
<value_type>FLOAT</value_type>
<units>V</units>
<preprocessing>
<step>
<type>TRIM</type>
<parameters>
<parameter>&quot;</parameter>
</parameters>
</step>
<step>
<type>TRIM</type>
<parameters>
<parameter>VDC</parameter>
</parameters>
</step>
</preprocessing>
<tags>
<tag>
<tag>GPON Port</tag>
<value>{#PON_NAME}</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>83148e945c884ce99713c43806af6283</uuid>
<name>{#PON_NAME}: Temperatura</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.56.5.1.10.{#CARD_INDEX}.{#PON_INDEX}</snmp_oid>
<key>sfpDiagTemperature[{#CARD_INDEX}.{#PON_INDEX}]</key>
<delay>2m</delay>
<value_type>FLOAT</value_type>
<units>ºC</units>
<preprocessing>
<step>
<type>TRIM</type>
<parameters>
<parameter>&quot;</parameter>
</parameters>
</step>
<step>
<type>TRIM</type>
<parameters>
<parameter>degrees Celsius</parameter>
</parameters>
</step>
</preprocessing>
<tags>
<tag>
<tag>GPON Port</tag>
<value>{#PON_NAME}</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>256a6c2404ef4d759c75c1d5f4104b59</uuid>
<name>{#PON_NAME}: Corrente Elétrica</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.56.5.1.8.{#CARD_INDEX}.{#PON_INDEX}</snmp_oid>
<key>sfpDiagTxBiasCurrent[{#CARD_INDEX}.{#PON_INDEX}]</key>
<delay>2m</delay>
<value_type>FLOAT</value_type>
<units>!mA</units>
<preprocessing>
<step>
<type>TRIM</type>
<parameters>
<parameter>&quot;</parameter>
</parameters>
</step>
<step>
<type>TRIM</type>
<parameters>
<parameter>mA</parameter>
</parameters>
</step>
</preprocessing>
<tags>
<tag>
<tag>GPON Port</tag>
<value>{#PON_NAME}</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>3da4a5d55323446ea0b6ada7676c5597</uuid>
<name>{#PON_NAME}: Tx Power</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.56.5.1.6.{#CARD_INDEX}.{#PON_INDEX}</snmp_oid>
<key>sfpDiagTxPower[{#CARD_INDEX}.{#PON_INDEX}]</key>
<delay>2m</delay>
<value_type>FLOAT</value_type>
<units>dBm</units>
<preprocessing>
<step>
<type>TRIM</type>
<parameters>
<parameter>&quot;</parameter>
</parameters>
</step>
<step>
<type>TRIM</type>
<parameters>
<parameter>dBm</parameter>
</parameters>
</step>
</preprocessing>
<tags>
<tag>
<tag>GPON Port</tag>
<value>{#PON_NAME}</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>70e2452a22da41d6b4229d6c575c1999</uuid>
<name>{#PON_NAME}: Tipo do SFP</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.56.6.1.13.{#CARD_INDEX}.{#PON_INDEX}</snmp_oid>
<key>sfpInvSpecificalType[{#CARD_INDEX}.{#PON_INDEX}]</key>
<delay>2m</delay>
<valuemap>
<name>PON: SFP Type</name>
</valuemap>
<tags>
<tag>
<tag>GPON Port</tag>
<value>{#PON_NAME}</value>
</tag>
</tags>
</item_prototype>
</item_prototypes>
<master_item>
<key>olt.api.stats</key>
</master_item>
<preprocessing>
<step>
<type>JAVASCRIPT</type>
<parameters>
<parameter>var data = JSON.parse(value);
var output = [];
if (data.FGLT) {
data.FGLT.forEach(function(card) {
if (card.pons) {
card.pons.forEach(function(pon) {
output.push({
&quot;{#CARD_INDEX}&quot;: card.cardIndex,
&quot;{#PON_INDEX}&quot;: pon.ponIndex,
&quot;{#PON_CODE}&quot;: pon.ponCode,
&quot;{#PON_NAME}&quot;: pon.ponName
});
});
}
});
}
return JSON.stringify(output);</parameter>
</parameters>
</step>
</preprocessing>
</discovery_rule>
<discovery_rule>
<uuid>de352b4307a24dc3b7a7ba228224663d</uuid>
<name>Discovery Card de Serviço</name>
<type>DEPENDENT</type>
<key>olt.service.card.discovery</key>
<item_prototypes>
<item_prototype>
<uuid>bb009bd92e18473ebbcc4daea67577c6</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Utilização de Memória %</name>
<type>CALCULATED</type>
<key>srv.avgUsage[{#CARD_INDEX}]</key>
<delay>2m</delay>
<value_type>FLOAT</value_type>
<units>%</units>
<params>(last(//srv.memAbsoluteUsage[{#CARD_INDEX}])*100)/last(//srv.totalMemSize[{#CARD_INDEX}])</params>
<preprocessing>
<step>
<type>JAVASCRIPT</type>
<parameters>
<parameter>return Math.round(value * 100)/100;</parameter>
</parameters>
</step>
</preprocessing>
<tags>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>bf0fc61688d24828a22a23437f0baf26</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Utilização de CPU %</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.9.29.1.1.4.{#CARD_INDEX}</snmp_oid>
<key>srv.cpuLoadAverage[{#CARD_INDEX}]</key>
<delay>2m</delay>
<units>%</units>
<tags>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>b4ffa90bd79744669c4b1acbeefb8329</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Status Administrativo</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.3.1.5.{#CARD_INDEX}</snmp_oid>
<key>srv.eqptBoardAdminStatus[{#CARD_INDEX}]</key>
<delay>2m</delay>
<valuemap>
<name>CARD: Status Administrativo</name>
</valuemap>
<tags>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>4aa8a84284a4439b996183274d01f8b8</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Código PBA</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.3.1.15.{#CARD_INDEX}</snmp_oid>
<key>srv.eqptBoardInventoryPBACode[{#CARD_INDEX}]</key>
<delay>1d</delay>
<value_type>CHAR</value_type>
<tags>
<tag>
<tag>application</tag>
<value>inventory</value>
</tag>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>c0f2e77e0c594edebd4720ae608b8311</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: SN do Card</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.3.1.19.{#CARD_INDEX}</snmp_oid>
<key>srv.eqptBoardInventorySerialNumber[{#CARD_INDEX}]</key>
<delay>1d</delay>
<value_type>CHAR</value_type>
<tags>
<tag>
<tag>application</tag>
<value>inventory</value>
</tag>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>0acd1bc1e82441bfa195ec8b97d0c034</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Status Operacional do Card</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.3.1.6.{#CARD_INDEX}</snmp_oid>
<key>srv.eqptBoardOperStatus[{#CARD_INDEX}]</key>
<delay>2m</delay>
<valuemap>
<name>CARD: Status Operacional</name>
</valuemap>
<tags>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>d955f5a1d1324e7baf380f63e4a1b6b3</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME} / Sensor {#SENSOR_INDEX}: Temperatura Atual</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.10.1.2.{#CARD_INDEX}.{#SENSOR_INDEX}</snmp_oid>
<key>srv.eqptBoardThermalSensorActualTemperature[{#CARD_INDEX}.{#SENSOR_INDEX}]</key>
<delay>2m</delay>
<value_type>FLOAT</value_type>
<units>ºC</units>
<tags>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>372b8ece709443f99d076ccd1b6927cf</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Status Energético do Card</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.23.3.1.4.{#CARD_INDEX}</snmp_oid>
<key>srv.eqptSlotPowerStatus[{#CARD_INDEX}]</key>
<delay>2m</delay>
<valuemap>
<name>CARD: Power Status</name>
</valuemap>
<tags>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>039582f21ece4b5b9ede608fdcaf769e</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Utilização de Memória</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.9.29.2.1.2.{#CARD_INDEX}</snmp_oid>
<key>srv.memAbsoluteUsage[{#CARD_INDEX}]</key>
<delay>2m</delay>
<value_type>FLOAT</value_type>
<units>!MB</units>
<tags>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>2a206cc0b25e451285a37057a606b348</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Status de Operação da CPU</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.9.29.1.1.5.{#CARD_INDEX}</snmp_oid>
<key>srv.operateStatus[{#CARD_INDEX}]</key>
<delay>10m</delay>
<value_type>FLOAT</value_type>
<valuemap>
<name>CARD: Status CPU</name>
</valuemap>
<tags>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
<item_prototype>
<uuid>76599aec4242436685dfc13209792312</uuid>
<name>{#CARD_TYPE}-{#CARD_CLASS} / {#CARD_NAME}: Total de Memória</name>
<type>SNMP_AGENT</type>
<snmp_oid>1.3.6.1.4.1.637.61.1.9.29.2.1.1.{#CARD_INDEX}</snmp_oid>
<key>srv.totalMemSize[{#CARD_INDEX}]</key>
<delay>2m</delay>
<value_type>FLOAT</value_type>
<units>!MB</units>
<tags>
<tag>
<tag>Card</tag>
<value>Service</value>
</tag>
</tags>
</item_prototype>
</item_prototypes>
<master_item>
<key>olt.api.stats</key>
</master_item>
<preprocessing>
<step>
<type>JAVASCRIPT</type>
<parameters>
<parameter>var data = JSON.parse(value);
var output = [];
if (data.FGLT) {
data.FGLT.forEach(function (card) {
output.push({
&quot;{#CARD_CLASS}&quot;: card.cardClass,
&quot;{#CARD_INDEX}&quot;: card.cardIndex,
&quot;{#CARD_NAME}&quot;: card.cardName,
&quot;{#CARD_NUMBER}&quot;: card.cardNumber,
&quot;{#CARD_TYPE}&quot;: card.cardType
});
});
}
return JSON.stringify(output);</parameter>
</parameters>
</step>
</preprocessing>
</discovery_rule>
</discovery_rules>
<tags>
<tag>
<tag>ipv0</tag>
</tag>
<tag>
<tag>target</tag>
<value>nokia</value>
</tag>
</tags>
<macros>
<macro>
<macro>{$ALERTLIMMITONTS}</macro>
<value>120</value>
</macro>
<macro>
<macro>{$LIMITONTOFF}</macro>
<value>20</value>
<description>Valor em %</description>
</macro>
</macros>
<valuemaps>
<valuemap>
<uuid>f3ba4a6f09994b2fa4753ca09cfec605</uuid>
<name>CARD: Power Status</name>
<mappings>
<mapping>
<value>1</value>
<newvalue>powerUp</newvalue>
</mapping>
<mapping>
<value>2</value>
<newvalue>powerDown</newvalue>
</mapping>
</mappings>
</valuemap>
<valuemap>
<uuid>ca194f4197714aa58de43a4088308a30</uuid>
<name>CARD: Status Administrativo</name>
<mappings>
<mapping>
<value>1</value>
<newvalue>Unlock</newvalue>
</mapping>
<mapping>
<value>2</value>
<newvalue>Lock</newvalue>
</mapping>
</mappings>
</valuemap>
<valuemap>
<uuid>f36bdc78e7f341c8a3421d8a00e930f8</uuid>
<name>CARD: Status CPU</name>
<mappings>
<mapping>
<value>1</value>
<newvalue>start</newvalue>
</mapping>
<mapping>
<value>2</value>
<newvalue>stop</newvalue>
</mapping>
<mapping>
<value>3</value>
<newvalue>proceeding</newvalue>
</mapping>
<mapping>
<value>4</value>
<newvalue>idle</newvalue>
</mapping>
<mapping>
<value>5</value>
<newvalue>not-operational</newvalue>
</mapping>
</mappings>
</valuemap>
<valuemap>
<uuid>9cd3f98309994cd1be946cd7d2819d4a</uuid>
<name>CARD: Status Operacional</name>
<mappings>
<mapping>
<value>1</value>
<newvalue>Enabled</newvalue>
</mapping>
<mapping>
<value>2</value>
<newvalue>Disabled</newvalue>
</mapping>
</mappings>
</valuemap>
<valuemap>
<uuid>987074d3178f4941ad79d06d4cff5e3f</uuid>
<name>PON: SFP Type</name>
<mappings>
<mapping>
<value>0</value>
<newvalue>Desconhecido</newvalue>
</mapping>
<mapping>
<value>7</value>
<newvalue>B</newvalue>
</mapping>
<mapping>
<value>8</value>
<newvalue>B+</newvalue>
</mapping>
<mapping>
<value>9</value>
<newvalue>C+</newvalue>
</mapping>
<mapping>
<value>10</value>
<newvalue>C++</newvalue>
</mapping>
</mappings>
</valuemap>
<valuemap>
<uuid>2546518a57394c75a2cc34f2b4f18517</uuid>
<name>PON: Status Operacional</name>
<mappings>
<mapping>
<value>1</value>
<newvalue>Up</newvalue>
</mapping>
<mapping>
<value>2</value>
<newvalue>Down</newvalue>
</mapping>
</mappings>
</valuemap>
</valuemaps>
</template>
</templates>
</zabbix_export>

120
doc/zabbix_integration.md Normal file
View File

@@ -0,0 +1,120 @@
# Guia Técnico de Integração Zabbix
Este documento descreve como construir templates Zabbix para consumir a **IPv0 OLT API**. Ele detalha os Headers, Regras de Descoberta (LLD), Pré-processamentos Java Script e estrutura dos itens.
## 1. Visão Geral (Master Item)
Toda a coleta de dados de uma OLT deve ser centralizada em um único **Master Item (HTTP Agent)**. Isso aproveita o cache da API e evita sobrecarga de conexões.
### Configuração do Master Item
* **Nome:** `OLT API Stats`
* **Type:** `HTTP agent`
* **Key:** `olt.api.stats`
* **URL:** `http://{$API_IP}:{$API_PORT}/api/v1/olt_stats`
* **Query Fields:**
* `host`: `{HOST.CONN}` ou `{HOST.IP}`
* **Headers:**
* `Content-Type`: `application/json`
* **Timeout:** `30s` (ou mais, dependendo do tamanho da OLT)
* **History:** `1h` (NÃO guarde histórico longo do JSON bruto se for muito grande)
* **Trends:** `0` (Texto não tem trend)
---
## 2. Padrão de Descoberta (Low Level Discovery - LLD)
A API retorna um JSON estruturado hierarquicamente (`NGFC`, `FGLT`, etc). Para criar itens no Zabbix, precisamos "achatar" essa estrutura usando **LLD Dependent Rules**.
### 2.1 LLD de Cards (Placas)
Para descobrir as placas (FGLT, FANT, etc):
* **Type:** `Dependent item`
* **Master Item:** `OLT API Stats`
* **Key:** `olt.card.discovery`
* **Preprocessing:**
1. **JavaScript**:
```javascript
var data = JSON.parse(value);
var output = [];
// Itera sobre tipos de cards conhecidos
['FGLT', 'FANT', 'NGFC'].forEach(function(type) {
if (data[type]) {
data[type].forEach(function(card) {
output.push({
"{#CARD_INDEX}": card.cardIndex,
"{#CARD_NAME}": card.cardName,
"{#CARD_TYPE}": card.cardType,
"{#CARD_SLOT}": card.cardSlot || card.cardNumber // Ajuste conforme driver
});
});
}
});
return JSON.stringify(output);
```
### 2.2 LLD de PONs (Portas)
Para descobrir as portas PON e criar métricas de suporte (Total/Online/Offline):
* **Type:** `Dependent item`
* **Master Item:** `OLT API Stats`
* **Key:** `olt.pon.discovery`
* **Preprocessing:**
1. **JavaScript**:
```javascript
var data = JSON.parse(value);
var output = [];
if (data.FGLT) {
data.FGLT.forEach(function(card) {
if (card.pons) {
card.pons.forEach(function(pon) {
output.push({
"{#PON_NAME}": pon.ponName,
"{#PON_INDEX}": pon.ponIndex,
"{#PON_CODE}": pon.ponCode, // Importante para SNMP
"{#CARD_INDEX}": card.cardIndex
});
});
}
});
}
return JSON.stringify(output);
```
---
## 3. Protótipos de Itens (Item Prototypes)
### 3.1 Métricas via JSON (Dependentes)
Métricas como "ONTs Online" vêm direto do JSON. Não use SNMP ou HTTP novo. Use **Dependent Item**.
* **Name:** `PON {#PON_NAME}: ONTs Online`
* **Type:** `Dependent item`
* **Master Item:** `OLT API Stats`
* **Key:** `pon.online[{#PON_INDEX}]`
* **Preprocessing:**
1. **JSONPath**:
```
$.FGLT[?(@.cardIndex=='{#CARD_INDEX}')].pons[?(@.ponIndex=='{#PON_INDEX}')].onuStats.up.first()
```
### 3.2 Métricas via SNMP (SNMP Agent)
Métricas físicas (Temperatura, Voltagem, Status Operacional) devem ser coletadas via SNMP direto da OLT, usando os índices descobertos.
* **Name:** `PON {#PON_NAME}: Temperatura`
* **Type:** `SNMP agent`
* **Key:** `pon.temp[{#PON_INDEX}]`
* **SNMP OID:**
* Exemplo Nokia: `1.3.6.1.4.1.637.61.1.56.5.1.10.{#CARD_INDEX}.{#PON_INDEX}`
* Exemplo Interface Genérica: `1.3.6.1.2.1.2.2.1.8.{#PON_CODE}`
---
## 4. Templates Disponíveis
Abaixo listamos os templates XML prontos para importação que seguem este padrão:
* **OLT Nokia**: [doc/templates/template-nokia-api.xml](doc/templates/template-nokia-api.xml)
* *Suporta: 7360 ISAM, Placas FGLT/FANT, Métricas de ONTs, Status Físico (SFP).*
---
**Nota:** Mantenha os templates versionados junto com o código da API para garantir compatibilidade com as estruturas JSON retornadas.

37
drivers/__init__.py Normal file
View File

@@ -0,0 +1,37 @@
from abc import ABC, abstractmethod
class OltDriver(ABC):
"""
Classe base abstrata para drivers de OLT.
Todos os drivers específicos devem herdar desta classe e implementar
seus métodos abstratos.
"""
def __init__(self, host, username, password, **kwargs):
self.host = host
self.username = username
self.password = password
self.options = kwargs
@abstractmethod
def connect(self):
"""
Estabelece a conexão com a OLT (SSH/Telnet).
Deve retornar o objeto de conexão (ex: Netmiko connection handler).
"""
pass
@abstractmethod
def get_olt_stats(self):
"""
Coleta estatísticas completas da OLT (Cards > PONs > ONTs).
Deve retornar um dicionário hierárquico:
{
"FGLT": [
{
"cardIndex": "...",
"pons": [...]
}
]
}
"""
pass

8
drivers/fiberhome.py Normal file
View File

@@ -0,0 +1,8 @@
from drivers import OltDriver
class FiberhomeDriver(OltDriver):
def connect(self):
raise NotImplementedError("Driver Fiberhome ainda não implementado.")
def get_olt_stats(self):
raise NotImplementedError("Driver Fiberhome ainda não implementado (v2.0).")

326
drivers/nokia.py Normal file
View File

@@ -0,0 +1,326 @@
from netmiko import ConnectHandler
from drivers import OltDriver
from utils.snmp import snmp_walk
from cachetools import TTLCache, cached
import re
import time
# Cache de estrutura (Cards/PONs) persistente por 1 hora
# Chave: IP do host
_structure_cache = TTLCache(maxsize=100, ttl=3600)
class NokiaDriver(OltDriver):
def connect(self):
device = {
'device_type': 'nokia_sros',
'host': self.host,
'username': self.username,
'password': self.password,
}
# Merge options (ssh_options, etc)
if self.options:
# Tratamento especial para ssh_options
if 'ssh_options' in self.options:
device.update(self.options['ssh_options'])
# Adiciona o restante das opções direto no device, excluindo snmp_community
device.update({k: v for k, v in self.options.items() if k not in ['ssh_options', 'snmp_community']})
device.setdefault('global_delay_factor', 2)
return ConnectHandler(**device)
def get_olt_stats(self):
start_time = time.time()
print(f"[{self.host}] Starting collection...", flush=True)
# 1. Obter Estatísticas via CLI
cli_stats = self._get_cli_stats()
print(f"[{self.host}] CLI stats collected in {time.time() - start_time:.2f}s", flush=True)
t_snmp = time.time()
# 2. Obter Estrutura via SNMP (Descoberta de Hardware) - Agora com Cache de 1h
structure = self._get_snmp_structure()
if structure is None:
print(f"[{self.host}] CRITICAL: SNMP structure is None!", flush=True)
structure = {}
print(f"[{self.host}] SNMP structure keys: {list(structure.keys())} (Time: {time.time() - t_snmp:.2f}s)", flush=True)
# 3. Merge dos dados
merged = self._merge_data(structure, cli_stats)
if merged is None:
print(f"[{self.host}] CRITICAL: Merged data is None!", flush=True)
merged = {}
print(f"[{self.host}] Total execution time: {time.time() - start_time:.2f}s", flush=True)
return merged
def _get_cli_stats(self):
try:
# O comando 'match' falhou (invalid token).
# Revertendo para o método '?' que sabemos que funciona (bypassa paginação).
device = {
'device_type': 'nokia_sros',
'host': self.host,
'username': self.username,
'password': self.password,
}
if self.options and 'ssh_options' in self.options:
device.update(self.options['ssh_options'])
# Recriando conexão para garantir estado limpo (send_command_timing exige cuidado)
conn = ConnectHandler(**device)
cmd = "show interface port ?"
print(f"[{self.host}] Sending CLI cmd: {cmd}", flush=True)
output = conn.send_command_timing(cmd, last_read=2.0)
conn.disconnect()
print(f"[{self.host}] CLI output length: {len(output)} chars", flush=True)
parsed = self._parse_cli_output(output)
print(f"[{self.host}] Parsed clean ONTs count: {sum(len(v['onuStats']) for v in ([],) if False) or sum(len(x) for x in parsed.values()) if parsed else 0}", flush=True)
return parsed
except Exception as e:
print(f"CLI Error: {e}. Falling back...", flush=True)
return self._fallback_cli_stats()
def _fallback_cli_stats(self):
try:
device = {
'device_type': 'nokia_sros',
'host': self.host,
'username': self.username,
'password': self.password,
}
if self.options and 'ssh_options' in self.options:
device.update(self.options['ssh_options'])
conn = ConnectHandler(**device)
output = conn.send_command_timing("show interface port", last_read=2.0)
conn.disconnect()
return self._parse_cli_output(output)
except Exception as ex:
print(f"Fallback CLI Error: {ex}")
return {}
def _parse_cli_output(self, output):
stats = {}
# Regex ajustado para pegar apenas ONTs
regex_ont = re.compile(r'(ont:\d+/\d+/\d+/\d+/\d+)\s+(\S+)\s+(\S+)')
lines = output.splitlines()
for line in lines:
line = line.strip()
match_ont = regex_ont.search(line)
if match_ont:
ont_full = match_ont.group(1)
ont_id_full = ont_full.replace("ont:", "")
parts = ont_id_full.split("/")
if len(parts) > 1:
pon_index = "/".join(parts[:-1])
if pon_index not in stats:
stats[pon_index] = {"online_onts": 0, "offline_onts": 0, "total_onts": 0}
stats[pon_index]["total_onts"] += 1
oper = match_ont.group(3).lower()
if "up" in oper:
stats[pon_index]["online_onts"] += 1
else:
stats[pon_index]["offline_onts"] += 1
return stats
@cached(_structure_cache, key=lambda self: self.host)
def _get_snmp_structure(self):
community = self.options.get('snmp_community', 'public')
host = self.host
cards_oid = '1.3.6.1.4.1.637.61.1.23.3.1.3'
try:
card_results = snmp_walk(host, community, cards_oid)
except Exception as e:
print(f"SNMP Error (Cards): {e}")
return {}
structure = {}
seq_counters = {}
for oid, value in card_results:
card_index = oid.split('.')[-1]
raw_type = value.replace('"', '').strip()
if raw_type == 'EMPTY' or not raw_type:
continue
if '-' in raw_type:
card_type, card_class = raw_type.split('-', 1)
else:
card_type, card_class = raw_type, ''
if card_type not in seq_counters:
seq_counters[card_type] = 0
seq_counters[card_type] += 1
card_seq = seq_counters[card_type]
# Construção do objeto Card mantendo a ordem desejada das chaves
card_obj = {
"cardClass": card_class,
"cardIndex": card_index,
"cardName": f"CARD {card_seq}",
"cardNumber": card_seq,
"cardType": card_type,
"pons": []
}
if card_type == 'FGLT':
# Pega PONs (estrutura apenas)
pons = self._get_snmp_pons(host, community, card_index, card_seq)
card_obj['pons'] = pons
if card_type not in structure:
structure[card_type] = []
structure[card_type].append(card_obj)
return structure
def _get_snmp_pons(self, host, community, card_index, card_seq):
base_oid = f'1.3.6.1.4.1.637.61.1.56.5.1.3.{card_index}'
try:
pon_results = snmp_walk(host, community, base_oid)
except:
return []
pons = []
for oid, value in pon_results:
pon_index = oid.split('.')[-1]
pon_code = value
pon_name = f"1/1/{card_seq}/{pon_index}"
# Objeto PON provisório (será recriado no merge para ordem final)
pons.append({
"cardIndex": card_index, # Necessário para lógica interna, será removido no final
"ponIndex": pon_index,
"ponName": pon_name,
"ponCode": pon_code,
"onuStats": {
"up": "0", "down": "0", "total": "0"
}
})
return pons
def _merge_data(self, structure, cli_stats):
if not structure:
return structure
cli_slots_map = {}
for k, v in cli_stats.items():
parts = k.split('/')
if len(parts) >= 4:
slot = parts[2]
port = parts[3]
if slot not in cli_slots_map:
cli_slots_map[slot] = {}
cli_slots_map[slot][port] = v
sorted_cli_slots = sorted(cli_slots_map.keys(), key=lambda x: int(x) if x.isdigit() else x)
# 1. Ordenação Top-Level: NGFC, FANT, FGLT
ordered_structure = {}
for type_key in ["NGFC", "FANT", "FGLT"]:
if type_key in structure:
ordered_structure[type_key] = structure[type_key]
# Adiciona quaisquer outros tipos que não estejam na lista prioritária
for k, v in structure.items():
if k not in ordered_structure:
ordered_structure[k] = v
if 'FGLT' in ordered_structure:
fglt_cards = ordered_structure['FGLT']
fglt_cards.sort(key=lambda x: int(x['cardIndex']) if x['cardIndex'].isdigit() else x['cardIndex'])
print(f"DEBUG: Processing {len(fglt_cards)} FGLT cards. CLI Slots: {sorted_cli_slots}")
for i, card in enumerate(fglt_cards):
real_slot = "0"
slot_stats = {}
if i < len(sorted_cli_slots):
real_slot = sorted_cli_slots[i]
if real_slot in cli_slots_map:
slot_stats = cli_slots_map[real_slot]
else:
# Fallback para cards sem CLI stats
# Empírico: Cards começam no slot 5 (ou 1?)
# card['cardNumber'] 1 -> Slot 5?
real_slot = str(card['cardNumber'] + 4)
print(f"DEBUG: Card {card['cardName']} (seq {card['cardNumber']}) mapped to Slot {real_slot}")
# Iterar sobre PONs
for j, pon in enumerate(card['pons']):
p_idx = pon['ponIndex']
# Valores default
up, down, total = "0", "0", "0"
if p_idx in slot_stats:
s = slot_stats[p_idx]
up = str(s['online_onts'])
down = str(s['offline_onts'])
total = str(s['total_onts'])
# CALCULAR PON CODE (IF-INDEX)
# Se real_slot for válido (não "0"), calculamos.
# Se for "0" (impossível com fallback acima), mantemos original.
final_code = pon['ponCode']
if real_slot != "0":
# PATCH: Correção para Slot 6 (FGLT) que mapeia para 0x07...
# Parece haver um offset de +1 a partir do Slot 6 (ou específico dele)
calc_slot = real_slot
if str(real_slot) == "6":
calc_slot = "7"
print(f"DEBUG: Slot 6 detected. Applying correction -> 7 for ifIndex calc.", flush=True)
final_code = self._calculate_if_index(calc_slot, p_idx)
# Debug para comparar valor SNMP original vs Calculado
if pon['ponCode'] and pon['ponCode'] != final_code:
print(f"DEBUG: Code Mismatch for {pon['ponName']}! SNMP={pon['ponCode']} Calc={final_code}", flush=True)
new_pon_obj = {
"ponCode": final_code,
"ponIndex": p_idx,
"ponName": f"PON 1/1/{real_slot}/{p_idx}",
"onuStats": {
"down": down,
"total": total,
"up": up
}
}
card['pons'][j] = new_pon_obj
return ordered_structure
def _calculate_if_index(self, slot, port):
"""
Calcula o if-index baseado no Slot e Porta.
Engenharia reversa:
Slot 5, Port 1 -> 94371840 (0x05A00000)
Slot 6, Port 1 -> 127926272 (0x07A00000) -> Fix: Usar Slot 7 calc
Fórmula: (Slot << 24) + ((159 + Port) << 16)
"""
try:
s = int(slot)
p = int(port)
return str((s << 24) + ((159 + p) << 16))
except:
return "0"

13
hosts.example.json Normal file
View File

@@ -0,0 +1,13 @@
{
"10.0.0.1": {
"username": "admin",
"password": "change_me",
"driver": "nokia",
"port": 22,
"ssh_options": {
"disabled_algorithms": {
"pubkeys": ["rsa-sha2-256", "rsa-sha2-512"]
}
}
}
}

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
Flask==3.0.3
netmiko==4.3.0
cachetools==5.3.3
python-dotenv==1.0.1
pysnmp
pyarmor

42
tools/build.sh Executable file
View File

@@ -0,0 +1,42 @@
# Definir diretórios (Relativos à raiz do projeto, assumindo execução via ./tools/build.sh)
# Mas vamos garantir que o script rode da raiz
cd "$(dirname "$0")/.."
echo "[BUILD] Limpando builds anteriores..."
rm -rf dist build release_production.zip
# Definir diretórios
STAGE_DIR="dist/ipv0-olt-api"
RELEASE_DIR="dist/release"
mkdir -p $STAGE_DIR
mkdir -p $RELEASE_DIR
echo "[BUILD] Iniciando Obfuscação Pyarmor (Modo Full)..."
# Usando Pyarmor para proteger todo o código fonte
# O comando gen irá usar a configuração existente em .pyarmor se houver, ou criar uma nova.
# --outer: Permite usar chave de licença externa (license.key via outer_keyname=license.key)
./venv/bin/pyarmor gen --outer -O $STAGE_DIR app.py drivers/ utils/ config.py
echo "[BUILD] Copiando arquivos estáticos..."
cp hosts.json $STAGE_DIR/
cp requirements.txt $STAGE_DIR/
cp README.md $STAGE_DIR/
cp tools/debug.py $STAGE_DIR/
cp tools/service/install.sh $STAGE_DIR/
cp tools/service/ipv0-olt-api.service $STAGE_DIR/
chmod +x $STAGE_DIR/install.sh
echo "[BUILD] Criando arquivo ZIP (via Python)..."
# Usar Python para zipar o CONTEÚDO do diretório de staging para dentro do zip
# O zip final ficará em dist/release/ipv0-olt-api.zip
# Estando em dist/ipv0-olt-api, ../release aponta para dist/release
cd $STAGE_DIR
../../venv/bin/python3 -c "import shutil; shutil.make_archive('../release/ipv0-olt-api', 'zip', '.')"
cd ../..
echo "✅ Build Process Complete!"
echo "Artifacts:"
echo " - Staging: $STAGE_DIR"
echo " - Release: $RELEASE_DIR/ipv0-olt-api.zip"
ls -F $RELEASE_DIR/

51
tools/debug.py Normal file
View File

@@ -0,0 +1,51 @@
import os
import sys
import subprocess
import time
import urllib.request
import json
def print_header(msg):
print(f"\n{'='*40}\n {msg}\n{'='*40}")
import glob
def check_pyarmor_binding():
print_header("Hardware & License (Machine ID)")
try:
# Usar ferramenta oficial do Pyarmor CLI para HD Info
subprocess.run([sys.executable, "-m", "pyarmor.cli.hdinfo"], check=False)
except Exception as e:
print(f"Erro ao consultar HD Info: {e}")
def check_service_status():
print_header("Service Status (systemd)")
ret = subprocess.run(["systemctl", "status", "ipv0-olt-api", "--no-pager"], capture_output=False)
if ret.returncode != 0:
print("⚠️ Serviço parece estar parado ou com erro.")
def check_api_health():
print_header("API Health Check")
url = "http://localhost:5050/health"
try:
print(f"Connecting to {url}...")
with urllib.request.urlopen(url, timeout=5) as response:
if response.status == 200:
data = json.loads(response.read().decode())
print(f"✅ API Online! Status: {data}")
else:
print(f"❌ API retornou status code: {response.status}")
except Exception as e:
print(f"❌ Falha na conexão com API: {e}")
print("Verifique se o serviço está rodando e se a licença é válida.")
if __name__ == "__main__":
print("IPv0 OLT API - Debug Tool (v3.1)")
check_service_status()
check_pyarmor_binding()
check_api_health()
print("\nLogs recentes:")
subprocess.run(["journalctl", "-u", "ipv0-olt-api", "-n", "10", "--no-pager"], check=False)

87
tools/gen_license.sh Executable file
View File

@@ -0,0 +1,87 @@
#!/bin/bash
# Ensure we are in project root
cd "$(dirname "$0")/.."
# Configuration
OUTPUT_BASE="dist/licenses"
PYARMOR="./venv/bin/pyarmor"
# Colors (only if terminal supports it, otherwise empty)
if [ -t 1 ]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
else
RED=''
GREEN=''
BLUE=''
NC=''
fi
echo "${BLUE}=== Gerador de Licenças IPv0 OLT API ===${NC}"
# 1. Solicitar Nome do Cliente
echo "Nome do Cliente (sem espaços, ex: ProvedorX): "
read CLIENT_NAME
if [ -z "$CLIENT_NAME" ]; then
echo "${RED}Erro: Nome do cliente é obrigatório.${NC}"
exit 1
fi
DEST_DIR="$OUTPUT_BASE/$CLIENT_NAME"
mkdir -p "$DEST_DIR"
# 2. Escolher Tipo de Licença
echo ""
echo "Tipo de Licença:"
echo "1) Data de Expiração (Trial/PoC)"
echo "2) Hardware (Produção/Machine ID)"
echo "Opção [1/2]: "
read OPTION
if [ "$OPTION" = "1" ]; then
# Licença por Data
echo "Data de Vencimento (YYYY-MM-DD): "
read EXPIRE_DATE
# Validação simples de formato YYYY-MM-DD
if ! echo "$EXPIRE_DATE" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then
echo "${RED}Erro: Formato de data inválido.${NC}"
exit 1
fi
echo "Gerando licença Trial para ${GREEN}$CLIENT_NAME${NC} até ${GREEN}$EXPIRE_DATE${NC}..."
$PYARMOR gen key -e "$EXPIRE_DATE"
elif [ "$OPTION" = "2" ]; then
# Licença por Hardware
echo "Machine ID do Cliente: "
read MACHINE_ID
if [ -z "$MACHINE_ID" ]; then
echo "${RED}Erro: Machine ID é obrigatório.${NC}"
exit 1
fi
echo "Gerando licença Permanente para ${GREEN}$CLIENT_NAME${NC} (ID: $MACHINE_ID)..."
$PYARMOR gen key --bind-device "$MACHINE_ID"
else
echo "${RED}Opção inválida.${NC}"
exit 1
fi
# 3. Mover e Verifica
# Por padrão, o pyarmor gera em dist/license.key (conforme config outer_keyname)
GENERATED_FILE="dist/license.key"
if [ -f "$GENERATED_FILE" ]; then
mv "$GENERATED_FILE" "$DEST_DIR/"
echo ""
echo "${GREEN}✅ Licença gerada com sucesso!${NC}"
echo "Arquivo: ${BLUE}$DEST_DIR/license.key${NC}"
echo "Envie este arquivo para o cliente."
else
echo "${RED}❌ Erro: O arquivo de licença não foi gerado.${NC}"
exit 1
fi

60
tools/service/install.sh Normal file
View File

@@ -0,0 +1,60 @@
#!/bin/bash
# IPv0 OLT API Installer
# Usage: sudo ./install.sh
if [ "$EUID" -ne 0 ]; then
echo "Please run as root (sudo ./install.sh)"
exit 1
fi
DEST_DIR="/opt/ipv0-olt-api"
echo "[INSTALL] Installing dependencies..."
apt-get update
apt-get install -y python3-venv python3-pip unzip
echo "[INSTALL] Setting up directory..."
mkdir -p $DEST_DIR
# Copiar arquivos do diretório atual para o destino (se não estiver lá)
if [ "$PWD" != "$DEST_DIR" ]; then
cp -r * $DEST_DIR/
fi
cd $DEST_DIR
echo "[INSTALL] Creating virtual environment..."
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
echo "[INSTALL] Configuring Service..."
cp ipv0-olt-api.service /etc/systemd/system/
systemctl daemon-reload
# Não habilitar/iniciar automaticamente, pois falta a licença
# systemctl enable ipv0-olt-api
# systemctl start ipv0-olt-api
echo ""
echo "[INSTALL] Dependencies installed!"
# Obter Machine ID de forma limpa
MACHINE_ID=$(./venv/bin/python3 -m pyarmor.cli.hdinfo 2>/dev/null | grep "Machine ID" | cut -d: -f2 | xargs)
# Cores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo ""
echo "Serviço 'ipv0-olt-api' configurado mas não iniciado - ${RED}PRECISA SER LICENCIADO${NC}."
echo ""
echo -e "Machine ID: ${YELLOW}${MACHINE_ID}${NC}"
echo ""
echo "NEXT STEPS:"
echo "1. Solicite sua licença."
echo "2. Copie a licença 'license.key' para /opt/ipv0-olt-api/"
echo "3. Inicialize o serviço: sudo systemctl enable --now ipv0-olt-api"
echo "4. Após licenciar e inicializar o serviço, verifique se a api está funcionando com:"
echo " curl http://localhost:5050/health"

View File

@@ -0,0 +1,14 @@
[Unit]
Description=IPv0 OLT API (Zabbix Integration)
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=/opt/ipv0-olt-api
ExecStart=/opt/ipv0-olt-api/venv/bin/python3 app.py
Restart=always
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target

27
utils/cache.py Normal file
View File

@@ -0,0 +1,27 @@
from cachetools import TTLCache, cached
from config import Config
from flask import request
# Criação do cache com TTL (Time To Live)
# O tamanho máximo e o tempo de vida são configurados via config.py
ttl_cache = TTLCache(maxsize=Config.CACHE_MAX_SIZE, ttl=Config.CACHE_TTL)
def get_cache_key(*args, **kwargs):
"""
Gera uma chave única para o cache baseada na URL completa da requisição.
Isso garante que query parameters diferentes (host, driver) gerem entradas diferentes.
"""
if request:
return request.url
return str(args) + str(kwargs)
def cache_response(func):
"""
Decorador wrapper para aplicar cache nas chamadas de função.
A chave do cache será baseada na URL da request.
"""
# A função wrapper precisa aceitar args/kwargs, mas a chave é gerada por get_cache_key
@cached(cache=ttl_cache, key=get_cache_key)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

41
utils/snmp.py Normal file
View File

@@ -0,0 +1,41 @@
import asyncio
from pysnmp.hlapi.v3arch.asyncio import *
async def _snmp_walk_async(host, community, oid):
results = []
# Configuração do Engine e dos dados de conexão
snmp_engine = SnmpEngine()
community_data = CommunityData(community, mpModel=1) # mpModel=1 para SNMPv2c
transport = await UdpTransportTarget.create((host, 161), timeout=2, retries=1)
context = ContextData()
# Realiza o Walk
iterator = walk_cmd(
snmp_engine,
community_data,
transport,
context,
ObjectType(ObjectIdentity(oid)),
lexicographicMode=False
)
async for errorIndication, errorStatus, errorIndex, varBinds in iterator:
if errorIndication:
print(f"SNMP Error: {errorIndication}")
break
elif errorStatus:
print(f"SNMP Error: {errorStatus.prettyPrint()}")
break
else:
for varBind in varBinds:
results.append((str(varBind[0]), str(varBind[1])))
snmp_engine.closeDispatcher()
return results
def snmp_walk(host, community, oid):
"""
Wrapper síncrono para o walk assíncrono.
"""
return asyncio.run(_snmp_walk_async(host, community, oid))