Source: management-system/src/backend/shared-electron-server/network/machines/machineInfoRequests.js

// Module responsible for updating the information about the different machines known to the MS

import {
  statusEndpoint,
  machineEndpoint,
  processEndpoint,
} from '../ms-engine-communication/module.js';
import logger from '../../logging.js';
import helpers from '../../../../shared-frontend-backend/helpers/javascriptHelpers.js';
const { deepEquals } = helpers;

/**
 * Sends request checking if there is an engine running on the given machine
 *
 * @param {object} machine
 * @param {string} machine.ip the ip of the given machine
 * @param {number} machine.port the port the engine is accessible through
 * @returns {Promise<boolean>} indicates if the machine is available or not
 */
export async function checkAvailability(machine) {
  // check if machine is reachable and running an engine; enforce usage of node http to avoid error in V8
  const available = await statusEndpoint.getStatus(machine);

  return available;
}

/**
 * Checks if there are deployments on the machine that are not known locally or vice versa and updates accordingly
 */
async function getUpdatedDeployments(machine, deployedProcesses, deploymentsOnMachine) {
  // check if there were changes in which processes are deployed to this machine
  const storedDeployments = Object.values(deployedProcesses).map(
    (deployment) => deployment.definitionId
  );
  const newDeploymentFound = deploymentsOnMachine.some(
    (deployment) => !storedDeployments.includes(deployment.definitionId)
  );
  const deploymentRemoved = storedDeployments.some(
    (definitionId) =>
      !deploymentsOnMachine.some((deployment) => deployment.definitionId === definitionId)
  );

  if (newDeploymentFound || deploymentRemoved) {
    deployedProcesses = deploymentsOnMachine.map((deploymentFromMachine) => {
      const deployment = Object.values(deployedProcesses).find(
        (process) => process.definitionId === deploymentFromMachine.definitionId
      );
      // if we already know this deployment => just use the already existing information
      if (deployment) {
        return deployment;
      }
      return {
        definitionId: deploymentFromMachine.definitionId,
        instances: [],
        bpmn: deploymentFromMachine.bpmn,
        name: deploymentFromMachine.definitionName,
        deploymentDate: deploymentFromMachine.deploymentDate,
        deploymentMethod: deploymentFromMachine.deploymentMethod,
      };
    });

    // create object with key: id of deplyed process, value: information about deployment
    deployedProcesses = deployedProcesses.reduce(
      (deployed, deployment) => ({
        ...deployed,
        [deployment.definitionId]: deployment,
      }),
      {}
    );

    return deployedProcesses;
  }

  return;
}

/**
 * Gets updated information about the processes deployed on the given machine
 *
 * @param {object} machine
 * @param {string} machine.ip the ip of the given machine
 * @param {number} machine.port the port the engine is accessible through
 * @returns {object|undefined} information about the processes deployed on the machine or undefined if nothing changed
 */
export async function getUpdatedDeploymentInfo(machine) {
  let deployedProcesses;
  let updated = false;

  if (!machine.deployedProcesses) {
    deployedProcesses = {};
    updated = true;
  } else {
    deployedProcesses = JSON.parse(JSON.stringify(machine.deployedProcesses));
  }

  const { name, hostname, id } = machine;
  logger.debug(`Sending requests for up to date deployment info to ${name || hostname || id}.`);

  // get information about all processes deployed to the current machine
  const deploymentsOnMachine = await processEndpoint.getDeployedProcesses(machine);

  const updatedDeployments = await getUpdatedDeployments(
    machine,
    deployedProcesses,
    deploymentsOnMachine
  );

  // deployments didn't change from last time they were checked
  if (updatedDeployments) {
    updated = true;
    deployedProcesses = updatedDeployments;
  }

  // simultaniously request instance information for all instances
  const deploymentInfoRequests = deploymentsOnMachine.map(async (deployment) => {
    const instanceIds = await processEndpoint.getProcessInstances(machine, deployment.definitionId);

    const instanceRequests = instanceIds.map(
      async (id) =>
        await processEndpoint.getInstanceInformation(machine, deployment.definitionId, id)
    );

    const instances = await Promise.all(instanceRequests);

    return {
      definitionId: deployment.definitionId,
      instances,
    };
  });

  const deploymentInfo = await Promise.all(deploymentInfoRequests);

  // update instance information if there are new instances or some instance changed state
  deploymentInfo.forEach((info) => {
    info.instances.forEach((instance) => {
      const deployment = Object.values(deployedProcesses).find(
        (process) => process.definitionId === info.definitionId
      );

      const storedInstanceIndex = deployment.instances.findIndex(
        (knownInstance) => instance.processInstanceId === knownInstance.processInstanceId
      );

      if (storedInstanceIndex < 0) {
        deployment.instances.push(instance);
        updated = true;
      } else {
        // check deep equality and only overwrite when something actually changed
        if (!deepEquals(deployment.instances[storedInstanceIndex], instance)) {
          deployment.instances[storedInstanceIndex] = instance;
          updated = true;
        }
      }
    });
  });

  if (updated) {
    return deployedProcesses;
  }

  return undefined;
}

/**
 * Returns the updated information for a machine if the machine information changed (returns undefined if not)
 *
 * @param {Object} machine the machine we want to request information for
 * @returns {Object|undefined} the updated object or undefined if there was no new information
 */
export async function getCompleteMachineInformation(machine) {
  let updated = false;
  let updatedMachine = { ...machine };

  const { name, hostname, ip } = machine;

  logger.debug(`Sending GET request to ${name || hostname || ip} to get machine info!`);
  const properties = await machineEndpoint.getProperties(machine);

  //delete some values that change to often to be stored (or just don't make sense to store)
  const { cores, physicalCores, processors, speed } = properties.cpu;

  properties.cpu = { cores, physicalCores, processors, speed };
  properties.mem = { total: properties.mem.total };
  properties.disk = properties.disk.map(({ type, total }) => ({ type, total }));
  properties.battery = { hasBattery: properties.battery.hasBattery };

  const metainfo = ['id', 'hostname', 'name', 'description'];

  Object.entries(properties).forEach(([propertyName, value]) => {
    if (value !== machine.machine[propertyName]) {
      updatedMachine.machine[propertyName] = value;
      updated = true;
    }

    if (metainfo.includes(propertyName)) {
      if (value !== updatedMachine[propertyName]) {
        updatedMachine[propertyName] = value;
        updated = true;
      }
    }
  });

  const deploymentInfo = await getUpdatedDeploymentInfo(machine);
  if (deploymentInfo) {
    updatedMachine.deployedProcesses = deploymentInfo;
    updated = true;
  }

  if (updated) {
    return updatedMachine;
  }
}