Arquitectura da Grid

A Grid está desenhada como um conjunto de componentes, em que cada tem o seu papel crucial em manter a Grid. Isto pode parecer um pouco complicado, mas esperamos que este documento ajude a esclarecer alguma confusão.

Os componentes chave

Os componentes principais da Grid são:

Event Bus
Usado para enviar mensagens que podem ser recebidas de forma assíncrona entre os outros componentes.
New Session Queue
Mantém uma lista de pedidos de sessão que serão assignadas a um Node pelo Distributor.
Distributor
Responsável por manter um modelo das localizações da Grid (slots) onde uma sessão pode ser lançada e também por aceitar novos pedidos de sessão e assignar a um slot livre.
Node
Executa uma sessão WebDriver. Cada sessão é assignada a um slot e cada Node tem um ou mais slots.
Session Map
Mantém um mapeamento entre um ID de sessão e o endereço do Node onde a sessão está a ser executada.
Router
Este é o ponto de entrada da Grid. É também a única parte da Grid que poderá estar exposta à Internet (embora nós não recomendemos). Este componente reencaminha novos pedidos para New Session Queue ou para o Node onde a sessão esteja a ser executada

Ao falar da Grid, há alguns conceitos úteis a ter em mente:

  • Um slot é o sítio onde uma sessão pode ser executada
  • Cada slot tem um stereotype. Isto é um conjunto mínimo de capacidades que um pedido de nova sessão terá que corresponder antes que o Distributor envie esse pedido ao Node que tenha esse slot
  • O Grid Model é como o Distributor mantém o estado actual da Grid. Como o nome sugere, este modelo pode perder o sincronismo com a realidade. Este mecanismo é preferível do que estar a questionar cada Node, e desta forma, o Distributor rapidamente consegue alocar uma nova sessão a um slot.

Chamadas Síncronas e Assíncronas

Existem duas formas de comunicação dentro da Grid:

  1. Chamadas Síncronas “REST-ish” que usam JSON sobre pedidos HTTP.
  2. Eventos Assíncronos enviados para o Event Bus.

Como é que escolhemos que tipo de mecanismo de comunicação a usar? Afinal, podiamos ter escolhido usar apenas comunicação baseada em eventos e tudo iria funcionar sem problemas.

No entanto a resposta correcta é, se a acção em curso é síncrona, por exemplo a maioria das chamadas WebDriver, ou se perder uma resposta é problemático, a Grid usa chamadas síncronas. Se quisermos propagar informação que pode ter várias partes interessadas, ou se perder a mensagem não for crítico, a Grid usará o event bus.

Um facto interessante a notar é que as chamadas assíncronas estão menos “presas” aos processos que as executam do que todas as chamadas síncronas.

Sequência de início e dependencias entre componentes

Embora a Grid seja desenhada para permitir que os componentes possam iniciar em qualquer ordem, conceptualmente é esperado que a ordem de início seja:

  1. O Event Bus e o Session Map iniciam primeiro. Estes componentes não tem qualquer dependencia, nem mesmo entre eles e como tal, podem iniciar em paralelo.
  2. A Session Queue inicia de seguida
  3. O Distributor inicia. Irá periodicamente procurar novos pedidos de sessão na Session Queue, embora possa também receber um evento de um pedidos de sessão.
  4. O Router pode ser agora iniciado. Novos pedidos de sessão são direccionados para a Session Queue, o Distributor tentará encontrar um slot onde a sessão possa ser executada.
  5. O Node pode ser iniciado, veja mais abaixo os detalhes de como o Node se regista na Grid. Uma vez que o registo esteja concluído, a Grid estará pronta a receber pedidos.

Nesta tabela pode ser visualizada a dependencia ente os vários componentes. Um “✅” indica que a dependência é síncrona.

Event BusDistributorNodeRouterSession MapSession Queue
Event BusX
DistributorX
NodeX
RouterX
Session MapX
Session QueueX

Registo de Node

O processo de registar um Node na Grid é um processo “leve”.

  1. Quando um Node inicia, vai publicar um evento “heart beat” numa base regular. Este evento contém o estado do Node.
  2. O Distributor escuta os eventos “heart beat” e quando obtém um, tenta um GET ao endpoint /status do Node. A Grid é preparada com base nesta informação.

O Distributor irá usar regularmente o endpoint /status para continuar a obter o estado do Node. Por seu lado, o Node continua a publicar um evento “heart beat” mesmo depois do registo ter sido concluído com sucesso. Isto é feito para que mesmo que um Distributor não tenha um estado da Grid possa reiniciar e assim obter novamente uma visão do estado da Grid e assim ficar actualizado.

Objecto Node Status

O Node Status é um blob JSON com os seguintes campos:

NomeTipoDescrição
availabilitystringUma string com up, draining, ou down. A mais importante é draining, que indica que não devem ser enviados novos pedidos de sessão para o Node e assim que a última sessão termine, o Node irá reiniciar ou concluir.
externalUrlstringUma URI que os outros componentes da Grid se devem ligar.
lastSessionCreatedintegerUm timestamp da última sessão que foi criada neste Node. O Distributor irá tentar enviar novos pedidos de sessão para o Node que esteja parado há mais tempo.
maxSessionCountintegerEmbora seja possível inferir o número máximo de sessões a partir da lista de slots disponíveis, este número é usado para determinar qual é o máximo de sessões que este Node pode executar em simultâneo antes que se considere que está “cheio”.
nodeIdstringUm identificador UUID para esta instância do Node.
osInfoobjectUm objecto contendo os campos arch, name, e version. Isto é usado pela Grid UI e pelas queries GraphQL.
slotsarrayUm array de objectos Slot (descritos na secção seguinte)
versionstringA versão do Node (para Selenium, será igual à versão do Selenium)

É recomendado que todos os campos tenham valores.

O Objecto Slot

O objecto Slot representa um slot dentro de um Node. Um “slot” é onde uma sessão consegue ser executada. É possível que um Node tenha mais do que um Slot capaz de executar ao mesmo tempo. Por exemplo, um Node pode ser capaz de executar até 10 sessões em simultâneo, mas podem ser uma qualquer combinação de Chrome, Firefox ou Edge e neste caso, o Node irá indicar 10 como o número máximo de sessões, indicando que podem ser 10 Chrome, 10 Firefox e 10 Edge.

NomeTipoDescrição
idstringUm identificador UUID para este slot
lastStartedstringtimestamp no formato ISO-8601 contendo a data em que a última sessão iniciou
stereotypeobjectConjunto mínimo de capacidades que fazem match com este slot. O exemplo mínimo será por exemplo {"browserName": "firefox"}
sessionobjectO objecto Session (descrito na secção seguinte)

O Objecto Session

Representa uma sessão em execução dentro de um Slot

NomeTipoDescrição
capabilitiesobjectA lista de capacidades fornecidas pela sessão. Irá coincidir com o valor obtido pelo comando nova sessão
startTimestringtimestamp no formato ISO-8601 contendo a data em que a última sessão iniciou
stereotypeobjectConjunto mínimo de capacidades que fazem match com este slot. O exemplo mínimo será por exemplo {"browserName": "firefox"}
uristringA URI usada pelo Node para comunicar com a sessão