2021-04-29 15:10:34 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2021-06-03 15:50:39 -04:00
using EpicGames.Core ;
2021-04-29 15:10:34 -04:00
using HordeServer.Services ;
2021-05-13 21:01:59 -04:00
using HordeServer.Utilities ;
2021-04-29 15:10:34 -04:00
using System ;
using System.Collections.Generic ;
using System.ComponentModel.DataAnnotations ;
using System.Linq ;
2021-06-03 15:50:39 -04:00
using System.Runtime.InteropServices ;
2021-04-29 15:10:34 -04:00
using System.Threading.Tasks ;
using TimeZoneConverter ;
namespace HordeServer
{
/// <summary>
/// Types of storage to use for log data
/// </summary>
public enum StorageProviderType
{
/// <summary>
/// AWS S3
/// </summary>
S3 ,
/// <summary>
/// Local filesystem
/// </summary>
FileSystem ,
/// <summary>
/// In-memory only (for testing)
/// </summary>
Transient ,
/// <summary>
/// Relay to another server (useful for testing against prod)
/// </summary>
Relay ,
} ;
/// <summary>
/// Specifies the service to use for controlling the size of the fleet
/// </summary>
public enum FleetManagerType
{
/// <summary>
/// Default (empty) instance
/// </summary>
None ,
/// <summary>
/// Use AWS EC2 instances
/// </summary>
Aws ,
}
2021-05-11 13:12:19 -04:00
/// <summary>
/// Settings for a remote execution instance
///
/// Instances are a concept from Google's remote execution API that Horde implements.
/// They are used for separating different type of executions into groups.
/// </summary>
public class RemoteExecInstanceSettings
{
/// <summary>
/// gRPC URL for the content-addressable storage to be used for this instance
/// </summary>
[Required]
public Uri ? CasUrl { get ; set ; }
/// <summary>
/// gRPC URL for the action cache to be used for this instance
/// </summary>
[Required]
public Uri ? ActionCacheUrl { get ; set ; }
/// <summary>
/// Service account token for accessing the gRPC URLs
/// </summary>
[Required]
public string? ServiceAccountToken { get ; set ; }
2021-06-11 10:45:23 -04:00
/// <summary>
/// List of pool IDs this instance can be scheduled in
/// </summary>
public List < string > ? PoolFilter { get ; set ; }
2021-06-15 05:50:49 -04:00
/// <summary>
/// Max time to wait for an agent to become available for serving a remote execution request (in milliseconds)
/// </summary>
public int AgentQueueTimeout { get ; set ; } = 5000 ;
2021-06-15 06:30:35 -04:00
internal TimeSpan AgentQueueTimeoutAsTimeSpan ( )
2021-06-15 05:50:49 -04:00
{
return TimeSpan . FromMilliseconds ( AgentQueueTimeout ) ;
}
2021-05-11 13:12:19 -04:00
}
/// <summary>
/// Settings for remote execution
/// </summary>
public class RemoteExecSettings
{
/// <summary>
/// Mapping instance names to instance settings
/// </summary>
public Dictionary < string , RemoteExecInstanceSettings > Instances { get ; set ; } = new Dictionary < string , RemoteExecInstanceSettings > ( ) ;
2021-06-15 05:50:49 -04:00
/// <summary>
/// Max number of concurrent leases per agent
/// </summary>
2021-06-15 06:30:35 -04:00
public int MaxConcurrentLeasesPerAgent { get ; set ; } = 2 ;
2021-05-11 13:12:19 -04:00
}
2021-04-29 15:10:34 -04:00
/// <summary>
/// Global settings for the application
/// </summary>
public class ServerSettings
{
/// <summary>
/// MongoDB connection string
/// </summary>
2021-06-03 15:50:39 -04:00
public string? DatabaseConnectionString { get ; set ; }
2021-04-29 15:10:34 -04:00
/// <summary>
/// MongoDB database name
/// </summary>
2021-06-03 15:50:39 -04:00
public string DatabaseName { get ; set ; } = "Horde" ;
2021-04-29 15:10:34 -04:00
/// <summary>
/// The claim type for administrators
/// </summary>
2021-05-13 21:01:59 -04:00
public string AdminClaimType { get ; set ; } = HordeClaimTypes . InternalRole ;
2021-04-29 15:10:34 -04:00
/// <summary>
/// Value of the claim type for administrators
/// </summary>
2021-05-13 21:01:59 -04:00
public string AdminClaimValue { get ; set ; } = "admin" ;
2021-04-29 15:10:34 -04:00
/// <summary>
/// Optional certificate to trust in order to access the database (eg. AWS public cert for TLS)
/// </summary>
public string? DatabasePublicCert { get ; set ; }
/// <summary>
/// Access the database in read-only mode (avoids creating indices or updating content)
/// Useful for debugging a local instance of HordeServer against a production database.
/// </summary>
public bool DatabaseReadOnlyMode { get ; set ; } = false ;
/// <summary>
/// Optional PFX certificate to use for encryting agent SSL traffic. This can be a self-signed certificate, as long as it's trusted by agents.
/// </summary>
public string? ServerPrivateCert { get ; set ; }
/// <summary>
/// Issuer for tokens from the auth provider
/// </summary>
public string? OidcAuthority { get ; set ; }
/// <summary>
/// Client id for the OIDC authority
/// </summary>
public string? OidcClientId { get ; set ; }
/// <summary>
/// Optional redirect url provided to OIDC login
/// </summary>
public string? OidcSigninRedirect { get ; set ; }
/// <summary>
/// Name of the issuer in bearer tokens from the server
/// </summary>
public string? JwtIssuer { get ; set ; } = null ! ;
/// <summary>
/// Secret key used to sign JWTs. This setting is typically only used for development. In prod, a unique secret key will be generated and stored in the DB for each unique server instance.
/// </summary>
public string? JwtSecret { get ; set ; } = null ! ;
/// <summary>
/// Length of time before JWT tokens expire, in hourse
/// </summary>
public int JwtExpiryTimeHours { get ; set ; } = 4 ;
/// <summary>
/// Disable authentication for debugging purposes
/// </summary>
public bool DisableAuth { get ; set ; }
/// <summary>
/// Whether to enable Cors, generally for development purposes
/// </summary>
public bool CorsEnabled { get ; set ; } = false ;
/// <summary>
/// Allowed Cors origin
/// </summary>
public string CorsOrigin { get ; set ; } = null ! ;
/// <summary>
/// Whether to enable a schedule in test data (false by default for development builds)
/// </summary>
public bool EnableScheduleInTestData { get ; set ; }
/// <summary>
/// Interval between rebuilding the schedule queue with a DB query.
/// </summary>
public TimeSpan SchedulePollingInterval { get ; set ; } = TimeSpan . FromSeconds ( 60.0 ) ;
/// <summary>
/// Interval between polling for new jobs
/// </summary>
public TimeSpan NoResourceBackOffTime { get ; set ; } = TimeSpan . FromSeconds ( 30.0 ) ;
/// <summary>
/// Interval between attempting to assign agents to take on jobs
/// </summary>
public TimeSpan InitiateJobBackOffTime { get ; set ; } = TimeSpan . FromSeconds ( 180.0 ) ;
/// <summary>
/// Interval between scheduling jobs when an unknown error occurs
/// </summary>
public TimeSpan UnknownErrorBackOffTime { get ; set ; } = TimeSpan . FromSeconds ( 120.0 ) ;
/// <summary>
/// Config for connecting to Redis server(s).
/// Setting it to null will disable Redis use and connection
/// See format at https://stackexchange.github.io/StackExchange.Redis/Configuration.html
/// </summary>
2021-06-03 18:44:45 -04:00
public string? RedisConnectionConfig { get ; set ; }
2021-04-29 15:10:34 -04:00
/// <summary>
/// Type of write cache to use in log service
/// Currently Supported: "InMemory" or "Redis"
/// </summary>
public string LogServiceWriteCacheType { get ; set ; } = "InMemory" ;
/// <summary>
/// Provider Type
/// Currently Supported: "S3" or "FileSystem"
/// </summary>
public StorageProviderType ExternalStorageProviderType { get ; set ; } = StorageProviderType . FileSystem ;
/// <summary>
2021-06-03 15:50:39 -04:00
/// Local log/artifact storage directory, if using type filesystem
2021-04-29 15:10:34 -04:00
/// </summary>
2021-06-03 15:50:39 -04:00
public string LocalLogsDir { get ; set ; } = "Logs" ;
/// <summary>
/// Gets the full path referred to by LocalStorageDir
/// </summary>
2021-06-03 16:16:09 -04:00
public DirectoryReference LocalLogsDirRef = > DirectoryReference . Combine ( Program . DataDir , LocalLogsDir ) ;
2021-06-03 15:50:39 -04:00
2021-06-24 16:57:21 -04:00
/// <summary>
/// Local blob storage directory, if using type filesystem
/// </summary>
public string LocalBlobsDir { get ; set ; } = "Blobs" ;
/// <summary>
/// Gets the full path referred to by LocalStorageDir
/// </summary>
public DirectoryReference LocalBlobsDirRef = > DirectoryReference . Combine ( Program . DataDir , LocalBlobsDir ) ;
2021-06-03 15:50:39 -04:00
/// <summary>
/// Local artifact storage directory, if using type filesystem
/// </summary>
public string LocalArtifactsDir { get ; set ; } = "Artifacts" ;
/// <summary>
/// Gets the full path referred to by LocalStorageDir
/// </summary>
2021-06-03 16:16:09 -04:00
public DirectoryReference LocalArtifactsDirRef = > DirectoryReference . Combine ( Program . DataDir , LocalArtifactsDir ) ;
2021-04-29 15:10:34 -04:00
/// <summary>
/// S3 bucket region for logfile storage
/// </summary>
public string S3BucketRegion { get ; set ; } = null ! ;
/// <summary>
/// Arn to assume for s3. "Basic", "AssumeRole", "AssumeRoleWebIdentity" only
/// </summary>
public string S3CredentialType { get ; set ; } = null ! ;
/// <summary>
/// S3 Client username (used in Basic auth type only)
/// </summary>
public string S3ClientKeyId { get ; set ; } = null ! ;
/// <summary>
/// S3 client password (used in Basic auth type only)
/// </summary>
public string S3ClientSecret { get ; set ; } = null ! ;
/// <summary>
/// Arn to assume for s3
/// </summary>
public string S3AssumeArn { get ; set ; } = null ! ;
/// <summary>
/// S3 log bucket name
/// </summary>
public string S3LogBucketName { get ; set ; } = null ! ;
/// <summary>
/// S3 artifact bucket name
/// </summary>
public string S3ArtifactBucketName { get ; set ; } = null ! ;
/// <summary>
/// When using a relay storage provider, specifies the remote server to use
/// </summary>
public string? LogRelayServer { get ; set ; }
/// <summary>
/// Authentication token for using a relay server
/// </summary>
public string? LogRelayBearerToken { get ; set ; }
/// <summary>
/// Whether to log json to stdout
/// </summary>
public bool LogJsonToStdOut { get ; set ; } = false ;
/// <summary>
/// Which fleet manager service to use
/// </summary>
public FleetManagerType FleetManager { get ; set ; } = FleetManagerType . None ;
/// <summary>
/// Whether to run scheduled jobs. Not wanted for development.
/// </summary>
public bool DisableSchedules { get ; set ; } = true ;
/// <summary>
/// Timezone for evaluating schedules
/// </summary>
public string? ScheduleTimeZone { get ; set ; }
/// <summary>
/// Address of the Perforce bridge
/// </summary>
public string? PerforceBridge { get ; set ; }
/// <summary>
/// Token for interacting with Slack
/// </summary>
public string? SlackToken { get ; set ; }
/// <summary>
/// Token for opening a socket to slack
/// </summary>
public string? SlackSocketToken { get ; set ; }
/// <summary>
/// Channel to send stream notification update failures to
/// </summary>
public string? UpdateStreamsNotificationChannel { get ; set ; }
/// <summary>
/// URI to the SmtpServer to use for sending email notifications
/// </summary>
public string? SmtpServer { get ; set ; }
/// <summary>
/// The email address to send email notifications from
/// </summary>
public string? EmailSenderAddress { get ; set ; }
/// <summary>
/// The name for the sender when sending email notifications
/// </summary>
public string? EmailSenderName { get ; set ; }
/// <summary>
/// The URl to use for generating links back to the dashboard.
/// </summary>
public Uri DashboardUrl { get ; set ; } = new Uri ( "https://localhost:3000" ) ;
/// <summary>
/// The URL to Helix Swarm server
/// </summary>
public Uri ? HelixSwarmServerUrl { get ; set ; }
/// <summary>
/// Username to use when communicating with Helix Swarm
/// </summary>
public string? HelixSwarmUsername { get ; set ; }
/// <summary>
/// Password to use when communicating with Helix Swarm
/// </summary>
public string? HelixSwarmPassword { get ; set ; }
/// <summary>
/// Projects in Helix Swarm to monitor, including the state of reviews in each project
/// Multiple projects can be specified, separated by comma.
/// </summary>
public string? HelixSwarmProjects { get ; set ; }
2021-05-11 13:12:19 -04:00
/// <summary>
/// The p4 bridge server
/// </summary>
public string? P4BridgeServer { get ; set ; }
/// <summary>
/// The p4 bridge service username
/// </summary>
public string? P4BridgeServiceUsername { get ; set ; }
/// <summary>
/// The p4 bridge service password
/// </summary>
public string? P4BridgeServicePassword { get ; set ; }
2021-06-16 09:20:08 -04:00
/// <summary>
/// Whether the p4 bridge service account can impersonate other users
/// </summary>
public bool P4BridgeCanImpersonate { get ; set ; } = false ;
2021-05-26 10:24:37 -04:00
/// <summary>
/// Whether to use the (temporary) P4 router while transitioning to p4 api
/// </summary>
public bool P4UseRouter { get ; set ; } = false ;
2021-04-29 15:10:34 -04:00
/// <summary>
/// Set the minimum size of the global thread pool
/// This value has been found in need of tweaking to avoid timeouts with the Redis client during bursts
/// of traffic. Default is 16 for .NET Core CLR. The correct value is dependent on the traffic the Horde Server
/// is receiving. For Epic's internal deployment, this is set to 40.
/// </summary>
public int? GlobalThreadPoolMinSize { get ; set ; }
2021-05-11 13:12:19 -04:00
/// <summary>
2021-05-17 14:31:23 -04:00
/// Path to the root config file
/// </summary>
public string? ConfigPath { get ; set ; }
/// <summary>
2021-05-11 13:12:19 -04:00
/// Settings for remote execution
/// </summary>
public RemoteExecSettings RemoteExecSettings { get ; set ; } = new RemoteExecSettings ( ) ;
2021-04-29 15:10:34 -04:00
/// <summary>
/// Lazily computed timezone value
/// </summary>
public TimeZoneInfo TimeZoneInfo
{
get
{
if ( CachedTimeZoneInfo = = null )
{
CachedTimeZoneInfo = ( ScheduleTimeZone = = null ) ? TimeZoneInfo . Local : TZConvert . GetTimeZoneInfo ( ScheduleTimeZone ) ;
}
return CachedTimeZoneInfo ;
}
}
private TimeZoneInfo ? CachedTimeZoneInfo ;
2021-06-04 10:45:13 -04:00
/// <summary>
/// Whether to open a browser on startup
/// </summary>
public bool OpenBrowser { get ; set ; } = false ;
2021-04-29 15:10:34 -04:00
/// <summary>
/// Check if Helix Swarm is enabled
/// </summary>
/// <returns>True if enabled</returns>
public bool IsSwarmEnabled ( )
{
return HelixSwarmServerUrl ! = null ;
}
/// <summary>
/// Parse the comma-separated string of Swarm projects into an actual array
/// </summary>
/// <returns>Array of Swarm projects</returns>
public string [ ] GetSwarmProjects ( )
{
return HelixSwarmProjects ! = null ? HelixSwarmProjects . Split ( "," ) : Array . Empty < string > ( ) ;
}
}
}