Ir para o conteúdo

Blogue ·

Porque um 200 do Proxmox não é um resultado

O Proxmox responde a um pedido de clonagem com um UPID e segue. Como ler esse identificador, sondá-lo com honestidade e distinguir terminado de bem-sucedido.

Por Provibr

Envia-se um pedido POST de clonagem a um nó Proxmox. Recebe-se de volta um HTTP 200 e uma cadeia de caracteres que parece ruído de linha. Nesse instante é verdadeira exatamente uma coisa: o pedido foi aceite. Nenhum disco foi copiado, nenhuma máquina existe, e também nada falhou ainda.

Quase todas as chamadas que alteram alguma coisa na API do Proxmox funcionam assim, e é um desenho perfeitamente razoável para operações que demoram minutos. Só se torna um problema quando o código que chama trata o 200 como um desfecho, que é o comportamento por omissão de todos os clientes HTTP alguma vez escritos.

É assim que lemos essas tarefas, e estas foram as partes que se revelaram mais afiadas do que parecem.

A cadeia é um identificador, e é estruturada

O que regressa é um UPID: um identificador separado por dois pontos que dá nome a uma tarefa em execução. Transporta, entre outros campos, o nó em que a tarefa corre, o tipo de tarefa, aquilo sobre que opera, e o utilizador que a iniciou.

Três desses campos são estruturais.

O nó é aquele a quem se tem de pedir o estado. Uma tarefa pertence ao nó que a corre, e uma pergunta ao nível do cluster não é a mesma pergunta. Tire o nó do identificador em vez da variável que por acaso tinha à mão, porque os dois podem divergir e o caso em que divergem é precisamente aquele que não testou.

O tipo diz o que foi de facto iniciado, que nem sempre é aquilo que se pediu pelo nome. Uma cópia de segurança iniciada pelo ponto de acesso do nó regressa com um tipo de tarefa vzdump, e não com algo com forma de convidado.

O alvo é o identificador do convidado nas operações sobre convidados, e pode estar vazio. Isso não é corrupção. Uma tarefa pode legitimamente ser sobre um nó em vez de sobre um convidado, pelo que o código que interpreta este campo tem de devolver «nenhum convidado» em vez de rebentar ou de assumir zero.

O nosso interpretador é deliberadamente estreito naquilo que rejeita: exige o prefixo, exige pelo menos oito campos separados por dois pontos, e exige que o nó e o tipo não estejam vazios. Tudo o resto pode faltar, porque o formato tem de continuar a funcionar para tipos de tarefa que nunca vimos.

Terminado não é o mesmo que bem-sucedido

Pergunte-se a um nó o estado de uma tarefa e recebem-se dois campos que respondem a duas perguntas diferentes. Um diz se a tarefa ainda corre ou se parou. O outro, presente apenas depois de ela parar, diz OK ou transporta uma cadeia de erro.

Escrever isso como dois predicados separados em vez de um não é pedantismo. Um único isDone() que os confunda produz o defeito em que uma clonagem falhada é comunicada como uma clonagem terminada, e a máquina que não existe recebe uma linha na base de dados. Temos duas funções: uma pergunta se parou, a outra pergunta se parou e se o estado de saída é exatamente OK. Tudo o resto é uma falha, incluindo estados de saída que nunca vimos, porque um estado que não reconhecemos não é autorização para presumir sucesso.

Onde vive a tarefa faz parte da API, não é um pormenor

Foram as cópias de segurança que nos ensinaram esta. É natural esperar que uma operação sobre um convidado fique pendurada no convidado: algo debaixo do caminho da própria máquina virtual. Não existe tal ponto de acesso. Uma cópia é iniciada no nó, com o identificador do convidado como parâmetro.

Isso não é uma esquisitice a contornar. Muda aquilo que é preciso saber antes de começar: qual o nó, qual o armazenamento nesse nó, e se esse armazenamento pode sequer conter cópias. Um armazenamento que só aceita imagens de disco aceita alegremente o pedido e falha dentro da tarefa, o que chega ao cliente como uma mensagem de erro de um programa de que ele nunca ouviu falar. Por isso filtramos a lista de destinos por aquilo que o armazenamento declara poder conter, e deixamos de fora qualquer armazenamento que esteja listado mas não responda, antes de oferecer a alguém uma escolha.

A mesma regra ao contrário aplica-se à colocação de um disco. A mesma ideia, tipo de conteúdo oposto, e vale a pena guardá-los como dois filtros explícitos em vez de um único filtro esperto e partilhado.

A sondagem é um ritmo, não um intervalo

Enquanto a tarefa corre é preciso perguntar repetidamente. As escolhas ingénuas são ambas más: sondar depressa para sempre é martelar durante uma hora a API de gestão de outra pessoa, sondar devagar faz com que todas as operações rápidas pareçam lentas.

O nosso compromisso são três tentativas rápidas com um segundo de intervalo, e depois de três em três segundos. Arrancar uma máquina termina dentro da janela rápida e parece instantâneo. Uma cópia de uma hora custa um pedido de três em três segundos, o que não é nada.

O ciclo termina exatamente de três maneiras, e as três são explícitas: a tarefa comunica que parou, o prazo do comando passa, ou o comando inteiro é cancelado. Não há quarto ramo nem caminho nenhum em que o ciclo simplesmente continue, o que é uma propriedade que vale a pena arranjar de propósito em vez de esperar que aconteça. O que um prazo para e não para é assunto próprio; aqui basta dizer que o ciclo de sondagem tem de ter uma maneira de perder.

A maioria das tarefas não diz em que ponto vai

Esta é a parte que desilude toda a gente que está a construir uma barra de progresso. O estado da tarefa dá em execução ou parada. Não dá uma percentagem.

Algumas tarefas escrevem o progresso no seu registo, como texto. Uma clonagem emite linhas com uma contagem transferida e uma percentagem entre parênteses. Para essas, lemos a cauda do registo e ficamos com a última percentagem que lá encontrarmos. Extrair uma percentagem de uma linha de registo não é elegante, e é o único sítio onde o número existe.

Para tudo o resto mostramos uma estimativa, e aqui a escolha de desenho importa mais do que a fórmula. A nossa aproxima-se assintoticamente de noventa por cento a partir de uma duração esperada, pelo que a barra anda sempre e nunca chega ao fim. Uma barra que fica nos 100 por cento enquanto nada acontece é pior do que barra nenhuma, porque diz a quem está a ver que já terminou, e depois contradiz-se.

A duração esperada que alimenta essa curva é um parâmetro de forma, não um prazo. A nossa diz que uma cópia demora habitualmente cerca de dez minutos e um restauro cerca de vinte, com o raciocínio de que um restauro lê o arquivo e escreve os discos e por isso move sensivelmente o dobro dos bytes. Se uma cópia correr mais tempo do que a estimativa, não parte nada; a curva limita-se a ficar educadamente por baixo da linha.

O erro está no registo, não no estado

Quando uma tarefa falha, o estado de saída é muitas vezes quase inútil. «unable to create VM» é uma categoria, não uma causa. A causa está nas últimas linhas do registo da tarefa.

Por isso um relatório de falha é montado: o estado de saída, mais a cauda do registo com as linhas de progresso filtradas, limitado a um punhado de linhas e a algumas centenas de caracteres. Filtrar as linhas de progresso importa mais do que parece, porque uma clonagem que falha pode acabar com vinte linhas de percentagens de transferência e a razão verdadeira por cima delas.

Uma regra sobre esse texto, a que nos agarramos com rigor: é uma citação. É o que o hipervisor disse, e vai para os nossos registos e para o ecrã sem alterações. Não a traduzimos, não a arrumamos, e não a parafraseamos para uma coisa mais simpática. A frase à volta é nossa e é traduzida para quem lê; a parte citada fica exatamente como o outro lado a disse, porque é a única cadeia de toda a troca que um engenheiro de suporte consegue pesquisar.

Não dobre as suas palavras dentro da frase da máquina

A última lição é a que não esperávamos aprender com um sondador de tarefas.

O nosso ciclo de espera recebia uma etiqueta de texto livre para a operação e dobrava-a em três frases: alguma coisa em curso, alguma coisa terminada, alguma coisa falhada. Funcionava, até alguém reparar que várias dessas etiquetas tinham sido escritas na língua do nosso escritório em vez de inglês. Os clientes liam frases meio traduzidas nas suas barras de progresso, e quando uma operação falhava essa frase meio traduzida ficava guardada para sempre no campo de erro.

A correção não foi corrigir as etiquetas. Foi remover a possibilidade: a operação é agora uma enumeração, e cada caso transporta identificadores para «em curso», «terminada» e «falhada» em vez de palavras. Não sobra texto nenhum para errar, e a frase é construída na língua de quem lê, no fim de tudo. Manter esses catálogos honestos acaba por ser uma disciplina só por si.

A lista de verificação

Para qualquer API que responda a uma alteração com um identificador em vez de um resultado:

  1. Interprete o identificador, não se limite a guardá-lo. Ele diz onde perguntar, e onde nem sempre é onde perguntou.
  2. Separe terminado de bem-sucedido, no sistema de tipos se for possível.
  3. Trate um estado terminal não reconhecido como falha, nunca como sucesso.
  4. Dê ao ciclo de sondagem um ritmo e uma forma garantida de acabar.
  5. Mostre o progresso que tem mesmo, e uma estimativa honesta que nunca possa ser confundida com progresso que tem.
  6. Cite o outro lado literalmente nos erros, e construa a sua própria frase à volta da citação em vez de dentro dela.

Nenhuma destas é difícil. As seis são coisas que primeiro fizemos mal.

Comece com um host e um agente

Crie uma conta, instale o agente com um único comando e ligue o seu primeiro host Proxmox. O período experimental dura 14 dias e não pede quaisquer dados de pagamento.