1316 lines
41 KiB
C#
1316 lines
41 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using AutomationTool;
|
|
using AutomationTool.DeviceReservation;
|
|
using UnrealBuildTool;
|
|
using System.Text.RegularExpressions;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using EpicGames.Core;
|
|
using System.Data;
|
|
using Gauntlet.Utils;
|
|
using System.Reflection;
|
|
|
|
namespace Gauntlet
|
|
{
|
|
/// <summary>
|
|
/// Device performance specification
|
|
/// </summary>
|
|
public enum EPerfSpec
|
|
{
|
|
Unspecified,
|
|
Minimum,
|
|
Recommended,
|
|
High
|
|
};
|
|
|
|
/// <summary>
|
|
/// Information that defines a device
|
|
/// </summary>
|
|
public class DeviceDefinition
|
|
{
|
|
public string Name { get; set; }
|
|
|
|
public string Address { get; set; }
|
|
|
|
public string DeviceData { get; set; }
|
|
|
|
// legacy - remove!
|
|
[JsonConverter(typeof(UnrealTargetPlatformConvertor))]
|
|
public UnrealTargetPlatform Type { get; set; }
|
|
|
|
[JsonConverter(typeof(UnrealTargetPlatformConvertor))]
|
|
public UnrealTargetPlatform? Platform { get; set; }
|
|
|
|
public EPerfSpec PerfSpec { get; set; }
|
|
|
|
public string Model { get; set; } = string.Empty;
|
|
|
|
public string Available { get; set; }
|
|
|
|
public bool RemoveOnShutdown { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format("{0} @ {1}. Platform={2} Model={3}", Name, Address, Platform, string.IsNullOrEmpty(Model) ? "Unspecified" : Model);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Device target constraint, can be expanded for specifying installed RAM, OS version, etc
|
|
/// </summary>
|
|
public class UnrealDeviceTargetConstraint : IEquatable<UnrealDeviceTargetConstraint>
|
|
{
|
|
public readonly UnrealTargetPlatform? Platform;
|
|
public readonly EPerfSpec PerfSpec;
|
|
public readonly string Model;
|
|
public readonly string DeviceName;
|
|
|
|
public UnrealDeviceTargetConstraint(UnrealTargetPlatform? Platform, EPerfSpec PerfSpec = EPerfSpec.Unspecified, string Model = null, string DeviceName = null)
|
|
{
|
|
this.Platform = Platform;
|
|
this.PerfSpec = PerfSpec;
|
|
this.Model = Model == null ? string.Empty : Model;
|
|
this.DeviceName = DeviceName == null ? string.Empty : DeviceName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests whether the constraint is identity, ie. unconstrained
|
|
/// </summary>
|
|
public bool IsIdentity()
|
|
{
|
|
return (PerfSpec == EPerfSpec.Unspecified) && (Model == string.Empty) && (DeviceName == string.Empty);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Check whether device satisfies the constraint
|
|
/// </summary>
|
|
public bool Check(ITargetDevice Device)
|
|
{
|
|
return Platform == Device.Platform && (IsIdentity() || this == DevicePool.Instance.GetConstraint(Device));
|
|
}
|
|
|
|
public bool Check(DeviceDefinition DeviceDef)
|
|
{
|
|
if (Platform != DeviceDef.Platform)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (IsIdentity())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool ModelMatch = Model == string.Empty ? true : Model.Equals(DeviceDef.Model, StringComparison.InvariantCultureIgnoreCase);
|
|
bool PerfMatch = (PerfSpec == EPerfSpec.Unspecified) ? true : PerfSpec == DeviceDef.PerfSpec;
|
|
bool NameMatch = DeviceName == string.Empty? true : DeviceName.Equals(DeviceDef.Name, StringComparison.InvariantCultureIgnoreCase);
|
|
return ModelMatch && PerfMatch && NameMatch;
|
|
}
|
|
|
|
public bool Equals(UnrealDeviceTargetConstraint Other)
|
|
{
|
|
if (ReferenceEquals(Other, null))
|
|
{
|
|
throw new AutomationException("Comparing null target constraint");
|
|
}
|
|
|
|
if (ReferenceEquals(this, Other))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return Other.Platform == Platform
|
|
&& Other.Model.Equals(Model, StringComparison.InvariantCultureIgnoreCase)
|
|
&& Other.DeviceName.Equals(DeviceName, StringComparison.InvariantCultureIgnoreCase)
|
|
&& Other.PerfSpec == PerfSpec;
|
|
}
|
|
|
|
public override bool Equals(object Obj)
|
|
{
|
|
if (ReferenceEquals(Obj, null))
|
|
{
|
|
throw new AutomationException("Comparing null target constraint");
|
|
}
|
|
|
|
if (ReferenceEquals(this, Obj))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (Obj.GetType() != typeof(UnrealDeviceTargetConstraint))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return Equals((UnrealDeviceTargetConstraint)Obj);
|
|
}
|
|
|
|
public static bool operator ==(UnrealDeviceTargetConstraint C1, UnrealDeviceTargetConstraint C2)
|
|
{
|
|
if (ReferenceEquals(C1, null) || ReferenceEquals(C2, null))
|
|
{
|
|
throw new AutomationException("Comparing null target constraint");
|
|
}
|
|
|
|
return C1.Equals(C2);
|
|
}
|
|
|
|
public static bool operator !=(UnrealDeviceTargetConstraint C1, UnrealDeviceTargetConstraint C2)
|
|
{
|
|
return !(C1 == C2);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
string Value = Platform.ToString();
|
|
if (PerfSpec != EPerfSpec.Unspecified)
|
|
{
|
|
Value = string.Format("{0}:{1}", Value, PerfSpec.ToString());
|
|
}
|
|
if(Model != string.Empty)
|
|
{
|
|
Value = string.Format("{0}:{1}", Value, Model);
|
|
}
|
|
if (DeviceName != string.Empty)
|
|
{
|
|
Value = string.Format("{0}:{1}", Value, DeviceName);
|
|
}
|
|
return Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Format to string the device constraint including its identify
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public string FormatWithIdentifier()
|
|
{
|
|
return string.Format("{0}:{1}", Platform, Model == string.Empty ? PerfSpec.ToString() : Model);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return ToString().GetHashCode();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Device marked as having a problem
|
|
/// </summary>
|
|
public struct ProblemDevice
|
|
{
|
|
public ProblemDevice(string Name, UnrealTargetPlatform Platform)
|
|
{
|
|
this.Name = Name;
|
|
this.Platform = Platform;
|
|
}
|
|
|
|
public string Name;
|
|
public UnrealTargetPlatform Platform;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Singleton class that's responsible for providing a list of devices and reserving them. Code should call
|
|
/// EnumerateDevices to build a list of desired devices, which must then be reserved by calling ReserveDevices.
|
|
/// Once done ReleaseDevices should be called.
|
|
///
|
|
/// These reservations exist at the process level and we rely on the device implementation to provide arbitrage
|
|
/// between difference processes and machines
|
|
///
|
|
/// </summary>
|
|
public class DevicePool : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Access to our singleton
|
|
/// </summary>
|
|
public static DevicePool Instance
|
|
{
|
|
get
|
|
{
|
|
if (_Instance == null || _Instance.bDisposed)
|
|
{
|
|
if (_Instance != null)
|
|
{
|
|
Log.Info("DevicePool has been disposed. Reinitilizing with a new instance.");
|
|
}
|
|
|
|
_Instance = new DevicePool();
|
|
}
|
|
|
|
return _Instance;
|
|
}
|
|
private set
|
|
{
|
|
_Instance = value;
|
|
}
|
|
}
|
|
|
|
public static bool SkipInstall;
|
|
|
|
public static bool FullClean;
|
|
|
|
public static bool DeviceReservationBlock;
|
|
|
|
public static bool? IsInstallStep;
|
|
|
|
/// <summary>
|
|
/// Active pool instance
|
|
/// </summary>
|
|
private static DevicePool _Instance;
|
|
|
|
/// <summary>
|
|
/// Object used for locking access to internal data
|
|
/// </summary>
|
|
private object LockObject = new object();
|
|
|
|
/// <summary>
|
|
/// List of all provisioned devices that are can be claimed
|
|
/// </summary>
|
|
private List<ITargetDevice> AvailableDevices = new List<ITargetDevice>();
|
|
|
|
/// <summary>
|
|
/// List of all provisioned devices that have been claimed
|
|
/// </summary>
|
|
private List<ITargetDevice> ClaimedDevices = new List<ITargetDevice>();
|
|
|
|
/// <summary>
|
|
/// List of all provisioned devices that were not provided by a reservation service
|
|
/// </summary>
|
|
private List<ITargetDevice> LocalDevices => AvailableDevices
|
|
.Union(ClaimedDevices)
|
|
.Except(ReservationServices.SelectMany(Service => Service.ReservedDevices))
|
|
.ToList();
|
|
|
|
/// <summary>
|
|
/// List of all enabled reservation services
|
|
/// </summary>
|
|
private List<IDeviceReservationService> ReservationServices = new List<IDeviceReservationService>();
|
|
|
|
/// <summary>
|
|
/// List of platforms we've had devices for
|
|
/// </summary>
|
|
private HashSet<UnrealTargetPlatform?> UsedPlatforms = new HashSet<UnrealTargetPlatform?>();
|
|
|
|
/// <summary>
|
|
/// List of device definitions that can be provisioned on demand
|
|
/// </summary>
|
|
private List<DeviceDefinition> UnprovisionedDevices = new List<DeviceDefinition>();
|
|
|
|
/// <summary>
|
|
/// List of definitions that failed to provision
|
|
/// </summary>
|
|
private List<DeviceDefinition> FailedProvisions = new List<DeviceDefinition>();
|
|
|
|
/// <summary>
|
|
/// Device constraints for performance profiles, etc
|
|
/// </summary>
|
|
private Dictionary<ITargetDevice, UnrealDeviceTargetConstraint> Constraints = new Dictionary<ITargetDevice, UnrealDeviceTargetConstraint>();
|
|
|
|
/// <summary>
|
|
/// Directory automation artifacts are saved to
|
|
/// </summary>
|
|
private string LocalTempDir;
|
|
|
|
/// <summary>
|
|
/// Protected constructor - code should use DevicePool.Instance
|
|
/// </summary>
|
|
protected DevicePool()
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
// Create two local devices by default for ease of running a client and server
|
|
AddLocalDevices(2);
|
|
InitializeReservationServices();
|
|
}
|
|
|
|
SkipInstall = Globals.Params.ParseParams("SkipInstall", "SkipCopy", "SkipDeploy");
|
|
FullClean = Globals.Params.ParseParam("FullClean");
|
|
DeviceReservationBlock = false;
|
|
}
|
|
|
|
#region IDisposable Support
|
|
bool bDisposed = false;
|
|
~DevicePool()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shutdown the pool and release all devices.
|
|
/// </summary>
|
|
public static void Shutdown()
|
|
{
|
|
Instance?.Dispose();
|
|
Instance = null;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform actual dispose behavior
|
|
/// </summary>
|
|
/// <param name="disposing"></param>
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (bDisposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lock (LockObject)
|
|
{
|
|
if (disposing)
|
|
{
|
|
// Dispose all local devices
|
|
foreach (ITargetDevice LocalDevice in LocalDevices)
|
|
{
|
|
DeviceConfigurationCache.Instance.RevertDeviceConfiguration(LocalDevice);
|
|
LocalDevice.Dispose();
|
|
}
|
|
|
|
// Dispose of any services. These dispose their associated devices
|
|
foreach (IDeviceReservationService Service in ReservationServices)
|
|
{
|
|
Service.Dispose();
|
|
}
|
|
|
|
// Cleanup things like platform sdk daemons
|
|
CleanupDevices(UsedPlatforms);
|
|
|
|
AvailableDevices.Clear();
|
|
ClaimedDevices.Clear();
|
|
ReservationServices.Clear();
|
|
UnprovisionedDevices.Clear();
|
|
}
|
|
}
|
|
|
|
bDisposed = true;
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Returns the number of available devices of the provided type. This includes unprovisioned devices but not reserved ones.
|
|
/// Note: unprovisioned devices are currently only returned when device is not constrained
|
|
/// </summary>
|
|
public int GetAvailableDeviceCount(UnrealDeviceTargetConstraint Constraint, Func<ITargetDevice, bool> Validate = null)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
return AvailableDevices.Where(D => Validate == null ? Constraint.Check(D) : Validate(D)).Count() +
|
|
UnprovisionedDevices.Where(D => Constraint.Check(D)).Count();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the number of available devices of the provided type. This includes unprovisioned devices but not reserved ones
|
|
/// Note: unprovisioned devices are currently only returned when device is not constrained
|
|
/// </summary>
|
|
public int GetTotalDeviceCount(UnrealDeviceTargetConstraint Constraint, Func<ITargetDevice, bool> Validate = null)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
return AvailableDevices.Union(ClaimedDevices).Where(D => Validate == null ? Constraint.Check(D) : Validate(D)).Count() +
|
|
UnprovisionedDevices.Where(D => Constraint.Check(D)).Count();
|
|
}
|
|
}
|
|
|
|
public UnrealDeviceTargetConstraint GetConstraint(ITargetDevice Device)
|
|
{
|
|
if (!Constraints.ContainsKey(Device))
|
|
{
|
|
throw new AutomationException("Device pool has no contstaint for {0} (device was likely released)", Device);
|
|
}
|
|
|
|
return Constraints[Device];
|
|
}
|
|
|
|
public void SetLocalOptions(string InLocalTemp, bool InUniqueTemps = false, string InDeviceURL = "")
|
|
{
|
|
LocalTempDir = InLocalTemp;
|
|
Legacy_DeviceURL = InDeviceURL;
|
|
}
|
|
|
|
public void AddLocalDevices(int MaxCount)
|
|
{
|
|
AddLocalDevices(MaxCount, BuildHostPlatform.Current.Platform);
|
|
}
|
|
|
|
public void AddLocalDevices(int MaxCount, UnrealTargetPlatform LocalPlatform)
|
|
{
|
|
int NumDevices = GetAvailableDeviceCount(new UnrealDeviceTargetConstraint(LocalPlatform));
|
|
|
|
for (int i = NumDevices; i < MaxCount; i++)
|
|
{
|
|
DeviceDefinition Def = new DeviceDefinition();
|
|
Def.Name = string.Format("LocalDevice{0}", i);
|
|
Def.Platform = LocalPlatform;
|
|
UnprovisionedDevices.Add(Def);
|
|
}
|
|
}
|
|
|
|
public void AddVirtualDevices(int MaxCount)
|
|
{
|
|
UnrealTargetPlatform LocalPlat = BuildHostPlatform.Current.Platform;
|
|
|
|
IEnumerable<IVirtualLocalDevice> VirtualDevices = Gauntlet.Utils.InterfaceHelpers.FindImplementations<IVirtualLocalDevice>()
|
|
.Where(F => F.CanRunVirtualFromPlatform(LocalPlat));
|
|
|
|
foreach (IVirtualLocalDevice Device in VirtualDevices)
|
|
{
|
|
UnrealTargetPlatform? DevicePlatform = Device.GetPlatform();
|
|
if (DevicePlatform != null)
|
|
{
|
|
int NumDevices = GetAvailableDeviceCount(new UnrealDeviceTargetConstraint(DevicePlatform));
|
|
for (int i = NumDevices; i < MaxCount; i++)
|
|
{
|
|
DeviceDefinition Def = new DeviceDefinition();
|
|
Def.Name = string.Format("Virtual{0}{1}", DevicePlatform.ToString(), i);
|
|
Def.Platform = DevicePlatform ?? BuildHostPlatform.Current.Platform;
|
|
UnprovisionedDevices.Add(Def);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Created a list of device definitions from the passed in reference. Needs work....
|
|
/// </summary>
|
|
/// <param name="DefaultPlatform"></param>
|
|
/// <param name="InputReference"></param>
|
|
/// <param name="ObeyConstraints"></param>
|
|
public void AddDevices(UnrealTargetPlatform DefaultPlatform, string InputReference, bool ObeyConstraints = true)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
List<ITargetDevice> NewDevices = new List<ITargetDevice>();
|
|
|
|
int SlashIndex = InputReference.IndexOf("\\") >= 0 ? InputReference.IndexOf("\\") : InputReference.IndexOf("/");
|
|
|
|
bool PossibleFileName = InputReference.IndexOfAny(Path.GetInvalidPathChars()) < 0 &&
|
|
(InputReference.IndexOf(":") == -1 || (InputReference.IndexOf(":") == SlashIndex - 1));
|
|
// Did they specify a file?
|
|
if (PossibleFileName && File.Exists(InputReference))
|
|
{
|
|
Log.Info("Adding devices from {Reference}", InputReference);
|
|
List<DeviceDefinition> DeviceDefinitions = JsonSerializer.Deserialize<List<DeviceDefinition>>(
|
|
File.ReadAllText(InputReference),
|
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
|
|
);
|
|
|
|
foreach (DeviceDefinition Def in DeviceDefinitions)
|
|
{
|
|
Log.Info("Adding {DeviceDetails}", Def);
|
|
|
|
// use Legacy field if it exists
|
|
if (Def.Platform == null)
|
|
{
|
|
Def.Platform = Def.Type;
|
|
}
|
|
|
|
// check for an availability constraint
|
|
if (string.IsNullOrEmpty(Def.Available) == false && ObeyConstraints)
|
|
{
|
|
// check whether disabled
|
|
if (String.Compare(Def.Available, "disabled", true) == 0)
|
|
{
|
|
Log.Info("Skipping {DeviceName} due to being disabled", Def.Name);
|
|
continue;
|
|
}
|
|
|
|
// availability is specified as a range, e.g 21:00-09:00.
|
|
Match M = Regex.Match(Def.Available, @"(\d{1,2}:\d\d)\s*-\s*(\d{1,2}:\d\d)");
|
|
|
|
if (M.Success)
|
|
{
|
|
DateTime From, To;
|
|
|
|
if (DateTime.TryParse(M.Groups[1].Value, out From) && DateTime.TryParse(M.Groups[2].Value, out To))
|
|
{
|
|
// these are just times so when parsed will have todays date. If the To time is less than
|
|
// From (22:00-06:00) time it spans midnight so move it to the next day
|
|
if (To < From)
|
|
{
|
|
To = To.AddDays(1);
|
|
}
|
|
|
|
// if From is in the future (e.g. it's 01:00 and range is 22:00-08:00) we may be in the previous days window,
|
|
// so move them both back a day
|
|
if (From > DateTime.Now)
|
|
{
|
|
From = From.AddDays(-1);
|
|
To = To.AddDays(-1);
|
|
}
|
|
|
|
if (DateTime.Now < From || DateTime.Now > To)
|
|
{
|
|
Log.Info("Skipping {DeviceName} due to availability constraint {Constraint}", Def.Name, Def.Available);
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log.Warning("Failed to parse availability {Constraint} for {DeviceName}", Def.Available, Def.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
Def.RemoveOnShutdown = true;
|
|
|
|
if (Def.Platform == null)
|
|
{
|
|
Def.Platform = DefaultPlatform;
|
|
}
|
|
|
|
UnprovisionedDevices.Add(Def);
|
|
}
|
|
|
|
// randomize devices so if there's a bad device st the start so we don't always hit it (or we do if its later)
|
|
UnprovisionedDevices = UnprovisionedDevices.OrderBy(D => Guid.NewGuid()).ToList();
|
|
}
|
|
else
|
|
{
|
|
if (string.IsNullOrEmpty(InputReference) == false)
|
|
{
|
|
string[] DevicesList = InputReference.Split(',');
|
|
|
|
foreach (string DeviceRef in DevicesList)
|
|
{
|
|
|
|
// check for <platform>:<address>:<port>|<model>. We pass address:port to device constructor
|
|
Match M = Regex.Match(DeviceRef, @"(.+?):(.+)");
|
|
|
|
UnrealTargetPlatform DevicePlatform = DefaultPlatform;
|
|
string DeviceAddress = DeviceRef;
|
|
string Model = string.Empty;
|
|
|
|
// When using device services, skip adding non-desktop local devices to pool if any of the services can support that platform
|
|
bool IsDesktop = DevicePlatform.IsInGroup(UnrealPlatformGroup.Desktop);
|
|
bool ReservationsEnabled = ReservationServices.Count > 0;
|
|
bool ServicesCanSupportThisPlatform = ReservationsEnabled
|
|
&& ReservationServices.Where(Service => Service.CanSupportDeviceConstraint(new UnrealDeviceTargetConstraint(DevicePlatform))).Any();
|
|
bool DeviceServiceEnabled = ServicesCanSupportThisPlatform;
|
|
|
|
if (!IsDesktop && DeviceRef.Equals("default", StringComparison.OrdinalIgnoreCase) && DeviceServiceEnabled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (M.Success)
|
|
{
|
|
if (!UnrealTargetPlatform.TryParse(M.Groups[1].ToString(), out DevicePlatform))
|
|
{
|
|
throw new AutomationException("platform {0} is not a recognized device type", M.Groups[1].ToString());
|
|
}
|
|
|
|
DeviceAddress = M.Groups[2].ToString();
|
|
|
|
// parse device model
|
|
if (DeviceAddress.Contains("|"))
|
|
{
|
|
string[] Components = DeviceAddress.Split(new char[] { '|' });
|
|
DeviceAddress = Components[0];
|
|
Model = Components[1];
|
|
}
|
|
|
|
}
|
|
|
|
Log.Info(KnownLogEvents.Gauntlet_DeviceEvent, "Added device {Platform}:{Address} to pool", DevicePlatform, DeviceAddress);
|
|
DeviceDefinition Def = new DeviceDefinition();
|
|
Def.Address = DeviceAddress;
|
|
Def.Name = DeviceAddress;
|
|
Def.Platform = DevicePlatform;
|
|
Def.Model = Model;
|
|
UnprovisionedDevices.Add(Def);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the list of devices to our internal availability list
|
|
/// </summary>
|
|
/// <param name="InDevices"></param>
|
|
public void RegisterDevices(IEnumerable<ITargetDevice> InDevices)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
AvailableDevices = AvailableDevices.Union(InDevices).ToList();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers the provided device for availability
|
|
/// </summary>
|
|
/// <param name="Device"></param>
|
|
/// <param name="Constraint"></param>
|
|
public void RegisterDevice(ITargetDevice Device, UnrealDeviceTargetConstraint Constraint = null)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
if (AvailableDevices.Contains(Device))
|
|
{
|
|
throw new Exception("Device already registered!");
|
|
}
|
|
|
|
Constraints[Device] = Constraint ?? new UnrealDeviceTargetConstraint(Device.Platform.Value);
|
|
UsedPlatforms.Add(Device.Platform);
|
|
AvailableDevices.Add(Device);
|
|
|
|
if (Log.IsVerbose)
|
|
{
|
|
Device.RunOptions = Device.RunOptions & ~CommandUtils.ERunOptions.NoLoggingOfRunCommand;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run the provided function across all our devices until it returns false. Devices are provisioned on demand (e.g turned from info into an ITargetDevice)
|
|
/// </summary>
|
|
public void EnumerateDevices(UnrealTargetPlatform Platform, Func<ITargetDevice, bool> Predicate)
|
|
{
|
|
EnumerateDevices(new UnrealDeviceTargetConstraint(Platform), Predicate);
|
|
}
|
|
|
|
public void EnumerateDevices(UnrealDeviceTargetConstraint Constraint, Func<ITargetDevice, bool> Predicate)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
List<ITargetDevice> Selection = new List<ITargetDevice>();
|
|
|
|
Log.Verbose(KnownLogEvents.Gauntlet_DeviceEvent, $"Enumerating devices for constraint {Constraint}");
|
|
|
|
Log.Verbose(KnownLogEvents.Gauntlet_DeviceEvent, $" Available devices:");
|
|
AvailableDevices.ForEach(D => Log.Verbose(KnownLogEvents.Gauntlet_DeviceEvent, $" {D.Platform}:{D.Name}"));
|
|
|
|
Log.Verbose(KnownLogEvents.Gauntlet_DeviceEvent, $" Unprovisioned devices:");
|
|
UnprovisionedDevices.ForEach(D => Log.Verbose(KnownLogEvents.Gauntlet_DeviceEvent, $" {D}"));
|
|
|
|
// randomize the order of all devices that are of this platform
|
|
var MatchingProvisionedDevices = AvailableDevices.Where(D => Constraint.Check(D)).ToList();
|
|
var MatchingUnprovisionedDevices = UnprovisionedDevices.Where(D => Constraint.Check(D)).ToList();
|
|
|
|
bool OutOfDevices = false;
|
|
bool ContinuePredicate = true;
|
|
|
|
do
|
|
{
|
|
// Go through all our provisioned devices to see if these fulfill the predicates
|
|
// requirements
|
|
|
|
ITargetDevice NextDevice = MatchingProvisionedDevices.FirstOrDefault();
|
|
|
|
while (NextDevice != null && ContinuePredicate)
|
|
{
|
|
Log.Verbose(KnownLogEvents.Gauntlet_DeviceEvent, "Checking {DeviceName} against predicate", NextDevice.Name);
|
|
MatchingProvisionedDevices.Remove(NextDevice);
|
|
ContinuePredicate = Predicate(NextDevice);
|
|
|
|
NextDevice = MatchingProvisionedDevices.FirstOrDefault();
|
|
}
|
|
|
|
if (ContinuePredicate)
|
|
{
|
|
// add more devices if possible
|
|
OutOfDevices = MatchingUnprovisionedDevices.Count() == 0;
|
|
|
|
DeviceDefinition NextDeviceDef = MatchingUnprovisionedDevices.FirstOrDefault();
|
|
|
|
if (NextDeviceDef != null)
|
|
{
|
|
Log.Verbose(KnownLogEvents.Gauntlet_DeviceEvent, "Provisioning device {DeviceName} for the pool", NextDeviceDef.Name);
|
|
|
|
// try to create a device. This can fail, but if so we'll just end up back here
|
|
// on the next iteration
|
|
ITargetDevice NewDevice = CreateAndRegisterDeviceFromDefinition(NextDeviceDef, Constraint);
|
|
|
|
MatchingUnprovisionedDevices.Remove(NextDeviceDef);
|
|
UnprovisionedDevices.Remove(NextDeviceDef);
|
|
|
|
if (NewDevice != null)
|
|
{
|
|
MatchingProvisionedDevices.Add(NewDevice);
|
|
Log.Verbose(KnownLogEvents.Gauntlet_DeviceEvent, "Added device {DeviceName} to pool", NewDevice.Name);
|
|
}
|
|
else
|
|
{
|
|
Log.Info(KnownLogEvents.Gauntlet_DeviceEvent, "Failed to provision {DeviceName}", NextDeviceDef.Name);
|
|
// track this
|
|
if (FailedProvisions.Contains(NextDeviceDef) == false)
|
|
{
|
|
FailedProvisions.Add(NextDeviceDef);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log.Info(KnownLogEvents.Gauntlet_DeviceEvent, "Pool ran out of devices of type {Constraint}!", Constraint);
|
|
OutOfDevices = true;
|
|
}
|
|
}
|
|
} while (OutOfDevices == false && ContinuePredicate);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Claim all devices in the provided list. Once reserved a device will not be seen by any code that
|
|
/// calls EnumerateDevices
|
|
/// </summary>
|
|
/// <param name="DeviceList"></param>
|
|
/// <returns></returns>
|
|
public bool ClaimDevices(IEnumerable<ITargetDevice> DeviceList)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
// can reserve if not reserved...
|
|
if (ClaimedDevices.Intersect(DeviceList).Count() > 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// remove these devices from the available list
|
|
AvailableDevices = AvailableDevices.Where(D => DeviceList.Contains(D) == false).ToList();
|
|
|
|
ClaimedDevices.AddRange(DeviceList);
|
|
DeviceList.All(D => UsedPlatforms.Add(D.Platform));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool ReserveDevicesFromService(Dictionary<UnrealDeviceTargetConstraint, int> DeviceTypes)
|
|
{
|
|
// Flatten the required devices into an enumerable
|
|
List<UnrealDeviceTargetConstraint> RequiredDeviceConstraints = DeviceTypes
|
|
.SelectMany(KVP => Enumerable.Repeat(KVP.Key, KVP.Value))
|
|
.ToList();
|
|
|
|
// Project each constraint into a supported service
|
|
Dictionary<IDeviceReservationService, List<UnrealDeviceTargetConstraint>> ServiceMapping = new();
|
|
foreach (UnrealDeviceTargetConstraint Constraint in RequiredDeviceConstraints)
|
|
{
|
|
foreach (IDeviceReservationService Service in ReservationServices)
|
|
{
|
|
if (Service.CanSupportDeviceConstraint(Constraint))
|
|
{
|
|
if (ServiceMapping.ContainsKey(Service))
|
|
{
|
|
ServiceMapping[Service].Add(Constraint);
|
|
}
|
|
else
|
|
{
|
|
ServiceMapping.Add(Service, new List<UnrealDeviceTargetConstraint> { Constraint });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine if any constraints do not have a service that can supply an appropriate device
|
|
List<UnrealDeviceTargetConstraint> DevicesWithService = ServiceMapping.Values.SelectMany(Constraint => Constraint).ToList();
|
|
List<UnrealDeviceTargetConstraint> DevicesLackingService = RequiredDeviceConstraints.Except(DevicesWithService).ToList();
|
|
|
|
if (DevicesLackingService.Any())
|
|
{
|
|
string Message = "No enabled reservation services were capable of supporting the following constraints:\n";
|
|
Message += string.Join("\n\t", DevicesLackingService);
|
|
Message += "\nThe following reservation services were available:\n";
|
|
Message += string.Join("\n\t", ReservationServices.Select(Service => Service.GetType().Name));
|
|
Message += "\nEnsure your environment is configured to enable any necessary services.";
|
|
throw new AutomationException(Message);
|
|
}
|
|
|
|
foreach (var ReservationPair in ServiceMapping)
|
|
{
|
|
if (!ReservationPair.Key.ReserveDevicesFromService(ReservationPair.Value))
|
|
{
|
|
Log.Info("Failed to reserve all devices from service. See above log for details");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Report target device issue to service with given error message
|
|
/// </summary>
|
|
public void ReportDeviceError(ITargetDevice Device, string ErrorMessage)
|
|
{
|
|
if (TryGetDevicesReservationService(Device, out IDeviceReservationService Service))
|
|
{
|
|
Service.ReportDeviceError(Device.Name, ErrorMessage);
|
|
}
|
|
else
|
|
{
|
|
Log.Verbose("{Device} was not reserved by a service! Ignoring error report.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Release all devices in the provided list from our reserved list
|
|
/// </summary>
|
|
/// <param name="DeviceList"></param>
|
|
public void ReleaseDevices(IEnumerable<ITargetDevice> DeviceList)
|
|
{
|
|
if (DeviceList == null || !DeviceList.Any())
|
|
{
|
|
return;
|
|
}
|
|
|
|
lock (LockObject)
|
|
{
|
|
List<ITargetDevice> KeepList = new List<ITargetDevice>();
|
|
foreach (ITargetDevice Device in DeviceList)
|
|
{
|
|
if (LocalDevices.Contains(Device))
|
|
{
|
|
DeviceConfigurationCache.Instance.RevertDeviceConfiguration(Device);
|
|
KeepList.Add(Device);
|
|
}
|
|
else if (TryGetDevicesReservationService(Device, out IDeviceReservationService Service))
|
|
{
|
|
Service.ReleaseDevices([Device]);
|
|
}
|
|
else
|
|
{
|
|
Log.Info("Attempted to release an unregistered device. Ensure the device is registered with DevicePool.Instance.RegisterDevice first.");
|
|
}
|
|
}
|
|
|
|
// Remove any provisioned devices
|
|
AvailableDevices = AvailableDevices.Where(Device => !DeviceList.Contains(Device)).ToList();
|
|
ClaimedDevices = ClaimedDevices.Where(Device => !DeviceList.Contains(Device)).ToList();
|
|
|
|
// But keep local devices available
|
|
KeepList.ForEach(D => { AvailableDevices.Remove(D); AvailableDevices.Insert(0, D); });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether device pool can accommodate requirements, optionally add service devices to meet demand
|
|
/// </summary>
|
|
public bool CheckAvailableDevices(Dictionary<UnrealDeviceTargetConstraint, int> RequiredDevices, IReadOnlyCollection<ProblemDevice> ProblemDevices = null, bool UseServiceDevices = true)
|
|
{
|
|
Dictionary<UnrealDeviceTargetConstraint, int> AvailableDeviceTypes = new Dictionary<UnrealDeviceTargetConstraint, int>();
|
|
Dictionary<UnrealDeviceTargetConstraint, int> TotalDeviceTypes = new Dictionary<UnrealDeviceTargetConstraint, int>();
|
|
|
|
// Do these "how many available" checks every time because the DevicePool provisions on demand so while it may think it has N machines,
|
|
// some of them may fail to be provisioned and we could end up with none!
|
|
|
|
// See how many of these types are in device pool (mostly to supply informative info if we can't meet these)
|
|
|
|
foreach (var PlatformRequirement in RequiredDevices)
|
|
{
|
|
UnrealDeviceTargetConstraint Constraint = PlatformRequirement.Key;
|
|
|
|
Func<ITargetDevice, bool> Validate = (ITargetDevice Device) =>
|
|
{
|
|
if (!Constraint.Check(Device))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ProblemDevices == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
foreach (ProblemDevice PDevice in ProblemDevices)
|
|
{
|
|
if (PDevice.Platform == Device.Platform && PDevice.Name == Device.Name)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
AvailableDeviceTypes[Constraint] = DevicePool.Instance.GetAvailableDeviceCount(Constraint, Validate);
|
|
TotalDeviceTypes[Constraint] = DevicePool.Instance.GetTotalDeviceCount(Constraint, Validate);
|
|
|
|
|
|
Log.Verbose(KnownLogEvents.Gauntlet_DeviceEvent, "{Constraint}: {Platform} devices required. Total:{Total}, Available:{Available}",
|
|
Constraint, PlatformRequirement.Value,
|
|
TotalDeviceTypes[PlatformRequirement.Key], AvailableDeviceTypes[PlatformRequirement.Key]);
|
|
}
|
|
|
|
// get a list of any platforms where we don't have enough
|
|
var TooFewTotalDevices = RequiredDevices.Where(KP => TotalDeviceTypes[KP.Key] < RequiredDevices[KP.Key]).Select(KP => KP.Key);
|
|
var TooFewCurrentDevices = RequiredDevices.Where(KP => AvailableDeviceTypes[KP.Key] < RequiredDevices[KP.Key]).Select(KP => KP.Key);
|
|
|
|
var Devices = TooFewTotalDevices.Concat(TooFewCurrentDevices);
|
|
|
|
// Request devices from the service if we need them
|
|
if (UseServiceDevices && (TooFewTotalDevices.Count() > 0 || TooFewCurrentDevices.Count() > 0))
|
|
{
|
|
Dictionary<UnrealDeviceTargetConstraint, int> DeviceCounts = new Dictionary<UnrealDeviceTargetConstraint, int>();
|
|
Devices.ToList().ForEach(Platform => DeviceCounts[Platform] = RequiredDevices[Platform]);
|
|
|
|
if (!ReserveDevicesFromService(DeviceCounts))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we can't ever run then throw an exception
|
|
if (TooFewTotalDevices.Count() > 0)
|
|
{
|
|
var MissingDeviceStrings = TooFewTotalDevices.Select(D => string.Format("Not enough devices of type {0} exist for test. ({1} required, {2} available)", D, RequiredDevices[D], AvailableDeviceTypes[D]));
|
|
Log.Error(KnownLogEvents.Gauntlet_DeviceEvent, string.Join("\n", MissingDeviceStrings));
|
|
throw new AutomationException("Not enough devices available");
|
|
}
|
|
|
|
// if we can't run now then return false
|
|
if (TooFewCurrentDevices.Count() > 0)
|
|
{
|
|
var MissingDeviceStrings = TooFewCurrentDevices.Select(D => string.Format("Not enough devices of type {0} available for test. ({1} required, {2} available)", D, RequiredDevices[D], AvailableDeviceTypes[D]));
|
|
Log.Verbose(KnownLogEvents.Gauntlet_DeviceEvent, string.Join("\n", MissingDeviceStrings));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Created and registered a device from the provided definition
|
|
/// </summary>
|
|
/// <param name="Def"></param>
|
|
/// <returns></returns>
|
|
public ITargetDevice CreateAndRegisterDeviceFromDefinition(DeviceDefinition Def, UnrealDeviceTargetConstraint Constraint,
|
|
IDeviceReservationService Service = null, IDeviceFactory Factory = null)
|
|
{
|
|
ITargetDevice NewDevice = null;
|
|
|
|
if (Factory == null)
|
|
{
|
|
Factory = InterfaceHelpers.FindImplementations<IDeviceFactory>()
|
|
.Where(F => F.CanSupportPlatform(Def.Platform))
|
|
.FirstOrDefault();
|
|
}
|
|
|
|
if (Factory == null)
|
|
{
|
|
throw new AutomationException("No IDeviceFactory implementation that supports {0}", Def.Platform);
|
|
}
|
|
|
|
try
|
|
{
|
|
bool IsDesktop = Def.Platform != null && UnrealBuildTool.Utils.GetPlatformsInClass(UnrealPlatformClass.Desktop).Contains(Def.Platform!.Value);
|
|
|
|
string ClientTempDir = GetCleanCachePath(Def);
|
|
|
|
if (IsDesktop)
|
|
{
|
|
NewDevice = Factory.CreateDevice(Def.Name, ClientTempDir);
|
|
}
|
|
else
|
|
{
|
|
NewDevice = Factory.CreateDevice(Def.Address, ClientTempDir, Def.DeviceData);
|
|
}
|
|
|
|
if (NewDevice == null)
|
|
{
|
|
Log.Warning(KnownLogEvents.Gauntlet_DeviceEvent, "Failed to create device {DeviceName}. Device could not be connected.", Def.Name);
|
|
return null;
|
|
}
|
|
|
|
if (NewDevice.IsAvailable == false)
|
|
{
|
|
Log.Info(KnownLogEvents.Gauntlet_DeviceEvent, "Assigned device {DeviceName} reports unavailable. Requesting a forced disconnect", NewDevice.Name);
|
|
NewDevice.Disconnect(true);
|
|
|
|
if (NewDevice.IsAvailable == false)
|
|
{
|
|
Log.Info(KnownLogEvents.Gauntlet_DeviceEvent, "Assigned device {DeviceName} still unavailable. Requesting a reboot", NewDevice.Name);
|
|
NewDevice.Reboot();
|
|
}
|
|
}
|
|
|
|
// Now validate if the kit meets the necessary requirements
|
|
string Message = string.Empty;
|
|
if (!TryValidateDeviceRequirements(NewDevice, ref Message))
|
|
{
|
|
if (!string.IsNullOrEmpty(Message))
|
|
{
|
|
if (Service != null)
|
|
{
|
|
Service.ReportDeviceError(Def.Name, Message);
|
|
}
|
|
}
|
|
Log.Info("\nSkipping device.");
|
|
return null;
|
|
}
|
|
|
|
lock (LockObject)
|
|
{
|
|
if (NewDevice != null)
|
|
{
|
|
RegisterDevice(NewDevice, Constraint);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception Ex)
|
|
{
|
|
string WarningMessage = $"Failed to create device {Def.Name}. {Ex.Message}\n{Ex.StackTrace}";
|
|
if (Ex is DeviceException)
|
|
{
|
|
if (Service != null)
|
|
{
|
|
Service.ReportDeviceError(Def.Name, WarningMessage);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log.Warning(KnownLogEvents.Gauntlet_DeviceEvent, WarningMessage);
|
|
}
|
|
}
|
|
|
|
return NewDevice;
|
|
}
|
|
|
|
private void InitializeReservationServices()
|
|
{
|
|
// In order to avoid constructing an instance of the service to determine if it's enabled on any number of types we have to do a bit of generics reflection...
|
|
foreach (Type ServiceType in InterfaceHelpers.FindTypes<IDeviceReservationService>(true, true))
|
|
{
|
|
MethodInfo Function = typeof(DevicePool).GetMethod("IsServiceEnabled", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
MethodInfo Generic = Function.MakeGenericMethod(ServiceType);
|
|
object ReturnValue = Generic.Invoke(this, null);
|
|
bool IsEnabled = (bool)ReturnValue;
|
|
if (IsEnabled)
|
|
{
|
|
ReservationServices.Add(Activator.CreateInstance(ServiceType) as IDeviceReservationService);
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsServiceEnabled<T>() where T : IDeviceReservationService
|
|
{
|
|
return T.Enabled;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies if the provided TargetDevice meets requirements such as firmware, login status, settings, etc.
|
|
/// </summary>
|
|
/// <param name="Device">The device to validate</param>
|
|
/// <param name="Message">The output message when failing to validate</param>
|
|
/// <returns>True if the device matches the required specifications</returns>
|
|
protected bool TryValidateDeviceRequirements(ITargetDevice Device, ref string Message)
|
|
{
|
|
IEnumerable<IDeviceValidator> Validators = InterfaceHelpers.FindImplementations<IDeviceValidator>(true).Where(Validator => Validator.bEnabled);
|
|
if (!Validators.Any())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool bInitiallyConnected = Device.IsConnected;
|
|
|
|
Log.Info(KnownLogEvents.Gauntlet_DeviceEvent, "\nValidating requirements for {DeviceName}...", Device.Name);
|
|
bool bSucceeded = true;
|
|
List<string> MessageAggregate = new();
|
|
|
|
foreach (IDeviceValidator Validator in Validators)
|
|
{
|
|
Log.Info("\nStarting validation for {Validator}", Validator);
|
|
|
|
string ValidationMessage = string.Empty;
|
|
if(!Validator.TryValidateDevice(Device, ref ValidationMessage))
|
|
{
|
|
Log.Info("Failed!");
|
|
bSucceeded = false;
|
|
if (!string.IsNullOrEmpty(ValidationMessage))
|
|
{
|
|
MessageAggregate.Add(ValidationMessage);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log.Info("Success!");
|
|
}
|
|
}
|
|
|
|
if(!bSucceeded)
|
|
{
|
|
Message = string.Join("\n", MessageAggregate.Where(M => !string.IsNullOrEmpty(M)));
|
|
Log.Info(KnownLogEvents.Gauntlet_DeviceEvent, "\nFailed to validate requirements on device {DeviceName}", Device.Name);
|
|
}
|
|
else
|
|
{
|
|
Log.Info(KnownLogEvents.Gauntlet_DeviceEvent, "\nAll validators passed, selecting device {DeviceName}\n", Device.Name);
|
|
}
|
|
|
|
// Most validators require establishing a connection to the device.
|
|
// If we weren't originally connected, disconnect so the initial connection state can be cached during device reservation
|
|
if(!bInitiallyConnected && Device.IsConnected)
|
|
{
|
|
Device.Disconnect();
|
|
}
|
|
|
|
return bSucceeded;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Explicitly release all device reservations
|
|
/// </summary>
|
|
private void ReleaseReservations()
|
|
{
|
|
foreach (IDeviceReservationService ReservationService in ReservationServices)
|
|
{
|
|
foreach (ITargetDevice Device in ReservationService.ReservedDevices)
|
|
{
|
|
AvailableDevices.Remove(Device);
|
|
ClaimedDevices.Remove(Device);
|
|
}
|
|
|
|
ReservationService.ReleaseDevices(ReservationService.ReservedDevices);
|
|
}
|
|
}
|
|
|
|
private void CleanupDevices(IEnumerable<UnrealTargetPlatform?> Platforms)
|
|
{
|
|
IEnumerable<IDeviceService> DeviceServices = InterfaceHelpers.FindImplementations<IDeviceService>();
|
|
if (DeviceServices.Any())
|
|
{
|
|
foreach (UnrealTargetPlatform? Platform in Platforms)
|
|
{
|
|
IDeviceService DeviceService = DeviceServices.Where(D => D.CanSupportPlatform(Platform)).FirstOrDefault();
|
|
if (DeviceService != null)
|
|
{
|
|
DeviceService.CleanupDevices();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool TryGetDevicesReservationService(ITargetDevice Device, out IDeviceReservationService OutService)
|
|
{
|
|
OutService = null;
|
|
|
|
foreach (IDeviceReservationService Service in ReservationServices)
|
|
{
|
|
if (Service.ReservedDevices.Contains(Device))
|
|
{
|
|
OutService = Service;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construct a path to hold cache files and make sure it's properly cleaned
|
|
/// </summary>
|
|
private string GetCleanCachePath(DeviceDefinition InDeviceDefiniton)
|
|
{
|
|
// Give the desktop platform a temp folder with its name under the device cache
|
|
string DeviceCache = Path.Combine(LocalTempDir, "DeviceCache");
|
|
string PlatformCache = Path.Combine(DeviceCache, InDeviceDefiniton.Platform.ToString());
|
|
string ClientCache = Path.Combine(PlatformCache, InDeviceDefiniton.Name);
|
|
|
|
// On Desktops, builds are installed in the device cache.
|
|
// When using device reservation blocks, we don't want to fully clean the cache and lose previously installed builds.
|
|
// If bRetainBuilds evaluates to true, it means we are in the second step or beyond in a device reservation block.
|
|
// In this case we'll just delete the left over UserDir which should already have been emptied by UnrealSession.
|
|
bool bRetainCache = Globals.Params.ParseParam("RetainCache");
|
|
bool bRetainBuilds = SkipInstall && !FullClean;
|
|
|
|
if(bRetainBuilds || bRetainCache)
|
|
{
|
|
Log.Info("Retaining build cache for device reservation block");
|
|
|
|
DirectoryInfo UserDirectory = new(Path.Combine(ClientCache, "UserDir"));
|
|
if(UserDirectory.Exists)
|
|
{
|
|
try
|
|
{
|
|
Log.Info("Cleaning stale user directory...");
|
|
SystemHelpers.Delete(UserDirectory, true, true);
|
|
}
|
|
catch(Exception Ex)
|
|
{
|
|
throw new AutomationException("Failed to clean user directory {0}. This could result in improper artifact reporting. {1}", UserDirectory, Ex);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int CleanAttempts = 0;
|
|
|
|
while (Directory.Exists(ClientCache))
|
|
{
|
|
DirectoryInfo ClientCacheDirectory = new(ClientCache);
|
|
|
|
try
|
|
{
|
|
Log.Info("Cleaning stale client device cache...");
|
|
SystemHelpers.Delete(ClientCacheDirectory, true, true);
|
|
}
|
|
catch (Exception Ex)
|
|
{
|
|
// If we fail to acquire the default client cache while using device reservation blocks,
|
|
// we can't ensure future tests will have their cache directories mapped to the correct build location
|
|
if (DeviceReservationBlock)
|
|
{
|
|
throw new AutomationException("Failed to clean default client device cache {0}. {1}", ClientCache, Ex);
|
|
}
|
|
|
|
// When not using device reservation blocks, we can just create a newly indexed directory for the client cache
|
|
else
|
|
{
|
|
string Warning = "Failed to clean client device cache {Folder}. A newly indexed directory will be created instead. {Message}";
|
|
Log.Warning(KnownLogEvents.Gauntlet_DeviceEvent, Warning, ClientCache, Ex.Message);
|
|
|
|
ClientCache = Path.Combine(PlatformCache, $"{InDeviceDefiniton.Name}_{++CleanAttempts}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// create this path
|
|
Log.Info("Client device cache set to {Directory}", ClientCache);
|
|
Directory.CreateDirectory(ClientCache);
|
|
}
|
|
|
|
return ClientCache;
|
|
}
|
|
|
|
#region Legacy Implementation
|
|
|
|
[Obsolete("Will be removed in a future release")]
|
|
public string DeviceURL
|
|
{
|
|
get { return Legacy_DeviceURL; }
|
|
set { Legacy_DeviceURL = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Device reservation service URL
|
|
/// </summary>
|
|
private string Legacy_DeviceURL;
|
|
|
|
[Obsolete("Misnomer, use ClaimDevices instead. Will be removed in a future release")]
|
|
public void ReserveDevices(IEnumerable<ITargetDevice> Devices)
|
|
{
|
|
ClaimDevices(Devices);
|
|
}
|
|
|
|
[Obsolete("Will be removed in a future release. Use the single parameter overload instead.")]
|
|
public bool ReserveDevicesFromService(string DeviceURL, Dictionary<UnrealDeviceTargetConstraint, int> DeviceTypes)
|
|
{
|
|
return ReserveDevicesFromService(DeviceTypes);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |