Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.Horde/Compute/ComputeMessages.cs
2025-05-18 13:04:45 +08:00

303 lines
8.6 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using EpicGames.Horde.Agents;
using EpicGames.Horde.Agents.Leases;
#pragma warning disable CA2227 // Collection properties should be read only
namespace EpicGames.Horde.Compute
{
/// <summary>
/// Describes port used by a compute task
/// </summary>
public class ConnectionMetadataPort
{
/// <summary>
/// Built-in port used for agent and compute task communication
/// </summary>
public const string ComputeId = "_horde_compute";
/// <summary>
/// Externally visible port that is mapped to agent port
/// In direct connection mode, these two are identical.
/// </summary>
public int Port { get; }
/// <summary>
/// Port the local process on the agent is listening on
/// </summary>
public int AgentPort { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="port"></param>
/// <param name="agentPort"></param>
public ConnectionMetadataPort(int port, int agentPort)
{
Port = port;
AgentPort = agentPort;
}
}
/// <summary>
/// Request a machine to execute compute requests
/// </summary>
public class AssignComputeRequest
{
/// <summary>
/// Desired protocol version for the client
/// </summary>
public int Protocol { get; set; }
/// <summary>
/// Condition to identify machines that can execute the request
/// </summary>
public Requirements? Requirements { get; set; }
/// <summary>
/// Arbitrary ID to correlate the same request over multiple calls.
/// It's recommended to pick something globally unique, such as a UUID.
/// </summary>
public string? RequestId { get; set; }
/// <summary>
/// Details for making an agent connection
/// </summary>
public ConnectionMetadataRequest? Connection { get; set; }
}
/// <summary>
/// Request details for making an agent connection
/// </summary>
public class ConnectionMetadataRequest
{
/// <summary>
/// Public IP of client requesting a compute resource (initiator)
/// As communication between client and Horde server may be on an internal network,
/// the client is responsible for resolving and providing this information.
/// </summary>
public string? ClientPublicIp { get; set; }
/// <summary>
/// TCP/IP ports the compute resource will listen to.
/// Key = arbitrary name identifying the port
/// Value = actual port number
/// Relay connection mode uses this information to set up port forwarding.
/// </summary>
public Dictionary<string, int> Ports { get; set; } = new();
/// <summary>
/// Type of connection mode that is preferred by the client. Server can still override.
/// </summary>
public ConnectionMode? ModePreference { get; set; }
/// <summary>
/// Prefer connecting to agent over a public IP even if a more optimal route is available. Server can still override.
/// This is useful to avoid sending traffic over VPN tunnels.
/// </summary>
public bool? PreferPublicIp { get; set; }
/// <summary>
/// Encryption mode to request. Server can still override.
/// </summary>
public Encryption? Encryption { get; set; }
/// <summary>
/// Maximum duration (in milliseconds) that communication can be inactive between
/// the compute client and task-running agent before the connection is terminated.
/// </summary>
public int? InactivityTimeoutMs { get; set; }
}
/// <summary>
/// Response to a cluster lookup request
/// </summary>
public class GetClusterResponse
{
/// <summary>
/// Compute cluster ID
/// </summary>
public ClusterId ClusterId { get; set; }
}
/// <summary>
/// Response to compute allocation request
/// </summary>
public class AssignComputeResponse
{
/// <summary>
/// IP address of the remote agent machine running the compute task
/// </summary>
public string Ip { get; set; } = String.Empty;
/// <summary>
/// How to establish a connection to the remote machine
/// </summary>
public ConnectionMode ConnectionMode { get; set; }
/// <summary>
/// An optional address (host:port) to use when connecting to agent via tunnel or relay mode
/// </summary>
public string? ConnectionAddress { get; set; }
/// <summary>
/// Port number on the remote machine
/// </summary>
public int Port { get; set; }
/// <summary>
/// Assigned ports (externally visible port -> local port on agent)
/// Key is an arbitrary name identifying the port (same as was given in <see cref="ConnectionMetadataRequest" />)
/// When relay mode is used, ports can mapped to a different externally visible port.
/// If compute task uses and listens to port 7000, that port can be externally represented as something else.
/// For example, port 32743 can be pointed to port 7000.
/// This makes no difference for the compute task process, but the client/initiator making connections must
/// pay attention to this mapping.
/// </summary>
public IReadOnlyDictionary<string, ConnectionMetadataPort> Ports { get; set; } = new Dictionary<string, ConnectionMetadataPort>();
/// <summary>
/// Encryption used
/// </summary>
public Encryption Encryption { get; set; } = Encryption.None;
/// <summary>
/// Cryptographic nonce to identify the request, as a hex string
/// </summary>
public string Nonce { get; set; } = String.Empty;
/// <summary>
/// AES key for the channel, as a hex string
/// </summary>
public string Key { get; set; } = String.Empty;
/// <summary>
/// X.509 certificate used for SSL/TLS encryption
/// </summary>
public string Certificate { get; set; } = String.Empty;
/// <summary>
/// Which cluster this remote machine belongs to
/// </summary>
public ClusterId ClusterId { get; set; }
/// <summary>
/// Identifier for the remote machine
/// </summary>
public AgentId AgentId { get; set; }
/// <summary>
/// Agent version for the remote machine
/// </summary>
public string? AgentVersion { get; set; }
/// <summary>
/// Identifier for the new lease on the remote machine
/// </summary>
public LeaseId LeaseId { get; set; }
/// <summary>
/// Resources assigned to this machine
/// </summary>
public Dictionary<string, int> AssignedResources { get; set; } = new Dictionary<string, int>();
/// <summary>
/// Version number for the compute protocol
/// </summary>
public int Protocol { get; set; }
/// <summary>
/// Properties of the agent assigned to do the work
/// </summary>
public IReadOnlyList<string> Properties { get; set; } = new List<string>();
}
/// <summary>
/// Describe how to connect to the remote machine
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ConnectionMode
{
/// <summary>
/// Connection is established directly to remote machine, behaving like a normal TCP/UDP connection
/// </summary>
Direct,
/// <summary>
/// Connection is tunneled through Horde server.
/// When connecting, initiator must send a tunnel handshake request indicating which machine/IP to tunnel to.
/// Once handshake is complete, TCP connection behaves as normal (UDP not supported)
/// </summary>
Tunnel,
/// <summary>
/// Connection is established to remote machine via a relay.
/// Forwarding is transparent and behaves like a normal TCP/UDP connection.
/// </summary>
Relay
}
/// <summary>
/// Describe encryption for the compute resource connection
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Encryption
{
/// <summary>
/// No encryption enabled
/// </summary>
None,
/// <summary>
/// Use custom AES-based encryption transport
/// </summary>
Aes,
/// <summary>
/// Use SSL/TLS encryption with RSA 2048-bits
/// </summary>
Ssl,
/// <summary>
/// Use SSL/TLS encryption with ECDSA P-256
/// </summary>
SslEcdsaP256
}
/// <summary>
/// Resource needs declaration request
/// </summary>
public class ResourceNeedsMessage
{
/// <summary>
/// Unique session ID performing compute resource requests
/// </summary>
public string SessionId { get; set; } = String.Empty;
/// <summary>
/// Pool of agents requesting resources from
/// </summary>
public string Pool { get; set; } = String.Empty;
/// <summary>
/// Key/value of resources needed by session (such as CPU or memory, see KnownPropertyNames in Horde.Server)
/// </summary>
public Dictionary<string, int> ResourceNeeds { get; set; } = new();
}
/// <summary>
/// Resource needs response
/// </summary>
public class GetResourceNeedsResponse
{
/// <summary>
/// List of resource needs
/// </summary>
public List<ResourceNeedsMessage> ResourceNeeds { get; set; } = new();
}
}