Netmiko com Python: automação de OLT Huawei via SSH

Netmiko é uma das bibliotecas Python mais usadas para conversar por SSH com equipamento de rede, e funciona com a OLT Huawei MA5800. O problema é que a OLT não se comporta como um roteador comum: pagina saída longa com ---- More, pergunta { <cr>||<K> } no meio do comando, muda o prompt a cada interface gpon e aceita poucas sessões ao mesmo tempo. Um script que ignora isso trava, devolve saída pela metade ou tira o acesso de quem está operando.
Eu mantenho um sistema próprio que conversa com OLTs MA5800 por SSH e Telnet, escrito direto sobre Paramiko. As lições deste guia saíram dele: preparar a sessão, não dividir a mesma conexão entre threads, respeitar o limite de VTY e começar só lendo. Para o lado do Netmiko, conferi o comportamento no código-fonte da versão 4.8.0, que é a referência dos exemplos abaixo.
Resposta rápida: Netmiko na OLT Huawei
- Instale com
pip install netmikonum ambiente virtual. A versão 4.8.0 pede Python 3.10 ou mais novo. - Use
device_type="huawei_olt"(ouhuawei_smartax, que é a mesma classe). Não usehuawei, que é para roteador e switch VRP. - Depois de conectar, rode
enable,scroll 512escreen-width 256para tirar a paginação e a quebra de linha. - Para comandos que mudam o prompt, como
interface gpon 0/1, passeexpect_string. - Uma sessão por OLT por thread, poucas threads e só comandos
displayaté o script provar que é confiável.
Instalar o Netmiko
Use um ambiente virtual, separado do Python do sistema. Num servidor Linux:
python3 -m venv .venv source .venv/bin/activate pip install netmiko python -c "import netmiko; print(netmiko.__version__)"
A última linha só confirma a versão instalada. O Netmiko traz junto o Paramiko, que faz o SSH, e o TextFSM com os templates do ntc-templates, que servem para outros fabricantes. Para a saída da OLT Huawei eu prefiro uma expressão regular própria, como você vai ver mais abaixo, porque assim sei exatamente o que está sendo lido.
Guarde usuário e senha da OLT em variáveis de ambiente ou num cofre de segredos, nunca no código. Os exemplos leem OLT_USER e OLT_PASS do ambiente.
Qual device_type usar na OLT Huawei
O device_type escolhe a classe que o Netmiko usa para preparar a sessão e reconhecer o prompt. No arquivo ssh_dispatcher.py da versão 4.8.0, estes são os nomes da Huawei que interessam aqui:
| device_type | Classe no Netmiko | Quando usar |
|---|---|---|
huawei_olt | HuaweiSmartAXSSH | OLT MA5800 e MA5600 por SSH. É o que este guia usa. |
huawei_smartax | HuaweiSmartAXSSH | Mesma classe, outro nome. |
huawei_olt_telnet | HuaweiSmartAXSSH (sobre Telnet) | Só se a OLT não tiver SSH habilitado. A senha viaja em texto puro. |
huawei_smartaxmmi | HuaweiSmartAXSSHMMI | Entra no modo MMI, feito para máquina. Teste em laboratório antes, porque a saída muda de formato. |
huawei / huawei_vrp | HuaweiSSH | Roteador e switch VRP. Errado para OLT: o preparo da sessão é outro. |
Quem pega um exemplo pronto de roteador e só troca o IP costuma deixar huawei. A conexão até abre, mas o preparo da sessão é o do VRP, e a OLT responde de um jeito que a classe não espera. O sintoma é o script parado até estourar o tempo.
O que o Netmiko faz quando conecta
Ao abrir a sessão com huawei_olt, a classe HuaweiSmartAXSSH faz quatro coisas antes de devolver o controle ao seu script:
- Se a OLT mostrar um aviso pós-login que pede
YES/NO, respondeyes. - Manda
undo smart, que desliga o pedido de{ <cr>||<K> }no fim dos comandos. Sem isso, cadadisplaycom parâmetros opcionais fica esperando um Enter a mais. - Manda
infoswitch cli OFFem modo enable, para a OLT não jogar mensagens de depuração no meio da saída. - Volta ao modo usuário e manda
scrollpara desligar a paginação.
No meu sistema, a sequência de preparo da MA5800 é esta, igual para SSH e Telnet:
enable scroll 512 screen-width 256 undo smart undo interactive config
A diferença que importa está em scroll 512 e screen-width 256. O scroll sem número, que o Netmiko manda, depende do comportamento padrão da versão do firmware. Com scroll 512 eu sei o que esperar. O screen-width 256 evita que linhas longas, como as da tabela de ONUs, quebrem no meio e estraguem o parser. O undo interactive desliga confirmações interativas, e o config no fim deixa a sessão pronta para comandos de interface.
Primeiro script: conectar e ler a placa
O primeiro teste com o Netmiko deve ser só leitura e numa OLT de laboratório, se você tiver uma:
import os
from netmiko import ConnectHandler
OLT = {
"device_type": "huawei_olt",
"host": "192.0.2.10",
"username": os.environ["OLT_USER"],
"password": os.environ["OLT_PASS"],
"conn_timeout": 15,
"session_log": "sessao-olt.log",
}
with ConnectHandler(**OLT) as conn:
print(conn.find_prompt())
conn.enable()
print(conn.send_command("display board 0"))ConnectHandler(**OLT)dentro dewith: a conexão é fechada no fim do bloco, mesmo se der erro. Na saída, a classe mandaquite respondeyà perguntaAre you sure to log out?.conn_timeout: tempo máximo para abrir o TCP. OLT atrás de link ruim pede mais que o padrão de 10 segundos.session_log: grava tudo o que passou pela sessão. O Netmiko troca a senha por asteriscos no arquivo, mas trate o log como dado sensível mesmo assim, porque ele tem a configuração da OLT.find_prompt(): mostra o prompt atual. Se aparecer com>, você está no modo usuário.enable()esend_command(): entram no modo privilegiado e rodam o comando, esperando o prompt voltar.
Se o display board 0 voltar com as placas, a conexão está certa. Outros comandos de leitura que valem para o primeiro teste estão nos comandos básicos da OLT Huawei MA5800.
Paginação e o —- More no meio da saída
A paginação é a causa mais comum de script travado em OLT Huawei. Sem desligar, a OLT para a cada tela com ---- More e espera uma tecla. O send_command do Netmiko espera o prompt, que não vem, e o script termina em ReadTimeout com metade da saída.
with ConnectHandler(**OLT) as conn:
conn.enable()
conn.send_command("scroll 512")
conn.send_command("screen-width 256")
conn.config_mode()
saida = conn.send_command("display ont info 0 all", read_timeout=180)
if "---- More" in saida:
raise RuntimeError("paginação ativa: confira o scroll")Três detalhes neste bloco. Primeiro, o scroll 512 e o screen-width 256 repetem o preparo que eu uso, para não depender do padrão do firmware. Segundo, o read_timeout=180: o padrão do send_command é de 10 segundos, pouco para listar todas as ONUs de uma OLT cheia. Terceiro, a checagem do ---- More na saída: se ele aparecer, alguma coisa no preparo falhou, e é melhor parar com erro do que gravar dado incompleto.
---- More, o código manda um espaço e continua lendo até a OLT ficar alguns segundos em silêncio. Funciona, mas é mais frágil do que desligar a paginação na origem.Modos, prompts e a interface gpon
O send_command descobre o prompt atual antes de mandar o comando e espera ele voltar depois. Quando o comando muda o prompt, como interface gpon 0/1 (o prompt passa a terminar em (config-if-gpon-0/1)#), o prompt esperado nunca aparece. A saída é dizer ao Netmiko o que esperar:
conn.config_mode()
conn.send_command("interface gpon 0/1", expect_string=r"\)#")
optico = conn.send_command("display ont optical-info 0 1")
conn.send_command("quit", expect_string=r"\)#")O expect_string=r"\)#" casa com qualquer prompt de modo de configuração, seja (config)# ou (config-if-gpon-0/1)#. O config_mode() já sabe lidar com o config da OLT, porque a classe espera )# depois dele. Dentro da interface, o display ont optical-info roda normalmente, e o quit volta para o modo config. Para entender o que ler nessa saída, veja o guia de diagnóstico de ONU com LOS.
Um erro comum é tentar rodar display ont info by-sn de dentro da interface gpon: a OLT responde Parameter error. Esse comando roda fora da interface. Por isso é bom o script saber em que modo está antes de cada comando.
Coletar display ont info 0 all e transformar em dados
O display ont info 0 all lista todas as ONUs provisionadas na OLT. É o comando que meu sistema usa para sincronizar o inventário quando a leitura por SNMP falha. Um trecho de saída, como exemplo e resumido:
-----------------------------------------------------------------------
F/S/P ONT SN Control Run Config Match
ID flag state state state
-----------------------------------------------------------------------
0/ 1/0 1 48575443A1B2C3D4 active online normal match
0/ 1/0 2 48575443A1B2C3E5 active offline initial initial
0/ 1/1 0 48575443A1B2C3F6 active online normal match
-----------------------------------------------------------------------| Coluna | Exemplo | O que significa |
|---|---|---|
| F/S/P | 0/ 1/0 | Frame, slot e porta. Repare no espaço depois da barra. |
| ONT ID | 1 | Número da ONU na porta PON. |
| SN | 48575443A1B2C3D4 | Serial em 16 caracteres hexadecimais. |
| Control flag | active | deactive é ONU desativada pela OLT. |
| Run state | online / offline | Estado atual. |
| Config state | normal | failed indica perfil que não foi aplicado. |
O parser abaixo lê só as linhas que começam com F/S/P e têm um serial de 16 caracteres hexadecimais. Essa exigência descarta o cabeçalho, as linhas de traços e a segunda tabela que a OLT imprime com as descrições das ONUs:
import re
LINHA = re.compile(
r"\s*(\d+)/\s*(\d+)/\s*(\d+)\s+(\d+)\s+([0-9A-Fa-f]{16})"
r"\s+(\S+)\s+(\S+)\s+(\S+)"
)
def parse_onts(raw):
onts = []
for linha in raw.splitlines():
m = LINHA.match(linha)
if not m:
continue
onts.append({
"fsp": f"{m[1]}/{m[2]}/{m[3]}",
"ont_id": int(m[4]),
"sn": m[5].upper(),
"control": m[6],
"run_state": m[7],
"config_state": m[8],
})
return ontsO \s* depois de cada barra existe por causa do 0/ 1/0: a OLT alinha o slot com um espaço. Sem isso, metade das linhas não casa. O resultado é uma lista de dicionários, pronta para virar CSV, ir para um banco ou alimentar um alerta de ONUs offline.
Se a ideia é acompanhar estado de ONU o tempo todo, SNMP pesa menos para a OLT do que abrir SSH a cada minuto. Veja como monitorar a OLT Huawei no Zabbix por SNMP. O Netmiko fica melhor para coleta sob demanda, auditoria e conferência de configuração.
Várias OLTs: uma sessão por thread e limite de VTY
Com uma OLT funciona, a vontade é rodar em todas ao mesmo tempo. Aqui moram os dois erros mais caros que já vi em automação de OLT.
O primeiro é dividir a mesma conexão entre threads. Meu sistema tinha uma sessão por OLT, e dois provisionamentos ao mesmo tempo escreviam no mesmo socket: os comandos de um entravam no meio dos comandos do outro. A correção foi amarrar cada sessão à thread que a pegou. Com o Netmiko vale a mesma regra: cada objeto ConnectHandler pertence a uma thread só.
O segundo é abrir sessões demais. A MA5800 aceita um número limitado de usuários conectados no VTY, e o seu script disputa essas vagas com a equipe do NOC. No meu sistema o teto é de três sessões por OLT, com fila de espera em vez de abrir mais uma.

import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from netmiko import (ConnectHandler, NetmikoAuthenticationException,
NetmikoTimeoutException, ReadTimeout)
OLTS = [f"192.0.2.{i}" for i in range(10, 15)]
BASE = {
"device_type": "huawei_olt",
"username": os.environ["OLT_USER"],
"password": os.environ["OLT_PASS"],
"conn_timeout": 15,
}
def coletar(host):
with ConnectHandler(**BASE, host=host) as conn:
conn.enable()
conn.config_mode()
raw = conn.send_command("display ont info 0 all",
read_timeout=180)
return host, parse_onts(raw)
FALHAS = (NetmikoTimeoutException, NetmikoAuthenticationException,
ReadTimeout)
with ThreadPoolExecutor(max_workers=4) as pool:
tarefas = {pool.submit(coletar, h): h for h in OLTS}
for t in as_completed(tarefas):
try:
host, onts = t.result()
offline = [o for o in onts if o["run_state"] == "offline"]
print(host, len(onts), "ONUs,", len(offline), "offline")
except FALHAS as e:
print(tarefas[t], "falhou:", type(e).__name__)ThreadPoolExecutor(max_workers=4): no máximo quatro OLTs ao mesmo tempo, uma sessão em cada. O resto espera na fila.coletar()abre, lê e fecha dentro da própria thread. Nenhuma conexão sai dela.FALHAS: as três exceções do Netmiko que mais aparecem. Timeout de conexão, senha errada eReadTimeoutde comando. Uma OLT fora do ar não derruba a coleta das outras.
A função parse_onts é a do bloco anterior. Num script de verdade, as duas ficam no mesmo arquivo ou num módulo importado.
Só leitura primeiro
Um laço com ont delete errado apaga clientes em segundos, em todas as OLTs da lista. Por isso, todo script novo começa só com comandos de consulta, e eu deixo isso garantido no código, não na boa vontade de quem roda:
PERMITIDOS = ("display ", "interface gpon ", "quit")
def rodar(conn, cmd, **kw):
if not cmd.startswith(PERMITIDOS):
raise ValueError(f"comando bloqueado: {cmd}")
return conn.send_command(cmd, **kw)A função rodar() recusa qualquer comando fora da lista. É uma trava simples, mas evita o pior caso: um comando de escrita montado por engano a partir de um dado mal lido. Quando chegar a hora de escrever na OLT, faça uma ONU por vez, leia o resultado antes do próximo passo e mantenha um modo de simulação que só imprime os comandos.
Para a configuração da OLT, o caminho mais seguro de começar a automatizar é o backup. O script de backup da OLT Huawei mostra a parte da OLT; o Netmiko pode ser a peça que dispara e confere esse processo.
Erros comuns e como resolver
| Sintoma | Causa provável | Correção |
|---|---|---|
ReadTimeout num display longo | Paginação ligada ou read_timeout curto | scroll 512 no preparo e read_timeout maior |
ReadTimeout logo após interface gpon | Prompt mudou e o Netmiko esperava o antigo | expect_string=r"\)#" |
NetmikoAuthenticationException | Usuário ou senha errados, ou usuário bloqueado na OLT | Teste o login à mão com o mesmo usuário |
NetmikoTimeoutException | Porta 22 fechada, ACL ou OLT fora do ar | Teste o SSH à mão a partir do mesmo servidor |
| Linhas cortadas no meio | Largura do terminal | screen-width 256 |
| Saída de um comando dentro de outro | Mesma conexão usada por duas threads | Uma conexão por thread |
| OLT recusa novas sessões | Vagas de VTY ocupadas | Menos workers e fechar sessões com with |
Quando o erro não é óbvio, abra o arquivo do session_log. Ele mostra exatamente o que a OLT respondeu, inclusive perguntas que o script não previa. Na maioria das vezes o problema aparece ali em poucos segundos. A documentação de cada parâmetro está no repositório oficial do Netmiko.
Perguntas frequentes
Qual device_type do Netmiko usar na OLT Huawei MA5800?
Use huawei_olt ou huawei_smartax, que apontam para a mesma classe, HuaweiSmartAXSSH. O huawei é para roteador e switch VRP e não prepara a sessão do jeito que a OLT precisa.
O Netmiko funciona por Telnet na OLT Huawei?
Funciona, com device_type="huawei_olt_telnet". Use só quando a OLT não tiver SSH, porque o Telnet manda a senha em texto puro pela rede.
Por que meu script trava no display ont info?
Quase sempre é a paginação. A OLT parou no ---- More esperando uma tecla e o Netmiko ficou esperando o prompt. Rode scroll 512 depois de conectar e aumente o read_timeout para OLTs com muitas ONUs.
Quantas sessões SSH posso abrir na mesma OLT?
Depende da configuração de VTY da sua OLT, e a equipe do NOC também usa essas vagas. Eu trabalho com no máximo três sessões automáticas por OLT e fila de espera para o resto.
Netmiko ou SNMP para monitorar ONUs?
SNMP para monitoramento contínuo, porque pesa menos na OLT e o Zabbix já sabe lidar. Netmiko para coletas sob demanda, auditoria e tudo o que só existe no CLI.
Conclusão
O Netmiko resolve a parte chata do SSH com a OLT Huawei: login, aviso pós-login, undo smart, prompt e saída limpa. O resto é com você: o device_type certo, scroll 512 e screen-width 256, expect_string quando o prompt muda, um parser que saiba do 0/ 1/0 e um pool que não divida sessão nem esgote o VTY.
Comece com um script que só lê, rode numa OLT por vez e guarde o session_log dos primeiros testes. Quando a leitura estiver confiável, a escrita vem com bem menos susto.
