Ir para o conteúdo

Blogue ·

Uma fronteira pertence ao núcleo

Revogar um agente alojado desprendia-o da encomenda e deixava uma máquina Proxmox a correr que ninguém alcançava e que todos continuavam a pagar.

Por Provibr

Um Cloud Agent é a versão do nosso agente em que a máquina é gerida por nós. O cliente encomenda um, nós clonamos um template do Proxmox, instalamos o agente nele e faturamos mensalmente. Do painel parece um agente como qualquer outro, e essa semelhança é a história inteira desta ronda.

Todos os outros agentes pertencem ao cliente. Revogá-lo é uma operação limpa e segura: a plataforma envia um aviso de revogação, o agente apaga a sua identidade, o seu certificado e o seu cofre de credenciais, e a linha pode depois ser eliminada. Não fica nada a correr em lado nenhum, porque a coisa que estava a correr nunca foi nossa.

Para um Cloud Agent isso não é verdade, e nada o impedia.

O que medimos antes de mudar seja o que for

Na base de dados de desenvolvimento, com uma encomenda real por trás de um agente real, chamámos o caminho de remoção exatamente como o botão do painel faz.

Comunicou mode: "hard". A linha do agente tinha desaparecido. A linha da encomenda continuava lá, continuava active, continuava a levar o vmid 5998, e o seu agent_id tinha passado discretamente a NULL, porque a chave estrangeira da encomenda para o agente é ON DELETE SET NULL.

Havia portanto uma máquina virtual num hipervisor, a correr, com um agente instalado que tinha apagado a própria identidade e que nunca mais podia por isso voltar a ligar-se, presa a uma encomenda que continuava a gerar uma cobrança mensal, e nenhuma linha em parte nenhuma que apontasse de uma para a outra. Inalcançável, impossível de parar a partir do painel, e não gratuita.

A restrição que produziu isto está correta. Pôr a referência a NULL é o comportamento certo para uma coluna que diz que agente está atualmente instalado aqui; uma cascata teria eliminado a encomenda e o registo de faturação com ela. O defeito é que nada fazia a pergunta que o esquema não podia fazer.

Duas guardas existentes, ambas a olhar para o lado errado

Eliminar um agente já tinha duas recusas, e vale a pena ser preciso sobre a razão por que nenhuma disparou.

A primeira recusa-se a eliminar um agente que esteja inscrito neste momento: revogue-o primeiro, para que se limpe a si próprio. Na altura em que a linha estava a ser eliminada, o agente acabara de ser revogado na mesma chamada, portanto não estava inscrito e a verificação ficava satisfeita.

A segunda recusa-se a eliminar um agente que ainda tenha integrações ou servidores pendurados nele, porque essas chaves estrangeiras propagam-se em cascata e um clique levaria o inventário inteiro à frente. Um Cloud Agent não tem nem uma coisa nem outra: é uma máquina nova que existe para correr o agente, por isso a verificação passou porque não havia nada para contar.

Ambas as guardas eram sobre o que seria destruído pela eliminação. A coisa que sobrevive à eliminação, e que custa dinheiro precisamente por sobreviver, ficava fora da forma da pergunta.

A fronteira vai para o núcleo, não para o botão

A reparação óbvia é verificar na ação de servidor por detrás do botão. Também está errada, e a razão é uma falha em que este código já caiu antes com um ponto final de autenticação de origem que contornava todos os controlos do lado do servidor.

Contámos os pontos de chamada. Fora das duas funções do núcleo há sete chamadas a revogar, a eliminar ou à remoção combinada, espalhadas por três ficheiros: três ações de servidor na página do agente, a ação em massa, e duas rotas da API v1. Uma verificação na ação do painel teria deixado as rotas da API abertas, e uma rota da API é exatamente o que um script de cliente usa.

Por isso a fronteira fica dentro de revokeAgentCore() e deleteAgentCore() propriamente ditas, o ponto de estrangulamento por onde passam as sete chamadas. Uma função, perguntada duas vezes.

Fica depois do ramo idempotente e não antes. Um agente que já está revogado não tem nada para revogar, e dizer a quem chama que cancele primeiro a encomenda seria uma falsidade sobre uma chamada que não vai fazer nada de qualquer maneira. Uma fronteira pertence à operação que muda mesmo alguma coisa.

O predicado é isto ainda pode ser cancelado, não isto está vivo

Há uma lista no pacote partilhado chamada CLOUD_AGENT_LIVE_STATUSES: os estados em que uma máquina existe e custa dinheiro. Tem quatro membros e é exatamente a lista errada para reaproveitar aqui.

O predicado que usamos é cloudAgentActionAllowed("cancel", status), que é verdadeiro para sete dos oito estados de encomenda, tudo menos uma encomenda que já está cancelada.

Os três estados da diferença são os interessantes, e failed é o mais afiado. Uma encomenda falhada não custa nada, portanto não está viva. Mas uma encomenda falhada é precisamente aquela que pode ter ido longe o bastante para criar uma máquina e depois ter caído, que é a razão por que cancelar é permitido a partir dela em primeiro lugar. Reaproveitar a lista de faturação teria deixado sem guarda o único caso com uma máquina órfã, e teria parecido perfeitamente razoável numa revisão.

Duas listas que querem dizer quase a mesma coisa afastam-se. A pergunta aqui não é isto custa dinheiro, é cancelar continua a ser o caminho, e exatamente uma função neste código responde a essa.

Dois caminhos de escrita que podem saltá-la

A guarda que escrevemos deriva todos os sítios da fonte do painel que revogam, eliminam à força ou escondem em modo suave uma linha de agente, com uma travessia da árvore sintática das árvores de aplicação e de biblioteca, e exige por sítio ou a fronteira ou uma razão escrita. Dois sítios levam uma razão: o próprio caminho de cancelamento, que remove a máquina, liberta o endereço e só depois revoga o agente, e o encerramento de conta, que revoga todos os agentes de uma organização e trata o Cloud Agent à parte no mesmo fluxo. Bloquear qualquer um deles fecharia uma saída correta em vez de poupar dinheiro.

A guarda tem os dois crivos que impedem um analisador de fonte de ficar verde no vazio: um limite inferior para quantos sítios de escrita tem de encontrar, três deles nomeados, e um teste que falha perante uma entrada da lista de exceções que aponte para um ficheiro que já não escreve nada. Corre 4 testes e 11 asserções.

Dois factos no ecrã, não um

O lado do painel precisava de dois booleanos separados e quase publicámos um.

orderOpen é um facto sobre a encomenda: isto ainda pode ser cancelado. canCancel é um facto sobre quem está a ver: esta pessoa pode cancelá-la. Juntá-los num único pode agir aqui significaria que um membro sem a permissão de cancelar vê o botão de revogar, usa-o, deita a máquina fora e não consegue parar a conta. Por isso os botões desaparecem em função do facto da encomenda, e o cartão fica com a razão lá dentro, porque porque é que não posso revogar este é a pergunta que faz toda a gente que já viu o botão nos seus outros agentes.

Uma coisa veio atrás porque a alteração a tornou falsa: as instruções de desinstalação aconselhavam revogar primeiro o agente no painel. Isso é um botão que já não existe nesta página.

O que registaríamos

  1. Uma semelhança na interface não é uma semelhança nas consequências. Dois agentes, um botão, um deles custa dinheiro depois de o carregar.
  2. ON DELETE SET NULL pode estar certo e ainda assim deixá-lo com um órfão. O esquema não consegue fazer a pergunta.
  3. Guardas sobre o que uma eliminação destrói não veem o que uma eliminação abandona.
  4. Conte os pontos de chamada antes de escolher onde vai a verificação. Os nossos eram sete, em três ficheiros, dois deles rotas da API.
  5. Ponha a fronteira no ponto de estrangulamento, não no botão. Um botão é uma das entradas.
  6. Ponha-a depois do ramo idempotente. Uma recusa numa chamada que não faria nada é uma mentira.
  7. Reaproveitar uma lista que quer dizer quase o mesmo é uma falha lenta. Custa dinheiro e ainda pode ser cancelado diferem em três estados, e a diferença é onde está o estrago.
  8. Um estado que não custa nada pode ainda assim ter deixado alguma coisa para trás. Um aprovisionamento falhado é o perigoso.
  9. Separe o facto sobre o objeto do facto sobre quem está a ver. Juntos, entregam um botão destrutivo a um membro limitado.
  10. Deixe a guarda ler a árvore. Um teste por entrada conhecida não vê a entrada que alguém acrescenta no mês que vem.

Para que fique claro o que isto é: o defeito foi reproduzido contra a base de dados de desenvolvimento antes de qualquer alteração, a verificação de ponta a ponta terminou em 26 verdes e foi provada vermelha antes disso em 16 verdes e 10 falhas, a verificação no navegador em 29 verdes e provada vermelha em 18 e 11 com o sinalizador da encomenda forçado a desligado, nove línguas em 178 verdes, e a verificação de junção correu sem uma linha de falha. Por provar: não esteve envolvida nenhuma máquina de cliente real, e o lado da faturação foi inspecionado em vez de exercitado. Três armadilhas de medição custaram tempo e vale a pena nomeá-las: a zona de perigo vive por trás de um parâmetro de separador, por isso uma verificação que o omite mede zero botões e lê isso como sucesso; uma barra invertida de escape dentro de um literal de modelo entregue a uma avaliação de página não é um escape; e um dos três scripts de catálogo achatou um espaço inquebrável que os outros dois preservaram.

Do trabalho de plataforma na Provibr. Porque é que uma máquina só pode ser vendida uma vez está em duas encomendas, uma máquina, porque é que um atalho é uma referência e nunca uma permissão está em um atalho é uma referência, não um direito, e uma palavra de estado tomada da tabela errada está em uma análise que fechava a sua janela.

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.