async handshake() {
    const handshakeTimeout = setTimeout(() => {
      global.kuzzle.log.error(
        `[CLUSTER] Failed to join the cluster: timed out (joinTimeout: ${this.config.joinTimeout}ms)`,
      );
      global.kuzzle.shutdown();
    }, this.config.joinTimeout);

    const mutex = new Mutex("clusterHandshake", {
      timeout: this.config.joinTimeout,
    });

    try {
      await mutex.lock();

      // Create the ID Key AFTER the handshake mutex is actually locked,
      // to prevent race conditions (other nodes attempting to connect to this
      // node while it's still initializing)
      await this.idCardHandler.createIdCard();
      debug("[CLUSTER] ID Card created");

      this.nodeId = this.idCardHandler.nodeId;

      await this.startHeartbeat();
      debug("[CLUSTER] Start heartbeat");

      let retried = false;
      let fullState = null;
      let nodes;

      debug("[CLUSTER] Start retrieving full state..");
      do {
        nodes = await this.idCardHandler.getRemoteIdCards();
        debug("[CLUSTER] %s remote nodes discovered", nodes.length);

        // No other nodes detected = no handshake required
        if (nodes.length === 0) {
          this.trackActivity(this.nodeId, this.ip, nodeActivityEnum.ADDED);
          return;
        }

        // Verify that no other node share the same IP address as this one
        const duplicate = nodes.filter((node) => node.ip === this.ip);

        if (duplicate.length > 0) {
          global.kuzzle.log.error(
            `[CLUSTER] Another node share the same IP address as this one (${this.ip}): ${duplicate[0].id}. Shutting down.`,
          );
          global.kuzzle.shutdown();
          return;
        }

        // Subscribe to remote nodes and start buffering sync messages
        await Bluebird.map(nodes, ({ id, ip }) => {
          const subscriber = new ClusterSubscriber(this, id, ip);
          this.remoteNodes.set(id, subscriber);
          return subscriber.init();
        });
        debug("[CLUSTER] Successfully subscribed to nodes");

        fullState = await this.command.getFullState(nodes);

        // Uh oh... no node was able to give us the full state.
        // We must retry later, to check if the redis keys have expired. If they
        // are still there and we still aren't able to fetch a full state, this
        // means we're probably facing a network split, and we must then shut
        // down.
        if (fullState === null) {
          if (retried) {
            global.kuzzle.log.error(
              "[CLUSTER] Could not connect to discovered cluster nodes (network split detected). Shutting down.",
            );
            global.kuzzle.shutdown();
            return;
          }

          // Disposes all subscribers
          for (const subscriber of this.remoteNodes.values()) {
            subscriber.dispose();
          }
          this.remoteNodes.clear();

          // Waits for a redis heartbeat round
          retried = true;
          const retryDelay = this.heartbeatDelay * 1.5;
          global.kuzzle.log.warn(
            `[CLUSTER] Unable to connect to discovered cluster nodes. Retrying in ${retryDelay}ms...`,
          );
          await Bluebird.delay(retryDelay);
        }
      } while (fullState === null);
      debug("[CLUSTER] Fullstate retrieved, loading into node..");

      await this.fullState.loadFullState(fullState);
      this.activity = fullState.activity ? fullState.activity : this.activity;

      debug("[CLUSTER] Fullstate loaded.");

      const handshakeResponses = await this.command.broadcastHandshake(nodes);

      debug("[CLUSTER] Successful handshakes with other nodes.");

      // Update subscribers: start synchronizing, or unsubscribes from nodes who
      // didn't respond
      for (const [nodeId, handshakeData] of Object.entries(
        handshakeResponses,
      )) {
        const subscriber = this.remoteNodes.get(nodeId);
        if (handshakeData === null) {
          subscriber.dispose();
          this.remoteNodes.delete(nodeId);
        } else {
          await this.idCardHandler.addNode(nodeId);
          const nodesStates = fullState.nodesState || [];
          const nodeStatus = nodesStates.find((node) => node.id === nodeId);
          subscriber.sync(
            nodeStatus ? nodeStatus.lastMessageId : handshakeData.lastMessageId,
          );
          global.kuzzle.log.info(
            `[CLUSTER] Successfully completed the handshake with node ${nodeId}`,
          );
        }
      }

      global.kuzzle.log.info("[CLUSTER] Successfully joined the cluster.");
      this.trackActivity(this.nodeId, this.ip, nodeActivityEnum.ADDED);
    } finally {
      clearTimeout(handshakeTimeout);
      await mutex.unlock();
    }
  }
Discovers other active nodes from the cluster and, if other nodes exist,
starts a handshake procedure to sync this node and to make it able to
handle new client requests

@return {void}